2247: Thread safe SQL interaction r=def- a=Zwelf

This PR intends to make the database interaction thread safe and optimizes some SQL queries. This is still a WIP, but since it is a rather big PR I wanted to get feedback early on.

Benefits:

* remove race conditions leading to undefined behavior and potential crashes
* logging game related database results in teehistorian would be possible

Behavior change:

* /top5team prints ranks in reverse order when passing a negative number (like /top5, /top5points)
* Optimize SQL statements for /rank /teamrank /top5 /top5team /points /top5points
* /load without parameters doesn't pass the SQL error to the user (as most other functions)
* Simplify IScore interface
* Add UUID to /save table (update of database schema necessary):
  ```
  ALTER TABLE record_saves ADD SaveID varchar(64);
  ```
* /save immediately kills team and loads it again if the database insert fails.

still TBD:
* [x] saving (team) score when finishing
* [x] loading team save
* [x] loading initial time and birthday check
* [x] /map and random map votes
* [x] RFC: generate a passphrase (2-3 word) if save-code exists or no save-code is given making /save failures much more rare and save-codes more secure
* [x] clean up code (removing now unused structs, ordering of functions in IScore)

Co-authored-by: Zwelf <zwelf@strct.cc>
This commit is contained in:
bors[bot] 2020-06-17 19:35:45 +00:00 committed by GitHub
commit 98b448ebcc
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
24 changed files with 3109 additions and 1382 deletions

1296
data/wordlist.txt Normal file

File diff suppressed because it is too large Load diff

View file

@ -2828,12 +2828,18 @@ void CServer::ConAddSqlServer(IConsole::IResult *pResult, void *pUserData)
{ {
apSqlServers[i] = new CSqlServer(pResult->GetString(1), pResult->GetString(2), pResult->GetString(3), pResult->GetString(4), pResult->GetString(5), pResult->GetInteger(6), &pSelf->m_GlobalSqlLock, ReadOnly, SetUpDb); apSqlServers[i] = new CSqlServer(pResult->GetString(1), pResult->GetString(2), pResult->GetString(3), pResult->GetString(4), pResult->GetString(5), pResult->GetInteger(6), &pSelf->m_GlobalSqlLock, ReadOnly, SetUpDb);
if(SetUpDb)
thread_init(CreateTablesThread, apSqlServers[i], "CreateTables");
char aBuf[512]; char aBuf[512];
str_format(aBuf, sizeof(aBuf), "Added new Sql%sServer: %d: DB: '%s' Prefix: '%s' User: '%s' IP: <{'%s'}> Port: %d", ReadOnly ? "Read" : "Write", i, apSqlServers[i]->GetDatabase(), apSqlServers[i]->GetPrefix(), apSqlServers[i]->GetUser(), apSqlServers[i]->GetIP(), apSqlServers[i]->GetPort()); str_format(aBuf, sizeof(aBuf),
"Added new Sql%sServer: %d: DB: '%s' Prefix: '%s' User: '%s' IP: <{'%s'}> Port: %d",
ReadOnly ? "Read" : "Write", i, apSqlServers[i]->GetDatabase(),
apSqlServers[i]->GetPrefix(), apSqlServers[i]->GetUser(),
apSqlServers[i]->GetIP(), apSqlServers[i]->GetPort());
pSelf->Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "server", aBuf); pSelf->Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "server", aBuf);
if(SetUpDb)
{
if(!apSqlServers[i]->CreateTables())
pSelf->SetErrorShutdown("database create tables failed");
}
return; return;
} }
} }
@ -2866,11 +2872,6 @@ void CServer::ConDumpSqlServers(IConsole::IResult *pResult, void *pUserData)
} }
} }
void CServer::CreateTablesThread(void *pData)
{
((CSqlServer *)pData)->CreateTables();
}
#endif #endif
void CServer::ConchainSpecialInfoupdate(IConsole::IResult *pResult, void *pUserData, IConsole::FCommandCallback pfnCallback, void *pCallbackUserData) void CServer::ConchainSpecialInfoupdate(IConsole::IResult *pResult, void *pUserData, IConsole::FCommandCallback pfnCallback, void *pCallbackUserData)

View file

@ -377,8 +377,6 @@ public:
// console commands for sqlmasters // console commands for sqlmasters
static void ConAddSqlServer(IConsole::IResult *pResult, void *pUserData); static void ConAddSqlServer(IConsole::IResult *pResult, void *pUserData);
static void ConDumpSqlServers(IConsole::IResult *pResult, void *pUserData); static void ConDumpSqlServers(IConsole::IResult *pResult, void *pUserData);
static void CreateTablesThread(void *pData);
#endif #endif
static void ConchainSpecialInfoupdate(IConsole::IResult *pResult, void *pUserData, IConsole::FCommandCallback pfnCallback, void *pCallbackUserData); static void ConchainSpecialInfoupdate(IConsole::IResult *pResult, void *pUserData, IConsole::FCommandCallback pfnCallback, void *pCallbackUserData);

View file

@ -167,11 +167,12 @@ void CSqlServer::Disconnect()
m_SqlLock.release(); m_SqlLock.release();
} }
void CSqlServer::CreateTables() bool CSqlServer::CreateTables()
{ {
if (!Connect()) if (!Connect())
return; return false;
bool Success = false;
try try
{ {
char aBuf[1024]; char aBuf[1024];
@ -186,13 +187,14 @@ void CSqlServer::CreateTables()
str_format(aBuf, sizeof(aBuf), "CREATE TABLE IF NOT EXISTS %s_maps (Map VARCHAR(128) BINARY NOT NULL, Server VARCHAR(32) BINARY NOT NULL, Mapper VARCHAR(128) BINARY NOT NULL, Points INT DEFAULT 0, Stars INT DEFAULT 0, Timestamp TIMESTAMP DEFAULT CURRENT_TIMESTAMP, UNIQUE KEY Map (Map)) CHARACTER SET utf8mb4;", m_aPrefix); str_format(aBuf, sizeof(aBuf), "CREATE TABLE IF NOT EXISTS %s_maps (Map VARCHAR(128) BINARY NOT NULL, Server VARCHAR(32) BINARY NOT NULL, Mapper VARCHAR(128) BINARY NOT NULL, Points INT DEFAULT 0, Stars INT DEFAULT 0, Timestamp TIMESTAMP DEFAULT CURRENT_TIMESTAMP, UNIQUE KEY Map (Map)) CHARACTER SET utf8mb4;", m_aPrefix);
executeSql(aBuf); executeSql(aBuf);
str_format(aBuf, sizeof(aBuf), "CREATE TABLE IF NOT EXISTS %s_saves (Savegame TEXT CHARACTER SET utf8mb4 BINARY NOT NULL, Map VARCHAR(128) BINARY NOT NULL, Code VARCHAR(128) BINARY NOT NULL, Timestamp TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, Server CHAR(4), DDNet7 BOOL DEFAULT FALSE, UNIQUE KEY (Map, Code)) CHARACTER SET utf8mb4;", m_aPrefix); str_format(aBuf, sizeof(aBuf), "CREATE TABLE IF NOT EXISTS %s_saves (Savegame TEXT CHARACTER SET utf8mb4 BINARY NOT NULL, Map VARCHAR(128) BINARY NOT NULL, Code VARCHAR(128) BINARY NOT NULL, Timestamp TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, Server CHAR(4), DDNet7 BOOL DEFAULT FALSE, SaveID VARCHAR(36), UNIQUE KEY (Map, Code)) CHARACTER SET utf8mb4;", m_aPrefix);
executeSql(aBuf); executeSql(aBuf);
str_format(aBuf, sizeof(aBuf), "CREATE TABLE IF NOT EXISTS %s_points (Name VARCHAR(%d) BINARY NOT NULL, Points INT DEFAULT 0, UNIQUE KEY Name (Name)) CHARACTER SET utf8mb4;", m_aPrefix, MAX_NAME_LENGTH); str_format(aBuf, sizeof(aBuf), "CREATE TABLE IF NOT EXISTS %s_points (Name VARCHAR(%d) BINARY NOT NULL, Points INT DEFAULT 0, UNIQUE KEY Name (Name)) CHARACTER SET utf8mb4;", m_aPrefix, MAX_NAME_LENGTH);
executeSql(aBuf); executeSql(aBuf);
dbg_msg("sql", "Tables were created successfully"); dbg_msg("sql", "Tables were created successfully");
Success = true;
} }
catch (sql::SQLException &e) catch (sql::SQLException &e)
{ {
@ -200,6 +202,7 @@ void CSqlServer::CreateTables()
} }
Disconnect(); Disconnect();
return Success;
} }
void CSqlServer::executeSql(const char *pCommand) void CSqlServer::executeSql(const char *pCommand)

View file

@ -17,7 +17,7 @@ public:
bool Connect(); bool Connect();
void Disconnect(); void Disconnect();
void CreateTables(); bool CreateTables();
void executeSql(const char *pCommand); void executeSql(const char *pCommand);
void executeSqlQuery(const char *pQuery); void executeSqlQuery(const char *pQuery);
@ -30,6 +30,7 @@ public:
const char* GetPass() { return m_aPass; } const char* GetPass() { return m_aPass; }
const char* GetIP() { return m_aIp; } const char* GetIP() { return m_aIp; }
int GetPort() { return m_Port; } int GetPort() { return m_Port; }
sql::Connection *Connection() const { return m_pConnection; }
static int ms_NumReadServer; static int ms_NumReadServer;
static int ms_NumWriteServer; static int ms_NumWriteServer;

View file

@ -1,6 +1,8 @@
#ifndef ENGINE_SERVER_SQL_STRING_HELPERS_H #ifndef ENGINE_SERVER_SQL_STRING_HELPERS_H
#define ENGINE_SERVER_SQL_STRING_HELPERS_H #define ENGINE_SERVER_SQL_STRING_HELPERS_H
#include <base/system.h>
namespace sqlstr namespace sqlstr
{ {
@ -38,6 +40,11 @@ public:
return *this; return *this;
} }
bool operator<(const CSqlString& other) const
{
return strcmp(m_aString, other.m_aString) < 0;
}
private: private:
char m_aString[size]; char m_aString[size];
char m_aClearString[size * 2 - 1]; char m_aClearString[size * 2 - 1];

View file

