diff --git a/addons/sourcemod/configs/shavit-chat.cfg b/addons/sourcemod/configs/shavit-chat.cfg index 99455dfc..158562bf 100644 --- a/addons/sourcemod/configs/shavit-chat.cfg +++ b/addons/sourcemod/configs/shavit-chat.cfg @@ -20,6 +20,8 @@ // {rank1} - player rank in percentage (1 decimal point) // {rank2} - player rank in percentage (2 decimal points) // {rank3} - player rank in percentage (3 decimal points) +// {styleXrank} - player rank on style X (X replaced with style ID) ie. "{style0rank}" +// {currentstylerank} - player rank on the style they are currently on, includes style name at the end ie. "#2 Normal" // {pts} - how many points a player has // {wrs} - how many wrs a player has // {wrrank} - player rank out of wrs held @@ -106,4 +108,20 @@ "name" "{rand}WRTOP10% {name}" "display" "WRTOP10%Top 10% of players by WRs held." } + + "11" + { + "free" "1" + "name" "{rand}[{currentstylerank}] {name}" + "message" "{default}" + "display" "[#Rank Current Style]Show your rank on your current style!" + } + + "12" + { + "free" "1" + "name" "{rand}[{style0rank} Normal] {name}" + "message" "{default}" + "display" "[#Rank Normal]Display your normal rank!" + } } diff --git a/addons/sourcemod/scripting/include/shavit/rankings.inc b/addons/sourcemod/scripting/include/shavit/rankings.inc index 1670c9bf..3e4793fc 100644 --- a/addons/sourcemod/scripting/include/shavit/rankings.inc +++ b/addons/sourcemod/scripting/include/shavit/rankings.inc @@ -47,6 +47,19 @@ forward void Shavit_OnTierAssigned(const char[] map, int tier); */ forward void Shavit_OnRankAssigned(int client, int rank, float points, bool first); +/** + * Gets called when the server acknowledges the client's ranking status for a style. + * It is called after OnClientPostAdminCheck and at forced rank recalculations. + * + * @param client Client index. + * @param style Style 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_OnStyleRankAssigned(int client, int style, int rank, float points, bool first); + /** * Gets the map tier for a specified map. * Use the map's display name. @@ -72,6 +85,15 @@ native StringMap Shavit_GetMapTiers(); */ native float Shavit_GetPoints(int client); +/** + * Gets player points on a style. + * + * @param client Client index. + * @param style Style index. + * @return Points. 0.0 if unranked. + */ +native float Shavit_GetStylePoints(int client, int style); + /** * Gets player rank. * @@ -80,6 +102,15 @@ native float Shavit_GetPoints(int client); */ native int Shavit_GetRank(int client); +/** + * Gets player rank on a style. + * + * @param client Client index. + * @param style Style index. + * @return Rank. 0 if unranked. + */ +native int Shavit_GetStyleRank(int client, int style); + /** * Gets the amount of players with over 0 points. * @@ -87,6 +118,14 @@ native int Shavit_GetRank(int client); */ native int Shavit_GetRankedPlayers(); +/** + * Gets the amount of players with over 0 points on a style. + * + * @param style Style index. + * @return Amount of ranked players. + */ +native int Shavit_GetStyleRankedPlayers(int style); + /** * Deletes tier setting for the specified map. * Points recalculation will run right after this is finished. @@ -164,8 +203,11 @@ public void __pl_shavit_rankings_SetNTVOptional() MarkNativeAsOptional("Shavit_GetMapTier"); MarkNativeAsOptional("Shavit_GetMapTiers"); MarkNativeAsOptional("Shavit_GetPoints"); + MarkNativeAsOptional("Shavit_GetStylePoints"); MarkNativeAsOptional("Shavit_GetRank"); + MarkNativeAsOptional("Shavit_GetStyleRank"); MarkNativeAsOptional("Shavit_GetRankedPlayers"); + MarkNativeAsOptional("Shavit_GetStyleRankedPlayers"); MarkNativeAsOptional("Shavit_Rankings_DeleteMap"); MarkNativeAsOptional("Shavit_GetWRCount"); MarkNativeAsOptional("Shavit_GetWRHolders"); diff --git a/addons/sourcemod/scripting/include/shavit/sql-create-tables-and-migrations.sp b/addons/sourcemod/scripting/include/shavit/sql-create-tables-and-migrations.sp index 99530982..f4703ec9 100644 --- a/addons/sourcemod/scripting/include/shavit/sql-create-tables-and-migrations.sp +++ b/addons/sourcemod/scripting/include/shavit/sql-create-tables-and-migrations.sp @@ -55,6 +55,7 @@ enum Migration_FixSQLiteMapzonesROWID, Migration_AddUsersFirstLogin, // 30 Migration_MoreFirstLoginStuff, + Migration_AddStylePoints, MIGRATIONS_END }; @@ -91,6 +92,7 @@ char gS_MigrationNames[][] = { "FixSQLiteMapzonesROWID", "AddUsersFirstLogin", "MoreFirstLoginStuff", + "AddStylePoints", }; static Database gH_SQL; @@ -182,6 +184,11 @@ public void SQL_CreateTables(Database hSQL, const char[] prefix, int driver) gS_SQLPrefix, sOptionalINNODB); AddQueryLog(trans, sQuery); + FormatEx(sQuery, sizeof(sQuery), + "CREATE TABLE IF NOT EXISTS `%sstylepoints` (`auth` INT NOT NULL, `style` TINYINT NOT NULL, `points` FLOAT NOT NULL DEFAULT 0, PRIMARY KEY (`auth`, `style`));", + gS_SQLPrefix); + AddQueryLog(trans, sQuery); + // //// shavit-stats // @@ -375,6 +382,7 @@ void ApplyMigration(int migration) case Migration_FixSQLiteMapzonesROWID: ApplyMigration_FixSQLiteMapzonesROWID(); case Migration_AddUsersFirstLogin: ApplyMigration_AddUsersFirstLogin(); case Migration_MoreFirstLoginStuff: ApplyMigration_MoreFirstLoginStuff(); + case Migration_AddStylePoints: ApplyMigration_AddStylePoints(); } } @@ -768,6 +776,13 @@ public void ApplyMigration_MoreFirstLoginStuff() gH_SQL.Execute(trans, Trans_MigrationSimple, TransMigrationSimple_Error, Migration_MoreFirstLoginStuff); } +void ApplyMigration_AddStylePoints() +{ + char sQuery[256]; + FormatEx(sQuery, sizeof(sQuery), "INSERT INTO %sstylepoints (auth, style) SELECT UNIQUE auth, style FROM %splayertimes;", gS_SQLPrefix, gS_SQLPrefix); + QueryLog(gH_SQL, SQL_TableMigrationSingleQuery_Callback, sQuery, Migration_AddStylePoints, DBPrio_High); +} + public void SQL_TableMigrationSingleQuery_Callback(Database db, DBResultSet results, const char[] error, any data) { InsertMigration(data); diff --git a/addons/sourcemod/scripting/shavit-chat.sp b/addons/sourcemod/scripting/shavit-chat.sp index 28c7527e..29a21707 100644 --- a/addons/sourcemod/scripting/shavit-chat.sp +++ b/addons/sourcemod/scripting/shavit-chat.sp @@ -108,12 +108,16 @@ char gS_CustomName[MAXPLAYERS+1][128]; char gS_CustomMessage[MAXPLAYERS+1][16]; chatstrings_t gS_ChatStrings; +stylestrings_t gS_StyleStrings[STYLE_LIMIT]; // chat procesor bool gB_Protobuf = false; bool gB_NewMessage[MAXPLAYERS+1]; StringMap gSM_Messages = null; +//style ranks +int gI_Styles = 0; + char gS_ControlCharacters[][] = {"\x01", "\x02", "\x03", "\x04", "\x05", "\x06", "\x07", "\x08", "\x09", "\x0A", "\x0B", "\x0C", "\x0D", "\x0E", "\x0F", "\x10" }; @@ -215,6 +219,19 @@ public void Shavit_OnChatConfigLoaded() Shavit_GetChatStringsStruct(gS_ChatStrings); } +public void Shavit_OnStyleConfigLoaded(int styles) +{ + gI_Styles = styles; + + for (int i = 0; i < STYLE_LIMIT; i++) + { + if (i < styles) + { + Shavit_GetStyleStringsStruct(i, gS_StyleStrings[i]); + } + } +} + public void OnMapStart() { if(!LoadChatConfig()) @@ -1319,6 +1336,31 @@ void FormatChat(int client, char[] buffer, int size) IntToString(iRank, temp, 32); ReplaceString(buffer, size, "{rank}", temp); + int iStyleRank; + char sStyleRank[16]; + char sStyleBuf[32]; + for (int i = 0; i < gI_Styles; i++) + { + iStyleRank = Shavit_GetStyleRank(client, i); + if(iStyleRank == 0) + { + strcopy(sStyleRank, 16, "Unranked"); + } + else + { + IntToString(iStyleRank, sStyleRank, 16); + Format(sStyleRank, 16, "#%s", sStyleRank); + } + FormatEx(sStyleBuf, 32, "{style%drank}", i); + ReplaceString(buffer, size, sStyleBuf, sStyleRank); + + if(i == Shavit_GetBhopStyle(client)) + { + FormatEx(sStyleBuf, 32, "%s %s", sStyleRank, gS_StyleStrings[i].sStyleName); + ReplaceString(buffer, size, "{currentstylerank}", sStyleBuf); + } + } + int iRanked = Shavit_GetRankedPlayers(); if(iRanked == 0) @@ -1558,6 +1600,31 @@ public int Native_GetPlainChatrank(Handle handler, int numParams) IntToString(iRank, sRank, 16); ReplaceString(buf, sizeof(buf), "{rank}", sRank); + int iStyleRank; + char sStyleRank[16]; + char sStyleBuf[32]; + for (int i = 0; i < gI_Styles; i++) + { + iStyleRank = Shavit_GetStyleRank(client, i); + if(iStyleRank == 0) + { + strcopy(sStyleRank, 16, "Unranked"); + } + else + { + IntToString(iStyleRank, sStyleRank, 16); + Format(sStyleRank, 16, "#%s", sStyleRank); + } + FormatEx(sStyleBuf, 32, "{style%drank}", i); + ReplaceString(buf, sizeof(buf), sStyleBuf, sStyleRank); + + if(i == Shavit_GetBhopStyle(client)) + { + FormatEx(sStyleBuf, 32, "%s %s", sStyleRank, gS_StyleStrings[i].sStyleName); + ReplaceString(buf, sizeof(buf), "{currentstylerank}", sStyleBuf); + } + } + int iRanked = Shavit_GetRankedPlayers(); if (iRanked == 0) diff --git a/addons/sourcemod/scripting/shavit-rankings.sp b/addons/sourcemod/scripting/shavit-rankings.sp index 6f37e457..2f9a4cf6 100644 --- a/addons/sourcemod/scripting/shavit-rankings.sp +++ b/addons/sourcemod/scripting/shavit-rankings.sp @@ -1,6 +1,6 @@ /* * shavit's Timer - Rankings - * by: shavit, rtldg + * by: shavit, rtldg, olivia * * This file is part of shavit's Timer (https://github.com/shavitush/bhoptimer) * @@ -59,7 +59,9 @@ enum struct ranking_t { int iRank; + int iStyleRank[STYLE_LIMIT]; float fPoints; + float fStylePoints[STYLE_LIMIT]; int iWRAmountAll; int iWRAmountCvar; int iWRHolderRankAll; @@ -68,6 +70,35 @@ enum struct ranking_t int iWRHolderRank[STYLE_LIMIT*2]; } +enum struct stylerankcache_t +{ + int iMenuPosition; + int iStyle; + int iTarget; +} + +enum struct styletop_t +{ + ArrayList sName; + int iSteamID[100]; + float fPoints[100]; + + void Init() + { + if(this.sName != null) + { + delete this.sName; + } + + this.sName = new ArrayList(ByteCountToCells(33), 100); + + for(int x = 0; x < this.sName.Length; x++) + { + this.sName.SetString(x, ""); + } + } +} + char gS_MySQLPrefix[32]; Database gH_SQL = null; bool gB_SQLWindowFunctions = false; @@ -99,15 +130,23 @@ Convar gCV_DefaultTier = null; ranking_t gA_Rankings[MAXPLAYERS+1]; int gI_RankedPlayers = 0; +int gI_StyleRankedPlayers[STYLE_LIMIT] = {0, ...}; + Menu gH_Top100Menu = null; +styletop_t gA_StyleTop[STYLE_LIMIT]; Handle gH_Forwards_OnTierAssigned = null; Handle gH_Forwards_OnRankAssigned = null; +Handle gH_Forwards_OnStyleRankAssigned = null; // Timer settings. chatstrings_t gS_ChatStrings; +stylestrings_t gS_StyleStrings[STYLE_LIMIT]; int gI_Styles = 0; +// Cache +stylerankcache_t gA_StyleRankCache[MAXPLAYERS+1]; + bool gB_WorldRecordsCached = false; bool gB_WRHolderTablesMade = false; bool gB_WRHoldersRefreshed = false; @@ -119,7 +158,7 @@ int gI_WRHoldersCvar; public Plugin myinfo = { name = "[shavit] Rankings", - author = "shavit, rtldg", + author = "shavit, rtldg, olivia", description = "A fair and competitive ranking system for shavit's bhoptimer.", version = SHAVIT_VERSION, url = "https://github.com/shavitush/bhoptimer" @@ -130,8 +169,11 @@ public APLRes AskPluginLoad2(Handle myself, bool late, char[] error, int err_max CreateNative("Shavit_GetMapTier", Native_GetMapTier); CreateNative("Shavit_GetMapTiers", Native_GetMapTiers); CreateNative("Shavit_GetPoints", Native_GetPoints); + CreateNative("Shavit_GetStylePoints", Native_GetStylePoints); CreateNative("Shavit_GetRank", Native_GetRank); + CreateNative("Shavit_GetStyleRank", Native_GetStyleRank); CreateNative("Shavit_GetRankedPlayers", Native_GetRankedPlayers); + CreateNative("Shavit_GetStyleRankedPlayers", Native_GetStyleRankedPlayers); CreateNative("Shavit_Rankings_DeleteMap", Native_Rankings_DeleteMap); CreateNative("Shavit_GetWRCount", Native_GetWRCount); CreateNative("Shavit_GetWRHolders", Native_GetWRHolders); @@ -147,16 +189,24 @@ public APLRes AskPluginLoad2(Handle myself, bool late, char[] error, int err_max public void OnPluginStart() { + for (int i = 0; i < sizeof(gA_StyleTop); i++) + { + gA_StyleTop[i].Init(); + } + gEV_Type = GetEngineVersion(); 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); + gH_Forwards_OnStyleRankAssigned = CreateGlobalForward("Shavit_OnStyleRankAssigned", ET_Event, Param_Cell, 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)"); RegConsoleCmd("sm_rank", Command_Rank, "Show your or someone else's rank. Usage: sm_rank [name]"); RegConsoleCmd("sm_top", Command_Top, "Show the top 100 players."); + RegConsoleCmd("sm_stylerank", Command_Rank, "Show your or someone else's rank on a style. Usage: sm_stylerank [name]"); + RegConsoleCmd("sm_styletop", Command_Top, "Show the top 100 players for a style."); RegAdminCmd("sm_settier", Command_SetTier, ADMFLAG_RCON, "Change the map's tier. Usage: sm_settier [map]"); RegAdminCmd("sm_setmaptier", Command_SetTier, ADMFLAG_RCON, "Change the map's tier. Usage: sm_setmaptier [map] (sm_settier alias)"); @@ -204,6 +254,14 @@ public void Shavit_OnChatConfigLoaded() public void Shavit_OnStyleConfigLoaded(int styles) { gI_Styles = styles; + + for (int i = 0; i < STYLE_LIMIT; i++) + { + if (i < styles) + { + Shavit_GetStyleStringsStruct(i, gS_StyleStrings[i]); + } + } } public void OnLibraryAdded(const char[] name) @@ -288,6 +346,31 @@ void CreateGetWeightedPointsFunction() "RETURN total; " ... "END;;", gS_MySQLPrefix, sWeightingLimit, gCV_WeightingMultiplier.FloatValue); + AddQueryLog(trans, sQuery); + + FormatEx(sQuery, sizeof(sQuery), + "CREATE FUNCTION GetStyleWeightedPoints(steamid INT, istyle INT) " ... + "RETURNS FLOAT " ... + "READS SQL DATA " ... + "BEGIN " ... + "DECLARE p FLOAT; " ... + "DECLARE total FLOAT DEFAULT 0.0; " ... + "DECLARE mult FLOAT DEFAULT 1.0; " ... + "DECLARE done INT DEFAULT 0; " ... + "DECLARE cur CURSOR FOR SELECT points FROM %splayertimes WHERE auth = steamid AND style = istyle AND points > 0.0 ORDER BY points DESC %s; " ... + "DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = 1; " ... + "OPEN cur; " ... + "iter: LOOP " ... + "FETCH cur INTO p; " ... + "IF done THEN " ... + "LEAVE iter; " ... + "END IF; " ... + "SET total = total + (p * mult); " ... + "SET mult = mult * %f; " ... + "END LOOP; " ... + "CLOSE cur; " ... + "RETURN total; " ... + "END;;", gS_MySQLPrefix, sWeightingLimit, gCV_WeightingMultiplier.FloatValue); AddQueryLog(trans, sQuery); @@ -310,12 +393,42 @@ public void OnClientConnected(int client) { ranking_t empty_ranking; gA_Rankings[client] = empty_ranking; + + stylerankcache_t empty_cache; + gA_StyleRankCache[client] = empty_cache; } public void OnClientAuthorized(int client, const char[] auth) { if (gH_SQL && !IsFakeClient(client)) { + int iSteamID = GetSteamAccountID(client); + + if(iSteamID == 0) + { + return; + } + + char sQuery[512]; + + for (int i = 0; i < gI_Styles; i++) + { + if (gI_Driver == Driver_mysql) + { + FormatEx(sQuery, 512, + "INSERT IGNORE INTO %sstylepoints (auth, style, points) VALUES (%d, %d, 0);", + gS_MySQLPrefix, iSteamID, i); + } + else // postgresql & sqlite + { + FormatEx(sQuery, 512, + "INSERT INTO %sstylepoints (auth, style, points) VALUES (%d, %d, 0) ON CONFLICT DO NOTHING;", + gS_MySQLPrefix, iSteamID, i); + } + + QueryLog(gH_SQL, SQL_InsertUser_Callback, sQuery, GetClientSerial(client)); + } + if (gB_WRHolderTablesMade) { UpdateWRs(client); @@ -325,6 +438,25 @@ public void OnClientAuthorized(int client, const char[] auth) } } +public void SQL_InsertUser_Callback(Database db, DBResultSet results, const char[] error, any data) +{ + if(results == null) + { + int client = GetClientFromSerial(data); + + if(client == 0) + { + LogError("Timer error! (rankings) Failed to insert a disconnected player's data to the table. Reason: %s", error); + } + else + { + LogError("Timer error! (rankings) Failed to insert \"%N\"'s data to the table. Reason: %s", client, error); + } + + return; + } +} + public void OnMapStart() { GetLowercaseMapName(gS_Map); @@ -569,45 +701,224 @@ public Action Command_Rank(int client, int args) { int target = client; - if(args > 0) + if (args > 0) { char sArgs[MAX_TARGET_LENGTH]; GetCmdArgString(sArgs, MAX_TARGET_LENGTH); target = FindTarget(client, sArgs, true, false); - if(target == -1) + if (target == -1) { return Plugin_Handled; } } - if(gA_Rankings[target].fPoints == 0.0) + if(IsValidClient(client)) { - Shavit_PrintToChat(client, "%T", "Unranked", client, gS_ChatStrings.sVariable2, target, gS_ChatStrings.sText); - - return Plugin_Handled; + gA_StyleRankCache[client].iTarget = target; + OpenRankMenu(client, 0); } - Shavit_PrintToChat(client, "%T", "Rank", client, gS_ChatStrings.sVariable2, target, gS_ChatStrings.sText, - gS_ChatStrings.sVariable, (gA_Rankings[target].iRank > gI_RankedPlayers)? gI_RankedPlayers:gA_Rankings[target].iRank, gS_ChatStrings.sText, - gI_RankedPlayers, - gS_ChatStrings.sVariable, gA_Rankings[target].fPoints, gS_ChatStrings.sText); - return Plugin_Handled; } +void OpenRankMenu(int client, int position = 0) +{ + if(!IsValidClient(gA_StyleRankCache[client].iTarget)) + { + return; + } + + Menu menu = new Menu(MenuHandler_Rank); + + char sDisplay[32]; + GetClientName(gA_StyleRankCache[client].iTarget, sDisplay, sizeof(sDisplay)); + + SetMenuTitle(menu, "%T\n ", "StyleRankTitle", client, sDisplay); + + FormatEx(sDisplay, 32, "%T\n ", "Overall", client); + menu.AddItem("-1", sDisplay); + + int[] iOrderedStyles = new int[gI_Styles]; + Shavit_GetOrderedStyles(iOrderedStyles, gI_Styles); + + for(int i = 0; i < gI_Styles; i++) + { + int iStyle = iOrderedStyles[i]; + + if(Shavit_GetStyleSettingInt(iStyle, "enabled") == -1) + { + continue; + } + + char sStyle[8]; + IntToString(iStyle, sStyle, sizeof(sStyle)); + menu.AddItem(sStyle, gS_StyleStrings[iStyle].sStyleName); + } + + menu.ExitButton = true; + DisplayMenuAtItem(menu, client, position, MENU_TIME_FOREVER); +} + +public void MenuHandler_Rank(Menu menu, MenuAction action, int param1, int param2) +{ + if(action == MenuAction_Select) + { + if(!IsValidClient(gA_StyleRankCache[param1].iTarget)) + { + return; + } + + char sStyle[8]; + menu.GetItem(param2, sStyle, sizeof(sStyle)); + int iStyle = StringToInt(sStyle); + int target = gA_StyleRankCache[param1].iTarget; + + if (iStyle < 0 || iStyle >= gI_Styles) + { + iStyle = -1; + } + + if (gA_Rankings[target].fPoints == 0.0) + { + Shavit_PrintToChat(param1, "%T", "Unranked", param1, gS_ChatStrings.sVariable2, target, gS_ChatStrings.sText); + } + else if (iStyle > -1 && gA_Rankings[target].fStylePoints[iStyle] == 0.0) + { + Shavit_PrintToChat(param1, "%T", "UnrankedOnStyle", param1, gS_ChatStrings.sVariable2, target, gS_ChatStrings.sText, gS_ChatStrings.sStyle, gS_StyleStrings[iStyle].sStyleName, gS_ChatStrings.sText); + } + else + { + if (iStyle == -1) + { + Shavit_PrintToChat(param1, "%T", "Rank", param1, gS_ChatStrings.sVariable2, target, gS_ChatStrings.sText, + gS_ChatStrings.sVariable, (gA_Rankings[target].iRank > gI_RankedPlayers)? gI_RankedPlayers:gA_Rankings[target].iRank, gS_ChatStrings.sText, + gI_RankedPlayers, + gS_ChatStrings.sVariable, gA_Rankings[target].fPoints, gS_ChatStrings.sText); + } + else + { + Shavit_PrintToChat(param1, "%T", "StyleRank", param1, gS_ChatStrings.sVariable2, target, gS_ChatStrings.sText, + gS_ChatStrings.sVariable, (gA_Rankings[target].iStyleRank[iStyle] > gI_StyleRankedPlayers[iStyle])? gI_StyleRankedPlayers[iStyle]:gA_Rankings[target].iStyleRank[iStyle], gS_ChatStrings.sText, + gI_StyleRankedPlayers[iStyle], + gS_ChatStrings.sVariable, gA_Rankings[target].fStylePoints[iStyle], gS_ChatStrings.sText, + gS_ChatStrings.sStyle, gS_StyleStrings[iStyle].sStyleName, gS_ChatStrings.sText); + } + } + + OpenRankMenu(param1, GetMenuSelectionPosition()); + } + else if (action == MenuAction_End) + delete menu; +} + public Action Command_Top(int client, int args) { - if(gH_Top100Menu != null) + if(IsValidClient(client)) { - gH_Top100Menu.SetTitle("%T (%d)\n ", "Top100", client, gI_RankedPlayers); - gH_Top100Menu.Display(client, MENU_TIME_FOREVER); + OpenTopMainMenu(client, 0); } return Plugin_Handled; } +void OpenTopMainMenu(int client, int position = 0) +{ + Menu menu = new Menu(MenuHandler_TopMain); + + char sDisplay[32]; + + SetMenuTitle(menu, "%T\n ", "StyleTopTitle", client); + + FormatEx(sDisplay, 32, "%T\n ", "Overall", client); + menu.AddItem("-1", sDisplay); + + int[] iOrderedStyles = new int[gI_Styles]; + Shavit_GetOrderedStyles(iOrderedStyles, gI_Styles); + + for(int i = 0; i < gI_Styles; i++) + { + int iStyle = iOrderedStyles[i]; + + if(Shavit_GetStyleSettingInt(iStyle, "enabled") == -1) + { + continue; + } + + char sStyle[8]; + IntToString(iStyle, sStyle, sizeof(sStyle)); + menu.AddItem(sStyle, gS_StyleStrings[iStyle].sStyleName); + } + + menu.ExitButton = true; + DisplayMenuAtItem(menu, client, position, MENU_TIME_FOREVER); +} + +public void MenuHandler_TopMain(Menu menu, MenuAction action, int param1, int param2) +{ + if(action == MenuAction_Select) + { + gA_StyleRankCache[param1].iMenuPosition = GetMenuSelectionPosition(); + char sStyle[8]; + menu.GetItem(param2, sStyle, sizeof(sStyle)); + int iStyle = StringToInt(sStyle); + + if (iStyle < 0 || iStyle >= gI_Styles) + { + iStyle = -1; + } + + if(iStyle == -1) + { + if(gH_Top100Menu != null) + { + gH_Top100Menu.SetTitle("%T (%d)\n ", "Top100", param1, gI_RankedPlayers); + gH_Top100Menu.Display(param1, MENU_TIME_FOREVER); + } + } + else + { + gA_StyleRankCache[param1].iStyle = iStyle; + OpenStyleTopMenu(param1); + } + } + else if (action == MenuAction_End) + delete menu; +} + +void OpenStyleTopMenu(int client) +{ + int iStyle = gA_StyleRankCache[client].iStyle; + char sDisplay[96]; + + Menu menu = new Menu(MenuHandler_Top); + + menu.SetTitle("%T (%d)\n ", "StyleTop100", client, gS_StyleStrings[iStyle].sStyleName, gI_StyleRankedPlayers[iStyle]); + + if (gI_StyleRankedPlayers[iStyle] == 0) + { + FormatEx(sDisplay, 64, "%T", "NoRankedPlayers", client); + menu.AddItem("-1", sDisplay, ITEMDRAW_DISABLED); + } + else + { + char sName[33]; + char sSteamID[33]; + for (int i = 0; i < (gI_StyleRankedPlayers[iStyle] < 100 ? gI_StyleRankedPlayers[iStyle] : 100); i++) + { + gA_StyleTop[iStyle].sName.GetString(i, sName, sizeof(sName)); + IntToString(gA_StyleTop[iStyle].iSteamID[i], sSteamID, sizeof(sSteamID)); + FormatEx(sDisplay, 96, "#%d - %s (%.2f)", i+1, sName, gA_StyleTop[iStyle].fPoints[i]); + menu.AddItem(sSteamID, sDisplay); + } + } + + menu.ExitBackButton = true; + menu.ExitButton = true; + DisplayMenu(menu, client, MENU_TIME_FOREVER); +} + public int MenuHandler_Top(Menu menu, MenuAction action, int param1, int param2) { if(action == MenuAction_Select) @@ -620,6 +931,11 @@ public int MenuHandler_Top(Menu menu, MenuAction action, int param1, int param2) FakeClientCommand(param1, "sm_profile [U:1:%s]", sInfo); } } + else if(action == MenuAction_Cancel) + { + if(param2 == MenuCancel_ExitBack) + OpenTopMainMenu(param1, gA_StyleRankCache[param1].iMenuPosition); + } return 0; } @@ -877,6 +1193,8 @@ public Action Command_RecalcAll(int client, int args) AddQueryLog(trans, sQuery); FormatEx(sQuery, sizeof(sQuery), "UPDATE %susers SET points = 0;", gS_MySQLPrefix); AddQueryLog(trans, sQuery); + FormatEx(sQuery, sizeof(sQuery), "UPDATE %sstylepoints SET points = 0;", gS_MySQLPrefix); + AddQueryLog(trans, sQuery); for(int i = 0; i < gI_Styles; i++) { @@ -903,7 +1221,7 @@ public void Trans_OnRecalcSuccess(Database db, any data, int numQueries, DBResul SetCmdReplySource(SM_REPLY_TO_CONSOLE); } - ReplyToCommand(client, "- Finished recalculating all points. Recalculating user points, top 100 and user cache."); + ReplyToCommand(client, "- Finished recalculating all points. Recalculating user points, top 100 and user cache for all styles."); UpdateAllPoints(true); } @@ -1015,13 +1333,19 @@ void UpdatePointsForSinglePlayer(int client) { int auth = GetSteamAccountID(client); - char sQuery[1024]; + char sQuery[1024], sStyleQuery[1024]; if (gCV_WeightingMultiplier.FloatValue == 1.0 || gB_SqliteHatesPOW) { FormatEx(sQuery, sizeof(sQuery), "UPDATE %susers SET points = (SELECT SUM(points) FROM %splayertimes WHERE auth = %d) WHERE auth = %d;", gS_MySQLPrefix, gS_MySQLPrefix, auth, auth); + + FormatEx(sStyleQuery, sizeof(sStyleQuery), + "UPDATE IGNORE %sstylepoints AS S INNER JOIN (SELECT auth, style, SUM(points) AS total FROM %splayertimes WHERE auth = %d GROUP BY style) P ON S.auth = P.auth SET S.points = P.total WHERE S.style = P.style;", + gS_MySQLPrefix, gS_MySQLPrefix, auth); + + QueryLog(gH_SQL, SQL_UpdateAllStylePoints_Callback, sStyleQuery); } else if (gB_SQLWindowFunctions) { @@ -1045,12 +1369,40 @@ void UpdatePointsForSinglePlayer(int client) sLimit, auth ); + + FormatEx(sStyleQuery, sizeof(sStyleQuery), + "UPDATE IGNORE %sstylepoints AS S SET points = (\n" + ... " SELECT SUM(points2) FROM (\n" + ... " SELECT style, (points * POW(%f, ROW_NUMBER() OVER (ORDER BY points DESC) - 1)) as points2\n" + ... " FROM %splayertimes\n" + ... " WHERE auth = %d AND points > 0 GROUP BY style\n" + ... " ORDER BY points DESC %s\n" + ... " ) as t\n" + ... ") P WHERE auth = %d AND S.style = t.style;", + gS_MySQLPrefix, + gCV_WeightingMultiplier.FloatValue, + gS_MySQLPrefix, + auth, + sLimit, + auth + ); + + QueryLog(gH_SQL, SQL_UpdateAllStylePoints_Callback, sStyleQuery); } else // We should only be here if mysql :) { FormatEx(sQuery, sizeof(sQuery), "UPDATE %susers SET points = GetWeightedPoints(auth) WHERE auth = %d;", gS_MySQLPrefix, auth); + + for (int i = 0; i < gI_Styles; i++) + { + FormatEx(sStyleQuery, sizeof(sStyleQuery), + "UPDATE IGNORE %sstylepoints SET points = GetStyleWeightedPoints(auth, %d) WHERE auth = %d and style = %d;", + gS_MySQLPrefix, i, auth, i); + + QueryLog(gH_SQL, SQL_UpdateAllStylePoints_Callback, sStyleQuery); + } } QueryLog(gH_SQL, SQL_UpdateAllPoints_Callback, sQuery, GetClientSerial(client)); @@ -1062,7 +1414,7 @@ void UpdateAllPoints(bool recalcall=false, char[] map="", int track=-1) LogError("DEBUG: 6 (UpdateAllPoints)"); #endif - char sQuery[1024]; + char sQuery[1024], sStyleQuery[1024]; char sLastLogin[69], sLimit[30], sMapWhere[512], sTrackWhere[64]; if (track != -1) @@ -1086,6 +1438,13 @@ void UpdateAllPoints(bool recalcall=false, char[] map="", int track=-1) "UPDATE %susers AS U SET points = P.total FROM (SELECT auth, SUM(points) AS total FROM %splayertimes GROUP BY auth) P WHERE U.auth = P.auth %s %s;", gS_MySQLPrefix, gS_MySQLPrefix, (sLastLogin[0] != 0) ? "AND " : "", sLastLogin); + + FormatEx(sStyleQuery, sizeof(sStyleQuery), + "UPDATE IGNORE %sstylepoints AS S SET points = P.total FROM (SELECT auth, style, SUM(points) AS total FROM %splayertimes GROUP BY auth, style) P WHERE S.auth = P.auth AND S.style = P.style %s %s;", + gS_MySQLPrefix, gS_MySQLPrefix, + (sLastLogin[0] != 0) ? "AND " : "", sLastLogin); + + QueryLog(gH_SQL, SQL_UpdateAllStylePoints_Callback, sStyleQuery); } else { @@ -1093,6 +1452,13 @@ void UpdateAllPoints(bool recalcall=false, char[] map="", int track=-1) "UPDATE %susers AS U INNER JOIN (SELECT auth, SUM(points) AS total FROM %splayertimes GROUP BY auth) P ON U.auth = P.auth SET U.points = P.total %s %s;", gS_MySQLPrefix, gS_MySQLPrefix, (sLastLogin[0] != 0) ? "WHERE" : "", sLastLogin); + + FormatEx(sStyleQuery, sizeof(sStyleQuery), + "UPDATE IGNORE %sstylepoints AS S INNER JOIN (SELECT auth, style, SUM(points) AS total FROM %splayertimes GROUP BY auth, style) P ON S.auth = P.auth SET S.points = P.total WHERE S.style = P.style %s %s;", + gS_MySQLPrefix, gS_MySQLPrefix, + (sLastLogin[0] != 0) ? "WHERE" : "", sLastLogin); + + QueryLog(gH_SQL, SQL_UpdateAllStylePoints_Callback, sStyleQuery); } } else if (gB_SQLWindowFunctions && gI_Driver == Driver_mysql) @@ -1126,6 +1492,34 @@ void UpdateAllPoints(bool recalcall=false, char[] map="", int track=-1) (sMapWhere[0] && sTrackWhere[0]) ? "AND" : "", sTrackWhere, sLimit); // TODO: Remove/move sLimit? + + FormatEx(sStyleQuery, sizeof(sStyleQuery), + "UPDATE IGNORE %sstylepoints AS s, (\n" + ... " SELECT auth, SUM(t.points2) as pp FROM (\n" + ... " SELECT p.auth, (p.points * POW(%f, ROW_NUMBER() OVER (PARTITION BY p.auth ORDER BY p.points DESC) - 1)) as points2\n" + ... " FROM %splayertimes AS p\n" + ... " JOIN %sstylepoints AS s2\n" + ... " ON s2.auth = p.auth %s %s\n" + ... " WHERE p.points > 0 AND p.auth IN (SELECT DISTINCT auth FROM %splayertimes %s %s %s %s)\n" + ... " ORDER BY p.points DESC GROUP BY style %s\n" + ... " ) AS t\n" + ... " GROUP by auth, style\n" + ... ") AS a\n" + ... "SET s.points = a.pp\n" + ... "WHERE s.auth = a.auth and s.style = a.style;", + gS_MySQLPrefix, + gCV_WeightingMultiplier.FloatValue, + gS_MySQLPrefix, + gS_MySQLPrefix, + sLastLogin[0] ? "AND" : "", sLastLogin, + gS_MySQLPrefix, + sMapWhere[0] ? "AND" : "", + sMapWhere, + sTrackWhere[0] ? "AND" : "", + sTrackWhere, + sLimit); // TODO: Remove/move sLimit? + + QueryLog(gH_SQL, SQL_UpdateAllStylePoints_Callback, sStyleQuery); } else if (gB_SQLWindowFunctions) { @@ -1150,6 +1544,30 @@ void UpdateAllPoints(bool recalcall=false, char[] map="", int track=-1) sMapWhere, (sMapWhere[0] && sTrackWhere[0]) ? "AND" : "", sTrackWhere); + + FormatEx(sStyleQuery, sizeof(sStyleQuery), + "UPDATE IGNORE %sstylepoints AS s\n" + ... "SET points = (\n" + ... " SELECT SUM(points2) FROM (\n" + ... " SELECT (points * POW(%f, ROW_NUMBER() OVER (ORDER BY points DESC) - 1)) AS points2\n" + ... " FROM %splayertimes\n" + ... " WHERE auth = s.auth AND points > 0\n" + ... " ORDER BY points DESC GROUP BY style %s\n" + ... " ) AS t\n" + ... ") p WHERE %s %s auth IN\n" + ... " (SELECT DISTINCT auth FROM %splayertimes WHERE style = t.style %s %s %s %s);", + gS_MySQLPrefix, + gCV_WeightingMultiplier.FloatValue, + gS_MySQLPrefix, + sLimit, // TODO: Remove/move sLimit? + sLastLogin, sLastLogin[0] ? "AND" : "", + gS_MySQLPrefix, + sMapWhere[0] ? "AND" : "", + sMapWhere, + sTrackWhere[0] ? "AND" : "", + sTrackWhere); + + QueryLog(gH_SQL, SQL_UpdateAllStylePoints_Callback, sStyleQuery); } else // !gB_SQLWindowFunctions && gI_Driver == Driver_mysql { @@ -1162,6 +1580,21 @@ void UpdateAllPoints(bool recalcall=false, char[] map="", int track=-1) sMapWhere, (sMapWhere[0] && sTrackWhere[0]) ? "AND" : "", sTrackWhere); + + for (int i = 0; i < gI_Styles; i++) + { + FormatEx(sQuery, sizeof(sQuery), + "UPDATE IGNORE %sstylepoints SET points = GetWeightedStylePoints(auth, %d) WHERE %s %s auth IN (SELECT DISTINCT auth FROM %splayertimes WHERE style = %d %s %s %s %s);", + gS_MySQLPrefix, i, + sLastLogin, (sLastLogin[0] != 0) ? "AND" : "", + gS_MySQLPrefix, i, + sMapWhere[0] ? "AND" : "", + sMapWhere, + sTrackWhere[0] ? "AND" : "", + sTrackWhere); + + QueryLog(gH_SQL, SQL_UpdateAllStylePoints_Callback, sStyleQuery); + } } QueryLog(gH_SQL, SQL_UpdateAllPoints_Callback, sQuery); @@ -1187,6 +1620,14 @@ public void SQL_UpdateAllPoints_Callback(Database db, DBResultSet results, const } } +public void SQL_UpdateAllStylePoints_Callback(Database db, DBResultSet results, const char[] error, any data) +{ + if(results == null) + { + LogError("Timer (rankings, update all style points) error! Reason: %s", error); + } +} + void UpdatePlayerRank(int client, bool first) { int iSteamID = 0; @@ -1204,6 +1645,19 @@ void UpdatePlayerRank(int client, bool first) hPack.WriteCell(first); QueryLog(gH_SQL, SQL_UpdatePlayerRank_Callback, sQuery, hPack, DBPrio_Low); + + for (int i = 0; i < gI_Styles; i++) + { + FormatEx(sQuery, 512, "SELECT s2.points, COUNT(*) FROM %sstylepoints s1 JOIN (SELECT points FROM %sstylepoints WHERE auth = %d AND style = %d) s2 WHERE style = %d AND s1.points >= s2.points;", + gS_MySQLPrefix, gS_MySQLPrefix, iSteamID, i, i); + + DataPack hStylePack = new DataPack(); + hStylePack.WriteCell(GetClientSerial(client)); + hStylePack.WriteCell(first); + hStylePack.WriteCell(i); + + QueryLog(gH_SQL, SQL_UpdatePlayerStyleRank_Callback, sQuery, hStylePack, DBPrio_Low); + } } } @@ -1244,6 +1698,45 @@ public void SQL_UpdatePlayerRank_Callback(Database db, DBResultSet results, cons } } +public void SQL_UpdatePlayerStyleRank_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()); + int iStyle = hPack.ReadCell(); + delete hPack; + + if(results == null) + { + LogError("Timer (rankings, update player style rank) error! Reason: %s", error); + + return; + } + + int client = GetClientFromSerial(iSerial); + + if(client == 0) + { + return; + } + + if(results.FetchRow()) + { + gA_Rankings[client].fStylePoints[iStyle] = results.FetchFloat(0); + gA_Rankings[client].iStyleRank[iStyle] = (gA_Rankings[client].fStylePoints[iStyle] > 0.0)? results.FetchInt(1):0; + + Call_StartForward(gH_Forwards_OnStyleRankAssigned); + Call_PushCell(client); + Call_PushCell(iStyle); + Call_PushCell(gA_Rankings[client].iStyleRank[iStyle]); + Call_PushCell(gA_Rankings[client].fStylePoints[iStyle]); + Call_PushCell(bFirst); + Call_Finish(); + } +} + void UpdateTop100() { char sQuery[512]; @@ -1254,6 +1747,21 @@ void UpdateTop100() gS_MySQLPrefix, gS_MySQLPrefix); QueryLog(gH_SQL, SQL_UpdateTop100_Callback, sQuery, 0, DBPrio_High); + + for (int i = 0; i < gI_Styles; i++) + { + FormatEx(sQuery, sizeof(sQuery), + "SELECT COUNT(*) as c, '' as auth, '' as name, '' as points FROM %sstylepoints as s WHERE style = %d AND points > 0.0 \ + UNION ALL \ + SELECT '' as c, %sstylepoints.auth, %susers.name, %sstylepoints.points \ + FROM %sstylepoints \ + INNER JOIN %susers \ + ON %sstylepoints.auth = %susers.auth \ + WHERE %sstylepoints.points > 0 AND %sstylepoints.style = %d ORDER BY c DESC, CAST(points AS FLOAT) DESC LIMIT 101;", + gS_MySQLPrefix, i, gS_MySQLPrefix, gS_MySQLPrefix,gS_MySQLPrefix, gS_MySQLPrefix, gS_MySQLPrefix, gS_MySQLPrefix, gS_MySQLPrefix, gS_MySQLPrefix, gS_MySQLPrefix, i); + + QueryLog(gH_SQL, SQL_UpdateStyleTop100_Callback, sQuery, i, DBPrio_High); + } } public void SQL_UpdateTop100_Callback(Database db, DBResultSet results, const char[] error, any data) @@ -1301,9 +1809,42 @@ public void SQL_UpdateTop100_Callback(Database db, DBResultSet results, const ch gH_Top100Menu.AddItem("-1", sDisplay); } + gH_Top100Menu.ExitBackButton = true; gH_Top100Menu.ExitButton = true; } +public void SQL_UpdateStyleTop100_Callback(Database db, DBResultSet results, const char[] error, any data) +{ + if (results == null) + { + LogError("Timer (rankings, update style top 100) error! Reason: %s", error); + + return; + } + + if (!results.FetchRow()) + { + LogError("Timer (rankings, update style top 100 b) error! Reason: failed to fetch first row"); + return; + } + + int iStyle = data; + gI_StyleRankedPlayers[iStyle] = results.FetchInt(0); + + int row = 0; + char sName[33]; + while (results.FetchRow()) + { + gA_StyleTop[iStyle].iSteamID[row] = results.FetchInt(1); + + results.FetchString(2, sName, 33); + gA_StyleTop[iStyle].sName.SetString(row, sName); + + gA_StyleTop[iStyle].fPoints[row] = results.FetchFloat(3); + row++; + } +} + bool DoWeHaveWindowFunctions(const char[] sVersion) { char buf[100][2]; @@ -1645,16 +2186,31 @@ public int Native_GetPoints(Handle handler, int numParams) return view_as(gA_Rankings[GetNativeCell(1)].fPoints); } +public int Native_GetStylePoints(Handle handler, int numParams) +{ + return view_as(gA_Rankings[GetNativeCell(1)].fStylePoints[GetNativeCell(2)]); +} + public int Native_GetRank(Handle handler, int numParams) { return gA_Rankings[GetNativeCell(1)].iRank; } +public int Native_GetStyleRank(Handle handler, int numParams) +{ + return gA_Rankings[GetNativeCell(1)].iStyleRank[GetNativeCell(2)]; +} + public int Native_GetRankedPlayers(Handle handler, int numParams) { return gI_RankedPlayers; } +public int Native_GetStyleRankedPlayers(Handle handler, int numParams) +{ + return gI_StyleRankedPlayers[GetNativeCell(1)]; +} + public int Native_Rankings_DeleteMap(Handle handler, int numParams) { char sMap[PLATFORM_MAX_PATH]; diff --git a/addons/sourcemod/translations/shavit-rankings.phrases.txt b/addons/sourcemod/translations/shavit-rankings.phrases.txt index 0b6a83b2..276bc739 100644 --- a/addons/sourcemod/translations/shavit-rankings.phrases.txt +++ b/addons/sourcemod/translations/shavit-rankings.phrases.txt @@ -16,18 +16,46 @@ "#format" "{1:s},{2:N},{3:s},{4:s},{5:d},{6:s},{7:d},{8:s},{9:.1f},{10:s}" "en" "{1}{2}{3} is ranked {4}{5}{6} out of {7} with {8}{9}{10} points." } + "StyleRank" + { + "#format" "{1:s},{2:N},{3:s},{4:s},{5:d},{6:s},{7:d},{8:s},{9:.1f},{10:s},{11:s},{12:s},{13:s}" + "en" "{1}{2}{3} is ranked {4}{5}{6} out of {7} with {8}{9}{10} points. ({11}{12}{13})" + } "Unranked" { "#format" "{1:s},{2:N},{3:s}" "en" "{1}{2}{3} is unranked." } + "UnrankedOnStyle" + { + "#format" "{1:s},{2:N},{3:s},{4:s},{5:s},{6:s}" + "en" "{1}{2}{3} is unranked on {4}{5}{6}." + } // ---------- Menus ---------- // "Top100" { "en" "Top 100 ranked players:" } + "StyleTop100" + { + "#format" "{1:s}" + "en" "Top 100 ranked players ({1}):" + } + "StyleRankTitle" + { + "#format" "{1:s}" + "en" "{1}'s rank:" + } + "StyleTopTitle" + { + "en" "Top Players Menu:" + } + "Overall" + { + "en" "Overall" + } "NoRankedPlayers" { "en" "No ranked players found." } -} \ No newline at end of file +}