Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Normalize and store PB stage times #1181

Open
wants to merge 9 commits into
base: master
Choose a base branch
from
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ enum
Migration_DeprecateExactTimeInt,
Migration_AddPlayertimesAuthFK,
Migration_FixSQLiteMapzonesROWID,
Migration_NormalizeStageTimes,
MIGRATIONS_END
};

Expand Down Expand Up @@ -87,6 +88,7 @@ char gS_MigrationNames[][] = {
"DeprecateExactTimeInt",
"AddPlayertimesAuthFK",
"FixSQLiteMapzonesROWID",
"NormalizeStageTimes",
};

static Database gH_SQL;
Expand Down Expand Up @@ -126,6 +128,14 @@ public void SQL_CreateTables(Database hSQL, const char[] prefix, int driver)
sOptionalINNODB = "ENGINE=INNODB";
}

// Enable foreign key constraints for SQLite (e.g. CASCADE DELETE)
// No-op within a transaction, so has to be its own query
if (driver == Driver_sqlite)
{
FormatEx(sQuery, sizeof(sQuery), "PRAGMA foreign_keys = ON");
QueryLog(gH_SQL, SQL_PragmaFKSqlite_Callback, sQuery, 0, DBPrio_High);
}

//
//// shavit-core
//
Expand Down Expand Up @@ -209,7 +219,7 @@ public void SQL_CreateTables(Database hSQL, const char[] prefix, int driver)
AddQueryLog(trans, sQuery);

FormatEx(sQuery, sizeof(sQuery),
"CREATE TABLE IF NOT EXISTS `%sstagetimeswr` (`style` TINYINT NOT NULL, `track` TINYINT NOT NULL DEFAULT 0, `map` VARCHAR(255) NOT NULL, `stage` TINYINT NOT NULL, `auth` INT NOT NULL, `time` FLOAT NOT NULL, PRIMARY KEY (`style`, `track`, `map`, `stage`)) %s;",
"CREATE TABLE IF NOT EXISTS `%sstagetimes` (`playertimes_id` INT NOT NULL, `stage` TINYINT NOT NULL, `time` FLOAT NOT NULL, PRIMARY KEY (`playertimes_id`, `stage`), FOREIGN KEY(`playertimes_id`) REFERENCES playertimes (`id`) ON UPDATE CASCADE ON DELETE CASCADE) %s;",
gS_SQLPrefix, sOptionalINNODB);
AddQueryLog(trans, sQuery);

Expand Down Expand Up @@ -266,6 +276,14 @@ public void SQL_CreateTables(Database hSQL, const char[] prefix, int driver)
hSQL.Execute(trans, Trans_CreateTables_Success, Trans_CreateTables_Error, 0, DBPrio_High);
}

public void SQL_PragmaFKSqlite_Callback(Database db, DBResultSet results, const char[] error, any data)
{
if (results == null)
{
SetFailState("Timer failed to enable foreign key constraints (SQLite). Reason: %s", error);
}
}

public void Trans_CreateTables_Error(Database db, any data, int numQueries, const char[] error, int failIndex, any[] queryData)
{
static char tablenames[][32] = {
Expand Down Expand Up @@ -369,6 +387,7 @@ void ApplyMigration(int migration)
case Migration_DeprecateExactTimeInt: ApplyMigration_DeprecateExactTimeInt();
case Migration_AddPlayertimesAuthFK: ApplyMigration_AddPlayertimesAuthFK();
case Migration_FixSQLiteMapzonesROWID: ApplyMigration_FixSQLiteMapzonesROWID();
case Migration_NormalizeStageTimes: ApplyMigration_NormalizeStageTimes();
}
}

Expand Down Expand Up @@ -508,6 +527,76 @@ void ApplyMigration_NormalizeMapzonePoints() // TODO: test with sqlite lol
QueryLog(gH_SQL, SQL_TableMigrationSingleQuery_Callback, sQuery, Migration_NormalizeMapzonePoints, DBPrio_High);
}

void ApplyMigration_NormalizeStageTimes()
{
char sQuery[192];
// Check if stagetimeswr exists before migration (it does not on fresh installs)
if (gI_Driver == Driver_mysql)
{
FormatEx(sQuery, sizeof(sQuery), "SHOW TABLES LIKE '%sstagetimeswr';", gS_SQLPrefix);
}
else if (gI_Driver == Driver_sqlite)
{
FormatEx(sQuery, sizeof(sQuery), "SELECT 1 FROM sqlite_master WHERE type='table' AND name='%sstagetimeswr';", gS_SQLPrefix);
}
else // PostgreSQL unaffected
{
InsertMigration(Migration_NormalizeStageTimes);
return;
}
rtldg marked this conversation as resolved.
Show resolved Hide resolved

QueryLog(gH_SQL, SQL_NormalizeStageTimes_Callback, sQuery, 0, DBPrio_High);
}