@ -386,10 +386,9 @@ void CGameContext::ConTeamTop5(IConsole::IResult *pResult, void *pUserData)
} }
if (pResult->NumArguments() > 0) if (pResult->NumArguments() > 0)
pSelf->Score()->ShowTeamTop5(pResult, pResult->m_ClientID, pUserData, pSelf->Score()->ShowTeamTop5(pResult->m_ClientID, pResult->GetInteger(0));
pResult->GetInteger(0));
else else
pSelf->Score()->ShowTeamTop5(pResult, pResult->m_ClientID, pUserData); pSelf->Score()->ShowTeamTop5(pResult->m_ClientID);
#if defined(CONF_SQL) #if defined(CONF_SQL)
if(pSelf->m_apPlayers[pResult->m_ClientID] && g_Config.m_SvUseSQL) if(pSelf->m_apPlayers[pResult->m_ClientID] && g_Config.m_SvUseSQL)
@ -417,10 +416,9 @@ void CGameContext::ConTop5(IConsole::IResult *pResult, void *pUserData)
} }
if (pResult->NumArguments() > 0) if (pResult->NumArguments() > 0)
pSelf->Score()->ShowTop5(pResult, pResult->m_ClientID, pUserData, pSelf->Score()->ShowTop5(pResult->m_ClientID, pResult->GetInteger(0));
pResult->GetInteger(0));
else else
pSelf->Score()->ShowTop5(pResult, pResult->m_ClientID, pUserData); pSelf->Score()->ShowTop5(pResult->m_ClientID);
#if defined(CONF_SQL) #if defined(CONF_SQL)
if(pSelf->m_apPlayers[pResult->m_ClientID] && g_Config.m_SvUseSQL) if(pSelf->m_apPlayers[pResult->m_ClientID] && g_Config.m_SvUseSQL)
@ -520,13 +518,16 @@ void CGameContext::ConMap(IConsole::IResult *pResult, void *pUserData)
if (!pPlayer) if (!pPlayer)
return; return;
if(pSelf->RateLimitPlayerVote(pResult->m_ClientID) || pSelf->RateLimitPlayerMapVote(pResult->m_ClientID))
return;
#if defined(CONF_SQL) #if defined(CONF_SQL)
if(g_Config.m_SvUseSQL) if(g_Config.m_SvUseSQL)
if(pPlayer->m_LastSQLQuery + g_Config.m_SvSqlQueriesDelay * pSelf->Server()->TickSpeed() >= pSelf->Server()->Tick()) if(pPlayer->m_LastSQLQuery + g_Config.m_SvSqlQueriesDelay * pSelf->Server()->TickSpeed() >= pSelf->Server()->Tick())
return; return;
#endif #endif
pSelf->Score()->MapVote(&pSelf->m_pMapVoteResult, pResult->m_ClientID, pResult->GetString(0)); pSelf->Score()->MapVote(pResult->m_ClientID, pResult->GetString(0));
#if defined(CONF_SQL) #if defined(CONF_SQL)
if(g_Config.m_SvUseSQL) if(g_Config.m_SvUseSQL)
@ -695,19 +696,20 @@ void CGameContext::ConSave(IConsole::IResult *pResult, void *pUserData)
if(pPlayer->m_LastSQLQuery + g_Config.m_SvSqlQueriesDelay * pSelf->Server()->TickSpeed() >= pSelf->Server()->Tick()) if(pPlayer->m_LastSQLQuery + g_Config.m_SvSqlQueriesDelay * pSelf->Server()->TickSpeed() >= pSelf->Server()->Tick())
return; return;
int Team = ((CGameControllerDDRace*) pSelf->m_pController)->m_Teams.m_Core.Team(pResult->m_ClientID); const char* pCode = "";
if(pResult->NumArguments() > 0)
pCode = pResult->GetString(0);
const char* pCode = pResult->GetString(0);
char aCountry[5]; char aCountry[5];
if(str_length(pCode) > 3 && pCode[0] >= 'A' && pCode[0] <= 'Z' && pCode[1] >= 'A' if(str_length(pCode) >= 3 && pCode[0] >= 'A' && pCode[0] <= 'Z' && pCode[1] >= 'A'
&& pCode[1] <= 'Z' && pCode[2] >= 'A' && pCode[2] <= 'Z') && pCode[1] <= 'Z' && pCode[2] >= 'A' && pCode[2] <= 'Z')
{ {
if(pCode[3] == ' ') if(str_length(pCode) == 3 || pCode[3] == ' ')
{ {
str_copy(aCountry, pCode, 4); str_copy(aCountry, pCode, 4);
pCode = str_skip_whitespaces_const(pCode + 4); pCode = str_skip_whitespaces_const(pCode + 4);
} }
else if(str_length(pCode) > 4 && pCode[4] == ' ') else if(str_length(pCode) == 4 || (str_length(pCode) > 4 && pCode[4] == ' '))
{ {
str_copy(aCountry, pCode, 5); str_copy(aCountry, pCode, 5);
pCode = str_skip_whitespaces_const(pCode + 5); pCode = str_skip_whitespaces_const(pCode + 5);
@ -724,7 +726,7 @@ void CGameContext::ConSave(IConsole::IResult *pResult, void *pUserData)
if(str_in_list(g_Config.m_SvSqlValidServerNames, ",", aCountry)) if(str_in_list(g_Config.m_SvSqlValidServerNames, ",", aCountry))
{ {
pSelf->Score()->SaveTeam(Team, pCode, pResult->m_ClientID, aCountry); pSelf->Score()->SaveTeam(pResult->m_ClientID, pCode, aCountry);
if(g_Config.m_SvUseSQL) if(g_Config.m_SvUseSQL)
pPlayer->m_LastSQLQuery = pSelf->Server()->Tick(); pPlayer->m_LastSQLQuery = pSelf->Server()->Tick();
@ -790,8 +792,7 @@ void CGameContext::ConTeamRank(IConsole::IResult *pResult, void *pUserData)
if (pResult->NumArguments() > 0) if (pResult->NumArguments() > 0)
if (!g_Config.m_SvHideScore) if (!g_Config.m_SvHideScore)
pSelf->Score()->ShowTeamRank(pResult->m_ClientID, pResult->GetString(0), pSelf->Score()->ShowTeamRank(pResult->m_ClientID, pResult->GetString(0));
true);
else else
pSelf->Console()->Print( pSelf->Console()->Print(
IConsole::OUTPUT_LEVEL_STANDARD, IConsole::OUTPUT_LEVEL_STANDARD,
@ -825,8 +826,7 @@ void CGameContext::ConRank(IConsole::IResult *pResult, void *pUserData)
if (pResult->NumArguments() > 0) if (pResult->NumArguments() > 0)
if (!g_Config.m_SvHideScore) if (!g_Config.m_SvHideScore)
pSelf->Score()->ShowRank(pResult->m_ClientID, pResult->GetString(0), pSelf->Score()->ShowRank(pResult->m_ClientID, pResult->GetString(0));
true);
else else
pSelf->Console()->Print( pSelf->Console()->Print(
IConsole::OUTPUT_LEVEL_STANDARD, IConsole::OUTPUT_LEVEL_STANDARD,
@ -1548,8 +1548,7 @@ void CGameContext::ConPoints(IConsole::IResult *pResult, void *pUserData)
if (pResult->NumArguments() > 0) if (pResult->NumArguments() > 0)
if (!g_Config.m_SvHideScore) if (!g_Config.m_SvHideScore)
pSelf->Score()->ShowPoints(pResult->m_ClientID, pResult->GetString(0), pSelf->Score()->ShowPoints(pResult->m_ClientID, pResult->GetString(0));
true);
else else
pSelf->Console()->Print( pSelf->Console()->Print(
IConsole::OUTPUT_LEVEL_STANDARD, IConsole::OUTPUT_LEVEL_STANDARD,
@ -1583,10 +1582,9 @@ void CGameContext::ConTopPoints(IConsole::IResult *pResult, void *pUserData)
} }
if (pResult->NumArguments() > 0) if (pResult->NumArguments() > 0)
pSelf->Score()->ShowTopPoints(pResult, pResult->m_ClientID, pUserData, pSelf->Score()->ShowTopPoints(pResult->m_ClientID, pResult->GetInteger(0));
pResult->GetInteger(0));
else else
pSelf->Score()->ShowTopPoints(pResult, pResult->m_ClientID, pUserData); pSelf->Score()->ShowTopPoints(pResult->m_ClientID);
if(pSelf->m_apPlayers[pResult->m_ClientID] && g_Config.m_SvUseSQL) if(pSelf->m_apPlayers[pResult->m_ClientID] && g_Config.m_SvUseSQL)
pSelf->m_apPlayers[pResult->m_ClientID]->m_LastSQLQuery = pSelf->Server()->Tick(); pSelf->m_apPlayers[pResult->m_ClientID]->m_LastSQLQuery = pSelf->Server()->Tick();

View file

@ -27,7 +27,7 @@ CHAT_COMMAND("dnd", "", CFGFLAG_CHAT|CFGFLAG_SERVER|CFGFLAG_NONTEEHISTORIC, ConD
CHAT_COMMAND("mapinfo", "?r[map]", CFGFLAG_CHAT|CFGFLAG_SERVER, ConMapInfo, this, "Show info about the map with name r gives (current map by default)") CHAT_COMMAND("mapinfo", "?r[map]", CFGFLAG_CHAT|CFGFLAG_SERVER, ConMapInfo, this, "Show info about the map with name r gives (current map by default)")
CHAT_COMMAND("timeout", "?s[code]", CFGFLAG_CHAT|CFGFLAG_SERVER, ConTimeout, this, "Set timeout protection code s") CHAT_COMMAND("timeout", "?s[code]", CFGFLAG_CHAT|CFGFLAG_SERVER, ConTimeout, this, "Set timeout protection code s")
CHAT_COMMAND("practice", "?i['0'|'1']", CFGFLAG_CHAT|CFGFLAG_SERVER, ConPractice, this, "Enable cheats (currently only /rescue) for your current team's run, but you can't earn a rank") CHAT_COMMAND("practice", "?i['0'|'1']", CFGFLAG_CHAT|CFGFLAG_SERVER, ConPractice, this, "Enable cheats (currently only /rescue) for your current team's run, but you can't earn a rank")
CHAT_COMMAND("save", "r[code]", CFGFLAG_CHAT|CFGFLAG_SERVER, ConSave, this, "Save team with code r to current server. To save to another server, use '/save s r' where s = server (case-sensitive: GER, RUS, etc) and r = code.") CHAT_COMMAND("save", "?r[code]", CFGFLAG_CHAT|CFGFLAG_SERVER, ConSave, this, "Save team with code r to current server. To save to another server, use '/save s r' where s = server (case-sensitive: GER, RUS, etc) and r = code.")
CHAT_COMMAND("load", "?r[code]", CFGFLAG_CHAT|CFGFLAG_SERVER, ConLoad, this, "Load with code r. /load to check your existing saves") CHAT_COMMAND("load", "?r[code]", CFGFLAG_CHAT|CFGFLAG_SERVER, ConLoad, this, "Load with code r. /load to check your existing saves")
CHAT_COMMAND("map", "?r[map]", CFGFLAG_CHAT|CFGFLAG_SERVER|CFGFLAG_NONTEEHISTORIC, ConMap, this, "Vote a map by name") CHAT_COMMAND("map", "?r[map]", CFGFLAG_CHAT|CFGFLAG_SERVER|CFGFLAG_NONTEEHISTORIC, ConMap, this, "Vote a map by name")
CHAT_COMMAND("rankteam", "?r[player name]", CFGFLAG_CHAT|CFGFLAG_SERVER, ConTeamRank, this, "Shows the team rank of player with name r (your team rank by default)") CHAT_COMMAND("rankteam", "?r[player name]", CFGFLAG_CHAT|CFGFLAG_SERVER, ConTeamRank, this, "Shows the team rank of player with name r (your team rank by default)")

View file

@ -1503,14 +1503,15 @@ void CCharacter::HandleTiles(int Index)
// start // start
if(((m_TileIndex == TILE_BEGIN) || (m_TileFIndex == TILE_BEGIN) || FTile1 == TILE_BEGIN || FTile2 == TILE_BEGIN || FTile3 == TILE_BEGIN || FTile4 == TILE_BEGIN || Tile1 == TILE_BEGIN || Tile2 == TILE_BEGIN || Tile3 == TILE_BEGIN || Tile4 == TILE_BEGIN) && (m_DDRaceState == DDRACE_NONE || m_DDRaceState == DDRACE_FINISHED || (m_DDRaceState == DDRACE_STARTED && !Team() && g_Config.m_SvTeam != 3))) if(((m_TileIndex == TILE_BEGIN) || (m_TileFIndex == TILE_BEGIN) || FTile1 == TILE_BEGIN || FTile2 == TILE_BEGIN || FTile3 == TILE_BEGIN || FTile4 == TILE_BEGIN || Tile1 == TILE_BEGIN || Tile2 == TILE_BEGIN || Tile3 == TILE_BEGIN || Tile4 == TILE_BEGIN) && (m_DDRaceState == DDRACE_NONE || m_DDRaceState == DDRACE_FINISHED || (m_DDRaceState == DDRACE_STARTED && !Team() && g_Config.m_SvTeam != 3)))
{ {
if(g_Config.m_SvResetPickups) if(Teams()->GetSaving(Team()))
{ {
for (int i = WEAPON_SHOTGUN; i < NUM_WEAPONS; ++i) if(m_LastStartWarning < Server()->Tick() - 3 * Server()->TickSpeed())
{ {
m_aWeapons[i].m_Got = false; GameServer()->SendChatTarget(GetPlayer()->GetCID(), "You can't start while loading/saving of team is in progress");
if(m_Core.m_ActiveWeapon == i) m_LastStartWarning = Server()->Tick();
m_Core.m_ActiveWeapon = WEAPON_GUN;
} }
Die(GetPlayer()->GetCID(), WEAPON_WORLD);
return;
} }
if(g_Config.m_SvTeam == 2 && (Team() == TEAM_FLOCK || Teams()->Count(Team()) <= 1)) if(g_Config.m_SvTeam == 2 && (Team() == TEAM_FLOCK || Teams()->Count(Team()) <= 1))
{ {
@ -1522,6 +1523,15 @@ void CCharacter::HandleTiles(int Index)
Die(GetPlayer()->GetCID(), WEAPON_WORLD); Die(GetPlayer()->GetCID(), WEAPON_WORLD);
return; return;
} }
if(g_Config.m_SvResetPickups)
{
for (int i = WEAPON_SHOTGUN; i < NUM_WEAPONS; ++i)
{
m_aWeapons[i].m_Got = false;
if(m_Core.m_ActiveWeapon == i)
m_Core.m_ActiveWeapon = WEAPON_GUN;
}
}
Teams()->OnCharacterStart(m_pPlayer->GetCID()); Teams()->OnCharacterStart(m_pPlayer->GetCID());
m_CpActive = -2; m_CpActive = -2;

View file

@ -57,9 +57,6 @@ void CGameContext::Construct(int Resetting)
m_ChatResponseTargetID = -1; m_ChatResponseTargetID = -1;
m_aDeleteTempfile[0] = 0; m_aDeleteTempfile[0] = 0;
m_TeeHistorianActive = false; m_TeeHistorianActive = false;
m_pRandomMapResult = nullptr;
m_pMapVoteResult = nullptr;
} }
CGameContext::CGameContext(int Resetting) CGameContext::CGameContext(int Resetting)
@ -901,6 +898,7 @@ void CGameContext::OnTick()
} }
if(Collision()->m_NumSwitchers > 0) if(Collision()->m_NumSwitchers > 0)
{
for (int i = 0; i < Collision()->m_NumSwitchers+1; ++i) for (int i = 0; i < Collision()->m_NumSwitchers+1; ++i)
{ {
for (int j = 0; j < MAX_CLIENTS; ++j) for (int j = 0; j < MAX_CLIENTS; ++j)
@ -919,28 +917,6 @@ void CGameContext::OnTick()
} }
} }
} }
if(m_pRandomMapResult && m_pRandomMapResult->m_Done)
{
str_copy(g_Config.m_SvMap, m_pRandomMapResult->m_aMap, sizeof(g_Config.m_SvMap));
m_pRandomMapResult = NULL;
}
if(m_pMapVoteResult && m_pMapVoteResult->m_Done)
{
m_VoteKick = false;
m_VoteSpec = false;
m_LastMapVote = time_get();
char aCmd[256];
str_format(aCmd, sizeof(aCmd), "sv_reset_file types/%s/flexreset.cfg; change_map \"%s\"", m_pMapVoteResult->m_aServer, m_pMapVoteResult->m_aMap);
char aChatmsg[512];
str_format(aChatmsg, sizeof(aChatmsg), "'%s' called vote to change server option '%s' (%s)", Server()->ClientName(m_pMapVoteResult->m_ClientID), m_pMapVoteResult->m_aMap, "/map");
CallVote(m_pMapVoteResult->m_ClientID, m_pMapVoteResult->m_aMap, aCmd, "/map", aChatmsg);
m_pMapVoteResult = NULL;
} }
#ifdef CONF_DEBUG #ifdef CONF_DEBUG
@ -1076,8 +1052,7 @@ void CGameContext::OnClientEnter(int ClientID)
// Can't set score here as LoadScore() is threaded, run it in // Can't set score here as LoadScore() is threaded, run it in
// LoadScoreThreaded() instead // LoadScoreThreaded() instead
Score()->LoadScore(ClientID); Score()->LoadPlayerData(ClientID);
Score()->CheckBirthday(ClientID);
{ {
int Empty = -1; int Empty = -1;
@ -1424,68 +1399,8 @@ void CGameContext::OnMessage(int MsgID, CUnpacker *pUnpacker, int ClientID)
} }
else if(MsgID == NETMSGTYPE_CL_CALLVOTE) else if(MsgID == NETMSGTYPE_CL_CALLVOTE)
{ {
int64 Now = Server()->Tick(); if(RateLimitPlayerVote(ClientID))
int64 TickSpeed = Server()->TickSpeed();
if(g_Config.m_SvRconVote && !Server()->GetAuthedState(ClientID))
{
SendChatTarget(ClientID, "You can only vote after logging in.");
return; return;
}
if (g_Config.m_SvDnsblVote && !m_pServer->DnsblWhite(ClientID) && Server()->DistinctClientCount() > 1)
{
// blacklisted by dnsbl
SendChatTarget(ClientID, "You are not allowed to vote due to DNSBL.");
return;
}
if(g_Config.m_SvSpamprotection && pPlayer->m_LastVoteTry && pPlayer->m_LastVoteTry + TickSpeed * 3 > Now)
return;
pPlayer->m_LastVoteTry = Now;
if(g_Config.m_SvSpectatorVotes == 0 && pPlayer->GetTeam() == TEAM_SPECTATORS)
{
SendChatTarget(ClientID, "Spectators aren't allowed to start a vote.");
return;
}
if(m_VoteCloseTime)
{
SendChatTarget(ClientID, "Wait for current vote to end before calling a new one.");
return;
}
if(Now < pPlayer->m_FirstVoteTick)
{
char aBuf[64];
str_format(aBuf, sizeof(aBuf), "You must wait %d seconds before making your first vote.", (int)((pPlayer->m_FirstVoteTick - Now) / TickSpeed) + 1);
SendChatTarget(ClientID, aBuf);
return;
}
int TimeLeft = pPlayer->m_LastVoteCall + TickSpeed * g_Config.m_SvVoteDelay - Now;
if(pPlayer->m_LastVoteCall && TimeLeft > 0)
{
char aChatmsg[64];
str_format(aChatmsg, sizeof(aChatmsg), "You must wait %d seconds before making another vote.", (int)(TimeLeft / TickSpeed) + 1);
SendChatTarget(ClientID, aChatmsg);
return;
}
NETADDR Addr;
Server()->GetClientAddr(ClientID, &Addr);
int VoteMuted = 0;
for(int i = 0; i < m_NumVoteMutes && !VoteMuted; i++)
if(!net_addr_comp_noport(&Addr, &m_aVoteMutes[i].m_Addr))
VoteMuted = (m_aVoteMutes[i].m_Expire - Server()->Tick()) / Server()->TickSpeed();
if(VoteMuted > 0)
{
char aChatmsg[64];
str_format(aChatmsg, sizeof(aChatmsg), "You are not permitted to vote for the next %d seconds.", VoteMuted);
SendChatTarget(ClientID, aChatmsg);
return;
}
char aChatmsg[512] = {0}; char aChatmsg[512] = {0};
char aDesc[VOTE_DESC_LENGTH] = {0}; char aDesc[VOTE_DESC_LENGTH] = {0};
@ -1516,11 +1431,12 @@ void CGameContext::OnMessage(int MsgID, CUnpacker *pUnpacker, int ClientID)
SendChatTarget(ClientID, "Invalid option"); SendChatTarget(ClientID, "Invalid option");
return; return;
} }
if(!Authed && (str_startswith(pOption->m_aCommand, "sv_map ") || str_startswith(pOption->m_aCommand, "change_map ") || str_startswith(pOption->m_aCommand, "random_map") || str_startswith(pOption->m_aCommand, "random_unfinished_map")) && time_get() < m_LastMapVote + (time_freq() * g_Config.m_SvVoteMapTimeDelay)) if((str_startswith(pOption->m_aCommand, "sv_map ")
|| str_startswith(pOption->m_aCommand, "change_map ")
|| str_startswith(pOption->m_aCommand, "random_map")
|| str_startswith(pOption->m_aCommand, "random_unfinished_map"))
&& RateLimitPlayerMapVote(ClientID))
{ {
str_format(aChatmsg, sizeof(aChatmsg), "There's a %d second delay between map-votes, please wait %d seconds.", g_Config.m_SvVoteMapTimeDelay, (int)(((m_LastMapVote+(g_Config.m_SvVoteMapTimeDelay * time_freq()))/time_freq())-(time_get()/time_freq())));
SendChatTarget(ClientID, aChatmsg);
return; return;
} }
@ -1877,18 +1793,8 @@ void CGameContext::OnMessage(int MsgID, CUnpacker *pUnpacker, int ClientID)
// reload scores // reload scores
Score()->PlayerData(ClientID)->Reset(); Score()->PlayerData(ClientID)->Reset();
Score()->LoadScore(ClientID);
Score()->PlayerData(ClientID)->m_CurrentTime = Score()->PlayerData(ClientID)->m_BestTime;
// -9999 stands for no time and isn't displayed in scoreboard, so
// shift the time by a second if the player actually took 9999
// seconds to finish the map.
if(!Score()->PlayerData(ClientID)->m_BestTime)
m_apPlayers[ClientID]->m_Score = -9999; m_apPlayers[ClientID]->m_Score = -9999;
else if((int)Score()->PlayerData(ClientID)->m_BestTime == -9999) Score()->LoadPlayerData(ClientID);
m_apPlayers[ClientID]->m_Score = -10000;
else
m_apPlayers[ClientID]->m_Score = Score()->PlayerData(ClientID)->m_BestTime;
} }
Server()->SetClientClan(ClientID, pMsg->m_pClan); Server()->SetClientClan(ClientID, pMsg->m_pClan);
Server()->SetClientCountry(ClientID, pMsg->m_Country); Server()->SetClientCountry(ClientID, pMsg->m_Country);
@ -2241,7 +2147,7 @@ void CGameContext::ConRandomMap(IConsole::IResult *pResult, void *pUserData)
int Stars = pResult->NumArguments() ? pResult->GetInteger(0) : -1; int Stars = pResult->NumArguments() ? pResult->GetInteger(0) : -1;
pSelf->m_pScore->RandomMap(&pSelf->m_pRandomMapResult, pSelf->m_VoteCreator, Stars); pSelf->m_pScore->RandomMap(pSelf->m_VoteCreator, Stars);
} }
void CGameContext::ConRandomUnfinishedMap(IConsole::IResult *pResult, void *pUserData) void CGameContext::ConRandomUnfinishedMap(IConsole::IResult *pResult, void *pUserData)
@ -2250,7 +2156,7 @@ void CGameContext::ConRandomUnfinishedMap(IConsole::IResult *pResult, void *pUse
int Stars = pResult->NumArguments() ? pResult->GetInteger(0) : -1; int Stars = pResult->NumArguments() ? pResult->GetInteger(0) : -1;
pSelf->m_pScore->RandomUnfinishedMap(&pSelf->m_pRandomMapResult, pSelf->m_VoteCreator, Stars); pSelf->m_pScore->RandomUnfinishedMap(pSelf->m_VoteCreator, Stars);
} }
void CGameContext::ConRestart(IConsole::IResult *pResult, void *pUserData) void CGameContext::ConRestart(IConsole::IResult *pResult, void *pUserData)
@ -3591,3 +3497,84 @@ void CGameContext::ForceVote(int EnforcerID, bool Success)
str_format(aBuf, sizeof(aBuf), "forcing vote %s", pOption); str_format(aBuf, sizeof(aBuf), "forcing vote %s", pOption);
Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "server", aBuf); Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "server", aBuf);
} }
bool CGameContext::RateLimitPlayerVote(int ClientID)
{
int64 Now = Server()->Tick();
int64 TickSpeed = Server()->TickSpeed();
CPlayer *pPlayer = m_apPlayers[ClientID];
if(g_Config.m_SvRconVote && !Server()->GetAuthedState(ClientID))
{
SendChatTarget(ClientID, "You can only vote after logging in.");
return true;
}
if (g_Config.m_SvDnsblVote && !m_pServer->DnsblWhite(ClientID) && Server()->DistinctClientCount() > 1)
{
// blacklisted by dnsbl
SendChatTarget(ClientID, "You are not allowed to vote due to DNSBL.");
return true;
}
if(g_Config.m_SvSpamprotection && pPlayer->m_LastVoteTry && pPlayer->m_LastVoteTry + TickSpeed * 3 > Now)
return true;
pPlayer->m_LastVoteTry = Now;
if(g_Config.m_SvSpectatorVotes == 0 && pPlayer->GetTeam() == TEAM_SPECTATORS)
{
SendChatTarget(ClientID, "Spectators aren't allowed to start a vote.");
return true;
}
if(m_VoteCloseTime)
{
SendChatTarget(ClientID, "Wait for current vote to end before calling a new one.");
return true;
}
if(Now < pPlayer->m_FirstVoteTick)
{
char aBuf[64];
str_format(aBuf, sizeof(aBuf), "You must wait %d seconds before making your first vote.", (int)((pPlayer->m_FirstVoteTick - Now) / TickSpeed) + 1);
SendChatTarget(ClientID, aBuf);
return true;
}
int TimeLeft = pPlayer->m_LastVoteCall + TickSpeed * g_Config.m_SvVoteDelay - Now;
if(pPlayer->m_LastVoteCall && TimeLeft > 0)
{
char aChatmsg[64];
str_format(aChatmsg, sizeof(aChatmsg), "You must wait %d seconds before making another vote.", (int)(TimeLeft / TickSpeed) + 1);
SendChatTarget(ClientID, aChatmsg);
return true;
}
NETADDR Addr;
Server()->GetClientAddr(ClientID, &Addr);
int VoteMuted = 0;
for(int i = 0; i < m_NumVoteMutes && !VoteMuted; i++)
if(!net_addr_comp_noport(&Addr, &m_aVoteMutes[i].m_Addr))
VoteMuted = (m_aVoteMutes[i].m_Expire - Server()->Tick()) / Server()->TickSpeed();
if(VoteMuted > 0)
{
char aChatmsg[64];
str_format(aChatmsg, sizeof(aChatmsg), "You are not permitted to vote for the next %d seconds.", VoteMuted);
SendChatTarget(ClientID, aChatmsg);
return true;
}
return false;
}
bool CGameContext::RateLimitPlayerMapVote(int ClientID)
{
if(!Server()->GetAuthedState(ClientID) && time_get() < m_LastMapVote + (time_freq() * g_Config.m_SvVoteMapTimeDelay))
{
char aChatmsg[512] = {0};
str_format(aChatmsg, sizeof(aChatmsg), "There's a %d second delay between map-votes, please wait %d seconds.",
g_Config.m_SvVoteMapTimeDelay, (int)((m_LastMapVote + g_Config.m_SvVoteMapTimeDelay * time_freq() - time_get())/time_freq()));
SendChatTarget(ClientID, aChatmsg);
return true;
}
return false;
}

