540 lines
18 KiB
Lua
540 lines
18 KiB
Lua
--- Recipe class
|
|
-- @classmod Recipe
|
|
|
|
local Recipe = {
|
|
_class = 'recipe',
|
|
_ingredients_mt = require('stdlib/data/modules/ingredients'),
|
|
_results_mt = require('stdlib/data/modules/results')
|
|
}
|
|
setmetatable(Recipe, {__index = require('stdlib/data/data')})
|
|
|
|
local Is = require('stdlib/utils/is')
|
|
local Item = require('stdlib/data/item')
|
|
|
|
function Recipe:_get(recipe)
|
|
local new = self:get(recipe, 'recipe')
|
|
--[[prototype
|
|
type, name
|
|
localised_name[opt]
|
|
localised_description[opt]
|
|
subgroup, order (needed when no main product)
|
|
--]]
|
|
--[[recipe
|
|
category
|
|
icon/icons (or has main_product)
|
|
crafting_machine_tint = {
|
|
primary, secondary, tertiary
|
|
}
|
|
normal/expensive = {
|
|
ingredients
|
|
results, result, result_count[opt=1] (result ignored if results present) at least 1 result
|
|
main_product
|
|
energy_required > 0.001
|
|
emissions_multiplier
|
|
requester_paste_multiplier
|
|
overload_multiplier
|
|
enabled <boolean>
|
|
hidden <boolean>
|
|
hide_from_stats <boolean>
|
|
allow_decomposition <boolean>
|
|
allow_as_intermediate <boolean>
|
|
allow_intermediates <boolean>
|
|
always_show_made_in <boolean>
|
|
show_amount_in_title <boolean>
|
|
always_show_products <boolean>
|
|
}
|
|
--]]
|
|
|
|
|
|
-- Convert the recipe to difficult format
|
|
|
|
-- Convert the ingredients to full format
|
|
--new:Ingredients()
|
|
|
|
-- Convert the results to full format
|
|
--new:Results()
|
|
return new
|
|
end
|
|
Recipe:set_caller(Recipe._get)
|
|
|
|
function Recipe:Results(get_expensive)
|
|
if self:valid('recipe') then
|
|
if get_expensive then
|
|
self:make_difficult()
|
|
end
|
|
if self.normal then
|
|
if self.normal.result then
|
|
self.normal.results = {
|
|
{type = 'item', name = self.normal.result, amount = self.normal.result_count or 1}
|
|
}
|
|
self.normal.result = nil
|
|
self.normal.result_count = nil
|
|
end
|
|
self.normal.results._owner = self
|
|
self.normal.results._valid = 'results'
|
|
setmetatable(self.normal.results, Recipe._results_mt)
|
|
if self.expensive.result then
|
|
self.expensive.results = {
|
|
{type = 'item', name = self.expensive.result, amount = self.expensive.result_count or 1}
|
|
}
|
|
self.expensive.result = nil
|
|
self.expensive.result_count = nil
|
|
end
|
|
self.expensive.results._owner = self
|
|
self.expensive.results._valid = 'results'
|
|
setmetatable(self.expensive.results, Recipe._results_mt)
|
|
return get_expensive and self.expensive.results or self.normal.results
|
|
else
|
|
if self.result then
|
|
self.results = {
|
|
{type = 'item', name = self.result, amount = self.result_count or 1}
|
|
}
|
|
self.result = nil
|
|
self.result_count = nil
|
|
end
|
|
self.results._owner = self
|
|
self.results._valid = 'results'
|
|
return setmetatable(self.results, Recipe._results_mt)
|
|
end
|
|
end
|
|
return self
|
|
end
|
|
|
|
function Recipe:Ingredients(get_expensive)
|
|
if self:valid('recipe') then
|
|
if get_expensive then
|
|
self:make_difficult()
|
|
end
|
|
if self.normal then
|
|
self.normal.ingredients._owner = self
|
|
self.normal.ingredients._valid = 'ingredients'
|
|
setmetatable(self.normal.ingredients, Recipe._ingredients_mt)
|
|
self.expensive.ingredients._owner = self
|
|
self.expensive.ingredients._valid = 'ingredients'
|
|
setmetatable(self.expensive.ingredients, Recipe._ingredients_mt)
|
|
return get_expensive and self.expensive.ingredients or self.normal.ingredients
|
|
else
|
|
self.ingredients._owner = self
|
|
self.ingredients._valid = 'ingredients'
|
|
return setmetatable(self.ingredients, Recipe._ingredients_mt)
|
|
end
|
|
end
|
|
return self
|
|
end
|
|
|
|
-- Returns a formated ingredient or prodcut table
|
|
local function format(ingredient, result_count)
|
|
--[[
|
|
Ingredient table
|
|
{"name", amount} -- Assumes a type of "item"
|
|
{
|
|
type :: string: "item" or "fluid".
|
|
name :: string: Prototype name of the required item or fluid.
|
|
amount :: uint: Amount of the item.
|
|
minimum_temperature :: uint (optional): The minimum fluid temperature required. Has no effect if type is '"item"'.
|
|
maximum_temperature :: uint (optional): The maximum fluid temperature allowed. Has no effect if type is '"item"'.
|
|
}
|
|
|
|
Product table
|
|
{
|
|
type :: string: "item" or "fluid".
|
|
name :: string: Prototype name of the result.
|
|
amount :: float (optional): If not specified, amount_min, amount_max and probability must all be specified.
|
|
temperature :: uint (optional): The fluid temperature of this product. Has no effect if type is '"item"'.
|
|
amount_min :: uint (optional):
|
|
amount_max :: uint (optional):
|
|
probability :: double (optional): A value in range [0, 1].
|
|
}
|
|
--]]
|
|
local object
|
|
if type(ingredient) == 'table' then
|
|
if ingredient.valid and ingredient:valid() then
|
|
return ingredient
|
|
elseif ingredient.name then
|
|
if Item(ingredient.name, ingredient.type):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: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:valid() then
|
|
object = {
|
|
type = item.type == 'fluid' and 'fluid' or 'item',
|
|
name = ingredient,
|
|
amount = result_count or 1
|
|
}
|
|
end
|
|
end
|
|
return object
|
|
end
|
|
|
|
-- get items for difficulties
|
|
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) 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) 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:valid() then
|
|
normal, expensive = get_difficulties(normal, expensive)
|
|
|
|
if self.normal then
|
|
if normal then
|
|
self.normal.ingredients[#self.normal.ingredients + 1] = normal
|
|
end
|
|
if expensive then
|
|
self.expensive.ingredients[#self.expensive.ingredients + 1] = expensive
|
|
end
|
|
elseif normal then
|
|
self.ingredients[#self.ingredients + 1] = normal
|
|
end
|
|
end
|
|
return self
|
|
end
|
|
|
|
--- 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: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
|
|
|
|
--- 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)
|
|
Is.Assert(replace, 'Missing recipe to replace')
|
|
if self: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
|
|
|
|
--- 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: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:valid() then
|
|
local Category = require('stdlib/data/category')
|
|
self.category = Category(category_name, 'recipe-category'):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:valid() then
|
|
local Tech = require('stdlib/data/technology')
|
|
Tech.add_effect(self, tech_name)
|
|
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:valid('recipe') then
|
|
local Tech = require('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: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: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:valid('recipe') then
|
|
normal, expensive = get_difficulties(normal, expensive)
|
|
local normal_main, expensive_main
|
|
if main_product then
|
|
if type(main_product) == 'string' and Item(main_product):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):valid() and normal.name
|
|
expensive_main = expensive and Item(expensive.name):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: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: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: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: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
|
|
|
|
Recipe.rep_ing = Recipe.replace_ingredient
|
|
Recipe.add_ing = Recipe.add_ingredient
|
|
Recipe.rem_ing = Recipe.remove_ingredient
|
|
|
|
Recipe._mt = {
|
|
__index = Recipe,
|
|
__call = Recipe._get,
|
|
__tostring = Recipe.tostring
|
|
}
|
|
|
|
return Recipe
|