Skip to content

Commit c77df2b

Browse files
authored
Add timestamp to header
- Added the timestamp of the record to the replay header - Added admin command !loadreplay to load and start a replay from a file - Added admin command !replayinfo to print the replay header from a file to the console
1 parent f00c88c commit c77df2b

4 files changed

Lines changed: 110 additions & 17 deletions

File tree

addons/sourcemod/scripting/include/shavit/replay-file.inc

Lines changed: 21 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -36,10 +36,11 @@
3636
// 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)
3737
// 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()`.
3838
// 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.
39+
// 0x0c: timestamp added
3940

4041
#define REPLAY_FORMAT_V2 "{SHAVITREPLAYFORMAT}{V2}"
4142
#define REPLAY_FORMAT_FINAL "{SHAVITREPLAYFORMAT}{FINAL}"
42-
#define REPLAY_FORMAT_SUBVERSION 0x0b
43+
#define REPLAY_FORMAT_SUBVERSION 0x0c
4344

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

@@ -57,6 +58,7 @@ enum struct replay_header_t
5758
int iPostFrames;
5859
float fTickrate;
5960
float fZoneOffset[2];
61+
int iTimestamp;
6062
}
6163

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

153155
while (!file.EndOfFile())
154156
{
155-
file.ReadLine(sLine, 320);
156-
int iStrings = ExplodeString(sLine, "|", sExplodedLine, 6, 64);
157+
file.ReadLine(sLine, sizeof(sLine));
158+
int iStrings = ExplodeString(sLine, "|", sExplodedLine, sizeof(sExplodedLine), sizeof(sExplodedLine[]));
157159

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

233235
char sHeader[64];
234236

235-
if(!file.ReadLine(sHeader, 64))
237+
if(!file.ReadLine(sHeader, sizeof(sHeader)))
236238
{
237239
delete file;
238240
return null;
239241
}
240242

241243
TrimString(sHeader);
242244
char sExplodedHeader[2][64];
243-
ExplodeString(sHeader, ":", sExplodedHeader, 2, 64);
245+
ExplodeString(sHeader, ":", sExplodedHeader, sizeof(sExplodedHeader), sizeof(sExplodedHeader[]));
244246

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

@@ -281,9 +283,9 @@ stock File ReadReplayHeader(const char[] path, replay_header_t header, int style
281283
else
282284
{
283285
char sAuthID[32];
284-
file.ReadString(sAuthID, 32);
285-
ReplaceString(sAuthID, 32, "[U:1:", "");
286-
ReplaceString(sAuthID, 32, "]", "");
286+
file.ReadString(sAuthID, sizeof(sAuthID));
287+
ReplaceString(sAuthID, sizeof(sAuthID), "[U:1:", "");
288+
ReplaceString(sAuthID, sizeof(sAuthID), "]", "");
287289
header.iSteamID = StringToInt(sAuthID);
288290
}
289291

@@ -303,6 +305,11 @@ stock File ReadReplayHeader(const char[] path, replay_header_t header, int style
303305
file.ReadInt32(view_as<int>(header.fZoneOffset[0]));
304306
file.ReadInt32(view_as<int>(header.fZoneOffset[1]));
305307
}
308+
309+
if (version >= 0x0c)
310+
{
311+
file.ReadInt32(view_as<int>(header.iTimestamp));
312+
}
306313
}
307314
else if(StrEqual(header.sReplayFormat, REPLAY_FORMAT_V2))
308315
{
@@ -343,7 +350,7 @@ stock File ReadReplayHeader(const char[] path, replay_header_t header, int style
343350
return file;
344351
}
345352

346-
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)
353+
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)
347354
{
348355
fFile.WriteLine("%d:" ... REPLAY_FORMAT_FINAL, REPLAY_FORMAT_SUBVERSION);
349356

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

362369
fFile.WriteInt32(view_as<int>(fZoneOffset[0]));
363370
fFile.WriteInt32(view_as<int>(fZoneOffset[1]));
371+
372+
fFile.WriteInt32(timestamp);
364373
}
365374

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

