--- Recipe class -- @classmod Data.Recipe local Data = require('__stdlib__/stdlib/data/data') local Table = require('__stdlib__/stdlib/utils/table') local Recipe = { __class = 'Recipe', __index = Data, } function Recipe:__call(recipe) local new = self:get(recipe, 'recipe') -- rawset(new, 'Ingredients', {}) -- rawset(new, 'Results', {}) return new end setmetatable(Recipe, Recipe) -- Returns a formated ingredient or prodcut table local function format(ingredient, result_count) local Item = require('__stdlib__/stdlib/data/item') local object if type(ingredient) == 'table' then if ingredient.valid and ingredient:is_valid() then return ingredient elseif ingredient.name then if Item(ingredient.name, ingredient.type):is_valid() then object = Table.deepcopy(ingredient) if not object.amount and not (object.amount_min and object.amount_max and object.probability) then error('Result table requires amount or probabilities') end end elseif #ingredient > 0 then -- Can only be item types not fluid local item = Item(ingredient[1]) if item:is_valid() and item.type ~= 'fluid' then object = { type = 'item', name = ingredient[1], amount = ingredient[2] or 1 } end end elseif type(ingredient) == 'string' then -- Our shortcut so we need to check it local item = Item(ingredient) if item:is_valid() then object = { type = item.type == 'fluid' and 'fluid' or 'item', name = ingredient, amount = result_count or 1 } end end return object end -- Format items for difficulties -- If expensive is a boolean then return a copy of normal for expensive local function get_difficulties(normal, expensive) return format(normal), format((expensive == true and Table.deepcopy(normal)) or expensive) end --- Remove an ingredient from an ingredients table. -- @tparam table ingredients -- @tparam string name Name of the ingredient to remove local function remove_ingredient(ingredients, name) name = name.name for i, ingredient in pairs(ingredients or {}) do if ingredient[1] == name or ingredient.name == name then ingredients[i] = nil return true end end end --- Replace an ingredient. -- @tparam table ingredients Ingredients table -- @tparam string find ingredient to replace -- @tparam concepts.ingredient replace -- @tparam boolean replace_name_only Don't replace amounts local function replace_ingredient(ingredients, find, replace, replace_name_only) for i, ingredient in pairs(ingredients or {}) do if ingredient[1] == find or ingredient.name == find then if replace_name_only then local amount = ingredient[2] or ingredient.amount replace.amount = amount end ingredients[i] = replace return true end end end --- Add an ingredient to a recipe. -- @tparam string|Concepts.ingredient normal -- @tparam[opt] string|Concepts.ingredient|boolean expensive -- @treturn Recipe function Recipe:add_ingredient(normal, expensive) if self:is_valid() then normal, expensive = get_difficulties(normal, expensive) if self.normal then if normal then self.normal.ingredients = self.normal.ingredients or {} self.normal.ingredients[#self.normal.ingredients + 1] = normal end if expensive then self.expensive.ingredients = self.expensive.ingredients or {} self.expensive.ingredients[#self.expensive.ingredients + 1] = expensive end elseif normal then self.ingredients = self.ingredients or {} self.ingredients[#self.ingredients + 1] = normal end end return self end Recipe.add_ing = Recipe.add_ingredient --- Remove one ingredient completely. -- @tparam string normal -- @tparam string|boolean expensive expensive recipe to remove, or if true remove normal recipe from both -- @treturn Recipe function Recipe:remove_ingredient(normal, expensive) if self:is_valid() then normal, expensive = get_difficulties(normal, expensive) if self.normal then if normal then remove_ingredient(self.normal.ingredients, normal) end if expensive then remove_ingredient(self.expensive.ingredients, expensive) end elseif normal then remove_ingredient(self.ingredients, normal) end end return self end Recipe.rem_ing = Recipe.remove_ingredient --- Replace one ingredient with another. -- @tparam string replace -- @tparam string|ingredient normal -- @tparam[opt] string|ingredient|boolean expensive function Recipe:replace_ingredient(replace, normal, expensive) assert(replace, 'Missing recipe to replace') if self:is_valid() then local n_string = type(normal) == 'string' local e_string = type(expensive == true and normal or expensive) == 'string' normal, expensive = get_difficulties(normal, expensive) if self.normal then if normal then replace_ingredient(self.normal.ingredients, replace, normal, n_string) end if expensive then replace_ingredient(self.expensive.ingredients, replace, expensive, e_string) end elseif normal then replace_ingredient(self.ingredients, replace, normal, n_string) end end return self end Recipe.rep_ing = Recipe.replace_ingredient -- Currently does no checking function Recipe:clear_ingredients() if self:is_valid() then if self.normal then if self.normal.ingredients then self.normal.ingredients = {} end if self.expensive.ingredients then self.expensive.ingredients = {} end elseif self.ingredients then self.ingredients = {} end end return self end --- Converts a recipe to the difficulty recipe format. -- @tparam[opt] number expensive_energy crafting energy_required for the expensive recipe -- @treturn self function Recipe:make_difficult(expensive_energy) if self:is_valid('recipe') and not self.normal then --convert all ingredients local normal, expensive = {}, {} for _, ingredient in ipairs(self.ingredients) do local this = format(ingredient) normal[#normal + 1] = this expensive[#expensive + 1] = Table.deepcopy(this) end local r_normal, r_expensive = {}, {} for _, ingredient in ipairs(self.results or { self.result }) do local this = format(ingredient) r_normal[#r_normal + 1] = this r_expensive[#r_expensive + 1] = Table.deepcopy(this) end self.normal = { enabled = self.enabled, energy_required = self.energy_required, ingredients = normal, results = r_normal, main_product = self.main_product } self.expensive = { enabled = self.enabled, energy_required = expensive_energy or self.energy_required, ingredients = expensive, results = r_expensive, main_product = self.main_product } self.ingredients = nil self.result = nil self.results = nil self.result_count = nil self.energy_required = nil self.enabled = nil self.main_product = nil end return self end --- Change the recipe category. -- @tparam string category_name The new crafting category -- @treturn self function Recipe:change_category(category_name) if self:is_valid() then local Category = require('__stdlib__/stdlib/data/category') self.category = Category(category_name, 'recipe-category'):is_valid() and category_name or self.category end return self end Recipe.set_category = Recipe.change_category --- Add to technology as a recipe unlock. -- @tparam string tech_name Name of the technology to add the unlock too -- @treturn self function Recipe:add_unlock(tech_name) if self:is_valid() then local Tech = require('__stdlib__/stdlib/data/technology') Tech.add_effect(self, tech_name) --self is passed as a valid recipe end return self end --- Remove the recipe unlock from the technology. -- @tparam string tech_name Name of the technology to remove the unlock from -- @treturn self function Recipe:remove_unlock(tech_name) if self:is_valid('recipe') then local Tech = require('__stdlib__/stdlib/data/technology') Tech.remove_effect(self, tech_name, 'unlock-recipe') end return self end --- Set the enabled status of the recipe. -- @tparam boolean enabled Enable or disable the recipe -- @treturn self function Recipe:set_enabled(enabled) if self:is_valid() then if self.normal then self.normal.enabled = enabled self.expensive.enabled = enabled else self.enabled = enabled end end return self end --- Convert result type to results type. -- @treturn self function Recipe:convert_results() if self:is_valid('recipe') then if self.normal then if self.normal.result then self.normal.results = { format(self.normal.result, self.normal.result_count or 1) } self.normal.result = nil self.normal.result_count = nil end if self.expensive.result then self.expensive.results = { format(self.expensive.result, self.expensive.result_count or 1) } self.expensive.result = nil self.expensive.result_count = nil end elseif self.result then self.results = { format(self.result, self.result_count or 1) } self.result = nil self.result_count = nil end end return self end --- Set the main product of the recipe. -- @tparam string|boolean main_product if boolean then use normal/expensive recipes passed as main product -- @tparam[opt] Concepts.Product|string normal recipe -- @tparam[opt] Concepts.Product|string expensive recipe -- @treturn self function Recipe:set_main_product(main_product, normal, expensive) if self:is_valid('recipe') then normal, expensive = get_difficulties(normal, expensive) local normal_main, expensive_main if main_product then local Item = require('__stdlib__/stdlib/data/item') if type(main_product) == 'string' and Item(main_product):is_valid() then normal_main = normal and main_product expensive_main = expensive and main_product elseif type(main_product) == 'boolean' then normal_main = normal and Item(normal.name):is_valid() and normal.name expensive_main = expensive and Item(expensive.name):is_valid() and expensive.name end if self.normal then self.normal.main_product = normal_main self.expensive.main_product = expensive_main else self.main_product = normal_main end end end return self end --- Remove the main product of the recipe. -- @tparam[opt=false] boolean for_normal -- @tparam[opt=false] boolean for_expensive function Recipe:remove_main_product(for_normal, for_expensive) if self:is_valid('recipe') then if self.normal then if for_normal or (for_normal == nil and for_expensive == nil) then self.normal.main_product = nil end if for_expensive or (for_normal == nil and for_expensive == nil) then self.expensive.main_product = nil end elseif for_normal or (for_normal == nil and for_expensive == nil) then self.main_product = nil end end return self end --- Add a new product to results, converts if needed. -- @tparam string|Concepts.product normal -- @tparam[opt] string|Concepts.product|boolean expensive -- @tparam[opt] string main_product function Recipe:add_result(normal, expensive, main_product) if self:is_valid() then normal, expensive = get_difficulties(normal, expensive) self:convert_results() self:set_main_product(main_product, normal, expensive) -- if self.normal then -- if normal then -- end -- if expensive then -- end -- elseif normal then -- end end return self end --- Remove a product from results, converts if needed. -- @tparam[opt] string|Concepts.product normal -- @tparam[opt] string|Concepts.product|boolean expensive -- @tparam[opt] string main_product new main_product to use function Recipe:remove_result(normal, expensive, main_product) if self:is_valid() then normal, expensive = get_difficulties(normal, expensive) self:convert_results() self:set_main_product(main_product, normal, expensive) -- if self.normal then -- if normal then -- end -- if expensive then -- end -- elseif normal then -- end end return self end --- Remove a product from results, converts if needed. -- @tparam string|Concepts.product result_name -- @tparam[opt] string|Concepts.product normal -- @tparam[opt] string|Concepts.product|boolean expensive -- @tparam[opt] string main_product function Recipe:replace_result(result_name, normal, expensive, main_product) if self:is_valid() and normal or expensive then result_name = format(result_name) if result_name then normal, expensive = get_difficulties(normal, expensive) self:convert_results() self:remove_result(result_name, expensive and result_name) self:set_main_product(main_product, normal, expensive) -- if self.normal then -- if normal then -- end -- if expensive then -- end -- elseif normal then -- end end end return self end return Recipe