mirror of
				https://gitea.your-land.de/Sokomine/yl_speak_up.git
				synced 2025-10-31 04:23:03 +01:00 
			
		
		
		
	
		
			
				
	
	
		
			797 lines
		
	
	
		
			32 KiB
		
	
	
	
		
			Lua
		
	
	
	
	
	
			
		
		
	
	
			797 lines
		
	
	
		
			32 KiB
		
	
	
	
		
			Lua
		
	
	
	
	
	
| -- This file contains what is necessary to execute/apply an effect.
 | |
| 
 | |
| -- for "deal_with_offered_item", used i.e. in yl_speak_up.get_fs_edit_option_effect_deal_with_offered_item
 | |
| yl_speak_up.dropdown_list_deal_with_offered_item = {
 | |
| 	minetest.formspec_escape("- please select -"),
 | |
| 	"Take the expected part of the offered item(s) and put them in the NPC's inventory.",
 | |
| 	"Accept all of the offered item(s) and put them in the NPC's inventory.",
 | |
| 	"Refuse and give the item(s) back."
 | |
| 	}
 | |
| -- the values that will be stored for yl_speak_up.dropdown_list_deal_with_offered_item above
 | |
| yl_speak_up.dropdown_values_deal_with_offered_item = {
 | |
| 	"do_nothing", "take_as_wanted", "take_all", "refuse_all"}
 | |
| 
 | |
| 
 | |
| -- check if a block of type node_name is blacklisted for a certain interaction
 | |
| -- (this is needed if a block is not prepared for interaction with NPC and
 | |
| -- expects to always be dealing with a player)
 | |
| -- Parameters:
 | |
| -- 	how		how to interact with the node
 | |
| -- 	node_name	the node to place
 | |
| -- 	node_there	the node that can currently be found at that position
 | |
| yl_speak_up.check_blacklisted = function(how, node_name, node_there)
 | |
| 	return yl_speak_up.blacklist_effect_on_block_interact[ node_name ]
 | |
| 	  or yl_speak_up.blacklist_effect_on_block_interact[ node_there ]
 | |
| 	  or (how == "place"       and yl_speak_up.blacklist_effect_on_block_place[ node_name ])
 | |
| 	  or (how == "dig"         and yl_speak_up.blacklist_effect_on_block_dig[   node_there ])
 | |
| 	  or (how == "punch"       and yl_speak_up.blacklist_effect_on_block_punch[ node_there ])
 | |
| 	  or (how == "right-click" and yl_speak_up.blacklist_effect_on_block_right_click[ node_there])
 | |
| 	  or (how == "put"         and yl_speak_up.blacklist_effect_on_block_right_click[ node_there])
 | |
| 	  or (how == "take"        and yl_speak_up.blacklist_effect_on_block_right_click[ node_there])
 | |
| end
 | |
| 
 | |
| 
 | |
| -- called by yl_speak_up.input_talk(..)
 | |
| --
 | |
| -- This function is called *after* the player has clicked on an option
 | |
| -- and *after* any actions (i.e. trade) have been completed either
 | |
| -- successfully (=action_was_succesful is true) or not.
 | |
| -- Unlike the preconditions, the effects are executed in ordered form,
 | |
| -- ordered by their r_id.
 | |
| -- Returns the new target dialog that is to be displayed next. This will
 | |
| -- usually be the one with the r_type "dialog" - unless r_type "on_failure"
 | |
| -- was encountered after an unsuccessful action *or* right after an
 | |
| -- effect that returned false.
 | |
| -- Note: In edit mode, effects will *not* be executed.
 | |
| yl_speak_up.execute_all_relevant_effects = function(player, effects, o_id, action_was_successful)
 | |
| 	local target_dialog = ""
 | |
| 	local pname = player:get_player_name()
 | |
| 	local n_id = yl_speak_up.speak_to[pname].n_id
 | |
| 	if(not(effects)) then
 | |
| 		yl_speak_up.debug_msg(player, n_id, o_id, "No effects given.")
 | |
| 		-- no effects? Then...return to the start dialog
 | |
| 		return {next_dialog = "", alternate_text = nil}
 | |
| 	end
 | |
| 	local edit_mode = (yl_speak_up.edit_mode[pname] == n_id)
 | |
| 	-- Important: the list of effects is *sorted* here. The order remains constant!
 | |
| 	local sorted_key_list = yl_speak_up.sort_keys(effects)
 | |
