Thread safe /save

This commit is contained in:
Zwelf 2020-06-02 16:27:31 +02:00
parent f5ebbf59b6
commit 031ac52320
15 changed files with 323 additions and 224 deletions

View file

@ -186,7 +186,7 @@ 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);
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);
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);

View file

@ -693,8 +693,6 @@ void CGameContext::ConSave(IConsole::IResult *pResult, void *pUserData)
if(pPlayer->m_LastSQLQuery + g_Config.m_SvSqlQueriesDelay * pSelf->Server()->TickSpeed() >= pSelf->Server()->Tick())
return;
int Team = ((CGameControllerDDRace*) pSelf->m_pController)->m_Teams.m_Core.Team(pResult->m_ClientID);
const char* pCode = pResult->GetString(0);
char aCountry[5];
if(str_length(pCode) > 3 && pCode[0] >= 'A' && pCode[0] <= 'Z' && pCode[1] >= 'A'
@ -722,7 +720,7 @@ void CGameContext::ConSave(IConsole::IResult *pResult, void *pUserData)
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)
pPlayer->m_LastSQLQuery = pSelf->Server()->Tick();

View file

@ -1503,6 +1503,22 @@ void CCharacter::HandleTiles(int Index)
// 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(Teams()->GetSaving(Team()))
{
GameServer()->SendChatTarget(GetPlayer()->GetCID(), "Your team is currently getting saved"); // TODO: better message
Die(GetPlayer()->GetCID(), WEAPON_WORLD);
return;
}
if(g_Config.m_SvTeam == 2 && (Team() == TEAM_FLOCK || Teams()->Count(Team()) <= 1))
{
if(m_LastStartWarning < Server()->Tick() - 3 * Server()->TickSpeed())
{
GameServer()->SendChatTarget(GetPlayer()->GetCID(), "You have to be in a team with other tees to start");
m_LastStartWarning = Server()->Tick();
}
Die(GetPlayer()->GetCID(), WEAPON_WORLD);
return;
}
if(g_Config.m_SvResetPickups)
{
for (int i = WEAPON_SHOTGUN; i < NUM_WEAPONS; ++i)
@ -1512,16 +1528,6 @@ void CCharacter::HandleTiles(int Index)
m_Core.m_ActiveWeapon = WEAPON_GUN;
}
}
if(g_Config.m_SvTeam == 2 && (Team() == TEAM_FLOCK || Teams()->Count(Team()) <= 1))
{
if(m_LastStartWarning < Server()->Tick() - 3 * Server()->TickSpeed())
{
GameServer()->SendChatTarget(GetPlayer()->GetCID(),"You have to be in a team with other tees to start");
m_LastStartWarning = Server()->Tick();
}
Die(GetPlayer()->GetCID(), WEAPON_WORLD);
return;
}
Teams()->OnCharacterStart(m_pPlayer->GetCID());
m_CpActive = -2;

View file

@ -25,6 +25,9 @@ CGameControllerDDRace::~CGameControllerDDRace()
void CGameControllerDDRace::Tick()
{
IGameController::Tick();
#if defined(CONF_SQL)
m_Teams.ProcessSaveTeam();
#endif
}
void CGameControllerDDRace::InitTeleporter()

View file

