Skip to content

Commit 575d1de

Browse files
Merge pull request #385 from BF3RM/feat/saveOrder
Feat/save order
2 parents d8cd41f + 564fa4a commit 575d1de

File tree

6 files changed

+129
-27
lines changed

6 files changed

+129
-27
lines changed

ext/Server/ProjectManager.lua

Lines changed: 45 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ ProjectManager = class 'ProjectManager'
33

44
local m_Logger = Logger("ProjectManager", false)
55

6-
local SAVE_VERSION = "0.1.1"
6+
local SAVE_VERSION = "0.1.2"
77

88
function ProjectManager:__init()
99
m_Logger:Write("Initializing ProjectManager")
@@ -19,6 +19,7 @@ function ProjectManager:RegisterVars()
1919
self.m_MapName = nil
2020
self.m_GameMode = nil
2121
self.m_LoadedBundles = {}
22+
self.m_GUID_To_Timestamps = {}
2223
end
2324

2425
function ProjectManager:RegisterEvents()
@@ -92,10 +93,11 @@ end
9293
---@param p_ProjectSave ProjectSave
9394
---@return ProjectSave|nil projectSave, string|nil errorMessage
9495
function ProjectManager:UpgradeSaveStructure(p_ProjectSave)
95-
local s_SaveVersion = p_ProjectSave[DataBaseManager.m_ExportDataName].saveVersion
96+
local s_SaveVersion = p_ProjectSave[DataBaseManager.m_ExportHeaderName].saveVersion
9697

9798
if s_SaveVersion == nil then -- Save from before versioning was implemented, try to upgrade to current version
9899
local s_Data = p_ProjectSave[DataBaseManager.m_ExportDataName]
100+
self:InsertTimestampsIntoObjects(s_Data)
99101

100102
-- Some pre-versioning save files had an isVanilla flag
101103
for _, l_DataEntry in pairs(s_Data) do
@@ -110,12 +112,25 @@ function ProjectManager:UpgradeSaveStructure(p_ProjectSave)
110112
return p_ProjectSave
111113
elseif s_SaveVersion > SAVE_VERSION then
112114
return nil, 'Importing save with a higher save format version than supported, please update MapEditor before importing'
113-
else
115+
elseif s_SaveVersion < SAVE_VERSION then
114116
-- New version updates are handled here
117+
local s_Data = p_ProjectSave[DataBaseManager.m_ExportDataName]
118+
self:InsertTimestampsIntoObjects(s_Data)
115119

116120
-- Update save version
117121
p_ProjectSave[DataBaseManager.m_ExportHeaderName].saveVersion = SAVE_VERSION
118122
return p_ProjectSave
123+
elseif s_SaveVersion == SAVE_VERSION then
124+
return p_ProjectSave
125+
end
126+
end
127+
128+
---@param p_Data table
129+
function ProjectManager:InsertTimestampsIntoObjects(p_Data)
130+
for l_Index, l_DataEntry in ipairs(p_Data) do
131+
if not l_DataEntry.timeStamp then
132+
l_DataEntry.timeStamp = 1000000000000 + l_Index
133+
end
119134
end
120135
end
121136

@@ -233,11 +248,21 @@ function ProjectManager:OnUpdatePass(p_Delta, p_Pass)
233248
local s_Msg
234249
s_ProjectSave, s_Msg = self:UpgradeSaveStructure(s_ProjectSave)
235250

251+
236252
if s_ProjectSave == nil then
237253
m_Logger:Error("Can't load project. Error: " .. tostring(s_Msg))
238254
return
239255
end
240256

257+
if s_ProjectSave.data ~= nil then
258+
for l_Index, l_Value in ipairs(s_ProjectSave.data) do
259+
-- store the timestamps to reference later
260+
self.m_GUID_To_Timestamps[l_Value.guid] = l_Value.timeStamp
261+
-- print(l_Value.timeStamp)
262+
end
263+
NetEvents:BroadcastLocal("Shared:StoreTimeStamps", self.m_GUID_To_Timestamps)
264+
end
265+
241266
self:CreateAndExecuteImitationCommands(s_ProjectSave[DataBaseManager.m_ExportDataName])
242267
end
243268
end
@@ -265,7 +290,6 @@ function ProjectManager:OnRequestProjectLoad(p_Player, p_ProjectId)
265290
Maps[s_MapName] == nil or
266291
s_GameModeName == nil or
267292
GameModes[s_GameModeName] == nil then
268-
269293
m_Logger:Error("Failed to load project, one or more fields of the project header are not set: " .. s_MapName .. " | " .. s_GameModeName)
270294
return
271295
end
@@ -311,11 +335,22 @@ function ProjectManager:SaveProjectCoroutine(p_ProjectHeader)
311335
-- TODO: get the GameObjectSaveDatas not from the transferdatas array, but from the GO array of the GOManager. (remove the GOTD array)
312336
for _, l_GameObject in pairs(GameObjectManager.m_GameObjects) do
313337
if l_GameObject:IsUserModified() == true or l_GameObject:HasOverrides() then
338+
-- check the old values that are stored
339+
local s_Guid = tostring(l_GameObject.guid)
340+
if self.m_GUID_To_Timestamps[s_Guid] ~= nil then
341+
l_GameObject.timeStamp = self.m_GUID_To_Timestamps[s_Guid]
342+
else
343+
self.m_GUID_To_Timestamps[s_Guid] = l_GameObject.timeStamp
344+
end
314345
s_Count = s_Count + 1
315346
table.insert(s_GameObjectSaveDatas, GameObjectSaveData(l_GameObject):GetAsTable())
316347
end
317348
end
318349

