· 9 years ago · Dec 02, 2016, 05:02 PM
1#include <sourcemod>
2
3#define ERR_SQL_CONNECT_FAILED "Failed to connect to SQL database. %s"
4#define ERR_SQL_CREATE_TABLES_FAILED "Failed to create SQL tables. %s"
5#define ERR_SQL_EXECUTE_QUERY_FAILED "Failed to execute SQL query. %s"
6#define ERR_SQL_PREPARE_QUERY_FAILED "Failed to prepare SQL query. %s"
7#define ERR_HOOK_EVENT_FAILED "Failed to hook event '%s'."
8
9#undef MAX_NAME_LENGTH
10
11#define MAX_NAME_LENGTH (32 + 1)
12#define MAX_IP_LENGTH (15 + 1)
13#define MAX_STEAMID_LENGTH (20 + 1)
14#define MAX_DATETIME_LENGTH (19 + 1)
15#define MAX_ARG_SIZE 33
16
17#define TEAM_NONE 0
18#define TEAM_SPECTATORS 1
19#define TEAM_SURVIVORS 2
20#define TEAM_INFECTED 3
21
22#define IS_VALID_CLIENT(%1) ((%1) > 0 && (%1) <= MaxClients)
23#define IS_VALID_INGAME(%1) (IS_VALID_CLIENT(%1) && IsClientInGame(%1))
24
25#define EVENTFLAGS_CHAT (1 << 0)
26#define EVENTFLAGS_TEAMCHAT (1 << 1)
27#define EVENTFLAGS_TEAM_SPECTATORS (TEAM_SPECTATORS << 2)
28#define EVENTFLAGS_TEAM_SURVIVORS (TEAM_SURVIVORS << 2)
29#define EVENTFLAGS_TEAM_INFECTED (TEAM_INFECTED << 2)
30
31public Plugin myinfo =
32{
33 name = "Logger",
34 author = "Martijn",
35 description = "Logs things.",
36 version = "0.8",
37 url = "http://www.sourcemod.net/"
38};
39
40static Database g_hDatabase = Database:INVALID_HANDLE;
41static int g_iLogID = 0;
42static int g_iPlayerIds[MAXPLAYERS + 1] = { 0, ... };
43static bool g_bTeamSay[MAXPLAYERS + 1] = { false, ... };
44
45public void OnPluginStart()
46{
47 g_hDatabase = ConnectToDatabase("sourcebans");
48
49 RegConsoleCmd("say", OnSay);
50 RegConsoleCmd("say_team", OnSayTeam);
51
52 // TryHookEvent("player_connect", OnPlayerConnect);
53 TryHookEvent("player_disconnect", OnPlayerDisconnect);
54 TryHookEvent("player_changename", OnPlayerChangeName);
55 TryHookEvent("player_say", OnPlayerChat);
56
57 HookUserMessage(GetUserMessageId("VoteStart"), OnVoteStart);
58 HookUserMessage(GetUserMessageId("VotePass"), OnVotePass);
59 HookUserMessage(GetUserMessageId("VoteFail"), OnVoteFail);
60}
61
62public void OnPluginEnd()
63{
64 CloseHandle(g_hDatabase);
65}
66
67//===========================================================================//
68// Logging stuff //
69//===========================================================================//
70
71static int LogPlayerId(const char[] steamid)
72{
73 char query[256];
74 Format(query, sizeof(query), "INSERT INTO sb_player_ids (steamid) VALUES ('%s') ON DUPLICATE KEY UPDATE id=LAST_INSERT_ID(id)", steamid);
75
76 SQL_LockDatabase(g_hDatabase);
77 FastQuery(g_hDatabase, query);
78 int playerid = SQL_GetInsertId(g_hDatabase);
79 SQL_UnlockDatabase(g_hDatabase);
80
81 return playerid;
82}
83
84static void LogPlayerIP(int playerid, const char[] ip)
85{
86 // Get the current date and time
87 char datetime[MAX_DATETIME_LENGTH];
88 FormatTime(datetime, sizeof(datetime), "%Y-%m-%d %H:%M:%S");
89
90 char query[256];
91 Format(query, sizeof(query), "INSERT INTO sb_player_ips (playerid, ip, last_seen) VALUES (%d, '%s', '%s') ON DUPLICATE KEY UPDATE last_seen = '%s'", playerid, ip, datetime, datetime);
92
93 SQL_TQuery(g_hDatabase, OnQueryCompleted, query);
94}
95
96static void LogPlayerName(int playerid, const char[] name)
97{
98 char escapedName[2 * MAX_NAME_LENGTH];
99 SQL_EscapeString(g_hDatabase, name, escapedName, sizeof(escapedName));
100
101 // Get the current date and time
102 char datetime[MAX_DATETIME_LENGTH];
103 FormatTime(datetime, sizeof(datetime), "%Y-%m-%d %H:%M:%S");
104
105 char query[256];
106 Format(query, sizeof(query), "INSERT INTO sb_player_names (playerid, name, last_seen) VALUES (%d, '%s', '%s') ON DUPLICATE KEY UPDATE last_seen = '%s'", playerid, escapedName, datetime, datetime);
107
108 SQL_TQuery(g_hDatabase, OnQueryCompleted, query);
109}
110
111static void LogServerEvent(int playerid, const char[] text, any ...)
112{
113 char formattedText[256];
114
115 // Format the text
116 VFormat(formattedText, sizeof(formattedText), text, 3);
117
118 LogEvent(playerid, "CONSOLE", GetEventFlags(false, false, 0), formattedText);
119}
120
121static void LogChatEvent(int playerid, const char[] name, bool teamChat, int team, const char[] text)
122{
123 LogEvent(playerid, name, GetEventFlags(true, teamChat, team), text);
124}
125
126static void LogEvent(int playerid, const char[] name, int flags, const char[] text)
127{
128 char query1[512], query2[128];
129 char formattedText[256], escapedText[256], escapedName[64];
130 char datetime[MAX_DATETIME_LENGTH];
131
132 // Ensure that a log is opened
133 if (g_iLogID == 0)
134 g_iLogID = CreateLog(g_hDatabase);
135
136 // Format the text
137 VFormat(formattedText, sizeof(formattedText), text, 4);
138
139 // Escape the text and name so that it is safe to insert into a query
140 SQL_EscapeString(g_hDatabase, formattedText, escapedText, sizeof(escapedText));
141 SQL_EscapeString(g_hDatabase, name, escapedName, sizeof(escapedName));
142
143 // Get the current date and time
144 FormatTime(datetime, sizeof(datetime), "%Y-%m-%d %H:%M:%S");
145
146 // Format the query
147 Format(query1, sizeof(query1), "INSERT INTO sb_game_events (gameid, playerid, time, name, flags, text) VALUES (%d, NULLIF(%d, 0), '%s', '%s', %d, '%s')",
148 g_iLogID, playerid, datetime, escapedName, flags, escapedText);
149
150 Format(query2, sizeof(query2), "UPDATE sb_games SET ended_at='%s' WHERE id=%d", datetime, g_iLogID);
151
152 SQL_TQuery(g_hDatabase, OnQueryCompleted, query1);
153 SQL_TQuery(g_hDatabase, OnQueryCompleted, query2);
154}
155
156static void LinkPlayerWithLog(int playerid)
157{
158 // Ensure that a log is opened
159 if (g_iLogID == 0)
160 g_iLogID = CreateLog(g_hDatabase);
161
162 char query[256];
163 Format(query, sizeof(query), "INSERT IGNORE INTO sb_game_players (gameid, playerid) VALUES (%d, %d)", g_iLogID, playerid);
164
165 SQL_TQuery(g_hDatabase, OnQueryCompleted, query);
166}
167
168static int CreateLog(Database database)
169{
170 // Get the server's hostname
171 char hostname[128];
172 char escapedHostname[128];
173
174 ServerCommand("sn_hostname");
175 GetConVarString(FindConVar("hostname"), hostname, sizeof(hostname));
176 SQL_EscapeString(g_hDatabase, hostname, escapedHostname, sizeof(escapedHostname));
177
178 // Get the current date and time
179 char datetime[MAX_DATETIME_LENGTH];
180 FormatTime(datetime, sizeof(datetime), "%Y-%m-%d %H:%M:%S");
181
182 // Get the server's IP
183 char ip[MAX_IP_LENGTH];
184 GetConVarString(FindConVar("ip"), ip, sizeof(ip));
185
186 // Get the server's hostport
187 int port = GetConVarInt(FindConVar("hostport"));
188
189 SQL_LockDatabase(database);
190
191 // Find the serverid
192 char query[256];
193 Format(query, sizeof(query), "SELECT sid FROM sb_servers WHERE ip='%s' AND port=%d", ip, port);
194 DBResultSet result = Query(database, query);
195 // TODO: Fail check
196 SQL_FetchRow(result);
197 int serverid = SQL_FetchInt(result, 0);
198 CloseHandle(result);
199
200 // Build the query
201 Format(query, sizeof(query), "INSERT IGNORE INTO sb_games (serverid, hostname, started_at, ended_at) VALUES (%d, '%s', '%s', '%s')", serverid, escapedHostname, datetime, datetime);
202 FastQuery(database, query);
203 int logID = SQL_GetInsertId(database);
204 SQL_UnlockDatabase(database);
205
206 return logID;
207}
208
209static int GetEventFlags(bool chat, bool teamchat, int team)
210{
211 return (chat ? EVENTFLAGS_CHAT : 0) | (teamchat ? EVENTFLAGS_TEAMCHAT : 0) | (team << 2);
212}
213
214static int GetClientPlayerId(int client)
215{
216 return g_iPlayerIds[client];
217}
218
219static int SetClientPlayerId(int client, int id)
220{
221 g_iPlayerIds[client] = id;
222}
223
224//===========================================================================//
225// Database stuff //
226//===========================================================================//
227
228public Database ConnectToDatabase(const char[] confName)
229{
230 char error[128];
231 Database database = SQL_Connect(confName, true, error, sizeof(error));
232
233 if (database == INVALID_HANDLE) {
234 SetFailState(ERR_SQL_CONNECT_FAILED, error);
235 }
236
237 CreateTables(database);
238
239 return database;
240}
241
242static void CreateTables(Database database)
243{
244 SQL_SetCharset(database, "utf8");
245
246 CloseHandle(SQL_Query(database,
247 "CREATE TABLE IF NOT EXISTS sb_player_ids (\
248 id INT NOT NULL AUTO_INCREMENT,\
249 steamid VARCHAR(20) NOT NULL,\
250 PRIMARY KEY (steamid),\
251 KEY(id)\
252 ) DEFAULT CHARSET=utf8;"));
253
254 CloseHandle(SQL_Query(database,
255 "CREATE TABLE IF NOT EXISTS sb_player_ips (\
256 playerid INT NOT NULL,\
257 ip VARCHAR(15) NOT NULL,\
258 last_seen DATETIME NOT NULL,\
259 PRIMARY KEY (playerid, ip),\
260 FOREIGN KEY (playerid) REFERENCES sb_player_ids(id) ON DELETE CASCADE\
261 ) DEFAULT CHARSET=utf8;"));
262
263 CloseHandle(SQL_Query(database,
264 "CREATE TABLE IF NOT EXISTS sb_player_names (\
265 playerid INT NOT NULL,\
266 name VARCHAR(32) NOT NULL,\
267 last_seen DATETIME NOT NULL,\
268 PRIMARY KEY (playerid, name),\
269 FOREIGN KEY (playerid) REFERENCES sb_player_ids(id) ON DELETE CASCADE\
270 ) DEFAULT CHARSET=utf8;"));
271
272 CloseHandle(SQL_Query(database,
273 "CREATE TABLE IF NOT EXISTS sb_games (\
274 id INT NOT NULL AUTO_INCREMENT,\
275 serverid INT NOT NULL,\
276 hostname VARCHAR(64) NOT NULL,\
277 started_at DATETIME NOT NULL,\
278 ended_at DATETIME NOT NULL,\
279 PRIMARY KEY (id),\
280 KEY(serverid, started_at)\
281 ) DEFAULT CHARSET=utf8;"));
282
283 CloseHandle(SQL_Query(database,
284 "CREATE TABLE IF NOT EXISTS sb_game_events (\
285 id INT NOT NULL AUTO_INCREMENT,\
286 gameid INT NOT NULL,\
287 playerid INT,\
288 time DATETIME NOT NULL,\
289 name VARCHAR(32) NOT NULL,\
290 flags INT,\
291 text TEXT,\
292 PRIMARY KEY (id),\
293 FOREIGN KEY (gameid) REFERENCES sb_games(id) ON DELETE CASCADE,\
294 FOREIGN KEY (playerid) REFERENCES sb_player_ids(id) ON DELETE SET NULL\
295 ) DEFAULT CHARSET=utf8;"));
296
297 CloseHandle(SQL_Query(database,
298 "CREATE TABLE IF NOT EXISTS sb_game_players (\
299 gameid INT NOT NULL,\
300 playerid INT,\
301 PRIMARY KEY (gameid, playerid),\
302 FOREIGN KEY (gameid) REFERENCES sb_games(id) ON DELETE CASCADE,\
303 FOREIGN KEY (playerid) REFERENCES sb_player_ids(id) ON DELETE CASCADE\
304 ) DEFAULT CHARSET=utf8;"));
305}
306
307public void OnCreateTables(Database db, any data, int numQueries, Handle[] results, any[] queryData)
308{
309}
310
311public void OnCreateTablesFailed(Database db, any data, int numQueries, const char[] error, int failIndex, any[] queryData)
312{
313 SetFailState(ERR_SQL_CREATE_TABLES_FAILED, error);
314}
315
316public void OnQueryCompleted(Handle owner, Handle query, const char[] error, any data)
317{
318 if (query == INVALID_HANDLE) {
319 LogError(ERR_SQL_EXECUTE_QUERY_FAILED, error);
320 }
321 else {
322 CloseHandle(query);
323 }
324}
325
326static void FastQuery(Database database, const char[] query)
327{
328 CloseHandle(Query(database, query));
329}
330
331static DBResultSet Query(Database database, const char[] query)
332{
333 DBResultSet result = SQL_Query(database, query);
334
335 if (result == INVALID_HANDLE) {
336 char error[128];
337
338 SQL_GetError(database, error, sizeof(error));
339 SQL_UnlockDatabase(database);
340
341 SetFailState(ERR_SQL_EXECUTE_QUERY_FAILED, error);
342
343 return DBResultSet:INVALID_HANDLE;
344 }
345
346 return result;
347}
348
349//===========================================================================//
350// Events stuff //
351//===========================================================================//
352
353public Action OnSay(client, args)
354{
355 g_bTeamSay[client] = false;
356}
357
358public Action OnSayTeam(client, args)
359{
360 g_bTeamSay[client] = true;
361}
362
363public void OnClientPostAdminCheck(int client)
364{
365 if (!IS_VALID_INGAME(client) || IsFakeClient(client) || GetClientPlayerId(client) != 0)
366 return;
367
368 char name[MAX_NAME_LENGTH];
369 char ip[MAX_IP_LENGTH];
370 char steamid[MAX_STEAMID_LENGTH];
371
372 GetClientName(client, name, sizeof(name));
373 GetClientIP(client, ip, sizeof(ip));
374 GetClientAuthString(client, steamid, sizeof(steamid));
375
376 int playerid = LogPlayerId(steamid);
377 LogPlayerIP(playerid, ip);
378 LogPlayerName(playerid, name);
379 LogServerEvent(playerid, "%s connected", name);
380 LinkPlayerWithLog(playerid);
381
382 SetClientPlayerId(client, playerid);
383}
384
385public Action OnPlayerDisconnect(Event event, const char[] eventName, bool dontBroadcast) {
386 // BUG: The event's bot flag is broken, we have to use the client id instead to check if we're dealing with a bot
387 int userid = GetEventInt(event, "userid");
388 int client = GetClientOfUserId(userid);
389 int playerid = GetClientPlayerId(client);
390
391 if (!IS_VALID_CLIENT(client) || IsFakeClient(client) || playerid == 0)
392 return Plugin_Continue;
393
394 char name[MAX_NAME_LENGTH];
395 char reason[128];
396
397 GetEventString(event, "name", name, sizeof(name));
398 GetEventString(event, "reason", reason, sizeof(reason));
399
400 LogServerEvent(playerid, "%s disconnected - %s", name, reason);
401
402 SetClientPlayerId(client, 0);
403
404 if (GetPlayerCount(client) == 0) {
405 g_iLogID = 0;
406 }
407
408 return Plugin_Continue;
409}
410
411public Action OnPlayerChangeName(Event event, const char[] eventName, bool dontBroadcast)
412{
413 int userid = GetEventInt(event, "userid");
414 int client = GetClientOfUserId(userid);
415 int playerid = GetClientPlayerId(client);
416
417 if (!IS_VALID_INGAME(client) || IsFakeClient(client))
418 return Plugin_Continue;
419
420 char oldname[MAX_NAME_LENGTH];
421 char newname[MAX_NAME_LENGTH];
422 char steamid[MAX_STEAMID_LENGTH];
423
424 GetEventString(event, "oldname", oldname, sizeof(oldname));
425 GetEventString(event, "newname", newname, sizeof(newname));
426 GetClientAuthString(client, steamid, sizeof(steamid));
427
428 LogPlayerName(playerid, newname);
429 LogServerEvent(playerid, "* %s changed name to %s", oldname, newname);
430
431 return Plugin_Continue;
432}
433
434public Action OnPlayerChat(Event event, const char[] eventName, bool dontBroadcast)
435{
436 int userid = GetEventInt(event, "userid");
437 int client = GetClientOfUserId(userid);
438 int playerid = g_iPlayerIds[client];
439
440 if (!IS_VALID_INGAME(client) || IsFakeClient(client))
441 return Plugin_Continue;
442
443 char name[MAX_NAME_LENGTH];
444 char text[128];
445
446 GetClientName(client, name, sizeof(name));
447 GetEventString(event, "text", text, sizeof(text));
448
449 LogChatEvent(playerid, name, g_bTeamSay[client], GetClientTeam(client), text);
450
451 return Plugin_Continue;
452}
453
454static int GetPlayerCount(int exclude=0)
455{
456 int players = 0;
457
458 for (int client = 1; client <= MaxClients; client++) {
459 if (client == exclude)
460 continue;
461
462 if (IS_VALID_INGAME(client) && !IsFakeClient(client))
463 players++;
464 }
465
466 return players;
467}
468
469static void TryHookEvent(const char[] name, EventHook callback)
470{
471 if (!HookEventEx(name, callback, EventHookMode_Post))
472 LogError(ERR_HOOK_EVENT_FAILED, name);
473}
474
475//===========================================================================//
476// Vote hook stuff //
477//===========================================================================//
478
479public Action:OnVoteStart(UserMsg msg_id, Handle bf, const players[], int playersNum, bool reliable, bool init)
480{
481 char name[MAX_NAME_LENGTH];
482 char details[MAX_ARG_SIZE];
483 char argument[MAX_ARG_SIZE];
484
485 BfReadByte(bf);
486 int client = BfReadByte(bf);
487 BfReadString(bf, details, sizeof(details));
488 BfReadString(bf, argument, sizeof(argument));
489
490 GetClientName(client, name, sizeof(name));
491 int playerid = GetClientPlayerId(client);
492
493 LogServerEvent(playerid, "* %s called vote: %s \"%s\"", name, details, argument);
494
495 return Plugin_Continue;
496}
497
498public Action:OnVotePass(UserMsg msg_id, Handle bf, const players[], int playersNum, bool reliable, bool init)
499{
500 char details[MAX_ARG_SIZE];
501 char param1[MAX_ARG_SIZE];
502
503 BfReadByte(bf);
504 BfReadString(bf, details, sizeof(details));
505 BfReadString(bf, param1, sizeof(param1));
506
507 LogServerEvent(0, "* Vote passed: %s \"%s\"", details, param1);
508
509 return Plugin_Continue;
510}
511
512public Action:OnVoteFail(UserMsg msg_id, Handle bf, const players[], int playersNum, bool reliable, bool init)
513{
514 LogServerEvent(0, "* Vote failed");
515
516 return Plugin_Continue;
517}