| 	if(not(sorted_key_list) or #sorted_key_list < 1) then
 | |
| 		yl_speak_up.debug_msg(player, n_id, o_id, "Error: No effects found. At least one of "..
 | |
| 			"type \"dialog\" is necessary.")
 | |
| 	elseif(not(edit_mode)) then
 | |
| 		yl_speak_up.debug_msg(player, n_id, o_id, "Executing effects: "..
 | |
| 			table.concat(sorted_key_list, ", ")..".")
 | |
| 	else
 | |
| 		yl_speak_up.debug_msg(player, n_id, o_id, "Not executing effects because in edit mode.")
 | |
| 	end
 | |
| 	-- failed actions may set an alternate text
 | |
| 	local alternate_text = nil
 | |
| 	local last_result = action_was_successful
 | |
| 	local res = true
 | |
| 	local refuse_items = true
 | |
| 	for i, k in ipairs(sorted_key_list) do
 | |
| 		local r = effects[ k ]
 | |
| 		yl_speak_up.debug_msg(player, n_id, o_id, "..executing "..
 | |
| 			tostring(r.r_id)..": "..yl_speak_up.show_effect(r, pname))
 | |
| 		-- do not execute effects in edit mode
 | |
| 		if(not(edit_mode)) then
 | |
| 			yl_speak_up.debug_msg(player, n_id, o_id,
 | |
| 				"Executing effect "..tostring(r.r_id)..".")
 | |
| 			res = yl_speak_up.execute_effect(player, n_id, o_id, r)
 | |
| 			if(not(res)) then
 | |
| 				yl_speak_up.debug_msg(player, n_id, o_id, tostring(r.r_id)..
 | |
| 					" -> Effect failed to execute.")
 | |
| 				alternate_text = r.alternate_text
 | |
| 			end
 | |
| 			if(r and r.r_type and r.r_type == "deal_with_offered_item") then
 | |
| 				refuse_items = true
 | |
| 				if(not(r.r_value) or r.r_value == "do_nothing") then
 | |
| 					refuse_items = false
 | |
| 				end
 | |
| 			end
 | |
| 		else
 | |
| 			-- in edit mode: assume that the effect was successful
 | |
| 			res = true
 | |
| 		end
 | |
| 		-- "dialog" gives us the normal target_dialog
 | |
| 		if(r.r_type and r.r_type == "dialog") then
 | |
| 			target_dialog = r.r_value
 | |
| 			alternate_text = r.alternate_text
 | |
| 		-- "on_failure" gives an alternate target dialog if the action
 | |
| 		-- or last effect failed
 | |
| 		elseif(r.r_type and r.r_type == "on_failure" and r.r_value and not(last_result)) then
 | |
| 			yl_speak_up.debug_msg(player, n_id, o_id, "Aborted executing effects at "..
 | |
| 				tostring(r.r_id)..". New target dialog: "..tostring(r.r_value)..".")
 | |
| 			-- we also stop execution here
 | |
| 			return {next_dialog = r.r_value, alternate_text = r.alternate_text}
 | |
| 		end
 | |
| 		last_result = res
 | |
| 	end
 | |
| 	-- all preconditions are true
 | |
| 	yl_speak_up.debug_msg(player, n_id, o_id, "Finished executing effects.")
 | |
| 	-- make sure to give unwanted items back if needed
 | |
| 	if(refuse_items) then
 | |
| 		-- check if there is actually something that needs to be given back
 | |
| 		local trade_inv = minetest.get_inventory({type="detached", name="yl_speak_up_player_"..pname})
 | |
| 		if(not(trade_inv:is_empty("npc_wants"))) then
 | |
| 			target_dialog = "d_got_item"
 | |
| 		end
 | |
| 	end
 | |
| 	return {next_dialog = target_dialog, alternate_text = alternate_text}
 | |
| end
 | |
| 
 | |
| 
 | |
| -- helper function for yl_speak_up.execute_effect
 | |
| -- used by "state" (pname is nil) and "property" (pname is nil)
 | |
| yl_speak_up.execute_effect_get_new_value = function(r, var_val, pname)
 | |
| 	-- for "state" - but not for "property"
 | |
| 	if(pname
 | |
| 	  and (r.r_operator == "quest_step"
 | |
| 	    or r.r_operator == "maximum" or r.r_operator == "minimum"
 | |
| 	    or r.r_operator == "increment" or r.r_operator == "decrement")) then
 | |
| 		var_val = yl_speak_up.get_quest_variable_value(pname, r.r_variable)
 | |
| 	end
 | |
| 	-- set the value of the variable
 | |
| 	local new_value = nil
 | |
| 	if(    r.r_operator and r.r_operator == "set_to") then
 | |
| 		new_value = r.r_var_cmp_value
 | |
| 	elseif(r.r_operator and r.r_operator == "unset") then
 | |
| 		new_value = nil
 | |
| 	elseif(r.r_operator and r.r_operator == "set_to_current_time") then
 | |
| 		-- we store the time in seconds - because microseconds would just
 | |
| 		-- confuse the users and be too fine grained anyway
 | |
| 		new_value = math.floor(minetest.get_us_time()/1000000)
 | |
| 	elseif(r.r_operator and r.r_operator == "quest_step") then
 | |
| 		-- quest_step and maximum are effectively the same
 | |
| 		-- TODO: later on, quest steps may be strings
 | |
| 		if(var_val and tonumber(var_val) and tonumber(r.r_var_cmp_value)) then
 | |
| 			new_value = math.max(tonumber(var_val), tonumber(r.r_var_cmp_value))
 | |
| 		else
 | |
| 			new_value = r.r_var_cmp_value
 | |
| 		end
 | |
| 	elseif(r.r_operator and r.r_operator == "maximum") then
 | |
| 		if(var_val and tonumber(var_val) and tonumber(r.r_var_cmp_value)) then
 | |
| 			new_value = math.max(tonumber(var_val), tonumber(r.r_var_cmp_value))
 | |
| 		else
 | |
| 			new_value = r.r_var_cmp_value
 | |
| 		end
 | |
| 	elseif(r.r_operator and r.r_operator == "minimum") then
 | |
| 		if(var_val and tonumber(var_val) and tonumber(r.r_var_cmp_value)) then
 | |
| 			new_value = math.min(tonumber(var_val), tonumber(r.r_var_cmp_value))
 | |
| 		else
 | |
| 			new_value = r.r_var_cmp_value
 | |
| 		end
 | |
| 	elseif(r.r_operator and r.r_operator == "increment") then
 | |
| 		if(var_val and tonumber(var_val) and tonumber(r.r_var_cmp_value)) then
 | |
| 			new_value = tonumber(var_val) + tonumber(r.r_var_cmp_value)
 | |
| 		else
 | |
| 			new_value = r.r_var_cmp_value
 | |
| 		end
 | |
| 	elseif(r.r_operator and r.r_operator == "decrement") then
 | |
| 		if(var_val and tonumber(var_val) and tonumber(r.r_var_cmp_value)) then
 | |
| 			new_value = tonumber(var_val) - tonumber(r.r_var_cmp_value)
 | |
| 		else
 | |
| 			new_value = -1 * r.r_var_cmp_value
 | |
| 		end
 | |
| 	else
 | |
| 		yl_speak_up.debug_msg(player, n_id, o_id, tostring(r.r_id).." "..
 | |
| 			"state: Unsupported type: "..tostring(r.r_value)..".")
 | |
| 		-- keep the old value
 | |
| 		new_value = var_val
 | |
| 	end
 | |
| 	return new_value
 | |
| end
 | |
| 
 | |
| 
 | |
| -- executes an effect/result r for the player and npc n_id;
 | |
| -- returns true on success (relevant for on_failure)
 | |
| -- Note: In edit mode, this function does not get called.
 | |
| yl_speak_up.execute_effect = function(player, n_id, o_id, r)
 | |
| 	if(not(r.r_type) or r.r_type == "") then
 | |
|                 -- nothing to do
 | |
|                 return true
 | |
| 	elseif(r.r_type == "auto" or r.r_type == "trade") then
 | |
| 		-- these effects don't do anything
 | |
| 		return true
 | |
| 	elseif(r.r_type == "put_into_block_inv"
 | |
| 	    or r.r_type == "take_from_block_inv") then
 | |
| 		-- get the inventory of the block
 | |
| 		if(not(r.r_pos) or type(r.r_pos) ~= "table"
 | |
| 		  or not(r.r_pos.x) or not(r.r_pos.y) or not(r.r_pos.z)) then
 | |
| 			-- position not found?
 | |
| 			yl_speak_up.debug_msg(player, n_id, o_id, tostring(r.r_id).." "..
 | |
| 				r.r_type..": No or incorrect position given: "..
 | |
| 				minetest.serialize(r.rp_pos)..".")
 | |
| 			return false
 | |
| 		end
 | |
| 		local meta = minetest.get_meta(r.r_pos)
 | |
| 		if(not(meta)) then
 | |
| 			yl_speak_up.debug_msg(player, n_id, o_id, tostring(r.r_id).." "..
 | |
| 				r.r_type..": Failed to get metadata at "..
 | |
| 				minetest.serialize(r.rp_pos)..".")
 | |
| 			return false
 | |
| 		end
 | |
| 		local inv = meta:get_inventory()
 | |
| 		if(not(inv)) then
 | |
| 			yl_speak_up.debug_msg(player, n_id, o_id, tostring(r.r_id).." "..
 | |
| 				r.r_type..": Failed to get inventory at "..
 | |
| 				minetest.serialize(r.rp_pos)..".")
 | |
| 			return false
 | |
| 		end
 | |
| 		local inv_name = r.r_inv_list_name
 | |
| 		-- get the inventory of the npc
 | |
| 		local npc_inv = minetest.get_inventory({type="detached",
 | |
| 					name="yl_speak_up_npc_"..tostring(n_id)})
 | |
| 		local npc_inv_name = "npc_main"
 | |
| 		-- for easier checking
 | |
| 		local how_to_interact = "take"
 | |
| 		if(r.r_type and r.r_type == "put_into_block_inv") then
 | |
| 			how_to_interact = "put"
 | |
| 		end
 | |
| 		local stack = ItemStack(r.r_itemstack)
 | |
| 		-- is there enough room for the item?
 | |
| 		if(how_to_interact == "put"
 | |
| 		  and not(inv:room_for_item(inv_name, stack))) then
 | |
| 			yl_speak_up.debug_msg(player, n_id, o_id, tostring(r.r_id).." "..
 | |
| 				r.r_type..": No room for \""..
 | |
| 				minetest.serialize(r.r_itemstack).."\""..
 | |
| 				" in node at "..
 | |
| 				minetest.serialize(r.r_pos)..", inv list \""..
 | |
| 				minetest.serialize(inv_name).."\".")
 | |
| 			return false
 | |
| 		elseif(how_to_interact == "take"
 | |
| 		  and not(npc_inv:room_for_item("npc_main", stack))) then
 | |
| 			yl_speak_up.debug_msg(player, n_id, o_id, tostring(r.r_id).." "..
 | |
| 				r.r_type..": NPC has no room for \""..
 | |
| 				minetest.serialize(r.r_itemstack).."\".")
 | |
| 			return false
 | |
| 		end
 | |
| 		-- does the item exist?
 | |
| 		if(how_to_interact == "put"
 | |
| 		  and not(npc_inv:contains_item(npc_inv_name, stack, false))) then
 | |
| 			yl_speak_up.debug_msg(player, n_id, o_id, tostring(r.r_id).." "..
 | |
| 				r.r_type..": NPC does not have \""..
 | |
| 				minetest.serialize(r.r_itemstack).."\".")
 | |
| 			return false
 | |
| 		elseif(how_to_interact == "take"
 | |
| 		  and not(inv:contains_item(inv_name, stack, false))) then
 | |
| 			yl_speak_up.debug_msg(player, n_id, o_id, tostring(r.r_id).." "..
 | |
| 				r.r_type..": Block at "..minetest.serialize(r.r_pos)..
 | |
| 				" does not contain \""..tostring(r.r_itemstack).."\" in list \""..
 | |
| 				tostring(r.r_inv_list).."\".")
 | |
| 			return false
 | |
| 		end
 | |
| 		-- check the blacklist
 | |
| 		local node = minetest.get_node(r.r_pos)
 | |
| 		if(not(node) or not(node.name)) then
 | |
| 			yl_speak_up.debug_msg(player, n_id, o_id, tostring(r.r_id).." "..
 | |
| 				r.r_type..": No node found at "..minetest.serialize(r.r_pos)..".")
 | |
| 			return false
 | |
| 		end
 | |
| 		-- do not interact with nodes on the blacklist
 | |
| 		if(yl_speak_up.check_blacklisted(how_to_interact, node.name, node.name)) then
 | |
| 			yl_speak_up.debug_msg(player, n_id, o_id, tostring(r.r_id).." "..
 | |
| 				r.r_type..": Blocks of type \""..tostring(node.name).."\" do not allow "..
 | |
| 				"interaction of type \""..tostring(r.r_value).."\" for NPC.")
 | |
| 			return false
 | |
| 		end
 | |
| 		-- construct a fake player
 | |
| 		local owner_name = yl_speak_up.npc_owner[ n_id ]
 | |
| 		if(not(owner_name) or owner_name == "") then
 | |
| 			yl_speak_up.debug_msg(player, n_id, o_id, tostring(r.r_id).." "..
 | |
| 				r.r_type..": NPC does not have an owner. Aborting.")
 | |
| 			return false
 | |
| 		end
 | |
| 		-- act in the name of the owner when accessing inventories
 | |
| 		local fake_player = {
 | |
| 			get_player_name = function() return owner_name end,
 | |
| 			is_player = function() return true end,
 | |
| 			is_fake_player = true,
 | |
| 			get_wielded_item = function(self, item)
 | |
| 				if(self._inventory and def.wield_list) then
 | |
| 					return self._inventory:get_stack(def.wield_list, self._wield_index)
 | |
| 				end
 | |
| 				return ItemStack(self._wielded_item)
 | |
| 			end,
 | |
| 		}
 | |
| 		-- TODO: get the fake player from pipeworks?
 | |
| 		local def = minetest.registered_nodes[ node.name ]
 | |
| 		if(def and def[ "allow_metadata_inventory_"..how_to_interact ]) then
 | |
| 			local res = def[ "allow_metadata_inventory_"..how_to_interact ](
 | |
| 						r.r_pos, inv_name, 1,
 | |
| 						ItemStack(r.r_itemstack),
 | |
| 						fake_player)
 | |
| 			if(not(res) or res < ItemStack(r.r_itemstack):get_count()) then
 | |
| 				yl_speak_up.debug_msg(player, n_id, o_id, tostring(r.r_id).." "..
 | |
| 					r.r_type..": allow_metadata_inventory_"..tostring(how_to_interact)..
 | |
| 					" forbits interaction at "..minetest.serialize(r.r_pos)..".")
 | |
| 				return false
 | |
| 			end
 | |
| 		end
 | |
| 		-- all ok so far; we can proceed
 | |
| 		if(how_to_interact == "put") then
 | |
| 			local r1 = npc_inv:remove_item(npc_inv_name, stack)
 | |
| 			local r2 = inv:add_item(inv_name, r1)
 | |
| 			return true
 | |
| 		elseif(how_to_interact == "take") then
 | |
| 			local r1 = inv:remove_item(inv_name, stack)
 | |
| 			local r2 = npc_inv:add_item(npc_inv_name, r1)
 | |
| 			return true
 | |
| 		end
 | |
| 		return false
 | |
| 	elseif(r.r_type == "deal_with_offered_item") then
 | |
| 		-- possible r_value: "do_nothing", "take_as_wanted", "take_all", "refuse_all"}
 | |
