From 256ec951e2e6f686e064eaa71a5b4e22028a3928 Mon Sep 17 00:00:00 2001 From: FaceDeer Date: Sun, 25 Aug 2019 23:49:04 -0600 Subject: [PATCH] bring back the hacked item place function --- functions.lua | 38 ++++---- init.lua | 28 ++++-- nodes/node_builder.lua | 24 ++++-- util_item_place_node.lua | 181 +++++++++++++++++++++++++++++++++++++++ 4 files changed, 244 insertions(+), 27 deletions(-) create mode 100644 util_item_place_node.lua diff --git a/functions.lua b/functions.lua index f4b6663..6aeebcf 100644 --- a/functions.lua +++ b/functions.lua @@ -119,14 +119,7 @@ local cardinal_dirs = { } digtron.cardinal_dirs = cardinal_dirs -- used by builder entities as well -- Mapping from facedir value to index in cardinal_dirs. -local 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 facedir_to_dir_map = digtron.facedir_to_dir_map -- Turn the cardinal directions into a set of integers you can add to a hash to step in that direction. local cardinal_dirs_hash = {} @@ -225,10 +218,11 @@ local refresh_adjacent = function(digtron_id) if layout[potential_target] == nil then local fields = data.meta.fields adjacent_to_builders[potential_target] = { - period = fields.period, - offset = fields.offset, + period = tonumber(fields.period) or 1, + offset = tonumber(fields.offset) or 0, item = fields.item, - facing = fields.facing, + facing = tonumber(fields.facing) or 0, + extrusion = tonumber(fields.extrusion) or 1, } end end @@ -783,6 +777,8 @@ local predict_build = function(digtron_id, new_pos, player_name, ignore_nodes) cost = cost + digtron.config.build_cost end + -- TODO handle extrusion + table.insert(built_nodes, { pos = target_pos, node = {name=item, param2=facing }, @@ -795,10 +791,17 @@ local predict_build = function(digtron_id, new_pos, player_name, ignore_nodes) end local build_nodes = function(built_nodes) + local leftovers = {} for _, build_info in ipairs(built_nodes) do - -- TODO: much more complicated than this, see hacked place_item method and stuff - minetest.set_node(build_info.pos, build_info.node) - end + local item_stack = ItemStack(build_info.node.name) + local buildpos = build_info.pos + local build_facing = build_info.node.param2 + local returned_stack, success = digtron.item_place_node(item_stack, digtron.fake_player, buildpos, build_facing) + if returned_stack:get_count() > 0 then + table.insert(leftovers, returned_stack) + end + end + return leftovers end local execute_built_callbacks = function(built_nodes) @@ -825,7 +828,7 @@ end -- Execute cycle digtron.execute_cycle = function(digtron_id, player_name) - local leftovers, nodes_to_dig, dig_cost = predict_dig(digtron_id, player_name) + local dig_leftovers, nodes_to_dig, dig_cost = predict_dig(digtron_id, player_name) local old_root_pos = retrieve_pos(digtron_id) local root_node = minetest.get_node(old_root_pos) local new_root_pos = vector.add(old_root_pos, cardinal_dirs[facedir_to_dir_index(root_node.param2)]) @@ -845,7 +848,8 @@ digtron.execute_cycle = function(digtron_id, player_name) digtron.build_to_world(digtron_id, new_root_pos, player_name) minetest.sound_play("digtron_construction", {gain = 0.5, pos=new_root_pos}) - build_nodes(built_nodes) + local build_leftovers = build_nodes(built_nodes, player_name) + -- There shouldn't normally be build_leftovers, but it's possible. -- Don't need to do fancy callback checking for digtron nodes since I made all those -- nodes and I know they don't have anything that needs to be done for them. @@ -857,6 +861,8 @@ digtron.execute_cycle = function(digtron_id, player_name) -- Must be called after digtron.build_to_world because it triggers falling nodes execute_dug_callbacks(nodes_dug) execute_built_callbacks(built_nodes) + + -- TODO try putting dig_leftovers and build_leftovers into the inventory one last time before ejecting it commit_predictive_inventory(digtron_id) else diff --git a/init.lua b/init.lua index 61ad2ab..5df8ef8 100644 --- a/init.lua +++ b/init.lua @@ -1,15 +1,33 @@ digtron = {} digtron.doc = {} -- TODO: move to doc file + +-- Sometimes we want builder heads to call an item's "on_place" method, other times we +-- don't want them to. There's no way to tell which situation is best programmatically +-- so we have to rely on whitelists to be on the safe side. +--first exact matches are tested, and the value given in this global table is returned +digtron.builder_on_place_items = { + ["default:torch"] = true, +} +-- Then a string prefix is checked, returning this value. Useful for enabling on_placed on a mod-wide basis. +digtron.builder_on_place_prefixes = { + ["farming:"] = true, + ["farming_plus:"] = true, + ["crops:"] = true, +} +-- Finally, items belonging to group "digtron_on_place" will have their on_place methods called. + + + digtron.mod_meta = minetest.get_mod_storage() local modpath = minetest.get_modpath(minetest.get_current_modname()) - - -dofile(modpath .. "/class_fakeplayer.lua") -digtron.fake_player = DigtronFakePlayer.create({x=0,y=0,z=0}, "fake_player") -- since we only need one fake player at a time and it doesn't retain useful state, create a global one and just update it as needed. - dofile(modpath.."/config.lua") + +dofile(modpath.."/class_fakeplayer.lua") +digtron.fake_player = DigtronFakePlayer.create({x=0,y=0,z=0}, "fake_player") -- since we only need one fake player at a time and it doesn't retain useful state, create a global one and just update it as needed. +dofile(modpath.."/util_item_place_node.lua") + dofile(modpath.."/entities.lua") dofile(modpath.."/functions.lua") dofile(modpath.."/controller.lua") diff --git a/nodes/node_builder.lua b/nodes/node_builder.lua index 81fe0b6..ef201bb 100644 --- a/nodes/node_builder.lua +++ b/nodes/node_builder.lua @@ -78,6 +78,16 @@ local inv = minetest.create_detached_inventory("digtron:builder_item", { return 0 end + local stack_def = minetest.registered_nodes[item] + if not stack_def and not digtron.whitelisted_on_place(item) then + return 0 -- don't allow craft items unless their on_place is whitelisted. + end + + -- If we're adding a wallmounted item and the build facing is greater than 5, reset it to 0 + if stack_def ~= nil and stack_def.paramtype2 == "wallmounted" and tonumber(meta:get_int("facing")) > 5 then + meta:set_int("facing", 0) + end + meta:set_string("item", item) digtron.update_builder_item(pos) minetest.show_formspec(player_name, "digtron:builder", get_formspec(pos)) @@ -140,29 +150,31 @@ minetest.register_on_player_receive_fields(function(sender, formname, fields) local extrusion = tonumber(fields.extrusion) if period and period > 0 then - meta:set_int("period", math.floor(tonumber(fields.period))) + meta:set_int("period", math.floor(period)) else period = meta:get_int("period") end if offset then - meta:set_int("offset", math.floor(tonumber(fields.offset))) + meta:set_int("offset", math.floor(offset)) else offset = meta:get_int("offset") end if facing and facing >= 0 and facing < 24 then - local inv = meta:get_inventory() - local target_item = inv:get_stack("main",1) + local target_item = ItemStack(meta:get_string("item")) if target_item:get_definition().paramtype2 == "wallmounted" then if facing < 6 then - meta:set_int("facing", math.floor(facing)) + meta:set_int("facing", facing) -- wallmounted facings only run from 0-5 end else meta:set_int("facing", math.floor(facing)) end + else + facing = meta:get_int("facing") end + if extrusion and extrusion > 0 and extrusion <= digtron.config.maximum_extrusion then - meta:set_int("extrusion", math.floor(tonumber(fields.extrusion))) + meta:set_int("extrusion", math.floor(extrusion)) else extrusion = meta:get_int("extrusion") end diff --git a/util_item_place_node.lua b/util_item_place_node.lua new file mode 100644 index 0000000..ba6ff37 --- /dev/null +++ b/util_item_place_node.lua @@ -0,0 +1,181 @@ +-- 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 and seeds. It was simpler to +-- just copy it here and chop out the special cases that were causing problems, and add some special handling. +-- for nodes that define on_place + +-- 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 has_prefix(str, prefix) + return str:sub(1, string.len(prefix)) == prefix +end + +digtron.whitelisted_on_place = function (item_name) + for listed_item, value in pairs(digtron.builder_on_place_items) do + if item_name == listed_item then return value end + end + + for prefix, value in pairs(digtron.builder_on_place_prefixes) do + if has_prefix(item_name, prefix) then return value end + end + + if minetest.get_item_group(item_name, "digtron_on_place") > 0 then return true end + + return false +end + +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_name = itemstack:get_name() + local def = itemstack:get_definition() + if (not def) or (param2 < 0) or (def.paramtype2 == "wallmounted" and param2 > 5) or (param2 > 23) then -- validate parameters + 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} + + -- Handle node-specific on_place calls as best we can. + if def.on_place and def.on_place ~= minetest.nodedef_default.on_place and digtron.whitelisted_on_place(item_name) then + if def.paramtype2 == "facedir" then + pointed_thing.under = vector.add(place_to, minetest.facedir_to_dir(param2)) + elseif def.paramtype2 == "wallmounted" then + pointed_thing.under = vector.add(place_to, minetest.wallmounted_to_dir(param2)) + end + + -- pass a copy of the item stack parameter because on_place might modify it directly and then we can't tell if we succeeded or not + -- though note that some mods do "creative_mode" handling within their own on_place methods, which makes it impossible for Digtron + -- to know what to do in that case - if you're in creative_mode Digtron will place such items but it will think it failed and not + -- deduct them from inventory no matter what Digtron's settings are. Unfortunate, but not very harmful and I have no workaround. + local returnstack, success = def.on_place(ItemStack(itemstack), placer, pointed_thing) + if returnstack and returnstack:get_count() < itemstack:get_count() then success = true end -- some mods neglect to return a success condition + if success then + -- Override the param2 value to force it to be what Digtron wants + local placed_node = minetest.get_node(place_to) + placed_node.param2 = param2 + minetest.set_node(place_to, placed_node) + end + + return returnstack, success + end + + if minetest.registered_nodes[item_name] == nil then + -- Permitted craft items are handled by the node-specific on_place call, above. + -- if we are a craft item and we get here then we're not whitelisted and we should fail. + -- Note that builder settings should be filtering out craft items like this before we get here, + -- but this will protect us just in case. + return itemstack, false + end + + 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 + + -- Add node and update + minetest.add_node(place_to, newnode) + + local take_item = true + + -- Run callback, using genuine player for per-node definition. + 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, using fake_player to take the blame. + -- Note that fake_player:update is called in the DigtronLayout class's "create" function, + -- which is called before Digtron does any of this building stuff, so it's not necessary + -- to update it here. + 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, digtron.fake_player, 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