From 53f04e6cef8e81c42e6cf22664c12de04ec9678a Mon Sep 17 00:00:00 2001 From: Vlyan Date: Sat, 22 May 2021 17:20:17 +0200 Subject: [PATCH] Working on 0.8.x - Title's Advancements are now reflected on actor - Migration update --- CHANGELOG.md | 6 +- system/scripts/actor.js | 61 ++++++++++ system/scripts/actors/base-sheet.js | 102 +++++++++------- system/scripts/actors/character-sheet.js | 41 ++++--- system/scripts/actors/twenty-questions.js | 16 ++- system/scripts/config.js | 2 +- system/scripts/dice/dietype/l5r-base-die.js | 4 +- system/scripts/dice/roll.js | 8 -- system/scripts/item.js | 66 +++++++---- system/scripts/items/advancement-sheet.js | 36 +++--- system/scripts/items/item-pattern-sheet.js | 3 - system/scripts/items/item-sheet.js | 19 ++- system/scripts/items/title-sheet.js | 35 +++--- system/scripts/migration.js | 110 ++++++++++-------- system/system.json | 10 +- system/templates/actors/character-sheet.html | 6 +- .../actors/character/advancement-others.html | 2 +- .../templates/actors/character/category.html | 16 +-- .../templates/actors/character/conflict.html | 4 +- .../actors/character/experience.html | 9 +- system/templates/actors/limited-sheet.html | 4 +- system/templates/actors/npc-sheet.html | 4 +- .../items/advancement/advancement-sheet.html | 16 +-- system/templates/items/armor/armor-sheet.html | 14 +-- system/templates/items/bond/bond-sheet.html | 14 +-- .../item-pattern/item-pattern-sheet.html | 14 +-- system/templates/items/item/item-sheet.html | 6 +- .../items/peculiarity/peculiarity-sheet.html | 18 +-- .../items/property/property-sheet.html | 4 +- .../signature-scroll-sheet.html | 12 +- .../items/technique/technique-entry.html | 4 +- .../items/technique/technique-sheet.html | 18 +-- system/templates/items/title/title-sheet.html | 20 ++-- 33 files changed, 423 insertions(+), 281 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ecc2756..5a967b9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,12 +12,14 @@ __! Be certain to carefully back up any critical user data before installing thi - 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 +- 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 +- Fix : The "Crescent Moon Style" technique rank from 4 to 2. +- Fix : Drop an advancement on a PC/NPC sheet now correctly add the bonus to the Actor (ex Air +1). - 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). +- Others minor optimizations (ex: 20q saving multiple item at once). ## 1.2.1 - Praised be Firefox - Fix dice swap on firefox that overflowed on the top and bottom of the RnK dialog diff --git a/system/scripts/actor.js b/system/scripts/actor.js index 546a0b1..30af8f5 100644 --- a/system/scripts/actor.js +++ b/system/scripts/actor.js @@ -108,4 +108,65 @@ export class ActorL5r5e extends Actor { } } } + + /** + * Add a Ring/Skill point to the current actor if the item is a advancement + * @param {Item} item + * @return {Promise} + */ + async addBonus(item) { + return this._updateActorFromAdvancement(item, true); + } + + /** + * Remove a Ring/Skill point to the current actor if the item is a advancement + * @param {Item} item + * @return {Promise} + */ + async removeBonus(item) { + return this._updateActorFromAdvancement(item, false); + } + + /** + * Alter Actor skill/ring from a advancement + * @param {Item} item + * @param {boolean} isAdd True=add, false=remove + * @return {Promise} + * @private + */ + async _updateActorFromAdvancement(item, isAdd) { + if (item && item.type === "advancement") { + const actor = foundry.utils.duplicate(this.data.data); + const itemData = item.data.data; + if (itemData.advancement_type === "ring") { + // Ring + if (isAdd) { + actor.rings[itemData.ring] = Math.min(9, actor.rings[itemData.ring] + 1); + } else { + actor.rings[itemData.ring] = Math.max(1, actor.rings[itemData.ring] - 1); + } + } else { + // Skill + const skillCatId = CONFIG.l5r5e.skills.get(itemData.skill); + if (skillCatId) { + if (isAdd) { + actor.skills[skillCatId][itemData.skill] = Math.min( + 9, + actor.skills[skillCatId][itemData.skill] + 1 + ); + } else { + actor.skills[skillCatId][itemData.skill] = Math.max( + 0, + actor.skills[skillCatId][itemData.skill] - 1 + ); + } + } + } + + // Update Actor + await this.update({ + data: foundry.utils.diffObject(this.data.data, actor), + }); + } + } } diff --git a/system/scripts/actors/base-sheet.js b/system/scripts/actors/base-sheet.js index adcdac7..1405ac1 100644 --- a/system/scripts/actors/base-sheet.js +++ b/system/scripts/actors/base-sheet.js @@ -58,9 +58,19 @@ export class BaseSheetL5r5e extends ActorSheet { // Add tech the character knows sheetData.items.forEach((item) => { - if (item.type === "technique") { - out[item.data.technique_type].push(item); - } + switch (item.type) { + case "technique": + out[item.data.technique_type].push(item); + break; + + case "title": + Array.from(item.data.items).forEach(([id, embedItem]) => { + if (embedItem.data.type === "technique") { + out[embedItem.data.data.technique_type].push(embedItem.data); + } + }); + break; + } //swi }); // Remove unused techs @@ -170,35 +180,44 @@ export class BaseSheetL5r5e extends ActorSheet { ) { return; } - item = item.toJSON(); // Dropped a item with same "id" as one owned, add qte instead - if (item.data.quantity && this.actor.data.items) { - const tmpItem = this.actor.data.items.find((e) => e.name === item.name && e.type === item.type); + if (item.data.data.quantity && this.actor.data.items) { + const tmpItem = this.actor.data.items.find((e) => e.name === item.data.name && e.type === item.data.type); if (tmpItem && this._modifyQuantity(tmpItem.id, 1)) { return; } } // Item subtype specific - switch (item.type) { + switch (item.data.type) { case "bond": // no break - case "advancement": // no break case "peculiarity": // no break case "item_pattern": // no break case "signature_scroll": // Modify the bought at rank to the current actor rank if (this.actor.data.data.identity?.school_rank) { - item.data.bought_at_rank = this.actor.data.data.identity.school_rank; + item.data.data.bought_at_rank = this.actor.data.data.identity.school_rank; } break; + case "advancement": + // Modify the bought at rank to the current actor rank + if (this.actor.data.data.identity?.school_rank) { + item.data.data.bought_at_rank = this.actor.data.data.identity.school_rank; + } + + // Specific advancements, remove 1 to selected ring/skill + await this.actor.addBonus(item); + break; + case "technique": // School_ability and mastery_ability, allow only 1 per type - if (CONFIG.l5r5e.techniques.get(item.data.technique_type)?.type === "school") { + if (CONFIG.l5r5e.techniques.get(item.data.data.technique_type)?.type === "school") { if ( Array.from(this.actor.items).some( - (e) => e.type === "technique" && e.data.data.technique_type === item.data.technique_type + (e) => + e.type === "technique" && e.data.data.technique_type === item.data.data.technique_type ) ) { ui.notifications.info(game.i18n.localize("l5r5e.techniques.only_one")); @@ -206,24 +225,25 @@ export class BaseSheetL5r5e extends ActorSheet { } // No cost for schools - item.data.xp_cost = 0; - item.data.xp_used = 0; - item.data.in_curriculum = true; + item.data.data.xp_cost = 0; + item.data.data.xp_used = 0; + item.data.data.in_curriculum = true; } else { // Check if technique is allowed for this character - if (!game.user.isGM && !this.actor.data.data.techniques[item.data.technique_type]) { + if (!game.user.isGM && !this.actor.data.data.techniques[item.data.data.technique_type]) { ui.notifications.info(game.i18n.localize("l5r5e.techniques.not_allowed")); return; } // Verify cost - item.data.xp_cost = item.data.xp_cost > 0 ? item.data.xp_cost : CONFIG.l5r5e.xp.techniqueCost; - item.data.xp_used = item.data.xp_cost; + item.data.data.xp_cost = + item.data.data.xp_cost > 0 ? item.data.data.xp_cost : CONFIG.l5r5e.xp.techniqueCost; + item.data.data.xp_used = item.data.data.xp_cost; } // Modify the bought at rank to the current actor rank if (this.actor.data.data.identity?.school_rank) { - item.data.bought_at_rank = this.actor.data.data.identity.school_rank; + item.data.data.bought_at_rank = this.actor.data.data.identity.school_rank; } break; } @@ -234,7 +254,7 @@ export class BaseSheetL5r5e extends ActorSheet { return; } - return this._onDropItemCreate(item); + return this._onDropItemCreate(item.data.toObject(false)); } /** @@ -395,12 +415,25 @@ export class BaseSheetL5r5e extends ActorSheet { event.preventDefault(); event.stopPropagation(); + let item; const itemId = $(event.currentTarget).data("item-id"); if (!itemId) { return; } - const item = this.actor.items.get(itemId); + const itemParentId = $(event.currentTarget).data("item-parent-id"); + if (itemParentId) { + // Embed Item + const parentItem = this.actor.items.get(itemParentId); + if (!parentItem) { + return; + } + item = parentItem.items.get(itemId); + } else { + // Regular item + item = this.actor.items.get(itemId); + } + if (!item) { return; } @@ -421,35 +454,20 @@ export class BaseSheetL5r5e extends ActorSheet { return; } - // Remove 1 qty if possible const tmpItem = this.actor.items.get(itemId); - if (tmpItem && tmpItem.data.data.quantity > 1 && this._modifyQuantity(tmpItem.id, -1)) { + if (!tmpItem) { + return; + } + + // Remove 1 qty if possible + if (tmpItem.data.data.quantity > 1 && this._modifyQuantity(tmpItem.id, -1)) { return; } const callback = async () => { // Specific advancements, remove 1 to selected ring/skill if (tmpItem.type === "advancement") { - const actor = duplicate(this.actor.data.data); - const itmData = tmpItem.data.data; - if (itmData.advancement_type === "ring") { - // Ring - actor.rings[itmData.ring] = Math.max(1, actor.rings[itmData.ring] - 1); - } else { - // Skill - const skillCatId = CONFIG.l5r5e.skills.get(itmData.skill); - if (skillCatId) { - actor.skills[skillCatId][itmData.skill] = Math.max( - 0, - actor.skills[skillCatId][itmData.skill] - 1 - ); - } - } - - // Update Actor - this.actor.update({ - data: diffObject(this.actor.data.data, actor), - }); + await this.actor.removeBonus(tmpItem); } return this.actor.deleteEmbeddedDocuments("Item", [itemId]); }; diff --git a/system/scripts/actors/character-sheet.js b/system/scripts/actors/character-sheet.js index 1e28ca1..42090a9 100644 --- a/system/scripts/actors/character-sheet.js +++ b/system/scripts/actors/character-sheet.js @@ -49,13 +49,10 @@ export class CharacterSheetL5r5e extends BaseSheetL5r5e { // Split Money sheetData.data.data.money = this._zeniToMoney(this.actor.data.data.zeni); - // Split school advancements by rank, and calculate xp spent + // Split school advancements by rank, and calculate xp spent and add it to total this._prepareSchoolAdvancement(sheetData); - // Titles - this._prepareTitles(sheetData); - - // Others + // Split Others advancements, and calculate xp spent and add it to total this._prepareOthersAdvancement(sheetData); // Total @@ -105,13 +102,6 @@ export class CharacterSheetL5r5e extends BaseSheetL5r5e { .activate("advancement_rank_" + (this.actor.data.data.identity.school_rank || 0)); } - /** - * Prepare Titles, and get xp spend - */ - _prepareTitles(sheetData) { - // TODO - } - /** * Split the school advancement, calculate the total xp spent and the current total xp spent by rank */ @@ -148,12 +138,23 @@ export class CharacterSheetL5r5e extends BaseSheetL5r5e { * Prepare Bonds, Item Pattern, Signature Scroll and get xp spend */ _prepareOthersAdvancement(sheetData) { + // Split OthersAdvancement from items sheetData.data.advancementsOthers = sheetData.items.filter((item) => ["bond", "item_pattern", "title", "signature_scroll"].includes(item.type) ); // Sort by rank desc - // sheetData.data.bondsList.sort((a, b) => (b.data.rank || 0) - (a.data.rank || 0)); + sheetData.data.advancementsOthers.sort((a, b) => (b.data.rank || 0) - (a.data.rank || 0)); + + // Total xp spent + sheetData.data.advancementsOthersTotalXp = sheetData.data.advancementsOthers.reduce( + (acc, item) => acc + (item.data.xp_used || 0), + 0 + ); + + // Update the total spent + sheetData.data.data.xp_spent = + parseInt(sheetData.data.data.xp_spent) + sheetData.data.advancementsOthersTotalXp; } /** @@ -177,6 +178,12 @@ export class CharacterSheetL5r5e extends BaseSheetL5r5e { return super._updateObject(event, formData); } + /** + * Convert a sum in Zeni to Zeni, Bu and Koku + * @param {number} zeni + * @return {{bu: number, koku: number, zeni: number}} + * @private + */ _zeniToMoney(zeni) { const money = { koku: 0, @@ -196,6 +203,14 @@ export class CharacterSheetL5r5e extends BaseSheetL5r5e { return money; } + /** + * Convert a sum in Zeni, Bu and Koku to Zeni + * @param {number} koku + * @param {number} bu + * @param {number} zeni + * @return {number} + * @private + */ _moneyToZeni(koku, bu, zeni) { return Math.floor(koku * CONFIG.l5r5e.money[0]) + Math.floor(bu * CONFIG.l5r5e.money[1]) + Math.floor(zeni); } diff --git a/system/scripts/actors/twenty-questions.js b/system/scripts/actors/twenty-questions.js index a5bce1d..612ad61 100644 --- a/system/scripts/actors/twenty-questions.js +++ b/system/scripts/actors/twenty-questions.js @@ -287,11 +287,14 @@ export class TwentyQuestions { // Clear and add items to actor const deleteIds = actor.data.items.map((e) => e.id); - await actor.deleteEmbeddedDocuments("Item", deleteIds); + if (deleteIds.length > 0) { + await actor.deleteEmbeddedDocuments("Item", deleteIds); + } // Add items in 20Q to actor - for (const types of Object.values(itemsCache)) { - for (const item of types) { + const newItemsData = []; + Object.values(itemsCache).forEach((types) => { + types.forEach((item) => { const itemData = foundry.utils.duplicate(item.data); if (itemData.data?.bought_at_rank) { itemData.data.bought_at_rank = 0; @@ -299,8 +302,11 @@ export class TwentyQuestions { if (itemData.data?.xp_spent) { itemData.data.xp_spent = 0; } - await actor.createEmbeddedDocuments("Item", [itemData]); - } + newItemsData.push(itemData); + }); + }); + if (newItemsData.length > 0) { + await actor.createEmbeddedDocuments("Item", newItemsData); } // Update actor diff --git a/system/scripts/config.js b/system/scripts/config.js index c8e3325..92f60f3 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/dietype/l5r-base-die.js b/system/scripts/dice/dietype/l5r-base-die.js index 1eed578..8077083 100644 --- a/system/scripts/dice/dietype/l5r-base-die.js +++ b/system/scripts/dice/dietype/l5r-base-die.js @@ -71,14 +71,14 @@ export class L5rBaseDie extends DiceTerm { * Evaluate the roll term, populating the results Array * @override */ - evaluate({ minimize = false, maximize = false } = {}) { + evaluate({ minimize = false, maximize = false, async = false } = {}) { if (this._evaluated) { throw new Error(`This ${this.constructor.name} has already been evaluated and is immutable`); } // Roll the initial number of dice for (let n = 1; n <= this.number; n++) { - this.roll({ minimize, maximize }); + this.roll({ minimize, maximize, async }); } // Apply modifiers diff --git a/system/scripts/dice/roll.js b/system/scripts/dice/roll.js index 822c9a1..8c741e4 100644 --- a/system/scripts/dice/roll.js +++ b/system/scripts/dice/roll.js @@ -294,14 +294,6 @@ export class RollL5r5e extends Roll { return renderTemplate(chatOptions.template, chatData); } - /** - * Render the HTML for the ChatMessage which should be added to the log - * @return {Promise} - */ - async getHTML() { - console.log(" --------- getHTML"); - } - /** * Transform a Roll instance into a ChatMessage, displaying the roll result. * This function can either create the ChatMessage directly, or return the data object that will be used to create. diff --git a/system/scripts/item.js b/system/scripts/item.js index 540dc4f..3967127 100644 --- a/system/scripts/item.js +++ b/system/scripts/item.js @@ -7,6 +7,14 @@ export class ItemL5r5e extends Item { return this.data.data.items || null; } + /** + * Return the linked Actor instance if any (current or embed) + * @return {Actor|null} + */ + get actor() { + return super.actor || game.actors.get(this.data.data.parent_id?.actor_id) || null; + } + /** * Create a new entity using provided input data * @override @@ -27,7 +35,7 @@ export class ItemL5r5e extends Item { */ async update(data = {}, context = {}) { // Regular - if (!this.data.data.parentId) { + if (!this.data.data.parent_id) { return super.update(data, context); } @@ -67,7 +75,7 @@ export class ItemL5r5e extends Item { this.data.data.items = new Map(); itemsData.forEach((item) => { - this.addEmbedItem(item, { save: false, newId: false }); + this.addEmbedItem(item, { save: false, newId: false, addBonusToActor: false }); }); } } @@ -75,10 +83,16 @@ export class ItemL5r5e extends Item { // ***** parent ids management ***** /** * Return a string with idemId + actorId if any - * @return {string} itemId|actor + * @return {{item_id: (string|null), actor_id?: (string|null)}} */ getParentsIds() { - return this.id + (this.actor?.data?._id ? `|${this.actor.data._id}` : ""); + const parent = { + item_id: this.id, + }; + if (this.actor?.data?._id) { + parent.actor_id = this.actor.data._id; + } + return parent; } /** @@ -86,17 +100,18 @@ export class ItemL5r5e extends Item { * @return {ItemL5r5e|null} */ getItemFromParentId() { + const parentIds = this.data.data.parent_id; let parentItem; - let [parentItemId, parentActorId] = this.data.data.parentId.split("|"); - if (parentActorId) { + if (parentIds?.actor_id) { // Actor item object - const parentActor = parentActorId ? game.actors.get(parentActorId) : null; - parentItem = parentActor?.items.get(parentItemId); - } else { + const parentActor = parentIds.actor_id ? game.actors.get(parentIds.actor_id) : null; + parentItem = parentActor?.items.get(parentIds.item_id); + } else if (parentIds?.item_id) { // World Object - parentItem = game.items.get(parentItemId); + parentItem = game.items.get(parentIds.item_id); } + return parentItem; } @@ -115,9 +130,10 @@ export class ItemL5r5e extends 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 + * @param {boolean} addBonusToActor if we update the actor bonus for advancements * @return {Promise} */ - async addEmbedItem(item, { save = true, newId = true } = {}) { + async addEmbedItem(item, { save = true, newId = true, addBonusToActor = true } = {}) { if (!item) { return; } @@ -133,14 +149,17 @@ export class ItemL5r5e extends Item { } // Tag parent (flags won't work as we have no id in db) - item.data.data.parentId = this.getParentsIds(); + item.data.data.parent_id = 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); + // Add bonus to actor + if (addBonusToActor) { + const actor = this.actor; + if (item instanceof Item && actor instanceof Actor) { + actor.addBonus(item); + } } if (save) { @@ -155,23 +174,28 @@ export class ItemL5r5e extends Item { * @return {Promise} */ async updateEmbedItem(item, { save = true } = {}) { - await this.addEmbedItem(item, { save, newId: false }); + await this.addEmbedItem(item, { save, newId: false, addBonusToActor: false }); } /** * Delete the Embed Item and clear the actor bonus if any - * @param {ItemL5r5e} item Object to add + * @param id Item id * @param {boolean} save if we save in db or not (used internally) + * @param {boolean} removeBonusFromActor if we update the actor bonus for advancements * @return {Promise} */ - async deleteEmbedItem(id, { save = true } = {}) { + async deleteEmbedItem(id, { save = true, removeBonusFromActor = 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 bonus from actor + if (removeBonusFromActor) { + const actor = this.actor; + const item = this.data.data.items.get(id); + if (item instanceof Item && actor instanceof Actor) { + actor.removeBonus(item); + } } // Remove the embed item diff --git a/system/scripts/items/advancement-sheet.js b/system/scripts/items/advancement-sheet.js index cfb3a3f..118a5d8 100644 --- a/system/scripts/items/advancement-sheet.js +++ b/system/scripts/items/advancement-sheet.js @@ -7,7 +7,7 @@ export class AdvancementSheetL5r5e extends ItemSheetL5r5e { /** * Sub Types of advancements */ - static types = ["ring", "skill"]; // "peculiarity" and "technique" have theirs own xp count + static types = ["ring", "skill"]; // others have theirs own xp count /** @override */ static get defaultOptions() { @@ -73,47 +73,55 @@ export class AdvancementSheetL5r5e extends ItemSheetL5r5e { // Modify image to reflect choice if (newChoice.ring) { + name = game.i18n.localize(`l5r5e.rings.${newChoice.ring}`) + "+1"; img = `systems/l5r5e/assets/icons/rings/${newChoice.ring}.svg`; } else if (newChoice.skill) { + name = + game.i18n.localize(`l5r5e.skills.${CONFIG.l5r5e.skills.get(newChoice.skill)}.${newChoice.skill}`) + + "+1"; img = `systems/l5r5e/assets/dices/default/skill_blank.svg`; } // Object embed in actor ? - if (this.actor) { - const actor = duplicate(this.actor.data.data); + const actor = this.document.actor; + if (actor) { + const actorData = foundry.utils.duplicate(actor.data.data); let skillCatId = null; // Old choices if (oldChoice.ring) { - actor.rings[oldChoice.ring] = Math.max(1, actor.rings[oldChoice.ring] - 1); + actorData.rings[oldChoice.ring] = Math.max(1, actorData.rings[oldChoice.ring] - 1); } if (oldChoice.skill) { skillCatId = CONFIG.l5r5e.skills.get(oldChoice.skill); - actor.skills[skillCatId][oldChoice.skill] = Math.max(0, actor.skills[skillCatId][oldChoice.skill] - 1); + actorData.skills[skillCatId][oldChoice.skill] = Math.max( + 0, + actorData.skills[skillCatId][oldChoice.skill] - 1 + ); } // new choices if (newChoice.ring) { - actor.rings[newChoice.ring] = actor.rings[newChoice.ring] + 1; - xp_used = actor.rings[newChoice.ring] * CONFIG.l5r5e.xp.ringCostMultiplier; + actorData.rings[newChoice.ring] = actorData.rings[newChoice.ring] + 1; + xp_used = actorData.rings[newChoice.ring] * CONFIG.l5r5e.xp.ringCostMultiplier; name = game.i18n.localize(`l5r5e.rings.${newChoice.ring}`) + - ` +1 (${actor.rings[newChoice.ring] - 1} -> ${actor.rings[newChoice.ring]})`; + ` +1 (${actorData.rings[newChoice.ring] - 1} -> ${actorData.rings[newChoice.ring]})`; } if (newChoice.skill) { skillCatId = CONFIG.l5r5e.skills.get(newChoice.skill); - actor.skills[skillCatId][newChoice.skill] = actor.skills[skillCatId][newChoice.skill] + 1; - xp_used = actor.skills[skillCatId][newChoice.skill] * CONFIG.l5r5e.xp.skillCostMultiplier; + actorData.skills[skillCatId][newChoice.skill] = actorData.skills[skillCatId][newChoice.skill] + 1; + xp_used = actorData.skills[skillCatId][newChoice.skill] * CONFIG.l5r5e.xp.skillCostMultiplier; name = game.i18n.localize(`l5r5e.skills.${skillCatId}.${newChoice.skill}`) + - ` +1 (${actor.skills[skillCatId][newChoice.skill] - 1} -> ${ - actor.skills[skillCatId][newChoice.skill] + ` +1 (${actorData.skills[skillCatId][newChoice.skill] - 1} -> ${ + actorData.skills[skillCatId][newChoice.skill] })`; } // Update Actor - await this.actor.update({ - data: diffObject(this.actor.data.data, actor), + await actor.update({ + data: foundry.utils.diffObject(actor.data.data, actorData), }); } diff --git a/system/scripts/items/item-pattern-sheet.js b/system/scripts/items/item-pattern-sheet.js index cb51069..1cc35bd 100644 --- a/system/scripts/items/item-pattern-sheet.js +++ b/system/scripts/items/item-pattern-sheet.js @@ -21,9 +21,6 @@ export class ItemPatternSheetL5r5e extends ItemSheetL5r5e { 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); diff --git a/system/scripts/items/item-sheet.js b/system/scripts/items/item-sheet.js index 43b534c..391d027 100644 --- a/system/scripts/items/item-sheet.js +++ b/system/scripts/items/item-sheet.js @@ -256,6 +256,23 @@ export class ItemSheetL5r5e extends ItemSheet { event.preventDefault(); event.stopPropagation(); const itemId = $(event.currentTarget).data("item-id"); - this.document.deleteEmbedItem(itemId); + const item = this.document.getEmbedItem(itemId); + if (!item) { + return; + } + + const callback = async () => { + this.document.deleteEmbedItem(itemId); + }; + + // Holing Ctrl = without confirm + if (event.ctrlKey) { + return callback(); + } + + game.l5r5e.HelpersL5r5e.confirmDeleteDialog( + game.i18n.format("l5r5e.global.delete_confirm", { name: item.name }), + callback + ); } } diff --git a/system/scripts/items/title-sheet.js b/system/scripts/items/title-sheet.js index ac5223d..4b86fd8 100644 --- a/system/scripts/items/title-sheet.js +++ b/system/scripts/items/title-sheet.js @@ -21,14 +21,15 @@ export class TitleSheetL5r5e extends ItemSheetL5r5e { 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 + // Automatically compute the xp cost + sheetData.data.data.xp_used = sheetData.data.embedItemsList.reduce( + (acc, item) => acc + (+item.data.xp_used || 0), + 0 + ); + return sheetData; } @@ -63,17 +64,19 @@ export class TitleSheetL5r5e extends ItemSheetL5r5e { // Check item type and subtype let item = await game.l5r5e.HelpersL5r5e.getDragnDropTargetObject(event); - if (!item || (item.documentName !== "Item" && !["technique", "advancement"].includes(item.data.type))) { + if (!item || item.documentName !== "Item" || !["technique", "advancement"].includes(item.data.type)) { return; } - const data = item.data.toJSON(); + const data = item.data.toObject(false); - console.log("------ data", data); // todo tmp + // Check xp for techs + if (item.data.type === "technique") { + data.data.xp_cost = data.data.xp_cost > 0 ? data.data.xp_cost : CONFIG.l5r5e.xp.techniqueCost; + data.data.xp_used = data.data.xp_cost; + } this.document.addEmbedItem(data); - - console.log(this.document); // todo tmp } /** @@ -93,16 +96,4 @@ export class TitleSheetL5r5e extends ItemSheetL5r5e { 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 5ee1eb1..3451cd3 100644 --- a/system/scripts/migration.js +++ b/system/scripts/migration.js @@ -3,10 +3,10 @@ */ export class MigrationL5r5e { /** - * Version needed for migration stuff to trigger + * Minimum Version needed for migration stuff to trigger * @type {string} */ - static NEEDED_VERSION = "1.1.0"; + static NEEDED_VERSION = "1.3.0"; /** * Return true if the current world need some updates @@ -14,7 +14,7 @@ export class MigrationL5r5e { */ static needUpdate() { const currentVersion = game.settings.get("l5r5e", "systemMigrationVersion"); - return currentVersion && isNewerVersion(MigrationL5r5e.NEEDED_VERSION, currentVersion); + return currentVersion && foundry.utils.isNewerVersion(MigrationL5r5e.NEEDED_VERSION, currentVersion); } /** @@ -26,6 +26,7 @@ export class MigrationL5r5e { return; } + // Warn the users ui.notifications.info( `Applying L5R5e System Migration for version ${game.system.data.version}.` + ` Please be patient and do not close your game or shut down your server.`, @@ -33,56 +34,53 @@ export class MigrationL5r5e { ); // Migrate World Actors - for (let a of game.actors.contents) { + for (let actor of game.actors.contents) { try { - const updateData = MigrationL5r5e._migrateActorData(a.data); - if (!isObjectEmpty(updateData)) { - console.log(`Migrating Actor entity ${a.name}`); - await a.update(updateData, { enforceTypes: false }); // TODO use Actor.updateDocuments(data, context) for multiple actors + const updateData = MigrationL5r5e._migrateActorData(actor.data); + if (!foundry.utils.isObjectEmpty(updateData)) { + console.log(`L5R5E | Migrating Actor entity ${actor.name}`); + await actor.update(updateData); } } catch (err) { - err.message = `Failed L5R5e system migration for Actor ${a.name}: ${err.message}`; + err.message = `L5R5E | Failed L5R5e system migration for Actor ${actor.name}: ${err.message}`; console.error(err); } } // Migrate World Items - for (let i of game.items.contents) { + for (let item of game.items.contents) { try { - const updateData = MigrationL5r5e._migrateItemData(i.data); - if (!isObjectEmpty(updateData)) { - console.log(`Migrating Item entity ${i.name}`); - await i.update(updateData, { enforceTypes: false }); // TODO use Item.updateDocuments(data, context) for multiple actors + const updateData = MigrationL5r5e._migrateItemData(item.data); + if (!foundry.utils.isObjectEmpty(updateData)) { + console.log(`L5R5E | Migrating Item entity ${item.name}`); + await item.update(updateData); } } catch (err) { - err.message = `Failed L5R5e system migration for Item ${i.name}: ${err.message}`; + err.message = `L5R5E | Failed L5R5e system migration for Item ${item.name}: ${err.message}`; console.error(err); } } // Migrate Actor Override Tokens - for (let s of game.scenes.contents) { + for (let scene of game.scenes.contents) { try { - const updateData = MigrationL5r5e._migrateSceneData(s.data); - if (!isObjectEmpty(updateData)) { - console.log(`Migrating Scene entity ${s.name}`); - await s.update(updateData, { enforceTypes: false }); // TODO use Scene.updateDocuments(data, context) for multiple actors + const updateData = MigrationL5r5e._migrateSceneData(scene.data); + if (!foundry.utils.isObjectEmpty(updateData)) { + console.log(`L5R5E | Migrating Scene entity ${scene.name}`); + await scene.update(updateData); } } catch (err) { - err.message = `Failed L5R5e system migration for Scene ${s.name}: ${err.message}`; + err.message = `L5R5E | Failed L5R5e system migration for Scene ${scene.name}: ${err.message}`; console.error(err); } } // Migrate World Compendium Packs - for (let p of game.packs) { - if (p.metadata.package !== "world") { + for (let pack of game.packs) { + if (pack.metadata.package !== "world" || !["Actor", "Item", "Scene"].includes(pack.metadata.entity)) { continue; } - if (!["Actor", "Item", "Scene"].includes(p.metadata.entity)) { - continue; - } - await MigrationL5r5e._migrateCompendium(p); + await MigrationL5r5e._migrateCompendium(pack); } // Set the migration as complete @@ -94,7 +92,7 @@ export class MigrationL5r5e { /** * Apply migration rules to all Entities within a single Compendium pack - * @param pack + * @param {Compendium} pack * @return {Promise} */ static async _migrateCompendium(pack) { @@ -103,18 +101,20 @@ export class MigrationL5r5e { return; } - // Unlock the pack for editing const wasLocked = pack.locked; - await pack.configure({ locked: false }); + try { + // Unlock the pack for editing + await pack.configure({ locked: false }); - // Begin by requesting server-side data model migration and get the migrated content - await pack.migrate(); - const content = await pack.getContent(); + // Begin by requesting server-side data model migration and get the migrated content + await pack.migrate(); + const documents = await pack.getDocuments(); + + // Iterate over compendium entries - applying fine-tuned migration functions + const updateDatasList = []; + for (let ent of documents) { + let updateData = {}; - // Iterate over compendium entries - applying fine-tuned migration functions - for (let ent of content) { - let updateData = {}; - try { switch (entity) { case "Actor": updateData = MigrationL5r5e._migrateActorData(ent.data); @@ -126,24 +126,30 @@ export class MigrationL5r5e { updateData = MigrationL5r5e._migrateSceneData(ent.data); break; } - if (isObjectEmpty(updateData)) { + if (foundry.utils.isObjectEmpty(updateData)) { continue; } - // Save the entry, if data was changed - updateData["_id"] = ent._id; - 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 - err.message = `Failed L5R5e system migration for entity ${ent.name} in pack ${pack.collection}: ${err.message}`; - console.error(err); + // Add the entry, if data was changed + updateData["_id"] = ent.data._id; + updateDatasList.push(updateData); + + console.log(`L5R5E | Migrating ${entity} entity ${ent.name} in Compendium ${pack.collection}`); } + + // Save the modified entries + if (updateDatasList.length > 0) { + await pack.documentClass.updateDocuments(updateDatasList, { pack: pack.collection }); + } + } catch (err) { + // Handle migration failures + err.message = `L5R5E | Failed system migration for entities ${entity} in pack ${pack.collection}: ${err.message}`; + console.error(err); } // Apply the original locked status for the pack pack.configure({ locked: wasLocked }); - console.log(`Migrated all ${entity} contents from Compendium ${pack.collection}`); + console.log(`L5R5E | Migrated all ${entity} contents from Compendium ${pack.collection}`); } /** @@ -153,7 +159,7 @@ export class MigrationL5r5e { * @return {Object} The updateData to apply */ static _migrateSceneData(scene) { - const tokens = duplicate(scene.tokens); + const tokens = foundry.utils.duplicate(scene.tokens); return { tokens: tokens.map((t) => { if (!t.actorId || t.actorLink || !t.actorData.data) { @@ -183,8 +189,6 @@ export class MigrationL5r5e { const updateData = {}; const actorData = actor.data; - console.log(actorData); // TODO TMP data.data ? à vérifier - // ***** Start of 1.1.0 ***** // Add "Prepared" in actor if (actorData.prepared === undefined) { @@ -211,6 +215,10 @@ export class MigrationL5r5e { actorData.rings_affinities.strength.value; updateData["data.rings_affinities." + actorData.rings_affinities.weakness.ring] = actorData.rings_affinities.weakness.value; + + // Delete old keys : not working :'( + updateData["-=data.rings_affinities.strength"] = null; + updateData["-=data.rings_affinities.weakness"] = null; } // ***** End of 1.3.0 ***** @@ -224,7 +232,7 @@ export class MigrationL5r5e { */ static cleanActorData(actorData) { const model = game.system.model.Actor[actorData.type]; - actorData.data = filterObject(actorData.data, model); + actorData.data = foundry.utils.filterObject(actorData.data, model); return actorData; } diff --git a/system/system.json b/system/system.json index 0b7cd49..8302445 100644 --- a/system/system.json +++ b/system/system.json @@ -6,8 +6,8 @@ "manifest": "https://gitlab.com/teaml5r/l5r5e/-/raw/master/system/system.json", "download": "https://gitlab.com/teaml5r/l5r5e/-/jobs/artifacts/v1.3.0/raw/l5r5e.zip?job=build", "version": "1.3.0", - "minimumCoreVersion": "0.8.2", - "compatibleCoreVersion": "0.8.2", + "minimumCoreVersion": "0.8.5", + "compatibleCoreVersion": "0.8.5", "manifestPlusVersion": "1.0.0", "socket": true, "author": "Team L5R", @@ -22,12 +22,6 @@ }, { "name": "Carter" - }, - { - "name": "Hrunh" - }, - { - "name": "Sasmira" } ], "background": "L5R-Header.webp", diff --git a/system/templates/actors/character-sheet.html b/system/templates/actors/character-sheet.html index 2c402be..75a0bb0 100644 --- a/system/templates/actors/character-sheet.html +++ b/system/templates/actors/character-sheet.html @@ -1,9 +1,9 @@
{{!-- Sheet Header --}}
- +
-

