yl_speak_up/functions.lua
2023-11-14 02:29:43 +01:00

748 lines
25 KiB
Lua

-- 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 = {}
-- store what the player last entered in an text_input action
yl_speak_up.last_text_input = {}
yl_speak_up.reset_vars_for_player = function(pname, reset_fs_version)
yl_speak_up.speak_to[pname] = nil
yl_speak_up.edit_mode[pname] = nil
yl_speak_up.last_text_input[pname] = nil
-- when just stopping editing: don't reset the fs_version
if(reset_fs_version) then
yl_speak_up.fs_version[pname] = nil
end
end
--###
-- Debug
--###
yl_speak_up.debug = true
--###
-- Helpers
--###
yl_speak_up.get_number_from_id = function(any_id)
if(not(any_id) or any_id == "d_got_item" or any_id == "d_end") then
return "0"
end
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 = {
"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 and 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)
-- make sure we never store any automaticly added generic dialogs
dialog = yl_speak_up.strip_generic_dialogs(dialog)
local content = minetest.write_json(dialog)
return minetest.safe_file_write(p, content)
end
-- if a player is supplied: include generic dialogs
yl_speak_up.load_dialog = function(n_id, player) -- returns the saved dialog
local p = save_path(n_id)
local file, err = io.open(p, "r")
if err then
return yl_speak_up.add_generic_dialogs({}, n_id, player)
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 yl_speak_up.add_generic_dialogs(dialog, n_id, player)
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, false)
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, false)
dialog.n_dialogs[d_id] = nil
yl_speak_up.save_dialog(n_id, dialog)
end
--###
--Formspecs
--###
-- get formspecs
-- talk
-- 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, dialog_text)
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 = (dialog_text or ""),
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.")
-- add an option for going back to the start of the dialog;
-- this is an option which the player can delete and change according to needs,
-- not a fixed button which may not always fit
if(not(dialog_text)) then
-- we want to go back to the start from here
local target_dialog = yl_speak_up.get_start_dialog_id(dialog)
-- this text will be used for the button
local option_text = "Let's go back to the start of our talk."
-- we just created this dialog - this will be the first option
yl_speak_up.add_new_option(dialog, pname, "1", future_d_id, option_text, target_dialog)
end
return future_d_id
end
-- add a new option/answer to dialog d_id with option_text (or default "")
-- option_text (optional) the text that shall be shown as option/answer
-- target_dialog (optional) the target dialog where the player will end up when choosing
-- this option/answer
yl_speak_up.add_new_option = function(dialog, pname, next_id, d_id, option_text, target_dialog)
if(not(dialog) or not(dialog.n_dialogs) or not(dialog.n_dialogs[d_id])) then
return nil
end
if dialog.n_dialogs[d_id].d_options == nil then
-- make sure d_options exists
dialog.n_dialogs[d_id].d_options = {}
else
-- we don't want an infinite amount of answers per dialog
local sorted_list = yl_speak_up.get_sorted_options(dialog.n_dialogs[d_id].d_options, "o_sort")
local anz_options = #sorted_list
if(anz_options >= yl_speak_up.max_number_of_options_per_dialog) then
-- nothing added
return nil
end
end
if(not(next_id)) then
next_id = yl_speak_up.find_next_id(dialog.n_dialogs[d_id].d_options)
end
local future_o_id = "o_" .. next_id
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 = (option_text or ""),
}
-- 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
-- log only in edit mode
local n_id = yl_speak_up.speak_to[pname].n_id
if(yl_speak_up.npc_was_changed[ n_id ]) then
table.insert(yl_speak_up.npc_was_changed[ n_id ],
"Dialog "..d_id..": Added new option/answer "..future_o_id..".")
end
-- letting d_got_item point back to itself is not a good idea because the
-- NPC will then end up in a loop; plus the d_got_item dialog is intended for
-- automatic processing, not for showing to the player
if(d_id == "d_got_item") then
-- unless the player specifies something better, we go back to the start dialog
-- (that is where d_got_item got called from anyway)
target_dialog = yl_speak_up.get_start_dialog_id(dialog)
-- ...and this option needs to be selected automaticly
dialog.n_dialogs[d_id].d_options[future_o_id].o_autoanswer = 1
elseif(d_id == "d_trade") then
-- we really don't want to go to another dialog from here
target_dialog = "d_trade"
-- ...and this option needs to be selected automaticly
dialog.n_dialogs[d_id].d_options[future_o_id].o_autoanswer = 1
end
local future_r_id = nil
-- create a fitting dialog result automaticly if possible:
-- give this new dialog a dialog result that leads back to this dialog
-- (which is more helpful than creating tons of empty dialogs)
if(target_dialog and (dialog.n_dialogs[target_dialog] or target_dialog == "d_end")) then
future_r_id = yl_speak_up.add_new_result(dialog, d_id, future_o_id)
-- actually store the new result
dialog.n_dialogs[d_id].d_options[future_o_id].o_results = {}
dialog.n_dialogs[d_id].d_options[future_o_id].o_results[future_r_id] = {
r_id = future_r_id,
r_type = "dialog",
r_value = target_dialog}
end
-- the d_got_item dialog is special; players can easily forget to add the
-- necessary preconditions and effects, so we do that manually here
if(d_id == "d_got_item") then
-- we also need a precondition so that the o_autoanswer can actually get called
dialog.n_dialogs[d_id].d_options[future_o_id].o_prerequisites = {}
-- we just added this option; this is the first and for now only precondition for it;
-- the player still has to adjust it, but at least it is a reasonable default
dialog.n_dialogs[d_id].d_options[future_o_id].o_prerequisites["p_1"] = {
p_id = "p_1",
p_type = "player_offered_item",
p_item_stack_size = tostring(next_id),
p_match_stack_size = "exactly",
-- this is just a simple example item and ought to be changed after adding
p_value = "default:stick "..tostring(next_id)}
-- we need to show the player that his action was successful
dialog.n_dialogs[d_id].d_options[future_o_id].o_results[future_r_id].alternate_text =
"Thank you for the "..tostring(next_id).." stick(s)! "..
"Never can't have enough sticks.\n$TEXT$"
-- we need an effect for accepting the item;
-- taking all that was offered and putting it into the NPC's inventory is a good default
future_r_id = yl_speak_up.add_new_result(dialog, d_id, future_o_id)
dialog.n_dialogs[d_id].d_options[future_o_id].o_results[future_r_id] = {
r_id = future_r_id,
r_type = "deal_with_offered_item",
r_value = "take_all"}
-- the trade dialog is equally special
elseif(d_id == "d_trade") then
dialog.n_dialogs[d_id].d_options[future_o_id].o_prerequisites = {}
-- this is just an example
dialog.n_dialogs[d_id].d_options[future_o_id].o_prerequisites["p_1"] = {
p_id = "p_1",
p_type = "npc_inv",
p_value = "inv_does_not_contain",
p_inv_list_name = "npc_main",
p_itemstack = "default:stick "..tostring(100-next_id)}
future_r_id = yl_speak_up.add_new_result(dialog, d_id, future_o_id)
-- example craft
dialog.n_dialogs[d_id].d_options[future_o_id].o_results[future_r_id] = {
r_id = future_r_id,
r_type = "craft",
r_value = "default:stick 4",
o_sort = "1",
r_craft_grid = {"default:wood", "", "", "", "", "", "", "", ""}}
end
return future_o_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)
if(not(options[a][sort_by])) then
return false
elseif(not(options[b][sort_by])) then
return true
-- sadly not all entries are numeric
elseif(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_<nr>, o_<nr> 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
-- identify multiple results that lead to target dialogs
yl_speak_up.check_for_disambigous_results = function(n_id, pname)
local errors_found = false
-- this is only checked when trying to edit this npc;
-- let's stick to check the dialogs of this one without generic dialogs
local dialog = yl_speak_up.load_dialog(n_id, false)
-- 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
local msg = "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!"
yl_speak_up.log_change(pname, n_id, msg, "error")
if(pname) then
minetest.chat_send_player(pname, msg)
end
errors_found = true
end
end
end
end
end
return errors_found
end
-- allow to enter force edit mode (useful when an NPC was broken)
yl_speak_up.force_edit_mode = {}
-- command to enter force edit mode
yl_speak_up.command_npc_talk_force_edit = function(pname, param)
if(not(pname)) then
return
end
if(yl_speak_up.force_edit_mode[pname]) then
yl_speak_up.force_edit_mode[pname] = nil
minetest.chat_send_player(pname,
"Ending force edit mode for NPC. From now on talks "..
"will no longer start in edit mode.")
else
yl_speak_up.force_edit_mode[pname] = true
minetest.chat_send_player(pname,
"STARTING force edit mode for NPC. From now on talks "..
"with NPC will always start in edit mode provided "..
"you are allowed to edit this NPC.\n"..
"In order to end force edit mode, give the command "..
"/npc_talk_force_edit a second time.")
end
end
-- Make the NPC talk
-- assign n_ID
-- usually this happens when talking to the NPC for the first time;
-- but if you want to you can call this function earlier (on spawn)
-- so that logging of spawning with the ID is possible
yl_speak_up.initialize_npc = function(self)
-- already configured?
if(not(self) or (self.yl_speak_up and self.yl_speak_up.id)) then
return self
end
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)
self.yl_speak_up = {
talk = m_talk,
id = m_id,
textures = self.textures
}
return self
end
function yl_speak_up.talk(self, clicker)
if not clicker and not clicker:is_player() then
return
end
if not self then
return
end
local id_prefix = "n"
-- we are not dealing with an NPC but with a position/block on the map
if(self.is_block) then
id_prefix = "p"
local owner = "- unknown -"
local talk_name = "- unknown -"
if(self.pos and self.pos and self.pos.x) then
local meta = minetest.get_meta(self.pos)
if(meta) then
owner = meta:get_string("owner") or ""
talk_name = meta:get_string("talk_name") or ""
end
end
self.yl_speak_up = {
is_block = true,
talk = true,
id = minetest.pos_to_string(self.pos, 0),
textures = {},
owner = owner,
npc_name = talk_name,
object = nil, -- blocks don't have an object
}
-- TODO: remember somewhere that this block is relevant
-- initialize the mob if necessary; this happens at the time of first talk, not at spawn time!
elseif(not(self.yl_speak_up) or not(self.yl_speak_up.id)) then
self = yl_speak_up.initialize_npc(self)
end
-- create a detached inventory for the npc and load its inventory
yl_speak_up.load_npc_inventory(id_prefix.."_"..tostring(self.yl_speak_up.id))
local npc_id = self.yl_speak_up.id
local n_id = id_prefix.."_" .. 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
local was = "This NPC"
if(id_prefix ~= "n") then
was = "This block"
end
-- 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 was)..
" [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, was.." 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].textures = self.yl_speak_up.textures
yl_speak_up.speak_to[pname].option_index = 1
-- the object itself may be needed in load_dialog for adding generic dialogs
yl_speak_up.speak_to[pname].obj = self.object
-- Load the dialog and see what we can do with it
-- this inculdes generic dialog parts;
-- make sure this is never true in edit mode (because in edit mode we want to
-- edit this particular NPC without generic parts)
local player = clicker
if(yl_speak_up.edit_mode[pname] == n_id) then
player = false
end
yl_speak_up.speak_to[pname].dialog = yl_speak_up.load_dialog(n_id, player)
-- 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
-- are we in force edit mode, and can the player edit this NPC?
if(yl_speak_up.force_edit_mode[pname]
and yl_speak_up.may_edit_npc(clicker, n_id)) then
yl_speak_up.edit_mode[pname] = n_id
end
local dialog = yl_speak_up.speak_to[pname].dialog
if(not(dialog.trades)) then
dialog.trades = {}
end
-- some NPC may have reset the animation; at least set it to the desired
-- value whenever we talk to the NPC
if self.yl_speak_up and self.yl_speak_up.animation then
self.object:set_animation(self.yl_speak_up.animation)
end
-- maintain a list of existing NPC, but do not force saving
yl_speak_up.update_npc_data(self, dialog, false)
yl_speak_up.show_fs(clicker, "talk", {n_id = n_id})
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 n_"..npc.." will shut up at pos "..
-- minetest.pos_to_string(obj:get_pos(),0).." on command of "..p_name)
minetest.chat_send_player(p_name, "NPC n_"..tostring(npc).." is now muted and will "..
"only talk to those who can edit the NPC.")
yl_speak_up.log_change(p_name, "n_"..npc, "muted - NPC stops talking")
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 n_"..tostring(npc).." is no longer muted and "..
"will talk with any player who right-clicks the NPC.")
-- minetest.chat_send_player(p_name,"NPC with ID n_"..npc.." will resume speech at pos "..
-- minetest.pos_to_string(obj:get_pos(),0).." on command of "..p_name)
yl_speak_up.log_change(p_name, "n_"..npc, "unmuted - NPC talks again")
end
end
-- checks if dialog contains d_id and o_id
yl_speak_up.check_if_dialog_has_option = function(dialog, d_id, o_id)
return (dialog and d_id and o_id
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_id])
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]
and yl_speak_up.speak_to[pname].may_edit_this_npc))
end