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 0000000..ebb3dc7 Binary files /dev/null and b/sounds/whirr.ogg differ diff --git a/textures/digtron_axel_side.png b/textures/digtron_axel_side.png new file mode 100644 index 0000000..b69b221 Binary files /dev/null and b/textures/digtron_axel_side.png differ diff --git a/textures/digtron_axel_top.png b/textures/digtron_axel_top.png new file mode 100644 index 0000000..86f292f Binary files /dev/null and b/textures/digtron_axel_top.png differ 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