forked from public/foundryvtt-reve-de-dragon
		
	en cas de descente des TMR, suppression des signes draconiques éphémères durant seulement 1 round
		
			
				
	
	
		
			792 lines
		
	
	
		
			27 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			792 lines
		
	
	
		
			27 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
| import { ChatVente } from "../achat-vente/chat-vente.js";
 | |
| import { ChatUtility } from "../chat-utility.js";
 | |
| import { renderTemplate, SYSTEM_SOCKET_ID } from "../constants.js";
 | |
| import { Grammar } from "../grammar.js";
 | |
| import { Monnaie } from "../item-monnaie.js";
 | |
| import { ITEM_TYPES } from "../constants.js";
 | |
| import { Misc } from "../misc.js";
 | |
| import { RdDAudio } from "../rdd-audio.js";
 | |
| import { RdDConfirm } from "../rdd-confirm.js";
 | |
| import { RdDUtility } from "../rdd-utility.js";
 | |
| import { SystemCompendiums } from "../settings/system-compendiums.js";
 | |
| import { RdDItem } from "../item.js";
 | |
| import { STATUSES } from "../settings/status-effects.js";
 | |
| 
 | |
| export class RdDBaseActor extends Actor {
 | |
| 
 | |
|   static _findCaracNode(carac, name) {
 | |
|     return Object.entries(carac)
 | |
|       .filter(it => Grammar.equalsInsensitive(it[1].label, name))
 | |
|       .map(it => it[0])
 | |
|       .find(it => it)
 | |
|   }
 | |
| 
 | |
|   static $findCaracByName(carac, name) {
 | |
|     const caracList = Object.entries(carac);
 | |
|     let entry = Misc.findFirstLike(name, caracList, { mapper: it => it[0], description: 'caractéristique', onMessage: m => { } });
 | |
|     if (!entry || entry.length == 0) {
 | |
|       entry = Misc.findFirstLike(name, caracList, { mapper: it => it[1].label, description: 'caractéristique' });
 | |
|     }
 | |
|     return entry && entry.length > 0 ? carac[entry[0]] : undefined;
 | |
|   }
 | |
| 
 | |
|   static getDefaultValue(actorType, path) {
 | |
|     if (path.includes('.')) {
 | |
|       path = path.split('.')
 | |
|     }
 | |
|     let obj = game.model.Actor[actorType]
 | |
|     for (let p of path) {
 | |
|       obj = obj ? obj[p] : undefined
 | |
|     }
 | |
|     return obj
 | |
|   }
 | |
| 
 | |
|   static getDefaultImg(itemType) {
 | |
|     return game.system.rdd.actorClasses[itemType]?.defaultIcon ?? defaultItemImg[itemType];
 | |
|   }
 | |
| 
 | |
|   static init() {
 | |
|     Handlebars.registerHelper('actor-isFeminin', actor => actor.isFeminin())
 | |
|     Hooks.on("preUpdateItem", (item, change, options, id) => Misc.documentIfResponsible(item.parent)?.onPreUpdateItem(item, change, options, id))
 | |
|     Hooks.on("createItem", (item, options, id) => Misc.documentIfResponsible(item.parent)?.onCreateItem(item, options, id))
 | |
|     Hooks.on("updateItem", (item, options, id) => Misc.documentIfResponsible(item.parent)?.onUpdateItem(item, options, id))
 | |
|     Hooks.on("deleteItem", (item, options, id) => Misc.documentIfResponsible(item.parent)?.onDeleteItem(item, options, id))
 | |
|     Hooks.on("updateActor", (actor, change, options, actorId) => Misc.documentIfResponsible(actor)?.onUpdateActor(change, options, actorId))
 | |
|   }
 | |
| 
 | |
|   static onSocketMessage(sockmsg) {
 | |
|     switch (sockmsg.msg) {
 | |
|       case "msg_remote_actor_call":
 | |
|         return RdDBaseActor.onRemoteActorCall(sockmsg.data, sockmsg.userId);
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   static remoteActorCall(callData) {
 | |
|     if (game.user.isGM) {
 | |
|       RdDBaseActor.onRemoteActorCall(callData, game.user.id)
 | |
|       return false
 | |
|     }
 | |
|     else {
 | |
|       game.socket.emit(SYSTEM_SOCKET_ID, {
 | |
|         msg: "msg_remote_actor_call",
 | |
|         data: callData,
 | |
|         userId: Misc.firstConnectedGMId()
 | |
|       })
 | |
|       return true
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   static onRemoteActorCall(callData, userId) {
 | |
|     const actor = RdDBaseActor.getRealActor(callData?.actorId, callData?.tokenId);
 | |
|     if (userId == game.user.id) {
 | |
|       // Seul le joueur choisi effectue l'appel: le joueur courant si propriétaire de l'actor, ou le MJ sinon
 | |
|       const args = callData.args;
 | |
|       console.info(`RdDBaseActor.onRemoteActorCall: pour l'Actor ${callData.actorId}, appel de RdDBaseActor.${callData.method}(`, ...args, ')');
 | |
|       actor[callData.method](...args);
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   static getRealActor(actorId, tokenId) {
 | |
|     if (tokenId) {
 | |
|       let token = canvas.tokens.get(tokenId)
 | |
|       if (token) {
 | |
|         return token.actor
 | |
|       }
 | |
|     }
 | |
|     return game.actors.get(actorId)
 | |
|   }
 | |
| 
 | |
|   getAlias() {
 | |
|     if (this.token?.name != null && this.token != this.prototypeToken) {
 | |
|       return this.token.name
 | |
|     }
 | |
|     return this.name
 | |
|   }
 | |
| 
 | |
|   isPersonnageJoueur() { return false }
 | |
| 
 | |
|   static extractActorMin = (actor) => { return { id: actor?.id, type: actor?.type, name: actor?.name, img: actor?.img }; };
 | |
| 
 | |
|   /**
 | |
|    * Cette methode surcharge Actor.create() pour ajouter si besoin des Items par défaut:
 | |
|    * compétences et monnaies.
 | |
|    *
 | |
|    * @param {Object} actorData template d'acteur auquel ajouter des informations.
 | |
|    * @param {Object} options   optionspour customiser la création
 | |
|    */
 | |
|   static async create(actorData, options) {
 | |
|     // import depuis un compendium
 | |
|     if (actorData instanceof Array) {
 | |
|       return super.create(actorData, options);
 | |
|     }
 | |
|     // Création d'un acteur avec des items (uniquement en cas de duplication): pas besoin d'ajouter d'items
 | |
|     if (actorData.items) {
 | |
|       return await super.create(actorData, options);
 | |
|     }
 | |
|     actorData.items = [];
 | |
|     if (actorData.type == "personnage") {
 | |
|       const competences = await SystemCompendiums.getCompetences(actorData.type);
 | |
|       actorData.items = actorData.items.concat(competences.map(i => i.toObject()))
 | |
|         .concat(Monnaie.monnaiesStandard());
 | |
|     }
 | |
|     else if (actorData.type == "commerce") {
 | |
|       actorData.items = actorData.items.concat(Monnaie.monnaiesStandard());
 | |
|     }
 | |
|     return super.create(actorData, options);
 | |
|   }
 | |
| 
 | |
|   constructor(docData, context = {}) {
 | |
|     if (!context.rdd?.ready) {
 | |
|       foundry.utils.mergeObject(context, { rdd: { ready: true } });
 | |
|       const ActorConstructor = game.system.rdd.actorClasses[docData.type];
 | |
|       if (ActorConstructor) {
 | |
|         if (!docData.img) {
 | |
|           docData.img = ActorConstructor.defaultIcon;
 | |
|         }
 | |
|         return new ActorConstructor(docData, context);
 | |
|       }
 | |
|     }
 | |
|     context.rdd = undefined
 | |
|     super(docData, context);
 | |
|   }
 | |
| 
 | |
|   findCaracByName(name) {
 | |
|     name = Grammar.toLowerCaseNoAccent(name)
 | |
|     switch (name) {
 | |
|       case 'reve-actuel': case 'reve actuel':
 | |
|         return this.system.carac.reve
 | |
|       case 'chance-actuelle': case 'chance actuelle':
 | |
|         return this.system.carac.chance
 | |
|       case 'vie':
 | |
|         return this.system.sante.vie
 | |
|     }
 | |
| 
 | |
|     const carac = this.system.carac;
 | |
|     return RdDBaseActor.$findCaracByName(carac, name);
 | |
|   }
 | |
| 
 | |
|   mapCarac(caracCode) { return caracCode }
 | |
| 
 | |
|   getCaracByName(name) {
 | |
|     name = this.mapCarac(Grammar.toLowerCaseNoAccent(name))
 | |
|     switch (name) {
 | |
|       case 'reve-actuel': case 'reve actuel':
 | |
|         return this.getCaracReveActuel();
 | |
|       case 'chance-actuelle': case 'chance-actuelle':
 | |
|         return this.getCaracChanceActuelle();
 | |
|     }
 | |
|     return this.findCaracByName(name);
 | |
|   }
 | |
| 
 | |
|   /* -------------------------------------------- */
 | |
|   async _preCreate(data, options, user) {
 | |
|     await super._preCreate(data, options, user);
 | |
| 
 | |
|     // Configure prototype token settings
 | |
|     if (this.type === "personnage") {
 | |
|       this.updateSource({
 | |
|         sight: { enabled: true },
 | |
|         actorLink: options.fromCompendium ? data.prototypeToken.actorLink : true,
 | |
|         disposition: CONST.TOKEN_DISPOSITIONS.FRIENDLY
 | |
|       })
 | |
|     } else {
 | |
|       const prototypeToken = {
 | |
|         sight: { enabled: true },
 | |
|         disposition: CONST.TOKEN_DISPOSITIONS.NEUTRAL
 | |
|       }
 | |
|       this.updateSource({ prototypeToken });
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   /* -------------------------------------------- */
 | |
|   prepareData() {
 | |
|     super.prepareData()
 | |
|     this.prepareActorData()
 | |
|     this.cleanupConteneurs()
 | |
|     this.computeEtatGeneral()
 | |
|     this.computeEncTotal()
 | |
|   }
 | |
| 
 | |
|   prepareActorData() { }
 | |
| 
 | |
|   async computeEtatGeneral() { }
 | |
|   /* -------------------------------------------- */
 | |
|   findPlayer() {
 | |
|     return game.users.players.find(player => player.active && player.character?.id == this.id);
 | |
|   }
 | |
| 
 | |
|   isCreatureEntite() { return this.isCreature() || this.isEntite() }
 | |
|   isCreature() { return false }
 | |
|   isEntite(typeentite = []) { return false }
 | |
|   isHautRevant() { return false }
 | |
|   isVehicule() { return false }
 | |
|   isPersonnage() { return false }
 | |
|   isFeminin() { return false }
 | |
|   getItem(id, type = undefined) {
 | |
|     const item = this.items.get(id);
 | |
|     if (type == undefined || (item?.type == type)) {
 | |
|       return item;
 | |
|     }
 | |
|     return undefined;
 | |
|   }
 | |
| 
 | |
|   listeSuivants(filter = suivant => true) { return [] }
 | |
|   listeSuivants(filter = suivant => true) { return [] }
 | |
|   listItems(type = undefined) { return (type ? this.itemTypes[type] : this.items); }
 | |
|   filterItems(filter, type = undefined) { return (type ? this.itemTypes[type] : this.items)?.filter(filter) ?? []; }
 | |
|   findItemLike(idOrName, type) {
 | |
|     return this.getItem(idOrName, type)
 | |
|       ?? Misc.findFirstLike(idOrName, this.listItems(type), { description: Misc.typeName('Item', type) });
 | |
|   }
 | |
| 
 | |
|   getMonnaie(id) { return this.findItemLike(id, 'monnaie'); }
 | |
|   getEncombrementMax() { return 0 }
 | |
| 
 | |
|   /* -------------------------------------------- */
 | |
|   async updateCarac(caracName, to) {
 | |
|   }
 | |
|   /* -------------------------------------------- */
 | |
|   async onPreUpdateItem(item, change, options, id) { }
 | |
| 
 | |
|   async onCreateItem(item, options, id) { }
 | |
| 
 | |
|   async onUpdateItem(item, options, id) { }
 | |
| 
 | |
|   async onUpdateActor(update, options, actorId) { }
 | |
| 
 | |
|   async onDeleteItem(item, options, id) {
 | |
|     if (item.isInventaire()) {
 | |
|       await this._removeItemFromConteneur(item)
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   async _removeItemFromConteneur(item) {
 | |
|     const updates = this.items.filter(it => it.isConteneur() && it.system.contenu.includes(item.id))
 | |
|       .map(conteneur => {
 | |
|         const nouveauContenu = conteneur.system.contenu.filter(id => id != item.id)
 | |
|         return { _id: conteneur.id, 'system.contenu': nouveauContenu }
 | |
|       })
 | |
|     if (updates.length > 0) {
 | |
|       await this.updateEmbeddedDocuments('Item', updates)
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   async onTimeChanging(oldTimestamp, newTimestamp) {
 | |
|     this.items.filter(it => it.isFinPeriode(oldTimestamp, newTimestamp))
 | |
|       .forEach(async it => await it.onFinPeriodeTemporel(oldTimestamp, newTimestamp))
 | |
|   }
 | |
| 
 | |
|   async creerObjetParMJ(object) {
 | |
|     if (this.isOwner) {
 | |
|       await this.createEmbeddedDocuments('Item', [object])
 | |
|       return
 | |
|     }
 | |
|     RdDBaseActor.remoteActorCall({
 | |
|       tokenId: this.token?.id,
 | |
|       actorId: this.id,
 | |
|       method: 'creerObjetParMJ', args: [object]
 | |
|     })
 | |
|   }
 | |
| 
 | |
|   /* -------------------------------------------- */
 | |
|   async cleanupConteneurs() {
 | |
|     if (Misc.isOwnerPlayer(this)) {
 | |
|       let updates = this.itemTypes['conteneur']
 | |
|         .filter(c => c.system.contenu.filter(id => this.getItem(id) == undefined).length > 0)
 | |
|         .map(c => { return { _id: c._id, 'system.contenu': c.system.contenu.filter(id => this.getItem(id) != undefined) } });
 | |
|       if (updates.length > 0) {
 | |
|         await this.updateEmbeddedDocuments("Item", updates)
 | |
|       }
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   /* -------------------------------------------- */
 | |
|   getFortune() {
 | |
|     return Monnaie.getFortune(this.itemTypes['monnaie']);
 | |
|   }
 | |
| 
 | |
|   /* -------------------------------------------- */
 | |
|   async itemQuantiteIncDec(id, value) {
 | |
|     let item = this.getItem(id);
 | |
|     if (item && item.isInventaire()) {
 | |
|       const quantite = Math.max(0, item.system.quantite + value);
 | |
|       await item.update({ 'system.quantite': quantite });
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   computePrixTotalEquipement() {
 | |
|     return this.items.filter(it => it.isInventaire())
 | |
|       .filter(it => !it.isMonnaie())
 | |
|       .map(it => it.valeurTotale())
 | |
|       .reduce(Misc.sum(), 0);
 | |
|   }
 | |
| 
 | |
|   async payerSols(depense) {
 | |
|     depense = Number(depense);
 | |
|     if (depense == 0) {
 | |
|       return;
 | |
|     }
 | |
|     let fortune = this.getFortune();
 | |
|     console.log("payer", game.user.character, depense, fortune);
 | |
|     // TODO: passer en handlebars
 | |
|     let msg = "";
 | |
|     if (fortune >= depense) {
 | |
|       await Monnaie.optimiserFortune(this, fortune - depense);
 | |
|       msg = `Vous avez payé <strong>${depense} Sols</strong>, qui ont été soustraits de votre argent.`;
 | |
|       RdDAudio.PlayContextAudio("argent"); // Petit son
 | |
|     } else {
 | |
|       msg = "Vous n'avez pas assez d'argent pour payer cette somme !";
 | |
|     }
 | |
| 
 | |
|     ChatMessage.create({
 | |
|       whisper: ChatUtility.getOwners(this),
 | |
|       content: msg
 | |
|     })
 | |
|   }
 | |
| 
 | |
|   async depenserSols(sols) {
 | |
|     let reste = this.getFortune() - Number(sols);
 | |
|     if (reste >= 0) {
 | |
|       await Monnaie.optimiserFortune(this, reste);
 | |
|     }
 | |
|     return reste;
 | |
|   }
 | |
| 
 | |
|   async ajouterSols(sols, fromActorId = undefined) {
 | |
|     sols = Number(sols);
 | |
|     if (sols == 0) {
 | |
|       return;
 | |
|     }
 | |
|     if (sols < 0) {
 | |
|       ui.notifications.error(`Impossible d'ajouter un gain de ${sols} <0`);
 | |
|       return;
 | |
|     }
 | |
|     if (fromActorId && !this.isOwner) {
 | |
|       RdDBaseActor.remoteActorCall({
 | |
|         userId: Misc.connectedGMOrUser(),
 | |
|         tokenId: this.token?.id,
 | |
|         actorId: this.id,
 | |
|         method: 'ajouterSols', args: [sols, fromActorId]
 | |
|       });
 | |
|     }
 | |
|     else {
 | |
|       const fromActor = game.actors.get(fromActorId)
 | |
|       await Monnaie.optimiserFortune(this, sols + this.getFortune());
 | |
| 
 | |
|       RdDAudio.PlayContextAudio("argent"); // Petit son
 | |
|       ChatMessage.create({
 | |
|         whisper: ChatUtility.getOwners(this),
 | |
|         content: `Vous avez reçu <strong>${sols} Sols</strong> ${fromActor ? " de " + fromActor.name : ''}, qui ont été ajoutés à votre argent.`
 | |
|       });
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   /* -------------------------------------------- */
 | |
| 
 | |
|   getQuantiteDisponible(item) {
 | |
|     return item?.isService() ? undefined : item?.getQuantite();
 | |
|   }
 | |
| 
 | |
|   /* -------------------------------------------- */
 | |
|   async achatVente(achat) {
 | |
|     if (achat.vendeurId == achat.acheteurId) {
 | |
|       ui.notifications.info("Inutile de se vendre à soi-même");
 | |
|       return;
 | |
|     }
 | |
|     if (!Misc.isFirstConnectedGM()) {
 | |
|       RdDBaseActor.remoteActorCall({
 | |
|         actorId: achat.vendeurId ?? achat.acheteurId,
 | |
|         method: 'achatVente', args: [achat]
 | |
|       });
 | |
|       return
 | |
|     }
 | |
|     const cout = Number(achat.prixTotal ?? 0);
 | |
|     const vendeur = achat.vendeurId ? game.actors.get(achat.vendeurId) : undefined;
 | |
|     const acheteur = achat.acheteurId ? game.actors.get(achat.acheteurId) : undefined;
 | |
|     const quantite = (achat.choix.nombreLots ?? 1) * (achat.vente.tailleLot);
 | |
|     const itemVendu = vendeur?.getItem(achat.vente.item._id) ?? game.items.get(achat.vente.item._id);
 | |
|     if (!itemVendu) {
 | |
|       ChatUtility.notifyUser(achat.userId, 'warn', vendeur ? `Le vendeur n'a pas plus de ${achat.vente.item.name} !` : `Impossible de retrouver: ${achat.vente.item.name} !`);
 | |
|       return;
 | |
|     }
 | |
|     if (vendeur && !vendeur.verifierQuantite(itemVendu, quantite)) {
 | |
|       ChatUtility.notifyUser(achat.userId, 'warn', `Le vendeur n'a pas assez de ${itemVendu.name} !`);
 | |
|       return
 | |
|     }
 | |
|     if (acheteur && !acheteur.verifierFortune(cout)) {
 | |
|       ChatUtility.notifyUser(achat.userId, 'warn', `Vous n'avez pas assez d'argent pour payer ${Math.ceil(cout / 100)} sols !`);
 | |
|       return;
 | |
|     }
 | |
|     await vendeur?.vendre(itemVendu, quantite, cout);
 | |
|     await acheteur?.acheter(itemVendu, quantite, cout, achat)
 | |
| 
 | |
|     if (cout > 0) {
 | |
|       RdDAudio.PlayContextAudio("argent");
 | |
|     }
 | |
|     const chatAchatItem = foundry.utils.duplicate(achat.vente);
 | |
|     chatAchatItem.quantiteTotal = quantite;
 | |
|     ChatMessage.create({
 | |
|       user: achat.userId,
 | |
|       speaker: { alias: (acheteur ?? vendeur).getAlias() },
 | |
|       whisper: ChatUtility.getOwners(this),
 | |
|       content: await renderTemplate('systems/foundryvtt-reve-de-dragon/templates/chat-achat-item.hbs', chatAchatItem)
 | |
|     });
 | |
| 
 | |
|     if (!achat.vente.quantiteIllimite) {
 | |
|       if (achat.vente.nbLots <= achat.choix.nombreLots) {
 | |
|         ChatUtility.removeChatMessageId(achat.chatMessageIdVente);
 | |
|       }
 | |
|       else if (achat.chatMessageIdVente) {
 | |
|         await ChatVente.diminuerQuantiteAchatVente(achat.chatMessageIdVente, achat.choix.nombreLots)
 | |
|       }
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   async vendre(item, quantite, cout) {
 | |
|     await this.ajouterSols(cout);
 | |
|     await this.decrementerQuantiteItem(item, quantite);
 | |
|   }
 | |
| 
 | |
|   async acheter(item, quantite, cout, achat) {
 | |
|     await this.depenserSols(cout)
 | |
|     const createdItemId = await this.creerQuantiteItem(item, quantite)
 | |
|     if (achat.choix.consommer && item.type == 'nourritureboisson' && createdItemId != undefined) {
 | |
|       achat.choix.doses = achat.choix.nombreLots;
 | |
|       await this.consommerNourritureboisson(createdItemId, achat.choix, achat.vente.actingUserId);
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   verifierFortune(cout) {
 | |
|     return this.getFortune() >= cout;
 | |
|   }
 | |
| 
 | |
|   verifierQuantite(item, quantiteDemande) {
 | |
|     const disponible = this.getQuantiteDisponible(item);
 | |
|     return disponible == undefined || disponible >= quantiteDemande;
 | |
|   }
 | |
| 
 | |
|   async consommerNourritureboisson(itemId, choix, userId) { }
 | |
| 
 | |
|   async decrementerQuantiteItem(item, quantite, options = { supprimerSiZero: true }) {
 | |
|     if (item.isService()) {
 | |
|       return;
 | |
|     }
 | |
|     const itemId = item.id;
 | |
|     let resteQuantite = (item.system.quantite ?? 1) - quantite;
 | |
|     if (resteQuantite <= 0) {
 | |
|       if (options.supprimerSiZero) {
 | |
|         await this.deleteEmbeddedDocuments("Item", [item.id]);
 | |
|       }
 | |
|       else {
 | |
|         await this.updateEmbeddedDocuments("Item", [{ _id: itemId, 'system.quantite': 0 }]);
 | |
|       }
 | |
|       if (resteQuantite < 0) {
 | |
|         ui.notifications.warn(`La quantité de ${item.name} était insuffisante, l'objet a donc été supprimé`)
 | |
|       }
 | |
|     }
 | |
|     else if (resteQuantite > 0) {
 | |
|       const realItem = this.getItem(item.id)
 | |
|       realItem.update({ 'system.quantite': resteQuantite });
 | |
|       await this.updateEmbeddedDocuments("Item", [{ _id: item.id, 'system.quantite': resteQuantite }]);
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   async creerQuantiteItem(item, quantite) {
 | |
|     if (this.canReceive(item)) {
 | |
|       const isItemEmpilable = "quantite" in item.system;
 | |
|       const baseItem = {
 | |
|         type: item.type,
 | |
|         img: item.img,
 | |
|         name: item.name,
 | |
|         system: foundry.utils.mergeObject(item.system, { quantite: isItemEmpilable ? quantite : undefined }, { inplace: false })
 | |
|       };
 | |
|       const newItems = isItemEmpilable ? [baseItem] : Array.from({ length: quantite }, (_, i) => baseItem);
 | |
|       const items = await this.createEmbeddedDocuments("Item", newItems);
 | |
|       return items.length > 0 ? items[0].id : undefined;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   /* -------------------------------------------- */
 | |
|   async computeEncTotal() {
 | |
|     if (!this.pack) {
 | |
|       this.encTotal = this.items.filter(it => RdDItem.getItemTypesInventaire().includes(it.type))
 | |
|         .map(it => it.getEncTotal()).reduce(Misc.sum(), 0)
 | |
|       return this.encTotal;
 | |
|     }
 | |
|     return 0;
 | |
|   }
 | |
| 
 | |
|   getEncTotal() {
 | |
|     return Math.floor(this.encTotal ?? 0);
 | |
|   }
 | |
| 
 | |
|   async createItem(type, name = undefined) {
 | |
|     if (!name) {
 | |
|       name = 'Nouveau ' + Misc.typeName('Item', type);
 | |
|     }
 | |
|     await this.createEmbeddedDocuments('Item', [{ name: name, type: type }], { renderSheet: true });
 | |
|   }
 | |
| 
 | |
|   canReceive(item) {
 | |
|     return false;
 | |
|   }
 | |
| 
 | |
|   async processDropItem(params) {
 | |
|     const targetActorId = this.id
 | |
|     const sourceActorId = params.sourceActorId
 | |
|     const sourceTokenId = params.sourceTokenId
 | |
|     const itemId = params.itemId
 | |
|     const destId = params.destId
 | |
|     const srcId = params.srcId
 | |
|     if (sourceActorId && sourceActorId != targetActorId) {
 | |
|       console.log("Moving objects", sourceActorId, sourceTokenId, targetActorId, itemId);
 | |
|       this.moveItemsBetweenActors(itemId, sourceActorId, sourceTokenId);
 | |
|       return false;
 | |
|     }
 | |
|     let result = true;
 | |
|     const item = this.getItem(itemId);
 | |
|     if (item?.isInventaire('all') && sourceActorId == targetActorId) {
 | |
|       // rangement
 | |
|       if (srcId != destId && itemId != destId) { // déplacement de l'objet
 | |
|         const src = this.getItem(srcId);
 | |
|         const dest = this.getItem(destId);
 | |
|         const cible = this.getContenantOrParent(dest);
 | |
|         const [empilable, message] = item.isInventaireEmpilable(dest);
 | |
|         if (empilable) {
 | |
|           await dest.empiler(item)
 | |
|           result = false;
 | |
|         }
 | |
|         // changer de conteneur
 | |
|         else if (!cible || this.conteneurPeutContenir(cible, item)) {
 | |
|           await this.enleverDeConteneur(item, src, params.onEnleverConteneur);
 | |
|           await this.ajouterDansConteneur(item, cible, params.onAjouterDansConteneur);
 | |
|           if (message && !dest.isConteneur()) {
 | |
|             ui.notifications.info(cible
 | |
|               ? `${message}<br>${item.name} a été déplacé dans: ${cible.name}`
 | |
|               : `${message}<br>${item.name} a été sorti du conteneur`);
 | |
|           }
 | |
|         }
 | |
|       }
 | |
|     }
 | |
|     await this.computeEncTotal();
 | |
|     return result;
 | |
|   }
 | |
| 
 | |
|   getContenantOrParent(dest) {
 | |
|     if (!dest || dest.isConteneur()) {
 | |
|       return dest;
 | |
|     }
 | |
|     return this.getContenant(dest);
 | |
|   }
 | |
| 
 | |
|   getContenant(item) {
 | |
|     return this.itemTypes['conteneur'].find(it => it.system.contenu.includes(item.id));
 | |
|   }
 | |
| 
 | |
| 
 | |
|   /* -------------------------------------------- */
 | |
|   conteneurPeutContenir(dest, moved) {
 | |
|     if (!dest) {
 | |
|       return true;
 | |
|     }
 | |
|     if (!dest.isConteneur()) {
 | |
|       return false;
 | |
|     }
 | |
|     if (moved.isConteneurContenu(dest)) {
 | |
|       ui.notifications.warn(`Impossible de déplacer un conteneur parent (${moved.name}) dans un de ses contenus ${dest.name} !`);
 | |
|       return false;
 | |
|     }
 | |
| 
 | |
|     // Calculer le total actuel des contenus
 | |
|     const encContenu = dest.getEncContenu();
 | |
|     const newEnc = moved.getEncTotal(); // Calculer le total actuel du nouvel objet
 | |
|     const placeDisponible = Misc.keepDecimals(dest.system.capacite - encContenu - newEnc, 4)
 | |
| 
 | |
|     // Teste si le conteneur de destination a suffisament de capacité pour recevoir le nouvel objet
 | |
|     if (placeDisponible < 0) {
 | |
|       ui.notifications.warn(
 | |
|         `Le conteneur ${dest.name} a une capacité de ${dest.system.capacite}, et contient déjà ${encContenu}.
 | |
|         Impossible d'y ranger: ${moved.name} d'encombrement ${newEnc}!`);
 | |
|       return false;
 | |
|     }
 | |
|     return true;
 | |
|   }
 | |
| 
 | |
|   /* -------------------------------------------- */
 | |
|   /** Ajoute un item dans un conteneur, sur la base de leurs ID */
 | |
|   async ajouterDansConteneur(item, conteneur, onAjouterDansConteneur) {
 | |
|     if (conteneur?.isConteneur()) {
 | |
|       item.estContenu = true;
 | |
|       const nouveauContenu = [...conteneur.system.contenu, item.id];
 | |
|       await conteneur.update({ 'system.contenu': nouveauContenu });
 | |
|       onAjouterDansConteneur(item.id, conteneur.id)
 | |
|     }
 | |
|     else {
 | |
|       item.estContenu = false;
 | |
|       await conteneur?.update({ 'system.-=contenu': undefined })
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   /* -------------------------------------------- */
 | |
|   /** Fonction de remise à plat de l'équipement (ie vide les champs 'contenu') */
 | |
|   async nettoyerConteneurs() {
 | |
|     RdDConfirm.confirmer({
 | |
|       settingConfirmer: "confirmation-vider",
 | |
|       content: `<p>Etes vous certain de vouloir vider tous les conteneurs ?</p>`,
 | |
|       title: 'Vider les conteneurs',
 | |
|       buttonLabel: 'Vider',
 | |
|       onAction: async () => {
 | |
|         const corrections = [];
 | |
|         for (let item of this.items) {
 | |
|           if (item.estContenu) {
 | |
|             item.estContenu = undefined;
 | |
|           }
 | |
|           if (item.system.contenu != undefined) {
 | |
|             if (item.type == 'conteneur') {
 | |
|               corrections.push({ _id: item.id, 'system.contenu': [] });
 | |
|             }
 | |
|             else {
 | |
|               corrections.push({ _id: item.id, 'system.-=contenu': undefined });
 | |
|             }
 | |
|           }
 | |
|         }
 | |
|         if (corrections.length > 0) {
 | |
|           await this.updateEmbeddedDocuments('Item', corrections);
 | |
|         }
 | |
|       }
 | |
|     });
 | |
|   }
 | |
| 
 | |
|   /* -------------------------------------------- */
 | |
|   buildSubConteneurObjetList(conteneurId, deleteList) {
 | |
|     let conteneur = this.getItem(conteneurId);
 | |
|     if (conteneur?.type == 'conteneur') { // Si c'est un conteneur
 | |
|       for (let subId of conteneur.system.contenu) {
 | |
|         let subObj = this.getItem(subId);
 | |
|         if (subObj) {
 | |
|           if (subObj.type == 'conteneur') {
 | |
|             this.buildSubConteneurObjetList(subId, deleteList);
 | |
|           }
 | |
|           deleteList.push({ id: subId, conteneurId: conteneurId });
 | |
|         }
 | |
|       }
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   /* -------------------------------------------- */
 | |
|   async deleteAllConteneur(itemId, options) {
 | |
|     let list = [];
 | |
|     list.push({ id: itemId, conteneurId: undefined }); // Init list
 | |
|     this.buildSubConteneurObjetList(itemId, list);
 | |
|     await this.deleteEmbeddedDocuments('Item', list.map(it => it.id), options);
 | |
|   }
 | |
| 
 | |
|   /* -------------------------------------------- */
 | |
|   /**
 | |
|    * Supprime un item d'un conteneur, sur la base de leurs ID
 | |
|    */
 | |
|   async enleverDeConteneur(item, conteneur, onEnleverDeConteneur) {
 | |
|     if (conteneur) {
 | |
|       if (conteneur.isConteneur()) {
 | |
|         const contenu = conteneur.system.contenu.filter(id => id != item.id);
 | |
|         await conteneur.update({ 'system.contenu': contenu });
 | |
|         onEnleverDeConteneur();
 | |
|       }
 | |
|       else {
 | |
|         await conteneur.update({ 'system.-=contenu': undefined })
 | |
|       }
 | |
|     }
 | |
|     item.estContenu = false;
 | |
|   }
 | |
| 
 | |
|   /* -------------------------------------------- */
 | |
|   async moveItemsBetweenActors(itemId, sourceActorId, sourceTokenId) {
 | |
|     let sourceActor = RdDBaseActor.getRealActor(sourceActorId, sourceTokenId)
 | |
|     let itemsList = [{ id: itemId, conteneurId: undefined }]
 | |
|     sourceActor.buildSubConteneurObjetList(itemId, itemsList); // Get itemId list
 | |
| 
 | |
|     const itemsDataToCreate = itemsList.map(it => sourceActor.getItem(it.id))
 | |
|       .map(it => foundry.utils.duplicate(it))
 | |
|       .map(it => { it.system.contenu = []; return it; });
 | |
|     let newItems = await this.createEmbeddedDocuments('Item', itemsDataToCreate);
 | |
| 
 | |
|     let itemMap = this._buildMapOldNewId(itemsList, newItems);
 | |
| 
 | |
|     for (let item of itemsList) { // Second boucle pour traiter la remise en conteneurs
 | |
|       // gestion conteneur/contenu
 | |
|       if (item.conteneurId) { // l'Objet était dans un conteneur
 | |
|         const newConteneurId = itemMap[item.conteneurId];
 | |
|         const newConteneur = this.getItem(newConteneurId);
 | |
|         const newItemId = itemMap[item.id]; // Get newItem
 | |
| 
 | |
|         console.log('New conteneur filling!', newConteneur, newItemId, item);
 | |
|         const nouveauContenu = [...newConteneur.system.contenu, newItemId]
 | |
|         await newConteneur.update({ 'system.contenu': nouveauContenu })
 | |
|       }
 | |
|     }
 | |
|     const deletedItemIds = itemsList.map(it => it.id)
 | |
|     await sourceActor.deleteEmbeddedDocuments('Item', deletedItemIds);
 | |
|   }
 | |
| 
 | |
|   _buildMapOldNewId(itemsList, newItems) {
 | |
|     let itemMap = {};
 | |
|     for (let i = 0; i < itemsList.length; i++) {
 | |
|       itemMap[itemsList[i].id] = newItems[i].id; // Pour garder le lien ancien / nouveau
 | |
|     }
 | |
|     return itemMap;
 | |
|   }
 | |
| 
 | |
|   /* -------------------------------------------- */
 | |
|   async postActorToChat(modeOverride) {
 | |
|     let chatData = {
 | |
|       doctype: 'Actor',
 | |
|       id: this.id,
 | |
|       type: this.type,
 | |
|       img: this.img,
 | |
|       pack: this.pack,
 | |
|       name: this.getAlias(),
 | |
|       system: { description: this.system.description }
 | |
|     }
 | |
|     renderTemplate('systems/foundryvtt-reve-de-dragon/templates/post-actor.hbs', chatData)
 | |
|       .then(html => ChatMessage.create(RdDUtility.chatDataSetup(html, modeOverride)));
 | |
|   }
 | |
| 
 | |
|   actionImpossible(action) {
 | |
|     ui.notifications.info(`${this.getAlias()} ne peut pas faire cette action: ${action}`)
 | |
|   }
 | |
| 
 | |
|   isAlcoolise() { return false }
 | |
|   async jetEthylisme() { this.actionImpossible("jet d'éthylisme") }
 | |
|   async rollAppelChance() { this.actionImpossible("appel à la chance") }
 | |
|   async jetDeMoral() { this.actionImpossible("jet de moral") }
 | |
| 
 | |
|   async resetItemUse() { }
 | |
|   async incDecItemUse(itemId, shouldIncrease = true) { }
 | |
|   getItemUse(itemId) { return 0; }
 | |
| 
 | |
|   async finDeRound(options = { terminer: false }) { }
 | |
|   isActorCombat() { return false }
 | |
|   getCaracInit(competence) { return 0 }
 | |
| 
 | |
|   listAttaques() {
 | |
|     return this.listActions({ isAttaque: true, isEquipe: false })
 | |
|   }
 | |
| 
 | |
|   listActions({ isAttaque = false, isEquipe = false }) { return [] }
 | |
| 
 | |
|   listActionsPossessions() {
 | |
|     return this.itemTypes[ITEM_TYPES.possession]
 | |
|       .map(p => {
 | |
|         return {
 | |
|           label: p.name,
 | |
|           action: 'possession',
 | |
|           possessionid: p.system.possessionid,
 | |
|         }
 | |
|       })
 | |
|   }
 | |
|   listActionsCombat() {
 | |
|     const possessions = this.listActionsPossessions()
 | |
|     return possessions.length > 0 ? possessions : this.listActions({})
 | |
| 
 | |
|   }
 | |
| } |