@ -45,7 +45,6 @@ void CPlayer::Reset()
m_JoinTick = Server()->Tick();
delete m_pCharacter;
m_pCharacter = 0;
m_KillMe = 0;
m_SpectatorID = SPEC_FREEVIEW;
m_LastActionTick = Server()->Tick();
m_TeamChangeTick = Server()->Tick();
@ -154,13 +153,6 @@ void CPlayer::Tick()
if(!Server()->ClientIngame(m_ClientID))
return;
if(m_KillMe != 0)
{
KillCharacter(m_KillMe);
m_KillMe = 0;
return;
}
if (m_ChatScore > 0)
m_ChatScore--;
@ -499,11 +491,6 @@ CCharacter *CPlayer::GetCharacter()
return 0;
}
void CPlayer::ThreadKillCharacter(int Weapon)
{
m_KillMe = Weapon;
}
void CPlayer::KillCharacter(int Weapon)
{
if(m_pCharacter)
@ -797,21 +784,30 @@ void CPlayer::ProcessSqlResult()
if(m_SqlQueryResult == nullptr || m_SqlQueryResult.use_count() != 1)
return;
if(m_SqlQueryResult->m_Done) // sql was query successful
if(m_SqlQueryResult->m_Done) // SQL request was successful
{
for(int i = 0; i < (int)(sizeof(m_SqlQueryResult->m_aaMessages)/sizeof(m_SqlQueryResult->m_aaMessages[0])); i++)
int NumMessages = (int)(sizeof(m_SqlQueryResult->m_aaMessages)/sizeof(m_SqlQueryResult->m_aaMessages[0]));
switch(m_SqlQueryResult->m_MessageKind)
{
if(m_SqlQueryResult->m_aaMessages[i][0] == 0)
break;
switch(m_SqlQueryResult->m_MessageTarget)
case CSqlPlayerResult::DIRECT:
for(int i = 0; i < NumMessages; i++)
{
case CSqlPlayerResult::DIRECT:
if(m_SqlQueryResult->m_aaMessages[i][0] == 0)
break;
GameServer()->SendChatTarget(m_ClientID, m_SqlQueryResult->m_aaMessages[i]);
break;
case CSqlPlayerResult::ALL:
GameServer()->SendChat(-1, CGameContext::CHAT_ALL, m_SqlQueryResult->m_aaMessages[i]);
break;
}
break;
case CSqlPlayerResult::ALL:
for(int i = 0; i < NumMessages; i++)
{
if(m_SqlQueryResult->m_aaMessages[i][0] == 0)
break;
GameServer()->SendChat(-1, CGameContext::CHAT_ALL, m_SqlQueryResult->m_aaMessages[i]);
}
break;
case CSqlPlayerResult::MAP_VOTE:
// TODO: start vote
break;
}
}
m_SqlQueryResult = nullptr;

View file

@ -47,7 +47,6 @@ public:
void OnPredictedEarlyInput(CNetObj_PlayerInput *NewInput);
void OnDisconnect(const char *pReason);
void ThreadKillCharacter(int Weapon = WEAPON_GAME);
void KillCharacter(int Weapon = WEAPON_GAME);
CCharacter *GetCharacter();
@ -174,7 +173,6 @@ public:
bool m_SpecTeam;
bool m_NinjaJetpack;
bool m_Afk;
int m_KillMe;
bool m_HasFinishScore;
int m_ChatScore;

View file

@ -52,7 +52,9 @@ void CSaveTee::save(CCharacter *pChr)
m_TuneZoneOld = pChr->m_TuneZoneOld;
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_PrevPos = pChr->m_PrevPos;
@ -183,6 +185,9 @@ void CSaveTee::load(CCharacter *pChr, int Team)
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),
"%s\t%d\t%d\t%d\t%d\t%d\t"
// weapons
@ -223,7 +228,7 @@ char* CSaveTee::GetString()
m_LastWeapon, m_QueuedWeapon,
// tee states
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,
m_TeleCheckpoint, m_LastPenalty,
(int)m_CorePos.x, (int)m_CorePos.y, m_Vel.x, m_Vel.y,
@ -420,6 +425,38 @@ bool CSaveTeam::HandleSaveError(int Result, int ClientID, CGameContext *pGameCon
return true;
}
void CSaveTeam::HandleLoadError(int Result, int ClientID, const CSaveTeam &SavedTeam, CGameContext *pGameContext)
{
if(Result == 1)
{
pGameContext->SendChatTarget(ClientID, "You have to be in a team (from 1-63)");
}
else if(Result == 2)
{
char aBuf[256];
str_format(aBuf, sizeof(aBuf), "Too many players in this team, should be %d", SavedTeam.GetMembersCount());
pGameContext->SendChatTarget(ClientID, aBuf);
}
else if(Result >= 10 && Result < 100)
{
char aBuf[256];
str_format(aBuf, sizeof(aBuf), "Unable to find player: '%s'", SavedTeam.m_pSavedTees[Result-10].GetName());
pGameContext->SendChatTarget(ClientID, aBuf);
}
else if(Result >= 100 && Result < 200)
{
char aBuf[256];
str_format(aBuf, sizeof(aBuf), "%s is racing right now, Team can't be loaded if a Tee is racing already", SavedTeam.m_pSavedTees[Result-100].GetName());
pGameContext->SendChatTarget(ClientID, aBuf);
}
else if(Result >= 200)
{
char aBuf[256];
str_format(aBuf, sizeof(aBuf), "Everyone has to be in a team, %s is in team 0 or the wrong team", SavedTeam.m_pSavedTees[Result-200].GetName());
pGameContext->SendChatTarget(ClientID, aBuf);
}
}
int CSaveTeam::load(int Team)
{
if(Team <= 0 || Team >= MAX_CLIENTS)
@ -474,7 +511,7 @@ int CSaveTeam::load(int Team)
return 0;
}
int CSaveTeam::MatchPlayer(char name[16])
int CSaveTeam::MatchPlayer(const char name[16])
{
for (int i = 0; i < MAX_CLIENTS; i++)
{
@ -486,7 +523,7 @@ int CSaveTeam::MatchPlayer(char name[16])
return -1;
}
CCharacter* CSaveTeam::MatchCharacter(char name[16], int SaveID)
CCharacter* CSaveTeam::MatchCharacter(const char name[16], int SaveID)
{
int ID = MatchPlayer(name);
if(ID >= 0 && m_pController->GameServer()->m_apPlayers[ID])

View file

@ -14,8 +14,8 @@ public:
void load(CCharacter* pchr, int Team);
char* GetString();
int LoadString(char* String);
vec2 GetPos() { return m_Pos; }
char* GetName() { return m_aName; }
vec2 GetPos() const { return m_Pos; }
const char* GetName() const { return m_aName; }
private:
@ -95,7 +95,7 @@ public:
CSaveTeam(IGameController* Controller);
~CSaveTeam();
char* GetString();
int GetMembersCount() { return m_MembersCount; }
int GetMembersCount() const { return m_MembersCount; }
int LoadString(const char* String);
int save(int Team);
int load(int Team);
@ -103,9 +103,10 @@ public:
// returns true if an error occured
static bool HandleSaveError(int Result, int ClientID, CGameContext *pGameContext);
static void HandleLoadError(int Result, int ClientID, const CSaveTeam &SavedTeam, CGameContext *pGameContext);
private:
int MatchPlayer(char name[16]);
CCharacter* MatchCharacter(char name[16], int SaveID);
int MatchPlayer(const char name[16]);
CCharacter* MatchCharacter(const char name[16], int SaveID);
IGameController* m_pController;

View file

@ -80,7 +80,7 @@ public:
virtual void RandomMap(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 GetSaves(int ClientID) = 0;

View file

@ -336,7 +336,7 @@ void CFileScore::RandomUnfinishedMap(int ClientID, int Stars)
GameServer()->SendChatTarget(ClientID, aBuf);
}
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];
str_format(aBuf, sizeof(aBuf), "Save-function not supported in file based servers");

View file

@ -78,7 +78,7 @@ public:
virtual void ShowPoints(int ClientID, const char* pName);
virtual void RandomMap(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 GetSaves(int ClientID);

View file

@ -21,7 +21,7 @@ std::atomic_int CSqlScore::ms_InstanceCount(0);
CSqlPlayerResult::CSqlPlayerResult() :
m_Done(0),
m_MessageTarget(DIRECT)
m_MessageKind(DIRECT)
{
for(int i = 0; i < (int)(sizeof(m_aaMessages)/sizeof(m_aaMessages[0])); i++)
m_aaMessages[i][0] = 0;
@ -324,82 +324,58 @@ bool CSqlScore::LoadScoreThread(CSqlServer* pSqlServer, const CSqlData<CSqlResul
void CSqlScore::MapVote(int ClientID, const char* MapName)
{
/*
*ppResult = std::make_shared<CMapVoteResult>();
CSqlMapVoteData *Tmp = new CSqlMapVoteData();
Tmp->m_ClientID = ClientID;
Tmp->m_RequestedMap = MapName;
Tmp->m_pResult = *ppResult;
str_copy(Tmp->m_aFuzzyMap, MapName, sizeof(Tmp->m_aFuzzyMap));
sqlstr::ClearString(Tmp->m_aFuzzyMap, sizeof(Tmp->m_aFuzzyMap));
sqlstr::FuzzyString(Tmp->m_aFuzzyMap, sizeof(Tmp->m_aFuzzyMap));
thread_init_and_detach(ExecSqlFunc, new CSqlExecData(MapVoteThread, Tmp), "map vote");
*/
ExecPlayerThread(MapVoteThread, "map vote", ClientID, MapName, 0);
}
bool CSqlScore::MapVoteThread(CSqlServer* pSqlServer, const CSqlData<CSqlResult> *pGameData, bool HandleFailure)
bool CSqlScore::MapVoteThread(CSqlServer* pSqlServer, const CSqlData<CSqlPlayerResult> *pGameData, bool HandleFailure)
{
/*
const CSqlMapVoteData *pData = dynamic_cast<const CSqlMapVoteData *>(pGameData);
const CSqlPlayerRequest *pData = dynamic_cast<const CSqlPlayerRequest *>(pGameData);
auto paMessages = pData->m_pResult->m_aaMessages;
if (HandleFailure)
return true;
try
{
char aFuzzyMap[128];
str_copy(aFuzzyMap, pData->m_Name.Str(), sizeof(aFuzzyMap));
sqlstr::ClearString(aFuzzyMap, sizeof(aFuzzyMap));
sqlstr::FuzzyString(aFuzzyMap, sizeof(aFuzzyMap));
char aBuf[768];
str_format(aBuf, sizeof(aBuf), "SELECT Map, Server FROM %s_maps WHERE Map LIKE '%s' COLLATE utf8mb4_general_ci ORDER BY CASE WHEN Map = '%s' THEN 0 ELSE 1 END, CASE WHEN Map LIKE '%s%%' THEN 0 ELSE 1 END, LENGTH(Map), Map LIMIT 1;", pSqlServer->GetPrefix(), pData->m_aFuzzyMap, pData->m_RequestedMap.ClrStr(), pData->m_RequestedMap.ClrStr());
str_format(aBuf, sizeof(aBuf),
"SELECT Map, Server "
"FROM %s_maps "
"WHERE Map LIKE '%s' COLLATE utf8mb4_general_ci "
"ORDER BY "
"CASE WHEN Map = '%s' THEN 0 ELSE 1 END, "
"CASE WHEN Map LIKE '%s%%' THEN 0 ELSE 1 END, "
"LENGTH(Map), Map "
"LIMIT 1;",
pSqlServer->GetPrefix(), aFuzzyMap,
pData->m_Name.ClrStr(), pData->m_Name.ClrStr()
);
pSqlServer->executeSqlQuery(aBuf);
CPlayer *pPlayer = pData->GameServer()->m_apPlayers[pData->m_ClientID];
int64 Now = pData->Server()->Tick();
int Timeleft = 0;
if(!pPlayer)
goto end;
Timeleft = pPlayer->m_LastVoteCall + pData->Server()->TickSpeed()*g_Config.m_SvVoteDelay - Now;
if(pSqlServer->GetResults()->rowsCount() != 1)
{
str_format(aBuf, sizeof(aBuf), "No map like \"%s\" found. Try adding a '%%' at the start if you don't know the first character. Example: /map %%castle for \"Out of Castle\"", pData->m_RequestedMap.Str());
pData->GameServer()->SendChatTarget(pData->m_ClientID, aBuf);
}
else 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) / pData->Server()->TickSpeed()) + 1);
pData->GameServer()->SendChatTarget(pData->m_ClientID, aBuf);
}
else if(pPlayer->m_LastVoteCall && Timeleft > 0)
{
char aChatmsg[512] = {0};
str_format(aChatmsg, sizeof(aChatmsg), "You must wait %d seconds before making another vote", (Timeleft/pData->Server()->TickSpeed())+1);
pData->GameServer()->SendChatTarget(pData->m_ClientID, aChatmsg);
}
else if(time_get() < pData->GameServer()->m_LastMapVote + (time_freq() * g_Config.m_SvVoteMapTimeDelay))
{
char chatmsg[512] = {0};
str_format(chatmsg, sizeof(chatmsg), "There's a %d second delay between map-votes, please wait %d seconds.", g_Config.m_SvVoteMapTimeDelay, (int)(((pData->GameServer()->m_LastMapVote+(g_Config.m_SvVoteMapTimeDelay * time_freq()))/time_freq())-(time_get()/time_freq())));
pData->GameServer()->SendChatTarget(pData->m_ClientID, chatmsg);
str_format(paMessages[0], sizeof(paMessages[0]),
"No map like \"%s\" found. "
"Try adding a '%%' at the start if you don't know the first character. "
"Example: /map %%castle for \"Out of Castle\"",
pData->m_Name.Str());
}
else
{
pSqlServer->GetResults()->next();
str_copy(pData->m_pResult->m_aMap, pSqlServer->GetResults()->getString("Map").c_str(), sizeof(pData->m_pResult->m_aMap));
str_copy(pData->m_pResult->m_aServer, pSqlServer->GetResults()->getString("Server").c_str(), sizeof(pData->m_pResult->m_aServer));
auto Server = pSqlServer->GetResults()->getString("Server");
auto Map = pSqlServer->GetResults()->getString("Map");
strcpy(paMessages[0], "/map"); // reason
str_copy(paMessages[1], Server.c_str(), sizeof(paMessages[0]));
str_copy(paMessages[2], Map.c_str(), sizeof(paMessages[0]));
for(char *p = pData->m_pResult->m_aServer; *p; p++)
for(char *p = paMessages[1]; *p; p++) // lower case server
*p = tolower(*p);
pData->m_pResult->m_ClientID = pData->m_ClientID;
pData->m_pResult->m_Done = true;
}
end:
pData->m_pResult->m_Done = true;
return true;
}
catch (sql::SQLException &e)
@ -407,13 +383,6 @@ bool CSqlScore::MapVoteThread(CSqlServer* pSqlServer, const CSqlData<CSqlResult>
dbg_msg("sql", "MySQL Error: %s", e.what());
dbg_msg("sql", "ERROR: Could not start Mapvote");
}
catch (CGameContextError &e)
{
dbg_msg("sql", "WARNING: Aborted mapvote due to reload/change of map.");
return true;
}
return false;
*/
return false;
}
@ -865,7 +834,7 @@ bool CSqlScore::ShowRankThread(CSqlServer* pSqlServer, const CSqlData<CSqlPlayer
}
else
{
pData->m_pResult->m_MessageTarget = CSqlPlayerResult::ALL;
pData->m_pResult->m_MessageKind = CSqlPlayerResult::ALL;
str_format(pData->m_pResult->m_aaMessages[0], sizeof(pData->m_pResult->m_aaMessages[0]),
"%d. %s Time: %02d:%05.2f, requested by %s",
Rank, pSqlServer->GetResults()->getString("Name").c_str(),
@ -965,7 +934,7 @@ bool CSqlScore::ShowTeamRankThread(CSqlServer* pSqlServer, const CSqlData<CSqlPl
}
else
{
pData->m_pResult->m_MessageTarget = CSqlPlayerResult::ALL;
pData->m_pResult->m_MessageKind = CSqlPlayerResult::ALL;
str_format(pData->m_pResult->m_aaMessages[0], sizeof(pData->m_pResult->m_aaMessages[0]),
"%d. %s Team time: %02d:%05.02f, requested by %s",
Rank, aNames, (int)(Time/60), Time-((int)Time/60*60), pData->m_RequestingPlayer.Str());
@ -1102,7 +1071,6 @@ bool CSqlScore::ShowTeamTop5Thread(CSqlServer* pSqlServer, const CSqlData<CSqlPl
int TeamSize = pSqlServer->GetResults()->getInt("TeamSize");
float Time = (float)pSqlServer->GetResults()->getDouble("Time");
int Rank = pSqlServer->GetResults()->getInt("Rank");
printf("%d", TeamSize);
char aNames[2300] = { 0 };
for(int i = 0; i < TeamSize; i++)
@ -1280,7 +1248,7 @@ bool CSqlScore::ShowPointsThread(CSqlServer* pSqlServer, const CSqlData<CSqlPlay
int Count = pSqlServer->GetResults()->getInt("Points");
int Rank = pSqlServer->GetResults()->getInt("Rank");
auto Name = pSqlServer->GetResults()->getString("Name");
pData->m_pResult->m_MessageTarget = CSqlPlayerResult::ALL;
pData->m_pResult->m_MessageKind = CSqlPlayerResult::ALL;
str_format(paMessages[0], sizeof(paMessages[0]),
"%d. %s Points: %d, requested by %s",
Rank, Name.c_str(), Count, pData->m_RequestingPlayer.Str());
@ -1499,126 +1467,128 @@ bool CSqlScore::RandomUnfinishedMapThread(CSqlServer* pSqlServer, const CSqlData
return false;
}
void CSqlScore::SaveTeam(int Team, const char* Code, int ClientID, const char* Server)
void CSqlScore::SaveTeam(int ClientID, const char* Code, const char* Server)
{
/*
if(((CGameControllerDDRace*)(GameServer()->m_pController))->m_Teams.GetSaving(Team))
auto pController = ((CGameControllerDDRace*)(GameServer()->m_pController));
int Team = pController->m_Teams.m_Core.Team(ClientID);
if(pController->m_Teams.GetSaving(Team))
return;
CSaveTeam SavedTeam(GameServer()->m_pController);
int Result = SavedTeam.save(Team);
auto SaveResult = std::make_shared<CSqlSaveResult>(ClientID, pController);
int Result = SaveResult->m_SavedTeam.save(Team);
if(CSaveTeam::HandleSaveError(Result, ClientID, GameServer()))
return;
// disable joining team while saving
((CGameControllerDDRace*)(GameServer()->m_pController))->m_Teams.SetSaving(Team, true);
pController->m_Teams.SetSaving(Team, SaveResult);
CSqlTeamSave *Tmp = new CSqlTeamSave();
Tmp->m_Team = Team;
Tmp->m_ClientID = ClientID;
// copy both save code and save state into the thread struct
CSqlTeamSave *Tmp = new CSqlTeamSave(SaveResult);
Tmp->m_Code = Code;
Tmp->m_SaveState = SavedTeam.GetString();
Tmp->m_Map = g_Config.m_SvMap;
FormatUuid(RandomUuid(), Tmp->m_Uuid, UUID_MAXSTRSIZE);
str_copy(Tmp->m_Server, Server, sizeof(Tmp->m_Server));
str_copy(Tmp->m_ClientName, this->Server()->ClientName(Tmp->m_ClientID), sizeof(Tmp->m_ClientName));
str_copy(Tmp->m_ClientName, this->Server()->ClientName(ClientID), sizeof(Tmp->m_ClientName));
// TODO: log event in Teehistorian
// TODO: find a way to send all players in the team the save code
((CGameControllerDDRace*)(GameServer()->m_pController))->m_Teams.KillSavedTeam(Team);
thread_init_and_detach(ExecSqlFunc, new CSqlExecData(SaveTeamThread, Tmp, false), "save team");
*/
// TODO: log event in teehistorian
pController->m_Teams.KillSavedTeam(ClientID, Team);
char aBuf[512];
// TODO: better message, maybe hint that one should wait until the next message to leave
str_format(aBuf, sizeof(aBuf), "Saving team initiated by '%s'", this->Server()->ClientName(ClientID));
GameServer()->SendChatTeam(Team, aBuf);
thread_init_and_detach(
CSqlExecData<CSqlSaveResult>::ExecSqlFunc,
new CSqlExecData<CSqlSaveResult>(SaveTeamThread, Tmp, false),
"save team");
}
bool CSqlScore::SaveTeamThread(CSqlServer* pSqlServer, const CSqlData<CSqlResult> *pGameData, bool HandleFailure)
bool CSqlScore::SaveTeamThread(CSqlServer* pSqlServer, const CSqlData<CSqlSaveResult> *pGameData, bool HandleFailure)
{
/*
const CSqlTeamSave *pData = dynamic_cast<const CSqlTeamSave *>(pGameData);
try
sqlstr::CSqlString<10> SaveState = pData->m_pResult->m_SavedTeam.GetString();
if(HandleFailure)
{
if (HandleFailure)
if (!g_Config.m_SvSqlFailureFile[0])
return true;
lock_wait(ms_FailureFileLock);
IOHANDLE File = io_open(g_Config.m_SvSqlFailureFile, IOFLAG_APPEND);
if(File)
{
if (!g_Config.m_SvSqlFailureFile[0])
return true;
dbg_msg("sql", "ERROR: Could not save Teamsave, writing insert to a file now...");
lock_wait(ms_FailureFileLock);
IOHANDLE File = io_open(g_Config.m_SvSqlFailureFile, IOFLAG_APPEND);
if(File)
{
dbg_msg("sql", "ERROR: Could not save Teamsave, writing insert to a file now...");
char aBuf[65536];
str_format(aBuf, sizeof(aBuf),
"INSERT IGNORE INTO %%s_saves(Savegame, Map, Code, Timestamp, Server, DDNet7) "
"VALUES ('%s', '%s', '%s', CURRENT_TIMESTAMP(), '%s', false);",
pData->m_SaveState, pData->m_Map.ClrStr(), pData->m_Code.ClrStr(), pData->m_Server
);
io_write(File, aBuf, str_length(aBuf));
io_write_newline(File);
io_close(File);
lock_unlock(ms_FailureFileLock);
pData->GameServer()->SendBroadcast("Database connection failed, teamsave written to a file instead. Admins will add it manually in a few days.", -1);
return true;
}
char aBuf[65536];
str_format(aBuf, sizeof(aBuf),
"INSERT IGNORE INTO %%s_saves(Savegame, Map, Code, Timestamp, Server, SaveID, DDNet7) "
"VALUES ('%s', '%s', '%s', CURRENT_TIMESTAMP(), '%s', '%s', false)",
SaveState.ClrStr(), pData->m_Map.ClrStr(),
pData->m_Code.ClrStr(), pData->m_Server, pData->m_Uuid
);
io_write(File, aBuf, str_length(aBuf));
io_write_newline(File);
io_close(File);
lock_unlock(ms_FailureFileLock);
dbg_msg("sql", "ERROR: Could not save Teamsave, NOT even to a file");
return false;
}
pData->m_pResult->m_Status = CSqlSaveResult::SAVE_SUCCESS;
strcpy(pData->m_pResult->m_aBroadcast,
"Database connection failed, teamsave written to a file instead. Admins will add it manually in a few days.");
return true;
}
lock_unlock(ms_FailureFileLock);
dbg_msg("sql", "ERROR: Could not save Teamsave, NOT even to a file");
return false;
}
else
{
try
{
char aBuf[512];
str_format(aBuf, sizeof(aBuf), "lock tables %s_saves write;", pSqlServer->GetPrefix());
pSqlServer->executeSql(aBuf);
str_format(aBuf, sizeof(aBuf), "select Savegame from %s_saves where Code = '%s' and Map = '%s';", pSqlServer->GetPrefix(), pData->m_Code.ClrStr(), pData->m_Map.ClrStr());
str_format(aBuf, sizeof(aBuf),
"SELECT Savegame "
"FROM %s_saves "
"WHERE Code = '%s' AND Map = '%s';",
pSqlServer->GetPrefix(), pData->m_Code.ClrStr(), pData->m_Map.ClrStr());
pSqlServer->executeSqlQuery(aBuf);
if (pSqlServer->GetResults()->rowsCount() == 0)
{
char aBuf[65536];
str_format(aBuf, sizeof(aBuf),
"INSERT IGNORE INTO %s_saves(Savegame, Map, Code, Timestamp, Server, DDNet7) VALUES ('%s', '%s', '%s', CURRENT_TIMESTAMP(), '%s', false)",
pSqlServer->GetPrefix(), pData->m_SaveState.ClrStr(), pData->m_Map.ClrStr(), pData->m_Code.ClrStr(), pData->m_Server
"INSERT IGNORE INTO %s_saves(Savegame, Map, Code, Timestamp, Server, SaveID, DDNet7) "
"VALUES ('%s', '%s', '%s', CURRENT_TIMESTAMP(), '%s', '%s', false)",
pSqlServer->GetPrefix(), SaveState.ClrStr(), pData->m_Map.ClrStr(),
pData->m_Code.ClrStr(), pData->m_Server, pData->m_Uuid
);
dbg_msg("sql", "%s", aBuf);
pSqlServer->executeSql(aBuf);
// be sure to keep all calls to pData->GameServer() after inserting the save, otherwise it might be lost due to CGameContextError.
char aBuf2[512];
str_format(aBuf2, sizeof(aBuf2), "Team successfully saved by %s. Use '/load %s' to continue", pData->m_ClientName, pData->m_Code.Str());
pData->GameServer()->SendChatTeam(Team, aBuf2);
str_format(pData->m_pResult->m_aMessage, sizeof(pData->m_pResult->m_aMessage),
"Team successfully saved by %s. Use '/load %s' to continue",
pData->m_ClientName, pData->m_Code.Str());
pData->m_pResult->m_Status = CSqlSaveResult::SAVE_SUCCESS;
}
else
{
dbg_msg("sql", "ERROR: This save-code already exists");
pData->GameServer()->SendChatTarget(pData->m_ClientID, "This save-code already exists");
pData->m_pResult->m_Status = CSqlSaveResult::SAVE_FAILED;
strcpy(pData->m_pResult->m_aMessage, "This save-code already exists");
}
}
catch (sql::SQLException &e)
{
pData->m_pResult->m_Status = CSqlSaveResult::SAVE_FAILED;
dbg_msg("sql", "MySQL Error: %s", e.what());
dbg_msg("sql", "ERROR: Could not save the team");
pData->GameServer()->SendChatTarget(pData->m_ClientID, "MySQL Error: Could not save the team");
strcpy(pData->m_pResult->m_aMessage, "MySQL Error: Could not save the team");
pSqlServer->executeSql("unlock tables;");
return false;
}
catch (CGameContextError &e)
{
dbg_msg("sql", "WARNING: Could not send chatmessage during saving team due to reload/change of map.");
}
}
catch (CGameContextError &e)
{
dbg_msg("sql", "WARNING: Aborted saving team due to reload/change of map.");
}
pSqlServer->executeSql("unlock tables;");
return true;
*/
return false;
}
void CSqlScore::LoadTeam(const char* Code, int ClientID)

View file

@ -5,7 +5,6 @@
#define GAME_SERVER_SCORE_SQL_H
#include <engine/server/sql_string_helpers.h>
#include <engine/shared/uuid_manager.h>
#include "../score.h"
@ -20,10 +19,40 @@ public:
{
DIRECT,
ALL,
} m_MessageTarget;
MAP_VOTE, // 3 Messages: Reason, Server, Map
} m_MessageKind;
char m_aaMessages[7][512];
};
class CSqlSaveResult {
public:
CSqlSaveResult(int PlayerID, IGameController* Controller) :
m_Status(SAVE_SUCCESS),
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;
};
class CSqlMapResult {
CSqlMapResult();
std::atomic_bool m_Done;
};
// result only valid if m_Done is set to true
class CSqlResult
{
@ -128,15 +157,16 @@ struct CSqlTeamScoreData : CSqlData<CSqlResult>
sqlstr::CSqlString<MAX_NAME_LENGTH> m_aNames[MAX_CLIENTS];
};
struct CSqlTeamSave : CSqlData<CSqlResult>
struct CSqlTeamSave : CSqlData<CSqlSaveResult>
{
virtual ~CSqlTeamSave();
using CSqlData<CSqlSaveResult>::CSqlData;
virtual ~CSqlTeamSave() {};
char m_ClientName[MAX_NAME_LENGTH];
CUuid m_SaveUuid;
sqlstr::CSqlString<128> m_Map;
sqlstr::CSqlString<128> m_Code;
sqlstr::CSqlString<65536> m_SaveState;
char m_Uuid[UUID_MAXSTRSIZE];
char m_Server[5];
};
@ -185,7 +215,7 @@ class CSqlScore: public IScore
static bool RandomMapThread(CSqlServer* pSqlServer, const CSqlData<CSqlResult> *pGameData, bool HandleFailure = false);
static bool RandomUnfinishedMapThread(CSqlServer* pSqlServer, const CSqlData<CSqlResult> *pGameData, bool HandleFailure = false);
static bool MapVoteThread(CSqlServer* pSqlServer, const CSqlData<CSqlResult> *pGameData, bool HandleFailure = false);
static bool MapVoteThread(CSqlServer* pSqlServer, const CSqlData<CSqlPlayerResult> *pGameData, bool HandleFailure = false);
static bool CheckBirthdayThread(CSqlServer* pSqlServer, const CSqlData<CSqlResult> *pGameData, bool HandleFailure = false);
static bool LoadScoreThread(CSqlServer* pSqlServer, const CSqlData<CSqlResult> *pGameData, bool HandleFailure = false);
@ -199,7 +229,7 @@ class CSqlScore: public IScore
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<CSqlResult> *pGameData, bool HandleFailure = false);
static bool SaveTeamThread(CSqlServer* pSqlServer, const CSqlData<CSqlSaveResult> *pGameData, bool HandleFailure = false);
static bool LoadTeamThread(CSqlServer* pSqlServer, const CSqlData<CSqlResult> *pGameData, bool HandleFailure = false);
static bool SaveScoreThread(CSqlServer* pSqlServer, const CSqlData<CSqlPlayerResult> *pGameData, bool HandleFailure = false);
@ -253,7 +283,7 @@ public:
virtual void GetSaves(int ClientID);
// requested by teams
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);
// Game relevant not allowed to fail

View file

@ -2,6 +2,9 @@
#include "teams.h"
#include "score.h"
#include <engine/shared/config.h>
#if defined(CONF_SQL)
#include "score/sql_score.h"
#endif
CGameTeams::CGameTeams(CGameContext *pGameContext) :
m_pGameContext(pGameContext)
@ -19,9 +22,11 @@ void CGameTeams::Reset()
m_MembersCount[i] = 0;
m_LastChat[i] = 0;
m_TeamLocked[i] = false;
m_IsSaving[i] = false;
m_Invited[i] = 0;
m_Practice[i] = false;
#if defined(CONF_SQL)
m_pSaveTeamResult[i] = nullptr;
#endif
}
}
@ -252,7 +257,8 @@ bool CGameTeams::SetCharacterTeam(int ClientID, int Team)
//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 && m_IsSaving[Team])
if((Team != TEAM_SUPER && GetSaving(Team))
|| (m_Core.Team(ClientID) != TEAM_SUPER && GetSaving(m_Core.Team(ClientID))))
return false;
SetForceCharacterTeam(ClientID, Team);
@ -331,6 +337,9 @@ void CGameTeams::ForceLeaveTeam(int ClientID)
SetTeamLock(m_Core.Team(ClientID), false);
ResetInvited(m_Core.Team(ClientID));
m_Practice[m_Core.Team(ClientID)] = false;
#if defined(CONF_SQL)
m_pSaveTeamResult[m_Core.Team(ClientID)] = nullptr;
#endif
}
}
@ -674,10 +683,47 @@ 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);
switch(m_pSaveTeamResult[Team]->m_Status)
{
case CSqlSaveResult::SAVE_SUCCESS:
{
ResetSavedTeam(m_pSaveTeamResult[Team]->m_RequestingPlayer, Team);
break;
}
case CSqlSaveResult::SAVE_FAILED:
{
int Result = m_pSaveTeamResult[Team]->m_SavedTeam.load(Team);
if(Result != 0)
CSaveTeam::HandleLoadError(Result, m_pSaveTeamResult[Team]->m_RequestingPlayer, m_pSaveTeamResult[Team]->m_SavedTeam, m_pGameContext);
break;
}
case CSqlSaveResult::LOAD_SUCCESS:
case CSqlSaveResult::LOAD_FAILED:
break; // TODO
}
m_pSaveTeamResult[Team] = nullptr;
}
}
#endif
void CGameTeams::OnCharacterSpawn(int ClientID)
{
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)])
// 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
@ -690,6 +736,8 @@ void CGameTeams::OnCharacterDeath(int ClientID, int Weapon)
m_Core.SetSolo(ClientID, false);
int Team = m_Core.Team(ClientID);
if(GetSaving(Team))
return;
bool Locked = TeamLocked(Team) && Weapon != WEAPON_GAME;
if(!Locked)
@ -750,30 +798,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++)
{
if(m_Core.Team(i) == Team && GameServer()->m_apPlayers[i])
{
GameServer()->m_apPlayers[i]->m_VotedForPractice = false;
GameServer()->m_apPlayers[i]->KillCharacter(WEAPON_SELF);
}
}
}
void CGameTeams::ResetSavedTeam(int ClientID, int Team)
{
for (int i = 0; i < MAX_CLIENTS; 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]->GetCharacter()->m_DDRaceState = DDRACE_NONE;
m_TeeFinished[i] = false;
SetForceCharacterTeam(i, 0);
}
}
for (int i = 0; i < MAX_CLIENTS; i++)
if(m_Core.Team(i) == Team && GameServer()->m_apPlayers[i])
GameServer()->m_apPlayers[i]->ThreadKillCharacter(-2);
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/server/gamecontext.h>
#if defined(CONF_SQL)
class CSqlSaveResult;
#endif
class CGameTeams
{
int m_TeamState[MAX_CLIENTS];
int m_MembersCount[MAX_CLIENTS];
bool m_TeeFinished[MAX_CLIENTS];
bool m_TeamLocked[MAX_CLIENTS];
bool m_IsSaving[MAX_CLIENTS];
uint64_t m_Invited[MAX_CLIENTS];
bool m_Practice[MAX_CLIENTS];
#if defined(CONF_SQL)
std::shared_ptr<CSqlSaveResult> m_pSaveTeamResult[MAX_CLIENTS];
#endif
class CGameContext * m_pGameContext;
@ -85,7 +91,11 @@ public:
void SetDDRaceState(CPlayer* Player, int DDRaceState);
void SetStartTime(CPlayer* Player, int StartTime);
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)
{
@ -114,15 +124,20 @@ public:
{
m_TeeFinished[ClientID] = finished;
}
void SetSaving(int TeamID, bool Value)
#if defined(CONF_SQL)
void SetSaving(int TeamID, std::shared_ptr<CSqlSaveResult> SaveResult)
{
m_IsSaving[TeamID] = Value;
m_pSaveTeamResult[TeamID] = SaveResult;
}
#endif
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)