Download Lua/GameRecording.lua from sirnii/myspace: direct link, hf CLI and curl.
- Browser
- Download file 10.1 kB
-
https://huggingface.co/spaces/sirnii/myspace/resolve/main/Lua/GameRecording.lua
- Command line
-
hf download hf://spaces/sirnii/myspace/Lua/GameRecording.lua
-
curl -L -o GameRecording.lua https://huggingface.co/spaces/sirnii/myspace/resolve/main/Lua/GameRecording.lua
10.1 kB
| 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 |