-- 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 -- r_type selected from values_what; the staffs allow to use other -- types like "function" or "give_item" etc. - but that is not -- supported here (cannot be edited or created; only be shown) -- r_value used to store the subtype of r_type -- -- a state/variable: -- r_variable name of a variable the player has *write* access to; -- dropdown list with allowed options -- r_operator selected from values_operator -- r_var_cmp_value can be set freely by the player (the variable will be -- set to this value) -- -- a block in the world: -- r_pos a position in the world; determined by asking the player -- to punch the block -- r_node (follows from r_pos) -- r_param2 (follows from r_pos) -- -- a craft receipe: -- r_value the expected craft result -- r_craft_grid array containing the stacks in the 9 craft grid fields in string form -- -- on_failure: -- r_value alternate target dialog if the previous *effect* failed -- -- chat_all: -- r_value chat message sent to all players -- -- -- give_item, take_item, move, function: requires npc_master priv -- -- Unlike in preconditions, trade (the trade action already happened) and -- inventory actions are not supported as effects. -- -- some helper lists for creating the formspecs and evaulating -- the player's answers: -- general direction of what could make up an effect local check_what = { "- please select -", "an internal state (i.e. of a quest)", -- 2 "a block somewhere", -- 3 "NPC crafts something", -- 4 "go to other dialog if the previous effect failed", -- 5 "send a chat message to all players", -- 6 "give item (created out of thin air) to player (requires npc_master priv)", -- 7 "take item from player and destroy it (requires npc_master priv)", -- 8 "move the player to a given position (requires npc_master priv)", -- 9 "execute Lua code (requires npc_master priv)", -- 10 "Call custom functions that are supposed to be overridden by the server.", -- 11 } -- how to store these as r_type in the precondition: local values_what = {"", "state", "block", "craft", "on_failure", "chat_all", -- the following require the npc_master priv: "give_item", "take_item", "move", "function", -- custom function (does not require npc_master priv) "custom"} -- unlike in the preconditions, the "I cannot punch it" option is -- not offered here - because the player (and later the NPC) needs -- to be able to build at this position local check_block = { "- please select -", -- 1 "If there is air: Place a block so that it looks like now.", -- 2 "If there is a block: Dig it.", -- 3 "Punch the block.", -- 4 "Right-click the block.", -- 5 } -- how to store these as p_value (the actual node data gets stored as p_node, p_param2 and p_pos): local values_block = {"", "place", "dig", "punch", "right-click"} -- comparison operators for variables local check_operator = { "- please select -", -- 1 "new value:", -- 2 "discard/unset/forget", -- 3 "current time", -- 4 "quest step completed:", -- 5 minetest.formspec_escape("max(current, new_value)"), -- 6 minetest.formspec_escape("min(current, new_value)"), -- 7 "increment by:", -- 8 "decrement by:", -- 9 } -- how to store these as r_value (the actual variable is stored in r_variable, and the value in r_new_value): local values_operator = {"", "set_to", "unset", "set_to_current_time", "quest_step", "maximum", "minimum", "increment", "decrement"} -- 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 = {} -- some values - like hour of day or HP of the player - can be read in -- a precondition but not be modified -- get the list of variables the player can *write* local tmp = yl_speak_up.get_quest_variables_with_write_access(pname) -- sort that list (the dropdown formspec element returns just an index) table.sort(tmp) for i, v in ipairs(tmp) do table.insert(var_list, v) end return var_list 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]) 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) if(not(r.r_type) or r.r_type == "") then return "(nothing): Nothing to do. No effect." elseif(r.r_type == "give_item") then return "give_item: Add \""..tostring(r.r_value).."\" to the player's inventory." elseif(r.r_type == "take_item") then return "take_item: Take \""..tostring(r.r_value).."\" from the player's inventory." elseif(r.r_type == "move") then return "move: Move the player to "..tostring(r.r_value).."." elseif(r.r_type == "function") then return "function: execute \""..tostring(r.r_value).."\"." elseif(r.r_type == "trade") then return "trade: obsolete (now defined as an action)" elseif(r.r_type == "dialog") then return "Switch to dialog \""..tostring(r.r_value).."\"." elseif(r.r_type == "state") then local var_name = "VARIABLE[ - ? - ]" if(r.r_variable) then var_name = "VARIABLE[ "..tostring( yl_speak_up.strip_pname_from_var(r.r_variable, pname)).." ]" end if(not(r.r_operator)) then return "Error: Operator not defined." elseif(r.r_operator == "set_to") then return "set "..var_name.." to value \"".. tostring(r.r_var_cmp_value).."\"" elseif(r.r_operator == "unset") then return "discard "..var_name.." (unset)" elseif(r.r_operator == "set_to_current_time") then return "set "..var_name.." to the current time" elseif(r.r_operator == "quest_step") then return "store that the player has completed quest step \"".. tostring(r.r_var_cmp_value).."\"" elseif(r.r_operator == "maximum") then return "set "..var_name.." to value \"".. tostring(r.r_var_cmp_value).."\" if its current value is larger than that" elseif(r.r_operator == "minimum") then return "set "..var_name.." to value \"".. tostring(r.r_var_cmp_value).."\" if its current value is lower than that" elseif(r.r_operator == "increment") then return "increment the value of "..var_name.." by \"".. tostring(r.r_var_cmp_value).."\"" elseif(r.r_operator == "decrement") then return "decrement the value of "..var_name.." by \"".. tostring(r.r_var_cmp_value).."\"" else return "ERROR: Wrong operator \""..tostring(r.r_operator).."\" for "..var_name end elseif(r.r_type == "block") then 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 return "ERROR: r.r_pos is "..minetest.serialize(r.r_pos) -- we don't check here yet which node is actually there - that will be done upon execution elseif(yl_speak_up.check_blacklisted(r.r_value, r.r_node, r.r_node)) then return "ERROR: Blocks of type \""..tostring(r.r_node).."\" do not allow ".. "interaction of type \""..tostring(r.r_value).."\" for NPC." elseif(r.r_value == "place") then return "Place \""..tostring(r.r_node).."\" with param2: "..tostring(r.r_param2).. " at "..minetest.pos_to_string(r.r_pos).."." elseif(r.r_value == "dig") then return "Dig the block at "..minetest.pos_to_string(r.r_pos).."." elseif(r.r_value == "punch") then return "Punch the block at "..minetest.pos_to_string(r.r_pos).."." elseif(r.r_value == "right-click") then return "Right-click the block at "..minetest.pos_to_string(r.r_pos).."." else return "ERROR: Don't know what to do with the block at ".. minetest.pos_to_string(r.r_pos)..": \""..tostring(r.r_value).."\"?" end elseif(r.r_type == "craft") then -- this is only shown in the edit options menu and when editing an effect; -- we can afford a bit of calculation here (it's not a precondtion...) if(not(r.r_value) or not(r.r_craft_grid)) then return "ERROR: Crafting not configured correctly." end local craft_str = "Craft \""..tostring(r.r_value).."\" from ".. table.concat(r.r_craft_grid, ", ").."." -- check here if the craft receipe is broken local input = {} input.items = {} for i, v in ipairs(r.r_craft_grid) do input.items[ i ] = ItemStack(v or "") end 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 return "Error: Recipe changed! No output for "..craft_str 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 return "Error: Amount of output changed! "..craft_str end return craft_str elseif(r.r_type == "on_failure") then return "If the *previous* effect failed, go to dialog \""..tostring(r.r_value).. "\"." elseif(r.r_type == "chat_all") then return "Send chat message: \""..tostring(r.r_value).."\"" elseif(r.r_type == "custom") then return "Call custom function with param: \""..tostring(r.r_value).."\"." end -- fallback 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 == "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(text) -- sending a chat message always counts as successful return true -- "Call custom functions that are supposed to be overridden by the server.", -- 11 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) return yl_speak_up.input_fs_edit_option_related(player, formname, fields, "r_", "o_results", yl_speak_up.max_result_effects, "(Ef)fect", "tmp_result", "Please punch the block you want to manipulate in your effect!", values_what, values_operator, values_block, {}, {}, check_what, check_operator, check_block, {}, {}, -- player variables with write access yl_speak_up.get_sorted_player_var_list_write_access, "edit_effects" ) end yl_speak_up.get_fs_edit_effects = function(player, table_click_result) return yl_speak_up.get_fs_edit_option_related(player, table_click_result, "r_", "o_results", yl_speak_up.max_result_effects, "(Ef)fect", "tmp_result", "What do you want to change with this effect?", values_what, values_operator, values_block, {}, {}, check_what, check_operator, check_block, {}, {}, -- player variables with write access yl_speak_up.get_sorted_player_var_list_write_access, yl_speak_up.show_effect, "table_of_elements", "Change the value of the following variable:", "Set variable to:", "New value:", "The NPC shall do something to the block at the following position:" ) end