File size: 17,225 Bytes
b6a38d7
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
if Libs.Network ~= "async" then return end
local IsValid = IsValid

MapVar("NetObjects", {}, weak_keys_meta)

function NetTempObject(o)
	NetObjects[o or false] = false
end

function NetObject(o)
	NetObjects[o or false] = true
end

local function IsNetObj(o)
	local net_obj = NetObjects[o]
	return net_obj ~= false
			and IsValid(o) and (net_obj or not IsValid(o:GetParent()))
			and o.__ancestors.Object and o.handle and true
end

local mp_print = Platform.developer and print or function() end

local IsNetObj = IsNetObj

-- InteractionRand

MapVar("InteractionTypeSeeds", {}) -- keeps hash values for the interaction type names

MapGameTimeRepeat("InteractionRandScheduleReset", nil, function()
	WaitServerTick(10000)
	ResetInteractionRand(ServerTime() / 10000)
end)

function ValidateInteractionSeeds()
	for int_type, obj_to_target in pairs(InteractionSeeds) do
		for obj, target_to_seed in pairs(obj_to_target) do
			if obj and not IsValid(obj) then
				obj_to_target[obj] = nil
			else
				for target in pairs(target_to_seed) do
					if target and not IsValid(target) then
						target_to_seed[target] = nil
					end
				end
				if next(target_to_seed) == nil then
					obj_to_target[obj] = nil
				end
			end
		end
		if next(obj_to_target) == nil then
			InteractionSeeds[int_type] = nil
		end
	end
end

function ClearInteraction(int_type, obj)
	while obj and obj.NetOwner do
		obj = obj.NetOwner
	end
	if not IsValid(obj) or not IsNetObj(obj) then
		return false
	end
	local obj_to_target = InteractionSeeds[int_type or "none"]
	if obj_to_target then
		obj_to_target[obj] = nil
	end
	return true
end

function InteractionRand(max, int_type, obj, target)
	int_type = int_type or "none"
	assert(type(int_type) == "string")
	-- avoid having objects with random handle, they cannot be unserialized remotely
	while obj and obj.NetOwner do
		obj = obj.NetOwner
	end
	obj = IsNetObj(obj) and obj or false
	target = target or false
	local obj_to_target = InteractionSeeds[int_type]
	if not obj_to_target then
		obj_to_target = setmetatable({}, weak_keys_meta)
		InteractionSeeds[int_type] = obj_to_target
	end
	local target_to_seed = obj_to_target[obj]
	if not target_to_seed then
		target_to_seed = setmetatable({}, weak_keys_meta)
		obj_to_target[obj] = target_to_seed
	end
	while target and target.NetOwner do
		target = target.NetOwner
	end
	local interaction_seed = target_to_seed[target]
	if not interaction_seed then
		local type_seed = InteractionTypeSeeds[int_type]
		if not type_seed then
			type_seed = xxhash(int_type)
			InteractionTypeSeeds[int_type] = type_seed
		end
		interaction_seed = bxor(InteractionSeed, type_seed, obj and obj.handle or 0, target and target.handle or 0)
	end
	local rand
	rand, interaction_seed = BraidRandom(interaction_seed, max)
	target_to_seed[target] = interaction_seed
	
	NetUpdateHash("InteractionRand", rand, max, int_type, obj, target)
	return rand, interaction_seed
end


-------------------------------------------------[ WAIT HANDLE RESOLVE ]------------------------------------------------------------------- 

if FirstLoad then
	HandleResolveMsg = {}
end

function OnHandleAssigned(handle)
	if HandleResolveMsg[handle] then
		Msg(HandleResolveMsg[handle])
		HandleResolveMsg[handle] = nil
	end
end

function WaitResolveHandle(handle)
	local obj = HandleToObject[handle]
	if not obj then
		HandleResolveMsg[handle] = HandleResolveMsg[handle] or {}
		WaitMsg(HandleResolveMsg[handle], 1000)
		HandleResolveMsg[handle] = nil
		obj = HandleToObject[handle]
	end
	return obj
end

-------------------------------------------------[ Net map utilities ]------------------------------------------------------

local function QueryNetObjects(only_non_permanent)
	return MapGet(true, "Object", function(o)
											return IsNetObj(o) and (not only_non_permanent or o:GetGameFlags(const.gofPermanent) == 0)
										end)
end

local function SerializeInteractionSeeds()
	return next(InteractionSeeds) ~= nil and NetSerialize(InteractionSeeds) or nil
end

