-- TODO: just for the apple quest...needs to be deleted later on yl_sokomine = {} yl_sokomine["q_apple_explorer"] = {} -- if player has npc_talk_owner priv AND is owner of this particular npc: -- chat option: "I am your owner. I have new orders for you. -- -> enters edit mode -- when edit_mode has been enabled, the following chat options are added to the options: -- chat option: "Add new answer/option to this dialog." -- -> adds a new aswer/option -- chat option: "That was all. I'm finished with giving you new orders. Remember them!" -- -> ends edit mode -- -- TODO: allow owner to mute/unmute npc (would be bad if players can already see what is going -- to happen while the owner creates a long quest) -- TODO: log changes to the npc in the server logfile --### -- Init --### -- store if the player is editing a particular NPC; format: yl_speak_up.edit_mode[pname] = npc_id yl_speak_up.edit_mode = {} -- changes applied in edit_mode are applied immediately - but not immediately stored to disk -- (this gives the players a chance to back off in case of unwanted changes) yl_speak_up.npc_was_changed = {} -- self (the npc as such) is rarely passed on to any functions; in order to be able to check if -- the player really owns the npc, we need to have that data available; -- format: yl_speak_up.npc_owner[ npc_id ] = owner_name yl_speak_up.npc_owner = {} function yl_speak_up.init_mob_table() return false end minetest.register_on_leaveplayer( function(player) yl_speak_up.speak_to[player:get_player_name()] = nil yl_speak_up.edit_mode[player:get_player_name()] = nil end ) minetest.register_on_joinplayer( function(player) yl_speak_up.speak_to[player:get_player_name()] = nil yl_speak_up.edit_mode[player:get_player_name()] = nil end ) --### -- Debug --### yl_speak_up.debug = true local function say(text) if yl_speak_up.debug then --minetest.chat_send_all(text) minetest.log("action", "[MOD] yl_speak_up: " .. text) end end --### -- Helpers --### local function get_prerequisite_types() --local t_gpt = {"item", "quest", "function", "auto", "delete"} local t_gpt = {"item", "function", "delete"} local s_gpt = "" for _, v in pairs(t_gpt) do s_gpt = s_gpt .. v .. "," end s_gpt = s_gpt:sub(1, -2) -- cut last comma return s_gpt, t_gpt end local function get_result_types() --local t_grt = {"dialog", "give_item", "quest", "function", "auto", "delete"} local t_grt = {"dialog","give_item","take_item","move","function","delete"} local s_grt = "" for _, v in pairs(t_grt) do s_grt = s_grt .. v .. "," end s_grt = s_grt:sub(1, -2) -- cut last comma return s_grt, t_grt end local function get_number_from_id(any_id) return string.split(any_id, "_")[2] end local function save_path(n_id) return yl_speak_up.worldpath .. yl_speak_up.path .. DIR_DELIM .. n_id .. ".json" end local function get_error_message() local formspec = { "formspec_version[3]", "size[13.4,8.5]", "bgcolor[#FF0000]", "label[0.2,0.35;Please save a NPC file first]", "button_exit[0.2,7.7;3,0.75;button_back;Back]" } return table.concat(formspec, "") end local function find_next_id(t) local start_id = 1 if t == nil then return start_id end local keynum = 1 for k, _ in pairs(t) do local keynum = tonumber(get_number_from_id(k)) if keynum >= start_id then start_id = keynum + 1 end end return start_id end local function sanitize_sort(options, value) local retval = value if value == "" or value == nil or tonumber(value) == nil then local temp = 0 for k, v in pairs(options) do if v.o_sort ~= nil then if tonumber(v.o_sort) > temp then temp = tonumber(v.o_sort) end end end retval = tostring(temp + 1) end return retval end --### --Load and Save --### local function save_dialog(n_id, dialog) if type(n_id) ~= "string" or type(dialog) ~= "table" then return false end local p = save_path(n_id) local content = minetest.write_json(dialog) return minetest.safe_file_write(p, content) end local function load_dialog(n_id) -- returns the saved dialog local p = save_path(n_id) local file, err = io.open(p, "r") if err then return {} end io.input(file) local content = io.read() local dialog = minetest.parse_json(content) io.close(file) if type(dialog) ~= "table" then dialog = {} end return dialog end local function fields_to_dialog(pname, fields) local n_id = yl_speak_up.speak_to[pname].n_id local dialog = load_dialog(n_id) local save_d_id = "" if next(dialog) == nil then -- No file found. Let's create the basic values dialog = {} dialog.n_dialogs = {} end if dialog.n_dialogs == nil or next(dialog.n_dialogs) == nil then --No dialogs found. Let's make a table dialog.n_dialogs = {} end if fields.d_text ~= "" then -- If there is dialog text, then save new or old dialog if fields.d_id == yl_speak_up.text_new_dialog_id then --New dialog -- -- Find highest d_id and increase by 1 save_d_id = "d_" .. find_next_id(dialog.n_dialogs) -- Initialize empty dialog dialog.n_dialogs[save_d_id] = {} else -- Already existing dialog save_d_id = fields.d_id end -- Change dialog dialog.n_dialogs[save_d_id].d_id = save_d_id dialog.n_dialogs[save_d_id].d_type = "text" dialog.n_dialogs[save_d_id].d_text = fields.d_text dialog.n_dialogs[save_d_id].d_sort = fields.d_sort end --Context yl_speak_up.speak_to[pname].d_id = save_d_id -- Just in case the NPC vlaues where changed or set dialog.n_id = n_id dialog.n_description = fields.n_description dialog.n_npc = fields.n_npc dialog.npc_owner = fields.npc_owner return dialog end local function options_to_dialog(pname) local dialog = yl_speak_up.speak_to[pname].dialog 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 p_id = yl_speak_up.speak_to[pname].p_id local r_id = yl_speak_up.speak_to[pname].r_id if yl_speak_up.speak_to[pname].d_text then dialog.n_dialogs[d_id].d_text = yl_speak_up.speak_to[pname].d_text end --Find the o_id to save to local future_o_id = "" if yl_speak_up.speak_to[pname].o_id ~= nil and yl_speak_up.speak_to[pname].o_id ~= yl_speak_up.text_new_option_id then future_o_id = yl_speak_up.speak_to[pname].o_id else future_o_id = "o_" .. find_next_id(dialog.n_dialogs[d_id].d_options) if dialog.n_dialogs[d_id].d_options == nil then dialog.n_dialogs[d_id].d_options = {} end dialog.n_dialogs[d_id].d_options[future_o_id] = {} end --Find the p_id to save to local future_p_id = "" if yl_speak_up.speak_to[pname].p_id ~= nil and yl_speak_up.speak_to[pname].p_id ~= yl_speak_up.text_new_prerequisite_id then future_p_id = yl_speak_up.speak_to[pname].p_id else future_p_id = "p_" .. find_next_id(dialog.n_dialogs[d_id].d_options[future_o_id].o_prerequisites) if future_p_id == "p_1" then dialog.n_dialogs[d_id].d_options[future_o_id].o_prerequisites = {} end dialog.n_dialogs[d_id].d_options[future_o_id].o_prerequisites[future_p_id] = {} end --Find the r_id to save to local future_r_id = "" if yl_speak_up.speak_to[pname].r_id ~= nil and yl_speak_up.speak_to[pname].r_id ~= yl_speak_up.text_new_result_id then future_r_id = yl_speak_up.speak_to[pname].r_id else future_r_id = "r_" .. find_next_id(dialog.n_dialogs[d_id].d_options[future_o_id].o_results) if future_r_id == "r_1" then dialog.n_dialogs[d_id].d_options[future_o_id].o_results = {} end dialog.n_dialogs[d_id].d_options[future_o_id].o_results[future_r_id] = {} end --Edit options dialog.n_dialogs[d_id].d_options[future_o_id].o_id = future_o_id dialog.n_dialogs[d_id].d_options[future_o_id].o_hide_when_prerequisites_not_met = yl_speak_up.speak_to[pname].o_hide or "false" dialog.n_dialogs[d_id].d_options[future_o_id].o_grey_when_prerequisites_not_met = yl_speak_up.speak_to[pname].o_grey or "false" local s = sanitize_sort(dialog.n_dialogs[d_id].d_options, yl_speak_up.speak_to[pname].o_sort) dialog.n_dialogs[d_id].d_options[future_o_id].o_sort = s dialog.n_dialogs[d_id].d_options[future_o_id].o_text_when_prerequisites_not_met = yl_speak_up.speak_to[pname].o_not_met or "" dialog.n_dialogs[d_id].d_options[future_o_id].o_text_when_prerequisites_met = yl_speak_up.speak_to[pname].o_met or "" --Edit prerequisites --Do we delete the prerequisite? if yl_speak_up.speak_to[pname].p_type == "delete" and yl_speak_up.speak_to[pname].p_id ~= nil and yl_speak_up.speak_to[pname].p_id ~= yl_speak_up.text_new_prerequisite_id then dialog.n_dialogs[d_id].d_options[future_o_id].o_prerequisites[future_p_id] = nil else if yl_speak_up.speak_to[pname].p_value ~= nil and yl_speak_up.speak_to[pname].p_value ~= "" then dialog.n_dialogs[d_id].d_options[future_o_id].o_prerequisites[future_p_id].p_id = future_p_id dialog.n_dialogs[d_id].d_options[future_o_id].o_prerequisites[future_p_id].p_type = yl_speak_up.speak_to[pname].p_type dialog.n_dialogs[d_id].d_options[future_o_id].o_prerequisites[future_p_id].p_value = yl_speak_up.speak_to[pname].p_value end end --Do we delete the result? if yl_speak_up.speak_to[pname].r_type == "delete" and yl_speak_up.speak_to[pname].r_id ~= nil and yl_speak_up.speak_to[pname].r_id ~= yl_speak_up.text_new_result_id then dialog.n_dialogs[d_id].d_options[future_o_id].o_results[future_r_id] = nil else if yl_speak_up.speak_to[pname].r_value ~= nil and yl_speak_up.speak_to[pname].r_value ~= "" then dialog.n_dialogs[d_id].d_options[future_o_id].o_results[future_r_id].r_id = future_r_id dialog.n_dialogs[d_id].d_options[future_o_id].o_results[future_r_id].r_type = yl_speak_up.speak_to[pname].r_type dialog.n_dialogs[d_id].d_options[future_o_id].o_results[future_r_id].r_value = yl_speak_up.speak_to[pname].r_value end end return dialog end local function delete_dialog(n_id, d_id) if d_id == yl_speak_up.text_new_dialog_id then return false end -- We don't delete "New dialog" local dialog = load_dialog(n_id) dialog.n_dialogs[d_id] = nil save_dialog(n_id, dialog) end local function delete_option(n_id, d_id, o_id) if d_id == yl_speak_up.text_new_dialog_id then return false end -- We don't delete "New dialog" Also, something might have gone wrong here. if o_id == yl_speak_up.text_new_option_id then return false end -- We don't delete "New option" local dialog = load_dialog(n_id) dialog.n_dialogs[d_id].d_options[o_id] = nil save_dialog(n_id, dialog) end local function delete_prerequisite(n_id, d_id, o_id, p_id) if d_id == yl_speak_up.text_new_dialog_id then return false end -- We don't delete "New dialog" Also, something might have gone wrong here. if o_id == yl_speak_up.text_new_option_id then return false end -- We don't delete "New option" Also, something might have gone wrong here. if p_id == yl_speak_up.text_new_prerequisite_id then return false end local dialog = load_dialog(n_id) dialog.n_dialogs[d_id].d_options[o_id].o_prerequisites[p_id] = nil save_dialog(n_id, dialog) end local function delete_result(n_id, d_id, o_id, r_id) if d_id == yl_speak_up.text_new_dialog_id then return false end -- We don't delete "New dialog" Also, something might have gone wrong here. if o_id == yl_speak_up.text_new_option_id then return false end -- We don't delete "New option" Also, something might have gone wrong here. if r_id == yl_speak_up.text_new_result_id then return false end local dialog = load_dialog(n_id) dialog.n_dialogs[d_id].d_options[o_id].o_resultss[r_id] = nil save_dialog(n_id, dialog) end local function calculate_displayable_options(pname, d_options) -- Let's go through all the options and see if we need to display them to the user local retval = {} local player = minetest.get_player_by_name(pname) if d_options == nil then return {} end for o_k, o_v in pairs(d_options) do --minetest.chat_send_all("#####"..o_k.."#####") local o_p_met = true if o_v.o_prerequisites == nil then --minetest.chat_send_all("display this because no prereq:"..dump(o_v)) else --minetest.chat_send_all("prereqs exists") if next(o_v.o_prerequisites) == nil then --minetest.chat_send_all("prereqs exist, but empty") else --minetest.chat_send_all("prereqs exists and not empty") --minetest.chat_send_all("if all prereqs are met, then we can display the option") local p_met = {} for p_k, p_v in pairs(o_v.o_prerequisites) do --minetest.chat_send_all("this is in a single prereq: "..dump(p_v)) p_met[p_k] = false local p_id = p_v.p_id if p_v.p_type == "item" then --minetest.chat_send_all("item! Does the player have this thing?") if player:get_inventory():contains_item("main", p_v.p_value) then --minetest.chat_send_all("found item:"..p_v.p_value) p_met[p_k] = true end end if p_v.p_type == "quest" then --minetest.chat_send_all("quest! let's call the quest api?") p_met[p_k] = false end if p_v.p_type == "function" then local code = p_v.p_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 = 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 "..p_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 "..p_id.." :"..dump(code) .. " for player unknown because of illegal bytecode") end end local f, msg = loadstring("return function(playername) " .. 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 = 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 "..p_id.." :"..dump(code) .. " for player "..pname) else minetest.log("error","[MOD] yl_speak_up: NPC with ID unknown could not compile the content of "..p_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 = 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 "..p_id.." :"..dump(code) .. " for player "..pname) else minetest.log("error","[MOD] yl_speak_up: NPC with ID unknown could not execute the content of "..p_id.." :"..dump(code) .. " for player unknown") end end if type(ret) == "boolean" then p_met[p_k] = ret end end end if p_v.p_type == "auto" then --minetest.chat_send_all("auto! what shall we do now?") p_met[p_k] = true end end -- is there a "false" in the p_met ? for m_k, m_v in pairs(p_met) do --minetest.chat_send_all(o_k..",m_v="..dump(m_v)) if m_v == false then o_p_met = false end end end end -- Can we display this option? retval[o_k] = o_p_met --[[ if o_p_met == true then minetest.chat_send_all("Prereqs say YES") retval[o_k] = o_p_met else minetest.chat_send_all("Prereqs say NOO") end ]]-- end return retval end local function calculate_portrait(pname, n_id) local tex = yl_speak_up.speak_to[pname].textures local head = "" head = head .. "[combine:8x8:-8,-8=" .. tex[2] .. ":-40,-8=" .. tex[2] return head end --### --Formspecs --### -- get formspecs -- dialog local function get_fs_setdialog(clicker, n_id, d_id) local dialog = load_dialog(n_id) local items = yl_speak_up.text_new_dialog_id local count = 1 local d_sort = "" local text = "" if next(dialog) == nil then -- file does not exist dialog.n_id = n_id dialog.n_npc = "" dialog.n_description = "" elseif dialog.n_dialogs == nil then -- file does exist, but no dialogs are set dialog.n_id = n_id text = "" else -- file exists and there's already content local n = 1 for k, v in pairs(dialog.n_dialogs) do n = n + 1 if k == d_id then count = n d_sort = v.d_sort or "" text = v.d_text or "" end items = items .. "," .. v.d_id end text = minetest.formspec_escape(text) end local formspec = { "formspec_version[3]", "size[13.4,8.5]", "label[0.2,0.35;", dialog.n_id, "]", "field[1.3,0.1;3.7,0.5;n_npc;;", dialog.n_npc, "]", "field[5.2,0.1;8,0.5;n_description;;", dialog.n_description, "]", "dropdown[5.2,0.75;5,0.75;d_id;", items, ";", count, "]", -- allow to change the owner of the npc "label[0.2,1;Owner]", "field[1.3,0.75;3.7,0.5;npc_owner;;", (yl_speak_up.npc_owner[ n_id ] or "- nobody -"), "]", "label[10.9,1;Sort]", "field[11.7,0.75;1.5,0.5;d_sort;;", d_sort, "]", "textarea[0.2,1.6;13,6;d_text;;", text, "]", "button_exit[0.2,7.7;3,0.75;button_cancel;Cancel]", "button[4,7.7;3,0.75;button_delete;Delete]", "button[7.1,7.7;3,0.75;button_option;Options]", "button[10.2,7.7;3,0.75;button_save;Save]", -- tooltips "tooltip[npc_owner;npc_owner: The name of the owner of the NPC - who can edit dialogs of this NPC if he has the npc_talk_owner priv;#FFFFFF;#000000]", "tooltip[n_npc;n_npc: The name of the NPC;#FFFFFF;#000000]", "tooltip[n_description;n_description: A description for the NPC;#FFFFFF;#000000]", "tooltip[d_sort;d_sort: Make this 0 on your dialog if you want it to be shown the first time a player talks to the NPC\nNegative values are ignored\n;#FFFFFF;#000000]", "tooltip[5.2,0.75;5,0.75;d_id: Dialog Id;#FFFFFF;#000000]", "tooltip[d_text;d_text: Dialog text. What the NPC says to you;#FFFFFF;#000000]" } return table.concat(formspec, "") end -- options local function get_fs_optiondialog(player, n_id, d_id, o_id, p_id, r_id) local dialog = load_dialog(n_id) local pname = player:get_player_name() if next(dialog) == nil or d_id == yl_speak_up.text_new_dialog_id or dialog.n_dialogs == nil then -- file does not exist or the user sent the New dialog return get_error_message(n_id) end -- default values local out = {} -- npc out.n_id = n_id -- dialog out.d_id = yl_speak_up.text_new_dialog_id out.d_text = "Dialog Text" -- option out.o_id_items = yl_speak_up.text_new_option_id out.o_id_count = 1 out.o_hide = "false" out.o_grey = "false" out.o_sort = "" out.o_met = "Text when conditions are met" out.o_not_met = "Text when conditions are not met" -- prerequisite out.p_id_items = yl_speak_up.text_new_prerequisite_id out.p_id_count = 1 out.p_type_items, out.p_type_items_table = get_prerequisite_types() out.p_type_count = 1 out.p_value = "" -- result out.r_id_items = yl_speak_up.text_new_result_id out.r_id_count = 1 out.r_type_items = get_result_types() out.r_type_count = 1 out.r_value = "" --dialogs for k, v in pairs(dialog.n_dialogs) do if k == d_id then out.d_id = v.d_id out.d_text = minetest.formspec_escape(v.d_text):trim() end end --options if dialog.n_dialogs[d_id].d_options ~= nil then local o_n = 1 for o_k, o_v in pairs(dialog.n_dialogs[d_id].d_options) do o_n = o_n + 1 if o_k == o_id then out.o_id_count = o_n end out.o_id_items = out.o_id_items .. "," .. o_v.o_id end if dialog.n_dialogs[d_id].d_options[o_id] then if dialog.n_dialogs[d_id].d_options[o_id].o_hide_when_prerequisites_not_met then out.o_hide = dialog.n_dialogs[d_id].d_options[o_id].o_hide_when_prerequisites_not_met end if dialog.n_dialogs[d_id].d_options[o_id].o_grey_when_prerequisites_not_met then out.o_grey = dialog.n_dialogs[d_id].d_options[o_id].o_grey_when_prerequisites_not_met end if dialog.n_dialogs[d_id].d_options[o_id].o_sort then out.o_sort = dialog.n_dialogs[d_id].d_options[o_id].o_sort end if dialog.n_dialogs[d_id].d_options[o_id].o_text_when_prerequisites_met then out.o_met = minetest.formspec_escape(dialog.n_dialogs[d_id].d_options[o_id].o_text_when_prerequisites_met):trim( ) end if dialog.n_dialogs[d_id].d_options[o_id].o_text_when_prerequisites_not_met then out.o_not_met = minetest.formspec_escape(dialog.n_dialogs[d_id].d_options[o_id].o_text_when_prerequisites_not_met):trim( ) end -- prerequisite --if dialog.n_dialogs[d_id].d_options[o_id].o_prerequisites ~= nil and p_id == yl_speak_up.text_new_prerequisite_id then -- new prerequisite --end if dialog.n_dialogs[d_id].d_options[o_id].o_prerequisites ~= nil then local p_n = 1 for p_k, p_v in pairs(dialog.n_dialogs[d_id].d_options[o_id].o_prerequisites) do p_n = p_n + 1 if p_k == p_id then out.p_id_count = p_n end out.p_id_items = out.p_id_items .. "," .. p_v.p_id end out.p_type_items, out.p_type_items_table = get_prerequisite_types() if dialog.n_dialogs[d_id].d_options[o_id].o_prerequisites[p_id] ~= nil and dialog.n_dialogs[d_id].d_options[o_id].o_prerequisites[p_id].p_type ~= nil then local p_type_n = 1 for _, p_type_v in pairs(out.p_type_items_table) do if p_type_v == dialog.n_dialogs[d_id].d_options[o_id].o_prerequisites[p_id].p_type then out.p_type_count = p_type_n end p_type_n = p_type_n + 1 end out.p_value = minetest.formspec_escape(dialog.n_dialogs[d_id].d_options[o_id].o_prerequisites[p_id].p_value):trim( ) end end -- result if dialog.n_dialogs[d_id].d_options[o_id].o_results ~= nil then local r_n = 1 for r_k, r_v in pairs(dialog.n_dialogs[d_id].d_options[o_id].o_results) do r_n = r_n + 1 if r_k == r_id then out.r_id_count = r_n end out.r_id_items = out.r_id_items .. "," .. r_v.r_id end out.r_type_items, out.r_type_items_table = get_result_types() if dialog.n_dialogs[d_id].d_options[o_id].o_results[r_id] ~= nil and dialog.n_dialogs[d_id].d_options[o_id].o_results[r_id].r_type ~= nil then local r_type_n = 1 for _, r_type_v in pairs(out.r_type_items_table) do if r_type_v == dialog.n_dialogs[d_id].d_options[o_id].o_results[r_id].r_type then out.r_type_count = r_type_n end r_type_n = r_type_n + 1 end out.r_value = minetest.formspec_escape(dialog.n_dialogs[d_id].d_options[o_id].o_results[r_id].r_value):trim() end end end end local formspec = { "formspec_version[3]", "size[13.4,8.5]", -- npc "field[-10,-10;4,0.5;n_id;;", out.n_id, "]", -- dialog "field[-10,-10;4,0.5;d_id;;", out.d_id, "]", "label[0.2,0.35;", out.d_id, "]", "field[1,0.1;12.2,0.5;d_text;;", out.d_text, "]", -- option "dropdown[0.2,0.75;2,0.75;o_id;", out.o_id_items, ";", out.o_id_count, "]", "checkbox[0.2,1.85;o_hide;Hide;", out.o_hide, "]", "checkbox[0.2,2.35;o_grey;Grey;", out.o_grey, "]", "field[1.6,2.05;0.75,0.5;o_sort;Sort;", out.o_sort, "]", "textarea[2.4,0.75;10.8,0.95;o_met;;", out.o_met, "]", "textarea[2.4,1.75;10.8,0.95;o_not_met;;", out.o_not_met, "]", -- prerequisite "dropdown[0.2,2.8;3,0.75;p_id;", out.p_id_items, ";", out.p_id_count, "]", "dropdown[0.2,3.8;3,0.75;p_type;", out.p_type_items, ";", out.p_type_count, "]", "textarea[3.4,2.8;9.8,2.1;p_value;;", out.p_value, "]", -- result "dropdown[0.2,5;3,0.75;r_id;", out.r_id_items, ";", out.r_id_count, "]", "dropdown[0.2,6;3,0.75;r_type;", out.r_type_items, ";", out.r_type_count, "]", "textarea[3.4,5;9.8,2.1;r_value;;", out.r_value, "]", -- buttons "button[0.2,7.7;3,0.75;button_back;Back]", "button[7.1,7.7;3,0.75;button_delete;Delete OPTION]", "button[10.2,7.7;3,0.75;button_save;Save]", -- tooltips "tooltip[d_text;d_text: Dialog text. What the NPC says to you\n\n", out.d_text, ";#FFFFFF;#000000]", "tooltip[0.2,0.75;2,0.75;o_id: Option Id;#FFFFFF;#000000]", "tooltip[o_met;o_met: Option text when prerequisites are met\n(What you say to the NPC)\n\n", out.o_met, ";#FFFFFF;#000000]", "tooltip[o_not_met;o_not_met: Option text when prerequisites are not met\n(What you say to the NPC instead)\n\n", out.o_not_met, ";#FFFFFF;#000000]", "tooltip[o_hide;o_hide: If checked, this option is hidden when prerequistes are not met;#FFFFFF;#000000]", "tooltip[o_grey;o_grey: If checked, this option is shown when prerequistes are not met, but grey and not selectable;#FFFFFF;#000000]", "tooltip[o_sort;o_sort: The lower the number, the higher up in the list this option goes\nNegative values are ignored;#FFFFFF;#000000]", "tooltip[0.2,2.8;3,0.75;p_id: Prerequisite Id;#FFFFFF;#000000]", "tooltip[0.2,3.8;3,0.75;p_type: Defines what the p_value stands for.\n\n", "item: requires the user to have this item in the inventory\n", "quest: requires the user to have completed a quest with this id\n", "function: executes the given LUA function which must return true or false. true = prerequisite met\n", "auto: ???\n", "delete: Deletes the chosen prerequiste when the dialog is saved", ";#FFFFFF;#000000]", "tooltip[p_value;p_value: This is evaluated to decide whether the prerequisites are met or not;#FFFFFF;#000000]", "tooltip[0.2,5;3,0.75;r_id: Result Id;#FFFFFF;#000000]", "tooltip[0.2,6;3,0.75;r_type: Defines what the r_value stands for.\n\n", "dialog: forwards the user to the given dialog d_id\n", "item: places this item in the inventory of the user\n", "quest: starts a quest with this id for the user\n", "function: executes the given LUA function\n", "auto: automatically forwards the user to the given dialog d_id after a given time\n", "delete: Deletes the chosen prerequiste when the dialog is saved", ";#FFFFFF;#000000]", "tooltip[r_value;r_value: This is what happens when the user chooses this option;#FFFFFF;#000000]" } return table.concat(formspec, "") end -- edit options (not via staff but via the "I am your owner" dialog) local function get_fs_edit_option_dialog(player, n_id, d_id, o_id) -- n_id, d_id and o_id have already been checked when this function is called local pname = player:get_player_name() local dialog = yl_speak_up.speak_to[pname].dialog local n_dialog = dialog.n_dialogs[d_id] local d_option = n_dialog.d_options[o_id] -- are there any preconditions? local list_of_preconditions = "" local prereq = d_option.o_prerequisites if(prereq) then for k, v in pairs(prereq) do list_of_preconditions = list_of_preconditions.. minetest.formspec_escape(v.p_id)..",#FFFF00,".. minetest.formspec_escape(v.p_type)..",".. minetest.formspec_escape(v.p_value).."," end end -- find the right target dialog for this option (if it exists) local target_dialog = nil -- and build the list of effects local list_of_effects = "" local results = d_option.o_results if(results) then for k, v in pairs(results) do if v.r_type == "dialog" and dialog.n_dialogs[v.r_value] ~= nil then -- there may be more than one in the data structure target_dialog = v.r_value elseif v.r_type ~= "dialog" then list_of_effects = list_of_effects.. minetest.formspec_escape(v.r_id)..",#FFFF00,".. minetest.formspec_escape(v.r_type)..",".. minetest.formspec_escape(v.r_value).."," end end end -- if no target dialog has been selected: default is to go to the dialog with d_sort 0 if(not(target_dialog) or target_dialog == "" or not(dialog.n_dialogs[target_dialog])) then for d, v in pairs(dialog.n_dialogs) do if(v.d_sort and tonumber(v.d_sort) == 0) then target_dialog = d end end end -- this is the text the NPC will say in reaction to this answer local next_text = "" if(target_dialog and dialog.n_dialogs[target_dialog]) then next_text = dialog.n_dialogs[target_dialog].d_text end -- build the list of available dialogs for the dropdown list(s) local dialog_list = yl_speak_up.text_new_dialog_id local dialog_selected = "1" -- if there are dialogs defined if(dialog and dialog.n_dialogs) then -- the first entry will be "New dialog" local n = 1 for k, v in pairs(dialog.n_dialogs) do dialog_list = dialog_list .. "," .. minetest.formspec_escape(v.d_id) -- which one is the current dialog? n = n + 1 if(v.d_id == target_dialog) then dialog_selected = tostring(n) end end end if(not(target_dialog)) then target_dialog = "- none -" end -- offer the correct preselection for hidden/grey/show text local alternate_answer_option = "3" if(d_option.o_hide_when_prerequisites_not_met == "true") then alternate_answer_option = "1" elseif(d_option.o_grey_when_prerequisites_not_met == "true") then alternate_answer_option = "2" end -- can the button "prev(ious)" be shown? local button_prev = "" -- can the button "next" be shown? local button_next = "" -- sort all options by o_sort local sorted_list = yl_speak_up.get_sorted_options(n_dialog.d_options, "o_sort") local o_found = o_id for i, o in ipairs(sorted_list) do if(o == o_id and sorted_list[ i-1 ]) then button_prev = "".. "button[7.4,17;2.0,0.9;edit_option_prev;Prev]".. "tooltip[edit_option_prev;Go to previous option/answer ".. "(according to o_sort).]" end if(o == o_id and sorted_list[ i+1 ]) then button_next = "".. "button[12.0,17;2.0,0.9;edit_option_next;Next]".. "tooltip[edit_option_next;Go to next option/answer ".. "(according to o_sort).]" end end -- build up the formspec local formspec = "".. "formspec_version[3]".. "size[21,18]".. "bgcolor[#00000000;false]".. -- button back to the current dialog (of which this is an option) "button[15.8,0.2;5.0,0.9;show_current_dialog;Back to dialog ".. minetest.formspec_escape(d_id).."]".. "tooltip[show_current_dialog;Go back to dialog ".. minetest.formspec_escape(d_id).." and continue editing that dialog.]".. -- the text the NPC says "label[0.2,0.6;NPC says:]".. "hypertext[1.2,1.2;19.6,2.5;d_text;".. minetest.formspec_escape(n_dialog.d_text) .. "\n".."]".. "tooltip[1.2,1.2;19.6,3.0;This is what the NPC says to the player.]".. -- list the preconditions "label[0.2,4.2;If all of the following pre(C)onditions are fulfilled:]".. -- TODO: perhaps add tooltip for the type of the conditions "tablecolumns[text;color,span=1;text;text]".. "table[1.2,4.5;19.6,2.0;table_of_preconditions;".. list_of_preconditions..";0]".. -- answer of the player (the actual option) "label[0.2,6.8;..the player may answer with this text:]".. "label[1.2,7.6;A:]".. "field[1.7,7.1;19.1,0.9;text_option_"..minetest.formspec_escape(o_id)..";;".. minetest.formspec_escape(d_option.o_text_when_prerequisites_met).."]".. "tooltip[option_text_met;This is the answer the player may choose if the ".. "preconditions are all fulfilled.]".. -- dropdown for selecting weather to show the alternate answer or not "label[0.2,8.6;..but if at least one pre(C)ondition is not fulfilled, then...]".. "dropdown[12.0,8.2;8.6,0.9;hide_or_grey_or_alternate_answer;".. "..hide this answer.,".. "..grey out this answer.,".. "..display the following alternate answer:;".. alternate_answer_option..";]".. -- alternate answer "label[1.2,9.6;A:]".. "field[1.7,9.1;19.1,0.9;option_text_not_met;;".. minetest.formspec_escape(d_option.o_text_when_prerequisites_not_met).."]".. "tooltip[option_text_not_met;This is the answer the player may choose if the ".. "preconditions are NOT all fulfilled.]".. -- list of effects "label[0.2,10.6;When this answer has been selected, apply the following (Ef)fects:]".. -- TODO: perhaps add tooltip for the type of the conditions "tablecolumns[text;color,span=1;text;text]".. "table[1.2,11.0;19.6,2.0;table_of_effect;".. list_of_effects..";0]".. -- ..and what the NPC will reply to that answer "label[0.2,13.6;The NPC will react to this answer with the following dialog:]".. "hypertext[1.2,14.2;19.6,2.5;d_text_next;".. minetest.formspec_escape(next_text) .. "\n".."]".. "tooltip[1.2,14.2;19.6,2.5;This is what the NPC will say next when the player has ".. "selected this answer here.]".. -- allow to change the target dialog via a dropdown menu "dropdown[14.8,13.2;5,0.9;d_id_"..minetest.formspec_escape(o_id)..";".. dialog_list..";"..dialog_selected..",]".. "tooltip[14.8,13.2;5,0.9;Select the target dialog with which the NPC shall react ".. "to this answer. Currently, dialog "..minetest.formspec_escape(target_dialog).. " is beeing displayed.;#FFFFFF;#000000]".. -- button: delete "button[0.2,17;2.0,0.9;del_option;Delete]".. "tooltip[del_option;Delete this option/answer.]".. -- button: add new "button[2.4,17;2.0,0.9;add_option;Add]".. "tooltip[add_option;Add a new option/answer to this dialog.]".. -- button: prev/next button_prev.. button_next.. -- button: go back to dialog (repeated from top of the page) "button[15.8,17;5.0,0.9;show_current_dialog;Back to dialog ".. minetest.formspec_escape(d_id).."]".. "tooltip[show_current_dialog;Go back to dialog ".. minetest.formspec_escape(d_id).." and continue editing that dialog.]".. -- allow to enter o_sort "label[9.6,17.5;Sort:]".. "field[10.6,17;1.0,0.9;edit_option_o_sort;;".. minetest.formspec_escape(d_option.o_sort).."]".. "tooltip[edit_option_o_sort;o_sort: The lower the number, the higher up in the ".. "list this option goes\nNegative values are ignored;#FFFFFF;#000000]".. -- hidden field containing the value of o_id "field[40,40;0.1,0.1;o_id;;"..minetest.formspec_escape(o_id).."]" return formspec end -- initialize the npc without having to use a staff; -- returns true when initialization possible local function get_fs_initial_config(player, n_id, d_id, is_initial_config) local pname = player:get_player_name() -- is the player allowed to edit this npc? if(not(yl_speak_up.npc_owner[ n_id ] == pname and minetest.check_player_privs(player, {npc_talk_owner=true})) and not(minetest.check_player_privs(player, {npc_master=true}))) then return get_error_message() end local tmp_name = n_id local tmp_descr = "A new NPC without description" local tmp_text = "Please provide your new NPC with a name and description!" -- use existing name and description as presets when just editing if(not(is_initial_config)) then local dialog = yl_speak_up.speak_to[pname].dialog tmp_name = dialog.n_npc tmp_descr = dialog.n_description tmp_text = "You can change the name and description of your NPC." end local formspec = "size[10,4]".. "label[0.2,0.2;"..tmp_text.."]".. -- name of the npc "label[0.2,1.05;Name:]".. "field[2.0,1.2;4,0.9;n_npc;;"..minetest.formspec_escape(tmp_name).."]".. "tooltip[n_npc;n_npc: The name of the NPC;#FFFFFF;#000000]".. -- description of the npc "label[0.2,2.05;Description:]".. "field[2.0,2.2;8,0.9;n_description;;"..minetest.formspec_escape(tmp_descr).."]".. "tooltip[n_description;n_description: A description for the NPC;#FFFFFF;#000000]".. -- save and exit buttons "button_exit[3.2,3.2;2,0.9;save_initial_config;Save]".. "button_exit[5.4,3.2;2,0.9;exit;Exit]" -- show the formspec to the player return formspec end -- talk local function get_fs_talkdialog(player, n_id, d_id) local pname = player:get_player_name() local dialog = yl_speak_up.speak_to[pname].dialog local context_d_id = yl_speak_up.speak_to[pname].d_id local active_dialog if not player and not player:is_player() then minetest.log( "action", "[MOD] yl_speak_up: User " .. pname .. " talked to unconfigured NPC with ID n_" .. n_id .. ", position of user was " .. minetest.pos_to_string(player:get_pos(), 0) ) return get_error_message() end --[[ If we have an explicit call for a certain d_id, we grab it from parameters. If not, we grab in from context. When neither are present, we grab it from d_sort ]]-- local c_d_id if d_id ~= nil then active_dialog = dialog.n_dialogs[d_id] c_d_id = d_id elseif yl_speak_up.speak_to[pname].d_id ~= nil then c_d_id = yl_speak_up.speak_to[pname].d_id active_dialog = dialog.n_dialogs[c_d_id] elseif dialog.n_dialogs ~= nil then -- Find the dialog with d_sort = 0 local lowest_sort = nil for k, v in pairs(dialog.n_dialogs) do if tonumber(v.d_sort) ~= nil then if not lowest_sort or tonumber(v.d_sort) < tonumber(lowest_sort) or v.d_sort == "0" then if tonumber(v.d_sort) >= 0 then lowest_sort = v.d_sort active_dialog = v c_d_id = k end end end end else -- it may be possible that this player can initialize this npc minetest.log( "action", "[MOD] yl_speak_up: User " .. pname .. " talked to unconfigured NPC with ID n_" .. n_id .. ", position of user was " .. minetest.pos_to_string(player:get_pos(), 0) ) -- this is the initial config return get_fs_initial_config(player, n_id, d_id, true) end if c_d_id == nil then return get_error_message() end yl_speak_up.speak_to[pname].d_id = c_d_id -- Now we have a dialog to display to the user -- do not crash in case of error if(not(active_dialog)) then return "size[6,2]".. "label[0.2,0.5;Ups! Something went wrong. Please try again.]" end local allowed = calculate_displayable_options(pname, active_dialog.d_options) yl_speak_up.speak_to[pname].allowed = allowed local portrait = calculate_portrait(pname, n_id) local formspec_v = minetest.get_player_information(pname).formspec_version local protocol_v = minetest.get_player_information(pname).protocol_version local formspec = {} local h -- show who owns the NPC (and is thus more or less responsible for what it says) local owner_info = "" if(yl_speak_up.npc_owner[ n_id ]) then owner_info = "\n\n(owned by "..minetest.formspec_escape(yl_speak_up.npc_owner[ n_id ])..")" end if formspec_v >= 4 then formspec = { "formspec_version[3]", "size[57,33]", "position[0,0.45]", "anchor[0,0.45]", "no_prepend[]", "bgcolor[#00000000;false]", -- Container "container[2,0.75]", -- Background "background[0,0;20,23;yl_speak_up_bg_dialog.png;false]", "background[0,24;54.5,7.5;yl_speak_up_bg_dialog.png;false]", -- Frame Dialog "image[-0.25,-0.25;1,1;yl_speak_up_bg_dialog_tl.png]", "image[-0.25,22.25;1,1;yl_speak_up_bg_dialog_bl.png]", "image[19.25,-0.25;1,1;yl_speak_up_bg_dialog_tr.png]", "image[19.25,22.25;1,1;yl_speak_up_bg_dialog_br.png]", "image[-0.25,0.75;1,21.5;yl_speak_up_bg_dialog_hl.png]", "image[19.25,0.75;1,21.5;yl_speak_up_bg_dialog_hr.png]", "image[0.75,-0.25;18.5,1;yl_speak_up_bg_dialog_vt.png]", "image[0.75,22.25;18.5,1;yl_speak_up_bg_dialog_vb.png]", -- Frame Options "image[-0.25,23.75;1,1;yl_speak_up_bg_dialog_tl.png]", "image[-0.25,30.75;1,1;yl_speak_up_bg_dialog_bl.png]", "image[53.75,23.75;1,1;yl_speak_up_bg_dialog_tr.png]", "image[53.75,30.75;1,1;yl_speak_up_bg_dialog_br.png]", "image[-0.25,24.75;1,6;yl_speak_up_bg_dialog_hl.png]", "image[53.75,24.75;1,6;yl_speak_up_bg_dialog_hr.png]", "image[0.75,23.75;53,1;yl_speak_up_bg_dialog_vt.png]", "image[0.75,30.75;53,1;yl_speak_up_bg_dialog_vb.png]", "style_type[button;bgcolor=#a37e45]", "style_type[button_exit;bgcolor=#a37e45]", -- Dialog --[[ "background[-1,-1;22,25;yl_speak_up_bg_dialog2.png;false]", "background[-1,23;58,10;yl_speak_up_bg_dialog2.png;false]", "style_type[button;bgcolor=#a37e45]", ]]-- "label[0.3,0.6;", minetest.formspec_escape(dialog.n_npc), "]", "label[0.3,1.8;", minetest.formspec_escape(dialog.n_description)..owner_info, "]", "image[15.5,0.5;4,4;", portrait, "]", } -- Is the player working on this particular npc? local edit_mode = (yl_speak_up.edit_mode[pname] == yl_speak_up.speak_to[pname].n_id) -- this is used to build a list of all available dialogs for a dropdown menu in edit mode -- (only relevant in edit mode) local dialog_list = yl_speak_up.text_new_dialog_id -- find the right index for the dialog_list dropdown above local d_id_to_dropdown_index = {} -- display the window with the text the NPC is saying if(edit_mode and dialog and dialog.n_dialogs) then -- sort all dialogs by d_sort local sorted_list = yl_speak_up.get_sorted_options(dialog.n_dialogs, "d_sort") -- add buttons for previous/next dialog for i, d in ipairs(sorted_list) do -- build the list of available dialogs for the dropdown list(s) dialog_list = dialog_list..","..minetest.formspec_escape(d) if(d == c_d_id and sorted_list[ i-1 ]) then table.insert(formspec, "button[8.5,4.0;2,0.9;prev_dialog_".. minetest.formspec_escape(sorted_list[i-1])..";<]") table.insert(formspec, "tooltip[prev_dialog;Go to previous dialog ".. minetest.formspec_escape(sorted_list[i-1])..".]") end if(d == c_d_id and sorted_list[ i+1 ]) then table.insert(formspec, "button[11,4.0;2,0.9;next_dialog_".. minetest.formspec_escape(sorted_list[i+1])..";>]") -- add a tooltip table.insert(formspec, "tooltip[next_dialog;Go to next dialog ".. minetest.formspec_escape(sorted_list[i+1])..".]") end d_id_to_dropdown_index[d] = i + 1 end table.insert(formspec, "label[0.2,4.6;Dialog:]") -- "..minetest.formspec_escape(c_d_id)..":]") table.insert(formspec, "dropdown[3.0,4.0;5,1;d_id;"..dialog_list..";"..d_id_to_dropdown_index[c_d_id]..",]") table.insert(formspec, "tooltip[3.0,4.0;5,1;Select the dialog you want to edit. Currently, dialog "..c_d_id.." is beeing displayed.;#FFFFFF;#000000]") -- add a "+" button for creating a new dialog table.insert(formspec, "button[13.9,4.0;1,0.9;show_new_dialog;+]") table.insert(formspec, "tooltip[show_new_dialog;Create a new dialog.]") -- allow to edit name and description of the NPC table.insert(formspec, "button[13.4,0.3;2,0.9;button_edit_name_and_description;Edit]") table.insert(formspec, "tooltip[button_edit_name_and_description;Edit name and description of your NPC.]") table.insert(formspec, "textarea[0.2,5;19.6,17.8;d_text;;") table.insert(formspec, minetest.formspec_escape(active_dialog.d_text)) table.insert(formspec, "]") else table.insert(formspec, "hypertext[0.2,5;19.6,17.8;d_text;") table.insert(formspec, minetest.formspec_escape(active_dialog.d_text) .. "\n") table.insert(formspec, "]") table.insert(formspec, "tooltip[d_text;") table.insert(formspec, minetest.formspec_escape(active_dialog.d_text):trim()) table.insert(formspec, ";#000000;#FFFFFF]") end table.insert(formspec, "scrollbaroptions[min=0;max=14;smallstep=1;largestep=2;arrows=show]") table.insert(formspec, "scrollbar[0.2,24.2;0.2,7;vertical;scr0;0]") table.insert(formspec, "scroll_container[0,24;56,7;scr0;vertical;1]") h = -0.8 -- allow to delete entries that have no options later on local anz_options = 0 -- Let#s sort the options by o_sort if active_dialog ~= nil and active_dialog.d_options ~= nil then local sorted_buttons = {} for _, ad_v in pairs(active_dialog.d_options) do sorted_buttons[tonumber(ad_v.o_sort)] = ad_v anz_options = anz_options + 1 end for _, sb_v in pairs(sorted_buttons) do local oid = minetest.formspec_escape(sb_v.o_id) -- in edit_mode: show all options if edit_mode then h = h + 1 -- add a button "o_:" that leads to an edit formspec for this option table.insert(formspec, "button[1.8," .. h .. ";2,0.9;edit_option_" .. oid .. ";"..oid..":]") -- add a tooltip "Edit target dialog, pre(C)onditions and (Ef)fects for option o_" table.insert(formspec, "tooltip[edit_option_" .. oid .. ";Edit target dialog, pre(C)onditions and (Ef)fects for option "..oid..".]") -- find the right target dialog for this option (if it exists): local target_dialog = nil local results = active_dialog.d_options[sb_v.o_id].o_results -- has this option more results/effects than just switching to another dialog? local has_other_results = false if(results ~= nil) then for k, v in pairs(results) do if v.r_type == "dialog" and dialog.n_dialogs[v.r_value] ~= nil then -- there may be more than one in the data structure target_dialog = v.r_value elseif v.r_type ~= "dialog" then has_other_results = true end end end if(target_dialog) then -- add a button "-> d_" that leads to the target dialog (if one is set) table.insert(formspec, "button[8.5," .. h .. ";1,0.9;button_" .. oid .. ";->]") -- add a tooltip "Go to target dialog d_" table.insert(formspec, "tooltip[button_" .. oid .. ";Go to target dialog "..minetest.formspec_escape(target_dialog).." that will be shown when this option ("..oid..") is selected.]") -- selecting an option this way MUST NOT execute the pre(C)onditions or (Ef)fects! end -- allow to set a new target dialog table.insert(formspec, "dropdown[3.9,"..h..";4.7,1;d_id_"..oid..";"..dialog_list..";"..minetest.formspec_escape(d_id_to_dropdown_index[target_dialog] or "0")..",]") -- add a tooltip "Change target dialog" table.insert(formspec, "tooltip[3.9,"..h..";4.7,1;Change target dialog for option "..oid..".;#FFFFFF;#000000]") -- are there any prerequirements? local prereq = active_dialog.d_options[sb_v.o_id].o_prerequisites if(prereq) then table.insert(formspec, "button[0.5," .. h .. ";0.5,0.9;conditions_"..oid..";C]") -- label: "There are pre(C)onditions required for showing this option. Display them." table.insert(formspec, "tooltip[conditions_" .. oid .. ";There are pre(C)onditions required for showing this option. Display them.]") end if(has_other_results) then table.insert(formspec, "button[1.1," .. h .. ";0.6,0.9;effects_"..oid..";Ef]") -- label: "There are further (Ef)fects (apart from switching to a new dialog) set for this option. Display them." table.insert(formspec, "tooltip[effects_" .. oid .. ";There are further (Ef)fects (apart from switching to a new dialog) set for this option. Display them.]") end -- show the actual text for the option table.insert(formspec, "field[9.4," .. h .. ";44.9,0.9;text_option_" .. oid .. ";;".. minetest.formspec_escape(sb_v.o_text_when_prerequisites_met).."]") -- add a tooltip "Edit the text that is displayed on button o_." table.insert(formspec, "tooltip[text_option_" .. oid .. ";Edit the text that is displayed on button "..oid..".]") -- normal mode: show an option if the prerequirements (if any are defined) are met elseif allowed[sb_v.o_id] == true then h = h + 1 table.insert(formspec, "button[0.5," .. h .. ";53.8,0.9;button_" .. oid .. ";]") table.insert( formspec, "tooltip[button_" .. oid .. ";" .. minetest.formspec_escape(sb_v.o_text_when_prerequisites_met) .. "]" ) local l = h + 0.45 table.insert(formspec, "label[0.7," .. l .. ";" .. minetest.formspec_escape(sb_v.o_text_when_prerequisites_met) .. "]") else if sb_v.o_hide_when_prerequisites_not_met == "true" then else if sb_v.o_grey_when_prerequisites_not_met == "true" and sb_v.o_text_when_prerequisites_not_met == "" then h = h + 1 table.insert(formspec, "button[0.5," .. h .. ";53.8,0.9;button_" .. oid .. ";]") table.insert( formspec, "tooltip[button_" .. oid .. ";" .. yl_speak_up.message_button_option_prerequisites_not_met_default .. "]" ) local l = h + 0.45 table.insert( formspec, "label[0.7," .. l .. ";" .. yl_speak_up.message_button_option_prerequisites_not_met_default .. "]" ) table.insert(formspec, "box[0.5," .. h .. ";53.8,0.9;#BBBBBB]") end if sb_v.o_grey_when_prerequisites_not_met == "true" and sb_v.o_text_when_prerequisites_not_met ~= "" then h = h + 1 table.insert(formspec, "button[0.5," .. h .. ";53.8,0.9;button_" .. oid .. ";]") table.insert( formspec, "tooltip[button_" .. oid .. ";" .. minetest.formspec_escape(sb_v.o_text_when_prerequisites_not_met) .. "]" ) local l = h + 0.45 table.insert( formspec, "label[0.7," .. l .. ";" .. minetest.formspec_escape(sb_v.o_text_when_prerequisites_not_met) .. "]" ) table.insert(formspec, "box[0.5," .. h .. ";53.8,0.9;#BBBBBB]") end if sb_v.o_grey_when_prerequisites_not_met == "false" and sb_v.o_text_when_prerequisites_not_met == "" then -- no hide, no grey, no text end if sb_v.o_grey_when_prerequisites_not_met == "false" and sb_v.o_text_when_prerequisites_not_met ~= "" then -- no grey, but text h = h + 1 table.insert(formspec, "button[0.5," .. h .. ";53.8,0.9;button_" .. oid .. ";]") table.insert( formspec, "tooltip[button_" .. oid .. ";" .. minetest.formspec_escape(sb_v.o_text_when_prerequisites_not_met) .. "]" ) local l = h + 0.45 table.insert( formspec, "label[0.7," .. l .. ";" .. minetest.formspec_escape(sb_v.o_text_when_prerequisites_not_met) .. "]" ) end end end end end -- If in edit mode, add two new menu entries: "add new options" and "end edit mode". if(edit_mode) then -- chat option: "Add a new answer/option to this dialog." h = h + 1 if(anz_options < yl_speak_up.max_number_of_options_per_dialog) then table.insert(formspec, "button[0.5," .. h .. ";53.8,0.9;add_option;]") table.insert(formspec, "tooltip[add_option;Adds a new option to this dialog. You can ".. "delete options via the option edit menu.]") table.insert(formspec, "label[0.7,"..(h+0.45)..";Add a new option/answer to this dialog. ".. "You can delete options via the option edit menu.]") -- the amount of allowed options/answers has been reached else table.insert(formspec, "box[0.5,"..h..";53.8,0.9;#BBBBBB]") table.insert(formspec, "label[0.7,"..(h+0.45)..";Maximum number of allowed answers/options ".. "reached. No further options/answers can be added.]") end -- chat option: "Delete this dialog." h = h + 1 if(active_dialog and active_dialog.d_text == "" and anz_options == 0) then table.insert(formspec, "button[0.5," .. h .. ";53.8,0.9;delete_this_empty_dialog;]") table.insert(formspec, "tooltip[delete_this_empty_dialog;Dialogs can only be deleted ".. "when they are empty and have no more options/answers. This is the case here, ".. "so the dialog can be deleted.]") table.insert(formspec, "label[0.7,"..(h+0.45)..";Delete this empty dialog.]") -- (but only show this option if the dialog is empty) else table.insert(formspec, "box[0.5,"..h..";53.8,0.9;#BBBBBB]") table.insert(formspec, "label[0.7,"..(h+0.45)..";If you want to delete this dialog, you ".. "need to delete all options and its text first.]") end -- chat option: "Make this dialog the first one shown when starting to talk." h = h + 1 if(active_dialog and active_dialog.d_sort and tonumber(active_dialog.d_sort) ~= 0) then table.insert(formspec, "button[0.5," .. h .. ";53.8,0.9;make_first_option;]") table.insert(formspec, "tooltip[make_first_option;The NPC has to start with one dialog ".. "when he is right-clicked. Make this dialog the one shown.]") table.insert(formspec, "label[0.7,"..(h+0.45)..";Make this dialog the first one shown ".. "when starting a conversation.]") -- (but only show this option if it's not already the first one) else table.insert(formspec, "box[0.5,"..h..";53.8,0.9;#BBBBBB]") table.insert(formspec, "label[0.7,"..(h+0.45)..";This dialog will be shown whenever ".. "a conversation is started.]") end -- chat option: "That was all. I'm finished with giving you new orders. Remember them!" h = h + 1 table.insert(formspec, "button_exit[0.5," .. h .. ";53.8,0.9;button_end_edit_mode;]") table.insert(formspec, "tooltip[button_end_edit_mode;Ends edit mode. From now on, your NPC will talk to you like he talks to other players. You can always give him new orders by entering edit mode again.]") table.insert(formspec, "label[0.7,"..(h+0.45)..";That was all. I'm finished with giving you new orders. Remember them!]") -- Offer to enter edit mode if the player has the npc_talk_owner priv AND owns the npc. -- The npc_master priv allows to edit all NPC. elseif((yl_speak_up.npc_owner[ n_id ] == pname and minetest.check_player_privs(player, {npc_talk_owner=true})) or (minetest.check_player_privs(player, {npc_master=true}))) then -- chat option: "I am your owner. I have new orders for you. h = h + 1 table.insert(formspec, "button_exit[0.5," .. h .. ";53.8,0.9;button_start_edit_mode;]") table.insert(formspec, "tooltip[button_start_edit_mode;Enters edit mode. In this mode, you can edit the texts the NPC says and the answers that can be given.]") table.insert(formspec, "label[0.7,"..(h+0.45)..";I am your owner. I have new orders for you.]") end h = h + 1 table.insert(formspec, "button_exit[0.5," .. h .. ";53.8,0.9;button_exit;]") table.insert(formspec, "tooltip[button_exit;" .. yl_speak_up.message_button_option_exit .. "]") local l = h + 0.45 table.insert(formspec, "label[0.7," .. l .. ";" .. yl_speak_up.message_button_option_exit .. "]") table.insert(formspec, "scroll_container_end[]") table.insert(formspec, "container_end[]") else minetest.log( "info", "[MOD] yl_speak_up: User " .. pname .. " talked to NPC ID n_" .. n_id .. " with an old formspec version!" ) local upgrade_warning = "" local max_number_of_buttons = yl_speak_up.max_number_of_buttons local start_index = yl_speak_up.speak_to[pname].option_index if formspec_v < 3 or protocol_v < 39 then local warn = { "box[0.3,3;15,2;red]", "label[0.7,3.2;", yl_speak_up.text_version_warning, "]" } upgrade_warning = table.concat(warn, "") end formspec = { "formspec_version[1]", "size[48,28]", "position[0,0.45]", "anchor[0,0.45]", "no_prepend[]", "bgcolor[#00000000;false]", -- Container "container[2,0.75]", -- Background "background[0,0;20,17;yl_speak_up_bg_dialog.png]", "background[0,18;45,8;yl_speak_up_bg_dialog.png]", -- Frame Dialog "image[-0.35,-0.35;1,1;yl_speak_up_bg_dialog_tl.png]", "image[-0.35,16.25;1,1;yl_speak_up_bg_dialog_bl.png]", "image[19.35,-0.35;1,1;yl_speak_up_bg_dialog_tr.png]", "image[19.35,16.25;1,1;yl_speak_up_bg_dialog_br.png]", "image[-0.35,0.35;1,19.5;yl_speak_up_bg_dialog_hl.png]", "image[19.35,0.35;1,19.5;yl_speak_up_bg_dialog_hr.png]", "image[0.35,-0.35;24.5,1;yl_speak_up_bg_dialog_vt.png]", "image[0.35,16.25;24.5,1;yl_speak_up_bg_dialog_vb.png]", -- Frame Options "image[-0.35,17.65;1,1;yl_speak_up_bg_dialog_tl.png]", "image[-0.35,25.35;1,1;yl_speak_up_bg_dialog_bl.png]", "image[44.35,17.65;1,1;yl_speak_up_bg_dialog_tr.png]", "image[44.35,25.35;1,1;yl_speak_up_bg_dialog_br.png]", "image[-0.35,18.35;1,8.5;yl_speak_up_bg_dialog_hl.png]", "image[44.35,18.35;1,8.5;yl_speak_up_bg_dialog_hr.png]", "image[0.35,17.65;56.5,1;yl_speak_up_bg_dialog_vt.png]", "image[0.35,25.35;56.5,1;yl_speak_up_bg_dialog_vb.png]", -- Upgrade Warning upgrade_warning, -- Dialog "label[0.3,0.6;", minetest.formspec_escape(dialog.n_npc), "]", "label[0.3,1.8;", minetest.formspec_escape(dialog.n_description), "]", "image[15.5,0.5;4,4;", portrait, "]", "textarea[0.5,5;19.6,13.5;;;", minetest.formspec_escape(active_dialog.d_text) .. "\n", "]", "container[0,18]" } h = -1 if active_dialog ~= nil and active_dialog.d_options ~= nil then -- Let's sort the options by o_sort. local sorted_buttons = {} for _, ad_v in pairs(active_dialog.d_options) do sorted_buttons[tonumber(ad_v.o_sort)] = ad_v end if #sorted_buttons > max_number_of_buttons then -- Generate arrows table.insert(formspec, "button[0.1,0;1,0.9;button_down;^]") table.insert(formspec, "button[0.1,7.0;1,0.9;button_up;v]") end local counter = 1 for _, sb_v in pairs(sorted_buttons) do local end_index = start_index + max_number_of_buttons if counter >= start_index and counter < end_index then local oid = minetest.formspec_escape(sb_v.o_id) if allowed[sb_v.o_id] == true then h = h + 1 table.insert( formspec, "button[1," .. h .. ";44,0.9;button_" .. oid .. ";" .. minetest.formspec_escape(sb_v.o_text_when_prerequisites_met) .. "]" ) table.insert( formspec, "tooltip[button_" .. oid .. ";" .. minetest.formspec_escape(sb_v.o_text_when_prerequisites_met) .. "]" ) else if sb_v.o_hide_when_prerequisites_not_met == "true" then -- hide! Nothing to do else if sb_v.o_grey_when_prerequisites_not_met == "true" and sb_v.o_text_when_prerequisites_not_met == "" then h = h + 1 local l = h - 0.2 table.insert(formspec, "box[1," .. l .. ";43,1;#BBBBBB]") table.insert( formspec, "label[3," .. h .. ";" .. yl_speak_up.message_button_option_prerequisites_not_met_default .. "]" ) end if sb_v.o_grey_when_prerequisites_not_met == "true" and sb_v.o_text_when_prerequisites_not_met ~= "" then h = h + 1 local l = h - 0.2 table.insert(formspec, "box[1," .. l .. ";43,1;#BBBBBB]") table.insert( formspec, "label[3," .. h .. ";" .. yl_speak_up.message_button_option_prerequisites_not_met_default .. " : " .. minetest.formspec_escape(sb_v.o_text_when_prerequisites_not_met) .. "]" ) end if sb_v.o_grey_when_prerequisites_not_met == "false" and sb_v.o_text_when_prerequisites_not_met == "" then -- no hide, no grey, no text end if sb_v.o_grey_when_prerequisites_not_met == "false" and sb_v.o_text_when_prerequisites_not_met ~= "" then h = h + 1 table.insert( formspec, "button[1," .. h .. ";44,0.9;button_" .. oid .. ";" .. minetest.formspec_escape(sb_v.o_text_when_prerequisites_not_met) .. "]" ) table.insert( formspec, "tooltip[button_" .. oid .. ";" .. minetest.formspec_escape(sb_v.o_text_when_prerequisites_not_met) .. "]" ) end end end end counter = counter + 1 end end h = h + 1 table.insert( formspec, "button_exit[1," .. h .. ";44,0.9;button_exit;" .. yl_speak_up.message_button_option_exit .. "]" ) table.insert(formspec, "tooltip[button_exit;" .. yl_speak_up.message_button_option_exit .. "]") table.insert(formspec, "container_end[]") table.insert(formspec, "container_end[]") end return table.concat(formspec, "") end -- receive fields -- options minetest.register_on_player_receive_fields( function(player, formname, fields) if formname ~= "yl_speak_up:optiondialog" then return end local pname = player:get_player_name() local n_id = yl_speak_up.speak_to[pname].n_id local d_id = yl_speak_up.speak_to[pname].d_id local dialog = yl_speak_up.speak_to[pname].dialog if fields then -- Button Cancel: Exit the form if fields.button_back or fields.quit then local temp_dialog = yl_speak_up.speak_to[pname].dialog yl_speak_up.speak_to[pname] = {} yl_speak_up.speak_to[pname].dialog = temp_dialog yl_speak_up.speak_to[pname].n_id = n_id yl_speak_up.speak_to[pname].d_id = d_id minetest.show_formspec(pname, "yl_speak_up:setdialog", get_fs_setdialog(player, n_id, d_id)) return end -- Button Delete Option: Delete option dialog, but do not exit if fields.button_delete and fields.d_id and fields.o_id ~= yl_speak_up.text_new_option_id then local temp_dialog = yl_speak_up.speak_to[pname].dialog yl_speak_up.speak_to[pname] = {} yl_speak_up.speak_to[pname].dialog = temp_dialog yl_speak_up.speak_to[pname].n_id = n_id yl_speak_up.speak_to[pname].d_id = d_id yl_speak_up.speak_to[pname].o_id = yl_speak_up.text_new_option_id yl_speak_up.speak_to[pname].p_id = yl_speak_up.text_new_prerequisite_id yl_speak_up.speak_to[pname].r_id = yl_speak_up.text_new_result_id local o_id = yl_speak_up.text_new_option_id local p_id = yl_speak_up.text_new_prerequisite_id local r_id = yl_speak_up.text_new_result_id delete_option(fields.n_id, fields.d_id, fields.o_id) minetest.show_formspec( pname, "yl_speak_up:optiondialog", get_fs_optiondialog(player, n_id, d_id, o_id, p_id, r_id) ) return end if fields.d_text then yl_speak_up.speak_to[pname].d_text = fields.d_text end if fields.d_type then yl_speak_up.speak_to[pname].d_type = fields.d_type end -- When a new o_id is chosen, then all the other stuff is invalidated and we need to take values from the dialog or even default values if fields.o_id ~= nil and fields.o_id ~= yl_speak_up.speak_to[pname].o_id then local o_id = fields.o_id yl_speak_up.speak_to[pname].o_id = o_id yl_speak_up.speak_to[pname].p_id = yl_speak_up.text_new_prerequisite_id yl_speak_up.speak_to[pname].r_id = yl_speak_up.text_new_result_id -- Depends on the dialog if o_id == yl_speak_up.text_new_option_id then --New dialog yl_speak_up.speak_to[pname].o_hide = "false" yl_speak_up.speak_to[pname].o_grey = "false" yl_speak_up.speak_to[pname].o_sort = "" yl_speak_up.speak_to[pname].o_met = "Text when conditions are not met" yl_speak_up.speak_to[pname].o_not_met = "Text when conditions are met" else -- existing dialog yl_speak_up.speak_to[pname].o_hide = dialog.n_dialogs[d_id].d_options[o_id].o_hide_when_prerequisites_not_met or "false" yl_speak_up.speak_to[pname].o_grey = dialog.n_dialogs[d_id].d_options[o_id].o_grey_when_prerequisites_not_met or "false" yl_speak_up.speak_to[pname].o_sort = dialog.n_dialogs[d_id].d_options[o_id].o_sort or "" yl_speak_up.speak_to[pname].o_met = dialog.n_dialogs[d_id].d_options[o_id].o_text_when_prerequisites_not_met or "Text when conditions are not met" yl_speak_up.speak_to[pname].o_not_met = dialog.n_dialogs[d_id].d_options[o_id].o_text_when_prerequisites_met or "Text when conditions are met" end else if fields.o_hide then yl_speak_up.speak_to[pname].o_hide = fields.o_hide end if fields.o_grey then yl_speak_up.speak_to[pname].o_grey = fields.o_grey end if fields.o_sort then yl_speak_up.speak_to[pname].o_sort = fields.o_sort end if fields.o_met then yl_speak_up.speak_to[pname].o_met = fields.o_met end if fields.o_not_met then yl_speak_up.speak_to[pname].o_not_met = fields.o_not_met end if fields.p_id ~= nil and fields.p_id ~= yl_speak_up.speak_to[pname].p_id then yl_speak_up.speak_to[pname].p_id = fields.p_id yl_speak_up.speak_to[pname].p_value = fields.p_value yl_speak_up.speak_to[pname].p_type = fields.p_type end if fields.p_value then yl_speak_up.speak_to[pname].p_value = fields.p_value end if fields.p_type then yl_speak_up.speak_to[pname].p_type = fields.p_type end if fields.r_id ~= nil and fields.r_id ~= yl_speak_up.speak_to[pname].r_id then yl_speak_up.speak_to[pname].r_id = fields.r_id yl_speak_up.speak_to[pname].r_value = fields.r_value yl_speak_up.speak_to[pname].r_type = fields.r_type end if fields.r_value then yl_speak_up.speak_to[pname].r_value = fields.r_value end if fields.r_type then yl_speak_up.speak_to[pname].r_type = fields.r_type end end end local o_id = fields.o_id or yl_speak_up.speak_to[pname].o_id local p_id = fields.p_id or yl_speak_up.speak_to[pname].p_id local r_id = fields.r_id or yl_speak_up.speak_to[pname].r_id -- Button Save if fields and fields.button_save and fields.n_id and fields.d_id then local temp_dialog = options_to_dialog(pname) save_dialog(n_id, temp_dialog) minetest.show_formspec( pname, "yl_speak_up:optiondialog", get_fs_optiondialog(player, fields.n_id, fields.d_id, fields.o_id, fields.p_id, fields.r_id) ) end minetest.show_formspec( pname, "yl_speak_up:optiondialog", get_fs_optiondialog(player, n_id, d_id, o_id, p_id, r_id) ) end ) -- dialog minetest.register_on_player_receive_fields( function(player, formname, fields) if formname ~= "yl_speak_up:setdialog" then return end local pname = player:get_player_name() local n_id = yl_speak_up.speak_to[pname].n_id local d_id = yl_speak_up.speak_to[pname].d_id or yl_speak_up.text_new_dialog_id -- Button Cancel: Exit the form if fields.button_cancel or fields.quit then yl_speak_up.speak_to[pname] = nil minetest.close_formspec(pname, "yl_speak_up:setdialog") return end -- Button Options with valid d_id: Show options formspec if fields.button_option and fields.d_id and fields.d_id ~= yl_speak_up.text_new_dialog_id then local o_id = yl_speak_up.text_new_option_id local p_id = yl_speak_up.text_new_prerequisite_id local r_id = yl_speak_up.text_new_result_id -- Context yl_speak_up.speak_to[pname].d_id = fields.d_id yl_speak_up.speak_to[pname].o_id = o_id yl_speak_up.speak_to[pname].p_id = p_id yl_speak_up.speak_to[pname].r_id = r_id minetest.show_formspec( pname, "yl_speak_up:optiondialog", get_fs_optiondialog(player, n_id, fields.d_id, o_id, p_id, r_id) ) return end -- Button Options with invalid d_id: Show options formspec if fields.button_option and fields.d_id and fields.d_id == yl_speak_up.text_new_dialog_id then yl_speak_up.speak_to[pname].d_id = yl_speak_up.text_new_dialog_id minetest.show_formspec(pname, "yl_speak_up:optiondialog", get_error_message()) return end -- Button Save: Save the settings, but do not exit if fields.button_save and fields.d_id then local dialog = fields_to_dialog(pname, fields) save_dialog(n_id, dialog) yl_speak_up.speak_to[pname].dialog = dialog if yl_speak_up.speak_to[pname].obj then local obj = yl_speak_up.speak_to[pname].obj local ent = obj:get_luaentity() if ent ~= nil then ent.yl_speak_up.npc_name = dialog.n_npc ent.yl_speak_up.npc_description = dialog.n_description ent.owner = dialog.npc_owner local i_text = dialog.n_npc .. "\n" .. dialog.n_description .. "\n" .. yl_speak_up.infotext obj:set_properties({infotext = i_text}) yl_speak_up.update_nametag(ent) end end minetest.show_formspec(pname, "yl_speak_up:setdialog", get_fs_setdialog(player, n_id, d_id)) return end -- Button Delete: Delete dialog, but do not exit if fields.button_delete and fields.d_id then delete_dialog(n_id, fields.d_id) yl_speak_up.speak_to[pname].d_id = yl_speak_up.text_new_dialog_id minetest.show_formspec( pname, "yl_speak_up:setdialog", get_fs_setdialog(player, n_id, yl_speak_up.text_new_dialog_id) ) end -- Change in Dropdown List: Show dialog formspec again with different dialog selected if fields.d_id then yl_speak_up.speak_to[pname].d_id = fields.d_id minetest.show_formspec(pname, "yl_speak_up:setdialog", get_fs_setdialog(player, n_id, fields.d_id)) end end ) -- talk -- helper function yl_speak_up.add_new_dialog = function(dialog, pname) local next_id = find_next_id(dialog.n_dialogs) local future_d_id = "d_" .. next_id -- Initialize empty dialog dialog.n_dialogs[future_d_id] = { d_id = future_d_id, d_type = "text", d_text = "", d_sort = next_id } -- store that there have been changes to this npc -- (better ask only when the new dialog is changed) -- table.insert(yl_speak_up.npc_was_changed[ yl_speak_up.edit_mode[pname] ], -- "Dialog "..future_d_id..": New dialog added.") return future_d_id end -- this is always called when switching to a new dialog; but only in edit_mode does -- it show a formspec asking for change/back/discard yl_speak_up.save_changes_and_switch_to_other_dialog = function(player, fields, target_dialog) if(not(target_dialog)) then target_dialog = "" end local pname = player:get_player_name() -- if the player is not even talking to this particular npc if(not(yl_speak_up.speak_to[pname])) then return end local edit_mode = (yl_speak_up.edit_mode[pname] == yl_speak_up.speak_to[pname].n_id) local d_id = yl_speak_up.speak_to[pname].d_id local n_id = yl_speak_up.speak_to[pname].n_id -- the player decided to go back and continue editing the current dialog if(edit_mode and fields.back_to_dialog_changes) then -- do NOT clear the list of changes; just show the old dialog again minetest.show_formspec(pname, "yl_speak_up:talk", get_fs_talkdialog(player, n_id, d_id)) return -- save changes and continue on to the next dialog elseif(edit_mode and fields.save_dialog_changes) then -- actually save the dialog (the one the NPC currently has) save_dialog(n_id, yl_speak_up.speak_to[pname].dialog) -- clear list of changes yl_speak_up.npc_was_changed[ n_id ] = {} -- discard changes and continue on to the next dialog elseif(edit_mode and fields.discard_dialog_changes) then -- actually restore the old state and discard the changes by loading the dialog anew yl_speak_up.speak_to[pname].dialog = load_dialog(n_id) -- clear list of changes yl_speak_up.npc_was_changed[ n_id ] = {} -- it is possible that the target dialog was newly added - and just discarded; -- in such a case stick to the old dialog local dialog = yl_speak_up.speak_to[pname].dialog if(not(dialog[target_dialog])) then target_dialog = yl_speak_up.speak_to[pname].d_id end end -- are there any changes which might be saved or discarded? if(edit_mode and yl_speak_up.npc_was_changed[ n_id ] and #yl_speak_up.npc_was_changed[ n_id ] > 0) then local target_name = "quit" if(target_dialog and target_dialog ~= "") then target_name = "go on to dialog "..minetest.formspec_escape(target_dialog) end local d_id = yl_speak_up.speak_to[pname].d_id -- reverse the order of the changes in the log so that newest are topmost local text = "" for i,t in ipairs(yl_speak_up.npc_was_changed[ n_id ]) do text = minetest.formspec_escape(t).."\n"..text end -- build a formspec showing the changes to this dialog and ask for save local formspec = "formspec_version[3]".. "size[14,6.2]".. "bgcolor[#00000000;false]".. "label[0.2,0.2;You are about to leave dialog "..minetest.formspec_escape(d_id).. " and "..target_name..".]".. "label[0.2,0.65;These changes have been applied to dialog ".. minetest.formspec_escape(d_id)..":]".. "hypertext[0.2,1;13.5,4;list_of_changes;".. minetest.formspec_escape(text) .. "\n".."]".. "button[1.2,5.2;3,0.9;discard_dialog_changes;Discard changes]".. "button[5.7,5.2;3,0.9;back_to_dialog_changes;Back]".. "button[10.2,5.2;3,0.9;save_dialog_changes;Save changes]".. "tooltip[save_dialog_changes;Save all changes to this dialog and "..target_name..".]".. "tooltip[discard_dialog_changes;Undo all changes and "..target_name..".]".. "tooltip[back_to_dialog_changes;Go back to dialog ".. minetest.formspec_escape(d_id).." and continue editing it.]".. -- hidden field telling about the target dialog "field[20,20;0.1,0.1;goto_target_dialog;target_dialog;"..minetest.formspec_escape(target_dialog).."]" -- actaully show the formspec to the player minetest.show_formspec(pname, "yl_speak_up:confirm_save", formspec) return end -- if there is nothing that needs to be saved or discarded: allow quit/exit if fields.quit or fields.button_exit then yl_speak_up.edit_mode[pname] = nil -- Formspec was quit yl_speak_up.speak_to[pname] = nil return end -- end edit mode and start a new chat - but this time in normal mode if not(target_dialog) or target_dialog == "" then yl_speak_up.edit_mode[pname] = nil target_dialog = nil end -- move on to the target dialog yl_speak_up.speak_to[pname].d_id = target_dialog minetest.show_formspec(pname, "yl_speak_up:talk", get_fs_talkdialog(player, n_id, target_dialog)) end -- helper function for sorting options/answers using options[o_id].o_sort -- (or dialogs by d_sort) yl_speak_up.get_sorted_options = function(options, sort_by) local sorted_list = {} for k,v in pairs(options) do table.insert(sorted_list, k) end table.sort(sorted_list, function(a,b) -- sadly not all entries are numeric if(tonumber(options[a][sort_by]) and tonumber(options[b][sort_by])) then return (tonumber(options[a][sort_by]) < tonumber(options[b][sort_by])) -- numbers have a higher priority elseif(tonumber(options[a][sort_by])) then return true elseif(tonumber(options[b][sort_by])) then return false -- if the value is the same: sort by index elseif(options[a][sort_by] == options[b][sort_by]) then return (a < b) else return (options[a][sort_by] < options[b][sort_by]) end end ) return sorted_list end -- helper function for formspec "yl_speak_up:talk" *and* formspec "yl_speak_up:edit_option_dialog" -- when a parameter was changed in edit mode; -- this is called when the player is in edit_mode (editing the NPC); -- the function checks if the player has changed any parameters -- Parameters: -- pname player name -- fields the fields returned from the formspec -- Returns: -- result table with information about what was added -- (for now, only result.show_next_option is of intrest in the option edit menu) yl_speak_up.edit_mode_apply_changes = function(pname, fields) local n_id = yl_speak_up.edit_mode[pname] local d_id = yl_speak_up.speak_to[pname].d_id local dialog = yl_speak_up.speak_to[pname].dialog -- this way we can store the actual changes and present them to the player for saving if(not(yl_speak_up.npc_was_changed[ n_id ])) then yl_speak_up.npc_was_changed[ n_id ] = {} end -- nothing to do if that dialog does not exist if(not(d_id) or not(dialog.n_dialogs) or not(dialog.n_dialogs[ d_id ])) then return end -- new options etc. may be added; store these IDs so that we can switch to the right target local result = {} -- make this the first dialog shown when starting a conversation if(fields.make_first_option) then -- check which dialog(s) previously had the highest priority and change thsoe for k, v in pairs(dialog.n_dialogs) do if(v and v.d_sort and (v.d_sort=="0" or v.d_sort==0)) then -- try to derive a sensible future sort priority from the key: -- here we make use of the d_ pattern; but even if that fails to yield -- a number, the sort function will later be able to deal with it anyway local new_priority = string.sub(k, 3) dialog.n_dialogs[ k ].d_sort = new_priority end end -- actually make this the chat with the highest priority dialog.n_dialogs[ d_id ].d_sort = "0" -- this is not immediately saved, even though the changes to the previous dialog with -- the highest priority cannot be automaticly undone (but as long as it is not saved, -- it really does not matter; and when saving, the player has to take some care) table.insert(yl_speak_up.npc_was_changed[ n_id ], "Dialog "..d_id..": Turned into new start dialog.") end -- detect changes to d_text: text of the dialog (what the npc is saying) -- (only happens in dialog edit menu) if(fields.d_text and dialog.n_dialogs[ d_id ].d_text ~= fields.d_text) then -- store that there have been changes to this npc table.insert(yl_speak_up.npc_was_changed[ n_id ], "Dialog "..d_id..": d_text (what the NPC says) was changed from \"".. tostring( dialog.n_dialogs[ d_id ].d_text).. "\" to \""..tostring(fields.d_text).."\".") -- actually change the text - but do not save to disk yet dialog.n_dialogs[ d_id ].d_text = fields.d_text end -- add a new option/answer if(fields[ "add_option"]) then -- count the options/answers so that the limit is not exceeded local anz_options = 0 local future_o_id = "o_" .. find_next_id(dialog.n_dialogs[d_id].d_options) if dialog.n_dialogs[d_id].d_options == nil then dialog.n_dialogs[d_id].d_options = {} else local sorted_list = yl_speak_up.get_sorted_options(dialog.n_dialogs[d_id].d_options, "o_sort") anz_options = #sorted_list end -- we don't want an infinite amount of answers per dialog if(anz_options >= yl_speak_up.max_number_of_options_per_dialog) then minetest.chat_send_player(pname, "Sorry. Only ".. tostring(yl_speak_up.max_number_of_options_per_dialog).. " options/answers are allowed per dialog.") fields.add_option = nil else dialog.n_dialogs[d_id].d_options[future_o_id] = { o_id = future_o_id, o_hide_when_prerequisites_not_met = "false", o_grey_when_prerequisites_not_met = "false", o_sort = -1, o_text_when_prerequisites_not_met = "", o_text_when_prerequisites_met = "", } -- necessary in order for it to work local s = sanitize_sort(dialog.n_dialogs[d_id].d_options, yl_speak_up.speak_to[pname].o_sort) dialog.n_dialogs[d_id].d_options[future_o_id].o_sort = s table.insert(yl_speak_up.npc_was_changed[ n_id ], "Dialog "..d_id..": Added new option/answer "..future_o_id..".") -- if this is selected in the options edit menu, we want to move straight on to the new option result["show_next_option"] = future_o_id end end if(fields[ "del_option"] and fields.o_id and dialog.n_dialogs[d_id].d_options[fields.o_id]) then local o_id = fields.o_id -- which dialog to show instead of the deleted one? local next_o_id = o_id local sorted_list = yl_speak_up.get_sorted_options(dialog.n_dialogs[d_id].d_options, "o_sort") for i, o in ipairs(sorted_list) do if(o == o_id and sorted_list[ i+1 ]) then next_o_id = sorted_list[ i+1 ] elseif(o == o_id and sorted_list[ i-1 ]) then next_o_id = sorted_list[ i-1 ] end end table.insert(yl_speak_up.npc_was_changed[ n_id ], "Dialog "..d_id..": Option "..tostring(o_id).." deleted.") -- actually delete the dialog dialog.n_dialogs[d_id].d_options[o_id] = nil -- the current dialog is deleted; we need to show another one result["show_next_option"] = next_o_id -- after deleting the entry, all previous/further changes to it are kind of unintresting return result end -- ignore entries to o_sort if they are not a number if(fields[ "edit_option_o_sort"] and tonumber(fields[ "edit_option_o_sort"]) and fields.o_id and dialog.n_dialogs[d_id].d_options[fields.o_id]) then local o_id = fields.o_id local new_nr = tonumber(fields[ "edit_option_o_sort"]) local old_nr = tonumber(dialog.n_dialogs[d_id].d_options[o_id].o_sort) -- if the nr is -1 (do not show) then we are done already: nothing to do if(old_nr == new_nr) then -- -1: do not list as option/answer (but still store and keep it) elseif(new_nr == -1 and old_nr ~= -1) then dialog.n_dialogs[d_id].d_options[o_id].o_sort = "-1" table.insert(yl_speak_up.npc_was_changed[ n_id ], "Dialog "..d_id..": Option "..tostring(o_id).." was set to -1 (do not list).") else -- get the old sorted list local sorted_list = yl_speak_up.get_sorted_options(dialog.n_dialogs[d_id].d_options, "o_sort") -- negative numbers are not shown local entries_shown_list = {} for i, o in ipairs(sorted_list) do local n = tonumber(dialog.n_dialogs[d_id].d_options[o].o_sort) if(n and n > 0 and o ~= o_id) then table.insert(entries_shown_list, o) end end -- insert the entry at the new position and let lua do the job table.insert(entries_shown_list, new_nr, o_id) -- take the indices from that new list as new sort values and store them; -- this has the side effect that duplicate entries get sorted out as well for i, o in ipairs(entries_shown_list) do dialog.n_dialogs[d_id].d_options[o].o_sort = tostring(i) end -- store that there was a cahnge table.insert(yl_speak_up.npc_was_changed[ n_id ], "Dialog "..d_id..": Option "..tostring(o_id).." was moved to position ".. tostring(new_nr)..".") end end -- changes to options are not possible if there are none if(dialog.n_dialogs[ d_id ].d_options) then -- detect changes to text_option_: text for option for k, v in pairs(dialog.n_dialogs[ d_id ].d_options) do if( fields[ "text_option_"..k ] and fields[ "text_option_"..k ] ~= v.o_text_when_prerequisites_met ) then -- store that there have been changes to this npc table.insert(yl_speak_up.npc_was_changed[ n_id ], "Dialog "..d_id..": The text for option "..tostring(k).. " was changed from \""..tostring(v.o_text_when_prerequisites_met).. "\" to \""..tostring(fields[ "text_option_"..k]).."\".") -- actually change the text of the option dialog.n_dialogs[ d_id ].d_options[ k ].o_text_when_prerequisites_met = fields[ "text_option_"..k ] end end -- detect changes to d_id_: target dialog for option for k, v in pairs(dialog.n_dialogs[ d_id ].d_options) do if( fields[ "d_id_"..k ] and v.o_results) then for kr, vr in pairs(v.o_results) do if( vr.r_type == "dialog" and vr.r_value and vr.r_value ~= fields[ "d_id_"..k ]) then -- this may also point to a new dialog if(fields[ "d_id_"..k ] == yl_speak_up.text_new_dialog_id) then -- create a new dialog and show it as new target dialog - but do not display this dialog directly (the player may follow the -> button) fields[ "d_id_"..k ] = yl_speak_up.add_new_dialog(dialog, pname) end -- store that there have been changes to this npc table.insert(yl_speak_up.npc_was_changed[ n_id ], "Dialog "..d_id..": The target dialog for option ".. tostring(k).." was changed from ".. tostring(vr.r_value).. " to "..tostring(fields[ "d_id_"..k])..".") -- actually change the target dialog vr.r_value = fields[ "d_id_"..k ] -- in options edit menu: show this update result["show_next_option"] = k end end -- this might be the first result elseif( fields["d_id_"..k ]) then -- this may also point to a new dialog if(fields[ "d_id_"..k ] == yl_speak_up.text_new_dialog_id) then -- create a new dialog and show it as new target dialog - but do not display this dialog directly (the player may follow the -> button) fields[ "d_id_"..k ] = yl_speak_up.add_new_dialog(dialog, pname) end -- store that a new option has been added to this dialog table.insert(yl_speak_up.npc_was_changed[ n_id ], "Dialog "..d_id..": The target dialog for option ".. tostring(k).." was changed from -default- to ".. tostring(fields[ "d_id_"..k])..".") -- create a new result (first the id, then the actual result) local future_r_id = "r_" .. find_next_id(dialog.n_dialogs[d_id].d_options[k].o_results) if future_r_id == "r_1" then dialog.n_dialogs[d_id].d_options[k].o_results = {} end -- actually store the new result dialog.n_dialogs[d_id].d_options[k].o_results[future_r_id] = { r_id = future_r_id, r_type = "dialog", r_value = fields[ "d_id_"..k ]} -- in options edit menu: show this update result["show_next_option"] = k end end end -- add a new dialog; either via "+" button or "New dialog" in dialog dropdown menu -- this has to be done after all the other changes because those (text changes etc.) still -- apply to the *old* dialog if(fields.show_new_dialog or(fields["d_id"] and fields["d_id"] == yl_speak_up.text_new_dialog_id)) then -- create the new dialog and make sure it gets shown local d_id = yl_speak_up.add_new_dialog(dialog, pname) -- actually show the new dialog fields["d_id"] = d_id fields["show_new_dialog"] = nil end -- delete one empty dialog if(fields.delete_this_empty_dialog) then local anz_options = 0 -- we need to show a new dialog after this one was deleted local new_dialog = d_id local sorted_list = yl_speak_up.get_sorted_options(dialog.n_dialogs, "d_sort") for i, k in ipairs(sorted_list) do -- count the options of this dialog if(k == d_id) then if(dialog.n_dialogs[d_id].d_options) then for o, w in pairs(dialog.n_dialogs[d_id].d_options) do anz_options = anz_options + 1 end end if(sorted_list[i+1]) then new_dialog = sorted_list[i+1] elseif(sorted_list[i-1]) then new_dialog = sorted_list[i-1] end end end -- there needs to be one dialog left after deleting this one, if(#sorted_list > 1 -- this dialog isn't allowed to hold any more options/answers and anz_options == 0 -- we really found a new dialog to show and new_dialog ~= d_id -- and the text needs to be empty and dialog.n_dialogs[ d_id ].d_text == "") then -- actually delete this dialog dialog.n_dialogs[ d_id ] = nil -- ..and store it to disk delete_dialog(n_id, d_id) -- switch to another dialog (this one was deleted after all) fields["d_id"] = new_dialog fields["show_new_dialog"] = nil else minetest.chat_send_player(pname, "Sorry. This dialog cannot be deleted (yet). ".. "It is either the only dialog left or has a non-empty text or has at ".. "least on remaining option/answer.") end end -- not in options edit menu? local o_id = fields.o_id if(not(o_id)) then return result end local d_option = dialog.n_dialogs[ d_id ].d_options[ o_id ] -- change alternate text when preconditions are not met -- (only happens in options edit menu) if(fields.option_text_not_met and d_option and d_option.o_text_when_prerequisites_not_met ~= fields.option_text_not_met) then -- add change to changelog table.insert(yl_speak_up.npc_was_changed[ n_id ], "Dialog "..d_id..": The alternate text for option "..tostring(o_id).. " was changed from \"".. tostring(d_option.o_text_when_prerequisites_not_met).."\" to \"".. tostring(fields.option_text_not_met).."\".") -- actually change the text of the option d_option.o_text_when_prerequisites_not_met = fields.option_text_not_met end -- handle hide/grey out/show alternate answer -- (only happens in options edit menu) if(fields.hide_or_grey_or_alternate_answer and d_option) then if(fields.hide_or_grey_or_alternate_answer == "..hide this answer." and d_option.o_hide_when_prerequisites_not_met ~= "true") then d_option.o_hide_when_prerequisites_not_met = "true" d_option.o_grey_when_prerequisites_not_met = "false" table.insert(yl_speak_up.npc_was_changed[ n_id ], "Dialog "..d_id..": If precondition for option "..tostring(o_id).. " is not met, hide option/answer.") -- make sure we show this options update next result["show_next_option"] = o_id elseif(fields.hide_or_grey_or_alternate_answer == "..grey out this answer." and d_option.o_grey_when_prerequisites_not_met ~= "true") then d_option.o_hide_when_prerequisites_not_met = "false" d_option.o_grey_when_prerequisites_not_met = "true" table.insert(yl_speak_up.npc_was_changed[ n_id ], "Dialog "..d_id..": If precondition for option "..tostring(o_id).. " is not met, grey out option/answer.") result["show_next_option"] = o_id elseif(fields.hide_or_grey_or_alternate_answer == "..display the following alternate answer:" and (d_option.o_hide_when_prerequisites_not_met ~= "false" or d_option.o_grey_when_prerequisites_not_met) ~= "false") then d_option.o_hide_when_prerequisites_not_met = "false" d_option.o_grey_when_prerequisites_not_met = "false" table.insert(yl_speak_up.npc_was_changed[ n_id ], "Dialog "..d_id..": If precondition for option "..tostring(o_id).. " is not met, show alternate option/answer.") result["show_next_option"] = o_id end end -- currently only contains result["show_new_option"] (which is needed for options edit menu) return result end -- end of yl_speak_up.edit_mode_apply_changes minetest.register_on_player_receive_fields( function(player, formname, fields) if formname ~= "yl_speak_up:confirm_save" then return end yl_speak_up.save_changes_and_switch_to_other_dialog(player, fields, fields.goto_target_dialog) end ) -- process input from formspec created in get_fs_edit_option_dialog(..) minetest.register_on_player_receive_fields( function(player, formname, fields) if formname ~= "yl_speak_up:edit_option_dialog" then return end local pname = player:get_player_name() -- Is the player working on this particular npc? local edit_mode = (yl_speak_up.edit_mode[pname] == yl_speak_up.speak_to[pname].n_id) if(not(edit_mode) or not(fields.o_id)) then return end local n_id = yl_speak_up.speak_to[pname].n_id local d_id = yl_speak_up.speak_to[pname].d_id -- this is passed on as a hidden field local o_id = fields.o_id local dialog = yl_speak_up.speak_to[pname].dialog local n_dialog = dialog.n_dialogs[d_id] local d_option = n_dialog.d_options[o_id] -- this meny is specific to an option for a dialog; if no dialog is selected, we really -- can't know what to do if(not(d_id)) then return end -- handles changes to o_text_when_prerequisites_met, target dialog, adding of a new dialog local result = yl_speak_up.edit_mode_apply_changes(pname, fields) -- if a new option was added or the target dialog of this one changed, display the right new option if(result and result["show_next_option"] and n_dialog.d_options[result["show_next_option"]]) then minetest.show_formspec(pname, "yl_speak_up:edit_option_dialog", get_fs_edit_option_dialog(player, n_id, d_id, result["show_next_option"])) return end -- back to the main dialog window? -- (this also happens when the last option was deleted) if(fields.show_current_dialog or fields.quit or fields.button_exit or not(d_option) or fields.del_option) then minetest.show_formspec(pname, "yl_speak_up:talk", get_fs_talkdialog(player, n_id, d_id)) return end -- the player wants to see the previous option/answer if(fields.edit_option_prev) then -- sort all options by o_sort local sorted_list = yl_speak_up.get_sorted_options(n_dialog.d_options, "o_sort") local o_found = o_id for i, o in ipairs(sorted_list) do if(o == o_id and sorted_list[ i-1]) then o_found = sorted_list[ i-1 ] end end -- show that dialog; fallback: show the same (o_id) again minetest.show_formspec(pname, "yl_speak_up:edit_option_dialog", get_fs_edit_option_dialog(player, n_id, d_id, o_found)) return -- the player wants to see the next option/answer elseif(fields.edit_option_next) then -- sort all options by o_sort local sorted_list = yl_speak_up.get_sorted_options(n_dialog.d_options, "o_sort") local o_found = o_id for i, o in ipairs(sorted_list) do if(o == o_id and sorted_list[ i+1 ]) then o_found = sorted_list[ i+1 ] end end -- show that dialog; fallback: show the same (o_id) again minetest.show_formspec(pname, "yl_speak_up:edit_option_dialog", get_fs_edit_option_dialog(player, n_id, d_id, o_found)) return end --minetest.chat_send_player(pname, "Fields: "..minetest.serialize(fields)) -- if ESC is pressed or anything else unpredicted happens: go back to the main dialog edit window -- reason: don't loose any unsaved changes to the dialog minetest.show_formspec(pname, "yl_speak_up:talk", get_fs_talkdialog(player, n_id, d_id)) end ) -- identify multiple results that lead to target dialogs yl_speak_up.check_for_disambigous_results = function(n_id, pname) local errors_found = false local dialog = load_dialog(n_id) -- nothing defined yet - nothing to repair if(not(dialog.n_dialogs)) then return end -- iterate over all dialogs for d_id, d in pairs(dialog.n_dialogs) do if(d_id and d and d.d_options) then -- iterate over all options for o_id, o in pairs(d.d_options) do if(o_id and o and o.o_results) then local dialog_results = {} -- iterate over all results for r_id, r in pairs(o.o_results) do if(r.r_type == "dialog") then table.insert(dialog_results, r_id) end end if(#dialog_results>1) then minetest.chat_send_player(pname, "ERROR: Dialog ".. tostring(d_id)..", option "..tostring(o_id).. ", has multiple results of type dialog: ".. minetest.serialize(dialog_results)..". Please ".. "let someone with npc_master priv fix that first!") errors_found = true end end end end end return errors_found end minetest.register_on_player_receive_fields( function(player, formname, fields) if formname ~= "yl_speak_up:talk" then return end local pname = player:get_player_name() local o = "" local n_id = yl_speak_up.speak_to[pname].n_id -- the player is trying to save the initial configuration if(fields.save_initial_config) then -- is the player allowed to initialize this npc? if(not(yl_speak_up.npc_owner[ n_id ] == pname and minetest.check_player_privs(player, {npc_talk_owner=true})) and not(minetest.check_player_privs(player, {npc_master=true}))) then return end if(not(fields.n_npc) or string.len(fields.n_npc) < 2) then minetest.chat_send_player(pname, "The name of your NPC needs to be at least two characters long.") return end if(not(fields.n_description) or string.len(fields.n_description) < 2) then minetest.chat_send_player(pname, "Please provide a description of your NPC!") return end -- sensible length limit if(string.len(fields.n_npc)>40 or string.len(fields.n_description)>40) then minetest.chat_send_player(pname, "The name and description of your NPC cannot be longer than 40 characters.") return end local dialog = yl_speak_up.speak_to[pname].dialog local d_id = yl_speak_up.speak_to[pname].d_id local count = 0 for k,v in pairs(dialog) do count = count + 1 end -- give the NPC its first dialog if(not(dialog) or count==0) then local f = {} -- create a new dialog f.d_id = yl_speak_up.text_new_dialog_id -- ...with this text f.d_text = "Hi. I am "..tostring(fields.n_npc)..". I don't know much yet.\n".. "Hopefully "..tostring(pname).." will teach me to talk soon." -- it is the first, initial dialog f.d_sort = "0" f.n_npc = fields.n_npc f.n_description = fields.n_description f.npc_owner = yl_speak_up.npc_owner[ n_id ] -- create and save the first dialog for this npc local dialog = fields_to_dialog(pname, f) save_dialog(n_id, dialog) yl_speak_up.speak_to[pname].dialog = dialog -- just change name and description else dialog = yl_speak_up.speak_to[pname].dialog dialog.n_npc = fields.n_npc dialog.n_description = fields.n_description save_dialog(n_id, dialog) end dialog = yl_speak_up.speak_to[pname].dialog -- show nametag etc. if yl_speak_up.speak_to[pname].obj then local obj = yl_speak_up.speak_to[pname].obj local ent = obj:get_luaentity() if ent ~= nil then ent.yl_speak_up.npc_name = dialog.n_npc ent.yl_speak_up.npc_description = dialog.n_description ent.owner = dialog.npc_owner local i_text = dialog.n_npc .. "\n" .. dialog.n_description .. "\n" .. yl_speak_up.infotext obj:set_properties({infotext = i_text}) yl_speak_up.update_nametag(ent) end end -- actually start a chat with our new npc minetest.show_formspec(pname, "yl_speak_up:talk", get_fs_talkdialog(player, n_id, d_id)) return end -- Is the player working on this particular npc? local edit_mode = (yl_speak_up.edit_mode[pname] == yl_speak_up.speak_to[pname].n_id) -- if in edit mode: detect if something was changed; if(edit_mode or fields.button_edit_name_and_description) then yl_speak_up.edit_mode_apply_changes(pname, fields) end -- the player wants to change name and description; show the formspec if(edit_mode and fields.button_edit_name_and_description) then -- this is not the initial config - but the same formspec can be used minetest.show_formspec(pname, "yl_speak_up:talk", get_fs_initial_config(player, n_id, yl_speak_up.speak_to[pname].d_id, false)) return end -- start edit mode (requires npc_talk_owner) if fields.button_start_edit_mode then -- check if this particular NPC is really owned by this player or if the player has global privs if(not(yl_speak_up.npc_owner[ yl_speak_up.speak_to[pname].n_id ] == pname and minetest.check_player_privs(player, {npc_talk_owner=true})) and not(minetest.check_player_privs(player, {npc_master=true}))) then minetest.chat_send_player(pname, "Sorry. You do not have the npc_talk_owner or npc_master priv.") return end -- the staff allows to create multiple target dialogs as result; this makes no sense -- and is too disambigous if(yl_speak_up.check_for_disambigous_results(n_id, pname)) then -- this needs to be fixed by someone with a staff; we don't know which dialog is the right -- result return end -- enter edit mode with that particular NPC yl_speak_up.edit_mode[pname] = yl_speak_up.speak_to[pname].n_id -- start a new chat - but this time in edit mode yl_speak_up.speak_to[pname].d_id = nil minetest.show_formspec(pname, "yl_speak_up:talk", get_fs_talkdialog(player, yl_speak_up.speak_to[pname].n_id, nil)) return -- end edit mode (does not require the priv; will only switch back to normal behaviour) elseif fields.button_end_edit_mode then -- if there are any changes done: ask first and don't end edit mode yet yl_speak_up.save_changes_and_switch_to_other_dialog(player, fields, nil) return end if fields.quit or fields.button_exit then -- if there are any changes done: ask first and don't quit yet yl_speak_up.save_changes_and_switch_to_other_dialog(player, fields, nil) return end if fields.button_up then yl_speak_up.speak_to[pname].option_index = yl_speak_up.speak_to[pname].option_index + yl_speak_up.max_number_of_buttons minetest.show_formspec( pname, "yl_speak_up:talk", get_fs_talkdialog(player, yl_speak_up.speak_to[pname].n_id, yl_speak_up.speak_to[pname].d_id) ) return elseif fields.button_down then --and yl_speak_up.speak_to[pname].option_index > yl_speak_up.max_number_of_buttons then yl_speak_up.speak_to[pname].option_index = yl_speak_up.speak_to[pname].option_index - yl_speak_up.max_number_of_buttons if yl_speak_up.speak_to[pname].option_index < 0 then yl_speak_up.speak_to[pname].option_index = 1 end minetest.show_formspec( pname, "yl_speak_up:talk", get_fs_talkdialog(player, yl_speak_up.speak_to[pname].n_id, yl_speak_up.speak_to[pname].d_id) ) return else yl_speak_up.speak_to[pname].option_index = 1 end for k, v in pairs(fields) do local s = string.split(k, "_") if s[1] == "button" and s[2] ~= nil and s[2] ~= "" and s[2] ~= "exit" and s[2] ~= "back" and s[3] ~= nil and s[2] ~= "up" and s[2] ~= "down" then o = s[2] .. "_" .. s[3] end end if not(edit_mode) and o == "" then return end -- Let's check if the button was among the "allowed buttons". Only those may be executed if not(edit_mode) and (yl_speak_up.speak_to[pname].allowed and yl_speak_up.speak_to[pname].allowed[o] == false) then return end -- button was clicked, now let's execute the results local d_id = yl_speak_up.speak_to[pname].d_id local dialog = yl_speak_up.speak_to[pname].dialog -- all three buttons (pre(C)onditions, (Ef)fects, edit option) lead to the same new formspec if( edit_mode ) then local n_dialog = dialog.n_dialogs[d_id] if(n_dialog and n_dialog.d_options) then for o_id,v in pairs(n_dialog.d_options) do if( fields["edit_option_"..o_id] or fields["conditions_"..o_id] or fields["effects_"..o_id]) then minetest.show_formspec(pname, "yl_speak_up:edit_option_dialog", get_fs_edit_option_dialog(player, yl_speak_up.speak_to[pname].n_id, d_id, o_id)) return end end end end -- in edit mode: another dialog was selected if ( o == "" and edit_mode) then -- if nothing better can be found: keep the old dialog local show_dialog = d_id -- dropdown menu was used; provided the dialog exists (and it's not the "New dialog" option) -- (if a new dialog was added using the "+" button, fields.d_id gets set accordingly) if(fields.d_id and fields.d_id ~= show_dialog and dialog.n_dialogs[fields.d_id]) then show_dialog = fields.d_id -- in edit mode: prev_dialog_../next_dialog_.. was selected else for k,v in pairs(dialog.n_dialogs) do if(fields["prev_dialog_"..k]) then show_dialog = k elseif(fields["next_dialog_"..k]) then show_dialog = k end end end -- if there are any changes done: ask first and don't switch to the new dialog yet if(show_dialog ~= d_id) then yl_speak_up.save_changes_and_switch_to_other_dialog(player, fields, show_dialog) -- show the same dialog again else minetest.show_formspec(pname, "yl_speak_up:talk", get_fs_talkdialog(player, n_id, d_id)) end -- no option was selected - so we need to end this here return end local n_dialog = dialog.n_dialogs[d_id] -- if there are no options, there can be no results either if(not(n_dialog) or not(n_dialog.d_options)) then return end local d_option = n_dialog.d_options[o] -- Let's do something if results exist if d_option.o_results ~= nil then for k, v in pairs(d_option.o_results) do -- in edit_mode, only the switching to the next dialog is executed (we want to edit, -- not solve quests, get/take items, teleport...) if not(edit_mode) and v.r_type == "give_item" then local item = ItemStack(v.r_value) if minetest.registered_items[item:get_name()] ~= nil then local r = player:get_inventory():add_item("main", item) else say("Item not found!") end end if not(edit_mode) and v.r_type == "take_item" then local item = ItemStack(v.r_value) if minetest.registered_items[item:get_name()] ~= nil then local r = player:get_inventory():remove_item("main", item) else say("Item not found!") end end if not(edit_mode) and v.r_type == "move" then local target_pos = nil local target_pos_valid = false --pos like (100,20,400) if minetest.string_to_pos(v.r_value) then target_pos = minetest.string_to_pos(v.r_value) target_pos_valid = true end --pos like 100,20,400 local maybe = string.split(v.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(v.r_value,1,1) == "{" and string.sub(v.r_value,-1,-1) == "}" then local might_be_pos = minetest.deserialize("return " .. v.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 say("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 = 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 move player "..pname.." because the content of "..v.r_id.." is wrong:"..dump(v.r_value)) else minetest.log("error","[MOD] yl_speak_up: NPC with unknown ID or without proper object could not move player "..dump(pname).." because the content of "..v.r_id.." is wrong:"..dump(v.r_value)) end end end if not(edit_mode) and v.r_type == "function" then local code = v.r_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 = 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 "..v.r_id.." :"..dump(v.r_value) .. " 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 "..v.r_id.." :"..dump(v.r_value) .. " for player unknown because of illegal bytecode") end end local f, msg = loadstring("return function(player) " .. 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 = 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 "..v.r_id.." :"..dump(v.r_value) .. " for player "..pname) else minetest.log("error","[MOD] yl_speak_up: NPC with ID unknown could not compile the content of "..v.r_id.." :"..dump(v.r_value) .. " 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 = 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 "..v.r_id.." :"..dump(v.r_value) .. " for player "..pname) else minetest.log("error","[MOD] yl_speak_up: NPC with ID unknown could not execute the content of "..v.r_id.." :"..dump(v.r_value) .. " for player unknown") end end end end if v.r_type == "dialog" then if dialog.n_dialogs[v.r_value] ~= nil then -- if there are any changes done: ask first and don't switch to the new dialog yet yl_speak_up.save_changes_and_switch_to_other_dialog(player, fields, v.r_value) return else say("This dialog does not exist") end end if v.r_type == "auto" then say("auto forward") end end end end ) -- Make the NPC talk function yl_speak_up.config(clicker, npc) if not clicker and not clicker:is_player() then return end if not npc then return end local npc_id = npc:get_luaentity().yl_speak_up.id local n_id = "n_" .. npc_id local pname = clicker:get_player_name() yl_speak_up.speak_to[pname] = {} yl_speak_up.speak_to[pname].n_id = n_id -- Memorize which player talks to which NPC yl_speak_up.speak_to[pname].d_id = yl_speak_up.text_new_dialog_id -- The only d_id we can rely on existing yl_speak_up.speak_to[pname].dialog = load_dialog(n_id) -- Load the dialog and see what we can do with it yl_speak_up.speak_to[pname].obj = npc -- find out who owns the npc while we still have easy access to the luaentity yl_speak_up.npc_owner[ n_id ] = npc:get_luaentity().owner minetest.show_formspec( pname, "yl_speak_up:setdialog", get_fs_setdialog(clicker, n_id, yl_speak_up.text_new_dialog_id) ) end --### -- Mob functions --### function yl_speak_up.on_rightclick(self, clicker) --local item = clicker:get_wielded_item() local name = clicker:get_player_name() -- Take the mob only with net or lasso if self.owner and self.owner == name then if mobs:capture_mob(self, clicker, nil, 100, 100, true, nil) then return end end -- protect npc with mobs:protector if mobs:protect(self, clicker) then return end -- bring up the dialog options if clicker then yl_speak_up.talk(self, clicker) return end end function yl_speak_up.on_spawn(self) --Let's assign an ID local m_talk = yl_speak_up.talk_after_spawn or true local m_id = yl_speak_up.number_of_npcs + 1 yl_speak_up.number_of_npcs = m_id yl_speak_up.modstorage:set_int("amount", m_id) yl_speak_up.mob_table[m_id] = "yl_speak_up:test_npc" self.yl_speak_up = { talk = m_talk, id = m_id, textures = self.textures } --Let's protect it self.protected = true self.tamed = true self.object:set_armor_groups({immortal = 100}) minetest.log( "action", "[MOD] yl_speak_up: NPC with ID n_" .. self.yl_speak_up.id .. " spawned at " .. minetest.pos_to_string(self.object:get_pos(), 0) ) --Let's do it only once return true end function yl_speak_up.after_activate(self, staticdata, def, dtime) minetest.log( "action", "[MOD] yl_speak_up: NPC with ID n_" .. self.yl_speak_up.id .. " activated at " .. minetest.pos_to_string(self.object:get_pos(), 0) ) if yl_speak_up.status == 2 then self.object:remove() return true end if self.yl_speak_up and self.yl_speak_up.skin then local tex = self.yl_speak_up.skin self.object:set_properties({textures = {tex[1], tex[2], tex[3], tex[4]}}) end if yl_speak_up.infotext then local i_text = "" if self.yl_speak_up.npc_name then i_text = i_text .. self.yl_speak_up.npc_name .. "\n" end if self.yl_speak_up.npc_description then i_text = i_text .. self.yl_speak_up.npc_description .. "\n" end i_text = i_text .. yl_speak_up.infotext self.object:set_properties({infotext = i_text}) end yl_speak_up.update_nametag(self) end function yl_speak_up.talk(self, clicker) if not clicker and not clicker:is_player() then return end if not self then return end if not self.yl_speak_up or not self.yl_speak_up.id then return end if not self.yl_speak_up or not self.yl_speak_up.talk or self.yl_speak_up.talk~=true then return end local npc_id = self.yl_speak_up.id local n_id = "n_" .. npc_id local pname = clicker:get_player_name() yl_speak_up.speak_to[pname] = {} yl_speak_up.speak_to[pname].n_id = n_id -- Memorize which player talks to which NPC yl_speak_up.speak_to[pname].dialog = load_dialog(n_id) -- Load the dialog and see what we can do with it yl_speak_up.speak_to[pname].textures = self.yl_speak_up.textures yl_speak_up.speak_to[pname].option_index = 1 yl_speak_up.speak_to[pname].obj = self.object -- remember whom the npc belongs to (as long as we still have self.owner available for easy access) yl_speak_up.npc_owner[ n_id ] = self.owner minetest.show_formspec(pname, "yl_speak_up:talk", get_fs_talkdialog(clicker, n_id)) end -- ### -- Fashion -- ### local function cape2texture(t) return "yl_speak_up_mask_cape.png^[combine:32x64:56,20=" .. t end local function shield2texture(t) return "yl_speak_up_mask_shield.png^[combine:32x64:0,0=(" .. t .. ")" end local function textures2skin(textures) local temp = {} -- Cape local cape = cape2texture(textures[1]) -- Main local main = textures[2] -- left (Shield) local left = shield2texture(textures[3]) -- right (Sword) local right = textures[4] temp = {cape, main, left, right} return temp end local function set_textures(obj, textures) -- this function takes the base name of the textures, converts them to usable textures and stores those on the NPC local skins = textures2skin(textures) local ent = obj:get_luaentity() ent.yl_speak_up.skin = skins obj:set_properties({textures = skins}) end local function get_npc_skins(skin) local retstring = "yl_speak_up_main_default.png" local rettable = {"yl_speak_up_main_default.png"} local temp = {} -- get the files out of modpath local mp_list = minetest.get_dir_list(yl_speak_up.modpath .. DIR_DELIM .. "textures", false) -- get the files out of worlddir local wp_list = minetest.get_dir_list( yl_speak_up.worldpath .. DIR_DELIM .. "worldmods" .. DIR_DELIM .. "yl_npc" .. DIR_DELIM .. "textures", false ) -- Let's join both lists. table.insert_all(temp, mp_list) table.insert_all(temp, wp_list) --[[ Let's see if the files are the ones we want. Format is yl_npc_main_name.png <-- Those are the ones we want yl_npc_cape_name.png yl_npc_item_name.png ]]-- local index = 1 local retindex = 1 for _, v in pairs(temp) do local s = string.split(v, "_") if s[1] == "yl" and s[2] == "npc" and s[3] == "main" then index = index + 1 retstring = retstring .. "," .. v table.insert(rettable, v) if v == skin then retindex = index end end end return rettable, retstring, retindex end local function get_npc_capes(cape) local retstring = "yl_npc_cape_default.png" local rettable = {"yl_npc_cape_default.png"} local temp = {} -- get the files out of modpath local mp_list = minetest.get_dir_list(yl_speak_up.modpath .. DIR_DELIM .. "textures", false) -- get the files out of worlddir local wp_list = minetest.get_dir_list( yl_speak_up.worldpath .. DIR_DELIM .. "worldmods" .. DIR_DELIM .. "yl_npc" .. DIR_DELIM .. "textures", false ) -- Let's join both lists. table.insert_all(temp, mp_list) table.insert_all(temp, wp_list) --[[ Let's see if the files are the ones we want. Format is yl_npc_main_name.png yl_npc_cape_name.png <-- Those are the ones we want yl_npc_item_name.png ]]-- local index = 1 local retindex = 1 for _, v in pairs(temp) do local s = string.split(v, "_") if s[1] == "yl" and s[2] == "npc" and s[3] == "cape" then index = index + 1 retstring = retstring .. "," .. v table.insert(rettable, v) if cape ~= "" and v == cape then retindex = index end end end return rettable, retstring, retindex end local function create_preview(main_skin) if main_skin == nil or main_skin == "" then main_skin = "default_greyscale.png" end local player_skin = "(" .. main_skin .. ")" local skin = "" -- Consistent on both sizes: --Chest skin = skin .. "([combine:16x32:-16,-12=" .. player_skin .. "^[mask:yl_speak_up_mask_chest.png)^" --Head skin = skin .. "([combine:16x32:-4,-8=" .. player_skin .. "^[mask:yl_speak_up_mask_head.png)^" --Hat skin = skin .. "([combine:16x32:-36,-8=" .. player_skin .. "^[mask:yl_speak_up_mask_head.png)^" --Right Arm skin = skin .. "([combine:16x32:-44,-12=" .. player_skin .. "^[mask:yl_speak_up_mask_rarm.png)^" --Right Leg skin = skin .. "([combine:16x32:0,0=" .. player_skin .. "^[mask:yl_speak_up_mask_rleg.png)^" -- Left Arm skin = skin .. "([combine:16x32:-24,-44=" .. player_skin .. "^[mask:(yl_speak_up_mask_rarm.png^[transformFX))^" --Left Leg skin = skin .. "([combine:16x32:-12,-32=" .. player_skin .. "^[mask:(yl_speak_up_mask_rleg.png^[transformFX))^" -- Add overlays for 64x skins. these wont appear if skin is 32x because it will be cropped out --Chest Overlay skin = skin .. "([combine:16x32:-16,-28=" .. player_skin .. "^[mask:yl_speak_up_mask_chest.png)^" --Right Arm Overlay skin = skin .. "([combine:16x32:-44,-28=" .. player_skin .. "^[mask:yl_speak_up_mask_rarm.png)^" --Right Leg Overlay skin = skin .. "([combine:16x32:0,-16=" .. player_skin .. "^[mask:yl_speak_up_mask_rleg.png)^" --Left Arm Overlay skin = skin .. "([combine:16x32:-40,-44=" .. player_skin .. "^[mask:(yl_speak_up_mask_rarm.png^[transformFX))^" --Left Leg Overlay skin = skin .. "([combine:16x32:4,-32=" .. player_skin .. "^[mask:(yl_speak_up_mask_rleg.png^[transformFX))" -- Full Preview skin = "(((" .. skin .. ")^[resize:64x128)^[mask:yl_speak_up_transform.png)" return skin end local function get_fs_fashion(pname) local textures = yl_speak_up.speak_to[pname].textures local maintable, mainlist, mainindex = get_npc_skins(textures[2]) local capetable, capelist, capeindex = get_npc_capes(textures[1]) local preview = create_preview(textures[2]) local formspec = { "formspec_version[3]", "size[13.4,9.5]", "dropdown[0.3,0.2;4,0.75;set_skin;", mainlist, ";", mainindex, "]", "label[4.6,0.45;", yl_speak_up.speak_to[pname].n_id, "]", "label[6,0.45;", yl_speak_up.speak_to[pname].n_npc, "]", "dropdown[9.1,0.2;4,0.75;set_cape;", capelist, ";", capeindex, "]", "field[0.3,3.2;4,0.75;set_sword;;", textures[4], "]", "field[9.1,3.2;4,0.75;set_shield;;", textures[3], "]", "field_close_on_enter[set_sword;false]", "field_close_on_enter[set_shield;false]", "image[0.3,1;4,2;", textures[2], "]", -- Main "image[9.1,1;4,2;", textures[1], "]", -- Cape "image[0.3,4.2;4,4;", textures[4], "]", -- Sword "image[9.1,4.2;4,4;", textures[3], "]", --textures[3],"]", -- Shield "image[4.7,1;4,8;", preview, "]", "button_exit[0.3,8.4;3,0.75;button_cancel;Cancel]", "button[10.1,8.4;3,0.75;button_save;Save]" } return table.concat(formspec, "") end minetest.register_on_player_receive_fields( --fashion function(player, formname, fields) if formname ~= "yl_speak_up:fashion" then return end local pname = player:get_player_name() local textures = yl_speak_up.speak_to[pname].textures if fields then if fields.quit or fields.button_cancel then yl_speak_up.speak_to[pname] = nil return end if fields.set_cape then textures[1] = fields.set_cape end if fields.set_skin then textures[2] = fields.set_skin end if fields.set_shield then textures[3] = fields.set_shield end if fields.set_sword then textures[4] = fields.set_sword end if fields.button_save then local obj = yl_speak_up.speak_to[pname].obj if obj ~= nil and obj:get_luaentity() ~= nil then -- save textures yl_speak_up.speak_to[pname].skins = textures2skin(textures) set_textures(obj, textures) end end --yl_speak_up.speak_to[pname].textures = textures end minetest.show_formspec(pname, "yl_speak_up:fashion", get_fs_fashion(pname)) end ) function yl_speak_up.fashion(player, obj) local luaentity = obj:get_luaentity() local pname = player:get_player_name() local npc_id = luaentity.yl_speak_up.id local skins = luaentity.yl_speak_up.skins local n_id = "n_" .. npc_id local t = luaentity.textures yl_speak_up.speak_to[pname] = {} yl_speak_up.speak_to[pname].n_id = n_id yl_speak_up.speak_to[pname].obj = obj yl_speak_up.speak_to[pname].textures = t yl_speak_up.speak_to[pname].skins = textures2skin(t) local dialog = load_dialog(n_id) if next(dialog) then yl_speak_up.speak_to[pname].n_npc = dialog.n_npc else yl_speak_up.speak_to[pname].n_npc = "Unknown" end minetest.show_formspec(pname, "yl_speak_up:fashion", get_fs_fashion(pname)) end yl_speak_up.update_nametag = function(self) if self.yl_speak_up.npc_name then -- the nametag is normal (green) if(self.yl_speak_up.talk) then self.object:set_nametag_attributes({color="#00ff00", text=self.yl_speak_up.npc_name}) -- the nametag has the addition "[muted]" and is magenta when muted else self.object:set_nametag_attributes({color="#ff00ff", text=self.yl_speak_up.npc_name.." [muted]"}) end end end -- mute the npc; either via the appropriate staff or via talking to him yl_speak_up.set_muted = function(p_name, obj, set_muted) if(not(obj)) then return end local luaentity = obj:get_luaentity() if(not(luaentity)) then return end local npc = luaentity.yl_speak_up.id npc_name = luaentity.yl_speak_up.npc_name -- fallback if(not(npc_name)) then npc_name = npc end if(set_muted and luaentity.yl_speak_up.talk) then -- the npc is willing to talk luaentity.yl_speak_up.talk = false yl_speak_up.update_nametag(luaentity) minetest.chat_send_player(p_name,"NPC with ID "..npc.." will shut up at pos ".. minetest.pos_to_string(obj:get_pos(),0).." on command of "..p_name) minetest.log("action","[MOD] yl_speak_up: NPC with ID n_"..npc.. " will shut up at pos "..minetest.pos_to_string(obj:get_pos(),0).. " on command of "..p_name) elseif(not(set_muted) and not(luaentity.yl_speak_up.talk)) then -- mute the npc luaentity.yl_speak_up.talk = true yl_speak_up.update_nametag(luaentity) minetest.chat_send_player(p_name,"NPC with ID "..npc.." will resume speech at pos ".. minetest.pos_to_string(obj:get_pos(),0).." on command of "..p_name) minetest.log("action","[MOD] yl_speak_up: NPC with ID n_"..npc.. " will resume speech at pos "..minetest.pos_to_string(obj:get_pos(),0).. " on command of "..p_name) end end