forked from public/foundryvtt-reve-de-dragon
		
	
		
			
				
	
	
		
			3354 lines
		
	
	
		
			123 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			3354 lines
		
	
	
		
			123 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
| import { RdDUtility } from "./rdd-utility.js";
 | |
| import { TMRUtility } from "./tmr-utility.js";
 | |
| import { RdDRollDialogEthylisme } from "./rdd-roll-ethylisme.js";
 | |
| import { RdDRoll } from "./rdd-roll.js";
 | |
| import { RdDTMRDialog } from "./rdd-tmr-dialog.js";
 | |
| import { Misc } from "./misc.js";
 | |
| import { RdDResolutionTable } from "./rdd-resolution-table.js";
 | |
| import { RdDDice } from "./rdd-dice.js";
 | |
| import { RdDRollTables } from "./rdd-rolltables.js";
 | |
| import { ChatUtility } from "./chat-utility.js";
 | |
| import { Grammar } from "./grammar.js";
 | |
| import { RdDAlchimie } from "./rdd-alchimie.js";
 | |
| import { STATUSES } from "./settings/status-effects.js";
 | |
| import { ReglesOptionnelles } from "./settings/regles-optionnelles.js";
 | |
| import { EffetsDraconiques } from "./tmr/effets-draconiques.js";
 | |
| import { Draconique } from "./tmr/draconique.js";
 | |
| import { CARACS, LIST_CARAC_PERSONNAGE, RdDCarac } from "./rdd-carac.js";
 | |
| import { DialogConsommer } from "./dialog-item-consommer.js";
 | |
| import { DialogFabriquerPotion } from "./dialog-fabriquer-potion.js";
 | |
| import { RollDataAjustements } from "./rolldata-ajustements-v1.js";
 | |
| import { RdDPossession } from "./rdd-possession.js";
 | |
| import { ACTOR_TYPES, renderTemplate, SHOW_DICE, SYSTEM_RDD, SYSTEM_SOCKET_ID } from "./constants.js";
 | |
| import { RdDConfirm } from "./rdd-confirm.js";
 | |
| import { DialogRepos } from "./sommeil/dialog-repos.js";
 | |
| import { RdDBaseActor } from "./actor/base-actor.js";
 | |
| import { RdDTimestamp } from "./time/rdd-timestamp.js";
 | |
| import { AppAstrologie } from "./sommeil/app-astrologie.js";
 | |
| import { RdDEmpoignade } from "./rdd-empoignade.js";
 | |
| import { ExperienceLog, XP_TOPIC } from "./actor/experience-log.js";
 | |
| import { ITEM_TYPES } from "./constants.js";
 | |
| import { RdDBaseActorSang } from "./actor/base-actor-sang.js";
 | |
| import { RdDCoeur } from "./coeur/rdd-coeur.js";
 | |
| import { DialogChoixXpCarac } from "./dialog-choix-xp-carac.js";
 | |
| 
 | |
| import { ATTAQUE_TYPE, RdDItemArme } from "./item/arme.js";
 | |
| import { RdDItemBlessure } from "./item/blessure.js";
 | |
| import { RdDItemTete } from "./item/tete.js";
 | |
| import { RdDItemSort } from "./item-sort.js";
 | |
| import { RdDItemRace } from "./item/race.js";
 | |
| import { RdDItemCompetence } from "./item-competence.js";
 | |
| import { RdDItemSigneDraconique } from "./item/signedraconique.js";
 | |
| import { RdDRencontre } from "./item/rencontre.js";
 | |
| import { DialogSelect } from "./dialog-select.js";
 | |
| import { PAS_DE_DRACONIC, POSSESSION_SANS_DRACONIC } from "./item/base-items.js";
 | |
| 
 | |
| import { RdDRollResult } from "./rdd-roll-result.js";
 | |
| import { RdDInitiative } from "./initiative.mjs";
 | |
| import RollDialog from "./roll/roll-dialog.mjs";
 | |
| import { OptionsAvancees, ROLL_DIALOG_V2, ROLL_DIALOG_V2_TEST } from "./settings/options-avancees.js";
 | |
| import { ROLL_TYPE_JEU, ROLL_TYPE_MEDITATION, ROLL_TYPE_SORT } from "./roll/roll-constants.mjs";
 | |
| import { PART_TACHE } from "./roll/roll-part-tache.mjs";
 | |
| import { PART_COMP } from "./roll/roll-part-comp.mjs";
 | |
| import { PART_OEUVRE } from "./roll/roll-part-oeuvre.mjs";
 | |
| import { PART_CUISINE } from "./roll/roll-part-cuisine.mjs";
 | |
| import { PART_SORT } from "./roll/roll-part-sort.mjs";
 | |
| 
 | |
| export const MAINS_DIRECTRICES = ['Droitier', 'Gaucher', 'Ambidextre']
 | |
| 
 | |
| 
 | |
| /* -------------------------------------------- */
 | |
| /**
 | |
|  * Extend the base Actor entity by defining a custom roll data structure which is ideal for the Simple system.
 | |
|  * @extends {Actor}
 | |
|  */
 | |
