diff --git a/addons/sourcemod/configs/shavit-styles.cfg b/addons/sourcemod/configs/shavit-styles.cfg index 8c4c68cb..8f322b43 100644 --- a/addons/sourcemod/configs/shavit-styles.cfg +++ b/addons/sourcemod/configs/shavit-styles.cfg @@ -60,6 +60,7 @@ "special" "0" // For third-party modules. The core plugins will not need this setting. "specialstring" "" // For modularity. Separated with semicolon. Built-in flags: "segments". "permission" "" // Permission required. Syntax: "flag;override". For example "p;style_tas" to require the 'p' flag or the "style_tas" override. + "ordering" "0" // Ordering in menus where styles appear. If this value is not present, style ID will be used instead. } "1" diff --git a/addons/sourcemod/scripting/include/shavit.inc b/addons/sourcemod/scripting/include/shavit.inc index d266ab6f..fdd4b7f9 100644 --- a/addons/sourcemod/scripting/include/shavit.inc +++ b/addons/sourcemod/scripting/include/shavit.inc @@ -23,7 +23,7 @@ #endif #define _shavit_included -#define SHAVIT_VERSION "2.4.0" +#define SHAVIT_VERSION "2.4.1" #define STYLE_LIMIT 256 #define MAX_ZONES 64 #define MAX_NAME_LENGTH_SQL 32 @@ -156,6 +156,7 @@ enum struct stylesettings_t bool bStrafeCountD; float fRankingMultiplier; int iSpecial; + int iOrdering; } enum struct chatstrings_t @@ -546,6 +547,18 @@ forward void Shavit_OnWorstRecord(int client, int style, float time, int jumps, */ forward void Shavit_OnTierAssigned(const char[] map, int tier); +/** + * Gets called when the server acknowledges the client's ranking status. + * It is called after OnClientPostAdminCheck and at forced rank recalculations. + * + * @param client Client index. + * @param rank Client's rank. (0 if unranked or unassigned) + * @param points Client's points. (0.0 if unranked or unassigned) + * @param first True if the forward is called after the initial connection, false if it is caused by recalculation. + * @noreturn + */ +forward void Shavit_OnRankAssigned(int client, int rank, float points, bool first); + /** * Called when replay playback starts. * @@ -652,6 +665,18 @@ native void Shavit_Replay_DeleteMap(const char[] map); */ native void Shavit_Rankings_DeleteMap(const char[] map); +/** + * Changes a player's bhop style. + * + * @param client Client index. + * @param style Style. + * @param force Ignore style permissions. + * @param manual Is it a manual style change? (Was it caused by user interaction?) + * @param noforward Bypasses the call to `Shavit_OnStyleChanged`. + * @return False if failed due to lack of access, true otherwise. + */ +native bool Shavit_ChangeClientStyle(int client, int style, bool force = false, bool manual = false, bool noforward = false); + /** * Finishes the map for a player, with their current timer stats. * Will not teleport the player to anywhere, it's handled inside the mapzones plugin. @@ -762,6 +787,13 @@ native void Shavit_GetWRTime(int style, float &time, int track); */ native float Shavit_GetWorldRecord(int style, int track); +/** + * Reloads WR leaderboards cache for the current map. + * + * @noreturn + */ +native void Shavit_ReloadLeaderboards(); + /** * Saves the WR's record ID for the current map on a variable. * Unused in base plugins, as of pre-1.4b. @@ -878,6 +910,17 @@ native void Shavit_PauseTimer(int client); */ native void Shavit_ResumeTimer(int client); +/** + * Deletes the specified replay file. + * Replay data will be unloaded if necessary. + * + * @param map Map display name. + * @param style Bhop style. + * @param track Timer track. + * @return true if replay existed, false otherwise. + */ +native bool Shavit_DeleteReplay(const char[] map, int style, int track); + /** * Retrieves the engine time of the replay bot's first frame. * @@ -1063,6 +1106,15 @@ native int Shavit_GetStyleStrings(int style, int stringtype, char[] StyleStrings */ native int Shavit_GetStyleCount(); +/** + * Gets an array with style IDs in their configured menu ordering as specified in the styles config. + * + * @param arr Reference to array to fill with style IDs. + * @param size Array size. + * @noreturn + */ +native void Shavit_GetOrderedStyles(int[] arr, int size); + /** * Saves chat related strings on string references. * @@ -1212,6 +1264,14 @@ native StringMap Shavit_GetMapTiers(); */ native bool Shavit_HasStyleAccess(int client, int style); +/** + * Determines whether a client's timer is paused or not. + * + * @param client Client index. + * @return Boolean value. + */ +native bool Shavit_IsPaused(int client); + /** * Use this native when printing anything in chat if it's related to the timer. * This native will auto-assign colors and a chat prefix. @@ -1265,6 +1325,8 @@ public SharedPlugin __pl_shavit = #if !defined REQUIRE_PLUGIN public void __pl_shavit_SetNTVOptional() { + MarkNativeAsOptional("Shavit_ChangeClientStyle"); + MarkNativeAsOptional("Shavit_DeleteReplay"); MarkNativeAsOptional("Shavit_FinishMap"); MarkNativeAsOptional("Shavit_ForceHUDUpdate"); MarkNativeAsOptional("Shavit_FormatChat"); @@ -1279,6 +1341,7 @@ public void __pl_shavit_SetNTVOptional() MarkNativeAsOptional("Shavit_GetHUDSettings"); MarkNativeAsOptional("Shavit_GetMapTier"); MarkNativeAsOptional("Shavit_GetMapTiers"); + MarkNativeAsOptional("Shavit_GetOrderedStyles"); MarkNativeAsOptional("Shavit_GetPerfectJumps"); MarkNativeAsOptional("Shavit_GetPlayerPB"); MarkNativeAsOptional("Shavit_GetPoints"); @@ -1315,6 +1378,7 @@ public void __pl_shavit_SetNTVOptional() MarkNativeAsOptional("Shavit_InsideZone"); MarkNativeAsOptional("Shavit_IsClientCreatingZone"); MarkNativeAsOptional("Shavit_IsKZMap"); + MarkNativeAsOptional("Shavit_IsPaused"); MarkNativeAsOptional("Shavit_IsPracticeMode"); MarkNativeAsOptional("Shavit_IsReplayDataLoaded"); MarkNativeAsOptional("Shavit_LoadSnapshot"); @@ -1323,6 +1387,7 @@ public void __pl_shavit_SetNTVOptional() MarkNativeAsOptional("Shavit_PauseTimer"); MarkNativeAsOptional("Shavit_PrintToChat"); MarkNativeAsOptional("Shavit_Rankings_DeleteMap"); + MarkNativeAsOptional("Shavit_ReloadLeaderboards"); MarkNativeAsOptional("Shavit_ReloadReplay"); MarkNativeAsOptional("Shavit_ReloadReplays"); MarkNativeAsOptional("Shavit_Replay_DeleteMap"); diff --git a/addons/sourcemod/scripting/shavit-core.sp b/addons/sourcemod/scripting/shavit-core.sp index bf00d882..63990a3a 100644 --- a/addons/sourcemod/scripting/shavit-core.sp +++ b/addons/sourcemod/scripting/shavit-core.sp @@ -131,6 +131,7 @@ ConVar sv_enablebunnyhopping = null; // timer settings bool gB_Registered = false; int gI_Styles = 0; +int gI_OrderedStyles[STYLE_LIMIT]; stylestrings_t gS_StyleStrings[STYLE_LIMIT]; stylesettings_t gA_StyleSettings[STYLE_LIMIT]; @@ -142,6 +143,8 @@ bool gB_StopChatSound = false; bool gB_HookedJump = false; char gS_LogPath[PLATFORM_MAX_PATH]; char gS_DeleteMap[MAXPLAYERS+1][160]; +char gS_WipePlayerID[MAXPLAYERS+1][32]; +char gS_Verification[MAXPLAYERS+1][16]; // flags int gI_StyleFlag[STYLE_LIMIT]; @@ -161,6 +164,7 @@ public Plugin myinfo = public APLRes AskPluginLoad2(Handle myself, bool late, char[] error, int err_max) { + CreateNative("Shavit_ChangeClientStyle", Native_ChangeClientStyle); CreateNative("Shavit_FinishMap", Native_FinishMap); CreateNative("Shavit_GetBhopStyle", Native_GetBhopStyle); CreateNative("Shavit_GetChatStrings", Native_GetChatStrings); @@ -170,6 +174,7 @@ public APLRes AskPluginLoad2(Handle myself, bool late, char[] error, int err_max CreateNative("Shavit_GetDatabase", Native_GetDatabase); CreateNative("Shavit_GetDB", Native_GetDB); CreateNative("Shavit_GetGameType", Native_GetGameType); + CreateNative("Shavit_GetOrderedStyles", Native_GetOrderedStyles); CreateNative("Shavit_GetPerfectJumps", Native_GetPerfectJumps); CreateNative("Shavit_GetStrafeCount", Native_GetStrafeCount); CreateNative("Shavit_GetStyleCount", Native_GetStyleCount); @@ -180,6 +185,7 @@ public APLRes AskPluginLoad2(Handle myself, bool late, char[] error, int err_max CreateNative("Shavit_GetTimerStatus", Native_GetTimerStatus); CreateNative("Shavit_HasStyleAccess", Native_HasStyleAccess); CreateNative("Shavit_IsKZMap", Native_IsKZMap); + CreateNative("Shavit_IsPaused", Native_IsPaused); CreateNative("Shavit_IsPracticeMode", Native_IsPracticeMode); CreateNative("Shavit_LoadSnapshot", Native_LoadSnapshot); CreateNative("Shavit_LogMessage", Native_LogMessage); @@ -222,6 +228,7 @@ public void OnPluginStart() gH_Forwards_OnTimerIncrementPost = CreateGlobalForward("Shavit_OnTimeIncrementPost", ET_Event, Param_Cell, Param_Cell, Param_Array); LoadTranslations("shavit-core.phrases"); + LoadTranslations("shavit-common.phrases"); // game types gEV_Type = GetEngineVersion(); @@ -297,6 +304,7 @@ public void OnPluginStart() // admin RegAdminCmd("sm_deletemap", Command_DeleteMap, ADMFLAG_ROOT, "Deletes all map data. Usage: sm_deletemap "); + RegAdminCmd("sm_wipeplayer", Command_WipePlayer, ADMFLAG_BAN, "Wipes all bhoptimer data for specified player. Usage: sm_wipeplayer "); // commands END // logs @@ -470,7 +478,10 @@ public Action Command_StartTimer(int client, int args) else { - Shavit_PrintToChat(client, "%T", "StartZoneUndefined", client, gS_ChatStrings.sWarning, gS_ChatStrings.sText); + char sTrack[32]; + GetTrackName(client, track, sTrack, 32); + + Shavit_PrintToChat(client, "%T", "StartZoneUndefined", client, gS_ChatStrings.sWarning, gS_ChatStrings.sText, gS_ChatStrings.sVariable2, sTrack, gS_ChatStrings.sText); } return Plugin_Handled; @@ -636,6 +647,214 @@ public Action Command_DeleteMap(int client, int args) return Plugin_Handled; } +public Action Command_WipePlayer(int client, int args) +{ + if(args == 0) + { + ReplyToCommand(client, "Usage: sm_wipeplayer \nAfter entering a SteamID, you will be prompted with a verification captcha."); + + return Plugin_Handled; + } + + char sArgString[32]; + GetCmdArgString(sArgString, 32); + + if(strlen(gS_Verification[client]) == 0 || !StrEqual(sArgString, gS_Verification[client])) + { + char sAlphabet[] = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890!@#"; + + for(int i = 0; i < GetRandomInt(5, 7); i++) + { + gS_Verification[client][i] = sAlphabet[GetRandomInt(0, sizeof(sAlphabet))]; + } + + strcopy(gS_WipePlayerID[client], 32, sArgString); + + Shavit_PrintToChat(client, "Preparing to delete all user data for SteamID %s%s%s. To confirm, enter %s!wipeplayer %s", + gS_ChatStrings.sVariable, sArgString, gS_ChatStrings.sText, gS_ChatStrings.sVariable2, gS_Verification[client]); + } + + else + { + int iLength = ((strlen(gS_WipePlayerID[client]) * 2) + 1); + char[] sEscapedAuthID = new char[iLength]; + gH_SQL.Escape(gS_WipePlayerID[client], sEscapedAuthID, iLength); + + Shavit_PrintToChat(client, "Deleting data for SteamID %s%s%s...", + gS_ChatStrings.sVariable, sEscapedAuthID, gS_ChatStrings.sText); + + DeleteUserData(client, sEscapedAuthID); + + strcopy(gS_Verification[client], 32, ""); + strcopy(gS_WipePlayerID[client], 32, ""); + } + + return Plugin_Handled; +} + +void DeleteUserData(int client, const char[] sAuthID3) +{ + if(gB_Replay) + { + char sQueryGetWorldRecords[256]; + FormatEx(sQueryGetWorldRecords, 256, + "SELECT map, id, style, track FROM %splayertimes WHERE auth = '%s' GROUP BY map, style, track;", + gS_MySQLPrefix, sAuthID3); + + DataPack pack = new DataPack(); + pack.WriteCell(client); + pack.WriteString(sAuthID3); + + gH_SQL.Query(SQL_DeleteUserData_GetRecords_Callback, sQueryGetWorldRecords, pack, DBPrio_High); + } + + else + { + char sQueryDeleteUserTimes[256]; + FormatEx(sQueryDeleteUserTimes, 256, + "DELETE FROM %splayertimes WHERE auth = '%s';", + gS_MySQLPrefix, sAuthID3); + + DataPack steamPack = new DataPack(); + steamPack.WriteString(sAuthID3); + steamPack.WriteCell(client); + + gH_SQL.Query(SQL_DeleteUserTimes_Callback, sQueryDeleteUserTimes, steamPack, DBPrio_High); + } +} + +public void SQL_DeleteUserData_GetRecords_Callback(Database db, DBResultSet results, const char[] error, any data) +{ + DataPack pack = view_as(data); + pack.Reset(); + int client = pack.ReadCell(); + + char sAuthID3[32]; + pack.ReadString(sAuthID3, 32); + delete pack; + + if(results == null) + { + LogError("Timer error! Failed to wipe user data (wipe | get player records). Reason: %s", error); + + return; + } + + Transaction trans = new Transaction(); + + while(results.FetchRow()) + { + char map[160]; + results.FetchString(0, map, 160); + + int id = results.FetchInt(1); + int style = results.FetchInt(2); + int track = results.FetchInt(3); + + char sQueryGetWorldRecordID[256]; + FormatEx(sQueryGetWorldRecordID, 256, + "SELECT id FROM %splayertimes WHERE map = '%s' AND style = %d AND track = %d ORDER BY time LIMIT 1;", + gS_MySQLPrefix, map, style, track); + + DataPack transPack = new DataPack(); + transPack.WriteString(map); + transPack.WriteCell(id); + transPack.WriteCell(style); + transPack.WriteCell(track); + + trans.AddQuery(sQueryGetWorldRecordID, transPack); + } + + DataPack steamPack = new DataPack(); + steamPack.WriteString(sAuthID3); + steamPack.WriteCell(client); + + gH_SQL.Execute(trans, Trans_OnRecordCompare, INVALID_FUNCTION, steamPack, DBPrio_High); +} + +public void Trans_OnRecordCompare(Database db, any data, int numQueries, DBResultSet[] results, any[] queryData) +{ + DataPack pack = view_as(data); + pack.Reset(); + char sAuthID3[32]; + pack.ReadString(sAuthID3, 32); + + for(int i = 0; i < numQueries; i++) + { + DataPack hQueryPack = view_as(queryData[i]); + hQueryPack.Reset(); + char sMap[32]; + hQueryPack.ReadString(sMap, 32); + + int iRecordID = hQueryPack.ReadCell(); + int iStyle = hQueryPack.ReadCell(); + int iTrack = hQueryPack.ReadCell(); + delete hQueryPack; + + if(results[i] != null && results[i].FetchRow()) + { + int iWR = results[i].FetchInt(0); + + if(iWR == iRecordID) + { + Shavit_DeleteReplay(sMap, iStyle, iTrack); + } + } + } + + char sQueryDeleteUserTimes[256]; + FormatEx(sQueryDeleteUserTimes, 256, + "DELETE FROM %splayertimes WHERE auth = '%s';", + gS_MySQLPrefix, sAuthID3); + + gH_SQL.Query(SQL_DeleteUserTimes_Callback, sQueryDeleteUserTimes, pack, DBPrio_High); +} + +public void SQL_DeleteUserTimes_Callback(Database db, DBResultSet results, const char[] error, any data) +{ + DataPack pack = view_as(data); + pack.Reset(); + char sAuthID3[32]; + pack.ReadString(sAuthID3, 32); + + if(results == null) + { + LogError("Timer error! Failed to wipe user data (wipe | delete user times). Reason: %s", error); + + delete pack; + + return; + } + + char sQueryDeleteUsers[256]; + FormatEx(sQueryDeleteUsers, 256, "DELETE FROM %susers WHERE auth = '%s';", + gS_MySQLPrefix, sAuthID3); + + gH_SQL.Query(SQL_DeleteUserData_Callback, sQueryDeleteUsers, pack, DBPrio_High); +} + +public void SQL_DeleteUserData_Callback(Database db, DBResultSet results, const char[] error, any data) +{ + DataPack pack = view_as(data); + pack.Reset(); + char sAuthID3[32]; + pack.ReadString(sAuthID3, 32); + int client = pack.ReadCell(); + delete pack; + + if(results == null) + { + LogError("Timer error! Failed to wipe user data (wipe | delete user data, id %s). Reason: %s", error, sAuthID3); + + return; + } + + Shavit_ReloadLeaderboards(); + + Shavit_PrintToChat(client, "Finished wiping timer data for user %s%s%s.", + gS_ChatStrings.sVariable, sAuthID3, gS_ChatStrings.sText); +} + public Action Command_AutoBhop(int client, int args) { if(!IsValidClient(client)) @@ -683,14 +902,16 @@ public Action Command_Style(int client, int args) for(int i = 0; i < gI_Styles; i++) { + int iStyle = gI_OrderedStyles[i]; + char sInfo[8]; - IntToString(i, sInfo, 8); + IntToString(iStyle, sInfo, 8); char sDisplay[64]; - if(gA_StyleSettings[i].bUnranked) + if(gA_StyleSettings[iStyle].bUnranked) { - FormatEx(sDisplay, 64, "%T %s", "StyleUnranked", client, gS_StyleStrings[i].sStyleName); + FormatEx(sDisplay, 64, "%T %s", "StyleUnranked", client, gS_StyleStrings[iStyle].sStyleName); } else @@ -699,7 +920,7 @@ public Action Command_Style(int client, int args) if(gB_WR) { - time = Shavit_GetWorldRecord(i, Track_Main); + time = Shavit_GetWorldRecord(iStyle, Track_Main); } if(time > 0.0) @@ -707,16 +928,16 @@ public Action Command_Style(int client, int args) char sTime[32]; FormatSeconds(time, sTime, 32, false); - FormatEx(sDisplay, 64, "%s - WR: %s", gS_StyleStrings[i].sStyleName, sTime); + FormatEx(sDisplay, 64, "%s - WR: %s", gS_StyleStrings[iStyle].sStyleName, sTime); } else { - strcopy(sDisplay, 64, gS_StyleStrings[i].sStyleName); + strcopy(sDisplay, 64, gS_StyleStrings[iStyle].sStyleName); } } - menu.AddItem(sInfo, sDisplay, (gA_Timers[client].iStyle == i || !Shavit_HasStyleAccess(client, i))? ITEMDRAW_DISABLED:ITEMDRAW_DEFAULT); + menu.AddItem(sInfo, sDisplay, (gA_Timers[client].iStyle == iStyle || !Shavit_HasStyleAccess(client, iStyle))? ITEMDRAW_DISABLED:ITEMDRAW_DEFAULT); } // should NEVER happen @@ -933,6 +1154,11 @@ public int Native_GetGameType(Handle handler, int numParams) return view_as(gEV_Type); } +public int Native_GetOrderedStyles(Handle handler, int numParams) +{ + return SetNativeArray(1, gI_OrderedStyles, GetNativeCell(2)); +} + public int Native_GetDatabase(Handle handler, int numParams) { return view_as(CloneHandle(gH_SQL, handler)); @@ -1009,6 +1235,34 @@ public int Native_StopTimer(Handle handler, int numParams) Call_Finish(); } +public int Native_ChangeClientStyle(Handle handler, int numParams) +{ + int client = GetNativeCell(1); + int style = GetNativeCell(2); + bool force = view_as(GetNativeCell(3)); + bool manual = view_as(GetNativeCell(4)); + bool noforward = view_as(GetNativeCell(5)); + + if(force || Shavit_HasStyleAccess(client, style)) + { + if(noforward) + { + gA_Timers[client].iStyle = style; + + UpdateStyleSettings(client); + } + + else + { + CallOnStyleChanged(client, gA_Timers[client].iStyle, style, manual); + } + + return true; + } + + return false; +} + public int Native_FinishMap(Handle handler, int numParams) { int client = GetNativeCell(1); @@ -1101,19 +1355,26 @@ public int Native_PrintToChat(Handle handler, int numParams) { int client = GetNativeCell(1); - if(!IsClientInGame(client)) - { - gB_StopChatSound = false; - - return; - } - static int iWritten = 0; // useless? char sBuffer[300]; FormatNativeString(0, 2, 3, 300, iWritten, sBuffer); Format(sBuffer, 300, "%s %s%s", gS_ChatStrings.sPrefix, gS_ChatStrings.sText, sBuffer); + if(client == 0) + { + PrintToServer("%s", sBuffer); + + return false; + } + + if(!IsClientInGame(client)) + { + gB_StopChatSound = false; + + return false; + } + Handle hSayText2 = StartMessageOne("SayText2", client, USERMSG_RELIABLE|USERMSG_BLOCKHOOKS); if(gB_Protobuf) @@ -1144,6 +1405,8 @@ public int Native_PrintToChat(Handle handler, int numParams) EndMessage(); gB_StopChatSound = false; + + return true; } public int Native_RestartTimer(Handle handler, int numParams) @@ -1240,6 +1503,11 @@ public int Native_SetPracticeMode(Handle handler, int numParams) gA_Timers[client].bPracticeMode = practice; } +public int Native_IsPaused(Handle handler, int numParams) +{ + return view_as(gA_Timers[GetNativeCell(1)].bPaused); +} + public int Native_IsPracticeMode(Handle handler, int numParams) { return view_as(gA_Timers[GetNativeCell(1)].bPracticeMode); @@ -1618,6 +1886,7 @@ bool LoadStyles() gA_StyleSettings[i].bStrafeCountD = view_as(kv.GetNum("strafe_count_d", true)); gA_StyleSettings[i].fRankingMultiplier = kv.GetFloat("rankingmultiplier", 1.00); gA_StyleSettings[i].iSpecial = kv.GetNum("special", 0); + gA_StyleSettings[i].iOrdering = kv.GetNum("ordering", i); if(!gB_Registered && strlen(gS_StyleStrings[i].sChangeCommand) > 0) { @@ -1656,7 +1925,7 @@ bool LoadStyles() strcopy(gS_StyleOverride[i], 32, (iCount >= 2)? sText[1]:""); } - i++; + gI_OrderedStyles[i] = i++; } while(kv.GotoNextKey()); @@ -1666,6 +1935,8 @@ bool LoadStyles() gI_Styles = i; gB_Registered = true; + SortCustom1D(gI_OrderedStyles, gI_Styles, SortAscending_StyleOrder); + Call_StartForward(gH_Forwards_OnStyleConfigLoaded); Call_PushCell(gI_Styles); Call_Finish(); @@ -1673,6 +1944,27 @@ bool LoadStyles() return true; } +public int SortAscending_StyleOrder(int index1, int index2, const int[] array, any hndl) +{ + int order1 = gA_StyleSettings[index1].iOrdering; + int order2 = gA_StyleSettings[index2].iOrdering; + + if(order1 < order2) + { + return -1; + } + + else if(order1 == order2) + { + return 0; + } + + else + { + return 1; + } +} + public Action Command_StyleChange(int client, int args) { char sCommand[128]; @@ -2397,3 +2689,17 @@ void UpdateStyleSettings(int client) SetEntityGravity(client, view_as(gA_StyleSettings[gA_Timers[client].iStyle].fGravityMultiplier)); } + +void GetTrackName(int client, int track, char[] output, int size) +{ + if(track < 0 || track >= TRACKS_SIZE) + { + FormatEx(output, size, "%T", "Track_Unknown", client); + + return; + } + + static char sTrack[16]; + FormatEx(sTrack, 16, "Track_%d", track); + FormatEx(output, size, "%T", sTrack, client); +} diff --git a/addons/sourcemod/scripting/shavit-hud.sp b/addons/sourcemod/scripting/shavit-hud.sp index 60ca06a5..bebb260f 100644 --- a/addons/sourcemod/scripting/shavit-hud.sp +++ b/addons/sourcemod/scripting/shavit-hud.sp @@ -39,6 +39,8 @@ #define HUD2_STYLE (1 << 5) #define HUD2_RANK (1 << 6) #define HUD2_TRACK (1 << 7) +#define HUD2_SPLITPB (1 << 8) +#define HUD2_MAPTIER (1 << 9) #define HUD_DEFAULT (HUD_MASTER|HUD_CENTER|HUD_ZONEHUD|HUD_OBSERVE|HUD_TOPLEFT|HUD_SYNC|HUD_TIMELEFT|HUD_2DVEL|HUD_SPECTATORS) #define HUD_DEFAULT2 0 @@ -83,6 +85,7 @@ EngineVersion gEV_Type = Engine_Unknown; bool gB_Replay = false; bool gB_Zones = false; bool gB_Sounds = false; +bool gB_Rankings = false; bool gB_BhopStats = false; // cache @@ -90,6 +93,7 @@ int gI_Cycle = 0; color_t gI_Gradient; int gI_GradientDirection = -1; int gI_Styles = 0; +char gS_Map[160]; Handle gH_HUDCookie = null; Handle gH_HUDCookieMain = null; @@ -171,6 +175,7 @@ public void OnPluginStart() gB_Replay = LibraryExists("shavit-replay"); gB_Zones = LibraryExists("shavit-zones"); gB_Sounds = LibraryExists("shavit-sounds"); + gB_Rankings = LibraryExists("shavit-rankings"); gB_BhopStats = LibraryExists("bhopstats"); // HUD handle @@ -231,6 +236,9 @@ public void OnPluginStart() public void OnMapStart() { + GetCurrentMap(gS_Map, 160); + GetMapDisplayName(gS_Map, gS_Map, 160); + if(gB_Late) { Shavit_OnStyleConfigLoaded(-1); @@ -255,6 +263,11 @@ public void OnLibraryAdded(const char[] name) gB_Sounds = true; } + else if(StrEqual(name, "shavit-rankings")) + { + gB_Rankings = true; + } + else if(StrEqual(name, "bhopstats")) { gB_BhopStats = true; @@ -278,6 +291,11 @@ public void OnLibraryRemoved(const char[] name) gB_Sounds = false; } + else if(StrEqual(name, "shavit-rankings")) + { + gB_Rankings = false; + } + else if(StrEqual(name, "bhopstats")) { gB_BhopStats = false; @@ -645,6 +663,17 @@ Action ShowHUDMenu(int client, int item) FormatEx(sHudItem, 64, "%T", "HudTrackText", client); menu.AddItem(sInfo, sHudItem); + FormatEx(sInfo, 16, "@%d", HUD2_SPLITPB); + FormatEx(sHudItem, 64, "%T", "HudSplitPbText", client); + menu.AddItem(sInfo, sHudItem); + + if(gB_Rankings) + { + FormatEx(sInfo, 16, "@%d", HUD2_MAPTIER); + FormatEx(sHudItem, 64, "%T", "HudMapTierText", client); + menu.AddItem(sInfo, sHudItem); + } + menu.ExitButton = true; menu.DisplayAt(client, item, 60); @@ -966,7 +995,23 @@ int AddHUDToBuffer_Source2013(int client, huddata_t data, char[] buffer, int max if((gI_HUDSettings[client] & HUD_ZONEHUD) > 0 && data.iZoneHUD != ZoneHUD_None) { - FormatEx(sLine, 128, "%T ", (data.iZoneHUD == ZoneHUD_Start)? "HudInStartZone":"HudInEndZone", client, data.iSpeed); + if(gB_Rankings && (gI_HUD2Settings[client] & HUD2_MAPTIER) == 0) + { + FormatEx(sLine, 128, "%T", "HudZoneTier", client, Shavit_GetMapTier(gS_Map)); + AddHUDLine(buffer, maxlen, sLine, iLines); + iLines++; + } + + if(data.iZoneHUD == ZoneHUD_Start) + { + FormatEx(sLine, 128, "%T ", "HudInStartZone", client, data.iSpeed); + } + + else + { + FormatEx(sLine, 128, "%T ", "HudInEndZone", client, data.iSpeed); + } + AddHUDLine(buffer, maxlen, sLine, iLines); return ++iLines; @@ -1095,6 +1140,7 @@ int AddHUDToBuffer_CSGO(int client, huddata_t data, char[] buffer, int maxlen) { FormatEx(sLine, 128, "%d u/s", data.iSpeed); AddHUDLine(buffer, maxlen, sLine, iLines); + iLines++; } } @@ -1115,8 +1161,24 @@ int AddHUDToBuffer_CSGO(int client, huddata_t data, char[] buffer, int maxlen) char sZoneHUD[64]; FormatEx(sZoneHUD, 64, "", ((gI_Gradient.r << 16) + (gI_Gradient.g << 8) + (gI_Gradient.b))); StrCat(buffer, maxlen, sZoneHUD); + + if(data.iZoneHUD == ZoneHUD_Start) + { + if(gB_Rankings && (gI_HUD2Settings[client] & HUD2_MAPTIER) == 0) + { + FormatEx(sZoneHUD, 32, "%T\n\n", "HudZoneTier", client, Shavit_GetMapTier(gS_Map)); + AddHUDLine(buffer, maxlen, sZoneHUD, iLines); + iLines++; + } + + FormatEx(sZoneHUD, 64, "%T", "HudInStartZoneCSGO", client, data.iSpeed); + } + + else + { + FormatEx(sZoneHUD, 64, "%T", "HudInEndZoneCSGO", client, data.iSpeed); + } - FormatEx(sZoneHUD, 64, "%T", (data.iZoneHUD == ZoneHUD_Start)? "HudInStartZoneCSGO":"HudInEndZoneCSGO", client, data.iSpeed); StrCat(buffer, maxlen, sZoneHUD); return ++iLines; @@ -1219,7 +1281,7 @@ int AddHUDToBuffer_CSGO(int client, huddata_t data, char[] buffer, int maxlen) StrCat(buffer, maxlen, ""); return iLines; -} +} void UpdateMainHUD(int client) { @@ -1262,7 +1324,7 @@ void UpdateMainHUD(int client) if(iReplayStyle != -1) { - fReplayTime = (Shavit_GetReplayTime(iReplayStyle, iReplayTrack) * gA_StyleSettings[iReplayStyle].fTimescale); + fReplayTime = Shavit_GetReplayTime(iReplayStyle, iReplayTrack); fReplayLength = Shavit_GetReplayLength(iReplayStyle, iReplayTrack); } } @@ -1492,21 +1554,35 @@ void UpdateTopLeftHUD(int client, bool wait) char sWRName[MAX_NAME_LENGTH]; Shavit_GetWRName(style, sWRName, MAX_NAME_LENGTH, track); - float fPBTime = Shavit_GetClientPB(target, style, track); - - char sPBTime[16]; - FormatSeconds(fPBTime, sPBTime, MAX_NAME_LENGTH); - char sTopLeft[128]; + FormatEx(sTopLeft, 128, "WR: %s (%s)", sWRTime, sWRName); - if(fPBTime != 0.0) + float fTargetPB = Shavit_GetClientPB(target, style, track); + char sTargetPB[64]; + FormatSeconds(fTargetPB, sTargetPB, 64); + Format(sTargetPB, 64, "%T: %s", "HudBestText", client, sTargetPB); + + float fSelfPB = Shavit_GetClientPB(client, style, track); + char sSelfPB[64]; + FormatSeconds(fSelfPB, sSelfPB, 64); + Format(sSelfPB, 64, "%T: %s", "HudBestText", client, sSelfPB); + + if((gI_HUD2Settings[client] & HUD2_SPLITPB) == 0 && target != client) { - FormatEx(sTopLeft, 128, "WR: %s (%s)\n%T: %s (#%d)", sWRTime, sWRName, "HudBestText", client, sPBTime, Shavit_GetRankForTime(style, fPBTime, track)); + if(fTargetPB != 0.0) + { + Format(sTopLeft, 128, "%s\n%s (%N)", sTopLeft, sTargetPB, target); + } + + if(fSelfPB != 0.0) + { + Format(sTopLeft, 128, "%s\n%s (%N)", sTopLeft, sSelfPB, client); + } } - else + else if(fSelfPB != 0.0) { - FormatEx(sTopLeft, 128, "WR: %s (%s)", sWRTime, sWRName); + Format(sTopLeft, 128, "%s\n%s (#%d)", sTopLeft, sSelfPB, Shavit_GetRankForTime(style, fSelfPB, track)); } SetHudTextParams(0.01, 0.01, 2.5, 255, 255, 255, 255, 0, 0.0, 0.0, 0.0); diff --git a/addons/sourcemod/scripting/shavit-misc.sp b/addons/sourcemod/scripting/shavit-misc.sp index 29f0c7c7..b71318a2 100644 --- a/addons/sourcemod/scripting/shavit-misc.sp +++ b/addons/sourcemod/scripting/shavit-misc.sp @@ -286,7 +286,7 @@ public void OnPluginStart() gCV_AdvertisementInterval = CreateConVar("shavit_misc_advertisementinterval", "600.0", "Interval between each chat advertisement.\nConfiguration file for those is configs/shavit-advertisements.cfg.\nSet to 0.0 to disable.\nRequires server restart for changes to take effect.", 0, true, 0.0); gCV_Checkpoints = CreateConVar("shavit_misc_checkpoints", "1", "Allow players to save and teleport to checkpoints.", 0, true, 0.0, true, 1.0); gCV_RemoveRagdolls = CreateConVar("shavit_misc_removeragdolls", "1", "Remove ragdolls after death?\n0 - Disabled\n1 - Only remove replay bot ragdolls.\n2 - Remove all ragdolls.", 0, true, 0.0, true, 2.0); - gCV_ClanTag = CreateConVar("shavit_misc_clantag", "{tr}{styletag} :: {time}", "Custom clantag for players.\n0 - Disabled\n{styletag} - style settings from shavit-styles.cfg.\n{style} - style name.\n{time} - formatted time.\n{tr} - first letter of track, if not default.", 0); + gCV_ClanTag = CreateConVar("shavit_misc_clantag", "{tr}{styletag} :: {time}", "Custom clantag for players.\n0 - Disabled\n{styletag} - style tag.\n{style} - style name.\n{time} - formatted time.\n{tr} - first letter of track.\n{rank} - player rank.", 0); gCV_DropAll = CreateConVar("shavit_misc_dropall", "1", "Allow all weapons to be dropped?\n0 - Disabled\n1 - Enabled", 0, true, 0.0, true, 1.0); gCV_ResetTargetname = CreateConVar("shavit_misc_resettargetname", "0", "Reset the player's targetname upon timer start?\nRecommended to leave disabled. Enable via per-map configs when necessary.\n0 - Disabled\n1 - Enabled", 0, true, 0.0, true, 1.0); gCV_RestoreStates = CreateConVar("shavit_misc_restorestates", "0", "Save the players' timer/position etc.. when they die/change teams,\nand load the data when they spawn?\n0 - Disabled\n1 - Enabled", 0, true, 0.0, true, 1.0); @@ -873,12 +873,20 @@ void UpdateClanTag(int client) GetTrackName(client, track, sTrack, 3); } + char sRank[8]; + + if(gB_Rankings) + { + IntToString(Shavit_GetRank(client), sRank, 8); + } + char sCustomTag[32]; strcopy(sCustomTag, 32, sTag); ReplaceString(sCustomTag, 32, "{style}", gS_StyleStrings[gI_Style[client]].sStyleName); ReplaceString(sCustomTag, 32, "{styletag}", gS_StyleStrings[gI_Style[client]].sClanTag); ReplaceString(sCustomTag, 32, "{time}", sTime); ReplaceString(sCustomTag, 32, "{tr}", sTrack); + ReplaceString(sCustomTag, 32, "{rank}", sRank); CS_SetClientClanTag(client, sCustomTag); } @@ -1454,13 +1462,10 @@ public Action Command_Save(int client, int args) } } - else + else if(SaveCheckpoint(client, index, bOverflow)) { - if(SaveCheckpoint(client, index, bOverflow)) - { - gA_CheckpointsCache[client].iCurrentCheckpoint = (bOverflow)? iMaxCPs:++gA_CheckpointsCache[client].iCheckpoints; - Shavit_PrintToChat(client, "%T", "MiscCheckpointsSaved", client, gA_CheckpointsCache[client].iCurrentCheckpoint, gS_ChatStrings.sVariable, gS_ChatStrings.sText); - } + gA_CheckpointsCache[client].iCurrentCheckpoint = (bOverflow)? iMaxCPs:++gA_CheckpointsCache[client].iCheckpoints; + Shavit_PrintToChat(client, "%T", "MiscCheckpointsSaved", client, gA_CheckpointsCache[client].iCurrentCheckpoint, gS_ChatStrings.sVariable, gS_ChatStrings.sText); } return Plugin_Handled; @@ -1591,8 +1596,10 @@ public int MenuHandler_Checkpoints(Menu menu, MenuAction action, int param1, int return 0; } - SaveCheckpoint(param1, ++gA_CheckpointsCache[param1].iCheckpoints); - gA_CheckpointsCache[param1].iCurrentCheckpoint = gA_CheckpointsCache[param1].iCheckpoints; + if(SaveCheckpoint(param1, gA_CheckpointsCache[param1].iCheckpoints + 1)) + { + gA_CheckpointsCache[param1].iCurrentCheckpoint = ++gA_CheckpointsCache[param1].iCheckpoints; + } } else @@ -1692,6 +1699,13 @@ bool SaveCheckpoint(int client, int index, bool overflow = false) return false; } + else if(Shavit_IsPaused(client) || Shavit_IsPaused(target)) + { + Shavit_PrintToChat(client, "%T", "CommandNoPause", client, gS_ChatStrings.sVariable, gS_ChatStrings.sText); + + return false; + } + char sKey[32]; int iSerial = GetClientSerial(client); FormatEx(sKey, 32, "%d_%d", iSerial, index); @@ -1893,6 +1907,13 @@ void TeleportToCheckpoint(int client, int index, bool suppressMessage) return; } + else if(Shavit_IsPaused(client)) + { + Shavit_PrintToChat(client, "%T", "CommandNoPause", client, gS_ChatStrings.sVariable, gS_ChatStrings.sText); + + return; + } + float pos[3]; CopyArray(cpcache.fPosition, pos, 3); @@ -1916,6 +1937,7 @@ void TeleportToCheckpoint(int client, int index, bool suppressMessage) timer_snapshot_t snapshot; CopyArray(cpcache.aSnapshot, snapshot, sizeof(timer_snapshot_t)); Shavit_LoadSnapshot(client, snapshot); + Shavit_ResumeTimer(client); float ang[3]; CopyArray(cpcache.fAngles, ang, 3); @@ -1936,6 +1958,22 @@ void TeleportToCheckpoint(int client, int index, bool suppressMessage) vel = NULL_VECTOR; } + if(cpcache.iTargetname != -1) + { + char sTargetname[64]; + gA_Targetnames.GetString(cpcache.iTargetname, sTargetname, 64); + + SetEntPropString(client, Prop_Data, "m_iName", sTargetname); + } + + if(cpcache.iClassname != -1) + { + char sClassname[64]; + gA_Classnames.GetString(cpcache.iClassname, sClassname, 64); + + SetEntPropString(client, Prop_Data, "m_iClassname", sClassname); + } + TeleportEntity(client, pos, ((gI_CheckpointsSettings[client] & CP_ANGLES) > 0 || cpcache.bSegmented)? ang:NULL_VECTOR, vel); @@ -1958,26 +1996,6 @@ void TeleportToCheckpoint(int client, int index, bool suppressMessage) SetEntPropFloat(client, Prop_Send, "m_flLaggedMovementValue", cpcache.fSpeed); SetEntPropEnt(client, Prop_Data, "m_hGroundEntity", cpcache.iGroundEntity); - int iTargetname = gA_Targetnames.FindValue(cpcache.iTargetname); - - if(iTargetname != -1) - { - char sTargetname[64]; - gA_Targetnames.GetString(iTargetname, sTargetname, 64); - - SetEntPropString(client, Prop_Data, "m_iName", sTargetname); - } - - int iClassname = gA_Classnames.FindValue(cpcache.iClassname); - - if(iClassname != -1) - { - char sClassname[64]; - gA_Classnames.GetString(iClassname, sClassname, 64); - - SetEntPropString(client, Prop_Data, "m_iClassname", sClassname); - } - if(gEV_Type != Engine_TF2) { SetEntPropFloat(client, Prop_Send, "m_flStamina", cpcache.fStamina); @@ -2703,6 +2721,7 @@ void LoadState(int client) Shavit_LoadSnapshot(client, gA_SaveStates[client]); Shavit_SetPracticeMode(client, gB_SaveStatesSegmented[client], false); + Shavit_ResumeTimer(client); if(gB_Replay && gA_SaveFrames[client] != null) { diff --git a/addons/sourcemod/scripting/shavit-rankings.sp b/addons/sourcemod/scripting/shavit-rankings.sp index 20fa4a1c..f77c6542 100644 --- a/addons/sourcemod/scripting/shavit-rankings.sp +++ b/addons/sourcemod/scripting/shavit-rankings.sp @@ -73,6 +73,7 @@ int gI_RankedPlayers = 0; Menu gH_Top100Menu = null; Handle gH_Forwards_OnTierAssigned = null; +Handle gH_Forwards_OnRankAssigned = null; // Timer settings. chatstrings_t gS_ChatStrings; @@ -127,6 +128,7 @@ public void OnAllPluginsLoaded() public void OnPluginStart() { gH_Forwards_OnTierAssigned = CreateGlobalForward("Shavit_OnTierAssigned", ET_Event, Param_String, Param_Cell); + gH_Forwards_OnRankAssigned = CreateGlobalForward("Shavit_OnRankAssigned", ET_Event, Param_Cell, Param_Cell, Param_Cell, Param_Cell); RegConsoleCmd("sm_tier", Command_Tier, "Prints the map's tier to chat."); RegConsoleCmd("sm_maptier", Command_Tier, "Prints the map's tier to chat. (sm_tier alias)"); @@ -387,7 +389,7 @@ public void OnClientPostAdminCheck(int client) { if(!IsFakeClient(client)) { - UpdatePlayerRank(client); + UpdatePlayerRank(client, true); } } @@ -705,7 +707,7 @@ public void Trans_OnRecalcSuccess(Database db, any data, int numQueries, DBResul { if(IsClientInGame(i) && IsClientAuthorized(i)) { - UpdatePlayerRank(i); + UpdatePlayerRank(i, false); } } @@ -780,7 +782,9 @@ void UpdateAllPoints() #endif char sQuery[128]; - FormatEx(sQuery, 128, "UPDATE %susers SET points = GetWeightedPoints(auth);", gS_MySQLPrefix); + FormatEx(sQuery, 128, "UPDATE %susers SET points = GetWeightedPoints(auth);", + gS_MySQLPrefix); + gH_SQL.Query(SQL_UpdateAllPoints_Callback, sQuery); } @@ -794,7 +798,7 @@ public void SQL_UpdateAllPoints_Callback(Database db, DBResultSet results, const } } -void UpdatePlayerRank(int client) +void UpdatePlayerRank(int client, bool first) { gI_Rank[client] = 0; gF_Points[client] = 0.0; @@ -809,12 +813,23 @@ void UpdatePlayerRank(int client) FormatEx(sQuery, 512, "SELECT p.points, COUNT(*) rank FROM %susers u JOIN (SELECT points FROM %susers WHERE auth = '%s' LIMIT 1) p WHERE u.points >= p.points LIMIT 1;", gS_MySQLPrefix, gS_MySQLPrefix, sAuthID); - gH_SQL.Query(SQL_UpdatePlayerRank_Callback, sQuery, GetClientSerial(client), DBPrio_Low); + DataPack hPack = new DataPack(); + hPack.WriteCell(GetClientSerial(client)); + hPack.WriteCell(first); + + gH_SQL.Query(SQL_UpdatePlayerRank_Callback, sQuery, hPack, DBPrio_Low); } } public void SQL_UpdatePlayerRank_Callback(Database db, DBResultSet results, const char[] error, any data) { + DataPack hPack = view_as(data); + hPack.Reset(); + + int iSerial = hPack.ReadCell(); + bool bFirst = view_as(hPack.ReadCell()); + delete hPack; + if(results == null) { LogError("Timer (rankings, update player rank) error! Reason: %s", error); @@ -822,7 +837,7 @@ public void SQL_UpdatePlayerRank_Callback(Database db, DBResultSet results, cons return; } - int client = GetClientFromSerial(data); + int client = GetClientFromSerial(iSerial); if(client == 0) { @@ -833,13 +848,22 @@ public void SQL_UpdatePlayerRank_Callback(Database db, DBResultSet results, cons { gF_Points[client] = results.FetchFloat(0); gI_Rank[client] = (gF_Points[client] > 0.0)? results.FetchInt(1):0; + + Call_StartForward(gH_Forwards_OnRankAssigned); + Call_PushCell(client); + Call_PushCell(gI_Rank[client]); + Call_PushCell(gF_Points[client]); + Call_PushCell(bFirst); + Call_Finish(); } } void UpdateRankedPlayers() { char sQuery[512]; - FormatEx(sQuery, 512, "SELECT COUNT(*) count FROM %susers WHERE points > 0.0;", gS_MySQLPrefix); + FormatEx(sQuery, 512, "SELECT COUNT(*) count FROM %susers WHERE points > 0.0;", + gS_MySQLPrefix); + gH_SQL.Query(SQL_UpdateRankedPlayers_Callback, sQuery, 0, DBPrio_High); } diff --git a/addons/sourcemod/scripting/shavit-replay.sp b/addons/sourcemod/scripting/shavit-replay.sp index e7d6232d..06b0ab1b 100644 --- a/addons/sourcemod/scripting/shavit-replay.sp +++ b/addons/sourcemod/scripting/shavit-replay.sp @@ -29,6 +29,7 @@ #undef REQUIRE_EXTENSIONS #include #include +#include #define REPLAY_FORMAT_V2 "{SHAVITREPLAYFORMAT}{V2}" #define REPLAY_FORMAT_FINAL "{SHAVITREPLAYFORMAT}{FINAL}" @@ -48,6 +49,7 @@ enum struct centralbot_cache_t int iStyle; ReplayStatus iReplayStatus; int iTrack; + int iPlaybackSerial; } enum struct replaystrings_t @@ -87,12 +89,15 @@ ArrayList gA_Frames[STYLE_LIMIT][TRACKS_SIZE]; float gF_StartTick[STYLE_LIMIT]; ReplayStatus gRS_ReplayStatus[STYLE_LIMIT]; framecache_t gA_FrameCache[STYLE_LIMIT][TRACKS_SIZE]; + bool gB_ForciblyStopped = false; +Handle gH_ReplayTimers[STYLE_LIMIT]; bool gB_Button[MAXPLAYERS+1]; int gI_PlayerFrames[MAXPLAYERS+1]; ArrayList gA_PlayerFrames[MAXPLAYERS+1]; int gI_Track[MAXPLAYERS+1]; +float gF_LastInteraction[MAXPLAYERS+1]; bool gB_Late = false; @@ -122,6 +127,8 @@ ConVar gCV_CentralBot = null; ConVar gCV_BotShooting = null; ConVar gCV_BotPlusUse = null; ConVar gCV_BotWeapon = null; +ConVar gCV_PlaybackCanStop = null; +ConVar gCV_PlaybackCooldown = null; // timer settings int gI_Styles = 0; @@ -153,6 +160,7 @@ public Plugin myinfo = public APLRes AskPluginLoad2(Handle myself, bool late, char[] error, int err_max) { + CreateNative("Shavit_DeleteReplay", Native_DeleteReplay); CreateNative("Shavit_GetReplayBotCurrentFrame", Native_GetReplayBotIndex); CreateNative("Shavit_GetReplayBotFirstFrame", Native_GetReplayBotFirstFrame); CreateNative("Shavit_GetReplayBotIndex", Native_GetReplayBotIndex); @@ -223,6 +231,8 @@ public void OnPluginStart() gCV_BotShooting = CreateConVar("shavit_replay_botshooting", "3", "Attacking buttons to allow for bots.\n0 - none\n1 - +attack\n2 - +attack2\n3 - both", 0, true, 0.0, true, 3.0); gCV_BotPlusUse = CreateConVar("shavit_replay_botplususe", "1", "Allow bots to use +use?", 0, true, 0.0, true, 1.0); gCV_BotWeapon = CreateConVar("shavit_replay_botweapon", "", "Choose which weapon the bot will hold.\nLeave empty to use the default.\nSet to \"none\" to have none.\nExample: weapon_usp"); + gCV_PlaybackCanStop = CreateConVar("shavit_replay_pbcanstop", "1", "Allow players to stop playback if they requested it?", 0, true, 0.0, true, 1.0); + gCV_PlaybackCooldown = CreateConVar("shavit_replay_pbcooldown", "10.0", "Cooldown in seconds to apply for players between each playback they request/stop.\nDoes not apply to RCON admins.", 0, true, 0.0); gCV_CentralBot.AddChangeHook(OnConVarChanged); @@ -311,6 +321,53 @@ public void AdminMenu_DeleteReplay(Handle topmenu, TopMenuAction action, TopMenu } } +void UnloadReplay(int style, int track) +{ + if(gCV_CentralBot.BoolValue && gA_CentralCache.iStyle == style && gA_CentralCache.iTrack == track) + { + StopCentralReplay(0); + } + + gA_Frames[style][track].Clear(); + gA_FrameCache[style][track].iFrameCount = 0; + gA_FrameCache[style][track].fTime = 0.0; + gA_FrameCache[style][track].bNewFormat = true; + strcopy(gA_FrameCache[style][track].sReplayName, MAX_NAME_LENGTH, "invalid"); + gI_ReplayTick[style] = -1; + + if(gI_ReplayBotClient[style] != 0) + { + UpdateReplayInfo(gI_ReplayBotClient[style], style, 0.0, track); + } +} + +public int Native_DeleteReplay(Handle handler, int numParams) +{ + char sMap[160]; + GetNativeString(1, sMap, 160); + + int iStyle = GetNativeCell(2); + int iTrack = GetNativeCell(3); + + char sTrack[4]; + FormatEx(sTrack, 4, "_%d", iTrack); + + char sPath[PLATFORM_MAX_PATH]; + FormatEx(sPath, PLATFORM_MAX_PATH, "%s/%d/%s%s.replay", gS_ReplayFolder, iStyle, gS_Map, (iTrack > 0)? sTrack:""); + + if(!FileExists(sPath) || !DeleteFile(sPath)) + { + return false; + } + + if(StrEqual(sMap, gS_Map)) + { + UnloadReplay(iStyle, iTrack); + } + + return true; +} + public int Native_GetReplayBotFirstFrame(Handle handler, int numParams) { SetNativeCellRef(2, gF_StartTick[GetNativeCell(1)]); @@ -362,7 +419,7 @@ public int Native_ReloadReplay(Handle handler, int numParams) gA_Frames[style][track] = new ArrayList(CELLS_PER_FRAME); gA_FrameCache[style][track].iFrameCount = 0; gA_FrameCache[style][track].fTime = 0.0; - gA_FrameCache[style][track].bNewFormat = false; + gA_FrameCache[style][track].bNewFormat = true; strcopy(gA_FrameCache[style][track].sReplayName, MAX_NAME_LENGTH, "invalid"); bool loaded = false; @@ -397,7 +454,9 @@ public int Native_ReloadReplay(Handle handler, int numParams) { gI_ReplayTick[style] = 0; gRS_ReplayStatus[style] = Replay_Start; - CreateTimer((gCV_ReplayDelay.FloatValue / 2.0), Timer_StartReplay, style, TIMER_FLAG_NO_MAPCHANGE); + + delete gH_ReplayTimers[style]; + gH_ReplayTimers[style] = CreateTimer((gCV_ReplayDelay.FloatValue / 2.0), Timer_StartReplay, style, TIMER_FLAG_NO_MAPCHANGE); } } @@ -494,7 +553,7 @@ public int Native_GetReplayTime(Handle handler, int numParams) return view_as(GetReplayLength(Track_Main, track)); } - return view_as(float(gI_ReplayTick[style]) / gF_Tickrate); + return view_as(float(gI_ReplayTick[style]) / gF_Tickrate * gA_StyleSettings[style].fTimescale); } public int Native_HijackAngles(Handle handler, int numParams) @@ -693,6 +752,13 @@ public void OnMapStart() SetFailState("Could not load the replay bots' configuration file. Make sure it exists (addons/sourcemod/configs/shavit-replay.cfg) and follows the proper syntax!"); } + // VERY RARE CASE + // this is required to not cause replays to break if we change map before playback starts, after it is requested + for(int i = 0; i < STYLE_LIMIT; i++) + { + gH_ReplayTimers[i] = null; + } + if(gB_Late) { Shavit_OnStyleConfigLoaded(-1); @@ -703,6 +769,7 @@ public void OnMapStart() gA_CentralCache.iStyle = -1; gA_CentralCache.iReplayStatus = Replay_Idle; gA_CentralCache.iTrack = Track_Main; + gA_CentralCache.iPlaybackSerial = 0; gB_ForciblyStopped = false; @@ -714,13 +781,6 @@ public void OnMapStart() return; } - bot_quota = FindConVar((gEV_Type != Engine_TF2)? "bot_quota":"tf_bot_quota"); - - if(bot_quota != null) - { - bot_quota.Flags &= ~FCVAR_NOTIFY; - } - char sTempMap[PLATFORM_MAX_PATH]; FormatEx(sTempMap, PLATFORM_MAX_PATH, "maps/%s.nav", gS_Map); @@ -743,60 +803,18 @@ public void OnMapStart() if(bot_controllable != null) { bot_controllable.BoolValue = false; - delete bot_controllable; } - ConVar bot_stop = FindConVar("bot_stop"); - - if(bot_stop != null) - { - bot_stop.BoolValue = true; - delete bot_stop; - } - - ConVar bot_quota_mode = FindConVar((gEV_Type != Engine_TF2)? "bot_quota_mode":"tf_bot_quota_mode"); - - if(bot_quota_mode != null) - { - bot_quota_mode.SetString("normal"); - delete bot_quota_mode; - } - - ConVar mp_limitteams = FindConVar("mp_limitteams"); - - if(mp_limitteams != null) - { - mp_limitteams.IntValue = 0; - delete mp_limitteams; - } - - ConVar bot_join_after_player = FindConVar((gEV_Type != Engine_TF2)? "bot_join_after_player":"tf_bot_join_after_player"); - - if(bot_join_after_player != null) - { - bot_join_after_player.BoolValue = false; - delete bot_join_after_player; - } - - ConVar bot_chatter = FindConVar("bot_chatter"); - - if(bot_chatter != null) - { - bot_chatter.SetString("off"); - delete bot_chatter; - } - - ConVar bot_zombie = FindConVar("bot_zombie"); - - if(bot_zombie != null) - { - bot_zombie.BoolValue = true; - delete bot_zombie; - } - - ConVar mp_autoteambalance = FindConVar("mp_autoteambalance"); - mp_autoteambalance.BoolValue = false; - delete mp_autoteambalance; + FindConVar((gEV_Type != Engine_TF2)? "bot_quota":"tf_bot_quota").Flags &= ~FCVAR_NOTIFY; + FindConVar("bot_stop").Flags &= ~FCVAR_CHEAT; + FindConVar("bot_stop").BoolValue = true; + FindConVar((gEV_Type != Engine_TF2)? "bot_quota_mode":"tf_bot_quota_mode").SetString("normal"); + FindConVar("mp_limitteams").IntValue = 0; + FindConVar((gEV_Type != Engine_TF2)? "bot_join_after_player":"tf_bot_join_after_player").BoolValue = false; + FindConVar("bot_chatter").SetString("off"); + FindConVar("bot_flipout").BoolValue = true; + FindConVar("bot_zombie").BoolValue = true; + FindConVar("mp_autoteambalance").BoolValue = false; ServerCommand((gEV_Type != Engine_TF2)? "bot_kick":"tf_bot_kick all"); @@ -834,7 +852,7 @@ public void OnMapStart() gA_Frames[i][j] = new ArrayList(CELLS_PER_FRAME); gA_FrameCache[i][j].iFrameCount = 0; gA_FrameCache[i][j].fTime = 0.0; - gA_FrameCache[i][j].bNewFormat = false; + gA_FrameCache[i][j].bNewFormat = true; strcopy(gA_FrameCache[i][j].sReplayName, MAX_NAME_LENGTH, "invalid"); loaded = DefaultLoadReplay(i, j); @@ -849,7 +867,9 @@ public void OnMapStart() { gI_ReplayTick[i] = 0; gRS_ReplayStatus[i] = Replay_Start; - CreateTimer((gCV_ReplayDelay.FloatValue / 2.0), Timer_StartReplay, i, TIMER_FLAG_NO_MAPCHANGE); + + delete gH_ReplayTimers[i]; + gH_ReplayTimers[i] = CreateTimer((gCV_ReplayDelay.FloatValue / 2.0), Timer_StartReplay, i, TIMER_FLAG_NO_MAPCHANGE); } } } @@ -1111,22 +1131,7 @@ bool DeleteReplay(int style, int track) return false; } - if(gCV_CentralBot.BoolValue && gA_CentralCache.iStyle == style && gA_CentralCache.iTrack == track) - { - StopCentralReplay(0); - } - - gA_Frames[style][track].Clear(); - gA_FrameCache[style][track].iFrameCount = 0; - gA_FrameCache[style][track].fTime = 0.0; - gA_FrameCache[style][track].bNewFormat = false; - strcopy(gA_FrameCache[style][track].sReplayName, MAX_NAME_LENGTH, "invalid"); - gI_ReplayTick[style] = -1; - - if(gI_ReplayBotClient[style] != 0) - { - UpdateReplayInfo(gI_ReplayBotClient[style], style, 0.0, track); - } + UnloadReplay(style, track); return true; } @@ -1616,7 +1621,7 @@ public Action OnPlayerRunCmd(int client, int &buttons, int &impulse, float vel[3 float vecVelocity[3]; MakeVectorFromPoints(vecCurrentPosition, vecPosition, vecVelocity); ScaleVector(vecVelocity, gF_Tickrate); - TeleportEntity(client, NULL_VECTOR, vecAngles, vecVelocity); + TeleportEntity(client, NULL_VECTOR, NULL_VECTOR, vecVelocity); } return Plugin_Changed; @@ -1627,7 +1632,9 @@ public Action OnPlayerRunCmd(int client, int &buttons, int &impulse, float vel[3 gI_ReplayTick[style] = 0; gRS_ReplayStatus[style] = gA_CentralCache.iReplayStatus = Replay_End; - CreateTimer((gCV_ReplayDelay.FloatValue / 2.0), Timer_EndReplay, style, TIMER_FLAG_NO_MAPCHANGE); + delete gH_ReplayTimers[style]; + gH_ReplayTimers[style] = CreateTimer((gCV_ReplayDelay.FloatValue / 2.0), Timer_EndReplay, style, TIMER_FLAG_NO_MAPCHANGE); + gA_CentralCache.iPlaybackSerial = 0; return Plugin_Changed; } @@ -1677,9 +1684,9 @@ public Action OnPlayerRunCmd(int client, int &buttons, int &impulse, float vel[3 MoveType movetype = gA_Frames[style][track].Get(gI_ReplayTick[style], 7); - if(movetype == MOVETYPE_LADDER) + if(movetype == MOVETYPE_LADDER || (movetype == MOVETYPE_WALK && (iReplayFlags & FL_ONGROUND) > 0)) { - mt = MOVETYPE_LADDER; + mt = movetype; } } @@ -1689,33 +1696,16 @@ public Action OnPlayerRunCmd(int client, int &buttons, int &impulse, float vel[3 MakeVectorFromPoints(vecCurrentPosition, vecPosition, vecVelocity); ScaleVector(vecVelocity, gF_Tickrate); - if(gI_ReplayTick[style] > 1) + if(gI_ReplayTick[style] > 1 && + // replay is going above 50k speed, just teleport at this point + (GetVectorLength(vecVelocity) > 50000.0 || + // bot is on ground.. if the distance between the previous position is much bigger (1.5x) than the expected according + // to the bot's velocity, teleport to avoid sync issues + (mt == MOVETYPE_WALK && GetVectorDistance(vecCurrentPosition, vecPosition) > GetVectorLength(vecVelocity) / gF_Tickrate * 1.5))) { - float vecLastPosition[3]; - vecLastPosition[0] = gA_Frames[style][track].Get(gI_ReplayTick[style] - 1, 0); - vecLastPosition[1] = gA_Frames[style][track].Get(gI_ReplayTick[style] - 1, 1); - vecLastPosition[2] = gA_Frames[style][track].Get(gI_ReplayTick[style] - 1, 2); + TeleportEntity(client, vecPosition, vecAngles, NULL_VECTOR); - // fix for replay not syncing - if(GetVectorDistance(vecLastPosition, vecCurrentPosition) >= 100.0 && IsWallBetween(vecLastPosition, vecCurrentPosition, client)) - { - TeleportEntity(client, vecPosition, NULL_VECTOR, NULL_VECTOR); - - return Plugin_Handled; - } - - #if defined DEBUG - PrintToChatAll("vecVelocity: %.02f | dist %.02f", GetVectorLength(vecVelocity), GetVectorDistance(vecLastPosition, vecPosition) * gF_Tickrate); - #endif - - if(GetVectorLength(vecVelocity) / (GetVectorDistance(vecLastPosition, vecPosition) * gF_Tickrate) > 2.0) - { - MakeVectorFromPoints(vecLastPosition, vecPosition, vecVelocity); - ScaleVector(vecVelocity, gF_Tickrate); - TeleportEntity(client, vecLastPosition, vecAngles, vecVelocity); - - return Plugin_Changed; - } + return Plugin_Changed; } TeleportEntity(client, NULL_VECTOR, vecAngles, vecVelocity); @@ -1770,20 +1760,19 @@ public Action OnPlayerRunCmd(int client, int &buttons, int &impulse, float vel[3 return Plugin_Continue; } -public bool Filter_Clients(int entity, int contentsMask, any data) -{ - return (1 <= entity <= MaxClients && entity != data); -} - -bool IsWallBetween(float pos1[3], float pos2[3], int bot) -{ - TR_TraceRayFilter(pos1, pos2, MASK_SOLID, RayType_EndPoint, Filter_Clients, bot); - - return !TR_DidHit(); -} - public Action Timer_EndReplay(Handle Timer, any data) { + gH_ReplayTimers[data] = null; + + int client = GetClientFromSerial(gA_CentralCache.iPlaybackSerial); + + if(client != 0) + { + gF_LastInteraction[client] = GetEngineTime(); + } + + gA_CentralCache.iPlaybackSerial = 0; + if(gCV_CentralBot.BoolValue && gB_ForciblyStopped) { gB_ForciblyStopped = false; @@ -1801,7 +1790,8 @@ public Action Timer_EndReplay(Handle Timer, any data) { gRS_ReplayStatus[data] = Replay_Start; - CreateTimer((gCV_ReplayDelay.FloatValue / 2.0), Timer_StartReplay, data, TIMER_FLAG_NO_MAPCHANGE); + delete gH_ReplayTimers[data]; + gH_ReplayTimers[data] = CreateTimer((gCV_ReplayDelay.FloatValue / 2.0), Timer_StartReplay, data, TIMER_FLAG_NO_MAPCHANGE); } else @@ -1815,6 +1805,8 @@ public Action Timer_EndReplay(Handle Timer, any data) public Action Timer_StartReplay(Handle Timer, any data) { + gH_ReplayTimers[data] = null; + if(gRS_ReplayStatus[data] == Replay_Running || (gCV_CentralBot.BoolValue && gB_ForciblyStopped)) { return Plugin_Stop; @@ -1964,24 +1956,29 @@ public Action Command_DeleteReplay(int client, int args) Menu menu = new Menu(DeleteReplay_Callback); menu.SetTitle("%T", "DeleteReplayMenuTitle", client); + int[] styles = new int[gI_Styles]; + Shavit_GetOrderedStyles(styles, gI_Styles); + for(int i = 0; i < gI_Styles; i++) { - if(!ReplayEnabled(i)) + int iStyle = styles[i]; + + if(!ReplayEnabled(iStyle)) { continue; } for(int j = 0; j < TRACKS_SIZE; j++) { - if(gA_FrameCache[i][j].iFrameCount == 0) + if(gA_FrameCache[iStyle][j].iFrameCount == 0) { continue; } char sInfo[8]; - FormatEx(sInfo, 8, "%d;%d", i, j); + FormatEx(sInfo, 8, "%d;%d", iStyle, j); - float time = GetReplayLength(i, j); + float time = GetReplayLength(iStyle, j); char sTrack[32]; GetTrackName(client, j, sTrack, 32); @@ -1993,12 +1990,12 @@ public Action Command_DeleteReplay(int client, int args) char sTime[32]; FormatSeconds(time, sTime, 32, false); - FormatEx(sDisplay, 64, "%s (%s) - %s", gS_StyleStrings[i].sStyleName, sTrack, sTime); + FormatEx(sDisplay, 64, "%s (%s) - %s", gS_StyleStrings[iStyle].sStyleName, sTrack, sTime); } else { - FormatEx(sDisplay, 64, "%s (%s)", gS_StyleStrings[i].sStyleName, sTrack); + FormatEx(sDisplay, 64, "%s (%s)", gS_StyleStrings[iStyle].sStyleName, sTrack); } menu.AddItem(sInfo, sDisplay); @@ -2108,14 +2105,29 @@ public Action Command_Replay(int client, int args) return Plugin_Handled; } - if(GetClientTeam(client) != 1 || GetSpectatorTarget(client) != gA_CentralCache.iClient) + if(GetClientTeam(client) > 1) + { + if(gEV_Type == Engine_TF2) + { + TF2_ChangeClientTeam(client, TFTeam_Spectator); + } + + else + { + ChangeClientTeam(client, CS_TEAM_SPECTATOR); + } + + SetEntPropEnt(client, Prop_Send, "m_hObserverTarget", gA_CentralCache.iClient); + } + + else if(GetSpectatorTarget(client) != gA_CentralCache.iClient) { Shavit_PrintToChat(client, "%T", "CentralReplaySpectator", client, gS_ChatStrings.sWarning, gS_ChatStrings.sText, gS_ChatStrings.sVariable, gS_ChatStrings.sText); return Plugin_Handled; } - if(CheckCommandAccess(client, "sm_deletereplay", ADMFLAG_RCON)) + if(CanStopCentral(client)) { char arg[8]; GetCmdArg(1, arg, 8); @@ -2194,7 +2206,7 @@ void OpenReplaySubMenu(int client, int track) Menu menu = new Menu(MenuHandler_ReplaySubmenu); menu.SetTitle("%T (%s)\n ", "CentralReplayTitle", client, sTrack); - if(CheckCommandAccess(client, "sm_deletereplay", ADMFLAG_RCON)) + if(CanStopCentral(client)) { char sDisplay[64]; FormatEx(sDisplay, 64, "%T", "CentralReplayStop", client); @@ -2202,17 +2214,22 @@ void OpenReplaySubMenu(int client, int track) menu.AddItem("stop", sDisplay, (gA_CentralCache.iReplayStatus != Replay_Idle)? ITEMDRAW_DEFAULT:ITEMDRAW_DISABLED); } + int[] styles = new int[gI_Styles]; + Shavit_GetOrderedStyles(styles, gI_Styles); + for(int i = 0; i < gI_Styles; i++) { - if(!ReplayEnabled(i)) + int iStyle = styles[i]; + + if(!ReplayEnabled(iStyle)) { continue; } char sInfo[8]; - IntToString(i, sInfo, 8); + IntToString(iStyle, sInfo, 8); - float time = GetReplayLength(i, track); + float time = GetReplayLength(iStyle, track); char sDisplay[64]; @@ -2221,15 +2238,15 @@ void OpenReplaySubMenu(int client, int track) char sTime[32]; FormatSeconds(time, sTime, 32, false); - FormatEx(sDisplay, 64, "%s - %s", gS_StyleStrings[i].sStyleName, sTime); + FormatEx(sDisplay, 64, "%s - %s", gS_StyleStrings[iStyle].sStyleName, sTime); } else { - strcopy(sDisplay, 64, gS_StyleStrings[i].sStyleName); + strcopy(sDisplay, 64, gS_StyleStrings[iStyle].sStyleName); } - menu.AddItem(sInfo, sDisplay, (gA_FrameCache[i][track].iFrameCount > 0)? ITEMDRAW_DEFAULT:ITEMDRAW_DISABLED); + menu.AddItem(sInfo, sDisplay, (gA_FrameCache[iStyle][track].iFrameCount > 0)? ITEMDRAW_DEFAULT:ITEMDRAW_DISABLED); } if(menu.ItemCount == 0) @@ -2253,7 +2270,7 @@ public int MenuHandler_ReplaySubmenu(Menu menu, MenuAction action, int param1, i char info[16]; menu.GetItem(param2, info, 16); - if(StrEqual(info, "stop")) + if(StrEqual(info, "stop") && CanStopCentral(param1)) { StopCentralReplay(param1); OpenReplaySubMenu(param1, gI_Track[param1]); @@ -2280,6 +2297,8 @@ public int MenuHandler_ReplaySubmenu(Menu menu, MenuAction action, int param1, i gI_ReplayTick[style] = 0; gA_CentralCache.iStyle = style; gA_CentralCache.iTrack = gI_Track[param1]; + gA_CentralCache.iPlaybackSerial = GetClientSerial(param1); + gF_LastInteraction[param1] = GetEngineTime(); gI_ReplayBotClient[style] = gA_CentralCache.iClient; gRS_ReplayStatus[style] = gA_CentralCache.iReplayStatus = Replay_Start; TeleportToStart(gA_CentralCache.iClient, style, gI_Track[param1]); @@ -2289,7 +2308,8 @@ public int MenuHandler_ReplaySubmenu(Menu menu, MenuAction action, int param1, i UpdateReplayInfo(gA_CentralCache.iClient, style, time, gI_Track[param1]); - CreateTimer((gCV_ReplayDelay.FloatValue / 2.0), Timer_StartReplay, style, TIMER_FLAG_NO_MAPCHANGE); + delete gH_ReplayTimers[style]; + gH_ReplayTimers[style] = CreateTimer((gCV_ReplayDelay.FloatValue / 2.0), Timer_StartReplay, style, TIMER_FLAG_NO_MAPCHANGE); } } @@ -2306,6 +2326,14 @@ public int MenuHandler_ReplaySubmenu(Menu menu, MenuAction action, int param1, i return 0; } +bool CanStopCentral(int client) +{ + return (CheckCommandAccess(client, "sm_deletereplay", ADMFLAG_RCON) || + (gCV_PlaybackCanStop.BoolValue && + GetClientSerial(client) == gA_CentralCache.iPlaybackSerial && + GetEngineTime() - gF_LastInteraction[client] > gCV_PlaybackCooldown.FloatValue)); +} + void TeleportToStart(int client, int style, int track) { if(gA_FrameCache[style][track].iFrameCount == 0) @@ -2333,6 +2361,12 @@ void StopCentralReplay(int client) } int style = gA_CentralCache.iStyle; + int player = GetClientFromSerial(gA_CentralCache.iPlaybackSerial); + + if(player != 0) + { + gF_LastInteraction[player] = GetEngineTime(); + } gRS_ReplayStatus[style] = gA_CentralCache.iReplayStatus = Replay_Idle; gI_ReplayTick[style] = 0; @@ -2340,6 +2374,7 @@ void StopCentralReplay(int client) gF_StartTick[style] = -65535.0; gA_CentralCache.iStyle = 0; gB_ForciblyStopped = true; + gA_CentralCache.iPlaybackSerial = 0; if(gA_CentralCache.iClient != -1) { @@ -2425,14 +2460,17 @@ void GetTrackName(int client, int track, char[] output, int size) float GetReplayLength(int style, int track) { + if(gA_FrameCache[style][track].iFrameCount == 0) + { + return 0.0; + } + if(gA_FrameCache[style][track].bNewFormat) { return gA_FrameCache[style][track].fTime; } - float fWRTime = Shavit_GetWorldRecord(style, track); - - return fWRTime; + return Shavit_GetWorldRecord(style, track) * gA_StyleSettings[style].fTimescale; } void GetReplayName(int style, int track, char[] buffer, int length) diff --git a/addons/sourcemod/scripting/shavit-stats.sp b/addons/sourcemod/scripting/shavit-stats.sp index 4787869c..6311054d 100644 --- a/addons/sourcemod/scripting/shavit-stats.sp +++ b/addons/sourcemod/scripting/shavit-stats.sp @@ -462,17 +462,22 @@ public void OpenStatsMenuCallback(Database db, DBResultSet results, const char[] gS_TargetName[client], "Profile", client, gS_TargetAuth[client], "Country", client, sCountry, sLastLogin, sClearString, gS_StyleStrings[0].sStyleName, "WorldRecords", client, iWRs, sRankingString); + int[] styles = new int[gI_Styles]; + Shavit_GetOrderedStyles(styles, gI_Styles); + for(int i = 0; i < gI_Styles; i++) { - if(gA_StyleSettings[i].bUnranked) + int iStyle = styles[i]; + + if(gA_StyleSettings[iStyle].bUnranked) { continue; } char sInfo[4]; - IntToString(i, sInfo, 4); + IntToString(iStyle, sInfo, 4); - menu.AddItem(sInfo, gS_StyleStrings[i].sStyleName); + menu.AddItem(sInfo, gS_StyleStrings[iStyle].sStyleName); } // should NEVER happen diff --git a/addons/sourcemod/scripting/shavit-wr.sp b/addons/sourcemod/scripting/shavit-wr.sp index fa55f129..63cd09b4 100644 --- a/addons/sourcemod/scripting/shavit-wr.sp +++ b/addons/sourcemod/scripting/shavit-wr.sp @@ -107,6 +107,7 @@ public APLRes AskPluginLoad2(Handle myself, bool late, char[] error, int err_max CreateNative("Shavit_GetWRName", Native_GetWRName); CreateNative("Shavit_GetWRRecordID", Native_GetWRRecordID); CreateNative("Shavit_GetWRTime", Native_GetWRTime); + CreateNative("Shavit_ReloadLeaderboards", Native_ReloadLeaderboards); CreateNative("Shavit_WR_DeleteMap", Native_WR_DeleteMap); // registers library, check "bool LibraryExists(const char[] name)" in order to use with other plugins @@ -507,13 +508,6 @@ public void SQL_UpdateWRCache_Callback(Database db, DBResultSet results, const c return; } - // resultset structure - // FIELD 0: style - // FIELD 1: id - // FIELD 2: time - sorted - // FIELD 3: name - // FIELD 4: track - // reset cache for(int i = 0; i < gI_Styles; i++) { @@ -556,6 +550,12 @@ public int Native_GetWRTime(Handle handler, int numParams) SetNativeCellRef(2, gF_WRTime[GetNativeCell(1)][GetNativeCell(3)]); } +public int Native_ReloadLeaderboards(Handle handler, int numParams) +{ + UpdateLeaderboards(); + UpdateWRCache(); +} + public int Native_GetWRRecordID(Handle handler, int numParams) { SetNativeCellRef(2, gI_WRRecordID[GetNativeCell(1)][GetNativeCell(3)]); @@ -745,15 +745,20 @@ void DeleteSubmenu(int client) Menu menu = new Menu(MenuHandler_Delete); menu.SetTitle("%T\n ", "DeleteMenuTitle", client); + int[] styles = new int[gI_Styles]; + Shavit_GetOrderedStyles(styles, gI_Styles); + for(int i = 0; i < gI_Styles; i++) { + int iStyle = styles[i]; + char sInfo[8]; - IntToString(i, sInfo, 8); + IntToString(iStyle, sInfo, 8); char sDisplay[64]; - FormatEx(sDisplay, 64, "%s (%T: %d)", gS_StyleStrings[i].sStyleName, "WRRecord", client, gI_RecordAmount[i][gA_WRCache[client].iLastTrack]); + FormatEx(sDisplay, 64, "%s (%T: %d)", gS_StyleStrings[iStyle].sStyleName, "WRRecord", client, gI_RecordAmount[iStyle][gA_WRCache[client].iLastTrack]); - menu.AddItem(sInfo, sDisplay, (gI_RecordAmount[i][gA_WRCache[client].iLastTrack] > 0)? ITEMDRAW_DEFAULT:ITEMDRAW_DISABLED); + menu.AddItem(sInfo, sDisplay, (gI_RecordAmount[iStyle][gA_WRCache[client].iLastTrack] > 0)? ITEMDRAW_DEFAULT:ITEMDRAW_DISABLED); } menu.ExitButton = true; @@ -775,17 +780,17 @@ public Action Command_DeleteAll(int client, int args) char sInfo[8]; IntToString(i, sInfo, 8); - int records = GetTrackRecordCount(i); + int iRecords = GetTrackRecordCount(i); char sTrack[64]; GetTrackName(client, i, sTrack, 64); - if(records > 0) + if(iRecords > 0) { - Format(sTrack, 64, "%s (%T: %d)", sTrack, "WRRecord", client, records); + Format(sTrack, 64, "%s (%T: %d)", sTrack, "WRRecord", client, iRecords); } - menu.AddItem(sInfo, sTrack, (records > 0)? ITEMDRAW_DEFAULT:ITEMDRAW_DISABLED); + menu.AddItem(sInfo, sTrack, (iRecords > 0)? ITEMDRAW_DEFAULT:ITEMDRAW_DISABLED); } menu.ExitButton = true; @@ -798,9 +803,57 @@ public int MenuHandler_DeleteAll_First(Menu menu, MenuAction action, int param1, { if(action == MenuAction_Select) { - char info[16]; - menu.GetItem(param2, info, 16); - gA_WRCache[param1].iLastTrack = StringToInt(info); + char sInfo[8]; + menu.GetItem(param2, sInfo, 8); + int iTrack = gA_WRCache[param1].iLastTrack = StringToInt(sInfo); + + char sTrack[64]; + GetTrackName(param1, iTrack, sTrack, 64); + + Menu subMenu = new Menu(MenuHandler_DeleteAll_Second); + subMenu.SetTitle("%T\n ", "DeleteTrackAllStyle", param1, sTrack); + + int[] styles = new int[gI_Styles]; + Shavit_GetOrderedStyles(styles, gI_Styles); + + for(int i = 0; i < gI_Styles; i++) + { + int iStyle = styles[i]; + + char sStyle[64]; + strcopy(sStyle, 64, gS_StyleStrings[iStyle].sStyleName); + + IntToString(iStyle, sInfo, 8); + + int iRecords = gI_RecordAmount[iStyle][iTrack]; + + if(iRecords > 0) + { + Format(sStyle, 64, "%s (%T: %d)", sStyle, "WRRecord", param1, iRecords); + } + + subMenu.AddItem(sInfo, sStyle, (iRecords > 0)? ITEMDRAW_DEFAULT:ITEMDRAW_DISABLED); + } + + subMenu.ExitButton = true; + subMenu.Display(param1, 20); + } + + else if(action == MenuAction_End) + { + delete menu; + } + + return 0; +} + +public int MenuHandler_DeleteAll_Second(Menu menu, MenuAction action, int param1, int param2) +{ + if(action == MenuAction_Select) + { + char sInfo[8]; + menu.GetItem(param2, sInfo, 8); + gA_WRCache[param1].iLastStyle = StringToInt(sInfo); DeleteAllSubmenu(param1); } @@ -819,7 +872,7 @@ void DeleteAllSubmenu(int client) GetTrackName(client, gA_WRCache[client].iLastTrack, sTrack, 32); Menu menu = new Menu(MenuHandler_DeleteAll); - menu.SetTitle("%T\n ", "DeleteAllRecordsMenuTitle", client, gS_Map, sTrack); + menu.SetTitle("%T\n ", "DeleteAllRecordsMenuTitle", client, gS_Map, sTrack, gS_StyleStrings[gA_WRCache[client].iLastStyle].sStyleName); char sMenuItem[64]; @@ -859,10 +912,12 @@ public int MenuHandler_DeleteAll(Menu menu, MenuAction action, int param1, int p char sTrack[32]; GetTrackName(LANG_SERVER, gA_WRCache[param1].iLastTrack, sTrack, 32); - Shavit_LogMessage("%L - deleted all %s track records from map `%s`.", param1, sTrack, gS_Map); + Shavit_LogMessage("%L - deleted all %s track and %s style records from map `%s`.", + param1, sTrack, gS_StyleStrings[gA_WRCache[param1].iLastStyle].sStyleName, gS_Map); char sQuery[256]; - FormatEx(sQuery, 256, "DELETE FROM %splayertimes WHERE map = '%s' AND track = %d;", gS_MySQLPrefix, gS_Map, gA_WRCache[param1].iLastTrack); + FormatEx(sQuery, 256, "DELETE FROM %splayertimes WHERE map = '%s' AND style = %d AND track = %d;", + gS_MySQLPrefix, gS_Map, gA_WRCache[param1].iLastStyle, gA_WRCache[param1].iLastTrack); gH_SQL.Query(DeleteAll_Callback, sQuery, GetClientSerial(param1), DBPrio_High); } @@ -885,24 +940,29 @@ public Action Command_DeleteStyleRecords(int client, int args) Menu menu = new Menu(MenuHandler_DeleteStyleRecords); menu.SetTitle("%T\n ", "DeleteStyleRecordsRecordsMenuTitle", client, gS_Map); + int[] styles = new int[gI_Styles]; + Shavit_GetOrderedStyles(styles, gI_Styles); + for(int i = 0; i < gI_Styles; i++) { - if(gA_StyleSettings[i].bUnranked) + int iStyle = styles[i]; + + if(gA_StyleSettings[iStyle].bUnranked) { continue; } char sInfo[8]; - IntToString(i, sInfo, 8); + IntToString(iStyle, sInfo, 8); char sDisplay[64]; - FormatEx(sDisplay, 64, "%s (%d %T)", gS_StyleStrings[i].sStyleName, gI_RecordAmount[i], "WRRecord", client); + FormatEx(sDisplay, 64, "%s (%d %T)", gS_StyleStrings[iStyle].sStyleName, gI_RecordAmount[iStyle], "WRRecord", client); int iTotalAmount = 0; for(int j = 0; j < TRACKS_SIZE; j++) { - iTotalAmount += gI_RecordAmount[i][j]; + iTotalAmount += gI_RecordAmount[iStyle][j]; } menu.AddItem(sInfo, sDisplay, (iTotalAmount > 0)? ITEMDRAW_DEFAULT:ITEMDRAW_DISABLED); @@ -1048,8 +1108,9 @@ public int MenuHandler_Delete(Menu menu, MenuAction action, int param1, int para { char info[16]; menu.GetItem(param2, info, 16); + gA_WRCache[param1].iLastStyle = StringToInt(info); - OpenDelete(param1, StringToInt(info)); + OpenDelete(param1); UpdateLeaderboards(); } @@ -1062,25 +1123,17 @@ public int MenuHandler_Delete(Menu menu, MenuAction action, int param1, int para return 0; } -void OpenDelete(int client, int style) +void OpenDelete(int client) { char sQuery[512]; + FormatEx(sQuery, 512, "SELECT p.id, u.name, p.time, p.jumps FROM %splayertimes p JOIN %susers u ON p.auth = u.auth WHERE map = '%s' AND style = %d AND track = %d ORDER BY time ASC, date ASC LIMIT 1000;", + gS_MySQLPrefix, gS_MySQLPrefix, gS_Map, gA_WRCache[client].iLastStyle, gA_WRCache[client].iLastTrack); - FormatEx(sQuery, 512, "SELECT p.id, u.name, p.time, p.jumps FROM %splayertimes p JOIN %susers u ON p.auth = u.auth WHERE map = '%s' AND style = %d AND track = %d ORDER BY time ASC, date ASC LIMIT 1000;", gS_MySQLPrefix, gS_MySQLPrefix, gS_Map, style, gA_WRCache[client].iLastTrack); - DataPack datapack = new DataPack(); - datapack.WriteCell(GetClientSerial(client)); - datapack.WriteCell(style); - - gH_SQL.Query(SQL_OpenDelete_Callback, sQuery, datapack, DBPrio_High); + gH_SQL.Query(SQL_OpenDelete_Callback, sQuery, GetClientSerial(client), DBPrio_High); } -public void SQL_OpenDelete_Callback(Database db, DBResultSet results, const char[] error, DataPack data) +public void SQL_OpenDelete_Callback(Database db, DBResultSet results, const char[] error, any data) { - data.Reset(); - int client = GetClientFromSerial(data.ReadCell()); - int style = data.ReadCell(); - delete data; - if(results == null) { LogError("Timer (WR OpenDelete) SQL query failed. Reason: %s", error); @@ -1088,13 +1141,17 @@ public void SQL_OpenDelete_Callback(Database db, DBResultSet results, const char return; } + int client = GetClientFromSerial(data); + if(client == 0) { return; } + int iStyle = gA_WRCache[client].iLastStyle; + Menu menu = new Menu(OpenDelete_Handler); - menu.SetTitle("%t", "ListClientRecords", gS_Map, gS_StyleStrings[style].sStyleName); + menu.SetTitle("%t", "ListClientRecords", gS_Map, gS_StyleStrings[iStyle].sStyleName); int iCount = 0; @@ -1140,10 +1197,10 @@ public int OpenDelete_Handler(Menu menu, MenuAction action, int param1, int para { if(action == MenuAction_Select) { - char info[16]; - menu.GetItem(param2, info, 16); + char sInfo[16]; + menu.GetItem(param2, sInfo, 16); - int id = StringToInt(info); + int id = StringToInt(sInfo); if(id != -1) { @@ -1174,9 +1231,9 @@ void OpenDeleteMenu(int client, int id) FormatEx(sMenuItem, 64, "%T", "MenuResponseYesSingle", client); - char info[16]; - IntToString(id, info, 16); - menu.AddItem(info, sMenuItem); + char sInfo[16]; + IntToString(id, sInfo, 16); + menu.AddItem(sInfo, sMenuItem); for(int i = 1; i <= GetRandomInt(1, 3); i++) { @@ -1192,9 +1249,10 @@ public int DeleteConfirm_Handler(Menu menu, MenuAction action, int param1, int p { if(action == MenuAction_Select) { - char info[16]; - menu.GetItem(param2, info, 16); - int iRecordID = StringToInt(info); + char sInfo[16]; + menu.GetItem(param2, sInfo, 16); + + int iRecordID = StringToInt(sInfo); if(iRecordID == -1) { @@ -1203,35 +1261,11 @@ public int DeleteConfirm_Handler(Menu menu, MenuAction action, int param1, int p return 0; } - for(int i = 0; i < gI_Styles; i++) - { - if(gA_StyleSettings[i].bUnranked) - { - continue; - } - - for(int j = 0; j < TRACKS_SIZE; j++) - { - if(gI_WRRecordID[i][j] != iRecordID) - { - continue; - } - - Call_StartForward(gH_OnWRDeleted); - Call_PushCell(i); - Call_PushCell(iRecordID); - Call_PushCell(j); - Call_Finish(); - } - } - - // TODO: display record details here (map name, player name/authid, time, rank on map) without dropping threaded queries - Shavit_LogMessage("%L - deleted record id %d.", param1, iRecordID); - char sQuery[256]; - FormatEx(sQuery, 256, "DELETE FROM %splayertimes WHERE id = %d;", gS_MySQLPrefix, iRecordID); + FormatEx(sQuery, 256, "SELECT u.auth, u.name, p.map, p.time, p.sync, p.perfs, p.jumps, p.strafes, p.id, p.date FROM %susers u LEFT JOIN %splayertimes p ON u.auth = p.auth WHERE p.id = %d;", + gS_MySQLPrefix, gS_MySQLPrefix, iRecordID); - gH_SQL.Query(DeleteConfirm_Callback, sQuery, GetClientSerial(param1), DBPrio_High); + gH_SQL.Query(GetRecordDetails_Callback, sQuery, GetClientSerial(param1), DBPrio_High); } else if(action == MenuAction_End) @@ -1242,8 +1276,101 @@ public int DeleteConfirm_Handler(Menu menu, MenuAction action, int param1, int p return 0; } +public void GetRecordDetails_Callback(Database db, DBResultSet results, const char[] error, any data) +{ + if(results == null) + { + LogError("Timer (WR GetRecordDetails) SQL query failed. Reason: %s", error); + + return; + } + + int client = GetClientFromSerial(data); + + if(client == 0) + { + return; + } + + if(results.FetchRow()) + { + char sAuthID[32]; + results.FetchString(0, sAuthID, 32); + + char sName[MAX_NAME_LENGTH]; + results.FetchString(1, sName, MAX_NAME_LENGTH); + + char sMap[160]; + results.FetchString(2, sMap, 160); + + float fTime = results.FetchFloat(3); + float fSync = results.FetchFloat(4); + float fPerfectJumps = results.FetchFloat(5); + + int iJumps = results.FetchInt(6); + int iStrafes = results.FetchInt(7); + int iRecordID = results.FetchInt(8); + int iTimestamp = results.FetchInt(9); + + int iStyle = gA_WRCache[client].iLastStyle; + int iTrack = gA_WRCache[client].iLastTrack; + bool bWRDeleted = (gI_WRRecordID[iStyle][iTrack] == iRecordID); + + // that's a big datapack ya yeet + DataPack hPack = new DataPack(); + hPack.WriteCell(GetClientSerial(client)); + hPack.WriteString(sAuthID); + hPack.WriteString(sName); + hPack.WriteString(sMap); + hPack.WriteCell(fTime); + hPack.WriteCell(fSync); + hPack.WriteCell(fPerfectJumps); + hPack.WriteCell(iJumps); + hPack.WriteCell(iStrafes); + hPack.WriteCell(iRecordID); + hPack.WriteCell(iTimestamp); + hPack.WriteCell(iStyle); + hPack.WriteCell(iTrack); + hPack.WriteCell(bWRDeleted); + + char sQuery[256]; + FormatEx(sQuery, 256, "DELETE FROM %splayertimes WHERE id = %d;", + gS_MySQLPrefix, iRecordID); + + gH_SQL.Query(DeleteConfirm_Callback, sQuery, hPack, DBPrio_High); + } +} + public void DeleteConfirm_Callback(Database db, DBResultSet results, const char[] error, any data) { + DataPack hPack = view_as(data); + hPack.Reset(); + + int iSerial = hPack.ReadCell(); + + char sAuthID[32]; + hPack.ReadString(sAuthID, 32); + + char sName[MAX_NAME_LENGTH]; + hPack.ReadString(sName, MAX_NAME_LENGTH); + + char sMap[160]; + hPack.ReadString(sMap, 160); + + float fTime = view_as(hPack.ReadCell()); + float fSync = view_as(hPack.ReadCell()); + float fPerfectJumps = view_as(hPack.ReadCell()); + + int iJumps = hPack.ReadCell(); + int iStrafes = hPack.ReadCell(); + int iRecordID = hPack.ReadCell(); + int iTimestamp = hPack.ReadCell(); + int iStyle = hPack.ReadCell(); + int iTrack = hPack.ReadCell(); + + bool bWRDeleted = view_as(hPack.ReadCell()); + delete hPack; + if(results == null) { LogError("Timer (WR DeleteConfirm) SQL query failed. Reason: %s", error); @@ -1251,6 +1378,15 @@ public void DeleteConfirm_Callback(Database db, DBResultSet results, const char[ return; } + if(bWRDeleted) + { + Call_StartForward(gH_OnWRDeleted); + Call_PushCell(iStyle); + Call_PushCell(iRecordID); + Call_PushCell(iTrack); + Call_Finish(); + } + UpdateWRCache(); for(int i = 1; i <= MaxClients; i++) @@ -1258,7 +1394,17 @@ public void DeleteConfirm_Callback(Database db, DBResultSet results, const char[ OnClientPutInServer(i); } - int client = GetClientFromSerial(data); + int client = GetClientFromSerial(iSerial); + + char sTrack[32]; + GetTrackName(LANG_SERVER, iTrack, sTrack, 32); + + char sDate[32]; + FormatTime(sDate, 32, "%Y-%m-%d %H:%M:%S", iTimestamp); + + // above the client == 0 so log doesn't get lost if admin disconnects between deleting record and query execution + Shavit_LogMessage("%L - deleted record. Runner: %s (%s) | Map: %s | Style: %s | Track: %s | Time: %.2f (%s) | Strafes: %d (%.1f%%) | Jumps: %d (%.1f%%) | Run date: %s | Record ID: %d", + client, sName, sAuthID, sMap, gS_StyleStrings[iStyle].sStyleName, sTrack, fTime, (bWRDeleted)? "WR":"not WR", iStrafes, fSync, iJumps, fPerfectJumps, sDate, iRecordID); if(client == 0) { @@ -1291,19 +1437,11 @@ public void DeleteAll_Callback(Database db, DBResultSet results, const char[] er return; } - for(int i = 0; i < gI_Styles; i++) - { - if(gA_StyleSettings[i].bUnranked) - { - continue; - } - - Call_StartForward(gH_OnWRDeleted); - Call_PushCell(i); - Call_PushCell(-1); - Call_PushCell(gA_WRCache[client].iLastTrack); - Call_Finish(); - } + Call_StartForward(gH_OnWRDeleted); + Call_PushCell(gA_WRCache[client].iLastStyle); + Call_PushCell(-1); + Call_PushCell(gA_WRCache[client].iLastTrack); + Call_Finish(); Shavit_PrintToChat(client, "%T", "DeletedRecordsMap", client, gS_ChatStrings.sVariable, gS_Map, gS_ChatStrings.sText); } @@ -1357,32 +1495,37 @@ Action ShowWRStyleMenu(int client, int track) Menu menu = new Menu(MenuHandler_StyleChooser); menu.SetTitle("%T", "WRMenuTitle", client); + int[] styles = new int[gI_Styles]; + Shavit_GetOrderedStyles(styles, gI_Styles); + for(int i = 0; i < gI_Styles; i++) { - if(gA_StyleSettings[i].bUnranked) + int iStyle = styles[i]; + + if(gA_StyleSettings[iStyle].bUnranked) { continue; } char sInfo[8]; - IntToString(i, sInfo, 8); + IntToString(iStyle, sInfo, 8); char sDisplay[64]; - if(StrEqual(gA_WRCache[client].sClientMap, gS_Map) && gF_WRTime[i][track] > 0.0) + if(StrEqual(gA_WRCache[client].sClientMap, gS_Map) && gF_WRTime[iStyle][track] > 0.0) { char sTime[32]; - FormatSeconds(gF_WRTime[i][track], sTime, 32, false); + FormatSeconds(gF_WRTime[iStyle][track], sTime, 32, false); - FormatEx(sDisplay, 64, "%s - WR: %s", gS_StyleStrings[i].sStyleName, sTime); + FormatEx(sDisplay, 64, "%s - WR: %s", gS_StyleStrings[iStyle].sStyleName, sTime); } else { - strcopy(sDisplay, 64, gS_StyleStrings[i].sStyleName); + strcopy(sDisplay, 64, gS_StyleStrings[iStyle].sStyleName); } - menu.AddItem(sInfo, sDisplay, (gI_RecordAmount[i][track] > 0 || !StrEqual(gA_WRCache[client].sClientMap, gS_Map))? ITEMDRAW_DEFAULT:ITEMDRAW_DISABLED); + menu.AddItem(sInfo, sDisplay, (gI_RecordAmount[iStyle][track] > 0 || !StrEqual(gA_WRCache[client].sClientMap, gS_Map))? ITEMDRAW_DEFAULT:ITEMDRAW_DISABLED); } // should NEVER happen @@ -2309,7 +2452,7 @@ public void SQL_OnFinish_Callback(Database db, DBResultSet results, const char[] void UpdateLeaderboards() { char sQuery[192]; - FormatEx(sQuery, 192, "SELECT style, time, track FROM %splayertimes WHERE map = '%s' ORDER BY time ASC, date ASC;", gS_MySQLPrefix, gS_Map); + FormatEx(sQuery, 192, "SELECT style, time, track FROM %splayertimes WHERE map = '%s' ORDER BY time ASC, date ASC LIMIT 1000;", gS_MySQLPrefix, gS_Map); gH_SQL.Query(SQL_UpdateLeaderboards_Callback, sQuery, 0); } diff --git a/addons/sourcemod/translations/shavit-core.phrases.txt b/addons/sourcemod/translations/shavit-core.phrases.txt index d5ada2b1..0fe6afda 100644 --- a/addons/sourcemod/translations/shavit-core.phrases.txt +++ b/addons/sourcemod/translations/shavit-core.phrases.txt @@ -113,8 +113,8 @@ // ---------- Zones ---------- // "StartZoneUndefined" { - "#format" "{1:s},{2:s}" - "en" "Your timer {1}will not{2} start as a start zone for the map is not defined." + "#format" "{1:s},{2:s},{3:s},{4:s},{5:s}" + "en" "Your timer {1}will not{2} start because the {3}{4}{5} start zone is not set." } "EndZoneUndefined" { diff --git a/addons/sourcemod/translations/shavit-hud.phrases.txt b/addons/sourcemod/translations/shavit-hud.phrases.txt index 6c1cc5d0..b266d17a 100644 --- a/addons/sourcemod/translations/shavit-hud.phrases.txt +++ b/addons/sourcemod/translations/shavit-hud.phrases.txt @@ -6,6 +6,11 @@ { "en" "Start Zone" } + "HudZoneTier" + { + "#format" "{1:d}" + "en" "Tier {1}" + } "HudInStartZone" { "#format" "{1:d}" @@ -131,6 +136,14 @@ { "en" "Timer track" } + "HudSplitPbText" + { + "en" "Split PB" + } + "HudMapTierText" + { + "en" "Map tier" + } // ---------- Record Bots ---------- // "ReplayText" { diff --git a/addons/sourcemod/translations/shavit-misc.phrases.txt b/addons/sourcemod/translations/shavit-misc.phrases.txt index dc4a070d..8920cf7c 100644 --- a/addons/sourcemod/translations/shavit-misc.phrases.txt +++ b/addons/sourcemod/translations/shavit-misc.phrases.txt @@ -11,6 +11,11 @@ "#format" "{1:s},{2:s},{3:s},{4:s}" "en" "You have to be {1}alive{2} or {3}spectate a player{4} to use this command." } + "CommandNoPause" + { + "#format" "{1:s},{2:s}" + "en" "Your timer has to be {1}resumed{2} to use this command." + } "CommandDisabled" { "#format" "{1:s},{2:s}" diff --git a/addons/sourcemod/translations/shavit-wr.phrases.txt b/addons/sourcemod/translations/shavit-wr.phrases.txt index b19fe1c9..528fc2cd 100644 --- a/addons/sourcemod/translations/shavit-wr.phrases.txt +++ b/addons/sourcemod/translations/shavit-wr.phrases.txt @@ -53,8 +53,8 @@ } "DeleteAllRecordsMenuTitle" { - "#format" "{1:s},{2:s}" - "en" "Delete ALL the records for '{1}'? (Track: {2})" + "#format" "{1:s},{2:s},{3:s}" + "en" "Delete ALL the records for '{1}'? (Track: {2} | Style: {3})" } "DeleteConfirm" { @@ -107,6 +107,11 @@ { "en" "Choose a track to delete a ALL records from:" } + "DeleteTrackAllStyle" + { + "#format" "{1:s}" + "en" "Choose a style to delete all the records for ({1}):" + } // ---------- Errors ---------- // "DatabaseError" {