Download CommonLua/GameRecording.lua from sirnii/myspace: direct link, hf CLI and curl.
- Browser
- Download file 20.1 kB
-
https://huggingface.co/spaces/sirnii/myspace/resolve/main/CommonLua/GameRecording.lua
- Command line
-
hf download hf://spaces/sirnii/myspace/CommonLua/GameRecording.lua
-
curl -L -o GameRecording.lua https://huggingface.co/spaces/sirnii/myspace/resolve/main/CommonLua/GameRecording.lua
20.1 kB
| DefineClass("TestValidator") | |
| IsGameReplayRunning = empty_func | |
| if Platform.developer then | |
| RecursiveCallMethods.GetTestData = "call" | |
| end | |
| function IsGameRecordingSupported() | |
| return config.SupportGameRecording and Libs.Network == "sync" | |
| end | |
| GameRecordVersion = 2 | |
| function ApplyRecordVersion(record) | |
| local version = not record and GameRecordVersion or record.version or 1 | |
| if version == GameRecordCurrentVersion then | |
| return | |
| end | |
| GameRecordCurrentVersion = version | |
| if version == 1 then | |
| RECORD_GTIME = 1 | |
| RECORD_EVENT = 2 | |
| RECORD_PARAM = 3 | |
| --RECORD_RAND = 4 | |
| --RECORD_HANDLE = 5 | |
| RECORD_RTIME = 6 | |
| RECORD_ETYPE = 7 | |
| --RECORD_HASH = 8 | |
| RECORD_SPEED = 9 | |
| else | |
| RECORD_GTIME = 1 | |
| RECORD_EVENT = 2 | |
| RECORD_PARAM = 3 | |
| RECORD_RTIME = 4 | |
| RECORD_ETYPE = 5 | |
| RECORD_SPEED = 6 | |
| end | |
| end | |
| if FirstLoad then | |
| GameRecordCurrentVersion = false | |
| ApplyRecordVersion() | |
| end | |
| config.GameRecordsPath = "AppData/GameRecords" | |
| local records_path = config.GameRecordsPath | |
| MapVar("GameRecord", false) -- to be available in the saves | |
| MapVar("GameReplay", false) | |
| MapVar("GameReplayThread", false) | |
| if FirstLoad then | |
| GameRecordScheduled = false | |
| GameReplayScheduled = false | |
| GameReplayPath = false | |
| GameReplaySaveLoading = false | |
| GameReplayUnresolved = false | |
| GameReplayWaitMap = false | |
| GameReplayToInject = false | |
| GameReplayFastForward = false | |
| GameRecordSaveRequests = false | |
| end | |
| function IsGameReplayRunning() | |
| return IsValidThread(GameReplayThread) and GameReplay | |
| end | |
| if not IsGameRecordingSupported() then | |
| return | |
| end | |
| local IsGameReplayRunning = IsGameReplayRunning | |
| function OnMsg.ChangeMap() | |
| GameReplayPath = false | |
| GameRecordScheduled = false | |
| StopGameReplay() | |
| end | |
| function OnMsg.NewMapLoaded() | |
| GameReplay = false | |
| GameRecord = GameRecordScheduled | |
| GameRecordScheduled = false | |
| if not not GameRecord then | |
| Msg("GameRecordingStarted") | |
| end | |
| end | |
| function OnMsg.NetGameJoined() | |
| GameRecord = false | |
| end | |
| function OnMsg.GameTestsBegin(auto_test) | |
| table.change(config, "GameTests_GameRecording", { | |
| EnableGameRecording = false, | |
| }) | |
| end | |
| function OnMsg.GameTestsEnd(auto_test) | |
| table.restore(config, "GameTests_GameRecording", true) | |
| end | |
| function SerializeRecordParams(...) | |
| return SerializeEx(const.SerializeCustom, CustomSerialize, ...) | |
| end | |
| function UnserializeRecordParams(params_str) | |
| return UnserializeEx(const.SerializeCustom, CustomUnserialize, params_str) | |
| end | |
| function PrepareRecordForSaving(record) | |
| record = record or GameRecord | |
| if record ~= GameRecord then return end | |
| record.game_time = GameTime() | |
| end | |
| function PlayGameRecord(record, start_idx) | |
| record = record or GameReplayScheduled | |
| if not record then return end | |
| assert(IsGameTimeThread()) | |
| assert(record.start_rand == MapLoadRandom) | |
| start_idx = start_idx or 1 | |
| GameReplay = record | |
| GameReplayScheduled = false | |
| GameReplaySaveLoading = false | |
| GameReplayThread = CurrentThread() | |
| if GameReplayWaitMap then | |
| WaitWakeup() -- ensure the map loading is complete | |
| end | |
| ApplyRecordVersion(record) | |
| local desync_any | |
| local total_time = Max((record[#record] or empty_table)[RECORD_GTIME] or 0, record.game_time or 0) | |
| local start_time = GameTime() | |
| if start_idx > #record or start_time > record[start_idx][RECORD_GTIME] then | |
| GameTestsPrint("Replay injection start mismatch!") | |
| start_idx = 1 | |
| while start_idx <= #record and start_time > record[start_idx][RECORD_GTIME] do | |
| start_idx = start_idx + 1 | |
| end | |
| end | |
| local version = record.version or 1 | |
| GameTestsPrint("Replay start at", Min(start_idx, #record), "/", #record, "events", "|", start_time, "/", total_time, "ms", "|", "Lua rev", record.lua_rev or 0, "/", LuaRevision, "|", "assets rev", record.assets_rev or 0, "/", AssetsRevision) | |
| for i = start_idx,#record do | |
| local event_time = record[i][RECORD_GTIME] | |
| local delay = event_time - now() | |
| local yield | |
| if delay > 0 then | |
| yield = record[i][RECORD_SPEED] == 0 | |
| Sleep(delay) | |
| else | |
| local last_record = record[i - 1] | |
| local prev_real_time = last_record and last_record[RECORD_RTIME] or record.real_time | |
| yield = prev_real_time ~= record[i][RECORD_RTIME] | |
| end | |
| if yield then | |
| -- make sure all game time threads created by the previous event have been started | |
| WaitAllOtherThreads() | |
| end | |
| if GameReplayThread ~= CurrentThread() or GameReplay ~= record then | |
| return | |
| end | |
| print("Replay", i, '/', #record) | |
| CreateGameTimeThread(function(record, i) | |
| local entry = record[i] | |
| local event, params_str = entry[RECORD_EVENT], entry[RECORD_PARAM] | |
| GameReplayUnresolved = false | |
| local success, err = ExecuteSyncEvent(event, UnserializeRecordParams(params_str)) | |
| if not success then | |
| GameTestsError("Replay", i, '/', #record, event, err) | |
| end | |
| if GameReplayUnresolved then | |
| GameTestsPrint("Replay", i, '/', #record, event, "unresolved objects:") | |
| for _, data in ipairs(GameReplayUnresolved) do | |
| local handle, class, pos = table.unpack(data) | |
| GameTestsPrint("\t", class, handle, "at", pos) | |
| end | |
| end | |
| Msg("GameRecordPlayed", i, record) | |
| end, record, i) | |
| end | |
| Sleep((record.game_time or 0) - now()) | |
| Sleep(0) Sleep(0) Sleep(0) Sleep(0) | |
| GameTestsPrint("Replay finished") | |
| Msg("GameReplayEnd", record) | |
| end | |
| local function IsSameClass(obj, class) | |
| if not obj then | |
| return -- object handle changed or object not yet spawned | |
| end | |
| if not class or obj.class == class then | |
| return true | |
| end | |
| local classdef = g_Classes[class] | |
| return not classdef or IsKindOf(classdef, obj.class) or IsKindOf(obj, class) -- recorded class renamed | |
| end | |
| function CustomSerialize(obj) | |
| local handle = obj.handle | |
| if handle and IsValid(obj) and obj:IsValidPos() then | |
| if obj:GetGameFlags(const.gofSyncObject | const.gofPermanent) == 0 then | |
| StoreErrorSource(obj, "Async object in sync event") | |
| end | |
| local record = GameRecord | |
| local handles = record and record.handles | |
| if handles then | |
| local class = obj.class | |
| handles[handle] = class | |
| return { handle, class, obj:GetPos() } | |
| end | |
| end | |
| end | |
| function CustomUnserialize(tbl) | |
| local handle, class, pos = table.unpack(tbl) | |
| local obj = HandleToObject[handle] | |
| if IsSameClass(obj, class) then | |
| return obj | |
| end | |
| local map_obj = MapGetFirst(pos, 0, class) | |
| if map_obj then | |
| return map_obj | |
| end | |
| GameReplayUnresolved = table.create_add(GameReplayUnresolved, tbl) | |
| end | |
| function CreateRecordedEvent(event_type) | |
| local origGameEvent = _G[event_type] | |
| _G[event_type] = function(event, ...) | |
| if IsGameReplayRunning() then | |
| if not config.GameReplay_EventsDuringPlaybackExpected then | |
| print("Ignoring", event_type, event, "during replay!") | |
| end | |
| return | |
| end | |
| local record = GameRecord | |
| if record then | |
| local params, err = SerializeRecordParams(...) | |
| assert(params, err) | |
| CreateGameTimeThread(function(event, event_type, record, params) | |
| -- in a thread to have the correct sync values (as the event will be started in a thread) | |
| local time = GameTime() | |
| local n = #record | |
| while n > 0 and record[n][RECORD_GTIME] > time do | |
| record[n] = nil | |
| n = n - 1 | |
| end | |
| n = n + 1 | |
| record[n] = { | |
| [RECORD_GTIME] = time, | |
| [RECORD_EVENT] = event, | |
| [RECORD_PARAM] = params, | |
| [RECORD_RTIME] = RealTime(), | |
| [RECORD_ETYPE] = event_type, | |
| [RECORD_SPEED] = GetTimeFactor(), | |
| } | |
| if config.GameRecordingAutoSave then | |
| SaveGameRecord() | |
| end | |
| end, event, event_type, record, params) | |
| end | |
| return origGameEvent(event, ...) | |
| end | |
| end | |
| function CreateRecordedMapLoadRandom() | |
| local origInitMapLoadRandom = InitMapLoadRandom | |
| InitMapLoadRandom = function() | |
| local rand | |
| if GameReplayScheduled then | |
| CreateGameTimeThread(PlayGameRecord) | |
| rand = GameReplayScheduled.start_rand | |
| else | |
| rand = origInitMapLoadRandom() | |
| if mapdata and mapdata.GameLogic and config.EnableGameRecording then | |
| assert(Game) | |
| assert(not IsGameReplayRunning()) | |
| GameReplay = false | |
| GameRecordScheduled = { | |
| start_rand = rand, | |
| map_name = GetMapName(), | |
| map_hash = mapdata.NetHash, | |
| os_time = os.time(), | |
| real_time = RealTime(), | |
| game = CloneObject(Game), | |
| lua_rev = LuaRevision, | |
| assets_rev = AssetsRevision, | |
| handles = {}, | |
| version = GameRecordVersion, | |
| net_update_hash = config.DebugReplayDesync, | |
| } | |
| Msg("GameRecordScheduled") | |
| end | |
| end | |
| return rand | |
| end | |
| end | |
| function CreateRecordedGenerateHandle() | |
| local origGenerateSyncHandle = GenerateSyncHandle | |
| GenerateSyncHandle = function(self) | |
| local h0, h = NextSyncHandle, origGenerateSyncHandle() | |
| if IsGameReplayRunning() and GameReplay.NextSyncHandle and GameReplay.handles and not IsSameClass(self, GameReplay.handles[h]) then | |
| -- the handle should be associated with another object, not yet spawn because of a previois desync | |
| NextSyncHandle = GameReplay.NextSyncHandle | |
| h = origGenerateSyncHandle() | |
| NextSyncHandle = h0 | |
| end | |
| return h | |
| end | |
| end | |
| function RegisterGameRecordOverrides() | |
| CreateRecordedEvent("NetSyncEvent") | |
| CreateRecordedMapLoadRandom() | |
| CreateRecordedGenerateHandle() | |
| end | |
| function OnMsg.ClassesGenerate() | |
| RegisterGameRecordOverrides() | |
| GameTests.PlayGameRecords = TestGameRecords | |
| end | |
| function TestGameRecords() | |
| local list = {} | |
| for _, test in ipairs(GetHGProjectTests()) do | |
| for _, info in ipairs(test.test_replays) do | |
| local name = string.format("%s.%s", test.id, info:GetRecordName()) | |
| if info.Disabled then | |
| GameTestsPrint("Skipping disabled test replay", name) | |
| else | |
| list[#list + 1] = {name = name, record = info.Record} | |
| end | |
| end | |
| end | |
| GameTestsPrintf("Found %d game records to test.", #(list or "")) | |
| for i, entry in ipairs(list) do | |
| GameTestsPrintf("Testing record %d/%d %s", i, #list, entry.name) | |
| local st = RealTime() | |
| local err, record = LoadGameRecord(entry.record) | |
| if err then | |
| GameTestsPrintf("Failed to load record %s", entry.record, err) | |
| else | |
| err = ReplayGameRecord(record) | |
| if err then | |
| GameTestsError("Replay error:", err) | |
| elseif not WaitReplayEnd() then | |
| GameTestsError("Replay timeout.") | |
| else | |
| GameTestsPrint("Replay finished in:", RealTime() - st, "ms") | |
| end | |
| end | |
| end | |
| end | |
| local function GenerateRecordPath(record) | |
| local err = AsyncCreatePath(records_path) | |
| if err then | |
| assert(err) | |
| return false | |
| end | |
| record = record or GameRecord | |
| local name = string.format("Record_%s_%s", os.date("%Y_%m_%d_%H_%M_%S", record.os_time), GetMapName()) | |
| if record.continue then | |
| name = name .. "_continue" | |
| end | |
| return string.format("%s/%s.lua", records_path, name) | |
| end | |
| function SaveGameRecord() | |
| if not GameRecord then return end | |
| local path = GameReplayPath or GenerateRecordPath(GameRecord) | |
| GameRecord.NextSyncHandle = NextSyncHandle | |
| if not GameRecordSaveRequests then | |
| GameRecordSaveRequests = {} | |
| CreateRealTimeThread(function() | |
| while true do | |
| for path, record in pairs(GameRecordSaveRequests) do | |
| GameRecordSaveRequests[path] = nil | |
| WaitSaveGameRecord(path, record) | |
| end | |
| if not next(GameRecordSaveRequests) then | |
| GameRecordSaveRequests = false | |
| break | |
| end | |
| end | |
| end) | |
| end | |
| GameRecordSaveRequests[path] = GameRecord | |
| end | |
| function WaitSaveGameRecord(path, record) | |
| record = record or GameRecord | |
| if not record then return end | |
| PrepareRecordForSaving(record) | |
| local code = pstr("return ", 64*1024) | |
| TableToLuaCode(record, nil, code) | |
| path = path or GenerateRecordPath(record) | |
| local err = AsyncStringToFile(path, code) | |
| if err then return err end | |
| Msg("GameReplaySaved", path) | |
| return nil, path | |
| end | |
| function ReplayGameRecord(record) | |
| record = record or not IsGameReplayRunning() and GameRecord or GameReplay | |
| local err | |
| if type(record) == "string" then | |
| local _, _, ext = SplitPath(record) | |
| local path = ext ~= "" and record or string.format("%s/%s.lua", records_path, record) | |
| err, record = LoadGameRecord(path) | |
| end | |
| if not record then | |
| return err or "No record found!" | |
| end | |
| PrepareRecordForSaving(record) | |
| Msg("GameRecordEnd", record) | |
| StopGameReplay() | |
| Msg("GameReplayStart", record) | |
| GameReplayScheduled = record | |
| if record.start_save then | |
| GameReplaySaveLoading = true | |
| GameReplayThread = CreateRealTimeThread(ReplayLoadGameSpecificSave, record) | |
| else | |
| CloseMenuDialogs() | |
| CreateRealTimeThread(function() | |
| LoadingScreenOpen("idLoadingScreen", "ReplayGameRecord") | |
| GameReplayWaitMap = true | |
| local game = CloneObject(record.game) | |
| ChangeMap("") | |
| NewGame(game) | |
| local map_name = record.map_name | |
| local map_hash = record.map_hash | |
| if map_hash and map_hash ~= table.get(MapData, map_name, "NetHash") then | |
| local matched | |
| for map, data in sorted_pairs(MapData) do | |
| if map_hash == data.NetHash then | |
| matched = map | |
| break | |
| end | |
| end | |
| if not matched then | |
| GameTestsPrint("Replay map has been modified!") | |
| elseif matched ~= map_name then | |
| GameTestsPrint("Replay map changed to", matched) | |
| end | |
| map_name = matched or map_name | |
| end | |
| ChangeMap(map_name) | |
| GameReplayWaitMap = false | |
| Wakeup(GameReplayThread) | |
| LoadingScreenClose("idLoadingScreen", "ReplayGameRecord") | |
| end) | |
| end | |
| end | |
| function ResaveGameRecord(path) | |
| local _, _, ext = SplitPath(path) | |
| local path = ext ~= "" and path or string.format("%s/%s.lua", records_path, path) | |
| local err, record = LoadGameRecord(path) | |
| if not record then | |
| return err or "No record found!" | |
| end | |
| StopGameReplay() | |
| if record.start_save then | |
| assert(false, "Not implemented!") | |
| else | |
| CloseMenuDialogs() | |
| CreateRealTimeThread(function() | |
| table.change(config, "ResaveGameRecord", { | |
| FixedMapLoadRandom = record.start_rand, | |
| StartGameOnPause = true, | |
| }) | |
| ChangeMap(record.map_name) | |
| table.restore(config, "ResaveGameRecord") | |
| GameReplayPath = path | |
| end) | |
| end | |
| end | |
| function WaitReplayEnd() | |
| while not WaitMsg("GameReplayEnd", 100) do | |
| if not IsChangingMap() and not IsValidThread(GameReplayThread) then | |
| return | |
| end | |
| end | |
| return true | |
| end | |
| function StopGameReplay() | |
| local thread = not GameReplaySaveLoading and GameReplayThread | |
| if not IsValidThread(thread) then return end | |
| GameReplayScheduled = false | |
| GameReplayThread = false | |
| Msg("GameReplayEnd") | |
| DeleteThread(thread, true) | |
| return true | |
| end | |
| function LoadGameRecord(path) | |
| local func, err = loadfile(path, nil, _ENV) | |
| if not func then | |
| return err | |
| end | |
| local success, record = procall(func) | |
| if not success then return record end | |
| return nil, record | |
| end | |
| function OnMsg.LoadGame() | |
| if not GameReplayToInject then | |
| return | |
| end | |
| StopGameReplay() | |
| if not GameRecord then | |
| print("Replay injection failed: No saved record found (maybe a saved game during a replay?)") | |
| return | |
| end | |
| for _, key in ipairs{"start_rand", "map_name", "os_time", "lua_rev", "assets_rev"} do | |
| if GameRecord[key] ~= GameReplayToInject[key] then | |
| print("Replay injection failed: Wrong game!") | |
| return | |
| end | |
| end | |
| print("Replay Injection Success.") | |
| CreateGameTimeThread(PlayGameRecord, GameReplayToInject, #GameRecord + 1) | |
| GameReplayToInject = false | |
| end | |
| function ToggleGameReplayInjection(record) | |
| record = record or GameRecord or GameReplay | |
| if record and record == GameReplayToInject then | |
| record = false | |
| print("Replay Injection Cancelled") | |
| elseif record then | |
| print("Replay Injection Ready") | |
| else | |
| print("No record found to inject") | |
| end | |
| GameReplayToInject = record | |
| end | |
| ---- | |
| function ReplayLoadGameSpecificSave(save, callbackOnload) | |
| print("You must implement your game loading function in ReplayLoadGameSpecificSave to use game replays with saves.") | |
| return true | |
| end | |
| function ReplayToggleFastForward(set) | |
| if not IsGameReplayRunning() then | |
| set = false | |
| elseif set == nil then | |
| set = not GameReplayFastForward | |
| end | |
| if GameReplayFastForward == set then | |
| return | |
| end | |
| GameReplayFastForward = set | |
| TurboSpeed(set, true) | |
| end | |
| function OnMsg.GameReplayEnd(record) | |
| if GameReplayFastForward then | |
| ReplayToggleFastForward() | |
| end | |
| -- allow to continue the recording | |
| if record then | |
| record.continue = true | |
| GameRecord = record | |
| end | |
| end | |
| function OnMsg.GameRecordPlayed(i, record) | |
| if record and GameReplayFastForward then | |
| local events_before_end = config.ReplayFastForwardBeforeEnd or 10 | |
| if i >= #record - events_before_end then | |
| print(events_before_end, "events before the end reached, stopping fast forward...") | |
| ReplayToggleFastForward() | |
| SetTimeFactor(0) | |
| end | |
| end | |
| end | |
| ---- | |
| if config.DebugReplayDesync then | |
| if FirstLoad then | |
| GameRecordSyncLog = false | |
| GameRecordSyncTest = false | |
| GameRecordSyncIdx = false | |
| HashLogSize = 32 | |
| end | |
| function OnMsg.AutorunEnd() | |
| pairs = totally_async_pairs | |
| end | |
| function OnMsg.ReloadLua() | |
| pairs = g_old_pairs | |
| end | |
| function OnMsg.NetUpdateHashReasons(enable_reasons) | |
| local record = GameRecord or GameRecordScheduled or GameReplay or GameReplayScheduled | |
| enable_reasons.GameRecord = record and record.net_update_hash and true or nil | |
| end | |
| function OnMsg.LoadGame() | |
| GameRecordSyncLog = false | |
| end | |
| local function StartSyncLogSaving(replay) | |
| local err = AsyncCreatePath("AppData/ReplaySyncLogs") | |
| if err then | |
| print("Failed to create NetHashLogs folder:", err) | |
| return | |
| end | |
| GameRecordSyncLog = true | |
| GameRecordSyncTest = replay and ((GameRecordSyncTest or 0) + 1) or false | |
| GameRecordSyncIdx = 1 | |
| end | |
| function OnMsg.GameRecordScheduled() | |
| StartSyncLogSaving() | |
| end | |
| function OnMsg.GameReplayStart() | |
| StartSyncLogSaving(true) | |
| end | |
| function OnMsg.GameReplayEnd(record) | |
| NetSaveHashLog("E", "Replay", GameRecordSyncTest) | |
| GameRecordSyncLog = false | |
| end | |
| function OnMsg.GameRecordEnd(record) | |
| NetSaveHashLog("E", "Record") | |
| GameRecordSyncLog = false | |
| end | |
| function OnMsg.SyncEvent() | |
| if not GameRecordSyncIdx then | |
| return | |
| end | |
| if GameRecordSyncTest then | |
| NetSaveHashLog(GameRecordSyncIdx, "Replay", GameRecordSyncTest) | |
| else | |
| NetSaveHashLog(GameRecordSyncIdx, "Record") | |
| end | |
| GameRecordSyncIdx = GameRecordSyncIdx + 1 | |
| end | |
| function NetSaveHashLog(prefix, logtype, suffix) | |
| if not GameRecordSyncLog then | |
| return | |
| end | |
| local str = pstr("") | |
| NetGetHashLog(str) | |
| if #str == 0 then | |
| return | |
| end | |
| local path = string.format("AppData/ReplaySyncLogs/%s%s%s.log", (prefix and tostring(prefix) .. "_" or ""), logtype, suffix and ("_" .. tostring(suffix)) or "") | |
| CreateRealTimeThread(function(path, str) | |
| local err = AsyncStringToFile(path, str) | |
| if err then | |
| printf("Failed to save %s: %s", path, err) | |
| end | |
| end, path, str) | |
| end | |
| function OnMsg.BugReportStart(print_func) | |
| local replay = IsGameReplayRunning() | |
| if not replay then return end | |
| print_func("\nGame replay running:", replay.lua_rev, replay.assets_rev) | |
| end | |
| end -- config.DebugReplayDesync | |
| if Platform.developer then | |
| function TestValidator:CollectTestData() | |
| local data = {} | |
| if Platform.developer then | |
| self:GetTestData(data) | |
| end | |
| return data | |
| end | |
| function TestValidator:rfnTestData(orig_data) | |
| local new_data = self:CollectTestData() | |
| local ignore_missing = true -- avoid breaking existing tests when adding or removing test validation entries | |
| if table.equal_values(orig_data, new_data, -1, ignore_missing) then | |
| return | |
| end | |
| GameTestsError("Test data validation failed for", self.class, | |
| "\n--- Orig data: ", ValueToStr(orig_data), | |
| "\n--- New data: ", ValueToStr(new_data)) | |
| end | |
| function TestValidator:CreateValidation() | |
| local data = self:CollectTestData() | |
| if next(data) == nil then | |
| print("No test data to validate!") | |
| return | |
| end | |
| NetSyncEvent("ObjFunc", self, "rfnTestData", data) | |
| print("Test data collected:", ValueToStr(data)) | |
| end | |
| function TestValidator:AsyncCheatValidate() | |
| self:CreateValidation() | |
| end | |
| function ReplayCreateUpdateScript() | |
| local record = GameReplay or GameRecord | |
| if not record then | |
| print("No replay running!") | |
| return | |
| end | |
| local lua_rev = record.lua_rev or LuaRevision | |
| local assets_rev = record.assets_rev or AssetsRevision | |
| local src_path = ConvertToOSPath("svnSrc/") | |
| local assets_path = ConvertToOSPath("svnAssets/") | |
| local scrip = { | |
| "@echo off", | |
| "cd " .. src_path, | |
| "svn cleanup", | |
| "svn up -r " .. lua_rev, | |
| "cd " .. assets_path, | |
| "svn cleanup", | |
| "svn up -r " .. assets_rev, | |
| } | |
| local path = string.format("%sUpdateToRev_%d_%d.bat", src_path, lua_rev, assets_rev) | |
| local err = AsyncStringToFile(path, table.concat(scrip, "\n")) | |
| if err then | |
| print("Failed to create script:", err) | |
| else | |
| print("Script created at:", path) | |
| end | |
| end | |
| end -- Platform.developer |