-- store which player is monitoring the NPC (for preconditions and -- effects) yl_speak_up.debug_mode_set_by_player = {} -- for sending debug information about preconditions and effects to -- the player who is monitoring the NPC -- (sometimes it is not easy/obvious to see why something failed) yl_speak_up.debug_msg = function(player, n_id, o_id, text) local dname = yl_speak_up.debug_mode_set_by_player[ n_id ] -- nobody cares if(not(dname)) then return end local pname = player:get_player_name() local d_id = yl_speak_up.speak_to[pname].d_id minetest.chat_send_player(dname, "[NPC "..tostring(n_id)..": ".. tostring(pname).."] <"..tostring(d_id).." "..tostring(o_id).. "> "..tostring(text)) end -- a chat command for entering and leaving debug mode; needs to be a chat command -- because the player may have wandered off from his NPC and get too many messages -- without a quick way to get rid of them otherwise minetest.register_chatcommand( 'npc_talk_debug', { description = "Sets you as debugger for the yl_speak_up-NPC with the ID .\n".. " lists the NPC you are currently debugging.\n".. " turns debug mode off again.", privs = {npc_talk_owner}, func = function(pname, param) if(param and param == "off") then local count = 0 for k, v in pairs(yl_speak_up.debug_mode_set_by_player) do if(v and v == pname) then yl_speak_up.debug_mode_set_by_player[ k ] = nil count = count + 1 minetest.chat_send_player(pname, "You are no longer ".. "debugging the NPC with the ID "..tostring(k)..".") end end minetest.chat_send_player(pname, "Removed you as debugger of ".. tostring(count).." NPCs.") return elseif(not(param) or param == "" or param == "list") then local count = 0 local text = "You are currently debugging the NPCs with the following IDs:\n" for k, v in pairs(yl_speak_up.debug_mode_set_by_player) do if(v and v == pname) then count = count + 1 text = text.." "..tostring(k) end end if(count == 0) then text = text.." - none -" else text = text.."\nTo turn debugging off, call this command with the ".. "parameter ." end minetest.chat_send_player(pname, text) return elseif(not(yl_speak_up.may_edit_npc(minetest.get_player_by_name(pname), param))) then minetest.chat_send_player(pname, "You do not have the necessary privs to ".. "edit that NPC.") return else yl_speak_up.debug_mode_set_by_player[ param ] = pname minetest.chat_send_player(pname, "You are now receiving debug information ".. "for NPC "..tostring(param)..".\nTo turn that off, type ".. "\"/npc_talk_debug off\".") end end }); -- evaluate those preconditions of type "function" (set by the staff) -- and execute those effects/results of type "function" (set by the staff) -- WARNING: This is extremly powerful! -- The code is taken out of the function -- calculate_displayable_options(..) -- (written by AliasAlreadyTaken). -- It requires the npc_master priv to add or edit this prereq. -- The function is called by -- * yl_speak_up.eval_precondition and -- * yl_speak_up.eval_effect. yl_speak_up.eval_and_execute_function = function(player, x_v, id_prefix) local pname = player:get_player_name() --minetest.chat_send_all("this is in a single prereq or effect: "..dump(x_v)) local x_id = x_v[ id_prefix.. "id" ] if x_v[ id_prefix.."type" ] ~= "function" then return true end local code = x_v[ id_prefix.."value" ] if code:byte(1) == 27 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 minetest.log("error","[MOD] yl_speak_up: NPC with ID n_"..npc.." at position "..minetest.pos_to_string(obj:get_pos(),0).." could not compile the content of "..x_id.." :"..dump(code) .. " because of illegal bytecode for player "..pname) else minetest.log("error","[MOD] yl_speak_up: NPC with ID unknown could not compile the content of "..x_id.." :"..dump(code) .. " for player unknown because of illegal bytecode") end end local param = "playername" if( id_prefix == "r_") then param = "player" end local f, msg = loadstring("return function("..param..") " .. code .. " end") if not f 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 minetest.log("error","[MOD] yl_speak_up: NPC with ID n_"..npc.." at position "..minetest.pos_to_string(obj:get_pos(),0).." could not compile the content of "..x_id.." :"..dump(code) .. " for player "..pname) else minetest.log("error","[MOD] yl_speak_up: NPC with ID unknown could not compile the content of "..x_id.." :"..dump(code) .. " for player unknown") end else local func = f() local ok, ret = pcall(func,pname) if not ok 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 minetest.log("error","[MOD] yl_speak_up: NPC with ID n_"..npc.." at position "..minetest.pos_to_string(obj:get_pos(),0).." could not execute the content of "..x_id.." :"..dump(code) .. " for player "..pname) else minetest.log("error","[MOD] yl_speak_up: NPC with ID unknown could not execute the content of "..x_id.." :"..dump(code) .. " for player unknown") end end if type(ret) == "boolean" then return ret end end -- fallback return false end -- These two functions -- * yl_speak_up.input_fs_edit_option_related and -- * yl_speak_up.get_fs_edit_option_related -- are very similar for preconditions and effects. Therefore they're called here -- with a lot of parameters. fs_edit_preconditions.lua and fs_edit_effects.lua -- contain only wrappers. yl_speak_up.input_fs_edit_option_related = function(player, formname, fields, id_prefix, element_list_name, max_entries_allowed, element_desc, tmp_data_cache, text_ask_for_punching, values_what, values_operator, values_block, values_trade, values_inv, check_what, check_operator, check_block, check_trade, check_inv, get_sorted_player_var_list_function, formspec_input_to ) if(not(player)) then return end local pname = player:get_player_name() -- what are we talking about? local n_id = yl_speak_up.speak_to[pname].n_id local d_id = yl_speak_up.speak_to[pname].d_id local o_id = yl_speak_up.speak_to[pname].o_id local x_id = yl_speak_up.speak_to[pname][ id_prefix.."id"] -- this only works in edit mode if(not(n_id) or yl_speak_up.edit_mode[pname] ~= n_id) then return end if(fields.back_from_cannot_be_edited) then yl_speak_up.show_fs(player, formspec_input_to, x_id) return end -- delete precondition if(fields.delete_element) then local dialog = yl_speak_up.speak_to[pname].dialog if(dialog and dialog.n_dialogs and x_id and dialog.n_dialogs[d_id] and dialog.n_dialogs[d_id].d_options and dialog.n_dialogs[d_id].d_options[o_id]) then local old_elem = dialog.n_dialogs[d_id].d_options[o_id][ element_list_name ][ x_id ] if(id_prefix == "r_" and old_elem.r_type == "dialog") then yl_speak_up.show_fs(player, "msg", { input_to = "yl_speak_up:"..formspec_input_to, formspec = "size[9,2]".. "label[0.2,0.5;Effects of the type \"dialog\" cannot be deleted.\n".. "Use the edit options or dialog menu to change the target dialog.]".. "button[1.5,1.5;2,0.9;back_from_cannot_be_edited;Back]"}) return end -- actually delete the element dialog.n_dialogs[d_id].d_options[o_id][ element_list_name ][ x_id ] = nil -- record this as a change, but do not save do disk yet table.insert(yl_speak_up.npc_was_changed[ n_id ], "Dialog "..tostring(d_id)..": "..element_desc.." "..tostring( x_id ).. " deleted for option "..tostring(o_id)..".") -- TODO: when trying to save: save to disk as well? -- show the new/changed element -- go back to the edit option dialog (after all we just deleted the prerequirement) yl_speak_up.show_fs(player, "msg", { input_to = "yl_speak_up:"..formspec_input_to, formspec = "size[6,2]".. "label[0.2,0.5;"..element_desc.." \"".. minetest.formspec_escape(tostring( x_id )).. "\" has been deleted.]".. "button[1.5,1.5;2,0.9;back_from_delete_element;Back]"}) return end end if(fields.select_block_pos) then minetest.chat_send_player(pname, text_ask_for_punching) -- this formspec expects the block punch: yl_speak_up.speak_to[pname].expect_block_punch = formspec_input_to return end -- field inputs: those do not trigger a sending of the formspec on their own local was_changed = false -- are we talking about an inventory? -- (inventory only applies to preconditions; not effects) local data = yl_speak_up.speak_to[pname][ tmp_data_cache ] if(((fields.inv_stack_name and fields.inv_stack_name ~= "") or (fields.store_item_name and fields.store_item_name ~= "")) and data and data.what and ((id_prefix == "p_" and data.what >= 5 and data.what <= 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 or (id_prefix == "r_" and data.what >= 7 and data.what <= 8))) then local wanted = "" local wanted_name = "" if(not(fields.store_item_name)) then local parts = fields.inv_stack_name:split(" ") local size = 1 if(parts and #parts > 1) then size = tonumber(parts[2]) if(not(size) or size < 1) then size = 1 end end wanted = parts[1].." "..tostring(size) wanted_name = parts[1] else local trade_inv = minetest.get_inventory({type="detached", name="yl_speak_up_player_"..pname}) if(not(trade_inv) or trade_inv:is_empty("npc_wants", 1)) then -- show error message yl_speak_up.show_fs(player, "msg", { input_to = "yl_speak_up:"..formspec_input_to, formspec = "size[8,2]".. "label[0.2,0.0;Please put an item(stack) into the slot ".. "next to the\n\"Store\" button first!]".. "button[1.5,1.5;2,0.9;back_from_error_msg;Back]"}) return end local stack = trade_inv:get_stack("npc_wants", 1) wanted = stack:get_name().." "..stack:get_count() wanted_name = stack:get_name() end -- does the item exist? if(minetest.registered_items[ wanted_name ]) then data.inv_stack_name = wanted fields.inv_stack_name = wanted else -- show error message yl_speak_up.show_fs(player, "msg", { input_to = "yl_speak_up:"..formspec_input_to, formspec = "size[8,2]".. "label[0.2,0.5;Error: \"".. minetest.formspec_escape(wanted).. "\" is not a valid item(stack).]".. "button[1.5,1.5;2,0.9;back_from_error_msg;Back]"}) return end -- comparison value for a variable (same for both preconditions and effects) elseif(fields.var_cmp_value and data and data.what and data.what == 2 and id_prefix ~= "a_") then data.var_cmp_value = fields.var_cmp_value was_changed = true -- text for a chat message elseif(fields.chat_msg_text and data and data.what and data.what == 6 and id_prefix == "r_") then data.chat_msg_text = fields.chat_msg_text was_changed = true elseif(fields.custom_param and fields.custom_param ~= "- Insert a text that is passed on to your function here -" and fields.custom_param ~= "" and data and data.what and data.what == 7 and id_prefix == "a_") then data.custom_param = fields.custom_param was_changed = true elseif(fields.action_item_quest_id and fields.action_item_quest_id ~= "" and fields.action_item_quest_id ~= "- none set -" and data and data.what and data.what == 4 and id_prefix == "a_") then data.item_quest_id = fields.action_item_quest_id was_changed = true end -- action_item_quest_id and action_item_desc can be set at the same time if(fields.action_item_desc and fields.action_item_desc ~= "" and fields.action_item_desc ~= "- no item set -" and data and data.what and data.what == 4 and id_prefix == "a_") then -- TODO: check if it diffrent from the default one of the stack data.item_desc = fields.action_item_desc was_changed = true end if(fields.quest_question and fields.quest_question ~= "" and data and data.what and data.what == 6 and id_prefix == "a_") then data.quest_question = fields.quest_question was_changed = true end -- quest question and answer can be given with the same press of the save button if(fields.quest_answer and fields.quest_answer ~= "- Insert the correct answer here -" and fields.quest_answer ~= "" and data and data.what and data.what == 6 and id_prefix == "a_") then data.quest_answer = fields.quest_answer was_changed = true end -- "move the player to a given position (requires npc_master priv)", -- 9 if(fields.move_to_x or fields.move_to_y or fields.move_to_z) then local dimension = {"x","y","z"} for i, dim in ipairs(dimension) do local text = fields["move_to_"..dim] if(text and text ~= "") then local val = tonumber(text) if(not(val) or val < -32000 or val > 32000) then yl_speak_up.show_fs(player, "msg", { input_to = "yl_speak_up:"..formspec_input_to, formspec = "size[9,2]".. "label[0.2,0.5;Error: The coordinate values have ".. "be in the range of -32000..32000.]".. "button[1.5,1.5;2,0.9;back_from_error_msg;Back]"}) return else data[ "move_to_"..dim ] = val end end end end -- the save button was pressed if(fields.save_element and data and data.what and values_what[ data.what ]) then -- for creating the new prerequirement; normal elements: p_type, p_value, p_id local v = {} -- determine p_type v[ id_prefix.."type" ] = values_what[ data.what ] local dialog = yl_speak_up.speak_to[pname].dialog if(not(dialog) or not(dialog.n_dialogs) or not(dialog.n_dialogs[d_id]) or not(dialog.n_dialogs[d_id].d_options) or not(dialog.n_dialogs[d_id].d_options[o_id])) then -- this really should not happen during the normal course of operation -- (only if the player sends forged formspec data or a bug occoured) minetest.chat_send_player(pname, "Dialog or option does not exist.") return end local elements = dialog.n_dialogs[d_id].d_options[o_id][ element_list_name ] if(not(elements)) then dialog.n_dialogs[d_id].d_options[o_id][ element_list_name ] = {} elements = dialog.n_dialogs[d_id].d_options[o_id][ element_list_name ] x_id = "new" end -- set x_id appropriately if(not(x_id) or x_id == "new") then x_id = id_prefix..yl_speak_up.find_next_id(elements) end v[ id_prefix.."id" ] = x_id -- if needed: show a message after successful save so that the player can take -- his items back from the trade_inv slots local show_save_msg = nil local sorted_key_list = yl_speak_up.sort_keys(elements) if( x_id == "new" and #sorted_key_list >= max_entries_allowed) then -- this really should not happen during the normal course of operation -- (only if the player sends forged formspec data or a bug occoured) minetest.chat_send_player(pname, "Maximum number of allowed entries exceeded.") return end -- "an internal state (i.e. of a quest)", -- 2 if(data.what == 2 and id_prefix ~= "a_") then v[ id_prefix.."value" ] = "expression" v[ id_prefix.."operator" ] = values_operator[ data.operator ] v[ id_prefix.."var_cmp_value" ] = (data.var_cmp_value or "") v[ id_prefix.."variable" ] = data.variable_name -- "a block somewhere", -- 3 elseif(data.what == 3 and id_prefix ~= "a_") then v[ id_prefix.."value" ] = values_block[ data.block ] if(not(data.block_pos) or not(data.node_data) or not(data.node_data.name)) then yl_speak_up.show_fs(player, "msg", { input_to = "yl_speak_up:"..formspec_input_to, formspec = "size[8,2]".. "label[0.2,0.5;Error: Please select a block first!]".. "button[1.5,1.5;2,0.9;back_from_error_msg;Back]"}) return end -- for "node_is_air", there is no need to store node name and parameter if(v[ id_prefix.."value" ] and (v[ id_prefix.."value" ] == "node_is_like" or v[ id_prefix.."value" ] == "node_is_diffrent_from") or v[ id_prefix.."value" ] == "place" or v[ id_prefix.."value" ] == "dig" or v[ id_prefix.."value" ] == "punch" or v[ id_prefix.."value" ] == "right-click") then v[ id_prefix.."node" ] = data.node_data.name v[ id_prefix.."param2" ] = data.node_data.param2 end -- preconditions can be applied to all blocks; effects may be more limited if(id_prefix == "r_" and yl_speak_up.check_blacklisted(v[id_prefix.."value"], -- we don't know yet which node will be there later on data.node_data.name, data.node_data.name)) then yl_speak_up.show_fs(player, "msg", { input_to = "yl_speak_up:"..formspec_input_to, formspec = "size[8,2]".. "label[0.2,0.5;Error: Blocks of type \"".. tostring(data.node_data.name).."\" do not allow\n".. "interaction of type \""..tostring(v[id_prefix.."value"]).. "\" for NPC.]".. "button[1.5,1.5;2,0.9;back_from_error_msg;Back]"}) return end -- we also need to store the position of the node v[ id_prefix.."pos" ] = {x = data.block_pos.x, y = data.block_pos.y, z = data.block_pos.z } -- "I can't punch it. The block is as the block *above* the one I punched.", if(id_prefix == "p_" and data.block == 5) then v.p_pos.y = v.p_pos.y + 1 end -- "a trade", -- 4 -- (only for preconditions; not for effects) elseif(data.what == 4 and id_prefix == "p_") then -- this depends on the trade associated with that option; therefore, -- it does not need any more parameters (they come dynamicly from the -- trade) v.p_value = values_trade[ data.trade ] -- "the inventory of the player", -- 5 -- "the inventory of the NPC", -- 6 -- (only for preconditions; not for effects) elseif((data.what == 5 or data.what == 6) and id_prefix == "p_") then v.p_value = values_inv[ data.inv ] if(v.p_value and v.p_value ~= "inv_is_empty") then -- we have checked this value earlier on v[ "p_itemstack" ] = data.inv_stack_name end -- "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 elseif(data.what and id_prefix == "r_" and (data.what == 7 or data.what == 8)) then if(not(data.inv_stack_name) or data.inv_stack_name == "") then yl_speak_up.show_fs(player, "msg", { input_to = "yl_speak_up:"..formspec_input_to, formspec = "size[8,2]".. "label[0.2,0.5;Error: Please provide the name of the ".. "\nitem you want to give or take!]".. "button[1.5,1.5;2,0.9;back_from_error_msg;Back]"}) return end if(not(minetest.check_player_privs(player, {npc_master=true}))) then yl_speak_up.show_fs(player, "msg", { input_to = "yl_speak_up:"..formspec_input_to, formspec = "size[9,2]".. "label[0.2,0.5;Error: You need the \"npc_master\" priv ".. " in order to set this effect.]".. "button[1.5,1.5;2,0.9;back_from_error_msg;Back]"}) return end v[ "r_value" ] = data.inv_stack_name -- "move the player to a given position (requires npc_master priv)", -- 9 elseif(data.what and id_prefix == "r_" and data.what == 9) then if(not(data.move_to_x) or not(data.move_to_y) or not(data.move_to_z)) then yl_speak_up.show_fs(player, "msg", { input_to = "yl_speak_up:"..formspec_input_to, formspec = "size[9,2]".. "label[0.2,0.5;Error: Please provide valid coordinates ".. " x, y and z!]".. "button[1.5,1.5;2,0.9;back_from_error_msg;Back]"}) return end if(not(minetest.check_player_privs(player, {npc_master=true}))) then yl_speak_up.show_fs(player, "msg", { input_to = "yl_speak_up:"..formspec_input_to, formspec = "size[9,2]".. "label[0.2,0.5;Error: You need the \"npc_master\" priv ".. " in order to set this effect.]".. "button[1.5,1.5;2,0.9;back_from_error_msg;Back]"}) return end v[ "r_value" ] = minetest.pos_to_string( {x = data.move_to_x, y = data.move_to_y, z = data.move_to_z}) -- "NPC crafts something", -- 4 -- (only for effects; not for preconditions) elseif(data.what and id_prefix == "r_" and data.what == 4) then local player_inv = player:get_inventory() if(player_inv:get_stack("craftpreview", 1):is_empty()) then yl_speak_up.show_fs(player, "msg", { input_to = "yl_speak_up:"..formspec_input_to, formspec = "size[8,2]".. "label[0.2,0.5;Error: Please prepare your craft grid first!".. "\nYour NPC needs to know what to craft.]".. "button[1.5,1.5;2,0.9;back_from_error_msg;Back]"}) return end -- store the craft result (Note: the craft result may change in the future -- if the server changes its craft result, making this craft invalid) v[ "r_value" ] = player_inv:get_stack("craftpreview", 1):to_string() v[ "r_craft_grid"] = {} for i = 1, 9 do -- store all the indigrents of the craft grid table.insert( v[ "r_craft_grid" ], player_inv:get_stack("craft", i):to_string()) end -- "go to other dialog if the *previous* effect failed", -- 5 -- (only for effects; not for preconditions) elseif(data.what and id_prefix == "r_" and data.what == 5) then v[ "r_value" ] = data.on_failure -- "send a chat message to all players", -- 6 -- (only for effects; not for preconditions) elseif(data.what and id_prefix == "r_" and data.what == 6) then data.chat_msg_text = fields.chat_msg_text -- allow saving only if the placeholders are all present -- (reason for requiring them: players and server owners ought to -- be able to see who is responsible for a message) if(not(string.find(data.chat_msg_text, "%$NPC_NAME%$")) or not(string.find(data.chat_msg_text, "%$PLAYER_NAME%$")) or not(string.find(data.chat_msg_text, "%$OWNER_NAME%$"))) then yl_speak_up.show_fs(player, "msg", { input_to = "yl_speak_up:"..formspec_input_to, formspec = "size[9,2.5]".. "label[0.2,0.5;Error: Your chat message needs to contain ".. "the following\nplaceholders: $NPC_NAME$, ".. "$PLAYER_NAME$ and $OWNER_NAME$.\nThat way, other ".. "players will know who sent the message.]".. "button[1.5,2.0;2,0.9;back_from_error_msg;Back]"}) return end v[ "r_value" ] = data.chat_msg_text -- "Normal trade - one item(stack) for another item(stack).", -- 3 -- (only for actions) elseif(data.what and id_prefix == "a_" and data.what == 3) then -- remember which option was selected yl_speak_up.speak_to[pname].o_id = o_id -- do not switch target dialog (we are in edit mode) yl_speak_up.speak_to[pname].target_d_id = nil -- just to make sure that the trade_id is properly set... if(not(data.trade_id)) then yl_speak_up.show_fs(player, "msg", { input_to = "yl_speak_up:"..formspec_input_to, formspec = "size[9,2.5]".. "label[0.2,0.5;Error: Missing trade ID.]".. "button[1.5,2.0;2,0.9;back_from_error_msg;Back]"}) return end -- the button is called store_trade_simple instead of save_element in -- the trade simple function(s); we want to store a trade fields.store_trade_simple = true local res = yl_speak_up.input_add_trade_simple(player, "", fields) -- the above function sets: -- dialog.trades[ trade_id ] = {pay={ps},buy={bs}, d_id = d_id, o_id = o_id} -- store the trade as an action: local dialog = yl_speak_up.speak_to[pname].dialog if(res and dialog.trades and dialog.trades[ data.trade_id ]) then v[ "a_value" ] = data.trade_id v[ "a_pay" ] = dialog.trades[ data.trade_id ].pay v[ "a_buy" ] = dialog.trades[ data.trade_id ].buy v[ "a_on_failure" ] = "" if(data.action_failure_dialog) then local dialog = yl_speak_up.speak_to[pname].dialog local sorted_dialog_list = yl_speak_up.sort_keys(dialog.n_dialogs) v[ "a_on_failure" ] = sorted_dialog_list[ data.action_failure_dialog ] end end -- "The NPC gives something to the player (i.e. a quest item).", -- 4 -- "The player is expected to give something to the NPC (i.e. a quest item).", -- 5 -- (only for actions) elseif(data.what and id_prefix == "a_" and (data.what == 4 or data.what == 5)) then local trade_inv_list = "npc_gives" if(data.what == 5) then trade_inv_list = "npc_wants" end local trade_inv = minetest.get_inventory({type="detached", name="yl_speak_up_player_"..pname}) if(not(trade_inv) or trade_inv:is_empty( trade_inv_list )) then yl_speak_up.show_fs(player, "msg", { input_to = "yl_speak_up:"..formspec_input_to, formspec = "size[9,2.5]".. "label[0.2,0.5;Please insert an item first! Your NPC".. "needs\nto know what it shall give to the player.]".. "button[1.5,2.0;2,0.9;back_from_error_msg;Back]"}) return end v[ "a_on_failure" ] = "" if(data.action_failure_dialog) then local dialog = yl_speak_up.speak_to[pname].dialog local sorted_dialog_list = yl_speak_up.sort_keys(dialog.n_dialogs) v[ "a_on_failure" ] = sorted_dialog_list[ data.action_failure_dialog ] end -- change the node in the slot local stack = trade_inv:get_stack( trade_inv_list, 1) if(not(stack) or not(minetest.registered_items[ stack:get_name() ])) then yl_speak_up.show_fs(player, "msg", { input_to = "yl_speak_up:"..formspec_input_to, formspec = "size[9,2.5]".. "label[0.2,0.5;This item is unkown. Please use only known".. "items.]".. "button[1.5,2.0;2,0.9;back_from_error_msg;Back]"}) return end -- is this particular item blacklisted on this server? if(yl_speak_up.blacklist_action_quest_item[ stack:get_name() ]) then yl_speak_up.show_fs(player, "msg", { input_to = "yl_speak_up:"..formspec_input_to, formspec = "size[9,2.5]".. "label[0.2,0.5;Sorry. This item is blacklisted on this ".. "server.\nYou can't use it as a quest item.]".. "button[1.5,2.0;2,0.9;back_from_error_msg;Back]"}) return end local meta = stack:get_meta() -- what does the NPC want to give? v[ "a_value" ] = stack:get_name().." "..stack:get_count() -- for displaying as a background image data.item_string = v[ "a_value" ] if(data.what == 5) then -- try to reconstruct $PLAYER_NAME$ (may not always work) local item_was_for = meta:get_string("yl_speak_up:quest_item_for") local new_desc = meta:get_string("description") if(item_was_for and item_was_for ~= "") then new_desc = string.gsub(new_desc, item_was_for, "$PLAYER_NAME$") end data.item_desc = new_desc end -- set new description if there is one set (optional) if(data.item_desc and data.item_desc ~= "" and data.item_desc ~= "- none set -") then if(data.what == 4) then meta:set_string("description", data.item_desc) end v[ "a_item_desc" ] = data.item_desc end if(data.what == 5) then data.item_quest_id = meta:get_string("yl_speak_up:quest_id") end -- set special ID (optional) if(data.item_quest_id and data.item_quest_id ~= "" and data.item_quest_id ~= "- no item set -") then if(data.what == 4) then -- which player got this quest item? meta:set_string("yl_speak_up:quest_item_for", pname) -- include the NPC id so that we know which NPC gave it meta:set_string("yl_speak_up:quest_item_from", tostring(n_id)) -- extend quest_id by NPC id so that it becomes more uniq meta:set_string("yl_speak_up:quest_id", tostring(n_id).." "..tostring(data.item_quest_id)) end v[ "a_item_quest_id" ] = data.item_quest_id end if( v["a_item_quest_id"] and not(v[ "a_item_desc"]) and data.what == 4) then yl_speak_up.show_fs(player, "msg", { input_to = "yl_speak_up:"..formspec_input_to, formspec = "size[9,2.5]".. "label[0.2,0.5;You can't set a special quest ID without ".. "also changing\nthe description. The player would be ".. "unable to tell\nthe quest item and normal items ".. "apartapart.]".. "button[1.5,2.0;2,0.9;back_from_error_msg;Back]"}) return end local player_inv = player:get_inventory() if(not(player_inv:room_for_item("main", stack))) then yl_speak_up.show_fs(player, "msg", { input_to = "yl_speak_up:"..formspec_input_to, formspec = "size[9,2.5]".. "label[0.2,0.5;You have no room in your inventory for ".. "the example\nitem. Please make room so that it can be".. "given back to you!]".. "button[1.5,2.0;2,0.9;back_from_error_msg;Back]"}) return end player_inv:add_item("main", stack) trade_inv:remove_item(trade_inv_list, stack) -- just send a message that the save was successful and give the player time to -- take his items back show_save_msg = "size[9,2.5]".. "label[0.2,0.5;The information was saved successfully.\n".. "The item has been returned to your inventory.]".. "button[1.5,2.0;2,0.9;back_from_error_msg;Back]" -- "The player has to manually enter a password or passphrase or some other text.", -- 6 elseif(data.what and id_prefix == "a_" and data.what == 6) then if(not(data.quest_question)) then data.quest_question = "Your answer:" end v[ "a_question" ] = data.quest_question -- the player setting this up needs to provide the correct answer if(not(data.quest_answer)) then yl_speak_up.show_fs(player, "msg", { input_to = "yl_speak_up:"..formspec_input_to, formspec = "size[9,2.5]".. "label[0.2,0.5;Error: Please provide the correct answer!\n".. "The answer the player gives is checked against this.]".. "button[1.5,2.0;2,0.9;back_from_error_msg;Back]"}) return end v[ "a_value" ] = data.quest_answer if(not(data.action_failure_dialog)) then yl_speak_up.show_fs(player, "msg", { input_to = "yl_speak_up:"..formspec_input_to, formspec = "size[9,2.5]".. "label[0.2,0.5;Error: Please provide a target dialog if ".. "the player gives the wrong answer.]".. "button[1.5,2.0;2,0.9;back_from_error_msg;Back]"}) return end local dialog = yl_speak_up.speak_to[pname].dialog local sorted_dialog_list = yl_speak_up.sort_keys(dialog.n_dialogs) v[ "a_on_failure" ] = sorted_dialog_list[ data.action_failure_dialog ] -- "Call custom functions that are supposed to be overridden by the server.", -- 7 elseif(data.what and id_prefix == "a_" and data.what == 7) then v[ "a_value" ] = data.custom_param local sorted_dialog_list = yl_speak_up.sort_keys(dialog.n_dialogs) v[ "a_on_failure" ] = sorted_dialog_list[ data.action_failure_dialog ] end -- only save if something was actually selected if(v[ id_prefix.."value"]) then if(not(dialog.n_dialogs[d_id].d_options[o_id][ element_list_name ])) then dialog.n_dialogs[d_id].d_options[o_id][ element_list_name ] = {} end -- store the change in the dialog dialog.n_dialogs[d_id].d_options[o_id][ element_list_name ][ x_id ] = v -- clear up data yl_speak_up.speak_to[pname][ id_prefix.."id" ] = nil yl_speak_up.speak_to[pname][ tmp_data_cache ] = nil -- record this as a change, but do not save do disk yet table.insert(yl_speak_up.npc_was_changed[ n_id ], "Dialog "..tostring(d_id)..": "..element_desc.." "..tostring(x_id).. " added/changed for option "..tostring(o_id)..".") if(show_save_msg) then yl_speak_up.show_fs(player, "msg", { input_to = "yl_speak_up:"..formspec_input_to, formspec = show_save_msg}) return end -- TODO: when trying to save: save to disk as well? -- show the new/changed precondition yl_speak_up.show_fs(player, formspec_input_to, x_id) return end end -- selections in a dropdown menu (they trigger sending the formspec) -- select a general direction/type first -- but *not* when enter was pressed (enter sends them all) if(fields.select_what and not(fields.key_enter) and not(fields.store_item_name)) then local nr = table.indexof(check_what, fields.select_what) yl_speak_up.speak_to[pname][ tmp_data_cache ] = { what = nr } end -- select a subtype for the "a trade" selection if(fields.select_trade) then local nr = table.indexof(check_trade, fields.select_trade) yl_speak_up.speak_to[pname][ tmp_data_cache ].trade = nr end -- select a subtype for the inventory selection (player or NPC) if(fields.select_inv) then local nr = table.indexof(check_inv, fields.select_inv) yl_speak_up.speak_to[pname][ tmp_data_cache ].inv = nr end -- select data regarding a block if(fields.select_block) then local nr = table.indexof(check_block, fields.select_block) yl_speak_up.speak_to[pname][ tmp_data_cache ].block = nr end -- select data regarding a variable if(fields.select_variable) then -- get the list of available variables (with the same elements -- and the same sort order as when the dropdown was displayed) local var_list = get_sorted_player_var_list_function(pname) local nr = table.indexof(var_list, fields.select_variable) if(nr) then yl_speak_up.speak_to[pname][ tmp_data_cache ].variable = nr yl_speak_up.speak_to[pname][ tmp_data_cache ].variable_name = var_list[ nr ] end end -- select data regarding an operator if(fields.select_operator) then local nr = table.indexof(check_operator, fields.select_operator) yl_speak_up.speak_to[pname][ tmp_data_cache ].operator = nr end if(fields.select_on_failure) then -- in this case we really want the name of the target dialog yl_speak_up.speak_to[pname][ tmp_data_cache ].on_failure = fields.select_on_failure end if(fields.select_on_action_failure and data and data.what and id_prefix == "a_") then local dialog = yl_speak_up.speak_to[pname].dialog local sorted_dialog_list = yl_speak_up.sort_keys(dialog.n_dialogs) local nr = table.indexof(sorted_dialog_list, fields.select_on_action_failure) yl_speak_up.speak_to[pname][ tmp_data_cache ].action_failure_dialog = nr end -- new variables have to be added (and deleted) somewhere after all if(fields.manage_variables) then -- remember which formspec we are comming from yl_speak_up.speak_to[pname][ "working_at" ] = formspec_input_to yl_speak_up.show_fs(player, "manage_variables") return end -- the player wants to change/edit a precondition or effect if(not(fields.back) and (fields.change_element or fields.select_what or fields.select_trade or fields.select_inv or fields.select_block or fields.select_variable or fields.select_operator or fields.select_on_failure or fields.select_on_action_failure or fields.back_from_error_msg or fields.store_item_name or was_changed or fields.key_enter or fields.quit -- return was pressed or fields.key_enter_field)) then yl_speak_up.show_fs(player, formspec_input_to) return end -- go back to the edit option dialog yl_speak_up.show_fs(player, "edit_option_dialog", {n_id = n_id, d_id = d_id, o_id = o_id, caller= formspec_input_to}) end yl_speak_up.get_fs_edit_option_related = function(player, table_click_result, id_prefix, element_list_name, max_entries_allowed, element_desc, tmp_data_cache, what_do_you_want_txt, values_what, values_operator, values_block, values_trade, values_inv, check_what, check_operator, check_block, check_trade, check_inv, get_sorted_player_var_list_function, show_element_function, table_of_name, text_variable, text_select_operator, text_select_value, text_block_position) if(not(player)) then return "" end local pname = player:get_player_name() -- what are we talking about? local n_id = yl_speak_up.speak_to[pname].n_id local d_id = yl_speak_up.speak_to[pname].d_id local o_id = yl_speak_up.speak_to[pname].o_id local x_id = yl_speak_up.speak_to[pname][ id_prefix.."id" ] -- this only works in edit mode if(not(n_id) or yl_speak_up.edit_mode[pname] ~= n_id) then return "size[1,1]label[0,0;You cannot edit this NPC.]" end local dialog = yl_speak_up.speak_to[pname].dialog if(not(dialog) or not(dialog.n_dialogs) or not(dialog.n_dialogs[d_id]) or not(dialog.n_dialogs[d_id].d_options) or not(dialog.n_dialogs[d_id].d_options[o_id])) then return "size[4,1]label[0,0;Dialog option does not exist.]" end local elements = dialog.n_dialogs[d_id].d_options[o_id][ element_list_name ] if(not(elements)) then elements = {} end -- did we arrive here through clicking on an element in the dialog edit options menu? if(table_click_result or elements[ table_click_result ]) then if(not(elements[ table_click_result ])) then -- which element has the player selected? local sorted_key_list = yl_speak_up.sort_keys(elements) local selected = minetest.explode_table_event(table_click_result) -- use "new" if nothing fits x_id = "new" if((selected.type == "CHG" or selected.type == "DLC") and selected.row <= #sorted_key_list) then x_id = sorted_key_list[ selected.row ] end if( x_id == "new" and #sorted_key_list >= max_entries_allowed) then return "size[9,1.5]".. "label[0.2,0.0;There are only up to ".. minetest.formspec_escape(yl_speak_up.max_result_effects).. " "..element_desc.."s allowed per dialog option.]".. "button[2.0,0.8;1.0,0.9;back;Back]" end else -- allow to directly specify a x_id to show x_id = table_click_result end -- store which element we are talking about yl_speak_up.speak_to[pname][ id_prefix.."id" ] = x_id -- nothing selected yet yl_speak_up.speak_to[pname][ tmp_data_cache ] = nil -- display the selected element if(x_id ~= "new") then return "formspec_version[3]".. "size[20,3]".. "bgcolor[#00000000;false]".. "label[0.2,0.5;Selected "..element_desc..":]".. "tablecolumns[text;color,span=1;text;text]".. "table[0.2,0.8;19.6,0.7;"..table_of_name..";".. minetest.formspec_escape(elements[ x_id ][ id_prefix.."id"]).. ",#FFFF00,".. minetest.formspec_escape(elements[ x_id ][ id_prefix.."type"]).. ",".. minetest.formspec_escape( show_element_function(elements[ x_id ]))..";0]".. "button[2.0,1.8;1.5,0.9;delete_element;Delete]".. "button[4.0,1.8;1.5,0.9;change_element;Change]".. "button[6.0,1.8;5.5,0.9;back;Back to edit dialog option \"".. tostring(o_id).."\"]" end end -- did the player get here through punching a block in the meantime? local block_pos = yl_speak_up.speak_to[pname].block_punched yl_speak_up.speak_to[pname].block_punched = nil local data = yl_speak_up.speak_to[pname][ tmp_data_cache ] if(not(data) or not(data.what)) then data = { what = 1} end -- fallback if(not(x_id)) then x_id = "new" end -- does the element exist already? if so: use the existing values as presets for data -- (so that the element can be edited) -- does kind of the opposite than the saving of values starting in line 323 of this file if(x_id ~= "new" and data.what == 1 and elements[ x_id ]) then local e = elements[ x_id ] if( id_prefix == "r_" and e[ "r_type" ] == "dialog") then -- dialog effects cannot be edited this way return "size[9,2]".. "label[0.2,0.5;Effects of the type \"dialog\" cannot be edited this way.\n".. "Use the edit options or dialog menu to change the target dialog.]".. "button[1.5,1.5;2,0.9;back_from_cannot_be_edited;Back]" end if( id_prefix == "p_" and e[ "p_type" ] == "item") then -- the staff-based item precondition can be translated to an editable -- inventory precondition which is equal e[ "p_type" ] = "player_inv" e[ "p_itemstack" ] = e[ "p_value"] e[ "p_value" ] = "inv_contains" end data.what = table.indexof(values_what, e[ id_prefix.."type" ]) if(data.what == -1) then data.what = 1 -- a state/variable (precondition + effect) elseif(data.what == 2 and id_prefix ~= "a_") then data.operator = math.max(1,table.indexof(values_operator, e[ id_prefix.."operator" ])) data.var_cmp_value = e[ id_prefix.."var_cmp_value" ] data.variable_name = e[ id_prefix.."variable" ] local var_list = get_sorted_player_var_list_function(pname) data.variable = math.max(1, table.indexof(var_list, e[ id_prefix.."variable"])+1) -- a block (precondition + effect) elseif(data.what == 3 and id_prefix ~= "a_") then data.block = math.max(1,table.indexof(values_block, e[ id_prefix.."value" ])) data.node_data = {} data.node_data.data = e[ id_prefix.."node" ] data.node_data.param2 = e[ id_prefix.."param2" ] data.block_pos = {x=e[ id_prefix.."pos" ].x, y=e[ id_prefix.."pos" ].y, z=e[ id_prefix.."pos" ].z} -- the block below was punched if(id_prefix == "p_" and data.block == 5) then data.block_pos.y = data.block_pos.y - 1 end -- a trade (precondition) elseif(data.what == 4 and id_prefix == "p_") then data.trade = math.max(1,table.indexof(values_trade, e[ "p_value" ])) -- an invenetory (precondition) elseif((data.what == 5 or data.what == 6) and id_prefix == "p_") then data.inv = math.max(1,table.indexof(values_inv, e["p_value"])) data.inv_stack_name = e[ "p_itemstack" ] -- crafting (effect) elseif(data.what and id_prefix == "r_" and data.what == 4) then -- those items can at least be shown as background images data.craftresult = e[ "r_value" ] data.craft_grid = e[ "r_craft_grid"] -- on failure (effect) elseif(data.what and id_prefix == "r_" and data.what == 5) then data.on_failure = e[ "r_value" ] -- send a chat message (effect) elseif(data.what and id_prefix == "r_" and data.what == 6) then data.chat_msg_text = e[ "r_value" ] -- normal trade (action) elseif(data.what and id_prefix == "a_" and data.what == 3) then data.trade_id = e[ "a_value" ] -- use as background images if(dialog and dialog.trades and dialog.trades[ data.trade_id ]) then data.pay = dialog.trades[ data.trade_id ].pay[1] data.buy = dialog.trades[ data.trade_id ].buy[1] end local sorted_dialog_list = yl_speak_up.sort_keys(dialog.n_dialogs) data.action_failure_dialog = math.max(1, table.indexof(sorted_dialog_list, e[ "a_on_failure" ])) -- npc_gives/npc_wants (action) elseif(data.what and id_prefix == "a_" and (data.what == 4 or data.what == 5)) then local sorted_dialog_list = yl_speak_up.sort_keys(dialog.n_dialogs) data.action_failure_dialog = math.max(1, table.indexof(sorted_dialog_list, e[ "a_on_failure" ])) -- data.item_string is used to show a background image data.item_string = e[ "a_value"] -- stack name and count (as string) data.item_desc = e[ "a_item_desc" ] data.item_quest_id = e[ "a_item_quest_id" ] local sorted_dialog_list = yl_speak_up.sort_keys(dialog.n_dialogs) data.action_failure_dialog = math.max(1, table.indexof(sorted_dialog_list, e[ "a_on_failure" ])) -- entering password (action) elseif(data.what and id_prefix == "a_" and data.what == 6) then data.quest_question = e[ "a_question" ] data.quest_answer = e[ "a_value" ] local sorted_dialog_list = yl_speak_up.sort_keys(dialog.n_dialogs) data.action_failure_dialog = math.max(1, table.indexof(sorted_dialog_list, e[ "a_on_failure" ])) -- custom function (action) elseif(data.what and id_prefix == "a_" and data.what == 7) then data.custom_param = e[ "a_value" ] local sorted_dialog_list = yl_speak_up.sort_keys(dialog.n_dialogs) data.action_failure_dialog = math.max(1, table.indexof(sorted_dialog_list, e[ "a_on_failure" ])) -- give_item/take_item elseif(data.what and id_prefix == "r_" and data.what >= 7 and data.what <= 8) then data.inv_stack_name = e[ "r_value" ] or "" -- move elseif(data.what and id_prefix == "r_" and data.what == 9) then if(e[ "r_value"] and type(e[ "r_value" ]) == "string") then local pos = minetest.string_to_pos(e[ "r_value" ]) if(pos) then data.move_to_x = pos.x data.move_to_y = pos.y data.move_to_z = pos.z end end end -- write that data back yl_speak_up.speak_to[pname][ tmp_data_cache ] = data end local save_button = "button[5.0,11.8;1,0.7;save_element;Save]" local formspec = "formspec_version[3]".. "size[20,13]".. "label[5,0.5;Edit "..element_desc.." \""..minetest.formspec_escape(x_id).."\"]".. "label[0.2,1.5;"..what_do_you_want_txt.."]".. "label[0.2,2.0;Something regarding...]".. "dropdown[4.0,1.8;14.0,0.6;select_what;".. table.concat(check_what, ",")..";".. tostring(data.what)..";]".. "button[3.0,11.8;1,0.7;back;Abort]" if(id_prefix ~= "a_") then formspec = formspec.. "label[1,10.5;If you are unsure if your setup of pre(C)onditions and (Ef)fects ".. "works as intended,\ntype \"/npc_talk_debug "..tostring(n_id).."\" ".. "in chat in order to enter debug mode. You can leave it with ".. "\"/npc_talk_debug off\".]" end -- "an internal state (i.e. of a quest)", -- 2 -- (state is the second offered option in both preconditions and effects list) if(data.what and data.what == 2 and id_prefix ~= "a_") then if(not(data.variable) or data.variable < 1) then data.variable = 1 -- not enough selected yet for saving save_button = "" elseif(not(data.operator) or data.operator == 1) then data.operator = 1 save_button = "" end local field_for_value = "field[11.2,4.8;7.0,0.6;var_cmp_value;;".. minetest.formspec_escape(data.var_cmp_value or "- enter value -").."]" -- do not show value input field for unary operators -- (unary operators are diffrent for prerequirements and effects) if(not(data.operator) or (id_prefix == "p_" and (data.operator == 1 or (data.operator>=8 and data.operator<11))) -- "unset", "set_to_current_time" or (id_prefix == "r_" and (data.operator == 3 or data.operator == 4))) then field_for_value = "label[11.2,5.1;- not used for this operator -]" end -- the list of available variables needs to be extended with the ones -- the player has read access to, and the order has to be constant -- (because dropdown just returns an index) local var_list = get_sorted_player_var_list_function(pname) local var_list_text = "- please select -" for i, v in ipairs(var_list) do local parts = string.split(v, " ") local var_name = v if(parts and parts[1] and parts[1] == pname) then table.remove(parts, 1) var_name = table.concat(parts, " ") end var_list_text = var_list_text..","..minetest.formspec_escape(tostring(var_name)) end formspec = formspec.. "label[0.2,3.3;"..text_variable.."]".. "label[0.2,4.3;Name of variable:]".. "dropdown[0.2,4.8;6.5,0.6;select_variable;".. var_list_text..";".. tostring(data.variable)..";]".. "label[7.0,4.3;"..text_select_operator.."]".. "dropdown[7.0,4.8;4.0,0.6;select_operator;".. table.concat(check_operator, ",")..";".. tostring(data.operator)..";]".. "label[11.2,4.3;"..text_select_value.."]".. field_for_value.. "button[0.2,6.0;4.0,0.6;manage_variables;Manage variables]".. "hypertext[1.2,7.0;16.0,2.5;some_text;".. "Note: Each variable is player-specific and will be set and ".. "checked for the player that currently talks to your NPC.\n".. "Note: You can set a variable to the current time in an effect. ".. "After that, use a precondition to check if that variable was set \"more ".. "than x seconds ago\" or \"less than x seconds ago\". This can be ".. "useful for prevending your NPC from handing out the same quest item again ".. "too quickly (players are inventive and may use your quest item for their ".. "own needs).\n]" -- "a block somewhere", -- 3 -- (block is the third offered option in both preconditions and effects list) elseif(data.what and data.what == 3 and id_prefix ~= "a_") then local block_pos_str = "- none set -" local node = {name = "- unknown -", param2 = "- unkown -"} if(not(block_pos) and data and data.block_pos) then block_pos = data.block_pos end local error_is_protected = "" if(block_pos) then -- store for later usage data.block_pos = block_pos local tmp_pos = {x=block_pos.x, y=block_pos.y, z=block_pos.z} -- "I can't punch it. The block is as the block *above* the one I punched.", -- (only valid for preconditions; not for effects - because the player and -- his NPC need to be able to build there) if(data.block and id_prefix == "p_" and data.block == 5) then tmp_pos.y = block_pos.y + 1 end -- effects (and, likewise, preconditions): the player at least has to be able to -- build at that position - check that if(minetest.is_protected(tmp_pos, pname)) then error_is_protected = "label[0.2,7.8;Error: ".. "The position you punched is protected. It cannot be used by ".. "your NPC for checks or building. Please select a diffrent block!]" block_pos = nil data.block_pos = nil else block_pos_str = minetest.pos_to_string(tmp_pos) node = minetest.get_node_or_nil(tmp_pos) if(not(node)) then node = {name = "- unknown -", param2 = "- unkown -"} end -- "There shall be air instead of this block.", -- (only valid for preconditions) if(data.block and id_prefix == "p_" and data.block == 3) then node = {name = "air", param2 = 0} end -- cache that (in case a sapling grows or someone else changes it) data.node_data = node end end if(node.name == "- unknown -") then save_button = "" end if(not(data.block) or data.block == 1) then data.block = 1 -- not enough selected yet for saving save_button = "" end formspec = formspec.. "label[0.2,3.3;"..text_block_position.."]".. "dropdown[4.0,3.5;16.0,0.6;select_block;".. table.concat(check_block, ",")..";".. tostring(data.block)..";]".. "label[0.2,4.8;Position of the block:]".. "label[4.0,4.8;"..minetest.formspec_escape(block_pos_str).."]".. "label[0.2,5.8;Name of block:]".. "label[4.0,5.8;"..minetest.formspec_escape(node.name).."]".. "label[0.2,6.8;Orientation (param2):]".. "label[4.0,6.8;"..minetest.formspec_escape(node.param2).."]".. "button_exit[10.0,5.5;4.0,0.7;select_block_pos;Set position of block]".. "tooltip[select_block_pos;Click on this button to select a block.\n".. "This menu will close and you will be asked to punch\n".. "the block at the position you want to check or change.\n".. "After punching it, you will be returned to this menu.]".. error_is_protected -- "a trade", -- 4 -- (trade - only for preconditions; effects have something else here) elseif(data.what and id_prefix == "p_" and data.what == 4) then if(not(data.trade) or data.trade == 1) then data.trade = 1 -- not enough selected yet for saving save_button = "" end formspec = formspec.. "label[0.2,3.3;If the action is a trade, the following shall be true:]".. "dropdown[4.0,3.5;16.0,0.6;select_trade;".. table.concat(check_trade, ",")..";".. tostring(data.trade)..";]" -- "the inventory of the player", -- 5 -- "the inventory of the NPC", -- 6 -- (inventory - only for preconditions; effects have something else here) elseif(data.what and id_prefix == "p_" and data.what >= 5 and data.what <= 6) then if(not(data.inv) or data.inv == 1) then data.inv = 1 -- not enough selected yet for saving save_button = "" end formspec = formspec.. "label[0.2,3.0;The following shall be true about the inventory:]".. "dropdown[4.0,3.2;16.0,0.6;select_inv;".. table.concat(check_inv, ",")..";".. tostring(data.inv)..";]".. "label[0.2,4.2;Name of the item(stack):]".. "field[4.0,4.0;16.0,0.6;inv_stack_name;;"..(data.inv_stack_name or "").."]".. "tooltip[inv_stack_name;Enter name of the block and amount.\n".. "Example: \"default:apple 3\" for three apples,\n".. " \"farming:bread\" for a bread.]".. "label[0.2,5.7;Or put the item in here\nand click on \"Store\":]".. "button[5.5,5.5;1.5,0.9;store_item_name;Store]".. "list[detached:yl_speak_up_player_"..pname..";npc_wants;4.0,5.5;1,1;]".. "label[8,4.9;Your inventory:]".. "list[current_player;main;8,5.3;8,4;]" -- "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 elseif(data.what and id_prefix == "r_" and (data.what == 7 or data.what == 8)) then local text = "The following item shall be created out of thin air and added to the ".. "player's inventory:" if(data.what == 8) then text = "The following item shall be removed from the player's inventory and ".. "be destroyed:" end formspec = formspec.. "label[0.2,3.0;"..text.."]".. "label[0.2,3.5;Note: You can *save* this effect only if you have the ".. "\"npc_master\" priv!]".. "label[0.2,4.2;Name of the item(stack):]".. "field[4.0,4.0;16.0,0.6;inv_stack_name;;"..(data.inv_stack_name or "").."]".. "tooltip[inv_stack_name;Enter name of the block and amount.\n".. "Example: \"default:apple 3\" for three apples,\n".. " \"farming:bread\" for a bread.]".. "label[0.2,5.7;Or put the item in here\nand click on \"Store\":]".. "button[5.5,5.5;1.5,0.9;store_item_name;Store]".. "list[detached:yl_speak_up_player_"..pname..";npc_wants;4.0,5.5;1,1;]".. "label[8,4.9;Your inventory:]".. "list[current_player;main;8,5.3;8,4;]" -- "move the player to a given position (requires npc_master priv)", -- 9 elseif(data.what and id_prefix == "r_" and data.what == 9) then formspec = formspec.. "label[0.2,3.0;Move the player to this position:]".. "label[0.2,3.5;Note: You can *save* this effect only if you have the ".. "\"npc_master\" priv!]".. "label[0.2,5.3;X:]".. "label[3.7,5.3;Y:]".. "label[7.2,5.3;Z:]".. "field[0.7,5.0;2.0,0.6;move_to_x;;"..(data.move_to_x or "").."]".. "field[4.2,5.0;2.0,0.6;move_to_y;;"..(data.move_to_y or "").."]".. "field[7.7,5.0;2.0,0.6;move_to_z;;"..(data.move_to_z or "").."]" -- "NPC crafts something", -- 4 -- (craft - only for effects - not for preconditions) elseif(data.what and id_prefix == "r_" and data.what == 4) then local bg_img = "" if(data and data.craftresult and data.craft_grid) then bg_img = "item_image[5.95,4.90;0.7,0.7;"..tostring(data.craftresult).."]" for i, v in ipairs(data.craft_grid) do if(v and v ~= "") then bg_img = bg_img.."item_image[".. tostring(1.15 + ((i-1)%3)*1.25)..",".. tostring(3.65 + math.floor((i-1)/3)*1.30).. ";0.7,0.7;"..tostring(v).."]" end end end formspec = formspec.. "label[8,2.6;Your invnetory:]".. "list[current_player;main;8,3;8,4;]".. "label[1,3.1;Your craft grid:]".. "list[current_player;craft;1,3.5;3,3;]".. "list[current_player;craftpreview;5.8,4.75;1,1;]".. "image[4.6,4.8;1,1;gui_furnace_arrow_bg.png^[transformR270]".. "label[1,8.0;Use your craft grid to show your NPC what to craft ".. "and how. Click on \"Save\" to save.]".. bg_img -- "go to other dialog if the *previous* effect failed", -- 5 -- (on_failure - only for effects - not for preconditions) elseif(data.what and id_prefix == "r_" and data.what == 5) then local dialog = yl_speak_up.speak_to[pname].dialog local sorted_dialog_list = yl_speak_up.sort_keys(dialog.n_dialogs) local nr = 1 if(not(data) or not(data.on_failure)) then save_button = "" else nr = table.indexof(sorted_dialog_list, data.on_failure) end local on_failure_dialog = "" if(dialog and dialog.n_dialogs and dialog.n_dialogs[ data.on_failure ]) then on_failure_dialog = "label[0.2,5.5;This will switch to dialog "..tostring(data.on_failure)..":]".. "hypertext[1.2,6.0;19.6,2.5;d_text;".. minetest.formspec_escape(dialog.n_dialogs[ data.on_failure ].d_text or "?").. "\n]" end formspec = formspec.. "label[0.2,3.3;If the *previous* effect failed,]".. "label[0.2,3.8;switch to the following dialog:]".. "dropdown[5.0,3.5;6.5,0.6;select_on_failure;".. table.concat(sorted_dialog_list, ",")..";".. tostring(nr)..";]".. on_failure_dialog -- "send a chat message to all players" -- 6 elseif(data.what and id_prefix == "r_" and data.what == 6) then local default_text = "$NPC_NAME$ (owned by $OWNER_NAME$) announces: $PLAYER_NAME$ ".. "- example; please enter the text -" if(not(data)) then data = {} end formspec = formspec.. "label[0.2,3.3;Send the following chat message to *all* players:]".. "label[0.2,4.1;Message:]".. "field[2.0,3.8;16.0,0.6;chat_msg_text;;".. minetest.formspec_escape( data.chat_msg_text or default_text).."]".. "label[0.2,5.3;Note: Your chat message needs to contain the following placeholders,".. " which will be replaced automaticly like in dialog texts:".. "\n$NPC_NAME$, $PLAYER_NAME$ and $OWNER_NAME$.]" -- "Normal trade - one item(stack) for another item(stack).", -- 3 elseif(data.what and id_prefix == "a_" and data.what == 3) then local dialog = yl_speak_up.speak_to[pname].dialog local d_id = yl_speak_up.speak_to[pname].d_id local o_id = yl_speak_up.speak_to[pname].o_id if(not(data.trade_id)) then data.trade_id = tostring(d_id).." "..tostring(o_id) end -- show the player which trade is stored local bg_img = "" if(data and data.buy and data.pay) then bg_img = "item_image[2.15,4.35;0.7,0.7;"..tostring(data.buy).."]".. "item_image[5.15,4.35;0.7,0.7;"..tostring(data.pay).."]" end yl_speak_up.speak_to[pname].trade_id = data.trade_id formspec = formspec.. "label[8,2.6;Your invnetory:]".. "list[current_player;main;8,3;8,4;]".. "label[0.2,3.1;Configure trade with "..minetest.formspec_escape(dialog.n_npc)..":]".. "label[0.5,3.8;The customer pays:]".. -- show the second slot of the setup inventory in the detached player's inv "list[detached:yl_speak_up_player_"..pname..";setup;2,4.2;1,1;]".. "image[3.5,4.2;1,1;gui_furnace_arrow_bg.png^[transformR270]".. "label[4.0,3.8;"..minetest.formspec_escape(dialog.n_npc or "?").." sells:]".. -- show the second slot of said inventory "list[detached:yl_speak_up_player_"..pname..";setup;5,4.2;1,1;1]".. bg_img.. yl_speak_up.set_on_action_failure_dialog(pname, data, "The player shall trade at least once.") -- "The NPC gives something to the player (i.e. a quest item).", -- 4 -- (only for actions) elseif(data.what and id_prefix == "a_" and data.what == 4) then if(not(data)) then data = {} end local bg_img = "" if(data and data.item_string) then bg_img = "item_image[2.15,3.65;0.7,0.7;"..tostring(data.item_string).."]" end formspec = formspec.. "label[8,2.6;Your inventory:]".. "list[current_player;main;8,3;8,4;]".. "label[1,3.1;"..minetest.formspec_escape(dialog.n_npc or "?").." gives:]".. "list[detached:yl_speak_up_player_"..pname..";npc_gives;2,3.5;1,1;]".. "label[3.2,4.0;".. minetest.formspec_escape( data.item_node_name or "- no item set -").."]".. "label[0.2,5.6;Set a description to turn the item into a special\n".. "quest item. Set a special ID (short text) so that\n".. "the player cannot create a fake item. Click on \n".. "\"Save\" to apply the changes.\n".. "You can use placeholders like $PLAYER_NAME$ etc.]".. "label[0.2,8.3;Special ID to set:]".. "field[3.2,8.0;14.5,0.6;action_item_quest_id;;".. minetest.formspec_escape( data.item_quest_id or "- none set -").."]".. "tooltip[action_item_quest_id;".. "Set this to a text that helps *you* to remember what this\n".. "special quest item is for (i.e. \"quest_deliver_augusts_".. "letter\").\n".. "The ID will be extended with the ID of the NPC and the\n".. "name of the player who got this item from the NPC.]".. "label[0.2,9.0;Description to set:]".. "field[3.2,8.7;14.5,0.6;action_item_desc;;".. minetest.formspec_escape( data.item_desc or "- no item set -").."]".. "tooltip[action_item_desc;".. "Set this to a text that helps the *player* to remember what\n".. "this special quest item is for (i.e. \"Letter from August to\n".. "Frederike\" for a piece of paper).\n".. "This description is shown in the inventory on mouseover.]".. bg_img.. yl_speak_up.set_on_action_failure_dialog(pname, data, "The player shall take this offered item.") -- "The player is expected to give something to the NPC (i.e. a quest item).", -- 5 -- (only for actions) elseif(data.what and id_prefix == "a_" and data.what == 5) then if(not(data)) then data = {} end local bg_img = "" if(data and data.item_string) then bg_img = "item_image[2.15,3.65;0.7,0.7;"..tostring(data.item_string).."]" end formspec = formspec.. "label[8,2.6;Your inventory:]".. "list[current_player;main;8,3;8,4;]".. "label[1,3.1;"..minetest.formspec_escape(dialog.n_npc or "?").." wants:]".. "list[detached:yl_speak_up_player_"..pname..";npc_wants;2,3.5;1,1;]".. "label[3.2,4.0;".. minetest.formspec_escape( data.item_node_name or "- no item set -").."]".. "label[0.2,6.1;If you want a special ID and description, create\n".. "those via the \"NPC gives something to the player\"\n".. "menu option first and insert that item here. Don't\n".. "use other placeholders than $PLAYER_NAME$ for this!]".. "label[0.2,8.3;Expected special ID:]".. "label[4.0,8.3;".. minetest.formspec_escape( data.item_quest_id or "- none set -").."]".. "label[0.2,9.0;Expected description:]".. "label[4.0,9.0;".. minetest.formspec_escape( data.item_desc or "- none set -").."]".. bg_img.. yl_speak_up.set_on_action_failure_dialog(pname, data, "The player shall give the NPC this item.") -- "The player has to manually enter a password or passphrase or some other text.", -- 6 -- (only for actions) elseif(data.what and id_prefix == "a_" and data.what == 6) then if(not(data)) then data = {} end formspec = formspec.. "label[0.2,3.3;What to ask the player and which answer to expect:]".. "label[0.2,4.0;Question to show:]".. "field[4.0,3.8;10.0,0.6;quest_question;;".. minetest.formspec_escape( data.quest_question or "Your answer:").."]".. "label[0.2,5.0;Expected answer:]".. "field[4.0,4.8;10.0,0.6;quest_answer;;".. minetest.formspec_escape( data.quest_answer or "- Insert the correct answer here -").."]".. "tooltip[quest_question;".. "This is just a short text that will be shown to remind\n".. "the player what he is asked for. Most of the question\n".. "ought to be part of the normal dialog of the NPC.]".. "tooltip[quest_answer;".. "The correct answer will not be shown to the player.\n".. "What the player enters will be compared to this\n".. "correct value.]".. "tooltip[select_on_action_failure;".. "If the player gives the wrong answer, you can show him\n".. "a diffrent target dialog (i.e. with text \"No, that answer\n".. "was wrong, but please try again!\"). In such a case the\n".. "effects/results of the current dialog option are *not*\n".. "executed.]".. yl_speak_up.set_on_action_failure_dialog(pname, data, "The player shall enter the correct answer.") -- "Call custom functions that are supposed to be overridden by the server.", -- 7 -- (only for actions) elseif(data.what and id_prefix == "a_" and data.what == 7) then if(not(data)) then data = {} end formspec = formspec.. "label[0.2,3.3;Note: Calling a custom function will require direct support ".. "from the server.]".. "label[0.2,4.0;Parameter for custom function:]".. "field[6.0,3.7;10.0,0.6;custom_param;;".. minetest.formspec_escape( data.custom_param or "- Insert a text that is passed on to your function here -").."]".. "tooltip[custom_param;".. "The custom parameter may help whoever implements the\n".. "custom function to more easily see what it belongs to.\n".. "Dialog and option ID are also passed as parameters.]".. "tooltip[select_on_action_failure;".. "If the player gives the wrong answer, you can show him\n".. "a diffrent target dialog (i.e. with text \"No, that answer\n".. "was wrong, but please try again!\"). In such a case the\n".. "effects/results of the current dialog option are *not*\n".. "executed.]".. yl_speak_up.set_on_action_failure_dialog(pname, data, "The player shall click on the right button.") end return formspec..save_button end -- helper function yl_speak_up.set_on_action_failure_dialog = function(pname, data, instruction) local dialog = yl_speak_up.speak_to[pname].dialog local nr = 1 if(data and data.action_failure_dialog) then nr = data.action_failure_dialog + 1 end local sorted_dialog_list = yl_speak_up.sort_keys(dialog.n_dialogs) local on_failure_dialog = "label[0.2,9.9;"..tostring(instruction).." If he doesn't, go to dialog:]".. "dropdown[11.0,9.6;2.8,0.6;select_on_action_failure;".. "- current one -,".. table.concat(sorted_dialog_list, ",")..";"..tostring(nr)..";]" if(nr and nr > 1) then local failure_d_id = sorted_dialog_list[ nr - 1] on_failure_dialog = on_failure_dialog.. "hypertext[1.2,10.4.0;19.6,1.0;d_text;".. minetest.formspec_escape(dialog.n_dialogs[ failure_d_id ].d_text or "?").. "\n]" end return on_failure_dialog end