yl_speak_up/quest_api.lua

554 lines
19 KiB
Lua

-- just some handling of variables
-- TODO: mark some vars as "need to be saved" while others are less important (i.e. timestamps)
-- the keys are of the form:
-- $ <blank> <player name> <blank> <variable name> (makes it easier to grant read access)
-- the values are of the form:
-- <current player name as key> : <value of variable for that player as value>
yl_speak_up.player_vars = {}
-- store when player_vars was last saved to disc
yl_speak_up.player_vars_last_save_time = 0
-- save the data to disc; either if force_save is set or enough time has passed
yl_speak_up.save_quest_variables = function(force_save)
if(not(force_save)
and (yl_speak_up.player_vars_last_save_time + yl_speak_up.player_vars_min_save_time >
math.floor(minetest.get_us_time()/1000000))) then
return
end
local json = minetest.write_json( yl_speak_up.player_vars )
-- actually store it on disk
minetest.safe_file_write(yl_speak_up.worldpath..yl_speak_up.player_vars_save_file..".json", json)
end
-- load the data from disc
yl_speak_up.load_quest_variables = function()
-- load the data from the file
local file, err = io.open(yl_speak_up.worldpath..yl_speak_up.player_vars_save_file..".json", "r")
if err then
return
end
io.input(file)
local text = io.read()
-- all values saved in the tables as such are strings
local data = minetest.parse_json(text, -1)
io.close(file)
if(type(data) ~= "table") then
return
end
for k,v in pairs(data) do
if(v == -1) then
data[ k ] = {}
end
end
yl_speak_up.player_vars = data
if(not(yl_speak_up.player_vars.meta)) then
yl_speak_up.player_vars["meta"] = {}
end
end
-- do so when this file is parsed
yl_speak_up.load_quest_variables()
-- new variables have to be added somehow
yl_speak_up.add_quest_variable = function(owner_name, variable_name)
local k = "$ "..tostring(owner_name).." "..tostring(variable_name)
if(not(owner_name) or not(variable_name)) then
return false
end
-- create a new empty table;
-- keys will be the names of players for which values are set
yl_speak_up.player_vars[ k ] = {}
-- a new variable was created - that deserves a forced save
yl_speak_up.save_quest_variables(true)
return true
end
-- accidentally created or no longer needed variables need to be deleted somehow
-- force_delete if set, the variable will be deleted no matter what; this is for
-- manual maintenance and not used in this mod
yl_speak_up.del_quest_variable = function(owner_name, variable_name, force_delete)
if(not(owner_name) or not(variable_name)) then
return " could not be deleted. Parameters mismatch."
end
local var_name = yl_speak_up.restore_complete_var_name(variable_name, owner_name)
if(not(var_name) or not(yl_speak_up.player_vars[ var_name ])) then
return text.." does not exist."
end
local text = "Variable \""..minetest.formspec_escape(var_name).."\""
-- forcefully delete - even if the variable is still beeing used
if(force_delete) then
yl_speak_up.player_vars[ k ] = nil
yl_speak_up.save_quest_variables(true)
return text.." deleted by force."
end
-- check if the player really owns the variable: not that important because only unused
-- variables can be deleted;
-- check if the variable is used by an NPC
local var_data = yl_speak_up.player_vars[ var_name ]
local npc_users = yl_speak_up.get_variable_metadata(var_name, "used_by_npc")
if(npc_users and #npc_users > 0) then
return text.." could not be deleted.\nIt is used by "..tostring(#npc_users).." NPC."
end
-- check if the variable is used by a node position (for quests)
local node_pos_users = yl_speak_up.get_variable_metadata(var_name, "used_by_node_pos")
if(node_pos_users and #node_pos_users > 0) then
return text.." could not be deleted.\nIt is used by "..tostring(#node_pos_users)..
" node positions (quest)."
end
-- check if the variable has any values stored
for k, v in pairs(var_data) do
if(k and k ~= "$META$") then
return text.." could not be deleted.\nIt contains at least one stored value."
end
end
-- actually delete the variable
yl_speak_up.player_vars[ var_name ] = nil
-- a variable was deleted - that deserves a forced save
yl_speak_up.save_quest_variables(true)
return text.." deleted successfully."
end
-- set the value of a variable used by a player in an NPC;
-- returns false if the variable cannot be set (i.e. does not exist)
yl_speak_up.set_quest_variable_value = function(player_name, variable_name, new_value)
-- the owner name is alrady encoded in the variable name
local k = tostring(variable_name)
if(not(variable_name) or not(player_name) or not(yl_speak_up.player_vars[ k ])) then
return false
end
if(new_value ~= nil) then
new_value = tostring(new_value)
end
yl_speak_up.player_vars[ k ][ player_name ] = new_value
-- a quest variable was changed - save that to disc (but no need to force it)
yl_speak_up.save_quest_variables(false)
return true
end
-- get the value of a variable used by a player in an NPC;
-- returns nil if the variable does not exist
yl_speak_up.get_quest_variable_value = function(player_name, variable_name)
-- the owner name is alrady encoded in the variable name
local k = tostring(variable_name)
if(not(variable_name) or not(player_name) or not(yl_speak_up.player_vars[ k ])) then
return nil
end
return yl_speak_up.player_vars[ k ][ player_name ]
end
yl_speak_up.get_quest_variables = function(pname, has_write_access)
if(not(pname)) then
return {}
end
local liste = {}
-- first: list the variables owned by the player
for k, v in pairs(yl_speak_up.player_vars) do
local parts = string.split(k, " ")
if(parts and parts[1] and parts[1] == "$" and parts[2] and parts[2] == pname) then
table.insert(liste, k)
end
end
-- if the player has the right privs: allow to access all other variables as well
if( minetest.check_player_privs(pname, {npc_master=true})
or minetest.check_player_privs(pname, {npc_talk_master=true})) then
for k, v in pairs(yl_speak_up.player_vars) do
local parts = string.split(k, " ")
-- variables owned by *other* players
if(parts and parts[1] and parts[1] == "$" and parts[2] and parts[2] ~= pname) then
table.insert(liste, k)
end
end
else
local right = "read_access"
if(has_write_access) then
right = "write_access"
end
-- insert those vars owned by other players where this one has access
for k, v in pairs(yl_speak_up.player_vars) do
if( k[ "$META$"]
and k[ "$META$"][ right ]
and k[ "$META$"][ right ][ pname ]) then
table.insert(liste, k)
end
end
end
table.sort(liste)
return liste
end
-- which variables can player pname read and use in preconditions?
-- returns a sorted list
yl_speak_up.get_quest_variables_with_read_access = function(pname)
return yl_speak_up.get_quest_variables(pname, false)
end
-- which variables can player pname write and use in effects/results?
yl_speak_up.get_quest_variables_with_write_access = function(pname)
return yl_speak_up.get_quest_variables(pname, true)
end
-- variables are personalized; they are prefixed by "$ <PLAYER_NAME> <VAR_NAME>"
-- helper function;
-- strip "$ PNAME " from variable names (but only for those owned by player with name pname)
yl_speak_up.strip_pname_from_var = function(var_name, pname)
local parts = string.split(var_name, " ")
if(parts and parts[1] and parts[1] == "$" and parts[2] and parts[2] == pname) then
table.remove(parts, 1) -- remove "$"
table.remove(parts, 1) -- remove pname
return table.concat(parts, " ")
end
return var_name
end
-- does the opposite of the function above; adds "$ PNAME " if needed
yl_speak_up.add_pname_to_var = function(var_name, pname)
if(not(var_name)) then
return ""
end
local parts = string.split(var_name, " ")
if(parts and parts[1] and parts[1] ~= "$") then
return "$ "..tostring(pname).." "..tostring(var_name)
end
return var_name
end
-- helper function for yl_speak_up.input_fs_edit_option_related
-- and yl_speak_up.get_fs_edit_option_p_and_e_state
yl_speak_up.strip_pname_from_varlist = function(var_list, pname)
local var_list_text = ""
-- strip pname from the variable names
for i, v in ipairs(var_list) do
var_list[i] = yl_speak_up.strip_pname_from_var(v, pname)
-- useful for presenting a list
var_list_text = var_list_text..","..minetest.formspec_escape(tostring(var_list[i]))
end
return var_list_text
end
-- (partly) the opposite of the function above - add the name of the player to a variable
-- name again if needed
yl_speak_up.restore_complete_var_name = function(var_name, pname)
local vparts = string.split(var_name or "", " ")
-- has the player name been stripped from the variable name for better readability?
if(vparts and #vparts > 0 and vparts[1] ~= "$") then
return "$ "..tostring(pname).." "..table.concat(vparts, " ")
end
return var_name
end
-- helper function for saving NPC data;
-- this only works if *someone* is currently talking to that NPC
yl_speak_up.get_pname_for_n_id = function(n_id)
for k, v in pairs(yl_speak_up.speak_to) do
if(v and v.n_id and v.n_id == n_id) then
return k
end
end
end
-- add or revoke read or write access to a variable
--
-- k: name of the variable
-- pname: the name of the player trying to grant or revoke the right
-- grant_to_pname: the name of the player who shall have that access right
-- grant_write_access:
-- if false: grant read access
-- if true: grant write access
-- do_grant:
-- if false: revoke acces
-- if true: grant access
-- returns true if the variable was found
yl_speak_up.manage_access_to_quest_variable = function(k, pname, grant_to_pname, what_to_grant, do_grant)
-- only read and write access can be granted
if(not(what_to_grant) or (what_to_grant ~= "read_access" and what_to_grant ~= "write_access")) then
return false
end
return yl_speak_up.set_variable_metadata(k, pname, what_to_grant, grant_to_pname, do_grant)
end
-- a more general way of setting metadata for variables
-- in general, meta_name is a table containing entries entry_name (usually players or npc_ids)
-- with assigned values (usually true) for quick lookup
yl_speak_up.set_variable_metadata = function(k, pname, meta_name, entry_name, new_value)
k = yl_speak_up.add_pname_to_var(k, pname)
-- delete/unset
if(not(new_value)) then
new_value = nil
end
-- the variable needs to exist
if(not(yl_speak_up.player_vars[ k ])) then
return false
end
-- make sure all the necessary tables exist
if( not(yl_speak_up.player_vars[ k ][ "$META$" ])) then
yl_speak_up.player_vars[ k ][ "$META$" ] = { meta_name = {} }
end
-- var_type (the type of the variable) is a single string
if(meta_name == "var_type") then
yl_speak_up.player_vars[ k ][ "$META$"][ meta_name ] = new_value
else
if( not(yl_speak_up.player_vars[ k ][ "$META$" ][ meta_name ])
or type(yl_speak_up.player_vars[ k ][ "$META$" ][ meta_name ]) ~= "table") then
yl_speak_up.player_vars[ k ][ "$META$" ][ meta_name ] = {}
end
yl_speak_up.player_vars[ k ][ "$META$"][ meta_name ][ entry_name ] = new_value
end
yl_speak_up.save_quest_variables(true)
return true
end
-- get a list of all players who have read or write access to variable k (belonging to pname)
-- (technically a table and not a list)
yl_speak_up.get_access_list_for_var = function(k, pname, access_what)
k = yl_speak_up.add_pname_to_var(k, pname)
if(not(k)
or not(yl_speak_up.player_vars[ k ])
or not(yl_speak_up.player_vars[ k ][ "$META$"])
or not(yl_speak_up.player_vars[ k ][ "$META$"][ access_what ])) then
return {}
end
return yl_speak_up.player_vars[ k ][ "$META$"][ access_what ]
end
-- the dialog data of an NPC is saved - use this to save some statistical data
-- plus store which variables are used by this NPC
-- TODO: show this data in a formspec to admins for maintenance
yl_speak_up.update_stored_npc_data = function(n_id, dialog)
-- in order to determine the position of the NPC, we need its object
local pname = yl_speak_up.get_pname_for_n_id(n_id)
local npc_pos = ""
if(pname) then
local obj = yl_speak_up.speak_to[pname].obj
if(obj and obj:get_pos()) then
npc_pos = minetest.pos_to_string(obj:get_pos())
end
end
-- gather statistical data about the NPC and find out which variables it uses
local anz_dialogs = 0
local anz_options = 0
local anz_preconditions = 0
local anz_actions = 0
local anz_effects = 0
local anz_trades = 0
local variables_p = {}
local variables_e = {}
if(dialog and dialog.n_dialogs) then
for d_id, d in pairs(dialog.n_dialogs) do
anz_dialogs = anz_dialogs + 1
if(d and d.d_options) then
for o_id, o in pairs(d.d_options) do
anz_options = anz_options + 1
if(o and o.o_prerequisites) then
for p_id, p in pairs(o.o_prerequisites) do
anz_preconditions = anz_preconditions + 1
if(p and p.p_type and p.p_type == "state"
and p.p_variable and p.p_variable ~= "") then
variables_p[ p.p_variable ] = true
end
end
end
if(o and o.actions) then
for a_id, a_data in pairs(o.actions) do
anz_actions = anz_actions + 1
end
end
if(o and o.o_results) then
for r_id, r in pairs(o.o_results) do
anz_effects = anz_effects + 1
if(r and r.r_type and r.r_type == "state"
and r.r_variable and r.r_variable ~= "") then
variables_e[ r.r_variable ] = true
end
end
end
end
end
end
end
if(dialog and dialog.trades) then
for trade_id, t_data in pairs(dialog.trades) do
-- not a trade that is the action of a dialog option; only trade list trades count
if(not(t_data.d_id)) then
anz_trades = anz_trades + 1
end
end
end
-- add a special variable (if needed) for saving the NPC meta data
if(not(yl_speak_up.player_vars[ "$NPC_META_DATA$" ])) then
yl_speak_up.player_vars[ "$NPC_META_DATA$" ] = {}
end
yl_speak_up.player_vars[ "$NPC_META_DATA$" ][ n_id ] = {
n_id = n_id,
name = tostring(dialog.n_npc),
owner = tostring(yl_speak_up.npc_owner[ n_id ]),
may_edit = dialog.n_may_edit or {},
pos = tostring(npc_pos),
anz_dialogs = anz_dialogs,
anz_options = anz_options,
anz_preconditions = anz_preconditions,
anz_actions = anz_actions,
anz_effects = anz_effects,
anz_trades = anz_trades,
last_modified = os.date(),
}
-- delete all old entries that are not longer needed
for k, v in pairs(yl_speak_up.player_vars) do
if(not(variables_p[ k ]) and not(variables_e[ k ])) then
yl_speak_up.set_variable_metadata(k, pname, "used_by_npc", n_id, false)
end
end
-- save in the variables' metadata which NPC uses it
-- (this is what we're mostly after - know which variable is used in which NPC)
for k, v in pairs(variables_p) do
yl_speak_up.set_variable_metadata(k, pname, "used_by_npc", n_id, true)
end
for k, v in pairs(variables_e) do
yl_speak_up.set_variable_metadata(k, pname, "used_by_npc", n_id, true)
end
-- force writing the data
yl_speak_up.save_quest_variables(true)
end
-- which NPC do use this variable?
yl_speak_up.get_variable_metadata = function(var_name, meta_name)
-- var_type (the type of the variable) is a single string
if(meta_name and var_name and meta_name == "var_type") then
if( not(yl_speak_up.player_vars[ var_name ])
or not(yl_speak_up.player_vars[ var_name ][ "$META$"])) then
return nil
end
return yl_speak_up.player_vars[ var_name ][ "$META$"][ meta_name ]
end
-- no variable, or nothing stored? then it's not used by any NPC either
if(not(var_name)
or not(meta_name)
or not(yl_speak_up.player_vars[ var_name ])
or not(yl_speak_up.player_vars[ var_name ][ "$META$"])
or not(yl_speak_up.player_vars[ var_name ][ "$META$"][ meta_name ])
or type(yl_speak_up.player_vars[ var_name ][ "$META$"][ meta_name ]) ~= "table") then
return {}
end
local meta_list = {}
for k, v in pairs(yl_speak_up.player_vars[ var_name ][ "$META$"][ meta_name ]) do
table.insert(meta_list, k)
end
table.sort(meta_list)
return meta_list
end
-- find out where this variable is used in NPCs
yl_speak_up.get_list_of_usage_of_variable = function(var_name, pname, check_preconditions,
back_button_name, back_button_text, is_internal_var)
-- TODO: check if the player really has read access to this variable
if(not(is_internal_var)) then
var_name = yl_speak_up.restore_complete_var_name(var_name, pname)
end
-- which NPC (might be several) is using this variable?
-- TODO: ..or if the player at least is owner of these NPC or has extended privs
local npc_list = yl_speak_up.get_variable_metadata(var_name, "used_by_npc")
-- list of all relevant preconditions, actions and effects
local res = {}
local count_read = 0
local count_changed = 0
for i, n_id in ipairs(npc_list) do
-- the NPC may not even be loaded
local dialog = yl_speak_up.load_dialog(n_id)
if(dialog and dialog.n_dialogs) then
for d_id, d in pairs(dialog.n_dialogs) do
if(d and d.d_options) then
for o_id, o in pairs(d.d_options) do
local p_text = ""
local r_text = ""
local sort_value = 0
if(o and o.o_prerequisites and check_preconditions) then
for p_id, p in pairs(o.o_prerequisites) do
if(p and p.p_type and p.p_type == "state"
and p.p_variable and p.p_variable == var_name) then
p_text = p_text..yl_speak_up.print_as_table_precon(p,pname)
sort_value = (p.p_var_cmp_value or 0)
count_read = count_read + 1
end
end
end
if(o and o.o_results) then
for r_id, r in pairs(o.o_results) do
if(r and r.r_type and r.r_type == "state"
and r.r_variable and r.r_variable == var_name) then
r_text = r_text..yl_speak_up.print_as_table_effect(r,pname)
-- values set in the results are more important than
-- those set in preconditions
sort_value = (r.r_var_cmp_value or 0)
count_changed = count_changed + 1
end
end
end
-- if preconditions or effects apply: show the action as well
if(o and o.actions and (p_text ~= "" or r_text ~= "")) then
for a_id, a in pairs(o.actions) do
-- no need to introduce an a_text; this will follow
-- directly after p_text, and p_text is finished
p_text = p_text..yl_speak_up.print_as_table_action(a, pname)
end
end
yl_speak_up.print_as_table_dialog(p_text, r_text, dialog,
n_id, d_id, o_id, res, o, sort_value)
end
end
end
end
end
local formspec = yl_speak_up.print_as_table_prepare_formspec(res, "table_of_variable_uses",
back_button_name, back_button_text)
table.insert(formspec,
"label[20.0,1.8;"..
minetest.formspec_escape("Variable \""..
minetest.colorize("#FFFF00", tostring(var_name or "- ? -"))..
"\" is used here:").."]")
if(count_read > 0 or count_changed > 0) then
table.insert(formspec,
"label[16.0,31.0;The variable is accessed in "..
minetest.colorize("#FFFF00", tostring(count_read).." pre(C)onditions")..
" and changed in "..
minetest.colorize("#55FF55", tostring(count_changed).." (Ef)fects")..
".]")
elseif(not(is_internal_var)) then
table.insert(formspec,
"button[0.2,30.6;56.6,1.2;delete_unused_variable;"..
minetest.formspec_escape("Delete this unused variable \""..
tostring(var_name or "- ? -")).."\".]")
else
table.insert(formspec,
"label[16.0,31.0;This is an internal variable and cannot be deleted.]")
end
return table.concat(formspec, "\n")
end