From 9039fe08b98e98d5fe272dc9af9baa6d7f411ceb Mon Sep 17 00:00:00 2001 From: Sokomine Date: Wed, 5 Jan 2022 19:07:23 +0100 Subject: [PATCH] split up fs_edit_preconditions into fs_edit and exec_eval_ --- exec_eval_preconditions.lua | 354 +++++++++++++++++++++++++++++++++++ fs_edit_preconditions.lua | 358 +----------------------------------- init.lua | 2 + 3 files changed, 359 insertions(+), 355 deletions(-) create mode 100644 exec_eval_preconditions.lua diff --git a/exec_eval_preconditions.lua b/exec_eval_preconditions.lua new file mode 100644 index 0000000..2477110 --- /dev/null +++ b/exec_eval_preconditions.lua @@ -0,0 +1,354 @@ +-- This file contains what is necessary to execute/evaluate a precondition. + +-- this is called directly in yl_speak_up.get_fs_talkdialog +-- it returns a list of options whose preconditions are fulfilled +-- allow_recursion may be false - we need to avoid infinite loops +yl_speak_up.calculate_displayable_options = function(pname, d_options, in_edit_mode, allow_recursion) + -- Let's go through all the options and see if we need to display them to the user + + local retval = {} + + local player = minetest.get_player_by_name(pname) + + if d_options == nil then + return {} + end + + -- sort the entries by o_sort so that preconditions referencing options earlier in the + -- list can work without causing loops or the like + local sorted_list = yl_speak_up.get_sorted_options(d_options, "o_sort") + for i, o_k in ipairs(sorted_list) do + if(not(in_edit_mode)) then + local o_v = d_options[ o_k ] + -- Can we display this option? + retval[o_k] = yl_speak_up.eval_all_preconditions(player, o_v.o_prerequisites, o_k, retval) + -- do we need to take care of an automatic autoanswer? + if(retval[o_k] and retval[o_k] == true and o_v.o_autoanswer and o_v.o_autoanswer == 1 + and allow_recursion) then + -- abort here - because we already know which option needs to be selected next + retval["autoanswer"] = o_k + return retval + end + -- if in edit mode: no need to evaluate preconditions + else + retval[o_k ] = true + end + end + return retval +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, other_options_true_or_false) + 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, pname)) + if(not(yl_speak_up.eval_precondition(player, n_id, p, other_options_true_or_false))) 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, other_options_true_or_false) + 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 + -- the owner is alrady encoded in the variable name + var_val = yl_speak_up.get_quest_variable_value(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 + if(p.p_var_cmp_value == nil) then + return false + end + -- 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 + if(p.p_var_cmp_value == nil) then + return false + end + -- 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(p.p_var_cmp_value == nil) then + return false + end + 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(p.p_var_cmp_value == nil) then + return false + end + 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(p.p_var_cmp_value == nil) then + return false + end + 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 == "more_than_x_seconds_ago") then + if(p.p_var_cmp_value == nil) then + return false + end + if(not(tonumber(var_val)) or not(tonumber(p.p_var_cmp_value))) then + return true + end + return (tonumber(var_val) + tonumber(p.p_var_cmp_value)) < + math.floor(minetest.get_us_time()/1000000) + elseif(p.p_operator == "less_than_x_seconds_ago") then + if(p.p_var_cmp_value == nil) then + return false + end + if(not(tonumber(var_val)) or not(tonumber(p.p_var_cmp_value))) then + return false + end + return (tonumber(var_val) + tonumber(p.p_var_cmp_value)) > + minetest.get_us_time()/1000000 + -- this is currently equivalent to >= but may change in the future + -- TODO: quest steps may be strings in the future + elseif(p.p_operator == "quest_step_done") then + -- if the variable is not set at all, then the quest step definitely + -- has not been reached yet + if((p.p_var_cmp_value == nil) or (var_val == nil)) then + return false + end + -- 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 + -- this is currently equivalent to < but may change in the future + -- TODO: quest steps may be strings in the future + elseif(p.p_operator == "quest_step_not_done") then + -- if the variable is not set at all, then the quest step definitely + -- has not been reached yet + if((p.p_var_cmp_value == nil) or (var_val == nil)) then + return true + end + 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" or p.p_type == "block_inv") then + local inv = nil + local inv_name = "main" + -- determine the right inventory + if(p.p_type == "player_inv") then + inv = player:get_inventory() + elseif(p.p_type == "npc_inv") then + inv = minetest.get_inventory({type="detached", + name="yl_speak_up_npc_"..tostring(n_id)}) + inv_name = "npc_main" + elseif(p.p_type == "block_inv") 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 + end + local meta = minetest.get_meta(p.p_pos) + if(not(meta)) then + return false + end + inv = meta:get_inventory() + if(not(inv)) then + return false + end + inv_name = p.p_inv_list_name + 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 + elseif(p.p_type == "player_offered_item") then + 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 + return false -- empty stack + end + local stack_wanted = ItemStack(p.p_value) + -- check group + if(p.p_item_group and p.p_item_group ~= "") then + local g = minetest.get_item_group(stack_got:get_name(), p.p_item_group) + if(not(g) or g == 0) then + return false -- wrong group + end + -- or: check item name + elseif(stack_got:get_name() ~= stack_wanted:get_name()) then + return false -- wrong item + end + -- independent of that: check stack size + if(p.p_match_stack_size and p.p_match_stack_size ~= "") then + local c_got = stack_got:get_count() + local c_wanted = stack_wanted:get_count() + if( p.p_match_stack_size == "exactly" and c_got ~= c_wanted) then + return false -- not exactly the same amount as the wanted one + elseif(p.p_match_stack_size == "less" and c_got >= c_wanted) then + return false -- didn't get less than the given number + elseif(p.p_match_stack_size == "more" and c_got <= c_wanted) then + return false -- didn't get more than the given number + elseif(p.p_match_stack_size == "another" and c_got == c_wanted) then + return false -- got the same than the given number + end + end + -- check quest_id + if(p.p_item_quest_id and p.p_item_quest_id ~= "") then + local meta = stack_got:get_meta() + -- 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") ~= p.p_item_quest_id) then + return false -- wrong quest_id + end + -- was this quest item given to another player? + if(meta:get_string("yl_speak_up:quest_item_for") ~= pname) then + return false -- wrong player + end + end + -- all ok + return true + elseif(p.p_type == "custom") then + -- execute the custom function + return yl_speak_up.precondition_custom(player, p.p_value) + elseif(p.p_type == "other") then + -- are the preconditions of another option fulfilled? + return (p.p_value + and other_options_true_or_false + and other_options_true_or_false[ p.p_value ] ~= nil + and tostring(other_options_true_or_false[ p.p_value ]) == tostring(p.p_fulfilled)) + end + -- fallback - unknown type + return false +end diff --git a/fs_edit_preconditions.lua b/fs_edit_preconditions.lua index 9e16c6c..edc1aff 100644 --- a/fs_edit_preconditions.lua +++ b/fs_edit_preconditions.lua @@ -1,6 +1,8 @@ -- TODO: check inscription of a sign? -- TODO: check mesecons related things? - +-- +-- This file contains what is necessary to add/edit a precondition. +-- -- Which diffrent types of preconditions are available? -- -> The following fields are part of a precondition: -- p_id the ID/key of the precondition/prerequirement @@ -303,360 +305,6 @@ yl_speak_up.show_precondition = function(p, pname) end --- this is called directly in yl_speak_up.get_fs_talkdialog --- it returns a list of options whose preconditions are fulfilled --- allow_recursion may be false - we need to avoid infinite loops -yl_speak_up.calculate_displayable_options = function(pname, d_options, in_edit_mode, allow_recursion) - -- Let's go through all the options and see if we need to display them to the user - - local retval = {} - - local player = minetest.get_player_by_name(pname) - - if d_options == nil then - return {} - end - - -- sort the entries by o_sort so that preconditions referencing options earlier in the - -- list can work without causing loops or the like - local sorted_list = yl_speak_up.get_sorted_options(d_options, "o_sort") - for i, o_k in ipairs(sorted_list) do - if(not(in_edit_mode)) then - local o_v = d_options[ o_k ] - -- Can we display this option? - retval[o_k] = yl_speak_up.eval_all_preconditions(player, o_v.o_prerequisites, o_k, retval) - -- do we need to take care of an automatic autoanswer? - if(retval[o_k] and retval[o_k] == true and o_v.o_autoanswer and o_v.o_autoanswer == 1 - and allow_recursion) then - -- abort here - because we already know which option needs to be selected next - retval["autoanswer"] = o_k - return retval - end - -- if in edit mode: no need to evaluate preconditions - else - retval[o_k ] = true - end - end - return retval -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, other_options_true_or_false) - 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, pname)) - if(not(yl_speak_up.eval_precondition(player, n_id, p, other_options_true_or_false))) 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, other_options_true_or_false) - 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 - -- the owner is alrady encoded in the variable name - var_val = yl_speak_up.get_quest_variable_value(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 - if(p.p_var_cmp_value == nil) then - return false - end - -- 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 - if(p.p_var_cmp_value == nil) then - return false - end - -- 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(p.p_var_cmp_value == nil) then - return false - end - 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(p.p_var_cmp_value == nil) then - return false - end - 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(p.p_var_cmp_value == nil) then - return false - end - 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 == "more_than_x_seconds_ago") then - if(p.p_var_cmp_value == nil) then - return false - end - if(not(tonumber(var_val)) or not(tonumber(p.p_var_cmp_value))) then - return true - end - return (tonumber(var_val) + tonumber(p.p_var_cmp_value)) < - math.floor(minetest.get_us_time()/1000000) - elseif(p.p_operator == "less_than_x_seconds_ago") then - if(p.p_var_cmp_value == nil) then - return false - end - if(not(tonumber(var_val)) or not(tonumber(p.p_var_cmp_value))) then - return false - end - return (tonumber(var_val) + tonumber(p.p_var_cmp_value)) > - minetest.get_us_time()/1000000 - -- this is currently equivalent to >= but may change in the future - -- TODO: quest steps may be strings in the future - elseif(p.p_operator == "quest_step_done") then - -- if the variable is not set at all, then the quest step definitely - -- has not been reached yet - if((p.p_var_cmp_value == nil) or (var_val == nil)) then - return false - end - -- 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 - -- this is currently equivalent to < but may change in the future - -- TODO: quest steps may be strings in the future - elseif(p.p_operator == "quest_step_not_done") then - -- if the variable is not set at all, then the quest step definitely - -- has not been reached yet - if((p.p_var_cmp_value == nil) or (var_val == nil)) then - return true - end - 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" or p.p_type == "block_inv") then - local inv = nil - local inv_name = "main" - -- determine the right inventory - if(p.p_type == "player_inv") then - inv = player:get_inventory() - elseif(p.p_type == "npc_inv") then - inv = minetest.get_inventory({type="detached", - name="yl_speak_up_npc_"..tostring(n_id)}) - inv_name = "npc_main" - elseif(p.p_type == "block_inv") 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 - end - local meta = minetest.get_meta(p.p_pos) - if(not(meta)) then - return false - end - inv = meta:get_inventory() - if(not(inv)) then - return false - end - inv_name = p.p_inv_list_name - 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 - elseif(p.p_type == "player_offered_item") then - 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 - return false -- empty stack - end - local stack_wanted = ItemStack(p.p_value) - -- check group - if(p.p_item_group and p.p_item_group ~= "") then - local g = minetest.get_item_group(stack_got:get_name(), p.p_item_group) - if(not(g) or g == 0) then - return false -- wrong group - end - -- or: check item name - elseif(stack_got:get_name() ~= stack_wanted:get_name()) then - return false -- wrong item - end - -- independent of that: check stack size - if(p.p_match_stack_size and p.p_match_stack_size ~= "") then - local c_got = stack_got:get_count() - local c_wanted = stack_wanted:get_count() - if( p.p_match_stack_size == "exactly" and c_got ~= c_wanted) then - return false -- not exactly the same amount as the wanted one - elseif(p.p_match_stack_size == "less" and c_got >= c_wanted) then - return false -- didn't get less than the given number - elseif(p.p_match_stack_size == "more" and c_got <= c_wanted) then - return false -- didn't get more than the given number - elseif(p.p_match_stack_size == "another" and c_got == c_wanted) then - return false -- got the same than the given number - end - end - -- check quest_id - if(p.p_item_quest_id and p.p_item_quest_id ~= "") then - local meta = stack_got:get_meta() - -- 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") ~= p.p_item_quest_id) then - return false -- wrong quest_id - end - -- was this quest item given to another player? - if(meta:get_string("yl_speak_up:quest_item_for") ~= pname) then - return false -- wrong player - end - end - -- all ok - return true - elseif(p.p_type == "custom") then - -- execute the custom function - return yl_speak_up.precondition_custom(player, p.p_value) - elseif(p.p_type == "other") then - -- are the preconditions of another option fulfilled? - return (p.p_value - and other_options_true_or_false - and other_options_true_or_false[ p.p_value ] ~= nil - and tostring(other_options_true_or_false[ p.p_value ]) == tostring(p.p_fulfilled)) - 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) diff --git a/init.lua b/init.lua index 824cf3e..b53aa91 100644 --- a/init.lua +++ b/init.lua @@ -27,6 +27,8 @@ dofile(modpath .. "fs_save_or_discard_or_back.lua") -- of course you can change them only if you have access to -- the server's file system or can execute lua code. dofile(modpath .. "custrom_functions_you_can_override.lua") +-- execute preconditions, actions and effects +dofile(modpath .. "exec_eval_preconditions.lua") -- some helper functions for formatting text for a formspec talbe dofile(modpath .. "print_as_table.lua") -- create i.e. a dropdown list of player names