public void SQL_NormalizeStageTimes_Callback(Database db, DBResultSet results, const char[] error, any data)
{
if (results == null)
{
LogError("Timer error! Existence detection of %sstagetimeswr failed. Reason: %s", gS_SQLPrefix, error);
return;
}

if (results.FetchRow()) //stagetimeswr exists
{
Transaction trans = new Transaction();
char sQuery[512];

FormatEx(sQuery, sizeof(sQuery),
"INSERT INTO `%sstagetimes` (playertimes_id, stage, time) \
SELECT \
PT.id AS `playertimes_id`, \
STWR.stage, \
STWR.time \
FROM `%splayertimes` AS `PT` \
INNER JOIN `%sstagetimeswr` AS `STWR` ON PT.auth = STWR.auth \
AND PT.map = STWR.map \
AND PT.track = STWR.track \
AND PT.style = STWR.style;",
gS_SQLPrefix, gS_SQLPrefix, gS_SQLPrefix
);
AddQueryLog(trans, sQuery);

FormatEx(sQuery, sizeof(sQuery), "DROP TABLE `%sstagetimeswr`;", gS_SQLPrefix);
AddQueryLog(trans, sQuery);

gH_SQL.Execute(trans, Trans_NormalizeStageTimes_Success, Trans_NormalizeStageTimes_Error, 0, DBPrio_High);
}
else
{
InsertMigration(Migration_NormalizeStageTimes);
}
}

public void Trans_NormalizeStageTimes_Success(Database db, any data, int numQueries, DBResultSet[] results, any[] queryData)
{
InsertMigration(Migration_NormalizeStageTimes);
}

public void Trans_NormalizeStageTimes_Error(Database db, any data, int numQueries, const char[] error, int failIndex, any[] queryData)
{
LogError("Timer error! NormalizeStageTimes migration transaction failed. Reason: %s", error);
}

void ApplyMigration_AddMapzonesForm()
{
char sQuery[192];
Expand Down
128 changes: 76 additions & 52 deletions addons/sourcemod/scripting/shavit-wr.sp
Original file line number Diff line number Diff line change
Expand Up @@ -575,9 +575,10 @@ void UpdateWRCache(int client = -1)
char sQuery[512];

FormatEx(sQuery, sizeof(sQuery),
"SELECT style, track, auth, stage, time FROM `%sstagetimeswr` WHERE map = '%s';",
gS_MySQLPrefix, gS_Map);

"SELECT WR.style, WR.track, ST.stage, %s \
FROM `%sstagetimes` AS `ST` INNER JOIN `%swrs` AS `WR` ON WR.id = ST.playertimes_id \
WHERE WR.map = '%s';", gI_Driver == Driver_mysql ? "REPLACE(FORMAT(ST.time, 9), ',', '')" : "printf(\"%.9f\", ST.time)",
gS_MySQLPrefix, gS_MySQLPrefix, gS_Map);
QueryLog(gH_SQL, SQL_UpdateWRStageTimes_Callback, sQuery);
}

Expand All @@ -604,9 +605,9 @@ public void SQL_UpdateWRStageTimes_Callback(Database db, DBResultSet results, co
{
int style = results.FetchInt(0);
int track = results.FetchInt(1);
int stage = results.FetchInt(3);
int stage = results.FetchInt(2);

gA_StageWR[style][track][stage] = results.FetchFloat(4);
gA_StageWR[style][track][stage] = results.FetchFloat(3);
}
}

Expand Down Expand Up @@ -2562,10 +2563,11 @@ public void Shavit_OnFinish(int client, int style, float time, int jumps, int st
}

bool bEveryone = (iOverwrite > 0);
bool bIsWR = (iOverwrite > 0 && (time < gF_WRTime[style][track] || gF_WRTime[style][track] == 0.0));
char sMessage[255];
char sMessage2[255];

