Epic/mods/simplecrafting_lib/register.lua

212 lines
6.1 KiB
Lua

--------------------------------------------------------------------------------------------------------------------
-- Local functions
-- Finds the greatest common divisor of the two input parameters
local function greatest_common_divisor(a, b)
local temp
while(b > 0) do
temp = b
b = a % b
a = temp
end
return a
end
-- Finds the greatest common divisor of an arbitrarily long list of numbers
local function gcd_list(list)
if #list == 1 then
return list[1]
end
local gcd = list[1]
for i=2, #list do
gcd = greatest_common_divisor(gcd, list[i])
end
return gcd
end
-- divides input, output and returns values by their greatest common divisor
-- does *not* modify time or any other values
local function reduce_recipe(def)
local list = {}
for _, count in pairs(def.input) do
table.insert(list, count)
end
if def.output then
table.insert(list, ItemStack(def.output):get_count())
end
if def.returns then
for _, count in pairs(def.returns) do
table.insert(list, count)
end
end
local gcd = gcd_list(list)
if gcd ~= 1 then
for item, count in pairs(def.input) do
def.input[item] = count/gcd
end
if def.output then
def.output:set_count(def.output:get_count()/gcd)
end
if def.returns then
for item, count in pairs(def.returns) do
def.returns[item] = count/gcd
end
end
end
end
-- Strip group: from group names to simplify comparison later
local function strip_groups(def)
local groups = {}
for item, count in pairs(def.input) do
local group = string.match(item, "^group:(%S+)$")
if group then
groups[group] = count
end
end
-- must be done in two steps like this in case the recipe has more than one group item
-- doing it in the first loop could invalidate the pairs iterator and miss one
for group, count in pairs(groups) do
def.input[group] = count
def.input["group:"..group] = nil
end
return def
end
-- Deep equals, used to check for duplicate recipes during registration
local deep_equals
deep_equals = function(test1, test2)
if test1 == test2 then
return true
end
if type(test1) ~= "table" or type(test2) ~= "table" then
return false
end
local value2
for key, value1 in pairs(test1) do
value2 = test2[key]
if deep_equals(value1, value2) == false then
return false
end
end
for key, _ in pairs(test2) do
if test1[key] == nil then
return false
end
end
return true
end
--------------------------------------------------------------------------------------------------------------------
-- Public API
simplecrafting_lib.recipe_equals = function(recipe1, recipe2)
for key, value1 in pairs(recipe1) do
local value2 = recipe2[key]
-- Special handling of output ItemStack
if key == "output" then
if type(value1) == "userdata" then
value1 = value1:to_string()
end
if type(value2) == "userdata" then
value2 = value2:to_string()
end
if value1 ~= value2 then
return false
end
else
if not deep_equals(value1, value2) then
return false
end
end
end
for key, _ in pairs(recipe2) do
if recipe1[key] == nil then
return false
end
end
return true
end
simplecrafting_lib.register = function(craft_type, def)
def.input = def.input or {}
reduce_recipe(def)
strip_groups(def)
local crafting_info = simplecrafting_lib.get_crafting_info(craft_type)
-- Check if this recipe has already been registered. Many different old-style recipes
-- can reduce down to equivalent recipes in this system, so this is a useful step
-- to keep things tidy and efficient.
local output_name
if def.output then
def.output = ItemStack(def.output)
output_name = def.output:get_name()
else
output_name = "none" -- special value for recipes with no output. Shouldn't conflict with group:none since output can't be a group
end
local existing_recipes = crafting_info.recipes_by_out[output_name]
if existing_recipes ~= nil then
for _, existing_recipe in pairs(existing_recipes) do
if simplecrafting_lib.recipe_equals(def, existing_recipe) then
return nil
end
end
end
table.insert(crafting_info.recipes, def)
local recipes_by_out = crafting_info.recipes_by_out
recipes_by_out[output_name] = recipes_by_out[output_name] or {}
recipes_by_out[output_name][#recipes_by_out[output_name]+1] = def
local recipes_by_in = crafting_info.recipes_by_in
for item, _ in pairs(def.input) do
recipes_by_in[item] = recipes_by_in[item] or {}
recipes_by_in[item][#recipes_by_in[item]+1] = def
end
return def
end
-- Registers the provided crafting recipe, and also
-- automatically creates and registers a "reverse" craft of the same type.
-- This should generally only be done with craft that turns one type of item into
-- one other type of item (for example, metal ingots <-> metal blocks), but
-- it will still work if there are multiple inputs.
-- If there's more than one input type it will use "returns" to give them to the
-- player in the reverse craft.
-- Don't use a recipe that has a "group:" input with this, because obviously that
-- can't be turned into an output. The mod will assert if you try to do this.
simplecrafting_lib.register_reversible = function(craft_type, forward_def)
local reverse_def = simplecrafting_lib.deep_copy(forward_def) -- copy before registering, registration messes with "group:" prefixes
simplecrafting_lib.register(craft_type, forward_def)
local forward_in = reverse_def.input
reverse_def.input = simplecrafting_lib.count_list_add({[reverse_def.output:get_name()] = reverse_def.output:get_count()}, reverse_def.returns)
local most_common_in_name = ""
local most_common_in_count = 0
for item, count in pairs(forward_in) do
assert(string.sub(item, 1, 6) ~= "group:")
if count > most_common_in_count then
most_common_in_name = item
most_common_in_count = count
end
end
local reverse_output = ItemStack()
reverse_output:set_name(most_common_in_name)
reverse_output:set_count(most_common_in_count)
reverse_def.output = reverse_output
forward_in[most_common_in_name] = nil
if next(forward_in) ~= nil then -- if there are any items left, they become returns
reverse_def.returns = forward_in
end
simplecrafting_lib.register(craft_type, reverse_def)
end