From ccd58d91a6ce957289e7e33dc4c8319e45f43d61 Mon Sep 17 00:00:00 2001 From: Matthew W Date: Sun, 3 Mar 2024 21:17:11 -0700 Subject: [PATCH 01/18] Clean up fixer upper a bit to make it easier to add more checks and fixes in the future. --- lib/data_fixer_upper.lua | 58 ++++++++++++++++++---------------------- 1 file changed, 26 insertions(+), 32 deletions(-) diff --git a/lib/data_fixer_upper.lua b/lib/data_fixer_upper.lua index f399b4c..7e82a6f 100644 --- a/lib/data_fixer_upper.lua +++ b/lib/data_fixer_upper.lua @@ -4,8 +4,10 @@ local machines_common = require "ui.machines.common" local fixer_upper = {} +local fixes = {} + --- Fix duplicate machine names by determining which id the machines should "collapse" to -function fixer_upper.duplicate_machine_name_fixer() +function fixes.duplicate_machine_names() print("Fixing duplicate machine names...") ---@type table @@ -45,7 +47,7 @@ function fixer_upper.duplicate_machine_name_fixer() end --- Fix old style machine data (no id) to new style (with id). This also needs to update recipes to use the id instead of the name. -function fixer_upper.machine_id_fixer() +function fixes.machine_id_needed() print("Fixing machine data...") ---@type table @@ -107,7 +109,7 @@ end --- Fix recipe data that uses machine names instead of IDs. -function fixer_upper.recipe_machine_fixer() +function fixes.recipe_machine_needed() -- Update the recipes to use the new machine IDs. print("Fixing recipe data...") @@ -180,11 +182,17 @@ end function fixer_upper.check() term.clear() term.setCursorPos(1, 1) - local duplicate_machine_names = checks.duplicate_machine_names() - local machine_id_needed = checks.machine_id_needed() - local recipe_machine_needed = checks.recipe_machine_needed() + local results = {} + local needs_run = false + for check_name, checker in pairs(checks) do + local out = checker() + results[check_name] = out + if out then + needs_run = true + end + end - if machine_id_needed or recipe_machine_needed or duplicate_machine_names then + if needs_run then term.setTextColor(colors.white) term.setCursorPos(1, 1) term.clear() @@ -192,14 +200,8 @@ function fixer_upper.check() print("Data Fixer Upper needs to run, press 'q' to quit, or any other key to continue.") print("Your data will be backed up.") print("\nModules:") - if duplicate_machine_names then - print("- Duplicate machine names") - end - if machine_id_needed then - print("- Machine IDs") - end - if recipe_machine_needed then - print("- Recipe machine IDs") + for check_name, result in pairs(results) do + print("-", check_name, result and "needs to be run." or "is OK.") end sleep() -- Ensure the event queue is cleared. local ev, key = os.pullEvent("key") @@ -213,25 +215,17 @@ function fixer_upper.check() machines_common.backup_save() recipe_handler.backup_save() - if duplicate_machine_names then - term.setTextColor(colors.orange) - print("Running machine ID fixer...") - - fixer_upper.machine_id_fixer() - end - - if machine_id_needed then - term.setTextColor(colors.orange) - print("Running machine ID fixer...") + for check_name, fixer in pairs(fixes) do + if results[check_name] then + term.setTextColor(colors.white) + print("Running", check_name, "fixer...") term.setTextColor(colors.lightGray) - fixer_upper.machine_id_fixer() - end - if recipe_machine_needed then - term.setTextColor(colors.orange) - print("Running recipe machine fixer...") - term.setTextColor(colors.lightGray) - fixer_upper.recipe_machine_fixer() + fixer() + + term.setTextColor(colors.white) + print("Finished", check_name, "fixer.") + end end if checks.duplicate_machine_names() then From 6358404f41ee920ec3707323c7de16c04e7c5ceb Mon Sep 17 00:00:00 2001 From: Matthew W Date: Mon, 4 Mar 2024 00:28:14 -0700 Subject: [PATCH 02/18] Add item management to items common --- ui/items/common.lua | 92 ++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 86 insertions(+), 6 deletions(-) diff --git a/ui/items/common.lua b/ui/items/common.lua index 1c59765..031c63d 100644 --- a/ui/items/common.lua +++ b/ui/items/common.lua @@ -1,18 +1,98 @@ --- Common functionality between the various machine menus. -local recipe_handler = require "recipe_handler" +-------------------------------------------------------------------------------- +-- Lua Language Server Type Definitions -- +-------------------------------------------------------------------------------- +---@class item_data An item and its associated data. +---@field name string The name of the item. +---@field id integer The unique identifier of the item. -local common = {} +-------------------------------------------------------------------------------- +-- End Language Server Type Definitions -- +-------------------------------------------------------------------------------- ---- Load the recipes from the file. +local file_helper = require "file_helper":instanced("data") + +---@class items_common +local common = { + ---@type table + item_lookup = {} +} + +--- Generate a unique id for an item. +---@return integer id The unique id. +local function generate_unique_id() + local id + + repeat + id = math.random(-1000000, 1000000) + until not common.item_ids[id] + + return id +end + +--- Add a new item to the list of items. +---@param name string The name of the item. +---@return integer id The unique id of the item. +function common.add_item(name) + local id = generate_unique_id() + local item = {name = name, id = id} + + common.item_lookup[id] = item + return id +end + +--- Remove an item from the list of items. +---@param id integer The unique id of the item. +function common.remove_item(id) + common.item_lookup[id] = nil +end + +--- Get the list of items. +---@return table The list of items. +function common.get_items() + return common.item_lookup +end + +--- Get the name of an item. +---@param id integer The unique id of the item. +---@return string? name The name of the item, or nil if it does not exist. +function common.get_item_name(id) + return common.item_lookup[id] and common.item_lookup[id].name +end + +--- Get the unique id of an item. +---@param name string The name of the item. +---@return integer? id The unique id of the item, or nil if it does not exist. +function common.get_item_id(name) + for id, item in pairs(common.item_lookup) do + if item.name == name then + return id + end + end +end + +--- Load the recipes and items from a file. function common.load() - recipe_handler.load() + -- Convert from a list to save a little bit of space + local list = file_helper:unserialize("items.lson", {}) + common.item_lookup = {} + + for _, item in ipairs(list) do + common.item_lookup[item.id] = item + end end ---- Save the recipes to the file. +--- Save the recipes and items to a file. function common.save() - recipe_handler.save() + -- Convert to a list to save a little bit of space + local list = {} + for _, item in pairs(common.item_lookup) do + table.insert(list, item) + end + + file_helper:serialize("items.lson", list) end return common From 17a18245791b3c258c6c0569a78e0ce3d831d404 Mon Sep 17 00:00:00 2001 From: Matthew W Date: Mon, 4 Mar 2024 00:29:04 -0700 Subject: [PATCH 03/18] Major change to alter all recipes and items to have IDs instead of names. Names will be handled by the items common. --- lib/recipe_handler.lua | 288 +++++++++++++++++++++-------------------- 1 file changed, 146 insertions(+), 142 deletions(-) diff --git a/lib/recipe_handler.lua b/lib/recipe_handler.lua index 8a7fb63..2a5fe6a 100644 --- a/lib/recipe_handler.lua +++ b/lib/recipe_handler.lua @@ -17,6 +17,7 @@ local graph = require "graph" local shallow_serialize = require "graph.shallow_serialize" local util = require "util" local machines_common = require "ui.machines.common" +local items_common = require "ui.items.common" -------------------------------------------------------------------------------- -- Lua Language Server Type Definitions -- @@ -37,10 +38,10 @@ local machines_common = require "ui.machines.common" ---@field machine integer The id of the machine used to craft the item. Defaults to `0` (Crafting Table). ---@field enabled boolean Whether or not the recipe is enabled. ---@field preferred boolean Whether or not the recipe is preferred. ----@field random_id number A random ID used to identify the recipe in the graph, mainly used when there are multiple recipes for the same item. +---@field id number A unique ID used to identify the recipe in the graph, mainly used when there are multiple recipes for the same item. ---@class RecipeIngredient A single ingredient in the recipe. ----@field name string The name of the ingredient. +---@field id integer The id of the item. ---@field amount number The amount of the ingredient. ---@field fluid boolean Whether or not the ingredient is a fluid. @@ -48,10 +49,10 @@ local machines_common = require "ui.machines.common" ---@field amount number The amount of the ingredient, in millibuckets. ---@field fluid true Whether or not the ingredient is a fluid. ----@alias RecipeLookup table +---@alias RecipeLookup table ---@class CraftingPlan A plan containing all the steps, in order, to craft the given item. ----@field item string The item to craft. +---@field item integer The id of the item to craft. ---@field output_count number The amount of items that will be outputted. ---@field steps CraftingPlanStep[] The steps to craft the item. @@ -59,20 +60,20 @@ local machines_common = require "ui.machines.common" ---@field raw_material_cost MaterialCost The cost of the raw materials needed to craft the item. ---@class CraftingPlanStep A single step in the crafting plan. ----@field item string The item to craft. +---@field item integer The id of the item to craft. ---@field output_count number The amount of items that will be outputted. ---@field needed number The amount of the item crafted that are needed in future steps. ---@field crafts number The amount of times to repeat the crafting process. ---@field recipe Recipe? The recipe to use to craft the item, if the item can be crafted. ---@class MultiCraftPlanStep A single step in a crafting plan, that can have multiple recipes. ----@field item string The item to craft. +---@field item integer The id of the item to craft. ---@field output_count number The amount of items that will be outputted. ---@field needed number The amount of the item crafted that are needed in future steps. ---@field crafts number The amount of times to repeat the crafting process. ---@field recipes Recipe[] The recipes that can be used to craft the item. ----@alias MaterialCost table +---@alias MaterialCost table -------------------------------------------------------------------------------- -- End Lua Language Server Type Definitions -- @@ -98,6 +99,18 @@ local lookup = {} ---@type RecipeGraph local recipe_graph = graph.new() --[[@as RecipeGraph]] +--- Generate a unique id for a recipe. +---@return integer id The unique id. +local function generate_unique_id() + local id + + repeat + id = math.random(-1000000, 1000000) + until not lookup[id] + + return id +end + --- Create a smaller version of a recipe list with unneeded data removed (things like `fluid = false` can be nil, so it isn't included in the output data). ---@return table recipes The ensmallified list of recipes. local function ensmallify() @@ -107,18 +120,14 @@ local function ensmallify() local recipe = recipes[i] local small_recipe = { result = { - name = recipe.result.name, + id = recipe.result.id, amount = recipe.result.amount, fluid = recipe.result.fluid }, ingredients = {}, - random_id = recipe.random_id, + id = recipe.id, } - if recipe.enabled then - small_recipe.enabled = true - end - if recipe.preferred then small_recipe.preferred = true end @@ -140,7 +149,7 @@ local function ensmallify() for j = 1, #recipe.ingredients do local ingredient = recipe.ingredients[j] small_recipe.ingredients[j] = { - name = ingredient.name, + id = ingredient.id, amount = ingredient.amount, fluid = ingredient.fluid, } @@ -167,13 +176,11 @@ local function build_lookup() for i = 1, #recipes do local recipe = recipes[i] - if recipe.enabled then - if not lookup[recipe.result.name] then - lookup[recipe.result.name] = {} - end - - table.insert(lookup[recipe.result.name], recipe) + if not lookup[recipe.result.id] then + lookup[recipe.result.id] = {} end + + table.insert(lookup[recipe.result.id], recipe) end end @@ -230,7 +237,7 @@ function RecipeHandler.parse_recipe(line) return nil end - if not recipe.result.name then + if not recipe.result.id then return nil end @@ -257,7 +264,7 @@ function RecipeHandler.parse_recipe(line) for i = 1, #recipe.ingredients do local ingredient = recipe.ingredients[i] - if not ingredient.name then + if not ingredient.id then return nil end @@ -273,24 +280,15 @@ function RecipeHandler.parse_recipe(line) return recipe --[[@as Recipe]] end ---- Parse a json style recipe from a string. ----@param json string The string to parse the recipe from. ----@return Recipe? recipe The recipe parsed from the string. -function RecipeHandler.parse_json_recipe(json) - ---@FIXME implement this - error("Not yet implemented.", 2) - return {} -end - --- Insert a recipe into the recipe list. ---@param recipe Recipe The recipe to insert. function RecipeHandler.insert(recipe) ---@FIXME confirm the recipe is valid table.insert(recipes, recipe) - if not lookup[recipe.result.name] then - lookup[recipe.result.name] = {} + if not lookup[recipe.result.id] then + lookup[recipe.result.id] = {} end - table.insert(lookup[recipe.result.name], recipe) + table.insert(lookup[recipe.result.id], recipe) end --- Save the recipes to the given file. @@ -323,11 +321,11 @@ function RecipeHandler.build_recipe_graph() recipe_graph = graph.new() --[[@as RecipeGraph]] -- First pass: Create all the nodes. One node for each recipe for an item. - for item_name, item_recipes in pairs(lookup) do + for item_id, item_recipes in pairs(lookup) do for i = 1, #item_recipes do local recipe = item_recipes[i] recipe_graph:add_node({ - item = item_name, + item = item_id, recipe = util.deep_copy(recipe), needed = 0, crafts = 0, @@ -345,13 +343,13 @@ function RecipeHandler.build_recipe_graph() if recipe then for j = 1, #recipe.ingredients do local ingredient = recipe.ingredients[j] - local ingredient_name = ingredient.name - local ingredient_nodes = recipe_graph:find_nodes(function(n) return n.value.item == ingredient_name end) --[[ @as RecipeGraphNode[] ]] + local ingredient_id = ingredient.id + local ingredient_nodes = recipe_graph:find_nodes(function(n) return n.value.item == ingredient_id end) --[[ @as RecipeGraphNode[] ]] if #ingredient_nodes == 0 then -- Ingredient doesn't have a recipe, create a node for it. local new_node = recipe_graph:add_node({ - item = ingredient_name, + item = ingredient_id, recipe = nil, needed = 0, crafts = 0, @@ -383,28 +381,28 @@ local function clean_crafting_plan(plan) local step = plan.steps[i] if not step then break end - if seen[step.recipe.random_id] then + if seen[step.recipe.id] then table.remove(plan.steps, i) -- We removed an entry, so we need to decrement i to land on the same -- position again next iteration. i = i - 1 else - seen[step.recipe.random_id] = true + seen[step.recipe.id] = true end end end --- Get the *first* recipe available for the given item. If the item (or ingredients) have multiple recipes, it will use the first and build a crafting plan from that. This method is faster than other methods, but breaks down if there is a loop. ----@param item string The item to get the recipe for. +---@param item integer The item id to get the recipe for. ---@param amount number The amount of the item to craft. ---@param max_depth number? The maximum depth to search for recipes. If set at 1, will only return the recipe for the given item. Higher values will give you recipes for items that are ingredients in the recipe for the given item. Be warned, if you set it too high and there are loops, you may have issues. Defaults to 1. ----@param recipe_selections table? A recipe in this lookup table will be used if crafting the item requires one of the ingredients in the list. This is useful for items which have multiple crafting recipes, as you can override which recipe that ingredient will be made with. Any item that has multiple recipes without a recipe in this list will use the first recipe that can be grabbed. ----@param item_exclusions table? A dictionary containing any items that the user already has, and should not be included in the crafting plan. +---@param recipe_selections table? A recipe in this lookup table will be used if crafting the item requires one of the ingredients in the list. This is useful for items which have multiple crafting recipes, as you can override which recipe that ingredient will be made with. Any item that has multiple recipes without a recipe in this list will use the first recipe that can be grabbed. +---@param item_exclusions table? A dictionary containing any items that the user already has, and should not be included in the crafting plan. ---@return FinalizedCraftingPlan? plan The crafting plan for the given item, or nil if no recipe was found. ---@return string? error The error message if no recipe was found. function RecipeHandler.get_first_recipe(item, amount, max_depth, recipe_selections, item_exclusions) - expect(1, item, "string") + expect(1, item, "number") expect(2, amount, "number") expect(3, max_depth, "number", "nil") expect(4, recipe_selections, "table", "nil") @@ -426,9 +424,9 @@ function RecipeHandler.get_first_recipe(item, amount, max_depth, recipe_selectio local recipe_selection = recipe_selections[item] if recipe_selection then - prints(0, "Recipe selection found for", item, ":", recipe_selection.result.name, "(", recipe_selection.random_id, ")") + prints(0, "Recipe selection found for", item, ":", recipe_selection.result.id, "(", recipe_selection.id, ")") -- We need to use this recipe instead of the others. - recipe_node = recipe_graph:find_node(function(n) return n.value.recipe.random_id == recipe_selection.random_id end) --[[@as RecipeGraphNode]] + recipe_node = recipe_graph:find_node(function(n) return n.value.recipe.id == recipe_selection.id end) --[[@as RecipeGraphNode]] else prints(0, "No recipe selection found for", item) end @@ -476,23 +474,23 @@ function RecipeHandler.get_first_recipe(item, amount, max_depth, recipe_selectio spaces = spaces + 2 for _, ingredient in ipairs(recipe.ingredients) do - local ingredient_nodes = recipe_graph:find_nodes(function(n) return n.value.item == ingredient.name end) --[[@as RecipeGraphNode[] ]] + local ingredient_nodes = recipe_graph:find_nodes(function(n) return n.value.item == ingredient.id end) --[[@as RecipeGraphNode[] ]] local ingredient_node = ingredient_nodes[1] -- Check if this ingredient has a recipe selection. - local selection = recipe_selections[ingredient.name] + local selection = recipe_selections[ingredient.id] if selection then -- We need to use this recipe instead of the others. - ingredient_node = recipe_graph:find_node(function(n) return n.value.recipe.random_id == selection.random_id end) --[[@as RecipeGraphNode]] + ingredient_node = recipe_graph:find_node(function(n) return n.value.recipe.id == selection.id end) --[[@as RecipeGraphNode]] else - recipe_selections[ingredient.name] = ingredient_node.value.recipe + recipe_selections[ingredient.id] = ingredient_node.value.recipe end - prints(spaces, "Looking at:", ingredient.name) + prints(spaces, "Looking at:", ingredient.id) if not ingredient_node then -- All ingredients should have nodes by this point - error(("Ingredient node not found for %s. This is likely a bug, please report it and include your recipe list."):format(ingredient.name)) + error(("Ingredient node not found for %s. This is likely a bug, please report it and include your recipe list."):format(ingredient.id)) end -- We need this many of the ingredient. @@ -539,19 +537,19 @@ function RecipeHandler.get_first_recipe(item, amount, max_depth, recipe_selectio end for _, ingredient in ipairs(recipe.ingredients) do - local ingredient_nodes = recipe_graph:find_nodes(function(n) return n.value.item == ingredient.name end) --[[@as RecipeGraphNode]] + local ingredient_nodes = recipe_graph:find_nodes(function(n) return n.value.item == ingredient.id end) --[[@as RecipeGraphNode]] local ingredient_node = ingredient_nodes[1] -- Check if this ingredient has a recipe selection. - local selection = recipe_selections[ingredient.name] + local selection = recipe_selections[ingredient.id] if selection then -- We need to use this recipe instead of the others. - ingredient_node = recipe_graph:find_node(function(n) return n.value.recipe.random_id == selection.random_id end) --[[@as RecipeGraphNode]] + ingredient_node = recipe_graph:find_node(function(n) return n.value.recipe.id == selection.id end) --[[@as RecipeGraphNode]] end if not ingredient_node then -- All ingredients should have nodes by this point - error(("Ingredient node not found for %s. This is likely a bug, please report it and include your recipe list."):format(ingredient.name)) + error(("Ingredient node not found for %s. This is likely a bug, please report it and include your recipe list."):format(ingredient.id)) end if not item_exclusions[ingredient_node.value.item] then @@ -584,14 +582,14 @@ end --- Get as many recipes as possible for the given item. ---@deprecated This method is not actually deprecated, but I would like a warning to show when people use it. This method *does not work*, and will error immediately. ----@param item string The item to get the recipes for. +---@param item integer The item id to get the recipes for. ---@param amount number The amount of the item to craft. ---@param max_depth number? The maximum depth to search for recipes. If set at 1, will only return the recipe for the given item. Higher values will give you recipes for items that are ingredients in the recipes for the given item. Be warned, if you set it too high and there are loops, you may have issues. Defaults to 1. ---@param max_iterations number? The total maximum number of iterations to perform. For each depth decrement, this value will also decrement. If this value reaches 0, the function will stop searching for more recipes and cancel the current recipe it was building. Defaults to 100. ---@return FinalizedCraftingPlan[]? plans The crafting plans for the given item. ---@return string? error The error message if no recipe was found. function RecipeHandler.get_all_recipes(item, amount, max_depth, max_iterations) - expect(1, item, "string") + expect(1, item, "number") expect(2, amount, "number") expect(3, max_depth, "number", "nil") expect(4, max_iterations, "number", "nil") @@ -633,7 +631,7 @@ function RecipeHandler.get_all_recipes(item, amount, max_depth, max_iterations) for i = 1, #recipe_nodes do prints(0, "Recipe node", i, ":", recipe_nodes[i].value.item) - prints(0, "Recipe node id:", recipe_nodes[i].value.recipe.random_id) + prints(0, "Recipe node id:", recipe_nodes[i].value.recipe.id) end -- Now, we step from each recipe node, into its ingredients, and calculate how @@ -681,16 +679,16 @@ function RecipeHandler.get_all_recipes(item, amount, max_depth, max_iterations) node.value.output_count = node.value.crafts * recipe.result.amount prints(spaces, "Output count:", node.value.output_count) - table.insert(step_list, node.value.recipe.random_id) - prints(spaces, "Inserted:", node.value.recipe.random_id) + table.insert(step_list, node.value.recipe.id) + prints(spaces, "Inserted:", node.value.recipe.id) for i, ingredient in ipairs(recipe.ingredients) do - local ingredient_nodes = current_graph:find_nodes(function(n) return n.value.item == ingredient.name end) --[[ @as RecipeGraphNode[] ]] + local ingredient_nodes = current_graph:find_nodes(function(n) return n.value.item == ingredient.id end) --[[ @as RecipeGraphNode[] ]] local n_nodes = #ingredient_nodes if n_nodes == 0 then -- All ingredients should have nodes by this point - error(("Ingredient node not found for %s. This is likely a bug, please report it and include your recipe list."):format(ingredient.name)) + error(("Ingredient node not found for %s. This is likely a bug, please report it and include your recipe list."):format(ingredient.id)) end -- for each ingredient node, split off a new graph and step through it. @@ -722,7 +720,7 @@ function RecipeHandler.get_all_recipes(item, amount, max_depth, max_iterations) graphs[#graphs + 1] = new_graph -- Grab the current node from the new graph - local new_current_node = new_graph:find_node(function(n) return n.value.recipe.random_id == node.value.recipe.random_id end) --[[@as RecipeGraphNode]] + local new_current_node = new_graph:find_node(function(n) return n.value.recipe.id == node.value.recipe.id end) --[[@as RecipeGraphNode]] new_current_node.value.crafts = old_crafts -- undo this as well new_current_node.value.output_count = old_crafts * recipe.result.amount -- and this @@ -770,10 +768,10 @@ function RecipeHandler.get_all_recipes(item, amount, max_depth, max_iterations) local step_list = {} step_lists[i] = step_list - local node = graphs[i]:find_node(function(n) return n.value.recipe.random_id == recipe_nodes[i].value.recipe.random_id end) --[[@as RecipeGraphNode]] + local node = graphs[i]:find_node(function(n) return n.value.recipe.id == recipe_nodes[i].value.recipe.id end) --[[@as RecipeGraphNode]] prints(0, "Starting through", node.value.item) - prints(0, "Random id:", node.value.recipe.random_id) + prints(0, "Random id:", node.value.recipe.id) step( node, @@ -797,7 +795,7 @@ function RecipeHandler.get_all_recipes(item, amount, max_depth, max_iterations) local function build_plan(crafting_plan, current_graph, step_list, step_i, depth) -- Get the current step's node. local ingredient_node = current_graph:find_node(function(n) - return n.value.recipe and n.value.recipe.random_id == step_list[step_i] + return n.value.recipe and n.value.recipe.id == step_list[step_i] end) --[[@as RecipeGraphNode]] if ingredient_node then prints(0, "Found ingredient node") @@ -824,7 +822,7 @@ function RecipeHandler.get_all_recipes(item, amount, max_depth, max_iterations) if not ingredient_node then -- All ingredients should have nodes by this point - error(("Ingredient node not found for %s. This is likely a bug, please report it and include your recipe list."):format(ingredient_node.value.name)) + error(("Ingredient node not found for %s. This is likely a bug, please report it and include your recipe list."):format(ingredient_node.value.id)) end -- Now that we've stepped through the ingredients, add this recipe to the @@ -903,18 +901,24 @@ function RecipeHandler.get_all_recipes(item, amount, max_depth, max_iterations) end --- Create a new recipe for the given item, but do not insert it into the recipe list. ----@param item string The item to create the recipe for. +---@param item_name string The name of the item to create the recipe for. ---@param output_count number The amount of the item that are outputted by the recipe. ---@param ingredients RecipeIngredient[] The ingredients required to craft the item. ---@param machine integer? The machine used to craft the item. Defaults to "crafting table". ---@param is_fluid boolean? Whether or not the item is a fluid. Defaults to false. ---@param is_preferred boolean? Whether or not the recipe is preferred. Defaults to false. ---@return Recipe recipe The recipe created. -function RecipeHandler.create_recipe_object(item, output_count, ingredients, machine, is_fluid, is_preferred) +function RecipeHandler.create_recipe_object(item_name, output_count, ingredients, machine, is_fluid, is_preferred) + -- Check items_common for the item id. If it doesn't exist, add it. + local item_id = items_common.get_item_id(item_name) + if not item_id then + item_id = items_common.add_item(item_name) + end + ---@type Recipe return { result = { - name = item, + id = item_id, amount = output_count, fluid = not not is_fluid }, @@ -922,24 +926,30 @@ function RecipeHandler.create_recipe_object(item, output_count, ingredients, mac machine = machine or 0, enabled = true, preferred = not not is_preferred, - random_id = math.random(-999999999, 999999999) -- probably enough, considering this isn't meant to house every recipe ever + id = generate_unique_id() } end --- Create a new recipe for the given item. ----@param item string The item to create the recipe for. +---@param item_name string The item name to create the recipe for. ---@param output_count number The amount of the item that are outputted by the recipe. ---@param ingredients RecipeIngredient[] The ingredients required to craft the item. ---@param machine integer? The machine used to craft the item. Defaults to "crafting table". ---@param is_fluid boolean? Whether or not the item is a fluid. Defaults to false. ---@return Recipe recipe The recipe created. -function RecipeHandler.create_recipe(item, output_count, ingredients, machine, is_fluid) +function RecipeHandler.create_recipe(item_name, output_count, ingredients, machine, is_fluid) + -- Check items_common for the item id. If it doesn't exist, add it. + local item_id = items_common.get_item_id(item_name) + if not item_id then + item_id = items_common.add_item(item_name) + end + ---@type Recipe - local recipe = RecipeHandler.create_recipe_object(item, output_count, ingredients, machine, is_fluid) + local recipe = RecipeHandler.create_recipe_object(item_name, output_count, ingredients, machine, is_fluid) table.insert(recipes, recipe) - lookup[item] = lookup[item] or {} - table.insert(lookup[item], recipe) + lookup[item_id] = lookup[item_id] or {} + table.insert(lookup[item_id], recipe) return recipe end @@ -981,7 +991,7 @@ function RecipeHandler.get_plan_as_text(plan, plan_number) end table.insert(ingredient_textual, ingredient_formatter:format( ingredient.amount * step.crafts, - ingredient.name, + ingredient.id, ingredient.amount * step.crafts > 1 and "s" or "", ingredient.fluid and " (fluid)" or "" )) @@ -992,9 +1002,9 @@ function RecipeHandler.get_plan_as_text(plan, plan_number) end local line = line_formatter:format( - machines_common.machines[step.recipe.machine].name, + machines_common.machines[step.recipe.machine].id, step.output_count, - step.recipe.result.name, + step.recipe.result.id, step.output_count > 1 and "s" or "", table.concat(ingredient_textual) ) @@ -1013,13 +1023,13 @@ function RecipeHandler.get_raw_material_cost(plan) for _, step in ipairs(plan.steps) do for _, ingredient in ipairs(step.recipe.ingredients) do - if not lookup[ingredient.name] then + if not lookup[ingredient.id] then -- This is a raw material, so add it to the cost. - if not cost[ingredient.name] then - cost[ingredient.name] = 0 + if not cost[ingredient.id] then + cost[ingredient.id] = 0 end - cost[ingredient.name] = cost[ingredient.name] + (ingredient.amount * step.crafts) + cost[ingredient.id] = cost[ingredient.id] + (ingredient.amount * step.crafts) end end end @@ -1028,7 +1038,7 @@ function RecipeHandler.get_raw_material_cost(plan) end --- Get the recipes for the given item. ----@param item string The item to get the recipes for. +---@param item integer The item to get the recipes for. ---@return Recipe[]? recipes The recipes for the given item. Nil if nothing. function RecipeHandler.get_recipes(item) return util.deep_copy(lookup[item]) @@ -1041,12 +1051,12 @@ function RecipeHandler.get_lookup() end --- Get a list of all the items that have recipes. ----@return string[] items The list of items that have recipes. +---@return integer[] items The list of items that have recipes. function RecipeHandler.get_items() - local items = {} ---@type string[] + local items = {} ---@type integer[] - for item_name in pairs(lookup) do - table.insert(items, item_name) + for item_id in pairs(lookup) do + table.insert(items, item_id) end table.sort(items) -- sort alphabetically @@ -1055,22 +1065,22 @@ function RecipeHandler.get_items() end --- Get a combined list of all items that have recipes and all items that are ingredients in recipes. ----@return string[] items The list of items that have recipes or are ingredients in recipes. +---@return integer[] items The list of items that have recipes or are ingredients in recipes. function RecipeHandler.get_all_items() - local items = {} ---@type string[] + local items = {} ---@type integer[] - local deduplicate = {} ---@type table + local deduplicate = {} ---@type table - for item_name in pairs(lookup) do - table.insert(items, item_name) - deduplicate[item_name] = true + for item_id in pairs(lookup) do + table.insert(items, item_id) + deduplicate[item_id] = true end for _, recipe in ipairs(recipes) do for _, ingredient in ipairs(recipe.ingredients) do - if not deduplicate[ingredient.name] then - table.insert(items, ingredient.name) - deduplicate[ingredient.name] = true + if not deduplicate[ingredient.id] then + table.insert(items, ingredient.id) + deduplicate[ingredient.id] = true end end end @@ -1082,10 +1092,10 @@ end -- Get a list of items that are needed to craft the given item. ---@param plan CraftingPlan The crafting plan to get the items for. ----@return string[] items The list of items that are needed to craft the given item. +---@return integer[] items The list of items that are needed to craft the given item. function RecipeHandler.get_needed_items(plan) - local items = {} ---@type string[] - local item_set = {} ---@type table + local items = {} ---@type integer[] + local item_set = {} ---@type table for _, step in ipairs(plan.steps) do -- Only add it if we haven't already added it, and if it's not a raw material. @@ -1096,9 +1106,9 @@ function RecipeHandler.get_needed_items(plan) for _, ingredient in ipairs(step.recipe.ingredients) do -- Only add it if we haven't already added it, and if it's not a raw material. - if not item_set[ingredient.name] and lookup[ingredient.name] then - table.insert(items, ingredient.name) - item_set[ingredient.name] = true + if not item_set[ingredient.id] and lookup[ingredient.id] then + table.insert(items, ingredient.id) + item_set[ingredient.id] = true end end end @@ -1108,16 +1118,13 @@ function RecipeHandler.get_needed_items(plan) return items end ---- Get a recipe via its name and id ----@param name string The name of the recipe to get. ----@param id number The random id of the recipe to get. +--- Get a recipe via its id. +---@param id number The id of the recipe to get. ---@return Recipe? recipe The recipe, or nil if not found. -function RecipeHandler.get_recipe(name, id) - local item_recipes = lookup[name] - - for i = 1, #item_recipes do - local recipe = item_recipes[i] - if recipe.random_id == id then +function RecipeHandler.get_recipe(id) + for i = 1, #recipes do + local recipe = recipes[i] + if recipe.id == id then return util.deep_copy(recipe) end end @@ -1125,16 +1132,13 @@ function RecipeHandler.get_recipe(name, id) return nil end ---- Edit data for a given recipe ----@param name string The name of the recipe to edit. ----@param id number The random id of the recipe to edit. ----@param data table The new data for the recipe, random ID will be ignored, so you can use RecipeHandler.create_recipe_object to create the data. Anything missing will remain unchanged. -function RecipeHandler.edit_recipe(name, id, data) - local item_recipes = lookup[name] - - for i = 1, #item_recipes do - local recipe = item_recipes[i] - if recipe.random_id == id then +--- Edit data for a given recipe. +---@param id number The id of the recipe to edit. +---@param data table The new data for the recipe, ID will be ignored, so you can use RecipeHandler.create_recipe_object to create the data. Anything missing will remain unchanged. +function RecipeHandler.edit_recipe(id, data) + for i = 1, #recipes do + local recipe = recipes[i] + if recipe.id == id then local preferred = recipe.preferred if data.preferred ~= nil then -- my brain isnt braining right now so this is an if statement instead of an or preferred = data.preferred @@ -1149,36 +1153,36 @@ function RecipeHandler.edit_recipe(name, id, data) end end - error(("No recipe found for %s with id %d"):format(name, id)) + error(("No recipe found with id %d"):format(id)) end ---- Remove a recipe by its name and id ----@param name string The name of the recipe to remove. ----@param id number The random id of the recipe to remove. +--- Remove a recipe by its id. +---@param id number The id of the recipe to remove. function RecipeHandler.remove_recipe(name, id) - local item_recipes = lookup[name] + local s1, s2 = false, false + -- Find the recipe in the main list and remove it. + for i = 1, #recipes do + local recipe = recipes[i] + if recipe.result.id == name and recipe.id == id then + table.remove(recipes, i) + s1 = true + break + end + end - if item_recipes then - -- Find the recipe in the lookup and remove it. + -- Find the recipe in the lookup and remove it. + for _, item_recipes in pairs(lookup) do for i = 1, #item_recipes do local recipe = item_recipes[i] - if recipe.random_id == id then + if recipe.id == id then table.remove(item_recipes, i) - break - end - end - - -- Find the recipe in the recipes list and remove it. - for i = 1, #recipes do - local recipe = recipes[i] - if recipe.result.name == name and recipe.random_id == id then - table.remove(recipes, i) + s2 = true return end end end - error(("No recipe found for %s with id %d"):format(name, id)) + error(("No recipe found for %s with id %d (%s|%s)"):format(name, id, s1, s2)) end --- Copy the save file to a backup. From c819dcbe523c4bdd34bf18912e549348f1e3ae6b Mon Sep 17 00:00:00 2001 From: Matthew W Date: Mon, 4 Mar 2024 00:58:03 -0700 Subject: [PATCH 04/18] Use the new ID and items system --- ui/items/edit.lua | 29 +++++++++++------- ui/items/edit_preferences.lua | 53 +++++++++++++++++++++------------ ui/items/get_item_details.lua | 55 ++++++++++++++++++++++++++--------- ui/items/remove.lua | 21 ++++++++----- ui/items/view.lua | 26 ++++++++++++----- 5 files changed, 127 insertions(+), 57 deletions(-) diff --git a/ui/items/edit.lua b/ui/items/edit.lua index e88f8b0..e637de4 100644 --- a/ui/items/edit.lua +++ b/ui/items/edit.lua @@ -4,21 +4,28 @@ local get_item_details = require "ui.items.get_item_details" local catch_error = require "ui.util.catch_error" local search = require "ui.util.search" local good_response = require "ui.util.good_response" +local items_common = require "ui.items.common" --- Edit item menu -> Search for an item by name, then edit its recipe. ---@param run_menu fun(name: string) The function to run another menu return function(run_menu) -- Get the list of item names - local item_names = recipe_handler.get_items() + local item_ids = recipe_handler.get_items() -- Now we need to collect all recipes for the items -- we will combine the item name with its random ID to make a unique key local recipe_names = {} - for _, item_name in pairs(item_names) do - local recipes = recipe_handler.get_recipes(item_name) + for _, item_id in pairs(item_ids) do + local recipes = recipe_handler.get_recipes(item_id) + local item_name = items_common.get_item_name(item_id) + + if not item_name then + error(("Item name for item ID %d does not exist."):format(item_id), 0) + end + if recipes then for _, recipe in pairs(recipes) do - table.insert(recipe_names, ("%s (%s)"):format(item_name, recipe.random_id)) + table.insert(recipe_names, ("%s (%s)"):format(item_name, recipe.id)) end end end @@ -27,22 +34,22 @@ return function(run_menu) table.sort(recipe_names) -- Search for a recipe - local recipe = search("Select Recipe", recipe_names) + local selected_recipe = search("Select Recipe", recipe_names) - if recipe then + if selected_recipe then -- Get the item name and recipe ID from the recipe name - local item_name, recipe_id = recipe:match("^(.+) %((.-)%)$") + local item_name, recipe_id = selected_recipe:match("^(.+) %((.-)%)$") recipe_id = tonumber(recipe_id) if not recipe_id then - error("You somehow managed to get a recipe ID that is not a number. How did you do that?", 0) + error(("Recipe ID for recipe %s does not exist."):format(selected_recipe), 0) end -- Get the recipe data - local recipe_data = recipe_handler.get_recipe(item_name, recipe_id) + local recipe_data = recipe_handler.get_recipe(recipe_id) if not recipe_data then - error("Between then and now how THE HECK DOES IT NOT EXIST?????????", 0) + error(("Recipe data for recipe %s does not exist."):format(selected_recipe), 0) end -- Get the new recipe data @@ -52,7 +59,7 @@ return function(run_menu) return end - recipe_handler.edit_recipe(item_name, recipe_id, new_recipe_data) + recipe_handler.edit_recipe(recipe_id, new_recipe_data) recipe_handler.save() good_response("Item edited", ("Edited item %s (outputs %d)."):format(new_recipe_data.result.name, new_recipe_data.result.amount)) diff --git a/ui/items/edit_preferences.lua b/ui/items/edit_preferences.lua index bd54b65..01fd7cb 100644 --- a/ui/items/edit_preferences.lua +++ b/ui/items/edit_preferences.lua @@ -2,6 +2,7 @@ local recipe_handler = require "recipe_handler" local search = require "ui.util.search" local good_response = require "ui.util.good_response" +local items_common = require "ui.items.common" --- Item preference menu -> Show items with multiple recipes, then select one to be preferred. ---@param run_menu fun(name: string) The function to run another menu @@ -12,8 +13,16 @@ return function(run_menu) -- Now, search through the list of names for recipes with multiple recipes local duplicate_item_names = {} - for _, item_name in pairs(item_names) do - local recipes = recipe_handler.get_recipes(item_name) + for _, item_id in pairs(item_names) do + local recipes = recipe_handler.get_recipes(item_id) + + -- Get the name of the item + local item_name = items_common.get_item_name(item_id) + + if not item_name then + error(("Item name for item ID %d does not exist."):format(item_id), 0) + end + if recipes and #recipes > 1 then table.insert(duplicate_item_names, item_name) end @@ -23,22 +32,29 @@ return function(run_menu) table.sort(duplicate_item_names) -- Search for a recipe - local item = search("Select Item", duplicate_item_names) + local item_name = search("Select Item", duplicate_item_names) - if not item then return end + if not item_name then return end + + -- Get the ID of the item. + local item_id = items_common.get_item_id(item_name) + + if not item_id then + error(("Item ID for item %s does not exist."):format(item_name), 0) + end -- Get the list of recipes for the item - local recipes = recipe_handler.get_recipes(item) + local item_recipes = recipe_handler.get_recipes(item_id) - if not recipes then - error("WHY DOESN'T IT EXIST?????") + if not item_recipes then + error(("Recipes for item %s (%d) do not exist."):format(item_name, item_id), 0) end - -- Now we need to collect all recipes for the items -- we will combine the item name with its random ID to make a unique key + -- Now we need to collect all recipes for the items -- we will combine the item name with its ID to make a unique key local recipe_names = {} - for _, recipe in pairs(recipes) do - table.insert(recipe_names, ("%s (%s)"):format(item, recipe.random_id)) + for _, item_recipe in pairs(item_recipes) do + table.insert(recipe_names, ("%s (%s)"):format(item_name, item_recipe.id)) end -- Sort the recipe names @@ -49,27 +65,26 @@ return function(run_menu) if not recipe then return end - -- Get the item name and recipe ID from the recipe name - local item_name, recipe_id = recipe:match("^(.+) %((.-)%)$") + local result_item_name, recipe_id = recipe:match("^(.+) %((.-)%)$") recipe_id = tonumber(recipe_id) if not recipe_id then - error("You somehow managed to get a recipe ID that is not a number. How did you do that?", 0) + error(("Recipe ID for recipe %s does not exist."):format(recipe), 0) end -- Get the recipe data - local recipe_data = recipe_handler.get_recipe(item_name, recipe_id) + local recipe_data = recipe_handler.get_recipe(recipe_id) if not recipe_data then - error("Between then and now how THE HECK DOES IT NOT EXIST?????????", 0) + error(("Recipe data for recipe ID %d does not exist."):format(recipe_id), 0) end - for _, recipe in pairs(recipes) do - recipe_handler.edit_recipe(item_name, recipe.random_id, {preferred = false}) + for _, item_recipe in pairs(item_recipes) do + recipe_handler.edit_recipe(item_recipe.id, {preferred = false}) end - recipe_handler.edit_recipe(item_name, recipe_id, {preferred = true}) + recipe_handler.edit_recipe(recipe_id, {preferred = true}) recipe_handler.save() - good_response("Set preference.", ("The preference for %s has been set to the requested recipe. It will be used from now on whenever a recipe requires the item."):format(item)) + good_response("Set preference.", ("The preference for %s has been set to the requested recipe. It will be used from now on whenever a recipe requires the item."):format(result_item_name)) end diff --git a/ui/items/get_item_details.lua b/ui/items/get_item_details.lua index 921e6d3..120339d 100644 --- a/ui/items/get_item_details.lua +++ b/ui/items/get_item_details.lua @@ -45,18 +45,26 @@ local recipe_handler = require "recipe_handler" ---@param default_item_search_name string? The default item name to search for. ---@return Recipe? recipe The new recipe for the item. return function(item_data, default_item_search_name) - local new_data = util.deep_copy(item_data) or { - result = { - name = "", - amount = 1, - fluid = false - }, - ingredients = {}, - machine = machines_common.machines[0].id, - } + local new_data = util.deep_copy(item_data) or recipe_handler.create_recipe_object("", 1, {}, 0, false) + new_data.ingredients = {} -- clear ingredients, since we're going to be adding them manually - local new_name = search("Enter item name", recipe_handler.get_all_items(), true, default_item_search_name) + local item_id_list = recipe_handler.get_items() + local item_name_list = {} + + for _, item_id in pairs(item_id_list) do + local item_name = items_common.get_item_name(item_id) + + if not item_name then + error(("Item name for item ID %d does not exist."):format(item_id), 0) + end + + table.insert(item_name_list, item_name) + end + + table.sort(item_name_list) + + local new_name = search("Enter item name", item_name_list, true, default_item_search_name) if not new_name then return end @@ -83,8 +91,24 @@ return function(item_data, default_item_search_name) end local ingredients = {} + + local all_item_ids = recipe_handler.get_all_items() + local all_item_names = {} + + for _, item_id in pairs(all_item_ids) do + local item_name = items_common.get_item_name(item_id) + + if not item_name then + error(("Item name for item ID %d does not exist."):format(item_id), 0) + end + + table.insert(all_item_names, item_name) + end + + table.sort(all_item_names) + for i = 1, unique_items do - local ingredient_name = search("Select ingredient", recipe_handler.get_all_items(), true) + local ingredient_name = search("Select ingredient", all_item_names, true) if not ingredient_name then return end @@ -94,8 +118,14 @@ return function(item_data, default_item_search_name) return end + local ingredient_id = items_common.get_item_id(ingredient_name) + + if not ingredient_id then + error(("Item ID for item %s does not exist."):format(ingredient_name), 0) + end + table.insert(ingredients, { - name = ingredient_name, + id = ingredient_id, amount = ingredient_count, fluid = false }) @@ -114,7 +144,6 @@ return function(item_data, default_item_search_name) error(("Machine %s not found."):format(machine), 0) end - return recipe_handler.create_recipe_object( new_name, output_count, diff --git a/ui/items/remove.lua b/ui/items/remove.lua index 45de4cb..6dc7527 100644 --- a/ui/items/remove.lua +++ b/ui/items/remove.lua @@ -1,6 +1,7 @@ local recipe_handler = require "recipe_handler" -local good_response = require "ui.util.good_response" +local items_common = require "ui.items.common" +local good_response = require "ui.util.good_response" local search = require "ui.util.search" local confirm = require "ui.util.confirmation_menu" @@ -8,16 +9,22 @@ local confirm = require "ui.util.confirmation_menu" ---@param run_menu fun(name: string) The function to run another menu return function(run_menu) -- Get the list of item names - local item_names = recipe_handler.get_items() + local item_ids = recipe_handler.get_items() -- Now we need to collect all recipes for the items -- we will combine the item name with its random ID to make a unique key local recipe_names = {} - for _, item_name in pairs(item_names) do - local recipes = recipe_handler.get_recipes(item_name) + for _, item_id in pairs(item_ids) do + local recipes = recipe_handler.get_recipes(item_id) + local item_name = items_common.get_item_name(item_id) + + if not item_name then + error(("Item name for item ID %d does not exist."):format(item_id), 0) + end + if recipes then for _, recipe in pairs(recipes) do - table.insert(recipe_names, ("%s (%s)"):format(item_name, recipe.random_id)) + table.insert(recipe_names, ("%s (%s)"):format(item_name, recipe.id)) end end end @@ -34,14 +41,14 @@ return function(run_menu) recipe_id = tonumber(recipe_id) if not recipe_id then - error("You somehow managed to get a recipe ID that is not a number. How did you do that?", 0) + error(("Recipe ID for recipe %s does not exist."):format(recipe), 0) end if not confirm(("Remove %s?"):format(recipe)) then return end - recipe_handler.remove_recipe(item_name, recipe_id) + recipe_handler.remove_recipe(recipe_id) recipe_handler.save() good_response("Item removed", ("Removed item %s."):format(recipe)) end diff --git a/ui/items/view.lua b/ui/items/view.lua index 82cdbbf..d738058 100644 --- a/ui/items/view.lua +++ b/ui/items/view.lua @@ -1,5 +1,6 @@ local recipe_handler = require "recipe_handler" local machines_common = require "ui.machines.common" +local items_common = require "ui.items.common" local good_response = require "ui.util.good_response" local search = require "ui.util.search" @@ -8,16 +9,22 @@ local search = require "ui.util.search" ---@param run_menu fun(name: string) The function to run another menu return function(run_menu) -- Get the list of item names - local item_names = recipe_handler.get_items() + local item_ids = recipe_handler.get_items() -- Now we need to collect all recipes for the items -- we will combine the item name with its random ID to make a unique key local recipe_names = {} - for _, item_name in pairs(item_names) do - local recipes = recipe_handler.get_recipes(item_name) + for _, item_id in pairs(item_ids) do + local recipes = recipe_handler.get_recipes(item_id) + local item_name = items_common.get_item_name(item_id) + + if not item_name then + error(("Item name for item ID %d does not exist."):format(item_id), 0) + end + if recipes then for _, recipe in pairs(recipes) do - table.insert(recipe_names, ("%s (%s)"):format(item_name, recipe.random_id)) + table.insert(recipe_names, ("%s (%s)"):format(item_name, recipe.id)) end end end @@ -41,7 +48,7 @@ return function(run_menu) end -- Get the recipe data - local recipe_data = recipe_handler.get_recipe(item_name, recipe_id) + local recipe_data = recipe_handler.get_recipe(recipe_id) if not recipe_data then error("Between then and now how THE HECK DOES IT NOT EXIST?????????", 0) @@ -50,7 +57,12 @@ return function(run_menu) local ingredients_text = {} local ingredient_formatter = " x%d %s %s" for _, ingredient in pairs(recipe_data.ingredients) do - table.insert(ingredients_text, ingredient_formatter:format(ingredient.amount, ingredient.name, ingredient.fluid and "(fluid)" or "")) + local ingredient_name = items_common.get_item_name(ingredient.name) + + if not ingredient_name then + error(("Ingredient name for item ID %d does not exist."):format(ingredient.name), 0) + end + table.insert(ingredients_text, ingredient_formatter:format(ingredient.amount, ingredient_name, ingredient.fluid and "(fluid)" or "")) end -- Find the machine by its id @@ -62,7 +74,7 @@ return function(run_menu) machine_name, recipe_data.result.amount, recipe_data.preferred and "Yes" or "No", - recipe_data.random_id, + recipe_data.id, table.concat(ingredients_text, "\n") ) ) From e3cedb9be5a4d45f2cf275166c877aa505155eaf Mon Sep 17 00:00:00 2001 From: Matthew W Date: Mon, 4 Mar 2024 01:05:11 -0700 Subject: [PATCH 05/18] Removing a machine should invalidate any recipes which use it. --- ui/machines/remove.lua | 30 ++++++++++++++++++++++++++++-- 1 file changed, 28 insertions(+), 2 deletions(-) diff --git a/ui/machines/remove.lua b/ui/machines/remove.lua index afe604e..47509ef 100644 --- a/ui/machines/remove.lua +++ b/ui/machines/remove.lua @@ -2,6 +2,7 @@ local common = require "ui.machines.common" local search = require "ui.util.search" local catch_error = require "ui.util.catch_error" local confirmation_menu = require "ui.util.confirmation_menu" +local recipe_handler = require "recipe_handler" --- Remove machine menu: Search for a machine by name, then remove it, if the user confirms. @@ -9,6 +10,7 @@ local confirmation_menu = require "ui.util.confirmation_menu" return function(run_menu) local machine_names = {} local machine_names_to_ids = {} + for _, data in pairs(common.machines) do table.insert(machine_names, data.name) machine_names_to_ids[data.name] = data.id @@ -16,8 +18,32 @@ return function(run_menu) local machine = search("Select machine for removal.", machine_names) if machine then - if confirmation_menu(("Remove %s?"):format(machine)) then - catch_error(common.remove_machine, machine_names_to_ids[machine]) + -- Given the machine id, search for recipes which use this machine and count + -- them and store them + local machine_id = machine_names_to_ids[machine] + local invalid_recipe_count = 0 + local invalidated_recipes = {} + for _, item_id in pairs(recipe_handler.get_items()) do + local recipes = recipe_handler.get_recipes(item_id) + + if recipes then + for _, recipe in pairs(recipes) do + if recipe.machine == machine_id then + invalid_recipe_count = invalid_recipe_count + 1 + table.insert(invalidated_recipes, recipe.id) + end + end + end + end + + if confirmation_menu(("Remove %s? This will also delete %d recipe(s)."):format(machine, invalid_recipe_count)) then + catch_error(common.remove_machine, machine_id) + for _, recipe_id in pairs(invalidated_recipes) do + catch_error(recipe_handler.remove_recipe, recipe_id) + end + + recipe_handler.save() + common.save() end end end From 7a7e0c4fd43b2a8e2ff081b5c0507a46676f4402 Mon Sep 17 00:00:00 2001 From: Matthew W Date: Mon, 4 Mar 2024 01:08:35 -0700 Subject: [PATCH 06/18] Use items common in crafting menu --- ui/crafting_menu.lua | 33 +++++++++++++++++++-------------- 1 file changed, 19 insertions(+), 14 deletions(-) diff --git a/ui/crafting_menu.lua b/ui/crafting_menu.lua index 20d1c1a..e5cfb58 100644 --- a/ui/crafting_menu.lua +++ b/ui/crafting_menu.lua @@ -1,6 +1,7 @@ local recipe_handler = require "recipe_handler" - local machines_common = require "ui.machines.common" +local items_common = require "ui.items.common" + local crafting_output = require "ui.util.good_response" local search = require "ui.util.search" local get_integer = require "ui.util.get_integer" @@ -11,7 +12,7 @@ local file_helper = require "file_helper" --- Crafting menu -> Search for an item, then get a crafting plan for it. ---@param run_menu fun(name: string) The function to run another menu return function(run_menu) - local item_names = recipe_handler.get_items() + local item_ids = recipe_handler.get_items() -- Calculate current recipe selections. -- Steps: @@ -28,15 +29,13 @@ return function(run_menu) -- Step 2: local machines = machines_common.machines - -- Step 3: - local recipes = recipe_handler.get_lookup() - - -- Step 4: local preferred_recipes = {} - for _, item in ipairs(item_names) do - local item_recipes = recipes[item] + for _, item_id in ipairs(item_ids) do + -- Step 3: + local item_recipes = recipe_handler.get_recipes(item_id) + -- Step 4: if item_recipes then if #item_recipes > 1 then local preferred_recipe = nil ---@type Recipe? @@ -55,7 +54,7 @@ return function(run_menu) end if preferred_recipe then - preferred_recipes[item] = preferred_recipe + preferred_recipes[item_id] = preferred_recipe end end end @@ -63,11 +62,17 @@ return function(run_menu) while true do - local item = search("Select item to craft", item_names) - if not item then + local item_name = search("Select item to craft", item_ids) + if not item_name then return end + local item_id = items_common.get_item_id(item_name) + + if not item_id then + error(("Item ID for item %s does not exist."):format(item_name), 0) + end + local needed = get_integer("How many do you need?", 1, 1) if not needed then return @@ -78,7 +83,7 @@ return function(run_menu) recipe_handler.build_recipe_graph() repeat local plan = recipe_handler.get_first_recipe( - item, + item_id, needed, 100, preferred_recipes, @@ -92,7 +97,7 @@ return function(run_menu) text_plan = text_plan .. "\n\nThe above crafting plan was also written to crafting_plan.txt." key_pressed = crafting_output( - ("Crafting Plan - x%d %s"):format(needed, item), + ("Crafting Plan - x%d %s"):format(needed, item_name), text_plan, "Press enter to continue, or 1 to select items to remove the cost of from the plan.", keys.enter, @@ -105,7 +110,7 @@ return function(run_menu) -- Remove the main item from the list of needed items. for i, needed_item in ipairs(needed_items) do - if needed_item == item then + if needed_item == item_name then table.remove(needed_items, i) break end From a6445717fb277a179ac957a9d7e7427ea08a8e1e Mon Sep 17 00:00:00 2001 From: Matthew W Date: Mon, 4 Mar 2024 01:09:08 -0700 Subject: [PATCH 07/18] update .gitignore --- .gitignore | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 05071de..aa3b9a2 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,9 @@ crafting_plan.txt lib/basalt.lua -data \ No newline at end of file +data +bruh +generaty.lua +links.txt +test.txt +xorify.lua +extern-lls \ No newline at end of file From 742afe86fdaba4843c8db713856cf7dd7dce0bfa Mon Sep 17 00:00:00 2001 From: Matthew W Date: Mon, 4 Mar 2024 01:09:30 -0700 Subject: [PATCH 08/18] Refactor code to handle loading errors --- helper.lua | 23 +++++++++++++++++------ 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/helper.lua b/helper.lua index 4eadfb6..9992fcd 100644 --- a/helper.lua +++ b/helper.lua @@ -15,12 +15,23 @@ package.path = package.path .. ";lib/?.lua;lib/?/init.lua" local function main() - -- Initial setup: Load everything - local machines_common = require "ui.machines.common" - machines_common.load() - - local items_common = require "ui.items.common" - items_common.load() + local load_ok, err = pcall(function() + -- Initial setup: Load everything + local machines_common = require "ui.machines.common" + machines_common.load() + + local items_common = require "ui.items.common" + items_common.load() + + local recipe_handler = require "recipe_handler" + recipe_handler.load() + end) + + if not load_ok then + printError("Error while loading data:", err) + require "data_fixer_upper".check() + return + end require "data_fixer_upper".check() From 257ff52222497e8fbc0ca049673a2f15b07cc3b2 Mon Sep 17 00:00:00 2001 From: Matthew W Date: Mon, 4 Mar 2024 01:13:49 -0700 Subject: [PATCH 09/18] Provide detailed error messages --- lib/recipe_handler.lua | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/lib/recipe_handler.lua b/lib/recipe_handler.lua index 2a5fe6a..c44c660 100644 --- a/lib/recipe_handler.lua +++ b/lib/recipe_handler.lua @@ -203,12 +203,12 @@ function RecipeHandler.load() for i = 1, lines.n do local line = lines[i] - local recipe = RecipeHandler.parse_recipe(line) + local recipe, err = RecipeHandler.parse_recipe(line) if recipe then table.insert(recipes, recipe) else - error(("Failed to parse recipe on line %d: %s"):format(i, line)) + error(("Failed to parse recipe on line %d: %s\n\n%s"):format(i, line, err)) end if i % 1000 == 0 then @@ -226,19 +226,20 @@ end --- Parse a recipe from a string. ---@param line string The string to parse the recipe from. ---@return Recipe? recipe The recipe parsed from the string. +---@return string? error The error message if the recipe could not be parsed. function RecipeHandler.parse_recipe(line) local recipe = textutils.unserialize(line) if not recipe then - return nil + return nil, "Failed to unserialize recipe." end if not recipe.result then - return nil + return nil, "No result found in recipe." end if not recipe.result.id then - return nil + return nil, "No result id found in recipe." end if not recipe.enabled then @@ -254,7 +255,7 @@ function RecipeHandler.parse_recipe(line) end if not recipe.ingredients then - return nil + return nil, "No ingredients found in recipe." end if not recipe.machine then @@ -265,7 +266,7 @@ function RecipeHandler.parse_recipe(line) local ingredient = recipe.ingredients[i] if not ingredient.id then - return nil + return nil, ("No id found for ingredient %d in recipe."):format(i) end if not ingredient.amount then From a300334a28e6a3cf5e2d48c076c8697f6d57ac2d Mon Sep 17 00:00:00 2001 From: Matthew W Date: Mon, 4 Mar 2024 01:14:41 -0700 Subject: [PATCH 10/18] Fix minor issues --- lib/data_fixer_upper.lua | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/lib/data_fixer_upper.lua b/lib/data_fixer_upper.lua index 7e82a6f..3b984a5 100644 --- a/lib/data_fixer_upper.lua +++ b/lib/data_fixer_upper.lua @@ -94,7 +94,7 @@ function fixes.machine_id_needed() error(("Data fixer upper failed: No ID was generated for machine %s."):format(recipe.machine), 0) end recipe_count = recipe_count + 1 - recipe_handler.edit_recipe(name, recipe.random_id, { + recipe_handler.edit_recipe(recipe.id, { machine = machine_names_to_ids[recipe.machine] }) end @@ -133,7 +133,7 @@ function fixes.recipe_machine_needed() if not machine then error(("Data fixer upper failed: No ID known for machine %s."):format(recipe.machine), 0) end - recipe_handler.edit_recipe(name, recipe.random_id, { + recipe_handler.edit_recipe(recipe.id, { machine = machine }) end @@ -194,11 +194,12 @@ function fixer_upper.check() if needs_run then term.setTextColor(colors.white) - term.setCursorPos(1, 1) - term.clear() + print() -- Ensure the cursor is on the screen. print("Data Fixer Upper needs to run, press 'q' to quit, or any other key to continue.") - print("Your data will be backed up.") + term.setTextColor(colors.yellow) + print(" Your data will be backed up.") + term.setTextColor(colors.white) print("\nModules:") for check_name, result in pairs(results) do print("-", check_name, result and "needs to be run." or "is OK.") From abd47084c2ee6f6ce92a3700e512b182c823ccad Mon Sep 17 00:00:00 2001 From: Matthew W Date: Mon, 4 Mar 2024 02:04:53 -0700 Subject: [PATCH 11/18] Expose save file names. --- lib/recipe_handler.lua | 21 +++++++++++---------- ui/items/common.lua | 14 +++++++++++--- ui/machines/common.lua | 13 +++++++------ 3 files changed, 29 insertions(+), 19 deletions(-) diff --git a/lib/recipe_handler.lua b/lib/recipe_handler.lua index c44c660..5e63997 100644 --- a/lib/recipe_handler.lua +++ b/lib/recipe_handler.lua @@ -90,8 +90,6 @@ local function prints(s, ...) end end -local SAVE_FILE = "recipes.list" - ---@type RecipeList local recipes = {} ---@type RecipeLookup @@ -194,12 +192,15 @@ local function zero_recipe_graph() end ---@class RecipeHandler -local RecipeHandler = {} +local RecipeHandler = { + SAVE_FILE = "recipes.list", + BACKUP_FILE = "recipes.list.bak" +} --- Load the recipes from the given file. WARNING: This wipes the currently loaded recipe list first, then loads the recipes. function RecipeHandler.load() recipes = {} ---@type RecipeList - local lines = file_helper:get_lines(SAVE_FILE) + local lines = file_helper:get_lines(RecipeHandler.SAVE_FILE) for i = 1, lines.n do local line = lines[i] @@ -305,7 +306,7 @@ function RecipeHandler.save() table.insert(lines, line) end - file_helper:write(SAVE_FILE, table.concat(lines, "\n")) + file_helper:write(RecipeHandler.SAVE_FILE, table.concat(lines, "\n")) end --- Build/rebuild the recipe graph. @@ -1154,17 +1155,17 @@ function RecipeHandler.edit_recipe(id, data) end end - error(("No recipe found with id %d"):format(id)) + error(("No recipe found with id %d"):format(id), 2) end --- Remove a recipe by its id. ---@param id number The id of the recipe to remove. -function RecipeHandler.remove_recipe(name, id) +function RecipeHandler.remove_recipe(id) local s1, s2 = false, false -- Find the recipe in the main list and remove it. for i = 1, #recipes do local recipe = recipes[i] - if recipe.result.id == name and recipe.id == id then + if recipe.id == id then table.remove(recipes, i) s1 = true break @@ -1183,12 +1184,12 @@ function RecipeHandler.remove_recipe(name, id) end end - error(("No recipe found for %s with id %d (%s|%s)"):format(name, id, s1, s2)) + error(("No recipe found for item with id %d (%s|%s)"):format(id, s1, s2), 2) end --- Copy the save file to a backup. function RecipeHandler.backup_save() - file_helper:write(SAVE_FILE .. ".bak", file_helper:get_all(SAVE_FILE)) + file_helper:write(RecipeHandler.BACKUP_FILE, file_helper:get_all(RecipeHandler.SAVE_FILE)) end return RecipeHandler diff --git a/ui/items/common.lua b/ui/items/common.lua index 031c63d..e7b5f03 100644 --- a/ui/items/common.lua +++ b/ui/items/common.lua @@ -17,7 +17,10 @@ local file_helper = require "file_helper":instanced("data") ---@class items_common local common = { ---@type table - item_lookup = {} + item_lookup = {}, + + SAVE_FILE = "items.lson", + BACKUP_FILE = "items.lson.bak" } --- Generate a unique id for an item. @@ -76,7 +79,7 @@ end --- Load the recipes and items from a file. function common.load() -- Convert from a list to save a little bit of space - local list = file_helper:unserialize("items.lson", {}) + local list = file_helper:unserialize(common.SAVE_FILE, {}) common.item_lookup = {} for _, item in ipairs(list) do @@ -92,7 +95,12 @@ function common.save() table.insert(list, item) end - file_helper:serialize("items.lson", list) + file_helper:serialize(common.SAVE_FILE, list) +end + +--- Backup the recipes and items to a file. +function common.backup_save() + file_helper:write(common.BACKUP_FILE, file_helper:get_all(common.SAVE_FILE)) end return common diff --git a/ui/machines/common.lua b/ui/machines/common.lua index 6174ffa..8dc949b 100644 --- a/ui/machines/common.lua +++ b/ui/machines/common.lua @@ -16,10 +16,11 @@ local common = { preference_level = 0, id = 0, } - } -} + }, -local SAVE_FILE = "machines.lson" + SAVE_FILE = "machines.lson", + BACKUP_FILE = "machines.lson.bak" +} --- Generate a unique integer for a machine. ---@return integer id The unique integer. @@ -48,7 +49,7 @@ end --- Load the list of machine names from the file. ---@return table list The list of machines. function common.load() - local list = file_helper:unserialize(SAVE_FILE, {}) + local list = file_helper:unserialize(common.SAVE_FILE, {}) common.machines = list -- Always ensure the crafting table exists @@ -62,7 +63,7 @@ end --- Save the list of machine names to the file. function common.save() - file_helper:serialize(SAVE_FILE, common.machines, true) + file_helper:serialize(common.SAVE_FILE, common.machines, true) end --- Edit information about a machine. @@ -145,7 +146,7 @@ end --- Backup the save file. function common.backup_save() - file_helper:write(SAVE_FILE .. ".bak", file_helper:get_all(SAVE_FILE)) + file_helper:write(common.BACKUP_FILE, file_helper:get_all(common.SAVE_FILE)) end return common From a43a2c337c09d77376c35ee061f599391d1575d6 Mon Sep 17 00:00:00 2001 From: Matthew W Date: Mon, 4 Mar 2024 02:33:03 -0700 Subject: [PATCH 12/18] Add `item_data_not_generated` data fixer module --- lib/data_fixer_upper.lua | 189 ++++++++++++++++++++++++++++++++++++++- 1 file changed, 185 insertions(+), 4 deletions(-) diff --git a/lib/data_fixer_upper.lua b/lib/data_fixer_upper.lua index 3b984a5..b15fd63 100644 --- a/lib/data_fixer_upper.lua +++ b/lib/data_fixer_upper.lua @@ -1,6 +1,7 @@ local file_helper = require "file_helper":instanced("data") local recipe_handler = require "recipe_handler" local machines_common = require "ui.machines.common" +local items_common = require "ui.items.common" local fixer_upper = {} @@ -145,6 +146,108 @@ function fixes.recipe_machine_needed() recipe_handler.save() end +--- Fix item data that is not generated. +function fixes.item_data_not_generated() + -- Search all recipes for items that don't have data. + -- We are looking for the following: + -- 1. recipe.id is nil + -- 1. a) recipe.random_id exists + -- 1. fix: if random_id exists, set id to random_id, otherwise generate a new id. + -- 2. recipe.result.id is nil + -- 2. a) recipe.result.name exists instead + -- 2. fix: if name exists, set id to the id of the item with that name -- we may need to generate a new id if the item doesn't exist. + -- 2. fail: if name doesn't exist, we can't fix this + -- 3. Check each ingredient, ingredient.id is nil + -- 3. a) ingredient.name exists + -- 3. fix: if name exists, set id to the id of the item with that name -- we may need to generate a new id if the item doesn't exist. + -- 3. fail: if name doesn't exist, we can't fix this + + -- Ensure that items common loaded OK, if not, fail. + local loaded_ok, err = pcall(items_common.load) + if not loaded_ok then + error(("Data fixer upper failed: Cannot load items_common: %s"):format(err), 0) + end + + -- Manually load the data from the file. + local lines = file_helper:get_lines(recipe_handler.SAVE_FILE) + local recipes = {} + + for _, line in ipairs(lines) do + local recipe = textutils.unserialize(line) --[[@as Recipe?]] + table.insert(recipes, recipe) + end + + -- Check for invalid data. + local recipe_count = 0 + + local used_recipe_ids = {} -- For manually generating IDs. + + for i, recipe in ipairs(recipes) do + -- 1. + if not recipe.id then + recipe_count = recipe_count + 1 + if recipe.random_id then + recipe.id = recipe.random_id + + -- 1. a) + recipe.random_id = nil + else + repeat + recipe.id = math.random(-1000000, 1000000) + until not used_recipe_ids[recipe.id] + end + + used_recipe_ids[recipe.id] = true + end + + -- 2. + if not recipe.result.id then + recipe_count = recipe_count + 1 + if recipe.result.name then + local id = items_common.get_item_id(recipe.result.name) + if id then + recipe.result.id = id + else + recipe.result.id = items_common.add_item(recipe.result.name) + end + + -- 2. a) + recipe.result.name = nil + else + error(("Data fixer upper failed: Both ID and name are missing for recipe %d."):format(i), 0) + end + end + + -- 3. + for j, ingredient in ipairs(recipe.ingredients) do + if not ingredient.id then + recipe_count = recipe_count + 1 + if ingredient.name then + local id = items_common.get_item_id(ingredient.name) + if id then + ingredient.id = id + else + ingredient.id = items_common.add_item(ingredient.name) + end + + -- 3. a) + ingredient.name = nil + else + error(("Data fixer upper failed: Both ID and name are missing for ingredient %d in recipe %d."):format(j, i), 0) + end + end + end + end + + -- Save the data back to the file. + local serialized = {} + for _, recipe in ipairs(recipes) do + table.insert(serialized, textutils.serialize(recipe, {compact=true})) + end + + file_helper:write(recipe_handler.SAVE_FILE, table.concat(serialized, "\n")) +end + local checks = {} function checks.duplicate_machine_names() @@ -179,9 +282,53 @@ function checks.recipe_machine_needed() return false end +--- Check if item data is not generated. +function checks.item_data_not_generated() + -- Search all recipes for items that don't have data. + -- We are looking for the following: + -- 1. recipe.id is nil + -- 1. a) recipe.random_id exists + -- 2. recipe.result.id is nil + -- 2. a) recipe.result.name exists instead + -- 3. Check each ingredient, ingredient.id is nil + -- 3. a) ingredient.name exists + -- + -- We also want to strip out any extra data, so if recipe.id is not nil, but recipe.random_id exists (or recipe.name) we want to remove it. + + -- Manually load the data from the file. + local lines = file_helper:get_lines(recipe_handler.SAVE_FILE) + local recipes = {} + + for _, line in ipairs(lines) do + local recipe = textutils.unserialize(line) --[[@as Recipe?]] + table.insert(recipes, recipe) + end + + -- Check for invalid data. + for _, recipe in ipairs(recipes) do + -- Missing cases + if not recipe.id or not recipe.result.id then + return true + end + + -- Extra cases + if recipe.random_id or recipe.result.name then + return true + end + + -- Ingredients... + for _, ingredient in ipairs(recipe.ingredients) do + -- Extra and missing case + if not ingredient.id or ingredient.name then + return true + end + end + end + + return false +end + function fixer_upper.check() - term.clear() - term.setCursorPos(1, 1) local results = {} local needs_run = false for check_name, checker in pairs(checks) do @@ -199,9 +346,43 @@ function fixer_upper.check() print("Data Fixer Upper needs to run, press 'q' to quit, or any other key to continue.") term.setTextColor(colors.yellow) print(" Your data will be backed up.") - term.setTextColor(colors.white) + + -- Check if any of the backup files exist already. + + -- Machines common backup + if file_helper:exists(machines_common.BACKUP_FILE) then + term.setBackgroundColor(colors.red) + term.setTextColor(colors.white) + write(" Warning: Machine data backup exists, and will be overwritten.") + term.setBackgroundColor(colors.black) + term.setTextColor(colors.white) + print() + end + + -- Recipe handler backup + if file_helper:exists(recipe_handler.BACKUP_FILE) then + term.setBackgroundColor(colors.red) + term.setTextColor(colors.white) + write(" Warning: Recipe data backup exists, and will be overwritten.") + term.setBackgroundColor(colors.black) + term.setTextColor(colors.white) + print() + end + + -- Items common backup + if file_helper:exists(items_common.BACKUP_FILE) then + term.setBackgroundColor(colors.red) + term.setTextColor(colors.white) + write(" Warning: Item data backup exists, and will be overwritten.") + term.setBackgroundColor(colors.black) + term.setTextColor(colors.white) + print() + end + + print("\nModules:") for check_name, result in pairs(results) do + term.setTextColor(result and colors.white or colors.gray) print("-", check_name, result and "needs to be run." or "is OK.") end sleep() -- Ensure the event queue is cleared. @@ -220,7 +401,7 @@ function fixer_upper.check() if results[check_name] then term.setTextColor(colors.white) print("Running", check_name, "fixer...") - term.setTextColor(colors.lightGray) + term.setTextColor(colors.lightGray) fixer() From 789c86d1f5b667e083632c842af918513b81c6f0 Mon Sep 17 00:00:00 2001 From: Matthew W Date: Mon, 4 Mar 2024 02:36:33 -0700 Subject: [PATCH 13/18] Items common needs to be saved as well. --- lib/data_fixer_upper.lua | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lib/data_fixer_upper.lua b/lib/data_fixer_upper.lua index b15fd63..b40b42c 100644 --- a/lib/data_fixer_upper.lua +++ b/lib/data_fixer_upper.lua @@ -246,6 +246,9 @@ function fixes.item_data_not_generated() end file_helper:write(recipe_handler.SAVE_FILE, table.concat(serialized, "\n")) + + -- Save the items data back to the file. + items_common.save() end local checks = {} From 7621e0d0c2d1cba1a4c189a5d489cd73e74c0d2a Mon Sep 17 00:00:00 2001 From: Matthew W Date: Mon, 4 Mar 2024 02:36:41 -0700 Subject: [PATCH 14/18] Fix boogs --- ui/items/common.lua | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/ui/items/common.lua b/ui/items/common.lua index e7b5f03..0373336 100644 --- a/ui/items/common.lua +++ b/ui/items/common.lua @@ -30,7 +30,7 @@ local function generate_unique_id() repeat id = math.random(-1000000, 1000000) - until not common.item_ids[id] + until not common.item_lookup[id] return id end @@ -95,12 +95,14 @@ function common.save() table.insert(list, item) end - file_helper:serialize(common.SAVE_FILE, list) + file_helper:serialize(common.SAVE_FILE, list, true) end --- Backup the recipes and items to a file. function common.backup_save() - file_helper:write(common.BACKUP_FILE, file_helper:get_all(common.SAVE_FILE)) + if file_helper:exists(common.SAVE_FILE) then + file_helper:write(common.BACKUP_FILE, file_helper:get_all(common.SAVE_FILE)) + end end return common From 1bc7071d8c109a96e54e3f00ec99428335389593 Mon Sep 17 00:00:00 2001 From: Matthew W Date: Mon, 4 Mar 2024 02:49:23 -0700 Subject: [PATCH 15/18] Fix displaying IDs, fix multiple errors. --- helper.lua | 3 +++ lib/recipe_handler.lua | 11 +++++++---- ui/crafting_menu.lua | 9 ++++++++- ui/items/view.lua | 4 ++-- 4 files changed, 20 insertions(+), 7 deletions(-) diff --git a/helper.lua b/helper.lua index 9992fcd..7d9fe3b 100644 --- a/helper.lua +++ b/helper.lua @@ -29,6 +29,9 @@ local function main() if not load_ok then printError("Error while loading data:", err) + + print("Checking if data can be fixed...") + require "data_fixer_upper".check() return end diff --git a/lib/recipe_handler.lua b/lib/recipe_handler.lua index 5e63997..508ff72 100644 --- a/lib/recipe_handler.lua +++ b/lib/recipe_handler.lua @@ -963,6 +963,9 @@ end function RecipeHandler.get_plan_as_text(plan, plan_number) local textual = {} ---@type string[] + -- Get the lookup of item ids to names + local item_lookup = items_common.get_items() + table.insert(textual, "===============") table.insert(textual, ("Crafting plan #%d raw material cost:"):format(plan_number or 1)) @@ -974,7 +977,7 @@ function RecipeHandler.get_plan_as_text(plan, plan_number) table.sort(raw_materials, function(a, b) return a[1] < b[1] end) for _, data in ipairs(raw_materials) do - table.insert(textual, (" %s: %d"):format(data[1], data[2])) + table.insert(textual, (" %s: %d"):format(item_lookup[data[1]] and item_lookup[data[1]].name or "Unknown Item", data[2])) end table.insert(textual, "===============") @@ -993,7 +996,7 @@ function RecipeHandler.get_plan_as_text(plan, plan_number) end table.insert(ingredient_textual, ingredient_formatter:format( ingredient.amount * step.crafts, - ingredient.id, + item_lookup[ingredient.id] and item_lookup[ingredient.id].name or "Unknown Item", ingredient.amount * step.crafts > 1 and "s" or "", ingredient.fluid and " (fluid)" or "" )) @@ -1004,9 +1007,9 @@ function RecipeHandler.get_plan_as_text(plan, plan_number) end local line = line_formatter:format( - machines_common.machines[step.recipe.machine].id, + machines_common.machines[step.recipe.machine] and machines_common.machines[step.recipe.machine].name or "Unknown Machine", step.output_count, - step.recipe.result.id, + item_lookup[step.recipe.result.id] and item_lookup[step.recipe.result.id].name or "Unknown Item", step.output_count > 1 and "s" or "", table.concat(ingredient_textual) ) diff --git a/ui/crafting_menu.lua b/ui/crafting_menu.lua index e5cfb58..6f8edc7 100644 --- a/ui/crafting_menu.lua +++ b/ui/crafting_menu.lua @@ -60,9 +60,16 @@ return function(run_menu) end end + -- Get all item names from item ids + local item_names = {} + for _, item_id in ipairs(item_ids) do + table.insert(item_names, items_common.get_item_name(item_id)) + end + + table.sort(item_names) while true do - local item_name = search("Select item to craft", item_ids) + local item_name = search("Select item to craft", item_names) if not item_name then return end diff --git a/ui/items/view.lua b/ui/items/view.lua index d738058..9cd8957 100644 --- a/ui/items/view.lua +++ b/ui/items/view.lua @@ -57,10 +57,10 @@ return function(run_menu) local ingredients_text = {} local ingredient_formatter = " x%d %s %s" for _, ingredient in pairs(recipe_data.ingredients) do - local ingredient_name = items_common.get_item_name(ingredient.name) + local ingredient_name = items_common.get_item_name(ingredient.id) if not ingredient_name then - error(("Ingredient name for item ID %d does not exist."):format(ingredient.name), 0) + error(("Ingredient name for item ID %d does not exist."):format(ingredient.id), 0) end table.insert(ingredients_text, ingredient_formatter:format(ingredient.amount, ingredient_name, ingredient.fluid and "(fluid)" or "")) end From b49960f47ba51fc4aebe94ec48d76323fffaa681 Mon Sep 17 00:00:00 2001 From: Matthew W Date: Mon, 4 Mar 2024 02:50:05 -0700 Subject: [PATCH 16/18] Update version string to 0.4.0 Going to test around a bit with this before pushing to main. --- ui/title.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui/title.lua b/ui/title.lua index 4e40d2b..c478a07 100644 --- a/ui/title.lua +++ b/ui/title.lua @@ -4,7 +4,7 @@ local PrimeUI = require "PrimeUI_cherrypicked" local MAIN_TITLE = "Microcraft Helper" -local VERSION_STRING = "0.3.2" +local VERSION_STRING = "0.4.0" local COMBINED = ("%s %s"):format(MAIN_TITLE, VERSION_STRING) --- Create the title of the page. From 71c4053a54b80c78a0884137a0308b8ad2279c35 Mon Sep 17 00:00:00 2001 From: Matthew W Date: Mon, 4 Mar 2024 02:57:04 -0700 Subject: [PATCH 17/18] No need to sort IDs --- lib/recipe_handler.lua | 6 ------ 1 file changed, 6 deletions(-) diff --git a/lib/recipe_handler.lua b/lib/recipe_handler.lua index 508ff72..6b185ac 100644 --- a/lib/recipe_handler.lua +++ b/lib/recipe_handler.lua @@ -1064,8 +1064,6 @@ function RecipeHandler.get_items() table.insert(items, item_id) end - table.sort(items) -- sort alphabetically - return items end @@ -1090,8 +1088,6 @@ function RecipeHandler.get_all_items() end end - table.sort(items) -- sort alphabetically - return items end @@ -1118,8 +1114,6 @@ function RecipeHandler.get_needed_items(plan) end end - table.sort(items) -- sort alphabetically - return items end From fcd9c95c2e6ec8b6d9d7132c84f5cf99ba4e8fb9 Mon Sep 17 00:00:00 2001 From: Matthew W Date: Mon, 4 Mar 2024 03:39:28 -0700 Subject: [PATCH 18/18] Fix editing recipes, not saving item data. --- lib/recipe_handler.lua | 19 ++++++++++++++++++- ui/items/common.lua | 11 +++++++++++ ui/items/edit.lua | 8 +++++--- ui/items/get_item_details.lua | 6 ++++-- ui/items/remove.lua | 2 +- ui/util/search.lua | 2 +- 6 files changed, 40 insertions(+), 8 deletions(-) diff --git a/lib/recipe_handler.lua b/lib/recipe_handler.lua index 6b185ac..c98c279 100644 --- a/lib/recipe_handler.lua +++ b/lib/recipe_handler.lua @@ -909,13 +909,30 @@ end ---@param machine integer? The machine used to craft the item. Defaults to "crafting table". ---@param is_fluid boolean? Whether or not the item is a fluid. Defaults to false. ---@param is_preferred boolean? Whether or not the recipe is preferred. Defaults to false. +---@param previous_name string? The previous name of the item, if editing an existing recipe. ---@return Recipe recipe The recipe created. -function RecipeHandler.create_recipe_object(item_name, output_count, ingredients, machine, is_fluid, is_preferred) +function RecipeHandler.create_recipe_object(item_name, output_count, ingredients, machine, is_fluid, is_preferred, previous_name) -- Check items_common for the item id. If it doesn't exist, add it. local item_id = items_common.get_item_id(item_name) + + if previous_name and not item_id then + -- The item id doesn't exist, but we have a previous name. This means the + -- item was renamed, so we need to get the id from the previous name. + item_id = items_common.get_item_id(previous_name) + if not item_id then + -- The previous name doesn't exist, this is an error. + error(("Previous name %s does not exist."):format(previous_name), 2) + end + + -- We need to update the item name in items_common. + items_common.edit_item(item_id, item_name) + elseif not previous_name and not item_id then + -- The item id doesn't exist, and we don't have a previous name. This means + -- the item is new, so we need to add it. item_id = items_common.add_item(item_name) end + ---@cast item_id integer It can no longer be nil after the above. ---@type Recipe return { diff --git a/ui/items/common.lua b/ui/items/common.lua index 0373336..173c3d3 100644 --- a/ui/items/common.lua +++ b/ui/items/common.lua @@ -43,13 +43,24 @@ function common.add_item(name) local item = {name = name, id = id} common.item_lookup[id] = item + + common.save() return id end +--- Edit an item in the list of items. +---@param id integer The unique id of the item. +---@param name string The new name of the item. +function common.edit_item(id, name) + common.item_lookup[id].name = name + common.save() +end + --- Remove an item from the list of items. ---@param id integer The unique id of the item. function common.remove_item(id) common.item_lookup[id] = nil + common.save() end --- Get the list of items. diff --git a/ui/items/edit.lua b/ui/items/edit.lua index e637de4..f8097b2 100644 --- a/ui/items/edit.lua +++ b/ui/items/edit.lua @@ -1,10 +1,10 @@ local recipe_handler = require "recipe_handler" +local items_common = require "ui.items.common" local get_item_details = require "ui.items.get_item_details" local catch_error = require "ui.util.catch_error" local search = require "ui.util.search" local good_response = require "ui.util.good_response" -local items_common = require "ui.items.common" --- Edit item menu -> Search for an item by name, then edit its recipe. ---@param run_menu fun(name: string) The function to run another menu @@ -53,7 +53,7 @@ return function(run_menu) end -- Get the new recipe data - local ok, new_recipe_data = catch_error(get_item_details, recipe_data, recipe_data.result.name) + local ok, new_recipe_data = catch_error(get_item_details, recipe_data, item_name) if not ok or not new_recipe_data then return @@ -62,6 +62,8 @@ return function(run_menu) recipe_handler.edit_recipe(recipe_id, new_recipe_data) recipe_handler.save() - good_response("Item edited", ("Edited item %s (outputs %d)."):format(new_recipe_data.result.name, new_recipe_data.result.amount)) + local new_item_name = items_common.get_item_name(new_recipe_data.result.id) + + good_response("Item edited", ("Edited item %s (outputs %d)."):format(new_item_name, new_recipe_data.result.amount)) end end diff --git a/ui/items/get_item_details.lua b/ui/items/get_item_details.lua index 120339d..f4120ea 100644 --- a/ui/items/get_item_details.lua +++ b/ui/items/get_item_details.lua @@ -42,7 +42,7 @@ local recipe_handler = require "recipe_handler" --- Get information about an item. ---@param item_data Recipe? The item data to edit, if any. ----@param default_item_search_name string? The default item name to search for. +---@param default_item_search_name string? The default item name to search for. If this is supplied, it will be passed as the previous name to create_recipe_object (i.e: This argument assumes that we are editing a recipe, not creating a new one.) ---@return Recipe? recipe The new recipe for the item. return function(item_data, default_item_search_name) local new_data = util.deep_copy(item_data) or recipe_handler.create_recipe_object("", 1, {}, 0, false) @@ -149,6 +149,8 @@ return function(item_data, default_item_search_name) output_count, ingredients, machine, - is_fluid + is_fluid, + false, + default_item_search_name ) end diff --git a/ui/items/remove.lua b/ui/items/remove.lua index 6dc7527..36b7c9f 100644 --- a/ui/items/remove.lua +++ b/ui/items/remove.lua @@ -5,7 +5,7 @@ local good_response = require "ui.util.good_response" local search = require "ui.util.search" local confirm = require "ui.util.confirmation_menu" ---- Remove machine menu -> Search for a machine by name, then remove it, if the user confirms. +--- Remove recipes menu -> Search for a recipe by name, then remove it, if the user confirms. ---@param run_menu fun(name: string) The function to run another menu return function(run_menu) -- Get the list of item names diff --git a/ui/util/search.lua b/ui/util/search.lua index c3283d3..c5b0627 100644 --- a/ui/util/search.lua +++ b/ui/util/search.lua @@ -6,7 +6,7 @@ local fzy = require "fzy_lua" --- Allows you to search for something. ---@param menu_subtitle string The subtitle of the menu. ---@param initial_list table The initial list of results. If nothing has been entered yet, this will be shown. ----@param no_selection_allowed boolean? If true, the user can select the "No results found." item and the current text will instead be returned. +---@param no_selection_allowed boolean? If true, the user can select the "No results found." item and the current text will instead be returned. Also allows the user to press shift+enter to force the current name. ---@param default_search string? The default text to show in the input box. ---@return string? text The text the user entered, or nil if the user cancelled. return function(menu_subtitle, initial_list, no_selection_allowed, default_search)