#pragma semicolon 1 #define PLUGIN_AUTHOR "jenz" #define PLUGIN_VERSION "1.1" #include #include #include #include #include Database g_hDatabase; bool g_bCorrectServer = false; int g_iClientHours[MAXPLAYERS + 1]; int g_iClientMinutes[MAXPLAYERS + 1]; ArrayList g_hPendingVoteChoices; public Plugin myinfo = { name = "UNLOZE_playtime_display", author = PLUGIN_AUTHOR, description = "collects all info for playtime display", version = PLUGIN_VERSION, url = "www.unloze.com" }; enum struct VoteChoiceRecord { char SteamId[64]; char VoteChoice[64]; int VoteWeight; } public void OnPluginStart() { if (!g_hDatabase) { Database.Connect(SQL_OnDatabaseConnect, "unloze_playtimestats"); } g_hPendingVoteChoices = new ArrayList(sizeof(VoteChoiceRecord)); g_bCorrectServer = GetConVarInt(FindConVar("hostport")) == 27015 ? true : false; } public void SQL_OnDatabaseConnect(Database db, const char[] error, any data) { if(!db || strlen(error)) { LogError("Database error: %s", error); return; } g_hDatabase = db; OnMapStart(); } public void OnMapStart() { if (!g_bCorrectServer) { return; } if (!g_hDatabase) { Database.Connect(SQL_OnDatabaseConnect, "unloze_playtimestats"); return; } char sMapname[64]; GetCurrentMap(sMapname, sizeof(sMapname)); insert_map(sMapname); } public void insert_map(char sMapname[64]) { char sQuery[512]; Format(sQuery, sizeof(sQuery), "INSERT INTO `playtime_display_map_history` (map_name) VALUES('%s')", sMapname); g_hDatabase.Query(SQL_FinishedQuery, sQuery, _, DBPrio_Normal); } //forward is called from OnClientAuthorized but also from timer every 1 minute. public void GetPlayerHoursServer(int client, int hours, int minutes) { if (!g_bCorrectServer) { return; } g_iClientHours[client] = hours; g_iClientMinutes[client] = minutes; select_user_session(client); } public void select_user_session(int client) { char sSID[64]; GetClientAuthId(client, AuthId_Steam2, sSID, sizeof(sSID)); char sQuery[512]; Format(sQuery, sizeof(sQuery), "select 1 from `playtime_display_sessions` where steamid = '%s' and session_end_dt > NOW() - INTERVAL 10 MINUTE limit 1", sSID); g_hDatabase.Query(SQL_FinishedQuerySelectUserSession, sQuery, GetClientSerial(client), DBPrio_Normal); } public void SQL_FinishedQuerySelectUserSession(Database db, DBResultSet results, const char[] error, int Serial) { if (!db || strlen(error)) { LogError("Query error 3: %s", error); delete results; return; } int client; if ((client = GetClientFromSerial(Serial)) == 0) { delete results; return; } if (!IsValidClient(client)) { delete results; return; } if (results.RowCount && results.FetchRow()) { //active session that we keep updating update_play_session(client, g_iClientHours[client], g_iClientMinutes[client]); } else { //new session that we insert //chaining the calls insert_player(client); } delete results; } public void update_play_session(int client, int client_hours, int client_minutes) { char sSID[64]; GetClientAuthId(client, AuthId_Steam2, sSID, sizeof(sSID)); //we only update the latest session row if it actually was updated inside the last 10 minutes. otherwise its an old session that should not be updated. char sQuery[512]; Format(sQuery, sizeof(sQuery), "UPDATE `playtime_display_sessions` SET session_end_playtime_minutes='%i', session_end_dt=NOW() WHERE steamid = '%s' and session_end_dt > NOW() - INTERVAL 10 MINUTE order by session_id desc limit 1", (client_hours * 60) + client_minutes, sSID); g_hDatabase.Query(SQL_FinishedQuery, sQuery, _, DBPrio_Normal); } public void insert_player(int client) { char sName[MAX_NAME_LENGTH]; GetClientName(client, sName, sizeof(sName)); int size2 = 2 * strlen(sName) + 1; char[] sEscapedName = new char[size2 + 1]; g_hDatabase.Escape(sName, sEscapedName, size2 + 1); char sIP[32]; GetClientIP(client, sIP, sizeof(sIP)); char countryCode[3]; GeoipCode2(sIP, countryCode); char sSID[64]; GetClientAuthId(client, AuthId_Steam2, sSID, sizeof(sSID)); int client_tier = GetPlayerTier_native(client); char sQuery[512]; Format(sQuery, sizeof(sQuery), "INSERT INTO `playtime_display_players` (`steamid`, `current_name`, `current_country`, `player_tier`) VALUES('%s', '%s', '%s', '%i') ON DUPLICATE KEY UPDATE `current_name` = '%s', `current_country` = '%s', `last_seen` = CURRENT_TIMESTAMP, `player_tier` = '%i'", sSID, sEscapedName, countryCode, client_tier, sEscapedName, countryCode, client_tier); g_hDatabase.Query(SQL_FinishedQueryUpsertPlayerCallback, sQuery, GetClientSerial(client), DBPrio_Normal); } public void SQL_FinishedQueryUpsertPlayerCallback(Database db, DBResultSet results, const char[] error, int Serial) { if (!db || strlen(error)) { LogError("Query error 3: %s", error); delete results; return; } int client; if ((client = GetClientFromSerial(Serial)) == 0) { delete results; return; } if (!IsValidClient(client)) { delete results; return; } delete results; upsert_name_history(client); } public void upsert_name_history(int client) { char sName[MAX_NAME_LENGTH]; GetClientName(client, sName, sizeof(sName)); int size2 = 2 * strlen(sName) + 1; char[] sEscapedName = new char[size2 + 1]; g_hDatabase.Escape(sName, sEscapedName, size2 + 1); char sSID[64]; GetClientAuthId(client, AuthId_Steam2, sSID, sizeof(sSID)); char sQuery[512]; Format(sQuery, sizeof(sQuery), "INSERT INTO `playtime_display_name_history` (`steamid`, `player_name`) VALUES('%s', '%s') ON DUPLICATE KEY UPDATE `player_name` = '%s', `last_seen` = CURRENT_TIMESTAMP", sSID, sEscapedName, sEscapedName); g_hDatabase.Query(SQL_FinishedQueryUpsertNameHistoryCallback, sQuery, GetClientSerial(client), DBPrio_Normal); } public void SQL_FinishedQueryUpsertNameHistoryCallback(Database db, DBResultSet results, const char[] error, int Serial) { if (!db || strlen(error)) { LogError("Query error 3: %s", error); delete results; return; } int client; if ((client = GetClientFromSerial(Serial)) == 0) { delete results; return; } if (!IsValidClient(client)) { delete results; return; } delete results; insert_play_session(client, g_iClientHours[client], g_iClientMinutes[client]); } public void insert_play_session(int client, int hours, int minutes) { char sName[MAX_NAME_LENGTH]; GetClientName(client, sName, sizeof(sName)); int size2 = 2 * strlen(sName) + 1; char[] sEscapedName = new char[size2 + 1]; g_hDatabase.Escape(sName, sEscapedName, size2 + 1); char sIP[32]; GetClientIP(client, sIP, sizeof(sIP)); char countryCode[3]; GeoipCode2(sIP, countryCode); char sSID[64]; GetClientAuthId(client, AuthId_Steam2, sSID, sizeof(sSID)); char sQuery[512]; Format(sQuery, sizeof(sQuery), "INSERT INTO `playtime_display_sessions` (`steamid`, `country_code`, `player_name`, `session_start_playtime_minutes`, `session_end_dt`) VALUES('%s', '%s','%s','%i', CURRENT_TIMESTAMP)", sSID, countryCode, sEscapedName, (hours * 60) + minutes); g_hDatabase.Query(SQL_FinishedQuery, sQuery, _, DBPrio_Normal); } public void SQL_FinishedQuery(Database db, DBResultSet results, const char[] error, any data) { if (!db || strlen(error)) { LogError("Query error 3: %s", error); } delete results; } public void OnClientDisconnect(int client) { if (g_iClientHours[client] == 0 && g_iClientMinutes[client] == 0) { return; } update_play_session(client, g_iClientHours[client], g_iClientMinutes[client]); g_iClientHours[client] = 0; g_iClientMinutes[client] = 0; } public void OnMapVoteRunnoffWarningStart() { if (!g_bCorrectServer) { return; } char sMapname[64]; GetCurrentMap(sMapname, sizeof(sMapname)); char sQuery[512]; //picking max() is usually considered haram Format(sQuery, sizeof(sQuery), "INSERT INTO `playtime_display_map_vote_events` (`map_id`, `current_map`, `result`) VALUES((select max(map_id) from `playtime_display_map_history`), '%s','Revote needed!')", sMapname); g_hDatabase.Query(SQL_FinishedQueryMapVoteEndCallback, sQuery, _, DBPrio_Normal); } public void OnMapVoteEnd(const char[] map) { if (!g_bCorrectServer) { return; } char sMapname[64]; GetCurrentMap(sMapname, sizeof(sMapname)); char sQuery[512]; //picking max() is usually considered haram Format(sQuery, sizeof(sQuery), "INSERT INTO `playtime_display_map_vote_events` (`map_id`, `current_map`, `result`) VALUES((select max(map_id) from `playtime_display_map_history`), '%s','%s')", sMapname, map); g_hDatabase.Query(SQL_FinishedQueryMapVoteEndCallback, sQuery, _, DBPrio_Normal); } public void SQL_FinishedQueryMapVoteEndCallback(Database db, DBResultSet results, const char[] error, any data) { if (!db || strlen(error)) { LogError("Query error 3: %s", error); } delete results; for (int j = 0; j < GetArraySize(g_hPendingVoteChoices); j++) { VoteChoiceRecord entry; GetArrayArray(g_hPendingVoteChoices, j, entry); char sQuery[512]; //again, selecting with max() is generally not very good but should be reliable given the context here. Format(sQuery, sizeof(sQuery), "INSERT INTO `playtime_display_map_vote_choices` (`vote_id`, `steamid`, `vote_choice`, `vote_weight`) VALUES ((select max(vote_id) from `playtime_display_map_vote_events`), '%s', '%s','%i')", entry.SteamId, entry.VoteChoice, entry.VoteWeight); g_hDatabase.Query(SQL_FinishedQuery, sQuery, _, DBPrio_Normal); } g_hPendingVoteChoices.Clear(); } public void OnMapVoteClientResult(char[] steamID, char[] vote_choice, int voteweight) { VoteChoiceRecord entry; strcopy(entry.SteamId, sizeof(entry.SteamId), steamID); strcopy(entry.VoteChoice, sizeof(entry.VoteChoice), vote_choice); entry.VoteWeight = voteweight; g_hPendingVoteChoices.PushArray(entry); } stock bool IsValidClient(int client) { if (client > 0 && client <= MaxClients && IsClientConnected(client) && IsClientInGame(client)) return true; return false; }