diff --git a/module/actor-sheet.js b/module/actor-sheet.js index b735fda4..803ad71c 100644 --- a/module/actor-sheet.js +++ b/module/actor-sheet.js @@ -10,7 +10,6 @@ import { DialogSplitItem } from "./dialog-split-item.js"; import { ReglesOptionelles } from "./settings/regles-optionelles.js"; import { RdDSheetUtility } from "./rdd-sheet-utility.js"; import { STATUSES } from "./settings/status-effects.js"; -import { Monnaie } from "./item-monnaie.js"; import { MAINS_DIRECTRICES } from "./actor.js"; import { RdDBaseActorSheet } from "./actor/base-actor-sheet.js"; import { RdDItem } from "./item.js"; @@ -46,10 +45,8 @@ export class RdDActorSheet extends RdDBaseActorSheet { effects: this.actor.effects.map(e => foundry.utils.deepClone(e)), limited: this.actor.limited, owner: this.actor.isOwner, - description: await TextEditor.enrichHTML(this.object.system.description, { async: true }), - biographie: await TextEditor.enrichHTML(this.object.system.biographie, { async: true }), - notes: await TextEditor.enrichHTML(this.object.system.notes, { async: true }), - notesmj: await TextEditor.enrichHTML(this.object.system.notesmj, { async: true }), + biographie: await TextEditor.enrichHTML(this.actor.system.biographie, { async: true }), + notes: await TextEditor.enrichHTML(this.actor.system.notes, { async: true }), }); mergeObject(formData.calc, { surenc: this.actor.computeMalusSurEncombrement(), diff --git a/module/actor.js b/module/actor.js index 8b85cdce..dca482fb 100644 --- a/module/actor.js +++ b/module/actor.js @@ -13,7 +13,6 @@ import { RdDItemSort } from "./item-sort.js"; import { Grammar } from "./grammar.js"; import { RdDEncaisser } from "./rdd-roll-encaisser.js"; import { RdDCombat } from "./rdd-combat.js"; -import { RdDAudio } from "./rdd-audio.js"; import { RdDItemCompetence } from "./item-competence.js"; import { RdDItemArme } from "./item-arme.js"; import { RdDAlchimie } from "./rdd-alchimie.js"; @@ -24,13 +23,11 @@ import { ReglesOptionelles } from "./settings/regles-optionelles.js"; import { EffetsDraconiques } from "./tmr/effets-draconiques.js"; import { Draconique } from "./tmr/draconique.js"; import { RdDCarac } from "./rdd-carac.js"; -import { Monnaie } from "./item-monnaie.js"; import { DialogConsommer } from "./dialog-item-consommer.js"; import { DialogFabriquerPotion } from "./dialog-fabriquer-potion.js"; import { RollDataAjustements } from "./rolldata-ajustements.js"; -import { RdDItem } from "./item.js"; import { RdDPossession } from "./rdd-possession.js"; -import { ENTITE_BLURETTE, ENTITE_INCARNE, ENTITE_NONINCARNE, HIDE_DICE, SHOW_DICE, SYSTEM_RDD, SYSTEM_SOCKET_ID } from "./constants.js"; +import { ENTITE_BLURETTE, ENTITE_INCARNE, ENTITE_NONINCARNE, SHOW_DICE, SYSTEM_RDD, SYSTEM_SOCKET_ID } from "./constants.js"; import { RdDConfirm } from "./rdd-confirm.js"; import { DialogValidationEncaissement } from "./dialog-validation-encaissement.js"; import { RdDRencontre } from "./item-rencontre.js"; @@ -77,12 +74,12 @@ export class RdDActor extends RdDBaseActor { /* -------------------------------------------- */ _prepareCreatureData(actorData) { - this.computeEncombrementTotalEtMalusArmure(); + this.computeEncTotal(); } /* -------------------------------------------- */ _prepareVehiculeData(actorData) { - this.computeEncombrementTotalEtMalusArmure(); + this.computeEncTotal(); } /* -------------------------------------------- */ @@ -94,7 +91,8 @@ export class RdDActor extends RdDBaseActor { RdDCarac.computeCarac(actorData.system) this.computeIsHautRevant(); await this.cleanupConteneurs(); - await this.computeEncombrementTotalEtMalusArmure(); + await this.computeEncTotal(); + await this.computeMalusArmure(); } /* -------------------------------------------- */ @@ -234,10 +232,6 @@ export class RdDActor extends RdDBaseActor { } /* -------------------------------------------- */ - getMonnaie(id) { - return this.findItemLike(id, 'monnaie'); - } - getTache(id) { return this.findItemLike(id, 'tache'); } @@ -993,237 +987,7 @@ export class RdDActor extends RdDBaseActor { await this.update({ [`system.attributs.${fieldName}.value`]: fieldValue }); } - /* -------------------------------------------- */ - _isConteneurContenu(item, conteneur) { - if (item?.isConteneur()) { // Si c'est un conteneur, il faut vérifier qu'on ne le déplace pas vers un sous-conteneur lui appartenant - for (let id of item.system.contenu) { - let subObjet = this.getItem(id); - if (subObjet?.id == conteneur.id) { - return true; // Loop detected ! - } - if (subObjet?.isConteneur()) { - return this._isConteneurContenu(subObjet, conteneur); - } - } - } - return false; - } - /* -------------------------------------------- */ - getRecursiveEnc(objet) { - if (!objet) { - return 0; - } - const tplData = objet.system; - if (objet.type != 'conteneur') { - return Number(tplData.encombrement) * Number(tplData.quantite); - } - const encContenus = tplData.contenu.map(idContenu => this.getRecursiveEnc(this.getItem(idContenu))); - return encContenus.reduce(Misc.sum(), 0) - + Number(tplData.encombrement) /* TODO? Number(tplData.quantite) -- on pourrait avoir plusieurs conteneurs...*/ - } - - /* -------------------------------------------- */ - 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?.isConteneur()) { - item.estContenu = false; - await this.updateEmbeddedDocuments('Item', [{ - _id: conteneur.id, - 'system.contenu': conteneur.system.contenu.filter(id => id != item.id) - }]); - onEnleverDeConteneur(); - } - } - - /* -------------------------------------------- */ - /** Ajoute un item dans un conteneur, sur la base - * de leurs ID */ - async ajouterDansConteneur(item, conteneur, onAjouterDansConteneur) { - if (!conteneur) { - // TODO: afficher - item.estContenu = false; - } - else if (conteneur.isConteneur()) { - item.estContenu = true; - await this.updateEmbeddedDocuments('Item', [{ - _id: conteneur.id, - 'system.contenu': [...conteneur.system.contenu, item.id] - }]); - onAjouterDansConteneur(item.id, conteneur.id); - } - } - - /* -------------------------------------------- */ - /** Fonction de remise à plat de l'équipement (ie vide les champs 'contenu') */ - async nettoyerConteneurs() { - RdDConfirm.confirmer({ - settingConfirmer: "confirmation-vider", - content: `

Etes vous certain de vouloir vider tous les conteneurs ?

`, - title: 'Vider les conteneurs', - buttonLabel: 'Vider', - onAction: async () => { - const corrections = []; - for (let item of this.items) { - if (item.estContenu) { - item.estContenu = undefined; - } - if (item.type == 'conteneur' && item.system.contenu.length > 0) { - corrections.push({ _id: item.id, 'system.contenu': [] }); - } - } - if (corrections.length > 0) { - await this.updateEmbeddedDocuments('Item', corrections); - } - } - }); - } - - async processDropItem(params) { - const targetActorId = this.id; - const sourceActorId = params.sourceActorId; - const itemId = params.itemId; - const destId = params.destId; - const srcId = params.srcId; - if (sourceActorId && sourceActorId != targetActorId) { - console.log("Moving objects", sourceActorId, targetActorId, itemId); - this.moveItemsBetweenActors(itemId, sourceActorId); - return false; - } - let result = true; - const item = this.getItem(itemId); - if (item?.isInventaire() && 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}
${item.name} a été déplacé dans: ${cible.name}` - : `${message}
${item.name} a été sorti du conteneur`); - } - } - } - } - await this.computeEncombrementTotalEtMalusArmure(); - 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, item) { - if (!dest) { - return true; - } - if (!dest.isConteneur()) { - return false; - } - const destData = dest - if (this._isConteneurContenu(item, dest)) { - ui.notifications.warn(`Impossible de déplacer un conteneur parent (${item.name}) dans un de ses contenus ${destData.name} !`); - return false; // Loop detected ! - } - - // Calculer le total actuel des contenus - let encContenu = this.getRecursiveEnc(dest) - Number(destData.system.encombrement); - let newEnc = this.getRecursiveEnc(item); // Calculer le total actuel du nouvel objet - - // Teste si le conteneur de destination a suffisament de capacité pour recevoir le nouvel objet - if (Number(destData.system.capacite) < encContenu + newEnc) { - ui.notifications.warn( - `Le conteneur ${dest.name} a une capacité de ${destData.system.capacite}, et contient déjà ${encContenu}. - Impossible d'y ranger: ${item.name} d'encombrement ${newEnc}!`); - return false; - } - return true; - - } - - /* -------------------------------------------- */ - async moveItemsBetweenActors(itemId, sourceActorId) { - let itemsList = [] - let sourceActor = game.actors.get(sourceActorId); - itemsList.push({ id: itemId, conteneurId: undefined }); // Init list - sourceActor.buildSubConteneurObjetList(itemId, itemsList); // Get itemId list - - const itemsDataToCreate = itemsList.map(it => sourceActor.getItem(it.id)) - .map(it => 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 - let newConteneurId = itemMap[item.conteneurId]; // Get conteneur - let newConteneur = this.getItem(newConteneurId); - - let newItemId = itemMap[item.id]; // Get newItem - - console.log('New conteneur filling!', newConteneur, newItemId, item); - let contenu = duplicate(newConteneur.system.contenu); - contenu.push(newItemId); - await this.updateEmbeddedDocuments('Item', [{ _id: newConteneurId, 'system.contenu': contenu }]); - } - } - for (let item of itemsList) { - await sourceActor.deleteEmbeddedDocuments('Item', [item.id]); - } - } - - _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; - } isSurenc() { return this.isPersonnage() ? (this.computeMalusSurEncombrement() < 0) : false @@ -1268,16 +1032,6 @@ export class RdDActor extends RdDBaseActor { return this.listItems(type).find(it => Grammar.toLowerCaseNoAccent(it.name) == name); } - /* -------------------------------------------- */ - async computeEncombrementTotalEtMalusArmure() { - if (!this.pack) { - await this.computeMalusArmure(); - this.encTotal = this.items.map(it => it.getEncTotal()).reduce(Misc.sum(), 0); - return this.encTotal; - } - return 0; - } - /* -------------------------------------------- */ async computeMalusArmure() { if (this.isPersonnage()) { @@ -1291,15 +1045,6 @@ export class RdDActor extends RdDBaseActor { } } - /* -------------------------------------------- */ - computePrixTotalEquipement() { - const valeur = this.items.filter(it => it.isInventaire()) - .filter(it => !it.isMonnaie()) - .map(it => it.valeurTotale()) - .reduce(Misc.sum(), 0); - return valeur; - } - /* -------------------------------------------- */ computeResumeBlessure(blessures = undefined) { blessures = blessures ?? this.system.blessures; @@ -1332,7 +1077,7 @@ export class RdDActor extends RdDBaseActor { } } - recompute(){ + recompute() { this.computeEtatGeneral(); } @@ -3282,7 +3027,7 @@ export class RdDActor extends RdDBaseActor { if (item && ['arme', 'armure'].includes(item.type)) { const isEquipe = !item.system.equipe; await this.updateEmbeddedDocuments('Item', [{ _id: item.id, "system.equipe": isEquipe }]); - this.computeEncombrementTotalEtMalusArmure(); // Mise à jour encombrement + this.computeEncTotal(); // Mise à jour encombrement if (isEquipe) this.verifierForceMin(item); } @@ -3575,186 +3320,6 @@ export class RdDActor extends RdDBaseActor { return; } - /* -------------------------------------------- */ - async payerSols(depense) { - depense = Number(depense); - if (depense == 0) { - return; - } - let fortune = super.getFortune(); - console.log("payer", game.user.character, depense, fortune); - let msg = ""; - if (fortune >= depense) { - await Monnaie.optimiserFortune(this, fortune - depense); - msg = `Vous avez payé ${depense} Sols, 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 !"; - } - - let message = { - whisper: ChatUtility.getWhisperRecipientsAndGMs(this.name), - content: msg - }; - ChatMessage.create(message); - } - - async depenserSols(sols) { - let reste = super.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 && !game.user.isGM) { - RdDBaseActor.remoteActorCall({ - userId: Misc.connectedGMOrUser(), - 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.getWhisperRecipientsAndGMs(this.name), - content: `Vous avez reçu ${sols} Sols ${fromActor ? " de " + fromActor.name : ''}, qui ont été ajoutés à votre argent.` - }); - } - } - - /* -------------------------------------------- */ - async monnaieIncDec(id, value) { - let monnaie = this.getMonnaie(id); - if (monnaie) { - const quantite = Math.max(0, monnaie.system.quantite + value); - await this.updateEmbeddedDocuments('Item', [{ _id: monnaie.id, 'system.quantite': quantite }]); - } - } - - /* -------------------------------------------- */ - async achatVente(achat) { - if (achat.vendeurId == achat.acheteurId) { - ui.notifications.info("Inutile de se vendre à soi-même"); - return; - } - if (!Misc.isUniqueConnectedGM()) { - 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 vente = achat.vente; - const quantite = (achat.choix.nombreLots ?? 1) * (vente.tailleLot); - const itemVendu = vendeur?.getItem(vente.item._id) ?? (await RdDItem.getCorrespondingItem(vente.item)); - if (!this.verifierQuantite(vendeur, itemVendu, quantite)) { - ChatUtility.notifyUser(achat.userId, 'warn', `Le vendeur n'a pas assez de ${itemVendu.name} !`); - return - } - - if ((acheteur?.getFortune() ?? 0) < Number(cout)) { - ChatUtility.notifyUser(achat.userId, 'warn', `Vous n'avez pas assez d'argent pour payer ${Math.ceil(cout / 100)} sols !`); - return; - } - await this.decrementerVente(vendeur, itemVendu, quantite, cout); - if (acheteur) { - await acheteur.depenserSols(cout); - let createdItemId = await acheteur.creerQuantiteItem(vente.item, quantite); - await acheteur.consommerNourritureAchetee(achat, vente, createdItemId); - } - if (cout > 0) { - RdDAudio.PlayContextAudio("argent"); - } - const chatAchatItem = duplicate(vente); - chatAchatItem.quantiteTotal = quantite; - ChatMessage.create({ - user: achat.userId, - speaker: { alias: (acheteur ?? vendeur).name }, - whisper: ChatUtility.getWhisperRecipientsAndGMs(this.name), - content: await renderTemplate('systems/foundryvtt-reve-de-dragon/templates/chat-achat-item.html', chatAchatItem) - }); - - if (!vente.quantiteIllimite) { - if (vente.quantiteNbLots <= achat.choix.nombreLots) { - ChatUtility.removeChatMessageId(achat.chatMessageIdVente); - } - else { - vente["properties"] = itemVendu.getProprietes(); - vente.quantiteNbLots -= achat.choix.nombreLots; - vente.jsondata = JSON.stringify(vente.item); - const messageVente = game.messages.get(achat.chatMessageIdVente); - messageVente.update({ content: await renderTemplate('systems/foundryvtt-reve-de-dragon/templates/chat-vente-item.html', vente) }); - messageVente.render(true); - } - } - } - - async decrementerVente(vendeur, itemVendu, quantite, cout) { - if (vendeur) { - await vendeur.ajouterSols(cout); - await vendeur.decrementerQuantiteItem(itemVendu, quantite); - } - } - - verifierQuantite(vendeur, item, quantiteTotal) { - const disponible = vendeur ? (item?.getQuantite() ?? 0) : quantiteTotal; - return disponible >= quantiteTotal; - } - - async consommerNourritureAchetee(achat, vente, createdItemId) { - if (achat.choix.consommer && vente.item.type == 'nourritureboisson' && createdItemId != undefined) { - achat.choix.doses = achat.choix.nombreLots; - await this.consommerNourritureboisson(createdItemId, achat.choix, vente.actingUserId); - } - } - - async decrementerQuantiteItem(item, quantite) { - let resteQuantite = (item.system.quantite ?? 1) - quantite; - if (resteQuantite <= 0) { - await this.deleteEmbeddedDocuments("Item", [item.id]); - if (resteQuantite < 0) { - ui.notifications.warn(`La quantité de ${item.name} était insuffisante, l'objet a donc été supprimé`) - } - } - else if (resteQuantite > 0) { - await this.updateEmbeddedDocuments("Item", [{ _id: item.id, 'system.quantite': resteQuantite }]); - } - } - - async creerQuantiteItem(item, quantite) { - const items = await this.createEmbeddedDocuments("Item", RdDActor.$prepareListeAchat(item, quantite)); - return items.length > 0 ? items[0].id : undefined; - } - - static $prepareListeAchat(item, quantite) { - const isItemEmpilable = "quantite" in item.system; - const achatData = { - type: item.type, - img: item.img, - name: item.name, - system: mergeObject(item.system, { quantite: isItemEmpilable ? quantite : undefined }), - }; - return isItemEmpilable ? [achatData] : Array.from({ length: quantite }, (_, i) => achatData); - } - /* -------------------------------------------- */ async effectuerTacheAlchimie(recetteId, tacheAlchimie, texteTache) { let recetteData = this.findItemLike(recetteId, 'recettealchimique'); diff --git a/module/actor/base-actor-sheet.js b/module/actor/base-actor-sheet.js index 7ffa9259..328e2b72 100644 --- a/module/actor/base-actor-sheet.js +++ b/module/actor/base-actor-sheet.js @@ -40,6 +40,7 @@ export class RdDBaseActorSheet extends ActorSheet { isLimited: userRightLevel >= CONST.DOCUMENT_OWNERSHIP_LEVELS.LIMITED, isObserver: userRightLevel >= CONST.DOCUMENT_OWNERSHIP_LEVELS.OBSERVER, isOwner: userRightLevel >= CONST.DOCUMENT_OWNERSHIP_LEVELS.OWNER, + owner: this.actor.isOwner, }); let formData = { @@ -49,13 +50,15 @@ export class RdDBaseActorSheet extends ActorSheet { img: this.actor.img, name: this.actor.name, system: foundry.utils.deepClone(this.actor.system), + description: await TextEditor.enrichHTML(this.actor.system.description, { async: true }), + notesmj: await TextEditor.enrichHTML(this.actor.system.notesmj, { async: true }), options: options, } this.filterItemsPerTypeForSheet(formData, this.actor.itemTypes); formData.calc = { fortune: this.toSolsDeniers(this.actor.getFortune()), prixTotalEquipement: this.actor.computePrixTotalEquipement(), - encTotal: await this.actor.computeEncombrementTotalEtMalusArmure(), + encTotal: await this.actor.computeEncTotal(), } this.objetVersConteneur = RdDUtility.buildArbreDeConteneurs(formData.conteneurs, formData.objets); @@ -138,22 +141,22 @@ export class RdDBaseActorSheet extends ActorSheet { this.html = html; this.html.find('.conteneur-name a').click(async event => { - RdDUtility.toggleAfficheContenu(RdDSheetUtility.getItemId(event)); + RdDUtility.toggleAfficheContenu(this.getItemId(event)); this.render(true); }); - this.html.find('.item-edit').click(async event => RdDSheetUtility.getItem(event, this.actor)?.sheet.render(true)) - this.html.find('.item-montrer').click(async event => RdDSheetUtility.getItem(event, this.actor)?.postItemToChat()); + this.html.find('.item-edit').click(async event => this.getItem(event)?.sheet.render(true)) + this.html.find('.item-montrer').click(async event => this.getItem(event)?.postItemToChat()); this.html.find('.actor-montrer').click(async event => this.actor.postActorToChat()); // Everything below here is only needed if the sheet is editable if (!this.options.editable) return; this.html.find('.item-split').click(async event => { - const item = RdDSheetUtility.getItem(event, this.actor); + const item = this.getItem(event); RdDSheetUtility.splitItem(item, this.actor); }); - this.html.find('.item-delete').click(async event => RdDUtility.confirmActorItemDelete(this, RdDSheetUtility.getItem(event, this.actor))); - this.html.find('.item-vendre').click(async event => RdDSheetUtility.getItem(event, this.actor)?.proposerVente(this.getQuantiteMax(item))); + this.html.find('.item-delete').click(async event => RdDUtility.confirmActorItemDelete(this, this.getItem(event))); + this.html.find('.item-vendre').click(async event => this.vendre(this.getItem(event))); this.html.find('.creer-un-objet').click(async event => { this.selectObjetTypeToCreate(); @@ -162,16 +165,19 @@ export class RdDBaseActorSheet extends ActorSheet { this.actor.nettoyerConteneurs(); }); this.html.find('.monnaie-plus').click(async event => { - this.actor.monnaieIncDec(RdDSheetUtility.getItemId(event), 1); + this.actor.monnaieIncDec(this.getItemId(event), 1); }); this.html.find('.monnaie-moins').click(async event => { - this.actor.monnaieIncDec(RdDSheetUtility.getItemId(event), -1); + this.actor.monnaieIncDec(this.getItemId(event), -1); }); - } - getQuantiteMax(item) { - return item.system.quantite; + getItemId(event) { + return RdDSheetUtility.getItemId(event); + } + + getItem(event) { + return RdDSheetUtility.getItem(event, this.actor); } /* -------------------------------------------- */ @@ -197,7 +203,6 @@ export class RdDBaseActorSheet extends ActorSheet { } } - /* -------------------------------------------- */ async selectObjetTypeToCreate() { let typeObjets = RdDItem.getItemTypesInventaire(); @@ -256,4 +261,8 @@ export class RdDBaseActorSheet extends ActorSheet { } } + vendre(item) { + item?.proposerVente(this.actor.getQuantiteDisponible(item)); + } + } diff --git a/module/actor/base-actor.js b/module/actor/base-actor.js index b95a7ff5..409cd793 100644 --- a/module/actor/base-actor.js +++ b/module/actor/base-actor.js @@ -1,6 +1,9 @@ +import { ChatUtility } from "../chat-utility.js"; import { SYSTEM_SOCKET_ID } from "../constants.js"; import { Monnaie } from "../item-monnaie.js"; +import { RdDItem } from "../item.js"; import { Misc } from "../misc.js"; +import { RdDAudio } from "../rdd-audio.js"; import { RdDUtility } from "../rdd-utility.js"; import { SystemCompendiums } from "../settings/system-compendiums.js"; @@ -73,14 +76,14 @@ export class RdDBaseActor extends Actor { if (actorData.items) { return await super.create(actorData, options); } - + actorData.items = []; if (actorData.type == "personnage") { const competences = await SystemCompendiums.getCompetences(actorData.type); - actorData.items = competences.map(i => i.toObject()) + actorData.items = actorData.items.concat(competences.map(i => i.toObject())) .concat(Monnaie.monnaiesStandard()); } - else { - actorData.items = []; + else if (actorData.type == "commerce") { + actorData.items = actorData.items.concat(Monnaie.monnaiesStandard()); } return super.create(actorData, options); } @@ -120,9 +123,11 @@ export class RdDBaseActor extends Actor { ?? Misc.findFirstLike(idOrName, this.listItems(type), { description: Misc.typeName('Item', type) }); } + getMonnaie(id) { return this.findItemLike(id, 'monnaie'); } recompute() { } + /* -------------------------------------------- */ async onPreUpdateItem(item, change, options, id) { } @@ -132,11 +137,218 @@ export class RdDBaseActor extends Actor { async onUpdateActor(update, options, actorId) { } - + /* -------------------------------------------- */ getFortune() { return Monnaie.getFortune(this.itemTypes['monnaie']); } + /* -------------------------------------------- */ + async monnaieIncDec(id, value) { + let monnaie = this.getMonnaie(id); + if (monnaie) { + const quantite = Math.max(0, monnaie.system.quantite + value); + await this.updateEmbeddedDocuments('Item', [{ _id: monnaie.id, '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); + let msg = ""; + if (fortune >= depense) { + await Monnaie.optimiserFortune(this, fortune - depense); + msg = `Vous avez payé ${depense} Sols, 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 !"; + } + + let message = { + whisper: ChatUtility.getWhisperRecipientsAndGMs(this.name), + content: msg + }; + ChatMessage.create(message); + } + + 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 && !game.user.isGM) { + RdDBaseActor.remoteActorCall({ + userId: Misc.connectedGMOrUser(), + 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.getWhisperRecipientsAndGMs(this.name), + content: `Vous avez reçu ${sols} Sols ${fromActor ? " de " + fromActor.name : ''}, qui ont été ajoutés à votre argent.` + }); + } + } + + /* -------------------------------------------- */ + + getQuantiteDisponible(item) { + return item?.getQuantite(); + } + + /* -------------------------------------------- */ + async achatVente(achat) { + if (achat.vendeurId == achat.acheteurId) { + ui.notifications.info("Inutile de se vendre à soi-même"); + return; + } + if (!Misc.isUniqueConnectedGM()) { + 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 vente = achat.vente; + const quantite = (achat.choix.nombreLots ?? 1) * (vente.tailleLot); + const itemVendu = vendeur?.getItem(vente.item._id); + if (!this.verifierQuantite(vendeur, 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 this.decrementerVente(vendeur, itemVendu, quantite, cout); + if (acheteur) { + await acheteur.depenserSols(cout); + let createdItemId = await acheteur.creerQuantiteItem(vente.item, quantite); + await acheteur.consommerNourritureAchetee(achat, vente, createdItemId); + } + if (cout > 0) { + RdDAudio.PlayContextAudio("argent"); + } + const chatAchatItem = duplicate(vente); + chatAchatItem.quantiteTotal = quantite; + ChatMessage.create({ + user: achat.userId, + speaker: { alias: (acheteur ?? vendeur).name }, + whisper: ChatUtility.getWhisperRecipientsAndGMs(this.name), + content: await renderTemplate('systems/foundryvtt-reve-de-dragon/templates/chat-achat-item.html', chatAchatItem) + }); + + if (!vente.quantiteIllimite) { + if (vente.quantiteNbLots <= achat.choix.nombreLots) { + ChatUtility.removeChatMessageId(achat.chatMessageIdVente); + } + else if (achat.chatMessageIdVente) { + vente["properties"] = itemVendu.getProprietes(); + vente.quantiteNbLots -= achat.choix.nombreLots; + vente.jsondata = JSON.stringify(vente.item); + const messageVente = game.messages.get(achat.chatMessageIdVente); + messageVente.update({ content: await renderTemplate('systems/foundryvtt-reve-de-dragon/templates/chat-vente-item.html', vente) }); + messageVente.render(true); + } + } + } + + async decrementerVente(vendeur, itemVendu, quantite, cout) { + if (vendeur) { + await vendeur.ajouterSols(cout); + await vendeur.decrementerQuantiteItem(itemVendu, quantite); + } + } + + verifierFortune(cout) { + return this.getFortune() >= cout; + } + + verifierQuantite(vendeur, item, quantiteTotal) { + const disponible = vendeur?.getQuantiteDisponible(item); + return disponible == undefined || disponible >= quantiteTotal; + } + + async consommerNourritureAchetee(achat, vente, createdItemId) { + if (achat.choix.consommer && vente.item.type == 'nourritureboisson' && createdItemId != undefined) { + achat.choix.doses = achat.choix.nombreLots; + await this.consommerNourritureboisson(createdItemId, achat.choix, vente.actingUserId); + } + } + + + async decrementerQuantiteItem(item, quantite, options={supprimerSiZero: true}) { + 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: item.id, '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) { + await this.updateEmbeddedDocuments("Item", [{ _id: item.id, 'system.quantite': resteQuantite }]); + } + } + + async creerQuantiteItem(item, quantite) { + const isItemEmpilable = "quantite" in item.system; + const baseItem = { + type: item.type, + img: item.img, + name: item.name, + system: mergeObject(item.system, { quantite: isItemEmpilable ? quantite : undefined }) + }; + 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.map(it => it.getEncTotal()).reduce(Misc.sum(), 0); + return this.encTotal; + } + return 0; + } + async createItem(type, name = undefined) { if (!name) { name = 'Nouveau ' + Misc.typeName('Item', type); @@ -148,6 +360,237 @@ export class RdDBaseActor extends Actor { return false; } + async processDropItem(params) { + const targetActorId = this.id; + const sourceActorId = params.sourceActorId; + const itemId = params.itemId; + const destId = params.destId; + const srcId = params.srcId; + if (sourceActorId && sourceActorId != targetActorId) { + console.log("Moving objects", sourceActorId, targetActorId, itemId); + this.moveItemsBetweenActors(itemId, sourceActorId); + return false; + } + let result = true; + const item = this.getItem(itemId); + if (item?.isInventaire() && 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}
${item.name} a été déplacé dans: ${cible.name}` + : `${message}
${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, item) { + if (!dest) { + return true; + } + if (!dest.isConteneur()) { + return false; + } + const destData = dest + if (this._isConteneurContenu(item, dest)) { + ui.notifications.warn(`Impossible de déplacer un conteneur parent (${item.name}) dans un de ses contenus ${destData.name} !`); + return false; // Loop detected ! + } + + // Calculer le total actuel des contenus + let encContenu = this.getRecursiveEnc(dest) - Number(destData.system.encombrement); + let newEnc = this.getRecursiveEnc(item); // Calculer le total actuel du nouvel objet + + // Teste si le conteneur de destination a suffisament de capacité pour recevoir le nouvel objet + if (Number(destData.system.capacite) < encContenu + newEnc) { + ui.notifications.warn( + `Le conteneur ${dest.name} a une capacité de ${destData.system.capacite}, et contient déjà ${encContenu}. + Impossible d'y ranger: ${item.name} d'encombrement ${newEnc}!`); + return false; + } + return true; + } + + /* -------------------------------------------- */ + _isConteneurContenu(item, conteneur) { + if (item?.isConteneur()) { // Si c'est un conteneur, il faut vérifier qu'on ne le déplace pas vers un sous-conteneur lui appartenant + for (let id of item.system.contenu) { + let subObjet = this.getItem(id); + if (subObjet?.id == conteneur.id) { + return true; // Loop detected ! + } + if (subObjet?.isConteneur()) { + return this._isConteneurContenu(subObjet, conteneur); + } + } + } + return false; + } + /* -------------------------------------------- */ + getRecursiveEnc(objet) { + if (!objet) { + return 0; + } + const tplData = objet.system; + if (objet.type != 'conteneur') { + return Number(tplData.encombrement) * Number(tplData.quantite); + } + const encContenus = tplData.contenu.map(idContenu => this.getRecursiveEnc(this.getItem(idContenu))); + return encContenus.reduce(Misc.sum(), 0) + + Number(tplData.encombrement) /* TODO? Number(tplData.quantite) -- on pourrait avoir plusieurs conteneurs...*/ + } + + /* -------------------------------------------- */ + /** Ajoute un item dans un conteneur, sur la base + * de leurs ID */ + async ajouterDansConteneur(item, conteneur, onAjouterDansConteneur) { + if (!conteneur) { + // TODO: afficher + item.estContenu = false; + } + else if (conteneur.isConteneur()) { + item.estContenu = true; + await this.updateEmbeddedDocuments('Item', [{ + _id: conteneur.id, + 'system.contenu': [...conteneur.system.contenu, item.id] + }]); + onAjouterDansConteneur(item.id, conteneur.id); + } + } + + /* -------------------------------------------- */ + /** Fonction de remise à plat de l'équipement (ie vide les champs 'contenu') */ + async nettoyerConteneurs() { + RdDConfirm.confirmer({ + settingConfirmer: "confirmation-vider", + content: `

Etes vous certain de vouloir vider tous les conteneurs ?

`, + title: 'Vider les conteneurs', + buttonLabel: 'Vider', + onAction: async () => { + const corrections = []; + for (let item of this.items) { + if (item.estContenu) { + item.estContenu = undefined; + } + if (item.type == 'conteneur' && item.system.contenu.length > 0) { + corrections.push({ _id: item.id, 'system.contenu': [] }); + } + } + 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?.isConteneur()) { + item.estContenu = false; + await this.updateEmbeddedDocuments('Item', [{ + _id: conteneur.id, + 'system.contenu': conteneur.system.contenu.filter(id => id != item.id) + }]); + onEnleverDeConteneur(); + } + } + + /* -------------------------------------------- */ + async moveItemsBetweenActors(itemId, sourceActorId) { + let itemsList = [] + let sourceActor = game.actors.get(sourceActorId); + itemsList.push({ id: itemId, conteneurId: undefined }); // Init list + sourceActor.buildSubConteneurObjetList(itemId, itemsList); // Get itemId list + + const itemsDataToCreate = itemsList.map(it => sourceActor.getItem(it.id)) + .map(it => 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 + let newConteneurId = itemMap[item.conteneurId]; // Get conteneur + let newConteneur = this.getItem(newConteneurId); + + let newItemId = itemMap[item.id]; // Get newItem + + console.log('New conteneur filling!', newConteneur, newItemId, item); + let contenu = duplicate(newConteneur.system.contenu); + contenu.push(newItemId); + await this.updateEmbeddedDocuments('Item', [{ _id: newConteneurId, 'system.contenu': contenu }]); + } + } + for (let item of itemsList) { + await sourceActor.deleteEmbeddedDocuments('Item', [item.id]); + } + } + + _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 = { diff --git a/module/rdd-main.js b/module/rdd-main.js index 7d68790f..13be7ddf 100644 --- a/module/rdd-main.js +++ b/module/rdd-main.js @@ -101,7 +101,7 @@ export class SystemReveDeDragon { /* -------------------------------------------- */ // Define custom Entity classes - CONFIG.Actor.documentClass = RdDActor; + CONFIG.Actor.documentClass = RdDBaseActor; CONFIG.Item.documentClass = RdDItem; CONFIG.RDD = { resolutionTable: RdDResolutionTable.resolutionTable, diff --git a/module/rdd-namegen.js b/module/rdd-namegen.js index f054f435..b27ea5bc 100644 --- a/module/rdd-namegen.js +++ b/module/rdd-namegen.js @@ -1,4 +1,4 @@ -import { RdDActor } from "./actor.js"; +import { RdDBaseActor } from "./actor/base-actor.js"; import { Misc } from "./misc.js"; import { RdDDice } from "./rdd-dice.js"; @@ -20,7 +20,7 @@ export class RdDNameGen { static async onCreerActeur(event) { const button = event.currentTarget; - await RdDActor.create({ + await RdDBaseActor.create({ name: button.attributes['data-nom'].value, type: button.attributes['data-type'].value }, diff --git a/templates/actor-sheet.html b/templates/actor-sheet.html index 3a824616..772f0e72 100644 --- a/templates/actor-sheet.html +++ b/templates/actor-sheet.html @@ -189,7 +189,7 @@

Biographie :

- {{editor biographie target="system.biographie" button=true owner=owner editable=true engine="prosemirror"}} + {{editor biographie target="system.biographie" button=true owner=options.owner editable=true engine="prosemirror"}}

Notes :