| export class RdDActor extends RdDBaseActorSang {
 | |
| 
 | |
|   /* -------------------------------------------- */
 | |
|   /**
 | |
|    * Prepare Character type specific data
 | |
|    */
 | |
|   prepareActorData() {
 | |
| 
 | |
|     RdDItemRace.applyRacialLimits(this)
 | |
| 
 | |
|     this.system.carac.melee.value = Math.floor((this.getForce() + parseInt(this.system.carac.agilite.value)) / 2);
 | |
|     this.system.carac.tir.value = Math.floor((parseInt(this.system.carac.vue.value) + parseInt(this.system.carac.dexterite.value)) / 2);
 | |
|     this.system.carac.lancer.value = Math.floor((parseInt(this.system.carac.tir.value) + parseInt(this.system.carac.force.value)) / 2);
 | |
|     this.system.carac.derobee.value = Math.floor(parseInt(((21 - this.system.carac.taille.value)) + parseInt(this.system.carac.agilite.value)) / 2);
 | |
| 
 | |
|     super.prepareActorData()
 | |
| 
 | |
|     this.system.attributs.sconst.value = RdDCarac.calculSConst(this.getConstitution())
 | |
|     this.system.attributs.sust.value = RdDCarac.getCaracDerivee(this.getTaille()).sust
 | |
| 
 | |
|     this.system.sante.fatigue.max = this.getFatigueMax()
 | |
|     this.system.sante.fatigue.value = Math.min(this.system.sante.fatigue.value, this.system.sante.fatigue.max);
 | |
| 
 | |
|     //Compteurs
 | |
|     this.system.reve.reve.max = 3 * this.getReve()
 | |
|     this.system.compteurs.chance.max = this.getChance()
 | |
| 
 | |
|     this.system.attributs.hautrevant.value = this.itemTypes[ITEM_TYPES.tete].find(it => RdDItemTete.isDonDeHautReve(it))
 | |
|       ? "Haut rêvant"
 | |
|       : "";
 | |
|   }
 | |
| 
 | |
|   /* -------------------------------------------- */
 | |
|   canReceive(item) {
 | |
|     return ![ITEM_TYPES.competencecreature, ITEM_TYPES.tarot, ITEM_TYPES.service].includes(item.type)
 | |
|   }
 | |
| 
 | |
|   isPersonnageJoueur() {
 | |
|     return this.hasPlayerOwner && this.prototypeToken.actorLink
 | |
|   }
 | |
| 
 | |
|   isPersonnage() { return true }
 | |
|   isFeminin() { return this.system.sexe.length > 0 && this.system.sexe.charAt(0).toLowerCase() == 'f' }
 | |
| 
 | |
|   isHautRevant() { return this.system.attributs.hautrevant.value != "" }
 | |
| 
 | |
|   /* -------------------------------------------- */
 | |
|   getAgilite() { return Misc.toInt(this.system.carac.agilite?.value ?? 0) }
 | |
|   getChance() { return Misc.toInt(this.system.carac.chance?.value ?? 0) }
 | |
| 
 | |
|   getReveActuel() { return Misc.toInt(this.system.reve?.reve?.value) ?? this.carac.reve.value ?? 0 }
 | |
|   getChanceActuel() {
 | |
|     return Number.isNumeric(this.system.compteurs.chance.value) ?
 | |
|       Misc.toInt(this.system.compteurs.chance.value) : this.getChance()
 | |
|   }
 | |
|   getMoralTotal() { return parseInt(this.system.compteurs.moral?.value ?? 0) }
 | |
| 
 | |
|   getEnduranceMax() { return Math.max(1, Math.max(this.getTaille() + this.getConstitution(), this.getVieMax() + this.getVolonte())) }
 | |
| 
 | |
|   /* -------------------------------------------- */
 | |
|   getEtatGeneral(options = { ethylisme: false }) {
 | |
|     const etatGeneral = this.system.compteurs.etat?.value ?? 0
 | |
|     // Pour les jets d'Ethylisme, on retire le malus d'éthylisme (p.162)
 | |
|     if (options.ethylisme) {
 | |
|       return etatGeneral - this.malusEthylisme()
 | |
|     }
 | |
|     return etatGeneral
 | |
|   }
 | |
| 
 | |
|   /* -------------------------------------------- */
 | |
|   getActivePoisons() {
 | |
|     return foundry.utils.duplicate(this.items.filter(item => item.type == 'poison' && item.system.active))
 | |
|   }
 | |
| 
 | |
|   /* -------------------------------------------- */
 | |
|   getMalusArmure() {
 | |
|     return this.itemTypes[ITEM_TYPES.armure].filter(it => it.system.equipe)
 | |
|       .map(it => it.system.malus)
 | |
|       .reduce(Misc.sum(), 0);
 | |
|   }
 | |
| 
 | |
|   listActions({ isAttaque = false, isEquipe = false }) {
 | |
|     // Recupération des attaques
 | |
|     const actions = this.listActionsAttaque()
 | |
|       .filter(it => !isEquipe || it.arme.system.equipe)
 | |
| 
 | |
|     if (!isAttaque && this.system.attributs.hautrevant.value) {
 | |
|       actions.push({ label: "Draconic", action: 'haut-reve', initOnly: true })
 | |
|     }
 | |
|     return actions
 | |
|   }
 | |
| 
 | |
|   /* -------------------------------------------- */
 | |
|   getTache(id) { return this.findItemLike(id, 'tache') }
 | |
|   getMeditation(id) { return this.findItemLike(id, 'meditation') }
 | |
|   getChant(id) { return this.findItemLike(id, 'chant') }
 | |
|   getDanse(id) { return this.findItemLike(id, 'danse') }
 | |
|   getMusique(id) { return this.findItemLike(id, 'musique') }
 | |
|   getOeuvre(id, type = 'oeuvre') { return this.findItemLike(id, type) }
 | |
|   getJeu(id) { return this.findItemLike(id, 'jeu') }
 | |
|   getRecetteCuisine(id) { return this.findItemLike(id, 'recettecuisine') }
 | |
| 
 | |
|   /* -------------------------------------------- */
 | |
|   getDemiReve() { return this.system.reve.tmrpos.coord }
 | |
|   getDraconics() { return this.itemTypes[ITEM_TYPES.competence].filter(it => it.system.categorie == 'draconic') }
 | |
|   getBestDraconic() { return foundry.utils.duplicate([...this.getDraconics(), PAS_DE_DRACONIC].sort(Misc.descending(it => it.system.niveau)).find(it => true)) }
 | |
|   getDraconicOuPossession() {
 | |
|     return [...this.getDraconics().filter(it => it.system.niveau >= 0), POSSESSION_SANS_DRACONIC]
 | |
|       .sort(Misc.descending(it => it.system.niveau))
 | |
|       .find(it => true)
 | |
|   }
 | |
| 
 | |
|   isForceInsuffisante(forceRequise) {
 | |
|     const force = parseInt(this.system.carac.force.value)
 | |
|     return forceRequise > force
 | |
|   }
 | |
| 
 | |
|   /* -------------------------------------------- */
 | |
|   /** Retourne une liste triée d'actions d'armes avec le split arme1 main / arme 2 main / lancer */
 | |
|   listActionsAttaque() {
 | |
|     const actions = []
 | |
|     const uniques = []
 | |
| 
 | |
|     const addAttaque = (arme, main = undefined, action = 'attaque') => {
 | |
|       const dommagesArme = RdDItemArme.valeurMain(arme.system.dommages, main)
 | |
|       const forceRequise = RdDItemArme.valeurMain(arme.system.force ?? 0, main)
 | |
|       const ecaillesEfficacite = arme.system.magique ? arme.system.ecaille_efficacite : 0;
 | |
| 
 | |
|       const comp = this.getCompetence(RdDActor.$getCompetenceAction(arme, main))
 | |
|       const unique = [comp.id, arme.name, dommagesArme, forceRequise, ecaillesEfficacite].join('|');
 | |
|       if (uniques.includes(unique)) {
 | |
|         return
 | |
|       }
 | |
|       uniques.push(unique);
 | |
| 
 | |
|       const caracCode = RdDActor.$getCaracAction(comp, main)
 | |
|       const caracValue = this.system.carac[caracCode].value
 | |
| 
 | |
|       const niveau = comp?.system.niveau ?? (['(lancer)', '(tir)'].includes(main) ? -8 : -6)
 | |
|       const ajustement = (arme.parent?.getEtatGeneral() ?? 0) + ecaillesEfficacite
 | |
| 
 | |
|       actions.push({
 | |
|         label: arme.name + (main ? ' ' + main : ''),
 | |
|         action: 'attaque',
 | |
|         initOnly: false,
 | |
|         arme: arme,
 | |
|         comp: comp,
 | |
|         main: main,
 | |
|         carac: { key: caracCode, value: caracValue },
 | |
|         equipe: arme.system.equipe,
 | |
|         dommagesArme: dommagesArme,
 | |
|         forceRequise: forceRequise,
 | |
|         initiative: RdDInitiative.getRollInitiative(caracValue, niveau, ajustement)
 | |
|       })
 | |
|     }
 | |
| 
 | |
|     this.itemTypes[ITEM_TYPES.arme]
 | |
|       .filter(it => it.isAttaque())
 | |
|       .sort(Misc.ascending(it => it.name))
 | |
|       .forEach(arme => {
 | |
|         if (arme.system.unemain && arme.system.competence && arme.system.resistance > 0) { addAttaque(arme, ATTAQUE_TYPE.UNE_MAIN) }
 | |
|         if (arme.system.deuxmains && arme.system.competence && arme.system.resistance > 0) { addAttaque(arme, ATTAQUE_TYPE.DEUX_MAINS) }
 | |
|         if (arme.system.lancer && arme.system.resistance > 0) { addAttaque(arme, ATTAQUE_TYPE.LANCER) }
 | |
|         if (arme.system.tir) { addAttaque(arme, ATTAQUE_TYPE.TIR) }
 | |
|       })
 | |
| 
 | |
|     addAttaque(RdDItemArme.pugilat(this), ATTAQUE_TYPE.CORPS_A_CORPS)
 | |
|     addAttaque(RdDItemArme.empoignade(this), ATTAQUE_TYPE.CORPS_A_CORPS, 'empoignade')
 | |
| 
 | |
|     return actions
 | |
|   }
 | |
| 
 | |
|   static $getCaracAction(comp, main) {
 | |
|     if (comp?.system.defaut_carac) {
 | |
|       return comp.system.defaut_carac
 | |
|     }
 | |
|     switch (main) {
 | |
|       case '(lancer)': return 'lancer'
 | |
|       case '(tir)': return 'tir'
 | |
|       default: return 'melee'
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   static $getCompetenceAction(arme, main) {
 | |
|     switch (main) {
 | |
|       case ATTAQUE_TYPE.UNE_MAIN: return arme.competence1Mains()
 | |
|       case ATTAQUE_TYPE.DEUX_MAINS: return arme.competence2Mains()
 | |
|       case ATTAQUE_TYPE.LANCER: return arme.system.lancer
 | |
|       case ATTAQUE_TYPE.TIR: return arme.system.tir
 | |
|       default: return arme.system.competence
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   /* -------------------------------------------- */
 | |
|   async $perteReveEnchantementsChateauDormants() {
 | |
|     const toUpdate = this.items.filter(it => [ITEM_TYPES.potion, ITEM_TYPES.gemme].includes(it.type))
 | |
|       .map(it => it.perteReveChateauDormant())
 | |
|       .filter(it => it != undefined)
 | |
| 
 | |
|     if (toUpdate.length > 0) {
 | |
|       console.log('perte de rêve des enchantements', toUpdate)
 | |
|       const messageUpdates = await Promise.all(
 | |
|         toUpdate.map(async it => await renderTemplate(`systems/foundryvtt-reve-de-dragon/templates/chat-pertereve-enchantement-chateaudormant.hbs`, it)))
 | |
|       ChatMessage.create({
 | |
|         whisper: ChatUtility.getOwners(this),
 | |
|         content: messageUpdates.reduce(Misc.joining('<br>'))
 | |
|       })
 | |
|       await this.updateEmbeddedDocuments('Item', toUpdate.map(it => it.update));
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   async $suppressionLancementsSort() {
 | |
|     const updates = this.itemTypes[ITEM_TYPES.sort]
 | |
|       .filter(it => it.system.lancements.length > 0)
 | |
|       .map(it => { return { _id: it.id, 'system.lancements': [] } })
 | |
|     if (updates.length > 0) {
 | |
|       await this.updateEmbeddedDocuments('Item', updates)
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   /** --------------------------------------------
 | |
|    * @returns true si l'acteur possède au moins 1 arme de mêlée équipée
 | |
|    */
 | |
|   hasArmeeMeleeEquipee() {
 | |
|     return this.itemTypes['arme'].find(it => it.system.equipe && it.system.competence != "")
 | |
|   }
 | |
| 
 | |
|   async prepareChateauDormant(consigne) {
 | |
|     if (consigne.ignorer) {
 | |
|       return;
 | |
|     }
 | |
|     if (consigne.stress.valeur > 0) {
 | |
|       await this.distribuerStress('stress', consigne.stress.valeur, consigne.stress.motif);
 | |
|     }
 | |
|     await this.update({ 'system.sommeil': consigne.sommeil })
 | |
|     const player = this.findPlayer();
 | |
|     if (player) {
 | |
|       ChatUtility.notifyUser(player.id, 'info', `Vous pouvez gérer la nuit de ${this.name}`);
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   async onTimeChanging(oldTimestamp, newTimestamp) {
 | |
|     await super.onTimeChanging(oldTimestamp, newTimestamp);
 | |
|     await this.setInfoSommeilInsomnie();
 | |
|   }
 | |
| 
 | |
|   async repos() {
 | |
|     await DialogRepos.create(this);
 | |
|   }
 | |
| 
 | |
|   /* -------------------------------------------- */
 | |
|   async grisReve(nbJours) {
 | |
|     let message = {
 | |
|       whisper: ChatUtility.getOwners(this),
 | |
|       content: `${nbJours} jours de gris rêve sont passés. `
 | |
|     };
 | |
|     for (let i = 0; i < nbJours; i++) {
 | |
|       await this.dormir(4, { grisReve: true });
 | |
|       await this._recuperationSante(message);
 | |
| 
 | |
|       const moralActuel = Misc.toInt(this.system.compteurs.moral.value);
 | |
|       if (moralActuel != 0) {
 | |
|         await this.moralIncDec(-Math.sign(moralActuel));
 | |
|       }
 | |
|       await this._recupereChance();
 | |
|       await this.transformerStress();
 | |
|       await this.setBonusPotionSoin(0);
 | |
|     }
 | |
|     await this.resetInfoSommeil()
 | |
|     ChatMessage.create(message);
 | |
|     setTimeout(() => this.sheet.render(), 20)
 | |
|   }
 | |
| 
 | |
|   async _recuperationSante(message) {
 | |
|     const maladiesPoisons = this.getMaladiesPoisons();
 | |
|     const isMaladeEmpoisonne = maladiesPoisons.length > 0;
 | |
|     this._messageRecuperationMaladiePoisons(maladiesPoisons, message);
 | |
| 
 | |
|     await this._recupererBlessures(message, isMaladeEmpoisonne);
 | |
|     await this._recupererVie(message, isMaladeEmpoisonne);
 | |
|   }
 | |
| 
 | |
|   getMaladiesPoisons() {
 | |
|     return this.items.filter(item => item.type == 'maladie' || (item.type == 'poison' && item.system.active));
 | |
|   }
 | |
| 
 | |
|   _messageRecuperationMaladiePoisons(maladiesPoisons, message) {
 | |
|     if (maladiesPoisons.length > 0) {
 | |
|       const identifies = maladiesPoisons.filter(it => it.system.identifie);
 | |
|       const nonIdentifies = maladiesPoisons.filter(it => !it.system.identifie);
 | |
|       message.content += 'Vous souffrez';
 | |
|       switch (nonIdentifies.length) {
 | |
|         case 0: break;
 | |
|         case 1: message.content += ` d'un mal inconnu`; break;
 | |
|         default: message.content += ` de ${nonIdentifies.length} maux inconnus`; break;
 | |
|       }
 | |
|       if (identifies.length > 0 && nonIdentifies.length > 0) { message.content += ' et' }
 | |
|       if (identifies.length > 0) {
 | |
|         message.content += ' de ' + identifies.map(it => it.name).reduce(Misc.joining(', '));
 | |
|       }
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   /* -------------------------------------------- */
 | |
|   async dormirChateauDormant() {
 | |
|     if (!ReglesOptionnelles.isUsing("chateau-dormant-gardien") || !this.system.sommeil || this.system.sommeil.nouveaujour) {
 | |
|       await this.$dormirChateauDormant();
 | |
|       setTimeout(() => this.sheet.render(), 20)
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   async $dormirChateauDormant() {
 | |
|     const message = {
 | |
|       whisper: ChatUtility.getOwners(this),
 | |
|       content: ""
 | |
|     };
 | |
| 
 | |
|     await this._recuperationSante(message);
 | |
|     await this._recupereMoralChateauDormant(message);
 | |
|     await this._recupereChance();
 | |
|     await this.transformerStress();
 | |
|     await this.retourSeuilDeReve(message);
 | |
|     await this.setBonusPotionSoin(0);
 | |
|     await this.retourSust(message);
 | |
|     await this.$perteReveEnchantementsChateauDormants();
 | |
|     await this.$suppressionLancementsSort();
 | |
|     await RdDCoeur.applyCoeurChateauDormant(this, message);
 | |
|     if (message.content != "") {
 | |
|       message.content = `A la fin Chateau Dormant, ${message.content}<br>Un nouveau jour se lève`;
 | |
|       ChatMessage.create(message);
 | |
|     }
 | |
|     await this.resetInfoSommeil();
 | |
|   }
 | |
| 
 | |
|   async resetInfoSommeil() {
 | |
|     await this.update({
 | |
|       'system.sommeil': {
 | |
|         nouveaujour: false,
 | |
|         date: game.system.rdd.calendrier.getTimestamp(),
 | |
|         moral: "neutre",
 | |
|         heures: 0,
 | |
|         insomnie: EffetsDraconiques.isSujetInsomnie(this)
 | |
|       }
 | |
|     });
 | |
|   }
 | |
| 
 | |
|   async setInfoSommeilInsomnie() {
 | |
|     if (EffetsDraconiques.isSujetInsomnie(this)) {
 | |
|       await this.update({ 'system.sommeil.insomnie': true });
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   async setInfoSommeilMoral(situationMoral) {
 | |
|     await this.update({ 'system.sommeil.moral': situationMoral });
 | |
|   }
 | |
| 
 | |
|   /* -------------------------------------------- */
 | |
|   async _recupereChance() {
 | |
|     if (!ReglesOptionnelles.isUsing("recuperation-chance")) { return }
 | |
| 
 | |
|     // On ne récupère un point de chance que si aucun appel à la chance dans la journée
 | |
|     if (this.getChanceActuel() < this.getChance() && !this.getFlag(SYSTEM_RDD, 'utilisationChance')) {
 | |
|       await this.chanceActuelleIncDec(1);
 | |
|     }
 | |
|     // Nouveau jour, suppression du flag
 | |
|     await this.unsetFlag(SYSTEM_RDD, 'utilisationChance');
 | |
|   }
 | |
| 
 | |
|   async _recupereMoralChateauDormant(message) {
 | |
|     if (!ReglesOptionnelles.isUsing("recuperation-moral")) { return }
 | |
| 
 | |
|     const etatMoral = this.system.sommeil?.moral ?? 'neutre';
 | |
|     const jetMoral = await this._jetDeMoral(etatMoral);
 | |
|     message.content += ` -- le jet de moral est ${etatMoral}, le moral ` + this._messageAjustementMoral(jetMoral.ajustement);
 | |
|   }
 | |
| 
 | |
|   _messageAjustementMoral(ajustement) {
 | |
|     switch (Math.sign(ajustement)) {
 | |
|       case 1:
 | |
|         return `remonte de ${ajustement}`;
 | |
|       case -1:
 | |
|         return `diminue de ${-ajustement}`;
 | |
|       case 0:
 | |
|         return 'reste stable';
 | |
|       default:
 | |
|         console.error(`Le signe de l'ajustement de moral ${ajustement} est ${Math.sign(ajustement)}, ce qui est innatendu`)
 | |
|         return `est ajusté de ${ajustement} (bizarre)`;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   /* -------------------------------------------- */
 | |
|   async _recupererBlessures(message, isMaladeEmpoisonne) {
 | |
|     const timestamp = game.system.rdd.calendrier.getTimestamp()
 | |
|     const blessures = this.filterItems(it => it.system.gravite > 0, ITEM_TYPES.blessure).sort(Misc.ascending(it => it.system.gravite))
 | |
| 
 | |
|     await Promise.all(blessures.map(async b => b.recuperationBlessure({
 | |
|       actor: this,
 | |
|       timestamp,
 | |
|       message,
 | |
|       isMaladeEmpoisonne,
 | |
|       blessures
 | |
|     })));
 | |
| 
 | |
|     await this.supprimerBlessures(it => it.system.gravite <= 0);
 | |
|   }
 | |
| 
 | |
|   /* -------------------------------------------- */
 | |
|   async _recupererVie(message, isMaladeEmpoisonne) {
 | |
|     const tData = this.system
 | |
|     let blessures = this.filterItems(it => it.system.gravite > 0, ITEM_TYPES.blessure);
 | |
|     if (blessures.length > 0) {
 | |
|       return
 | |
|     }
 | |
|     let vieManquante = tData.sante.vie.max - tData.sante.vie.value;
 | |
|     if (vieManquante > 0) {
 | |
|       let rolled = await this.jetRecuperationConstitution(0, message)
 | |
|       if (!isMaladeEmpoisonne && rolled.isSuccess) {
 | |
|         const gain = Math.min(rolled.isPart ? 2 : 1, vieManquante);
 | |
|         message.content += " -- récupération de vie: " + gain;
 | |
|         await this.santeIncDec("vie", gain);
 | |
|       }
 | |
|       else if (rolled.isETotal) {
 | |
|         message.content += " -- perte de vie: 1";
 | |
|         await this.santeIncDec("vie", -1);
 | |
|       }
 | |
|       else {
 | |
|         message.content += " -- vie stationnaire ";
 | |
|       }
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   /* -------------------------------------------- */
 | |
|   async jetRecuperationConstitution(bonusSoins, message = undefined) {
 | |
|     let difficulte = Math.min(0, this.system.sante.vie.value - this.system.sante.vie.max) + bonusSoins + this.system.sante.bonusPotion;
 | |
|     let rolled = await RdDResolutionTable.roll(this.system.carac.constitution.value, difficulte);
 | |
|     if (message) {
 | |
|       message.content = await renderTemplate("systems/foundryvtt-reve-de-dragon/templates/roll/explain.hbs", {
 | |
|         actor: this,
 | |
|         carac: this.system.carac.constitution,
 | |
|         rolled
 | |
|       })
 | |
|     }
 | |
|     return rolled;
 | |
|   }
 | |
| 
 | |
|   /* -------------------------------------------- */
 | |
|   async remiseANeuf() {
 | |
|     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);
 | |
|     await ChatMessage.create({
 | |
|       whisper: ChatUtility.getOwners(this),
 | |
|       content: 'Remise à neuf de ' + this.name
 | |
|     });
 | |
|   }
 | |
| 
 | |
|   /* -------------------------------------------- */
 | |
|   async dormir(heures, options = { grisReve: false, chateauDormant: false }) {
 | |
|     const message = {
 | |
|       whisper: ChatUtility.getOwners(this),
 | |
|       content: this.name + ': '
 | |
|     };
 | |
|     const insomnie = this.system.sommeil?.insomnie || heures == 0;
 | |
|     await this.recupereEndurance({ message: message, demi: insomnie });
 | |
|     if (insomnie) {
 | |
|       message.content += 'Vous ne trouvez pas le sommeil';
 | |
|     }
 | |
|     else {
 | |
|       let dormi = await this.$dormirDesHeures(message, heures, options);
 | |
|       if (dormi.jetsReve.length > 0) {
 | |
|         message.content += `Vous récupérez ${dormi.jetsReve.map(it => it < 0 ? '0 (réveil)' : it).reduce(Misc.joining("+"))} Points de rêve. `;
 | |
|       }
 | |
|       if (dormi.etat == 'eveil') {
 | |
|         await this.reveilReveDeDragon(message, dormi.heures);
 | |
|       }
 | |
|       options.chateauDormant = options.chateauDormant && dormi.heures >= heures;
 | |
|       message.content += `Vous avez dormi ${dormi.heures <= 1 ? 'une heure' : (dormi.heures + ' heures')}. `;
 | |
|     }
 | |
| 
 | |
|     if (!options.grisReve) {
 | |
|       ChatMessage.create(message);
 | |
|     }
 | |
|     if (options.chateauDormant) {
 | |
|       await this.$dormirChateauDormant();
 | |
|     }
 | |
|     setTimeout(() => this.sheet.render(), 20)
 | |
|   }
 | |
| 
 | |
|   async reveilReveDeDragon(message, heures) {
 | |
|     const restant = Math.max(this.system.sommeil?.heures - heures, 0)
 | |
|     if (restant > 0) {
 | |
|       await this.update({ 'system.sommeil': { heures: restant } });
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   async $dormirDesHeures(message, heures, options) {
 | |
|     const dormi = { heures: 0, etat: 'dort', jetsReve: [] };
 | |
|     for (; dormi.heures < heures && dormi.etat == 'dort'; dormi.heures++) {
 | |
|       await this.$recupererEthylisme(message);
 | |
|       if (options.grisReve) {
 | |
|         await this.$recupererFatigue(message);
 | |
|       }
 | |
|       else if (!this.system.sommeil?.insomnie) {
 | |
|         await this.$recupererFatigue(message);
 | |
|         await this.$jetRecuperationReve(dormi, message);
 | |
|         if (dormi.etat == 'dort' && EffetsDraconiques.isDonDoubleReve(this)) {
 | |
|           await this.$jetRecuperationReve(dormi, message);
 | |
|         }
 | |
|       }
 | |
|     }
 | |
|     return dormi;
 | |
|   }
 | |
| 
 | |
|   /* -------------------------------------------- */
 | |
|   async $jetRecuperationReve(dormi, message) {
 | |
|     if (this.getReveActuel() < this.system.reve.seuil.value) {
 | |
|       const reve = await RdDDice.rollTotal("1dr")
 | |
|       if (reve >= 7) {
 | |
|         // Rêve de Dragon !
 | |
|         message.content += `Vous faites un <strong>Rêve de Dragon</strong> de ${reve} Points de rêve qui vous réveille! `;
 | |
|         await this.combattreReveDeDragon(reve);
 | |
|         dormi.jetsReve.push(-1);
 | |
|         dormi.etat = 'eveil'
 | |
|         return
 | |
|       }
 | |
|       else {
 | |
|         if (!ReglesOptionnelles.isUsing("recuperation-reve")) {
 | |
|           ChatMessage.create({
 | |
|             whisper: ChatUtility.getOwners(this),
 | |
|             content: `Pas de récupération de rêve (${reve} points ignorés)`
 | |
|           })
 | |
|           dormi.jetsReve.push(0)
 | |
|         }
 | |
|         else {
 | |
|           await this.reveActuelIncDec(reve)
 | |
|           dormi.jetsReve.push(reve)
 | |
|         }
 | |
|       }
 | |
|     }
 | |
|     dormi.etat = 'dort'
 | |
|   }
 | |
| 
 | |
|   /* -------------------------------------------- */
 | |
|   async $recupererEthylisme(message) {
 | |
|     if (!ReglesOptionnelles.isUsing("recuperation-ethylisme")) { return; }
 | |
|     let value = Math.min(Number.parseInt(this.system.compteurs.ethylisme.value) + 1, 1);
 | |
|     if (value <= 0) {
 | |
|       message.content += `Vous dégrisez un peu (${RdDUtility.getNomEthylisme(value)}). `;
 | |
|     }
 | |
|     await this.update({
 | |
|       'system.compteurs.ethylisme': {
 | |
|         nb_doses: 0,
 | |
|         jet_moral: false,
 | |
|         value: value
 | |
|       }
 | |
|     })
 | |
|   }
 | |
| 
 | |
|   /* -------------------------------------------- */
 | |
|   async recupereEndurance({ message, demi }) {
 | |
|     let max = this._computeEnduranceMax();
 | |
|     if (demi) {
 | |
|       max = Math.floor(max / 2);
 | |
|     }
 | |
|     const manquant = max - this.system.sante.endurance.value;
 | |
|     if (manquant > 0) {
 | |
|       await this.santeIncDec("endurance", manquant);
 | |
|       message.content += `Vous récuperez ${manquant} points d'endurance. `;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   /* -------------------------------------------- */
 | |
|   async $recupererFatigue(message) {
 | |
|     if (ReglesOptionnelles.isUsing("appliquer-fatigue")) {
 | |
|       let fatigue = this.system.sante.fatigue.value
 | |
|       const fatigueMin = this.getFatigueMin()
 | |
|       if (fatigue <= fatigueMin) {
 | |
|         return
 | |
|       }
 | |
|       fatigue = Math.max(fatigueMin, this._calculRecuperationSegment(fatigue))
 | |
|       await this.update({ 'system.sante.fatigue.value': fatigue });
 | |
|       if (fatigue == 0) {
 | |
|         message.content += "Vous êtes complêtement reposé. ";
 | |
|       }
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   /* -------------------------------------------- */
 | |
|   _calculRecuperationSegment(actuel) {
 | |
|     const segments = RdDUtility.getSegmentsFatigue(this.system.sante.endurance.max);
 | |
|     let cumul = 0;
 | |
|     let i;
 | |
|     for (i = 0; i < 11; i++) {
 | |
|       cumul += segments[i];
 | |
|       let diff = cumul - actuel;
 | |
|       if (diff >= 0) {
 | |
|         const limit2Segments = Math.floor(segments[i] / 2);
 | |
|         if (diff > limit2Segments && i > 0) {
 | |
|           cumul -= segments[i - 1]; // le segment est à moins de la moitié, il est récupéré 
 | |
|         }
 | |
|         cumul -= segments[i];
 | |
|         break;
 | |
|       }
 | |
|     };
 | |
|     return cumul;
 | |
|   }
 | |
| 
 | |
|   /* -------------------------------------------- */
 | |
|   async retourSeuilDeReve(message) {
 | |
|     const seuil = this.system.reve.seuil.value;
 | |
|     const reveActuel = this.getReveActuel();
 | |
|     if (reveActuel > seuil) {
 | |
|       message.content += `<br>Votre rêve redescend vers son seuil naturel (${seuil}, nouveau rêve actuel ${(reveActuel - 1)})`;
 | |
|       await this.reveActuelIncDec(-1);
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   async retourSust(message) {
 | |
|     const tplData = this.system;
 | |
|     const sustNeeded = tplData.attributs.sust.value;
 | |
|     const sustConsomme = tplData.compteurs.sust.value;
 | |
|     const eauConsomme = tplData.compteurs.eau.value;
 | |
|     if (game.settings.get(SYSTEM_RDD, "appliquer-famine-soif").includes('famine') && sustConsomme < sustNeeded) {
 | |
|       const perte = sustConsomme < Math.min(0.5, sustNeeded) ? 3 : (sustConsomme <= (sustNeeded / 2) ? 2 : 1);
 | |
|       message.content += `<br>Vous ne vous êtes sustenté que de ${sustConsomme} pour un appétit de ${sustNeeded}, vous avez faim!
 | |
|         La famine devrait vous faire ${perte} points d'endurance non récupérables, notez le cumul de côté et ajustez l'endurance`;
 | |
|     }
 | |
| 
 | |
|     if (game.settings.get(SYSTEM_RDD, "appliquer-famine-soif").includes('soif') && eauConsomme < sustNeeded) {
 | |
|       const perte = eauConsomme < Math.min(0.5, sustNeeded) ? 12 : (eauConsomme <= (sustNeeded / 2) ? 6 : 3);
 | |
|       message.content += `<br>Vous n'avez bu que ${eauConsomme} doses de liquide pour une soif de ${sustNeeded}, vous avez soif!
 | |
|         La soif devrait vous faire ${perte} points d'endurance non récupérables, notez le cumul de côté et ajustez l'endurance`;
 | |
|     }
 | |
|     await this.updateCompteurValue('sust', 0);
 | |
|     await this.updateCompteurValue('eau', 0);
 | |
|   }
 | |
| 
 | |
|   /* -------------------------------------------- */
 | |
|   async combattreReveDeDragon(force) {
 | |
|     const rencontre = await game.system.rdd.rencontresTMR.getReveDeDragon(force);
 | |
|     let rollData = {
 | |
|       actor: this,
 | |
|       competence: this.getDraconicOuPossession(),
 | |
|       canClose: false,
 | |
|       rencontre: rencontre,
 | |
|       tmr: true,
 | |
|       use: { libre: false, conditions: false },
 | |
|       forceCarac: { 'reve-actuel': { label: "Rêve Actuel", value: this.getReveActuel() } }
 | |
|     }
 | |
|     rollData.competence.system.defaut_carac = 'reve-actuel';
 | |
| 
 | |
|     const dialog = await RdDRoll.create(this, rollData,
 | |
|       { html: 'systems/foundryvtt-reve-de-dragon/templates/dialog-roll-reve-de-dragon.hbs' },
 | |
|       {
 | |
|         name: 'maitrise',
 | |
|         label: 'Maîtriser le Rêve de Dragon',
 | |
|         callbacks: [
 | |
|           { action: async r => this.resultCombatReveDeDragon(r) }
 | |
|         ]
 | |
|       }
 | |
|     );
 | |
|     dialog.render(true);
 | |
|   }
 | |
| 
 | |
|   /* -------------------------------------------- */
 | |
|   async resultCombatReveDeDragon(rollData) {
 | |
|     const result = rollData.rolled.isSuccess
 | |
|       ? rollData.rencontre.system.succes
 | |
|       : rollData.rencontre.system.echec;
 | |
| 
 | |
|     RdDRencontre.appliquer(result.effets, {}, rollData)
 | |
|   }
 | |
| 
 | |
|   /* -------------------------------------------- */
 | |
|   async sortMisEnReserve(sort, draconic, coord, ptreve) {
 | |
|     await this.createEmbeddedDocuments("Item", [RdDItemSort.prepareSortEnReserve(sort, draconic, ptreve, coord)],
 | |
|       { renderSheet: false });
 | |
|     this.tmrApp.updateTokens();
 | |
|   }
 | |
| 
 | |
|   /* -------------------------------------------- */
 | |
|   async updateCarac(caracName, to) {
 | |
|     to = Number(to)
 | |
|     if (!RdDItemRace.checkRacialMax(this, caracName, to)) {
 | |
|       return
 | |
|     }
 | |
|     let updates = {};
 | |
|     if (caracName == LIST_CARAC_PERSONNAGE.reve.code) {
 | |
|       if (to > Misc.toInt(this.system.reve.seuil.value)) {
 | |
|         updates[`system.reve.seuil.value`] = to; // SFA : Direct and packed changes
 | |
|         //this.setPointsDeSeuil(to);
 | |
|       }
 | |
|     }
 | |
|     if (caracName == LIST_CARAC_PERSONNAGE.chance.code) {
 | |
|       if (to > Misc.toInt(this.system.compteurs.chance.value)) {
 | |
|         updates[`system.compteurs.chance.value`] = to; // SFA : Direct and packed changes
 | |
|         //this.setPointsDeChance(to);
 | |
|       }
 | |
|     }
 | |
|     let selectedCarac = this.findCaracByName(caracName);
 | |
|     const from = selectedCarac.value
 | |
|     updates[`system.carac.${caracName}.value`] = to;
 | |
|     await this.update(updates);
 | |
|     await ExperienceLog.add(this, XP_TOPIC.CARAC, from, to, caracName);
 | |
|   }
 | |
| 
 | |
|   /* -------------------------------------------- */
 | |
|   async updateCaracXP(caracName, to) {
 | |
|     if (caracName == 'Taille') {
 | |
|       return;
 | |
|     }
 | |
|     let selectedCarac = this.findCaracByName(caracName);
 | |
|     if (!selectedCarac.derivee) {
 | |
|       const from = Number(selectedCarac.xp);
 | |
|       await this.update({ [`system.carac.${caracName}.xp`]: to });
 | |
|       await ExperienceLog.add(this, XP_TOPIC.XPCARAC, from, to, caracName);
 | |
|     }
 | |
|     this.checkCaracXP(caracName);
 | |
|   }
 | |
| 
 | |
|   /* -------------------------------------------- */
 | |
|   async updateCaracXPAuto(caracName) {
 | |
|     if (caracName == 'Taille') {
 | |
|       return;
 | |
|     }
 | |
|     let carac = this.findCaracByName(caracName);
 | |
|     if (carac) {
 | |
|       carac = foundry.utils.duplicate(carac);
 | |
|       const fromXp = Number(carac.xp);
 | |
|       const fromValue = Number(carac.value);
 | |
|       let toXp = fromXp;
 | |
|       let toValue = fromValue;
 | |
|       while (toXp >= RdDCarac.getCaracNextXp(toValue) && toXp > 0) {
 | |
|         toXp -= RdDCarac.getCaracNextXp(toValue);
 | |
|         toValue++;
 | |
|       }
 | |
|       carac.xp = toXp;
 | |
|       carac.value = toValue;
 | |
|       await this.update({ [`system.carac.${caracName}`]: carac });
 | |
|       await ExperienceLog.add(this, XP_TOPIC.XPCARAC, fromXp, toXp, caracName);
 | |
|       await ExperienceLog.add(this, XP_TOPIC.CARAC, fromValue, toValue, caracName);
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   /* -------------------------------------------- */
 | |
|   async updateCompetenceXPAuto(idOrName) {
 | |
|     let competence = this.getCompetence(idOrName);
 | |
|     if (competence) {
 | |
|       const fromXp = Number(competence.system.xp);
 | |
|       const fromNiveau = Number(competence.system.niveau);
 | |
|       let toXp = fromXp;
 | |
|       let toNiveau = fromNiveau;
 | |
|       while (toXp >= RdDItemCompetence.getCompetenceNextXp(toNiveau) && toXp > 0) {
 | |
|         toXp -= RdDItemCompetence.getCompetenceNextXp(toNiveau);
 | |
|         toNiveau++;
 | |
|       }
 | |
|       await competence.update({
 | |
|         "system.xp": toXp,
 | |
|         "system.niveau": toNiveau,
 | |
|       }, { render: false })
 | |
|       await ExperienceLog.add(this, XP_TOPIC.XP, fromXp, toXp, competence.name);
 | |
|       await ExperienceLog.add(this, XP_TOPIC.NIVEAU, fromNiveau, toNiveau, competence.name);
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   async updateCompetenceStress(idOrName) {
 | |
|     const competence = this.getCompetence(idOrName);
 | |
|     if (!competence) {
 | |
|       return;
 | |
|     }
 | |
|     const fromXp = competence.system.xp;
 | |
|     const fromXpStress = this.system.compteurs.experience.value;
 | |
|     const fromNiveau = Number(competence.system.niveau);
 | |
|     const xpSuivant = RdDItemCompetence.getCompetenceNextXp(fromNiveau);
 | |
|     const xpRequis = xpSuivant - fromXp;
 | |
|     if (fromXpStress <= 0 || fromNiveau >= competence.system.niveau_archetype) {
 | |
|       ui.notifications.info(`La compétence ne peut pas augmenter!
 | |
|           stress disponible: ${fromXpStress}
 | |
|           expérience requise: ${xpRequis}
 | |
|           niveau : ${fromNiveau}
 | |
|           archétype : ${competence.system.niveau_archetype}`);
 | |
|       return
 | |
|     }
 | |
|     const xpUtilise = Math.max(0, Math.min(fromXpStress, xpRequis));
 | |
|     const gainNiveau = (xpUtilise >= xpRequis || xpRequis <= 0) ? 1 : 0;
 | |
|     const toNiveau = fromNiveau + gainNiveau;
 | |
|     const newXp = gainNiveau > 0 ? Math.max(fromXp - xpSuivant, 0) : (fromXp + xpUtilise);
 | |
|     await competence.update({
 | |
|       "system.xp": newXp,
 | |
|       "system.niveau": toNiveau,
 | |
|     }, { render: false })
 | |
|     const toXpStress = Math.max(0, fromXpStress - xpUtilise);
 | |
|     await this.update({ "system.compteurs.experience.value": toXpStress });
 | |
| 
 | |
|     await ExperienceLog.add(this, XP_TOPIC.TRANSFORM, fromXpStress, toXpStress, `Dépense stress`);
 | |
|     await ExperienceLog.add(this, XP_TOPIC.XP, fromXp, newXp, competence.name);
 | |
|     await ExperienceLog.add(this, XP_TOPIC.NIVEAU, fromNiveau, toNiveau, competence.name);
 | |
|   }
 | |
| 
 | |
|   /* -------------------------------------------- */
 | |
|   async updateCompetence(idOrName, compValue) {
 | |
|     const competence = this.getCompetence(idOrName);
 | |
|     if (competence) {
 | |
|       const toNiveau = compValue ?? RdDItemCompetence.getNiveauBase(competence.system.categorie, competence.getCategories());
 | |
|       this.notifyCompetencesTronc(competence, toNiveau);
 | |
|       const fromNiveau = competence.system.niveau;
 | |
|       await competence.update({ 'system.niveau': toNiveau }, { render: true })
 | |
|       await ExperienceLog.add(this, XP_TOPIC.NIVEAU, fromNiveau, toNiveau, competence.name, true);
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   notifyCompetencesTronc(competence, toNiveau) {
 | |
|     const listTronc = RdDItemCompetence.getListTronc(competence.name).filter(it => {
 | |
|       const autreComp = this.getCompetence(it);
 | |
|       const niveauTr = autreComp?.system.niveau ?? 0;
 | |
|       return niveauTr < 0 && niveauTr < toNiveau;
 | |
|     });
 | |
|     if (listTronc.length > 0) {
 | |
|       ui.notifications.info(
 | |
|         "Vous avez modifié une compétence 'tronc'. Vérifiez que les compétences suivantes évoluent ensemble jusqu'au niveau 0 : <br>"
 | |
|         + Misc.join(listTronc, '<br>'));
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   /* -------------------------------------------- */
 | |
|   async updateCompetenceXP(idOrName, toXp) {
 | |
|     let competence = this.getCompetence(idOrName);
 | |
|     if (competence) {
 | |
|       if (isNaN(toXp) || typeof (toXp) != 'number') toXp = 0;
 | |
|       const fromXp = competence.system.xp;
 | |
|       this.checkCompetenceXP(idOrName, toXp);
 | |
|       await competence.update({ 'system.xp': toXp }, { render: false })
 | |
|       await ExperienceLog.add(this, XP_TOPIC.XP, fromXp, toXp, competence.name, true);
 | |
|       if (toXp > fromXp) {
 | |
|         RdDUtility.checkThanatosXP(competence)
 | |
|       }
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   /* -------------------------------------------- */
 | |
|   async updateCompetenceXPSort(idOrName, toXpSort) {
 | |
|     let competence = this.getCompetence(idOrName);
 | |
|     if (competence) {
 | |
|       if (isNaN(toXpSort) || typeof (toXpSort) != 'number') toXpSort = 0;
 | |
|       const fromXpSort = competence.system.xp_sort;
 | |
|       await competence.update({ 'system.xp_sort': toXpSort }, { render: false })
 | |
|       await ExperienceLog.add(this, XP_TOPIC.XPSORT, fromXpSort, toXpSort, competence.name, true);
 | |
|       if (toXpSort > fromXpSort) {
 | |
|         RdDUtility.checkThanatosXP(competence)
 | |
|       }
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   /* -------------------------------------------- */
 | |
|   async updateCompetenceArchetype(idOrName, compValue) {
 | |
|     let competence = this.getCompetence(idOrName)
 | |
|     if (competence) {
 | |
|       await competence.update({ 'system.niveau_archetype': Math.max(compValue ?? 0, 0) })
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   async deleteExperienceLog(from, count) {
 | |
|     if (from >= 0 && count > 0) {
 | |
|       let expLog = foundry.utils.duplicate(this.system.experiencelog);
 | |
|       expLog.splice(from, count);
 | |
|       await this.update({ [`system.experiencelog`]: expLog });
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   /* -------------------------------------------- */
 | |
|   async updateCompteurValue(fieldName, to) {
 | |
|     const from = this.system.compteurs[fieldName].value
 | |
|     await this.update({ [`system.compteurs.${fieldName}.value`]: to });
 | |
|     await this.addStressExperienceLog(fieldName, from, to, fieldName, true);
 | |
|   }
 | |
| 
 | |
|   /* -------------------------------------------- */
 | |
|   async addCompteurValue(fieldName, add, raison) {
 | |
|     let from = this.system.compteurs[fieldName].value;
 | |
|     const to = Number(from) + Number(add);
 | |
|     await this.update({ [`system.compteurs.${fieldName}.value`]: to });
 | |
|     await this.addStressExperienceLog(fieldName, from, to, raison);
 | |
|   }
 | |
| 
 | |
|   async addStressExperienceLog(topic, from, to, raison, manuel) {
 | |
|     switch (topic) {
 | |
|       case 'stress':
 | |
|         return await ExperienceLog.add(this, XP_TOPIC.STRESS, from, to, raison, manuel)
 | |
|       case 'experience':
 | |
|         return await ExperienceLog.add(this, XP_TOPIC.TRANSFORM, from, to, raison, manuel)
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   /* -------------------------------------------- */
 | |
|   async distribuerStress(compteur, stress, motif) {
 | |
|     if (game.user.isGM && this.hasPlayerOwner) {
 | |
|       switch (compteur) {
 | |
|         case 'stress': case 'experience':
 | |
|           await this.addCompteurValue(compteur, stress, motif);
 | |
|           const message = `${this.name} a reçu ${stress} points ${compteur == 'stress' ? "de stress" : "d'expérience"} (raison : ${motif})`;
 | |
|           ui.notifications.info(message);
 | |
|           game.users.players.filter(player => player.active && player.character?.id == this.id)
 | |
|             .forEach(player => ChatUtility.notifyUser(player.id, 'info', message));
 | |
|       }
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   /* -------------------------------------------- */
 | |
|   async updateAttributeValue(fieldName, fieldValue) {
 | |
|     await this.update({ [`system.attributs.${fieldName}.value`]: fieldValue });
 | |
|   }
 | |
| 
 | |
|   /* -------------------------------------------- */
 | |
|   ethylisme() {
 | |
|     return this.system.compteurs.ethylisme?.value ?? 1;
 | |
|   }
 | |
|   malusEthylisme() {
 | |
|     return Math.min(0, this.ethylisme())
 | |
|   }
 | |
|   isAlcoolise() {
 | |
|     return this.ethylisme() < 1
 | |
|   }
 | |
| 
 | |
| 
 | |
|   /* -------------------------------------------- */
 | |
|   async actionRefoulement(item) {
 | |
|     const refoulement = item?.system.refoulement ?? 0;
 | |
|     if (refoulement > 0) {
 | |
|       RdDConfirm.confirmer({
 | |
|         settingConfirmer: "confirmation-refouler",
 | |
|         content: `<p>Prennez-vous le risque de refouler ${item.name} pour ${refoulement} points de refoulement ?</p>`,
 | |
|         title: 'Confirmer la refoulement',
 | |
|         buttonLabel: 'Refouler',
 | |
|         onAction: async () => {
 | |
|           await this.ajouterRefoulement(refoulement, `une queue ${item.name}`);
 | |
|           await item.delete();
 | |
|         }
 | |
|       });
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   /* -------------------------------------------- */
 | |
|   async ajouterRefoulement(value = 1, refouler) {
 | |
|     let refoulement = this.system.reve.refoulement.value + value;
 | |
|     const roll = new Roll("1d20");
 | |
|     await roll.evaluate();
 | |
|     await roll.toMessage({ flavor: `${this.name} refoule ${refouler} pour ${value} points de refoulement (total: ${refoulement})` });
 | |
|     if (roll.total <= refoulement) {
 | |
|       refoulement = 0;
 | |
|       await this.ajouterSouffle({ chat: true });
 | |
|     }
 | |
|     await this.update({ "system.reve.refoulement.value": refoulement });
 | |
|     return roll;
 | |
|   }
 | |
| 
 | |
|   /* -------------------------------------------- */
 | |
|   async ajouterSouffle(options = { chat: false }) {
 | |
|     let souffle = await RdDRollTables.getSouffle()
 | |
|     //souffle.id = undefined; //TBC
 | |
|     await this.createEmbeddedDocuments('Item', [souffle]);
 | |
|     if (options.chat) {
 | |
|       ChatMessage.create({
 | |
|         whisper: ChatUtility.getOwners(this),
 | |
|         content: this.name + " subit un Souffle de Dragon  : " + souffle.name
 | |
|       });
 | |
|     }
 | |
|     return souffle;
 | |
|   }
 | |
| 
 | |
|   /* -------------------------------------------- */
 | |
|   async ajouterQueue(options = { chat: false }) {
 | |
|     let queue;
 | |
|     if (this.system.reve.reve.thanatosused) {
 | |
|       queue = await RdDRollTables.getOmbre();
 | |
|       await this.update({ "system.reve.reve.thanatosused": false });
 | |
|     }
 | |
|     else {
 | |
|       queue = await RdDRollTables.getQueue();
 | |
|     }
 | |
|     await this.createEmbeddedDocuments('Item', [queue]);
 | |
|     if (options.chat) {
 | |
|       ChatMessage.create({
 | |
|         whisper: ChatUtility.getOwners(this),
 | |
|         content: this.name + " subit une Queue de Dragon : " + queue.name
 | |
|       });
 | |
|     }
 | |
|     return queue;
 | |
|   }
 | |
| 
 | |
|   /* -------------------------------------------- */
 | |
|   /* -------------------------------------------- */
 | |
|   async changeTMRVisible() {
 | |
|     await this.setTMRVisible(this.system.reve.tmrpos.cache ? true : false);
 | |
|   }
 | |
| 
 | |
|   async setTMRVisible(newState) {
 | |
|     await this.update({ 'system.reve.tmrpos.cache': !newState });
 | |
|     this.notifyRefreshTMR();
 | |
|   }
 | |
| 
 | |
|   isTMRCache() {
 | |
|     return this.system.reve.tmrpos.cache;
 | |
|   }
 | |
| 
 | |
|   notifyRefreshTMR() {
 | |
|     game.socket.emit(SYSTEM_SOCKET_ID, {
 | |
|       msg: "msg_tmr_move", data: {
 | |
|         actorId: this._id,
 | |
|         tmrPos: this.system.reve.tmrpos
 | |
|       }
 | |
|     });
 | |
|   }
 | |
| 
 | |
|   /* -------------------------------------------- */
 | |
|   async addDonDeHautReve() {
 | |
|     if (!game.user.isGM || this.isHautRevant()) {
 | |
|       return
 | |
|     }
 | |
|     const donHR = await RdDItemTete.teteDonDeHautReve()
 | |
|     if (donHR) {
 | |
|       this.createEmbeddedDocuments('Item', [donHR.toObject()])
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   async addSortReserve(item) {
 | |
|     if (item?.type == ITEM_TYPES.sort && !item.system.isrituel) {
 | |
|       this.$createSortReserve(item)
 | |
|       return
 | |
|     }
 | |
|     const selectSortReserve = {
 | |
|       title: "Créer un sort en réserve",
 | |
|       label: "Choisir un sort",
 | |
|       list: this.itemTypes[ITEM_TYPES.sort].filter(it => !it.system.isrituel)
 | |
|     }
 | |
|     DialogSelect.select(selectSortReserve, sort => this.$createSortReserve(sort))
 | |
|   }
 | |
| 
 | |
|   $createSortReserve(sort) {
 | |
|     this.createEmbeddedDocuments("Item",
 | |
|       [{
 | |
|         type: ITEM_TYPES.sortreserve,
 | |
|         name: sort.name,
 | |
|         img: sort.img,
 | |
|         system: { sortid: sort.id, draconic: sort.system.draconic, ptreve: Number(sort.system.ptreve.match(/\d+/)), coord: 'A1', heurecible: 'Vaisseau' }
 | |
|       }],
 | |
|       { renderSheet: true })
 | |
|   }
 | |
|   /* -------------------------------------------- */
 | |
|   async reinsertionAleatoire(raison, accessible = tmr => true) {
 | |
|     const innaccessible = this.buildTMRInnaccessible();
 | |
|     let tmr = await TMRUtility.getTMRAleatoire(tmr => accessible(tmr) && !innaccessible.includes(tmr.coord));
 | |
|     ChatMessage.create({
 | |
|       content: `${raison} : ré-insertion aléatoire.`,
 | |
|       whisper: ChatUtility.getOwners(this)
 | |
|     });
 | |
|     await this.forcerPositionTMRInconnue(tmr);
 | |
|     return tmr;
 | |
|   }
 | |
| 
 | |
|   async forcerPositionTMRInconnue(tmr) {
 | |
|     await this.setTMRVisible(false);
 | |
|     await this.updateCoordTMR(tmr.coord);
 | |
|     this.notifyRefreshTMR();
 | |
|   }
 | |
| 
 | |
|   /* -------------------------------------------- */
 | |
|   buildTMRInnaccessible() {
 | |
|     return this.items.filter(it => it.type == ITEM_TYPES.casetmr).filter(it => EffetsDraconiques.isInnaccessible(it)).map(it => it.system.coord)
 | |
|   }
 | |
| 
 | |
|   /* -------------------------------------------- */
 | |
|   getRencontresTMR() {
 | |
|     return this.itemTypes[ITEM_TYPES.rencontre];
 | |
|   }
 | |
| 
 | |
|   /* -------------------------------------------- */
 | |
| 
 | |
|   getRencontreTMREnAttente() {
 | |
|     const position = this.getDemiReve()
 | |
|     return this.itemTypes[ITEM_TYPES.rencontre].find(it => it.system.coord == position)
 | |
|   }
 | |
| 
 | |
|   /* -------------------------------------------- */
 | |
|   async addTMRRencontre(currentRencontre) {
 | |
|     const toCreate = currentRencontre.toObject();
 | |
|     console.log('actor.addTMRRencontre(', toCreate, ')');
 | |
|     this.createEmbeddedDocuments('Item', [toCreate]);
 | |
|   }
 | |
| 
 | |
|   /* -------------------------------------------- */
 | |
|   async updateCoordTMR(coord) {
 | |
|     await this.update({ "system.reve.tmrpos.coord": coord });
 | |
|   }
 | |
| 
 | |
|   /* -------------------------------------------- */
 | |
|   async reveActuelIncDec(value) {
 | |
|     const reve = Math.max(this.system.reve.reve.value + value, 0);
 | |
|     await this.update({ "system.reve.reve.value": reve });
 | |
|   }
 | |
| 
 | |
|   async chanceActuelleIncDec(value) {
 | |
|     const chance = Math.min(this.getChance(), Math.max(this.getChanceActuel() + value, 0));
 | |
|     await this.update({ "system.compteurs.chance.value": chance });
 | |
|   }
 | |
| 
 | |
|   /* -------------------------------------------- */
 | |
|   async regainPointDeSeuil() {
 | |
|     const seuil = Misc.toInt(this.system.reve.seuil.value);
 | |
|     const seuilMax = Misc.toInt(this.system.carac.reve.value)
 | |
|       + 2 * EffetsDraconiques.countAugmentationSeuil(this);
 | |
| 
 | |
|     if (seuil < seuilMax) {
 | |
|       await this.setPointsDeSeuil(Math.min(seuil + 1, seuilMax));
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   /* -------------------------------------------- */
 | |
|   async setPointsDeSeuil(seuil) {
 | |
|     await this.update({ "system.reve.seuil.value": seuil });
 | |
|   }
 | |
| 
 | |
|   /* -------------------------------------------- */
 | |
|   async setPointsDeChance(chance) {
 | |
|     await this.updateCompteurValue("chance", chance);
 | |
|   }
 | |
| 
 | |
|   async jetEndurance(resteEndurance = undefined) {
 | |
|     const result = super.jetEndurance(resteEndurance);
 | |
|     if (result.jetEndurance == 1) {
 | |
|       ChatMessage.create({ content: await this._gainXpConstitutionJetEndurance() });
 | |
|     }
 | |
|     return result;
 | |
|   }
 | |
| 
 | |
|   /* -------------------------------------------- */
 | |
|   getSConst() { return RdDCarac.calculSConst(this.getConstitution()) }
 | |
| 
 | |
|   async ajoutXpConstitution(xp) {
 | |
|     await this.update({ "system.carac.constitution.xp": Misc.toInt(this.system.carac.constitution.xp) + xp });
 | |
|   }
 | |
| 
 | |
|   async _gainXpConstitutionJetEndurance() {
 | |
|     await this.ajoutXpConstitution(1); // +1 XP !
 | |
|     return `${this.name} a obtenu 1 sur son Jet d'Endurance et a gagné 1 point d'Expérience en Constitution. Ce point d'XP a été ajouté automatiquement.`;
 | |
|   }
 | |
| 
 | |
|   /* -------------------------------------------- */
 | |
|   async jetDeMoral(situation, messageReussi = undefined, messageManque = undefined) {
 | |
|     const jetMoral = await this._jetDeMoral(situation);
 | |
|     const finMessage = (jetMoral.succes ? messageReussi : messageManque) ?? (jetMoral.ajustement == 0 ? "Vous gardez votre moral" : jetMoral.ajustement > 0 ? "Vous gagnez du moral" : "Vous perdez du moral");
 | |
|     ChatMessage.create({
 | |
|       whisper: ChatUtility.getOwners(this),
 | |
|       content: `${finMessage} - jet ${jetMoral.succes ? "réussi" : "manqué"} en situation ${situation} (${jetMoral.jet}/${jetMoral.difficulte}).`
 | |
|     });
 | |
|     return jetMoral.ajustement;
 | |
|   }
 | |
| 
 | |
|   async _jetDeMoral(situation) {
 | |
|     const moralActuel = Misc.toInt(this.system.compteurs.moral.value);
 | |
|     const jet = await RdDDice.rollTotal("1d20");
 | |
|     const difficulte = 10 + moralActuel;
 | |
|     const succes = jet <= difficulte;
 | |
|     const jetMoral = {
 | |
|       actuel: moralActuel,
 | |
|       jet: jet,
 | |
|       situation: situation,
 | |
|       difficulte: difficulte,
 | |
|       succes: succes,
 | |
|       ajustement: this._calculAjustementMoral(succes, moralActuel, situation)
 | |
|     };
 | |
|     await this.moralIncDec(jetMoral.ajustement);
 | |
|     return jetMoral;
 | |
|   }
 | |
| 
 | |
|   /* -------------------------------------------- */
 | |
|   async moralIncDec(ajustementMoral) {
 | |
|     if (ajustementMoral != 0) {
 | |
|       const startMoral = parseInt(this.system.compteurs.moral.value)
 | |
|       const moralTheorique = startMoral + ajustementMoral
 | |
|       if (moralTheorique > 3) { // exaltation
 | |
|         const ajoutExaltation = moralTheorique - 3
 | |
|         const exaltation = parseInt(this.system.compteurs.exaltation.value) + ajoutExaltation
 | |
|         await this.updateCompteurValue('exaltation', exaltation)
 | |
|       }
 | |
|       if (moralTheorique < -3) { // dissolution
 | |
|         const ajoutDissolution = -3 - moralTheorique
 | |
|         const dissolution = parseInt(this.system.compteurs.dissolution.value) + ajoutDissolution
 | |
|         await this.updateCompteurValue('dissolution', dissolution)
 | |
|       }
 | |
|       await this.updateCompteurValue('moral', Math.max(-3, Math.min(moralTheorique, 3)));
 | |
|     }
 | |
|     return this.system.compteurs.moral.value;
 | |
|   }
 | |
| 
 | |
|   /* -------------------------------------------- */
 | |
|   _calculAjustementMoral(succes, moral, situation) {
 | |
|     switch (situation) {
 | |
|       case 'heureux': case 'heureuse': return succes ? 1 : 0;
 | |
|       case 'malheureuse': case 'malheureux': return succes ? 0 : -1;
 | |
|       case 'neutre':
 | |
|         if (succes && moral < 0) return 1;
 | |
|         if (!succes && moral > 0) return -1;
 | |
|     }
 | |
|     return 0;
 | |
|   }
 | |
| 
 | |
|   /* -------------------------------------------- */
 | |
|   async jetEthylisme() {
 | |
|     let rollData = {
 | |
|       vie: this.system.sante.vie.max,
 | |
|       forceAlcool: 0,
 | |
|       etat: this.getEtatGeneral({ ethylisme: true }),
 | |
|       diffNbDoses: -Number(this.system.compteurs.ethylisme.nb_doses || 0),
 | |
|       finalLevel: 0,
 | |
|       diffConditions: 0,
 | |
|       ajustementsForce: CONFIG.RDD.difficultesLibres
 | |
|     }
 | |
|     let html = await renderTemplate('systems/foundryvtt-reve-de-dragon/templates/dialog-roll-ethylisme.hbs', rollData);
 | |
|     new RdDRollDialogEthylisme(html, rollData, this, r => this.saouler(r.forceAlcool)).render(true);
 | |
|   }
 | |
| 
 | |
|   async mangerNourriture(item) {
 | |
|     return (await DialogConsommer.create(this, item)).render(true);
 | |
|   }
 | |
| 
 | |
|   async actionLire(item) {
 | |
|     const tache = await this.creerTacheDepuisLivre(item, { renderSheet: false });
 | |
|     if (tache) {
 | |
|       await this.rollTache(tache.id);
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   async fabriquerDecoctionHerbe(item) {
 | |
|     if (item.isHerbeAPotion()) {
 | |
|       return DialogFabriquerPotion.create(this, item);
 | |
|     }
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   /* -------------------------------------------- */
 | |
|   async consommer(item, choix) {
 | |
|     switch (item.type) {
 | |
|       case 'nourritureboisson':
 | |
|       case 'herbe': case 'faune':
 | |
|         return await this.consommerNourritureboisson(item.id, choix);
 | |
|       case 'potion':
 | |
|         return await this.consommerPotion(item)
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   /* -------------------------------------------- */
 | |
|   async consommerNourritureboisson(itemId, choix = { doses: 1, seForcer: false, supprimerSiZero: false }) {
 | |
|     if (!this.isOwner) {
 | |
|       RdDBaseActor.remoteActorCall({
 | |
|         tokenId: this.token?.id,
 | |
|         actorId: this.id,
 | |
|         method: 'consommerNourritureboisson', args: [itemId, choix]
 | |
|       })
 | |
|       return
 | |
|     }
 | |
|     const item = this.getItem(itemId)
 | |
|     if (!item.getUtilisationCuisine()) {
 | |
|       return;
 | |
|     }
 | |
|     if (choix.doses > item.system.quantite) {
 | |
|       ui.notifications.warn(`Il n'y a pas assez de ${item.name} pour manger ${choix.doses}`)
 | |
|       return;
 | |
|     }
 | |
|     if (!this._apprecierCuisine(item, choix.seForcer)) {
 | |
|       ui.notifications.info(`${this.name} ne n'arrive pas à manger de ${item.name}`)
 | |
|       return;
 | |
|     }
 | |
|     await this.manger(item, choix.doses, { diminuerQuantite: false });
 | |
|     await this.boire(item, choix.doses, { diminuerQuantite: false });
 | |
|     await item.diminuerQuantite(choix.doses, choix);
 | |
|   }
 | |
| 
 | |
|   async _apprecierCuisine(item, seForcer) {
 | |
|     const surmonteExotisme = await this._surmonterExotisme(item, seForcer);
 | |
|     if (surmonteExotisme) {
 | |
|       await this.apprecier(CARACS.ODORATGOUT, 'cuisine', item.system.qualite, item.system.boisson ? "apprécie la boisson" : "apprécie le plat");
 | |
|     }
 | |
|     else if (seForcer) {
 | |
|       await this.jetDeMoral('malheureux');
 | |
|     }
 | |
|     else {
 | |
|       return false;
 | |
|     }
 | |
|     return true;
 | |
|   }
 | |
| 
 | |
|   /* -------------------------------------------- */
 | |
|   async _surmonterExotisme(item) {
 | |
|     const qualite = Math.min(item.system.qualite, 0)
 | |
|     const exotisme = item.system.exotisme
 | |
|     if (exotisme < 0 || qualite < 0) {
 | |
|       const competence = qualite > 0 ? 'cuisine' : undefined
 | |
|       const difficulte = Math.min(exotisme, qualite)
 | |
|       const rolled = await this.doRollCaracCompetence(CARACS.VOLONTE, competence, difficulte, { title: `tente de surmonter l'exotisme de ${item.name}` })
 | |
|       return rolled.isSuccess
 | |
|     }
 | |
|     return true;
 | |
|   }
 | |
| 
 | |
|   /* -------------------------------------------- */
 | |
|   async apprecier(carac, compName, qualite, title) {
 | |
|     const competence = this.getCompetence(compName);
 | |
|     const minQualite = Math.max(1, competence?.system.niveau ?? 0);
 | |
|     if (qualite <= minQualite) {
 | |
|       ui.notifications.info(`${this.name} a un niveau ${competence.system.niveau} en ${competence.name}, trop élevé pour apprécier la qualité de ${qualite}`)
 | |
|       return;
 | |
|     }
 | |
|     const rolled = await this.doRollCaracCompetence(carac, undefined, 0, { title });
 | |
|     if (rolled?.isSuccess) {
 | |
|       await this.jetDeMoral('heureux');
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   /* -------------------------------------------- */
 | |
|   async manger(item, doses, options = { diminuerQuantite: true }) {
 | |
|     const sust = item.system.sust
 | |
|     if (sust > 0) {
 | |
|       await this.updateCompteurValue('sust', RdDActor.$calculNewSust(this.system.compteurs.sust.value, sust, doses));
 | |
|     }
 | |
|     await item.diminuerQuantite(doses, options);
 | |
|   }
 | |
| 
 | |
| 
 | |
|   /* -------------------------------------------- */
 | |
|   async boire(item, doses, options = { diminuerQuantite: true }) {
 | |
|     const desaltere = item.system.desaltere;
 | |
|     if (desaltere > 0) {
 | |
|       await this.updateCompteurValue('eau', RdDActor.$calculNewSust(this.system.compteurs.eau.value, desaltere, doses));
 | |
|     }
 | |
|     if (item.isAlcool()) {
 | |
|       for (let i = 0; i < doses; i++) {
 | |
|         await this.saouler(item.system.force, item);
 | |
|       }
 | |
|     }
 | |
|     await item.diminuerQuantite(doses, options);
 | |
|   }
 | |
| 
 | |
|   static $calculNewSust(value, sust, doses) {
 | |
|     return Misc.keepDecimals(Number(value) + Number(sust) * Number(doses), 1);
 | |
|   }
 | |
| 
 | |
|   /* -------------------------------------------- */
 | |
|   async saouler(forceAlcool, alcool = undefined) {
 | |
|     let ethylisme = foundry.utils.duplicate(this.system.compteurs.ethylisme);
 | |
| 
 | |
|     const etat = this.getEtatGeneral({ ethylisme: true });
 | |
|     const nbDoses = Number(this.system.compteurs.ethylisme.nb_doses || 0);
 | |
|     const ethylismeData = {
 | |
|       alias: this.getAlias(),
 | |
|       actor: this,
 | |
|       vie: this.system.sante.vie.max,
 | |
|       alcool: alcool,
 | |
|       jetVie: {
 | |
|         forceAlcool: forceAlcool,
 | |
|         nbDoses: nbDoses,
 | |
|         selectedCarac: this.system.sante.vie,
 | |
|         jetResistance: 'ethylisme',
 | |
|         carac: this.system.carac,
 | |
|         caracValue: this.system.sante.vie.max,
 | |
|         finalLevel: etat + forceAlcool - nbDoses
 | |
|       },
 | |
|     }
 | |
| 
 | |
|     await RdDResolutionTable.rollData(ethylismeData.jetVie);
 | |
|     this.gererExperience(ethylismeData.jetVie);
 | |
|     RollDataAjustements.calcul(ethylismeData.jetVie, this);
 | |
|     if (ethylismeData.jetVie.rolled.isSuccess) {
 | |
|       ethylisme.nb_doses++;
 | |
|     } else {
 | |
|       ethylisme.value = Math.max(ethylisme.value - 1, -7);
 | |
|       ethylisme.nb_doses = 0;
 | |
| 
 | |
|       let perte = await RdDDice.rollTotal("1d6");
 | |
|       ethylismeData.perteEndurance = await this.santeIncDec("endurance", -perte);
 | |
| 
 | |
|       if (!ethylisme.jet_moral) {
 | |
|         ethylismeData.jetMoral = await this._jetDeMoral('heureuse');
 | |
|         if (ethylismeData.jetMoral.ajustement == 1) {
 | |
|           ethylismeData.moralAlcool = 'heureux';
 | |
|           ethylisme.jet_moral = true;
 | |
|         } else if (ethylisme.value == -1) {
 | |
|           ethylismeData.jetMoral.ajustement = -1;
 | |
|           ethylismeData.moralAlcool = 'triste';
 | |
|           ethylisme.jet_moral = true;
 | |
|           await this.moralIncDec(-1);
 | |
|         }
 | |
|       }
 | |
|       if (ethylisme.value < 0) {
 | |
|         // Qui a bu boira (p 164)
 | |
|         ethylismeData.jetVolonte = {
 | |
|           selectedCarac: this.system.carac.volonte,
 | |
|           caracValue: this.system.carac.volonte.value,
 | |
|           ethylisme: Number(ethylisme.value),
 | |
|           finalLevel: Number(ethylisme.value) + Number(this.system.compteurs.moral.value)
 | |
|         }
 | |
|         await RdDResolutionTable.rollData(ethylismeData.jetVolonte);
 | |
|         this.gererExperience(ethylismeData.jetVolonte);
 | |
|         RollDataAjustements.calcul(ethylismeData.jetVolonte, this);
 | |
|       }
 | |
|     }
 | |
|     ethylismeData.ajustementEthylique = ethylisme.value;
 | |
|     ethylismeData.nomEthylisme = RdDUtility.getNomEthylisme(ethylisme.value);
 | |
|     ethylismeData.doses = ethylisme.nb_doses;
 | |
| 
 | |
|     await this.update({ 'system.compteurs.ethylisme': ethylisme });
 | |
|     await RdDRollResult.displayRollData(ethylismeData, this, 'chat-resultat-ethylisme.hbs');
 | |
|   }
 | |
| 
 | |
|   /* -------------------------------------------- */
 | |
|   async jetGoutCuisine() {
 | |
|     console.info('Jet de Gout/Cuisine');
 | |
|     return true;
 | |
|   }
 | |
| 
 | |
|   /* -------------------------------------------- */
 | |
|   async transformerStress() {
 | |
|     if (!ReglesOptionnelles.isUsing("transformation-stress")) { return }
 | |
|     const fromStress = Number(this.system.compteurs.stress.value);
 | |
|     if (this.system.sommeil?.insomnie || fromStress <= 0) {
 | |
|       return;
 | |
|     }
 | |
| 
 | |
|     const stressRoll = await this._stressRoll(this.getReveActuel());
 | |
| 
 | |
|     const conversion = Math.floor(fromStress * stressRoll.factor / 100);
 | |
|     let dissolution = Math.max(0, Number(this.system.compteurs.dissolution.value));
 | |
|     let exaltation = Math.max(0, Number(this.system.compteurs.exaltation.value));
 | |
|     const annule = Math.min(dissolution, exaltation);
 | |
|     dissolution -= annule;
 | |
|     exaltation -= annule;
 | |
|     const perteDissolution = Math.max(0, Math.min(dissolution, conversion));
 | |
| 
 | |
|     let stressRollData = {
 | |
|       alias: this.getAlias(),
 | |
|       selectedCarac: this.system.carac.reve,
 | |
|       rolled: stressRoll,
 | |
|       stress: fromStress,
 | |
|       perte: Math.min(conversion, fromStress),
 | |
|       convertis: conversion - perteDissolution,
 | |
|       xp: conversion - perteDissolution + exaltation,
 | |
|       dissolution: dissolution,
 | |
|       exaltation: exaltation
 | |
|     };
 | |
| 
 | |
|     ChatMessage.create({
 | |
|       whisper: ChatUtility.getOwners(this),
 | |
|       content: await renderTemplate(`systems/foundryvtt-reve-de-dragon/templates/chat-resultat-transformer-stress.hbs`, stressRollData)
 | |
|     });
 | |
| 
 | |
|     const toStress = Math.max(fromStress - stressRollData.perte - 1, 0);
 | |
|     const fromXpSress = Number(this.system.compteurs.experience.value);
 | |
|     const toXpStress = fromXpSress + Number(stressRollData.xp);
 | |
|     const updates = {
 | |
|       "system.compteurs.stress.value": toStress,
 | |
|       "system.compteurs.experience.value": toXpStress,
 | |
|       "system.compteurs.dissolution.value": dissolution - perteDissolution,
 | |
|       "system.compteurs.exaltation.value": 0
 | |
|     }
 | |
|     await this.update(updates);
 | |
|     await ExperienceLog.add(this, XP_TOPIC.STRESS, fromStress, toStress, 'Transformation')
 | |
|     await ExperienceLog.add(this, XP_TOPIC.TRANSFORM, fromXpSress, toXpStress, 'Transformation')
 | |
|   }
 | |
| 
 | |
|   /* -------------------------------------------- */
 | |
|   async _stressRoll(reveActuel) {
 | |
|     let result = await RdDResolutionTable.roll(reveActuel, 0);
 | |
|     if (result.isPart) {
 | |
|       result.second = await RdDResolutionTable.roll(reveActuel, 0);
 | |
|     }
 | |
|     result.factor = this._getFacteurStress(result);
 | |
|     return result;
 | |
|   }
 | |
| 
 | |
|   /* -------------------------------------------- */
 | |
|   _getFacteurStress(stressRoll) {
 | |
|     switch (stressRoll.code) {
 | |
|       case "sign": return 75;
 | |
|       case "norm": return 50;
 | |
|       case "echec": return 20;
 | |
|       case "epart": return 10;
 | |
|       case "etotal": return 0;
 | |
|       case "part":
 | |
|         if (stressRoll.second.isSign) {
 | |
|           stressRoll.quality = "Double Particulière";
 | |
|           return 150;
 | |
|         }
 | |
|         return 100;
 | |
|     }
 | |
|     return 0;
 | |
|   }
 | |
| 
 | |
|   /* -------------------------------------------- */
 | |
|   createCallbackExperience() {
 | |
|     return {
 | |
|       action: r => this.appliquerAjoutExperience(r)
 | |
|     };
 | |
|   }
 | |
| 
 | |
|   /* -------------------------------------------- */
 | |
|   createCallbackAppelAuMoral() { /* Si l'appel au moral est utilisé, on l'affiche dans le chat et on diminue éventuellement le moral */
 | |
|     return {
 | |
|       action: r => this.appliquerAppelMoral(r)
 | |
|     };
 | |
|   }
 | |
| 
 | |
| 
 | |
|   /* -------------------------------------------- */
 | |
|   isCaracMax(code) {
 | |
|     return RdDItemRace.isRacialMax(this, code)
 | |
|   }
 | |
| 
 | |
|   async checkCaracXP(caracName, display = true) {
 | |
|     let carac = this.findCaracByName(caracName);
 | |
|     if (carac && carac.xp > 0) {
 | |
|       const niveauSuivant = Number(carac.value) + 1;
 | |
|       let xpNeeded = RdDCarac.getCaracNextXp(niveauSuivant);
 | |
|       if (carac.xp >= xpNeeded) {
 | |
|         carac = foundry.utils.duplicate(carac);
 | |
|         carac.value = niveauSuivant;
 | |
| 
 | |
|         let checkXp = {
 | |
|           alias: this.getAlias(),
 | |
|           carac: caracName,
 | |
|           value: niveauSuivant,
 | |
|           xp: carac.xp
 | |
|         }
 | |
|         if (display) {
 | |
|           ChatMessage.create({
 | |
|             whisper: ChatUtility.getOwners(this),
 | |
|             content: await renderTemplate(`systems/foundryvtt-reve-de-dragon/templates/chat-actor-carac-xp.hbs`, checkXp)
 | |
|           });
 | |
|         }
 | |
|         return checkXp;
 | |
|       }
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   /* -------------------------------------------- */
 | |
|   async checkCompetenceXP(compName, newXP, display = true) {
 | |
|     let compData = this.getCompetence(compName);
 | |
|     if (compData && newXP && newXP == compData.system.xp) { // Si édition, mais sans changement XP
 | |
|       return;
 | |
|     }
 | |
|     newXP = (newXP) ? newXP : compData.system.xp;
 | |
|     if (compData && newXP > 0) {
 | |
|       let xpNeeded = RdDItemCompetence.getCompetenceNextXp(compData.system.niveau + 1);
 | |
|       if (newXP >= xpNeeded) {
 | |
|         let newCompData = foundry.utils.duplicate(compData);
 | |
|         newCompData.system.niveau += 1;
 | |
|         newCompData.system.xp = newXP;
 | |
|         let checkXp = {
 | |
|           alias: this.getAlias(),
 | |
|           competence: newCompData.name,
 | |
|           niveau: newCompData.system.niveau,
 | |
|           xp: newCompData.system.xp,
 | |
|           archetype: newCompData.system.niveau_archetype,
 | |
|           archetypeWarning: newCompData.system.niveau > compData.system.niveau_archetype
 | |
|         }
 | |
|         if (display) {
 | |
|           ChatMessage.create({
 | |
|             whisper: ChatUtility.getOwners(this),
 | |
|             content: await renderTemplate(`systems/foundryvtt-reve-de-dragon/templates/chat-actor-competence-xp.hbs`, checkXp)
 | |
|           });
 | |
|         }
 | |
|         return checkXp;
 | |
|       }
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   /* -------------------------------------------- */
 | |
|   async appliquerAjoutExperience(rollData, hideChatMessage = 'show') {
 | |
|     if (!rollData.rolled.isPart ||
 | |
|       rollData.finalLevel >= 0 ||
 | |
|       game.settings.get("core", "rollMode") == 'selfroll' ||
 | |
|       !Misc.hasConnectedGM()) {
 | |
|       return
 | |
|     }
 | |
|     hideChatMessage = hideChatMessage == 'hide' || (Misc.isRollModeHiddenToPlayer() && !game.user.isGM)
 | |
|     let xpData = await this._appliquerExperience(rollData.rolled, rollData.selectedCarac.label, rollData.competence, rollData.jetResistance);
 | |
|     if (xpData.length) {
 | |
|       const content = await renderTemplate(`systems/foundryvtt-reve-de-dragon/templates/chat-actor-gain-xp.hbs`, {
 | |
|         actor: this,
 | |
|         xpData
 | |
|       })
 | |
|       if (hideChatMessage) {
 | |
|         ChatUtility.blindMessageToGM({ content: content })
 | |
|       }
 | |
|       else {
 | |
|         ChatMessage.create({
 | |
|           whisper: ChatUtility.getOwners(this),
 | |
|           content: content
 | |
|         });
 | |
|       }
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   /* -------------------------------------------- */
 | |
|   async appliquerAppelMoral(rollData) {
 | |
|     if (!rollData.use.moral || game.settings.get("core", "rollMode") == 'selfroll') {
 | |
|       return
 | |
|     }
 | |
|     if (rollData.rolled.isEchec ||
 | |
|       (rollData.diviseurSignificative && (rollData.rolled.roll * rollData.diviseurSignificative > rollData.rolled.score))) {
 | |
|       rollData.perteMoralEchec = rollData.moral <= -3 ? 'dissolution' : 'perte';
 | |
|       rollData.moral = await this.moralIncDec(-1); /* L'appel au moral a échoué. Le personnage perd un point de moral */
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   /* -------------------------------------------- */
 | |
|   $filterSortList(sortList, coord) {
 | |
|     return sortList.filter(it => RdDItemSort.isSortOnCoord(it, coord))
 | |
|   }
 | |
| 
 | |
|   /* -------------------------------------------- */
 | |
|   computeDraconicAndSortIndex(sortList) {
 | |
|     let draconicList = this.getDraconics();
 | |
|     for (let sort of sortList) {
 | |
|       let draconicsSort = RdDItemSort.getDraconicsSort(draconicList, sort).map(it => it.name);
 | |
|       for (let index = 0; index < draconicList.length && sort.system.listIndex == undefined; index++) {
 | |
|         if (draconicsSort.includes(draconicList[index].name)) {
 | |
|           sort.system.listIndex = index;
 | |
|         }
 | |
|       }
 | |
|     }
 | |
|     return draconicList;
 | |
|   }
 | |
| 
 | |
|   /* -------------------------------------------- */
 | |
|   async rollUnSort(coord) {
 | |
|     if (RdDEmpoignade.checkEmpoignadeEnCours(this)) {
 | |
|       return
 | |
|     }
 | |
|     if (EffetsDraconiques.isSortImpossible(this)) {
 | |
|       ui.notifications.error("Une queue ou un souffle vous empèche de lancer de sort!")
 | |
|       return
 | |
|     }
 | |
|     if (OptionsAvancees.isUsing(ROLL_DIALOG_V2)) {
 | |
|       return await this.rollUnSortV2();
 | |
|     }
 | |
|     // Duplication car les pts de reve sont modifiés dans le sort!
 | |
|     let sorts = foundry.utils.duplicate(this.itemTypes[ITEM_TYPES.sort].filter(it => RdDItemSort.isSortOnCoord(it, coord)))
 | |
|     if (sorts.length == 0) {
 | |
|       ui.notifications.info(`Aucun sort disponible en ${TMRUtility.getTMR(coord).label} !`);
 | |
|       return
 | |
|     }
 | |
| 
 | |
|     const draconicList = this.computeDraconicAndSortIndex(sorts)
 | |
|     const reve = foundry.utils.duplicate(this.system.carac.reve)
 | |
| 
 | |
|     const dialog = await this.openRollDialog({
 | |
|       name: 'lancer-un-sort',
 | |
|       label: 'Lancer un sort',
 | |
|       template: 'systems/foundryvtt-reve-de-dragon/templates/dialog-roll-sort.hbs',
 | |
|       rollData: {
 | |
|         carac: { 'reve': reve },
 | |
|         forceCarac: { 'reve': reve },
 | |
|         selectedCarac: reve,
 | |
|         draconicList: draconicList,
 | |
|         competence: draconicList[0],
 | |
|         sortList: sorts,
 | |
|         selectedSort: sorts[0],
 | |
|         tmr: TMRUtility.getTMR(coord),
 | |
|         diffLibre: RdDItemSort.getDifficulte(sorts[0], -7), // Per default at startup
 | |
|         coutreve: Array(30).fill().map((item, index) => 1 + index),
 | |
|       },
 | |
|       callbacks: [{ action: r => this._rollUnSortResult(r) }]
 | |
|     });
 | |
|     this.tmrApp?.setTMRPendingAction(dialog);
 | |
|   }
 | |
| 
 | |
|   async rollUnSortV2() {
 | |
|     const rollData = {
 | |
|       ids: { actorId: this.id },
 | |
|       type: { allowed: [ROLL_TYPE_SORT], current: ROLL_TYPE_SORT }
 | |
|     };
 | |
|     const dialog = await RollDialog.create(rollData, {
 | |
|       callbacks: [roll => {
 | |
|         this.tmrApp?.restoreTMRAfterAction();
 | |
|         if (roll.closeTMR) {
 | |
|           this.tmrApp?.close();
 | |
|           this.tmrApp = undefined;
 | |
|         }
 | |
|       } ],
 | |
|       onRollDone: RollDialog.onRollDoneClose,
 | |
|       onClose: () => {
 | |
|         this.tmrApp?.restoreTMRAfterAction();
 | |
|       }
 | |
|     });
 | |
|     this.tmrApp?.setTMRPendingAction(dialog);
 | |
|   }
 | |
| 
 | |
|   /* -------------------------------------------- */
 | |
|   isMauvaiseRencontre() { // Gestion queue/souffle 'Mauvaise Rencontre en Perpective'
 | |
|     let addMsg = "";
 | |
|     let rencSpecial = EffetsDraconiques.mauvaiseRencontre(this);
 | |
|     if (rencSpecial) {
 | |
|       if (rencSpecial.type != 'souffle') {
 | |
|         this.deleteEmbeddedDocuments('Item', [rencSpecial.id]); // Suppression dans la liste des queues
 | |
|         addMsg = " La queue a été supprimée de votre fiche automatiquement";
 | |
|       } else {
 | |
|         addMsg = " Vous devez gérer manuellement le décompte de mauvaises rencontres.";
 | |
|       }
 | |
|       ChatMessage.create({
 | |
|         content: "Vous êtes sous le coup d'une Mauvaise Rencontre en Persective." + addMsg,
 | |
|         whisper: ChatUtility.getOwners(this)
 | |
|       });
 | |
|     }
 | |
|     return rencSpecial;
 | |
|   }
 | |
| 
 | |
|   /* -------------------------------------------- */
 | |
|   getCoutFatigueTMR() { // Pour l'instant uniquement Inertie Draconique
 | |
|     let countInertieDraconique = EffetsDraconiques.countInertieDraconique(this);
 | |
|     if (countInertieDraconique > 0) {
 | |
|       ChatMessage.create({
 | |
|         content: `Vous êtes sous le coup d'Inertie Draconique : vous perdrez ${countInertieDraconique + 1} cases de Fatigue par déplacement au lieu d'une.`,
 | |
|         whisper: ChatUtility.getOwners(this)
 | |
|       });
 | |
|     }
 | |
|     return countInertieDraconique + 1;
 | |
|   }
 | |
| 
 | |
|   /* -------------------------------------------- */
 | |
|   async checkSoufflePeage(tmr) {
 | |
|     if ((tmr.type == 'pont' || tmr.type == 'cite') && EffetsDraconiques.isPeage(this)) {
 | |
|       await this.reveActuelIncDec(-1);
 | |
|       ChatMessage.create({
 | |
|         content: "Vous êtes sous le coup d'un Péage : l'entrée sur cette case vous a coûté 1 Point de Rêve (déduit automatiquement).",
 | |
|         whisper: ChatUtility.getOwners(this)
 | |
|       });
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   /* -------------------------------------------- */
 | |
|   async _rollUnSortResult(rollData) {
 | |
|     let rolled = rollData.rolled;
 | |
|     let selectedSort = rollData.selectedSort;
 | |
| 
 | |
|     rollData.isSortReserve = rollData.mettreEnReserve && !selectedSort.system.isrituel;
 | |
|     rollData.show = {}
 | |
|     rollData.depenseReve = Number(selectedSort.system.ptreve_reel);
 | |
| 
 | |
|     if (rollData.competence.name.includes('Thanatos')) { // Si Thanatos
 | |
|       await this.update({ "system.reve.reve.thanatosused": true });
 | |
|     }
 | |
|     let reveActuel = parseInt(this.system.reve.reve.value)
 | |
|     if (rolled.isSuccess) { // Réussite du sort !
 | |
|       if (rolled.isPart) {
 | |
|         rollData.depenseReve = Math.max(Math.floor(rollData.depenseReve / 2), 1);
 | |
|       }
 | |
|       if (rollData.isSortReserve) {
 | |
|         rollData.depenseReve++;
 | |
|       }
 | |
|       if (reveActuel > rollData.depenseReve) {
 | |
|         // Incrémenter/gére le bonus de case
 | |
|         RdDItemSort.incrementBonusCase(this, selectedSort, rollData.tmr.coord);
 | |
| 
 | |
|         if (rollData.isSortReserve) {
 | |
|           await this.sortMisEnReserve(selectedSort, rollData.competence, rollData.tmr.coord, Number(selectedSort.system.ptreve_reel));
 | |
|         }
 | |
|         else {
 | |
|           console.log('lancement de sort', rollData.selectedSort)
 | |
|           const lancements = RdDItemSort.prepareSortAddLancement(rollData.selectedSort, rollData.selectedSort.system.ptreve_reel)
 | |
|           await this.updateEmbeddedDocuments('Item',
 | |
|             [{ _id: rollData.selectedSort._id, 'system.lancements': lancements }]
 | |
|           )
 | |
|         }
 | |
|       }
 | |
|       else {
 | |
|         rollData.depenseReve = 0;
 | |
|         rollData.show.reveInsuffisant = true;
 | |
|         foundry.utils.mergeObject(rollData.rolled, RdDResolutionTable.getResultat("echec"), { overwrite: true });
 | |
|       }
 | |
|     } else {
 | |
|       if (rolled.isETotal) { // Echec total !
 | |
|         rollData.depenseReve = Math.min(reveActuel, Math.floor(rollData.depenseReve * 1.5))
 | |
|         // TODO: mise en réserve d'un échec total...
 | |
|         // await dialog mse en réserve, pour traitement échec total
 | |
|       } else {
 | |
|         rollData.depenseReve = 0
 | |
|       }
 | |
|     }
 | |
| 
 | |
|     reveActuel = Math.max(reveActuel - rollData.depenseReve, 0);
 | |
|     await this.update({ "system.reve.reve.value": reveActuel });
 | |
| 
 | |
|     await RdDRollResult.displayRollData(rollData, this, 'chat-resultat-sort.hbs');
 | |
| 
 | |
|     if (reveActuel == 0) { // 0 points de reve
 | |
|       ChatMessage.create({ content: this.name + " est réduit à 0 Points de Rêve, et tombe endormi !" });
 | |
|     }
 | |
|     if (!rollData.isSortReserve || !rolled.isSuccess) {
 | |
|       this.tmrApp?.close();
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   /**
 | |
|    * Méthode pour faire un jet prédéterminer sans ouvrir la fenêtre de dialogue
 | |
|    * @param {*} caracName 
 | |
|    * @param {*} compName 
 | |
|    * @param {*} diff 
 | |
|    * @param {*} options 
 | |
|    * @returns 
 | |
|    */
 | |
|   async doRollCaracCompetence(caracName, compName, diff, options = { title: "" }) {
 | |
|     const carac = this.getCaracByName(caracName);
 | |
|     if (!carac) {
 | |
|       ui.notifications.warn(`${this.name} n'a pas de caractéristique correspondant à ${caracName}`)
 | |
|       return;
 | |
|     }
 | |
|     const competence = this.getCompetence(compName);
 | |
|     let rollData = {
 | |
|       alias: this.getAlias(),
 | |
|       caracValue: Number(carac.value),
 | |
|       selectedCarac: carac,
 | |
|       competence: competence,
 | |
|       diffLibre: diff,
 | |
|       show: { title: options?.title ?? '' }
 | |
|     };
 | |
|     RollDataAjustements.calcul(rollData, this);
 | |
|     await RdDResolutionTable.rollData(rollData);
 | |
|     this.gererExperience(rollData);
 | |
|     await RdDRollResult.displayRollData(rollData, this)
 | |
|     return rollData.rolled;
 | |
|   }
 | |
| 
 | |
|   /* -------------------------------------------- */
 | |
|   gererExperience(rollData) {
 | |
|     this.createCallbackExperience().action(rollData);
 | |
|   }
 | |
| 
 | |
|   /* -------------------------------------------- */
 | |
|   async creerTacheDepuisLivre(item, options = { renderSheet: true }) {
 | |
|     const nomTache = "Lire " + item.name;
 | |
|     const filterTacheLecture = it => it.type == 'tache' && it.name == nomTache;
 | |
|     let tachesExistantes = this.filterItems(filterTacheLecture);
 | |
|     if (tachesExistantes.length == 0) {
 | |
|       const tache = {
 | |
|         name: nomTache, type: 'tache',
 | |
|         system: {
 | |
|           carac: 'intellect',
 | |
|           competence: 'Écriture',
 | |
|           difficulte: item.system.difficulte,
 | |
|           periodicite: "60 minutes",
 | |
|           fatigue: 2,
 | |
|           points_de_tache: item.system.points_de_tache,
 | |
|           points_de_tache_courant: 0,
 | |
|           description: "Lecture du livre " + item.name + " - XP : " + item.system.xp + " - Compétences : " + item.system.competence
 | |
|         }
 | |
|       }
 | |
|       await this.createEmbeddedDocuments('Item', [tache], options);
 | |
|       tachesExistantes = this.filterItems(filterTacheLecture);
 | |
|     }
 | |
|     return tachesExistantes.length > 0 ? tachesExistantes[0] : undefined;
 | |
|   }
 | |
| 
 | |
|   blessuresASoigner() {
 | |
|     return (this.itemTypes[ITEM_TYPES.blessure])
 | |
|       .filter(it => it.system.gravite > 0 && it.system.gravite <= 6)
 | |
|       .filter(it => !(it.system.premierssoins.done && it.system.soinscomplets.done))
 | |
|       .sort(Misc.descending(b => (b.system.premierssoins.done ? "A" : "B") + b.system.gravite))
 | |
|   }
 | |
| 
 | |
|   async getTacheBlessure(blesse, blessure) {
 | |
|     const gravite = blessure?.system.gravite ?? 0;
 | |
|     if (gravite > 0) {
 | |
|       const tache = this.itemTypes['tache'].find(it => it.system.itemId == blessure.id)
 | |
|         ?? await RdDItemBlessure.createTacheSoinBlessure(this, gravite);
 | |
|       await blessure?.updateTacheSoinBlessure(tache);
 | |
|       return tache
 | |
|     }
 | |
|     return undefined;
 | |
|   }
 | |
| 
 | |
|   async rollCaracCompetence(caracName, compName, diff, options = { title: "" }) {
 | |
| 
 | |
|     if (OptionsAvancees.isUsing(ROLL_DIALOG_V2)) {
 | |
|       const rollData = {
 | |
|         ids: { actorId: this.id },
 | |
|         type: { allowed: [PART_COMP], current: PART_COMP },
 | |
|         selected: {
 | |
|           carac: { key: caracName },
 | |
|           comp: { key: compName, forced: options.forced },
 | |
|           diff: { value: diff ?? 0 }
 | |
|         }
 | |
|       }
 | |
|       RollDialog.create(rollData, foundry.utils.mergeObject(options, { onRollDone: RollDialog.onRollDoneClose }))
 | |
|       return
 | |
|     }
 | |
| 
 | |
|     RdDEmpoignade.checkEmpoignadeEnCours(this)
 | |
|     const competence = this.getCompetence(compName);
 | |
|     await this.openRollDialog({
 | |
|       name: 'jet-competence',
 | |
|       label: 'Jet ' + Grammar.apostrophe('de', competence.name),
 | |
|       template: 'systems/foundryvtt-reve-de-dragon/templates/dialog-roll-competence.hbs',
 | |
|       rollData: {
 | |
|         alias: this.getAlias(),
 | |
|         carac: this.system.carac,
 | |
|         selectedCarac: this.getCaracByName(caracName),
 | |
|         selectedCaracName: caracName,
 | |
|         diffLibre: diff,
 | |
|         competence: competence,
 | |
|         show: { title: options?.title ?? '' }
 | |
|       },
 | |
|       // TODO:
 | |
|       callbacks: [{ action: r => this.$onRollCompetence(r, options) }]
 | |
|     });
 | |
|   }
 | |
| 
 | |
|   /* -------------------------------------------- */
 | |
|   async rollTache(id, options = {}) {
 | |
| 
 | |
|     RdDEmpoignade.checkEmpoignadeEnCours(this)
 | |
|     const tache = this.getTache(id)
 | |
|     if (OptionsAvancees.isUsing(ROLL_DIALOG_V2)) {
 | |
|       const rollData = {
 | |
|         ids: { actorId: this.id },
 | |
|         selected: { tache: { key: tache.id, forced: options.forced } },
 | |
|         type: { allowed: [PART_TACHE], current: PART_TACHE }
 | |
|       }
 | |
|       RollDialog.create(rollData, options)
 | |
|       return
 | |
|     }
 | |
| 
 | |
|     const compData = this.getCompetence(tache.system.competence)
 | |
|     compData.system.defaut_carac = tache.system.carac; // Patch !
 | |
| 
 | |
|     await this.openRollDialog({
 | |
|       name: 'jet-competence',
 | |
|       label: 'Jet de Tâche ' + tache.name,
 | |
|       template: 'systems/foundryvtt-reve-de-dragon/templates/dialog-roll-competence.hbs',
 | |
|       rollData: {
 | |
|         competence: compData,
 | |
|         tache: tache,
 | |
|         diffLibre: tache.system.difficulte,
 | |
|         diffConditions: 0,
 | |
|         use: { libre: false, conditions: true },
 | |
|         carac: {
 | |
|           [tache.system.carac]: foundry.utils.duplicate(this.system.carac[tache.system.carac])
 | |
|         }
 | |
|       },
 | |
|       callbacks: [{ action: r => this._tacheResult(r, options) }]
 | |
|     });
 | |
|   }
 | |
| 
 | |
|   /* -------------------------------------------- */
 | |
|   async _tacheResult(rollData, options) {
 | |
|     // Mise à jour de la tache
 | |
|     rollData.appliquerFatigue = ReglesOptionnelles.isUsing("appliquer-fatigue");
 | |
|     rollData.tache = foundry.utils.duplicate(rollData.tache);
 | |
|     rollData.tache.system.points_de_tache_courant += rollData.rolled.ptTache;
 | |
|     if (rollData.rolled.isETotal) {
 | |
|       rollData.tache.system.difficulte--;
 | |
|     }
 | |
|     if (rollData.rolled.isSuccess) {
 | |
|       rollData.tache.system.nb_jet_succes++;
 | |
|     } else {
 | |
|       rollData.tache.system.nb_jet_echec++;
 | |
|     }
 | |
|     rollData.tache.system.tentatives = rollData.tache.system.nb_jet_succes + rollData.tache.system.nb_jet_echec;
 | |
| 
 | |
|     await this.updateEmbeddedDocuments('Item', [rollData.tache]);
 | |
|     await this.santeIncDec("fatigue", rollData.tache.system.fatigue);
 | |
| 
 | |
|     await RdDRollResult.displayRollData(rollData, this, 'chat-resultat-tache.hbs');
 | |
|     if (options?.onRollAutomate) {
 | |
|       options.onRollAutomate(rollData);
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   /* -------------------------------------------- */
 | |
|   async rollJeu(id) {
 | |
|     const jeu = this.getJeu(id);
 | |
| 
 | |
|     if (OptionsAvancees.isUsing(ROLL_DIALOG_V2)) {
 | |
|       const rollData = {
 | |
|         ids: { actorId: this.id },
 | |
|         selected: { jeu: { key: jeu.id } },
 | |
|         type: { allowed: [ROLL_TYPE_JEU], current: ROLL_TYPE_JEU }
 | |
|       }
 | |
|       await RollDialog.create(rollData, { onRollDone: RollDialog.onRollDoneClose })
 | |
|       return
 | |
|     }
 | |
| 
 | |
|     const listCarac = jeu.system.caraccomp.toLowerCase().split(/[.,:\/-]/).map(it => it.trim());
 | |
|     const carac = listCarac.length > 0 ? listCarac[0] : 'chance'
 | |
|     const artData = {
 | |
|       art: 'jeu', verbe: 'Jeu',
 | |
|       use: { libre: true, conditions: true, },
 | |
|       competence: foundry.utils.duplicate(this.getCompetence('jeu')),
 | |
|       forceCarac: {}
 | |
|     };
 | |
|     listCarac.forEach(c => artData.forceCarac[c] = this.system.carac[c]);
 | |
|     artData.competence.system.niveauReel = artData.competence.system.niveau;
 | |
|     artData.competence.system.niveau = Math.max(artData.competence.system.niveau, jeu.system.base);
 | |
|     await this._rollArtV1(artData, carac, jeu);
 | |
|   }
 | |
| 
 | |
| 
 | |
|   /* -------------------------------------------- */
 | |
|   async rollMeditation(id) {
 | |
|     const meditation = foundry.utils.duplicate(this.getMeditation(id));
 | |
| 
 | |
|     if (meditation && OptionsAvancees.isUsing(ROLL_DIALOG_V2)) {
 | |
|       const rollData = {
 | |
|         ids: { actorId: this.id },
 | |
|         selected: { meditation: { key: id } },
 | |
|         type: { allowed: [ROLL_TYPE_MEDITATION], current: ROLL_TYPE_MEDITATION }
 | |
|       }
 | |
|       await RollDialog.create(rollData, { onRollDone: RollDialog.onRollDoneClose })
 | |
|       return
 | |
|     }
 | |
| 
 | |
|     const competence = foundry.utils.duplicate(this.getCompetence(meditation.system.competence));
 | |
|     competence.system.defaut_carac = "intellect"; // Meditation = toujours avec intellect
 | |
|     let meditationData = {
 | |
|       competence: competence,
 | |
|       meditation: meditation,
 | |
|       conditionMeditation: { isHeure: false, isVeture: false, isComportement: false, isPurification: false },
 | |
|       diffConditions: 0,
 | |
|       use: { libre: false, conditions: true, },
 | |
|       carac: { "intellect": this.system.carac.intellect }
 | |
|     };
 | |
| 
 | |
|     const dialog = await RdDRoll.create(this, meditationData,
 | |
|       { html: 'systems/foundryvtt-reve-de-dragon/templates/dialog-roll-meditation.hbs' },
 | |
|       {
 | |
|         name: 'jet-meditation',
 | |
|         label: "Jet de méditation",
 | |
|         callbacks: [
 | |
|           this.createCallbackExperience(),
 | |
|           { action: r => this._meditationResult(r) }
 | |
|         ]
 | |
|       });
 | |
|     dialog.render(true);
 | |
|   }
 | |
| 
 | |
|   /* -------------------------------------------- */
 | |
|   async _meditationResult(meditationRoll) {
 | |
|     if (meditationRoll.rolled.isSuccess) {
 | |
|       await this.createEmbeddedDocuments("Item", [RdDItemSigneDraconique.prepareSigneDraconiqueMeditation(meditationRoll.meditation, meditationRoll.rolled)]);
 | |
|     }
 | |
|     if (meditationRoll.rolled.isEPart) {
 | |
|       await this.updateEmbeddedDocuments('Item', [{ _id: meditationRoll.meditation._id, 'system.malus': meditationRoll.meditation.system.malus - 1 }]);
 | |
|     }
 | |
|     await this.santeIncDec("fatigue", 2);
 | |
|     await RdDRollResult.displayRollData(meditationRoll, this.name, 'chat-resultat-meditation.hbs');
 | |
|   }
 | |
| 
 | |
|   /* -------------------------------------------- */
 | |
|   _getSignesDraconiques(coord) {
 | |
|     const type = TMRUtility.getTMRType(coord);
 | |
|     return this.itemTypes[ITEM_TYPES.signedraconique].filter(it => it.system.typesTMR.includes(type));
 | |
|   }
 | |
| 
 | |
|   /* -------------------------------------------- */
 | |
|   isResonanceSigneDraconique(coord) {
 | |
|     return this._getSignesDraconiques(coord).length > 0;
 | |
|   }
 | |
| 
 | |
|   /* -------------------------------------------- */
 | |
|   async rollLireSigneDraconique(coord) {
 | |
|     if (!this.isHautRevant()) {
 | |
|       ui.notifications.info("Seul un haut rêvant peut lire un signe draconique!");
 | |
|       return
 | |
|     }
 | |
|     let signes = this._getSignesDraconiques(coord)
 | |
|     if (signes.length == 0) {
 | |
|       ui.notifications.info(`Aucun signe draconiques en ${coord} !`)
 | |
|       return
 | |
|     }
 | |
| 
 | |
|     let draconicList = this.getDraconics()
 | |
|       .map(draconic => {
 | |
|         let draconicLecture = foundry.utils.duplicate(draconic)
 | |
|         draconicLecture.system.defaut_carac = "intellect"
 | |
|         return draconicLecture
 | |
|       });
 | |
| 
 | |
|     const intellect = this.system.carac.intellect;
 | |
|     let rollData = {
 | |
|       carac: { 'intellect': intellect },
 | |
|       selectedCarac: intellect,
 | |
|       competence: draconicList[0],
 | |
|       draconicList: draconicList,
 | |
|       signe: signes[0],
 | |
|       signes: signes,
 | |
|       tmr: TMRUtility.getTMR(coord),
 | |
|       diffLibre: signes[0].system.difficulte,
 | |
|     }
 | |
| 
 | |
|     const dialog = await RdDRoll.create(this, rollData,
 | |
|       {
 | |
|         html: 'systems/foundryvtt-reve-de-dragon/templates/dialog-roll-signedraconique.hbs',
 | |
|         close: async html => await this._onCloseRollDialog(html)
 | |
|       },
 | |
|       {
 | |
|         name: 'lire-signe-draconique',
 | |
|         label: 'Lire le signe draconique',
 | |
|         callbacks: [
 | |
|           this.createCallbackExperience(),
 | |
|           { action: r => this._rollLireSigneDraconique(r) }
 | |
|         ]
 | |
|       }
 | |
|     );
 | |
|     dialog.render(true);
 | |
|     this.tmrApp?.setTMRPendingAction(dialog);
 | |
|   }
 | |
| 
 | |
|   async _onCloseRollDialog(html) {
 | |
|     this.tmrApp?.restoreTMRAfterAction()
 | |
|   }
 | |
| 
 | |
|   /* -------------------------------------------- */
 | |
|   async _rollLireSigneDraconique(rollData) {
 | |
|     const compData = rollData.competence;
 | |
|     if (!RdDItemCompetence.isDraconic(compData)) {
 | |
|       ui.notifications.error(`La compétence ${compData.name} n'est pas une compétence draconique`);
 | |
|       return;
 | |
|     }
 | |
|     rollData.xpSort = RdDItemSigneDraconique.getXpSortSigneDraconique(rollData.rolled.code, rollData.signe);
 | |
|     if (rollData.xpSort > 0) {
 | |
|       const fromXp = Number(compData.system.xp_sort);
 | |
|       const toXp = fromXp + rollData.xpSort;
 | |
|       await this.updateEmbeddedDocuments("Item", [{ _id: compData._id, 'system.xp_sort': toXp }]);
 | |
|       await ExperienceLog.add(this, XP_TOPIC.XPSORT, fromXp, toXp, `${rollData.competence.name} : signe draconique`);
 | |
|     }
 | |
|     await this.deleteEmbeddedDocuments("Item", [rollData.signe._id]);
 | |
|     await RdDRollResult.displayRollData(rollData, this.name, 'chat-resultat-lecture-signedraconique.hbs');
 | |
|     this.tmrApp.close();
 | |
|   }
 | |
| 
 | |
|   /* -------------------------------------------- */
 | |
|   async rollAppelChance(onSuccess = () => { }, onEchec = () => { }) {
 | |
|     await this.openRollDialog({
 | |
|       name: 'appelChance',
 | |
|       label: 'Appel à la chance',
 | |
|       template: 'systems/foundryvtt-reve-de-dragon/templates/dialog-roll-carac.hbs',
 | |
|       rollData: { selectedCarac: this.getCaracByName('chance-actuelle'), surprise: '' },
 | |
|       callbacks: [{ action: r => this.$appelChanceResult(r, onSuccess, onEchec) }]
 | |
|     });
 | |
|   }
 | |
| 
 | |
|   /* -------------------------------------------- */
 | |
|   async $appelChanceResult(rollData, onSuccess, onEchec) {
 | |
|     await RdDRollResult.displayRollData(rollData, this, 'chat-resultat-appelchance.hbs')
 | |
|     if (rollData.rolled.isSuccess) {
 | |
|       await this.setFlag(SYSTEM_RDD, 'utilisationChance', true);
 | |
|       await this.chanceActuelleIncDec(-1);
 | |
|       onSuccess();
 | |
|     }
 | |
|     else {
 | |
|       onEchec();
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   /* -------------------------------------------- */
 | |
|   async appelDestinee(onSuccess = () => { }, onEchec = () => { }) {
 | |
|     let destinee = this.system.compteurs.destinee?.value ?? 0;
 | |
|     if (destinee > 0) {
 | |
|       ChatMessage.create({ content: `<span class="rdd-roll-part">${this.name} a fait appel à la Destinée !</span>` });
 | |
|       destinee--;
 | |
|       await this.updateCompteurValue("destinee", destinee);
 | |
|       onSuccess();
 | |
|     }
 | |
|     else {
 | |
|       onEchec();
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   /* -------------------------------------------- */
 | |
|   getHeureNaissance() {
 | |
|     return this.system.heure ?? 0;
 | |
|   }
 | |
| 
 | |
|   /* -------------------------------------------- */
 | |
|   ajustementAstrologique() {
 | |
|     // selon l'heure de naissance...
 | |
|     return game.system.rdd.calendrier.getAjustementAstrologique(this.system.heure, this.name)
 | |
|   }
 | |
| 
 | |
|   /* -------------------------------------------- */
 | |
|   checkDesirLancinant() {
 | |
|     let queue = this.filterItems(it => it.type == 'queue' || it.type == 'ombre')
 | |
|       .filter(it => it.system.categorie == 'lancinant');
 | |
|     return (queue.length > 0);
 | |
|   }
 | |
| 
 | |
|   /* -------------------------------------------- */
 | |
|   async _appliquerExperience(rolled, caracName, competence, jetResistance) {
 | |
|     // Pas d'XP
 | |
|     if (!rolled.isPart || rolled.finalLevel >= 0) {
 | |
|       return []
 | |
|     }
 | |
|     if (this.checkDesirLancinant()) {
 | |
|       // Cas de désir lancinant, pas d'expérience sur particulière
 | |
|       ChatMessage.create({
 | |
|         content: `Vous souffrez au moins d'un Désir Lancinant, vous ne pouvez pas gagner d'expérience sur une Particulière tant que le désir n'est pas assouvi`,
 | |
|         whisper: ChatUtility.getOwners(this)
 | |
|       });
 | |
|       return []
 | |
|     }
 | |
| 
 | |
|     let xp = Math.abs(rolled.finalLevel);
 | |
|     // impair: arrondi inférieur en carac
 | |
|     let xpCarac = competence ? Math.floor(xp / 2) : Math.max(Math.floor(xp / 2), 1);
 | |
| 
 | |
|     const xpCompetence = competence ? xp - xpCarac : 0;
 | |
|     if (jetResistance) {
 | |
|       const message = `Jet de résistance ${jetResistance}, l'expérience est limitée à 1`;
 | |
|       ui.notifications.info(message);
 | |
|       console.log(message)
 | |
|       // max 1 xp sur jets de résistance
 | |
|       xpCarac = Math.min(1, xpCarac);
 | |
|     }
 | |
|     return [
 | |
|       ...(await this._xpCompetence({ competence, xpCompetence })),
 | |
|       ...(await this._xpCarac({ caracName, xpCarac }))
 | |
|     ];
 | |
|   }
 | |
| 
 | |
|   /* -------------------------------------------- */
 | |
|   async _xpCompetence(xpData) {
 | |
|     if (xpData.competence) {
 | |
|       const from = Number(xpData.competence.system.xp);
 | |
|       const to = from + xpData.xpCompetence;
 | |
|       await this.updateEmbeddedDocuments('Item', [{ _id: xpData.competence._id, 'system.xp': to }]);
 | |
|       xpData.checkComp = await this.checkCompetenceXP(xpData.competence.name, undefined, false);
 | |
|       await ExperienceLog.add(this, XP_TOPIC.XP, from, to, xpData.competence.name);
 | |
|       return [xpData]
 | |
|     }
 | |
|     return []
 | |
|   }
 | |
| 
 | |
|   /* -------------------------------------------- */
 | |
|   async _xpCarac(xpData) {
 | |
|     if (xpData.xpCarac > 0) {
 | |
|       const carac = foundry.utils.duplicate(this.system.carac)
 | |
|       const code = RdDBaseActor._findCaracNode(carac, xpData.caracName)
 | |
|       const selectedCarac = carac[code]
 | |
|       if (this.isCaracMax(code)) {
 | |
|         ui.notifications.info(`Pas d'expérience: la caractéristique '${selectedCarac.label}' est déjà au maximum pour ${this.name}`)
 | |
|         return []
 | |
|       }
 | |
|       if (selectedCarac && !selectedCarac.derivee) {
 | |
|         const from = Number(selectedCarac.xp)
 | |
|         const to = from + xpData.xpCarac
 | |
|         selectedCarac.xp = to
 | |
|         await this.update({ "system.carac": carac })
 | |
|         xpData.checkCarac = await this.checkCaracXP(selectedCarac.label, false)
 | |
|         await ExperienceLog.add(this, XP_TOPIC.XPCARAC, from, to, xpData.caracName)
 | |
|         return [xpData]
 | |
|       } else {
 | |
|         return await this._xpCaracDerivee(xpData)
 | |
|       }
 | |
|     }
 | |
|     return []
 | |
|   }
 | |
| 
 | |
|   async _xpCaracDerivee(xpData) {
 | |
|     const caracs = RdDActor._getComposantsCaracDerivee(xpData.caracName)
 | |
|       .map(c => foundry.utils.mergeObject(this.system.carac[c], { isMax: this.isCaracMax(c) }, { inplace: false }))
 | |
|     switch (caracs.filter(it => !it.isMax).length) {
 | |
|       case 0:
 | |
|         xpData.caracRepartitionManuelle = true;
 | |
|         return [xpData]
 | |
|       case 1:
 | |
|         xpData.caracName = caracs.find(it => !it.isMax).label
 | |
|         return this._xpCarac(xpData)
 | |
|       default:
 | |
|         await DialogChoixXpCarac.choix(this, xpData, caracs)
 | |
|         return []
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   static _getComposantsCaracDerivee(caracName) {
 | |
|     switch (Grammar.toLowerCaseNoAccent(caracName)) {
 | |
|       case CARACS.REVE_ACTUEL: case 'reve actuel': return [CARACS.REVE]
 | |
|       case CARACS.CHANCE_ACTUELLE: case 'chance actuelle': return [CARACS.CHANCE]
 | |
|       case CARACS.TIR: return [CARACS.DEXTERITE, CARACS.VUE]
 | |
|       case CARACS.LANCER: return [CARACS.FORCE, CARACS.DEXTERITE, CARACS.VUE]
 | |
|       case CARACS.MELEE: return [CARACS.FORCE, CARACS.AGILITE]
 | |
|       case CARACS.DEROBEE: return [CARACS.AGILITE]
 | |
|     }
 | |
|     return []
 | |
|   }
 | |
| 
 | |
| 
 | |
|   /* -------------------------------------------- */
 | |
|   async deleteNombresAstraux() {
 | |
|     const deletions = this.itemTypes['nombreastral'].map(it => it._id);
 | |
|     await this.deleteEmbeddedDocuments("Item", deletions);
 | |
|   }
 | |
| 
 | |
|   /* -------------------------------------------- */
 | |
|   async ajouteNombreAstral(date, nbAstral, isValid) {
 | |
|     const indexDate = Number.parseInt(date);
 | |
|     // Ajout du nombre astral
 | |
|     const item = {
 | |
|       name: "Nombre Astral", type: "nombreastral", system:
 | |
|       {
 | |
|         value: nbAstral,
 | |
|         istrue: isValid,
 | |
|         jourindex: indexDate,
 | |
|         jourlabel: RdDTimestamp.formatIndexDate(indexDate)
 | |
|       }
 | |
|     };
 | |
|     await this.createEmbeddedDocuments("Item", [item]);
 | |
|   }
 | |
| 
 | |
|   async supprimerAnciensNombresAstraux() {
 | |
|     const calendrier = game.system.rdd.calendrier;
 | |
|     if (calendrier) {
 | |
|       const toDelete = this.itemTypes['nombreastral']
 | |
|         .filter(it => calendrier.isAfterIndexDate(it.system.jourindex))
 | |
|         .map(it => it._id);
 | |
|       await this.deleteEmbeddedDocuments("Item", toDelete);
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   /* -------------------------------------------- */
 | |
|   async astrologieNombresAstraux() {
 | |
|     await this.supprimerAnciensNombresAstraux();
 | |
|     await AppAstrologie.create(this);
 | |
|   }
 | |
| 
 | |
|   /* -------------------------------------------- */
 | |
|   countMonteeLaborieuse() { // Return +1 par queue/ombre/souffle Montée Laborieuse présente
 | |
|     let countMonteeLaborieuse = EffetsDraconiques.countMonteeLaborieuse(this);
 | |
|     if (countMonteeLaborieuse > 0) {
 | |
|       ChatMessage.create({
 | |
|         content: `Vous êtes sous le coup d'une Montée Laborieuse : vos montées en TMR coûtent ${countMonteeLaborieuse} Point de Rêve de plus.`,
 | |
|         whisper: ChatUtility.getOwners(this)
 | |
|       });
 | |
|     }
 | |
|     return countMonteeLaborieuse;
 | |
|   }
 | |
| 
 | |
|   /* -------------------------------------------- */
 | |
|   refreshTMRView() {
 | |
|     if (this.tmrApp) {
 | |
|       this.tmrApp.externalRefresh();
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   /* -------------------------------------------- */
 | |
|   async displayTMR(mode = "normal") {
 | |
|     if (this.tmrApp) {
 | |
|       ui.notifications.warn("Vous êtes déja dans les TMR....")
 | |
|       this.tmrApp.forceTMRDisplay()
 | |
|       return false
 | |
|     }
 | |
|     if (mode != 'visu' && this.isDemiReve()) {
 | |
|       ui.notifications.warn("Le personnage est déjà dans les Terres Médianes, elles s'affichent en visualisation")
 | |
|       mode = "visu"; // bascule le mode en visu automatiquement
 | |
|     }
 | |
|     if (mode == 'visu') {
 | |
|       await this._doDisplayTMR(mode)
 | |
|       return false
 | |
|     }
 | |
|     else {
 | |
|       const rencontre = this.getRencontreTMREnAttente();
 | |
|       const settingConfirmer = rencontre ? "confirmation-tmr-rencontre" : "confirmation-tmr";
 | |
|       const messageRencontre = rencontre ? `<p>Vous devrez combattre ${rencontre.system.genre == 'f' ? 'la' : 'le'} ${rencontre.name} de ${rencontre.system.force} points de Rêve</p>` : '';
 | |
|       RdDConfirm.confirmer({
 | |
|         settingConfirmer: settingConfirmer,
 | |
|         content: `<p>Voulez vous monter dans les TMR en mode ${mode}?</p>` + messageRencontre,
 | |
|         title: 'Confirmer la montée dans les TMR',
 | |
|         buttonLabel: 'Monter dans les TMR',
 | |
|         onAction: async () => await this._doDisplayTMR(mode)
 | |
|       })
 | |
|       return true
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   async _doDisplayTMR(mode) {
 | |
|     let isRapide = mode == "rapide";
 | |
|     if (mode != "visu") {
 | |
|       let minReveValue = (isRapide && !EffetsDraconiques.isDeplacementAccelere(this) ? 3 : 2) + this.countMonteeLaborieuse();
 | |
|       if (this.getReveActuel() < minReveValue) {
 | |
|         ChatMessage.create({
 | |
|           content: `Vous n'avez les ${minReveValue} Points de Reve nécessaires pour monter dans les Terres Médianes`,
 | |
|           whisper: ChatUtility.getOwners(this)
 | |
|         });
 | |
|         return;
 | |
|       }
 | |
|       await this.setEffect(STATUSES.StatusDemiReve, true);
 | |
|     }
 | |
| 
 | |
|     const fatigue = this.system.sante.fatigue.value;
 | |
|     const endurance = this.system.sante.endurance.max;
 | |
|     let tmrFormData = {
 | |
|       mode: mode,
 | |
|       fatigue: RdDUtility.calculFatigueHtml(fatigue, endurance),
 | |
|       draconic: this.getDraconics(),
 | |
|       sort: this.itemTypes['sort'],
 | |
|       signes: this.itemTypes['signedraconique'],
 | |
|       caracReve: parseInt(this.system.carac.reve.value),
 | |
|       pointsReve: this.getReveActuel(),
 | |
|       isRapide: isRapide,
 | |
|       isGM: game.user.isGM,
 | |
|       hasPlayerOwner: this.hasPlayerOwner
 | |
|     }
 | |
| 
 | |
|     this.tmrApp = await RdDTMRDialog.create(this, tmrFormData);
 | |
|     await this.tmrApp.render(true);
 | |
|     await this.tmrApp.onDeplacement()
 | |
|   }
 | |
| 
 | |
|   async quitterTMR(message, viewOnly, cumulFatigue) {
 | |
|     if (this.tmrApp) {
 | |
|       this.tmrApp = undefined
 | |
|       const appliquerFatigue = ReglesOptionnelles.isUsing("appliquer-fatigue");
 | |
|       await this.santeIncDec(
 | |
|         appliquerFatigue ? "fatigue" : "endurance",
 | |
|         (appliquerFatigue ? 1 : -1) * cumulFatigue)
 | |
|       if (!viewOnly) {
 | |
|         await this.supprimerSignesDraconiques(it => it.system.ephemere && it.system.duree == '1 round', { render: false })
 | |
|         await this.setEffect(STATUSES.StatusDemiReve, false)
 | |
|         ChatUtility.tellToUserAndGM(message)
 | |
|       }
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   async supprimerSignesDraconiques(filter = it => true, options = { render: true }) {
 | |
|     const signes = this.itemTypes[ITEM_TYPES.signedraconique].filter(filter)
 | |
|     if (signes.length > 0) {
 | |
|       this.deleteEmbeddedDocuments("Item", signes.map(item => item.id), options)
 | |
|     }
 | |
|   }
 | |
| 
 | |
| 
 | |
|   /* -------------------------------------------- */
 | |
|   async rollSoins(blesse, blessureId) {
 | |
|     const blessure = blesse.blessuresASoigner().find(it => it.id == blessureId);
 | |
|     if (blessure) {
 | |
|       if (!blessure.system.premierssoins.done) {
 | |
|         const tache = await this.getTacheBlessure(blesse, blessure);
 | |
|         return await this.rollTache(tache.id, {
 | |
|           onRollAutomate: async r => blesse.onRollTachePremiersSoins(blessureId, r),
 | |
|           title: 'Premiers soins',
 | |
|           forced: true
 | |
|         });
 | |
|       }
 | |
|       else if (!blessure.system.soinscomplets.done) {
 | |
|         const diff = blessure.system.difficulte + (blessure.system.premierssoins.bonus ?? 0);
 | |
|         return await this.rollCaracCompetence(CARACS.DEXTERITE, "Chirurgie", diff, {
 | |
|           title: "Soins complets",
 | |
|           onRollAutomate: r => blesse.onRollSoinsComplets(blessureId, r),
 | |
|           forced: true
 | |
|         })
 | |
|       }
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   async onRollTachePremiersSoins(blessureId, rollData) {
 | |
|     if (!this.isOwner) {
 | |
|       return RdDBaseActor.remoteActorCall({
 | |
|         tokenId: this.token?.id,
 | |
|         actorId: this.id,
 | |
|         method: 'onRollTachePremiersSoins', args: [blessureId, rollData]
 | |
|       })
 | |
|     }
 | |
|     const blessure = this.getItem(blessureId, 'blessure')
 | |
| 
 | |
|     if (blessure && !blessure.system.premierssoins.done) {
 | |
|       const tache = rollData.tache;
 | |
|       if (rollData.rolled.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) {
 | |
|           await this.deleteEmbeddedDocuments('Item', [tache.id])
 | |
|         }
 | |
|       }
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   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) },
 | |
|         })
 | |
|       }
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   /* -------------------------------------------- */
 | |
|   conjurerPossession(possession) {
 | |
|     RdDPossession.onConjurerPossession(this, possession)
 | |
|   }
 | |
| 
 | |
|   /* -------------------------------------------- */
 | |
|   verifierForceMin(item) {
 | |
|     if (item.type == 'arme' && item.system.force > parseInt(this.system.carac.force.value)) {
 | |
|       ChatMessage.create({
 | |
|         content: `<strong>${this.name} s'est équipé(e) de l'arme ${item.name}, mais n'a pas une force suffisante pour l'utiliser normalement </strong>
 | |
|                   (${item.system.force} nécessaire pour une Force de ${this.system.carac.force.value})`
 | |
|       });
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   /* -------------------------------------------- */
 | |
|   async equiperObjet(item) {
 | |
|     if (item?.isEquipable()) {
 | |
|       const isEquipe = !item.system.equipe;
 | |
|       await item.update({ "system.equipe": isEquipe });
 | |
|       this.computeEncTotal();
 | |
|       if (isEquipe)
 | |
|         this.verifierForceMin(item);
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   /* -------------------------------------------- */
 | |
|   async computeArmure(dmg) {
 | |
|     let baseDmg = (dmg.dmgArme ?? 0) + (dmg.dmgActor ?? 0);
 | |
|     let protection = 0;
 | |
|     if (dmg.encaisserSpecial != "noarmure") {
 | |
|       const armures = this.items.filter(it => it.type == "armure" && it.system.equipe)
 | |
| 
 | |
|       for (const armure of armures) {
 | |
|         protection += await RdDDice.rollTotal(armure.system.protection.toString());
 | |
|         if (baseDmg > 0 && dmg.encaisserSpecial != "noarmure") {
 | |
|           await armure.deteriorerArmure(baseDmg)
 | |
|           baseDmg = 0;
 | |
|         }
 | |
|       }
 | |
|       protection -= Math.min(dmg.penetration, protection)
 | |
|       protection += this.getProtectionNaturelle();
 | |
|       // Gestion des cas particuliers sur la fenêtre d'encaissement
 | |
|       if (dmg.encaisserSpecial == "chute") {
 | |
|         protection = Math.min(protection, 2);
 | |
|       }
 | |
|     }
 | |
|     console.log("Final protect", protection, dmg)
 | |
|     return protection;
 | |
|   }
 | |
| 
 | |
|   /* -------------------------------------------- */
 | |
|   /** @override */
 | |
|   getRollData() {
 | |
|     const rollData = super.getRollData();
 | |
|     return rollData;
 | |
|   }
 | |
| 
 | |
|   /* -------------------------------------------- */
 | |
|   async resetItemUse() {
 | |
|     await this.unsetFlag(SYSTEM_RDD, 'itemUse');
 | |
|   }
 | |
| 
 | |
|   /* -------------------------------------------- */
 | |
|   async incDecItemUse(itemId, shouldIncrease = true) {
 | |
|     if (shouldIncrease) {
 | |
|       const currentItemUse = this.getFlag(SYSTEM_RDD, 'itemUse');
 | |
|       let itemUse = currentItemUse ? foundry.utils.duplicate(currentItemUse) : {};
 | |
|       itemUse[itemId] = (itemUse[itemId] ?? 0) + 1;
 | |
|       await this.setFlag(SYSTEM_RDD, 'itemUse', itemUse);
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   /* -------------------------------------------- */
 | |
|   getItemUse(itemId) {
 | |
|     let itemUse = this.getFlag(SYSTEM_RDD, 'itemUse') ?? {};
 | |
|     console.log("ITEM USE GET", itemUse);
 | |
|     return itemUse[itemId] ?? 0;
 | |
|   }
 | |
| 
 | |
|   /* -------------------------------------------- */
 | |
|   async effectuerTacheAlchimie(recetteId, tacheAlchimie, texteTache) {
 | |
|     let recetteData = this.findItemLike(recetteId, 'recettealchimique');
 | |
|     if (recetteData) {
 | |
|       if (tacheAlchimie != "couleur" && tacheAlchimie != "consistance") {
 | |
|         ui.notifications.warn(`L'étape alchimique ${tacheAlchimie} - ${texteTache} est inconnue`);
 | |
|         return;
 | |
|       }
 | |
| 
 | |
|       const sansCristal = tacheAlchimie == "couleur" && this.items.filter(it => it.isCristalAlchimique()).length == 0;
 | |
|       const caracTache = RdDAlchimie.getCaracTache(tacheAlchimie);
 | |
|       const alchimieData = this.getCompetence("alchimie");
 | |
| 
 | |
|       let rollData = {
 | |
|         recette: recetteData,
 | |
|         carac: { [caracTache]: this.system.carac[caracTache] },
 | |
|         selectedCarac: this.system.carac[caracTache],
 | |
|         competence: alchimieData,
 | |
|         diffLibre: RdDAlchimie.getDifficulte(texteTache),
 | |
|         diffConditions: sansCristal ? -4 : 0,
 | |
|         alchimie: {
 | |
|           tache: Misc.upperFirst(tacheAlchimie),
 | |
|           texte: texteTache,
 | |
|           sansCristal: sansCristal
 | |
|         }
 | |
|       }
 | |
|       rollData.competence.system.defaut_carac = caracTache;
 | |
| 
 | |
|       const dialog = await RdDRoll.create(this, rollData,
 | |
|         { html: 'systems/foundryvtt-reve-de-dragon/templates/dialog-roll-alchimie.hbs' },
 | |
|         {
 | |
|           name: 'tache-alchimique',
 | |
|           label: 'Tache Alchimique',
 | |
|           callbacks: [
 | |
|             this.createCallbackExperience(),
 | |
|             this.createCallbackAppelAuMoral(),
 | |
|             { action: async r => await this._alchimieResult(r, false) }
 | |
|           ]
 | |
|         }
 | |
|       );
 | |
|       dialog.render(true);
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   isCristalAlchimique(it) {
 | |
|     return it.type == 'objet' && Grammar.toLowerCaseNoAccent(it.name) == 'cristal alchimique' && it.system.quantite > 0;
 | |
|   }
 | |
| 
 | |
|   /* -------------------------------------------- */
 | |
|   async _alchimieResult(rollData) {
 | |
|     await RdDRollResult.displayRollData(rollData, this, 'chat-resultat-alchimie.hbs');
 | |
|   }
 | |
| 
 | |
|   /* -------------------------------------------- */
 | |
|   static $transformSubActeurSuivant = (suivant, link) => {
 | |
|     return foundry.utils.mergeObject(RdDBaseActor.extractActorMin(suivant), {
 | |
|       ephemere: !suivant.prototypeToken.actorLink,
 | |
|       coeur: link.coeur ?? 0,
 | |
|       prochainCoeur: link.prochainCoeur ?? link.coeur ?? 0
 | |
|     })
 | |
|   };
 | |
| 
 | |
|   listeSuivants(filter = suivant => true) {
 | |
|     return RdDActor.$buildSubActorLinks(
 | |
|       this.system.subacteurs.suivants, RdDActor.$transformSubActeurSuivant
 | |
|     ).filter(filter)
 | |
|   }
 | |
|   listeAmoureux() {
 | |
|     return this.listeSuivants(it => it.coeur > 0 && it.type == ACTOR_TYPES.personnage)
 | |
|   }
 | |
| 
 | |
|   getSuivant(subActorId) {
 | |
|     const suivant = this.system.subacteurs.suivants.find(it => it.id == subActorId);
 | |
|     if (suivant) {
 | |
|       return RdDActor.$transformSubActeurSuivant(game.actors.get(suivant.id), suivant);
 | |
|     }
 | |
|     return undefined
 | |
|   }
 | |
| 
 | |
|   getPointsCoeur(subActorId) {
 | |
|     return this.getSuivant(subActorId)?.coeur ?? 0;
 | |
|   }
 | |
| 
 | |
|   async setPointsCoeur(subActorId, coeurs, options = { immediat: false }) {
 | |
|     const newSuivants = foundry.utils.duplicate(this.system.subacteurs.suivants)
 | |
|     const amoureux = newSuivants.find(it => it.id == subActorId);
 | |
|     if (amoureux) {
 | |
|       amoureux[options.immediat ? 'coeur' : 'prochainCoeur'] = coeurs
 | |
|       await this.update({ 'system.subacteurs.suivants': newSuivants });
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   /* -------------------------------------------- */
 | |
|   static $transformSubActeurVehicule = (vehicle, link) => {
 | |
|     return foundry.utils.mergeObject(RdDBaseActor.extractActorMin(vehicle), {
 | |
|       system: { categorie: vehicle.system.categorie, etat: vehicle.system.etat }
 | |
|     })
 | |
|   };
 | |
| 
 | |
|   listeVehicules() {
 | |
|     return RdDActor.$buildSubActorLinks(this.system.subacteurs.vehicules, RdDActor.$transformSubActeurVehicule)
 | |
|   }
 | |
| 
 | |
|   /* -------------------------------------------- */
 | |
|   static $transformSubActeurCreature = (actor, link) => RdDBaseActor.extractActorMin(actor)
 | |
| 
 | |
|   listeMontures() {
 | |
|     return RdDActor.$buildSubActorLinks(this.system.subacteurs.montures, RdDActor.$transformSubActeurCreature);
 | |
|   }
 | |
| 
 | |
|   /* -------------------------------------------- */
 | |
|   static $buildSubActorLinks(subActors, actorTransformation = (actor, link) => undefined) {
 | |
|     if (!subActors) {
 | |
|       return []
 | |
|     }
 | |
|     return subActors.map(link => {
 | |
|       const actor = game.actors.get(link.id)
 | |
|       return actor ? actorTransformation(actor, link) : undefined
 | |
|     })
 | |
|       .filter(it => it != undefined)
 | |
|       .sort(Misc.ascending(it => it.name))
 | |
|   }
 | |
| 
 | |
|   /* -------------------------------------------- */
 | |
|   addSubActeur(subActor) {
 | |
|     if (!this.isAddSubActeurAllowed(subActor)) {
 | |
|       return
 | |
|     }
 | |
|     const subActorOnlyId = { id: subActor._id };
 | |
|     if (subActor.type == 'vehicule') {
 | |
|       this.pushSubActeur(subActorOnlyId, this.system.subacteurs.vehicules, `system.subacteurs.vehicules`, `Le véhicule ${subActor.name}`)
 | |
|     } else if (subActor.type == 'creature') {
 | |
|       this.pushSubActeur(subActorOnlyId, this.system.subacteurs.montures, 'system.subacteurs.montures', `L'animal ${subActor.name}`)
 | |
|     } else if (subActor.type == 'personnage') {
 | |
|       this.pushSubActeur(subActorOnlyId, this.system.subacteurs.suivants, 'system.subacteurs.suivants', `Le compagnon ${subActor.name}`)
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   async pushSubActeur(subActor, dataArray, dataPath, dataName) {
 | |
|     let alreadyPresent = dataArray.find(attached => attached.id == subActor.id);
 | |
|     if (!alreadyPresent) {
 | |
|       let newArray = [...dataArray, subActor]
 | |
|       await this.update({ [dataPath]: newArray });
 | |
|     } else {
 | |
|       ui.notifications.warn(dataName + " est déja attaché à " + this.name);
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   isAddSubActeurAllowed(subActor) {
 | |
|     if (subActor?.id == undefined) {
 | |
|       ui.notifications.warn("Aucun acteur à ajouter")
 | |
|       return false
 | |
|     }
 | |
|     if (subActor?.id == this.id) {
 | |
|       ui.notifications.warn("Vous ne pouvez pas attacher un acteur à lui même")
 | |
|       return false
 | |
|     }
 | |
|     else if (!subActor?.isOwner) {
 | |
|       ui.notifications.warn("Vous n'avez pas les droits sur l'acteur que vous attachez.")
 | |
|       return false
 | |
|     }
 | |
|     return true
 | |
|   }
 | |
| 
 | |
|   async deleteSubActeur(actorId) {
 | |
|     ['vehicules', 'suivants', 'montures'].forEach(async type => {
 | |
|       const subList = this.system.subacteurs[type];
 | |
|       if (subList.find(it => it.id == actorId)) {
 | |
|         let newList = subList.filter(it => it.id != actorId)
 | |
|         await this.update({ [`system.subacteurs.${type}`]: newList }, { renderSheet: false });
 | |
|       }
 | |
|     })
 | |
|   }
 | |
| 
 | |
|   /* -------------------------------------------- */
 | |
|   async buildPotionGuerisonList(pointsGuerison) {
 | |
|     const pointsGuerisonInitial = pointsGuerison;
 | |
|     const blessures = this.filterItems(it => it.system.gravite > 0, 'blessure').sort(Misc.descending(it => it.system.gravite))
 | |
|     const ids = []
 | |
|     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)`);
 | |
|         ids.push(blessure.id)
 | |
|       }
 | |
|     }
 | |
|     if (ids.length > 0) {
 | |
|       await this.supprimerBlessures(it => ids.includes(it.id));
 | |
|     }
 | |
|     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);
 | |
|     }
 | |
|     guerisonData.pointsConsommes = pointsGuerisonInitial - pointsGuerison;
 | |
|     return guerisonData;
 | |
|   }
 | |
| 
 | |
|   /* -------------------------------------------- */
 | |
|   async consommerPotionSoin(potion) {
 | |
|     potion.alias = this.name;
 | |
|     potion.supprimer = true;
 | |
| 
 | |
|     if (potion.system.magique) {
 | |
|       // Gestion de la résistance:
 | |
|       potion.rolled = await RdDResolutionTable.roll(this.getReveActuel(), -8);
 | |
|       if (potion.rolled.isEchec) {
 | |
|         await this.reveActuelIncDec(-1);
 | |
|         potion.guerisonData = await this.buildPotionGuerisonList(potion.system.puissance);
 | |
|         potion.guerisonMinutes = potion.guerisonData.pointsConsommes * 5;
 | |
|       }
 | |
|     }
 | |
|     if (!potion.system.magique || potion.rolled.isSuccess) {
 | |
|       await this.setBonusPotionSoin(potion.system.herbebonus);
 | |
|     }
 | |
|     ChatMessage.create({
 | |
|       whisper: ChatUtility.getOwners(this),
 | |
|       content: await renderTemplate(`systems/foundryvtt-reve-de-dragon/templates/chat-consommer-potion-soin.hbs`, potion)
 | |
|     });
 | |
|   }
 | |
| 
 | |
|   async setBonusPotionSoin(bonus) {
 | |
|     await this.update({ 'system.sante.bonusPotion': bonus });
 | |
|   }
 | |
| 
 | |
|   /* -------------------------------------------- */
 | |
|   async consommerPotionRepos(potion) {
 | |
|     potion.alias = this.name;
 | |
|     potion.supprimer = true;
 | |
| 
 | |
|     if (potion.system.magique) {
 | |
|       // Gestion de la résistance:
 | |
|       potion.rolled = await RdDResolutionTable.roll(this.getReveActuel(), -8);
 | |
|       if (potion.rolled.isEchec) {
 | |
|         await this.reveActuelIncDec(-1);
 | |
|         let fatigueActuelle = this.getFatigueActuelle();
 | |
|         potion.caseFatigueReel = Math.min(fatigueActuelle, potion.system.puissance);
 | |
|         potion.guerisonDureeUnite = (potion.system.reposalchimique) ? "rounds" : "minutes";
 | |
|         potion.guerisonDureeValue = (potion.system.reposalchimique) ? potion.caseFatigueReel : potion.caseFatigueReel * 5;
 | |
|         potion.aphasiePermanente = false;
 | |
|         if (potion.system.reposalchimique) {
 | |
|           let chanceAphasie = await RdDDice.rollTotal("1d100");
 | |
|           if (chanceAphasie <= potion.system.pr) {
 | |
|             potion.aphasiePermanente = true;
 | |
|           }
 | |
|         }
 | |
|         await this.santeIncDec("fatigue", -potion.caseFatigueReel);
 | |
|       }
 | |
|     }
 | |
|     if (!potion.system.magique || potion.rolled.isSuccess) {
 | |
|       this.bonusRepos = potion.system.herbebonus;
 | |
|     }
 | |
|     ChatMessage.create({
 | |
|       whisper: ChatUtility.getOwners(this),
 | |
|       content: await renderTemplate(`systems/foundryvtt-reve-de-dragon/templates/chat-consommer-potion-repos.hbs`, potion)
 | |
|     });
 | |
|   }
 | |
| 
 | |
|   /* -------------------------------------------- */
 | |
|   async fabriquerPotion(herbeData) {
 | |
|     const newPotion = {
 | |
|       name: `Potion de ${herbeData.system.categorie} (${herbeData.name})`, type: 'potion',
 | |
|       img: "systems/foundryvtt-reve-de-dragon/icons/objets/fiole_verre.webp",
 | |
|       system: {
 | |
|         quantite: 1, cout: 0, encombrement: 0.1,
 | |
|         categorie: herbeData.system.categorie,
 | |
|         etat: 'Liquide',
 | |
|         herbe: herbeData.name,
 | |
|         rarete: herbeData.system.rarete,
 | |
|         herbebrins: herbeData.nbBrins,
 | |
|         herbebonus: herbeData.herbebonus,
 | |
|         description: ""
 | |
|       }
 | |
|     }
 | |
|     await this.createEmbeddedDocuments('Item', [newPotion])
 | |
|     await this.diminuerQuantiteObjet(herbeData._id, herbeData.nbBrins)
 | |
| 
 | |
|     ChatMessage.create({
 | |
|       whisper: ChatUtility.getOwners(this),
 | |
|       content: await renderTemplate(`systems/foundryvtt-reve-de-dragon/templates/chat-fabriquer-potion-base.hbs`, {
 | |
|         alias: this.getAlias(),
 | |
|         nbBrinsReste: herbeData.system.quantite - herbeData.nbBrins,
 | |
|         potion: newPotion,
 | |
|         herbe: herbeData
 | |
|       })
 | |
|     })
 | |
|   }
 | |
| 
 | |
|   /* -------------------------------------------- */
 | |
|   async diminuerQuantiteObjet(id, nb, options = { supprimerSiZero: false }) {
 | |
|     await this.getItem(id)?.diminuerQuantite(nb, options);
 | |
|   }
 | |
| 
 | |
|   /* -------------------------------------------- */
 | |
|   async consommerPotionGenerique(potion) {
 | |
|     potion.alias = this.name;
 | |
| 
 | |
|     if (potion.system.magique) {
 | |
|       // Gestion de la résistance:
 | |
|       potion.rolled = await RdDResolutionTable.roll(this.getReveActuel(), -8);
 | |
|       if (potion.rolled.isEchec) {
 | |
|         await this.reveActuelIncDec(-1);
 | |
|       }
 | |
|     }
 | |
|     ChatMessage.create({
 | |
|       whisper: ChatUtility.getOwners(this),
 | |
|       content: await renderTemplate(`systems/foundryvtt-reve-de-dragon/templates/chat-consommer-potion-generique.hbs`, potion)
 | |
|     });
 | |
|   }
 | |
| 
 | |
|   /* -------------------------------------------- */
 | |
|   async consommerPotion(potion) {
 | |
|     if (potion.system.categorie.includes('Soin')) {
 | |
|       this.consommerPotionSoin(potion);
 | |
|     } else if (potion.system.categorie.includes('Repos')) {
 | |
|       this.consommerPotionRepos(potion);
 | |
|     } else {
 | |
|       this.consommerPotionGenerique(potion);
 | |
|     }
 | |
|     await this.diminuerQuantiteObjet(potion.id, 1, { supprimerSiZero: potion.supprimer });
 | |
|   }
 | |
| 
 | |
|   /* -------------------------------------------- */
 | |
|   async onUpdateActor(update, options, actorId) {
 | |
|     const updatedEndurance = update?.system?.sante?.endurance
 | |
|     if (updatedEndurance && options.diff) {
 | |
|       await this.setEffect(STATUSES.StatusUnconscious, updatedEndurance.value == 0)
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   /* -------------------------------------------- */
 | |
|   async onPreUpdateItem(item, change, options, id) {
 | |
|     if (item.isCompetencePersonnage() && item.system.defaut_carac && item.system.xp) {
 | |
|       await this.checkCompetenceXP(item.name, item.system.xp);
 | |
|     }
 | |
|   }
 | |
|   /* -------------------------------------------- */
 | |
|   async onCreateItem(item, options, id) {
 | |
|     switch (item.type) {
 | |
|       case ITEM_TYPES.tete:
 | |
|       case ITEM_TYPES.queue:
 | |
|       case ITEM_TYPES.ombre:
 | |
|       case ITEM_TYPES.souffle:
 | |
|         await this.onCreateOwnedDraconique(item, options, id)
 | |
|         break
 | |
|       case ITEM_TYPES.race:
 | |
|         await this.onCreateOwnedRace(item, options, id)
 | |
|         break
 | |
|     }
 | |
|     await super.onCreateItem(item, options, id)
 | |
|     await item.onCreateItemTemporel(this);
 | |
|     await item.onCreateDecoupeComestible(this);
 | |
|   }
 | |
| 
 | |
|   async onDeleteItem(item, options, id) {
 | |
|     switch (item.type) {
 | |
|       case ITEM_TYPES.tete:
 | |
|       case ITEM_TYPES.queue:
 | |
|       case ITEM_TYPES.ombre:
 | |
|       case ITEM_TYPES.souffle:
 | |
|         await this.onDeleteOwnedDraconique(item, options, id)
 | |
|         break
 | |
|       case ITEM_TYPES.race:
 | |
|         await this.onDeleteOwnedRace(item, options, id)
 | |
|         break
 | |
|       case ITEM_TYPES.casetmr:
 | |
|         await this.onDeleteOwnedCaseTmr(item, options, id)
 | |
|         break
 | |
|       case ITEM_TYPES.empoignade:
 | |
|         await RdDEmpoignade.deleteLinkedEmpoignade(this.id, item)
 | |
|         break
 | |
|     }
 | |
|     super.onDeleteItem(item, options, id)
 | |
|   }
 | |
| 
 | |
|   /* -------------------------------------------- */
 | |
|   async onCreateOwnedDraconique(item, options, id) {
 | |
|     if (Misc.isFirstConnectedGM()) {
 | |
|       let draconique = Draconique.all().find(it => it.match(item));
 | |
|       if (draconique) {
 | |
|         await draconique.onActorCreateOwned(this, item)
 | |
|         this.notifyGestionTeteSouffleQueue(item, draconique.manualMessage());
 | |
|       }
 | |
|       await this.setInfoSommeilInsomnie();
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   /* -------------------------------------------- */
 | |
|   async onDeleteOwnedDraconique(item, options, id) {
 | |
|     if (Misc.isFirstConnectedGM()) {
 | |
|       let draconique = Draconique.all().find(it => it.match(item));
 | |
|       if (draconique) {
 | |
|         await draconique.onActorDeleteOwned(this, item)
 | |
|       }
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   async onCreateOwnedRace(item, options, id) {
 | |
|     if (Misc.isFirstConnectedGM()) {
 | |
|       const raceIds = this.itemTypes[ITEM_TYPES.race].map(it => it.id).filter(id => id != item.id)
 | |
|       if (raceIds.length > 0) {
 | |
|         await this.deleteEmbeddedDocuments('Item', raceIds)
 | |
|       }
 | |
|       await this._applyRaceCaracUpdates(item, 1)
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   async onDeleteOwnedRace(item, options, id) {
 | |
|     if (Misc.isFirstConnectedGM()) {
 | |
|       await this._applyRaceCaracUpdates(item, -1)
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   async _applyRaceCaracUpdates(item, sign) {
 | |
|     const updates = {};
 | |
|     RdDCarac.caracs(it => true).forEach(c => {
 | |
|       const toAdd = Number(foundry.utils.getProperty(item, c.path)) * sign
 | |
|       if (toAdd != 0) {
 | |
|         updates[c.path] = Number(foundry.utils.getProperty(this, c.path)) + toAdd
 | |
|       }
 | |
|     })
 | |
|     if (Object.keys(updates).length > 0) {
 | |
|       await this.update(updates)
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   /* -------------------------------------------- */
 | |
|   async onDeleteOwnedCaseTmr(item, options, id) {
 | |
|     if (Misc.isFirstConnectedGM()) {
 | |
|       let draconique = Draconique.all().find(it => it.isCase(item));
 | |
|       if (draconique) {
 | |
|         await draconique.onActorDeleteCaseTmr(this, item)
 | |
|       }
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   /* -------------------------------------------- */
 | |
|   notifyGestionTeteSouffleQueue(item, manualMessage = true) {
 | |
|     ChatMessage.create({
 | |
|       whisper: ChatUtility.getOwners(this),
 | |
|       content: `${this.name} a reçu un/une ${item.type}: ${item.name}, qui ${manualMessage ? "n'est pas" : "est"} géré(e) automatiquement. ${manualMessage ? manualMessage : ''}`
 | |
|     });
 | |
|   }
 | |
| 
 | |
|   async nouvelleIncarnation() {
 | |
|     let incarnation = this.toObject();
 | |
| 
 | |
|     incarnation.items = Array.from(this.items.filter(it => it.type == ITEM_TYPES.competence),
 | |
|       it => {
 | |
|         it = it.toObject();
 | |
|         it.id = undefined;
 | |
|         it.system.niveau = it.system.base;
 | |
|         it.system.niveau_archetype = Math.max(it.system.niveau + (it.system.xp > 0 ? 1 : 0), it.system.niveau_archetype);
 | |
|         it.system.xp = 0;
 | |
|         it.system.xp_sort = 0;
 | |
|         it.system.default_diffLibre = 0;
 | |
|         return it;
 | |
|       });
 | |
| 
 | |
|     incarnation.name = 'Réincarnation de ' + incarnation.name
 | |
|     incarnation.system = {
 | |
|       carac: foundry.utils.duplicate(this.system.carac),
 | |
|       heure: RdDTimestamp.defHeure(await RdDDice.rollHeure({ rollMode: "selfroll", showDice: SHOW_DICE })).key,
 | |
|       age: 18,
 | |
|       biographie: '',
 | |
|       notes: '',
 | |
|       experiencelog: [],
 | |
|       'compteurs.experience.value': 3000,
 | |
|       'reve.seuil.value': this.system.carac.reve.value,
 | |
|       'reve.reve.value': this.system.carac.reve.value,
 | |
|       subacteurs: { suivants: [], montures: [], vehicules: [] },
 | |
|     }
 | |
|     incarnation = await RdDBaseActor.create(incarnation);
 | |
|     await incarnation.deleteEmbeddedDocuments('ActiveEffect', incarnation.getEmbeddedCollection("ActiveEffect").map(it => it.id));
 | |
|     await incarnation.remiseANeuf();
 | |
|     incarnation.sheet.render(true);
 | |
|   }
 | |
| 
 | |
| 
 | |
|   /* -------------------------------------------- */
 | |
|   async _rollArtV2(oeuvreId) {
 | |
|     const oeuvre = this.items.get(oeuvreId)
 | |
|     const rollData = {
 | |
|       ids: { actorId: this.id },
 | |
|       selected: { oeuvre: { key: oeuvre.id } },
 | |
|       type: { allowed: [PART_OEUVRE], current: PART_OEUVRE, },
 | |
|     }
 | |
|     await RollDialog.create(rollData, { onRollDone: RollDialog.onRollDoneClose })
 | |
|   }
 | |
| 
 | |
|   /* -------------------------------------------- */
 | |
|   async rollOeuvre(id) {
 | |
|     if (OptionsAvancees.isUsing(ROLL_DIALOG_V2)) {
 | |
|       return await this._rollArtV2(id)
 | |
|     }
 | |
|     else {
 | |
|       const artData = { art: 'oeuvre', verbe: 'Interpréter' }
 | |
|       const oeuvre = foundry.utils.duplicate(this.findItemLike(id, artData.art))
 | |
|       await this._rollArtV1(artData, oeuvre.system.default_carac, oeuvre)
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   async rollChant(id) {
 | |
|     if (OptionsAvancees.isUsing(ROLL_DIALOG_V2)) {
 | |
|       await this._rollArtV2(id)
 | |
|     }
 | |
|     else {
 | |
|       const artData = { art: 'chant', verbe: 'Chanter' }
 | |
|       const oeuvre = foundry.utils.duplicate(this.getChant(id))
 | |
|       await this._rollArtV1(artData, "ouie", oeuvre)
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   async rollDanse(id) {
 | |
|     if (OptionsAvancees.isUsing(ROLL_DIALOG_V2)) {
 | |
|       await this._rollArtV2(id)
 | |
|     }
 | |
|     else {
 | |
|       const artData = { art: 'danse', verbe: 'Danser', forceCarac: {} }
 | |
|       const oeuvre = foundry.utils.duplicate(this.findItemLike(id, artData.art))
 | |
|       let selectedCarac = this.getCompetence(oeuvre.system.competence)?.system.defaut_carac
 | |
|       if (oeuvre.system.apparence) {
 | |
|         artData.forceCarac['apparence'] = foundry.utils.duplicate(this.system.carac.apparence)
 | |
|         selectedCarac = "apparence"
 | |
|       }
 | |
|       if (oeuvre.system.agilite) {
 | |
|         artData.forceCarac['agilite'] = foundry.utils.duplicate(this.system.carac.agilite)
 | |
|         selectedCarac = "agilite"
 | |
|       }
 | |
|       await this._rollArtV1(artData, selectedCarac, oeuvre)
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   async rollMusique(id) {
 | |
|     if (OptionsAvancees.isUsing(ROLL_DIALOG_V2)) {
 | |
|       await this._rollArtV2(id)
 | |
|     }
 | |
|     else {
 | |
|       const artData = { art: 'musique', verbe: 'Jouer' }
 | |
|       const oeuvre = this.findItemLike(id, artData.art)
 | |
|       await this._rollArtV1(artData, "ouie", oeuvre)
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   /* -------------------------------------------- */
 | |
|   async _rollArtV1(artData, selected, oeuvre, callbackAction = async r => await this._resultArt(r)) {
 | |
|     oeuvre.system.niveau = oeuvre.system.niveau ?? 0;
 | |
|     foundry.utils.mergeObject(artData,
 | |
|       {
 | |
|         oeuvre: oeuvre,
 | |
|         art: oeuvre.type,
 | |
|         competence: foundry.utils.duplicate(this.getCompetence(artData.compName ?? oeuvre.system.competence ?? artData.art)),
 | |
|         diffLibre: - oeuvre.system.niveau,
 | |
|         diffConditions: 0,
 | |
|         use: { libre: false, conditions: true, surenc: false },
 | |
|         selectedCarac: foundry.utils.duplicate(this.system.carac[selected])
 | |
|       },
 | |
|       { overwrite: false });
 | |
|     artData.competence.system.defaut_carac = selected;
 | |
|     if (!artData.forceCarac) {
 | |
|       artData.forceCarac = {};
 | |
|       artData.forceCarac[selected] = foundry.utils.duplicate(this.system.carac[selected]);
 | |
|     }
 | |
| 
 | |
|     await this.openRollDialog({
 | |
|       name: `jet-${artData.art}`,
 | |
|       label: `${artData.verbe} ${oeuvre.name}`,
 | |
|       template: `systems/foundryvtt-reve-de-dragon/templates/dialog-roll-${oeuvre.type}.hbs`,
 | |
|       rollData: artData,
 | |
|       callbacks: [{ action: callbackAction }],
 | |
|     })
 | |
|   }
 | |
| 
 | |
|   async _resultArt(artData) {
 | |
|     const niveau = artData.oeuvre.system.niveau ?? 0;
 | |
|     const baseQualite = (artData.rolled.isSuccess ? niveau : artData.competence.system.niveau);
 | |
|     artData.qualiteFinale = Math.min(baseQualite, niveau) + artData.rolled.ptQualite
 | |
| 
 | |
|     await RdDRollResult.displayRollData(artData, this.name, `chat-resultat-${artData.art}.hbs`);
 | |
|   }
 | |
| 
 | |
|   /* -------------------------------------------- */
 | |
|   async rollRecetteCuisine(id) {
 | |
|     const recette = this.getRecetteCuisine(id);
 | |
|     if (OptionsAvancees.isUsing(ROLL_DIALOG_V2)) {
 | |
|       const rollData = {
 | |
|         ids: { actorId: this.id },
 | |
|         type: { allowed: [PART_CUISINE], current: PART_CUISINE },
 | |
|         selected: {
 | |
|           cuisine: { key: recette.id }
 | |
|         }
 | |
|       }
 | |
|       RollDialog.create(rollData, { onRollDone: RollDialog.onRollDoneClose })
 | |
|       return
 | |
|     }
 | |
| 
 | |
|     const artData = {
 | |
|       verbe: 'Cuisiner',
 | |
|       compName: 'cuisine',
 | |
|       proportions: 1,
 | |
|       ajouterEquipement: false
 | |
|     };
 | |
|     await this._rollArtV1(artData, 'odoratgout', recette, r => this._resultRecetteCuisine(r));
 | |
|   }
 | |
| 
 | |
|   /* -------------------------------------------- */
 | |
|   async _resultRecetteCuisine(cuisine) {
 | |
|     const niveauRecette = cuisine.oeuvre.system.niveau ?? 0;
 | |
|     const baseQualite = (cuisine.rolled.isSuccess ? niveauRecette : cuisine.competence.system.niveau);
 | |
|     cuisine.qualiteFinale = Math.min(baseQualite, niveauRecette) + cuisine.rolled.ptQualite
 | |
|     cuisine.exotismeFinal = Math.min(Math.min(cuisine.qualiteFinale, cuisine.oeuvre.system.exotisme ?? 0), 0);
 | |
|     cuisine.sust = cuisine.oeuvre.system.sust * Math.min(cuisine.proportions, cuisine.proportionsMax ?? cuisine.proportions)
 | |
|     const platCuisine = {
 | |
|       name: cuisine.oeuvre.name,
 | |
|       type: 'nourritureboisson',
 | |
|       img: 'systems/foundryvtt-reve-de-dragon/icons/objets/provision_cuite.webp',
 | |
|       system: {
 | |
|         "description": cuisine.oeuvre.system.description,
 | |
|         "sust": 1,
 | |
|         "qualite": cuisine.qualiteFinale,
 | |
|         "exotisme": cuisine.exotismeFinal,
 | |
|         "encombrement": 0.1,
 | |
|         "quantite": Math.max(1, Math.floor(cuisine.sust)),
 | |
|         "cout": Math.max(cuisine.qualiteFinale) * 0.01
 | |
|       }
 | |
|     }
 | |
|     if (cuisine.ajouterEquipement) {
 | |
|       await this.createEmbeddedDocuments('Item', [platCuisine]);
 | |
|       ui.notifications.info(`${platCuisine.system.quantite} rations de ${platCuisine.name} ont été ajoutés à votre équipement`);
 | |
|     }
 | |
|     cuisine.platCuisine = platCuisine;
 | |
|     await RdDRollResult.displayRollData(cuisine, this.name, `chat-resultat-${cuisine.art}.hbs`);
 | |
|   }
 | |
| 
 | |
|   async preparerNourriture(item) {
 | |
|     if (item.getUtilisationCuisine() == 'brut' && OptionsAvancees.isUsing(ROLL_DIALOG_V2)) {
 | |
|       const rollData = {
 | |
|         ids: { actorId: this.id },
 | |
|         type: { allowed: [PART_CUISINE], current: PART_CUISINE },
 | |
|         selected: {
 | |
|           cuisine: { key: item.id }
 | |
|         }
 | |
|       }
 | |
|       RollDialog.create(rollData, { onRollDone: RollDialog.onRollDoneClose })
 | |
|       return
 | |
|     }
 | |
| 
 | |
|     if (item.getUtilisationCuisine() == 'brut') {
 | |
|       const nourriture = {
 | |
|         name: 'Plat de ' + item.name,
 | |
|         type: 'recettecuisine',
 | |
|         img: item.img,
 | |
|         system: {
 | |
|           sust: item.system.sust,
 | |
|           exotisme: item.system.exotisme,
 | |
|           ingredients: item.name
 | |
|         }
 | |
|       }
 | |
|       const artData = {
 | |
|         verbe: 'Préparer',
 | |
|         compName: 'cuisine',
 | |
|         proportions: 1,
 | |
|         proportionsMax: Math.min(50, item.system.quantite),
 | |
|         ajouterEquipement: true
 | |
|       };
 | |
|       await this._rollArtV1(artData, 'odoratgout', nourriture, async (cuisine) => {
 | |
|         await this._resultRecetteCuisine(cuisine);
 | |
|         const remaining = Math.max(item.system.quantite - cuisine.proportions, 0);
 | |
|         if (remaining > 0) {
 | |
|           await item.update({ 'system.quantite': remaining })
 | |
|         }
 | |
|         else {
 | |
|           await this.deleteEmbeddedDocuments('Item', [item.id]);
 | |
|         }
 | |
|       });
 | |
|     }
 | |
|   }
 | |
| }
 | |
| 
 | |
| 
 |