yl_speak_up/fs_talkdialog.lua

956 lines
38 KiB
Lua

-- This is the main talkdialog the NPC shows when right-clicked.
-- helper function for yl_speak_up.fs_talkdialog
yl_speak_up.calculate_portrait = function(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
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
-- 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) 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
-- 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
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
-- 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
-- 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["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
end
-- older formspecs (before v3) do not offer a scroll container and have to scroll manually;
-- we maintain a player-name-based counter in order to see if this line ought to be shown
yl_speak_up.old_fs_version_show_line = function(pname)
-- the player is using a new enough version for scroll_container to work
if(not(pname)) then
return true
end
local max_number_of_buttons = yl_speak_up.max_number_of_buttons
local start_index = yl_speak_up.speak_to[pname].option_index
local counter = yl_speak_up.speak_to[pname].counter
yl_speak_up.speak_to[pname].counter = counter + 1
if counter < start_index or counter >= start_index + max_number_of_buttons then
return false
end
return true
end
-- show an edit option in the main menu of the NPC;
-- helper function for yl_speak_up.fs_talkdialog(..)
yl_speak_up.add_edit_button_fs_talkdialog = function(formspec, h, button_name, tooltip, label,
show_main_not_alternate, alternate_label, is_exit_button,
pname)
-- do not show this button at all if there is no alternate text and the condition is false
if(not(alternate_label) and not(show_main_not_alternate)) then
return h
end
local button_dimensions = "0.5,"..(h+1)..";53.8,0.9;"
local label_start_pos = "0.7"
-- older formspecs (before v4) do not offer a scroll container and have to scroll manually
if(pname) then
if(not(yl_speak_up.old_fs_version_show_line(pname))) then
return h
end
-- there has to be more room for the up and down arrows
button_dimensions = "1.2,"..(h+1)..";52.3,0.9;"
label_start_pos = "1.4"
end
h = h + 1
if(show_main_not_alternate) then
if(is_exit_button) then
table.insert(formspec, "button_exit["..button_dimensions..tostring(button_name)..";]")
else
table.insert(formspec, "button["..button_dimensions..tostring(button_name)..";]")
end
table.insert(formspec, "tooltip["..tostring(button_name)..";"..tostring(tooltip).."]")
table.insert(formspec, "label["..label_start_pos..","..(h+0.45)..";"..tostring(label).."]")
else
table.insert(formspec, "box["..button_dimensions.."#BBBBBB]")
table.insert(formspec, "label["..label_start_pos..","..(h+0.45)..";"..
tostring(alternate_label).."]")
end
return h
end
-- show a formspec element in the main menu of the NPC (with tooltip);
-- helper function for yl_speak_up.fs_talkdialog(..)
yl_speak_up.add_formspec_element_with_tooltip_if = function(formspec, element_type, position, element_name,
element_text, tooltip, condition)
if(not(condition)) then
return
end
table.insert(formspec, element_type.."["..position..";"..element_name..";"..element_text.."]")
table.insert(formspec, "tooltip["..tostring(element_name)..";"..tostring(tooltip).."]")
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 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 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
--[[ 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
-- (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))
-- 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
-- no actions shall be executed
local o_id = allowed["autoanswer"]
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
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 "size[8,1.5]"..
"label[0.2,-0.2;"..
minetest.formspec_escape(dialog.n_npc or "- ? -")..
" does not seem to be intrested.\n"..
"Please take your item back and try something else.]"..
"button[2,1.0;1.5,0.9;show_player_offers_item;Ok]"
end
end
yl_speak_up.speak_to[pname].allowed = allowed
local portrait = yl_speak_up.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
local fs_version = 1
if formspec_v >= 4 then
fs_version = 3
elseif formspec_v >= 2 then
fs_version = 2
else
fs_version = 1
end
-- store which formspec version the player has
if(not(yl_speak_up.fs_version[pname])) then
yl_speak_up.fs_version[pname] = fs_version
end
formspec = {
"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]",
"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,
"]",
}
-- add those things that only exist in formspec_v >= 4
if(fs_version > 2) then
table.insert(formspec, "style_type[button;bgcolor=#a37e45]")
table.insert(formspec, "style_type[button_exit;bgcolor=#a37e45]") -- Dialog
-- table.insert(formspec, "background[-1,-1;22,25;yl_speak_up_bg_dialog2.png;false]")
-- table.insert(formspec, "background[-1,23;58,10;yl_speak_up_bg_dialog2.png;false]")
-- table.insert(formspec, "style_type[button;bgcolor=#a37e45]")
end
-- 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 = {}
-- allow to change skin, wielded items etc.
if(edit_mode) then
table.insert(formspec, "button[15.75,3.5;3.5,0.9;edit_skin;Edit Skin]")
end
-- 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) 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
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]")
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)
table.insert(formspec, "textarea[0.2,5;19.6,17.8;d_text;;"..
minetest.formspec_escape(active_dialog.d_text)..
"]")
else
if(alternate_text) then
alternate_text = string.gsub(alternate_text, "%$TEXT%$", active_dialog.d_text)
end
-- replace $NPC_NAME$ etc.
local t = minetest.formspec_escape(yl_speak_up.replace_vars_in_text(
(alternate_text or active_dialog.d_text), dialog, pname))
if(fs_version > 2) then
yl_speak_up.add_formspec_element_with_tooltip_if(formspec,
"hypertext", "0.2,5;19.6,17.8", "d_text",
"<normal>"..t.."\n</normal>",
t:trim()..";#000000;#FFFFFF",
true)
else
yl_speak_up.add_formspec_element_with_tooltip_if(formspec,
"textarea", "0.2,5;19.6,17.8", "",
";"..t.."\n",
t:trim(),
true)
end
end
local pname_for_old_fs = nil
if(fs_version > 2) then
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]")
else
if(fs_version < 2) then
-- if the player has an older formspec version
minetest.log( "info",
"[MOD] yl_speak_up: User " .. pname .. " talked to NPC ID n_" .. n_id ..
" with an old formspec version!")
table.insert(formspec,
"box[0.3,20;19,2.6;red]"..
"label[0.7,20.3;"..yl_speak_up.text_version_warning.."]")
-- The up and down buttons are microscopic. Offer some (still small)
-- additional text buttons so that players have a chance to hit them.
table.insert(formspec, "button[49,22.8;6,0.9;button_down;^ Scroll Up ^]")
table.insert(formspec, "button[49,31.8;6,0.9;button_up;v Scroll Down v]")
end
-- old formspec versions need to remember somewhere extern how far the player scrolled
pname_for_old_fs = pname
yl_speak_up.speak_to[pname_for_old_fs].counter = 1
table.insert(formspec, "container[0,24]")
if(fs_version < 2) then
-- very small, ugly, and difficult to hit
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]")
else
-- somewhat larger and quite usable (v2 is pretty ok)
table.insert(formspec, "button[0.1,0;1,3;button_down;^\nU\np]")
table.insert(formspec, "button[0.1,3.2;1,4.5;button_up;D\no\nw\nn\nv]")
table.insert(formspec, "button[53.5,0;1,3;button_down;^\nU\np]")
table.insert(formspec, "button[53.5,3.2;1,4.5;button_up;D\no\nw\nn\nv]")
end
end
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 and yl_speak_up.old_fs_version_show_line(pname_for_old_fs)) then
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_<nr>:" 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 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_<nr>" 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..";"..
minetest.formspec_escape(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)))
-- 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)..",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)
-- normal mode: show an option if the prerequirements (if any are defined) are met
elseif(not(edit_mode)) then
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? 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
end
end
end
-- add a "I want to give you something" button to the first dialog if the NPC accepts items
if(active_dialog and active_dialog.d_sort and tonumber(active_dialog.d_sort) == 0) 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
-- If in edit mode, add new menu entries: "add new options", "end edit mode" and what else is needed.
if(edit_mode) then
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)
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)
-- chat option: Mute/Unmute NPC
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
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
-- 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.may_edit_npc(player, n_id)) 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
end
-- add a Let's trade button to the first dialog if the NPC has trades
if(active_dialog and active_dialog.d_sort and tonumber(active_dialog.d_sort) == 0
and dialog.trades) then
local has_trades = nil
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
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)
end
h = yl_speak_up.add_edit_button_fs_talkdialog(formspec, h,
"button_exit",
yl_speak_up.message_button_option_exit,
yl_speak_up.message_button_option_exit,
true, nil, true, pname_for_old_fs) -- button_exit
if(fs_version > 2) then
table.insert(formspec, "scroll_container_end[]")
else
table.insert(formspec, "container_end[]")
end
table.insert(formspec, "container_end[]")
return table.concat(formspec, "")
end