From d9560f164c5b6180bc34bce011aa334fff309e13 Mon Sep 17 00:00:00 2001 From: mnelenpridumivat Date: Thu, 2 Jan 2025 13:57:02 +0300 Subject: [PATCH] add WIP support of loading from new saves --- src/xrCore/Save/MemoryBuffer.h | 6 +- src/xrCore/Save/SaveChunk.cpp | 343 +++++++++++++++++++- src/xrCore/Save/SaveChunk.h | 28 +- src/xrCore/Save/SaveInterface.h | 2 + src/xrCore/Save/SaveManager.cpp | 145 ++++++++- src/xrCore/Save/SaveManager.h | 22 +- src/xrCore/Save/SaveObject.cpp | 28 ++ src/xrCore/Save/SaveObject.h | 5 + src/xrCore/Save/SaveVariables.cpp | 38 +-- src/xrCore/Save/SaveVariables.h | 190 +++-------- src/xrGame/Actor_Network.cpp | 2 +- src/xrGame/action_planner_inline.h | 6 + src/xrGame/alife_object_registry.cpp | 2 +- src/xrGame/alife_spawn_registry.cpp | 4 + src/xrGame/alife_storage_manager.cpp | 55 +++- src/xrGame/alife_storage_manager.h | 1 + src/xrGame/saved_game_wrapper.cpp | 40 ++- src/xrScripts/exports/SaveSystem_script.cpp | 6 +- 18 files changed, 703 insertions(+), 220 deletions(-) diff --git a/src/xrCore/Save/MemoryBuffer.h b/src/xrCore/Save/MemoryBuffer.h index 15ebdb9a01..fa3a3af074 100644 --- a/src/xrCore/Save/MemoryBuffer.h +++ b/src/xrCore/Save/MemoryBuffer.h @@ -87,8 +87,10 @@ class CMemoryBuffer template<> bool Write(shared_str data) { - Write(data.size()); - Write(data.c_str(), data.size()); + string256 buffer; + memcpy(buffer, data.c_str(), data.size()); + buffer[data.size()] = 0; + Write(buffer, data.size()+1); return true; } diff --git a/src/xrCore/Save/SaveChunk.cpp b/src/xrCore/Save/SaveChunk.cpp index 43b46ed264..93a7d3d4b2 100644 --- a/src/xrCore/Save/SaveChunk.cpp +++ b/src/xrCore/Save/SaveChunk.cpp @@ -29,21 +29,28 @@ void CSaveChunk::Write(CMemoryBuffer& Buffer) void CSaveChunk::ReadArray(u64& Size) { if (_currentArrayStack.empty()) { - VERIFY(_variables[_currentReadIndex]->IsArray()); - _currentArrayStack.push((CSaveVariableArray*)_variables[_currentReadIndex]); + auto Array = smart_cast(_variables[_currentReadIndex]); + VERIFY(Array); + _currentArrayStack.push(Array); } else { auto CurrentArray = _currentArrayStack.top(); - VERIFY(CurrentArray->GetCurrentElement()->IsArray()); - _currentArrayStack.push((CSaveVariableArray*)CurrentArray); + auto CurrentElement = smart_cast(CurrentArray->GetCurrentElement()); + VERIFY(CurrentElement); + _currentArrayStack.push(CurrentElement); } Size = _currentArrayStack.top()->GetSize(); } void CSaveChunk::WriteArray(u64 Size) { - _variables.emplace_back(new CSaveVariableArray(Size)); - auto ArrayPtr = (CSaveVariableArray*)_variables.back(); + if (Size == u64(-1)) { + _variables.emplace_back(new CSaveVariableArrayUnspec()); + } + else { + _variables.emplace_back(new CSaveVariableArray(Size)); + } + auto ArrayPtr = (ISaveVariableArray*)_variables.back(); _currentArrayStack.push(ArrayPtr); } @@ -463,6 +470,330 @@ void CSaveChunk::r_string(LPSTR S) } } +void CSaveChunk::Parse(IReader* stream) +{ + { + ESaveVariableType type; + stream->r(&type, sizeof(ESaveVariableType)); + while(type == ESaveVariableType::t_chunkStart) { + shared_str subchunk_name; + CSaveManager::GetInstance().ConditionalReadString(stream, subchunk_name); + CSaveChunk* NewChunk = new CSaveChunk(subchunk_name); + NewChunk->Parse(stream); + stream->r(&type, sizeof(ESaveVariableType)); + _subchunks[subchunk_name] = NewChunk; + } + ParseRec(stream, type); + } +} + +void CSaveChunk::ParseRec(IReader* stream, ESaveVariableType type_key) +{ + ESaveVariableType type = type_key; + while (type != ESaveVariableType::t_chunkEnd) { + switch (type) + { + case ESaveVariableType::t_chunkStart: { + if (_currentArrayStack.empty()) { + Msg("_currentArrayStack.empty()"); + } + //VERIFY(!_currentArrayStack.empty()); + shared_str subchunk_name; + CSaveManager::GetInstance().ConditionalReadString(stream, subchunk_name); + CSaveChunk* NewChunk = new CSaveChunk(subchunk_name); + NewChunk->Parse(stream); + VERIFY(!_currentArrayStack.empty()); + _currentArrayStack.top()->AddVariable(NewChunk); + break; + } + case ESaveVariableType::t_bool: { + bool Value; + CSaveManager::GetInstance().ConditionalReadBool(stream, Value); + auto Var = new CSaveVariableBool(Value); + if (_currentArrayStack.empty()) { + _variables.emplace_back(Var); + } + else { + _currentArrayStack.top()->AddVariable(Var); + } + break; + } + case ESaveVariableType::t_float: { + auto Var = new CSaveVariableFloat(stream->r_float()); + if (_currentArrayStack.empty()) { + _variables.emplace_back(Var); + } + else { + _currentArrayStack.top()->AddVariable(Var); + } + break; + } + case ESaveVariableType::t_double: { + double Value; + stream->r(&Value, sizeof(double)); + auto Var = new CSaveVariableFloat(stream->r_float()); + if (_currentArrayStack.empty()) { + _variables.emplace_back(Var); + } + else { + _currentArrayStack.top()->AddVariable(Var); + } + break; + } + case ESaveVariableType::t_u64: { + auto Var = new CSaveVariableU64(stream->r_u64()); + if (_currentArrayStack.empty()) { + _variables.emplace_back(Var); + } + else { + _currentArrayStack.top()->AddVariable(Var); + } + break; + } + case ESaveVariableType::t_u64_op32: { + auto Var = new CSaveVariableU64(stream->r_u32()); + if (_currentArrayStack.empty()) { + _variables.emplace_back(Var); + } + else { + _currentArrayStack.top()->AddVariable(Var); + } + break; + } + case ESaveVariableType::t_u64_op16: { + auto Var = new CSaveVariableU64(stream->r_u16()); + if (_currentArrayStack.empty()) { + _variables.emplace_back(Var); + } + else { + _currentArrayStack.top()->AddVariable(Var); + } + break; + } + case ESaveVariableType::t_u64_op8: { + auto Var = new CSaveVariableU64(stream->r_u8()); + if (_currentArrayStack.empty()) { + _variables.emplace_back(Var); + } + else { + _currentArrayStack.top()->AddVariable(Var); + } + break; + } + case ESaveVariableType::t_s64: { + auto Var = new CSaveVariableS64(stream->r_s64()); + if (_currentArrayStack.empty()) { + _variables.emplace_back(Var); + } + else { + _currentArrayStack.top()->AddVariable(Var); + } + break; + } + case ESaveVariableType::t_s64_op32: { + auto Var = new CSaveVariableS64(stream->r_s32()); + if (_currentArrayStack.empty()) { + _variables.emplace_back(Var); + } + else { + _currentArrayStack.top()->AddVariable(Var); + } + break; + } + case ESaveVariableType::t_s64_op16: { + auto Var = new CSaveVariableS64(stream->r_s16()); + if (_currentArrayStack.empty()) { + _variables.emplace_back(Var); + } + else { + _currentArrayStack.top()->AddVariable(Var); + } + break; + } + case ESaveVariableType::t_s64_op8: { + auto Var = new CSaveVariableS64(stream->r_s8()); + if (_currentArrayStack.empty()) { + _variables.emplace_back(Var); + } + else { + _currentArrayStack.top()->AddVariable(Var); + } + break; + } + case ESaveVariableType::t_u32: { + auto Var = new CSaveVariableU32(stream->r_u32()); + if (_currentArrayStack.empty()) { + _variables.emplace_back(Var); + } + else { + _currentArrayStack.top()->AddVariable(Var); + } + break; + } + case ESaveVariableType::t_u32_op16: { + auto Var = new CSaveVariableU32(stream->r_u16()); + if (_currentArrayStack.empty()) { + _variables.emplace_back(Var); + } + else { + _currentArrayStack.top()->AddVariable(Var); + } + break; + } + case ESaveVariableType::t_u32_op8: { + auto Var = new CSaveVariableU32(stream->r_u8()); + if (_currentArrayStack.empty()) { + _variables.emplace_back(Var); + } + else { + _currentArrayStack.top()->AddVariable(Var); + } + break; + } + case ESaveVariableType::t_s32: { + auto Var = new CSaveVariableS32(stream->r_s32()); + if (_currentArrayStack.empty()) { + _variables.emplace_back(Var); + } + else { + _currentArrayStack.top()->AddVariable(Var); + } + break; + } + case ESaveVariableType::t_s32_op16: { + auto Var = new CSaveVariableS32(stream->r_s16()); + if (_currentArrayStack.empty()) { + _variables.emplace_back(Var); + } + else { + _currentArrayStack.top()->AddVariable(Var); + } + break; + } + case ESaveVariableType::t_s32_op8: { + auto Var = new CSaveVariableS32(stream->r_s8()); + if (_currentArrayStack.empty()) { + _variables.emplace_back(Var); + } + else { + _currentArrayStack.top()->AddVariable(Var); + } + break; + } + case ESaveVariableType::t_u16: { + auto Var = new CSaveVariableU16(stream->r_u16()); + if (_currentArrayStack.empty()) { + _variables.emplace_back(Var); + } + else { + _currentArrayStack.top()->AddVariable(Var); + } + break; + } + case ESaveVariableType::t_u16_op8: { + auto Var = new CSaveVariableU16(stream->r_u8()); + if (_currentArrayStack.empty()) { + _variables.emplace_back(Var); + } + else { + _currentArrayStack.top()->AddVariable(Var); + } + break; + } + case ESaveVariableType::t_s16: { + auto Var = new CSaveVariableS16(stream->r_s16()); + if (_currentArrayStack.empty()) { + _variables.emplace_back(Var); + } + else { + _currentArrayStack.top()->AddVariable(Var); + } + break; + } + case ESaveVariableType::t_s16_op8: { + auto Var = new CSaveVariableS16(stream->r_s8()); + if (_currentArrayStack.empty()) { + _variables.emplace_back(Var); + } + else { + _currentArrayStack.top()->AddVariable(Var); + } + break; + } + case ESaveVariableType::t_u8: { + auto Var = new CSaveVariableU8(stream->r_u8()); + if (_currentArrayStack.empty()) { + _variables.emplace_back(Var); + } + else { + _currentArrayStack.top()->AddVariable(Var); + } + break; + } + case ESaveVariableType::t_s8: { + auto Var = new CSaveVariableS8(stream->r_s8()); + if (_currentArrayStack.empty()) { + _variables.emplace_back(Var); + } + else { + _currentArrayStack.top()->AddVariable(Var); + } + break; + } + case ESaveVariableType::t_string: { + shared_str Value; + CSaveManager::GetInstance().ConditionalReadString(stream, Value); + auto Var = new CSaveVariableString(Value); + if (_currentArrayStack.empty()) { + _variables.emplace_back(Var); + } + else { + _currentArrayStack.top()->AddVariable(Var); + } + break; + } + case ESaveVariableType::t_array: { + size_t Size; + stream->r(&Size, sizeof(size_t)); + auto Var = new CSaveVariableArray(Size); + if (_currentArrayStack.empty()) { + _variables.emplace_back(Var); + } + else { + _currentArrayStack.top()->AddVariable(Var); + } + _currentArrayStack.push(Var); + for (u64 i = 0; i < Size; ++i) { + stream->r(&type, sizeof(ESaveVariableType)); + ParseRec(stream, type); + } + _currentArrayStack.pop(); + break; + } + case ESaveVariableType::t_arrayUnspec: { + auto Var = new CSaveVariableArrayUnspec(); + if (_currentArrayStack.empty()) { + _variables.emplace_back(Var); + } + else { + _currentArrayStack.top()->AddVariable(Var); + } + _currentArrayStack.push(Var); + stream->r(&type, sizeof(ESaveVariableType)); + while (type != ESaveVariableType::t_arrayUnspecEnd) { + stream->r(&type, sizeof(ESaveVariableType)); + ParseRec(stream, type); + } + _currentArrayStack.pop(); + } + default: { + FATAL("Invalid save chunk type!"); + } + } + stream->r(&type, sizeof(ESaveVariableType)); + } +} + /*void CSaveChunk::r_stringZ(xr_string& dest) { if (_currentArrayStack.empty()) { diff --git a/src/xrCore/Save/SaveChunk.h b/src/xrCore/Save/SaveChunk.h index 6f0771f805..e43e177fbe 100644 --- a/src/xrCore/Save/SaveChunk.h +++ b/src/xrCore/Save/SaveChunk.h @@ -10,7 +10,9 @@ class XRCORE_API CSaveChunk: public ISaveable { xr_map _subchunks; xr_vector _variables; u64 _currentReadIndex; - xr_stack _currentArrayStack; + xr_stack _currentArrayStack; + + void ParseRec(IReader* stream, ESaveVariableType type_key); public: CSaveChunk(shared_str ChunkName) : _chunkName(ChunkName) {} @@ -19,7 +21,6 @@ class XRCORE_API CSaveChunk: public ISaveable { void Write(CMemoryBuffer& Buffer); virtual ESaveVariableType GetVariableType() override { return ESaveVariableType::t_chunk; } - virtual bool IsArray() override { return false; } void ReadArray(u64& Size); void WriteArray(u64 Size); @@ -32,8 +33,6 @@ class XRCORE_API CSaveChunk: public ISaveable { void w_bool(bool a); void w_float(float a); void w_double(double a); - //void w_vec3(const Fvector& a); - //void w_vec4(const Fvector4& a); void w_u64(u64 a); void w_s64(s64 a); void w_u32(u32 a); @@ -44,17 +43,8 @@ class XRCORE_API CSaveChunk: public ISaveable { void w_s8(s8 a); void w_string(LPCSTR S); - //void w_string(LPCSTR S) { w_stringZ(S); } - - //void w_stringZ(const shared_str& p); - //void w_stringZ(const xr_string& p); - //void w_matrix(const Fmatrix& M); - //void w_clientID(ClientID& C); - // reading - utilities void r_bool(bool& A); - //void r_vec3(Fvector& A); - //void r_vec4(Fvector4& A); void r_float(float& A); void r_double(double& A); void r_u64(u64& A); @@ -66,18 +56,8 @@ class XRCORE_API CSaveChunk: public ISaveable { void r_u8(u8& A); void r_s8(s8& A); void r_string(LPSTR S); - //void r_stringZ(xr_string& dest); - //void r_stringZ(shared_str& dest); - //void r_stringZ_s(LPSTR string, u32 size); - - //template - //inline void r_stringZ_s(char(&string)[size]) - //{ - // r_stringZ_s(string, size); - //} - //void r_matrix(Fmatrix& M); - //void r_clientID(ClientID& C); + void Parse(IReader* stream); DECLARE_SCRIPT_REGISTER_FUNCTION }; \ No newline at end of file diff --git a/src/xrCore/Save/SaveInterface.h b/src/xrCore/Save/SaveInterface.h index d8d4335bd2..4ac3f6db4f 100644 --- a/src/xrCore/Save/SaveInterface.h +++ b/src/xrCore/Save/SaveInterface.h @@ -7,6 +7,8 @@ class XRCORE_API ISaveObject { public: virtual void BeginChunk(shared_str ChunkName) = 0; virtual void EndChunk() = 0; + virtual void BeginArray(size_t Size) = 0; + virtual void EndArray() = 0; virtual bool IsSave() = 0; diff --git a/src/xrCore/Save/SaveManager.cpp b/src/xrCore/Save/SaveManager.cpp index 9fe2d84fe9..bbe8be0d5d 100644 --- a/src/xrCore/Save/SaveManager.cpp +++ b/src/xrCore/Save/SaveManager.cpp @@ -1,6 +1,9 @@ #include "stdafx.h" #include "SaveManager.h" #include "MemoryBuffer.h" +//#include "../../xrGame/Level.h" +//#include "../../xrGame/Actor.h" +//#include "../../xrGame/ai_space.h" CSaveManager::CSaveManager() { @@ -40,6 +43,16 @@ CSaveObjectSave* CSaveManager::BeginSave() return SaveData; } +CSaveObjectLoad* CSaveManager::BeginLoad(IReader* stream) +{ + ReadHeader(stream); + ReadStrings(stream); + ReadBools(stream); + auto LoadData = new CSaveObjectLoad(); + LoadData->Parse(stream); + return LoadData; +} + void CSaveManager::WriteSavedData(const string_path& to_file) { SaveWriter = FS.w_open(to_file); @@ -94,11 +107,11 @@ void CSaveManager::ConditionalWriteString(shared_str Value, CMemoryBuffer& buffe } } -void CSaveManager::ConditionalWriteBool(bool& Value, CMemoryBuffer& buffer) +void CSaveManager::ConditionalWriteBool(bool Value, CMemoryBuffer& buffer) { if (TestFlag(CSaveManager::ESaveManagerFlagsGeneral::EUseBoolOptimization)) { - BoolQueue->push(&Value); + BoolQueue->push(Value); ++BoolsNum; } else { @@ -106,9 +119,66 @@ void CSaveManager::ConditionalWriteBool(bool& Value, CMemoryBuffer& buffer) } } +void CSaveManager::ConditionalReadBool(IReader* stream, bool& Value) +{ + if (TestFlag(CSaveManager::ESaveManagerFlagsGeneral::EUseBoolOptimization)) + { + Value = BoolQueue->front(); + BoolQueue->pop(); + } + else { + stream->r(&Value, sizeof(bool)); + } +} + +void CSaveManager::ConditionalReadString(IReader* stream, shared_str& Value) +{ + if (TestFlag(CSaveManager::ESaveManagerFlagsGeneral::EUseStringOptimization)) { + u64 StringRefID = stream->r_u64(); + auto StringKey = StringRefID >> 32; + u32 StringVecID = StringRefID & 0xFFFFFFFF; + auto it = StringsHashesMap->find(StringKey); + VERIFY(it != StringsHashesMap->end()); + VERIFY(StringVecID < it->second.size()); + Value = it->second[StringVecID]; + } + else { + Value = ReadStringInternal(stream); + } +} + +bool CSaveManager::GetGameInfoFast(IReader* stream, SGameInfoFast& data) +{ + { + ESaveVariableType type; + stream->r(&type, sizeof(ESaveVariableType)); + if (type != ESaveVariableType::t_chunk) { + return false; + } + } + data.m_actor_health = stream->r_float(); + data.m_game_time = stream->r_u64(); + data.m_level_name = ReadStringInternal(stream); + return true; +} + +void CSaveManager::SkipGameInfo(IReader* stream) +{ + SGameInfoFast data; + GetGameInfoFast(stream, data); +} + +void CSaveManager::WriteGameInfo(const SGameInfoFast& data) +{ + GameInfo = data; +} + void CSaveManager::WriteHeader() { Buffers.BufferHeader->Write(ESaveVariableType::t_chunk); + Buffers.BufferHeader->Write(GameInfo.m_actor_health); + Buffers.BufferHeader->Write(GameInfo.m_game_time); + Buffers.BufferHeader->Write(GameInfo.m_level_name); Buffers.BufferHeader->Write(ControlFlagsDefault.flags); Buffers.BufferHeader->Write(SaveWriter); } @@ -157,11 +227,82 @@ void CSaveManager::WriteData() Buffers.BufferGeneral->Write(SaveWriter); } +void CSaveManager::ReadHeader(IReader* stream) +{ + SkipGameInfo(stream); + ControlFlagsDefault.flags = stream->r_u8(); +} + +void CSaveManager::ReadStrings(IReader* stream) +{ + { + ESaveVariableType type; + stream->r(&type, sizeof(ESaveVariableType)); + VERIFY(type == ESaveVariableType::t_chunk); + } + StringsHashesMap = xr_make_unique>>(); + auto MapSize = stream->r_u64(); + { + ESaveVariableType type; + stream->r(&type, sizeof(ESaveVariableType)); + VERIFY(type == ESaveVariableType::t_array); + } + for (u64 i = 0; i < MapSize; ++i) { + u32 MapKey = stream->r_u32(); + { + ESaveVariableType type; + stream->r(&type, sizeof(ESaveVariableType)); + VERIFY(type == ESaveVariableType::t_array); + } + auto ArraySize = stream->r_u64(); + for (u64 j = 0; j < ArraySize; ++j) { + StringsHashesMap->try_emplace(MapKey, xr_vector()); + StringsHashesMap->at(MapKey).emplace_back(ReadStringInternal(stream)); + } + } +} + +void CSaveManager::ReadBools(IReader* stream) +{ + { + ESaveVariableType type; + stream->r(&type, sizeof(ESaveVariableType)); + VERIFY(type == ESaveVariableType::t_chunk); + } + BoolQueue = xr_make_unique>(); + BoolsNum = stream->r_u64(); + Flags8 Flags; + u8 ReadFlags = 8; + for (u64 i = 0; i < BoolsNum; ++i) { + if (ReadFlags == 8) { + Flags.flags = stream->r_u8(); + ReadFlags = 0; + } + BoolQueue->push(Flags.bitTest(ReadFlags++)); + } +} + +/*void CSaveManager::ReadData(IReader* stream) +{ + +}*/ + void CSaveManager::CompileData() { SaveData->Write(Buffers.BufferGeneral); } +shared_str CSaveManager::ReadStringInternal(IReader* stream) +{ + /*auto str_size = stream->r_u32(); + VERIFY(str_size < 256); + string256 Str; + stream->r(Str, str_size);*/ + shared_str buffer; + stream->r_stringZ(buffer); + return buffer; +} + inline void CSaveManager::SMemoryBuffers::Init() { VERIFY(!(BufferHeader || BufferStrings || BufferBools || BufferGeneral)); BufferHeader = new CMemoryBuffer(); diff --git a/src/xrCore/Save/SaveManager.h b/src/xrCore/Save/SaveManager.h index 84bbed80fd..d38e4761ab 100644 --- a/src/xrCore/Save/SaveManager.h +++ b/src/xrCore/Save/SaveManager.h @@ -61,18 +61,35 @@ class XRCORE_API CSaveManager }; + struct SGameInfoFast { + u64 m_game_time; + shared_str m_level_name; + float m_actor_health; + }; + + bool GetGameInfoFast(IReader* stream, SGameInfoFast& data); + void SkipGameInfo(IReader* stream); + void WriteGameInfo(const SGameInfoFast& data); + private: SMemoryBuffers Buffers; Flags8 ControlFlagsDefault; Flags8 ControlFlagsRead; + SGameInfoFast GameInfo; void WriteHeader(); void WriteStrings(); void WriteBools(); void WriteData(); + void ReadHeader(IReader* stream); + void ReadStrings(IReader* stream); + void ReadBools(IReader* stream); + //void ReadData(IReader* stream); void CompileData(); + shared_str ReadStringInternal(IReader* stream); + public: enum class ESaveManagerFlagsGeneral : u8 { @@ -94,9 +111,12 @@ class XRCORE_API CSaveManager bool IsSaving(); CSaveObjectSave* BeginSave(); + CSaveObjectLoad* BeginLoad(IReader* stream); void WriteSavedData(const string_path& to_file); void ConditionalWriteString(shared_str Value, CMemoryBuffer& buffer); - void ConditionalWriteBool(bool& Value, CMemoryBuffer& buffer); + void ConditionalWriteBool(bool Value, CMemoryBuffer& buffer); + void ConditionalReadString(IReader* stream, shared_str& Value); + void ConditionalReadBool(IReader* stream, bool& Value); }; \ No newline at end of file diff --git a/src/xrCore/Save/SaveObject.cpp b/src/xrCore/Save/SaveObject.cpp index 920cf29a11..fb2b5998cd 100644 --- a/src/xrCore/Save/SaveObject.cpp +++ b/src/xrCore/Save/SaveObject.cpp @@ -34,6 +34,11 @@ void CSaveObject::EndChunk() _chunkStack.pop(); } +void CSaveObject::EndArray() +{ + GetCurrentChunk()->EndArray(); +} + CSaveObjectSave::CSaveObjectSave() { //_rootChunk = new CSaveChunk("Root"); @@ -53,6 +58,11 @@ void CSaveObjectSave::BeginChunk(shared_str ChunkName) _chunkStack.push(_chunkStack.top()->BeginChunk(ChunkName)); } +void CSaveObjectSave::BeginArray(size_t Size) +{ + GetCurrentChunk()->WriteArray(Size); +} + ISaveObject& CSaveObjectSave::operator<<(float& Value) { GetCurrentChunk()->w_float(Value); @@ -149,6 +159,11 @@ void CSaveObjectLoad::BeginChunk(shared_str ChunkName) _chunkStack.push(_chunkStack.top()->FindChunk(ChunkName)); } +void CSaveObjectLoad::BeginArray(size_t Size) +{ + GetCurrentChunk()->ReadArray(Size); +} + ISaveObject& CSaveObjectLoad::operator<<(float& Value) { GetCurrentChunk()->r_float(Value); @@ -221,6 +236,19 @@ ISaveObject& CSaveObjectLoad::operator<<(LPSTR S) return *this; } +void CSaveObjectLoad::Parse(IReader* stream) +{ + { + ESaveVariableType type; + stream->r(&type, sizeof(ESaveVariableType)); + VERIFY(type == ESaveVariableType::t_chunkStart); + } + shared_str chunkName; + CSaveManager::GetInstance().ConditionalReadString(stream, chunkName); + VERIFY(chunkName == "Root"); + GetCurrentChunk()->Parse(stream); +} + ISaveObject& operator<<(ISaveObject& Object, char& Value) { return Object << (s8&)Value; } \ No newline at end of file diff --git a/src/xrCore/Save/SaveObject.h b/src/xrCore/Save/SaveObject.h index 20e7056002..e85f6612d3 100644 --- a/src/xrCore/Save/SaveObject.h +++ b/src/xrCore/Save/SaveObject.h @@ -22,6 +22,7 @@ class XRCORE_API CSaveObject: CSaveObject(CSaveChunk* Root); ~CSaveObject(); void EndChunk() override; + void EndArray() override; template ISaveObject& Serialize(xr_map& Value) { @@ -374,6 +375,7 @@ class XRCORE_API CSaveObjectSave: public CSaveObject { CSaveObjectSave(CSaveChunk* Root); virtual void BeginChunk(shared_str ChunkName) override; + virtual void BeginArray(size_t Size) override; virtual bool IsSave() override { return true; } @@ -399,6 +401,7 @@ class XRCORE_API CSaveObjectLoad: public CSaveObject { CSaveObjectLoad(CSaveChunk* Root); virtual void BeginChunk(shared_str ChunkName) override; + virtual void BeginArray(size_t Size) override; virtual bool IsSave() override { return false; } @@ -415,4 +418,6 @@ class XRCORE_API CSaveObjectLoad: public CSaveObject { virtual ISaveObject& operator<<(bool& Value) override; virtual ISaveObject& operator<<(LPSTR S) override; + void Parse(IReader* stream); + }; \ No newline at end of file diff --git a/src/xrCore/Save/SaveVariables.cpp b/src/xrCore/Save/SaveVariables.cpp index 6f443c339b..152195a8ad 100644 --- a/src/xrCore/Save/SaveVariables.cpp +++ b/src/xrCore/Save/SaveVariables.cpp @@ -37,23 +37,6 @@ void CSaveVariableDouble::Write(CMemoryBuffer& Buffer) Buffer.Write(_value); } -/*void CSaveVariableVec3::Write(CMemoryBuffer& Buffer) -{ - Buffer.Write(ESaveVariableType::t_vec3); - Buffer.Write(_value.x); - Buffer.Write(_value.y); - Buffer.Write(_value.z); -} - -void CSaveVariableVec4::Write(CMemoryBuffer& Buffer) -{ - Buffer.Write(ESaveVariableType::t_vec3); - Buffer.Write(_value.x); - Buffer.Write(_value.y); - Buffer.Write(_value.z); - Buffer.Write(_value.w); -}*/ - void CSaveVariableU64::Write(CMemoryBuffer& Buffer) { if (CSaveManager::GetInstance().TestFlag(CSaveManager::ESaveManagerFlagsGeneral::EUseIntOptimization)) { @@ -180,17 +163,18 @@ void CSaveVariableString::Write(CMemoryBuffer& Buffer) CSaveManager::GetInstance().ConditionalWriteString(_value, Buffer); } -/*void CSaveVariableMatrix::Write(CMemoryBuffer& Buffer) +CSaveVariableArrayUnspec::~CSaveVariableArrayUnspec() { - Buffer.Write(ESaveVariableType::t_matrix); - Buffer.Write(_value._11); Buffer.Write(_value._12); Buffer.Write(_value._13); Buffer.Write(_value._14); - Buffer.Write(_value._21); Buffer.Write(_value._22); Buffer.Write(_value._23); Buffer.Write(_value._24); - Buffer.Write(_value._31); Buffer.Write(_value._32); Buffer.Write(_value._33); Buffer.Write(_value._34); - Buffer.Write(_value._41); Buffer.Write(_value._42); Buffer.Write(_value._43); Buffer.Write(_value._44); + for (size_t i = 0; i < _array.size(); ++i) { + xr_delete(_array[i]); + } } -void CSaveVariableClientID::Write(CMemoryBuffer& Buffer) +void CSaveVariableArrayUnspec::Write(CMemoryBuffer& Buffer) { - Buffer.Write(ESaveVariableType::t_clientID); - Buffer.Write(_value.value()); -}*/ + Buffer.Write(ESaveVariableType::t_arrayUnspec); + for (const auto& elem : _array) { + elem->Write(Buffer); + } + Buffer.Write(ESaveVariableType::t_arrayUnspecEnd); +} diff --git a/src/xrCore/Save/SaveVariables.h b/src/xrCore/Save/SaveVariables.h index 3af36955b4..1235adb96d 100644 --- a/src/xrCore/Save/SaveVariables.h +++ b/src/xrCore/Save/SaveVariables.h @@ -43,6 +43,8 @@ enum class XRCORE_API ESaveVariableType : u8 { t_chunkStart, t_chunkEnd, t_array, + t_arrayUnspec, + t_arrayUnspecEnd, t_chunk, t_invalid = u8(-1), }; @@ -50,39 +52,70 @@ enum class XRCORE_API ESaveVariableType : u8 { class XRCORE_API ISaveable{ public: virtual ESaveVariableType GetVariableType() = 0; - virtual bool IsArray() = 0; + //virtual bool IsArray() = 0; virtual void Write(CMemoryBuffer& Buffer) = 0; }; -class XRCORE_API CSaveVariableBase: public ISaveable { +class XRCORE_API CSaveVariableBase: + public ISaveable +{ protected: virtual void* GetValue() { return nullptr; } public: virtual ESaveVariableType GetVariableType() override { return ESaveVariableType::t_invalid; } - virtual bool IsArray() { return false; } + //virtual bool IsArray() override { return false; } }; -class XRCORE_API CSaveVariableArray : - public CSaveVariableBase +class XRCORE_API ISaveVariableArray { - u64 _size; +protected: u64 _currentReadPos = 0; xr_vector _array; +public: + virtual ISaveable* GetCurrentElement() = 0; + virtual void Next() = 0; + virtual void AddVariable(ISaveable* data) = 0; + virtual u64 GetSize() { return -1; } +}; + +// Similar to CSaveVariableArray, but without io limit safety. More dangerous, use if CSaveVariableArray usage impossible +class XRCORE_API CSaveVariableArrayUnspec : + public CSaveVariableBase, + public ISaveVariableArray +{ + +public: + CSaveVariableArrayUnspec() {} + ~CSaveVariableArrayUnspec(); + + virtual ESaveVariableType GetVariableType() override { return ESaveVariableType::t_arrayUnspec; } + virtual void Write(CMemoryBuffer& Buffer) override; + + virtual ISaveable* GetCurrentElement() override { VERIFY(_currentReadPos < _array.size()); return _array[_currentReadPos]; } + virtual void Next() override { ++_currentReadPos; } + + virtual void AddVariable(ISaveable* data) override { _array.emplace_back(data); } +}; + +class XRCORE_API CSaveVariableArray : + public CSaveVariableBase, + public ISaveVariableArray +{ + u64 _size; public: CSaveVariableArray(u64 Size) : _size(Size) {} ~CSaveVariableArray(); virtual ESaveVariableType GetVariableType() override { return ESaveVariableType::t_array; } - virtual bool IsArray() { return true; } virtual void Write(CMemoryBuffer& Buffer) override; - u64 GetSize() { return _size; } - ISaveable* GetCurrentElement() { VERIFY(_currentReadPos < _size); return _array[_currentReadPos]; } - void Next() { ++_currentReadPos; } + virtual u64 GetSize() override { return _size; } + virtual ISaveable* GetCurrentElement() override { VERIFY(_currentReadPos < _size); return _array[_currentReadPos]; } + virtual void Next() override { ++_currentReadPos; } - void AddVariable(ISaveable* data) { _array.emplace_back(data); } + virtual void AddVariable(ISaveable* data) override { _array.emplace_back(data); } }; class XRCORE_API CSaveVariableBool: @@ -133,38 +166,6 @@ class XRCORE_API CSaveVariableDouble : virtual void Write(CMemoryBuffer& Buffer) override; }; -/*class XRCORE_API CSaveVariableVec3 : - public CSaveVariableBase -{ - friend struct SSaveVariableGetter; - Fvector _value; - -protected: - virtual void* GetValue() override { return &_value; } - -public: - CSaveVariableVec3(const Fvector& Value) : _value(Value) {} - - virtual ESaveVariableType GetVariableType() override { return ESaveVariableType::t_vec3; } - virtual void Write(CMemoryBuffer& Buffer) override; -}; - -class XRCORE_API CSaveVariableVec4 : - public CSaveVariableBase -{ - friend struct SSaveVariableGetter; - Fvector4 _value; - -protected: - virtual void* GetValue() override { return &_value; } - -public: - CSaveVariableVec4(const Fvector4& Value) : _value(Value) {} - - virtual ESaveVariableType GetVariableType() override { return ESaveVariableType::t_vec4; } - virtual void Write(CMemoryBuffer& Buffer) override; -};*/ - class XRCORE_API CSaveVariableU64 : public CSaveVariableBase { @@ -293,109 +294,6 @@ class XRCORE_API CSaveVariableS8 : virtual void Write(CMemoryBuffer& Buffer) override; }; -/*class CSaveVariableFloatQ16 : - public CSaveVariableBase -{ - friend struct SSaveVariableGetter; - u16 _value; - -protected: - virtual void* GetValue() override { - static float UnwrappedValue; // Hack: safe return pointer of local variable - A = (float(_value) * (max - min)) / 65535.f + min; // floating-point-error possible - VERIFY((A >= min - EPS_S) && (A <= max + EPS_S)); - return &UnwrappedValue; - } - -public: - CSaveVariableFloatQ16(float Value, float Min, float Max) : _value(Value) { - VERIFY(Value >= Min && Value <= Max); - float q = (Value - Min) / (Max - Min); - _value = u16(iFloor(q * 65535.f + 0.5f)); - } - - virtual ESaveVariableType GetVariableType() override { return ESaveVariableType::t_float_q16; } -}; - -class CSaveVariableFloatQ8 : - public CSaveVariableBase -{ - friend struct SSaveVariableGetter; - u8 _value; - -protected: - virtual void* GetValue() override { return &_value; } - -public: - CSaveVariableFloatQ8(float Value, float Min, float Max) : _value(Value) { - VERIFY(Value >= Min && Value <= Max); - float q = (Value - Min) / (Max - Min); - _value = u8(iFloor(q * 255.f + 0.5f)); - } - - virtual ESaveVariableType GetVariableType() override { return ESaveVariableType::t_float_q8; } -}; - -class CSaveVariableAngle16 : - public CSaveVariableBase -{ - friend struct SSaveVariableGetter; - float _value; - -protected: - virtual void* GetValue() override { return &_value; } - -public: - CSaveVariableAngle16(float Value) : _value(Value) {} - - virtual ESaveVariableType GetVariableType() override { return ESaveVariableType::t_angle16; } -}; - -class CSaveVariableAngle8 : - public CSaveVariableBase -{ - friend struct SSaveVariableGetter; - float _value; - -protected: - virtual void* GetValue() override { return &_value; } - -public: - CSaveVariableAngle8(float Value) : _value(Value) {} - - virtual ESaveVariableType GetVariableType() override { return ESaveVariableType::t_angle8; } -}; - -class CSaveVariableDir : - public CSaveVariableBase -{ - friend struct SSaveVariableGetter; - Fvector _value; - -protected: - virtual void* GetValue() override { return &_value; } - -public: - CSaveVariableDir(const Fvector& Value) : _value(Value) {} - - virtual ESaveVariableType GetVariableType() override { return ESaveVariableType::t_dir; } -}; - -class CSaveVariableSdir : - public CSaveVariableBase -{ - friend struct SSaveVariableGetter; - Fvector _value; - -protected: - virtual void* GetValue() override { return &_value; } - -public: - CSaveVariableSdir(const Fvector& Value) : _value(Value) {} - - virtual ESaveVariableType GetVariableType() override { return ESaveVariableType::t_sdir; } -};*/ - class XRCORE_API CSaveVariableString : public CSaveVariableBase { diff --git a/src/xrGame/Actor_Network.cpp b/src/xrGame/Actor_Network.cpp index 2f31aecae0..0b1ff3e9c0 100644 --- a/src/xrGame/Actor_Network.cpp +++ b/src/xrGame/Actor_Network.cpp @@ -1566,7 +1566,7 @@ void CActor::Serialize(ISaveObject& Object) Object.BeginChunk("CActor::Camera"); { cam_Active()->Serialize(Object); - u8* Value = (u8*)cam_active; + u8* Value = (u8*)&cam_active; Object << *Value; if (!Object.IsSave()) { cam_Set(EActorCameras(*Value)); diff --git a/src/xrGame/action_planner_inline.h b/src/xrGame/action_planner_inline.h index 070c54df51..b003858c8e 100644 --- a/src/xrGame/action_planner_inline.h +++ b/src/xrGame/action_planner_inline.h @@ -551,10 +551,12 @@ IC void CPlanner::Serialize(ISaveObject& Object) { auto I = this->m_evaluators.begin(); auto E = this->m_evaluators.end(); + Object.BeginArray(this->m_evaluators.size()); for (; I != E; ++I) { (*I).second->Serialize(Object); } + Object.EndArray(); //((CSaveObject&)Object).Serialize(this->m_evaluators, fastdelegate::MakeDelegate(this, &CPlanner::SerializeEval)); /*Object << this->m_evaluators; auto I = this->m_evaluators.begin(); @@ -573,10 +575,12 @@ IC void CPlanner::Serialize(ISaveObject& Object) { auto I = this->m_operators.begin(); auto E = this->m_operators.end(); + Object.BeginArray(this->m_operators.size()); for (; I != E; ++I) { (*I).m_operator->Serialize(Object); } + Object.EndArray(); //((CSaveObject&)Object).Serialize(this->m_operators, fastdelegate::MakeDelegate(this, &CPlanner::SerializeOper)); /*auto I = this->m_operators.begin(); auto E = this->m_operators.end(); @@ -594,10 +598,12 @@ IC void CPlanner::Serialize(ISaveObject& Object) { auto I = this->m_storage.m_storage.begin(); auto E = this->m_storage.m_storage.end(); + Object.BeginArray(this->m_storage.m_storage.size()); for (; I != E; ++I) { Object << I->m_condition << I->m_value; } + Object.EndArray(); //((CSaveObject&)Object).Serialize(this->m_storage.m_storage, fastdelegate::MakeDelegate(this, &CPlanner::SerializeStor)); /*u64 count; GraphEngineSpace::_solver_condition_type condition; diff --git a/src/xrGame/alife_object_registry.cpp b/src/xrGame/alife_object_registry.cpp index c3998591c8..cd85b52713 100644 --- a/src/xrGame/alife_object_registry.cpp +++ b/src/xrGame/alife_object_registry.cpp @@ -96,7 +96,7 @@ void CALifeObjectRegistry::save (IWriter &memory_stream, CSE_ALifeDynamicObje void CALifeObjectRegistry::Serialize(ISaveObject& Object, CSE_ALifeDynamicObject* object, u32& object_count) { - VERIFY(!Object.IsSave()); + VERIFY(Object.IsSave()); if (!Object.IsSave()) { return; } diff --git a/src/xrGame/alife_spawn_registry.cpp b/src/xrGame/alife_spawn_registry.cpp index 1c711b96f1..1fa2e4cae3 100644 --- a/src/xrGame/alife_spawn_registry.cpp +++ b/src/xrGame/alife_spawn_registry.cpp @@ -324,6 +324,7 @@ void CALifeSpawnRegistry::serialize_updates(ISaveObject& Object) Object << Value; SPAWN_GRAPH::vertex_iterator I = m_spawns.vertices().begin(); SPAWN_GRAPH::vertex_iterator E = m_spawns.vertices().end(); + Object.BeginArray(m_spawns.vertex_count()); for (; I != E; ++I) { Object.BeginChunk("CALifeSpawnRegistry::m_spawns::vertex"); { @@ -333,6 +334,7 @@ void CALifeSpawnRegistry::serialize_updates(ISaveObject& Object) } Object.EndChunk(); } + Object.EndArray(); } else { u64 Value; @@ -340,6 +342,7 @@ void CALifeSpawnRegistry::serialize_updates(ISaveObject& Object) u32 vertex_id; SPAWN_GRAPH::vertex_iterator I = m_spawns.vertices().begin(); SPAWN_GRAPH::vertex_iterator E = m_spawns.vertices().end(); + Object.BeginArray(m_spawns.vertex_count()); for (; I != E; ++I) { Object.BeginChunk("CALifeSpawnRegistry::m_spawns::vertex"); { @@ -351,6 +354,7 @@ void CALifeSpawnRegistry::serialize_updates(ISaveObject& Object) } Object.EndChunk(); } + Object.EndArray(); } } Object.EndChunk(); diff --git a/src/xrGame/alife_storage_manager.cpp b/src/xrGame/alife_storage_manager.cpp index 587d7e799d..cba35a4468 100644 --- a/src/xrGame/alife_storage_manager.cpp +++ b/src/xrGame/alife_storage_manager.cpp @@ -22,6 +22,8 @@ #include "../xrEngine/IGame_Persistent.h" #include "autosave_manager.h" #include "Save/SaveManager.h" +#include +#include "alife_simulator.h" XRCORE_API string_path g_bug_report_file; @@ -65,6 +67,12 @@ void CALifeStorageManager::save (LPCSTR save_name_no_check, bool update_name) CSaveObjectSave* SaveObj = CSaveManager::GetInstance().BeginSave(); { + CSaveManager::SGameInfoFast info; + info.m_actor_health = g_actor ? g_actor->GetfHealth() : 1.0f; + info.m_game_time = ai().alife().time_manager().game_time(); + auto map_name = Level().name(); + info.m_level_name = map_name.size() ? map_name : "Start"; + CSaveManager::GetInstance().WriteGameInfo(info); header().Serialize(*SaveObj); time_manager().Serialize(*SaveObj); spawns().Serialize(*SaveObj); @@ -145,6 +153,46 @@ void CALifeStorageManager::load (void *buffer, const u32 &buffer_size, LPCSTR fi Level().autosave_manager().on_game_loaded (); } +void CALifeStorageManager::load(IReader* stream, LPCSTR file_name) +{ + //IReader source(buffer, buffer_size); + + CSaveObjectLoad* SaveObj = CSaveManager::GetInstance().BeginLoad(stream); + { + CSaveManager::GetInstance().SkipGameInfo(stream); + header().Serialize(*SaveObj); + time_manager().Serialize(*SaveObj); + spawns().Serialize(*SaveObj); + graph().on_load(); + objects().Serialize(*SaveObj); + } + + VERIFY(can_register_objects()); + can_register_objects(false); + CALifeObjectRegistry::OBJECT_REGISTRY::iterator B = objects().objects().begin(); + CALifeObjectRegistry::OBJECT_REGISTRY::iterator E = objects().objects().end(); + CALifeObjectRegistry::OBJECT_REGISTRY::iterator I; + for (I = B; I != E; ++I) { + ALife::_OBJECT_ID id = (*I).second->ID; + (*I).second->ID = server().PerformIDgen(id); + VERIFY(id == (*I).second->ID); + register_object((*I).second, false); + } + + registry().Serialize(*SaveObj); + xr_delete(SaveObj); + + can_register_objects(true); + + for (I = B; I != E; ++I) + (*I).second->on_register(); + + if (!g_pGameLevel) + return; + + Level().autosave_manager().on_game_loaded(); +} + bool CALifeStorageManager::load (LPCSTR save_name_no_check) { LPCSTR game_saves_path = FS.get_path("$game_saves$")->m_Path; @@ -191,12 +239,15 @@ bool CALifeStorageManager::load (LPCSTR save_name_no_check) unload (); reload (m_section); - u32 source_count = stream->r_u32(); + load(stream, file_name); + FS.r_close(stream); + + /*u32 source_count = stream->r_u32(); void *source_data = xr_malloc(source_count); rtc_decompress (source_data,source_count,stream->pointer(),stream->length() - 3*sizeof(u32)); FS.r_close (stream); load (source_data, source_count, file_name); - xr_free (source_data); + xr_free (source_data);*/ groups().on_after_game_load (); diff --git a/src/xrGame/alife_storage_manager.h b/src/xrGame/alife_storage_manager.h index d1803b0642..0d0f2ac793 100644 --- a/src/xrGame/alife_storage_manager.h +++ b/src/xrGame/alife_storage_manager.h @@ -24,6 +24,7 @@ class CALifeStorageManager : public virtual CALifeSimulatorBase { private: void prepare_objects_for_save(); void load (void *buffer, const u32 &buffer_size, LPCSTR file_name); + void load(IReader* stream, LPCSTR file_name); public: IC CALifeStorageManager (xrServer *server, LPCSTR section); diff --git a/src/xrGame/saved_game_wrapper.cpp b/src/xrGame/saved_game_wrapper.cpp index e706428a9a..0cf97f01d3 100644 --- a/src/xrGame/saved_game_wrapper.cpp +++ b/src/xrGame/saved_game_wrapper.cpp @@ -17,6 +17,7 @@ #include "alife_simulator.h" #include "alife_spawn_registry.h" #include "../xrEngine/string_table.h" +#include extern LPCSTR alife_section; @@ -36,14 +37,23 @@ bool CSavedGameWrapper::saved_game_exist (LPCSTR saved_game_name) bool CSavedGameWrapper::valid_saved_game (IReader &stream) { - if (stream.length() < 8) + if (stream.length() < 8) { return (false); + } - if (stream.r_u32() != u32(-1)) - return (false); + ESaveVariableType type; + stream.r(&type, sizeof(ESaveVariableType)); + if (type != ESaveVariableType::t_chunk) { + return false; + } - if (stream.r_u32() < ALIFE_VERSION) - return (false); + //if (stream.r_u32() != u32(-1)) + // return (false); + + //if (stream.r_u32() < ALIFE_VERSION) + // return (false); + + stream.seek(0); return (true); } @@ -77,7 +87,23 @@ CSavedGameWrapper::CSavedGameWrapper (LPCSTR saved_game_name) return; } - u32 source_count = stream->r_u32(); + CSaveManager::SGameInfoFast data; + if (!CSaveManager::GetInstance().GetGameInfoFast(stream, data)) { + FS.r_close(stream); + CALifeTimeManager time_manager(alife_section); + m_game_time = time_manager.game_time(); + m_actor_health = 1.f; + m_level_id = _LEVEL_ID(-1); + m_level_name = ""; + return; + } + m_game_time = data.m_game_time; + m_actor_health = data.m_actor_health; + m_level_id = _LEVEL_ID(-1); + m_level_name = data.m_level_name; + return; + + /*u32 source_count = stream->r_u32(); void *source_data = xr_malloc(source_count); rtc_decompress (source_data,source_count,stream->pointer(),stream->length() - 3*sizeof(u32)); FS.r_close (stream); @@ -168,5 +194,5 @@ CSavedGameWrapper::CSavedGameWrapper (LPCSTR saved_game_name) F_entity_Destroy (object); } - xr_free (source_data); + xr_free (source_data);*/ } diff --git a/src/xrScripts/exports/SaveSystem_script.cpp b/src/xrScripts/exports/SaveSystem_script.cpp index 03854172aa..d71eaf7274 100644 --- a/src/xrScripts/exports/SaveSystem_script.cpp +++ b/src/xrScripts/exports/SaveSystem_script.cpp @@ -244,8 +244,10 @@ void SaveSystemScript::script_register(lua_State* L) class_("SaveObject"), class_("SaveObjectSave") //.def("GetCurrentChunk", &CSaveObject::GetCurrentChunk) - .def("BeginChunk", &CSaveObject::BeginChunk) + .def("BeginChunk", &CSaveObjectSave::BeginChunk) .def("EndChunk", &CSaveObject::EndChunk) + .def("BeginArray", &CSaveObjectSave::BeginArray) + .def("EndArray", &CSaveObject::EndArray) .def("s_vec3", &CSaveObject_script::w_vec3) .def("s_float", &CSaveObject_script::w_float) .def("s_u64", &CSaveObject_script::w_u64) @@ -263,6 +265,8 @@ void SaveSystemScript::script_register(lua_State* L) //.def("GetCurrentChunk", &CSaveObject::GetCurrentChunk) .def("BeginChunk", &CSaveObjectLoad::BeginChunk) .def("EndChunk", &CSaveObject::EndChunk) + .def("BeginArray", &CSaveObjectLoad::BeginArray) + .def("EndArray", &CSaveObject::EndArray) .def("s_vec3", &CSaveObject_script::r_vec3, pure_out_value<2>()) .def("s_float", &CSaveObject_script::r_float, pure_out_value<2>()) .def("s_u64", &CSaveObject_script::r_u64, pure_out_value<2>())