| 		if(not(r.r_value) or r.r_value == "do_nothing") then
 | |
| 			return true -- we're good at doing nothing
 | |
| 		end
 | |
| 		local pname = player:get_player_name()
 | |
| 		local inv = minetest.get_inventory({type="detached", name="yl_speak_up_player_"..pname})
 | |
| 		local stack_got = inv:get_stack("npc_wants",1)
 | |
| 		if(stack_got:is_empty()) then
 | |
| 			yl_speak_up.debug_msg(player, n_id, o_id, tostring(r.r_id).." "..
 | |
| 					r.r_type..": The offered stack is empty.")
 | |
| 			return false -- the player gave nothing
 | |
| 		end
 | |
| 		-- get the inventory of the npc
 | |
| 		local npc_inv = minetest.get_inventory({type="detached",
 | |
| 					name="yl_speak_up_npc_"..tostring(n_id)})
 | |
| 		-- shall we take all or just as much as the NPC wants?
 | |
| 		local stack_wanted = ItemStack(r.r_value)
 | |
| 		local amount = 0
 | |
| 		if(r.r_value == "take_all") then
 | |
| 			amount = stack_got:get_count()
 | |
| 		elseif(r.r_value == "takeas_wanted") then
 | |
| 			amount = stack_wanted:get_count()
 | |