View file

@ -57,8 +57,6 @@ enum
class IConsole; class IConsole;
class IEngine; class IEngine;
class IStorage; class IStorage;
class CRandomMapResult;
class CMapVoteResult;
struct CAntibotData; struct CAntibotData;
class CGameContext : public IGameServer class CGameContext : public IGameServer
@ -81,9 +79,6 @@ class CGameContext : public IGameServer
CMapBugs m_MapBugs; CMapBugs m_MapBugs;
CPrng m_Prng; CPrng m_Prng;
std::shared_ptr<CRandomMapResult> m_pRandomMapResult;
std::shared_ptr<CMapVoteResult> m_pMapVoteResult;
static void CommandCallback(int ClientID, int FlagMask, const char *pCmd, IConsole::IResult *pResult, void *pUser); static void CommandCallback(int ClientID, int FlagMask, const char *pCmd, IConsole::IResult *pResult, void *pUser);
static void TeeHistorianWrite(const void *pData, int DataSize, void *pUser); static void TeeHistorianWrite(const void *pData, int DataSize, void *pUser);
@ -264,6 +259,10 @@ public:
bool PlayerModerating(); bool PlayerModerating();
void ForceVote(int EnforcerID, bool Success); void ForceVote(int EnforcerID, bool Success);
// Checks if player can vote and notify them about the reason
bool RateLimitPlayerVote(int ClientID);
bool RateLimitPlayerMapVote(int ClientID);
private: private:
bool m_VoteWillPass; bool m_VoteWillPass;

View file

