-- 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", "(internal) player's health points", } -- TODO: check inscription of a sign? -- TODO: invlist as dropdown of inventory lists at detected position -- TODO: variable as dropdown of allowed variables -- which diffrent types of preconditions are available? -- possible variables: -- item an inventory item or block (check if it exists) -- -> needs to be a minetest.registered_item[ item ] -- text some text -- pos a position in the world (not too far from the NPC) -- -> determined by punching -- variable name of a variable -- -- TODO: may all boil down to automaticly setting some lua code? -- -> better write a function for each one that can be called when needed -- 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(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 "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 yl_speak_up.input_fs_edit_preconditions = function(player, formname, fields) if(not(player)) then return end local pname = player:get_player_name() -- what are we talking about? 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 local p_id = yl_speak_up.speak_to[pname].p_id -- this only works in edit mode if(not(n_id) or yl_speak_up.edit_mode[pname] ~= n_id) then return end -- TODO: make delete work if(fields.select_block_pos) then minetest.chat_send_player(pname, "Please punch the block you want to check in your precondition!") -- this formspec expects the block punch: yl_speak_up.speak_to[pname].expect_block_punch = "edit_preconditions" return end -- field inputs: those do not trigger a sending of the formspec on their own -- are we talking about an inventory? local data = yl_speak_up.speak_to[pname].tmp_prereq if(fields.inv_stack_name and fields.inv_stack_name ~= "" and data and data.what and data.what >= 5 and data.what <= 6) then local parts = fields.inv_stack_name:split(" ") local size = 1 if(parts and #parts > 1) then size = tonumber(parts[2]) if(not(size) or size < 1) then size = 1 end end -- does the item exist? if(minetest.registered_items[ parts[1] ]) then data.inv_stack_name = parts[1].." "..tostring(size) else -- show error message yl_speak_up.show_fs(player, "msg", { input_to = "yl_speak_up:edit_preconditions", formspec = "size[8,2]".. "label[0.2,0.5;Error: \"".. minetest.formspec_escape(fields.inv_stack_name).. "\" is not a valid item(stack).]".. "button[1.5,1.5;2,0.9;back_from_error_msg;Back]"}) return end -- comparison value for a variable elseif(fields.var_cmp_value and data and data.what and data.what >= 5 and data.what <= 6) then data.var_cmp_value = fields.var_cmp_value end -- the save button was pressed if(fields.save_prereq and data and data.what and values_what[ data.what ]) then -- for creating the new prerequirement; normal elements: p_type, p_value, p_id local pq = {} -- determine p_type pq.p_type = values_what[ data.what ] local dialog = yl_speak_up.speak_to[pname].dialog if(not(dialog) or not(dialog.n_dialogs) or not(dialog.n_dialogs[d_id]) or not(dialog.n_dialogs[d_id].d_options) or not(dialog.n_dialogs[d_id].d_options[o_id])) then -- this really should not happen during the normal course of operation -- (only if the player sends forged formspec data or a bug occoured) minetest.chat_send_player(pname, "Dialog or option does not exist.") return end local prereq = dialog.n_dialogs[d_id].d_options[o_id].o_prerequisites -- set p_id appropriately if(not(p_id) or p_id == "new") then p_id = "p_"..yl_speak_up.find_next_id(prereq) if(not(prereq)) then dialog.n_dialogs[d_id].d_options[o_id].o_prerequisites = {} prereq = dialog.n_dialogs[d_id].d_options[o_id].o_prerequisites end end pq.p_id = p_id -- "an internal state (i.e. of a quest)", -- 2 if(data.what == 2) then pq.p_value = values_operator[ data.operator ] pq.p_var_cmp_value = (data.var_cmp_value or "") -- TODO: p_variable -- "a block somewhere", -- 3 elseif(data.what == 3) then pq.p_value = values_block[ data.block ] if(not(data.block_pos) or not(data.node_data) or not(data.node_data.name)) then yl_speak_up.show_fs(player, "msg", { input_to = "yl_speak_up:edit_preconditions", formspec = "size[8,2]".. "label[0.2,0.5;Error: Please select a block first!]".. "button[1.5,1.5;2,0.9;back_from_error_msg;Back]"}) return end -- for "node_is_air", there is no need to store node name and parameter if(pq.p_value and (pq.p_value == "node_is_like" or pq.p_value == "node_is_diffrent_from")) then pq.p_node = data.node_data.name pq.p_param2 = data.node_data.param2 end -- we also need to store the position of the node pq.p_pos = {x = data.block_pos.x, y = data.block_pos.y, z = data.block_pos.z } -- "I can't punch it. The block is as the block *above* the one I punched.", if(data.block == 5) then pq.p_pos.y = pq.p_pos.y + 1 end -- "a trade", -- 4 elseif(data.what == 4) then -- this depends on the trade associated with that option; therefore, -- it does not need any more parameters (they come dynamicly from the -- trade) pq.p_value = values_trade[ data.trade ] -- "the inventory of the player", -- 5 -- "the inventory of the NPC", -- 6 elseif(data.what == 5 or data.what == 6) then pq.p_value = values_inv[ data.inv ] if(pq.p_value and pq.p_value ~= "inv_is_empty") then -- we have checked this value earlier on pq[ "p_itemstack" ] = data.inv_stack_name end end -- only save if something was actually selected if(pq.p_value) then -- store the change in the dialog dialog.n_dialogs[d_id].d_options[o_id].o_prerequisites[ p_id ] = pq -- clear up data yl_speak_up.speak_to[pname].p_id = nil yl_speak_up.speak_to[pname].tmp_prereq = nil -- record this as a change, but do not save do disk yet table.insert(yl_speak_up.npc_was_changed[ n_id ], "Dialog "..tostring(d_id)..": ".."Pre(C)ondition "..tostring(p_id).. " added/changed for option "..tostring(o_id)..".") -- TODO: when trying to save: save to disk as well? -- show the new/changed precondition yl_speak_up.show_fs(player, "edit_preconditions", p_id) return end end -- selections in a dropdown menu (they trigger sending the formspec) -- select a general direction/type first if(fields.select_what) then local nr = table.indexof(check_what, fields.select_what) yl_speak_up.speak_to[pname].tmp_prereq = { what = nr } -- select a subtype for the "a trade" selection elseif(fields.select_trade) then local nr = table.indexof(check_trade, fields.select_trade) yl_speak_up.speak_to[pname].tmp_prereq.trade = nr -- select a subtype for the inventory selection (player or NPC) elseif(fields.select_inv) then local nr = table.indexof(check_inv, fields.select_inv) yl_speak_up.speak_to[pname].tmp_prereq.inv = nr -- select data regarding a block elseif(fields.select_block) then local nr = table.indexof(check_block, fields.select_block) yl_speak_up.speak_to[pname].tmp_prereq.block = nr -- select data regarding a variable elseif(fields.select_variable) then -- TODO: this needs to include player-specific variables local nr = table.indexof(check_variable, fields.select_variable) yl_speak_up.speak_to[pname].tmp_prereq.variable = nr -- select data regarding an operator elseif(fields.select_operator) then local nr = table.indexof(check_operator, fields.select_operator) yl_speak_up.speak_to[pname].tmp_prereq.operator = nr end -- the player wants to change/edit a precondition if(not(fields.back) and (fields.change_prereq or fields.select_what or fields.select_trade or fields.select_inv or fields.select_block or fields.select_variable or fields.select_operator or fields.back_from_error_msg)) then yl_speak_up.show_fs(player, "edit_preconditions") return end -- go back to the edit option dialog yl_speak_up.show_fs(player, "edit_option_dialog", {n_id = n_id, d_id = d_id, o_id = o_id, caller="edit_precondition"}) end yl_speak_up.get_fs_edit_preconditions = function(player, table_click_result) if(not(player)) then return "" end local pname = player:get_player_name() -- what are we talking about? 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 local p_id = yl_speak_up.speak_to[pname].p_id -- this only works in edit mode if(not(n_id) or yl_speak_up.edit_mode[pname] ~= n_id) then return "size[1,1]label[0,0;You cannot edit this NPC.]" end local dialog = yl_speak_up.speak_to[pname].dialog if(not(dialog) or not(dialog.n_dialogs) or not(dialog.n_dialogs[d_id]) or not(dialog.n_dialogs[d_id].d_options) or not(dialog.n_dialogs[d_id].d_options[o_id])) then return "size[4,1]label[0,0;Dialog option does not exist.]" end local prereq = dialog.n_dialogs[d_id].d_options[o_id].o_prerequisites if(not(prereq)) then prereq = {} end -- did we arrive here through clicking on a prereq in the dialog edit options menu? if(table_click_result or prereq[ table_click_result ]) then if(not(prereq[ table_click_result ])) then -- which precondition has the player selected? local sorted_key_list = yl_speak_up.sort_keys(prereq) local selected = minetest.explode_table_event(table_click_result) -- use "new" if nothing fits p_id = "new" if((selected.type == "CHG" or selected.type == "DLC") and selected.row <= #sorted_key_list) then p_id = sorted_key_list[ selected.row ] end if( p_id == "new" and #sorted_key_list >= yl_speak_up.max_prerequirements) then return "size[9,1.5]".. "label[0.2,0.0;There are only up to ".. minetest.formspec_escape(yl_speak_up.max_prerequirements).. " pre(C)onditions allowed per dialog option.]".. "button[2.0,0.8;1.0,0.9;back;Back]" end else -- allow to directly specify a p_id to show p_id = table_click_result end -- store which prereq we are talking about yl_speak_up.speak_to[pname].p_id = p_id -- display the selected prereq if(p_id ~= "new") then return "formspec_version[3]".. "size[20,3]".. "bgcolor[#00000000;false]".. "label[0.2,0.5;Selected pre(C)ondition:]".. "tablecolumns[text;color,span=1;text;text]".. "table[0.2,0.8;19.6,0.7;table_of_preconditions;".. minetest.formspec_escape(prereq[ p_id ].p_id)..",#FFFF00,".. minetest.formspec_escape(prereq[ p_id ].p_type)..",".. minetest.formspec_escape( yl_speak_up.show_precondition(prereq[ p_id ]))..";0]".. "button[2.0,1.8;1.5,0.9;delete_prereq;Delete]".. "button[4.0,1.8;1.5,0.9;change_prereq;Change]".. "button[6.0,1.8;1,0.9;back;Back]" end end -- did the player get here through punching a block in the meantime? local block_pos = yl_speak_up.speak_to[pname].block_punched yl_speak_up.speak_to[pname].block_punched = nil local data = yl_speak_up.speak_to[pname].tmp_prereq if(not(data) or not(data.what)) then data = { what = 1} end -- fallback if(not(p_id)) then p_id = "new" end local save_button = "button[5.0,9.0;1,0.7;save_prereq;Save]" local formspec = "formspec_version[3]".. "size[20,10]".. "label[5,0.5;Edit pre(C)ondition \""..minetest.formspec_escape(p_id).."\"]".. "label[0.2,1.5;What do you want to check in this precondition?]".. "label[0.2,2.0;Something regarding...]".. "dropdown[4.0,1.8;14.0,0.6;select_what;".. table.concat(check_what, ",")..";".. tostring(data.what)..";]".. "button[3.0,9.0;1,0.7;back;Abort]" -- "an internal state (i.e. of a quest)", -- 2 if(data.what and data.what == 2) then if(not(data.variable) or data.variable == 1) then data.variable = 1 -- not enough selected yet for saving save_button = "" elseif(not(data.operator) or data.operator == 1) then data.operator = 1 save_button = "" end local field_for_value = "field[11.2,4.8;7.0,0.6;var_cmp_value;;".. minetest.formspec_escape(data.var_cmp_value or "- enter value -").."]" -- do not show value input field for unary operators if(not(data.operator) or data.operator == 1 or data.operator >= 8) then field_for_value = "label[11.2,5.1;- not used for this operator -]" end -- TODO: the list of available variables needs to be extended -- with the ones the player has read access to formspec = formspec.. "label[0.2,3.3;The following expression shall be true:]".. "label[0.2,4.3;Name of variable:]".. "dropdown[0.2,4.8;6.5,0.6;select_variable;".. table.concat(check_variable, ",")..";".. tostring(data.variable)..";]".. "label[7.0,4.3;Operator:]".. "dropdown[7.0,4.8;4.0,0.6;select_operator;".. table.concat(check_operator, ",")..";".. tostring(data.operator)..";]".. "label[11.2,4.3;Value to compare with:]".. field_for_value -- "a trade", -- 4 elseif(data.what and data.what == 4) then if(not(data.trade) or data.trade == 1) then data.trade = 1 -- not enough selected yet for saving save_button = "" end formspec = formspec.. "label[0.2,3.3;If the action is a trade, the following shall be true:]".. "dropdown[4.0,3.5;16.0,0.6;select_trade;".. table.concat(check_trade, ",")..";".. tostring(data.trade)..";]" -- "the inventory of the player", -- 5 -- "the inventory of the NPC", -- 6 elseif(data.what and data.what >= 5 and data.what <= 6) then if(not(data.inv) or data.inv == 1) then data.inv = 1 -- not enough selected yet for saving save_button = "" end formspec = formspec.. "label[0.2,3.3;The following shall be true about the inventory:]".. "dropdown[4.0,3.5;16.0,0.6;select_inv;".. table.concat(check_inv, ",")..";".. tostring(data.inv)..";]".. "label[0.2,4.5;Name of the item(stack):]".. "field[4.0,4.3;16.0,0.6;inv_stack_name;;"..(data.inv_stack_name or "").."]".. "tooltip[inv_stack_name;Enter name of the block and amount.\n".. "Example: \"default:apple 3\" for three apples,\n".. " \"farming:bread\" for a bread.]" -- "a block somewhere", -- 3 elseif(data.what and data.what == 3) then local block_pos_str = "- none set -" local node = {name = "- unknown -", param2 = "- unkown -"} if(not(block_pos) and data and data.block_pos) then block_pos = data.block_pos end if(block_pos) then -- store for later usage data.block_pos = block_pos tmp_pos = {x=block_pos.x, y=block_pos.y, z=block_pos.z} -- "I can't punch it. The block is as the block *above* the one I punched.", if(data.block and data.block == 5) then tmp_pos.y = block_pos.y + 1 end block_pos_str = minetest.pos_to_string(tmp_pos) node = minetest.get_node_or_nil(tmp_pos) if(not(node)) then node = {name = "- unknown -", param2 = "- unkown -"} end -- "There shall be air instead of this block.", if(data.block and data.block == 3) then node = {name = "air", param2 = 0} end -- cache that (in case a sapling grows or someone else changes it) data.node_data = node end if(node.name == "- unknown -") then save_button = "" end if(not(data.block) or data.block == 1) then data.block = 1 -- not enough selected yet for saving save_button = "" end formspec = formspec.. "label[0.2,3.3;The following shall be true about the block:]".. "dropdown[4.0,3.5;16.0,0.6;select_block;".. table.concat(check_block, ",")..";".. tostring(data.block)..";]".. "label[0.2,4.8;Position of the block:]".. "label[4.0,4.8;"..minetest.formspec_escape(block_pos_str).."]".. "label[0.2,5.8;Name of block:]".. "label[4.0,5.8;"..minetest.formspec_escape(node.name).."]".. "label[0.2,6.8;Orientation (param2):]".. "label[4.0,6.8;"..minetest.formspec_escape(node.param2).."]".. "button_exit[10.0,5.5;4.0,0.7;select_block_pos;Set position of block]".. "tooltip[select_block_pos;Click on this button to select a block.\n".. "This menu will close and you will be asked to punch\n".. "the block at the position you want to check.\n".. "After punching it, you will be returned to this menu.]" end return formspec..save_button end