Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 21 additions & 10 deletions addons/sourcemod/scripting/include/shavit/replay-file.inc
Original file line number Diff line number Diff line change
Expand Up @@ -36,10 +36,11 @@
// 0x09: bumped with no actual file changes because time calculation in regards to offsets have been changed/fixed since it seems to have been using the end-zone-offset incorrectly (and should now be fine hopefully since 2021-12-21 / a146b51fb16febf1847657fba7ef9e0c056d7476)
// 0x0a: Replay-frame saving was originally in `OnPlayerRunCmd()` (`Shavit_OnUserCmdPre`). It was moved to `OnPlayerRunCmdPost()` so we could capture `buttons`/`vel` after every plugin had modified them (55b6253b30e1f0152e7c79077f03a7684fd774f7 / after 0x08 but before 0x09 / v3.1.0). This was a mistake because grabbing `flags` in `OnPlayerRunCmdPost()` is busted and fucked and shit. So this version defucks that. Replay-file format is unchanged, but .pos, .flags, and .mt are grabbed in `OnPlayerRunCmdPre()` and the rest of the values are grabbed in `OnPlayerRunCmdPost()`.
// 0x0b: Well, 0x0a causes problems with timescaled/tas replay playback & JHUD calculation, so just reverting 0x0a until there's a better plan for everything. Replay-file format is unchanged.
// 0x0c: timestamp added

#define REPLAY_FORMAT_V2 "{SHAVITREPLAYFORMAT}{V2}"
#define REPLAY_FORMAT_FINAL "{SHAVITREPLAYFORMAT}{FINAL}"
#define REPLAY_FORMAT_SUBVERSION 0x0b
#define REPLAY_FORMAT_SUBVERSION 0x0c

#define REPLAY_FRAMES_PER_WRITE 100 // amounts of frames to write per read/write call

Expand All @@ -57,6 +58,7 @@ enum struct replay_header_t
int iPostFrames;
float fTickrate;
float fZoneOffset[2];
int iTimestamp;
}