| 			-- the NPC didn't get enough
 | |
| 			if(amount > stack_got:get_count()) then
 | |
| 				yl_speak_up.debug_msg(player, n_id, o_id, tostring(r.r_id).." "..
 | |
| 					r.r_type..": The offered stack \""..tostring(stack_got)..
 | |
| 					"is smaller than what the NPC wanted: \""..
 | |
| 					tostring(stack_wanted).."\".")
 | |
| 				return false
 | |
| 			end
 | |
| 		end
 | |
| 		local take_stack = stack_got:get_name().." "..tostring(amount)
 | |
| 		if(not(npc_inv:room_for_item("npc_main", take_stack))) then
 | |
| 			yl_speak_up.debug_msg(player, n_id, o_id, tostring(r.r_id).." "..
 | |
| 				r.r_type..": The NPC has not enough room for \""..tostring(take_stack).."\".")
 | |
| 			return false
 | |
| 		end
 | |
| 		-- actually transfer the item from the player to the NPC's inventory
 | |
| 		npc_inv:add_item("npc_main", take_stack)
 | |
| 		inv:remove_item("npc_wants", take_stack)
 | |
| 		-- returning of any leftover items needs to happen after *all* effects
 | |
| 		-- are executed; we don't need to take any special preparations here
 | |
| 
 | |
| 	elseif(r.r_type == "dialog"
 | |
| 	    or r.r_type == "on_failure") then
 | |
| 		-- this needs to be handled in the calling function
 | |
| 		return true
 | |
| 	elseif(r.r_type == "function") then
 | |
| 	-- this can only be set and edited with the staff
 | |
| 		if(not(yl_speak_up.npc_has_priv(n_id, "effect_exec_lua", r.r_is_generic))) then
 | |
| 			return false
 | |
| 		end
 | |
| 		return yl_speak_up.eval_and_execute_function(player, r, "r_")
 | |
| 	-- this can only be set and edited with the staff
 | |
| 	elseif(r.r_type == "give_item") then
 | |
| 		if(not(r.r_value)) then
 | |
| 			return false
 | |
| 		end
 | |
| 		if(not(yl_speak_up.npc_has_priv(n_id, "effect_give_item", r.r_is_generic))) then
 | |
| 			return false
 | |
| 		end
 | |
| 		local item = ItemStack(r.r_value)
 | |
| 		if(not(minetest.registered_items[item:get_name()])) then
 | |
| 			yl_speak_up.debug_msg(player, n_id, o_id, tostring(r.r_id).." "..
 | |
| 				"give_item: "..tostring(item:get_name()).." unknown.")
 | |
| 			return false
 | |
| 		end
 | |
| 		local r = player:get_inventory():add_item("main", item)
 | |
| 		if(not(r)) then
 | |
| 			yl_speak_up.debug_msg(player, n_id, o_id, tostring(r.r_id).." "..
 | |
| 				"give_item: "..tostring(item:get_name()).." failed.")
 | |
| 			return false
 | |
