diff --git a/binaries/data/mods/public/gamesettings/attributes/StartTime.js b/binaries/data/mods/public/gamesettings/attributes/StartTime.js new file mode 100644 index 0000000000..24f60a2589 --- /dev/null +++ b/binaries/data/mods/public/gamesettings/attributes/StartTime.js @@ -0,0 +1,7 @@ +GameSettings.prototype.Attributes.StartTime = class extends GameSetting +{ + onFinalizeAttributes(attribs) + { + attribs.startTime = Date.now(); + } +}; diff --git a/source/ps/Replay.cpp b/source/ps/Replay.cpp index 8ef85fa93d..b96fffc82a 100644 --- a/source/ps/Replay.cpp +++ b/source/ps/Replay.cpp @@ -85,7 +85,14 @@ void CReplayLogger::StartGame(JS::MutableHandleValue attribs) Script::ToJSVal(rq, &mods, g_Mods.GetEnabledModsData()); Script::SetProperty(rq, attribs, "mods", mods); - m_Directory = createDateIndexSubdirectory(VisualReplay::GetDirectoryPath()); + std::wstring matchID; + Script::GetProperty(rq, attribs, "matchID", matchID); + ENSURE(!matchID.empty()); + + double startTime = 0; + Script::GetProperty(rq, attribs, "startTime", startTime); + + m_Directory = createReplaySubdirectory(VisualReplay::GetDirectoryPath(), matchID, startTime); debug_printf("FILES| Replay written to '%s'\n", m_Directory.string8().c_str()); m_Stream = new std::ofstream(OsString(m_Directory / L"commands.txt"), std::ofstream::out | std::ofstream::trunc); @@ -95,12 +102,9 @@ void CReplayLogger::StartGame(JS::MutableHandleValue attribs) void CReplayLogger::Turn(u32 n, u32 turnLength, std::vector& commands) { Script::Request rq(m_ScriptInterface); - *m_Stream << "turn " << n << " " << turnLength << "\n"; - for (SimulationCommand& command : commands) *m_Stream << "cmd " << command.player << " " << Script::StringifyJSON(rq, &command.data, false) << "\n"; - *m_Stream << "end\n"; m_Stream->flush(); } diff --git a/source/ps/Util.cpp b/source/ps/Util.cpp index 1ac56c268b..b2741fe2fe 100644 --- a/source/ps/Util.cpp +++ b/source/ps/Util.cpp @@ -33,8 +33,10 @@ #include "ps/Filesystem.h" #include "ps/Pyrogenesis.h" +#include #include #include +#include #include #include #include @@ -119,6 +121,26 @@ OsPath createDateIndexSubdirectory(const OsPath& parentDir) return path; } +OsPath createReplaySubdirectory(const OsPath& parentDir, const std::wstring& matchID, double startTimeMillis) +{ + using namespace std::chrono; + + const system_clock::time_point timePoint{milliseconds(static_cast(startTimeMillis))}; + const year_month_day ymd{floor(timePoint)}; + const std::string baseName = fmt::format("{:04}-{:02}-{:02}_{}", + static_cast(ymd.year()), static_cast(ymd.month()), static_cast(ymd.day()), + utf8_from_wstring(matchID)); + + for (int i = 0; ; ++i) + { + const OsPath path = parentDir / (i == 0 ? baseName : fmt::format("{}_{:04}", baseName, i)); + if (DirectoryExists(path) || std::filesystem::is_regular_file(path.string())) + continue; + ENSURE(CreateDirectories(path, 0700) == INFO::OK); + return path; + } +} + std::string Hexify(const std::string& s) { std::stringstream str; diff --git a/source/ps/Util.h b/source/ps/Util.h index 36cd505f4e..a660c4d128 100644 --- a/source/ps/Util.h +++ b/source/ps/Util.h @@ -1,4 +1,4 @@ -/* Copyright (C) 2025 Wildfire Games. +/* Copyright (C) 2026 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify @@ -35,6 +35,8 @@ CStr GetStatusAsString(Status status); OsPath createDateIndexSubdirectory(const OsPath& parentDir); +OsPath createReplaySubdirectory(const OsPath& parentDir, const std::wstring& matchID, double startTimeMillis); + Status tex_write(Tex* t, const VfsPath& filename); std::string Hexify(const std::string& s);