From beed475fc79a733390830756a47abedc14c53dd5 Mon Sep 17 00:00:00 2001 From: FaceDeer Date: Sun, 25 Aug 2019 20:09:09 -0600 Subject: [PATCH] begin reintroducing builder nodes --- controller.lua | 6 + entities.lua | 9 +- functions.lua | 21 +++- init.lua | 3 +- nodes/node_builder.lua | 254 +++++++++++++++++++++++++++++++++++++++ nodes/node_digger.lua | 1 + nodes/node_storage.lua | 39 ++++-- sounds/digtron_error.ogg | Bin 0 -> 11504 bytes 8 files changed, 311 insertions(+), 22 deletions(-) create mode 100644 nodes/node_builder.lua create mode 100644 sounds/digtron_error.ogg diff --git a/controller.lua b/controller.lua index 18c3c90..ef8282e 100644 --- a/controller.lua +++ b/controller.lua @@ -173,7 +173,13 @@ minetest.register_node("digtron:controller", { end, on_rightclick = function(pos, node, clicker, itemstack, pointed_thing) + local returnstack, success = digtron.on_rightclick(pos, node, clicker, itemstack, pointed_thing) + if returnstack then + return returnstack, success + end + if clicker == nil then return end + local meta = minetest.get_meta(pos) local digtron_id = meta:get_string("digtron_id") local player_name = clicker:get_player_name() diff --git a/entities.lua b/entities.lua index ba1e954..26ba9c0 100644 --- a/entities.lua +++ b/entities.lua @@ -160,11 +160,10 @@ end digtron.update_builder_item = function(pos) digtron.remove_builder_item(pos) - node_inventory_table.pos = pos - local inv = minetest.get_inventory(node_inventory_table) - local item_stack = inv:get_stack("main", 1) - if not item_stack:is_empty() then - digtron.create_builder_item = item_stack:get_name() + local meta = minetest.get_meta(pos) + local item = meta:get_string("builder_item") + if item ~= "" then + digtron.create_builder_item = item minetest.add_entity(pos,"digtron:builder_item") end end diff --git a/functions.lua b/functions.lua index c0f5a4f..c212ff1 100644 --- a/functions.lua +++ b/functions.lua @@ -394,7 +394,7 @@ digtron.disassemble = function(digtron_id, player_name) local bbox = retrieve_bounding_box(digtron_id) local root_pos = retrieve_pos(digtron_id) if not root_pos then - minetest.log("error", "digtron.disassemble was unable to find a position for " .. digtron_id + minetest.log("error", "[Digtron] digtron.disassemble was unable to find a position for " .. digtron_id .. ", disassembly was impossible. Has the digtron been removed from world?") return end @@ -406,7 +406,7 @@ digtron.disassemble = function(digtron_id, player_name) local inv = digtron.retrieve_inventory(digtron_id) if not (layout and inv) then - minetest.log("error", "digtron.disassemble was unable to find either layout or inventory record for " .. digtron_id + minetest.log("error", "[Digtron] digtron.disassemble was unable to find either layout or inventory record for " .. digtron_id .. ", disassembly was impossible. Clearing any other remaining data for this id.") dispose_id(digtron_id) return @@ -470,7 +470,7 @@ digtron.remove_from_world = function(digtron_id, player_name) local root_pos = retrieve_pos(digtron_id) if not layout then - minetest.log("error", "digtron.remove_from_world Unable to find layout record for " .. digtron_id + minetest.log("error", "[Digtron] digtron.remove_from_world Unable to find layout record for " .. digtron_id .. ", wiping any remaining metadata for this id to prevent corruption. Sorry!") if root_pos then local meta = minetest.get_meta(root_pos) @@ -481,7 +481,7 @@ digtron.remove_from_world = function(digtron_id, player_name) end if not root_pos then - minetest.log("error", "digtron.remove_from_world Unable to find position for " .. digtron_id + minetest.log("error", "[Digtron] digtron.remove_from_world Unable to find position for " .. digtron_id .. ", it may have already been removed from the world.") return {} end @@ -826,6 +826,19 @@ digtron.on_blast = function(pos, intensity) return drops end +-- Use this inside other on_rightclicks for configuring Digtron nodes, this +-- overrides if you're right-clicking with another Digtron node and assumes +-- that you're trying to build it. +digtron.on_rightclick = function(pos, node, clicker, itemstack, pointed_thing) + local item_def = itemstack:get_definition() + if item_def.type == "node" and minetest.get_item_group(itemstack:get_name(), "digtron") > 0 then + local returnstack, success = minetest.item_place_node(itemstack, clicker, pointed_thing) + if success and item_def.sounds and item_def.sounds.place and item_def.sounds.place.name then + minetest.sound_play(item_def.sounds.place, {pos = pos}) + end + return returnstack, success + end +end ------------------------------------------------------------------------------------ -- Creative trash diff --git a/init.lua b/init.lua index 52393aa..61ad2ab 100644 --- a/init.lua +++ b/init.lua @@ -15,4 +15,5 @@ dofile(modpath.."/functions.lua") dofile(modpath.."/controller.lua") dofile(modpath.."/nodes/node_misc.lua") dofile(modpath.."/nodes/node_storage.lua") -dofile(modpath.."/nodes/node_digger.lua") \ No newline at end of file +dofile(modpath.."/nodes/node_digger.lua") +dofile(modpath.."/nodes/node_builder.lua") \ No newline at end of file diff --git a/nodes/node_builder.lua b/nodes/node_builder.lua new file mode 100644 index 0000000..9f1eea4 --- /dev/null +++ b/nodes/node_builder.lua @@ -0,0 +1,254 @@ +-- internationalization boilerplate +local MP = minetest.get_modpath(minetest.get_current_modname()) +local S, NS = dofile(MP.."/intllib.lua") + + +-- Note: builders go in group 4 + +-- TODO make this global +local player_interacting_with_builder_pos = {} + +local get_formspec = function(pos) + local meta = minetest.get_meta(pos) + + local period = meta:get_int("period") + if period < 1 then period = 1 end + local offset = meta:get_int("offset") + local extrusion = meta:get_int("extrusion") + local build_facing = meta:get_int("build_facing") + local item_name = meta:get_string("build_item") + + return "size[8,5.2]" .. + "item_image[0,0;1,1;" .. item_name .. "]".. + "listcolors[#00000069;#5A5A5A00;#141318;#30434C;#FFF]" .. + "list[detached:digtron:builder_item;main;0,0;1,1;]" .. + "field[1.3,0.8;1,0.1;extrusion;" .. S("Extrusion") .. ";" ..extrusion .. "]" .. + "tooltip[extrusion;" .. S("Builder will extrude this many blocks in the direction it is facing.\nCan be set from 1 to @1.\nNote that Digtron won't build into unloaded map regions.", digtron.config.maximum_extrusion) .. "]" .. + "field[2.3,0.8;1,0.1;period;" .. S("Periodicity") .. ";".. period .. "]" .. + "tooltip[period;" .. S("Builder will build once every n steps.\nThese steps are globally aligned, so all builders with the\nsame period and offset will build on the same location.") .. "]" .. + "field[3.3,0.8;1,0.1;offset;" .. S("Offset") .. ";" .. offset .. "]" .. + "tooltip[offset;" .. S("Offsets the start of periodicity counting by this amount.\nFor example, a builder with period 2 and offset 0 builds\nevery even-numbered block and one with period 2 and\noffset 1 builds every odd-numbered block.") .. "]" .. + "button[4.0,0.5;1,0.1;set;" .. S("Save &\nShow") .. "]" .. + "tooltip[set;" .. S("Saves settings") .. "]" .. + "field[5.3,0.8;1,0.1;build_facing;" .. S("Facing") .. ";" .. build_facing .. "]" .. + "tooltip[build_facing;" .. S("Value from 0-23. Not all block types make use of this.\nUse the 'Read & Save' button to copy the facing of the block\ncurrently in the builder output location.") .. "]" .. + "button[6.0,0.5;1,0.1;read;" .. S("Read &\nSave") .. "]" .. + "tooltip[read;" .. S("Reads the facing of the block currently in the build location,\nthen saves all settings.") .. "]" .. + "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]" .. + "listring[detached:digtron:builder_item;main]" +end + +---------------------------------------------------------------------- +-- Detached inventory for setting the builder item + +local inv = minetest.create_detached_inventory("digtron:builder_item", { + allow_move = function(inv, from_list, from_index, to_list, to_index, count, player) + return 0 + end, + allow_put = function(inv, listname, index, stack, player) + -- Always disallow put, but use this to read what the player *tried* adding and set the builder appropriately + local item = stack:get_name() + + -- Ignore unknown items + if minetest.registered_items[item] == nil then return 0 end + + local player_name = player:get_player_name() + local pos = player_interacting_with_builder_pos[player_name] + if pos == nil then + return 0 + end + + local node = minetest.get_node(pos) + if node.name ~= "digtron:builder" then + minetest.log("warning", "[Digtron] builder detached inventory had player " .. player_name + .. " attempt to set " .. item .. " at " .. minetest.pos_to_string(pos) .. + " but the node at that location was a " .. node.name) + return 0 + end + + local meta = minetest.get_meta(pos) + local digtron_id = meta:get_string("digtron_id") + if digtron_id ~= "" then + minetest.log("warning", "[Digtron] builder detached inventory had player " .. player_name + .. " attempt to set " .. item .. " at " .. minetest.pos_to_string(pos) .. + " but the builder node at that location was already assembled into " .. digtron_id) + return 0 + end + + meta:set_string("build_item", item) + minetest.show_formspec(player_name, "digtron:builder", get_formspec(pos)) + + return 0 + end, + allow_take = function(inv, listname, index, stack, player) + return 0 + end, +-- on_move = function(inv, from_list, from_index, to_list, to_index, count, player) +-- end, +-- on_take = function(inv, listname, index, stack, player) +-- end, +-- on_put = function(inv, listname, index, stack, player) +-- end +}) +inv:set_size("main", 1) + +local builder_on_rightclick = function(pos, node, clicker, itemstack, pointed_thing) + local returnstack, success = digtron.on_rightclick(pos, node, clicker, itemstack, pointed_thing) + if returnstack then + return returnstack, success + end + + if clicker == nil then return end + local player_name = clicker:get_player_name() + + local meta = minetest.get_meta(pos) + + local digtron_id = meta:get_string("digtron_id") + if digtron_id ~= "" then + minetest.sound_play({name = "digtron_error", gain = 0.1}, {to_player=account.name}) + minetest.chat_send_player(player_name, "This Digtron is active, interact with it via the controller node.") + return + end + + player_interacting_with_builder_pos[player_name] = pos + minetest.show_formspec(player_name, + "digtron:builder", + get_formspec(pos)) +end + +minetest.register_on_player_receive_fields(function(sender, formname, fields) + if formname ~= "digtron:builder" then + return + end + + local player_name = sender:get_player_name() + local pos = player_interacting_with_builder_pos[player_name] + if pos == nil then + minetest.log("error", "[Digtron] ".. player_name .. " tried interacting with a Digtron builder but" + .. " no position was recorded.") + return + end + + local meta = minetest.get_meta(pos) + local period = tonumber(fields.period) + local offset = tonumber(fields.offset) + local build_facing = tonumber(fields.build_facing) + local extrusion = tonumber(fields.extrusion) + + if period and period > 0 then + meta:set_int("period", math.floor(tonumber(fields.period))) + else + period = meta:get_int("period") + end + if offset then + meta:set_int("offset", math.floor(tonumber(fields.offset))) + else + offset = meta:get_int("offset") + end + if build_facing and build_facing >= 0 and build_facing < 24 then + local inv = meta:get_inventory() + local target_item = inv:get_stack("main",1) + if target_item:get_definition().paramtype2 == "wallmounted" then + if build_facing < 6 then + meta:set_int("build_facing", math.floor(build_facing)) + -- wallmounted facings only run from 0-5 + end + else + meta:set_int("build_facing", math.floor(build_facing)) + end + end + if extrusion and extrusion > 0 and extrusion <= digtron.config.maximum_extrusion then + meta:set_int("extrusion", math.floor(tonumber(fields.extrusion))) + else + extrusion = meta:get_int("extrusion") + end + +-- if fields.set then +-- digtron.show_offset_markers(pos, offset, period) +-- +-- elseif fields.read then +-- local facing = minetest.get_node(pos).param2 +-- local buildpos = digtron.find_new_pos(pos, facing) +-- local target_node = minetest.get_node(buildpos) +-- if target_node.name ~= "air" and minetest.get_item_group(target_node.name, "digtron") == 0 then +-- local meta = minetest.get_meta(pos) +-- local inv = meta:get_inventory() +-- local target_name = digtron.builder_read_item_substitutions[target_node.name] or target_node.name +-- inv:set_stack("main", 1, target_name) +-- meta:set_int("build_facing", target_node.param2) +-- end +-- end + + if fields.help then + minetest.after(0.5, doc.show_entry, sender:get_player_name(), "nodes", "digtron:builder", true) + end + + digtron.update_builder_item(pos) +end) + + +-- Builds objects in the targeted node. This is a complicated beastie. +minetest.register_node("digtron:builder", { + description = S("Digtron Builder Module"), + _doc_items_longdesc = digtron.doc.builder_longdesc, + _doc_items_usagehelp = digtron.doc.builder_usagehelp, + groups = {cracky = 3, oddly_breakable_by_hand=3, digtron = 4}, + drop = "digtron:builder", + sounds = default.node_sound_metal_defaults(), + paramtype = "light", + paramtype2= "facedir", + is_ground_content = false, + tiles = { + "digtron_plate.png^[transformR90", + "digtron_plate.png^[transformR270", + "digtron_plate.png", + "digtron_plate.png^[transformR180", + "digtron_plate.png^digtron_builder.png", + "digtron_plate.png", + }, + + drawtype = "nodebox", + 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.get_meta(pos) + meta:set_int("period", 1) + meta:set_int("offset", 0) + meta:set_int("build_facing", 0) + meta:set_int("extrusion", 1) + end, + + on_rightclick = builder_on_rightclick, + + on_destruct = function(pos) + digtron.remove_builder_item(pos) + end, + + after_place_node = function(pos) + digtron.update_builder_item(pos) + end, + + can_dig = digtron.can_dig, + on_blast = digtron.on_blast, +}) \ No newline at end of file diff --git a/nodes/node_digger.lua b/nodes/node_digger.lua index c33de51..24bdbdc 100644 --- a/nodes/node_digger.lua +++ b/nodes/node_digger.lua @@ -2,6 +2,7 @@ local MP = minetest.get_modpath(minetest.get_current_modname()) local S, NS = dofile(MP.."/intllib.lua") +-- TODO: make global local player_interacting_with_digtron_pos = {} local get_formspec = function(pos, player_name) diff --git a/nodes/node_storage.lua b/nodes/node_storage.lua index 56ba22f..9a28347 100644 --- a/nodes/node_storage.lua +++ b/nodes/node_storage.lua @@ -6,9 +6,6 @@ local S, NS = dofile(MP.."/intllib.lua") local get_inventory_formspec = function(pos, player_name) return "size[8,9.3]" .. - default.gui_bg .. - default.gui_bg_img .. - default.gui_slots .. "label[0,0;" .. S("Inventory items") .. "]" .. "list[nodemeta:"..pos.x..","..pos.y..","..pos.z..";main;0,0.6;8,4;]" .. "list[current_player;main;0,5.15;8,1;]" .. @@ -57,6 +54,13 @@ minetest.register_node("digtron:inventory", { on_blast = digtron.on_blast, on_rightclick = function(pos, node, clicker, itemstack, pointed_thing) + local returnstack, success = digtron.on_rightclick(pos, node, clicker, itemstack, pointed_thing) + if returnstack then + return returnstack, success + end + + if clicker == nil then return end + local meta = minetest.get_meta(pos) local digtron_id = meta:get("digtron_id") local player_name = clicker:get_player_name() @@ -65,7 +69,8 @@ minetest.register_node("digtron:inventory", { "digtron_inventory:"..minetest.pos_to_string(pos)..":"..player_name, get_inventory_formspec(pos, player_name)) else - minetest.chat_send_player(clicker:get_player_name(), "This Digtron is active, interact with its inventory via the controller node.") + minetest.sound_play({name = "digtron_error", gain = 0.1}, {to_player=account.name}) + minetest.chat_send_player(clicker:get_player_name(), "This Digtron is active, interact with it via the controller node.") end end, @@ -94,9 +99,6 @@ minetest.register_node("digtron:inventory", { local get_fuelstore_formspec = function(pos, player_name) return "size[8,9.3]" .. - default.gui_bg .. - default.gui_bg_img .. - default.gui_slots .. "label[0,0;" .. S("Fuel items") .. "]" .. "list[nodemeta:"..pos.x..","..pos.y..","..pos.z..";fuel;0,0.6;8,4;]" .. "list[current_player;main;0,5.15;8,1;]" .. @@ -157,6 +159,13 @@ minetest.register_node("digtron:fuelstore", { on_blast = digtron.on_blast, on_rightclick = function(pos, node, clicker, itemstack, pointed_thing) + local returnstack, success = digtron.on_rightclick(pos, node, clicker, itemstack, pointed_thing) + if returnstack then + return returnstack, success + end + + if clicker == nil then return end + local meta = minetest.get_meta(pos) local digtron_id = meta:get("digtron_id") local player_name = clicker:get_player_name() @@ -165,7 +174,8 @@ minetest.register_node("digtron:fuelstore", { "digtron_fuelstore:"..minetest.pos_to_string(pos)..":"..player_name, get_fuelstore_formspec(pos, player_name)) else - minetest.chat_send_player(clicker:get_player_name(), "This Digtron is active, interact with its inventory via the controller node.") + minetest.sound_play({name = "digtron_error", gain = 0.1}, {to_player=account.name}) + minetest.chat_send_player(clicker:get_player_name(), "This Digtron is active, interact with it via the controller node.") end end, @@ -200,9 +210,6 @@ minetest.register_node("digtron:fuelstore", { -- local get_combined_formspec = function(pos, player_name) return "size[8,9.9]" .. - default.gui_bg .. - default.gui_bg_img .. - default.gui_slots .. "label[0,0;" .. S("Inventory items") .. "]" .. "list[nodemeta:"..pos.x..","..pos.y..","..pos.z..";main;0,0.6;8,3;]" .. "label[0,3.5;" .. S("Fuel items") .. "]" .. @@ -279,6 +286,13 @@ minetest.register_node("digtron:combined_storage", { on_blast = digtron.on_blast, on_rightclick = function(pos, node, clicker, itemstack, pointed_thing) + local returnstack, success = digtron.on_rightclick(pos, node, clicker, itemstack, pointed_thing) + if returnstack then + return returnstack, success + end + + if clicker == nil then return end + local meta = minetest.get_meta(pos) local digtron_id = meta:get("digtron_id") local player_name = clicker:get_player_name() @@ -287,7 +301,8 @@ minetest.register_node("digtron:combined_storage", { "digtron_combined_storage:"..minetest.pos_to_string(pos)..":"..player_name, get_combined_formspec(pos, player_name)) else - minetest.chat_send_player(clicker:get_player_name(), "This Digtron is active, interact with its inventory via the controller node.") + minetest.sound_play({name = "digtron_error", gain = 0.1}, {to_player=account.name}) + minetest.chat_send_player(clicker:get_player_name(), "This Digtron is active, interact with it via the controller node.") end end, -- diff --git a/sounds/digtron_error.ogg b/sounds/digtron_error.ogg new file mode 100644 index 0000000000000000000000000000000000000000..d1ae7c953723886e0459c05a760a01bf93715e4d GIT binary patch literal 11504 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