| 		end
 | |
| 		return true
 | |
| 	-- this can only be set and edited with the staff
 | |
| 	elseif(r.r_type == "take_item") then
 | |
| 		if(not(yl_speak_up.npc_has_priv(n_id, "effect_take_item", r.r_is_generic))) then
 | |
| 			return false
 | |
| 		end
 | |
| 		if(not(r.r_value)) then
 | |
| 			return false
 | |
| 		end
 | |
| 		local item = ItemStack(r.r_value)
 | |
| 		if(not(minetest.registered_items[item:get_name()])) then
 | |
| 			yl_speak_up.debug_msg(player, n_id, o_id, tostring(r.r_id).." "..
 | |
| 				"take_item: "..tostring(item:get_name()).." unknown.")
 | |
| 			return false
 | |
| 		end
 | |
| 		local r = player:get_inventory():remove_item("main", item)
 | |
| 		if(not(r)) then
 | |
| 			yl_speak_up.debug_msg(player, n_id, o_id, tostring(r.r_id).." "..
 | |
| 				"take_item: "..tostring(item:get_name()).." failed.")
 | |
| 			return false
 | |
| 		end
 | |
| 		return true
 | |
| 	-- this can only be set and edited with the staff
 | |
| 	elseif(r.r_type == "move") then
 | |
| 		if(not(yl_speak_up.npc_has_priv(n_id, "effect_move_player", r.r_is_generic))) then
 | |
| 			return false
 | |
| 		end
 | |
| 		-- copeid/moved here from AliasAlreadyTakens code in functions.lua
 | |
| 		local target_pos = nil
 | |
| 		local target_pos_valid = false
 | |
| 
 | |
| 		--pos like (100,20,400)
 | |
| 		if minetest.string_to_pos(r.r_value) then
 | |
| 		    target_pos = minetest.string_to_pos(r.r_value)
 | |
| 		    target_pos_valid = true
 | |
| 		end
 | |
| 
 | |
| 		--pos like 100,20,400
 | |
| 		local maybe = string.split(r.r_value, ",")
 | |
| 		if not target_pos_valid and maybe and tonumber(maybe[1])
 | |
| 		  and tonumber(maybe[2]) and tonumber(maybe[3]) and maybe[4] == nil and
 | |
| 		    tonumber(maybe[1]) <= 32000 and tonumber(maybe[1]) >= -32000 and
 | |
| 		    tonumber(maybe[2]) <= 32000 and tonumber(maybe[2]) >= -32000 and
 | |
| 		    tonumber(maybe[3]) <= 32000 and tonumber(maybe[3]) >= -32000 then
 | |
| 		        target_pos = {x=maybe[1],y=maybe[2],z=maybe[3]}
 | |
| 		        target_pos_valid = true
 | |
| 		end
 | |
| 
 | |
| 		--pos like {x=100,y=20,z=400}
 | |
| 		if not target_pos_valid and string.sub(r.r_value,1,1) == "{"
 | |
| 		  and string.sub(r.r_value,-1,-1) == "}" then
 | |
| 		    local might_be_pos = minetest.deserialize("return " .. r.r_value)
 | |
| 		    if tonumber(might_be_pos.x)
 | |
| 		     and tonumber(might_be_pos.x) <= 32000
 | |
| 		     and tonumber(might_be_pos.x) >= -32000
 | |
| 		     and tonumber(might_be_pos.y)
 | |
| 		     and tonumber(might_be_pos.y) <= 32000
 | |
| 		     and tonumber(might_be_pos.y) >= -32000
 | |
| 		     and tonumber(might_be_pos.z)
 | |
| 		     and tonumber(might_be_pos.z) <= 32000
 | |
| 		     and tonumber(might_be_pos.z) >= -32000 then
 | |
| 		        target_pos = might_be_pos
 | |
| 		        target_pos_valid = true
 | |
| 		    end
 | |
| 		end
 | |
| 
 | |
| 		if target_pos_valid == true then
 | |
| 		    player:set_pos(target_pos)
 | |
| 		    if vector.distance(player:get_pos(),target_pos) >= 2 then
 | |
| 			yl_speak_up.log_change(pname, n_id, tostring(r.r_id)..": "..
 | |
| 				"Something went wrong! Player wasn't moved properly.")
 | |
| 		    end
 | |
| 		end
 | |
| 
 | |
| 		-- Debug
 | |
| 		if target_pos_valid == false then
 | |
| 		    local obj = yl_speak_up.speak_to[pname].obj
 | |
| 		    local n_id = yl_speak_up.speak_to[pname].n_id
 | |
| 		    local npc = yl_speak_up.get_number_from_id(n_id)
 | |
| 		    if obj:get_luaentity() and tonumber(npc) then
 | |
| 			yl_speak_up.log_change(pname, n_id, tostring(r.r_id)..": "..
 | |
| 				"NPC at "..minetest.pos_to_string(obj:get_pos(),0)..
 | |
| 				" could not move player "..pname.." because the content of "..
 | |
| 				tostring(r.r_id).." is wrong:"..dump(r.r_value))
 | |
| 		    else
 | |
| 			yl_speak_up.log_change(pname, n_id, tostring(r.r_id)..": "..
 | |
| 				"NPC with unknown ID or without proper object "..
 | |
| 				" could not move player "..pname.." because the content of "..
 | |
| 				tostring(r.r_id).." is wrong:"..dump(r.r_value))
 | |
| 		    end
 | |
| 		    return false
 | |
| 		end
 | |
| 		return true
 | |
| 
 | |
| 	-- "an internal state (i.e. of a quest)", -- 2
 | |
| 	elseif(r.r_type == "state") then
 | |
| 		if(not(r.r_variable) or r.r_variable == "") then
 | |
| 			return false
 | |
| 		end
 | |
| 		local pname = player:get_player_name()
 | |
| 		-- set the value of the variable
 | |
| 		local new_value = yl_speak_up.execute_effect_get_new_value(r, nil, pname)
 | |
| 		-- the owner is already encoded in the variable name
 | |
| 		local ret = yl_speak_up.set_quest_variable_value(pname, r.r_variable, new_value)
 | |
