mirror of
				https://gitea.your-land.de/Sokomine/yl_speak_up.git
				synced 2025-11-03 22:03:08 +01:00 
			
		
		
		
	
		
			
				
	
	
		
			409 lines
		
	
	
		
			16 KiB
		
	
	
	
		
			Lua
		
	
	
	
	
	
			
		
		
	
	
			409 lines
		
	
	
		
			16 KiB
		
	
	
	
		
			Lua
		
	
	
	
	
	
-- TODO: check inscription of a sign?
 | 
						|
-- TODO: invlist as dropdown of inventory lists at detected position
 | 
						|
 | 
						|
-- Which diffrent types of preconditions are available?
 | 
						|
-- -> The following fields are part of a precondition:
 | 
						|
-- 	p_id		the ID/key of the precondition/prerequirement
 | 
						|
-- 	p_type		selected from values_what; the staffs allow to use other
 | 
						|
-- 			types like "function" or "has_item" - but that is not
 | 
						|
-- 			supported here (cannot be edited or created; only be shown)
 | 
						|
-- 	p_value		used to store the subtype of p_type
 | 
						|
--
 | 
						|
-- a state/variable:
 | 
						|
--	p_variable	name of a variable the player has read access to;
 | 
						|
--	                dropdown list with allowed options
 | 
						|
--	p_operator	selected from values_operator
 | 
						|
--	p_var_cmp_value can be set freely by the player
 | 
						|
--
 | 
						|
-- a block in the world:
 | 
						|
--	p_pos		a position in the world; determined by asking the player
 | 
						|
--			to punch the block
 | 
						|
--	p_node		(follows from p_pos)
 | 
						|
--	p_param2	(follows from p_pos)
 | 
						|
--
 | 
						|
-- a trade defined as an action: no variables needed (buy and pay stack follow
 | 
						|
-- 			from the trade set as action)
 | 
						|
--
 | 
						|
-- an inventory:
 | 
						|
-- 	p_itemstack	an itemstack; needs to be a minetest.registered_item[..];
 | 
						|
-- 			size/count is also checked
 | 
						|
 | 
						|
-- some helper lists for creating the formspecs and evaulating
 | 
						|
-- the player's answers:
 | 
						|
 | 
						|
-- general direction of what a prerequirement may be about
 | 
						|
local check_what = {
 | 
						|
	"- please select -",
 | 
						|
	"an internal state (i.e. of a quest)", -- 2
 | 
						|
	"a block somewhere", -- 3
 | 
						|
	"a trade", -- 4
 | 
						|
	"the inventory of the player", -- 5
 | 
						|
	"the inventory of the NPC", -- 6
 | 
						|
}
 | 
						|
 | 
						|
-- how to store these as p_type in the precondition:
 | 
						|
local values_what = {"", "state", "block", "trade", "player_inv", "npc_inv"}
 | 
						|
 | 
						|
-- options for "a trade"
 | 
						|
local check_trade = {
 | 
						|
	"- please select -",
 | 
						|
	"The NPC has the item(s) he wants to sell in his inventory.", -- 2
 | 
						|
	"The player has the item(s) needed to pay the price.", -- 3
 | 
						|
	"The NPC ran out of stock.", -- 4
 | 
						|
	"The player cannot afford the price.", -- 5
 | 
						|
}
 | 
						|
 | 
						|
-- how to store these as p_value:
 | 
						|
local values_trade = {"", "npc_can_sell", "player_can_buy", "npc_is_out_of_stock", "player_has_not_enough"}
 | 
						|
 | 
						|
-- options for "the inventory of " (either player or NPC; perhaps blocks later on)
 | 
						|
local check_inv = {
 | 
						|
	"- please select -",
 | 
						|
	"The inventory contains the following item:",
 | 
						|
	"The inventory *does not* contain the following item:",
 | 
						|
	"There is room for the following item in the inventory:",
 | 
						|
	"The inventory is empty.",
 | 
						|
}
 | 
						|
 | 
						|
-- how to store these as p_value (the actual itemstack gets stored as p_itemstack):
 | 
						|