+

{{> 'systems/l5r5e/templates/actors/character/identity.html'}}
@@ -28,7 +28,7 @@ {{!-- Skills Tab --}}
    - {{#each actor.data.data.skills as |category id|}} + {{#each data.data.skills as |category id|}} {{> 'systems/l5r5e/templates/actors/character/category.html' category=category categoryId=id}} {{/each}}
diff --git a/system/templates/actors/character/advancement-others.html b/system/templates/actors/character/advancement-others.html index 1d9c28a..062b24e 100644 --- a/system/templates/actors/character/advancement-others.html +++ b/system/templates/actors/character/advancement-others.html @@ -1,5 +1,5 @@ - {{advancement.name}}{{#if advancement.data.bond_type}} ({{advancement.data.bond_type}}){{/if}} + {{advancement.name}}{{#if advancement.data.bond_type}} ({{advancement.data.bond_type}}){{/if}} {{advancement.data.xp_used}} {{advancement.data.rank}} {{#if editable}} diff --git a/system/templates/actors/character/category.html b/system/templates/actors/character/category.html index a98dad1..249c4e0 100644 --- a/system/templates/actors/character/category.html +++ b/system/templates/actors/character/category.html @@ -1,15 +1,15 @@
  • -

    {{ localizeSkill categoryId 'title' }}

    +

    {{localizeSkill categoryId 'title'}}

      - {{#each category as |skill id| }} - {{> 'systems/l5r5e/templates/actors/character/skill.html' categoryId=../categoryId skill=skill skillId=id }} + {{#each category as |skill id|}} + {{> 'systems/l5r5e/templates/actors/character/skill.html' categoryId=../categoryId skill=skill skillId=id}} {{/each}}
      -
    • {{ localizeSkill categoryId 'air' }}
    • -
    • {{ localizeSkill categoryId 'earth' }}
    • -
    • {{ localizeSkill categoryId 'fire' }}
    • -
    • {{ localizeSkill categoryId 'water' }}
    • -
    • {{ localizeSkill categoryId 'void' }}
    • +
    • {{localizeSkill categoryId 'air'}}
    • +
    • {{localizeSkill categoryId 'earth'}}
    • +
    • {{localizeSkill categoryId 'fire'}}
    • +
    • {{localizeSkill categoryId 'water'}}
    • +
    • {{localizeSkill categoryId 'void'}}
  • \ No newline at end of file diff --git a/system/templates/actors/character/conflict.html b/system/templates/actors/character/conflict.html index 259a348..1085ad8 100644 --- a/system/templates/actors/character/conflict.html +++ b/system/templates/actors/character/conflict.html @@ -1,8 +1,8 @@
    {{localize 'l5r5e.conflict.initiative.title'}} - - + + diff --git a/system/templates/actors/character/experience.html b/system/templates/actors/character/experience.html index f29ae31..85f7b7e 100644 --- a/system/templates/actors/character/experience.html +++ b/system/templates/actors/character/experience.html @@ -70,7 +70,7 @@ {{localize 'l5r5e.name'}} {{localize 'l5r5e.advancements.spent'}} {{localize 'l5r5e.rank'}} - {{#if editable}} + {{#if options.editable}} {{/if}} @@ -80,5 +80,12 @@ {{> 'systems/l5r5e/templates/actors/character/advancement-others.html' advancement=advancement editable=../options.editable}} {{/each}} + + + + {{localize 'l5r5e.advancements.total_xp_rank'}} : {{data.advancementsOthersTotalXp}} + + +
    \ No newline at end of file diff --git a/system/templates/actors/limited-sheet.html b/system/templates/actors/limited-sheet.html index 2188dca..37a484c 100644 --- a/system/templates/actors/limited-sheet.html +++ b/system/templates/actors/limited-sheet.html @@ -1,8 +1,8 @@ - + {{!-- Sheet Header --}}
    -

    +

    {{!-- Sheet identity --}}
    • diff --git a/system/templates/actors/npc-sheet.html b/system/templates/actors/npc-sheet.html index 89fdcb4..4c2de75 100644 --- a/system/templates/actors/npc-sheet.html +++ b/system/templates/actors/npc-sheet.html @@ -2,8 +2,8 @@ {{!-- Sheet Header --}}
      - -

      + +

      {{> 'systems/l5r5e/templates/actors/npc/identity.html'}}
      diff --git a/system/templates/items/advancement/advancement-sheet.html b/system/templates/items/advancement/advancement-sheet.html index 8d0a798..dc8db51 100644 --- a/system/templates/items/advancement/advancement-sheet.html +++ b/system/templates/items/advancement/advancement-sheet.html @@ -1,23 +1,23 @@
      - -

      + +

      {{!-- Sheet Body --}}
      {{!-- Attributes Tab --}}
      - {{#ifCond document.data.data.advancement_type '==' 'ring' }} + {{#ifCond data.data.advancement_type '==' 'ring' }} {{/ifCond}} - {{#ifCond document.data.data.advancement_type '==' 'skill' }} + {{#ifCond data.data.advancement_type '==' 'skill' }} {{/ifCond}}
    {{#if technique.data.description}} diff --git a/system/templates/items/technique/technique-sheet.html b/system/templates/items/technique/technique-sheet.html index 01effa3..56e1920 100644 --- a/system/templates/items/technique/technique-sheet.html +++ b/system/templates/items/technique/technique-sheet.html @@ -1,45 +1,45 @@
    - -

    + +

    {{!-- Sheet Body --}}
    {{!-- Attributes Tab --}}
    {{> '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 04a0074..a0da636 100644 --- a/system/templates/items/title/title-sheet.html +++ b/system/templates/items/title/title-sheet.html @@ -1,7 +1,7 @@
    - -

    + +

    {{!-- Sheet Navigation --}}