diff --git a/icons/items/services.webp b/icons/items/services.webp new file mode 100644 index 00000000..a55077d6 Binary files /dev/null and b/icons/items/services.webp differ 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-sheet.js b/module/actor-sheet.js index a8ef1ab5..0108d369 100644 --- a/module/actor-sheet.js +++ b/module/actor-sheet.js @@ -57,7 +57,7 @@ export class RdDActorSheet extends ActorSheet { notes: await TextEditor.enrichHTML(this.object.system.notes, { async: true }), notesmj: await TextEditor.enrichHTML(this.object.system.notesmj, { async: true }), calc: { - fortune: Monnaie.getFortune(this.actor), + fortune: Monnaie.getFortuneSolsDeniers(this.actor), encTotal: await this.actor.computeEncombrementTotalEtMalusArmure(), surenc: this.actor.computeMalusSurEncombrement(), prixTotalEquipement: this.actor.computePrixTotalEquipement(), @@ -137,7 +137,7 @@ export class RdDActorSheet extends ActorSheet { RdDSheetUtility.splitItem(item, this.actor); }); this.html.find('.item-edit').click(async event => RdDSheetUtility.getItem(event, this.actor)?.sheet.render(true)) - this.html.find('.item-delete').click(async event => RdDUtility.confirmerSuppressionItem(this, RdDSheetUtility.getItem(event, 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.html.find('.item-montrer').click(async event => RdDSheetUtility.getItem(event, this.actor)?.postItem()); this.html.find('.item-action').click(async event => RdDSheetUtility.getItem(event, this.actor)?.actionPrincipale(this.actor)); diff --git a/module/actor.js b/module/actor.js index 4dd54eed..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' }, { @@ -1406,6 +1406,7 @@ export class RdDActor extends Actor { /* -------------------------------------------- */ computePrixTotalEquipement() { const valeur = this.items.filter(it => it.isInventaire()) + .filter(it => !it.isMonnaie()) .map(it => it.valeurTotale()) .reduce(Misc.sum(), 0); return valeur; @@ -3769,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 }, @@ -3809,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); @@ -3820,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-monnaie.js b/module/item-monnaie.js index be030cbd..d766c0af 100644 --- a/module/item-monnaie.js +++ b/module/item-monnaie.js @@ -72,6 +72,14 @@ export class Monnaie { return 0; } + static getFortuneSolsDeniers(actor) { + const fortune = Monnaie.getFortune(actor); + return { + sols: Math.floor(fortune), + deniers: Math.round(100 * (fortune - Math.floor(fortune))) + }; + } + static async optimiserFortune(actor, fortune) { let resteEnDeniers = Math.round(fortune * 100); let monnaies = actor.itemTypes['monnaie']; 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-sheet.js b/module/item-sheet.js index 90ad5eb7..27c1ebb8 100644 --- a/module/item-sheet.js +++ b/module/item-sheet.js @@ -209,7 +209,7 @@ export class RdDItemSheet extends ItemSheet { this.html.find('.item-split').click(async event => RdDSheetUtility.splitItem(RdDSheetUtility.getItem(event, this.actor), this.actor, async () => this.render(true))); this.html.find('.item-edit').click(async event => RdDSheetUtility.getItem(event, this.actor)?.sheet.render(true)); - this.html.find('.item-delete').click(async event => RdDUtility.confirmerSuppressionItem(this, RdDSheetUtility.getItem(event, 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.html.find('.item-montrer').click(async event => RdDSheetUtility.getItem(event, this.actor)?.postItem()); this.html.find('.item-action').click(async event => RdDSheetUtility.getItem(event, this.actor)?.actionPrincipale(this.actor, async () => this.render(true))); diff --git a/module/item.js b/module/item.js index 175bfaa8..6a2b5fed 100644 --- a/module/item.js +++ b/module/item.js @@ -3,6 +3,7 @@ import { Grammar } from "./grammar.js"; import { Misc } from "./misc.js"; import { RdDHerbes } from "./rdd-herbes.js"; import { RdDUtility } from "./rdd-utility.js"; +import { SystemCompendiums } from "./settings/system-compendiums.js"; const typesObjetsInventaire = [ "arme", @@ -57,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", @@ -69,11 +71,7 @@ export const defaultItemImg = { export class RdDItem extends Item { static getDefaultImg(itemType) { - return defaultItemImg[itemType]; - } - - static isItemInventaire(newLocal) { - return typesObjetsInventaire.includes(newLocal.type); + return game.system.rdd.itemClasses[itemType]?.defaultIcon ?? defaultItemImg[itemType]; } static isFieldInventaireModifiable(type, field) { @@ -92,6 +90,42 @@ export class RdDItem extends Item { return true; } + static async getCorrespondingItem(itemRef) { + if (itemRef.pack) { + return await SystemCompendiums.loadDocument(itemRef) + } + return game.items.get(itemRef.id ?? itemRef._id); + } + + static getItemTypesInventaire() { + return typesObjetsInventaire + } + + static getTypesOeuvres() { + return typesObjetsOeuvres + } + + constructor(docData, context = {}) { + if (!context.rdd?.ready) { + mergeObject(context, { rdd: { ready: true } }); + const ItemConstructor = game.system.rdd.itemClasses[docData.type]; + if (ItemConstructor) { + if (!docData.img) { + docData.img = ItemConstructor.defaultIcon; + } + return new ItemConstructor(docData, context); + } + } + if (!docData.img) { + docData.img = RdDItem.getDefaultImg(docData.type); + } + super(docData, context); + } + + static get defaultIcon() { + return undefined; + } + getUniteQuantite() { switch (this.type) { case "monnaie": return "(Pièces)" @@ -107,32 +141,17 @@ export class RdDItem extends Item { return ''; } - constructor(itemData, context) { - if (!itemData.img) { - itemData.img = RdDItem.getDefaultImg(itemData.type); - } - super(itemData, context); - } - - static getItemTypesInventaire() { - return typesObjetsInventaire - } - - static getTypesOeuvres() { - return typesObjetsOeuvres - } - - isCompetencePersonnage() { - return this.type == 'competence' - } - isCompetenceCreature() { - return this.type == 'competencecreature' - } + isCompetencePersonnage() { return this.type == 'competence' } + isCompetenceCreature() { return this.type == 'competencecreature' } + isConteneur() { return this.type == 'conteneur'; } + isMonnaie() { return this.type == 'monnaie'; } + isNourritureBoisson() { return this.type == 'nourritureboisson'; } + isService() { return this.type == 'service'; } isCompetence() { return typesObjetsCompetence.includes(this.type) } isInventaire() { - return RdDItem.isItemInventaire(this) + return typesObjetsInventaire.includes(this.type); } isOeuvre() { return typesObjetsOeuvres.includes(this.type) @@ -146,12 +165,7 @@ export class RdDItem extends Item { isConnaissance() { return typesObjetsConnaissance.includes(this.type) } - isConteneur() { - return this.type == 'conteneur'; - } - isMonnaie() { - return this.type == 'monnaie'; - } + getItemGroup() { if (this.isInventaire()) return "equipement"; @@ -175,10 +189,6 @@ export class RdDItem extends Item { return !this.isConteneur() || (this.system.contenu?.length ?? 0) == 0; } - isNourritureBoisson() { - return this.type == 'nourritureboisson'; - } - isComestible() { switch (this.type) { case 'nourritureboisson': return 'pret'; @@ -209,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() { @@ -284,7 +294,7 @@ export class RdDItem extends Item { } return undefined; } - + /* -------------------------------------------- */ async actionPrincipale(actor, onActionItem = async () => { }) { if (!this.getActionPrincipale()) { @@ -410,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 []; } /* -------------------------------------------- */ @@ -452,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; } @@ -706,5 +730,4 @@ export class RdDItem extends Item { ...this._inventaireTemplateChatData() ] } - } diff --git a/module/migrations.js b/module/migrations.js index 5f2e8d36..f2765567 100644 --- a/module/migrations.js +++ b/module/migrations.js @@ -29,6 +29,39 @@ class Migration { } +class _1_5_34_migrationPngWebp { + get code() { return "migrationPngWebp"; } + get version() { return "1.5.34"; } + async migrate() { + + const regexOldPngJpg = /(systems\/foundryvtt-reve-de-dragon\/icons\/.*)\.(png|jpg)/; + const replaceWithWebp = '$1.webp'; + function convertImgToWebp(img) { + return img.replace(regexOldPngJpg, replaceWithWebp); + } + function prepareDocumentsImgUpdate(documents) { + return documents.filter(it => it.img && it.img.match(regexOldPngJpg)) + .map(it => { + return { _id: it.id, img: convertImgToWebp(it.img) } + }); + } + + const itemsUpdates = prepareDocumentsImgUpdate(game.items); + const actorsUpdates = prepareDocumentsImgUpdate(game.actors); + //Migrate system png to webp + await Item.updateDocuments(itemsUpdates); + await Actor.updateDocuments(actorsUpdates); + game.actors.forEach(actor => { + if (actor.token?.img && actor.token.img.match(regexOldPngJpg)) { + actor.update({ "token.img": convertImgToWebp(actor.token.img) }); + } + const actorItemsToUpdate = prepareDocumentsImgUpdate(actor.items); + actor.updateEmbeddedDocuments('Item', actorItemsToUpdate); + }); + } +} + + class _10_0_16_MigrationSortsReserve extends Migration { get code() { return "creation-item-sort-reserve"; } get version() { return "10.0.16"; } @@ -296,6 +329,7 @@ class _10_3_17_Monnaies extends Migration { export class Migrations { static getMigrations() { return [ + new _1_5_34_migrationPngWebp(), new _10_0_16_MigrationSortsReserve(), new _10_0_17_MigrationCompetenceCreature(), new _10_0_21_VehiculeStructureResistanceMax(), diff --git a/module/rdd-calendrier-editeur.js b/module/rdd-calendrier-editeur.js index 936dee45..d097a1d3 100644 --- a/module/rdd-calendrier-editeur.js +++ b/module/rdd-calendrier-editeur.js @@ -29,7 +29,7 @@ export class RdDCalendrierEditeur extends Dialog { this.html.find("input[name='nomMois']").val(this.calendrierData.moisKey); this.html.find("select[name='nomHeure']").val(this.calendrierData.heureKey); this.html.find("select[name='jourMois']").val(this.calendrierData.jourMois); - this.html.find("select[name='minutesRelative']").val(calendrierData.minutesRelative); + this.html.find("select[name='minutesRelative']").val(this.calendrierData.minutesRelative); this.html.find("select[name='annee']").val(this.calendrierData.annee); } diff --git a/module/rdd-combat.js b/module/rdd-combat.js index 9dd406f2..b9186d1d 100644 --- a/module/rdd-combat.js +++ b/module/rdd-combat.js @@ -162,7 +162,6 @@ export class RdDCombatManager extends Combat { if (arme.system.unemain && arme.system.deuxmains && !dommages.includes("/")) { ui.notifications.info("Les dommages de l'arme à 1/2 mains " + arme.name + " ne sont pas corrects (ie sous la forme X/Y)"); } - console.log(">>>>", arme) if ((arme.system.unemain && arme.system.competence) || (arme.system.competence.toLowerCase().includes("corps à corps"))) { actions.push(RdDCombatManager.$prepareAttaqueArme({ diff --git a/module/rdd-hotbar-drop.js b/module/rdd-hotbar-drop.js index 4bef344f..e2963f64 100644 --- a/module/rdd-hotbar-drop.js +++ b/module/rdd-hotbar-drop.js @@ -1,4 +1,3 @@ -import { Misc } from "./misc.js"; export class RdDHotbar { diff --git a/module/rdd-main.js b/module/rdd-main.js index 912d6209..e3d456d9 100644 --- a/module/rdd-main.js +++ b/module/rdd-main.js @@ -1,13 +1,3 @@ -/** - * RdD system - * Author: LeRatierBretonnien - * Software License: GNU GPLv3 - */ - -/* -------------------------------------------- */ - -/* -------------------------------------------- */ -// Import Modules import { SYSTEM_RDD, SYSTEM_SOCKET_ID } from "./constants.js"; import { RdDActor } from "./actor.js"; import { RdDItemSheet } from "./item-sheet.js"; @@ -44,302 +34,281 @@ 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"; -/* -------------------------------------------- */ -/* Foundry VTT Initialization */ -/* -------------------------------------------- */ +/** + * RdD system + * Author: LeRatierBretonnien + * Software License: GNU GPLv3 + */ +export class SystemReveDeDragon { - - -/************************************************************************************/ -Hooks.once("init", async function () { - console.log(`Initializing Reve de Dragon System`); - - // preload handlebars templates - RdDUtility.preloadHandlebarsTemplates(); - // Create useful storage space - game.system.rdd = { - TMRUtility, - RdDUtility, - RdDHotbar, - RdDPossession, + static start() { + const system = new SystemReveDeDragon(); + Hooks.once('init', async () => await system.onInit()); + Hooks.once('diceSoNiceReady', (dice3d) => RdDDice.diceSoNiceReady(dice3d)); } - /* -------------------------------------------- */ - game.settings.register(SYSTEM_RDD, "accorder-entite-cauchemar", { - name: "Accorder le rêve aux entités", - hint: "A quel moment les personnages doivent accorder leur rêve aux entités de cauchemar", - scope: "world", - config: true, - type: String, - choices: { // If choices are defined, the resulting setting will be a select menu - "avant-attaque": "Avant l'attaque", - "avant-defense": "Avant la défense", - "avant-encaissement": "Avant l'encaissement", - }, - default: "avant-encaissement" - }); - - /* -------------------------------------------- */ - game.settings.register(SYSTEM_RDD, "calendrier", { - name: "calendrier", - scope: "world", - config: false, - default: RdDCalendrier.createCalendrierInitial(), - type: Object - }); - - /* -------------------------------------------- */ - game.settings.register(SYSTEM_RDD, "migration-png-webp-1.5.34", { - name: "calendrier", - scope: "world", - config: false, - default: false, - type: Boolean - }); - - /* -------------------------------------------- */ - game.settings.register(SYSTEM_RDD, "liste-nombre-astral", { - name: "liste-nombre-astral", - scope: "world", - config: false, - default: [], - type: Object - }); - - /* -------------------------------------------- */ - game.settings.register(SYSTEM_RDD, "calendrier-pos", { - name: "calendrierPos", - scope: "client", - config: false, - default: RdDCalendrier.createCalendrierPos(), - type: Object - }); - - - /* -------------------------------------------- */ - game.settings.register(SYSTEM_RDD, "supprimer-dialogues-combat-chat", { - name: "Supprimer les dialogues de combat", - hint: "Si désactivée, tous les dialogues de combat sont conservés dans la conversation", - scope: "world", - config: true, - default: true, - type: Boolean - }); - /* -------------------------------------------- */ - game.settings.register(SYSTEM_RDD, "activer-sons-audio", { - name: "Activer les bruitages intégrés", - hint: "Si activé, certaines actions en jeu déclenchent un son d'ambiance", - scope: "world", - config: true, - default: true, - type: Boolean - }); - /* -------------------------------------------- */ - game.settings.register(SYSTEM_RDD, "appliquer-famine-soif", { - name: "Notifier de la famine et la soif pour", - hint: "Indique si les cas de famine et de soif seront indiqués durant Château Dormant", - scope: "world", - config: true, - type: String, - choices: { - "aucun": "ni la famine, ni la soif", - "famine": "seulement la famine", - "famine-soif": "la famine et la soif", - }, - default: "aucun" - }); - - /* -------------------------------------------- */ - // Set an initiative formula for the system - CONFIG.Combat.initiative = { - formula: "1+(1d6/10)", - decimals: 2 - }; - - /* -------------------------------------------- */ - game.socket.on(SYSTEM_SOCKET_ID, async (sockmsg) => { - console.log(">>>>> MSG RECV", sockmsg); - try { - RdDUtility.onSocketMessage(sockmsg); - RdDCombat.onSocketMessage(sockmsg); - ChatUtility.onSocketMessage(sockmsg); - RdDActor.onSocketMessage(sockmsg); - } catch (e) { - console.error('game.socket.on(SYSTEM_SOCKET_ID) Exception: ', sockmsg, ' => ', e) + constructor() { + this.RdDUtility = RdDUtility; + this.RdDHotbar = RdDHotbar; + this.itemClasses = { + service: RdDItemService + } + this.actorClasses = { } - }); - - /* -------------------------------------------- */ - // Define custom Entity classes - CONFIG.Actor.documentClass = RdDActor; - CONFIG.Item.documentClass = RdDItem; - CONFIG.RDD = { - resolutionTable: RdDResolutionTable.resolutionTable, - carac_array: RdDUtility.getCaracArray(), - ajustementsConditions: RdDUtility.getAjustementsConditions(), - difficultesLibres: RdDUtility.getDifficultesLibres() } /* -------------------------------------------- */ - // Register sheet application classes - Actors.unregisterSheet("core", ActorSheet); - Actors.registerSheet(SYSTEM_RDD, RdDActorSheet, { types: ["personnage"], makeDefault: true }); - Actors.registerSheet(SYSTEM_RDD, RdDActorCreatureSheet, { types: ["creature"], makeDefault: true }); - Actors.registerSheet(SYSTEM_RDD, RdDActorVehiculeSheet, { types: ["vehicule"], makeDefault: true }); - Actors.registerSheet(SYSTEM_RDD, RdDActorEntiteSheet, { types: ["entite"], makeDefault: true }); - Items.unregisterSheet("core", ItemSheet); + /* Foundry VTT Initialization */ + /* -------------------------------------------- */ + async onInit() { + game.system.rdd = this; - RdDItemSheet.register(RdDSigneDraconiqueItemSheet); - RdDItemSheet.register(RdDRencontreItemSheet); - RdDItemSheet.register(RdDConteneurItemSheet); - RdDItemSheet.register(RdDHerbeItemSheet); - RdDItemSheet.register(RdDFauneItemSheet); - RdDItemSheet.register(RdDIngredientItemSheet); + console.log(`Initializing Reve de Dragon System`); - Items.registerSheet(SYSTEM_RDD, RdDItemSheet, { - types: [ - "competence", "competencecreature", - "recettealchimique", "musique", "chant", "danse", "jeu", "recettecuisine", "oeuvre", - "objet", "arme", "armure", "livre", "potion", "munition", - "monnaie", "nourritureboisson", "gemme", - "meditation", "queue", "ombre", "souffle", "tete", "casetmr", "sort", "sortreserve", - "nombreastral", "tache", "maladie", "poison", "possession", - "tarot", "extraitpoetique" - ], makeDefault: true - }); - CONFIG.Combat.documentClass = RdDCombatManager; + // preload handlebars templates + RdDUtility.preloadHandlebarsTemplates(); - // préparation des différents modules - SystemCompendiums.init(); - DialogChronologie.init(); - ReglesOptionelles.init(); - RdDUtility.init(); - RdDDice.init(); - RdDCommands.init(); - RdDCombat.init(); - RdDCombatManager.init(); - RdDTokenHud.init(); - RdDActor.init(); - RddCompendiumOrganiser.init(); - EffetsDraconiques.init() - TMRUtility.init(); - RdDHotbar.initDropbar(); - RdDPossession.init(); - TMRRencontres.init(); - Environnement.init(); -}); + /* -------------------------------------------- */ + game.settings.register(SYSTEM_RDD, "accorder-entite-cauchemar", { + name: "Accorder le rêve aux entités", + hint: "A quel moment les personnages doivent accorder leur rêve aux entités de cauchemar", + scope: "world", + config: true, + type: String, + choices: { // If choices are defined, the resulting setting will be a select menu + "avant-attaque": "Avant l'attaque", + "avant-defense": "Avant la défense", + "avant-encaissement": "Avant l'encaissement", + }, + default: "avant-encaissement" + }); -/* -------------------------------------------- */ -function messageDeBienvenue() { - if (game.user.isGM) { - ChatUtility.removeChatMessageContaining('
'); - ChatMessage.create({ - user: game.user.id, - content: `
Bienvenue dans le Rêve des Dragons ! + /* -------------------------------------------- */ + game.settings.register(SYSTEM_RDD, "calendrier", { + name: "calendrier", + scope: "world", + config: false, + default: RdDCalendrier.createCalendrierInitial(), + type: Object + }); + + /* -------------------------------------------- */ + game.settings.register(SYSTEM_RDD, "liste-nombre-astral", { + name: "liste-nombre-astral", + scope: "world", + config: false, + default: [], + type: Object + }); + + /* -------------------------------------------- */ + game.settings.register(SYSTEM_RDD, "calendrier-pos", { + name: "calendrierPos", + scope: "client", + config: false, + default: RdDCalendrier.createCalendrierPos(), + type: Object + }); + + + /* -------------------------------------------- */ + game.settings.register(SYSTEM_RDD, "supprimer-dialogues-combat-chat", { + name: "Supprimer les dialogues de combat", + hint: "Si désactivée, tous les dialogues de combat sont conservés dans la conversation", + scope: "world", + config: true, + default: true, + type: Boolean + }); + /* -------------------------------------------- */ + game.settings.register(SYSTEM_RDD, "activer-sons-audio", { + name: "Activer les bruitages intégrés", + hint: "Si activé, certaines actions en jeu déclenchent un son d'ambiance", + scope: "world", + config: true, + default: true, + type: Boolean + }); + /* -------------------------------------------- */ + game.settings.register(SYSTEM_RDD, "appliquer-famine-soif", { + name: "Notifier de la famine et la soif pour", + hint: "Indique si les cas de famine et de soif seront indiqués durant Château Dormant", + scope: "world", + config: true, + type: String, + choices: { + "aucun": "ni la famine, ni la soif", + "famine": "seulement la famine", + "famine-soif": "la famine et la soif", + }, + default: "aucun" + }); + + /* -------------------------------------------- */ + // Set an initiative formula for the system + CONFIG.Combat.initiative = { + formula: "1+(1d6/10)", + decimals: 2 + }; + + /* -------------------------------------------- */ + game.socket.on(SYSTEM_SOCKET_ID, async (sockmsg) => { + console.log(">>>>> MSG RECV", sockmsg); + try { + RdDUtility.onSocketMessage(sockmsg); + RdDCombat.onSocketMessage(sockmsg); + ChatUtility.onSocketMessage(sockmsg); + RdDActor.onSocketMessage(sockmsg); + } catch (e) { + console.error('game.socket.on(SYSTEM_SOCKET_ID) Exception: ', sockmsg, ' => ', e) + } + }); + + /* -------------------------------------------- */ + // Define custom Entity classes + CONFIG.Actor.documentClass = RdDActor; + CONFIG.Item.documentClass = RdDItem; + CONFIG.RDD = { + resolutionTable: RdDResolutionTable.resolutionTable, + carac_array: RdDUtility.getCaracArray(), + ajustementsConditions: RdDUtility.getAjustementsConditions(), + difficultesLibres: RdDUtility.getDifficultesLibres() + } + + /* -------------------------------------------- */ + // Register sheet application classes + Actors.unregisterSheet("core", ActorSheet); + Actors.registerSheet(SYSTEM_RDD, RdDActorSheet, { types: ["personnage"], makeDefault: true }); + Actors.registerSheet(SYSTEM_RDD, RdDActorCreatureSheet, { types: ["creature"], makeDefault: true }); + Actors.registerSheet(SYSTEM_RDD, RdDActorVehiculeSheet, { types: ["vehicule"], makeDefault: true }); + Actors.registerSheet(SYSTEM_RDD, RdDActorEntiteSheet, { types: ["entite"], makeDefault: true }); + Items.unregisterSheet("core", ItemSheet); + + RdDItemSheet.register(RdDSigneDraconiqueItemSheet); + RdDItemSheet.register(RdDRencontreItemSheet); + RdDItemSheet.register(RdDConteneurItemSheet); + RdDItemSheet.register(RdDHerbeItemSheet); + RdDItemSheet.register(RdDFauneItemSheet); + RdDItemSheet.register(RdDIngredientItemSheet); + RdDItemSheet.register(RdDServiceItemSheet); + + Items.registerSheet(SYSTEM_RDD, RdDItemSheet, { + types: [ + "competence", "competencecreature", + "recettealchimique", "musique", "chant", "danse", "jeu", "recettecuisine", "oeuvre", + "objet", "arme", "armure", "livre", "potion", "munition", + "monnaie", "nourritureboisson", "gemme", + "meditation", "queue", "ombre", "souffle", "tete", "casetmr", "sort", "sortreserve", + "nombreastral", "tache", "maladie", "poison", "possession", + "tarot", "extraitpoetique" + ], makeDefault: true + }); + CONFIG.Combat.documentClass = RdDCombatManager; + + // préparation des différents modules + SystemCompendiums.init(); + DialogChronologie.init(); + ReglesOptionelles.init(); + RdDUtility.init(); + RdDDice.init(); + RdDCommands.init(); + RdDCombat.init(); + RdDCombatManager.init(); + RdDTokenHud.init(); + RdDActor.init(); + RddCompendiumOrganiser.init(); + EffetsDraconiques.init() + TMRUtility.init(); + RdDHotbar.initDropbar(); + RdDPossession.init(); + TMRRencontres.init(); + Environnement.init(); + + Hooks.once('ready', () => this.onReady()); + } + + async onReady() { + + /* -------------------------------------------- */ + /* Foundry VTT Initialization */ + /* -------------------------------------------- */ + // CSS patch for v9 + if (game.version) { + let sidebar = document.getElementById("sidebar"); + sidebar.style.width = "min-content"; + } + + if (Misc.isUniqueConnectedGM()) { + new Migrations().migrate(); + } + + StatusEffects.onReady(); + RdDHerbes.initializeHerbes(); + RdDDice.onReady(); + /* -------------------------------------------- */ + /* Affiche/Init le calendrier */ + let calendrier = new RdDCalendrier(); + let templatePath = "systems/foundryvtt-reve-de-dragon/templates/calendar-template.html"; + let templateData = {}; + renderTemplate(templatePath, templateData).then(html => { + calendrier.render(true); + }); + game.system.rdd.calendrier = calendrier; // Reference; + + // Avertissement si joueur sans personnage + if (!game.user.isGM && game.user.character == undefined) { + ui.notifications.info("Attention ! Vous n'êtes connecté à aucun personnage !"); + ChatMessage.create({ + content: "ATTENTION Le joueur " + game.user.name + " n'est connecté à aucun personnage !", + user: game.user.id + }); + } + if (Misc.isUniqueConnectedGM()) { + this.messageDeBienvenue(); + this.registerUsageCount(SYSTEM_RDD); + } + } + + /* -------------------------------------------- */ + messageDeBienvenue() { + if (game.user.isGM) { + ChatUtility.removeChatMessageContaining('
'); + ChatMessage.create({ + user: game.user.id, + content: `
Bienvenue dans le Rêve des Dragons !
Vous trouverez quelques informations pour démarrer dans ce document : @Compendium[foundryvtt-reve-de-dragon.rappel-des-regles.7uGrUHGdPu0EmIu2]{Documentation MJ/Joueurs}
La commande /aide dans le chat permet de voir les commandes spécifiques à Rêve de Dragon.
` }); - } -} - -/* -------------------------------------------- */ -// Register world usage statistics -function registerUsageCount(registerKey) { - if (game.user.isGM) { - game.settings.register("world", "world-key", { - name: "Unique world key", - scope: "world", - config: false, - default: "NONE", - type: String - }); - - let worldKey = game.settings.get("world", "world-key") - if (worldKey == undefined || worldKey == "") { - worldKey = randomID(32) - game.settings.set("world", "world-key", worldKey) } - let regURL = `https://www.uberwald.me/fvtt_appcount/count.php?name="${registerKey}"&worldKey="${worldKey}"&version="${game.release.generation}.${game.release.build}"&system="${game.system.id}"&systemversion="${game.system.version}"` - $.ajax(regURL) - /* -------------------------------------------- */ - } -} - -/* -------------------------------------------- */ -/* Foundry VTT Initialization */ -/* -------------------------------------------- */ -Hooks.once("ready", async function () { - await migrationPngWebp_1_5_34() - if (Misc.isUniqueConnectedGM()) { - new Migrations().migrate(); } - StatusEffects.onReady(); - RdDHerbes.initializeHerbes(); - RdDDice.onReady(); /* -------------------------------------------- */ - /* Affiche/Init le calendrier */ - let calendrier = new RdDCalendrier(); - let templatePath = "systems/foundryvtt-reve-de-dragon/templates/calendar-template.html"; - let templateData = {}; - renderTemplate(templatePath, templateData).then(html => { - calendrier.render(true); - }); - game.system.rdd.calendrier = calendrier; // Reference; + // Register world usage statistics + async registerUsageCount(registerKey) { + if (game.user.isGM) { + game.settings.register("world", "world-key", { + name: "Unique world key", + scope: "world", + config: false, + default: "NONE", + type: String + }); - // Avertissement si joueur sans personnage - if (!game.user.isGM && game.user.character == undefined) { - ui.notifications.info("Attention ! Vous n'êtes connecté à aucun personnage !"); - ChatMessage.create({ - content: "ATTENTION Le joueur " + game.user.name + " n'est connecté à aucun personnage !", - user: game.user.id - }); - } - if (Misc.isUniqueConnectedGM()) { - messageDeBienvenue(); - registerUsageCount(SYSTEM_RDD); - } -}); - -async function migrationPngWebp_1_5_34() { - if (!game.settings.get(SYSTEM_RDD, "migration-png-webp-1.5.34")) { - const regexOldPngJpg = /(systems\/foundryvtt-reve-de-dragon\/icons\/.*)\.(png|jpg)/; - const replaceWithWebp = '$1.webp'; - function convertImgToWebp(img) { - return img.replace(regexOldPngJpg, replaceWithWebp); - } - function prepareDocumentsImgUpdate(documents) { - return documents.filter(it => it.img && it.img.match(regexOldPngJpg)) - .map(it => { - return { _id: it.id, img: convertImgToWebp(it.img) } - }); - } - const itemsUpdates = prepareDocumentsImgUpdate(game.items); - const actorsUpdates = prepareDocumentsImgUpdate(game.actors); - //Migrate system png to webp - await Item.updateDocuments(itemsUpdates); - await Actor.updateDocuments(actorsUpdates); - game.actors.forEach(actor => { - if (actor.token?.img && actor.token.img.match(regexOldPngJpg)) { - actor.update({ "token.img": convertImgToWebp(actor.token.img) }); + let worldKey = game.settings.get("world", "world-key") + if (worldKey == undefined || worldKey == "") { + worldKey = randomID(32) + game.settings.set("world", "world-key", worldKey) } - const actorItemsToUpdate = prepareDocumentsImgUpdate(actor.items); - actor.updateEmbeddedDocuments('Item', actorItemsToUpdate); - }); - game.settings.set(SYSTEM_RDD, "migration-png-webp-1.5.34", true) - } - - // CSS patch for v9 - if (game.version) { - let sidebar = document.getElementById("sidebar"); - sidebar.style.width = "min-content"; + let regURL = `https://www.uberwald.me/fvtt_appcount/count.php?name="${registerKey}"&worldKey="${worldKey}"&version="${game.release.generation}.${game.release.build}"&system="${game.system.id}"&systemversion="${game.system.version}"` + $.ajax(regURL) + /* -------------------------------------------- */ + } } } -/* -------------------------------------------- */ -/* Dice-so-nice ready */ -/* -------------------------------------------- */ -Hooks.once('diceSoNiceReady', (dice3d) => RdDDice.diceSoNiceReady(dice3d)); +SystemReveDeDragon.start(); diff --git a/module/rdd-utility.js b/module/rdd-utility.js index 59e9f7a3..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) { @@ -947,7 +953,7 @@ export class RdDUtility { } /* -------------------------------------------- */ - static async confirmerSuppressionItem(sheet, item, htmlToDelete) { + static async confirmActorItemDelete(sheet, item, htmlToDelete) { const itemId = item.id; const confirmationSuppression = { settingConfirmer: "confirmation-supprimer-" + item.getItemGroup(), diff --git a/module/settings/system-compendiums.js b/module/settings/system-compendiums.js index be1af7d1..b3148fc5 100644 --- a/module/settings/system-compendiums.js +++ b/module/settings/system-compendiums.js @@ -87,6 +87,11 @@ export class SystemCompendiums extends FormApplication { return items; } + static async loadDocument(document) { + const pack = game.packs.get(document.pack); + return await pack.getDocument(document.id ?? document._id); + } + static async getItems(compendium, itemType = undefined) { const items = await SystemCompendiums.getPackContent(compendium, 'Item'); return (itemType ? items.filter(it => it.type == itemType) : items); diff --git a/styles/simple.css b/styles/simple.css index 65e9d41b..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; } @@ -771,11 +771,41 @@ div.placeholder-resolution span.table-proba-reussite{ input[type="date"], input[type="time"]) { color: rgba(255, 255, 255, 0.75); - background: rgba(255, 255, 255, 0.05); + background: rgba(255, 255, 255, 0.1); border: 0 none; margin-bottom: 0.2rem; } +input[type="number"] { + text-align: right; + padding-right: 0.5rem; + max-width: 7rem; +} + +select:is(.number-x,.number-x2,.number-x3,.number-x4,.number-x5) { + padding-right: 0.2rem; + text-align: center; +} +input:is(.number,.number-x,.number-x2,.number-x3,.number-x4,.number-x5) { + padding-right: 0.2rem; + text-align: right; +} +:is(input,select).number-x { + max-width: 1.4rem; +} +:is(input,select).number-x2 { + max-width: 2.4rem; +} +:is(input,select).number-x3 { + max-width: 3.4rem; +} +:is(input,select).number-x4 { + max-width: 4.4rem; +} +:is(input,select).number-x5 { + max-width: 4.4rem; +} + form.rdddialogchrono input[type=datetime-local] { min-width: 20px; padding: 0; @@ -818,7 +848,21 @@ section.sheet-body:after { display: block; clear: both; } - +a.rdd-world-content-link i { + color: var(--color-text-dark-inactive); + margin-right: 0.25em; +} +a.rdd-world-content-link { + background: hsla(280, 50%, 50%, 0.1); + padding: 1px 4px; + border: 1px solid var(--color-border-dark-tertiary); + border-radius: 2px; + white-space: nowrap; + word-break: break-all; +} +a.content-link { + background: hsla(45, 100%, 80%, 0.2); +} li label.compteur { display: inline-block; diff --git a/system.json b/system.json index 39e6093a..b5096bdc 100644 --- a/system.json +++ b/system.json @@ -1,8 +1,8 @@ { "id": "foundryvtt-reve-de-dragon", "title": "Rêve de Dragon", - "version": "10.3.17", - "download": "https://www.uberwald.me/gitea/public/foundryvtt-reve-de-dragon/archive/foundryvtt-reve-de-dragon-10.3.17.zip", + "version": "10.4.0", + "download": "https://www.uberwald.me/gitea/public/foundryvtt-reve-de-dragon/archive/foundryvtt-reve-de-dragon-10.4.0.zip", "manifest": "https://www.uberwald.me/gitea/public/foundryvtt-reve-de-dragon/raw/v10/system.json", "compatibility": { "minimum": "10", 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/actor-creature-sheet.html b/templates/actor-creature-sheet.html index f6404bd5..0e93a954 100644 --- a/templates/actor-creature-sheet.html +++ b/templates/actor-creature-sheet.html @@ -60,6 +60,7 @@ {{!-- Equipment Tab --}}
+ {{> "systems/foundryvtt-reve-de-dragon/templates/actor/inventaire-monnaie.html"}} {{> "systems/foundryvtt-reve-de-dragon/templates/actor/inventaire.html"}}
diff --git a/templates/actor-sheet.html b/templates/actor-sheet.html index a7243520..20782f13 100644 --- a/templates/actor-sheet.html +++ b/templates/actor-sheet.html @@ -119,7 +119,7 @@ {{!-- Equipment Tab --}}
- + {{> "systems/foundryvtt-reve-de-dragon/templates/actor/inventaire-monnaie.html"}} {{> "systems/foundryvtt-reve-de-dragon/templates/actor/inventaire.html"}} {{> "systems/foundryvtt-reve-de-dragon/templates/actor/liens-animaux.html"}} {{> "systems/foundryvtt-reve-de-dragon/templates/actor/liens-suivants.html"}} diff --git a/templates/actor-vehicule-sheet.html b/templates/actor-vehicule-sheet.html index 0f09a705..fe9dcf88 100644 --- a/templates/actor-vehicule-sheet.html +++ b/templates/actor-vehicule-sheet.html @@ -91,6 +91,7 @@ {{!-- Equipment Tab --}}
+ {{> "systems/foundryvtt-reve-de-dragon/templates/actor/inventaire-monnaie.html"}} {{> "systems/foundryvtt-reve-de-dragon/templates/actor/inventaire.html"}}
diff --git a/templates/actor/header-hautreve.html b/templates/actor/header-hautreve.html index a4d12700..0e9cb9ac 100644 --- a/templates/actor/header-hautreve.html +++ b/templates/actor/header-hautreve.html @@ -1,10 +1,14 @@ {{#if system.attributs.hautrevant.value}}
- Montée dans les Terres Médianes ! + + Montée dans les Terres Médianes ! + - Montée accélérée dans les Terres Médianes ! + + Montée accélérée dans les Terres Médianes ! + Regarder les Terres Médianes diff --git a/templates/actor/inventaire-monnaie.html b/templates/actor/inventaire-monnaie.html index d0bc788f..851d2188 100644 --- a/templates/actor/inventaire-monnaie.html +++ b/templates/actor/inventaire-monnaie.html @@ -1,4 +1,4 @@ -

Argent et Monnaies (fortune: {{calc.fortune}} sols)

+

Argent et Monnaies (fortune: {{calc.fortune.sols}} sols {{calc.fortune.deniers}} deniers)

    {{#each monnaie as |piece id|}}
  • diff --git a/templates/actor/inventaire.html b/templates/actor/inventaire.html index 01896200..1456e8fe 100644 --- a/templates/actor/inventaire.html +++ b/templates/actor/inventaire.html @@ -1,5 +1,3 @@ -{{> "systems/foundryvtt-reve-de-dragon/templates/actor/inventaire-monnaie.html" monnaie=monnaie}} -

    Equipement

    Nouvel objet diff --git a/templates/common/compendium-link.hbs b/templates/common/compendium-link.hbs index 3efebb63..5b4a1af9 100644 --- a/templates/common/compendium-link.hbs +++ b/templates/common/compendium-link.hbs @@ -1 +1,6 @@ -{{name}} \ No newline at end of file +{{#if pack}} +{{!-- draggable="true" --}} +{{name}} +{{else}} +{{name}} +{{/if}} diff --git a/templates/dialog-item-achat.html b/templates/dialog-item-achat.html index ade170ef..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}}

    @@ -86,9 +89,9 @@

    - + {{prixTotal}} - Sols + Sols
    {{/if}} diff --git a/templates/dialog-item-consommer.html b/templates/dialog-item-consommer.html index 0bbd47fc..48cd286f 100644 --- a/templates/dialog-item-consommer.html +++ b/templates/dialog-item-consommer.html @@ -3,7 +3,7 @@

    {{item.name}}

    -
    {{#if item.system.sust}} diff --git a/templates/dialog-item-split.html b/templates/dialog-item-split.html index 0f61fc55..a15f642a 100644 --- a/templates/dialog-item-split.html +++ b/templates/dialog-item-split.html @@ -4,7 +4,7 @@
    -
    diff --git a/templates/dialog-item-vente.html b/templates/dialog-item-vente.html index 2ba62e3b..4a0f1051 100644 --- a/templates/dialog-item-vente.html +++ b/templates/dialog-item-vente.html @@ -16,13 +16,13 @@ quantiteIllimite}}checked{{/if}} /> {{/unless}} -
-
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/item/partial-inventaire.html b/templates/item/partial-inventaire.html index a8c8a5d6..12abbdb6 100644 --- a/templates/item/partial-inventaire.html +++ b/templates/item/partial-inventaire.html @@ -1,25 +1,25 @@
-
-
-
{{#if (or (ne type 'monnaie') (gt system.cout 0))}} - {{else}} - {{/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}}}

+ +