yl_speak_up/fs_edit_preconditions.lua

571 lines
20 KiB
Lua

-- 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