diff --git a/css/foundryvtt-reve-de-dragon.css b/css/foundryvtt-reve-de-dragon.css index 8665644c..5ceaf8af 100644 --- a/css/foundryvtt-reve-de-dragon.css +++ b/css/foundryvtt-reve-de-dragon.css @@ -1153,6 +1153,35 @@ body { display: flex; min-width: max-content; } +.system-foundryvtt-reve-de-dragon .conteneur-sature { + color: #c00 !important; + font-weight: bold; +} +.system-foundryvtt-reve-de-dragon .conteneur-capacity-bar { + width: 5rem; + height: 0.4rem; + background: rgba(0, 0, 0, 0.1); + border-radius: 0.2rem; + overflow: hidden; + margin: 0 0.3rem; +} +.system-foundryvtt-reve-de-dragon .conteneur-capacity-fill { + height: 100%; + background: #4a90d9; + border-radius: 0.2rem; + transition: width 0.3s; +} +.system-foundryvtt-reve-de-dragon .conteneur-parent-badge { + font-size: 0.7rem; + color: #666; + margin-left: 0.3rem; +} +.system-foundryvtt-reve-de-dragon .conteneur-parent-badge::before { + content: "("; +} +.system-foundryvtt-reve-de-dragon .conteneur-parent-badge::after { + content: ")"; +} .system-foundryvtt-reve-de-dragon .blessure-control { flex-grow: 1; flex-direction: row; @@ -1218,6 +1247,96 @@ body { vertical-align: baseline; padding: 0.1rem; } +.system-foundryvtt-reve-de-dragon .dialog-gerer-herbes table { + width: 100%; +} +.system-foundryvtt-reve-de-dragon .dialog-gerer-herbes table th, +.system-foundryvtt-reve-de-dragon .dialog-gerer-herbes table td { + padding: 0.2rem 0.3rem !important; + vertical-align: middle !important; +} +.system-foundryvtt-reve-de-dragon .dialog-gerer-herbes table th { + color: var(--color-text-light-heading, #f0f0f0) !important; + font-weight: bold !important; +} +.system-foundryvtt-reve-de-dragon .dialog-gerer-herbes table th:first-child, +.system-foundryvtt-reve-de-dragon .dialog-gerer-herbes table td:first-child { + width: 36px !important; + text-align: center !important; +} +.system-foundryvtt-reve-de-dragon .dialog-gerer-herbes table th:nth-child(2), +.system-foundryvtt-reve-de-dragon .dialog-gerer-herbes table td:nth-child(2) { + width: auto !important; +} +.system-foundryvtt-reve-de-dragon .dialog-gerer-herbes table th:nth-child(3), +.system-foundryvtt-reve-de-dragon .dialog-gerer-herbes table td:nth-child(3) { + width: 3rem !important; + text-align: center !important; +} +.system-foundryvtt-reve-de-dragon .dialog-gerer-herbes table th:nth-child(4), +.system-foundryvtt-reve-de-dragon .dialog-gerer-herbes table td:nth-child(4) { + width: 6rem !important; + text-align: center !important; +} +.system-foundryvtt-reve-de-dragon .dialog-gerer-herbes table th:nth-child(5), +.system-foundryvtt-reve-de-dragon .dialog-gerer-herbes table td:nth-child(5) { + width: 5rem !important; + text-align: center !important; +} +.system-foundryvtt-reve-de-dragon .dialog-gerer-herbes table th:nth-child(7), +.system-foundryvtt-reve-de-dragon .dialog-gerer-herbes table td:nth-child(7) { + width: 2.5rem !important; + text-align: center !important; +} +.system-foundryvtt-reve-de-dragon .dialog-gerer-herbes table select { + max-width: 12rem !important; + min-width: 8rem !important; +} +.system-foundryvtt-reve-de-dragon .dialog-gerer-herbes table img { + width: 30px !important; + height: 30px !important; + object-fit: cover !important; + border: none !important; +} +.system-foundryvtt-reve-de-dragon .dialog-gerer-ingredients table { + width: 100%; +} +.system-foundryvtt-reve-de-dragon .dialog-gerer-ingredients table td { + padding: 0.2rem 0.3rem !important; + vertical-align: middle !important; +} +.system-foundryvtt-reve-de-dragon .dialog-gerer-ingredients table td:first-child { + width: 36px !important; + text-align: center !important; +} +.system-foundryvtt-reve-de-dragon .dialog-gerer-ingredients table td:nth-child(2) { + width: auto !important; +} +.system-foundryvtt-reve-de-dragon .dialog-gerer-ingredients table td:nth-child(3) { + width: 3rem !important; + text-align: center !important; +} +.system-foundryvtt-reve-de-dragon .dialog-gerer-ingredients table td:nth-child(4) { + width: 6rem !important; + text-align: center !important; +} +.system-foundryvtt-reve-de-dragon .dialog-gerer-ingredients table td:nth-child(5) { + width: auto !important; +} +.system-foundryvtt-reve-de-dragon .dialog-gerer-ingredients table td:nth-child(6) { + width: 2.5rem !important; + text-align: center !important; +} +.system-foundryvtt-reve-de-dragon .dialog-gerer-ingredients table select { + max-width: 12rem !important; + min-width: 8rem !important; +} +.system-foundryvtt-reve-de-dragon .dialog-gerer-ingredients table img { + width: 30px !important; + height: 30px !important; + object-fit: cover !important; + border: none !important; +} .system-foundryvtt-reve-de-dragon .dice-img { border-width: 0; max-width: 1.5rem; diff --git a/less/foundryvtt-reve-de-dragon.less b/less/foundryvtt-reve-de-dragon.less index 2bedcac5..aa8ac3b7 100644 --- a/less/foundryvtt-reve-de-dragon.less +++ b/less/foundryvtt-reve-de-dragon.less @@ -415,6 +415,35 @@ display: flex; min-width: max-content; } + .conteneur-sature { + color: #c00 !important; + font-weight: bold; + } + .conteneur-capacity-bar { + width: 5rem; + height: 0.4rem; + background: rgba(0, 0, 0, 0.1); + border-radius: 0.2rem; + overflow: hidden; + margin: 0 0.3rem; + } + .conteneur-capacity-fill { + height: 100%; + background: #4a90d9; + border-radius: 0.2rem; + transition: width 0.3s; + } + .conteneur-parent-badge { + font-size: 0.7rem; + color: #666; + margin-left: 0.3rem; + &::before { + content: "("; + } + &::after { + content: ")"; + } + } .blessure-control { flex-grow: 1; @@ -483,6 +512,98 @@ padding: 0.1rem; } + .dialog-gerer-herbes table { + width: 100%; + } + .dialog-gerer-herbes table th, + .dialog-gerer-herbes table td { + padding: 0.2rem 0.3rem !important; + vertical-align: middle !important; + } + .dialog-gerer-herbes table th { + color: var(--color-text-light-heading, #f0f0f0) !important; + font-weight: bold !important; + } + .dialog-gerer-herbes table th:first-child, + .dialog-gerer-herbes table td:first-child { + width: 36px !important; + text-align: center !important; + } + .dialog-gerer-herbes table th:nth-child(2), + .dialog-gerer-herbes table td:nth-child(2) { + width: auto !important; + } + .dialog-gerer-herbes table th:nth-child(3), + .dialog-gerer-herbes table td:nth-child(3) { + width: 3rem !important; + text-align: center !important; + } + .dialog-gerer-herbes table th:nth-child(4), + .dialog-gerer-herbes table td:nth-child(4) { + width: 6rem !important; + text-align: center !important; + } + .dialog-gerer-herbes table th:nth-child(5), + .dialog-gerer-herbes table td:nth-child(5) { + width: 5rem !important; + text-align: center !important; + } + .dialog-gerer-herbes table th:nth-child(7), + .dialog-gerer-herbes table td:nth-child(7) { + width: 2.5rem !important; + text-align: center !important; + } + .dialog-gerer-herbes table select { + max-width: 12rem !important; + min-width: 8rem !important; + } + .dialog-gerer-herbes table img { + width: 30px !important; + height: 30px !important; + object-fit: cover !important; + border: none !important; + } + + .dialog-gerer-ingredients table { + width: 100%; + } + .dialog-gerer-ingredients table td { + padding: 0.2rem 0.3rem !important; + vertical-align: middle !important; + } + .dialog-gerer-ingredients table td:first-child { + width: 36px !important; + text-align: center !important; + } + .dialog-gerer-ingredients table td:nth-child(2) { + width: auto !important; + } + .dialog-gerer-ingredients table td:nth-child(3) { + width: 3rem !important; + text-align: center !important; + } + .dialog-gerer-ingredients table td:nth-child(4) { + width: 6rem !important; + text-align: center !important; + } + .dialog-gerer-ingredients table td:nth-child(5) { + width: auto !important; + } + .dialog-gerer-ingredients table td:nth-child(6) { + width: 2.5rem !important; + text-align: center !important; + } + .dialog-gerer-ingredients table select { + max-width: 12rem !important; + min-width: 8rem !important; + } + .dialog-gerer-ingredients table img { + width: 30px !important; + height: 30px !important; + object-fit: cover !important; + border: none !important; + } + .dice-img { border-width: 0; max-width: 1.5rem; diff --git a/module/actor-sheet.js b/module/actor-sheet.js index 142897b3..2d9b9df4 100644 --- a/module/actor-sheet.js +++ b/module/actor-sheet.js @@ -31,7 +31,7 @@ export class RdDActorSheet extends RdDBaseActorSangSheet { static get defaultOptions() { return foundry.utils.mergeObject(RdDBaseActorReveSheet.defaultOptions, { template: "systems/foundryvtt-reve-de-dragon/templates/actor-sheet.hbs", - width: 550, + width: 640, showCompNiveauBase: false, vueArchetype: false, }, { inplace: false }) @@ -101,7 +101,8 @@ export class RdDActorSheet extends RdDBaseActorSangSheet { cacheTMR: this.actor.isTMRCache() } - formData.livresLus = formData.livres.filter(l => l.system.lu) + formData.livresLus = formData.livres.filter(l => l.system.estCopieLue) + formData.livres = formData.livres.filter(l => !l.system.estCopieLue) formData.race = actor.itemTypes[ITEM_TYPES.race].find(it => true) formData.subacteurs = { @@ -219,6 +220,18 @@ export class RdDActorSheet extends RdDBaseActorSangSheet { const { DialogGererHerbes } = await import("./dialog-gerer-herbes.js") DialogGererHerbes.create(this.actor) }) + this.html.find('.gerer-ingredients').click(async event => { + const { DialogGererIngredients } = await import("./dialog-gerer-ingredients.js") + DialogGererIngredients.create(this.actor) + }) + this.html.find('.gerer-deniers').click(async event => { + const { DialogGererDeniers } = await import("./dialog-gerer-deniers.js") + DialogGererDeniers.create(this.actor) + }) + this.html.find('.gerer-potions').click(async event => { + const { DialogGererPotions } = await import("./dialog-gerer-potions.js") + DialogGererPotions.create(this.actor) + }) if (game.user.isGM) { // experience log this.html.find('.experiencelog-delete').click(async event => { diff --git a/module/actor.js b/module/actor.js index 37128cc6..2a688e7e 100644 --- a/module/actor.js +++ b/module/actor.js @@ -1264,6 +1264,7 @@ export class RdDActor extends RdDBaseActorSang { if (tache) { await this.rollTache(tache.id); } + await this.creerCopieLue(item) } async fabriquerDecoctionHerbe(item) { diff --git a/module/actor/base-actor-reve-sheet.js b/module/actor/base-actor-reve-sheet.js index bdccb641..d37f3355 100644 --- a/module/actor/base-actor-reve-sheet.js +++ b/module/actor/base-actor-reve-sheet.js @@ -14,7 +14,7 @@ export class RdDBaseActorReveSheet extends RdDBaseActorSheet { /** @override */ static get defaultOptions() { return foundry.utils.mergeObject(RdDBaseActorSheet.defaultOptions, { - width: 550 + width: 640 }); } diff --git a/module/actor/base-actor-sheet.js b/module/actor/base-actor-sheet.js index 18530449..ef77ec94 100644 --- a/module/actor/base-actor-sheet.js +++ b/module/actor/base-actor-sheet.js @@ -23,7 +23,9 @@ export class RdDBaseActorSheet extends foundry.appv1.sheets.ActorSheet { classes: ["rdd", "sheet", "actor"], tabs: [{ navSelector: ".sheet-tabs", contentSelector: ".sheet-body", initial: "carac" }], dragDrop: [{ dragSelector: ".item-list .item", dropSelector: undefined }], - vueDetaillee: false + scrollY: [".sheet-body"], + vueDetaillee: false, + triEquipement: 'name' }, { inplace: false }) } @@ -45,7 +47,7 @@ export class RdDBaseActorSheet extends foundry.appv1.sheets.ActorSheet { effects: this.actor.effects } - RdDUtility.filterItemsPerTypeForSheet(formData, this.actor.itemTypes); + RdDUtility.filterItemsPerTypeForSheet(formData, this.actor.itemTypes, this.options.triEquipement); formData.calc = { fortune: Monnaie.toSolsDeniers(this.actor.getFortune()), prixTotalEquipement: this.actor.computePrixTotalEquipement(), @@ -104,6 +106,16 @@ export class RdDBaseActorSheet extends foundry.appv1.sheets.ActorSheet { this.html.find('.recherche').prop("disabled", false) + this.html.find('.item-list .item').dblclick(async event => { + const item = this.getItem(event) + if (item) item.sheet.render(true) + }) + + this.html.find('.tri-equipement').change(event => { + this.options.triEquipement = event.currentTarget.value; + this.render(true); + }) + // Everything below here is only needed if the sheet is editable if (!this.options.editable) return; @@ -111,6 +123,46 @@ export class RdDBaseActorSheet extends foundry.appv1.sheets.ActorSheet { this.html.find('.item-delete').click(async event => await RdDUtility.confirmActorItemDelete(this.getItem(event), this.actor)); this.html.find('.item-quantite-plus').click(async event => await this.actor.itemQuantiteIncDec(this.getItemId(event), 1)); this.html.find('.item-quantite-moins').click(async event => await this.actor.itemQuantiteIncDec(this.getItemId(event), -1)); + this.html.find('.item-quantite').change(async event => { + const input = event.currentTarget; + const item = this.actor.items.get(input.dataset.itemId); + if (item) { + const newQty = Math.max(0, parseInt(input.value) || 0); + await item.update({ 'system.quantite': newQty }); + if (newQty === 0) this.render(true); + } + }) + + this.html.find('.item-quantite').on('contextmenu', async event => { + event.preventDefault() + const item = this.getItem(event) + if (!item?.isInventaire()) return + new Dialog({ + title: `Quantité — ${item.name}`, + content: `
Réduire la quantité de :
+ `, + buttons: { + reduce: { + icon: '', + label: 'Retirer', + callback: html => { + const amount = html.find('.qty-amount').val() + if (amount === 'all') { + item.delete() + this.render(true) + } else { + item.update({ 'system.quantite': Math.max(0, item.system.quantite - parseInt(amount)) }) + } + } + } + } + }).render(true) + }) this.html.find('.creer-un-objet').click(async event => await this.selectObjetTypeToCreate()) this.html.find('.nettoyer-conteneurs').click(async event => await this.actor.nettoyerConteneurs()) @@ -180,15 +232,28 @@ export class RdDBaseActorSheet extends foundry.appv1.sheets.ActorSheet { /* -------------------------------------------- */ async _onDropItem(event, dragData) { const destItemId = this.html.find(event.target)?.closest('.item').attr('data-item-id') + const previousItemIds = new Set(this.actor.items.keys()) const dropParams = await RdDSheetUtility.prepareItemDropParameters(destItemId, this.actor, dragData, this.objetVersConteneur) if (dropParams) { const callSuper = await this.actor.processDropItem(dropParams) if (callSuper) { await super._onDropItem(event, dragData) } + // For external drops onto a container row, place the new item inside + if (destItemId && dropParams.sourceActorId !== this.actor.id) { + const container = this.actor.items.get(destItemId) + if (container?.isConteneur()) { + const dropped = [...this.actor.items].find(i => !previousItemIds.has(i.id)) + if (dropped && !this.actor.findConteneur(dropped)) { + await this.actor.ajouterDansConteneur(dropped, container, () => {}) + } + } + } } } + + /* -------------------------------------------- */ async selectObjetTypeToCreate() { let types = this.getTypesInventaire().sort(Misc.ascending(type => Misc.typeName('Item', type))); diff --git a/module/actor/base-actor.js b/module/actor/base-actor.js index e68e11a4..98551db3 100644 --- a/module/actor/base-actor.js +++ b/module/actor/base-actor.js @@ -906,6 +906,18 @@ export class RdDBaseActor extends Actor { async rollAppelChance() { this.actionImpossible("appel à la chance") } async jetDeMoral() { this.actionImpossible("jet de moral") } + async creerCopieLue(item) { + if (item.type !== ITEM_TYPES.livre) return + const existante = this.items.find(i => i.type === 'livre' && i.system.estCopieLue && i.name === item.name) + if (existante) return + const copie = item.toObject() + copie.system.estCopieLue = true + copie.system.lu = true + copie.system.quantite = 0 + delete copie._id + await this.createEmbeddedDocuments('Item', [copie], { renderSheet: false }) + } + async resetItemUse() { } async incDecItemUse(itemId, shouldIncrease = true) { } getItemUse(itemId) { return 0; } diff --git a/module/dialog-gerer-deniers.js b/module/dialog-gerer-deniers.js new file mode 100644 index 00000000..e1f7ff53 --- /dev/null +++ b/module/dialog-gerer-deniers.js @@ -0,0 +1,112 @@ +import { ITEM_TYPES } from "./constants.js" +import { RdDSheetUtility } from "./rdd-sheet-utility.js" +import { Monnaie } from "./item-monnaie.js" + +const { HandlebarsApplicationMixin, ApplicationV2 } = foundry.applications.api + +export class DialogGererDeniers extends HandlebarsApplicationMixin(ApplicationV2) { + + static DEFAULT_OPTIONS = { + tag: "form", + classes: ["dialog-gerer-deniers"], + form: { + submitOnChange: false, + closeOnSubmit: false + }, + position: { + width: 700, + height: "auto" + }, + window: { + title: "Gérer ses deniers", + icon: "fa-solid fa-coins" + } + } + + static PARTS = { + form: { template: "systems/foundryvtt-reve-de-dragon/templates/actor/dialog-gerer-deniers.hbs" } + } + + static async create(actor) { + const dialog = new DialogGererDeniers({ actor }) + dialog.render(true) + } + + constructor(options) { + super(options) + this.actor = options.actor + } + + async _prepareContext() { + const monnaies = this.actor.itemTypes[ITEM_TYPES.monnaie] || [] + const conteneurs = this.actor.itemTypes[ITEM_TYPES.conteneur] || [] + const fortune = Monnaie.toSolsDeniers(this.actor.getFortune()) + + const monnaieData = monnaies.map(piece => ({ + id: piece.id, + name: piece.name, + img: piece.img, + quantite: piece.system.quantite, + cout: piece.system.cout, + conteneurId: this.actor.findConteneur(piece)?.id || "" + })) + + const conteneurOptions = [ + { id: "", name: "— Aucun —" }, + ...conteneurs.map(c => ({ id: c.id, name: c.name })) + ] + + return { monnaies: monnaieData, conteneurs: conteneurOptions, fortune } + } + + async _onRender(context, options) { + this.element.querySelectorAll(".denier-moins").forEach(el => { + el.addEventListener("click", async event => { + const id = RdDSheetUtility.getItemId(event) + const piece = this.actor.items.get(id) + if (!piece) return + const q = Number(piece.system.quantite) + if (q <= 0) return + await piece.update({ "system.quantite": Math.max(0, q - 1) }) + this.render(true) + }) + }) + + this.element.querySelectorAll(".denier-plus").forEach(el => { + el.addEventListener("click", async event => { + const id = RdDSheetUtility.getItemId(event) + const piece = this.actor.items.get(id) + if (!piece) return + await piece.update({ "system.quantite": Number(piece.system.quantite) + 1 }) + this.render(true) + }) + }) + + this.element.querySelectorAll(".denier-edit").forEach(el => { + el.addEventListener("click", async event => { + const id = RdDSheetUtility.getItemId(event) + const piece = this.actor.items.get(id) + if (piece) piece.sheet.render(true) + }) + }) + + this.element.querySelectorAll(".denier-conteneur").forEach(el => { + el.addEventListener("change", async event => { + const id = RdDSheetUtility.getItemId(event) + const newConteneurId = String(event.currentTarget.value) + const piece = this.actor.items.get(id) + if (!piece) return + + const currentConteneur = this.actor.findConteneur(piece) + const newConteneur = newConteneurId ? this.actor.items.get(newConteneurId) : null + + if (currentConteneur) { + await this.actor.enleverDeConteneur(piece, currentConteneur, () => {}) + } + if (newConteneur) { + await this.actor.ajouterDansConteneur(piece, newConteneur, () => {}) + } + }) + }) + } +} diff --git a/module/dialog-gerer-herbes.js b/module/dialog-gerer-herbes.js new file mode 100644 index 00000000..c1498732 --- /dev/null +++ b/module/dialog-gerer-herbes.js @@ -0,0 +1,122 @@ +import { ITEM_TYPES } from "./constants.js" +import { RdDSheetUtility } from "./rdd-sheet-utility.js" + +const { HandlebarsApplicationMixin, ApplicationV2 } = foundry.applications.api + +export class DialogGererHerbes extends HandlebarsApplicationMixin(ApplicationV2) { + + static DEFAULT_OPTIONS = { + tag: "form", + classes: ["dialog-gerer-herbes"], + form: { + submitOnChange: false, + closeOnSubmit: false + }, + position: { + width: 650, + height: "auto" + }, + window: { + title: "Gérer ses herbes", + icon: "fa-solid fa-leaf" + } + } + + static PARTS = { + form: { template: "systems/foundryvtt-reve-de-dragon/templates/actor/dialog-gerer-herbes.hbs" } + + + } + + static async create(actor) { + const dialog = new DialogGererHerbes({ actor }) + dialog.render(true) + } + + constructor(options) { + super(options) + this.actor = options.actor + } + + async _prepareContext() { + const herbes = this.actor.itemTypes[ITEM_TYPES.herbe] || [] + const conteneurs = this.actor.itemTypes[ITEM_TYPES.conteneur] || [] + + const herbeData = herbes.map(herbe => ({ + id: herbe.id, + name: herbe.name, + img: herbe.img, + quantite: herbe.system.quantite, + categorie: herbe.system.categorie, + niveau: herbe.system.niveau, + isHerbeAPotion: herbe.isHerbeAPotion(), + conteneurId: this.actor.findConteneur(herbe)?.id || "" + })) + + const conteneurOptions = [ + { id: "", name: "— Aucun —" }, + ...conteneurs.map(c => ({ id: c.id, name: c.name })) + ] + + return { herbes: herbeData, conteneurs: conteneurOptions } + } + + async _onRender(context, options) { + this.element.querySelectorAll(".herbe-moins").forEach(el => { + el.addEventListener("click", async event => { + const id = RdDSheetUtility.getItemId(event) + const herbe = this.actor.items.get(id) + if (!herbe) return + const q = Number(herbe.system.quantite) + if (q <= 0) return + await herbe.update({ "system.quantite": Math.max(0, q - 1) }) + this.render(true) + }) + }) + + this.element.querySelectorAll(".herbe-plus").forEach(el => { + el.addEventListener("click", async event => { + const id = RdDSheetUtility.getItemId(event) + const herbe = this.actor.items.get(id) + if (!herbe) return + await herbe.update({ "system.quantite": Number(herbe.system.quantite) + 1 }) + this.render(true) + }) + }) + + this.element.querySelectorAll(".herbe-edit").forEach(el => { + el.addEventListener("click", async event => { + const herbeId = RdDSheetUtility.getItemId(event) + const herbe = this.actor.items.get(herbeId) + if (herbe) herbe.sheet.render(true) + }) + }) + + this.element.querySelectorAll(".herbe-decoction").forEach(el => { + el.addEventListener("click", async event => { + const herbeId = RdDSheetUtility.getItemId(event) + const herbe = this.actor.items.get(herbeId) + if (herbe) this.actor.fabriquerDecoctionHerbe(herbe) + }) + }) + + this.element.querySelectorAll(".herbe-conteneur").forEach(el => { + el.addEventListener("change", async event => { + const herbeId = RdDSheetUtility.getItemId(event) + const newConteneurId = String(event.currentTarget.value) + const herbe = this.actor.items.get(herbeId) + if (!herbe) return + + const currentConteneur = this.actor.findConteneur(herbe) + const newConteneur = newConteneurId ? this.actor.items.get(newConteneurId) : null + + if (currentConteneur) { + await this.actor.enleverDeConteneur(herbe, currentConteneur, () => {}) + } + if (newConteneur) { + await this.actor.ajouterDansConteneur(herbe, newConteneur, () => {}) + } + }) + }) + } +} diff --git a/module/dialog-gerer-ingredients.js b/module/dialog-gerer-ingredients.js new file mode 100644 index 00000000..e3388c4e --- /dev/null +++ b/module/dialog-gerer-ingredients.js @@ -0,0 +1,110 @@ +import { ITEM_TYPES } from "./constants.js" +import { RdDSheetUtility } from "./rdd-sheet-utility.js" + +const { HandlebarsApplicationMixin, ApplicationV2 } = foundry.applications.api + +export class DialogGererIngredients extends HandlebarsApplicationMixin(ApplicationV2) { + + static DEFAULT_OPTIONS = { + tag: "form", + classes: ["dialog-gerer-ingredients"], + form: { + submitOnChange: false, + closeOnSubmit: false + }, + position: { + width: 650, + height: "auto" + }, + window: { + title: "Gérer ses ingrédients", + icon: "fa-solid fa-flask" + } + } + + static PARTS = { + form: { template: "systems/foundryvtt-reve-de-dragon/templates/actor/dialog-gerer-ingredients.hbs" } + } + + static async create(actor) { + const dialog = new DialogGererIngredients({ actor }) + dialog.render(true) + } + + constructor(options) { + super(options) + this.actor = options.actor + } + + async _prepareContext() { + const ingredients = this.actor.itemTypes[ITEM_TYPES.ingredient] || [] + const conteneurs = this.actor.itemTypes[ITEM_TYPES.conteneur] || [] + + const ingredientData = ingredients.map(ing => ({ + id: ing.id, + name: ing.name, + img: ing.img, + quantite: ing.system.quantite, + categorie: ing.system.categorie, + conteneurId: this.actor.findConteneur(ing)?.id || "" + })) + + const conteneurOptions = [ + { id: "", name: "— Aucun —" }, + ...conteneurs.map(c => ({ id: c.id, name: c.name })) + ] + + return { ingredients: ingredientData, conteneurs: conteneurOptions } + } + + async _onRender(context, options) { + this.element.querySelectorAll(".ingredient-moins").forEach(el => { + el.addEventListener("click", async event => { + const id = RdDSheetUtility.getItemId(event) + const ing = this.actor.items.get(id) + if (!ing) return + const q = Number(ing.system.quantite) + if (q <= 0) return + await ing.update({ "system.quantite": Math.max(0, q - 1) }) + this.render(true) + }) + }) + + this.element.querySelectorAll(".ingredient-plus").forEach(el => { + el.addEventListener("click", async event => { + const id = RdDSheetUtility.getItemId(event) + const ing = this.actor.items.get(id) + if (!ing) return + await ing.update({ "system.quantite": Number(ing.system.quantite) + 1 }) + this.render(true) + }) + }) + + this.element.querySelectorAll(".ingredient-edit").forEach(el => { + el.addEventListener("click", async event => { + const ingId = RdDSheetUtility.getItemId(event) + const ing = this.actor.items.get(ingId) + if (ing) ing.sheet.render(true) + }) + }) + + this.element.querySelectorAll(".ingredient-conteneur").forEach(el => { + el.addEventListener("change", async event => { + const ingId = RdDSheetUtility.getItemId(event) + const newConteneurId = String(event.currentTarget.value) + const ing = this.actor.items.get(ingId) + if (!ing) return + + const currentConteneur = this.actor.findConteneur(ing) + const newConteneur = newConteneurId ? this.actor.items.get(newConteneurId) : null + + if (currentConteneur) { + await this.actor.enleverDeConteneur(ing, currentConteneur, () => {}) + } + if (newConteneur) { + await this.actor.ajouterDansConteneur(ing, newConteneur, () => {}) + } + }) + }) + } +} diff --git a/module/dialog-gerer-potions.js b/module/dialog-gerer-potions.js new file mode 100644 index 00000000..e6292d3a --- /dev/null +++ b/module/dialog-gerer-potions.js @@ -0,0 +1,121 @@ +import { ITEM_TYPES } from "./constants.js" +import { RdDSheetUtility } from "./rdd-sheet-utility.js" + +const { HandlebarsApplicationMixin, ApplicationV2 } = foundry.applications.api + +export class DialogGererPotions extends HandlebarsApplicationMixin(ApplicationV2) { + + static DEFAULT_OPTIONS = { + tag: "form", + classes: ["dialog-gerer-potions"], + form: { + submitOnChange: false, + closeOnSubmit: false + }, + position: { + width: 700, + height: "auto" + }, + window: { + title: "Gérer ses potions", + icon: "fa-solid fa-flask" + } + } + + static PARTS = { + form: { template: "systems/foundryvtt-reve-de-dragon/templates/actor/dialog-gerer-potions.hbs" } + } + + static async create(actor) { + const dialog = new DialogGererPotions({ actor }) + dialog.render(true) + } + + constructor(options) { + super(options) + this.actor = options.actor + } + + async _prepareContext() { + const potions = this.actor.itemTypes[ITEM_TYPES.potion] || [] + const conteneurs = this.actor.itemTypes[ITEM_TYPES.conteneur] || [] + + const potionData = potions.map(p => ({ + id: p.id, + name: p.name, + img: p.img, + quantite: p.system.quantite, + categorie: p.system.categorie, + conteneurId: this.actor.findConteneur(p)?.id || "" + })) + + const conteneurOptions = [ + { id: "", name: "— Aucun —" }, + ...conteneurs.map(c => ({ id: c.id, name: c.name })) + ] + + return { potions: potionData, conteneurs: conteneurOptions } + } + + async _onRender(context, options) { + this.element.querySelectorAll(".potion-moins").forEach(el => { + el.addEventListener("click", async event => { + const id = RdDSheetUtility.getItemId(event) + const potion = this.actor.items.get(id) + if (!potion) return + const q = Number(potion.system.quantite) + if (q <= 0) return + await potion.update({ "system.quantite": Math.max(0, q - 1) }) + this.render(true) + }) + }) + + this.element.querySelectorAll(".potion-plus").forEach(el => { + el.addEventListener("click", async event => { + const id = RdDSheetUtility.getItemId(event) + const potion = this.actor.items.get(id) + if (!potion) return + await potion.update({ "system.quantite": Number(potion.system.quantite) + 1 }) + this.render(true) + }) + }) + + this.element.querySelectorAll(".potion-consommer").forEach(el => { + el.addEventListener("click", async event => { + const id = RdDSheetUtility.getItemId(event) + const potion = this.actor.items.get(id) + if (potion) { + await this.actor.consommerPotion(potion) + this.render(true) + } + }) + }) + + this.element.querySelectorAll(".potion-edit").forEach(el => { + el.addEventListener("click", async event => { + const id = RdDSheetUtility.getItemId(event) + const potion = this.actor.items.get(id) + if (potion) potion.sheet.render(true) + }) + }) + + this.element.querySelectorAll(".potion-conteneur").forEach(el => { + el.addEventListener("change", async event => { + const id = RdDSheetUtility.getItemId(event) + const newConteneurId = String(event.currentTarget.value) + const potion = this.actor.items.get(id) + if (!potion) return + + const currentConteneur = this.actor.findConteneur(potion) + const newConteneur = newConteneurId ? this.actor.items.get(newConteneurId) : null + + if (currentConteneur) { + await this.actor.enleverDeConteneur(potion, currentConteneur, () => {}) + } + if (newConteneur) { + await this.actor.ajouterDansConteneur(potion, newConteneur, () => {}) + } + }) + }) + } +} diff --git a/module/item/item-actions.js b/module/item/item-actions.js index 06594208..7fc7e4fe 100644 --- a/module/item/item-actions.js +++ b/module/item/item-actions.js @@ -96,7 +96,7 @@ const _MARQUER_LU = { code: 'item-marquer-lu', label: 'Marquer comme lu', icon: it => it.system.lu ? 'fa-solid fa-book-open' : 'fa-regular fa-book', actorFilter: actor => actor.isPersonnage(), - action: (item, actor) => item.update({ 'system.lu': !item.system.lu }) + action: async (item, actor) => { await actor.creerCopieLue(item) } } const _REFOULER = { @@ -113,13 +113,20 @@ const _SORT_RESERVE = { action: (item, actor) => actor.addSortReserve(item) } +const _VIDER = { + code: 'item-vider', label: 'Vider le conteneur', icon: 'fa-solid fa-box-open', + filter: it => it.isConteneur() && (it.system.contenu?.length > 0), + actorFilter: actor => actor.isOwner, + action: (item, actor) => item.update({ 'system.contenu': [] }) +} + export const COMMON_ACTIONS = [_EQUIPER] export const DEFAULT_ACTIONS = [_ACHETER, _SPLIT, _SPACEHOLDER, _VENDRE, _MONTRER, _EDIT, _DELETE] export const ITEM_ACTIONS = { faune: [_CUISINER, _MANGER_CRU], ingredient: [_CUISINER, _MANGER_CRU], - conteneur: [_OUVRIR], + conteneur: [_VIDER, _OUVRIR], herbe: [_DECOCTION, _CUISINER, _MANGER_CRU], livre: [_LIRE, _MARQUER_LU], nourritureboisson: [_MANGER, _BOIRE], diff --git a/module/rdd-utility.js b/module/rdd-utility.js index 281516ac..b8a19e70 100644 --- a/module/rdd-utility.js +++ b/module/rdd-utility.js @@ -145,6 +145,7 @@ export class RdDUtility { 'systems/foundryvtt-reve-de-dragon/templates/actor/possessions.hbs', 'systems/foundryvtt-reve-de-dragon/templates/actor/resonances.hbs', 'systems/foundryvtt-reve-de-dragon/templates/actor/taches.hbs', + 'systems/foundryvtt-reve-de-dragon/templates/actor/livres.hbs', 'systems/foundryvtt-reve-de-dragon/templates/actor/oeuvres.hbs', 'systems/foundryvtt-reve-de-dragon/templates/actor/oeuvre.hbs', 'systems/foundryvtt-reve-de-dragon/templates/actor/jeux.hbs', @@ -174,6 +175,10 @@ export class RdDUtility { 'systems/foundryvtt-reve-de-dragon/templates/actor/commerce-inventaire.hbs', 'systems/foundryvtt-reve-de-dragon/templates/actor/commerce-inventaire-item.hbs', 'systems/foundryvtt-reve-de-dragon/templates/actor/tooltip-bonmoments.hbs', + 'systems/foundryvtt-reve-de-dragon/templates/actor/dialog-gerer-herbes.hbs', + 'systems/foundryvtt-reve-de-dragon/templates/actor/dialog-gerer-ingredients.hbs', + 'systems/foundryvtt-reve-de-dragon/templates/actor/dialog-gerer-deniers.hbs', + 'systems/foundryvtt-reve-de-dragon/templates/actor/dialog-gerer-potions.hbs', //Items 'systems/foundryvtt-reve-de-dragon/templates/scripts/autocomplete-script.hbs', 'systems/foundryvtt-reve-de-dragon/templates/scripts/autocomplete.hbs', @@ -414,6 +419,7 @@ export class RdDUtility { let objet = inventaires.find(objet => (id == objet._id)); if (objet) { objet.estContenu = true; + objet.parentConteneurName = conteneur.name; objetVersConteneur[id] = conteneur._id; conteneur.subItems.push(objet); } @@ -474,6 +480,8 @@ export class RdDUtility { item: item, vide: isConteneur && item.system.contenu.length == 0, ouvert: isConteneur && RdDUtility.getAfficheContenu(item._id), + sature: isConteneur && item.system.encTotal > item.system.capacite, + fillPercent: isConteneur ? Math.min(100, (item.system.encTotal / (item.system.capacite || 1)) * 100) : 0, options: options }; optionsArbre.ouvert = inventaire.ouvert @@ -484,7 +492,7 @@ export class RdDUtility { return ligneObjet; } - static filterItemsPerTypeForSheet(formData, itemTypes) { + static filterItemsPerTypeForSheet(formData, itemTypes, triEquipement = 'name') { Object.values(ITEM_TYPES).forEach(t => { formData[RdDItem.itemTypePluriel(t)] = Misc.arrayOrEmpty(itemTypes[t]) itemTypes[t].forEach(item => item.actions = item.itemActions()) @@ -494,7 +502,7 @@ export class RdDUtility { formData.competences = formData.competences.concat(formData.competencecreatures) formData.monnaies = formData.monnaies.sort(Monnaie.triValeurEntiere()) - formData.inventaires = RdDUtility.prepareInventaire(itemTypes) + formData.inventaires = RdDUtility.prepareInventaire(itemTypes, triEquipement) } static buildInventaireConteneur(actorId, itemId, options) { @@ -502,7 +510,7 @@ export class RdDUtility { const item = actor?.items.get(itemId) if (item?.type == ITEM_TYPES.conteneur) { const formData = {} - RdDUtility.filterItemsPerTypeForSheet(formData, actor.itemTypes); + RdDUtility.filterItemsPerTypeForSheet(formData, actor.itemTypes, options?.triEquipement); RdDUtility.buildArbreDeConteneurs(formData.conteneurs, formData.inventaires); item.subItems = formData.conteneurs.find(it => it._id == itemId)?.subItems; @@ -511,11 +519,25 @@ export class RdDUtility { return '' } - static prepareInventaire(itemTypes) { - return RdDItem.getItemTypesInventaire('all') + static prepareInventaire(itemTypes, triEquipement = 'name') { + let items = RdDItem.getItemTypesInventaire('all') .map(t => Misc.arrayOrEmpty(itemTypes[t])) - .reduce((a, b) => a.concat(b), []) - .sort(Misc.ascending(it => it.name)) + .reduce((a, b) => a.concat(b), []); + items = items.filter(it => !(it.type === ITEM_TYPES.livre && it.system.estCopieLue)); + switch (triEquipement) { + case 'type': + items.sort(Misc.ascending(it => Misc.typeName('Item', it.type))); + break; + case 'enc': + items.sort(Misc.ascending(it => it.system.encombrement ?? 0)); + break; + case 'quantite': + items.sort(Misc.descending(it => it.system.quantite ?? 0)); + break; + default: + items.sort(Misc.ascending(it => it.name)); + } + return items; } /* -------------------------------------------- */ @@ -525,7 +547,23 @@ export class RdDUtility { const profondeur = optionsArbre.profondeur; optionsArbre.profondeur++; - const lignesContenu = conteneur.subItems.sort(Misc.ascending(it => it.name)) + const sortBy = options.triEquipement || 'name'; + let sorted = [...conteneur.subItems]; + sorted = sorted.filter(it => !(it.type === ITEM_TYPES.livre && it.system.estCopieLue)); + switch (sortBy) { + case 'type': + sorted.sort(Misc.ascending(it => Misc.typeName('Item', it.type))); + break; + case 'enc': + sorted.sort(Misc.ascending(it => it.system.encombrement ?? 0)); + break; + case 'quantite': + sorted.sort(Misc.descending(it => it.system.quantite ?? 0)); + break; + default: + sorted.sort(Misc.ascending(it => it.name)); + } + const lignesContenu = sorted .map(contenu => this.buildLigneInventaire(contenu, options, optionsArbre)); return `