File size: 10,073 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 |
function ReplayLoadGameSpecificSave(gameRecord)
Pause("load-replay-save")
LoadGameSessionData(gameRecord.start_save)
Resume("load-replay-save")
end
config.GameReplay_EventsDuringPlaybackExpected = true
if FirstLoad then
origSetTimeFactor = SetTimeFactor
end
local test_time = 50000
if FirstLoad then
GameTesting = false
end
function OnMsg.GameTestsBegin(auto_test)
GameTesting = true
end
function OnMsg.GameTestsEnd(auto_test)
GameTesting = false
end
function OnMsg.Resume()
if GameTesting then __SetTimeFactor(test_time) end
end
function OnMsg.GameReplayStart()
GameRecord = false
SetGameRecording(false)
end
function OnMsg.GameReplaySaved(path)
print("Saved replay " .. path)
GameRecord = false
SetGameRecording(false)
end
local _netFuncsToOverride = { "NetSyncEvent", "NetEchoEvent" }
local _netFuncToNetFuncArray = { ["NetSyncEvent"] = "NetSyncEvents", ["NetEchoEvent"] = "NetEvents" }
local _defaultNetFunc = "NetSyncEvent"
_replayDesynced = false
function IsGameReplayRecording()
return not not GameRecord
end
function StopGameRecord()
if IsValidThread(GameReplayThread) then
DeleteThread(GameReplayThread)
GameReplayThread = false
Resume("UI")
Msg("GameReplayEnd")
GameRecord = false
end
end
function NetSyncEvents.ReplayEnded()
GameTestsPrint("Replay done")
if IsValidThread(GameReplayThread) then DeleteThread(GameReplayThread) end
Msg("GameReplayEnd")
end
function ZuluStartScheduledReplay()
if not GameReplayScheduled then return end
local record = GameReplayScheduled
GameReplayScheduled = false
GameReplay = record
GameReplay.next_hash = 1
GameReplay.next_rand = 1
_replayDesynced = false
GameReplayThread = CreateGameTimeThread(function()
if GameReplayThread ~= CurrentThread() and IsValidThread(GameReplayThread) then
DeleteThread(GameReplayThread)
end
assert(GameTime() == 0)
assert(IsGameTimeThread())
assert(record.map_name == GetMapName())
assert(record.start_rand == MapLoadRandom)
local total_time = Max((record[#record] or empty_table)[RECORD_GTIME] or 0, record.game_time or 0)
Msg("GameReplayStart")
GameTestsPrint("Replay start:", #record, "events", "|", string.format(total_time * 0.001), "sec", "|", "Lua rev", record.lua_rev or 0, "/", LuaRevision, "|", "assets rev", record.assets_rev or 0, "/", AssetsRevision)
for i = 1, #record do
local entry = record[i]
local event, params = entry[RECORD_EVENT], entry[RECORD_PARAM]
local gtime, rtime, etype = entry[RECORD_GTIME], entry[RECORD_RTIME], entry[RECORD_ETYPE]
ScheduleSyncEvent(event, Serialize(UnserializeRecordParams(params)), gtime)
if event == "FenceReceived" then
WaitMsg("ReplayFenceCleared")
end
if i == #record then
ScheduleSyncEvent("ReplayEnded", false, gtime)
end
end
GameReplayThread = CreateGameTimeThread(function()
WaitMsg("GameReplayEnd")
end)
end)
end
function OnMsg.CanSaveGameQuery(query)
query.replay_running = IsGameReplayRunning() or nil
query.replay_recording = IsGameReplayRecording() or nil
end
if FirstLoad then
ContinueOnReplayDesync = not not Platform.trailer
end
local function lReplayDesynced()
_replayDesynced = true
if ContinueOnReplayDesync then
return
end
DeleteThread(GameReplayThread)
Msg("GameReplayEnd", GameReplay)
end
function RegisterGameRecordOverrides()
for i, event_type in ipairs(_netFuncsToOverride) do
CreateRecordedEvent(event_type)
end
CreateRecordedMapLoadRandom()
CreateRecordedGenerateHandle()
if FirstLoad then -- overwrite only on first load as it is a C function
local origNetUpdateNesh = NetUpdateHash
local function hashRecordingUpdate(...)
local hash = origNetUpdateNesh(...)
NetHashRecordingTracker(...)
return hash
end
NetUpdateHash = hashRecordingUpdate
end
local origInteractionRand = InteractionRand
local function RecordedInteractionRand(...)
local rand = origInteractionRand(...)
InteractionRandRecordingTracker(rand, ...)
return rand
end
InteractionRand = RecordedInteractionRand
end
function InteractionRandRecordingTracker(rolledRand, ...)
local playingReplay = IsGameReplayRunning()
local recordingReplay = IsGameReplayRecording()
if not playingReplay and not recordingReplay then return end
local paramsSerialized = Serialize({...})
local hash = xxhash(GameTime(), rolledRand, paramsSerialized)
if playingReplay then
local expectedHashIdx = GameReplay.next_rand
local expectedHashData = GameReplay.rand_list[expectedHashIdx]
local expectedHash = type(expectedHashData) == "table" and expectedHashData[1] or expectedHashData
if not expectedHash and expectedHashIdx > #GameReplay.rand_list then
return -- its over!
end
if hash ~= expectedHash and not _replayDesynced then
--randoms can start before record starts an event, but after load
if expectedHashIdx == 1 and playingReplay and not recordingReplay then
if GameState.loading_savegame or GameState.loading then
return
end
end
local params = {...}
GameTestsError("Replay desynced @", GameTime(), "Rand expected", expectedHash, "but got", hash, " expectedHashIdx", expectedHashIdx)
print("incoming :", GameTime(), rolledRand, GetStack())
print("expected :", expectedHashData[4], expectedHashData[3], expectedHashData[5])
lReplayDesynced()
end
GameReplay.next_rand = expectedHashIdx + 1
elseif GameRecord and GameRecord.rand_list then
--when rand_list gets serialized for saving, valid objs will get serialized as PlaceObject(..., which will then place them when the file is booted;
--get rid of those now;
local params = {...}
for i = #params, 1, -1 do
local o = params[i]
if IsValid(o) then
params[i] = string.format("Obj with class %s and handle %d", o.class, o:HasMember("handle") and o.handle or "N/A")
end
end
GameRecord.rand_list[#GameRecord.rand_list + 1] = { hash, params, rolledRand, GameTime(), GetStack() }
end
end
function Dbg_StringToBytesAsString(str) -- For use with paramsSerialized
return table.concat(({str}), ", ")
end
function NetHashRecordingTracker(...)
local params = ({...})
if GameReplayScheduled then
if params[1] == "NewMapLoaded" then
ZuluStartScheduledReplay()
end
end
local playingReplay = IsGameReplayRunning()
local recordingReplay = IsGameReplayRecording()
if not playingReplay and not recordingReplay then return end
-- Old replays dont have these captured.
if GameReplay and GameReplay.lua_rev == 327744 then
if params[1] == "ResetInteractionRand" or params[1] == "InteractionRand" then
return
end
end
-- Replay over event
if playingReplay and params and params[1] == "SyncEvent" and params[2] == "ReplayEnded" then
return
end
local paramsSerialized = Serialize(params) -- for debugging
local netHashVal = NetGetHashValue()
local hash = xxhash(GameTime(), netHashVal)
assert(netHashVal ~= 1)
-- Check if the incoming hash is the same as the next expected hash if replaying,
-- or record it if recording.
if playingReplay then
local expectedHashIdx = GameReplay.next_hash
local expectedHashData = GameReplay.hash_list[expectedHashIdx]
local expectedHash = type(expectedHashData) == "table" and expectedHashData[1] or expectedHashData
if not expectedHash and expectedHashIdx > #GameReplay.hash_list then
return -- its over, no need to check anymore!
end
if hash ~= expectedHash and not _replayDesynced then
GameTestsError("Replay desynced @", GameTime(), "Hash expected", expectedHash, "but got", hash)
lReplayDesynced()
end
GameReplay.next_hash = expectedHashIdx + 1
elseif GameRecord and GameRecord.hash_list then
GameRecord.hash_list[#GameRecord.hash_list + 1] = { hash, paramsSerialized, GameTime(), GetStack() }
end
end
function SetGameRecording(val)
config.EnableGameRecording = val
end
function CreateRecordedMapLoadRandom()
local origInitMapLoadRandom = InitMapLoadRandom
InitMapLoadRandom = function()
if GameTime() ~= 0 then -- Coming from InitGameVar and not ChangeMap
return origInitMapLoadRandom()
end
local rand
if GameReplayScheduled then
rand = GameReplayScheduled.start_rand
else
rand = origInitMapLoadRandom()
if mapdata and mapdata.GameLogic and config.EnableGameRecording then
assert(not IsGameReplayRunning())
GameReplay = false
print("Game is being recorded.")
GameRecordScheduled = {
start_rand = rand,
map_name = GetMapName(),
os_time = os.time(),
real_time = RealTime(),
game = Game,
lua_rev = LuaRevision,
assets_rev = AssetsRevision,
handles = {},
version = GameRecordVersion,
hash_list = {},
rand_list = {},
net_update_hash = true
}
end
end
return rand
end
end
function ZuluStartRecordingReplay()
if GameReplayScheduled then return end
CreateRealTimeThread(function()
local save = GatherSessionData():str()
SetGameRecording(true)
assert(not GameRecord)
LoadGameSessionData(save)
GameRecord.start_save = save
assert(GameRecord)
end)
end
local function SuspendAutosave()
config.AutosaveSuspended = true
end
local function ResumeAutosave()
config.AutosaveSuspended = false
end
OnMsg.GameReplayStart = SuspendAutosave
OnMsg.GameRecordingStarted = SuspendAutosave
OnMsg.GameReplayEnd = ResumeAutosave
OnMsg.GameReplaySaved = ResumeAutosave
if FirstLoad then
ShowReplayUI = not not Platform.trailer
ReplayUISpeed = false
end
function OnMsg.GameReplayStart()
ObjModified("replay_ui")
if ShowReplayUI then
ReplayUISpeed = ReplayUISpeed or const.DefaultTimeFactor
SetTimeFactor(ReplayUISpeed)
Pause("UI")
end
end
function OnMsg.GameReplayEnd()
GameRecord = false
ObjModified("replay_ui")
Resume("UI")
SetTimeFactor(const.DefaultTimeFactor)
end
function OnMsg.GameRecordingStarted()
ObjModified("replay_ui")
end
function OnMsg.GameReplaySaved()
ObjModified("replay_ui")
end
-- During replay playback all incoming NetSyncEvents are bypassed.
function PlaybackNetSyncEvent(eventId, ...)
if IsGameReplayRunning() then
NetSyncEvents[eventId](...)
else
NetSyncEvent(eventId, ...)
end
end |