Open oversword opened 3 years ago
This code is capable of finding all crafting conflicts It may produce false positives where groups and shapeless crafts are concerned Each conflict will need to be manually confirmed
local function is_group(item)
return string.sub(item,1,6) == "group:"
end
local function item_has_group(item, group)
local def = minetest.registered_items[item]
return def and def.groups and def.groups[group]
end
local function same_item(item, other_item)
return item == other_item
or minetest.registered_aliases[item] == other_item
or minetest.registered_aliases[other_item] == item
end
local function item_match(item, other_item)
if item == other_item then return true end
if item == nil or other_item == nil then return end
if is_group(item) then
if is_group(other_item) then return end
return item_has_group(other_item, string.sub(item, 7))
end
if is_group(other_item) then
return item_has_group(item, string.sub(other_item, 7))
end
end
local function normalize(l)
local r = {}
for _,i in pairs(l) do
table.insert(r, i)
end
table.sort(r)
return r
end
local function recipe_match(recipe, other_recipe)
if recipe.method ~= other_recipe.method then return end
if recipe.width ~= 0
and other_recipe.width ~= 0
and recipe.width ~= other_recipe.width then return end
local items = table.copy(recipe.items)
local other_items = table.copy(other_recipe.items)
if recipe.width == 0 or other_recipe.width == 0 then
items = normalize(items)
other_items = normalize(other_recipe.items)
end
if #items ~= #other_items then return end
for i,r in pairs(items) do
if not item_match(other_items[i], r) then return end
end
for i,r in pairs(other_items) do
if not item_match(items[i], r) then return end
end
return true
end
local function is_slopey(item)
return
string.find(item, "moreblocks:")
or string.find(item, "micro_")
or string.find(item, "slab_")
or string.find(item, "slope_")
or string.find(item, "panel_")
or string.find(item, "stair_")
end
local function all_slopey(items)
for _,item in pairs(items) do
if not is_slopey(item) then return end
end
return true
end
local function is_dye(item)
return string.find(item, "dye:")
end
local enabled_reports = {
conflict = true,
duplicate = true,
other = true,
dye = true,
slope = true,
empty = true,
}
local report_files = {}
for report, enabled in pairs(enabled_reports) do
if enabled then
report_files[report] = io.open(minetest.get_worldpath().."/".."report_"..report, "a")
end
end
local function report_conflict(recipe, other_recipe)
local item_stack = ItemStack(recipe.output)
local item_name = item_stack:get_name()
local other_item_stack = ItemStack(other_recipe.output)
local other_item_name = other_item_stack:get_name()
local duplicate = same_item(item_name, other_item_name)
local conflict_report = tostring(recipe.output).." "..(duplicate and "duplicates" or "conflicts with").." "..tostring(other_recipe.output).." (" .. tostring(recipe.type) .. "):"
.."\n"..dump(recipe.items)
.."\n"..dump(other_recipe.items)
.."\n========================\n"
-- minetest.log("error", conflict_report)
if recipe.type ~= "normal" and recipe.type ~= "shapeless" then
if enabled_reports.other then
report_files.other:write(conflict_report)
end
return
end
if (is_slopey(item_name) and is_slopey(other_item_name))
or (all_slopey(recipe.items) and all_slopey(other_recipe.items)) then
if enabled_reports.slope then
report_files.slope:write(conflict_report)
end
return
end
if is_dye(item_name) and is_dye(other_item_name) then
if enabled_reports.dye then
report_files.dye:write(conflict_report)
end
return
end
if duplicate then
if enabled_reports.duplicate then
report_files.duplicate:write(conflict_report)
end
return
end
if enabled_reports.conflict then
report_files.conflict:write(conflict_report)
end
end
local function get_all_conflicts()
minetest.log("error", "Starting")
local all_recipes = {}
local all_items = minetest.registered_items
for item_name,item_def in pairs(all_items) do
local recipes = minetest.get_all_craft_recipes(item_name)
if enabled_reports.empty and (not recipes or #recipes == 0) and not (item_def.groups and item_def.groups.not_in_creative_inventory) then
report_files.empty:write("No craft for "..item_name.."\n")
end
if recipes then
for i,recipe in ipairs(recipes) do
-- if is_slopey(item_name) or all_slopey(recipe.items) then
table.insert(all_recipes, recipe)
-- end
end
end
end
minetest.log("error", ("Assessing %i items in %f combinations"):format(#all_recipes, ((#all_recipes-1)*#all_recipes)/2))
local dones = 0
for i,recipe in ipairs(all_recipes) do
for j=i+1,#all_recipes do
local other_recipe = all_recipes[j]
dones = dones+1
if recipe_match(recipe, other_recipe) then
report_conflict(
recipe, other_recipe
)
end
if dones % 1000000 == 0 then
minetest.log("error", "Done: "..tostring(dones))
end
end
end
for report, file in pairs(report_files) do
file:close()
end
minetest.log("error", "Done")
end
local function get_all_conflicts_ui()
minetest.log("error", "Starting")
local all_recipes_by_type = {}
local all_items = unified_inventory.crafts_for.recipe
for item_name,recipes in pairs(all_items) do
if recipes then
for i,recipe in ipairs(recipes) do
local recipe_type = recipe.type
if recipe_type == "shapeless" then recipe_type = "normal" end
if not all_recipes_by_type[recipe_type] then
all_recipes_by_type[recipe_type] = {}
end
table.insert(all_recipes_by_type[recipe_type], recipe)
end
end
end
all_recipes_by_type.normal = nil
all_recipes_by_type.sieving = nil
all_recipes_by_type.digging_chance = nil
for recipe_type, all_recipes in pairs(all_recipes_by_type) do
minetest.log("error", ("Assessing %i items in %f combinations for %s"):format(#all_recipes, ((#all_recipes-1)*#all_recipes)/2, recipe_type))
local dones = 0
for i,recipe in ipairs(all_recipes) do
for j=i+1,#all_recipes do
local other_recipe = all_recipes[j]
dones = dones+1
if recipe_match(recipe, other_recipe) then
report_conflict(
recipe, other_recipe
)
end
if dones % 1000000 == 0 then
minetest.log("error", "Done: "..tostring(dones))
end
end
end
end
for report, file in pairs(report_files) do
file:close()
end
minetest.log("error", "Done")
end
minetest.register_on_mods_loaded(function()
minetest.after(10, get_all_conflicts_ui)
end)
Stop closing it stupid robot
This should use some decent hash indexes instead of parallelization.
This should use some decent hash indexes instead of parallelization.
@appgurueu Could you be clearer? What could use hash indexes?
This should use some decent hash indexes instead of parallelization.
@appgurueu Could you be clearer? What could use hash indexes?
The way Minetest already uses a couple (unfortunately rather poor) indices to speed things up. Shapeful recipes not using groups can be fully hashed; other recipes are hashed by item count from what I've seen. Simply looping over all recipes and then feeding a valid input for the recipe into minetest.get_craft_result(recipe)
and checking that against the output should work and leverage MT's hashes (assuming that get_craft_result
is deterministic). It also cuts down on code significantly.