local S = technic.getter local has_digilines = minetest.get_modpath("digilines") local has_mesecons = minetest.get_modpath("mesecons") local has_vizlib = minetest.get_modpath("vizlib") local has_jumpdrive = minetest.get_modpath("jumpdrive") local has_mcl = minetest.get_modpath("mcl_formspec") local quarry_max_depth = technic.config:get_int("quarry_max_depth") local quarry_dig_particles = technic.config:get_bool("quarry_dig_particles") local quarry_time_limit = technic.config:get_int("quarry_time_limit") local quarry_demand = 10000 local network_time_limit = 30000 local infotext do local name = S("@1 Quarry", S("HV")) local demand = S("Demand: @1", technic.EU_string(quarry_demand)) infotext = { active = S("@1 Active", name).."\n"..demand, disabled = S("@1 Disabled", name), finished = S("@1 Finished", name), purge = S("@1 Purging Cache", name), unpowered = S("@1 Unpowered", name), } end -- Hard-coded outward-spiral dig pattern for up to 17x17 dig area local dig_pattern = { 0,1,2,2,3,3,0,0,0,1,1,1,2,2,2,2,3,3,3,3,0,0,0,0,0,1,1,1,1,1,2,2, 2,2,2,2,3,3,3,3,3,3,0,0,0,0,0,0,0,1,1,1,1,1,1,1,2,2,2,2,2,2,2,2, 3,3,3,3,3,3,3,3,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,2,2,2,2,2,2, 2,2,2,2,3,3,3,3,3,3,3,3,3,3,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1, 1,1,1,1,2,2,2,2,2,2,2,2,2,2,2,2,3,3,3,3,3,3,3,3,3,3,3,3,0,0,0,0, 0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,2,2,2,2,2,2,2,2,2,2, 2,2,2,2,3,3,3,3,3,3,3,3,3,3,3,3,3,3,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2, 3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 } -- Convert the dig pattern values to x/z offset vectors do local head = vector.new() dig_pattern[0] = head for i = 1, #dig_pattern do head = vector.add(head, minetest.facedir_to_dir(dig_pattern[i])) dig_pattern[i] = {x = head.x, z = head.z} end end -- Cache of pipeworks fake players local fake_players = {} minetest.register_on_leaveplayer(function(player) fake_players[player:get_player_name()] = nil end) local function get_fake_player(name) if not fake_players[name] then fake_players[name] = pipeworks.create_fake_player({name = name}) end return fake_players[name] end local function player_allowed(pos, name) local owner = minetest.get_meta(pos):get_string("owner") if owner == "" or owner == name then return true end return not minetest.is_protected(pos, name) end local function can_dig_node(pos, dig_pos, node_name, owner, digger) if node_name == "air" or node_name == "vacuum:vacuum" then return false end if vector.equals(pos, dig_pos) then return false -- Don't dig self end local def = minetest.registered_nodes[node_name] if not def or not def.diggable or (def.can_dig and not def.can_dig(dig_pos, digger)) then return false end if def._mcl_hardness == -1 then return false end return not minetest.is_protected(dig_pos, owner) end local function do_purge(pos, meta) local inv = meta:get_inventory() for i, stack in ipairs(inv:get_list("cache")) do if not stack:is_empty() then technic.tube_inject_item(pos, pos, vector.new(0, 1, 0), stack:to_table()) inv:set_stack("cache", i, "") break end end if inv:is_empty("cache") then meta:set_int("purge_on", 0) end end local function spawn_dig_particles(pos, dig_pos, node) local end_pos = vector.new(pos.x, pos.y - 0.5, pos.z) local dist = vector.distance(dig_pos, end_pos) local t = math.sqrt((2 * dist) / 20) local acc = vector.multiply(vector.subtract(end_pos, dig_pos), (1 / dist) * 20) minetest.add_particlespawner({ amount = 50, time = 0.5, minpos = vector.subtract(dig_pos, 0.4), maxpos = vector.add(dig_pos, 0.4), minacc = acc, maxacc = acc, minsize = 0.5, maxsize = 1.5, minexptime = t, maxexptime = t, node = node, }) end local function do_digging(pos, meta, net_time) local us_start = minetest.get_us_time() local step = tonumber(meta:get("step") or "") if not step then -- Missing metadata or not yet updated by conversion LBM, abort digging return end local radius = meta:get_int("size") local diameter = radius * 2 + 1 local num_steps = diameter * diameter local dug = meta:get_int("dug") local max_depth = meta:get_int("max_depth") local offset = { x = meta:get_int("offset_x"), y = math.floor(step / num_steps) + 1 - meta:get_int("offset_y"), z = meta:get_int("offset_z") } if dug == -1 then -- Find ground before digging if offset.y > max_depth then meta:set_int("finished", 1) return end local pos1 = { x = pos.x + offset.x - radius, y = pos.y - offset.y, z = pos.z + offset.z - radius } local pos2 = { x = pos.x + offset.x + radius, y = pos.y - offset.y, z = pos.z + offset.z + radius } minetest.load_area(pos1, pos2) local nodes = minetest.find_nodes_in_area(pos1, pos2, {"air", "vacuum:vacuum"}) if #nodes < num_steps then -- There are nodes to dig, start digging at this layer meta:set_int("dug", 0) else -- Move down to next layer meta:set_int("step", step + num_steps) end return end local owner = meta:get_string("owner") local digger = get_fake_player(owner) while true do -- Search for something to dig if offset.y > max_depth then -- Finished digging meta:set_int("finished", 1) meta:set_int("purge_on", 1) break end local dig_offset = dig_pattern[step % num_steps] local dig_pos = { x = pos.x + offset.x + dig_offset.x, y = pos.y - offset.y, z = pos.z + offset.z + dig_offset.z, } step = step + 1 if step % num_steps == 0 then -- Finished this layer, move down offset.y = offset.y + 1 end local node = technic.get_or_load_node(dig_pos) if can_dig_node(pos, dig_pos, node.name, owner, digger) then -- Found something to dig, dig it and stop minetest.remove_node(dig_pos) if quarry_dig_particles then spawn_dig_particles(pos, dig_pos, node) end local inv = meta:get_inventory() local drops = minetest.get_node_drops(node.name, "") local full = false for _, item in ipairs(drops) do local left = inv:add_item("cache", item) while not left:is_empty() do -- Cache is full, forcibly purge until the item fits full = true do_purge(pos, meta) left = inv:add_item("cache", left) end end dug = dug + 1 if full or dug % 99 == 0 then -- Time to purge the cache meta:set_int("purge_on", 1) end break end local us_used = minetest.get_us_time() - us_start if us_used > quarry_time_limit or net_time + us_used > network_time_limit then break end end meta:set_int("dug", dug) meta:set_int("step", step) end local function quarry_run(pos, _, _, network) local meta = minetest.get_meta(pos) if meta:get_int("purge_on") == 1 then -- Purging meta:set_string("infotext", infotext.purge) meta:set_int("HV_EU_demand", 0) do_purge(pos, meta) elseif meta:get_int("finished") == 1 then -- Finished meta:set_string("infotext", infotext.finished) meta:set_int("HV_EU_demand", 0) elseif meta:get_int("enabled") == 1 then -- Active if meta:get_int("HV_EU_input") >= quarry_demand then meta:set_string("infotext", infotext.active) do_digging(pos, meta, network.lag) else meta:set_string("infotext", infotext.unpowered) end meta:set_int("HV_EU_demand", quarry_demand) else -- Disabled meta:set_int("HV_EU_demand", 0) meta:set_string("infotext", infotext.disabled) if not meta:get_inventory():is_empty("cache") then meta:set_int("purge_on", 1) end end end local function reset_quarry(meta) meta:set_int("step", 0) meta:set_int("dug", -1) meta:set_int("purge_on", 1) meta:set_int("finished", 0) end local size = minetest.get_modpath("mcl_formspec") and "size[9,10]" or "size[8,9]" local base_formspec = size.. "label[0,0;"..S("@1 Quarry", S("HV")).."]".. "list[context;cache;0,0.7;4,3;]".. "listring[context;cache]".. "button[6,0.6;2,1;restart;"..S("Restart").."]".. "field[4.3,2.1;2,1;size;"..S("Radius")..";${size}]".. "field[6.3,2.1;2,1;max_depth;"..S("Max Depth")..";${max_depth}]".. "field[4.3,3.1;1.333,1;offset_x;"..S("Offset X")..";${offset_x}]".. "field[5.633,3.1;1.333,1;offset_y;"..S("Offset Y")..";${offset_y}]".. "field[6.966,3.1;1.333,1;offset_z;"..S("Offset Z")..";${offset_z}]" if has_digilines then base_formspec = base_formspec.. "field[4.3,4.2;4,1;channel;"..S("Digiline Channel")..";${channel}]" end if has_mcl then base_formspec = base_formspec.. mcl_formspec.get_itemslot_bg(0,0.7,4,3).. -- player inventory "list[current_player;main;0,5.5;9,3;9]".. mcl_formspec.get_itemslot_bg(0,5.5,9,3).. "list[current_player;main;0,8.74;9,1;]".. mcl_formspec.get_itemslot_bg(0,8.74,9,1).. "listring[current_player;main]" else base_formspec = base_formspec.. "list[current_player;main;0,5;8,4;]".. "listring[current_player;main]" end local function update_formspec(meta) local fs = base_formspec local status = S("Digging not started") if meta:get_int("purge_on") == 1 then status = S("Purging cache") elseif meta:get_int("finished") == 1 then status = S("Digging finished") elseif meta:get_int("enabled") == 1 then local diameter = meta:get_int("size") * 2 + 1 local num_steps = diameter * diameter local y_level = math.floor(meta:get_int("step") / num_steps) + 1 - meta:get_int("offset_y") if y_level < 0 then status = S("Digging @1 m above machine", math.abs(y_level)) else status = S("Digging @1 m below machine", y_level) end end if meta:get_int("enabled") == 1 then fs = fs.."button[4,0.6;2,1;disable;"..S("Enabled").."]" else fs = fs.."button[4,0.6;2,1;enable;"..S("Disabled").."]" end if has_mesecons then local selected = meta:get("mesecons") or "true" if has_jumpdrive then fs = fs.."checkbox[0,3.6;mesecons;"..S("Enable Mesecons Control")..";"..selected.."]" else fs = fs.."checkbox[0,3.8;mesecons;"..S("Enable Mesecons Control")..";"..selected.."]" end end if has_jumpdrive then local selected = meta:get("reset_on_move") or "true" if has_mesecons then fs = fs.."checkbox[0,4.1;reset_on_move;"..S("Restart When Moved")..";"..selected.."]" else fs = fs.."checkbox[0,3.8;reset_on_move;"..S("Restart When Moved")..";"..selected.."]" end end meta:set_string("formspec", fs.."label[4,0;"..status.."]") end local function clamp(value, min, max, default) value = tonumber(value) or default or max return math.min(math.max(value, min), max) end local function quarry_receive_fields(pos, _, fields, sender) local player_name = sender:get_player_name() if not player_allowed(pos, player_name) then minetest.chat_send_player(player_name, S("You are not allowed to edit this!")) return end local meta = minetest.get_meta(pos) if fields.size then meta:set_int("size", clamp(fields.size, 0, 8, 4)) end if fields.max_depth then local depth = clamp(fields.max_depth, 1, quarry_max_depth) meta:set_int("max_depth", depth) local ymin = -math.min(10, depth - 1) meta:set_int("offset_y", clamp(meta:get_int("offset_y"), ymin, 10, 0)) end if fields.offset_x then meta:set_int("offset_x", clamp(fields.offset_x, -10, 10, 0)) end if fields.offset_y then local ymin = -math.min(10, meta:get_int("max_depth") - 1) meta:set_int("offset_y", clamp(fields.offset_y, ymin, 10, 0)) end if fields.offset_z then meta:set_int("offset_z", clamp(fields.offset_z, -10, 10, 0)) end if fields.mesecons then meta:set_string("mesecons", fields.mesecons) end if fields.reset_on_move then meta:set_string("reset_on_move", fields.reset_on_move) end if fields.channel then meta:set_string("channel", fields.channel) end if fields.enable then meta:set_int("enabled", 1) end if fields.disable then meta:set_int("enabled", 0) end if fields.restart then reset_quarry(meta) end update_formspec(meta) end local function show_working_area(pos, _, player) if not player or player:get_wielded_item():get_name() ~= "" then -- Only spawn particles when using an empty hand return end local meta = minetest.get_meta(pos) local radius = meta:get_int("size") + 0.5 local offset = vector.new(meta:get_int("offset_x"), meta:get_int("offset_y"), meta:get_int("offset_z")) local depth = meta:get_int("max_depth") + 0.5 -- Draw area from top corner to bottom corner local pos1 = vector.add(pos, vector.new(offset.x - radius, offset.y - 0.5, offset.z - radius)) local pos2 = vector.add(pos, vector.new(offset.x + radius, -depth, offset.z + radius)) vizlib.draw_area(pos1, pos2, {player = player}) end local function digiline_action(pos, _, channel, msg) local meta = minetest.get_meta(pos) if channel ~= meta:get_string("channel") then return end -- Convert string message to table if type(msg) == "string" then msg = msg:lower() if msg == "get" or msg == "on" or msg == "off" or msg == "restart" then msg = {command = msg} elseif msg:sub(1, 7) == "radius " then msg = {command = "radius", value = msg:sub(8,-1)} elseif msg:sub(1,10) == "max_depth " then msg = {command = "max_depth", value = msg:sub(11,-1)} elseif msg:sub(1,9) == "offset_x " then msg = {command = "offset_x", value = msg:sub(10,-1)} elseif msg:sub(1,9) == "offset_y " then msg = {command = "offset_y", value = msg:sub(10,-1)} elseif msg:sub(1,9) == "offset_z " then msg = {command = "offset_z", value = msg:sub(10,-1)} elseif msg:sub(1,7) == "offset " then local s = string.split(msg:sub(8,-1), ",") msg = {command = "offset", value = {x = s[1], y = s[2], z = s[3]}} end end if type(msg) ~= "table" then return end -- Convert old message format to new format if msg.command ~= "set" and msg.command ~= "get" then local cmd = msg.command if cmd == "restart" then msg = {command = "set", restart = true} elseif cmd == "on" or cmd == "off" then msg = {command = "set", enabled = msg.command == "on"} elseif cmd == "radius" or cmd == "max_depth" or cmd == "offset" or cmd == "offset_x" or cmd == "offset_y" or cmd == "offset_z" then msg = {command = "set", [cmd] = msg.value} end end -- Process message if msg.command == "get" then local diameter = meta:get_int("size") * 2 + 1 local num_steps = diameter * diameter local offset = { x = meta:get_int("offset_x"), y = meta:get_int("offset_y"), z = meta:get_int("offset_z") } digilines.receptor_send(pos, technic.digilines.rules, channel, { enabled = meta:get_int("enabled") == 1, finished = meta:get_int("finished") == 1, radius = meta:get_int("size"), max_depth = meta:get_int("max_depth"), offset_x = offset.x, offset_y = offset.y, offset_z = offset.z, offset = offset, dug_nodes = meta:get_int("dug"), dig_level = -(math.floor(meta:get_int("step") / num_steps) + 1 - offset.y), }) elseif msg.command == "set" then if msg.enabled ~= nil then meta:set_int("enabled", msg.enabled == true and 1 or 0) end if msg.restart == true then reset_quarry(meta) end if msg.radius then meta:set_int("size", clamp(msg.radius, 0, 8, 4)) end if msg.max_depth then local depth = clamp(msg.max_depth, 1, quarry_max_depth) meta:set_int("max_depth", depth) local ymin = -math.min(10, depth - 1) meta:set_int("offset_y", clamp(meta:get_int("offset_y"), ymin, 10, 0)) end if msg.offset_x then meta:set_int("offset_x", clamp(msg.offset_x, -10, 10, 0)) end if msg.offset_y then local ymin = -math.min(10, meta:get_int("max_depth") - 1) meta:set_int("offset_y", clamp(msg.offset_y, ymin, 10, 0)) end if msg.offset_z then meta:set_int("offset_z", clamp(msg.offset_z, -10, 10, 0)) end if msg.offset and type(msg.offset) == "table" then local ymin = -math.min(10, meta:get_int("max_depth") - 1) meta:set_int("offset_x", clamp(msg.offset.x, -10, 10, 0)) meta:set_int("offset_y", clamp(msg.offset.y, ymin, 10, 0)) meta:set_int("offset_z", clamp(msg.offset.z, -10, 10, 0)) end end end minetest.register_node("technic:quarry", { description = S("@1 Quarry", S("HV")), tiles = { "technic_carbon_steel_block.png^pipeworks_tube_connection_metallic.png", "technic_carbon_steel_block.png^technic_quarry_bottom.png", "technic_carbon_steel_block.png^technic_cable_connection_overlay.png", "technic_carbon_steel_block.png^technic_cable_connection_overlay.png", "technic_carbon_steel_block.png^technic_cable_connection_overlay.png", "technic_carbon_steel_block.png^technic_cable_connection_overlay.png" }, groups = {cracky = 2, tubedevice = 1, technic_machine = 1, technic_hv = 1, pickaxey = 2}, is_ground_content = false, _mcl_blast_resistance = 1, _mcl_hardness = 0.8, connect_sides = {"front", "back", "left", "right"}, tube = { connect_sides = {top = 1}, -- Lower priority than tubes, so items will prefer any tube to another quarry priority = 10, can_go = function(pos, node, velocity, stack) -- Always eject up, even if items came in another way return { vector.new(0, 1, 0) } end }, on_punch = has_vizlib and show_working_area or nil, on_rightclick = function(pos) local meta = minetest.get_meta(pos) update_formspec(meta) end, on_construct = function(pos) local meta = minetest.get_meta(pos) meta:set_int("size", 4) meta:set_int("offset_x", 0) meta:set_int("offset_y", 0) meta:set_int("offset_z", 0) meta:set_int("max_depth", quarry_max_depth) meta:set_string("mesecons", "false") meta:set_string("reset_on_move", "false") meta:get_inventory():set_size("cache", 12) reset_quarry(meta) update_formspec(meta) end, on_movenode = has_jumpdrive and function(_, pos) local meta = minetest.get_meta(pos) if meta:get("reset_on_move") ~= "false" then reset_quarry(meta) end end or nil, after_place_node = function(pos, placer, itemstack) minetest.get_meta(pos):set_string("owner", placer:get_player_name()) pipeworks.scan_for_tube_objects(pos) end, can_dig = function(pos, player) return minetest.get_meta(pos):get_inventory():is_empty("cache") end, after_dig_node = pipeworks.scan_for_tube_objects, on_receive_fields = quarry_receive_fields, technic_run = quarry_run, allow_metadata_inventory_move = function(pos, from_list, from_index, to_list, to_index, count, player) return player_allowed(pos, player:get_player_name()) and count or 0 end, allow_metadata_inventory_put = function(pos, listname, index, stack, player) return player_allowed(pos, player:get_player_name()) and stack:get_count() or 0 end, allow_metadata_inventory_take = function(pos, listname, index, stack, player) return player_allowed(pos, player:get_player_name()) and stack:get_count() or 0 end, on_metadata_inventory_move = technic.machine_on_inventory_move, on_metadata_inventory_put = technic.machine_on_inventory_put, on_metadata_inventory_take = technic.machine_on_inventory_take, mesecons = { effector = { action_on = function(pos) local meta = minetest.get_meta(pos) if meta:get("mesecons") ~= "false" then meta:set_int("enabled", 1) end end, action_off = function(pos) local meta = minetest.get_meta(pos) if meta:get("mesecons") ~= "false" then meta:set_int("enabled", 0) end end } }, digiline = { receptor = { rules = technic.digilines.rules, }, effector = { rules = technic.digilines.rules, action = digiline_action, } }, }) minetest.register_craft({ output = "technic:quarry", recipe = { {"technic:carbon_plate", "pipeworks:filter", "technic:composite_plate"}, {"basic_materials:motor", "technic:machine_casing", "technic:diamond_drill_head"}, {"technic:carbon_steel_block", "technic:hv_cable", "technic:carbon_steel_block"} } }) technic.register_machine("HV", "technic:quarry", technic.receiver) minetest.register_lbm({ label = "Old quarry conversion", name = "technic:old_quarry_conversion", nodenames = {"technic:quarry"}, run_at_every_load = false, action = function(pos, node) local meta = minetest.get_meta(pos) if meta:get("step") then -- Quarry v3, don't do anything -- This can happen when a quarry is moved with a jumpdrive return elseif meta:get("quarry_pos") then -- Quarry v2, calculate step if quarry is digging below local level = meta:get_int("dig_level") - pos.y if level < 0 then local diameter = meta:get_int("size") * 2 + 1 local num_steps = diameter * diameter -- Convert the negative value to positive, and go back up one level level = math.abs(level + 1) meta:set_int("step", level * num_steps) end -- Delete unused meta values meta:set_string("quarry_dir", "") meta:set_string("quarry_pos", "") meta:set_string("dig_pos", "") meta:set_string("dig_level", "") meta:set_string("dig_index", "") meta:set_string("dig_steps", "") else -- Quarry v1, reset quarry reset_quarry(meta) end local dir = minetest.facedir_to_dir(node.param2) local offset = vector.multiply(dir, meta:get_int("size") + 1) meta:set_int("offset_x", offset.x) meta:set_int("offset_y", 4) meta:set_int("offset_z", offset.z) if not meta:get("max_depth") then meta:set_int("max_depth", quarry_max_depth) end update_formspec(meta) end })