-- A simple special-purpose class, this is used for building up sets of three-dimensional points for fast reference Pointset = {} Pointset.__index = Pointset function Pointset.create() local set = {} setmetatable(set,Pointset) set.points = {} return set end function Pointset:set(x, y, z, value) -- sets a value in the 3D array "points". if self.points[x] == nil then self.points[x] = {} end if self.points[x][y] == nil then self.points[x][y] = {} end self.points[x][y][z] = value end function Pointset:set_if_not_in(excluded, x, y, z, value) -- If a value is not already set for this point in the 3D array "excluded", set it in "points" if excluded:get(x, y, z) ~= nil then return end self:set(x, y, z, value) end function Pointset:get(x, y, z) -- return a value from the 3D array "points" if self.points[x] == nil or self.points[x][y] == nil then return nil end return self.points[x][y][z] end function Pointset:set_pos(pos, value) self:set(pos.x, pos.y, pos.z, value) end function Pointset:set_pos_if_not_in(excluded, pos, value) self:set_if_not_in(excluded, pos.x, pos.y, pos.z, value) end function Pointset:get_pos(pos) return self:get(pos.x, pos.y, pos.z) end function Pointset:pop() -- returns a point that's in the 3D array, and then removes it. local pos = {} local ytable local ztable local val local count = 0 for _ in pairs(self.points) do count = count + 1 end if count == 0 then return nil end pos.x, ytable = next(self.points) pos.y, ztable = next(ytable) pos.z, val = next(ztable) self.points[pos.x][pos.y][pos.z] = nil count = 0 for _ in pairs(self.points[pos.x][pos.y]) do count = count + 1 end if count == 0 then self.points[pos.x][pos.y] = nil end count = 0 for _ in pairs(self.points[pos.x]) do count = count + 1 end if count == 0 then self.points[pos.x] = nil end return pos, val end function Pointset:get_pos_list(value) -- Returns a list of all points with the given value in standard Minetest vector format. If no value is provided, returns all points local outlist = {} for x, ytable in ipairs(self.points) do for y, ztable in ipairs(ytable) do for z, val in ipairs(ztable) do if (value == nil and val ~= nil ) or val == value then table.insert(outlist, {x=x, y=y, z=z}) end end end end return outlist end -- Configuration local xnotreetap_max_charge = 30000 -- Maximum charge of the saw -- Gives 2500 nodes on a single charge (about 50 complete normal trees) local xnotreetap_charge_per_node = 12 -- Cut down tree leaves. Leaf decay may cause slowness on large trees -- if this is disabled. local xnotreetap_leaves = true -- First value is node name; second is whether the node is considered even if xnotreetap_leaves is false. local nodes = { -- Rubber trees from moretrees or technic_worldgen if moretrees isn't installed {"moretrees:rubber_tree_trunk_empty", true}, {"moretrees:rubber_tree_trunk", true}, {"moretrees:rubber_tree_leaves", false}, } -- Remember node visited recursive and not dig twice local nodeVisited = Pointset.create() local timber_nodenames = {} for _, node in pairs(nodes) do if xnotreetap_leaves or node[2] then timber_nodenames[node[1]] = true end end local S = technic.getter technic.register_power_tool("technic:xnotreetap", xnotreetap_max_charge) local mesecons_materials = minetest.get_modpath("mesecons_materials") -- This function checks if the specified node should be sawed local function check_if_node_sawed(pos) local node = minetest.get_node(pos) local node_name = node.name if timber_nodenames[node_name] or (xnotreetap_leaves and minetest.get_item_group(node_name, "leaves") ~= 0) or minetest.get_item_group(node_name, "tree") ~= 0 then minetest.log("action", "[Xno Tree Tap] "..node_name.." good node to collect.") --print to log return true end return false end -- Table for saving what was sawed down local produced = {} local function myString (v) return v.x.." "..v.y.." "..v.z.." " end -- Save the items sawed down so that we can drop them in a nice single stack local function handle_drops(drops) for _, item in ipairs(drops) do local stack = ItemStack(item) local name = stack:get_name() local p = produced[name] if not p then produced[name] = stack else p:set_count(p:get_count() + stack:get_count()) end end end --- Iterator over positions to try to saw around a sawed node. -- This returns positions in a 3x1x3 area around the position, plus the -- position above it. This does not return the bottom position to prevent -- the chainsaw from cutting down nodes below the cutting position. -- @param pos Sawing position. local function iterSawTries(pos) -- Copy position to prevent mangling it local pos = vector.new(pos) local i = 0 return function() i = i + 1 -- Given a (top view) area like so (where 5 is the starting position): -- X --> -- Z 123 -- | 456 -- V 789 -- This will return positions 1, 4, 7, 2, 8 (skip 5), 3, 6, 9, -- and the position above 5. if i == 1 then -- Move to starting position pos.x = pos.x - 1 pos.z = pos.z - 1 elseif i == 4 or i == 7 then -- Move to next X and back to start of Z when we reach -- the end of a Z line. pos.x = pos.x + 1 pos.z = pos.z - 2 elseif i == 5 then -- Skip the middle position (we've already run on it) -- and double-increment the counter. pos.z = pos.z + 2 i = i + 1 elseif i <= 9 then -- Go to next Z. pos.z = pos.z + 1 elseif i == 10 then -- Move back to center and up. -- The Y+ position must be last so that we don't dig -- straight upward and not come down (since the Y- -- position isn't checked). pos.x = pos.x - 1 pos.z = pos.z - 1 pos.y = pos.y + 1 else return nil end return pos end end -- inserisco un valore in tabella local function add_table(table, toadd) table:set(toadd.x, toadd.y, toadd.z, true) end -- bool controllo se un valore è presente in tabella local function in_table(table, pos) local val = table:get(pos.x, pos.y, pos.z) local visited = 'NO' if val ~= nil then if val then visited = 'SI' end minetest.log("action", "[Xno Tree Tap] "..myString(pos).." visited? " .. visited) --print to log return val end return false end -- verifico se un nodo è nella tabella visitato local function check_if_node_visited (pos) return in_table(nodeVisited, pos) end local function set_node_visited (pos) minetest.log("action", "[Xno Tree Tap] "..myString(pos).." set node visited.") --print to log add_table(nodeVisited, pos) end -- This function does all the hard work. Recursively we dig the node at hand -- if it is in the table and then search the surroundings for more stuff to dig. local function recursive_dig(pos, remaining_charge) if remaining_charge < xnotreetap_charge_per_node then return remaining_charge end if check_if_node_visited(pos) then return remaining_charge end if not check_if_node_sawed(pos) then return remaining_charge end local node = minetest.get_node(pos) if node.name == "moretrees:rubber_tree_trunk" then --raccolta gomma node.name = "moretrees:rubber_tree_trunk_empty" minetest.swap_node(pos, node) handle_drops(minetest.get_node_drops("technic:raw_latex", "")) remaining_charge = remaining_charge - xnotreetap_charge_per_node -- Wood found - cut it -- handle_drops(minetest.get_node_drops(node.name, "")) -- minetest.remove_node(pos) -- remaining_charge = remaining_charge - xnotreetap_charge_per_node end if node.name == "moretrees:rubber_tree_leaves" then -- Leaves found - cut it handle_drops(minetest.get_node_drops(node.name, "")) minetest.remove_node(pos) remaining_charge = remaining_charge - xnotreetap_charge_per_node end set_node_visited(pos) -- Check surroundings and run recursively if any charge left for npos in iterSawTries(pos) do if remaining_charge < xnotreetap_charge_per_node then break end if check_if_node_sawed(npos) then remaining_charge = recursive_dig(npos, remaining_charge) else minetest.check_for_falling(npos) end end return remaining_charge end -- Function to randomize positions for new node drops local function get_drop_pos(pos) local drop_pos = {} for i = 0, 8 do -- Randomize position for a new drop drop_pos.x = pos.x + math.random(-3, 3) drop_pos.y = pos.y - 1 drop_pos.z = pos.z + math.random(-3, 3) -- Move the randomized position upwards until -- the node is air or unloaded. for y = drop_pos.y, drop_pos.y + 5 do drop_pos.y = y local node = minetest.get_node_or_nil(drop_pos) if not node then -- If the node is not loaded yet simply drop -- the item at the original digging position. return pos elseif node.name == "air" then -- Add variation to the entity drop position, -- but don't let drops get too close to the edge drop_pos.x = drop_pos.x + (math.random() * 0.8) - 0.5 drop_pos.z = drop_pos.z + (math.random() * 0.8) - 0.5 return drop_pos end end end -- Return the original position if this takes too long return pos end -- Chainsaw entry point local function xnotreetap_dig(pos, current_charge) -- Start sawing things down local remaining_charge = recursive_dig(pos, current_charge) minetest.sound_play("chainsaw", {pos = pos, gain = 1.0, max_hear_distance = 10}) -- Now drop items for the player for name, stack in pairs(produced) do -- Drop stacks of stack max or less local count, max = stack:get_count(), stack:get_stack_max() stack:set_count(max) while count > max do minetest.add_item(get_drop_pos(pos), stack) count = count - max end stack:set_count(count) minetest.add_item(get_drop_pos(pos), stack) end -- Clean up produced = {} return remaining_charge end minetest.register_tool("technic:xnotreetap", { description = S("Xno Tree Tap"), inventory_image = "technic_tree_tap.png", stack_max = 1, wear_represents = "technic_RE_charge", on_refill = technic.refill_RE_charge, on_use = function(itemstack, user, pointed_thing) if pointed_thing.type ~= "node" then return itemstack end --check tool charge local meta = minetest.deserialize(itemstack:get_metadata()) if not meta or not meta.charge or meta.charge < xnotreetap_charge_per_node then return end --check node protection local pos = pointed_thing.under if minetest.is_protected(pos, user:get_player_name()) then minetest.record_protection_violation(pos, user:get_player_name()) return end --can collect only from rubber local node = minetest.get_node(pos) local node_name = node.name if node_name ~= "moretrees:rubber_tree_trunk" then return end -- Send current charge to digging function so that the -- chainsaw will stop after digging a number of nodes meta.charge = xnotreetap_dig(pointed_thing.under, meta.charge) if not technic.creative_mode then technic.set_RE_wear(itemstack, meta.charge, xnotreetap_max_charge) itemstack:set_metadata(minetest.serialize(meta)) end return itemstack end, }) minetest.register_craft({ output = "technic:xnotreetap", recipe = { {"pipeworks:tube_1", "group:wood", "default:stick"}, {"technic:battery", "default:stick", "default:stick"}, {"technic:battery", "default:stick", "default:stick"}, }, }) --minetest.register_abm({ -- label = "Tools: xno tree tap", -- nodenames = {"moretrees:rubber_tree_trunk_empty"}, -- interval = 60, -- chance = 15, -- action = function(pos, node) -- if minetest.find_node_near(pos, (moretrees and moretrees.leafdecay_radius) or 5, {"moretrees:rubber_tree_leaves"}) then -- node.name = "moretrees:rubber_tree_trunk" -- minetest.swap_node(pos, node) -- end -- end --})