-- 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 if(listname and listname == "npc_gives") then yl_speak_up.input_fs_action_npc_gives(player, "action_npc_gives", {}) return end 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) or {} local max_attempts = tonumber(timer_data["max_attempts"] or 0) local duration = tonumber(timer_data["duration"] or 0) if(max_attempts > 0 and 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 p = tonumber(p) -- eliminate entries that are in the future if(p and p < time_now and (p + duration > time_now)) then new_times = new_times.." "..tostring(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 >= max_attempts) then yl_speak_up.debug_msg(player, n_id, o_id, "Action for option ".. tostring(d_id).."_"..tostring(o_id).. " was attempted "..tostring(count).. " times withhin the last "..tostring(duration).. " seconds. Maximum allowed attempts are: ".. tostring(max_attempts)..".") -- 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) or {} duration = tonumber(timer_data["duration"] or 0) if(duration > 0) then local last_time = yl_speak_up.get_quest_variable_value(pname, timer_name) last_time = tonumber(last_time or 0) -- timers in the future are ignored if(last_time > 0 and last_time < time_now and last_time + duration > time_now) then -- show the same dialog again, but with the failure message yl_speak_up.debug_msg(player, n_id, o_id, "Action for option ".. tostring(d_id).."_"..tostring(o_id).. " has last been completed ".. tostring(time_now - last_time).. " seconds ago. It can only be repeated after ".. tostring(duration).. " seconds have passed.") 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 -- 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.log_change(pname, n_id, "Player failed to complete action "..tostring(a_id).. " "..tostring(o_id).." "..tostring(d_id)..": ".. yl_speak_up.show_action(this_action)) 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 local this_action = actions[ sorted_key_list[ nr ]] yl_speak_up.log_change(pname, n_id, "Player completed action "..tostring(a_id).. " "..tostring(o_id).." "..tostring(d_id)..": ".. yl_speak_up.show_action(this_action)) 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 -- end conversation if(target_dialog and target_dialog == "d_end") then yl_speak_up.stop_talking(pname) return end 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 -- "Show something custom (has to be provided by the server)", -- evaluate elseif(a.a_type == "evaluate") then yl_speak_up.show_fs(player, "action_evaluate", 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)..".") yl_speak_up.log_change(pname, n_id, "Action "..tostring(a_id).. " "..tostring(yl_speak_up.speak_to[pname].o_id).. " "..tostring(yl_speak_up.speak_to[pname].d_id).. " failed: Player gave item \""..tostring(cmp).."\", but we wanted: \"".. 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).") yl_speak_up.log_change(pname, n_id, "Action "..tostring(a_id).. " "..tostring(yl_speak_up.speak_to[pname].o_id).. " "..tostring(yl_speak_up.speak_to[pname].d_id).. " failed: Player gave item with wrong quest 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.") yl_speak_up.log_change(pname, n_id, "Action "..tostring(a_id).. " "..tostring(yl_speak_up.speak_to[pname].o_id).. " "..tostring(yl_speak_up.speak_to[pname].d_id).. " failed: Player gave quest item that belonged to player ".. tostring(meta:get_string("yl_speak_up:quest_item_for"))..".") 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}) if(not(yl_speak_up.speak_to[pname])) then return end 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 local n_id = yl_speak_up.speak_to[pname].n_id -- 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())) if(not(success)) then yl_speak_up.log_change(pname, n_id, "Action "..tostring(a_id).. " "..tostring(yl_speak_up.speak_to[pname].o_id).. " "..tostring(yl_speak_up.speak_to[pname].d_id).. ": Player answered with \""..tostring(fields.quest_answer:trim()).. "\", but we expected: \""..tostring(a.a_value:trim()).."\".") else yl_speak_up.log_change(pname, n_id, "Action "..tostring(a_id).. " "..tostring(yl_speak_up.speak_to[pname].o_id).. " "..tostring(yl_speak_up.speak_to[pname].d_id).. ": Answer is correct.") end -- store what the player entered so that it can be examined by other functions yl_speak_up.last_text_input[pname] = fields.quest_answer:trim() -- the action was a either a success or failure yl_speak_up.execute_next_action(player, a_id, success) return end -- no scrolling desired fields.button_up = nil fields.button_down = nil --[[ this is too disruptive; it's better to just let the player select a button -- 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 local alternate_text = (a.a_question or "Your answer:").."\n\n".. (dialog.n_npc or "- ? -").." looks expectantly at you.]" local formspec = {} table.insert(formspec, "label[0.7,1.8;Answer:]") table.insert(formspec, "button[45,1.0;9,1.8;finished_action;Send this answer]") -- show the actual text for the option yl_speak_up.add_formspec_element_with_tooltip_if(formspec, "field", "4.0,1.0;40,1.5", "quest_answer", ";", --..minetest.formspec_escape(""), "Enter your answer here.", true) local h = 2.0 h = yl_speak_up.add_edit_button_fs_talkdialog(formspec, h, "finished_action", "Please enter your answer in the input field above and click here to ".. "send it.", minetest.formspec_escape("[Send this answer]"), true, nil, nil, pname_for_old_fs) h = yl_speak_up.add_edit_button_fs_talkdialog(formspec, h, "back_to_talk", "If you don't know the answer or don't want to answer right now, ".. "choose this option to get back to the previous dialog.", "I give up. Let's talk about something diffrent.", true, nil, nil, pname_for_old_fs) -- do not offer edit_mode in the trade formspec because it makes no sense there; return yl_speak_up.show_fs_decorated(pname, nil, h, alternate_text, "", table.concat(formspec, "\n"), nil, h) --[[ old version with extra formspec return --"size[12.0,4.5]".. yl_speak_up.show_fs_simple_deco(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 -- action of the type "evaluate" yl_speak_up.input_fs_action_evaluate = function(player, formname, fields) local pname = player:get_player_name() local a_id = yl_speak_up.speak_to[pname].a_id -- the custom input_handler may have something to say here as well local a = yl_speak_up.get_action_by_player(player) if(player and a and a.a_value) then local custom_data = yl_speak_up.custom_functions_a_[a.a_value] if(custom_data and custom_data.code_input_handler) then local n_id = yl_speak_up.speak_to[pname].n_id local fun = custom_data.code_input_handler -- actually call the function (which may change the value of fields) fields = fun(player, n_id, a, formname, fields) end end -- back from error_msg? then show the formspec again if(fields.back_from_error_msg) then yl_speak_up.show_fs(player, "action_evaluate", nil) return end if(fields.back_to_talk) then -- the action was aborted yl_speak_up.execute_next_action(player, a_id, nil) return end if(fields.failed_action) then -- the action failed yl_speak_up.execute_next_action(player, a_id, false) return end if(fields.finished_action) then -- the action was a success yl_speak_up.execute_next_action(player, a_id, true) return end -- else show a message to the player that he ought to decide yl_speak_up.show_fs(player, "msg", { input_to = "yl_speak_up:action_evaluate", formspec = "size[7,1.5]".. "label[0.2,-0.2;".. "Please click on one of the offered options\nor select \"Back to talk\"!]".. "button[2,1.0;1.5,0.9;back_from_error_msg;Back]"}) end yl_speak_up.get_fs_action_evaluate = function(player, param) 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 a = yl_speak_up.get_action_by_player(player) if(not(a)) then return "" end if(not(player) or not(a.a_value)) then return "label[0.2,0.5;Ups! An internal error occoured. Please tell your ".. "local admin to check the brain of this lifeform here.]".. "button[1.5,1.5;2,0.9;back_to_talk;Back]" end local custom_data = yl_speak_up.custom_functions_a_[a.a_value] if(not(custom_data) or not(custom_data.code)) then return "label[0.2,0.5;Ups! An internal error occoured. Please tell your ".. "local admin that the internal function ".. minetest.formspec_escape(tostring(a.a_value)).. "somehow got lost/broken.]".. "button[1.5,1.5;2,0.9;back_to_talk;Back]" end local fun = custom_data.code -- actually call the function return fun(player, n_id, a) end