diff --git a/LICENSE.txt b/LICENSE.txt new file mode 100644 index 0000000..f85a59e --- /dev/null +++ b/LICENSE.txt @@ -0,0 +1,33 @@ +Modular Tunnel Boring Machine: +The Almighty Digtron +==================== + +All source code aside from the contents of util_item_place_node.lua and all art are by FaceDeer. + +util_item_place_node.lua is derived from default mod code under the LGPL 2.1, see header of that file for details. + +Sounds are under various licenses, see the license.txt file in the /sounds directory for details. + +License for Code +---------------- + +Copyright (C) 2016 FaceDeer + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation; either version 2.1 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License along +with this program; if not, write to the Free Software Foundation, Inc., +51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +License for Textures +--------------------------------------- + +CC-BY-SA 3.0 UNPORTED. Created by FaceDeer diff --git a/README.txt b/README.txt new file mode 100644 index 0000000..bef8e2b --- /dev/null +++ b/README.txt @@ -0,0 +1,156 @@ + Modular Tunnel Boring Machine, aka + The Almighty Digtron + ==================== + +This mod contains a set of nodes that can be used to construct highly customizable and modular tunnel-boring machines, bridge-builders, road-pavers, wall-o-matics, and other such construction/destruction contraptions. + +The basic nodes that can be assembled into a functioning digging machine are: + +* Digger heads, which excavate material in front of them when the machine is triggered +* Builder heads, which build a user-configured node in front of them +* Inventory modules, which hold material produced by the digger and provide material to the builders +* Control node, used to trigger the machine and move it in a particular direction. + +A digging machine's components must be connected to the control node via a path leading through the faces of the nodes - diagonal connections across edges and corners don't count. + +Important general concepts +-------------------------- + +Several general concepts are important when building more sophisticated diggers. + +* Facing - a number between 0-23 that determines which direction a node is facing and what orientation it has. Not all nodes make use of facing (basic blocks such as cobble or sand have no facing, for example) so it's not always necessary to set this when configuring a builder head. The facing of already-placed nodes can be altered through the use of the screwdriver tool. + +* Period - Builder and digger heads can be made periodic by changing the period value to something other than 1. This determines how frequently they trigger. A period of 1 triggers on every node, a period of 2 triggers once every second node, a period of 3 triggers once every third node, etc. These are useful when setting up a machine to place regularly-spaced features as it goes. For example, you could have a builder head that places a torch every 8 steps, or a digger node that punches a landing in the side of a vertical stairwell at every level. + +* Offset - The location at which a periodic module triggers is globally uniform. This is handy if you want to line up the nodes you're building (for example, placing pillars and a crosspiece every 4 nodes in a tunnel, or punching alcoves in a wall to place glass windows). If you wish to change how the pattern lines up, modify the "offset" setting. + +Note that offset and period are calculated from the location of the *controller* node, which is shared across the whole array of modules, so it's not necessary to line up all of your builder heads in the same row or column. + +* Shift-right-clicking - since most of the nodes of the digging machine have control screens associated with right-clicking, building additional nodes on top of them or rotating them with the screwdriver requires the shift key to be held down when right-clicking on them. + +Detailed module guide +===================== + +Control Module +-------------- + +Right-click on this module to make the digging machine go. The digging machine will go in the direction that the control module is oriented, so in theory you could adjust the digging machine's position by reoriented the control module (or building additional ones) and nudging it to the side. + +A control module can only trigger once per second. Gives you time to enjoy the scenery and smell the flowers (or their mulched remains, at any rate). + +If you're standing within the digging machine's volume, or in a node adjacent to it, you will be pulled along with the machine when it moves. + +Pusher Module +------------- + +Aka the "can you rebuild it six inches to the left" module. This is a much simplified control module that does not trigger the digger or builder heads when right-clicked, it only moves the digging machine. It's up to you to ensure there's space for it to move into. + +Digger Head +----------- + +Facing of a digger head is significant; it will excavate material from the node on the spinning grinder wheel face of the digger head. Generally speaking, you'll want these to face forward - though having them aimed to the sides can also be useful. + +Digger heads can have a period and offset defined if you want them to punch regularly-spaced holes. Note that diggers aimed forward should generally always have a period of 1, otherwise the digging machine may be unable to move. + +Sand Digger Head +---------------- + +This specialized digger head is designed to excavate only loose material such as sand or gravel. It has no period/offset settings; it will always attempt to dig sand when it's present in its target node. It leaves all other types of nodes alone. (in techinical terms, this digger digs nodes belonging to the "falling_node" group) + +The intended purpose of this digger is to be aimed at the ceiling or walls of a tunnel being dug, making spaces to allow shoring nodes to be inserted into unstable roofs but leaving the wall alone if it's composed of a more stable material. + +Builder Head +------------ + +A builder head is the most complex component of this system. It has period and offset properties, and also an inventory slot where you "program" it by placing an example of the node type that you want it to build. Only a single item is needed here, any additional items in this inventory stack will be shunted into the digger's general inventory (or ejected from the control node if there's no space for it). + +Builders also have a "facing" setting. If you haven't memorized the meaning of the 24 facing values yet, builder heads have a helpful "Read & Save" button to fill this value in for you. Simply build a temporary instance of the node in the output location in front of the builder, adjust it to the orientation you want using the screwdriver tool, and then when you click the "Read & Save" button the node's facing will be read and saved. + +Inventory Module +---------------- + +Inventory modules have the same capacity as a chest. They're used both for storing the products of the digger heads and as the source of materials used by the builder heads. A digging machine whose builder heads are laying down cobble can automatically self-replenish in this way, but note that an inventory module is still required as buffer space even if the digger heads produced everything needed by the builder heads in a given cycle. + +Inventory modules are not required for a digging-only machine. If there's not enough storage space to hold the materials produced by the digging heads, the excess material will be ejected out the back of the control node. They're handy for accumulating ores and other building materials, though. + +Digging machines can have multiple inventory modules added to expand their capacity. + +Structural Module +----------------- + +These nodes allow otherwise-disconnected sections of builder machines to be linked together. You can also use them to build a platform to stand on as you ride your mighty mechanical leviathan through the landscape. + +Digtron Lamp +------------ + +A light source that moves along with the digging machine. Convenient if you're digging a tunnel that you don't intend to outfit with torches or other permanent light fixtures. Not quite as bright as a torch since the protective lens tends to get grimy while burrowing through the earth. + +What Do These Noises Mean? +========================== + +When a digging machine is unable to complete a cycle it will make one of several noises to indicate what the problem is. + +Squealing traction wheels indicates a mobility problem. If the squealing is accompanied by a buzzer, the digging machine has encountered an obstruction it can't dig through. This could be a protected region (the digging machine has only the priviledges of the player triggering it), a chest containing items, or perhaps the digger was incorrectly designed and can't dug the correctly sized and shaped cavity for it to move forward into. There are many possibilities. + +Squealing traction wheels with no accompanying buzzer indicates that the digging machine has no solid adjacent nodes to push off of. Tunnel boring machines cannot fly or swim, not even through lava, and they don't dig fast enough to "catch sick air" when they emerge from a cliffside. If you wish to cross a chasm you'll need to ensure that there are builder heads placing a solid surface as you go. + +A ringing bell indicates that there are insufficient materials in inventory to supply all the builder heads for this cycle. + +A short high-pitched honk means that one or more of the builder heads don't have an item set. A common oversight, especially with large and elaborate digging machines, that might be hard to notice and annoying to fix if not noticed right away. + +Crafting recipes +================ + +All machine nodes are constructed from a "Digtron Core" craft item and other materials. + +Digtron cores are made with the following recipe: + + steel +steel mese fragment steel + steel + +Digger heads: + + diamond +diamond core diamond + diamond + +Sand/gravel digger heads: + + steel + steel core steel + steel + +Builder heads: + + mese fragment +mese fragment core mese fragment + mese fragment + +Controller heads: + + mese crystal + mese crystal core mese crystal + mese crystal + +Inventory modules: + + chest + core + +Structural modules: + +stick stick + core +stick stick + +Lantern module: + + torch + core + +Pusher controller: + + coal +coal core coal + coal diff --git a/depends.txt b/depends.txt new file mode 100644 index 0000000..4ad96d5 --- /dev/null +++ b/depends.txt @@ -0,0 +1 @@ +default diff --git a/init.lua b/init.lua new file mode 100644 index 0000000..92e0a88 --- /dev/null +++ b/init.lua @@ -0,0 +1,22 @@ +dofile( minetest.get_modpath( "digtron" ) .. "/util.lua" ) +dofile( minetest.get_modpath( "digtron" ) .. "/pointset.lua" ) + +dofile( minetest.get_modpath( "digtron" ) .. "/node_misc.lua" ) -- contains inventory and structure nodes +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" ) + +digtron.refractory = 1.0 -- How long a digtron waits between cycles. + +-- digtron group numbers: +-- 1 - generic digtron node, nothing special is done with these. They're just dragged along. +-- 2 - inventory-holding digtron, has a "main" inventory that the digtron can add to and take from. +-- 3 - digger head, has an "execute_dig" method in its definition +-- 4 - builder head, has a "test_build" and "execute_build" method in its definition + + + + + diff --git a/node_builders.lua b/node_builders.lua new file mode 100644 index 0000000..b8218b7 --- /dev/null +++ b/node_builders.lua @@ -0,0 +1,185 @@ +-- Note: builders go in group 4 and have both test_build and execute_build methods. + +-- Builds objects in the targeted node. This is a complicated beastie. +minetest.register_node("digtron:builder", { + description = "Builder Unit", + groups = {cracky = 3, stone = 1, digtron = 4}, + drop = "digtron:builder", + paramtype2= 'facedir', + tiles = { + "digtron_plate.png^[transformR90", + "digtron_plate.png^[transformR270", + "digtron_plate.png", + "digtron_plate.png^[transformR180", + "digtron_builder.png", + "digtron_plate.png", + }, + + drawtype = "nodebox", + paramtype = "light", + node_box = { + type = "fixed", + fixed = { + {-0.25, 0.3125, 0.3125, 0.25, 0.5, 0.5}, -- FrontFrame_top + {-0.25, -0.5, 0.3125, 0.25, -0.3125, 0.5}, -- FrontFrame_bottom + {0.3125, -0.25, 0.3125, 0.5, 0.25, 0.5}, -- FrontFrame_right + {-0.5, -0.25, 0.3125, -0.3125, 0.25, 0.5}, -- FrontFrame_left + {-0.5, 0.25, -0.5, -0.25, 0.5, 0.5}, -- edge_topright + {-0.5, -0.5, -0.5, -0.25, -0.25, 0.5}, -- edge_bottomright + {0.25, 0.25, -0.5, 0.5, 0.5, 0.5}, -- edge_topleft + {0.25, -0.5, -0.5, 0.5, -0.25, 0.5}, -- edge_bottomleft + {-0.25, 0.4375, -0.5, 0.25, 0.5, -0.4375}, -- backframe_top + {-0.25, -0.5, -0.5, 0.25, -0.4375, -0.4375}, -- backframe_bottom + {-0.5, -0.25, -0.5, -0.4375, 0.25, -0.4375}, -- backframe_left + {0.4375, -0.25, -0.5, 0.5, 0.25, -0.4375}, -- Backframe_right + {-0.0625, -0.3125, 0.3125, 0.0625, 0.3125, 0.375}, -- frontcross_vertical + {-0.3125, -0.0625, 0.3125, 0.3125, 0.0625, 0.375}, -- frontcross_horizontal + } + }, + + on_construct = function(pos) + local meta = minetest.env:get_meta(pos) + meta:set_string("formspec", + "size[8,5.2]" .. + default.gui_bg .. + default.gui_bg_img .. + default.gui_slots .. + "list[current_name;main;0.5,0;1,1;]" .. +-- "tooltip[main;Builder will build the type of node in this slot. Note that only one item needs to be placed here, to 'program' it. The builder will draw construction materials from the central inventory when building.]" .. + "label[0.5,0.8;Node to build]" .. + "field[2.5,0.8;1,0.1;period;Periodicity;${period}]" .. + "tooltip[period;Builder will build once every n steps. These steps are globally aligned, so all builders with the same period and offset will build on the same location.]" .. + "field[3.5,0.8;1,0.1;offset;Offset;${offset}]" .. + "tooltip[offset;Offsets the start of periodicity counting by this amount. For example, a builder with period 2 and offset 0 builds every even-numbered node and one with period 2 and offset 1 builds every odd-numbered node.]" .. + "button_exit[4.2,0.5;1,0.1;set;Save]" .. + "tooltip[set;Saves settings]" .. + "field[5.7,0.8;1,0.1;build_facing;Facing;${build_facing}]" .. + "tooltip[build_facing;Value from 0-23. Not all node types make use of this. Use the 'Read & Save' button to copy the facing of the node currently in the builder output location]" .. + "button_exit[6.4,0.5;1,0.1;read;Read &\nSave]" .. + "tooltip[read;Reads the facing of the node currently in the build location, then saves all settings]" .. + "list[current_player;main;0,1.3;8,1;]" .. + "list[current_player;main;0,1.3;8,1;]" .. + default.get_hotbar_bg(0,1.3) .. + "list[current_player;main;0,2.5;8,3;8]" .. + "listring[current_player;main]" + ) + meta:set_string("period", 1) + meta:set_string("offset", 0) + meta:set_string("build_facing", 0) + + local inv = meta:get_inventory() + inv:set_size("main", 1) + end, + + on_receive_fields = function(pos, formname, fields, sender) + local meta = minetest.get_meta(pos) + local period = tonumber(fields.period) + local offset = tonumber(fields.offset) + if period and period > 0 then + meta:set_string("period", math.floor(tonumber(fields.period))) + end + if offset then + meta:set_string("offset", math.floor(tonumber(fields.offset))) + end + + if fields.read then + local meta = minetest.get_meta(pos) + local facing = minetest.get_node(pos).param2 + local buildpos = digtron.find_new_pos(pos, facing) + meta:set_string("build_facing", minetest.get_node(buildpos).param2) + else + local build_facing = tonumber(fields.build_facing) + if build_facing and build_facing >= 0 and build_facing < 24 then + meta:set_string("build_facing", math.floor(build_facing)) + end + end + end, + + -- "builder at pos, imagine that you're in test_pos. If you're willing and able to build from there, take the item you need from inventory. + -- return the item you took and the inventory location you took it from so it can be put back after all the other builders have been tested. + -- If you couldn't get the item from inventory, return an error code so we can abort the cycle. + -- If you're not supposed to build at all, or the location is obstructed, return 0 to let us know you're okay and we shouldn't abort." + test_build = function(pos, test_pos, inventory_positions, protected_nodes, nodes_dug, controlling_coordinate, controller_pos) + local meta = minetest.get_meta(pos) + local facing = minetest.get_node(pos).param2 + local buildpos = digtron.find_new_pos(test_pos, facing) + + if (buildpos[controlling_coordinate] + meta:get_string("offset")) % meta:get_string("period") ~= 0 then + --It's not the builder's turn to build right now. + return 0 + end + + if not digtron.can_move_to(buildpos, protected_nodes, nodes_dug) then + --using "can_move_to" instead of "can_build_to" test case in case the builder is pointed "backward", and will thus + --be building into the space that it's currently in and will be vacating after moving, or in case the builder is aimed + --sideways and a fellow digtron node was ahead of it (will also be moving out of the way). + + --If the player has built his digtron stupid (eg has another digtron node in the place the builder wants to build) this + --assumption is wrong, but I can't hold the player's hand through *every* possible bad design decision. Worst case, + --the digtron will think its inventory can't handle the next build step and abort the build when it actually could have + --managed one more cycle. That's not a bad outcome for a digtron array that was built stupidly to begin with. + --The player should be thanking me for all the error-checking I *do* do, really. + --Ungrateful wretch. + return 0 + end + + local inv = minetest.get_inventory({type="node", pos=pos}) + local item_stack = inv:get_stack("main", 1) + local count = item_stack:get_count() + if count ~= 0 then + if count > 1 then + -- player has put more than one item in the "program" slot. Wasteful. Move all the rest to the main inventory so it can be used. + item_stack:set_count(count - 1) + digtron.place_in_inventory(item_stack, inventory_positions, controller_pos) + item_stack:set_count(1) + inv:set_stack("main", 1, item_stack) + end + local source_location = digtron.take_from_inventory(item_stack:get_name(), inventory_positions) + if source_location ~= nil then + return {item=item_stack, location=source_location} + end + return 2 -- error code for "needed an item but couldn't get it from inventory" + else + return 1 -- error code for "this builder's item slot is unset" + end + end, + + execute_build = function(pos, player, inventory_positions, protected_nodes, nodes_dug, controlling_coordinate, controller_pos) + local meta = minetest.get_meta(pos) + local build_facing = meta:get_string("build_facing") + local facing = minetest.get_node(pos).param2 + local buildpos = digtron.find_new_pos(pos, facing) + + if (buildpos[controlling_coordinate] + meta:get_string("offset")) % meta:get_string("period") ~= 0 then + return nil + end + + if digtron.can_build_to(buildpos, protected_nodes, nodes_dug) then + local inv = minetest.get_inventory({type="node", pos=pos}) + local item_stack = inv:get_stack("main", 1) + local count = item_stack:get_count() + if not item_stack:is_empty() then + local sourcepos = digtron.take_from_inventory(item_stack:get_name(), inventory_positions) + if sourcepos == nil then + -- item not in inventory! Need to sound the angry buzzer to let the player know, so return false. + return false + end + local returned_stack, success = digtron.item_place_node(item_stack, player, buildpos, tonumber(build_facing)) + if success == true then + --flag this node as *not* to be dug. + nodes_dug:set(buildpos.x, buildpos.y, buildpos.z, false) + else + --failed to build for some unknown reason. Put the item back in inventory. + digtron.place_in_specific_inventory(item_stack, sourcepos, inventory_positions, controller_pos) + end + end + end + return true -- no errors were encountered that we should notify the player about + end, + + can_dig = function(pos,player) + local meta = minetest.get_meta(pos) + local inv = meta:get_inventory() + return inv:is_empty("main") + end, +}) \ No newline at end of file diff --git a/node_controllers.lua b/node_controllers.lua new file mode 100644 index 0000000..f8b0b6b --- /dev/null +++ b/node_controllers.lua @@ -0,0 +1,317 @@ +local controller_nodebox ={ + {-0.3125, -0.3125, -0.3125, 0.3125, 0.3125, 0.3125}, -- Core + {-0.1875, 0.3125, -0.1875, 0.1875, 0.5, 0.1875}, -- +y_connector + {-0.1875, -0.5, -0.1875, 0.1875, -0.3125, 0.1875}, -- -y_Connector + {0.3125, -0.1875, -0.1875, 0.5, 0.1875, 0.1875}, -- +x_connector + {-0.5, -0.1875, -0.1875, -0.3125, 0.1875, 0.1875}, -- -x_connector + {-0.1875, -0.1875, 0.3125, 0.1875, 0.1875, 0.5}, -- +z_connector + {-0.5, 0.125, -0.5, -0.125, 0.5, -0.3125}, -- back_connector_3 + {0.125, 0.125, -0.5, 0.5, 0.5, -0.3125}, -- back_connector_1 + {0.125, -0.5, -0.5, 0.5, -0.125, -0.3125}, -- back_connector_2 + {-0.5, -0.5, -0.5, -0.125, -0.125, -0.3125}, -- back_connector_4 +} + +-- Master controller. Most complicated part of the whole system. Determines which direction a digtron moves and triggers all of its component parts. +minetest.register_node("digtron:controller", { + description = "Digtron Control Unit", + groups = {cracky = 3, stone = 1, digtron = 1}, + drop = 'digtron:controller', + paramtype2= 'facedir', + -- Aims in the +Z direction by default + tiles = { + "digtron_plate.png^[transformR90", + "digtron_plate.png^[transformR270", + "digtron_plate.png", + "digtron_plate.png^[transformR180", + "digtron_plate.png", + "digtron_control.png", + }, + + drawtype = "nodebox", + paramtype = "light", + node_box = { + type = "fixed", + fixed = controller_nodebox, + }, + + 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 did a cycle. + return + end + + local layout = digtron.get_all_digtron_neighbours(pos, clicker) + if layout.all == nil then + -- get_all_digtron_neighbours returns nil if the digtron array touches unloaded nodes, too dangerous to do anything in that situation. Abort. + minetest.sound_play("buzzer", {gain=0.5, pos=pos}) + return + end + + if layout.traction == false then + -- digtrons can't fly + minetest.sound_play("squeal", {gain=1.0, pos=pos}) + return + end + + local facing = minetest.get_node(pos).param2 + local controlling_coordinate = digtron.get_controlling_coordinate(pos, facing) + + ---------------------------------------------------------------------------------------------------------------------- + + local nodes_dug = Pointset.create() + local items_dropped = {} + + -- execute the execute_dig method on all digtron components that have one + -- This builds a set of nodes that will be dug and returns a list of products that will be generated + -- but doesn't actually dig the nodes yet. That comes later. + -- If we dug them now, sand would fall and some digtron nodes would die. + for k, location in pairs(layout.diggers) do + local target = minetest.get_node(location) + local targetdef = minetest.registered_nodes[target.name] + if targetdef.execute_dig ~= nil then + local dropped = targetdef.execute_dig(location, layout.protected, nodes_dug, controlling_coordinate) + if dropped ~= nil then + for _, itemname in pairs(dropped) do + table.insert(items_dropped, itemname) + end + end + else + minetest.log(string.format("%s has digger group but is missing execute_dig method! This is an error in mod programming, file a bug.", targetdef.name)) + end + end + + ---------------------------------------------------------------------------------------------------------------------- + + -- test if any digtrons are obstructed by non-digtron nodes that haven't been marked + -- as having been dug. + local can_move = true + for _, location in pairs(layout.all) do + local newpos = digtron.find_new_pos(location, facing) + if not digtron.can_move_to(newpos, layout.protected, nodes_dug) then + can_move = false + end + end + + if not can_move then + -- mark this node as waiting, will clear this flag in digtron.refractory seconds + minetest.get_meta(pos):set_string("waiting", "true") + minetest.after(digtron.refractory, + function (pos) + minetest.get_meta(pos):set_string("waiting", nil) + end, pos + ) + minetest.sound_play("squeal", {gain=1.0, pos=pos}) + minetest.sound_play("buzzer", {gain=0.5, pos=pos}) + return --Abort, don't dig and don't build. + end + + ---------------------------------------------------------------------------------------------------------------------- + + -- ask each builder node if it can get what it needs from inventory to build this cycle. + -- This is a complicated test because each builder needs to actually *take* the item it'll + -- need from inventory, and then we put it all back afterward. + local can_build = true + local test_build_return = nil + local test_items = {} + for k, location in pairs(layout.builders) do + local target = minetest.get_node(location) + local targetdef = minetest.registered_nodes[target.name] + local test_location = digtron.find_new_pos(location, facing) + if targetdef.test_build ~= nil then + test_build_return = targetdef.test_build(location, test_location, layout.inventories, layout.protected, nodes_dug, controlling_coordinate, layout.controller) + if test_build_return == 1 or test_build_return == 2 then + can_build = false + break + end + if test_build_return ~= 0 then + table.insert(test_items, test_build_return) + end + else + minetest.log(string.format("%s has builder group but is missing test_build method! This is an error in mod programming, file a bug.", targetdef.name)) + end + end + for k, item_return in pairs(test_items) do + --Put everything back where it came from + digtron.place_in_specific_inventory(item_return.item, item_return.location, layout.inventories, layout.controller) + end + + if not can_build then + minetest.get_meta(pos):set_string("waiting", "true") + minetest.after(digtron.refractory, + function (pos) + minetest.get_meta(pos):set_string("waiting", nil) + end, pos + ) + if test_build_return == 1 then + minetest.sound_play("honk", {gain=0.5, pos=pos}) -- A builder is not configured + elseif test_build_return == 2 then + minetest.sound_play("dingding", {gain=1.0, pos=pos}) -- Insufficient inventory + end + return --Abort, don't dig and don't build. + end + + ---------------------------------------------------------------------------------------------------------------------- + + -- All tests passed, ready to go for real! + minetest.sound_play("construction", {gain=1.0, pos=pos}) + + -- store or drop the products of the digger heads + for _, itemname in pairs(items_dropped) do + digtron.place_in_inventory(itemname, layout.inventories, pos) + end + + -- if the player is standing within the array or next to it, move him too. + local player_pos = clicker:getpos() + local move_player = false + if player_pos.x >= layout.extents.min_x - 1 and player_pos.x <= layout.extents.max_x + 1 and + player_pos.y >= layout.extents.min_y - 1 and player_pos.y <= layout.extents.max_y + 1 and + player_pos.z >= layout.extents.min_z - 1 and player_pos.z <= layout.extents.max_z + 1 then + move_player = true + end + + --move the array + digtron.move_digtron(facing, layout.all, layout.extents, nodes_dug) + local oldpos = {x=pos.x, y=pos.y, z=pos.z} + pos = digtron.find_new_pos(pos, facing) + if move_player then + clicker:moveto(digtron.find_new_pos(player_pos, facing), true) + end + + -- 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.refractory, + function (pos) + minetest.get_meta(pos):set_string("waiting", nil) + end, pos + ) + + -- execute_build on all digtron components that have one + for k, location in pairs(layout.builders) do + local target = minetest.get_node(location) + local targetdef = minetest.registered_nodes[target.name] + if targetdef.execute_build ~= nil then + --using the old location of the controller as fallback so that any leftovers land with the rest of the digger output. Not that there should be any. + can_build = targetdef.execute_build(location, clicker, layout.inventories, layout.protected, nodes_dug, controlling_coordinate, oldpos) + else + minetest.log(string.format("%s has builder group but is missing execute_build method! This is an error in mod programming, file a bug.", targetdef.name)) + end + end + if can_build == false then + -- We weren't able to detect this build failure ahead of time, so make a big noise now. This is strange, shouldn't happen often. + minetest.sound_play("dingding", {gain=1.0, pos=pos}) + minetest.sound_play("buzzer", {gain=0.5, pos=pos}) + end + + -- finally, dig out any nodes remaining to be dug. Some of these will have had their flag revoked because + -- a builder put something there or because they're another digtron node. + local node_to_dig, whether_to_dig = nodes_dug:pop() + while node_to_dig ~= nil do + if whether_to_dig == true then + minetest.remove_node(node_to_dig) + end + node_to_dig, whether_to_dig = nodes_dug:pop() + end + end, +}) + +-- A much simplified control unit that only moves the digtron, and doesn't trigger the diggers or builders. +-- Handy for shoving a digtron to the side if it's been built a bit off. +minetest.register_node("digtron:pusher", { + description = "Digtron Pusher Unit", + groups = {cracky = 3, stone = 1, digtron = 1}, + drop = 'digtron:pusher', + paramtype2= 'facedir', + -- Aims in the +Z direction by default + tiles = { + "digtron_plate.png^[transformR90^[colorize:#00880030", + "digtron_plate.png^[transformR270^[colorize:#00880030", + "digtron_plate.png^[colorize:#00880030", + "digtron_plate.png^[transformR180^[colorize:#00880030", + "digtron_plate.png^[colorize:#00880030", + "digtron_control.png^[colorize:#00880030", + }, + + drawtype = "nodebox", + paramtype = "light", + node_box = { + type = "fixed", + fixed = controller_nodebox, + }, + + 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 did a cycle. + return + end + + local layout = digtron.get_all_digtron_neighbours(pos, clicker) + if layout.all == nil then + -- get_all_digtron_neighbours returns nil if the digtron array touches unloaded nodes, too dangerous to do anything in that situation. Abort. + minetest.sound_play("buzzer", {gain=0.5, pos=pos}) + return + end + + if layout.traction == false then + -- digtrons can't fly + minetest.sound_play("squeal", {gain=1.0, pos=pos}) + return + end + + local facing = minetest.get_node(pos).param2 + local controlling_coordinate = digtron.get_controlling_coordinate(pos, facing) + + local nodes_dug = Pointset.create() -- empty set, we're not digging anything + + -- test if any digtrons are obstructed by non-digtron nodes that haven't been marked + -- as having been dug. + local can_move = true + for _, location in pairs(layout.all) do + local newpos = digtron.find_new_pos(location, facing) + if not digtron.can_move_to(newpos, layout.protected, nodes_dug) then + can_move = false + end + end + + if not can_move then + -- mark this node as waiting, will clear this flag in digtron.refractory seconds + minetest.get_meta(pos):set_string("waiting", "true") + minetest.after(digtron.refractory, + function (pos) + minetest.get_meta(pos):set_string("waiting", nil) + end, pos + ) + minetest.sound_play("squeal", {gain=1.0, pos=pos}) + minetest.sound_play("buzzer", {gain=0.5, pos=pos}) + return --Abort + end + + minetest.sound_play("truck", {gain=1.0, pos=pos}) + + -- if the player is standing within the array or next to it, move him too. + local player_pos = clicker:getpos() + local move_player = false + if player_pos.x >= layout.extents.min_x - 1 and player_pos.x <= layout.extents.max_x + 1 and + player_pos.y >= layout.extents.min_y - 1 and player_pos.y <= layout.extents.max_y + 1 and + player_pos.z >= layout.extents.min_z - 1 and player_pos.z <= layout.extents.max_z + 1 then + move_player = true + end + + --move the array + digtron.move_digtron(facing, layout.all, layout.extents, nodes_dug) + local oldpos = {x=pos.x, y=pos.y, z=pos.z} + pos = digtron.find_new_pos(pos, facing) + if move_player then + clicker:moveto(digtron.find_new_pos(player_pos, facing), true) + end + + -- 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.refractory, + function (pos) + minetest.get_meta(pos):set_string("waiting", nil) + end, pos + ) + end, +}) \ No newline at end of file diff --git a/node_diggers.lua b/node_diggers.lua new file mode 100644 index 0000000..f2ffdff --- /dev/null +++ b/node_diggers.lua @@ -0,0 +1,147 @@ +-- Note: diggers go in group 3 and have an execute_dig method. + +local digger_nodebox = { + {-0.5, -0.5, 0, 0.5, 0.5, 0.4375}, -- Block + {-0.4375, -0.3125, 0.4375, 0.4375, 0.3125, 0.5}, -- Cutter1 + {-0.3125, -0.4375, 0.4375, 0.3125, 0.4375, 0.5}, -- Cutter2 + {-0.5, -0.125, -0.125, 0.5, 0.125, 0}, -- BackFrame1 + {-0.125, -0.5, -0.125, 0.125, 0.5, 0}, -- BackFrame2 + {-0.25, -0.25, -0.5, 0.25, 0.25, 0}, -- Drive +} + +-- Digs out nodes that are "in front" of the digger head. +minetest.register_node("digtron:digger", { + description = "Digger Head", + groups = {cracky = 3, stone = 1, digtron = 3}, + drop = 'digtron:digger', + paramtype = "light", + paramtype2= 'facedir', + + drawtype="nodebox", + node_box = { + type = "fixed", + fixed = digger_nodebox, + }, + + -- Aims in the +Z direction by default + tiles = { + "digtron_plate.png^[transformR90", + "digtron_plate.png^[transformR270", + "digtron_plate.png", + "digtron_plate.png^[transformR180", + { + name = "digtron_digger.png", + animation = { + type = "vertical_frames", + aspect_w = 16, + aspect_h = 16, + length = 1.0, + }, + }, + "digtron_motor.png", + }, + + on_construct = function(pos) + local meta = minetest.env:get_meta(pos) + meta:set_string("formspec", + "size[3.5,1]" .. + default.gui_bg .. + default.gui_bg_img .. + default.gui_slots .. + "field[0.5,0.8;1,0.1;period;Periodicity;${period}]" .. + "tooltip[period;Digger will dig once every n steps. These steps are globally aligned, all diggers with the same period and offset will dig on the same location.]" .. + "field[1.5,0.8;1,0.1;offset;Offset;${offset}]" .. + "tooltip[offset;Offsets the start of periodicity counting by this amount. For example, a digger with period 2 and offset 0 digs every even-numbered node and one with period 2 and offset 1 digs every odd-numbered node.]" .. + "button_exit[2.2,0.5;1,0.1;set;Save]" .. + "tooltip[set;Saves settings]" + ) + meta:set_string("period", 1) + meta:set_string("offset", 0) + + local inv = meta:get_inventory() + inv:set_size("main", 1) + end, + + on_receive_fields = function(pos, formname, fields, sender) + local meta = minetest.get_meta(pos) + local period = tonumber(fields.period) + local offset = tonumber(fields.offset) + if period and period > 0 then + meta:set_string("period", math.floor(tonumber(fields.period))) + end + if offset then + meta:set_string("offset", math.floor(tonumber(fields.offset))) + end + end, + + execute_dig = function(pos, protected_nodes, nodes_dug, controlling_coordinate) + local facing = minetest.get_node(pos).param2 + local digpos = digtron.find_new_pos(pos, facing) + + if protected_nodes:get(digpos.x, digpos.y, digpos.z) then + return nil + end + + local meta = minetest.get_meta(pos) + if (digpos[controlling_coordinate] + meta:get_string("offset")) % meta:get_string("period") ~= 0 then + return nil + end + + return digtron.mark_diggable(digpos, nodes_dug) + end, +}) + +-- A special-purpose digger to deal with stuff like sand and gravel in the ceiling. It always digs (no periodicity or offset), but it only digs falling_block nodes +minetest.register_node("digtron:sand_digger", { + description = "Sand Digger Head", + groups = {cracky = 3, stone = 1, digtron = 3}, + drop = 'digtron:sand_digger', + paramtype = "light", + paramtype2= 'facedir', + + drawtype="nodebox", + node_box = { + type = "fixed", + fixed = digger_nodebox, + }, + + -- Aims in the +Z direction by default + tiles = { + "digtron_plate.png^[transformR90^[colorize:#88880030", + "digtron_plate.png^[transformR270^[colorize:#88880030", + "digtron_plate.png^[colorize:#88880030", + "digtron_plate.png^[transformR180^[colorize:#88880030", + { + name = "digtron_digger.png^[colorize:#88880030", + animation = { + type = "vertical_frames", + aspect_w = 16, + aspect_h = 16, + length = 1.0, + }, + }, + "digtron_motor.png^[colorize:#88880030", + }, + + can_dig = function(pos,player) + local meta = minetest.get_meta(pos) + local inv = meta:get_inventory() + return inv:is_empty("main") and inv:is_empty("inv") + end, + + execute_dig = function(pos, protected_nodes, nodes_dug, controlling_coordinate) + local facing = minetest.get_node(pos).param2 + local digpos = digtron.find_new_pos(pos, facing) + + if protected_nodes:get(digpos.x, digpos.y, digpos.z) then + return nil + end + + local target_node = minetest.get_node(digpos) + if minetest.get_item_group(target_node.name, "falling_node") ~= 0 then + return digtron.mark_diggable(digpos, nodes_dug) + end + + return nil + end, +}) \ No newline at end of file diff --git a/node_misc.lua b/node_misc.lua new file mode 100644 index 0000000..3168a46 --- /dev/null +++ b/node_misc.lua @@ -0,0 +1,79 @@ +-- A do-nothing "structural" node, to ensure all digtron nodes that are supposed to be connected to each other can be connected to each other. +minetest.register_node("digtron:structure", { + description = "Digger Structure", + groups = {cracky = 3, stone = 1, digtron = 1}, + drop = 'digtron:structure', + tiles = {"digtron_plate.png"}, + drawtype = "nodebox", + paramtype = "light", + node_box = { + type = "fixed", + fixed = { + {0.3125, 0.3125, -0.5, 0.5, 0.5, 0.5}, + {0.3125, -0.5, -0.5, 0.5, -0.3125, 0.5}, + {-0.5, 0.3125, -0.5, -0.3125, 0.5, 0.5}, + {-0.5, -0.5, -0.5, -0.3125, -0.3125, 0.5}, + {-0.3125, 0.3125, 0.3125, 0.3125, 0.5, 0.5}, + {-0.3125, -0.5, 0.3125, 0.3125, -0.3125, 0.5}, + {-0.5, -0.3125, 0.3125, -0.3125, 0.3125, 0.5}, + {0.3125, -0.3125, 0.3125, 0.5, 0.3125, 0.5}, + {-0.5, -0.3125, -0.5, -0.3125, 0.3125, -0.3125}, + {0.3125, -0.3125, -0.5, 0.5, 0.3125, -0.3125}, + {-0.3125, 0.3125, -0.5, 0.3125, 0.5, -0.3125}, + {-0.3125, -0.5, -0.5, 0.3125, -0.3125, -0.3125}, + } + }, +}) + +-- A modest light source that will move with the digtron, handy for working in a tunnel you aren't bothering to install permanent lights in. +minetest.register_node("digtron:light", { + description = "Digger Light", + groups = {cracky = 3, stone = 1, digtron = 1}, + drop = 'digtron:light', + tiles = {"digtron_light.png"}, + drawtype = "nodebox", + paramtype = "light", + light_source = 10, + paramtype2 = "wallmounted", + node_box = { + type = "wallmounted", + wall_top = {-0.25, 0.3125, -0.25, 0.25, 0.5, 0.25}, + wall_bottom = {-0.25, -0.3125, -0.25, 0.25, -0.5, 0.25}, + wall_side = {-0.5, -0.25, -0.25, -0.1875, 0.25, 0.25}, + }, +}) + +-- Storage buffer. Builder nodes draw from this inventory and digger nodes deposit into it. +-- Note that inventories are digtron group 2. +minetest.register_node("digtron:inventory", +{ + description = "Digtron Inventory Hopper", + groups = {cracky = 3, stone = 1, digtron = 2}, + drop = 'digtron:inventory', + paramtype2= 'facedir', + tiles = {"digtron_inventory.png"}, + + on_construct = function(pos) + local meta = minetest.get_meta(pos) + meta:set_string("formspec", + "size[8,9]" .. + default.gui_bg .. + default.gui_bg_img .. + default.gui_slots .. + "list[current_name;main;0,0.3;8,4;]" .. + "list[current_player;main;0,4.85;8,1;]" .. + "list[current_player;main;0,6.08;8,3;8]" .. + "listring[current_name;main]" .. + "listring[current_player;main]" .. + default.get_hotbar_bg(0,4.85) + ) + local inv = meta:get_inventory() + inv:set_size("main", 8*4) + end, + + can_dig = function(pos,player) + local meta = minetest.get_meta(pos) + local inv = meta:get_inventory() + return inv:is_empty("main") + end, +}) \ No newline at end of file diff --git a/pointset.lua b/pointset.lua new file mode 100644 index 0000000..5bfd22a --- /dev/null +++ b/pointset.lua @@ -0,0 +1,73 @@ +-- A simple special-purpose class, this is used for building up sets of three-dimensional points +-- I only added features to it as I needed them so may not be highly useful outside of this mod's context. + +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: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 \ No newline at end of file diff --git a/recipes.lua b/recipes.lua new file mode 100644 index 0000000..e25f065 --- /dev/null +++ b/recipes.lua @@ -0,0 +1,115 @@ +minetest.register_craftitem("digtron:digtron_core", { + description = "Digtron Core", + inventory_image = "digtron_core.png" +}) + +minetest.register_craft({ + output = "digtron:digtron_core", + recipe = { + {"","default:steel_ingot",""}, + {"default:steel_ingot","default:mese_crystal_fragment","default:steel_ingot"}, + {"","default:steel_ingot",""} + } +}) + +minetest.register_craft({ + output = "digtron:controller", + recipe = { + {"","default:mese_crystal",""}, + {"default:mese_crystal","digtron:digtron_core","default:mese_crystal"}, + {"","default:mese_crystal",""} + } +}) + +minetest.register_craft({ + output = "digtron:builder", + recipe = { + {"","default:mese_crystal_fragment",""}, + {"default:mese_crystal_fragment","digtron:digtron_core","default:mese_crystal_fragment"}, + {"","default:mese_crystal_fragment",""} + } +}) + +minetest.register_craft({ + output = "digtron:light", + recipe = { + {"","default:torch",""}, + {"","digtron:digtron_core",""}, + {"","",""} + } +}) + +minetest.register_craft({ + output = "digtron:digger", + recipe = { + {"","default:diamond",""}, + {"default:diamond","digtron:digtron_core","default:diamond"}, + {"","default:diamond",""} + } +}) + +minetest.register_craft({ + output = "digtron:sand_digger", + recipe = { + {"","default:steel_ingot",""}, + {"default:steel_ingot","digtron:digtron_core","default:steel_ingot"}, + {"","default:steel_ingot",""} + } +}) + +minetest.register_craft({ + output = "digtron:inventory", + recipe = { + {"","default:chest",""}, + {"","digtron:digtron_core",""}, + {"","",""} + } +}) + +minetest.register_craft({ + output = "digtron:structure", + recipe = { + {"default:stick","","default:stick"}, + {"","digtron:digtron_core",""}, + {"default:stick","","default:stick"} + } +}) + +minetest.register_craft({ + output = "digtron:pusher", + recipe = { + {"","default:coal_lump",""}, + {"default:coal_lump","digtron:digtron_core","default:coal_lump"}, + {"","default:coal_lump",""} + } +}) + +-- And some recycling reactions to get digtron cores out of the "cheap" parts: + +minetest.register_craft({ + output = "digtron:digtron_core", + recipe = { + {"digtron:structure"}, + } +}) + +minetest.register_craft({ + output = "digtron:digtron_core", + recipe = { + {"digtron:inventory"}, + } +}) + +minetest.register_craft({ + output = "digtron:digtron_core", + recipe = { + {"digtron:light"}, + } +}) + +minetest.register_craft({ + output = "digtron:digtron_core", + recipe = { + {"digtron:pusher"}, + } +}) \ No newline at end of file diff --git a/sounds/buzzer.ogg b/sounds/buzzer.ogg new file mode 100644 index 0000000..df9bdd3 Binary files /dev/null and b/sounds/buzzer.ogg differ diff --git a/sounds/construction.ogg b/sounds/construction.ogg new file mode 100644 index 0000000..cb19cb7 Binary files /dev/null and b/sounds/construction.ogg differ diff --git a/sounds/dingding.ogg b/sounds/dingding.ogg new file mode 100644 index 0000000..108a685 Binary files /dev/null and b/sounds/dingding.ogg differ diff --git a/sounds/honk.ogg b/sounds/honk.ogg new file mode 100644 index 0000000..dee4f94 Binary files /dev/null and b/sounds/honk.ogg differ diff --git a/sounds/license.txt b/sounds/license.txt new file mode 100644 index 0000000..77f5a88 --- /dev/null +++ b/sounds/license.txt @@ -0,0 +1,99 @@ +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 +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 + + +Creative Commons Attribution 3.0 license: + +THE WORK (AS DEFINED BELOW) IS PROVIDED UNDER THE TERMS OF THIS CREATIVE COMMONS PUBLIC LICENSE ("CCPL" OR "LICENSE"). THE WORK IS PROTECTED BY COPYRIGHT AND/OR OTHER APPLICABLE LAW. ANY USE OF THE WORK OTHER THAN AS AUTHORIZED UNDER THIS LICENSE OR COPYRIGHT LAW IS PROHIBITED. + +BY EXERCISING ANY RIGHTS TO THE WORK PROVIDED HERE, YOU ACCEPT AND AGREE TO BE BOUND BY THE TERMS OF THIS LICENSE. TO THE EXTENT THIS LICENSE MAY BE CONSIDERED TO BE A CONTRACT, THE LICENSOR GRANTS YOU THE RIGHTS CONTAINED HERE IN CONSIDERATION OF YOUR ACCEPTANCE OF SUCH TERMS AND CONDITIONS. + +1. Definitions + + "Adaptation" means a work based upon the Work, or upon the Work and other pre-existing works, such as a translation, adaptation, derivative work, arrangement of music or other alterations of a literary or artistic work, or phonogram or performance and includes cinematographic adaptations or any other form in which the Work may be recast, transformed, or adapted including in any form recognizably derived from the original, except that a work that constitutes a Collection will not be considered an Adaptation for the purpose of this License. For the avoidance of doubt, where the Work is a musical work, performance or phonogram, the synchronization of the Work in timed-relation with a moving image ("synching") will be considered an Adaptation for the purpose of this License. + "Collection" means a collection of literary or artistic works, such as encyclopedias and anthologies, or performances, phonograms or broadcasts, or other works or subject matter other than works listed in Section 1(f) below, which, by reason of the selection and arrangement of their contents, constitute intellectual creations, in which the Work is included in its entirety in unmodified form along with one or more other contributions, each constituting separate and independent works in themselves, which together are assembled into a collective whole. A work that constitutes a Collection will not be considered an Adaptation (as defined above) for the purposes of this License. + "Distribute" means to make available to the public the original and copies of the Work or Adaptation, as appropriate, through sale or other transfer of ownership. + "Licensor" means the individual, individuals, entity or entities that offer(s) the Work under the terms of this License. + "Original Author" means, in the case of a literary or artistic work, the individual, individuals, entity or entities who created the Work or if no individual or entity can be identified, the publisher; and in addition (i) in the case of a performance the actors, singers, musicians, dancers, and other persons who act, sing, deliver, declaim, play in, interpret or otherwise perform literary or artistic works or expressions of folklore; (ii) in the case of a phonogram the producer being the person or legal entity who first fixes the sounds of a performance or other sounds; and, (iii) in the case of broadcasts, the organization that transmits the broadcast. + "Work" means the literary and/or artistic work offered under the terms of this License including without limitation any production in the literary, scientific and artistic domain, whatever may be the mode or form of its expression including digital form, such as a book, pamphlet and other writing; a lecture, address, sermon or other work of the same nature; a dramatic or dramatico-musical work; a choreographic work or entertainment in dumb show; a musical composition with or without words; a cinematographic work to which are assimilated works expressed by a process analogous to cinematography; a work of drawing, painting, architecture, sculpture, engraving or lithography; a photographic work to which are assimilated works expressed by a process analogous to photography; a work of applied art; an illustration, map, plan, sketch or three-dimensional work relative to geography, topography, architecture or science; a performance; a broadcast; a phonogram; a compilation of data to the extent it is protected as a copyrightable work; or a work performed by a variety or circus performer to the extent it is not otherwise considered a literary or artistic work. + "You" means an individual or entity exercising rights under this License who has not previously violated the terms of this License with respect to the Work, or who has received express permission from the Licensor to exercise rights under this License despite a previous violation. + "Publicly Perform" means to perform public recitations of the Work and to communicate to the public those public recitations, by any means or process, including by wire or wireless means or public digital performances; to make available to the public Works in such a way that members of the public may access these Works from a place and at a place individually chosen by them; to perform the Work to the public by any means or process and the communication to the public of the performances of the Work, including by public digital performance; to broadcast and rebroadcast the Work by any means including signs, sounds or images. + "Reproduce" means to make copies of the Work by any means including without limitation by sound or visual recordings and the right of fixation and reproducing fixations of the Work, including storage of a protected performance or phonogram in digital form or other electronic medium. + +2. Fair Dealing Rights. Nothing in this License is intended to reduce, limit, or restrict any uses free from copyright or rights arising from limitations or exceptions that are provided for in connection with the copyright protection under copyright law or other applicable laws. + +3. License Grant. Subject to the terms and conditions of this License, Licensor hereby grants You a worldwide, royalty-free, non-exclusive, perpetual (for the duration of the applicable copyright) license to exercise the rights in the Work as stated below: + + to Reproduce the Work, to incorporate the Work into one or more Collections, and to Reproduce the Work as incorporated in the Collections; + to create and Reproduce Adaptations provided that any such Adaptation, including any translation in any medium, takes reasonable steps to clearly label, demarcate or otherwise identify that changes were made to the original Work. For example, a translation could be marked "The original work was translated from English to Spanish," or a modification could indicate "The original work has been modified."; + to Distribute and Publicly Perform the Work including as incorporated in Collections; and, + to Distribute and Publicly Perform Adaptations. + + For the avoidance of doubt: + Non-waivable Compulsory License Schemes. In those jurisdictions in which the right to collect royalties through any statutory or compulsory licensing scheme cannot be waived, the Licensor reserves the exclusive right to collect such royalties for any exercise by You of the rights granted under this License; + Waivable Compulsory License Schemes. In those jurisdictions in which the right to collect royalties through any statutory or compulsory licensing scheme can be waived, the Licensor waives the exclusive right to collect such royalties for any exercise by You of the rights granted under this License; and, + Voluntary License Schemes. The Licensor waives the right to collect royalties, whether individually or, in the event that the Licensor is a member of a collecting society that administers voluntary licensing schemes, via that society, from any exercise by You of the rights granted under this License. + +The above rights may be exercised in all media and formats whether now known or hereafter devised. The above rights include the right to make such modifications as are technically necessary to exercise the rights in other media and formats. Subject to Section 8(f), all rights not expressly granted by Licensor are hereby reserved. + +4. Restrictions. The license granted in Section 3 above is expressly made subject to and limited by the following restrictions: + + You may Distribute or Publicly Perform the Work only under the terms of this License. You must include a copy of, or the Uniform Resource Identifier (URI) for, this License with every copy of the Work You Distribute or Publicly Perform. You may not offer or impose any terms on the Work that restrict the terms of this License or the ability of the recipient of the Work to exercise the rights granted to that recipient under the terms of the License. You may not sublicense the Work. You must keep intact all notices that refer to this License and to the disclaimer of warranties with every copy of the Work You Distribute or Publicly Perform. When You Distribute or Publicly Perform the Work, You may not impose any effective technological measures on the Work that restrict the ability of a recipient of the Work from You to exercise the rights granted to that recipient under the terms of the License. This Section 4(a) applies to the Work as incorporated in a Collection, but this does not require the Collection apart from the Work itself to be made subject to the terms of this License. If You create a Collection, upon notice from any Licensor You must, to the extent practicable, remove from the Collection any credit as required by Section 4(b), as requested. If You create an Adaptation, upon notice from any Licensor You must, to the extent practicable, remove from the Adaptation any credit as required by Section 4(b), as requested. + If You Distribute, or Publicly Perform the Work or any Adaptations or Collections, You must, unless a request has been made pursuant to Section 4(a), keep intact all copyright notices for the Work and provide, reasonable to the medium or means You are utilizing: (i) the name of the Original Author (or pseudonym, if applicable) if supplied, and/or if the Original Author and/or Licensor designate another party or parties (e.g., a sponsor institute, publishing entity, journal) for attribution ("Attribution Parties") in Licensor's copyright notice, terms of service or by other reasonable means, the name of such party or parties; (ii) the title of the Work if supplied; (iii) to the extent reasonably practicable, the URI, if any, that Licensor specifies to be associated with the Work, unless such URI does not refer to the copyright notice or licensing information for the Work; and (iv) , consistent with Section 3(b), in the case of an Adaptation, a credit identifying the use of the Work in the Adaptation (e.g., "French translation of the Work by Original Author," or "Screenplay based on original Work by Original Author"). The credit required by this Section 4 (b) may be implemented in any reasonable manner; provided, however, that in the case of a Adaptation or Collection, at a minimum such credit will appear, if a credit for all contributing authors of the Adaptation or Collection appears, then as part of these credits and in a manner at least as prominent as the credits for the other contributing authors. For the avoidance of doubt, You may only use the credit required by this Section for the purpose of attribution in the manner set out above and, by exercising Your rights under this License, You may not implicitly or explicitly assert or imply any connection with, sponsorship or endorsement by the Original Author, Licensor and/or Attribution Parties, as appropriate, of You or Your use of the Work, without the separate, express prior written permission of the Original Author, Licensor and/or Attribution Parties. + Except as otherwise agreed in writing by the Licensor or as may be otherwise permitted by applicable law, if You Reproduce, Distribute or Publicly Perform the Work either by itself or as part of any Adaptations or Collections, You must not distort, mutilate, modify or take other derogatory action in relation to the Work which would be prejudicial to the Original Author's honor or reputation. Licensor agrees that in those jurisdictions (e.g. Japan), in which any exercise of the right granted in Section 3(b) of this License (the right to make Adaptations) would be deemed to be a distortion, mutilation, modification or other derogatory action prejudicial to the Original Author's honor and reputation, the Licensor will waive or not assert, as appropriate, this Section, to the fullest extent permitted by the applicable national law, to enable You to reasonably exercise Your right under Section 3(b) of this License (right to make Adaptations) but not otherwise. + +5. Representations, Warranties and Disclaimer + +UNLESS OTHERWISE MUTUALLY AGREED TO BY THE PARTIES IN WRITING, LICENSOR OFFERS THE WORK AS-IS AND MAKES NO REPRESENTATIONS OR WARRANTIES OF ANY KIND CONCERNING THE WORK, EXPRESS, IMPLIED, STATUTORY OR OTHERWISE, INCLUDING, WITHOUT LIMITATION, WARRANTIES OF TITLE, MERCHANTIBILITY, FITNESS FOR A PARTICULAR PURPOSE, NONINFRINGEMENT, OR THE ABSENCE OF LATENT OR OTHER DEFECTS, ACCURACY, OR THE PRESENCE OF ABSENCE OF ERRORS, WHETHER OR NOT DISCOVERABLE. SOME JURISDICTIONS DO NOT ALLOW THE EXCLUSION OF IMPLIED WARRANTIES, SO SUCH EXCLUSION MAY NOT APPLY TO YOU. + +6. Limitation on Liability. EXCEPT TO THE EXTENT REQUIRED BY APPLICABLE LAW, IN NO EVENT WILL LICENSOR BE LIABLE TO YOU ON ANY LEGAL THEORY FOR ANY SPECIAL, INCIDENTAL, CONSEQUENTIAL, PUNITIVE OR EXEMPLARY DAMAGES ARISING OUT OF THIS LICENSE OR THE USE OF THE WORK, EVEN IF LICENSOR HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. + +7. Termination + + This License and the rights granted hereunder will terminate automatically upon any breach by You of the terms of this License. Individuals or entities who have received Adaptations or Collections from You under this License, however, will not have their licenses terminated provided such individuals or entities remain in full compliance with those licenses. Sections 1, 2, 5, 6, 7, and 8 will survive any termination of this License. + Subject to the above terms and conditions, the license granted here is perpetual (for the duration of the applicable copyright in the Work). Notwithstanding the above, Licensor reserves the right to release the Work under different license terms or to stop distributing the Work at any time; provided, however that any such election will not serve to withdraw this License (or any other license that has been, or is required to be, granted under the terms of this License), and this License will continue in full force and effect unless terminated as stated above. + +8. Miscellaneous + + Each time You Distribute or Publicly Perform the Work or a Collection, the Licensor offers to the recipient a license to the Work on the same terms and conditions as the license granted to You under this License. + Each time You Distribute or Publicly Perform an Adaptation, Licensor offers to the recipient a license to the original Work on the same terms and conditions as the license granted to You under this License. + If any provision of this License is invalid or unenforceable under applicable law, it shall not affect the validity or enforceability of the remainder of the terms of this License, and without further action by the parties to this agreement, such provision shall be reformed to the minimum extent necessary to make such provision valid and enforceable. + No term or provision of this License shall be deemed waived and no breach consented to unless such waiver or consent shall be in writing and signed by the party to be charged with such waiver or consent. + This License constitutes the entire agreement between the parties with respect to the Work licensed here. There are no understandings, agreements or representations with respect to the Work not specified here. Licensor shall not be bound by any additional provisions that may appear in any communication from You. This License may not be modified without the mutual written agreement of the Licensor and You. + The rights granted under, and the subject matter referenced, in this License were drafted utilizing the terminology of the Berne Convention for the Protection of Literary and Artistic Works (as amended on September 28, 1979), the Rome Convention of 1961, the WIPO Copyright Treaty of 1996, the WIPO Performances and Phonograms Treaty of 1996 and the Universal Copyright Convention (as revised on July 24, 1971). These rights and subject matter take effect in the relevant jurisdiction in which the License terms are sought to be enforced according to the corresponding provisions of the implementation of those treaty provisions in the applicable national law. If the standard suite of rights granted under applicable copyright law includes additional rights not granted under this License, such additional rights are deemed to be included in the License; this License is not intended to restrict the license of any rights under applicable law. + + +Creative Common public domain 1.0 statement of purpose: + +The laws of most jurisdictions throughout the world automatically confer exclusive Copyright and Related Rights (defined below) upon the creator and subsequent owner(s) (each and all, an "owner") of an original work of authorship and/or a database (each, a "Work"). + +Certain owners wish to permanently relinquish those rights to a Work for the purpose of contributing to a commons of creative, cultural and scientific works ("Commons") that the public can reliably and without fear of later claims of infringement build upon, modify, incorporate in other works, reuse and redistribute as freely as possible in any form whatsoever and for any purposes, including without limitation commercial purposes. These owners may contribute to the Commons to promote the ideal of a free culture and the further production of creative, cultural and scientific works, or to gain reputation or greater distribution for their Work in part through the use and efforts of others. + +For these and/or other purposes and motivations, and without any expectation of additional consideration or compensation, the person associating CC0 with a Work (the "Affirmer"), to the extent that he or she is an owner of Copyright and Related Rights in the Work, voluntarily elects to apply CC0 to the Work and publicly distribute the Work under its terms, with knowledge of his or her Copyright and Related Rights in the Work and the meaning and intended legal effect of CC0 on those rights. + +1. Copyright and Related Rights. A Work made available under CC0 may be protected by copyright and related or neighboring rights ("Copyright and Related Rights"). Copyright and Related Rights include, but are not limited to, the following: + + the right to reproduce, adapt, distribute, perform, display, communicate, and translate a Work; + moral rights retained by the original author(s) and/or performer(s); + publicity and privacy rights pertaining to a person's image or likeness depicted in a Work; + rights protecting against unfair competition in regards to a Work, subject to the limitations in paragraph 4(a), below; + rights protecting the extraction, dissemination, use and reuse of data in a Work; + database rights (such as those arising under Directive 96/9/EC of the European Parliament and of the Council of 11 March 1996 on the legal protection of databases, and under any national implementation thereof, including any amended or successor version of such directive); and + other similar, equivalent or corresponding rights throughout the world based on applicable law or treaty, and any national implementations thereof. + +2. Waiver. To the greatest extent permitted by, but not in contravention of, applicable law, Affirmer hereby overtly, fully, permanently, irrevocably and unconditionally waives, abandons, and surrenders all of Affirmer's Copyright and Related Rights and associated claims and causes of action, whether now known or unknown (including existing as well as future claims and causes of action), in the Work (i) in all territories worldwide, (ii) for the maximum duration provided by applicable law or treaty (including future time extensions), (iii) in any current or future medium and for any number of copies, and (iv) for any purpose whatsoever, including without limitation commercial, advertising or promotional purposes (the "Waiver"). Affirmer makes the Waiver for the benefit of each member of the public at large and to the detriment of Affirmer's heirs and successors, fully intending that such Waiver shall not be subject to revocation, rescission, cancellation, termination, or any other legal or equitable action to disrupt the quiet enjoyment of the Work by the public as contemplated by Affirmer's express Statement of Purpose. + +3. Public License Fallback. Should any part of the Waiver for any reason be judged legally invalid or ineffective under applicable law, then the Waiver shall be preserved to the maximum extent permitted taking into account Affirmer's express Statement of Purpose. In addition, to the extent the Waiver is so judged Affirmer hereby grants to each affected person a royalty-free, non transferable, non sublicensable, non exclusive, irrevocable and unconditional license to exercise Affirmer's Copyright and Related Rights in the Work (i) in all territories worldwide, (ii) for the maximum duration provided by applicable law or treaty (including future time extensions), (iii) in any current or future medium and for any number of copies, and (iv) for any purpose whatsoever, including without limitation commercial, advertising or promotional purposes (the "License"). The License shall be deemed effective as of the date CC0 was applied by Affirmer to the Work. Should any part of the License for any reason be judged legally invalid or ineffective under applicable law, such partial invalidity or ineffectiveness shall not invalidate the remainder of the License, and in such case Affirmer hereby affirms that he or she will not (i) exercise any of his or her remaining Copyright and Related Rights in the Work or (ii) assert any associated claims and causes of action with respect to the Work, in either case contrary to Affirmer's express Statement of Purpose. + +4. Limitations and Disclaimers. + + No trademark or patent rights held by Affirmer are waived, abandoned, surrendered, licensed or otherwise affected by this document. + Affirmer offers the Work as-is and makes no representations or warranties of any kind concerning the Work, express, implied, statutory or otherwise, including without limitation warranties of title, merchantability, fitness for a particular purpose, non infringement, or the absence of latent or other defects, accuracy, or the present or absence of errors, whether or not discoverable, all to the greatest extent permissible under applicable law. + Affirmer disclaims responsibility for clearing rights of other persons that may apply to the Work or any use thereof, including without limitation any person's Copyright and Related Rights in the Work. Further, Affirmer disclaims responsibility for obtaining any necessary consents, permissions or other rights required for any use of the Work. + Affirmer understands and acknowledges that Creative Commons is not a party to this document and has no duty or obligation with respect to this CC0 or use of the Work. diff --git a/sounds/squeal.ogg b/sounds/squeal.ogg new file mode 100644 index 0000000..2711625 Binary files /dev/null and b/sounds/squeal.ogg differ diff --git a/sounds/truck.ogg b/sounds/truck.ogg new file mode 100644 index 0000000..2709400 Binary files /dev/null and b/sounds/truck.ogg differ diff --git a/textures/digtron_builder.png b/textures/digtron_builder.png new file mode 100644 index 0000000..fc3d5cc Binary files /dev/null and b/textures/digtron_builder.png differ diff --git a/textures/digtron_control.png b/textures/digtron_control.png new file mode 100644 index 0000000..0dfd1e4 Binary files /dev/null and b/textures/digtron_control.png differ diff --git a/textures/digtron_core.png b/textures/digtron_core.png new file mode 100644 index 0000000..5edb280 Binary files /dev/null and b/textures/digtron_core.png differ diff --git a/textures/digtron_digger.png b/textures/digtron_digger.png new file mode 100644 index 0000000..effc101 Binary files /dev/null and b/textures/digtron_digger.png differ diff --git a/textures/digtron_inventory.png b/textures/digtron_inventory.png new file mode 100644 index 0000000..0e5ad0c Binary files /dev/null and b/textures/digtron_inventory.png differ diff --git a/textures/digtron_light.png b/textures/digtron_light.png new file mode 100644 index 0000000..695dcaf Binary files /dev/null and b/textures/digtron_light.png differ diff --git a/textures/digtron_motor.png b/textures/digtron_motor.png new file mode 100644 index 0000000..4e1f437 Binary files /dev/null and b/textures/digtron_motor.png differ diff --git a/textures/digtron_plate.png b/textures/digtron_plate.png new file mode 100644 index 0000000..7c1c327 Binary files /dev/null and b/textures/digtron_plate.png differ 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 diff --git a/util_item_place_node.lua b/util_item_place_node.lua new file mode 100644 index 0000000..759ef8e --- /dev/null +++ b/util_item_place_node.lua @@ -0,0 +1,138 @@ +-- The default minetest.item_place_node from item.lua was hard to work with given some of the details +-- of how it handled pointed_thing. It also didn't work right with default:torch. It was simpler to +-- just copy it here and chop out the special cases that were causing problems. + +-- This specific file is therefore licensed under the LGPL 2.1 + +--GNU Lesser General Public License, version 2.1 +--Copyright (C) 2011-2016 celeron55, Perttu Ahola +--Copyright (C) 2011-2016 Various Minetest developers and contributors + +--This program is free software; you can redistribute it and/or modify it under the terms +--of the GNU Lesser General Public License as published by the Free Software Foundation; +--either version 2.1 of the License, or (at your option) any later version. + +--This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; +--without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +--See the GNU Lesser General Public License for more details: +--https://www.gnu.org/licenses/old-licenses/lgpl-2.1.html + +-- Mapping from facedir value to index in facedir_to_dir. +digtron.facedir_to_dir_map = { + [0]=1, 2, 3, 4, + 5, 2, 6, 4, + 6, 2, 5, 4, + 1, 5, 3, 6, + 1, 6, 3, 5, + 1, 4, 3, 2, +} + +local function copy_pointed_thing(pointed_thing) + return { + type = pointed_thing.type, + above = vector.new(pointed_thing.above), + under = vector.new(pointed_thing.under), + } +end + +local function check_attached_node(p, n) + local def = minetest.registered_nodes[n.name] + local d = {x = 0, y = 0, z = 0} + if def.paramtype2 == "wallmounted" then + -- The fallback vector here is in case 'wallmounted to dir' is nil due + -- to voxelmanip placing a wallmounted node without resetting a + -- pre-existing param2 value that is out-of-range for wallmounted. + -- The fallback vector corresponds to param2 = 0. + d = minetest.wallmounted_to_dir(n.param2) or {x = 0, y = 1, z = 0} + else + d.y = -1 + end + local p2 = vector.add(p, d) + local nn = minetest.get_node(p2).name + local def2 = minetest.registered_nodes[nn] + if def2 and not def2.walkable then + return false + end + return true +end + +digtron.item_place_node = function(itemstack, placer, place_to, param2) + local item = itemstack:peek_item() + local def = itemstack:get_definition() + if def.type ~= "node" then + return itemstack, false + end + + local pointed_thing = {} + pointed_thing.type = "node" + pointed_thing.above = {x=place_to.x, y=place_to.y, z=place_to.z} + pointed_thing.under = {x=place_to.x, y=place_to.y - 1, z=place_to.z} + + local oldnode = minetest.get_node_or_nil(place_to) + + --this should never happen, digtron is testing for adjacent unloaded nodes before getting here. + if not oldnode then + minetest.log("info", placer:get_player_name() .. " tried to place" + .. " node in unloaded position " .. minetest.pos_to_string(place_to) + .. " using a digtron.") + return itemstack, false + end + + local newnode = {name = def.name, param1 = 0, param2 = param2} + if def.place_param2 ~= nil then + newnode.param2 = def.place_param2 + end + + -- Check if the node is attached and if it can be placed there + if minetest.get_item_group(def.name, "attached_node") ~= 0 and + not check_attached_node(place_to, newnode) then + minetest.log("action", "attached node " .. def.name .. + " can not be placed at " .. minetest.pos_to_string(place_to)) + return itemstack, false + end + + -- digtron HACK! the default torch mod uses "on_place" to change its model to the correct one, + -- not "after_place_node". It probably should be using after_place_node, but until then I must + -- adapt as best I can to the quirks of default. + if newnode.name == "default:torch" then + if newnode.param2 == 0 then + newnode.name = "default:torch_ceiling" + elseif newnode.param2 > 1 then + newnode.name = "default:torch_wall" + end + end + + -- Add node and update + minetest.add_node(place_to, newnode) + + local take_item = true + + -- Run callback + if def.after_place_node then + -- Deepcopy place_to and pointed_thing because callback can modify it + local place_to_copy = {x=place_to.x, y=place_to.y, z=place_to.z} + local pointed_thing_copy = copy_pointed_thing(pointed_thing) + if def.after_place_node(place_to_copy, placer, itemstack, + pointed_thing_copy) then + take_item = false + end + end + + -- Run script hook + local _, callback + for _, callback in ipairs(minetest.registered_on_placenodes) do + -- Deepcopy pos, node and pointed_thing because callback can modify them + local place_to_copy = {x=place_to.x, y=place_to.y, z=place_to.z} + local newnode_copy = {name=newnode.name, param1=newnode.param1, param2=newnode.param2} + local oldnode_copy = {name=oldnode.name, param1=oldnode.param1, param2=oldnode.param2} + local pointed_thing_copy = copy_pointed_thing(pointed_thing) + if callback(place_to_copy, newnode_copy, placer, oldnode_copy, itemstack, pointed_thing_copy) then + take_item = false + end + end + + if take_item then + itemstack:take_item() + end + return itemstack, true +end \ No newline at end of file