From 2695c6103383996fedfd12098cf3f28c5fc43fe2 Mon Sep 17 00:00:00 2001 From: FaceDeer Date: Fri, 30 Dec 2016 23:38:18 -0700 Subject: Initial commit Initial commit. Contains digger head, sand digger, builder, controller, pusher, structure node, light, inventory, and digtron core crafting item. --- util.lua | 312 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 312 insertions(+) create mode 100644 util.lua (limited to 'util.lua') diff --git a/util.lua b/util.lua new file mode 100644 index 0000000..c9e6a34 --- /dev/null +++ b/util.lua @@ -0,0 +1,312 @@ +-- A random assortment of methods used in various places in this mod. + +digtron = {} + +dofile( minetest.get_modpath( "digtron" ) .. "/util_item_place_node.lua" ) -- separated out to avoid potential for license complexity + +digtron.find_new_pos = function(pos, facing) + -- finds the point one node "forward", based on facing + local dir = minetest.facedir_to_dir(facing) + local newpos = {} + newpos.x = pos.x + dir.x + newpos.y = pos.y + dir.y + newpos.z = pos.z + dir.z + return newpos +end + +digtron.mark_diggable = function(pos, nodes_dug) + -- mark the node as dug, if the player provided would have been able to dig it. + -- Don't *actually* dig the node yet, though, because if we dig a node with sand over it the sand will start falling + -- and then destroy whatever node we place there subsequently (either by a builder head or by moving a digtron node) + -- I don't like sand. It's coarse and rough and irritating and it gets everywhere. And it necessitates complicated dig routines. + -- returns what will be dropped by digging these nodes. + + local target = minetest.get_node(pos) + + -- prevent digtrons from being marked for digging. + if minetest.get_item_group(target.name, "digtron") ~= 0 then + return nil + end + + local targetdef = minetest.registered_nodes[target.name] + if targetdef.can_dig == nil or targetdef.can_dig(pos, player) then + nodes_dug:set(pos.x, pos.y, pos.z, true) + if target.name ~= "air" then + return minetest.get_node_drops(target.name, "") + end + end + return nil +end + +digtron.can_build_to = function(pos, protected_nodes, dug_nodes) + -- Returns whether a space is clear to have something put into it + + if protected_nodes:get(pos.x, pos.y, pos.z) then + return false + end + + -- tests if the location pointed to is clear to move something into + local target = minetest.get_node(pos) + if target.name == "air" or + dug_nodes:get(pos.x, pos.y, pos.z) == true or + minetest.registered_nodes[target.name].buildable_to == true + then + return true + end + return false +end + +digtron.can_move_to = function(pos, protected_nodes, dug_nodes) + -- Same as can_build_to, but also checks if the current node is part of the digtron. + -- this allows us to disregard obstructions that *will* move out of the way. + if digtron.can_build_to(pos, protected_nodes, dug_nodes) == true or + minetest.get_item_group(minetest.get_node(pos).name, "digtron") ~= 0 then + return true + end + return false +end + +digtron.move_node = function(pos, newpos) + -- Moves nodes, preserving digtron metadata and inventory + local node = minetest.get_node(pos) + minetest.add_node(newpos, { name=node.name, param1=node.param1, param2=node.param2 }) + + local oldmeta = minetest.get_meta(pos) + local oldinv = oldmeta:get_inventory() + local list = oldinv:get_list("main") + local oldformspec = oldmeta:get_string("formspec") + + local newmeta = minetest.get_meta(newpos) + local newinv = newmeta:get_inventory() + newinv:set_list("main", list) + newmeta:set_string("formspec", oldformspec) + + newmeta:set_string("offset", oldmeta:get_string("offset")) + newmeta:set_string("period", oldmeta:get_string("period")) + newmeta:set_string("build_facing", oldmeta:get_string("build_facing")) + + -- remove node from old position + minetest.remove_node(pos) +end + +digtron.get_all_digtron_neighbours = function(pos, player) + -- returns table containing a list of all digtron node locations, lists of special digtron node types, a table of the coordinate extents of the digtron array, a Pointset of protected nodes, and a bool to determine if the array is adjacent to non-digtron nodes (for traction) + + --minetest.debug(string.format("digtron search started at component %d %d %d", pos.x, pos.y, pos.z)) + + local layout = {} + --initialize. We're assuming that the start position is a controller digtron, should be a safe assumption since only the controller node should call this + layout.traction = false + layout.all = {} + layout.inventories = {} + layout.diggers = {} + layout.builders = {} + layout.extents = {} + layout.protected = Pointset.create() -- if any nodes we look at are protected, make note of that. That way we don't need to keep re-testing protection state later. + layout.controller = {x=pos.x, y=pos.y, z=pos.z} --Make a deep copy of the pos parameter just in case the calling code wants to play silly buggers with it + + table.insert(layout.all, layout.controller) + layout.extents.max_x = pos.x + layout.extents.min_x = pos.x + layout.extents.max_y = pos.y + layout.extents.min_y = pos.y + layout.extents.max_z = pos.z + layout.extents.min_z = pos.z + + -- temporary pointsets used while searching + local to_test = Pointset.create() + local tested = Pointset.create() + + tested:set(pos.x, pos.y, pos.z, true) + to_test:set(pos.x + 1, pos.y, pos.z, true) + to_test:set(pos.x - 1, pos.y, pos.z, true) + to_test:set(pos.x, pos.y + 1, pos.z, true) + to_test:set(pos.x, pos.y - 1, pos.z, true) + to_test:set(pos.x, pos.y, pos.z + 1, true) + to_test:set(pos.x, pos.y, pos.z - 1, true) + + if minetest.is_protected(pos, player:get_player_name()) and not minetest.check_player_privs(player, "protection_bypass") then + layout.protected:set(pos.x, pos.y, pos.z, true) + end + + -- Do a loop on to_test positions, adding new to_test positions as we find digtron nodes. This is a flood fill operation + -- that follows node faces (no diagonals) + local testpos, _ = to_test:pop() + while testpos ~= nil do + tested:set(testpos.x, testpos.y, testpos.z, true) -- track nodes we've looked at to prevent infinite loops + local node = minetest.get_node(testpos) + + if node.name == "ignore" then + --buildtron array is next to unloaded nodes, too dangerous to do anything. Abort. + layout.all = nil + return layout + end + + if minetest.is_protected(pos, player:get_player_name()) and not minetest.check_player_privs(player, "protection_bypass") then + layout.protected:set(testpos.x, testpos.y, testpos.z, true) + end + + local group_number = minetest.get_item_group(node.name, "digtron") + if group_number > 0 then + --minetest.debug(string.format("found digtron component at %d %d %d", testpos.x, testpos.y, testpos.z)) + --found one. Add it to the digtrons output + table.insert(layout.all, testpos) + + -- update extents + layout.extents.max_x = math.max(layout.extents.max_x, testpos.x) + layout.extents.min_x = math.min(layout.extents.min_x, testpos.x) + layout.extents.max_y = math.max(layout.extents.max_y, testpos.y) + layout.extents.min_y = math.min(layout.extents.min_y, testpos.y) + layout.extents.max_z = math.max(layout.extents.max_z, testpos.z) + layout.extents.min_z = math.min(layout.extents.min_z, testpos.z) + + -- add a reference to this node's position to special node lists + if group_number == 2 then + table.insert(layout.inventories, testpos) + elseif group_number == 3 then + table.insert(layout.diggers, testpos) + elseif group_number == 4 then + table.insert(layout.builders, testpos) + end + + --queue up potential new test points adjacent to this digtron node + to_test:set_if_not_in(tested, testpos.x + 1, testpos.y, testpos.z, true) + to_test:set_if_not_in(tested, testpos.x - 1, testpos.y, testpos.z, true) + to_test:set_if_not_in(tested, testpos.x, testpos.y + 1, testpos.z, true) + to_test:set_if_not_in(tested, testpos.x, testpos.y - 1, testpos.z, true) + to_test:set_if_not_in(tested, testpos.x, testpos.y, testpos.z + 1, true) + to_test:set_if_not_in(tested, testpos.x, testpos.y, testpos.z - 1, true) + elseif not layout.traction and minetest.registered_nodes[node.name].buildable_to ~= true then + -- Tracks whether the digtron is hovering in mid-air. If any part of the digtron array touches something solid, it can move. + layout.traction = true + end + + testpos, _ = to_test:pop() + end + + return layout +end + +digtron.place_in_inventory = function(itemname, inventory_positions, fallback_pos) + --tries placing the item in each inventory node in turn. If there's no room, drop it at fallback_pos + local itemstack = ItemStack(itemname) + for k, location in pairs(inventory_positions) do + local inv = minetest.get_inventory({type="node", pos=location}) + itemstack = inv:add_item("main", itemstack) + if itemstack:is_empty() then + return nil + end + end + minetest.add_item(fallback_pos, itemstack) +end + +digtron.place_in_specific_inventory = function(itemname, pos, inventory_positions, fallback_pos) + --tries placing the item in a specific inventory. Other parameters are used as fallbacks on failure + --Use this method for putting stuff back after testing and failed builds so that if the player + --is trying to keep various inventories organized manually stuff will go back where it came from, + --probably. + local itemstack = ItemStack(itemname) + local inv = minetest.get_inventory({type="node", pos=pos}) + local returned_stack = inv:add_item("main", itemstack) + if not returned_stack:is_empty() then + -- we weren't able to put the item back into that particular inventory for some reason. + -- try putting it *anywhere.* + digtron.place_in_inventory(returned_stack, inventory_positions, fallback_pos) + end +end + +digtron.take_from_inventory = function(itemname, inventory_positions) + --tries to take an item from each inventory node in turn. Returns location of inventory item was taken from on success, nil on failure + local itemstack = ItemStack(itemname) + for k, location in pairs(inventory_positions) do + local inv = minetest.get_inventory({type="node", pos=location}) + local output = inv:remove_item("main", itemstack) + if not output:is_empty() then + return location + end + end + return nil +end + +digtron.move_digtron = function(facing, digtrons, extents, nodes_dug) + -- move everything. Note! order is important or they'll step on each other, that's why this has complicated loops and filtering. + -- Nodes are moved in a "caterpillar" pattern - front plane first, then next plane back, then next plane back, etc. + -- positions in the digtron list will be updated when this method executes. Note that the inventories list shares + -- references to the node position tables in the digtron list, so it will reflect the updates too. + local dir = digtron.facedir_to_dir_map[facing] + local increment + local filter + local index + local target + if dir == 1 then -- z+ + filter = "z" + increment = -1 + index = extents.max_z + target = extents.min_z + extents.max_z = extents.max_z + 1 + extents.min_z = extents.min_z + 1 + elseif dir == 2 then -- x+ + filter = "x" + increment = -1 + index = extents.max_x + target = extents.min_x + extents.max_x = extents.max_x + 1 + extents.min_x = extents.min_x + 1 + elseif dir == 3 then -- z- + filter = "z" + increment = 1 + index = extents.min_z + target = extents.max_z + extents.max_z = extents.max_z - 1 + extents.min_z = extents.min_z - 1 + elseif dir == 4 then -- x- + filter = "x" + increment = 1 + index = extents.min_x + target = extents.max_x + extents.max_x = extents.max_x - 1 + extents.min_x = extents.min_x - 1 + elseif dir == 5 then -- y- + filter = "y" + increment = 1 + index = extents.min_y + target = extents.max_y + extents.max_y = extents.max_y - 1 + extents.min_y = extents.min_y - 1 + elseif dir == 6 then -- y+ + filter = "y" + increment = -1 + index = extents.max_y + target = extents.min_y + extents.max_y = extents.max_y + 1 + extents.min_y = extents.min_y + 1 + end + + while index ~= target + increment do + for k, location in pairs(digtrons) do + if location[filter] == index then + local newpos = digtron.find_new_pos(location, facing) + digtron.move_node(location, newpos) + --By updating the digtron position table in-place we also update all the special node tables as well + digtrons[k].x= newpos.x + digtrons[k].y= newpos.y + digtrons[k].z= newpos.z + nodes_dug:set(newpos.x, newpos.y, newpos.z, false) -- we've moved a digtron node into this space, mark it so that we don't dig it. + end + end + index = index + increment + end +end + +-- Used to determine which coordinate is being checked for periodicity. eg, if the digtron is moving in the z direction, then periodicity is checked for every n nodes in the z axis. +digtron.get_controlling_coordinate = function(pos, facedir) + -- used for determining builder period and offset + local dir = digtron.facedir_to_dir_map[facedir] + if dir == 1 or dir == 3 then + return "z" + elseif dir == 2 or dir == 4 then + return "x" + else + return "y" + end +end \ No newline at end of file -- cgit v1.2.3