218 lines
6.1 KiB
Text
218 lines
6.1 KiB
Text
--[[
|
|
This script handles the serialization of data using the marshal library and the saving of this data to disk depending
|
|
on save game name. What this means is that you can save entire tables to disk instead of saving to object packet.
|
|
This script was created to alleviate issues with using packets to save dynamic data (pstor) which lead to save corruption.
|
|
Also it removes some restrictions on what you can save.
|
|
|
|
*only store valid lua types such as numbers, strings, boolean, functions or tables that contain these valid types. Userdata needs to have a special
|
|
__persist function defined in it's metatable. See how it is done for CTime in _G.script
|
|
|
|
*Supposedly you can save userdata if you write a proper __persist method for the metatable but I have failed to achieve proper results with serializing CTime.
|
|
|
|
*You must register for 'save_state' and 'load_state' and add your own table to m_data for it to be encoded then stored in *.scoc
|
|
*Although marshal is pretty fast, keep in mind that encoding/decoding a ton of data, saves will start to noticeablely take longer to save/load.
|
|
*For testing/debugging you can uncomment the print_table calls in save_state and load_state. It will save the before and after tables to print_table.txt in your main directory.
|
|
|
|
|
|
by: Alundaio
|
|
--]]
|
|
local m_data = {}
|
|
|
|
-- store stuff that you want to persist even offline
|
|
m_data.se_object = {}
|
|
|
|
-- store stuff only for online objects. When object goes offline this table is purged.
|
|
m_data.game_object = {}
|
|
|
|
-- PDA known contacts
|
|
-- m_data.actor_contacts = {}
|
|
|
|
saved_game_extension_ex = save_extension() .. "_data"
|
|
|
|
local function on_pstor_load_all(obj,packet)
|
|
local id = obj:id()
|
|
local state = get_game_object_state(obj,false)
|
|
if (state and db.storage[id]) then
|
|
if (state.pstor_all) then
|
|
db.storage[id].pstor = state.pstor_all
|
|
state.pstor_all = nil
|
|
end
|
|
|
|
if (state.pstor_ctime) then
|
|
db.storage[id].pstor_ctime = state.pstor_ctime
|
|
state.pstor_ctime = nil
|
|
end
|
|
end
|
|
end
|
|
|
|
function on_game_start()
|
|
if not isMarshal then
|
|
return
|
|
end
|
|
--RegisterScriptCallback("on_pstor_load_all",on_pstor_load_all)
|
|
end
|
|
|
|
-- called from engine!
|
|
function CALifeStorageManager_before_save(fname)
|
|
if not isMarshal then
|
|
return
|
|
end
|
|
|
|
--printf("CALifeStorageManager_before_save BEFORE callback")
|
|
|
|
m_data.GAME_VERSION = GAME_VERSION
|
|
|
|
SendScriptCallback("save_state",m_data)
|
|
|
|
--printf("CALifeStorageManager_before_save AFTER callback")
|
|
|
|
-- save pstor
|
|
for id,t in pairs(db.storage) do
|
|
if (m_data.game_object[id]) then
|
|
if (t.pstor and not is_empty(t.pstor)) then
|
|
m_data.game_object[id].pstor_all = t.pstor
|
|
end
|
|
|
|
-- serialization with game.CTime.__persist
|
|
if (t.pstor_ctime and not is_empty(t.pstor_ctime)) then
|
|
m_data.game_object[id].pstor_ctime = t.pstor_ctime
|
|
end
|
|
end
|
|
end
|
|
|
|
--ProcessEventQueueState(m_data,true)
|
|
|
|
-- clean out game_object table of empty sub tables
|
|
for id,tbl in pairs(m_data.game_object) do
|
|
for k,v in pairs(tbl) do
|
|
if (type(v) == "table" and is_empty(v)) then
|
|
m_data.game_object[id][k] = nil
|
|
end
|
|
end
|
|
end
|
|
|
|
local data = marshal.encode(m_data)
|
|
if not (data) then
|
|
return
|
|
end
|
|
|
|
local path = getFS():update_path('$game_saves$', '')
|
|
|
|
lfs.mkdir(path) -- incase savegame folder doesn't exist yet
|
|
path = path .. fname:sub(0,-6):lower() .. saved_game_extension_ex
|
|
|
|
local savegame = io.open(path,"wb")
|
|
if not (io.type(savegame) == "file") then
|
|
printf("Error: Unable to write to %s",path)
|
|
return
|
|
end
|
|
|
|
--printf("axr_main: saving custom data %s",path)
|
|
savegame:write(data)
|
|
savegame:close()
|
|
|
|
--printf("CALifeStorageManager_before_save FINISHED")
|
|
end
|
|
|
|
-- called from engine!
|
|
function CALifeStorageManager_after_save(fname)
|
|
if ffx_path_utils then
|
|
fname = ffx_path_utils.get_file_name(fname, false)
|
|
end
|
|
|
|
local _path = getFS():update_path('$game_saves$', '')
|
|
|
|
SendScriptCallback("save_file_created", _path, fname)
|
|
end
|
|
|
|
-- called from engine!
|
|
function CALifeStorageManager_new_game(fname)
|
|
if ffx_path_utils then
|
|
fname = ffx_path_utils.get_file_name(fname, false)
|
|
end
|
|
|
|
local _path = getFS():update_path('$game_saves$', '')
|
|
|
|
SendScriptCallback("new_game_created", _path, fname)
|
|
end
|
|
|
|
-- called from engine!
|
|
function CALifeStorageManager_save(fname)
|
|
--printf("CALifeStorageManager_save START FINISHED")
|
|
end
|
|
|
|
-- called from engine
|
|
function CALifeStorageManager_load(fname)
|
|
if not isMarshal then
|
|
return
|
|
end
|
|
|
|
local path = fname:sub(0,-6) .. saved_game_extension_ex
|
|
|
|
--alun_utils.debug_write(strformat("CALifeStorageManager_load %s",path))
|
|
|
|
local savegame = io.open(path,"rb")
|
|
if not (io.type(savegame) == "file") then
|
|
return
|
|
end
|
|
|
|
local data = savegame:read("*all")
|
|
savegame:close()
|
|
|
|
if not (data and data ~= "") then
|
|
printf("Error: Failed to read %s",path)
|
|
return
|
|
end
|
|
|
|
m_data = marshal.decode(data)
|
|
|
|
--ProcessEventQueueState(m_data,false)
|
|
|
|
-- For debugging save state
|
|
--alun_utils.print_table(m_data,"m_data_on_load ("..path..")")
|
|
|
|
SendScriptCallback("load_state",m_data)
|
|
|
|
if ffx_path_utils then
|
|
fname = ffx_path_utils.get_file_name(fname, false)
|
|
end
|
|
local _path = getFS():update_path('$game_saves$', '')
|
|
SendScriptCallback("save_file_loaded", _path, fname)
|
|
|
|
--alun_utils.debug_write(strformat("CALifeStorageManager_load END"))
|
|
end
|
|
|
|
function get_state()
|
|
return m_data
|
|
end
|
|
|
|
function decode(t)
|
|
return marshal.decode(t)
|
|
end
|
|
|
|
-- storage based on ID but verified by object name
|
|
function get_game_object_state(obj,create_if_dont_exist)
|
|
local id = obj:id()
|
|
local name = obj:name()
|
|
if not (m_data.game_object[id]) then
|
|
if not (create_if_dont_exist) then
|
|
return
|
|
end
|
|
m_data.game_object[id] = {}
|
|
m_data.game_object[id].name = name
|
|
end
|
|
return m_data.game_object[id]
|
|
end
|
|
|
|
function get_se_obj_state(se_obj,create_if_dont_exist)
|
|
local id = se_obj.id
|
|
local name = se_obj:name()
|
|
if not (m_data.se_object[id]) then
|
|
if not (create_if_dont_exist) then
|
|
return
|
|
end
|
|
m_data.se_object[id] = {}
|
|
m_data.se_object[id].name = name
|
|
end
|
|
return m_data.se_object[id]
|
|
end
|