-- 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
--
--###
-- 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 = {}
-- store the current trade between player and npc in case it gets edited in the meantime
yl_speak_up.trade = {}
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
--###
yl_speak_up.get_number_from_id = function(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
yl_speak_up.get_error_message = function()
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
yl_speak_up.find_next_id = function(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(yl_speak_up.get_number_from_id(k))
if keynum >= start_id then
start_id = keynum + 1
end
end
return start_id
end
yl_speak_up.sanitize_sort = function(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
--###
-- we can't really log changes here in this function because we don't know *what* has been changed
yl_speak_up.save_dialog = function(n_id, dialog)
if type(n_id) ~= "string" or type(dialog) ~= "table" then
return false
end
local p = save_path(n_id)
-- save some data (in particular usage of quest variables)
yl_speak_up.update_stored_npc_data(n_id, dialog)
local content = minetest.write_json(dialog)
return minetest.safe_file_write(p, content)
end
yl_speak_up.load_dialog = function(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
-- used by staff and input_inital_config
yl_speak_up.fields_to_dialog = function(pname, fields)
local n_id = yl_speak_up.speak_to[pname].n_id
local dialog = yl_speak_up.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_" .. yl_speak_up.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
yl_speak_up.delete_dialog = function(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 = yl_speak_up.load_dialog(n_id)
dialog.n_dialogs[d_id] = nil
yl_speak_up.save_dialog(n_id, dialog)
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
-- talk
yl_speak_up.get_fs_talkdialog = function(player, n_id, d_id, alternate_text)
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)
-- 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
return yl_speak_up.get_fs_talkdialog(player, n_id, target_dialog, res.alternate_text)
end
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,
"]",
}
-- 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 and sorted_list[ i-1 ]) then
local prev_dialog = minetest.formspec_escape(sorted_list[i-1])
table.insert(formspec, "button[8.5,4.0;2,0.9;prev_dialog_"..prev_dialog..";<]")
table.insert(formspec, "tooltip[prev_dialog_"..prev_dialog..
";Go to previous dialog "..prev_dialog..".]")
end
if(d == c_d_id and sorted_list[ i+1 ]) then
local next_dialog = minetest.formspec_escape(sorted_list[i+1])
table.insert(formspec, "button[11,4.0;2,0.9;next_dialog_"..next_dialog..";>]")
table.insert(formspec, "tooltip[next_dialog_"..next_dialog..
";Go to next dialog "..next_dialog..".]")
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, "button[15.7,0.3;2,0.9;button_save_dialog;Save]")
table.insert(formspec, "tooltip[button_save_dialog;Save this dialog.]")
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
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))
table.insert(formspec, "hypertext[0.2,5;19.6,17.8;d_text;")
table.insert(formspec, t .. "\n")
table.insert(formspec, "]")
table.insert(formspec, "tooltip[d_text;")
table.insert(formspec, t: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[2.3," .. 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[9.0," .. 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[4.4,"..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[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
if(prereq and next(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.6," .. 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\n"..
"to a new dialog) set for this option. Display them.]")
end
local actions = active_dialog.d_options[sb_v.o_id].actions
-- are there any actions defined?
if(actions and next(actions)) then
table.insert(formspec, "button[1.1," .. h .. ";0.5,0.9;effects_"..oid..";A]")
table.insert(formspec, "tooltip[effects_" .. oid .. ";"..
"There is an (A)ction (i.e. a trade) that will happen\n"..
"when switching to a new dialog. Display effects and\n"..
"trade of this option.]")
end
-- show the actual text for the option
table.insert(formspec, "field[9.9," .. h .. ";44.4,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
-- replace $NPC_NAME$ etc.
local t = minetest.formspec_escape(yl_speak_up.replace_vars_in_text(
sb_v.o_text_when_prerequisites_met, dialog, pname))
table.insert(formspec, "button[0.5," .. h .. ";53.8,0.9;button_" .. oid .. ";]")
table.insert(
formspec,
"tooltip[button_" .. oid .. ";" .. t .. "]"
)
local l = h + 0.45
table.insert(formspec, "label[0.7," .. l .. ";" .. t .. "]")
else
if sb_v.o_hide_when_prerequisites_not_met == "true" then
else
-- replace $NPC_NAME$ etc.
local t = minetest.formspec_escape(yl_speak_up.replace_vars_in_text(
sb_v.o_text_when_prerequisites_not_met, dialog, pname))
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 .. ";" .. t .. "]"
)
local l = h + 0.45
table.insert(
formspec,
"label[0.7," .. l .. ";" .. t .. "]"
)
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 .. ";" .. t .. "]"
)
local l = h + 0.45
table.insert(
formspec,
"label[0.7," .. l .. ";" .. t .. "]"
)
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: "Show what points to this dialog."
h = h + 1
table.insert(formspec, "button[0.5," .. h .. ";53.8,0.9;show_what_points_to_this_dialog;]")
table.insert(formspec, "tooltip[show_what_points_to_this_dialog;"..
"Show which other dialog options or failed actions\n"..
"or effects lead the player to this dialog here.]")
table.insert(formspec, "label[0.7,"..(h+0.45)..";Show what points to this dialog.]")
-- 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
-- access the inventory of the NPC
h = h + 1
table.insert(formspec, "button[0.5," .. h .. ";53.8,0.9;show_inventory;]")
table.insert(formspec, "tooltip[show_inventory;Access and manage the inventory of the NPC. "..
"This is used for adding trade items, getting collected payments and managing "..
"quest items.]")
table.insert(formspec, "label[0.7,"..(h+0.45)..";"..
"Show your inventory (only accessible to owner)!]")
-- chat option: Mute/Unmute NPC
h = h + 1
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(luaentity and luaentity.yl_speak_up.talk) then
table.insert(formspec, "button[0.5," .. h .. ";53.8,0.9;mute_npc;]")
table.insert(formspec, "tooltip[mute_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.]")
table.insert(formspec, "label[0.7,"..(h+0.45)..";State: Not muted. Stop talking to other "..
"players while I give you new orders.]")
elseif(luaentity) then
-- unmute the NPC
table.insert(formspec, "button[0.5," .. h .. ";53.8,0.9;un_mute_npc;]")
table.insert(formspec, "tooltip[un_mute_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.]")
table.insert(formspec, "label[0.7,"..(h+0.45)..";State: You are currently muted. Talk to "..
"anyone again who wants to talk to you.]")
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.may_edit_npc(player, n_id)) 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
-- 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
if(has_trades) then
h = h + 1
table.insert(formspec, "button[0.5," .. h .. ";53.8,0.9;show_trade_list;]")
table.insert(formspec, "tooltip[show_trade_list;Show a list of trades the NPC has to offer.]")
table.insert(formspec, "label[0.7,"..(h+0.45)..";Let's trade!]")
end
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[]")
-- if the player has an older formspec version
-- TODO: the version for older formspec versions lacks quite a lot - in particular regarding editing
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
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))
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;;;",
t .. "\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
-- talk
-- helper function
-- the option to override next_id and provide a value is needed when a new dialog was
-- added, then edited, and then discarded; it's still needed after that, but has to
-- be reset to empty state (wasn't stored before)
yl_speak_up.add_new_dialog = function(dialog, pname, next_id)
if(not(next_id)) then
next_id = yl_speak_up.find_next_id(dialog.n_dialogs)
end
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
-- add a new result to option o_id of dialog d_id
yl_speak_up.add_new_result = function(dialog, d_id, o_id)
if(not(dialog) or not(dialog.n_dialogs) or not(dialog.n_dialogs[d_id])
or not(dialog.n_dialogs[d_id].d_options) or not(dialog.n_dialogs[d_id].d_options[o_id])) then
return
end
-- create a new result (first the id, then the actual result)
local future_r_id = "r_" .. yl_speak_up.find_next_id(dialog.n_dialogs[d_id].d_options[o_id].o_results)
if future_r_id == "r_1" then
dialog.n_dialogs[d_id].d_options[o_id].o_results = {}
end
dialog.n_dialogs[d_id].d_options[o_id].o_results[future_r_id] = {}
return future_r_id
end
-- this is useful for result types that can exist only once per option
-- (apart from editing with the staff);
-- examples: "dialog" and "trade";
-- returns tue r_id or nil if no result of that type has been found
yl_speak_up.get_result_id_by_type = function(dialog, d_id, o_id, result_type)
if(not(dialog) or not(dialog.n_dialogs) or not(dialog.n_dialogs[d_id])
or not(dialog.n_dialogs[d_id].d_options) or not(dialog.n_dialogs[d_id].d_options[o_id])) then
return
end
local results = dialog.n_dialogs[d_id].d_options[o_id].o_results
if(not(results)) then
return
end
for k, v in pairs(results) do
if(v.r_type == result_type) then
return k
end
end
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
-- simple sort of keys of a table numericly;
-- this is not efficient - but that doesn't matter: the lists are small and
-- it is only executed when configuring an NPC
-- simple: if the parameter is true, the keys will just be sorted (i.e. player names) - which is
-- not enough for d_, o_ etc. (which need more care when sorting)
yl_speak_up.sort_keys = function(t, simple)
local keys = {}
for k, v in pairs(t) do
-- add a prefix so that p_2 ends up before p_10
if(not(simple) and string.len(k) == 3) then
k = "a"..k
end
table.insert(keys, k)
end
table.sort(keys)
if(simple) then
return keys
end
for i,k in ipairs(keys) do
-- avoid cutting the single a from a_1 (action 1)
if(k and string.sub(k, 1, 1) == "a" and string.sub(k, 2, 2) ~= "_") then
-- remove the leading blank
keys[i] = string.sub(k, 2)
end
end
return keys
end
-- helper function for yl_speak_up.edit_mode_apply_changes;
-- makes sure the new dialog (and a result/effect "dialog" for each option) exist
yl_speak_up.prepare_new_dialog_for_option = function(dialog, pname, n_id, d_id, o_id,target_dialog,o_results)
-- is there a result/effect of the type "dialog" already? else use a fallback
local result = {r_value = "-default-"}
if(o_results) then
for kr, vr in pairs(o_results) do
if( vr.r_type == "dialog" ) then
result = vr
end
end
end
-- this may also point to a new dialog
if(target_dialog == 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)
target_dialog = yl_speak_up.add_new_dialog(dialog, pname, nil)
elseif( result.r_value and result.r_value == target_dialog) then
-- no problem - the right dialog is set already
return target_dialog
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(o_id).." was changed from "..
tostring(result.r_value or "-default-").." to "..
tostring(target_dialog)..".")
-- does the result/effect of type "dialog" exist already? then we're done
if(result.r_type and result.r_type == "dialog") then
-- actually change the target dialog
result.r_value = target_dialog
return target_dialog
end
-- create a new result (first the id, then the actual result)
local future_r_id = yl_speak_up.add_new_result(dialog, d_id, o_id)
-- actually store the new result
dialog.n_dialogs[d_id].d_options[o_id].o_results[future_r_id] = {
r_id = future_r_id,
r_type = "dialog",
r_value = target_dialog}
return target_dialog
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
-- 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)
-- mute/unmute gets logged in the function and does not need extra log entries
local obj = yl_speak_up.speak_to[pname].obj
if(fields.mute_npc and obj) then
yl_speak_up.set_muted(pname, obj, true)
elseif(fields.un_mute_npc and obj) then
yl_speak_up.set_muted(pname, obj, false)
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_" .. yl_speak_up.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
-- this is already checked earlier on and the button only shown if
-- options can be added; so this can reamin a chat message
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 = yl_speak_up.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 ]) then
local new_target_dialog = yl_speak_up.prepare_new_dialog_for_option(
dialog, pname, n_id, d_id, k, fields[ "d_id_"..k ], v.o_results)
if(new_target_dialog ~= fields[ "d_id_"..k ]) then
fields[ "d_id_"..k ] = new_target_dialog
-- in options edit menu: show this update
result["show_next_option"] = k
end
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, nil)
-- 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
yl_speak_up.delete_dialog(n_id, d_id)
yl_speak_up.log_change(pname, n_id,
"Deleted dialog "..tostring(d_id)..".")
-- switch to another dialog (this one was deleted after all)
fields["d_id"] = new_dialog
fields["show_new_dialog"] = nil
else
-- deleting is only possible from the talk menu, and there the delete
-- button is only shown if the dialog can be deleted; so this can remain
-- a chat message
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
-- toggle autoselection/autoclick of an option
if(d_option and fields.option_autoanswer and fields.option_autoanswer ~= "") then
if(d_option.o_autoanswer and d_option.o_autoanswer == 1
and fields.option_autoanswer == "by clicking on it") then
d_option.o_autoanswer = nil
table.insert(yl_speak_up.npc_was_changed[ n_id ],
"Dialog "..d_id..": The modus for option "..tostring(o_id)..
" was changed from \"automaticly\" to \"by clicking on it\".")
elseif((not(d_option.o_autoanswer) or d_option.o_autoanswer ~= 1)
and fields.option_autoanswer == "automaticly") then
d_option.o_autoanswer = 1
table.insert(yl_speak_up.npc_was_changed[ n_id ],
"Dialog "..d_id..": The modus for option "..tostring(o_id)..
" was changed from \"by clicking on it\" to \"automaticly\".")
end
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 the following 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
-- how many times can the player fail to execute the action successfully?
if(fields[ "timer_max_attempts_on_failure"]) then
local field_name = "timer_max_attempts_on_failure"
local timer_name = "timer_on_failure_"..tostring(d_id).."_"..tostring(o_id)
if(not(tonumber(fields[ field_name ]))) then
fields[ field_name ] = 0
end
-- make sure the variable exists
if(yl_speak_up.add_time_based_variable(timer_name)) then
yl_speak_up.set_variable_metadata(timer_name, nil, "parameter", "max_attempts",
fields[ field_name ])
end
end
-- ..and how long has the player to wait in order to try again?
if(fields[ "timer_max_seconds_on_failure"]) then
local field_name = "timer_max_seconds_on_failure"
local timer_name = "timer_on_failure_"..tostring(d_id).."_"..tostring(o_id)
if(not(tonumber(fields[ field_name ]))) then
fields[ field_name ] = 0
end
-- make sure the variable exists
if(yl_speak_up.add_time_based_variable(timer_name)) then
yl_speak_up.set_variable_metadata(timer_name, nil, "parameter", "duration",
fields[ field_name ])
end
end
if(fields[ "timer_max_seconds_on_success"]) then
local field_name = "timer_max_seconds_on_success"
local timer_name = "timer_on_success_"..tostring(d_id).."_"..tostring(o_id)
if(not(tonumber(fields[ field_name ]))) then
fields[ field_name ] = 0
end
-- make sure the variable exists
if(yl_speak_up.add_time_based_variable(timer_name)) then
yl_speak_up.set_variable_metadata(timer_name, nil, "parameter", "duration",
fields[ field_name ])
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
-- 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 = yl_speak_up.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
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 = ""
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
-- 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
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
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["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
-- Make the NPC talk
--###
-- 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)
-- create a detached inventory for the npc and load its inventory
yl_speak_up.load_npc_inventory("n_"..tostring(self.yl_speak_up.id))
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
local npc_id = self.yl_speak_up.id
local n_id = "n_" .. npc_id
-- 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
local pname = clicker:get_player_name()
if not self.yl_speak_up or not self.yl_speak_up.talk or self.yl_speak_up.talk~=true then
-- show a formspec to other players that this NPC is busy
if(not(yl_speak_up.may_edit_npc(clicker, n_id))) then
-- show a formspec so that the player knows that he may come back later
yl_speak_up.show_fs(player, "msg", {input_to = "yl_spaek_up:ignore", formspec =
"size[6,2]"..
"label[1.2,0.0;"..minetest.formspec_escape((self.yl_speak_up.npc_name or "This NPC")..
" [muted]").."]"..
"label[0.2,0.5;Sorry! I'm currently busy learning new things.]"..
"label[0.2,1.0;Please come back later.]"..
"button_exit[2.5,1.5;1,0.9;ok;Ok]"})
return
end
-- allow the owner to edit (and subsequently unmute) the npc
minetest.chat_send_player(pname, "This NPC is muted. It will only talk to you.")
end
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 = yl_speak_up.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
-- is this player explicitly allowed to edit this npc?
if(yl_speak_up.speak_to[pname].dialog
and yl_speak_up.speak_to[pname].dialog.n_may_edit
and yl_speak_up.speak_to[pname].dialog.n_may_edit[pname]
and minetest.check_player_privs(clicker, {npc_talk_owner=true})) then
yl_speak_up.speak_to[pname].may_edit_this_npc = true
end
yl_speak_up.show_fs(clicker, "talk", {n_id = n_id})
local dialog = yl_speak_up.speak_to[pname].dialog
if(not(dialog.trades)) then
dialog.trades = {}
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
local 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
-- has the player the right privs?
-- this is used for the "I am your master" talk based configuration; *NOT* for the staffs!
yl_speak_up.may_edit_npc = function(player, n_id)
if(not(player)) then
return false
end
local pname = player:get_player_name()
-- is the player allowed to edit this npc?
return ((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_talk_master=true})
or minetest.check_player_privs(player, {npc_master=true})
or yl_speak_up.speak_to[pname].may_edit_this_npc)
end
-- log changes done by players or admins to NPCs
yl_speak_up.log_change = function(pname, n_id, text)
-- make sure all variables are defined
if(not(pname)) then
pname = "- unkown player -"
end
if(not(n_id)) then
n_id = "- unknown NPC -"
end
if(not(text)) then
text = "- no text given -"
end
-- we don't want newlines in the texts
text = string.gsub(text, "\n", "\\n")
local log_text = "<"..tostring(n_id).."> ["..tostring(pname).."]: "..text
-- log in general logfile
minetest.log("yl_speak_up "..log_text)
-- log with timestamp
local log_text = tostring(os.time())..log_text
-- TODO: log in a file for each npc and show it on demand?
end