350+
table.sort(s_GameObjectSaveDatas, function(a, b)
351+
return a.timeStamp < b.timeStamp
352+
end)
353+
319354
-- m_Logger:Write("vvvvvvvvvvvvvvvvv")
320355
-- m_Logger:Write("GameObjectSaveDatas: " .. count)
321356
-- for _, gameObjectSaveData in pairs(s_GameObjectSaveDatas) do
@@ -352,6 +387,7 @@ function ProjectManager:CreateAndExecuteImitationCommands(p_ProjectSaveData)
352387
--end
353388

354389
local s_Command
390+
local s_TimeStamp = self.m_GUID_To_Timestamps[s_Guid]
355391

356392
-- Vanilla and nohavok objects are handled in levelloader
357393
if l_GameObjectSaveData.origin == GameObjectOriginType.Vanilla or
@@ -362,7 +398,8 @@ function ProjectManager:CreateAndExecuteImitationCommands(p_ProjectSaveData)
362398
sender = "LoadingSaveFile",
363399
type = CommandActionType.DeleteGameObjectCommand,
364400
gameObjectTransferData = {
365-
guid = s_Guid
401+
guid = s_Guid,
402+
timeStamp = s_TimeStamp
366403
}
367404
}
368405
else
@@ -372,7 +409,8 @@ function ProjectManager:CreateAndExecuteImitationCommands(p_ProjectSaveData)
372409
type = CommandActionType.SetTransformCommand,
373410
gameObjectTransferData = {
374411
guid = s_Guid,
375-
transform = l_GameObjectSaveData.transform
412+
transform = l_GameObjectSaveData.transform,
413+
timeStamp = s_TimeStamp
376414
}
377415
}
378416
end
@@ -390,6 +428,7 @@ function ProjectManager:CreateAndExecuteImitationCommands(p_ProjectSaveData)
390428
name = l_GameObjectSaveData.name,
391429
blueprintCtrRef = l_GameObjectSaveData.blueprintCtrRef,
392430
parentData = l_GameObjectSaveData.parentData or GameObjectParentData:GetRootParentData(),
431+
timeStamp = s_TimeStamp,
393432
transform = l_GameObjectSaveData.transform,
394433
variation = l_GameObjectSaveData.variation or 0,
395434
gameEntities = {},

ext/Shared/Modules/CommandActions.lua

Lines changed: 13 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -45,14 +45,15 @@ function CommandActions:SpawnGameObject(p_Command, p_UpdatePass)
4545
end
4646

4747
local s_SpawnResult = GameObjectManager:InvokeBlueprintSpawn(s_GameObjectTransferData.guid:upper(),
48-
p_Command.sender,
49-
s_GameObjectTransferData.blueprintCtrRef.partitionGuid,
50-
s_GameObjectTransferData.blueprintCtrRef.instanceGuid,
51-
s_GameObjectTransferData.parentData,
52-
s_GameObjectTransferData.transform,
53-
s_GameObjectTransferData.variation,
54-
false,
55-
s_GameObjectTransferData.overrides
48+
p_Command.sender,
49+
s_GameObjectTransferData.blueprintCtrRef.partitionGuid,
50+
s_GameObjectTransferData.blueprintCtrRef.instanceGuid,
51+
s_GameObjectTransferData.parentData,
52+
s_GameObjectTransferData.transform,
53+
s_GameObjectTransferData.variation,
54+
false,
55+
s_GameObjectTransferData.overrides,
56+
s_GameObjectTransferData.timeStamp
5657
)
5758

5859
if s_SpawnResult == false then
@@ -94,6 +95,7 @@ function CommandActions:DeleteGameObject(p_Command, p_UpdatePass)
9495

