From 0e627419268d8d191600d7b8f43d2b656ed7a61f Mon Sep 17 00:00:00 2001 From: Sokomine Date: Thu, 6 Jan 2022 18:15:54 +0100 Subject: [PATCH] extracted exec_apply_effects.lua from fs_edit_effects.lua --- exec_apply_effects.lua | 683 ++++++++++++++++++++++++++++++++++++++++ fs_edit_effects.lua | 685 +---------------------------------------- init.lua | 1 + 3 files changed, 686 insertions(+), 683 deletions(-) create mode 100644 exec_apply_effects.lua diff --git a/exec_apply_effects.lua b/exec_apply_effects.lua new file mode 100644 index 0000000..b3d0cb1 --- /dev/null +++ b/exec_apply_effects.lua @@ -0,0 +1,683 @@ +-- This file contains what is necessary to execute/apply an effect. + +-- for "deal_with_offered_item", used i.e. in yl_speak_up.get_fs_edit_option_effect_deal_with_offered_item +yl_speak_up.dropdown_list_deal_with_offered_item = { + minetest.formspec_escape("- please select -"), + "Take the expected part of the offered item(s) and put them in the NPC's inventory.", + "Accept all of the offered item(s) and put them in the NPC's inventory.", + "Refuse and give the item(s) back." + } +-- the values that will be stored for yl_speak_up.dropdown_list_deal_with_offered_item above +yl_speak_up.dropdown_values_deal_with_offered_item = { + "do_nothing", "take_as_wanted", "take_all", "refuse_all"} + + +-- check if a block of type node_name is blacklisted for a certain interaction +-- (this is needed if a block is not prepared for interaction with NPC and +-- expects to always be dealing with a player) +-- Parameters: +-- 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) + 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 ]) + or (how == "dig" and yl_speak_up.blacklist_effect_on_block_dig[ node_there ]) + or (how == "punch" and yl_speak_up.blacklist_effect_on_block_punch[ node_there ]) + or (how == "right-click" and yl_speak_up.blacklist_effect_on_block_right_click[ node_there]) + or (how == "put" and yl_speak_up.blacklist_effect_on_block_right_click[ node_there]) + or (how == "take" and yl_speak_up.blacklist_effect_on_block_right_click[ node_there]) +end + + +-- called by yl_speak_up.input_talk(..) +-- +-- This function is called *after* the player has clicked on an option +-- and *after* any actions (i.e. trade) have been completed either +-- successfully (=action_was_succesful is true) or not. +-- Unlike the preconditions, the effects are executed in ordered form, +-- ordered by their r_id. +-- Returns the new target dialog that is to be displayed next. This will +-- usually be the one with the r_type "dialog" - unless r_type "on_failure" +-- was encountered after an unsuccessful action *or* right after an +-- effect that returned false. +-- Note: In edit mode, effects will *not* be executed. +yl_speak_up.execute_all_relevant_effects = function(player, effects, o_id, action_was_successful) + local target_dialog = "" + local pname = player:get_player_name() + local n_id = yl_speak_up.speak_to[pname].n_id + if(not(effects)) then + yl_speak_up.debug_msg(player, n_id, o_id, "No effects given.") + -- no effects? Then...return to the start dialog + return {next_dialog = "", alternate_text = nil} + end + local edit_mode = (yl_speak_up.edit_mode[pname] == n_id) + -- Important: the list of effects is *sorted* here. The order remains constant! + local sorted_key_list = yl_speak_up.sort_keys(effects) + if(not(sorted_key_list) or #sorted_key_list < 1) then + yl_speak_up.debug_msg(player, n_id, o_id, "Error: No effects found. At least one of ".. + "type \"dialog\" is necessary.") + elseif(not(edit_mode)) then + yl_speak_up.debug_msg(player, n_id, o_id, "Executing effects: ".. + table.concat(sorted_key_list, ", ")..".") + else + yl_speak_up.debug_msg(player, n_id, o_id, "Not executing effects because in edit mode.") + end + -- failed actions may set an alternate text + local alternate_text = nil + local last_result = action_was_successful + local res = true + for i, k in ipairs(sorted_key_list) do + local r = effects[ k ] + yl_speak_up.debug_msg(player, n_id, o_id, "..executing ".. + tostring(r.r_id)..": "..yl_speak_up.show_effect(r, pname)) + -- do not execute effects in edit mode + if(not(edit_mode)) then + yl_speak_up.debug_msg(player, n_id, o_id, + "Executing effect "..tostring(r.r_id)..".") + res = yl_speak_up.execute_effect(player, n_id, o_id, r) + if(not(res)) then + yl_speak_up.debug_msg(player, n_id, o_id, tostring(r.r_id).. + " -> Effect failed to execute.") + alternate_text = r.alternate_text + end + else + -- in edit mode: assume that the effect was successful + res = true + end + -- "dialog" gives us the normal target_dialog + if(r.r_type and r.r_type == "dialog") then + target_dialog = r.r_value + alternate_text = r.alternate_text + -- "on_failure" gives an alternate target dialog if the action + -- or last effect failed + elseif(r.r_type and r.r_type == "on_failure" and r.r_value and not(last_result)) then + yl_speak_up.debug_msg(player, n_id, o_id, "Aborted executing effects at ".. + tostring(r.r_id)..". New target dialog: "..tostring(r.r_value)..".") + -- we also stop execution here + return {next_dialog = r.r_value, alternate_text = r.alternate_text} + end + last_result = res + end + -- all preconditions are true + yl_speak_up.debug_msg(player, n_id, o_id, "Finished executing effects.") + return {next_dialog = target_dialog, alternate_text = alternate_text} +end + + +-- executes an effect/result r for the player and npc n_id; +-- returns true on success (relevant for on_failure) +-- Note: In edit mode, this function does not get called. +yl_speak_up.execute_effect = function(player, n_id, o_id, r) + if(not(r.r_type) or r.r_type == "") then + -- nothing to do + return true + elseif(r.r_type == "auto" or r.r_type == "trade") then + -- these effects don't do anything + return true + elseif(r.r_type == "put_into_block_inv" + or r.r_type == "take_from_block_inv") then + -- get the inventory of the block + if(not(r.r_pos) or type(r.r_pos) ~= "table" + or not(r.r_pos.x) or not(r.r_pos.y) or not(r.r_pos.z)) then + -- position not found? + yl_speak_up.debug_msg(player, n_id, o_id, tostring(r.r_id).." ".. + r.r_type..": No or incorrect position given: ".. + minetest.serialize(r.rp_pos)..".") + return false + end + local meta = minetest.get_meta(r.r_pos) + if(not(meta)) then + yl_speak_up.debug_msg(player, n_id, o_id, tostring(r.r_id).." ".. + r.r_type..": Failed to get metadata at ".. + minetest.serialize(r.rp_pos)..".") + return false + end + local inv = meta:get_inventory() + if(not(inv)) then + yl_speak_up.debug_msg(player, n_id, o_id, tostring(r.r_id).." ".. + r.r_type..": Failed to get inventory at ".. + minetest.serialize(r.rp_pos)..".") + return false + end + local inv_name = r.r_inv_list_name + -- get the inventory of the npc + local npc_inv = minetest.get_inventory({type="detached", + name="yl_speak_up_npc_"..tostring(n_id)}) + local npc_inv_name = "npc_main" + -- for easier checking + local how_to_interact = "take" + if(r.r_type and r.r_type == "put_into_block_inv") then + how_to_interact = "put" + end + local stack = ItemStack(r.r_itemstack) + -- is there enough room for the item? + if(how_to_interact == "put" + and not(inv:room_for_item(inv_name, stack))) then + yl_speak_up.debug_msg(player, n_id, o_id, tostring(r.r_id).." ".. + r.r_type..": No room for \"".. + minetest.serialize(r.r_itemstack).."\"".. + " in node at ".. + minetest.serialize(r.r_pos)..", inv list \"".. + minetest.serialize(inv_name).."\".") + return false + elseif(how_to_interact == "take" + and not(npc_inv:room_for_item("npc_main", stack))) then + yl_speak_up.debug_msg(player, n_id, o_id, tostring(r.r_id).." ".. + r.r_type..": NPC has no room for \"".. + minetest.serialize(r.r_itemstack).."\".") + return false + end + -- does the item exist? + if(how_to_interact == "put" + and not(npc_inv:contains_item(npc_inv_name, stack, false))) then + yl_speak_up.debug_msg(player, n_id, o_id, tostring(r.r_id).." ".. + r.r_type..": NPC does not have \"".. + minetest.serialize(r.r_itemstack).."\".") + return false + elseif(how_to_interact == "take" + and not(inv:contains_item(inv_name, stack, false))) then + yl_speak_up.debug_msg(player, n_id, o_id, tostring(r.r_id).." ".. + r.r_type..": Block at "..minetest.serialize(r.r_pos).. + " does not contain \""..tostring(r.r_itemstack).."\" in list \"".. + tostring(r.r_inv_list).."\".") + return false + end + -- check the blacklist + local node = minetest.get_node(r.r_pos) + if(not(node) or not(node.name)) then + yl_speak_up.debug_msg(player, n_id, o_id, tostring(r.r_id).." ".. + r.r_type..": No node found at "..minetest.serialize(r.r_pos)..".") + 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 + 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.") + return false + end + -- construct a 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 + -- 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 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 ]( + r.r_pos, inv_name, 1, + ItemStack(r.r_itemstack), + fake_player) + if(not(res) or res < ItemStack(r.r_itemstack):get_count()) then + yl_speak_up.debug_msg(player, n_id, o_id, tostring(r.r_id).." ".. + r.r_type..": allow_metadata_inventory_"..tostring(how_to_interact).. + " forbits interaction at "..minetest.serialize(r.r_pos)..".") + return false + end + end + -- all ok so far; we can proceed + if(how_to_interact == "put") then + local r1 = npc_inv:remove_item(npc_inv_name, stack) + local r2 = inv:add_item(inv_name, r1) + return true + elseif(how_to_interact == "take") then + local r1 = inv:remove_item(inv_name, stack) + local r2 = npc_inv:add_item(npc_inv_name, r1) + return true + end + return false + elseif(r.r_type == "dialog" + or r.r_type == "on_failure") then + -- this needs to be handled in the calling function + return true + elseif(r.r_type == "function") then + -- this can only be set and edited with the staff + return yl_speak_up.eval_and_execute_function(player, r, "r_") + -- this can only be set and edited with the staff + elseif(r.r_type == "give_item") then + if(not(r.r_value)) then + return false + end + local item = ItemStack(r.r_value) + if(not(minetest.registered_items[item:get_name()])) then + yl_speak_up.debug_msg(player, n_id, o_id, tostring(r.r_id).." ".. + "give_item: "..tostring(item:get_name()).." unknown.") + return false + end + local r = player:get_inventory():add_item("main", item) + if(not(r)) then + yl_speak_up.debug_msg(player, n_id, o_id, tostring(r.r_id).." ".. + "give_item: "..tostring(item:get_name()).." failed.") + return false + end + return true + -- this can only be set and edited with the staff + elseif(r.r_type == "take_item") then + if(not(r.r_value)) then + return false + end + local item = ItemStack(r.r_value) + if(not(minetest.registered_items[item:get_name()])) then + yl_speak_up.debug_msg(player, n_id, o_id, tostring(r.r_id).." ".. + "take_item: "..tostring(item:get_name()).." unknown.") + return false + end + local r = player:get_inventory():remove_item("main", item) + if(not(r)) then + yl_speak_up.debug_msg(player, n_id, o_id, tostring(r.r_id).." ".. + "take_item: "..tostring(item:get_name()).." failed.") + return false + end + return true + -- this can only be set and edited with the staff + elseif(r.r_type == "move") then + -- copeid/moved here from AliasAlreadyTakens code in functions.lua + local target_pos = nil + local target_pos_valid = false + + --pos like (100,20,400) + if minetest.string_to_pos(r.r_value) then + target_pos = minetest.string_to_pos(r.r_value) + target_pos_valid = true + end + + --pos like 100,20,400 + local maybe = string.split(r.r_value, ",") + if not target_pos_valid and maybe and tonumber(maybe[1]) + and tonumber(maybe[2]) and tonumber(maybe[3]) and maybe[4] == nil and + tonumber(maybe[1]) <= 32000 and tonumber(maybe[1]) >= -32000 and + tonumber(maybe[2]) <= 32000 and tonumber(maybe[2]) >= -32000 and + tonumber(maybe[3]) <= 32000 and tonumber(maybe[3]) >= -32000 then + target_pos = {x=maybe[1],y=maybe[2],z=maybe[3]} + target_pos_valid = true + end + + --pos like {x=100,y=20,z=400} + if not target_pos_valid and string.sub(r.r_value,1,1) == "{" + and string.sub(r.r_value,-1,-1) == "}" then + local might_be_pos = minetest.deserialize("return " .. r.r_value) + if tonumber(might_be_pos.x) + and tonumber(might_be_pos.x) <= 32000 + and tonumber(might_be_pos.x) >= -32000 + and tonumber(might_be_pos.y) + and tonumber(might_be_pos.y) <= 32000 + and tonumber(might_be_pos.y) >= -32000 + and tonumber(might_be_pos.z) + and tonumber(might_be_pos.z) <= 32000 + and tonumber(might_be_pos.z) >= -32000 then + target_pos = might_be_pos + target_pos_valid = true + end + end + + if target_pos_valid == true then + player:set_pos(target_pos) + if vector.distance(player:get_pos(),target_pos) >= 2 then + yl_speak_up.log_change(pname, n_id, tostring(r.r_id)..": ".. + "Something went wrong! Player wasn't moved properly.") + end + end + + -- Debug + if target_pos_valid == false then + local obj = yl_speak_up.speak_to[pname].obj + local n_id = yl_speak_up.speak_to[pname].n_id + local npc = yl_speak_up.get_number_from_id(n_id) + if obj:get_luaentity() and tonumber(npc) then + yl_speak_up.log_change(pname, n_id, tostring(r.r_id)..": ".. + "NPC at "..minetest.pos_to_string(obj:get_pos(),0).. + " could not move player "..pname.." because the content of ".. + tostring(r.r_id).." is wrong:"..dump(r.r_value)) + else + yl_speak_up.log_change(pname, n_id, tostring(r.r_id)..": ".. + "NPC with unknown ID or without proper object ".. + " could not move player "..pname.." because the content of ".. + tostring(r.r_id).." is wrong:"..dump(r.r_value)) + end + return false + end + return true + + -- "an internal state (i.e. of a quest)", -- 2 + elseif(r.r_type == "state") then + if(not(r.r_variable) or r.r_variable == "") then + return false + end + local pname = player:get_player_name() + local owner = yl_speak_up.npc_owner[ n_id ] + -- set the value of the variable + local new_value = nil + if( r.r_operator and r.r_operator == "set_to") then + new_value = r.r_var_cmp_value + elseif(r.r_operator and r.r_operator == "unset") then + new_value = nil + elseif(r.r_operator and r.r_operator == "set_to_current_time") then + -- we store the time in seconds - because microseconds would just + -- confuse the users and be too fine grained anyway + new_value = math.floor(minetest.get_us_time()/1000000) + elseif(r.r_operator and r.r_operator == "quest_step") then + -- quest_step and maximum are effectively the same + -- TODO: later on, quest steps may be strings + local var_val = yl_speak_up.get_quest_variable_value(pname, r.r_variable) + if(var_value and tonumber(var_val) and tonumber(r.r_var_cmp_value)) then + new_value = math.max(tonumber(var_val), tonumber(r.r_var_cmp_value)) + else + new_value = r.r_var_cmp_value + end + elseif(r.r_operator and r.r_operator == "maximum") then + local var_val = yl_speak_up.get_quest_variable_value(pname, r.r_variable) + if(var_value and tonumber(var_val) and tonumber(r.r_var_cmp_value)) then + new_value = math.max(tonumber(var_val), tonumber(r.r_var_cmp_value)) + else + new_value = r.r_var_cmp_value + end + elseif(r.r_operator and r.r_operator == "minimum") then + local var_val = yl_speak_up.get_quest_variable_value(pname, r.r_variable) + if(var_value and tonumber(var_val) and tonumber(r.r_var_cmp_value)) then + new_value = math.min(tonumber(var_val), tonumber(r.r_var_cmp_value)) + else + new_value = r.r_var_cmp_value + end + elseif(r.r_operator and r.r_operator == "increment") then + local var_val = yl_speak_up.get_quest_variable_value(pname, r.r_variable) + if(var_value and tonumber(var_val) and tonumber(r.r_var_cmp_value)) then + new_value = tonumber(var_val) + tonumber(r.r_var_cmp_value) + else + new_value = r.r_var_cmp_value + end + elseif(r.r_operator and r.r_operator == "decrement") then + local var_val = yl_speak_up.get_quest_variable_value(pname, r.r_variable) + if(var_value and tonumber(var_val) and tonumber(r.r_var_cmp_value)) then + new_value = tonumber(var_val) + tonumber(r.r_var_cmp_value) + else + new_value = -1 * r.r_var_cmp_value + end + else + yl_speak_up.debug_msg(player, n_id, o_id, tostring(r.r_id).." ".. + "state: Unsupported type: "..tostring(r.r_value)..".") + return false + end + -- the owner is already encoded in the variable name + local ret = yl_speak_up.set_quest_variable_value(pname, r.r_variable, new_value) + yl_speak_up.debug_msg(player, n_id, o_id, tostring(r.r_id).." ".. + "state: Success: "..tostring(ret).." for setting "..tostring(r.r_variable).." to ".. + tostring(new_value)..".") + return ret + -- "a block somewhere" -- 3 + elseif(r.r_type == "block") then + -- is the position given correctly? + if(not(r.r_pos) or type(r.r_pos) ~= "table" + or not(r.r_pos.x) or not(r.r_pos.y) or not(r.r_pos.z)) then + yl_speak_up.debug_msg(player, n_id, o_id, tostring(r.r_id).." ".. + "block: Missing or wrong position given: ".. + minetest.serialize(r.r_pos)..".") + return false + end + -- check protection (relevant for some actions): the *owner* + -- of the NPC needs to be able to build there + local is_protected = minetest.is_protected(r.r_pos, yl_speak_up.npc_owner[ n_id ] or "?") + -- human readable position; mostly for debug messages + local pos_str = tostring(minetest.pos_to_string(r.r_pos)) + -- the area has to be loaded + local node = minetest.get_node_or_nil(r.r_pos) + if(not(node)) then + yl_speak_up.debug_msg(player, n_id, o_id, tostring(r.r_id).." ".. + "block: Not loaded (nil) at pos "..pos_str..".") + 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 + -- construct the right text for the error message + local nname = node.name + if(r.r_value == "place") then + nname = r.r_node + end + yl_speak_up.debug_msg(player, n_id, o_id, tostring(r.r_id).." ".. + "block: Blocks of type \""..tostring(nname).."\" do not allow ".. + "interaction of type \""..tostring(r.r_value).."\" for NPC.") + return false + end + -- if node has owner set: check if owner == npc owner + local meta = minetest.get_meta(r.r_pos) + if(meta + and meta:get_string("owner") and meta:get_string("owner") ~= "" + and meta:get_string("owner") ~= yl_speak_up.npc_owner[ n_id ]) then + yl_speak_up.debug_msg(player, n_id, o_id, tostring(r.r_id).." ".. + "block: Blocks at "..pos_str.." is owned by "..meta:get_string("owner").. + ". NPC is owned by "..tostring(yl_speak_up.npc_owner[ n_id ]).. + " and thus cannot interact with it.") + 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 + yl_speak_up.debug_msg(player, n_id, o_id, tostring(r.r_id).." ".. + "block: place - "..pos_str.." is protected. Can't place.") + return false + end + if(not(node) or not(node.name) or node.name ~= "air") then + yl_speak_up.debug_msg(player, n_id, o_id, tostring(r.r_id).." ".. + "block: place - there is already a block at pos "..pos_str.. + ". Can't place.") + return false + end + -- does the NPC have this block in his inventory? else he can't place it + local npc_inv = minetest.get_inventory({type="detached", + name="yl_speak_up_npc_"..tostring(n_id)}) + if(not(npc_inv:contains_item("npc_main", tostring(r.r_node)))) then + yl_speak_up.debug_msg(player, n_id, o_id, tostring(r.r_id).." ".. + "block: place - NPC does not have "..tostring(r.r_node).. + " in his inventory for placing at "..pos_str..".") + return false + end + -- TODO: switch to minetest.place_node in the future once the bug with placing + -- on an air node is fixed + -- actually place the node + minetest.set_node(r.r_pos, {name=r.r_node, param2=r.r_param2}) + -- consume the item + npc_inv:remove_item("npc_main", tostring(r.r_node)) + return true + -- "If there is a block: Dig it.", -- 3 + elseif(r.r_value and r.r_value == "dig") then + if(is_protected) then + yl_speak_up.debug_msg(player, n_id, o_id, tostring(r.r_id).." ".. + "block: place - "..pos_str.." is protected. Can't place.") + return false + end + if(not(node) or not(node.name) or node.name == "air" + or not(minetest.registered_items[ node.name ])) then + yl_speak_up.debug_msg(player, n_id, o_id, tostring(r.r_id).." ".. + "block: dig - there is no block at pos "..pos_str..".") + return false + end + -- TODO: use dig_node once that can put the items in the inventory + -- local dig_res = minetest.dig_node(r.r_pos) + if(minetest.registered_items[ node.name ].can_dig + and not(minetest.registered_items[ node.name ].can_dig(r.r_pos))) then + yl_speak_up.debug_msg(player, n_id, o_id, tostring(r.r_id).." ".. + "block: dig - Can't dig block at pos "..pos_str..".") + return false + end + -- actually remove the node + minetest.remove_node(r.r_pos) + -- get node drops when digging without a tool + local drop_list = minetest.get_node_drops(node, nil) + local npc_inv = minetest.get_inventory({type="detached", + name="yl_speak_up_npc_"..tostring(n_id)}) + -- put the drops into the inventory of the NPC + for i, d in ipairs(drop_list) do + local rest = npc_inv:add_item("npc_main", ItemStack(d)) + if(rest and not(rest:is_empty()) and rest:get_count()>0) then + yl_speak_up.debug_msg(player, n_id, o_id, tostring(r.r_id).. + " block: dig (info) - NPC had no room for item drop ".. + rest:to_string().." from digging at "..pos_str..".") + end + end + yl_speak_up.debug_msg(player, n_id, o_id, tostring(r.r_id).." ".. + "block: dig - success: "..tostring(node.name).." at pos "..pos_str..".") + return true + -- "Punch the block.", -- 4 + elseif(r.r_value and r.r_value == "punch") then + -- even air can be punched - even if that is pretty pointless + minetest.punch_node(r.r_pos) + return true + -- "Right-click the block.", -- 5 + elseif(r.r_value and r.r_value == "right-click") then + if(not(node) or not(node.name) or not(minetest.registered_nodes[node.name])) then + return false + end + -- do not right-click nodes that have a metadata formspec string + local meta = minetest.get_meta(r.r_pos) + if(meta and meta:get_string("formspec") and meta:get_string("formspec") ~= "") then + yl_speak_up.debug_msg(player, n_id, o_id, tostring(r.r_id).." ".. + "block: right-click - The block at "..pos_str.." has a ".. + "formspec set. NPC can't read these. Interaction not possible.") + return false + end + -- do not right-click nodes that have an inventory (they most likely show a + -- formspec - which the NPC can't use anyway) + local inv = meta:get_inventory() + for k, l in pairs(inv:get_lists()) do + -- if the inventory contains any lists: abort + yl_speak_up.debug_msg(player, n_id, o_id, tostring(r.r_id).." ".. + "block: right-click - The block at "..pos_str.." has an ".. + "inventory. Most likely it will show a formspec on right-click. ".. + "NPC can't read these. Interaction not possible.") + return false + end + -- is it a door? + if(doors.registered_doors[node.name]) then + doors.door_toggle( r.r_pos, node, nil) --, clicker) + yl_speak_up.debug_msg(player, n_id, o_id, tostring(r.r_id).." ".. + "block: Opened/closed door at "..pos_str..".") + -- is it a normal trapdoor? + elseif(doors.registered_trapdoors[node.name]) then + doors.trapdoor_toggle(r.r_pos, node, nil) --, clicker) + 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.") + end + end + return false + -- ""NPC crafts something", -- 4 + elseif(r.r_type == "craft") then + if(not(r.r_craft_grid) or not(r.r_value)) then + return false + end + local input = {} + input.items = {} + -- multiple slots in the craft grid may contain the same item + local sum_up = {} + for i, v in ipairs(r.r_craft_grid) do + if(v and v ~= "") then + local stack = ItemStack(v) + -- store this for later crafting + input.items[ i ] = stack + local name = stack:get_name() + if(sum_up[ name ]) then + sum_up[ name ] = sum_up[ name ] + stack:get_count() + else + sum_up[ name ] = stack:get_count() + end + else + -- empty itemstack in this slot + input.items[ i ] = ItemStack("") + end + end + -- does the NPC have all these items in his inventory? + local npc_inv = minetest.get_inventory({type="detached", + name="yl_speak_up_npc_"..tostring(n_id)}) + for k, v in pairs(sum_up) do + if(not(npc_inv:contains_item("npc_main", k.." "..v))) then + yl_speak_up.debug_msg(player, n_id, o_id, tostring(r.r_id).." ".. + "Crafting failed: NPC does not have "..tostring(k.." "..v)) + return false + end + end + -- do these input items form a valid craft recipe? + input.method = "normal" -- normal crafting; no cooking or fuel or the like + input.width = 3 + local output, decremented_input = minetest.get_craft_result(input) + if(output.item:is_empty()) then + yl_speak_up.debug_msg(player, n_id, o_id, tostring(r.r_id).." ".. + "Crafting failed: No output for that recipe.") + return false + end + -- the craft receipe may have changed in the meantime and yield a diffrent result + local expected_stack = ItemStack(r.r_value) + if(output.item:get_name() ~= expected_stack:get_name() + or output.item:get_count() ~= expected_stack:get_count()) then + yl_speak_up.debug_msg(player, n_id, o_id, tostring(r.r_id).." ".. + "Crafting failed: Diffrent output: "..tostring(output.item:to_string())) + return false + end + yl_speak_up.debug_msg(player, n_id, o_id, tostring(r.r_id).." ".. + "Great: Crafting is possible!") + -- actually consume the items required, return the ones in decremented_input + for i, v in ipairs(r.r_craft_grid) do + if(v and v ~= "") then + npc_inv:remove_item("npc_main", ItemStack(v)) + end + end + -- add the craft result + if(not(npc_inv:room_for_item("npc_main", output.item))) then + yl_speak_up.debug_msg(player, n_id, o_id, tostring(r.r_id).." ".. + "No room for craft result "..output.item:to_string()) + end + npc_inv:add_item("npc_main", output.item) + -- add the decremented_inputs + for k,v in pairs(decremented_input.items) do + if(k and not(v:is_empty())) then + if(not(npc_inv:room_for_item("npc_main", v))) then + yl_speak_up.debug_msg(player, n_id, o_id, tostring(r.r_id).." ".. + "No room for craft decr. input "..v:to_string()) + end + -- actually give the decremented input to the NPC + npc_inv:add_item("npc_main", v) + end + end + return true + -- "send a chat message to all players", -- 6 + elseif(r.r_type == "chat_all") then + local pname = player:get_player_name() + local dialog = yl_speak_up.speak_to[pname].dialog + local text = r.r_value + -- replace $NPC_NAME$, $OWNER_NAME$, $PLAYER_NAME$ etc. + text = yl_speak_up.replace_vars_in_text(text, dialog, pname) + minetest.chat_send_all( + yl_speak_up.chat_all_prefix.. + minetest.colorize(yl_speak_up.chat_all_color, text)) + -- sending a chat message always counts as successful + return true + -- "Call custom functions that are supposed to be overridden by the server.", -- 13 + elseif(r.r_type == "custom") then + -- execute the custom function + return yl_speak_up.effect_custom(player, r.r_value) + end + -- fallback: unkown type + return false +end diff --git a/fs_edit_effects.lua b/fs_edit_effects.lua index ab05cd3..c304ac0 100644 --- a/fs_edit_effects.lua +++ b/fs_edit_effects.lua @@ -1,4 +1,5 @@ - +-- This file contains what is necessary to add/edit an effect. +-- -- Which diffrent types of effects are available? -- -> The following fields are part of an effect/result: -- r_id the ID/key of the effect/result @@ -128,17 +129,6 @@ local values_operator = {"", "set_to", "unset", "set_to_current_time", "quest_step", "maximum", "minimum", "increment", "decrement"} --- for "deal_with_offered_item", used i.e. in yl_speak_up.get_fs_edit_option_effect_deal_with_offered_item -yl_speak_up.dropdown_list_deal_with_offered_item = { - minetest.formspec_escape("- please select -"), - "Take the expected part of the offered item(s) and put them in the NPC's inventory.", - "Accept all of the offered item(s) and put them in the NPC's inventory.", - "Refuse and give the item(s) back." - } --- the values that will be stored for yl_speak_up.dropdown_list_deal_with_offered_item above -yl_speak_up.dropdown_values_deal_with_offered_item = { - "do_nothing", "take_as_wanted", "take_all", "refuse_all"} - -- get the list of variables the player has *write* access to yl_speak_up.get_sorted_player_var_list_write_access = function(pname) local var_list = {} @@ -155,24 +145,6 @@ yl_speak_up.get_sorted_player_var_list_write_access = function(pname) end --- check if a block of type node_name is blacklisted for a certain interaction --- (this is needed if a block is not prepared for interaction with NPC and --- expects to always be dealing with a player) --- Parameters: --- 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) - 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 ]) - or (how == "dig" and yl_speak_up.blacklist_effect_on_block_dig[ node_there ]) - or (how == "punch" and yl_speak_up.blacklist_effect_on_block_punch[ node_there ]) - or (how == "right-click" and yl_speak_up.blacklist_effect_on_block_right_click[ node_there]) - or (how == "put" and yl_speak_up.blacklist_effect_on_block_right_click[ node_there]) - or (how == "take" and yl_speak_up.blacklist_effect_on_block_right_click[ node_there]) -end - -- returns a human-readable text as description of the effects -- (as shown in the edit options dialog and in the edit effect formspec) yl_speak_up.show_effect = function(r, pname) @@ -307,659 +279,6 @@ yl_speak_up.show_effect = function(r, pname) return tostring(r.r_value) end - --- called by yl_speak_up.input_talk(..) --- --- This function is called *after* the player has clicked on an option --- and *after* any actions (i.e. trade) have been completed either --- successfully (=action_was_succesful is true) or not. --- Unlike the preconditions, the effects are executed in ordered form, --- ordered by their r_id. --- Returns the new target dialog that is to be displayed next. This will --- usually be the one with the r_type "dialog" - unless r_type "on_failure" --- was encountered after an unsuccessful action *or* right after an --- effect that returned false. --- Note: In edit mode, effects will *not* be executed. -yl_speak_up.execute_all_relevant_effects = function(player, effects, o_id, action_was_successful) - local target_dialog = "" - local pname = player:get_player_name() - local n_id = yl_speak_up.speak_to[pname].n_id - if(not(effects)) then - yl_speak_up.debug_msg(player, n_id, o_id, "No effects given.") - -- no effects? Then...return to the start dialog - return {next_dialog = "", alternate_text = nil} - end - local edit_mode = (yl_speak_up.edit_mode[pname] == n_id) - -- Important: the list of effects is *sorted* here. The order remains constant! - local sorted_key_list = yl_speak_up.sort_keys(effects) - if(not(sorted_key_list) or #sorted_key_list < 1) then - yl_speak_up.debug_msg(player, n_id, o_id, "Error: No effects found. At least one of ".. - "type \"dialog\" is necessary.") - elseif(not(edit_mode)) then - yl_speak_up.debug_msg(player, n_id, o_id, "Executing effects: ".. - table.concat(sorted_key_list, ", ")..".") - else - yl_speak_up.debug_msg(player, n_id, o_id, "Not executing effects because in edit mode.") - end - -- failed actions may set an alternate text - local alternate_text = nil - local last_result = action_was_successful - local res = true - for i, k in ipairs(sorted_key_list) do - local r = effects[ k ] - yl_speak_up.debug_msg(player, n_id, o_id, "..executing ".. - tostring(r.r_id)..": "..yl_speak_up.show_effect(r, pname)) - -- do not execute effects in edit mode - if(not(edit_mode)) then - yl_speak_up.debug_msg(player, n_id, o_id, - "Executing effect "..tostring(r.r_id)..".") - res = yl_speak_up.execute_effect(player, n_id, o_id, r) - if(not(res)) then - yl_speak_up.debug_msg(player, n_id, o_id, tostring(r.r_id).. - " -> Effect failed to execute.") - alternate_text = r.alternate_text - end - else - -- in edit mode: assume that the effect was successful - res = true - end - -- "dialog" gives us the normal target_dialog - if(r.r_type and r.r_type == "dialog") then - target_dialog = r.r_value - alternate_text = r.alternate_text - -- "on_failure" gives an alternate target dialog if the action - -- or last effect failed - elseif(r.r_type and r.r_type == "on_failure" and r.r_value and not(last_result)) then - yl_speak_up.debug_msg(player, n_id, o_id, "Aborted executing effects at ".. - tostring(r.r_id)..". New target dialog: "..tostring(r.r_value)..".") - -- we also stop execution here - return {next_dialog = r.r_value, alternate_text = r.alternate_text} - end - last_result = res - end - -- all preconditions are true - yl_speak_up.debug_msg(player, n_id, o_id, "Finished executing effects.") - return {next_dialog = target_dialog, alternate_text = alternate_text} -end - - --- executes an effect/result r for the player and npc n_id; --- returns true on success (relevant for on_failure) --- Note: In edit mode, this function does not get called. -yl_speak_up.execute_effect = function(player, n_id, o_id, r) - if(not(r.r_type) or r.r_type == "") then - -- nothing to do - return true - elseif(r.r_type == "auto" or r.r_type == "trade") then - -- these effects don't do anything - return true - elseif(r.r_type == "put_into_block_inv" - or r.r_type == "take_from_block_inv") then - -- get the inventory of the block - if(not(r.r_pos) or type(r.r_pos) ~= "table" - or not(r.r_pos.x) or not(r.r_pos.y) or not(r.r_pos.z)) then - -- position not found? - yl_speak_up.debug_msg(player, n_id, o_id, tostring(r.r_id).." ".. - r.r_type..": No or incorrect position given: ".. - minetest.serialize(r.rp_pos)..".") - return false - end - local meta = minetest.get_meta(r.r_pos) - if(not(meta)) then - yl_speak_up.debug_msg(player, n_id, o_id, tostring(r.r_id).." ".. - r.r_type..": Failed to get metadata at ".. - minetest.serialize(r.rp_pos)..".") - return false - end - local inv = meta:get_inventory() - if(not(inv)) then - yl_speak_up.debug_msg(player, n_id, o_id, tostring(r.r_id).." ".. - r.r_type..": Failed to get inventory at ".. - minetest.serialize(r.rp_pos)..".") - return false - end - local inv_name = r.r_inv_list_name - -- get the inventory of the npc - local npc_inv = minetest.get_inventory({type="detached", - name="yl_speak_up_npc_"..tostring(n_id)}) - local npc_inv_name = "npc_main" - -- for easier checking - local how_to_interact = "take" - if(r.r_type and r.r_type == "put_into_block_inv") then - how_to_interact = "put" - end - local stack = ItemStack(r.r_itemstack) - -- is there enough room for the item? - if(how_to_interact == "put" - and not(inv:room_for_item(inv_name, stack))) then - yl_speak_up.debug_msg(player, n_id, o_id, tostring(r.r_id).." ".. - r.r_type..": No room for \"".. - minetest.serialize(r.r_itemstack).."\"".. - " in node at ".. - minetest.serialize(r.r_pos)..", inv list \"".. - minetest.serialize(inv_name).."\".") - return false - elseif(how_to_interact == "take" - and not(npc_inv:room_for_item("npc_main", stack))) then - yl_speak_up.debug_msg(player, n_id, o_id, tostring(r.r_id).." ".. - r.r_type..": NPC has no room for \"".. - minetest.serialize(r.r_itemstack).."\".") - return false - end - -- does the item exist? - if(how_to_interact == "put" - and not(npc_inv:contains_item(npc_inv_name, stack, false))) then - yl_speak_up.debug_msg(player, n_id, o_id, tostring(r.r_id).." ".. - r.r_type..": NPC does not have \"".. - minetest.serialize(r.r_itemstack).."\".") - return false - elseif(how_to_interact == "take" - and not(inv:contains_item(inv_name, stack, false))) then - yl_speak_up.debug_msg(player, n_id, o_id, tostring(r.r_id).." ".. - r.r_type..": Block at "..minetest.serialize(r.r_pos).. - " does not contain \""..tostring(r.r_itemstack).."\" in list \"".. - tostring(r.r_inv_list).."\".") - return false - end - -- check the blacklist - local node = minetest.get_node(r.r_pos) - if(not(node) or not(node.name)) then - yl_speak_up.debug_msg(player, n_id, o_id, tostring(r.r_id).." ".. - r.r_type..": No node found at "..minetest.serialize(r.r_pos)..".") - 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 - 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.") - return false - end - -- construct a 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 - -- 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 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 ]( - r.r_pos, inv_name, 1, - ItemStack(r.r_itemstack), - fake_player) - if(not(res) or res < ItemStack(r.r_itemstack):get_count()) then - yl_speak_up.debug_msg(player, n_id, o_id, tostring(r.r_id).." ".. - r.r_type..": allow_metadata_inventory_"..tostring(how_to_interact).. - " forbits interaction at "..minetest.serialize(r.r_pos)..".") - return false - end - end - -- all ok so far; we can proceed - if(how_to_interact == "put") then - local r1 = npc_inv:remove_item(npc_inv_name, stack) - local r2 = inv:add_item(inv_name, r1) - return true - elseif(how_to_interact == "take") then - local r1 = inv:remove_item(inv_name, stack) - local r2 = npc_inv:add_item(npc_inv_name, r1) - return true - end - return false - elseif(r.r_type == "dialog" - or r.r_type == "on_failure") then - -- this needs to be handled in the calling function - return true - elseif(r.r_type == "function") then - -- this can only be set and edited with the staff - return yl_speak_up.eval_and_execute_function(player, r, "r_") - -- this can only be set and edited with the staff - elseif(r.r_type == "give_item") then - if(not(r.r_value)) then - return false - end - local item = ItemStack(r.r_value) - if(not(minetest.registered_items[item:get_name()])) then - yl_speak_up.debug_msg(player, n_id, o_id, tostring(r.r_id).." ".. - "give_item: "..tostring(item:get_name()).." unknown.") - return false - end - local r = player:get_inventory():add_item("main", item) - if(not(r)) then - yl_speak_up.debug_msg(player, n_id, o_id, tostring(r.r_id).." ".. - "give_item: "..tostring(item:get_name()).." failed.") - return false - end - return true - -- this can only be set and edited with the staff - elseif(r.r_type == "take_item") then - if(not(r.r_value)) then - return false - end - local item = ItemStack(r.r_value) - if(not(minetest.registered_items[item:get_name()])) then - yl_speak_up.debug_msg(player, n_id, o_id, tostring(r.r_id).." ".. - "take_item: "..tostring(item:get_name()).." unknown.") - return false - end - local r = player:get_inventory():remove_item("main", item) - if(not(r)) then - yl_speak_up.debug_msg(player, n_id, o_id, tostring(r.r_id).." ".. - "take_item: "..tostring(item:get_name()).." failed.") - return false - end - return true - -- this can only be set and edited with the staff - elseif(r.r_type == "move") then - -- copeid/moved here from AliasAlreadyTakens code in functions.lua - local target_pos = nil - local target_pos_valid = false - - --pos like (100,20,400) - if minetest.string_to_pos(r.r_value) then - target_pos = minetest.string_to_pos(r.r_value) - target_pos_valid = true - end - - --pos like 100,20,400 - local maybe = string.split(r.r_value, ",") - if not target_pos_valid and maybe and tonumber(maybe[1]) - and tonumber(maybe[2]) and tonumber(maybe[3]) and maybe[4] == nil and - tonumber(maybe[1]) <= 32000 and tonumber(maybe[1]) >= -32000 and - tonumber(maybe[2]) <= 32000 and tonumber(maybe[2]) >= -32000 and - tonumber(maybe[3]) <= 32000 and tonumber(maybe[3]) >= -32000 then - target_pos = {x=maybe[1],y=maybe[2],z=maybe[3]} - target_pos_valid = true - end - - --pos like {x=100,y=20,z=400} - if not target_pos_valid and string.sub(r.r_value,1,1) == "{" - and string.sub(r.r_value,-1,-1) == "}" then - local might_be_pos = minetest.deserialize("return " .. r.r_value) - if tonumber(might_be_pos.x) - and tonumber(might_be_pos.x) <= 32000 - and tonumber(might_be_pos.x) >= -32000 - and tonumber(might_be_pos.y) - and tonumber(might_be_pos.y) <= 32000 - and tonumber(might_be_pos.y) >= -32000 - and tonumber(might_be_pos.z) - and tonumber(might_be_pos.z) <= 32000 - and tonumber(might_be_pos.z) >= -32000 then - target_pos = might_be_pos - target_pos_valid = true - end - end - - if target_pos_valid == true then - player:set_pos(target_pos) - if vector.distance(player:get_pos(),target_pos) >= 2 then - yl_speak_up.log_change(pname, n_id, tostring(r.r_id)..": ".. - "Something went wrong! Player wasn't moved properly.") - end - end - - -- Debug - if target_pos_valid == false then - local obj = yl_speak_up.speak_to[pname].obj - local n_id = yl_speak_up.speak_to[pname].n_id - local npc = yl_speak_up.get_number_from_id(n_id) - if obj:get_luaentity() and tonumber(npc) then - yl_speak_up.log_change(pname, n_id, tostring(r.r_id)..": ".. - "NPC at "..minetest.pos_to_string(obj:get_pos(),0).. - " could not move player "..pname.." because the content of ".. - tostring(r.r_id).." is wrong:"..dump(r.r_value)) - else - yl_speak_up.log_change(pname, n_id, tostring(r.r_id)..": ".. - "NPC with unknown ID or without proper object ".. - " could not move player "..pname.." because the content of ".. - tostring(r.r_id).." is wrong:"..dump(r.r_value)) - end - return false - end - return true - - -- "an internal state (i.e. of a quest)", -- 2 - elseif(r.r_type == "state") then - if(not(r.r_variable) or r.r_variable == "") then - return false - end - local pname = player:get_player_name() - local owner = yl_speak_up.npc_owner[ n_id ] - -- set the value of the variable - local new_value = nil - if( r.r_operator and r.r_operator == "set_to") then - new_value = r.r_var_cmp_value - elseif(r.r_operator and r.r_operator == "unset") then - new_value = nil - elseif(r.r_operator and r.r_operator == "set_to_current_time") then - -- we store the time in seconds - because microseconds would just - -- confuse the users and be too fine grained anyway - new_value = math.floor(minetest.get_us_time()/1000000) - elseif(r.r_operator and r.r_operator == "quest_step") then - -- quest_step and maximum are effectively the same - -- TODO: later on, quest steps may be strings - local var_val = yl_speak_up.get_quest_variable_value(pname, r.r_variable) - if(var_value and tonumber(var_val) and tonumber(r.r_var_cmp_value)) then - new_value = math.max(tonumber(var_val), tonumber(r.r_var_cmp_value)) - else - new_value = r.r_var_cmp_value - end - elseif(r.r_operator and r.r_operator == "maximum") then - local var_val = yl_speak_up.get_quest_variable_value(pname, r.r_variable) - if(var_value and tonumber(var_val) and tonumber(r.r_var_cmp_value)) then - new_value = math.max(tonumber(var_val), tonumber(r.r_var_cmp_value)) - else - new_value = r.r_var_cmp_value - end - elseif(r.r_operator and r.r_operator == "minimum") then - local var_val = yl_speak_up.get_quest_variable_value(pname, r.r_variable) - if(var_value and tonumber(var_val) and tonumber(r.r_var_cmp_value)) then - new_value = math.min(tonumber(var_val), tonumber(r.r_var_cmp_value)) - else - new_value = r.r_var_cmp_value - end - elseif(r.r_operator and r.r_operator == "increment") then - local var_val = yl_speak_up.get_quest_variable_value(pname, r.r_variable) - if(var_value and tonumber(var_val) and tonumber(r.r_var_cmp_value)) then - new_value = tonumber(var_val) + tonumber(r.r_var_cmp_value) - else - new_value = r.r_var_cmp_value - end - elseif(r.r_operator and r.r_operator == "decrement") then - local var_val = yl_speak_up.get_quest_variable_value(pname, r.r_variable) - if(var_value and tonumber(var_val) and tonumber(r.r_var_cmp_value)) then - new_value = tonumber(var_val) + tonumber(r.r_var_cmp_value) - else - new_value = -1 * r.r_var_cmp_value - end - else - yl_speak_up.debug_msg(player, n_id, o_id, tostring(r.r_id).." ".. - "state: Unsupported type: "..tostring(r.r_value)..".") - return false - end - -- the owner is already encoded in the variable name - local ret = yl_speak_up.set_quest_variable_value(pname, r.r_variable, new_value) - yl_speak_up.debug_msg(player, n_id, o_id, tostring(r.r_id).." ".. - "state: Success: "..tostring(ret).." for setting "..tostring(r.r_variable).." to ".. - tostring(new_value)..".") - return ret - -- "a block somewhere" -- 3 - elseif(r.r_type == "block") then - -- is the position given correctly? - if(not(r.r_pos) or type(r.r_pos) ~= "table" - or not(r.r_pos.x) or not(r.r_pos.y) or not(r.r_pos.z)) then - yl_speak_up.debug_msg(player, n_id, o_id, tostring(r.r_id).." ".. - "block: Missing or wrong position given: ".. - minetest.serialize(r.r_pos)..".") - return false - end - -- check protection (relevant for some actions): the *owner* - -- of the NPC needs to be able to build there - local is_protected = minetest.is_protected(r.r_pos, yl_speak_up.npc_owner[ n_id ] or "?") - -- human readable position; mostly for debug messages - local pos_str = tostring(minetest.pos_to_string(r.r_pos)) - -- the area has to be loaded - local node = minetest.get_node_or_nil(r.r_pos) - if(not(node)) then - yl_speak_up.debug_msg(player, n_id, o_id, tostring(r.r_id).." ".. - "block: Not loaded (nil) at pos "..pos_str..".") - 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 - -- construct the right text for the error message - local nname = node.name - if(r.r_value == "place") then - nname = r.r_node - end - yl_speak_up.debug_msg(player, n_id, o_id, tostring(r.r_id).." ".. - "block: Blocks of type \""..tostring(nname).."\" do not allow ".. - "interaction of type \""..tostring(r.r_value).."\" for NPC.") - return false - end - -- if node has owner set: check if owner == npc owner - local meta = minetest.get_meta(r.r_pos) - if(meta - and meta:get_string("owner") and meta:get_string("owner") ~= "" - and meta:get_string("owner") ~= yl_speak_up.npc_owner[ n_id ]) then - yl_speak_up.debug_msg(player, n_id, o_id, tostring(r.r_id).." ".. - "block: Blocks at "..pos_str.." is owned by "..meta:get_string("owner").. - ". NPC is owned by "..tostring(yl_speak_up.npc_owner[ n_id ]).. - " and thus cannot interact with it.") - 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 - yl_speak_up.debug_msg(player, n_id, o_id, tostring(r.r_id).." ".. - "block: place - "..pos_str.." is protected. Can't place.") - return false - end - if(not(node) or not(node.name) or node.name ~= "air") then - yl_speak_up.debug_msg(player, n_id, o_id, tostring(r.r_id).." ".. - "block: place - there is already a block at pos "..pos_str.. - ". Can't place.") - return false - end - -- does the NPC have this block in his inventory? else he can't place it - local npc_inv = minetest.get_inventory({type="detached", - name="yl_speak_up_npc_"..tostring(n_id)}) - if(not(npc_inv:contains_item("npc_main", tostring(r.r_node)))) then - yl_speak_up.debug_msg(player, n_id, o_id, tostring(r.r_id).." ".. - "block: place - NPC does not have "..tostring(r.r_node).. - " in his inventory for placing at "..pos_str..".") - return false - end - -- TODO: switch to minetest.place_node in the future once the bug with placing - -- on an air node is fixed - -- actually place the node - minetest.set_node(r.r_pos, {name=r.r_node, param2=r.r_param2}) - -- consume the item - npc_inv:remove_item("npc_main", tostring(r.r_node)) - return true - -- "If there is a block: Dig it.", -- 3 - elseif(r.r_value and r.r_value == "dig") then - if(is_protected) then - yl_speak_up.debug_msg(player, n_id, o_id, tostring(r.r_id).." ".. - "block: place - "..pos_str.." is protected. Can't place.") - return false - end - if(not(node) or not(node.name) or node.name == "air" - or not(minetest.registered_items[ node.name ])) then - yl_speak_up.debug_msg(player, n_id, o_id, tostring(r.r_id).." ".. - "block: dig - there is no block at pos "..pos_str..".") - return false - end - -- TODO: use dig_node once that can put the items in the inventory - -- local dig_res = minetest.dig_node(r.r_pos) - if(minetest.registered_items[ node.name ].can_dig - and not(minetest.registered_items[ node.name ].can_dig(r.r_pos))) then - yl_speak_up.debug_msg(player, n_id, o_id, tostring(r.r_id).." ".. - "block: dig - Can't dig block at pos "..pos_str..".") - return false - end - -- actually remove the node - minetest.remove_node(r.r_pos) - -- get node drops when digging without a tool - local drop_list = minetest.get_node_drops(node, nil) - local npc_inv = minetest.get_inventory({type="detached", - name="yl_speak_up_npc_"..tostring(n_id)}) - -- put the drops into the inventory of the NPC - for i, d in ipairs(drop_list) do - local rest = npc_inv:add_item("npc_main", ItemStack(d)) - if(rest and not(rest:is_empty()) and rest:get_count()>0) then - yl_speak_up.debug_msg(player, n_id, o_id, tostring(r.r_id).. - " block: dig (info) - NPC had no room for item drop ".. - rest:to_string().." from digging at "..pos_str..".") - end - end - yl_speak_up.debug_msg(player, n_id, o_id, tostring(r.r_id).." ".. - "block: dig - success: "..tostring(node.name).." at pos "..pos_str..".") - return true - -- "Punch the block.", -- 4 - elseif(r.r_value and r.r_value == "punch") then - -- even air can be punched - even if that is pretty pointless - minetest.punch_node(r.r_pos) - return true - -- "Right-click the block.", -- 5 - elseif(r.r_value and r.r_value == "right-click") then - if(not(node) or not(node.name) or not(minetest.registered_nodes[node.name])) then - return false - end - -- do not right-click nodes that have a metadata formspec string - local meta = minetest.get_meta(r.r_pos) - if(meta and meta:get_string("formspec") and meta:get_string("formspec") ~= "") then - yl_speak_up.debug_msg(player, n_id, o_id, tostring(r.r_id).." ".. - "block: right-click - The block at "..pos_str.." has a ".. - "formspec set. NPC can't read these. Interaction not possible.") - return false - end - -- do not right-click nodes that have an inventory (they most likely show a - -- formspec - which the NPC can't use anyway) - local inv = meta:get_inventory() - for k, l in pairs(inv:get_lists()) do - -- if the inventory contains any lists: abort - yl_speak_up.debug_msg(player, n_id, o_id, tostring(r.r_id).." ".. - "block: right-click - The block at "..pos_str.." has an ".. - "inventory. Most likely it will show a formspec on right-click. ".. - "NPC can't read these. Interaction not possible.") - return false - end - -- is it a door? - if(doors.registered_doors[node.name]) then - doors.door_toggle( r.r_pos, node, nil) --, clicker) - yl_speak_up.debug_msg(player, n_id, o_id, tostring(r.r_id).." ".. - "block: Opened/closed door at "..pos_str..".") - -- is it a normal trapdoor? - elseif(doors.registered_trapdoors[node.name]) then - doors.trapdoor_toggle(r.r_pos, node, nil) --, clicker) - 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.") - end - end - return false - -- ""NPC crafts something", -- 4 - elseif(r.r_type == "craft") then - if(not(r.r_craft_grid) or not(r.r_value)) then - return false - end - local input = {} - input.items = {} - -- multiple slots in the craft grid may contain the same item - local sum_up = {} - for i, v in ipairs(r.r_craft_grid) do - if(v and v ~= "") then - local stack = ItemStack(v) - -- store this for later crafting - input.items[ i ] = stack - local name = stack:get_name() - if(sum_up[ name ]) then - sum_up[ name ] = sum_up[ name ] + stack:get_count() - else - sum_up[ name ] = stack:get_count() - end - else - -- empty itemstack in this slot - input.items[ i ] = ItemStack("") - end - end - -- does the NPC have all these items in his inventory? - local npc_inv = minetest.get_inventory({type="detached", - name="yl_speak_up_npc_"..tostring(n_id)}) - for k, v in pairs(sum_up) do - if(not(npc_inv:contains_item("npc_main", k.." "..v))) then - yl_speak_up.debug_msg(player, n_id, o_id, tostring(r.r_id).." ".. - "Crafting failed: NPC does not have "..tostring(k.." "..v)) - return false - end - end - -- do these input items form a valid craft recipe? - input.method = "normal" -- normal crafting; no cooking or fuel or the like - input.width = 3 - local output, decremented_input = minetest.get_craft_result(input) - if(output.item:is_empty()) then - yl_speak_up.debug_msg(player, n_id, o_id, tostring(r.r_id).." ".. - "Crafting failed: No output for that recipe.") - return false - end - -- the craft receipe may have changed in the meantime and yield a diffrent result - local expected_stack = ItemStack(r.r_value) - if(output.item:get_name() ~= expected_stack:get_name() - or output.item:get_count() ~= expected_stack:get_count()) then - yl_speak_up.debug_msg(player, n_id, o_id, tostring(r.r_id).." ".. - "Crafting failed: Diffrent output: "..tostring(output.item:to_string())) - return false - end - yl_speak_up.debug_msg(player, n_id, o_id, tostring(r.r_id).." ".. - "Great: Crafting is possible!") - -- actually consume the items required, return the ones in decremented_input - for i, v in ipairs(r.r_craft_grid) do - if(v and v ~= "") then - npc_inv:remove_item("npc_main", ItemStack(v)) - end - end - -- add the craft result - if(not(npc_inv:room_for_item("npc_main", output.item))) then - yl_speak_up.debug_msg(player, n_id, o_id, tostring(r.r_id).." ".. - "No room for craft result "..output.item:to_string()) - end - npc_inv:add_item("npc_main", output.item) - -- add the decremented_inputs - for k,v in pairs(decremented_input.items) do - if(k and not(v:is_empty())) then - if(not(npc_inv:room_for_item("npc_main", v))) then - yl_speak_up.debug_msg(player, n_id, o_id, tostring(r.r_id).." ".. - "No room for craft decr. input "..v:to_string()) - end - -- actually give the decremented input to the NPC - npc_inv:add_item("npc_main", v) - end - end - return true - -- "send a chat message to all players", -- 6 - elseif(r.r_type == "chat_all") then - local pname = player:get_player_name() - local dialog = yl_speak_up.speak_to[pname].dialog - local text = r.r_value - -- replace $NPC_NAME$, $OWNER_NAME$, $PLAYER_NAME$ etc. - text = yl_speak_up.replace_vars_in_text(text, dialog, pname) - minetest.chat_send_all( - yl_speak_up.chat_all_prefix.. - minetest.colorize(yl_speak_up.chat_all_color, text)) - -- sending a chat message always counts as successful - return true - -- "Call custom functions that are supposed to be overridden by the server.", -- 13 - elseif(r.r_type == "custom") then - -- execute the custom function - return yl_speak_up.effect_custom(player, r.r_value) - end - -- fallback: unkown type - return false -end - - -- these are only wrapper functions for those in fs_edit_general.lua yl_speak_up.input_fs_edit_effects = function(player, formname, fields) diff --git a/init.lua b/init.lua index 7409448..a599dd4 100644 --- a/init.lua +++ b/init.lua @@ -30,6 +30,7 @@ dofile(modpath .. "custrom_functions_you_can_override.lua") -- execute preconditions, actions and effects dofile(modpath .. "exec_eval_preconditions.lua") dofile(modpath .. "exec_actions.lua") +dofile(modpath .. "exec_apply_effects.lua") -- some helper functions for formatting text for a formspec talbe dofile(modpath .. "print_as_table.lua") -- create i.e. a dropdown list of player names