diff --git a/README.md b/README.md index 0692cbb..cc09d06 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,6 @@ +## No Longer Being Maintained +Due to time constraints, I'm no longer able to keep updating this module with any amount of frequency. You're welcome to keep using it; there's a decent chance that it'll get updated from time to time because my group uses it, but I won't be keeping up with feature requests or non-critical bug reports at the level that'd be expected for something actively being maintained. Thanks for sticking with me on this wild ride, it's been a lot of fun! + ![Header Image](https://raw.githubusercontent.com/crash1115/5e-training/master/media/cover.png) ![GitHub All Releases](https://img.shields.io/github/downloads/crash1115/5e-training/total) ![GitHub Releases](https://img.shields.io/github/downloads/crash1115/5e-training/latest/total) ![GitHub release (latest by date)](https://img.shields.io/github/v/release/crash1115/5e-training?label=latest%20version) @@ -10,7 +13,7 @@ Tracking & Training is a module for the dnd5e system in Foundry VTT that adds a Check out the [wiki](https://github.com/crash1115/5e-training/wiki) for instructions, screenshots, sample macros, compatibility info, API documentation, and more! ## Got Questions? Find a Bug? -Contact me on Discord (CRASH1115#2944) to chat, or create an issue right here on GitHub. +Create an issue right here on GitHub. If it's a critical/breaking bug, I'll try to fix it when I can. Feature requests likely won't be worked on unless I can find a use case for them with my groups that use this. ## Attributions and Special Thanks - Thanks to KLO#1490 for Korean translations diff --git a/changelog.md b/changelog.md index 6dd9da7..0093c8c 100644 --- a/changelog.md +++ b/changelog.md @@ -1,3 +1,8 @@ +# Version 1.0.0 (Foundry v10) +- Compatibility update for Foundry v10 +- Importing has been streamlined and now also handles categories +- As of this release I will no longer be officially maintaing this module. You're welcome to keep submitting bug reports, and I'll try to fix issues if/when I can, but feature requests that haven't been gotten to by now will likely never get done. Thanks all for sticking with me this far, really appreciate all the support. + # Version 0.6.9 - Restrict module to dnd5e system (credit MrVauxs) - Fix compatibility issues with MidiQOL and BetterRolls (credit tposney) diff --git a/lang/en.json b/lang/en.json index 087a034..e05a871 100644 --- a/lang/en.json +++ b/lang/en.json @@ -25,8 +25,7 @@ "C5ETRAINING.AdjustProgressValue": "Adjust Progress", "C5ETRAINING.Roll": "Roll", "C5ETRAINING.ToolNotFoundWarning": "Selected tool not found. Please edit the item and select a new one.", - - "C5ETRAINING.ProgressionStyleFixed": "Fixed Value", + "C5ETRAINING.MacroNotFoundWarning": "Couldn't find macro with name", "C5ETRAINING.CompletedATrackedItem": "completed a tracked item.", @@ -150,7 +149,6 @@ "C5ETRAINING.ProgressionStyleAbilityCheck": "Ability Check", "C5ETRAINING.ProgressionStyleSkillCheck": "Skill Check", "C5ETRAINING.ProgressionStyleToolCheck": "Tool Check", - "C5ETRAINING.ProgressionStyleMacro": "Macro", "C5ETRAINING.ProgressionStyleFixedHeader": "Details - Fixed Value", "C5ETRAINING.ProgressionStyleAbilityCheckHeader": "Details - Ability Check", @@ -176,11 +174,9 @@ "C5ETRAINING.CheckTypeColumnHeader": "Type", "C5ETRAINING.ProgressColumnHeader": "Progress", "C5ETRAINING.ToggleInfo": "Toggle Info", - "C5ETRAINING.AdjustProgressValue": "Adjust Progress", "C5ETRAINING.EditItem": "Edit Item", "C5ETRAINING.EditCategory": "Edit Category", - "C5ETRAINING.DeleteCategory": "Delete Category", "C5ETRAINING.None": "None", "C5ETRAINING.Delete": "Delete", @@ -199,8 +195,8 @@ "C5ETRAINING.ExportTrackedItemsTooltip": "Export tracked items to a file", "C5ETRAINING.ImportTrackedItemsTooltip": "Import tracked items from a file", - "C5ETRAINING.ExportNoTrackedItems": "No items to export. Cancelling export.", - "C5ETRAINING.ImportNoTrackedItems": "No items to import. Cancelling import.", + "C5ETRAINING.ExportNoTrackedItems": "No items or categories to export. Cancelling export.", + "C5ETRAINING.ImportNoTrackedItems": "No items or categories to import. Cancelling import.", "C5ETRAINING.ImportNoFile": "No file selected. Cancelling import.", "C5ETRAINING.ImportTypeSelectionTitle": "How should we import?", @@ -209,9 +205,9 @@ "C5ETRAINING.ImportTypeSelectionOverwrite": "Replace", "C5ETRAINING.ImportTypeSelectionCombine": "Combine", - "C5ETRAINING.ImportDupeWarningTitle": "Possible Duplicate Items", - "C5ETRAINING.ImportDupeWarningText": "We found some things during the import that we thought might be duplicate items. Instead of just deleting whichever one we felt like, we kept them both so you can figure out which to keep.", - "C5ETRAINING.ImportDupeWarningConfirm": "Got it, thanks!", + "C5ETRAINING.ImportOldWarningTitle": "Could Not Import Categories", + "C5ETRAINING.ImportOldWarningText": "The data you imported is from an older version of the module that didn't support the export of category data along with items. We imported it for you anyway, but all items have been placed into the Uncategorized category.", + "C5ETRAINING.ImportOldWarningConfirm": "Got it, thanks!", "C5ETRAINING.ImportComplete": "Import Complete", diff --git a/module.json b/module.json index b525911..f2fb7ba 100644 --- a/module.json +++ b/module.json @@ -1,16 +1,10 @@ { - "name": "5e-training", "title": "Crash's Tracking & Training (5e)", "description": "Adds a tab to the character sheet to keep track of downtime activities, training, spell scribing, faction reputation, quest progress, and anything else you can think of.", - "author": "CRASH1115#2944", "url": "https://github.com/crash1115/5e-training", "readme": "https://github.com/crash1115/5e-training/blob/master/README.md", "bugs": "https://github.com/crash1115/5e-training/issues", - "version": "0.6.9", - "system": ["dnd5e"], - "minimumCoreVersion": "0.8.2", - "compatibleCoreVersion": "9", - "minimumSystemVersion": "1.3.0", + "version": "1.0.0", "esmodules": [ "scripts/crash-5e-training.js" ], @@ -24,19 +18,45 @@ { "lang": "en", "name": "English", - "path": "lang/en.json" + "path": "lang/en.json", + "flags": {} }, { "lang": "ko", "name": "한국어", - "path": "lang/ko.json" + "path": "lang/ko.json", + "flags": {} }, { "lang": "es", "name": "Español", - "path": "lang/es.json" + "path": "lang/es.json", + "flags": {} } ], "manifest": "https://raw.githubusercontent.com/crash1115/5e-training/master/module.json", - "download": "https://github.com/crash1115/5e-training/releases/download/v0.6.9/5e-training.zip" + "download": "https://github.com/crash1115/5e-training/releases/download/v1.0.0/5e-training.zip", + "id": "5e-training", + "authors": [ + { + "name": "CRASH1115#2944", + "flags": {} + } + ], + "compatibility": { + "minimum": "10", + "verified": "10" + }, + "relationships": { + "systems": [{ + "id": "dnd5e", + "type": "system", + "manifest": "https://raw.githubusercontent.com/foundryvtt/dnd5e/master/system.json", + "compatibility": { + "minimum": "2.0.3", + "verified": "2.0.3" + } + }] + } } + diff --git a/scripts/CrashTrackingAndTraining.js b/scripts/CrashTrackingAndTraining.js index d9e1436..6e5aea1 100644 --- a/scripts/CrashTrackingAndTraining.js +++ b/scripts/CrashTrackingAndTraining.js @@ -192,7 +192,7 @@ export default class CrashTrackingAndTraining { // Progression Type: Ability Check or DC - SKILL else if (rollType === "SKILL"){ - let abilityName = CONFIG.DND5E.skills[thisItem.skill]; + let abilityName = CONFIG.DND5E.skills[thisItem.skill].label; // Roll to increase progress let options = CrashTrackingAndTraining.getRollOptions(); let r = await actor.rollSkill(thisItem.skill, options); @@ -398,11 +398,11 @@ export default class CrashTrackingAndTraining { // Gets and formats an array of tools the actor has in their inventory. Used for selection menus static getActorTools(actorId){ let actor = game.actors.get(actorId); - let items = actor.data.items; + let items = actor.items; let tools = items.filter(item => item.type === "tool"); let formatted = tools.map(obj => { let newObj = {}; - newObj.value = obj.data._id; + newObj.value = obj._id; newObj.label = obj.name; return newObj; }); @@ -446,13 +446,17 @@ export default class CrashTrackingAndTraining { static exportItems(actorId){ let actor = game.actors.get(actorId); let allItems = actor.getFlag("5e-training","trainingItems") || []; - if(allItems.length < 1){ + let allCategories = actor.getFlag("5e-training","categories") || []; + let dataToExport = { + items: allItems, + categories: allCategories + } + if(allItems.length < 1 && allCategories.length < 1){ ui.notifications.info(game.i18n.localize("C5ETRAINING.ExportNoTrackedItems")); return; } - let jsonData = JSON.stringify(allItems); - saveDataToFile(jsonData, 'application/json', `tracked-items-backup.json`); - ui.notifications.info(game.i18n.localize("C5ETRAINING.ExportComplete")); + let jsonData = JSON.stringify(dataToExport); + saveDataToFile(jsonData, 'application/json', `${actor.id}-tracked-items-backup.json`); } static async importItems(actorId){ @@ -465,81 +469,164 @@ export default class CrashTrackingAndTraining { return; } readTextFromFile(file).then(async contents => { - let importedItems = JSON.parse(contents); - if(importedItems.length < 1){ + let importedData = JSON.parse(contents); + let importedItems = []; + let importedCategories = []; + + // Handles old format of export/import + if(importedData.items){ + importedItems = importedData.items; + importedCategories = importedData.categories; + } else { + new Dialog({ + title: game.i18n.localize("C5ETRAINING.ImportOldWarningTitle"), + content: `

