yl_speak_up/fs_edit_effects.lua

793 lines
33 KiB
Lua

-- 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