9596
local s_GameObjectTransferData = {
9697
guid = p_Command.gameObjectTransferData.guid,
98+
timeStamp = p_Command.gameObjectTransferData.timeStamp,
9799
isDeleted = true
98100
}
99101

@@ -119,7 +121,7 @@ function CommandActions:UndeleteGameObject(p_Command, p_UpdatePass)
119121
m_Logger:Write("UndeleteGameObject with guid " .. p_Command.gameObjectTransferData.guid)
120122

121123
if SanitizeEnum(p_Command.gameObjectTransferData.origin) == GameObjectOriginType.Custom or
122-
SanitizeEnum(p_Command.gameObjectTransferData.origin) == GameObjectOriginType.CustomChild then
124+
SanitizeEnum(p_Command.gameObjectTransferData.origin) == GameObjectOriginType.CustomChild then
123125
return CommandActions:SpawnGameObject(p_Command, p_UpdatePass)
124126
end
125127

@@ -234,7 +236,8 @@ function CommandActions:SetTransform(p_Command, p_UpdatePass)
234236

235237
local s_GameObjectTransferData = {
236238
guid = p_Command.gameObjectTransferData.guid,
237-
transform = p_Command.gameObjectTransferData.transform
239+
transform = p_Command.gameObjectTransferData.transform,
240+
timeStamp = p_Command.gameObjectTransferData.timeStamp
238241
}
239242