local function UnserializeInteractionSeeds(interact)
	local seeds = interact and NetUnserialize(interact) or {}
	for int_type, obj_to_target in pairs(seeds) do
		setmetatable(obj_to_target, weak_keys_meta)
		for obj, target_to_seed in pairs(obj_to_target) do
			setmetatable(target_to_seed, weak_keys_meta)
		end
	end
	return seeds
end

if Platform.developer then
	SerializeInteractionSeeds = function()
		local map1 = {}
		for int_type, obj_to_target in pairs(InteractionSeeds) do
			local map2 = setmetatable({}, weak_keys_meta)
			for obj, target_to_seed in pairs(obj_to_target) do
				local map3 = setmetatable({}, weak_keys_meta)
				for target, seed in pairs(target_to_seed) do
					if not target then
						map3[false] = seed
					elseif target.handle then
						map3[target.handle] = seed
					else
						assert(false, "Interaction target without handle: " .. tostring(target.class))
					end
				end
				if not obj then
					map2[false] = map3
				elseif obj.handle then
					map2[obj.handle] = map3
				else
					assert(false, "Interaction object without handle: " .. tostring(obj.class))
				end
			end
			map1[int_type] = map2
		end
		return next(map1) ~= nil and NetSerialize(map1) or nil
	end
	UnserializeInteractionSeeds = function(interact)
		local seeds = interact and NetUnserialize(interact) or {}
		local map1 = {}
		local count = 0
		for int_type, obj_handle_to_target in pairs(seeds) do
			local map2 = {}
			for obj_handle, target_handle_to_seed in pairs(obj_handle_to_target) do
				local map3 = {}
				for target_handle, seed in pairs(target_handle_to_seed) do
					if type(target_handle) == "number" then
						local target = HandleToObject[target_handle]
						if target then
							map3[target] = seed
						else
							assert(false, string.format("Missing interaction target %d from '%s'", target_handle, int_type))
						end
					else
						map3[target_handle] = seed
					end
					count = count + 1
				end
				if type(obj_handle) == "number" then
					local obj = HandleToObject[obj_handle]
					if obj then
						map2[obj] = map3
					else
						printf("Missing interaction object %d from '%s'", obj_handle, int_type)
						assert(false, "Missing interaction object!")
					end
				else
					map2[obj_handle] = map3
				end
			end
			map1[int_type] = map2
		end
		return map1
	end
end

function NetGetRandData()
	ValidateInteractionSeeds()
	local rand_data = {
		map = MapLoadRandom,
		interact = SerializeInteractionSeeds(),
		seed = InteractionSeed,
	}
	return rand_data
end

function NetSetRandData(rand_data)
	MapLoadRandom = rand_data.map
	InteractionSeeds = UnserializeInteractionSeeds(rand_data.interact)
	InteractionSeed = rand_data.seed
end

function NetGetGameState()
	local success, err, state, state_local = procall(function()
		local non_permanent_objs = QueryNetObjects(true)
		local net_objs = QueryNetObjects()
		local scenario_data, scenario_data_local = NetGetScenarioData()

		local game_data = {}
		game_data.map_name = GetMapName()
		game_data.map_hash = mapdata.NetHash
		game_data.paused = not not PauseReasons.UserPaused
		game_data.rand = NetGetRandData()
		game_data.objects = NetGetNetObjs(non_permanent_objs)
		game_data.props = NetGetNetObjProps(non_permanent_objs)
		game_data.dynamic = NetGetDynamicData(net_objs)
		game_data.scenario = scenario_data
		game_data.globals = {}
		Msg("NetStateGet", game_data.globals)
		local state, err = NetSerialize(game_data)
		if not state then
			assert(false, "Network serialization error: " .. tostring(err))
			return err
		end
		FindSerializeError(state, game_data)

		local game_data_local = {}
		game_data_local.scenario = scenario_data_local
		game_data_local.objects = NetGetLocalObjData()
		game_data_local.MapLoadRandom = MapLoadRandom
		local state_local, err = NetSerialize(game_data_local)
		if not state_local then
			assert(false, "Network serialization error: " .. tostring(err))
			return err
		end
		FindSerializeError(state_local, game_data_local)

		local compressed_state = Compress(state)
		local compressed_state_local = Compress(state_local)
		return false, compressed_state, compressed_state_local
	end)
	if not success then
		return err
	end
	return err, state, state_local
end

