From 4f9b72c63f3d6ef388a7b931cc98b9dfad4f0d9c Mon Sep 17 00:00:00 2001 From: Vlyan Date: Fri, 21 May 2021 14:12:14 +0200 Subject: [PATCH] Working on 0.8.x - Working on title - Added property update/delete for item patterns --- CHANGELOG.md | 9 +- system/lang/en-en.json | 1 + system/lang/es-es.json | 1 + system/lang/fr-fr.json | 1 + system/packs/core-item-patterns.db | 10 +- system/scripts/actors/base-sheet.js | 21 ++ .../scripts/actors/twenty-questions-dialog.js | 28 +-- system/scripts/actors/twenty-questions.js | 8 +- system/scripts/config.js | 2 +- system/scripts/dice/dice-picker-dialog.js | 13 +- system/scripts/dice/gm-tools-dialog.js | 2 +- system/scripts/helpers.js | 4 +- system/scripts/item.js | 183 ++++++++++++++++++ system/scripts/items/item-pattern-sheet.js | 117 +++++++++++ system/scripts/items/item-sheet.js | 48 ++++- system/scripts/items/title-sheet.js | 91 +++++++++ system/scripts/migration.js | 8 +- system/scripts/preloadTemplates.js | 111 ++++++----- system/template.json | 8 +- system/templates/dice/roll-n-keep-dialog.html | 7 + .../item-pattern/item-pattern-sheet.html | 11 ++ system/templates/items/title/title-sheet.html | 3 +- 22 files changed, 586 insertions(+), 101 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a41032c..ecc2756 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,19 +1,22 @@ # Changelog ## 1.3.0 - Foundry 0.8.x compatibility -_! Be certain to carefully back up any critical user data before installing this update. !_ +__! Be certain to carefully back up any critical user data before installing this update !__ - Updated the System to the new version of Foundry VTT (a lot of things broke). - NPC can now have strengths/weaknesses with all rings. -- Added "Title, Bond, Signature Scroll and Item Pattern": +- Added "Title", "Bond", "Signature Scroll" and "Item Pattern": - The item types. - Theirs compendiums entries. - A new list in experience tab to not mess with school cursus. - - Item pattern can be dropped on another item to add the associated property. + - Item patterns : + - Can be dropped on another item to add the associated property. + - To change the linked property, drop any property on the item pattern sheet. - Added an optional "Specificity" technique type to serve as a catch-all (by request). - Added Mantis Clan compendium entries - Fix : rnkMessage not passing on actor object for NPCs (thanks to Bragma). - Fix : The "Crescent Moon Style" technique rank from 4 to 2 - QoL : RnK button is now black in chat if no actions are left in roll. +- QoL : Added symbols legend in RnK dialog as reminder. - QoL : Added "(x Max)" display in RnK picker for max number of dice to keep (thanks to Bragma). ## 1.2.1 - Praised be Firefox diff --git a/system/lang/en-en.json b/system/lang/en-en.json index 61acead..def5ea5 100644 --- a/system/lang/en-en.json +++ b/system/lang/en-en.json @@ -138,6 +138,7 @@ "quantity": "Quantity", "weight": "Weight", "properties": "Properties", + "linked_property": "linked Property", "weapons": { "title": "Weapons", "damage": "Damage", diff --git a/system/lang/es-es.json b/system/lang/es-es.json index a2f99a2..c7dbcb8 100644 --- a/system/lang/es-es.json +++ b/system/lang/es-es.json @@ -138,6 +138,7 @@ "quantity": "Cantidad", "weight": "Peso", "properties": "Propiedades", + "linked_property": "linked Property", "weapons": { "title": "Armas", "damage": "Daño", diff --git a/system/lang/fr-fr.json b/system/lang/fr-fr.json index 7457b4a..1daca5c 100644 --- a/system/lang/fr-fr.json +++ b/system/lang/fr-fr.json @@ -138,6 +138,7 @@ "quantity": "Quantité", "weight": "Poids", "properties": "Propriétés", + "linked_property": "Propriété liée", "weapons": { "title": "Armement", "damage": "Dégâts de base (DDB)", diff --git a/system/packs/core-item-patterns.db b/system/packs/core-item-patterns.db index a70e710..b7751a9 100644 --- a/system/packs/core-item-patterns.db +++ b/system/packs/core-item-patterns.db @@ -1,5 +1,5 @@ -{"_id":"L5RCoreItp000001","name":"Kakita Pattern","permission":{"default":0},"type":"item_pattern","data":{"in_curriculum":false,"xp_used":0,"rank":1,"bought_at_rank":0,"ring":"void","xp_cost":"6","rarity_modifier":"4","description":"","book_reference":"Shadowlands p.109"},"sort":100001,"flags":{"l5r5e":{"linkedPropertyId":"L5RCorePro000017"}},"img":"systems/l5r5e/assets/icons/items/item_pattern.svg","effects":[]} -{"_id":"L5RCoreItp000002","name":"Kenzō Blade","permission":{"default":0},"type":"item_pattern","data":{"in_curriculum":false,"xp_used":0,"rank":1,"bought_at_rank":0,"ring":"void","xp_cost":"8","rarity_modifier":"5","description":"","book_reference":"Shadowlands p.109"},"sort":100001,"flags":{"l5r5e":{"linkedPropertyId":"L5RCorePro000018"}},"img":"systems/l5r5e/assets/icons/items/item_pattern.svg","effects":[]} -{"_id":"L5RCoreItp000003","name":"Shirogane Jade Inlay","permission":{"default":0},"type":"item_pattern","data":{"in_curriculum":false,"xp_used":0,"rank":1,"bought_at_rank":0,"ring":"void","xp_cost":"3","rarity_modifier":"2","description":"","book_reference":"Shadowlands p.109"},"sort":100001,"flags":{"l5r5e":{"linkedPropertyId":"L5RCorePro000019"}},"img":"systems/l5r5e/assets/icons/items/item_pattern.svg","effects":[]} -{"_id":"L5RCoreItp000004","name":"Uchema’s Technique","permission":{"default":0},"type":"item_pattern","data":{"in_curriculum":false,"xp_used":0,"rank":1,"bought_at_rank":0,"ring":"void","xp_cost":"4","rarity_modifier":"3","description":"","book_reference":"Shadowlands p.109"},"sort":100001,"flags":{"l5r5e":{"linkedPropertyId":"L5RCorePro000020"}},"img":"systems/l5r5e/assets/icons/items/item_pattern.svg","effects":[]} -{"_id":"L5RCoreItp000005","name":"Yasunori Steel","permission":{"default":0},"type":"item_pattern","data":{"in_curriculum":false,"xp_used":0,"rank":1,"bought_at_rank":0,"ring":"void","xp_cost":"5","rarity_modifier":"4","description":"","book_reference":"Shadowlands p.109"},"sort":100001,"flags":{"l5r5e":{"linkedPropertyId":"L5RCorePro000021"}},"img":"systems/l5r5e/assets/icons/items/item_pattern.svg","effects":[]} +{"_id":"L5RCoreItp000001","name":"Kakita Pattern","permission":{"default":0},"type":"item_pattern","data":{"in_curriculum":false,"xp_used":0,"rank":1,"bought_at_rank":0,"ring":"void","xp_cost":"6","rarity_modifier":"4","linked_property_id":"L5RCorePro000017","description":"","book_reference":"Shadowlands p.109"},"sort":100001,"flags":{},"img":"systems/l5r5e/assets/icons/items/item_pattern.svg","effects":[]} +{"_id":"L5RCoreItp000002","name":"Kenzō Blade","permission":{"default":0},"type":"item_pattern","data":{"in_curriculum":false,"xp_used":0,"rank":1,"bought_at_rank":0,"ring":"void","xp_cost":"8","rarity_modifier":"5","linked_property_id":"L5RCorePro000018","description":"","book_reference":"Shadowlands p.109"},"sort":100001,"flags":{},"img":"systems/l5r5e/assets/icons/items/item_pattern.svg","effects":[]} +{"_id":"L5RCoreItp000003","name":"Shirogane Jade Inlay","permission":{"default":0},"type":"item_pattern","data":{"in_curriculum":false,"xp_used":0,"rank":1,"bought_at_rank":0,"ring":"void","xp_cost":"3","rarity_modifier":"2","linked_property_id":"L5RCorePro000019","description":"","book_reference":"Shadowlands p.109"},"sort":100001,"flags":{},"img":"systems/l5r5e/assets/icons/items/item_pattern.svg","effects":[]} +{"_id":"L5RCoreItp000004","name":"Uchema’s Technique","permission":{"default":0},"type":"item_pattern","data":{"in_curriculum":false,"xp_used":0,"rank":1,"bought_at_rank":0,"ring":"void","xp_cost":"4","rarity_modifier":"3","linked_property_id":"L5RCorePro000020","description":"","book_reference":"Shadowlands p.109"},"sort":100001,"flags":{},"img":"systems/l5r5e/assets/icons/items/item_pattern.svg","effects":[]} +{"_id":"L5RCoreItp000005","name":"Yasunori Steel","permission":{"default":0},"type":"item_pattern","data":{"in_curriculum":false,"xp_used":0,"rank":1,"bought_at_rank":0,"ring":"void","xp_cost":"5","rarity_modifier":"4","linked_property_id":"L5RCorePro000021","description":"","book_reference":"Shadowlands p.109"},"sort":100001,"flags":{},"img":"systems/l5r5e/assets/icons/items/item_pattern.svg","effects":[]} diff --git a/system/scripts/actors/base-sheet.js b/system/scripts/actors/base-sheet.js index f5f3cca..adcdac7 100644 --- a/system/scripts/actors/base-sheet.js +++ b/system/scripts/actors/base-sheet.js @@ -324,6 +324,10 @@ export class BaseSheetL5r5e extends ActorSheet { event.stopPropagation(); const type = $(event.currentTarget).data("item-type"); + if (!type) { + return; + } + const titles = { item: "ITEM.TypeItem", armor: "ITEM.TypeArmor", @@ -336,6 +340,10 @@ export class BaseSheetL5r5e extends ActorSheet { item_pattern: "ITEM.TypeItem_pattern", signature_scroll: "ITEM.TypeSignature_scroll", }; + if (!titles[type]) { + return; + } + const created = await this.actor.createEmbeddedDocuments("Item", [ { name: game.i18n.localize(titles[type]), @@ -343,6 +351,9 @@ export class BaseSheetL5r5e extends ActorSheet { img: `${CONFIG.l5r5e.paths.assets}icons/items/${type}.svg`, }, ]); + if (created?.length > 0) { + return; + } const item = this.actor.items.get(created[0].id); // assign current school rank to the new adv/tech @@ -385,7 +396,14 @@ export class BaseSheetL5r5e extends ActorSheet { event.stopPropagation(); const itemId = $(event.currentTarget).data("item-id"); + if (!itemId) { + return; + } + const item = this.actor.items.get(itemId); + if (!item) { + return; + } item.sheet.render(true); } @@ -399,6 +417,9 @@ export class BaseSheetL5r5e extends ActorSheet { event.stopPropagation(); const itemId = $(event.currentTarget).data("item-id"); + if (!itemId) { + return; + } // Remove 1 qty if possible const tmpItem = this.actor.items.get(itemId); diff --git a/system/scripts/actors/twenty-questions-dialog.js b/system/scripts/actors/twenty-questions-dialog.js index afd8f09..eae7f60 100644 --- a/system/scripts/actors/twenty-questions-dialog.js +++ b/system/scripts/actors/twenty-questions-dialog.js @@ -334,7 +334,7 @@ export class TwentyQuestionsDialog extends FormApplication { if (this.object.data.step13.advantage.length > 0) { formData["step13.skill"] = "none"; - setProperty(this.object.data, "step13.disadvantage", []); + foundry.utils.setProperty(this.object.data, "step13.disadvantage", []); } // Update 20Q object data @@ -364,14 +364,14 @@ export class TwentyQuestionsDialog extends FormApplication { this.cache = {}; for (const stepName of TwentyQuestions.itemsList) { // Check if current step value is a array - let step = getProperty(this.object.data, stepName); + let step = foundry.utils.getProperty(this.object.data, stepName); if (!step || !Array.isArray(step)) { step = []; } // Init cache if not exist - if (!hasProperty(this.cache, stepName)) { - setProperty(this.cache, stepName, []); + if (!foundry.utils.hasProperty(this.cache, stepName)) { + foundry.utils.setProperty(this.cache, stepName, []); } // Get linked Item, and store it in cache (delete null value and old items) @@ -385,9 +385,9 @@ export class TwentyQuestionsDialog extends FormApplication { continue; } newStep.push(id); - getProperty(this.cache, stepName).push(item); + foundry.utils.getProperty(this.cache, stepName).push(item); } - setProperty(this.object.data, stepName, newStep); + foundry.utils.setProperty(this.object.data, stepName, newStep); } } @@ -400,7 +400,7 @@ export class TwentyQuestionsDialog extends FormApplication { roll.actor = this._actor; await roll.roll(); - setProperty(this.object.data, stepName, roll.result); + foundry.utils.setProperty(this.object.data, stepName, roll.result); return roll.toMessage({ flavor: flavor }); } @@ -410,7 +410,7 @@ export class TwentyQuestionsDialog extends FormApplication { */ _addOwnedItem(item, stepName) { // Add to Step (uniq id only) - let step = getProperty(this.object.data, stepName); + let step = foundry.utils.getProperty(this.object.data, stepName); if (!step) { step = []; } @@ -420,7 +420,7 @@ export class TwentyQuestionsDialog extends FormApplication { step.push(item.id); // Add to cache - getProperty(this.cache, stepName).push(item); + foundry.utils.getProperty(this.cache, stepName).push(item); } /** @@ -429,14 +429,14 @@ export class TwentyQuestionsDialog extends FormApplication { */ _deleteOwnedItem(stepName, itemId) { // Delete from current step - let step = getProperty(this.object.data, stepName); + let step = foundry.utils.getProperty(this.object.data, stepName); step = step.filter((e) => !!e && e !== itemId); - setProperty(this.object.data, stepName, step); + foundry.utils.setProperty(this.object.data, stepName, step); // Delete from cache - let cache = getProperty(this.cache, stepName); + let cache = foundry.utils.getProperty(this.cache, stepName); cache = cache.filter((e) => !!e && e.id !== itemId); - setProperty(this.cache, stepName, cache); + foundry.utils.setProperty(this.cache, stepName, cache); } /** @@ -444,7 +444,7 @@ export class TwentyQuestionsDialog extends FormApplication { * @private */ _getSkillZero(skillsList, skillsPoints, stepName) { - const stepSkillId = getProperty(this.object.data, stepName); + const stepSkillId = foundry.utils.getProperty(this.object.data, stepName); const out = {}; Object.entries(skillsList).forEach(([cat, val]) => { out[cat] = val.filter( diff --git a/system/scripts/actors/twenty-questions.js b/system/scripts/actors/twenty-questions.js index c43ec7e..a5bce1d 100644 --- a/system/scripts/actors/twenty-questions.js +++ b/system/scripts/actors/twenty-questions.js @@ -267,7 +267,7 @@ export class TwentyQuestions { // Rings - Reset to 1, and apply modifiers CONFIG.l5r5e.stances.forEach((ring) => (actorDatas.rings[ring] = 1)); TwentyQuestions.ringList.forEach((formName) => { - const ring = getProperty(this.data, formName); + const ring = foundry.utils.getProperty(this.data, formName); if (ring !== "none") { actorDatas.rings[ring] = actorDatas.rings[ring] + 1; } @@ -278,7 +278,7 @@ export class TwentyQuestions { actorDatas.skills[skillCat][skillId] = 0; }); TwentyQuestions.skillList.forEach((formName) => { - const skillId = getProperty(this.data, formName); + const skillId = foundry.utils.getProperty(this.data, formName); const skillCat = CONFIG.l5r5e.skills.get(skillId); if (skillId !== "none") { actorDatas.skills[skillCat][skillId] = actorDatas.skills[skillCat][skillId] + 1; @@ -292,7 +292,7 @@ export class TwentyQuestions { // Add items in 20Q to actor for (const types of Object.values(itemsCache)) { for (const item of types) { - const itemData = duplicate(item.data); + const itemData = foundry.utils.duplicate(item.data); if (itemData.data?.bought_at_rank) { itemData.data.bought_at_rank = 0; } @@ -377,7 +377,7 @@ export class TwentyQuestions { summariesRingsOrSkills(listName) { const store = {}; TwentyQuestions[listName].forEach((formName) => { - const id = getProperty(this.data, formName); + const id = foundry.utils.getProperty(this.data, formName); if (!id || id === "none") { return; } diff --git a/system/scripts/config.js b/system/scripts/config.js index 92f60f3..c8e3325 100644 --- a/system/scripts/config.js +++ b/system/scripts/config.js @@ -23,7 +23,7 @@ L5R5E.techniques.set("ninjutsu", { type: "core", displayInTypes: true }); L5R5E.techniques.set("school_ability", { type: "school", displayInTypes: false }); L5R5E.techniques.set("mastery_ability", { type: "school", displayInTypes: false }); // Title -L5R5E.techniques.set("title_ability", { type: "title", displayInTypes: false }); +// L5R5E.techniques.set("title_ability", { type: "title", displayInTypes: false }); // Custom L5R5E.techniques.set("specificity", { type: "custom", displayInTypes: false }); diff --git a/system/scripts/dice/dice-picker-dialog.js b/system/scripts/dice/dice-picker-dialog.js index fe312fd..78a058a 100644 --- a/system/scripts/dice/dice-picker-dialog.js +++ b/system/scripts/dice/dice-picker-dialog.js @@ -399,7 +399,7 @@ export class DicePickerDialog extends FormApplication { // Update Actor if (this._actor) { - const actorData = duplicate(this._actor.data.data); + const actorData = foundry.utils.duplicate(this._actor.data.data); // Update the actor stance on initiative only if (this.object.isInitiativeRoll) { @@ -416,10 +416,13 @@ export class DicePickerDialog extends FormApplication { actorData.void_points.value = Math.max(actorData.void_points.value - 1, 0); } - // Update actor - await this._actor.update({ - data: diffObject(this._actor.data.data, actorData), - }); + // Update actor if needed + const updateDiff = foundry.utils.diffObject(this._actor.data.data, actorData); + if (Object.keys(updateDiff).length > 0) { + await this._actor.update({ + data: foundry.utils.diffObject(this._actor.data.data, actorData), + }); + } } // Build the formula diff --git a/system/scripts/dice/gm-tools-dialog.js b/system/scripts/dice/gm-tools-dialog.js index 6a49a5c..a931ebe 100644 --- a/system/scripts/dice/gm-tools-dialog.js +++ b/system/scripts/dice/gm-tools-dialog.js @@ -65,7 +65,7 @@ export class GmToolsDialog extends FormApplication { */ async close(options = {}) { // TODO better implementation needed : see KeyboardManager._onEscape(event, up, modifiers) - // This windows is always open, so esc key si stuck at step 2 : Object.keys(ui.windows).length > 0 + // This windows is always open, so esc key is stuck at step 2 : Object.keys(ui.windows).length > 0 // Case 3 (GM) - release controlled objects if (canvas?.ready && game.user.isGM && Object.keys(canvas.activeLayer._controlled).length) { canvas.activeLayer.releaseAll(); diff --git a/system/scripts/helpers.js b/system/scripts/helpers.js index 5cd57d0..665f4dc 100644 --- a/system/scripts/helpers.js +++ b/system/scripts/helpers.js @@ -137,7 +137,6 @@ export class HelpersL5r5e { } catch (err) { console.warn(err); } - console.log(" ***** getObjectGameOrPack", document); return document; } @@ -151,8 +150,9 @@ export class HelpersL5r5e { "armor", "weapon", "technique", - "peculiarity", "property", + "peculiarity", + "advancement", "title", "bond", "signature_scroll", diff --git a/system/scripts/item.js b/system/scripts/item.js index ed9d4e2..540dc4f 100644 --- a/system/scripts/item.js +++ b/system/scripts/item.js @@ -1,4 +1,12 @@ export class ItemL5r5e extends Item { + /** + * A reference to the Collection of embedded Item instances in the document, indexed by _id. + * @returns {Collection} + */ + get items() { + return this.data.data.items || null; + } + /** * Create a new entity using provided input data * @override @@ -9,4 +17,179 @@ export class ItemL5r5e extends Item { } return super.create(data, context); } + + /** + * Update this Document using incremental data, saving it to the database. + * @see {@link Document.updateDocuments} + * @param {object} [data={}] Differential update data which modifies the existing values of this document data + * @param {DocumentModificationContext} [context={}] Additional context which customizes the update workflow + * @returns {Promise} The updated Document instance + */ + async update(data = {}, context = {}) { + // Regular + if (!this.data.data.parentId) { + return super.update(data, context); + } + + // **** Embed Items, need to get the parents **** + const parentItem = this.getItemFromParentId(); + if (!parentItem) { + console.warn(`Embed parentItem not found`); + return; + } + + // Merge (DocumentData cannot be set) + const result = foundry.utils.mergeObject(this.data, foundry.utils.expandObject(data)); + if (result.name) { + this.data.name = result.name; + } + if (result.img) { + this.data.img = result.img; + } + if (result.data) { + this.data.data = result.data; + } + + // Update + await parentItem.updateEmbedItem(this.data.toObject(false)); + + // Return new value for sheet + return new Promise((resolve) => resolve(this)); + } + + /** @override */ + prepareData() { + super.prepareData(); + + // Prepare Embed items + if (!(this.data.data.items instanceof Map)) { + const itemsData = Array.isArray(this.data.data.items) ? this.data.data.items : []; + this.data.data.items = new Map(); + + itemsData.forEach((item) => { + this.addEmbedItem(item, { save: false, newId: false }); + }); + } + } + + // ***** parent ids management ***** + /** + * Return a string with idemId + actorId if any + * @return {string} itemId|actor + */ + getParentsIds() { + return this.id + (this.actor?.data?._id ? `|${this.actor.data._id}` : ""); + } + + /** + * Return the Item Object for the "parentId" + * @return {ItemL5r5e|null} + */ + getItemFromParentId() { + let parentItem; + let [parentItemId, parentActorId] = this.data.data.parentId.split("|"); + + if (parentActorId) { + // Actor item object + const parentActor = parentActorId ? game.actors.get(parentActorId) : null; + parentItem = parentActor?.items.get(parentItemId); + } else { + // World Object + parentItem = game.items.get(parentItemId); + } + return parentItem; + } + + // ***** Embedded items management ***** + /** + * Shortcut for this.data.data.items.get + * @param id + * @return {ItemL5r5e|null} + */ + getEmbedItem(id) { + return this.items?.get(id) || null; + } + + /** + * Add a Embed Item + * @param {ItemL5r5e} item Object to add + * @param {boolean} save if we save in db or not (used internally) + * @param {boolean} newId if we change the id + * @return {Promise} + */ + async addEmbedItem(item, { save = true, newId = true } = {}) { + if (!item) { + return; + } + + if (!(item instanceof Item) && item._id) { + // Data -> Item + item = new ItemL5r5e(item); + } + + // New id + if (newId) { + item.data._id = foundry.utils.randomID(); + } + + // Tag parent (flags won't work as we have no id in db) + item.data.data.parentId = this.getParentsIds(); + + // Object + this.data.data.items.set(item.data._id, item); + + // TODO add bonus from actor + if (this.actor instanceof Actor) { + // const item = this.data.data.items.get(id); + } + + if (save) { + await this.saveEmbedItems(); + } + } + + /** + * Update a Embed Item + * @param {ItemL5r5e} item Object to add + * @param {boolean} save if we save in db or not (used internally) + * @return {Promise} + */ + async updateEmbedItem(item, { save = true } = {}) { + await this.addEmbedItem(item, { save, newId: false }); + } + + /** + * Delete the Embed Item and clear the actor bonus if any + * @param {ItemL5r5e} item Object to add + * @param {boolean} save if we save in db or not (used internally) + * @return {Promise} + */ + async deleteEmbedItem(id, { save = true } = {}) { + if (!this.data.data.items.has(id)) { + return; + } + + // TODO remove bonus from actor + if (this.actor instanceof Actor) { + // const item = this.data.data.items.get(id); + } + + // Remove the embed item + this.data.data.items.delete(id); + + if (save) { + await this.saveEmbedItems(); + } + } + + /** + * Save all the Embed Items + * @return {Promise} + */ + async saveEmbedItems() { + await this.update({ + "data.items": Array.from(this.data.data.items).map(([id, item]) => item.data.toObject(false)), + }); + this.sheet.render(false); + } } diff --git a/system/scripts/items/item-pattern-sheet.js b/system/scripts/items/item-pattern-sheet.js index c82a4d4..cb51069 100644 --- a/system/scripts/items/item-pattern-sheet.js +++ b/system/scripts/items/item-pattern-sheet.js @@ -14,4 +14,121 @@ export class ItemPatternSheetL5r5e extends ItemSheetL5r5e { tabs: [{ navSelector: ".sheet-tabs", contentSelector: ".sheet-body", initial: "description" }], }); } + + /** + * @return {Object|Promise} + */ + async getData(options = {}) { + const sheetData = await super.getData(options); + + sheetData.data.dtypes = ["String", "Number", "Boolean"]; + sheetData.data.ringsList = game.l5r5e.HelpersL5r5e.getRingsList(); + + // Linked Property + sheetData.data.linkedProperty = await this.getLinkedProperty(sheetData); + + return sheetData; + } + + /** + * Get the linked property name + * @param sheetData + * @return {Promise} + */ + async getLinkedProperty(sheetData) { + if (sheetData.data.data.linked_property_id) { + const linkedProperty = await game.l5r5e.HelpersL5r5e.getObjectGameOrPack({ + id: sheetData.data.data.linked_property_id, + type: "Item", + }); + if (linkedProperty) { + return { + id: linkedProperty.data._id, + name: linkedProperty.data.name, + }; + } + } + return null; + } + + /** + * Subscribe to events from the sheet. + * @param html HTML content of the sheet. + */ + activateListeners(html) { + super.activateListeners(html); + + // Everything below here is only needed if the sheet is editable + if (!this.options.editable) { + return; + } + + // Delete the linked property + html.find(`.linked-property-delete`).on("click", this._deleteLinkedProperty.bind(this)); + } + + /** + * Callback actions which occur when a dragged element is dropped on a target. + * @param {DragEvent} event The originating DragEvent + * @private + */ + async _onDrop(event) { + // Everything below here is only needed if the sheet is editable + if (!this.options.editable) { + return; + } + + // Only property allowed here + let item = await game.l5r5e.HelpersL5r5e.getDragnDropTargetObject(event); + if (!item || item.documentName !== "Item" || item.data.type !== "property") { + return; + } + + // Set the new property, and update + this.document.data.data.linked_property_id = item.id; + this.document.update({ + data: { + linked_property_id: this.document.data.data.linked_property_id, + }, + }); + } + + /** + * Remove the link to a property for the current item + * @param {Event} event + * @return {Promise} + * @private + */ + async _deleteLinkedProperty(event) { + event.preventDefault(); + event.stopPropagation(); + + let name; + const linkedProperty = await game.l5r5e.HelpersL5r5e.getObjectGameOrPack({ + id: this.document.data.data.linked_property_id, + type: "Item", + }); + if (linkedProperty) { + name = linkedProperty.data.name; + } + + const callback = async () => { + this.document.data.data.linked_property_id = null; + this.document.update({ + data: { + linked_property_id: this.document.data.data.linked_property_id, + }, + }); + }; + + // Holing Ctrl = without confirm + if (event.ctrlKey || !name) { + return callback(); + } + + game.l5r5e.HelpersL5r5e.confirmDeleteDialog( + game.i18n.format("l5r5e.global.delete_confirm", { name }), + callback + ); + } } diff --git a/system/scripts/items/item-sheet.js b/system/scripts/items/item-sheet.js index 2234e64..43b534c 100644 --- a/system/scripts/items/item-sheet.js +++ b/system/scripts/items/item-sheet.js @@ -142,8 +142,11 @@ export class ItemSheetL5r5e extends ItemSheet { } // Specific ItemPattern's drop, get the associated props instead - if (item.data.type === "item_pattern" && item.data.flags.l5r5e?.linkedPropertyId) { - item = await game.packs.get("l5r5e.core-properties").getDocument(item.data.flags.l5r5e.linkedPropertyId); + if (item.data.type === "item_pattern" && item.data.data.linked_property_id) { + item = await game.l5r5e.HelpersL5r5e.getObjectGameOrPack({ + id: item.data.data.linked_property_id, + type: "Item", + }); } // Final object has to be a property @@ -179,6 +182,8 @@ export class ItemSheetL5r5e extends ItemSheet { /** * Delete a property from the current item + * @param {Event} event + * @return {Promise} * @private */ _deleteProperty(event) { @@ -214,4 +219,43 @@ export class ItemSheetL5r5e extends ItemSheet { callback ); } + + /** + * Add a embed item + * @param event + * @private + */ + _addSubItem(event) { + event.preventDefault(); + event.stopPropagation(); + const itemId = $(event.currentTarget).data("item-id"); + console.log("TODO _addSubItem", itemId); // TODO _addSubItem + } + + /** + * Add a embed item + * @param event + * @private + */ + _editSubItem(event) { + event.preventDefault(); + event.stopPropagation(); + const itemId = $(event.currentTarget).data("item-id"); + const item = this.document.items.get(itemId); + if (item) { + item.sheet.render(true); + } + } + + /** + * Delete a embed item + * @param event + * @private + */ + _deleteSubItem(event) { + event.preventDefault(); + event.stopPropagation(); + const itemId = $(event.currentTarget).data("item-id"); + this.document.deleteEmbedItem(itemId); + } } diff --git a/system/scripts/items/title-sheet.js b/system/scripts/items/title-sheet.js index c7413e4..ac5223d 100644 --- a/system/scripts/items/title-sheet.js +++ b/system/scripts/items/title-sheet.js @@ -14,4 +14,95 @@ export class TitleSheetL5r5e extends ItemSheetL5r5e { tabs: [{ navSelector: ".sheet-tabs", contentSelector: ".sheet-body", initial: "description" }], }); } + + /** + * @return {Object|Promise} + */ + async getData(options = {}) { + const sheetData = await super.getData(options); + + sheetData.data.dtypes = ["String", "Number", "Boolean"]; + sheetData.data.ringsList = game.l5r5e.HelpersL5r5e.getRingsList(); + + console.log(sheetData.data.data.items); // todo tmp + // Prepare OwnedItems + sheetData.data.embedItemsList = this._prepareEmbedItems(sheetData.data.data.items); + + console.log(sheetData); // todo tmp + return sheetData; + } + + /** + * Prepare Embed items + * @param {[]|Map} itemsMap + * @return {[]} + * @private + */ + _prepareEmbedItems(itemsMap) { + let itemsList = itemsMap; + if (itemsMap instanceof Map) { + itemsList = Array.from(itemsMap).map(([id, item]) => item.data); + } + + // Sort by rank desc + itemsList.sort((a, b) => (b.data.rank || 0) - (a.data.rank || 0)); + + return itemsList; + } + + /** + * Callback actions which occur when a dragged element is dropped on a target. + * @param {DragEvent} event The originating DragEvent + * @private + */ + async _onDrop(event) { + // Everything below here is only needed if the sheet is editable + if (!this.options.editable) { + return; + } + + // Check item type and subtype + let item = await game.l5r5e.HelpersL5r5e.getDragnDropTargetObject(event); + if (!item || (item.documentName !== "Item" && !["technique", "advancement"].includes(item.data.type))) { + return; + } + + const data = item.data.toJSON(); + + console.log("------ data", data); // todo tmp + + this.document.addEmbedItem(data); + + console.log(this.document); // todo tmp + } + + /** + * Subscribe to events from the sheet. + * @param html HTML content of the sheet. + */ + activateListeners(html) { + super.activateListeners(html); + + // Everything below here is only needed if the sheet is editable + if (!this.options.editable) { + return; + } + + // *** Items : add, edit, delete *** + html.find(".item-add").on("click", this._addSubItem.bind(this)); + html.find(`.item-edit`).on("click", this._editSubItem.bind(this)); + html.find(`.item-delete`).on("click", this._deleteSubItem.bind(this)); + } + + /** + * This method is called upon form submission after form data is validated + * @param {Event} event The initial triggering submission event + * @param {object} formData The object of validated form data with which to update the object + * @returns {Promise} A Promise which resolves once the update operation has completed + * @abstract + */ + // async _updateObject(event, formData) { + // console.log("------- _updateObject.", formData); // todo TMP + // return super._updateObject(event, formData); + // } } diff --git a/system/scripts/migration.js b/system/scripts/migration.js index da8dc50..5ee1eb1 100644 --- a/system/scripts/migration.js +++ b/system/scripts/migration.js @@ -38,7 +38,7 @@ export class MigrationL5r5e { const updateData = MigrationL5r5e._migrateActorData(a.data); if (!isObjectEmpty(updateData)) { console.log(`Migrating Actor entity ${a.name}`); - await a.update(updateData, { enforceTypes: false }); + await a.update(updateData, { enforceTypes: false }); // TODO use Actor.updateDocuments(data, context) for multiple actors } } catch (err) { err.message = `Failed L5R5e system migration for Actor ${a.name}: ${err.message}`; @@ -52,7 +52,7 @@ export class MigrationL5r5e { const updateData = MigrationL5r5e._migrateItemData(i.data); if (!isObjectEmpty(updateData)) { console.log(`Migrating Item entity ${i.name}`); - await i.update(updateData, { enforceTypes: false }); + await i.update(updateData, { enforceTypes: false }); // TODO use Item.updateDocuments(data, context) for multiple actors } } catch (err) { err.message = `Failed L5R5e system migration for Item ${i.name}: ${err.message}`; @@ -66,7 +66,7 @@ export class MigrationL5r5e { const updateData = MigrationL5r5e._migrateSceneData(s.data); if (!isObjectEmpty(updateData)) { console.log(`Migrating Scene entity ${s.name}`); - await s.update(updateData, { enforceTypes: false }); + await s.update(updateData, { enforceTypes: false }); // TODO use Scene.updateDocuments(data, context) for multiple actors } } catch (err) { err.message = `Failed L5R5e system migration for Scene ${s.name}: ${err.message}`; @@ -132,7 +132,7 @@ export class MigrationL5r5e { // Save the entry, if data was changed updateData["_id"] = ent._id; - await pack.updateEntity(updateData); + await pack.updateEntity(updateData); // TODO use Item/Actor.updateDocuments(data, context) for multiple actors console.log(`Migrated ${entity} entity ${ent.name} in Compendium ${pack.collection}`); } catch (err) { // Handle migration failures diff --git a/system/scripts/preloadTemplates.js b/system/scripts/preloadTemplates.js index 1aff616..fb02bce 100644 --- a/system/scripts/preloadTemplates.js +++ b/system/scripts/preloadTemplates.js @@ -1,62 +1,61 @@ export const PreloadTemplates = async function () { - const templatePaths = [ + const tpl = CONFIG.l5r5e.paths.templates; + return loadTemplates([ // Add paths to "systems/l5r5e/templates" // *** Actors : PC *** - "systems/l5r5e/templates/actors/character/advancement-school.html", - "systems/l5r5e/templates/actors/character/advancement-others.html", - "systems/l5r5e/templates/actors/character/attributes.html", - "systems/l5r5e/templates/actors/character/category.html", - "systems/l5r5e/templates/actors/character/conflict.html", - "systems/l5r5e/templates/actors/character/experience.html", - "systems/l5r5e/templates/actors/character/identity.html", - "systems/l5r5e/templates/actors/character/inventory.html", - "systems/l5r5e/templates/actors/character/narrative.html", - "systems/l5r5e/templates/actors/character/rings.html", - "systems/l5r5e/templates/actors/character/skill.html", - "systems/l5r5e/templates/actors/character/social.html", - "systems/l5r5e/templates/actors/character/stance.html", - "systems/l5r5e/templates/actors/character/techniques.html", - "systems/l5r5e/templates/actors/character/twenty-questions-item.html", + `${tpl}actors/character/advancement-school.html`, + `${tpl}actors/character/advancement-others.html`, + `${tpl}actors/character/attributes.html`, + `${tpl}actors/character/category.html`, + `${tpl}actors/character/conflict.html`, + `${tpl}actors/character/experience.html`, + `${tpl}actors/character/identity.html`, + `${tpl}actors/character/inventory.html`, + `${tpl}actors/character/narrative.html`, + `${tpl}actors/character/rings.html`, + `${tpl}actors/character/skill.html`, + `${tpl}actors/character/social.html`, + `${tpl}actors/character/stance.html`, + `${tpl}actors/character/techniques.html`, + `${tpl}actors/character/twenty-questions-item.html`, // *** Actors : Npc *** - "systems/l5r5e/templates/actors/npc/attributes.html", - "systems/l5r5e/templates/actors/npc/conflict.html", - "systems/l5r5e/templates/actors/npc/identity.html", - "systems/l5r5e/templates/actors/npc/inventory.html", - "systems/l5r5e/templates/actors/npc/narrative.html", - "systems/l5r5e/templates/actors/npc/rings.html", - "systems/l5r5e/templates/actors/npc/social.html", - "systems/l5r5e/templates/actors/npc/skill.html", - "systems/l5r5e/templates/actors/npc/techniques.html", + `${tpl}actors/npc/attributes.html`, + `${tpl}actors/npc/conflict.html`, + `${tpl}actors/npc/identity.html`, + `${tpl}actors/npc/inventory.html`, + `${tpl}actors/npc/narrative.html`, + `${tpl}actors/npc/rings.html`, + `${tpl}actors/npc/social.html`, + `${tpl}actors/npc/skill.html`, + `${tpl}actors/npc/techniques.html`, // *** Items *** - "systems/l5r5e/templates/items/advancement/advancement-entry.html", - "systems/l5r5e/templates/items/advancement/advancement-sheet.html", - "systems/l5r5e/templates/items/armor/armors.html", - "systems/l5r5e/templates/items/armor/armor-entry.html", - "systems/l5r5e/templates/items/armor/armor-sheet.html", - "systems/l5r5e/templates/items/bond/bond-entry.html", - "systems/l5r5e/templates/items/bond/bond-sheet.html", - "systems/l5r5e/templates/items/item/items.html", - "systems/l5r5e/templates/items/item/item-entry.html", - "systems/l5r5e/templates/items/item/item-value.html", - "systems/l5r5e/templates/items/item/item-sheet.html", - "systems/l5r5e/templates/items/item/item-infos.html", - "systems/l5r5e/templates/items/item-pattern/item-pattern-entry.html", - "systems/l5r5e/templates/items/item-pattern/item-pattern-sheet.html", - "systems/l5r5e/templates/items/peculiarity/peculiarity-entry.html", - "systems/l5r5e/templates/items/peculiarity/peculiarity-sheet.html", - "systems/l5r5e/templates/items/property/properties.html", - "systems/l5r5e/templates/items/property/property-entry.html", - "systems/l5r5e/templates/items/property/property-sheet.html", - "systems/l5r5e/templates/items/signature-scroll/signature-scroll-entry.html", - "systems/l5r5e/templates/items/signature-scroll/signature-scroll-sheet.html", - "systems/l5r5e/templates/items/technique/technique-entry.html", - "systems/l5r5e/templates/items/technique/technique-sheet.html", - "systems/l5r5e/templates/items/title/title-entry.html", - "systems/l5r5e/templates/items/title/title-sheet.html", - "systems/l5r5e/templates/items/weapon/weapons.html", - "systems/l5r5e/templates/items/weapon/weapon-entry.html", - "systems/l5r5e/templates/items/weapon/weapon-sheet.html", - ]; - - return loadTemplates(templatePaths); + `${tpl}items/advancement/advancement-entry.html`, + `${tpl}items/advancement/advancement-sheet.html`, + `${tpl}items/armor/armors.html`, + `${tpl}items/armor/armor-entry.html`, + `${tpl}items/armor/armor-sheet.html`, + `${tpl}items/bond/bond-entry.html`, + `${tpl}items/bond/bond-sheet.html`, + `${tpl}items/item/items.html`, + `${tpl}items/item/item-entry.html`, + `${tpl}items/item/item-value.html`, + `${tpl}items/item/item-sheet.html`, + `${tpl}items/item/item-infos.html`, + `${tpl}items/item-pattern/item-pattern-entry.html`, + `${tpl}items/item-pattern/item-pattern-sheet.html`, + `${tpl}items/peculiarity/peculiarity-entry.html`, + `${tpl}items/peculiarity/peculiarity-sheet.html`, + `${tpl}items/property/properties.html`, + `${tpl}items/property/property-entry.html`, + `${tpl}items/property/property-sheet.html`, + `${tpl}items/signature-scroll/signature-scroll-entry.html`, + `${tpl}items/signature-scroll/signature-scroll-sheet.html`, + `${tpl}items/technique/technique-entry.html`, + `${tpl}items/technique/technique-sheet.html`, + `${tpl}items/title/title-entry.html`, + `${tpl}items/title/title-sheet.html`, + `${tpl}items/weapon/weapons.html`, + `${tpl}items/weapon/weapon-entry.html`, + `${tpl}items/weapon/weapon-sheet.html`, + ]); }; diff --git a/system/template.json b/system/template.json index 4139f4f..cb7cf2c 100644 --- a/system/template.json +++ b/system/template.json @@ -155,7 +155,9 @@ "templates": { "basics": { "book_reference": "", - "description": "" + "description": "", + "parent_id": null, + "items": [] }, "advancement": { "in_curriculum": false, @@ -214,7 +216,8 @@ }, "title": { "templates": ["basics", "advancement"], - "xp_cost": 3 + "xp_cost": 3, + "advancements": [] }, "bond": { "templates": ["basics", "advancement"], @@ -223,6 +226,7 @@ }, "item_pattern": { "templates": ["basics", "advancement"], + "linked_property_id": null, "rarity_modifier": "", "xp_cost": 3 }, diff --git a/system/templates/dice/roll-n-keep-dialog.html b/system/templates/dice/roll-n-keep-dialog.html index dca5a24..162f166 100644 --- a/system/templates/dice/roll-n-keep-dialog.html +++ b/system/templates/dice/roll-n-keep-dialog.html @@ -102,6 +102,13 @@ + {{!-- Symbols Helpers --}} +
+ {{localize 'l5r5e.chatdices.successes'}} + | {{localize 'l5r5e.chatdices.explosives'}} + | {{localize 'l5r5e.chatdices.opportunities'}} + | {{localize 'l5r5e.chatdices.strife'}} +
{{else}} {{!-- Non editable DiceList history --}} diff --git a/system/templates/items/item-pattern/item-pattern-sheet.html b/system/templates/items/item-pattern/item-pattern-sheet.html index 7647c1b..d536335 100644 --- a/system/templates/items/item-pattern/item-pattern-sheet.html +++ b/system/templates/items/item-pattern/item-pattern-sheet.html @@ -27,6 +27,17 @@ {{ localize 'l5r5e.bought_at_rank' }} + {{> 'systems/l5r5e/templates/items/item/item-infos.html'}} diff --git a/system/templates/items/title/title-sheet.html b/system/templates/items/title/title-sheet.html index d2ac7e7..04a0074 100644 --- a/system/templates/items/title/title-sheet.html +++ b/system/templates/items/title/title-sheet.html @@ -32,7 +32,6 @@ {{!-- Embbed advancements --}}
- {{!-- TODO --}} {{!-- Others progession (does not count in school xp) --}}
{{localize 'l5r5e.advancements.title'}} @@ -48,7 +47,7 @@
- {{#each data.advancementsOthers as |advancement advancementId|}} + {{#each data.embedItemsList as |advancement|}} {{> 'systems/l5r5e/templates/actors/character/advancement-others.html' advancement=advancement editable=../options.editable}} {{/each}}