From 4c24424102d9faac6658ce05527c745b7af3ee00 Mon Sep 17 00:00:00 2001 From: Sokomine Date: Fri, 22 Mar 2024 22:25:24 +0100 Subject: [PATCH] moved files from yl_speak_up/editor/ to this mod --- api/api_alternate_text.lua | 434 ++++ api/api_quest_steps.lua | 270 +++ api/formspec_helpers.lua | 318 +++ api/fs_edit_general.lua | 2554 +++++++++++++++++++++ chat_commands_in_edit_mode.lua | 28 + command_force_edit_mode.lua | 24 + edit_mode.lua | 144 ++ edit_mode_apply_changes.lua | 570 +++++ exec_actions_action_inv_changed.lua | 62 + exec_all_relevant_effects.lua | 9 + fs/fs_add_quest_steps.lua | 763 ++++++ fs/fs_add_trade_simple_in_edit_mode.lua | 31 + fs/fs_assign_quest_step.lua | 343 +++ fs/fs_do_trade_simple_in_edit_mode.lua | 54 + fs/fs_edit_actions.lua | 133 ++ fs/fs_edit_effects.lua | 352 +++ fs/fs_edit_options_dialog.lua | 853 +++++++ fs/fs_edit_preconditions.lua | 377 +++ fs/fs_fashion.lua | 266 +++ fs/fs_fashion_extended.lua | 205 ++ fs/fs_get_list_of_usage_of_variable.lua | 93 + fs/fs_initial_config_in_edit_mode.lua | 202 ++ fs/fs_manage_quest_steps.lua | 364 +++ fs/fs_manage_quests.lua | 248 ++ fs/fs_manage_variables.lua | 483 ++++ fs/fs_notes.lua | 66 + fs/fs_properties.lua | 139 ++ fs/fs_quest_gui.lua | 41 + fs/fs_save_or_discard_or_back.lua | 147 ++ fs/fs_show_all_var_values.lua | 64 + fs/fs_show_what_points_to_this_dialog.lua | 256 +++ fs/fs_talkdialog_edit_mode.lua | 753 ++++++ print_as_table.lua | 124 + show_fs_in_edit_mode.lua | 98 + trade_in_edit_mode.lua | 70 + 35 files changed, 10938 insertions(+) create mode 100644 api/api_alternate_text.lua create mode 100644 api/api_quest_steps.lua create mode 100644 api/formspec_helpers.lua create mode 100644 api/fs_edit_general.lua create mode 100644 chat_commands_in_edit_mode.lua create mode 100644 command_force_edit_mode.lua create mode 100644 edit_mode.lua create mode 100644 edit_mode_apply_changes.lua create mode 100644 exec_actions_action_inv_changed.lua create mode 100644 exec_all_relevant_effects.lua create mode 100644 fs/fs_add_quest_steps.lua create mode 100644 fs/fs_add_trade_simple_in_edit_mode.lua create mode 100644 fs/fs_assign_quest_step.lua create mode 100644 fs/fs_do_trade_simple_in_edit_mode.lua create mode 100644 fs/fs_edit_actions.lua create mode 100644 fs/fs_edit_effects.lua create mode 100644 fs/fs_edit_options_dialog.lua create mode 100644 fs/fs_edit_preconditions.lua create mode 100644 fs/fs_fashion.lua create mode 100644 fs/fs_fashion_extended.lua create mode 100644 fs/fs_get_list_of_usage_of_variable.lua create mode 100644 fs/fs_initial_config_in_edit_mode.lua create mode 100644 fs/fs_manage_quest_steps.lua create mode 100644 fs/fs_manage_quests.lua create mode 100644 fs/fs_manage_variables.lua create mode 100644 fs/fs_notes.lua create mode 100644 fs/fs_properties.lua create mode 100644 fs/fs_quest_gui.lua create mode 100644 fs/fs_save_or_discard_or_back.lua create mode 100644 fs/fs_show_all_var_values.lua create mode 100644 fs/fs_show_what_points_to_this_dialog.lua create mode 100644 fs/fs_talkdialog_edit_mode.lua create mode 100644 print_as_table.lua create mode 100644 show_fs_in_edit_mode.lua create mode 100644 trade_in_edit_mode.lua 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