function NetSetGameState(compressed_data, compressed_data_local)
	if not compressed_data then return end
	local game_data = Decompress(compressed_data)
	if not game_data then return "decompress" end
	game_data = NetUnserialize(game_data)
	if not game_data then return "unserialize" end
	local game_data_local, err_local
	if compressed_data_local then
		local data = Decompress(compressed_data_local)
		if not data then
			err_local = "decompress"
		else
			data = NetUnserialize(data)
			if not data then
				err_local = "unserialize"
			elseif data.MapLoadRandom == game_data.rand.map then
				game_data_local = data
			else
				err_local = "version"
			end
		end
	end

	assert(game_data.map_name == GetMapName())
	
	if game_data.map_hash ~= mapdata.NetHash then
		print("[NET ERROR] The local map is different version")
		--return "map"
	end

	local success, error = procall(function()
		-- remove all non-permanent objects - they will be copied from the host
		local non_permanent_objs = QueryNetObjects(true)
		DoneObjects(non_permanent_objs)

		NetCreateNetObjs(game_data.objects)
		NetSetNetObjProps(game_data.props)
		NetSetDynamicData(game_data.dynamic)
		NetSetRandData(game_data.rand)
		NetSetScenarioData(game_data.scenario, game_data_local and game_data_local.scenario)
		if game_data_local then
			SetLocalObjData(game_data_local.objects)
		end
		SetTimeFactor(const.DefaultTimeFactor)
		SetPause(game_data.paused or false)
		Msg("NetStateSet", game_data.globals) --> allow the dynamic objects to init some parts based on their surrounding environment or whatever)
	end)
	
	if not success then
		assert(false, tostring(error))
		return "failed", err_local
	end
	return nil, err_local
end

function NetGetNetObjs(objects)
	local object_data = {}
	local count = 0
	for _, obj in ipairs(objects) do
		object_data[count + 1] = obj.class
		object_data[count + 2] = obj.handle
		object_data[count + 3] = obj:GetPos()
		count = count + 3
	end
	return object_data
end

function NetCreateNetObjs(object_data)
	for i=1,#object_data, 3 do
		local class, handle, pos = unpack_params(object_data, i, i + 2)
		local obj = PlaceObject(class, { handle = handle })
		if obj then
			Object.SetPos(obj, pos) --> don't use the object's original method to avoid custom logic
		else
			mp_print("Cannot create an object", class, "with handle", handle)
		end
	end
end

local network_ignore_props = {
	Pos = true
}