@ -9,8 +9,12 @@
#include "DDRace.h" #include "DDRace.h"
#include "gamemode.h" #include "gamemode.h"
#if defined(CONF_SQL)
#include <game/server/score/sql_score.h>
#endif
CGameControllerDDRace::CGameControllerDDRace(class CGameContext *pGameServer) : CGameControllerDDRace::CGameControllerDDRace(class CGameContext *pGameServer) :
IGameController(pGameServer), m_Teams(pGameServer) IGameController(pGameServer), m_Teams(pGameServer), m_pInitResult(nullptr)
{ {
m_pGameType = g_Config.m_SvTestingCommands ? TEST_NAME : GAME_NAME; m_pGameType = g_Config.m_SvTestingCommands ? TEST_NAME : GAME_NAME;
@ -25,6 +29,18 @@ CGameControllerDDRace::~CGameControllerDDRace()
void CGameControllerDDRace::Tick() void CGameControllerDDRace::Tick()
{ {
IGameController::Tick(); IGameController::Tick();
#if defined(CONF_SQL)
m_Teams.ProcessSaveTeam();
if(m_pInitResult != nullptr && m_pInitResult.use_count() == 1)
{
if(m_pInitResult->m_Done)
{
m_CurrentRecord = m_pInitResult->m_CurrentRecord;
}
m_pInitResult = nullptr;
}
#endif
} }
void CGameControllerDDRace::InitTeleporter() void CGameControllerDDRace::InitTeleporter()

View file

@ -8,6 +8,7 @@
#include <vector> #include <vector>
#include <map> #include <map>
struct CSqlInitResult;
class CGameControllerDDRace: public IGameController class CGameControllerDDRace: public IGameController
{ {
public: public:
@ -22,5 +23,7 @@ public:
void InitTeleporter(); void InitTeleporter();
virtual void Tick(); virtual void Tick();
std::shared_ptr<CSqlInitResult> m_pInitResult;
}; };
#endif // GAME_SERVER_GAMEMODES_DDRACE_H #endif // GAME_SERVER_GAMEMODES_DDRACE_H

View file

@ -12,6 +12,10 @@
#include "gamemodes/DDRace.h" #include "gamemodes/DDRace.h"
#include <time.h> #include <time.h>
#if defined(CONF_SQL)
#include "score/sql_score.h"
#endif
MACRO_ALLOC_POOL_ID_IMPL(CPlayer, MAX_CLIENTS) MACRO_ALLOC_POOL_ID_IMPL(CPlayer, MAX_CLIENTS)
IServer *CPlayer::Server() const { return m_pGameServer->Server(); } IServer *CPlayer::Server() const { return m_pGameServer->Server(); }
@ -41,7 +45,6 @@ void CPlayer::Reset()
m_JoinTick = Server()->Tick(); m_JoinTick = Server()->Tick();
delete m_pCharacter; delete m_pCharacter;
m_pCharacter = 0; m_pCharacter = 0;
m_KillMe = 0;
m_SpectatorID = SPEC_FREEVIEW; m_SpectatorID = SPEC_FREEVIEW;
m_LastActionTick = Server()->Tick(); m_LastActionTick = Server()->Tick();
m_TeamChangeTick = Server()->Tick(); m_TeamChangeTick = Server()->Tick();
@ -119,6 +122,8 @@ void CPlayer::Reset()
m_Last_Team = 0; m_Last_Team = 0;
#if defined(CONF_SQL) #if defined(CONF_SQL)
m_LastSQLQuery = 0; m_LastSQLQuery = 0;
m_SqlQueryResult = nullptr;
m_SqlFinishResult = nullptr;
#endif #endif
int64 Now = Server()->Tick(); int64 Now = Server()->Tick();
@ -127,7 +132,7 @@ void CPlayer::Reset()
// non-empty, allow them to vote immediately. This allows players to // non-empty, allow them to vote immediately. This allows players to
// vote after map changes or when they join an empty server. // vote after map changes or when they join an empty server.
// //
// Otherwise, block voting in the begnning after joining. // Otherwise, block voting in the beginning after joining.
if(Now > GameServer()->m_NonEmptySince + 10 * TickSpeed) if(Now > GameServer()->m_NonEmptySince + 10 * TickSpeed)
m_FirstVoteTick = Now + g_Config.m_SvJoinVoteDelay * TickSpeed; m_FirstVoteTick = Now + g_Config.m_SvJoinVoteDelay * TickSpeed;
else else
@ -143,16 +148,35 @@ void CPlayer::Tick()
#ifdef CONF_DEBUG #ifdef CONF_DEBUG
if(!g_Config.m_DbgDummies || m_ClientID < MAX_CLIENTS-g_Config.m_DbgDummies) if(!g_Config.m_DbgDummies || m_ClientID < MAX_CLIENTS-g_Config.m_DbgDummies)
#endif #endif
#if defined(CONF_SQL)
if(m_SqlQueryResult != nullptr && m_SqlQueryResult.use_count() == 1)
{
ProcessSqlResult(*m_SqlQueryResult);
m_SqlQueryResult = nullptr;
}
if(m_SqlFinishResult != nullptr && m_SqlFinishResult.use_count() == 1)
{
ProcessSqlResult(*m_SqlFinishResult);
m_SqlFinishResult = nullptr;
}
if(m_SqlRandomMapResult!= nullptr && m_SqlRandomMapResult.use_count() == 1)
{
if(m_SqlRandomMapResult->m_Done)
{
if(m_SqlRandomMapResult->m_aMessage[0] != '\0')
GameServer()->SendChatTarget(m_ClientID, m_SqlRandomMapResult->m_aMessage);
if(m_SqlRandomMapResult->m_Map[0] != '\0')
str_copy(g_Config.m_SvMap, m_SqlRandomMapResult->m_Map, sizeof(g_Config.m_SvMap));
else
GameServer()->m_LastMapVote = 0;
}
m_SqlRandomMapResult = nullptr;
}
#endif
if(!Server()->ClientIngame(m_ClientID)) if(!Server()->ClientIngame(m_ClientID))
return; return;
if(m_KillMe != 0)
{
KillCharacter(m_KillMe);
m_KillMe = 0;
return;
}
if (m_ChatScore > 0) if (m_ChatScore > 0)
m_ChatScore--; m_ChatScore--;
@ -233,7 +257,7 @@ void CPlayer::Tick()
int CurrentIndex = GameServer()->Collision()->GetMapIndex(m_ViewPos); int CurrentIndex = GameServer()->Collision()->GetMapIndex(m_ViewPos);
m_TuneZone = GameServer()->Collision()->IsTune(CurrentIndex); m_TuneZone = GameServer()->Collision()->IsTune(CurrentIndex);
if (m_TuneZone != m_TuneZoneOld) // don't send tunigs all the time if (m_TuneZone != m_TuneZoneOld) // don't send tunings all the time
{ {
GameServer()->SendTuningParams(m_ClientID, m_TuneZone); GameServer()->SendTuningParams(m_ClientID, m_TuneZone);
} }
@ -491,11 +515,6 @@ CCharacter *CPlayer::GetCharacter()
return 0; return 0;
} }
void CPlayer::ThreadKillCharacter(int Weapon)
{
m_KillMe = Weapon;
}
void CPlayer::KillCharacter(int Weapon) void CPlayer::KillCharacter(int Weapon)
{ {
if(m_pCharacter) if(m_pCharacter)
@ -782,3 +801,79 @@ void CPlayer::SpectatePlayerName(const char *pName)
} }
} }
} }
#if defined(CONF_SQL)
void CPlayer::ProcessSqlResult(CSqlPlayerResult &Result)
{
if(Result.m_Done) // SQL request was successful
{
int NumMessages = (int)(sizeof(Result.m_aaMessages)/sizeof(Result.m_aaMessages[0]));
switch(Result.m_MessageKind)
{
case CSqlPlayerResult::DIRECT:
for(int i = 0; i < NumMessages; i++)
{
if(Result.m_aaMessages[i][0] == 0)
break;
GameServer()->SendChatTarget(m_ClientID, Result.m_aaMessages[i]);
}
break;
case CSqlPlayerResult::ALL:
for(int i = 0; i < NumMessages; i++)
{
if(Result.m_aaMessages[i][0] == 0)
break;
GameServer()->SendChat(-1, CGameContext::CHAT_ALL, Result.m_aaMessages[i]);
}
break;
case CSqlPlayerResult::BROADCAST:
if(Result.m_Data.m_Broadcast[0] != 0)
GameServer()->SendBroadcast(Result.m_Data.m_Broadcast, -1);
break;
case CSqlPlayerResult::MAP_VOTE:
GameServer()->m_VoteKick = false;
GameServer()->m_VoteSpec = false;
GameServer()->m_LastMapVote = time_get();
char aCmd[256];
str_format(aCmd, sizeof(aCmd),
"sv_reset_file types/%s/flexreset.cfg; change_map \"%s\"",
Result.m_Data.m_MapVote.m_Server, Result.m_Data.m_MapVote.m_Map);
char aChatmsg[512];
str_format(aChatmsg, sizeof(aChatmsg), "'%s' called vote to change server option '%s' (%s)",
Server()->ClientName(m_ClientID), Result.m_Data.m_MapVote.m_Map, "/map");
GameServer()->CallVote(m_ClientID, Result.m_Data.m_MapVote.m_Map, aCmd, "/map", aChatmsg);
break;
case CSqlPlayerResult::PLAYER_INFO:
GameServer()->Score()->PlayerData(m_ClientID)->Set(
Result.m_Data.m_Info.m_Time,
Result.m_Data.m_Info.m_CpTime
);
m_Score = Result.m_Data.m_Info.m_Score;
m_HasFinishScore = Result.m_Data.m_Info.m_HasFinishScore;
// -9999 stands for no time and isn't displayed in scoreboard, so
// shift the time by a second if the player actually took 9999
// seconds to finish the map.
if(m_HasFinishScore && m_Score == -9999)
m_Score = -10000;
Server()->ExpireServerInfo();
int Birthday = Result.m_Data.m_Info.m_Birthday;
if(Birthday != 0)
{
char aBuf[512];
str_format(aBuf, sizeof(aBuf),
"Happy DDNet birthday to %s for finishing their first map %d year%s ago!",
Server()->ClientName(m_ClientID), Birthday, Birthday > 1 ? "s" : "");
GameServer()->SendChat(-1, CGameContext::CHAT_ALL, aBuf, m_ClientID);
str_format(aBuf, sizeof(aBuf),
"Happy DDNet birthday, %s!\nYou have finished your first map exactly %d year%s ago!",
Server()->ClientName(m_ClientID), Birthday, Birthday > 1 ? "s" : "");
GameServer()->SendBroadcast(aBuf, m_ClientID);
}
break;
}
}
}
#endif

View file

@ -5,7 +5,14 @@
// this include should perhaps be removed // this include should perhaps be removed
#include "entities/character.h" #include "entities/character.h"
#include "score.h"
#include "gamecontext.h" #include "gamecontext.h"
#include <memory>
#if defined(CONF_SQL)
class CSqlPlayerResult;
class CSqlRandomMapResult;
#endif
// player object // player object
class CPlayer class CPlayer
@ -41,7 +48,6 @@ public:
void OnPredictedEarlyInput(CNetObj_PlayerInput *NewInput); void OnPredictedEarlyInput(CNetObj_PlayerInput *NewInput);
void OnDisconnect(const char *pReason); void OnDisconnect(const char *pReason);
void ThreadKillCharacter(int Weapon = WEAPON_GAME);
void KillCharacter(int Weapon = WEAPON_GAME); void KillCharacter(int Weapon = WEAPON_GAME);
CCharacter *GetCharacter(); CCharacter *GetCharacter();
@ -168,7 +174,6 @@ public:
bool m_SpecTeam; bool m_SpecTeam;
bool m_NinjaJetpack; bool m_NinjaJetpack;
bool m_Afk; bool m_Afk;
int m_KillMe;
bool m_HasFinishScore; bool m_HasFinishScore;
int m_ChatScore; int m_ChatScore;
@ -194,7 +199,11 @@ public:
bool m_Halloween; bool m_Halloween;
bool m_FirstPacket; bool m_FirstPacket;
#if defined(CONF_SQL) #if defined(CONF_SQL)
void ProcessSqlResult(CSqlPlayerResult &Result);
int64 m_LastSQLQuery; int64 m_LastSQLQuery;
std::shared_ptr<CSqlPlayerResult> m_SqlQueryResult;
std::shared_ptr<CSqlPlayerResult> m_SqlFinishResult;
std::shared_ptr<CSqlRandomMapResult> m_SqlRandomMapResult;
#endif #endif
bool m_NotEligibleForFinish; bool m_NotEligibleForFinish;
int64 m_EligibleForFinishCheck; int64 m_EligibleForFinishCheck;

View file

