diff --git a/addons/sourcemod/scripting/include/shavit.inc b/addons/sourcemod/scripting/include/shavit.inc index 7e30f51c..0506284f 100644 --- a/addons/sourcemod/scripting/include/shavit.inc +++ b/addons/sourcemod/scripting/include/shavit.inc @@ -590,9 +590,10 @@ forward Action Shavit_OnFinishPre(int client, timer_snapshot_t snapshot); * @param perfs Perfect jump percentage (0.0 to 100.0) or 100.0 when not measured. * @param avgvel Player's average velocity throughout the run. * @param maxvel Player's highest reached velocity. + * @param timestamp System time of when player finished. * @noreturn */ -forward 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); +forward 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); /** * Like Shavit_OnFinish, but after the insertion query was called. @@ -611,9 +612,10 @@ forward void Shavit_OnFinish(int client, int style, float time, int jumps, int s * @param perfs Perfect jump percentage (0.0 to 100.0) or 100.0 when not measured. * @param avgvel Player's average velocity throughout the run. * @param maxvel Player's highest reached velocity. + * @param timestamp System time of when player finished. * @noreturn */ -forward void Shavit_OnFinish_Post(int client, int style, float time, int jumps, int strafes, float sync, int rank, int overwrite, int track, float oldtime, float perfs, float avgvel, float maxvel); +forward void Shavit_OnFinish_Post(int client, int style, float time, int jumps, int strafes, float sync, int rank, int overwrite, int track, float oldtime, float perfs, float avgvel, float maxvel, int timestamp); /** * Called when there's a new WR on the map. @@ -630,9 +632,10 @@ forward void Shavit_OnFinish_Post(int client, int style, float time, int jumps, * @param perfs Perfect jump percentage (0.0 to 100.0) or 100.0 when not measured. * @param avgvel Player's average velocity throughout the run. * @param maxvel Player's highest reached velocity. + * @param timestamp System time of when player finished. * @noreturn */ -forward void Shavit_OnWorldRecord(int client, int style, float time, int jumps, int strafes, float sync, int track, float oldwr, float oldtime, float perfs, float avgvel, float maxvel); +forward void Shavit_OnWorldRecord(int client, int style, float time, int jumps, int strafes, float sync, int track, float oldwr, float oldtime, float perfs, float avgvel, float maxvel, int timestamp); /** * Called when an admin deletes a WR. @@ -789,9 +792,10 @@ forward Action Shavit_OnStageMessage(int client, int stageNumber, char[] message * @param perfs Perfect jump percentage (0.0 to 100.0) or 100.0 when not measured. * @param avgvel Player's average velocity throughout the run. * @param maxvel Player's highest reached velocity. + * @param timestamp System time of when player finished. * @noreturn */ -forward void Shavit_OnWorstRecord(int client, int style, float time, int jumps, int strafes, float sync, int track, float oldtime, float perfs, float avgvel, float maxvel); +forward void Shavit_OnWorstRecord(int client, int style, float time, int jumps, int strafes, float sync, int track, float oldtime, float perfs, float avgvel, float maxvel, int timestamp); /** * Gets called when a map's tier is assigned. @@ -839,6 +843,50 @@ forward void Shavit_OnReplayEnd(int client); */ forward void Shavit_OnReplaysLoaded(); +/** + * Called when a player finishes a time. Allows you to save a replay even if the run is not a WR. + * + * @param client Client index. + * @param style Style the record was done on. + * @param time Record time. + * @param jumps Jumps amount. + * @param strafes Amount of strafes. + * @param sync Sync percentage (0.0 to 100.0) or -1.0 when not measured. + * @param track Timer track. + * @param oldtime The player's best time on the map before this finish. + * @param perfs Perfect jump percentage (0.0 to 100.0) or 100.0 when not measured. + * @param avgvel Player's average velocity throughout the run. + * @param maxvel Player's highest reached velocity. + * @param timestamp System time of when player finished. + * @param isWR If the time is the new WR. + * @param isTooLong If the time is too long to save a replay if the time is a WR. + * @return Return Plugin_Changed (or higher) to cause a copy of the replay to be saved. Return Plugin_Continue otherwise. + */ +forward Action Shavit_ShouldSaveReplayCopy(int client, int style, float time, int jumps, int strafes, float sync, int track, float oldtime, float perfs, float avgvel, float maxvel, int timestamp, bool isWR, bool isTooLong); + +/** + * Called when either a WR replay or a copy of a replay has been saved. + * + * @param client Client index. + * @param style Style the record was done on. + * @param time Record time. + * @param jumps Jumps amount. + * @param strafes Amount of strafes. + * @param sync Sync percentage (0.0 to 100.0) or -1.0 when not measured. + * @param track Timer track. + * @param oldtime The player's best time on the map before this finish. + * @param perfs Perfect jump percentage (0.0 to 100.0) or 100.0 when not measured. + * @param avgvel Player's average velocity throughout the run. + * @param maxvel Player's highest reached velocity. + * @param timestamp System time of when player finished. + * @param isWR If the time is the new WR. + * @param isTooLong If the time is too long to save a replay if the time is a WR. + * @param isCopy If the path points to a copy of the replay. + * @param path Path to the saved replay. + * @noreturn + */ +forward void Shavit_OnReplaySaved(int client, int style, float time, int jumps, int strafes, float sync, int track, float oldtime, float perfs, float avgvel, float maxvel, int timestamp, bool isWR, bool isTooLong, bool isCopy, const char[] path); + /** * Called when top left HUD updates. * diff --git a/addons/sourcemod/scripting/shavit-core.sp b/addons/sourcemod/scripting/shavit-core.sp index 53d86b6f..7084c2c3 100644 --- a/addons/sourcemod/scripting/shavit-core.sp +++ b/addons/sourcemod/scripting/shavit-core.sp @@ -266,7 +266,7 @@ public void OnPluginStart() gH_Forwards_Stop = CreateGlobalForward("Shavit_OnStop", ET_Event, Param_Cell, Param_Cell); gH_Forwards_StopPre = CreateGlobalForward("Shavit_OnStopPre", ET_Event, Param_Cell, Param_Cell); gH_Forwards_FinishPre = CreateGlobalForward("Shavit_OnFinishPre", ET_Event, Param_Cell, Param_Array); - gH_Forwards_Finish = CreateGlobalForward("Shavit_OnFinish", ET_Event, Param_Cell, Param_Cell, Param_Cell, Param_Cell, Param_Cell, Param_Cell, Param_Cell, Param_Cell, Param_Cell, Param_Cell, Param_Cell); + gH_Forwards_Finish = CreateGlobalForward("Shavit_OnFinish", ET_Event, Param_Cell, Param_Cell, Param_Cell, Param_Cell, Param_Cell, Param_Cell, Param_Cell, Param_Cell, Param_Cell, Param_Cell, Param_Cell, Param_Cell); gH_Forwards_OnRestart = CreateGlobalForward("Shavit_OnRestart", ET_Event, Param_Cell, Param_Cell); gH_Forwards_OnEnd = CreateGlobalForward("Shavit_OnEnd", ET_Event, Param_Cell, Param_Cell); gH_Forwards_OnPause = CreateGlobalForward("Shavit_OnPause", ET_Event, Param_Cell, Param_Cell); @@ -1738,6 +1738,7 @@ public int Native_FinishMap(Handle handler, int numParams) Call_PushCell(snapshot.fMaxVelocity); } + Call_PushCell(GetTime()); Call_Finish(); StopTimer(client); diff --git a/addons/sourcemod/scripting/shavit-replay.sp b/addons/sourcemod/scripting/shavit-replay.sp index 6facc866..93e3f605 100644 --- a/addons/sourcemod/scripting/shavit-replay.sp +++ b/addons/sourcemod/scripting/shavit-replay.sp @@ -130,6 +130,8 @@ bool gB_Late = false; Handle gH_OnReplayStart = null; Handle gH_OnReplayEnd = null; Handle gH_OnReplaysLoaded = null; +Handle gH_ShouldSaveReplayCopy = null; +Handle gH_OnReplaySaved = null; // server specific float gF_Tickrate = 0.0; @@ -244,9 +246,11 @@ public void OnPluginStart() LoadTranslations("shavit-replay.phrases"); // forwards - gH_OnReplayStart = CreateGlobalForward("Shavit_OnReplayStart", ET_Event, Param_Cell, Param_Cell, Param_Cell, Param_Cell); - gH_OnReplayEnd = CreateGlobalForward("Shavit_OnReplayEnd", ET_Event, Param_Cell, Param_Cell, Param_Cell, Param_Cell); + gH_OnReplayStart = CreateGlobalForward("Shavit_OnReplayStart", ET_Event, Param_Cell); + gH_OnReplayEnd = CreateGlobalForward("Shavit_OnReplayEnd", ET_Event, Param_Cell); gH_OnReplaysLoaded = CreateGlobalForward("Shavit_OnReplaysLoaded", ET_Event); + gH_ShouldSaveReplayCopy = CreateGlobalForward("Shavit_ShouldSaveReplayCopy", ET_Event, Param_Cell, Param_Cell, Param_Cell, Param_Cell, Param_Cell, Param_Cell, Param_Cell, Param_Cell, Param_Cell, Param_Cell, Param_Cell, Param_Cell, Param_Cell, Param_Cell); + gH_OnReplaySaved = CreateGlobalForward("Shavit_OnReplaySaved", ET_Ignore, Param_Cell, Param_Cell, Param_Cell, Param_Cell, Param_Cell, Param_Cell, Param_Cell, Param_Cell, Param_Cell, Param_Cell, Param_Cell, Param_Cell, Param_Cell, Param_Cell, Param_Cell, Param_String); // game specific gEV_Type = GetEngineVersion(); @@ -967,6 +971,14 @@ public void OnMapStart() CreateDirectory(gS_ReplayFolder, 511); } + char sPath[PLATFORM_MAX_PATH]; + FormatEx(sPath, PLATFORM_MAX_PATH, "%s/copy", gS_ReplayFolder); + + if(!DirExists(sPath)) + { + CreateDirectory(sPath, 511); + } + for(int i = 0; i < gI_Styles; i++) { gI_ReplayTick[i] = -1; @@ -978,7 +990,6 @@ public void OnMapStart() continue; } - char sPath[PLATFORM_MAX_PATH]; FormatEx(sPath, PLATFORM_MAX_PATH, "%s/%d", gS_ReplayFolder, i); if(!DirExists(sPath)) @@ -1276,21 +1287,8 @@ bool LoadReplay(int style, int track, const char[] path) return false; } - -bool SaveReplay(int style, int track, float time, int steamid, char[] name, int preframes, ArrayList playerrecording, int timerstartframe) +void WriteReplayHeader(File fFile, int style, int track, float time, int steamid, int preframes, int timerstartframe, int iSize) { - char sTrack[4]; - FormatEx(sTrack, 4, "_%d", track); - - char sPath[PLATFORM_MAX_PATH]; - FormatEx(sPath, PLATFORM_MAX_PATH, "%s/%d/%s%s.replay", gS_ReplayFolder, style, gS_Map, (track > 0)? sTrack:""); - - if(FileExists(sPath)) - { - DeleteFile(sPath); - } - - File fFile = OpenFile(sPath, "wb"); fFile.WriteLine("%d:" ... REPLAY_FORMAT_FINAL, REPLAY_FORMAT_SUBVERSION); fFile.WriteString(gS_Map, true); @@ -1298,30 +1296,68 @@ bool SaveReplay(int style, int track, float time, int steamid, char[] name, int fFile.WriteInt8(track); fFile.WriteInt32(timerstartframe - preframes); - int iSize = playerrecording.Length; fFile.WriteInt32(iSize - preframes); fFile.WriteInt32(view_as(time)); fFile.WriteInt32(steamid); +} + +void SaveReplay(int style, int track, float time, int steamid, char[] name, int preframes, ArrayList playerrecording, int timerstartframe, int timestamp, bool saveCopy, bool saveWR, char[] outPath, int outPathLen) +{ + char sTrack[4]; + FormatEx(sTrack, 4, "_%d", track); + + File fWR = null; + File fCopy = null; + + if (saveWR) + { + FormatEx(outPath, outPathLen, "%s/%d/%s%s.replay", gS_ReplayFolder, style, gS_Map, (track > 0)? sTrack:""); + DeleteFile(outPath); + fWR = OpenFile(outPath, "wb"); + } + + if (saveCopy) + { + FormatEx(outPath, outPathLen, "%s/copy/%d_%d_%s.replay", gS_ReplayFolder, timestamp, steamid, gS_Map); + DeleteFile(outPath); + fCopy = OpenFile(outPath, "wb"); + } + + int iSize = playerrecording.Length; + + if (saveWR) + { + WriteReplayHeader(fWR, style, track, time, steamid, preframes, timerstartframe, iSize); + + // How did I trigger this? + if(gA_Frames[style][track] == null) + { + gA_Frames[style][track] = new ArrayList(CELLS_PER_FRAME); + } + else + { + gA_Frames[style][track].Clear(); + } + } + + if (saveCopy) + { + WriteReplayHeader(fCopy, style, track, time, steamid, preframes, timerstartframe, iSize); + } any aFrameData[CELLS_PER_FRAME]; any aWriteData[CELLS_PER_FRAME * FRAMES_PER_WRITE]; int iFramesWritten = 0; - // How did I trigger this? - if(gA_Frames[style][track] == null) - { - gA_Frames[style][track] = new ArrayList(CELLS_PER_FRAME); - } - else - { - gA_Frames[style][track].Clear(); - } - - for(int i = preframes; i < iSize; i++) { playerrecording.GetArray(i, aFrameData, CELLS_PER_FRAME); - gA_Frames[style][track].PushArray(aFrameData); + + if (saveWR) + { + gA_Frames[style][track].PushArray(aFrameData); + } + for(int j = 0; j < CELLS_PER_FRAME; j++) { aWriteData[(CELLS_PER_FRAME * iFramesWritten) + j] = aFrameData[j]; @@ -1329,21 +1365,32 @@ bool SaveReplay(int style, int track, float time, int steamid, char[] name, int if(++iFramesWritten == FRAMES_PER_WRITE || i == iSize - 1) { - fFile.Write(aWriteData, CELLS_PER_FRAME * iFramesWritten, 4); + if (saveWR) + { + fWR.Write(aWriteData, CELLS_PER_FRAME * iFramesWritten, 4); + } + if (saveCopy) + { + fCopy.Write(aWriteData, CELLS_PER_FRAME * iFramesWritten, 4); + } iFramesWritten = 0; } } - delete fFile; + delete fWR; + delete fCopy; + + if (!saveWR) + { + return; + } gA_FrameCache[style][track].iFrameCount = iSize - preframes; gA_FrameCache[style][track].fTime = time; gA_FrameCache[style][track].bNewFormat = true; strcopy(gA_FrameCache[style][track].sReplayName, MAX_NAME_LENGTH, name); gA_FrameCache[style][track].iPreFrames = timerstartframe - preframes; - - return true; } bool DeleteReplay(int style, int track, bool unload_replay = false, int accountid = 0) @@ -1761,52 +1808,90 @@ public void Shavit_OnLeaveZone(int client, int type, int track, int id, int enti } } -public void Shavit_OnFinish(int client, int style, float time, int jumps, int strafes, float sync, int track) +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) { - if(Shavit_IsPracticeMode(client)) - { - return; - } - - if(!gCV_Enabled.BoolValue || (gCV_TimeLimit.FloatValue > 0.0 && time > gCV_TimeLimit.FloatValue)) + if(Shavit_IsPracticeMode(client) || !gCV_Enabled.BoolValue || gI_PlayerFrames[client] == 0) { return; } + bool isTooLong = (gCV_TimeLimit.FloatValue > 0.0 && time > gCV_TimeLimit.FloatValue); + bool isWR = true; + float length = GetReplayLength(style, track); if(gA_FrameCache[style][track].bNewFormat) { if(length > 0.0 && time > length) { - return; + isWR = false; } } - else { float wrtime = Shavit_GetWorldRecord(style, track); if(wrtime != 0.0 && time > wrtime) { - return; + isWR = false; } } - if(gI_PlayerFrames[client] == 0) + Action action = Plugin_Continue; + Call_StartForward(gH_ShouldSaveReplayCopy); + Call_PushCell(client); + Call_PushCell(style); + Call_PushCell(time); + Call_PushCell(jumps); + Call_PushCell(strafes); + Call_PushCell(sync); + Call_PushCell(track); + Call_PushCell(oldtime); + Call_PushCell(perfs); + Call_PushCell(avgvel); + Call_PushCell(maxvel); + Call_PushCell(timestamp); + Call_PushCell(isTooLong); + Call_PushCell(isWR); + Call_Finish(action); + + bool makeCopy = (action != Plugin_Continue); + bool makeWR = (isWR && !isTooLong); + + if (!makeCopy && !makeWR) { return; } - + int iSteamID = GetSteamAccountID(client); char sName[MAX_NAME_LENGTH]; GetClientName(client, sName, MAX_NAME_LENGTH); ReplaceString(sName, MAX_NAME_LENGTH, "#", "?"); - SaveReplay(style, track, time, iSteamID, sName, gI_PlayerPrerunFrames[client], gA_PlayerFrames[client], gI_PlayerTimerStartFrames[client]); + char path[PLATFORM_MAX_PATH]; + SaveReplay(style, track, time, iSteamID, sName, gI_PlayerPrerunFrames[client], gA_PlayerFrames[client], gI_PlayerTimerStartFrames[client], timestamp, makeCopy, makeWR, path, sizeof(path)); - if(ReplayEnabled(style)) + Call_StartForward(gH_OnReplaySaved); + Call_PushCell(client); + Call_PushCell(style); + Call_PushCell(time); + Call_PushCell(jumps); + Call_PushCell(strafes); + Call_PushCell(sync); + Call_PushCell(track); + Call_PushCell(oldtime); + Call_PushCell(perfs); + Call_PushCell(avgvel); + Call_PushCell(maxvel); + Call_PushCell(timestamp); + Call_PushCell(isTooLong); + Call_PushCell(isWR); + Call_PushCell(makeCopy); + Call_PushString(path); + Call_Finish(); + + if(makeWR && ReplayEnabled(style)) { if(gCV_CentralBot.BoolValue && gA_CentralCache.iStyle == style && gA_CentralCache.iTrack == track) { diff --git a/addons/sourcemod/scripting/shavit-wr.sp b/addons/sourcemod/scripting/shavit-wr.sp index ea38ac44..f34a6e34 100644 --- a/addons/sourcemod/scripting/shavit-wr.sp +++ b/addons/sourcemod/scripting/shavit-wr.sp @@ -133,10 +133,10 @@ public void OnPluginStart() #endif // forwards - gH_OnWorldRecord = CreateGlobalForward("Shavit_OnWorldRecord", ET_Event, Param_Cell, Param_Cell, Param_Cell, Param_Cell, Param_Cell, Param_Cell, Param_Cell, Param_Cell, Param_Cell, Param_Cell, Param_Cell, Param_Cell); - gH_OnFinish_Post = CreateGlobalForward("Shavit_OnFinish_Post", ET_Event, Param_Cell, Param_Cell, Param_Cell, Param_Cell, Param_Cell, Param_Cell, Param_Cell, Param_Cell, Param_Cell, Param_Cell, Param_Cell, Param_Cell, Param_Cell); + gH_OnWorldRecord = CreateGlobalForward("Shavit_OnWorldRecord", ET_Event, Param_Cell, Param_Cell, Param_Cell, Param_Cell, Param_Cell, Param_Cell, Param_Cell, Param_Cell, Param_Cell, Param_Cell, Param_Cell, Param_Cell, Param_Cell); + gH_OnFinish_Post = CreateGlobalForward("Shavit_OnFinish_Post", ET_Event, Param_Cell, Param_Cell, Param_Cell, Param_Cell, Param_Cell, Param_Cell, Param_Cell, Param_Cell, Param_Cell, Param_Cell, Param_Cell, Param_Cell, Param_Cell, Param_Cell); gH_OnWRDeleted = CreateGlobalForward("Shavit_OnWRDeleted", ET_Event, Param_Cell, Param_Cell, Param_Cell, Param_Cell); - gH_OnWorstRecord = CreateGlobalForward("Shavit_OnWorstRecord", ET_Event, Param_Cell, Param_Cell, Param_Cell, Param_Cell, Param_Cell, Param_Cell, Param_Cell, Param_Cell, Param_Cell, Param_Cell, Param_Cell); + gH_OnWorstRecord = CreateGlobalForward("Shavit_OnWorstRecord", ET_Event, Param_Cell, Param_Cell, Param_Cell, Param_Cell, Param_Cell, Param_Cell, Param_Cell, Param_Cell, Param_Cell, Param_Cell, Param_Cell, Param_Cell); gH_OnFinishMessage = CreateGlobalForward("Shavit_OnFinishMessage", ET_Event, Param_Cell, Param_CellByRef, Param_Array, Param_Cell, Param_Cell, Param_String, Param_Cell); // player commands @@ -1978,7 +1978,7 @@ public void SQL_CreateTable_Callback(Database db, DBResultSet results, const cha OnMapStart(); } -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) +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) { // do not risk overwriting the player's data if their PB isn't loaded to cache yet if(!gA_WRCache[client].bLoadedCache) @@ -2035,6 +2035,7 @@ public void Shavit_OnFinish(int client, int style, float time, int jumps, int st Call_PushCell(perfs); Call_PushCell(avgvel); Call_PushCell(maxvel); + Call_PushCell(timestamp); Call_Finish(); #if defined DEBUG @@ -2058,6 +2059,7 @@ public void Shavit_OnFinish(int client, int style, float time, int jumps, int st Call_PushCell(perfs); Call_PushCell(avgvel); Call_PushCell(maxvel); + Call_PushCell(timestamp); Call_Finish(); } @@ -2087,7 +2089,7 @@ public void Shavit_OnFinish(int client, int style, float time, int jumps, int st FormatEx(sQuery, 512, "INSERT INTO %splayertimes (auth, map, time, jumps, date, style, strafes, sync, points, track, perfs) VALUES (%d, '%s', %f, %d, %d, %d, %d, %.2f, 0.0, %d, %.2f);", - gS_MySQLPrefix, iSteamID, gS_Map, time, jumps, GetTime(), style, strafes, sync, track, perfs); + gS_MySQLPrefix, iSteamID, gS_Map, time, jumps, timestamp, style, strafes, sync, track, perfs); } else // update @@ -2097,7 +2099,7 @@ public void Shavit_OnFinish(int client, int style, float time, int jumps, int st FormatEx(sQuery, 512, "UPDATE %splayertimes SET time = %f, jumps = %d, date = %d, strafes = %d, sync = %.02f, points = 0.0, perfs = %.2f WHERE map = '%s' AND auth = %d AND style = %d AND track = %d;", - gS_MySQLPrefix, time, jumps, GetTime(), strafes, sync, perfs, gS_Map, iSteamID, style, track); + gS_MySQLPrefix, time, jumps, timestamp, strafes, sync, perfs, gS_Map, iSteamID, style, track); } gH_SQL.Query(SQL_OnFinish_Callback, sQuery, GetClientSerial(client), DBPrio_High); @@ -2116,6 +2118,7 @@ public void Shavit_OnFinish(int client, int style, float time, int jumps, int st Call_PushCell(perfs); Call_PushCell(avgvel); Call_PushCell(maxvel); + Call_PushCell(timestamp); Call_Finish(); }