859 lines
32 KiB
Lua
859 lines
32 KiB
Lua
--
|
|
-- 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
|