@ -1,9 +1,10 @@
#include "save.h"
#include <new> #include <new>
#include <cstdio> #include <cstdio>
#include "save.h"
#include "teams.h" #include "teams.h"
#include "./gamemodes/DDRace.h" #include "gamemodes/DDRace.h"
#include <engine/shared/config.h> #include <engine/shared/config.h>
CSaveTee::CSaveTee() CSaveTee::CSaveTee()
@ -16,13 +17,14 @@ CSaveTee::~CSaveTee()
void CSaveTee::save(CCharacter *pChr) void CSaveTee::save(CCharacter *pChr)
{ {
str_copy(m_aName, pChr->m_pPlayer->Server()->ClientName(pChr->m_pPlayer->GetCID()), sizeof(m_aName)); m_ClientID = pChr->m_pPlayer->GetCID();
str_copy(m_aName, pChr->m_pPlayer->Server()->ClientName(m_ClientID), sizeof(m_aName));
m_Alive = pChr->m_Alive; m_Alive = pChr->m_Alive;
m_Paused = abs(pChr->m_pPlayer->IsPaused()); m_Paused = abs(pChr->m_pPlayer->IsPaused());
m_NeededFaketuning = pChr->m_NeededFaketuning; m_NeededFaketuning = pChr->m_NeededFaketuning;
m_TeeFinished = pChr->Teams()->TeeFinished(pChr->m_pPlayer->GetCID()); m_TeeFinished = pChr->Teams()->TeeFinished(m_ClientID);
m_IsSolo = pChr->m_Solo; m_IsSolo = pChr->m_Solo;
for(int i = 0; i< NUM_WEAPONS; i++) for(int i = 0; i< NUM_WEAPONS; i++)
@ -51,7 +53,9 @@ void CSaveTee::save(CCharacter *pChr)
m_TuneZoneOld = pChr->m_TuneZoneOld; m_TuneZoneOld = pChr->m_TuneZoneOld;
if(pChr->m_StartTime) if(pChr->m_StartTime)
m_Time = pChr->Server()->Tick() - pChr->m_StartTime + 60 * pChr->Server()->TickSpeed(); m_Time = pChr->Server()->Tick() - pChr->m_StartTime;
else
m_Time = 0;
m_Pos = pChr->m_Pos; m_Pos = pChr->m_Pos;
m_PrevPos = pChr->m_PrevPos; m_PrevPos = pChr->m_PrevPos;
@ -182,6 +186,9 @@ void CSaveTee::load(CCharacter *pChr, int Team)
char* CSaveTee::GetString() char* CSaveTee::GetString()
{ {
// Add time penalty of 60 seconds (only to the database)
int Time = m_Time + 60 * SERVER_TICK_SPEED;
str_format(m_aString, sizeof(m_aString), str_format(m_aString, sizeof(m_aString),
"%s\t%d\t%d\t%d\t%d\t%d\t" "%s\t%d\t%d\t%d\t%d\t%d\t"
// weapons // weapons
@ -222,7 +229,7 @@ char* CSaveTee::GetString()
m_LastWeapon, m_QueuedWeapon, m_LastWeapon, m_QueuedWeapon,
// tee states // tee states
m_SuperJump, m_Jetpack, m_NinjaJetpack, m_FreezeTime, m_FreezeTick, m_DeepFreeze, m_EndlessHook, m_SuperJump, m_Jetpack, m_NinjaJetpack, m_FreezeTime, m_FreezeTick, m_DeepFreeze, m_EndlessHook,
m_DDRaceState, m_Hit, m_Collision, m_TuneZone, m_TuneZoneOld, m_Hook, m_Time, m_DDRaceState, m_Hit, m_Collision, m_TuneZone, m_TuneZoneOld, m_Hook, Time,
(int)m_Pos.x, (int)m_Pos.y, (int)m_PrevPos.x, (int)m_PrevPos.y, (int)m_Pos.x, (int)m_Pos.y, (int)m_PrevPos.x, (int)m_PrevPos.y,
m_TeleCheckpoint, m_LastPenalty, m_TeleCheckpoint, m_LastPenalty,
(int)m_CorePos.x, (int)m_CorePos.y, m_Vel.x, m_Vel.y, (int)m_CorePos.x, (int)m_CorePos.y, m_Vel.x, m_Vel.y,
@ -243,7 +250,7 @@ char* CSaveTee::GetString()
return m_aString; return m_aString;
} }
int CSaveTee::LoadString(char* String) int CSaveTee::LoadString(const char* String)
{ {
int Num; int Num;
Num = sscanf(String, Num = sscanf(String,
@ -398,6 +405,8 @@ bool CSaveTeam::HandleSaveError(int Result, int ClientID, CGameContext *pGameCon
{ {
switch(Result) switch(Result)
{ {
case 0:
return false;
case 1: case 1:
pGameContext->SendChatTarget(ClientID, "You have to be in a team (from 1-63)"); pGameContext->SendChatTarget(ClientID, "You have to be in a team (from 1-63)");
break; break;
@ -408,44 +417,19 @@ bool CSaveTeam::HandleSaveError(int Result, int ClientID, CGameContext *pGameCon
pGameContext->SendChatTarget(ClientID, "Unable to find all Characters"); pGameContext->SendChatTarget(ClientID, "Unable to find all Characters");
break; break;
case 4: case 4:
pGameContext->SendChatTarget(ClientID, "Your team is not started yet"); pGameContext->SendChatTarget(ClientID, "Your team has not started yet");
break;
default: // this state should never be reached
pGameContext->SendChatTarget(ClientID, "Unknown error while saving");
break; break;
default:
return false;
} }
return true; return true;
} }
int CSaveTeam::load(int Team) void CSaveTeam::load(int Team)
{ {
if(Team <= 0 || Team >= MAX_CLIENTS)
return 1;
CGameTeams* pTeams = &(((CGameControllerDDRace*)m_pController)->m_Teams); CGameTeams* pTeams = &(((CGameControllerDDRace*)m_pController)->m_Teams);
if(pTeams->Count(Team) > m_MembersCount)
return 2;
CCharacter *pChr;
for (int i = 0; i < m_MembersCount; i++)
{
int ID = MatchPlayer(m_pSavedTees[i].GetName());
if(ID == -1) // first check if team can be loaded / do not load half teams
{
return i+10; // +10 to leave space for other return-values
}
if(m_pController->GameServer()->m_apPlayers[ID] && m_pController->GameServer()->m_apPlayers[ID]->GetCharacter() && m_pController->GameServer()->m_apPlayers[ID]->GetCharacter()->m_DDRaceState)
{
return i+100; // +100 to leave space for other return-values
}
if(Team != pTeams->m_Core.Team(ID))
{
return i+200; // +100 to leave space for other return-values
}
}
pTeams->ChangeTeamState(Team, m_TeamState); pTeams->ChangeTeamState(Team, m_TeamState);
pTeams->SetTeamLock(Team, m_TeamLocked); pTeams->SetTeamLock(Team, m_TeamLocked);
if(m_Practice) if(m_Practice)
@ -453,14 +437,16 @@ int CSaveTeam::load(int Team)
for (int i = 0; i < m_MembersCount; i++) for (int i = 0; i < m_MembersCount; i++)
{ {
pChr = MatchCharacter(m_pSavedTees[i].GetName(), i); int ClientID = m_pSavedTees[i].GetClientID();
if(pChr) if(m_pController->GameServer()->m_apPlayers[ClientID] && pTeams->m_Core.Team(ClientID) == Team)
{ {
CCharacter *pChr = MatchCharacter(m_pSavedTees[i].GetClientID(), i);
m_pSavedTees[i].load(pChr, Team); m_pSavedTees[i].load(pChr, Team);
} }
} }
if(m_pController->GameServer()->Collision()->m_NumSwitchers) if(m_pController->GameServer()->Collision()->m_NumSwitchers)
{
for(int i=1; i < m_pController->GameServer()->Collision()->m_NumSwitchers+1; i++) for(int i=1; i < m_pController->GameServer()->Collision()->m_NumSwitchers+1; i++)
{ {
m_pController->GameServer()->Collision()->m_pSwitchers[i].m_Status[Team] = m_pSwitchers[i].m_Status; m_pController->GameServer()->Collision()->m_pSwitchers[i].m_Status[Team] = m_pSwitchers[i].m_Status;
@ -468,33 +454,15 @@ int CSaveTeam::load(int Team)
m_pController->GameServer()->Collision()->m_pSwitchers[i].m_EndTick[Team] = m_pController->Server()->Tick() - m_pSwitchers[i].m_EndTime; m_pController->GameServer()->Collision()->m_pSwitchers[i].m_EndTick[Team] = m_pController->Server()->Tick() - m_pSwitchers[i].m_EndTime;
m_pController->GameServer()->Collision()->m_pSwitchers[i].m_Type[Team] = m_pSwitchers[i].m_Type; m_pController->GameServer()->Collision()->m_pSwitchers[i].m_Type[Team] = m_pSwitchers[i].m_Type;
} }
return 0; }
} }
int CSaveTeam::MatchPlayer(char name[16]) CCharacter* CSaveTeam::MatchCharacter(int ClientID, int SaveID)
{ {
for (int i = 0; i < MAX_CLIENTS; i++) if(m_pController->GameServer()->m_apPlayers[ClientID]->GetCharacter())
{ return m_pController->GameServer()->m_apPlayers[ClientID]->GetCharacter();
if(str_comp(m_pController->Server()->ClientName(i), name) == 0)
{
return i;
}
}
return -1;
}
CCharacter* CSaveTeam::MatchCharacter(char name[16], int SaveID)
{
int ID = MatchPlayer(name);
if(ID >= 0 && m_pController->GameServer()->m_apPlayers[ID])
{
if(m_pController->GameServer()->m_apPlayers[ID]->GetCharacter())
return m_pController->GameServer()->m_apPlayers[ID]->GetCharacter();
else else
return m_pController->GameServer()->m_apPlayers[ID]->ForceSpawn(m_pSavedTees[SaveID].GetPos()); return m_pController->GameServer()->m_apPlayers[ClientID]->ForceSpawn(m_pSavedTees[SaveID].GetPos());
}
return 0;
} }
char* CSaveTeam::GetString() char* CSaveTeam::GetString()
@ -667,3 +635,48 @@ int CSaveTeam::LoadString(const char* String)
return 0; return 0;
} }
bool CSaveTeam::MatchPlayers(const char (*paNames)[MAX_NAME_LENGTH], const int *pClientID, int NumPlayer, char *pMessage, int MessageLen)
{
if(NumPlayer > m_MembersCount)
{
str_format(pMessage, MessageLen, "Too many players in this team, should be %d", m_MembersCount);
return false;
}
// check for wrong players
for(int i = 0; i < NumPlayer; i++)
{
int Found = false;
for(int j = 0; j < m_MembersCount; j++)
{
if(strcmp(paNames[i], m_pSavedTees[j].GetName()) == 0)
{
Found = true;
}
}
if(!Found)
{
str_format(pMessage, MessageLen, "'%s' don't belong to this team", paNames[i]);
return false;
}
}
// check for missing players
for(int i = 0; i < m_MembersCount; i++)
{
int Found = false;
for(int j = 0; j < NumPlayer; j++)
{
if(strcmp(m_pSavedTees[i].GetName(), paNames[j]) == 0)
{
m_pSavedTees[i].SetClientID(pClientID[j]);
Found = true;
}
}
if(!Found)
{
str_format(pMessage, MessageLen, "'%s' has to be in this team", m_pSavedTees[i].GetName());
return false;
}
}
return true;
}

View file

@ -1,8 +1,9 @@
#ifndef GAME_SERVER_SAVE_H #ifndef GAME_SERVER_SAVE_H
#define GAME_SERVER_SAVE_H #define GAME_SERVER_SAVE_H
#include "./entities/character.h" #include "entities/character.h"
#include <game/server/gamecontroller.h> class IGameController;
class CGameContext;
class CSaveTee class CSaveTee
{ {
@ -12,11 +13,14 @@ public:
void save(CCharacter* pchr); void save(CCharacter* pchr);
void load(CCharacter* pchr, int Team); void load(CCharacter* pchr, int Team);
char* GetString(); char* GetString();
int LoadString(char* String); int LoadString(const char* String);
vec2 GetPos() { return m_Pos; } vec2 GetPos() const { return m_Pos; }
char* GetName() { return m_aName; } const char* GetName() const { return m_aName; }
int GetClientID() const { return m_ClientID; }
void SetClientID(int ClientID) { m_ClientID = ClientID; };
private: private:
int m_ClientID;
char m_aString [2048]; char m_aString [2048];
char m_aName [16]; char m_aName [16];
@ -94,16 +98,19 @@ public:
CSaveTeam(IGameController* Controller); CSaveTeam(IGameController* Controller);
~CSaveTeam(); ~CSaveTeam();
char* GetString(); char* GetString();
int GetMembersCount() { return m_MembersCount; } int GetMembersCount() const { return m_MembersCount; }
// MatchPlayers has to be called afterwards
int LoadString(const char* String); int LoadString(const char* String);
// returns true if a team can load, otherwise writes a nice error Message in pMessage
bool MatchPlayers(const char (*paNames)[MAX_NAME_LENGTH], const int *pClientID, int NumPlayer, char *pMessage, int MessageLen);
int save(int Team); int save(int Team);
int load(int Team); void load(int Team);
CSaveTee* m_pSavedTees; CSaveTee* m_pSavedTees;
// returns true if an error occured
static bool HandleSaveError(int Result, int ClientID, CGameContext *pGameContext); static bool HandleSaveError(int Result, int ClientID, CGameContext *pGameContext);
private: private:
int MatchPlayer(char name[16]); CCharacter* MatchCharacter(int ClientID, int SaveID);
CCharacter* MatchCharacter(char name[16], int SaveID);
IGameController* m_pController; IGameController* m_pController;

View file

@ -2,9 +2,9 @@
#define GAME_SERVER_SCORE_H #define GAME_SERVER_SCORE_H
#include <memory> #include <memory>
#include <atomic>
#include "entities/character.h" #include "save.h"
#include "gamecontext.h"
enum enum
{ {
@ -19,6 +19,7 @@ public:
{ {
Reset(); Reset();
} }
~CPlayerData() {}
void Reset() void Reset()
{ {
@ -31,6 +32,7 @@ public:
void Set(float Time, float CpTime[NUM_CHECKPOINTS]) void Set(float Time, float CpTime[NUM_CHECKPOINTS])
{ {
m_BestTime = Time; m_BestTime = Time;
m_CurrentTime = Time;
for(int i = 0; i < NUM_CHECKPOINTS; i++) for(int i = 0; i < NUM_CHECKPOINTS; i++)
m_aBestCpTime[i] = CpTime[i]; m_aBestCpTime[i] = CpTime[i];
} }
@ -40,28 +42,6 @@ public:
float m_aBestCpTime[NUM_CHECKPOINTS]; float m_aBestCpTime[NUM_CHECKPOINTS];
}; };
// Watch this: TODO(2019-05-20): Temporary fix for the random maps race
// condition. See you in ten years.
class CRandomMapResult
{
public:
bool m_Done;
char m_aMap[64];
CRandomMapResult() : m_Done(false) {}
};
class CMapVoteResult
{
public:
bool m_Done;
char m_aMap[64];
char m_aServer[32];
int m_ClientID;
CMapVoteResult() : m_Done(false) {}
};
class IScore class IScore
{ {
CPlayerData m_aPlayerData[MAX_CLIENTS]; CPlayerData m_aPlayerData[MAX_CLIENTS];
@ -72,26 +52,25 @@ public:
CPlayerData *PlayerData(int ID) { return &m_aPlayerData[ID]; } CPlayerData *PlayerData(int ID) { return &m_aPlayerData[ID]; }
virtual void MapInfo(int ClientID, const char *pMapName) = 0; virtual void MapInfo(int ClientID, const char *pMapName) = 0;
virtual void MapVote(std::shared_ptr<CMapVoteResult> *ppResult, int ClientID, const char *pMapName) = 0; virtual void MapVote(int ClientID, const char *pMapName) = 0;
virtual void CheckBirthday(int ClientID) = 0; virtual void LoadPlayerData(int ClientID) = 0;
virtual void LoadScore(int ClientID) = 0;
virtual void SaveScore(int ClientID, float Time, const char *pTimestamp, float aCpTime[NUM_CHECKPOINTS], bool NotEligible) = 0; virtual void SaveScore(int ClientID, float Time, const char *pTimestamp, float aCpTime[NUM_CHECKPOINTS], bool NotEligible) = 0;
virtual void SaveTeamScore(int *pClientIDs, unsigned int Size, float Time, const char *pTimestamp) = 0; virtual void SaveTeamScore(int *pClientIDs, unsigned int Size, float Time, const char *pTimestamp) = 0;
virtual void ShowTop5(IConsole::IResult *pResult, int ClientID, void *pUserData, int Debut=1) = 0; virtual void ShowTop5(int ClientID, int Offset=1) = 0;
virtual void ShowRank(int ClientID, const char *pName, bool Search=false) = 0; virtual void ShowRank(int ClientID, const char *pName) = 0;
virtual void ShowTeamTop5(IConsole::IResult *pResult, int ClientID, void *pUserData, int Debut=1) = 0; virtual void ShowTeamTop5(int ClientID, int Offset=1) = 0;
virtual void ShowTeamRank(int ClientID, const char *pName, bool Search=false) = 0; virtual void ShowTeamRank(int ClientID, const char *pName) = 0;
virtual void ShowTopPoints(IConsole::IResult *pResult, int ClientID, void *pUserData, int Debut=1) = 0; virtual void ShowTopPoints(int ClientID, int Offset=1) = 0;
virtual void ShowPoints(int ClientID, const char *pName, bool Search=false) = 0; virtual void ShowPoints(int ClientID, const char *pName) = 0;
virtual void RandomMap(std::shared_ptr<CRandomMapResult> *ppResult, int ClientID, int Stars) = 0; virtual void RandomMap(int ClientID, int Stars) = 0;
virtual void RandomUnfinishedMap(std::shared_ptr<CRandomMapResult> *ppResult, int ClientID, int Stars) = 0; virtual void RandomUnfinishedMap(int ClientID, int Stars) = 0;
virtual void SaveTeam(int Team, const char *pCode, int ClientID, const char *pServer) = 0; virtual void SaveTeam(int ClientID, const char *pCode, const char *pServer) = 0;
virtual void LoadTeam(const char *pCode, int ClientID) = 0; virtual void LoadTeam(const char *pCode, int ClientID) = 0;
virtual void GetSaves(int ClientID) = 0; virtual void GetSaves(int ClientID) = 0;

View file

@ -58,7 +58,7 @@ void CFileScore::MapInfo(int ClientID, const char* MapName)
// TODO: implement // TODO: implement
} }
void CFileScore::MapVote(std::shared_ptr<CMapVoteResult> *ppResult, int ClientID, const char* MapName) void CFileScore::MapVote(int ClientID, const char* MapName)
{ {
// TODO: implement // TODO: implement
} }
@ -205,12 +205,7 @@ void CFileScore::UpdatePlayer(int ID, float Score,
Save(); Save();
} }
void CFileScore::CheckBirthday(int ClientID) void CFileScore::LoadPlayerData(int ClientID)
{
// TODO: implement
}
void CFileScore::LoadScore(int ClientID)
{ {
CPlayerScore *pPlayer = SearchScore(ClientID, 0); CPlayerScore *pPlayer = SearchScore(ClientID, 0);
if (pPlayer) if (pPlayer)
@ -241,36 +236,31 @@ void CFileScore::SaveScore(int ClientID, float Time, const char *pTimestamp,
UpdatePlayer(ClientID, Time, CpTime); UpdatePlayer(ClientID, Time, CpTime);
} }
void CFileScore::ShowTop5(IConsole::IResult *pResult, int ClientID, void CFileScore::ShowTop5(int ClientID, int Offset)
void *pUserData, int Debut)
{ {
CGameContext *pSelf = (CGameContext *) pUserData;
char aBuf[512]; char aBuf[512];
Debut = maximum(1, Debut < 0 ? m_Top.size() + Debut - 3 : Debut); Offset = maximum(1, Offset < 0 ? m_Top.size() + Offset - 3 : Offset);
pSelf->SendChatTarget(ClientID, "----------- Top 5 -----------"); GameServer()->SendChatTarget(ClientID, "----------- Top 5 -----------");
for (int i = 0; i < 5; i++) for (int i = 0; i < 5; i++)
{ {
if (i + Debut > m_Top.size()) if (i + Offset > m_Top.size())
break; break;
CPlayerScore *r = &m_Top[i + Debut - 1]; CPlayerScore *r = &m_Top[i + Offset - 1];
str_format(aBuf, sizeof(aBuf), str_format(aBuf, sizeof(aBuf),
"%d. %s Time: %d minute(s) %5.2f second(s)", i + Debut, "%d. %s Time: %d minute(s) %5.2f second(s)", i + Offset,
r->m_aName, (int)r->m_Score / 60, r->m_aName, (int)r->m_Score / 60,
r->m_Score - ((int)r->m_Score / 60 * 60)); r->m_Score - ((int)r->m_Score / 60 * 60));
pSelf->SendChatTarget(ClientID, aBuf); GameServer()->SendChatTarget(ClientID, aBuf);
} }
pSelf->SendChatTarget(ClientID, "------------------------------"); GameServer()->SendChatTarget(ClientID, "------------------------------");
} }
void CFileScore::ShowRank(int ClientID, const char* pName, bool Search) void CFileScore::ShowRank(int ClientID, const char* pName)
{ {
CPlayerScore *pScore; CPlayerScore *pScore;
int Pos = -2; int Pos = -2;
char aBuf[512]; char aBuf[512];
if (!Search)
pScore = SearchScore(ClientID, &Pos);
else
pScore = SearchName(pName, &Pos, 1); pScore = SearchName(pName, &Pos, 1);
if (pScore && Pos > -1) if (pScore && Pos > -1)
@ -285,66 +275,63 @@ void CFileScore::ShowRank(int ClientID, const char* pName, bool Search)
"%d. %s Time: %d minute(s) %5.2f second(s), requested by (%s)", Pos, "%d. %s Time: %d minute(s) %5.2f second(s), requested by (%s)", Pos,
pScore->m_aName, (int)Time / 60, pScore->m_aName, (int)Time / 60,
Time - ((int)Time / 60 * 60), Server()->ClientName(ClientID)); Time - ((int)Time / 60 * 60), Server()->ClientName(ClientID));
if (!Search) if (g_Config.m_SvHideScore)
GameServer()->SendChat(-1, CGameContext::CHAT_ALL, aBuf, ClientID);
else
GameServer()->SendChatTarget(ClientID, aBuf); GameServer()->SendChatTarget(ClientID, aBuf);
else
GameServer()->SendChat(-1, CGameContext::CHAT_ALL, aBuf, ClientID);
return; return;
} }
else if (Pos == -1) else if (Pos == -1)
str_format(aBuf, sizeof(aBuf), "Several players were found."); str_format(aBuf, sizeof(aBuf), "Several players were found.");
else else
str_format(aBuf, sizeof(aBuf), "%s is not ranked", str_format(aBuf, sizeof(aBuf), "%s is not ranked", pName);
Search ? pName : Server()->ClientName(ClientID));
GameServer()->SendChatTarget(ClientID, aBuf); GameServer()->SendChatTarget(ClientID, aBuf);
} }
void CFileScore::ShowTeamTop5(IConsole::IResult *pResult, int ClientID, void *pUserData, int Debut) void CFileScore::ShowTeamTop5(int ClientID, int Offset)
{ {
char aBuf[512]; char aBuf[512];
str_format(aBuf, sizeof(aBuf), "Team ranks not supported in file based servers"); str_format(aBuf, sizeof(aBuf), "Team ranks not supported in file based servers");
GameServer()->SendChatTarget(ClientID, aBuf); GameServer()->SendChatTarget(ClientID, aBuf);
} }
void CFileScore::ShowTeamRank(int ClientID, const char* pName, bool Search) void CFileScore::ShowTeamRank(int ClientID, const char* pName)
{ {
char aBuf[512]; char aBuf[512];
str_format(aBuf, sizeof(aBuf), "Team ranks not supported in file based servers"); str_format(aBuf, sizeof(aBuf), "Team ranks not supported in file based servers");
GameServer()->SendChatTarget(ClientID, aBuf); GameServer()->SendChatTarget(ClientID, aBuf);
} }
void CFileScore::ShowTopPoints(IConsole::IResult *pResult, int ClientID, void *pUserData, int Debut) void CFileScore::ShowTopPoints(int ClientID, int Offset)
{ {
char aBuf[512]; char aBuf[512];
str_format(aBuf, sizeof(aBuf), "Team ranks not supported in file based servers"); str_format(aBuf, sizeof(aBuf), "Team ranks not supported in file based servers");
GameServer()->SendChatTarget(ClientID, aBuf); GameServer()->SendChatTarget(ClientID, aBuf);
} }
void CFileScore::ShowPoints(int ClientID, const char* pName, bool Search) void CFileScore::ShowPoints(int ClientID, const char* pName)
{ {
char aBuf[512]; char aBuf[512];
str_format(aBuf, sizeof(aBuf), "Points not supported in file based servers"); str_format(aBuf, sizeof(aBuf), "Points not supported in file based servers");
GameServer()->SendChatTarget(ClientID, aBuf); GameServer()->SendChatTarget(ClientID, aBuf);
} }
void CFileScore::RandomMap(std::shared_ptr<CRandomMapResult> *ppResult, int ClientID, int stars) void CFileScore::RandomMap(int ClientID, int Stars)
{ {
char aBuf[512]; char aBuf[512];
str_format(aBuf, sizeof(aBuf), "Random map not supported in file based servers"); str_format(aBuf, sizeof(aBuf), "Random map not supported in file based servers");
GameServer()->SendChatTarget(ClientID, aBuf); GameServer()->SendChatTarget(ClientID, aBuf);
*ppResult = NULL;
} }
void CFileScore::RandomUnfinishedMap(std::shared_ptr<CRandomMapResult> *ppResult, int ClientID, int stars) void CFileScore::RandomUnfinishedMap(int ClientID, int Stars)
{ {
char aBuf[512]; char aBuf[512];
str_format(aBuf, sizeof(aBuf), "Random unfinished map not supported in file based servers"); str_format(aBuf, sizeof(aBuf), "Random unfinished map not supported in file based servers");
GameServer()->SendChatTarget(ClientID, aBuf); GameServer()->SendChatTarget(ClientID, aBuf);
*ppResult = NULL;
} }
void CFileScore::SaveTeam(int Team, const char* Code, int ClientID, const char* Server) void CFileScore::SaveTeam(int ClientID, const char* Code, const char* Server)
{ {
char aBuf[512]; char aBuf[512];
str_format(aBuf, sizeof(aBuf), "Save-function not supported in file based servers"); str_format(aBuf, sizeof(aBuf), "Save-function not supported in file based servers");

View file

@ -21,10 +21,8 @@ class CFileScore: public IScore
float m_Score; float m_Score;
float m_aCpTime[NUM_CHECKPOINTS]; float m_aCpTime[NUM_CHECKPOINTS];
CPlayerScore() CPlayerScore() {}
{
}
;
CPlayerScore(const char *pName, float Score, CPlayerScore(const char *pName, float Score,
float aCpTime[NUM_CHECKPOINTS]); float aCpTime[NUM_CHECKPOINTS]);
@ -49,7 +47,6 @@ class CFileScore: public IScore
{ {
return SearchName(Server()->ClientName(ID), pPosition, 0); return SearchName(Server()->ClientName(ID), pPosition, 0);
} }
;
CPlayerScore *SearchName(const char *pName, int *pPosition, bool MatchCase); CPlayerScore *SearchName(const char *pName, int *pPosition, bool MatchCase);
void UpdatePlayer(int ID, float Score, float aCpTime[NUM_CHECKPOINTS]); void UpdatePlayer(int ID, float Score, float aCpTime[NUM_CHECKPOINTS]);
@ -63,27 +60,24 @@ public:
CFileScore(CGameContext *pGameServer); CFileScore(CGameContext *pGameServer);
~CFileScore(); ~CFileScore();
virtual void CheckBirthday(int ClientID); virtual void LoadPlayerData(int ClientID);
virtual void LoadScore(int ClientID);
virtual void MapInfo(int ClientID, const char* MapName); virtual void MapInfo(int ClientID, const char* MapName);
virtual void MapVote(std::shared_ptr<CMapVoteResult> *ppResult, int ClientID, const char* MapName); virtual void MapVote(int ClientID, const char* MapName);
virtual void SaveScore(int ClientID, float Time, const char *pTimestamp, virtual void SaveScore(int ClientID, float Time, const char *pTimestamp,
float CpTime[NUM_CHECKPOINTS], bool NotEligible); float CpTime[NUM_CHECKPOINTS], bool NotEligible);
virtual void SaveTeamScore(int* ClientIDs, unsigned int Size, float Time, const char *pTimestamp); virtual void SaveTeamScore(int* ClientIDs, unsigned int Size, float Time, const char *pTimestamp);
virtual void ShowTop5(IConsole::IResult *pResult, int ClientID, virtual void ShowTop5(int ClientID, int Offset = 1);
void *pUserData, int Debut = 1); virtual void ShowRank(int ClientID, const char* pName);
virtual void ShowRank(int ClientID, const char* pName, bool Search = false);
virtual void ShowTeamTop5(IConsole::IResult *pResult, int ClientID, virtual void ShowTeamTop5(int ClientID, int Offset = 1);
void *pUserData, int Debut = 1); virtual void ShowTeamRank(int ClientID, const char* pName);
virtual void ShowTeamRank(int ClientID, const char* pName, bool Search = false);
virtual void ShowTopPoints(IConsole::IResult *pResult, int ClientID, void *pUserData, int Debut); virtual void ShowTopPoints(int ClientID, int Offset);
virtual void ShowPoints(int ClientID, const char* pName, bool Search); virtual void ShowPoints(int ClientID, const char* pName);
virtual void RandomMap(std::shared_ptr<CRandomMapResult> *ppResult, int ClientID, int stars); virtual void RandomMap(int ClientID, int Stars);
virtual void RandomUnfinishedMap(std::shared_ptr<CRandomMapResult> *ppResult, int ClientID, int stars); virtual void RandomUnfinishedMap(int ClientID, int Stars);
virtual void SaveTeam(int Team, const char* Code, int ClientID, const char* Server); virtual void SaveTeam(int ClientID, const char* Code, const char* Server);
virtual void LoadTeam(const char* Code, int ClientID); virtual void LoadTeam(const char* Code, int ClientID);
virtual void GetSaves(int ClientID); virtual void GetSaves(int ClientID);

File diff suppressed because it is too large Load diff

View file

@ -1,113 +1,146 @@
/* (c) Shereef Marzouk. See "licence DDRace.txt" and the readme.txt in the root of the distribution for more information. */ /* (c) Shereef Marzouk. See "licence DDRace.txt" and the readme.txt in the root of the distribution for more information. */
/* Based on Race mod stuff and tweaked by GreYFoX@GTi and others to fit our DDRace needs. */ /* Based on Race mod stuff and tweaked by GreYFoX@GTi and others to fit our DDRace needs. */
/* CSqlScore Class by Sushi Tee*/ /* CSqlScore Class by Sushi Tee*/
#ifndef GAME_SERVER_SCORE_SQL_SCORE_H #ifndef GAME_SERVER_SCORE_SQL_H
#define GAME_SERVER_SCORE_SQL_SCORE_H #define GAME_SERVER_SCORE_SQL_H
#include <exception>
#include <base/system.h>
#include <engine/console.h>
#include <engine/server/sql_connector.h>
#include <engine/server/sql_string_helpers.h> #include <engine/server/sql_string_helpers.h>
#include <game/prng.h>
#include <game/voting.h>
#include "../score.h" #include "../score.h"
class CSqlServer;
class CGameContextError : public std::runtime_error struct CSqlPlayerResult
{ {
public: std::atomic_bool m_Done;
CGameContextError(const char* pMsg) : std::runtime_error(pMsg) {} CSqlPlayerResult();
enum Variant
{
DIRECT,
ALL,
BROADCAST,
MAP_VOTE,
PLAYER_INFO,
} m_MessageKind;
char m_aaMessages[7][512];
union {
char m_Broadcast[1024];
struct {
float m_Time;
float m_CpTime[NUM_CHECKPOINTS];
int m_Score;
int m_HasFinishScore;
int m_Birthday; // 0 indicates no birthday
} m_Info;
struct
{
char m_Reason[VOTE_REASON_LENGTH];
char m_Server[32+1];
char m_Map[128+1];
} m_MapVote;
} m_Data; // PLAYER_INFO
void SetVariant(Variant v);
}; };
struct CSqlRandomMapResult
{
std::atomic_bool m_Done;
CSqlRandomMapResult() :
m_Done(false)
{
m_Map[0] = '\0';
m_aMessage[0] = '\0';
}
char m_Map[128];
char m_aMessage[512];
};
// generic implementation to provide gameserver and server struct CSqlSaveResult {
CSqlSaveResult(int PlayerID, IGameController* Controller) :
m_Status(SAVE_FAILED),
m_SavedTeam(CSaveTeam(Controller)),
m_RequestingPlayer(PlayerID)
{
m_aMessage[0] = '\0';
m_aBroadcast[0] = '\0';
}
enum
{
SAVE_SUCCESS,
// load team in the following two cases
SAVE_FAILED,
LOAD_SUCCESS,
LOAD_FAILED,
} m_Status;
char m_aMessage[512];
char m_aBroadcast[512];
CSaveTeam m_SavedTeam;
int m_RequestingPlayer;
CUuid m_SaveID;
};
struct CSqlInitResult
{
CSqlInitResult() :
m_Done(false),
m_CurrentRecord(0)
{ }
std::atomic_bool m_Done;
float m_CurrentRecord;
};
// holding relevant data for one thread, and function pointer for return values
template < typename TResult >
struct CSqlData struct CSqlData
{ {
CSqlData() : m_Map(ms_pMap), m_GameUuid(ms_pGameUuid) CSqlData(std::shared_ptr<TResult> pSqlResult) :
m_pResult(pSqlResult)
{ }
std::shared_ptr<TResult> m_pResult;
virtual ~CSqlData() = default;
};
struct CSqlInitData : CSqlData<CSqlInitResult>
{ {
m_Instance = ms_Instance; using CSqlData<CSqlInitResult>::CSqlData;
} // current map
virtual ~CSqlData() {}
bool isGameContextVaild() const
{
return m_Instance == ms_Instance && ms_GameContextAvailable;
}
CGameContext* GameServer() const { return isGameContextVaild() ? ms_pGameServer : throw CGameContextError("[CSqlData]: GameServer() unavailable."); }
IServer* Server() const { return isGameContextVaild() ? ms_pServer : throw CGameContextError("[CSqlData]: Server() unavailable."); }
CPlayerData* PlayerData(int ID) const { return isGameContextVaild() ? &ms_pPlayerData[ID] : throw CGameContextError("[CSqlData]: PlayerData() unavailable."); }
sqlstr::CSqlString<128> m_Map; sqlstr::CSqlString<128> m_Map;
sqlstr::CSqlString<UUID_MAXSTRSIZE> m_GameUuid;
// counter to keep track to which instance of GameServer this object belongs to.
int m_Instance;
static CGameContext *ms_pGameServer;
static IServer *ms_pServer;
static CPlayerData *ms_pPlayerData;
static const char *ms_pMap;
static const char *ms_pGameUuid;
static bool ms_GameContextAvailable;
// contains the instancecount of the current GameServer
static int ms_Instance;
}; };
struct CSqlExecData struct CSqlPlayerRequest : CSqlData<CSqlPlayerResult>
{ {
CSqlExecData(bool (*pFuncPtr) (CSqlServer*, const CSqlData *, bool), CSqlData *pSqlData, bool ReadOnly = true) : using CSqlData<CSqlPlayerResult>::CSqlData;
m_pFuncPtr(pFuncPtr), // object being requested, either map (128 bytes) or player (16 bytes)
m_pSqlData(pSqlData), sqlstr::CSqlString<128> m_Name;
m_ReadOnly(ReadOnly) // current map
{ sqlstr::CSqlString<128> m_Map;
++ms_InstanceCount; sqlstr::CSqlString<MAX_NAME_LENGTH> m_RequestingPlayer;
} // relevant for /top5 kind of requests
~CSqlExecData() int m_Offset;
{
--ms_InstanceCount;
}
bool (*m_pFuncPtr) (CSqlServer*, const CSqlData *, bool);
CSqlData *m_pSqlData;
bool m_ReadOnly;
// keeps track of score-threads
volatile static int ms_InstanceCount;
}; };
struct CSqlPlayerData : CSqlData struct CSqlRandomMapRequest : CSqlData<CSqlRandomMapResult>
{ {
int m_ClientID; using CSqlData<CSqlRandomMapResult>::CSqlData;
sqlstr::CSqlString<MAX_NAME_LENGTH> m_Name; sqlstr::CSqlString<32> m_ServerType;
sqlstr::CSqlString<32> m_CurrentMap;
sqlstr::CSqlString<MAX_NAME_LENGTH> m_RequestingPlayer;
int m_Stars;
}; };
// used for mapinfo struct CSqlScoreData : CSqlData<CSqlPlayerResult>
struct CSqlMapData : CSqlData
{ {
int m_ClientID; using CSqlData<CSqlPlayerResult>::CSqlData;
sqlstr::CSqlString<128> m_RequestedMap;
char m_aFuzzyMap[128];
sqlstr::CSqlString<MAX_NAME_LENGTH> m_Name;
};
// used for mapvote
struct CSqlMapVoteData : CSqlMapData
{
std::shared_ptr<CMapVoteResult> m_pResult;
};
struct CSqlScoreData : CSqlData
{
int m_ClientID;
sqlstr::CSqlString<MAX_NAME_LENGTH> m_Map;
char m_GameUuid[UUID_MAXSTRSIZE];
sqlstr::CSqlString<MAX_NAME_LENGTH> m_Name; sqlstr::CSqlString<MAX_NAME_LENGTH> m_Name;
bool m_NotEligible; int m_ClientID;
float m_Time; float m_Time;
char m_aTimestamp[TIMESTAMP_STR_LENGTH]; char m_aTimestamp[TIMESTAMP_STR_LENGTH];
float m_aCpCurrent[NUM_CHECKPOINTS]; float m_aCpCurrent[NUM_CHECKPOINTS];
@ -116,111 +149,147 @@ struct CSqlScoreData : CSqlData
char m_aRequestingPlayer[MAX_NAME_LENGTH]; char m_aRequestingPlayer[MAX_NAME_LENGTH];
}; };
struct CSqlTeamScoreData : CSqlData struct CSqlTeamScoreData : CSqlData<void>
{ {
bool m_NotEligible; using CSqlData<void>::CSqlData;
char m_GameUuid[UUID_MAXSTRSIZE];
sqlstr::CSqlString<MAX_NAME_LENGTH> m_Map;
float m_Time; float m_Time;
char m_aTimestamp[TIMESTAMP_STR_LENGTH]; char m_aTimestamp[TIMESTAMP_STR_LENGTH];
unsigned int m_Size; unsigned int m_Size;
int m_aClientIDs[MAX_CLIENTS];
sqlstr::CSqlString<MAX_NAME_LENGTH> m_aNames[MAX_CLIENTS]; sqlstr::CSqlString<MAX_NAME_LENGTH> m_aNames[MAX_CLIENTS];
}; };
struct CSqlTeamSave : CSqlData struct CSqlTeamSave : CSqlData<CSqlSaveResult>
{ {
virtual ~CSqlTeamSave(); using CSqlData<CSqlSaveResult>::CSqlData;
virtual ~CSqlTeamSave() {};
int m_Team;
int m_ClientID;
char m_ClientName[MAX_NAME_LENGTH]; char m_ClientName[MAX_NAME_LENGTH];
sqlstr::CSqlString<128> m_Code;
char m_Map[128];
char m_Code[128];
char m_aGeneratedCode[128];
char m_Server[5]; char m_Server[5];
}; };
struct CSqlTeamLoad : CSqlData struct CSqlTeamLoad : CSqlData<CSqlSaveResult>
{ {
using CSqlData<CSqlSaveResult>::CSqlData;
sqlstr::CSqlString<128> m_Code; sqlstr::CSqlString<128> m_Code;
sqlstr::CSqlString<128> m_Map;
sqlstr::CSqlString<MAX_NAME_LENGTH> m_RequestingPlayer;
int m_ClientID; int m_ClientID;
char m_ClientName[MAX_NAME_LENGTH]; // struct holding all player names in the team or an empty string
char m_aClientNames[MAX_CLIENTS][MAX_NAME_LENGTH];
int m_aClientID[MAX_CLIENTS];
int m_NumPlayer;
}; };
struct CSqlGetSavesData: CSqlData // controls one thread
template < typename TResult >
struct CSqlExecData
{ {
int m_ClientID; CSqlExecData(
sqlstr::CSqlString<MAX_NAME_LENGTH> m_Name; bool (*pFuncPtr) (CSqlServer*, const CSqlData<TResult> *, bool),
CSqlData<TResult> *pSqlResult,
bool ReadOnly = true
);
~CSqlExecData();
bool (*m_pFuncPtr) (CSqlServer*, const CSqlData<TResult> *, bool);
CSqlData<TResult> *m_pSqlData;
bool m_ReadOnly;
static void ExecSqlFunc(void *pUser);
}; };
struct CSqlRandomMap : CSqlScoreData class IServer;
{ class CGameContext;
std::shared_ptr<CRandomMapResult> m_pResult;
};
class CSqlScore: public IScore class CSqlScore: public IScore
{ {
static LOCK ms_FailureFileLock;
static bool Init(CSqlServer* pSqlServer, const CSqlData<CSqlInitResult> *pGameData, bool HandleFailure);
static bool RandomMapThread(CSqlServer* pSqlServer, const CSqlData<CSqlRandomMapResult> *pGameData, bool HandleFailure = false);
static bool RandomUnfinishedMapThread(CSqlServer* pSqlServer, const CSqlData<CSqlRandomMapResult> *pGameData, bool HandleFailure = false);
static bool MapVoteThread(CSqlServer* pSqlServer, const CSqlData<CSqlPlayerResult> *pGameData, bool HandleFailure = false);
static bool LoadPlayerDataThread(CSqlServer* pSqlServer, const CSqlData<CSqlPlayerResult> *pGameData, bool HandleFailure = false);
static bool MapInfoThread(CSqlServer* pSqlServer, const CSqlData<CSqlPlayerResult> *pGameData, bool HandleFailure = false);
static bool ShowRankThread(CSqlServer* pSqlServer, const CSqlData<CSqlPlayerResult> *pGameData, bool HandleFailure = false);
static bool ShowTeamRankThread(CSqlServer* pSqlServer, const CSqlData<CSqlPlayerResult> *pGameData, bool HandleFailure = false);
static bool ShowTop5Thread(CSqlServer* pSqlServer, const CSqlData<CSqlPlayerResult> *pGameData, bool HandleFailure = false);
static bool ShowTeamTop5Thread(CSqlServer* pSqlServer, const CSqlData<CSqlPlayerResult> *pGameData, bool HandleFailure = false);
static bool ShowTimesThread(CSqlServer* pSqlServer, const CSqlData<CSqlPlayerResult> *pGameData, bool HandleFailure = false);
static bool ShowPointsThread(CSqlServer* pSqlServer, const CSqlData<CSqlPlayerResult> *pGameData, bool HandleFailure = false);
static bool ShowTopPointsThread(CSqlServer* pSqlServer, const CSqlData<CSqlPlayerResult> *pGameData, bool HandleFailure = false);
static bool GetSavesThread(CSqlServer* pSqlServer, const CSqlData<CSqlPlayerResult> *pGameData, bool HandleFailure = false);
static bool SaveTeamThread(CSqlServer* pSqlServer, const CSqlData<CSqlSaveResult> *pGameData, bool HandleFailure = false);
static bool LoadTeamThread(CSqlServer* pSqlServer, const CSqlData<CSqlSaveResult> *pGameData, bool HandleFailure = false);
static bool SaveScoreThread(CSqlServer* pSqlServer, const CSqlData<CSqlPlayerResult> *pGameData, bool HandleFailure = false);
static bool SaveTeamScoreThread(CSqlServer* pSqlServer, const CSqlData<void> *pGameData, bool HandleFailure = false);
CGameContext *GameServer() { return m_pGameServer; } CGameContext *GameServer() { return m_pGameServer; }
IServer *Server() { return m_pServer; } IServer *Server() { return m_pServer; }
CGameContext *m_pGameServer; CGameContext *m_pGameServer;
IServer *m_pServer; IServer *m_pServer;
static void ExecSqlFunc(void *pUser); std::vector<std::string> m_aWordlist;
CPrng m_Prng;
void GeneratePassphrase(char *pBuf, int BufSize);
static bool Init(CSqlServer* pSqlServer, const CSqlData *pGameData, bool HandleFailure); // returns new SqlResult bound to the player, if no current Thread is active for this player
std::shared_ptr<CSqlPlayerResult> NewSqlPlayerResult(int ClientID);
char m_aMap[64]; // Creates for player database requests
char m_aGameUuid[UUID_MAXSTRSIZE]; void ExecPlayerThread(
bool (*pFuncPtr) (CSqlServer*, const CSqlData<CSqlPlayerResult> *, bool),
static LOCK ms_FailureFileLock; const char* pThreadName,
int ClientID,
static bool CheckBirthdayThread(CSqlServer* pSqlServer, const CSqlData *pGameData, bool HandleFailure = false); const char* pName,
static bool MapInfoThread(CSqlServer* pSqlServer, const CSqlData *pGameData, bool HandleFailure = false); int Offset
static bool MapVoteThread(CSqlServer* pSqlServer, const CSqlData *pGameData, bool HandleFailure = false); );
static bool LoadScoreThread(CSqlServer* pSqlServer, const CSqlData *pGameData, bool HandleFailure = false);
static bool SaveScoreThread(CSqlServer* pSqlServer, const CSqlData *pGameData, bool HandleFailure = false);
static bool SaveTeamScoreThread(CSqlServer* pSqlServer, const CSqlData *pGameData, bool HandleFailure = false);
static bool ShowRankThread(CSqlServer* pSqlServer, const CSqlData *pGameData, bool HandleFailure = false);
static bool ShowTop5Thread(CSqlServer* pSqlServer, const CSqlData *pGameData, bool HandleFailure = false);
static bool ShowTeamRankThread(CSqlServer* pSqlServer, const CSqlData *pGameData, bool HandleFailure = false);
static bool ShowTeamTop5Thread(CSqlServer* pSqlServer, const CSqlData *pGameData, bool HandleFailure = false);
static bool ShowTimesThread(CSqlServer* pSqlServer, const CSqlData *pGameData, bool HandleFailure = false);
static bool ShowPointsThread(CSqlServer* pSqlServer, const CSqlData *pGameData, bool HandleFailure = false);
static bool ShowTopPointsThread(CSqlServer* pSqlServer, const CSqlData *pGameData, bool HandleFailure = false);
static bool RandomMapThread(CSqlServer* pSqlServer, const CSqlData *pGameData, bool HandleFailure = false);
static bool RandomUnfinishedMapThread(CSqlServer* pSqlServer, const CSqlData *pGameData, bool HandleFailure = false);
static bool SaveTeamThread(CSqlServer* pSqlServer, const CSqlData *pGameData, bool HandleFailure = false);
static bool LoadTeamThread(CSqlServer* pSqlServer, const CSqlData *pGameData, bool HandleFailure = false);
static bool GetSavesThread(CSqlServer* pSqlServer, const CSqlData *pGameData, bool HandleFailure = false);
public: public:
// keeps track of score-threads
static std::atomic_int ms_InstanceCount;
CSqlScore(CGameContext *pGameServer); CSqlScore(CGameContext *pGameServer);
~CSqlScore(); ~CSqlScore() {}
virtual void CheckBirthday(int ClientID); // Requested by game context, shouldn't fail in case the player started another thread
virtual void LoadScore(int ClientID); virtual void RandomMap(int ClientID, int Stars);
virtual void RandomUnfinishedMap(int ClientID, int Stars);
virtual void MapVote(int ClientID, const char* MapName);
virtual void LoadPlayerData(int ClientID);
// Requested by players (fails if another request by this player is active)
virtual void MapInfo(int ClientID, const char* MapName); virtual void MapInfo(int ClientID, const char* MapName);
virtual void MapVote(std::shared_ptr<CMapVoteResult> *ppResult, int ClientID, const char* MapName); virtual void ShowRank(int ClientID, const char* pName);
virtual void ShowTeamRank(int ClientID, const char* pName);
virtual void ShowPoints(int ClientID, const char* pName);
virtual void ShowTimes(int ClientID, const char* pName, int Offset = 1);
virtual void ShowTimes(int ClientID, int Offset = 1);
virtual void ShowTop5(int ClientID, int Offset = 1);
virtual void ShowTeamTop5(int ClientID, int Offset = 1);
virtual void ShowTopPoints(int ClientID, int Offset = 1);
virtual void GetSaves(int ClientID);
// requested by teams
virtual void SaveTeam(int ClientID, const char* Code, const char* Server);
virtual void LoadTeam(const char* Code, int ClientID);
// Game relevant not allowed to fail due to an ongoing SQL request.
virtual void SaveScore(int ClientID, float Time, const char *pTimestamp, virtual void SaveScore(int ClientID, float Time, const char *pTimestamp,
float CpTime[NUM_CHECKPOINTS], bool NotEligible); float CpTime[NUM_CHECKPOINTS], bool NotEligible);
virtual void SaveTeamScore(int* aClientIDs, unsigned int Size, float Time, const char *pTimestamp); virtual void SaveTeamScore(int* aClientIDs, unsigned int Size, float Time, const char *pTimestamp);
virtual void ShowRank(int ClientID, const char* pName, bool Search = false);
virtual void ShowTeamRank(int ClientID, const char* pName, bool Search = false);
virtual void ShowTimes(int ClientID, const char* pName, int Debut = 1);
virtual void ShowTimes(int ClientID, int Debut = 1);
virtual void ShowTop5(IConsole::IResult *pResult, int ClientID,
void *pUserData, int Debut = 1);
virtual void ShowTeamTop5(IConsole::IResult *pResult, int ClientID,
void *pUserData, int Debut = 1);
virtual void ShowPoints(int ClientID, const char* pName, bool Search = false);
virtual void ShowTopPoints(IConsole::IResult *pResult, int ClientID,
void *pUserData, int Debut = 1);
virtual void RandomMap(std::shared_ptr<CRandomMapResult> *ppResult, int ClientID, int stars);
virtual void RandomUnfinishedMap(std::shared_ptr<CRandomMapResult> *ppResult, int ClientID, int stars);
virtual void SaveTeam(int Team, const char* Code, int ClientID, const char* Server);
virtual void LoadTeam(const char* Code, int ClientID);
virtual void GetSaves(int ClientID);
virtual void OnShutdown(); virtual void OnShutdown();
}; };
#endif // GAME_SERVER_SCORE_SQL_SCORE_H #endif // GAME_SERVER_SCORE_SQL_H

View file

@ -2,6 +2,9 @@
#include "teams.h" #include "teams.h"
#include "score.h" #include "score.h"
#include <engine/shared/config.h> #include <engine/shared/config.h>
#if defined(CONF_SQL)
#include "score/sql_score.h"
#endif
CGameTeams::CGameTeams(CGameContext *pGameContext) : CGameTeams::CGameTeams(CGameContext *pGameContext) :
m_pGameContext(pGameContext) m_pGameContext(pGameContext)
@ -19,9 +22,11 @@ void CGameTeams::Reset()
m_MembersCount[i] = 0; m_MembersCount[i] = 0;
m_LastChat[i] = 0; m_LastChat[i] = 0;
m_TeamLocked[i] = false; m_TeamLocked[i] = false;
m_IsSaving[i] = false;
m_Invited[i] = 0; m_Invited[i] = 0;
m_Practice[i] = false; m_Practice[i] = false;
#if defined(CONF_SQL)
m_pSaveTeamResult[i] = nullptr;
#endif
} }
} }
@ -250,6 +255,11 @@ bool CGameTeams::SetCharacterTeam(int ClientID, int Team)
if (m_Practice[m_Core.Team(ClientID)]) if (m_Practice[m_Core.Team(ClientID)])
return false; return false;
//you can not join a team which is currently in the process of saving,
//because the save-process can fail and then the team is reset into the game
if((Team != TEAM_SUPER && GetSaving(Team))
|| (m_Core.Team(ClientID) != TEAM_SUPER && GetSaving(m_Core.Team(ClientID))))
return false;
SetForceCharacterTeam(ClientID, Team); SetForceCharacterTeam(ClientID, Team);
//GameServer()->CreatePlayerSpawn(Character(id)->m_Core.m_Pos, TeamMask()); //GameServer()->CreatePlayerSpawn(Character(id)->m_Core.m_Pos, TeamMask());
@ -327,6 +337,7 @@ void CGameTeams::ForceLeaveTeam(int ClientID)
SetTeamLock(m_Core.Team(ClientID), false); SetTeamLock(m_Core.Team(ClientID), false);
ResetInvited(m_Core.Team(ClientID)); ResetInvited(m_Core.Team(ClientID));
m_Practice[m_Core.Team(ClientID)] = false; m_Practice[m_Core.Team(ClientID)] = false;
// do not reset SaveTeamResult, because it should be logged into teehistorian even if the team leaves
} }
} }
@ -670,10 +681,56 @@ void CGameTeams::OnFinish(CPlayer* Player, float Time, const char *pTimestamp)
} }
} }
#if defined(CONF_SQL)
void CGameTeams::ProcessSaveTeam()
{
for(int Team = 0; Team < MAX_CLIENTS; Team++)
{
if(m_pSaveTeamResult[Team] == nullptr || m_pSaveTeamResult[Team].use_count() != 1)
continue;
if(m_pSaveTeamResult[Team]->m_aBroadcast[0] != '\0')
GameServer()->SendBroadcast(m_pSaveTeamResult[Team]->m_aBroadcast, -1);
if(m_pSaveTeamResult[Team]->m_aMessage[0] != '\0')
GameServer()->SendChatTeam(Team, m_pSaveTeamResult[Team]->m_aMessage);
// TODO: log load/save success/fail in teehistorian
switch(m_pSaveTeamResult[Team]->m_Status)
{
case CSqlSaveResult::SAVE_SUCCESS:
{
ResetSavedTeam(m_pSaveTeamResult[Team]->m_RequestingPlayer, Team);
char aSaveID[UUID_MAXSTRSIZE];
FormatUuid(m_pSaveTeamResult[Team]->m_SaveID, aSaveID, UUID_MAXSTRSIZE);
dbg_msg("save", "Save successful: %s", aSaveID);
break;
}
case CSqlSaveResult::SAVE_FAILED:
if(m_MembersCount[Team] > 0)
m_pSaveTeamResult[Team]->m_SavedTeam.load(Team);
break;
case CSqlSaveResult::LOAD_SUCCESS:
{
if(m_MembersCount[Team] > 0)
m_pSaveTeamResult[Team]->m_SavedTeam.load(Team);
char aSaveID[UUID_MAXSTRSIZE];
FormatUuid(m_pSaveTeamResult[Team]->m_SaveID, aSaveID, UUID_MAXSTRSIZE);
dbg_msg("save", "Load successful: %s", aSaveID);
break;
}
case CSqlSaveResult::LOAD_FAILED:
break;
}
m_pSaveTeamResult[Team] = nullptr;
}
}
#endif
void CGameTeams::OnCharacterSpawn(int ClientID) void CGameTeams::OnCharacterSpawn(int ClientID)
{ {
m_Core.SetSolo(ClientID, false); m_Core.SetSolo(ClientID, false);
if(GetSaving(m_Core.Team(ClientID)))
return;
if (m_Core.Team(ClientID) >= TEAM_SUPER || !m_TeamLocked[m_Core.Team(ClientID)]) if (m_Core.Team(ClientID) >= TEAM_SUPER || !m_TeamLocked[m_Core.Team(ClientID)])
// Important to only set a new team here, don't remove from an existing // Important to only set a new team here, don't remove from an existing
// team since a newly joined player does by definition not have an old team // team since a newly joined player does by definition not have an old team
@ -686,6 +743,8 @@ void CGameTeams::OnCharacterDeath(int ClientID, int Weapon)
m_Core.SetSolo(ClientID, false); m_Core.SetSolo(ClientID, false);
int Team = m_Core.Team(ClientID); int Team = m_Core.Team(ClientID);
if(GetSaving(Team))
return;
bool Locked = TeamLocked(Team) && Weapon != WEAPON_GAME; bool Locked = TeamLocked(Team) && Weapon != WEAPON_GAME;
if(!Locked) if(!Locked)
@ -746,30 +805,27 @@ void CGameTeams::SetClientInvited(int Team, int ClientID, bool Invited)
} }
} }
void CGameTeams::KillSavedTeam(int Team) #if defined(CONF_SQL)
void CGameTeams::KillSavedTeam(int ClientID, int Team)
{ {
// Set so that no finish is accidentally given to some of the players
ChangeTeamState(Team, CGameTeams::TEAMSTATE_OPEN);
for(int i = 0; i < MAX_CLIENTS; i++) for(int i = 0; i < MAX_CLIENTS; i++)
{ {
if(m_Core.Team(i) == Team && GameServer()->m_apPlayers[i]) if(m_Core.Team(i) == Team && GameServer()->m_apPlayers[i])
{ {
// Set so that no finish is accidentally given to some of the players GameServer()->m_apPlayers[i]->m_VotedForPractice = false;
GameServer()->m_apPlayers[i]->GetCharacter()->m_DDRaceState = DDRACE_NONE; GameServer()->m_apPlayers[i]->KillCharacter(WEAPON_SELF);
m_TeeFinished[i] = false; }
} }
} }
void CGameTeams::ResetSavedTeam(int ClientID, int Team)
{
for (int i = 0; i < MAX_CLIENTS; i++) for (int i = 0; i < MAX_CLIENTS; i++)
{
if(m_Core.Team(i) == Team && GameServer()->m_apPlayers[i]) if(m_Core.Team(i) == Team && GameServer()->m_apPlayers[i])
GameServer()->m_apPlayers[i]->ThreadKillCharacter(-2); {
SetForceCharacterTeam(i, 0);
ChangeTeamState(Team, CGameTeams::TEAMSTATE_EMPTY);
// unlock team when last player leaves
SetTeamLock(Team, false);
ResetInvited(Team);
m_Practice[Team] = false;
} }
}
}
#endif