${game.i18n.localize("C5ETRAINING.ImportOldWarningText")}

`, + buttons: { + ok: {icon: "", label: game.i18n.localize("C5ETRAINING.ImportOldWarningConfirm")}, + }, + default: "ok" + }).render(true); + console.log("Crash's Tracking & Training (5e) | Import detected old format.", importedData); + importedItems = importedData; + } + + if(importedItems.length < 1 && importedCategories.legnth < 1){ ui.notifications.info(game.i18n.localize("C5ETRAINING.ImportNoTrackedItems")); return; } - let act = "quit"; - let content = `

${game.i18n.localize("C5ETRAINING.ImportTypeSelectionOverwrite")}: ${game.i18n.localize("C5ETRAINING.ImportTypeSelectionTextOverwrite")}

-

${game.i18n.localize("C5ETRAINING.ImportTypeSelectionCombine")}: ${game.i18n.localize("C5ETRAINING.ImportTypeSelectionTextCombine")}

`; - // Create dialog - new Dialog({ - title: game.i18n.localize("C5ETRAINING.ImportTypeSelectionTitle"), - content: content, - buttons: { - overwrite: {icon: "", label: game.i18n.localize("C5ETRAINING.ImportTypeSelectionOverwrite"), callback: () => act = "overwrite"}, - add: {icon: "", label: game.i18n.localize("C5ETRAINING.ImportTypeSelectionCombine"), callback: () => act = "add"}, - quit: {icon: "", label: game.i18n.localize("C5ETRAINING.Cancel"), callback: () => act = "quit"}, - }, - default: "quit", - close: async (html) => { - if(act === "quit"){ - return; - } else if (act === "overwrite") { - let currentCategories = actor.getFlag("5e-training","categories") || []; - let currentCategoryIds = currentCategories.map(c => c.id); - - for(var i = 0; i < importedItems.length; i++){ - // Unset missing category ID's - if((currentCategoryIds.length === 0) || (currentCategoryIds.indexOf(importedItems[i].category) === -1)){ - importedItems[i].category = ""; - } - } - actor.setFlag("5e-training","trainingItems",importedItems); - await ui.notifications.info(game.i18n.localize("C5ETRAINING.ImportComplete")); - } else if (act === "add") { - let currentItems = actor.getFlag("5e-training","trainingItems") || []; - let currentCategories = actor.getFlag("5e-training","categories") || []; - let currentIds = currentItems.map(i => i.id); - let currentNames = currentItems.map(i => i.name); - let currentCategoryIds = currentCategories.map(c => c.id); - let possibleDupes = false; - - for(var i = 0; i < importedItems.length; i++){ - // De-dupe ID's - if(currentIds.indexOf(importedItems[i].id) > -1){ - let matchedIdx = currentIds.indexOf(importedItems[i].id); - importedItems[i].id = randomID(); - possibleDupes = true; - } - - // Check for duplicate names - if(currentNames.indexOf(importedItems[i].name) > -1){ - possibleDupes = true; - } - - // Unset missing category ID's - if((currentCategoryIds.length === 0) || (currentCategoryIds.indexOf(importedItems[i].category) === -1)){ - importedItems[i].category = ""; - } + + let currentCategories = actor.getFlag("5e-training","categories") || []; + let currentCategoryIds = currentCategories.map(c => c.id); + let currentCategoryNames = currentCategories.map(c => c.name); + let categoriesToDelete = []; + + for(var i=0; i < importedCategories.length; i++){ + // De-dupe category ID's + if(currentCategoryIds.indexOf(importedCategories[i].id) > -1){ + let newId = randomID() + let oldId = importedCategories[i].id; + for(var j = 0; j < importedItems.length; j++){ + if(importedItems[j].category === oldId){ + importedItems[j].category = newId; } - let combinedItems = currentItems.concat(importedItems); - await actor.setFlag("5e-training","trainingItems", combinedItems); - ui.notifications.info(game.i18n.localize("C5ETRAINING.ImportComplete")); - if(possibleDupes){ - new Dialog({ - title: game.i18n.localize("C5ETRAINING.ImportDupeWarningTitle"), - content: `

