844 lines
		
	
	
		
			34 KiB
		
	
	
	
		
			Lua
		
	
	
	
	
	
			
		
		
	
	
			844 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()
 | |
| 	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 = {}
 | |
| 	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)
 | |
| 		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("<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)
 | |
| 		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
 | |
| 	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
 |