|
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_RTIME = 6 |
|
RECORD_ETYPE = 7 |
|
|
|
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) |
|
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() |
|
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 |
|
|
|
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 |
|
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) |
|
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) |
|
|
|
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 |
|
|
|
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 |
|
|
|
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 |
|
|
|
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 |
|
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 |