229 changes: 133 additions & 96 deletions src/game/server/gamecontext.cpp
Expand Up @@ -131,7 +131,7 @@ bool CGameContext::EmulateBug(int Bug)
return m_MapBugs.Contains(Bug);
}

void CGameContext::FillAntibot(CAntibotData *pData)
void CGameContext::FillAntibot(CAntibotRoundData *pData)
{
if(!pData->m_Map.m_pTiles)
{
Expand Down Expand Up @@ -517,7 +517,7 @@ void CGameContext::SendVoteSet(int ClientID)

void CGameContext::SendVoteStatus(int ClientID, int Total, int Yes, int No)
{
if (Total > VANILLA_MAX_CLIENTS && m_apPlayers[ClientID] && m_apPlayers[ClientID]->m_ClientVersion <= VERSION_DDRACE)
if (Total > VANILLA_MAX_CLIENTS && m_apPlayers[ClientID] && m_apPlayers[ClientID]->GetClientVersion() <= VERSION_DDRACE)
{
Yes = float(Yes) * VANILLA_MAX_CLIENTS / float(Total);
No = float(No) * VANILLA_MAX_CLIENTS / float(Total);
Expand Down Expand Up @@ -592,15 +592,19 @@ void CGameContext::SendTuningParams(int ClientID, int Zone)
else
pParams = (int *)&(m_aTuningList[Zone]);

unsigned int last = sizeof(m_Tuning)/sizeof(int);
if (m_apPlayers[ClientID] && m_apPlayers[ClientID]->m_ClientVersion < VERSION_DDNET_EXTRATUNES)
last = 33;
else if (m_apPlayers[ClientID] && m_apPlayers[ClientID]->m_ClientVersion < VERSION_DDNET_HOOKDURATION_TUNE)
last = 37;
else if (m_apPlayers[ClientID] && m_apPlayers[ClientID]->m_ClientVersion < VERSION_DDNET_FIREDELAY_TUNE)
last = 38;
unsigned int Last = sizeof(m_Tuning)/sizeof(int);
if(m_apPlayers[ClientID])
{
int ClientVersion = m_apPlayers[ClientID]->GetClientVersion();
if(ClientVersion < VERSION_DDNET_EXTRATUNES)
Last = 33;
else if(ClientVersion < VERSION_DDNET_HOOKDURATION_TUNE)
Last = 37;
else if(ClientVersion < VERSION_DDNET_FIREDELAY_TUNE)
Last = 38;
}

for(unsigned i = 0; i < last; i++)
for(unsigned i = 0; i < Last; i++)
{
if (m_apPlayers[ClientID] && m_apPlayers[ClientID]->GetCharacter())
{
Expand Down Expand Up @@ -1095,6 +1099,13 @@ void CGameContext::OnClientEnter(int ClientID)

if(!Server()->ClientPrevIngame(ClientID))
{
IServer::CClientInfo Info;
Server()->GetClientInfo(ClientID, &Info);
if(Info.m_GotDDNetVersion)
{
OnClientDDNetVersionKnown(ClientID);
}

char aBuf[512];
str_format(aBuf, sizeof(aBuf), "'%s' entered and joined the %s", Server()->ClientName(ClientID), m_pController->GetTeamName(m_apPlayers[ClientID]->GetTeam()));
SendChat(-1, CGameContext::CHAT_ALL, aBuf);
Expand Down Expand Up @@ -1218,6 +1229,79 @@ void CGameContext::OnClientEngineDrop(int ClientID, const char *pReason)
}
}

void CGameContext::OnClientDDNetVersionKnown(int ClientID)
{
IServer::CClientInfo Info;
Server()->GetClientInfo(ClientID, &Info);
int ClientVersion = Info.m_DDNetVersion;
dbg_msg("ddnet", "cid=%d version=%d", ClientID, ClientVersion);

if(m_TeeHistorianActive)
{
if(Info.m_pConnectionID && Info.m_pDDNetVersionStr)
{
m_TeeHistorian.RecordDDNetVersion(ClientID, *Info.m_pConnectionID, ClientVersion, Info.m_pDDNetVersionStr);
}
else
{
m_TeeHistorian.RecordDDNetVersionOld(ClientID, ClientVersion);
}
}

CPlayer *pPlayer = m_apPlayers[ClientID];
if(ClientVersion >= VERSION_DDNET_GAMETICK)
pPlayer->m_TimerType = g_Config.m_SvDefaultTimerType;

//first update his teams state
((CGameControllerDDRace *)m_pController)->m_Teams.SendTeamsState(ClientID);

//second give him records
SendRecord(ClientID);

//third give him others current time for table score
if(g_Config.m_SvHideScore)
{
return;
}
for(int i = 0; i < MAX_CLIENTS; i++)
{
if(m_apPlayers[i] && Score()->PlayerData(i)->m_CurrentTime > 0)
{
CNetMsg_Sv_PlayerTime Msg;
Msg.m_Time = Score()->PlayerData(i)->m_CurrentTime * 100;
Msg.m_ClientID = i;
Server()->SendPackMsg(&Msg, MSGFLAG_VITAL, ClientID);
//also send its time to others

}
}
//also send its time to others
if(Score()->PlayerData(ClientID)->m_CurrentTime > 0)
{
//TODO: make function for this fucking steps
CNetMsg_Sv_PlayerTime Msg;
Msg.m_Time = Score()->PlayerData(ClientID)->m_CurrentTime * 100;
Msg.m_ClientID = ClientID;
Server()->SendPackMsg(&Msg, MSGFLAG_VITAL, -1);
}

//and give him correct tunings
if (ClientVersion >= VERSION_DDNET_EXTRATUNES)
SendTuningParams(ClientID, pPlayer->m_TuneZone);

//tell old clients to update
if (ClientVersion < VERSION_DDNET_UPDATER_FIXED && g_Config.m_SvClientSuggestionOld[0] != '\0')
SendBroadcast(g_Config.m_SvClientSuggestionOld, ClientID);
//tell known bot clients that they're botting and we know it
if (((ClientVersion >= 15 && ClientVersion < 100) || ClientVersion == 502) && g_Config.m_SvClientSuggestionBot[0] != '\0')
SendBroadcast(g_Config.m_SvClientSuggestionBot, ClientID);
//autoban known bot versions
if(g_Config.m_SvBannedVersions[0] != '\0' && IsVersionBanned(ClientVersion))
{
Server()->Kick(ClientID, "unsupported client");
}
}

void CGameContext::OnMessage(int MsgID, CUnpacker *pUnpacker, int ClientID)
{
void *pRawMsg = m_NetObjHandler.SecureUnpackMsg(MsgID, pUnpacker);
Expand Down Expand Up @@ -1302,27 +1386,27 @@ void CGameContext::OnMessage(int MsgID, CUnpacker *pUnpacker, int ClientID)
{
if (str_comp_nocase_num(pMsg->m_pMessage+1, "w ", 2) == 0)
{
char pWhisperMsg[256];
str_copy(pWhisperMsg, pMsg->m_pMessage + 3, 256);
Whisper(pPlayer->GetCID(), pWhisperMsg);
char aWhisperMsg[256];
str_copy(aWhisperMsg, pMsg->m_pMessage + 3, 256);
Whisper(pPlayer->GetCID(), aWhisperMsg);
}
else if (str_comp_nocase_num(pMsg->m_pMessage+1, "whisper ", 8) == 0)
{
char pWhisperMsg[256];
str_copy(pWhisperMsg, pMsg->m_pMessage + 9, 256);
Whisper(pPlayer->GetCID(), pWhisperMsg);
char aWhisperMsg[256];
str_copy(aWhisperMsg, pMsg->m_pMessage + 9, 256);
Whisper(pPlayer->GetCID(), aWhisperMsg);
}
else if (str_comp_nocase_num(pMsg->m_pMessage+1, "c ", 2) == 0)
{
char pWhisperMsg[256];
str_copy(pWhisperMsg, pMsg->m_pMessage + 3, 256);
Converse(pPlayer->GetCID(), pWhisperMsg);
char aWhisperMsg[256];
str_copy(aWhisperMsg, pMsg->m_pMessage + 3, 256);
Converse(pPlayer->GetCID(), aWhisperMsg);
}
else if (str_comp_nocase_num(pMsg->m_pMessage+1, "converse ", 9) == 0)
{
char pWhisperMsg[256];
str_copy(pWhisperMsg, pMsg->m_pMessage + 10, 256);
Converse(pPlayer->GetCID(), pWhisperMsg);
char aWhisperMsg[256];
str_copy(aWhisperMsg, pMsg->m_pMessage + 10, 256);
Converse(pPlayer->GetCID(), aWhisperMsg);
}
else
{
Expand Down Expand Up @@ -1752,66 +1836,19 @@ void CGameContext::OnMessage(int MsgID, CUnpacker *pUnpacker, int ClientID)
}
else if (MsgID == NETMSGTYPE_CL_ISDDNET)
{
int Version = pUnpacker->GetInt();

if (pUnpacker->Error())
{
if (pPlayer->m_ClientVersion < VERSION_DDRACE)
pPlayer->m_ClientVersion = VERSION_DDRACE;
}
else if(pPlayer->m_ClientVersion < Version)
pPlayer->m_ClientVersion = Version;

if(pPlayer->m_ClientVersion >= VERSION_DDNET_GAMETICK)
pPlayer->m_TimerType = g_Config.m_SvDefaultTimerType;

dbg_msg("ddnet", "%d using Custom Client %d", ClientID, pPlayer->m_ClientVersion);

//first update his teams state
((CGameControllerDDRace*)m_pController)->m_Teams.SendTeamsState(ClientID);

//second give him records
SendRecord(ClientID);

//third give him others current time for table score
if(g_Config.m_SvHideScore) return;
for(int i = 0; i < MAX_CLIENTS; i++)
{
if(m_apPlayers[i] && Score()->PlayerData(i)->m_CurrentTime > 0)
{
CNetMsg_Sv_PlayerTime Msg;
Msg.m_Time = Score()->PlayerData(i)->m_CurrentTime * 100;
Msg.m_ClientID = i;
Server()->SendPackMsg(&Msg, MSGFLAG_VITAL, ClientID);
//also send its time to others

}
}
//also send its time to others
if(Score()->PlayerData(ClientID)->m_CurrentTime > 0)
IServer::CClientInfo Info;
Server()->GetClientInfo(ClientID, &Info);
if(Info.m_GotDDNetVersion)
{
//TODO: make function for this fucking steps
CNetMsg_Sv_PlayerTime Msg;
Msg.m_Time = Score()->PlayerData(ClientID)->m_CurrentTime * 100;
Msg.m_ClientID = ClientID;
Server()->SendPackMsg(&Msg, MSGFLAG_VITAL, -1);
return;
}

//and give him correct tunings
if (Version >= VERSION_DDNET_EXTRATUNES)
SendTuningParams(ClientID, pPlayer->m_TuneZone);

//tell old clients to update
if (Version < VERSION_DDNET_UPDATER_FIXED && g_Config.m_SvClientSuggestionOld[0] != '\0')
SendBroadcast(g_Config.m_SvClientSuggestionOld, ClientID);
//tell known bot clients that they're botting and we know it
if (((Version >= 15 && Version < 100) || Version == 502) && g_Config.m_SvClientSuggestionBot[0] != '\0')
SendBroadcast(g_Config.m_SvClientSuggestionBot, ClientID);
//autoban known bot versions
if(g_Config.m_SvBannedVersions[0] != '\0' && IsVersionBanned(Version))
int DDNetVersion = pUnpacker->GetInt();
if(pUnpacker->Error() || DDNetVersion < 0)
{
Server()->Kick(ClientID, "unsupported client");
DDNetVersion = VERSION_DDRACE;
}
Server()->SetClientDDNetVersion(ClientID, DDNetVersion);
OnClientDDNetVersionKnown(ClientID);
}
else if (MsgID == NETMSGTYPE_CL_SHOWOTHERS)
{
Expand Down Expand Up @@ -2617,13 +2654,19 @@ void CGameContext::OnInit(/*class IKernel *pKernel*/)
m_pConsole = Kernel()->RequestInterface<IConsole>();
m_pEngine = Kernel()->RequestInterface<IEngine>();
m_pStorage = Kernel()->RequestInterface<IStorage>();
m_Antibot.Init(this);
m_pAntibot = Kernel()->RequestInterface<IAntibot>();
m_pAntibot->RoundStart(this);
m_World.SetGameServer(this);
m_Events.SetGameServer(this);

m_GameUuid = RandomUuid();
Console()->SetTeeHistorianCommandCallback(CommandCallback, this);

uint64 aSeed[2];
secure_random_fill(aSeed, sizeof(aSeed));
m_Prng.Seed(aSeed);
m_World.m_Core.m_pPrng = &m_Prng;

DeleteTempfile();

//if(!data) // only load once
Expand Down Expand Up @@ -2738,6 +2781,7 @@ void CGameContext::OnInit(/*class IKernel *pKernel*/)
GameInfo.m_GameUuid = m_GameUuid;
GameInfo.m_pServerVersion = aVersion;
GameInfo.m_StartTime = time(0);
GameInfo.m_pPrngDescription = m_Prng.Description();

GameInfo.m_pServerName = g_Config.m_SvName;
GameInfo.m_ServerPort = g_Config.m_SvPort;
Expand Down Expand Up @@ -3044,6 +3088,8 @@ void CGameContext::OnShutdown(bool FullShutdown)
if (FullShutdown)
Score()->OnShutdown();

Antibot()->RoundEnd();

if(m_TeeHistorianActive)
{
m_TeeHistorian.Finish();
Expand Down Expand Up @@ -3427,18 +3473,18 @@ void CGameContext::Whisper(int ClientID, char *pStr)

void CGameContext::WhisperID(int ClientID, int VictimID, char *pMessage)
{
if (!CheckClientID2(ClientID))
if(!CheckClientID2(ClientID))
return;

if (!CheckClientID2(VictimID))
if(!CheckClientID2(VictimID))
return;

if (m_apPlayers[ClientID])
if(m_apPlayers[ClientID])
m_apPlayers[ClientID]->m_LastWhisperTo = VictimID;

char aBuf[256];

if (m_apPlayers[ClientID] && m_apPlayers[ClientID]->m_ClientVersion >= VERSION_DDNET_WHISPER)
if(GetClientVersion(ClientID) >= VERSION_DDNET_WHISPER)
{
CNetMsg_Sv_Chat Msg;
Msg.m_Team = CHAT_WHISPER_SEND;
Expand All @@ -3455,7 +3501,7 @@ void CGameContext::WhisperID(int ClientID, int VictimID, char *pMessage)
SendChatTarget(ClientID, aBuf);
}

if (m_apPlayers[VictimID] && m_apPlayers[VictimID]->m_ClientVersion >= VERSION_DDNET_WHISPER)
if(GetClientVersion(VictimID) >= VERSION_DDNET_WHISPER)
{
CNetMsg_Sv_Chat Msg2;
Msg2.m_Team = CHAT_WHISPER_RECV;
Expand Down Expand Up @@ -3541,18 +3587,9 @@ void CGameContext::List(int ClientID, const char *pFilter)

int CGameContext::GetClientVersion(int ClientID)
{
return m_apPlayers[ClientID]
? m_apPlayers[ClientID]->m_ClientVersion
: 0;
}

void CGameContext::SetClientVersion(int ClientID, int Version)
{
if(!m_apPlayers[ClientID])
{
return;
}
m_apPlayers[ClientID]->m_ClientVersion = Version;
IServer::CClientInfo Info = {0};
Server()->GetClientInfo(ClientID, &Info);
return Info.m_DDNetVersion;
}

bool CGameContext::PlayerModerating()
Expand Down
17 changes: 9 additions & 8 deletions src/game/server/gamecontext.h
Expand Up @@ -3,6 +3,7 @@
#ifndef GAME_SERVER_GAMECONTEXT_H
#define GAME_SERVER_GAMECONTEXT_H

#include <engine/antibot.h>
#include <engine/server.h>
#include <engine/console.h>
#include <engine/shared/memheap.h>
Expand All @@ -11,7 +12,6 @@
#include <game/mapbugs.h>
#include <game/voting.h>

#include "antibot.h"
#include "eventhandler.h"
#include "gamecontroller.h"
#include "gameworld.h"
Expand Down Expand Up @@ -67,6 +67,7 @@ class CGameContext : public IGameServer
IConsole *m_pConsole;
IEngine *m_pEngine;
IStorage *m_pStorage;
IAntibot *m_pAntibot;
CLayers m_Layers;
CCollision m_Collision;
CNetObjHandler m_NetObjHandler;
Expand All @@ -78,7 +79,7 @@ class CGameContext : public IGameServer
ASYNCIO *m_pTeeHistorianFile;
CUuid m_GameUuid;
CMapBugs m_MapBugs;
CAntibot m_Antibot;
CPrng m_Prng;

std::shared_ptr<CRandomMapResult> m_pRandomMapResult;
std::shared_ptr<CMapVoteResult> m_pMapVoteResult;
Expand All @@ -101,8 +102,6 @@ class CGameContext : public IGameServer
static void ConChangeMap(IConsole::IResult *pResult, void *pUserData);
static void ConRandomMap(IConsole::IResult *pResult, void *pUserData);
static void ConRandomUnfinishedMap(IConsole::IResult *pResult, void *pUserData);
static void ConSaveTeam(IConsole::IResult *pResult, void *pUserData);
static void ConLoadTeam(IConsole::IResult *pResult, void *pUserData);
static void ConRestart(IConsole::IResult *pResult, void *pUserData);
static void ConBroadcast(IConsole::IResult *pResult, void *pUserData);
static void ConSay(IConsole::IResult *pResult, void *pUserData);
Expand Down Expand Up @@ -130,7 +129,7 @@ class CGameContext : public IGameServer
CCollision *Collision() { return &m_Collision; }
CTuningParams *Tuning() { return &m_Tuning; }
CTuningParams *TuningList() { return &m_aTuningList[0]; }
CAntibot *Antibot() { return &m_Antibot; }
IAntibot *Antibot() { return m_pAntibot; }

CGameContext();
~CGameContext();
Expand All @@ -146,7 +145,6 @@ class CGameContext : public IGameServer
// helper functions
class CCharacter *GetPlayerChar(int ClientID);
bool EmulateBug(int Bug);
void FillAntibot(CAntibotData *pData);

// voting
void StartVote(const char *pDesc, const char *pCommand, const char *pReason);
Expand Down Expand Up @@ -253,14 +251,14 @@ class CGameContext : public IGameServer
virtual const char *NetVersion();

// DDRace

void OnClientDDNetVersionKnown(int ClientID);
virtual void FillAntibot(CAntibotRoundData *pData);
int ProcessSpamProtection(int ClientID);
int GetDDRaceTeam(int ClientID);
// Describes the time when the first player joined the server.
int64 m_NonEmptySince;
int64 m_LastMapVote;
int GetClientVersion(int ClientID);
void SetClientVersion(int ClientID, int Version);
bool PlayerExists(int ClientID) { return m_apPlayers[ClientID]; };
// Returns true if someone is actively moderating.
bool PlayerModerating();
Expand Down Expand Up @@ -329,6 +327,7 @@ class CGameContext : public IGameServer
static void ConDND(IConsole::IResult *pResult, void *pUserData);
static void ConMapInfo(IConsole::IResult *pResult, void *pUserData);
static void ConTimeout(IConsole::IResult *pResult, void *pUserData);
static void ConPractice(IConsole::IResult *pResult, void *pUserData);
static void ConSave(IConsole::IResult *pResult, void *pUserData);
static void ConLoad(IConsole::IResult *pResult, void *pUserData);
static void ConMap(IConsole::IResult *pResult, void *pUserData);
Expand All @@ -337,6 +336,7 @@ class CGameContext : public IGameServer
static void ConBroadTime(IConsole::IResult *pResult, void *pUserData);
static void ConJoinTeam(IConsole::IResult *pResult, void *pUserData);
static void ConLockTeam(IConsole::IResult *pResult, void *pUserData);
static void ConUnlockTeam(IConsole::IResult *pResult, void *pUserData);
static void ConInviteTeam(IConsole::IResult *pResult, void *pUserData);
static void ConMe(IConsole::IResult *pResult, void *pUserData);
static void ConWhisper(IConsole::IResult *pResult, void *pUserData);
Expand Down Expand Up @@ -396,6 +396,7 @@ class CGameContext : public IGameServer
void WhisperID(int ClientID, int VictimID, char *pMessage);
void Converse(int ClientID, char *pStr);
bool IsVersionBanned(int Version);
void UnlockTeam(int ClientID, int Team);

public:
CLayers *Layers() { return &m_Layers; }
Expand Down
2 changes: 1 addition & 1 deletion src/game/server/gamecontroller.cpp
Expand Up @@ -516,7 +516,7 @@ void IGameController::Snap(int SnappingClient)
CPlayer *pPlayer = SnappingClient > -1 ? GameServer()->m_apPlayers[SnappingClient] : 0;
CPlayer *pPlayer2;

if(pPlayer && (pPlayer->m_TimerType == CPlayer::TIMERTYPE_GAMETIMER || pPlayer->m_TimerType == CPlayer::TIMERTYPE_GAMETIMER_AND_BROADCAST) && pPlayer->m_ClientVersion >= VERSION_DDNET_GAMETICK)
if(pPlayer && (pPlayer->m_TimerType == CPlayer::TIMERTYPE_GAMETIMER || pPlayer->m_TimerType == CPlayer::TIMERTYPE_GAMETIMER_AND_BROADCAST) && pPlayer->GetClientVersion() >= VERSION_DDNET_GAMETICK)
{
if((pPlayer->GetTeam() == -1 || pPlayer->IsPaused())
&& pPlayer->m_SpectatorID != SPEC_FREEVIEW
Expand Down
4 changes: 2 additions & 2 deletions src/game/server/gameworld.cpp
Expand Up @@ -186,8 +186,8 @@ void CGameWorld::UpdatePlayerMaps()
!GameServer()->m_apPlayers[i]->IsPaused() && GameServer()->m_apPlayers[i]->GetTeam() != -1 &&
!ch->CanCollide(i) &&
(!GameServer()->m_apPlayers[i] ||
GameServer()->m_apPlayers[i]->m_ClientVersion == VERSION_VANILLA ||
(GameServer()->m_apPlayers[i]->m_ClientVersion >= VERSION_DDRACE &&
GameServer()->m_apPlayers[i]->GetClientVersion() == VERSION_VANILLA ||
(GameServer()->m_apPlayers[i]->GetClientVersion() >= VERSION_DDRACE &&
!GameServer()->m_apPlayers[i]->m_ShowOthers
)
)
Expand Down
21 changes: 12 additions & 9 deletions src/game/server/player.cpp
Expand Up @@ -103,7 +103,6 @@ void CPlayer::Reset()

GameServer()->Score()->PlayerData(m_ClientID)->Reset();

m_ClientVersion = VERSION_VANILLA;
m_ShowOthers = g_Config.m_SvShowOthersDefault;
m_ShowAll = g_Config.m_SvShowAllDefault;
m_SpecTeam = 0;
Expand Down Expand Up @@ -136,6 +135,7 @@ void CPlayer::Reset()

m_NotEligibleForFinish = false;
m_EligibleForFinishCheck = 0;
m_VotedForPractice = false;
}

void CPlayer::Tick()
Expand Down Expand Up @@ -296,12 +296,13 @@ void CPlayer::Snap(int SnappingClient)
if(!pPlayerInfo)
return;

int ClientVersion = GetClientVersion();
pPlayerInfo->m_Latency = SnappingClient == -1 ? m_Latency.m_Min : GameServer()->m_apPlayers[SnappingClient]->m_aActLatency[m_ClientID];
pPlayerInfo->m_Local = (int)(m_ClientID == SnappingClient && (m_Paused != PAUSE_PAUSED || m_ClientVersion >= VERSION_DDNET_OLD));
pPlayerInfo->m_Local = (int)(m_ClientID == SnappingClient && (m_Paused != PAUSE_PAUSED || ClientVersion >= VERSION_DDNET_OLD));
pPlayerInfo->m_ClientID = id;
pPlayerInfo->m_Team = (m_ClientVersion < VERSION_DDNET_OLD || m_Paused != PAUSE_PAUSED || m_ClientID != SnappingClient) && m_Paused < PAUSE_SPEC ? m_Team : TEAM_SPECTATORS;
pPlayerInfo->m_Team = (ClientVersion < VERSION_DDNET_OLD || m_Paused != PAUSE_PAUSED || m_ClientID != SnappingClient) && m_Paused < PAUSE_SPEC ? m_Team : TEAM_SPECTATORS;

if(m_ClientID == SnappingClient && m_Paused == PAUSE_PAUSED && m_ClientVersion < VERSION_DDNET_OLD)
if(m_ClientID == SnappingClient && m_Paused == PAUSE_PAUSED && ClientVersion < VERSION_DDNET_OLD)
pPlayerInfo->m_Team = TEAM_SPECTATORS;

// send 0 if times of others are not shown
Expand Down Expand Up @@ -337,10 +338,7 @@ void CPlayer::Snap(int SnappingClient)

void CPlayer::FakeSnap()
{
// This is problematic when it's sent before we know whether it's a non-64-player-client
// Then we can't spectate players at the start

if(m_ClientVersion >= VERSION_DDNET_OLD)
if(GetClientVersion() >= VERSION_DDNET_OLD)
return;

int FakeID = VANILLA_MAX_CLIENTS - 1;
Expand Down Expand Up @@ -421,7 +419,7 @@ void CPlayer::OnPredictedInput(CNetObj_PlayerInput *NewInput)
// Magic number when we can hope that client has successfully identified itself
if(m_NumInputs == 20)
{
if(g_Config.m_SvClientSuggestion[0] != '\0' && m_ClientVersion <= VERSION_DDNET_OLD)
if(g_Config.m_SvClientSuggestion[0] != '\0' && GetClientVersion() <= VERSION_DDNET_OLD)
GameServer()->SendBroadcast(g_Config.m_SvClientSuggestion, m_ClientID);
}
}
Expand Down Expand Up @@ -481,6 +479,11 @@ void CPlayer::OnPredictedEarlyInput(CNetObj_PlayerInput *NewInput)
m_pCharacter->OnDirectInput(NewInput);
}

int CPlayer::GetClientVersion() const
{
return m_pGameServer->GetClientVersion(m_ClientID);
}

CCharacter *CPlayer::GetCharacter()
{
if(m_pCharacter && m_pCharacter->IsAlive())
Expand Down
3 changes: 2 additions & 1 deletion src/game/server/player.h
Expand Up @@ -26,6 +26,7 @@ class CPlayer
void SetTeam(int Team, bool DoChatMsg=true);
int GetTeam() const { return m_Team; };
int GetCID() const { return m_ClientID; };
int GetClientVersion() const;

void Tick();
void PostTick();
Expand Down Expand Up @@ -162,7 +163,6 @@ class CPlayer
bool IsPlaying();
int64 m_Last_KickVote;
int64 m_Last_Team;
int m_ClientVersion;
bool m_ShowOthers;
bool m_ShowAll;
bool m_SpecTeam;
Expand Down Expand Up @@ -198,6 +198,7 @@ class CPlayer
#endif
bool m_NotEligibleForFinish;
int64 m_EligibleForFinishCheck;
bool m_VotedForPractice;
};

#endif
257 changes: 190 additions & 67 deletions src/game/server/save.cpp

Large diffs are not rendered by default.

17 changes: 9 additions & 8 deletions src/game/server/save.h
Expand Up @@ -14,12 +14,12 @@ class CSaveTee
char* GetString();
int LoadString(char* String);
vec2 GetPos() { return m_Pos; }
char* GetName() { return m_name; }
char* GetName() { return m_aName; }

private:

char m_String [2048];
char m_name [16];
char m_aString [2048];
char m_aName [16];

int m_Alive;
int m_Paused;
Expand Down Expand Up @@ -64,7 +64,7 @@ class CSaveTee
int m_CpTime;
int m_CpActive;
int m_CpLastBroadcast;
float m_CpCurrent[25];
float m_aCpCurrent[25];

int m_NotEligibleForFinish;

Expand All @@ -85,7 +85,7 @@ class CSaveTee
int m_HookTick;
int m_HookState;

char aGameUuid[16];
char m_aGameUuid[UUID_MAXSTRSIZE];
};

class CSaveTeam
Expand All @@ -98,7 +98,7 @@ class CSaveTeam
int LoadString(const char* String);
int save(int Team);
int load(int Team);
CSaveTee* SavedTees;
CSaveTee* m_pSavedTees;

static bool HandleSaveError(int Result, int ClientID, CGameContext *pGameContext);
private:
Expand All @@ -107,20 +107,21 @@ class CSaveTeam

IGameController* m_pController;

char m_String[65536];
char m_aString[65536];

struct SSimpleSwitchers
{
int m_Status;
int m_EndTime;
int m_Type;
};
SSimpleSwitchers* m_Switchers;
SSimpleSwitchers* m_pSwitchers;

int m_TeamState;
int m_MembersCount;
int m_NumSwitchers;
int m_TeamLocked;
int m_Practice;
};

#endif // GAME_SERVER_SAVE_H
1 change: 1 addition & 0 deletions src/game/server/score.h
Expand Up @@ -93,6 +93,7 @@ class IScore

virtual void SaveTeam(int Team, const char *pCode, int ClientID, const char *pServer) = 0;
virtual void LoadTeam(const char *pCode, int ClientID) = 0;
virtual void GetSaves(int ClientID) = 0;

// called when the server is shut down but not on mapchange/reload
virtual void OnShutdown() = 0;
Expand Down
7 changes: 7 additions & 0 deletions src/game/server/score/file_score.cpp
Expand Up @@ -358,6 +358,13 @@ void CFileScore::LoadTeam(const char* Code, int ClientID)
GameServer()->SendChatTarget(ClientID, aBuf);
}

void CFileScore::GetSaves(int ClientID)
{
char aBuf[512];
str_format(aBuf, sizeof(aBuf), "Save-function not supported in file based servers");
GameServer()->SendChatTarget(ClientID, aBuf);
}

void CFileScore::OnShutdown()
{
;
Expand Down
1 change: 1 addition & 0 deletions src/game/server/score/file_score.h
Expand Up @@ -85,6 +85,7 @@ class CFileScore: public IScore
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();

Expand Down
104 changes: 81 additions & 23 deletions src/game/server/score/sql_score.cpp
Expand Up @@ -410,18 +410,18 @@ bool CSqlScore::MapInfoThread(CSqlServer* pSqlServer, const CSqlData *pGameData,
int ago = pSqlServer->GetResults()->getInt("Ago");
float ownTime = (float)pSqlServer->GetResults()->getDouble("OwnTime");

char pAgoString[40] = "\0";
char pReleasedString[60] = "\0";
char aAgoString[40] = "\0";
char aReleasedString[60] = "\0";
if(stamp != 0)
{
sqlstr::AgoTimeToString(ago, pAgoString);
str_format(pReleasedString, sizeof(pReleasedString), ", released %s ago", pAgoString);
sqlstr::AgoTimeToString(ago, aAgoString);
str_format(aReleasedString, sizeof(aReleasedString), ", released %s ago", aAgoString);
}

char pAverageString[60] = "\0";
char aAverageString[60] = "\0";
if(average > 0)
{
str_format(pAverageString, sizeof(pAverageString), " in %d:%02d average", average / 60, average % 60);
str_format(aAverageString, sizeof(aAverageString), " in %d:%02d average", average / 60, average % 60);
}

char aStars[20];
Expand All @@ -436,13 +436,13 @@ bool CSqlScore::MapInfoThread(CSqlServer* pSqlServer, const CSqlData *pGameData,
default: aStars[0] = '\0';
}

char pOwnFinishesString[40] = "\0";
char aOwnFinishesString[40] = "\0";
if(ownTime > 0)
{
str_format(pOwnFinishesString, sizeof(pOwnFinishesString), ", your time: %02d:%05.2f", (int)(ownTime/60), ownTime-((int)ownTime/60*60));
str_format(aOwnFinishesString, sizeof(aOwnFinishesString), ", your time: %02d:%05.2f", (int)(ownTime/60), ownTime-((int)ownTime/60*60));
}

str_format(aBuf, sizeof(aBuf), "\"%s\" by %s on %s, %s, %d %s%s, %d %s by %d %s%s%s", aMap, aMapper, aServer, aStars, points, points == 1 ? "point" : "points", pReleasedString, finishes, finishes == 1 ? "finish" : "finishes", finishers, finishers == 1 ? "tee" : "tees", pAverageString, pOwnFinishesString);
str_format(aBuf, sizeof(aBuf), "\"%s\" by %s on %s, %s, %d %s%s, %d %s by %d %s%s%s", aMap, aMapper, aServer, aStars, points, points == 1 ? "point" : "points", aReleasedString, finishes, finishes == 1 ? "finish" : "finishes", finishers, finishers == 1 ? "tee" : "tees", aAverageString, aOwnFinishesString);
}

pData->GameServer()->SendChatTarget(pData->m_ClientID, aBuf);
Expand Down Expand Up @@ -596,8 +596,8 @@ bool CSqlScore::SaveTeamScoreThread(CSqlServer* pSqlServer, const CSqlData *pGam
IOHANDLE File = io_open(g_Config.m_SvSqlFailureFile, IOFLAG_APPEND);
if(File)
{
const char pUUID[] = "SET @id = UUID();";
io_write(File, pUUID, sizeof(pUUID) - 1);
const char aUUID[] = "SET @id = UUID();";
io_write(File, aUUID, sizeof(aUUID) - 1);
io_write_newline(File);

char aBuf[2300];
Expand Down Expand Up @@ -1099,26 +1099,26 @@ bool CSqlScore::ShowTimesThread(CSqlServer* pSqlServer, const CSqlData *pGameDat

while(pSqlServer->GetResults()->next())
{
char pAgoString[40] = "\0";
char aAgoString[40] = "\0";
pSince = pSqlServer->GetResults()->getInt("Ago");
pStamp = pSqlServer->GetResults()->getInt("Stamp");
pTime = (float)pSqlServer->GetResults()->getDouble("Time");

sqlstr::AgoTimeToString(pSince,pAgoString);
sqlstr::AgoTimeToString(pSince,aAgoString);

if(pData->m_Search) // last 5 times of a player
{
if(pStamp == 0) // stamp is 00:00:00 cause it's an old entry from old times where there where no stamps yet
str_format(aBuf, sizeof(aBuf), "%02d:%05.02f, don't know how long ago", (int)(pTime/60), pTime-((int)pTime/60*60));
else
str_format(aBuf, sizeof(aBuf), "%s ago, %02d:%05.02f", pAgoString, (int)(pTime/60), pTime-((int)pTime/60*60));
str_format(aBuf, sizeof(aBuf), "%s ago, %02d:%05.02f", aAgoString, (int)(pTime/60), pTime-((int)pTime/60*60));
}
else // last 5 times of the server
{
if(pStamp == 0) // stamp is 00:00:00 cause it's an old entry from old times where there where no stamps yet
str_format(aBuf, sizeof(aBuf), "%s, %02d:%05.02f, don't know when", pSqlServer->GetResults()->getString("Name").c_str(), (int)(pTime/60), pTime-((int)pTime/60*60));
else
str_format(aBuf, sizeof(aBuf), "%s, %s ago, %02d:%05.02f", pSqlServer->GetResults()->getString("Name").c_str(), pAgoString, (int)(pTime/60), pTime-((int)pTime/60*60));
str_format(aBuf, sizeof(aBuf), "%s, %s ago, %02d:%05.02f", pSqlServer->GetResults()->getString("Name").c_str(), aAgoString, (int)(pTime/60), pTime-((int)pTime/60*60));
}
pData->GameServer()->SendChatTarget(pData->m_ClientID, aBuf);
}
Expand Down Expand Up @@ -1389,6 +1389,7 @@ void CSqlScore::SaveTeam(int Team, const char* Code, int ClientID, const char* S
Tmp->m_ClientID = ClientID;
Tmp->m_Code = Code;
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));

thread_init_and_detach(ExecSqlFunc, new CSqlExecData(SaveTeamThread, Tmp, false), "save team");
}
Expand Down Expand Up @@ -1468,8 +1469,8 @@ bool CSqlScore::SaveTeamThread(CSqlServer* pSqlServer, const CSqlData *pGameData

// be sure to keep all calls to pData->GameServer() after inserting the save, otherwise it might be lost due to CGameContextError.

char aBuf2[256];
str_format(aBuf2, sizeof(aBuf2), "Team successfully saved. Use '/load %s' to continue", pData->m_Code.Str());
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);
((CGameControllerDDRace*)(pData->GameServer()->m_pController))->m_Teams.KillSavedTeam(Team);
}
Expand Down Expand Up @@ -1507,6 +1508,7 @@ void CSqlScore::LoadTeam(const char* Code, int ClientID)
CSqlTeamLoad *Tmp = new CSqlTeamLoad();
Tmp->m_Code = Code;
Tmp->m_ClientID = ClientID;
str_copy(Tmp->m_ClientName, Server()->ClientName(Tmp->m_ClientID), sizeof(Tmp->m_ClientName));

thread_init_and_detach(ExecSqlFunc, new CSqlExecData(LoadTeamThread, Tmp), "load team");
}
Expand Down Expand Up @@ -1556,11 +1558,10 @@ bool CSqlScore::LoadTeamThread(CSqlServer* pSqlServer, const CSqlData *pGameData
pData->GameServer()->SendChatTarget(pData->m_ClientID, "Unable to load savegame: data corrupted");
else
{

bool Found = false;
for (int i = 0; i < SavedTeam.GetMembersCount(); i++)
{
if(str_comp(SavedTeam.SavedTees[i].GetName(), pData->Server()->ClientName(pData->m_ClientID)) == 0)
if(str_comp(SavedTeam.m_pSavedTees[i].GetName(), pData->Server()->ClientName(pData->m_ClientID)) == 0)
{
Found = true;
break;
Expand All @@ -1587,25 +1588,26 @@ bool CSqlScore::LoadTeamThread(CSqlServer* pSqlServer, const CSqlData *pGameData
else if(Num >= 10 && Num < 100)
{
char aBuf[256];
str_format(aBuf, sizeof(aBuf), "Unable to find player: '%s'", SavedTeam.SavedTees[Num-10].GetName());
str_format(aBuf, sizeof(aBuf), "Unable to find player: '%s'", SavedTeam.m_pSavedTees[Num-10].GetName());
pData->GameServer()->SendChatTarget(pData->m_ClientID, aBuf);
}
else if(Num >= 100 && Num < 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.SavedTees[Num-100].GetName());
str_format(aBuf, sizeof(aBuf), "%s is racing right now, Team can't be loaded if a Tee is racing already", SavedTeam.m_pSavedTees[Num-100].GetName());
pData->GameServer()->SendChatTarget(pData->m_ClientID, aBuf);
}
else if(Num >= 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.SavedTees[Num-200].GetName());
str_format(aBuf, sizeof(aBuf), "Everyone has to be in a team, %s is in team 0 or the wrong team", SavedTeam.m_pSavedTees[Num-200].GetName());
pData->GameServer()->SendChatTarget(pData->m_ClientID, aBuf);
}
else
{
pData->GameServer()->SendChatTeam(Team, "Loading successfully done");
char aBuf[512];
str_format(aBuf, sizeof(aBuf), "Loading successfully done by %s", pData->m_ClientName);
pData->GameServer()->SendChatTeam(Team, aBuf);
str_format(aBuf, sizeof(aBuf), "DELETE from %s_saves where Code='%s' and Map='%s';", pSqlServer->GetPrefix(), pData->m_Code.ClrStr(), pData->m_Map.ClrStr());
pSqlServer->executeSql(aBuf);
}
Expand Down Expand Up @@ -1633,4 +1635,60 @@ bool CSqlScore::LoadTeamThread(CSqlServer* pSqlServer, const CSqlData *pGameData
return false;
}

void CSqlScore::GetSaves(int ClientID)
{
CSqlGetSavesData *Tmp = new CSqlGetSavesData();
Tmp->m_ClientID = ClientID;
Tmp->m_Name = Server()->ClientName(ClientID);

thread_init_and_detach(ExecSqlFunc, new CSqlExecData(GetSavesThread, Tmp, false), "get saves");
}

bool CSqlScore::GetSavesThread(CSqlServer* pSqlServer, const CSqlData *pGameData, bool HandleFailure)
{
const CSqlGetSavesData *pData = dynamic_cast<const CSqlGetSavesData *>(pGameData);

if (HandleFailure)
return true;

try
{
char aBuf[512];

str_format(aBuf, sizeof(aBuf), "SELECT count(*) as NumSaves, UNIX_TIMESTAMP(CURRENT_TIMESTAMP)-UNIX_TIMESTAMP(max(Timestamp)) as Ago FROM %s_saves WHERE Map='%s' AND Savegame regexp '\\n%s\\t';", pSqlServer->GetPrefix(), pData->m_Map.ClrStr(), pData->m_Name.ClrStr());
pSqlServer->executeSqlQuery(aBuf);
if(pSqlServer->GetResults()->next())
{
int NumSaves = pSqlServer->GetResults()->getInt("NumSaves");

int Ago = pSqlServer->GetResults()->getInt("Ago");
char aAgoString[40] = "\0";
char aLastSavedString[60] = "\0";
if(Ago)
{
sqlstr::AgoTimeToString(Ago, aAgoString);
str_format(aLastSavedString, sizeof(aLastSavedString), ", last saved %s ago", aAgoString);
}

str_format(aBuf, sizeof(aBuf), "%s has %d save%s on %s%s", pData->m_Name.Str(), NumSaves, NumSaves == 1 ? "" : "s", pData->m_Map.Str(), aLastSavedString);
pData->GameServer()->SendChatTarget(pData->m_ClientID, aBuf);
}

dbg_msg("sql", "Showing saves done");
return true;
}
catch (sql::SQLException &e)
{
dbg_msg("sql", "MySQL Error: %s", e.what());
dbg_msg("sql", "ERROR: Could not get saves");
pData->GameServer()->SendChatTarget(pData->m_ClientID, "MySQL Error: Could not get saves");
}
catch (CGameContextError &e)
{
dbg_msg("sql", "WARNING: Aborted getting saves due to reload/change of map.");
return true;
}
return false;
}

#endif
10 changes: 10 additions & 0 deletions src/game/server/score/sql_score.h
Expand Up @@ -132,6 +132,7 @@ struct CSqlTeamSave : CSqlData

int m_Team;
int m_ClientID;
char m_ClientName[MAX_NAME_LENGTH];
sqlstr::CSqlString<128> m_Code;
char m_Server[5];
};
Expand All @@ -140,6 +141,13 @@ struct CSqlTeamLoad : CSqlData
{
sqlstr::CSqlString<128> m_Code;
int m_ClientID;
char m_ClientName[MAX_NAME_LENGTH];
};

struct CSqlGetSavesData: CSqlData
{
int m_ClientID;
sqlstr::CSqlString<MAX_NAME_LENGTH> m_Name;
};

struct CSqlRandomMap : CSqlScoreData
Expand Down Expand Up @@ -181,6 +189,7 @@ class CSqlScore: public IScore
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:

Expand Down Expand Up @@ -209,6 +218,7 @@ class CSqlScore: public IScore
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();
};
Expand Down
59 changes: 50 additions & 9 deletions src/game/server/teams.cpp
Expand Up @@ -21,6 +21,7 @@ void CGameTeams::Reset()
m_TeamLocked[i] = false;
m_IsSaving[i] = false;
m_Invited[i] = 0;
m_Practice[i] = false;
}
}

Expand Down Expand Up @@ -83,13 +84,17 @@ void CGameTeams::OnCharacterStart(int ClientID)
{
ChangeTeamState(m_Core.Team(ClientID), TEAMSTATE_STARTED);

int NumPlayers = Count(m_Core.Team(ClientID));

char aBuf[512];
str_format(
aBuf,
sizeof(aBuf),
"Team %d started with these %d players: ",
"Team %d started with %d player%s: ",
m_Core.Team(ClientID),
Count(m_Core.Team(ClientID)));
NumPlayers,
NumPlayers == 1 ? "" : "s");


bool First = true;

Expand Down Expand Up @@ -180,7 +185,35 @@ void CGameTeams::CheckTeamFinished(int Team)
float Time = (float)(Server()->Tick() - GetStartTime(TeamPlayers[0]))
/ ((float)Server()->TickSpeed());
if (Time < 0.000001f)
{
return;
}

if(m_Practice[Team])
{
ChangeTeamState(Team, TEAMSTATE_FINISHED);

char aBuf[256];
str_format(aBuf, sizeof(aBuf),
"Your team would've finished in: %d minute(s) %5.2f second(s). Since you had practice mode enabled your rank doesn't count.",
(int)Time / 60, Time - ((int)Time / 60 * 60));

for(int i = 0; i < MAX_CLIENTS; i++)
{
if(m_Core.Team(i) == Team && GameServer()->m_apPlayers[i])
{
GameServer()->SendChatTarget(i, aBuf);
}
}

for(unsigned int i = 0; i < PlayersCount; ++i)
{
SetDDRaceState(TeamPlayers[i], DDRACE_FINISHED);
}

return;
}

char aTimestamp[TIMESTAMP_STR_LENGTH];
str_timestamp_format(aTimestamp, sizeof(aTimestamp), FORMAT_SPACE); // 2019-04-02 19:41:58

Expand Down Expand Up @@ -253,9 +286,14 @@ void CGameTeams::SetForceCharacterTeam(int ClientID, int Team)
}

if (OldTeam != Team)
for (int LoopClientID = 0; LoopClientID < MAX_CLIENTS; ++LoopClientID)
if (GetPlayer(LoopClientID))
{
for(int LoopClientID = 0; LoopClientID < MAX_CLIENTS; ++LoopClientID)
if(GetPlayer(LoopClientID))
SendTeamsState(LoopClientID);

if(GetPlayer(ClientID))
GetPlayer(ClientID)->m_VotedForPractice = false;
}
}

void CGameTeams::ForceLeaveTeam(int ClientID)
Expand All @@ -280,6 +318,7 @@ void CGameTeams::ForceLeaveTeam(int ClientID)
// unlock team when last player leaves
SetTeamLock(m_Core.Team(ClientID), false);
ResetInvited(m_Core.Team(ClientID));
m_Practice[m_Core.Team(ClientID)] = false;
}
}

Expand Down Expand Up @@ -385,7 +424,7 @@ void CGameTeams::SendTeamsState(int ClientID)
if (g_Config.m_SvTeam == 3)
return;

if (!m_pGameContext->m_apPlayers[ClientID] || m_pGameContext->m_apPlayers[ClientID]->m_ClientVersion <= VERSION_DDRACE)
if (!m_pGameContext->m_apPlayers[ClientID] || m_pGameContext->m_apPlayers[ClientID]->GetClientVersion() <= VERSION_DDRACE)
return;

CMsgPacker Msg(NETMSGTYPE_SV_TEAMSSTATE);
Expand Down Expand Up @@ -587,7 +626,7 @@ void CGameTeams::OnFinish(CPlayer* Player, float Time, const char *pTimestamp)
NeedToSendNewRecord = true;
for (int i = 0; i < MAX_CLIENTS; i++)
{
if (GetPlayer(i) && GetPlayer(i)->m_ClientVersion >= VERSION_DDRACE)
if (GetPlayer(i) && GetPlayer(i)->GetClientVersion() >= VERSION_DDRACE)
{
if (!g_Config.m_SvHideScore || i == Player->GetCID())
{
Expand All @@ -600,19 +639,19 @@ void CGameTeams::OnFinish(CPlayer* Player, float Time, const char *pTimestamp)
}
}

if (NeedToSendNewRecord && Player->m_ClientVersion >= VERSION_DDRACE)
if (NeedToSendNewRecord && Player->GetClientVersion() >= VERSION_DDRACE)
{
for (int i = 0; i < MAX_CLIENTS; i++)
{
if (GameServer()->m_apPlayers[i]
&& GameServer()->m_apPlayers[i]->m_ClientVersion >= VERSION_DDRACE)
&& GameServer()->m_apPlayers[i]->GetClientVersion() >= VERSION_DDRACE)
{
GameServer()->SendRecord(i);
}
}
}

if (Player->m_ClientVersion >= VERSION_DDRACE)
if (Player->GetClientVersion() >= VERSION_DDRACE)
{
CNetMsg_Sv_DDRaceTime Msg;
Msg.m_Time = (int)(Time * 100.0f);
Expand Down Expand Up @@ -729,4 +768,6 @@ void CGameTeams::KillSavedTeam(int Team)
// unlock team when last player leaves
SetTeamLock(Team, false);
ResetInvited(Team);

m_Practice[Team] = false;
}
17 changes: 17 additions & 0 deletions src/game/server/teams.h
Expand Up @@ -13,6 +13,7 @@ class CGameTeams
bool m_TeamLocked[MAX_CLIENTS];
bool m_IsSaving[MAX_CLIENTS];
uint64_t m_Invited[MAX_CLIENTS];
bool m_Practice[MAX_CLIENTS];

class CGameContext * m_pGameContext;

Expand Down Expand Up @@ -122,6 +123,22 @@ class CGameTeams
{
return m_IsSaving[TeamID];
}

void EnablePractice(int Team)
{
if(Team <= TEAM_FLOCK || Team >= TEAM_SUPER)
return;

m_Practice[Team] = true;
}

bool IsPractice(int Team)
{
if(Team <= TEAM_FLOCK || Team >= TEAM_SUPER)
return false;

return m_Practice[Team];
}
};

#endif
40 changes: 38 additions & 2 deletions src/game/server/teehistorian.cpp
Expand Up @@ -80,12 +80,13 @@ void CTeeHistorian::WriteHeader(const CGameInfo *pGameInfo)
char aGameTypeBuffer[128];
char aMapNameBuffer[128];
char aMapSha256Buffer[256];
char aPrngDescription[128];

char aJson[2048];

#define E(buf, str) EscapeJson(buf, sizeof(buf), str)

str_format(aJson, sizeof(aJson), "{\"comment\":\"%s\",\"version\":\"%s\",\"game_uuid\":\"%s\",\"server_version\":\"%s\",\"start_time\":\"%s\",\"server_name\":\"%s\",\"server_port\":\"%d\",\"game_type\":\"%s\",\"map_name\":\"%s\",\"map_size\":\"%d\",\"map_sha256\":\"%s\",\"map_crc\":\"%08x\",\"config\":{",
str_format(aJson, sizeof(aJson), "{\"comment\":\"%s\",\"version\":\"%s\",\"game_uuid\":\"%s\",\"server_version\":\"%s\",\"start_time\":\"%s\",\"server_name\":\"%s\",\"server_port\":\"%d\",\"game_type\":\"%s\",\"map_name\":\"%s\",\"map_size\":\"%d\",\"map_sha256\":\"%s\",\"map_crc\":\"%08x\",\"prng_description\":\"%s\",\"config\":{",
E(aCommentBuffer, TEEHISTORIAN_NAME),
TEEHISTORIAN_VERSION,
aGameUuid,
Expand All @@ -97,7 +98,8 @@ void CTeeHistorian::WriteHeader(const CGameInfo *pGameInfo)
E(aMapNameBuffer, pGameInfo->m_pMapName),
pGameInfo->m_MapSize,
E(aMapSha256Buffer, aMapSha256),
pGameInfo->m_MapCrc);
pGameInfo->m_MapCrc,
E(aPrngDescription, pGameInfo->m_pPrngDescription));
Write(aJson, str_length(aJson));

char aBuffer1[1024];
Expand Down Expand Up @@ -488,6 +490,40 @@ void CTeeHistorian::EndTick()
m_State = STATE_BEFORE_TICK;
}

void CTeeHistorian::RecordDDNetVersionOld(int ClientID, int DDNetVersion)
{
CPacker Buffer;
Buffer.Reset();
Buffer.AddInt(ClientID);
Buffer.AddInt(DDNetVersion);

if(m_Debug)
{
dbg_msg("teehistorian", "ddnetver_old cid=%d ddnet_version=%d", ClientID, DDNetVersion);
}

WriteExtra(UUID_TEEHISTORIAN_DDNETVER_OLD, Buffer.Data(), Buffer.Size());
}

void CTeeHistorian::RecordDDNetVersion(int ClientID, CUuid ConnectionID, int DDNetVersion, const char *pDDNetVersionStr)
{
CPacker Buffer;
Buffer.Reset();
Buffer.AddInt(ClientID);
Buffer.AddRaw(&ConnectionID, sizeof(ConnectionID));
Buffer.AddInt(DDNetVersion);
Buffer.AddString(pDDNetVersionStr, 0);

if(m_Debug)
{
char aConnnectionID[UUID_MAXSTRSIZE];
FormatUuid(ConnectionID, aConnnectionID, sizeof(aConnnectionID));
dbg_msg("teehistorian", "ddnetver cid=%d connection_id=%s ddnet_version=%d ddnet_version_str=%s", ClientID, aConnnectionID, DDNetVersion, pDDNetVersionStr);
}

WriteExtra(UUID_TEEHISTORIAN_DDNETVER, Buffer.Data(), Buffer.Size());
}

void CTeeHistorian::RecordAuthInitial(int ClientID, int Level, const char *pAuthName)
{
CPacker Buffer;
Expand Down
4 changes: 4 additions & 0 deletions src/game/server/teehistorian.h
Expand Up @@ -23,6 +23,7 @@ class CTeeHistorian
CUuid m_GameUuid;
const char *m_pServerVersion;
time_t m_StartTime;
const char *m_pPrngDescription;

const char *m_pServerName;
int m_ServerPort;
Expand Down Expand Up @@ -63,6 +64,9 @@ class CTeeHistorian

void EndTick();

void RecordDDNetVersionOld(int ClientID, int DDNetVersion);
void RecordDDNetVersion(int ClientID, CUuid ConnectionID, int DDNetVersion, const char *pDDNetVersionStr);

void RecordAuthInitial(int ClientID, int Level, const char *pAuthName);
void RecordAuthLogin(int ClientID, int Level, const char *pAuthName);
void RecordAuthLogout(int ClientID);
Expand Down
2 changes: 1 addition & 1 deletion src/game/variables.h
Expand Up @@ -163,7 +163,7 @@ MACRO_CONFIG_STR(SvServerType, sv_server_type, 64, "none", CFGFLAG_SERVER, "Type
MACRO_CONFIG_INT(SvSendVotesPerTick, sv_send_votes_per_tick, 5, 1, 15, CFGFLAG_SERVER, "Number of vote options being send per tick")

MACRO_CONFIG_INT(SvRescue, sv_rescue, 0, 0, 1, CFGFLAG_SERVER, "Allow /rescue command so players can teleport themselves out of freeze")
MACRO_CONFIG_INT(SvRescueDelay, sv_rescue_delay, 5, 0, 1000, CFGFLAG_SERVER, "Number of seconds between two rescues")
MACRO_CONFIG_INT(SvRescueDelay, sv_rescue_delay, 1, 0, 1000, CFGFLAG_SERVER, "Number of seconds between two rescues")

#if defined(CONF_VIDEORECORDER)
MACRO_CONFIG_INT(ClVideoPauseWithDemo, cl_video_pausewithdemo, 1, 0, 1, CFGFLAG_CLIENT|CFGFLAG_SAVE, "Pause video rendering when demo playing pause")
Expand Down
7 changes: 4 additions & 3 deletions src/game/version.h
Expand Up @@ -2,9 +2,10 @@
/* If you are missing that file, acquire a complete release at teeworlds.com. */
#ifndef GAME_VERSION_H
#define GAME_VERSION_H
#define GAME_VERSION "0.6.4, 13.1"
#define GAME_VERSION "0.6.4, 13.2"
#define GAME_NETVERSION "0.6 626fce9a778df4d4"
#define GAME_RELEASE_VERSION "13.1"
#define CLIENT_VERSIONNR 13010
#define GAME_NAME "DDNet"
#define GAME_RELEASE_VERSION "13.2"
#define CLIENT_VERSIONNR 13020
extern const char *GIT_SHORTREV_HASH;
#endif
75 changes: 75 additions & 0 deletions src/test/prng.cpp
@@ -0,0 +1,75 @@
#include "test.h"
#include <gtest/gtest.h>

#include <game/prng.h>

// https://www.pcg-random.org/using-pcg-c-basic.html, retrieved 2020-05-25
// suggests to use `pcg-32-global.demo.c`:
//
// ```
// unix% ./pcg32-demo
// pcg32_random_r:
// - result: 32-bit unsigned int (uint32_t)
// - period: 2^64 (* 2^63 streams)
// - state type: pcg32_random_t (16 bytes)
// - output func: XSH-RR
//
// Round 1:
// 32bit: 0xa15c02b7 0x7b47f409 0xba1d3330 0x83d2f293 0xbfa4784b 0xcbed606e
// Coins: HHTTTHTHHHTHTTTHHHHHTTTHHHTHTHTHTTHTTTHHHHHHTTTTHHTTTTTHTTTTTTTHT
// Rolls: 3 4 1 1 2 2 3 2 4 3 2 4 3 3 5 2 3 1 3 1 5 1 4 1 5 6 4 6 6 2 6 3 3
// Cards: Qd Ks 6d 3s 3d 4c 3h Td Kc 5c Jh Kd Jd As 4s 4h Ad Th Ac Jc 7s Qs
// 2s 7h Kh 2d 6c Ah 4d Qh 9h 6s 5s 2c 9c Ts 8d 9s 3c 8c Js 5d 2h 6h
// 7d 8s 9d 5h 8h Qc 7c Tc
// â‹®
// â‹®
// Round 5:
// 32bit: 0xfcef7cd6 0x1b488b5a 0xd0daf7ea 0x1d9a70f7 0x241a37cf 0x9a3857b7
// Coins: HHHHTHHTTHTTHHHTTTHHTHTHTTTTHTTHTHTTTHHHTHTHTTHTTHTHHTHTHHHTHTHTT
// Rolls: 5 4 1 2 6 1 3 1 5 6 3 6 2 1 4 4 5 2 1 5 6 5 6 4 4 4 5 2 6 4 3 5 6
// Cards: 4d 9s Qc 9h As Qs 7s 4c Kd 6h 6s 2c 8c 5d 7h 5h Jc 3s 7c Jh Js Ks
// Tc Jd Kc Th 3h Ts Qh Ad Td 3c Ah 2d 3d 5c Ac 8s 5s 9c 2h 6c 6d Kh
// Qd 8d 7d 2s 8h 4h 9d 4s
// ```
//
// Numbers can also be seen at
// https://github.com/imneme/pcg-c/blob/83252d9c23df9c82ecb42210afed61a7b42402d7/test-high/expected/check-pcg32.out.
//
// It's also the output of `pcg32-global-demo.c` from
// https://github.com/imneme/pcg-c-basic/tree/bc39cd76ac3d541e618606bcc6e1e5ba5e5e6aa3.
//
// Only the first 6 numbers are taken from the generator directly, after this,
// something more complicated is done.

static const unsigned int PCG32_GLOBAL_DEMO[] = {
0xa15c02b7, 0x7b47f409, 0xba1d3330, 0x83d2f293, 0xbfa4784b, 0xcbed606e,
};

TEST(Prng, EqualsPcg32GlobalDemo)
{
uint64 aSeed[2] = {42, 54};

CPrng Prng;
Prng.Seed(aSeed);
for(unsigned i = 0; i < sizeof(PCG32_GLOBAL_DEMO) / sizeof(PCG32_GLOBAL_DEMO[0]); i++)
{
EXPECT_EQ(Prng.RandomBits(), PCG32_GLOBAL_DEMO[i]);
}
}

TEST(Prng, Description)
{
CPrng Prng;
EXPECT_STREQ(Prng.Description(), "pcg-xsh-rr:unseeded");

uint64 aSeed0[2] = {0xfedbca9876543210, 0x0123456789abcdef};
uint64 aSeed1[2] = {0x0123456789abcdef, 0xfedcba9876543210};
uint64 aSeed2[2] = {0x0000000000000000, 0x0000000000000000};

Prng.Seed(aSeed0);
EXPECT_STREQ(Prng.Description(), "pcg-xsh-rr:fedbca9876543210:0123456789abcdef");
Prng.Seed(aSeed1);
EXPECT_STREQ(Prng.Description(), "pcg-xsh-rr:0123456789abcdef:fedcba9876543210");
Prng.Seed(aSeed2);
EXPECT_STREQ(Prng.Description(), "pcg-xsh-rr:0000000000000000:0000000000000000");
}
40 changes: 39 additions & 1 deletion src/test/teehistorian.cpp
Expand Up @@ -57,6 +57,7 @@ class TeeHistorian : public ::testing::Test
m_GameInfo.m_GameUuid = CalculateUuid("test@ddnet.tw");
m_GameInfo.m_pServerVersion = "DDNet test";
m_GameInfo.m_StartTime = time(0);
m_GameInfo.m_pPrngDescription = "test-prng:02468ace";

m_GameInfo.m_pServerName = "server name";
m_GameInfo.m_ServerPort = 8303;
Expand Down Expand Up @@ -91,7 +92,7 @@ class TeeHistorian : public ::testing::Test
{
static CUuid TEEHISTORIAN_UUID = CalculateUuid("teehistorian@ddnet.tw");
static const char PREFIX1[] = "{\"comment\":\"teehistorian@ddnet.tw\",\"version\":\"2\",\"game_uuid\":\"a1eb7182-796e-3b3e-941d-38ca71b2a4a8\",\"server_version\":\"DDNet test\",\"start_time\":\"";
static const char PREFIX2[] = "\",\"server_name\":\"server name\",\"server_port\":\"8303\",\"game_type\":\"game type\",\"map_name\":\"Kobra 3 Solo\",\"map_size\":\"903514\",\"map_sha256\":\"0123456789012345678901234567890123456789012345678901234567890123\",\"map_crc\":\"eceaf25c\",\"config\":{},\"tuning\":{},\"uuids\":[";
static const char PREFIX2[] = "\",\"server_name\":\"server name\",\"server_port\":\"8303\",\"game_type\":\"game type\",\"map_name\":\"Kobra 3 Solo\",\"map_size\":\"903514\",\"map_sha256\":\"0123456789012345678901234567890123456789012345678901234567890123\",\"map_crc\":\"eceaf25c\",\"prng_description\":\"test-prng:02468ace\",\"config\":{},\"tuning\":{},\"uuids\":[";
static const char PREFIX3[] = "]}";

char aTimeBuf[64];
Expand Down Expand Up @@ -319,6 +320,43 @@ TEST_F(TeeHistorian, ExtraMessage)
Expect(EXPECTED, sizeof(EXPECTED));
}

TEST_F(TeeHistorian, DDNetVersion)
{
const unsigned char EXPECTED[] = {
// EX uuid=60daba5c-52c4-3aeb-b8ba-b2953fb55a17 data_len=50
0x4a,
0x13, 0x97, 0xb6, 0x3e, 0xee, 0x4e, 0x39, 0x19,
0xb8, 0x6a, 0xb0, 0x58, 0x88, 0x7f, 0xca, 0xf5,
0x32,
// (DDNETVER) cid=0 connection_id=fb13a576-d35f-4893-b815-eedc6d98015b
// ddnet_version=13010 ddnet_version_str=DDNet 13.1 (3623f5e4cd184556)
0x00,
0xfb, 0x13, 0xa5, 0x76, 0xd3, 0x5f, 0x48, 0x93,
0xb8, 0x15, 0xee, 0xdc, 0x6d, 0x98, 0x01, 0x5b,
0x92, 0xcb, 0x01, 'D', 'D', 'N', 'e', 't',
' ', '1', '3', '.', '1', ' ', '(', '3',
'6', '2', '3', 'f', '5', 'e', '4', 'c',
'd', '1', '8', '4', '5', '5', '6', ')',
0x00,
// EX uuid=1397b63e-ee4e-3919-b86a-b058887fcaf5 data_len=4
0x4a,
0x41, 0xb4, 0x95, 0x41, 0xf2, 0x6f, 0x32, 0x5d,
0x87, 0x15, 0x9b, 0xaf, 0x4b, 0x54, 0x4e, 0xf9,
0x04,
// (DDNETVER_OLD) cid=1 ddnet_version=13010
0x01, 0x92, 0xcb, 0x01,
0x40, // FINISH
};
CUuid ConnectionID = {
0xfb, 0x13, 0xa5, 0x76, 0xd3, 0x5f, 0x48, 0x93,
0xb8, 0x15, 0xee, 0xdc, 0x6d, 0x98, 0x01, 0x5b,
};
m_TH.RecordDDNetVersion(0, ConnectionID, 13010, "DDNet 13.1 (3623f5e4cd184556)");
m_TH.RecordDDNetVersionOld(1, 13010);
Finish();
Expect(EXPECTED, sizeof(EXPECTED));
}

TEST_F(TeeHistorian, Auth)
{
const unsigned char EXPECTED[] = {
Expand Down