mirror of
https://gitea.wildfiregames.com/0ad/0ad
synced 2026-08-15 14:43:32 -07:00
Replay: name replay folders after matchID instead of a per-machine index
Replay folders were named YYYY-MM-DD_NNNN, a per-machine sequence counter. The same multiplayer match got a different folder name per participant, and unrelated matches could share a name, making it hard to correlate replays across players (rating aggregation, replay sharing, bug reports) without parsing commands.txt first. The engine already generates a matchID once per match, synced to all participants before the match starts. Replay folders are now named YYYY-MM-DD_<matchID>, so the same match produces the same folder name everywhere. The date comes from a new "startTime" attribute, set once by the host (StartTime.js, alongside MatchID.js) and synced like matchID, instead of each machine reading its own local clock. createDateIndexSubdirectory is untouched (still used by OOS logs and serialization tests); a new createReplaySubdirectory is added for replays only, following the same collision-handling pattern (incrementing suffix) as the existing function. This also means resuming a match (network rejoin, or a singleplayer game reloaded via StartSavedGame, both of which reuse the same matchID) saves an additional replay instead of overwriting the original. Fixes #8922
This commit is contained in:
parent
1709149740
commit
3808ca0965
4 changed files with 40 additions and 5 deletions
|
|
@ -0,0 +1,7 @@
|
|||
GameSettings.prototype.Attributes.StartTime = class extends GameSetting
|
||||
{
|
||||
onFinalizeAttributes(attribs)
|
||||
{
|
||||
attribs.startTime = Date.now();
|
||||
}
|
||||
};
|
||||
|
|
@ -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<SimulationCommand>& 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();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -33,8 +33,10 @@
|
|||
#include "ps/Filesystem.h"
|
||||
#include "ps/Pyrogenesis.h"
|
||||
|
||||
#include <chrono>
|
||||
#include <ctime>
|
||||
#include <filesystem>
|
||||
#include <fmt/format.h>
|
||||
#include <iomanip>
|
||||
#include <memory>
|
||||
#include <sstream>
|
||||
|
|
@ -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<i64>(startTimeMillis))};
|
||||
const year_month_day ymd{floor<days>(timePoint)};
|
||||
const std::string baseName = fmt::format("{:04}-{:02}-{:02}_{}",
|
||||
static_cast<int>(ymd.year()), static_cast<unsigned>(ymd.month()), static_cast<unsigned>(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;
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
Loading…
Reference in a new issue