374-
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)
383+
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)
375384
{
376385
int pos = FormatEx(buf, 512, "%d:%s\n%s", REPLAY_FORMAT_SUBVERSION, REPLAY_FORMAT_FINAL, sMap);
377386
pos += 1; // skip past NUL
@@ -388,6 +397,8 @@ stock int WriteReplayHeaderToBuffer(char[] buf, int style, int track, float time
388397

389398
cell2buf(buf, pos, view_as<int>(fZoneOffset[0]));
390399
cell2buf(buf, pos, view_as<int>(fZoneOffset[1]));
400+
401+
cell2buf(buf, pos, timestamp);
391402

392403
return pos;
393404
}

addons/sourcemod/scripting/shavit-replay-playback.sp

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -483,6 +483,8 @@ public void OnPluginStart()
483483

484484
// commands
485485
RegAdminCmd("sm_deletereplay", Command_DeleteReplay, ADMFLAG_RCON, "Open replay deletion menu.");
486+
RegAdminCmd("sm_loadreplay", Command_LoadReplay, ADMFLAG_RCON, "Start a replay from file. Usage: sm_loadreplay <path>");
487+
RegAdminCmd("sm_replayinfo", Command_ReplayInfo, ADMFLAG_RCON, "Prints a replays header to console. Usage: sm_replayinfo <path>");
486488
RegConsoleCmd("sm_replay", Command_Replay, "Opens the central bot menu. For admins: 'sm_replay stop' to stop the playback.");
487489

488490
// database
@@ -2886,6 +2888,75 @@ public void Shavit_OnWRDeleted(int style, int id, int track, int accountid, cons
28862888
DeleteReplay(style, track, accountid, mapname);
28872889
}
28882890

2891+
Action Command_ReplayInfo(int client, int args)
2892+
{
2893+
if(args == 0)
2894+
{
2895+
Shavit_PrintToChat(client, "%T", "ArgumentsMissing", client, "sm_replayinfo <path>");
2896+
return Plugin_Handled;
2897+
}
2898+
2899+
char sPath[PLATFORM_MAX_PATH];
2900+
GetCmdArgString(sPath, sizeof(sPath));
2901+
2902+
if(!FileExists(sPath))
2903+
{
2904+
Shavit_PrintToChat(client, "%T", "ReplayFileNotFound", client, sPath);
2905+
return Plugin_Handled;
2906+
}
2907+
2908+
replay_header_t aHeader;
2909+
File hFile = ReadReplayHeader(sPath, aHeader, -1, -1);
2910+
if(hFile == null)
2911+
{
2912+
Shavit_PrintToChat(client, "%T", "FailedToReadHeader", client);
2913+
return Plugin_Handled;
2914+
}
2915+
delete hFile;
2916+
2917+
int iFileSize = FileSize(sPath);
2918+
2919+
PrintToConsole(client, "%T", "ReplayInfo", client, sPath, iFileSize,
2920+
aHeader.sReplayFormat, aHeader.iReplayVersion, aHeader.sMap, aHeader.iStyle, aHeader.iTrack, aHeader.iPreFrames, aHeader.iFrameCount,
2921+
aHeader.fTime, aHeader.iSteamID, aHeader.iPostFrames, aHeader.fTickrate, aHeader.fZoneOffset[0], aHeader.fZoneOffset[1], aHeader.iTimestamp);
2922+
2923+
return Plugin_Handled;
2924+
}
2925+
2926+
Action Command_LoadReplay(int client, int args)
2927+
{
2928+
if(args == 0)
2929+
{
2930+
Shavit_PrintToChat(client, "%T", "ArgumentsMissing", client, "sm_loadreplay <path>");
2931+
return Plugin_Handled;
2932+
}
2933+
2934+
char sPath[PLATFORM_MAX_PATH];
2935+
GetCmdArgString(sPath, sizeof(sPath));
2936+
2937+
if(!FileExists(sPath))
2938+
{
2939+
Shavit_PrintToChat(client, "%T", "ReplayFileNotFound", client, sPath);
2940+
return Plugin_Handled;
2941+
}
2942+
2943+
replay_header_t aHeader;
2944+
File hFile = ReadReplayHeader(sPath, aHeader);
2945+
if(hFile == null)
2946+
{
2947+
Shavit_PrintToChat(client, "%T", "FailedToReadHeader", client);
2948+
return Plugin_Handled;
2949+
}
2950+
delete hFile;
2951+
2952+
if(Shavit_StartReplayFromFile(aHeader.iStyle, aHeader.iTrack, -1.0, client, -1, Replay_Dynamic, true, sPath) == 0)
2953+
{
2954+
Shavit_PrintToChat(client, "%T", "FailedToCreateReplay", client);
2955+
}
2956+
2957+
return Plugin_Handled;
2958+
}
2959+
28892960
public Action Command_DeleteReplay(int client, int args)
28902961
{
28912962
if(!IsValidClient(client))

addons/sourcemod/scripting/shavit-replay-recorder.sp

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -434,7 +434,7 @@ void DoReplaySaverCallbacks(int iSteamID, int client, int style, float time, int
434434
if (gB_Floppy)
435435
{
436436
char headerbuf[512];
437-
int headersize = WriteReplayHeaderToBuffer(headerbuf, style, track, time, iSteamID, gI_PlayerPrerunFrames[client], postframes, fZoneOffset, gI_PlayerFrames[client], gF_Tickrate, gS_Map);
437+
int headersize = WriteReplayHeaderToBuffer(headerbuf, style, track, time, iSteamID, gI_PlayerPrerunFrames[client], postframes, fZoneOffset, gI_PlayerFrames[client], gF_Tickrate, gS_Map, timestamp);
438438

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

459-
if (SaveReplay(style, track, time, iSteamID, gI_PlayerPrerunFrames[client], playerrecording, gI_PlayerFrames[client], postframes, fZoneOffset, tmp))
459+
if (SaveReplay(style, track, time, iSteamID, gI_PlayerPrerunFrames[client], playerrecording, gI_PlayerFrames[client], postframes, fZoneOffset, tmp, timestamp))
460460
{
461461
saved = true;
462462
RenameFile(path, tmp);
@@ -494,13 +494,11 @@ void FloppyAsynchronouslySavedMyReplayWhichWasNiceOfThem(bool saved, any value)
494494
int postframes = dp.ReadCell();
495495
char sName[MAX_NAME_LENGTH];
496496
dp.ReadString(sName, sizeof(sName));
497-
delete dp;
498497

499498
if (!saved)
500499
{
501500
LogError("Failed to save replay... Skipping OnReplaySaved");
502501
delete playerrecording; // importante!
503-
delete paths;
504502
return;
505503
}
506504

@@ -527,7 +525,6 @@ void FloppyAsynchronouslySavedMyReplayWhichWasNiceOfThem(bool saved, any value)
527525
Call_Finish();
528526

529527
delete playerrecording;
530-
delete paths;
531528
}
532529

533530
public void Shavit_OnFinish(int client, int style, float time, int jumps, int strafes, float sync, int track, float oldtime, float perfs, float avgvel, float maxvel, int timestamp)
@@ -577,7 +574,7 @@ public void Shavit_OnFinish(int client, int style, float time, int jumps, int st
577574
}
578575
}
579576

580-
bool SaveReplay(int style, int track, float time, int steamid, int preframes, ArrayList playerrecording, int iSize, int postframes, float fZoneOffset[2], const char[] sPath)
577+
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)
581578
{
582579
File fReplay = null;
583580

@@ -586,7 +583,7 @@ bool SaveReplay(int style, int track, float time, int steamid, int preframes, Ar
586583
return false;
587584
}
588585

589-
WriteReplayHeader(fReplay, style, track, time, steamid, preframes, postframes, fZoneOffset, iSize, gF_Tickrate, gS_Map);
586+
WriteReplayHeader(fReplay, style, track, time, steamid, preframes, postframes, fZoneOffset, iSize, gF_Tickrate, gS_Map, timestamp);
590587
WriteReplayFrames(playerrecording, iSize, fReplay);
591588

592589
delete fReplay;

addons/sourcemod/translations/shavit-replay.phrases.txt

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,19 @@
11
"Phrases"
22
{
3+
"ReplayInfo"
4+
{
5+
"#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}"
6+
"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}"
7+
}
8+
"ReplayFileNotFound"
9+
{
10+
"#format" "{1:s}"
11+
"en" "File not found. ({1})"
12+
}
13+
"FailedToReadHeader"
14+
{
15+
"en" "Failed to read replay header."
16+
}
317
// ---------- Menus ---------- //
418
"DeleteReplayAdminMenu"
519
{

0 commit comments

Comments
 (0)