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[context;input]".. "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} })