${game.i18n.localize("C5ETRAINING.ImportDupeWarningText")}

`, - buttons: { - ok: {icon: "", label: game.i18n.localize("C5ETRAINING.ImportDupeWarningConfirm")}, - }, - default: "ok" - }).render(true); + } + importedCategories[i].id = newId; + } + + // If category exists with same name, combine into one category + if(currentCategoryNames.indexOf(importedCategories[i].name) > -1){ + let newCategory = importedCategories[i]; + let existingCategory = currentCategories.filter(obj => obj.name === importedCategories[i].name)[0]; + let existingCategoryId = existingCategory.id; + let importedCategoryId = newCategory.id; + + for(var j = 0; j < importedItems.length; j++){ + if(importedItems[j].category === importedCategoryId){ + importedItems[j].category = existingCategoryId; } } + // Flag these categories for deletion + categoriesToDelete.push(importedCategoryId); } - }).render(true); + + } + + let currentItems = actor.getFlag("5e-training","trainingItems") || []; + let currentIds = currentItems.map(i => i.id); + let currentNames = currentItems.map(i => i.name); + + for(var i = 0; i < importedItems.length; i++){ + // De-dupe item ID's + if(currentIds.indexOf(importedItems[i].id) > -1){ + importedItems[i].id = randomID(); + } + + // Unset missing category ID's + let importedCategoryIds = importedCategories.map(c => c.id); + let availableCategoryIds = currentCategoryIds.concat(importedCategoryIds) + if((availableCategoryIds.length === 0) || (availableCategoryIds.indexOf(importedItems[i].category) === -1)){ + importedItems[i].category = ""; + } + } + + let combinedItems = currentItems.concat(importedItems); + let combinedCategories = currentCategories.concat(importedCategories).filter(c => !categoriesToDelete.includes(c.id)); + + await actor.setFlag("5e-training","categories", combinedCategories); + await actor.setFlag("5e-training","trainingItems", combinedItems); + + ui.notifications.info(game.i18n.localize("C5ETRAINING.ImportComplete")); + + // let act = "quit"; + //let content = `

