diff --git a/README.md b/README.md index ffd3207..09e1425 100644 --- a/README.md +++ b/README.md @@ -14,11 +14,11 @@ Default is "Create New Item" obviously you need to adapt if you have a different ### Configure subtype Recipe ![img.png](pictures/configure.png) +#### tool: (0.5.x) +you may add a tool the crafting process requires. #### cost: you may add costs to the crafting process #### Ingredients: - - You may add Items via drag and drop as Ingredients. #### skill: you may add a skill that is required in the crafting process. @@ -67,9 +67,16 @@ You do not need to import those just drag and drop them into your recipes or use You need to import them and then grant permission to the users you want to have access to it. The recipecompendium will only show the recipes the user has access to. +### Settings +![img.png](pictures/toolconfig.png) +- You can enable or disable tools for recipes feature 0.5.x default it is disabled. +- You can configure the tool list your recipes can select from. +(however if you do you might risk incompatibility to others, if the list is missing some default dnd5e tools tell me so) ## latest features: +### 0.5.x feature tool +you now can use tools, if you do not have the tool you dont get a check you simple fail and your ingredients won't vanish. ### 0.4.x feature compendiums, you now can use items directly from compendium, module comes with 4 compendiums ingredients,rolltables,potions and recipes diff --git a/css/crafting.css b/css/crafting.css index 4e0cd49..5779e9e 100644 --- a/css/crafting.css +++ b/css/crafting.css @@ -49,6 +49,10 @@ cursor: pointer; } +.beavers-crafting .recipe .action.result { + padding:5px; +} + .beavers-crafting .drop-area { /* Center the content */ align-items: center; diff --git a/lang/en.json b/lang/en.json index d2f6bba..81261da 100644 --- a/lang/en.json +++ b/lang/en.json @@ -9,6 +9,7 @@ "ingredients": "Ingredients", "quantity": "Quantity", "skills": "Skill", + "tools": "Tool", "dc": "DC", "results": "Results", "dropItem": "drop item", @@ -44,7 +45,21 @@ "displayIngredients": { "name": "Display ingredient sheet", "hint": "Show ItemSheet when clicking on ingredient items of a recipe." + }, + "useTool": { + "name": "Recipes use tools", + "hint": "A Recipe can require to have a tool present on actor." + }, + "toolButton": { + "name": "Configrue tools available", + "hint": "Configure list of tools that are available in tool dropdown when creating recipes", + "label": "configure" } + }, + "tool-config": { + "title": "Tool Config", + "dropItem": "drop tool", + "warn": "Be warned if you manipulate items from this list it will reduce your compatibility to imported/exported recipes. E.g. if you delete tools imported recipes that require this tool might break." } } } \ No newline at end of file diff --git a/module.json b/module.json index 58f214f..feee6f4 100644 --- a/module.json +++ b/module.json @@ -2,7 +2,7 @@ "title": "Beaver's Crafting System", "description": "A Crafting Module for DnD", "id": "beavers-crafting", - "version": "0.4.1", + "version": "0.5.0", "authors": [ { "name": "angryBeaver", diff --git a/package.json b/package.json index e9d4d0e..bc2354b 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "beavers-crafting", "title": "Beaver's Crafting", - "version": "0.4.1", + "version": "0.5.0", "description": "Crafting", "devDir": "C:\\Users\\User\\AppData\\Local\\FoundryVTT\\Data\\modules", "main": "src/main.js", diff --git a/pictures/configure.png b/pictures/configure.png index 65e93a8..023db9a 100644 Binary files a/pictures/configure.png and b/pictures/configure.png differ diff --git a/pictures/toolconfig.png b/pictures/toolconfig.png new file mode 100644 index 0000000..f726086 Binary files /dev/null and b/pictures/toolconfig.png differ diff --git a/src/Crafting.ts b/src/Crafting.ts index ae3a074..4b567a6 100644 --- a/src/Crafting.ts +++ b/src/Crafting.ts @@ -28,7 +28,8 @@ export class Crafting { } async craft(): Promise { - const result = await this.checkSkill(); + const result = await this.checkTool(); + await this.checkSkill(result); await this.evaluateAnyOf(); RecipeCompendium.validateRecipeToItemList(Object.values(this.recipe.ingredients), this.actor.items, result); this.checkCurrency(result); @@ -55,6 +56,12 @@ export class Crafting { return result; } + async checkTool(result?: Result): Promise { + if(!result) result = new DefaultResult(); + if (result.hasErrors) return result; + return await RecipeCompendium.validateTool(this.recipe,this.actor.items,result); + } + async evaluateAnyOf(){ const toDelete:string[] = []; const toAdd:Component[] = []; @@ -137,7 +144,12 @@ export class Crafting { async _sendToChat(result: Result) { let content = await renderTemplate(`modules/${Settings.NAMESPACE}/templates/crafting-chat.hbs`, - {recipe: this.recipe, result: result, roll: this.roll}) + { + recipe: this.recipe, + result: result, + roll: this.roll, + displayTool: Settings.get(Settings.USE_TOOL) + }) content = TextEditor.enrichHTML(content); await ChatMessage.create({ content: content, diff --git a/src/Recipe.ts b/src/Recipe.ts index d5c2b4e..9b2e57b 100644 --- a/src/Recipe.ts +++ b/src/Recipe.ts @@ -1,8 +1,9 @@ import {Settings} from "./Settings.js"; import {DefaultCurrency} from "./Exchange.js"; -import {sanitizeUuid} from "./helpers/Utility.js"; +import {getItem, sanitizeUuid} from "./helpers/Utility.js"; +import {getToolConfig} from "./apps/ToolConfig.js"; -export class Recipe implements RecipeStoreData{ +export class Recipe { id:string; name:string; img:string; @@ -10,6 +11,7 @@ export class Recipe implements RecipeStoreData{ results:Map; skill?:Skill; currency?:Currency; + tool?:string; _trash:Trash; static fromItem(item):Recipe{ @@ -27,6 +29,7 @@ export class Recipe implements RecipeStoreData{ this.results = data.results || {} this.skill = data.skill; this.currency = data.currency; + this.tool = data.tool; this._trash = { ingredients:{}, results:{}, @@ -38,7 +41,11 @@ export class Recipe implements RecipeStoreData{ ingredients: this.serializeIngredients(), skill: this.skill, results: this.serializeResults(), - currency: this.currency + currency: this.currency, + tool: this.tool, + } + if(!this.tool){ + serialized["-=tool"] = null; } if(!this.skill){ serialized["-=skill"] = null; @@ -92,6 +99,14 @@ export class Recipe implements RecipeStoreData{ removeCurrency(){ delete this.currency; } + async addTool(){ + const config = await getToolConfig() + this.tool = config[0].uuid; + } + removeTool(){ + delete this.tool; + } + } interface Trash { ingredients:{}; @@ -137,4 +152,5 @@ interface RecipeStoreData { results:Map; skill?:Skill; currency?:Currency; + tool?:string; } \ No newline at end of file diff --git a/src/Settings.js b/src/Settings.js index 603cb32..222efa4 100644 --- a/src/Settings.js +++ b/src/Settings.js @@ -1,13 +1,26 @@ +import {ToolConfig} from "./apps/ToolConfig.js"; + export class Settings { static NAMESPACE = "beavers-crafting"; static CREATE_ITEM_TITLE = "createItemTitle"; static DISPLAY_RESULTS = "displayResults"; static DISPLAY_INGREDIENTS = "displayIngredients"; + static USE_TOOL = "useTool"; + static TOOL_CONFIG_BUTTON= "toolConfigButton"; + static TOOL_CONFIG = "toolConfig;" static RECIPE_SUBTYPE = "Recipe"; static ANYOF_SUBTYPE = "AnyOf"; static init() { + game.settings.register(this.NAMESPACE, this.USE_TOOL, { + name: game.i18n.localize('beaversCrafting.settings.useTool.name'), + hint: game.i18n.localize('beaversCrafting.settings.useTool.hint'), + scope: "world", + config: true, + default: false, + type: Boolean, + }); game.settings.register(this.NAMESPACE, this.CREATE_ITEM_TITLE, { name: game.i18n.localize('beaversCrafting.settings.createItemTitle.name'), hint: game.i18n.localize('beaversCrafting.settings.createItemTitle.hint'), @@ -32,12 +45,58 @@ export class Settings { default: true, type: Boolean, }); - } + game.settings.register(this.NAMESPACE, this.TOOL_CONFIG,{ + name: "ToolConfig", + scope: "world", + config: false, + default: defaultToolConfig, + type: Object + }); + game.settings.registerMenu(this.NAMESPACE, this.TOOL_CONFIG_BUTTON, { + name: game.i18n.localize('beaversCrafting.settings.toolButton.name'), + label: game.i18n.localize("beaversCrafting.settings.toolButton.label"), + hint: game.i18n.localize('beaversCrafting.settings.toolButton.hint'), + scope: "world", + type: ToolConfig, + restricted: true + }); + } static get(key){ return game.settings.get(this.NAMESPACE, key); }; + static set(key,value){ + game.settings.set(this.NAMESPACE,key,value); + } + +} -} \ No newline at end of file +const defaultToolConfig = [ + "Compendium.dnd5e.items.8NS6MSOdXtUqD7Ib", + "Compendium.dnd5e.items.rTbVrNcwApnuTz5E", + "Compendium.dnd5e.items.fC0lFK8P4RuhpfaU", + "Compendium.dnd5e.items.YfBwELTgPFHmQdHh", + "Compendium.dnd5e.items.hM84pZnpCqKfi8XH", + "Compendium.dnd5e.items.PUMfwyVUbtyxgYbD", + "Compendium.dnd5e.items.skUih6tBvcBbORzA", + "Compendium.dnd5e.items.YHCmjsiXxZ9UdUhU", + "Compendium.dnd5e.items.hJS8yEVkqgJjwfWa", + "Compendium.dnd5e.items.woWZ1sO5IUVGzo58", + "Compendium.dnd5e.items.KndVe2insuctjIaj", + "Compendium.dnd5e.items.0d08g1i5WXnNrCNA", + "Compendium.dnd5e.items.ap9prThUB2y9lDyj", + "Compendium.dnd5e.items.xKErqkLo4ASYr5EP", + "Compendium.dnd5e.items.SztwZhbhZeCqyAes", + "Compendium.dnd5e.items.Y9S75go1hLMXUD48", + "Compendium.dnd5e.items.jhjo20QoiD5exf09", + "Compendium.dnd5e.items.ccm5xlWhx74d6lsK", + "Compendium.dnd5e.items.ugzwHl8vYaPu2GNd", + "Compendium.dnd5e.items.i89okN7GFTWHsvPy", + "Compendium.dnd5e.items.IBhDAr7WkhWPYLVn", + "Compendium.dnd5e.items.cG3m4YlHfbQlLEOx", + "Compendium.dnd5e.items.il2GNi8C0DvGLL9P", + "Compendium.dnd5e.items.V13fjV5oSmvbRdgP", + "Compendium.dnd5e.items.6rocoBx5jdzG1QQH", + ]; \ No newline at end of file diff --git a/src/apps/CraftingApp.ts b/src/apps/CraftingApp.ts index 14fed7b..c0e9de7 100644 --- a/src/apps/CraftingApp.ts +++ b/src/apps/CraftingApp.ts @@ -3,6 +3,7 @@ import {FilterType, RecipeCompendium} from "./RecipeCompendium.js"; import {Crafting} from "../Crafting.js"; import {getDataFrom, getItem} from "../helpers/Utility.js"; import {Settings} from "../Settings.js"; +import {getToolConfig} from "./ToolConfig.js"; export class CraftingApp extends Application { data: { @@ -63,6 +64,8 @@ export class CraftingApp extends Application { return null; } data.result = RecipeCompendium.validateRecipeToItemList(Object.values(data.recipe.ingredients), this.data.actor.items); + const crafting = await Crafting.from(this.data.actor.id, this.data.recipe.id); + data.result = await crafting.checkTool(data.result); data.content = await renderTemplate('modules/beavers-crafting/templates/recipe-sheet.hbs', { recipe: data.recipe, @@ -71,7 +74,9 @@ export class CraftingApp extends Application { editable: false, result: data.result, displayResults:Settings.get(Settings.DISPLAY_RESULTS), - displayIngredients:Settings.get(Settings.DISPLAY_RESULTS) + displayIngredients:Settings.get(Settings.DISPLAY_RESULTS), + tools: await getToolConfig(), + displayTool: Settings.get(Settings.USE_TOOL) }); return data; } diff --git a/src/apps/RecipeCompendium.ts b/src/apps/RecipeCompendium.ts index ec7fa36..a2ddd0f 100644 --- a/src/apps/RecipeCompendium.ts +++ b/src/apps/RecipeCompendium.ts @@ -1,5 +1,5 @@ //the firstdraft implementation will be kept simple stupid and not performant at all. -import {Recipe} from "../Recipe.js"; +import {DefaultComponent, Recipe} from "../Recipe.js"; import {Settings} from "../Settings.js"; import {AnyOf} from "./AnyOfSheet.js"; import {getItem, sanitizeUuid} from "../helpers/Utility.js"; @@ -55,6 +55,7 @@ export class RecipeCompendium { if (await this.isAnyAnyOfInList(listOfAnyOfIngredients, actor.items)) { //isAvailable or usable ! when any item matches anyOf has the given quantity const listOfIngredientsWithoutAnyOf = Object.values(recipe.ingredients).filter(component => component.type !== Settings.ANYOF_SUBTYPE); const result = RecipeCompendium.validateRecipeToItemList(listOfIngredientsWithoutAnyOf, actor.items); + await RecipeCompendium.validateTool(recipe,actor.items,result); if ((filter == FilterType.usable && !result.hasErrors) || (filter == FilterType.available && result.isAvailable)) { returnList.push(recipe); @@ -80,6 +81,24 @@ export class RecipeCompendium { return true; } + static async validateTool(recipe,listOfItems,result ?: Result): Promise{ + if (!result) result = new DefaultResult(); + if( recipe.tool && Settings.get(Settings.USE_TOOL)) { + const item = await getItem(recipe.tool); + const component = new DefaultComponent(item, item.uuid, "Item"); + result.tool = { + component: component, + isAvailable: false, + difference: 0, + }; + result.tool.isAvailable = listOfItems.filter((i) => RecipeCompendium.isSame(i, component)).length > 0; + if(!result.tool.isAvailable){ + result.hasErrors = true; + } + } + return result; + } + static validateRecipeToItemList(listOfIngredients: Component[], listOfItems, result ?: Result): Result { if (!result) result = new DefaultResult(); result.isAvailable = listOfIngredients.length === 0; diff --git a/src/apps/RecipeSheet.ts b/src/apps/RecipeSheet.ts index 60e005b..5274fc5 100644 --- a/src/apps/RecipeSheet.ts +++ b/src/apps/RecipeSheet.ts @@ -4,6 +4,7 @@ import {getCurrencies, getSkills} from "../systems/dnd5e.js" import {RecipeCompendium} from "./RecipeCompendium.js"; import {getDataFrom, getItem} from "../helpers/Utility.js"; import {isAnyOf} from "./AnyOfSheet.js"; +import {getToolConfig} from "./ToolConfig.js"; const recipeSheets: { [key: string]: RecipeSheet } = {}; @@ -67,7 +68,16 @@ export class RecipeSheet { async render(){ let template = await renderTemplate('modules/beavers-crafting/templates/recipe-sheet.hbs', - {recipe: this.recipe,currencies: getCurrencies(),skills: getSkills(),editable:this.editable,displayResults:Settings.get(Settings.DISPLAY_RESULTS),displayIngredients:Settings.get(Settings.DISPLAY_RESULTS)}); + { + recipe: this.recipe, + currencies: getCurrencies(), + skills: getSkills(), + editable:this.editable, + displayResults:Settings.get(Settings.DISPLAY_RESULTS), + displayIngredients:Settings.get(Settings.DISPLAY_RESULTS), + tools: await getToolConfig(), + displayTool: Settings.get(Settings.USE_TOOL) + }); this.recipeElement.find('.recipe').remove(); this.recipeElement.append(template); this.handleEvents(); @@ -90,6 +100,14 @@ export class RecipeSheet { this.recipe.addSkill(); this.update(); }); + this.recipeElement.find('.tools .item-add').click(e=>{ + this.recipe.addTool() + .then(()=>this.update()); + }); + this.recipeElement.find('.tools .item-delete').click(e=>{ + this.recipe.removeTool(); + this.update(); + }); this.recipeElement.find('.currencies .item-delete').click(e=>{ this.recipe.removeCurrency(); this.update(); diff --git a/src/apps/ToolConfig.ts b/src/apps/ToolConfig.ts new file mode 100644 index 0000000..747b1f3 --- /dev/null +++ b/src/apps/ToolConfig.ts @@ -0,0 +1,105 @@ +import {DefaultComponent} from "../Recipe.js"; +import {Settings} from "../Settings.js"; +import {getDataFrom, getItem} from "../helpers/Utility.js"; + +const components:Component[] = []; + +export class ToolConfig extends FormApplication { + + tools:Component[] + + static get defaultOptions(): any { + // @ts-ignore + const title = game.i18n.localize("beaversCrafting.tool-config.title"); + return mergeObject(super.defaultOptions, { + title: title, + template: "modules/beavers-crafting/templates/tool-config.hbs", + id: "beavers-crafting-tool-config", + width: 300, + height: 600, + closeOnSubmit: true, + resizable:true, + classes: ["dnd5e", "beavers-crafting","tool-config"] + + + }) + } + + async getData(options: any): Promise { + this.tools = await getToolConfig(); + return { + tools: this.tools + } + } + + activateListeners(html) { + super.activateListeners(html); + html.find('.item-delete').on("click",e=>{ + const index = e.target.dataset.id; + this.tools.splice(index,1); + this._updateTool(); + }); + + const dropFilter = new DragDrop({ + dropSelector: '.drop-area', + permissions: { + dragstart: this._canDragStart.bind(this), + drop: this._canDragDrop.bind(this) + }, + callbacks: { + dragstart: this._onDragStart.bind(this), + dragover: this._onDragOver.bind(this), + drop: this._onDrop.bind(this) + } + }); + this._dragDrop.push(dropFilter); + dropFilter.bind(html[0]); + } + + async _onDrop(e){ + const isFilterDrop = $(e.target).hasClass("drop-area"); + if(isFilterDrop){ + return this._onDropFilter(e); + } + } + + async _onDropFilter(e:DragEvent){ + const data = getDataFrom(e) + if(data){ + if(data.type !== "Item") return; + await _addToolConfig(data.uuid); + await this._updateTool(); + } + } + + async _updateObject(event, formData) { + await this._updateTool(); + } + + async _updateTool(){ + Settings.set(Settings.TOOL_CONFIG, this.tools.map(c => c.uuid)); + this.render(); + } +} + + + +export async function getToolConfig(): Promise{ + if(components.length === 0) { + await _setToolConfig(); + } + return components; +} + +async function _setToolConfig(){ + components.length = 0; + const tools = Settings.get(Settings.TOOL_CONFIG) || []; + for (const uuid of tools) { + await _addToolConfig(uuid); + } +} +async function _addToolConfig(uuid){ + const item = await getItem(uuid); + const component = new DefaultComponent(item, uuid, "Tool"); + components.push(component); +} diff --git a/src/types.ts b/src/types.ts index 22f7ea1..d4c7c23 100644 --- a/src/types.ts +++ b/src/types.ts @@ -17,6 +17,7 @@ interface Result { total: number, difference: number, }; + tool?:IngredientResult hasErrors:boolean; hasException:boolean; isAvailable:boolean; diff --git a/templates/crafting-chat.hbs b/templates/crafting-chat.hbs index 25ce93d..46ff35c 100644 --- a/templates/crafting-chat.hbs +++ b/templates/crafting-chat.hbs @@ -20,6 +20,23 @@
+ {{#if displayTool}} + {{#if recipe.tool}} +
+
+
+
+
+ {{#if result.tool.isAvailable }} + + {{else}} + + {{/if}} +
+
+ {{/if}} + {{/if}} {{#if recipe.currency}}
diff --git a/templates/recipe-sheet.hbs b/templates/recipe-sheet.hbs index a1e417d..8d21649 100644 --- a/templates/recipe-sheet.hbs +++ b/templates/recipe-sheet.hbs @@ -1,5 +1,49 @@
+ {{#if displayTool}} +
+
+
{{localize "beaversCrafting.recipe.tools"}}
+ {{#if editable}} +
+ {{#unless recipe.tool}} + + + + {{/unless}} +
+ {{/if}} +
+ {{#if recipe.tool}} +
+ + {{#if result}} +
+ {{#if result.tool.isAvailable }} + + {{else}} + + {{/if}} +
+ {{/if}} + {{#if editable}} +
+ + + +
+ {{/if}} +
+ {{/if}} +
+ {{/if}}
{{localize "beaversCrafting.recipe.currency"}}
@@ -49,7 +93,8 @@
{{#each recipe.ingredients}}
-
+

{{this.name}}

diff --git a/templates/tool-config.hbs b/templates/tool-config.hbs new file mode 100644 index 0000000..e014e3c --- /dev/null +++ b/templates/tool-config.hbs @@ -0,0 +1,21 @@ +
+
+ {{localize "beaversCrafting.tool-config.warn"}} +
+ {{#each tools}} +
+
+
+

{{this.name}}

+
+
+ + + +
+
+ {{/each}} +
+
{{localize "beaversCrafting.tool-config.dropItem"}}
+
+
\ No newline at end of file