melterns/tinkering/nodes/tool_station.lua

556 lines
14 KiB
Lua

tool_station = {}
local tool_list_cache = nil
function tool_station.get_tool_type_list(ix, iy, mx)
local formspec = ""
local x = 0
local y = 0
formspec = formspec..("button[%d,%d;1,1;anvil;Anvil]"):format(x + ix, y + iy)
x = x + 1
for t, tool in pairs(tinkering.tools) do
local toolmod = tool.mod_name or "tinkering"
formspec = formspec.. ("item_image_button[%d,%d;1,1;%s;%s;]"):format(x + ix, y + iy, toolmod..":steel_"..t, t)
formspec = formspec.. ("tooltip[%s;%s]"):format(t, tool.description)
x = x + 1
if x >= mx then
y = y + 1
x = 0
end
end
return formspec
end
function tool_station.get_formspec(comp_list)
if not tool_list_cache then
tool_list_cache = tool_station.get_tool_type_list(8, 0, 5)
end
local w = 1
local h = 0
local x = 1
local y = 0
local til = ""
if comp_list then
for _,comp in pairs(comp_list) do
local img = tinkering.components[comp].image .. "^[colorize:#1e1e1e:255"
til = til .. "image[" .. (x * 1) .. "," .. (y + 0.8) .. ";1,1;".. img .. "]"
y = y + 1
h = h + 1
if y > 2 then
y = 0
x = x + 1
end
if h > 3 then
h = 3
w = w + 1
end
end
else
h = 3
w = 3
end
return "size[13,8.5]"..
default.gui_bg..
default.gui_bg_img..
default.gui_slots..
"label[0,0;Tool Station]"..
til..
"list[context;input;1,0.8;" .. w .. "," .. h .. ";]"..
"list[context;output;5,1.8;1,1;]"..
"image[4,1.8;1,1;gui_furnace_arrow_bg.png^[transformR270]"..
"list[current_player;main;0,4.25;8,1;]"..
"list[current_player;main;0,5.5;8,3;8]"..
tool_list_cache..
"listring[current_player;main]"..
"listring[context;input]"..
"listring[current_player;main]"..
"listring[context;output]"..
"listring[current_player;main]"..
default.get_hotbar_bg(0, 4.25)
end
local function get_metalgroup(groups)
if not groups then return nil end
for g,i in pairs(groups) do
if g:find("material_") == 1 then
return g:gsub("^material_", "")
end
end
return nil
end
-- Get tool components from specified stacks
function tool_station.get_types(list, tool_type)
local tool = tinkering.tools[tool_type]
if not tool then return nil end
local result = {}
local items_required = {}
local components = {}
for _,stack in pairs(list) do
if not result then break end
local stack_name = stack:get_name()
for tt, ty in pairs(tool.components) do
if not result then break end
local in_grp = minetest.get_item_group(stack_name, "tc_"..ty) > 0
if in_grp then
if components[tt] == nil then
local mtg = get_metalgroup(minetest.registered_items[stack_name].groups)
if mtg ~= nil then
result[tt] = mtg
if not items_required[stack_name] then
items_required[stack_name] = 0
end
items_required[stack_name] = items_required[stack_name] + 1
components[tt] = true
end
else
-- Don't allow multiple components of the same type to avoid confusion
result = nil
items_required = nil
components = {}
break
end
end
end
end
return result, items_required
end
function tool_station.get_tool(list)
local tool_fnd = nil
local tool_type = nil
for _,stack in pairs(list) do
local stack_name = stack:get_name()
if minetest.get_item_group(stack_name, "tinker_tool") > 0 then
if tool_fnd == nil then
for t in pairs(tinkering.tools) do
if minetest.get_item_group(stack_name, "tinker_"..t) > 0 then
tool_type = t
break
end
end
tool_fnd = stack
else
-- Don't allow multiple tools in the repair grid at the same time to avoid confusion
tool_fnd = nil
break
end
end
end
return tool_fnd, tool_type
end
local function decode_meta(s)
local t = {}
for k, v in string.gmatch(s, "(%w+)=(%w+)") do
t[k] = v
end
return t
end
local function find_material(stack)
-- Meltables
for metal,list in pairs(metal_melter.melts) do
for type,stacks in pairs(list) do
for _,st in pairs(stacks) do
if st == stack then
return metal, type
end
end
end
end
-- Grouped
for mat,iv in pairs(tinkering.materials) do
if iv.base == "group" and minetest.get_item_group(stack, iv.default) > 0 then
return mat, "block"
elseif stack == iv.default then
return mat, "ingot"
end
end
return nil
end
local function get_materials_in_list(list, skip)
local result = {}
for _,stack in pairs(list) do
local stack_name = stack:get_name()
if stack_name ~= "" and stack_name ~= skip then
local material, type = find_material(stack_name)
if material then
if result[material] then
result[material].count = result[material].count + stack:get_count()
else
result[material] = {stack = stack_name, type = type, count = stack:get_count()}
end
end
end
end
return result
end
local function match_materials(list1, materials)
local matches = {}
for name,type in pairs(materials) do
if list1[type] then
matches[type] = list1[type]
end
end
-- Return nothing if there are materials not suitable
for name in pairs(list1) do
if not matches[name] then
matches = {}
break
end
end
return matches
end
local function take_from_list(list, item, list2)
for _,stack in pairs(list) do
local stack_name = stack:get_name()
if stack_name == item then
stack:clear()
elseif list2[stack_name] then
if list2[stack_name] > stack:get_count() then
list2[stack_name] = list2[stack_name] - stack:get_count()
stack:clear()
else
stack:set_count(stack:get_count() - list2[stack_name])
list2[stack_name] = 0
end
end
end
return list
end
local function handle_take_output(pos, listname)
local meta = minetest.get_meta(pos)
local inv = meta:get_inventory()
local tooltype = meta:get_string("tool_type")
local list = inv:get_list(listname)
if tooltype ~= "" then
local types, items = tool_station.get_types(list, tooltype)
if not types then return end
local res = {}
for _,stack in pairs(list) do
local stack_name = stack:get_name()
if items[stack_name] then
if not res[stack_name] then
res[stack_name] = items[stack_name]
end
if res[stack_name] > 0 then
if stack:get_count() > res[stack_name] then
stack:set_count(stack:get_count() - res[stack_name])
res[stack_name] = 0
else
res[stack_name] = res[stack_name] - stack:get_count()
stack:clear()
end
end
end
end
inv:set_list(listname, list)
else
local tool, tool_type_ = tool_station.get_tool(list)
if tool then
local comp_mats = tool:get_meta():get_string("materials")
if comp_mats and comp_mats ~= "" then
local materials = decode_meta(comp_mats)
-- Material list found, now we can start doing repair work or replacing a component
local mat_grid = get_materials_in_list(list, tool:get_name())
-- Find components to remove
local for_removal = {}
local removed_types = {}
local repair = true
local tool_comps = tinkering.tools[tool_type_].components
for mat, stat in pairs(mat_grid) do
for name, comp in pairs(tool_comps) do
if stat.type == comp and not removed_types[comp] then
for_removal[stat.stack] = 1
removed_types[comp] = true
repair = false
end
end
end
if not repair then
inv:set_list(listname, take_from_list(list, tool:get_name(), for_removal))
end
if tool:get_wear() ~= 0 and repair then
local matches = match_materials(mat_grid, materials)
local repair_cap = 0
for mat, stat in pairs(matches) do
repair_cap = repair_cap + math.min(stat.count, 3)
end
if repair_cap > 0 then
local _take = 1
for i = 1, repair_cap do
local tool_wear = 65535 - tool:get_wear()
local repair_cnt = (0.33 * 65535) * i
local new_wear = 65535 - (tool_wear + repair_cnt)
if new_wear > 0 then
_take = _take + 1
end
end
local to_take = {}
local exch = _take
for type, c in pairs(matches) do
if not to_take[c.stack] then to_take[c.stack] = 0 end
if c.count < exch then
to_take[c.stack] = to_take[c.stack] + c.count
exch = exch - 1
else
to_take[c.stack] = to_take[c.stack] + exch
break
end
end
inv:set_list(listname, take_from_list(list, tool:get_name(), to_take))
end
end
end
end
end
end
local function on_timer(pos, elapsed)
local meta = minetest.get_meta(pos)
local inv = meta:get_inventory()
local refresh = false
local output = nil
-- Get selected tool type
local tool_type = meta:get_string("tool_type")
local list = inv:get_list("input")
if tool_type ~= "" then
local results = tool_station.get_types(list, tool_type)
if results then
-- Attempt to create the tool with the provided materials
local tool_res = tinkering.create_tool(tool_type, results, true)
if tool_res then
output = tool_res
end
end
meta:set_string("formspec", tool_station.get_formspec(tinkering.tools[tool_type].components))
else
local tool, tool_type_ = tool_station.get_tool(list)
if tool then
local comp_mats = tool:get_meta():get_string("materials")
if comp_mats and comp_mats ~= "" then
local materials = decode_meta(comp_mats)
-- Material list found, now we can start doing repair work or replacing a component
local mat_grid = get_materials_in_list(list, tool:get_name())
-- Find components to replace
local comp_repl = {}
local repair = true
local tool_comps = tinkering.tools[tool_type_].components
for mat, stat in pairs(mat_grid) do
if comp_repl == nil then break end
for name, comp in pairs(tool_comps) do
if stat.type == comp then
if comp_repl[name] then
-- Dont allow multiple of the same component to avoid confusion
comp_repl = nil
break
else
comp_repl[name] = mat
end
repair = false
end
end
end
if not repair and comp_repl then
-- Add non-replacement materials back
for i,v in pairs(materials) do
if not comp_repl[i] then
comp_repl[i] = v
end
end
local tool_res = tinkering.create_tool(tool_type_, comp_repl, true, nil, {wear = tool:get_wear()})
if tool_res then
output = tool_res
end
end
-- Attempt to repair tool with provided items
if tool:get_wear() ~= 0 and repair then
local matches = match_materials(mat_grid, materials)
local repair_cap = 0
for mat, stat in pairs(matches) do
repair_cap = repair_cap + math.min(stat.count, 3)
end
if repair_cap > 0 then
local tool_wear = 65535 - tool:get_wear()
local repair_cnt = (0.33 * 65535) * repair_cap
local new_wear = 65535 - (tool_wear + repair_cnt)
if new_wear < 0 then
new_wear = 0
end
local tool_res = tinkering.create_tool(tool_type_, materials, true, nil, {wear = new_wear})
if tool_res then
output = tool_res
end
end
end
end
end
meta:set_string("formspec", tool_station.get_formspec())
end
if output then
inv:set_list("output", {output})
else
inv:set_list("output", {})
end
return refresh
end
local function allow_metadata_inventory_put (pos, listname, index, stack, player)
if minetest.is_protected(pos, player:get_player_name()) then
return 0
end
if listname == "output" then
return 0
end
return stack:get_count()
end
local function allow_metadata_inventory_move (pos, from_list, from_index, to_list, to_index, count, player)
local meta = minetest.get_meta(pos)
local inv = meta:get_inventory()
local stack = inv:get_stack(from_list, from_index)
return allow_metadata_inventory_put(pos, to_list, to_index, stack, player)
end
local function allow_metadata_inventory_take (pos, listname, index, stack, player)
if minetest.is_protected(pos, player:get_player_name()) then
return 0
end
return stack:get_count()
end
local function on_construct(pos)
local meta = minetest.get_meta(pos)
meta:set_string("formspec", tool_station.get_formspec())
-- Create inventory
local inv = meta:get_inventory()
inv:set_size('input', 9)
inv:set_size('output', 1)
-- Set tool type meta
meta:set_string("tool_type", "")
end
local function on_take(pos, listname, index, stack, player)
local inv = minetest.get_meta(pos):get_inventory()
if listname == "output" then
handle_take_output(pos, "input")
end
minetest.get_node_timer(pos):start(0.02)
end
local function can_dig(pos, player)
local meta = minetest.get_meta(pos)
local inv = meta:get_inventory()
return inv:is_empty("input") and inv:is_empty("output")
end
local function on_receive_fields(pos, formname, fields, sender)
if sender and minetest.is_protected(pos, sender:get_player_name()) then
return 0
end
local meta = minetest.get_meta(pos)
if fields["anvil"] then
meta:set_string("tool_type", "")
else
for name,_ in pairs(fields) do
if tinkering.tools[name] then
meta:set_string("tool_type", name)
break
end
end
end
minetest.get_node_timer(pos):start(0.02)
end
minetest.register_node("tinkering:tool_station", {
description = "Tool Station",
tiles = {
"tinkering_workbench_top.png", "tinkering_bench_bottom.png",
"tinkering_bench_side.png", "tinkering_bench_side.png",
"tinkering_bench_side.png", "tinkering_bench_side.png"
},
drawtype = "nodebox",
paramtype = "light",
node_box = tinkering.bench,
on_construct = on_construct,
legacy_facedir_simple = true,
is_ground_content = false,
sounds = default.node_sound_wood_defaults(),
can_dig = can_dig,
on_timer = on_timer,
on_construct = on_construct,
on_receive_fields = on_receive_fields,
on_metadata_inventory_move = function(pos)
minetest.get_node_timer(pos):start(0.05)
end,
on_metadata_inventory_put = function(pos)
minetest.get_node_timer(pos):start(0.05)
end,
on_metadata_inventory_take = on_take,
allow_metadata_inventory_put = allow_metadata_inventory_put,
allow_metadata_inventory_take = allow_metadata_inventory_take,
allow_metadata_inventory_move = allow_metadata_inventory_move,
groups = {choppy = 2, oddly_breakable_by_hand = 2}
})