${game.i18n.localize("C5ETRAINING.ImportTypeSelectionOverwrite")}: ${game.i18n.localize("C5ETRAINING.ImportTypeSelectionTextOverwrite")}

+ //

${game.i18n.localize("C5ETRAINING.ImportTypeSelectionCombine")}: ${game.i18n.localize("C5ETRAINING.ImportTypeSelectionTextCombine")}

`; + // Create dialog + // new Dialog({ + // title: game.i18n.localize("C5ETRAINING.ImportTypeSelectionTitle"), + // content: content, + // buttons: { + // overwrite: {icon: "", label: game.i18n.localize("C5ETRAINING.ImportTypeSelectionOverwrite"), callback: () => act = "overwrite"}, + // add: {icon: "", label: game.i18n.localize("C5ETRAINING.ImportTypeSelectionCombine"), callback: () => act = "add"}, + // quit: {icon: "", label: game.i18n.localize("C5ETRAINING.Cancel"), callback: () => act = "quit"}, + // }, + // default: "quit", + // close: async (html) => { + // if(act === "quit"){ + // return; + // } else if (act === "overwrite") { + // let currentCategories = actor.getFlag("5e-training","categories") || []; + // let currentCategoryIds = currentCategories.map(c => c.id); + + // for(var i = 0; i < importedItems.length; i++){ + // // Unset missing category ID's + // if((currentCategoryIds.length === 0) || (currentCategoryIds.indexOf(importedItems[i].category) === -1)){ + // importedItems[i].category = ""; + // } + // } + // actor.setFlag("5e-training","trainingItems",importedItems); + // await ui.notifications.info(game.i18n.localize("C5ETRAINING.ImportComplete")); + // } else if (act === "add") { + // let currentItems = actor.getFlag("5e-training","trainingItems") || []; + // let currentCategories = actor.getFlag("5e-training","categories") || []; + // let currentIds = currentItems.map(i => i.id); + // let currentNames = currentItems.map(i => i.name); + // let currentCategoryIds = currentCategories.map(c => c.id); + // let possibleDupes = false; + + // for(var i = 0; i < importedItems.length; i++){ + // // De-dupe ID's + // if(currentIds.indexOf(importedItems[i].id) > -1){ + // let matchedIdx = currentIds.indexOf(importedItems[i].id); + // importedItems[i].id = randomID(); + // possibleDupes = true; + // } + + // // Check for duplicate names + // if(currentNames.indexOf(importedItems[i].name) > -1){ + // possibleDupes = true; + // } + + // // Unset missing category ID's + // if((currentCategoryIds.length === 0) || (currentCategoryIds.indexOf(importedItems[i].category) === -1)){ + // importedItems[i].category = ""; + // } + // } + // let combinedItems = currentItems.concat(importedItems); + // await actor.setFlag("5e-training","trainingItems", combinedItems); + // ui.notifications.info(game.i18n.localize("C5ETRAINING.ImportComplete")); + // if(possibleDupes){ + // new Dialog({ + // title: game.i18n.localize("C5ETRAINING.ImportDupeWarningTitle"), + // content: `

