forked from your-land-mirror/yl_speak_up
master #4
188
addons/action_send_mail.lua
Normal file
188
addons/action_send_mail.lua
Normal file
@ -0,0 +1,188 @@
|
||||
-- requires mail.send from the mail mod
|
||||
|
||||
-- sending a mail allows to give feedback - and for players to ask the NPC owner to add more texts
|
||||
-- and let the NPC answer to further questions
|
||||
|
||||
-- define the custom action named "send_mail"
|
||||
local action_send_mail = {
|
||||
-- this information is necessary for allowing to add this as an action to an option
|
||||
description = "Send a mail via the mail_mod mod for feedback/ideas/etc.",
|
||||
-- define the parameters that can be set when the action is added
|
||||
param1_text = "To:",
|
||||
param1_desc = "Who shall receive this mail? Default: $OWNER_NAME$."..
|
||||
"\nNote: Leave fields empty for the default values."..
|
||||
"\nNote: All parameters allow to use the usual replacements like $NPC_NAME$,"..
|
||||
"\n\t$OWNER_NAME$, $PLAYER_NAME$, $VAR name_of_your_var$, $PROP name_of_prop$.",
|
||||
param2_text = "From:",
|
||||
param2_desc = "Who shall be listed as the sender of this mail?\n"..
|
||||
"The player talking to the NPC might be best as it makes answering easier.\n"..
|
||||
"Default: $PLAYER_NAME$. Also allowed: $OWNER_NAME$.",
|
||||
param3_text = "cc:",
|
||||
param3_desc = "(optional) Whom to send a carbon copy to?"..
|
||||
"\nThis is useful if multiple players may edit this NPC."..
|
||||
"\nIt is also possible to send a copy to $PLAYER_NAME$.",
|
||||
param4_text = "bcc:",
|
||||
param4_desc = "(optional) Who gets set in the bcc?",
|
||||
param5_text = "Subject:",
|
||||
param5_desc = "The subject of the mail. The player talking to the NPC\n"..
|
||||
"will provide the actual text for the body of the mail.\n"..
|
||||
"Default: \"$NPC_NAME$: regarding $PLAYER_NAME$\"",
|
||||
}
|
||||
|
||||
|
||||
-- this function will show a formspec whenever our custom action "send_mail" is executed
|
||||
--yl_speak_up.custom_functions_a_[ "send_mail" ].code = function(player, n_id, a)
|
||||
action_send_mail.code = function(player, n_id, a)
|
||||
local pname = player:get_player_name()
|
||||
-- sending the mail can either succeed or fail; pdata.tmp_mail_* variables store the result
|
||||
local pdata = yl_speak_up.speak_to[pname]
|
||||
-- just the normal dialog data from the NPC (contains name of the NPC and owner)
|
||||
local dialog = yl_speak_up.speak_to[pname].dialog
|
||||
local npc_name = "- (this NPC) -"
|
||||
local owner_name = "- (his owner) -"
|
||||
if(dialog) then
|
||||
-- the NPC is the one "forwarding" the message (so that the receiver will know
|
||||
-- *which* NPC was talked to)
|
||||
npc_name = minetest.formspec_escape(dialog.n_npc or "- ? -")
|
||||
-- usually the owner is the receiver
|
||||
owner_name = minetest.formspec_escape(a.a_param1 or dialog.npc_owner or "- ? ")
|
||||
end
|
||||
|
||||
-- the mail was already sent successful; we still return once to this formspec so that
|
||||
-- the player gets this information and can finish the action successfully
|
||||
if(pdata and pdata.tmp_mail_to and pdata.tmp_mail_success) then
|
||||
local mail_to = minetest.formspec_escape(pdata.tmp_mail_to or "?")
|
||||
-- unset temporary variables that are no longer needed
|
||||
pdata.tmp_mail_success = nil
|
||||
pdata.tmp_mail_error = nil
|
||||
pdata.tmp_mail_to = nil
|
||||
return table.concat({
|
||||
"size[20,3]label[0.2,0.7;",
|
||||
npc_name,
|
||||
" has sent a mail containing your text to ",
|
||||
mail_to,
|
||||
" and awaits further instructions."..
|
||||
"\nPlease be patient and wait for a reply. This may take some time as ",
|
||||
mail_to,
|
||||
" has to receive, read and answer the mail.]",
|
||||
-- offer a button to finally complete the action successfully
|
||||
"button[4,2;6.8,0.9;finished_action;Ok. I'll wait.]",
|
||||
}, "")
|
||||
|
||||
-- we tried to send the mail - and an error occoured
|
||||
elseif(pdata and pdata.tmp_mail_to and pdata.tmp_mail_error) then
|
||||
local mail_to = minetest.formspec_escape(pdata.tmp_mail_to or "?")
|
||||
local error_msg = minetest.formspec_escape(pdata.tmp_mail_error or "?")
|
||||
-- unset temporary variables that are no longer needed
|
||||
pdata.tmp_mail_success = nil
|
||||
pdata.tmp_mail_error = nil
|
||||
pdata.tmp_mail_to = nil
|
||||
return table.concat({
|
||||
"size[20,8]label[0.2,0.7;",
|
||||
npc_name,
|
||||
" FAILED to sent a mail containing your text to ",
|
||||
mail_to,
|
||||
" in order to get help!]",
|
||||
"textarea[0.2,1.8;19.6,5;;The following error(s) occourd:;",
|
||||
error_msg,
|
||||
"]",
|
||||
-- the action can no longer be completed successfully; best to back to talk
|
||||
"button[7,7.0;6.0,0.9;back_to_talk;Back to talk]",
|
||||
}, "")
|
||||
end
|
||||
|
||||
-- the mail has not been sent yet; show the normal formspec asking for text input
|
||||
return table.concat({"size[20,8.5]label[4,0.7;Send a message to ",
|
||||
npc_name,
|
||||
"]",
|
||||
"button[17.8,0.2;2.0,0.9;back_to_talk;Back]",
|
||||
"label[0.2,7.0;Note: ",
|
||||
npc_name,
|
||||
" will send a mail to ",
|
||||
minetest.formspec_escape(a.a_param1 or dialog.npc_owner or "- ? "),
|
||||
", requesting instructions how to respond. This may take a while.]",
|
||||
"button[3.6,7.5;6.0,0.9;back_to_talk;Abort and go back]",
|
||||
"button[10.2,7.5;6.0,0.9;send_mail;Send this message]",
|
||||
-- read-only
|
||||
"textarea[0.2,1.8;19.6,5;message_text;Write your message for ",
|
||||
npc_name,
|
||||
" here, and then click on \"Send this message\":;",
|
||||
"",
|
||||
"]",
|
||||
})
|
||||
end
|
||||
|
||||
|
||||
-- whenever our formspec above for the custom action "send_mail" received input (player clicked
|
||||
-- on a button), this function is called
|
||||
action_send_mail.code_input_handler = function(player, n_id, a, formname, fields)
|
||||
local pname = player:get_player_name()
|
||||
if(not(pname) or not(yl_speak_up.speak_to[pname])) then
|
||||
return fields
|
||||
end
|
||||
-- sending was aborted or there was no text to send
|
||||
if( not(fields.message_text) or fields.message_text == ""
|
||||
or not(fields.send_mail) or fields.send_mail == "") then
|
||||
return fields
|
||||
end
|
||||
local dialog = yl_speak_up.speak_to[pname].dialog
|
||||
|
||||
-- prepare data/parameters for the mail we want to send
|
||||
-- $PLAYER_NAME$, $OWNER_NAME$, $NPC_NAME$, $VAR *$ $PROP *$ are allowed replacements!!
|
||||
local mail_to = yl_speak_up.replace_vars_in_text(a.a_param1, dialog, pname)
|
||||
local mail_from = yl_speak_up.replace_vars_in_text(a.a_param2, dialog, pname)
|
||||
local mail_cc = yl_speak_up.replace_vars_in_text(a.a_param3, dialog, pname)
|
||||
local mail_bcc = yl_speak_up.replace_vars_in_text(a.a_param4, dialog, pname)
|
||||
local mail_subject = yl_speak_up.replace_vars_in_text(a.a_param5, dialog, pname)
|
||||
if(not(mail_to) or mail_to == "") then
|
||||
mail_to = dialog.npc_owner
|
||||
end
|
||||
-- make sure the sender is not forged; we allow EITHER the name of the owner of the NPC
|
||||
-- OR the name of the player currently talking to the npc
|
||||
if(not(mail_from) or mail_from == "" or mail_from ~= dialog.npc_owner) then
|
||||
mail_from = pname
|
||||
end
|
||||
if(not(mail_cc) or mail_cc == "") then
|
||||
mail_cc = nil
|
||||
end
|
||||
if(not(mail_bcc) or mail_bcc == "") then
|
||||
mail_bcc = nil
|
||||
end
|
||||
if(not(mail_subject) or mail_subject == "") then
|
||||
mail_subject = (dialog.n_npc or "- ? -")..": regarding "..pname
|
||||
end
|
||||
-- actually send the mail via the mail_mod mod
|
||||
local success, error_msg = mail.send({
|
||||
from = mail_from,
|
||||
to = mail_to,
|
||||
cc = mail_cc,
|
||||
bcc = mail_bcc,
|
||||
subject = mail_subject,
|
||||
body = "Dear "..tostring(dialog.npc_owner)..",\n\n"..tostring(pname)..
|
||||
" asked me something I don't know the answer to. Hope you can help? "..
|
||||
"This is the request:\n\n"..
|
||||
tostring(fields.message_text or "- no message -")
|
||||
})
|
||||
-- Sending this mail was either successful or not. We want to display this to the player.
|
||||
-- Therefore, we set fields.back_from_error_msg. This tells the calling function that it
|
||||
-- needs to display the formspec generated by the function
|
||||
-- yl_speak_up.custom_functions_a_[ "send_mail" ].code
|
||||
-- again.
|
||||
fields.back_from_error_msg = true
|
||||
-- The function displaying the formspec needs to know that it has to display the result
|
||||
-- of sending the mail now. We need to store these variables somewhere.
|
||||
local pdata = yl_speak_up.speak_to[pname]
|
||||
pdata.tmp_mail_success = success
|
||||
pdata.tmp_mail_error = error_msg
|
||||
pdata.tmp_mail_to = mail_to
|
||||
-- the function has to return fields
|
||||
return fields
|
||||
end
|
||||
|
||||
|
||||
if(minetest.global_exists("mail")
|
||||
and type(mail) == "table"
|
||||
and type(mail.send) == "function") then
|
||||
-- only add this action if the mail mod and the mail.send function exist
|
||||
yl_speak_up.custom_functions_a_[ "send_mail" ] = action_send_mail
|
||||
end
|
||||
45
addons/effect_send_coordinates.lua
Normal file
45
addons/effect_send_coordinates.lua
Normal file
@ -0,0 +1,45 @@
|
||||
-- hand out a preconfigured waypoint compass to the player
|
||||
yl_speak_up.custom_functions_r_[ "send_coordinates" ] = {
|
||||
description = "Send a chat message to the player with coordinates.",
|
||||
param1_text = "X coordinate:",
|
||||
param1_desc = "The target x coordinate.",
|
||||
param2_text = "Y coordinate:",
|
||||
param2_desc = "The target y coordinate.",
|
||||
param3_text = "Z coordinate:",
|
||||
param3_desc = "The target z coordinate.",
|
||||
param4_text = "Name of target location:",
|
||||
param4_desc = "This is how the target location is called, i.e. \"Hidden treasure chest\".",
|
||||
-- the color cannot be set this way
|
||||
-- param5_text = "Color code in Hex:",
|
||||
-- param5_desc = "Give the color for the compass here. Example: \"FFD700\".\n"..
|
||||
-- "Needs to be 6 characters long, with each character ranging\n"..
|
||||
-- "from 0-9 or beeing A, B, C, D, E or F.\n"..
|
||||
-- "Or just write something like yellow, orange etc.",
|
||||
code = function(player, n_id, r)
|
||||
local pname = player:get_player_name()
|
||||
local coords = core.string_to_pos((r.r_param1 or "0")..","..
|
||||
(r.r_param2 or "0")..","..
|
||||
(r.r_param3 or "0"))
|
||||
local town = (r.r_param4 or "- some place somewhere -")
|
||||
if(not(coords)) then
|
||||
minetest.chat_send_player(pname, "Sorry. There was an internal error with the "..
|
||||
"coordinates. Please inform whoever is responsible for this NPC.")
|
||||
|
||||
return false
|
||||
end
|
||||
if(not(pname) or not(yl_speak_up.speak_to[pname])) then
|
||||
return false
|
||||
end
|
||||
local dialog = yl_speak_up.speak_to[pname].dialog
|
||||
minetest.chat_send_player(pname,
|
||||
(dialog.n_npc or "- ? -")..": \""..
|
||||
tostring(town).."\" can be found at "..core.pos_to_string(coords, 0)..".")
|
||||
if(minetest.get_modpath("waypoint_compass")) then
|
||||
minetest.chat_send_player(pname, "If you have a waypoint compass, right-click "..
|
||||
"while wielding it. Select \"copy:\" to copy the location above into "..
|
||||
"your compass.")
|
||||
end
|
||||
-- the function was successful (effects only return true or false)
|
||||
return true
|
||||
end,
|
||||
}
|
||||
94
addons/effect_send_mail.lua
Normal file
94
addons/effect_send_mail.lua
Normal file
@ -0,0 +1,94 @@
|
||||
|
||||
-- requires mail.send from the mail mod
|
||||
|
||||
-- NPC can send out mails in order to sum up a quest state or complex step
|
||||
-- - or just to inform their owner that they ran out of stock
|
||||
-- There is also a similar action defined in another file. The action
|
||||
-- allows the player that talks to the NPC to enter his/her own mailtext.
|
||||
-- The *effect* here requires that the text has been configured in advance.
|
||||
|
||||
-- define the custom effect named "send_mail"
|
||||
local effect_send_mail = {
|
||||
-- this information is necessary for allowing to add this as an effect to an option
|
||||
description = "Send a preconfigured mail via the mail_mod mod for quest state etc.",
|
||||
-- define the parameters that can be set when the action is added
|
||||
param1_text = "To:",
|
||||
param1_desc = "Who shall receive this mail? Default: $PLAYER_NAME$."..
|
||||
"\nNote: Leave fields empty for the default values."..
|
||||
"\nNote: All parameters allow to use the usual replacements like $NPC_NAME$,"..
|
||||
"\n\t$OWNER_NAME$, $PLAYER_NAME$, $VAR name_of_your_var$, $PROP name_of_prop$.",
|
||||
-- the "From:" field will always be the name of the owner of the NPC
|
||||
-- param2_text = "From:",
|
||||
-- param2_desc = "Who shall be listed as the sender of this mail?\n"..
|
||||
-- "The player talking to the NPC might be best as it makes answering easier.\n"..
|
||||
-- "Default: $PLAYER_NAME$. Also allowed: $OWNER_NAME$.",
|
||||
param3_text = "cc:",
|
||||
param3_desc = "(optional) Whom to send a carbon copy to?"..
|
||||
"\nThis is useful if multiple players may edit this NPC."..
|
||||
"\nIt is also possible to send a copy to $PLAYER_NAME$.",
|
||||
param4_text = "bcc:",
|
||||
param4_desc = "(optional) Who gets set in the bcc?",
|
||||
param5_text = "Subject:",
|
||||
param5_desc = "The subject of the mail. Ought to give the player information\n"..
|
||||
"which NPC sent this mail and why.\n"..
|
||||
"Default: \"$NPC_NAME$ has a message from $OWNER_NAME$\"",
|
||||
param6_text = "Mail text:",
|
||||
param6_desc = "The actual text of the mail. Use the usual replacements to make the mail\n"..
|
||||
"meaningful! You may want to use $VAR name_of_your_var$.\n"..
|
||||
"Note: Use \\n to create a newline!",
|
||||
}
|
||||
|
||||
|
||||
|
||||
-- the actual implementation of the function - run when the effect is executed
|
||||
effect_send_mail.code = function(player, n_id, r)
|
||||
local pname = player:get_player_name()
|
||||
if(not(pname) or not(yl_speak_up.speak_to[pname])) then
|
||||
return fields
|
||||
end
|
||||
local dialog = yl_speak_up.speak_to[pname].dialog
|
||||
|
||||
-- prepare data/parameters for the mail we want to send
|
||||
-- $PLAYER_NAME$, $OWNER_NAME$, $NPC_NAME$, $VAR *$ $PROP *$ are allowed replacements!
|
||||
local mail_to = yl_speak_up.replace_vars_in_text(r.r_param1, dialog, pname)
|
||||
local mail_from = yl_speak_up.replace_vars_in_text(r.r_param2, dialog, pname)
|
||||
local mail_cc = yl_speak_up.replace_vars_in_text(r.r_param3, dialog, pname)
|
||||
local mail_bcc = yl_speak_up.replace_vars_in_text(r.r_param4, dialog, pname)
|
||||
local mail_subject = yl_speak_up.replace_vars_in_text(r.r_param5, dialog, pname)
|
||||
local mail_text = yl_speak_up.replace_vars_in_text(r.r_param6, dialog, pname)
|
||||
-- this is in reverse of the actions: the mail is usually sent to the player the
|
||||
-- NPC is talking with - e.g. as a reminder of a quest status
|
||||
if(not(mail_to) or mail_to == "") then
|
||||
mail_to = pname
|
||||
end
|
||||
-- the mail always originates from the owner of the NPC
|
||||
mail_from = dialog.npc_owner
|
||||
if(not(mail_cc) or mail_cc == "") then
|
||||
mail_cc = nil
|
||||
end
|
||||
if(not(mail_bcc) or mail_bcc == "") then
|
||||
mail_bcc = nil
|
||||
end
|
||||
if(not(mail_subject) or mail_subject == "") then
|
||||
mail_subject = (dialog.n_npc or "- ? -").." has a message from "..(dialog.npc_owner or "- ? -")
|
||||
end
|
||||
-- actually send the mail via the mail_mod mod
|
||||
local success, error_msg = mail.send({
|
||||
from = mail_from,
|
||||
to = mail_to,
|
||||
cc = mail_cc,
|
||||
bcc = mail_bcc,
|
||||
subject = mail_subject,
|
||||
body = "Message from "..tostring(dialog.n_npc or "- ? -")..":\n\n"..
|
||||
table.concat(string.split(mail_text or "- no message -", "\\n"), "\n")
|
||||
})
|
||||
return success
|
||||
end
|
||||
|
||||
|
||||
if(minetest.global_exists("mail")
|
||||
and type(mail) == "table"
|
||||
and type(mail.send) == "function") then
|
||||
-- only add this effect if the mail mod and the mail.send function exist
|
||||
yl_speak_up.custom_functions_r_[ "send_mail" ] = effect_send_mail
|
||||
end
|
||||
20
addons/load_addons.lua
Normal file
20
addons/load_addons.lua
Normal file
@ -0,0 +1,20 @@
|
||||
|
||||
-- this file lods addons - actions, preconditions, effects and other things
|
||||
-- - which may not be of intrest to all games
|
||||
-- - and which usually require other mods to be installed in order to work
|
||||
|
||||
local path_addons = yl_speak_up.modpath..DIR_DELIM.."addons"..DIR_DELIM
|
||||
|
||||
|
||||
-- the action "send_mail" requires the "mail" mod and allows to send
|
||||
-- ingame mails via actions
|
||||
if(minetest.global_exists("mail")
|
||||
and type(mail) == "table"
|
||||
and type(mail.send) == "function") then
|
||||
|
||||
dofile(path_addons .. "action_send_mail.lua")
|
||||
dofile(path_addons .. "effect_send_mail.lua")
|
||||
end
|
||||
|
||||
-- makes mostly sense if the waypoint_compass mod is installed
|
||||
dofile(path_addons.."effect_send_coordinates.lua")
|
||||
@ -8,26 +8,6 @@ yl_speak_up.stop_talking = function(pname)
|
||||
end
|
||||
|
||||
|
||||
-- helper function for
|
||||
-- yl_speak_up.get_fs_talkdialog and
|
||||
-- yl_speak_up.check_and_add_as_generic_dialog
|
||||
-- find the dialog with d_sort == 0 or lowest number
|
||||
yl_speak_up.get_start_dialog_id = function(dialog)
|
||||
if(not(dialog) or not(dialog.n_dialogs)) then
|
||||
return nil
|
||||
end
|
||||
-- Find the dialog with d_sort = 0 or alternatively with the lowest number
|
||||
local lowest_sort = nil
|
||||
local d_id = nil
|
||||
for k, v in pairs(dialog.n_dialogs) do
|
||||
local nr = tonumber(v.d_sort)
|
||||
if(not(lowest_sort) or (nr and nr >= 0 and nr < lowest_sort)) then
|
||||
lowest_sort = nr
|
||||
d_id = k
|
||||
end
|
||||
end
|
||||
return d_id
|
||||
end
|
||||
|
||||
|
||||
-- count visits to this dialog - but *not* for generic dialogs as those are just linked and not
|
||||
|
||||
@ -24,6 +24,30 @@ yl_speak_up.replace_vars_in_text = function(text, dialog, pname)
|
||||
PLAYER_NAME = pname,
|
||||
}
|
||||
|
||||
-- only try to replace variables if there are variables inside the text
|
||||
if(string.find(text, "$VAR ")) then
|
||||
local varlist = yl_speak_up.get_quest_variables(dialog.npc_owner, true)
|
||||
for i,v in ipairs(varlist) do
|
||||
local v_name = string.sub(v, 3)
|
||||
-- only allow to replace unproblematic variable names
|
||||
if(not(string.find(v_name, "[^%w^%s^_^%-^%.]"))) then
|
||||
-- remove leading $ from $ var_owner_name var_name
|
||||
subs["VAR "..v_name] = yl_speak_up.get_quest_variable_value(dialog.npc_owner, v) or "- not set -"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- only replace properties if any properties are used inside the text
|
||||
if(string.find(text, "$PROP ")) then
|
||||
local properties = yl_speak_up.get_npc_properties(pname)
|
||||
for k,v in pairs(properties) do
|
||||
-- only allow to replace unproblematic property names
|
||||
if(not(string.find(k, "[^%w^%s^_^%-^%.]"))) then
|
||||
subs["PROP "..k] = v
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local day_time_name = "day"
|
||||
local day_time = minetest.get_timeofday()
|
||||
if(day_time < 0.5) then
|
||||
@ -41,7 +65,9 @@ yl_speak_up.replace_vars_in_text = function(text, dialog, pname)
|
||||
-- substitutions in it using substring captured by "()" in
|
||||
-- pattern. "[%a_]+" means one or more letter or underscore.
|
||||
-- If lookup returns nil, then no substitution is made.
|
||||
text = string.gsub(text or "", "%$([%a_]+)%$", subs)
|
||||
-- Note: Names of variables may contain alphanumeric signs, spaces, "_", "-" and ".".
|
||||
-- Variables with other names cannot be replaced.
|
||||
text = string.gsub(text or "", "%$([%w%s_%-%.]+)%$", subs)
|
||||
|
||||
return text
|
||||
end
|
||||
|
||||
@ -48,14 +48,7 @@ yl_speak_up.command_npc_talk = function(pname, param)
|
||||
-- implemented in fs_npc_list.lua:
|
||||
return yl_speak_up.command_npc_force_restore_npc(pname, rest)
|
||||
elseif(cmd and cmd == "privs") then
|
||||
-- TODO: make this available for npc_talk_admin?
|
||||
if(not(minetest.check_player_privs(pname, {privs = true}))) then
|
||||
minetest.chat_send_player(pname, "This command is used for managing "..
|
||||
"privs (like execute lua, teleportation, giving items...) for NPC. "..
|
||||
"You lack the \"privs\" priv required to "..
|
||||
"run this command.")
|
||||
return
|
||||
end
|
||||
-- the command now checks for player privs
|
||||
-- implemented in npc_privs.lua:
|
||||
return yl_speak_up.command_npc_talk_privs(pname, rest)
|
||||
end
|
||||
@ -68,9 +61,9 @@ yl_speak_up.command_npc_talk = function(pname, param)
|
||||
" version show human-readable version information\n"..
|
||||
" list shows a list of NPC that you can edit\n"..
|
||||
" debug debug a particular NPC\n"..
|
||||
" generic [requores npc_talk_admin priv] list, add or remove NPC as generic NPC\n"..
|
||||
" privs list, grant or revoke privs for your NPC\n"..
|
||||
" generic [requires npc_talk_admin priv] list, add or remove NPC as generic NPC\n"..
|
||||
" force_restore_npc [requires npc_talk_admin priv] restore NPC that got lost\n"..
|
||||
" privs [requires privs priv] list, grant or revoke privs for an NPC\n"..
|
||||
-- reload is fully handled in register_once
|
||||
"Note: /npc_talk_reload [requires privs priv] reloads the code of the mod without server "..
|
||||
"restart."..
|
||||
|
||||
29
config.lua
29
config.lua
@ -129,15 +129,32 @@ yl_speak_up.player_vars_min_save_time = 60
|
||||
------------------------------------------------------------------------------
|
||||
-- Privs - usually no need to change
|
||||
------------------------------------------------------------------------------
|
||||
-- * set the name of the priv that allows to add, edit and change preconditions, actions and
|
||||
-- effects listed in yl_speak_up.npc_priv_names in npc_privs.lua
|
||||
-- * this also allows the player to use the "/npc_talk privs" command to assign these privs
|
||||
-- to NPC
|
||||
-- * it does *NOT* include the "precon_exec_lua" and "effect_exec_lua" priv - just
|
||||
-- "effect_give_item", "effect_take_item" and "effect_move_player"
|
||||
-- NPC need npc privs in order to use some preconditions, actions and effects.
|
||||
--
|
||||
-- Plaers need privs in order to add, edit and change preconditions, actions and
|
||||
-- effects listed in yl_speak_up.npc_priv_names in npc_privs.lua.
|
||||
--
|
||||
-- The following player priv allows the player to use the "/npc_talk privs" command to
|
||||
-- grant/revoke/see these npc privs for *all NPC - not only for those the player can edit!
|
||||
-- * default: "npc_talk_admin" (but can also be set to "npc_master" or "privs" if you want)
|
||||
yl_speak_up.npc_privs_priv = "npc_talk_admin"
|
||||
|
||||
-- depending on your server, you might want to allow /npc_talk privs to be used by players
|
||||
-- who *don't* have the privs priv;
|
||||
-- WANRING: "precon_exec_lua" and "effect_exec_lua" are dangerous npc privs. Only players
|
||||
-- with the privs priv ought to be able to use those!
|
||||
-- The privs priv is the fallback if nothing is specified here.
|
||||
yl_speak_up.npc_priv_needs_player_priv = {}
|
||||
-- these privs allow to create items out of thin air - similar to the "give" priv
|
||||
yl_speak_up.npc_priv_needs_player_priv["effect_give_item"] = "give"
|
||||
yl_speak_up.npc_priv_needs_player_priv["effect_take_item"] = "give"
|
||||
-- on servers with travelnets and/or teleporters, you'd most likely want to allow every
|
||||
-- player to let NPC teleport players around; the "interact" priv covers that
|
||||
--yl_speak_up.npc_priv_needs_player_priv["effect_move_player"] = "interact"
|
||||
-- on YourLand, travel is very restricted; only those who can teleport players around can
|
||||
-- do it with NPC as well; for backward compatibility, this is set for all servers
|
||||
yl_speak_up.npc_priv_needs_player_priv["effect_move_player"] = "bring"
|
||||
|
||||
------------------------------------------------------------------------------
|
||||
-- Blacklists - not all blocks may be suitable for all effects NPC can do
|
||||
------------------------------------------------------------------------------
|
||||
|
||||
@ -177,9 +177,10 @@ yl_speak_up.execute_next_action = function(player, a_id, result_of_a_id, formnam
|
||||
yl_speak_up.speak_to[pname].d_id = this_action.a_on_failure
|
||||
yl_speak_up.speak_to[pname].o_id = nil
|
||||
yl_speak_up.speak_to[pname].a_id = nil
|
||||
yl_speak_up.show_fs(player, "talk", {n_id = n_id,
|
||||
d_id = this_action.a_on_failure,
|
||||
alternate_text = this_action.alternate_text})
|
||||
-- allow d_end, d_trade, d_got_item etc. to work as a_on_failure
|
||||
yl_speak_up.show_next_talk_fs_after_action(player, pname,
|
||||
this_action.a_on_failure, formname,
|
||||
dialog, d_id, n_id, this_action.alternate_text)
|
||||
return
|
||||
else
|
||||
local this_action = actions[ sorted_key_list[ nr ]]
|
||||
@ -220,6 +221,26 @@ yl_speak_up.execute_next_action = function(player, a_id, result_of_a_id, formnam
|
||||
local target_dialog = res.next_dialog
|
||||
yl_speak_up.speak_to[pname].o_id = nil
|
||||
yl_speak_up.speak_to[pname].a_id = nil
|
||||
|
||||
-- the function above returns a target dialog; show that to the player
|
||||
yl_speak_up.show_next_talk_fs_after_action(player, pname, target_dialog, formname,
|
||||
dialog, target_dialog, n_id, res.alternate_text)
|
||||
end
|
||||
|
||||
|
||||
-- after completing the action - either successfully or if it failed:
|
||||
yl_speak_up.show_next_talk_fs_after_action = function(player, pname, target_dialog, formname,
|
||||
dialog, d_id, n_id, alternate_text)
|
||||
-- allow to switch to d_trade from any dialog
|
||||
if(target_dialog and target_dialog == "d_trade") then
|
||||
yl_speak_up.show_fs(player, "trade_list")
|
||||
return
|
||||
end
|
||||
-- allow to switch to d_got_item from any dialog
|
||||
if(target_dialog and target_dialog == "d_got_item") then
|
||||
yl_speak_up.show_fs(player, "player_offers_item")
|
||||
return
|
||||
end
|
||||
-- end conversation
|
||||
if(target_dialog and target_dialog == "d_end") then
|
||||
yl_speak_up.stop_talking(pname)
|
||||
@ -229,18 +250,16 @@ yl_speak_up.execute_next_action = function(player, a_id, result_of_a_id, formnam
|
||||
end
|
||||
return
|
||||
end
|
||||
-- the special dialogs d_trade and d_got_item have no actions or effects - thus
|
||||
-- d_id cannot become d_trade or d_got_item
|
||||
if(not(target_dialog)
|
||||
or target_dialog == ""
|
||||
or not(dialog.n_dialogs[target_dialog])) then
|
||||
target_dialog = d_id
|
||||
end
|
||||
if(target_dialog and target_dialog == "d_trade") then
|
||||
yl_speak_up.show_fs(player, "trade_list")
|
||||
return
|
||||
end
|
||||
-- the function above returns a target dialog; show that to the player
|
||||
-- actually show the next dialog to the player
|
||||
yl_speak_up.show_fs(player, "talk", {n_id = n_id, d_id = target_dialog,
|
||||
alternate_text = res.alternate_text})
|
||||
alternate_text = alternate_text})
|
||||
end
|
||||
|
||||
|
||||
@ -293,6 +312,22 @@ yl_speak_up.get_action_by_player = function(player)
|
||||
end
|
||||
|
||||
|
||||
-- did the NPC try to give something to the player already - and the player didn't take it?
|
||||
-- then give that old item back to the NPC
|
||||
yl_speak_up.action_take_back_failed_npc_gives = function(trade_inv, npc_inv)
|
||||
if(not(trade_inv) or not(npc_inv)) then
|
||||
return
|
||||
end
|
||||
local last_stack = trade_inv:get_stack("npc_gives", 1)
|
||||
if(not(last_stack:is_empty())) then
|
||||
-- strip any metadata to avoid stacking problems
|
||||
npc_inv:add_item("npc_main", last_stack:get_name().." "..last_stack:get_count())
|
||||
-- clear the stack
|
||||
trade_inv:set_stack("npc_gives", 1, "")
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
-- Create the quest item by taking a raw item (i.e. a general piece of paper) out
|
||||
-- of the NPC's inventory, applying a description (if given) and quest id (if
|
||||
-- given); place the quest item in the trade inv of the player in the npc_gives slot.
|
||||
@ -312,6 +347,10 @@ yl_speak_up.action_quest_item_prepare = function(player)
|
||||
local stack = ItemStack(a.a_value)
|
||||
-- get the inventory of the NPC
|
||||
local npc_inv = minetest.get_inventory({type="detached", name="yl_speak_up_npc_"..tostring(n_id)})
|
||||
|
||||
local trade_inv = minetest.get_inventory({type="detached", name="yl_speak_up_player_"..pname})
|
||||
yl_speak_up.action_take_back_failed_npc_gives(trade_inv, npc_inv)
|
||||
|
||||
-- does the NPC have the item we are looking for?
|
||||
if(not(npc_inv:contains_item("npc_main", stack))) then
|
||||
local o_id = yl_speak_up.speak_to[pname].o_id
|
||||
@ -341,7 +380,6 @@ yl_speak_up.action_quest_item_prepare = function(player)
|
||||
-- put the stack in the npc_gives-slot of the trade inventory of the player
|
||||
-- (as that slot is managed by the NPC alone we don't have to worry about
|
||||
-- anything else in the slot)
|
||||
local trade_inv = minetest.get_inventory({type="detached", name="yl_speak_up_player_"..pname})
|
||||
-- actually put the stack in there
|
||||
trade_inv:set_stack("npc_gives", 1, new_stack)
|
||||
return true
|
||||
|
||||
@ -489,6 +489,8 @@ yl_speak_up.execute_effect = function(player, n_id, o_id, r)
|
||||
elseif(r.r_type == "function") then
|
||||
-- this can only be set and edited with the staff
|
||||
if(not(yl_speak_up.npc_has_priv(n_id, "effect_exec_lua", r.r_is_generic))) then
|
||||
yl_speak_up.debug_msg(player, n_id, o_id, tostring(r.r_id).." "..
|
||||
r.r_type..": The NPC does not have the \"effect_exec_lua\" priv.")
|
||||
return false
|
||||
end
|
||||
return yl_speak_up.eval_and_execute_function(player, r, "r_")
|
||||
@ -498,6 +500,8 @@ yl_speak_up.execute_effect = function(player, n_id, o_id, r)
|
||||
return false
|
||||
end
|
||||
if(not(yl_speak_up.npc_has_priv(n_id, "effect_give_item", r.r_is_generic))) then
|
||||
yl_speak_up.debug_msg(player, n_id, o_id, tostring(r.r_id).." "..
|
||||
r.r_type..": The NPC does not have the \"effect_give_item\" priv.")
|
||||
return false
|
||||
end
|
||||
local item = ItemStack(r.r_value)
|
||||
@ -518,6 +522,8 @@ yl_speak_up.execute_effect = function(player, n_id, o_id, r)
|
||||
-- this can only be set and edited with the staff
|
||||
elseif(r.r_type == "take_item") then
|
||||
if(not(yl_speak_up.npc_has_priv(n_id, "effect_take_item", r.r_is_generic))) then
|
||||
yl_speak_up.debug_msg(player, n_id, o_id, tostring(r.r_id).." "..
|
||||
r.r_type..": The NPC does not have the \"effect_take_item\" priv.")
|
||||
return false
|
||||
end
|
||||
if(not(r.r_value)) then
|
||||
@ -539,6 +545,8 @@ yl_speak_up.execute_effect = function(player, n_id, o_id, r)
|
||||
-- this can only be set and edited with the staff
|
||||
elseif(r.r_type == "move") then
|
||||
if(not(yl_speak_up.npc_has_priv(n_id, "effect_move_player", r.r_is_generic))) then
|
||||
yl_speak_up.debug_msg(player, n_id, o_id, tostring(r.r_id).." "..
|
||||
r.r_type..": The NPC does not have the \"effect_move_player\" priv.")
|
||||
return false
|
||||
end
|
||||
-- copeid/moved here from AliasAlreadyTakens code in functions.lua
|
||||
@ -791,6 +799,7 @@ yl_speak_up.execute_effect = function(player, n_id, o_id, r)
|
||||
return yl_speak_up.use_tool_on_block(r, "on_use", player, n_id, o_id)
|
||||
end
|
||||
-- even air can be punched - even if that is pretty pointless
|
||||
-- TODO: some blocks may define their own functions and care for what the player wields (i.e. cheese mod)
|
||||
minetest.punch_node(r.r_pos, nil)
|
||||
return true
|
||||
-- "Right-click the block.", -- 5
|
||||
|
||||
@ -13,10 +13,15 @@ local use_d_name = true
|
||||
-- in order to be able to deal with multiple NPC in ink, we use the NPC id n_id
|
||||
-- plus the dialog id d_id as a name prefix; o_id, a_id and r_id are appended
|
||||
-- as needed
|
||||
yl_speak_up.export_to_ink.print_knot_name = function(lines, knot_name)
|
||||
yl_speak_up.export_to_ink.print_knot_name = function(lines, knot_name, use_prefix, dialog_names)
|
||||
if(knot_name and dialog_names[knot_name]) then
|
||||
knot_name = dialog_names[knot_name]
|
||||
end
|
||||
knot_name = use_prefix..tostring(knot_name or "ERROR")
|
||||
table.insert(lines, "\n\n=== ")
|
||||
table.insert(lines, tostring(knot_name or "ERROR"))
|
||||
table.insert(lines, knot_name)
|
||||
table.insert(lines, " ===")
|
||||
return knot_name
|
||||
end
|
||||
|
||||
|
||||
@ -55,7 +60,7 @@ end
|
||||
-- choices are a bit complicated as they may contain alternate_text that is to be
|
||||
-- displayed instead (in yl_speak_up) and before (in ink) shwoing the target dialog text;
|
||||
-- also, the divert_to target dialog may need to be rewritten
|
||||
yl_speak_up.export_to_ink.print_choice = function(lines, choice_text, n_id, start_dialog,
|
||||
yl_speak_up.export_to_ink.print_choice = function(lines, choice_text, use_prefix, start_dialog,
|
||||
alternate_text, divert_to, only_once, label,
|
||||
precondition_list, effect_list,
|
||||
dialog_names)
|
||||
@ -111,19 +116,18 @@ yl_speak_up.export_to_ink.print_choice = function(lines, choice_text, n_id, star
|
||||
table.insert(lines, "\n")
|
||||
end
|
||||
-- actually go to the dialog this option leads to
|
||||
table.insert(lines, " -> ")
|
||||
table.insert(lines, " -> "..use_prefix)
|
||||
if(not(start_dialog) or start_dialog == "") then
|
||||
start_dialog = "d_1"
|
||||
end
|
||||
if(not(divert_to) or divert_to == "") then
|
||||
-- go back to the start dialog (the start dialog may have been changed)
|
||||
divert_to = tostring(start_dialog)
|
||||
elseif(divert_to == "d_end" or divert_to == tostring(n_id).."_d_end") then
|
||||
elseif(divert_to == "d_end" or divert_to == use_prefix.."d_end") then
|
||||
-- go back to choosing between talking to NPC and end
|
||||
divert_to = tostring(n_id).."_main"
|
||||
elseif(string.sub(divert_to, 1, 2) ~= "n_") then
|
||||
-- make sure it is prefixed with the n_id
|
||||
divert_to = tostring(n_id).."_"..tostring(divert_to)
|
||||
divert_to = "d_end"
|
||||
else
|
||||
divert_to = tostring(divert_to)
|
||||
end
|
||||
if(dialog_names and dialog_names[divert_to]) then
|
||||
divert_to = dialog_names[divert_to]
|
||||
@ -134,12 +138,8 @@ end
|
||||
|
||||
-- this prints the dialog as a knot - but without choices (those are added to the lines table later)
|
||||
-- d: dialog
|
||||
yl_speak_up.export_to_ink.print_dialog_knot = function(lines, n_id, d_id, d)
|
||||
local knot_name = tostring(n_id).."_"..tostring(d_id)
|
||||
if(use_d_name) then
|
||||
knot_name = (d.d_name or knot_name)
|
||||
end
|
||||
ink_export.print_knot_name(lines, knot_name)
|
||||
yl_speak_up.export_to_ink.print_dialog_knot = function(lines, use_prefix, d_id, d, dialog_names)
|
||||
local knot_name = ink_export.print_knot_name(lines, d_id, use_prefix, dialog_names)
|
||||
|
||||
-- many characters at the start of a line have a special meaning;
|
||||
-- hopefully they will not be obstrusive later on;
|
||||
@ -159,28 +159,30 @@ end
|
||||
-- a knot for each action
|
||||
-- Parameter:
|
||||
-- a action
|
||||
yl_speak_up.export_to_ink.print_action_knot = function(lines, n_id, d_id, o_id, start_dialog,
|
||||
a, alternate_text_on_success, next_target, dialog_names)
|
||||
local knot_name = tostring(n_id).."_"..tostring(d_id).."_"..tostring(o_id).."_"..tostring(a.a_id)
|
||||
ink_export.print_knot_name(lines, knot_name)
|
||||
yl_speak_up.export_to_ink.print_action_knot = function(lines, use_prefix, d_id, o_id, start_dialog,
|
||||
a, alternate_text_on_success, next_target, dialog_names,
|
||||
e_list_on_success)
|
||||
local action_prefix = use_prefix.."action_"..tostring(a.a_id).."_"..tostring(o_id).."_"
|
||||
local knot_name = ink_export.print_knot_name(lines, d_id, action_prefix, dialog_names)
|
||||
|
||||
table.insert(lines, "\n:action: ")
|
||||
table.insert(lines, a.a_id)
|
||||
table.insert(lines, " ")
|
||||
table.insert(lines, yl_speak_up.show_action(a))
|
||||
table.insert(lines, "A: "..minetest.serialize(a or {})..".")
|
||||
|
||||
ink_export.print_choice(lines, "Action was successful", n_id, start_dialog,
|
||||
ink_export.print_choice(lines, "Action was successful", use_prefix, start_dialog,
|
||||
alternate_text_on_success, next_target, false, nil,
|
||||
nil, nil, dialog_names)
|
||||
nil, e_list_on_success, dialog_names)
|
||||
|
||||
ink_export.print_choice(lines, "Action failed", n_id, start_dialog,
|
||||
ink_export.print_choice(lines, "Action failed", use_prefix, start_dialog,
|
||||
a.alternate_text, a.a_on_failure, false, nil,
|
||||
nil, nil, dialog_names)
|
||||
|
||||
ink_export.print_choice(lines, "Back", n_id, start_dialog,
|
||||
nil, tostring(n_id).."_"..tostring(d_id), false, nil,
|
||||
ink_export.print_choice(lines, "Back", use_prefix, start_dialog,
|
||||
nil, tostring(d_id), false, nil,
|
||||
nil, nil, dialog_names)
|
||||
return knot_name
|
||||
return string.sub(knot_name, string.len(use_prefix)+1)
|
||||
end
|
||||
|
||||
|
||||
@ -190,11 +192,11 @@ end
|
||||
-- Parameter:
|
||||
-- r effect/result
|
||||
-- r_prev previous effect
|
||||
yl_speak_up.export_to_ink.print_effect_knot = function(lines, n_id, d_id, o_id, start_dialog,
|
||||
yl_speak_up.export_to_ink.print_effect_knot = function(lines, use_prefix, d_id, o_id, start_dialog,
|
||||
r, r_prev, alternate_text_on_success, next_target,
|
||||
dialog_names)
|
||||
local knot_name = tostring(n_id).."_"..tostring(d_id).."_"..tostring(o_id).."_"..tostring(r.r_id)
|
||||
ink_export.print_knot_name(lines, knot_name)
|
||||
local effect_prefix = use_prefix.."effect_"..tostring(r.r_id).."_"..tostring(o_id).."_"
|
||||
local knot_name = ink_export.print_knot_name(lines, d_id, effect_prefix, dialog_names)
|
||||
|
||||
table.insert(lines, "\n:effect: ")
|
||||
table.insert(lines, r.r_id)
|
||||
@ -208,20 +210,19 @@ yl_speak_up.export_to_ink.print_effect_knot = function(lines, n_id, d_id, o_id,
|
||||
-- show text of the *previous effect* - because that is the one which may have failed:
|
||||
table.insert(lines, yl_speak_up.show_effect(r_prev))
|
||||
|
||||
ink_export.print_choice(lines, "Effect was successful", n_id, start_dialog,
|
||||
ink_export.print_choice(lines, "Effect was successful", use_prefix, start_dialog,
|
||||
alternate_text_on_success, next_target, false, nil,
|
||||
nil, nil, dialog_names)
|
||||
|
||||
ink_export.print_choice(lines, "Effect failed", n_id, start_dialog,
|
||||
ink_export.print_choice(lines, "Effect failed", use_prefix, start_dialog,
|
||||
r.alternate_text, r.r_value, false, nil,
|
||||
nil, nil, dialog_names)
|
||||
|
||||
return knot_name
|
||||
return string.sub(knot_name, string.len(use_prefix)+1)
|
||||
end
|
||||
|
||||
|
||||
-- which variables are used by this NPC?
|
||||
yl_speak_up.export_to_ink.print_variables_used = function(lines, dialog, n_id, pname)
|
||||
yl_speak_up.export_to_ink.print_variables_used = function(lines, dialog)
|
||||
if(not(dialog) or not(dialog.n_dialogs)) then
|
||||
return
|
||||
end
|
||||
@ -306,7 +307,7 @@ local var_with_operator = function(liste, var_name, op, var_cmp_value, vars_used
|
||||
-- "quest_step_done", "quest_step_not_done"
|
||||
end
|
||||
|
||||
yl_speak_up.export_to_ink.translate_precondition_list = function(dialog, preconditions, vars_used, n_id,
|
||||
yl_speak_up.export_to_ink.translate_precondition_list = function(dialog, preconditions, vars_used, use_prefix,
|
||||
dialog_names)
|
||||
-- collect preconditions that may work in ink
|
||||
local liste = {}
|
||||
@ -320,9 +321,9 @@ yl_speak_up.export_to_ink.translate_precondition_list = function(dialog, precond
|
||||
var_with_operator(liste, p.p_value, p.p_operator, p.p_var_cmp_value, vars_used)
|
||||
elseif(p and p.p_type and p.p_type == "evaluate" and p.p_value == "counted_visits_to_option") then
|
||||
-- simulate the visit counter that ink has in yl_speak_up
|
||||
local tmp_var_name = n_id.."_"..p.p_param1
|
||||
local tmp_var_name = use_prefix..p.p_param1
|
||||
if(dialog_names[tmp_var_name]) then
|
||||
tmp_var_name = dialog_names[tmp_var_name].."."..tostring(p.p_param2)
|
||||
tmp_var_name = use_prefix..dialog_names[tmp_var_name].."."..tostring(p.p_param2)
|
||||
else
|
||||
tmp_var_name = tmp_var_name.. "_"..tostring(p.p_param2)
|
||||
end
|
||||
@ -360,7 +361,7 @@ local set_var_to_value = function(liste, var_name_full, op, val, vars_used)
|
||||
end
|
||||
end
|
||||
|
||||
yl_speak_up.export_to_ink.translate_effect_list = function(dialog, effects, vars_used, n_id)
|
||||
yl_speak_up.export_to_ink.translate_effect_list = function(dialog, effects, vars_used)
|
||||
-- collect effects that may work in ink
|
||||
local liste = {}
|
||||
-- variables may be set in effects
|
||||
@ -377,7 +378,8 @@ yl_speak_up.export_to_ink.translate_effect_list = function(dialog, effects, vars
|
||||
end
|
||||
|
||||
|
||||
yl_speak_up.export_to_ink_language = function(dialog, n_id)
|
||||
-- Note: use_prefix ought to be tostring(n_id).."_" or ""
|
||||
yl_speak_up.export_to_ink_language = function(dialog, use_prefix)
|
||||
local start_dialog = yl_speak_up.get_start_dialog_id(dialog)
|
||||
if(not(start_dialog)) then
|
||||
start_dialog = "d_1"
|
||||
@ -388,25 +390,47 @@ yl_speak_up.export_to_ink_language = function(dialog, n_id)
|
||||
and dialog.n_dialogs[start_dialog].d_name) then
|
||||
start_dialog = dialog.n_dialogs[start_dialog].d_name
|
||||
else
|
||||
start_dialog = tostring(n_id).."_"..tostring(start_dialog)
|
||||
start_dialog = tostring(start_dialog)
|
||||
end
|
||||
|
||||
local main = tostring(n_id).."_main"
|
||||
local tmp = {"-> ", main,
|
||||
"\n=== ", main, " ===",
|
||||
-- prefix all dialog names with this;
|
||||
-- advantage: several NPC dialog exports can be combined into one inc game
|
||||
-- where the player can talk to diffrent NPC (which can have the
|
||||
-- same dialog names without conflict thanks to the prefix)
|
||||
-- use_prefix = tostring(n_id).."_"
|
||||
if(not(use_prefix)) then
|
||||
use_prefix = ""
|
||||
end
|
||||
|
||||
-- go to the main loop whenever the player ends the conversation with the NPC;
|
||||
-- this allows to create an additional dialog in INK where the player can then
|
||||
-- decide to talk to multiple NPC - or to continue his conversation with the
|
||||
-- same NPC
|
||||
local main_loop = use_prefix.."d_end"
|
||||
local tmp = {"-> ", main_loop,
|
||||
"\n=== ", main_loop, " ===",
|
||||
"\nWhat do you wish to do?",
|
||||
"\n+ Talk to ", tostring(dialog.n_npc), " -> ", tostring(start_dialog),
|
||||
"\n+ Talk to ", tostring(dialog.n_npc or prefix or "-unknown-"), " -> ", use_prefix..tostring(start_dialog),
|
||||
"\n+ End -> END"}
|
||||
|
||||
local vars_used = ink_export.print_variables_used(tmp, dialog, n_id, pname)
|
||||
local vars_used = ink_export.print_variables_used(tmp, dialog)
|
||||
|
||||
local sorted_d_list = yl_speak_up.get_dialog_list_for_export(dialog)
|
||||
-- d_got_item may contain alternate texts - so it is of intrest here
|
||||
-- (also links to other dialogs)
|
||||
if(dialog.n_dialogs["d_got_item"]) then
|
||||
table.insert(sorted_d_list, "d_got_item")
|
||||
end
|
||||
-- maybe not that useful to set up this one in inK; add it for completeness
|
||||
if(dialog.n_dialogs["d_trade"]) then
|
||||
table.insert(sorted_d_list, "d_trade")
|
||||
end
|
||||
|
||||
-- make use of dialog names if wanted
|
||||
local dialog_names = {}
|
||||
for i, d_id in ipairs(sorted_d_list) do
|
||||
if(use_d_name) then
|
||||
local n = tostring(n_id).."_"..tostring(d_id)
|
||||
local n = tostring(d_id)
|
||||
local d = dialog.n_dialogs[d_id]
|
||||
dialog_names[n] = (d.d_name or n)
|
||||
end
|
||||
@ -417,7 +441,7 @@ yl_speak_up.export_to_ink_language = function(dialog, n_id)
|
||||
local tmp2 = {}
|
||||
local d = dialog.n_dialogs[d_id]
|
||||
-- print the dialog knot, but without choices (those we add in the following loop)
|
||||
local this_knot_name = ink_export.print_dialog_knot(tmp, n_id, d_id, d)
|
||||
local this_knot_name = ink_export.print_dialog_knot(tmp, use_prefix, d_id, d, dialog_names)
|
||||
|
||||
-- iterate over all options
|
||||
local sorted_o_list = yl_speak_up.get_sorted_options(dialog.n_dialogs[d_id].d_options or {}, "o_sort")
|
||||
@ -434,7 +458,7 @@ yl_speak_up.export_to_ink_language = function(dialog, n_id)
|
||||
for k, r_id in ipairs(sorted_e_list) do
|
||||
local r = o_data.o_results[r_id]
|
||||
if(r and r.r_type and r.r_type == "dialog") then
|
||||
target_dialog = tostring(n_id).."_"..tostring(r.r_value)
|
||||
target_dialog = tostring(r.r_value)
|
||||
alternate_text_on_success = r.alternate_text or ""
|
||||
end
|
||||
end
|
||||
@ -455,7 +479,7 @@ yl_speak_up.export_to_ink_language = function(dialog, n_id)
|
||||
-- whatever dialog comes previously - the dialog, an action, or
|
||||
-- another on_failure dialog - needs to lead to this dialog
|
||||
target_dialog = ink_export.print_effect_knot(tmp2,
|
||||
n_id, d_id, o_id, start_dialog,
|
||||
use_prefix, d_id, o_id, start_dialog,
|
||||
r, r_prev,
|
||||
alternate_text_on_success, target_dialog,
|
||||
dialog_names)
|
||||
@ -470,37 +494,68 @@ yl_speak_up.export_to_ink_language = function(dialog, n_id)
|
||||
alternate_text_on_success,
|
||||
sorted_e_list, o_data.o_results, 1)
|
||||
|
||||
-- if it is an action knot then the effects have to go to the action knot
|
||||
local e_list = ink_export.translate_effect_list(dialog, o_data.o_results,
|
||||
vars_used)
|
||||
-- iterate backwards through the actions (though usually only one is supported)
|
||||
for k = #sorted_a_list, 1, -1 do
|
||||
local a_id = sorted_a_list[k]
|
||||
local a = o_data.actions[a_id]
|
||||
|
||||
target_dialog = ink_export.print_action_knot(tmp2,
|
||||
n_id, d_id, o_id, start_dialog,
|
||||
use_prefix, d_id, o_id, start_dialog,
|
||||
a,
|
||||
alternate_text_on_success, target_dialog, dialog_names)
|
||||
alternate_text_on_success, target_dialog, dialog_names,
|
||||
e_list)
|
||||
-- has been dealt with
|
||||
alternate_text_on_success = ""
|
||||
end
|
||||
|
||||
-- which preconditions can be translated to ink?
|
||||
local p_list = ink_export.translate_precondition_list(dialog, o_data.o_prerequisites,
|
||||
vars_used, n_id, dialog_names)
|
||||
local e_list = ink_export.translate_effect_list(dialog, o_data.o_results,
|
||||
vars_used, n_id)
|
||||
vars_used, use_prefix, dialog_names)
|
||||
|
||||
-- what remains is to print the option/choice itself
|
||||
local o_text = o_data.o_text_when_prerequisites_met
|
||||
local o_prefix = ""
|
||||
if(d.o_random) then
|
||||
o_text = "[One of these options is randomly selected]"
|
||||
o_prefix = "randomly_"
|
||||
elseif(o_data.o_autoanswer) then
|
||||
o_text = "[Automaticly selected if preconditions are met]"
|
||||
o_prefix = "automaticly_"
|
||||
end
|
||||
-- if the target is an action knot: do not print the effect list as that belongs
|
||||
-- to the action knot!
|
||||
if(#sorted_a_list > 0) then
|
||||
e_list = {}
|
||||
end
|
||||
ink_export.print_choice(tmp,
|
||||
-- TODO: deal with when_prerequisites_not_met
|
||||
o_data.o_text_when_prerequisites_met, n_id, start_dialog,
|
||||
o_text, use_prefix, start_dialog,
|
||||
alternate_text_on_success, target_dialog,
|
||||
o_data.o_visit_only_once,
|
||||
o_id, p_list, e_list, dialog_names)
|
||||
o_data.o_visit_only_once, -- print + (often) or * (only once)
|
||||
o_prefix..o_id, p_list, e_list, dialog_names)
|
||||
-- deal with o_grey_when_prerequisites_not_met (grey out this answer)
|
||||
if( o_data.o_text_when_prerequisites_not_met
|
||||
and o_data.o_text_when_prerequisites_not_met ~= ""
|
||||
and o_data.o_grey_when_prerequisites_not_met
|
||||
and o_data.o_grey_when_prerequisites_not_met == "true") then
|
||||
o_text = o_data.o_text_when_prerequisites_not_met
|
||||
-- this option cannot be selected - so choose d_end as target dialog
|
||||
ink_export.print_choice(tmp,
|
||||
o_text, use_prefix, start_dialog,
|
||||
alternate_text_on_success, "d_end",
|
||||
o_data.o_visit_only_once, -- print + (often) or * (only once)
|
||||
"grey_out_"..o_id, p_list, e_list, dialog_names)
|
||||
end
|
||||
-- Note: Showing an alternate text if the preconditions are not met is not
|
||||
-- covered here. It makes little sense for the NPC as the option appears
|
||||
-- but cannot be clicked. It exists for backward compatibility of old NPC
|
||||
-- on the Your Land server.
|
||||
end -- dealt with the option
|
||||
table.insert(tmp, "\n")
|
||||
-- add way to end talking to the NPC
|
||||
ink_export.print_choice(tmp, "Farewell!", n_id, start_dialog,
|
||||
nil, tostring(n_id).."_main", false, nil, dialog_names)
|
||||
ink_export.print_choice(tmp, "Farewell!", use_prefix, start_dialog,
|
||||
nil, "d_end", false, nil, dialog_names)
|
||||
|
||||
-- add the knots for actions and effects for this dialog and all its options:
|
||||
for _, line in ipairs(tmp2) do
|
||||
|
||||
@ -33,6 +33,10 @@ yl_speak_up.input_fs_action_npc_gives = function(player, formname, fields)
|
||||
-- the npc_gives slot does not accept input - so we don't have to check for any misplaced items
|
||||
-- but if the player aborts, give the item back to the NPC
|
||||
if(fields.back_to_talk) then
|
||||
-- actually take the item back into the NPC's inventory
|
||||
local n_id = yl_speak_up.speak_to[pname].n_id
|
||||
local npc_inv = minetest.get_inventory({type="detached", name="yl_speak_up_npc_"..tostring(n_id)})
|
||||
yl_speak_up.action_take_back_failed_npc_gives(trade_inv, npc_inv)
|
||||
-- strip the quest item info from the stack (so that it may stack again)
|
||||
-- and give that (hopefully) stackable stack back to the NPC
|
||||
yl_speak_up.action_quest_item_take_back(player)
|
||||
|
||||
@ -1,23 +1,3 @@
|
||||
-- helper function that is also used by export_to_ink.lua
|
||||
-- returns a sorted dialog list without special or generic dialogs
|
||||
yl_speak_up.get_dialog_list_for_export = function(dialog)
|
||||
local liste = {}
|
||||
if(not(dialog) or not(dialog.n_dialogs)) then
|
||||
return liste
|
||||
end
|
||||
-- sort the list of dialogs by d_id
|
||||
local liste_sorted = yl_speak_up.sort_keys(dialog.n_dialogs or {}, true)
|
||||
for _, d_id in ipairs(liste_sorted) do
|
||||
-- only normal dialogs - no d_trade, d_got_item, d_dynamic etc;
|
||||
if(not(yl_speak_up.is_special_dialog(d_id))
|
||||
-- also no generic dialogs (they do not come from this NPC)
|
||||
and not(dialog.n_dialogs[d_id].is_generic)) then
|
||||
table.insert(liste, d_id)
|
||||
end
|
||||
end
|
||||
return liste
|
||||
end
|
||||
|
||||
|
||||
yl_speak_up.export_to_simple_dialogs_language = function(dialog, n_id)
|
||||
|
||||
@ -38,7 +18,7 @@ yl_speak_up.export_to_simple_dialogs_language = function(dialog, n_id)
|
||||
table.insert(tmp, "\n")
|
||||
for o_id, o_data in pairs(dialog.n_dialogs[d_id].d_options or {}) do
|
||||
local target_dialog = nil
|
||||
for r_id, r_data in pairs(o_data.o_results) do
|
||||
for r_id, r_data in pairs(o_data.o_results or {}) do
|
||||
if(r_data.r_type and r_data.r_type == "dialog") then
|
||||
target_dialog = r_data.r_value
|
||||
end
|
||||
@ -68,8 +48,52 @@ yl_speak_up.input_export = function(player, formname, fields)
|
||||
return yl_speak_up.show_fs(player, "export", "show_simple_dialogs")
|
||||
elseif(fields and (fields.import or fields.back_from_error_msg)) then
|
||||
return yl_speak_up.show_fs(player, "export", "import")
|
||||
elseif(fields and fields.really_import and fields.new_dialog_input
|
||||
and string.sub(fields.new_dialog_input, 1, 3) == "-> ") then
|
||||
local pname = player:get_player_name()
|
||||
if(not(pname) or not(yl_speak_up.speak_to[pname])) then
|
||||
return
|
||||
end
|
||||
local n_id = yl_speak_up.speak_to[pname].n_id
|
||||
-- can the player edit this npc?
|
||||
if(not(yl_speak_up.may_edit_npc(player, n_id))) then
|
||||
return yl_speak_up.show_fs(player, "msg", {
|
||||
input_to = "yl_speak_up:export",
|
||||
formspec = yl_speak_up.build_fs_quest_edit_error(
|
||||
"You do not own this NPC and are not allowed to edit it!",
|
||||
"back_from_error_msg")})
|
||||
end
|
||||
-- import in ink format
|
||||
local dialog = yl_speak_up.speak_to[pname].dialog
|
||||
local log = {}
|
||||
local log_level = 1
|
||||
yl_speak_up.parse_ink.import_from_ink(dialog, fields.new_dialog_input, log_level, log)
|
||||
-- save the changed dialog
|
||||
yl_speak_up.save_dialog(n_id, dialog)
|
||||
for i_, t_ in ipairs(log) do
|
||||
minetest.chat_send_player(pname, t_)
|
||||
end
|
||||
-- log the change
|
||||
return yl_speak_up.show_fs(player, "msg", {
|
||||
input_to = "yl_speak_up:export",
|
||||
formspec = "size[10,3]"..
|
||||
"label[0.5,1.0;Partially imported dialog data in ink format "..
|
||||
" successfully.]"..
|
||||
"button[3.5,2.0;2,0.9;back_from_error_msg;Back]"
|
||||
})
|
||||
|
||||
elseif(fields and fields.really_import and fields.new_dialog_input) then
|
||||
-- importing requires the "privs" priv
|
||||
-- can that possibly be json format?
|
||||
if(not(string.sub(fields.new_dialog_input, 1, 1) == "{")) then
|
||||
return yl_speak_up.show_fs(player, "msg", {
|
||||
input_to = "yl_speak_up:export",
|
||||
formspec = yl_speak_up.build_fs_quest_edit_error(
|
||||
"This does not seem to be in .json format. Please make sure "..
|
||||
"your import starts with a \"{\"!",
|
||||
"back_from_error_msg")})
|
||||
end
|
||||
-- importing in .json format requires the "privs" priv
|
||||
-- and it imports more information like npc name
|
||||
if(not(minetest.check_player_privs(player, {privs=true}))) then
|
||||
return yl_speak_up.show_fs(player, "msg", {
|
||||
input_to = "yl_speak_up:export",
|
||||
@ -147,7 +171,7 @@ yl_speak_up.input_export = function(player, formname, fields)
|
||||
-- save it
|
||||
yl_speak_up.save_dialog(n_id, new_dialog)
|
||||
-- log the change
|
||||
yl_speak_up.log_change(pname, n_id, "Imported new dialog.")
|
||||
yl_speak_up.log_change(pname, n_id, "Imported new dialog in .json format (complete).")
|
||||
return yl_speak_up.show_fs(player, "msg", {
|
||||
input_to = "yl_speak_up:export",
|
||||
formspec = "size[10,3]"..
|
||||
@ -230,7 +254,7 @@ yl_speak_up.get_fs_export = function(player, param)
|
||||
-- TODO
|
||||
explanation = "This is the format used by the \"Ink\" scripting language. "..
|
||||
"TODO: The export is not complete yet."
|
||||
content = yl_speak_up.export_to_ink_language(dialog, n_id)
|
||||
content = yl_speak_up.export_to_ink_language(dialog, tostring(n_id).."_")
|
||||
elseif(param and param == "show_simple_dialogs") then
|
||||
b3 = "label[9.8,17.6;Simple dialogs format]"
|
||||
explanation = "This is the format used by the \"simple_dialogs\" mod. "..
|
||||
@ -250,7 +274,7 @@ yl_speak_up.get_fs_export = function(player, param)
|
||||
end
|
||||
return table.concat({"size[20,20]label[4,0.5;Export of NPC ",
|
||||
minetest.formspec_escape(n_id or "- ? -"),
|
||||
"dialog data in .json format]",
|
||||
" dialog data in .json format]",
|
||||
"button[17.8,0.2;2.0,0.9;back;Back]",
|
||||
"button[15.4,0.2;2.0,0.9;import;Import]",
|
||||
"tooltip[import;WARNING: This is highly experimental and requires the \"privs\" priv.\n"..
|
||||
|
||||
@ -213,10 +213,9 @@ yl_speak_up.get_fs_initial_config = function(player, n_id, d_id, is_initial_conf
|
||||
"Export: Show the dialog in .json format which you can"..
|
||||
"\n\tcopy and store on your computer.]",
|
||||
-- name of the npc
|
||||
"checkbox[2.2,0.9;show_nametag;;",
|
||||
"checkbox[2.2,0.9;show_nametag;Show nametag;",
|
||||
tostring(tmp_show_nametag),
|
||||
"]",
|
||||
"label[2.7,0.9;Show nametag]",
|
||||
"label[0.2,1.65;Name:]",
|
||||
"field[2.2,1.2;4,0.9;n_npc;;",
|
||||
minetest.formspec_escape(tmp_name),
|
||||
|
||||
@ -447,7 +447,8 @@ yl_speak_up.get_fs_talkdialog = function(player, n_id, d_id, alternate_text, rec
|
||||
elseif(d_id and d_id ~= "d_generic_start_dialog" and yl_speak_up.speak_to[pname].d_id ~= nil) then
|
||||
c_d_id = yl_speak_up.speak_to[pname].d_id
|
||||
active_dialog = dialog.n_dialogs[c_d_id]
|
||||
elseif dialog.n_dialogs ~= nil then
|
||||
-- do this only if the dialog is already configured/created_at:
|
||||
elseif dialog.n_dialogs ~= nil and dialog.created_at then
|
||||
-- Find the dialog with d_sort = 0
|
||||
c_d_id = yl_speak_up.get_start_dialog_id(dialog)
|
||||
if(c_d_id) then
|
||||
|
||||
802
functions.lua
802
functions.lua
@ -1,802 +0,0 @@
|
||||
--###
|
||||
-- Init
|
||||
--###
|
||||
|
||||
-- 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.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" or any_id == "d_dynamic") 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)
|
||||
-- never store d_dynamic dialogs
|
||||
if(dialog.n_dialogs and dialog.n_dialogs["d_dynamic"]) then
|
||||
dialog.n_dialogs["d_dynamic"] = nil
|
||||
end
|
||||
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)
|
||||
|
||||
-- note: add_generic_dialogs will also add an empty d_dynamic dialog
|
||||
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
|
||||
-- would be too difficult to add an exception for edit_mode here; thus, we do it directly here:
|
||||
if(yl_speak_up.npc_was_changed
|
||||
and 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
|
||||
|
||||
|
||||
-- returns true if someone is speaking to the NPC
|
||||
yl_speak_up.npc_is_in_conversation = function(n_id)
|
||||
for name, data in pairs(yl_speak_up.speak_to) do
|
||||
if(data and data.n_id and data.n_id == n_id) then
|
||||
return true
|
||||
end
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
|
||||
-- returns a list of players that are in conversation with this NPC
|
||||
yl_speak_up.npc_is_in_conversation_with = function(n_id)
|
||||
local liste = {}
|
||||
for name, data in pairs(yl_speak_up.speak_to) do
|
||||
if(data and data.n_id and data.n_id == n_id) then
|
||||
table.insert(liste, name)
|
||||
end
|
||||
end
|
||||
return liste
|
||||
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
|
||||
|
||||
|
||||
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
|
||||
-- this makes it a bit easier to access some values later on:
|
||||
yl_speak_up.speak_to[pname]._self = self
|
||||
-- Load the dialog and see what we can do with it
|
||||
-- this inculdes generic dialog parts;
|
||||
yl_speak_up.speak_to[pname].dialog = yl_speak_up.load_dialog(n_id, clicker)
|
||||
|
||||
-- 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
|
||||
|
||||
local dialog = yl_speak_up.speak_to[pname].dialog
|
||||
if(not(dialog.trades)) then
|
||||
dialog.trades = {}
|
||||
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), false, dialog)
|
||||
|
||||
|
||||
-- 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
|
||||
|
||||
-- checks if dialog exists
|
||||
yl_speak_up.check_if_dialog_exists = function(dialog, d_id)
|
||||
return (dialog and d_id
|
||||
and dialog.n_dialogs
|
||||
and dialog.n_dialogs[d_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
|
||||
|
||||
|
||||
yl_speak_up.is_special_dialog = function(d_id)
|
||||
if(not(d_id)) then
|
||||
return false
|
||||
end
|
||||
return (d_id == "d_trade" or d_id == "d_got_item" or d_id == "d_dynamic" or d_id == "d_end")
|
||||
end
|
||||
|
||||
|
||||
yl_speak_up.d_name_to_d_id = function(dialog, d_name)
|
||||
if(not(dialog) or not(dialog.n_dialogs) or not(d_name) or d_name == "") then
|
||||
return nil
|
||||
end
|
||||
-- it is already the ID of an existing dialog
|
||||
if(dialog.n_dialogs[d_name]) then
|
||||
return d_name
|
||||
end
|
||||
-- search all dialogs for one with a fitting d_name
|
||||
for k,v in pairs(dialog.n_dialogs) do
|
||||
if(v and v.d_name and v.d_name == d_name) then
|
||||
return k
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
-- get the name of a dialog (reverse of above)
|
||||
yl_speak_up.d_id_to_d_name = function(dialog, d_id)
|
||||
if(not(dialog) or not(dialog.n_dialogs) or not(d_id) or d_id == ""
|
||||
or not(dialog.n_dialogs[d_id])
|
||||
or not(dialog.n_dialogs[d_id].d_name)
|
||||
or dialog.n_dialogs[d_id].d_name == "") then
|
||||
return d_id
|
||||
end
|
||||
return dialog.n_dialogs[d_id].d_name
|
||||
end
|
||||
|
||||
|
||||
yl_speak_up.get_sorted_dialog_name_list = function(dialog)
|
||||
local liste = {}
|
||||
if(dialog and dialog.n_dialogs) then
|
||||
for k, v in pairs(dialog.n_dialogs) do
|
||||
-- this will be used for dropdown lists - so we use formspec_escape
|
||||
table.insert(liste, minetest.formspec_escape(v.d_name or k or "?"))
|
||||
end
|
||||
-- sort alphabethicly
|
||||
table.sort(liste)
|
||||
end
|
||||
return liste
|
||||
end
|
||||
|
||||
|
||||
-- how many own (not special, not generic) dialogs does the NPC have?
|
||||
yl_speak_up.count_dialogs = function(dialog)
|
||||
local count = 0
|
||||
if(not(dialog) or not(dialog.n_dialogs)) then
|
||||
return 0
|
||||
end
|
||||
for d_id, v in pairs(dialog.n_dialogs) do
|
||||
if(d_id
|
||||
and not(yl_speak_up.is_special_dialog(d_id))
|
||||
and not(dialog.n_dialogs[d_id].is_generic)) then
|
||||
count = count + 1
|
||||
end
|
||||
end
|
||||
return count
|
||||
end
|
||||
858
functions_dialogs.lua
Normal file
858
functions_dialogs.lua
Normal file
@ -0,0 +1,858 @@
|
||||
--
|
||||
-- These functions here access and manipulate the "dialogs" data structure.
|
||||
-- It is loaded for each player whenever the player talks to an NPC. Each
|
||||
-- talking player gets *a copy* of that data structure.
|
||||
--
|
||||
-- As this mod is about this "dialogs" data structure and its editing, this
|
||||
-- isn't the only place in this mod where the data structure is accessed
|
||||
-- and/or manipulated. This here just contains some common functions.
|
||||
--
|
||||
--###
|
||||
-- Helpers
|
||||
--###
|
||||
|
||||
yl_speak_up.string_starts_with = function(str, starts_with)
|
||||
return (string.sub(str, 1, string.len(starts_with)) == starts_with)
|
||||
end
|
||||
|
||||
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" or any_id == "d_dynamic") then
|
||||
return "0"
|
||||
end
|
||||
return string.split(any_id, "_")[2]
|
||||
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
|
||||
|
||||
|
||||
-- helper function for
|
||||
-- yl_speak_up.get_fs_talkdialog and
|
||||
-- yl_speak_up.check_and_add_as_generic_dialog
|
||||
-- find the dialog with d_sort == 0 or lowest number
|
||||
yl_speak_up.get_start_dialog_id = function(dialog)
|
||||
if(not(dialog) or not(dialog.n_dialogs)) then
|
||||
return nil
|
||||
end
|
||||
-- Find the dialog with d_sort = 0 or alternatively with the lowest number
|
||||
local lowest_sort = nil
|
||||
local d_id = nil
|
||||
for k, v in pairs(dialog.n_dialogs) do
|
||||
local nr = tonumber(v.d_sort)
|
||||
if(not(lowest_sort) or (nr and nr >= 0 and nr < lowest_sort)) then
|
||||
lowest_sort = nr
|
||||
d_id = k
|
||||
end
|
||||
end
|
||||
return d_id
|
||||
end
|
||||
|
||||
|
||||
-- helper function that is also used by export_to_ink.lua
|
||||
-- returns a sorted dialog list without special or generic dialogs
|
||||
yl_speak_up.get_dialog_list_for_export = function(dialog)
|
||||
local liste = {}
|
||||
if(not(dialog) or not(dialog.n_dialogs)) then
|
||||
return liste
|
||||
end
|
||||
-- sort the list of dialogs by d_id
|
||||
local liste_sorted = yl_speak_up.sort_keys(dialog.n_dialogs or {}, true)
|
||||
for _, d_id in ipairs(liste_sorted) do
|
||||
-- only normal dialogs - no d_trade, d_got_item, d_dynamic etc;
|
||||
if(not(yl_speak_up.is_special_dialog(d_id))
|
||||
-- also no generic dialogs (they do not come from this NPC)
|
||||
and not(dialog.n_dialogs[d_id].is_generic)) then
|
||||
table.insert(liste, d_id)
|
||||
end
|
||||
end
|
||||
-- now that the list contains only normal dialogs, we can sort by d_sort
|
||||
-- (thus allowing d_9 to be listed earlier than d_10 etc.)
|
||||
table.sort(liste, function(a, b)
|
||||
return dialog and dialog.n_dialogs and dialog.n_dialogs[a] and dialog.n_dialogs[b]
|
||||
and ((tonumber(dialog.n_dialogs[a].d_sort or "") or 0)
|
||||
< (tonumber(dialog.n_dialogs[b].d_sort or "") or 0)) end)
|
||||
return liste
|
||||
end
|
||||
|
||||
|
||||
--###
|
||||
--Formspecs
|
||||
--###
|
||||
|
||||
|
||||
-- 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)
|
||||
-- Note: pname is only passed to yl_speak_up.add_new_option - which is only used if
|
||||
-- dialog_text is empty (and only for logging)
|
||||
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
|
||||
|
||||
|
||||
-- update existing or create a new dialog named d_name with d_text
|
||||
-- (useful for import from ink and likewise functionality)
|
||||
-- this also prepares the dialog for options update
|
||||
yl_speak_up.update_dialog = function(log, dialog, dialog_name, dialog_text)
|
||||
if(dialog_name and yl_speak_up.is_special_dialog(dialog_name)) then
|
||||
-- d_trade, d_got_item, d_dynamic and d_end are not imported because they need to be handled diffrently
|
||||
table.insert(log, "Note: Not importing dialog text for \""..tostring(dialog_name).."\" because it is a special dialog.")
|
||||
-- the options of thes special dialogs are still relevant
|
||||
return dialog_name
|
||||
end
|
||||
-- does a dialog with name d_name already exist?
|
||||
local d_id = yl_speak_up.d_name_to_d_id(dialog, dialog_name)
|
||||
-- name the thing for logging purposes
|
||||
local log_str = "Dialog "..tostring(d_id)
|
||||
if(dialog_name and dialog_name ~= d_id) then
|
||||
log_str = log_str.." ["..tostring(dialog_name).."]:"
|
||||
else
|
||||
log_str = log_str..": "
|
||||
end
|
||||
local is_new = false
|
||||
if(not(d_id)) then
|
||||
local next_id = nil
|
||||
-- if dialog_name matches the d_<nr> pattern but d_<nr> does not exist,
|
||||
-- then try to create *that* dialog
|
||||
if(dialog_name and string.sub(dialog_name, 1, 2) == "d_") then
|
||||
next_id = tonumber(string.sub(dialog_name, 3))
|
||||
end
|
||||
-- pname is nil - thus no logging and no adding of a back to start option
|
||||
-- next_id is also usually nil - so just add a new dialog
|
||||
d_id = yl_speak_up.add_new_dialog(dialog, nil, next_id, dialog_text)
|
||||
if(not(d_id)) then
|
||||
-- the creation may have failed (i.e. dialog not beeing a dialog,
|
||||
-- or too many dialogs in dialog already)
|
||||
table.insert(log, log_str.."FAILED to create new dialog.")
|
||||
return nil
|
||||
end
|
||||
-- we got a new name for the log
|
||||
log_str = "New dialog "..tostring(d_id).." ["..tostring(dialog_name).."]: "
|
||||
is_new = true
|
||||
table.insert(log, log_str.." Created successfully.")
|
||||
|
||||
elseif(dialog.n_dialogs[d_id].d_text ~= dialog_text) then
|
||||
-- else update the text
|
||||
table.insert(log, log_str.." Changed dialog text from \""..
|
||||
tostring(dialog.n_dialogs[d_id].d_text).."\" to \""..tostring(dialog_text).."\".")
|
||||
-- actually change the dialog text
|
||||
dialog.n_dialogs[d_id].d_text = dialog_text
|
||||
end
|
||||
|
||||
local d_data = dialog.n_dialogs[d_id]
|
||||
-- set d_name if it differs from d_id
|
||||
if(d_id ~= dialog_name
|
||||
and (not(d_data.d_name)
|
||||
or(d_data.d_name ~= dialog_name))) then
|
||||
if(not(is_new)) then
|
||||
-- log only if it's not a new dialog
|
||||
table.insert(log, log_str.."Changed dialog name from \""..
|
||||
tostring(d_data.d_name).."\" to \""..tostring(dialog_name).."\".")
|
||||
end
|
||||
-- actually change the dialog name
|
||||
d_data.d_name = dialog_name
|
||||
end
|
||||
|
||||
-- the random option is set for the dialog entire; we will have to process the individual
|
||||
-- options in order to find out if this dialog is o_random; the first option that is sets
|
||||
-- it for the dialog -> keep the old value
|
||||
--d_data.o_random = nil
|
||||
|
||||
-- there may be existing options that won't get updated; deal with them:
|
||||
-- remember which options the dialog has and which sort order they had
|
||||
d_data.d_tmp_sorted_option_list = yl_speak_up.get_sorted_options(d_data.d_options or {}, "o_sort") or {}
|
||||
-- this value is increased whenever an option gets updated - so that we can have options
|
||||
-- that don't get an update sorted in after those options that did
|
||||
d_data.d_tmp_sort_value = 1
|
||||
-- mark all existing options as requirilng an update
|
||||
for i, o_id in ipairs(d_data.d_tmp_sorted_option_list or {}) do
|
||||
d_data.d_options[o_id].o_tmp_needs_update = true
|
||||
end
|
||||
-- mark this dialog as having received an update (meaning we won't have to update d_sort after
|
||||
-- all dialogs have been updated)
|
||||
d_data.d_tmp_has_been_updated = true
|
||||
return d_id
|
||||
end
|
||||
|
||||
|
||||
-- helper function for update_dialog_options_completed;
|
||||
-- adds a precondition of p_type "false" to the option so that the option is no longer displayed
|
||||
-- if disable_option is false, then all preconditions of p_type "false" will be changed to p_type "true"
|
||||
-- and thus the option will be shown to the player again
|
||||
yl_speak_up.update_disable_dialog_option = function(o_data, disable_option)
|
||||
-- is this otpion already deactivated?
|
||||
local is_deactivated = false
|
||||
for p_id, p in pairs(o_data.o_prerequisites or {}) do
|
||||
if(p and p_id and p.p_type == "false") then
|
||||
is_deactivated = true
|
||||
-- if we want to re-enable the option, then this here is the place
|
||||
if(not(disable_option)) then
|
||||
-- change the type from false to true - this particular precondition
|
||||
-- will now always be true
|
||||
p.p_type = "true"
|
||||
-- we continue work here because the player may have created multiple
|
||||
-- options of this type
|
||||
end
|
||||
end
|
||||
end
|
||||
-- if not: add a precondition of type "false"
|
||||
if(not(is_deactivated) and disable_option) then
|
||||
-- we need to add a new precondition of type "false"
|
||||
-- make sure we can add the prereq:
|
||||
if(not(o_data.o_prerequisites)) then
|
||||
o_data.o_prerequisites = {}
|
||||
end
|
||||
local future_p_id = "p_"..tostring(yl_speak_up.find_next_id(o_data.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
|
||||
o_data.o_prerequisites[future_p_id] = { p_id = future_p_id, p_type = "false"}
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
-- call this *after* all dialog options have been updated for dialog_name
|
||||
yl_speak_up.update_dialog_options_completed = function(log, dialog, d_id)
|
||||
local d_data = dialog.n_dialogs[d_id]
|
||||
if(not(d_data)) then
|
||||
return
|
||||
end
|
||||
for i, o_id in ipairs(d_data.d_tmp_sorted_option_list or {}) do
|
||||
local o_data = d_data.d_options[o_id]
|
||||
if(o_data.o_tmp_needs_update) then
|
||||
-- update the sort value so that this option will be listed *after* those
|
||||
-- options that actually did get updated
|
||||
o_data.o_sort = d_data.d_tmp_sort_value
|
||||
d_data.d_tmp_sort_value = d_data.d_tmp_sort_value + 1
|
||||
-- this option has now been processed
|
||||
o_data.o_tmp_needs_update = nil
|
||||
|
||||
-- name the thing for logging purposes
|
||||
local log_str = "Dialog "..tostring(d_id)
|
||||
if(dialog_name and dialog_name ~= d_id) then
|
||||
log_str = log_str.." ["..tostring(d_id).."]"
|
||||
end
|
||||
table.insert(log, log_str..", option <"..tostring(o_id)..">: "..
|
||||
"Option exists in old dialog but not in import. Keeping option.")
|
||||
-- add a precondition of p_type "false" to the option so that the option
|
||||
-- is no longer displayed
|
||||
yl_speak_up.update_disable_dialog_option(o_data, true)
|
||||
end
|
||||
end
|
||||
-- clean up the dialog
|
||||
d_data.d_tmp_sorted_option_list = nil
|
||||
d_data.d_tmp_sort_value = nil
|
||||
end
|
||||
|
||||
|
||||
-- make sure only one dialog has d_sort set to 0 (and is thus the start dialog)
|
||||
yl_speak_up.update_start_dialog = function(log, dialog, start_dialog_name, start_with_d_sort)
|
||||
local start_d_id = yl_speak_up.d_name_to_d_id(dialog, start_dialog_name)
|
||||
if(not(start_d_id)) then
|
||||
return
|
||||
end
|
||||
for d_id, d in pairs(dialog.n_dialogs) do
|
||||
if(d_id == start_d_id) then
|
||||
if(not(d.d_sort) or d.d_sort ~= 0) then
|
||||
table.insert(log, "Setting start dialog to "..tostring(start_dialog_name)..".")
|
||||
end
|
||||
d.d_sort = 0
|
||||
-- the start dialog certainly is *a* start dialog (with the buttons)
|
||||
d.is_a_start_dialog = true
|
||||
elseif(not(d.d_tmp_has_been_updated)) then
|
||||
-- sort this dialog behind the others
|
||||
d.d_sort = start_with_d_sort
|
||||
start_with_d_sort = start_with_d_sort + 1
|
||||
end
|
||||
d.d_tmp_has_been_updated = nil
|
||||
end
|
||||
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
|
||||
-- Note: pname is only used for logging (and for changing o_sort)
|
||||
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 ""),
|
||||
}
|
||||
|
||||
local start_with_o_sort = nil
|
||||
if(pname and pname ~= "") then
|
||||
-- log only in edit mode
|
||||
local n_id = yl_speak_up.speak_to[pname].n_id
|
||||
-- would be too difficult to add an exception for edit_mode here; thus, we do it directly here:
|
||||
if(yl_speak_up.npc_was_changed
|
||||
and 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
|
||||
|
||||
start_with_o_sort = yl_speak_up.speak_to[pname].o_sort
|
||||
end
|
||||
|
||||
-- necessary in order for it to work
|
||||
local new_o_sort = yl_speak_up.sanitize_sort(dialog.n_dialogs[d_id].d_options, start_with_o_sort)
|
||||
dialog.n_dialogs[d_id].d_options[future_o_id].o_sort = new_o_sort
|
||||
|
||||
-- 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
|
||||
|
||||
|
||||
-- update existing or create a new option named option_name for dialog dialog_name
|
||||
-- If option_name starts with..
|
||||
-- new_ create a new option (discard the rest of option_name)
|
||||
-- automaticly_ set o_autoanswer
|
||||
-- randomly_ set o_random *for the dialog*
|
||||
-- grey_out_ set o_text_when_prerequisites_not_met
|
||||
-- ..and take what remains as option_name.
|
||||
-- (useful for import from ink and likewise functionality)
|
||||
--
|
||||
-- TODO: these notes need to be taken care of in the calling function
|
||||
-- Note: The calling function may need to adjust o_sort according to its needs.
|
||||
-- Note: Preconditions, actions and effects are not handled here (apart from the "dialog"
|
||||
-- effect/result for the redirection to the target dialog)
|
||||
yl_speak_up.update_dialog_option = function(log, dialog, dialog_name, option_name,
|
||||
option_text, option_text_if_preconditions_false,
|
||||
target_dialog, alternate_text, visit_only_once, sort_order)
|
||||
-- does the dialog we want to add to exist?
|
||||
local d_id = yl_speak_up.d_name_to_d_id(dialog, dialog_name)
|
||||
if(not(d_id)) then
|
||||
if(not(yl_speak_up.is_special_dialog(dialog_name))) then
|
||||
-- the dialog does not exist - we cannot add an option to a nonexistant dialog
|
||||
return nil
|
||||
end
|
||||
-- options for special dialogs have to start with "automaticly_"
|
||||
local parts = string.split(option_name or "", "_")
|
||||
if(not(parts) or not(parts[1]) or parts[1] ~= "automaticly") then
|
||||
option_name = "automaticly_"..table.concat(parts[2], "_")
|
||||
end
|
||||
-- for d_trade and d_got_item effects and preconditions are created WITH DEFAULT VALUES TODO
|
||||
d_id = dialog_name
|
||||
-- make sure the relevant dialog and fields exist
|
||||
dialog.n_dialogs[d_id] = dialog.n_dialogs[d_id] or {}
|
||||
dialog.n_dialogs[d_id].d_options = dialog.n_dialogs[d_id].d_options or {}
|
||||
end
|
||||
-- name the thing for logging purposes
|
||||
local log_str = "Dialog "..tostring(d_id)
|
||||
if(dialog_name and dialog_name ~= d_id) then
|
||||
log_str = log_str.." ["..tostring(dialog_name).."]"
|
||||
end
|
||||
log_str = log_str..", option <"..tostring(option_name)..">: "
|
||||
local is_new = false
|
||||
|
||||
-- translate the name of the target_dialog if needed
|
||||
if(target_dialog and not(yl_speak_up.is_special_dialog(target_dialog))) then
|
||||
target_dialog = yl_speak_up.d_name_to_d_id(dialog, target_dialog)
|
||||
end
|
||||
-- TODO: dialogs d_got_item and d_trade are special
|
||||
|
||||
local o_id = option_name
|
||||
local mode = 0
|
||||
local text_when_prerequisites_not_met = ""
|
||||
local parts = string.split(o_id, "_")
|
||||
if(not(parts) or not(parts[1]) or not(parts[2])) then
|
||||
table.insert(log, log_str.."FAILED to create unknown option \""..tostring(o_id).."\".")
|
||||
return nil
|
||||
elseif(o_id and parts[1] == "new") then
|
||||
-- we are asked to create a *new* option
|
||||
o_id = nil
|
||||
elseif(o_id and parts[1] == "automaticly") then
|
||||
-- this option will be automaticly selected if its preconditions are true
|
||||
mode = 1
|
||||
option_name = parts[2]
|
||||
o_id = option_name
|
||||
elseif(o_id and parts[1] == "randomly") then
|
||||
-- this option will be randomly selected if its preconditions are true;
|
||||
-- (that means all other options of this dialog will have to be randomly as well;
|
||||
-- something which cannot be done here as there is no guarantee that all options
|
||||
-- *exist* at this point)
|
||||
mode = 2
|
||||
option_name = parts[2]
|
||||
o_id = option_name
|
||||
elseif(o_id and parts[1] ~= "o") then
|
||||
table.insert(log, log_str.."FAILED to create unknown option \""..tostring(o_id).."\".")
|
||||
return nil
|
||||
end
|
||||
|
||||
|
||||
-- if the option does not exist: create it
|
||||
if( not(dialog.n_dialogs[d_id].d_options)
|
||||
or not(o_id) or o_id == ""
|
||||
or not(dialog.n_dialogs[d_id].d_options[o_id])) then
|
||||
local next_id = nil
|
||||
-- get the id part (number) from o_id - because we may be creating a new option here -
|
||||
-- but said option may have a diffrent *name* than what a new option would get by
|
||||
-- default
|
||||
if(o_id) then
|
||||
next_id = string.sub(o_id, 3)
|
||||
if(next_id == "" or not(tonumber(next_id))) then
|
||||
next_id = nil
|
||||
table.insert(log, log_str.."FAILED to create new option \""..tostring(o_id).."\".")
|
||||
return
|
||||
end
|
||||
end
|
||||
-- pname is nil - thus no logging here
|
||||
o_id = yl_speak_up.add_new_option(dialog, nil, next_id, d_id, option_text, target_dialog)
|
||||
if(not(o_id)) then
|
||||
return nil
|
||||
end
|
||||
is_new = true
|
||||
end
|
||||
|
||||
-- abbreviate that
|
||||
local o_data = dialog.n_dialogs[d_id].d_options[o_id]
|
||||
|
||||
-- cchnage option_text if needed
|
||||
if(o_data.o_text_when_prerequisites_met ~= option_text) then
|
||||
table.insert(log, log_str.."Changed option text from \""..
|
||||
tostring(o_data.o_text_when_prerequisites_met)..
|
||||
"\" to \""..tostring(option_text).."\" for option \""..tostring(o_id).."\".")
|
||||
end
|
||||
-- actually update the text
|
||||
o_data.o_text_when_prerequisites_met = option_text
|
||||
|
||||
-- chnage greyed out text if needed
|
||||
if(o_data.o_text_when_prerequisites_not_met ~= option_text_if_preconditions_false
|
||||
and option_text_if_preconditions_false) then
|
||||
table.insert(log, log_str.."Changed greyed out text when prerequisites not met from \""..
|
||||
tostring(o_data.o_text_when_prerequisites_not_met)..
|
||||
"\" to \""..tostring(option_text_if_preconditions_false or "")..
|
||||
"\" for option \""..tostring(o_id).."\".")
|
||||
-- make sure the greyed out text gets shown (or not shown)
|
||||
o_data.o_text_when_prerequisites_not_met = option_text_if_preconditions_false or ""
|
||||
end
|
||||
-- make grey_out_ text visible if necessary
|
||||
if(o_data.o_text_when_prerequisites_not_met and o_data.o_text_when_prerequisites_not_met ~= ""
|
||||
and option_text_if_preconditions_false and option_text_if_preconditions_false ~= "") then
|
||||
-- make sure this text is really shown - and greyed out
|
||||
-- (resetting this can only happen through editing the NPC directly; not through import)
|
||||
o_data.o_hide_when_prerequisites_not_met = "false"
|
||||
o_data.o_grey_when_prerequisites_not_met = "true"
|
||||
else
|
||||
-- if this were not set to true, then the player would see a clickable button for
|
||||
-- the option - but that button would do nothing
|
||||
o_data.o_hide_when_prerequisites_not_met = "true"
|
||||
o_data.o_grey_when_prerequisites_not_met = "false"
|
||||
end
|
||||
|
||||
local r_found = false
|
||||
-- the target_dialog may have been changed
|
||||
for r_id, r in pairs(o_data.o_results or {}) do
|
||||
-- we found the right result/effect that holds the (current) target_dialog
|
||||
if(r and r.r_type and r.r_type == "dialog") then
|
||||
r_found = true
|
||||
if(not(r.r_value) or r.r_value ~= target_dialog) then
|
||||
if(is_new) then
|
||||
table.insert(log, log_str.."Successfully created new option \""..
|
||||
tostring(o_id).."\" with target dialog \""..
|
||||
tostring(target_dialog).."\".")
|
||||
else
|
||||
table.insert(log, log_str.."Changed target dialog from \""..
|
||||
tostring(r.r_value).."\" to \""..tostring(target_dialog)..
|
||||
"\" for option \""..tostring(o_id).."\".")
|
||||
end
|
||||
-- actually change the target dialog
|
||||
r.r_value = target_dialog
|
||||
end
|
||||
-- the alternate_text may have been changed
|
||||
if(r.alternate_text ~= alternate_text) then
|
||||
table.insert(log, log_str.."Changed alternate text from \""..
|
||||
tostring(r.r_alternate_text).."\" to \""..tostring(alternate_text)..
|
||||
"\" for option \""..tostring(o_id).."\".")
|
||||
r.alternate_text = alternate_text
|
||||
end
|
||||
end
|
||||
end
|
||||
-- for some reason the effect pointing to the target dialog got lost!
|
||||
if(r_found and is_new) then
|
||||
table.insert(log, log_str.."Set target dialog to "..tostring(target_dialog)..
|
||||
" for new option \""..tostring(o_id).."\".")
|
||||
end
|
||||
if(not(r_found)) then
|
||||
-- create the result/effect that points to the target_dialog
|
||||
local r_id = yl_speak_up.add_new_result(dialog, d_id, o_id)
|
||||
if(r_id) then
|
||||
o_data.o_results[r_id].r_type = "dialog"
|
||||
o_data.o_results[r_id].r_value = target_dialog
|
||||
o_data.o_results[r_id].alternate_text = alternate_text
|
||||
table.insert(log, log_str.."Set target dialog to "..tostring(target_dialog)..
|
||||
" for option \""..tostring(o_id).."\".")
|
||||
end
|
||||
end
|
||||
|
||||
-- "randomly selected" applies to the *dialog* - it is set there and not in the individual option
|
||||
local d_data = dialog.n_dialogs[d_id]
|
||||
-- is this option selected randomly?
|
||||
if( mode == 2 and not(d_data.o_random)) then
|
||||
table.insert(log, log_str.."Changed DIALOG \""..tostring(d_id).."\" to RANDOMLY SELECTED.")
|
||||
d_data.o_random = 1
|
||||
end
|
||||
|
||||
-- is this option selected automaticly if all preconditions are met?
|
||||
if(mode == 1 and not(o_data.o_autoanswer)) then
|
||||
o_data.o_autoanswer = 1
|
||||
table.insert(log, log_str.."Changed option \""..tostring(o_id).."\" to AUTOMATICLY SELECTED.")
|
||||
-- mode is 0 - that means everything is normal for this option
|
||||
elseif(mode ~= 1 and o_data.o_autoanswer) then
|
||||
o_data.o_autoanswer = nil
|
||||
table.insert(log, log_str.."Removed AUTOMATICLY SELECTED from option \""..tostring(o_id).."\".")
|
||||
end
|
||||
|
||||
-- the visit_only_once option is handled without logging as it might create too many
|
||||
-- entries in the log without adding any helpful information
|
||||
if(visit_only_once
|
||||
and (not(o_data.o_visit_only_once)
|
||||
or o_data.o_visit_only_once ~= 1)) then
|
||||
o_data.o_visit_only_once = 1
|
||||
elseif(not(visit_only_once)
|
||||
and o_data.o_visit_only_once and o_data.o_visit_only_once == 1) then
|
||||
o_data.o_visit_only_once = nil
|
||||
end
|
||||
-- set sort order of options (no logging because that might get too spammy)
|
||||
if(sort_order) then
|
||||
o_data.o_sort = sort_order
|
||||
end
|
||||
-- this option has been updated
|
||||
o_data.o_tmp_needs_update = false
|
||||
if(o_data.o_sort and d_data.d_tmp_sort_value and o_data.o_sort >= d_data.d_tmp_sort_value) then
|
||||
-- make sure this stores the highest o_sort value we found
|
||||
d_data.d_tmp_sort_value = o_data.o_sort + 1
|
||||
end
|
||||
return 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] = {}
|
||||
dialog.n_dialogs[d_id].d_options[o_id].o_results[future_r_id].r_id = future_r_id
|
||||
return future_r_id
|
||||
end
|
||||
-- TODO: we need yl_speak_up.update_dialog_option_result as well
|
||||
|
||||
|
||||
-- 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
|
||||
|
||||
|
||||
-- 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
|
||||
|
||||
-- checks if dialog exists
|
||||
yl_speak_up.check_if_dialog_exists = function(dialog, d_id)
|
||||
return (dialog and d_id
|
||||
and dialog.n_dialogs
|
||||
and dialog.n_dialogs[d_id])
|
||||
end
|
||||
|
||||
|
||||
|
||||
yl_speak_up.is_special_dialog = function(d_id)
|
||||
if(not(d_id)) then
|
||||
return false
|
||||
end
|
||||
return (d_id == "d_trade" or d_id == "d_got_item" or d_id == "d_dynamic" or d_id == "d_end")
|
||||
end
|
||||
|
||||
|
||||
yl_speak_up.d_name_to_d_id = function(dialog, d_name)
|
||||
if(not(dialog) or not(dialog.n_dialogs) or not(d_name) or d_name == "") then
|
||||
return nil
|
||||
end
|
||||
-- it is already the ID of an existing dialog
|
||||
if(dialog.n_dialogs[d_name]) then
|
||||
return d_name
|
||||
end
|
||||
-- search all dialogs for one with a fitting d_name
|
||||
for k,v in pairs(dialog.n_dialogs) do
|
||||
if(v and v.d_name and v.d_name == d_name) then
|
||||
return k
|
||||
end
|
||||
end
|
||||
return nil
|
||||
end
|
||||
|
||||
|
||||
-- get the name of a dialog (reverse of above)
|
||||
yl_speak_up.d_id_to_d_name = function(dialog, d_id)
|
||||
if(not(dialog) or not(dialog.n_dialogs) or not(d_id) or d_id == ""
|
||||
or not(dialog.n_dialogs[d_id])
|
||||
or not(dialog.n_dialogs[d_id].d_name)
|
||||
or dialog.n_dialogs[d_id].d_name == "") then
|
||||
return d_id
|
||||
end
|
||||
return dialog.n_dialogs[d_id].d_name
|
||||
end
|
||||
|
||||
|
||||
|
||||
|
||||
-- how many own (not special, not generic) dialogs does the NPC have?
|
||||
yl_speak_up.count_dialogs = function(dialog)
|
||||
local count = 0
|
||||
if(not(dialog) or not(dialog.n_dialogs)) then
|
||||
return 0
|
||||
end
|
||||
for d_id, v in pairs(dialog.n_dialogs) do
|
||||
if(d_id
|
||||
and not(yl_speak_up.is_special_dialog(d_id))
|
||||
and not(dialog.n_dialogs[d_id].is_generic)) then
|
||||
count = count + 1
|
||||
end
|
||||
end
|
||||
return count
|
||||
end
|
||||
112
functions_save_restore_dialogs.lua
Normal file
112
functions_save_restore_dialogs.lua
Normal file
@ -0,0 +1,112 @@
|
||||
|
||||
--###
|
||||
--Load and Save
|
||||
--###
|
||||
|
||||
local function save_path(n_id)
|
||||
return yl_speak_up.worldpath .. yl_speak_up.path .. DIR_DELIM .. n_id .. ".json"
|
||||
end
|
||||
|
||||
-- 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)
|
||||
-- never store d_dynamic dialogs
|
||||
if(dialog.n_dialogs and dialog.n_dialogs["d_dynamic"]) then
|
||||
dialog.n_dialogs["d_dynamic"] = nil
|
||||
end
|
||||
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)
|
||||
|
||||
-- note: add_generic_dialogs will also add an empty d_dynamic dialog
|
||||
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
|
||||
|
||||
-- this deletes the dialog with id d_id from the npc n_id's dialogs;
|
||||
-- it loads the dialogs from the npc's savefile, deletes dialog d_id,
|
||||
-- and then saves the dialogs back to the npc's savefile in order to
|
||||
-- keep things consistent
|
||||
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
|
||||
|
||||
|
||||
|
||||
-- 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
|
||||
|
||||
328
functions_talk.lua
Normal file
328
functions_talk.lua
Normal file
@ -0,0 +1,328 @@
|
||||
|
||||
--###
|
||||
-- Init
|
||||
--###
|
||||
|
||||
-- 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 = {}
|
||||
|
||||
|
||||
--###
|
||||
-- Debug
|
||||
--###
|
||||
|
||||
yl_speak_up.debug = true
|
||||
|
||||
|
||||
---###
|
||||
-- general formpsec
|
||||
---###
|
||||
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.get_sorted_dialog_name_list = function(dialog)
|
||||
local liste = {}
|
||||
if(dialog and dialog.n_dialogs) then
|
||||
for k, v in pairs(dialog.n_dialogs) do
|
||||
-- this will be used for dropdown lists - so we use formspec_escape
|
||||
table.insert(liste, minetest.formspec_escape(v.d_name or k or "?"))
|
||||
end
|
||||
-- sort alphabethicly
|
||||
table.sort(liste)
|
||||
end
|
||||
return liste
|
||||
end
|
||||
|
||||
---###
|
||||
-- player related
|
||||
---###
|
||||
|
||||
yl_speak_up.reset_vars_for_player = function(pname, reset_fs_version)
|
||||
yl_speak_up.speak_to[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
|
||||
|
||||
|
||||
|
||||
---###
|
||||
-- player and npc related
|
||||
---###
|
||||
|
||||
-- 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
|
||||
|
||||
|
||||
-- returns true if someone is speaking to the NPC
|
||||
yl_speak_up.npc_is_in_conversation = function(n_id)
|
||||
for name, data in pairs(yl_speak_up.speak_to) do
|
||||
if(data and data.n_id and data.n_id == n_id) then
|
||||
return true
|
||||
end
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
|
||||
-- returns a list of players that are in conversation with this NPC
|
||||
yl_speak_up.npc_is_in_conversation_with = function(n_id)
|
||||
local liste = {}
|
||||
for name, data in pairs(yl_speak_up.speak_to) do
|
||||
if(data and data.n_id and data.n_id == n_id) then
|
||||
table.insert(liste, name)
|
||||
end
|
||||
end
|
||||
return liste
|
||||
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
|
||||
|
||||
|
||||
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
|
||||
-- this makes it a bit easier to access some values later on:
|
||||
yl_speak_up.speak_to[pname]._self = self
|
||||
-- Load the dialog and see what we can do with it
|
||||
-- this inculdes generic dialog parts;
|
||||
yl_speak_up.speak_to[pname].dialog = yl_speak_up.load_dialog(n_id, clicker)
|
||||
|
||||
-- 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
|
||||
|
||||
local dialog = yl_speak_up.speak_to[pname].dialog
|
||||
if(not(dialog.trades)) then
|
||||
dialog.trades = {}
|
||||
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), false, dialog)
|
||||
|
||||
|
||||
-- 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
|
||||
|
||||
|
||||
-- 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
|
||||
|
||||
1039
import_from_ink.lua
Normal file
1039
import_from_ink.lua
Normal file
File diff suppressed because it is too large
Load Diff
10
init.lua
10
init.lua
@ -212,7 +212,9 @@ yl_speak_up.reload = function(modpath, log_entry)
|
||||
-- some generic dialogs
|
||||
dofile(modpath .. "api/api_properties.lua")
|
||||
-- the main functionality of the mod
|
||||
dofile(modpath .. "functions.lua")
|
||||
dofile(modpath .. "functions_dialogs.lua")
|
||||
dofile(modpath .. "functions_save_restore_dialogs.lua")
|
||||
dofile(modpath .. "functions_talk.lua")
|
||||
-- implementation of the chat commands registered in register_once.lua:
|
||||
dofile(modpath .. "chat_commands.lua")
|
||||
|
||||
@ -220,6 +222,10 @@ yl_speak_up.reload = function(modpath, log_entry)
|
||||
dofile(modpath .. "api/api_npc_list.lua")
|
||||
dofile(modpath .. "fs/fs_npc_list.lua")
|
||||
|
||||
-- this may load custom things like preconditions, actions, effects etc.
|
||||
-- which may depend on the existance of other mods
|
||||
dofile(modpath .. "addons/load_addons.lua")
|
||||
|
||||
-- some general functions that are useful for mobs_redo
|
||||
-- (react to right-click, nametag color etc.)
|
||||
-- only gets loaded if mobs_redo (mobs) exists as mod
|
||||
@ -229,6 +235,8 @@ yl_speak_up.reload = function(modpath, log_entry)
|
||||
dofile(modpath .. "export_to_ink.lua")
|
||||
dofile(modpath .. "fs/fs_export.lua")
|
||||
|
||||
dofile(modpath .. "import_from_ink.lua")
|
||||
|
||||
-- edit_mode.lua has been moved to the mod npc_talk_edit:
|
||||
-- dofile(modpath .. "editor/edit_mode.lua")
|
||||
|
||||
|
||||
@ -31,21 +31,57 @@ function yl_speak_up.do_mobs_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
|
||||
local pos = self.object:get_pos()
|
||||
if mobs:capture_mob(self, clicker, nil, 100, 100, true, nil) then
|
||||
if(self.yl_speak_up) then
|
||||
local n_id = "?"
|
||||
if(self and self.yl_speak_up and self.yl_speak_up.id) then
|
||||
n_id = "n_"..tostring(self.yl_speak_up.id)
|
||||
|
||||
-- if someone other than the owner placed the mob, then we need to
|
||||
-- adjust the owner back from placer to real_owner
|
||||
if(self.yl_speak_up.real_owner and self.yl_speak_up.real_owner ~= self.owner) then
|
||||
self.owner = self.yl_speak_up.real_owner
|
||||
end
|
||||
end
|
||||
|
||||
-- Take the mob only with net or lasso
|
||||
if self.owner and (self.owner == name or yl_speak_up.may_edit_npc(clicker, n_id)) then
|
||||
local pos = self.object:get_pos()
|
||||
self.yl_speak_up.last_pos = minetest.pos_to_string(pos, 0)
|
||||
-- the mob can be picked up by someone who can just *edit* it but is not *the* owner
|
||||
if(self.owner ~= name) then
|
||||
self.yl_speak_up.real_owner = self.owner
|
||||
end
|
||||
-- try to capture the mob
|
||||
local egg_stack = mobs:capture_mob(self, clicker, nil, 100, 100, true, nil)
|
||||
if(egg_stack and self.yl_speak_up) then
|
||||
minetest.log("action","[MOD] yl_speak_up "..
|
||||
" NPC n_"..tostring(self.yl_speak_up.id)..
|
||||
" named "..tostring(self.yl_speak_up.npc_name)..
|
||||
" (owned by "..tostring(self.owner)..
|
||||
") picked up by "..tostring(clicker:get_player_name())..
|
||||
" at pos "..minetest.pos_to_string(pos, 0)..".")
|
||||
|
||||
-- players want to know *which* NPC will "hatch" from this egg;
|
||||
-- sadly there is no point in modifying egg_data as that has already
|
||||
-- been put into the inventory of the player and is just a copy now
|
||||
local player_inv = clicker:get_inventory()
|
||||
for i, v in ipairs(player_inv:get_list("main") or {}) do
|
||||
local m = v:get_meta()
|
||||
local d = minetest.deserialize(m:get_string("") or {})
|
||||
-- adjust the description text of the NPC in the inventory
|
||||
if(d and d.yl_speak_up and d.yl_speak_up.id) then
|
||||
local d2 = d.yl_speak_up
|
||||
local text = (d2.npc_name or "- nameless -").. ", "..
|
||||
(d2.npc_description or "-").."\n"..
|
||||
"(n_"..tostring(d2.id)..", owned by "..
|
||||
tostring(d.owner).."),\n"..
|
||||
"picked up at "..tostring(d2.last_pos or "?").."."
|
||||
m:set_string("description", text)
|
||||
player_inv:set_stack("main", i, v)
|
||||
end
|
||||
end
|
||||
return
|
||||
end
|
||||
return
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- protect npc with mobs:protector
|
||||
if mobs:protect(self, clicker) then
|
||||
|
||||
@ -13,6 +13,17 @@ yl_speak_up.npc_priv_names = {
|
||||
"effect_exec_lua", "effect_give_item", "effect_take_item", "effect_move_player",
|
||||
}
|
||||
|
||||
-- make sure this table exists
|
||||
if(not(yl_speak_up.npc_priv_needs_player_priv)) then
|
||||
yl_speak_up.npc_priv_needs_player_priv = {}
|
||||
end
|
||||
-- and set it to privs if nothing is specified (because the *_lua are extremly dangerous npc_privs!)
|
||||
for i, p in ipairs(yl_speak_up.npc_priv_names) do
|
||||
if(not(yl_speak_up.npc_priv_needs_player_priv[p])) then
|
||||
yl_speak_up.npc_priv_needs_player_priv[p] = "privs"
|
||||
end
|
||||
end
|
||||
|
||||
-- either the npc with n_id *or* if generic_npc_id is set the generic npc with the
|
||||
-- id generic_npc_id needs to have been granted priv_name
|
||||
yl_speak_up.npc_has_priv = function(n_id, priv_name, generic_npc_id)
|
||||
@ -74,27 +85,43 @@ end
|
||||
-- "If called with parameter [list], all granted privs for all NPC are shown.",
|
||||
-- privs = {privs = true},
|
||||
yl_speak_up.command_npc_talk_privs = function(pname, param)
|
||||
-- can the player see the privs for all NPC? or just for those he can edit?
|
||||
local list_all = false
|
||||
local ptmp = {}
|
||||
ptmp[yl_speak_up.npc_privs_priv] = true
|
||||
if(minetest.check_player_privs(pname, ptmp)) then
|
||||
list_all = true
|
||||
end
|
||||
if(not(param) or param == "") then
|
||||
-- if the npc priv has a player priv as requirement, then list that
|
||||
local tmp = {}
|
||||
for i, p in ipairs(yl_speak_up.npc_priv_names) do
|
||||
table.insert(tmp, tostring(p)..
|
||||
" ["..tostring(yl_speak_up.npc_priv_needs_player_priv[p]).."]")
|
||||
end
|
||||
minetest.chat_send_player(pname,
|
||||
"Usage: [grant|revoke|list] <n_id> <priv>\n"..
|
||||
"The following privilege exist:\n\t"..
|
||||
table.concat(yl_speak_up.npc_priv_names, ", ")..".")
|
||||
"The following privilege exist [and require you to have this priv to set]:\n\t"..
|
||||
table.concat(tmp, ", ")..".")
|
||||
return
|
||||
end
|
||||
local player = minetest.get_player_by_name(pname)
|
||||
local parts = string.split(param, " ")
|
||||
if(parts[1] == "list") then
|
||||
local text = "This list contains the privs of each NPC in the form of "..
|
||||
"<npc_name>: <list of privs>"
|
||||
local text = "This list contains the privs of each NPC you can edit "..
|
||||
"in the form of <npc_name>: <list of privs>"
|
||||
-- create list of all existing extra privs for npc
|
||||
for n_id, v in pairs(yl_speak_up.npc_priv_table) do
|
||||
text = text..".\n"..tostring(n_id)..":"
|
||||
local found = false
|
||||
for priv, w in pairs(v) do
|
||||
text = text.." "..tostring(priv)
|
||||
found = true
|
||||
end
|
||||
if(not(found)) then
|
||||
text = text.." <none>"
|
||||
if(list_all or yl_speak_up.may_edit_npc(player, n_id)) then
|
||||
text = text..".\n"..tostring(n_id)..":"
|
||||
local found = false
|
||||
for priv, w in pairs(v) do
|
||||
text = text.." "..tostring(priv)
|
||||
found = true
|
||||
end
|
||||
if(not(found)) then
|
||||
text = text.." <none>"
|
||||
end
|
||||
end
|
||||
end
|
||||
minetest.chat_send_player(pname, text..".")
|
||||
@ -114,6 +141,31 @@ yl_speak_up.command_npc_talk_privs = function(pname, param)
|
||||
table.concat(yl_speak_up.npc_priv_names, ", ")..".")
|
||||
return
|
||||
end
|
||||
|
||||
-- does the player have the necessary player priv to grant or revoke this npc priv?
|
||||
local ptmp = {}
|
||||
ptmp[yl_speak_up.npc_priv_needs_player_priv[priv]] = true
|
||||
if(not(minetest.check_player_privs(pname, ptmp))) then
|
||||
minetest.chat_send_player(pname, "You lack the \""..
|
||||
tostring(yl_speak_up.npc_priv_needs_player_priv[priv])..
|
||||
"\" priv required to grant or revoke this NPC priv!")
|
||||
return
|
||||
end
|
||||
|
||||
-- does the player have the right to edit/change this npc?
|
||||
if(not(list_all) and not(yl_speak_up.may_edit_npc(player, n_id))) then
|
||||
minetest.chat_send_player(pname, "You can only set privs for NPC which you can edit. \""..
|
||||
tostring(n_id).." cannot be edited by you.")
|
||||
return
|
||||
end
|
||||
|
||||
-- revoking privs of nonexistant NPC is allowed - but not granting them privs
|
||||
local id = tonumber(string.sub(n_id, 3)) or 0
|
||||
if(command == "grant" and not(yl_speak_up.npc_list[id])) then
|
||||
minetest.chat_send_player(pname,
|
||||
"Unknown NPC \""..tostring(n_id).."\".\n")
|
||||
return
|
||||
end
|
||||
if(command == "grant" and not(yl_speak_up.npc_priv_table[n_id])) then
|
||||
yl_speak_up.npc_priv_table[n_id] = {}
|
||||
end
|
||||
|
||||
@ -198,9 +198,12 @@ yl_speak_up.get_quest_variable_value = function(player_name, variable_name)
|
||||
-- the owner name is alrady encoded in the variable name
|
||||
local k = tostring(variable_name)
|
||||
if(not(variable_name) or not(player_name) or not(yl_speak_up.player_vars[ k ])) then
|
||||
yl_speak_up.get_variable_metadata(k_long, "default_value", true)
|
||||
return nil
|
||||
end
|
||||
-- return stored value OR the default value
|
||||
return yl_speak_up.player_vars[ k ][ player_name ]
|
||||
or yl_speak_up.player_vars[ k ][ "$META$"][ "default_value" ]
|
||||
end
|
||||
|
||||
|
||||
@ -368,6 +371,12 @@ yl_speak_up.set_variable_metadata = function(k, pname, meta_name, entry_name, ne
|
||||
-- var_type (the type of the variable) is a single string
|
||||
if(meta_name == "var_type") then
|
||||
yl_speak_up.player_vars[ k ][ "$META$"][ meta_name ] = new_value
|
||||
elseif(meta_name == "default_value") then
|
||||
-- reset default value to nil with empty string:
|
||||
if(new_value == "") then
|
||||
new_value = nil
|
||||
end
|
||||
yl_speak_up.player_vars[ k ][ "$META$"][ meta_name ] = new_value
|
||||
else
|
||||
if( not(yl_speak_up.player_vars[ k ][ "$META$" ][ meta_name ])
|
||||
or type(yl_speak_up.player_vars[ k ][ "$META$" ][ meta_name ]) ~= "table") then
|
||||
@ -394,6 +403,22 @@ yl_speak_up.get_access_list_for_var = function(k, pname, access_what)
|
||||
end
|
||||
|
||||
|
||||
-- helper function that searces for variables that will be replaced with their
|
||||
-- values in text when displayed; helper function for yl_speak_up.update_stored_npc_data
|
||||
-- (for keeping track of which NPC uses which variables)
|
||||
-- changes table vars_used
|
||||
yl_speak_up.find_player_vars_in_text = function(vars_used, text)
|
||||
if(not(text) or text == "") then
|
||||
return vars_used
|
||||
end
|
||||
for v in string.gmatch(text, "%$VAR ([%w%s_%-%.]+)%$") do
|
||||
-- add the $ prefix again
|
||||
vars_used["$ "..tostring(v)] = true
|
||||
end
|
||||
return vars_used
|
||||
end
|
||||
|
||||
|
||||
-- the dialog data of an NPC is saved - use this to save some statistical data
|
||||
-- plus store which variables are used by this NPC
|
||||
-- TODO: show this data in a formspec to admins for maintenance
|
||||
@ -414,26 +439,36 @@ yl_speak_up.update_stored_npc_data = function(n_id, dialog)
|
||||
local anz_actions = 0
|
||||
local anz_effects = 0
|
||||
local anz_trades = 0
|
||||
local variables_p = {}
|
||||
local variables_e = {}
|
||||
-- used in d.d_text dialog texts,
|
||||
-- o.o_text_when_prerequisites_met, o.o_text_when_prerequisites_not_met,
|
||||
-- preconditions and effects
|
||||
local variables_used = {}
|
||||
if(dialog and dialog.n_dialogs) then
|
||||
for d_id, d in pairs(dialog.n_dialogs) do
|
||||
anz_dialogs = anz_dialogs + 1
|
||||
if(d) then
|
||||
-- find all variables used in the text
|
||||
variables_used = yl_speak_up.find_player_vars_in_text(variables_used, d.d_text)
|
||||
end
|
||||
if(d and d.d_options) then
|
||||
for o_id, o in pairs(d.d_options) do
|
||||
anz_options = anz_options + 1
|
||||
variables_used = yl_speak_up.find_player_vars_in_text(variables_used, o.o_text_when_prerequisites_met)
|
||||
variables_used = yl_speak_up.find_player_vars_in_text(variables_used, o.o_text_when_prerequisites_not_met)
|
||||
if(o and o.o_prerequisites) then
|
||||
for p_id, p in pairs(o.o_prerequisites) do
|
||||
anz_preconditions = anz_preconditions + 1
|
||||
if(p and p.p_type and p.p_type == "state"
|
||||
and p.p_variable and p.p_variable ~= "") then
|
||||
variables_p[ p.p_variable ] = true
|
||||
variables_used[ p.p_variable ] = true
|
||||
end
|
||||
end
|
||||
end
|
||||
if(o and o.actions) then
|
||||
for a_id, a_data in pairs(o.actions) do
|
||||
anz_actions = anz_actions + 1
|
||||
-- actions can have alternate_text
|
||||
variables_used = yl_speak_up.find_player_vars_in_text(variables_used, a_data.alternate_text)
|
||||
end
|
||||
end
|
||||
if(o and o.o_results) then
|
||||
@ -441,8 +476,10 @@ yl_speak_up.update_stored_npc_data = function(n_id, dialog)
|
||||
anz_effects = anz_effects + 1
|
||||
if(r and r.r_type and r.r_type == "state"
|
||||
and r.r_variable and r.r_variable ~= "") then
|
||||
variables_e[ r.r_variable ] = true
|
||||
variables_used[ r.r_variable ] = true
|
||||
end
|
||||
-- effects can have alternate_text
|
||||
variables_used = yl_speak_up.find_player_vars_in_text(variables_used, r.alternate_text)
|
||||
end
|
||||
end
|
||||
end
|
||||
@ -478,17 +515,14 @@ yl_speak_up.update_stored_npc_data = function(n_id, dialog)
|
||||
|
||||
-- delete all old entries that are not longer needed
|
||||
for k, v in pairs(yl_speak_up.player_vars) do
|
||||
if(not(variables_p[ k ]) and not(variables_e[ k ])) then
|
||||
if(not(variables_used[ k ])) then
|
||||
yl_speak_up.set_variable_metadata(k, pname, "used_by_npc", n_id, false)
|
||||
end
|
||||
end
|
||||
|
||||
-- save in the variables' metadata which NPC uses it
|
||||
-- (this is what we're mostly after - know which variable is used in which NPC)
|
||||
for k, v in pairs(variables_p) do
|
||||
yl_speak_up.set_variable_metadata(k, pname, "used_by_npc", n_id, true)
|
||||
end
|
||||
for k, v in pairs(variables_e) do
|
||||
for k, v in pairs(variables_used) do
|
||||
yl_speak_up.set_variable_metadata(k, pname, "used_by_npc", n_id, true)
|
||||
end
|
||||
-- force writing the data
|
||||
@ -499,7 +533,7 @@ end
|
||||
-- which NPC do use this variable?
|
||||
yl_speak_up.get_variable_metadata = function(var_name, meta_name, get_as_is)
|
||||
-- var_type (the type of the variable) is a single string
|
||||
if(meta_name and var_name and meta_name == "var_type") then
|
||||
if(meta_name and var_name and (meta_name == "var_type" or meta_name == "default_value")) then
|
||||
if( not(yl_speak_up.player_vars[ var_name ])
|
||||
or not(yl_speak_up.player_vars[ var_name ][ "$META$"])) then
|
||||
return nil
|
||||
|
||||
@ -254,6 +254,15 @@ The replacements will not be applied in edit mode.
|
||||
|
||||
Servers can define [additional custom replacements](#add-simple-variables).
|
||||
|
||||
It is also possible to insert the *value* of variables into the text. Only variables the owner of the NPC has read access to can be replaced. Example: The variable "Example Variable Nr. 3" (without blanks would be a better name, i.e. "example\_variable\_nr\_3"), created by "exampleplayer", could be inserted by inserting the following text into dialog texts and options:
|
||||
$VAR exampleplayer Example Variable Nr. 3$
|
||||
It will be replaced by the *value* that variable has for the player that is talking to the NPC.
|
||||
|
||||
Properties can be replaced in a similar way. The value of the property "job" of the NPC for example could thus be shown:
|
||||
$PROP job$
|
||||
|
||||
Variables and properties can only be replaced if their *name* contains only alphanumeric signs (a-z, A-Z, 0-9), spaces, "\_", "-" and/or ".".
|
||||
|
||||
|
||||
### 1.8 Alternate Text
|
||||
<a name="alternate_text"></a>
|
||||
|
||||
@ -35,6 +35,13 @@ end
|
||||
-- show formspec with highest possible version information for the player
|
||||
-- force_version: optional parameter
|
||||
yl_speak_up.show_fs_ver = function(pname, formname, formspec, force_version)
|
||||
-- catch errors
|
||||
if(not(formspec)) then
|
||||
force_version = "1"
|
||||
formspec = "size[4,2]label[0,0;Error: No text found for form\n\""..
|
||||
minetest.formspec_escape(formname).."\"]"..
|
||||
"button_exit[1.5,1.5;1,0.5;exit;Exit]"
|
||||
end
|
||||
-- if the formspec already calls for a specific formspec version: use that one
|
||||
if(string.sub(formspec, 1, 17) == "formspec_version[") then
|
||||
minetest.show_formspec(pname, formname, formspec)
|
||||
|
||||
Loading…
Reference in New Issue
Block a user