From 7c70e944b1122c7a4c96af774dbe9bbe55544828 Mon Sep 17 00:00:00 2001 From: Vincent Vandemeulebrouck Date: Fri, 23 Dec 2022 00:34:17 +0100 Subject: [PATCH] Ajout des "boutiques" MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Une boutique est un Item service permettant de définir l'inventaire en vente, et de le vendre facilement. Les boutiques peuvent être accédées par les joueurs (avec le lien) pour y faire leurs courses. --- icons/items/services.webp | Bin 0 -> 6368 bytes lang/fr.json | 1 + module/actor.js | 54 +++++++----- module/dialog-item-achat.js | 92 +++++++++++++------- module/dialog-item-vente.js | 31 +++---- module/item-service-sheet.js | 81 +++++++++++++++++ module/item-service.js | 140 ++++++++++++++++++++++++++++++ module/item.js | 54 +++++++----- module/rdd-main.js | 4 + module/rdd-utility.js | 8 +- styles/simple.css | 4 +- template.json | 7 ++ templates/dialog-item-achat.html | 17 ++-- templates/item-service-sheet.html | 103 ++++++++++++++++++++++ templates/post-item-service.html | 8 ++ 15 files changed, 506 insertions(+), 98 deletions(-) create mode 100644 icons/items/services.webp create mode 100644 module/item-service-sheet.js create mode 100644 module/item-service.js create mode 100644 templates/item-service-sheet.html create mode 100644 templates/post-item-service.html diff --git a/icons/items/services.webp b/icons/items/services.webp new file mode 100644 index 0000000000000000000000000000000000000000..a55077d669d87469266ea648f806631a5b57a860 GIT binary patch literal 6368 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 literal 0 HcmV?d00001 diff --git a/lang/fr.json b/lang/fr.json index 3cc1bf86..36819273 100644 --- a/lang/fr.json +++ b/lang/fr.json @@ -27,6 +27,7 @@ "TypeArmure": "Armure", "TypeConteneur": "Conteneur", "TypeNourritureboisson": "Nourriture & boisson", + "TypeService": "Services/Boutique", "TypeChant": "Chant", "TypeDanse": "Danse", "TypeMusique": "Musique", diff --git a/module/actor.js b/module/actor.js index 00a2df1c..40d77e64 100644 --- a/module/actor.js +++ b/module/actor.js @@ -188,17 +188,17 @@ export class RdDActor extends Actor { canReceive(item) { if (this.isCreature()) { - return item.type == 'competencecreature' || RdDItem.isItemInventaire(item); + return item.type == 'competencecreature' || item.isInventaire(); } if (this.isEntite()) { return item.type == 'competencecreature'; } if (this.isVehicule()) { - return RdDItem.isItemInventaire(item); + return item.isInventaire(); } if (this.isPersonnage()) { switch (item.type) { - case 'competencecreature': case 'tarot': + case 'competencecreature': case 'tarot': case 'service': return false; } return true; @@ -461,7 +461,7 @@ export class RdDActor extends Actor { selectedCaracName: 'apparence', competences: this.itemTypes['competence'] }; - + const dialog = await RdDRoll.create(this, rollData, { html: 'systems/foundryvtt-reve-de-dragon/templates/dialog-roll.html' }, { @@ -3770,35 +3770,32 @@ export class RdDActor extends Actor { } const cout = Number(achat.prixTotal ?? 0); - const vente = achat.vente; - const acheteur = achat.acheteurId ? game.actors.get(achat.acheteurId) : undefined; const vendeur = achat.vendeurId ? game.actors.get(achat.vendeurId) : undefined; - let itemVendu = vendeur?.getObjet(vente.item._id); - - if (vendeur && (itemVendu?.getQuantite() ?? 0) < achat.quantiteTotal) { - ChatUtility.notifyUser(achat.userId, 'warn', `Le vendeur n'a plus assez de ${vente.item.name} !`); - return; + const service = achat.serviceId ? (vendeur?.getObjet(achat.serviceId) ?? game.items.get(achat.serviceId)) : 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?.getObjet(vente.item._id) ?? (await RdDItem.getCorrespondingItem(vente.item)); + if (!this.verifierQuantite(service, vendeur, itemVendu, quantite)) { + ChatUtility.notifyUser(achat.userId, 'warn', `Le vendeur n'a pas assez de ${itemVendu.name} !`); + return } + if (Monnaie.getFortune(acheteur) < Number(cout)) { ChatUtility.notifyUser(achat.userId, 'warn', `Vous n'avez pas assez d'argent pour payer ${Math.ceil(cout / 100)} sols !`); return; } - - achat.quantiteTotal = (achat.choix.nombreLots ?? 1) * (vente.tailleLot); - if (vendeur) { - await vendeur.ajouterSols(cout); - await vendeur.decrementerQuantiteItem(itemVendu, achat.quantiteTotal,); - } + await this.decrementerVente(service, vendeur, itemVendu, quantite, cout); if (acheteur) { await acheteur.depenserSols(cout); - let createdItemId = await acheteur.creerQuantiteItem(vente.item, achat.quantiteTotal); + 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 = achat.quantiteTotal; + chatAchatItem.quantiteTotal = quantite; ChatMessage.create({ user: achat.userId, speaker: { alias: (acheteur ?? vendeur).name }, @@ -3810,8 +3807,8 @@ export class RdDActor extends Actor { if (vente.quantiteNbLots <= achat.choix.nombreLots) { ChatUtility.removeChatMessageId(achat.chatMessageIdVente); } - else { - vente["properties"] = new RdDItem(vente.item).getProprietes(); + else if (!service) { + vente["properties"] = itemVendu.getProprietes(); vente.quantiteNbLots -= achat.choix.nombreLots; vente.jsondata = JSON.stringify(vente.item); const messageVente = game.messages.get(achat.chatMessageIdVente); @@ -3821,6 +3818,21 @@ export class RdDActor extends Actor { } } + async decrementerVente(service, vendeur, itemVendu, quantite, cout) { + if (service) { + await service.venteRefItem(itemVendu, quantite, cout) + } + else if (vendeur) { + await vendeur.ajouterSols(cout); + await vendeur.decrementerQuantiteItem(itemVendu, quantite); + } + } + + verifierQuantite(service, vendeur, item, quantiteTotal) { + const disponible = service ? service.getQuantiteDisponible(item, quantiteTotal) : (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; diff --git a/module/dialog-item-achat.js b/module/dialog-item-achat.js index 947e18e7..5509b178 100644 --- a/module/dialog-item-achat.js +++ b/module/dialog-item-achat.js @@ -1,12 +1,13 @@ +import { Misc } from "./misc.js"; import { RdDUtility } from "./rdd-utility.js"; export class DialogItemAchat extends Dialog { - static venteData(button) { - const vendeurId = button.attributes['data-vendeurId']?.value; + static preparerAchat(chatButton) { + const vendeurId = chatButton.attributes['data-vendeurId']?.value; const vendeur = vendeurId ? game.actors.get(vendeurId) : undefined; const acheteur = RdDUtility.getSelectedActor(); - const json = button.attributes['data-jsondata']?.value; + const json = chatButton.attributes['data-jsondata']?.value; if (!acheteur && !vendeur) { ui.notifications.info("Pas d'acheteur ni de vendeur, aucun changement"); return undefined; @@ -16,46 +17,68 @@ export class DialogItemAchat extends Dialog { return undefined; } - const prixLot = Number(button.attributes['data-prixLot']?.value ?? 0); return { - item: json ? JSON.parse(json) : undefined, - actingUserId: game.user.id, - vendeurId: vendeurId, - vendeur: vendeur, - acheteur: acheteur, - tailleLot: parseInt(button.attributes['data-tailleLot']?.value ?? 1), - quantiteIllimite: button.attributes['data-quantiteIllimite']?.value == 'true', - quantiteNbLots: parseInt(button.attributes['data-quantiteNbLots']?.value), - choix: { - nombreLots: 1, - seForcer: false, - supprimerSiZero: true - }, - prixLot: prixLot, - prixTotal: prixLot, - isVente: prixLot > 0, - chatMessageIdVente: RdDUtility.findChatMessageId(button) + item: (json ? JSON.parse(json) : undefined), + vendeur, + acheteur, + nbLots: parseInt(chatButton.attributes['data-quantiteNbLots']?.value), + tailleLot: parseInt(chatButton.attributes['data-tailleLot']?.value ?? 1), + prixLot: Number(chatButton.attributes['data-prixLot']?.value ?? 0), + quantiteIllimite: chatButton.attributes['data-quantiteIllimite']?.value == 'true', + chatMessageIdVente: RdDUtility.findChatMessageId(chatButton), }; } - static async onAcheter(venteData) { + + static async onAcheter({ item, vendeur, acheteur, service, tailleLot, prixLot, nbLots, quantiteIllimite, chatMessageIdVente }) { + const venteData = { + item, + actingUserId: game.user.id, + vendeurId: vendeur?.id, + vendeur, + acheteur, + service, + tailleLot, + quantiteIllimite, + quantiteNbLots: nbLots, + choix: { seForcer: false, supprimerSiZero: true }, + prixLot, + isVente: prixLot > 0, + isConsommable: item.type == 'nourritureboisson' && acheteur?.isPersonnage(), + chatMessageIdVente + }; + + DialogItemAchat.changeNombreLots(venteData, 1); const html = await renderTemplate(`systems/foundryvtt-reve-de-dragon/templates/dialog-item-achat.html`, venteData); new DialogItemAchat(html, venteData).render(true); } + static changeNombreLots(venteData, nombreLots) { + venteData.choix.nombreLots = nombreLots; + venteData.prixTotal = (nombreLots * venteData.prixLot).toFixed(2); + if (venteData.isConsommable) { + const doses = nombreLots * venteData.tailleLot; + venteData.totalSust = Misc.keepDecimals(doses * (venteData.item.system.sust ?? 0), 2); + venteData.totalDesaltere = venteData.item.system.boisson + ? Misc.keepDecimals(doses * (venteData.item.system.desaltere ?? 0), 2) + : 0; + } + } + constructor(html, venteData) { - const isConsommable = venteData.item.type == 'nourritureboisson' && venteData.acheteur?.isPersonnage(); let options = { classes: ["dialogachat"], width: 400, height: 'fit-content', 'z-index': 99999 }; const actionAchat = venteData.prixLot > 0 ? "Acheter" : "Prendre"; const buttons = {}; - if (isConsommable) { + if (venteData.isConsommable) { buttons["consommer"] = { label: venteData.item.system.boisson ? "Boire" : "Manger", callback: it => this.onAchatConsommer() } } buttons[actionAchat] = { label: actionAchat, callback: it => { this.onAchat(); } }; buttons["decliner"] = { label: "Décliner", callback: it => { } }; + const acheteur = venteData.acheteur?.name ?? 'Un acheteur'; + const vendeur = (venteData.service ?? venteData.vendeur)?.name ?? 'Un vendeur'; let conf = { - title: venteData.acheteur ? venteData.acheteur.name + " - " + actionAchat : actionAchat, + title: `${acheteur} - ${actionAchat} à ${vendeur}`, content: html, default: actionAchat, buttons: buttons @@ -69,6 +92,7 @@ export class DialogItemAchat extends Dialog { await this.html.find(".nombreLots").change(); (this.venteData.vendeur ?? this.venteData.acheteur).achatVente({ userId: game.user.id, + serviceId: this.venteData.service?.id, vendeurId: this.venteData.vendeur?.id, acheteurId: this.venteData.acheteur?.id, prixTotal: this.venteData.prixTotal, @@ -96,13 +120,21 @@ export class DialogItemAchat extends Dialog { } setNombreLots(nombreLots) { - if (nombreLots > this.venteData.quantiteNbLots) { - ui.notifications.warn(`Seulement ${this.venteData.quantiteNbLots} lots disponibles, vous ne pouvez pas en prendre ${nombreLots}`) + + if (!this.venteData.quantiteIllimite) { + if (!this.venteData.quantiteIllimite && nombreLots > this.venteData.quantiteNbLots) { + ui.notifications.warn(`Seulement ${this.venteData.quantiteNbLots} lots disponibles, vous ne pouvez pas en prendre ${nombreLots}`) + } + nombreLots = Math.min(nombreLots, this.venteData.quantiteNbLots); } - this.venteData.choix.nombreLots = Math.min(nombreLots, this.venteData.quantiteNbLots); - this.venteData.prixTotal = (nombreLots * this.venteData.prixLot).toFixed(2); - this.html.find(".nombreLots").val(this.venteData.choix.nombreLots); + + DialogItemAchat.changeNombreLots(this.venteData, nombreLots); + + this.html.find(".nombreLots").val(nombreLots); this.html.find(".prixTotal").text(this.venteData.prixTotal); + this.html.find("span.total-sust").text(this.venteData.totalSust); + this.html.find("span.total-desaltere").text(this.venteData.totalDesaltere); } + } \ No newline at end of file diff --git a/module/dialog-item-vente.js b/module/dialog-item-vente.js index 2b09b60b..4441456f 100644 --- a/module/dialog-item-vente.js +++ b/module/dialog-item-vente.js @@ -2,21 +2,24 @@ import { HtmlUtility } from "./html-utility.js"; export class DialogItemVente extends Dialog { - static async display(item, callback) { - const quantite = item.isConteneur() ? 1 : item.system.quantite; + static async display({ item, callback, service = undefined, quantiteMax = undefined }) { + const quantite = quantiteMax ?? item.getQuantite(); + const isOwned = item.isOwned; + // const isOwned = item.isOwned || service?.actor; const venteData = { item: item, - alias: item.actor?.name ?? game.user.name, - vendeurId: item.actor?.id, + alias: item.actor?.name ?? service?.name ?? game.user.name, + serviceId: service?.id, + vendeurId: item.actor?.id ?? service?.actor?.id, prixOrigine: item.system.cout, prixUnitaire: item.system.cout, prixLot: item.system.cout, tailleLot: 1, quantiteNbLots: quantite, quantiteMaxLots: quantite, - quantiteMax: quantite , - quantiteIllimite: !item.isOwned, - isOwned: item.isOwned, + quantiteMax: quantite, + quantiteIllimite: service? service.system.illimite : !isOwned, + isOwned: isOwned, }; const html = await renderTemplate(`systems/foundryvtt-reve-de-dragon/templates/dialog-item-vente.html`, venteData); return new DialogItemVente(venteData, html, callback).render(true); @@ -54,7 +57,7 @@ export class DialogItemVente extends Dialog { await this.html.find(".quantiteIllimite").change(); await this.html.find(".prixLot").change(); this.callback(this.venteData); - } + } /* -------------------------------------------- */ setPrixLot(prixLot) { @@ -68,13 +71,11 @@ export class DialogItemVente extends Dialog { this.html.find(".prixLot").val(this.venteData.prixLot); } this.venteData.tailleLot = tailleLot; - if (this.venteData.isOwned) { - // recalculer le nombre de lots max - this.venteData.quantiteMaxLots = Math.floor(this.venteData.quantiteMax / tailleLot); - this.venteData.quantiteNbLots = Math.min(this.venteData.quantiteMaxLots, this.venteData.quantiteNbLots); - this.html.find(".quantiteNbLots").val(this.venteData.quantiteNbLots); - this.html.find(".quantiteNbLots").attr("max", this.venteData.quantiteMaxLots) - } + // recalculer le nombre de lots max + this.venteData.quantiteMaxLots = Math.floor(this.venteData.quantiteMax / tailleLot); + this.venteData.quantiteNbLots = Math.min(this.venteData.quantiteMaxLots, this.venteData.quantiteNbLots); + this.html.find(".quantiteNbLots").val(this.venteData.quantiteNbLots); + this.html.find(".quantiteNbLots").attr("max", this.venteData.quantiteMaxLots) } setNbLots(nbLots) { diff --git a/module/item-service-sheet.js b/module/item-service-sheet.js new file mode 100644 index 00000000..97488a57 --- /dev/null +++ b/module/item-service-sheet.js @@ -0,0 +1,81 @@ +import { RdDItemSheet } from "./item-sheet.js"; +import { Misc } from "./misc.js"; +import { RdDUtility } from "./rdd-utility.js"; +import { SystemCompendiums } from "./settings/system-compendiums.js"; +import { DialogItemAchat } from "./dialog-item-achat.js"; +import { RdDItem } from "./item.js"; +import { RdDItemService } from "./item-service.js"; + +export class RdDServiceItemSheet extends RdDItemSheet { + + static get ITEM_TYPE() { return "service" }; + + async getData() { + const formData = await super.getData(); + formData.disabled = formData.isGM || formData.isOwned ? '' : 'disabled'; + return formData; + } + + activateListeners(html) { + super.activateListeners(html); + + this.html.find('a.rdd-world-content-link').click(async event => { + const itemRef = this.getItemRef(event); + game.items.get(itemRef.id)?.sheet.render(true) + }); + + this.html.find('a.sub-item-acheter').click(async event => { + const subItem = this.item.findRefItem(this.getItemRef(event)); + await this.item.acheter(RdDUtility.getSelectedActor(), subItem); + }); + + if (!this.options.editable) return; + + this.html.find('a.sub-item-vendre').click(async event => { + const subItem = this.item.findRefItem(this.getItemRef(event)); + await this.item.vendre(subItem); + }); + + this.html.find('a.sub-item-delete').click(async event => { + await this.item.removeRefItem(this.getItemRef(event)); + }); + + this.html.find('a.sub-item-quantite-moins').click(async event => await this.item.increaseRefItemQuantite(this.getItemRef(event), -1)) + this.html.find('a.sub-item-quantite-plus').click(async event => await this.item.increaseRefItemQuantite(this.getItemRef(event), 1)) + this.html.find('input.sub-item-quantite').change(async event => { + const newQuantite = Math.max(0, Number.parseInt(this.html.find(event.currentTarget).val())); + await this.item.updateRefItem(this.getItemRef(event), it => it.system.quantite = newQuantite); + }) + this.html.find('input.sub-item-cout').change(async event => { + const newCout = Math.max(0, Number(this.html.find(event.currentTarget).val())); + await this.item.updateRefItem(this.getItemRef(event), it => it.system.cout = newCout); + }) + this.html.find('a.sub-item-info-add').click(__ => + ui.notifications.info(`Utiliser le glisser-déposer pour ajouter des objets depuis un compendium ou les objets du monde`) + ); + + } + + async _onDropItem(event, dragData) { + let linkedItem = fromUuidSync(dragData.uuid); + const existing = this.item.system.items.find(it => it.pack == linkedItem.pack && it.id == linkedItem.id && it.type == linkedItem.type); + if (existing) { + ui.notifications.warn(`${this.item.name} contient déjà un ${existing.name}`); + return; + } + if (linkedItem.pack) { + linkedItem = await SystemCompendiums.loadDocument(linkedItem); + } + if (linkedItem.isInventaire()) { + await this.item.addRefItem(RdDServiceItemSheet.createSubItem(linkedItem)); + } + else { + ui.notifications.warn(`${this.item.name} ne peut pas proposer à la vente de ${Misc.typeName('Item', linkedItem.type)}: ${linkedItem.name}`); + } + } + + getItemRef(event) { + const itemRow = this.html.find(event.currentTarget)?.parents('.item.service-item'); + return { id: itemRow?.data("item-id"), pack: itemRow?.data("pack") ?? undefined } + } +} diff --git a/module/item-service.js b/module/item-service.js new file mode 100644 index 00000000..17fc6ab5 --- /dev/null +++ b/module/item-service.js @@ -0,0 +1,140 @@ +import { DialogItemAchat } from "./dialog-item-achat.js"; +import { RdDItem } from "./item.js"; +import { Misc } from "./misc.js"; + +export class RdDItemService extends RdDItem { + + static get defaultIcon() { + return "systems/foundryvtt-reve-de-dragon/icons/items/services.webp"; + } + + /** @override*/ + getUserLevel(user) { + const level = super.getUserLevel(user); + if (level == CONST.DOCUMENT_OWNERSHIP_LEVELS.NONE) { + // si quelqu'un a accès au lien d'un service, il peut le voir + return CONST.DOCUMENT_OWNERSHIP_LEVELS.LIMITED; + } + return level; + } + + isService() { return true; } + getChatItemTemplate() { return 'systems/foundryvtt-reve-de-dragon/templates/post-item-service.html'; } + getProprietes() { return []; } + + getServiceItem(itemRef) { + if (this.isService()) { + return this.system.items.find(it => it.id == itemRef.id && it.pack == itemRef.pack); + } + return undefined; + } + + getQuantiteDisponible(itemRef, max) { + if (this.system.illimite) { + return max; + } + const subItem = this.getServiceItem(itemRef); + return subItem?.system.quantite ?? 0; + } + + async venteRefItem(ref, quantite, cout) { + if (this.actor) { + await this.actor.ajouterSols(cout); + } + await this.increaseRefItemQuantite(ref, -quantite); + } + + async vendre(subItem) { + const item = await RdDItem.getCorrespondingItem(subItem); + const quantiteMax = this.system.illimite ? undefined : subItem.system.quantite; + await item.proposerVente({ service: this, quantiteMax }); + } + + async acheter(acheteur, subItem) { + if (!acheteur) { + ui.notifications.warn(`Pas d'acheteur sélectionné`); + return; + } + const nbLots = this.system.illimite ? 1 : subItem.system.quantite; + if (nbLots <= 0) { + ui.notifications.warn(`${this.name} n'a plus de ${subItem.name} en vente`); + return; + } + + await DialogItemAchat.onAcheter({ + item: await RdDItem.getCorrespondingItem(subItem), + acheteur, + service: this, + quantiteIllimite: this.system.illimite, + nbLots, + tailleLot: 1, + prixLot: subItem.system.cout + }); + } + + static createSubItem(linkedItem) { + return { + id: linkedItem.id, + pack: linkedItem.pack, + name: linkedItem.name, + img: linkedItem.img, + system: { + quantite: 1, + cout: linkedItem.system.cout ?? 0 + } + }; + } + + static matchRefItem({ id, pack }) { + return it => it.id == id && (pack ? (it.pack == pack) : (!it.pack)); + } + + findRefItem(ref) { + return this.system.items.find(RdDItemService.matchRefItem(ref)); + } + + async increaseRefItemQuantite(ref, quantite) { + await this.updateRefItem(ref, + it => it.system.quantite = Math.max(0, it.system.quantite + quantite) + ); + } + + async updateRefItem(ref, update = it => { }) { + await this.updateRefItems(RdDItemService.matchRefItem(ref), update); + } + + async addRefItem(newItem) { + if (!newItem.id) { + ui.notifications.warn(`${newItem?.name ?? '??'} n'a pas d'identifiant`); + return; + } + if (this.system.items.find(RdDItemService.matchRefItem(newItem))) { + ui.notifications.warn(`${newItem?.name ?? newItem.id} est déjà présent ici`); + return; + } + await this.setRefItems([...this.system.items, newItem]); + } + + async removeRefItem(ref) { + await this.removeRefItems(RdDItemService.matchRefItem(ref)); + } + + async removeRefItems(matcher = it => false) { + await this.setRefItems(this.system.items.filter(it => !matcher(it))); + } + + async updateRefItems(matcher = it => false, update = it => { }) { + const updatedList = this.system.items.map(it => { + if (matcher(it)) { + update(it); + } + return it; + }); + await this.setRefItems(updatedList); + } + + async setRefItems(newItems) { + await this.update({ 'system.items': newItems.sort(Misc.ascending(it => it.type + ':' + it.name)) }); + } + +} \ No newline at end of file diff --git a/module/item.js b/module/item.js index 7bfcb36c..6a2b5fed 100644 --- a/module/item.js +++ b/module/item.js @@ -58,6 +58,7 @@ export const defaultItemImg = { poison: "systems/foundryvtt-reve-de-dragon/icons/maladies_venins/venin.webp", oeuvre: "systems/foundryvtt-reve-de-dragon/icons/competence_comedie.webp", nourritureboisson: "systems/foundryvtt-reve-de-dragon/icons/objets/provision_crue.webp", + service: "systems/foundryvtt-reve-de-dragon/icons/items/services.webp", signedraconique: "systems/foundryvtt-reve-de-dragon/icons/tmr/signe_draconique.webp", gemme: "systems/foundryvtt-reve-de-dragon/icons/gemmes/almaze.webp", possession: "systems/foundryvtt-reve-de-dragon/icons/entites/possession2.webp", @@ -73,10 +74,6 @@ export class RdDItem extends Item { return game.system.rdd.itemClasses[itemType]?.defaultIcon ?? defaultItemImg[itemType]; } - static isItemInventaire(newLocal) { - return typesObjetsInventaire.includes(newLocal.type); - } - static isFieldInventaireModifiable(type, field) { switch (field) { case 'quantite': @@ -154,7 +151,7 @@ export class RdDItem extends Item { return typesObjetsCompetence.includes(this.type) } isInventaire() { - return RdDItem.isItemInventaire(this) + return typesObjetsInventaire.includes(this.type); } isOeuvre() { return typesObjetsOeuvres.includes(this.type) @@ -222,7 +219,7 @@ export class RdDItem extends Item { } getQuantite() { - return Math.round(this.isConteneur() ? 1 : (this.system.quantite ?? 0)) + return Math.round(this.system.quantite ?? 0) } getEncTotal() { @@ -297,7 +294,7 @@ export class RdDItem extends Item { } return undefined; } - + /* -------------------------------------------- */ async actionPrincipale(actor, onActionItem = async () => { }) { if (!this.getActionPrincipale()) { @@ -423,31 +420,38 @@ export class RdDItem extends Item { return [true, undefined]; } - async proposerVente() { + async proposerVente({ service = undefined, quantiteMax = undefined }) { console.log(this); if (this.isConteneurNonVide()) { ui.notifications.warn(`Votre ${this.name} n'est pas vide, pas possible de le proposer`); return; } - await DialogItemVente.display(this, async (vente) => { - vente["properties"] = this.getProprietes(); - if (vente.isOwned) { - if (vente.quantiteNbLots * vente.tailleLot > vente.quantiteMax) { - ui.notifications.warn(`Vous avez ${vente.quantiteMax} ${vente.item.name}, ce n'est pas suffisant pour vendre ${vente.quantiteNbLots} de ${vente.tailleLot}`) - return; + await DialogItemVente.display({ + item: this, + service, + quantiteMax, + callback: async (vente) => { + vente["properties"] = this.getProprietes(); + if (vente.isOwned) { + if (vente.quantiteNbLots * vente.tailleLot > vente.quantiteMax) { + ui.notifications.warn(`Vous avez ${vente.quantiteMax} ${vente.item.name}, ce n'est pas suffisant pour vendre ${vente.quantiteNbLots} de ${vente.tailleLot}`) + return; + } } - } - vente.jsondata = JSON.stringify(vente.item); + vente.jsondata = JSON.stringify(vente.item); - console.log(vente); - let html = await renderTemplate('systems/foundryvtt-reve-de-dragon/templates/chat-vente-item.html', vente); - ChatMessage.create(RdDUtility.chatDataSetup(html)); + let html = await renderTemplate('systems/foundryvtt-reve-de-dragon/templates/chat-vente-item.html', vente); + ChatMessage.create(RdDUtility.chatDataSetup(html)); + } }); } /* -------------------------------------------- */ getProprietes() { - return this[`_${this.type}ChatData`]().filter(it => it != undefined); + if (this[`_${this.type}ChatData`]) { + return this[`_${this.type}ChatData`]().filter(it => it != undefined); + } + return []; } /* -------------------------------------------- */ @@ -465,12 +469,19 @@ export class RdDItem extends Item { payload: chatData, }); - renderTemplate('systems/foundryvtt-reve-de-dragon/templates/post-item.html', chatData).then(html => { + renderTemplate(this.getChatItemTemplate(), chatData).then(html => { let chatOptions = RdDUtility.chatDataSetup(html, modeOverride); ChatMessage.create(chatOptions) }); } + getChatItemTemplate() { + switch (this.type) { + case 'service': return 'systems/foundryvtt-reve-de-dragon/templates/post-item-service.html'; + } + return 'systems/foundryvtt-reve-de-dragon/templates/post-item.html'; + } + static propertyIfDefined(name, val, condition = true) { return condition ? `${name}: ${val}` : undefined; } @@ -719,5 +730,4 @@ export class RdDItem extends Item { ...this._inventaireTemplateChatData() ] } - } diff --git a/module/rdd-main.js b/module/rdd-main.js index 4e192d99..e3d456d9 100644 --- a/module/rdd-main.js +++ b/module/rdd-main.js @@ -34,6 +34,8 @@ import { Environnement } from "./environnement.js"; import { RdDIngredientItemSheet } from "./item-ingredient-sheet.js"; import { RdDFauneItemSheet } from "./item-faune-sheet.js"; import { RdDConteneurItemSheet } from "./item-conteneur-sheet.js"; +import { RdDServiceItemSheet } from "./item-service-sheet.js"; +import { RdDItemService } from "./item-service.js"; /** * RdD system @@ -52,6 +54,7 @@ export class SystemReveDeDragon { this.RdDUtility = RdDUtility; this.RdDHotbar = RdDHotbar; this.itemClasses = { + service: RdDItemService } this.actorClasses = { } @@ -190,6 +193,7 @@ export class SystemReveDeDragon { RdDItemSheet.register(RdDHerbeItemSheet); RdDItemSheet.register(RdDFauneItemSheet); RdDItemSheet.register(RdDIngredientItemSheet); + RdDItemSheet.register(RdDServiceItemSheet); Items.registerSheet(SYSTEM_RDD, RdDItemSheet, { types: [ diff --git a/module/rdd-utility.js b/module/rdd-utility.js index ec3fdc30..33908d61 100644 --- a/module/rdd-utility.js +++ b/module/rdd-utility.js @@ -203,6 +203,7 @@ export class RdDUtility { 'systems/foundryvtt-reve-de-dragon/templates/item-signedraconique-sheet.html', 'systems/foundryvtt-reve-de-dragon/templates/item-possession-sheet.html', 'systems/foundryvtt-reve-de-dragon/templates/item-extraitpoetique-sheet.html', + 'systems/foundryvtt-reve-de-dragon/templates/item-service-sheet.html', // partial enums 'systems/foundryvtt-reve-de-dragon/templates/enum-caracteristiques.html', 'systems/foundryvtt-reve-de-dragon/templates/enum-base-competence.html', @@ -431,6 +432,7 @@ export class RdDUtility { formData.oeuvres = this.arrayOrEmpty(itemTypes['oeuvre']); formData.jeux = this.arrayOrEmpty(itemTypes['jeu']); + formData.services = this.arrayOrEmpty(itemTypes['service']); formData.recettescuisine = this.arrayOrEmpty(itemTypes['recettecuisine']); formData.recettesAlchimiques = this.arrayOrEmpty(itemTypes['recettealchimique']); formData.maladies = this.arrayOrEmpty(itemTypes['maladie']); @@ -814,7 +816,7 @@ export class RdDUtility { // gestion bouton tchat Acheter html.on("click", '.button-acheter', event => { - const venteData = DialogItemAchat.venteData(event.currentTarget); + const venteData = DialogItemAchat.preparerAchat(event.currentTarget); if (venteData) { DialogItemAchat.onAcheter(venteData); } @@ -830,6 +832,10 @@ export class RdDUtility { ChatUtility.removeChatMessageId(RdDUtility.findChatMessageId(event.currentTarget)); } }); + html.on("click", '.rdd-world-content-link', async event => { + const itemId = html.find(event.currentTarget)?.data("id"); + game.items.get(itemId)?.sheet.render(true) + }); } static findChatMessageId(current) { diff --git a/styles/simple.css b/styles/simple.css index b7efbd0b..e7488487 100644 --- a/styles/simple.css +++ b/styles/simple.css @@ -599,7 +599,7 @@ input:is(.blessure-premiers_soins, .blessure-soins_complets) { .editor { border: 2; height: fit-content; - min-height: 8rem; + min-height: 5rem; padding: 0 3px; } @@ -611,7 +611,7 @@ input:is(.blessure-premiers_soins, .blessure-soins_complets) { .small-editor { border: 2; - min-height: 4rem; + min-height: 2rem; padding: 0 3px; } diff --git a/template.json b/template.json index 7b86582a..63f2374d 100644 --- a/template.json +++ b/template.json @@ -562,6 +562,7 @@ "recettealchimique", "musique", "chant", "danse", "jeu", "recettecuisine", "oeuvre", "objet", "arme", "armure", "conteneur", "herbe", "ingredient", "faune", "livre", "potion", "munition", "monnaie", "nourritureboisson", "gemme", + "service", "meditation", "rencontre", "queue", "ombre", "souffle", "tete", "casetmr", "signedraconique", "sort", "sortreserve", "nombreastral", "tache", "maladie", "poison", "possession", "tarot", "extraitpoetique" @@ -741,6 +742,12 @@ "prpermanent": false, "prdate": 0 }, + "service": { + "templates": [ "description"], + "illimite": false, + "items": [], + "services": [] + }, "musique": { "templates": [ "description" ], "niveau": "", diff --git a/templates/dialog-item-achat.html b/templates/dialog-item-achat.html index 50f0d0e2..6bdff205 100644 --- a/templates/dialog-item-achat.html +++ b/templates/dialog-item-achat.html @@ -2,15 +2,17 @@
- {{#if vendeur}} + {{#if service}} + {{service.name}} + {{else if vendeur}} {{vendeur.name}} {{else}} Vendeur MJ {{/if}}
-
+
{{item.name}}
-
+
{{#if acheteur}} {{acheteur.name}} @@ -40,8 +42,10 @@ {{else}}Quantité{{/if}}
- +
@@ -50,8 +54,7 @@ {{#if item.system.sust}}

Cette {{#if item.system.boisson}}boisson{{else}}nourriture{{/if}} vous apportera - {{totalSust}} - de sustantation.

+ {{totalSust}} de sustantation.

{{/if}} {{#if item.system.boisson}}

diff --git a/templates/item-service-sheet.html b/templates/item-service-sheet.html new file mode 100644 index 00000000..534fdd76 --- /dev/null +++ b/templates/item-service-sheet.html @@ -0,0 +1,103 @@ +

+ {{>"systems/foundryvtt-reve-de-dragon/templates/header-item.html"}} + +
+
+ {{editor description target="system.description" button=true owner=owner editable=(or isGM isOwner) engine="prosemirror"}} +
+ {{!-- +
+
    +
  • + + + + + +
  • + {{#each system.services as |service key|}} +
  • + + + + +
    + + {{#unless @root.disabled}} + + + {{/unless}} +
    +
  • + {{/each}} +
+
+
+ --}} +
+ + Quantité en vente illimitée +
+
+
    +
  • + + {{#unless system.illimite}} + + {{/unless}} + +
    + {{#unless disabled}} + + + + {{/unless}} +
    +
  • + {{#each system.items as |item key|}} +
  • + + {{#unless @root.system.illimite}} + + {{#unless @root.disabled}} + + {{/unless}} + + {{#unless @root.disabled}} + + {{/unless}} + + {{/unless}} + + + +
    + + {{#unless @root.disabled}} + + + {{/unless}} +
    +
  • + {{/each}} +
+
+ {{#if isGM}} +
+
+ +
+ {{editor descriptionmj target="system.descriptionmj" button=true owner=owner editable=true engine="prosemirror"}} +
+
+ {{/if}} +
+
diff --git a/templates/post-item-service.html b/templates/post-item-service.html new file mode 100644 index 00000000..4073de14 --- /dev/null +++ b/templates/post-item-service.html @@ -0,0 +1,8 @@ +
+ {{#if img}} + + {{/if}} +

{{> 'systems/foundryvtt-reve-de-dragon/templates/common/compendium-link.hbs' pack=pack id=_id name=name}}

+

{{{system.description}}}

+ +