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