From b9bebe82bd8358a91a2f1784981183deaa9a4bee Mon Sep 17 00:00:00 2001 From: Sokomine Date: Sat, 20 Jul 2024 01:20:08 +0200 Subject: [PATCH] allow NPC to use tools when punching and right-clicking blocks --- config.lua | 2 + exec_apply_effects.lua | 138 ++++++++++++++++++++++++++++++++++------- readme.md | 6 ++ 3 files changed, 122 insertions(+), 24 deletions(-) diff --git a/config.lua b/config.lua index 6fb0698..60da249 100644 --- a/config.lua +++ b/config.lua @@ -159,6 +159,8 @@ yl_speak_up.blacklist_effect_on_block_right_click = {} -- taking something out of the inventory of a block or putting something in yl_speak_up.blacklist_effect_on_block_put = {} yl_speak_up.blacklist_effect_on_block_take = {} +-- tools the NPC shall not be able to use (covers both punching and right-click): +yl_speak_up.blacklist_effect_tool_use = {} -- If some items are for some reasons not at all acceptable as quest items, -- blacklist them here. The data structure is the same as for the tables above. diff --git a/exec_apply_effects.lua b/exec_apply_effects.lua index 6073d3d..592b4d7 100644 --- a/exec_apply_effects.lua +++ b/exec_apply_effects.lua @@ -19,7 +19,11 @@ yl_speak_up.dropdown_values_deal_with_offered_item = { -- how how to interact with the node -- node_name the node to place -- node_there the node that can currently be found at that position -yl_speak_up.check_blacklisted = function(how, node_name, node_there) +-- tool_name the name of the tool the NPC wants to use (punch or right-click with) +yl_speak_up.check_blacklisted = function(how, node_name, node_there, tool_name) + if(tool_name) then + return yl_speak_up.blacklist_effect_tool_use[ tool_name ] + end return yl_speak_up.blacklist_effect_on_block_interact[ node_name ] or yl_speak_up.blacklist_effect_on_block_interact[ node_there ] or (how == "place" and yl_speak_up.blacklist_effect_on_block_place[ node_name ]) @@ -31,6 +35,79 @@ yl_speak_up.check_blacklisted = function(how, node_name, node_there) end +-- create fake playerdata so that the NPC can interact with inventories, punch and right-click blocks +yl_speak_up.get_fake_player = function(owner_name, wielded_item) + return { + get_player_name = function() + return owner_name + end, + is_player = function() + return true + end, + is_fake_player = true, + get_wielded_item = function(self, item) + return ItemStack(wielded_item) + end, + get_player_control = function() + -- NPC is not sneaking + return {} + end, + } +end + + +-- shall the NPC wield and use a tool? if so that tools' on_use or on_place +-- function takes precedence over the block it's used on +yl_speak_up.use_tool_on_block = function(r, fun_name, player, n_id, o_id) + if(not(r.r_wielded) or r.r_wielded == "") then + return false + end + -- we need the owner_name for creating the fake player + local owner_name = yl_speak_up.npc_owner[ n_id ] + if(not(owner_name) or owner_name == "") then + yl_speak_up.debug_msg(player, n_id, o_id, tostring(r.r_id).." ".. + r.r_type..": NPC does not have an owner. Aborting.") + return false + end + local npc_inv = minetest.get_inventory({type="detached", name="yl_speak_up_npc_"..tostring(n_id)}) + -- can the tool be used? + local tool_err_msg = nil + if(not(minetest.registered_items[r.r_wielded])) then + tool_err_msg = "Tool not defined" + elseif(not(minetest.registered_items[r.r_wielded][fun_name])) then + tool_err_msg = "Tool does not support "..tostring(r.r_value).."ing" + -- do not use forbidden tools + elseif(yl_speak_up.check_blacklisted(nil, nil, nil, r.r_wielded)) then + tool_err_msg = "NPC are not allowed to use this tool" + -- does the NPC have the item he's supposed to wield? + elseif(not(npc_inv:contains_item("npc_main", r.r_wielded, false))) then + tool_err_msg = "NPC lacks tool" + end + if(tool_err_msg) then + yl_speak_up.debug_msg(player, n_id, o_id, tostring(r.r_id).. + " block: "..tostring(r.r_value).." - "..tool_err_msg.. ": \""..tostring(r.r_wielded).."\".") + return false + end + -- act in the name of the owner when accessing inventories + local fake_player = yl_speak_up.get_fake_player(owner_name, r.r_wielded) + local itemstack = fake_player:get_wielded_item() + local pointed_thing = { + type = "node", + under = r.r_pos, + above = {x=r.r_pos.x, y=r.r_pos.y+1, z=r.r_pos.z} + } + local new_itemstack = minetest.registered_items[r.r_wielded][fun_name]( + itemstack, fake_player, pointed_thing) + minetest.chat_send_player("singleplayer", "Did the rightclicking. Result: "..new_itemstack:get_name().." fun_name: "..tostring(fun_name)) + if(new_itemstack) then + -- apply any itemstack changes + npc_inv:remove_item("npc_main", itemstack) + npc_inv:add_item("npc_main", new_itemstack) + end + return true +end + + -- called by yl_speak_up.input_talk(..) -- and also by yl_speak_up.get_fs_trade_list(..) -- @@ -318,7 +395,8 @@ yl_speak_up.execute_effect = function(player, n_id, o_id, r) return false end -- do not interact with nodes on the blacklist - if(yl_speak_up.check_blacklisted(how_to_interact, node.name, node.name)) then + -- (this here is inventory interaction, so no need to check for tools) + if(yl_speak_up.check_blacklisted(how_to_interact, node.name, node.name, nil)) then yl_speak_up.debug_msg(player, n_id, o_id, tostring(r.r_id).." ".. r.r_type..": Blocks of type \""..tostring(node.name).."\" do not allow ".. "interaction of type \""..tostring(r.r_value).."\" for NPC.") @@ -332,18 +410,7 @@ yl_speak_up.execute_effect = function(player, n_id, o_id, r) return false end -- act in the name of the owner when accessing inventories - local fake_player = { - get_player_name = function() return owner_name end, - is_player = function() return true end, - is_fake_player = true, - get_wielded_item = function(self, item) - if(self._inventory and def.wield_list) then - return self._inventory:get_stack(def.wield_list, self._wield_index) - end - return ItemStack(self._wielded_item) - end, - } - -- TODO: get the fake player from pipeworks? + local fake_player = yl_speak_up.get_fake_player(owner_name, "") local def = minetest.registered_nodes[ node.name ] if(def and def[ "allow_metadata_inventory_"..how_to_interact ]) then local res = def[ "allow_metadata_inventory_"..how_to_interact ]( @@ -618,7 +685,7 @@ yl_speak_up.execute_effect = function(player, n_id, o_id, r) return false end -- do not interact with nodes on the blacklist - if(yl_speak_up.check_blacklisted(r.r_value, r.r_node, node.name)) then + if(yl_speak_up.check_blacklisted(r.r_value, r.r_node, node.name, nil)) then -- construct the right text for the error message local nname = node.name if(r.r_value == "place") then @@ -640,6 +707,14 @@ yl_speak_up.execute_effect = function(player, n_id, o_id, r) " and thus cannot interact with it.") return false end + -- create a fake player and a suitable itemstack + local owner_name = yl_speak_up.npc_owner[ n_id ] + if(not(owner_name) or owner_name == "") then + yl_speak_up.debug_msg(player, n_id, o_id, tostring(r.r_id).." ".. + r.r_type..": NPC does not have an owner. Aborting.") + return false + end + -- "If there is air: Place a block so that it looks like now.", -- 2 if(r.r_value and r.r_value == "place") then if(is_protected) then @@ -710,11 +785,22 @@ yl_speak_up.execute_effect = function(player, n_id, o_id, r) return true -- "Punch the block.", -- 4 elseif(r.r_value and r.r_value == "punch") then + -- shall the NPC wield and use an item? if so that items' on_use function takes + -- precedence + if(r.r_wielded and r.r_wielded ~= "") then + return yl_speak_up.use_tool_on_block(r, "on_use", player, n_id, o_id) + end -- even air can be punched - even if that is pretty pointless - minetest.punch_node(r.r_pos) + minetest.punch_node(r.r_pos, nil) return true -- "Right-click the block.", -- 5 elseif(r.r_value and r.r_value == "right-click") then + -- shall the NPC wield and use an item? if so that items' on_use function takes + -- precedence + if(r.r_wielded and r.r_wielded ~= "") then + return yl_speak_up.use_tool_on_block(r, "on_place", player, n_id, o_id) + end + -- with a tool, clicking on air might make sense; without a tool it doesn't if(not(node) or not(node.name) or not(minetest.registered_nodes[node.name])) then return false end @@ -748,14 +834,18 @@ yl_speak_up.execute_effect = function(player, n_id, o_id, r) yl_speak_up.debug_msg(player, n_id, o_id, tostring(r.r_id).." ".. "block: Opened/closed trapdoor at "..pos_str..".") elseif(minetest.registered_nodes[node.name] - and minetest.registered_nodes[node.name].on_rightclick - and minetest.registered_nodes[node.name].on_rightclick(r.r_pos, node, nil)) then - minetest.registered_nodes[node.name].on_rightclick(r.r_pos, node, nil) - yl_speak_up.debug_msg(player, n_id, o_id, tostring(r.r_id).." ".. - "block: right-clicked at at pos "..pos_str..".") - else - yl_speak_up.debug_msg(player, n_id, o_id, tostring(r.r_id).." ".. - "block: right-click at at pos "..pos_str.." had no effect.") + and minetest.registered_nodes[node.name].on_rightclick) then + local fake_player = yl_speak_up.get_fake_player(owner_name, "") + local itemstack = ItemStack("") + local pointed_thing = nil -- TODO + if(minetest.registered_nodes[node.name].on_rightclick( + r.r_pos, node, fake_player, itemstack, pointed_thing)) then + yl_speak_up.debug_msg(player, n_id, o_id, tostring(r.r_id).." ".. + "block: right-clicked at at pos "..pos_str..".") + else + yl_speak_up.debug_msg(player, n_id, o_id, tostring(r.r_id).." ".. + "block: right-click at at pos "..pos_str.." had no effect.") + end end end return false diff --git a/readme.md b/readme.md index da10382..478aa4f 100644 --- a/readme.md +++ b/readme.md @@ -728,6 +728,12 @@ contain all the blocks which do not allow the NPCs this kind of interaction.
You may i.e. set the `put` and `take` tables for blocks that do extensive checks on the player object which the NPC simply can't provide. + +``` +yl_speak_up.blacklist_effect_tool_use +``` +is a similar table. Please let it contain a list of all the tool item names that NPC are _not_ allowed to u se. + _Note_: The best way to deal with local adjustments may be to create your own mod, i.e. `yl_speak_up_addons`, and let that mod depend on this one, `yl_speak_up`, and do the necessary calls. This is very useful