240243
local s_CommandActionResult = {
@@ -246,7 +249,6 @@ function CommandActions:SetTransform(p_Command, p_UpdatePass)
246249
return s_CommandActionResult, CARResponseType.Success
247250
end
248251

249-
250252
function CommandActions:SetVariation(p_Command, p_UpdatePass)
251253
if p_Command.gameObjectTransferData == nil then
252254
m_Logger:Error("The SetTransform needs to have a valid gameObjectTransferData set.")

ext/Shared/Modules/GameObjectManager.lua

Lines changed: 62 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,16 @@ function GameObjectManager:__init(p_Realm)
1010
m_Logger:Write("Initializing GameObjectManager: " .. tostring(p_Realm))
1111
self.m_Realm = p_Realm
1212
self:RegisterVars()
13+
self:RegisterEvents()
14+
end
15+
16+
function GameObjectManager:RegisterEvents()
17+
Events:Subscribe("Shared:StoreTimeStamps", self, self.StoreTimeStamps)
18+
end
19+
20+
---@param p_GUID_To_Timestamps table
21+
function GameObjectManager:StoreTimeStamps(p_GUID_To_Timestamps)
22+
self.m_GUID_To_Timestamps = p_GUID_To_Timestamps
1323
end
1424

1525
function GameObjectManager:RegisterVars()
@@ -24,6 +34,9 @@ function GameObjectManager:RegisterVars()
2434

2535
--- key: child (ReferenceObjectData) guid, value: parent GameObject guid
2636
self.m_ReferenceObjectDatas = {}
37+
38+
-- workaround for origin type 3
39+
self.m_GUID_To_Timestamps = {}
2740
end
2841

2942
function GameObjectManager:OnLevelDestroy()
@@ -44,7 +57,8 @@ end
4457
---@param p_Variation integer
4558
---@param p_IsPreviewSpawn boolean
4659
---@param p_Overrides table
47-
function GameObjectManager:InvokeBlueprintSpawn(p_GameObjectGuid, p_SenderName, p_BlueprintPartitionGuid, p_BlueprintInstanceGuid, p_ParentData, p_LinearTransform, p_Variation, p_IsPreviewSpawn, p_Overrides)
60+
---@param p_TimeStamp number
61+
function GameObjectManager:InvokeBlueprintSpawn(p_GameObjectGuid, p_SenderName, p_BlueprintPartitionGuid, p_BlueprintInstanceGuid, p_ParentData, p_LinearTransform, p_Variation, p_IsPreviewSpawn, p_Overrides, p_TimeStamp)
4862
if p_BlueprintPartitionGuid == nil or
4963
p_BlueprintInstanceGuid == nil or
5064
p_LinearTransform == nil then
@@ -66,15 +80,15 @@ function GameObjectManager:InvokeBlueprintSpawn(p_GameObjectGuid, p_SenderName,
6680

6781
-- m_Logger:Write('Invoking spawning of blueprint: '.. s_ObjectBlueprint.name .. " | ".. s_Blueprint.typeInfo.name .. ", ID: " .. p_GameObjectGuid .. ", Instance: " .. tostring(p_BlueprintInstanceGuid) .. ", Variation: " .. p_Variation)
6882
if p_IsPreviewSpawn == false then
69-
self.m_PendingCustomBlueprintGuids[p_BlueprintInstanceGuid] = { customGuid = p_GameObjectGuid, creatorName = p_SenderName, parentData = p_ParentData, overrides = p_Overrides }
83+
self.m_PendingCustomBlueprintGuids[p_BlueprintInstanceGuid] = { customGuid = p_GameObjectGuid, creatorName = p_SenderName, parentData = p_ParentData, overrides = p_Overrides, timeStamp = p_TimeStamp }
7084
else
7185
local s_PreviewSpawnParentData = GameObjectParentData {
7286
guid = EMPTY_GUID, -- Root
7387
typeName = "previewSpawn",
7488
}
7589
m_Logger:Write("Added s_PreviewSpawnParentData: " .. tostring(s_PreviewSpawnParentData.guid))
7690
m_Logger:WriteTable(s_PreviewSpawnParentData)
77-
self.m_PendingCustomBlueprintGuids[p_BlueprintInstanceGuid] = { customGuid = p_GameObjectGuid, creatorName = p_SenderName, parentData = s_PreviewSpawnParentData, overrides = p_Overrides }
91+
self.m_PendingCustomBlueprintGuids[p_BlueprintInstanceGuid] = { customGuid = p_GameObjectGuid, creatorName = p_SenderName, parentData = s_PreviewSpawnParentData, overrides = p_Overrides, timeStamp = p_TimeStamp }
7892
end
7993

8094
local s_Params = EntityCreationParams()
@@ -153,6 +167,14 @@ function GameObjectManager:OnEntityCreateFromBlueprint(p_HookCtx, p_Blueprint, p
153167
end
154168
end
155169

170+
local s_TimeStamp
171+
if s_PendingCustomBlueprintInfo then
172+
s_TimeStamp = s_PendingCustomBlueprintInfo.timeStamp
173+
end
174+
if not s_TimeStamp then
175+
s_TimeStamp = SharedUtils:GetTimeMS()
176+
end
177+
156178
---@type GameObject
157179
local s_GameObject = GameObject {
158180
guid = GenerateTempGuid(), -- we set a tempGuid, it will later be set to a vanilla or custom guid
@@ -161,6 +183,7 @@ function GameObjectManager:OnEntityCreateFromBlueprint(p_HookCtx, p_Blueprint, p
161183
transform = p_Transform,
162184
variation = s_Variation,
163185
origin = GameObjectOriginType.Vanilla,
186+
timeStamp = s_TimeStamp,
164187
isDeleted = false,
165188
isEnabled = true,
166189
gameEntities = {},
@@ -318,9 +341,9 @@ function GameObjectManager:OnEntityCreateFromBlueprint(p_HookCtx, p_Blueprint, p
318341
-- TODO: update blueprint data with the correct realm if its client or server only
319342
if self.m_Realm == Realm.Realm_Server then
320343
m_Logger:Write(s_UnresolvedRODCount .. ' client-only gameobjects weren\'t resolved')
321-
-- for l_Guid, l_Value in pairs(self.m_ReferenceObjectDatas) do
322-
-- m_Logger:Write(tostring(l_Guid) .. ', '..l_Value.typeName)
323-
-- end
344+
-- for l_Guid, l_Value in pairs(self.m_ReferenceObjectDatas) do
345+
-- m_Logger:Write(tostring(l_Guid) .. ', '..l_Value.typeName)
346+
-- end
324347
elseif self.m_Realm == Realm.Realm_Client then
325348
m_Logger:Write(s_UnresolvedRODCount .. ' server-only gameobjects weren\'t resolved')
326349
-- for l_Guid, l_Value in pairs(self.m_ReferenceObjectDatas) do
@@ -360,7 +383,7 @@ end
360383
function GameObjectManager:ResolveRootObject(p_GameObject, p_PendingInfo)
361384
self.m_GameObjects[tostring(p_GameObject.guid)] = nil -- Remove temp guid from array
362385

363-
if p_PendingInfo then -- We spawned this custom entitybus
386+
if p_PendingInfo then -- We spawned this custom entitybus
364387
p_GameObject.parentData = GameObjectParentData {
365388
guid = p_PendingInfo.parentData.guid,
366389
typeName = p_PendingInfo.parentData.typeName,
@@ -369,26 +392,46 @@ function GameObjectManager:ResolveRootObject(p_GameObject, p_PendingInfo)
369392
}
370393
p_GameObject.guid = Guid(p_PendingInfo.customGuid)
371394
p_GameObject.origin = GameObjectOriginType.Custom
395+
-- if not p_GameObject.timeStamp or p_GameObject.timeStamp == 0 then
396+
-- self:InsertTimestamp(p_GameObject)
397+
-- end
372398
else
373-
374399
if string.find(p_GameObject.blueprintCtrRef.name:lower(), "nohavok") then
375400
local s_BundleName = p_GameObject.blueprintCtrRef.name:gsub('NoHavok_', '')
376401
p_GameObject.origin = GameObjectOriginType.NoHavok
377402
-- No parent data, add the bundle name as an offset and use a predefined havok guid
378403
p_GameObject.guid = self:GetNoHavokGuid(HAVOK_GUID, s_BundleName .. '/' .. p_GameObject.name, p_GameObject.transform.trans)
404+
-- if not p_GameObject.timeStamp or p_GameObject.timeStamp == 0 then
405+
-- self:InsertTimestamp(p_GameObject)
406+
-- end
379407
else
380408
-- This is a vanilla root object
381409
p_GameObject.guid = self:GetVanillaGuid(p_GameObject.name, p_GameObject.transform.trans)
382410
p_GameObject.origin = GameObjectOriginType.Vanilla
411+
-- if not p_GameObject.timeStamp or p_GameObject.timeStamp == 0 then
412+
-- self:InsertTimestamp(p_GameObject)
413+
-- end
383414

384415
--table.insert(self.m_VanillaGameObjectGuids, p_GameObject.guid)
385416
self.m_VanillaGameObjectGuids[tostring(p_GameObject.guid)] = p_GameObject.guid
386417
end
387418
end
419+
-- if p_GameObject.timeStamp == 0 then
420+
-- self:InsertTimestamp(p_GameObject)
421+
-- end
388422

389423
self.m_GameObjects[tostring(p_GameObject.guid)] = p_GameObject
390424
end
391425

426+
---@param p_GameObject GameObject
427+
function GameObjectManager:InsertTimestamp(p_GameObject)
428+
local s_ObjectTimeStamp = self.m_GUID_To_Timestamps[tostring(p_GameObject.guid)]
429+
if not s_ObjectTimeStamp then
430+
s_ObjectTimeStamp = SharedUtils:GetTimeMS()
431+
end
432+
p_GameObject.timeStamp = s_ObjectTimeStamp
433+
end
434+
392435
function GameObjectManager:ResolveChildObject(p_GameObject, p_ParentGameObject)
393436
-- This is a child of either a custom gameObject or a vanilla gameObject, find the parent!
394437
p_GameObject.parentData = GameObjectParentData {
@@ -408,9 +451,16 @@ function GameObjectManager:ResolveChildObject(p_GameObject, p_ParentGameObject)
408451
if p_GameObject.origin == GameObjectOriginType.Vanilla then
409452
p_GameObject.guid = self:GetVanillaGuid(p_GameObject.name, p_GameObject.transform.trans)
410453
--table.insert(self.m_VanillaGameObjectGuids, p_GameObject.guid)
454+
-- if not p_GameObject.timeStamp or p_GameObject.timeStamp == 0 then
455+
-- self:InsertTimestamp(p_GameObject)
456+
-- end
411457
self.m_VanillaGameObjectGuids[tostring(p_GameObject.guid)] = p_GameObject.guid
412458
elseif p_GameObject.origin == GameObjectOriginType.NoHavok then
413459
p_GameObject.guid = self:GetNoHavokGuid(p_GameObject.parentData.guid, p_GameObject.name, p_GameObject.transform.trans)
460+
461+
-- if not p_GameObject.timeStamp or p_GameObject.timeStamp == 0 then
462+
-- self:InsertTimestamp(p_GameObject)
463+
-- end
414464
else
415465
local i = 1
416466
local s_CustomGuid
@@ -422,6 +472,10 @@ function GameObjectManager:ResolveChildObject(p_GameObject, p_ParentGameObject)
422472

423473
p_GameObject.guid = s_CustomGuid
424474
p_GameObject.origin = GameObjectOriginType.CustomChild
475+
476+
-- if not p_GameObject.timeStamp or p_GameObject.timeStamp == 0 then
477+
-- self:InsertTimestamp(p_GameObject)
478+
-- end
425479
end
426480

427481
self.m_GameObjects[tostring(p_GameObject.guid)] = p_GameObject

ext/Shared/Types/Definitions.lua

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
---@field guid string
1313
---@field blueprintCtrRef table|nil
1414
---@field transform LinearTransform
15+
---@field timeStamp number
1516
---@field origin GameObjectOriginType
1617
---@field original table|nil
1718
---@field localTransform LinearTransform

0 commit comments

Comments
 (0)