-- Which diffrent types of effects are available? -- -> The following fields are part of an effect/result: -- r_id the ID/key of the effect/result -- r_type selected from values_what; the staffs allow to use other -- types like "function" or "give_item" etc. - but that is not -- supported here (cannot be edited or created; only be shown) -- r_value used to store the subtype of r_type -- -- a state/variable: -- r_variable name of a variable the player has *write* access to; -- dropdown list with allowed options -- r_operator selected from values_operator -- r_var_cmp_value can be set freely by the player (the variable will be -- set to this value) -- -- a block in the world: -- r_pos a position in the world; determined by asking the player -- to punch the block -- r_node (follows from r_pos) -- r_param2 (follows from r_pos) -- -- a craft receipe: -- r_value the expected craft result -- r_craft_grid array containing the stacks in the 9 craft grid fields in string form -- -- on_failure: -- r_value alternate target dialog if the action failed -- -- chat_all: -- r_value chat message sent to all players -- -- Unlike in preconditions, trade (the trade action already happened) and -- inventory actions are not supported as effects. -- -- some helper lists for creating the formspecs and evaulating -- the player's answers: -- general direction of what could make up an effect local check_what = { "- please select -", "an internal state (i.e. of a quest)", -- 2 "a block somewhere", -- 3 "NPC crafts soemthing", -- 4 "go to other dialog if the action (i.e. trade) failed", -- 5 "send a chat message to all players", -- 6 } -- how to store these as r_type in the precondition: local values_what = {"", "state", "block", "craft", "on_failure", "chat_all"} -- unlike in the preconditions, the "I cannot punch it" option is -- not offered here - because the player (and later the NPC) needs -- to be able to build at this position local check_block = { "- please select -", -- 1 "If there is air: Place a block so that it looks like now.", -- 2 "If there is a block: Dig it.", -- 3 -- TODO: not sure if punching a block (as an npc) is possible without bugs "Punch the block.", -- 4 -- TODO: not sure if right-clicking a block (as an npc) is possible without bugs "Right-click the block.", -- 5 } -- how to store these as p_value (the actual node data gets stored as p_node, p_param2 and p_pos): local values_block = {"", "place", "dig", "punch", "right-click"} -- comparison operators for variables local check_operator = { "- please select -", -- 1 "set to value:", -- 2 "is no longer needed (unset)", -- 3 } -- how to store these as r_value (the actual variable is stored in r_variable, and the value in r_new_value): local values_operator = {"", "set_to", "unset"} -- get the list of variables the player has *write* access to yl_speak_up.get_sorted_player_var_list_write_access = function(pname) local var_list = {} -- some values - like hour of day or HP of the player - can be read in -- a precondition but not be modified -- get the list of variables the player can *write* local tmp = yl_speak_up.get_quest_variables_with_write_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 effects -- (as shown in the edit options dialog and in the edit effect formspec) yl_speak_up.show_effect = function(r) if(not(r.r_type) or r.r_type == "") then return "(nothing): Nothing to do. No effect." elseif(r.r_type == "give_item") then return "give_item: Add \""..tostring(r.r_value).."\" to the player's inventory." elseif(r.r_type == "take_item") then return "take_item: Take \""..tostring(r.r_value).."\" from the player's inventory." elseif(r.r_type == "move") then return "move: Move the player to "..tostring(r.r_value).."." elseif(r.r_type == "function") then return "function: execute \""..tostring(r.r_value).."\"." elseif(r.r_type == "trade") then return "trade: obsolete (now defined as an action)" elseif(r.r_type == "dialog") then return "Switch to dialog \""..tostring(r.r_value).."\"." elseif(r.r_type == "state") then if(not(r.r_operator)) then return "Error: Operator not defined." elseif(r.r_operator == "set_to") then return "set VARIABLE[ "..tostring(r.r_variable).." ] to value \"".. tostring(r.r_var_cmp_value).."\"" elseif(r.r_operator == "unset") then return "discard VARIABLE[ "..tostring(r.r_variable).." ] (unset)" else return "ERROR: Wrong operator \""..tostring(r.r_operator).."\" for ".. "VARIABLE[ "..tostring(r.r_variable).." ]" end elseif(r.r_type == "block") then 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 return "ERROR: r.r_pos is "..minetest.serialize(r.r_pos) elseif(r.r_value == "place") then return "Place \""..tostring(r.r_node).."\" with param2: "..tostring(r.r_param2).. " at "..minetest.pos_to_string(p.p_pos).."." elseif(r.r_value == "dig") then return "Dig the block at "..minetest.pos_to_string(r.r_pos).."." elseif(r.r_value == "punch") then return "Punch the block at "..minetest.pos_to_string(r.r_pos).."." elseif(r.r_value == "right-click") then return "Right-click the block at "..minetest.pos_to_string(r.r_pos).."." else return "ERROR: Don't know what to do with the block at ".. minetest.pos_to_string(r.r_pos)..": \""..tostring(r.r_value).."\"?" end elseif(r.r_type == "craft") then if(not(r.r_value) or not(r.r_craft_grid)) then return "ERROR: Crafting not configured correctly." end -- TODO: check here if the craft receipe is broken? return "Craft \""..tostring(r.r_value).."\" from "..table.concat(r.r_craft_grid, ", ").."." elseif(r.r_type == "on_failure") then return "If the action (i.e. trade) failed, go to dialog \""..tostring(r.r_value).. "\"." elseif(r.r_type == "chat_all") then return "Send chat message: \""..tostring(r.r_value).."\"" end -- fallback return tostring(r.r_value) end -- executes an effect/result r for the player and npc n_id; -- returns true on success (relevant for on_failure) -- TODO: in edit mode, nothing gets executed except perhaps dialog 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 == "dialog") then -- TODO: how to handle target dialog? return true elseif(r.r_type == "function") then return yl_speak_up.eval_and_execute_function(player, r, "r_") elseif(r.r_type == "give_item") then if(not(v.r_value)) then return false end local item = ItemStack(v.r_value) if(not(minetest.registered_items[item:get_name()])) then yl_speak_up.debug_msg(player, n_id, o_id, tostring(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_id).." ".. "give_item: "..tostring(item:get_name()).." failed.") return false end return true elseif(r.r_type == "take_item") then if(not(v.r_value)) then return false end local item = ItemStack(v.r_value) if(not(minetest.registered_items[item:get_name()])) then yl_speak_up.debug_msg(player, n_id, o_id, tostring(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_id).." ".. "take_item: "..tostring(item:get_name()).." failed.") return false end return true elseif(r.r_type == "move") then -- TODO: copy/adjust effect "move" -- "an internal state (i.e. of a quest)", -- 2 elseif(r.r_type == "state") then -- TODO: implement effect "state" -- "a block somewhere" -- 3 elseif(r.r_type == "block") then -- TODO: implement effect "block" -- ""NPC crafts soemthing", -- 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_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_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_id).." ".. "Crafting failed: Diffrent output: "..tostring(output.item:to_string())) return false end -- TODO: actually consume the items required, return the ones in decremented_input yl_speak_up.debug_msg(player, n_id, o_id, tostring(r_id).." ".. "Great: Crafting is possible!") return true -- "go to other dialog if the action (i.e. trade) failed", -- 5 elseif(r.r_type == "on_failure") then -- TODO: implement effect on_failure -- "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(text) -- sending a chat message always counts as successful return true end -- fallback: unkown type return false end -- these are only wrapper functions for those in fs_edit_general.lua yl_speak_up.input_fs_edit_effects = function(player, formname, fields) return yl_speak_up.input_fs_edit_option_related(player, formname, fields, "r_", "o_results", yl_speak_up.max_result_effects, "(Ef)fect", "tmp_result", "Please punch the block you want to manipulate in your effect!", values_what, values_operator, values_block, {}, {}, check_what, check_operator, check_block, {}, {}, -- player variables with write access yl_speak_up.get_sorted_player_var_list_write_access, "edit_effects" ) end yl_speak_up.get_fs_edit_effects = function(player, table_click_result) return yl_speak_up.get_fs_edit_option_related(player, table_click_result, "r_", "o_results", yl_speak_up.max_result_effects, "(Ef)fect", "tmp_result", "What do you want to change with this effect?", check_what, check_operator, check_block, {}, {}, -- player variables with write access yl_speak_up.get_sorted_player_var_list_write_access, yl_speak_up.show_effect, "table_of_elements", "Change the value of the following variable:", "What to do:", "New value:", "The NPC shall do something to the block at the following position:" ) end