if(iOverwrite > 0 && (time < gF_WRTime[style][track] || gF_WRTime[style][track] == 0.0)) // WR?
if(bIsWR)
{
float fOldWR = gF_WRTime[style][track];
gF_WRTime[style][track] = time;
Expand Down Expand Up @@ -2599,36 +2601,6 @@ public void Shavit_OnFinish(int client, int style, float time, int jumps, int st
#if defined DEBUG
Shavit_PrintToChat(client, "old: %.01f new: %.01f", fOldWR, time);
#endif

Transaction trans = new Transaction();
char query[512];

FormatEx(query, sizeof(query),
"DELETE FROM `%sstagetimeswr` WHERE style = %d AND track = %d AND map = '%s';",
gS_MySQLPrefix, style, track, gS_Map
);

AddQueryLog(trans, query);

for (int i = 0; i < MAX_STAGES; i++)
{
float fTime = gA_StageTimes[client][i];
gA_StageWR[style][track][i] = fTime;

if (fTime == 0.0)
{
continue;
}

FormatEx(query, sizeof(query),
"INSERT INTO `%sstagetimeswr` (`style`, `track`, `map`, `auth`, `time`, `stage`) VALUES (%d, %d, '%s', %d, %f, %d);",
gS_MySQLPrefix, style, track, gS_Map, iSteamID, fTime, i
);

AddQueryLog(trans, query);
}

gH_SQL.Execute(trans, Trans_ReplaceStageTimes_Success, Trans_ReplaceStageTimes_Error, 0, DBPrio_High);
}

int iRank = GetRankForTime(style, time, track);
Expand Down Expand Up @@ -2668,8 +2640,36 @@ public void Shavit_OnFinish(int client, int style, float time, int jumps, int st
{
float fPoints = gB_Rankings ? Shavit_GuessPointsForTime(track, style, -1, time, gF_WRTime[style][track]) : 0.0;

Transaction trans = new Transaction();
char sQuery[1024];

FormatEx(sQuery, sizeof(sQuery), "CREATE TEMPORARY TABLE IF NOT EXISTS `Insert_Stages` (`playertimes_id` INT, `stage` TINYINT NOT NULL, `time` FLOAT NOT NULL);");
AddQueryLog(trans, sQuery);

FormatEx(sQuery, sizeof(sQuery), "DELETE FROM `Insert_Stages`;");
AddQueryLog(trans, sQuery);

for (int i = 0; i < MAX_STAGES; i++)
{
float fTime = gA_StageTimes[client][i];

if(bIsWR)
{
gA_StageWR[style][track][i] = fTime;
}

if (fTime == 0.0)
{
continue;
}

FormatEx(sQuery, sizeof(sQuery),
"INSERT INTO `Insert_Stages` (`stage`, `time`) VALUES (%d, %.9f);",
i, fTime
);
AddQueryLog(trans, sQuery);
}

if(iOverwrite == 1) // insert
{
FormatEx(sMessage, 255, "%s[%s]%s %T",
Expand All @@ -2678,6 +2678,18 @@ public void Shavit_OnFinish(int client, int style, float time, int jumps, int st
FormatEx(sQuery, sizeof(sQuery),
"INSERT INTO %splayertimes (auth, map, time, jumps, date, style, strafes, sync, points, track, perfs) VALUES (%d, '%s', %.9f, %d, %d, %d, %d, %.2f, %f, %d, %.2f);",
gS_MySQLPrefix, iSteamID, gS_Map, time, jumps, timestamp, style, strafes, sync, fPoints, track, perfs);
AddQueryLog(trans, sQuery);

// Will affect 0 rows on linear/unstaged maps
// TODO: Needs PostgreSQL support
FormatEx(sQuery, sizeof(sQuery), "UPDATE `Insert_Stages` SET `playertimes_id` = %s;", gI_Driver == Driver_mysql ? "LAST_INSERT_ID()" : "last_insert_rowid()");
AddQueryLog(trans, sQuery);

FormatEx(sQuery, sizeof(sQuery),
"INSERT INTO `%sstagetimes` (playertimes_id, stage, time) SELECT playertimes_id, stage, time FROM `Insert_Stages`;",
gS_MySQLPrefix
);
AddQueryLog(trans, sQuery);
}
else // update
{
Expand All @@ -2687,9 +2699,26 @@ public void Shavit_OnFinish(int client, int style, float time, int jumps, int st
FormatEx(sQuery, sizeof(sQuery),
"UPDATE %splayertimes SET time = %.9f, jumps = %d, date = %d, strafes = %d, sync = %.02f, points = %f, perfs = %.2f, completions = completions + 1 WHERE map = '%s' AND auth = %d AND style = %d AND track = %d;",
gS_MySQLPrefix, time, jumps, timestamp, strafes, sync, fPoints, perfs, gS_Map, iSteamID, style, track);
AddQueryLog(trans, sQuery);

// Will affect 0 rows on linear/unstaged maps
FormatEx(sQuery, sizeof(sQuery), "UPDATE `Insert_Stages` SET `playertimes_id` = (SELECT id FROM `%splayertimes` WHERE map = '%s' AND auth = %d AND style = %d AND track = %d);",
gS_MySQLPrefix, gS_Map, iSteamID, style, track);
AddQueryLog(trans, sQuery);

// Delete all stage times first (safer than an UPDATE in unexpected cases where the new completion has fewer/no stage times, e.g. bad zoning)
FormatEx(sQuery, sizeof(sQuery), "DELETE FROM `%sstagetimes` WHERE playertimes_id IN (SELECT id FROM `%splayertimes` WHERE map = '%s' AND auth = %d AND style = %d AND track = %d);",
gS_MySQLPrefix, gS_MySQLPrefix, gS_Map, iSteamID, style, track);
AddQueryLog(trans, sQuery);

FormatEx(sQuery, sizeof(sQuery),
"INSERT INTO `%sstagetimes` (playertimes_id, stage, time) SELECT playertimes_id, stage, time FROM `Insert_Stages`;",
gS_MySQLPrefix
);
AddQueryLog(trans, sQuery);
}

QueryLog(gH_SQL, SQL_OnFinish_Callback, sQuery, GetClientSerial(client), DBPrio_High);
gH_SQL.Execute(trans, Trans_OnFinishTimes_Success, Trans_OnFinishTimes_Error, GetClientSerial(client), DBPrio_High);

Call_StartForward(gH_OnFinish_Post);
Call_PushCell(client);
Expand Down Expand Up @@ -2805,15 +2834,8 @@ public void SQL_OnIncrementCompletions_Callback(Database db, DBResultSet results
}
}

public void SQL_OnFinish_Callback(Database db, DBResultSet results, const char[] error, any data)
public void Trans_OnFinishTimes_Success(Database db, any data, int numQueries, DBResultSet[] results, any[] queryData)
{
if(results == null)
{
LogError("Timer (WR OnFinish) SQL query failed. Reason: %s", error);

return;
}

int client = GetClientFromSerial(data);

if(client == 0)
Expand All @@ -2824,14 +2846,9 @@ public void SQL_OnFinish_Callback(Database db, DBResultSet results, const char[]
UpdateWRCache(client);
}

public void Trans_ReplaceStageTimes_Success(Database db, any data, int numQueries, DBResultSet[] results, any[] queryData)
public void Trans_OnFinishTimes_Error(Database db, any data, int numQueries, const char[] error, int failIndex, any[] queryData)
{
return;
}

public void Trans_ReplaceStageTimes_Error(Database db, any data, int numQueries, const char[] error, int failIndex, any[] queryData)
{
LogError("Timer (ReplaceStageTimes) SQL query failed %d/%d. Reason: %s", failIndex, numQueries, error);
LogError("Timer (WR OnFinish) SQL query failed %d/%d. Reason: %s", failIndex, numQueries, error);
}

void UpdateLeaderboards()
Expand Down Expand Up @@ -2909,6 +2926,13 @@ public Action Shavit_OnStageMessage(int client, int stageNumber, char[] message,

gA_StageTimes[client][stageNumber] = stageTime;

// Don't show ANY stage message if 0.0
// (e.g. if stage zone intersects with start zone)
if (stageTime == 0.0)
{
return Plugin_Handled;
}

if (stageTimeWR == 0.0)
{
return Plugin_Continue;
Expand Down
21 changes: 18 additions & 3 deletions addons/sourcemod/scripting/shavit-zones.sp
Original file line number Diff line number Diff line change
Expand Up @@ -4942,13 +4942,17 @@ public Action Shavit_OnStart(int client, int track)

public void Shavit_OnRestart(int client, int track)
{
gI_LastStage[client] = 0;

if (!IsPlayerAlive(client))
{
return;
}

// For stage zones that intersect with start zone
if (!Shavit_InsideZone(client, Zone_Start, -1))
{
gI_LastStage[client] = 0;
}

int iIndex = GetZoneIndex(Zone_Start, track);

if(gCV_TeleportToStart.BoolValue)
Expand Down Expand Up @@ -5245,6 +5249,17 @@ public void StartTouchPost(int entity, int other)
}
}

case Zone_Start:
{
// Same logic as TouchPost Zone_Start:
// - reset last stage instantly for main start zone
// - only reset for bonus start zone if client's current track is a bonus
if (Shavit_GetClientTrack(other) != Track_Main || track == Track_Main)
{
gI_LastStage[other] = 0;
}
}

case Zone_End:
{
if (status == Timer_Running && Shavit_GetClientTrack(other) == track)
Expand All @@ -5266,7 +5281,7 @@ public void StartTouchPost(int entity, int other)
FormatSeconds(Shavit_GetClientTime(other), sTime, 32, true);

char sMessage[255];
FormatEx(sMessage, 255, "%T", "ZoneStageEnter", other, gS_ChatStrings.sText, gS_ChatStrings.sVariable2, num, gS_ChatStrings.sText, gS_ChatStrings.sVariable2, sTime, gS_ChatStrings.sText);
FormatEx(sMessage, 255, "%T", "ZoneStageEnter", other, gS_ChatStrings.sText, gS_ChatStrings.sVariable, num, gS_ChatStrings.sText, gS_ChatStrings.sVariable, sTime, gS_ChatStrings.sText);

Action aResult = Plugin_Continue;
Call_StartForward(gH_Forwards_StageMessage);
Expand Down
Loading