mirror of
https://github.com/ddnet/ddnet.git
synced 2024-11-14 03:58:18 +00:00
3683 lines
100 KiB
C++
3683 lines
100 KiB
C++
/* (c) Magnus Auvinen. See licence.txt in the root of the distribution for more information. */
|
|
/* If you are missing that file, acquire a complete release at teeworlds.com. */
|
|
|
|
#define _WIN32_WINNT 0x0501
|
|
|
|
#include <new>
|
|
|
|
#include <stdlib.h> // qsort
|
|
#include <stdarg.h>
|
|
#include <string.h>
|
|
#include <climits>
|
|
#include <locale.h> //setlocale
|
|
|
|
#include <base/math.h>
|
|
#include <base/vmath.h>
|
|
#include <base/system.h>
|
|
|
|
#include <game/client/components/menus.h>
|
|
#include <game/client/gameclient.h>
|
|
#include <game/editor/editor.h>
|
|
|
|
#include <engine/client.h>
|
|
#include <engine/config.h>
|
|
#include <engine/console.h>
|
|
#include <engine/editor.h>
|
|
#include <engine/engine.h>
|
|
#include <engine/graphics.h>
|
|
#include <engine/input.h>
|
|
#include <engine/keys.h>
|
|
#include <engine/map.h>
|
|
#include <engine/masterserver.h>
|
|
#include <engine/serverbrowser.h>
|
|
#include <engine/sound.h>
|
|
#include <engine/storage.h>
|
|
#include <engine/textrender.h>
|
|
|
|
#include <engine/external/md5/md5.h>
|
|
|
|
#include <engine/shared/config.h>
|
|
#include <engine/shared/compression.h>
|
|
#include <engine/shared/datafile.h>
|
|
#include <engine/shared/demo.h>
|
|
#include <engine/shared/filecollection.h>
|
|
#include <engine/shared/ghost.h>
|
|
#include <engine/shared/network.h>
|
|
#include <engine/shared/packer.h>
|
|
#include <engine/shared/protocol.h>
|
|
#include <engine/shared/protocol_ex.h>
|
|
#include <engine/shared/ringbuffer.h>
|
|
#include <engine/shared/snapshot.h>
|
|
#include <engine/shared/fifo.h>
|
|
#include <engine/shared/uuid_manager.h>
|
|
|
|
#include <game/extrainfo.h>
|
|
#include <game/version.h>
|
|
|
|
#include <mastersrv/mastersrv.h>
|
|
|
|
#include <engine/client/serverbrowser.h>
|
|
|
|
#if defined(CONF_FAMILY_WINDOWS)
|
|
#define WIN32_LEAN_AND_MEAN
|
|
#include <windows.h>
|
|
#endif
|
|
|
|
#include "friends.h"
|
|
#include "serverbrowser.h"
|
|
#include "fetcher.h"
|
|
#include "updater.h"
|
|
#include "client.h"
|
|
|
|
#include <zlib.h>
|
|
|
|
#include "SDL.h"
|
|
#ifdef main
|
|
#undef main
|
|
#endif
|
|
|
|
void CGraph::Init(float Min, float Max)
|
|
{
|
|
m_Min = Min;
|
|
m_Max = Max;
|
|
m_Index = 0;
|
|
}
|
|
|
|
void CGraph::ScaleMax()
|
|
{
|
|
int i = 0;
|
|
m_Max = 0;
|
|
for(i = 0; i < MAX_VALUES; i++)
|
|
{
|
|
if(m_aValues[i] > m_Max)
|
|
m_Max = m_aValues[i];
|
|
}
|
|
}
|
|
|
|
void CGraph::ScaleMin()
|
|
{
|
|
int i = 0;
|
|
m_Min = m_Max;
|
|
for(i = 0; i < MAX_VALUES; i++)
|
|
{
|
|
if(m_aValues[i] < m_Min)
|
|
m_Min = m_aValues[i];
|
|
}
|
|
}
|
|
|
|
void CGraph::Add(float v, float r, float g, float b)
|
|
{
|
|
m_Index = (m_Index+1)&(MAX_VALUES-1);
|
|
m_aValues[m_Index] = v;
|
|
m_aColors[m_Index][0] = r;
|
|
m_aColors[m_Index][1] = g;
|
|
m_aColors[m_Index][2] = b;
|
|
}
|
|
|
|
void CGraph::Render(IGraphics *pGraphics, int Font, float x, float y, float w, float h, const char *pDescription)
|
|
{
|
|
//m_pGraphics->BlendNormal();
|
|
|
|
|
|
pGraphics->TextureSet(-1);
|
|
|
|
pGraphics->QuadsBegin();
|
|
pGraphics->SetColor(0, 0, 0, 0.75f);
|
|
IGraphics::CQuadItem QuadItem(x, y, w, h);
|
|
pGraphics->QuadsDrawTL(&QuadItem, 1);
|
|
pGraphics->QuadsEnd();
|
|
|
|
pGraphics->LinesBegin();
|
|
pGraphics->SetColor(0.95f, 0.95f, 0.95f, 1.00f);
|
|
IGraphics::CLineItem LineItem(x, y+h/2, x+w, y+h/2);
|
|
pGraphics->LinesDraw(&LineItem, 1);
|
|
pGraphics->SetColor(0.5f, 0.5f, 0.5f, 0.75f);
|
|
IGraphics::CLineItem Array[2] = {
|
|
IGraphics::CLineItem(x, y+(h*3)/4, x+w, y+(h*3)/4),
|
|
IGraphics::CLineItem(x, y+h/4, x+w, y+h/4)};
|
|
pGraphics->LinesDraw(Array, 2);
|
|
for(int i = 1; i < MAX_VALUES; i++)
|
|
{
|
|
float a0 = (i-1)/(float)MAX_VALUES;
|
|
float a1 = i/(float)MAX_VALUES;
|
|
int i0 = (m_Index+i-1)&(MAX_VALUES-1);
|
|
int i1 = (m_Index+i)&(MAX_VALUES-1);
|
|
|
|
float v0 = (m_aValues[i0]-m_Min) / (m_Max-m_Min);
|
|
float v1 = (m_aValues[i1]-m_Min) / (m_Max-m_Min);
|
|
|
|
IGraphics::CColorVertex Array[2] = {
|
|
IGraphics::CColorVertex(0, m_aColors[i0][0], m_aColors[i0][1], m_aColors[i0][2], 0.75f),
|
|
IGraphics::CColorVertex(1, m_aColors[i1][0], m_aColors[i1][1], m_aColors[i1][2], 0.75f)};
|
|
pGraphics->SetColorVertex(Array, 2);
|
|
IGraphics::CLineItem LineItem(x+a0*w, y+h-v0*h, x+a1*w, y+h-v1*h);
|
|
pGraphics->LinesDraw(&LineItem, 1);
|
|
|
|
}
|
|
pGraphics->LinesEnd();
|
|
|
|
pGraphics->TextureSet(Font);
|
|
pGraphics->QuadsBegin();
|
|
pGraphics->QuadsText(x+2, y+h-16, 16, pDescription);
|
|
|
|
char aBuf[32];
|
|
str_format(aBuf, sizeof(aBuf), "%.2f", m_Max);
|
|
pGraphics->QuadsText(x+w-8*str_length(aBuf)-8, y+2, 16, aBuf);
|
|
|
|
str_format(aBuf, sizeof(aBuf), "%.2f", m_Min);
|
|
pGraphics->QuadsText(x+w-8*str_length(aBuf)-8, y+h-16, 16, aBuf);
|
|
pGraphics->QuadsEnd();
|
|
}
|
|
|
|
|
|
void CSmoothTime::Init(int64 Target)
|
|
{
|
|
m_Snap = time_get();
|
|
m_Current = Target;
|
|
m_Target = Target;
|
|
m_aAdjustSpeed[0] = 0.3f;
|
|
m_aAdjustSpeed[1] = 0.3f;
|
|
m_Graph.Init(0.0f, 0.5f);
|
|
}
|
|
|
|
void CSmoothTime::SetAdjustSpeed(int Direction, float Value)
|
|
{
|
|
m_aAdjustSpeed[Direction] = Value;
|
|
}
|
|
|
|
int64 CSmoothTime::Get(int64 Now)
|
|
{
|
|
int64 c = m_Current + (Now - m_Snap);
|
|
int64 t = m_Target + (Now - m_Snap);
|
|
|
|
// it's faster to adjust upward instead of downward
|
|
// we might need to adjust these abit
|
|
|
|
float AdjustSpeed = m_aAdjustSpeed[0];
|
|
if(t > c)
|
|
AdjustSpeed = m_aAdjustSpeed[1];
|
|
|
|
float a = ((Now-m_Snap)/(float)time_freq()) * AdjustSpeed;
|
|
if(a > 1.0f)
|
|
a = 1.0f;
|
|
|
|
int64 r = c + (int64)((t-c)*a);
|
|
|
|
m_Graph.Add(a+0.5f,1,1,1);
|
|
|
|
return r;
|
|
}
|
|
|
|
void CSmoothTime::UpdateInt(int64 Target)
|
|
{
|
|
int64 Now = time_get();
|
|
m_Current = Get(Now);
|
|
m_Snap = Now;
|
|
m_Target = Target;
|
|
}
|
|
|
|
void CSmoothTime::Update(CGraph *pGraph, int64 Target, int TimeLeft, int AdjustDirection)
|
|
{
|
|
int UpdateTimer = 1;
|
|
|
|
if(TimeLeft < 0)
|
|
{
|
|
int IsSpike = 0;
|
|
if(TimeLeft < -50)
|
|
{
|
|
IsSpike = 1;
|
|
|
|
m_SpikeCounter += 5;
|
|
if(m_SpikeCounter > 50)
|
|
m_SpikeCounter = 50;
|
|
}
|
|
|
|
if(IsSpike && m_SpikeCounter < 15)
|
|
{
|
|
// ignore this ping spike
|
|
UpdateTimer = 0;
|
|
pGraph->Add(TimeLeft, 1,1,0);
|
|
}
|
|
else
|
|
{
|
|
pGraph->Add(TimeLeft, 1,0,0);
|
|
if(m_aAdjustSpeed[AdjustDirection] < 30.0f)
|
|
m_aAdjustSpeed[AdjustDirection] *= 2.0f;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if(m_SpikeCounter)
|
|
m_SpikeCounter--;
|
|
|
|
pGraph->Add(TimeLeft, 0,1,0);
|
|
|
|
m_aAdjustSpeed[AdjustDirection] *= 0.95f;
|
|
if(m_aAdjustSpeed[AdjustDirection] < 2.0f)
|
|
m_aAdjustSpeed[AdjustDirection] = 2.0f;
|
|
}
|
|
|
|
if(UpdateTimer)
|
|
UpdateInt(Target);
|
|
}
|
|
|
|
|
|
CClient::CClient() : m_DemoPlayer(&m_SnapshotDelta)
|
|
{
|
|
m_DemoRecorder[0] = CDemoRecorder(&m_SnapshotDelta);
|
|
m_DemoRecorder[1] = CDemoRecorder(&m_SnapshotDelta);
|
|
m_DemoRecorder[2] = CDemoRecorder(&m_SnapshotDelta);
|
|
|
|
m_pEditor = 0;
|
|
m_pInput = 0;
|
|
m_pGraphics = 0;
|
|
m_pSound = 0;
|
|
m_pGameClient = 0;
|
|
m_pMap = 0;
|
|
m_pConsole = 0;
|
|
|
|
m_RenderFrameTime = 0.0001f;
|
|
m_RenderFrameTimeLow = 1.0f;
|
|
m_RenderFrameTimeHigh = 0.0f;
|
|
m_RenderFrames = 0;
|
|
m_LastRenderTime = time_get();
|
|
|
|
m_GameTickSpeed = SERVER_TICK_SPEED;
|
|
|
|
m_SnapCrcErrors = 0;
|
|
m_AutoScreenshotRecycle = false;
|
|
m_AutoStatScreenshotRecycle = false;
|
|
m_AutoCSVRecycle = false;
|
|
m_EditorActive = false;
|
|
|
|
m_AckGameTick[0] = -1;
|
|
m_AckGameTick[1] = -1;
|
|
m_CurrentRecvTick[0] = 0;
|
|
m_CurrentRecvTick[1] = 0;
|
|
m_RconAuthed[0] = 0;
|
|
m_RconAuthed[1] = 0;
|
|
m_RconPassword[0] = '\0';
|
|
m_Password[0] = '\0';
|
|
|
|
// version-checking
|
|
m_aVersionStr[0] = '0';
|
|
m_aVersionStr[1] = '\0';
|
|
|
|
// pinging
|
|
m_PingStartTime = 0;
|
|
|
|
m_aCurrentMap[0] = 0;
|
|
|
|
m_aCmdConnect[0] = 0;
|
|
|
|
// map download
|
|
m_aMapdownloadFilename[0] = 0;
|
|
m_aMapdownloadName[0] = 0;
|
|
m_pMapdownloadTask = NULL;
|
|
m_MapdownloadFile = 0;
|
|
m_MapdownloadChunk = 0;
|
|
m_MapdownloadCrc = 0;
|
|
m_MapdownloadAmount = -1;
|
|
m_MapdownloadTotalsize = -1;
|
|
|
|
m_pDDNetInfoTask = NULL;
|
|
m_aNews[0] = '\0';
|
|
|
|
m_CurrentServerInfoRequestTime = -1;
|
|
|
|
m_CurrentInput[0] = 0;
|
|
m_CurrentInput[1] = 0;
|
|
m_LastDummy = 0;
|
|
m_LastDummy2 = 0;
|
|
|
|
mem_zero(&m_aInputs, sizeof(m_aInputs));
|
|
|
|
m_State = IClient::STATE_OFFLINE;
|
|
m_aServerAddressStr[0] = 0;
|
|
|
|
mem_zero(m_aSnapshots, sizeof(m_aSnapshots));
|
|
m_SnapshotStorage[0].Init();
|
|
m_SnapshotStorage[1].Init();
|
|
m_ReceivedSnapshots[0] = 0;
|
|
m_ReceivedSnapshots[1] = 0;
|
|
|
|
m_VersionInfo.m_State = CVersionInfo::STATE_INIT;
|
|
|
|
if(g_Config.m_ClDummy == 0)
|
|
m_LastDummyConnectTime = 0;
|
|
|
|
m_ReconnectTime = 0;
|
|
|
|
m_GenerateTimeoutSeed = true;
|
|
}
|
|
|
|
// ----- send functions -----
|
|
int CClient::SendMsg(CMsgPacker *pMsg, int Flags)
|
|
{
|
|
return SendMsgEx(pMsg, Flags, false);
|
|
}
|
|
|
|
int CClient::SendMsgEx(CMsgPacker *pMsg, int Flags, bool System)
|
|
{
|
|
CNetChunk Packet;
|
|
|
|
if(State() == IClient::STATE_OFFLINE)
|
|
return 0;
|
|
|
|
mem_zero(&Packet, sizeof(CNetChunk));
|
|
|
|
Packet.m_ClientID = 0;
|
|
Packet.m_pData = pMsg->Data();
|
|
Packet.m_DataSize = pMsg->Size();
|
|
|
|
// HACK: modify the message id in the packet and store the system flag
|
|
if(*((unsigned char*)Packet.m_pData) == 1 && System && Packet.m_DataSize == 1)
|
|
{
|
|
dbg_break();
|
|
}
|
|
|
|
*((unsigned char*)Packet.m_pData) <<= 1;
|
|
if(System)
|
|
*((unsigned char*)Packet.m_pData) |= 1;
|
|
|
|
if(Flags&MSGFLAG_VITAL)
|
|
Packet.m_Flags |= NETSENDFLAG_VITAL;
|
|
if(Flags&MSGFLAG_FLUSH)
|
|
Packet.m_Flags |= NETSENDFLAG_FLUSH;
|
|
|
|
if(Flags&MSGFLAG_RECORD)
|
|
{
|
|
for(int i = 0; i < RECORDER_MAX; i++)
|
|
if(m_DemoRecorder[i].IsRecording())
|
|
m_DemoRecorder[i].RecordMessage(Packet.m_pData, Packet.m_DataSize);
|
|
}
|
|
|
|
if(!(Flags&MSGFLAG_NOSEND))
|
|
{
|
|
m_NetClient[g_Config.m_ClDummy].Send(&Packet);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
void CClient::SendInfo()
|
|
{
|
|
CMsgPacker Msg(NETMSG_INFO);
|
|
Msg.AddString(GameClient()->NetVersion(), 128);
|
|
Msg.AddString(m_Password, 128);
|
|
SendMsgEx(&Msg, MSGFLAG_VITAL|MSGFLAG_FLUSH);
|
|
}
|
|
|
|
|
|
void CClient::SendEnterGame()
|
|
{
|
|
CMsgPacker Msg(NETMSG_ENTERGAME);
|
|
SendMsgEx(&Msg, MSGFLAG_VITAL|MSGFLAG_FLUSH);
|
|
}
|
|
|
|
void CClient::SendReady()
|
|
{
|
|
CMsgPacker Msg(NETMSG_READY);
|
|
SendMsgEx(&Msg, MSGFLAG_VITAL|MSGFLAG_FLUSH);
|
|
}
|
|
|
|
void CClient::SendMapRequest()
|
|
{
|
|
if(m_MapdownloadFile)
|
|
io_close(m_MapdownloadFile);
|
|
m_MapdownloadFile = Storage()->OpenFile(m_aMapdownloadFilename, IOFLAG_WRITE, IStorage::TYPE_SAVE);
|
|
CMsgPacker Msg(NETMSG_REQUEST_MAP_DATA);
|
|
Msg.AddInt(m_MapdownloadChunk);
|
|
SendMsgEx(&Msg, MSGFLAG_VITAL|MSGFLAG_FLUSH);
|
|
}
|
|
|
|
void CClient::RconAuth(const char *pName, const char *pPassword)
|
|
{
|
|
if(RconAuthed())
|
|
return;
|
|
|
|
if(pPassword != m_RconPassword)
|
|
str_copy(m_RconPassword, pPassword, sizeof(m_RconPassword));
|
|
|
|
CMsgPacker Msg(NETMSG_RCON_AUTH);
|
|
Msg.AddString(pName, 32);
|
|
Msg.AddString(pPassword, 32);
|
|
Msg.AddInt(1);
|
|
SendMsgEx(&Msg, MSGFLAG_VITAL);
|
|
}
|
|
|
|
void CClient::Rcon(const char *pCmd)
|
|
{
|
|
CMsgPacker Msg(NETMSG_RCON_CMD);
|
|
Msg.AddString(pCmd, 256);
|
|
SendMsgEx(&Msg, MSGFLAG_VITAL);
|
|
}
|
|
|
|
bool CClient::ConnectionProblems()
|
|
{
|
|
return m_NetClient[g_Config.m_ClDummy].GotProblems() != 0;
|
|
}
|
|
|
|
void CClient::DirectInput(int *pInput, int Size)
|
|
{
|
|
int i;
|
|
CMsgPacker Msg(NETMSG_INPUT);
|
|
Msg.AddInt(m_AckGameTick[g_Config.m_ClDummy]);
|
|
Msg.AddInt(m_PredTick[g_Config.m_ClDummy]);
|
|
Msg.AddInt(Size);
|
|
|
|
for(i = 0; i < Size/4; i++)
|
|
Msg.AddInt(pInput[i]);
|
|
|
|
SendMsgEx(&Msg, 0);
|
|
}
|
|
|
|
void CClient::SendInput()
|
|
{
|
|
int64 Now = time_get();
|
|
|
|
if(m_PredTick[g_Config.m_ClDummy] <= 0)
|
|
return;
|
|
|
|
if(m_LastDummy != (bool)g_Config.m_ClDummy)
|
|
{
|
|
m_LastDummy = g_Config.m_ClDummy;
|
|
GameClient()->OnDummySwap();
|
|
}
|
|
|
|
bool Force = false;
|
|
// fetch input
|
|
for(int Dummy = 0; Dummy < 2; Dummy++)
|
|
{
|
|
if(!m_DummyConnected && Dummy != 0)
|
|
{
|
|
break;
|
|
}
|
|
int i = g_Config.m_ClDummy ^ Dummy;
|
|
int Size = GameClient()->OnSnapInput(m_aInputs[i][m_CurrentInput[i]].m_aData, Dummy, Force);
|
|
|
|
if(Size)
|
|
{
|
|
// pack input
|
|
CMsgPacker Msg(NETMSG_INPUT);
|
|
Msg.AddInt(m_AckGameTick[i]);
|
|
Msg.AddInt(m_PredTick[i]);
|
|
Msg.AddInt(Size);
|
|
|
|
m_aInputs[i][m_CurrentInput[i]].m_Tick = m_PredTick[i];
|
|
m_aInputs[i][m_CurrentInput[i]].m_PredictedTime = m_PredictedTime.Get(Now);
|
|
m_aInputs[i][m_CurrentInput[i]].m_Time = Now;
|
|
|
|
// pack it
|
|
for(int k = 0; k < Size/4; k++)
|
|
Msg.AddInt(m_aInputs[i][m_CurrentInput[i]].m_aData[k]);
|
|
|
|
m_CurrentInput[i]++;
|
|
m_CurrentInput[i] %= 200;
|
|
|
|
SendMsgExY(&Msg, MSGFLAG_FLUSH, true, i);
|
|
// ugly workaround for dummy. we need to send input with dummy to prevent
|
|
// prediction time resets. but if we do it too often, then it's
|
|
// impossible to use grenade with frozen dummy that gets hammered...
|
|
if(g_Config.m_ClDummyCopyMoves || m_CurrentInput[i] % 2)
|
|
Force = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
const char *CClient::LatestVersion()
|
|
{
|
|
return m_aVersionStr;
|
|
}
|
|
|
|
// TODO: OPT: do this a lot smarter!
|
|
int *CClient::GetInput(int Tick)
|
|
{
|
|
int Best = -1;
|
|
for(int i = 0; i < 200; i++)
|
|
{
|
|
if(m_aInputs[g_Config.m_ClDummy][i].m_Tick <= Tick && (Best == -1 || m_aInputs[g_Config.m_ClDummy][Best].m_Tick < m_aInputs[g_Config.m_ClDummy][i].m_Tick))
|
|
Best = i;
|
|
}
|
|
|
|
if(Best != -1)
|
|
return (int *)m_aInputs[g_Config.m_ClDummy][Best].m_aData;
|
|
return 0;
|
|
}
|
|
|
|
bool CClient::InputExists(int Tick)
|
|
{
|
|
for(int i = 0; i < 200; i++)
|
|
if(m_aInputs[g_Config.m_ClDummy][i].m_Tick == Tick)
|
|
return true;
|
|
return false;
|
|
}
|
|
|
|
// ------ state handling -----
|
|
void CClient::SetState(int s)
|
|
{
|
|
if(m_State == IClient::STATE_QUITING)
|
|
return;
|
|
|
|
int Old = m_State;
|
|
if(g_Config.m_Debug)
|
|
{
|
|
char aBuf[128];
|
|
str_format(aBuf, sizeof(aBuf), "state change. last=%d current=%d", m_State, s);
|
|
m_pConsole->Print(IConsole::OUTPUT_LEVEL_DEBUG, "client", aBuf);
|
|
}
|
|
m_State = s;
|
|
if(Old != s)
|
|
{
|
|
GameClient()->OnStateChange(m_State, Old);
|
|
|
|
if(s == IClient::STATE_OFFLINE && m_ReconnectTime == 0)
|
|
{
|
|
if(g_Config.m_ClReconnectFull > 0 && (str_find_nocase(ErrorString(), "full") || str_find_nocase(ErrorString(), "reserved")))
|
|
m_ReconnectTime = time_get() + time_freq() * g_Config.m_ClReconnectFull;
|
|
else if(g_Config.m_ClReconnectTimeout > 0 && (str_find_nocase(ErrorString(), "Timeout") || str_find_nocase(ErrorString(), "Too weak connection")))
|
|
m_ReconnectTime = time_get() + time_freq() * g_Config.m_ClReconnectTimeout;
|
|
}
|
|
}
|
|
}
|
|
|
|
// called when the map is loaded and we should init for a new round
|
|
void CClient::OnEnterGame()
|
|
{
|
|
// reset input
|
|
int i;
|
|
for(i = 0; i < 200; i++)
|
|
{
|
|
m_aInputs[0][i].m_Tick = -1;
|
|
m_aInputs[1][i].m_Tick = -1;
|
|
}
|
|
m_CurrentInput[0] = 0;
|
|
m_CurrentInput[1] = 0;
|
|
|
|
// reset snapshots
|
|
m_aSnapshots[g_Config.m_ClDummy][SNAP_CURRENT] = 0;
|
|
m_aSnapshots[g_Config.m_ClDummy][SNAP_PREV] = 0;
|
|
m_SnapshotStorage[g_Config.m_ClDummy].PurgeAll();
|
|
m_ReceivedSnapshots[g_Config.m_ClDummy] = 0;
|
|
m_SnapshotParts = 0;
|
|
m_PredTick[g_Config.m_ClDummy] = 0;
|
|
m_CurrentRecvTick[g_Config.m_ClDummy] = 0;
|
|
m_CurGameTick[g_Config.m_ClDummy] = 0;
|
|
m_PrevGameTick[g_Config.m_ClDummy] = 0;
|
|
|
|
if(g_Config.m_ClDummy == 0)
|
|
m_LastDummyConnectTime = 0;
|
|
|
|
GameClient()->OnEnterGame();
|
|
}
|
|
|
|
void CClient::EnterGame()
|
|
{
|
|
if(State() == IClient::STATE_DEMOPLAYBACK)
|
|
return;
|
|
|
|
// now we will wait for two snapshots
|
|
// to finish the connection
|
|
SendEnterGame();
|
|
OnEnterGame();
|
|
|
|
ServerInfoRequest(); // fresh one for timeout protection
|
|
m_aTimeoutCodeSent[0] = false;
|
|
m_aTimeoutCodeSent[1] = false;
|
|
}
|
|
|
|
void GenerateTimeoutCode(char *pBuffer, unsigned Size, char *pSeed, const NETADDR &Addr, bool Dummy)
|
|
{
|
|
md5_state_t Md5;
|
|
md5_byte_t aDigest[16];
|
|
md5_init(&Md5);
|
|
|
|
const char *pDummy = Dummy ? "dummy" : "normal";
|
|
|
|
md5_append(&Md5, (unsigned char *)pDummy, str_length(pDummy) + 1);
|
|
md5_append(&Md5, (unsigned char *)pSeed, str_length(pSeed) + 1);
|
|
md5_append(&Md5, (unsigned char *)&Addr, sizeof(Addr));
|
|
md5_finish(&Md5, aDigest);
|
|
|
|
unsigned short Random[8];
|
|
mem_copy(Random, aDigest, sizeof(Random));
|
|
generate_password(pBuffer, Size, Random, 8);
|
|
}
|
|
|
|
void CClient::GenerateTimeoutSeed()
|
|
{
|
|
secure_random_password(g_Config.m_ClTimeoutSeed, sizeof(g_Config.m_ClTimeoutSeed), 16);
|
|
}
|
|
|
|
void CClient::GenerateTimeoutCodes()
|
|
{
|
|
if(g_Config.m_ClTimeoutSeed[0])
|
|
{
|
|
for(int i = 0; i < 2; i++)
|
|
{
|
|
GenerateTimeoutCode(m_aTimeoutCodes[i], sizeof(m_aTimeoutCodes[i]), g_Config.m_ClTimeoutSeed, m_ServerAddress, i);
|
|
|
|
char aBuf[64];
|
|
str_format(aBuf, sizeof(aBuf), "timeout code '%s' (%s)", m_aTimeoutCodes[i], i == 0 ? "normal" : "dummy");
|
|
m_pConsole->Print(IConsole::OUTPUT_LEVEL_ADDINFO, "client", aBuf);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
str_copy(m_aTimeoutCodes[0], g_Config.m_ClTimeoutCode, sizeof(m_aTimeoutCodes[0]));
|
|
str_copy(m_aTimeoutCodes[1], g_Config.m_ClDummyTimeoutCode, sizeof(m_aTimeoutCodes[1]));
|
|
}
|
|
}
|
|
|
|
void CClient::Connect(const char *pAddress, const char *pPassword)
|
|
{
|
|
char aBuf[512];
|
|
int Port = 8303;
|
|
|
|
Disconnect();
|
|
|
|
str_copy(m_aServerAddressStr, pAddress, sizeof(m_aServerAddressStr));
|
|
|
|
str_format(aBuf, sizeof(aBuf), "connecting to '%s'", m_aServerAddressStr);
|
|
m_pConsole->Print(IConsole::OUTPUT_LEVEL_STANDARD, "client", aBuf);
|
|
|
|
ServerInfoRequest();
|
|
if(net_host_lookup(m_aServerAddressStr, &m_ServerAddress, m_NetClient[0].NetType()) != 0)
|
|
{
|
|
char aBufMsg[256];
|
|
str_format(aBufMsg, sizeof(aBufMsg), "could not find the address of %s, connecting to localhost", aBuf);
|
|
m_pConsole->Print(IConsole::OUTPUT_LEVEL_STANDARD, "client", aBufMsg);
|
|
net_host_lookup("localhost", &m_ServerAddress, m_NetClient[0].NetType());
|
|
}
|
|
|
|
if(!pPassword)
|
|
m_Password[0] = 0;
|
|
else
|
|
str_copy(m_Password, pPassword, sizeof(m_Password));
|
|
|
|
m_RconAuthed[0] = 0;
|
|
if(m_ServerAddress.port == 0)
|
|
m_ServerAddress.port = Port;
|
|
m_NetClient[0].Connect(&m_ServerAddress);
|
|
SetState(IClient::STATE_CONNECTING);
|
|
|
|
for(int i = 0; i < RECORDER_MAX; i++)
|
|
if(m_DemoRecorder[i].IsRecording())
|
|
DemoRecorder_Stop(i);
|
|
|
|
m_InputtimeMarginGraph.Init(-150.0f, 150.0f);
|
|
m_GametimeMarginGraph.Init(-150.0f, 150.0f);
|
|
|
|
GenerateTimeoutCodes();
|
|
}
|
|
|
|
void CClient::DisconnectWithReason(const char *pReason)
|
|
{
|
|
char aBuf[512];
|
|
str_format(aBuf, sizeof(aBuf), "disconnecting. reason='%s'", pReason?pReason:"unknown");
|
|
m_pConsole->Print(IConsole::OUTPUT_LEVEL_STANDARD, "client", aBuf);
|
|
|
|
// stop demo playback and recorder
|
|
m_DemoPlayer.Stop();
|
|
for(int i = 0; i < RECORDER_MAX; i++)
|
|
DemoRecorder_Stop(i);
|
|
|
|
//
|
|
m_RconAuthed[0] = 0;
|
|
m_UseTempRconCommands = 0;
|
|
m_pConsole->DeregisterTempAll();
|
|
m_NetClient[0].Disconnect(pReason);
|
|
SetState(IClient::STATE_OFFLINE);
|
|
m_pMap->Unload();
|
|
|
|
// disable all downloads
|
|
m_MapdownloadChunk = 0;
|
|
if(m_pMapdownloadTask)
|
|
m_pMapdownloadTask->Abort();
|
|
if(m_MapdownloadFile)
|
|
io_close(m_MapdownloadFile);
|
|
m_MapdownloadFile = 0;
|
|
m_MapdownloadCrc = 0;
|
|
m_MapdownloadTotalsize = -1;
|
|
m_MapdownloadAmount = 0;
|
|
|
|
// clear the current server info
|
|
mem_zero(&m_CurrentServerInfo, sizeof(m_CurrentServerInfo));
|
|
mem_zero(&m_ServerAddress, sizeof(m_ServerAddress));
|
|
|
|
// clear snapshots
|
|
m_aSnapshots[g_Config.m_ClDummy][SNAP_CURRENT] = 0;
|
|
m_aSnapshots[g_Config.m_ClDummy][SNAP_PREV] = 0;
|
|
m_ReceivedSnapshots[g_Config.m_ClDummy] = 0;
|
|
}
|
|
|
|
void CClient::Disconnect()
|
|
{
|
|
if(m_DummyConnected)
|
|
DummyDisconnect(0);
|
|
if(m_State != IClient::STATE_OFFLINE)
|
|
DisconnectWithReason(0);
|
|
}
|
|
|
|
bool CClient::DummyConnected()
|
|
{
|
|
return m_DummyConnected;
|
|
}
|
|
|
|
bool CClient::DummyConnecting()
|
|
{
|
|
return !m_DummyConnected && m_LastDummyConnectTime > 0 && m_LastDummyConnectTime + GameTickSpeed() * 5 > GameTick();
|
|
}
|
|
|
|
void CClient::DummyConnect()
|
|
{
|
|
if(m_LastDummyConnectTime > 0 && m_LastDummyConnectTime + GameTickSpeed() * 5 > GameTick())
|
|
return;
|
|
|
|
if(m_NetClient[0].State() != NET_CONNSTATE_ONLINE && m_NetClient[0].State() != NET_CONNSTATE_PENDING)
|
|
return;
|
|
|
|
if(m_DummyConnected)
|
|
return;
|
|
|
|
m_LastDummyConnectTime = GameTick();
|
|
|
|
m_RconAuthed[1] = 0;
|
|
|
|
m_DummySendConnInfo = true;
|
|
|
|
g_Config.m_ClDummyCopyMoves = 0;
|
|
g_Config.m_ClDummyHammer = 0;
|
|
|
|
//connecting to the server
|
|
m_NetClient[1].Connect(&m_ServerAddress);
|
|
}
|
|
|
|
void CClient::DummyDisconnect(const char *pReason)
|
|
{
|
|
if(!m_DummyConnected)
|
|
return;
|
|
|
|
m_NetClient[1].Disconnect(pReason);
|
|
g_Config.m_ClDummy = 0;
|
|
m_RconAuthed[1] = 0;
|
|
m_DummyConnected = false;
|
|
GameClient()->OnDummyDisconnect();
|
|
}
|
|
|
|
int CClient::SendMsgExY(CMsgPacker *pMsg, int Flags, bool System, int NetClient)
|
|
{
|
|
CNetChunk Packet;
|
|
|
|
mem_zero(&Packet, sizeof(CNetChunk));
|
|
|
|
Packet.m_ClientID = 0;
|
|
Packet.m_pData = pMsg->Data();
|
|
Packet.m_DataSize = pMsg->Size();
|
|
|
|
// HACK: modify the message id in the packet and store the system flag
|
|
if(*((unsigned char*)Packet.m_pData) == 1 && System && Packet.m_DataSize == 1)
|
|
{
|
|
dbg_break();
|
|
}
|
|
|
|
*((unsigned char*)Packet.m_pData) <<= 1;
|
|
if(System)
|
|
*((unsigned char*)Packet.m_pData) |= 1;
|
|
|
|
if(Flags&MSGFLAG_VITAL)
|
|
Packet.m_Flags |= NETSENDFLAG_VITAL;
|
|
if(Flags&MSGFLAG_FLUSH)
|
|
Packet.m_Flags |= NETSENDFLAG_FLUSH;
|
|
|
|
m_NetClient[NetClient].Send(&Packet);
|
|
return 0;
|
|
}
|
|
|
|
void CClient::GetServerInfo(CServerInfo *pServerInfo)
|
|
{
|
|
mem_copy(pServerInfo, &m_CurrentServerInfo, sizeof(m_CurrentServerInfo));
|
|
|
|
if(m_DemoPlayer.IsPlaying() && g_Config.m_ClDemoAssumeRace)
|
|
str_copy(pServerInfo->m_aGameType, "DDraceNetwork", 14);
|
|
}
|
|
|
|
void CClient::ServerInfoRequest()
|
|
{
|
|
mem_zero(&m_CurrentServerInfo, sizeof(m_CurrentServerInfo));
|
|
m_CurrentServerInfoRequestTime = 0;
|
|
}
|
|
|
|
int CClient::LoadData()
|
|
{
|
|
m_DebugFont = Graphics()->LoadTexture("debug_font.png", IStorage::TYPE_ALL, CImageInfo::FORMAT_AUTO, IGraphics::TEXLOAD_NORESAMPLE);
|
|
return 1;
|
|
}
|
|
|
|
// ---
|
|
|
|
void *CClient::SnapGetItem(int SnapID, int Index, CSnapItem *pItem)
|
|
{
|
|
CSnapshotItem *i;
|
|
dbg_assert(SnapID >= 0 && SnapID < NUM_SNAPSHOT_TYPES, "invalid SnapID");
|
|
i = m_aSnapshots[g_Config.m_ClDummy][SnapID]->m_pAltSnap->GetItem(Index);
|
|
pItem->m_DataSize = m_aSnapshots[g_Config.m_ClDummy][SnapID]->m_pAltSnap->GetItemSize(Index);
|
|
pItem->m_Type = m_aSnapshots[g_Config.m_ClDummy][SnapID]->m_pAltSnap->GetItemType(Index);
|
|
pItem->m_ID = i->ID();
|
|
return (void *)i->Data();
|
|
}
|
|
|
|
void CClient::SnapInvalidateItem(int SnapID, int Index)
|
|
{
|
|
CSnapshotItem *i;
|
|
dbg_assert(SnapID >= 0 && SnapID < NUM_SNAPSHOT_TYPES, "invalid SnapID");
|
|
i = m_aSnapshots[g_Config.m_ClDummy][SnapID]->m_pAltSnap->GetItem(Index);
|
|
if(i)
|
|
{
|
|
if((char *)i < (char *)m_aSnapshots[g_Config.m_ClDummy][SnapID]->m_pAltSnap || (char *)i > (char *)m_aSnapshots[g_Config.m_ClDummy][SnapID]->m_pAltSnap + m_aSnapshots[g_Config.m_ClDummy][SnapID]->m_SnapSize)
|
|
m_pConsole->Print(IConsole::OUTPUT_LEVEL_DEBUG, "client", "snap invalidate problem");
|
|
if((char *)i >= (char *)m_aSnapshots[g_Config.m_ClDummy][SnapID]->m_pSnap && (char *)i < (char *)m_aSnapshots[g_Config.m_ClDummy][SnapID]->m_pSnap + m_aSnapshots[g_Config.m_ClDummy][SnapID]->m_SnapSize)
|
|
m_pConsole->Print(IConsole::OUTPUT_LEVEL_DEBUG, "client", "snap invalidate problem");
|
|
i->m_TypeAndID = -1;
|
|
}
|
|
}
|
|
|
|
void *CClient::SnapFindItem(int SnapID, int Type, int ID)
|
|
{
|
|
// TODO: linear search. should be fixed.
|
|
int i;
|
|
|
|
if(!m_aSnapshots[g_Config.m_ClDummy][SnapID])
|
|
return 0x0;
|
|
|
|
for(i = 0; i < m_aSnapshots[g_Config.m_ClDummy][SnapID]->m_pSnap->NumItems(); i++)
|
|
{
|
|
CSnapshotItem *pItem = m_aSnapshots[g_Config.m_ClDummy][SnapID]->m_pAltSnap->GetItem(i);
|
|
if(m_aSnapshots[g_Config.m_ClDummy][SnapID]->m_pAltSnap->GetItemType(i) == Type && pItem->ID() == ID)
|
|
return (void *)pItem->Data();
|
|
}
|
|
return 0x0;
|
|
}
|
|
|
|
int CClient::SnapNumItems(int SnapID)
|
|
{
|
|
dbg_assert(SnapID >= 0 && SnapID < NUM_SNAPSHOT_TYPES, "invalid SnapID");
|
|
if(!m_aSnapshots[g_Config.m_ClDummy][SnapID])
|
|
return 0;
|
|
return m_aSnapshots[g_Config.m_ClDummy][SnapID]->m_pSnap->NumItems();
|
|
}
|
|
|
|
void CClient::SnapSetStaticsize(int ItemType, int Size)
|
|
{
|
|
m_SnapshotDelta.SetStaticsize(ItemType, Size);
|
|
}
|
|
|
|
|
|
void CClient::DebugRender()
|
|
{
|
|
static NETSTATS Prev, Current;
|
|
static int64 LastSnap = 0;
|
|
static float FrameTimeAvg = 0;
|
|
char aBuffer[512];
|
|
|
|
if(!g_Config.m_Debug)
|
|
return;
|
|
|
|
//m_pGraphics->BlendNormal();
|
|
Graphics()->TextureSet(m_DebugFont);
|
|
Graphics()->MapScreen(0,0,Graphics()->ScreenWidth(),Graphics()->ScreenHeight());
|
|
Graphics()->QuadsBegin();
|
|
|
|
if(time_get()-LastSnap > time_freq())
|
|
{
|
|
LastSnap = time_get();
|
|
Prev = Current;
|
|
net_stats(&Current);
|
|
}
|
|
|
|
/*
|
|
eth = 14
|
|
ip = 20
|
|
udp = 8
|
|
total = 42
|
|
*/
|
|
FrameTimeAvg = FrameTimeAvg*0.9f + m_RenderFrameTime*0.1f;
|
|
str_format(aBuffer, sizeof(aBuffer), "ticks: %8d %8d mem %dk %d gfxmem: %dk fps: %3d",
|
|
m_CurGameTick[g_Config.m_ClDummy], m_PredTick[g_Config.m_ClDummy],
|
|
mem_stats()->allocated/1024,
|
|
mem_stats()->total_allocations,
|
|
Graphics()->MemoryUsage()/1024,
|
|
(int)(1.0f/FrameTimeAvg + 0.5f));
|
|
Graphics()->QuadsText(2, 2, 16, aBuffer);
|
|
|
|
|
|
{
|
|
int SendPackets = (Current.sent_packets-Prev.sent_packets);
|
|
int SendBytes = (Current.sent_bytes-Prev.sent_bytes);
|
|
int SendTotal = SendBytes + SendPackets*42;
|
|
int RecvPackets = (Current.recv_packets-Prev.recv_packets);
|
|
int RecvBytes = (Current.recv_bytes-Prev.recv_bytes);
|
|
int RecvTotal = RecvBytes + RecvPackets*42;
|
|
|
|
if(!SendPackets) SendPackets++;
|
|
if(!RecvPackets) RecvPackets++;
|
|
str_format(aBuffer, sizeof(aBuffer), "send: %3d %5d+%4d=%5d (%3d kbps) avg: %5d\nrecv: %3d %5d+%4d=%5d (%3d kbps) avg: %5d",
|
|
SendPackets, SendBytes, SendPackets*42, SendTotal, (SendTotal*8)/1024, SendBytes/SendPackets,
|
|
RecvPackets, RecvBytes, RecvPackets*42, RecvTotal, (RecvTotal*8)/1024, RecvBytes/RecvPackets);
|
|
Graphics()->QuadsText(2, 14, 16, aBuffer);
|
|
}
|
|
|
|
// render rates
|
|
{
|
|
int y = 0;
|
|
int i;
|
|
for(i = 0; i < 256; i++)
|
|
{
|
|
if(m_SnapshotDelta.GetDataRate(i))
|
|
{
|
|
str_format(aBuffer, sizeof(aBuffer), "%4d %20s: %8d %8d %8d", i, GameClient()->GetItemName(i), m_SnapshotDelta.GetDataRate(i)/8, m_SnapshotDelta.GetDataUpdates(i),
|
|
(m_SnapshotDelta.GetDataRate(i)/m_SnapshotDelta.GetDataUpdates(i))/8);
|
|
Graphics()->QuadsText(2, 100+y*12, 16, aBuffer);
|
|
y++;
|
|
}
|
|
}
|
|
}
|
|
|
|
str_format(aBuffer, sizeof(aBuffer), "pred: %d ms", GetPredictionTime());
|
|
Graphics()->QuadsText(2, 70, 16, aBuffer);
|
|
Graphics()->QuadsEnd();
|
|
|
|
// render graphs
|
|
if(g_Config.m_DbgGraphs)
|
|
{
|
|
//Graphics()->MapScreen(0,0,400.0f,300.0f);
|
|
float w = Graphics()->ScreenWidth()/4.0f;
|
|
float h = Graphics()->ScreenHeight()/6.0f;
|
|
float sp = Graphics()->ScreenWidth()/100.0f;
|
|
float x = Graphics()->ScreenWidth()-w-sp;
|
|
|
|
m_FpsGraph.ScaleMax();
|
|
m_FpsGraph.ScaleMin();
|
|
m_FpsGraph.Render(Graphics(), m_DebugFont, x, sp*5, w, h, "FPS");
|
|
m_InputtimeMarginGraph.Render(Graphics(), m_DebugFont, x, sp*5+h+sp, w, h, "Prediction Margin");
|
|
m_GametimeMarginGraph.Render(Graphics(), m_DebugFont, x, sp*5+h+sp+h+sp, w, h, "Gametime Margin");
|
|
}
|
|
}
|
|
|
|
void CClient::Restart()
|
|
{
|
|
char aBuf[512];
|
|
shell_execute(Storage()->GetBinaryPath(PLAT_CLIENT_EXEC, aBuf, sizeof aBuf));
|
|
Quit();
|
|
}
|
|
|
|
void CClient::Quit()
|
|
{
|
|
SetState(IClient::STATE_QUITING);
|
|
}
|
|
|
|
const char *CClient::ErrorString()
|
|
{
|
|
return m_NetClient[0].ErrorString();
|
|
}
|
|
|
|
void CClient::Render()
|
|
{
|
|
if(g_Config.m_ClOverlayEntities)
|
|
{
|
|
vec3 bg = HslToRgb(vec3(g_Config.m_ClBackgroundEntitiesHue/255.0f, g_Config.m_ClBackgroundEntitiesSat/255.0f, g_Config.m_ClBackgroundEntitiesLht/255.0f));
|
|
Graphics()->Clear(bg.r, bg.g, bg.b);
|
|
}
|
|
else
|
|
{
|
|
vec3 bg = HslToRgb(vec3(g_Config.m_ClBackgroundHue/255.0f, g_Config.m_ClBackgroundSat/255.0f, g_Config.m_ClBackgroundLht/255.0f));
|
|
Graphics()->Clear(bg.r, bg.g, bg.b);
|
|
}
|
|
|
|
GameClient()->OnRender();
|
|
DebugRender();
|
|
|
|
if(State() == IClient::STATE_ONLINE && g_Config.m_ClAntiPingLimit)
|
|
{
|
|
int64 Now = time_get();
|
|
g_Config.m_ClAntiPing = (m_PredictedTime.Get(Now)-m_GameTime[g_Config.m_ClDummy].Get(Now))*1000/(float)time_freq() > g_Config.m_ClAntiPingLimit;
|
|
}
|
|
}
|
|
|
|
vec3 CClient::GetColorV3(int v)
|
|
{
|
|
return HslToRgb(vec3(((v>>16)&0xff)/255.0f, ((v>>8)&0xff)/255.0f, 0.5f+(v&0xff)/255.0f*0.5f));
|
|
}
|
|
|
|
const char *CClient::LoadMap(const char *pName, const char *pFilename, unsigned WantedCrc)
|
|
{
|
|
static char s_aErrorMsg[128];
|
|
|
|
SetState(IClient::STATE_LOADING);
|
|
|
|
if(!m_pMap->Load(pFilename))
|
|
{
|
|
str_format(s_aErrorMsg, sizeof(s_aErrorMsg), "map '%s' not found", pFilename);
|
|
return s_aErrorMsg;
|
|
}
|
|
|
|
// get the crc of the map
|
|
if(m_pMap->Crc() != WantedCrc)
|
|
{
|
|
str_format(s_aErrorMsg, sizeof(s_aErrorMsg), "map differs from the server. %08x != %08x", m_pMap->Crc(), WantedCrc);
|
|
m_pConsole->Print(IConsole::OUTPUT_LEVEL_ADDINFO, "client", s_aErrorMsg);
|
|
m_pMap->Unload();
|
|
return s_aErrorMsg;
|
|
}
|
|
|
|
// stop demo recording if we loaded a new map
|
|
for(int i = 0; i < RECORDER_MAX; i++)
|
|
DemoRecorder_Stop(i);
|
|
|
|
char aBuf[256];
|
|
str_format(aBuf, sizeof(aBuf), "loaded map '%s'", pFilename);
|
|
m_pConsole->Print(IConsole::OUTPUT_LEVEL_ADDINFO, "client", aBuf);
|
|
m_ReceivedSnapshots[g_Config.m_ClDummy] = 0;
|
|
|
|
str_copy(m_aCurrentMap, pName, sizeof(m_aCurrentMap));
|
|
str_copy(m_aCurrentMapPath, pFilename, sizeof(m_aCurrentMapPath));
|
|
|
|
return 0x0;
|
|
}
|
|
|
|
|
|
|
|
const char *CClient::LoadMapSearch(const char *pMapName, int WantedCrc)
|
|
{
|
|
const char *pError = 0;
|
|
char aBuf[512];
|
|
str_format(aBuf, sizeof(aBuf), "loading map, map=%s wanted crc=%08x", pMapName, WantedCrc);
|
|
m_pConsole->Print(IConsole::OUTPUT_LEVEL_ADDINFO, "client", aBuf);
|
|
SetState(IClient::STATE_LOADING);
|
|
|
|
// try the normal maps folder
|
|
str_format(aBuf, sizeof(aBuf), "maps/%s.map", pMapName);
|
|
pError = LoadMap(pMapName, aBuf, WantedCrc);
|
|
if(!pError)
|
|
return pError;
|
|
|
|
// try the downloaded maps
|
|
str_format(aBuf, sizeof(aBuf), "downloadedmaps/%s_%08x.map", pMapName, WantedCrc);
|
|
pError = LoadMap(pMapName, aBuf, WantedCrc);
|
|
if(!pError)
|
|
return pError;
|
|
|
|
// search for the map within subfolders
|
|
char aFilename[128];
|
|
str_format(aFilename, sizeof(aFilename), "%s.map", pMapName);
|
|
if(Storage()->FindFile(aFilename, "maps", IStorage::TYPE_ALL, aBuf, sizeof(aBuf)))
|
|
pError = LoadMap(pMapName, aBuf, WantedCrc);
|
|
|
|
return pError;
|
|
}
|
|
|
|
int CClient::PlayerScoreNameComp(const void *a, const void *b)
|
|
{
|
|
CServerInfo::CClient *p0 = (CServerInfo::CClient *)a;
|
|
CServerInfo::CClient *p1 = (CServerInfo::CClient *)b;
|
|
if(p0->m_Player && !p1->m_Player)
|
|
return -1;
|
|
if(!p0->m_Player && p1->m_Player)
|
|
return 1;
|
|
if(p0->m_Score > p1->m_Score)
|
|
return -1;
|
|
if(p0->m_Score < p1->m_Score)
|
|
return 1;
|
|
return str_comp_nocase(p0->m_aName, p1->m_aName);
|
|
}
|
|
|
|
void CClient::ProcessConnlessPacket(CNetChunk *pPacket)
|
|
{
|
|
//server count from master server
|
|
if(pPacket->m_DataSize == (int)sizeof(SERVERBROWSE_COUNT) + 2 && mem_comp(pPacket->m_pData, SERVERBROWSE_COUNT, sizeof(SERVERBROWSE_COUNT)) == 0)
|
|
{
|
|
unsigned char *pP = (unsigned char*) pPacket->m_pData;
|
|
pP += sizeof(SERVERBROWSE_COUNT);
|
|
int ServerCount = ((*pP)<<8) | *(pP+1);
|
|
int ServerID = -1;
|
|
for(int i = 0; i < IMasterServer::MAX_MASTERSERVERS; i++)
|
|
{
|
|
if(!m_pMasterServer->IsValid(i))
|
|
continue;
|
|
NETADDR tmp = m_pMasterServer->GetAddr(i);
|
|
if(net_addr_comp(&pPacket->m_Address, &tmp) == 0)
|
|
{
|
|
ServerID = i;
|
|
break;
|
|
}
|
|
}
|
|
if(ServerCount > -1 && ServerID != -1)
|
|
{
|
|
m_pMasterServer->SetCount(ServerID, ServerCount);
|
|
if(g_Config.m_Debug)
|
|
dbg_msg("mastercount", "server %d got %d servers", ServerID, ServerCount);
|
|
}
|
|
}
|
|
// server list from master server
|
|
if(pPacket->m_DataSize >= (int)sizeof(SERVERBROWSE_LIST) &&
|
|
mem_comp(pPacket->m_pData, SERVERBROWSE_LIST, sizeof(SERVERBROWSE_LIST)) == 0)
|
|
{
|
|
// check for valid master server address
|
|
bool Valid = false;
|
|
for(int i = 0; i < IMasterServer::MAX_MASTERSERVERS; ++i)
|
|
{
|
|
if(m_pMasterServer->IsValid(i))
|
|
{
|
|
NETADDR Addr = m_pMasterServer->GetAddr(i);
|
|
if(net_addr_comp(&pPacket->m_Address, &Addr) == 0)
|
|
{
|
|
Valid = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
if(!Valid)
|
|
return;
|
|
|
|
int Size = pPacket->m_DataSize-sizeof(SERVERBROWSE_LIST);
|
|
int Num = Size/sizeof(CMastersrvAddr);
|
|
CMastersrvAddr *pAddrs = (CMastersrvAddr *)((char*)pPacket->m_pData+sizeof(SERVERBROWSE_LIST));
|
|
for(int i = 0; i < Num; i++)
|
|
{
|
|
NETADDR Addr;
|
|
|
|
static unsigned char s_IPV4Mapping[] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF };
|
|
|
|
// copy address
|
|
if(!mem_comp(s_IPV4Mapping, pAddrs[i].m_aIp, sizeof(s_IPV4Mapping)))
|
|
{
|
|
mem_zero(&Addr, sizeof(Addr));
|
|
Addr.type = NETTYPE_IPV4;
|
|
Addr.ip[0] = pAddrs[i].m_aIp[12];
|
|
Addr.ip[1] = pAddrs[i].m_aIp[13];
|
|
Addr.ip[2] = pAddrs[i].m_aIp[14];
|
|
Addr.ip[3] = pAddrs[i].m_aIp[15];
|
|
}
|
|
else
|
|
{
|
|
Addr.type = NETTYPE_IPV6;
|
|
mem_copy(Addr.ip, pAddrs[i].m_aIp, sizeof(Addr.ip));
|
|
}
|
|
Addr.port = (pAddrs[i].m_aPort[0]<<8) | pAddrs[i].m_aPort[1];
|
|
|
|
m_ServerBrowser.Set(Addr, IServerBrowser::SET_MASTER_ADD, -1, 0x0);
|
|
}
|
|
}
|
|
|
|
// server info
|
|
if(pPacket->m_DataSize >= (int)sizeof(SERVERBROWSE_INFO))
|
|
{
|
|
int Type = -1;
|
|
if(mem_comp(pPacket->m_pData, SERVERBROWSE_INFO, sizeof(SERVERBROWSE_INFO)) == 0)
|
|
Type = SERVERINFO_VANILLA;
|
|
else if(mem_comp(pPacket->m_pData, SERVERBROWSE_INFO_64_LEGACY, sizeof(SERVERBROWSE_INFO_64_LEGACY)) == 0)
|
|
Type = SERVERINFO_64_LEGACY;
|
|
else if(mem_comp(pPacket->m_pData, SERVERBROWSE_INFO_EXTENDED, sizeof(SERVERBROWSE_INFO_EXTENDED)) == 0)
|
|
Type = SERVERINFO_EXTENDED;
|
|
else if(mem_comp(pPacket->m_pData, SERVERBROWSE_INFO_EXTENDED_MORE, sizeof(SERVERBROWSE_INFO_EXTENDED_MORE)) == 0)
|
|
Type = SERVERINFO_EXTENDED_MORE;
|
|
|
|
if(Type != -1)
|
|
{
|
|
void *pData = (unsigned char *)pPacket->m_pData + sizeof(SERVERBROWSE_INFO);
|
|
int DataSize = pPacket->m_DataSize - sizeof(SERVERBROWSE_INFO);
|
|
ProcessServerInfo(Type, &pPacket->m_Address, pData, DataSize);
|
|
}
|
|
}
|
|
}
|
|
|
|
static int SavedServerInfoType(int Type)
|
|
{
|
|
if(Type == SERVERINFO_EXTENDED_MORE)
|
|
return SERVERINFO_EXTENDED;
|
|
|
|
return Type;
|
|
}
|
|
|
|
void CClient::ProcessServerInfo(int RawType, NETADDR *pFrom, const void *pData, int DataSize)
|
|
{
|
|
CServerBrowser::CServerEntry *pEntry = m_ServerBrowser.Find(*pFrom);
|
|
|
|
CServerInfo Info = {0};
|
|
int SavedType = SavedServerInfoType(RawType);
|
|
if((SavedType == SERVERINFO_64_LEGACY || SavedType == SERVERINFO_EXTENDED)
|
|
&& pEntry && pEntry->m_GotInfo && SavedType == pEntry->m_Info.m_Type)
|
|
{
|
|
Info = pEntry->m_Info;
|
|
}
|
|
|
|
Info.m_Type = SavedType;
|
|
|
|
net_addr_str(pFrom, Info.m_aAddress, sizeof(Info.m_aAddress), true);
|
|
|
|
CUnpacker Up;
|
|
Up.Reset(pData, DataSize);
|
|
|
|
#define GET_STRING(array) str_copy(array, Up.GetString(CUnpacker::SANITIZE_CC|CUnpacker::SKIP_START_WHITESPACES), sizeof(array))
|
|
#define GET_INT(integer) (integer) = str_toint(Up.GetString())
|
|
|
|
int Offset = 0; // Only used for SavedType == SERVERINFO_64_LEGACY
|
|
int Token;
|
|
int PacketNo = 0; // Only used if SavedType == SERVERINFO_EXTENDED
|
|
|
|
GET_INT(Token);
|
|
if(RawType != SERVERINFO_EXTENDED_MORE)
|
|
{
|
|
GET_STRING(Info.m_aVersion);
|
|
GET_STRING(Info.m_aName);
|
|
GET_STRING(Info.m_aMap);
|
|
|
|
if(SavedType == SERVERINFO_EXTENDED)
|
|
{
|
|
GET_INT(Info.m_MapCrc);
|
|
GET_INT(Info.m_MapSize);
|
|
}
|
|
|
|
GET_STRING(Info.m_aGameType);
|
|
GET_INT(Info.m_Flags);
|
|
GET_INT(Info.m_NumPlayers);
|
|
GET_INT(Info.m_MaxPlayers);
|
|
GET_INT(Info.m_NumClients);
|
|
GET_INT(Info.m_MaxClients);
|
|
if(Info.m_aMap[0])
|
|
Info.m_HasRank = m_ServerBrowser.HasRank(Info.m_aMap);
|
|
|
|
// don't add invalid info to the server browser list
|
|
if(Info.m_NumClients < 0 || Info.m_MaxClients < 0 ||
|
|
Info.m_NumPlayers < 0 || Info.m_MaxPlayers < 0 ||
|
|
Info.m_NumPlayers > Info.m_NumClients || Info.m_MaxPlayers > Info.m_MaxClients)
|
|
{
|
|
return;
|
|
}
|
|
|
|
switch(SavedType)
|
|
{
|
|
case SERVERINFO_VANILLA:
|
|
if(Info.m_MaxPlayers > VANILLA_MAX_CLIENTS ||
|
|
Info.m_MaxClients > VANILLA_MAX_CLIENTS)
|
|
{
|
|
return;
|
|
}
|
|
break;
|
|
case SERVERINFO_64_LEGACY:
|
|
if(Info.m_MaxPlayers > MAX_CLIENTS ||
|
|
Info.m_MaxClients > MAX_CLIENTS)
|
|
{
|
|
return;
|
|
}
|
|
break;
|
|
case SERVERINFO_EXTENDED:
|
|
if(Info.m_NumPlayers > Info.m_NumClients)
|
|
return;
|
|
break;
|
|
default:
|
|
dbg_assert(false, "unknown serverinfo type");
|
|
}
|
|
|
|
if(SavedType == SERVERINFO_64_LEGACY)
|
|
Offset = Up.GetInt();
|
|
|
|
// Check for valid offset.
|
|
if(Offset < 0)
|
|
return;
|
|
|
|
if(SavedType == SERVERINFO_EXTENDED)
|
|
PacketNo = 0;
|
|
}
|
|
else
|
|
{
|
|
GET_INT(PacketNo);
|
|
// 0 needs to be excluded because that's reserved for the main packet.
|
|
if(PacketNo <= 0 || PacketNo >= 64)
|
|
return;
|
|
}
|
|
|
|
bool DuplicatedPacket = false;
|
|
if(SavedType == SERVERINFO_EXTENDED)
|
|
{
|
|
Up.GetString(); // extra info, reserved
|
|
|
|
uint64 Flag = (uint64)1 << PacketNo;
|
|
DuplicatedPacket = Info.m_ReceivedPackets&Flag;
|
|
Info.m_ReceivedPackets |= Flag;
|
|
}
|
|
|
|
bool IgnoreError = false;
|
|
for(int i = Offset; i < MAX_CLIENTS && Info.m_NumReceivedClients < MAX_CLIENTS && !Up.Error(); i++)
|
|
{
|
|
CServerInfo::CClient *pClient = &Info.m_aClients[Info.m_NumReceivedClients];
|
|
GET_STRING(pClient->m_aName);
|
|
if(Up.Error())
|
|
{
|
|
// Packet end, no problem unless it happens during one
|
|
// player info, so ignore the error.
|
|
IgnoreError = true;
|
|
break;
|
|
}
|
|
GET_STRING(pClient->m_aClan);
|
|
GET_INT(pClient->m_Country);
|
|
GET_INT(pClient->m_Score);
|
|
GET_INT(pClient->m_Player);
|
|
if(SavedType == SERVERINFO_EXTENDED)
|
|
{
|
|
Up.GetString(); // extra info, reserved
|
|
}
|
|
if(!Up.Error())
|
|
{
|
|
if(SavedType == SERVERINFO_64_LEGACY)
|
|
{
|
|
uint64 Flag = (uint64)1 << i;
|
|
if(!(Info.m_ReceivedPackets&Flag))
|
|
{
|
|
Info.m_ReceivedPackets |= Flag;
|
|
Info.m_NumReceivedClients++;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
Info.m_NumReceivedClients++;
|
|
}
|
|
}
|
|
}
|
|
|
|
if(!Up.Error() || IgnoreError)
|
|
{
|
|
qsort(Info.m_aClients, Info.m_NumReceivedClients, sizeof(*Info.m_aClients), PlayerScoreNameComp);
|
|
|
|
if(!DuplicatedPacket && (!pEntry || !pEntry->m_GotInfo || SavedType >= pEntry->m_Info.m_Type))
|
|
{
|
|
m_ServerBrowser.Set(*pFrom, IServerBrowser::SET_TOKEN, Token, &Info);
|
|
pEntry = m_ServerBrowser.Find(*pFrom);
|
|
|
|
if(SavedType == SERVERINFO_VANILLA && Is64Player(&Info) && pEntry)
|
|
{
|
|
pEntry->m_Request64Legacy = true;
|
|
// Force a quick update.
|
|
m_ServerBrowser.RequestImpl64(pEntry->m_Addr, pEntry);
|
|
}
|
|
}
|
|
|
|
// Player info is irrelevant for the client (while connected),
|
|
// it gets its info from elsewhere.
|
|
//
|
|
// SERVERINFO_EXTENDED_MORE doesn't carry any server
|
|
// information, so just skip it.
|
|
if(net_addr_comp(&m_ServerAddress, pFrom) == 0 && RawType != SERVERINFO_EXTENDED_MORE)
|
|
{
|
|
// Only accept server info that has a type that is
|
|
// newer or equal to something the server already sent
|
|
// us.
|
|
if(SavedType >= m_CurrentServerInfo.m_Type)
|
|
{
|
|
mem_copy(&m_CurrentServerInfo, &Info, sizeof(m_CurrentServerInfo));
|
|
m_CurrentServerInfo.m_NetAddr = m_ServerAddress;
|
|
m_CurrentServerInfoRequestTime = -1;
|
|
}
|
|
}
|
|
}
|
|
|
|
#undef GET_STRING
|
|
#undef GET_INT
|
|
}
|
|
|
|
void CClient::ProcessServerPacket(CNetChunk *pPacket)
|
|
{
|
|
CUnpacker Unpacker;
|
|
Unpacker.Reset(pPacket->m_pData, pPacket->m_DataSize);
|
|
CMsgPacker Packer(NETMSG_EX);
|
|
|
|
// unpack msgid and system flag
|
|
int Msg;
|
|
bool Sys;
|
|
CUuid Uuid;
|
|
|
|
int Result = UnpackMessageID(&Msg, &Sys, &Uuid, &Unpacker, &Packer);
|
|
if(Result == UNPACKMESSAGE_ERROR)
|
|
{
|
|
return;
|
|
}
|
|
else if(Result == UNPACKMESSAGE_ANSWER)
|
|
{
|
|
SendMsgEx(&Packer, MSGFLAG_VITAL, true);
|
|
}
|
|
|
|
if(Sys)
|
|
{
|
|
// system message
|
|
if((pPacket->m_Flags&NET_CHUNKFLAG_VITAL) != 0 && Msg == NETMSG_MAP_CHANGE)
|
|
{
|
|
const char *pMap = Unpacker.GetString(CUnpacker::SANITIZE_CC|CUnpacker::SKIP_START_WHITESPACES);
|
|
int MapCrc = Unpacker.GetInt();
|
|
int MapSize = Unpacker.GetInt();
|
|
const char *pError = 0;
|
|
|
|
if(Unpacker.Error())
|
|
return;
|
|
|
|
if(m_DummyConnected)
|
|
DummyDisconnect(0);
|
|
|
|
for(int i = 0; pMap[i]; i++) // protect the player from nasty map names
|
|
{
|
|
if(pMap[i] == '/' || pMap[i] == '\\')
|
|
pError = "strange character in map name";
|
|
}
|
|
|
|
if(MapSize < 0)
|
|
pError = "invalid map size";
|
|
|
|
if(pError)
|
|
DisconnectWithReason(pError);
|
|
else
|
|
{
|
|
pError = LoadMapSearch(pMap, MapCrc);
|
|
|
|
if(!pError)
|
|
{
|
|
m_pConsole->Print(IConsole::OUTPUT_LEVEL_ADDINFO, "client/network", "loading done");
|
|
SendReady();
|
|
}
|
|
else
|
|
{
|
|
str_format(m_aMapdownloadFilename, sizeof(m_aMapdownloadFilename), "downloadedmaps/%s_%08x.map", pMap, MapCrc);
|
|
|
|
char aBuf[256];
|
|
str_format(aBuf, sizeof(aBuf), "starting to download map to '%s'", m_aMapdownloadFilename);
|
|
m_pConsole->Print(IConsole::OUTPUT_LEVEL_ADDINFO, "client/network", aBuf);
|
|
|
|
m_MapdownloadChunk = 0;
|
|
str_copy(m_aMapdownloadName, pMap, sizeof(m_aMapdownloadName));
|
|
|
|
m_MapdownloadCrc = MapCrc;
|
|
m_MapdownloadTotalsize = MapSize;
|
|
m_MapdownloadAmount = 0;
|
|
|
|
ResetMapDownload();
|
|
|
|
if(g_Config.m_ClHttpMapDownload)
|
|
{
|
|
char aUrl[256];
|
|
char aFilename[64];
|
|
char aEscaped[128];
|
|
str_format(aFilename, sizeof(aFilename), "%s_%08x.map", pMap, MapCrc);
|
|
|
|
EscapeUrl(aEscaped, sizeof(aEscaped), aFilename);
|
|
str_format(aUrl, sizeof(aUrl), "%s/", g_Config.m_ClDDNetMapDownloadUrl);
|
|
|
|
// We only trust our own custom-selected CAs for our own servers.
|
|
// Other servers can use any CA trusted by the system.
|
|
bool UseDDNetCA =
|
|
str_comp_nocase_num("maps.ddnet.tw/", aUrl, 14) == 0 ||
|
|
str_comp_nocase_num("http://maps.ddnet.tw/", aUrl, 21) == 0 ||
|
|
str_comp_nocase_num("https://maps.ddnet.tw/", aUrl, 22) == 0;
|
|
|
|
str_append(aUrl, aEscaped, sizeof(aUrl));
|
|
|
|
m_pMapdownloadTask = std::make_shared<CFetchTask>(Storage(), aUrl, m_aMapdownloadFilename, IStorage::TYPE_SAVE, UseDDNetCA, true);
|
|
Engine()->AddJob(m_pMapdownloadTask);
|
|
}
|
|
else
|
|
SendMapRequest();
|
|
}
|
|
}
|
|
}
|
|
else if(Msg == NETMSG_MAP_DATA)
|
|
{
|
|
int Last = Unpacker.GetInt();
|
|
int MapCRC = Unpacker.GetInt();
|
|
int Chunk = Unpacker.GetInt();
|
|
int Size = Unpacker.GetInt();
|
|
const unsigned char *pData = Unpacker.GetRaw(Size);
|
|
|
|
// check for errors
|
|
if(Unpacker.Error() || Size <= 0 || MapCRC != m_MapdownloadCrc || Chunk != m_MapdownloadChunk || !m_MapdownloadFile)
|
|
return;
|
|
|
|
io_write(m_MapdownloadFile, pData, Size);
|
|
|
|
m_MapdownloadAmount += Size;
|
|
|
|
if(Last)
|
|
{
|
|
if(m_MapdownloadFile)
|
|
{
|
|
io_close(m_MapdownloadFile);
|
|
m_MapdownloadFile = 0;
|
|
}
|
|
FinishMapDownload();
|
|
}
|
|
else
|
|
{
|
|
// request new chunk
|
|
m_MapdownloadChunk++;
|
|
|
|
CMsgPacker Msg(NETMSG_REQUEST_MAP_DATA);
|
|
Msg.AddInt(m_MapdownloadChunk);
|
|
SendMsgEx(&Msg, MSGFLAG_VITAL|MSGFLAG_FLUSH);
|
|
|
|
if(g_Config.m_Debug)
|
|
{
|
|
char aBuf[256];
|
|
str_format(aBuf, sizeof(aBuf), "requested chunk %d", m_MapdownloadChunk);
|
|
m_pConsole->Print(IConsole::OUTPUT_LEVEL_DEBUG, "client/network", aBuf);
|
|
}
|
|
}
|
|
}
|
|
else if((pPacket->m_Flags&NET_CHUNKFLAG_VITAL) != 0 && Msg == NETMSG_CON_READY)
|
|
{
|
|
GameClient()->OnConnected();
|
|
}
|
|
else if(Msg == NETMSG_PING)
|
|
{
|
|
CMsgPacker Msg(NETMSG_PING_REPLY);
|
|
SendMsgEx(&Msg, 0);
|
|
}
|
|
else if((pPacket->m_Flags&NET_CHUNKFLAG_VITAL) != 0 && Msg == NETMSG_RCON_CMD_ADD)
|
|
{
|
|
const char *pName = Unpacker.GetString(CUnpacker::SANITIZE_CC);
|
|
const char *pHelp = Unpacker.GetString(CUnpacker::SANITIZE_CC);
|
|
const char *pParams = Unpacker.GetString(CUnpacker::SANITIZE_CC);
|
|
if(Unpacker.Error() == 0)
|
|
m_pConsole->RegisterTemp(pName, pParams, CFGFLAG_SERVER, pHelp);
|
|
}
|
|
else if((pPacket->m_Flags&NET_CHUNKFLAG_VITAL) != 0 && Msg == NETMSG_RCON_CMD_REM)
|
|
{
|
|
const char *pName = Unpacker.GetString(CUnpacker::SANITIZE_CC);
|
|
if(Unpacker.Error() == 0)
|
|
m_pConsole->DeregisterTemp(pName);
|
|
}
|
|
else if((pPacket->m_Flags&NET_CHUNKFLAG_VITAL) != 0 && Msg == NETMSG_RCON_AUTH_STATUS)
|
|
{
|
|
int Result = Unpacker.GetInt();
|
|
if(Unpacker.Error() == 0)
|
|
m_RconAuthed[g_Config.m_ClDummy] = Result;
|
|
int Old = m_UseTempRconCommands;
|
|
m_UseTempRconCommands = Unpacker.GetInt();
|
|
if(Unpacker.Error() != 0)
|
|
m_UseTempRconCommands = 0;
|
|
if(Old != 0 && m_UseTempRconCommands == 0)
|
|
m_pConsole->DeregisterTempAll();
|
|
}
|
|
else if((pPacket->m_Flags&NET_CHUNKFLAG_VITAL) != 0 && Msg == NETMSG_RCON_LINE)
|
|
{
|
|
const char *pLine = Unpacker.GetString();
|
|
if(Unpacker.Error() == 0)
|
|
GameClient()->OnRconLine(pLine);
|
|
}
|
|
else if(Msg == NETMSG_PING_REPLY)
|
|
{
|
|
char aBuf[256];
|
|
str_format(aBuf, sizeof(aBuf), "latency %.2f", (time_get() - m_PingStartTime)*1000 / (float)time_freq());
|
|
m_pConsole->Print(IConsole::OUTPUT_LEVEL_STANDARD, "client/network", aBuf);
|
|
}
|
|
else if(Msg == NETMSG_INPUTTIMING)
|
|
{
|
|
int InputPredTick = Unpacker.GetInt();
|
|
int TimeLeft = Unpacker.GetInt();
|
|
int64 Now = time_get();
|
|
|
|
// adjust our prediction time
|
|
int64 Target = 0;
|
|
for(int k = 0; k < 200; k++)
|
|
{
|
|
if(m_aInputs[g_Config.m_ClDummy][k].m_Tick == InputPredTick)
|
|
{
|
|
Target = m_aInputs[g_Config.m_ClDummy][k].m_PredictedTime + (Now - m_aInputs[g_Config.m_ClDummy][k].m_Time);
|
|
Target = Target - (int64)(((TimeLeft-PREDICTION_MARGIN)/1000.0f)*time_freq());
|
|
break;
|
|
}
|
|
}
|
|
|
|
if(Target)
|
|
m_PredictedTime.Update(&m_InputtimeMarginGraph, Target, TimeLeft, 1);
|
|
}
|
|
else if(Msg == NETMSG_SNAP || Msg == NETMSG_SNAPSINGLE || Msg == NETMSG_SNAPEMPTY)
|
|
{
|
|
int NumParts = 1;
|
|
int Part = 0;
|
|
int GameTick = Unpacker.GetInt();
|
|
int DeltaTick = GameTick-Unpacker.GetInt();
|
|
int PartSize = 0;
|
|
int Crc = 0;
|
|
int CompleteSize = 0;
|
|
const char *pData = 0;
|
|
|
|
// only allow packets from the server we actually want
|
|
if(net_addr_comp(&pPacket->m_Address, &m_ServerAddress))
|
|
return;
|
|
|
|
// we are not allowed to process snapshot yet
|
|
if(State() < IClient::STATE_LOADING)
|
|
return;
|
|
|
|
if(Msg == NETMSG_SNAP)
|
|
{
|
|
NumParts = Unpacker.GetInt();
|
|
Part = Unpacker.GetInt();
|
|
}
|
|
|
|
if(Msg != NETMSG_SNAPEMPTY)
|
|
{
|
|
Crc = Unpacker.GetInt();
|
|
PartSize = Unpacker.GetInt();
|
|
}
|
|
|
|
pData = (const char *)Unpacker.GetRaw(PartSize);
|
|
|
|
if(Unpacker.Error() || NumParts < 1 || Part < 0 || PartSize < 0)
|
|
return;
|
|
|
|
if(GameTick >= m_CurrentRecvTick[g_Config.m_ClDummy])
|
|
{
|
|
if(GameTick != m_CurrentRecvTick[g_Config.m_ClDummy])
|
|
{
|
|
m_SnapshotParts = 0;
|
|
m_CurrentRecvTick[g_Config.m_ClDummy] = GameTick;
|
|
}
|
|
|
|
mem_copy((char*)m_aSnapshotIncomingData + Part*MAX_SNAPSHOT_PACKSIZE, pData, clamp(PartSize, 0, (int)sizeof(m_aSnapshotIncomingData) - Part*MAX_SNAPSHOT_PACKSIZE));
|
|
m_SnapshotParts |= 1<<Part;
|
|
|
|
if(m_SnapshotParts == (unsigned)((1<<NumParts)-1))
|
|
{
|
|
static CSnapshot Emptysnap;
|
|
CSnapshot *pDeltaShot = &Emptysnap;
|
|
int PurgeTick;
|
|
void *pDeltaData;
|
|
int DeltaSize;
|
|
unsigned char aTmpBuffer2[CSnapshot::MAX_SIZE];
|
|
unsigned char aTmpBuffer3[CSnapshot::MAX_SIZE];
|
|
CSnapshot *pTmpBuffer3 = (CSnapshot*)aTmpBuffer3; // Fix compiler warning for strict-aliasing
|
|
int SnapSize;
|
|
|
|
CompleteSize = (NumParts-1) * MAX_SNAPSHOT_PACKSIZE + PartSize;
|
|
|
|
// reset snapshoting
|
|
m_SnapshotParts = 0;
|
|
|
|
// find snapshot that we should use as delta
|
|
Emptysnap.Clear();
|
|
|
|
// find delta
|
|
if(DeltaTick >= 0)
|
|
{
|
|
int DeltashotSize = m_SnapshotStorage[g_Config.m_ClDummy].Get(DeltaTick, 0, &pDeltaShot, 0);
|
|
|
|
if(DeltashotSize < 0)
|
|
{
|
|
// couldn't find the delta snapshots that the server used
|
|
// to compress this snapshot. force the server to resync
|
|
if(g_Config.m_Debug)
|
|
{
|
|
char aBuf[256];
|
|
str_format(aBuf, sizeof(aBuf), "error, couldn't find the delta snapshot");
|
|
m_pConsole->Print(IConsole::OUTPUT_LEVEL_DEBUG, "client", aBuf);
|
|
}
|
|
|
|
// ack snapshot
|
|
// TODO: combine this with the input message
|
|
m_AckGameTick[g_Config.m_ClDummy] = -1;
|
|
return;
|
|
}
|
|
}
|
|
|
|
// decompress snapshot
|
|
pDeltaData = m_SnapshotDelta.EmptyDelta();
|
|
DeltaSize = sizeof(int)*3;
|
|
|
|
if(CompleteSize)
|
|
{
|
|
int IntSize = CVariableInt::Decompress(m_aSnapshotIncomingData, CompleteSize, aTmpBuffer2, sizeof(aTmpBuffer2));
|
|
|
|
if(IntSize < 0) // failure during decompression, bail
|
|
return;
|
|
|
|
pDeltaData = aTmpBuffer2;
|
|
DeltaSize = IntSize;
|
|
}
|
|
|
|
// unpack delta
|
|
SnapSize = m_SnapshotDelta.UnpackDelta(pDeltaShot, pTmpBuffer3, pDeltaData, DeltaSize);
|
|
if(SnapSize < 0)
|
|
{
|
|
dbg_msg("client", "delta unpack failed!=%d", SnapSize);
|
|
return;
|
|
}
|
|
|
|
if(Msg != NETMSG_SNAPEMPTY && pTmpBuffer3->Crc() != Crc)
|
|
{
|
|
if(g_Config.m_Debug)
|
|
{
|
|
char aBuf[256];
|
|
str_format(aBuf, sizeof(aBuf), "snapshot crc error #%d - tick=%d wantedcrc=%d gotcrc=%d compressed_size=%d delta_tick=%d",
|
|
m_SnapCrcErrors, GameTick, Crc, pTmpBuffer3->Crc(), CompleteSize, DeltaTick);
|
|
m_pConsole->Print(IConsole::OUTPUT_LEVEL_DEBUG, "client", aBuf);
|
|
}
|
|
|
|
m_SnapCrcErrors++;
|
|
if(m_SnapCrcErrors > 10)
|
|
{
|
|
// to many errors, send reset
|
|
m_AckGameTick[g_Config.m_ClDummy] = -1;
|
|
SendInput();
|
|
m_SnapCrcErrors = 0;
|
|
}
|
|
return;
|
|
}
|
|
else
|
|
{
|
|
if(m_SnapCrcErrors)
|
|
m_SnapCrcErrors--;
|
|
}
|
|
|
|
// purge old snapshots
|
|
PurgeTick = DeltaTick;
|
|
if(m_aSnapshots[g_Config.m_ClDummy][SNAP_PREV] && m_aSnapshots[g_Config.m_ClDummy][SNAP_PREV]->m_Tick < PurgeTick)
|
|
PurgeTick = m_aSnapshots[g_Config.m_ClDummy][SNAP_PREV]->m_Tick;
|
|
if(m_aSnapshots[g_Config.m_ClDummy][SNAP_CURRENT] && m_aSnapshots[g_Config.m_ClDummy][SNAP_CURRENT]->m_Tick < PurgeTick)
|
|
PurgeTick = m_aSnapshots[g_Config.m_ClDummy][SNAP_CURRENT]->m_Tick;
|
|
m_SnapshotStorage[g_Config.m_ClDummy].PurgeUntil(PurgeTick);
|
|
|
|
// add new
|
|
m_SnapshotStorage[g_Config.m_ClDummy].Add(GameTick, time_get(), SnapSize, pTmpBuffer3, 1);
|
|
|
|
// for antiping: if the projectile netobjects from the server contains extra data, this is removed and the original content restored before recording demo
|
|
unsigned char aExtraInfoRemoved[CSnapshot::MAX_SIZE];
|
|
mem_copy(aExtraInfoRemoved, pTmpBuffer3, SnapSize);
|
|
CServerInfo Info;
|
|
GetServerInfo(&Info);
|
|
if(IsDDNet(&Info))
|
|
SnapshotRemoveExtraInfo(aExtraInfoRemoved);
|
|
|
|
// add snapshot to demo
|
|
for(int i = 0; i < RECORDER_MAX; i++)
|
|
{
|
|
if(m_DemoRecorder[i].IsRecording())
|
|
{
|
|
// write snapshot
|
|
m_DemoRecorder[i].RecordSnapshot(GameTick, aExtraInfoRemoved, SnapSize);
|
|
}
|
|
}
|
|
|
|
// apply snapshot, cycle pointers
|
|
m_ReceivedSnapshots[g_Config.m_ClDummy]++;
|
|
|
|
m_CurrentRecvTick[g_Config.m_ClDummy] = GameTick;
|
|
|
|
// we got two snapshots until we see us self as connected
|
|
if(m_ReceivedSnapshots[g_Config.m_ClDummy] == 2)
|
|
{
|
|
// start at 200ms and work from there
|
|
m_PredictedTime.Init(GameTick*time_freq()/50);
|
|
m_PredictedTime.SetAdjustSpeed(1, 1000.0f);
|
|
m_GameTime[g_Config.m_ClDummy].Init((GameTick-1)*time_freq()/50);
|
|
m_aSnapshots[g_Config.m_ClDummy][SNAP_PREV] = m_SnapshotStorage[g_Config.m_ClDummy].m_pFirst;
|
|
m_aSnapshots[g_Config.m_ClDummy][SNAP_CURRENT] = m_SnapshotStorage[g_Config.m_ClDummy].m_pLast;
|
|
m_LocalStartTime = time_get();
|
|
SetState(IClient::STATE_ONLINE);
|
|
DemoRecorder_HandleAutoStart();
|
|
}
|
|
|
|
// adjust game time
|
|
if(m_ReceivedSnapshots[g_Config.m_ClDummy] > 2)
|
|
{
|
|
int64 Now = m_GameTime[g_Config.m_ClDummy].Get(time_get());
|
|
int64 TickStart = GameTick*time_freq()/50;
|
|
int64 TimeLeft = (TickStart-Now)*1000 / time_freq();
|
|
m_GameTime[g_Config.m_ClDummy].Update(&m_GametimeMarginGraph, (GameTick-1)*time_freq()/50, TimeLeft, 0);
|
|
}
|
|
|
|
if(m_ReceivedSnapshots[g_Config.m_ClDummy] > 50 && !m_aTimeoutCodeSent[g_Config.m_ClDummy])
|
|
{
|
|
if(IsDDNet(&m_CurrentServerInfo))
|
|
{
|
|
m_aTimeoutCodeSent[g_Config.m_ClDummy] = true;
|
|
CNetMsg_Cl_Say Msg;
|
|
Msg.m_Team = 0;
|
|
char aBuf[256];
|
|
str_format(aBuf, sizeof(aBuf), "/timeout %s", m_aTimeoutCodes[g_Config.m_ClDummy]);
|
|
Msg.m_pMessage = aBuf;
|
|
CMsgPacker Packer(Msg.MsgID());
|
|
Msg.Pack(&Packer);
|
|
SendMsgExY(&Packer, MSGFLAG_VITAL, false, g_Config.m_ClDummy);
|
|
}
|
|
}
|
|
|
|
// ack snapshot
|
|
m_AckGameTick[g_Config.m_ClDummy] = GameTick;
|
|
}
|
|
}
|
|
}
|
|
else if(Msg == NETMSG_RCONTYPE)
|
|
{
|
|
bool UsernameReq = Unpacker.GetInt() & 1;
|
|
GameClient()->OnRconType(UsernameReq);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if((pPacket->m_Flags&NET_CHUNKFLAG_VITAL) != 0 || Msg == NETMSGTYPE_SV_EXTRAPROJECTILE)
|
|
{
|
|
// game message
|
|
for(int i = 0; i < RECORDER_MAX; i++)
|
|
if(m_DemoRecorder[i].IsRecording())
|
|
m_DemoRecorder[i].RecordMessage(pPacket->m_pData, pPacket->m_DataSize);
|
|
|
|
GameClient()->OnMessage(Msg, &Unpacker);
|
|
}
|
|
}
|
|
}
|
|
|
|
void CClient::ProcessServerPacketDummy(CNetChunk *pPacket)
|
|
{
|
|
CUnpacker Unpacker;
|
|
Unpacker.Reset(pPacket->m_pData, pPacket->m_DataSize);
|
|
|
|
// unpack msgid and system flag
|
|
int Msg = Unpacker.GetInt();
|
|
int Sys = Msg&1;
|
|
Msg >>= 1;
|
|
|
|
if(Unpacker.Error())
|
|
return;
|
|
|
|
if(Sys)
|
|
{
|
|
if(Msg == NETMSG_CON_READY)
|
|
{
|
|
m_DummyConnected = true;
|
|
g_Config.m_ClDummy = 1;
|
|
Rcon("crashmeplx");
|
|
if(m_RconAuthed[0])
|
|
RconAuth("", m_RconPassword);
|
|
}
|
|
else if(Msg == NETMSG_SNAP || Msg == NETMSG_SNAPSINGLE || Msg == NETMSG_SNAPEMPTY)
|
|
{
|
|
int NumParts = 1;
|
|
int Part = 0;
|
|
int GameTick = Unpacker.GetInt();
|
|
int DeltaTick = GameTick-Unpacker.GetInt();
|
|
int PartSize = 0;
|
|
int Crc = 0;
|
|
int CompleteSize = 0;
|
|
const char *pData = 0;
|
|
|
|
// only allow packets from the server we actually want
|
|
if(net_addr_comp(&pPacket->m_Address, &m_ServerAddress))
|
|
return;
|
|
|
|
// we are not allowed to process snapshot yet
|
|
if(State() < IClient::STATE_LOADING)
|
|
return;
|
|
|
|
if(Msg == NETMSG_SNAP)
|
|
{
|
|
NumParts = Unpacker.GetInt();
|
|
Part = Unpacker.GetInt();
|
|
}
|
|
|
|
if(Msg != NETMSG_SNAPEMPTY)
|
|
{
|
|
Crc = Unpacker.GetInt();
|
|
PartSize = Unpacker.GetInt();
|
|
}
|
|
|
|
pData = (const char *)Unpacker.GetRaw(PartSize);
|
|
|
|
if(Unpacker.Error() || NumParts < 1 || Part < 0 || PartSize < 0)
|
|
return;
|
|
|
|
if(GameTick >= m_CurrentRecvTick[!g_Config.m_ClDummy])
|
|
{
|
|
if(GameTick != m_CurrentRecvTick[!g_Config.m_ClDummy])
|
|
{
|
|
m_SnapshotParts = 0;
|
|
m_CurrentRecvTick[!g_Config.m_ClDummy] = GameTick;
|
|
}
|
|
|
|
mem_copy((char*)m_aSnapshotIncomingData + Part*MAX_SNAPSHOT_PACKSIZE, pData, clamp(PartSize, 0, (int)sizeof(m_aSnapshotIncomingData) - Part*MAX_SNAPSHOT_PACKSIZE));
|
|
m_SnapshotParts |= 1<<Part;
|
|
|
|
if(m_SnapshotParts == (unsigned)((1<<NumParts)-1))
|
|
{
|
|
static CSnapshot Emptysnap;
|
|
CSnapshot *pDeltaShot = &Emptysnap;
|
|
int PurgeTick;
|
|
void *pDeltaData;
|
|
int DeltaSize;
|
|
unsigned char aTmpBuffer2[CSnapshot::MAX_SIZE];
|
|
unsigned char aTmpBuffer3[CSnapshot::MAX_SIZE];
|
|
CSnapshot *pTmpBuffer3 = (CSnapshot*)aTmpBuffer3; // Fix compiler warning for strict-aliasing
|
|
int SnapSize;
|
|
|
|
CompleteSize = (NumParts-1) * MAX_SNAPSHOT_PACKSIZE + PartSize;
|
|
|
|
// reset snapshoting
|
|
m_SnapshotParts = 0;
|
|
|
|
// find snapshot that we should use as delta
|
|
Emptysnap.Clear();
|
|
|
|
// find delta
|
|
if(DeltaTick >= 0)
|
|
{
|
|
int DeltashotSize = m_SnapshotStorage[!g_Config.m_ClDummy].Get(DeltaTick, 0, &pDeltaShot, 0);
|
|
|
|
if(DeltashotSize < 0)
|
|
{
|
|
// couldn't find the delta snapshots that the server used
|
|
// to compress this snapshot. force the server to resync
|
|
if(g_Config.m_Debug)
|
|
{
|
|
char aBuf[256];
|
|
str_format(aBuf, sizeof(aBuf), "error, couldn't find the delta snapshot");
|
|
m_pConsole->Print(IConsole::OUTPUT_LEVEL_DEBUG, "client", aBuf);
|
|
}
|
|
|
|
// ack snapshot
|
|
// TODO: combine this with the input message
|
|
m_AckGameTick[!g_Config.m_ClDummy] = -1;
|
|
return;
|
|
}
|
|
}
|
|
|
|
// decompress snapshot
|
|
pDeltaData = m_SnapshotDelta.EmptyDelta();
|
|
DeltaSize = sizeof(int)*3;
|
|
|
|
if(CompleteSize)
|
|
{
|
|
int IntSize = CVariableInt::Decompress(m_aSnapshotIncomingData, CompleteSize, aTmpBuffer2, sizeof(aTmpBuffer2));
|
|
|
|
if(IntSize < 0) // failure during decompression, bail
|
|
return;
|
|
|
|
pDeltaData = aTmpBuffer2;
|
|
DeltaSize = IntSize;
|
|
}
|
|
|
|
// unpack delta
|
|
SnapSize = m_SnapshotDelta.UnpackDelta(pDeltaShot, pTmpBuffer3, pDeltaData, DeltaSize);
|
|
if(SnapSize < 0)
|
|
{
|
|
m_pConsole->Print(IConsole::OUTPUT_LEVEL_DEBUG, "client", "delta unpack failed!");
|
|
return;
|
|
}
|
|
|
|
if(Msg != NETMSG_SNAPEMPTY && pTmpBuffer3->Crc() != Crc)
|
|
{
|
|
if(g_Config.m_Debug)
|
|
{
|
|
char aBuf[256];
|
|
str_format(aBuf, sizeof(aBuf), "snapshot crc error #%d - tick=%d wantedcrc=%d gotcrc=%d compressed_size=%d delta_tick=%d",
|
|
m_SnapCrcErrors, GameTick, Crc, pTmpBuffer3->Crc(), CompleteSize, DeltaTick);
|
|
m_pConsole->Print(IConsole::OUTPUT_LEVEL_DEBUG, "client", aBuf);
|
|
}
|
|
|
|
m_SnapCrcErrors++;
|
|
if(m_SnapCrcErrors > 10)
|
|
{
|
|
// to many errors, send reset
|
|
m_AckGameTick[!g_Config.m_ClDummy] = -1;
|
|
SendInput();
|
|
m_SnapCrcErrors = 0;
|
|
}
|
|
return;
|
|
}
|
|
else
|
|
{
|
|
if(m_SnapCrcErrors)
|
|
m_SnapCrcErrors--;
|
|
}
|
|
|
|
// purge old snapshots
|
|
PurgeTick = DeltaTick;
|
|
if(m_aSnapshots[!g_Config.m_ClDummy][SNAP_PREV] && m_aSnapshots[!g_Config.m_ClDummy][SNAP_PREV]->m_Tick < PurgeTick)
|
|
PurgeTick = m_aSnapshots[!g_Config.m_ClDummy][SNAP_PREV]->m_Tick;
|
|
if(m_aSnapshots[!g_Config.m_ClDummy][SNAP_CURRENT] && m_aSnapshots[!g_Config.m_ClDummy][SNAP_CURRENT]->m_Tick < PurgeTick)
|
|
PurgeTick = m_aSnapshots[!g_Config.m_ClDummy][SNAP_CURRENT]->m_Tick;
|
|
m_SnapshotStorage[!g_Config.m_ClDummy].PurgeUntil(PurgeTick);
|
|
|
|
// add new
|
|
m_SnapshotStorage[!g_Config.m_ClDummy].Add(GameTick, time_get(), SnapSize, pTmpBuffer3, 1);
|
|
|
|
// apply snapshot, cycle pointers
|
|
m_ReceivedSnapshots[!g_Config.m_ClDummy]++;
|
|
|
|
m_CurrentRecvTick[!g_Config.m_ClDummy] = GameTick;
|
|
|
|
// we got two snapshots until we see us self as connected
|
|
if(m_ReceivedSnapshots[!g_Config.m_ClDummy] == 2)
|
|
{
|
|
// start at 200ms and work from there
|
|
//m_PredictedTime[!g_Config.m_ClDummy].Init(GameTick*time_freq()/50);
|
|
//m_PredictedTime[!g_Config.m_ClDummy].SetAdjustSpeed(1, 1000.0f);
|
|
m_GameTime[!g_Config.m_ClDummy].Init((GameTick-1)*time_freq()/50);
|
|
m_aSnapshots[!g_Config.m_ClDummy][SNAP_PREV] = m_SnapshotStorage[!g_Config.m_ClDummy].m_pFirst;
|
|
m_aSnapshots[!g_Config.m_ClDummy][SNAP_CURRENT] = m_SnapshotStorage[!g_Config.m_ClDummy].m_pLast;
|
|
m_LocalStartTime = time_get();
|
|
SetState(IClient::STATE_ONLINE);
|
|
}
|
|
|
|
// adjust game time
|
|
if(m_ReceivedSnapshots[!g_Config.m_ClDummy] > 2)
|
|
{
|
|
int64 Now = m_GameTime[!g_Config.m_ClDummy].Get(time_get());
|
|
int64 TickStart = GameTick*time_freq()/50;
|
|
int64 TimeLeft = (TickStart-Now)*1000 / time_freq();
|
|
m_GameTime[!g_Config.m_ClDummy].Update(&m_GametimeMarginGraph, (GameTick-1)*time_freq()/50, TimeLeft, 0);
|
|
}
|
|
|
|
// ack snapshot
|
|
m_AckGameTick[!g_Config.m_ClDummy] = GameTick;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
GameClient()->OnMessage(Msg, &Unpacker, 1);
|
|
}
|
|
}
|
|
|
|
void CClient::ResetMapDownload()
|
|
{
|
|
if(m_pMapdownloadTask)
|
|
{
|
|
m_pMapdownloadTask->Abort();
|
|
m_pMapdownloadTask = NULL;
|
|
}
|
|
m_MapdownloadFile = 0;
|
|
m_MapdownloadAmount = 0;
|
|
}
|
|
|
|
void CClient::FinishMapDownload()
|
|
{
|
|
const char *pError;
|
|
m_pConsole->Print(IConsole::OUTPUT_LEVEL_ADDINFO, "client/network", "download complete, loading map");
|
|
|
|
int prev = m_MapdownloadTotalsize;
|
|
m_MapdownloadTotalsize = -1;
|
|
|
|
// load map
|
|
pError = LoadMap(m_aMapdownloadName, m_aMapdownloadFilename, m_MapdownloadCrc);
|
|
if(!pError)
|
|
{
|
|
ResetMapDownload();
|
|
m_pConsole->Print(IConsole::OUTPUT_LEVEL_ADDINFO, "client/network", "loading done");
|
|
SendReady();
|
|
}
|
|
else if(m_pMapdownloadTask) // fallback
|
|
{
|
|
ResetMapDownload();
|
|
m_MapdownloadTotalsize = prev;
|
|
SendMapRequest();
|
|
}
|
|
else
|
|
{
|
|
if(m_MapdownloadFile)
|
|
{
|
|
io_close(m_MapdownloadFile);
|
|
m_MapdownloadFile = 0;
|
|
}
|
|
ResetMapDownload();
|
|
DisconnectWithReason(pError);
|
|
}
|
|
}
|
|
|
|
void CClient::ResetDDNetInfo()
|
|
{
|
|
if(m_pDDNetInfoTask)
|
|
{
|
|
m_pDDNetInfoTask->Abort();
|
|
m_pDDNetInfoTask = NULL;
|
|
}
|
|
}
|
|
|
|
void CClient::FinishDDNetInfo()
|
|
{
|
|
ResetDDNetInfo();
|
|
m_pStorage->RenameFile("ddnet-info.json.tmp", "ddnet-info.json", IStorage::TYPE_SAVE);
|
|
LoadDDNetInfo();
|
|
}
|
|
|
|
void CClient::LoadDDNetInfo()
|
|
{
|
|
const json_value *pDDNetInfo = m_ServerBrowser.LoadDDNetInfo();
|
|
|
|
if(!pDDNetInfo)
|
|
return;
|
|
|
|
const json_value *pVersion = json_object_get(pDDNetInfo, "version");
|
|
if(pVersion && pVersion->type == json_string)
|
|
{
|
|
const char *pVersionString = json_string_get(pVersion);
|
|
if(str_comp(pVersionString, GAME_RELEASE_VERSION))
|
|
{
|
|
str_copy(m_aVersionStr, pVersionString, sizeof(m_aVersionStr));
|
|
}
|
|
else
|
|
{
|
|
m_aVersionStr[0] = '0';
|
|
m_aVersionStr[1] = '\0';
|
|
}
|
|
}
|
|
|
|
const json_value *pNews = json_object_get(pDDNetInfo, "news");
|
|
if(pNews && pNews->type == json_string)
|
|
{
|
|
const char *pNewsString = json_string_get(pNews);
|
|
|
|
if(m_aNews[0] && str_comp(m_aNews, pNewsString))
|
|
g_Config.m_UiPage = CMenus::PAGE_NEWS;
|
|
|
|
str_copy(m_aNews, pNewsString, sizeof(m_aNews));
|
|
}
|
|
}
|
|
|
|
void CClient::PumpNetwork()
|
|
{
|
|
for(int i=0; i<3; i++)
|
|
{
|
|
m_NetClient[i].Update();
|
|
}
|
|
|
|
if(State() != IClient::STATE_DEMOPLAYBACK)
|
|
{
|
|
// check for errors
|
|
if(State() != IClient::STATE_OFFLINE && State() != IClient::STATE_QUITING && m_NetClient[0].State() == NETSTATE_OFFLINE)
|
|
{
|
|
SetState(IClient::STATE_OFFLINE);
|
|
Disconnect();
|
|
char aBuf[256];
|
|
str_format(aBuf, sizeof(aBuf), "offline error='%s'", m_NetClient[0].ErrorString());
|
|
m_pConsole->Print(IConsole::OUTPUT_LEVEL_STANDARD, "client", aBuf);
|
|
}
|
|
|
|
//
|
|
if(State() == IClient::STATE_CONNECTING && m_NetClient[0].State() == NETSTATE_ONLINE)
|
|
{
|
|
// we switched to online
|
|
m_pConsole->Print(IConsole::OUTPUT_LEVEL_STANDARD, "client", "connected, sending info");
|
|
SetState(IClient::STATE_LOADING);
|
|
SendInfo();
|
|
}
|
|
}
|
|
|
|
// process packets
|
|
CNetChunk Packet;
|
|
for(int i=0; i < 3; i++)
|
|
{
|
|
while(m_NetClient[i].Recv(&Packet))
|
|
{
|
|
if(Packet.m_ClientID == -1 || i > 1)
|
|
{
|
|
ProcessConnlessPacket(&Packet);
|
|
}
|
|
else if(i > 0 && i < 2)
|
|
{
|
|
if(g_Config.m_ClDummy)
|
|
ProcessServerPacket(&Packet); //self
|
|
else
|
|
ProcessServerPacketDummy(&Packet); //multiclient
|
|
}
|
|
else
|
|
{
|
|
if(g_Config.m_ClDummy)
|
|
ProcessServerPacketDummy(&Packet); //multiclient
|
|
else
|
|
ProcessServerPacket(&Packet); //self
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void CClient::OnDemoPlayerSnapshot(void *pData, int Size)
|
|
{
|
|
// update ticks, they could have changed
|
|
const CDemoPlayer::CPlaybackInfo *pInfo = m_DemoPlayer.Info();
|
|
CSnapshotStorage::CHolder *pTemp;
|
|
m_CurGameTick[g_Config.m_ClDummy] = pInfo->m_Info.m_CurrentTick;
|
|
m_PrevGameTick[g_Config.m_ClDummy] = pInfo->m_PreviousTick;
|
|
|
|
// handle snapshots
|
|
pTemp = m_aSnapshots[g_Config.m_ClDummy][SNAP_PREV];
|
|
m_aSnapshots[g_Config.m_ClDummy][SNAP_PREV] = m_aSnapshots[g_Config.m_ClDummy][SNAP_CURRENT];
|
|
m_aSnapshots[g_Config.m_ClDummy][SNAP_CURRENT] = pTemp;
|
|
|
|
mem_copy(m_aSnapshots[g_Config.m_ClDummy][SNAP_CURRENT]->m_pSnap, pData, Size);
|
|
mem_copy(m_aSnapshots[g_Config.m_ClDummy][SNAP_CURRENT]->m_pAltSnap, pData, Size);
|
|
|
|
GameClient()->OnNewSnapshot();
|
|
}
|
|
|
|
void CClient::OnDemoPlayerMessage(void *pData, int Size)
|
|
{
|
|
CUnpacker Unpacker;
|
|
Unpacker.Reset(pData, Size);
|
|
|
|
// unpack msgid and system flag
|
|
int Msg = Unpacker.GetInt();
|
|
int Sys = Msg&1;
|
|
Msg >>= 1;
|
|
|
|
if(Unpacker.Error())
|
|
return;
|
|
|
|
if(!Sys)
|
|
GameClient()->OnMessage(Msg, &Unpacker);
|
|
}
|
|
/*
|
|
const IDemoPlayer::CInfo *client_demoplayer_getinfo()
|
|
{
|
|
static DEMOPLAYBACK_INFO ret;
|
|
const DEMOREC_PLAYBACKINFO *info = m_DemoPlayer.Info();
|
|
ret.first_tick = info->first_tick;
|
|
ret.last_tick = info->last_tick;
|
|
ret.current_tick = info->current_tick;
|
|
ret.paused = info->paused;
|
|
ret.speed = info->speed;
|
|
return &ret;
|
|
}*/
|
|
|
|
/*
|
|
void DemoPlayer()->SetPos(float percent)
|
|
{
|
|
demorec_playback_set(percent);
|
|
}
|
|
|
|
void DemoPlayer()->SetSpeed(float speed)
|
|
{
|
|
demorec_playback_setspeed(speed);
|
|
}
|
|
|
|
void DemoPlayer()->SetPause(int paused)
|
|
{
|
|
if(paused)
|
|
demorec_playback_pause();
|
|
else
|
|
demorec_playback_unpause();
|
|
}*/
|
|
|
|
void CClient::Update()
|
|
{
|
|
if(State() == IClient::STATE_DEMOPLAYBACK)
|
|
{
|
|
m_DemoPlayer.Update();
|
|
if(m_DemoPlayer.IsPlaying())
|
|
{
|
|
// update timers
|
|
const CDemoPlayer::CPlaybackInfo *pInfo = m_DemoPlayer.Info();
|
|
m_CurGameTick[g_Config.m_ClDummy] = pInfo->m_Info.m_CurrentTick;
|
|
m_PrevGameTick[g_Config.m_ClDummy] = pInfo->m_PreviousTick;
|
|
m_GameIntraTick[g_Config.m_ClDummy] = pInfo->m_IntraTick;
|
|
m_GameTickTime[g_Config.m_ClDummy] = pInfo->m_TickTime;
|
|
}
|
|
else
|
|
{
|
|
// disconnect on error
|
|
Disconnect();
|
|
}
|
|
}
|
|
else if(State() == IClient::STATE_ONLINE && m_ReceivedSnapshots[g_Config.m_ClDummy] >= 3)
|
|
{
|
|
if(m_ReceivedSnapshots[!g_Config.m_ClDummy] >= 3)
|
|
{
|
|
// switch dummy snapshot
|
|
int64 Now = m_GameTime[!g_Config.m_ClDummy].Get(time_get());
|
|
while(1)
|
|
{
|
|
CSnapshotStorage::CHolder *pCur = m_aSnapshots[!g_Config.m_ClDummy][SNAP_CURRENT];
|
|
int64 TickStart = (pCur->m_Tick)*time_freq()/50;
|
|
|
|
if(TickStart < Now)
|
|
{
|
|
CSnapshotStorage::CHolder *pNext = m_aSnapshots[!g_Config.m_ClDummy][SNAP_CURRENT]->m_pNext;
|
|
if(pNext)
|
|
{
|
|
m_aSnapshots[!g_Config.m_ClDummy][SNAP_PREV] = m_aSnapshots[!g_Config.m_ClDummy][SNAP_CURRENT];
|
|
m_aSnapshots[!g_Config.m_ClDummy][SNAP_CURRENT] = pNext;
|
|
|
|
// set ticks
|
|
m_CurGameTick[!g_Config.m_ClDummy] = m_aSnapshots[!g_Config.m_ClDummy][SNAP_CURRENT]->m_Tick;
|
|
m_PrevGameTick[!g_Config.m_ClDummy] = m_aSnapshots[!g_Config.m_ClDummy][SNAP_PREV]->m_Tick;
|
|
}
|
|
else
|
|
break;
|
|
}
|
|
else
|
|
break;
|
|
}
|
|
}
|
|
|
|
// switch snapshot
|
|
int Repredict = 0;
|
|
int64 Freq = time_freq();
|
|
int64 Now = m_GameTime[g_Config.m_ClDummy].Get(time_get());
|
|
int64 PredNow = m_PredictedTime.Get(time_get());
|
|
|
|
while(1)
|
|
{
|
|
CSnapshotStorage::CHolder *pCur = m_aSnapshots[g_Config.m_ClDummy][SNAP_CURRENT];
|
|
int64 TickStart = (pCur->m_Tick)*time_freq()/50;
|
|
|
|
if(TickStart < Now)
|
|
{
|
|
CSnapshotStorage::CHolder *pNext = m_aSnapshots[g_Config.m_ClDummy][SNAP_CURRENT]->m_pNext;
|
|
if(pNext)
|
|
{
|
|
m_aSnapshots[g_Config.m_ClDummy][SNAP_PREV] = m_aSnapshots[g_Config.m_ClDummy][SNAP_CURRENT];
|
|
m_aSnapshots[g_Config.m_ClDummy][SNAP_CURRENT] = pNext;
|
|
|
|
// set ticks
|
|
m_CurGameTick[g_Config.m_ClDummy] = m_aSnapshots[g_Config.m_ClDummy][SNAP_CURRENT]->m_Tick;
|
|
m_PrevGameTick[g_Config.m_ClDummy] = m_aSnapshots[g_Config.m_ClDummy][SNAP_PREV]->m_Tick;
|
|
|
|
if(m_LastDummy2 == (bool)g_Config.m_ClDummy && m_aSnapshots[g_Config.m_ClDummy][SNAP_CURRENT] && m_aSnapshots[g_Config.m_ClDummy][SNAP_PREV])
|
|
{
|
|
GameClient()->OnNewSnapshot();
|
|
Repredict = 1;
|
|
}
|
|
}
|
|
else
|
|
break;
|
|
}
|
|
else
|
|
break;
|
|
}
|
|
|
|
if(m_LastDummy2 != (bool)g_Config.m_ClDummy)
|
|
{
|
|
m_LastDummy2 = g_Config.m_ClDummy;
|
|
}
|
|
|
|
if(m_aSnapshots[g_Config.m_ClDummy][SNAP_CURRENT] && m_aSnapshots[g_Config.m_ClDummy][SNAP_PREV])
|
|
{
|
|
int64 CurtickStart = (m_aSnapshots[g_Config.m_ClDummy][SNAP_CURRENT]->m_Tick)*time_freq()/50;
|
|
int64 PrevtickStart = (m_aSnapshots[g_Config.m_ClDummy][SNAP_PREV]->m_Tick)*time_freq()/50;
|
|
int PrevPredTick = (int)(PredNow*50/time_freq());
|
|
int NewPredTick = PrevPredTick+1;
|
|
|
|
m_GameIntraTick[g_Config.m_ClDummy] = (Now - PrevtickStart) / (float)(CurtickStart-PrevtickStart);
|
|
m_GameTickTime[g_Config.m_ClDummy] = (Now - PrevtickStart) / (float)Freq; //(float)SERVER_TICK_SPEED);
|
|
|
|
CurtickStart = NewPredTick*time_freq()/50;
|
|
PrevtickStart = PrevPredTick*time_freq()/50;
|
|
m_PredIntraTick[g_Config.m_ClDummy] = (PredNow - PrevtickStart) / (float)(CurtickStart-PrevtickStart);
|
|
|
|
if(NewPredTick < m_aSnapshots[g_Config.m_ClDummy][SNAP_PREV]->m_Tick-SERVER_TICK_SPEED || NewPredTick > m_aSnapshots[g_Config.m_ClDummy][SNAP_PREV]->m_Tick+SERVER_TICK_SPEED)
|
|
{
|
|
m_pConsole->Print(IConsole::OUTPUT_LEVEL_ADDINFO, "client", "prediction time reset!");
|
|
m_PredictedTime.Init(m_aSnapshots[g_Config.m_ClDummy][SNAP_CURRENT]->m_Tick*time_freq()/50);
|
|
}
|
|
|
|
if(NewPredTick > m_PredTick[g_Config.m_ClDummy])
|
|
{
|
|
m_PredTick[g_Config.m_ClDummy] = NewPredTick;
|
|
Repredict = 1;
|
|
|
|
// send input
|
|
SendInput();
|
|
}
|
|
}
|
|
|
|
// only do sane predictions
|
|
if(Repredict)
|
|
{
|
|
if(m_PredTick[g_Config.m_ClDummy] > m_CurGameTick[g_Config.m_ClDummy] && m_PredTick[g_Config.m_ClDummy] < m_CurGameTick[g_Config.m_ClDummy]+50)
|
|
GameClient()->OnPredict();
|
|
}
|
|
|
|
// fetch server info if we don't have it
|
|
if(State() >= IClient::STATE_LOADING &&
|
|
m_CurrentServerInfoRequestTime >= 0 &&
|
|
time_get() > m_CurrentServerInfoRequestTime)
|
|
{
|
|
m_ServerBrowser.RequestCurrentServer(m_ServerAddress);
|
|
m_CurrentServerInfoRequestTime = time_get()+time_freq()*2;
|
|
}
|
|
}
|
|
|
|
// STRESS TEST: join the server again
|
|
#ifdef CONF_DEBUG
|
|
if(g_Config.m_DbgStress)
|
|
{
|
|
static int64 ActionTaken = 0;
|
|
int64 Now = time_get();
|
|
if(State() == IClient::STATE_OFFLINE)
|
|
{
|
|
if(Now > ActionTaken+time_freq()*2)
|
|
{
|
|
m_pConsole->Print(IConsole::OUTPUT_LEVEL_DEBUG, "stress", "reconnecting!");
|
|
Connect(g_Config.m_DbgStressServer);
|
|
ActionTaken = Now;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if(Now > ActionTaken+time_freq()*(10+g_Config.m_DbgStress))
|
|
{
|
|
m_pConsole->Print(IConsole::OUTPUT_LEVEL_DEBUG, "stress", "disconnecting!");
|
|
Disconnect();
|
|
ActionTaken = Now;
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
|
|
// pump the network
|
|
PumpNetwork();
|
|
|
|
if(m_pMapdownloadTask)
|
|
{
|
|
if(m_pMapdownloadTask->State() == CFetchTask::STATE_DONE)
|
|
FinishMapDownload();
|
|
else if(m_pMapdownloadTask->State() == CFetchTask::STATE_ERROR)
|
|
{
|
|
dbg_msg("webdl", "http failed, falling back to gameserver");
|
|
ResetMapDownload();
|
|
SendMapRequest();
|
|
}
|
|
else if(m_pMapdownloadTask->State() == CFetchTask::STATE_ABORTED)
|
|
{
|
|
m_pMapdownloadTask = NULL;
|
|
}
|
|
}
|
|
|
|
if(m_pDDNetInfoTask)
|
|
{
|
|
if(m_pDDNetInfoTask->State() == CFetchTask::STATE_DONE)
|
|
FinishDDNetInfo();
|
|
else if(m_pDDNetInfoTask->State() == CFetchTask::STATE_ERROR)
|
|
{
|
|
dbg_msg("ddnet-info", "download failed");
|
|
ResetDDNetInfo();
|
|
}
|
|
else if(m_pDDNetInfoTask->State() == CFetchTask::STATE_ABORTED)
|
|
{
|
|
m_pDDNetInfoTask = NULL;
|
|
}
|
|
}
|
|
|
|
// update the maser server registry
|
|
MasterServer()->Update();
|
|
|
|
// update the server browser
|
|
m_ServerBrowser.Update(m_ResortServerBrowser);
|
|
m_ResortServerBrowser = false;
|
|
|
|
// update gameclient
|
|
if(!m_EditorActive)
|
|
GameClient()->OnUpdate();
|
|
|
|
if(m_ReconnectTime > 0 && time_get() > m_ReconnectTime)
|
|
{
|
|
Connect(m_aServerAddressStr);
|
|
m_ReconnectTime = 0;
|
|
}
|
|
}
|
|
|
|
void CClient::RegisterInterfaces()
|
|
{
|
|
Kernel()->RegisterInterface(static_cast<IDemoRecorder*>(&m_DemoRecorder[RECORDER_MANUAL]), false);
|
|
Kernel()->RegisterInterface(static_cast<IDemoPlayer*>(&m_DemoPlayer), false);
|
|
Kernel()->RegisterInterface(static_cast<IGhostRecorder*>(&m_GhostRecorder), false);
|
|
Kernel()->RegisterInterface(static_cast<IGhostLoader*>(&m_GhostLoader), false);
|
|
Kernel()->RegisterInterface(static_cast<IServerBrowser*>(&m_ServerBrowser), false);
|
|
#if !defined(CONF_PLATFORM_MACOSX) && !defined(__ANDROID__)
|
|
Kernel()->RegisterInterface(static_cast<IUpdater*>(&m_Updater), false);
|
|
#endif
|
|
Kernel()->RegisterInterface(static_cast<IFriends*>(&m_Friends), false);
|
|
Kernel()->ReregisterInterface(static_cast<IFriends*>(&m_Foes));
|
|
}
|
|
|
|
void CClient::InitInterfaces()
|
|
{
|
|
// fetch interfaces
|
|
m_pEngine = Kernel()->RequestInterface<IEngine>();
|
|
m_pEditor = Kernel()->RequestInterface<IEditor>();
|
|
//m_pGraphics = Kernel()->RequestInterface<IEngineGraphics>();
|
|
m_pSound = Kernel()->RequestInterface<IEngineSound>();
|
|
m_pGameClient = Kernel()->RequestInterface<IGameClient>();
|
|
m_pInput = Kernel()->RequestInterface<IEngineInput>();
|
|
m_pMap = Kernel()->RequestInterface<IEngineMap>();
|
|
m_pMasterServer = Kernel()->RequestInterface<IEngineMasterServer>();
|
|
#if !defined(CONF_PLATFORM_MACOSX) && !defined(__ANDROID__)
|
|
m_pUpdater = Kernel()->RequestInterface<IUpdater>();
|
|
#endif
|
|
m_pStorage = Kernel()->RequestInterface<IStorage>();
|
|
|
|
m_DemoEditor.Init(m_pGameClient->NetVersion(), &m_SnapshotDelta, m_pConsole, m_pStorage);
|
|
|
|
m_ServerBrowser.SetBaseInfo(&m_NetClient[2], m_pGameClient->NetVersion());
|
|
|
|
FetcherInit();
|
|
|
|
#if !defined(CONF_PLATFORM_MACOSX) && !defined(__ANDROID__)
|
|
m_Updater.Init();
|
|
#endif
|
|
|
|
m_Friends.Init();
|
|
m_Foes.Init(true);
|
|
|
|
m_GhostRecorder.Init();
|
|
m_GhostLoader.Init();
|
|
}
|
|
|
|
void CClient::Run()
|
|
{
|
|
m_LocalStartTime = time_get();
|
|
m_SnapshotParts = 0;
|
|
|
|
if(m_GenerateTimeoutSeed)
|
|
{
|
|
GenerateTimeoutSeed();
|
|
}
|
|
|
|
unsigned int Seed;
|
|
secure_random_fill(&Seed, sizeof(Seed));
|
|
srand(Seed);
|
|
|
|
if(g_Config.m_Debug)
|
|
{
|
|
g_UuidManager.DebugDump();
|
|
}
|
|
|
|
// init SDL
|
|
{
|
|
if(SDL_Init(0) < 0)
|
|
{
|
|
dbg_msg("client", "unable to init SDL base: %s", SDL_GetError());
|
|
return;
|
|
}
|
|
|
|
atexit(SDL_Quit); // ignore_convention
|
|
}
|
|
|
|
// init graphics
|
|
{
|
|
m_pGraphics = CreateEngineGraphicsThreaded();
|
|
|
|
bool RegisterFail = false;
|
|
RegisterFail = RegisterFail || !Kernel()->RegisterInterface(static_cast<IEngineGraphics*>(m_pGraphics)); // register graphics as both
|
|
RegisterFail = RegisterFail || !Kernel()->RegisterInterface(static_cast<IGraphics*>(m_pGraphics), false);
|
|
|
|
if(RegisterFail || m_pGraphics->Init() != 0)
|
|
{
|
|
dbg_msg("client", "couldn't init graphics");
|
|
return;
|
|
}
|
|
}
|
|
|
|
// init sound, allowed to fail
|
|
m_SoundInitFailed = Sound()->Init() != 0;
|
|
|
|
// open socket
|
|
{
|
|
NETADDR BindAddr;
|
|
if(g_Config.m_Bindaddr[0] && net_host_lookup(g_Config.m_Bindaddr, &BindAddr, NETTYPE_ALL) == 0)
|
|
{
|
|
// got bindaddr
|
|
BindAddr.type = NETTYPE_ALL;
|
|
}
|
|
else
|
|
{
|
|
mem_zero(&BindAddr, sizeof(BindAddr));
|
|
BindAddr.type = NETTYPE_ALL;
|
|
}
|
|
for(int i = 0; i < 3; i++)
|
|
{
|
|
do
|
|
{
|
|
BindAddr.port = (secure_rand() % 64511) + 1024;
|
|
}
|
|
while(!m_NetClient[i].Open(BindAddr, 0));
|
|
}
|
|
}
|
|
|
|
// init font rendering
|
|
Kernel()->RequestInterface<IEngineTextRender>()->Init();
|
|
|
|
// init the input
|
|
Input()->Init();
|
|
|
|
// start refreshing addresses while we load
|
|
MasterServer()->RefreshAddresses(m_NetClient[0].NetType());
|
|
|
|
// init the editor
|
|
m_pEditor->Init();
|
|
|
|
// load and save a map to fix it
|
|
/*if(m_pEditor->Load(arg, IStorage::TYPE_ALL))
|
|
m_pEditor->Save(arg);
|
|
return;*/
|
|
|
|
// load data
|
|
if(!LoadData())
|
|
return;
|
|
|
|
GameClient()->OnInit();
|
|
|
|
char aBuf[256];
|
|
str_format(aBuf, sizeof(aBuf), "version %s", GameClient()->NetVersion());
|
|
m_pConsole->Print(IConsole::OUTPUT_LEVEL_STANDARD, "client", aBuf);
|
|
|
|
// connect to the server if wanted
|
|
/*
|
|
if(config.cl_connect[0] != 0)
|
|
Connect(config.cl_connect);
|
|
config.cl_connect[0] = 0;
|
|
*/
|
|
|
|
//
|
|
m_FpsGraph.Init(0.0f, 200.0f);
|
|
|
|
// never start with the editor
|
|
g_Config.m_ClEditor = 0;
|
|
|
|
// process pending commands
|
|
m_pConsole->StoreCommands(false);
|
|
|
|
#if defined(CONF_FAMILY_UNIX)
|
|
m_Fifo.Init(m_pConsole, g_Config.m_ClInputFifo, CFGFLAG_CLIENT);
|
|
#endif
|
|
|
|
// loads the existing ddnet-info.json file if it exists
|
|
LoadDDNetInfo();
|
|
// but still request the new one from server
|
|
if(g_Config.m_ClShowWelcome)
|
|
g_Config.m_ClShowWelcome = 0;
|
|
else
|
|
RequestDDNetInfo();
|
|
|
|
bool LastD = false;
|
|
bool LastQ = false;
|
|
bool LastE = false;
|
|
bool LastG = false;
|
|
|
|
int64 LastTime = time_get_microseconds();
|
|
int64 LastRenderTime = time_get();
|
|
|
|
while(1)
|
|
{
|
|
set_new_tick();
|
|
|
|
// handle pending connects
|
|
if(m_aCmdConnect[0])
|
|
{
|
|
str_copy(g_Config.m_UiServerAddress, m_aCmdConnect, sizeof(g_Config.m_UiServerAddress));
|
|
Connect(m_aCmdConnect);
|
|
m_aCmdConnect[0] = 0;
|
|
}
|
|
|
|
// progress on dummy connect if security token handshake skipped/passed
|
|
if(m_DummySendConnInfo && !m_NetClient[1].SecurityTokenUnknown())
|
|
{
|
|
m_DummySendConnInfo = false;
|
|
|
|
// send client info
|
|
CMsgPacker MsgInfo(NETMSG_INFO);
|
|
MsgInfo.AddString(GameClient()->NetVersion(), 128);
|
|
MsgInfo.AddString(m_Password, 128);
|
|
SendMsgExY(&MsgInfo, MSGFLAG_VITAL|MSGFLAG_FLUSH, true, 1);
|
|
|
|
// update netclient
|
|
m_NetClient[1].Update();
|
|
|
|
// send ready
|
|
CMsgPacker MsgReady(NETMSG_READY);
|
|
SendMsgExY(&MsgReady, MSGFLAG_VITAL|MSGFLAG_FLUSH, true, 1);
|
|
|
|
// startinfo
|
|
GameClient()->SendDummyInfo(true);
|
|
|
|
// send enter game an finish the connection
|
|
CMsgPacker MsgEnter(NETMSG_ENTERGAME);
|
|
SendMsgExY(&MsgEnter, MSGFLAG_VITAL|MSGFLAG_FLUSH, true, 1);
|
|
}
|
|
|
|
// update input
|
|
if(Input()->Update())
|
|
break; // SDL_QUIT
|
|
#if !defined(CONF_PLATFORM_MACOSX) && !defined(__ANDROID__)
|
|
Updater()->Update();
|
|
#endif
|
|
|
|
// update sound
|
|
Sound()->Update();
|
|
|
|
// panic quit button
|
|
if(CtrlShiftKey(KEY_Q, LastQ))
|
|
{
|
|
Quit();
|
|
break;
|
|
}
|
|
|
|
if(CtrlShiftKey(KEY_D, LastD))
|
|
g_Config.m_Debug ^= 1;
|
|
|
|
if(CtrlShiftKey(KEY_G, LastG))
|
|
g_Config.m_DbgGraphs ^= 1;
|
|
|
|
if(CtrlShiftKey(KEY_E, LastE))
|
|
{
|
|
g_Config.m_ClEditor = g_Config.m_ClEditor^1;
|
|
Input()->MouseModeRelative();
|
|
Input()->SetIMEState(true);
|
|
}
|
|
|
|
// render
|
|
{
|
|
if(g_Config.m_ClEditor)
|
|
{
|
|
if(!m_EditorActive)
|
|
{
|
|
Input()->MouseModeRelative();
|
|
GameClient()->OnActivateEditor();
|
|
m_EditorActive = true;
|
|
}
|
|
}
|
|
else if(m_EditorActive)
|
|
m_EditorActive = false;
|
|
|
|
Update();
|
|
int64 Now = time_get();
|
|
|
|
if((g_Config.m_GfxBackgroundRender || m_pGraphics->WindowOpen())
|
|
&& (!g_Config.m_GfxAsyncRenderOld || m_pGraphics->IsIdle())
|
|
&& (!g_Config.m_GfxRefreshRate || (time_freq() / (int64)g_Config.m_GfxRefreshRate) <= Now - LastRenderTime))
|
|
{
|
|
m_RenderFrames++;
|
|
|
|
// update frametime
|
|
m_RenderFrameTime = (Now - m_LastRenderTime) / (float)time_freq();
|
|
if(m_RenderFrameTime < m_RenderFrameTimeLow)
|
|
m_RenderFrameTimeLow = m_RenderFrameTime;
|
|
if(m_RenderFrameTime > m_RenderFrameTimeHigh)
|
|
m_RenderFrameTimeHigh = m_RenderFrameTime;
|
|
m_FpsGraph.Add(1.0f/m_RenderFrameTime, 1,1,1);
|
|
|
|
// keep the overflow time - it's used to make sure the gfx refreshrate is reached
|
|
int64 AdditionalTime = g_Config.m_GfxRefreshRate ? ((Now - LastRenderTime) - (time_freq() / (int64)g_Config.m_GfxRefreshRate)) : 0;
|
|
// if the value is over a second time loose, reset the additional time (drop the frames, that are lost already)
|
|
if(AdditionalTime > time_freq())
|
|
AdditionalTime = time_freq();
|
|
LastRenderTime = Now - AdditionalTime;
|
|
m_LastRenderTime = Now;
|
|
|
|
#ifdef CONF_DEBUG
|
|
if(g_Config.m_DbgStress)
|
|
{
|
|
if((m_RenderFrames%10) == 0)
|
|
{
|
|
if(!m_EditorActive)
|
|
Render();
|
|
else
|
|
{
|
|
m_pEditor->UpdateAndRender();
|
|
DebugRender();
|
|
}
|
|
m_pGraphics->Swap();
|
|
}
|
|
}
|
|
else
|
|
#endif
|
|
{
|
|
if(!m_EditorActive)
|
|
Render();
|
|
else
|
|
{
|
|
m_pEditor->UpdateAndRender();
|
|
DebugRender();
|
|
}
|
|
m_pGraphics->Swap();
|
|
}
|
|
|
|
Input()->NextFrame();
|
|
}
|
|
|
|
if(Input()->VideoRestartNeeded())
|
|
{
|
|
m_pGraphics->Init();
|
|
LoadData();
|
|
GameClient()->OnInit();
|
|
}
|
|
}
|
|
|
|
AutoScreenshot_Cleanup();
|
|
AutoCSV_Cleanup();
|
|
|
|
// check conditions
|
|
if(State() == IClient::STATE_QUITING)
|
|
break;
|
|
|
|
#if defined(CONF_FAMILY_UNIX)
|
|
m_Fifo.Update();
|
|
#endif
|
|
|
|
// beNice
|
|
int64 Now = time_get_microseconds();
|
|
int64 SleepTimeInMicroSeconds = 0;
|
|
bool Slept = false;
|
|
if(
|
|
#ifdef CONF_DEBUG
|
|
g_Config.m_DbgStress ||
|
|
#endif
|
|
(g_Config.m_ClRefreshRateInactive && !m_pGraphics->WindowActive()))
|
|
{
|
|
SleepTimeInMicroSeconds = ((int64)1000000 / (int64)g_Config.m_ClRefreshRateInactive) - (Now - LastTime);
|
|
if(SleepTimeInMicroSeconds / (int64)1000 > (int64)0)
|
|
thread_sleep(SleepTimeInMicroSeconds / (int64)1000);
|
|
Slept = true;
|
|
}
|
|
else if(g_Config.m_ClRefreshRate)
|
|
{
|
|
SleepTimeInMicroSeconds = ((int64)1000000 / (int64)g_Config.m_ClRefreshRate) - (Now - LastTime);
|
|
if(SleepTimeInMicroSeconds > (int64)0)
|
|
net_socket_read_wait(m_NetClient[0].m_Socket, SleepTimeInMicroSeconds);
|
|
Slept = true;
|
|
}
|
|
if(Slept)
|
|
{
|
|
// if the diff gets too small it shouldn't get even smaller (drop the updates, that could not be handled)
|
|
if(SleepTimeInMicroSeconds < (int64)-1000000)
|
|
SleepTimeInMicroSeconds = (int64)-1000000;
|
|
// don't go higher than the game ticks speed, because the network is waking up the client with the server's snapshots anyway
|
|
else if(SleepTimeInMicroSeconds > (int64)1000000 / m_GameTickSpeed)
|
|
SleepTimeInMicroSeconds = (int64)1000000 / m_GameTickSpeed;
|
|
// the time diff between the time that was used actually used and the time the thread should sleep/wait
|
|
// will be calculated in the sleep time of the next update tick by faking the time it should have slept/wait.
|
|
// so two cases (and the case it slept exactly the time it should):
|
|
// - the thread slept/waited too long, then it adjust the time to sleep/wait less in the next update tick
|
|
// - the thread slept/waited too less, then it adjust the time to sleep/wait more in the next update tick
|
|
LastTime = Now + SleepTimeInMicroSeconds;
|
|
}
|
|
else
|
|
LastTime = Now;
|
|
|
|
if(g_Config.m_DbgHitch)
|
|
{
|
|
thread_sleep(g_Config.m_DbgHitch);
|
|
g_Config.m_DbgHitch = 0;
|
|
}
|
|
|
|
// update local time
|
|
m_LocalTime = (time_get()-m_LocalStartTime)/(float)time_freq();
|
|
}
|
|
|
|
#if defined(CONF_FAMILY_UNIX)
|
|
m_Fifo.Shutdown();
|
|
#endif
|
|
|
|
GameClient()->OnShutdown();
|
|
Disconnect();
|
|
|
|
delete m_pEditor;
|
|
m_pGraphics->Shutdown();
|
|
|
|
// shutdown SDL
|
|
{
|
|
SDL_Quit();
|
|
}
|
|
}
|
|
|
|
bool CClient::CtrlShiftKey(int Key, bool &Last)
|
|
{
|
|
if(Input()->KeyIsPressed(KEY_LCTRL) && Input()->KeyIsPressed(KEY_LSHIFT) && !Last && Input()->KeyIsPressed(Key))
|
|
{
|
|
Last = true;
|
|
return true;
|
|
}
|
|
else if(Last && !Input()->KeyIsPressed(Key))
|
|
Last = false;
|
|
|
|
return false;
|
|
}
|
|
|
|
void CClient::Con_Connect(IConsole::IResult *pResult, void *pUserData)
|
|
{
|
|
CClient *pSelf = (CClient *)pUserData;
|
|
str_copy(pSelf->m_aCmdConnect, pResult->GetString(0), sizeof(pSelf->m_aCmdConnect));
|
|
}
|
|
|
|
void CClient::Con_Disconnect(IConsole::IResult *pResult, void *pUserData)
|
|
{
|
|
CClient *pSelf = (CClient *)pUserData;
|
|
pSelf->Disconnect();
|
|
}
|
|
|
|
void CClient::Con_DummyConnect(IConsole::IResult *pResult, void *pUserData)
|
|
{
|
|
CClient *pSelf = (CClient *)pUserData;
|
|
pSelf->DummyConnect();
|
|
}
|
|
|
|
void CClient::Con_DummyDisconnect(IConsole::IResult *pResult, void *pUserData)
|
|
{
|
|
CClient *pSelf = (CClient *)pUserData;
|
|
pSelf->DummyDisconnect(0);
|
|
}
|
|
|
|
void CClient::Con_Quit(IConsole::IResult *pResult, void *pUserData)
|
|
{
|
|
CClient *pSelf = (CClient *)pUserData;
|
|
pSelf->Quit();
|
|
}
|
|
|
|
void CClient::Con_Minimize(IConsole::IResult *pResult, void *pUserData)
|
|
{
|
|
CClient *pSelf = (CClient *)pUserData;
|
|
pSelf->Graphics()->Minimize();
|
|
}
|
|
|
|
void CClient::Con_Ping(IConsole::IResult *pResult, void *pUserData)
|
|
{
|
|
CClient *pSelf = (CClient *)pUserData;
|
|
|
|
CMsgPacker Msg(NETMSG_PING);
|
|
pSelf->SendMsgEx(&Msg, 0);
|
|
pSelf->m_PingStartTime = time_get();
|
|
}
|
|
|
|
void CClient::AutoScreenshot_Start()
|
|
{
|
|
if(g_Config.m_ClAutoScreenshot)
|
|
{
|
|
Graphics()->TakeScreenshot("auto/autoscreen");
|
|
m_AutoScreenshotRecycle = true;
|
|
}
|
|
}
|
|
|
|
void CClient::AutoStatScreenshot_Start()
|
|
{
|
|
if(g_Config.m_ClAutoStatboardScreenshot)
|
|
{
|
|
Graphics()->TakeScreenshot("auto/stats/autoscreen");
|
|
m_AutoStatScreenshotRecycle = true;
|
|
}
|
|
}
|
|
|
|
void CClient::AutoScreenshot_Cleanup()
|
|
{
|
|
if(m_AutoScreenshotRecycle)
|
|
{
|
|
if(g_Config.m_ClAutoScreenshotMax)
|
|
{
|
|
// clean up auto taken screens
|
|
CFileCollection AutoScreens;
|
|
AutoScreens.Init(Storage(), "screenshots/auto", "autoscreen", ".png", g_Config.m_ClAutoScreenshotMax);
|
|
}
|
|
m_AutoScreenshotRecycle = false;
|
|
}
|
|
}
|
|
|
|
void CClient::AutoStatScreenshot_Cleanup()
|
|
{
|
|
if(m_AutoStatScreenshotRecycle)
|
|
{
|
|
if(g_Config.m_ClAutoStatboardScreenshotMax)
|
|
{
|
|
// clean up auto taken screens
|
|
CFileCollection AutoScreens;
|
|
AutoScreens.Init(Storage(), "screenshots/auto/stats", "autoscreen", ".png", g_Config.m_ClAutoStatboardScreenshotMax);
|
|
}
|
|
m_AutoStatScreenshotRecycle = false;
|
|
}
|
|
}
|
|
|
|
void CClient::AutoCSV_Start()
|
|
{
|
|
if(g_Config.m_ClAutoCSV)
|
|
m_AutoCSVRecycle = true;
|
|
}
|
|
|
|
void CClient::AutoCSV_Cleanup()
|
|
{
|
|
if(m_AutoCSVRecycle)
|
|
{
|
|
if(g_Config.m_ClAutoCSVMax)
|
|
{
|
|
// clean up auto csvs
|
|
CFileCollection AutoRecord;
|
|
AutoRecord.Init(Storage(), "record/csv", "autorecord", ".csv", g_Config.m_ClAutoCSVMax);
|
|
}
|
|
m_AutoCSVRecycle = false;
|
|
}
|
|
}
|
|
|
|
void CClient::Con_Screenshot(IConsole::IResult *pResult, void *pUserData)
|
|
{
|
|
CClient *pSelf = (CClient *)pUserData;
|
|
pSelf->Graphics()->TakeScreenshot(0);
|
|
}
|
|
|
|
void CClient::Con_Rcon(IConsole::IResult *pResult, void *pUserData)
|
|
{
|
|
CClient *pSelf = (CClient *)pUserData;
|
|
pSelf->Rcon(pResult->GetString(0));
|
|
}
|
|
|
|
void CClient::Con_RconAuth(IConsole::IResult *pResult, void *pUserData)
|
|
{
|
|
CClient *pSelf = (CClient *)pUserData;
|
|
pSelf->RconAuth("", pResult->GetString(0));
|
|
}
|
|
|
|
void CClient::Con_RconLogin(IConsole::IResult *pResult, void *pUserData)
|
|
{
|
|
CClient *pSelf = (CClient *)pUserData;
|
|
pSelf->RconAuth(pResult->GetString(0), pResult->GetString(1));
|
|
}
|
|
|
|
void CClient::Con_AddFavorite(IConsole::IResult *pResult, void *pUserData)
|
|
{
|
|
CClient *pSelf = (CClient *)pUserData;
|
|
NETADDR Addr;
|
|
if(net_addr_from_str(&Addr, pResult->GetString(0)) == 0)
|
|
pSelf->m_ServerBrowser.AddFavorite(Addr);
|
|
}
|
|
|
|
void CClient::Con_RemoveFavorite(IConsole::IResult *pResult, void *pUserData)
|
|
{
|
|
CClient *pSelf = (CClient *)pUserData;
|
|
NETADDR Addr;
|
|
if(net_addr_from_str(&Addr, pResult->GetString(0)) == 0)
|
|
pSelf->m_ServerBrowser.RemoveFavorite(Addr);
|
|
}
|
|
|
|
void CClient::DemoSliceBegin()
|
|
{
|
|
const CDemoPlayer::CPlaybackInfo *pInfo = m_DemoPlayer.Info();
|
|
g_Config.m_ClDemoSliceBegin = pInfo->m_Info.m_CurrentTick;
|
|
}
|
|
|
|
void CClient::DemoSliceEnd()
|
|
{
|
|
const CDemoPlayer::CPlaybackInfo *pInfo = m_DemoPlayer.Info();
|
|
g_Config.m_ClDemoSliceEnd = pInfo->m_Info.m_CurrentTick;
|
|
}
|
|
|
|
void CClient::Con_DemoSliceBegin(IConsole::IResult *pResult, void *pUserData)
|
|
{
|
|
CClient *pSelf = (CClient *)pUserData;
|
|
pSelf->DemoSliceBegin();
|
|
}
|
|
|
|
void CClient::Con_DemoSliceEnd(IConsole::IResult *pResult, void *pUserData)
|
|
{
|
|
CClient *pSelf = (CClient *)pUserData;
|
|
pSelf->DemoSliceEnd();
|
|
}
|
|
|
|
void CClient::DemoSlice(const char *pDstPath, CLIENTFUNC_FILTER pfnFilter, void *pUser)
|
|
{
|
|
if(m_DemoPlayer.IsPlaying())
|
|
{
|
|
const char *pDemoFileName = m_DemoPlayer.GetDemoFileName();
|
|
m_DemoEditor.Slice(pDemoFileName, pDstPath, g_Config.m_ClDemoSliceBegin, g_Config.m_ClDemoSliceEnd, pfnFilter, pUser);
|
|
}
|
|
}
|
|
|
|
const char *CClient::DemoPlayer_Play(const char *pFilename, int StorageType)
|
|
{
|
|
int Crc;
|
|
const char *pError;
|
|
Disconnect();
|
|
m_NetClient[0].ResetErrorString();
|
|
|
|
// try to start playback
|
|
m_DemoPlayer.SetListener(this);
|
|
|
|
if(m_DemoPlayer.Load(Storage(), m_pConsole, pFilename, StorageType))
|
|
return "error loading demo";
|
|
|
|
// load map
|
|
Crc = (m_DemoPlayer.Info()->m_Header.m_aMapCrc[0]<<24)|
|
|
(m_DemoPlayer.Info()->m_Header.m_aMapCrc[1]<<16)|
|
|
(m_DemoPlayer.Info()->m_Header.m_aMapCrc[2]<<8)|
|
|
(m_DemoPlayer.Info()->m_Header.m_aMapCrc[3]);
|
|
pError = LoadMapSearch(m_DemoPlayer.Info()->m_Header.m_aMapName, Crc);
|
|
if(pError)
|
|
{
|
|
DisconnectWithReason(pError);
|
|
return pError;
|
|
}
|
|
|
|
GameClient()->OnConnected();
|
|
|
|
// setup buffers
|
|
mem_zero(m_aDemorecSnapshotData, sizeof(m_aDemorecSnapshotData));
|
|
|
|
m_aSnapshots[g_Config.m_ClDummy][SNAP_CURRENT] = &m_aDemorecSnapshotHolders[SNAP_CURRENT];
|
|
m_aSnapshots[g_Config.m_ClDummy][SNAP_PREV] = &m_aDemorecSnapshotHolders[SNAP_PREV];
|
|
|
|
m_aSnapshots[g_Config.m_ClDummy][SNAP_CURRENT]->m_pSnap = (CSnapshot *)m_aDemorecSnapshotData[SNAP_CURRENT][0];
|
|
m_aSnapshots[g_Config.m_ClDummy][SNAP_CURRENT]->m_pAltSnap = (CSnapshot *)m_aDemorecSnapshotData[SNAP_CURRENT][1];
|
|
m_aSnapshots[g_Config.m_ClDummy][SNAP_CURRENT]->m_SnapSize = 0;
|
|
m_aSnapshots[g_Config.m_ClDummy][SNAP_CURRENT]->m_Tick = -1;
|
|
|
|
m_aSnapshots[g_Config.m_ClDummy][SNAP_PREV]->m_pSnap = (CSnapshot *)m_aDemorecSnapshotData[SNAP_PREV][0];
|
|
m_aSnapshots[g_Config.m_ClDummy][SNAP_PREV]->m_pAltSnap = (CSnapshot *)m_aDemorecSnapshotData[SNAP_PREV][1];
|
|
m_aSnapshots[g_Config.m_ClDummy][SNAP_PREV]->m_SnapSize = 0;
|
|
m_aSnapshots[g_Config.m_ClDummy][SNAP_PREV]->m_Tick = -1;
|
|
|
|
// enter demo playback state
|
|
SetState(IClient::STATE_DEMOPLAYBACK);
|
|
|
|
m_DemoPlayer.Play();
|
|
GameClient()->OnEnterGame();
|
|
|
|
return 0;
|
|
}
|
|
|
|
void CClient::Con_Play(IConsole::IResult *pResult, void *pUserData)
|
|
{
|
|
CClient *pSelf = (CClient *)pUserData;
|
|
pSelf->DemoPlayer_Play(pResult->GetString(0), IStorage::TYPE_ALL);
|
|
}
|
|
|
|
void CClient::Con_DemoPlay(IConsole::IResult *pResult, void *pUserData)
|
|
{
|
|
CClient *pSelf = (CClient *)pUserData;
|
|
if(pSelf->m_DemoPlayer.IsPlaying())
|
|
{
|
|
if(pSelf->m_DemoPlayer.BaseInfo()->m_Paused)
|
|
{
|
|
pSelf->m_DemoPlayer.Unpause();
|
|
}
|
|
else
|
|
{
|
|
pSelf->m_DemoPlayer.Pause();
|
|
}
|
|
}
|
|
}
|
|
|
|
void CClient::Con_DemoSpeed(IConsole::IResult *pResult, void *pUserData)
|
|
{
|
|
CClient *pSelf = (CClient *)pUserData;
|
|
pSelf->m_DemoPlayer.SetSpeed(pResult->GetFloat(0));
|
|
}
|
|
|
|
void CClient::DemoRecorder_Start(const char *pFilename, bool WithTimestamp, int Recorder)
|
|
{
|
|
if(State() != IClient::STATE_ONLINE)
|
|
m_pConsole->Print(IConsole::OUTPUT_LEVEL_STANDARD, "demorec/record", "client is not online");
|
|
else
|
|
{
|
|
char aFilename[128];
|
|
if(WithTimestamp)
|
|
{
|
|
char aDate[20];
|
|
str_timestamp(aDate, sizeof(aDate));
|
|
str_format(aFilename, sizeof(aFilename), "demos/%s_%s.demo", pFilename, aDate);
|
|
}
|
|
else
|
|
str_format(aFilename, sizeof(aFilename), "demos/%s.demo", pFilename);
|
|
m_DemoRecorder[Recorder].Start(Storage(), m_pConsole, aFilename, GameClient()->NetVersion(), m_aCurrentMap, m_pMap->Crc(), "client", m_pMap->MapSize(), 0, m_pMap->File());
|
|
}
|
|
}
|
|
|
|
void CClient::DemoRecorder_HandleAutoStart()
|
|
{
|
|
if(g_Config.m_ClAutoDemoRecord)
|
|
{
|
|
DemoRecorder_Stop(RECORDER_AUTO);
|
|
char aBuf[512];
|
|
str_format(aBuf, sizeof(aBuf), "auto/%s", m_aCurrentMap);
|
|
DemoRecorder_Start(aBuf, true, RECORDER_AUTO);
|
|
if(g_Config.m_ClAutoDemoMax)
|
|
{
|
|
// clean up auto recorded demos
|
|
CFileCollection AutoDemos;
|
|
AutoDemos.Init(Storage(), "demos/auto", "" /* empty for wild card */, ".demo", g_Config.m_ClAutoDemoMax);
|
|
}
|
|
}
|
|
}
|
|
|
|
void CClient::DemoRecorder_Stop(int Recorder)
|
|
{
|
|
m_DemoRecorder[Recorder].Stop();
|
|
}
|
|
|
|
void CClient::DemoRecorder_AddDemoMarker(int Recorder)
|
|
{
|
|
m_DemoRecorder[Recorder].AddDemoMarker();
|
|
}
|
|
|
|
class IDemoRecorder *CClient::DemoRecorder(int Recorder)
|
|
{
|
|
return &m_DemoRecorder[Recorder];
|
|
}
|
|
|
|
void CClient::Con_Record(IConsole::IResult *pResult, void *pUserData)
|
|
{
|
|
CClient *pSelf = (CClient *)pUserData;
|
|
if(pResult->NumArguments())
|
|
pSelf->DemoRecorder_Start(pResult->GetString(0), false, RECORDER_MANUAL);
|
|
else
|
|
pSelf->DemoRecorder_Start(pSelf->m_aCurrentMap, true, RECORDER_MANUAL);
|
|
}
|
|
|
|
void CClient::Con_StopRecord(IConsole::IResult *pResult, void *pUserData)
|
|
{
|
|
CClient *pSelf = (CClient *)pUserData;
|
|
pSelf->DemoRecorder_Stop(RECORDER_MANUAL);
|
|
}
|
|
|
|
void CClient::Con_AddDemoMarker(IConsole::IResult *pResult, void *pUserData)
|
|
{
|
|
CClient *pSelf = (CClient *)pUserData;
|
|
pSelf->DemoRecorder_AddDemoMarker(RECORDER_MANUAL);
|
|
pSelf->DemoRecorder_AddDemoMarker(RECORDER_RACE);
|
|
pSelf->DemoRecorder_AddDemoMarker(RECORDER_AUTO);
|
|
}
|
|
|
|
void CClient::ServerBrowserUpdate()
|
|
{
|
|
m_ResortServerBrowser = true;
|
|
}
|
|
|
|
void CClient::ConchainServerBrowserUpdate(IConsole::IResult *pResult, void *pUserData, IConsole::FCommandCallback pfnCallback, void *pCallbackUserData)
|
|
{
|
|
pfnCallback(pResult, pCallbackUserData);
|
|
if(pResult->NumArguments())
|
|
((CClient *)pUserData)->ServerBrowserUpdate();
|
|
}
|
|
|
|
void CClient::SwitchWindowScreen(int Index)
|
|
{
|
|
// Todo SDL: remove this when fixed (changing screen when in fullscreen is bugged)
|
|
if(g_Config.m_GfxFullscreen)
|
|
{
|
|
ToggleFullscreen();
|
|
if(Graphics()->SetWindowScreen(Index))
|
|
g_Config.m_GfxScreen = Index;
|
|
ToggleFullscreen();
|
|
}
|
|
else
|
|
{
|
|
if(Graphics()->SetWindowScreen(Index))
|
|
g_Config.m_GfxScreen = Index;
|
|
}
|
|
}
|
|
|
|
void CClient::ConchainWindowScreen(IConsole::IResult *pResult, void *pUserData, IConsole::FCommandCallback pfnCallback, void *pCallbackUserData)
|
|
{
|
|
CClient *pSelf = (CClient *)pUserData;
|
|
if(pSelf->Graphics() && pResult->NumArguments())
|
|
{
|
|
if(g_Config.m_GfxScreen != pResult->GetInteger(0))
|
|
pSelf->SwitchWindowScreen(pResult->GetInteger(0));
|
|
}
|
|
else
|
|
pfnCallback(pResult, pCallbackUserData);
|
|
}
|
|
|
|
void CClient::ToggleFullscreen()
|
|
{
|
|
if(Graphics()->Fullscreen(g_Config.m_GfxFullscreen^1))
|
|
g_Config.m_GfxFullscreen ^= 1;
|
|
}
|
|
|
|
void CClient::ConchainFullscreen(IConsole::IResult *pResult, void *pUserData, IConsole::FCommandCallback pfnCallback, void *pCallbackUserData)
|
|
{
|
|
CClient *pSelf = (CClient *)pUserData;
|
|
if(pSelf->Graphics() && pResult->NumArguments())
|
|
{
|
|
if(g_Config.m_GfxFullscreen != pResult->GetInteger(0))
|
|
pSelf->ToggleFullscreen();
|
|
}
|
|
else
|
|
pfnCallback(pResult, pCallbackUserData);
|
|
}
|
|
|
|
void CClient::ToggleWindowBordered()
|
|
{
|
|
g_Config.m_GfxBorderless ^= 1;
|
|
Graphics()->SetWindowBordered(!g_Config.m_GfxBorderless);
|
|
}
|
|
|
|
void CClient::ConchainWindowBordered(IConsole::IResult *pResult, void *pUserData, IConsole::FCommandCallback pfnCallback, void *pCallbackUserData)
|
|
{
|
|
CClient *pSelf = (CClient *)pUserData;
|
|
if(pSelf->Graphics() && pResult->NumArguments())
|
|
{
|
|
if(!g_Config.m_GfxFullscreen && (g_Config.m_GfxBorderless != pResult->GetInteger(0)))
|
|
pSelf->ToggleWindowBordered();
|
|
}
|
|
else
|
|
pfnCallback(pResult, pCallbackUserData);
|
|
}
|
|
|
|
void CClient::ToggleWindowVSync()
|
|
{
|
|
if(Graphics()->SetVSync(g_Config.m_GfxVsync^1))
|
|
g_Config.m_GfxVsync ^= 1;
|
|
}
|
|
|
|
void CClient::ConchainWindowVSync(IConsole::IResult *pResult, void *pUserData, IConsole::FCommandCallback pfnCallback, void *pCallbackUserData)
|
|
{
|
|
CClient *pSelf = (CClient *)pUserData;
|
|
if(pSelf->Graphics() && pResult->NumArguments())
|
|
{
|
|
if(g_Config.m_GfxVsync != pResult->GetInteger(0))
|
|
pSelf->ToggleWindowVSync();
|
|
}
|
|
else
|
|
pfnCallback(pResult, pCallbackUserData);
|
|
}
|
|
|
|
void CClient::ConchainTimeoutSeed(IConsole::IResult *pResult, void *pUserData, IConsole::FCommandCallback pfnCallback, void *pCallbackUserData)
|
|
{
|
|
CClient *pSelf = (CClient *)pUserData;
|
|
pfnCallback(pResult, pCallbackUserData);
|
|
if(pResult->NumArguments())
|
|
pSelf->m_GenerateTimeoutSeed = false;
|
|
}
|
|
|
|
void CClient::RegisterCommands()
|
|
{
|
|
m_pConsole = Kernel()->RequestInterface<IConsole>();
|
|
// register server dummy commands for tab completion
|
|
m_pConsole->Register("kick", "i[id] ?r[reason]", CFGFLAG_SERVER, 0, 0, "Kick player with specified id for any reason");
|
|
m_pConsole->Register("ban", "s[ip|id] ?i[minutes] r[reason]", CFGFLAG_SERVER, 0, 0, "Ban player with ip/id for x minutes for any reason");
|
|
m_pConsole->Register("unban", "s[ip]", CFGFLAG_SERVER, 0, 0, "Unban ip");
|
|
m_pConsole->Register("bans", "", CFGFLAG_SERVER, 0, 0, "Show banlist");
|
|
m_pConsole->Register("status", "", CFGFLAG_SERVER, 0, 0, "List players");
|
|
m_pConsole->Register("shutdown", "", CFGFLAG_SERVER, 0, 0, "Shut down");
|
|
m_pConsole->Register("record", "s[file]", CFGFLAG_SERVER, 0, 0, "Record to a file");
|
|
m_pConsole->Register("stoprecord", "", CFGFLAG_SERVER, 0, 0, "Stop recording");
|
|
m_pConsole->Register("reload", "", CFGFLAG_SERVER, 0, 0, "Reload the map");
|
|
|
|
m_pConsole->Register("dummy_connect", "", CFGFLAG_CLIENT, Con_DummyConnect, this, "connect dummy");
|
|
m_pConsole->Register("dummy_disconnect", "", CFGFLAG_CLIENT, Con_DummyDisconnect, this, "disconnect dummy");
|
|
|
|
m_pConsole->Register("quit", "", CFGFLAG_CLIENT|CFGFLAG_STORE, Con_Quit, this, "Quit Teeworlds");
|
|
m_pConsole->Register("exit", "", CFGFLAG_CLIENT|CFGFLAG_STORE, Con_Quit, this, "Quit Teeworlds");
|
|
m_pConsole->Register("minimize", "", CFGFLAG_CLIENT|CFGFLAG_STORE, Con_Minimize, this, "Minimize Teeworlds");
|
|
m_pConsole->Register("connect", "s[host|ip]", CFGFLAG_CLIENT, Con_Connect, this, "Connect to the specified host/ip");
|
|
m_pConsole->Register("disconnect", "", CFGFLAG_CLIENT, Con_Disconnect, this, "Disconnect from the server");
|
|
m_pConsole->Register("ping", "", CFGFLAG_CLIENT, Con_Ping, this, "Ping the current server");
|
|
m_pConsole->Register("screenshot", "", CFGFLAG_CLIENT, Con_Screenshot, this, "Take a screenshot");
|
|
m_pConsole->Register("rcon", "r[rcon-command]", CFGFLAG_CLIENT, Con_Rcon, this, "Send specified command to rcon");
|
|
m_pConsole->Register("rcon_auth", "s[password]", CFGFLAG_CLIENT, Con_RconAuth, this, "Authenticate to rcon");
|
|
m_pConsole->Register("rcon_login", "s[username] r[password]", CFGFLAG_CLIENT, Con_RconLogin, this, "Authenticate to rcon with a username");
|
|
m_pConsole->Register("play", "r[file]", CFGFLAG_CLIENT|CFGFLAG_STORE, Con_Play, this, "Play the file specified");
|
|
m_pConsole->Register("record", "?s[file]", CFGFLAG_CLIENT, Con_Record, this, "Record to the file");
|
|
m_pConsole->Register("stoprecord", "", CFGFLAG_CLIENT, Con_StopRecord, this, "Stop recording");
|
|
m_pConsole->Register("add_demomarker", "", CFGFLAG_CLIENT, Con_AddDemoMarker, this, "Add demo timeline marker");
|
|
m_pConsole->Register("add_favorite", "s[host|ip]", CFGFLAG_CLIENT, Con_AddFavorite, this, "Add a server as a favorite");
|
|
m_pConsole->Register("remove_favorite", "s[host|ip]", CFGFLAG_CLIENT, Con_RemoveFavorite, this, "Remove a server from favorites");
|
|
m_pConsole->Register("demo_slice_start", "", CFGFLAG_CLIENT, Con_DemoSliceBegin, this, "");
|
|
m_pConsole->Register("demo_slice_end", "", CFGFLAG_CLIENT, Con_DemoSliceEnd, this, "");
|
|
m_pConsole->Register("demo_play", "", CFGFLAG_CLIENT, Con_DemoPlay, this, "Play demo");
|
|
m_pConsole->Register("demo_speed", "i[speed]", CFGFLAG_CLIENT, Con_DemoSpeed, this, "Set demo speed");
|
|
|
|
// used for server browser update
|
|
m_pConsole->Chain("cl_timeout_seed", ConchainTimeoutSeed, this);
|
|
|
|
m_pConsole->Chain("br_filter_string", ConchainServerBrowserUpdate, this);
|
|
m_pConsole->Chain("br_filter_gametype", ConchainServerBrowserUpdate, this);
|
|
m_pConsole->Chain("br_filter_serveraddress", ConchainServerBrowserUpdate, this);
|
|
|
|
m_pConsole->Chain("gfx_screen", ConchainWindowScreen, this);
|
|
m_pConsole->Chain("gfx_fullscreen", ConchainFullscreen, this);
|
|
m_pConsole->Chain("gfx_borderless", ConchainWindowBordered, this);
|
|
m_pConsole->Chain("gfx_vsync", ConchainWindowVSync, this);
|
|
|
|
// DDRace
|
|
|
|
|
|
#define CONSOLE_COMMAND(name, params, flags, callback, userdata, help) m_pConsole->Register(name, params, flags, 0, 0, help);
|
|
#include <game/ddracecommands.h>
|
|
}
|
|
|
|
static CClient *CreateClient()
|
|
{
|
|
CClient *pClient = static_cast<CClient *>(mem_alloc(sizeof(CClient), 1));
|
|
mem_zero(pClient, sizeof(CClient));
|
|
return new(pClient) CClient;
|
|
}
|
|
|
|
/*
|
|
Server Time
|
|
Client Mirror Time
|
|
Client Predicted Time
|
|
|
|
Snapshot Latency
|
|
Downstream latency
|
|
|
|
Prediction Latency
|
|
Upstream latency
|
|
*/
|
|
|
|
#if defined(CONF_PLATFORM_MACOSX) || defined(__ANDROID__)
|
|
extern "C" int SDL_main(int argc, char **argv_) // ignore_convention
|
|
{
|
|
const char **argv = const_cast<const char **>(argv_);
|
|
#else
|
|
int main(int argc, const char **argv) // ignore_convention
|
|
{
|
|
#endif
|
|
bool Silent = false;
|
|
bool RandInitFailed = false;
|
|
|
|
for(int i = 1; i < argc; i++) // ignore_convention
|
|
{
|
|
if(str_comp("-s", argv[i]) == 0 || str_comp("--silent", argv[i]) == 0) // ignore_convention
|
|
{
|
|
Silent = true;
|
|
#if defined(CONF_FAMILY_WINDOWS)
|
|
FreeConsole();
|
|
#endif
|
|
break;
|
|
}
|
|
}
|
|
|
|
if(secure_random_init() != 0)
|
|
{
|
|
RandInitFailed = true;
|
|
}
|
|
|
|
CClient *pClient = CreateClient();
|
|
IKernel *pKernel = IKernel::Create();
|
|
pKernel->RegisterInterface(pClient, false);
|
|
pClient->RegisterInterfaces();
|
|
|
|
// create the components
|
|
IEngine *pEngine = CreateEngine("DDNet", Silent);
|
|
IConsole *pConsole = CreateConsole(CFGFLAG_CLIENT);
|
|
IStorage *pStorage = CreateStorage("Teeworlds", IStorage::STORAGETYPE_CLIENT, argc, argv); // ignore_convention
|
|
IConfig *pConfig = CreateConfig();
|
|
IEngineSound *pEngineSound = CreateEngineSound();
|
|
IEngineInput *pEngineInput = CreateEngineInput();
|
|
IEngineTextRender *pEngineTextRender = CreateEngineTextRender();
|
|
IEngineMap *pEngineMap = CreateEngineMap();
|
|
IEngineMasterServer *pEngineMasterServer = CreateEngineMasterServer();
|
|
|
|
if(RandInitFailed)
|
|
{
|
|
dbg_msg("secure", "could not initialize secure RNG");
|
|
return -1;
|
|
}
|
|
|
|
{
|
|
bool RegisterFail = false;
|
|
|
|
RegisterFail = RegisterFail || !pKernel->RegisterInterface(pEngine);
|
|
RegisterFail = RegisterFail || !pKernel->RegisterInterface(pConsole);
|
|
RegisterFail = RegisterFail || !pKernel->RegisterInterface(pConfig);
|
|
|
|
RegisterFail = RegisterFail || !pKernel->RegisterInterface(static_cast<IEngineSound*>(pEngineSound)); // register as both
|
|
RegisterFail = RegisterFail || !pKernel->RegisterInterface(static_cast<ISound*>(pEngineSound), false);
|
|
|
|
RegisterFail = RegisterFail || !pKernel->RegisterInterface(static_cast<IEngineInput*>(pEngineInput)); // register as both
|
|
RegisterFail = RegisterFail || !pKernel->RegisterInterface(static_cast<IInput*>(pEngineInput), false);
|
|
|
|
RegisterFail = RegisterFail || !pKernel->RegisterInterface(static_cast<IEngineTextRender*>(pEngineTextRender)); // register as both
|
|
RegisterFail = RegisterFail || !pKernel->RegisterInterface(static_cast<ITextRender*>(pEngineTextRender), false);
|
|
|
|
RegisterFail = RegisterFail || !pKernel->RegisterInterface(static_cast<IEngineMap*>(pEngineMap)); // register as both
|
|
RegisterFail = RegisterFail || !pKernel->RegisterInterface(static_cast<IMap*>(pEngineMap), false);
|
|
|
|
RegisterFail = RegisterFail || !pKernel->RegisterInterface(static_cast<IEngineMasterServer*>(pEngineMasterServer)); // register as both
|
|
RegisterFail = RegisterFail || !pKernel->RegisterInterface(static_cast<IMasterServer*>(pEngineMasterServer), false);
|
|
|
|
RegisterFail = RegisterFail || !pKernel->RegisterInterface(CreateEditor(), false);
|
|
RegisterFail = RegisterFail || !pKernel->RegisterInterface(CreateGameClient(), false);
|
|
RegisterFail = RegisterFail || !pKernel->RegisterInterface(pStorage);
|
|
|
|
if(RegisterFail)
|
|
{
|
|
delete pKernel;
|
|
pClient->~CClient();
|
|
mem_free(pClient);
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
pEngine->Init();
|
|
pConfig->Init();
|
|
pEngineMasterServer->Init();
|
|
pEngineMasterServer->Load();
|
|
|
|
// register all console commands
|
|
pClient->RegisterCommands();
|
|
|
|
pKernel->RequestInterface<IGameClient>()->OnConsoleInit();
|
|
|
|
// init client's interfaces
|
|
pClient->InitInterfaces();
|
|
|
|
// execute config file
|
|
IOHANDLE File = pStorage->OpenFile(CONFIG_FILE, IOFLAG_READ, IStorage::TYPE_ALL);
|
|
if(File)
|
|
{
|
|
io_close(File);
|
|
pConsole->ExecuteFile(CONFIG_FILE);
|
|
}
|
|
else // fallback
|
|
{
|
|
pConsole->ExecuteFile("settings.cfg");
|
|
}
|
|
|
|
// execute autoexec file
|
|
File = pStorage->OpenFile(AUTOEXEC_CLIENT_FILE, IOFLAG_READ, IStorage::TYPE_ALL);
|
|
if(File)
|
|
{
|
|
io_close(File);
|
|
pConsole->ExecuteFile(AUTOEXEC_CLIENT_FILE);
|
|
}
|
|
else // fallback
|
|
{
|
|
pConsole->ExecuteFile(AUTOEXEC_FILE);
|
|
}
|
|
|
|
if(g_Config.m_ClConfigVersion < 1)
|
|
{
|
|
if(g_Config.m_ClAntiPing == 0)
|
|
{
|
|
g_Config.m_ClAntiPingPlayers = 1;
|
|
g_Config.m_ClAntiPingGrenade = 1;
|
|
g_Config.m_ClAntiPingWeapons = 1;
|
|
}
|
|
}
|
|
g_Config.m_ClConfigVersion = 1;
|
|
|
|
// parse the command line arguments
|
|
if(argc > 1) // ignore_convention
|
|
pConsole->ParseArguments(argc-1, &argv[1]); // ignore_convention
|
|
|
|
pClient->Engine()->InitLogfile();
|
|
|
|
#if defined(CONF_FAMILY_WINDOWS)
|
|
if(!g_Config.m_ClShowConsole)
|
|
FreeConsole();
|
|
#endif
|
|
|
|
// For XOpenIM in SDL2: https://bugzilla.libsdl.org/show_bug.cgi?id=3102
|
|
setlocale(LC_ALL, "");
|
|
|
|
// run the client
|
|
dbg_msg("client", "starting...");
|
|
pClient->Run();
|
|
|
|
// write down the config and quit
|
|
pConfig->Save();
|
|
|
|
delete pKernel;
|
|
pClient->~CClient();
|
|
mem_free(pClient);
|
|
|
|
return 0;
|
|
}
|
|
|
|
// DDRace
|
|
|
|
const char *CClient::GetCurrentMap()
|
|
{
|
|
return m_aCurrentMap;
|
|
}
|
|
|
|
const char *CClient::GetCurrentMapPath()
|
|
{
|
|
return m_aCurrentMapPath;
|
|
}
|
|
|
|
unsigned CClient::GetMapCrc()
|
|
{
|
|
return m_pMap->Crc();
|
|
}
|
|
|
|
void CClient::RaceRecord_Start(const char *pFilename)
|
|
{
|
|
if(State() != IClient::STATE_ONLINE)
|
|
m_pConsole->Print(IConsole::OUTPUT_LEVEL_STANDARD, "demorec/record", "client is not online");
|
|
else
|
|
m_DemoRecorder[RECORDER_RACE].Start(Storage(), m_pConsole, pFilename, GameClient()->NetVersion(), m_aCurrentMap, m_pMap->Crc(), "client", m_pMap->MapSize(), 0, m_pMap->File());
|
|
}
|
|
|
|
void CClient::RaceRecord_Stop()
|
|
{
|
|
if(m_DemoRecorder[RECORDER_RACE].IsRecording())
|
|
m_DemoRecorder[RECORDER_RACE].Stop();
|
|
}
|
|
|
|
bool CClient::RaceRecord_IsRecording()
|
|
{
|
|
return m_DemoRecorder[RECORDER_RACE].IsRecording();
|
|
}
|
|
|
|
|
|
void CClient::RequestDDNetInfo()
|
|
{
|
|
char aUrl[256];
|
|
static bool s_IsWinXP = os_is_winxp_or_lower();
|
|
if(s_IsWinXP)
|
|
str_copy(aUrl, "http://info.ddnet.tw/info", sizeof(aUrl));
|
|
else
|
|
str_copy(aUrl, "https://info.ddnet.tw/info", sizeof(aUrl));
|
|
|
|
if(g_Config.m_BrIndicateFinished)
|
|
{
|
|
char aEscaped[128];
|
|
EscapeUrl(aEscaped, sizeof(aEscaped), g_Config.m_PlayerName);
|
|
str_append(aUrl, "?name=", sizeof(aUrl));
|
|
str_append(aUrl, aEscaped, sizeof(aUrl));
|
|
}
|
|
|
|
m_pDDNetInfoTask = std::make_shared<CFetchTask>(Storage(), aUrl, "ddnet-info.json.tmp", IStorage::TYPE_SAVE, true, true);
|
|
Engine()->AddJob(m_pDDNetInfoTask);
|
|
}
|
|
|
|
int CClient::GetPredictionTime()
|
|
{
|
|
int64 Now = time_get();
|
|
return (int)((m_PredictedTime.Get(Now)-m_GameTime[g_Config.m_ClDummy].Get(Now))*1000/(float)time_freq());
|
|
}
|