diff --git a/fs_edit_preconditions.lua b/fs_edit_preconditions.lua index 0d8a6a1..aa7e934 100644 --- a/fs_edit_preconditions.lua +++ b/fs_edit_preconditions.lua @@ -109,7 +109,17 @@ local check_variable = { -- 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(p.p_type == "state") then + 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(p.p_operator == "not") then return "not( "..tostring(p.p_variable).." )" elseif(p.p_operator == "is_set") then @@ -158,6 +168,141 @@ yl_speak_up.show_precondition = function(p) 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) + if(not(prereq)) then +minetest.chat_send_player(player:get_player_name(), "No preconditions given for "..tostring(o_id)..".") + -- no prerequirements? then they are automaticly fulfilled + return true + end + local pname = player:get_player_name() +minetest.chat_send_player(pname, "Checking preconditions for "..tostring(o_id).."..") -- TODO + local n_id = yl_speak_up.speak_to[pname].n_id + for k, p in pairs(prereq) do +minetest.chat_send_player(pname, "..checking "..tostring(p.p_id)..": "..yl_speak_up.show_precondition(p)) -- TODO + if(not(yl_speak_up.eval_precondition(player, n_id, p))) then + -- no need to look any further - once we hit a false, it'll stay false +minetest.chat_send_player(pname, " -> is false! Aborting.") -- TODO + return false + end + end + -- all preconditions are true +minetest.chat_send_player(pname, "All preconditions true for "..tostring(o_id)..".") -- TODO + 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_precondition_function(player, p) + elseif(p.p_type == "state") then + -- TODO: implement +--[[ + if(p.p_operator == "not") then + return "not( "..tostring(p.p_variable).." )" + elseif(p.p_operator == "is_set") then + return tostring(p.p_variable).." ~= nil (is_set)" + elseif(p.p_operator == "is_unset") then + return tostring(p.p_variable).." == nil (is_unset)" + end + return 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 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 + + yl_speak_up.input_fs_edit_preconditions = function(player, formname, fields) if(not(player)) then return diff --git a/functions.lua b/functions.lua index 1eba5fe..104f559 100644 --- a/functions.lua +++ b/functions.lua @@ -349,6 +349,70 @@ local function delete_option(n_id, d_id, o_id) end +-- evaluate those preconditions of type "function" (set by the staff) +-- WARNING: This is extremly powerful! +-- The code is taken out of the function +-- calculate_displayable_options(..) +-- (written by AliasAlreadyTaken). +-- It requires the npc_master priv to add or edit this prereq. +-- The function is called by yl_speak_up.eval_precondition +yl_speak_up.eval_precondition_function = function(player, p_v) + local pname = player:get_player_name() + + --minetest.chat_send_all("this is in a single prereq: "..dump(p_v)) + local p_id = p_v.p_id + if p_v.p_type ~= "function" then + return true + end + + local code = p_v.p_value + if code:byte(1) == 27 then + local obj = yl_speak_up.speak_to[pname].obj + local n_id = yl_speak_up.speak_to[pname].n_id + local npc = get_number_from_id(n_id) + if obj:get_luaentity() and tonumber(npc) then + minetest.log("error","[MOD] yl_speak_up: NPC with ID n_"..npc.." at position "..minetest.pos_to_string(obj:get_pos(),0).." could not compile the content of "..p_id.." :"..dump(code) .. " because of illegal bytecode for player "..pname) + else + minetest.log("error","[MOD] yl_speak_up: NPC with ID unknown could not compile the content of "..p_id.." :"..dump(code) .. " for player unknown because of illegal bytecode") + end + end + + local f, msg = loadstring("return function(playername) " .. code .. " end") + + if not f then + local obj = yl_speak_up.speak_to[pname].obj + local n_id = yl_speak_up.speak_to[pname].n_id + local npc = get_number_from_id(n_id) + if obj:get_luaentity() and tonumber(npc) then + minetest.log("error","[MOD] yl_speak_up: NPC with ID n_"..npc.." at position "..minetest.pos_to_string(obj:get_pos(),0).." could not compile the content of "..p_id.." :"..dump(code) .. " for player "..pname) + else + minetest.log("error","[MOD] yl_speak_up: NPC with ID unknown could not compile the content of "..p_id.." :"..dump(code) .. " for player unknown") + end + else + local func = f() + + local ok, ret = pcall(func,pname) + + if not ok then + local obj = yl_speak_up.speak_to[pname].obj + local n_id = yl_speak_up.speak_to[pname].n_id + local npc = get_number_from_id(n_id) + if obj:get_luaentity() and tonumber(npc) then + minetest.log("error","[MOD] yl_speak_up: NPC with ID n_"..npc.." at position "..minetest.pos_to_string(obj:get_pos(),0).." could not execute the content of "..p_id.." :"..dump(code) .. " for player "..pname) + else + minetest.log("error","[MOD] yl_speak_up: NPC with ID unknown could not execute the content of "..p_id.." :"..dump(code) .. " for player unknown") + end + end + + if type(ret) == "boolean" then + return ret + end + end + -- fallback + return false +end + + local function calculate_displayable_options(pname, d_options) -- Let's go through all the options and see if we need to display them to the user @@ -361,113 +425,13 @@ local function calculate_displayable_options(pname, d_options) end for o_k, o_v in pairs(d_options) do - --minetest.chat_send_all("#####"..o_k.."#####") - local o_p_met = true - if o_v.o_prerequisites == nil then - --minetest.chat_send_all("display this because no prereq:"..dump(o_v)) - else - --minetest.chat_send_all("prereqs exists") - if next(o_v.o_prerequisites) == nil then - --minetest.chat_send_all("prereqs exist, but empty") - else - --minetest.chat_send_all("prereqs exists and not empty") - --minetest.chat_send_all("if all prereqs are met, then we can display the option") - local p_met = {} - for p_k, p_v in pairs(o_v.o_prerequisites) do - --minetest.chat_send_all("this is in a single prereq: "..dump(p_v)) - p_met[p_k] = false - local p_id = p_v.p_id - if p_v.p_type == "item" then - --minetest.chat_send_all("item! Does the player have this thing?") - if player:get_inventory():contains_item("main", p_v.p_value) then - --minetest.chat_send_all("found item:"..p_v.p_value) - p_met[p_k] = true - end - end - if p_v.p_type == "quest" then - --minetest.chat_send_all("quest! let's call the quest api?") - p_met[p_k] = false - end - if p_v.p_type == "function" then - - local code = p_v.p_value - if code:byte(1) == 27 then - local obj = yl_speak_up.speak_to[pname].obj - local n_id = yl_speak_up.speak_to[pname].n_id - local npc = get_number_from_id(n_id) - if obj:get_luaentity() and tonumber(npc) then - minetest.log("error","[MOD] yl_speak_up: NPC with ID n_"..npc.." at position "..minetest.pos_to_string(obj:get_pos(),0).." could not compile the content of "..p_id.." :"..dump(code) .. " because of illegal bytecode for player "..pname) - else - minetest.log("error","[MOD] yl_speak_up: NPC with ID unknown could not compile the content of "..p_id.." :"..dump(code) .. " for player unknown because of illegal bytecode") - end - end - - local f, msg = loadstring("return function(playername) " .. code .. " end") - - if not f then - local obj = yl_speak_up.speak_to[pname].obj - local n_id = yl_speak_up.speak_to[pname].n_id - local npc = get_number_from_id(n_id) - if obj:get_luaentity() and tonumber(npc) then - minetest.log("error","[MOD] yl_speak_up: NPC with ID n_"..npc.." at position "..minetest.pos_to_string(obj:get_pos(),0).." could not compile the content of "..p_id.." :"..dump(code) .. " for player "..pname) - else - minetest.log("error","[MOD] yl_speak_up: NPC with ID unknown could not compile the content of "..p_id.." :"..dump(code) .. " for player unknown") - end - else - - - local func = f() - - local ok, ret = pcall(func,pname) - - if not ok then - local obj = yl_speak_up.speak_to[pname].obj - local n_id = yl_speak_up.speak_to[pname].n_id - local npc = get_number_from_id(n_id) - if obj:get_luaentity() and tonumber(npc) then - minetest.log("error","[MOD] yl_speak_up: NPC with ID n_"..npc.." at position "..minetest.pos_to_string(obj:get_pos(),0).." could not execute the content of "..p_id.." :"..dump(code) .. " for player "..pname) - else - minetest.log("error","[MOD] yl_speak_up: NPC with ID unknown could not execute the content of "..p_id.." :"..dump(code) .. " for player unknown") - end - end - - if type(ret) == "boolean" then - p_met[p_k] = ret - end - - end - - - - end - if p_v.p_type == "auto" then - --minetest.chat_send_all("auto! what shall we do now?") - p_met[p_k] = true - end - end - -- is there a "false" in the p_met ? - for m_k, m_v in pairs(p_met) do - --minetest.chat_send_all(o_k..",m_v="..dump(m_v)) - if m_v == false then - o_p_met = false - end - end - end - end -- Can we display this option? - retval[o_k] = o_p_met - --[[ - if o_p_met == true then - minetest.chat_send_all("Prereqs say YES") - retval[o_k] = o_p_met - else - minetest.chat_send_all("Prereqs say NOO") - end - ]]-- + retval[o_k] = yl_speak_up.eval_all_preconditions(player, o_v.o_prerequisites, o_k) end return retval end + local function calculate_portrait(pname, n_id) local tex = yl_speak_up.speak_to[pname].textures