From 0c2f9f0b49c01f9883ae5e48eeb031cfce4ce1ac Mon Sep 17 00:00:00 2001 From: Sokomine Date: Wed, 5 Jan 2022 21:57:02 +0100 Subject: [PATCH] put exec_actions.lua into extra file --- exec_actions.lua | 657 +++++++++++++++++++++++++++++++++++++++++++ fs_edit_actions.lua | 665 +------------------------------------------- init.lua | 1 + 3 files changed, 662 insertions(+), 661 deletions(-) create mode 100644 exec_actions.lua diff --git a/exec_actions.lua b/exec_actions.lua new file mode 100644 index 0000000..1d7f9b0 --- /dev/null +++ b/exec_actions.lua @@ -0,0 +1,657 @@ +-- This file contains what is necessary to execute an action. + +-- 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() + 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 + 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 + + +-- actions - in contrast to preconditions and effects - may take time +-- because the player usually gets presented a formspec and needs to +-- react to that; thus, we can't just execute all actions simultaneously +yl_speak_up.execute_next_action = function(player, a_id, result_of_a_id) + 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 + local dialog = yl_speak_up.speak_to[pname].dialog + yl_speak_up.debug_msg(player, n_id, o_id, "Last action: "..tostring(a_id).." returned ".. + tostring(result_of_a_id)..".") + local actions = {} + local effects = {} + local sorted_key_list = {} + if(dialog + and dialog.n_dialogs + and dialog.n_dialogs[d_id] + and dialog.n_dialogs[d_id].d_options + and dialog.n_dialogs[d_id].d_options[o_id]) then + -- get the actual actions + actions = dialog.n_dialogs[d_id].d_options[o_id].actions + -- needed later on when all actions are executed + effects = dialog.n_dialogs[d_id].d_options[o_id].o_results + end + if(actions) then + -- sort the actions so that we can execute them always in the + -- same order + sorted_key_list = yl_speak_up.sort_keys(actions) + local nr = 0 + if(not(a_id)) then + -- check if the action(s) can be executed + local time_now = yl_speak_up.get_time_in_seconds() + -- is there a limiton how many failed attempts there can be per time? + 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(timer_data + and timer_data["max_attempts"] and tonumber(timer_data["max_attempts"]) > 0 + and timer_data["duration"] and tonumber(timer_data["duration"]) > 0) then + local new_times = "" + local times = yl_speak_up.get_quest_variable_value(pname, timer_name) + local parts = string.split(times or "", " ") + local count = 0 + for i, p in ipairs(parts) do + if(p and tonumber(p) + and (tonumber(p) + tonumber(timer_data["duration"])>time_now)) then + new_times = new_times.." "..p + count = count + 1 + end + end + -- all timers are expired + if(count == 0) then + yl_speak_up.set_quest_variable_value(pname, timer_name, nil) + -- some timers are expired + elseif(new_times ~= times) then + yl_speak_up.set_quest_variable_value(pname, timer_name, new_times) + end + if(count >= tonumber(timer_data["max_attempts"])) then + -- show the same dialog again, but with the failure message + yl_speak_up.show_fs(player, "talk", {n_id = n_id, d_id = d_id, + alternate_text = timer_data[ "alternate_text" ] + or yl_speak_up.standard_text_if_action_failed_too_often}) + return + end + end + -- is there a limiton how fast the action may be repeated again? + timer_name = "timer_on_success_"..tostring(d_id).."_"..tostring(o_id) + timer_data = yl_speak_up.get_variable_metadata(timer_name, "parameter", true) + if(timer_data + and timer_data["duration"] and tonumber(timer_data["duration"]) > 0) then + local last_time = yl_speak_up.get_quest_variable_value(pname, timer_name) + if(last_time and tonumber(last_time) + and tonumber(last_time) + tonumber(timer_data["duration"]) > time_now) then + -- show the same dialog again, but with the failure message + yl_speak_up.show_fs(player, "talk", {n_id = n_id, d_id = d_id, + alternate_text = timer_data[ "alternate_text" ] + or yl_speak_up.standard_text_if_action_repeated_too_soon}) + return + else + -- the timer has expired + yl_speak_up.set_quest_variable_value(pname, timer_name, nil) + end + end + + else -- if(a_id) then + nr = table.indexof(sorted_key_list, a_id) + -- did the player go back? + if(nr > -1 and result_of_a_id == nil) then + -- set the current action to nil + yl_speak_up.speak_to[pname].a_id = nil + -- no option of the new dialog has been selected yet + yl_speak_up.speak_to[pname].o_id = nil + -- show the new dialog + yl_speak_up.debug_msg(player, n_id, o_id, "Action ".. + tostring(a_id).." aborted. Switching back to dialog ".. + tostring(d_id)..".") + yl_speak_up.speak_to[pname].o_id = nil + yl_speak_up.speak_to[pname].a_id = nil + yl_speak_up.show_fs(player, "talk", {n_id = n_id, d_id = d_id}) + return + -- did the action fail? + elseif(nr > -1 and not(result_of_a_id)) then + -- is there a limiton how many failed attempts there can be per time? + 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) + -- store that (another?) attempt to do the action failed + if(timer_data + and timer_data["max_attempts"] and tonumber(timer_data["max_attempts"])> 0 + and timer_data["duration"] and tonumber(timer_data["duration"])> 0) then + local times = yl_speak_up.get_quest_variable_value(pname, timer_name) + if(not(times)) then + times = "" + end + -- variables are stored as strings, not as lists + yl_speak_up.set_quest_variable_value(pname, timer_name, + times.." "..yl_speak_up.get_time_in_seconds()) + end + + local this_action = actions[ sorted_key_list[ nr ]] + -- if there is an on_failure target dialog: go there + if(this_action.a_on_failure) then + -- go back to the same dialog + if(not(dialog.n_dialogs[this_action.a_on_failure])) then + this_action.a_on_failure = d_id + end + -- set the current action to nil + yl_speak_up.speak_to[pname].a_id = nil + -- no option of the new dialog has been selected yet + yl_speak_up.speak_to[pname].o_id = nil + -- show the new dialog + yl_speak_up.debug_msg(player, n_id, o_id, "Action ".. + tostring(a_id).." failed. Switching to dialog ".. + tostring(this_action.a_on_failure)..".") + yl_speak_up.speak_to[pname].d_id = this_action.a_on_failure + yl_speak_up.speak_to[pname].o_id = nil + yl_speak_up.speak_to[pname].a_id = nil + yl_speak_up.show_fs(player, "talk", {n_id = n_id, + d_id = this_action.a_on_failure, + alternate_text = this_action.alternate_text}) + return + else + yl_speak_up.debug_msg(player, n_id, o_id, "Action ".. + tostring(a_id).." failed, but no a_on_failure target ".. + "dialog defined. Continuing execution.") + end + end + end + -- get the next entry + if(nr > -1 and nr < #sorted_key_list and sorted_key_list[nr + 1]) then + local next_action = actions[ sorted_key_list[ nr + 1 ]] + -- store which action we are currently executing + yl_speak_up.speak_to[pname].a_id = next_action.a_id + -- execute the next action + yl_speak_up.debug_msg(player, n_id, o_id, "Executing next action ".. + tostring(next_action.a_id)..".") + yl_speak_up.execute_action(player, n_id, o_id, next_action) + -- the player needs time to react + return + end + end + -- when all actions are executed: + -- is there a limiton how fast the action may be repeated again? + 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) + -- store that the action was executed successfully + if(timer_data + and timer_data["duration"] and tonumber(timer_data["duration"]) > 0) then + yl_speak_up.set_quest_variable_value(pname, timer_name, yl_speak_up.get_time_in_seconds()) + end + -- set the current action to nil + yl_speak_up.speak_to[pname].a_id = nil + yl_speak_up.debug_msg(player, n_id, o_id, "All actions have been executed successfully. ".. + "Doing effects/results now.") + -- execute all effects/results + local res = yl_speak_up.execute_all_relevant_effects(player, effects, o_id, true) + local target_dialog = res.next_dialog + yl_speak_up.speak_to[pname].o_id = nil + yl_speak_up.speak_to[pname].a_id = nil + if(not(target_dialog) + or target_dialog == "" + or not(dialog.n_dialogs[target_dialog])) then + target_dialog = d_id + end + -- the function above returns a target dialog; show that to the player + yl_speak_up.show_fs(player, "talk", {n_id = n_id, d_id = target_dialog, + alternate_text = res.alternate_text}) +end + + +yl_speak_up.execute_action = function(player, n_id, o_id, a) + if(not(a.a_type) or a.a_type == "" or a.a_type == "none") then + -- no action - nothing to do + return true + elseif(a.a_type == "trade") then + yl_speak_up.show_fs(player, "trade_simple", a.a_value) + return + elseif(a.a_type == "npc_gives") then + yl_speak_up.show_fs(player, "action_npc_gives", a.a_value) + return + elseif(a.a_type == "npc_wants") then + yl_speak_up.show_fs(player, "action_npc_wants", a.a_value) + return + elseif(a.a_type == "text_input") then + -- start with an empty answer + yl_speak_up.show_fs(player, "action_text_input", "") + return + elseif(a.a_type == "custom") then + yl_speak_up.show_fs(player, "action_custom", a.a_value) + return + end + -- fallback: unkown type + return false +end + + +-- helper function; +-- returns the action the player is currently faced with (or nil if none) +yl_speak_up.get_action_by_player = function(player) + 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 + local d_id = yl_speak_up.speak_to[pname].d_id + local o_id = yl_speak_up.speak_to[pname].o_id + local a_id = yl_speak_up.speak_to[pname].a_id + if(not(dialog) or not(d_id) or not(o_id) or not(a_id) + 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]) + or not(dialog.n_dialogs[d_id].d_options[o_id].actions) + or not(dialog.n_dialogs[d_id].d_options[o_id].actions[a_id])) then + return nil + end + return dialog.n_dialogs[d_id].d_options[o_id].actions[a_id] +end + + +-- Create the quest item by taking a raw item (i.e. a general piece of paper) out +-- of the NPC's inventory, applying a description (if given) and quest id (if +-- given); place the quest item in the trade inv of the player in the npc_gives slot. +-- The npc_gives inv is managed mostly by the NPC, except when in edit mode. We can +-- just overwrite anything old in there. +-- Returns false if the creation of the quest item wasn't possible (i.e. the +-- NPC had no paper left). +yl_speak_up.action_quest_item_prepare = function(player) + -- which action are we talking about? + local a = yl_speak_up.get_action_by_player(player) + if(not(a) or not(a.a_id) or not(a.a_value)) then + return false + end + local pname = player:get_player_name() + local n_id = yl_speak_up.speak_to[pname].n_id + -- what shall the NPC give? + local stack = ItemStack(a.a_value) + -- get the inventory of the NPC + local npc_inv = minetest.get_inventory({type="detached", name="yl_speak_up_npc_"..tostring(n_id)}) + -- does the NPC have the item we are looking for? + if(not(npc_inv:contains_item("npc_main", stack))) then + local o_id = yl_speak_up.speak_to[pname].o_id + yl_speak_up.debug_msg(player, n_id, o_id, "Action "..tostring(a.a_id)..": NPC ran out of ".. + tostring(a.a_value)..".") + -- just go back; the player didn't do anything wrong + return nil + end + -- get the items from the NPCs inventory + local new_stack = npc_inv:remove_item("npc_main", stack) + local meta = new_stack:get_meta() + -- if given: set the item stack description + if(a.a_item_desc and a.a_item_desc ~= "") then + local dialog = yl_speak_up.speak_to[pname].dialog + -- replace $PLAYER_NAME$ etc. in quest item description + meta:set_string("description", yl_speak_up.replace_vars_in_text(a.a_item_desc, dialog, pname)) + end + if(a.a_item_quest_id and a.a_item_quest_id ~= "") 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(a.a_item_quest_id)) + end + -- put the stack in the npc_gives-slot of the trade inventory of the player + -- (as that slot is managed by the NPC alone we don't have to worry about + -- anything else in the slot) + local trade_inv = minetest.get_inventory({type="detached", name="yl_speak_up_player_"..pname}) + -- actually put the stack in there + trade_inv:set_stack("npc_gives", 1, new_stack) + return true +end + + +-- check if the item in the npc_gives slot is the one the NPC wants +yl_speak_up.action_quest_item_check = function(player) + -- which action are we talking about? + local a = yl_speak_up.get_action_by_player(player) + if(not(a) or not(a.a_id) or not(a.a_value)) then + return false + end + local pname = player:get_player_name() + local n_id = yl_speak_up.speak_to[pname].n_id + local o_id = yl_speak_up.speak_to[pname].o_id + -- get the item that needs to be checked + local trade_inv = minetest.get_inventory({type="detached", name="yl_speak_up_player_"..pname}) + local stack = trade_inv:get_stack("npc_wants", 1) + -- nothing there? + if(stack:is_empty()) then + yl_speak_up.debug_msg(player, n_id, o_id, "Action "..tostring(a.a_id)..": No item found.") + return false + end + local cmp = tostring(stack:get_name()).." "..(stack:get_count()) + -- wrong item or wrong amount? + if(cmp ~= a.a_value) then + yl_speak_up.debug_msg(player, n_id, o_id, "Action "..tostring(a.a_id).. + ": Wrong item given. Got: "..stack:to_string().. + " Expected: "..tostring(a.a_value)..".") + return false + end + local meta = stack:get_meta() + -- the description is not checked; just the quest id (if given) + if(a.a_item_quest_id and a.a_item_quest_id ~= "") then + -- we don't check here if the item was given by the right NPC; + -- only the quest id has to fit + if(meta:get_string("yl_speak_up:quest_id") ~= a.a_item_quest_id) then + yl_speak_up.debug_msg(player, n_id, o_id, "Action "..tostring(a.a_id).. + ": Wrong quest item (wrong ID).") + return false + end + -- was this quest item given to another player? + if(meta:get_string("yl_speak_up:quest_item_for") ~= pname) then + yl_speak_up.debug_msg(player, n_id, o_id, "Action "..tostring(a.a_id).. + ": Quest item was given to ".. + tostring(meta:get_string("yl_speak_up:quest_item_for")).. + ", but "..tostring(pname).." gave it.") + return false + end + end + yl_speak_up.debug_msg(player, n_id, o_id, "Action "..tostring(a.a_id).. + ": Quest item checked ok.") + return true +end + + +-- strip the quest information from the item and give it back to the NPC; +-- returns the modified stack (but also places it in the NPC's inventory) +yl_speak_up.action_quest_item_take_back = function(player) + -- which action are we talking about? + local a = yl_speak_up.get_action_by_player(player) + if(not(a) or not(a.a_id) or not(a.a_value)) then + return false + end + local pname = player:get_player_name() + local n_id = yl_speak_up.speak_to[pname].n_id + -- get the item that the NPC shall take back (or accept in npc_wants) + local trade_inv = minetest.get_inventory({type="detached", name="yl_speak_up_player_"..pname}) + local stack = trade_inv:get_stack("npc_wants", 1) + -- if it was the wrong item: + if(not(yl_speak_up.action_quest_item_check(player))) then + local player_inv = player:get_inventory() + -- give the item back to the player + local remaining = player_inv:add_item("main", stack) + -- very unlikely - but in case the item did not fit back into the player's inv: + if(remaining and not(remaining:is_empty())) then + local p = player:get_pos() + -- throw it at the player + minetest.add_item({x=p.x, y=p.y+1, z=p.z}, stack) + end + -- remove it from the trade inv slot + trade_inv:set_stack("npc_wants", 1, ItemStack()) + return false + end + -- we already checked that it is the correct item + local meta = stack:get_meta() + -- if given: set the item stack description + if(a.a_item_desc and a.a_item_desc ~= "") then + meta:set_string("description", "") + end + -- delete all the special IDs that where added before + if(a.a_item_quest_id and a.a_item_quest_id ~= "") then + meta:set_string("yl_speak_up:quest_item_for", "") + meta:set_string("yl_speak_up:quest_item_from", "") + meta:set_string("yl_speak_up:quest_id", "") + end + local npc_inv = minetest.get_inventory({type="detached", name="yl_speak_up_npc_"..tostring(n_id)}) + -- Has the NPC room enough for the item? + -- If the NPC doesn't have room, the item will be destroyed in the next step by setting + -- npc_wants to an empty stack. While this may lead to some item loss, it is more important + -- that the quest item was properly accepted (and discarded of) rather than worrying about + -- where to put it or even giving it back and letting the quest fail. + if(npc_inv:room_for_item("npc_main", stack)) then + npc_inv:add_item("npc_main", stack) + -- save the inventory of the NPC + yl_speak_up.save_npc_inventory(n_id) + end + -- the NPC has accepted the item + trade_inv:set_stack("npc_wants", 1, ItemStack()) + return true +end + + +-- show the diffrent action-related formspecs and handle input to them +-- (Note: trade is handled in trade_simple.lua) + +yl_speak_up.input_fs_action_npc_gives = function(player, formname, fields) + -- back from error_msg? then show the formspec again + if(fields.back_from_error_msg) then + -- do not create a new item + yl_speak_up.show_fs(player, "action_npc_gives", nil) + return + end + local pname = player:get_player_name() + local trade_inv = minetest.get_inventory({type="detached", name="yl_speak_up_player_"..pname}) + local a_id = yl_speak_up.speak_to[pname].a_id + if(fields.npc_does_not_have_item) then + -- the NPC can't supply the item - abort the action + yl_speak_up.execute_next_action(player, a_id, nil) + return + end + -- is the npc_gives inv empty? then all went as expected. + -- (it does not really matter which button the player pressed in this case) + if(trade_inv:is_empty("npc_gives")) then + -- the NPC has given the item to the player; save the NPCs inventory + local n_id = yl_speak_up.speak_to[pname].n_id + yl_speak_up.save_npc_inventory(n_id) + -- the action was a success; the NPC managed to give the item to the player + yl_speak_up.execute_next_action(player, a_id, true) + return + end + -- the npc_gives slot does not accept input - so we don't have to check for any misplaced items + -- but if the player aborts, give the item back to the NPC + if(fields.back_to_talk) then + -- strip the quest item info from the stack (so that it may stack again) + -- and give that (hopefully) stackable stack back to the NPC + yl_speak_up.action_quest_item_take_back(player) + -- the action failed + yl_speak_up.execute_next_action(player, a_id, nil) + return + end + -- else show a message to the player that he ought to take the item + yl_speak_up.show_fs(player, "msg", { + input_to = "yl_speak_up:action_npc_gives", + formspec = "size[7,1.5]".. + "label[0.2,-0.2;".. + "Please take the offered item and click on \"Done\"!\n".. + "If you can't take it, click on \"Back to talk\".]".. + "button[2,1.0;1.5,0.9;back_from_error_msg;Back]"}) +end + + +yl_speak_up.get_fs_action_npc_gives = function(player, param) + -- called for the first time; create the item the NPC wants to give + if(param) then + if(not(yl_speak_up.action_quest_item_prepare(player))) then + local pname = player:get_player_name() + local dialog = yl_speak_up.speak_to[pname].dialog + -- it's not the fault of the player that the NPC doesn't have the item; + -- so tell him that (the action will still fail) + return "size[7,2.0]".. + "label[0.2,-0.2;".. + minetest.formspec_escape(dialog.n_npc or "- ? -").. + " is very sorry:\n".. + "The item intended for you is currently unavailable.\n".. + "Please come back later!]".. + "button[2,1.5;1.5,0.9;npc_does_not_have_item;Back]" + end + end + local pname = player:get_player_name() + local dialog = yl_speak_up.speak_to[pname].dialog + return "size[8.5,8]".. + "list[current_player;main;0.2,3.85;8,1;]".. + "list[current_player;main;0.2,5.08;8,3;8]".. + "button[0.2,0.0;2.0,0.9;back_to_talk;Back to talk]".. + "button[4.75,1.6;1.5,0.9;finished_action;Done]".. + + "tooltip[back_to_talk;Click here if you don't want to (or can't)\n".. + "take the offered item.]".. + "tooltip[finished_action;Click here once you have taken the item and\n".. + "stored it in your inventory.]".. + "label[1.5,0.7;"..minetest.formspec_escape(dialog.n_npc or "- ? -").. + " offers to you:]".. + -- unlike the npc_gives slot - which is used for setting up the NPC - the + -- npc_gives slot does not allow putting something in + "list[detached:yl_speak_up_player_"..pname..";npc_gives;3.25,1.5;1,1;]" .. + "label[1.5,2.7;Take the offered item and click on \"Done\" to proceed.]" +end + + +yl_speak_up.input_fs_action_npc_wants = function(player, formname, fields) + -- back from error_msg? then show the formspec again + if(fields.back_from_error_msg) then + yl_speak_up.show_fs(player, "action_npc_wants", nil) + return + end + local pname = player:get_player_name() + local trade_inv = minetest.get_inventory({type="detached", name="yl_speak_up_player_"..pname}) + local a_id = yl_speak_up.speak_to[pname].a_id + -- is the npc_wants inv empty and the player pressed the back to talk button? then the action failed. + if(trade_inv:is_empty("npc_wants") and fields.back_to_talk) then + -- the action was aborted + yl_speak_up.execute_next_action(player, a_id, nil) + return + end + -- the player tried to give something; check if it is the right thing + if(not(trade_inv:is_empty("npc_wants"))) then + local stack = trade_inv:get_stack("npc_wants", 1) + -- check if it really is the item the NPC wanted; let the NPC take it + local is_correct_item = yl_speak_up.action_quest_item_take_back(player) + -- the action may have been a success or failure + yl_speak_up.execute_next_action(player, a_id, is_correct_item) + return + end + -- else show a message to the player + yl_speak_up.show_fs(player, "msg", { + input_to = "yl_speak_up:action_npc_wants", + formspec = "size[7,1.5]".. + "label[0.2,-0.2;".. + "Please insert the item for the npc and click on \"Done\"!\n".. + "If you don't have what he wants, click on \"Back to talk\".]".. + "button[2,1.0;1.5,0.9;back_from_error_msg;Back]"}) +end + + +yl_speak_up.get_fs_action_npc_wants = function(player, param) + local pname = player:get_player_name() + local dialog = yl_speak_up.speak_to[pname].dialog + return "size[8.5,8]".. + "list[current_player;main;0.2,3.85;8,1;]".. + "list[current_player;main;0.2,5.08;8,3;8]".. + "button[0.2,0.0;2.0,0.9;back_to_talk;Back to talk]".. + "button[4.75,1.6;1.5,0.9;finished_action;Done]".. + + "tooltip[back_to_talk;Click here if you don't know what item the\n".. + "NPC wants or don't have the desired item.]".. + "tooltip[finished_action;Click here once you have placed the item in\n".. + "the waiting slot.]".. + "label[1.5,0.7;"..minetest.formspec_escape(dialog.n_npc or "- ? -").. + " expects something from you:]".. + "list[detached:yl_speak_up_player_"..pname..";npc_wants;3.25,1.5;1,1;]" .. + "label[1.5,2.7;Insert the right item and click on \"Done\" to proceed.]" +end + + +yl_speak_up.input_fs_action_text_input = function(player, formname, fields) + -- back from error_msg? then show the formspec again + if(fields.back_from_error_msg) then + -- the error message is only shown if the input was empty + yl_speak_up.show_fs(player, "action_text_input", "") + return + end + local pname = player:get_player_name() + local a_id = yl_speak_up.speak_to[pname].a_id + local a = yl_speak_up.get_action_by_player(player) + if(fields.back_to_talk) then + -- the action was aborted + yl_speak_up.execute_next_action(player, a_id, nil) + return + end + if(fields.finished_action and fields.quest_answer and fields.quest_answer ~= "") then + -- is the answer correct? + -- strip leading and tailing blanks + local success = not(not(fields.quest_answer and a.a_value + and fields.quest_answer:trim() == a.a_value:trim())) + -- the action was a either a success or failure + yl_speak_up.execute_next_action(player, a_id, success) + return + end + -- else show a message to the player + yl_speak_up.show_fs(player, "msg", { + input_to = "yl_speak_up:action_text_input", + formspec = "size[7,1.5]".. + "label[0.2,-0.2;".. + "Please answer the question and click on \"Send answer\"!\n".. + "If you don't know the answer, click on \"Back to talk\".]".. + "button[2,1.0;1.5,0.9;back_from_error_msg;Back]"}) +end + + +yl_speak_up.get_fs_action_text_input = function(player, param) + local pname = player:get_player_name() + local dialog = yl_speak_up.speak_to[pname].dialog + local a = yl_speak_up.get_action_by_player(player) + if(not(a)) then + return "" + end + return "size[12.0,4.5]".. + "button[0.2,0.0;2.0,0.9;back_to_talk;Back to talk]".. + "button[2.0,3.7;3.0,0.9;finished_action;Send answer]".. + + "tooltip[back_to_talk;Click here if you don't know the answer.]".. + "tooltip[finished_action;Click here once you've entered the answer.]".. + "label[0.2,1.2;"..minetest.formspec_escape(a.a_question or "Your answer:").."]".. + "label[0.2,1.9;Answer:]".. + "field[1.6,2.2;10.0,0.6;quest_answer;;"..tostring(param or "").."]".. + "label[0.2,2.8;"..minetest.formspec_escape( + "["..(dialog.n_npc or "- ? -").." looks expectantly at you.]").."]" +end diff --git a/fs_edit_actions.lua b/fs_edit_actions.lua index 3ba801a..889ff20 100644 --- a/fs_edit_actions.lua +++ b/fs_edit_actions.lua @@ -1,4 +1,5 @@ - +-- 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 @@ -29,8 +30,8 @@ -- call a custom formspec ("custom"): -- a_value parameter for the custom function -- --- a general, more complex formspec-basted puzzle ("puzzle"): not supported yet --- TODO: implement "puzzle" type? +-- 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 @@ -56,65 +57,6 @@ local check_what = { local values_what = {"", "none", "trade", "npc_gives", "npc_wants", "text_input", "custom", "puzzle"} --- 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() - 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 - 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 - - -- 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) @@ -143,605 +85,6 @@ yl_speak_up.show_action = function(a) return tostring(a.a_value) end - --- actions - in contrast to preconditions and effects - may take time --- because the player usually gets presented a formspec and needs to --- react to that; thus, we can't just execute all actions simultaneously -yl_speak_up.execute_next_action = function(player, a_id, result_of_a_id) - 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 - local dialog = yl_speak_up.speak_to[pname].dialog - yl_speak_up.debug_msg(player, n_id, o_id, "Last action: "..tostring(a_id).." returned ".. - tostring(result_of_a_id)..".") - local actions = {} - local effects = {} - local sorted_key_list = {} - if(dialog - and dialog.n_dialogs - and dialog.n_dialogs[d_id] - and dialog.n_dialogs[d_id].d_options - and dialog.n_dialogs[d_id].d_options[o_id]) then - -- get the actual actions - actions = dialog.n_dialogs[d_id].d_options[o_id].actions - -- needed later on when all actions are executed - effects = dialog.n_dialogs[d_id].d_options[o_id].o_results - end - if(actions) then - -- sort the actions so that we can execute them always in the - -- same order - sorted_key_list = yl_speak_up.sort_keys(actions) - local nr = 0 - if(not(a_id)) then - -- check if the action(s) can be executed - local time_now = yl_speak_up.get_time_in_seconds() - -- is there a limiton how many failed attempts there can be per time? - 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(timer_data - and timer_data["max_attempts"] and tonumber(timer_data["max_attempts"]) > 0 - and timer_data["duration"] and tonumber(timer_data["duration"]) > 0) then - local new_times = "" - local times = yl_speak_up.get_quest_variable_value(pname, timer_name) - local parts = string.split(times or "", " ") - local count = 0 - for i, p in ipairs(parts) do - if(p and tonumber(p) - and (tonumber(p) + tonumber(timer_data["duration"])>time_now)) then - new_times = new_times.." "..p - count = count + 1 - end - end - -- all timers are expired - if(count == 0) then - yl_speak_up.set_quest_variable_value(pname, timer_name, nil) - -- some timers are expired - elseif(new_times ~= times) then - yl_speak_up.set_quest_variable_value(pname, timer_name, new_times) - end - if(count >= tonumber(timer_data["max_attempts"])) then - -- show the same dialog again, but with the failure message - yl_speak_up.show_fs(player, "talk", {n_id = n_id, d_id = d_id, - alternate_text = timer_data[ "alternate_text" ] - or yl_speak_up.standard_text_if_action_failed_too_often}) - return - end - end - -- is there a limiton how fast the action may be repeated again? - timer_name = "timer_on_success_"..tostring(d_id).."_"..tostring(o_id) - timer_data = yl_speak_up.get_variable_metadata(timer_name, "parameter", true) - if(timer_data - and timer_data["duration"] and tonumber(timer_data["duration"]) > 0) then - local last_time = yl_speak_up.get_quest_variable_value(pname, timer_name) - if(last_time and tonumber(last_time) - and tonumber(last_time) + tonumber(timer_data["duration"]) > time_now) then - -- show the same dialog again, but with the failure message - yl_speak_up.show_fs(player, "talk", {n_id = n_id, d_id = d_id, - alternate_text = timer_data[ "alternate_text" ] - or yl_speak_up.standard_text_if_action_repeated_too_soon}) - return - else - -- the timer has expired - yl_speak_up.set_quest_variable_value(pname, timer_name, nil) - end - end - - else -- if(a_id) then - nr = table.indexof(sorted_key_list, a_id) - -- did the player go back? - if(nr > -1 and result_of_a_id == nil) then - -- set the current action to nil - yl_speak_up.speak_to[pname].a_id = nil - -- no option of the new dialog has been selected yet - yl_speak_up.speak_to[pname].o_id = nil - -- show the new dialog - yl_speak_up.debug_msg(player, n_id, o_id, "Action ".. - tostring(a_id).." aborted. Switching back to dialog ".. - tostring(d_id)..".") - yl_speak_up.speak_to[pname].o_id = nil - yl_speak_up.speak_to[pname].a_id = nil - yl_speak_up.show_fs(player, "talk", {n_id = n_id, d_id = d_id}) - return - -- did the action fail? - elseif(nr > -1 and not(result_of_a_id)) then - -- is there a limiton how many failed attempts there can be per time? - 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) - -- store that (another?) attempt to do the action failed - if(timer_data - and timer_data["max_attempts"] and tonumber(timer_data["max_attempts"])> 0 - and timer_data["duration"] and tonumber(timer_data["duration"])> 0) then - local times = yl_speak_up.get_quest_variable_value(pname, timer_name) - if(not(times)) then - times = "" - end - -- variables are stored as strings, not as lists - yl_speak_up.set_quest_variable_value(pname, timer_name, - times.." "..yl_speak_up.get_time_in_seconds()) - end - - local this_action = actions[ sorted_key_list[ nr ]] - -- if there is an on_failure target dialog: go there - if(this_action.a_on_failure) then - -- go back to the same dialog - if(not(dialog.n_dialogs[this_action.a_on_failure])) then - this_action.a_on_failure = d_id - end - -- set the current action to nil - yl_speak_up.speak_to[pname].a_id = nil - -- no option of the new dialog has been selected yet - yl_speak_up.speak_to[pname].o_id = nil - -- show the new dialog - yl_speak_up.debug_msg(player, n_id, o_id, "Action ".. - tostring(a_id).." failed. Switching to dialog ".. - tostring(this_action.a_on_failure)..".") - yl_speak_up.speak_to[pname].d_id = this_action.a_on_failure - yl_speak_up.speak_to[pname].o_id = nil - yl_speak_up.speak_to[pname].a_id = nil - yl_speak_up.show_fs(player, "talk", {n_id = n_id, - d_id = this_action.a_on_failure, - alternate_text = this_action.alternate_text}) - return - else - yl_speak_up.debug_msg(player, n_id, o_id, "Action ".. - tostring(a_id).." failed, but no a_on_failure target ".. - "dialog defined. Continuing execution.") - end - end - end - -- get the next entry - if(nr > -1 and nr < #sorted_key_list and sorted_key_list[nr + 1]) then - local next_action = actions[ sorted_key_list[ nr + 1 ]] - -- store which action we are currently executing - yl_speak_up.speak_to[pname].a_id = next_action.a_id - -- execute the next action - yl_speak_up.debug_msg(player, n_id, o_id, "Executing next action ".. - tostring(next_action.a_id)..".") - yl_speak_up.execute_action(player, n_id, o_id, next_action) - -- the player needs time to react - return - end - end - -- when all actions are executed: - -- is there a limiton how fast the action may be repeated again? - 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) - -- store that the action was executed successfully - if(timer_data - and timer_data["duration"] and tonumber(timer_data["duration"]) > 0) then - yl_speak_up.set_quest_variable_value(pname, timer_name, yl_speak_up.get_time_in_seconds()) - end - -- set the current action to nil - yl_speak_up.speak_to[pname].a_id = nil - yl_speak_up.debug_msg(player, n_id, o_id, "All actions have been executed successfully. ".. - "Doing effects/results now.") - -- execute all effects/results - local res = yl_speak_up.execute_all_relevant_effects(player, effects, o_id, true) - local target_dialog = res.next_dialog - yl_speak_up.speak_to[pname].o_id = nil - yl_speak_up.speak_to[pname].a_id = nil - if(not(target_dialog) - or target_dialog == "" - or not(dialog.n_dialogs[target_dialog])) then - target_dialog = d_id - end - -- the function above returns a target dialog; show that to the player - yl_speak_up.show_fs(player, "talk", {n_id = n_id, d_id = target_dialog, - alternate_text = res.alternate_text}) -end - - -yl_speak_up.execute_action = function(player, n_id, o_id, a) - if(not(a.a_type) or a.a_type == "" or a.a_type == "none") then - -- no action - nothing to do - return true - elseif(a.a_type == "trade") then - yl_speak_up.show_fs(player, "trade_simple", a.a_value) - return - elseif(a.a_type == "npc_gives") then - yl_speak_up.show_fs(player, "action_npc_gives", a.a_value) - return - elseif(a.a_type == "npc_wants") then - yl_speak_up.show_fs(player, "action_npc_wants", a.a_value) - return - elseif(a.a_type == "text_input") then - -- start with an empty answer - yl_speak_up.show_fs(player, "action_text_input", "") - return - elseif(a.a_type == "custom") then - yl_speak_up.show_fs(player, "action_custom", a.a_value) - return - end - -- fallback: unkown type - return false -end - - --- helper function; --- returns the action the player is currently faced with (or nil if none) -yl_speak_up.get_action_by_player = function(player) - 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 - local d_id = yl_speak_up.speak_to[pname].d_id - local o_id = yl_speak_up.speak_to[pname].o_id - local a_id = yl_speak_up.speak_to[pname].a_id - if(not(dialog) or not(d_id) or not(o_id) or not(a_id) - 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]) - or not(dialog.n_dialogs[d_id].d_options[o_id].actions) - or not(dialog.n_dialogs[d_id].d_options[o_id].actions[a_id])) then - return nil - end - return dialog.n_dialogs[d_id].d_options[o_id].actions[a_id] -end - - --- Create the quest item by taking a raw item (i.e. a general piece of paper) out --- of the NPC's inventory, applying a description (if given) and quest id (if --- given); place the quest item in the trade inv of the player in the npc_gives slot. --- The npc_gives inv is managed mostly by the NPC, except when in edit mode. We can --- just overwrite anything old in there. --- Returns false if the creation of the quest item wasn't possible (i.e. the --- NPC had no paper left). -yl_speak_up.action_quest_item_prepare = function(player) - -- which action are we talking about? - local a = yl_speak_up.get_action_by_player(player) - if(not(a) or not(a.a_id) or not(a.a_value)) then - return false - end - local pname = player:get_player_name() - local n_id = yl_speak_up.speak_to[pname].n_id - -- what shall the NPC give? - local stack = ItemStack(a.a_value) - -- get the inventory of the NPC - local npc_inv = minetest.get_inventory({type="detached", name="yl_speak_up_npc_"..tostring(n_id)}) - -- does the NPC have the item we are looking for? - if(not(npc_inv:contains_item("npc_main", stack))) then - local o_id = yl_speak_up.speak_to[pname].o_id - yl_speak_up.debug_msg(player, n_id, o_id, "Action "..tostring(a.a_id)..": NPC ran out of ".. - tostring(a.a_value)..".") - -- just go back; the player didn't do anything wrong - return nil - end - -- get the items from the NPCs inventory - local new_stack = npc_inv:remove_item("npc_main", stack) - local meta = new_stack:get_meta() - -- if given: set the item stack description - if(a.a_item_desc and a.a_item_desc ~= "") then - local dialog = yl_speak_up.speak_to[pname].dialog - -- replace $PLAYER_NAME$ etc. in quest item description - meta:set_string("description", yl_speak_up.replace_vars_in_text(a.a_item_desc, dialog, pname)) - end - if(a.a_item_quest_id and a.a_item_quest_id ~= "") 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(a.a_item_quest_id)) - end - -- put the stack in the npc_gives-slot of the trade inventory of the player - -- (as that slot is managed by the NPC alone we don't have to worry about - -- anything else in the slot) - local trade_inv = minetest.get_inventory({type="detached", name="yl_speak_up_player_"..pname}) - -- actually put the stack in there - trade_inv:set_stack("npc_gives", 1, new_stack) - return true -end - - --- check if the item in the npc_gives slot is the one the NPC wants -yl_speak_up.action_quest_item_check = function(player) - -- which action are we talking about? - local a = yl_speak_up.get_action_by_player(player) - if(not(a) or not(a.a_id) or not(a.a_value)) then - return false - end - local pname = player:get_player_name() - local n_id = yl_speak_up.speak_to[pname].n_id - local o_id = yl_speak_up.speak_to[pname].o_id - -- get the item that needs to be checked - local trade_inv = minetest.get_inventory({type="detached", name="yl_speak_up_player_"..pname}) - local stack = trade_inv:get_stack("npc_wants", 1) - -- nothing there? - if(stack:is_empty()) then - yl_speak_up.debug_msg(player, n_id, o_id, "Action "..tostring(a.a_id)..": No item found.") - return false - end - local cmp = tostring(stack:get_name()).." "..(stack:get_count()) - -- wrong item or wrong amount? - if(cmp ~= a.a_value) then - yl_speak_up.debug_msg(player, n_id, o_id, "Action "..tostring(a.a_id).. - ": Wrong item given. Got: "..stack:to_string().. - " Expected: "..tostring(a.a_value)..".") - return false - end - local meta = stack:get_meta() - -- the description is not checked; just the quest id (if given) - if(a.a_item_quest_id and a.a_item_quest_id ~= "") then - -- we don't check here if the item was given by the right NPC; - -- only the quest id has to fit - if(meta:get_string("yl_speak_up:quest_id") ~= a.a_item_quest_id) then - yl_speak_up.debug_msg(player, n_id, o_id, "Action "..tostring(a.a_id).. - ": Wrong quest item (wrong ID).") - return false - end - -- was this quest item given to another player? - if(meta:get_string("yl_speak_up:quest_item_for") ~= pname) then - yl_speak_up.debug_msg(player, n_id, o_id, "Action "..tostring(a.a_id).. - ": Quest item was given to ".. - tostring(meta:get_string("yl_speak_up:quest_item_for")).. - ", but "..tostring(pname).." gave it.") - return false - end - end - yl_speak_up.debug_msg(player, n_id, o_id, "Action "..tostring(a.a_id).. - ": Quest item checked ok.") - return true -end - - --- strip the quest information from the item and give it back to the NPC; --- returns the modified stack (but also places it in the NPC's inventory) -yl_speak_up.action_quest_item_take_back = function(player) - -- which action are we talking about? - local a = yl_speak_up.get_action_by_player(player) - if(not(a) or not(a.a_id) or not(a.a_value)) then - return false - end - local pname = player:get_player_name() - local n_id = yl_speak_up.speak_to[pname].n_id - -- get the item that the NPC shall take back (or accept in npc_wants) - local trade_inv = minetest.get_inventory({type="detached", name="yl_speak_up_player_"..pname}) - local stack = trade_inv:get_stack("npc_wants", 1) - -- if it was the wrong item: - if(not(yl_speak_up.action_quest_item_check(player))) then - local player_inv = player:get_inventory() - -- give the item back to the player - local remaining = player_inv:add_item("main", stack) - -- very unlikely - but in case the item did not fit back into the player's inv: - if(remaining and not(remaining:is_empty())) then - local p = player:get_pos() - -- throw it at the player - minetest.add_item({x=p.x, y=p.y+1, z=p.z}, stack) - end - -- remove it from the trade inv slot - trade_inv:set_stack("npc_wants", 1, ItemStack()) - return false - end - -- we already checked that it is the correct item - local meta = stack:get_meta() - -- if given: set the item stack description - if(a.a_item_desc and a.a_item_desc ~= "") then - meta:set_string("description", "") - end - -- delete all the special IDs that where added before - if(a.a_item_quest_id and a.a_item_quest_id ~= "") then - meta:set_string("yl_speak_up:quest_item_for", "") - meta:set_string("yl_speak_up:quest_item_from", "") - meta:set_string("yl_speak_up:quest_id", "") - end - local npc_inv = minetest.get_inventory({type="detached", name="yl_speak_up_npc_"..tostring(n_id)}) - -- Has the NPC room enough for the item? - -- If the NPC doesn't have room, the item will be destroyed in the next step by setting - -- npc_wants to an empty stack. While this may lead to some item loss, it is more important - -- that the quest item was properly accepted (and discarded of) rather than worrying about - -- where to put it or even giving it back and letting the quest fail. - if(npc_inv:room_for_item("npc_main", stack)) then - npc_inv:add_item("npc_main", stack) - -- save the inventory of the NPC - yl_speak_up.save_npc_inventory(n_id) - end - -- the NPC has accepted the item - trade_inv:set_stack("npc_wants", 1, ItemStack()) - return true -end - - --- show the diffrent action-related formspecs and handle input to them --- (Note: trade is handled in trade_simple.lua) - -yl_speak_up.input_fs_action_npc_gives = function(player, formname, fields) - -- back from error_msg? then show the formspec again - if(fields.back_from_error_msg) then - -- do not create a new item - yl_speak_up.show_fs(player, "action_npc_gives", nil) - return - end - local pname = player:get_player_name() - local trade_inv = minetest.get_inventory({type="detached", name="yl_speak_up_player_"..pname}) - local a_id = yl_speak_up.speak_to[pname].a_id - if(fields.npc_does_not_have_item) then - -- the NPC can't supply the item - abort the action - yl_speak_up.execute_next_action(player, a_id, nil) - return - end - -- is the npc_gives inv empty? then all went as expected. - -- (it does not really matter which button the player pressed in this case) - if(trade_inv:is_empty("npc_gives")) then - -- the NPC has given the item to the player; save the NPCs inventory - local n_id = yl_speak_up.speak_to[pname].n_id - yl_speak_up.save_npc_inventory(n_id) - -- the action was a success; the NPC managed to give the item to the player - yl_speak_up.execute_next_action(player, a_id, true) - return - end - -- the npc_gives slot does not accept input - so we don't have to check for any misplaced items - -- but if the player aborts, give the item back to the NPC - if(fields.back_to_talk) then - -- strip the quest item info from the stack (so that it may stack again) - -- and give that (hopefully) stackable stack back to the NPC - yl_speak_up.action_quest_item_take_back(player) - -- the action failed - yl_speak_up.execute_next_action(player, a_id, nil) - return - end - -- else show a message to the player that he ought to take the item - yl_speak_up.show_fs(player, "msg", { - input_to = "yl_speak_up:action_npc_gives", - formspec = "size[7,1.5]".. - "label[0.2,-0.2;".. - "Please take the offered item and click on \"Done\"!\n".. - "If you can't take it, click on \"Back to talk\".]".. - "button[2,1.0;1.5,0.9;back_from_error_msg;Back]"}) -end - - -yl_speak_up.get_fs_action_npc_gives = function(player, param) - -- called for the first time; create the item the NPC wants to give - if(param) then - if(not(yl_speak_up.action_quest_item_prepare(player))) then - local pname = player:get_player_name() - local dialog = yl_speak_up.speak_to[pname].dialog - -- it's not the fault of the player that the NPC doesn't have the item; - -- so tell him that (the action will still fail) - return "size[7,2.0]".. - "label[0.2,-0.2;".. - minetest.formspec_escape(dialog.n_npc or "- ? -").. - " is very sorry:\n".. - "The item intended for you is currently unavailable.\n".. - "Please come back later!]".. - "button[2,1.5;1.5,0.9;npc_does_not_have_item;Back]" - end - end - local pname = player:get_player_name() - local dialog = yl_speak_up.speak_to[pname].dialog - return "size[8.5,8]".. - "list[current_player;main;0.2,3.85;8,1;]".. - "list[current_player;main;0.2,5.08;8,3;8]".. - "button[0.2,0.0;2.0,0.9;back_to_talk;Back to talk]".. - "button[4.75,1.6;1.5,0.9;finished_action;Done]".. - - "tooltip[back_to_talk;Click here if you don't want to (or can't)\n".. - "take the offered item.]".. - "tooltip[finished_action;Click here once you have taken the item and\n".. - "stored it in your inventory.]".. - "label[1.5,0.7;"..minetest.formspec_escape(dialog.n_npc or "- ? -").. - " offers to you:]".. - -- unlike the npc_gives slot - which is used for setting up the NPC - the - -- npc_gives slot does not allow putting something in - "list[detached:yl_speak_up_player_"..pname..";npc_gives;3.25,1.5;1,1;]" .. - "label[1.5,2.7;Take the offered item and click on \"Done\" to proceed.]" -end - - -yl_speak_up.input_fs_action_npc_wants = function(player, formname, fields) - -- back from error_msg? then show the formspec again - if(fields.back_from_error_msg) then - yl_speak_up.show_fs(player, "action_npc_wants", nil) - return - end - local pname = player:get_player_name() - local trade_inv = minetest.get_inventory({type="detached", name="yl_speak_up_player_"..pname}) - local a_id = yl_speak_up.speak_to[pname].a_id - -- is the npc_wants inv empty and the player pressed the back to talk button? then the action failed. - if(trade_inv:is_empty("npc_wants") and fields.back_to_talk) then - -- the action was aborted - yl_speak_up.execute_next_action(player, a_id, nil) - return - end - -- the player tried to give something; check if it is the right thing - if(not(trade_inv:is_empty("npc_wants"))) then - local stack = trade_inv:get_stack("npc_wants", 1) - -- check if it really is the item the NPC wanted; let the NPC take it - local is_correct_item = yl_speak_up.action_quest_item_take_back(player) - -- the action may have been a success or failure - yl_speak_up.execute_next_action(player, a_id, is_correct_item) - return - end - -- else show a message to the player - yl_speak_up.show_fs(player, "msg", { - input_to = "yl_speak_up:action_npc_wants", - formspec = "size[7,1.5]".. - "label[0.2,-0.2;".. - "Please insert the item for the npc and click on \"Done\"!\n".. - "If you don't have what he wants, click on \"Back to talk\".]".. - "button[2,1.0;1.5,0.9;back_from_error_msg;Back]"}) -end - - -yl_speak_up.get_fs_action_npc_wants = function(player, param) - local pname = player:get_player_name() - local dialog = yl_speak_up.speak_to[pname].dialog - return "size[8.5,8]".. - "list[current_player;main;0.2,3.85;8,1;]".. - "list[current_player;main;0.2,5.08;8,3;8]".. - "button[0.2,0.0;2.0,0.9;back_to_talk;Back to talk]".. - "button[4.75,1.6;1.5,0.9;finished_action;Done]".. - - "tooltip[back_to_talk;Click here if you don't know what item the\n".. - "NPC wants or don't have the desired item.]".. - "tooltip[finished_action;Click here once you have placed the item in\n".. - "the waiting slot.]".. - "label[1.5,0.7;"..minetest.formspec_escape(dialog.n_npc or "- ? -").. - " expects something from you:]".. - "list[detached:yl_speak_up_player_"..pname..";npc_wants;3.25,1.5;1,1;]" .. - "label[1.5,2.7;Insert the right item and click on \"Done\" to proceed.]" -end - - -yl_speak_up.input_fs_action_text_input = function(player, formname, fields) - -- back from error_msg? then show the formspec again - if(fields.back_from_error_msg) then - -- the error message is only shown if the input was empty - yl_speak_up.show_fs(player, "action_text_input", "") - return - end - local pname = player:get_player_name() - local a_id = yl_speak_up.speak_to[pname].a_id - local a = yl_speak_up.get_action_by_player(player) - if(fields.back_to_talk) then - -- the action was aborted - yl_speak_up.execute_next_action(player, a_id, nil) - return - end - if(fields.finished_action and fields.quest_answer and fields.quest_answer ~= "") then - -- is the answer correct? - -- strip leading and tailing blanks - local success = not(not(fields.quest_answer and a.a_value - and fields.quest_answer:trim() == a.a_value:trim())) - -- the action was a either a success or failure - yl_speak_up.execute_next_action(player, a_id, success) - return - end - -- else show a message to the player - yl_speak_up.show_fs(player, "msg", { - input_to = "yl_speak_up:action_text_input", - formspec = "size[7,1.5]".. - "label[0.2,-0.2;".. - "Please answer the question and click on \"Send answer\"!\n".. - "If you don't know the answer, click on \"Back to talk\".]".. - "button[2,1.0;1.5,0.9;back_from_error_msg;Back]"}) -end - - -yl_speak_up.get_fs_action_text_input = function(player, param) - local pname = player:get_player_name() - local dialog = yl_speak_up.speak_to[pname].dialog - local a = yl_speak_up.get_action_by_player(player) - if(not(a)) then - return "" - end - return "size[12.0,4.5]".. - "button[0.2,0.0;2.0,0.9;back_to_talk;Back to talk]".. - "button[2.0,3.7;3.0,0.9;finished_action;Send answer]".. - - "tooltip[back_to_talk;Click here if you don't know the answer.]".. - "tooltip[finished_action;Click here once you've entered the answer.]".. - "label[0.2,1.2;"..minetest.formspec_escape(a.a_question or "Your answer:").."]".. - "label[0.2,1.9;Answer:]".. - "field[1.6,2.2;10.0,0.6;quest_answer;;"..tostring(param or "").."]".. - "label[0.2,2.8;"..minetest.formspec_escape( - "["..(dialog.n_npc or "- ? -").." looks expectantly at you.]").."]" -end - - -- these are only wrapper functions for those in fs_edit_general.lua yl_speak_up.input_fs_edit_actions = function(player, formname, fields) diff --git a/init.lua b/init.lua index b53aa91..7409448 100644 --- a/init.lua +++ b/init.lua @@ -29,6 +29,7 @@ dofile(modpath .. "fs_save_or_discard_or_back.lua") dofile(modpath .. "custrom_functions_you_can_override.lua") -- execute preconditions, actions and effects dofile(modpath .. "exec_eval_preconditions.lua") +dofile(modpath .. "exec_actions.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