-- 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: -- $ (makes it easier to grant read access) -- the values are of the form: -- : 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, "$NIL_VALUE$") io.close(file) if(type(data) ~= "table") then return end for k,v in pairs(data) do if(v == "$NIL_VALUE$") 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 -- time based variables are used for "Limit guessing:" and "Limit repeating:"; they ensure that -- options with actions cannot be repeated indefintely; -- returns false if the variable could not be created; else it returns the variable metadata yl_speak_up.add_time_based_variable = function(variable_name) if(not(yl_speak_up.player_vars[ variable_name ])) then yl_speak_up.player_vars[ variable_name ] = {} yl_speak_up.player_vars[ variable_name ][ "$META$" ] = {} yl_speak_up.player_vars[ variable_name ][ "$META$"][ "var_type" ] = "time_based" yl_speak_up.save_quest_variables(true) return yl_speak_up.player_vars[ variable_name ][ "$META$"] elseif(yl_speak_up.player_vars[ variable_name ] and yl_speak_up.player_vars[ variable_name ][ "$META$"] and type(yl_speak_up.player_vars[ variable_name ][ "$META$"]) == "table" and yl_speak_up.player_vars[ variable_name ][ "$META$"][ "var_type" ] == "time_based") then return yl_speak_up.player_vars[ variable_name ][ "$META$"] end return false 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(yl_speak_up.player_vars[ k ]["$META$"] and yl_speak_up.player_vars[ k ]["$META$"][ "debug" ] and type(yl_speak_up.player_vars[ k ]["$META$"][ "debug" ]) == "table") then for p, _ in pairs(yl_speak_up.player_vars[ k ]["$META$"][ "debug" ]) do minetest.chat_send_player(p, "[Variable ".. minetest.colorize("#FFFF00", tostring(k)).. ", player ".. minetest.colorize("#FFFF00", tostring(player_name)).. "] old value: \"".. minetest.colorize("#FFFF00", tostring(yl_speak_up.player_vars[ k ][ player_name ])).. "\" new value: \"".. minetest.colorize("#FFFF00", tostring(new_value)).."\".") end 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 "$ " -- 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) if(pname) then k = yl_speak_up.add_pname_to_var(k, pname) end -- 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, get_as_is) -- 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 -- do not transform into a list; get the table if(get_as_is) then return yl_speak_up.player_vars[ var_name ][ "$META$"][ meta_name ] 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 -- show which variables the player is currently debugging yl_speak_up.get_list_of_debugged_variables = function(pname) if(not(pname) or pname == "") then return end local res = {} for k, v in pairs(yl_speak_up.player_vars) do if(k and v and v[ "$META$" ] and v[ "$META$" ][ "debug" ]) then -- this will be used in a table presented to the player table.insert(res, minetest.formspec_escape(k)) end end return res end -- helper function; time is sometimes needed yl_speak_up.get_time_in_seconds = function() return math.floor(minetest.get_us_time()/1000000) end