local values_inv = {"", "inv_contains", "inv_does_not_contain", "has_room_for", "inv_is_empty"}
 | 
						|
 | 
						|
local check_block = {
 | 
						|
	"- please select -",
 | 
						|
	"The block is as it is now.",
 | 
						|
	"There shall be air instead of this block.",
 | 
						|
	"The block is diffrent from how it is now.",
 | 
						|
	"I can't punch it. The block is as the block *above* the one I punched.",
 | 
						|
}
 | 
						|
 | 
						|
-- how to store these as p_value (the actual node data gets stored as p_node, p_param2 and p_pos):
 | 
						|
-- Note: "node_is_like" occours twice because it is used to cover blocks that
 | 
						|
--       cannot be punched as well as normal blocks.
 | 
						|
local values_block = {"", "node_is_like", "node_is_air", "node_is_diffrent_from", "node_is_like"}
 | 
						|
 | 
						|
-- comparison operators for variables
 | 
						|
local check_operator = {
 | 
						|
	"- please select -", -- 1
 | 
						|
	"== (is equal)", -- 2
 | 
						|
	"~= (is not equal)", -- 3
 | 
						|
	">= (is greater or equal)", -- 4
 | 
						|
	">  (is greater)", -- 5
 | 
						|
	"<= (is smaller or equal)", -- 6
 | 
						|
	"<  (is smaller)", -- 7
 | 
						|
	"not (logically invert)", -- 8
 | 
						|
	"is_set (has a value)", -- 9
 | 
						|
	"is_unset (has no value)" -- 10
 | 
						|
}
 | 
						|
 | 
						|
-- how to store these as p_value (the actual variable is stored in p_variable, and the value in p_cmp_value):
 | 
						|
local values_operator = {"", "==", "~=", ">=", ">", "<=", "<", "not", "is_set", "is_unset"}
 | 
						|
 | 
						|
-- some internal ones...
 | 
						|
