diff --git a/config.lua b/config.lua index 9d3bb89..56cdc00 100644 --- a/config.lua +++ b/config.lua @@ -9,6 +9,13 @@ yl_speak_up.path = "yl_speak_up_dialogs" -- What shall we call the folder all the inventories of the NPC will reside in? yl_speak_up.inventory_path = "yl_speak_up_inventories" +-- Where shall player-specific varialbes (usually quest states) be stored? +yl_speak_up.player_vars_save_file = "yl_speak_up_player_vars" + +-- amount of time in seconds that has to have passed before the above file will be saved again +-- (more time can pass if no variable is changed) +yl_speak_up.player_vars_min_save_time = 60 + -- Texts yl_speak_up.message_button_option_exit = "Farewell!" diff --git a/fs_edit_effects.lua b/fs_edit_effects.lua index d561a94..db27ec1 100644 --- a/fs_edit_effects.lua +++ b/fs_edit_effects.lua @@ -122,7 +122,7 @@ 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) +yl_speak_up.show_effect = function(r, pname) if(not(r.r_type) or r.r_type == "") then return "(nothing): Nothing to do. No effect." elseif(r.r_type == "give_item") then @@ -138,18 +138,22 @@ yl_speak_up.show_effect = function(r) elseif(r.r_type == "dialog") then return "Switch to dialog \""..tostring(r.r_value).."\"." elseif(r.r_type == "state") then + local var_name = "VARIABLE[ - ? - ]" + if(r.r_variable) then + var_name = "VARIABLE[ "..tostring( + yl_speak_up.strip_pname_from_var(r.r_variable, pname)).." ]" + end 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 \"".. + return "set "..var_name.." to value \"".. tostring(r.r_var_cmp_value).."\"" elseif(r.r_operator == "unset") then - return "discard VARIABLE[ "..tostring(r.r_variable).." ] (unset)" + return "discard "..var_name.." (unset)" elseif(r.r_operator == "set_to_current_time") then - return "set VARIABLE[ "..tostring(r.r_variable).." ] to the current time" + return "set "..var_name.." to the current time" else - return "ERROR: Wrong operator \""..tostring(r.r_operator).."\" for ".. - "VARIABLE[ "..tostring(r.r_variable).." ]" + return "ERROR: Wrong operator \""..tostring(r.r_operator).."\" for "..var_name end elseif(r.r_type == "block") then if(not(r.r_pos) or type(r.r_pos) ~= "table" @@ -251,7 +255,7 @@ yl_speak_up.execute_all_relevant_effects = function(player, effects, o_id, actio for i, k in ipairs(sorted_key_list) do local r = effects[ k ] yl_speak_up.debug_msg(player, n_id, o_id, "..executing ".. - tostring(r.r_id)..": "..yl_speak_up.show_effect(r)) + tostring(r.r_id)..": "..yl_speak_up.show_effect(r, pname)) -- do not execute effects in edit mode if(not(edit_mode)) then yl_speak_up.debug_msg(player, n_id, o_id, diff --git a/fs_edit_general.lua b/fs_edit_general.lua index 40b8c6e..b1cf076 100644 --- a/fs_edit_general.lua +++ b/fs_edit_general.lua @@ -242,7 +242,7 @@ yl_speak_up.save_element_p_or_a_or_e = function( v[ id_prefix.."value" ] = "expression" v[ id_prefix.."operator" ] = values_operator[ data.operator ] v[ id_prefix.."var_cmp_value" ] = (data.var_cmp_value or "") - v[ id_prefix.."variable" ] = data.variable_name + v[ id_prefix.."variable" ] = yl_speak_up.add_pname_to_var(data.variable_name, pname) -- "a block somewhere", -- 3 elseif(data.what == 3 and id_prefix ~= "a_") then @@ -899,6 +899,7 @@ yl_speak_up.input_fs_edit_option_related = function(player, formname, fields, -- get the list of available variables (with the same elements -- and the same sort order as when the dropdown was displayed) local var_list = get_sorted_player_var_list_function(pname) + yl_speak_up.strip_pname_from_varlist(var_list, pname) local nr = table.indexof(var_list, fields.select_variable) if(nr) then yl_speak_up.speak_to[pname][ tmp_data_cache ].variable = nr @@ -1125,7 +1126,7 @@ yl_speak_up.get_fs_edit_option_related = function(player, table_click_result, minetest.formspec_escape(elements[ x_id ][ id_prefix.."type"]).. ",".. minetest.formspec_escape( - show_element_function(elements[ x_id ]))..";0]".. + show_element_function(elements[ x_id ], pname))..";0]".. "button[2.0,1.8;1.5,0.9;delete_element;Delete]".. "button[4.0,1.8;1.5,0.9;change_element;Change]".. "button[6.0,1.8;5.5,0.9;back;Back to edit dialog option \"".. @@ -1327,11 +1328,12 @@ yl_speak_up.get_fs_edit_option_p_and_e_state = function( if(e) then data.operator = math.max(1,table.indexof(values_operator, e[ id_prefix.."operator" ])) data.var_cmp_value = e[ id_prefix.."var_cmp_value" ] - data.variable_name = e[ id_prefix.."variable" ] - data.variable = math.max(1, table.indexof(var_list, e[ id_prefix.."variable"])+1) + data.variable_name = yl_speak_up.strip_pname_from_var(e[ id_prefix.."variable" ], pname) + data.variable = table.indexof(var_list, e[ id_prefix.."variable"]) end + local var_list_stripped = yl_speak_up.strip_pname_from_varlist(var_list, pname) if(not(data.variable) or data.variable < 1) then - data.variable = 1 + data.variable = 0 -- not enough selected yet for saving save_button = "" elseif(not(data.operator) or data.operator == 1) then @@ -1351,22 +1353,12 @@ yl_speak_up.get_fs_edit_option_p_and_e_state = function( -- the list of available variables needs to be extended with the ones -- the player has read access to, and the order has to be constant -- (because dropdown just returns an index) - local var_list_text = "- please select -" - for i, v in ipairs(var_list) do - local parts = string.split(v, " ") - local var_name = v - if(parts and parts[1] and parts[1] == pname) then - table.remove(parts, 1) - var_name = table.concat(parts, " ") - end - var_list_text = var_list_text..","..minetest.formspec_escape(tostring(var_name)) - end return formspec.. "label[0.2,3.3;"..text_variable.."]".. "label[0.2,4.3;Name of variable:]".. "dropdown[0.2,4.8;6.5,0.6;select_variable;".. - var_list_text..";".. - tostring(data.variable)..";]".. + "- please select -"..var_list_stripped..";".. + tostring(data.variable + 1)..";]".. "label[7.0,4.3;"..text_select_operator.."]".. "dropdown[7.0,4.8;4.0,0.6;select_operator;".. table.concat(check_operator, ",")..";".. diff --git a/fs_edit_options_dialog.lua b/fs_edit_options_dialog.lua index b821e37..801b924 100644 --- a/fs_edit_options_dialog.lua +++ b/fs_edit_options_dialog.lua @@ -236,7 +236,7 @@ yl_speak_up.get_fs_edit_option_dialog = function(player, n_id, d_id, o_id, calle minetest.formspec_escape(v.p_id)..",#FFFF00,".. minetest.formspec_escape(v.p_type)..",".. minetest.formspec_escape( - yl_speak_up.show_precondition(v)).."," + yl_speak_up.show_precondition(v, pname)).."," count_prereq = count_prereq + 1 end end @@ -299,7 +299,7 @@ yl_speak_up.get_fs_edit_option_dialog = function(player, n_id, d_id, o_id, calle minetest.formspec_escape(v.r_id)..",#999999,".. minetest.formspec_escape(v.r_type)..",".. minetest.formspec_escape( - yl_speak_up.show_effect(v)).."," + yl_speak_up.show_effect(v, pname)).."," -- there may be more than one in the data structure target_dialog = v.r_value target_effect = v @@ -308,7 +308,7 @@ yl_speak_up.get_fs_edit_option_dialog = function(player, n_id, d_id, o_id, calle minetest.formspec_escape(v.r_id)..",#FFFF00,".. minetest.formspec_escape(v.r_type)..",".. minetest.formspec_escape( - yl_speak_up.show_effect(v)).."," + yl_speak_up.show_effect(v, pname)).."," end count_effects = count_effects + 1 end diff --git a/fs_edit_preconditions.lua b/fs_edit_preconditions.lua index ac21c95..4d86d9c 100644 --- a/fs_edit_preconditions.lua +++ b/fs_edit_preconditions.lua @@ -135,7 +135,7 @@ end -- 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) +yl_speak_up.show_precondition = function(p, pname) if(not(p.p_type) or p.p_type == "") then return "(nothing): Always true." elseif(p.p_type == "item") then @@ -147,25 +147,30 @@ yl_speak_up.show_precondition = function(p) elseif(p.p_type == "function") then return "function: evaluate "..tostring(p.p_value) elseif(p.p_type == "state") then + local var_name = "VALUE_OF[ - ? - ]" + if(p.p_variable) then + var_name = "VALUE_OF[ "..tostring( + yl_speak_up.strip_pname_from_var(p.p_variable, pname)).." ]" + end if(not(p.p_operator)) then return "Error: Operator not defined." elseif(p.p_operator == "not") then - return "not( VALUE_OF[ "..tostring(p.p_variable).." ] )" + return "not( "..var_name.." )" elseif(p.p_operator == "is_set") then - return "VALUE_OF[ "..tostring(p.p_variable).." ] ~= nil (is_set)" + return var_name.." ~= nil (is_set)" elseif(p.p_operator == "is_unset") then - return "VALUE_OF[ "..tostring(p.p_variable).." ] == nil (is_unset)" + return var_name.." == nil (is_unset)" elseif(p.p_operator == "more_than_x_seconds_ago") then - return "VALUE_OF[ "..tostring(p.p_variable).." ] was set to current time ".. + return var_name.." was set to current time ".. "*more* than "..tostring(p.p_var_cmp_value).." seconds ago" elseif(p.p_operator == "less_than_x_seconds_ago") then - return "VALUE_OF[ "..tostring(p.p_variable).." ] was set to current time ".. + return var_name.." was set to current time ".. "*less* than "..tostring(p.p_var_cmp_value).." seconds ago" end if(p.p_var_cmp_value == "") then - return "VALUE_OF[ "..tostring(p.p_variable).." ] "..tostring(p.p_operator).." \"\"" + return var_name.." "..tostring(p.p_operator).." \"\"" end - return "VALUE_OF[ "..tostring(p.p_variable).." ] "..tostring(p.p_operator).." ".. + return var_name.." "..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" @@ -224,7 +229,7 @@ yl_speak_up.eval_all_preconditions = function(player, prereq, o_id) yl_speak_up.debug_msg(player, n_id, o_id, "Checking preconditions..") for k, p in pairs(prereq) do yl_speak_up.debug_msg(player, n_id, o_id, "..checking ".. - tostring(p.p_id)..": "..yl_speak_up.show_precondition(p)) + tostring(p.p_id)..": "..yl_speak_up.show_precondition(p, pname)) if(not(yl_speak_up.eval_precondition(player, n_id, p))) then yl_speak_up.debug_msg(player, n_id, o_id, tostring(p.p_id).. " -> is false. Aborting.") diff --git a/quest_api.lua b/quest_api.lua index afa8284..88ad077 100644 --- a/quest_api.lua +++ b/quest_api.lua @@ -4,34 +4,79 @@ -- TODO: handle read (and write?) access for other players -- TODO: add a function to check if the player has read/write access -- TODO: mark some vars as "need to be saved" while others are less important (i.e. timestamps) --- TODO: save state whenever a new variable is added - or else if the last change was more than x minutes ago? -- the keys are of the form: --- (makes it easier to grant read access) +-- $ (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, -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 +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) + 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 yl_speak_up.del_quest_variable = function(owner_name, variable_name) - local k = tostring(owner_name).." "..tostring(variable_name) + local k = "$ "..tostring(owner_name).." "..tostring(variable_name) if(not(owner_name) or not(variable_name)) then return false end + -- a variable was deleted - that deserves a forced save + yl_speak_up.save_quest_variables(true) yl_speak_up.player_vars[ k ] = nil end @@ -44,7 +89,9 @@ yl_speak_up.set_quest_variable_value = function(player_name, variable_name, new_ if(not(variable_name) or not(player_name) or not(yl_speak_up.player_vars[ k ])) then return false end - yl_speak_up.player_vars[ k ][ player_name ] = new_value + yl_speak_up.player_vars[ k ][ player_name ] = tostring(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 @@ -70,7 +117,7 @@ yl_speak_up.get_quest_variables_with_read_access = function(pname) local liste = {} for k, v in pairs(yl_speak_up.player_vars) do local parts = string.split(k, " ") - if(parts and parts[1] and parts[1] == pname) then + if(parts and parts[1] and parts[1] == "$" and parts[2] and parts[2] == pname) then table.insert(liste, k) end end @@ -88,7 +135,7 @@ yl_speak_up.get_quest_variables_with_write_access = function(pname) local liste = {} for k, v in pairs(yl_speak_up.player_vars) do local parts = string.split(k, " ") - if(parts and parts[1] and parts[1] == pname) then + if(parts and parts[1] and parts[1] == "$" and parts[2] and parts[2] == pname) then table.insert(liste, k) end end @@ -151,3 +198,42 @@ yl_speak_up.get_fs_manage_variables = function(player, param) -- TODO: delete variable "button[2.0,3.5;1.0,0.6;back;Back]" 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) + 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