/*
* shavit's Timer - Rankings
* by: shavit, rtldg, olivia
*
* This file is part of shavit's Timer (https://github.com/shavitush/bhoptimer)
*
* This program is free software; you can redistribute it and/or modify it under
* the terms of the GNU General Public License, version 3.0, as published by the
* Free Software Foundation.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
* FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
* details.
*
* You should have received a copy of the GNU General Public License along with
* this program. If not, see .
*
*/
// Design idea:
// Rank 1 per map/style/track gets ((points per tier * tier) * 1.5) + (rank 1 time in seconds / 15.0) points.
// Records below rank 1 get points% relative to their time in comparison to rank 1.
//
// Bonus track gets a 0.25* final multiplier for points and is treated as tier 1.
//
// Points for all styles are combined to promote competitive and fair gameplay.
// A player that gets good times at all styles should be ranked high.
//
// Total player points are weighted in the following way: (descending sort of points)
// points[0] * 0.975^0 + points[1] * 0.975^1 + points[2] * 0.975^2 + ... + points[n] * 0.975^n
//
// The ranking leaderboard will be calculated upon: map start.
// Points are calculated per-player upon: connection/map.
// Points are calculated per-map upon: map start, map end, tier changes.
// Rankings leaderboard is re-calculated once per map change.
// A command will be supplied to recalculate all of the above.
//
// Heavily inspired by pp (performance points) from osu!, written by Tom94. https://github.com/ppy/osu-performance
#include
#include
#include
#include
#include
#include
#undef REQUIRE_PLUGIN
#undef REQUIRE_EXTENSIONS
#include
#pragma newdecls required
#pragma semicolon 1
// #define DEBUG
enum struct ranking_t
{
int iRank;
int iStyleRank[STYLE_LIMIT];
float fPoints;
float fStylePoints[STYLE_LIMIT];
int iWRAmountAll;
int iWRAmountCvar;
int iWRHolderRankAll;
int iWRHolderRankCvar;
int iWRAmount[STYLE_LIMIT*2];
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;
bool gB_SqliteHatesPOW = false;
int gI_Driver = Driver_unknown;
bool gB_Stats = false;
bool gB_Late = false;
bool gB_TierQueried = false;
bool gB_MapStarted = false;
int gI_Tier = 1; // No floating numbers for tiers, sorry.
char gS_Map[PLATFORM_MAX_PATH];
EngineVersion gEV_Type = Engine_Unknown;
ArrayList gA_ValidMaps = null;
StringMap gA_MapTiers = null;
Convar gCV_PointsPerTier = null;
Convar gCV_WeightingMultiplier = null;
Convar gCV_WeightingLimit = null;
Convar gCV_LastLoginRecalculate = null;
Convar gCV_MVPRankOnes_Slow = null;
Convar gCV_MVPRankOnes = null;
Convar gCV_MVPRankOnes_Main = null;
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;
bool gB_WRHoldersRefreshedTimer = false;
int gI_WRHolders[2][STYLE_LIMIT];
int gI_WRHoldersAll;
int gI_WRHoldersCvar;
public Plugin myinfo =
{
name = "[shavit] Rankings",
author = "shavit, rtldg, olivia",
description = "A fair and competitive ranking system for shavit's bhoptimer.",
version = SHAVIT_VERSION,
url = "https://github.com/shavitush/bhoptimer"
}
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);
CreateNative("Shavit_GetWRHolderRank", Native_GetWRHolderRank);
CreateNative("Shavit_GuessPointsForTime", Native_GuessPointsForTime);
RegPluginLibrary("shavit-rankings");
gB_Late = late;
return APLRes_Success;
}
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)");
RegAdminCmd("sm_recalcmap", Command_RecalcMap, ADMFLAG_RCON, "Recalculate the current map's records' points.");
RegAdminCmd("sm_recalcall", Command_RecalcAll, ADMFLAG_ROOT, "Recalculate the points for every map on the server. Run this after you change the ranking multiplier for a style or after you install the plugin.");
gCV_PointsPerTier = new Convar("shavit_rankings_pointspertier", "50.0", "Base points to use for per-tier scaling.\nRead the design idea to see how it works: https://github.com/shavitush/bhoptimer/issues/465", 0, true, 1.0);
gCV_WeightingMultiplier = new Convar("shavit_rankings_weighting", "0.975", "Weighting multiplier. 1.0 to disable weighting.\nFormula: p[0] * this^0 + p[1] * this^1 + p[2] * this^2 + ... + p[n] * this^n\nRestart server to apply.", 0, true, 0.01, true, 1.0);
gCV_WeightingLimit = new Convar("shavit_rankings_weighting_limit", "0", "Limit the number of times retrieved for calculating a player's weighted points to this number.\n0 = no limit\nFor reference, a weighting of 0.975 to the power of 300 is 0.00050278777 and results in pretty much nil points for any further weighted times.\nUnused when shavit_rankings_weighting is 1.0.\nYou probably won't need to change this unless you have hundreds of thousands of player times in your database.", 0, true, 0.0, false);
gCV_LastLoginRecalculate = new Convar("shavit_rankings_llrecalc", "0", "Maximum amount of time (in minutes) since last login to recalculate points for a player.\nsm_recalcall does not respect this setting.\n0 - disabled, don't filter anyone", 0, true, 0.0);
gCV_MVPRankOnes_Slow = new Convar("shavit_rankings_mvprankones_slow", "1", "Uses a slower but more featureful MVP counting system.\nEnables the WR Holder ranks & counts for every style & track.\nYou probably won't need to change this unless you have hundreds of thousands of player times in your database.", 0, true, 0.0, true, 1.0);
gCV_MVPRankOnes = new Convar("shavit_rankings_mvprankones", "2", "Set the players' amount of MVPs to the amount of #1 times they have.\n0 - Disabled\n1 - Enabled, for all styles.\n2 - Enabled, for default style only.\n(CS:S/CS:GO only)", 0, true, 0.0, true, 2.0);
gCV_MVPRankOnes_Main = new Convar("shavit_rankings_mvprankones_maintrack", "1", "If set to 0, all tracks will be counted for the MVP stars.\nOtherwise, only the main track will be checked.\n\nRequires \"shavit_stats_mvprankones\" set to 1 or above.\n(CS:S/CS:GO only)", 0, true, 0.0, true, 1.0);
gCV_DefaultTier = new Convar("shavit_rankings_default_tier", "1", "Sets the default tier for new maps added.", 0, true, 0.0, true, 10.0);
Convar.AutoExecConfig();
LoadTranslations("common.phrases");
LoadTranslations("shavit-common.phrases");
LoadTranslations("shavit-rankings.phrases");
// tier cache
gA_ValidMaps = new ArrayList(ByteCountToCells(PLATFORM_MAX_PATH));
gA_MapTiers = new StringMap();
if(gB_Late)
{
Shavit_OnChatConfigLoaded();
Shavit_OnDatabaseLoaded();
}
if (gEV_Type != Engine_TF2)
{
CreateTimer(1.0, Timer_MVPs, 0, TIMER_REPEAT);
}
}
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 OnLibraryAdded(const char[] name)
{
if(StrEqual(name, "shavit-stats"))
{
gB_Stats = true;
}
}
public void OnLibraryRemoved(const char[] name)
{
if(StrEqual(name, "shavit-stats"))
{
gB_Stats = false;
}
}
public void Shavit_OnDatabaseLoaded()
{
GetTimerSQLPrefix(gS_MySQLPrefix, 32);
gH_SQL = Shavit_GetDatabase(gI_Driver);
for(int i = 1; i <= MaxClients; i++)
{
if (IsClientConnected(i) && IsClientAuthorized(i))
{
OnClientAuthorized(i, "");
}
}
DbStuffPostMapStart();
QueryLog(gH_SQL, SQL_Version_Callback,
gI_Driver == Driver_sqlite
? "WITH p AS (SELECT COUNT(*) FROM pragma_function_list WHERE name = 'pow') SELECT sqlite_version(), * FROM p;"
: "SELECT VERSION();");
}
void CreateGetWeightedPointsFunction()
{
if (gCV_WeightingMultiplier.FloatValue == 1.0)
{
return;
}
char sQuery[2048];
Transaction trans = new Transaction();
AddQueryLog(trans, "DROP PROCEDURE IF EXISTS UpdateAllPoints;;"); // old (and very slow) deprecated method
AddQueryLog(trans, "DROP FUNCTION IF EXISTS GetWeightedPoints;;"); // this is here, just in case we ever choose to modify or optimize the calculation
AddQueryLog(trans, "DROP FUNCTION IF EXISTS GetRecordPoints;;");
char sWeightingLimit[30];
if (gCV_WeightingLimit.IntValue > 0)
{
FormatEx(sWeightingLimit, sizeof(sWeightingLimit), "LIMIT %d", gCV_WeightingLimit.IntValue);
}
FormatEx(sQuery, sizeof(sQuery),
"CREATE FUNCTION GetWeightedPoints(steamid 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 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);
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);
gH_SQL.Execute(trans, Trans_RankingsSetupSuccess, Trans_RankingsSetupError, 0, DBPrio_High);
}
public void Trans_RankingsSetupError(Database db, any data, int numQueries, const char[] error, int failIndex, any[] queryData)
{
LogError("Your Mysql/Mariadb didn't let us create the GetWeightedPoints function. Either update your DB version so it doesn't need GetWeightedPoints (to 8.0 or 10.2), fix your DB permissions, OR set `shavit_rankings_weighting` to `1.0`.");
LogError("Timer (rankings) error %d/%d. Reason: %s", failIndex, numQueries, error);
SetFailState("Read the error log");
}
public void Trans_RankingsSetupSuccess(Database db, any data, int numQueries, DBResultSet[] results, any[] queryData)
{
DbStuffPostMapStart();
}
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);
}
UpdatePlayerRank(client, true);
}
}
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);
Shavit_OnStyleConfigLoaded(Shavit_GetStyleCount()); // just in case :)
gB_MapStarted = true;
DbStuffPostMapStart();
}
void DbStuffPostMapStart()
{
if (gH_SQL == null || !gB_MapStarted)
{
return;
}
if (gB_WRHolderTablesMade && !gB_WRHoldersRefreshed)
{
RefreshWRHolders();
}
// do NOT keep running this more than once per map, as UpdateAllPoints() is called after this eventually and locks up the database while it is running
if (gB_TierQueried)
{
return;
}
if (gB_SqliteHatesPOW && gCV_WeightingMultiplier.FloatValue < 1.0)
{
LogError("Rankings Weighting multiplier set but sqlite extension isn't supported. Try using db.sqlite.ext from Sourcemod 1.12 or higher.");
}
if (gH_Top100Menu == null)
{
UpdateTop100();
}
// Default tier.
// I won't repeat the same mistake blacky has done with tier 3 being default..
gI_Tier = gCV_DefaultTier.IntValue;
char sQuery[512];
FormatEx(sQuery, sizeof(sQuery), "SELECT map, tier FROM %smaptiers ORDER BY map ASC;", gS_MySQLPrefix);
QueryLog(gH_SQL, SQL_FillTierCache_Callback, sQuery, 0, DBPrio_High);
gB_TierQueried = true;
}
public void SQL_FillTierCache_Callback(Database db, DBResultSet results, const char[] error, any data)
{
if(results == null)
{
LogError("Timer (rankings, fill tier cache) error! Reason: %s", error);
return;
}
gA_ValidMaps.Clear();
gA_MapTiers.Clear();
while(results.FetchRow())
{
char sMap[PLATFORM_MAX_PATH];
results.FetchString(0, sMap, sizeof(sMap));
LowercaseString(sMap);
int tier = results.FetchInt(1);
gA_MapTiers.SetValue(sMap, tier);
gA_ValidMaps.PushString(sMap);
Call_StartForward(gH_Forwards_OnTierAssigned);
Call_PushString(sMap);
Call_PushCell(tier);
Call_Finish();
}
if (!gA_MapTiers.GetValue(gS_Map, gI_Tier))
{
Call_StartForward(gH_Forwards_OnTierAssigned);
Call_PushString(gS_Map);
Call_PushCell(gI_Tier);
Call_Finish();
char sQuery[512];
FormatEx(sQuery, sizeof(sQuery), "REPLACE INTO %smaptiers (map, tier) VALUES ('%s', %d);", gS_MySQLPrefix, gS_Map, gI_Tier);
QueryLog(gH_SQL, SQL_SetMapTier_Callback, sQuery, 0, DBPrio_High);
}
}
public void OnMapEnd()
{
gB_TierQueried = false;
gB_MapStarted = false;
gB_WRHoldersRefreshed = false;
gB_WRHoldersRefreshedTimer = false;
gB_WorldRecordsCached = false;
}
public void Shavit_OnWRDeleted(int style, int id, int track, int accountid, const char[] mapname)
{
if (!StrEqual(gS_Map, mapname))
{
return;
}
char sQuery[1024];
// bUseCurrentMap=true because shavit-wr should maybe have updated the wr even through the updatewrcache query hasn't run yet
FormatRecalculate(true, track, style, sQuery, sizeof(sQuery));
QueryLog(gH_SQL, SQL_Recalculate_Callback, sQuery, (style << 8) | track, DBPrio_High);
UpdateAllPoints(true);
}
public void Shavit_OnWorldRecordsCached()
{
gB_WorldRecordsCached = true;
}
public Action Timer_MVPs(Handle timer)
{
if (gCV_MVPRankOnes.IntValue == 0)
{
return Plugin_Continue;
}
for (int i = 1; i <= MaxClients; i++)
{
if (IsValidClient(i))
{
CS_SetMVPCount(i, Shavit_GetWRCount(i, -1, -1, true));
}
}
return Plugin_Continue;
}
void UpdateWRs(int client)
{
int iSteamID = GetSteamAccountID(client);
if(iSteamID == 0)
{
return;
}
char sQuery[512];
if (gCV_MVPRankOnes_Slow.BoolValue)
{
FormatEx(sQuery, sizeof(sQuery),
" SELECT *, 0 as track, 0 as type FROM %swrhrankmain WHERE auth = %d \
UNION SELECT *, 1 as track, 0 as type FROM %swrhrankbonus WHERE auth = %d \
UNION SELECT *, -1, 1 as type FROM %swrhrankall WHERE auth = %d \
UNION SELECT *, -1, 2 as type FROM %swrhrankcvar WHERE auth = %d;",
gS_MySQLPrefix, iSteamID, gS_MySQLPrefix, iSteamID, gS_MySQLPrefix, iSteamID, gS_MySQLPrefix, iSteamID);
}
else
{
FormatEx(sQuery, sizeof(sQuery),
"SELECT 0 as wrrank, -1 as style, auth, COUNT(*), -1 as track, 2 as type FROM %swrs WHERE auth = %d %s %s;",
gS_MySQLPrefix,
iSteamID,
(gCV_MVPRankOnes.IntValue == 2) ? "AND style = 0" : "",
(gCV_MVPRankOnes_Main.BoolValue) ? "AND track = 0" : ""
);
}
QueryLog(gH_SQL, SQL_GetWRs_Callback, sQuery, GetClientSerial(client));
}
public void SQL_GetWRs_Callback(Database db, DBResultSet results, const char[] error, any data)
{
if(results == null)
{
LogError("SQL_GetWRs_Callback failed. Reason: %s", error);
return;
}
int client = GetClientFromSerial(data);
if(client == 0)
{
return;
}
while (results.FetchRow())
{
int wrrank = results.FetchInt(0);
int style = results.FetchInt(1);
//int auth = results.FetchInt(2);
int wrcount = results.FetchInt(3);
int track = results.FetchInt(4);
int type = results.FetchInt(5);
if (type == 0)
{
int index = STYLE_LIMIT*track + style;
gA_Rankings[client].iWRAmount[index] = wrcount;
gA_Rankings[client].iWRHolderRank[index] = wrrank;
}
else if (type == 1)
{
gA_Rankings[client].iWRAmountAll = wrcount;
gA_Rankings[client].iWRHolderRankAll = wrcount;
}
else if (type == 2)
{
gA_Rankings[client].iWRAmountCvar = wrcount;
gA_Rankings[client].iWRHolderRankCvar = wrrank;
}
}
}
public Action Command_Tier(int client, int args)
{
int tier = gI_Tier;
char sMap[PLATFORM_MAX_PATH];
if(args == 0)
{
sMap = gS_Map;
}
else
{
GetCmdArgString(sMap, sizeof(sMap));
LowercaseString(sMap);
if(!GuessBestMapName(gA_ValidMaps, sMap, sMap) || !gA_MapTiers.GetValue(sMap, tier))
{
Shavit_PrintToChat(client, "%t", "Map was not found", sMap);
return Plugin_Handled;
}
}
Shavit_PrintToChat(client, "%T", "CurrentTier", client, gS_ChatStrings.sVariable, sMap, gS_ChatStrings.sText, gS_ChatStrings.sVariable2, tier, gS_ChatStrings.sText);
return Plugin_Handled;
}
public Action Command_Rank(int client, int args)
{
int target = client;
if (args > 0)
{
char sArgs[MAX_TARGET_LENGTH];
GetCmdArgString(sArgs, MAX_TARGET_LENGTH);
target = FindTarget(client, sArgs, true, false);
if (target == -1)
{
return Plugin_Handled;
}
}
if(IsValidClient(client))
{
gA_StyleRankCache[client].iTarget = target;
OpenRankMenu(client, 0);
}
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(IsValidClient(client))
{
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)
{
char sInfo[32];
menu.GetItem(param2, sInfo, 32);
if(gB_Stats && !StrEqual(sInfo, "-1"))
{
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;
}
public Action Command_SetTier(int client, int args)
{
char sArg[8];
GetCmdArg(1, sArg, 8);
int tier = StringToInt(sArg);
int maxtier = GetMaxTier();
if(args == 0 || tier < 1 || tier > maxtier)
{
ReplyToCommand(client, "%T", "ArgumentsMissing", client, "sm_settier (1-%d) [map]", maxtier);
return Plugin_Handled;
}
char map[PLATFORM_MAX_PATH];
if (args < 2)
{
gI_Tier = tier;
map = gS_Map;
}
else
{
GetCmdArg(2, map, sizeof(map));
TrimString(map);
LowercaseString(map);
if (!map[0])
{
Shavit_PrintToChat(client, "Invalid map name");
return Plugin_Handled;
}
}
gA_MapTiers.SetValue(map, tier);
Call_StartForward(gH_Forwards_OnTierAssigned);
Call_PushString(map);
Call_PushCell(tier);
Call_Finish();
Shavit_PrintToChat(client, "%T", "SetTier", client, gS_ChatStrings.sVariable2, tier, gS_ChatStrings.sText);
Shavit_LogMessage("%L - set tier of `%s` to %d", client, gS_Map, tier);
char sQuery[512];
FormatEx(sQuery, sizeof(sQuery), "REPLACE INTO %smaptiers (map, tier) VALUES ('%s', %d);", gS_MySQLPrefix, map, tier);
DataPack data = new DataPack();
data.WriteCell(client ? GetClientSerial(client) : 0);
data.WriteString(map);
QueryLog(gH_SQL, SQL_SetMapTier_Callback, sQuery, data);
return Plugin_Handled;
}
public void SQL_SetMapTier_Callback(Database db, DBResultSet results, const char[] error, DataPack data)
{
if(results == null)
{
LogError("Timer (rankings, set map tier) error! Reason: %s", error);
return;
}
if (data == null)
{
return;
}
int serial;
char map[PLATFORM_MAX_PATH];
data.Reset();
serial = data.ReadCell();
data.ReadString(map, sizeof(map));
if (StrEqual(map, gS_Map))
{
ReallyRecalculateCurrentMap();
}
else
{
RecalculateSpecificMap(map, serial);
}
delete data;
}
public Action Command_RecalcMap(int client, int args)
{
ReallyRecalculateCurrentMap();
ReplyToCommand(client, "Recalc started.");
return Plugin_Handled;
}
// You can use Sourcepawn_GetRecordPoints() as a reference for how the queries calculate points.
void FormatRecalculate(bool bUseCurrentMap, int track, int style, char[] sQuery, int sQueryLen, const char[] map = "")
{
float fMultiplier = Shavit_GetStyleSettingFloat(style, "rankingmultiplier");
if (track > 0)
{
fMultiplier *= 0.25;
}
if (Shavit_GetStyleSettingBool(style, "unranked") || fMultiplier == 0.0)
{
FormatEx(sQuery, sQueryLen,
"UPDATE %splayertimes SET points = 0 WHERE style = %d AND track %c 0 %s%s%s;",
gS_MySQLPrefix,
style,
(track > 0) ? '>' : '=',
(bUseCurrentMap) ? "AND map = '" : "",
(bUseCurrentMap) ? gS_Map : "",
(bUseCurrentMap) ? "'" : ""
);
return;
}
if (bUseCurrentMap)
{
float fTier = (track > 0) ? 1.0 : float(gI_Tier);
// a faster, joinless query is used for main due to it having 70% of playertimes.
if (track == Track_Main && gB_WorldRecordsCached)
{
float fWR = Shavit_GetWorldRecord(style, track);
FormatEx(sQuery, sQueryLen,
"UPDATE %splayertimes " ...
"SET points = %f * (%f / time) " ...
"WHERE style = %d AND track = 0 AND map = '%s';",
gS_MySQLPrefix,
(((gCV_PointsPerTier.FloatValue * fTier) * 1.5) + (fWR / 15.0)) * fMultiplier,
fWR,
style,
gS_Map
);
}
else if (gI_Driver == Driver_sqlite)
{
FormatEx(sQuery, sQueryLen,
"UPDATE %splayertimes AS PT\n"
... "SET\n"
... " points =\n"
... " (%f + (WR.time / 15.0))\n"
... " * (WR.time / PT.time)\n"
... " * %f\n"
... "FROM %swrs WR\n"
... "WHERE PT.track %c 0 AND PT.style = %d AND PT.map = '%s'\n"
... " AND PT.track = WR.track AND PT.style = WR.style AND PT.map = WR.map;",
gS_MySQLPrefix,
((gCV_PointsPerTier.FloatValue * fTier) * 1.5),
fMultiplier,
gS_MySQLPrefix,
(track > 0) ? '>' : '=',
style,
gS_Map
);
}
else
{
FormatEx(sQuery, sQueryLen,
"UPDATE %splayertimes AS PT " ...
"INNER JOIN %swrs WR ON " ...
" PT.track = WR.track AND PT.style = WR.style AND PT.map = WR.map " ...
"SET " ...
" PT.points = "...
" (%f + (WR.time / 15.0)) " ...
" * (WR.time / PT.time) " ...
" * %f " ...
"WHERE PT.track %c 0 AND PT.style = %d AND PT.map = '%s';",
gS_MySQLPrefix, gS_MySQLPrefix,
((gCV_PointsPerTier.FloatValue * fTier) * 1.5),
fMultiplier,
(track > 0) ? '>' : '=',
style,
gS_Map
);
}
}
else if (gI_Driver == Driver_sqlite)
{
char mapfilter[50+PLATFORM_MAX_PATH];
if (map[0]) FormatEx(mapfilter, sizeof(mapfilter), "AND PT.map = '%s'", map);
FormatEx(sQuery, sQueryLen,
"UPDATE %splayertimes AS PT\n"
... "SET points =\n"
... " (((%f * %s) * 1.5) + (WR.time / 15.0))\n"
... " * (WR.time / PT.time)\n"
... " * %f\n"
... "FROM %swrs AS WR\n"
... "JOIN %smaptiers AS MT ON\n"
... " PT.map = MT.map\n"
... "WHERE PT.track %c 0 AND PT.track = WR.track AND PT.style = %d AND PT.style = WR.style %s AND PT.map = WR.map;",
gS_MySQLPrefix,
gCV_PointsPerTier.FloatValue,
(track > 0) ? "1" : "MT.tier",
fMultiplier,
gS_MySQLPrefix,
gS_MySQLPrefix,
(track > 0) ? '>' : '=',
style,
mapfilter
);
}
else
{
char mapfilter[50+PLATFORM_MAX_PATH];
if (map[0]) FormatEx(mapfilter, sizeof(mapfilter), "AND PT.map = '%s'", map);
FormatEx(sQuery, sQueryLen,
"UPDATE %splayertimes AS PT " ...
"INNER JOIN %swrs AS WR ON " ...
" PT.track %c 0 AND PT.track = WR.track AND PT.style = %d AND PT.style = WR.style %s AND PT.map = WR.map " ...
"INNER JOIN %smaptiers AS MT ON " ...
" PT.map = MT.map " ...
"SET " ...
" PT.points = "...
" (((%f * %s) * 1.5) + (WR.time / 15.0)) " ...
" * (WR.time / PT.time) " ...
" * %f;",
gS_MySQLPrefix,
gS_MySQLPrefix,
(track > 0) ? '>' : '=',
style,
mapfilter,
gS_MySQLPrefix,
gCV_PointsPerTier.FloatValue,
(track > 0) ? "1" : "MT.tier",
fMultiplier
);
}
}
public Action Command_RecalcAll(int client, int args)
{
ReplyToCommand(client, "- Started recalculating points for all maps. Check console for output.");
Transaction trans = new Transaction();
char sQuery[1024];
FormatEx(sQuery, sizeof(sQuery), "UPDATE %splayertimes SET points = 0;", gS_MySQLPrefix);
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++)
{
if (!Shavit_GetStyleSettingBool(i, "unranked") && Shavit_GetStyleSettingFloat(i, "rankingmultiplier") != 0.0)
{
FormatRecalculate(false, Track_Main, i, sQuery, sizeof(sQuery));
AddQueryLog(trans, sQuery);
FormatRecalculate(false, Track_Bonus, i, sQuery, sizeof(sQuery));
AddQueryLog(trans, sQuery);
}
}
gH_SQL.Execute(trans, Trans_OnRecalcSuccess, Trans_OnRecalcFail, (client == 0)? 0:GetClientSerial(client));
return Plugin_Handled;
}
public void Trans_OnRecalcSuccess(Database db, any data, int numQueries, DBResultSet[] results, any[] queryData)
{
int client = (data == 0)? 0:GetClientFromSerial(data);
if(client != 0)
{
SetCmdReplySource(SM_REPLY_TO_CONSOLE);
}
ReplyToCommand(client, "- Finished recalculating all points. Recalculating user points, top 100 and user cache for all styles.");
UpdateAllPoints(true);
}
public void Trans_OnRecalcFail(Database db, any data, int numQueries, const char[] error, int failIndex, any[] queryData)
{
LogError("Timer (rankings) error! Recalculation failed. Reason: %s", error);
}
void RecalculateSpecificMap(const char[] map, int serial)
{
Transaction trans = new Transaction();
char sQuery[1024];
// Only maintrack times because bonus times aren't tiered.
FormatEx(sQuery, sizeof(sQuery), "UPDATE %splayertimes SET points = 0 WHERE map = '%s' AND track = 0;", gS_MySQLPrefix, map);
AddQueryLog(trans, sQuery);
for(int i = 0; i < gI_Styles; i++)
{
if (!Shavit_GetStyleSettingBool(i, "unranked") && Shavit_GetStyleSettingFloat(i, "rankingmultiplier") != 0.0)
{
FormatRecalculate(false, Track_Main, i, sQuery, sizeof(sQuery), map);
AddQueryLog(trans, sQuery);
}
}
gH_SQL.Execute(trans, Trans_OnRecalcSuccess, Trans_OnRecalcFail, serial);
}
void ReallyRecalculateCurrentMap()
{
#if defined DEBUG
LogError("DEBUG: 5xxx (ReallyRecalculateCurrentMap)");
#endif
Transaction trans = new Transaction();
char sQuery[1024];
FormatEx(sQuery, sizeof(sQuery), "UPDATE %splayertimes SET points = 0 WHERE map = '%s';", gS_MySQLPrefix, gS_Map);
AddQueryLog(trans, sQuery);
for (int i = 0; i < gI_Styles; i++)
{
if (!Shavit_GetStyleSettingBool(i, "unranked") && Shavit_GetStyleSettingFloat(i, "rankingmultiplier") != 0.0)
{
FormatRecalculate(true, Track_Main, i, sQuery, sizeof(sQuery));
AddQueryLog(trans, sQuery);
FormatRecalculate(true, Track_Bonus, i, sQuery, sizeof(sQuery));
AddQueryLog(trans, sQuery);
}
}
gH_SQL.Execute(trans, Trans_OnReallyRecalcSuccess, Trans_OnReallyRecalcFail, 0);
}
public void Trans_OnReallyRecalcSuccess(Database db, any data, int numQueries, DBResultSet[] results, any[] queryData)
{
UpdateAllPoints(true, gS_Map);
}
public void Trans_OnReallyRecalcFail(Database db, any data, int numQueries, const char[] error, int failIndex, any[] queryData)
{
LogError("Timer (rankings) error! ReallyRecalculateCurrentMap failed. Reason: %s", error);
}
public void Shavit_OnFinish_Post(int client, int style, float time, int jumps, int strafes, float sync, int rank, int overwrite, int track)
{
if (Shavit_GetStyleSettingBool(style, "unranked") || Shavit_GetStyleSettingFloat(style, "rankingmultiplier") == 0.0)
{
return;
}
if (rank != 1)
{
UpdatePointsForSinglePlayer(client);
return;
}
#if defined DEBUG
PrintToServer("Recalculating points. (%s, %d, %d)", map, track, style);
#endif
char sQuery[1024];
FormatRecalculate(true, track, style, sQuery, sizeof(sQuery));
QueryLog(gH_SQL, SQL_Recalculate_Callback, sQuery, (style << 8) | track, DBPrio_High);
UpdateAllPoints(true, gS_Map, track);
}
public void SQL_Recalculate_Callback(Database db, DBResultSet results, const char[] error, any data)
{
int track = data & 0xFF;
int style = data >> 8;
if(results == null)
{
LogError("Timer (rankings, recalculate map points, %s, style=%d) error! Reason: %s", (track == Track_Main) ? "main" : "bonus", style, error);
return;
}
#if defined DEBUG
PrintToServer("Recalculated (%s, style=%d).", (track == Track_Main) ? "main_" : "bonus", style);
#endif
}
void UpdatePointsForSinglePlayer(int client)
{
int auth = GetSteamAccountID(client);
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)
{
char sLimit[30];
if (gCV_WeightingLimit.IntValue > 0)
FormatEx(sLimit, sizeof(sLimit), "LIMIT %d", gCV_WeightingLimit.IntValue);
FormatEx(sQuery, sizeof(sQuery),
"UPDATE %susers 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 = %d AND points > 0\n"
... " ORDER BY points DESC %s\n"
... " ) as t\n"
... ") WHERE auth = %d;",
gS_MySQLPrefix,
gCV_WeightingMultiplier.FloatValue,
gS_MySQLPrefix,
auth,
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));
}
void UpdateAllPoints(bool recalcall=false, char[] map="", int track=-1)
{
#if defined DEBUG
LogError("DEBUG: 6 (UpdateAllPoints)");
#endif
char sQuery[1024], sStyleQuery[1024];
char sLastLogin[69], sLimit[30], sMapWhere[512], sTrackWhere[64];
if (track != -1)
FormatEx(sTrackWhere, sizeof(sTrackWhere), "track = %d", track);
if (map[0])
FormatEx(sMapWhere, sizeof(sMapWhere), "map = '%s'", map);
if (!recalcall && gCV_LastLoginRecalculate.IntValue > 0)
{
FormatEx(sLastLogin, sizeof(sLastLogin), "lastlogin > %d", (GetTime() - gCV_LastLoginRecalculate.IntValue * 60));
}
if (gCV_WeightingLimit.IntValue > 0)
FormatEx(sLimit, sizeof(sLimit), "LIMIT %d", gCV_WeightingLimit.IntValue);
if (gCV_WeightingMultiplier.FloatValue == 1.0 || gB_SqliteHatesPOW)
{
if (gI_Driver == Driver_sqlite)
{
FormatEx(sQuery, sizeof(sQuery),
"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
{
FormatEx(sQuery, sizeof(sQuery),
"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)
{
if (sLastLogin[0])
Format(sLastLogin, sizeof(sLastLogin), "u2.%s", sLastLogin);
// fuck you mysql
FormatEx(sQuery, sizeof(sQuery),
"UPDATE %susers AS u, (\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 %susers AS u2\n"
... " ON u2.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 %s\n"
... " ) AS t\n"
... " GROUP by auth\n"
... ") AS a\n"
... "SET u.points = a.pp\n"
... "WHERE u.auth = a.auth;",
gS_MySQLPrefix,
gCV_WeightingMultiplier.FloatValue,
gS_MySQLPrefix,
gS_MySQLPrefix,
sLastLogin[0] ? "AND" : "", sLastLogin,
gS_MySQLPrefix,
(sMapWhere[0] || sTrackWhere[0]) ? "WHERE" : "",
sMapWhere,
(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)
{
FormatEx(sQuery, sizeof(sQuery),
"UPDATE %susers AS u\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 = u.auth AND points > 0\n"
... " ORDER BY points DESC %s\n"
... " ) AS t\n"
... ") WHERE %s %s auth IN\n"
... " (SELECT DISTINCT auth FROM %splayertimes %s %s %s %s);",
gS_MySQLPrefix,
gCV_WeightingMultiplier.FloatValue,
gS_MySQLPrefix,
sLimit, // TODO: Remove/move sLimit?
sLastLogin, sLastLogin[0] ? "AND" : "",
gS_MySQLPrefix,
(sMapWhere[0] || sTrackWhere[0]) ? "WHERE" : "",
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
{
FormatEx(sQuery, sizeof(sQuery),
"UPDATE %susers SET points = GetWeightedPoints(auth) WHERE %s %s auth IN (SELECT DISTINCT auth FROM %splayertimes %s %s %s %s);",
gS_MySQLPrefix,
sLastLogin, (sLastLogin[0] != 0) ? "AND" : "",
gS_MySQLPrefix,
(sMapWhere[0] || sTrackWhere[0]) ? "WHERE" : "",
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);
}
public void SQL_UpdateAllPoints_Callback(Database db, DBResultSet results, const char[] error, any data)
{
if(results == null)
{
LogError("Timer (rankings, update all points) error! Reason: %s", error);
return;
}
UpdateTop100();
for (int i = 1; i <= MaxClients; i++)
{
if (IsClientInGame(i) && IsClientAuthorized(i))
{
UpdatePlayerRank(i, false);
}
}
}
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;
if((iSteamID = GetSteamAccountID(client)) != 0)
{
// if there's any issue with this query,
// add "ORDER BY points DESC " before "LIMIT 1"
char sQuery[512];
FormatEx(sQuery, 512, "SELECT u2.points, COUNT(*) FROM %susers u1 JOIN (SELECT points FROM %susers WHERE auth = %d) u2 WHERE u1.points >= u2.points;",
gS_MySQLPrefix, gS_MySQLPrefix, iSteamID);
DataPack hPack = new DataPack();
hPack.WriteCell(GetClientSerial(client));
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);
}
}
}
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);
return;
}
int client = GetClientFromSerial(iSerial);
if(client == 0)
{
return;
}
if(results.FetchRow())
{
gA_Rankings[client].fPoints = results.FetchFloat(0);
gA_Rankings[client].iRank = (gA_Rankings[client].fPoints > 0.0)? results.FetchInt(1):0;
Call_StartForward(gH_Forwards_OnRankAssigned);
Call_PushCell(client);
Call_PushCell(gA_Rankings[client].iRank);
Call_PushCell(gA_Rankings[client].fPoints);
Call_PushCell(bFirst);
Call_Finish();
}
}
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];
FormatEx(sQuery, sizeof(sQuery),
"SELECT * FROM (SELECT COUNT(*) as c, 0 as auth, '' as name, '' as p FROM %susers WHERE points > 0) a \
UNION ALL \
SELECT * FROM (SELECT -1 as c, auth, name, FORMAT(points, 2) FROM %susers WHERE points > 0 ORDER BY points DESC LIMIT 100) b;",
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)
{
if(results == null)
{
LogError("Timer (rankings, update top 100) error! Reason: %s", error);
return;
}
if (!results.FetchRow())
{
LogError("Timer (rankings, update top 100 b) error! Reason: failed to fetch first row");
return;
}
gI_RankedPlayers = results.FetchInt(0);
delete gH_Top100Menu;
gH_Top100Menu = new Menu(MenuHandler_Top);
int row = 0;
while(results.FetchRow())
{
char sSteamID[32];
results.FetchString(1, sSteamID, 32);
char sName[32+1];
results.FetchString(2, sName, sizeof(sName));
char sPoints[16];
results.FetchString(3, sPoints, 16);
char sDisplay[96];
FormatEx(sDisplay, 96, "#%d - %s (%s)", (++row), sName, sPoints);
gH_Top100Menu.AddItem(sSteamID, sDisplay);
}
if(gH_Top100Menu.ItemCount == 0)
{
char sDisplay[64];
FormatEx(sDisplay, 64, "%t", "NoRankedPlayers");
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];
ExplodeString(sVersion, ".", buf, 2, 100);
int iMajor = StringToInt(buf[0]);
int iMinor = StringToInt(buf[1]);
if (gI_Driver == Driver_sqlite)
{
// 2018~
return iMajor > 3 || (iMajor == 3 && iMinor >= 25); // 2018~
}
else if (gI_Driver == Driver_pgsql)
{
// 2009~
return iMajor > 8 || (iMajor == 8 && iMinor >= 4);
}
else if (gI_Driver == Driver_mysql)
{
if (StrContains(sVersion, "MariaDB") != -1)
{
// 2016~
return iMajor > 10 || (iMajor == 10 && iMinor >= 2);
}
else // mysql then...
{
// 2018~
return iMajor > 8 || (iMajor == 8 && iMinor >= 0);
}
}
return false;
}
public void SQL_Version_Callback(Database db, DBResultSet results, const char[] error, any data)
{
if (results == null || !results.FetchRow())
{
LogError("Timer (rankings) error! Failed to retrieve VERSION(). Reason: %s", error);
}
else
{
char sVersion[100];
results.FetchString(0, sVersion, sizeof(sVersion));
gB_SQLWindowFunctions = DoWeHaveWindowFunctions(sVersion);
if (gI_Driver == Driver_sqlite)
{
gB_SqliteHatesPOW = results.FetchInt(1) == 0;
}
}
if (!gB_SQLWindowFunctions)
{
if (gI_Driver == Driver_sqlite)
{
SetFailState("sqlite version not supported. Try using db.sqlite.ext from Sourcemod 1.12 or higher.");
}
else if (gI_Driver == Driver_pgsql)
{
LogError("Okay, really? Your postgres version is from 2014 or earlier... come on, brother...");
SetFailState("Update postgresql");
}
else // mysql
{
// Mysql 5.7 is a cancer upon society. EOS is Oct 2023!! Unbelievable.
// Please update your servers already, nfoservers.
CreateGetWeightedPointsFunction();
}
}
char sWRHolderRankTrackQueryYuck[] =
"%s %s%s AS \
SELECT \
0 as wrrank, \
style, auth, COUNT(auth) as wrcount \
FROM %swrs WHERE track %c 0 GROUP BY style, auth;";
char sWRHolderRankTrackQueryRANK[] =
"%s %s%s AS \
SELECT \
RANK() OVER(PARTITION BY style ORDER BY COUNT(auth) DESC, auth ASC) \
as wrrank, \
style, auth, COUNT(auth) as wrcount \
FROM %swrs WHERE track %c 0 GROUP BY style, auth;";
char sWRHolderRankOtherQueryYuck[] =
"%s %s%s AS \
SELECT \
0 as wrrank, \
-1 as style, auth, COUNT(*) \
FROM %swrs %s %s %s %s GROUP BY auth;";
char sWRHolderRankOtherQueryRANK[] =
"%s %s%s AS \
SELECT \
RANK() OVER(ORDER BY COUNT(auth) DESC, auth ASC) \
as wrrank, \
-1 as style, auth, COUNT(*) as wrcount \
FROM %swrs %s %s %s %s GROUP BY auth;";
char sQuery[800];
Transaction trans = new Transaction();
if (gI_Driver == Driver_sqlite)
{
FormatEx(sQuery, sizeof(sQuery), "DROP VIEW IF EXISTS %swrhrankmain;", gS_MySQLPrefix);
AddQueryLog(trans, sQuery);
FormatEx(sQuery, sizeof(sQuery), "DROP VIEW IF EXISTS %swrhrankbonus;", gS_MySQLPrefix);
AddQueryLog(trans, sQuery);
FormatEx(sQuery, sizeof(sQuery), "DROP VIEW IF EXISTS %swrhrankall;", gS_MySQLPrefix);
AddQueryLog(trans, sQuery);
FormatEx(sQuery, sizeof(sQuery), "DROP VIEW IF EXISTS %swrhrankcvar;", gS_MySQLPrefix);
AddQueryLog(trans, sQuery);
}
FormatEx(sQuery, sizeof(sQuery),
!gB_SQLWindowFunctions ? sWRHolderRankTrackQueryYuck : sWRHolderRankTrackQueryRANK,
gI_Driver == Driver_sqlite ? "CREATE VIEW IF NOT EXISTS" : "CREATE OR REPLACE VIEW",
gS_MySQLPrefix, "wrhrankmain", gS_MySQLPrefix, '=');
AddQueryLog(trans, sQuery);
FormatEx(sQuery, sizeof(sQuery),
!gB_SQLWindowFunctions ? sWRHolderRankTrackQueryYuck : sWRHolderRankTrackQueryRANK,
gI_Driver == Driver_sqlite ? "CREATE VIEW IF NOT EXISTS" : "CREATE OR REPLACE VIEW",
gS_MySQLPrefix, "wrhrankbonus", gS_MySQLPrefix, '>');
AddQueryLog(trans, sQuery);
FormatEx(sQuery, sizeof(sQuery),
!gB_SQLWindowFunctions ? sWRHolderRankOtherQueryYuck : sWRHolderRankOtherQueryRANK,
gI_Driver == Driver_sqlite ? "CREATE VIEW IF NOT EXISTS" : "CREATE OR REPLACE VIEW",
gS_MySQLPrefix, "wrhrankall", gS_MySQLPrefix, "", "", "", "");
AddQueryLog(trans, sQuery);
FormatEx(sQuery, sizeof(sQuery),
!gB_SQLWindowFunctions ? sWRHolderRankOtherQueryYuck : sWRHolderRankOtherQueryRANK,
gI_Driver == Driver_sqlite ? "CREATE VIEW IF NOT EXISTS" : "CREATE OR REPLACE VIEW",
gS_MySQLPrefix, "wrhrankcvar", gS_MySQLPrefix,
(gCV_MVPRankOnes.IntValue == 2 || gCV_MVPRankOnes_Main.BoolValue) ? "WHERE" : "",
(gCV_MVPRankOnes.IntValue == 2) ? "style = 0" : "",
(gCV_MVPRankOnes.IntValue == 2 && gCV_MVPRankOnes_Main.BoolValue) ? "AND" : "",
(gCV_MVPRankOnes_Main.BoolValue) ? "track = 0" : "");
AddQueryLog(trans, sQuery);
gH_SQL.Execute(trans, Trans_WRHolderRankTablesSuccess, Trans_WRHolderRankTablesError, 0, DBPrio_High);
}
public void Trans_WRHolderRankTablesSuccess(Database db, any data, int numQueries, DBResultSet[] results, any[] queryData)
{
gB_WRHolderTablesMade = true;
for(int i = 1; i <= MaxClients; i++)
{
if (IsClientConnected(i) && IsClientAuthorized(i))
{
UpdateWRs(i);
}
}
RefreshWRHolders();
}
void RefreshWRHolders()
{
if (gB_WRHoldersRefreshedTimer)
{
return;
}
gB_WRHoldersRefreshedTimer = true;
CreateTimer(10.0, Timer_RefreshWRHolders, 0, TIMER_FLAG_NO_MAPCHANGE);
}
public Action Timer_RefreshWRHolders(Handle timer, any data)
{
RefreshWRHoldersActually();
return Plugin_Stop;
}
void RefreshWRHoldersActually()
{
char sQuery[1024];
if (gCV_MVPRankOnes_Slow.BoolValue)
{
FormatEx(sQuery, sizeof(sQuery),
" SELECT 0 as type, 0 as track, style, COUNT(DISTINCT auth) FROM %swrhrankmain GROUP BY style \
UNION SELECT 0 as type, 1 as track, style, COUNT(DISTINCT auth) FROM %swrhrankbonus GROUP BY style \
UNION SELECT 1 as type, -1 as track, -1 as style, COUNT(DISTINCT auth) FROM %swrhrankall \
UNION SELECT 2 as type, -1 as track, -1 as style, COUNT(DISTINCT auth) FROM %swrhrankcvar;",
gS_MySQLPrefix, gS_MySQLPrefix, gS_MySQLPrefix, gS_MySQLPrefix);
}
else
{
FormatEx(sQuery, sizeof(sQuery),
"SELECT 2 as type, -1 as track, -1 as style, COUNT(DISTINCT auth) FROM %swrs %s %s %s %s;",
gS_MySQLPrefix,
(gCV_MVPRankOnes.IntValue == 2 || gCV_MVPRankOnes_Main.BoolValue) ? "WHERE" : "",
(gCV_MVPRankOnes.IntValue == 2) ? "style = 0" : "",
(gCV_MVPRankOnes.IntValue == 2 && gCV_MVPRankOnes_Main.BoolValue) ? "AND" : "",
(gCV_MVPRankOnes_Main.BoolValue) ? "track = 0" : ""
);
}
QueryLog(gH_SQL, SQL_GetWRHolders_Callback, sQuery);
gB_WRHoldersRefreshed = true;
}
public void Trans_WRHolderRankTablesError(Database db, any data, int numQueries, const char[] error, int failIndex, any[] queryData)
{
LogError("Timer (WR Holder Rank table creation %d/%d) SQL query failed. Reason: %s", failIndex, numQueries, error);
}
public void SQL_GetWRHolders_Callback(Database db, DBResultSet results, const char[] error, any data)
{
if(results == null)
{
LogError("Timer (get WR Holder amount) SQL query failed. Reason: %s", error);
return;
}
while (results.FetchRow())
{
int type = results.FetchInt(0);
int track = results.FetchInt(1);
int style = results.FetchInt(2);
int total = results.FetchInt(3);
if (type == 0)
{
gI_WRHolders[track][style] = total;
}
else if (type == 1)
{
gI_WRHoldersAll = total;
}
else if (type == 2)
{
gI_WRHoldersCvar = total;
}
}
}
public int Native_GetWRCount(Handle handler, int numParams)
{
int client = GetNativeCell(1);
int track = GetNativeCell(2);
int style = GetNativeCell(3);
bool usecvars = view_as(GetNativeCell(4));
if (usecvars)
{
return gA_Rankings[client].iWRAmountCvar;
}
else if (track == -1 && style == -1)
{
return gA_Rankings[client].iWRAmountAll;
}
if (track > Track_Bonus)
{
track = Track_Bonus;
}
return gA_Rankings[client].iWRAmount[STYLE_LIMIT*track + style];
}
public int Native_GetWRHolders(Handle handler, int numParams)
{
int track = GetNativeCell(1);
int style = GetNativeCell(2);
bool usecvars = view_as(GetNativeCell(3));
if (usecvars)
{
return gI_WRHoldersCvar;
}
else if (track == -1 && style == -1)
{
return gI_WRHoldersAll;
}
if (track > Track_Bonus)
{
track = Track_Bonus;
}
return gI_WRHolders[track][style];
}
public int Native_GetWRHolderRank(Handle handler, int numParams)
{
int client = GetNativeCell(1);
int track = GetNativeCell(2);
int style = GetNativeCell(3);
bool usecvars = view_as(GetNativeCell(4));
if (usecvars)
{
return gA_Rankings[client].iWRHolderRankCvar;
}
else if (track == -1 && style == -1)
{
return gA_Rankings[client].iWRHolderRankAll;
}
if (track > Track_Bonus)
{
track = Track_Bonus;
}
return gA_Rankings[client].iWRHolderRank[STYLE_LIMIT*track + style];
}
public int Native_GetMapTier(Handle handler, int numParams)
{
int tier = 0;
char sMap[PLATFORM_MAX_PATH];
GetNativeString(1, sMap, sizeof(sMap));
if (!sMap[0])
{
return gI_Tier;
}
gA_MapTiers.GetValue(sMap, tier);
return tier;
}
public int Native_GetMapTiers(Handle handler, int numParams)
{
return view_as(CloneHandle(gA_MapTiers, handler));
}
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];
GetNativeString(1, sMap, sizeof(sMap));
LowercaseString(sMap);
char sQuery[512];
FormatEx(sQuery, sizeof(sQuery), "DELETE FROM %smaptiers WHERE map = '%s';", gS_MySQLPrefix, sMap);
QueryLog(gH_SQL, SQL_DeleteMap_Callback, sQuery, StrEqual(gS_Map, sMap, false), DBPrio_High);
return 1;
}
public int Native_GuessPointsForTime(Handle plugin, int numParams)
{
int rtrack = GetNativeCell(1);
int rstyle = GetNativeCell(2);
int tier = GetNativeCell(3);
float rtime = view_as(GetNativeCell(4));
float pwr = view_as(GetNativeCell(5));
float ppoints = Sourcepawn_GetRecordPoints(
rtrack,
rtime,
gCV_PointsPerTier.FloatValue,
Shavit_GetStyleSettingFloat(rstyle, "rankingmultiplier"),
pwr,
float(tier == -1 ? gI_Tier : tier)
);
return view_as(ppoints);
}
float Sourcepawn_GetRecordPoints(int rtrack, float rtime, float pointspertier, float stylemultiplier, float pwr, float ptier)
{
float ppoints = 0.0;
if (rtrack > 0)
{
ptier = 1.0;
}
ppoints = ((pointspertier * ptier) * 1.5) + (pwr / 15.0);
ppoints *= (pwr / rtime);
ppoints *= stylemultiplier;
if (rtrack > 0)
{
ppoints *= 0.25;
}
return ppoints;
}
public void SQL_DeleteMap_Callback(Database db, DBResultSet results, const char[] error, any data)
{
if(results == null)
{
LogError("Timer (rankings deletemap) SQL query failed. Reason: %s", error);
return;
}
if(view_as(data))
{
gI_Tier = gCV_DefaultTier.IntValue;
UpdateAllPoints(true);
}
}
int GetMaxTier()
{
float val = 10.0;
gCV_DefaultTier.GetBounds(ConVarBound_Upper, val);
return RoundToFloor(val);
}