| 		yl_speak_up.debug_msg(player, n_id, o_id, tostring(r.r_id).." "..
 | |
| 			"state: Success: "..tostring(ret).." for setting "..tostring(r.r_variable).." to "..
 | |
| 			tostring(new_value)..".")
 | |
|                 return ret
 | |
| 	-- "the value of a property of the NPC (for generic NPC)", -- property
 | |
| 	elseif(r.r_type == "property") then
 | |
| 		local pname = player:get_player_name()
 | |
| 		local property_data = yl_speak_up.get_npc_properties(pname)
 | |
| 		if(not(property_data)) then
 | |
| 			yl_speak_up.debug_msg(player, n_id, o_id, tostring(r.r_id).." "..
 | |
| 				"property: Failed to access properties of NPC "..tostring(n_id))
 | |
| 			return false
 | |
| 		end
 | |
| 		if(not(r.r_value) or r.r_value == "") then
 | |
| 			yl_speak_up.debug_msg(player, n_id, o_id, tostring(r.r_id).." "..
 | |
| 				"property: No name of property given")
 | |
| 			return false
 | |
| 		end
 | |
| 		local var_val = property_data.properties[r.r_value]
 | |
| 		-- set the value of the variable
 | |
| 		local new_value = yl_speak_up.execute_effect_get_new_value(r, var_val, nil)
 | |
| 		local res = yl_speak_up.set_npc_property(pname, r.r_value, new_value)
 | |
| 		if(res ~= "OK") then
 | |
| 			yl_speak_up.debug_msg(player, n_id, o_id, tostring(r.r_id).." "..
 | |
| 				"property: "..tostring(res))
 | |
| 			return false
 | |
| 		end
 | |
| 		yl_speak_up.debug_msg(player, n_id, o_id, tostring(r.r_id).." "..
 | |
| 			"state: Success: Set property "..tostring(r.r_value).." to "..
 | |
| 			tostring(new_value)..".")
 | |
| 		return true
 | |
| 	-- "something that has to be calculated or evaluated (=call a function)", -- evaluate
 | |
| 	elseif(r.r_type == "evaluate") then
 | |
| 		-- TODO
 | |
| 		return true
 | |
| 	-- "a block somewhere" -- 3
 | |
| 	elseif(r.r_type == "block") then
 | |
| 		-- is the position given correctly?
 | |
| 		if(not(r.r_pos) or type(r.r_pos) ~= "table"
 | |
| 		 or not(r.r_pos.x) or not(r.r_pos.y) or not(r.r_pos.z)) then
 | |
| 			yl_speak_up.debug_msg(player, n_id, o_id, tostring(r.r_id).." "..
 | |
| 				"block: Missing or wrong position given: "..
 | |
| 				minetest.serialize(r.r_pos)..".")
 | |
| 			return false
 | |
| 		end
 | |
| 		-- check protection (relevant for some actions): the *owner*
 | |
| 		-- of the NPC needs to be able to build there
 | |
| 		local is_protected = minetest.is_protected(r.r_pos, yl_speak_up.npc_owner[ n_id ] or "?")
 | |
| 		-- human readable position; mostly for debug messages
 | |
| 		local pos_str = tostring(minetest.pos_to_string(r.r_pos))
 | |
| 		-- the area has to be loaded
 | |
| 		local node = minetest.get_node_or_nil(r.r_pos)
 | |
| 		if(not(node)) then
 | |
| 			yl_speak_up.debug_msg(player, n_id, o_id, tostring(r.r_id).." "..
 | |
| 				"block: Not loaded (nil) at pos "..pos_str..".")
 | |
| 			return false
 | |
| 		end
 | |
| 		-- do not interact with nodes on the blacklist
 | |
| 		if(yl_speak_up.check_blacklisted(r.r_value, r.r_node, node.name)) then
 | |
| 			-- construct the right text for the error message
 | |
| 			local nname = node.name
 | |
| 			if(r.r_value == "place") then
 | |
| 				nname = r.r_node
 | |
| 			end
 | |
| 			yl_speak_up.debug_msg(player, n_id, o_id, tostring(r.r_id).." "..
 | |
| 				"block: Blocks of type \""..tostring(nname).."\" do not allow "..
 | |
| 				"interaction of type \""..tostring(r.r_value).."\" for NPC.")
 | |
| 			return false
 | |
| 		end
 | |
| 		-- if node has owner set: check if owner == npc owner
 | |
| 		local meta = minetest.get_meta(r.r_pos)
 | |
| 		if(meta
 | |
| 		  and meta:get_string("owner") and meta:get_string("owner") ~= ""
 | |
| 		  and meta:get_string("owner") ~= yl_speak_up.npc_owner[ n_id ]) then
 | |
| 			yl_speak_up.debug_msg(player, n_id, o_id, tostring(r.r_id).." "..
 | |
| 				"block: Blocks at "..pos_str.." is owned by "..meta:get_string("owner")..
 | |
| 				". NPC is owned by "..tostring(yl_speak_up.npc_owner[ n_id ])..
 | |
| 				" and thus cannot interact with it.")
 | |
| 			return false
 | |
| 		end
 | |
| 		-- "If there is air: Place a block so that it looks like now.", -- 2
 | |
| 		if(r.r_value and r.r_value == "place") then
 | |
| 			if(is_protected) then
 | |
| 				yl_speak_up.debug_msg(player, n_id, o_id, tostring(r.r_id).." "..
 | |
| 					"block: place - "..pos_str.." is protected. Can't place.")
 | |
| 				return false
 | |
| 			end
 | |
| 			if(not(node) or not(node.name) or node.name ~= "air") then
 | |
| 				yl_speak_up.debug_msg(player, n_id, o_id, tostring(r.r_id).." "..
 | |
| 					"block: place - there is already a block at pos "..pos_str..
 | |
| 					". Can't place.")
 | |
| 				return false
 | |
| 			end
 | |
| 			-- does the NPC have this block in his inventory? else he can't place it
 | |
| 			local npc_inv = minetest.get_inventory({type="detached",
 | |
| 					name="yl_speak_up_npc_"..tostring(n_id)})
 | |
| 			if(not(npc_inv:contains_item("npc_main", tostring(r.r_node)))) then
 | |
