diff --git a/api/api_alternate_text.lua b/api/api_alternate_text.lua
new file mode 100644
index 0000000..be6b321
--- /dev/null
+++ b/api/api_alternate_text.lua
@@ -0,0 +1,434 @@
+-- helper function for yl_speak_up.handle_input_fs_edit_option_related
+-- (handle editing of alternate texts that are shown instead of the normal dialog)
+yl_speak_up.handle_edit_actions_alternate_text = function(
+ player, pname, n_id, d_id, o_id, x_id, id_prefix,
+ formspec_input_to, data, fields, tmp_data_cache)
+ local dialog = yl_speak_up.speak_to[pname].dialog
+ 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
+ -- edit_dialog_options: these first two buttons can only be pressed in this dialog
+ -- action failed: want to edit the text that is shown when switching to the next dialog?
+ if(fields.button_edit_action_failed_dialog) then
+ -- the target effect is the (failed) action
+ local target_action = {}
+ local actions = dialog.n_dialogs[ d_id ].d_options[ o_id ].actions
+ if(actions) then
+ for a_id, a in pairs(actions) do
+ if(a and a.a_id) then
+ target_action = a
+ end
+ end
+ end
+ if(not(target_action)) then
+ return
+ end
+ -- remember what we're working at
+ yl_speak_up.speak_to[pname].edit_alternate_text_for = target_action
+ yl_speak_up.show_fs(player, "msg", {
+ input_to = "yl_speak_up:"..formspec_input_to,
+ formspec = yl_speak_up.extend_fs_edit_dialog_modification(
+ dialog, target_action.a_on_failure, target_action.alternate_text,
+ "if the action \""..tostring(target_action.a_id)..
+ "\" of option \""..tostring(o_id)..
+ "\" of dialog \""..tostring(d_id)..
+ "\" failed because the player did something wrong")
+ })
+ -- showing new formspec - the calling function shall return as well
+ return true
+
+ -- action was successful: want to edit the text that is shown when switching to the next dialog?
+ elseif(fields.button_edit_action_success_dialog) then
+ -- the target effect is the "dialog" effect
+ local target_effect = {}
+ local results = dialog.n_dialogs[ d_id ].d_options[ o_id ].o_results
+ if(results) then
+ for r_id, r in pairs(results) do
+ if(r and r.r_type and r.r_type == "dialog") then
+ target_effect = r
+ end
+ end
+ end
+ if(not(target_effect)) then
+ return
+ end
+ -- remember what we're working at
+ yl_speak_up.speak_to[pname].edit_alternate_text_for = target_effect
+ -- this only happens in edit_options_dialog; log it directly
+ yl_speak_up.show_fs(player, "msg", {
+ input_to = "yl_speak_up:"..formspec_input_to,
+ formspec = yl_speak_up.extend_fs_edit_dialog_modification(
+ dialog, target_effect.r_value, target_effect.alternate_text,
+ "if the action "..
+ "of option \""..tostring(o_id)..
+ "\" of dialog \""..tostring(d_id)..
+ "\" was successful - or if there was no action")
+ })
+ -- showing new formspec - the calling function shall return as well
+ return true
+
+ -- in edit action dialog: edit alternate text for a failed action
+ elseif(fields.button_edit_action_on_failure_text_change) then
+ local sorted_dialog_list = yl_speak_up.sort_keys(dialog.n_dialogs)
+ local failure_id = ""
+ -- action is beeing edited; data.action_failure_dialog points to an index
+ if(data and data.action_failure_dialog) then
+ failure_id = sorted_dialog_list[ data.action_failure_dialog ]
+ end
+ -- remember what we edit
+ data.x_id = x_id
+ data.id_prefix = id_prefix
+ yl_speak_up.speak_to[pname].edit_alternate_text_for = data
+ yl_speak_up.show_fs(player, "msg", {
+ input_to = "yl_speak_up:"..formspec_input_to,
+ formspec = yl_speak_up.extend_fs_edit_dialog_modification(
+ dialog, failure_id, data.alternate_text,
+ "if the action \""..tostring(x_id)..
+ "\" of option \""..tostring(o_id)..
+ "\" of dialog \""..tostring(d_id)..
+ "\" failed because the player did something wrong")
+ })
+ -- showing new formspec - the calling function shall return as well
+ return true
+
+ -- edit alternate text for an on_failure effect
+ elseif(fields.button_edit_effect_on_failure_text_change) then
+ -- remember what we edit
+ data.x_id = x_id
+ data.id_prefix = id_prefix
+ yl_speak_up.speak_to[pname].edit_alternate_text_for = data
+ yl_speak_up.show_fs(player, "msg", {
+ input_to = "yl_speak_up:"..formspec_input_to,
+ formspec = yl_speak_up.extend_fs_edit_dialog_modification(
+ dialog, data.on_failure, data.alternate_text,
+ "if the effect \""..tostring(x_id)..
+ "\" of option \""..tostring(o_id)..
+ "\" of dialog \""..tostring(d_id)..
+ "\" failed to execute correctly")
+ })
+ -- showing new formspec - the calling function shall return as well
+ return true
+
+ -- edit alternate text for when the player has failed to do the action too many times
+ elseif(fields.button_edit_limit_action_failed_repeat) then
+ local timer_name = "timer_on_failure_"..tostring(d_id).."_"..tostring(o_id)
+ local timer_data = yl_speak_up.get_variable_metadata( timer_name, "parameter", true)
+ local alternate_text = yl_speak_up.standard_text_if_action_failed_too_often
+ if(timer_data and timer_data["alternate_text"]) then
+ alternate_text = timer_data["alternate_text"]
+ end
+ -- remember what we're working at
+ yl_speak_up.speak_to[pname].edit_alternate_text_for = "timer_on_failure"
+ yl_speak_up.show_fs(player, "msg", {
+ input_to = "yl_speak_up:"..formspec_input_to,
+ formspec = yl_speak_up.extend_fs_edit_dialog_modification(
+ dialog, d_id, alternate_text,
+ "if the player failed to complete the action "..
+ "\" of option \""..tostring(o_id)..
+ "\" of dialog \""..tostring(d_id)..
+ "\" too many times",
+ true) -- forbid_turn_into_new_dialog
+ })
+ -- showing new formspec - the calling function shall return as well
+ return true
+ -- edit alternate text whent he player has to wait a bit until he's allowed to repeat the
+ -- action (to avoid i.e. unlimited quest item handout)
+ elseif(fields.button_edit_limit_action_success_repeat) then
+ local timer_name = "timer_on_success_"..tostring(d_id).."_"..tostring(o_id)
+ local timer_data = yl_speak_up.get_variable_metadata( timer_name, "parameter", true)
+ local alternate_text = yl_speak_up.standard_text_if_action_repeated_too_soon
+ if(timer_data and timer_data["alternate_text"]) then
+ alternate_text = timer_data["alternate_text"]
+ end
+ -- remember what we're working at
+ yl_speak_up.speak_to[pname].edit_alternate_text_for = "timer_on_success"
+ yl_speak_up.show_fs(player, "msg", {
+ input_to = "yl_speak_up:"..formspec_input_to,
+ formspec = yl_speak_up.extend_fs_edit_dialog_modification(
+ dialog, d_id, alternate_text,
+ "if the player has complete the action "..
+ "\" of option \""..tostring(o_id)..
+ "\" of dialog \""..tostring(d_id)..
+ "\" just not long enough ago",
+ true) -- forbid_turn_into_new_dialog
+ })
+ -- showing new formspec - the calling function shall return as well
+ return true
+
+ -- save the changes
+ elseif(fields.save_dialog_modification) then
+ local old_text = "-none-"
+ local target_element = yl_speak_up.speak_to[pname].edit_alternate_text_for
+ if(target_element
+ and (target_element == "timer_on_failure" or target_element == "timer_on_success")) then
+ -- we're changing a timer (both can be handled the same way here)
+ local timer_name = target_element.."_"..tostring(d_id).."_"..tostring(o_id)
+ local timer_data = yl_speak_up.get_variable_metadata( timer_name, "parameter", true)
+ local alternate_text = yl_speak_up.standard_text_if_action_failed_too_often
+ if(target_element == "timer_on_success") then
+ alternate_text = yl_speak_up.standard_text_if_action_repeated_too_soon
+ end
+ if(timer_data and timer_data["alternate_text"]) then
+ alternate_text = timer_data["alternate_text"]
+ end
+ -- store the modified alternate text
+ if(fields.d_text_new and fields.d_text_new ~= ""
+ and fields.d_text_new ~= alternate_text) then
+ -- make sure the variable exists
+ if(yl_speak_up.add_time_based_variable(timer_name)) then
+ yl_speak_up.set_variable_metadata(timer_name, nil, "parameter",
+ "alternate_text", fields.d_text_new)
+ -- log the change
+ yl_speak_up.log_change(pname, n_id,
+ "Dialog "..d_id..", option "..tostring(o_id)..
+ ": The text displayed for "..tostring(target_element)..
+ " was changed from "..
+ "["..tostring(alternate_text).."] to ["..
+ tostring(fields.d_text_new).."].")
+ end
+ end
+ elseif(target_element) then
+ data = target_element
+ id_prefix = "a_"
+ if(target_element.r_id) then
+ id_prefix = "r_"
+ end
+ old_text = target_element.alternate_text
+ else
+ old_text = data.alternate_text
+ end
+ if(data and fields.d_text_new and fields.d_text_new ~= "$TEXT$"
+ and fields.d_text_new ~= data.alternate_text) then
+ -- store modification
+ -- not necessary for edit_option_dialog
+ if(tmp_data_cache) then
+ data.alternate_text = fields.d_text_new
+ yl_speak_up.speak_to[pname][ tmp_data_cache ] = data
+ else
+ target_element.alternate_text = fields.d_text_new
+ end
+ if(id_prefix == "r_") then
+ local failure_id = data.on_failure
+ -- effect is beeing edited; data.on_failure contains the dialog name
+ if(data and data.on_failure) then
+ failure_id = data.on_failure
+ -- edit_option_dialog: data.r_value contains the dialog name
+ elseif(target_element and target_element.r_value) then
+ failure_id = target_element.r_value
+ end
+ -- record the change
+ table.insert(yl_speak_up.npc_was_changed[ n_id ],
+ "Dialog "..d_id..": The text displayed for dialog "..
+ tostring(failure_id).." when selecting option "..
+ tostring(o_id).." in dialog "..tostring( d_id )..
+ " and effect "..tostring(x_id).." failed "..
+ " was changed from "..
+ "["..tostring(old_text).."] to ["..tostring(fields.d_text_new).."].")
+ elseif(id_prefix == "a_") then
+ local sorted_dialog_list = yl_speak_up.sort_keys(dialog.n_dialogs)
+ local failure_id = ""
+ -- action is beeing edited; data.action_failure_dialog points to an index
+ if(data and data.action_failure_dialog) then
+ failure_id = sorted_dialog_list[ data.action_failure_dialog ]
+ -- edit_option_dialog: data.a_on_failure contains the dialog name
+ elseif(target_element and target_element.a_on_failure) then
+ failure_id = target_element.a_on_failure
+ end
+ -- record the change
+ table.insert(yl_speak_up.npc_was_changed[ n_id ],
+ "Dialog "..d_id..": The text displayed for dialog "..
+ tostring(failure_id).." when the action "..
+ tostring(x_id).." of option "..
+ tostring( o_id ).." in dialog "..tostring( d_id )..
+ " failed, was changed from "..
+ "["..tostring(old_text).."] to ["..tostring(fields.d_text_new).."].")
+ end
+ -- saved; finished editing
+ yl_speak_up.speak_to[pname].edit_alternate_text_for = nil
+ end
+ -- turn this alternate answer into a new dialog
+ elseif(fields.turn_alternate_text_into_new_dialog) then
+ local target_element = yl_speak_up.speak_to[pname].edit_alternate_text_for
+ if(target_element) then
+ data = target_element
+ if(data.id_prefix and data.x_id) then
+ id_prefix = data.id_prefix
+ x_id = data.x_id
+ else
+ id_prefix = "a_"
+ x_id = target_element.a_id
+ if(target_element.r_id) then
+ id_prefix = "r_"
+ x_id = target_element.r_id
+ end
+ end
+ end
+ -- create the new dialog
+ local new_dialog_id = yl_speak_up.add_new_dialog(dialog, pname, nil)
+ -- set the text (the previous alternate text)
+ dialog.n_dialogs[ new_dialog_id ].d_text = data.alternate_text
+ -- edit option: effect dialog - this is the normal progression from this dialog to the next
+ if( data.r_id and data.r_type and data.r_type == "dialog") then
+ data.r_value = new_dialog_id
+ data.alternate_text = nil
+ table.insert(yl_speak_up.npc_was_changed[ n_id ],
+ "Dialog "..d_id..": The alternate text for effect "..tostring(x_id)..
+ " (dialog) of option "..tostring(o_id).." was turned into the new dialog "..
+ tostring(new_dialog_id).." (edit option).")
+
+ -- edit option: the action failed
+ elseif(data.a_id and data.a_on_failure) then
+ data.a_on_failure = new_dialog_id
+ data.alternate_text = nil
+ table.insert(yl_speak_up.npc_was_changed[ n_id ],
+ "Dialog "..d_id..": The alternate text for action "..tostring(data.a_id)..
+ " of option "..tostring(o_id).." was turned into the new dialog "..
+ tostring(new_dialog_id).." (edit option).")
+
+ -- edit action: the action failed
+ elseif(data.what and data.what == 6 and data.action_failure_dialog) then
+ local sorted_dialog_list = yl_speak_up.sort_keys(dialog.n_dialogs)
+ data.action_failure_dialog = math.max(1,
+ table.indexof(sorted_dialog_list, new_dialog_id))
+ data.a_on_failure = new_dialog_id
+ data.alternate_text = nil
+ -- make sure its stored correctly
+ dialog.n_dialogs[d_id].d_options[o_id].actions[x_id].a_on_failure = new_dialog_id
+ dialog.n_dialogs[d_id].d_options[o_id].actions[x_id].alternate_text = nil
+ yl_speak_up.speak_to[pname][ tmp_data_cache ] = data
+ table.insert(yl_speak_up.npc_was_changed[ n_id ],
+ "Dialog "..d_id..": The alternate text for action "..tostring(x_id)..
+ " of option "..tostring(o_id).." was turned into the new dialog "..
+ tostring(new_dialog_id).." (edit action).")
+
+ -- edit effect: on_failure - the previous effect failed
+ elseif(data.what and data.what == 5 and data.on_failure) then
+ data.on_failure = new_dialog_id
+ data.alternate_text = nil
+ -- make sure its stored correctly
+ dialog.n_dialogs[d_id].d_options[o_id].o_results[x_id].on_failure = new_dialog_id
+ dialog.n_dialogs[d_id].d_options[o_id].o_results[x_id].alternate_text = nil
+ yl_speak_up.speak_to[pname][ tmp_data_cache ] = data
+ table.insert(yl_speak_up.npc_was_changed[ n_id ],
+ "Dialog "..d_id..": The alternate text for effect "..tostring(x_id)..
+ " of option "..tostring(o_id).." was turned into the new dialog "..
+ tostring(new_dialog_id).." (edit effect).")
+ end
+ end
+end
+
+
+yl_speak_up.show_colored_dialog_text = function(dialog, data, d_id, hypertext_pos,
+ alternate_label_text, postfix, button_name)
+ if(not(data)) then
+ return ""
+ end
+ -- if(math.random(1,2)==1) then data.alternate_text = "This is an alternate text.\n$TEXT$" end
+ -- slightly red in order to indicate that this is an on_failure dialog
+ local color = "776666"
+ -- ..except for normal redirecting to the next dialog with the dialog effect
+ -- (slightly yellow there)
+ if(data.r_id and data.r_type and data.r_type == "dialog") then
+ color = "777766"
+ end
+ local add_info_alternate_text = ""
+ local text = ""
+ if(dialog and dialog.n_dialogs and dialog.n_dialogs[ d_id ]) then
+ text = dialog.n_dialogs[ d_id ].d_text
+ end
+ if(d_id == "d_got_item") then
+ color = "777777"
+ text = "[This dialog shall only have automatic options. The text is therefore irrelevant.]"
+ end
+ if(d_id == "d_end") then
+ color = "777777"
+ text = "[The NPC will end this conversation.]"
+ end
+ if(not(text)) then
+ text = "[ERROR: No text!]"
+ end
+ if(data and data.alternate_text and data.alternate_text ~= "") then
+ add_info_alternate_text = alternate_label_text
+ -- replace $TEXT$ with the normal dialog text and make the new text yellow
+ text = "")..
+ ""
+ -- slightly blue in order to indicate that this is a modified text
+ color = "333366"
+ end
+ -- fallback
+ if(not(text)) then
+ text = "ERROR: No dialog text found for dialog \""..tostring(d_id).."\"!"
+ end
+ -- display the variables in orange
+ text = yl_speak_up.replace_vars_in_text(text,
+ -- fake dialog; just adds the colors
+ -- also MY_NAME..but we can easily replace just one
+ { n_npc = "",
+ npc_owner = ""},
+ -- pname
+ "")
+
+ local edit_button = ""
+ -- if there is the possibility that an alternate text may be displayed: allow to edit it
+ -- and calculate the position of the button from the hypertext_pos position and size
+ if(button_name and button_name ~= "") then
+ local parts = string.split(hypertext_pos, ";")
+ local start = string.split(parts[1], ",")
+ local size = string.split(parts[2], ",")
+ edit_button = "button_exit["..
+ tostring(tonumber(start[1]) + tonumber(size[1]) - 3.5)..","..
+ tostring(tonumber(start[2]) + tonumber(size[2]) - 0.9)..";"..
+ "3.0,0.7;"..button_name..";Edit this text]"
+ end
+ return add_info_alternate_text..
+ postfix..
+ "hypertext["..hypertext_pos..";"..
+ minetest.formspec_escape(text or "?")..
+ "\n]"..
+ -- display the edit button *inside*/on top of the hypertext field
+ edit_button
+end
+
+-- this allows to edit modifications of a dialog that are applied when a given option
+-- is choosen - i.e. when the NPC wants to answer some questions - but those answers
+-- do not warrant their own dialog
+yl_speak_up.extend_fs_edit_dialog_modification = function(dialog, d_id, alternate_dialog_text, explanation,
+ forbid_turn_into_new_dialog)
+
+ local nd = "button[9.0,12.3;6,0.7;turn_alternate_text_into_new_dialog;Turn this into a new dialog]"
+ if(forbid_turn_into_new_dialog) then
+ nd = ""
+ end
+ return table.concat({"size[20,13.5]",
+ "label[6.0,0.5;Edit alternate text]",
+ "label[0.2,1.0;The alternate text which you can edit here will be shown instead of "..
+ "the normal text of the dialog \"", tostring(d_id), "\" - but *only*\n",
+ tostring(explanation or "- missing explanation -"), ".]",
+ "label[0.2,2.3;This is the normal text of dialog \"",
+ minetest.formspec_escape(tostring(d_id)), "\", shown for reference:]",
+ yl_speak_up.show_colored_dialog_text(
+ dialog,
+ {r_id = "", r_type = "dialog"},
+ d_id,
+ "1.2,2.6;18.0,4.0;d_text_orig",
+ "", -- no modifications possible at this step
+ "",
+ ""), -- no edit button here as this text cannot be changed here
+ "label[0.2,7.3;Enter the alternate text here. $TEXT$ will be replaced with the normal "..
+ "dialog text above:]",
+ "textarea[1.2,7.6;18.0,4.5;d_text_new;;",
+ minetest.formspec_escape(alternate_dialog_text or "$TEXT$"), "]",
+ "button[3.0,12.3;1,0.7;back_from_edit_dialog_modification;Abort]",
+ "button[6.0,12.3;1,0.7;save_dialog_modification;Save]",
+ nd
+ }, "")
+end
+
diff --git a/api/api_quest_steps.lua b/api/api_quest_steps.lua
new file mode 100644
index 0000000..4290725
--- /dev/null
+++ b/api/api_quest_steps.lua
@@ -0,0 +1,270 @@
+
+-- returns a table with helpful information *if* the player is working on a quest;
+-- else error_msg is set
+yl_speak_up.player_is_working_on_quest = function(player)
+ if(not(player)) then
+ return
+ end
+ local t = {}
+ t.pname = player:get_player_name()
+ if(not(t.pname)) then
+ return {error_msg = "Player not found."}
+ end
+ if(not(yl_speak_up.speak_to or not(yl_speak_up.speak_to[t.pname]))) then
+ return {error_msg = "Player not working on a quest."}
+ end
+ t.q_id = yl_speak_up.speak_to[t.pname].q_id
+ if(not(t.q_id) or not(yl_speak_up.quests) or not(yl_speak_up.quests[t.q_id])) then
+ return {error_msg = "No quest selected or quest not found."}
+ end
+ t.quest = yl_speak_up.quests[t.q_id]
+ if(not(t.quest.step_data) or type(t.quest.step_data) ~= "table") then
+ yl_speak_up.quests[t.q_id].step_data = {}
+ end
+ -- TODO: check if the player has access to that data
+ t.step_data = yl_speak_up.quests[t.q_id].step_data
+ t.current_step = yl_speak_up.speak_to[t.pname].quest_step
+ -- check here if the step exists
+ if(t.current_step and not(t.step_data[t.current_step])) then
+ yl_speak_up.speak_to[t.pname].quest_step = nil
+ t.current_step = nil
+ end
+ -- t contains pname, q_id, quest, step_data and current_step - or error_msg
+ return t
+end
+
+
+-- show the error message created above
+yl_speak_up.build_fs_quest_edit_error = function(error_msg, back_button_name)
+ return "size[10,3]"..
+ "label[0.2,0.5;Error:]"..
+ "label[0.5,1.0;"..minetest.colorize("#FFFF00",
+ minetest.formspec_escape(
+ minetest.wrap_text(tostring(error_msg), 80)))..
+ "]button[3.5,2.0;2,0.9;"..tostring(back_button_name)..";Back]"
+end
+
+
+-- for which other quest steps is this_step needed for?
+yl_speak_up.quest_step_required_for = function(step_data, this_step)
+ -- find out the next quest step
+ local required_for = {}
+ for s, d in pairs(step_data) do
+ if(s and d and d.one_step_required and type(d.one_step_required) == "table"
+ and table.indexof(d.one_step_required, this_step) ~= -1) then
+ table.insert(required_for, s)
+ end
+ if(s and d and d.all_steps_required and type(d.all_steps_required) == "table"
+ and table.indexof(d.all_steps_required, this_step) ~= -1) then
+ table.insert(required_for, s)
+ end
+ end
+ table.sort(required_for)
+ return required_for
+end
+
+
+-- sorts quest steps into lists: start, middle, end, unconnected
+yl_speak_up.quest_step_get_start_end_unconnected_lists = function(step_data)
+ local start_steps = {}
+ local end_steps = {}
+ local unconnected_steps = {}
+ -- construct tables of *candidates* for start/end steps first
+ for s, d in pairs(step_data) do
+ if(#d.one_step_required == 0 and #d.all_steps_required == 0) then
+ start_steps[s] = true
+ end
+ end_steps[s] = true
+ end
+ for s, d in pairs(step_data) do
+ -- anything that is required somewhere cannot be an end step
+ for i, s2 in ipairs(d.one_step_required or {}) do
+ end_steps[s2] = nil
+ end
+ for i, s2 in ipairs(d.all_steps_required or {}) do
+ end_steps[s2] = nil
+ end
+ end
+ local lists = {}
+ lists.start_steps = {}
+ lists.end_steps = {}
+ lists.unconnected_steps = {}
+ lists.middle_steps = {}
+ for s, d in pairs(step_data) do
+ -- if it's both a start and end step, then it's an unconnected step
+ if(start_steps[s] and end_steps[s]) then
+ table.insert(lists.unconnected_steps, s)
+ elseif(start_steps[s]) then
+ table.insert(lists.start_steps, s)
+ elseif(end_steps[s]) then
+ table.insert(lists.end_steps, s)
+ else
+ table.insert(lists.middle_steps, s)
+ end
+ end
+ return lists
+end
+
+
+-- some lists are offered in diffrent formspecs for selection;
+-- this function will display the right quest step if possible
+-- res needs to be yl_speak_up.player_is_working_on_quest(player)
+yl_speak_up.handle_input_routing_show_a_quest_step = function(player, formname, fields, back_field_name, res)
+ if(not(player) or not(fields) or (fields and fields.back) or not(res)) then
+ return false
+ end
+
+ if(res.error_msg) then
+ yl_speak_up.show_fs(player, "msg", {
+ input_to = formname,
+ formspec = yl_speak_up.build_fs_quest_edit_error(error_msg, back_field_name)
+ })
+ return true
+ end
+ local step_data = res.step_data or {}
+
+ -- which quest step to show next? (if any)
+ local show_step = ""
+ -- was a quest step selected from the start/end/unconnected lists?
+ local list = {}
+ local field_name = ""
+ local row_offset = 0
+ if( fields.select_from_start_steps and fields.select_from_start_steps ~= "") then
+ -- selected a start quest step
+ list = yl_speak_up.quest_step_get_start_end_unconnected_lists(step_data).start_steps
+ field_name = "select_from_start_steps"
+ elseif(fields.select_from_end_steps and fields.select_from_end_steps ~= "") then
+ -- selected an end quest step
+ list = yl_speak_up.quest_step_get_start_end_unconnected_lists(step_data).end_steps
+ field_name = "select_from_end_steps"
+ elseif(fields.select_from_unconnected_steps and fields.select_from_unconnected_steps ~= "") then
+ -- selected an unconnected/unused quest step
+ list = yl_speak_up.quest_step_get_start_end_unconnected_lists(step_data).unconnected_steps
+ field_name = "select_from_unconnected_steps"
+ elseif(res.current_step and step_data[res.current_step] and fields.one_step_required) then
+ list = step_data[res.current_step].one_step_required
+ field_name = "one_step_required"
+ elseif(res.current_step and step_data[res.current_step] and fields.all_steps_required) then
+ list = step_data[res.current_step].all_steps_required
+ field_name = "all_steps_required"
+ elseif(res.current_step and step_data[res.current_step] and fields.next_steps_show) then
+ list = yl_speak_up.quest_step_required_for(step_data, res.current_step)
+ field_name = "next_steps_show"
+ elseif(fields.add_from_available) then
+ -- selected a quest step from the list of available steps offered
+ list = yl_speak_up.speak_to[res.pname].available_quest_steps or {}
+ field_name = "add_from_available"
+ -- this table has a header
+ row_offset = 1
+
+ -- show prev logical step
+ elseif(fields.show_prev_step and res.current_step and step_data[res.current_step]) then
+ if( #step_data[res.current_step].one_step_required > 0) then
+ show_step = step_data[res.current_step].one_step_required[1]
+ elseif(#step_data[res.current_step].all_steps_required > 0) then
+ show_step = step_data[res.current_step].all_steps_required[1]
+ end
+ -- show next logical step
+ elseif(fields.show_next_step) then
+ local list = yl_speak_up.quest_step_required_for(res.step_data, res.current_step)
+ if(list and #list > 0) then
+ show_step = list[1]
+ end
+ end
+
+ if(list and field_name) then
+ local selected = minetest.explode_table_event(fields[field_name])
+ -- if a table uses a header, row_offset will be 1; else 0
+ if(selected and selected.row and selected.row > row_offset and selected.row <= #list + row_offset) then
+ show_step = list[selected.row - row_offset]
+ end
+ end
+ -- actually show the selected quest step
+ if(show_step and show_step ~= "") then
+ yl_speak_up.speak_to[res.pname].quest_step = show_step
+ yl_speak_up.show_fs(player, "manage_quest_steps", show_step)
+ return true
+ -- show the entire list
+ elseif(fields.show_step_list) then
+ yl_speak_up.speak_to[res.pname].tmp_index_general = -1
+ yl_speak_up.speak_to[res.pname].quest_step = nil
+ yl_speak_up.show_fs(player, "manage_quest_steps", nil)
+ return true
+ end
+ return false
+end
+
+
+-- describe a location where a quest step can be set; also used by yl_speak_up.fs_manage_quest_steps
+yl_speak_up.quest_step_show_where_set = function(pname, formspec, label, n_id, d_id, o_id, box_color, nr)
+ if(not(pname)) then
+ return
+ end
+ -- what are we talking about?
+ local dialog = nil
+ if(yl_speak_up.speak_to[pname] and yl_speak_up.speak_to[pname].n_id == n_id) then
+ dialog = yl_speak_up.speak_to[pname].dialog
+ else
+ dialog = yl_speak_up.load_dialog(n_id, false)
+ end
+ local name_txt = "- ? -"
+ local dialog_txt = "- ? -"
+ local option_txt = "- ? -"
+ if(yl_speak_up.check_if_dialog_has_option(dialog, d_id, o_id)) then
+ dialog_txt = dialog.n_dialogs[d_id].d_text or "- ? -"
+ option_txt = dialog.n_dialogs[d_id].d_options[o_id].o_text_when_prerequisites_met or "- ? -"
+ name_txt = (dialog.n_npc or "- ? -")
+ if(dialog.n_description and dialog.n_description ~= "") then
+ name_txt = name_txt..", "..tostring(dialog.n_description)
+ end
+ end
+ -- are we dealing with an NPC?
+ local id_label = "the block at position "
+ if(n_id and string.sub(n_id, 1, 2) == "n_") then
+ id_label = "NPC "
+ end
+
+ if(box_color) then
+ name_txt = name_txt.." ["..tostring(n_id).."]"
+ table.insert(formspec, "label[0.2,0.2;")
+ if(nr) then
+ table.insert(formspec, tostring(nr)..". ")
+ end
+ table.insert(formspec, id_label)
+ table.insert(formspec, minetest.colorize("#AAAAFF", minetest.formspec_escape(name_txt)))
+ table.insert(formspec, " says in dialog ")
+ table.insert(formspec, minetest.colorize("#AAAAFF", minetest.formspec_escape(d_id)..":]"))
+ table.insert(formspec, "]")
+ table.insert(formspec, "box[1.0,0.4;16,1.8;")
+ table.insert(formspec, box_color)
+ table.insert(formspec, "]")
+ table.insert(formspec, "textarea[1.0,0.4;16,1.8;;;")
+ table.insert(formspec, minetest.formspec_escape(dialog_txt))
+ table.insert(formspec, "]")
+ table.insert(formspec, "label[1.0,2.4;Answer ")
+ table.insert(formspec, minetest.colorize("#AAAAFF", minetest.formspec_escape(o_id..": ")))
+ table.insert(formspec, minetest.colorize("#AAAAFF", minetest.formspec_escape(option_txt)))
+ table.insert(formspec, "]")
+ return
+ end
+ table.insert(formspec, "label[0.2,0;")
+ table.insert(formspec, label or "which will be set by ")
+ table.insert(formspec, id_label)
+ table.insert(formspec, minetest.colorize("#AAAAFF", minetest.formspec_escape(n_id)))
+ table.insert(formspec, ":]")
+ table.insert(formspec, "label[1.0,0.4;")
+ table.insert(formspec, minetest.colorize("#AAAAFF", minetest.formspec_escape(name_txt)))
+ table.insert(formspec, "]")
+ table.insert(formspec, "label[0.2,0.9;when answering to dialog ")
+ table.insert(formspec, minetest.colorize("#AAAAFF", minetest.formspec_escape(d_id)))
+ table.insert(formspec, ":]")
+ table.insert(formspec, "textarea[1.0,1.1;16,1.8;;;")
+ table.insert(formspec, minetest.formspec_escape(dialog_txt))
+ table.insert(formspec, "]")
+ table.insert(formspec, "label[0.2,3.2;with the following answer/option ")
+ table.insert(formspec, minetest.colorize("#AAAAFF", minetest.formspec_escape(o_id)))
+ table.insert(formspec, ":]")
+ table.insert(formspec, "label[1.0,3.6;")
+ table.insert(formspec, minetest.colorize("#AAAAFF", minetest.formspec_escape(option_txt)))
+ table.insert(formspec, "]")
+end
diff --git a/api/formspec_helpers.lua b/api/formspec_helpers.lua
new file mode 100644
index 0000000..c659645
--- /dev/null
+++ b/api/formspec_helpers.lua
@@ -0,0 +1,318 @@
+
+-- helper function:
+-- create a formspec dropdown list with player names (first entry: Add player) and
+-- an option to delete players from that list
+-- Note: With the what_is_the_list_about-parameter, it is possible to handle i.e. variables as well
+yl_speak_up.create_dropdown_playerlist = function(player, pname,
+ table_of_names, index_selected,
+ start_x, start_y, stretch_x, h, dropdown_name, what_is_the_list_about, delete_button_text,
+ field_name_for_adding_player, explain_add_player,
+ field_name_for_deleting_player, explain_delete_player)
+
+ local text = "dropdown["..tostring(start_x)..","..tostring(start_y)..";"..
+ tostring(3.8 + stretch_x)..","..tostring(h)..";"..
+ tostring(dropdown_name)..";Add "..tostring(what_is_the_list_about)..":"
+ -- table_of_names is a table with the playernames as keys
+ -- we want to work with indices later on; in order to be able to do that reliably, we
+ -- need a defined order of names
+ local tmp_list = yl_speak_up.sort_keys(table_of_names, true)
+ for i, p in ipairs(tmp_list) do
+ text = text..","..minetest.formspec_escape(p)
+ end
+ -- has an entry been selected?
+ if(not(index_selected) or index_selected < 0 or index_selected > #tmp_list+1) then
+ index_selected = 1
+ end
+ text = text..";"..tostring(index_selected)..";]"
+ if(index_selected == 1) then
+ -- first index "Add player" selected? Then offer a field for entering the name
+ text = text.."field["..tostring(start_x + 4.0 + stretch_x)..","..tostring(start_y)..
+ ";"..tostring(3.5 + stretch_x)..","..tostring(h)..";"..
+ tostring(field_name_for_adding_player)..";;]"..
+ "tooltip["..tostring(field_name_for_adding_player)..";"..
+ tostring(explain_add_player).."]"
+ else
+ text = text.."button["..tostring(start_x + 3.8 + stretch_x)..","..tostring(start_y)..
+ ";"..tostring(3.4 + stretch_x)..","..tostring(h)..";"..
+ tostring(field_name_for_deleting_player)..";"..
+ tostring(delete_button_text).."]"..
+ "tooltip["..tostring(field_name_for_deleting_player)..";"..
+ tostring(explain_delete_player).."]"
+ end
+ return text
+end
+
+
+
+-- manages back, exit, prev, next, add_list_entry, del_entry_general
+--
+-- if a new entry is to be added, the following function that is passed as a parmeter
+-- is called:
+-- function_add_new_entry(pname, fields.add_entry_general)
+-- expected return value: index of fields.add_entry_general in the new list
+--
+-- if an entry is to be deleted, the following function that is passed as a parameter
+-- is called:
+-- function_del_old_entry(pname, entry_name)
+-- expected return value: text describing weather the removal worked or not
+--
+-- if any other fields are set that this function does not process, the following
+-- function that is passed on as a parameter can be used:
+-- function_input_check_fields(player, formname, fields, entry_name, list_of_entries)
+-- expected return value: nil if the function found work; else entry_name
+--
+yl_speak_up.handle_input_fs_manage_general = function(player, formname, fields,
+ what_is_the_list_about, min_length, max_length, function_add_new_entry,
+ list_of_entries, function_del_old_entry, function_input_check_fields)
+ local pname = player:get_player_name()
+ local what = minetest.formspec_escape(what_is_the_list_about or "?")
+ local fs_name = formname
+ if(formname and string.sub(formname, 0, 12) == "yl_speak_up:") then
+ formname = string.sub(formname, 13)
+ end
+ if(fields and fields.back_from_msg) then
+ yl_speak_up.show_fs(player, formname, fields.stored_value_for_player)
+ return
+ end
+ -- leave this formspec
+ if(fields and (fields.quit or fields.exit or fields.back)) then
+ local last_fs = yl_speak_up.speak_to[pname][ "working_at" ]
+ local last_params = yl_speak_up.speak_to[pname][ "working_at_params" ]
+ yl_speak_up.speak_to[pname].tmp_index_general = nil
+ yl_speak_up.show_fs(player, last_fs, last_params)
+ return
+ -- add a new entry?
+ elseif(fields and fields.add_list_entry) then
+ local error_msg = ""
+ if(not(fields.add_entry_general) or fields.add_entry_general == ""
+ or fields.add_entry_general:trim() == "") then
+ error_msg = "Please enter the name of the "..what.." you want to create!"
+ -- limit names to something more sensible
+ elseif(string.len(fields.add_entry_general) > max_length) then
+ error_msg = "The name of your new "..what.." is too long.\n"..
+ "Only up to "..tostring(max_length).." characters are allowed."
+ elseif(string.len(fields.add_entry_general:trim()) < min_length) then
+ error_msg = "The name of your new "..what.." is too short.\n"..
+ "It has to be at least "..tostring(min_length).." characters long."
+ elseif(table.indexof(list_of_entries, fields.add_entry_general:trim()) > 0) then
+ error_msg = "A "..what.." with the name\n \""..
+ minetest.formspec_escape(fields.add_entry_general:trim())..
+ "\"\nexists already."
+ else
+ fields.add_entry_general = fields.add_entry_general:trim()
+ -- this depends on what is created
+ local res = function_add_new_entry(pname, fields.add_entry_general)
+ -- not really an error msg here - but fascilitates output
+ error_msg = "A new "..what.." named\n \""..
+ minetest.formspec_escape(fields.add_entry_general)..
+ "\"\nhas been created."
+ if(not(res) or (type(res) == "number" and res == -1)) then
+ error_msg = "Failed to create "..what.." named\n \""..
+ minetest.formspec_escape(fields.add_entry_general).."\"."
+ -- pass on any error messages
+ elseif(type(res) == "string") then
+ error_msg = res
+ else
+ -- select this new entry (add 1 because the first entry of our
+ -- list is adding a new entry)
+ yl_speak_up.speak_to[pname].tmp_index_general = res + 1
+ end
+ end
+ yl_speak_up.show_fs(player, "msg", {
+ input_to = "yl_speak_up:"..formname,
+ formspec = "size[10,2]"..
+ "label[0.2,0.0;"..error_msg.."]"..
+ "button[1.5,1.5;2,0.9;back_from_msg;Back]"})
+ return
+ -- scroll through the variables with prev/next buttons
+ elseif(fields and (fields["prev"] or fields["next"])) then
+ local index = yl_speak_up.speak_to[pname].tmp_index_general
+ if(not(index)) then
+ yl_speak_up.speak_to[pname].tmp_index_general = 1
+ elseif(fields["prev"] and index > 1) then
+ yl_speak_up.speak_to[pname].tmp_index_general = index - 1
+ elseif(fields["next"] and index <= #list_of_entries) then
+ yl_speak_up.speak_to[pname].tmp_index_general = index + 1
+ end
+ yl_speak_up.show_fs(player, formname, fields.stored_value_for_player)
+ return
+ end
+
+ -- an entry was selected in the dropdown list
+ if(fields and fields.list_of_entries and fields.list_of_entries ~= "") then
+ local index = table.indexof(list_of_entries, fields.list_of_entries)
+ -- show the "Add :" entry
+ if(fields.list_of_entries == "Add "..what..":") then
+ index = 0
+ yl_speak_up.speak_to[pname].tmp_index_general = 1
+ yl_speak_up.show_fs(player, formname, fields.stored_value_for_player)
+ return
+ end
+ if(index and index > -1) then
+ yl_speak_up.speak_to[pname].tmp_index_general = index + 1
+ end
+ end
+ local entry_name = list_of_entries[ yl_speak_up.speak_to[pname].tmp_index_general - 1]
+
+ -- delete entry
+ if(fields and ((fields.del_entry_general and fields.del_entry_general ~= ""))
+ and entry_name and entry_name ~= "") then
+ local text = function_del_old_entry(pname, entry_name)
+ yl_speak_up.show_fs(player, "msg", {
+ input_to = "yl_speak_up:"..formname,
+ formspec = "size[10,2]"..
+ "label[0.2,0.0;Trying to delete "..what.." \""..
+ minetest.formspec_escape(tostring(entry_name))..
+ "\":\n"..text.."]"..
+ "button[1.5,1.5;2,0.9;back_from_msg;Back]"})
+ return
+
+ -- maybe the custom function knows what to do with this
+ elseif(fields
+ and not(function_input_check_fields(player, formname, fields, entry_name, list_of_entries))) then
+ -- the function_input_check_fields managed to handle this input
+ return
+
+ -- an entry was selected in the dropdown list
+ elseif(entry_name and entry_name ~= "") then
+ -- show the same formspec again, with a diffrent variable selected
+ yl_speak_up.show_fs(player, formname)
+ return
+ end
+ -- try to go back to the last formspec shown before this one
+ if(not(yl_speak_up.speak_to[pname])) then
+ return
+ end
+ local last_fs = yl_speak_up.speak_to[pname][ "working_at" ]
+ local last_params = yl_speak_up.speak_to[pname][ "working_at_params" ]
+ yl_speak_up.show_fs(player, last_fs, last_params)
+end
+
+
+-- inserts buttons into formspec which allow to select previous/next entry, to go back,
+-- create new entries, delete entries and select entries from a dropdown menu;
+-- returns the currently selected entry or nil (=create new entry)
+-- Note: Designed for a formspec of size "size[18,12]"
+yl_speak_up.build_fs_manage_general = function(player, param,
+ formspec, list_of_entries,
+ text_add_new, tooltip_add_new,
+ what_is_the_list_about,
+ tooltip_add_entry_general, tooltip_del_entry_general,
+ optional_add_space)
+ if(not(optional_add_space)) then
+ optional_add_space = 0
+ end
+ local selected = nil
+ local pname = player:get_player_name()
+ -- the yl_speak_up.create_dropdown_playerlist function needs a table - not a list
+ local table_of_entries = {}
+ for i, k in ipairs(list_of_entries) do
+ table_of_entries[ k ] = true
+ end
+ -- "Add variable:" is currently selected
+ if(not(yl_speak_up.speak_to[pname].tmp_index_general)
+ or yl_speak_up.speak_to[pname].tmp_index_general == 1
+ or not(list_of_entries[ yl_speak_up.speak_to[pname].tmp_index_general - 1])) then
+ yl_speak_up.speak_to[pname].tmp_index_general = 1
+ table.insert(formspec, "button[")
+ table.insert(formspec, tostring(12.2 + 2 * optional_add_space))
+ table.insert(formspec, ",2.15;")
+ table.insert(formspec, tostring(2.5 + 2 * optional_add_space))
+ table.insert(formspec, ",0.6;add_list_entry;")
+ table.insert(formspec, minetest.formspec_escape(text_add_new))
+ table.insert(formspec, "]")
+ table.insert(formspec, "tooltip[add_list_entry;")
+ table.insert(formspec, minetest.formspec_escape(tooltip_add_new))
+ table.insert(formspec, "]")
+ else
+ -- index 1 is "Add variable:"
+ selected = list_of_entries[ yl_speak_up.speak_to[pname].tmp_index_general - 1]
+ end
+ if(yl_speak_up.speak_to[pname].tmp_index_general > 1) then
+ table.insert(formspec, "button[4.0,0.2;2.0,0.6;prev;< Prev]"..
+ "button[4.0,11.0;2.0,0.6;prev;< Prev]")
+ end
+ if(yl_speak_up.speak_to[pname].tmp_index_general <= #list_of_entries) then
+ table.insert(formspec, "button[12.0,0.2;2.0,0.6;next;Next >]"..
+ "button[12.0,11.0;2.0,0.6;next;Next >]")
+ end
+ table.insert(formspec, "button[0.0,0.2;2.0,0.6;back;Back]"..
+ "button[8.0,11.0;2.0,0.6;back;Back]")
+ local what = minetest.formspec_escape(what_is_the_list_about)
+ table.insert(formspec, "label[7.0,0.4;* Manage your ")
+ table.insert(formspec, what)
+ table.insert(formspec, "s *]")
+ table.insert(formspec, "label[0.2,2.45;Your ")
+ table.insert(formspec, what)
+ table.insert(formspec, ":]")
+ -- offer a dropdown list and a text input field for new varialbe names for adding
+ table.insert(formspec, yl_speak_up.create_dropdown_playerlist(
+ player, pname,
+ table_of_entries,
+ yl_speak_up.speak_to[pname].tmp_index_general,
+ 2.6 + (optional_add_space), 2.15, 1.0, 0.6,
+ "list_of_entries",
+ what,
+ "Delete selected "..what,
+ "add_entry_general",
+ minetest.formspec_escape(tooltip_add_entry_general),
+ "del_entry_general",
+ minetest.formspec_escape(tooltip_del_entry_general)
+ ))
+
+ -- either nil or the text of the selected entry
+ return selected
+end
+
+
+
+-- small helper function for the function below
+yl_speak_up.get_sub_fs_colorize_table = function(formspec, table_specs, liste, color)
+ table.insert(formspec, "tablecolumns[color;text]table[")
+ table.insert(formspec, table_specs)
+ local tmp = {}
+ for k, v in pairs(liste) do
+ table.insert(tmp, color or "#FFFFFF")
+ table.insert(tmp, minetest.formspec_escape(v))
+ end
+ table.insert(formspec, table.concat(tmp, ","))
+ table.insert(formspec, ";]")
+end
+
+
+yl_speak_up.get_sub_fs_show_list_in_box = function(formspec,
+ label, field_name, liste, start_x, start_y, width, height, label_ident,
+ box_color, column_color,
+ tooltip_text, add_lines)
+ local dim_str = tostring(width)..","..tostring(height)
+ table.insert(formspec, "container[")
+ table.insert(formspec, tostring(start_x)..","..tostring(start_y)..";")
+ table.insert(formspec, dim_str)
+ table.insert(formspec, "]")
+ table.insert(formspec, "box[0,0;")
+ table.insert(formspec, dim_str)
+ table.insert(formspec, ";")
+ table.insert(formspec, box_color or "#666666")
+ table.insert(formspec, "]")
+ -- add buttons etc. first so that the label remains visible on top
+ if(add_lines) then
+ table.insert(formspec, add_lines)
+ end
+ table.insert(formspec, "label[")
+ table.insert(formspec, tostring(0.1 + label_ident))
+ table.insert(formspec, ",0.5;")
+ table.insert(formspec, label)
+ table.insert(formspec, "]")
+ yl_speak_up.get_sub_fs_colorize_table(formspec,
+ "0.1,0.7;"..tostring(width-0.2)..","..tostring(height-0.8)..";"..tostring(field_name)..";",
+ liste or {}, column_color)
+ if(tooltip_text and tooltip_text ~= "") then
+ table.insert(formspec, "tooltip[")
+ table.insert(formspec, field_name)
+ table.insert(formspec, ";")
+ table.insert(formspec, tooltip_text)
+ table.insert(formspec, "\n\nClick on an element to select it.")
+ table.insert(formspec, "]")
+ end
+ table.insert(formspec, "container_end[]")
+end
diff --git a/api/fs_edit_general.lua b/api/fs_edit_general.lua
new file mode 100644
index 0000000..1916c0f
--- /dev/null
+++ b/api/fs_edit_general.lua
@@ -0,0 +1,2554 @@
+
+-- helper function; get a formspec with the inventory of the player (for selecting items)
+yl_speak_up.fs_your_inventory_select_item = function(pname, data)
+ return "label[0.2,4.2;Name of the item(stack):]"..
+ "field[4.0,4.0;16.0,0.6;inv_stack_name;;"..(data.inv_stack_name or "").."]"..
+ "tooltip[inv_stack_name;Enter name of the block and amount.\n"..
+ "Example: \"default:apple 3\" for three apples,\n"..
+ " \"farming:bread\" for a bread.]"..
+ "label[0.2,5.7;Or put the item in here\nand click on \"Update\":]"..
+ "button[5.5,5.5;1.5,0.9;store_item_name;Update]"..
+ "list[detached:yl_speak_up_player_"..pname..";npc_wants;4.0,5.5;1,1;]"..
+ "label[8,4.9;Your inventory:]"..
+ "list[current_player;main;8,5.3;8,4;]"
+end
+
+
+-- helper function: get the names of the inventory lists of the node at position
+-- pos on the map and return the index of search_for_list_name in that index
+yl_speak_up.get_node_inv_lists = function(pos, search_for_list_name)
+ if(not(pos)) then
+ return {inv_lists = {"- no inventory -"}, index = "1"}
+ end
+ local meta = minetest.get_meta(pos)
+ if(not(meta)) then
+ return {inv_lists = {"- no inventory -"}, index = "1"}
+ end
+ local inv_lists = {}
+ local index = -1
+ local inv = meta:get_inventory()
+
+ table.insert(inv_lists, minetest.formspec_escape("- please select -"))
+ for k,v in pairs(inv:get_lists()) do
+ table.insert(inv_lists, k)
+ if(search_for_list_name == k) then
+ index = #inv_lists
+ end
+ end
+ return {inv_lists = inv_lists, index = tostring(index)}
+end
+
+
+-- helper function for yl_speak_up.handle_input_fs_edit_option_related
+yl_speak_up.delete_element_p_or_a_or_e = function(
+ player, pname, n_id, d_id, o_id, x_id, id_prefix,
+ element_list_name, element_desc, formspec_input_to)
+ -- does the dialog we want to modify exist?
+ local dialog = yl_speak_up.speak_to[pname].dialog
+ if(not(dialog and dialog.n_dialogs
+ and x_id
+ and dialog.n_dialogs[d_id]
+ and dialog.n_dialogs[d_id].d_options
+ and dialog.n_dialogs[d_id].d_options[o_id])) then
+ yl_speak_up.show_fs(player, "msg", {
+ input_to = "yl_speak_up:"..formspec_input_to,
+ formspec = "size[9,2]"..
+ "label[0.2,0.5;The dialog that is supposed to contain the\n"..
+ "element that you want to delete does not exist.]"..
+ "button[1.5,1.5;2,0.9;back_from_cannot_be_edited;Back]"})
+ return
+ end
+ local old_elem = dialog.n_dialogs[d_id].d_options[o_id][ element_list_name ][ x_id ]
+ if(id_prefix == "r_" and old_elem and old_elem.r_type == "dialog") then
+ yl_speak_up.show_fs(player, "msg", {
+ input_to = "yl_speak_up:"..formspec_input_to,
+ formspec = "size[9,2]"..
+ "label[0.2,0.5;Effects of the type \"dialog\" cannot be deleted.\n"..
+ "Use the edit options or dialog menu to change the target dialog.]"..
+ "button[1.5,1.5;2,0.9;back_from_cannot_be_edited;Back]"})
+ return
+ end
+ -- actually delete the element
+ dialog.n_dialogs[d_id].d_options[o_id][ element_list_name ][ x_id ] = nil
+ -- record this as a change, but do not save do disk yet
+ table.insert(yl_speak_up.npc_was_changed[ n_id ],
+ "Dialog "..tostring(d_id)..": "..element_desc.." "..tostring( x_id )..
+ " deleted for option "..tostring(o_id)..".")
+ -- TODO: when trying to save: save to disk as well?
+ -- show the new/changed element
+ -- go back to the edit option dialog (after all we just deleted the prerequirement)
+ yl_speak_up.show_fs(player, "msg", {
+ input_to = "yl_speak_up:"..formspec_input_to,
+ formspec = "size[6,2]"..
+ "label[0.2,0.5;"..element_desc.." \""..
+ minetest.formspec_escape(tostring( x_id ))..
+ "\" has been deleted.]"..
+ "button[1.5,1.5;2,0.9;back_from_delete_element;Back]"})
+ return
+end
+
+
+-- helper function for yl_speak_up.save_element_p_or_a_or_e
+yl_speak_up.save_element_check_priv = function(player, priv_name, formspec_input_to, explanation)
+ local priv_list = {}
+ priv_list[priv_name] = true
+ if(not(minetest.check_player_privs(player, priv_list))) then
+ yl_speak_up.show_fs(player, "msg", {
+ input_to = "yl_speak_up:"..formspec_input_to,
+ formspec = "size[9,2]"..
+ "label[0.2,0.5;Error: You need the \""..
+ tostring(priv_name).."\" priv"..
+ tostring(explanation)..".]"..
+ "button[1.5,1.5;2,0.9;back_from_error_msg;Back]"})
+ return false
+ end
+ return true
+end
+
+-- helper function for yl_speak_up.handle_input_fs_edit_option_related
+yl_speak_up.save_element_p_or_a_or_e = function(
+ player, pname, n_id, d_id, o_id, x_id, id_prefix, tmp_data_cache,
+ element_list_name, element_desc, max_entries_allowed,
+ values_what, values_operator, values_block, values_trade, values_inv,
+ formspec_input_to, data, fields)
+
+ -- for creating the new prerequirement; normal elements: p_type, p_value, p_id
+ local v = {}
+ -- determine p_type
+ v[ id_prefix.."type" ] = values_what[ data.what ]
+ -- so that we don't have to compare number values of data.what
+ local what_type = values_what[ data.what ]
+
+ local dialog = yl_speak_up.speak_to[pname].dialog
+ 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
+ -- this really should not happen during the normal course of operation
+ -- (only if the player sends forged formspec data or a bug occoured)
+ minetest.chat_send_player(pname, "Dialog or option does not exist.")
+ return
+ end
+ local elements = dialog.n_dialogs[d_id].d_options[o_id][ element_list_name ]
+ if(not(elements)) then
+ dialog.n_dialogs[d_id].d_options[o_id][ element_list_name ] = {}
+ elements = dialog.n_dialogs[d_id].d_options[o_id][ element_list_name ]
+ x_id = "new"
+ end
+ -- set x_id appropriately
+ if(not(x_id) or x_id == "new") then
+ x_id = id_prefix..yl_speak_up.find_next_id(elements)
+ end
+ v[ id_prefix.."id" ] = x_id
+
+ -- if needed: show a message after successful save so that the player can take
+ -- his items back from the trade_inv slots
+ local show_save_msg = nil
+ local sorted_key_list = yl_speak_up.sort_keys(elements)
+ if( x_id == "new" and #sorted_key_list >= max_entries_allowed) then
+ -- this really should not happen during the normal course of operation
+ -- (only if the player sends forged formspec data or a bug occoured)
+ minetest.chat_send_player(pname, "Maximum number of allowed entries exceeded.")
+ return
+ end
+ -- "an internal state (i.e. of a quest)", -- 2
+ if(what_type == "state" and id_prefix ~= "a_") then
+ v[ id_prefix.."value" ] = "expression"
+ v[ id_prefix.."operator" ] = values_operator[ data.operator ]
+ v[ id_prefix.."var_cmp_value" ] = (data.var_cmp_value or "")
+ -- if it is a custom server function,then do not preifx it with $ playername
+ if(id_prefix == "p_") then
+ local idx = table.indexof(yl_speak_up.custom_server_functions.precondition_descriptions,
+ data.variable_name)
+ if(idx > -1) then
+ v[ id_prefix.."variable" ] = data.variable_name
+ else
+ v[ id_prefix.."variable" ] = yl_speak_up.add_pname_to_var(data.variable_name, pname)
+ end
+ else
+ v[ id_prefix.."variable" ] = yl_speak_up.add_pname_to_var(data.variable_name, pname)
+ end
+
+ -- "the value of a property of the NPC (for generic NPC)"
+ elseif(what_type == "property" and id_prefix ~= "a_") then
+ v[ id_prefix.."value" ] = (data.property or "")
+ v[ id_prefix.."operator" ] = values_operator[ data.operator ]
+ v[ id_prefix.."var_cmp_value" ] = (data.var_cmp_value or "")
+
+ -- "something that has to be calculated or evaluated (=call a function)"
+ elseif(what_type == "evaluate") then
+ v[ id_prefix.."value" ] = (data.function_name or "")
+ v[ id_prefix.."operator" ] = values_operator[ data.operator ]
+ v[ id_prefix.."var_cmp_value" ] = (data.var_cmp_value or "")
+ -- transfer the parameters
+ for i = 1, 9 do
+ local s = "param"..tostring(i)
+ v[ id_prefix..s ] = (data[s] or "")
+ end
+
+ -- "a block somewhere", -- 3
+ elseif(what_type == "block" and id_prefix ~= "a_") then
+ v[ id_prefix.."value" ] = values_block[ data.block ]
+ if(not(data.block_pos) or not(data.node_data) or not(data.node_data.name)) then
+ yl_speak_up.show_fs(player, "msg", {
+ input_to = "yl_speak_up:"..formspec_input_to,
+ formspec = "size[8,2]"..
+ "label[0.2,0.5;Error: Please select a block first!]"..
+ "button[1.5,1.5;2,0.9;back_from_error_msg;Back]"})
+ return
+ end
+ -- for "node_is_air", there is no need to store node name and parameter
+ if(v[ id_prefix.."value" ]
+ and (v[ id_prefix.."value" ] == "node_is_like"
+ or v[ id_prefix.."value" ] == "node_is_diffrent_from")
+ or v[ id_prefix.."value" ] == "place"
+ or v[ id_prefix.."value" ] == "dig"
+ or v[ id_prefix.."value" ] == "punch"
+ or v[ id_prefix.."value" ] == "right-click") then
+ v[ id_prefix.."node" ] = data.node_data.name
+ v[ id_prefix.."param2" ] = data.node_data.param2
+ end
+ -- preconditions can be applied to all blocks; effects may be more limited
+ if(id_prefix == "r_"
+ and yl_speak_up.check_blacklisted(v[id_prefix.."value"],
+ -- we don't know yet which node will be there later on
+ data.node_data.name, data.node_data.name)) then
+ yl_speak_up.show_fs(player, "msg", {
+ input_to = "yl_speak_up:"..formspec_input_to,
+ formspec = "size[8,2]"..
+ "label[0.2,0.5;Error: Blocks of type \""..
+ tostring(data.node_data.name).."\" do not allow\n"..
+ "interaction of type \""..tostring(v[id_prefix.."value"])..
+ "\" for NPC.]"..
+ "button[1.5,1.5;2,0.9;back_from_error_msg;Back]"})
+ return
+ end
+ -- we also need to store the position of the node
+ v[ id_prefix.."pos" ] = {x = data.block_pos.x, y = data.block_pos.y, z = data.block_pos.z }
+ -- "I can't punch it. The block is as the block *above* the one I punched.",
+ if(id_prefix == "p_" and data.block == 5) then
+ v.p_pos.y = v.p_pos.y + 1
+ end
+
+ -- "a trade", -- 4
+ -- (only for preconditions; not for effects)
+ elseif(what_type == "trade" and id_prefix == "p_") then
+ -- this depends on the trade associated with that option; therefore,
+ -- it does not need any more parameters (they come dynamicly from the
+ -- trade)
+ v.p_value = values_trade[ data.trade ]
+
+ -- "the inventory of the player", -- 5
+ -- "the inventory of the NPC", -- 6
+ -- "the inventory of a block somewhere", -- 7
+ -- (only for preconditions; not for effects)
+ elseif((id_prefix == "p_"
+ and (what_type == "player_inv" or what_type == "npc_inv" or what_type == "block_inv"))
+ or(id_prefix == "r_"
+ and (what_type == "put_into_block_inv" or what_type == "take_from_block_inv"))) then
+ -- changing the inventory of a block? we need to set p_value to something
+ if(id_prefix == "r_") then
+ -- just to be sure something is stored there...
+ v.r_value = data.inv_stack_name
+ -- for easier access in the formspec
+ v.r_itemstack = data.inv_stack_name
+ -- store in p_value what we want to check regarding the inv (contains/contains not/empty/..)
+ else
+ v.p_value = values_inv[ data.inv ]
+ end
+ if(v.p_value and v.p_value ~= "inv_is_empty") then
+ if(not(data.inv_stack_name) or data.inv_stack_name == "") then
+ yl_speak_up.show_fs(player, "msg", {
+ input_to = "yl_speak_up:"..formspec_input_to,
+ formspec = "size[8,2]"..
+ "label[0.2,0.5;Error: Please provide the name of the "..
+ "\nitem you want to check for!]"..
+ "button[1.5,1.5;2,0.9;back_from_error_msg;Back]"})
+ return
+ end
+ -- we have checked this value earlier on
+ v[ id_prefix.."itemstack" ] = data.inv_stack_name
+ end
+
+ if(data and data.what_type == "player_inv") then
+ data.inv_list_name = "main"
+ elseif(data and data.what_type == "npc_inv") then
+ data.inv_list_name = "npc_main"
+ elseif(data and data.what_type == "block_inv") then
+ data.inv_list_name = "main"
+ end
+ if(not(data.inv_list_name) or data.inv_list_name == "") then
+ yl_speak_up.show_fs(player, "msg", {
+ input_to = "yl_speak_up:"..formspec_input_to,
+ formspec = "size[8,2]"..
+ "label[0.2,0.5;Error: Please provide the name of the "..
+ "\ninventory you want to access!]"..
+ "button[1.5,1.5;2,0.9;back_from_error_msg;Back]"})
+ return
+ end
+ -- the name of the inventory list we want to access
+ v[ id_prefix.."inv_list_name" ] = data.inv_list_name
+
+ -- the inventory of a block
+ if(what_type == "block_inv"
+ or what_type == "put_into_block_inv"
+ or what_type == "take_from_block_inv") then
+ if(not(data.block_pos) or not(data.node_data) or not(data.node_data.name)) then
+ yl_speak_up.show_fs(player, "msg", {
+ input_to = "yl_speak_up:"..formspec_input_to,
+ formspec = "size[8,2]"..
+ "label[0.2,0.5;Error: Please select a block first!]"..
+ "button[1.5,1.5;2,0.9;back_from_error_msg;Back]"})
+ return
+ end
+ -- we also need to store the position of the node
+ v[ id_prefix.."pos" ] = {x = data.block_pos.x, y = data.block_pos.y, z = data.block_pos.z }
+ end
+
+ -- "give item (created out of thin air) to player (requires yl_speak_up.npc_privs_priv priv)", -- 9
+ -- "take item from player and destroy it (requires yl_speak_up.npc_privs_priv priv)", -- 10
+ elseif(id_prefix == "r_" and (what_type == "give_item" or what_type == "take_item")) then
+ if(not(data.inv_stack_name) or data.inv_stack_name == "") then
+ yl_speak_up.show_fs(player, "msg", {
+ input_to = "yl_speak_up:"..formspec_input_to,
+ formspec = "size[8,2]"..
+ "label[0.2,0.5;Error: Please provide the name of the "..
+ "\nitem you want to give or take!]"..
+ "button[1.5,1.5;2,0.9;back_from_error_msg;Back]"})
+ return
+ end
+ local priv_list = {}
+ if(not(yl_speak_up.save_element_check_priv(player, yl_speak_up.npc_privs_priv,
+ formspec_input_to, " in order to set this effect"))) then
+ return
+ end
+ v[ "r_value" ] = data.inv_stack_name
+
+
+ -- "move the player to a given position (requires yl_speak_up.npc_privs_priv priv)", -- 11
+ elseif(what_type == "move" and id_prefix == "r_") then
+ if(not(data.move_to_x) or not(data.move_to_y) or not(data.move_to_z)) then
+ yl_speak_up.show_fs(player, "msg", {
+ input_to = "yl_speak_up:"..formspec_input_to,
+ formspec = "size[9,2]"..
+ "label[0.2,0.5;Error: Please provide valid coordinates "..
+ " x, y and z!]"..
+ "button[1.5,1.5;2,0.9;back_from_error_msg;Back]"})
+ return
+ end
+ if(not(yl_speak_up.save_element_check_priv(player, yl_speak_up.npc_privs_priv,
+ formspec_input_to, " in order to set this effect"))) then
+ return
+ end
+ v[ "r_value" ] = minetest.pos_to_string(
+ {x = data.move_to_x, y = data.move_to_y, z = data.move_to_z})
+
+ -- effect "execute Lua code (requires npc_master priv)", -- precondition: 8; effect: 12
+ elseif((what_type == "function" and id_prefix == "p_")
+ or (what_type == "function" and id_prefix == "r_")) then
+ if(not(data.lua_code) or data.lua_code == "") then
+ yl_speak_up.show_fs(player, "msg", {
+ input_to = "yl_speak_up:"..formspec_input_to,
+ formspec = "size[9,2]"..
+ "label[0.2,0.5;Error: Please enter the Lua code you want "..
+ "to execute!]"..
+ "button[1.5,1.5;2,0.9;back_from_error_msg;Back]"})
+ return
+ end
+ if(not(yl_speak_up.save_element_check_priv(player, "npc_master",
+ formspec_input_to, " in order to set this"))) then
+ return
+ end
+ v[ id_prefix.."value" ] = data.lua_code
+
+ -- "NPC crafts something", -- 6
+ -- (only for effects; not for preconditions)
+ elseif(what_type == "craft" and id_prefix == "r_") then
+ local player_inv = player:get_inventory()
+ if(player_inv:get_stack("craftpreview", 1):is_empty()) then
+ yl_speak_up.show_fs(player, "msg", {
+ input_to = "yl_speak_up:"..formspec_input_to,
+ formspec = "size[8,2]"..
+ "label[0.2,0.5;Error: Please prepare your craft grid first!"..
+ "\nYour NPC needs to know what to craft.]"..
+ "button[1.5,1.5;2,0.9;back_from_error_msg;Back]"})
+ return
+ end
+ -- store the craft result (Note: the craft result may change in the future
+ -- if the server changes its craft result, making this craft invalid)
+ v[ "r_value" ] = player_inv:get_stack("craftpreview", 1):to_string()
+ v[ "r_craft_grid"] = {}
+ for i = 1, 9 do
+ -- store all the indigrents of the craft grid
+ table.insert( v[ "r_craft_grid" ],
+ player_inv:get_stack("craft", i):to_string())
+ end
+
+ -- "go to other dialog if the *previous* effect failed", -- 7
+ -- (only for effects; not for preconditions)
+ elseif(what_type == "on_failure" and id_prefix == "r_") then
+ v[ "r_value" ] = data.on_failure
+
+ -- "send a chat message to all players", -- 8
+ -- (only for effects; not for preconditions)
+ elseif(what_type == "chat_all" and id_prefix == "r_") then
+ data.chat_msg_text = fields.chat_msg_text
+ -- allow saving only if the placeholders are all present
+ -- (reason for requiring them: players and server owners ought to
+ -- be able to see who is responsible for a message)
+ if(not(string.find(data.chat_msg_text, "%$NPC_NAME%$"))
+ or not(string.find(data.chat_msg_text, "%$PLAYER_NAME%$"))
+ or not(string.find(data.chat_msg_text, "%$OWNER_NAME%$"))) then
+ yl_speak_up.show_fs(player, "msg", {
+ input_to = "yl_speak_up:"..formspec_input_to,
+ formspec = "size[9,2.5]"..
+ "label[0.2,0.5;Error: Your chat message needs to contain "..
+ "the following\nplaceholders: $NPC_NAME$, "..
+ "$PLAYER_NAME$ and $OWNER_NAME$.\nThat way, other "..
+ "players will know who sent the message.]"..
+ "button[1.5,2.0;2,0.9;back_from_error_msg;Back]"})
+ return
+ end
+ v[ "r_value" ] = data.chat_msg_text
+
+ -- "Normal trade - one item(stack) for another item(stack).", -- 3
+ -- (only for actions)
+ elseif(what_type == "trade" and id_prefix == "a_") then
+ -- remember which option was selected
+ yl_speak_up.speak_to[pname].o_id = o_id
+ -- do not switch target dialog (we are in edit mode)
+ yl_speak_up.speak_to[pname].target_d_id = nil
+ -- just to make sure that the trade_id is properly set...
+ if(not(data.trade_id)) then
+ yl_speak_up.show_fs(player, "msg", {
+ input_to = "yl_speak_up:"..formspec_input_to,
+ formspec = "size[9,2.5]"..
+ "label[0.2,0.5;Error: Missing trade ID.]"..
+ "button[1.5,2.0;2,0.9;back_from_error_msg;Back]"})
+ return
+ end
+ -- the button is called store_trade_simple instead of save_element in
+ -- the trade simple function(s); we want to store a trade
+ fields.store_trade_simple = true
+ local res = yl_speak_up.input_add_trade_simple(player, "", fields, nil)
+ -- the above function sets:
+ -- dialog.trades[ trade_id ] = {pay={ps},buy={bs}, d_id = d_id, o_id = o_id}
+ -- store the trade as an action:
+ local dialog = yl_speak_up.speak_to[pname].dialog
+ if(res and dialog.trades and dialog.trades[ data.trade_id ]) then
+ v[ "a_value" ] = data.trade_id
+ v[ "a_pay" ] = dialog.trades[ data.trade_id ].pay
+ v[ "a_buy" ] = dialog.trades[ data.trade_id ].buy
+ yl_speak_up.edit_mode_set_a_on_failure(data, pname, v)
+ end
+
+ -- "The NPC gives something to the player (i.e. a quest item).", -- 4
+ -- "The player is expected to give something to the NPC (i.e. a quest item).", -- 5
+ -- (only for actions)
+ elseif(((what_type == "npc_gives" or what_type == "npc_wants") and id_prefix == "a_")
+ or (what_type == "player_offered_item" and id_prefix == "p_")) then
+ local trade_inv_list = what_type
+ if(id_prefix == "p_") then
+ trade_inv_list = "npc_wants"
+ end
+ local trade_inv = minetest.get_inventory({type="detached", name="yl_speak_up_player_"..pname})
+ if(not(trade_inv) or trade_inv:is_empty( trade_inv_list )) then
+ local what = "give to"
+ if(id_prefix == "p_") then
+ what = "accept from"
+ end
+ yl_speak_up.show_fs(player, "msg", {
+ input_to = "yl_speak_up:"..formspec_input_to,
+ formspec = "size[9,2.5]"..
+ "label[0.2,0.5;Please insert an item first! Your NPC "..
+ "needs\nto know what it shall "..what.." the player.]"..
+ "button[1.5,2.0;2,0.9;back_from_error_msg;Back]"})
+ return
+ end
+ yl_speak_up.edit_mode_set_a_on_failure(data, pname, v)
+ -- change the node in the slot
+ local stack = trade_inv:get_stack( trade_inv_list, 1)
+ if(not(stack) or not(minetest.registered_items[ stack:get_name() ])) then
+ yl_speak_up.show_fs(player, "msg", {
+ input_to = "yl_speak_up:"..formspec_input_to,
+ formspec = "size[9,2.5]"..
+ "label[0.2,0.5;This item is unkown. Please use only known"..
+ "items.]"..
+ "button[1.5,2.0;2,0.9;back_from_error_msg;Back]"})
+ return
+ end
+ -- is this particular item blacklisted on this server?
+ -- this is only relevant for actions, not for preconditions
+ if(id_prefix ~= "p_" and yl_speak_up.blacklist_action_quest_item[ stack:get_name() ]) then
+ yl_speak_up.show_fs(player, "msg", {
+ input_to = "yl_speak_up:"..formspec_input_to,
+ formspec = "size[9,2.5]"..
+ "label[0.2,0.5;Sorry. This item is blacklisted on this "..
+ "server.\nYou can't use it as a quest item.]"..
+ "button[1.5,2.0;2,0.9;back_from_error_msg;Back]"})
+ return
+ end
+ local meta = stack:get_meta()
+ -- what does the NPC want to give?
+ v[ id_prefix.."value" ] = stack:get_name().." "..stack:get_count()
+ -- for displaying as a background image
+ data.item_string = v[ id_prefix.."value" ]
+ if(what_type == "npc_wants" or what_type == "player_offered_item") then
+ -- try to reconstruct $PLAYER_NAME$ (may not always work)
+ local item_was_for = meta:get_string("yl_speak_up:quest_item_for")
+ local new_desc = meta:get_string("description")
+ if(item_was_for and item_was_for ~= "") then
+ new_desc = string.gsub(new_desc, item_was_for, "$PLAYER_NAME$")
+ end
+ data.item_desc = new_desc
+ end
+ -- set new description if there is one set (optional)
+ if(data.item_desc
+ and data.item_desc ~= ""
+ and data.item_desc ~= "- none set -") then
+ if(what_type == "npc_gives") then
+ meta:set_string("description", data.item_desc)
+ end
+ v[ id_prefix.."item_desc" ] = data.item_desc
+ end
+ if(what_type == "npc_wants" or what_type == "player_offers_item") then
+ data.item_quest_id = meta:get_string("yl_speak_up:quest_id")
+ end
+ -- set special ID (optional)
+ if(data.item_quest_id
+ and data.item_quest_id ~= ""
+ and data.item_quest_id ~= "- no item set -") then
+ if(what_type == "npc_gives") then
+ -- which player got this quest item?
+ meta:set_string("yl_speak_up:quest_item_for", pname)
+ -- include the NPC id so that we know which NPC gave it
+ meta:set_string("yl_speak_up:quest_item_from", tostring(n_id))
+ -- extend quest_id by NPC id so that it becomes more uniq
+ meta:set_string("yl_speak_up:quest_id",
+ tostring(n_id).." "..tostring(data.item_quest_id))
+ end
+ v[ id_prefix.."item_quest_id" ] = data.item_quest_id
+ end
+ if( v["a_item_quest_id"] and not(v[ "a_item_desc"]) and what_type == "npc_gives") then
+ yl_speak_up.show_fs(player, "msg", {
+ input_to = "yl_speak_up:"..formspec_input_to,
+ formspec = "size[9,2.5]"..
+ "label[0.2,0.5;You can't set a special quest ID without "..
+ "also changing\nthe description. The player would be "..
+ "unable to tell\nthe quest item and normal items "..
+ "apartapart.]"..
+ "button[1.5,2.0;2,0.9;back_from_error_msg;Back]"})
+ return
+ end
+ if(data.item_group and data.item_group ~= ""
+ and data.item_group ~= "- no, just this one item -") then
+ v["p_item_group"] = data.item_group
+ end
+ v["p_item_stack_size"] = data.item_stack_size
+ v["p_match_stack_size"] = data.match_stack_size
+ local player_inv = player:get_inventory()
+ if(not(player_inv:room_for_item("main", stack))) then
+ yl_speak_up.show_fs(player, "msg", {
+ input_to = "yl_speak_up:"..formspec_input_to,
+ formspec = "size[9,2.5]"..
+ "label[0.2,0.5;You have no room in your inventory for "..
+ "the example\nitem. Please make room so that it can be"..
+ "given back to you!]"..
+ "button[1.5,2.0;2,0.9;back_from_error_msg;Back]"})
+ return
+ end
+ player_inv:add_item("main", stack)
+ trade_inv:remove_item(trade_inv_list, stack)
+ -- just send a message that the save was successful and give the player time to
+ -- take his items back
+ show_save_msg = "size[9,2.5]"..
+ "label[0.2,0.5;The information was saved successfully.\n"..
+ "The item has been returned to your inventory.]"..
+ "button[1.5,2.0;2,0.9;back_from_saving;Back]"
+
+ -- "The player has to manually enter a password or passphrase or some other text.", -- 6
+ elseif(what_type == "text_input" and id_prefix == "a_") then
+ if(not(data.quest_question)) then
+ data.quest_question = "Your answer:"
+ end
+ v[ "a_question" ] = data.quest_question
+ -- the player setting this up needs to provide the correct answer
+ if(not(data.quest_answer)) then
+ yl_speak_up.show_fs(player, "msg", {
+ input_to = "yl_speak_up:"..formspec_input_to,
+ formspec = "size[9,2.5]"..
+ "label[0.2,0.5;Error: Please provide the correct answer!\n"..
+ "The answer the player gives is checked against this.]"..
+ "button[1.5,2.0;2,0.9;back_from_error_msg;Back]"})
+ return
+ end
+ v[ "a_value" ] = data.quest_answer
+ if(not(data.action_failure_dialog)) then
+ yl_speak_up.show_fs(player, "msg", {
+ input_to = "yl_speak_up:"..formspec_input_to,
+ formspec = "size[9,2.5]"..
+ "label[0.2,0.5;Error: Please provide a target dialog if "..
+ "the player gives the wrong answer.]"..
+ "button[1.5,2.0;2,0.9;back_from_error_msg;Back]"})
+ return
+ end
+ yl_speak_up.edit_mode_set_a_on_failure(data, pname, v)
+
+ elseif(what_type == "deal_with_offered_item" and id_prefix == "r_") then
+ if(not(data.select_deal_with_offered_item) or data.select_deal_with_offered_item < 2) then
+ yl_speak_up.show_fs(player, "msg", {
+ input_to = "yl_speak_up:"..formspec_input_to,
+ formspec = "size[9,2.5]"..
+ "label[0.2,0.5;Error: Please select what the NPC shall do!]"..
+ "button[1.5,2.0;2,0.9;back_from_error_msg;Back]"})
+ return
+ end
+ v[ "r_value" ] = yl_speak_up.dropdown_values_deal_with_offered_item[data.select_deal_with_offered_item]
+
+ -- "Call custom functions that are supposed to be overridden by the server.", --
+ -- precondition: 9; action: 7; effect: 13
+ elseif((id_prefix == "a_" and what_type == "custom")
+ or (id_prefix == "p_" and what_type == "custom")
+ or (id_prefix == "r_" and what_type == "custom")) then
+ v[ id_prefix.."value" ] = data.custom_param
+ if(id_prefix == "a_") then
+ v[ "a_on_failure" ] = data.action_failure_dialog
+ end
+
+ -- "the type of the entity of the NPC",
+ -- precondition: (last entry)
+ elseif(what_type == "entity_type" and id_prefix == "p_") then
+ -- Note: We might check for minetest.registered_entities[data.entity_type] - but
+ -- that doesn't really help much - so we skip that.
+ v[ "p_value" ] = data.entity_type
+
+ -- "The preconditions of another dialog option are fulfilled/not fulfilled.", -- 10
+ -- precondition: 10
+ elseif(what_type == "other" and id_prefix == "p_") then
+ if(data.other_o_id and data.other_o_id ~= "-select-") then
+ v[ "p_value" ] = data.other_o_id
+ end
+ if(data.fulfilled and data.fulfilled ~= "-select-") then
+ v[ "p_fulfilled" ] = data.fulfilled
+ end
+
+ elseif(what_type == "true") then
+ v[ "p_value" ] = true -- doesn't matter here - just *some* value
+ elseif(what_type == "false") then
+ v[ "p_value" ] = true -- doesn't matter here - just *some* value
+ end
+
+ v[ "alternate_text" ] = data.alternate_text
+
+ -- only save if something was actually selected
+ if(v[ id_prefix.."value"]) then
+ if(not(dialog.n_dialogs[d_id].d_options[o_id][ element_list_name ])) then
+ dialog.n_dialogs[d_id].d_options[o_id][ element_list_name ] = {}
+ end
+ -- store the change in the dialog
+ dialog.n_dialogs[d_id].d_options[o_id][ element_list_name ][ x_id ] = v
+ -- clear up data
+ yl_speak_up.speak_to[pname][ id_prefix.."id" ] = nil
+ yl_speak_up.speak_to[pname][ tmp_data_cache ] = nil
+ -- record this as a change, but do not save do disk yet
+ table.insert(yl_speak_up.npc_was_changed[ n_id ],
+ "Dialog "..tostring(d_id)..": "..element_desc.." "..tostring(x_id)..
+ " added/changed for option "..tostring(o_id)..".")
+ if(show_save_msg) then
+ yl_speak_up.show_fs(player, "msg", {
+ input_to = "yl_speak_up:"..formspec_input_to,
+ formspec = show_save_msg})
+ return
+ end
+ -- TODO: when trying to save: save to disk as well?
+ -- show the new/changed precondition
+ yl_speak_up.show_fs(player, formspec_input_to, x_id)
+ return
+ else
+ -- make sure the player is informed that saving failed
+ yl_speak_up.show_fs(player, "msg", {
+ input_to = "yl_speak_up:"..formspec_input_to,
+ formspec = "size[8,2]"..
+ "label[0.2,0.5;Error: There is no \""..tostring(id_prefix)..
+ "value\" set.\n"..
+ "\nCould not save.]"..
+ "button[1.5,1.5;2,0.9;back_from_error_msg;Back]"})
+ return
+ end
+end
+
+
+-- These two functions
+-- * yl_speak_up.handle_input_fs_edit_option_related and
+-- * yl_speak_up.build_fs_edit_option_related
+-- are very similar for preconditions and effects. Therefore they're called here
+-- with a lot of parameters. fs_edit_preconditions.lua and fs_edit_effects.lua
+-- contain only wrappers.
+
+yl_speak_up.handle_input_fs_edit_option_related = function(player, formname, fields,
+ id_prefix, element_list_name, max_entries_allowed,
+ element_desc, tmp_data_cache,
+ text_ask_for_punching,
+ values_what, values_operator, values_block, values_trade, values_inv,
+ check_what, check_operator, check_block, check_trade, check_inv,
+ get_sorted_player_var_list_function,
+ formspec_input_to
+ )
+ if(not(player)) then
+ return
+ end
+ local pname = player:get_player_name()
+ -- what are we talking about?
+ local n_id = yl_speak_up.speak_to[pname].n_id
+ local d_id = yl_speak_up.speak_to[pname].d_id
+ local o_id = yl_speak_up.speak_to[pname].o_id
+ local x_id = yl_speak_up.speak_to[pname][ id_prefix.."id"]
+
+ -- this only works in edit mode
+ if(not(n_id) or yl_speak_up.edit_mode[pname] ~= n_id) then
+ return
+ end
+
+ if(fields.back_from_cannot_be_edited
+ or fields.back_from_show_var_usage) then
+ yl_speak_up.show_fs(player, formspec_input_to, x_id)
+ return
+ end
+
+ -- clear editing cache tmp_data_cache for all other types
+ if(id_prefix ~= "p_") then
+ yl_speak_up.speak_to[pname][ "tmp_prereq" ] = nil
+ end
+ if(id_prefix ~= "a_") then
+ yl_speak_up.speak_to[pname][ "tmp_action" ] = nil
+ end
+ if(id_prefix ~= "r_") then
+ yl_speak_up.speak_to[pname][ "tmp_effect" ] = nil
+ end
+
+ -- delete precondition, action or effect
+ if(fields.delete_element) then
+ yl_speak_up.delete_element_p_or_a_or_e( player, pname, n_id, d_id, o_id, x_id, id_prefix,
+ element_list_name, element_desc, formspec_input_to)
+ return
+ end
+
+ if(fields.select_block_pos) then
+ minetest.chat_send_player(pname, text_ask_for_punching)
+ -- this formspec expects the block punch:
+ yl_speak_up.speak_to[pname].expect_block_punch = formspec_input_to
+ return
+ end
+
+ -- field inputs: those do not trigger a sending of the formspec on their own
+
+ local was_changed = false
+ -- are we talking about an inventory?
+ -- (inventory only applies to preconditions; not effects)
+ local data = yl_speak_up.speak_to[pname][ tmp_data_cache ]
+ local what_type = ""
+ if(data and data.what and values_what[ data.what ]) then
+ what_type = values_what[ data.what ]
+ end
+
+ if(((fields.inv_stack_name and fields.inv_stack_name ~= "")
+ or (fields.store_item_name and fields.store_item_name ~= ""))
+ and data and data.what
+ and ((id_prefix == "p_"
+ and (what_type == "player_inv" or what_type == "npc_inv" or what_type == "block_inv"))
+ -- "give item (created out of thin air) to player (requires yl_speak_up.npc_privs_priv priv)", -- 9
+ -- "take item from player and destroy it (requires yl_speak_up.npc_privs_priv priv)", -- 10
+ or (id_prefix == "r_"
+ and (what_type == "give_item" or what_type == "take_item"
+ or what_type == "put_into_block_inv" or what_type == "take_from_block_inv")))) then
+ local wanted = ""
+ local wanted_name = ""
+ if(not(fields.store_item_name)) then
+ local parts = fields.inv_stack_name:split(" ")
+ local size = 1
+ if(parts and #parts > 1) then
+ size = tonumber(parts[2])
+ if(not(size) or size < 1) then
+ size = 1
+ end
+ end
+ wanted = parts[1].." "..tostring(size)
+ wanted_name = parts[1]
+ else
+ local trade_inv = minetest.get_inventory({type="detached",
+ name="yl_speak_up_player_"..pname})
+ if(not(trade_inv) or trade_inv:is_empty("npc_wants", 1)) then
+ -- show error message
+ yl_speak_up.show_fs(player, "msg", {
+ input_to = "yl_speak_up:"..formspec_input_to,
+ formspec = "size[8,2]"..
+ "label[0.2,0.0;Please put an item(stack) into the slot "..
+ "next to the\n\"Store\" button first!]"..
+ "button[1.5,1.5;2,0.9;back_from_error_msg;Back]"})
+ return
+ end
+ local stack = trade_inv:get_stack("npc_wants", 1)
+ wanted = stack:get_name().." "..stack:get_count()
+ wanted_name = stack:get_name()
+ end
+ -- does the item exist?
+ if(minetest.registered_items[ wanted_name ]) then
+ data.inv_stack_name = wanted
+ fields.inv_stack_name = wanted
+ else
+ -- show error message
+ yl_speak_up.show_fs(player, "msg", {
+ input_to = "yl_speak_up:"..formspec_input_to,
+ formspec = "size[8,2]"..
+ "label[0.2,0.5;Error: \""..
+ minetest.formspec_escape(wanted)..
+ "\" is not a valid item(stack).]"..
+ "button[1.5,1.5;2,0.9;back_from_error_msg;Back]"})
+ return
+ end
+
+ elseif(fields.select_deal_with_offered_item and fields.select_deal_with_offered_item ~= "") then
+ data.select_deal_with_offered_item = table.indexof(
+ yl_speak_up.dropdown_list_deal_with_offered_item,
+ fields.select_deal_with_offered_item)
+
+ elseif(fields.select_accept_group and fields.select_accept_group ~= ""
+ and data and data.what and what_type == "player_offered_item" and id_prefix == "p_") then
+ data.item_group = fields.select_accept_group
+
+ elseif(fields.select_match_stack_size and fields.select_match_stack_size ~= ""
+ and data and data.what and what_type == "player_offered_item" and id_prefix == "p_") then
+ data.match_stack_size = fields.select_match_stack_size:split(" ")[1]
+
+ -- comparison value for a variable (same for both preconditions and effects)
+ -- (also used for checking return values of functions and property values)
+ elseif(fields.var_cmp_value
+ and data and data.what
+ and (what_type == "state" or what_type == "property" or what_type == "evaluate")) then
+ data.var_cmp_value = fields.var_cmp_value
+ was_changed = true
+
+ -- text for a chat message
+ elseif(fields.chat_msg_text
+ and data and data.what and what_type == "chat_all" and id_prefix == "r_") then
+ data.chat_msg_text = fields.chat_msg_text
+ was_changed = true
+
+ elseif(fields.custom_param
+ and fields.custom_param ~= "- Insert a text that is passed on to your function here -"
+ and fields.custom_param ~= ""
+ and data and data.what
+ and ((id_prefix == "a_" and what_type == "custom")
+ or (id_prefix == "p_" and what_type == "custom")
+ or (id_prefix == "r_" and what_type == "custom"))) then
+ data.custom_param = fields.custom_param
+ was_changed = true
+
+ elseif(fields.action_item_quest_id
+ and fields.action_item_quest_id ~= ""
+ and fields.action_item_quest_id ~= "- none set -"
+ and data and data.what and what_type == "npc_gives" and id_prefix == "a_") then
+ data.item_quest_id = fields.action_item_quest_id
+ was_changed = true
+ end
+ -- action_item_quest_id and action_item_desc can be set at the same time
+ if(fields.action_item_desc
+ and fields.action_item_desc ~= ""
+ and fields.action_item_desc ~= "- no item set -"
+ and data and data.what and what_type == "npc_gives" and id_prefix == "a_") then
+ -- TODO: check if it diffrent from the default one of the stack
+ data.item_desc = fields.action_item_desc
+ was_changed = true
+ end
+ if(fields.quest_question
+ and fields.quest_question ~= ""
+ and data and data.what and what_type == "text_input" and id_prefix == "a_") then
+ data.quest_question = fields.quest_question
+ was_changed = true
+ end
+ -- quest question and answer can be given with the same press of the save button
+ if(fields.quest_answer
+ and fields.quest_answer ~= "- Insert the correct answer here -"
+ and fields.quest_answer ~= ""
+ and data and data.what and what_type == "text_input" and id_prefix == "a_") then
+ data.quest_answer = fields.quest_answer
+ was_changed = true
+ end
+
+ -- "move the player to a given position (requires yl_speak_up.npc_privs_priv priv)", -- 11
+ if(fields.move_to_x or fields.move_to_y or fields.move_to_z) then
+ local dimension = {"x","y","z"}
+ for i, dim in ipairs(dimension) do
+ local text = fields["move_to_"..dim]
+ if(text and text ~= "") then
+ local val = tonumber(text)
+ if(not(val) or val < -32000 or val > 32000) then
+ yl_speak_up.show_fs(player, "msg", {
+ input_to = "yl_speak_up:"..formspec_input_to,
+ formspec = "size[9,2]"..
+ "label[0.2,0.5;Error: The coordinate values have "..
+ "be in the range of -32000..32000.]"..
+ "button[1.5,1.5;2,0.9;back_from_error_msg;Back]"})
+ return
+ else
+ data[ "move_to_"..dim ] = val
+ end
+ end
+ end
+ end
+ -- lua code
+ if(fields.lua_code) then
+ data.lua_code = fields.lua_code
+ end
+ -- select the type of the entity for "the type of the entity of the NPC"
+ if(fields.entity_type) then
+ data.entity_type = fields.entity_type
+ end
+ -- if the type of operator is changed: store any new values that may need storing
+ if(what_type == "evaluate"
+ and (fields.set_param0 or fields.set_param1 or fields.set_param2 or fields.set_param3
+ or fields.set_param4 or fields.set_param5 or fields.set_param6 or fields.set_param7
+ or fields.set_param8 or fields.set_param9)) then
+ for i = 1, 9 do
+ local pn = "param"..tostring(i)
+ if(fields["set_"..pn]) then
+ if(data[pn] ~= fields["set_"..pn]) then
+ data[pn] = fields["set_"..pn]
+ was_changed = true
+ end
+ end
+ end
+ end
+
+
+ -- the save button was pressed
+ if(fields.save_element and data and data.what and values_what[ data.what ]) then
+ local v = yl_speak_up.save_element_p_or_a_or_e(
+ player, pname, n_id, d_id, o_id, x_id, id_prefix, tmp_data_cache,
+ element_list_name, element_desc, max_entries_allowed,
+ values_what, values_operator, values_block, values_trade, values_inv,
+ formspec_input_to, data, fields)
+ return
+ end
+
+
+ -- selections in a dropdown menu (they trigger sending the formspec)
+
+ -- select a general direction/type first
+ -- but *not* when enter was pressed (enter sends them all)
+ if(fields.select_what and not(fields.key_enter) and not(fields.store_item_name)) then
+ local nr = table.indexof(check_what, fields.select_what)
+ yl_speak_up.speak_to[pname][ tmp_data_cache ] = { what = nr }
+ end
+ -- select a subtype for the "a trade" selection
+ if(fields.select_trade) then
+ local nr = table.indexof(check_trade, fields.select_trade)
+ yl_speak_up.speak_to[pname][ tmp_data_cache ].trade = nr
+ end
+ -- select a subtype for the inventory selection (player or NPC)
+ if(fields.select_inv) then
+ local nr = table.indexof(check_inv, fields.select_inv)
+ yl_speak_up.speak_to[pname][ tmp_data_cache ].inv = nr
+ end
+ -- select data regarding a block
+ if(fields.select_block) then
+ local nr = table.indexof(check_block, fields.select_block)
+ yl_speak_up.speak_to[pname][ tmp_data_cache ].block = nr
+ end
+ -- select data regarding the inventory list of a block
+ if(fields.inv_list_name and fields.inv_list_name ~= "") then
+ local tmp = yl_speak_up.get_node_inv_lists(
+ yl_speak_up.speak_to[pname][ tmp_data_cache ].block_pos,
+ fields.inv_list_name)
+ -- if that inventory list really exists in that block: all ok
+ if(tmp and tmp.index ~= "" and tmp.index ~= "1") then
+ yl_speak_up.speak_to[pname][ tmp_data_cache ].inv_list_name = fields.inv_list_name
+ end
+ end
+ -- select data regarding a variable
+ if(fields.select_variable) then
+ -- get the list of available variables (with the same elements
+ -- and the same sort order as when the dropdown was displayed)
+ local var_list = get_sorted_player_var_list_function(pname)
+ yl_speak_up.strip_pname_from_varlist(var_list, pname)
+ local nr = table.indexof(var_list, fields.select_variable)
+ if(nr) then
+ yl_speak_up.speak_to[pname][ tmp_data_cache ].variable = nr
+ yl_speak_up.speak_to[pname][ tmp_data_cache ].variable_name = var_list[ nr ]
+ end
+ end
+ -- select data regarding an operator
+ if(fields.select_operator) then
+ local nr = table.indexof(check_operator, fields.select_operator)
+ yl_speak_up.speak_to[pname][ tmp_data_cache ].operator = nr
+ end
+ -- "the value of a property of the NPC (for generic NPC)"
+ if(fields.property and fields.property ~= "") then
+ yl_speak_up.speak_to[pname][ tmp_data_cache ].property = fields.property
+ end
+ -- "something that has to be calculated or evaluated (=call a function)"
+ if(fields.select_function_name and fields.select_function_name ~= "") then
+ for k, v in pairs(yl_speak_up["custom_functions_"..id_prefix]) do
+ if(v["description"] == fields.select_function_name) then
+ yl_speak_up.speak_to[pname][ tmp_data_cache ].function_name = k
+ end
+ end
+ end
+ -- "something that has to be calculated or evaluated (=call a function)"
+ if(fields.evaluate and fields.evaluate ~= "") then
+ yl_speak_up.speak_to[pname][ tmp_data_cache ].evaluate = fields.evaluate
+ end
+ for i = 1,9 do
+ local s = "param"..tostring(i)
+ if(fields[s] and fields[s] ~= "") then
+ yl_speak_up.speak_to[pname][ tmp_data_cache ][s] = fields[s]
+ end
+ end
+ -- another dialog option is true or false
+ -- Note: "-select-" can be choosen here as well
+ if(fields.select_other_o_id and fields.select_other_o_id ~= "") then
+ yl_speak_up.speak_to[pname][ tmp_data_cache ].other_o_id = fields.select_other_o_id
+ end
+ -- Note: "-select-" can be choosen here as well
+ if(fields.select_fulfilled and fields.select_fulfilled ~= "") then
+ yl_speak_up.speak_to[pname][ tmp_data_cache ].fulfilled = fields.select_fulfilled
+ end
+ if(fields.select_on_failure) then
+ -- in this case we really want the name of the target dialog
+ local dialog = yl_speak_up.speak_to[pname].dialog
+ yl_speak_up.speak_to[pname][ tmp_data_cache ].on_failure =
+ yl_speak_up.d_name_to_d_id(dialog, fields.select_on_failure)
+ end
+ if(fields.select_on_action_failure
+ and data and data.what and id_prefix == "a_") then
+ local dialog = yl_speak_up.speak_to[pname].dialog
+ yl_speak_up.speak_to[pname][ tmp_data_cache ].action_failure_dialog =
+ yl_speak_up.d_name_to_d_id(dialog, fields.select_on_action_failure)
+ end
+
+ -- new variables have to be added (and deleted) somewhere after all
+ if(fields.manage_variables) then
+ -- remember which formspec we are comming from
+ yl_speak_up.speak_to[pname][ "working_at" ] = formspec_input_to
+ if(data.variable) then
+ yl_speak_up.speak_to[pname].tmp_index_variable = data.variable - 1
+ end
+ yl_speak_up.show_fs(player, "manage_variables")
+ return
+ end
+
+ -- handle editing and changing of alternate texts for actions
+ if( fields.button_edit_action_on_failure_text_change
+ or fields.button_edit_effect_on_failure_text_change
+ or fields.turn_alternate_text_into_new_dialog
+ or fields.save_dialog_modification) then
+ yl_speak_up.handle_edit_actions_alternate_text(
+ player, pname, n_id, d_id, o_id, x_id, id_prefix,
+ formspec_input_to, data, fields, tmp_data_cache)
+ if(not(fields.save_dialog_modification)
+ and not(fields.turn_alternate_text_into_new_dialog)) then
+ return
+ end
+ was_changed = true
+ -- we are back from that submenu
+ elseif(fields.back_from_edit_dialog_modification) then
+ was_changed = true
+ end
+
+ -- show var usage - starting from clicking on a precondition or effect in the
+ -- edit options menu and viewing the list containing that selected element
+ if( fields.show_var_usage and x_id) then
+ local dialog = yl_speak_up.speak_to[pname].dialog
+ local element = dialog.n_dialogs[d_id].d_options[o_id][ element_list_name ][ x_id ]
+ if(element and element[ id_prefix.."variable"]) then
+ local effect_name = "(Ef)fect"
+ if(id_prefix == "p_") then
+ effect_name = "pre(C)ondition"
+ end
+ yl_speak_up.show_fs(player, "msg", {
+ input_to = "yl_speak_up:"..formspec_input_to,
+ formspec = yl_speak_up.fs_get_list_of_usage_of_variable(
+ element[ id_prefix.."variable"], pname, true,
+ "back_from_show_var_usage",
+ "Back to select "..effect_name.." "..tostring(x_id)..
+ " of option "..tostring(o_id)..
+ " of dialog "..tostring(d_id),
+ -- internal variable?
+ (data and data.variable and data.variable < 3))
+ })
+ return
+ end
+ -- show var usuage - but this time from the edit dialog for that precondition or effect
+ elseif(fields.show_var_usage_edit_element and x_id) then
+ local dialog = yl_speak_up.speak_to[pname].dialog
+ local element = nil
+ -- x_id may be "new" and this may be the first element in element_list_name
+ if(dialog.n_dialogs[d_id].d_options[o_id][ element_list_name ]) then
+ element = dialog.n_dialogs[d_id].d_options[o_id][ element_list_name ][ x_id ]
+ end
+ if(not(element) or data.variable_name) then
+ element = {}
+ element[ id_prefix.."variable"] = data.variable_name
+ end
+ if(element and element[ id_prefix.."variable"]) then
+ local effect_name = "(Ef)fect"
+ if(id_prefix == "p_") then
+ effect_name = "pre(C)ondition"
+ end
+ yl_speak_up.show_fs(player, "msg", {
+ input_to = "yl_speak_up:"..formspec_input_to,
+ formspec = yl_speak_up.fs_get_list_of_usage_of_variable(
+ element[ id_prefix.."variable"], pname, true,
+ "back_from_error_msg",
+ "Back to select "..effect_name.." "..tostring(x_id)..
+ " of option "..tostring(o_id)..
+ " of dialog "..tostring(d_id),
+ -- internal variable?
+ (data and data.variable and data.variable < 3))
+ })
+ return
+ end
+ -- allow to delete unused variables
+ elseif(fields.delete_unused_variable) then
+ -- try to delete the variable (button comes from the show usage of variable formspec)
+ local text = yl_speak_up.del_quest_variable(pname, data.variable_name, nil)
+ yl_speak_up.show_fs(player, "msg", {
+ input_to = "yl_speak_up:"..formspec_input_to,
+ formspec = "size[10,2]"..
+ "label[0.2,0.0;Trying to delete variable \""..
+ minetest.formspec_escape(tostring(data.variable_name))..
+ "\":\n"..text.."]"..
+ "button[1.5,1.5;2,0.9;back_from_error_msg;Back]"})
+ return
+ end
+
+ -- the player wants to change/edit a precondition or effect
+ if(not(fields.back)
+ and (fields.change_element or fields.select_what or fields.select_trade
+ or fields.select_inv or fields.select_block
+ or fields.inv_list_name
+ or fields.select_deal_with_offered_item
+ or fields.select_accept_group
+ or fields.select_match_stack_size
+ or fields.select_variable or fields.select_operator
+ or fields.select_on_failure
+ or fields.select_on_action_failure
+ or fields.back_from_error_msg
+ or fields.store_item_name
+ or fields.select_other_o_id
+ or fields.select_fulfilled
+ or fields.select_function_name
+ or fields.entity_type
+ or was_changed
+ or fields.key_enter
+ or fields.quit
+ -- return was pressed
+ or fields.key_enter_field)) then
+ yl_speak_up.show_fs(player, formspec_input_to)
+ return
+ end
+
+ -- go back to the edit option dialog
+ yl_speak_up.show_fs(player, "edit_option_dialog",
+ {n_id = n_id, d_id = d_id, o_id = o_id, caller= formspec_input_to})
+end
+
+
+yl_speak_up.build_fs_edit_option_related = function(player, table_click_result,
+ id_prefix, element_list_name, max_entries_allowed,
+ element_desc, tmp_data_cache,
+ what_do_you_want_txt,
+ values_what, values_operator, values_block, values_trade, values_inv,
+ check_what, check_operator, check_block, check_trade, check_inv,
+ get_sorted_player_var_list_function,
+ show_element_function,
+ table_of_name,
+ text_variable, text_select_operator, text_select_value,
+ text_block_position)
+ if(not(player)) then
+ return ""
+ end
+ local pname = player:get_player_name()
+ -- what are we talking about?
+ local n_id = yl_speak_up.speak_to[pname].n_id
+ local d_id = yl_speak_up.speak_to[pname].d_id
+ local o_id = yl_speak_up.speak_to[pname].o_id
+ local x_id = yl_speak_up.speak_to[pname][ id_prefix.."id" ]
+
+ -- this only works in edit mode
+ if(not(n_id) or yl_speak_up.edit_mode[pname] ~= n_id) then
+ return "size[1,1]label[0,0;You cannot edit this NPC.]"
+ end
+
+ local dialog = yl_speak_up.speak_to[pname].dialog
+ 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 "size[4,1]label[0,0;Dialog option does not exist.]"
+ end
+
+ local elements = dialog.n_dialogs[d_id].d_options[o_id][ element_list_name ]
+ if(not(elements)) then
+ elements = {}
+ end
+
+ -- did we arrive here through clicking on an element in the dialog edit options menu?
+ if(table_click_result or elements[ table_click_result ]) then
+ if(not(elements[ table_click_result ])) then
+ -- which element has the player selected?
+ local sorted_key_list = yl_speak_up.sort_keys(elements)
+ local selected = minetest.explode_table_event(table_click_result)
+ -- use "new" if nothing fits
+ x_id = "new"
+ if((selected.type == "CHG" or selected.type == "DLC")
+ and selected.row <= #sorted_key_list) then
+ x_id = sorted_key_list[ selected.row ]
+ end
+
+ if( x_id == "new" and #sorted_key_list >= max_entries_allowed) then
+ return "size[9,1.5]"..
+ "label[0.2,0.0;There are only up to "..
+ minetest.formspec_escape(yl_speak_up.max_result_effects)..
+ " "..element_desc.."s allowed per dialog option.]"..
+ "button[2.0,0.8;1.0,0.9;back;Back]"
+ end
+ else
+ -- allow to directly specify a x_id to show
+ x_id = table_click_result
+ end
+
+ local show_var_usage = ""
+ if(x_id
+
+ and elements[ x_id ]
+ and elements[ x_id ][ id_prefix.."type"]
+ and elements[ x_id ][ id_prefix.."type"] == "state"
+ and elements[ x_id ][ id_prefix.."variable"]) then
+ show_var_usage = "button[12.0,1.8;6.5,0.9;show_var_usage;"..
+ "Show where this variable is used]"
+ end
+ -- store which element we are talking about
+ yl_speak_up.speak_to[pname][ id_prefix.."id" ] = x_id
+ -- nothing selected yet
+ yl_speak_up.speak_to[pname][ tmp_data_cache ] = nil
+ -- display the selected element
+ if(x_id ~= "new") then
+ return "size[20,3]"..
+ "bgcolor[#00000000;false]"..
+ "label[0.2,0.5;Selected "..element_desc..":]"..
+ "tablecolumns[text;color,span=1;text;text]"..
+ "table[0.2,0.8;19.6,0.7;"..table_of_name..";"..
+ minetest.formspec_escape(elements[ x_id ][ id_prefix.."id"])..
+ ",#FFFF00,"..
+ minetest.formspec_escape(elements[ x_id ][ id_prefix.."type"])..
+ ","..
+ minetest.formspec_escape(
+ show_element_function(elements[ x_id ], pname))..";0]"..
+ "button[2.0,1.8;1.5,0.9;delete_element;Delete]"..
+ "button[4.0,1.8;1.5,0.9;change_element;Change]"..
+ "button[6.0,1.8;5.5,0.9;back;Back to edit dialog option \""..
+ tostring(o_id).."\"]"..
+ show_var_usage
+ end
+ end
+
+ local data = yl_speak_up.speak_to[pname][ tmp_data_cache ]
+
+ if(not(data) or not(data.what)) then
+ data = { what = 1}
+ end
+ -- fallback
+ if(not(x_id)) then
+ x_id = "new"
+ end
+
+ local e = nil
+ -- does the element exist already? if so: use the existing values as presets for data
+ -- (so that the element can be edited)
+ -- does kind of the opposite than the saving of values starting in line 323 of this file
+ if(x_id ~= "new" and data.what == 1 and elements[ x_id ]) then
+ e = elements[ x_id ]
+ if( id_prefix == "r_" and e[ "r_type" ] == "dialog") then
+ -- dialog effects cannot be edited this way
+ return "size[9,2]"..
+ "label[0.2,0.5;Effects of the type \"dialog\" cannot be edited this way.\n"..
+ "Use the edit options or dialog menu to change the target dialog.]"..
+ "button[1.5,1.5;2,0.9;back_from_cannot_be_edited;Back]"
+ end
+ if( id_prefix == "p_" and e[ "p_type" ] == "item") then
+ -- the staff-based item precondition can be translated to an editable
+ -- inventory precondition which is equal
+ e[ "p_type" ] = "player_inv"
+ e[ "p_itemstack" ] = e[ "p_value"]
+ e[ "p_value" ] = "inv_contains"
+ end
+
+ data.what = table.indexof(values_what, e[ id_prefix.."type" ])
+ if(data.what == -1) then
+ data.what = 1
+
+ -- npc_gives/npc_wants (action)
+ -- (two seperate functions, but can be handled here together)
+ elseif(data.what and id_prefix == "a_" and (data.what == 4 or data.what == 5)) then
+ data.action_failure_dialog = e[ "a_on_failure" ]
+ -- data.item_string is used to show a background image
+ data.item_string = e[ "a_value"] -- stack name and count (as string)
+ data.item_desc = e[ "a_item_desc" ]
+ data.item_quest_id = e[ "a_item_quest_id" ]
+
+ -- player_offered_item precondition
+ elseif(data.what and id_prefix == "p_" and (data.what == 8)) then
+ -- data.item_string is used to show a background image
+ data.item_string = e[ "p_value"] -- stack name and count (as string)
+ data.item_desc = e[ "p_item_desc" ]
+ data.item_quest_id = e[ "p_item_quest_id" ]
+ data.item_group = e[ "p_item_group" ]
+ data.item_stack_size = e["p_item_stack_size"]
+ data.match_stack_size = e["p_match_stack_size"]
+ end
+
+ if(e[ "alternate_text"]) then
+ data.alternate_text = e[ "alternate_text" ]
+ end
+ -- write that data back
+ yl_speak_up.speak_to[pname][ tmp_data_cache ] = data
+ end
+
+ local save_button = "button[5.0,12.2;1,0.7;save_element;Save]"
+ local formspec =
+ "size[20,13]"..
+ "label[5,0.5;Edit "..element_desc.." \""..minetest.formspec_escape(x_id)..
+ "\" of option \""..minetest.formspec_escape(tostring(o_id))..
+ "\" of dialog \""..minetest.formspec_escape(tostring(d_id)).."\"]"..
+ "label[0.2,1.5;"..what_do_you_want_txt.."]"..
+ "label[0.2,2.0;Something regarding...]"..
+ "dropdown[4.0,1.8;14.0,0.6;select_what;"..
+ table.concat(check_what, ",")..";"..
+ tostring(data.what)..";]"..
+ "button[3.0,12.2;1,0.7;back;Abort]"
+
+ if(id_prefix ~= "a_") then
+ formspec = formspec..
+ "label[1,10.5;If you are unsure if your setup of pre(C)onditions and (Ef)fects "..
+ "works as intended,\ntype \"/npc_talk debug "..tostring(n_id).."\" "..
+ "in chat in order to enter debug mode. You can leave it with "..
+ "\"/npc_talk debug off\".]"
+ end
+
+
+ if(data.what) then
+ yl_speak_up.speak_to[pname][ tmp_data_cache ] = data
+ end
+ local what_type = ""
+ if(data and data.what and values_what[ data.what ]) then
+ what_type = values_what[ data.what ]
+ end
+ -- "an internal state (i.e. of a quest)", -- 2
+ -- (state is the second offered option in both preconditions and effects list)
+ if(data.what and what_type == "state" and id_prefix ~= "a_") then
+ return yl_speak_up.get_sub_fs_edit_option_p_and_e_state(
+ pname, dialog, formspec, data, id_prefix, save_button, e,
+ text_variable, text_select_value, text_select_operator,
+ values_operator, check_operator, get_sorted_player_var_list_function )
+
+ -- "the value of a property of the NPC (for generic NPC)"
+ elseif(data.what and what_type == "property" and id_prefix ~= "a_") then
+ return yl_speak_up.get_sub_fs_edit_option_p_and_e_property(
+ pname, dialog, formspec, data, id_prefix, save_button, e,
+ text_select_operator, values_operator, check_operator)
+
+ -- "something that has to be calculated or evaluated (=call a function)"
+ elseif(data.what and what_type == "evaluate") then
+ return yl_speak_up.get_sub_fs_edit_option_p_and_e_evaluate(
+ pname, dialog, formspec, data, id_prefix, save_button, e,
+ text_select_operator, values_operator, check_operator)
+
+ -- "a block somewhere", -- 3
+ -- (block is the third offered option in both preconditions and effects list)
+ elseif(data.what and what_type == "block" and id_prefix ~= "a_") then
+ return yl_speak_up.get_sub_fs_edit_option_p_and_e_block(
+ pname, dialog, formspec, data, id_prefix, save_button, e,
+ text_block_position, values_block, check_block)
+
+ -- "a trade", -- 4
+ -- (trade - only for preconditions; effects have something else here)
+ elseif(data.what and id_prefix == "p_" and what_type == "trade") then
+ return yl_speak_up.get_sub_fs_edit_option_precondition_trade(
+ pname, dialog, formspec, data, id_prefix, save_button, e,
+ values_trade, check_trade)
+
+ -- "the type of the entity of the NPC",
+ -- (entity_type - only for preconditions)
+ elseif(data.what and id_prefix == "p_" and what_type == "entity_type") then
+ return yl_speak_up.get_sub_fs_edit_option_precondition_entity_type(
+ pname, dialog, formspec, data, id_prefix, save_button, e,
+ values_trade, check_trade)
+
+ -- "the inventory of the player", -- 5
+ -- "the inventory of the NPC", -- 6
+ -- "the inventory of a block somewhere", -- 7
+ -- "put item from the NPC's inventory into a chest etc.", -- 4 (effect)
+ -- "take item from a chest etc. and put it into the NPC's inventory", -- 5 (effect)
+ -- (inventory - only for preconditions; effects have something else here)
+ elseif((data.what and id_prefix == "p_"
+ and (what_type == "player_inv" or what_type == "npc_inv" or what_type == "block_inv"))
+ or (data.what and id_prefix == "r_"
+ and (what_type == "put_into_block_inv" or what_type == "take_from_block_inv"))) then
+ -- the inventory of a block needs more input options (in particular block selection)
+ data.what_type = what_type
+ return yl_speak_up.get_sub_fs_edit_option_precondition_inv(
+ pname, dialog, formspec, data, id_prefix, save_button, e,
+ values_inv, check_inv, values_block)
+
+ elseif(data.what and id_prefix == "r_" and what_type == "deal_with_offered_item") then
+ return yl_speak_up.get_sub_fs_edit_option_effect_deal_with_offered_item(
+ pname, dialog, formspec, data, id_prefix, save_button, e)
+
+ -- "give item (created out of thin air) to player (requires yl_speak_up.npc_privs_priv priv)", -- 9
+ -- "take item from player and destroy it (requires yl_speak_up.npc_privs_priv priv)", -- 10
+ elseif(data.what and id_prefix == "r_" and (what_type == "give_item" or what_type=="take_item")) then
+ return yl_speak_up.get_sub_fs_edit_option_effect_give_item_or_take_item(
+ pname, dialog, formspec, data, id_prefix, save_button, e)
+
+ -- "move the player to a given position (requires yl_speak_up.npc_privs_priv priv)", -- 11
+ elseif(data.what and id_prefix == "r_" and what_type == "move") then
+ return yl_speak_up.get_sub_fs_edit_option_effect_move(
+ pname, dialog, formspec, data, id_prefix, save_button, e)
+
+ -- "execute Lua code (requires npc_master priv)", -- precondition: 8; effect: 12
+ elseif((data.what and id_prefix == "p_" and what_type == "function")
+ or (data.what and id_prefix == "r_" and what_type == "function")) then
+ return yl_speak_up.get_sub_fs_edit_option_p_and_e_function(
+ pname, dialog, formspec, data, id_prefix, save_button, e)
+
+ -- "NPC crafts something", -- 6
+ -- (craft - only for effects - not for preconditions)
+ elseif(data.what and id_prefix == "r_" and what_type == "craft") then
+ return yl_speak_up.get_sub_fs_edit_option_effect_craft(
+ pname, dialog, formspec, data, id_prefix, save_button, e)
+
+ -- "go to other dialog if the *previous* effect failed", -- 5
+ -- (on_failure - only for effects - not for preconditions)
+ elseif(data.what and id_prefix == "r_" and what_type == "on_failure") then
+ return yl_speak_up.get_sub_fs_edit_option_effect_on_failure(
+ pname, dialog, formspec, data, id_prefix, save_button, e)
+
+ -- "send a chat message to all players" -- 8
+ elseif(data.what and id_prefix == "r_" and what_type == "chat_all") then
+ return yl_speak_up.get_sub_fs_edit_option_effect_chat_all(
+ pname, dialog, formspec, data, id_prefix, save_button, e)
+
+ -- "Normal trade - one item(stack) for another item(stack).", -- 3
+ elseif(data.what and id_prefix == "a_" and what_type == "trade") then
+ return yl_speak_up.get_sub_fs_edit_option_action_trade(
+ pname, dialog, formspec, data, id_prefix, save_button, e)
+
+ -- "The NPC gives something to the player (i.e. a quest item).", -- 4
+ -- (only for actions)
+ elseif(data.what and id_prefix == "a_" and what_type == "npc_gives") then
+ return yl_speak_up.get_sub_fs_edit_option_action_npc_gives(
+ pname, dialog, formspec, data, id_prefix, save_button, e)
+
+ -- "The player is expected to give something to the NPC (i.e. a quest item).", -- 5
+ -- (only for actions)
+ -- "an item the player offered/gave to the NPC", (as precondition)
+ elseif(data.what and ((id_prefix == "a_" and what_type == "npc_wants")
+ or (id_prefix == "p_" and what_type == "player_offered_item"))) then
+ return yl_speak_up.get_sub_fs_edit_option_action_npc_wants_or_accepts(
+ pname, dialog, formspec, data, id_prefix, save_button, e)
+
+ -- "The player has to manually enter a password or passphrase or some other text.", -- 6
+ -- (only for actions)
+ elseif(data.what and id_prefix == "a_" and what_type == "text_input") then
+ return yl_speak_up.get_sub_fs_edit_option_action_text_input(
+ pname, dialog, formspec, data, id_prefix, save_button, e)
+
+ -- "Call custom functions that are supposed to be overridden by the server.", -- 7
+ -- precondition: 9; action: 7; effect: 13
+ elseif(data.what
+ and ((id_prefix == "a_" and what_type == "custom")
+ or (id_prefix == "p_" and what_type == "custom")
+ or (id_prefix == "r_" and what_type == "custom"))) then
+ return yl_speak_up.get_sub_fs_edit_option_all_custom(
+ pname, dialog, formspec, data, id_prefix, save_button, e)
+
+ -- "The preconditions of another dialog option are fulfilled/not fulfilled.", -- 10
+ -- precondition: 9
+ elseif(data.what and id_prefix == "p_" and what_type == "other") then
+ return yl_speak_up.get_sub_fs_other_option_preconditions(
+ pname, dialog, formspec, data, id_prefix, save_button, e)
+ end
+ -- create a new precondition, action or effect
+ return formspec..save_button
+end
+
+
+----------------------------------------------------------------------------
+-- begin of formspecs for types of preconditions, actions and effects
+
+-- helper function for "state", "property" and "evaluate";
+-- shows dropdown for operator and input field for comparison value var_cmp_value
+yl_speak_up.get_sub_fs_operator_based_comparison = function(data, id_prefix, save_button, e,
+ values_operator, check_operator,
+ what_is_this, text_what_is_this,
+ text_select_operator, text_select_value)
+ if(e) then
+ data.operator = math.max(1,table.indexof(values_operator, e[ id_prefix.."operator" ]))
+ data.var_cmp_value = e[ id_prefix.."var_cmp_value" ]
+ end
+ if(not(data[what_is_this]) or data[what_is_this] == "" or tostring(data[what_is_this]) == -1
+ or tostring(data[what_is_this]) == "0") then
+ -- not enough selected yet for saving
+ save_button = ""
+ elseif(not(data.operator) or data.operator == 1) then
+ data.operator = 1
+ save_button = ""
+ end
+ local field_for_value = "field[11.7,4.8;7.5,0.6;var_cmp_value;;"..
+ minetest.formspec_escape(data.var_cmp_value or "- enter value -").."]"
+ -- do not show value input field for unary operators
+ -- (unary operators are diffrent for prerequirements and effects)
+ if(not(data.operator)
+ or (id_prefix == "p_" and (data.operator == 1 or (data.operator>=8 and data.operator<11)))
+ -- "unset", "set_to_current_time"
+ or (id_prefix == "r_" and (data.operator == 3 or data.operator == 4))) then
+ field_for_value = "label[11.7,5.1;- not used for this operator -]"
+ end
+ -- the list of available variables needs to be extended with the ones
+ -- the player has read access to, and the order has to be constant
+ -- (because dropdown just returns an index)
+ return "label[0.2,3.3;"..text_what_is_this.."]"..
+ "label[0.2,4.3;Name of "..what_is_this..":]"..
+ "label[7.0,4.3;"..text_select_operator.."]"..
+ "dropdown[7.0,4.8;4.5,0.6;select_operator;"..
+ table.concat(check_operator, ",")..";"..
+ tostring(data.operator)..";]"..
+ "label[11.7,4.3;"..text_select_value.."]"..
+ field_for_value..
+ save_button
+end
+
+
+-- "an internal state (i.e. of a quest)", -- 2
+-- (state is the second offered option in both preconditions and effects list)
+yl_speak_up.get_sub_fs_edit_option_p_and_e_state = function(
+ pname, dialog, formspec, data, id_prefix, save_button, e,
+ text_variable, text_select_value, text_select_operator,
+ values_operator, check_operator, get_sorted_player_var_list_function )
+ -- the list of available variables needs to be extended with the ones
+ -- the player has read access to, and the order has to be constant
+ -- (because dropdown just returns an index)
+ local var_list = get_sorted_player_var_list_function(pname)
+ local var_list_stripped = yl_speak_up.strip_pname_from_varlist(var_list, pname)
+ if(e) then
+ data.variable_name = yl_speak_up.strip_pname_from_var(e[ id_prefix.."variable" ], pname)
+ data.variable = table.indexof(var_list, e[ id_prefix.."variable"])
+ end
+ if(not(data.variable) or data.variable < 1) then
+ data.variable = 0
+ end
+ return formspec..
+ yl_speak_up.get_sub_fs_operator_based_comparison(data, id_prefix, save_button, e,
+ values_operator, check_operator, "variable", text_variable,
+ text_select_operator, text_select_value)..
+ "dropdown[0.2,4.8;6.5,0.6;select_variable;"..
+ "- please select -"..var_list_stripped..";"..
+ tostring(data.variable + 1)..";]"..
+ "button[0.2,6.0;4.0,0.6;manage_variables;Manage variables]"..
+ "button[4.7,6.0;6.5,0.6;show_var_usage_edit_element;Show where this variable is used]"..
+ "hypertext[1.2,7.0;16.0,2.5;some_text;"..
+ "Note: Each variable is player-specific and will be set and "..
+ "checked for the player that currently talks to your NPC.\n"..
+ "Note: You can set a variable to the current time in an effect. "..
+ "After that, use a precondition to check if that variable was set \"more "..
+ "than x seconds ago\" or \"less than x seconds ago\". This can be "..
+ "useful for prevending your NPC from handing out the same quest item again "..
+ "too quickly (players are inventive and may use your quest item for their "..
+ "own needs).\n]"
+end
+
+
+-- "the value of a property of the NPC (for generic NPC)"
+yl_speak_up.get_sub_fs_edit_option_p_and_e_property = function(
+ pname, dialog, formspec, data, id_prefix, save_button, e,
+ text_select_operator, values_operator, check_operator)
+ if(e) then
+ data.property = e[ id_prefix.."value"]
+ end
+ local operator_list = {}
+ for i, v in ipairs(check_operator) do
+ local v2 = values_operator[i]
+ if( v2 ~= "quest_step_done" and v2 ~= "quest_step_not_done"
+ and v2 ~= "true_for_param" and v2 ~= "false_for_param") then
+ table.insert(operator_list, v)
+ end
+ end
+ local text_compare_with = "Compare property with this value:"
+ if(id_prefix == "r_") then
+ text_select_operator = "Set property to:"
+ text_compare_with = "New value:"
+ end
+ -- the list of available variables needs to be extended with the ones
+ return formspec..
+ yl_speak_up.get_sub_fs_operator_based_comparison(data, id_prefix, save_button, e,
+ values_operator, operator_list, "property",
+ "The NPC shall have the following property:",
+ text_select_operator, text_compare_with)..
+ "field[1.0,4.8;5.0,0.6;property;;"..
+ minetest.formspec_escape(data.property or "- enter name -").."]"..
+ "hypertext[1.2,7.0;16.0,2.5;some_text;"..
+ "Note: Properties are useful for NPC that have a generic "..
+ "behaviour and may vary their behaviour slightly.\n"..
+ "Properties starting with \"server\" can only be set or changed by "..
+ "players with the \"npc_talk_admin\" privilege."..
+ "]"
+end
+
+
+-- "something that has to be calculated or evaluated (=call a function)"
+yl_speak_up.get_sub_fs_edit_option_p_and_e_evaluate = function(
+ pname, dialog, formspec, data, id_prefix, save_button, e,
+ text_select_operator, values_operator, check_operator)
+ local fun_list = {}
+ for k, v in pairs(yl_speak_up["custom_functions_"..id_prefix]) do
+ table.insert(fun_list, v["description"] or k)
+ end
+ table.sort(fun_list)
+ local func_selected = 0
+
+ local func_data = nil
+ if(e) then
+ --data.function_name = e[ id_prefix.."value"]
+ data.function_name = e[ id_prefix.."value"]
+ for i = 1, 9 do
+ local s = "param"..tostring(i)
+ if(e[id_prefix..s]) then
+ data[s] = e[id_prefix..s]
+ end
+ end
+ end
+ local add_description = "Nothing selected."
+ if(data.function_name) then
+ func_data = yl_speak_up["custom_functions_"..id_prefix][data.function_name]
+ -- add the fields for param1..param9:
+ if(func_data) then
+ local xoff = 0
+ for i = 1, 9 do
+ if(i > 5) then
+ xoff = 10
+ end
+ local paramn = "param"..tostring(i)
+ local s = func_data[paramn.."_text"]
+ if(s) then
+ formspec = formspec..
+ "label["..(0.2 + xoff)..","..(6.05 + ((i-1)%5)*0.8)..";"..
+ minetest.formspec_escape(s).."]"..
+ "field["..(4.0 + xoff)..","..(5.8 + ((i-1)%5)*0.8)..
+ ";5.0,0.6;set_"..paramn..";;"..
+ minetest.formspec_escape(
+ data[paramn] or "").."]"..
+ "tooltip[set_"..paramn..";"..
+ minetest.formspec_escape(
+ func_data[paramn.."_desc"] or "?").."]"
+ end
+ end
+ func_selected = table.indexof(fun_list, func_data["description"])
+ add_description = func_data["description"]
+ -- necessary so that the save_button can be shown
+ data["function"] = func_selected
+ end
+ end
+ local operator_list = {}
+ for i, v in ipairs(check_operator) do
+ local v2 = values_operator[i]
+ if( v2 ~= "quest_step_done" and v2 ~= "quest_step_not_done"
+ and v2 ~= "true_for_param" and v2 ~= "false_for_param") then
+ table.insert(operator_list, v)
+ end
+ end
+ local text_operator_and_comparison = ""
+ local explanation = ""
+ local dlength = "6.5"
+ if(id_prefix == "p_") then
+ text_operator_and_comparison = yl_speak_up.get_sub_fs_operator_based_comparison(
+ data, id_prefix, save_button, e,
+ values_operator, operator_list, "function",
+ "Execute and evaluate the following function:",
+ "Operator for checking result:", "Compare the return value with this value:")
+ explanation =
+ "Note: Functions are called with parameters which are passed on to them. "..
+ "The function then calculates a result. This result can be compared to a given "..
+ "value. What the function calculates and what it returns depends on its "..
+ "implementation."
+ else
+ dlength = "15" -- we have more room for the dropdown here
+ if(not(data["function"]) or data["function"]=="") then
+ save_button = ""
+ end
+ text_operator_and_comparison =
+ "label[0.2,3.3;Execute the following function:]"..
+ "label[0.2,4.3;Name of function:]"..
+ save_button
+ explanation =
+ "Note: Functions are called with parameters which are passed on to them. "..
+ "Functions used as effects/results ought to change something, i.e. set a "..
+ "variable to a new value."
+ if(id_prefix == "a_") then
+ explanation =
+ "Note: Functions are called with parameters which are passed on to "..
+ "them. Functions used as actions need to return a valid formspec. This "..
+ "formspec is then displayed to the player. The clicks the player does in "..
+ "that formspec are sent to another custom function linked to it."
+ end
+ end
+ -- the list of available variables needs to be extended with the ones
+ return formspec..
+ text_operator_and_comparison..
+ -- show the description of the function again (the space in the dropdown menu is a bit
+ -- limited)
+ "label[7.5,3.3;"..minetest.formspec_escape(add_description).."]"..
+ "dropdown[0.2,4.8;"..dlength..",0.6;select_function_name;"..
+ "- please select -,"..table.concat(fun_list, ",")..";"..
+ tostring(func_selected + 1)..";]"..
+ "hypertext[1.2,9.6;16.0,2.5;some_text;"..
+ explanation..
+ "]"
+end
+
+
+-- helper function for:
+-- yl_speak_up.get_sub_fs_edit_option_p_and_e_block
+yl_speak_up.get_block_pos_info = function(pname, data, id_prefix, e, values_block, ignore_protection)
+ -- are we more intrested in the inventory of the block or in the block itself?
+ local looking_at_inventory = false
+ if(data and data.what_type
+ and (data.what_type == "block_inv"
+ or data.what_type == "put_into_block_inv"
+ or data.what_type == "take_from_block_inv")) then
+ looking_at_inventory = true
+ end
+ -- did the player get here through punching a block in the meantime?
+ local block_pos = yl_speak_up.speak_to[pname].block_punched
+ yl_speak_up.speak_to[pname].block_punched = nil
+ if(e and e[id_prefix.."pos"]) then
+ -- if we are not looking for the inventory of a block:
+ if(looking_at_inventory) then
+ data.block = math.max(1,table.indexof(values_block, e[ id_prefix.."value" ]))
+ end
+ data.node_data = {}
+ data.node_data.data = e[ id_prefix.."node" ]
+ data.node_data.param2 = e[ id_prefix.."param2" ]
+ data.block_pos = {x=e[ id_prefix.."pos" ].x,
+ y=e[ id_prefix.."pos" ].y,
+ z=e[ id_prefix.."pos" ].z}
+ -- the block below was punched
+ if(id_prefix == "p_" and data.block == 5) then
+ data.block_pos.y = data.block_pos.y - 1
+ end
+ end
+ local block_pos_str = "- none set -"
+ local node = {name = "- unknown -", param2 = "- unkown -"}
+ if(not(block_pos) and data and data.block_pos) then
+ block_pos = data.block_pos
+ end
+ local error_is_protected = ""
+ if(block_pos) then
+ -- store for later usage
+ data.block_pos = block_pos
+ local tmp_pos = {x=block_pos.x, y=block_pos.y, z=block_pos.z}
+ -- "I can't punch it. The block is as the block *above* the one I punched.",
+ -- (only valid for preconditions; not for effects - because the player and
+ -- his NPC need to be able to build there)
+ if(data.block and id_prefix == "p_" and data.block == 5) then
+ tmp_pos.y = block_pos.y + 1
+ end
+ -- effects (and, likewise, preconditions): the player at least has to be able to
+ -- build at that position - check that
+ if(not(ignore_protection) and minetest.is_protected(tmp_pos, pname)) then
+ error_is_protected = "label[0.2,7.8;Error: "..
+ "The position you punched is protected. It cannot be used by "..
+ "your NPC for checks or building. Please select a diffrent block!]"
+ block_pos = nil
+ data.block_pos = nil
+ else
+ block_pos_str = minetest.pos_to_string(tmp_pos)
+ node = minetest.get_node_or_nil(tmp_pos)
+ if(not(node)) then
+ node = {name = "- unknown -", param2 = "- unkown -"}
+ end
+ -- "There shall be air instead of this block.",
+ -- (only valid for preconditions)
+ if(data.block and id_prefix == "p_" and data.block == 3) then
+ node = {name = "air", param2 = 0}
+ end
+ -- cache that (in case a sapling grows or someone else changes it)
+ data.node_data = node
+ end
+ end
+ local show_save_button = true
+ if(node.name == "- unknown -") then
+ show_save_button = false
+ end
+ -- if we are dealing with the *inventory* of a block, the state of the block is of no intrest here
+ if(not(looking_at_inventory) and (not(data.block) or data.block == 1)) then
+ data.block = 1
+ -- not enough selected yet for saving
+ show_save_button = false
+ end
+
+ return {block_pos = block_pos, block_pos_str = block_pos_str, node = node,
+ error_is_protected = error_is_protected,
+ show_save_button = show_save_button}
+end
+
+
+
+-- "a block somewhere", -- 3
+-- (block is the third offered option in both preconditions and effects list)
+yl_speak_up.get_sub_fs_edit_option_p_and_e_block = function(
+ pname, dialog, formspec, data, id_prefix, save_button, e,
+ text_block_position, values_block, check_block)
+
+ local res = yl_speak_up.get_block_pos_info(pname, data, id_prefix, e, values_block, false)
+ if(not(res.show_save_button)) then
+ save_button = ""
+ end
+ return formspec..
+ "label[0.2,3.3;"..text_block_position.."]"..
+ "dropdown[4.0,3.5;16.0,0.6;select_block;"..
+ table.concat(check_block, ",")..";"..
+ tostring(data.block)..";]"..
+ "label[0.2,4.8;Position of the block:]"..
+ "label[4.0,4.8;"..minetest.formspec_escape(res.block_pos_str).."]"..
+ "label[0.2,5.8;Name of block:]"..
+ "label[4.0,5.8;"..minetest.formspec_escape(res.node.name).."]"..
+ "label[0.2,6.8;Orientation (param2):]"..
+ "label[4.0,6.8;"..minetest.formspec_escape(res.node.param2).."]"..
+ "button_exit[10.0,5.5;4.0,0.7;select_block_pos;Set position of block]"..
+ "tooltip[select_block_pos;Click on this button to select a block.\n"..
+ "This menu will close and you will be asked to punch\n"..
+ "the block at the position you want to check or change.\n"..
+ "After punching it, you will be returned to this menu.]"..
+ res.error_is_protected..
+ save_button
+end
+
+
+-- "a trade", -- 4
+-- (trade - only for preconditions; effects have something else here)
+yl_speak_up.get_sub_fs_edit_option_precondition_trade = function(
+ pname, dialog, formspec, data, id_prefix, save_button, e,
+ values_trade, check_trade)
+ if(e) then
+ data.trade = math.max(1,table.indexof(values_trade, e[ "p_value" ]))
+ end
+ if(not(data.trade) or data.trade == 1) then
+ data.trade = 1
+ -- not enough selected yet for saving
+ save_button = ""
+ end
+ return formspec..
+ "label[0.2,3.3;If the action is a trade, the following shall be true:]"..
+ "dropdown[4.0,3.5;16.0,0.6;select_trade;"..
+ table.concat(check_trade, ",")..";"..
+ tostring(data.trade)..";]"..
+ save_button
+end
+
+
+-- "the type of the entity of the NPC",
+-- (entity_type - only for preconditions)
+yl_speak_up.get_sub_fs_edit_option_precondition_entity_type = function(
+ pname, dialog, formspec, data, id_prefix, save_button, e,
+ values_trade, check_trade)
+ if(e) then
+ data.entity_type = e[ "p_value" ]
+ end
+ if(not(data.entity_type) or data.entity_type == "") then
+ -- get the name/type of the current entity
+ if(yl_speak_up.speak_to[pname].obj) then
+ local obj = yl_speak_up.speak_to[pname].obj
+ if(obj) then
+ local entity = obj:get_luaentity()
+ if(entity) then
+ data.entity_type = entity.name
+ end
+ end
+ end
+ end
+ return formspec..
+ "label[0.2,3.3;The entity (this NPC) is of type:]"..
+ "field[5.0,3.0;6.0,0.6;entity_type;;"..(data.entity_type or "").."]"..
+ "label[0.2,4.3;Note: This is only really useful for generic NPCs.]"..
+ save_button
+end
+
+
+-- "the inventory of the player", -- 5
+-- "the inventory of the NPC", -- 6
+-- "the inventory of a block somewhere", -- 7
+-- "put item from the NPC's inventory into a chest etc.", -- 4 (effect)
+-- "take item from a chest etc. and put it into the NPC's inventory", -- 5 (effect)
+-- (inventory - only for preconditions; effects have something else here)
+yl_speak_up.get_sub_fs_edit_option_precondition_inv = function(
+ pname, dialog, formspec, data, id_prefix, save_button, e,
+ values_inv, check_inv, values_block)
+ if(e) then
+ data.inv = math.max(1,table.indexof(values_inv, e["p_value"]))
+ data.inv_stack_name = e[ id_prefix.."itemstack" ]
+ end
+ if(id_prefix == "p_" and (not(data.inv) or data.inv == 1)) then
+ data.inv = 1
+ -- not enough selected yet for saving
+ save_button = ""
+ end
+ local block_selection = ""
+ if(data and data.what_type
+ and (data.what_type == "block_inv"
+ or data.what_type == "put_into_block_inv"
+ or data.what_type == "take_from_block_inv")) then
+ local inv_list_name = ""
+ if(e) then
+ -- not really relevant here but needed for getting the position
+ e[ id_prefix.."value" ] = "node_is_like"
+ inv_list_name = e[ id_prefix.."inv_list_name"]
+ end
+ -- positions of nodes in protected areas are allowed for inventory access
+ local res = yl_speak_up.get_block_pos_info(pname, data, id_prefix, e, values_block, true)
+ if(not(res.show_save_button)) then
+ save_button = ""
+ end
+ -- which inventory lists are available?
+ local tmp = yl_speak_up.get_node_inv_lists(res.block_pos, inv_list_name)
+ block_selection = ""..
+ "label[0.2,7.0;Position of the block:]"..
+ "label[4.0,7.0;"..minetest.formspec_escape(res.block_pos_str).."]"..
+ "label[0.2,7.5;Name of block:]"..
+ "label[4.0,7.5;"..minetest.formspec_escape(res.node.name).."]"..
+ "label[0.2,8.0;Orientation (param2):]"..
+ "label[4.0,8.0;"..minetest.formspec_escape(res.node.param2).."]"..
+ "label[0.2,8.5;Inventory list name:]"..
+ "dropdown[4.0,8.2;3.8,0.6;inv_list_name;"..
+ table.concat(tmp.inv_lists, ",")..";"..
+ tostring(tmp.index)..";]"..
+ "button_exit[0.2,9.0;4.0,0.7;select_block_pos;Set position of block]"..
+ "tooltip[select_block_pos;Click on this button to select a block.\n"..
+ "This menu will close and you will be asked to punch\n"..
+ "the block at the position you want to check or change.\n"..
+ "After punching it, you will be returned to this menu.]"
+ end
+ local intro = ""
+ -- for preconditions: contain/does not contain item, is empty, ..
+ if(id_prefix == "p_") then
+ intro = "label[0.2,3.0;The following shall be true about the inventory:]"..
+ "dropdown[4.0,3.2;16.0,0.6;select_inv;"..
+ table.concat(check_inv, ",")..";"..
+ tostring(data.inv)..";]"
+ -- for results/effects:
+ elseif(data.what_type == "put_into_block_inv") then
+ intro = "label[0.2,3.0;The NPC shall put the following item from his inventory "..
+ "into the given block's inventory:]"
+ elseif(data.what_type == "take_from_block_inv") then
+ intro = "label[0.2,3.0;The NPC shall take the following item from the given block's "..
+ "inventory and put it into his own inventory:]"
+ end
+ return formspec..
+ intro..
+ yl_speak_up.fs_your_inventory_select_item(pname, data)..
+ block_selection..
+ save_button
+end
+
+
+-- "an item the player offered to the NPC"
+yl_speak_up.get_sub_fs_edit_option_effect_deal_with_offered_item = function(
+ pname, dialog, formspec, data, id_prefix, save_button, e)
+ if(e) then
+ data.select_deal_with_offered_item = table.indexof(
+ yl_speak_up.dropdown_values_deal_with_offered_item,
+ e[ "r_value" ])
+ end
+ if(not(data) or not(data.select_deal_with_offered_item)
+ or data.select_deal_with_offered_item < 2) then
+ save_button = ""
+ data.select_deal_with_offered_item = 1
+ end
+ return formspec..
+ "label[0.2,3.3;The NPC shall:]"..
+ "dropdown[4.0,3.0;15.0,0.7;select_deal_with_offered_item;"..
+ table.concat(yl_speak_up.dropdown_list_deal_with_offered_item, ",")..";"..
+ tostring(data.select_deal_with_offered_item)..";]"..
+ save_button
+end
+
+
+-- "give item (created out of thin air) to player (requires yl_speak_up.npc_privs_priv priv)", -- 9
+-- "take item from player and destroy it (requires yl_speak_up.npc_privs_priv priv)", -- 10
+yl_speak_up.get_sub_fs_edit_option_effect_give_item_or_take_item = function(
+ pname, dialog, formspec, data, id_prefix, save_button, e)
+ if(e) then
+ data.inv_stack_name = e[ "r_value" ] or ""
+ end
+ local text = "The following item shall be created out of thin air and added to the "..
+ "player's inventory:"
+ local priv_name = "effect_give_item"
+ if(data.what == 10) then
+ text = "The following item shall be removed from the player's inventory and "..
+ "be destroyed:"
+ priv_name = "effect_take_item"
+ end
+ return formspec..
+ "label[0.2,3.0;"..text.."]"..
+ "label[0.2,3.5;Note: You can *save* this effect only if you have the "..
+ "\""..tostring(yl_speak_up.npc_privs_priv).."\" priv!]"..
+ "label[0.2,8.0;"..
+ "And in order to be able to execute it, this NPC\n"..
+ "needs the \""..tostring(priv_name).."\" priv.\n\t"..
+ "Type \"/npc_talk_privs grant "..tostring(yl_speak_up.speak_to[pname].n_id)..
+ " "..tostring(priv_name).."\"\nin order to grant this.]"..
+ yl_speak_up.fs_your_inventory_select_item(pname, data)..
+ save_button
+end
+
+
+-- "move the player to a given position (requires yl_speak_up.npc_privs_priv priv)", -- 11
+yl_speak_up.get_sub_fs_edit_option_effect_move = function(
+ pname, dialog, formspec, data, id_prefix, save_button, e)
+ if(e) then
+ if(e[ "r_value"] and type(e[ "r_value" ]) == "string") then
+ local pos = minetest.string_to_pos(e[ "r_value" ])
+ if(pos) then
+ data.move_to_x = pos.x
+ data.move_to_y = pos.y
+ data.move_to_z = pos.z
+ end
+ end
+ end
+ return formspec..
+ "label[0.2,3.0;Move the player to this position:]"..
+ "label[0.2,3.5;Note: You can *save* this effect only if you have the "..
+ "\""..tostring(yl_speak_up.npc_privs_priv).."\" priv!\n"..
+ "And in order to be able to execute it, this NPC needs the \""..
+ "effect_move_player\" priv.\n\t"..
+ "Type \"/npc_talk_privs grant "..tostring(yl_speak_up.speak_to[pname].n_id)..
+ " effect_move_player\" in order to grant this.]"..
+ "label[0.2,5.3;X:]"..
+ "label[3.7,5.3;Y:]"..
+ "label[7.2,5.3;Z:]"..
+ "field[0.7,5.0;2.0,0.6;move_to_x;;"..(data.move_to_x or "").."]"..
+ "field[4.2,5.0;2.0,0.6;move_to_y;;"..(data.move_to_y or "").."]"..
+ "field[7.7,5.0;2.0,0.6;move_to_z;;"..(data.move_to_z or "").."]"..
+ save_button
+end
+
+
+-- "execute Lua code (requires npc_master priv)", -- precondition: 8; effect: 12
+yl_speak_up.get_sub_fs_edit_option_p_and_e_function = function(
+ pname, dialog, formspec, data, id_prefix, save_button, e)
+ if(e) then
+ if(e[ id_prefix.."value"] and e[ id_prefix.."value"] ~= "") then
+ data.lua_code = e[ id_prefix.."value" ]
+ end
+ end
+ local priv_name = "precon_exec_lua"
+ if(id_prefix == "_r") then
+ priv_name = "effect_exec_lua"
+ end
+ return formspec..
+ "label[0.2,3.0;Execute the following Lua code (ought to return true or false):]"..
+ "label[0.2,3.5;Note: You can *save* this effect only if you have the "..
+ "\"npc_master\" priv!\n"..
+ "And in order to be able to execute it, this NPC needs the \""..
+ tostring(priv_name).."\" priv.\n\t"..
+ "Type \"/npc_talk_privs grant "..tostring(yl_speak_up.speak_to[pname].n_id)..
+ " "..tostring(priv_name).."\" in order to grant this.]"..
+ "textarea[0.2,5.0;20,4.0;lua_code;;"..
+ minetest.formspec_escape(tostring(data.lua_code)).."]"..
+ save_button
+end
+
+
+-- "NPC crafts something", -- 6
+-- (craft - only for effects - not for preconditions)
+yl_speak_up.get_sub_fs_edit_option_effect_craft = function(
+ pname, dialog, formspec, data, id_prefix, save_button, e)
+ if(e) then
+ -- those items can at least be shown as background images
+ data.craftresult = e[ "r_value" ]
+ data.craft_grid = e[ "r_craft_grid"]
+ end
+ local bg_img = ""
+ if(data and data.craftresult and data.craft_grid) then
+ bg_img = "item_image[5.95,8.70;0.7,0.7;"..tostring(data.craftresult).."]"..
+ "image[4.6,8.6;1,1;gui_furnace_arrow_bg.png^[transformR270]"
+ for i, v in ipairs(data.craft_grid) do
+ if(v and v ~= "") then
+ bg_img = bg_img.."item_image["..
+ tostring(1.15 + ((i-1)%3)*1.25)..","..
+ tostring(8.15 + math.floor((i-1)/3)*0.65)..
+ ";0.7,0.7;"..tostring(v).."]"
+ end
+ end
+ end
+ return formspec..
+ "label[8,2.6;Your invnetory:]"..
+ "list[current_player;main;8,3;8,4;]"..
+ "label[1,3.1;Your craft grid:]"..
+ "list[current_player;craft;1,3.5;3,3;]"..
+ "list[current_player;craftpreview;5.8,4.75;1,1;]"..
+ "image[4.6,4.8;1,1;gui_furnace_arrow_bg.png^[transformR270]"..
+ "label[1,8.0;Use your craft grid to show your NPC what to craft "..
+ "and how. Click on \"Save\" to save. Currently stored:]"..
+ bg_img..
+ save_button
+end
+
+
+-- "go to other dialog if the *previous* effect failed", -- 5
+-- (on_failure - only for effects - not for preconditions)
+yl_speak_up.get_sub_fs_edit_option_effect_on_failure = function(
+ pname, dialog, formspec, data, id_prefix, save_button, e)
+ if(e) then
+ data.on_failure = e[ "r_value" ]
+ data.alternate_text = e[ "alternate_text" ]
+ end
+ local dialog = yl_speak_up.speak_to[pname].dialog
+ local sorted_dialog_list = yl_speak_up.get_sorted_dialog_name_list(dialog)
+ local nr = 1
+ if(not(data) or not(data.on_failure)
+ or not(dialog.n_dialogs)
+ or not(dialog.n_dialogs[data.on_failure])) then
+ save_button = ""
+ else
+ local t = dialog.n_dialogs[data.on_failure].d_name or data.on_failure
+ nr = math.max(0, table.indexof(sorted_dialog_list, t))
+ end
+ local on_failure_dialog = ""
+ if(dialog and dialog.n_dialogs and dialog.n_dialogs[ data.on_failure ]) then
+ on_failure_dialog =
+ "label[0.2,5.5;This will switch to dialog \""..
+ minetest.formspec_escape(tostring(data.on_failure)).."\""..
+ yl_speak_up.show_colored_dialog_text(
+ dialog,
+ data,
+ data.on_failure,
+ "1.2,5.8;18.0,2.0;d_text",
+ ", but with the following *modified* text",
+ ":]",
+ "button_edit_effect_on_failure_text_change")
+ end
+ return formspec..
+ "label[0.2,3.3;If the *previous* effect failed,]"..
+ "label[0.2,3.8;switch to the following dialog:]"..
+ "dropdown[5.0,3.5;6.5,0.6;select_on_failure;"..
+ table.concat(sorted_dialog_list, ",")..";"..
+ tostring(nr)..";]"..
+ on_failure_dialog..
+ save_button
+end
+
+
+-- "send a chat message to all players" -- 8
+yl_speak_up.get_sub_fs_edit_option_effect_chat_all = function(
+ pname, dialog, formspec, data, id_prefix, save_button, e)
+ if(e) then
+ data.chat_msg_text = e[ "r_value" ]
+ end
+ local default_text = "$NPC_NAME$ (owned by $OWNER_NAME$) announces: $PLAYER_NAME$ "..
+ "- example; please enter the text -"
+ return formspec..
+ "label[0.2,3.3;Send the following chat message to *all* players:]"..
+ "label[0.2,4.1;Message:]"..
+ "field[2.0,3.8;16.0,0.6;chat_msg_text;;"..
+ minetest.formspec_escape(
+ data.chat_msg_text
+ or default_text).."]"..
+ "label[0.2,5.3;Note: Your chat message needs to contain the following placeholders,"..
+ " which will be replaced automaticly like in dialog texts:"..
+ "\n$NPC_NAME$, $PLAYER_NAME$ and $OWNER_NAME$.]"..
+ save_button
+end
+
+
+-- "Normal trade - one item(stack) for another item(stack).", -- 3
+yl_speak_up.get_sub_fs_edit_option_action_trade = function(
+ pname, dialog, formspec, data, id_prefix, save_button, e)
+ if(e) then
+ data.trade_id = e[ "a_value" ]
+ -- use as background images
+ if(dialog and dialog.trades and dialog.trades[ data.trade_id ]) then
+ data.pay = dialog.trades[ data.trade_id ].pay[1]
+ data.buy = dialog.trades[ data.trade_id ].buy[1]
+ end
+ data.action_failure_dialog = e[ "a_on_failure" ]
+ end
+ local dialog = yl_speak_up.speak_to[pname].dialog
+ local d_id = yl_speak_up.speak_to[pname].d_id
+ local o_id = yl_speak_up.speak_to[pname].o_id
+ if(not(data.trade_id)) then
+ data.trade_id = tostring(d_id).." "..tostring(o_id)
+ end
+ -- show the player which trade is stored
+ local bg_img = ""
+ if(data and data.buy and data.pay) then
+ bg_img = "item_image[1.15,4.35;0.7,0.7;"..tostring(data.pay).."]"..
+ "item_image[6.15,4.35;0.7,0.7;"..tostring(data.buy).."]"
+ end
+ yl_speak_up.speak_to[pname].trade_id = data.trade_id
+ return formspec..
+ "label[8,2.6;Your invnetory:]"..
+ "list[current_player;main;8,3;8,4;]"..
+ "label[0.2,3.1;Configure trade with "..minetest.formspec_escape(dialog.n_npc)..":]"..
+ "label[0.5,3.8;The customer pays:]"..
+ -- show the second slot of the setup inventory in the detached player's inv
+ "list[detached:yl_speak_up_player_"..pname..";setup;2,4.2;1,1;]"..
+ "image[3.5,4.2;1,1;gui_furnace_arrow_bg.png^[transformR270]"..
+ "label[4.0,3.8;"..minetest.formspec_escape(dialog.n_npc or "?").." sells:]"..
+ -- show the second slot of said inventory
+ "list[detached:yl_speak_up_player_"..pname..";setup;5,4.2;1,1;1]"..
+ bg_img..
+ yl_speak_up.set_on_action_failure_dialog(pname, data,
+ "The player shall trade at least once.")..
+ save_button
+end
+
+
+-- "The NPC gives something to the player (i.e. a quest item).", -- 4
+-- (only for actions)
+yl_speak_up.get_sub_fs_edit_option_action_npc_gives = function(
+ pname, dialog, formspec, data, id_prefix, save_button, e)
+ local bg_img = ""
+ if(e) then
+ data.item_quest_id = data.item_quest_id or e["item_quest_id"]
+ data.item_desc = data.item_desc or e["item_desc"]
+ end
+ if(data and (data.item_node_name or data.item_string)) then
+ bg_img = "item_image[1.15,3.65;0.7,0.7;"..
+ tostring(data.item_node_name or data.item_string).."]"
+ end
+ return formspec..
+ "label[8,2.6;Your inventory:]"..
+ "list[current_player;main;8,3;8,4;]"..
+ "label[1,3.1;"..minetest.formspec_escape(dialog.n_npc or "?").." gives:]"..
+ "list[detached:yl_speak_up_player_"..pname..";npc_gives;2,3.5;1,1;]"..
+ "label[3.2,4.0;"..
+ minetest.formspec_escape(
+ data.item_node_name
+ or "- no item set -").."]"..
+ "label[0.2,5.6;Set a description to turn the item into a special\n"..
+ "quest item. Set a special ID (short text) so that\n"..
+ "the player cannot create a fake item. Click on \n"..
+ "\"Save\" to apply the changes.\n"..
+ "You can use placeholders like $PLAYER_NAME$ etc.]"..
+ "label[0.2,8.3;Special ID to set:]"..
+ "field[3.2,8.0;14.5,0.6;action_item_quest_id;;"..
+ minetest.formspec_escape(
+ data.item_quest_id
+ or "- none set -").."]"..
+ "tooltip[action_item_quest_id;"..
+ "Set this to a text that helps *you* to remember what this\n"..
+ "special quest item is for (i.e. \"quest_deliver_augusts_"..
+ "letter\").\n"..
+ "The ID will be extended with the ID of the NPC and the\n"..
+ "name of the player who got this item from the NPC.]"..
+ "label[0.2,9.0;Description to set:]"..
+ "field[3.2,8.7;14.5,0.6;action_item_desc;;"..
+ minetest.formspec_escape(
+ data.item_desc
+ or "- no item set -").."]"..
+ "tooltip[action_item_desc;"..
+ "Set this to a text that helps the *player* to remember what\n"..
+ "this special quest item is for (i.e. \"Letter from August to\n"..
+ "Frederike\" for a piece of paper).\n"..
+ "This description is shown in the inventory on mouseover.]"..
+ bg_img..
+ yl_speak_up.set_on_action_failure_dialog(pname, data,
+ "The player shall take this offered item.")..
+ save_button
+end
+
+
+-- "The player is expected to give something to the NPC (i.e. a quest item).", -- 5
+-- (only for actions)
+-- "an item the player offered/gave to the NPC", (as precondition)
+yl_speak_up.get_sub_fs_edit_option_action_npc_wants_or_accepts = function(
+ pname, dialog, formspec, data, id_prefix, save_button, e)
+ local bg_img = ""
+ local node_name = ""
+ if(e) then
+ data.item_quest_id = data.item_quest_id or e["item_quest_id"]
+ data.item_desc = data.item_desc or e["item_desc"]
+ data.item_group = data.item_group or e["item_group"]
+ data.item_stack_size = data.item_stack_size or e["item_stack_size"]
+ data.match_stack_size = data.match_stack_size or e["match_stack_size"]
+ end
+ if(data and (data.item_node_name or data.item_string)) then
+ node_name = tostring(data.item_node_name or data.item_string)
+ bg_img = "item_image[1.15,3.65;0.7,0.7;"..node_name.."]"
+ end
+ local info_text = ""
+ if(id_prefix == "p_") then
+ local group_list = {minetest.formspec_escape("- no, just this one item -")}
+ -- get node name without amount
+ local parts = node_name:split(" ")
+ local nr = 1
+ local count = 1
+ local amount = tostring(1)
+ -- prepare group_list
+ if(data and parts and minetest.registered_items[ parts[1] ]) then
+ for k,v in pairs(minetest.registered_items[ parts[1] ].groups) do
+ table.insert(group_list, k)
+ count = count + 1
+ if(data.item_group and data.item_group == k) then
+ nr = count
+ end
+ end
+ amount = tostring(parts[2])
+ end
+ local size_list = {"any amount", "exactly "..amount,
+ "less than "..amount, "more than "..amount, "another amount than "..amount}
+ local match_size = 1
+ for i, list_text in ipairs(size_list) do
+ if(data.match_stack_size and data.match_stack_size == list_text:split(" ")[ 1 ]) then
+ match_size = i
+ end
+ end
+ if(data) then
+ data.item_stack_size = amount
+ end
+ info_text =
+ "label[1,2.6;The player offered:]"..
+ "label[6.7,3.1;of:]"..
+ "dropdown[2,2.8;4.5,0.6;select_match_stack_size;"..
+ table.concat(size_list, ",")..";"..
+ tostring(match_size or 1)..";]"..
+ "label[1,4.8;...and also all other items of the group:]"..
+ "dropdown[2,5.1;5.0,0.6;select_accept_group;"..
+ table.concat(group_list, ",")..";"..tostring(nr)..";]"
+ else
+ info_text =
+ "label[1,3.1;"..minetest.formspec_escape(dialog.n_npc or "?").." wants:]"..
+ yl_speak_up.set_on_action_failure_dialog(pname, data,
+ "The player shall give the NPC this item.")
+ end
+ return formspec..
+ "label[8,2.6;Your inventory:]"..
+ "list[current_player;main;8,3;8,4;]"..
+ "list[detached:yl_speak_up_player_"..pname..";npc_wants;2,3.5;1,1;]"..
+ "label[3.2,4.0;"..
+ minetest.formspec_escape(
+ node_name
+ or "- no item set -").."]"..
+ "label[0.2,6.1;If you want a special ID and description, create\n"..
+ "those via the \"NPC gives something to the player\"\n"..
+ "menu option first and insert that item here. Don't\n"..
+ "use other placeholders than $PLAYER_NAME$ for this!]"..
+ "label[0.2,8.3;Expected special ID:]"..
+ "label[4.0,8.3;"..
+ minetest.formspec_escape(
+ data.item_quest_id
+ or "- none set -").."]"..
+ "label[0.2,9.0;Expected description:]"..
+ "label[4.0,9.0;"..
+ minetest.formspec_escape(
+ data.item_desc
+ or "- none set -").."]"..
+ bg_img..
+ info_text..
+ save_button
+end
+
+
+-- "The player has to manually enter a password or passphrase or some other text.", -- 6
+-- (only for actions)
+yl_speak_up.get_sub_fs_edit_option_action_text_input = function(
+ pname, dialog, formspec, data, id_prefix, save_button, e)
+ if(e) then
+ data.quest_question = e[ "a_question" ]
+ data.quest_answer = e[ "a_value" ]
+ data.action_failure_dialog = e[ "a_on_failure" ]
+ end
+ return formspec..
+ "label[0.2,3.3;What to ask the player and which answer to expect:]"..
+ "label[0.2,4.0;Question to show:]"..
+ "field[4.0,3.8;10.0,0.6;quest_question;;"..
+ minetest.formspec_escape(
+ data.quest_question
+ or "Your answer:").."]"..
+ "label[0.2,5.0;Expected answer:]"..
+ "field[4.0,4.8;10.0,0.6;quest_answer;;"..
+ minetest.formspec_escape(
+ data.quest_answer
+ or "- Insert the correct answer here -").."]"..
+ "tooltip[quest_question;"..
+ "This is just a short text that will be shown to remind\n"..
+ "the player what he is asked for. Most of the question\n"..
+ "ought to be part of the normal dialog of the NPC.]"..
+ "tooltip[quest_answer;"..
+ "The correct answer will not be shown to the player.\n"..
+ "What the player enters will be compared to this\n"..
+ "correct value.]"..
+ "tooltip[select_on_action_failure;"..
+ "If the player gives the wrong answer, you can show him\n"..
+ "a diffrent target dialog (i.e. with text \"No, that answer\n"..
+ "was wrong, but please try again!\"). In such a case the\n"..
+ "effects/results of the current dialog option are *not*\n"..
+ "executed.]"..
+ yl_speak_up.set_on_action_failure_dialog(pname, data,
+ "The player shall enter the correct answer.")..
+ save_button
+end
+
+
+-- "Call custom functions that are supposed to be overridden by the server.", -- 7
+-- precondition: 9; action: 7; effect: 13
+yl_speak_up.get_sub_fs_edit_option_all_custom = function(
+ pname, dialog, formspec, data, id_prefix, save_button, e)
+ if(e) then
+ data.custom_param = e[ id_prefix.."value" ]
+ if(id_prefix == "a_") then
+ data.action_failure_dialog = e[ "a_on_failure" ]
+ end
+ end
+ formspec = formspec..
+ "label[0.2,3.3;Note: Calling a custom function will require direct support "..
+ "from the server.]"..
+ "label[0.2,4.0;Parameter for custom function:]"..
+ "field[6.0,3.7;10.0,0.6;custom_param;;"..
+ minetest.formspec_escape(
+ data.custom_param
+ or "- Insert a text that is passed on to your function here -").."]"..
+ "tooltip[custom_param;"..
+ "The custom parameter may help whoever implements the\n"..
+ "custom function to more easily see what it belongs to.\n"..
+ "Dialog and option ID are also passed as parameters.]"
+ if(id_prefix == "a_") then
+ formspec = formspec..
+ "tooltip[select_on_action_failure;"..
+ "If the player gives the wrong answer, you can show him\n"..
+ "a diffrent target dialog (i.e. with text \"No, that answer\n"..
+ "was wrong, but please try again!\"). In such a case the\n"..
+ "effects/results of the current dialog option are *not*\n"..
+ "executed.]"..
+ yl_speak_up.set_on_action_failure_dialog(pname, data,
+ "The player shall click on the right button.")
+ else
+ formspec = formspec..
+ "label[0.3,5.0;Note: Your custom function has to return either true "..
+ "or false.]"
+ end
+ return formspec..save_button
+end
+
+
+-- "The preconditions of another dialog option are fulfilled/not fulfilled.", -- 10
+-- precondition: 10
+yl_speak_up.get_sub_fs_other_option_preconditions = function(
+ pname, dialog, formspec, data, id_prefix, save_button, e)
+ local dialog = yl_speak_up.speak_to[pname].dialog
+ local d_id = yl_speak_up.speak_to[pname].d_id
+ local o_id = yl_speak_up.speak_to[pname].o_id
+ -- only o_id with a *lower* o_sort value are suitable (else evaluation would become
+ -- difficult and loops might be created)
+ local o_id_list = {}
+ local options = dialog.n_dialogs[ d_id ].d_options
+ if(options) then
+ local this_option = options[ o_id ]
+ if(not(this_option) or not(this_option.o_sort)) then
+ this_option = {o_sort = 0}
+ end
+ for k, v in pairs(options) do
+ if(k and v and v.o_sort and v.o_sort < this_option.o_sort) then
+ table.insert(o_id_list, minetest.formspec_escape(k))
+ end
+ end
+ end
+ if(e) then
+ data.other_o_id = e[ "p_value" ]
+ data.fulfilled = e[ "p_fulfilled" ]
+ end
+ local nr = math.max(0, table.indexof(o_id_list, data.other_o_id))
+ nr_fulfilled = 1
+ if(data.fulfilled == "true") then
+ nr_fulfilled = 2
+ elseif(data.fulfilled == "false") then
+ nr_fulfilled = 3
+ end
+ if(nr == 0 or nr_fulfilled == 1) then
+ save_button = ""
+ end
+ return formspec..
+ "label[0.2,3.3;Note: You can only select dialog options with a *lower* o_sort value "..
+ "for this evaluation.]"..
+ "label[0.2,4.0;The preconditions of dialog option:]"..
+ "dropdown[6.0,3.7;3.0,0.6;select_other_o_id;-select-,"..
+ table.concat(o_id_list, ",")..";"..
+ tostring(nr + 1)..";]"..
+ "label[9.2,4.0;..shall be:]"..
+ "dropdown[11,3.7;2.0,0.6;select_fulfilled;-select-,true,false;"..
+ tostring(nr_fulfilled).."]"..
+ "tooltip[select_other_o_id;"..
+ "Sometimes you may need the same preconditions for more than\n"..
+ "one dialog option - or you may need one dialog option to be\n"..
+ "available exactly when another one is *not* available.\n"..
+ "This is what you can do here.]"..
+ "tooltip[select_fulfilled;"..
+ "If you select \"true\" here, then this precondition will be\n"..
+ "fulfilled when all the preconditions of the dialog option you\n"..
+ "selected here are true as well.\n"..
+ "If you select \"false\", this precondition will only be\n"..
+ "fulfilled if the other dialog option you selected here\n"..
+ "is not true.]"..
+ save_button
+end
+
+
+-- end of formspecs for types of preconditions, actions and effects
+----------------------------------------------------------------------------
+
+-- helper function
+yl_speak_up.set_on_action_failure_dialog = function(pname, data, instruction)
+ local dialog = yl_speak_up.speak_to[pname].dialog
+ local nr = 1
+
+ local sorted_dialog_list = yl_speak_up.get_sorted_dialog_name_list(dialog)
+ if(data and data.action_failure_dialog
+ and dialog.n_dialogs
+ and dialog.n_dialogs[data.action_failure_dialog]) then
+ local t = dialog.n_dialogs[data.action_failure_dialog].d_name or data.action_failure_dialog
+ nr = math.max(0, table.indexof(sorted_dialog_list, t)) + 1
+ end
+ local start_at = "9.9;"
+ if(nr and nr > 1) then
+ start_at = "9.7;"
+ end
+ local on_failure_dialog =
+ "label[0.2,"..start_at..tostring(instruction).." If he doesn't, go to dialog:]"..
+ "dropdown[11,9.6;8.0,0.6;select_on_action_failure;"..
+ "- current one -,"..
+ table.concat(sorted_dialog_list, ",")..";"..tostring(nr)..";]"
+ if(nr and nr > 1) then
+ return on_failure_dialog..
+ yl_speak_up.show_colored_dialog_text(
+ dialog,
+ data,
+ sorted_dialog_list[ nr - 1],
+ "1.2,10.2;18.0,1.8;d_text",
+ "label[0.2,10.1;...and show the following *modified* text:]",
+ "",
+ "button_edit_action_on_failure_text_change")
+ end
+ return on_failure_dialog
+end
+
+
+yl_speak_up.edit_mode_set_a_on_failure = function(data, pname, v)
+ if(pname and data and data.action_failure_dialog) then
+ v[ "a_on_failure" ] = yl_speak_up.d_name_to_d_id(
+ yl_speak_up.speak_to[pname].dialog,
+ data.action_failure_dialog)
+ else
+ v[ "a_on_failure" ] = ""
+ end
+end
diff --git a/chat_commands_in_edit_mode.lua b/chat_commands_in_edit_mode.lua
new file mode 100644
index 0000000..686a4d5
--- /dev/null
+++ b/chat_commands_in_edit_mode.lua
@@ -0,0 +1,28 @@
+
+-- Implementation of chat commands that were registered in register_once
+-- (here: only add those additional things needed for edit mode)
+
+local old_command_npc_talk = yl_speak_up.command_npc_talk
+yl_speak_up.command_npc_talk = function(pname, param)
+ if(not(pname)) then
+ return
+ end
+ -- activates edit mode when talking to an NPC; but only if the player can edit that NPC
+ if(param and param == "force_edit") then
+ -- implemented in functions.lua:
+ return yl_speak_up.command_npc_talk_force_edit(pname, rest)
+ end
+ -- not perfect - but at least some help
+ if(param
+ and (param == "help force_edit"
+ or param == "? force_edit"
+ or param == "force_edit help")) then
+ minetest.chat_send_player(pname,
+ "Toggles force edit mode. This is helpful if you cut yourself out "..
+ "of editing an NPC by breaking it. From now on all NPC you will talk to "..
+ "will already be in edit mode (provided you are allowed to edit them)."..
+ "\nIssuing the command again ends force edit mode.")
+ return
+ end
+ return old_command_npc_talk(pname, param)
+end
diff --git a/command_force_edit_mode.lua b/command_force_edit_mode.lua
new file mode 100644
index 0000000..b79fb7a
--- /dev/null
+++ b/command_force_edit_mode.lua
@@ -0,0 +1,24 @@
+
+
+-- allow to enter force edit mode (useful when an NPC was broken)
+yl_speak_up.force_edit_mode = {}
+-- command to enter force edit mode
+yl_speak_up.command_npc_talk_force_edit = function(pname, param)
+ if(not(pname)) then
+ return
+ end
+ if(yl_speak_up.force_edit_mode[pname]) then
+ yl_speak_up.force_edit_mode[pname] = nil
+ minetest.chat_send_player(pname,
+ "Ending force edit mode for NPC. From now on talks "..
+ "will no longer start in edit mode.")
+ else
+ yl_speak_up.force_edit_mode[pname] = true
+ minetest.chat_send_player(pname,
+ "STARTING force edit mode for NPC. From now on talks "..
+ "with NPC will always start in edit mode provided "..
+ "you are allowed to edit this NPC.\n"..
+ "In order to end force edit mode, give the command "..
+ "/npc_talk_force_edit a second time.")
+ end
+end
diff --git a/edit_mode.lua b/edit_mode.lua
new file mode 100644
index 0000000..d71aabf
--- /dev/null
+++ b/edit_mode.lua
@@ -0,0 +1,144 @@
+
+-- if player has npc_talk_owner priv AND is owner of this particular npc:
+-- chat option: "I am your owner. I have new orders for you.
+-- -> enters edit mode
+-- when edit_mode has been enabled, the following chat options are added to the options:
+-- chat option: "Add new answer/option to this dialog."
+-- -> adds a new aswer/option
+-- chat option: "That was all. I'm finished with giving you new orders. Remember them!"
+-- -> ends edit mode
+-- (happens in fs/fs_talkdialog_in_edit_mode.lua)
+
+
+-- store if the player is editing a particular NPC; format: yl_speak_up.edit_mode[pname] = npc_id
+yl_speak_up.edit_mode = {}
+
+-- changes applied in edit_mode are applied immediately - but not immediately stored to disk
+-- (this gives the players a chance to back off in case of unwanted changes)
+yl_speak_up.npc_was_changed = {}
+
+
+-- is the player in edit mode?
+yl_speak_up.in_edit_mode = function(pname)
+ return pname
+ and yl_speak_up.edit_mode[pname]
+ and (yl_speak_up.edit_mode[pname] == yl_speak_up.speak_to[pname].n_id)
+end
+
+
+-- reset edit_mode when stopping to talk to an NPC
+local old_reset_vars_for_player = yl_speak_up.reset_vars_for_player
+yl_speak_up.reset_vars_for_player = function(pname, reset_fs_version)
+ yl_speak_up.edit_mode[pname] = nil
+ old_reset_vars_for_player(pname, reset_fs_version)
+end
+
+
+-- make sure generic dialogs are never included in edit_mode (because in edit mode we want to
+-- edit this particular NPC without generic parts)
+local old_load_dialog = yl_speak_up.load_dialog
+yl_speak_up.load_dialog = function(n_id, player) -- returns the saved dialog
+ if(player and yl_speak_up.in_edit_mode(player:get_player_name())) then
+ return old_load_dialog(n_id, false)
+ end
+ return old_load_dialog(n_id, player)
+end
+
+
+-- in edit mode the dialog may be saved. visits to a particular dialog are of no intrest here
+local old_count_visits_to_dialog = yl_speak_up.count_visits_to_dialog
+yl_speak_up.count_visits_to_dialog = function(pname)
+ if(yl_speak_up.in_edit_mode(pname)) then
+ return
+ end
+ return old_count_visits_to_dialog(pname)
+end
+
+
+local modpath = npc_talk_edit.modpath
+
+-- this is a way to provide additional help if a mod adds further commands (like the editor)
+yl_speak_up.add_to_command_help_text = yl_speak_up.add_to_command_help_text..
+ "\nAdditional commands provided by "..tostring(npc_talk_edit.modname)..":\n"..
+ " force_edit forces edit mode for any NPC you talk to\n"
+
+ -- overrides of functions fo fs/fs_talkdialog.lua when in edit_mode (or for entering/leaving it)
+ dofile(modpath .. "fs/fs_talkdialog_edit_mode.lua")
+
+ -- edit preconditions (can be reached through edit options dialog)
+ dofile(modpath .. "fs/fs_edit_preconditions.lua")
+ -- edit actions (can be reached through edit options dialog)
+ dofile(modpath .. "fs/fs_edit_actions.lua")
+ -- edit effects (can be reached through edit options dialog)
+ dofile(modpath .. "fs/fs_edit_effects.lua")
+ -- edit options dialog (detailed configuration of options in edit mode)
+ dofile(modpath .. "fs/fs_edit_options_dialog.lua")
+
+ -- the player wants to change something regarding the dialog
+ dofile(modpath .. "edit_mode_apply_changes.lua")
+
+
+
+ -- handle page changes and asking for saving when in edit mode:
+ dofile(modpath .. "show_fs_in_edit_mode.lua")
+ -- ask if the player wants to save, discard or go back in edit mode
+ dofile(modpath .. "fs/fs_save_or_discard_or_back.lua")
+ -- the player wants to change something regarding the dialog
+ dofile(modpath .. "edit_mode_apply_changes.lua")
+
+ -- assign a quest step to a dialog option/answer
+ dofile(modpath .. "fs/fs_assign_quest_step.lua")
+
+ -- in edit_mode we need a more complex reaction to inventory changes
+ dofile(modpath .. "exec_actions_action_inv_changed.lua")
+ -- in edit_mode: effects are not executed
+ dofile(modpath .. "exec_all_relevant_effects.lua")
+ -- some helper functions for formatting text for a formspec talbe
+ dofile(modpath .. "print_as_table.lua")
+ -- create i.e. a dropdown list of player names
+ dofile(modpath .. "api/formspec_helpers.lua")
+ -- handle alternate text for dialogs
+ dofile(modpath .. "api/api_alternate_text.lua")
+ -- helpful for debugging the content of the created dialog structure
+ dofile(modpath .. "fs/fs_show_what_points_to_this_dialog.lua")
+ -- common functions for editing preconditions and effects
+ dofile(modpath .. "api/fs_edit_general.lua")
+ -- edit preconditions (can be reached through edit options dialog)
+ dofile(modpath .. "fs/fs_edit_preconditions.lua")
+ -- edit actions (can be reached through edit options dialog)
+ dofile(modpath .. "fs/fs_edit_actions.lua")
+ -- edit effects (can be reached through edit options dialog)
+ dofile(modpath .. "fs/fs_edit_effects.lua")
+ -- edit options dialog (detailed configuration of options in edit mode)
+ dofile(modpath .. "fs/fs_edit_options_dialog.lua")
+ dofile(modpath .. "fs/fs_initial_config_in_edit_mode.lua")
+ dofile(modpath .. "trade_in_edit_mode.lua")
+ dofile(modpath .. "fs/fs_add_trade_simple_in_edit_mode.lua")
+ -- handle back button diffrently when editing a trade as an action:
+ dofile(modpath .. "fs/fs_do_trade_simple_in_edit_mode.lua")
+ -- as the name says: list which npc acesses a variable how and in which context
+ dofile(modpath .. "fs/fs_get_list_of_usage_of_variable.lua")
+ -- show which values are stored for which player in a quest variable
+ dofile(modpath .. "fs/fs_show_all_var_values.lua")
+ -- manage quest variables: add, delete, manage access rights etc.
+ dofile(modpath .. "fs/fs_manage_variables.lua")
+ -- GUI for adding/editing quests
+ dofile(modpath .. "fs/fs_manage_quests.lua")
+ -- GUI for adding/editing quest steps for the quests
+ dofile(modpath .. "api/api_quest_steps.lua")
+ dofile(modpath .. "fs/fs_manage_quest_steps.lua")
+ -- used by the above
+ dofile(modpath .. "fs/fs_add_quest_steps.lua")
+ -- setting skin, wielded item etc.
+ dofile(modpath .. "fs/fs_fashion.lua")
+ dofile(modpath .. "fs/fs_fashion_extended.lua")
+ -- properties for NPC without specific dialogs that want to make use of
+ dofile(modpath .. "fs/fs_properties.lua")
+ -- /npc_talk force_edit (when talking to an NPC in the normal way fails):
+ dofile(modpath .. "command_force_edit_mode.lua")
+ -- add the force_edit option to the chat commands
+ dofile(modpath .. "chat_commands_in_edit_mode.lua")
+ -- creating and maintaining quests
+ dofile(modpath .. "fs/fs_quest_gui.lua")
+ -- take notes regarding what the NPC is for
+ dofile(modpath .. "fs/fs_notes.lua")
diff --git a/edit_mode_apply_changes.lua b/edit_mode_apply_changes.lua
new file mode 100644
index 0000000..25efaa6
--- /dev/null
+++ b/edit_mode_apply_changes.lua
@@ -0,0 +1,570 @@
+
+
+-- helper function for yl_speak_up.edit_mode_apply_changes;
+-- makes sure the new dialog (and a result/effect "dialog" for each option) exist
+yl_speak_up.prepare_new_dialog_for_option = function(dialog, pname, n_id, d_id, o_id,target_dialog,o_results)
+ -- this may also point to a new dialog
+ if(target_dialog == yl_speak_up.text_new_dialog_id) then
+ -- create a new dialog and show it as new target dialog - but do not display
+ -- this dialog directly (the player may follow the -> button)
+ target_dialog = yl_speak_up.add_new_dialog(dialog, pname, nil)
+ end
+ -- translate name into dialog id
+ target_dialog = yl_speak_up.d_name_to_d_id(dialog, target_dialog)
+ -- is there a result/effect of the type "dialog" already? else use a fallback
+ local result = {} --{r_value = "-default-"}
+ if(o_results) then
+ for kr, vr in pairs(o_results) do
+ if( vr.r_type == "dialog" ) then
+ result = vr
+ -- no problem - the right dialog is set already
+ if(result.r_value and result.r_value == target_dialog) then
+ return target_dialog
+ else
+ -- no need to search any further
+ break
+ end
+ end
+ end
+ end
+ local old_d = tostring(result.r_value or "-default-")
+ if(result.r_value and dialog.n_dialogs[result.r_value] and dialog.n_dialogs[result.r_value].d_name) then
+ old_d = old_d..":"..tostring(dialog.n_dialogs[result.r_value].d_name)
+ end
+ local new_d = tostring(target_dialog)
+ if(target_dialog and dialog.n_dialogs[target_dialog] and dialog.n_dialogs[target_dialog].d_name) then
+ new_d = new_d..":"..tostring(dialog.n_dialogs[target_dialog].d_name)
+ end
+ -- store that a new option has been added to this dialog
+ table.insert(yl_speak_up.npc_was_changed[ n_id ],
+ "Dialog "..d_id..": The target dialog for option "..
+ tostring(o_id).." was changed from "..
+ old_d.." to "..new_d..".")
+ -- does the result/effect of type "dialog" exist already? then we're done
+ if(result.r_type and result.r_type == "dialog") then
+ -- actually change the target dialog
+ result.r_value = target_dialog
+ return target_dialog
+ end
+ -- create a new result (first the id, then the actual result)
+ local future_r_id = yl_speak_up.add_new_result(dialog, d_id, o_id)
+ -- actually store the new result
+ dialog.n_dialogs[d_id].d_options[o_id].o_results[future_r_id] = {
+ r_id = future_r_id,
+ r_type = "dialog",
+ r_value = target_dialog}
+ return target_dialog
+end
+
+
+-- helper function for formspec "yl_speak_up:talk" *and* formspec "yl_speak_up:edit_option_dialog"
+-- when a parameter was changed in edit mode;
+-- this is called when the player is in edit_mode (editing the NPC);
+-- the function checks if the player has changed any parameters
+-- Parameters:
+-- pname player name
+-- fields the fields returned from the formspec
+-- Returns:
+-- result table with information about what was added
+-- (for now, only result.show_next_option is of intrest in the option edit menu)
+yl_speak_up.edit_mode_apply_changes = function(pname, fields)
+ local n_id = yl_speak_up.edit_mode[pname]
+ if(not(n_id) or not(yl_speak_up.speak_to[pname])) then
+ return
+ end
+ local d_id = yl_speak_up.speak_to[pname].d_id
+ local dialog = yl_speak_up.speak_to[pname].dialog
+
+ -- check if the player is allowed to edit this NPC
+ if(not(yl_speak_up.may_edit_npc(minetest.get_player_by_name(pname), n_id))) then
+ return
+ end
+
+ -- this way we can store the actual changes and present them to the player for saving
+ if(not(yl_speak_up.npc_was_changed[ n_id ])) then
+ yl_speak_up.npc_was_changed[ n_id ] = {}
+ end
+
+
+ -- nothing to do if that dialog does not exist
+ if(not(d_id) or not(dialog.n_dialogs) or not(dialog.n_dialogs[ d_id ])) then
+ return
+ end
+
+ -- allow owner to mute/unmute npc (would be bad if players can already see what is going
+ -- to happen while the owner creates a long quest)
+ -- mute/unmute gets logged in the function and does not need extra log entries
+ local obj = yl_speak_up.speak_to[pname].obj
+ if(fields.mute_npc and obj) then
+ yl_speak_up.set_muted(pname, obj, true)
+ elseif(fields.un_mute_npc and obj) then
+ yl_speak_up.set_muted(pname, obj, false)
+ end
+
+ -- changes to d_dynamic are *not* changed (the content of that dialog has to be provided
+ -- dynamicly by a function):
+ if(d_id == "d_dynamic") then
+ return
+ end
+
+ -- new options etc. may be added; store these IDs so that we can switch to the right target
+ local result = {}
+
+ -- make this the first dialog shown when starting a conversation
+ if(fields.make_first_option) then
+ -- check which dialog(s) previously had the highest priority and change thsoe
+ for k, v in pairs(dialog.n_dialogs) do
+ if(v and v.d_sort and (v.d_sort=="0" or v.d_sort==0)) then
+ -- try to derive a sensible future sort priority from the key:
+ -- here we make use of the d_ pattern; but even if that fails to yield
+ -- a number, the sort function will later be able to deal with it anyway
+ local new_priority = string.sub(k, 3)
+ dialog.n_dialogs[ k ].d_sort = new_priority
+ end
+ end
+ -- actually make this the chat with the highest priority
+ dialog.n_dialogs[ d_id ].d_sort = "0"
+ -- this is not immediately saved, even though the changes to the previous dialog with
+ -- the highest priority cannot be automaticly undone (but as long as it is not saved,
+ -- it really does not matter; and when saving, the player has to take some care)
+ table.insert(yl_speak_up.npc_was_changed[ n_id ],
+ "Dialog "..d_id..": Turned into new start dialog.")
+ end
+
+ -- if it is *a* start dialog: buttons like give item to npc/trade/etc. will be shown
+ if(fields.turn_into_a_start_dialog) then
+ if(dialog.n_dialogs[ d_id ].is_a_start_dialog) then
+ -- no need to waste space...
+ dialog.n_dialogs[ d_id ].is_a_start_dialog = nil
+ table.insert(yl_speak_up.npc_was_changed[ n_id ],
+ "Dialog "..d_id..": Is no longer *a* start dialog (regarding buttons).")
+ else
+ dialog.n_dialogs[ d_id ].is_a_start_dialog = true
+ table.insert(yl_speak_up.npc_was_changed[ n_id ],
+ "Dialog "..d_id..": Turned into *a* start dialog (regarding buttons).")
+ end
+ end
+
+ -- detect changes to d_text: text of the dialog (what the npc is saying)
+ -- (only happens in dialog edit menu)
+ if(fields.d_text and dialog.n_dialogs[ d_id ].d_text ~= fields.d_text) then
+ -- store that there have been changes to this npc
+ table.insert(yl_speak_up.npc_was_changed[ n_id ],
+ "Dialog "..d_id..": d_text (what the NPC says) was changed from \""..
+ tostring( dialog.n_dialogs[ d_id ].d_text)..
+ "\" to \""..tostring(fields.d_text).."\".")
+ -- actually change the text - but do not save to disk yet
+ dialog.n_dialogs[ d_id ].d_text = fields.d_text
+ end
+
+ if(fields.d_name and fields.d_name ~= "" and dialog.n_dialogs[ d_id ].d_name ~= fields.d_name) then
+ if(fields.d_name ~= d_id
+ and not(yl_speak_up.is_special_dialog(d_id))) then
+ local err_msg = nil
+ -- check if there are no duplicate names
+ for k, v in pairs(dialog.n_dialogs) do
+ if(v and v.d_name and v.d_name == fields.d_name and k ~= d_id) then
+ err_msg = "Sorry. That name has already been used for dialog "..
+ tostring(k).."."
+ end
+ end
+ if(dialog.n_dialogs[fields.d_name]) then
+ err_msg = "Sorry. There is already a dialog with a dialog id of "..
+ tostring(fields.d_name).."."
+ elseif(yl_speak_up.is_special_dialog(fields.d_name)) then
+ err_msg = "Sorry. That is a special dialog ID. You cannot use it as "..
+ "a manually set name."
+ elseif(string.sub(fields.d_name, 1, 2) == "d_") then
+ err_msg = "Sorry. Names starting with \"d_\" are not allowed. They "..
+ "may later be needed for new dialogs."
+ end
+ -- TODO: check if the name is allowed (only normal chars, numbers and underscore)
+ if(err_msg) then
+ minetest.chat_send_player(pname, err_msg)
+ else
+ table.insert(yl_speak_up.npc_was_changed[ n_id ],
+ "Dialog "..d_id..": renamed from \""..
+ tostring(dialog.n_dialogs[ d_id ].d_name)..
+ "\" to \""..tostring(fields.d_name).."\".")
+ dialog.n_dialogs[ d_id ].d_name = fields.d_name
+ end
+ end
+ end
+
+ -- add a new option/answer
+ if(fields[ "add_option"]) then
+ local future_o_id = yl_speak_up.add_new_option(dialog, pname, nil, d_id, "", d_id)
+ if(not(future_o_id)) then
+ -- this is already checked earlier on and the button only shown if
+ -- options can be added; so this can reamin a chat message
+ minetest.chat_send_player(pname, "Sorry. Only "..
+ tostring(yl_speak_up.max_number_of_options_per_dialog)..
+ " options/answers are allowed per dialog.")
+ fields.add_option = nil
+ else
+ -- add_new_option has added a dialog result for us already - no need to do that again
+
+ -- if this is selected in the options edit menu, we want to move straight on to the new option
+ result["show_next_option"] = future_o_id
+ end
+ end
+
+ -- delete an option directly from the main fs_talkdialog
+ if(dialog.n_dialogs[d_id].d_options) then
+ for o_id, o_v in pairs(dialog.n_dialogs[d_id].d_options) do
+ if(o_id and fields["delete_option_"..o_id]) then
+ fields["del_option"] = true
+ fields.o_id = o_id
+ -- ..or move an option up by one in the list
+ elseif(o_id and fields["option_move_up_"..o_id]) then
+ fields["option_move_up"] = true
+ fields.o_id = o_id
+ -- ..or move an option down by one in the list
+ elseif(o_id and fields["option_move_down_"..o_id]) then
+ fields["option_move_down"] = true
+ fields.o_id = o_id
+ end
+ end
+ end
+
+ if(fields[ "del_option"] and fields.o_id and dialog.n_dialogs[d_id].d_options[fields.o_id]) then
+ local o_id = fields.o_id
+ -- which dialog to show instead of the deleted one?
+ local next_o_id = o_id
+ local sorted_list = yl_speak_up.get_sorted_options(dialog.n_dialogs[d_id].d_options, "o_sort")
+ for i, o in ipairs(sorted_list) do
+ if(o == o_id and sorted_list[ i+1 ]) then
+ next_o_id = sorted_list[ i+1 ]
+ elseif(o == o_id and sorted_list[ i-1 ]) then
+ next_o_id = sorted_list[ i-1 ]
+ end
+ end
+ table.insert(yl_speak_up.npc_was_changed[ n_id ],
+ "Dialog "..d_id..": Option "..tostring(o_id).." deleted.")
+ -- actually delete the dialog
+ dialog.n_dialogs[d_id].d_options[o_id] = nil
+ -- the current dialog is deleted; we need to show another one
+ result["show_next_option"] = next_o_id
+ -- after deleting the entry, all previous/further changes to it are kind of unintresting
+ return result
+ end
+
+ -- move an option up by one
+ local d_options = dialog.n_dialogs[d_id].d_options
+ if(fields[ "option_move_up"] and fields.o_id and d_options[fields.o_id]) then
+ local sorted_o_list = yl_speak_up.get_sorted_options(d_options, "o_sort")
+ local idx = table.indexof(sorted_o_list, fields.o_id)
+ if(idx > 1) then
+ -- swap the two positions
+ local tmp = dialog.n_dialogs[d_id].d_options[fields.o_id].o_sort
+ dialog.n_dialogs[d_id].d_options[fields.o_id].o_sort =
+ dialog.n_dialogs[d_id].d_options[sorted_o_list[idx - 1]].o_sort
+ dialog.n_dialogs[d_id].d_options[sorted_o_list[idx - 1]].o_sort = tmp
+ table.insert(yl_speak_up.npc_was_changed[ n_id ],
+ "Dialog "..d_id..": Option "..tostring(fields.o_id).." was moved up by one.")
+ end
+ -- ..or move the option down by one
+ elseif(fields[ "option_move_down"] and fields.o_id and d_options[fields.o_id]) then
+ local sorted_o_list = yl_speak_up.get_sorted_options(d_options, "o_sort")
+ local idx = table.indexof(sorted_o_list, fields.o_id)
+ if(idx > 0 and idx < #sorted_o_list) then
+ local tmp = dialog.n_dialogs[d_id].d_options[fields.o_id].o_sort
+ dialog.n_dialogs[d_id].d_options[fields.o_id].o_sort =
+ dialog.n_dialogs[d_id].d_options[sorted_o_list[idx + 1]].o_sort
+ dialog.n_dialogs[d_id].d_options[sorted_o_list[idx + 1]].o_sort = tmp
+ table.insert(yl_speak_up.npc_was_changed[ n_id ],
+ "Dialog "..d_id..": Option "..tostring(fields.o_id).." was moved down by one.")
+ end
+ end
+
+ -- ignore entries to o_sort if they are not a number
+ if(fields[ "edit_option_o_sort"]
+ and tonumber(fields[ "edit_option_o_sort"])
+ and fields.o_id and dialog.n_dialogs[d_id].d_options[fields.o_id]) then
+ local o_id = fields.o_id
+ local new_nr = tonumber(fields[ "edit_option_o_sort"])
+ local old_nr = tonumber(dialog.n_dialogs[d_id].d_options[o_id].o_sort)
+ -- if the nr is -1 (do not show) then we are done already: nothing to do
+ if(old_nr == new_nr) then
+ -- -1: do not list as option/answer (but still store and keep it)
+ elseif(new_nr == -1 and old_nr ~= -1) then
+ dialog.n_dialogs[d_id].d_options[o_id].o_sort = "-1"
+ table.insert(yl_speak_up.npc_was_changed[ n_id ],
+ "Dialog "..d_id..": Option "..tostring(o_id).." was set to -1 (do not list).")
+ else
+ -- get the old sorted list
+ local sorted_list = yl_speak_up.get_sorted_options(dialog.n_dialogs[d_id].d_options, "o_sort")
+ -- negative numbers are not shown
+ local entries_shown_list = {}
+ for i, o in ipairs(sorted_list) do
+ local n = tonumber(dialog.n_dialogs[d_id].d_options[o].o_sort)
+ if(n and n > 0 and o ~= o_id) then
+ table.insert(entries_shown_list, o)
+ end
+ end
+ -- insert the entry at the new position and let lua do the job
+ table.insert(entries_shown_list, new_nr, o_id)
+ -- take the indices from that new list as new sort values and store them;
+ -- this has the side effect that duplicate entries get sorted out as well
+ for i, o in ipairs(entries_shown_list) do
+ dialog.n_dialogs[d_id].d_options[o].o_sort = tostring(i)
+ end
+ -- store that there was a cahnge
+ table.insert(yl_speak_up.npc_was_changed[ n_id ],
+ "Dialog "..d_id..": Option "..tostring(o_id).." was moved to position "..
+ tostring(new_nr)..".")
+ end
+ end
+
+ -- changes to options are not possible if there are none
+ if(dialog.n_dialogs[ d_id ].d_options) then
+
+ -- detect changes to text_option_: text for option
+ for k, v in pairs(dialog.n_dialogs[ d_id ].d_options) do
+ if( fields[ "text_option_"..k ]
+ and fields[ "text_option_"..k ] ~= v.o_text_when_prerequisites_met ) then
+ -- store that there have been changes to this npc
+ table.insert(yl_speak_up.npc_was_changed[ n_id ],
+ "Dialog "..d_id..": The text for option "..tostring(k)..
+ " was changed from \""..tostring(v.o_text_when_prerequisites_met)..
+ "\" to \""..tostring(fields[ "text_option_"..k]).."\".")
+ -- actually change the text of the option
+ dialog.n_dialogs[ d_id ].d_options[ k ].o_text_when_prerequisites_met = fields[ "text_option_"..k ]
+ end
+ end
+
+ -- detect changes to d_id_: target dialog for option
+ for k, v in pairs(dialog.n_dialogs[ d_id ].d_options) do
+ if(fields[ "d_id_"..k ]) then
+ local new_target_dialog = yl_speak_up.prepare_new_dialog_for_option(
+ dialog, pname, n_id, d_id, k, fields[ "d_id_"..k ], v.o_results)
+ if(new_target_dialog ~= fields[ "d_id_"..k ]
+ and not( dialog.n_dialogs[new_target_dialog]
+ and dialog.n_dialogs[new_target_dialog].d_name
+ and dialog.n_dialogs[new_target_dialog].d_name == fields["d_id_"..k])
+ ) then
+ fields[ "d_id_"..k ] = new_target_dialog
+ -- in options edit menu: show this update
+ result["show_next_option"] = k
+ end
+ end
+ end
+ end
+
+ -- add a new dialog; either via "+" button or "New dialog" in dialog dropdown menu
+ -- this has to be done after all the other changes because those (text changes etc.) still
+ -- apply to the *old* dialog
+ if(fields.show_new_dialog
+ or(fields["d_id"] and fields["d_id"] == yl_speak_up.text_new_dialog_id)) then
+ -- create the new dialog and make sure it gets shown
+ local d_id = yl_speak_up.add_new_dialog(dialog, pname, nil)
+ -- actually show the new dialog
+ fields["d_id"] = d_id
+ fields["show_new_dialog"] = nil
+ end
+
+ -- delete one empty dialog
+ if(fields.delete_this_empty_dialog) then
+ local anz_options = 0
+ -- we need to show a new dialog after this one was deleted
+ local new_dialog = d_id
+ local sorted_list = yl_speak_up.get_sorted_options(dialog.n_dialogs, "d_sort")
+ for i, k in ipairs(sorted_list) do
+ -- count the options of this dialog
+ if(k == d_id) then
+ if(dialog.n_dialogs[d_id].d_options) then
+ for o, w in pairs(dialog.n_dialogs[d_id].d_options) do
+ anz_options = anz_options + 1
+ end
+ end
+ if(sorted_list[i+1]) then
+ new_dialog = sorted_list[i+1]
+ elseif(sorted_list[i-1]) then
+ new_dialog = sorted_list[i-1]
+ end
+ end
+ end
+ -- there needs to be one dialog left after deleting this one,
+ -- (as there is always d_dynamic we need to leave *two* dialogs)
+ if(#sorted_list > 2
+ -- this dialog isn't allowed to hold any more options/answers
+ and anz_options == 0
+ -- we really found a new dialog to show
+ and new_dialog ~= d_id
+ -- and the text needs to be empty
+ and dialog.n_dialogs[ d_id ].d_text == "") then
+ -- actually delete this dialog
+ dialog.n_dialogs[ d_id ] = nil
+ -- ..and store it to disk
+ yl_speak_up.delete_dialog(n_id, d_id)
+ yl_speak_up.log_change(pname, n_id,
+ "Deleted dialog "..tostring(d_id)..".")
+ -- switch to another dialog (this one was deleted after all)
+ fields["d_id"] = new_dialog
+ fields["show_new_dialog"] = nil
+ else
+ -- deleting is only possible from the talk menu, and there the delete
+ -- button is only shown if the dialog can be deleted; so this can remain
+ -- a chat message
+ minetest.chat_send_player(pname, "Sorry. This dialog cannot be deleted (yet). "..
+ "It is either the only dialog left or has a non-empty text or has at "..
+ "least on remaining option/answer.")
+ end
+ end
+
+ -- not in options edit menu?
+ local o_id = fields.o_id
+ if(not(o_id)) then
+ return result
+ end
+
+ local d_option = dialog.n_dialogs[ d_id ].d_options[ o_id ]
+ -- change alternate text when preconditions are not met
+ -- (only happens in options edit menu)
+ if(fields.option_text_not_met and d_option
+ and d_option.o_text_when_prerequisites_not_met ~= fields.option_text_not_met) then
+ -- add change to changelog
+ table.insert(yl_speak_up.npc_was_changed[ n_id ],
+ "Dialog "..d_id..": The alternate text for option "..tostring(o_id)..
+ " was changed from \""..
+ tostring(d_option.o_text_when_prerequisites_not_met).."\" to \""..
+ tostring(fields.option_text_not_met).."\".")
+ -- actually change the text of the option
+ d_option.o_text_when_prerequisites_not_met = fields.option_text_not_met
+ end
+
+ -- toggle visit often/only *once*
+ if(d_option and fields.option_visits and fields.option_visits ~= "") then
+ local old_visit_mode = "often"
+ if(d_option.o_visit_only_once and d_option.o_visit_only_once == 1) then
+ old_visit_mode = "*once*"
+ end
+ if(fields.option_visits ~= old_visit_mode) then
+ if(fields.option_visits == "often") then
+ d_option.o_visit_only_once = 0
+ table.insert(yl_speak_up.npc_was_changed[ n_id ],
+ "Dialog "..d_id..": Option "..tostring(o_id)..
+ " can now be visited often/unlimited (default).")
+ elseif(fields.option_visits == "*once*") then
+ d_option.o_visit_only_once = 1
+ table.insert(yl_speak_up.npc_was_changed[ n_id ],
+ "Dialog "..d_id..": Option "..tostring(o_id)..
+ " can now be visited only *once* per talk.")
+ end
+ end
+ end
+
+ -- toggle autoselection/autoclick of an option
+ if(d_option and fields.option_autoanswer and fields.option_autoanswer ~= "") then
+ local old_answer_mode = "by clicking on it"
+ if(dialog.n_dialogs[ d_id ].o_random) then
+ old_answer_mode = "randomly"
+ elseif(d_option.o_autoanswer and d_option.o_autoanswer == 1) then
+ old_answer_mode = "automaticly"
+ end
+ if(fields.option_autoanswer ~= old_answer_mode) then
+ local new_answer_mode = ""
+ if(fields.option_autoanswer == "by clicking on it") then
+ d_option.o_autoanswer = nil
+ -- the dialog is no longer random
+ dialog.n_dialogs[ d_id ].o_random = nil
+ new_answer_mode = fields.option_autoanswer
+ elseif(fields.option_autoanswer == "automaticly") then
+ d_option.o_autoanswer = 1
+ -- the dialog is no longer random
+ dialog.n_dialogs[ d_id ].o_random = nil
+ new_answer_mode = fields.option_autoanswer
+ elseif(fields.option_autoanswer == "randomly") then
+ d_option.o_autoanswer = nil
+ -- the entire dialog needs to be set to randomly - not just this option
+ dialog.n_dialogs[ d_id ].o_random = 1
+ new_answer_mode = fields.option_autoanswer
+ end
+ if(new_answer_mode ~= "" and new_answer_mode ~= old_answer_mode) then
+ local random_was_changed = ""
+ if(new_answer_mode == "randomly" or old_answer_mode == "randomly") then
+ random_was_changed = " Note that changes to/from \"randomly\" "..
+ "affect the entire dialog!"
+ end
+ table.insert(yl_speak_up.npc_was_changed[ n_id ],
+ "Dialog "..d_id..": The modus for option "..tostring(o_id)..
+ " was changed from \""..old_answer_mode.."\" to \""..
+ new_answer_mode.."\"."..random_was_changed)
+ end
+ end
+ end
+
+ -- handle hide/grey out/show alternate answer
+ -- (only happens in options edit menu)
+ if(fields.hide_or_grey_or_alternate_answer and d_option) then
+ if(fields.hide_or_grey_or_alternate_answer == "..hide this answer."
+ and d_option.o_hide_when_prerequisites_not_met ~= "true") then
+ d_option.o_hide_when_prerequisites_not_met = "true"
+ d_option.o_grey_when_prerequisites_not_met = "false"
+ table.insert(yl_speak_up.npc_was_changed[ n_id ],
+ "Dialog "..d_id..": If precondition for option "..tostring(o_id)..
+ " is not met, hide option/answer.")
+ -- make sure we show this options update next
+ result["show_next_option"] = o_id
+ elseif(fields.hide_or_grey_or_alternate_answer == "..grey out the following answer:"
+ and d_option.o_grey_when_prerequisites_not_met ~= "true") then
+ d_option.o_hide_when_prerequisites_not_met = "false"
+ d_option.o_grey_when_prerequisites_not_met = "true"
+ table.insert(yl_speak_up.npc_was_changed[ n_id ],
+ "Dialog "..d_id..": If precondition for option "..tostring(o_id)..
+ " is not met, grey out option/answer.")
+ result["show_next_option"] = o_id
+ elseif(fields.hide_or_grey_or_alternate_answer == "..display the following alternate answer:"
+ and (d_option.o_hide_when_prerequisites_not_met ~= "false"
+ or d_option.o_grey_when_prerequisites_not_met) ~= "false") then
+ d_option.o_hide_when_prerequisites_not_met = "false"
+ d_option.o_grey_when_prerequisites_not_met = "false"
+ table.insert(yl_speak_up.npc_was_changed[ n_id ],
+ "Dialog "..d_id..": If precondition for option "..tostring(o_id)..
+ " is not met, show alternate option/answer.")
+ result["show_next_option"] = o_id
+ end
+ end
+
+ -- how many times can the player fail to execute the action successfully?
+ if(fields[ "timer_max_attempts_on_failure"]) then
+ local field_name = "timer_max_attempts_on_failure"
+ local timer_name = "timer_on_failure_"..tostring(d_id).."_"..tostring(o_id)
+ if(not(tonumber(fields[ field_name ]))) then
+ fields[ field_name ] = 0
+ end
+ -- make sure the variable exists
+ if(yl_speak_up.add_time_based_variable(timer_name)) then
+ yl_speak_up.set_variable_metadata(timer_name, nil, "parameter", "max_attempts",
+ fields[ field_name ])
+ end
+ end
+ -- ..and how long has the player to wait in order to try again?
+ if(fields[ "timer_max_seconds_on_failure"]) then
+ local field_name = "timer_max_seconds_on_failure"
+ local timer_name = "timer_on_failure_"..tostring(d_id).."_"..tostring(o_id)
+ if(not(tonumber(fields[ field_name ]))) then
+ fields[ field_name ] = 0
+ end
+ -- make sure the variable exists
+ if(yl_speak_up.add_time_based_variable(timer_name)) then
+ yl_speak_up.set_variable_metadata(timer_name, nil, "parameter", "duration",
+ fields[ field_name ])
+ end
+ end
+ if(fields[ "timer_max_seconds_on_success"]) then
+ local field_name = "timer_max_seconds_on_success"
+ local timer_name = "timer_on_success_"..tostring(d_id).."_"..tostring(o_id)
+ if(not(tonumber(fields[ field_name ]))) then
+ fields[ field_name ] = 0
+ end
+ -- make sure the variable exists
+ if(yl_speak_up.add_time_based_variable(timer_name)) then
+ yl_speak_up.set_variable_metadata(timer_name, nil, "parameter", "duration",
+ fields[ field_name ])
+ end
+ end
+ -- currently only contains result["show_new_option"] (which is needed for options edit menu)
+ return result
+end
+-- end of yl_speak_up.edit_mode_apply_changes
diff --git a/exec_actions_action_inv_changed.lua b/exec_actions_action_inv_changed.lua
new file mode 100644
index 0000000..d16f056
--- /dev/null
+++ b/exec_actions_action_inv_changed.lua
@@ -0,0 +1,62 @@
+
+local old_action_inv_changed = yl_speak_up.action_inv_changed
+-- monitor changes to the npc_gives and npc_wants slots (in particular when editing actions)
+-- how: can be "put" or "take"
+yl_speak_up.action_inv_changed = function(inv, listname, index, stack, player, how)
+ if(not(player)) then
+ return
+ end
+ 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
+ -- if not in edit mode: the player may just be normally interacting with the NPC;
+ -- nothing to do for us here (wait for the player to click on "save")
+ if(not(n_id) or yl_speak_up.edit_mode[pname] ~= n_id) then
+ return old_action_inv_changed(inv, listname, index, stack, player, how)
+ end
+ -- is the player in the process of editing an action of the npc_gives/npc_wants type?
+ local target_fs = "edit_actions"
+ local data = yl_speak_up.speak_to[pname][ "tmp_action" ]
+ if(not(data) or (data.what ~= 4 and data.what ~= 5)) then
+ -- we are editing an action
+ if(data) then
+ return
+ end
+ -- it might be a precondition
+ data = yl_speak_up.speak_to[pname][ "tmp_prereq" ]
+ if(not(data) or (data.what ~= 8)) then
+ return
+ end
+ target_fs = "edit_preconditions"
+ end
+ -- "The NPC gives something to the player (i.e. a quest item).", -- 4
+ -- "The player is expected to give something to the NPC (i.e. a quest item).", -- 5
+ if(how == "put") then
+ data.item_node_name = stack:get_name().." "..stack:get_count()
+ local meta = stack:get_meta()
+ if(meta and meta:get_string("description")) then
+ -- try to reconstruct $PLAYER_NAME$ (may not always work)
+ local item_was_for = meta:get_string("yl_speak_up:quest_item_for")
+ local new_desc = meta:get_string("description")
+ if(item_was_for and item_was_for ~= "") then
+ new_desc = string.gsub(new_desc, item_was_for, "$PLAYER_NAME$")
+ end
+ data.item_desc = new_desc
+ end
+ if(meta and meta:get_string("yl_speak_up:quest_id")) then
+ data.item_quest_id = meta:get_string("yl_speak_up:quest_id")
+ end
+ elseif(how == "take" and data.what == 4) then
+ data.item_desc = "- no item set -"
+ data.item_node_name = ""
+ elseif(how == "take" and data.what == 5) then
+ data.item_desc = "- no item set -"
+ data.item_node_name = ""
+ end
+ -- show the updated formspec to the player
+ yl_speak_up.show_fs(player, target_fs, nil)
+ -- no need to check anything more here; the real checks need to be done
+ -- when the player presses the save/store/execute button
+end
diff --git a/exec_all_relevant_effects.lua b/exec_all_relevant_effects.lua
new file mode 100644
index 0000000..be67eef
--- /dev/null
+++ b/exec_all_relevant_effects.lua
@@ -0,0 +1,9 @@
+
+local old_execute_all_relevant_effects = yl_speak_up.execute_all_relevant_effects
+yl_speak_up.execute_all_relevant_effects = function(player, effects, o_id, action_was_successful, d_option,
+ dry_run_no_exec) -- dry_run_no_exec for edit_mode
+ -- if in edit mode: do a dry run - do *not* execute the effects
+ local edit_mode = (player and yl_speak_up.in_edit_mode(player:get_player_name()))
+ -- we pass this as an additional parameter so that it doesn't have to be re-evaluated for each effect
+ return old_execute_all_relevant_effects(player, effects, o_id, action_was_successful, d_option, edit_mode)
+end
diff --git a/fs/fs_add_quest_steps.lua b/fs/fs_add_quest_steps.lua
new file mode 100644
index 0000000..ba88747
--- /dev/null
+++ b/fs/fs_add_quest_steps.lua
@@ -0,0 +1,763 @@
+-- helper function to make unintresting zeros in tables less visible
+local grey_if_zero = function(fs, n)
+ if(n and n == 0) then
+ table.insert(fs, "#444444")
+ else
+ table.insert(fs, "#FFFFFF")
+ end
+ table.insert(fs, minetest.formspec_escape(n))
+end
+
+
+-- small helper function
+local show_error_fs = function(player, text)
+ yl_speak_up.show_fs(player, "msg", {
+ input_to = "yl_speak_up:add_quest_steps",
+ formspec = yl_speak_up.build_fs_quest_edit_error(text, "back_from_error_msg")})
+end
+
+
+-- find out in how many quest steps this NPC or location is used;
+-- ID can either be n_ or a location p_(x,y,z)
+yl_speak_up.count_used_in_quest_steps = function(id, step_data)
+ local used = 0
+ for s, d in pairs(step_data or {}) do
+ for loc_id, loc in pairs(d.where or {}) do
+ if(loc and loc.n_id and loc.n_id == id) then
+ used = used + 1
+ end
+ end
+ end
+ return used
+end
+
+
+-- This order imposed here on the quest steps is the one in which the
+-- quest steps have to be solved - as far as we can tell (the quest
+-- may be in the process of beeing created and not logicly complete yet).
+yl_speak_up.get_sorted_quest_step_list_by_prev_step = function(pname, q_id)
+ if(not(q_id) or not(yl_speak_up.quests[q_id]) or not(yl_speak_up.quests[q_id].step_data)) then
+ return {}
+ end
+
+ -- for now: sort alphabeticly
+ local step_data = yl_speak_up.quests[q_id].step_data
+ local liste = {}
+ for k, v in pairs(step_data) do
+ table.insert(liste, k)
+ end
+ table.sort(liste)
+ return liste
+end
+
+--[[ still belongs to the above function
+ -- add back links (or rather: forward links to quest steps that get enabled)
+ local connected = table.copy(step_data)
+ for k, v in pairs(connected) do
+ -- will store the inverse data
+ connected[k].inv_one_step_required = {}
+ connected[k].inv_all_steps_require = {}
+ end
+ for k, v in pairs(connected) do
+ if(k and v and v.one_step_required) then
+ for i, s in ipairs(v.one_step_required) do
+ table.insert(connected[s].inv_one_step_required, k)
+ end
+ end
+ if(k and v and v.all_steps_required) then
+ for i, s in ipairs(v.all_steps_required) do
+ table.insert(connected[s].inv_all_steps_required, k)
+ end
+ end
+ end
+ -- get those quest steps that are not connected and not used yet
+ local liste = {}
+ for k, v in pairs(connected) do
+ if(k and v
+ and #v.one_step_required == 0 and #v.all_steps_required == 0
+ and #v.inv_one_step_required == 0 and #v.inv_all_steps_required == 0) then
+ table.insert(liste, k)
+ end
+ end
+ -- sort alphabeticly
+ table.sort(liste)
+ -- remove those entries from our connection table (they're not connected anyway);
+ -- we have already added them to the beginning of the list
+ for i, v in ipairs(liste) do
+ connected[v] = nil
+ end
+
+
+ return liste
+end
+--]]
+
+
+-- helper function: find out if a quest step is required by other quest steps
+yl_speak_up.quest_step_get_required_for_steps = function(step_data)
+ local required_for_steps = {}
+ for s, d in pairs(step_data) do
+ required_for_steps[s] = {}
+ end
+ for s, d in pairs(step_data) do
+ if(s and d and d.one_step_required and type(d.one_step_required) == "table") then
+ for i, s2 in ipairs(d.one_step_required) do
+ table.insert(required_for_steps[s2], s)
+ end
+ end
+ if(s and d and d.all_steps_required and type(d.all_steps_required) == "table") then
+ for i, s2 in ipairs(d.all_steps_required) do
+ table.insert(required_for_steps[s2], s)
+ end
+ end
+ end
+ return required_for_steps
+end
+
+
+yl_speak_up.input_fs_add_quest_steps = function(player, formname, fields)
+ if(not(fields) or not(player)) then
+ return
+ end
+ local res = yl_speak_up.player_is_working_on_quest(player)
+ if(res.error_msg) then
+ return show_error_fs(player, res.error_msg)
+ end
+ local pname = res.pname
+ local q_id = res.q_id
+ local current_step = res.current_step
+ local step_data = res.step_data
+ local quest = res.quest
+
+ if(fields.back_from_error_msg) then
+ yl_speak_up.show_fs(player, "add_quest_steps")
+ return
+ end
+ local mode = yl_speak_up.speak_to[pname].quest_step_mode
+ if(fields.back) then
+ -- go back to quest overview
+ if(mode and (mode == "manage_quest_npcs" or mode == "manage_quest_locations")) then
+ return yl_speak_up.show_fs(player, "manage_quests")
+ end
+ return yl_speak_up.show_fs(player, "manage_quest_steps", current_step)
+ end
+
+ -- has a quest step be selected?
+ local work_step = nil
+ if(fields.add_element and fields.add_element_name) then
+ if( mode and mode == "manage_quest_npcs") then
+ -- manually entered an NPC ID
+ local npc_id = fields.add_element_name or ""
+ -- just check if it is *potentially* an NPC ID; that way NPC the quest
+ -- creator has no write access to can be added
+ if(string.sub(npc_id, 1, 2) ~= "n_"
+ or not(tonumber(string.sub(npc_id, 3)))) then
+ return show_error_fs(player, "This is not an NPC ID. They have the form n_.")
+ end
+ -- only npcs that are not yet added (and we store IDs without n_ prefix)
+ local id = tonumber(string.sub(npc_id, 3))
+ if(id and table.indexof(res.quest.npcs or {}, id) == -1) then
+ table.insert(yl_speak_up.quests[q_id].npcs, id)
+ yl_speak_up.save_quest(q_id)
+ end
+ return yl_speak_up.show_fs(player, "add_quest_steps")
+ elseif(mode and mode == "manage_quest_locations") then
+ -- manually entered a quest location
+ local location_id = fields.add_element_name or ""
+ local d = yl_speak_up.player_vars["$NPC_META_DATA$"][location_id]
+ local error_msg = ""
+ -- the owner is not checked; that way, locations can be added where the
+ -- quest onwer does not (yet) have write access
+ if(string.sub(location_id, 1, 1) ~= "p") then
+ error_msg = "This is not a location ID."
+ elseif(not(d)) then
+ error_msg = "Location not found."
+ end
+ if(error_msg ~= "") then
+ return show_error_fs(player, error_msg)
+ end
+ -- only locations that are not yet added
+ if(table.indexof(res.quest.locations or {}, location_id) == -1) then
+ table.insert(yl_speak_up.quests[q_id].locations, location_id)
+ yl_speak_up.save_quest(q_id)
+ end
+ return yl_speak_up.show_fs(player, "add_quest_steps")
+ end
+
+ -- create a new quest step
+ local new_step = fields.add_element_name:trim()
+ -- a new one shall be created
+ local msg = yl_speak_up.quest_step_add_quest_step(pname, q_id, new_step)
+ if(msg ~= "OK") then
+ return show_error_fs(player, msg)
+ end
+ -- this will also be set if the quest step exists already; this is fine so far
+ work_step = new_step
+
+ elseif(fields.add_from_available
+ and yl_speak_up.speak_to[pname].list_available) then
+ -- selected a quest step from the list of available steps offered
+ local liste = yl_speak_up.speak_to[pname].list_available
+ local selected = minetest.explode_table_event(fields.add_from_available)
+ if(selected and selected.row and selected.row > 1 and selected.row <= #liste + 1) then
+ work_step = liste[selected.row - 1]
+ end
+
+ elseif(fields.add_to_npc_list
+ and yl_speak_up.speak_to[pname].list_available) then
+ -- selected an NPC from the list of available NPC offered
+ local liste = yl_speak_up.speak_to[pname].list_available
+ local selected = minetest.explode_table_event(fields.add_to_npc_list)
+ if(selected and selected.row and selected.row > 1 and selected.row <= #liste + 1) then
+ local npc_id = liste[selected.row - 1]
+ if(table.indexof(res.quest.npcs or {}, npc_id) == -1) then
+ table.insert(yl_speak_up.quests[q_id].npcs, npc_id)
+ yl_speak_up.save_quest(q_id)
+ end
+ end
+ return yl_speak_up.show_fs(player, "add_quest_steps")
+
+ elseif(fields.add_to_location_list
+ and yl_speak_up.speak_to[pname].list_available) then
+ -- selected a location from the list of available locations offered
+ local liste = yl_speak_up.speak_to[pname].list_available
+ local selected = minetest.explode_table_event(fields.add_to_location_list)
+ if(selected and selected.row and selected.row > 1 and selected.row <= #liste + 1) then
+ local location_id = liste[selected.row - 1]
+ if(table.indexof(res.quest.locations or {}, location_id) == -1) then
+ table.insert(yl_speak_up.quests[q_id].locations, location_id)
+ yl_speak_up.save_quest(q_id)
+ end
+ end
+ return yl_speak_up.show_fs(player, "add_quest_steps")
+
+ elseif(fields.delete_from_one_step_required and current_step and step_data[current_step]) then
+ -- remove a quest step from the list (from one step required)
+ local selected = minetest.explode_table_event(fields.delete_from_one_step_required)
+ local liste = (step_data[current_step].one_step_required or {})
+ if(selected and selected.row and selected.row > 1 and selected.row <= #liste + 1) then
+ table.remove(yl_speak_up.quests[q_id].step_data[current_step].one_step_required, selected.row-1)
+ end
+ yl_speak_up.save_quest(q_id)
+ return yl_speak_up.show_fs(player, "add_quest_steps")
+
+ elseif(fields.delete_from_all_steps_required and current_step and step_data[current_step]) then
+ -- remove a quest step from the lists (from all steps required)
+ local selected = minetest.explode_table_event(fields.delete_from_all_steps_required)
+ local liste = (step_data[current_step].all_steps_required or {})
+ if(selected and selected.row and selected.row > 1 and selected.row <= #liste + 1) then
+ table.remove(yl_speak_up.quests[q_id].step_data[current_step].all_steps_required, selected.row-1)
+ end
+ yl_speak_up.save_quest(q_id)
+ return yl_speak_up.show_fs(player, "add_quest_steps")
+
+ elseif(fields.delete_from_npc_list) then
+ -- remove an NPC from the list of contributors
+ local selected = minetest.explode_table_event(fields.delete_from_npc_list)
+ local liste = (res.quest.npcs or {})
+ if(selected and selected.row and selected.row > 1 and selected.row <= #liste + 1) then
+ -- *can* it be removed, or is it needed somewhere?
+ local full_id = "n_"..tostring(liste[selected.row - 1])
+ if(yl_speak_up.count_used_in_quest_steps(full_id, step_data) > 0) then
+ return show_error_fs(player, "This NPC is needed for setting a quest step.")
+ end
+ table.remove(yl_speak_up.quests[q_id].npcs, selected.row - 1)
+ end
+ yl_speak_up.save_quest(q_id)
+ return yl_speak_up.show_fs(player, "add_quest_steps")
+
+ elseif(fields.delete_from_location_list) then
+ -- remove a location from the list of contributors
+ local selected = minetest.explode_table_event(fields.delete_from_location_list)
+ local liste = (res.quest.locations or {})
+ if(selected and selected.row and selected.row > 1 and selected.row <= #liste + 1) then
+ -- *can* it be removed, or is it needed somewhere?
+ local full_id = liste[selected.row - 1]
+ if(yl_speak_up.count_used_in_quest_steps(full_id, step_data) > 0) then
+ return show_error_fs(player, "This location is needed for setting a quest step.")
+ end
+ table.remove(yl_speak_up.quests[q_id].locations, selected.row - 1)
+ end
+ yl_speak_up.save_quest(q_id)
+ return yl_speak_up.show_fs(player, "add_quest_steps")
+ end
+
+ if(not(work_step)) then
+ return -- TODO
+ elseif(mode == "embedded_select") then
+ yl_speak_up.speak_to[pname].quest_step = work_step
+ return yl_speak_up.show_fs(player, "manage_quest_steps", work_step)
+ elseif(mode == "assign_quest_step") then
+ -- TODO: what if there's already a step assigned?
+ -- actually add the step
+ local n_id = yl_speak_up.speak_to[pname].n_id
+ local d_id = yl_speak_up.speak_to[pname].d_id
+ local o_id = yl_speak_up.speak_to[pname].o_id
+ -- this saves the quest data as well if needed
+ local msg = yl_speak_up.quest_step_add_where(pname, q_id, work_step,
+ {n_id = n_id, d_id = d_id, o_id = o_id})
+ if(msg ~= "OK") then
+ return show_error_fs(player, msg)
+ end
+ if(not(n_id)) then
+ return show_error_fs(player, "NPC or location not found.")
+ end
+ -- store the new connection in the NPC file itself (do not load generic dialogs)
+ local dialog = yl_speak_up.load_dialog(n_id, false)
+ if(yl_speak_up.check_if_dialog_has_option(dialog, d_id, o_id)) then
+ -- ok - the tables exist, so we can store the connection
+ dialog.n_dialogs[d_id].d_options[o_id].quest_id = quest.var_name
+ dialog.n_dialogs[d_id].d_options[o_id].quest_step = work_step
+ -- write it back to disc
+ yl_speak_up.save_dialog(n_id, dialog)
+ else
+ return show_error_fs(player, "Failed to save this quest step for this NPC.")
+ end
+ -- the player is working on the NPC - thus, the NPC may be in a modified stage
+ -- that hasn't been written to disc yet, and we need to adjust this stage as well
+ dialog = yl_speak_up.speak_to[pname].dialog
+ if(yl_speak_up.check_if_dialog_has_option(dialog, d_id, o_id)) then
+ -- ok - the tables exist, so we can store the connection
+ dialog.n_dialogs[d_id].d_options[o_id].quest_id = quest.var_name
+ dialog.n_dialogs[d_id].d_options[o_id].quest_step = work_step
+ yl_speak_up.speak_to[pname].dialog = dialog
+ else
+ return show_error_fs(player, "Failed to update NPC.")
+ end
+ -- show the newly created or selected step
+ yl_speak_up.speak_to[pname].quest_step = work_step
+ -- log the change
+ yl_speak_up.log_change(pname, n_id,
+ "Dialog "..tostring(d_id)..": Option "..tostring(o_id)..
+ " will now set quest step \""..
+ tostring(dialog.n_dialogs[d_id].d_options[o_id].quest_step)..
+ "\" of quest \""..
+ tostring(dialog.n_dialogs[d_id].d_options[o_id].quest_id).."\".")
+ return yl_speak_up.show_fs(player, "manage_quest_steps", work_step)
+ elseif(not(current_step) or not(step_data[current_step])) then
+ return yl_speak_up.show_fs(player, "manage_quests")
+ end
+
+ local required_for_steps = yl_speak_up.quest_step_get_required_for_steps(step_data)
+
+ -- make sure we have a sane data structure
+ for i, s in ipairs({current_step, work_step}) do
+ if(s and yl_speak_up.quests[q_id].step_data[s]) then
+ if(not(yl_speak_up.quests[q_id].step_data[s].one_step_required)) then
+ yl_speak_up.quests[q_id].step_data[s].one_step_required = {}
+ end
+ if(not(yl_speak_up.quests[q_id].step_data[s].all_steps_required)) then
+ yl_speak_up.quests[q_id].step_data[s].all_steps_required = {}
+ end
+ end
+ end
+ -- actually do the work
+ if(mode == "add_to_one_needed") then
+ table.insert(yl_speak_up.quests[q_id].step_data[current_step].one_step_required, work_step)
+ elseif(mode == "add_to_all_needed") then
+ table.insert(yl_speak_up.quests[q_id].step_data[current_step].all_steps_required, work_step)
+ elseif(mode == "insert_after_prev_step") then
+ -- the work_step requires what the current step used to require
+ if(#step_data[current_step].one_step_required == 1) then
+ -- a clear insert is possible
+ yl_speak_up.quests[q_id].step_data[work_step].one_step_required = {
+ step_data[current_step].one_step_required[1]}
+ yl_speak_up.quests[q_id].step_data[current_step].one_step_required[1] = work_step
+ else
+ -- no useful information on what the new work_step ought to depend on;
+ -- we just insert the new step at the first place
+ table.insert(yl_speak_up.quests[q_id].step_data[current_step].one_step_required,
+ 1, work_step)
+ end
+ return yl_speak_up.show_fs(player, "manage_quest_steps", work_step)
+ elseif(mode == "insert_before_next_step") then
+ -- the work_step requires the current_step
+ table.insert(yl_speak_up.quests[q_id].step_data[work_step].one_step_required, 1, current_step)
+ -- the current step has exactly one successor? then we adjust that one
+ if(#required_for_steps[current_step] == 1) then
+ local next_step = required_for_steps[current_step][1]
+ local i = table.indexof(step_data[next_step].one_step_required, current_step)
+ local a = table.indexof(step_data[next_step].all_steps_required, current_step)
+ if(i > -1) then
+ -- is it in one_step_required? -> replace current_step with work_step
+ yl_speak_up.quests[q_id].step_data[next_step].one_step_required[i] = work_step
+ elseif(a > -1) then
+ -- or in all_steps_required? -> replace current_step with work_step
+ yl_speak_up.quests[q_id].step_data[next_step].all_steps_required[i] =work_step
+ end
+ end
+ return yl_speak_up.show_fs(player, "manage_quest_steps", work_step)
+ end
+ yl_speak_up.save_quest(q_id)
+ return yl_speak_up.show_fs(player, "add_quest_steps")
+end
+
+
+-- small helper function for yl_speak_up.get_fs_add_quest_steps;
+-- lists all the quest steps found in liste in the order they occour there
+yl_speak_up.quest_step_list_show_table = function(formspec, table_specs, liste, data, required_for_steps)
+ table.insert(formspec, "tablecolumns["..
+ "color;text,align=right;".. -- #d.one_step_required
+ "color;text,align=right;".. -- #d.all_steps_required
+ "color;text,align=right;".. -- #required_for_steps (quest steps that need this one)
+ "color;text,align=right;".. -- #where (locations/NPC that *set* this quest step)
+ "color;text,align=left".. -- name of quest step
+ "]table[")
+ table.insert(formspec, table_specs)
+ table.insert(formspec,"#FFFFFF,(O),#FFFFFF,(A),#FFFFFF,(U),#FFFFFF,(L),#FFFFFF,Name of step:,")
+ local tmp = {}
+ for i, s in ipairs(liste or {}) do
+ local d = data[s]
+ if(not(d.one_step_required) or type(d.one_step_required) ~= "table") then
+ d.one_step_required = {}
+ end
+ grey_if_zero(tmp, #d.one_step_required)
+ if(not(d.all_steps_required) or type(d.all_steps_required) ~= "table") then
+ d.all_steps_required = {}
+ end
+ grey_if_zero(tmp, #d.all_steps_required)
+ if(not(required_for_steps[s])) then
+ required_for_steps[s] = {}
+ end
+ grey_if_zero(tmp, #required_for_steps[s])
+
+ if(not(d.where) or type(d.where) ~= "table") then
+ d.where = {}
+ end
+ local anz_where = 0
+ for k, v in pairs(d.where) do
+ anz_where = anz_where + 1
+ end
+ grey_if_zero(tmp, anz_where)
+
+ table.insert(tmp, "#AAFFAA")
+ table.insert(tmp, minetest.formspec_escape(s))
+ end
+ table.insert(formspec, table.concat(tmp, ","))
+ table.insert(formspec, ";]")
+end
+
+
+-- returns list of NPCs that pname can edit and that are not yet part of quest_npc_list
+yl_speak_up.quest_get_npc_candidate_list = function(pname, quest_npc_liste)
+ -- build a list of candidates
+ local npc_list = {}
+ for k, v in pairs(yl_speak_up.npc_list) do
+ -- only NPC that are not already added
+ if(table.indexof(quest_npc_liste or {}, k) == -1
+ -- and only those that the player can edit
+ and (v.owner == pname or (v.may_edit and v.may_edit[pname]))) then
+ table.insert(npc_list, k)
+ end
+ end
+ table.sort(npc_list)
+ return npc_list
+end
+
+
+-- lists npc that are either already added or could be added
+-- can also handle locations
+yl_speak_up.quest_npc_show_table = function(formspec, table_specs, liste, step_data, is_location_list)
+ table.insert(formspec, "tablecolumns["..
+ "color;text,align=right;".. -- used in this many quest steps
+ "color;text,align=left;".. -- n_id (number, for NPC) or p_(-185,3,-146) (for locations)
+ "color;text,align=left;".. -- owner
+ "color;text,align=left".. -- name of NPC
+ "]table[")
+ table.insert(formspec, table_specs)
+ if(is_location_list) then
+ table.insert(formspec,"#FFFFFF,Used:,#FFFFFF,PositionID:,#FFFFFF,Name")
+ else
+ table.insert(formspec,"#FFFFFF,Used:,#FFFFFF,n_id:,#FFFFFF,Name")
+ end
+ table.insert(formspec, minetest.formspec_escape(","))
+ table.insert(formspec, " description:,#FFFFFF,Owner:,")
+ local tmp = {}
+ for i, n_id in ipairs(liste or {}) do
+ local full_id = n_id
+ if(not(is_location_list)) then
+ full_id = "n_"..tostring(n_id)
+ end
+ grey_if_zero(tmp, yl_speak_up.count_used_in_quest_steps(full_id, step_data))
+ -- the n_id of the NPC
+ table.insert(tmp, "#AAFFAA")
+ if(is_location_list) then
+ -- this already encodes the position but contains , and ()
+ table.insert(tmp, minetest.formspec_escape(n_id))
+ else
+ table.insert(tmp, "n_"..minetest.formspec_escape(n_id))
+ end
+ -- get information from the NPC list (see fs_npc_list.lua)
+ local owner = "- ? -"
+ local name = "- ? -"
+ if(yl_speak_up.npc_list[n_id]) then
+ local npc = yl_speak_up.npc_list[n_id]
+ owner = npc.owner
+ name = (npc.name or name)
+ if(npc.desc and npc.desc ~= "") then
+ name = name..', '..(npc.desc or "")
+ end
+ end
+ -- name and description of the NPC
+ table.insert(tmp, "#AAFFAA")
+ table.insert(tmp, minetest.formspec_escape(name))
+ -- owner of the NPC
+ table.insert(tmp, "#AAFFAA")
+ table.insert(tmp, minetest.formspec_escape(owner))
+ end
+ table.insert(formspec, table.concat(tmp, ","))
+ table.insert(formspec, ";]")
+end
+
+
+-- returns list of locations that pname can edit and that are not yet part of quest_location_list
+yl_speak_up.quest_get_location_candidate_list = function(pname, quest_location_liste)
+ -- build a list of candidates of locations
+ local location_list = {}
+ for n_id, v in pairs(yl_speak_up.player_vars["$NPC_META_DATA$"] or {}) do
+ -- TODO: better detection would be helpful
+ if(string.sub(n_id, 1, 1) == "p"
+ -- only locations that are not yet added
+ and table.indexof(quest_location_liste or {}, n_id) == -1
+ -- and only those that the player can edit
+ and (v.owner == pname or (v.may_edit and v.may_edit[pname]))) then
+ table.insert(location_list, n_id)
+ end
+ end
+ table.sort(location_list)
+ return location_list
+end
+
+
+-- param is unused
+yl_speak_up.get_fs_add_quest_steps = function(player, param)
+ local res = yl_speak_up.player_is_working_on_quest(player)
+ if(res.error_msg) then
+ return yl_speak_up.build_fs_quest_edit_error(res.error_msg, "back")
+ end
+ local pname = res.pname
+ local step_data = res.step_data or {}
+
+
+ -- find out if a quest step is required by other quest steps
+ local required_for_steps = yl_speak_up.quest_step_get_required_for_steps(step_data)
+
+ local current_step = nil
+ local this_step_data = nil
+ if(pname and yl_speak_up.speak_to[pname] and yl_speak_up.speak_to[pname].quest_step) then
+ current_step = yl_speak_up.speak_to[pname].quest_step
+ this_step_data = step_data[current_step]
+ end
+ local mode = ""
+ if(param) then
+ mode = param
+ yl_speak_up.speak_to[pname].quest_step_mode = param
+ elseif(pname and yl_speak_up.speak_to[pname] and yl_speak_up.speak_to[pname].quest_step_mode) then
+ mode = yl_speak_up.speak_to[pname].quest_step_mode
+ end
+
+ local add_what = "Add a new quest step named:"
+ if(mode == "manage_quest_npcs") then
+ add_what = "Add the NPC with n_:"
+ elseif(mode == "manage_quest_locations") then
+ add_what = "Add a location by entering its ID directly:"
+ end
+
+ local formspec = {}
+ if(mode and mode == "embedded_select") then
+ table.insert(formspec, "size[30,12]container[6,0;18.5,12]")
+ current_step = nil
+ else
+ table.insert(formspec, "size[18.5,17.3]")
+ end
+
+ -- add back button
+ table.insert(formspec, "button[8,0;2,0.7;back;Back]")
+ -- show which quest we're working at
+ table.insert(formspec, "label[0.2,1.0;Quest ID:]")
+ table.insert(formspec, "label[3.0,1.0;")
+ table.insert(formspec, minetest.formspec_escape(res.q_id))
+ table.insert(formspec, "]")
+ table.insert(formspec, "label[0.2,1.5;Quest name:]")
+ table.insert(formspec, "label[3.0,1.5;")
+ table.insert(formspec, minetest.formspec_escape(res.quest.name or "- unknown -"))
+ table.insert(formspec, "]")
+
+ -- add new quest step
+ table.insert(formspec, "label[0.2,2.2;")
+ table.insert(formspec, add_what)
+ table.insert(formspec, "]")
+ table.insert(formspec, "button[16.1,2.4;1.2,0.7;add_element;Add]")
+ table.insert(formspec, "field[1.0,2.4;15,0.7;add_element_name;;]")
+
+ local y_pos = 3.3
+ if(current_step and mode == "insert_after_prev_step") then
+ local prev_step = "-"
+ if(this_step_data and this_step_data.one_step_required and #this_step_data.one_step_required > 0) then
+ prev_step = this_step_data.one_step_required[1]
+ end
+ table.insert(formspec, "label[0.2,3.3;between the previous step:]")
+ table.insert(formspec, "label[1.0,3.7;")
+ table.insert(formspec, minetest.colorize("#AAFFAA", minetest.formspec_escape(prev_step)))
+ table.insert(formspec, "]")
+ table.insert(formspec, "label[0.2,4.1;and the currently selected step:]")
+ table.insert(formspec, "label[1.0,4.5;")
+ table.insert(formspec, minetest.colorize("#FFFF00", minetest.formspec_escape(current_step)))
+ table.insert(formspec, "]")
+ y_pos = 5.3
+ elseif(current_step and mode == "insert_before_next_step") then
+ local next_step = "-"
+ if(current_step and required_for_steps[current_step] and #required_for_steps[current_step] > 0) then
+ next_step = required_for_steps[current_step][1]
+ end
+ table.insert(formspec, "label[0.2,3.3;between the currently selected step:]")
+ table.insert(formspec, "label[1.0,3.7;")
+ table.insert(formspec, minetest.colorize("#FFFF00", minetest.formspec_escape(current_step)))
+ table.insert(formspec, "]")
+ table.insert(formspec, "label[0.2,4.1;and the next step:]")
+ table.insert(formspec, "label[1.0,4.5;")
+ table.insert(formspec, minetest.colorize("#AAFFAA", minetest.formspec_escape(next_step)))
+ table.insert(formspec, "]")
+ y_pos = 5.3
+ elseif(current_step and mode == "add_to_one_needed") then
+ table.insert(formspec, "label[0.2,3.3;as a requirement to the currently selected step:]")
+ table.insert(formspec, "label[1.0,3.7;")
+ table.insert(formspec, minetest.colorize("#FFFF00", minetest.formspec_escape(current_step)))
+ table.insert(formspec, "]")
+ table.insert(formspec, "label[0.2,4.1;so that "..
+ minetest.colorize("#9999FF", "at least one")..
+ " of these requirements is fulfilled:]")
+ yl_speak_up.quest_step_list_show_table(formspec,
+ "0.2,4.3;17.0,3.0;delete_from_one_step_required;",
+ step_data[current_step].one_step_required,
+ step_data, required_for_steps)
+ table.insert(formspec, "label[0.2,7.5;(Click on an entry to delete it from the list above.)]")
+ y_pos = 8.3
+ elseif(current_step and mode == "add_to_all_needed") then
+ table.insert(formspec, "label[0.2,3.3;as a requirement to the currently selected step:]")
+ table.insert(formspec, "label[1.0,3.7;")
+ table.insert(formspec, minetest.colorize("#FFFF00", minetest.formspec_escape(current_step)))
+ table.insert(formspec, "]")
+ table.insert(formspec, "label[0.2,4.1;so that "..
+ minetest.colorize("#9999FF", "all")..
+ " of these requirements are fulfilled:]")
+ yl_speak_up.quest_step_list_show_table(formspec,
+ "0.2,4.3;17.0,3.0;delete_from_all_steps_required;",
+ step_data[current_step].all_steps_required,
+ step_data, required_for_steps)
+ table.insert(formspec, "label[0.2,7.5;(Click on an entry to delete it from the list above.)]")
+ y_pos = 8.3
+ -- add a quest step to an NPC or location
+ elseif(mode == "assign_quest_step") then
+ table.insert(formspec, "container[0,3.3;17,4]")
+ -- what are we talking about?
+ local n_id = yl_speak_up.speak_to[pname].n_id
+ local d_id = yl_speak_up.speak_to[pname].d_id
+ local o_id = yl_speak_up.speak_to[pname].o_id
+ -- describe where in the dialog of the NPC or location this quest step shall be set
+ yl_speak_up.quest_step_show_where_set(pname, formspec, "which will be set by ", n_id, d_id, o_id, nil, nil)
+ table.insert(formspec, "container_end[]")
+ y_pos = 7.8
+
+ -- which NPC may contribute to the quest?
+ elseif(mode == "manage_quest_npcs") then
+ table.insert(formspec, "container[0,3.3;18,6]")
+ table.insert(formspec, "label[0.2,0;so that the NPC "..
+ minetest.colorize("#9999FF", "may contribute")..
+ " to the quest like these NPC:]")
+ yl_speak_up.quest_npc_show_table(formspec,
+ "0.2,0.2;17.0,3.0;delete_from_npc_list;",
+ res.quest.npcs or {},
+ step_data, false)
+ table.insert(formspec, "label[0.2,3.4;(Click on an entry to delete it from the list above.)]")
+ local available_npcs = yl_speak_up.quest_get_npc_candidate_list(pname, res.quest.npcs or {})
+ yl_speak_up.speak_to[pname].list_available = available_npcs
+ table.insert(formspec, "label[0.2,4.4;or select an NPC from the list below:]")
+ yl_speak_up.quest_npc_show_table(formspec,
+ "0.2,4.6;17.0,6.0;add_to_npc_list;",
+ available_npcs or {}, step_data, false)
+ table.insert(formspec, "label[0.2,10.8;Used: Shows in how many quest steps this NPC is used.]")
+ table.insert(formspec, "container_end[]")
+ return table.concat(formspec, "")
+ -- which locations may contribute to the quest?
+ elseif(mode == "manage_quest_locations") then
+ table.insert(formspec, "container[0,3.3;18,6]")
+ table.insert(formspec, "label[0.2,0;so that the location "..
+ minetest.colorize("#9999FF", "may contribute")..
+ " to the quest like these locations:]")
+ yl_speak_up.quest_npc_show_table(formspec,
+ "0.2,0.2;17.0,3.0;delete_from_location_list;",
+ res.quest.locations or {},
+ step_data, true)
+ table.insert(formspec, "label[0.2,3.4;(Click on an entry to delete it from the list above.)]")
+ local available_locations = yl_speak_up.quest_get_location_candidate_list(pname, res.quest.locations or {})
+ yl_speak_up.speak_to[pname].list_available = available_locations
+ table.insert(formspec, "label[0.2,4.4;or select a location from the list below:]")
+ yl_speak_up.quest_npc_show_table(formspec,
+ "0.2,4.6;17.0,6.0;add_to_location_list;",
+ available_locations or {}, step_data, true)
+ table.insert(formspec, "label[0.2,10.8;Used: Shows in how many quest steps this location is used.]")
+ table.insert(formspec, "container_end[]")
+ return table.concat(formspec, "")
+ end
+
+ -- some quest steps may not be available/may not make sense
+ local not_available = {}
+ if(current_step and step_data[current_step] and (not(mode) or mode ~= "assign_quest_step")) then
+ -- steps that are already required
+ for i, s in ipairs(step_data[current_step].one_step_required or {}) do
+ not_available[s] = true
+ end
+ for i, s in ipairs(step_data[current_step].all_steps_required or {}) do
+ not_available[s] = true
+ end
+ -- steps that directly require this quest step here
+ for i, s in ipairs(required_for_steps[current_step] or {}) do
+ not_available[s] = true
+ end
+ end
+ if(current_step and (not(mode) or mode ~= "assign_quest_step")) then
+ not_available[current_step] = true
+ end
+ -- build a list of candidates
+ local available_steps = {}
+ for k, v in pairs(step_data) do
+ if(not(not_available[k])) then
+ table.insert(available_steps, k)
+ end
+ end
+ table.sort(available_steps)
+ yl_speak_up.speak_to[pname].list_available = available_steps
+
+ table.insert(formspec, "container[0,")
+ table.insert(formspec, tostring(y_pos))
+ table.insert(formspec, ";30,20]")
+
+ table.insert(formspec, "label[0.2,0;or select an existing quest step from the list below")
+ if(mode and mode == "embedded_select") then
+ table.insert(formspec, minetest.colorize("#9999FF", " to display the step")..":]")
+ else
+ table.insert(formspec, ":]")
+ end
+ yl_speak_up.quest_step_list_show_table(formspec,
+ "0.2,0.2;17,6.0;add_from_available;",
+ available_steps,
+ step_data, required_for_steps)
+ table.insert(formspec, "label[0.2,6.5;Legend: The numbers show the amount of quest steps...\n"..
+ "\t(O) from which (o)ne needs to be achieved for this quest step\n"..
+ "\t(A) that (a)ll need to be achieved for this quest step\n"..
+ "\t(U) that require/(u)se this quest step in some form\n"..
+ "\t(L) Number of locations (npc/places) that "..
+ minetest.colorize("#9999FF", "set").." this quest step]")
+ table.insert(formspec, "container_end[]")
+
+ return table.concat(formspec, "")
+end
+
+yl_speak_up.register_fs("add_quest_steps",
+ yl_speak_up.input_fs_add_quest_steps,
+ -- param is unused here
+ yl_speak_up.get_fs_add_quest_steps,
+ -- no special formspec version required
+ nil
+)
diff --git a/fs/fs_add_trade_simple_in_edit_mode.lua b/fs/fs_add_trade_simple_in_edit_mode.lua
new file mode 100644
index 0000000..1e22d0d
--- /dev/null
+++ b/fs/fs_add_trade_simple_in_edit_mode.lua
@@ -0,0 +1,31 @@
+-- override fs/fs_add_trade_simple.lua:
+-- (this is kept here as it is trade related and does not change the formspec as such)
+
+local old_input_add_trade_simple = yl_speak_up.input_add_trade_simple
+yl_speak_up.input_add_trade_simple = function(player, formname, fields, input_to)
+ if(not(player)) then
+ return 0
+ end
+ local pname = player:get_player_name()
+
+ input_to = "add_trade_simple"
+ -- are we editing an action of the type trade?
+ if( yl_speak_up.speak_to[pname][ "tmp_action" ]
+ and yl_speak_up.speak_to[pname][ "tmp_action" ].what == 3
+ and yl_speak_up.in_edit_mode(pname)
+ and yl_speak_up.edit_mode[pname] == n_id) then
+ input_to = "edit_actions"
+ end
+
+ return old_input_add_trade_simple(player, formname, fields, input_to)
+end
+
+
+yl_speak_up.register_fs("add_trade_simple",
+ -- the input function is a new one now
+ yl_speak_up.input_add_trade_simple,
+ -- the get_fs function stays the same
+ yl_speak_up.get_fs_add_trade_simple_wrapper,
+ -- force formspec version 1 (not changed):
+ 1
+)
diff --git a/fs/fs_assign_quest_step.lua b/fs/fs_assign_quest_step.lua
new file mode 100644
index 0000000..ebf06cb
--- /dev/null
+++ b/fs/fs_assign_quest_step.lua
@@ -0,0 +1,343 @@
+-- assign a quest step to a dialog option/answe
+-- This is the formspec where this is handled.
+
+-- small helper function
+local show_error_fs = function(player, text)
+ yl_speak_up.show_fs(player, "msg", {
+ input_to = "yl_speak_up:assign_quest_step",
+ formspec = yl_speak_up.build_fs_quest_edit_error(text, "back_from_error_msg")})
+end
+
+
+-- small helper function
+yl_speak_up.get_choose_until_step_list = function(pname, q_id, quest_step)
+ local choose_until_step = {
+ " - the player restarts the quest -",
+ " - this quest step -",
+ " - the quest step immediately following this one -"
+ }
+ local cdata = yl_speak_up.quests[q_id].step_data[quest_step]
+ for step_name, d in pairs(yl_speak_up.quests[q_id].step_data or {}) do
+ if(step_name ~= quest_step
+ and cdata
+ -- exclude steps that quest_step depends on
+ and table.indexof(cdata.one_step_required or {}, step_name) == -1
+ and table.indexof(cdata.all_steps_required or {}, step_name) == -1) then
+ table.insert(choose_until_step, minetest.formspec_escape(step_name))
+ end
+ end
+ return choose_until_step
+end
+
+
+yl_speak_up.input_fs_assign_quest_step = function(player, formname, fields)
+ if(not(player)) then
+ return ""
+ end
+ local pname = player:get_player_name()
+ -- what are we talking about?
+ local n_id = yl_speak_up.speak_to[pname].n_id
+ local d_id = yl_speak_up.speak_to[pname].d_id
+ local o_id = yl_speak_up.speak_to[pname].o_id
+
+ if(not(n_id) or yl_speak_up.edit_mode[pname] ~= n_id) then
+ return
+ end
+ local dialog = yl_speak_up.speak_to[pname].dialog
+ if(not(yl_speak_up.check_if_dialog_has_option(dialog, d_id, o_id))) then
+ return
+ end
+
+ -- go back to edit options field
+ if((fields and fields.quit)
+ or (fields and fields.back and fields.back ~= "")) then
+ yl_speak_up.show_fs(player, "edit_option_dialog",
+ {n_id = n_id, d_id = d_id, o_id = o_id})
+ return
+ elseif(fields and fields.back_from_error_msg) then
+ yl_speak_up.show_fs(player, "assign_quest_step", nil)
+ return
+ -- show manage quests formspec
+ elseif(fields and fields.manage_quests) then
+ -- store information so that the back button can work
+ yl_speak_up.speak_to[pname][ "working_at" ] = "assign_quest_step"
+ yl_speak_up.show_fs(player, "manage_quests", nil)
+ return
+ elseif(fields and fields.show_quest) then
+ local q_id = yl_speak_up.speak_to[pname].q_id
+ local quest_var_name = nil
+ if(q_id and yl_speak_up.quests[q_id]) then
+ quest_var_name = yl_speak_up.strip_pname_from_var(yl_speak_up.quests[q_id].var_name, pname)
+ end
+ yl_speak_up.show_fs(player, "manage_quests", quest_var_name)
+ return
+ elseif(fields and fields.select_quest_id and fields.select_quest_id ~= "") then
+ local parts = string.split(fields.select_quest_id, " ")
+ if(parts and parts[1] and yl_speak_up.quests[parts[1]]) then
+ -- TODO: check if the player has access rights to that quest
+ -- TODO: check if the NPC has been added to that quest
+ yl_speak_up.speak_to[pname].q_id = parts[1]
+ yl_speak_up.show_fs(player, "add_quest_steps", "assign_quest_step")
+ return
+ end
+ elseif(fields and fields.show_step) then
+ yl_speak_up.show_fs(player, "manage_quest_steps", yl_speak_up.speak_to[pname].quest_step)
+ return
+ elseif(fields and fields.change_show_until) then
+ yl_speak_up.show_fs(player, "assign_quest_step", "change_show_until")
+ return
+ elseif(fields and fields.store_show_until and fields.select_show_until) then
+ local res = yl_speak_up.player_is_working_on_quest(player)
+ if(res.error_msg) then
+ return yl_speak_up.build_fs_quest_edit_error(res.error_msg, "back")
+ end
+ local choose_until_step = yl_speak_up.get_choose_until_step_list(
+ res.pname, res.q_id, yl_speak_up.speak_to[pname].quest_step)
+ local index = table.indexof(choose_until_step, fields.select_show_until or "")
+ if(index ~= -1) then
+ local dialog = yl_speak_up.speak_to[pname].dialog
+ if(not(yl_speak_up.check_if_dialog_has_option(dialog, d_id, o_id))) then
+ return
+ end
+ table.insert(yl_speak_up.npc_was_changed[ n_id ],
+ tostring(d_id).." "..tostring(o_id)..
+ " quest step will be offered until player reached step \""..
+ tostring(fields.select_show_until).."\".")
+ if(fields.select_show_until == " - the player restarts the quest -") then
+ fields.select_show_until = nil
+ elseif(fields.select_show_until == " - this quest step -") then
+ fields.select_show_until = yl_speak_up.speak_to[pname].quest_step
+ elseif(fields.select_show_until == " - the quest step immediately following this one -") then
+ fields.select_show_until = " next_step"
+ end
+ dialog.n_dialogs[d_id].d_options[o_id].quest_show_until = fields.select_show_until
+ end
+ yl_speak_up.show_fs(player, "assign_quest_step")
+ return
+ elseif(fields.delete_assignment and fields.delete_assignment ~= "") then
+ local n_id = yl_speak_up.speak_to[pname].n_id
+ local d_id = yl_speak_up.speak_to[pname].d_id
+ local o_id = yl_speak_up.speak_to[pname].o_id
+ -- load the dialog (which may be diffrent from what the player is working on)
+ local stored_dialog = yl_speak_up.load_dialog(n_id, false)
+ -- check if the dialog exists
+ if(not(yl_speak_up.check_if_dialog_has_option(stored_dialog, d_id, o_id))) then
+ return show_error_fs(player, "Dialog or option not found.")
+ end
+ local quest_step_name = dialog.n_dialogs[d_id].d_options[o_id].quest_step
+ local var_name = dialog.n_dialogs[d_id].d_options[o_id].quest_id
+ local q_id = yl_speak_up.get_quest_id_by_var_name(var_name, pname)
+ -- we will ignore the return value so that this connection can be deleted even if
+ -- something went wrong (quest deleted, no write access to quest etc.)
+ local msg = yl_speak_up.quest_step_del_where(pname, q_id, quest_step_name,
+ {n_id = n_id, d_id = d_id, o_id = o_id})
+ if(not(n_id)) then
+ return show_error_fs(player, "NPC or location not found.")
+ end
+ -- log the change
+ yl_speak_up.log_change(pname, n_id,
+ "Dialog "..tostring(d_id)..": Option "..tostring(o_id)..
+ " no longer sets quest step \""..
+ tostring(dialog.n_dialogs[d_id].d_options[o_id].quest_step)..
+ "\" of quest \""..
+ tostring(dialog.n_dialogs[d_id].d_options[o_id].quest_id).."\".")
+ -- we have updated the quest step data - we need to update the NPC as well
+ local dialog = yl_speak_up.load_dialog(n_id, false)
+ if(yl_speak_up.check_if_dialog_has_option(dialog, d_id, o_id)) then
+ -- ok - the tables exist, so we can delete the connection
+ -- (if it doesn't exist there's no need to change the stored NPC)
+ dialog.n_dialogs[d_id].d_options[o_id].quest_id = nil
+ dialog.n_dialogs[d_id].d_options[o_id].quest_step = nil
+ -- write it back to disc
+ yl_speak_up.save_dialog(n_id, dialog)
+ end
+ -- the player is working on the NPC - thus, the NPC may be in a modified stage
+ -- that hasn't been written to disc yet, and we need to adjust this stage as well
+ dialog = yl_speak_up.speak_to[pname].dialog
+ -- delete the connection
+ dialog.n_dialogs[d_id].d_options[o_id].quest_id = nil
+ dialog.n_dialogs[d_id].d_options[o_id].quest_step = nil
+ yl_speak_up.show_fs(player, "edit_option_dialog",
+ {n_id = n_id, d_id = d_id, o_id = o_id})
+ return
+ elseif(not(fields) or not(fields.save)) then
+ return
+ end
+ -- actually store the data
+ local error_msg = ""
+ -- check if the quest exists
+ local quest_list = yl_speak_up.get_sorted_quest_list(pname)
+ local idx = table.indexof(quest_list, fields.quest_id or "")
+ if(not(fields.quest_id) or fields.quest_id == "" or idx < 1) then
+ error_msg = "Quest not found."
+ elseif(not(fields.quest_step)
+ or string.len(fields.quest_step) < 1
+ or string.len(fields.quest_step) > 80) then
+ error_msg = "The name of the quest step has to be between\n"..
+ "1 and 80 characters long."
+ end
+ if(error_msg ~= "") then
+ yl_speak_up.show_fs(player, "msg", {
+ input_to = "yl_speak_up:assign_quest_step",
+ formspec = "size[9,2]"..
+ "label[0.2,0.5;Error: "..minetest.formspec_escape(error_msg).."]"..
+ "button[1.5,1.5;2,0.9;back_from_error_msg;Back]"})
+ return
+ end
+ -- we identify quests by their var_name - not by their q_id
+ -- (makes it easier to transfer quests from one server to another later)
+ dialog.n_dialogs[d_id].d_options[o_id].quest_id = yl_speak_up.add_pname_to_var(fields.quest_id, pname)
+ dialog.n_dialogs[d_id].d_options[o_id].quest_step = fields.quest_step
+ if(not(yl_speak_up.npc_was_changed[ n_id ])) then
+ yl_speak_up.npc_was_changed[ n_id ] = {}
+ end
+ table.insert(yl_speak_up.npc_was_changed[ n_id ],
+ "Dialog "..d_id..": Option "..tostring(o_id)..
+ " has been set as quest step \""..
+ tostring(fields.quest_step).."\" for quest \""..tostring(fields.quest_id).."\".")
+ yl_speak_up.show_fs(player, "edit_option_dialog",
+ {n_id = n_id, d_id = d_id, o_id = o_id})
+end
+
+
+yl_speak_up.get_fs_assign_quest_step = function(player, param)
+ if(not(player)) then
+ return ""
+ end
+ local pname = player:get_player_name()
+ local n_id = yl_speak_up.speak_to[pname].n_id
+ local d_id = yl_speak_up.speak_to[pname].d_id
+ local o_id = yl_speak_up.speak_to[pname].o_id
+ -- this only works in edit mode
+ if(not(n_id) or yl_speak_up.edit_mode[pname] ~= n_id) then
+ return "size[1,1]label[0,0;You cannot edit this NPC.]"
+ end
+ local dialog = yl_speak_up.speak_to[pname].dialog
+ if(not(yl_speak_up.check_if_dialog_has_option(dialog, d_id, o_id))) then
+ return "size[4,1]label[0,0;Dialog option does not exist.]"
+ end
+ local d_option = dialog.n_dialogs[d_id].d_options[o_id]
+ local quest_id = d_option.quest_id or ""
+ local quest_step = d_option.quest_step or ""
+ local quest_show_until = d_option.quest_show_until or " - the player restarts the quest -"
+ if(quest_show_until == quest_step) then
+ quest_show_until = " - this quest step -"
+ -- players cannot create quest steps with leading blanks
+ elseif(quest_show_until == " next_step") then
+ quest_show_until = " - the quest step immediately following this one -"
+ end
+
+ -- has a quest been selected?
+ local q_id = yl_speak_up.get_quest_id_by_var_name(quest_id, pname)
+ if(not(q_id)) then
+ local npc_id = tonumber(string.sub(n_id, 3))
+ local quest_list = {}
+ for id, d in pairs(yl_speak_up.quests) do
+ if(table.indexof(d.npcs or {}, npc_id) ~= -1) then
+ table.insert(quest_list, minetest.formspec_escape(
+ tostring(id).." "..tostring(d.name)))
+ end
+ end
+ if(#quest_list < 1) then
+ return "size[14,4]"..
+ "label[4.0,0.5;Using this option/answer shall be a quest step.]"..
+ "label[0.2,1.4;"..
+ "This NPC "..tostring(n_id).." has not been added to a quest yet.\n"..
+ "Please add him to the NPC list of one of your quests first!\n"..
+ "Search the \"Edit\" button for \"NPC that (may) participate:\" "..
+ "while viewing the desired quest.]"..
+ "button[0.2,3.0;3.6,0.8;manage_quests;Manage Quests]"..
+ "button[9.5,3.0;4.0,0.8;back;Back to edit option "..tostring(o_id).."]"
+ end
+ local selected = 1
+
+-- local quest_list = yl_speak_up.get_sorted_quest_list(pname)
+-- for i, v in ipairs(quest_list) do
+-- quest_list[i] = minetest.formspec_escape(v)
+-- if(quest_id and v == quest_id) then
+-- selected = i
+-- end
+-- end
+ return "size[14,4]"..
+ "label[4.0,0.5;Using this option/answer shall be a quest step.]"..
+ "label[0.2,1.4;Select a quest:]"..
+ "dropdown[4.0,1.0;9.5,0.8;select_quest_id;"..
+ table.concat(quest_list, ',')..";"..
+ tostring(selected)..",]"..
+ "label[0.2,2.1;If you want to use a quest not mentionned here or a new one, "..
+ "click on \"Manage Quests\"\n"..
+ "and search the \"Edit\" button for \"NPC that (may) participate:\".]"..
+ "button[0.2,3.0;3.6,0.8;manage_quests;Manage Quests]"..
+ "button[9.5,3.0;4.0,0.8;back;Back to edit option "..tostring(o_id).."]"
+ end
+
+ -- this is the currently relevant quest
+ yl_speak_up.speak_to[pname].q_id = q_id
+ yl_speak_up.speak_to[pname].quest_step = quest_step
+ local choose_until_step = {}
+ local show_until = ""
+ if(param and param == "change_show_until") then
+ local choose_until_step = yl_speak_up.get_choose_until_step_list(pname, q_id, quest_step)
+ local index = table.indexof(choose_until_step, quest_show_until or "")
+ if(index == -1) then
+ index = 1
+ end
+ show_until = "dropdown[3.0,5.1;10,0.5;select_show_until;"..
+ table.concat(choose_until_step, ",")..";"..tostring(index)..";]"..
+ "button[13.5,4.8;4.3,0.8;store_show_until;Store]"
+ else
+ show_until = "label[3.0,5.4;"..minetest.colorize("#AAFFAA",
+ minetest.formspec_escape(quest_show_until)).."]"..
+ "button[13.5,4.8;4.3,0.8;change_show_until;Edit]"
+ end
+ local formspec = {
+ "size[18,7]"..
+ "label[3.0,0.5;Using this option/answer shall be a quest step.]"..
+ "label[0.2,1.4;Quest ID:]"..
+ "label[0.2,1.9;Quest name:]"..
+ "label[0.2,3.4;quest step:]"..
+ "button[13.5,3.1;4.3,0.8;show_step;Show this quest step]"..
+ "label[0.2,3.9;"..
+ "This quest step will be set for the player after the effects of "..
+ "the dialog option are executed.]"..
+
+ "label[0.2,4.9;"..
+ "This dialog option will be shown until the player has reached the "..
+ "following quest step:]"..
+ "button[0.2,0.2;2.0,0.8;delete_assignment;Delete]"..
+ "tooltip[delete_assignment;"..
+ "Delete the assignment of this quest step to this dialog option.\n"..
+ "Neither the quest step nor the dialog option will be deleted.\n"..
+ "Just their connection. Afterwards, you can assign a new or\n"..
+ "diffrent quest step to the dialog option.]"..
+ "button[13.5,6.0;4.3,0.8;manage_quests;Manage quests]"
+ }
+ table.insert(formspec, "label[3.0,1.4;")
+ table.insert(formspec, minetest.formspec_escape(q_id))
+ table.insert(formspec, "]")
+ table.insert(formspec, "label[3.0,1.9;")
+ table.insert(formspec, minetest.formspec_escape(yl_speak_up.quests[q_id].name or "- ? -"))
+ table.insert(formspec, "]")
+ table.insert(formspec, "button[13.5,1.0;4.3,0.8;show_quest;Show this quest ")
+ table.insert(formspec, tostring(q_id))
+ table.insert(formspec, "]")
+ table.insert(formspec, "label[0.2,2.9;This option here (")
+ table.insert(formspec, tostring(o_id))
+ table.insert(formspec, ") will be available once the player has reached all required ".. "quest steps for the following]")
+ table.insert(formspec, "label[3.0,3.4;")
+ table.insert(formspec, minetest.colorize("#FFFF00", minetest.formspec_escape(quest_step)))
+ table.insert(formspec, "]")
+ table.insert(formspec, "button[6.0,6.0;6.0,0.8;back;Back to edit option ")
+ table.insert(formspec, tostring(o_id))
+ table.insert(formspec, "]")
+ table.insert(formspec, show_until)
+ return table.concat(formspec, " ")
+end
+
+
+yl_speak_up.register_fs("assign_quest_step",
+ yl_speak_up.input_fs_assign_quest_step,
+ yl_speak_up.get_fs_assign_quest_step,
+ -- no special formspec required:
+ nil
+)
diff --git a/fs/fs_do_trade_simple_in_edit_mode.lua b/fs/fs_do_trade_simple_in_edit_mode.lua
new file mode 100644
index 0000000..a5aa73e
--- /dev/null
+++ b/fs/fs_do_trade_simple_in_edit_mode.lua
@@ -0,0 +1,54 @@
+-- spimple trading: one item(stack) for another item(stack)
+-- (in edit_mode it's a bit diffrent)
+
+
+-- if in edit mode: go back to the edit_options dialog
+local old_input_do_trade_simple = yl_speak_up.input_do_trade_simple
+yl_speak_up.input_do_trade_simple = function(player, formname, fields)
+ if(not(player)) then
+ return 0
+ end
+ local pname = player:get_player_name()
+
+ -- which trade are we talking about?
+ local trade = yl_speak_up.trade[pname]
+
+
+ local n_id = yl_speak_up.speak_to[pname].n_id
+ -- if in edit mode: go back to the edit options dialog
+ if(fields.back_to_edit_options
+ and n_id and yl_speak_up.in_edit_mode(pname)) then
+ local dialog = yl_speak_up.speak_to[pname].dialog
+ local tr = dialog.trades[ trade.trade_id ]
+ if(tr) then
+ -- done trading
+ yl_speak_up.speak_to[pname].target_d_id = nil
+ yl_speak_up.speak_to[pname].trade_id = nil
+ -- go to the edit options dialog
+ yl_speak_up.show_fs(player, "edit_option_dialog",
+ {n_id = n_id, d_id = tr.d_id, o_id = tr.o_id})
+ return
+ end
+ end
+
+
+ -- can the player edit this trade?
+ if(fields.edit_trade_simple
+ and n_id and yl_speak_up.in_edit_mode(pname)) then
+ -- force edit mode for this trade
+ trade.edit_trade = true
+ yl_speak_up.trade[pname] = trade
+ end
+
+ return old_input_do_trade_simple(player, formname, fields)
+end
+
+
+yl_speak_up.register_fs("do_trade_simple",
+ -- new version implemented here:
+ yl_speak_up.input_do_trade_simple,
+ -- this is just the old function:
+ yl_speak_up.get_fs_do_trade_simple_wrapper,
+ -- force formspec version 1:
+ 1
+)
diff --git a/fs/fs_edit_actions.lua b/fs/fs_edit_actions.lua
new file mode 100644
index 0000000..1b94fb1
--- /dev/null
+++ b/fs/fs_edit_actions.lua
@@ -0,0 +1,133 @@
+-- This file contains what is necessary to add/edit an action.
+--
+-- Which diffrent types of actions are available?
+-- -> The following fields are part of an action:
+-- a_id the ID/key of the action
+-- a_type selected from values_what
+-- a_value used to store the subtype of a_type
+--
+-- no action (none): nothing to do.
+--
+-- a trade ("trade"):
+-- a_buy what the NPC sells (itemstack)
+-- a_pay what the NPC wants as payment (itemstack)
+--
+-- giving and taking of items ("npc_gives" and "npc_wants"):
+-- a_on_failure if the action fails, go to this dialog
+-- a_value itemstack of the given/wanted item in string form
+-- a_item_desc the description the NPC shall set for that itemstack
+-- (so that the player can distinguish it from other
+-- itemstacks with the same items)
+-- a_item_quest_id Special ID to make sure that it is really the *right*
+-- item and not just something the player faked with an
+-- engraving table or something similar
+--
+-- the player has to enter text ("text_input"):
+-- a_value the expected answer the player has to enter
+-- a_question the question the NPC shall ask the player (so that the
+-- player can know which answer is expected here)
+--
+-- Show something custom (has to be provided by the server). (=call a function) ("evaluate"):
+-- a_value the name of the function that is to be called
+-- a_param1 the first paramter (optional; depends on function)
+-- ..
+-- a_param9 the 9th parameter (optional; depends on function)
+--
+-- a general, more complex formspec-basted puzzle ("puzzle"): not supported
+-- (custom may be more helpful)
+--
+--
+-- Note: Trades are not stored as actions - they are stored in
+-- dialog.trades[ trade_id ] with == " "
+--
+
+-- some helper lists for creating the formspecs and evaulating
+-- the player's answers:
+
+-- general direction of what could make up an action
+local check_what = {
+ "- please select -",
+ "No action (default).", -- 2
+ "Normal trade - one item(stack) for another item(stack).", -- 3
+ "The NPC gives something to the player (i.e. a quest item).", -- 4
+ "The player is expected to give something to the NPC (i.e. a quest item).", -- 5
+ "The player has to manually enter a password or passphrase or some other text.", -- 6
+ "Show something custom (has to be provided by the server).",
+ -- "The player has to move virtual items in a virtual inventory to the right position.", -- 8
+}
+
+-- how to store these as a_type in the action:
+local values_what = {"", "none", "trade", "npc_gives", "npc_wants", "text_input", "evaluate", "puzzle"}
+
+
+-- returns a human-readable text as description of the action
+-- (as shown in the edit options dialog and in the edit effect formspec)
+yl_speak_up.show_action = function(a)
+ if(not(a.a_type) or a.a_type == "" or a.a_type == "none") then
+ return "(nothing): Nothing to do. No action."
+ elseif(a.a_type == "trade") then
+ return "NPC sells \""..table.concat(a.a_buy, ";").."\" for \""..
+ table.concat(a.a_pay, ";").."\"."
+ elseif(a.a_type == "npc_gives") then
+ return "The NPC gives \""..tostring(a.a_item_desc or "- default description -")..
+ "\" (\""..tostring(a.a_value or "- ? -").."\") "..
+ "with ID \""..tostring(a.a_item_quest_id or "- no special ID -").."\"."
+ elseif(a.a_type == "npc_wants") then
+ return "The NPC wants \""..tostring(a.a_item_desc or "- default description -")..
+ "\" (\""..tostring(a.a_value or "- ? -").."\") "..
+ "with ID \""..tostring(a.a_item_quest_id or "- no special ID -").."\"."
+ elseif(a.a_type == "text_input") then
+ return "Q: \""..tostring(a.a_question).."\" A:\""..tostring(a.a_value).."\"."
+ elseif(a.a_type == "evaluate") then
+ local str = ""
+ for i = 1, 9 do
+ str = str..tostring(a["a_param"..tostring(i)])
+ if(i < 9) then
+ str = str..","
+ end
+ end
+ return "FUNCTION["..tostring(a.a_value).."]("..str..")"
+-- puzzle is unused; better do that via custom
+-- elseif(a.a_type == "puzzle") then
+-- return "puzzle:"
+ end
+ -- fallback
+ return tostring(a.a_value)
+end
+
+-- these are only wrapper functions for those in fs_edit_general.lua
+
+yl_speak_up.input_edit_actions = function(player, formname, fields)
+ return yl_speak_up.handle_input_fs_edit_option_related(player, formname, fields,
+ "a_", "actions", yl_speak_up.max_actions,
+ "(A)ctions", "tmp_action",
+ nil, -- unused - no block operations
+ values_what, {}, {}, {}, {},
+ check_what, {}, {}, {}, {},
+ nil, -- no variables
+ "edit_actions"
+ )
+end
+
+yl_speak_up.get_fs_edit_actions = function(player, table_click_result)
+ return yl_speak_up.build_fs_edit_option_related(player, table_click_result,
+ "a_", "actions", yl_speak_up.max_actions,
+ "(A)ctions", "tmp_action",
+ "What do you want to happen in this (A)ction?",
+ values_what, {}, {}, {}, {},
+ check_what, {}, {}, {}, {},
+ nil, -- no variables
+ yl_speak_up.show_action,
+ "table_of_elements",
+ nil, nil, nil, -- no variable handling here
+ nil -- nothing block-related to do here
+ )
+end
+
+
+yl_speak_up.register_fs("edit_actions",
+ yl_speak_up.input_edit_actions,
+ yl_speak_up.get_fs_edit_actions,
+ -- no special formspec required:
+ nil
+)
diff --git a/fs/fs_edit_effects.lua b/fs/fs_edit_effects.lua
new file mode 100644
index 0000000..d9bb8dd
--- /dev/null
+++ b/fs/fs_edit_effects.lua
@@ -0,0 +1,352 @@
+-- This file contains what is necessary to add/edit an effect.
+--
+-- Which diffrent types of effects are available?
+-- -> The following fields are part of an effect/result:
+-- r_id the ID/key of the effect/result
+-- r_type selected from values_what; the staffs allow to use other
+-- types like "function" or "give_item" etc. - but that is not
+-- supported here (cannot be edited or created; only be shown)
+-- r_value used to store the subtype of r_type
+--
+-- a state/variable ("state"):
+-- r_variable name of a variable the player has *write* access to;
+-- dropdown list with allowed options
+-- r_operator selected from values_operator
+-- r_var_cmp_value can be set freely by the player (the variable will be
+-- set to this value)
+--
+-- the value of a property of the NPC (for generic NPC) ("property"):
+-- r_value name of the property that is to be changed
+-- r_operator how shall the property be changed?
+-- r_var_cmp_value the new value (or increment/decrement) for this property
+--
+-- something that has to be calculated or evaluated (=call a function) ("evaluate"):
+-- r_value the name of the function that is to be called
+-- r_param1 the first paramter (optional; depends on function)
+-- ..
+-- r_param9 the 9th parameter (optional; depends on function)
+--
+-- a block in the world ("block"):
+-- r_pos a position in the world; determined by asking the player
+-- to punch the block
+-- r_node (follows from r_pos)
+-- r_param2 (follows from r_pos)
+--
+-- place an item into the inventory of a block (i.e. a chest; "put_into_block_inv"):
+-- r_pos the position of the target block
+-- r_inv_list_name the inventory list where the item shall be moved to (often "main")
+-- r_itemstack the itemstack that is to be moved
+--
+-- take item out of the inventory of a block (i.e. a chest; "take_from_block_inv");
+-- same as "put_into_block_inv"
+--
+-- accept items the player has given to the NPC ("deal_with_offered_item"):
+-- r_value subtype; one of yl_speak_up.dropdown_values_deal_with_offered_item
+--
+-- a craft receipe ("craft"):
+-- r_value the expected craft result
+-- r_craft_grid array containing the stacks in the 9 craft grid fields in string form
+--
+-- on_failure ("on_failure"):
+-- r_value alternate target dialog if the previous *effect* failed
+--
+-- chat_all ("chat_all"):
+-- r_value chat message sent to all players
+--
+--
+-- give item to player ("give_item"): requires yl_speak_up.npc_privs_priv priv
+-- r_value the itemstack that shall be added to the player's inventory
+--
+-- take item from player's inventory ("take_item"): requires yl_speak_up.npc_privs_priv priv
+-- r_value the itemstack that will be removed from the player's inventory
+--
+-- move the player to a position ("move"): requires yl_speak_up.npc_privs_priv priv
+-- r_value the position where the player shall be moved to
+--
+-- execute lua code ("function"): requires npc_master priv
+-- r_value the lua code that shall be executed
+--
+-- Unlike in preconditions, trade (the trade action already happened) and
+-- inventory actions are not supported as effects.
+--
+
+-- some helper lists for creating the formspecs and evaulating
+-- the player's answers:
+
+-- general direction of what could make up an effect
+local check_what = {
+ "- please select -",
+ "an internal state (i.e. of a quest)", -- 2
+ "the value of a property of the NPC (for generic NPC)", -- property
+ "something that has to be calculated or evaluated (=call a function)", -- evaluate
+ "a block somewhere", -- 3
+ "put item from the NPC's inventory into a chest etc.", -- 4
+ "take item from a chest etc. and put it into the NPC's inventory",
+ -- 5
+ "an item the player offered to the NPC",
+ "NPC crafts something", -- 6
+ "go to other dialog if the previous effect failed", -- 7
+ "send a chat message to all players", -- 8
+ "give item (created out of thin air) to player (requires "..
+ tostring(yl_speak_up.npc_privs_priv).." priv)", -- 9
+ "take item from player and destroy it (requires "..
+ tostring(yl_speak_up.npc_privs_priv).." priv)", -- 10
+ "move the player to a given position (requires "..
+ tostring(yl_speak_up.npc_privs_priv).." priv)", -- 11
+ "execute Lua code (requires npc_master priv)", -- 12
+}
+
+-- how to store these as r_type in the precondition:
+local values_what = {"", "state",
+ "property", "evaluate", "block",
+ -- interact with the inventory of blocks on the map
+ "put_into_block_inv", "take_from_block_inv",
+ -- the player gave an item to the NPC; now deal with it somehow
+ "deal_with_offered_item",
+ -- crafting, handling failure, send chat message to all
+ "craft", "on_failure", "chat_all",
+ -- the following require the yl_speak_up.npc_privs_priv priv:
+ "give_item", "take_item", "move",
+ -- the following require the npc_master priv:
+ "function",
+ }
+
+-- unlike in the preconditions, the "I cannot punch it" option is
+-- not offered here - because the player (and later the NPC) needs
+-- to be able to build at this position
+local check_block = {
+ "- please select -", -- 1
+ "If there is air: Place a block so that it looks like now.", -- 2
+ "If there is a block: Dig it.", -- 3
+ "Punch the block.", -- 4
+ "Right-click the block.", -- 5
+}
+
+-- how to store these as p_value (the actual node data gets stored as p_node, p_param2 and p_pos):
+local values_block = {"", "place", "dig", "punch", "right-click"}
+
+-- comparison operators for variables
+local check_operator = {
+ "- please select -", -- 1
+ "new value:", -- 2
+ "discard/unset/forget", -- 3
+ "current time", -- 4
+ "quest step completed:", -- 5
+ minetest.formspec_escape("max(current, new_value)"), -- 6
+ minetest.formspec_escape("min(current, new_value)"), -- 7
+ "increment by:", -- 8
+ "decrement by:", -- 9
+}
+
+-- how to store these as r_value (the actual variable is stored in r_variable, and the value in r_new_value):
+local values_operator = {"", "set_to", "unset", "set_to_current_time",
+ "quest_step", "maximum", "minimum", "increment", "decrement"}
+
+
+-- get the list of variables the player has *write* access to
+yl_speak_up.get_sorted_player_var_list_write_access = function(pname)
+ local var_list = {}
+ -- some values - like hour of day or HP of the player - can be read in
+ -- a precondition but not be modified
+ -- get the list of variables the player can *write*
+ local tmp = yl_speak_up.get_quest_variables_with_write_access(pname)
+ -- sort that list (the dropdown formspec element returns just an index)
+ table.sort(tmp)
+ for i, v in ipairs(tmp) do
+ table.insert(var_list, v)
+ end
+ return var_list
+end
+
+
+-- helper function for yl_speak_up.show_effect
+-- used by "state" and "property"
+yl_speak_up.show_effect_with_operator = function(r, var_name)
+ if(not(r.r_operator)) then
+ return "Error: Operator not defined."
+ elseif(r.r_operator == "set_to") then
+ return "set "..var_name.." to value \""..
+ tostring(r.r_var_cmp_value).."\""
+ elseif(r.r_operator == "unset") then
+ return "discard "..var_name.." (unset)"
+ elseif(r.r_operator == "set_to_current_time") then
+ return "set "..var_name.." to the current time"
+ elseif(r.r_operator == "quest_step") then
+ return "store that the player has completed quest step \""..
+ tostring(r.r_var_cmp_value).."\""
+ elseif(r.r_operator == "maximum") then
+ return "set "..var_name.." to value \""..
+ tostring(r.r_var_cmp_value).."\" if its current value is larger than that"
+ elseif(r.r_operator == "minimum") then
+ return "set "..var_name.." to value \""..
+ tostring(r.r_var_cmp_value).."\" if its current value is lower than that"
+ elseif(r.r_operator == "increment") then
+ return "increment the value of "..var_name.." by \""..
+ tostring(r.r_var_cmp_value).."\""
+ elseif(r.r_operator == "decrement") then
+ return "decrement the value of "..var_name.." by \""..
+ tostring(r.r_var_cmp_value).."\""
+ else
+ return "ERROR: Wrong operator \""..tostring(r.r_operator).."\" for "..var_name
+ end
+end
+
+
+-- returns a human-readable text as description of the effects
+-- (as shown in the edit options dialog and in the edit effect formspec)
+yl_speak_up.show_effect = function(r, pname)
+ if(not(r.r_type) or r.r_type == "") then
+ return "(nothing): Nothing to do. No effect."
+ elseif(r.r_type == "give_item") then
+ return "give_item: Add \""..tostring(r.r_value).."\" to the player's inventory."
+ elseif(r.r_type == "take_item") then
+ return "take_item: Take \""..tostring(r.r_value).."\" from the player's inventory."
+ elseif(r.r_type == "move") then
+ return "move: Move the player to "..tostring(r.r_value).."."
+ elseif(r.r_type == "function") then
+ return "function: execute \""..tostring(r.r_value).."\"."
+ elseif(r.r_type == "trade") then
+ return "trade: obsolete (now defined as an action)"
+ elseif(r.r_type == "dialog") then
+ return "Switch to dialog \""..tostring(r.r_value).."\"."
+ elseif(r.r_type == "state") then
+ local var_name = "VARIABLE[ - ? - ]"
+ if(r.r_variable) then
+ var_name = "VARIABLE[ "..tostring(
+ yl_speak_up.strip_pname_from_var(r.r_variable, pname)).." ]"
+ end
+ return yl_speak_up.show_effect_with_operator(r, var_name)
+ -- the value of a property of the NPC (for generic NPC) ("property"):
+ elseif(r.r_type == "property") then
+ local var_name = "PROPERTY[ "..tostring(r.r_value or "- ? -").." ]"
+ return yl_speak_up.show_effect_with_operator(r, var_name)
+ -- something that has to be calculated or evaluated (=call a function) ("evaluate"):
+ elseif(r.r_type == "evaluate") then
+ local str = ""
+ for i = 1, 9 do
+ str = str..tostring(r["r_param"..tostring(i)])
+ if(i < 9) then
+ str = str..","
+ end
+ end
+ return "FUNCTION["..tostring(r.r_value).."]("..str..")"
+ elseif(r.r_type == "block") then
+ if(not(r.r_pos) or type(r.r_pos) ~= "table"
+ or not(r.r_pos.x) or not(r.r_pos.y) or not(r.r_pos.z)) then
+ return "ERROR: r.r_pos is "..minetest.serialize(r.r_pos)
+ -- we don't check here yet which node is actually there - that will be done upon execution
+ elseif(yl_speak_up.check_blacklisted(r.r_value, r.r_node, r.r_node)) then
+ return "ERROR: Blocks of type \""..tostring(r.r_node).."\" do not allow "..
+ "interaction of type \""..tostring(r.r_value).."\" for NPC."
+ elseif(r.r_value == "place") then
+ return "Place \""..tostring(r.r_node).."\" with param2: "..tostring(r.r_param2)..
+ " at "..minetest.pos_to_string(r.r_pos).."."
+ elseif(r.r_value == "dig") then
+ return "Dig the block at "..minetest.pos_to_string(r.r_pos).."."
+ elseif(r.r_value == "punch") then
+ return "Punch the block at "..minetest.pos_to_string(r.r_pos).."."
+ elseif(r.r_value == "right-click") then
+ return "Right-click the block at "..minetest.pos_to_string(r.r_pos).."."
+ else
+ return "ERROR: Don't know what to do with the block at "..
+ minetest.pos_to_string(r.r_pos)..": \""..tostring(r.r_value).."\"?"
+ end
+ elseif(r.r_type == "craft") then
+ -- this is only shown in the edit options menu and when editing an effect;
+ -- we can afford a bit of calculation here (it's not a precondtion...)
+ if(not(r.r_value) or not(r.r_craft_grid)) then
+ return "ERROR: Crafting not configured correctly."
+ end
+ local craft_str = "Craft \""..tostring(r.r_value).."\" from "..
+ table.concat(r.r_craft_grid, ", ").."."
+ -- check here if the craft receipe is broken
+ local input = {}
+ input.items = {}
+ for i, v in ipairs(r.r_craft_grid) do
+ input.items[ i ] = ItemStack(v or "")
+ end
+ input.method = "normal" -- normal crafting; no cooking or fuel or the like
+ input.width = 3
+ local output, decremented_input = minetest.get_craft_result(input)
+ if(output.item:is_empty()) then
+ return "Error: Recipe changed! No output for "..craft_str
+ end
+ -- the craft receipe may have changed in the meantime and yield a diffrent result
+ local expected_stack = ItemStack(r.r_value)
+ if(output.item:get_name() ~= expected_stack:get_name()
+ or output.item:get_count() ~= expected_stack:get_count()) then
+ return "Error: Amount of output changed! "..craft_str
+ end
+ return craft_str
+ elseif(r.r_type == "on_failure") then
+ return "If the *previous* effect failed, go to dialog \""..tostring(r.r_value).. "\"."
+ elseif(r.r_type == "chat_all") then
+ return "Send chat message: \""..tostring(r.r_value).."\""
+ elseif(r.r_type == "put_into_block_inv") then
+ if(not(r.r_pos) or type(r.r_pos) ~= "table"
+ or not(r.r_pos.x) or not(r.r_pos.y) or not(r.r_pos.z)) then
+ return "ERROR: r.r_pos is "..minetest.serialize(r.r_pos)
+ end
+ return "Put item \""..tostring(r.r_itemstack).."\" from NPC inv into block at "..
+ minetest.pos_to_string(r.r_pos)..
+ " in inventory list \""..tostring(r.r_inv_list_name).."\"."
+ elseif(r.r_type == "take_from_block_inv") then
+ if(not(r.r_pos) or type(r.r_pos) ~= "table"
+ or not(r.r_pos.x) or not(r.r_pos.y) or not(r.r_pos.z)) then
+ return "ERROR: r.r_pos is "..minetest.serialize(r.r_pos)
+ end
+ return "Take item \""..tostring(r.r_itemstack).."\" from block at "..
+ minetest.pos_to_string(r.r_pos)..
+ " out of inventory list \""..tostring(r.r_inv_list_name)..
+ "\" and put it into the NPC's inventory."
+ elseif(r.r_type == "deal_with_offered_item") then
+ local nr = 1
+ if(r.r_value) then
+ nr = math.max(1, table.indexof(yl_speak_up.dropdown_values_deal_with_offered_item,
+ r.r_value))
+ return yl_speak_up.dropdown_list_deal_with_offered_item[ nr ]
+ end
+ return "ERROR: Missing subtype r.r_value: \""..tostring(r.r_value).."\""
+ end
+ -- fallback
+ return tostring(r.r_value)
+end
+
+-- these are only wrapper functions for those in fs_edit_general.lua
+
+yl_speak_up.input_edit_effects = function(player, formname, fields)
+ return yl_speak_up.handle_input_fs_edit_option_related(player, formname, fields,
+ "r_", "o_results", yl_speak_up.max_result_effects,
+ "(Ef)fect", "tmp_result",
+ "Please punch the block you want to manipulate in your effect!",
+ values_what, values_operator, values_block, {}, {},
+ check_what, check_operator, check_block, {}, {},
+ -- player variables with write access
+ yl_speak_up.get_sorted_player_var_list_write_access,
+ "edit_effects"
+ )
+end
+
+yl_speak_up.get_fs_edit_effects = function(player, table_click_result)
+ return yl_speak_up.build_fs_edit_option_related(player, table_click_result,
+ "r_", "o_results", yl_speak_up.max_result_effects,
+ "(Ef)fect", "tmp_result",
+ "What do you want to change with this effect?",
+ values_what, values_operator, values_block, {}, {},
+ check_what, check_operator, check_block, {}, {},
+ -- player variables with write access
+ yl_speak_up.get_sorted_player_var_list_write_access,
+ yl_speak_up.show_effect,
+ "table_of_elements",
+ "Change the value of the following variable:", "Set variable to:", "New value:",
+ "The NPC shall do something to the block at the following position:"
+ )
+end
+
+
+yl_speak_up.register_fs("edit_effects",
+ yl_speak_up.input_edit_effects,
+ yl_speak_up.get_fs_edit_effects,
+ -- no special formspec required:
+ nil
+)
diff --git a/fs/fs_edit_options_dialog.lua b/fs/fs_edit_options_dialog.lua
new file mode 100644
index 0000000..16eaa38
--- /dev/null
+++ b/fs/fs_edit_options_dialog.lua
@@ -0,0 +1,853 @@
+
+-- helper function; used by
+-- * yl_speak_up.get_fs_edit_option_dialog and
+-- * yl_speak_up.get_fs_edit_trade_limit
+yl_speak_up.get_list_of_effects_and_target_dialog_and_effect = function(dialog, results, pname, target_dialog, target_effect)
+ local list_of_effects = ""
+ local count_effects = 0
+ if(results) then
+ local sorted_key_list = yl_speak_up.sort_keys(results)
+ for i, k in ipairs(sorted_key_list) do
+ local v = results[ k ]
+ if v.r_type == "dialog" and (dialog.n_dialogs[v.r_value] ~= nil or v.r_value == "d_end" or v.r_value == "d_got_item") then
+ list_of_effects = list_of_effects..
+ minetest.formspec_escape(v.r_id)..",#999999,"..
+ minetest.formspec_escape(v.r_type)..","..
+ minetest.formspec_escape(
+ yl_speak_up.show_effect(v, pname))..","
+ -- there may be more than one in the data structure
+ target_dialog = v.r_value
+ target_effect = v
+ elseif v.r_type ~= "dialog" then
+ list_of_effects = list_of_effects..
+ minetest.formspec_escape(v.r_id)..",#FFFF00,"..
+ minetest.formspec_escape(v.r_type)..","..
+ minetest.formspec_escape(
+ yl_speak_up.show_effect(v, pname))..","
+ end
+ count_effects = count_effects + 1
+ end
+ end
+ if(count_effects < yl_speak_up.max_result_effects) then
+ list_of_effects = list_of_effects..",#00FF00,add,Add a new (Ef)fect"
+ else
+ list_of_effects = list_of_effects..",#AAAAAA,-,"..
+ "Maximum amount of allowed (Ef)fects per option reached!"
+ end
+ return {list = list_of_effects, target_dialog = target_dialog, target_effect = target_effect}
+end
+
+
+-- process input from formspec created in get_fs_edit_option_dialog(..)
+yl_speak_up.input_edit_option_dialog = function(player, formname, fields)
+ if formname ~= "yl_speak_up:edit_option_dialog" then
+ return
+ end
+ local pname = player:get_player_name()
+
+ -- Is the player working on this particular npc?
+ local edit_mode = (yl_speak_up.edit_mode[pname] == yl_speak_up.speak_to[pname].n_id)
+ if(not(edit_mode)) then
+ return
+ end
+ local n_id = yl_speak_up.speak_to[pname].n_id
+ local d_id = yl_speak_up.speak_to[pname].d_id
+ local o_id = yl_speak_up.speak_to[pname].o_id
+
+ local dialog = yl_speak_up.speak_to[pname].dialog
+ local n_dialog = dialog.n_dialogs[d_id]
+ if(not(n_dialog) or not(n_dialog.d_options)) then
+ return
+ end
+ local d_option = n_dialog.d_options[o_id]
+ if(not(d_option)) then
+ return
+ end
+
+ if(fields.assign_quest_step and fields.assign_quest_step ~= "") then
+ yl_speak_up.show_fs(player, "assign_quest_step",
+ {n_id = n_id, d_id = d_id, o_id = o_id})
+ return
+ end
+
+ if(fields.switch_tab and fields.switch_tab == "2") then
+ yl_speak_up.show_fs(player, "edit_option_dialog",
+ {n_id = n_id, d_id = d_id, o_id = o_id,
+ caller="show_if_action_failed"})
+ return
+ elseif(fields.switch_tab and fields.switch_tab == "1") then
+ yl_speak_up.show_fs(player, "edit_option_dialog",
+ {n_id = n_id, d_id = d_id, o_id = o_id,
+ caller="show_if_action_succeeded"})
+ return
+ elseif(fields.switch_tab and fields.switch_tab == "3") then
+ yl_speak_up.show_fs(player, "edit_option_dialog",
+ {n_id = n_id, d_id = d_id, o_id = o_id,
+ caller="show_tab_limit_guessing"})
+ return
+ elseif(fields.switch_tab and fields.switch_tab == "4") then
+ yl_speak_up.show_fs(player, "edit_option_dialog",
+ {n_id = n_id, d_id = d_id, o_id = o_id,
+ caller="show_tab_limit_repeating"})
+ return
+ end
+
+ -- this menu is specific to an option for a dialog; if no dialog is selected, we really
+ -- can't know what to do
+ if(not(o_id) and d_id) then
+ yl_speak_up.show_fs(player, "talk", {n_id = n_id, d_id = d_id})
+ elseif(not(d_id)) then
+ return
+ end
+
+ -- backwards compatibility to when this was a hidden field
+ fields.o_id = o_id
+ -- handles changes to o_text_when_prerequisites_met, target dialog, adding of a new dialog
+ local result = yl_speak_up.edit_mode_apply_changes(pname, fields)
+ -- if a new option was added or the target dialog of this one changed, display the right new option
+ if(result and result["show_next_option"] and n_dialog.d_options[result["show_next_option"]]) then
+ yl_speak_up.show_fs(player, "edit_option_dialog",
+ {n_id = n_id, d_id = d_id, o_id = result["show_next_option"],
+ caller="show_next_option"})
+ return
+ end
+
+ if(fields.save_option) then
+ yl_speak_up.show_fs(player, "edit_option_dialog",
+ {n_id = n_id, d_id = d_id, o_id = o_id, caller="save_option"})
+ return
+ end
+
+ -- want to edit the text that is shown when switching to the next dialog?
+ if(fields.button_edit_action_failed_dialog
+ or fields.button_edit_action_success_dialog
+ or fields.save_dialog_modification
+ or fields.button_edit_limit_action_failed_repeat
+ or fields.button_edit_limit_action_success_repeat
+ or fields.turn_alternate_text_into_new_dialog) then
+ if( yl_speak_up.handle_edit_actions_alternate_text(
+ -- x_id, id_prefix, target_element and tmp_data_cache are nil here
+ player, pname, n_id, d_id, o_id, nil, nil,
+ "edit_option_dialog", nil, fields, nil)) then
+ -- the function above showed a formspec already
+ return
+ else
+ yl_speak_up.show_fs(player, "edit_option_dialog",
+ {n_id = n_id, d_id = d_id, o_id = o_id,
+ caller="back_from_edit_dialog_modifications"})
+ return
+ end
+
+ elseif(fields.back_from_edit_dialog_modification) then
+ -- no longer working on an alternate text
+ yl_speak_up.speak_to[pname].edit_alternate_text_for = nil
+ yl_speak_up.show_fs(player, "edit_option_dialog",
+ {n_id = n_id, d_id = d_id, o_id = o_id, caller="back_from_edit_dialog_modifications"})
+ return
+ end
+
+ -- back to the main dialog window?
+ -- (this also happens when the last option was deleted)
+ if(fields.show_current_dialog or fields.quit or fields.button_exit or not(d_option) or fields.del_option) then
+ yl_speak_up.show_fs(player, "talk", {n_id = n_id, d_id = d_id})
+ return
+ end
+
+ -- the player wants to see the previous option/answer
+ if(fields.edit_option_prev) then
+ -- sort all options by o_sort
+ local sorted_list = yl_speak_up.get_sorted_options(n_dialog.d_options, "o_sort")
+ local o_found = o_id
+ for i, o in ipairs(sorted_list) do
+ if(o == o_id and sorted_list[ i-1]) then
+ o_found = sorted_list[ i-1 ]
+ end
+ end
+ -- show that dialog; fallback: show the same (o_id) again
+ yl_speak_up.show_fs(player, "edit_option_dialog",
+ {n_id = n_id, d_id = d_id, o_id = o_found, caller="prev option"})
+ return
+
+ -- the player wants to see the next option/answer
+ elseif(fields.edit_option_next) then
+ -- sort all options by o_sort
+ local sorted_list = yl_speak_up.get_sorted_options(n_dialog.d_options, "o_sort")
+ local o_found = o_id
+ for i, o in ipairs(sorted_list) do
+ if(o == o_id and sorted_list[ i+1 ]) then
+ o_found = sorted_list[ i+1 ]
+ end
+ end
+ -- show that dialog; fallback: show the same (o_id) again
+ yl_speak_up.show_fs(player, "edit_option_dialog",
+ {n_id = n_id, d_id = d_id, o_id = o_found, caller="next option"})
+ return
+
+ -- the player clicked on a precondition
+ elseif(fields.table_of_preconditions) then
+ yl_speak_up.show_fs(player, "edit_preconditions", fields.table_of_preconditions)
+ return
+
+ -- the player clicked on an action
+ elseif(fields.table_of_actions) then
+ yl_speak_up.show_fs(player, "edit_actions", fields.table_of_actions)
+ return
+
+ -- the player clicked on an effect
+ elseif(fields.table_of_effects) then
+ yl_speak_up.show_fs(player, "edit_effects", fields.table_of_effects)
+ return
+ end
+
+ -- if ESC is pressed or anything else unpredicted happens: go back to the main dialog edit window
+ -- reason: don't loose any unsaved changes to the dialog
+ yl_speak_up.show_fs(player, "talk", {n_id = n_id, d_id = d_id})
+end
+
+
+-- edit options (not via staff but via the "I am your owner" dialog)
+yl_speak_up.get_fs_edit_option_dialog = function(player, n_id, d_id, o_id, caller)
+ -- n_id, d_id and o_id have already been checked when this function is called
+ local pname = player:get_player_name()
+ local dialog = yl_speak_up.speak_to[pname].dialog
+ local n_dialog = dialog.n_dialogs[d_id]
+
+ -- currently no trade running (we're editing options)
+ yl_speak_up.trade[pname] = nil
+ yl_speak_up.speak_to[pname].trade_id = nil
+
+ if(not(n_dialog) or not(n_dialog.d_options) or not(n_dialog.d_options[o_id])) then
+ return "size[6,2]"..
+ "label[0.2,0.5;Ups! Option "..minetest.formspec_escape(tostring(o_id))..
+ " does not exist.]"..
+ "button_exit[2,1.5;1,0.9;exit;Exit]"
+ end
+ local d_option = n_dialog.d_options[o_id]
+
+ -- if it is a quest step, then show that; else allow creating a quest step
+ local quest_step_text = "button[15.4,0.1;6.0,0.9;assign_quest_step;Turn this into a quest step]"
+ if( d_option.quest_id and d_option.quest_id ~= ""
+ and d_option.quest_step and d_option.quest_step ~= "") then
+ local q_id = ""
+ local quest_name = "["..tostring(d_option.quest_id).."] - unknown quest -"
+ for q_id, data in pairs(yl_speak_up.quests) do
+ if(data and data.var_name == d_option.quest_id) then
+ quest_name = "["..tostring(q_id)..": "..
+ tostring(yl_speak_up.strip_pname_from_var(data.var_name, pname))..
+ "] "..
+ tostring(data.name)
+ end
+ end
+ quest_step_text = table.concat({"box[4.9,0.0;14.0,1.1;#BB77BB]",
+ "label[0.2,0.3;This is quest step:]",
+ "label[5.0,0.3;",
+ minetest.colorize("#00FFFF",
+ minetest.formspec_escape(d_option.quest_step)),
+ "]",
+ "label[0.2,0.8;of the quest:]",
+ "label[5.0,0.8;",
+ minetest.colorize("#CCCCFF",
+ minetest.formspec_escape(quest_name)),
+ "]",
+ "button[19.4,0.1;2.0,0.9;assign_quest_step;Change]"
+ }, "")
+ end
+
+
+ -- offer the correct preselection for hidden/grey/show text
+ local alternate_answer_option = "3"
+ if(d_option.o_hide_when_prerequisites_not_met == "true") then
+ alternate_answer_option = "1"
+ elseif(d_option.o_grey_when_prerequisites_not_met == "true") then
+ alternate_answer_option = "2"
+ end
+
+ local answer_mode = 0
+ -- shall this option be choosen automaticly?
+ if(d_option.o_autoanswer and d_option.o_autoanswer == 1) then
+ answer_mode = 1
+ -- or randomly?
+ elseif(n_dialog.o_random) then
+ answer_mode = 2
+ end
+
+ -- show an option only once
+ local visit_only_once = 0
+ if(d_option.o_visit_only_once and d_option.o_visit_only_once == 1) then
+ visit_only_once = 1
+ end
+
+ local answer_text =
+ -- answer of the player (the actual option)
+ "container[0.0,8.3]"..
+ "label[0.2,0.0;..the player may answer with this text"..
+ minetest.formspec_escape(" [dialog option \""..tostring(o_id).."\"]:").."]"..
+ "dropdown[13.3,-0.4;2.5,0.7;option_visits;"..
+ "often,*once*;"..tostring(visit_only_once + 1)..";]"..
+ "tooltip[option_visits;\"often\" allows to select this option whenever the\n"..
+ "\tpreconditions are fulfilled.\n"..
+ "\"*once*\" greys out the option after it has been selected\n"..
+ "\tone time successfully.\n"..
+ "Useful for visually marking options as read for the player.\n"..
+ "Talking to the NPC anew resets this option and it can be selected again.]"..
+ "dropdown[16.0,-0.4;5.3,0.7;option_autoanswer;"..
+ "by clicking on it,automaticly,randomly;"..tostring(answer_mode+1)..";]"
+ -- (automaticly *by fulfilling the prerequirements*)
+ if(d_id == "d_got_item" or d_id == "d_trade") then
+ answer_mode = 1
+ d_option.o_autoanswer = 1
+ answer_text =
+ "container[0.0,8.3]"..
+ "label[0.2,0.0;..this option will be selected automaticly.]"
+ end
+ if(answer_mode == 0 and (d_id ~= "d_got_item" and d_id ~= "d_trade")) then
+ answer_text = table.concat({answer_text,
+ "label[1.2,0.8;A:]",
+ "field[1.7,0.3;19.6,0.9;text_option_",
+ minetest.formspec_escape(o_id),
+ ";;",
+ minetest.formspec_escape(d_option.o_text_when_prerequisites_met),
+ "]",
+ "tooltip[option_text_met;This is the answer the player may choose if the "..
+ "preconditions are all fulfilled.]",
+ -- dropdown for selecting weather to show the alternate answer or not
+ "label[0.2,1.7;..but if at least one pre(C)ondition is not fulfilled, then...]",
+ "dropdown[12.0,1.3;9.3,0.7;hide_or_grey_or_alternate_answer;",
+ "..hide this answer.,",
+ "..grey out the following answer:,",
+ "..display the following alternate answer:;",
+ alternate_answer_option,
+ ";]",
+ -- alternate answer
+ "label[1.2,2.5;A:]",
+ "field[1.7,2.0;19.6,0.9;option_text_not_met;;",
+ minetest.formspec_escape(d_option.o_text_when_prerequisites_not_met),
+ "]",
+ "tooltip[option_text_not_met;This is the answer the player may choose if the "..
+ "preconditions are NOT all fulfilled.]",
+ "container_end[]"
+ }, "")
+ elseif(answer_mode == 1) then
+ answer_text = answer_text..
+ "label[1.2,0.8;This option will not be shown but will be selected automaticly if all "..
+ "prerequirements are fullfilled.]"..
+ "label[1.2,1.4;The remaining options of this dialog will in this case not be evaluated.]"..
+ "label[1.2,2.0;The NPC will proceed as if this option was choosen manually.]"..
+ "label[1.2,2.6;"
+ if(d_id == "d_got_item") then
+ answer_text = answer_text..
+ "Note: This is used here to process items that the player gave to the NPC."
+ elseif(d_id == "d_trade") then
+ answer_text = answer_text..
+ "Note: This is useful for refilling stock by crafting new things when "..
+ "necessary, or for getting\nsupplies from a storage, or for storing "..
+ "traded goods in external storage chests."
+ else
+ answer_text = answer_text..
+ "This is i.e. useful for offering a diffrent start dialog depending on the "..
+ "player's progress in a quest."
+ end
+ answer_text = answer_text .. "]container_end[]"
+ elseif(answer_mode == 2) then
+ answer_text = answer_text..
+ "label[1.2,0.8;One option of the dialog - for example this one - will be selected randomly.]"..
+ "label[1.2,1.4;The other options of this dialog will be set to random as well.]"..
+ "label[1.2,2.0;The NPC will proceed as if this dialog was choosen manually.]"..
+ "label[1.2,2.6;Useful for small talk for generic NPC but usually not for quests.]"..
+ "container_end[]"
+ end
+
+ -- remember which option we are working at (better than a hidden field)
+ yl_speak_up.speak_to[pname].o_id = o_id
+ -- are there any preconditions?
+ local list_of_preconditions = ""
+ local prereq = d_option.o_prerequisites
+ local count_prereq = 0
+ if(prereq) then
+ local sorted_key_list = yl_speak_up.sort_keys(prereq)
+ for i, k in ipairs(sorted_key_list) do
+ local v = prereq[ k ]
+ list_of_preconditions = list_of_preconditions..
+ minetest.formspec_escape(v.p_id)..",#FFFF00,"..
+ minetest.formspec_escape(v.p_type)..","..
+ minetest.formspec_escape(
+ yl_speak_up.show_precondition(v, pname))..","
+ count_prereq = count_prereq + 1
+ end
+ end
+ if(count_prereq < yl_speak_up.max_prerequirements) then
+ list_of_preconditions = list_of_preconditions..",#00FF00,add,Add a new pre(C)ondition"
+ else
+ list_of_preconditions = list_of_preconditions..",#AAAAAA,-,"..
+ "Maximum amount of pre(C)onditions per option reached!"
+ end
+
+ -- build action list the same way as list of preconditions and effects
+ local list_of_actions = ""
+ local actions = d_option.actions
+ local count_actions = 0
+ local action_data = nil
+ -- if autoanswer or random is choosen, then there can be no action
+ if(answer_mode == 1 or answer_mode == 2) then
+ actions = nil
+ count_actions = 0
+ caller = ""
+ end
+ if(actions) then
+ local sorted_key_list = yl_speak_up.sort_keys(actions)
+ for i, k in ipairs(sorted_key_list) do
+ local v = actions[ k ]
+ list_of_actions = list_of_actions..
+ minetest.formspec_escape(v.a_id)..",#FFFF00,"..
+ minetest.formspec_escape(v.a_type)..","..
+ minetest.formspec_escape(
+ yl_speak_up.show_action(v))..","
+ count_actions = count_actions + 1
+ action_data = v
+ end
+ end
+ if(count_actions < yl_speak_up.max_actions) then
+ list_of_actions = list_of_actions..",#00FF00,add,Add a new (A)ction"
+ else
+ list_of_actions = list_of_actions..",#AAAAAA,-,"..
+ "Maximum amount of (A)ctions per option reached!"
+ end
+
+ -- list of (A)ctions (there can only be one per option; i.e. a trade)
+ local action_list_text =
+ "container[0.0,12.0]"..
+ "label[0.2,0.0;When this answer has been selected, start the following (A)ction:]"..
+ "tablecolumns[text;color,span=1;text;text]"
+ if(answer_mode == 1) then
+ action_list_text = action_list_text..
+ "label[1.2,0.6;No actions are executed because this option here is automaticly selected.]"..
+ "container_end[]"
+ elseif(answer_mode == 2) then
+ action_list_text = action_list_text..
+ "label[1.2,0.6;No actions are executed because this option here is selected randomly.]"..
+ "container_end[]"
+ else
+ action_list_text = action_list_text..
+ "table[1.2,0.3;20.2,0.7;table_of_actions;"..
+ list_of_actions..";0]"..
+ "container_end[]"
+ end
+
+ -- find the right target dialog for this option (if it exists)
+ local target_dialog = nil
+ -- which effect holds the information about the target dialog?
+ -- set this to a fallback for yl_speak_up.show_colored_dialog_text
+ local target_effect = {r_id = "-?-", r_type = "dialog"}
+ -- and build the list of effects
+ local results = d_option.o_results
+ -- create a new dialog type option if needed
+ if(not(results) or not(next(results))) then
+ target_dialog = yl_speak_up.prepare_new_dialog_for_option(
+ dialog, pname, n_id, d_id, o_id,
+ yl_speak_up.text_new_dialog_id,
+ results)
+ -- make sure we are up to date (a new option was inserted)
+ results = d_option.o_results
+ end
+ -- constructs the list_of_effects; may also update target_dialog and target_effect
+ local res = yl_speak_up.get_list_of_effects_and_target_dialog_and_effect(dialog, results, pname,
+ target_dialog, target_effect)
+ local list_of_effects = res.list
+ target_dialog = res.target_dialog
+ target_effect = res.target_effect
+
+ -- if no target dialog has been selected: default is to go to the dialog with d_sort 0
+ if(not(target_dialog) or target_dialog == "" or
+ (not(dialog.n_dialogs[target_dialog])
+ and target_dialog ~= "d_end"
+ and target_dialog ~= "d_got_item")) then
+ for d, v in pairs(dialog.n_dialogs) do
+ if(v.d_sort and tonumber(v.d_sort) == 0) then
+ target_dialog = d
+ end
+ end
+ end
+ -- build the list of available dialogs for the dropdown list(s)
+ local dialog_list = yl_speak_up.text_new_dialog_id
+ local dialog_selected = "1"
+ -- if there are dialogs defined
+ if(dialog and dialog.n_dialogs) then
+ -- the first entry will be "New dialog"
+ local n = 1
+ for k, v in pairs(dialog.n_dialogs) do
+ local d_name = (v.d_name or v.d_id or "?")
+ -- build the list of available dialogs for the dropdown list(s)
+ dialog_list = dialog_list..","..minetest.formspec_escape(d_name)
+ -- which one is the current dialog?
+ n = n + 1
+ if(v.d_id == target_dialog) then
+ dialog_selected = tostring(n)
+ end
+ end
+ if(target_dialog == "d_end") then
+ dialog_selected = tostring(n + 1)
+ end
+ end
+ dialog_list = dialog_list..",d_end"
+ if(not(target_dialog)) then
+ target_dialog = "- none -"
+ end
+
+
+ -- can the button "prev(ious)" be shown?
+ local button_prev = ""
+ -- can the button "next" be shown?
+ local button_next = ""
+ -- sort all options by o_sort
+ local sorted_list = yl_speak_up.get_sorted_options(n_dialog.d_options, "o_sort")
+ local o_found = o_id
+ local anz_options = 0
+ for i, o in ipairs(sorted_list) do
+ -- the buttons are inside a container; thus, Y is 0.0
+ if(o == o_id and sorted_list[ i-1 ]) then
+ button_prev = ""..
+ "button[7.9,0.0;2.0,0.9;edit_option_prev;Prev]"..
+ "tooltip[edit_option_prev;Go to previous option/answer "..
+ "(according to o_sort).]"
+ end
+ if(o == o_id and sorted_list[ i+1 ]) then
+ button_next = ""..
+ "button[12.5,0.0;2.0,0.9;edit_option_next;Next]"..
+ "tooltip[edit_option_next;Go to next option/answer "..
+ "(according to o_sort).]"
+ end
+ anz_options = anz_options + 1
+ end
+
+ -- less than yl_speak_up.max_number_of_options_per_dialog options?
+ local button_add = ""..
+ -- the buttons are inside a container; thus, Y is 0.0
+ "button[2.4,0.0;2.0,0.9;add_option;Add]"..
+ "tooltip[add_option;Add a new option/answer to this dialog.]"
+ if(anz_options >= yl_speak_up.max_number_of_options_per_dialog
+ or target_dialog == "d_end") then
+ button_add = ""
+ end
+
+ -- make all following coordinates relative
+ local action_text = "container[0.2,14.0]"..
+ "box[0.25,0.0;21.0,6.7;#555555]"
+ local tab_list = "tabheader[0.2,0.0;switch_tab;"..
+ "If the action was successful:,"..
+ "If the action failed:,"..
+ "Limit guessing:,"..
+ "Limit repeating:"
+ -- show what happens if the action fails
+ if(caller == "show_if_action_failed") then
+ -- allow to switch between successful and failed actions
+ action_text = action_text..tab_list..";2;true;true]"..
+ "label[0.4,0.6;"..
+ "If the player *failed* to complete the above action correctly,]"
+ if(action_data and action_data.a_on_failure
+ and dialog.n_dialogs and dialog.n_dialogs[ action_data.a_on_failure]) then
+ action_text = action_text..
+ -- ..and what the NPC will reply to that answer
+ "tooltip[1.2,3.9;19.6,2.5;This is what the NPC will say next when "..
+ "the player has failed to complete the action.]"..
+
+ "container[0.0,3.2]"..
+ "label[0.4,0.4;..the NPC will react to this failed action with the "..
+ "following dialog \""..tostring(action_data.a_on_failure)..
+ "\""..
+ yl_speak_up.show_colored_dialog_text(
+ dialog,
+ action_data,
+ action_data.a_on_failure,
+ "1.2,0.7;19.6,2.5;d_text_next",
+ "with the *modified* text",
+ ":]",
+ "button_edit_action_failed_dialog")..
+ "container_end[]"
+ else
+ action_text = action_text..
+ "label[0.4,3.6;..go back to the initial dialog.]"
+ end
+ -- show time-based restrictions (max guesses per time);
+ -- the values will be saved in function yl_speak_up.edit_mode_apply_changes
+ elseif( caller == "show_tab_limit_guessing") then
+ local timer_name = "timer_on_failure_"..tostring(d_id).."_"..tostring(o_id)
+ local timer_data = yl_speak_up.get_variable_metadata(timer_name, "parameter", true)
+ if(not(timer_data)) then
+ timer_data = {}
+ end
+ action_text = table.concat({action_text,
+ tab_list,
+ ";3;true;true]",
+ -- allow to switch between successful and failed actions
+ "label[0.4,0.6;",
+ "Apply the following time-based restrictions to limit wild guessing:]",
+ -- timer for failed actions
+ "label[0.4,1.6;The player can make]",
+ "field[4.9,1.0;1.5,0.9;timer_max_attempts_on_failure;;",
+ tostring(timer_data[ "max_attempts" ] or 0),
+ "]",
+ "label[6.7,1.6;attempts to complete this action successfully each]",
+ "field[17.5,1.0;1.5,0.9;timer_max_seconds_on_failure;;",
+ tostring(timer_data[ "duration" ] or 0),
+ "]",
+ "label[19.2,1.6;seconds.]",
+ "label[0.4,2.2;Hint: 3 attempts per 1200 seconds (=20 minutes or one MineTest day)"..
+ " may be good values to\navoid wild guessing while not making the player "..
+ "having to wait too long to try again.]"..
+ "tooltip[timer_max_attempts_on_failure;How many tries shall the player have?"..
+ "\nA value of 0 disables this restriction.]"..
+ "tooltip[timer_max_seconds_on_failure;After which time can the player try again?"..
+ "\nA value of 0 disables this restriction.]"..
+ -- ..and what the NPC will explain in such a case
+ "tooltip[1.2,3.9;19.6,2.5;This is what the NPC will say next when "..
+ "\nthe player has failed to complete the action too"..
+ "\nmany times for the NPC's patience and the player"..
+ "\nhas to wait some time before guessing again.]"..
+ "container[0.0,3.2]"..
+ "label[0.4,0.4;The NPC will explain his unwillingness to accept more "..
+ "guesses ",
+ yl_speak_up.show_colored_dialog_text(
+ dialog,
+ {alternate_text = (timer_data[ "alternate_text" ]
+ or yl_speak_up.standard_text_if_action_failed_too_often)},
+ d_id, -- show the same dialog again
+ "1.2,0.7;19.6,2.5;d_text_next",
+ "with the following text",
+ ":]",
+ "button_edit_limit_action_failed_repeat"),
+ "container_end[]"
+ }, "")
+ -- show time-based restrictions (time between repeating this action successfully)
+ elseif( caller == "show_tab_limit_repeating") then
+ local timer_name = "timer_on_success_"..tostring(d_id).."_"..tostring(o_id)
+ local timer_data = yl_speak_up.get_variable_metadata(timer_name, "parameter", true)
+ if(not(timer_data)) then
+ timer_data = {}
+ end
+ action_text = table.concat({action_text,
+ tab_list,
+ ";4;true;true]",
+ "label[0.4,0.6;",
+ "Apply the following time-based restrictions to limit too quick repeating:]",
+ -- timer for successful actions
+ "label[0.4,1.6;If the player completed the action successfully, he shall have to"..
+ " wait]",
+ "field[15.0,1.0;1.5,0.9;timer_max_seconds_on_success;;",
+ tostring(timer_data[ "duration" ] or 0),
+ "]",
+ "label[16.7,1.6;seconds until he]",
+ "label[0.4,2.1;can repeat the action. Hint: 1200 seconds (=20 minutes or one ",
+ "MineTest day) may be a good value.]",
+ "tooltip[timer_max_seconds_on_success;",
+ minetest.formspec_escape(
+ "If you hand out a quest item, you may not want the player"..
+ "\nto immediately repeat the action countless times, thus"..
+ "\nemptying the NPC's storage and using the quest item for"..
+ "\nother purposes. On the other hand, quest items may get "..
+ "\nlost, so the player needs a way to repeat each step."..
+ "\n1200 seconds may be a good value here as well."),
+ "]",
+ -- ..and what the NPC will explain in such a case
+ "tooltip[1.2,3.9;19.6,2.5;",
+ minetest.formspec_escape(
+ "This is what the NPC will say next when the player"..
+ "\nwants to repeat the action too soon for the NPC's"..
+ "\ntaste - after all the NPC does not have infinite "..
+ "\ninventory ressources, and the player may abuse the "..
+ "\nquest item for entirely diffrent purposes.."),
+ "]",
+ "container[0.0,3.2]",
+ -- this will lead back to the same dialog
+ "label[0.4,0.4;The NPC will explain his unwillingness to repeat the "..
+ "action so soon ",
+ yl_speak_up.show_colored_dialog_text(
+ dialog,
+ {alternate_text = (timer_data[ "alternate_text" ]
+ or yl_speak_up.standard_text_if_action_repeated_too_soon)},
+ d_id, -- show the same dialog again
+ "1.2,0.7;19.6,2.5;d_text_next",
+ "with the following text",
+ ":]",
+ "button_edit_limit_action_success_repeat"),
+ "container_end[]"
+ }, "")
+ -- show what happens if the action was successful
+ else
+ -- no action defined
+ if(count_actions == 0) then
+ -- do not show tabheader
+ action_text = action_text..
+ "label[0.4,0.6;"..
+ "There is no (A)ction defined. Directly apply the following (Ef)fects:]"
+ else
+ -- allow to switch between successful and failed actions
+ action_text = table.concat({action_text,
+ tab_list,
+ ";1;true;true]",
+ "label[0.4,0.6;",
+ "If the player completed the above action successfully, "..
+ "apply the following (Ef)fects:]"
+ }, "")
+ end
+ action_text = table.concat({action_text,
+ -- list of effects
+ "tablecolumns[text;color,span=1;text;text]",
+ "table[1.2,0.9;19.6,2.0;table_of_effects;",
+ list_of_effects,
+ ";0]",
+ "tooltip[1.2,0.9;19.6,2.0;"..
+ "*All* (Ef)fects are executed after the action (if there is\n"..
+ "one defined in this option) has been completed successfully\n"..
+ "by the player. If there is no action defined, then the\n"..
+ "(Ef)fects will always be executed when this option here is\n"..
+ "selected.\n"..
+ "Please click on an (Ef)fect in order to edit or delete it!]",
+ "container[0.0,3.2]",
+ "label[0.4,0.4;The NPC will react to this answer with dialog:]"
+ }, "")
+ if(d_id == "d_trade") then
+ action_text = action_text..
+ "label[13.5,0.4;..by showing his trade list.]"..
+ "container_end[]"
+ else
+ action_text = table.concat({action_text,
+ -- allow to change the target dialog via a dropdown menu
+ "dropdown[11,0.0;9.8,0.7;d_id_",
+ minetest.formspec_escape(o_id),
+ ";",
+ dialog_list,
+ ";",
+ dialog_selected,
+ ",]",
+ "tooltip[10.2,0.0;3.0,0.7;Select the target dialog with which the NPC shall react "..
+ "to this answer.\nCurrently, dialog \"",
+ minetest.formspec_escape(target_dialog),
+ "\" is beeing displayed.;#FFFFFF;#000000]",
+ -- ..and what the NPC will reply to that answer
+ "tooltip[1.2,0.7;19.6,2.5;This is what the NPC will say next when the player has "..
+ "selected this answer here.]",
+ yl_speak_up.show_colored_dialog_text(
+ dialog,
+ -- this is either the "dialog" effect or an empty fallback
+ target_effect,
+ -- this is the text the NPC will say in reaction to this answer
+ target_dialog,
+ "1.2,0.7;19.6,2.5;d_text",
+ "label[13.5,0.4;with the following *modified* text:]",
+ "",
+ "button_edit_action_success_dialog"),
+ "container_end[]"
+ }, "")
+ end
+ end
+ action_text = action_text.."container_end[]"
+
+ -- build up the formspec
+ local formspec = table.concat({
+ "size[22,22]",
+ "bgcolor[#00000000;false]",
+ -- button back to the current dialog (of which this is an option)
+ "button[16.4,0.2;5.0,0.9;show_current_dialog;Back to dialog ",
+ minetest.formspec_escape(d_id),
+ "]",
+ "tooltip[show_current_dialog;Go back to dialog ",
+ minetest.formspec_escape(d_id),
+ " and continue editing that dialog.]",
+ -- tell the player what this formspec is about
+ "label[6.5,0.4;You are editing dialog option \"",
+ tostring(o_id),
+ "\":]",
+
+ -- the text the NPC says
+ "container[0.0,0.9]",
+ "label[0.2,0.0;NPC says ",
+ minetest.formspec_escape("[dialog \""..tostring(d_id).."\"]:"),
+ "]",
+ yl_speak_up.show_colored_dialog_text(
+ dialog,
+ {r_id = "", r_type = "dialog"},
+ d_id,
+ "1.2,0.3;20.2,2.5;d_text",
+ "", -- no modifications possible at this step
+ "",
+ ""), -- no edit button here as this text cannot be changed here
+ "tooltip[1.2,0.3;20.2,3.0;This is what the NPC says to the player.]",
+ "container_end[]",
+
+ "container[0.0,3.9]",
+ quest_step_text,
+ "container_end[]",
+
+ -- list the preconditions
+ "container[0.0,5.4]",
+ "label[0.2,0.0;If all of the following pre(C)onditions are fulfilled:]",
+ "tablecolumns[text;color,span=1;text;text]",
+ "table[1.2,0.3;20.2,2.0;table_of_preconditions;",
+ list_of_preconditions,
+ ";0]",
+ "tooltip[1.2,0.3;20.2,2.0;",
+ "*All* pre(C)onditions need to be true in order\n"..
+ "for the option to be offered to the player.\n"..
+ "Please click on a pre(C)ondition in order\n"..
+ "to edit or delete it!]",
+ "container_end[]",
+
+ -- answer of the player (the actual option)
+ answer_text,
+
+ -- list of (A)ctions (there can only be one per option; i.e. a trade)
+ action_list_text,
+
+ -- list effects and target dialog for successful - and target dialog for unsuccessful
+ -- actions (including a toggle button)
+ action_text,
+
+ -- container for the buttons/footer
+ "container[0.0,20.9]",
+ -- button: delete
+ "button[0.2,0.0;2.0,0.9;del_option;Delete]",
+ "tooltip[del_option;Delete this option/answer.]",
+ -- button: add new
+ button_add,
+ -- button: save
+ "button[4.6,0.0;2.0,0.9;save_option;Save]",
+ "tooltip[save_option;Save what you canged (or discard it).]",
+ -- button: prev/next
+ button_prev,
+ button_next,
+ -- button: go back to dialog (repeated from top of the page)
+ "button[15.8,0.0;5.0,0.9;show_current_dialog;Back to dialog ",
+ minetest.formspec_escape(d_id),
+ "]",
+ "tooltip[show_current_dialog;Go back to dialog ",
+ minetest.formspec_escape(d_id),
+ " and continue editing that dialog.]",
+ -- allow to enter o_sort
+ "label[10.1,0.5;Sort:]",
+ "field[11.1,0.0;1.0,0.9;edit_option_o_sort;;",
+ minetest.formspec_escape(d_option.o_sort),
+ "]",
+ "tooltip[edit_option_o_sort;o_sort: The lower the number, the higher up in the "..
+ "list this option goes\nNegative values are ignored;#FFFFFF;#000000]",
+ "container_end[]"
+ }, "")
+ return formspec
+end
+
+
+yl_speak_up.get_fs_edit_option_dialog_wrapper = function(player, param)
+ if(not(param)) then
+ param = {}
+ end
+ local pname = player:get_player_name()
+ yl_speak_up.speak_to[pname].o_id = param.o_id
+ return yl_speak_up.get_fs_edit_option_dialog(player, param.n_id, param.d_id, param.o_id, param.caller)
+end
+
+
+yl_speak_up.register_fs("edit_option_dialog",
+ yl_speak_up.input_edit_option_dialog,
+ yl_speak_up.get_fs_edit_option_dialog_wrapper,
+ -- no special formspec required:
+ nil
+)
diff --git a/fs/fs_edit_preconditions.lua b/fs/fs_edit_preconditions.lua
new file mode 100644
index 0000000..63c4758
--- /dev/null
+++ b/fs/fs_edit_preconditions.lua
@@ -0,0 +1,377 @@
+-- This file contains what is necessary to add/edit a precondition.
+--
+-- Which diffrent types of preconditions are available?
+-- -> The following fields are part of a precondition:
+-- p_id the ID/key of the precondition/prerequirement
+-- p_type selected from values_what
+-- p_value used to store the subtype of p_type
+--
+-- a state/variable ("state"):
+-- p_variable name of a variable the player has read access to;
+-- dropdown list with allowed options
+-- p_operator selected from values_operator
+-- p_var_cmp_value can be set freely by the player
+--
+-- the value of a property of the NPC (for generic NPC) ("property"):
+-- p_value name of the property that shall be checked
+-- p_operator operator for cheking the property against p_expected_val
+-- p_var_cmp_value the expected value of the property
+--
+-- something that has to be calculated or evaluated (=call a function) ("evaluate"):
+-- p_value the name of the function that is to be called
+-- p_param1 the first paramter (optional; depends on function)
+-- ..
+-- p_param9 the 9th parameter (optional; depends on function)
+-- p_operator operator for checking the result
+-- p_var_cmp_value compare the result of the function with this value
+--
+-- a block in the world ("block"):
+-- p_pos a position in the world; determined by asking the player
+-- to punch the block
+-- p_node (follows from p_pos)
+-- p_param2 (follows from p_pos)
+--
+-- a trade defined as an action ("trade"): no variables needed (buy and pay stack
+-- follow from the trade set as action)
+--
+-- an inventory: ("player_inv", "npc_inv" or "block_inv")
+-- p_itemstack an itemstack; needs to be a minetest.registered_item[..];
+-- size/count is also checked
+--
+-- the inventory of a block on the map: ("block_inv", in addition to the ones above)
+-- p_pos a position in the world; determined by asking the player
+-- to punch the block
+-- p_inv_list_name name of the inventory list of the block
+--
+-- the player offered/gave the NPC an item: ("player_offered_item"):
+-- p_value an itemstack; needs to be a minetest.registered_item[..];
+-- size/count is checked for some subtypes
+-- p_match_stack_size does the NPC expect exactly one stack size - or is
+-- more or less etc. also ok?
+-- p_item_group are items of this group instead of the exact item name
+-- also acceptable?
+-- p_item_desc the description of the itemstack (set by another quest NPC
+-- so that the player can distinguish it from other itemstacks
+-- with the same item name; see action "npc_gives")
+-- p_item_quest_id Special ID to make sure that it is really the *right*
+-- item and not just something the player faked with an
+-- engraving table or something similar
+--
+-- a function ("function"): requires npc_master to create and edit
+-- p_value the lua code to execute and evaulate
+--
+-- depends on another option:
+-- p_value name of the other option of this dialog that is considered
+-- p_fulfilled shall option p_value be true or false?
+--
+-- the type of the entity of the NPC:
+-- p_value name of the entity (i.e. npc_talk:talking_npc)
+
+
+-- some helper lists for creating the formspecs and evaulating
+-- the player's answers:
+
+-- general direction of what a prerequirement may be about
+local check_what = {
+ "- please select -",
+ "an internal state (i.e. of a quest)", -- 2
+ "the value of a property of the NPC (for generic NPC)",
+ "something that has to be calculated or evaluated (=call a function)",
+ "a block somewhere", -- 3
+ "a trade", -- 4
+ "the inventory of the player", -- 5
+ "the inventory of the NPC", -- 6
+ "the inventory of a block somewhere", -- 7
+ "an item the player offered/gave to the NPC", -- 8
+ "execute Lua code (requires npc_master priv)", -- 7 -> 9
+ "The preconditions of another dialog option are fulfilled/not fulfilled.", -- 9 -> 11
+ "nothing - always true (useful for generic dialogs)",
+ "nothing - always false (useful for temporally deactivating an option)",
+ "the type of the entity of the NPC",
+}
+
+-- how to store these as p_type in the precondition:
+local values_what = {"", "state", "property", "evaluate", "block", "trade",
+ "player_inv", "npc_inv", "block_inv",
+ "player_offered_item",
+ -- "function" requires npc_master priv:
+ "function",
+ -- depends on the preconditions of another option
+ "other",
+ "true", "false",
+ "entity_type"}
+
+-- options for "a trade"
+local check_trade = {
+ "- please select -",
+ "The NPC has the item(s) he wants to sell in his inventory.", -- 2
+ "The player has the item(s) needed to pay the price.", -- 3
+ "The NPC ran out of stock.", -- 4
+ "The player cannot afford the price.", -- 5
+}
+
+-- how to store these as p_value:
+local values_trade = {"", "npc_can_sell", "player_can_buy", "npc_is_out_of_stock", "player_has_not_enough"}
+
+-- options for "the inventory of " (either player or NPC; perhaps blocks later on)
+local check_inv = {
+ "- please select -",
+ "The inventory contains the following item:",
+ "The inventory *does not* contain the following item:",
+ "There is room for the following item in the inventory:",
+ "The inventory is empty.",
+}
+
+-- how to store these as p_value (the actual itemstack gets stored as p_itemstack):
+local values_inv = {"", "inv_contains", "inv_does_not_contain", "has_room_for", "inv_is_empty"}
+
+local check_block = {
+ "- please select -",
+ "The block is as it is now.",
+ "There shall be air instead of this block.",
+ "The block is diffrent from how it is now.",
+ "I can't punch it. The block is as the block *above* the one I punched.",
+}
+
+-- how to store these as p_value (the actual node data gets stored as p_node, p_param2 and p_pos):
+-- Note: "node_is_like" occours twice because it is used to cover blocks that
+-- cannot be punched as well as normal blocks.
+local values_block = {"", "node_is_like", "node_is_air", "node_is_diffrent_from", "node_is_like"}
+
+-- comparison operators for variables
+local check_operator = {
+ "- please select -", -- 1
+ "== (is equal)", -- 2
+ "~= (is not equal)", -- 3
+ ">= (is greater or equal)", -- 4
+ "> (is greater)", -- 5
+ "<= (is smaller or equal)", -- 6
+ "< (is smaller)", -- 7
+ "not (logically invert)", -- 8
+ "is_set (has a value)", -- 9
+ "is_unset (has no value)", -- 10
+ "more than x seconds ago", -- 11
+ "less than x seconds ago", -- 12
+ "has completed quest step", -- 13
+ "quest step *not* completed", -- 14
+}
+
+-- how to store these as p_value (the actual variable is stored in p_variable, and the value in p_cmp_value):
+local values_operator = {"", "==", "~=", ">=", ">", "<=", "<", "not", "is_set", "is_unset",
+ "more_than_x_seconds_ago","less_than_x_seconds_ago",
+ "quest_step_done", "quest_step_not_done"}
+
+
+
+-- get the list of variables the player has read access to
+yl_speak_up.get_sorted_player_var_list_read_access = function(pname)
+ local var_list = {}
+ -- copy the values that are server-specific
+ for i, v in ipairs(yl_speak_up.custom_server_functions.precondition_descriptions) do
+ table.insert(var_list, v)
+ end
+ -- get the list of variables the player can read
+ local tmp = yl_speak_up.get_quest_variables_with_read_access(pname)
+ -- sort that list (the dropdown formspec element returns just an index)
+ table.sort(tmp)
+ for i, v in ipairs(tmp) do
+ table.insert(var_list, v)
+ end
+ return var_list
+end
+
+
+-- returns a human-readable text as description of the precondition
+-- (as shown in the edit options dialog and in the edit precondition formspec)
+yl_speak_up.show_precondition = function(p, pname)
+ if(not(p.p_type) or p.p_type == "") then
+ return "(nothing): Always true."
+ elseif(p.p_type == "item") then
+ return "item: The player has \""..tostring(p.p_value).."\" in his inventory."
+ elseif(p.p_type == "quest") then
+ return "quest: Always false."
+ elseif(p.p_type == "auto") then
+ return "auto: Always true."
+ elseif(p.p_type == "true") then
+ return "true: Always true."
+ elseif(p.p_type == "false") then
+ return "false: Always false."
+ elseif(p.p_type == "function") then
+ return "function: evaluate "..tostring(p.p_value)
+ elseif(p.p_type == "state") then
+ local var_name = "VALUE_OF[ - ? - ]"
+ if(p.p_variable) then
+ var_name = "VALUE_OF[ "..tostring(
+ yl_speak_up.strip_pname_from_var(p.p_variable, pname)).." ]"
+ end
+ if(not(p.p_operator)) then
+ return "Error: Operator not defined."
+ elseif(p.p_operator == "not") then
+ return "not( "..var_name.." )"
+ elseif(p.p_operator == "is_set") then
+ return var_name.." ~= nil (is_set)"
+ elseif(p.p_operator == "is_unset") then
+ return var_name.." == nil (is_unset)"
+ elseif(p.p_operator == "more_than_x_seconds_ago") then
+ return var_name.." was set to current time "..
+ "*more* than "..tostring(p.p_var_cmp_value).." seconds ago"
+ elseif(p.p_operator == "less_than_x_seconds_ago") then
+ return var_name.." was set to current time "..
+ "*less* than "..tostring(p.p_var_cmp_value).." seconds ago"
+ elseif(p.p_operator == "quest_step_done") then
+ return var_name.." shows: player completed quest step \""..
+ tostring(p.p_var_cmp_value).."\" successfully"
+ elseif(p.p_operator == "quest_step_not_done") then
+ return var_name.." shows: player has not yet completed quest step \""..
+ tostring(p.p_var_cmp_value).."\""
+ end
+ if(p.p_var_cmp_value == "") then
+ return var_name.." "..tostring(p.p_operator).." \"\""
+ end
+ return var_name.." "..tostring(p.p_operator).." "..
+ tostring(p.p_var_cmp_value)
+ elseif(p.p_type == "property") then
+ local i = math.max(1,table.indexof(values_operator, p.p_operator))
+ return tostring(p.p_value)..
+ " "..tostring(check_operator[i])..
+ " "..tostring(p.p_var_cmp_value)
+ elseif(p.p_type == "evaluate") then
+ local str = ""
+ for i = 1, 9 do
+ str = str..tostring(p["p_param"..tostring(i)])
+ if(i < 9) then
+ str = str..","
+ end
+ end
+ local i_op = math.max(1,table.indexof(values_operator, p.p_operator))
+ return "FUNCTION["..tostring(p.p_value).."]"..
+ "("..str..") "..tostring(check_operator[i_op])..
+ " "..tostring(p.p_var_cmp_value)
+ elseif(p.p_type == "block") then
+ if(not(p.p_pos) or type(p.p_pos) ~= "table"
+ or not(p.p_pos.x) or not(p.p_pos.y) or not(p.p_pos.z)) then
+ return "ERROR: p.p_pos is "..minetest.serialize(p.p_pos)
+ elseif(p.p_value == "node_is_like") then
+ return "The block at "..minetest.pos_to_string(p.p_pos).." is \""..
+ tostring(p.p_node).."\" with param2: "..tostring(p.p_param2).."."
+ elseif(p.p_value == "node_is_air") then
+ return "There is no block at "..minetest.pos_to_string(p.p_pos).."."
+ elseif(p.p_value == "node_is_diffrent_from") then
+ return "There is another block than \""..tostring(p.p_node).."\" at "..
+ minetest.pos_to_string(p.p_pos)..", or it is at least "..
+ "rotated diffrently (param2 is not "..tostring(p.p_param2)..")."
+ end
+ elseif(p.p_type == "trade") then
+ local nr = table.indexof(values_trade, p.p_value)
+ if(nr and check_trade[ nr ]) then
+ return check_trade[ nr ]
+ end
+ elseif(p.p_type == "player_inv" or p.p_type == "npc_inv" or p.p_type == "block_inv") then
+ local who = "The player"
+ local what = "\""..tostring(p.p_itemstack).."\" in his inventory."
+ if(p.p_type == "npc_inv") then
+ who = "The NPC"
+ elseif(p.p_type == "block_inv") then
+ if(not(p.p_pos) or type(p.p_pos) ~= "table"
+ or not(p.p_pos.x) or not(p.p_pos.y) or not(p.p_pos.z)) then
+ return "ERROR: p.p_pos is "..minetest.serialize(p.p_pos)
+ end
+ who = "The block at "..minetest.pos_to_string(p.p_pos)
+ what = "\""..tostring(p.p_itemstack).."\" in inventory list \""..
+ tostring(p.p_inv_list_name).."\"."
+ end
+ if(p.p_value == "inv_contains") then
+ return who.." has "..what
+ elseif(p.p_value == "inv_does_not_contain") then
+ return who.." does not have "..what
+ elseif(p.p_value == "has_room_for") then
+ return who.." has room for "..what
+ elseif(p.p_value == "inv_is_empty") then
+ if(p.p_type == "block_inv") then
+ return who.." has an empty inventory list \""..
+ tostring(p.p_inv_list_name).."\"."
+ end
+ return who.." has an empty inventory."
+ end
+ elseif(p.p_type == "player_offered_item") then
+ local item = tostring(p.p_value:split(" ")[1])
+ local amount = tostring(p.p_value:split(" ")[2])
+ local match = "any amount"
+ if(p.p_match_stack_size == "any") then
+ match = "any amount"
+ elseif(p.p_match_stack_size == "exactly") then
+ match = "exactly "..tostring(amount)
+ elseif(p.p_match_stack_size == "less"
+ or p.p_match_stack_size == "more") then
+ match = p.p_match_stack_size.." than "..tostring(amount)
+ elseif(p.p_match_stack_size == "another") then
+ match = "another amount than " ..tostring(amount)
+ end
+ if(p.p_item_group and p.p_item_group ~= "") then
+ return "The player offered "..tostring(match).." item(s) of the group \""..
+ tostring(item).."\"."
+ elseif((p.p_item_quest_id and p.p_item_quest_id ~= "")
+ or (p.p_item_desc and p.p_item_desc ~= "")) then
+ return "The player offered "..tostring(match).." of \""..
+ tostring(p.p_item_desc or "- default description -")..
+ "\" (\""..tostring(item or "- ? -").."\") "..
+ "with ID \""..tostring(p.p_item_quest_id or "- no special ID -").."\"."
+ else
+ return "The player offered "..tostring(match).." of \""..tostring(item).."\"."
+ end
+ elseif(p.p_type == "other") then
+ local fulfilled = "fulfilled"
+ if(not(p.p_fulfilled) or p.p_fulfilled ~= "true") then
+ fulfilled = "*not* fulfilled"
+ end
+ return "The preconditions for dialog option \""..tostring(p.p_value).."\" are "..
+ fulfilled.."."
+ elseif(p.p_type == "entity_tpye") then
+ return "the type of the entity of the NPC is: \""..tostring(p.p_value).."\"."
+ end
+ -- fallback
+ return tostring(p.p_value)
+end
+
+
+-- these are only wrapper functions for those in fs_edit_general.lua
+
+yl_speak_up.input_edit_preconditions = function(player, formname, fields)
+ return yl_speak_up.handle_input_fs_edit_option_related(player, formname, fields,
+ "p_", "o_prerequisites", yl_speak_up.max_prerequirements,
+ "pre(C)ondition", "tmp_prereq",
+ "Please punch the block you want to check in your precondition!",
+ values_what, values_operator, values_block, values_trade, values_inv,
+ check_what, check_operator, check_block, check_trade, check_inv,
+ -- player variables with read access
+ yl_speak_up.get_sorted_player_var_list_read_access,
+ "edit_preconditions"
+ )
+end
+
+
+yl_speak_up.get_fs_edit_preconditions = function(player, table_click_result)
+ return yl_speak_up.build_fs_edit_option_related(player, table_click_result,
+ "p_", "o_prerequisites", yl_speak_up.max_prerequirements,
+ "pre(C)ondition", "tmp_prereq",
+ "What do you want to check in this precondition?",
+ values_what, values_operator, values_block, values_trade, values_inv,
+ check_what, check_operator, check_block, check_trade, check_inv,
+ -- player variables with read access
+ yl_speak_up.get_sorted_player_var_list_read_access,
+ -- show one precondition element
+ yl_speak_up.show_precondition,
+ "table_of_preconditions",
+ "The following expression shall be true:", "Operator:",
+ "Value to compare with (in some cases parameter):",
+ "The following shall be true about the block:"
+ )
+end
+
+
+yl_speak_up.register_fs("edit_preconditions",
+ yl_speak_up.input_edit_preconditions,
+ yl_speak_up.get_fs_edit_preconditions,
+ -- no special formspec required:
+ nil
+)
diff --git a/fs/fs_fashion.lua b/fs/fs_fashion.lua
new file mode 100644
index 0000000..bed667b
--- /dev/null
+++ b/fs/fs_fashion.lua
@@ -0,0 +1,266 @@
+-- ###
+-- Fashion
+-- ###
+
+-- normal skins for NPC - without wielded items or capes etc.
+yl_speak_up.input_fashion = function(player, formname, fields)
+ if formname ~= "yl_speak_up:fashion" then
+ return
+ end
+
+ local pname = player:get_player_name()
+ local n_id = yl_speak_up.speak_to[pname].n_id
+
+ -- is the player editing this npc? if not: abort
+ if(not(yl_speak_up.edit_mode[pname])
+ or (yl_speak_up.edit_mode[pname] ~= n_id)) then
+ return ""
+ end
+
+ -- catch ESC as well
+ if(not(fields) or (fields.quit or fields.button_cancel or fields.button_exit)) then
+ yl_speak_up.show_fs(player, "talk", {n_id = yl_speak_up.speak_to[pname].n_id,
+ d_id = yl_speak_up.speak_to[pname].d_id})
+ return
+ end
+
+ -- which texture from the textures list are we talking about?
+ -- this depends on the model!
+ local mesh = yl_speak_up.get_mesh(pname)
+ local texture_index = yl_speak_up.mesh_data[mesh].texture_index
+ if(not(texture_index)) then
+ texture_index = 1
+ end
+
+ -- show extra formspec with wielded item configuration and cape setup
+ if(fields.button_config_wielded_items
+ and yl_speak_up.mesh_data[mesh].can_show_wielded_items) then
+ yl_speak_up.show_fs(player, "fashion_extended")
+ return
+ end
+
+ -- which skins are available? this depends on mob_type
+ local mob_type = yl_speak_up.get_mob_type(pname)
+ local skins = yl_speak_up.mob_skins[mob_type]
+
+
+ local textures = yl_speak_up.speak_to[pname].textures
+
+ -- fallback if something went wrong (i.e. unkown NPC)
+ local skin = (textures[texture_index] or "")
+ if(not(skins)) then
+ skins = {skin}
+ end
+ local skin_index = table.indexof(skins, skin)
+ if(skin_index == -1) then
+ skin_index = 1
+ end
+ local new_skin = skin
+ -- switch back to the stored old skin
+ if(fields.button_old_skin) then
+ local old_texture = yl_speak_up.speak_to[pname].old_texture
+ if(old_texture) then
+ new_skin = old_texture
+ end
+ -- store the new skin
+ elseif(fields.button_store_new_skin) then
+ yl_speak_up.speak_to[pname].old_texture = skin
+ -- show previous skin
+ elseif(fields.button_prev_skin) then
+ if(skin_index > 1) then
+ new_skin = skins[skin_index - 1]
+ else
+ new_skin = skins[#skins]
+ end
+ -- show next skin
+ elseif(fields.button_next_skin) then
+ if(skin_index < #skins) then
+ new_skin = skins[skin_index + 1]
+ else
+ new_skin = skins[1]
+ end
+ -- set directly via list
+ elseif(fields.set_skin_normal) then
+ local new_index = table.indexof(skins, fields.set_skin_normal)
+ if(new_index ~= -1) then
+ new_skin = skins[new_index]
+ end
+ end
+
+ -- if there is a new skin to consider
+ if(textures[texture_index] ~= new_skin) then
+ textures[texture_index] = new_skin
+ yl_speak_up.mesh_update_textures(pname, textures)
+ end
+ if(fields.set_animation
+ and yl_speak_up.mesh_data[mesh]
+ and yl_speak_up.mesh_data[mesh].animation
+ and yl_speak_up.mesh_data[mesh].animation[fields.set_animation]
+ and yl_speak_up.speak_to[pname]
+ and yl_speak_up.speak_to[pname].obj) then
+ local obj = yl_speak_up.speak_to[pname].obj
+ obj:set_animation(yl_speak_up.mesh_data[mesh].animation[fields.set_animation])
+ -- store the animation so that it can be restored on reload
+ local entity = obj:get_luaentity()
+ if(entity) then
+ entity.yl_speak_up.animation = yl_speak_up.mesh_data[mesh].animation[fields.set_animation]
+ end
+ end
+ if(fields.button_old_skin or fields.button_store_new_skin) then
+ yl_speak_up.speak_to[pname].old_texture = nil
+ yl_speak_up.show_fs(player, "talk", {n_id = yl_speak_up.speak_to[pname].n_id,
+ d_id = yl_speak_up.speak_to[pname].d_id})
+ return
+ end
+ yl_speak_up.show_fs(player, "fashion")
+end
+
+
+-- this only sets the *skin*, depending on the mesh of the NPC;
+-- capes and wielded items are supported by an extended formspec for those
+-- NPC that can handle them
+yl_speak_up.get_fs_fashion = function(pname)
+ -- which texture from the textures list are we talking about?
+ -- this depends on the model!
+ local mesh = yl_speak_up.get_mesh(pname)
+ if(not(mesh) or not(yl_speak_up.mesh_data[mesh])) then
+ return "size[9,2]label[0,0;Error: Mesh data missing.]"
+ end
+ local texture_index = yl_speak_up.mesh_data[mesh].texture_index
+ if(not(texture_index)) then
+ texture_index = 1
+ end
+
+ -- which skins are available? this depends on mob_type
+ local mob_type = yl_speak_up.get_mob_type(pname)
+ local skins = yl_speak_up.mob_skins[mob_type]
+
+ local textures = yl_speak_up.speak_to[pname].textures
+ local skin = ""
+ if(textures and textures[texture_index]) then
+ skin = (textures[texture_index] or "")
+ end
+ -- store the old texture so that we can go back to it
+ local old_texture = yl_speak_up.speak_to[pname].old_texture
+ if(not(old_texture)) then
+ yl_speak_up.speak_to[pname].old_texture = skin
+ old_texture = skin
+ end
+ -- fallback if something went wrong
+ if(not(skins)) then
+ skins = {old_texture}
+ end
+
+ local button_cancel = "Cancel"
+ -- is this player editing this particular NPC? then rename the button
+ if( yl_speak_up.edit_mode[pname]
+ and yl_speak_up.edit_mode[pname] == yl_speak_up.speak_to[pname].n_id) then
+ button_cancel = "Back"
+ end
+
+ local skin_list = table.concat(skins, ",")
+ local skin_index = table.indexof(skins, skin)
+ if(skin_index == -1) then
+ skin_index = ""
+ end
+
+ local tmp_textures = textures
+ if(texture_index ~= 1) then
+ tmp_textures = yl_speak_up.textures2skin(textures)
+ end
+ local preview = yl_speak_up.skin_preview_3d(mesh, tmp_textures, "2,1;6,12", "8,1;6,12")
+-- local preview = yl_speak_up.mesh_data[mesh].skin_preview(skin)
+
+ local formspec = {
+ "container[0.5,4.0]",
+ "dropdown[0.75,14.1;16.25,1.5;set_skin_normal;",
+ skin_list or "",
+ ";",
+ tostring(skin_index) or "",
+ "]",
+ "label[0.75,13.6;The name of this skin is:]",
+
+ "button[0.75,0.75;1.2,12;button_prev_skin;<]",
+ "button[15.75,0.75;1.2,12;button_next_skin;>]",
+ "tooltip[button_prev_skin;Select previous skin in list.]",
+ "tooltip[button_next_skin;Select next skin in list.]",
+ "tooltip[set_skin_normal;Select a skin from the list.]",
+ preview,
+ -- we add a special button for setting the skin in the player answer/reply window
+ }
+ if(yl_speak_up.mesh_data[mesh].animation
+ and yl_speak_up.speak_to[pname]
+ and yl_speak_up.speak_to[pname].obj) then
+ local anim_list = {}
+ for k, v in pairs(yl_speak_up.mesh_data[mesh].animation) do
+ table.insert(anim_list, k)
+ end
+ table.sort(anim_list)
+ -- which animation is the NPC currently running?
+ local obj = yl_speak_up.speak_to[pname].obj
+ local curr_anim = obj:get_animation(pname)
+ local anim = ""
+ -- does the current animation match any stored one?
+ for k, v in pairs(yl_speak_up.mesh_data[mesh].animation) do
+ if(v.x and v.y and curr_anim and curr_anim.x and curr_anim.y
+ and v.x == curr_anim.x and v.y == curr_anim.y) then
+ anim = k
+ end
+ end
+ local anim_index = table.indexof(anim_list, anim)
+ if(anim_index == -1) then
+ anim_index = "1"
+ end
+ table.insert(formspec, "label[0.75,16.4;Do the following animation:]")
+ table.insert(formspec, "dropdown[0.75,16.9;16.25,1.5;set_animation;")
+ table.insert(formspec, table.concat(anim_list, ','))
+ table.insert(formspec, ";")
+ table.insert(formspec, tostring(anim_index) or "")
+ table.insert(formspec, "]")
+ end
+ table.insert(formspec, "container_end[]")
+
+ local left_window = table.concat(formspec, "")
+ formspec = {}
+ local h = -0.8
+ local button_text = "This shall be your new skin. Wear it proudly!"
+ if(skin == old_texture) then
+ button_text = "This is your old skin. It is fine. Keep it!"
+ end
+ h = yl_speak_up.add_edit_button_fs_talkdialog(formspec, h,
+ "button_store_new_skin",
+ "The NPC will wear the currently selected skin.",
+ button_text,
+ true, nil, nil, nil)
+ h = yl_speak_up.add_edit_button_fs_talkdialog(formspec, h,
+ "button_old_skin",
+ "The NPC will wear the skin he wore before you started changing it.",
+ "On a second throught - Keep your old skin. It was fine.",
+ (skin ~= old_texture), nil, nil, nil)
+ h = yl_speak_up.add_edit_button_fs_talkdialog(formspec, h,
+ "button_config_wielded_items",
+ "What shall the NPC wield, and which cape shall he wear?",
+ "I'll tell you what you shall wield.",
+ (yl_speak_up.mesh_data[mesh].can_show_wielded_items),
+ "You don't know how to show wielded items. Thus, we can't configure them.",
+ nil, nil)
+ return yl_speak_up.show_fs_decorated(pname, true, h,
+ "",
+ left_window,
+ table.concat(formspec, ""),
+ nil,
+ h)
+end
+
+yl_speak_up.get_fs_fashion_wrapper = function(player, param)
+ local pname = player:get_player_name()
+ return yl_speak_up.get_fs_fashion(pname)
+end
+
+
+yl_speak_up.register_fs("fashion",
+ yl_speak_up.input_fashion,
+ yl_speak_up.get_fs_fashion_wrapper,
+ -- no special formspec required:
+ nil
+)
diff --git a/fs/fs_fashion_extended.lua b/fs/fs_fashion_extended.lua
new file mode 100644
index 0000000..f512f16
--- /dev/null
+++ b/fs/fs_fashion_extended.lua
@@ -0,0 +1,205 @@
+
+-- inspired/derived from the wieldview mod in 3darmor
+yl_speak_up.get_wield_texture = function(item)
+ if(not(item) or not(minetest.registered_items[ item ])) then
+ return "3d_armor_trans.png"
+ end
+ local def = minetest.registered_items[ item ]
+ if(def.inventory_image ~= "") then
+ return def.inventory_image
+ elseif(def.tiles and type(def.tiles[1]) == "string" and def.tiles[1] ~= "") then
+ return minetest.inventorycube(def.tiles[1])
+ end
+ return "3d_armor_trans.png"
+end
+
+
+yl_speak_up.fashion_wield_give_items_back = function(player, pname)
+ -- move the item back to the player's inventory (if possible)
+ local trade_inv = minetest.get_inventory({type="detached", name="yl_speak_up_player_"..pname})
+ local player_inv = player:get_inventory()
+ local left_stack = trade_inv:get_stack("wield", 1)
+ local right_stack = trade_inv:get_stack("wield", 2)
+ if(left_stack and not(left_stack:is_empty())
+ and player_inv:add_item("main", left_stack)) then
+ trade_inv:set_stack("wield", 1, "")
+ end
+ if(right_stack and not(right_stack:is_empty())
+ and player_inv:add_item("main", right_stack)) then
+ trade_inv:set_stack("wield", 2, "")
+ end
+end
+
+
+-- set what the NPC shall wield and which cape to wear
+yl_speak_up.input_fashion_extended = function(player, formname, fields)
+ if formname ~= "yl_speak_up:fashion_extended" then
+ return
+ end
+
+ local pname = player:get_player_name()
+ local textures = yl_speak_up.speak_to[pname].textures
+
+ local n_id = yl_speak_up.speak_to[pname].n_id
+
+ -- is the player editing this npc? if not: abort
+ if(not(yl_speak_up.edit_mode[pname])
+ or (yl_speak_up.edit_mode[pname] ~= n_id)) then
+ return ""
+ end
+
+ -- catch ESC as well
+ if(not(fields)
+ or (fields.quit or fields.button_cancel or fields.button_exit or fields.button_save)) then
+ yl_speak_up.fashion_wield_give_items_back(player, pname)
+ yl_speak_up.show_fs(player, "fashion")
+ return
+
+ elseif(fields.button_wield_left
+ or fields.button_wield_right) then
+ local trade_inv = minetest.get_inventory({type="detached", name="yl_speak_up_player_"..pname})
+ local player_inv = player:get_inventory()
+ local left_stack = trade_inv:get_stack("wield", 1)
+ local right_stack = trade_inv:get_stack("wield", 2)
+ if(left_stack and left_stack:get_name() and fields.button_wield_left) then
+ textures[4] = yl_speak_up.get_wield_texture(left_stack:get_name())
+ yl_speak_up.log_change(pname, n_id,
+ "(fashion) sword changed to "..tostring(fields.set_sword)..".")
+ end
+ if(right_stack and right_stack:get_name() and fields.button_wield_right) then
+ textures[3] = yl_speak_up.get_wield_texture(right_stack:get_name())
+ yl_speak_up.log_change(pname, n_id,
+ "(fashion) shield changed to "..tostring(fields.set_shield)..".")
+ end
+ yl_speak_up.fashion_wield_give_items_back(player, pname)
+
+ -- only change cape if there really is a diffrent one selected
+ elseif(fields.set_cape and fields.set_cape ~= textures[1]) then
+
+ local mob_type = yl_speak_up.get_mob_type(pname)
+ local capes = yl_speak_up.mob_capes[mob_type] or {}
+ -- only set the cape if it is part of the list of allowed capes
+ if(table.indexof(capes, fields.set_cape) ~= -1) then
+ textures[1] = fields.set_cape
+ yl_speak_up.log_change(pname, n_id,
+ "(fashion) cape changed to "..tostring(fields.set_cape)..".")
+ end
+ end
+
+ if(fields.button_wield_left or fields.button_wield_right or fields.set_cape or fields.button_sve) then
+ yl_speak_up.fashion_wield_give_items_back(player, pname)
+ yl_speak_up.mesh_update_textures(pname, textures)
+ yl_speak_up.show_fs(player, "fashion_extended")
+ return
+ end
+ yl_speak_up.show_fs(player, "fashion")
+end
+
+
+yl_speak_up.get_fs_fashion_extended = function(pname)
+ -- which texture from the textures list are we talking about?
+ -- this depends on the model!
+ local mesh = yl_speak_up.get_mesh(pname)
+ local texture_index = yl_speak_up.mesh_data[mesh].texture_index
+ if(not(texture_index)) then
+ texture_index = 1
+ end
+
+ local textures = yl_speak_up.speak_to[pname].textures
+ local skin = (textures[texture_index] or "")
+
+ -- which skins are available? this depends on mob_type
+ local mob_type = yl_speak_up.get_mob_type(pname)
+ local skins = yl_speak_up.mob_skins[mob_type] or {skin}
+ local capes = yl_speak_up.mob_capes[mob_type] or {}
+ local cape = "" -- TODO
+
+ -- is this player editing this particular NPC? then rename the button
+ if(not(yl_speak_up.edit_mode[pname])
+ or yl_speak_up.edit_mode[pname] ~= yl_speak_up.speak_to[pname].n_id) then
+ return "label[Error. Not in Edit mode!]"
+ end
+
+ -- make sure the cape can be unset again
+ if(#capes < 1 or capes[1] ~= "") then
+ table.insert(capes, 1, "")
+ end
+ local cape_list = table.concat(capes, ",")
+ local cape_index = table.indexof(capes, cape)
+ if(cape_index == -1) then
+ cape_index = ""
+ end
+
+ local tmp_textures = textures
+ if(texture_index ~= 1) then
+ tmp_textures = yl_speak_up.textures2skin(textures)
+ end
+ local preview = yl_speak_up.skin_preview_3d(mesh, tmp_textures, "4.7,0.5;5,10", nil)
+
+ local button_cancel = "Cancel"
+ -- is this player editing this particular NPC? then rename the button
+ if( yl_speak_up.edit_mode[pname]
+ and yl_speak_up.edit_mode[pname] == yl_speak_up.speak_to[pname].n_id) then
+ button_cancel = "Back"
+ end
+ local formspec = {
+ "size[13.4,15]",
+ "label[0.3,0.2;Skin: ",
+ minetest.formspec_escape(skin),
+ "]",
+ "label[4.6,0.65;",
+ yl_speak_up.speak_to[pname].n_id,
+ "]",
+ "label[6,0.65;",
+ (yl_speak_up.speak_to[pname].n_npc or "- nameless -"),
+ "]",
+ "dropdown[9.1,0.2;4,0.75;set_cape;",
+ cape_list, ";", cape_index, "]",
+ "label[0.3,4.2;Left:]",
+ "label[9.1,4.2;Right:]",
+ "field_close_on_enter[set_sword;false]",
+ "field_close_on_enter[set_shield;false]",
+ "image[9.1,1;4,2;",
+ textures[1] or "",
+ "]", -- Cape
+ "image[0.3,4.2;4,4;",
+ textures[4] or "",
+ "]", -- Sword
+ "image[9.1,4.2;4,4;",
+ textures[3] or "",
+ "]", --textures[3],"]", -- Shield
+ "tooltip[0.3,4.2;4,4;This is: ",
+ minetest.formspec_escape(textures[4]),
+ "]",
+ "tooltip[9.1,4.2;4,4;This is: ",
+ minetest.formspec_escape(textures[3]),
+ "]",
+ preview or "",
+ "button[0.3,8.4;3,0.75;button_cancel;"..button_cancel.."]",
+ "button[10.1,8.4;3,0.75;button_save;Save]",
+ "list[current_player;main;1.8,10;8,4;]",
+ -- set wielded items
+ "label[0.3,9.7;Wield\nleft:]",
+ "label[12.0,9.7;Wield\nright:]",
+ "list[detached:yl_speak_up_player_"..tostring(pname)..";wield;0.3,10.5;1,1;]",
+ "list[detached:yl_speak_up_player_"..tostring(pname)..";wield;12.0,10.5;1,1;1]",
+ "button[0.3,11.7;1,0.6;button_wield_left;Set]",
+ "button[12.0,11.7;1,0.6;button_wield_right;Set]",
+ "tooltip[button_wield_left;Set and store what your NPC shall wield in its left hand.]",
+ "tooltip[button_wield_right;Set and store what your NPC shall wield in its right hand.]",
+ }
+ return table.concat(formspec, "")
+end
+
+
+yl_speak_up.get_fs_fashion_extended_wrapper = function(player, param)
+ local pname = player:get_player_name()
+ return yl_speak_up.get_fs_fashion_extended(pname)
+end
+
+yl_speak_up.register_fs("fashion_extended",
+ yl_speak_up.input_fashion_extended,
+ yl_speak_up.get_fs_fashion_extended_wrapper,
+ -- no special formspec required:
+ nil
+)
diff --git a/fs/fs_get_list_of_usage_of_variable.lua b/fs/fs_get_list_of_usage_of_variable.lua
new file mode 100644
index 0000000..6118a6b
--- /dev/null
+++ b/fs/fs_get_list_of_usage_of_variable.lua
@@ -0,0 +1,93 @@
+
+-- this function is called in fs_edit_general.lua when creating preconditions/effects
+-- and from fs_manage_variables.lua when the player clicks on a button;
+-- input to this formspec is sent to the respective calling functions
+
+-- find out where this variable is used in NPCs
+yl_speak_up.fs_get_list_of_usage_of_variable = function(var_name, pname, check_preconditions,
+ back_button_name, back_button_text, is_internal_var)
+ -- TODO: check if the player really has read access to this variable
+ if(not(is_internal_var)) then
+ var_name = yl_speak_up.restore_complete_var_name(var_name, pname)
+ end
+ -- which NPC (might be several) is using this variable?
+ -- TODO: ..or if the player at least is owner of these NPC or has extended privs
+ local npc_list = yl_speak_up.get_variable_metadata(var_name, "used_by_npc")
+ -- list of all relevant preconditions, actions and effects
+ local res = {}
+ local count_read = 0
+ local count_changed = 0
+ for i, n_id in ipairs(npc_list) do
+ -- the NPC may not even be loaded
+ local dialog = yl_speak_up.load_dialog(n_id, false)
+ if(dialog and dialog.n_dialogs) then
+ for d_id, d in pairs(dialog.n_dialogs) do
+ if(d and d.d_options) then
+ for o_id, o in pairs(d.d_options) do
+ local p_text = ""
+ local r_text = ""
+ local sort_value = 0
+ if(o and o.o_prerequisites and check_preconditions) then
+ for p_id, p in pairs(o.o_prerequisites) do
+ if(p and p.p_type and p.p_type == "state"
+ and p.p_variable and p.p_variable == var_name) then
+ p_text = p_text..yl_speak_up.print_as_table_precon(p,pname)
+ sort_value = (p.p_var_cmp_value or 0)
+ count_read = count_read + 1
+ end
+ end
+ end
+ if(o and o.o_results) then
+ for r_id, r in pairs(o.o_results) do
+ if(r and r.r_type and r.r_type == "state"
+ and r.r_variable and r.r_variable == var_name) then
+ r_text = r_text..yl_speak_up.print_as_table_effect(r,pname)
+ -- values set in the results are more important than
+ -- those set in preconditions
+ sort_value = (r.r_var_cmp_value or 0)
+ count_changed = count_changed + 1
+ end
+ end
+ end
+ -- if preconditions or effects apply: show the action as well
+ if(o and o.actions and (p_text ~= "" or r_text ~= "")) then
+ for a_id, a in pairs(o.actions) do
+ -- no need to introduce an a_text; this will follow
+ -- directly after p_text, and p_text is finished
+ p_text = p_text..yl_speak_up.print_as_table_action(a, pname)
+ end
+ end
+ yl_speak_up.print_as_table_dialog(p_text, r_text, dialog,
+ n_id, d_id, o_id, res, o, sort_value)
+ end
+ end
+ end
+ end
+ end
+
+ local formspec = yl_speak_up.print_as_table_prepare_formspec(res, "table_of_variable_uses",
+ back_button_name, back_button_text)
+ table.insert(formspec,
+ "label[20.0,1.8;"..
+ minetest.formspec_escape("Variable \""..
+ minetest.colorize("#FFFF00", tostring(var_name or "- ? -"))..
+ "\" is used here:").."]")
+
+ if(count_read > 0 or count_changed > 0) then
+ table.insert(formspec,
+ "label[16.0,31.0;The variable is accessed in "..
+ minetest.colorize("#FFFF00", tostring(count_read).." pre(C)onditions")..
+ " and changed in "..
+ minetest.colorize("#55FF55", tostring(count_changed).." (Ef)fects")..
+ ".]")
+ elseif(not(is_internal_var)) then
+ table.insert(formspec,
+ "button[0.2,30.6;56.6,1.2;delete_unused_variable;"..
+ minetest.formspec_escape("Delete this unused variable \""..
+ tostring(var_name or "- ? -")).."\".]")
+ else
+ table.insert(formspec,
+ "label[16.0,31.0;This is an internal variable and cannot be deleted.]")
+ end
+ return table.concat(formspec, "\n")
+end
diff --git a/fs/fs_initial_config_in_edit_mode.lua b/fs/fs_initial_config_in_edit_mode.lua
new file mode 100644
index 0000000..1172bdf
--- /dev/null
+++ b/fs/fs_initial_config_in_edit_mode.lua
@@ -0,0 +1,202 @@
+
+-- in addition: set who can edit this npc;
+-- add buttons for fashion (skin editing) and properties;
+local old_input_fs_initial_config = yl_speak_up.input_fs_initial_config
+yl_speak_up.input_fs_initial_config = function(player, formname, fields)
+ local pname = player:get_player_name()
+ local n_id = yl_speak_up.speak_to[pname].n_id
+
+ if(fields.back_from_error_msg) then
+ -- no point in showing the formspec or error message again if we did so already
+ if(not(yl_speak_up.may_edit_npc(player, n_id))) then
+ return
+ end
+ -- show this formspec again
+ yl_speak_up.show_fs(player, "initial_config",
+ {n_id = n_id, d_id = yl_speak_up.speak_to[pname].d_id, false})
+ return
+ end
+
+ if(fields.button_export_dialog) then
+ yl_speak_up.show_fs(player, "export")
+ return
+ end
+
+ if(fields.edit_skin and fields.edit_skin ~= "") then
+ yl_speak_up.show_fs(player, "fashion")
+ return
+ end
+
+ if(fields.edit_properties and fields.edit_properties ~= "") then
+ yl_speak_up.show_fs(player, "properties")
+ return
+ end
+
+ if((not(fields.save_initial_config)
+ and not(fields.show_nametag)
+ and not(fields.list_may_edit)
+ and not(fields.add_may_edit)
+ and not(fields.delete_may_edit)
+ ) or (fields and fields.exit)) then
+ local dialog = yl_speak_up.speak_to[pname].dialog
+ -- unconfigured NPC
+ if(fields and fields.exit and not(dialog) or not(dialog.n_dialogs)) then
+ minetest.chat_send_player(pname, "Aborting initial configuration.")
+ return
+ end
+ -- is the player editing the npc? then leaving this config
+ -- dialog has to lead back to the talk dialog
+ if(yl_speak_up.edit_mode[pname] == n_id and n_id) then
+ yl_speak_up.show_fs(player, "talk",
+ {n_id = n_id, d_id = yl_speak_up.speak_to[pname].d_id})
+ end
+ -- else we can quit here
+ return
+ end
+
+
+
+ local error_msg = nil
+ local dialog = yl_speak_up.speak_to[pname].dialog
+ local done = false
+ if(not(yl_speak_up.may_edit_npc(player, n_id))) then
+ error_msg = "You are not allowed to edit this NPC."
+
+ -- want to change who may edit this npc?
+ -- delete a player from the list of those allowed to edit the NPC
+ elseif(fields.delete_may_edit and fields.delete_may_edit ~= ""
+ and fields.list_may_edit and fields.list_may_edit ~= "") then
+ if(pname ~= yl_speak_up.npc_owner[ n_id ]) then
+ error_msg = "Only the owner of the NPC\ncan change this."
+ elseif(not(dialog)) then
+ error_msg = "Please set a name for your NPC first!"
+ else
+ -- actually delete the player from the list
+ dialog.n_may_edit[ fields.list_may_edit ] = nil
+ -- show the next entry
+ yl_speak_up.speak_to[pname].tmp_index = math.max(1,
+ yl_speak_up.speak_to[pname].tmp_index-1)
+ end
+ done = true
+ -- add a player who may edit this NPC
+ elseif(fields.add_may_edit_button and fields.add_may_edit and fields.add_may_edit ~= "") then
+ if(pname ~= yl_speak_up.npc_owner[ n_id ]) then
+ error_msg = "Only the owner of the NPC\ncan change this."
+ -- Note: The owner can now add himself as well. This may be useful before transfering
+ -- ownership of the NPC to an inexperienced new user who might need help.
+-- elseif(fields.add_may_edit == pname) then
+-- error_msg = "You are already the owner of this NPC!\nNo need to add you extra here."
+ elseif(not(minetest.check_player_privs(fields.add_may_edit, {interact=true}))) then
+ error_msg = "Player \""..minetest.formspec_escape(fields.add_may_edit)..
+ "\" not found."
+ elseif(not(dialog)) then
+ error_msg = "Please set a name for the NPC first!"
+ else
+ if(not(dialog.n_may_edit)) then
+ dialog.n_may_edit = {}
+ end
+ dialog.n_may_edit[ fields.add_may_edit ] = true
+ -- jump to the index with this player so that the player sees that he has been added
+ local tmp_list = yl_speak_up.sort_keys(dialog.n_may_edit, true)
+ local index = table.indexof(tmp_list, fields.add_may_edit)
+ if(index and index > 0) then
+ -- "Add player:" is added before all other names, so +1
+ yl_speak_up.speak_to[pname].tmp_index = index + 1
+ end
+ end
+ done = true
+ -- selected a player name in the woy may edit this NPC dropdown?
+ elseif(fields.list_may_edit and fields.list_may_edit ~= "") then
+ local tmp_list = yl_speak_up.sort_keys(dialog.n_may_edit, true)
+ local index = table.indexof(tmp_list, fields.list_may_edit)
+ if(fields.list_may_edit == "Add player:") then
+ index = 0
+ end
+ if(index and index > -1) then
+ yl_speak_up.speak_to[pname].tmp_index = index + 1
+ end
+ done = true
+ end
+ if(error_msg) then
+ yl_speak_up.show_fs(player, "msg", { input_to = "yl_speak_up:initial_config",
+ formspec = "size[6,2]"..
+ "label[0.2,0.0;"..tostring(error_msg).."]"..
+ "button[2,1.5;1,0.9;back_from_error_msg;Back]"})
+ return
+ end
+
+ if( fields.add_may_edit and fields.add_may_edit ~= "") then
+ yl_speak_up.save_dialog(n_id, dialog)
+ yl_speak_up.log_change(pname, n_id,
+ "Added to \"may be edited by\": "..tostring(fields.add_may_edit))
+ elseif(fields.delete_may_edit and fields.delete_may_edit ~= ""
+ and fields.list_may_edit and fields.list_may_edit ~= "") then
+ yl_speak_up.save_dialog(n_id, dialog)
+ yl_speak_up.log_change(pname, n_id,
+ "Removed from \"may be edited by\": "..tostring(fields.list_may_edit))
+ elseif(not(done) or fields.save_initial_config) then
+ return old_input_fs_initial_config(player, formname, fields)
+ end
+ -- update display after editing may_edit_npc:
+ yl_speak_up.show_fs(player, "initial_config",
+ {n_id = n_id, d_id = yl_speak_up.speak_to[pname].d_id, false})
+end
+
+
+-- initialize the npc without having to use a staff;
+
+-- add option to show, add and delete other players who may edit this npc;
+-- add buttons for skin change and editing properties
+local old_get_fs_initial_config = yl_speak_up.get_fs_initial_config
+yl_speak_up.get_fs_initial_config = function(player, n_id, d_id, is_initial_config, add_formspec)
+ -- nothing to add if this is the initial configuration
+ if(is_initial_config) then
+ return old_get_fs_initial_config(player, n_id, d_id, is_initial_config, nil)
+ end
+
+ local pname = player:get_player_name()
+ local dialog = yl_speak_up.speak_to[pname].dialog
+ -- dialog.n_may_edit was a string for a short time in development
+ if(not(dialog.n_may_edit) or type(dialog.n_may_edit) ~= "table") then
+ dialog.n_may_edit = {}
+ end
+ local table_of_names = dialog.n_may_edit
+ local may_be_edited_by = {
+ -- these buttons and formspecs are provided by the editor:
+ "button[1.0,5.5;4,0.9;edit_skin;Edit Skin]",
+ "button[6.0,5.5;4,0.9;edit_properties;Edit Properties]",
+ -- who can edit this NPC?
+ "label[0.2,4.45;May be\nedited by:]",
+ -- offer a dropdown list and a text input field for player names for adding
+ yl_speak_up.create_dropdown_playerlist(player, pname,
+ table_of_names, yl_speak_up.speak_to[pname].tmp_index,
+ 2.2, 4.3, 0.0, 1.0, "list_may_edit", "player",
+ "Remove selected\nplayer from list",
+ "add_may_edit",
+ "Enter the name of the player whom you\n"..
+ "want to grant the right to edit your NPC.\n"..
+ "The player needs at least the npc_talk_owner priv\n"..
+ "in order to actually edit the NPC.\n"..
+ "Click on \"Add\" to add the new player.",
+ "delete_may_edit",
+ "If you click here, the player will no\n"..
+ "longer be able to edit your NPC."
+ )}
+ if(not(yl_speak_up.speak_to[pname].tmp_index) or yl_speak_up.speak_to[pname].tmp_index < 2) then
+ table.insert(may_be_edited_by, "button[9.8,4.3;1.0,1.0;add_may_edit_button;Add]")
+ table.insert(may_be_edited_by, "tooltip[add_may_edit_button;Click here to add the player "..
+ "listed to the left\nto those who can edit this NPC.]")
+ end
+ -- show the formspec to the player
+ return old_get_fs_initial_config(player, n_id, d_id, is_initial_config, may_be_edited_by)
+end
+
+
+yl_speak_up.register_fs("initial_config",
+ -- this function has been changed here:
+ yl_speak_up.input_fs_initial_config,
+ -- still handled by the wrapper:
+ yl_speak_up.get_fs_initial_config_wrapper,
+ -- no special formspec required:
+ nil
+)
diff --git a/fs/fs_manage_quest_steps.lua b/fs/fs_manage_quest_steps.lua
new file mode 100644
index 0000000..354dc8f
--- /dev/null
+++ b/fs/fs_manage_quest_steps.lua
@@ -0,0 +1,364 @@
+
+-- Imposing an order on the quest steps is...tricky as best as what will
+-- be more important to the players will be the order in which the
+-- quest steps have to be solved/done - and not an alphabetical order.
+-- But we need an order here for a dropdown menu to select each
+-- quest step even if it hasn't been assigned any place in the chain
+-- of quest steps yet. So - alphabetical order.
+yl_speak_up.get_sorted_quest_step_list = function(pname, q_id)
+ local quest_step_list = {}
+ if(not(pname) or not(yl_speak_up.speak_to[pname])) then
+ return {}
+ end
+ local q_id = yl_speak_up.speak_to[pname].q_id
+
+ if(q_id and yl_speak_up.quests[q_id] and yl_speak_up.quests[q_id].step_data) then
+ for step_id, v in pairs(yl_speak_up.quests[q_id].step_data) do
+ table.insert(quest_step_list, step_id)
+ end
+ end
+ table.sort(quest_step_list)
+ return quest_step_list
+end
+
+
+-- helper functions for yl_speak_up.input_fs_manage_quest_steps(..)
+-- returns the index of the new quest step
+yl_speak_up.input_fs_manage_quest_steps_add_new_entry = function(pname, entry_name)
+ local q_id = yl_speak_up.speak_to[pname].q_id
+ local res = yl_speak_up.quest_step_add_quest_step(pname, q_id, entry_name)
+ -- might make sense to show the error message somewhere
+ if(res ~= "OK") then
+ return res
+ end
+ -- the new entry will be somewhere in it
+ local quest_step_list = yl_speak_up.get_sorted_quest_step_list(pname)
+ return table.indexof(quest_step_list, entry_name)
+end
+
+
+-- helper functions for yl_speak_up.input_fs_manage_quest_steps(..)
+-- returns a text describing if deleting the quest worked
+yl_speak_up.input_fs_manage_quest_steps_del_old_entry = function(pname, entry_name)
+ local q_id = yl_speak_up.speak_to[pname].q_id
+ return yl_speak_up.quest_step_del_quest_step(pname, q_id, entry_name)
+end
+
+
+-- helper functions for yl_speak_up.input_fs_manage_quest_steps(..)
+-- implements all the functions that are specific to managing quest steps and not part of
+-- general item management
+yl_speak_up.input_fs_manage_quest_steps_check_fields = function(player, formname, fields, quest_step_name, list_of_entries)
+ local pname = player:get_player_name()
+ if(not(quest_step_name)) then
+ quest_step_name = ""
+ end
+--[[ TODO: implement some back button functionality?
+ if(fields and fields.show_variable) then
+ yl_speak_up.show_fs(player, "manage_variables", {var_name = quest_name})
+ return
+ end
+--]]
+ -- this function didn't have anything to do
+ return "NOTHING FOUND"
+end
+
+
+
+-- makes use of yl_speak_up.handle_input_fs_manage_general and is thus pretty short
+yl_speak_up.input_fs_manage_quest_steps = function(player, formname, fields)
+ local pname = player:get_player_name()
+
+ -- route diffrently when the task was adding a quest step
+ if(fields and fields.back
+ and pname
+ and yl_speak_up.speak_to[pname].d_id
+ and yl_speak_up.speak_to[pname].o_id) then
+ return yl_speak_up.show_fs(player, "edit_option_dialog", {
+ d_id = yl_speak_up.speak_to[pname].d_id,
+ o_id = yl_speak_up.speak_to[pname].o_id,
+ })
+ end
+ if(not(fields) or fields.manage_quests or fields.back) then
+ return yl_speak_up.show_fs(player, "manage_quests")
+ end
+ local res = yl_speak_up.player_is_working_on_quest(player)
+
+ -- show a particular quest step?
+ if(yl_speak_up.handle_input_routing_show_a_quest_step(player, formname, fields, "back_from_error_msg", res)) then
+ return
+ end
+
+ if(res.current_step) then
+ -- forward input from that formspec...
+ if((yl_speak_up.speak_to[res.pname].quest_step_mode == "embedded_select")
+ and (fields.add_from_available
+ or (fields.add_step and fields.add_quest_step))) then
+ return yl_speak_up.input_fs_add_quest_steps(player, "yl_speak_up:add_quest_steps", fields)
+ end
+ end
+
+ local modes = {"add_to_one_needed", "add_to_all_needed",
+ "insert_after_prev_step", "insert_before_next_step"}
+ for i, mode in ipairs(modes) do
+ if(fields[mode] and fields[mode] ~= "") then
+ -- let that function sort out what to do;
+ -- yl_speak_up.speak_to[pname].q_id and yl_speak_up.speak_to[pname].quest_step
+ -- ought to be set to the current quest and step by now
+ yl_speak_up.speak_to[pname].quest_step_mode = mode
+ yl_speak_up.show_fs(player, "add_quest_steps")
+ return
+ end
+ end
+
+ local quest_step_list = yl_speak_up.get_sorted_quest_step_list(pname)
+ local res = yl_speak_up.handle_input_fs_manage_general(player, formname, fields,
+ -- what_is_the_list_about, min_length, max_length, function_add_new_entry,
+ "quest step", 2, 70,
+ yl_speak_up.input_fs_manage_quest_steps_add_new_entry,
+ quest_step_list,
+ yl_speak_up.input_fs_manage_quest_steps_del_old_entry,
+ yl_speak_up.input_fs_manage_quest_steps_check_fields)
+ return true
+end
+
+
+yl_speak_up.get_fs_manage_quest_steps = function(player, param)
+ -- small helper function
+ local em = function(text)
+ return minetest.colorize("#9999FF", text)
+ end
+
+ local res = yl_speak_up.player_is_working_on_quest(player)
+ if(res.error_msg) then
+ return yl_speak_up.build_fs_quest_edit_error(res.error_msg, "back")
+ end
+ local step_data = res.step_data
+ local quest_step_list = yl_speak_up.get_sorted_quest_step_list(res.pname)
+ if(param and param ~= "") then
+ local index = table.indexof(quest_step_list, param)
+ yl_speak_up.speak_to[res.pname].tmp_index_general = index + 1
+ end
+ local idx = yl_speak_up.speak_to[res.pname].tmp_index_general
+ if(idx and idx > 1) then
+ yl_speak_up.speak_to[res.pname].quest_step = quest_step_list[idx - 1]
+ end
+
+ local formspec = {}
+ table.insert(formspec, "size[30,12]"..
+ "container[6,0;18.5,12]"..
+ "label[0.2,1.2;A quest step is a single thing a player may do in a quest - "..
+ "like talking to an NPC.\n"..
+ "Usually not all quest steps can be done/solved at all times.]")
+ local selected = yl_speak_up.build_fs_manage_general(player, param,
+ formspec, quest_step_list,
+ "Create quest step",
+ "Create a new quest step for this quest.",
+ "quest step",
+ "Enter the name of the new quest step you want to create.\n"..
+ "This is an internal text shown only to yourself.\n"..
+ "Players cannot see the names of quest steps.",
+ "If you click here, the selected quest step will be deleted.\n"..
+ "This will only be possible if it's not used anywhere.",
+ "1.0")
+ table.insert(formspec, "container_end[]")
+
+ if(not(selected) or selected == "" or not(step_data) or not(step_data[selected])) then
+ formspec = {} -- we start a new one
+ -- insert a nicely formated list of quest steps
+ yl_speak_up.speak_to[res.pname].quest_step_mode = "embedded_select"
+ table.insert(formspec, yl_speak_up.get_fs_add_quest_steps(player, nil))
+ table.insert(formspec, "container_end[]")
+ local lists = yl_speak_up.quest_step_get_start_end_unconnected_lists(step_data)
+ yl_speak_up.get_sub_fs_show_list_in_box(formspec,
+ "Start steps:", "select_from_start_steps", lists.start_steps,
+ "0.1", "2.7", "5.6", "4.3", 0, nil, "#AAFFAA",
+ "The quest begins with this (or one of these) steps.\n"..
+ "You need at least one start step.",
+ nil)
+ yl_speak_up.get_sub_fs_show_list_in_box(formspec,
+ "Unconnected steps:", "select_from_unconnected_steps", lists.unconnected_steps,
+ "0.1", "7.0", "5.6", "4.3", 0, nil, "#FFAAAA",
+ "These steps are not used yet. They are not required\n"..
+ "by any other step and do not require steps either.\n"..
+ "Please decide what to do with them!",
+ nil)
+ yl_speak_up.get_sub_fs_show_list_in_box(formspec,
+ "Quest ends with steps:", "select_from_end_steps", lists.end_steps,
+ "24", "2.7", "5.6", "8.5", "0.1", nil, "#AAFFAA",
+ "This quest ends with these steps. They are not required\n"..
+ "by any other steps and have no successor.\n"..
+ "Your quest needs at least one end step.",
+ nil)
+ return table.concat(formspec, "")
+ end
+ -- find out the next quest step
+ local required_for = yl_speak_up.quest_step_required_for(step_data, selected)
+
+ -- middle (this quest step)
+ table.insert(formspec, "container[6,2.7;12,10.8]"..
+ "label[0.2,0.5;This quest step is named:]"..
+ "box[0.7,0.7;17,0.7;#000000]"..
+ "label[0.8,1.1;")
+ table.insert(formspec, minetest.colorize("#AAFFAA", minetest.formspec_escape(selected)))
+ table.insert(formspec, "]")
+ table.insert(formspec, "container_end[]")
+ -- show the locations where this quest step is set
+ local c = 0
+ for where_id, d in pairs(step_data[selected].where or {}) do
+ c = c + 1
+ end
+ if(c > 1) then
+ table.insert(formspec, "label[6.2,4.5;This quest step can be set in diffrent ways (")
+ table.insert(formspec, tostring(c))
+ table.insert(formspec, " ways) by:]")
+ table.insert(formspec, "scrollbaroptions[max=")
+ table.insert(formspec, tostring((c-1.5) * 30)) -- 10 units for default 0.1 scroll factor
+ table.insert(formspec, ";thumbsize=15")
+ table.insert(formspec, "]")
+ table.insert(formspec, "scrollbar[23.2,4.7;0.3,5;vertical;scrollbar_where;0.1]")
+ elseif(c == 1) then
+ table.insert(formspec, "label[6.2,4.5;This quest step can be set by:]")
+ else
+ table.insert(formspec, "label[6.2,4.5;This quest step cannot be set yet.]")
+ end
+ table.insert(formspec, "scroll_container[6.2,4.7;17,5;scrollbar_where;vertical;]")
+ c = 0
+ for where_id, d in pairs(step_data[selected].where or {}) do
+ table.insert(formspec, "container[0,")
+ table.insert(formspec, tostring(0.2 + c * 3.0))
+ table.insert(formspec, ";17,4]")
+ table.insert(formspec, "box[0,0;17,2.7;")
+ if(c%2 == 0) then
+ table.insert(formspec, "#000000]")
+ else
+ table.insert(formspec, "#222222]")
+ end
+ -- describe where in the dialog of the NPC or location this quest step shall be set
+ local s = "This quest step can be set by "
+ if(c > 0) then
+ s = "..alternatively, this quest step can be set by "
+ end
+ yl_speak_up.quest_step_show_where_set(res.pname, formspec, s, d.n_id, d.d_id, d.o_id,
+ "#444488", c + 1) --"#513F23", c + 1) --"#a37e45", c + 1)
+ table.insert(formspec, "container_end[]")
+ c = c + 1
+ end
+ table.insert(formspec, "scroll_container_end[]")
+
+ -- left side (previous quest step)
+ table.insert(formspec, "container[0,0;5.8,13.5]"..
+ "label[0.2,2.0;"..em("Required previous").."]"..
+ "label[0.2,2.4;quest step(s):]"..
+ "style[insert_before_next_step,insert_after_prev_step,"..
+ "add_to_one_needed,add_to_all_needed;bgcolor=blue;textcolor=yellow]"..
+ "button[0.1,0.1;5.6,0.8;show_step_list;Show all quest steps]")
+ yl_speak_up.get_sub_fs_show_list_in_box(formspec,
+ em("One of").." these quest steps:", "one_step_required",
+ step_data[selected].one_step_required,
+ "0.1", "2.7", "5.6", "4.3", 0, nil, "#AAFFAA",
+ "At least "..em("one of").." these previous quest steps listed here has to be\n"..
+ "achieved by the player who is trying to solve this quest.\n"..
+ "Only then can the player try to achieve this current quest\n"..
+ "step that you are editing here.\n"..
+ "If this is empty, then it's usually the/a first step of the quest.\n"..
+ "If there is one entry, then that entry is the previous quest step.\n"..
+ "If there are multiple entries, then players have alternate ways\n"..
+ "to achieve this current quest step here.",
+ "button[4.6,0.0;0.94,0.7;add_to_one_needed;Edit]"..
+ "tooltip[add_to_one_needed;Add or remove a quest step to this list.]")
+ yl_speak_up.get_sub_fs_show_list_in_box(formspec,
+ em("All of").." these quest steps:", "all_steps_required",
+ step_data[selected].all_steps_required,
+ "0.1", "7.0", "5.6", "4.3", 0, nil, "#AAFFAA",
+ "Sometimes there may not be a particular order in which\n"..
+ "quest steps ought to be solved. Imagine for example getting\n"..
+ "indigrents for a cake and delivering them to an NPC.\n"..
+ "The quest steps listed here can be done in "..em("any order")..".\n"..
+ "They have "..em("all").." to be achieved first in order for this current\n"..
+ "quest step to become available.\n"..
+ "Usually this list is empty.",
+ "button[4.6,0.0;0.94,0.7;add_to_all_needed;Edit]"..
+ "tooltip[add_to_all_needed;Add or remove a quest step to this list.]")
+ if( #step_data[selected].one_step_required > 0
+ or #step_data[selected].all_steps_required > 0) then
+ if( #step_data[selected].one_step_required
+ + #step_data[selected].all_steps_required > 1) then
+ table.insert(formspec, "style[show_prev_step;bgcolor=red]")
+ end
+ table.insert(formspec, "button[5.6,1.7;0.6,7.0;show_prev_step;<]"..
+ "tooltip[show_prev_step;Show the previous step according to "..
+ em("your quest logic")..".\n"..
+ "The button turns "..minetest.colorize("#FF0000", "red")..
+ " if there is more than one option. In such\na case "..
+ "the alphabeticly first previous quest step is choosen.\n"..
+ "The other "..em("< Prev").." button shows the previous step "..
+ "in\n"..em("alphabetical order")..".]")
+ end
+ -- add buttons for inserting steps between this and the prev/next one
+ if(#step_data[selected].one_step_required <= 1) then
+ table.insert(formspec,
+ "button[5.6,8.7;0.6,2.6;insert_after_prev_step;+]"..
+ "tooltip[insert_after_prev_step;"..
+ "Insert a new quest step between the "..em("previous step")..
+ " (shown\n"..
+ "to the left) and the "..em("current step")..
+ " (shown in the middle).\n"..
+ "Note: This only makes sense if there's just one or no\n"..
+ " previous step.]")
+ end
+ table.insert(formspec, "container_end[]")
+
+
+ -- right side (next quest step)
+ table.insert(formspec, "container[23.8,0;5.8,13.5]"..
+ "label[0.6,2.0;Achieving this quest step]"..
+ "label[0.6,2.4;"..em("helps").." the quester to:]"..
+ "button[0.4,0.1;5.6,0.8;manage_quests;Manage quests]")
+ yl_speak_up.get_sub_fs_show_list_in_box(formspec,
+ "get these quest step(s) "..em("next")..":", "next_steps_show",
+ required_for,
+ -- the label needs to be moved slightly to the right to make room for the > button
+ "0.4", "2.7", "5.6", "8.5", "0.1", nil, "#AAFFAA",
+ "Once the current quest step has been achieved by the\n"..
+ "player, the player can strive to achieve these next\n"..
+ "quest step(s).\n"..
+ "If this is empty, then it's either the/a last step (quest\n"..
+ "solved!) - or the quest is not properly set up.",
+ nil)
+
+ if(required_for and #required_for > 0) then
+ if(#required_for > 1) then
+ table.insert(formspec, "style[show_next_step;bgcolor=red]")
+ end
+ table.insert(formspec, "button[0,1.7;0.6,7.0;show_next_step;>]"..
+ "tooltip[show_next_step;Show the next step according to "..
+ em("your quest logic")..".\n"..
+ "The button turns "..minetest.colorize("#FF0000", "red")..
+ " if there is more than one option. In such\na case "..
+ "the alphabeticly first next quest step is choosen.\n"..
+ "The other "..em("Next >").." button shows the next step "..
+ "in\n"..em("alphabetical order")..".]")
+ end
+ if(#required_for <= 1) then
+ table.insert(formspec,
+ "button[0,8.7;0.6,2.6;insert_before_next_step;+]"..
+ "tooltip[insert_before_next_step;"..
+ "Insert a new quest step between "..em("current step")..
+ " (shown\nin the middle) "..
+ "and the "..em("next step").." (shown to the right).\n"..
+ "Note: This only makes sense if there's just one or no\n"..
+ " next step.]")
+ end
+ table.insert(formspec, "container_end[]")
+
+ return table.concat(formspec, "")
+end
+
+
+yl_speak_up.register_fs("manage_quest_steps",
+ yl_speak_up.input_fs_manage_quest_steps,
+ yl_speak_up.get_fs_manage_quest_steps,
+ -- no special formspec required:
+ nil
+)
diff --git a/fs/fs_manage_quests.lua b/fs/fs_manage_quests.lua
new file mode 100644
index 0000000..fac3b87
--- /dev/null
+++ b/fs/fs_manage_quests.lua
@@ -0,0 +1,248 @@
+-- helper functions for yl_speak_up.input_fs_manage_quests(..)
+-- returns the index of the new quest
+yl_speak_up.fun_input_fs_manage_quests_add_new_entry = function(pname, entry_name)
+ local res = yl_speak_up.add_quest(pname, entry_name,
+ "Name of your quest",
+ "Enter a longer description here for describing the quest "..
+ "to players who search for one.",
+ "Enter a short description here describing what the quest is about.",
+ "Room for comments/notes")
+ -- might make sense to show the error message somewhere
+ if(res ~= "OK") then
+ return res
+ end
+ -- the new entry will be somewhere in it
+ local quest_list = yl_speak_up.get_sorted_quest_list(pname)
+ return table.indexof(quest_list, entry_name)
+end
+
+-- helper functions for yl_speak_up.input_fs_manage_quests(..)
+-- returns a text describing if deleting the quest worked
+yl_speak_up.fun_input_fs_manage_quests_del_old_entry = function(pname, entry_name)
+ -- get q_id from entry_name
+ local q_id = yl_speak_up.get_quest_id_by_var_name(entry_name, pname)
+ return yl_speak_up.del_quest(q_id, pname)
+end
+
+-- helper functions for yl_speak_up.input_fs_manage_quests(..)
+-- implements all the functions that are specific to managing quests and not part of
+-- general item management
+yl_speak_up.fun_input_fs_manage_quests_check_fields = function(player, formname, fields, quest_name, list_of_entries)
+ local pname = player:get_player_name()
+ if(not(quest_name)) then
+ quest_name = ""
+ end
+ if(fields and fields.show_variable) then
+ yl_speak_up.show_fs(player, "manage_variables", {var_name = quest_name})
+ return
+ end
+ -- this function didn't have anything to do
+ return "NOTHING FOUND"
+end
+
+
+-- makes use of yl_speak_up.handle_input_fs_manage_general and is thus pretty short
+yl_speak_up.input_fs_manage_quests = function(player, formname, fields)
+ local pname = player:get_player_name()
+ if(fields and fields.manage_quest_steps and fields.manage_quest_steps ~= "") then
+ -- the quest we're working at is stored in yl_speak_up.speak_to[pname].q_id
+ yl_speak_up.show_fs(player, "manage_quest_steps")
+ return
+ end
+
+ -- show and edit NPCs that may contribute
+ if( fields and fields.edit_npcs) then
+ return yl_speak_up.show_fs(player, "add_quest_steps", "manage_quest_npcs")
+ elseif(fields and fields.edit_locations) then
+ return yl_speak_up.show_fs(player, "add_quest_steps", "manage_quest_locations")
+ end
+
+ -- show a particular quest step from the start/unconnected/end list?
+ local res = yl_speak_up.player_is_working_on_quest(player)
+ if(yl_speak_up.speak_to[pname]
+ and yl_speak_up.speak_to[pname].q_id
+ and yl_speak_up.handle_input_routing_show_a_quest_step(player, formname, fields, "back", res)) then
+ return
+ end
+
+ local quest_list = yl_speak_up.get_sorted_quest_list(pname)
+ local res = yl_speak_up.handle_input_fs_manage_general(player, formname, fields,
+ -- what_is_the_list_about, min_length, max_length, function_add_new_entry,
+ "quest", 2, 80,
+ yl_speak_up.fun_input_fs_manage_quests_add_new_entry,
+ quest_list,
+ yl_speak_up.fun_input_fs_manage_quests_del_old_entry,
+ yl_speak_up.fun_input_fs_manage_quests_check_fields)
+ return true
+end
+
+
+yl_speak_up.get_fs_manage_quests = function(player, param)
+ local pname = player:get_player_name()
+ local quest_list = yl_speak_up.get_sorted_quest_list(pname)
+ local formspec = {}
+ if(param and param ~= "") then
+ local index = table.indexof(quest_list, param)
+ yl_speak_up.speak_to[pname].tmp_index_general = index + 1
+ end
+ table.insert(formspec, "size[30,12]"..
+ "container[6,0;18.5,12]"..
+ "label[0.2,1.2;A quest is a linear sequence of quest steps. Quests can "..
+ "depend on and influence other quests.\n"..
+ "Progress for each player is stored in a variable. The name of "..
+ "that variable cannot be changed after creation.]")
+-- if(true) then return formspec[1] end -- TODO: temporally disabled for YL
+ local selected = yl_speak_up.build_fs_manage_general(player, param,
+ formspec, quest_list,
+ "Create quest",
+ "Create a new varialbe with the name\n"..
+ "you entered in the field to the left.",
+ "quest",
+ "Enter the name of the new quest you want to create.\n"..
+ "You can't change this name afterwards. But you *can*\n"..
+ "add and change a human readable description later on.",
+ "If you click here, the selected quest will be deleted.\n"..
+ "This will only be possible if it's not used anywhere.")
+ if(not(selected) or selected == "") then
+ table.insert(formspec, "container_end[]")
+ return table.concat(formspec, "")
+ end
+ local var_name = yl_speak_up.restore_complete_var_name(selected, pname)
+ local quest = {}
+ for q_id, data in pairs(yl_speak_up.quests) do
+ if(data and data.var_name and data.var_name == var_name) then
+ quest = data
+ end
+ end
+
+ -- which quest is the player working on? that's important for showing quest steps
+ if(not(yl_speak_up.speak_to[pname])) then
+ yl_speak_up.speak_to[pname] = {}
+ end
+ yl_speak_up.speak_to[pname].q_id = quest.id
+
+ local quest_state_selected = table.indexof({"created","testing","open","official"}, quest.state)
+ if(quest_state_selected == -1) then
+ quest_state_selected = 1
+ end
+ -- index 1 is "Add variable:"
+
+ table.insert(formspec, "button[12,2.15;4.5,0.6;show_variable;Show and edit this variable]")
+
+ table.insert(formspec, "scroll_container[0,3;18,8;scr0;vertical;1]")
+ table.insert(formspec, "button[12,0.15;4.5,0.6;manage_quest_steps;Manage quest steps]")
+
+ table.insert(formspec, "label[0.5,0.5;Quest ID:]")
+ table.insert(formspec, "label[3.5,0.5;"..minetest.formspec_escape(quest.id or "- ? -").."]")
+ table.insert(formspec, "label[0.5,1.1;State:]")
+ table.insert(formspec, "dropdown[3.5,0.8;4.0,0.5;quest_state;created,testing,open,official;"..tostring(quest_state_selected).."]")
+ table.insert(formspec, "label[0.5,1.7;Creator/Owner:]")
+ table.insert(formspec, "field[3.5,1.4;4.0,0.5;quest_owner;;"..minetest.formspec_escape(quest.owner or "- ? -").."]")
+ table.insert(formspec, "label[0.5,2.3;Name:]")
+ table.insert(formspec, "field[3.5,2.0;13.0,0.5;quest_name;;"..minetest.formspec_escape(quest.name or "- ? -").."]")
+ table.insert(formspec, "label[0.5,2.9;Short Description:]")
+ table.insert(formspec, "field[3.5,2.6;13.0,0.5;quest_short_desc;;"..minetest.formspec_escape(quest.short_desc or "- ? -").."]")
+ table.insert(formspec, "label[0.5,3.5;Full Description:]")
+ table.insert(formspec, "textarea[3.5,3.2;13.0,1.5;quest_desc;;"..minetest.formspec_escape(quest.description or "- ? -").."]")
+ table.insert(formspec, "label[0.5,5.1;Internal comment:]")
+ table.insert(formspec, "textarea[3.5,4.8;13.0,1.5;quest_comment;;"..minetest.formspec_escape(quest.comment or "- ? -").."]")
+
+ table.insert(formspec, "button[3.5,6.5;4.0,0.8;save_changes;TODO Save changes]")
+ table.insert(formspec, "scroll_container_end[]")
+ table.insert(formspec, "container_end[]")
+
+ -- TODO: make the content of the fields and textareas more readable (more contrast)
+ -- TODO: actually process and store changed entries
+--[[
+ -- TODO: entries that are not yet shown:
+ quest.var_name = var_name -- name of the variable where progress is stored for each player
+ quest.subquests = {} -- list of other quest_ids that contribute to this quest
+ -- -> determined from quests.npcs and quests.locations
+ quest.is_subquest_of = {} -- list of quest_ids this quest contributes to
+ -- -> determined from quests.npcs and quests.locations
+ quest.rewards = {} -- list of rewards (item stacks) for this ques
+ quest.testers = {} -- list of player names that can test the quest
+ -- -> during the created/testing phase: any player for which
+ -- quest.var_name is set to a value
+ quest.solved_by = {} -- list of names of players that solved the quest at least once
+--]]
+
+ -- left side: quest steps
+ -- quest.step_data = {}
+ -- table containing information about a quest step (=key)
+ -- this may also be information about WHERE a quest step shall take place
+ table.insert(formspec, "button[0.1,0.1;5.6,0.8;show_step_list;Show all quest steps]")
+ local lists = yl_speak_up.quest_step_get_start_end_unconnected_lists(quest.step_data or {})
+ yl_speak_up.get_sub_fs_show_list_in_box(formspec,
+ "Start steps:", "select_from_start_steps", lists.start_steps,
+ "0.1", "1.0", "5.6", "3.5", 0, nil, "#AAFFAA",
+ "The quest begins with this (or one of these) steps.\n"..
+ "You need at least one start step.",
+ nil)
+ yl_speak_up.get_sub_fs_show_list_in_box(formspec,
+ "Unconnected steps:", "select_from_unconnected_steps", lists.unconnected_steps,
+ "0.1", "4.5", "5.6", "3.5", 0, nil, "#FFAAAA",
+ "These steps are not used yet. They are not required\n"..
+ "by any other step and do not require steps either.\n"..
+ "Please decide what to do with them!",
+ nil)
+ yl_speak_up.get_sub_fs_show_list_in_box(formspec,
+ "Quest ends with steps:", "select_from_end_steps", lists.end_steps,
+ "0.1", "8.0", "5.6", "3.5", 0, nil, "#AAFFAA",
+ "This quest ends with these steps. They are not required\n"..
+ "by any other steps and have no successor.\n"..
+ "Your quest needs at least one end step.",
+ nil)
+
+ -- right side:
+ -- All these values could in theory either be derived from quest.var_name
+ -- and/or from quest.step_data.where.
+ -- These lists here are needed regardless of that because they may be used
+ -- to add NPC/locations/quest items that are *not yet* used but are planned
+ -- to be used for this quest eventually.
+ table.insert(formspec, "style[edit_npcs,edit_locations,edit_items;bgcolor=blue;textcolor=yellow]")
+ -- quest.npcs = {}
+ -- list of NPC that *may* contribute to this quest (only IDs without leading n_)
+ -- turn that list into a more readable list of names
+ local npc_names = {}
+ for i, id in ipairs(quest.npcs or {}) do
+ local d = yl_speak_up.npc_list[id] or {}
+ table.insert(npc_names, "n_"..tostring(id).." "..(d.name or "- unknown -"))
+ end
+ yl_speak_up.get_sub_fs_show_list_in_box(formspec,
+ "NPC that (may) participate:", "select_from_npcs", npc_names,
+ "24", "1.0", "5.6", "3.5", 0, nil, "#AAAAFF",
+ "This is a list of NPC that may be relevant for this quest.\n"..
+ "Add an NPC to this list and then edit the NPC.\n"..
+ "Now you can set quest steps from this quest in the NPC's options.",
+ "button[4.6,0.0;0.94,0.7;edit_npcs;Edit]")
+ -- quest.locations = {}
+ -- list of locations that *may* contribute to this quest
+ yl_speak_up.get_sub_fs_show_list_in_box(formspec,
+ "Locations:", "select_from_locations", quest.locations or {},
+ "24", "4.5", "5.6", "3.5", 0, nil, "#AAAAFF",
+ "This is a list of locations that may be relevant for this quest.\n"..
+ "It works the same way as for the NPC above.",
+ "button[4.6,0.0;0.94,0.7;edit_locations;Edit]")
+ -- quest.items = {}
+ -- data of quest items that *may* be created and/or accepted in this quest
+ yl_speak_up.get_sub_fs_show_list_in_box(formspec,
+ "Quest items:", "select_from_quest_items", quest.items or {},
+ "24", "8.0", "5.6", "3.5", 0, nil, "#FFFFFF",
+ "This is a list of quest items.\n"..
+ "Add quest items here in order to use them more easily in your NPC.",
+ "button[4.6,0.0;0.94,0.7;edit_items;Edit]")
+
+
+ -- store the quest ID so that we know what we're working at
+ yl_speak_up.speak_to[pname].q_id = quest.id
+ return table.concat(formspec, "")
+end
+
+
+yl_speak_up.register_fs("manage_quests",
+ yl_speak_up.input_fs_manage_quests,
+ yl_speak_up.get_fs_manage_quests,
+ -- no special formspec required:
+ nil
+)
diff --git a/fs/fs_manage_variables.lua b/fs/fs_manage_variables.lua
new file mode 100644
index 0000000..794ba90
--- /dev/null
+++ b/fs/fs_manage_variables.lua
@@ -0,0 +1,483 @@
+
+-- helper functions for yl_speak_up.input_fs_manage_variables(..)
+-- returns the index of the new variable
+yl_speak_up.fun_input_fs_manage_variables_add_new_entry = function(pname, entry_name)
+ local res = yl_speak_up.add_quest_variable(pname, entry_name)
+ if(not(res)) then
+ return -1
+ end
+ local var_list = yl_speak_up.get_quest_variables(pname, true)
+ -- make names of own variables shorter
+ yl_speak_up.strip_pname_from_varlist(var_list, pname)
+ table.sort(var_list)
+ return table.indexof(var_list, entry_name)
+end
+
+-- helper functions for yl_speak_up.input_fs_manage_variables(..)
+-- returns a text describing if deleting the variable worked
+yl_speak_up.fun_input_fs_manage_variables_del_old_entry = function(pname, entry_name)
+ -- delete (empty) variable
+ return yl_speak_up.del_quest_variable(pname, entry_name, nil)
+end
+
+-- helper functions for yl_speak_up.input_fs_manage_variables(..)
+-- implements all the functions that are specific to managing variables and not part of
+-- general item management
+yl_speak_up.fun_input_fs_manage_variables_check_fields = function(player, formname, fields, var_name, list_of_entries)
+ local pname = player:get_player_name()
+ if(not(var_name)) then
+ var_name = ""
+ end
+ local var_name_with_prefix = yl_speak_up.restore_complete_var_name(var_name, pname)
+
+ -- show all stored values for a variable in a table
+ if(fields and fields.show_stored_values_for_var and var_name) then
+ yl_speak_up.show_fs(player, "msg", {
+ input_to = "yl_speak_up:manage_variables",
+ formspec = yl_speak_up.fs_show_all_var_values(player, pname, var_name)
+ })
+ return
+ -- show details about a quest (if it is a quest variable)
+ elseif(fields and fields.show_quest) then
+ yl_speak_up.show_fs(player, "manage_quests", var_name)
+ return
+ -- show where this variable is used
+ elseif(fields and fields.show_var_usage and fields.show_var_usage ~= "") then
+ yl_speak_up.show_fs(player, "msg", {
+ input_to = "yl_speak_up:manage_variables",
+ formspec = yl_speak_up.fs_get_list_of_usage_of_variable(
+ fields.list_of_entries, pname, true,
+ "back_from_msg",
+ "Back to manage variables",
+ -- not an internal variable
+ false)
+ })
+ return
+ -- enable, disable and list variables in debug mode
+ elseif(fields and fields.enable_debug_mode and var_name) then
+ yl_speak_up.set_variable_metadata(var_name, pname, "debug", pname, true)
+ yl_speak_up.show_fs(player, "msg", {
+ input_to = "yl_speak_up:manage_variables",
+ formspec = "size[10,2]"..
+ "label[0.2,0.0;Activating debug mode for variable \""..
+ minetest.colorize("#FFFF00",
+ minetest.formspec_escape(tostring(var_name)))..
+ "\".\nYou will now receive a chat message whenever the "..
+ "variable changes.]"..
+ "button[1.5,1.5;2,0.9;back_from_msg;Back]"})
+ return
+ elseif(fields and fields.disable_debug_mode and var_name) then
+ yl_speak_up.set_variable_metadata(var_name, pname, "debug", pname, nil)
+ yl_speak_up.show_fs(player, "msg", {
+ input_to = "yl_speak_up:manage_variables",
+ formspec = "size[10,2]"..
+ "label[0.2,0.0;Deactivating debug mode for variable \""..
+ minetest.colorize("#FFFF00",
+ minetest.formspec_escape(tostring(var_name)))..
+ "\".\nYou will no longer receive a chat message whenever the "..
+ "variable changes.]"..
+ "button[1.5,1.5;2,0.9;back_from_msg;Back]"})
+ return
+ elseif(fields and fields.list_debug_mode) then
+ yl_speak_up.show_fs(player, "msg", {
+ input_to = "yl_speak_up:manage_variables",
+ formspec = "size[10,6]"..
+ "label[0.2,0.0;You are currently receiving debug information for the "..
+ "following variables:]"..
+ "tablecolumns[text]"..
+ "table[0.8,0.8;8.8,4.0;list_of_variables_in_debug_mode;"..
+ -- the table entries will already be formspec_escaped
+ table.concat(yl_speak_up.get_list_of_debugged_variables(pname), ",").."]"..
+ "button[1.5,5.5;2,0.9;back_from_msg;Back]"})
+ return
+
+ -- a player name was given; the value for that player shall be shown
+ elseif(fields and fields.show_stored_value_for_player and var_name
+ and fields.stored_value_for_player and fields.stored_value_for_player ~= "") then
+ yl_speak_up.show_fs(player, "manage_variables", {var_name = var_name,
+ for_player = fields.stored_value_for_player})
+ return
+ -- change the value for a player (possibly to nil)
+ elseif(fields and fields.store_new_value_for_player and var_name
+ and fields.stored_value_for_player and fields.stored_value_for_player ~= "") then
+ local old_value = yl_speak_up.get_quest_variable_value(
+ fields.stored_value_for_player, var_name_with_prefix)
+ yl_speak_up.set_quest_variable_value(fields.stored_value_for_player, var_name_with_prefix,
+ fields.current_value_for_player)
+ local new_value = yl_speak_up.get_quest_variable_value(
+ fields.stored_value_for_player, var_name_with_prefix)
+ local success_msg = minetest.colorize("#00FF00", "Successfully set variable")
+ if(new_value ~= fields.current_value_for_player) then
+ success_msg = minetest.colorize("#FF0000", "FAILED TO set variable")
+ end
+ yl_speak_up.show_fs(player, "msg", {
+ input_to = "yl_speak_up:manage_variables",
+ formspec = "size[10,2.5]"..
+ "label[0.2,0.0;"..success_msg.." \""..
+ minetest.colorize("#FFFF00",
+ minetest.formspec_escape(tostring(var_name)))..
+ "\"\nfor player "..
+ minetest.formspec_escape(fields.stored_value_for_player)..
+ "\n(old value: "..
+ minetest.colorize("#AAAAAA", old_value)..
+ ")\nto new value "..
+ minetest.colorize("#FFFF00", fields.current_value_for_player)..".]"..
+ "button[1.5,2.0;2,0.9;back_from_msg;Back]"})
+ return
+ -- remove the value for a player (set to nil)
+ elseif(fields and fields.unset_value_for_player and var_name
+ and fields.stored_value_for_player and fields.stored_value_for_player ~= "") then
+ local old_value = yl_speak_up.get_quest_variable_value(
+ fields.stored_value_for_player, var_name_with_prefix)
+ yl_speak_up.set_quest_variable_value(fields.stored_value_for_player, var_name_with_prefix, nil)
+ local new_value = yl_speak_up.get_quest_variable_value(
+ fields.stored_value_for_player, var_name_with_prefix)
+ local success_msg = minetest.colorize("#00FF00", "Unset variable")
+ if(new_value) then
+ success_msg = minetest.colorize("#FF0000", "FAILED TO unset variable")
+ end
+ yl_speak_up.show_fs(player, "msg", {
+ input_to = "yl_speak_up:manage_variables",
+ formspec = "size[10,2]"..
+ "label[0.2,0.0;"..success_msg.." \""..
+ minetest.colorize("#FFFF00",
+ minetest.formspec_escape(tostring(var_name)))..
+ "\"\nfor player "..
+ minetest.formspec_escape(fields.stored_value_for_player)..
+ "\n(old value: "..
+ minetest.colorize("#AAAAAA", old_value)..
+ ").]"..
+ "button[1.5,1.5;2,0.9;back_from_msg;Back]"})
+ return
+ -- revoke read or write access to a variable
+ elseif(fields
+ and ((fields.revoke_player_var_read_access and fields.revoke_player_var_read_access ~= "")
+ or (fields.revoke_player_var_write_access and fields.revoke_player_var_write_access ~= ""))
+ and var_name) then
+ local right = "read"
+ if(fields.revoke_player_var_write_access and fields.revoke_player_var_write_access ~= "") then
+ right = "write"
+ end
+ -- which player are we talking about?
+ local selected = yl_speak_up.speak_to[pname]["tmp_index_var_"..right.."_access"]
+ local pl_with_access = yl_speak_up.get_access_list_for_var(var_name, pname, right.."_access")
+ local tmp_list = {}
+ for k, v in pairs(pl_with_access) do
+ table.insert(tmp_list, k)
+ end
+ table.sort(tmp_list)
+ local grant_to = ""
+ if(selected > 1) then
+ grant_to = tmp_list[ selected-1 ]
+ end
+ local error_msg = ""
+ local pl_with_access = yl_speak_up.get_access_list_for_var(var_name, pname, right.."_access")
+ if(not(grant_to) or grant_to == "") then
+ error_msg = "For which player do you want to revoke "..right.." access?"
+ elseif(pname ~= yl_speak_up.npc_owner[ n_id ]
+ and not(minetest.check_player_privs(pname, {npc_talk_master=true}))) then
+ error_msg = "Only the owner of the NPC or players with\n"..
+ "the npc_talk_master priv can change this."
+ elseif(not(pl_with_access[ grant_to ])) then
+ error_msg = minetest.formspec_escape(grant_to).." doesn't have "..right..
+ " access\nto this variable. Nothing changed."
+ -- try to revoke access
+ elseif(not(yl_speak_up.manage_access_to_quest_variable(var_name, pname, grant_to,
+ right.."_access", nil))) then
+ error_msg = "An internal error occoured."
+ else
+ -- not really an error message here...rather a success message
+ error_msg = "Revoked "..right.." access to variable\n\""..
+ minetest.formspec_escape(var_name)..
+ "\"\nfor player "..minetest.formspec_escape(grant_to)..".\n"..
+ "Note: This will *not* affect existing preconditions/effects!"
+ end
+ yl_speak_up.show_fs(player, "msg", {
+ input_to = "yl_speak_up:manage_variables",
+ formspec = "size[6,2]"..
+ "label[0.2,0.0;"..error_msg.."]"..
+ "button[1.5,1.5;2,0.9;back_from_msg;Back]"})
+ return
+
+ -- grant read or write access to a variable
+ elseif(fields
+ and ((fields.grant_player_var_read_access and fields.grant_player_var_read_access ~= "")
+ or (fields.grant_player_var_write_access and fields.grant_player_var_write_access ~= ""))
+ and var_name) then
+ local right = "read"
+ if(fields.grant_player_var_write_access and fields.grant_player_var_write_access ~= "") then
+ right = "write"
+ end
+ local grant_to = fields[ "grant_player_var_"..right.."_access"]
+ local error_msg = ""
+ local pl_with_access = yl_speak_up.get_access_list_for_var(var_name, pname, right.."_access")
+ if(pname ~= yl_speak_up.npc_owner[ n_id ]
+ and not(minetest.check_player_privs(pname, {npc_talk_master=true}))) then
+ error_msg = "Only the owner of the NPC or players with\n"..
+ "the npc_talk_master priv can change this."
+ elseif(grant_to == pname) then
+ error_msg = "You already have "..right.." access to this variable."
+ elseif(pl_with_access[ grant_to ]) then
+ error_msg = minetest.formspec_escape(grant_to).." already has "..right..
+ " access\nto this variable."
+ elseif(not(minetest.check_player_privs(grant_to, {interact=true}))) then
+ error_msg = "Player \""..minetest.formspec_escape(grant_to).."\" not found."
+ -- try to grant access
+ elseif(not(yl_speak_up.manage_access_to_quest_variable(var_name, pname, grant_to,
+ right.."_access", true))) then
+ error_msg = "An internal error occoured."
+ else
+ -- not really an error message here...rather a success message
+ error_msg = "Granted "..right.." access to variable\n\""..
+ minetest.formspec_escape(var_name)..
+ "\"\nto player "..minetest.formspec_escape(grant_to).."."
+ end
+ yl_speak_up.show_fs(player, "msg", {
+ input_to = "yl_speak_up:manage_variables",
+ formspec = "size[6,2]"..
+ "label[0.2,0.0;"..error_msg.."]"..
+ "button[1.5,1.5;2,0.9;back_from_msg;Back]"})
+ return
+
+ -- the player clicked on a name in the list of players with read or write access
+ elseif(fields
+ and ((fields.list_var_read_access and fields.list_var_read_access ~= "")
+ or (fields.list_var_write_access and fields.list_var_write_access ~= ""))
+ and var_name) then
+ local right = "read"
+ if(fields.list_var_write_access and fields.list_var_write_access ~= "") then
+ right = "write"
+ end
+ local selected = fields[ "list_var_"..right.."_access"]
+ local pl_with_access = yl_speak_up.get_access_list_for_var(var_name, pname, right.."_access")
+ local tmp_list = {}
+ for k, v in pairs(pl_with_access) do
+ table.insert(tmp_list, k)
+ end
+ table.sort(tmp_list)
+ local index = table.indexof(tmp_list, selected)
+ if(selected == "Add player:") then
+ index = 0
+ end
+ if(index and index > -1) then
+ yl_speak_up.speak_to[pname]["tmp_index_var_"..right.."_access"] = index + 1
+ end
+ yl_speak_up.show_fs(player, "manage_variables")
+ return
+ end
+ -- this function didn't have anything to do
+ return "NOTHING FOUND"
+end
+
+
+-- makes use of yl_speak_up.handle_input_fs_manage_general and is thus pretty short
+yl_speak_up.input_fs_manage_variables = function(player, formname, fields)
+ local pname = player:get_player_name()
+ local list_of_entries = yl_speak_up.get_quest_variables(pname, true)
+ -- make names of own variables shorter
+ yl_speak_up.strip_pname_from_varlist(list_of_entries, pname)
+ table.sort(list_of_entries)
+
+ local res = yl_speak_up.handle_input_fs_manage_general(player, formname, fields,
+ -- what_is_the_list_about, min_length, max_length, function_add_new_entry,
+ "variable", 2, 30,
+ yl_speak_up.fun_input_fs_manage_variables_add_new_entry,
+ list_of_entries,
+ yl_speak_up.fun_input_fs_manage_variables_del_old_entry,
+ yl_speak_up.fun_input_fs_manage_variables_check_fields)
+end
+
+
+yl_speak_up.get_fs_manage_variables = function(player, param)
+ local pname = player:get_player_name()
+ -- variables owned by the player - including those with write access
+ local var_list = yl_speak_up.get_quest_variables(pname, true)
+ -- make names of own variables shorter
+ yl_speak_up.strip_pname_from_varlist(var_list, pname)
+
+ local formspec = {}
+ if(not(param) or type(param) ~= "table") then
+ param = {}
+ end
+ if(param and type(param) == "table" and param.var_name and param.var_name ~= "") then
+ local index = table.indexof(var_list, param.var_name)
+ yl_speak_up.speak_to[pname].tmp_index_general = index + 1
+ end
+ table.insert(formspec, "size[18,12]"..
+ "label[0.2,1.2;Note: Each variable will store a diffrent value for each "..
+ "player who interacts with the NPC.\n"..
+ "You can grant read and write access to other players for your "..
+ "variables so that they can also use them as well.]")
+ local selected = yl_speak_up.build_fs_manage_general(player, param.var_name,
+ formspec, var_list,
+ "Create variable",
+ "Create a new varialbe with the name\n"..
+ "you entered in the field to the left.",
+ "variable",
+ "Enter the name of the new variable you\n"..
+ "want to create.",
+ "If you click here, the selected variable\n"..
+ "will be deleted.")
+ if(selected and selected ~= "") then
+ local k = selected
+ -- index 1 is "Add variable:"
+ local pl_with_read_access = yl_speak_up.get_access_list_for_var(k, pname, "read_access")
+ local pl_with_write_access = yl_speak_up.get_access_list_for_var(k, pname, "write_access")
+ if(not(yl_speak_up.speak_to[pname].tmp_index_var_read_access)
+ or yl_speak_up.speak_to[pname].tmp_index_var_read_access == 1) then
+ yl_speak_up.speak_to[pname].tmp_index_var_read_access = 1
+ table.insert(formspec, "button[14.6,2.95;1.0,0.6;add_read_access;Add]"..
+ "tooltip[add_read_access;Grant the player whose name you entered\n"..
+ "you entered in the field to the left read access\n"..
+ "to your variable.]")
+ end
+ if(not(yl_speak_up.speak_to[pname].tmp_index_var_write_access)
+ or yl_speak_up.speak_to[pname].tmp_index_var_write_access == 1) then
+ yl_speak_up.speak_to[pname].tmp_index_var_write_access = 1
+ table.insert(formspec, "button[14.6,3.95;1.0,0.6;add_write_access;Add]"..
+ "tooltip[add_write_access;Grant the player whose name you entered\n"..
+ "you entered in the field to the left *write* access\n"..
+ "to your variable.]")
+ end
+ local list_of_npc_users = "- none -"
+ local list_of_node_pos_users = "- none -"
+ -- expand name of variable k again
+ local k_long = yl_speak_up.add_pname_to_var(k, pname)
+ -- which npc and which node_pos use this variable? create a list for display
+ local c1 = 0
+ local c2 = 0
+ if(k_long
+ and yl_speak_up.player_vars[ k_long ]
+ and yl_speak_up.player_vars[ k_long ][ "$META$"]) then
+ local npc_users = yl_speak_up.get_variable_metadata(k_long, "used_by_npc")
+ c1 = #npc_users
+ if(npc_users and c1 > 0) then
+ list_of_npc_users = minetest.formspec_escape(table.concat(npc_users, ", "))
+ end
+ local node_pos_users = yl_speak_up.get_variable_metadata(k_long, "used_by_node_pos")
+ c2 = #node_pos_users
+ if(node_pos_users and c2 > 0) then
+ list_of_node_pos_users = minetest.formspec_escape(table.concat(
+ node_pos_users, ", "))
+ end
+ end
+ table.insert(formspec, "button[10.0,10.05;4.0,0.6;list_debug_mode;What am I debugging?]"..
+ "tooltip[list_debug_mode;"..
+ "Show for which variables you currently have "..
+ "\nactivated the debug mode.]")
+ local debuggers = yl_speak_up.get_variable_metadata(k_long, "debug")
+ local i = table.indexof(debuggers, pname)
+ if(i and i > 0) then
+ table.insert(formspec,
+ "button[5.0,10.05;4.0,0.6;disable_debug_mode;Deactivate debug mode]"..
+ "tooltip[disable_debug_mode;"..
+ "You will no longer receive a chat message "..
+ "\nwhen this value changes for a player.]")
+ else
+ table.insert(formspec,
+ "button[5.0,10.05;4.0,0.6;enable_debug_mode;Activate debug mode]"..
+ "tooltip[enable_debug_mode;"..
+ "You will receive a chat message whenever the value "..
+ "\nof this variable changes for one player. The debug\n"..
+ "messages will be sent even after relogin.]")
+ end
+ -- checking/changing debug value for one specific player
+ if(not(param.for_player)) then
+ param.for_player = ""
+ end
+ table.insert(formspec,
+ "label[0.2,8.05;Show stored value for player:]"..
+ "field[4.9,7.75;4.0,0.6;stored_value_for_player;;")
+ table.insert(formspec, minetest.formspec_escape(param.for_player))
+ table.insert(formspec, "]")
+ table.insert(formspec,
+ "button[9.0,7.75;4.5,0.6;show_stored_value_for_player;Show value for this player]"..
+ "tooltip[stored_value_for_player;Enter the name of the player for which you\n"..
+ "want to check (or change) the stored value.]"..
+ "tooltip[show_stored_value_for_player;Click here to read and the current value"..
+ "\nstored for this player.]")
+ if(param.for_player and param.for_player ~= "") then
+ local v = yl_speak_up.get_quest_variable_value(param.for_player, k_long) or ""
+ table.insert(formspec,
+ "label[0.2,9.05;Found stored value:]"..
+ "field[4.9,8.75;4.0,0.6;current_value_for_player;;")
+ table.insert(formspec, minetest.formspec_escape(v))
+ table.insert(formspec, "]"..
+ "tooltip[current_value_for_player;You can see and change the current "..
+ "value here.]"..
+ "button[9.0,8.75;4.5,0.6;store_new_value_for_player;"..
+ "Store this as new value]"..
+ "tooltip[store_new_value_for_player;"..
+ "Click here to update the stored value for this player."..
+ "\nWARNING: Be very careful here and never do this without"..
+ "\n informing the player about this change!]"..
+ "button[13.9,8.75;3.0,0.6;unset_value_for_player;"..
+ "Remove this entry]"..
+ "tooltip[unset_value_for_player;Click here to delete the entry for this "..
+ "player.\nSetting the entry to an empty string would not be "..
+ "the same!]")
+ end
+ table.insert(formspec, "button[12.2,2.15;3.0,0.6;show_var_usage;Where is it used?]"..
+ "tooltip[show_var_usage;Show which NPC use this variable in which context.]"..
+ -- offer a dropdown list and a text input field for new varialbe names for adding
+ "label[0.2,3.25;Players with read access to this variable:]")
+ table.insert(formspec, yl_speak_up.create_dropdown_playerlist(player, pname,
+ pl_with_read_access,
+ yl_speak_up.speak_to[pname].tmp_index_var_read_access,
+ 6.9, 2.95, 0.0, 0.6, "list_var_read_access", "player",
+ "Remove player from list",
+ "grant_player_var_read_access",
+ "Enter the name of the player that shall\n"..
+ "have read access to this variable.",
+ "revoke_player_var_read_access",
+ "If you click here, the selected player\n"..
+ "will no longer be able to add new\n"..
+ "pre(C)onditions which read your variable."))
+ table.insert(formspec, "label[0.2,4.25;Players with *write* access to this variable:]")
+ table.insert(formspec, yl_speak_up.create_dropdown_playerlist(player, pname,
+ pl_with_write_access,
+ yl_speak_up.speak_to[pname].tmp_index_var_write_access,
+ 6.9, 3.95, 0.0, 0.6,
+ "list_var_write_access", "player", "Remove player from list",
+ "grant_player_var_write_access",
+ "Enter the name of the player that shall\n"..
+ "have *write* access to this variable.",
+ "revoke_player_var_write_access",
+ "If you click here, the selected player\n"..
+ "will no longer be able to *write* new\n"..
+ "values into this variable."))
+ local var_type = (yl_speak_up.get_variable_metadata(k_long, "var_type")
+ or "String/text or numerical value, depending on how you use it")
+ table.insert(formspec, "label[0.2,5.05;Type of variable: ")
+ table.insert(formspec, minetest.colorize("#FFFF00", var_type)) -- show variable type
+ table.insert(formspec, ".]")
+ if(var_type == "quest") then
+ table.insert(formspec, "button[4.2,4.75;4.5,0.6;show_quest;Show and edit this quest]")
+ end
+ table.insert(formspec, "label[0.2,6.05;This variable is used by the following ")
+ table.insert(formspec,
+ minetest.colorize("#FFFF00", tostring(c1)).." NPC:\n\t"..
+ -- those are only n_id - no need to formspec_escape that
+ minetest.colorize("#FFFF00", list_of_npc_users))
+ table.insert(formspec, ".]")
+ table.insert(formspec, "label[0.2,7.05;This variable is used by the following ")
+ table.insert(formspec,
+ minetest.colorize("#FFFF00", tostring(c2)).." node positions:\n\t"..
+ -- those are only pos_to_string(..) - no need to formspec_escape that
+ minetest.colorize("#FFFF00", list_of_node_pos_users))
+ table.insert(formspec, ".]")
+ table.insert(formspec,
+ "button[0.2,10.05;4.0,0.6;show_stored_values_for_var;Show all stored values]"..
+ "tooltip[show_stored_values_for_var;A diffrent value can be stored for each "..
+ "player.\nShow these values in a table.]")
+ end
+ return table.concat(formspec, "")
+end
+
+
+yl_speak_up.register_fs("manage_variables",
+ yl_speak_up.input_fs_manage_variables,
+ yl_speak_up.get_fs_manage_variables,
+ -- no special formspec required:
+ nil
+)
diff --git a/fs/fs_notes.lua b/fs/fs_notes.lua
new file mode 100644
index 0000000..84fd44f
--- /dev/null
+++ b/fs/fs_notes.lua
@@ -0,0 +1,66 @@
+
+
+yl_speak_up.input_notes = function(player, formname, fields)
+ if(fields and fields.back) then
+ return yl_speak_up.show_fs(player, "talk")
+ elseif(fields and fields.store_notes and fields.notes_text) then
+ local pname = player:get_player_name()
+ local dialog = yl_speak_up.speak_to[pname].dialog
+ local n_id = yl_speak_up.speak_to[pname].n_id
+ -- update the dialog data the player sees
+ dialog.d_notes = fields.notes_text
+ -- actually store/update it on disc as well
+ local stored_dialog = yl_speak_up.load_dialog(n_id, false)
+ stored_dialog.d_notes = dialog.d_notes
+ yl_speak_up.save_dialog(n_id, stored_dialog)
+ -- log the change
+ yl_speak_up.log_change(pname, n_id, "Updated notes to: "..tostring(dialog.d_notes))
+ return yl_speak_up.show_fs(player, "msg", {
+ input_to = "yl_speak_up:notes",
+ formspec = "size[10,3]"..
+ "label[0.5,1.0;Notes successfully updated.]"..
+ "button[3.5,2.0;2,0.9;back_from_error_msg;Back]"
+ })
+ else
+ return yl_speak_up.show_fs(player, "notes")
+ end
+end
+
+
+yl_speak_up.get_fs_notes = function(player, param)
+ local pname = player:get_player_name()
+ local n_id = yl_speak_up.speak_to[pname].n_id
+ -- generic dialogs are not part of the NPC
+ local dialog = yl_speak_up.speak_to[pname].dialog
+ return table.concat({"size[20,20]",
+ "label[2,0.5;Internal notes on NPC ",
+ minetest.formspec_escape(n_id or "- ? -"),
+ ", named ",
+ minetest.formspec_escape(dialog.n_npc) or "- ? -",
+ "]",
+ "button[17.8,0.2;2.0,0.9;back;Back]",
+ "button[15.0,0.2;2.0,0.9;store_notes;Save]",
+ "textarea[0.2,2;19.6,13;notes_text;Notes (shown only to those who can edit this NPC):;",
+ minetest.formspec_escape(dialog.d_notes or "Enter text here."),
+ "]",
+ "textarea[0.2,15.2;19.6,4.8;;;",
+ "This can be used to make your NPC more intresting by storing information about..\n"..
+ "* its character\n"..
+ "* special characteristics of the NPC\n"..
+ "* linguistic peculiarities and habits\n"..
+ "* origin, relationships, lots of lore\n"..
+ "* friendships / enmities\n"..
+ "* personal goals / motivations / background\n"..
+ "* planned quests\n"..
+ "* trades\n"..
+ "and whatever else you want to keep notes on for this NPC.]"
+ })
+end
+
+
+yl_speak_up.register_fs("notes",
+ yl_speak_up.input_notes,
+ yl_speak_up.get_fs_notes,
+ -- no special formspec required:
+ nil
+)
diff --git a/fs/fs_properties.lua b/fs/fs_properties.lua
new file mode 100644
index 0000000..903e2e9
--- /dev/null
+++ b/fs/fs_properties.lua
@@ -0,0 +1,139 @@
+-- allow owner to see and edit properties of the NPC
+
+yl_speak_up.input_properties = function(player, formname, fields)
+ 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
+
+ if(fields and fields.back and fields.back ~= "") then
+ yl_speak_up.show_fs(player, "initial_config",
+ {n_id = n_id, d_id = yl_speak_up.speak_to[pname].d_id, false})
+ return
+ end
+
+ local selected_row = nil
+ if(fields and fields.store_new_val and fields.store_new_val ~= "" and fields.prop_val) then
+ yl_speak_up.set_npc_property(pname, fields.prop_name, fields.prop_val, "manually")
+
+ elseif(fields and fields.delete_prop and fields.delete_prop ~= "") then
+ -- delete the old property
+ yl_speak_up.set_npc_property(pname, fields.prop_name, nil, "manually")
+
+ elseif(fields and fields.table_of_properties) then
+ local selected = minetest.explode_table_event(fields.table_of_properties)
+ if(selected.type == "CHG" or selected.type == "DLC") then
+ selected_row = selected.row
+ end
+ end
+ yl_speak_up.show_fs(player, "properties", {selected = selected_row})
+end
+
+
+yl_speak_up.get_fs_properties = function(pname, selected)
+ if(not(pname)) then
+ return ""
+ end
+ local n_id = yl_speak_up.speak_to[pname].n_id
+
+ -- is the player editing this npc? if not: abort
+ if(not(yl_speak_up.edit_mode[pname])
+ or (yl_speak_up.edit_mode[pname] ~= n_id)) then
+ return ""
+ end
+
+ -- we want the long version with additional information
+ local property_data = yl_speak_up.get_npc_properties_long_version(pname, true)
+ if(not(property_data)) then
+ -- something went wrong - there really is nothing useful we can do
+ -- if the NPC we want to interact with doesn't exist or is broken
+ return
+ end
+ local s = ""
+ if(not(property_data.prop_names)) then
+ property_data.prop_names = {}
+ end
+ local anz_prop = #property_data.prop_names
+ for i, k in ipairs(property_data.prop_names) do
+ local v = property_data.properties[k]
+ s = s.."#BBBBFF,"..minetest.formspec_escape(k)..","..minetest.formspec_escape(v)..","
+ end
+ s = s.."#00FF00,add,Add a new property"
+
+ if(not(selected) or selected == "") then
+ selected = -1
+ end
+ local add_selected = "label[3.5,6.5;No property selected.]"
+ selected = tonumber(selected)
+ if(selected > anz_prop + 1 or selected < 1) then
+ selected = -1
+ elseif(selected > anz_prop) then
+ add_selected = "label[0.2,6.5;Add new property:]"..
+ "field[3.0,6.5;3.5,1.0;prop_name;;]"..
+ "label[6.5,6.5;with value:]"..
+ "field[8.2,6.5;4.5,1.0;prop_val;;]"..
+ "button[8.2,7.8;2.0,1.0;store_new_val;Store]"
+ -- external properties can usually only be read but not be changed
+ elseif(string.sub(property_data.prop_names[selected], 1, 5) == "self."
+ and not(yl_speak_up.custom_property_handler[property_data.prop_names[selected]])) then
+ add_selected = "label[3.5,6.5;Properties of the type \"self.\" usually cannot be modified.]"
+ elseif(string.sub(property_data.prop_names[selected], 1, 6) == "server"
+ and not(minetest.check_player_privs(pname, {npc_talk_admin=true}))) then
+ add_selected = "label[3.5,6.5;Properties starting with \"server\" can only be "..
+ "changed by players\nwho have the \"npc_talk_admin\" priv."
+ else
+ local name = property_data.prop_names[selected]
+ local val = minetest.formspec_escape(property_data.properties[name])
+ local name_esc = minetest.formspec_escape(name)
+ add_selected = "label[0.2,6.5;Change property:]"..
+ "field[3.0,6.5;3.5,1.0;prop_name;;"..name_esc.."]"..
+ "label[6.5,6.5;to value:]"..
+ "field[8.2,6.5;4.5,1.0;prop_val;;"..val.."]"..
+ "button[8.2,7.8;2.0,1.0;store_new_val;Store]"..
+ "button[10.4,7.8;2.0,1.0;delete_prop;Delete]"
+ end
+ if(selected < 1) then
+ selected = ""
+ end
+ local dialog = yl_speak_up.speak_to[pname].dialog
+ local npc_name = minetest.formspec_escape(dialog.n_npc or "- nameless -")
+ return table.concat({"size[12.5,8.5]",
+ "label[2,0;Properties of ",
+ npc_name,
+ " (ID: ",
+ tostring(n_id),
+ "):]",
+ "tablecolumns[color,span=1;text;text]",
+ "table[0.2,0.5;12,4.0;table_of_properties;",
+ s,
+ ";",
+ tostring(selected),
+ "]",
+ "tooltip[0.2,0.5;12,4.0;",
+ "Click on \"add\" to add a new property.\n",
+ "Click on the name of a property to change or delete it.]",
+ "label[2.0,4.5;"..
+ "Properties are important for NPC that want to make use of generic dialogs.\n"..
+ "Properties can be used to determine which generic dialog(s) shall apply to\n"..
+ "this particular NPC and how they shall be configured. You need the\n"..
+ "\"npc_talk_admin\" priv to edit properties starting with the text \"server\".]",
+ "button[5.0,7.8;2.0,0.9;back;Back]",
+ add_selected
+ }, "")
+end
+
+yl_speak_up.get_fs_properties_wrapper = function(player, param)
+ if(not(param)) then
+ param = {}
+ end
+ local pname = player:get_player_name()
+ return yl_speak_up.get_fs_properties(pname, param.selected)
+end
+
+yl_speak_up.register_fs("properties",
+ yl_speak_up.input_properties,
+ yl_speak_up.get_fs_properties_wrapper,
+ -- force formspec version 1:
+ 1
+)
diff --git a/fs/fs_quest_gui.lua b/fs/fs_quest_gui.lua
new file mode 100644
index 0000000..e596756
--- /dev/null
+++ b/fs/fs_quest_gui.lua
@@ -0,0 +1,41 @@
+yl_speak_up.input_quest_gui = function(player, formname, fields)
+ -- this return value is necessary for custom actions
+ local ret = {quit = true}
+
+ local pname = player:get_player_name()
+ if(fields and fields.back_from_msg) then
+ yl_speak_up.show_fs(player, "quest_gui")
+ return ret
+ end
+
+ -- new variables have to be added (and deleted) somewhere after all
+ if(fields.manage_variables) then
+ -- remember which formspec we are comming from
+ yl_speak_up.speak_to[pname][ "working_at" ] = "quest_gui"
+ yl_speak_up.show_fs(player, "manage_variables")
+ return ret
+ elseif(fields.manage_quests) then
+ yl_speak_up.speak_to[pname][ "working_at" ] = "quest_gui"
+ yl_speak_up.show_fs(player, "manage_quests")
+ return ret
+ end
+ -- the calling NPC shall no longer do anything
+ return ret
+end
+
+
+yl_speak_up.get_fs_quest_gui = function(player, param)
+ local pname = player:get_player_name()
+ return "size[24,20]"..
+ "label[0,0.5;Hi. This is a quest admin gui.]"..
+ "button[0.2,1.0;4.0,0.6;manage_variables;Manage variables]"..
+ "button[6.2,1.0;4.0,0.6;manage_quests;Manage quests]"
+end
+
+
+yl_speak_up.register_fs("quest_gui",
+ yl_speak_up.input_quest_gui,
+ yl_speak_up.get_fs_quest_gui,
+ -- no special formspec required:
+ nil
+)
diff --git a/fs/fs_save_or_discard_or_back.lua b/fs/fs_save_or_discard_or_back.lua
new file mode 100644
index 0000000..26736a5
--- /dev/null
+++ b/fs/fs_save_or_discard_or_back.lua
@@ -0,0 +1,147 @@
+
+-- when the player is editing the NPC and has changed it without having
+-- saved the changes yet: ask what shall be done (save? discard? back?)
+yl_speak_up.input_save_or_discard_changes = function(player, formname, fields)
+ local pname = player:get_player_name()
+ -- if the player is not even talking to this particular npc
+ if(not(yl_speak_up.speak_to[pname])) then
+ return
+ end
+
+ local target_dialog = yl_speak_up.speak_to[pname].target_dialog
+ if(not(target_dialog)) then
+ target_dialog = ""
+ end
+
+ local edit_mode = (yl_speak_up.edit_mode[pname] == yl_speak_up.speak_to[pname].n_id)
+ local d_id = yl_speak_up.speak_to[pname].d_id
+ local n_id = yl_speak_up.speak_to[pname].n_id
+ local o_id = yl_speak_up.speak_to[pname].o_id
+
+ -- the player decided to go back and continue editing the current dialog
+ if(edit_mode and fields.back_to_dialog_changes) then
+ -- do NOT clear the list of changes; just show the old dialog again
+ yl_speak_up.show_fs(player, "show_last_fs", {})
+ return
+
+ -- save changes and continue on to the next dialog
+ elseif(edit_mode and fields.save_dialog_changes) then
+ -- actually save the dialog (the one the NPC currently has)
+ yl_speak_up.save_dialog(n_id, yl_speak_up.speak_to[pname].dialog)
+ -- log all the changes
+ for i, c in ipairs(yl_speak_up.npc_was_changed[ n_id ]) do
+ yl_speak_up.log_change(pname, n_id, c)
+ end
+ -- clear list of changes
+ yl_speak_up.npc_was_changed[ n_id ] = {}
+ -- save_dialog removed d_dynamic (because that is never to be saved!); we have
+ -- to add d_dynamic back so that we can use it as a target dialog in further editing:
+ yl_speak_up.speak_to[pname].dialog.n_dialogs["d_dynamic"] = {}
+ yl_speak_up.speak_to[pname].dialog.n_dialogs["d_dynamic"].d_options = {}
+
+ -- discard changes and continue on to the next dialog
+ elseif(edit_mode and fields.discard_dialog_changes) then
+ -- the current dialog and the one we want to show next may both be new dialogs;
+ -- if we just reload the old state, they would both get lost
+ local target_dialog_data = yl_speak_up.speak_to[pname].dialog.n_dialogs[ target_dialog ]
+ -- actually restore the old state and discard the changes by loading the dialog anew
+ yl_speak_up.speak_to[pname].dialog = yl_speak_up.load_dialog(n_id, false)
+ -- clear list of changes
+ yl_speak_up.npc_was_changed[ n_id ] = {}
+ local dialog = yl_speak_up.speak_to[pname].dialog
+ -- do we have to save again after restoring current and target dialog?
+ local need_to_save = false
+ -- if the current dialog was a new one, it will be gone now - restore it
+ if(d_id and d_id ~= "" and not(dialog.n_dialogs[ d_id ])) then
+ -- we can't just restore the current dialog - after all the player wanted
+ -- to discard the changes; but we can recreate the current dialog so that it
+ -- is in the "new dialog" state again
+ local next_id = tonumber(string.sub( d_id, 3))
+ yl_speak_up.add_new_dialog(dialog, pname, next_id)
+ yl_speak_up.log_change(pname, n_id, "Saved new dialog "..tostring( d_id )..".")
+ need_to_save = true
+ end
+ if(target_dialog and target_dialog ~= "" and not(dialog.n_dialogs[ target_dialog ])) then
+ -- restore the new target dialog
+ dialog.n_dialogs[ target_dialog ] = target_dialog_data
+ yl_speak_up.log_change(pname, n_id, "Saved new dialog "..tostring( target_dialog )..".")
+ need_to_save = true
+ end
+ if(need_to_save) then
+ yl_speak_up.save_dialog(n_id, dialog)
+ end
+ end
+
+ -- are there any changes which might be saved or discarded?
+ if(edit_mode
+ and yl_speak_up.npc_was_changed[ n_id ]
+ and #yl_speak_up.npc_was_changed[ n_id ] > 0) then
+
+ yl_speak_up.show_fs(player, "save_or_discard_changes", {})
+ return
+ end
+
+ yl_speak_up.show_fs(player, "proceed_after_save", {})
+end
+
+
+yl_speak_up.get_fs_save_or_discard_changes = function(player, param)
+ local pname = player:get_player_name()
+ local n_id = yl_speak_up.speak_to[pname].n_id
+ -- TODO
+ local target_name = "quit"
+ local target_dialog = nil -- TODO
+ if(target_dialog and target_dialog ~= "") then
+ target_name = "go on to dialog "..minetest.formspec_escape(target_dialog)
+ if(target_dialog == "edit_option_dialog") then
+ target_name = "edit option \""..
+ minetest.formspec_escape(tostring(o_id)).."\" of dialog \""..
+ minetest.formspec_escape(tostring(d_id)).."\""
+ end
+ end
+
+ yl_speak_up.speak_to[pname].target_dialog = target_dialog
+ local d_id = yl_speak_up.speak_to[pname].d_id
+ -- reverse the order of the changes in the log so that newest are topmost
+ local text = ""
+ for i,t in ipairs(yl_speak_up.npc_was_changed[ n_id ]) do
+ text = minetest.formspec_escape(t).."\n"..text
+ end
+ -- build a formspec showing the changes to this dialog and ask for save
+ return table.concat({"size[14,6.2]",
+ "bgcolor[#00000000;false]",
+ -- TODO: make this more flexible
+ "label[0.2,0.2;You are about to leave dialog ",
+ minetest.formspec_escape(d_id),
+ " and ",
+ target_name,
+ ".]",
+ "label[0.2,0.65;These changes have been applied to dialog ",
+ minetest.formspec_escape(d_id),
+ ":]",
+ "hypertext[0.2,1;13.5,4;list_of_changes;",
+ minetest.formspec_escape(text),
+ "\n",
+ "]",
+ "button_exit[1.2,5.2;3,0.9;discard_dialog_changes;Discard changes]",
+ "button[5.7,5.2;3,0.9;back_to_dialog_changes;Back]",
+ "button_exit[10.2,5.2;3,0.9;save_dialog_changes;Save changes]",
+ "tooltip[save_dialog_changes;Save all changes to this dialog and ",
+ target_name,
+ ".]",
+ "tooltip[discard_dialog_changes;Undo all changes and ",
+ target_name,
+ ".]",
+ "tooltip[back_to_dialog_changes;Go back to dialog ",
+ minetest.formspec_escape(d_id),
+ " and continue editing it.]"
+ }, "")
+end
+
+
+yl_speak_up.register_fs("save_or_discard_changes",
+ yl_speak_up.input_save_or_discard_changes,
+ yl_speak_up.get_fs_save_or_discard_changes,
+ -- no special formspec required:
+ nil
+)
diff --git a/fs/fs_show_all_var_values.lua b/fs/fs_show_all_var_values.lua
new file mode 100644
index 0000000..db0389f
--- /dev/null
+++ b/fs/fs_show_all_var_values.lua
@@ -0,0 +1,64 @@
+
+-- called only by mange_variables formspec
+yl_speak_up.fs_show_all_var_values = function(player, pname, var_name)
+ -- wrong parameters? no need to show an error message here
+ if(not(var_name) or not(pname) or not(player)) then
+ return ""
+ end
+ -- TODO: check if the player really has read access to this variable
+ var_name = yl_speak_up.restore_complete_var_name(var_name, pname)
+
+ -- player names with values as key; normally the player name is the key and
+ -- the value the value - but that would be a too long list to display, and
+ -- so we rearrange the array for display here
+ local players_with_value = {}
+ -- the diffrent values that exist
+ local values = {}
+ local var_data = yl_speak_up.player_vars[ var_name ]
+ local count_players = 0
+ for player_name, v in pairs(var_data) do
+ -- metadata is diffrent and not of relevance here
+ if(player_name and player_name ~= "$META$" and v) then
+ if(not(players_with_value[ v ])) then
+ players_with_value[ v ] = {}
+ table.insert(values, v)
+ end
+ table.insert(players_with_value[ v ], player_name)
+ count_players = count_players + 1
+ end
+ end
+ -- the values ought to be shown in a sorted way
+ table.sort(values)
+
+ -- construct the lines that shall form the table
+ local lines = {"#FFFFFF,Value:,#FFFFFF,Players for which this value is stored:"}
+ for i, v in ipairs(values) do
+ table.insert(lines,
+ "#FFFF00,"..minetest.formspec_escape(v)..",#CCCCCC,"..
+ -- text, prefix, line_length, max_lines
+ yl_speak_up.wrap_long_lines_for_table(
+ table.concat(players_with_value[ v ], ", "),
+ ",,,#CCCCCC,", 80, 8))
+ end
+ -- true here means: lines are already sorted;
+ -- ",": don't insert blank lines between entries
+ local formspec = yl_speak_up.print_as_table_prepare_formspec(lines, "table_of_variable_values",
+ "back_from_msg", "Back", true, ",",
+ "color,span=1;text;color,span=1;text") -- the table columns
+ table.insert(formspec,
+ "label[18.0,1.8;"..
+ minetest.formspec_escape("For variable \""..
+ minetest.colorize("#FFFF00", tostring(var_name or "- ? -"))..
+ "\", these values are stored:").."]")
+
+ if(values and #values > 0) then
+ table.insert(formspec,
+ "label[18.0,31.0;The variable holds "..
+ minetest.colorize("#FFFF00", tostring(#values)).." diffrent values for "..
+ minetest.colorize("#FFFF00", tostring(count_players)).." diffrent players.]")
+ else
+ table.insert(formspec,
+ "label[18.0,31.0;The variable does not currently hold any stored values.]")
+ end
+ return table.concat(formspec, "\n")
+end
diff --git a/fs/fs_show_what_points_to_this_dialog.lua b/fs/fs_show_what_points_to_this_dialog.lua
new file mode 100644
index 0000000..d4319e2
--- /dev/null
+++ b/fs/fs_show_what_points_to_this_dialog.lua
@@ -0,0 +1,256 @@
+-- helpful for debugging the content/texts of the created dialog structure
+
+yl_speak_up.input_show_what_points_to_this_dialog = function(player, formname, fields)
+ local pname = player:get_player_name()
+ if(fields.back_from_show_what_points_here
+ or not(fields.turn_dialog_into_alternate_text)) then
+ -- back to the dialog
+ yl_speak_up.show_fs(player, "talk",
+ {n_id = yl_speak_up.speak_to[pname].n_id,
+ d_id = yl_speak_up.speak_to[pname].d_id})
+ return
+ end
+ -- fields.turn_dialog_into_alternate_text is set
+ local n_id = yl_speak_up.speak_to[pname].n_id
+ local this_dialog = yl_speak_up.speak_to[pname].d_id
+ local dialog = yl_speak_up.speak_to[pname].dialog
+ if(not(dialog)
+ or not(dialog.n_dialogs)
+ or not(this_dialog)
+ or not(dialog.n_dialogs[ this_dialog ])) then
+ return
+ end
+ -- only show this information when editing this npc
+ if(yl_speak_up.edit_mode[pname] ~= yl_speak_up.speak_to[pname].n_id) then
+ return
+ end
+ -- find out what needs to be turned into an alternate dialog
+ local found = {}
+ -- how many options does the current dialog have?
+ local count_options = 0
+ -- iterate over all dialogs
+ for d_id, d in pairs(dialog.n_dialogs) do
+ -- the dialog itself may have options that point back to the dialog itself
+ if(d.d_options) then
+ -- iterate over all options
+ for o_id, o in pairs(d.d_options) do
+ -- this is only possible if there are no options for this dialog
+ if(d_id == this_dialog) then
+ count_options = count_options + 1
+ end
+ -- preconditions are not relevant;
+ -- effects are (dialog and on_failure)
+ if(o.o_results) then
+ for r_id, r in pairs(o.o_results) do
+ if(r and r.r_type and r.r_type == "dialog"
+ and r.r_value == this_dialog) then
+ table.insert(found, {d_id=d_id, o_id=o_id, id=r_id,
+ element=r, text="option was selected"})
+ elseif(r and r.r_type and r.r_type == "on_failure"
+ and r.r_value == this_dialog) then
+ table.insert(found, {d_id=d_id, o_id=o_id, id=r_id,
+ element=r, text="the previous effect failed"})
+ end
+ end
+ end
+ -- actions may be relevant
+ if(o.actions) then
+ for a_id, a in pairs(o.actions) do
+ if(a and a.a_on_failure
+ and a.a_on_failure == this_dialog) then
+ table.insert(found, {d_id=d_id, o_id=o_id, id=a_id,
+ element=a, text="action failed"})
+ end
+ end
+ end
+ end
+ end
+ end
+
+ local error_msg = ""
+ if(count_options > 0) then
+ error_msg = "This dialog still has dialog options.\nConversion not possible."
+ elseif(#found < 1) then
+ error_msg = "Found no option, action or effect\nthat points to this dialog."
+ elseif(#found > 1) then
+ error_msg = "Found more than one option, action\nor effect that points to this dialog."
+ end
+ if(error_msg ~= "") then
+ yl_speak_up.show_fs(player, "msg", {
+ input_to = "yl_speak_up:show_what_points_to_this_dialog",
+ formspec = "size[8,2]"..
+ "label[0.2,0.5;Error: "..error_msg.."]"..
+ "button[1.5,1.5;2,0.9;back_from_error_msg;Back]"})
+ return
+ end
+
+ -- all fine so far; this is the text we want to set as alternate text
+ local d_text = dialog.n_dialogs[ this_dialog ].d_text
+ local f = found[1]
+ -- are we dealing with a result/effect or an action?
+ t = "o_results"
+ if(f.element.a_id) then
+ t = "actions"
+ end
+ -- there may already be an alternate text stored there
+ local alternate_text = dialog.n_dialogs[ f.d_id ].d_options[ f.o_id ][ t ][ f.id ].alternate_text
+ if(not(alternate_text)) then
+ -- no old text found
+ alternate_text = d_text
+ else
+ -- the old text may reference this d_text
+ alternate_text = string.gsub(alternate_text, "%$TEXT%$", d_text)
+ end
+ -- log the change
+ table.insert(yl_speak_up.npc_was_changed[ n_id ],
+ "Dialog "..tostring(this_dialog)..": Deleted this dialog and turned it into an "..
+ "alternate text for dialog \""..tostring(f.d_id).."\" option \""..tostring(f.o_id)..
+ "\" element \""..tostring(f.id).."\".")
+
+ -- actually set the new alternate_text
+ dialog.n_dialogs[ f.d_id ].d_options[ f.o_id ][ t ][ f.id ].alternate_text = alternate_text
+ -- delete this dialog
+ dialog.n_dialogs[ this_dialog ] = nil
+ -- we need to show a new/diffrent dialog to the player now - because the old one was deleted
+ yl_speak_up.speak_to[pname].d_id = f.d_id
+ yl_speak_up.speak_to[pname].o_id = f.o_id
+ if(t == "o_results") then
+ -- we can't really know where this ought to point to - the old dialog is gone, so
+ -- let's point to the current dialog to avoid errors
+ dialog.n_dialogs[ f.d_id ].d_options[ f.o_id ][ t ][ f.id ].r_value = f.d_id
+ -- dialog - normal switching to the next dialog or on_failure - the previous effect failed:
+ yl_speak_up.show_fs(player, "edit_effects", f.id)
+ else
+ -- the player may have to change this manually; we really can't know what would fit
+ -- (but the old dialog is gone)
+ dialog.n_dialogs[ f.d_id ].d_options[ f.o_id ][ t ][ f.id ].a_on_failure = f.d_id
+ -- an action failed:
+ yl_speak_up.show_fs(player, "edit_actions", f.id)
+ end
+end
+
+
+-- show which dialogs point/lead to this_dialog
+yl_speak_up.get_fs_show_what_points_to_this_dialog = function(player, this_dialog)
+ local pname = player:get_player_name()
+ local dialog = yl_speak_up.speak_to[pname].dialog
+ if(not(dialog)
+ or not(dialog.n_dialogs)
+ or not(this_dialog)
+ or not(dialog.n_dialogs[ this_dialog ])) then
+ return ""
+ end
+
+ -- only show this information when editing this npc
+ if(yl_speak_up.edit_mode[pname] ~= yl_speak_up.speak_to[pname].n_id) then
+ return ""
+ end
+ local found = {}
+ -- colored lines for the table showing the results
+ local res = {}
+ -- iterate over all dialogs
+ for d_id, d in pairs(dialog.n_dialogs) do
+ -- the dialog itself may have options that point back to the dialog itself
+ if(d.d_options) then
+ -- iterate over all options
+ for o_id, o in pairs(d.d_options) do
+ local r_text = ""
+ local p_text = ""
+ local alternate_dialog = nil
+ local alternate_text = nil
+ -- preconditions are not relevant;
+ -- effects are (dialog and on_failure)
+ if(o.o_results) then
+ for r_id, r in pairs(o.o_results) do
+ if(r and r.r_type and r.r_type == "dialog"
+ and r.r_value == this_dialog) then
+ r_text = r_text..yl_speak_up.print_as_table_effect(
+ r, pname)
+ table.insert(found, {d_id, o_id, r_id,
+ "option was selected"})
+ alternate_dialog = r.r_value
+ if(r.alternate_text) then
+ alternate_text =
+ "Show alternate text: "..
+ tostring(r.alternate_text)
+ end
+ elseif(r and r.r_type and r.r_type == "on_failure"
+ and r.r_value == this_dialog) then
+ r_text = r_text..yl_speak_up.print_as_table_effect(
+ r, pname)
+ table.insert(found, {d_id, o_id, r_id,
+ "the previous effect failed"})
+ alternate_dialog = r.r_value
+ alternate_text = "The previous effect failed. "
+ if(r.alternate_text) then
+ alternate_text = alternate_text..
+ "Show alternate text: "..
+ tostring(r.alternate_text)
+ else
+ alternate_text = alternate_text..
+ "Show this dialog here."
+ end
+ end
+ end
+ end
+ -- actions may be relevant
+ if(o.actions) then
+ for a_id, a in pairs(o.actions) do
+ if(a and a.a_on_failure
+ and a.a_on_failure == this_dialog) then
+ p_text = p_text..yl_speak_up.print_as_table_action(
+ a, pname)
+ table.insert(found, {d_id, o_id, a_id,
+ "action failed"})
+ alternate_dialog = a.a_on_failure
+ alternate_text = "The action failed. "
+ if(a.alternate_text) then
+ alternate_text = alternate_text..
+ "Show this alternate text: "..
+ tostring(a.alternate_text)
+ else
+ alternate_text = alternate_text..
+ "Show this dialog here."
+ end
+ end
+ end
+ end
+ yl_speak_up.print_as_table_dialog(p_text, r_text, dialog,
+ dialog.n_id, d_id, o_id,
+ -- sort value: formed by dialog and option id (not perfect but
+ -- good enough)
+ res, o, tostring(d_id).." "..tostring(o_id),
+ alternate_dialog, alternate_text)
+ end
+ end
+ end
+
+ local d_id = this_dialog
+ local formspec = yl_speak_up.print_as_table_prepare_formspec(res, "table_of_dialog_uses",
+ "back_from_show_what_points_here", "Back to dialog \""..tostring(d_id).."\"")
+ table.insert(formspec,
+ "label[20.0,1.8;Dialog \""..minetest.formspec_escape(this_dialog)..
+ "\" is referenced here:]")
+ if(#found ~= 1) then
+ table.insert(formspec,
+ "label[16.0,31.0;"..
+ minetest.formspec_escape("This dialog \""..tostring(d_id)..
+ "\" can be reached from "..
+ minetest.colorize("#FFFF00", tostring(#found))..
+ " options/actions/results.").."]")
+ else
+ table.insert(formspec,
+ "button[0.2,30.6;56.6,1.2;turn_dialog_into_alternate_text;"..
+ minetest.formspec_escape("Turn this dialog \""..
+ tostring(d_id)).."\" into an alternate text.]")
+ end
+ return table.concat(formspec, "\n")
+end
+
+
+yl_speak_up.register_fs("show_what_points_to_this_dialog",
+ yl_speak_up.input_show_what_points_to_this_dialog,
+ yl_speak_up.get_fs_show_what_points_to_this_dialog,
+ -- no special formspec required:
+ nil
+)
diff --git a/fs/fs_talkdialog_edit_mode.lua b/fs/fs_talkdialog_edit_mode.lua
new file mode 100644
index 0000000..4b7645e
--- /dev/null
+++ b/fs/fs_talkdialog_edit_mode.lua
@@ -0,0 +1,753 @@
+
+-- we have a better version of this in the editor - so not offer this particular entry:
+yl_speak_up.get_fs_talkdialog_add_basic_edit = function(
+ pname, formspec, h, pname_for_old_fs, is_a_start_dialog,
+ active_dialog, luaentity, may_edit_npc, anz_options)
+ return {h = h, formspec = formspec}
+end
+
+
+-- This is the main talkdialog the NPC shows when right-clicked. (now in edit_mode)
+local old_input_talk = yl_speak_up.input_talk
+yl_speak_up.input_talk = function(player, formname, fields)
+ local pname = player:get_player_name()
+ -- Is the player working on this particular npc?
+ local edit_mode = yl_speak_up.in_edit_mode(pname)
+ -- if not: do the normal things outside edit mode
+ if(not(edit_mode) and not(fields.button_start_edit_mode)) then
+ return old_input_talk(player, formname, fields)
+ end
+ -- selected option/answer
+ local o = ""
+ -- do all the processing and executing old_input_talk (in non-edit_mode)
+ -- can do - but do not execute any actions; just return o
+ fields.just_return_selected_option = true
+ o = old_input_talk(player, formname, fields)
+ -- old_input_talk handled it (including some error detection like
+ -- wrong formname, not talking to npc, npc not configured)
+
+ -- if in edit mode: detect if something was changed;
+ local result = yl_speak_up.edit_mode_apply_changes(pname, fields)
+ -- o is only nil if the old function returned nil; it does that
+ -- when it found a fitting reaction to a button press
+ if(not(o) and not(fields.button_start_edit_mode)) then
+ return
+ end
+ local n_id = yl_speak_up.speak_to[pname].n_id
+
+ -- start edit mode (requires npc_talk_owner)
+ if fields.button_start_edit_mode then
+ -- check if this particular NPC is really owned by this player or if the player has global privs
+ if(not(yl_speak_up.may_edit_npc(player, n_id))) then
+ minetest.chat_send_player(pname, "Sorry. You do not have the npc_talk_owner or npc_talk_master priv.")
+ return
+ end
+ -- the staff allows to create multiple target dialogs as result; this makes no sense
+ -- and is too disambigous
+ if(yl_speak_up.check_for_disambigous_results(n_id, pname)) then
+ -- this needs to be fixed by someone with a staff; we don't know which dialog is the right
+ -- result
+ return
+ end
+ -- make sure the inventory of the NPC is loaded
+ yl_speak_up.load_npc_inventory(n_id, true, nil)
+ -- for older formspec versions: reset scroll counter
+ yl_speak_up.speak_to[pname].counter = 1
+ yl_speak_up.speak_to[pname].option_index = 1
+ -- enter edit mode with that particular NPC
+ yl_speak_up.edit_mode[pname] = yl_speak_up.speak_to[pname].n_id
+ -- load the NPC dialog anew - but only what the NPC itself has to say, no generic dialogs
+ yl_speak_up.speak_to[pname].dialog = yl_speak_up.load_dialog(n_id, false)
+ -- start a new chat - but this time in edit mode
+ yl_speak_up.speak_to[pname].d_id = nil
+ yl_speak_up.show_fs(player, "talk", {n_id = yl_speak_up.speak_to[pname].n_id, d_id = nil})
+ return
+ -- end edit mode (does not require the priv; will only switch back to normal behaviour)
+ elseif fields.button_end_edit_mode then
+ -- if there are any changes done: ask first and don't end edit mode yet
+ yl_speak_up.show_fs(player, "quit", nil)
+ return
+ end
+
+
+ -- show which dialogs point to this one
+ if(fields.show_what_points_to_this_dialog) then
+ local dialog = yl_speak_up.speak_to[pname].dialog
+ local d_id = yl_speak_up.speak_to[pname].d_id
+ yl_speak_up.show_fs(player, "show_what_points_to_this_dialog",
+ yl_speak_up.speak_to[pname].d_id)
+ return
+ end
+
+ -- the player wants to change name and description; show the formspec
+ if(fields.button_edit_name_and_description) then
+ -- this is not the initial config - but the same formspec can be used
+ yl_speak_up.show_fs(player, "initial_config",
+ {n_id = n_id, d_id = yl_speak_up.speak_to[pname].d_id, false})
+ return
+ end
+
+ -- change skin, cape and wielded items
+ if(fields.edit_skin) then
+ local dialog = yl_speak_up.speak_to[pname].dialog
+ -- necessary so that the fashin formspec can be created
+ yl_speak_up.speak_to[pname].n_npc = dialog.n_npc
+ yl_speak_up.show_fs(player, "fashion")
+ return
+ end
+
+ if(fields.button_save_dialog) then
+ yl_speak_up.show_fs(player, "talk",
+ {n_id = n_id, d_id = yl_speak_up.speak_to[pname].d_id, do_save = true})
+ return
+ end
+
+ if(fields.button_export_dialog) then
+ yl_speak_up.show_fs(player, "export")
+ return
+ end
+
+ if(fields.button_edit_notes) then
+ yl_speak_up.show_fs(player, "notes")
+ return
+ end
+
+ -- the player wants to give something to the NPC
+ -- (more complex in edit mode)
+ if(fields.player_offers_item) then
+ local dialog = yl_speak_up.speak_to[pname].dialog
+ local future_d_id = "d_got_item"
+ -- make sure this dialog exists; create if needed
+ if(not(dialog.n_dialogs[ future_d_id ])) then
+ dialog.n_dialogs[future_d_id] = {
+ d_id = future_d_id,
+ d_type = "text",
+ d_text = "",
+ d_sort = 9999 -- make this the last option
+ }
+ end
+ -- in edit mode: allow to edit the options
+ yl_speak_up.show_fs(player, "talk", {n_id = n_id, d_id = future_d_id})
+ return
+ end
+
+
+ -- button was clicked, now let's execute the results
+ local d_id = yl_speak_up.speak_to[pname].d_id
+ local dialog = yl_speak_up.speak_to[pname].dialog
+
+ -- all three buttons (pre(C)onditions, (Ef)fects, edit option) lead to the same new formspec
+ local n_dialog = dialog.n_dialogs[d_id]
+
+ if(n_dialog and n_dialog.d_options) then
+ for o_id,v in pairs(n_dialog.d_options) do
+ if( fields["edit_option_"..o_id]
+ or fields["conditions_"..o_id]
+ or fields["actions_"..o_id]
+ or fields["quests_"..o_id]
+ or fields["effects_"..o_id]) then
+ -- store which option we want to edit
+ yl_speak_up.speak_to[pname].o_id = o_id
+ -- if something was changed: ask for confirmation
+ yl_speak_up.show_fs(player, "edit_option_dialog",
+ {n_id = yl_speak_up.speak_to[pname].n_id,
+ d_id = d_id, o_id = o_id, caller="button"})
+ return
+ end
+ end
+ end
+
+ -- we may soon need actions and o_results from the selected_option
+ local selected_option = {}
+ if(yl_speak_up.check_if_dialog_has_option(dialog, d_id, o)) then
+ selected_option = dialog.n_dialogs[d_id].d_options[o]
+ end
+
+ -- translate a dialog name into a d_id
+ fields.d_id = yl_speak_up.d_name_to_d_id(dialog, fields.d_id)
+ -- in edit mode: has another dialog been selected?
+ -- if nothing better can be found: keep the old dialog
+ local show_dialog = d_id
+ -- an option button was selected;
+ -- since we do not execute actions and effects in edit mode, we need to find out the
+ -- right target dialog manually (and assume all went correct)
+ if( o ~= "" ) then
+ -- find out the normal target dialog of this option
+ if(selected_option and selected_option.o_results) then
+ for k, v in pairs(selected_option.o_results) do
+ if(v and v.r_type == "dialog") then
+ show_dialog = v.r_value
+ end
+ end
+ end
+ -- dropdown menu was used; provided the dialog exists (and it's not the "New dialog" option)
+ -- (if a new dialog was added using the "+" button, fields.d_id gets set accordingly)
+ elseif(fields.d_id and fields.d_id ~= show_dialog and dialog.n_dialogs[fields.d_id]) then
+ show_dialog = fields.d_id
+ -- in edit mode: prev_dialog_../next_dialog_.. was selected
+ else
+ for k,v in pairs(dialog.n_dialogs) do
+ if(fields["prev_dialog_"..k]) then
+ show_dialog = k
+ elseif(fields["next_dialog_"..k]) then
+ show_dialog = k
+ end
+ end
+ end
+
+ yl_speak_up.show_fs(player, "talk", {n_id = n_id, d_id = show_dialog})
+ -- no option was selected - so we need to end this here
+ return
+end
+
+
+-- in edit mode, *all* options are displayed
+local old_calculate_displayable_options = yl_speak_up.calculate_displayable_options
+yl_speak_up.calculate_displayable_options = function(pname, d_options, allow_recursion)
+ if(yl_speak_up.in_edit_mode(pname)) then
+ -- no options - nothing to do
+ if(not(d_options)) then
+ return {}
+ end
+ -- in edit mode: show all options (but sort them first)
+ local retval = {}
+ local sorted_list = yl_speak_up.get_sorted_options(d_options, "o_sort")
+ for i, o_k in ipairs(sorted_list) do
+ retval[o_k] = true
+ end
+ return retval
+ end
+ -- outside edit mode: really calculate what can be displayed
+ return old_calculate_displayable_options(pname, d_options, allow_recursion)
+end
+
+
+-- in edit mode, autoanswer, random dialogs and d_got_item play no role and are *not* applied
+-- (we want to see and edit all options regardless of preconditions)
+local old_apply_autoanswer_etc = yl_speak_up.apply_autoanswer_and_random_and_d_got_item
+yl_speak_up.apply_autoanswer_and_random_and_d_got_item = function(player, pname, d_id, dialog, allowed, active_dialog, recursion_depth)
+ -- no automatic switching in edit_mode
+ if(yl_speak_up.in_edit_mode(pname)) then
+ return
+ end
+ return old_apply_autoanswer_etc(player, pname, d_id, dialog, allowed, active_dialog, recursion_depth)
+end
+
+
+-- helper function for yl_speak_up.get_fs_talkdialog:
+-- shows the text the NPC "speaks" and adds edit and navigation buttons
+-- (all only in *edit_mode*)
+local old_talkdialog_main_text = yl_speak_up.get_fs_talkdialog_main_text
+yl_speak_up.get_fs_talkdialog_main_text = function(pname, formspec, h, dialog, dialog_list, c_d_id, active_dialog, alternate_text)
+ if(not(yl_speak_up.in_edit_mode(pname))) then
+ return old_talkdialog_main_text(pname, formspec, h, dialog, dialog_list, c_d_id, active_dialog, alternate_text)
+ end
+ local d_id_to_dropdown_index = {}
+ -- allow to change skin, wielded items etc.
+ table.insert(formspec, "button[15.75,3.5;3.5,0.9;edit_skin;Edit Skin]")
+
+ if(not(dialog) or not(dialog.n_dialogs)) then
+ return {h = h, formspec = formspec, d_id_to_dropdown_index = {}, dialog_list = dialog_list}
+ end
+
+ -- display the window with the text the NPC is saying
+ -- sort all dialogs by d_sort
+ local sorted_list = yl_speak_up.get_sorted_options(dialog.n_dialogs, "d_sort")
+ -- add buttons for previous/next dialog
+ for i, d in ipairs(sorted_list) do
+ local d_name = dialog.n_dialogs[d].d_name or d
+ -- build the list of available dialogs for the dropdown list(s)
+ dialog_list = dialog_list..","..minetest.formspec_escape(d_name)
+ if(d == c_d_id) then
+ local prev_dialog = tostring(minetest.formspec_escape(sorted_list[i-1]))
+ yl_speak_up.add_formspec_element_with_tooltip_if(formspec,
+ "button", "15.4,5.0;2,0.9", "prev_dialog_"..prev_dialog,
+ "<",
+ "Go to previous dialog "..prev_dialog..".",
+ (sorted_list[ i-1 ]))
+ local next_dialog = tostring(minetest.formspec_escape(sorted_list[i+1]))
+ yl_speak_up.add_formspec_element_with_tooltip_if(formspec,
+ "button", "17.6,5.0;2,0.9", "next_dialog_"..next_dialog,
+ ">",
+ "Go to next dialog "..next_dialog..".",
+ (sorted_list[ i+1 ]))
+ end
+ d_id_to_dropdown_index[d] = i + 1
+ end
+ dialog_list = dialog_list..",d_end"
+ d_id_to_dropdown_index["d_end"] = #sorted_list + 2
+
+
+ if(not(yl_speak_up.is_special_dialog(c_d_id))) then
+ table.insert(formspec, "label[0.2,4.2;Dialog ")
+ table.insert(formspec, minetest.formspec_escape(c_d_id))
+ table.insert(formspec, ":]")
+ table.insert(formspec, "field[5.0,3.6;9.8,1.2;d_name;;")
+ table.insert(formspec, minetest.formspec_escape(dialog.n_dialogs[c_d_id].d_name or c_d_id))
+ table.insert(formspec, "]")
+ table.insert(formspec, "tooltip[d_name;Dialogs can have a *name* that is diffrent from\n"..
+ "their ID (which is i.e. d_4). The name will be shown\n"..
+ "in the dropdown list. Save a new name by clicking on\n"..
+ "the dialog \"Save\" button.]")
+ end
+
+ table.insert(formspec, "label[0.2,5.5;Dialog:]") -- "..minetest.formspec_escape(c_d_id)..":]")
+ table.insert(formspec, "dropdown[5.0,5.0;9.8,1;d_id;"..dialog_list..";"..
+ (d_id_to_dropdown_index[c_d_id] or "1")..",]")
+ table.insert(formspec, "tooltip[5.0,5.0;9.8,1;"..
+ "Select the dialog you want to edit. Currently, dialog "..c_d_id..
+ " is beeing displayed.;#FFFFFF;#000000]")
+
+ yl_speak_up.add_formspec_element_with_tooltip_if(formspec,
+ "button", "3.5,5.0;1,0.9", "show_new_dialog",
+ "+",
+ "Create a new dialog.",
+ true)
+ yl_speak_up.add_formspec_element_with_tooltip_if(formspec,
+ "button", "11.0,0.3;2,1.0", "button_edit_notes",
+ "Notes",
+ "Keep notes of what this NPC is for, how his character is etc.",
+ true)
+ yl_speak_up.add_formspec_element_with_tooltip_if(formspec,
+ "button", "13.2,0.3;2,0.9", "button_edit_name_and_description",
+ "Edit",
+ "Edit name and description of your NPC.",
+ true)
+ yl_speak_up.add_formspec_element_with_tooltip_if(formspec,
+ "button", "15.4,0.3;2,0.9", "button_save_dialog",
+ "Save",
+ "Save this dialog.",
+ true)
+ yl_speak_up.add_formspec_element_with_tooltip_if(formspec,
+ "button", "17.5,0.3;2.4,0.9", "button_export_dialog",
+ "Export",
+ "Export: Show the dialog in .json format which you can copy and store on your computer.",
+ true)
+
+
+ local tmp = "[0.2,6;19.6,16.8;d_text;"
+ -- static help text instead of text input field for d_got_item
+ if(c_d_id == "d_got_item") then
+ table.insert(formspec, "hypertext"..tmp..
+ "Note:\nThis is a special dialog. "..
+ "It will be called when the player clicks on "..
+ "I want to give you something."..
+ "\nMost of the things listed below will be added automaticly when you add a "..
+ "new option to this dialog. In most cases you may just have to edit the "..
+ "precondition so that the right item is accepted, and then "..
+ "set the target dialog according to your needs. Please also "..
+ "edit the alternate text so that it fits your item!"..
+ "\nThis is how it works in detail:"..
+ "\nEach option you add here ought to deal with one item(stack) that "..
+ "the NPC expects from the player, i.e. farming:bread 2. "..
+ "Each option needs to be selected automaticly and ought to contain:"..
+ "\n* a precondition regarding "..
+ "an item the player offered/gave to the NPC "..
+ "(shown as player_offered_item in overview) "..
+ "where you define which item(stack) is relevant for this option"..
+ "\n* an effect regarding an item the player offered to the NPC "..
+ "(shown as deal_with_offered_item in overview) "..
+ "where you define what shall happen to the offered item. Usually "..
+ "the NPC will accept the item and put it into its inventory."..
+ "\n* Don't forget to set a suitable target dialog for the effect! "..
+ "Your NPC ought to comment on what he got, i.e. "..
+ "Thank you for those two breads! You saved me from starving."..
+ "You can also work with an alternate text here (as is done in the "..
+ "default setup when adding a new option here)."..
+ "\n]")
+ -- static help text instead of text input field for d_trade
+ elseif(c_d_id == "d_trade") then
+ table.insert(formspec, "hypertext"..tmp..
+ "Note:\nThis is a special dialog. "..
+ "It will be called when the player clicks on "..
+ "Let's trade!."..
+ "\nSome of the things listed below will be added automaticly when you add a "..
+ "new option to this dialog. In most cases you may just have to edit the "..
+ "precondition so that the right item(stack) is beeing "..
+ "searched for, and you need to add suitable effects. The ones added "..
+ "automaticly are just an example."..
+ "\nNote that once the NPC found a matching precondition, it will execute the "..
+ "relevant effects and present the player the trade list. Any further options "..
+ "that might also fit will not be executed this time. Only one option "..
+ "(or none) will be selected each time."..
+ "\nThis is how it works in detail:"..
+ "\nEach option you add here ought to deal with one item(stack) that "..
+ "the NPC might or might not have in its inventory, "..
+ "i.e. default:stick 4. "..
+ "Each option needs to be selected automaticly and ought to contain:"..
+ "\n* at least one precondition regarding "..
+ "the inventory of the NPC "..
+ "where you define which item(stack) is relevant for this option "..
+ "(you can add multiple such preconditions for each option)"..
+ "\n* at least one effect regarding what the NPC shall do if the "..
+ "precondition matches. In most cases, NPC crafts something, "..
+ "put item from the NPC's inventory into a chest etc. or "..
+ "take item from a chest etc. and put it into the NPC's inventory "..
+ "will be what you are looking for. More than one effect is possible."..
+ "\n* In this particular case, no target dialog needs to be selected. After "..
+ "executing the effect(s), the trade list view will be shown to the "..
+ "player."..
+ "\n]")
+ elseif(c_d_id == "d_dynamic") then
+ table.insert(formspec, "hypertext"..tmp..
+ "Note:\nThis is a special dialog. "..
+ "Each time right before this special dialog is displayed, a "..
+ "function is called that can fill the d_dynamic dialog "..
+ "with text and options."..
+ "\nThat function has to decide based on NPC, player and context what "..
+ "it wants to display this time."..
+ "\nThe d_dynamic dialog is never saved as part of the dialog. "..
+ "It has to be dynamicly created by your function each time it is needed."..
+ "\nThe d_dynamic dialog will always be available as a legitimate target "..
+ "dialog of a dialog option. Its options can do all that which "..
+ "options of other dialogs can do. Its options can also lead back to "..
+ "normal static parts of the dialog."..
+ "\n]")
+ elseif(active_dialog and active_dialog.d_text) then
+ table.insert(formspec, "textarea"..tmp..";"..
+ minetest.formspec_escape(active_dialog.d_text or "?")..
+ "]")
+ else
+ table.insert(formspec, "textarea"..tmp..";"..
+ minetest.formspec_escape("[no text]")..
+ "]")
+ end
+ return {h = h, formspec = formspec, d_id_to_dropdown_index = d_id_to_dropdown_index,
+ dialog_list = dialog_list}
+end
+
+
+-- helper function for yl_speak_up.get_fs_talkdialog:
+-- prints one entry (option/answer) in normal and *edit_mode*
+local old_talkdialog_line = yl_speak_up.get_fs_talkdialog_line
+yl_speak_up.get_fs_talkdialog_line = function(
+ formspec, h, pname_for_old_fs, oid, sb_v,
+ dialog, allowed, pname,
+ -- these additional parameters are needed *here*, in edit_mode:
+ active_dialog, dialog_list, d_id_to_dropdown_index,
+ current_index, anz_options)
+
+ if(not(yl_speak_up.in_edit_mode(pname))) then
+ -- in normal mode:
+ return old_talkdialog_line(formspec, h, pname_for_old_fs, oid, sb_v,
+ dialog, allowed, pname,
+ active_dialog, dialog_list, d_id_to_dropdown_index,
+ current_index, anz_options)
+ end
+
+ -- in edit mode:
+ local offset = 0.0
+ local field_length = 43.8
+ if(pname_for_old_fs) then
+ offset = 0.7
+ field_length = 41.8
+ end
+ h = h + 1
+ -- add a button "o_:" that leads to an edit formspec for this option
+ yl_speak_up.add_formspec_element_with_tooltip_if(formspec,
+ "button", tostring(2.9+offset).."," .. h .. ";2,0.9", "edit_option_" .. oid,
+ oid,
+ "Edit target dialog, pre(C)onditions and (Ef)fects for option "..oid..".",
+ true)
+ -- find the right target dialog for this option (if it exists):
+ local target_dialog = nil
+ local results = active_dialog.d_options[sb_v.o_id].o_results
+ -- has this option more results/effects than just switching to another dialog?
+ local has_other_results = false
+ if(results ~= nil) then
+ for k, v in pairs(results) do
+ if v.r_type == "dialog"
+ and (dialog.n_dialogs[v.r_value] ~= nil
+ or yl_speak_up.is_special_dialog(v.r_value)) then
+ -- there may be more than one in the data structure
+ target_dialog = v.r_value
+ elseif v.r_type ~= "dialog" then
+ has_other_results = true
+ end
+ end
+ end
+ -- add a button "-> d_" that leads to the target dialog (if one is set)
+ -- selecting an option this way MUST NOT execute the pre(C)onditions or (Ef)fects!
+ local arrow = "->"
+ local only_once = ""
+ if(sb_v.o_visit_only_once and sb_v.o_visit_only_once == 1) then
+ arrow = "*"
+ only_once = "\nNote: This option can be selected only *once* per talk!"
+ end
+ yl_speak_up.add_formspec_element_with_tooltip_if(formspec,
+ "button", tostring(12.6+offset)..","..h..";1,0.9", "button_" .. oid,
+ arrow,
+ "Go to target dialog "..minetest.formspec_escape(target_dialog or "")..
+ " that will be shown when this option ("..oid..") is selected."..
+ only_once,
+ (target_dialog))
+
+ -- allow to set a new target dialog
+ table.insert(formspec, "dropdown["..tostring(5.0+offset)..","..h..";7.7,1;d_id_"..
+ oid..";"..
+ dialog_list..";"..
+ (d_id_to_dropdown_index[(target_dialog or "?")] or "0")..",]")
+ -- add a tooltip "Change target dialog"
+ table.insert(formspec, "tooltip[5.0,"..h..";4.7,1;"..
+ "Change target dialog for option "..oid..".;#FFFFFF;#000000]")
+
+ -- are there any prerequirements?
+ local prereq = active_dialog.d_options[sb_v.o_id].o_prerequisites
+ yl_speak_up.add_formspec_element_with_tooltip_if(formspec,
+ "button", tostring(0.5+offset)..","..h..";0.5,0.9", "conditions_"..oid,
+ "C",
+ "There are pre(C)onditions required for showing this option. Display them.",
+ (prereq and next(prereq)))
+
+ yl_speak_up.add_formspec_element_with_tooltip_if(formspec,
+ "button", tostring(1.6+offset)..","..h..";0.6,0.9", "effects_"..oid,
+ "Ef",
+ "There are further (Ef)fects (apart from switching\n"..
+ "to a new dialog) set for this option. Display them.",
+ (has_other_results))
+
+ local d_option = active_dialog.d_options[sb_v.o_id]
+ yl_speak_up.add_formspec_element_with_tooltip_if(formspec,
+ "button", tostring(2.25+offset)..","..h..";0.6,0.9", "quests_"..oid,
+ "Q",
+ "This option sets a (Q)est step if possible.\n"..
+ "A special precondition is evaluated automaticly\n"..
+ "to check if the quest step can be set.",
+ (d_option and d_option.quest_id and d_option.quest_step))
+
+ -- are there any actions defined?
+ local actions = active_dialog.d_options[sb_v.o_id].actions
+ yl_speak_up.add_formspec_element_with_tooltip_if(formspec,
+ "button", tostring(1.05+offset)..","..h..";0.5,0.9", "actions_"..oid,
+ "A",
+ "There is an (A)ction (i.e. a trade) that will happen\n"..
+ "when switching to a new dialog. Display actions and\n"..
+ "trade of this option.",
+ (actions and next(actions)))
+
+ if(sb_v.o_autoanswer) then
+ table.insert(formspec,
+ "label["..tostring(13.5+offset+0.2)..","..tostring(h+0.5)..";"..
+ minetest.formspec_escape("[Automaticly selected if preconditions are met] "..
+ tostring(sb_v.o_text_when_prerequisites_met))..
+ "]")
+ elseif(active_dialog.o_random) then
+ table.insert(formspec,
+ "label["..tostring(13.5+offset+0.2)..","..tostring(h+0.5)..";"..
+ minetest.formspec_escape("[One of these options is randomly selected] "..
+ tostring(sb_v.o_text_when_prerequisites_met))..
+ "]")
+ else
+ -- show the actual text for the option
+ yl_speak_up.add_formspec_element_with_tooltip_if(formspec,
+ "field", tostring(13.5+offset)..","..h..";"..
+ tostring(field_length-5.3)..",0.9",
+ "text_option_" .. oid,
+ ";"..minetest.formspec_escape(sb_v.o_text_when_prerequisites_met),
+ "Edit the text that is displayed on button "..oid..".",
+ true)
+ end
+ yl_speak_up.add_formspec_element_with_tooltip_if(formspec,
+ "button", tostring(10.5+offset+field_length-2.2)..","..h..";1.0,0.9", "delete_option_"..oid,
+ "Del",
+ "Delete this option/answer.",
+ true)
+
+ yl_speak_up.add_formspec_element_with_tooltip_if(formspec,
+-- "image_button", tostring(9.9+offset+field_length-0.5)..","..h..";0.5,0.9"..
+-- ";gui_furnace_arrow_bg.png^[transformR180",
+ "button", tostring(10.5+offset+field_length-1.1)..","..h..";0.5,0.9",
+ "option_move_down_"..oid,
+ "v",
+ "Move this option/answer one down.",
+ (current_index < anz_options))
+ yl_speak_up.add_formspec_element_with_tooltip_if(formspec,
+-- "image_button", tostring(9.9+offset+field_length-1.0)..","..h..";0.5,0.9"..
+-- ";gui_furnace_arrow_bg.png",
+ "button", tostring(10.5+offset+field_length-0.5)..","..h..";0.5,0.9",
+ "option_move_up_"..oid,
+ "^",
+ "Move this option/answer one up.",
+ (current_index > 1))
+ return {h = h, formspec = formspec}
+end
+
+
+-- add a prefix to "I want to give you something." dialog option in edit_mode:
+local old_talkdialog_offers_item = yl_speak_up.get_fs_talkdialog_add_player_offers_item
+yl_speak_up.get_fs_talkdialog_add_player_offers_item = function(pname, formspec, h, dialog, add_text, pname_for_old_fs)
+ local offer_item_add_text = nil
+ if(yl_speak_up.in_edit_mode(pname)) then
+ -- force showing the "I want to give you something"-text
+ offer_item_add_text = minetest.formspec_escape("[dialog d_got_item] -> ")
+ end
+ return old_talkdialog_offers_item(pname, formspec, h, dialog, offer_item_add_text, pname_for_old_fs)
+end
+
+
+-- helper function for yl_speak_up.get_fs_talkdialog:
+-- if the player can edit the NPC,
+-- either add a button for entering edit mode
+-- or add the buttons needed to edit the dialog when in *edit mode*
+local old_talkdialog_add_edit_and_command_buttons = yl_speak_up.get_fs_talkdialog_add_edit_and_command_buttons
+yl_speak_up.get_fs_talkdialog_add_edit_and_command_buttons = function(
+ pname, formspec, h, pname_for_old_fs, is_a_start_dialog,
+ active_dialog, luaentity, may_edit_npc, anz_options)
+ -- add the buttons that are added to all editable NPC *first*:
+ -- inventory access, commands for mobs_npc, custom commands
+ local res = old_talkdialog_add_edit_and_command_buttons(
+ pname, formspec, h, pname_for_old_fs, is_a_start_dialog,
+ active_dialog, luaentity, may_edit_npc, anz_options)
+ formspec = res.formspec
+ h = res.h
+ -- if the player cannot *enter* edit_mode:
+ if(not(may_edit_npc)) then
+ return res
+ end
+ local edit_mode = yl_speak_up.in_edit_mode(pname)
+ -- button "show log" for those who can edit the NPC (entering edit mode is not required)
+ local text = minetest.formspec_escape(
+ "[Log] Show me your log.")
+ h = yl_speak_up.add_edit_button_fs_talkdialog(formspec, h,
+ "show_log",
+ text, text,
+ true, nil, nil, pname_for_old_fs)
+ -- Offer to enter edit mode if the player has the npc_talk_owner priv OR is allowed to edit the NPC.
+ -- The npc_talk_master priv allows to edit all NPC.
+ if(not(edit_mode)) then
+ h = yl_speak_up.add_edit_button_fs_talkdialog(formspec, h,
+ "button_start_edit_mode",
+ "Enters edit mode. In this mode, you can edit the texts the NPC says and the "..
+ "answers that can be given.",
+ -- chat option: "I am your owner. I have new orders for you.
+ "I am your owner. I have new orders for you.",
+ true, nil, true, pname_for_old_fs) -- is button_exit
+ return {h = h, formspec = formspec}
+ end
+
+ local offset = 0.0
+ -- If in edit mode, add new menu entries: "add new options", "end edit mode" and what else is needed.
+ h = yl_speak_up.add_edit_button_fs_talkdialog(formspec, h,
+ "add_option",
+ -- chat option: "Add a new answer/option to this dialog."
+ "Adds a new option to this dialog. You can delete options via the option edit menu.",
+ "Add a new option/answer to this dialog. You can delete options via the option "..
+ "edit menu.",
+ -- the amount of allowed options/answers has been reached
+ (anz_options < yl_speak_up.max_number_of_options_per_dialog),
+ "Maximum number of allowed answers/options reached. No further options/answers "..
+ "can be added.", nil, pname_for_old_fs)
+
+ h = yl_speak_up.add_edit_button_fs_talkdialog(formspec, h,
+ "delete_this_empty_dialog",
+ -- chat option: "Delete this dialog."
+ "Dialogs can only be deleted when they are empty and have no more "..
+ "options/answers. This is the case here, so the dialog can be deleted.",
+ "Delete this empty dialog.",
+ (active_dialog and active_dialog.d_text == "" and anz_options == 0),
+ -- (but only show this option if the dialog is empty)
+ "If you want to delete this dialog, you need to delete all options and its "..
+ "text first.", nil, pname_for_old_fs)
+
+ h = yl_speak_up.add_edit_button_fs_talkdialog(formspec, h,
+ "show_what_points_to_this_dialog",
+ -- chat option: "Show what points to this dialog."
+ "Show which other dialog options or failed actions\n"..
+ "or effects lead the player to this dialog here.",
+ "Show what points to this dialog.",
+ -- there is no alternate text to show
+ true, nil, nil, pname_for_old_fs)
+
+ h = yl_speak_up.add_edit_button_fs_talkdialog(formspec, h,
+ "make_first_option",
+ -- chat option: "Make this dialog the first one shown when starting to talk."
+ "The NPC has to start with one dialog when he is right-clicked. "..
+ "Make this dialog the one shown.",
+ "Make this dialog the first one shown when starting a conversation.",
+ (active_dialog and active_dialog.d_sort and tonumber(active_dialog.d_sort) ~= 0),
+ -- (but only show this option if it's not already the first one)
+ "This dialog will be shown whenever a conversation is started.", nil,pname_for_old_fs)
+
+ local b_text = "Turn this into"
+ if(is_a_start_dialog) then
+ b_text = "This shall no longer be"
+ end
+ h = yl_speak_up.add_edit_button_fs_talkdialog(formspec, h,
+ "turn_into_a_start_dialog",
+ "With automatic selection of options, it is possible that the real\n"..
+ "start dialog will never be shown to the player. However, we need\n"..
+ "to add some buttons to that start dialog for i.e. giving items\n"..
+ "to the NPC and for trading. Therefore, dialogs can be marked as\n"..
+ "*a* start dialog so that these buttons will be added to those dialogs.",
+ b_text.." *a* start dialog where buttons for trade etc. are shown.",
+ not(active_dialog and active_dialog.d_sort and tonumber(active_dialog.d_sort) == 0),
+ "The start dialog automaticly counts as *a* start dialog where buttons for "..
+ "trade etc. are shown.", nil, pname_for_old_fs)
+
+ -- chat option: Mute/Unmute NPC
+ h = yl_speak_up.add_edit_button_fs_talkdialog(formspec, h,
+ "mute_npc",
+ -- chat option: mute the NPC
+ "The NPC will no longer show his dialogs when he is right-clicked. This is "..
+ "useful while you edit the NPC and don't want players to see "..
+ "unfinished entries and/or quests.",
+ "State: Not muted. Stop talking to other players while I give you new orders.",
+ (luaentity and luaentity.yl_speak_up.talk), nil, nil, pname_for_old_fs)
+ h = yl_speak_up.add_edit_button_fs_talkdialog(formspec, h,
+ "un_mute_npc",
+ -- unmute the NPC
+ "The NPC will show his dialogs to other players when he is right-clicked. "..
+ "This is the normal mode of operation. Choose this when you are "..
+ "finished editing.",
+ "State: You are currently muted. Talk to anyone again who wants to talk to you.",
+ -- the NPC has to be there
+ (luaentity and not(luaentity.yl_speak_up.talk)), nil, nil, pname_for_old_fs)
+
+
+ h = yl_speak_up.add_edit_button_fs_talkdialog(formspec, h,
+ "button_end_edit_mode",
+ "Ends edit mode. From now on, your NPC will talk to you like he talks to other "..
+ "players. You can always give him new orders by entering edit mode again.",
+ -- chat option:"That was all. I'm finished with giving you new orders. Remember them!"
+ "That was all. I'm finished with giving you new orders. Remember them!",
+ true, nil, true, pname_for_old_fs) -- is button_exit
+ return {h = h, formspec = formspec}
+end
+
+
+-- apply force_edit_mode if necessary
+local old_get_fs_talkdialog = yl_speak_up.get_fs_talkdialog
+yl_speak_up.get_fs_talkdialog = function(player, n_id, d_id, alternate_text, recursion_depth)
+ if(not(player)) then
+ return
+ end
+ local pname = player:get_player_name()
+ -- are we in force edit mode, and can the player edit this NPC?
+ if(yl_speak_up.force_edit_mode[pname]
+ -- not already in edit mode?
+ and (not(yl_speak_up.edit_mode[pname]) or yl_speak_up.edit_mode[pname] ~= n_id)
+ and yl_speak_up.may_edit_npc(player, n_id)) then
+ yl_speak_up.edit_mode[pname] = n_id
+ end
+ return old_get_fs_talkdialog(player, n_id, d_id, alternate_text, recursion_depth)
+end
+
+--[[
+yl_speak_up.get_fs_talk_wrapper = function(player, param)
+ if(not(param)) then
+ param = {}
+ end
+ -- recursion depth from autoanswer: 0 (the player selected manually)
+ return yl_speak_up.get_fs_talkdialog(player, param.n_id, param.d_id, param.alternate_text,0)
+end
+--]]
+
+yl_speak_up.register_fs("talk",
+ -- this function is changed here:
+ yl_speak_up.input_talk,
+ -- the underlying function is changed as well - but the wrapper calls that already; so ok:
+ yl_speak_up.get_fs_talk_wrapper,
+ -- no special formspec required:
+ nil
+)
+
diff --git a/print_as_table.lua b/print_as_table.lua
new file mode 100644
index 0000000..66ecdc7
--- /dev/null
+++ b/print_as_table.lua
@@ -0,0 +1,124 @@
+-- helper function
+yl_speak_up.wrap_long_lines_for_table = function(text, prefix, line_length, max_lines)
+ -- show newlines as <\n> in order to save space
+ local text = (text or "?")
+ text = string.gsub(text, "\n", minetest.formspec_escape("
"))
+ -- break the text up into lines of length x
+ local parts = minetest.wrap_text(text, line_length, true)
+ if(not(parts) or #parts < 2) then
+ return minetest.formspec_escape(text)
+ end
+ local show_parts = {}
+ -- only show the first two lines (we don't have infinite room)
+ for i, p in ipairs(parts) do
+ if(i <= max_lines) then
+ table.insert(show_parts, minetest.formspec_escape(p))
+ end
+ end
+ if(#parts > max_lines) then
+ return table.concat(show_parts, prefix)..minetest.formspec_escape(" [...]")
+ end
+ return table.concat(show_parts, prefix)
+end
+
+
+-- helper functions for yl_speak_up.fs_get_list_of_usage_of_variable
+-- and yl_speak_up.show_what_points_to_this_dialog
+yl_speak_up.print_as_table_precon = function(p, pname)
+ return ",#FFFF00,"..
+ minetest.formspec_escape(tostring(p.p_id))..
+ ",#FFFF00,pre(C)ondition,#FFFF00,"..
+ minetest.formspec_escape(p.p_type)..",#FFFF00,"..
+ minetest.formspec_escape(yl_speak_up.show_precondition(p, pname))
+end
+
+
+yl_speak_up.print_as_table_effect = function(r, pname)
+ return ",#55FF55,"..
+ minetest.formspec_escape(tostring(r.r_id))..
+ ",#55FF55,(Ef)fect,#55FF55,"..
+ minetest.formspec_escape(r.r_type)..",#55FF55,"..
+ minetest.formspec_escape(yl_speak_up.show_effect(r, pname))
+end
+
+
+yl_speak_up.print_as_table_action = function(a, pname)
+ return ",#FF9900,"..
+ minetest.formspec_escape(tostring(a.a_id))..
+ ",#FF9900,(A)ction,#FF9900,"..
+ minetest.formspec_escape(a.a_type)..",#FF9900,"..
+ -- these lines can get pretty long when a description for a quest item is set
+ yl_speak_up.wrap_long_lines_for_table(
+ yl_speak_up.show_action(a, pname),
+ ",#FFFFFF,,#FFFFFF,,#FFFFFF,,#FF9900,",
+ 80, 4)
+end
+
+
+yl_speak_up.print_as_table_dialog = function(p_text, r_text, dialog, n_id, d_id, o_id, res, o, sort_value,
+ alternate_dialog, alternate_text)
+ if(p_text == "" and r_text == "" ) then
+ return
+ end
+ local d_text = yl_speak_up.wrap_long_lines_for_table(
+ dialog.n_dialogs[ d_id ].d_text or "?",
+ ",#FFFFFF,,#FFFFFF,,#FFFFFF,,#BBBBFF,",
+ 80, 3)
+ if(not(alternate_dialog) or not(alternate_text)) then
+ alternate_text = ""
+ else
+ alternate_text = ",#BBBBFF,"..minetest.formspec_escape(tostring(alternate_dialog))..
+ -- show alternate text in a diffrent color
+ ",#BBBBFF,Dialog,#BBBBFF,says next:,#FFBBBB,"..
+ yl_speak_up.wrap_long_lines_for_table(
+ alternate_text,
+ ",#FFFFFF,,#FFFFFF,,#FFFFFF,,#FFBBBB,",
+ 80, 3)
+ end
+ res[ tostring(n_id).." "..tostring(d_id).." "..tostring(o_id) ] = {
+ text = "#6666FF,"..
+ tostring(n_id)..",#6666FF,NPC,#6666FF,named:,#6666FF,"..
+ minetest.formspec_escape(dialog.n_npc or "?")..","..
+ "#BBBBFF,"..
+ tostring(d_id)..",#BBBBFF,Dialog,#BBBBFF,says:,#BBBBFF,"..
+ d_text..","..
+ "#FFFFFF,"..
+ tostring(o_id)..",#FFFFFF,Option,#FFFFFF,A:,#FFFFFF,"..
+ minetest.formspec_escape(tostring(o.o_text_when_prerequisites_met or "?"))..
+ p_text..r_text..
+ alternate_text,
+ sort_value = sort_value}
+end
+
+
+yl_speak_up.print_as_table_prepare_formspec = function(res, table_name, back_button_name, back_button_text,
+ is_already_sorted, concat_with, table_columns)
+ local sorted_res = {}
+ -- this is the default for "show where a variable is used"
+ if(not(is_already_sorted)) then
+ local sorted_list = yl_speak_up.get_sorted_options(res, "sort_value")
+ for i, k in pairs(sorted_list) do
+ table.insert(sorted_res, res[ k ].text)
+ end
+ table_columns = "color,span=1;text;color,span=1;text;color,span=1;text;color,span=1;text"
+ else
+ sorted_res = res
+ end
+ if(not(concat_with)) then
+ -- insert blank lines between lines belonging together
+ concat_with = ",#FFFFFF,,#FFFFFF,,#FFFFFF,,#FFFFFF,,"
+ end
+ local formspec = {
+ "size[57,33]",
+ -- back to the list with that one precondition or effect
+ "button[0.2,0.2;56.6,1.2;"..back_button_name..";"..
+ minetest.formspec_escape(back_button_text).."]",
+ "button[0.2,31.6;56.6,1.2;"..back_button_name..";"..
+ minetest.formspec_escape(back_button_text).."]",
+ "tablecolumns["..tostring(table_columns).."]",
+ }
+ table.insert(formspec,
+ "table[1.2,2.4;55.0,28.0;"..tostring(table_name)..";"..
+ table.concat(sorted_res, concat_with).."]")
+ return formspec
+end
diff --git a/show_fs_in_edit_mode.lua b/show_fs_in_edit_mode.lua
new file mode 100644
index 0000000..5a53c7f
--- /dev/null
+++ b/show_fs_in_edit_mode.lua
@@ -0,0 +1,98 @@
+
+-- when in edit mode: ask for saving dialogs when needed
+local old_show_fs = yl_speak_up.show_fs
+yl_speak_up.show_fs = function(player, fs_name, param)
+ if(not(player)) then
+ return
+ end
+ local pname = player:get_player_name()
+ if(not(yl_speak_up.speak_to[pname])) then
+ return
+ end
+
+ local last_fs = yl_speak_up.speak_to[pname].last_fs
+ -- show the save or discard changes dialog
+ if(fs_name and fs_name == "save_or_discard_changes") then
+ yl_speak_up.show_fs_ver(pname, "yl_speak_up:save_or_discard_changes",
+ yl_speak_up.get_fs_save_or_discard_changes(player, param))
+ return
+
+ -- the player either saved or discarded; we may proceed now
+ elseif(fs_name and fs_name == "proceed_after_save") then
+ fs_name = yl_speak_up.speak_to[pname].next_fs
+ param = yl_speak_up.speak_to[pname].next_fs_param
+ yl_speak_up.speak_to[pname].next_fs = nil
+ yl_speak_up.speak_to[pname].next_fs_param = nil
+ yl_speak_up.speak_to[pname].last_fs = fs_name
+ yl_speak_up.speak_to[pname].last_fs_param = param
+ if(not(fs_name) or fs_name == "quit") then
+ yl_speak_up.reset_vars_for_player(pname, false)
+ return
+ end
+
+ -- the player clicked on "back" in the above dialog
+ elseif(fs_name and fs_name == "show_last_fs") then
+ -- call the last formspec again - and with the same parameters
+ fs_name = yl_speak_up.speak_to[pname].last_fs
+ param = yl_speak_up.speak_to[pname].last_fs_param
+
+ -- do we need to check if there is something that needs saving?
+ elseif(fs_name
+ -- msg is just a loop for displaying (mostly error) messages
+ and fs_name ~= "msg"
+ and fs_name ~= "player_offers_item"
+ -- is the player editing the NPC? that is: might there be any changes?
+ and (yl_speak_up.edit_mode[pname] == yl_speak_up.speak_to[pname].n_id)) then
+ local last_fs = yl_speak_up.speak_to[pname].last_fs
+ local d_id = yl_speak_up.speak_to[pname].d_id
+ local o_id = yl_speak_up.speak_to[pname].o_id
+ -- only these two formspecs need to ask specificly if the data ought to be saved
+ if(last_fs == "talk" or last_fs == "edit_option_dialog" or fs_name == "quit") then
+ local last_param = yl_speak_up.speak_to[pname].last_fs_param
+ local show_save_fs = false
+ if(not(param)) then
+ param = {}
+ end
+ -- set the target dialog
+ yl_speak_up.speak_to[pname].target_dialog = param.d_id
+ -- if we are switching from one dialog to another: is it the same?
+ if(last_fs == "talk" and fs_name == last_fs
+ and param and param.d_id and param.d_id ~= d_id) then
+ -- diffrent parameters: save (if needed)
+ show_save_fs = true
+ elseif(fs_name == "talk" and param and param.do_save) then
+ -- player clicked on save button
+ show_save_fs = true
+ -- leaving a dialog: save!
+ elseif(last_fs == "talk" and fs_name ~= last_fs) then
+ show_save_fs = true
+ -- clicking on "save" in an edit option dialog: save!
+ elseif(last_fs == "edit_option_dialog" and fs_name == last_fs
+ and param and param.caller and param.caller == "save_option") then
+ show_save_fs = true
+ -- leaving editing an option: save!
+ elseif(last_fs == "edit_option_dialog" and fs_name ~= last_fs) then
+ show_save_fs = true
+ -- quitting: save!
+ elseif(fs_name == "quit") then
+ yl_speak_up.speak_to[pname].target_dialog = nil
+ show_save_fs = true
+ end
+ -- show the save or discard dialog
+ if(show_save_fs) then
+ yl_speak_up.speak_to[pname].next_fs = fs_name
+ yl_speak_up.speak_to[pname].next_fs_param = param
+ -- check first if it's necessary to ask for save or discard
+ yl_speak_up.input_save_or_discard_changes(player, "", {})
+ return
+ end
+ end
+ -- store the new formspec
+ yl_speak_up.speak_to[pname].last_fs = fs_name
+ -- and its parameter
+ yl_speak_up.speak_to[pname].last_fs_param = param
+ end
+
+ -- Note: fs_name and param *may* have been changed in edit_mode by the code above
+ old_show_fs(player, fs_name, param)
+end
diff --git a/trade_in_edit_mode.lua b/trade_in_edit_mode.lua
new file mode 100644
index 0000000..35d5a6f
--- /dev/null
+++ b/trade_in_edit_mode.lua
@@ -0,0 +1,70 @@
+-- overrides for api/api_trade_inv.lua:
+
+-- the player *can* place something into the npc_gives inventory list in edit_mode:
+local old_trade_inv_allow_put = yl_speak_up.trade_inv_allow_put
+yl_speak_up.trade_inv_allow_put = function(inv, listname, index, stack, player)
+ if(not(player)) then
+ return 0
+ end
+ -- allow putting something in in edit mode - but not otherwise
+ if(listname and listname == "npc_gives") then
+ local pname = player:get_player_name()
+ local n_id = yl_speak_up.speak_to[pname].n_id
+ -- only in edit mode! else the NPC manages this slot
+ if(n_id and yl_speak_up.in_edit_mode(pname)) then
+ return stack:get_count()
+ end
+ end
+ return old_trade_inv_allow_put(inv, listname, index, stack, player)
+end
+
+
+-- prevent do_trade_simple from executing trade and reporting successful action:
+local old_do_trade_simple = yl_speak_up.do_trade_simple
+yl_speak_up.do_trade_simple = function(player, count)
+ if(not(player)) then
+ return
+ end
+
+ local pname = player:get_player_name()
+ -- which trade are we talking about?
+ local trade = yl_speak_up.trade[pname]
+
+ if(trade.n_id and yl_speak_up.edit_mode[pname] == trade.n_id) then
+ -- instruct old_do_trade_simple to neither execute the trade nor see this
+ -- as an action that was executed
+ trade.dry_run_no_exec = true
+ end
+ return old_do_trade_simple(player, count)
+end
+
+
+
+-- overrides for api/api_trade.lua:
+
+-- do not allow deleting trades that are actions of an option if not in edit mode:
+local old_delete_trade_simple = yl_speak_up.delete_trade_simple
+yl_speak_up.delete_trade_simple = function(player, trade_id)
+ local pname = player:get_player_name()
+ local n_id = yl_speak_up.speak_to[pname].n_id
+ -- get the necessary dialog data
+ local dialog = yl_speak_up.speak_to[pname].dialog
+ if(dialog and dialog.trades and trade_id
+ and dialog.trades[ trade_id ] and n_id) then
+
+ if( dialog.trades[ trade_id ].d_id
+ and yl_speak_up.edit_mode[pname] ~= n_id) then
+ yl_speak_up.show_fs(player, "msg", {
+ input_to = "yl_speak_up:do_trade_simple",
+ formspec = "size[6,2]"..
+ "label[0.2,-0.2;"..
+ "Trades that are attached to dialog options\n"..
+ "can only be deleted in edit mode. Please tell\n"..
+ "your NPC that you are its owner and have\n"..
+ "new commands!]"..
+ "button[2,1.5;1,0.9;back_from_error_msg;Back]"})
+ return
+ end
+ end
+ return old_delete_trade_simple(player, trade_id)
+end