enum struct frame_t
Expand Down Expand Up @@ -152,8 +154,8 @@ stock bool ReadReplayFrames(File file, replay_header_t header, frame_cache_t cac

while (!file.EndOfFile())
{
file.ReadLine(sLine, 320);
int iStrings = ExplodeString(sLine, "|", sExplodedLine, 6, 64);
file.ReadLine(sLine, sizeof(sLine));
int iStrings = ExplodeString(sLine, "|", sExplodedLine, sizeof(sExplodedLine), sizeof(sExplodedLine[]));

aReplayData[0] = StringToFloat(sExplodedLine[0]);
aReplayData[1] = StringToFloat(sExplodedLine[1]);
Expand Down Expand Up @@ -232,15 +234,15 @@ stock File ReadReplayHeader(const char[] path, replay_header_t header, int style

char sHeader[64];

if(!file.ReadLine(sHeader, 64))
if(!file.ReadLine(sHeader, sizeof(sHeader)))
{
delete file;
return null;
}

TrimString(sHeader);
char sExplodedHeader[2][64];
ExplodeString(sHeader, ":", sExplodedHeader, 2, 64);
ExplodeString(sHeader, ":", sExplodedHeader, sizeof(sExplodedHeader), sizeof(sExplodedHeader[]));

strcopy(header.sReplayFormat, sizeof(header.sReplayFormat), sExplodedHeader[1]);

Expand Down Expand Up @@ -281,9 +283,9 @@ stock File ReadReplayHeader(const char[] path, replay_header_t header, int style
else
{
char sAuthID[32];
file.ReadString(sAuthID, 32);
ReplaceString(sAuthID, 32, "[U:1:", "");
ReplaceString(sAuthID, 32, "]", "");
file.ReadString(sAuthID, sizeof(sAuthID));
ReplaceString(sAuthID, sizeof(sAuthID), "[U:1:", "");
ReplaceString(sAuthID, sizeof(sAuthID), "]", "");
header.iSteamID = StringToInt(sAuthID);
}

Expand All @@ -303,6 +305,11 @@ stock File ReadReplayHeader(const char[] path, replay_header_t header, int style
file.ReadInt32(view_as<int>(header.fZoneOffset[0]));
file.ReadInt32(view_as<int>(header.fZoneOffset[1]));
}

if (version >= 0x0c)
{
file.ReadInt32(view_as<int>(header.iTimestamp));
}
}
else if(StrEqual(header.sReplayFormat, REPLAY_FORMAT_V2))
{
Expand Down Expand Up @@ -343,7 +350,7 @@ stock File ReadReplayHeader(const char[] path, replay_header_t header, int style
return file;
}

stock void WriteReplayHeader(File fFile, int style, int track, float time, int steamid, int preframes, int postframes, float fZoneOffset[2], int iSize, float tickrate, const char[] sMap)
stock void WriteReplayHeader(File fFile, int style, int track, float time, int steamid, int preframes, int postframes, float fZoneOffset[2], int iSize, float tickrate, const char[] sMap, int timestamp)
{
fFile.WriteLine("%d:" ... REPLAY_FORMAT_FINAL, REPLAY_FORMAT_SUBVERSION);

Expand All @@ -361,6 +368,8 @@ stock void WriteReplayHeader(File fFile, int style, int track, float time, int s

fFile.WriteInt32(view_as<int>(fZoneOffset[0]));
fFile.WriteInt32(view_as<int>(fZoneOffset[1]));

fFile.WriteInt32(timestamp);
}

stock void cell2buf(char[] buf, int& pos, int cell)
Expand All @@ -371,7 +380,7 @@ stock void cell2buf(char[] buf, int& pos, int cell)
buf[pos++] = (cell >> 24) & 0xFF;
}

stock int WriteReplayHeaderToBuffer(char[] buf, int style, int track, float time, int steamid, int preframes, int postframes, float fZoneOffset[2], int totalframes, float tickrate, const char[] sMap)
stock int WriteReplayHeaderToBuffer(char[] buf, int style, int track, float time, int steamid, int preframes, int postframes, float fZoneOffset[2], int totalframes, float tickrate, const char[] sMap, int timestamp)
{
int pos = FormatEx(buf, 512, "%d:%s\n%s", REPLAY_FORMAT_SUBVERSION, REPLAY_FORMAT_FINAL, sMap);
pos += 1; // skip past NUL
Expand All @@ -389,6 +398,8 @@ stock int WriteReplayHeaderToBuffer(char[] buf, int style, int track, float time
cell2buf(buf, pos, view_as<int>(fZoneOffset[0]));
cell2buf(buf, pos, view_as<int>(fZoneOffset[1]));

cell2buf(buf, pos, timestamp);

return pos;
}

Expand Down
71 changes: 71 additions & 0 deletions addons/sourcemod/scripting/shavit-replay-playback.sp
Original file line number Diff line number Diff line change
Expand Up @@ -483,6 +483,8 @@ public void OnPluginStart()

// commands
RegAdminCmd("sm_deletereplay", Command_DeleteReplay, ADMFLAG_RCON, "Open replay deletion menu.");
RegAdminCmd("sm_playreplayfile", Command_PlayReplayFile, ADMFLAG_RCON, "Start a replay from file. Usage: sm_playreplayfile <path>");
RegAdminCmd("sm_replayfileinfo", Command_ReplayFileInfo, ADMFLAG_RCON, "Prints a replay-file's header to console. Usage: sm_replayfileinfo <path>");
RegConsoleCmd("sm_replay", Command_Replay, "Opens the central bot menu. For admins: 'sm_replay stop' to stop the playback.");

// database
Expand Down Expand Up @@ -2886,6 +2888,75 @@ public void Shavit_OnWRDeleted(int style, int id, int track, int accountid, cons
DeleteReplay(style, track, accountid, mapname);
}

Action Command_ReplayFileInfo(int client, int args)
{
if(args == 0)
{
Shavit_PrintToChat(client, "%T", "ArgumentsMissing", client, "sm_replayfileinfo <path>");
return Plugin_Handled;
}

char sPath[PLATFORM_MAX_PATH];
GetCmdArgString(sPath, sizeof(sPath));

if(!FileExists(sPath))
{
Shavit_PrintToChat(client, "%T", "ReplayFileNotFound", client, sPath);
return Plugin_Handled;
}

replay_header_t aHeader;
File hFile = ReadReplayHeader(sPath, aHeader, -1, -1);
if(hFile == null)
{
Shavit_PrintToChat(client, "%T", "FailedToReadReplayHeader", client);
return Plugin_Handled;
}
delete hFile;

int iFileSize = FileSize(sPath);

PrintToConsole(client, "%T", "ReplayFileInfo", client, sPath, iFileSize,
aHeader.sReplayFormat, aHeader.iReplayVersion, aHeader.sMap, aHeader.iStyle, aHeader.iTrack, aHeader.iPreFrames, aHeader.iFrameCount,
aHeader.fTime, aHeader.iSteamID, aHeader.iPostFrames, aHeader.fTickrate, aHeader.fZoneOffset[0], aHeader.fZoneOffset[1], aHeader.iTimestamp);

return Plugin_Handled;
}

Action Command_PlayReplayFile(int client, int args)
{
if(args == 0)
{
Shavit_PrintToChat(client, "%T", "ArgumentsMissing", client, "sm_playreplayfile <path>");
return Plugin_Handled;
}

char sPath[PLATFORM_MAX_PATH];
GetCmdArgString(sPath, sizeof(sPath));

if(!FileExists(sPath))
{
Shavit_PrintToChat(client, "%T", "ReplayFileNotFound", client, sPath);
return Plugin_Handled;
}

replay_header_t aHeader;
File hFile = ReadReplayHeader(sPath, aHeader);
if(hFile == null)
{
Shavit_PrintToChat(client, "%T", "FailedToReadHeader", client);
return Plugin_Handled;
}
delete hFile;

if(Shavit_StartReplayFromFile(aHeader.iStyle, aHeader.iTrack, -1.0, client, -1, Replay_Dynamic, true, sPath) == 0)
{
Shavit_PrintToChat(client, "%T", "FailedToCreateReplay", client);
}

return Plugin_Handled;
}

public Action Command_DeleteReplay(int client, int args)
{
if(!IsValidClient(client))
Expand Down
8 changes: 4 additions & 4 deletions addons/sourcemod/scripting/shavit-replay-recorder.sp
Original file line number Diff line number Diff line change
Expand Up @@ -434,7 +434,7 @@ void DoReplaySaverCallbacks(int iSteamID, int client, int style, float time, int
if (gB_Floppy)
{
char headerbuf[512];
int headersize = WriteReplayHeaderToBuffer(headerbuf, style, track, time, iSteamID, gI_PlayerPrerunFrames[client], postframes, fZoneOffset, gI_PlayerFrames[client], gF_Tickrate, gS_Map);
int headersize = WriteReplayHeaderToBuffer(headerbuf, style, track, time, iSteamID, gI_PlayerPrerunFrames[client], postframes, fZoneOffset, gI_PlayerFrames[client], gF_Tickrate, gS_Map, timestamp);

SRCWRFloppy_AsyncSaveReplay(
FloppyAsynchronouslySavedMyReplayWhichWasNiceOfThem
Expand All @@ -456,7 +456,7 @@ void DoReplaySaverCallbacks(int iSteamID, int client, int style, float time, int
paths.GetString(i, path, sizeof(path));
FormatEx(tmp, sizeof(tmp), "%s.tmp", path);

if (SaveReplay(style, track, time, iSteamID, gI_PlayerPrerunFrames[client], playerrecording, gI_PlayerFrames[client], postframes, fZoneOffset, tmp))
if (SaveReplay(style, track, time, iSteamID, gI_PlayerPrerunFrames[client], playerrecording, gI_PlayerFrames[client], postframes, fZoneOffset, tmp, timestamp))
{
saved = true;
RenameFile(path, tmp);
Expand Down Expand Up @@ -577,7 +577,7 @@ public void Shavit_OnFinish(int client, int style, float time, int jumps, int st
}
}

bool SaveReplay(int style, int track, float time, int steamid, int preframes, ArrayList playerrecording, int iSize, int postframes, float fZoneOffset[2], const char[] sPath)
bool SaveReplay(int style, int track, float time, int steamid, int preframes, ArrayList playerrecording, int iSize, int postframes, float fZoneOffset[2], const char[] sPath, int timestamp)
{
File fReplay = null;

Expand All @@ -586,7 +586,7 @@ bool SaveReplay(int style, int track, float time, int steamid, int preframes, Ar
return false;
}

WriteReplayHeader(fReplay, style, track, time, steamid, preframes, postframes, fZoneOffset, iSize, gF_Tickrate, gS_Map);
WriteReplayHeader(fReplay, style, track, time, steamid, preframes, postframes, fZoneOffset, iSize, gF_Tickrate, gS_Map, timestamp);
WriteReplayFrames(playerrecording, iSize, fReplay);

delete fReplay;
Expand Down
14 changes: 14 additions & 0 deletions addons/sourcemod/translations/shavit-replay.phrases.txt
Original file line number Diff line number Diff line change
@@ -1,5 +1,19 @@
"Phrases"
{
"ReplayFileInfo"
{
"#format" "{1:s},{2:d},{3:s},{4:0x},{5:s},{6:d},{7:d},{8:d},{9:d},{10:f},{11:u},{12:d},{13:f},{14:f},{15:f},{16:d}"
"en" "File: {1} ({2} Bytes)\nFormat: {3}\nVersion: {4}\nMap: {5}\nStyle: {6}\nTrack: {7}\nPreframes: {8}\nFramecount: {9}\nTime: {10}\nAccountID: {11}\nPostframes: {12}\nTickrate: {13}\nZoneoffsets: {14}/{15}\nTimestamp: {16}"
}
"ReplayFileNotFound"
{
"#format" "{1:s}"
"en" "File not found. ({1})"
}
"FailedToReadReplayHeader"
{
"en" "Failed to read replay header."
}
// ---------- Menus ---------- //
"DeleteReplayAdminMenu"
{
Expand Down
Loading