function NetGetNetObjProps(objects)
	local prop_objdata = {}
	for i=1,#objects do
		local obj = objects[i]
		local modified_props = GetModifiedProperties( obj, nil, network_ignore_props )
		if modified_props then
			modified_props.__obj = obj
			AddNetObjDebugInfo(obj, modified_props)
			
			local serialized_props, err = NetSerialize(modified_props)
			if serialized_props and not FindSerializeError(serialized_props, modified_props) then
				prop_objdata[#prop_objdata + 1] = serialized_props
			else
				mp_print("Serialize properties failed for", format_value(obj), err)
				if Platform.developer then
					DebugPrint(format_value(modified_props))
				end
				assert(false, "Serialize properties failed")
			end
		end
	end
	return prop_objdata
end

function NetSetNetObjProps(prop_objdata)
	local warned = false
	for i = 1,#prop_objdata do
		local modified_props = NetUnserialize(prop_objdata[i])
		local obj = modified_props.__obj
		if obj then
			local props = obj:GetProperties()
			for j = 1, #props do
				local id = props[j].id
				local value = modified_props[id]
				if value ~= nil then
					local success, error = procall(obj.SetProperty, obj, id, value)
					if not success then
						mp_print("Failed to set", obj.class, "property", id, ":", error)
					end
				end
			end
		else
			ShowNetObjDebugInfo(modified_props)
		end
	end
end

function NetGetDynamicData(objects)
	local dynamic_objdata = {}
	for _, obj in ipairs(objects) do
		local data = {}

		procall(obj.GetDynamicData, obj, data)
		
		if next(data) then
			data.__obj = obj
			AddNetObjDebugInfo(obj, data)
			
			local serialized_data, err = NetSerialize(data)
			if serialized_data and not FindSerializeError(serialized_data, data) then
				dynamic_objdata[#dynamic_objdata + 1] = serialized_data
			else
				mp_print("Serialize dynamic data failed for", format_value(obj), err)
				assert(false, "Serialize dynamic data failed")
			end
		end
	end
	return dynamic_objdata
end

function NetSetDynamicData(dynamic_objdata)
	for i = 1,#dynamic_objdata do
		local data = NetUnserialize(dynamic_objdata[i])
		local obj = data.__obj
		if obj then
			local success, error = procall(obj.SetDynamicData, obj, data)
			if not success then
				mp_print("Failed to set", obj.class, "dynamic data:", error)
			end
		else
			ShowNetObjDebugInfo(data)
		end
	end
end

function NetAdjustTime(time)
	return time and (GameTime() - time) or nil
end

function NetGetLocalObjData()
	local objects = MapGet(true, "LootObj", function(o) return not IsNetObj(o) and o:GetGameFlags(const.gofPermanent) == 0 end)
	local objdata = {}
	for _, obj in ipairs(objects) do
		local data = {}
		data.__class = obj.class
		data.__pos = obj:GetPos()
		data.__props = GetModifiedProperties( obj, nil, network_ignore_props )
		procall(obj.GetDynamicData, obj, data)
		if next(data) then
			local serialized_data, err = NetSerialize(data)
			if serialized_data and not FindSerializeError(serialized_data, data) then
				objdata[#objdata + 1] = serialized_data
			else
				mp_print("Serialize dynamic data failed for", format_value(obj), err)
				assert(false, "Serialize dynamic data failed")
			end
		end
	end
	return objdata
end

function SetLocalObjData(objdata)
	for i = 1, #objdata do
		local data = NetUnserialize(objdata[i])
		local obj = PlaceObject(data.__class)
		if obj then
			NetTempObject(obj)
			Object.SetPos(obj, data.__pos) --> don't use the object's original method to avoid custom logic		
			local modified_props = data.__props
			local props = obj:GetProperties()
			for j = 1, #props do
				local id = props[j].id
				local value = modified_props[id]
				if value ~= nil then
					local success, error = procall(obj.SetProperty, obj, id, value)
					if not success then
						mp_print("Failed to set", obj.class, "property", id, ":", error)
					end
				end
			end
			local success, error = procall(obj.SetDynamicData, obj, data)
			if not success then
				mp_print("Failed to set", obj.class, "dynamic data:", error)
			end
		end
	end
end

-- Pause/Resume

function SetPause(pause)
	local paused = not not PauseReasons.UserPaused
	if paused == pause then return end
	if pause then
		Pause("UserPaused")
	else
		Resume("UserPaused")
	end
end

function TogglePause()
	local paused = not not PauseReasons.UserPaused
	if netSwarmSocket then
		NetEchoEvent("SetPause", not paused)
	else
		local pause = not paused
		SetPause(pause)
		return pause
	end
end

function NetEvents.SetPause(pause)
	SetPause(pause)
end

-------------------------------------------------[ Server time ]------------------------------------------------------

function ServerTime()
	return IsRealTimeThread() and (RealTime() + netServerRealTimeDelta) or (GameTime() + netServerGameTimeDelta)
end

function NetSetServerTime(server_time)
	if server_time then
		local estimated_rt = RealTime() + netServerRealTimeDelta
		local estimated_gt = GameTime() + netServerGameTimeDelta
		log("servertime", server_time, ", gt error", estimated_gt - server_time, ", rt error", estimated_rt - server_time)
		
		local new_rt_delta = server_time - RealTime()
		local new_gt_delta = server_time - GameTime()
		if netServerRealTimeDelta ~= new_rt_delta or netServerGameTimeDelta ~= new_gt_delta then
			netServerRealTimeDelta = new_rt_delta
			netServerGameTimeDelta = new_gt_delta
			Msg("ServerTimeUpdate")
		end
	else
		netServerRealTimeDelta = 0
		netServerGameTimeDelta = 0
	end
end

function WaitServerTime(time)
	while WaitMsg("ServerTimeUpdate", time - ServerTime()) do end
end

function WaitServerTick(tick_interval, tick_phase)
	WaitServerTime(ServerTime() / tick_interval * tick_interval + tick_interval + (tick_phase or 0))
end

function OnMsg.NetPing(server_time)
	NetSetServerTime(server_time)
end

-- SetTimeFactor
if FirstLoad then
	__SetTimeFactor = SetTimeFactor
end
NetEvents.SetTimeFactor = __SetTimeFactor
function SetTimeFactor(time_factor, sync)
	if sync then
		NetEchoEvent("SetTimeFactor", time_factor)
	else
		__SetTimeFactor(time_factor)
	end
end

-- Debug

function AddNetObjDebugInfo() end
function ReportMissingObj() end
function ShowNetObjDebugInfo() end

if Platform.developer then

MapVar("__missing_net_objs", {})
function ReportMissingObj(handle, class, pos)
	if handle and not __missing_net_objs[handle] then
		__missing_net_objs[handle] = true
		mp_print("Missing", class or "object", "with handle", handle, "at", pos)
	end
end

function AddNetObjDebugInfo(obj, data)
	data.__class = obj.class
	data.__handle = obj.handle
	data.__pos = obj:GetVisualPos()
end

function ShowNetObjDebugInfo(data)
	ReportMissingObj(data.__handle, data.__class, data.__pos)
end

end