diff --git a/src/engine/server/databases/connection.h b/src/engine/server/databases/connection.h index a5ae66d64..a872c7d27 100644 --- a/src/engine/server/databases/connection.h +++ b/src/engine/server/databases/connection.h @@ -21,7 +21,7 @@ public: virtual IDbConnection *Copy() = 0; // returns the database prefix - const char *GetPrefix() { return m_aPrefix; } + const char *GetPrefix() const { return m_aPrefix; } virtual const char *BinaryCollate() const = 0; // can be inserted into queries to convert a timestamp variable to the unix timestamp virtual void ToUnixTimestamp(const char *pTimestamp, char *aBuf, unsigned int BufferSize) = 0; @@ -35,6 +35,8 @@ public: virtual const char *InsertIgnore() const = 0; // ORDER BY RANDOM()/RAND() virtual const char *Random() const = 0; + // Get Median Map Time from l.Map + virtual const char *MedianMapTime(char *pBuffer, int BufferSize) const = 0; enum Status { diff --git a/src/engine/server/databases/mysql.cpp b/src/engine/server/databases/mysql.cpp index 946cd13d8..c0c120805 100644 --- a/src/engine/server/databases/mysql.cpp +++ b/src/engine/server/databases/mysql.cpp @@ -309,6 +309,18 @@ int CMysqlConnection::GetBlob(int Col, unsigned char *pBuffer, int BufferSize) c #endif } +const char *CMysqlConnection::MedianMapTime(char *pBuffer, int BufferSize) const +{ + str_format(pBuffer, BufferSize, + "SELECT MEDIAN(Time) " + "OVER (PARTITION BY Map) " + "FROM %s_race " + "GROUP BY Map " + "HAVING Map = l.Map", + GetPrefix()); + return pBuffer; +} + void CMysqlConnection::AddPoints(const char *pPlayer, int Points) { char aBuf[512]; diff --git a/src/engine/server/databases/mysql.h b/src/engine/server/databases/mysql.h index cf6e7a740..2a4af9927 100644 --- a/src/engine/server/databases/mysql.h +++ b/src/engine/server/databases/mysql.h @@ -35,6 +35,7 @@ public: virtual const char *CollateNocase() const { return "CONVERT(? USING utf8mb4) COLLATE utf8mb4_general_ci"; } virtual const char *InsertIgnore() const { return "INSERT IGNORE"; }; virtual const char *Random() const { return "RAND()"; }; + virtual const char *MedianMapTime(char *pBuffer, int BufferSize) const; virtual Status Connect(); virtual void Disconnect(); diff --git a/src/engine/server/databases/sqlite.cpp b/src/engine/server/databases/sqlite.cpp index a4f2d9df9..e87548097 100644 --- a/src/engine/server/databases/sqlite.cpp +++ b/src/engine/server/databases/sqlite.cpp @@ -214,6 +214,23 @@ int CSqliteConnection::GetBlob(int Col, unsigned char *pBuffer, int BufferSize) return Size; } +const char *CSqliteConnection::MedianMapTime(char *pBuffer, int BufferSize) const +{ + str_format(pBuffer, BufferSize, + "SELECT AVG(" + " CASE counter %% 2 " + " WHEN 0 THEN CASE WHEN rn IN (counter / 2, counter / 2 + 1) THEN Time END " + " WHEN 1 THEN CASE WHEN rn = counter / 2 + 1 THEN Time END END) " + " OVER (PARTITION BY Map) AS Median " + "FROM (" + " SELECT *, ROW_NUMBER() " + " OVER (PARTITION BY Map ORDER BY Time) rn, COUNT(*) " + " OVER (PARTITION BY Map) counter " + " FROM %s_race where Map = l.Map) as r", + GetPrefix()); + return pBuffer; +} + bool CSqliteConnection::Execute(const char *pQuery) { char *pErrorMsg; diff --git a/src/engine/server/databases/sqlite.h b/src/engine/server/databases/sqlite.h index b1b68f4c6..00b2cfee1 100644 --- a/src/engine/server/databases/sqlite.h +++ b/src/engine/server/databases/sqlite.h @@ -22,6 +22,7 @@ public: virtual const char *CollateNocase() const { return "? COLLATE NOCASE"; } virtual const char *InsertIgnore() const { return "INSERT OR IGNORE"; }; virtual const char *Random() const { return "RANDOM()"; }; + virtual const char *MedianMapTime(char *pBuffer, int BufferSize) const; virtual Status Connect(); virtual void Disconnect(); diff --git a/src/game/server/score.cpp b/src/game/server/score.cpp index 4640d78b1..6a721d2b7 100644 --- a/src/game/server/score.cpp +++ b/src/game/server/score.cpp @@ -355,12 +355,13 @@ bool CScore::MapInfoThread(IDbConnection *pSqlServer, const ISqlData *pGameData) char aTimestamp[512]; pSqlServer->ToUnixTimestamp("l.Timestamp", aTimestamp, sizeof(aTimestamp)); - char aBuf[1024]; + char aMedianMapTime[2048]; + char aBuf[4096]; str_format(aBuf, sizeof(aBuf), "SELECT l.Map, l.Server, Mapper, Points, Stars, " " (SELECT COUNT(Name) FROM %s_race WHERE Map = l.Map) AS Finishes, " " (SELECT COUNT(DISTINCT Name) FROM %s_race WHERE Map = l.Map) AS Finishers, " - " (SELECT ROUND(AVG(Time)) FROM %s_race WHERE Map = l.Map) AS Average, " + " (%s) AS Median, " " %s AS Stamp, " " %s-%s AS Ago, " " (SELECT MIN(Time) FROM %s_race WHERE Map = l.Map AND Name = ?) AS OwnTime " @@ -374,7 +375,8 @@ bool CScore::MapInfoThread(IDbConnection *pSqlServer, const ISqlData *pGameData) " Map " " LIMIT 1" ") as l;", - pSqlServer->GetPrefix(), pSqlServer->GetPrefix(), pSqlServer->GetPrefix(), + pSqlServer->GetPrefix(), pSqlServer->GetPrefix(), + pSqlServer->MedianMapTime(aMedianMapTime, sizeof(aMedianMapTime)), aTimestamp, aCurrentTimestamp, aTimestamp, pSqlServer->GetPrefix(), pSqlServer->GetPrefix(), pSqlServer->CollateNocase()); @@ -396,7 +398,7 @@ bool CScore::MapInfoThread(IDbConnection *pSqlServer, const ISqlData *pGameData) int Stars = pSqlServer->GetInt(5); int Finishes = pSqlServer->GetInt(6); int Finishers = pSqlServer->GetInt(7); - int Average = pSqlServer->GetInt(8); + float Median = pSqlServer->GetInt(8); int Stamp = pSqlServer->GetInt(9); int Ago = pSqlServer->GetInt(10); float OwnTime = pSqlServer->GetFloat(11); @@ -409,11 +411,11 @@ bool CScore::MapInfoThread(IDbConnection *pSqlServer, const ISqlData *pGameData) str_format(aReleasedString, sizeof(aReleasedString), ", released %s ago", aAgoString); } - char aAverageString[60] = "\0"; - if(Average > 0) + char aMedianString[60] = "\0"; + if(Median > 0) { - str_time((int64)Average * 100, TIME_HOURS, aBuf, sizeof(aBuf)); - str_format(aAverageString, sizeof(aAverageString), " in %s average", aBuf); + str_time((int64)Median * 100, TIME_HOURS, aBuf, sizeof(aBuf)); + str_format(aMedianString, sizeof(aMedianString), " in %s median", aBuf); } char aStars[20]; @@ -443,7 +445,7 @@ bool CScore::MapInfoThread(IDbConnection *pSqlServer, const ISqlData *pGameData) aReleasedString, Finishes, Finishes == 1 ? "finish" : "finishes", Finishers, Finishers == 1 ? "tee" : "tees", - aAverageString, aOwnFinishesString); + aMedianString, aOwnFinishesString); } else {