| 				yl_speak_up.debug_msg(player, n_id, o_id, tostring(r.r_id).." "..
 | |
| 					"block: place - NPC does not have "..tostring(r.r_node)..
 | |
| 					" in his inventory for placing at "..pos_str..".")
 | |
| 				return false
 | |
| 			end
 | |
| 			-- TODO: switch to minetest.place_node in the future once the bug with placing
 | |
| 			--       on an air node is fixed
 | |
| 			-- actually place the node
 | |
| 			minetest.set_node(r.r_pos, {name=r.r_node, param2=r.r_param2})
 | |
| 			-- consume the item
 | |
| 			npc_inv:remove_item("npc_main", tostring(r.r_node))
 | |
| 			return true
 | |
| 		-- "If there is a block: Dig it.", -- 3
 | |
| 		elseif(r.r_value and r.r_value == "dig") then
 | |
| 			if(is_protected) then
 | |
| 				yl_speak_up.debug_msg(player, n_id, o_id, tostring(r.r_id).." "..
 | |
| 					"block: place - "..pos_str.." is protected. Can't place.")
 | |
| 				return false
 | |
| 			end
 | |
| 			if(not(node) or not(node.name) or node.name == "air"
 | |
| 			   or not(minetest.registered_items[ node.name ])) then
 | |
| 				yl_speak_up.debug_msg(player, n_id, o_id, tostring(r.r_id).." "..
 | |
| 					"block: dig - there is no block at pos "..pos_str..".")
 | |
| 				return false
 | |
| 			end
 | |
| 			-- TODO: use dig_node once that can put the items in the inventory
 | |
| 			-- local dig_res = minetest.dig_node(r.r_pos)
 | |
| 			if(minetest.registered_items[ node.name ].can_dig
 | |
| 			   and not(minetest.registered_items[ node.name ].can_dig(r.r_pos))) then
 | |
| 				yl_speak_up.debug_msg(player, n_id, o_id, tostring(r.r_id).." "..
 | |
| 					"block: dig - Can't dig block at pos "..pos_str..".")
 | |
| 				return false
 | |
| 			end
 | |
| 			-- actually remove the node
 | |
| 			minetest.remove_node(r.r_pos)
 | |
| 			-- get node drops when digging without a tool
 | |
| 			local drop_list = minetest.get_node_drops(node, nil)
 | |
| 			local npc_inv = minetest.get_inventory({type="detached",
 | |
| 				name="yl_speak_up_npc_"..tostring(n_id)})
 | |
| 			-- put the drops into the inventory of the NPC
 | |
| 			for i, d in ipairs(drop_list) do
 | |
| 				local rest = npc_inv:add_item("npc_main", ItemStack(d))
 | |
| 				if(rest and not(rest:is_empty()) and rest:get_count()>0) then
 | |
| 					yl_speak_up.debug_msg(player, n_id, o_id, tostring(r.r_id)..
 | |
| 					" block: dig (info) - NPC had no room for item drop "..
 | |
| 					rest:to_string().." from digging at "..pos_str..".")
 | |
| 				end
 | |
| 			end
 | |
| 			yl_speak_up.debug_msg(player, n_id, o_id, tostring(r.r_id).." "..
 | |
| 					"block: dig - success: "..tostring(node.name).." at pos "..pos_str..".")
 | |
| 			return true
 | |
| 		-- "Punch the block.", -- 4
 | |
| 		elseif(r.r_value and r.r_value == "punch") then
 | |
| 			-- even air can be punched - even if that is pretty pointless
 | |
| 			minetest.punch_node(r.r_pos)
 | |
| 			return true
 | |
| 		-- "Right-click the block.", -- 5
 | |
| 		elseif(r.r_value and r.r_value == "right-click") then
 | |
| 			if(not(node) or not(node.name) or not(minetest.registered_nodes[node.name])) then
 | |
| 				return false
 | |
| 			end
 | |
| 			-- do not right-click nodes that have a metadata formspec string
 | |
| 			local meta = minetest.get_meta(r.r_pos)
 | |
| 			if(meta and meta:get_string("formspec") and meta:get_string("formspec") ~= "") then
 | |
| 				yl_speak_up.debug_msg(player, n_id, o_id, tostring(r.r_id).." "..
 | |
| 					"block: right-click - The block at "..pos_str.." has a "..
 | |
| 					"formspec set. NPC can't read these. Interaction not possible.")
 | |
| 				return false
 | |
| 			end
 | |
| 			-- do not right-click nodes that have an inventory (they most likely show a
 | |
| 			-- formspec - which the NPC can't use anyway)
 | |
| 			local inv = meta:get_inventory()
 | |
| 			for k, l in pairs(inv:get_lists()) do
 | |
| 				-- if the inventory contains any lists: abort
 | |
| 				yl_speak_up.debug_msg(player, n_id, o_id, tostring(r.r_id).." "..
 | |
| 					"block: right-click - The block at "..pos_str.." has an "..
 | |
| 					"inventory. Most likely it will show a formspec on right-click. "..
 | |
| 					"NPC can't read these. Interaction not possible.")
 | |
| 				return false
 | |
| 			end
 | |
| 			-- is it a door?
 | |
| 			if(doors.registered_doors[node.name]) then
 | |
| 				doors.door_toggle(    r.r_pos, node, nil) --, clicker)
 | |
| 				yl_speak_up.debug_msg(player, n_id, o_id, tostring(r.r_id).." "..
 | |
| 					"block: Opened/closed door at "..pos_str..".")
 | |
| 			-- is it a normal trapdoor?
 | |
| 			elseif(doors.registered_trapdoors[node.name]) then
 | |
| 				doors.trapdoor_toggle(r.r_pos, node, nil) --, clicker)
 | |
| 				yl_speak_up.debug_msg(player, n_id, o_id, tostring(r.r_id).." "..
 | |
| 					"block: Opened/closed trapdoor at "..pos_str..".")
 | |
| 			elseif(minetest.registered_nodes[node.name]
 | |
| 			  and minetest.registered_nodes[node.name].on_rightclick
 | |
| 			  and minetest.registered_nodes[node.name].on_rightclick(r.r_pos, node, nil)) then
 | |
