summaryrefslogtreecommitdiff
path: root/sourcemod/scripting/gokz-localdb/db
diff options
context:
space:
mode:
authornavewindre <nw@moneybot.cc>2023-12-04 18:06:10 +0100
committernavewindre <nw@moneybot.cc>2023-12-04 18:06:10 +0100
commitaef0d1c1268ab7d4bc18996c9c6b4da16a40aadc (patch)
tree43e766b51704f4ab8b383583bdc1871eeeb9c698 /sourcemod/scripting/gokz-localdb/db
parent38f1140c11724da05a23a10385061200b907cf6e (diff)
bbbbbbbbwaaaaaaaaaaa
Diffstat (limited to 'sourcemod/scripting/gokz-localdb/db')
-rw-r--r--sourcemod/scripting/gokz-localdb/db/cache_js.sp67
-rw-r--r--sourcemod/scripting/gokz-localdb/db/create_tables.sp36
-rw-r--r--sourcemod/scripting/gokz-localdb/db/helpers.sp18
-rw-r--r--sourcemod/scripting/gokz-localdb/db/save_js.sp291
-rw-r--r--sourcemod/scripting/gokz-localdb/db/save_time.sp83
-rw-r--r--sourcemod/scripting/gokz-localdb/db/set_cheater.sp64
-rw-r--r--sourcemod/scripting/gokz-localdb/db/setup_client.sp99
-rw-r--r--sourcemod/scripting/gokz-localdb/db/setup_database.sp34
-rw-r--r--sourcemod/scripting/gokz-localdb/db/setup_map.sp71
-rw-r--r--sourcemod/scripting/gokz-localdb/db/setup_map_courses.sp45
-rw-r--r--sourcemod/scripting/gokz-localdb/db/sql.sp406
-rw-r--r--sourcemod/scripting/gokz-localdb/db/timer_setup.sp167
12 files changed, 1381 insertions, 0 deletions
diff --git a/sourcemod/scripting/gokz-localdb/db/cache_js.sp b/sourcemod/scripting/gokz-localdb/db/cache_js.sp
new file mode 100644
index 0000000..b0df708
--- /dev/null
+++ b/sourcemod/scripting/gokz-localdb/db/cache_js.sp
@@ -0,0 +1,67 @@
+/*
+ Caches the player's personal best jumpstats.
+*/
+
+
+
+void DB_CacheJSPBs(int client, int steamID)
+{
+ ClearCache(client);
+
+ char query[1024];
+
+ Transaction txn = SQL_CreateTransaction();
+
+ FormatEx(query, sizeof(query), sql_jumpstats_getpbs, steamID);
+ txn.AddQuery(query);
+
+ FormatEx(query, sizeof(query), sql_jumpstats_getblockpbs, steamID, steamID);
+ txn.AddQuery(query);
+
+ SQL_ExecuteTransaction(gH_DB, txn, DB_TxnSuccess_CacheJSPBs, DB_TxnFailure_Generic, GetClientUserId(client), DBPrio_High);
+}
+
+public void DB_TxnSuccess_CacheJSPBs(Handle db, int userID, int numQueries, Handle[] results, any[] queryData)
+{
+ int client = GetClientOfUserId(userID);
+ if (client < 1 || client > MaxClients || !IsClientAuthorized(client) || IsFakeClient(client))
+ {
+ return;
+ }
+
+ int distance, mode, jumpType, block;
+
+ while (SQL_FetchRow(results[0]))
+ {
+ distance = SQL_FetchInt(results[0], 0);
+ mode = SQL_FetchInt(results[0], 1);
+ jumpType = SQL_FetchInt(results[0], 2);
+
+ gI_PBJSCache[client][mode][jumpType][JumpstatDB_Cache_Distance] = block;
+ }
+
+ while (SQL_FetchRow(results[1]))
+ {
+ distance = SQL_FetchInt(results[1], 0);
+ mode = SQL_FetchInt(results[1], 1);
+ jumpType = SQL_FetchInt(results[1], 2);
+ block = SQL_FetchInt(results[1], 3);
+
+ gI_PBJSCache[client][mode][jumpType][JumpstatDB_Cache_BlockDistance] = distance;
+ gI_PBJSCache[client][mode][jumpType][JumpstatDB_Cache_Block] = block;
+ }
+}
+
+void ClearCache(int client)
+{
+ for (int mode = 0; mode < MODE_COUNT; mode += 1)
+ {
+ for (int type = 0; type < JUMPTYPE_COUNT; type += 1)
+ {
+ for (int cache = 0; cache < JUMPSTATDB_CACHE_COUNT; cache += 1)
+ {
+ gI_PBJSCache[client][mode][type][cache] = 0;
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/sourcemod/scripting/gokz-localdb/db/create_tables.sp b/sourcemod/scripting/gokz-localdb/db/create_tables.sp
new file mode 100644
index 0000000..2138830
--- /dev/null
+++ b/sourcemod/scripting/gokz-localdb/db/create_tables.sp
@@ -0,0 +1,36 @@
+/*
+ Table creation and alteration.
+*/
+
+
+
+void DB_CreateTables()
+{
+ Transaction txn = SQL_CreateTransaction();
+
+ switch (g_DBType)
+ {
+ case DatabaseType_SQLite:
+ {
+ txn.AddQuery(sqlite_players_create);
+ txn.AddQuery(sqlite_maps_create);
+ txn.AddQuery(sqlite_mapcourses_create);
+ txn.AddQuery(sqlite_times_create);
+ txn.AddQuery(sqlite_jumpstats_create);
+ txn.AddQuery(sqlite_vbpos_create);
+ txn.AddQuery(sqlite_startpos_create);
+ }
+ case DatabaseType_MySQL:
+ {
+ txn.AddQuery(mysql_players_create);
+ txn.AddQuery(mysql_maps_create);
+ txn.AddQuery(mysql_mapcourses_create);
+ txn.AddQuery(mysql_times_create);
+ txn.AddQuery(mysql_jumpstats_create);
+ txn.AddQuery(mysql_vbpos_create);
+ txn.AddQuery(mysql_startpos_create);
+ }
+ }
+
+ SQL_ExecuteTransaction(gH_DB, txn, _, DB_TxnFailure_Generic, _, DBPrio_High);
+} \ No newline at end of file
diff --git a/sourcemod/scripting/gokz-localdb/db/helpers.sp b/sourcemod/scripting/gokz-localdb/db/helpers.sp
new file mode 100644
index 0000000..1eff866
--- /dev/null
+++ b/sourcemod/scripting/gokz-localdb/db/helpers.sp
@@ -0,0 +1,18 @@
+/*
+ Database helper functions and callbacks.
+*/
+
+
+
+/* Error report callback for failed transactions */
+public void DB_TxnFailure_Generic(Handle db, any data, int numQueries, const char[] error, int failIndex, any[] queryData)
+{
+ LogError("Database transaction error: %s", error);
+}
+
+/* Error report callback for failed transactions which deletes the DataPack */
+public void DB_TxnFailure_Generic_DataPack(Handle db, DataPack data, int numQueries, const char[] error, int failIndex, any[] queryData)
+{
+ delete data;
+ LogError("Database transaction error: %s", error);
+}
diff --git a/sourcemod/scripting/gokz-localdb/db/save_js.sp b/sourcemod/scripting/gokz-localdb/db/save_js.sp
new file mode 100644
index 0000000..1d50754
--- /dev/null
+++ b/sourcemod/scripting/gokz-localdb/db/save_js.sp
@@ -0,0 +1,291 @@
+/*
+ Inserts or updates the player's jumpstat into the database.
+*/
+
+
+
+public void OnLanding_SaveJumpstat(Jump jump)
+{
+ int mode = GOKZ_GetCoreOption(jump.jumper, Option_Mode);
+
+ // No tiers given for 'Invalid' jumps.
+ if (jump.type == JumpType_Invalid || jump.type == JumpType_FullInvalid
+ || jump.type == JumpType_Fall || jump.type == JumpType_Other
+ || jump.type != JumpType_LadderJump && jump.offset < -JS_OFFSET_EPSILON
+ || jump.distance > JS_MAX_JUMP_DISTANCE
+ || jump.type == JumpType_LadderJump && jump.distance < JS_MIN_LAJ_BLOCK_DISTANCE
+ || jump.type != JumpType_LadderJump && jump.distance < JS_MIN_BLOCK_DISTANCE)
+ {
+ return;
+ }
+
+ char query[1024];
+ DataPack data;
+ int steamid = GetSteamAccountID(jump.jumper);
+ int int_dist = RoundToNearest(jump.distance * GOKZ_DB_JS_DISTANCE_PRECISION);
+
+ // Non-block
+ if (gI_PBJSCache[jump.jumper][mode][jump.type][JumpstatDB_Cache_Distance] == 0
+ || int_dist > gI_PBJSCache[jump.jumper][mode][jump.type][JumpstatDB_Cache_Distance])
+ {
+ data = JSRecord_FillDataPack(jump, steamid, mode, false);
+ Transaction txn_noblock = SQL_CreateTransaction();
+ FormatEx(query, sizeof(query), sql_jumpstats_getrecord, steamid, jump.type, mode, 0);
+ txn_noblock.AddQuery(query);
+ SQL_ExecuteTransaction(gH_DB, txn_noblock, DB_TxnSuccess_LookupJSRecordForSave, DB_TxnFailure_Generic_DataPack, data, DBPrio_Low);
+ }
+
+ // Block
+ if (jump.block > 0
+ && (gI_PBJSCache[jump.jumper][mode][jump.type][JumpstatDB_Cache_Block] == 0
+ || (jump.block > gI_PBJSCache[jump.jumper][mode][jump.type][JumpstatDB_Cache_Block]
+ || jump.block == gI_PBJSCache[jump.jumper][mode][jump.type][JumpstatDB_Cache_Block]
+ && int_dist > gI_PBJSCache[jump.jumper][mode][jump.type][JumpstatDB_Cache_BlockDistance])))
+ {
+ data = JSRecord_FillDataPack(jump, steamid, mode, true);
+ Transaction txn_block = SQL_CreateTransaction();
+ FormatEx(query, sizeof(query), sql_jumpstats_getrecord, steamid, jump.type, mode, 1);
+ txn_block.AddQuery(query);
+ SQL_ExecuteTransaction(gH_DB, txn_block, DB_TxnSuccess_LookupJSRecordForSave, DB_TxnFailure_Generic_DataPack, data, DBPrio_Low);
+ }
+}
+
+static DataPack JSRecord_FillDataPack(Jump jump, int steamid, int mode, bool blockJump)
+{
+ DataPack data = new DataPack();
+ data.WriteCell(jump.jumper);
+ data.WriteCell(steamid);
+ data.WriteCell(jump.type);
+ data.WriteCell(mode);
+ data.WriteCell(RoundToNearest(jump.distance * GOKZ_DB_JS_DISTANCE_PRECISION));
+ data.WriteCell(blockJump ? jump.block : 0);
+ data.WriteCell(jump.strafes);
+ data.WriteCell(RoundToNearest(jump.sync * GOKZ_DB_JS_SYNC_PRECISION));
+ data.WriteCell(RoundToNearest(jump.preSpeed * GOKZ_DB_JS_PRE_PRECISION));
+ data.WriteCell(RoundToNearest(jump.maxSpeed * GOKZ_DB_JS_MAX_PRECISION));
+ data.WriteCell(RoundToNearest(jump.duration * GetTickInterval() * GOKZ_DB_JS_AIRTIME_PRECISION));
+ return data;
+}
+
+public void DB_TxnSuccess_LookupJSRecordForSave(Handle db, DataPack data, int numQueries, Handle[] results, any[] queryData)
+{
+ data.Reset();
+ int client = data.ReadCell();
+ int steamid = data.ReadCell();
+ int jumpType = data.ReadCell();
+ int mode = data.ReadCell();
+ int distance = data.ReadCell();
+ int block = data.ReadCell();
+ int strafes = data.ReadCell();
+ int sync = data.ReadCell();
+ int pre = data.ReadCell();
+ int max = data.ReadCell();
+ int airtime = data.ReadCell();
+
+ if (!IsValidClient(client))
+ {
+ delete data;
+ return;
+ }
+
+ char query[1024];
+ int rows = SQL_GetRowCount(results[0]);
+ if (rows == 0)
+ {
+ FormatEx(query, sizeof(query), sql_jumpstats_insert, steamid, jumpType, mode, distance, block > 0, block, strafes, sync, pre, max, airtime);
+ }
+ else
+ {
+ SQL_FetchRow(results[0]);
+ int rec_distance = SQL_FetchInt(results[0], JumpstatDB_Lookup_Distance);
+ int rec_block = SQL_FetchInt(results[0], JumpstatDB_Lookup_Block);
+
+ if (rec_block == 0)
+ {
+ gI_PBJSCache[client][mode][jumpType][JumpstatDB_Cache_Distance] = rec_distance;
+ }
+ else
+ {
+ gI_PBJSCache[client][mode][jumpType][JumpstatDB_Cache_Block] = rec_block;
+ gI_PBJSCache[client][mode][jumpType][JumpstatDB_Cache_BlockDistance] = rec_distance;
+ }
+
+ if (block < rec_block || block == rec_block && distance < rec_distance)
+ {
+ delete data;
+ return;
+ }
+
+ if (rows < GOKZ_DB_JS_MAX_JUMPS_PER_PLAYER)
+ {
+ FormatEx(query, sizeof(query), sql_jumpstats_insert, steamid, jumpType, mode, distance, block > 0, block, strafes, sync, pre, max, airtime);
+ }
+ else
+ {
+ for (int i = 1; i < GOKZ_DB_JS_MAX_JUMPS_PER_PLAYER; i++)
+ {
+ SQL_FetchRow(results[0]);
+ }
+ int min_rec_id = SQL_FetchInt(results[0], JumpstatDB_Lookup_JumpID);
+ FormatEx(query, sizeof(query), sql_jumpstats_update, steamid, jumpType, mode, distance, block > 0, block, strafes, sync, pre, max, airtime, min_rec_id);
+ }
+
+ }
+
+ Transaction txn = SQL_CreateTransaction();
+ txn.AddQuery(query);
+ SQL_ExecuteTransaction(gH_DB, txn, DB_TxnSuccess_SaveJSRecord, DB_TxnFailure_Generic_DataPack, data, DBPrio_Low);
+}
+
+public void DB_TxnSuccess_SaveJSRecord(Handle db, DataPack data, int numQueries, Handle[] results, any[] queryData)
+{
+ data.Reset();
+ int client = data.ReadCell();
+ data.ReadCell();
+ int jumpType = data.ReadCell();
+ int mode = data.ReadCell();
+ int distance = data.ReadCell();
+ int block = data.ReadCell();
+ int strafes = data.ReadCell();
+ int sync = data.ReadCell();
+ int pre = data.ReadCell();
+ int max = data.ReadCell();
+ int airtime = data.ReadCell();
+ delete data;
+
+ if (!IsValidClient(client) || GOKZ_JS_GetOption(client, JSOption_JumpstatsMaster) == JSToggleOption_Disabled)
+ {
+ return;
+ }
+
+ float distanceFloat = float(distance) / GOKZ_DB_JS_DISTANCE_PRECISION;
+ float syncFloat = float(sync) / GOKZ_DB_JS_SYNC_PRECISION;
+ float preFloat = float(pre) / GOKZ_DB_JS_PRE_PRECISION;
+ float maxFloat = float(max) / GOKZ_DB_JS_MAX_PRECISION;
+
+ if (block == 0)
+ {
+ gI_PBJSCache[client][mode][jumpType][JumpstatDB_Cache_Distance] = distance;
+ GOKZ_PrintToChat(client, true, "%t", "Jump Record",
+ client,
+ gC_JumpTypes[jumpType],
+ distanceFloat,
+ gC_ModeNamesShort[mode]);
+ }
+ else
+ {
+ gI_PBJSCache[client][mode][jumpType][JumpstatDB_Cache_Block] = block;
+ gI_PBJSCache[client][mode][jumpType][JumpstatDB_Cache_BlockDistance] = distance;
+ GOKZ_PrintToChat(client, true, "%t", "Block Jump Record",
+ client,
+ block,
+ gC_JumpTypes[jumpType],
+ distanceFloat,
+ gC_ModeNamesShort[mode],
+ block);
+ }
+
+ Call_OnJumpstatPB(client, jumpType, mode, distanceFloat, block, strafes, syncFloat, preFloat, maxFloat, airtime);
+}
+
+public void DB_DeleteBestJump(int client, int steamAccountID, int jumpType, int mode, int isBlock)
+{
+ DataPack data = new DataPack();
+ data.WriteCell(client == 0 ? -1 : GetClientUserId(client)); // -1 if called from server console
+ data.WriteCell(steamAccountID);
+ data.WriteCell(jumpType);
+ data.WriteCell(mode);
+ data.WriteCell(isBlock);
+
+ char query[1024];
+
+ FormatEx(query, sizeof(query), sql_jumpstats_deleterecord, steamAccountID, jumpType, mode, isBlock);
+
+ Transaction txn = SQL_CreateTransaction();
+ txn.AddQuery(query);
+
+ SQL_ExecuteTransaction(gH_DB, txn, DB_TxnSuccess_BestJumpDeleted, DB_TxnFailure_Generic_DataPack, data, DBPrio_Low);
+}
+
+public void DB_TxnSuccess_BestJumpDeleted(Handle db, DataPack data, int numQueries, Handle[] results, any[] queryData)
+{
+ char blockString[16] = "";
+
+ data.Reset();
+ int client = GetClientOfUserId(data.ReadCell());
+ int steamAccountID = data.ReadCell();
+ int jumpType = data.ReadCell();
+ int mode = data.ReadCell();
+ bool isBlock = data.ReadCell() == 1;
+ delete data;
+
+ if (isBlock)
+ {
+ FormatEx(blockString, sizeof(blockString), "%T ", "Block", client);
+ }
+
+ ClearCache(client);
+
+ GOKZ_PrintToChatAndLog(client, true, "%t", "Best Jump Deleted",
+ gC_ModeNames[mode],
+ blockString,
+ gC_JumpTypes[jumpType],
+ steamAccountID & 1,
+ steamAccountID >> 1);
+}
+
+public void DB_DeleteAllJumps(int client, int steamAccountID)
+{
+ DataPack data = new DataPack();
+ data.WriteCell(client == 0 ? -1 : GetClientUserId(client)); // -1 if called from server console
+ data.WriteCell(steamAccountID);
+
+ char query[1024];
+
+ FormatEx(query, sizeof(query), sql_jumpstats_deleteallrecords, steamAccountID);
+
+ Transaction txn = SQL_CreateTransaction();
+ txn.AddQuery(query);
+
+ SQL_ExecuteTransaction(gH_DB, txn, DB_TxnSuccess_AllJumpsDeleted, DB_TxnFailure_Generic_DataPack, data, DBPrio_Low);
+}
+
+public void DB_TxnSuccess_AllJumpsDeleted(Handle db, DataPack data, int numQueries, Handle[] results, any[] queryData)
+{
+ data.Reset();
+ int client = GetClientOfUserId(data.ReadCell());
+ int steamAccountID = data.ReadCell();
+ delete data;
+
+ ClearCache(client);
+
+ GOKZ_PrintToChatAndLog(client, true, "%t", "All Jumps Deleted",
+ steamAccountID & 1,
+ steamAccountID >> 1);
+}
+
+public void DB_DeleteJump(int client, int jumpID)
+{
+ DataPack data = new DataPack();
+ data.WriteCell(client == 0 ? -1 : GetClientUserId(client)); // -1 if called from server console
+ data.WriteCell(jumpID);
+
+ char query[1024];
+ FormatEx(query, sizeof(query), sql_jumpstats_deletejump, jumpID);
+
+ Transaction txn = SQL_CreateTransaction();
+ txn.AddQuery(query);
+
+ SQL_ExecuteTransaction(gH_DB, txn, DB_TxnSuccess_JumpDeleted, DB_TxnFailure_Generic_DataPack, data, DBPrio_Low);
+}
+
+public void DB_TxnSuccess_JumpDeleted(Handle db, DataPack data, int numQueries, Handle[] results, any[] queryData)
+{
+ data.Reset();
+ int client = GetClientOfUserId(data.ReadCell());
+ int jumpID = data.ReadCell();
+ delete data;
+
+ GOKZ_PrintToChatAndLog(client, true, "%t", "Jump Deleted",
+ jumpID);
+}
diff --git a/sourcemod/scripting/gokz-localdb/db/save_time.sp b/sourcemod/scripting/gokz-localdb/db/save_time.sp
new file mode 100644
index 0000000..84589a5
--- /dev/null
+++ b/sourcemod/scripting/gokz-localdb/db/save_time.sp
@@ -0,0 +1,83 @@
+/*
+ Inserts the player's time into the database.
+*/
+
+
+
+void DB_SaveTime(int client, int course, int mode, int style, float runTime, int teleportsUsed)
+{
+ if (IsFakeClient(client))
+ {
+ return;
+ }
+
+ char query[1024];
+ int steamID = GetSteamAccountID(client);
+ int mapID = GOKZ_DB_GetCurrentMapID();
+ int runTimeMS = GOKZ_DB_TimeFloatToInt(runTime);
+
+ DataPack data = new DataPack();
+ data.WriteCell(GetClientUserId(client));
+ data.WriteCell(steamID);
+ data.WriteCell(mapID);
+ data.WriteCell(course);
+ data.WriteCell(mode);
+ data.WriteCell(style);
+ data.WriteCell(runTimeMS);
+ data.WriteCell(teleportsUsed);
+
+ Transaction txn = SQL_CreateTransaction();
+
+ // Save runTime to DB
+ FormatEx(query, sizeof(query), sql_times_insert, steamID, mode, style, runTimeMS, teleportsUsed, mapID, course);
+ txn.AddQuery(query);
+
+ SQL_ExecuteTransaction(gH_DB, txn, DB_TxnSuccess_SaveTime, DB_TxnFailure_Generic_DataPack, data, DBPrio_Normal);
+}
+
+public void DB_TxnSuccess_SaveTime(Handle db, DataPack data, int numQueries, Handle[] results, any[] queryData)
+{
+ data.Reset();
+ int client = GetClientOfUserId(data.ReadCell());
+ int steamID = data.ReadCell();
+ int mapID = data.ReadCell();
+ int course = data.ReadCell();
+ int mode = data.ReadCell();
+ int style = data.ReadCell();
+ int runTimeMS = data.ReadCell();
+ int teleportsUsed = data.ReadCell();
+ delete data;
+
+ if (!IsValidClient(client))
+ {
+ return;
+ }
+
+ Call_OnTimeInserted(client, steamID, mapID, course, mode, style, runTimeMS, teleportsUsed);
+}
+
+public void DB_DeleteTime(int client, int timeID)
+{
+ DataPack data = new DataPack();
+ data.WriteCell(client == 0 ? -1 : GetClientUserId(client)); // -1 if called from server console
+ data.WriteCell(timeID);
+
+ char query[1024];
+ FormatEx(query, sizeof(query), sql_times_delete, timeID);
+
+ Transaction txn = SQL_CreateTransaction();
+ txn.AddQuery(query);
+
+ SQL_ExecuteTransaction(gH_DB, txn, DB_TxnSuccess_TimeDeleted, DB_TxnFailure_Generic_DataPack, data, DBPrio_Low);
+}
+
+public void DB_TxnSuccess_TimeDeleted(Handle db, DataPack data, int numQueries, Handle[] results, any[] queryData)
+{
+ data.Reset();
+ int client = GetClientOfUserId(data.ReadCell());
+ int timeID = data.ReadCell();
+ delete data;
+
+ GOKZ_PrintToChatAndLog(client, true, "%t", "Time Deleted",
+ timeID);
+}
diff --git a/sourcemod/scripting/gokz-localdb/db/set_cheater.sp b/sourcemod/scripting/gokz-localdb/db/set_cheater.sp
new file mode 100644
index 0000000..c63f161
--- /dev/null
+++ b/sourcemod/scripting/gokz-localdb/db/set_cheater.sp
@@ -0,0 +1,64 @@
+/*
+ Sets whether player is a cheater in the database.
+*/
+
+
+
+void DB_SetCheater(int cheaterClient, bool cheater)
+{
+ if (gB_Cheater[cheaterClient] == cheater)
+ {
+ return;
+ }
+
+ gB_Cheater[cheaterClient] = cheater;
+
+ DataPack data = new DataPack();
+ data.WriteCell(-1);
+ data.WriteCell(GetSteamAccountID(cheaterClient));
+ data.WriteCell(cheater);
+
+ char query[128];
+
+ Transaction txn = SQL_CreateTransaction();
+
+ FormatEx(query, sizeof(query), sql_players_set_cheater, cheater ? 1 : 0, GetSteamAccountID(cheaterClient));
+ txn.AddQuery(query);
+
+ SQL_ExecuteTransaction(gH_DB, txn, DB_TxnSuccess_SetCheater, DB_TxnFailure_Generic_DataPack, data, DBPrio_High);
+}
+
+void DB_SetCheaterSteamID(int client, int cheaterSteamID, bool cheater)
+{
+ DataPack data = new DataPack();
+ data.WriteCell(client == 0 ? -1 : GetClientUserId(client)); // -1 if called from server console
+ data.WriteCell(cheaterSteamID);
+ data.WriteCell(cheater);
+
+ char query[128];
+
+ Transaction txn = SQL_CreateTransaction();
+
+ FormatEx(query, sizeof(query), sql_players_set_cheater, cheater ? 1 : 0, cheaterSteamID);
+ txn.AddQuery(query);
+
+ SQL_ExecuteTransaction(gH_DB, txn, DB_TxnSuccess_SetCheater, DB_TxnFailure_Generic_DataPack, data, DBPrio_High);
+}
+
+public void DB_TxnSuccess_SetCheater(Handle db, DataPack data, int numQueries, Handle[] results, any[] queryData)
+{
+ data.Reset();
+ int client = GetClientOfUserId(data.ReadCell());
+ int steamID = data.ReadCell();
+ bool cheater = view_as<bool>(data.ReadCell());
+ delete data;
+
+ if (cheater)
+ {
+ GOKZ_PrintToChatAndLog(client, true, "%t", "Set Cheater", steamID & 1, steamID >> 1);
+ }
+ else
+ {
+ GOKZ_PrintToChatAndLog(client, true, "%t", "Set Not Cheater", steamID & 1, steamID >> 1);
+ }
+} \ No newline at end of file
diff --git a/sourcemod/scripting/gokz-localdb/db/setup_client.sp b/sourcemod/scripting/gokz-localdb/db/setup_client.sp
new file mode 100644
index 0000000..848be87
--- /dev/null
+++ b/sourcemod/scripting/gokz-localdb/db/setup_client.sp
@@ -0,0 +1,99 @@
+/*
+ Inserts the player into the database, or else updates their information.
+*/
+
+
+
+void DB_SetupClient(int client)
+{
+ if (IsFakeClient(client))
+ {
+ return;
+ }
+
+ // Setup Client Step 1 - Upsert them into Players Table
+ char query[1024], name[MAX_NAME_LENGTH], nameEscaped[MAX_NAME_LENGTH * 2 + 1], clientIP[16], country[45];
+
+ int steamID = GetSteamAccountID(client);
+ if (!GetClientName(client, name, MAX_NAME_LENGTH))
+ {
+ LogMessage("Couldn't get name of %L.", client);
+ name = "Unknown";
+ }
+ SQL_EscapeString(gH_DB, name, nameEscaped, MAX_NAME_LENGTH * 2 + 1);
+ if (!GetClientIP(client, clientIP, sizeof(clientIP)))
+ {
+ LogMessage("Couldn't get IP of %L.", client);
+ clientIP = "Unknown";
+ }
+ if (!GeoipCountry(clientIP, country, sizeof(country)))
+ {
+ LogMessage("Couldn't get country of %L (%s).", client, clientIP);
+ country = "Unknown";
+ }
+
+ DataPack data = new DataPack();
+ data.WriteCell(GetClientUserId(client));
+ data.WriteCell(steamID);
+
+ Transaction txn = SQL_CreateTransaction();
+
+ // Insert/Update player into Players table
+ switch (g_DBType)
+ {
+ case DatabaseType_SQLite:
+ {
+ // UPDATE OR IGNORE
+ FormatEx(query, sizeof(query), sqlite_players_update, nameEscaped, country, clientIP, steamID);
+ txn.AddQuery(query);
+ // INSERT OR IGNORE
+ FormatEx(query, sizeof(query), sqlite_players_insert, nameEscaped, country, clientIP, steamID);
+ txn.AddQuery(query);
+ }
+ case DatabaseType_MySQL:
+ {
+ // INSERT ... ON DUPLICATE KEY ...
+ FormatEx(query, sizeof(query), mysql_players_upsert, nameEscaped, country, clientIP, steamID);
+ txn.AddQuery(query);
+ }
+ }
+
+ FormatEx(query, sizeof(query), sql_players_get_cheater, steamID);
+ txn.AddQuery(query);
+
+ SQL_ExecuteTransaction(gH_DB, txn, DB_TxnSuccess_SetupClient, DB_TxnFailure_Generic_DataPack, data, DBPrio_High);
+}
+
+public void DB_TxnSuccess_SetupClient(Handle db, DataPack data, int numQueries, Handle[] results, any[] queryData)
+{
+ data.Reset();
+ int client = GetClientOfUserId(data.ReadCell());
+ int steamID = data.ReadCell();
+ delete data;
+
+ if (client == 0 || !IsClientAuthorized(client))
+ {
+ return;
+ }
+
+ switch (g_DBType)
+ {
+ case DatabaseType_SQLite:
+ {
+ if (SQL_FetchRow(results[2]))
+ {
+ gB_Cheater[client] = SQL_FetchInt(results[2], 0) == 1;
+ }
+ }
+ case DatabaseType_MySQL:
+ {
+ if (SQL_FetchRow(results[1]))
+ {
+ gB_Cheater[client] = SQL_FetchInt(results[1], 0) == 1;
+ }
+ }
+ }
+
+ gB_ClientSetUp[client] = true;
+ Call_OnClientSetup(client, steamID, gB_Cheater[client]);
+} \ No newline at end of file
diff --git a/sourcemod/scripting/gokz-localdb/db/setup_database.sp b/sourcemod/scripting/gokz-localdb/db/setup_database.sp
new file mode 100644
index 0000000..4965541
--- /dev/null
+++ b/sourcemod/scripting/gokz-localdb/db/setup_database.sp
@@ -0,0 +1,34 @@
+/*
+ Set up the connection to the local database.
+*/
+
+
+
+void DB_SetupDatabase()
+{
+ char error[255];
+ gH_DB = SQL_Connect("gokz", true, error, sizeof(error));
+ if (gH_DB == null)
+ {
+ SetFailState("Database connection failed. Error: \"%s\".", error);
+ }
+
+ char databaseType[8];
+ SQL_ReadDriver(gH_DB, databaseType, sizeof(databaseType));
+ if (strcmp(databaseType, "sqlite", false) == 0)
+ {
+ g_DBType = DatabaseType_SQLite;
+ }
+ else if (strcmp(databaseType, "mysql", false) == 0)
+ {
+ g_DBType = DatabaseType_MySQL;
+ }
+ else
+ {
+ SetFailState("Incompatible database driver. Use SQLite or MySQL.");
+ }
+
+ DB_CreateTables();
+
+ Call_OnDatabaseConnect();
+} \ No newline at end of file
diff --git a/sourcemod/scripting/gokz-localdb/db/setup_map.sp b/sourcemod/scripting/gokz-localdb/db/setup_map.sp
new file mode 100644
index 0000000..a02e2d2
--- /dev/null
+++ b/sourcemod/scripting/gokz-localdb/db/setup_map.sp
@@ -0,0 +1,71 @@
+/*
+ Inserts the map information into the database.
+ Retrieves the MapID of the map and stores it in a global variable.
+*/
+
+
+
+void DB_SetupMap()
+{
+ gB_MapSetUp = false;
+
+ char query[1024];
+
+ char map[PLATFORM_MAX_PATH];
+ GetCurrentMapDisplayName(map, sizeof(map));
+
+ char escapedMap[PLATFORM_MAX_PATH * 2 + 1];
+ SQL_EscapeString(gH_DB, map, escapedMap, sizeof(escapedMap));
+
+ Transaction txn = SQL_CreateTransaction();
+
+ // Insert/Update map into database
+ switch (g_DBType)
+ {
+ case DatabaseType_SQLite:
+ {
+ // UPDATE OR IGNORE
+ FormatEx(query, sizeof(query), sqlite_maps_update, escapedMap);
+ txn.AddQuery(query);
+ // INSERT OR IGNORE
+ FormatEx(query, sizeof(query), sqlite_maps_insert, escapedMap);
+ txn.AddQuery(query);
+ }
+ case DatabaseType_MySQL:
+ {
+ // INSERT ... ON DUPLICATE KEY ...
+ FormatEx(query, sizeof(query), mysql_maps_upsert, escapedMap);
+ txn.AddQuery(query);
+ }
+ }
+ // Retrieve mapID of map name
+ FormatEx(query, sizeof(query), sql_maps_findid, escapedMap, escapedMap);
+ txn.AddQuery(query);
+
+ SQL_ExecuteTransaction(gH_DB, txn, DB_TxnSuccess_SetupMap, DB_TxnFailure_Generic, 0, DBPrio_High);
+}
+
+public void DB_TxnSuccess_SetupMap(Handle db, any data, int numQueries, Handle[] results, any[] queryData)
+{
+ switch (g_DBType)
+ {
+ case DatabaseType_SQLite:
+ {
+ if (SQL_FetchRow(results[2]))
+ {
+ gI_DBCurrentMapID = SQL_FetchInt(results[2], 0);
+ gB_MapSetUp = true;
+ Call_OnMapSetup();
+ }
+ }
+ case DatabaseType_MySQL:
+ {
+ if (SQL_FetchRow(results[1]))
+ {
+ gI_DBCurrentMapID = SQL_FetchInt(results[1], 0);
+ gB_MapSetUp = true;
+ Call_OnMapSetup();
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/sourcemod/scripting/gokz-localdb/db/setup_map_courses.sp b/sourcemod/scripting/gokz-localdb/db/setup_map_courses.sp
new file mode 100644
index 0000000..69bb89e
--- /dev/null
+++ b/sourcemod/scripting/gokz-localdb/db/setup_map_courses.sp
@@ -0,0 +1,45 @@
+/*
+ Inserts the map's courses into the database.
+*/
+
+
+
+void DB_SetupMapCourses()
+{
+ char query[512];
+
+ Transaction txn = SQL_CreateTransaction();
+
+ for (int course = 0; course < GOKZ_MAX_COURSES; course++)
+ {
+ if (!GOKZ_GetCourseRegistered(course))
+ {
+ continue;
+ }
+
+ switch (g_DBType)
+ {
+ case DatabaseType_SQLite:FormatEx(query, sizeof(query), sqlite_mapcourses_insert, gI_DBCurrentMapID, course);
+ case DatabaseType_MySQL:FormatEx(query, sizeof(query), mysql_mapcourses_insert, gI_DBCurrentMapID, course);
+ }
+ txn.AddQuery(query);
+ }
+
+ SQL_ExecuteTransaction(gH_DB, txn, INVALID_FUNCTION, DB_TxnFailure_Generic, _, DBPrio_High);
+}
+
+void DB_SetupMapCourse(int course)
+{
+ char query[512];
+
+ Transaction txn = SQL_CreateTransaction();
+
+ switch (g_DBType)
+ {
+ case DatabaseType_SQLite:FormatEx(query, sizeof(query), sqlite_mapcourses_insert, gI_DBCurrentMapID, course);
+ case DatabaseType_MySQL:FormatEx(query, sizeof(query), mysql_mapcourses_insert, gI_DBCurrentMapID, course);
+ }
+ txn.AddQuery(query);
+
+ SQL_ExecuteTransaction(gH_DB, txn, INVALID_FUNCTION, DB_TxnFailure_Generic, _, DBPrio_High);
+} \ No newline at end of file
diff --git a/sourcemod/scripting/gokz-localdb/db/sql.sp b/sourcemod/scripting/gokz-localdb/db/sql.sp
new file mode 100644
index 0000000..46ea5e3
--- /dev/null
+++ b/sourcemod/scripting/gokz-localdb/db/sql.sp
@@ -0,0 +1,406 @@
+/*
+ SQL query templates.
+*/
+
+
+
+// =====[ PLAYERS ]=====
+
+char sqlite_players_create[] = "\
+CREATE TABLE IF NOT EXISTS Players ( \
+ SteamID32 INTEGER NOT NULL, \
+ Alias TEXT, \
+ Country TEXT, \
+ IP TEXT, \
+ Cheater INTEGER NOT NULL DEFAULT '0', \
+ LastPlayed TIMESTAMP NULL DEFAULT NULL, \
+ Created TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, \
+ CONSTRAINT PK_Player PRIMARY KEY (SteamID32))";
+
+char mysql_players_create[] = "\
+CREATE TABLE IF NOT EXISTS Players ( \
+ SteamID32 INTEGER UNSIGNED NOT NULL, \
+ Alias VARCHAR(32), \
+ Country VARCHAR(45), \
+ IP VARCHAR(15), \
+ Cheater TINYINT UNSIGNED NOT NULL DEFAULT '0', \
+ LastPlayed TIMESTAMP NULL DEFAULT NULL, \
+ Created TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, \
+ CONSTRAINT PK_Player PRIMARY KEY (SteamID32))";
+
+char sqlite_players_insert[] = "\
+INSERT OR IGNORE INTO Players (Alias, Country, IP, SteamID32, LastPlayed) \
+ VALUES ('%s', '%s', '%s', %d, CURRENT_TIMESTAMP)";
+
+char sqlite_players_update[] = "\
+UPDATE OR IGNORE Players \
+ SET Alias='%s', Country='%s', IP='%s', LastPlayed=CURRENT_TIMESTAMP \
+ WHERE SteamID32=%d";
+
+char mysql_players_upsert[] = "\
+INSERT INTO Players (Alias, Country, IP, SteamID32, LastPlayed) \
+ VALUES ('%s', '%s', '%s', %d, CURRENT_TIMESTAMP) \
+ ON DUPLICATE KEY UPDATE \
+ SteamID32=VALUES(SteamID32), Alias=VALUES(Alias), Country=VALUES(Country), \
+ IP=VALUES(IP), LastPlayed=VALUES(LastPlayed)";
+
+char sql_players_get_cheater[] = "\
+SELECT Cheater \
+ FROM Players \
+ WHERE SteamID32=%d";
+
+char sql_players_set_cheater[] = "\
+UPDATE Players \
+ SET Cheater=%d \
+ WHERE SteamID32=%d";
+
+
+
+// =====[ MAPS ]=====
+
+char sqlite_maps_create[] = "\
+CREATE TABLE IF NOT EXISTS Maps ( \
+ MapID INTEGER NOT NULL, \
+ Name VARCHAR(32) NOT NULL UNIQUE, \
+ LastPlayed TIMESTAMP NULL DEFAULT NULL, \
+ Created TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, \
+ CONSTRAINT PK_Maps PRIMARY KEY (MapID))";
+
+char mysql_maps_create[] = "\
+CREATE TABLE IF NOT EXISTS Maps ( \
+ MapID INTEGER UNSIGNED NOT NULL AUTO_INCREMENT, \
+ Name VARCHAR(32) NOT NULL UNIQUE, \
+ LastPlayed TIMESTAMP NULL DEFAULT NULL, \
+ Created TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, \
+ CONSTRAINT PK_Maps PRIMARY KEY (MapID))";
+
+char sqlite_maps_insert[] = "\
+INSERT OR IGNORE INTO Maps (Name, LastPlayed) \
+ VALUES ('%s', CURRENT_TIMESTAMP)";
+
+char sqlite_maps_update[] = "\
+UPDATE OR IGNORE Maps \
+ SET LastPlayed=CURRENT_TIMESTAMP \
+ WHERE Name='%s'";
+
+char mysql_maps_upsert[] = "\
+INSERT INTO Maps (Name, LastPlayed) \
+ VALUES ('%s', CURRENT_TIMESTAMP) \
+ ON DUPLICATE KEY UPDATE \
+ LastPlayed=CURRENT_TIMESTAMP";
+
+char sql_maps_findid[] = "\
+SELECT MapID, Name \
+ FROM Maps \
+ WHERE Name LIKE '%%%s%%' \
+ ORDER BY (Name='%s') DESC, LENGTH(Name) \
+ LIMIT 1";
+
+
+
+// =====[ MAPCOURSES ]=====
+
+char sqlite_mapcourses_create[] = "\
+CREATE TABLE IF NOT EXISTS MapCourses ( \
+ MapCourseID INTEGER NOT NULL, \
+ MapID INTEGER NOT NULL, \
+ Course INTEGER NOT NULL, \
+ Created INTEGER NOT NULL DEFAULT CURRENT_TIMESTAMP, \
+ CONSTRAINT PK_MapCourses PRIMARY KEY (MapCourseID), \
+ CONSTRAINT UQ_MapCourses_MapIDCourse UNIQUE (MapID, Course), \
+ CONSTRAINT FK_MapCourses_MapID FOREIGN KEY (MapID) REFERENCES Maps(MapID) \
+ ON UPDATE CASCADE ON DELETE CASCADE)";
+
+char mysql_mapcourses_create[] = "\
+CREATE TABLE IF NOT EXISTS MapCourses ( \
+ MapCourseID INTEGER UNSIGNED NOT NULL AUTO_INCREMENT, \
+ MapID INTEGER UNSIGNED NOT NULL, \
+ Course INTEGER UNSIGNED NOT NULL, \
+ Created TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, \
+ CONSTRAINT PK_MapCourses PRIMARY KEY (MapCourseID), \
+ CONSTRAINT UQ_MapCourses_MapIDCourse UNIQUE (MapID, Course), \
+ CONSTRAINT FK_MapCourses_MapID FOREIGN KEY (MapID) REFERENCES Maps(MapID) \
+ ON UPDATE CASCADE ON DELETE CASCADE)";
+
+char sqlite_mapcourses_insert[] = "\
+INSERT OR IGNORE INTO MapCourses (MapID, Course) \
+ VALUES (%d, %d)";
+
+char mysql_mapcourses_insert[] = "\
+INSERT IGNORE INTO MapCourses (MapID, Course) \
+ VALUES (%d, %d)";
+
+
+
+// =====[ TIMES ]=====
+
+char sqlite_times_create[] = "\
+CREATE TABLE IF NOT EXISTS Times ( \
+ TimeID INTEGER NOT NULL, \
+ SteamID32 INTEGER NOT NULL, \
+ MapCourseID INTEGER NOT NULL, \
+ Mode INTEGER NOT NULL, \
+ Style INTEGER NOT NULL, \
+ RunTime INTEGER NOT NULL, \
+ Teleports INTEGER NOT NULL, \
+ Created INTEGER NOT NULL DEFAULT CURRENT_TIMESTAMP, \
+ CONSTRAINT PK_Times PRIMARY KEY (TimeID), \
+ CONSTRAINT FK_Times_SteamID32 FOREIGN KEY (SteamID32) REFERENCES Players(SteamID32) \
+ ON UPDATE CASCADE ON DELETE CASCADE, CONSTRAINT FK_Times_MapCourseID \
+ FOREIGN KEY (MapCourseID) REFERENCES MapCourses(MapCourseID) \
+ ON UPDATE CASCADE ON DELETE CASCADE)";
+
+char mysql_times_create[] = "\
+CREATE TABLE IF NOT EXISTS Times ( \
+ TimeID INTEGER UNSIGNED NOT NULL AUTO_INCREMENT, \
+ SteamID32 INTEGER UNSIGNED NOT NULL, \
+ MapCourseID INTEGER UNSIGNED NOT NULL, \
+ Mode TINYINT UNSIGNED NOT NULL, \
+ Style TINYINT UNSIGNED NOT NULL, \
+ RunTime INTEGER UNSIGNED NOT NULL, \
+ Teleports SMALLINT UNSIGNED NOT NULL, \
+ Created TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, \
+ CONSTRAINT PK_Times PRIMARY KEY (TimeID), \
+ CONSTRAINT FK_Times_SteamID32 FOREIGN KEY (SteamID32) REFERENCES Players(SteamID32) \
+ ON UPDATE CASCADE ON DELETE CASCADE, \
+ CONSTRAINT FK_Times_MapCourseID FOREIGN KEY (MapCourseID) REFERENCES MapCourses(MapCourseID) \
+ ON UPDATE CASCADE ON DELETE CASCADE)";
+
+char sql_times_insert[] = "\
+INSERT INTO Times (SteamID32, MapCourseID, Mode, Style, RunTime, Teleports) \
+ SELECT %d, MapCourseID, %d, %d, %d, %d \
+ FROM MapCourses \
+ WHERE MapID=%d AND Course=%d";
+
+char sql_times_delete[] = "\
+DELETE FROM Times \
+ WHERE TimeID=%d";
+
+
+
+// =====[ JUMPSTATS ]=====
+
+char sqlite_jumpstats_create[] = "\
+CREATE TABLE IF NOT EXISTS Jumpstats ( \
+ JumpID INTEGER NOT NULL, \
+ SteamID32 INTEGER NOT NULL, \
+ JumpType INTEGER NOT NULL, \
+ Mode INTEGER NOT NULL, \
+ Distance INTEGER NOT NULL, \
+ IsBlockJump INTEGER NOT NULL, \
+ Block INTEGER NOT NULL, \
+ Strafes INTEGER NOT NULL, \
+ Sync INTEGER NOT NULL, \
+ Pre INTEGER NOT NULL, \
+ Max INTEGER NOT NULL, \
+ Airtime INTEGER NOT NULL, \
+ Created INTEGER NOT NULL DEFAULT CURRENT_TIMESTAMP, \
+ CONSTRAINT PK_Jumpstats PRIMARY KEY (JumpID), \
+ CONSTRAINT FK_Jumpstats_SteamID32 FOREIGN KEY (SteamID32) REFERENCES Players(SteamID32) \
+ ON UPDATE CASCADE ON DELETE CASCADE)";
+
+char mysql_jumpstats_create[] = "\
+CREATE TABLE IF NOT EXISTS Jumpstats ( \
+ JumpID INTEGER UNSIGNED NOT NULL AUTO_INCREMENT, \
+ SteamID32 INTEGER UNSIGNED NOT NULL, \
+ JumpType TINYINT UNSIGNED NOT NULL, \
+ Mode TINYINT UNSIGNED NOT NULL, \
+ Distance INTEGER UNSIGNED NOT NULL, \
+ IsBlockJump TINYINT UNSIGNED NOT NULL, \
+ Block SMALLINT UNSIGNED NOT NULL, \
+ Strafes INTEGER UNSIGNED NOT NULL, \
+ Sync INTEGER UNSIGNED NOT NULL, \
+ Pre INTEGER UNSIGNED NOT NULL, \
+ Max INTEGER UNSIGNED NOT NULL, \
+ Airtime INTEGER UNSIGNED NOT NULL, \
+ Created TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, \
+ CONSTRAINT PK_Jumpstats PRIMARY KEY (JumpID), \
+ CONSTRAINT FK_Jumpstats_SteamID32 FOREIGN KEY (SteamID32) REFERENCES Players(SteamID32) \
+ ON UPDATE CASCADE ON DELETE CASCADE)";
+
+char sql_jumpstats_insert[] = "\
+INSERT INTO Jumpstats (SteamID32, JumpType, Mode, Distance, IsBlockJump, Block, Strafes, Sync, Pre, Max, Airtime) \
+ VALUES (%d, %d, %d, %d, %d, %d, %d, %d, %d, %d, %d)";
+
+char sql_jumpstats_update[] = "\
+UPDATE Jumpstats \
+ SET \
+ SteamID32=%d, \
+ JumpType=%d, \
+ Mode=%d, \
+ Distance=%d, \
+ IsBlockJump=%d, \
+ Block=%d, \
+ Strafes=%d, \
+ Sync=%d, \
+ Pre=%d, \
+ Max=%d, \
+ Airtime=%d \
+ WHERE \
+ JumpID=%d";
+
+char sql_jumpstats_getrecord[] = "\
+SELECT JumpID, Distance, Block \
+ FROM \
+ Jumpstats \
+ WHERE \
+ SteamID32=%d AND \
+ JumpType=%d AND \
+ Mode=%d AND \
+ IsBlockJump=%d \
+ ORDER BY Block DESC, Distance DESC";
+
+char sql_jumpstats_deleterecord[] = "\
+DELETE \
+ FROM \
+ Jumpstats \
+ WHERE \
+ JumpID = \
+ ( SELECT * FROM ( \
+ SELECT JumpID \
+ FROM \
+ Jumpstats \
+ WHERE \
+ SteamID32=%d AND \
+ JumpType=%d AND \
+ Mode=%d AND \
+ IsBlockJump=%d \
+ ORDER BY Block DESC, Distance DESC \
+ LIMIT 1 \
+ ) AS tmp \
+ )";
+
+char sql_jumpstats_deleteallrecords[] = "\
+DELETE \
+ FROM \
+ Jumpstats \
+ WHERE \
+ SteamID32 = %d;";
+
+char sql_jumpstats_deletejump[] = "\
+DELETE \
+ FROM \
+ Jumpstats \
+ WHERE \
+ JumpID = %d;";
+
+char sql_jumpstats_getpbs[] = "\
+SELECT MAX(Distance), Mode, JumpType \
+ FROM \
+ Jumpstats \
+ WHERE \
+ SteamID32=%d \
+ GROUP BY \
+ Mode, JumpType";
+
+char sql_jumpstats_getblockpbs[] = "\
+SELECT MAX(js.Distance), js.Mode, js.JumpType, js.Block \
+ FROM \
+ Jumpstats js \
+ INNER JOIN \
+ ( \
+ SELECT Mode, JumpType, MAX(BLOCK) Block \
+ FROM \
+ Jumpstats \
+ WHERE \
+ IsBlockJump=1 AND \
+ SteamID32=%d \
+ GROUP BY \
+ Mode, JumpType \
+ ) pb \
+ ON \
+ js.Mode=pb.Mode AND \
+ js.JumpType=pb.JumpType AND \
+ js.Block=pb.Block \
+ WHERE \
+ js.SteamID32=%d \
+ GROUP BY \
+ js.Mode, js.JumpType, js.Block";
+
+
+
+// =====[ VB POSITIONS ]=====
+
+char sqlite_vbpos_create[] = "\
+CREATE TABLE IF NOT EXISTS VBPosition ( \
+ SteamID32 INTEGER NOT NULL, \
+ MapID INTEGER NOT NULL, \
+ X REAL NOT NULL, \
+ Y REAL NOT NULL, \
+ Z REAL NOT NULL, \
+ Course INTEGER NOT NULL, \
+ IsStart INTEGER NOT NULL, \
+ CONSTRAINT PK_VBPosition PRIMARY KEY (SteamID32, MapID, IsStart), \
+ CONSTRAINT FK_VBPosition_SteamID32 FOREIGN KEY (SteamID32) REFERENCES Players(SteamID32), \
+ CONSTRAINT FK_VBPosition_MapID FOREIGN KEY (MapID) REFERENCES Maps(MapID) \
+ ON UPDATE CASCADE ON DELETE CASCADE)";
+
+char mysql_vbpos_create[] = "\
+CREATE TABLE IF NOT EXISTS VBPosition ( \
+ SteamID32 INTEGER UNSIGNED NOT NULL, \
+ MapID INTEGER UNSIGNED NOT NULL, \
+ X REAL NOT NULL, \
+ Y REAL NOT NULL, \
+ Z REAL NOT NULL, \
+ Course INTEGER NOT NULL, \
+ IsStart INTEGER NOT NULL, \
+ CONSTRAINT PK_VBPosition PRIMARY KEY (SteamID32, MapID, IsStart), \
+ CONSTRAINT FK_VBPosition_SteamID32 FOREIGN KEY (SteamID32) REFERENCES Players(SteamID32), \
+ CONSTRAINT FK_VBPosition_MapID FOREIGN KEY (MapID) REFERENCES Maps(MapID) \
+ ON UPDATE CASCADE ON DELETE CASCADE)";
+
+char sql_vbpos_upsert[] = "\
+REPLACE INTO VBPosition (SteamID32, MapID, X, Y, Z, Course, IsStart) \
+ VALUES (%d, %d, %f, %f, %f, %d, %d)";
+
+char sql_vbpos_get[] = "\
+SELECT SteamID32, MapID, Course, IsStart, X, Y, Z \
+ FROM \
+ VBPosition \
+ WHERE \
+ SteamID32 = %d AND \
+ MapID = %d";
+
+
+
+// =====[ START POSITIONS ]=====
+
+char sqlite_startpos_create[] = "\
+CREATE TABLE IF NOT EXISTS StartPosition ( \
+ SteamID32 INTEGER NOT NULL, \
+ MapID INTEGER NOT NULL, \
+ X REAL NOT NULL, \
+ Y REAL NOT NULL, \
+ Z REAL NOT NULL, \
+ Angle0 REAL NOT NULL, \
+ Angle1 REAL NOT NULL, \
+ CONSTRAINT PK_StartPosition PRIMARY KEY (SteamID32, MapID), \
+ CONSTRAINT FK_StartPosition_SteamID32 FOREIGN KEY (SteamID32) REFERENCES Players(SteamID32) \
+ CONSTRAINT FK_StartPosition_MapID FOREIGN KEY (MapID) REFERENCES Maps(MapID) \
+ ON UPDATE CASCADE ON DELETE CASCADE)";
+
+char mysql_startpos_create[] = "\
+CREATE TABLE IF NOT EXISTS StartPosition ( \
+ SteamID32 INTEGER UNSIGNED NOT NULL, \
+ MapID INTEGER UNSIGNED NOT NULL, \
+ X REAL NOT NULL, \
+ Y REAL NOT NULL, \
+ Z REAL NOT NULL, \
+ Angle0 REAL NOT NULL, \
+ Angle1 REAL NOT NULL, \
+ CONSTRAINT PK_StartPosition PRIMARY KEY (SteamID32, MapID), \
+ CONSTRAINT FK_StartPosition_SteamID32 FOREIGN KEY (SteamID32) REFERENCES Players(SteamID32), \
+ CONSTRAINT FK_StartPosition_MapID FOREIGN KEY (MapID) REFERENCES Maps(MapID) \
+ ON UPDATE CASCADE ON DELETE CASCADE)";
+
+char sql_startpos_upsert[] = "\
+REPLACE INTO StartPosition (SteamID32, MapID, X, Y, Z, Angle0, Angle1) \
+ VALUES (%d, %d, %f, %f, %f, %f, %f)";
+
+char sql_startpos_get[] = "\
+SELECT SteamID32, MapID, X, Y, Z, Angle0, Angle1 \
+ FROM \
+ StartPosition \
+ WHERE \
+ SteamID32 = %d AND \
+ MapID = %d";
diff --git a/sourcemod/scripting/gokz-localdb/db/timer_setup.sp b/sourcemod/scripting/gokz-localdb/db/timer_setup.sp
new file mode 100644
index 0000000..b123eeb
--- /dev/null
+++ b/sourcemod/scripting/gokz-localdb/db/timer_setup.sp
@@ -0,0 +1,167 @@
+
+// ===== [ SAVE TIMER SETUP ] =====
+
+void DB_SaveTimerSetup(int client)
+{
+ bool txnHasQuery = false;
+ int course;
+ float position[3], angles[3];
+
+ if (!IsValidClient(client))
+ {
+ return;
+ }
+
+ int steamid = GetSteamAccountID(client);
+ DataPack data = new DataPack();
+
+ data.WriteCell(client);
+ data.WriteCell(steamid);
+
+ char query[1024];
+ Transaction txn = SQL_CreateTransaction();
+
+ if (GOKZ_GetStartPosition(client, position, angles) == StartPositionType_Custom)
+ {
+ FormatEx(query, sizeof(query), sql_startpos_upsert, steamid, gI_DBCurrentMapID, position[0], position[1], position[2], angles[0], angles[1]);
+ txn.AddQuery(query);
+ txnHasQuery = true;
+ }
+
+ course = GOKZ_GetVirtualButtonPosition(client, position, true);
+ if (course != -1)
+ {
+ FormatEx(query, sizeof(query), sql_vbpos_upsert, steamid, gI_DBCurrentMapID, position[0], position[1], position[2], course, 1);
+ txn.AddQuery(query);
+ txnHasQuery = true;
+ }
+
+ course = GOKZ_GetVirtualButtonPosition(client, position, false);
+ if (course != -1)
+ {
+ FormatEx(query, sizeof(query), sql_vbpos_upsert, steamid, gI_DBCurrentMapID, position[0], position[1], position[2], course, 0);
+ txn.AddQuery(query);
+ txnHasQuery = true;
+ }
+
+ if (txnHasQuery)
+ {
+ SQL_ExecuteTransaction(gH_DB, txn, DB_TxnSuccess_SaveTimerSetup, DB_TxnFailure_Generic_DataPack, data, DBPrio_Low);
+ }
+ else
+ {
+ delete data;
+ delete txn;
+ }
+}
+
+public void DB_TxnSuccess_SaveTimerSetup(Handle db, DataPack data, int numQueries, Handle[] results, any[] queryData)
+{
+ data.Reset();
+ int client = data.ReadCell();
+ int steamid = data.ReadCell();
+ delete data;
+
+ if (!IsValidClient(client) || steamid != GetSteamAccountID(client))
+ {
+ return;
+ }
+
+ GOKZ_PrintToChat(client, true, "%t", "Timer Setup Saved");
+}
+
+
+
+// ===== [ LOAD TIMER SETUP ] =====
+
+void DB_LoadTimerSetup(int client, bool doChatMessage = false)
+{
+ if (!IsValidClient(client))
+ {
+ return;
+ }
+
+ int steamid = GetSteamAccountID(client);
+
+ DataPack data = new DataPack();
+ data.WriteCell(client);
+ data.WriteCell(steamid);
+ data.WriteCell(doChatMessage);
+
+ char query[1024];
+ Transaction txn = SQL_CreateTransaction();
+
+ // Virtual Buttons
+ FormatEx(query, sizeof(query), sql_vbpos_get, steamid, gI_DBCurrentMapID);
+ txn.AddQuery(query);
+
+ // Start Position
+ FormatEx(query, sizeof(query), sql_startpos_get, steamid, gI_DBCurrentMapID);
+ txn.AddQuery(query);
+
+ SQL_ExecuteTransaction(gH_DB, txn, DB_TxnSuccess_LoadTimerSetup, DB_TxnFailure_Generic_DataPack, data, DBPrio_Normal);
+}
+
+public void DB_TxnSuccess_LoadTimerSetup(Handle db, DataPack data, int numQueries, DBResultSet[] results, any[] queryData)
+{
+ data.Reset();
+ int client = data.ReadCell();
+ int steamid = data.ReadCell();
+ bool doChatMessage = data.ReadCell();
+ delete data;
+
+ if (!IsValidClient(client) || steamid != GetSteamAccountID(client))
+ {
+ return;
+ }
+
+ int course;
+ bool isStart, vbSetup = false;
+ float position[3], angles[3];
+
+ if (results[0].RowCount > 0 && results[0].FetchRow())
+ {
+ position[0] = results[0].FetchFloat(TimerSetupDB_GetVBPos_PositionX);
+ position[1] = results[0].FetchFloat(TimerSetupDB_GetVBPos_PositionY);
+ position[2] = results[0].FetchFloat(TimerSetupDB_GetVBPos_PositionZ);
+ course = results[0].FetchInt(TimerSetupDB_GetVBPos_Course);
+ isStart = results[0].FetchInt(TimerSetupDB_GetVBPos_IsStart) == 1;
+
+ GOKZ_SetVirtualButtonPosition(client, position, course, isStart);
+ vbSetup = true;
+ }
+
+ if (results[0].RowCount > 1 && results[0].FetchRow())
+ {
+ position[0] = results[0].FetchFloat(TimerSetupDB_GetVBPos_PositionX);
+ position[1] = results[0].FetchFloat(TimerSetupDB_GetVBPos_PositionY);
+ position[2] = results[0].FetchFloat(TimerSetupDB_GetVBPos_PositionZ);
+ course = results[0].FetchInt(TimerSetupDB_GetVBPos_Course);
+ isStart = results[0].FetchInt(TimerSetupDB_GetVBPos_IsStart) == 1;
+
+ GOKZ_SetVirtualButtonPosition(client, position, course, isStart);
+ vbSetup = true;
+ }
+
+ if (results[1].RowCount > 0 && results[1].FetchRow())
+ {
+ position[0] = results[1].FetchFloat(TimerSetupDB_GetStartPos_PositionX);
+ position[1] = results[1].FetchFloat(TimerSetupDB_GetStartPos_PositionY);
+ position[2] = results[1].FetchFloat(TimerSetupDB_GetStartPos_PositionZ);
+ angles[0] = results[1].FetchFloat(TimerSetupDB_GetStartPos_Angle0);
+ angles[1] = results[1].FetchFloat(TimerSetupDB_GetStartPos_Angle1);
+ angles[2] = 0.0;
+
+ GOKZ_SetStartPosition(client, StartPositionType_Custom, position, angles);
+ }
+
+ if (vbSetup)
+ {
+ GOKZ_LockVirtualButtons(client);
+ }
+
+ if (doChatMessage)
+ {
+ GOKZ_PrintToChat(client, true, "%t", "Timer Setup Loaded");
+ }
+}