From d7e5309833bc7044447982424dc8431d84d7a1a0 Mon Sep 17 00:00:00 2001 From: FaceDeer Date: Sun, 8 Jan 2017 01:23:10 -0700 Subject: [PATCH] Added a rotation controller Well that was a lot of work. Also, switched the "waiting" timer management to the actual on_timer system - I noticed that minetest.after wasn't persisting through server shutdown and restart, that could put a controller in a "broken" state. --- README.txt | 5 + init.lua | 3 +- node_axle.lua | 58 ++++++++ node_builders.lua | 6 + node_controllers.lua | 32 +++-- recipes.lua | 17 ++- sounds/license.txt | 3 +- sounds/whirr.ogg | Bin 0 -> 22337 bytes textures/digtron_axel_side.png | Bin 0 -> 737 bytes textures/digtron_axel_top.png | Bin 0 -> 743 bytes util_execute_cycle.lua | 12 +- util_layout.lua | 250 ++++++++++++++++++++++++++++++++- util_movement.lua | 6 +- 13 files changed, 361 insertions(+), 31 deletions(-) create mode 100644 node_axle.lua create mode 100644 sounds/whirr.ogg create mode 100644 textures/digtron_axel_side.png create mode 100644 textures/digtron_axel_top.png diff --git a/README.txt b/README.txt index a2103ea..6cd65b9 100644 --- a/README.txt +++ b/README.txt @@ -52,6 +52,11 @@ Aka the "can you rebuild it six inches to the left" module. This is a much simpl Since movement alone does not require fuel, a pusher module has no internal furnace. +Rotation Unit +----------- + +This magical module can rotate a Digtron array in place around itself. Right-clicking on it will rotate the Digtron 90 degrees in the direction the orange arrows on its sides indicate (widdershins around the Y axis by default, use the screwdriver to change this) assuming there's space for the Digtron in its new orientation. Builders and diggers will not trigger. + Digger Head ----------- diff --git a/init.lua b/init.lua index eed2ad7..af0492c 100644 --- a/init.lua +++ b/init.lua @@ -6,7 +6,8 @@ dofile( minetest.get_modpath( "digtron" ) .. "/node_storage.lua" ) -- contains i dofile( minetest.get_modpath( "digtron" ) .. "/node_diggers.lua" ) -- contains all diggers dofile( minetest.get_modpath( "digtron" ) .. "/node_builders.lua" ) -- contains all builders (there's just one currently) dofile( minetest.get_modpath( "digtron" ) .. "/node_controllers.lua" ) -- controllers -dofile( minetest.get_modpath( "digtron" ) .."/recipes.lua" ) +dofile( minetest.get_modpath( "digtron" ) .. "/node_axle.lua" ) -- Rotation controller +dofile( minetest.get_modpath( "digtron" ) .. "/recipes.lua" ) digtron.creative_mode = false -- this causes digtrons to operate without consuming fuel or building materials. digtron.particle_effects = true -- Enables the spray of particles out the back of a digger head and puffs of smoke from the controller diff --git a/node_axle.lua b/node_axle.lua new file mode 100644 index 0000000..5859777 --- /dev/null +++ b/node_axle.lua @@ -0,0 +1,58 @@ +minetest.register_node("digtron:axle", { + description = "Digtron Rotation Unit", + groups = {cracky = 3, oddly_breakable_by_hand=3, digtron = 1}, + drop = "digtron:axel", + sounds = digtron.metal_sounds, + paramtype = "light", + paramtype2= "facedir", + is_ground_content = false, + -- Aims in the +Z direction by default + tiles = { + "digtron_axel_top.png", + "digtron_axel_top.png", + "digtron_axel_side.png", + "digtron_axel_side.png", + "digtron_axel_side.png", + "digtron_axel_side.png", + }, + + drawtype = "nodebox", + node_box = { + type = "fixed", + fixed = { + {-0.5, 0.3125, -0.3125, 0.5, 0.5, 0.3125}, -- Uppercap + {-0.5, -0.5, -0.3125, 0.5, -0.3125, 0.3125}, -- Lowercap + {-0.3125, 0.3125, -0.5, 0.3125, 0.5, -0.3125}, -- Uppercap_edge2 + {-0.3125, 0.3125, 0.3125, 0.3125, 0.5, 0.5}, -- Uppercap_edge1 + {-0.3125, -0.5, -0.5, 0.3125, -0.3125, -0.3125}, -- Lowercap_edge1 + {-0.3125, -0.5, 0.3125, 0.3125, -0.3125, 0.5}, -- Lowercap_edge2 + {-0.25, -0.3125, -0.25, 0.25, 0.3125, 0.25}, -- Axel + } + }, + + on_rightclick = function(pos, node, clicker, itemstack, pointed_thing) + local meta = minetest.get_meta(pos) + if meta:get_string("waiting") == "true" then + -- Been too soon since last time the digtron rotated. + return + end + local image = digtron.get_layout_image(pos, clicker) + digtron.rotate_layout_image(image, node.param2) + if digtron.can_write_layout_image(image, clicker) then + digtron.write_layout_image(image) + + minetest.sound_play("whirr", {gain=1.0, pos=pos}) + meta = minetest.get_meta(pos) + meta:set_string("waiting", "true") + meta:set_string("infotext", nil) + minetest.get_node_timer(pos):start(digtron.cycle_time*2) + else + minetest.sound_play("buzzer", {gain=1.0, pos=pos}) + meta:set_string("infotext", "Digtron is obstructed.") + end + end, + + on_timer = function(pos, elapsed) + minetest.get_meta(pos):set_string("waiting", nil) + end, +}) \ No newline at end of file diff --git a/node_builders.lua b/node_builders.lua index 94467a8..9e348cc 100644 --- a/node_builders.lua +++ b/node_builders.lua @@ -89,6 +89,8 @@ minetest.register_node("digtron:builder", { offset = meta:get_int("offset") end if build_facing and build_facing >= 0 and build_facing < 24 then + -- TODO: wallmounted facings only run from 0-5, a player could theoretically put a wallmounted item into the builder and then manually set the build facing to an invalid number + -- Should prevent that somehow. But not tonight. meta:set_int("build_facing", math.floor(build_facing)) end @@ -134,6 +136,10 @@ minetest.register_node("digtron:builder", { on_destruct = function(pos) digtron.remove_builder_item(pos) end, + + after_place_node = function(pos) + digtron.update_builder_item(pos) + end, allow_metadata_inventory_put = function(pos, listname, index, stack, player) local inv = minetest.get_inventory({type="node", pos=pos}) diff --git a/node_controllers.lua b/node_controllers.lua index 323b8f2..7dc1e7f 100644 --- a/node_controllers.lua +++ b/node_controllers.lua @@ -58,11 +58,11 @@ minetest.register_node("digtron:controller", { -- Start the delay before digtron can run again. minetest.get_meta(newpos):set_string("waiting", "true") - minetest.after(digtron.cycle_time, - function (pos) - minetest.get_meta(pos):set_string("waiting", nil) - end, newpos - ) + minetest.get_node_timer(newpos):start(digtron.cycle_time) + end, + + on_timer = function(pos, elapsed) + minetest.get_meta(pos):set_string("waiting", nil) end, }) @@ -180,6 +180,11 @@ minetest.register_node("digtron:auto_controller", { meta:set_string("waiting", "true") meta:set_string("formspec", auto_formspec) end, + + on_timer = function(pos, elapsed) + minetest.get_meta(pos):set_string("waiting", nil) + end, + }) --------------------------------------------------------------------------------------------------------------- @@ -250,11 +255,7 @@ minetest.register_node("digtron:pusher", { if not can_move then -- mark this node as waiting, will clear this flag in digtron.cycle_time seconds meta:set_string("waiting", "true") - minetest.after(digtron.cycle_time, - function (pos) - minetest.get_meta(pos):set_string("waiting", nil) - end, pos - ) + minetest.get_node_timer(pos):start(digtron.cycle_time) minetest.sound_play("squeal", {gain=1.0, pos=pos}) minetest.sound_play("buzzer", {gain=0.5, pos=pos}) meta:set_string("infotext", "Digtron is obstructed.") @@ -283,10 +284,11 @@ minetest.register_node("digtron:pusher", { -- Start the delay before digtron can run again. Do this after moving the array or pos will be wrong. minetest.get_meta(pos):set_string("waiting", "true") - minetest.after(digtron.cycle_time, - function (pos) - minetest.get_meta(pos):set_string("waiting", nil) - end, pos - ) + minetest.get_node_timer(pos):start(digtron.cycle_time) end, + + on_timer = function(pos, elapsed) + minetest.get_meta(pos):set_string("waiting", nil) + end, + }) \ No newline at end of file diff --git a/recipes.lua b/recipes.lua index 141ebf2..7f368cf 100644 --- a/recipes.lua +++ b/recipes.lua @@ -102,6 +102,15 @@ minetest.register_craft({ } }) +minetest.register_craft({ + output = "digtron:axle", + recipe = { + {"default:coal_lump","default:coal_lump","default:coal_lump"}, + {"default:coal_lump","digtron:digtron_core","default:coal_lump"}, + {"default:coal_lump","default:coal_lump","default:coal_lump"} + } +}) + -- Structural minetest.register_craft({ @@ -193,7 +202,6 @@ minetest.register_craft({ } }) - minetest.register_craft({ output = "digtron:digtron_core", recipe = { @@ -227,4 +235,11 @@ minetest.register_craft({ recipe = { {"digtron:pusher"}, } +}) + +minetest.register_craft({ + output = "digtron:digtron_core", + recipe = { + {"digtron:axle"}, + } }) \ No newline at end of file diff --git a/sounds/license.txt b/sounds/license.txt index 165e797..afaaa45 100644 --- a/sounds/license.txt +++ b/sounds/license.txt @@ -1,13 +1,14 @@ The sounds in this folder were sampled from source .wavs from Freesound.org. Specifically: buzzer.ogg - https://freesound.org/people/hypocore/sounds/164090/ - public domain via CC 1.0 by hypocore -construction.ogg - https://www.freesound.org/people/mediapetros/sounds/109117/ - under the CC by 3.0 license by mediapetros +construction.ogg - https://www.freesound.org/people/mediapetros/sounds/109117/ - under the CC BY 3.0 license by mediapetros dingding.ogg - https://www.freesound.org/people/JohnsonBrandEditing/sounds/173932/ public domain via CC 1.0 by JohnsonBrandEditing squeal.ogg - https://www.freesound.org/people/RutgerMuller/sounds/104026/ public domain via CC 1.0 by RutgerMuller honk.ogg - https://freesound.org/people/bigmanjoe/sounds/349922/ public domain via CC 1.0 by bigmanjoe truck.ogg - https://www.freesound.org/people/jberkuta14/sounds/134898/ public domain via CC 1.0 by jberkuta14 sploosh.ogg - https://www.freesound.org/people/mr_marcello/sounds/257609/ public domain via CC 1.0 by mr_marcello woopwoopwoop.ogg - https://www.freesound.org/people/gregconquest/sounds/188012/ public domain via CC 1.0 by gregconquest +whirr.ogg - https://www.freesound.org/people/daveincamas/sounds/25034/ - under the CC BY 3.0 license by daveincamas Creative Commons Attribution 3.0 license: diff --git a/sounds/whirr.ogg b/sounds/whirr.ogg new file mode 100644 index 0000000000000000000000000000000000000000..ebb3dc737ea4bef991da667253f84e2fb2553ab1 GIT binary patch literal 22337 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 T3W1CX00000NkvXXu0mjf9xO(n literal 0 HcmV?d00001 diff --git a/textures/digtron_axel_top.png b/textures/digtron_axel_top.png new file mode 100644 index 0000000000000000000000000000000000000000..86f292fa6c2164565b2cc73ba5d786d3385459c3 GIT binary patch literal 743 zcmV?P)1XZphhBz(`^l;urW4~A|)})-q)EkXHxji*xSA{f;*EfG29UZOL>-Bnle0==%%NIgIL}spccAPQf zvK)}e;P&C4F^Vbx03tf)^m_mh005#27-JJ?N-0Sy%SzDf|FnN`adC2T0sv=cXM1~l z-g{Nm7y*DKSOsJjBCEzbO)sB6Yah4g=jQKU%$|#l7xurdiMF#Cp>2CYjGRZ|ik`_35Stcx)MptE4TBQhp)nMkm{EUKMZGm8?O^9lq2 zlWFtsxBseYl4X9m`r+Z*#ynXqAKML*8~Uvh{r%TH0`kzJwJLCX`;m~CdHe8ifB!Wl Z{U0^qU|}f6T3i4C002ovPDHLkV1j3(NVWg~ literal 0 HcmV?d00001 diff --git a/util_execute_cycle.lua b/util_execute_cycle.lua index 0fe212e..c9614d5 100644 --- a/util_execute_cycle.lua +++ b/util_execute_cycle.lua @@ -122,11 +122,7 @@ digtron.execute_cycle = function(pos, clicker) if not can_move then -- mark this node as waiting, will clear this flag in digtron.cycle_time seconds minetest.get_meta(pos):set_string("waiting", "true") - minetest.after(digtron.cycle_time, - function (pos) - minetest.get_meta(pos):set_string("waiting", nil) - end, pos - ) + minetest.get_node_timer(pos):start(digtron.cycle_time) minetest.sound_play("squeal", {gain=1.0, pos=pos}) minetest.sound_play("buzzer", {gain=0.5, pos=pos}) return pos, "Digtron is obstructed.\n" .. status_text, 3 --Abort, don't dig and don't build. @@ -181,11 +177,7 @@ digtron.execute_cycle = function(pos, clicker) if not can_build then minetest.get_meta(pos):set_string("waiting", "true") - minetest.after(digtron.cycle_time, - function (pos) - minetest.get_meta(pos):set_string("waiting", nil) - end, pos - ) + minetest.get_node_timer(pos):start(digtron.cycle_time) local return_string = nil local return_code = 5 if test_build_return_code == 3 then diff --git a/util_layout.lua b/util_layout.lua index bae75fd..5b68919 100644 --- a/util_layout.lua +++ b/util_layout.lua @@ -108,4 +108,252 @@ digtron.get_all_digtron_neighbours = function(pos, player) end return layout -end \ No newline at end of file +end + +-- Rotation magic +-------------------------------------------------------------------------------------------------------- + +local facedir_rotate = { + ['x'] = { + [-1] = {[0]=4, 5, 6, 7, 22, 23, 20, 21, 0, 1, 2, 3, 13, 14, 15, 12, 19, 16, 17, 18, 10, 11, 8, 9}, -- 270 degrees + [1] = {[0]=8, 9, 10, 11, 0, 1, 2, 3, 22, 23, 20, 21, 15, 12, 13, 14, 17, 18, 19, 16, 6, 7, 4, 5}, -- 90 degrees + }, + ['y'] = { + [-1] = {[0]=3, 0, 1, 2, 19, 16, 17, 18, 15, 12, 13, 14, 7, 4, 5, 6, 11, 8, 9, 10, 21, 22, 23, 20}, -- 270 degrees + [1] = {[0]=1, 2, 3, 0, 13, 14, 15, 12, 17, 18, 19, 16, 9, 10, 11, 8, 5, 6, 7, 4, 23, 20, 21, 22}, -- 90 degrees + }, + ['z'] = { + [-1] = {[0]=16, 17, 18, 19, 5, 6, 7, 4, 11, 8, 9, 10, 0, 1, 2, 3, 20, 21, 22, 23, 12, 13, 14, 15}, -- 270 degrees + [1] = {[0]=12, 13, 14, 15, 7, 4, 5, 6, 9, 10, 11, 8, 20, 21, 22, 23, 0, 1, 2, 3, 16, 17, 18, 19}, -- 90 degrees + } +} + +local wallmounted_rotate = { + ['x'] = { + [-1] = {[0]=4, 5, 2, 3, 1, 0}, -- 270 degrees + [1] = {[0]=5, 4, 2, 3, 0, 1}, -- 90 degrees + }, + ['y'] = { + [-1] = {[0]=0, 1, 4, 5, 3, 2}, -- 270 degrees + [1] = {[0]=0, 1, 5, 4, 2, 3}, -- 90 degrees + }, + ['z'] = { + [-1] = {[0]=3, 2, 0, 1, 4, 5}, -- 270 degrees + [1] = {[0]=2, 3, 1, 0, 4, 5}, -- 90 degrees + } +} + + --90 degrees CW about x-axis: (x, y, z) -> (x, -z, y) + --90 degrees CCW about x-axis: (x, y, z) -> (x, z, -y) + --90 degrees CW about y-axis: (x, y, z) -> (-z, y, x) + --90 degrees CCW about y-axis: (x, y, z) -> (z, y, -x) + --90 degrees CW about z-axis: (x, y, z) -> (y, -x, z) + --90 degrees CCW about z-axis: (x, y, z) -> (-y, x, z) +local rotate_pos = function(axis, direction, pos) + if axis == "x" then + if direction < 0 then + return {x= pos.x, y= -pos.z, z= pos.y} + else + return {x= pos.x, y= pos.z, z= -pos.y} + end + elseif axis == "y" then + if direction < 0 then + return {x= -pos.z, y= pos.y, z= pos.x} + else + return {x= pos.z, y= pos.y, z= -pos.x} + end + else + if direction < 0 then + return {x= -pos.y, y= pos.x, z= pos.z} + else + return {x= pos.y, y= -pos.x, z= pos.z} + end + end +end + +local get_node_image = function(pos, node) + local node_image = {node=node, pos={x=pos.x, y=pos.y, z=pos.z}} + node_image.paramtype2 = minetest.registered_nodes[node.name].paramtype2 + local meta = minetest.get_meta(pos) + node_image.meta = meta:to_table() + + -- Record what kind of thing we've got in a builder node so its facing can be rotated properly + if minetest.get_item_group(node.name, "digtron") == 4 then + local build_item = node_image.meta.inventory.main[1] + if build_item ~= "" then + local build_item_def = minetest.registered_nodes[ItemStack(build_item):get_name()] + node_image.build_item_paramtype2 = build_item_def.paramtype2 + end + end + return node_image +end + +local rotate_node_image = function(node_image, origin, axis, direction, old_pos_pointset) + -- Facings + if node_image.paramtype2 == "wallmounted" then + node_image.node.param2 = wallmounted_rotate[axis][direction][node_image.node.param2] + elseif node_image.paramtype2 == "facedir" then + node_image.node.param2 = facedir_rotate[axis][direction][node_image.node.param2] + end + + if node_image.build_item_paramtype2 == "wallmounted" then + node_image.meta.fields.build_facing = wallmounted_rotate[axis][direction][node_image.meta.fields.build_facing] + elseif node_image.build_item_paramtype2 == "facedir" then + node_image.meta.fields.build_facing = facedir_rotate[axis][direction][node_image.meta.fields.build_facing] + end + + node_image.meta.fields.waiting = nil -- If we're rotating a controller that's in the "waiting" state, clear it. Otherwise it may stick like that. + + -- record the old location so we can destroy the old node if the rotation operation is possible + old_pos_pointset:set(node_image.pos.x, node_image.pos.y, node_image.pos.z, true) + + -- position in space relative to origin + local pos = vector.subtract(node_image.pos, origin) + pos = rotate_pos(axis, direction, pos) + -- Move back to original reference frame + node_image.pos = vector.add(pos, origin) + + return node_image +end + +digtron.rotate_layout_image = function(layout_image, facedir) + -- To convert this into the direction the "top" of the axel node is pointing in: + -- 0, 1, 2, 3 == (0,1,0) + -- 4, 5, 6, 7 == (0,0,1) + -- 8, 9, 10, 11 == (0,0,-1) + -- 12, 13, 14, 15 == (1,0,0) + -- 16, 17, 18, 19 == (-1,0,0) + -- 20, 21, 22, 23== (0,-1,0) + + local top = { + [0]={axis="y", dir=-1}, + {axis="z", dir=1}, + {axis="z", dir=-1}, + {axis="x", dir=1}, + {axis="x", dir=-1}, + {axis="y", dir=1}, + } + local params = top[math.floor(facedir/4)] + + layout_image.old_pos_pointset = Pointset:create() + + for k, node_image in pairs(layout_image.all) do + rotate_node_image(node_image, layout_image.controller, params.axis, params.dir, layout_image.old_pos_pointset) + end + return layout_image +end + +digtron.can_write_layout_image = function(layout_image, player) + for k, node_image in pairs(layout_image.all) do + if not layout_image.old_pos_pointset:get(node_image.pos.x, node_image.pos.y, node_image.pos.z) + and not minetest.registered_nodes[minetest.get_node(node_image.pos).name].buildable_to then + return false + elseif minetest.is_protected(node_image.pos, player:get_player_name()) and not minetest.check_player_privs(player, "protection_bypass") then + return false + end + end + return true +end + +digtron.write_layout_image = function(layout_image) + -- destroy the old digtron + local oldpos, _ = layout_image.old_pos_pointset:pop() + while oldpos ~= nil do + local old_def = minetest.registered_nodes[minetest.get_node(oldpos).name] + minetest.remove_node(oldpos) + if old_def.after_dig_node ~= nil then + old_def.after_dig_node(oldpos) + end + oldpos, _ = layout_image.old_pos_pointset:pop() + end + + -- create the new one + for k, node_image in pairs(layout_image.all) do + minetest.add_node(node_image.pos, node_image.node) + minetest.get_meta(node_image.pos):from_table(node_image.meta) + + local new_def = minetest.registered_nodes[node_image.node.name] + if new_def.after_place_node ~= nil then + new_def.after_place_node(node_image.pos) + end + end +end + +-- Similar to get_layout, but far more comprehensive. This produces a data structure plus a set of temporary inventories that will allow the digtron to be rotated and then recreated. +digtron.get_layout_image = function(pos, player) + + local image = {} + --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 + image.all = {} + image.extents = {} + image.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 + image.contains_protected_node = false -- used to indicate if at least one node in this digtron array is protected from the player. + + table.insert(image.all, get_node_image(pos, minetest.get_node(pos))) + + image.extents.max_x = pos.x + image.extents.min_x = pos.x + image.extents.max_y = pos.y + image.extents.min_y = pos.y + image.extents.max_z = pos.z + image.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 + image.contains_protected_node = 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. + return nil + end + + local group_number = minetest.get_item_group(node.name, "digtron") + if group_number > 0 then + --found one. Add it to the digtrons output + table.insert(image.all, get_node_image(testpos, node)) + + if minetest.is_protected(pos, player:get_player_name()) and not minetest.check_player_privs(player, "protection_bypass") then + image.contains_protected_node = true + end + + -- update extents + image.extents.max_x = math.max(image.extents.max_x, testpos.x) + image.extents.min_x = math.min(image.extents.min_x, testpos.x) + image.extents.max_y = math.max(image.extents.max_y, testpos.y) + image.extents.min_y = math.min(image.extents.min_y, testpos.y) + image.extents.max_z = math.max(image.extents.max_z, testpos.z) + image.extents.min_z = math.min(image.extents.min_z, testpos.z) + + --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) + end + + testpos, _ = to_test:pop() + end + + return image +end diff --git a/util_movement.lua b/util_movement.lua index 218ba48..f7c36dd 100644 --- a/util_movement.lua +++ b/util_movement.lua @@ -6,8 +6,10 @@ digtron.move_node = function(pos, newpos, player_name) minetest.log("action", string.format("%s moves %s from (%d, %d, %d) to (%d, %d, %d), displacing %s", player_name, node.name, pos.x, pos.y, pos.z, newpos.x, newpos.y, newpos.z, oldnode.name)) minetest.add_node(newpos, { name=node.name, param1=node.param1, param2=node.param2 }) -- copy the metadata - local oldmeta = minetest.get_meta(pos):to_table() - minetest.get_meta(newpos):from_table(oldmeta) + local oldmeta_table = minetest.get_meta(pos):to_table() + local meta = minetest.get_meta(newpos) + meta:from_table(oldmeta_table) + meta:set_string("waiting", nil) -- If a controller moves another controller that's in the waiting state, clear the waiting state otherwise it might get stuck like that (we've moved it away from the target of the pending 'clear the waiting state' delegate call). That means you can run a digtron as fast as you want by rapidly clicking between two different controllers, but shhh - don't tell the player that. -- Move the little floaty entity inside the builders if minetest.get_item_group(node.name, "digtron") == 4 then