-- This is the main talkdialog the NPC shows when right-clicked. yl_speak_up.stop_talking = function(pname) if(not(pname)) then return end yl_speak_up.edit_mode[pname] = nil yl_speak_up.speak_to[pname] = nil minetest.close_formspec(pname, "yl_speak_up:talk") end yl_speak_up.input_talk = function(player, formname, fields) if formname ~= "yl_speak_up:talk" then return end local pname = player:get_player_name() local o = "" -- error: not talking? if(not(yl_speak_up.speak_to[pname])) then return end local n_id = yl_speak_up.speak_to[pname].n_id -- the NPC needs to be configured first; route input to the configuration dialog if(not(yl_speak_up.speak_to[pname].dialog) or not(yl_speak_up.speak_to[pname].dialog.n_npc) or not(yl_speak_up.speak_to[pname].d_id)) then yl_speak_up.input_fs_initial_config(player, formname, fields) return end if(fields.show_log) then -- show a log yl_speak_up.show_fs(player, "show_log", {log_type = "full"}) return end -- mobs_redo based NPC may follow their owner, stand or wander around local new_move_order = "" if(fields.order_stand) then new_move_order = "stand" elseif(fields.order_follow) then new_move_order = "follow" elseif(fields.order_wander) then new_move_order = "wander" end if(new_move_order ~= "") then local dialog = yl_speak_up.speak_to[pname].dialog yl_speak_up.set_npc_property(pname, "self.order", new_move_order, "move_order") minetest.chat_send_player(pname, tostring(dialog.n_npc or "NPC").." tells you: ".. "Ok. I will "..tostring(new_move_order)..".") yl_speak_up.stop_talking(pname) 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 local result = yl_speak_up.edit_mode_apply_changes(pname, fields) end -- show which dialogs point to this one if(edit_mode and fields.show_what_points_to_this_dialog) then local dialog = yl_speak_up.speak_to[pname].dialog local d_id = yl_speak_up.speak_to[pname].d_id yl_speak_up.show_fs(player, "show_what_points_to_this_dialog", yl_speak_up.speak_to[pname].d_id) return 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 yl_speak_up.show_fs(player, "initial_config", {n_id = n_id, d_id = yl_speak_up.speak_to[pname].d_id, false}) return end -- the player wants to access the inventory of the NPC if((edit_mode and fields.show_inventory) or (fields.show_inventory and yl_speak_up.may_edit_npc(player, n_id))) then -- the inventory is just an inventory with a back button; come back to this dialog here yl_speak_up.show_fs(player, "inventory") return end -- change skin, cape and wielded items if(edit_mode and fields.edit_skin) then local dialog = yl_speak_up.speak_to[pname].dialog -- necessary so that the fashin formspec can be created yl_speak_up.speak_to[pname].n_npc = dialog.n_npc yl_speak_up.show_fs(player, "fashion") return end if(edit_mode and fields.button_save_dialog) then yl_speak_up.show_fs(player, "talk", {n_id = n_id, d_id = yl_speak_up.speak_to[pname].d_id, do_save = true}) 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.may_edit_npc(player, n_id))) 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 -- for older formspec versions: reset scroll counter yl_speak_up.speak_to[pname].counter = 1 yl_speak_up.speak_to[pname].option_index = 1 -- enter edit mode with that particular NPC yl_speak_up.edit_mode[pname] = yl_speak_up.speak_to[pname].n_id -- load the NPC dialog anew - but only what the NPC itself has to say, no generic dialogs yl_speak_up.speak_to[pname].dialog = yl_speak_up.load_dialog(n_id, false) -- start a new chat - but this time in edit mode yl_speak_up.speak_to[pname].d_id = nil yl_speak_up.show_fs(player, "talk", {n_id = yl_speak_up.speak_to[pname].n_id, d_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.show_fs(player, "quit", 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.show_fs(player, "quit", nil) return end -- allow the player to take the item back if(fields.show_player_offers_item and fields.show_player_offers_item ~= "") then yl_speak_up.show_fs(player, "player_offers_item", 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 yl_speak_up.show_fs(player, "talk", {n_id = yl_speak_up.speak_to[pname].n_id, d_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 yl_speak_up.show_fs(player, "talk", {n_id = yl_speak_up.speak_to[pname].n_id, d_id = yl_speak_up.speak_to[pname].d_id}) return else yl_speak_up.speak_to[pname].option_index = 1 end -- the player wants to see the trade list if(fields.show_trade_list) then yl_speak_up.show_fs(player, "trade_list", nil) return end -- the player wants to give something to the NPC if(fields.player_offers_item) then if(not(edit_mode)) then -- normal mode: take the item the player wants to offer yl_speak_up.show_fs(player, "player_offers_item", nil) else local dialog = yl_speak_up.speak_to[pname].dialog local future_d_id = "d_got_item" -- make sure this dialog exists; create if needed if(not(dialog.n_dialogs[ future_d_id ])) then dialog.n_dialogs[future_d_id] = { d_id = future_d_id, d_type = "text", d_text = "", d_sort = 9999 -- make this the last option } end -- in edit mode: allow to edit the options yl_speak_up.show_fs(player, "talk", {n_id = n_id, d_id = future_d_id}) end return end for k, v in pairs(fields) do -- only split into 2 parts at max local s = string.split(k, "_", false, 2) 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 -- we may soon need actions and o_results from the selected_option local selected_option = {} if(d_id and o and dialog and dialog.n_dialogs and dialog.n_dialogs[d_id] and dialog.n_dialogs[d_id].d_options and dialog.n_dialogs[d_id].d_options[o]) then selected_option = dialog.n_dialogs[d_id].d_options[o] end if(not(edit_mode)) then -- abort if the option does not exist if(not(selected_option)) then return end yl_speak_up.speak_to[pname].o_id = o -- start with executing the first action yl_speak_up.execute_next_action(player, nil, true) return end -- if(edit_mode) (the other case has been dealt with above) -- all three buttons (pre(C)onditions, (Ef)fects, edit option) lead to the same new formspec 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["actions_"..o_id] or fields["effects_"..o_id]) then -- store which option we want to edit yl_speak_up.speak_to[pname].o_id = o_id -- if something was changed: ask for confirmation yl_speak_up.show_fs(player, "edit_option_dialog", {n_id = yl_speak_up.speak_to[pname].n_id, d_id = d_id, o_id = o_id, caller="button"}) return end end end -- in edit mode: has another dialog been selected? -- if nothing better can be found: keep the old dialog local show_dialog = d_id -- an option button was selected; -- since we do not execute actions and effects in edit mode, we need to find out the -- right target dialog manually (and assume all went correct) if( o ~= "" ) then -- find out the normal target dialog of this option if(selected_option and selected_option.o_results) then for k, v in pairs(selected_option.o_results) do if(v and v.r_type == "dialog") then show_dialog = v.r_value end end end -- 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) elseif(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 yl_speak_up.show_fs(player, "talk", {n_id = n_id, d_id = show_dialog}) -- no option was selected - so we need to end this here return end -- helper function for -- yl_speak_up.get_fs_talkdialog and -- yl_speak_up.check_and_add_as_generic_dialog -- find the dialog with d_sort == 0 or lowest number yl_speak_up.get_start_dialog_id = function(dialog) if(not(dialog) or not(dialog.n_dialogs)) then return nil end -- Find the dialog with d_sort = 0 or alternatively with the lowest number local lowest_sort = nil local d_id = nil for k, v in pairs(dialog.n_dialogs) do local nr = tonumber(v.d_sort) if(not(lowest_sort) or (nr and nr >= 0 and nr < lowest_sort)) then lowest_sort = nr d_id = k end end return d_id end -- helper function for yl_speak_up.get_fs_talkdialog: -- shows the text the NPC "speaks" and adds edit and navigation buttons -- (all only in *edit_mode*) yl_speak_up.get_fs_talkdialog_main_text_in_edit_mode = function( formspec, h, dialog, dialog_list, edit_mode, c_d_id, active_dialog) local d_id_to_dropdown_index = {} if(not(edit_mode)) then return {h = h, formspec = formspec, d_id_to_dropdown_index = {}, dialog_list = dialog_list} end -- allow to change skin, wielded items etc. table.insert(formspec, "button[15.75,3.5;3.5,0.9;edit_skin;Edit Skin]") if(not(dialog) or not(dialog.n_dialogs)) then return {h = h, formspec = formspec, d_id_to_dropdown_index = {}, dialog_list = dialog_list} end -- display the window with the text the NPC is saying -- 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) then local prev_dialog = tostring(minetest.formspec_escape(sorted_list[i-1])) yl_speak_up.add_formspec_element_with_tooltip_if(formspec, "button", "8.5,4.0;2,0.9", "prev_dialog_"..prev_dialog, "<", "Go to previous dialog "..prev_dialog..".", (sorted_list[ i-1 ])) local next_dialog = tostring(minetest.formspec_escape(sorted_list[i+1])) yl_speak_up.add_formspec_element_with_tooltip_if(formspec, "button", "11,4.0;2,0.9", "next_dialog_"..next_dialog, ">", "Go to next dialog "..next_dialog..".", (sorted_list[ i+1 ])) end d_id_to_dropdown_index[d] = i + 1 end dialog_list = dialog_list..",d_end" d_id_to_dropdown_index["d_end"] = #sorted_list + 2 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] or "1")..",]") 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]") yl_speak_up.add_formspec_element_with_tooltip_if(formspec, "button", "13.9,4.0;1,0.9", "show_new_dialog", "+", "Create a new dialog.", true) yl_speak_up.add_formspec_element_with_tooltip_if(formspec, "button", "13.4,0.3;2,0.9", "button_edit_name_and_description", "Edit", "Edit name and description of your NPC.", true) yl_speak_up.add_formspec_element_with_tooltip_if(formspec, "button", "15.7,0.3;2,0.9", "button_save_dialog", "Save", "Save this dialog.", true) -- static help text instead of text input field for d_got_item if(c_d_id == "d_got_item") then table.insert(formspec, "hypertext[0.2,5;19.6,17.8;d_text;".. "Note:\nThis is a special dialog.".. "It will be called when the player clicks on ".. "I want to give you something.".. "\nMost of the things listed below will be added automaticly when you add a ".. "new option to this dialog. In most cases you may just have to edit the ".. "precondition so that the right item is accepted, and then ".. "set the target dialog according to your needs. Please also ".. "edit the alternate text so that it fits your item!".. "\nThis is how it works in detail:".. "\nEach option you add here ought to deal with one item(stack) that ".. "the NPC expects from the player, i.e. farming:bread 2. ".. "Each option needs to be selected automaticly and ought to contain:".. "\n* a precondition regarding ".. "an item the player offered/gave to the NPC ".. "(shown as player_offered_item in overview) ".. "where you define which item(stack) is relevant for this option".. "\n* an effect regarding an item the player offered to the NPC ".. "(shown as deal_with_offered_item in overview) ".. "where you define what shall happen to the offered item. Usually ".. "the NPC will accept the item and put it into its inventory.".. "\n* Don't forget to set a suitable target dialog for the effect! ".. "Your NPC ought to comment on what he got, i.e. ".. "Thank you for those two breads! You saved me from starving.".. "You can also work with an alternate text here (as is done in the ".. "default setup when adding a new option here).".. "\n]") -- static help text instead of text input field for d_trade elseif(c_d_id == "d_trade") then table.insert(formspec, "hypertext[0.2,5;19.6,17.8;d_text;".. "Note:\nThis is a special dialog.".. "It will be called when the player clicks on ".. "Let's trade!.".. "\nSome of the things listed below will be added automaticly when you add a ".. "new option to this dialog. In most cases you may just have to edit the ".. "precondition so that the right item(stack) is beeing ".. "searched for, and you need to add suitable effects. The ones added ".. "automaticly are just an example.".. "\nNote that once the NPC found a matching precondition, it will execute the ".. "relevant effects and present the player the trade list. Any further options ".. "that might also fit will not be executed this time. Only one option ".. "(or none) will be selected each time.".. "\nThis is how it works in detail:".. "\nEach option you add here ought to deal with one item(stack) that ".. "the NPC might or might not have in its inventory, ".. "i.e. default:stick 4. ".. "Each option needs to be selected automaticly and ought to contain:".. "\n* at least one precondition regarding ".. "the inventory of the NPC ".. "where you define which item(stack) is relevant for this option ".. "(you can add multiple such preconditions for each option)".. "\n* at least one effect regarding what the NPC shall do if the ".. "precondition matches. In most cases, NPC crafts something, ".. "put item from the NPC's inventory into a chest etc. or ".. "take item from a chest etc. and put it into the NPC's inventory ".. "will be what you are looking for. More than one effect is possible.".. "\n* In this particular case, no target dialog needs to be selected. After ".. "executing the effect(s), the trade list view will be shown to the ".. "player.".. "\n]") elseif(active_dialog and active_dialog.d_text) then table.insert(formspec, "textarea[0.2,5;19.6,17.8;d_text;;".. minetest.formspec_escape(active_dialog.d_text or "?").. "]") else table.insert(formspec, "textarea[0.2,5;19.6,17.8;d_text;;".. minetest.formspec_escape("[no text]").. "]") end return {h = h, formspec = formspec, d_id_to_dropdown_index = d_id_to_dropdown_index, dialog_list = dialog_list} end -- helper function for yl_speak_up.get_fs_talkdialog: -- prints one entry (option/answer) in normal mode - not in edit_mode yl_speak_up.get_fs_talkdialog_line_in_normal_mode = function( formspec, h, pname_for_old_fs, oid, sb_v, dialog, allowed, pname) local t = "- no text given -" local t_alt = nil -- the preconditions are fulfilled; showe the option if(allowed[sb_v.o_id] == true) then -- replace $NPC_NAME$ etc. t = minetest.formspec_escape(yl_speak_up.replace_vars_in_text( sb_v.o_text_when_prerequisites_met, dialog, pname)) -- precondition not fulfilled? the option shall be hidden elseif(sb_v.o_hide_when_prerequisites_not_met == "true") then -- show nothing; t_alt remains nil t = nil -- precondition not fulfilled, and autoanswer active? Then hide this option. elseif(sb_v.o_autoanswer) then -- show nothing; t_alt remains nil t = nil -- precondition not fulfilled? the option shall be greyed out -- default to greyed out (this option cannot be selected) elseif(sb_v.o_grey_when_prerequisites_not_met == "true") then local text = sb_v.o_text_when_prerequisites_not_met if(not(text) or text == "") then text = t or yl_speak_up.message_button_option_prerequisites_not_met_default end t = nil -- replace $NPC_NAME$ etc. t_alt = minetest.formspec_escape(yl_speak_up.replace_vars_in_text( text, dialog, pname)) elseif(sb_v.o_grey_when_prerequisites_not_met == "false" and sb_v.o_text_when_prerequisites_not_met ~= "") then -- show in normal coor t = minetest.formspec_escape(yl_speak_up.replace_vars_in_text( sb_v.o_text_when_prerequisites_not_met, dialog, pname)) end if(t or t_alt) then -- actually show the button h = yl_speak_up.add_edit_button_fs_talkdialog(formspec, h, "button_" .. oid, t, t, (t and not(t_alt)), t_alt, nil, pname_for_old_fs) end return {h = h, formspec = formspec} end -- helper function for yl_speak_up.get_fs_talkdialog: -- prints one entry (option/answer) in *edit_mode* yl_speak_up.get_fs_talkdialog_line_in_edit_mode = function( formspec, h, pname_for_old_fs, oid, sb_v, dialog, active_dialog, dialog_list, d_id_to_dropdown_index, current_index, anz_options) local offset = 0.0 local field_length = 44.4 if(pname_for_old_fs) then offset = 0.7 field_length = 42.4 end h = h + 1 -- add a button "o_:" that leads to an edit formspec for this option yl_speak_up.add_formspec_element_with_tooltip_if(formspec, "button", tostring(2.3+offset).."," .. h .. ";2,0.9", "edit_option_" .. oid, oid, "Edit target dialog, pre(C)onditions and (Ef)fects for option "..oid..".", true) -- 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 or v.r_value == "d_end" or v.r_value == "d_trade" or v.r_value == "d_got_item") 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 -- add a button "-> d_" that leads to the target dialog (if one is set) -- selecting an option this way MUST NOT execute the pre(C)onditions or (Ef)fects! yl_speak_up.add_formspec_element_with_tooltip_if(formspec, "button", tostring(9.0+offset)..","..h..";1,0.9", "button_" .. oid, "->", "Go to target dialog "..minetest.formspec_escape(target_dialog or "").. " that will be shown when this option ("..oid..") is selected.", (target_dialog)) -- allow to set a new target dialog table.insert(formspec, "dropdown["..tostring(4.4+offset)..","..h..";4.7,1;d_id_".. oid..";".. dialog_list..";".. (d_id_to_dropdown_index[(target_dialog or "?")] or "0")..",]") -- add a tooltip "Change target dialog" table.insert(formspec, "tooltip[4.4,"..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 yl_speak_up.add_formspec_element_with_tooltip_if(formspec, "button", tostring(0.5+offset)..","..h..";0.5,0.9", "conditions_"..oid, "C", "There are pre(C)onditions required for showing this option. Display them.", (prereq and next(prereq))) yl_speak_up.add_formspec_element_with_tooltip_if(formspec, "button", tostring(1.6+offset)..","..h..";0.6,0.9", "effects_"..oid, "Ef", "There are further (Ef)fects (apart from switching\n".. "to a new dialog) set for this option. Display them.", (has_other_results)) -- are there any actions defined? local actions = active_dialog.d_options[sb_v.o_id].actions yl_speak_up.add_formspec_element_with_tooltip_if(formspec, "button", tostring(1.1+offset)..","..h..";0.5,0.9", "actions_"..oid, "A", "There is an (A)ction (i.e. a trade) that will happen\n".. "when switching to a new dialog. Display actions and\n".. "trade of this option.", (actions and next(actions))) if(sb_v.o_autoanswer) then table.insert(formspec, "label["..tostring(9.9+offset+0.2)..","..tostring(h+0.5)..";".. minetest.formspec_escape("[Automaticly selected if preconditions are met]").. "]") elseif(active_dialog.o_random) then table.insert(formspec, "label["..tostring(9.9+offset+0.2)..","..tostring(h+0.5)..";".. minetest.formspec_escape("[One of these options is randomly selected]").. "]") else -- show the actual text for the option yl_speak_up.add_formspec_element_with_tooltip_if(formspec, "field", tostring(9.9+offset)..","..h..";".. tostring(field_length-2.3)..",0.9", "text_option_" .. oid, ";"..minetest.formspec_escape(sb_v.o_text_when_prerequisites_met), "Edit the text that is displayed on button "..oid..".", true) end yl_speak_up.add_formspec_element_with_tooltip_if(formspec, "button", tostring(9.9+offset+field_length-2.2)..","..h..";1.0,0.9", "delete_option_"..oid, "Del", "Delete this option/answer.", true) yl_speak_up.add_formspec_element_with_tooltip_if(formspec, -- "image_button", tostring(9.9+offset+field_length-0.5)..","..h..";0.5,0.9".. -- ";gui_furnace_arrow_bg.png^[transformR180", "button", tostring(9.9+offset+field_length-1.1)..","..h..";0.5,0.9", "option_move_down_"..oid, "v", "Move this option/answer one down.", (current_index < anz_options)) yl_speak_up.add_formspec_element_with_tooltip_if(formspec, -- "image_button", tostring(9.9+offset+field_length-1.0)..","..h..";0.5,0.9".. -- ";gui_furnace_arrow_bg.png", "button", tostring(9.9+offset+field_length-0.5)..","..h..";0.5,0.9", "option_move_up_"..oid, "^", "Move this option/answer one up.", (current_index > 1)) return {h = h, formspec = formspec} end -- helper function for yl_speak_up.get_fs_talkdialog: -- if the player can edit the NPC, -- either add a button for entering edit mode -- or add the buttons needed to edit the dialog when in *edit mode* yl_speak_up.get_fs_talkdialog_add_edit_buttons = function( formspec, h, pname_for_old_fs, is_a_start_dialog, active_dialog, luaentity, edit_mode, may_edit_npc, anz_options) if(not(may_edit_npc)) then return {h = h, formspec = formspec} end -- button "show log" for those who can edit the NPC (entering edit mode is not required) text = minetest.formspec_escape( "[Log] Show me your log.") h = yl_speak_up.add_edit_button_fs_talkdialog(formspec, h, "show_log", text, text, true, nil, nil, pname_for_old_fs) -- Offer to enter edit mode if the player has the npc_talk_owner priv OR is allowed to edit the NPC. -- The npc_master priv allows to edit all NPC. if(not(edit_mode)) then h = yl_speak_up.add_edit_button_fs_talkdialog(formspec, h, "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.", -- chat option: "I am your owner. I have new orders for you. "I am your owner. I have new orders for you.", true, nil, true, pname_for_old_fs) -- is button_exit return {h = h, formspec = formspec} end local offset = 0.0 -- If in edit mode, add new menu entries: "add new options", "end edit mode" and what else is needed. h = yl_speak_up.add_edit_button_fs_talkdialog(formspec, h, "add_option", -- chat option: "Add a new answer/option to this dialog." "Adds a new option to this dialog. You can delete options via the option edit menu.", "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 (anz_options < yl_speak_up.max_number_of_options_per_dialog), "Maximum number of allowed answers/options reached. No further options/answers ".. "can be added.", nil, pname_for_old_fs) h = yl_speak_up.add_edit_button_fs_talkdialog(formspec, h, "delete_this_empty_dialog", -- chat option: "Delete this 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.", "Delete this empty dialog.", (active_dialog and active_dialog.d_text == "" and anz_options == 0), -- (but only show this option if the dialog is empty) "If you want to delete this dialog, you need to delete all options and its ".. "text first.", nil, pname_for_old_fs) h = yl_speak_up.add_edit_button_fs_talkdialog(formspec, h, "show_what_points_to_this_dialog", -- chat option: "Show what points to this dialog." "Show which other dialog options or failed actions\n".. "or effects lead the player to this dialog here.", "Show what points to this dialog.", -- there is no alternate text to show true, nil, nil, pname_for_old_fs) h = yl_speak_up.add_edit_button_fs_talkdialog(formspec, h, "make_first_option", -- chat option: "Make this dialog the first one shown when starting to talk." "The NPC has to start with one dialog when he is right-clicked. ".. "Make this dialog the one shown.", "Make this dialog the first one shown when starting a conversation.", (active_dialog and active_dialog.d_sort and tonumber(active_dialog.d_sort) ~= 0), -- (but only show this option if it's not already the first one) "This dialog will be shown whenever a conversation is started.", nil,pname_for_old_fs) local b_text = "Turn this into" if(is_a_start_dialog) then b_text = "This shall no longer be" end h = yl_speak_up.add_edit_button_fs_talkdialog(formspec, h, "turn_into_a_start_dialog", "With automatic selection of options, it is possible that the real\n".. "start dialog will never be shown to the player. However, we need\n".. "to add some buttons to that start dialog for i.e. giving items\n".. "to the NPC and for trading. Therefore, dialogs can be marked as\n".. "*a* start dialog so that these buttons will be added to those dialogs.", b_text.." *a* start dialog where buttons for trade etc. are shown.", not(active_dialog and active_dialog.d_sort and tonumber(active_dialog.d_sort) == 0), "The start dialog automaticly counts as *a* start dialog where buttons for ".. "trade etc. are shown.", nil, pname_for_old_fs) -- chat option: Mute/Unmute NPC h = yl_speak_up.add_edit_button_fs_talkdialog(formspec, h, "mute_npc", -- chat option: mute the NPC "The NPC will no longer show his dialogs when he is right-clicked. This is ".. "useful while you edit the NPC and don't want players to see ".. "unfinished entries and/or quests.", "State: Not muted. Stop talking to other players while I give you new orders.", (luaentity and luaentity.yl_speak_up.talk), nil, nil, pname_for_old_fs) h = yl_speak_up.add_edit_button_fs_talkdialog(formspec, h, "un_mute_npc", -- unmute the NPC "The NPC will show his dialogs to other players when he is right-clicked. ".. "This is the normal mode of operation. Choose this when you are ".. "finished editing.", "State: You are currently muted. Talk to anyone again who wants to talk to you.", -- the NPC has to be there (luaentity and not(luaentity.yl_speak_up.talk)), nil, nil, pname_for_old_fs) h = yl_speak_up.add_edit_button_fs_talkdialog(formspec, h, "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.", -- chat option:"That was all. I'm finished with giving you new orders. Remember them!" "That was all. I'm finished with giving you new orders. Remember them!", true, nil, true, pname_for_old_fs) -- is button_exit return {h = h, formspec = formspec} end -- recursion_depth is increased each time autoanswer is automaticly selected yl_speak_up.get_fs_talkdialog = function(player, n_id, d_id, alternate_text, recursion_depth) 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(dialog)) then yl_speak_up.log_change(pname, n_id, "unconfigured NPC beeing talked to at ".. minetest.pos_to_string(player:get_pos()), "action") return yl_speak_up.get_error_message() end -- currently no trade running (we're editing options) yl_speak_up.trade[pname] = nil yl_speak_up.speak_to[pname].trade_id = nil -- add a d_trade dialog if necessary if(dialog and dialog.trades and dialog.n_dialogs and not(dialog.n_dialogs["d_trade"])) then yl_speak_up.add_new_dialog(dialog, pname, "trade", "[no text]") 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 -- the generic start dialog contains only those options that are generic; -- choose the right start dialog of the NPC if(d_id ~= nil and d_id ~= "d_generic_start_dialog") then active_dialog = dialog.n_dialogs[d_id] c_d_id = d_id elseif(d_id and d_id ~= "d_generic_start_dialog" and 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 c_d_id = yl_speak_up.get_start_dialog_id(dialog) if(c_d_id) then active_dialog = dialog.n_dialogs[c_d_id] end else -- it may be possible that this player can initialize this npc yl_speak_up.log_change(pname, n_id, "unconfigured NPC beeing talked to at ".. minetest.pos_to_string(player:get_pos()), "action") -- this is the initial config -- (input ends up at yl_speak_up.input_talk and needs to be rerouted) return yl_speak_up.get_fs_initial_config(player, n_id, d_id, true) end if c_d_id == nil then return yl_speak_up.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 -- 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) -- evaluate the preconditions of each option and check if the option can be offered local allowed = yl_speak_up.calculate_displayable_options(pname, active_dialog.d_options, edit_mode, -- avoid loops by limiting max recoursion depths for autoanswers (recursion_depth < yl_speak_up.max_allowed_recursion_depth)) -- autoanswer or o_random may force to select a particular dialog local go_to_next_dialog = nil -- abort here if needed - the autoanswer/autoselection did choose an option for us alread if(not(edit_mode) and allowed and allowed["autoanswer"] and allowed["autoanswer"] ~= "") then go_to_next_dialog = allowed["autoanswer"] -- randomly select an answer elseif(not(edit_mode) and allowed and active_dialog.o_random and (recursion_depth < yl_speak_up.max_allowed_recursion_depth)) then local liste = {} -- only allowed options can be randomly selected from for o_id, v in pairs(allowed) do if(v) then table.insert(liste, o_id) end end -- randomly select one of the possible dialogs if(#liste > 0) then go_to_next_dialog = liste[math.random(1, #liste)] end end if(go_to_next_dialog and go_to_next_dialog ~= "") then -- no actions shall be executed local o_id = go_to_next_dialog local effects = active_dialog.d_options[o_id].o_results -- execute all effects/results local res = yl_speak_up.execute_all_relevant_effects(player, effects, o_id, true) local target_dialog = res.next_dialog yl_speak_up.speak_to[pname].o_id = nil yl_speak_up.speak_to[pname].a_id = nil -- end the conversation? if(target_dialog and target_dialog == "d_end") then yl_speak_up.stop_talking(pname) -- a formspec is expected here; provide one that has an exit button only return "size[2,1]".. "button_exit[0,0;1,1;Exit;exit]" end if(not(target_dialog) or target_dialog == "" or not(dialog.n_dialogs[target_dialog])) then target_dialog = yl_speak_up.speak_to[pname].d_id end -- show the new target dialog and exit -- the recursion_depth will be increased by one (we did autoselect here and need to -- avoid infinite loops) return yl_speak_up.get_fs_talkdialog(player, n_id, target_dialog, res.alternate_text, recursion_depth + 1) end -- is the player comming back from trying to offer something to the NPC? -- And is the NPC trying to return the item? if(not(edit_mode) and d_id == "d_got_item") then local pname = player:get_player_name() local trade_inv = minetest.get_inventory({type="detached", name="yl_speak_up_player_"..pname}) if(not(trade_inv:is_empty("npc_wants"))) then return "formspec_version[1]".. yl_speak_up.show_fs_simple_deco(8, 2.5).. "label[0.5,0.5;".. minetest.formspec_escape(dialog.n_npc or "- ? -").. " does not seem to be intrested in that.\n".. "Please take your item back and try something else.]".. "button[3.5,1.5;1.5,1.0;show_player_offers_item;Ok]" end end yl_speak_up.speak_to[pname].allowed = allowed local pname_for_old_fs = yl_speak_up.get_pname_for_old_fs(pname) local fs_version = yl_speak_up.fs_version[pname] local formspec = {} local h -- 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 -- allow to change skin, wielded items etc. -- display the window with the text the NPC is saying in *edit_mode* local res_edit_top = yl_speak_up.get_fs_talkdialog_main_text_in_edit_mode( formspec, h, dialog, dialog_list, edit_mode, c_d_id, active_dialog) -- we are finished with adding buttons and text etc. to the left side of the formspec local left_window_fs = table.concat(res_edit_top.formspec, "\n") dialog_list = res_edit_top.dialog_list -- find the right index for the dialog_list dropdown above local d_id_to_dropdown_index = res_edit_top.d_id_to_dropdown_index -- empty formspec for the bottom part formspec = {} 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_o_list = yl_speak_up.get_sorted_options(active_dialog.d_options, "o_sort") for _, sb_v in ipairs(sorted_o_list) do anz_options = anz_options + 1 end for i, s_o_id in ipairs(sorted_o_list) do local sb_v = active_dialog.d_options[s_o_id] local oid = minetest.formspec_escape(sb_v.o_id) local res = {} -- in edit_mode: show all options if(edit_mode) then res = yl_speak_up.get_fs_talkdialog_line_in_edit_mode( formspec, h, pname_for_old_fs, oid, sb_v, dialog, active_dialog, dialog_list, d_id_to_dropdown_index, i, #sorted_o_list) -- normal mode: show an option if the prerequirements (if any are defined) are met elseif(not(edit_mode)) then res = yl_speak_up.get_fs_talkdialog_line_in_normal_mode( formspec, h, pname_for_old_fs, oid, sb_v, dialog, allowed, pname) end formspec = res.formspec h = res.h end end -- with automatic selection from the start dialog, it is possible that the -- real start dialog is never shown; thus, add those buttons which need to -- be shown just once to all dialogs with is_a_start_dialog set local is_a_start_dialog = (active_dialog and active_dialog.d_sort and (tonumber(active_dialog.d_sort) == 0 or active_dialog.is_a_start_dialog)) -- add a "I want to give you something" button to the first dialog if the NPC accepts items if(is_a_start_dialog) then local offer_item_add_text = "" if(edit_mode) then offer_item_add_text = minetest.formspec_escape("[dialog d_got_item] -> ") end h = yl_speak_up.add_edit_button_fs_talkdialog(formspec, h, "player_offers_item", "If you want to give something (items) to this NPC\n".. "- either because he requested it or as a present -\n".. "click here. The NPC will return items he doesn't want.", offer_item_add_text.."I want to give you something.", -- show this in edit mode and when the NPC actually accepts items (edit_mode or dialog.n_dialogs["d_got_item"]), nil, nil, pname_for_old_fs) end -- can the player edit this NPC? local may_edit_npc = yl_speak_up.may_edit_npc(player, n_id) -- for muting and for checking the owner/order, the luaentity is needed local obj = yl_speak_up.speak_to[pname].obj -- some precautions - someone else might have eliminated the NPC in the meantime local luaentity = nil if(obj) then luaentity = obj:get_luaentity() end -- If in edit mode, add new menu entries: "add new options", "end edit mode" and what else is needed. -- Else allow to enter edit mode local res = yl_speak_up.get_fs_talkdialog_add_edit_buttons( formspec, h, pname_for_old_fs, is_a_start_dialog, active_dialog, luaentity, edit_mode, may_edit_npc, anz_options) formspec = res.formspec h = res.h -- add a Let's trade button to the first dialog if the NPC has trades local has_trades = nil if(is_a_start_dialog and dialog.trades) then for k, v in pairs(dialog.trades) do -- has the NPC any *public* trades that are not effects/results? if(not(v.hide) and not(v.d_id)) then has_trades = true break end end end if(has_trades) then h = yl_speak_up.add_edit_button_fs_talkdialog(formspec, h, "show_trade_list", "Show a list of trades the NPC has to offer.", "Let's trade!", (has_trades), nil, nil, pname_for_old_fs) elseif(is_a_start_dialog and may_edit_npc) then -- show the "show your inventory"-button even when not in edit mode h = yl_speak_up.add_edit_button_fs_talkdialog(formspec, h, "show_inventory", "Access and manage the inventory of the NPC. This is used for adding trade ".. "items, getting collected payments and managing quest items.", "Show your inventory (only accessible to owner)!", true, nil, nil, pname_for_old_fs) end -- mobs_redo based NPC can follow, stand or wander around if(luaentity and luaentity.order and may_edit_npc -- not all mobs need or support this feature and table.indexof(yl_speak_up.emulate_orders_on_rightclick, luaentity.name) > -1) then if(luaentity.order ~= "follow") then h = yl_speak_up.add_edit_button_fs_talkdialog(formspec, h, "order_follow", "The NPC will follow you.", "New order: Follow me!", ((luaentity.owner == pname) and (luaentity.order ~= "follow")), "New order: Follow me. (Only available for owner).", nil, pname_for_old_fs) end h = yl_speak_up.add_edit_button_fs_talkdialog(formspec, h, "order_stand", "The NPC will wait here.", "New order: Stand here.", (luaentity.order ~= "stand"), nil, nil, pname_for_old_fs) h = yl_speak_up.add_edit_button_fs_talkdialog(formspec, h, "order_wander", "The NPC will wander around randomly.", "New order: Wander around a bit on your own.", (luaentity.order ~= "walking"), nil, nil, pname_for_old_fs) end -- we are finished with adding buttons to the bottom of the formspec local bottom_window_fs = table.concat(formspec, "\n") return yl_speak_up.show_fs_decorated(pname, edit_mode, h, alternate_text, left_window_fs, bottom_window_fs, active_dialog, h) end