View file

@ -5,15 +5,21 @@
#include <game/teamscore.h> #include <game/teamscore.h>
#include <game/server/gamecontext.h> #include <game/server/gamecontext.h>
#if defined(CONF_SQL)
class CSqlSaveResult;
#endif
class CGameTeams class CGameTeams
{ {
int m_TeamState[MAX_CLIENTS]; int m_TeamState[MAX_CLIENTS];
int m_MembersCount[MAX_CLIENTS]; int m_MembersCount[MAX_CLIENTS];
bool m_TeeFinished[MAX_CLIENTS]; bool m_TeeFinished[MAX_CLIENTS];
bool m_TeamLocked[MAX_CLIENTS]; bool m_TeamLocked[MAX_CLIENTS];
bool m_IsSaving[MAX_CLIENTS];
uint64_t m_Invited[MAX_CLIENTS]; uint64_t m_Invited[MAX_CLIENTS];
bool m_Practice[MAX_CLIENTS]; bool m_Practice[MAX_CLIENTS];
#if defined(CONF_SQL)
std::shared_ptr<CSqlSaveResult> m_pSaveTeamResult[MAX_CLIENTS];
#endif
class CGameContext * m_pGameContext; class CGameContext * m_pGameContext;
@ -85,7 +91,11 @@ public:
void SetDDRaceState(CPlayer* Player, int DDRaceState); void SetDDRaceState(CPlayer* Player, int DDRaceState);
void SetStartTime(CPlayer* Player, int StartTime); void SetStartTime(CPlayer* Player, int StartTime);
void SetCpActive(CPlayer* Player, int CpActive); void SetCpActive(CPlayer* Player, int CpActive);
void KillSavedTeam(int Team); #if defined(CONF_SQL)
void KillSavedTeam(int ClientID, int Team);
void ResetSavedTeam(int ClientID, int Team);
void ProcessSaveTeam();
#endif
bool TeeFinished(int ClientID) bool TeeFinished(int ClientID)
{ {
@ -114,15 +124,20 @@ public:
{ {
m_TeeFinished[ClientID] = finished; m_TeeFinished[ClientID] = finished;
} }
#if defined(CONF_SQL)
void SetSaving(int TeamID, bool Value) void SetSaving(int TeamID, std::shared_ptr<CSqlSaveResult> SaveResult)
{ {
m_IsSaving[TeamID] = Value; m_pSaveTeamResult[TeamID] = SaveResult;
} }
#endif
bool GetSaving(int TeamID) bool GetSaving(int TeamID)
{ {
return m_IsSaving[TeamID]; #if defined(CONF_SQL)
return m_pSaveTeamResult[TeamID] != nullptr;
#else
return false;
#endif
} }
void EnablePractice(int Team) void EnablePractice(int Team)