yl_speak_up/exec_actions.lua

858 lines
34 KiB
Lua

-- 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()
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
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, formname)
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 = {}
local d_option = {}
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
-- needed later for setting quest_step (optional)
d_option = dialog.n_dialogs[d_id].d_options[o_id]
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, d_option)
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)
-- we are done with this; close any open forms
if(formname) then
minetest.close_formspec(pname, formname)
end
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, formname)
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, formname)
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, formname)
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()
if(not(pname) or not(yl_speak_up.speak_to[pname])) then
return
end
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, formname)
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, formname)
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()
-- the player is no longer talking to the NPC
if(not(pname) or not(yl_speak_up.speak_to[pname])) then
return
end
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, formname)
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, formname)
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("<your answer>"),
"Enter your answer here.",
true)
local h = 2.0
local pname_for_old_fs = nil
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, formame)
return
end
if(fields.failed_action) then
-- the action failed
yl_speak_up.execute_next_action(player, a_id, false, formame)
return
end
if(fields.finished_action) then
-- the action was a success
yl_speak_up.execute_next_action(player, a_id, true, formame)
return
end
if(fields.quit) then
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