local check_variable = {
 | 
						|
	"- please select -",
 | 
						|
	"(internal) hour of ingame day", -- 2
 | 
						|
	"(internal) player's health points", -- 3
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
-- get the list of variables the player has read access to
 | 
						|
yl_speak_up.get_sorted_player_var_list_read_access = function(pname)
 | 
						|
	local var_list = {}
 | 
						|
	-- copy the values from check_variable
 | 
						|
	for i, v in ipairs(check_variable) do
 | 
						|
		table.insert(var_list, v)
 | 
						|
	end
 | 
						|
	-- get the list of variables the player can read
 | 
						|
	local tmp = yl_speak_up.get_quest_variables_with_read_access(pname)
 | 
						|
	-- sort that list (the dropdown formspec element returns just an index)
 | 
						|
	table.sort(tmp)
 | 
						|
	for i, v in ipairs(tmp) do
 | 
						|
		table.insert(var_list, v)
 | 
						|
	end
 | 
						|
	return var_list
 | 
						|
end
 | 
						|
 | 
						|
 | 
						|
-- returns a human-readable text as description of the precondition
 | 
						|
-- (as shown in the edit options dialog and in the edit precondition formspec)
 | 
						|
yl_speak_up.show_precondition = function(p)
 | 
						|
	if(not(p.p_type) or p.p_type == "") then
 | 
						|
		return "(nothing): Always true."
 | 
						|
	elseif(p.p_type == "item") then
 | 
						|
		return "item: The player has \""..tostring(p.p_value).."\" in his inventory."
 | 
						|
	elseif(p.p_type == "quest") then
 | 
						|
		return "quest: Always false."
 | 
						|
	elseif(p.p_type == "auto") then
 | 
						|
		return "auto: Always true."
 | 
						|
	elseif(p.p_type == "function") then
 | 
						|
		return "function: evaluate "..tostring(p_value)
 | 
						|
	elseif(p.p_type == "state") then
 | 
						|
		if(not(p.p_operator)) then
 | 
						|
			return "Error: Operator not defined."
 | 
						|
		elseif(p.p_operator == "not") then
 | 
						|
			return "not( VALUE_OF[ "..tostring(p.p_variable).." ] )"
 | 
						|
		elseif(p.p_operator == "is_set") then
 | 
						|
			return "VALUE_OF[ "..tostring(p.p_variable).." ] ~= nil (is_set)"
 | 
						|
		elseif(p.p_operator == "is_unset") then
 | 
						|
			return "VALUE_OF[ "..tostring(p.p_variable).." ] == nil (is_unset)"
 | 
						|
		end
 | 
						|
		if(p.p_var_cmp_value == "") then
 | 
						|
			return "VALUE_OF[ "..tostring(p.p_variable).." ] "..tostring(p.p_operator).." \"\""
 | 
						|
		end
 | 
						|
		return "VALUE_OF[ "..tostring(p.p_variable).." ] "..tostring(p.p_operator).." "..
 | 
						|
			tostring(p.p_var_cmp_value)
 | 
						|
	elseif(p.p_type == "block") then
 | 
						|
		if(not(p.p_pos) or type(p.p_pos) ~= "table"
 | 
						|
		  or not(p.p_pos.x) or not(p.p_pos.y) or not(p.p_pos.z)) then
 | 
						|
			return "ERROR: p.p_pos is "..minetest.serialize(p.p_pos)
 | 
						|
		elseif(p.p_value == "node_is_like") then
 | 
						|
			return "The block at "..minetest.pos_to_string(p.p_pos).." is \""..
 | 
						|
				tostring(p.p_node).."\" with param2: "..tostring(p.p_param2).."."
 | 
						|
		elseif(p.p_value == "node_is_air") then
 | 
						|
			return "There is no block at "..minetest.pos_to_string(p.p_pos).."."
 | 
						|
		elseif(p.p_value == "node_is_diffrent_from") then
 | 
						|
			return "There is another block than \""..tostring(p.p_node).."\" at "..
 | 
						|
				minetest.pos_to_string(p.p_pos)..", or it is at least "..
 | 
						|
				"rotated diffrently (param2 is not "..tostring(p.p_param2)..")."
 | 
						|
		end
 | 
						|
	elseif(p.p_type == "trade") then
 | 
						|
		local nr = table.indexof(values_trade, p.p_value)
 | 
						|
		if(nr and check_trade[ nr ]) then
 | 
						|
			return check_trade[ nr ]
 | 
						|
		end
 | 
						|
	elseif(p.p_type == "player_inv" or p.p_type == "npc_inv") then
 | 
						|
		local who = "The player"
 | 
						|
		if(p.p_type == "npc_inv") then
 | 
						|
			who = "The NPC"
 | 
						|
		end
 | 
						|
		if(p.p_value == "inv_contains") then
 | 
						|
			return who.." has \""..tostring(p.p_itemstack).."\" in his inventory."
 | 
						|
		elseif(p.p_value == "inv_does_not_contain") then
 | 
						|
			return who.." does not have \""..tostring(p.p_itemstack).."\" in his inventory."
 | 
						|
		elseif(p.p_value == "has_room_for") then
 | 
						|
			return who.." has room for \""..tostring(p.p_itemstack).."\" in his inventory."
 | 
						|
		elseif(p.p_value == "inv_is_empty") then
 | 
						|
			return who.." has an empty inventory."
 | 
						|
		end
 | 
						|
	end
 | 
						|
	-- fallback
 | 
						|
	return tostring(p.p_value)
 | 
						|
end
 | 
						|
 | 
						|
 | 
						|
-- called by calculate_displayable_options(..);
 | 
						|
-- returns false if a single precondition is false
 | 
						|
-- Important: If something cannot be determined (i.e. the node is nil),
 | 
						|
--            *both* the condition and its inverse condition may be
 | 
						|
--            true (or false).
 | 
						|
yl_speak_up.eval_all_preconditions = function(player, prereq, o_id)
 | 
						|
	local pname = player:get_player_name()
 | 
						|
	local n_id = yl_speak_up.speak_to[pname].n_id
 | 
						|
	if(not(prereq)) then
 | 
						|
		yl_speak_up.debug_msg(player, n_id, o_id, "No preconditions given.")
 | 
						|
		-- no prerequirements? then they are automaticly fulfilled
 | 
						|
		return true
 | 
						|
	end
 | 
						|
	yl_speak_up.debug_msg(player, n_id, o_id, "Checking preconditions..")
 | 
						|
	for k, p in pairs(prereq) do
 | 
						|
		yl_speak_up.debug_msg(player, n_id, o_id, "..checking "..
 | 
						|
			tostring(p.p_id)..": "..yl_speak_up.show_precondition(p))
 | 
						|
		if(not(yl_speak_up.eval_precondition(player, n_id, p))) then
 | 
						|
			yl_speak_up.debug_msg(player, n_id, o_id, tostring(p.p_id)..
 | 
						|
				" -> is false. Aborting.")
 | 
						|
			-- no need to look any further - once we hit a false, it'll stay false
 | 
						|
			return false
 | 
						|
		end
 | 
						|
	end
 | 
						|
	-- all preconditions are true
 | 
						|
	yl_speak_up.debug_msg(player, n_id, o_id, "OK. All preconditions true.")
 | 
						|
	return true
 | 
						|
end
 | 
						|
 | 
						|
 | 
						|
-- checks if precondition p is true for the player and npc n_id
 | 
						|
yl_speak_up.eval_precondition = function(player, n_id, p)
 | 
						|
	if(not(p.p_type) or p.p_type == "") then
 | 
						|
		-- empty prerequirement: automaticly true (fallback)
 | 
						|
		return true
 | 
						|
	elseif(p.p_type == "item") then
 | 
						|
		-- a precondition set by using the staff;
 | 
						|
		-- aequivalent to p.p_type == "player_inv" and p.p_itemstack == "inv_contains"
 | 
						|
		return player:get_inventory():contains_item("main", p.p_value)
 | 
						|
	elseif(p.p_type == "quest") then
 | 
						|
		-- a precondition set by using the staff; intended as future quest interface?
 | 
						|
		return false
 | 
						|
	elseif(p.p_type == "auto") then
 | 
						|
		-- a precondition set by using the staff; kept for compatibility
 | 
						|
		return true
 | 
						|
	elseif(p.p_type == "function") then
 | 
						|
		-- a precondition set by using the staff;
 | 
						|
		-- extremly powerful (executes any lua code)
 | 
						|
		return yl_speak_up.eval_and_execute_function(player, p, "p_")
 | 
						|
	elseif(p.p_type == "state") then
 | 
						|
		local var_val = false
 | 
						|
		if(not(p.p_variable) or p.p_variable == "") then
 | 
						|
			-- broken precondition
 | 
						|
			return false
 | 
						|
		-- "(internal) hour of ingame day", -- 2
 | 
						|
		elseif(p.p_variable == check_variable[2]) then
 | 
						|
			-- timeofday is between 0..1; translate to 24 hours
 | 
						|
			var_val = math.floor((minetest.get_timeofday() * 24)+0.5)
 | 
						|
		-- "(internal) player's health points", -- 3
 | 
						|
		elseif(p.p_variable == check_variable[3]) then
 | 
						|
			var_val = player:get_hp()
 | 
						|
		else
 | 
						|
			local pname = player:get_player_name()
 | 
						|
			local owner = yl_speak_up.npc_owner[ n_id ]
 | 
						|
			-- get the value of the variable
 | 
						|
			var_val = yl_speak_up.get_quest_variable_value(owner, pname, p.p_variable)
 | 
						|
		end
 | 
						|
 | 
						|
		if(p.p_operator == "not") then
 | 
						|
			return not(var_val)
 | 
						|
		elseif(p.p_operator == "is_set") then
 | 
						|
			return var_val ~= nil
 | 
						|
		elseif(p.p_operator == "is_unset") then
 | 
						|
			return var_val == nil
 | 
						|
		-- for security reasons: do this manually instead of just evaluating a term
 | 
						|
		elseif(p.p_operator == "==") then
 | 
						|
			-- best do these comparisons in string form to make sure both are of same type
 | 
						|
			return tostring(var_val) == tostring(p.p_var_cmp_value)
 | 
						|
		elseif(p.p_operator == "~=") then
 | 
						|
			return tostring(var_val) ~= tostring(p.p_var_cmp_value)
 | 
						|
		elseif(p.p_operator == ">=") then
 | 
						|
			-- compare numeric if possible
 | 
						|
			if(tonumber(var_val) and tonumber(p.p_var_cmp_value)) then
 | 
						|
				return tonumber(var_val) >= tonumber(p.p_var_cmp_value)
 | 
						|
			-- fallback: compare as strings
 | 
						|
		else
 | 
						|
				return tostring(var_val) >= tostring(p.p_var_cmp_value)
 | 
						|
			end
 | 
						|
		elseif(p.p_operator == ">") then
 | 
						|
			if(tonumber(var_val) and tonumber(p.p_var_cmp_value)) then
 | 
						|
				return tonumber(var_val) >  tonumber(p.p_var_cmp_value)
 | 
						|
			else
 | 
						|
				return tostring(var_val) >  tostring(p.p_var_cmp_value)
 | 
						|
			end
 | 
						|
		elseif(p.p_operator == "<=") then
 | 
						|
			if(tonumber(var_val) and tonumber(p.p_var_cmp_value)) then
 | 
						|
				return tonumber(var_val) <= tonumber(p.p_var_cmp_value)
 | 
						|
			else
 | 
						|
				return tostring(var_val) <= tostring(p.p_var_cmp_value)
 | 
						|
			end
 | 
						|
		elseif(p.p_operator == "<") then
 | 
						|
			if(tonumber(var_val) and tonumber(p.p_var_cmp_value)) then
 | 
						|
				return tonumber(var_val) <  tonumber(p.p_var_cmp_value)
 | 
						|
			else
 | 
						|
				return tostring(var_val) <  tostring(p.p_var_cmp_value)
 | 
						|
			end
 | 
						|
		end
 | 
						|
		-- unsupported operator
 | 
						|
		return false
 | 
						|
	elseif(p.p_type == "block") then
 | 
						|
		if(not(p.p_pos) or type(p.p_pos) ~= "table"
 | 
						|
		  or not(p.p_pos.x) or not(p.p_pos.y) or not(p.p_pos.z)) then
 | 
						|
			return false
 | 
						|
		elseif(p.p_value == "node_is_like") then
 | 
						|
			local node = minetest.get_node_or_nil(p.p_pos)
 | 
						|
			return (node and node.name and node.name == p.p_node and node.param2 == p.p_param2)
 | 
						|
		elseif(p.p_value == "node_is_air") then
 | 
						|
			local node = minetest.get_node_or_nil(p.p_pos)
 | 
						|
			return (node and node.name and node.name == "air")
 | 
						|
		elseif(p.p_value == "node_is_diffrent_from") then
 | 
						|
			local node = minetest.get_node_or_nil(p.p_pos)
 | 
						|
			return (node and node.name and (node.name ~= p.p_node or node.param2 ~= p.p_param2))
 | 
						|
		end
 | 
						|
		-- fallback - unsupported option
 | 
						|
		return false
 | 
						|
	elseif(p.p_type == "trade") then
 | 
						|
		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
 | 
						|
		-- if there is no trade, then this condition is true
 | 
						|
		if(not(dialog) or not(dialog.trades) or not(d_id) or not(o_id)) then
 | 
						|
			return true
 | 
						|
		end
 | 
						|
		local trade = dialog.trades[ tostring(d_id).." "..tostring(o_id) ]
 | 
						|
		-- something is wrong with the trade
 | 
						|
		if(not(trade)
 | 
						|
		  or not(trade.pay) or not(trade.pay[1]) or not(trade.buy) or not(trade.buy[1])) then
 | 
						|
			return false
 | 
						|
		end
 | 
						|
		if(    p.p_value == "npc_can_sell") then
 | 
						|
			local npc_inv = minetest.get_inventory({type="detached",
 | 
						|
				name="yl_speak_up_npc_"..tostring(n_id)})
 | 
						|
			return npc_inv:contains_item("npc_main", trade.buy[1])
 | 
						|
		elseif(p.p_value == "npc_is_out_of_stock") then
 | 
						|
			local npc_inv = minetest.get_inventory({type="detached",
 | 
						|
				name="yl_speak_up_npc_"..tostring(n_id)})
 | 
						|
			return not(npc_inv:contains_item("npc_main", trade.buy[1]))
 | 
						|
		elseif(p.p_value == "player_can_buy") then
 | 
						|
			local player_inv = player:get_inventory()
 | 
						|
			return player_inv:contains_item("main", trade.pay[1])
 | 
						|
		elseif(p.p_value == "player_has_not_enough") then
 | 
						|
			local player_inv = player:get_inventory()
 | 
						|
			return not(player_inv:contains_item("main", trade.pay[1]))
 | 
						|
		end
 | 
						|
		return false
 | 
						|
	elseif(p.p_type == "player_inv" or p.p_type == "npc_inv") then
 | 
						|
		local inv = nil
 | 
						|
		local inv_name = "main"
 | 
						|
		-- determine the right inventory
 | 
						|
		if(p.p_type == "player_inv") then
 | 
						|
			inv = player:get_inventory()
 | 
						|
		else
 | 
						|
			inv = minetest.get_inventory({type="detached",
 | 
						|
                                name="yl_speak_up_npc_"..tostring(n_id)})
 | 
						|
			inv_name = "npc_main"
 | 
						|
		end
 | 
						|
		if(    p.p_itemstack and p.p_value == "inv_contains") then
 | 
						|
			return inv:contains_item(inv_name, p.p_itemstack)
 | 
						|
		elseif(p.p_itemstack and p.p_value == "inv_does_not_contain") then
 | 
						|
			return not(inv:contains_item(inv_name, p.p_itemstack))
 | 
						|
		elseif(p.p_itemstack and p.p_value == "has_room_for") then
 | 
						|
			return inv:room_for_item(inv_name, p.p_itemstack)
 | 
						|
		elseif(p.p_value == "inv_is_empty") then
 | 
						|
			return inv:is_empty(inv_name)
 | 
						|
		end
 | 
						|
		return false
 | 
						|
	end
 | 
						|
	-- fallback - unknown type
 | 
						|
	return false
 | 
						|
end
 | 
						|
 | 
						|
 | 
						|
-- these are only wrapper functions for those in fs_edit_general.lua
 | 
						|
 | 
						|
yl_speak_up.input_fs_edit_preconditions = function(player, formname, fields)
 | 
						|
	return yl_speak_up.input_fs_edit_option_related(player, formname, fields,
 | 
						|
		"p_", "o_prerequisites", yl_speak_up.max_prerequirements,
 | 
						|
		"pre(C)ondition", "tmp_prereq",
 | 
						|
		"Please punch the block you want to check in your precondition!",
 | 
						|
		values_what, values_operator, values_block, values_trade, values_inv,
 | 
						|
		check_what, check_operator, check_block, check_trade, check_inv,
 | 
						|
		-- player variables with read access
 | 
						|
		yl_speak_up.get_sorted_player_var_list_read_access,
 | 
						|
		"edit_preconditions"
 | 
						|
		)
 | 
						|
end
 | 
						|
 | 
						|
 | 
						|
yl_speak_up.get_fs_edit_preconditions = function(player, table_click_result)
 | 
						|
	return yl_speak_up.get_fs_edit_option_related(player, table_click_result,
 | 
						|
		"p_", "o_prerequisites", yl_speak_up.max_prerequirements,
 | 
						|
		"pre(C)ondition", "tmp_prereq",
 | 
						|
		"What do you want to check in this precondition?",
 | 
						|
		check_what, check_operator, check_block, check_trade, check_inv,
 | 
						|
		-- player variables with read access
 | 
						|
		yl_speak_up.get_sorted_player_var_list_read_access,
 | 
						|
		-- show one precondition element
 | 
						|
		yl_speak_up.show_precondition,
 | 
						|
		"table_of_preconditions",
 | 
						|
		"The following expression shall be true:", "Operator:", "Value to compare with:",
 | 
						|
		"The following shall be true about the block:"
 | 
						|
		)
 | 
						|
end
 |