diff --git a/stdlib/binser.lua b/stdlib/binser.lua index d120f63..59dc64e 100644 --- a/stdlib/binser.lua +++ b/stdlib/binser.lua @@ -47,7 +47,7 @@ if not select then return #arg else local values = {} - for i = math.max(index + 1, 1), #arg do + for i = math.max(index, 1), #arg do table.insert(values, arg[i]) end return unpack(values) diff --git a/stdlib/savedata.lua b/stdlib/savedata.lua new file mode 100644 index 0000000..6256864 --- /dev/null +++ b/stdlib/savedata.lua @@ -0,0 +1,180 @@ +local self = {} + +local binser = require('stdlib.binser') +require('stdlib.util') + +local savedataName +local savedata = { + _lastSave = {0, 0, 0, 0, 0} +} +local loadedSavedata = false + +self.saveNextFrame = false + +---@param name string @a unique name for the entire modfile +---@param forceIgnore boolean | nil +--- for picking a name: +--- please head to this link: +--- https://www.random.org/strings/?num=1&len=16&digits=on&upperalpha=on&loweralpha=on&unique=on&format=plain&rnd=new +--- and generate yourself a hash. afterwards, append it to your game name like so: +--- savedata.initializeModule('myGameName_doAmaUOBIjiaSWyz') +--- this helps ensure no collisions happen, breaking both of the modfiles in the process! +--- **DON'T CHANGE THIS!!** else you'll break existing saves +function self.initializeModule(name, forceIgnore) + if loadedSavedata then + error('uranium: cannot initialize savedata module after uranium.init!', 3) + end + if not forceIgnore then + if #name < 16 then + error('uranium: savedata name too short! must be at least 16 characters long', 2) + end + + local normalChars = 0 + local specialChars = 0 + for i = 1, #name do + local char = string.sub(name, i, i) + local special = string.lower(char) == string.upper(char) + if special then + specialChars = specialChars + 1 + else + normalChars = normalChars + 1 + end + end + + if normalChars < 4 then + error('uranium: savedata name should have at least 4 non-special characters!', 2) + end + if specialChars < 2 then + error('uranium: savedata name should have at least 2 special characters!', 2) + end + end + + savedataName = name +end + +local function checkIfInitialized() + if not savedataName then + error('uranium: savedata not initialized! please run savedata.initializeModule first', 3) + end +end + +local function convertSource(s) + local pathSplit = split(s.source, '/') + local lastTwo = pathSplit[#pathSplit - 1] .. '/' .. string.sub(pathSplit[#pathSplit], 1, -5) + return lastTwo +end + +---@param data table +---@param name string | nil +function self.s(data, name) + if not name then name = convertSource(debug.getinfo(2, 'S')) end + checkIfInitialized() + if loadedSavedata then + error('uranium: cannot add new savedata after initializing!', 3) + end + if string.sub(name, 1, 1) == '_' then + error('uranium: cannot start savedata name with an underscore', 2) + end + savedata[name] = data +end + +--- prefers tab1 with type mismatches; prefers tab2 with value mismatches +local function mergeTable(tab1, tab2) + local tab = {} + for k, v1 in pairs(tab1) do + local v2 = tab2[k] + if type(v1) ~= type(v2) then + tab[k] = v1 + else + if type(v1) == 'table' then + tab[k] = mergeTable(v1, v2) + else + tab[k] = v2 + end + end + end + return tab +end + +--- replaces tab1 with tab2, preserving pointers +--- this sucks +local function replaceFromRoot(tab1, tab2) + local keys = {} + for k in pairs(tab1) do + if not includes(keys, k) then + table.insert(keys, k) + end + end + for k in pairs(tab2) do + if not includes(keys, k) then + table.insert(keys, k) + end + end + + for _, key in ipairs(keys) do + if type(tab1[key]) == type(tab2[key]) and type(tab1[key]) == 'table' then + replaceFromRoot(tab1[key], tab2[key]) + else + tab1[key] = tab2[key] + end + end +end + +function self.load() + checkIfInitialized() + print('loading...') + local profile = PROFILEMAN:GetMachineProfile():GetSaved() + local save = {} + local serialized = profile[savedataName] + + if not serialized then + print('no savedata found') + save = savedata + else + print('savedata found: ' .. serialized) + save = binser.deserializeN(serialized, 1) + print('deserialized: ' .. fullDump(save)) + end + + local newSavedata = mergeTable(savedata, save) + replaceFromRoot(savedata, newSavedata) + print('merged: ' .. fullDump(savedata)) +end + +---@param instant boolean | nil +function self.save(instant) + checkIfInitialized() + print('saving...') + local profile = PROFILEMAN:GetMachineProfile():GetSaved() + savedata._lastSave = {Hour(), Minute(), DayOfMonth(), MonthOfYear(), Year()} + print('savedata: ' .. fullDump(savedata)) + local serialized = binser.serialize(savedata) + print('serialized: ' .. serialized) + profile[savedataName] = serialized + + uranium:call('save') + if instant then + PROFILEMAN:SaveMachineProfile() + uranium:call('saveFinished') + else + self.saveNextFrame = true + end +end + +function uranium.init() + loadedSavedata = true + if savedataName then + self.load() + end +end + +function uranium.update() + if self.saveNextFrame then + self.saveNextFrame = false + PROFILEMAN:SaveMachineProfile() + uranium:call('saveFinished') + print('saved to profile') + end +end + +return self \ No newline at end of file diff --git a/stdlib/util.lua b/stdlib/util.lua index 7f1dbd7..b98b0bc 100644 --- a/stdlib/util.lua +++ b/stdlib/util.lua @@ -154,4 +154,26 @@ function clearMetatables(tab) clearMetatables(obj) end end +end + +---@param source string +---@param sep string +---@return string[] +-- https://stackoverflow.com/a/42607786 +function split(source, sep) + local result, i = {}, 1 + while true do + local a, b = string.find(source, sep) + if not a then break end + local candidat = string.sub(source, 1, a - 1) + if candidat ~= '' then + result[i] = candidat + end + i = i + 1 + source = string.sub(source, b + 1) + end + if source ~= '' then + result[i] = source + end + return result end \ No newline at end of file diff --git a/typings.lua b/typings.lua index bbfe5c9..80f7e48 100644 --- a/typings.lua +++ b/typings.lua @@ -11,6 +11,8 @@ GAMESTATE = {} PREFSMAN = {} ---@type ScreenManager SCREENMAN = {} +---@type ProfileManager +PROFILEMAN = {} ---@return Quad --- Defines a Quad actor.