| 				minetest.registered_nodes[node.name].on_rightclick(r.r_pos, node, nil)
 | |
| 				yl_speak_up.debug_msg(player, n_id, o_id, tostring(r.r_id).." "..
 | |
| 					"block: right-clicked at at pos "..pos_str..".")
 | |
| 			else
 | |
| 				yl_speak_up.debug_msg(player, n_id, o_id, tostring(r.r_id).." "..
 | |
| 					"block: right-click at at pos "..pos_str.." had no effect.")
 | |
| 			end
 | |
| 		end
 | |
| 		return false
 | |
| 	-- ""NPC crafts something", -- 4
 | |
| 	elseif(r.r_type == "craft") then
 | |
| 		if(not(r.r_craft_grid) or not(r.r_value)) then
 | |
| 			return false
 | |
| 		end
 | |
| 		local input = {}
 | |
| 		input.items = {}
 | |
| 		-- multiple slots in the craft grid may contain the same item
 | |
| 		local sum_up = {}
 | |
| 		for i, v in ipairs(r.r_craft_grid) do
 | |
| 			if(v and v ~= "") then
 | |
| 				local stack = ItemStack(v)
 | |
| 				-- store this for later crafting
 | |
| 				input.items[ i ] = stack
 | |
| 				local name = stack:get_name()
 | |
| 				if(sum_up[ name ]) then
 | |
| 					sum_up[ name ] = sum_up[ name ] + stack:get_count()
 | |
| 				else
 | |
| 					sum_up[ name ] = stack:get_count()
 | |
| 				end
 | |
| 			else
 | |
| 				-- empty itemstack in this slot
 | |
| 				input.items[ i ] = ItemStack("")
 | |
| 			end
 | |
| 		end
 | |
| 		-- does the NPC have all these items in his inventory?
 | |
| 		local npc_inv = minetest.get_inventory({type="detached",
 | |
| 					name="yl_speak_up_npc_"..tostring(n_id)})
 | |
| 		for k, v in pairs(sum_up) do
 | |
|                         if(not(npc_inv:contains_item("npc_main", k.." "..v))) then
 | |
| 				yl_speak_up.debug_msg(player, n_id, o_id, tostring(r.r_id).." "..
 | |
| 					"Crafting failed: NPC does not have "..tostring(k.." "..v))
 | |
| 				return false
 | |
| 			end
 | |
| 		end
 | |
| 		-- do these input items form a valid craft recipe?
 | |
| 		input.method = "normal" -- normal crafting; no cooking or fuel or the like
 | |
| 		input.width = 3
 | |
| 		local output, decremented_input = minetest.get_craft_result(input)
 | |
| 		if(output.item:is_empty()) then
 | |
| 			yl_speak_up.debug_msg(player, n_id, o_id, tostring(r.r_id).." "..
 | |
| 				"Crafting failed: No output for that recipe.")
 | |
| 			return false
 | |
| 		end
 | |
| 		-- the craft receipe may have changed in the meantime and yield a diffrent result
 | |
| 		local expected_stack = ItemStack(r.r_value)
 | |
| 		if(output.item:get_name() ~= expected_stack:get_name()
 | |
| 		  or output.item:get_count() ~= expected_stack:get_count()) then
 | |
| 			yl_speak_up.debug_msg(player, n_id, o_id, tostring(r.r_id).." "..
 | |
| 				"Crafting failed: Diffrent output: "..tostring(output.item:to_string()))
 | |
| 			return false
 | |
| 		end
 | |
| 		yl_speak_up.debug_msg(player, n_id, o_id, tostring(r.r_id).." "..
 | |
| 				"Great: Crafting is possible!")
 | |
| 		-- actually consume the items required, return the ones in decremented_input
 | |
| 		for i, v in ipairs(r.r_craft_grid) do
 | |
| 			if(v and v ~= "") then
 | |
| 				npc_inv:remove_item("npc_main", ItemStack(v))
 | |
| 			end
 | |
| 		end
 | |
| 		-- add the craft result
 | |
| 		if(not(npc_inv:room_for_item("npc_main", output.item))) then
 | |
| 			yl_speak_up.debug_msg(player, n_id, o_id, tostring(r.r_id).." "..
 | |
| 				"No room for craft result "..output.item:to_string())
 | |
| 		end
 | |
| 		npc_inv:add_item("npc_main", output.item)
 | |
| 		-- add the decremented_inputs
 | |
| 		for k,v in pairs(decremented_input.items) do
 | |
| 			if(k and not(v:is_empty())) then
 | |
| 				if(not(npc_inv:room_for_item("npc_main", v))) then
 | |
| 					yl_speak_up.debug_msg(player, n_id, o_id, tostring(r.r_id).." "..
 | |
| 						"No room for craft decr. input "..v:to_string())
 | |
| 				end
 | |
| 				-- actually give the decremented input to the NPC
 | |
| 				npc_inv:add_item("npc_main", v)
 | |
| 			end
 | |
| 		end
 | |
| 		return true
 | |
| 	-- "send a chat message to all players", -- 6
 | |
| 	elseif(r.r_type == "chat_all") then
 | |
| 		local pname = player:get_player_name()
 | |
| 		local dialog = yl_speak_up.speak_to[pname].dialog
 | |
| 		local text = r.r_value
 | |
| 		-- replace $NPC_NAME$, $OWNER_NAME$, $PLAYER_NAME$ etc.
 | |
| 		text = yl_speak_up.replace_vars_in_text(text, dialog, pname)
 | |
| 		minetest.chat_send_all(
 | |
| 			yl_speak_up.chat_all_prefix..
 | |
| 			minetest.colorize(yl_speak_up.chat_all_color, text))
 | |
| 		-- sending a chat message always counts as successful
 | |
| 		return true
 | |
| 	-- "Call custom functions that are supposed to be overridden by the server.", -- 13
 | |
| 	elseif(r.r_type == "custom") then
 | |
| 		-- execute the custom function
 | |
| 		return yl_speak_up.effect_custom(player, r.r_value)
 | |
| 	end
 | |
| 	-- fallback: unkown type
 | |
| 	return false
 | |
| end
 |