${game.i18n.localize("C5ETRAINING.ImportDupeWarningText")}

`, + // buttons: { + // ok: {icon: "", label: game.i18n.localize("C5ETRAINING.ImportDupeWarningConfirm")}, + // }, + // default: "ok" + // }).render(true); + // } + // } + // } + // }).render(true); }); }); input.trigger('click'); diff --git a/scripts/crash-5e-training.js b/scripts/crash-5e-training.js index 5c8f889..c13b619 100644 --- a/scripts/crash-5e-training.js +++ b/scripts/crash-5e-training.js @@ -25,7 +25,7 @@ async function addTrainingTab(app, html, data) { if (showTrainingTab){ // Get our actor and our flags - let actor = game.actors.contents.find(a => a.data._id === data.actor._id); + let actor = game.actors.contents.find(a => a._id === data.actor._id); let trainingItems = await actor.getFlag("5e-training", "trainingItems"); // Update the nav menu @@ -214,8 +214,8 @@ async function addTrainingTab(app, html, data) { // we have training enabled, this returns true. function adjustSheetWidth(app){ let settingEnabled = !!game.settings.get("5e-training", "extraSheetWidth"); - let sheetHasTab = ((app.object.data.type === 'npc') && game.settings.get("5e-training", "enableTrainingNpc")) || - ((app.object.data.type === 'character') && game.settings.get("5e-training", "enableTraining")); + let sheetHasTab = ((app.object.type === 'npc') && game.settings.get("5e-training", "enableTrainingNpc")) || + ((app.object.type === 'character') && game.settings.get("5e-training", "enableTraining")); let currentWidth = app.position.width; let defaultWidth = app.options.width; let sheetIsSmaller = currentWidth < (defaultWidth + game.settings.get("5e-training", "extraSheetWidth")); @@ -236,7 +236,7 @@ async function migrateAllActors(){ // If the user can't update the actor, skip it let currentUserId = game.userId; - let currentUserOwnsActor = a.data.permission[currentUserId] === 3; + let currentUserOwnsActor = a.permission[currentUserId] === 3; let currentUserIsGm = game.user.isGM; if(!currentUserOwnsActor && !currentUserIsGm){ console.log("Crash's Tracking & Training (5e): " + game.i18n.localize("C5ETRAINING.Skipping") + ": " + a.data.name); diff --git a/scripts/handlebars-helpers.js b/scripts/handlebars-helpers.js index abaee3f..44a6589 100644 --- a/scripts/handlebars-helpers.js +++ b/scripts/handlebars-helpers.js @@ -18,7 +18,7 @@ export function registerHelpers(){ } else if(item.progressionStyle === "ABILITY" ){ formatted = CONFIG.DND5E.abilities[item.ability]; } else if(item.progressionStyle === "SKILL" ){ - formatted = CONFIG.DND5E.skills[item.skill]; + formatted = CONFIG.DND5E.skills[item.skill].label; } else if(item.progressionStyle === "TOOL" ){ let toolId = item.tool; let tool = actor.items.filter(item => { return item._id === toolId })[0];