193 lines
5.1 KiB
Lua
193 lines
5.1 KiB
Lua
local self = {}
|
|
|
|
local binser = require('stdlib.binser')
|
|
require('stdlib.util')
|
|
local scheduler = require('stdlib.scheduler')
|
|
|
|
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
|
|
scheduler:scheduleInTicks(2, function()
|
|
self.saveNextFrame = false
|
|
PROFILEMAN:SaveMachineProfile()
|
|
uranium:call('saveFinished')
|
|
end)
|
|
end
|
|
end
|
|
|
|
function self.getLastSave()
|
|
checkIfInitialized()
|
|
if savedata._lastSave and savedata._lastSave[1] == 0 and savedata._lastSave[2] == 0 and savedata._lastSave[3] == 0 and savedata._lastSave[4] == 0 and savedata._lastSave[5] == 0 then
|
|
return nil
|
|
else
|
|
return savedata._lastSave
|
|
end
|
|
end
|
|
|
|
function uranium.init()
|
|
loadedSavedata = true
|
|
if savedataName then
|
|
self.load()
|
|
end
|
|
end
|
|
|
|
function self.enableAutosave()
|
|
checkIfInitialized()
|
|
function uranium.exit()
|
|
self.save(true)
|
|
end
|
|
end
|
|
|
|
return self |