mirror of
https://github.com/ddnet/ddnet.git
synced 2024-11-20 06:58:20 +00:00
c43fa1f34a
It's the exact same mechanism than mailto:foo@bar.com links, adapted to teeworlds. Such links looks like: teeworlds:1.2.3.4:8300 teeworlds:localhost Browsers may require extra configuration to be able to open such links[1]. Once everything is set up, clicking on a link in any webpage will prompt the user to pick an application to open the link. Choosing (a compatible) teeworlds client will open it and automatically connect it to the given address. Behind the scene, the browser will run the client with only one argument: the full address. Hence it is the same than running the teeworlds client with the following command line: teeworlds "teeworlds:1.2.3.4:8300" The rational for this change is to allow websites like teerank.com, teeworlds-stats.info, and others to provide a link to directly connect to a server. Also users or webpages could also link a server. [1]: http://kb.mozillazine.org/Register_protocol
2483 lines
67 KiB
C++
2483 lines
67 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. */
|
|
#include <new>
|
|
|
|
#include <stdlib.h> // qsort
|
|
#include <stdarg.h>
|
|
|
|
#include <base/math.h>
|
|
#include <base/system.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/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/mapchecker.h>
|
|
#include <engine/shared/network.h>
|
|
#include <engine/shared/packer.h>
|
|
#include <engine/shared/protocol.h>
|
|
#include <engine/shared/ringbuffer.h>
|
|
#include <engine/shared/snapshot.h>
|
|
|
|
#include <game/version.h>
|
|
|
|
#include <mastersrv/mastersrv.h>
|
|
#include <versionsrv/versionsrv.h>
|
|
|
|
#include "friends.h"
|
|
#include "serverbrowser.h"
|
|
#include "client.h"
|
|
|
|
#if defined(CONF_FAMILY_WINDOWS)
|
|
#define WIN32_LEAN_AND_MEAN
|
|
#include <windows.h>
|
|
#endif
|
|
|
|
#include "SDL.h"
|
|
#ifdef main
|
|
#undef main
|
|
#endif
|
|
|
|
void CGraph::Init(float Min, float Max)
|
|
{
|
|
m_MinRange = m_Min = Min;
|
|
m_MaxRange = m_Max = Max;
|
|
m_Index = 0;
|
|
}
|
|
|
|
void CGraph::ScaleMax()
|
|
{
|
|
int i = 0;
|
|
m_Max = m_MaxRange;
|
|
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_MinRange;
|
|
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, IGraphics::CTextureHandle FontTexture, float x, float y, float w, float h, const char *pDescription)
|
|
{
|
|
//m_pGraphics->BlendNormal();
|
|
|
|
|
|
pGraphics->TextureClear();
|
|
|
|
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(FontTexture);
|
|
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(&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_WindowMustRefocus = 0;
|
|
m_SnapCrcErrors = 0;
|
|
m_AutoScreenshotRecycle = false;
|
|
m_EditorActive = false;
|
|
|
|
m_AckGameTick = -1;
|
|
m_CurrentRecvTick = 0;
|
|
m_RconAuthed = 0;
|
|
|
|
// version-checking
|
|
m_aVersionStr[0] = '0';
|
|
m_aVersionStr[1] = 0;
|
|
|
|
// pinging
|
|
m_PingStartTime = 0;
|
|
|
|
//
|
|
m_aCurrentMap[0] = 0;
|
|
m_CurrentMapCrc = 0;
|
|
|
|
//
|
|
m_aCmdConnect[0] = 0;
|
|
|
|
// map download
|
|
m_aMapdownloadFilename[0] = 0;
|
|
m_aMapdownloadName[0] = 0;
|
|
m_MapdownloadFile = 0;
|
|
m_MapdownloadChunk = 0;
|
|
m_MapdownloadCrc = 0;
|
|
m_MapdownloadAmount = -1;
|
|
m_MapdownloadTotalsize = -1;
|
|
|
|
m_CurrentInput = 0;
|
|
|
|
m_State = IClient::STATE_OFFLINE;
|
|
m_aServerAddressStr[0] = 0;
|
|
|
|
mem_zero(m_aSnapshots, sizeof(m_aSnapshots));
|
|
m_SnapshotStorage.Init();
|
|
m_RecivedSnapshots = 0;
|
|
|
|
m_VersionInfo.m_State = CVersionInfo::STATE_INIT;
|
|
}
|
|
|
|
// ----- send functions -----
|
|
int CClient::SendMsg(CMsgPacker *pMsg, int Flags)
|
|
{
|
|
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();
|
|
|
|
if(Flags&MSGFLAG_VITAL)
|
|
Packet.m_Flags |= NETSENDFLAG_VITAL;
|
|
if(Flags&MSGFLAG_FLUSH)
|
|
Packet.m_Flags |= NETSENDFLAG_FLUSH;
|
|
|
|
if(Flags&MSGFLAG_RECORD)
|
|
{
|
|
if(m_DemoRecorder.IsRecording())
|
|
m_DemoRecorder.RecordMessage(Packet.m_pData, Packet.m_DataSize);
|
|
}
|
|
|
|
if(!(Flags&MSGFLAG_NOSEND))
|
|
m_NetClient.Send(&Packet);
|
|
return 0;
|
|
}
|
|
|
|
void CClient::SendInfo()
|
|
{
|
|
CMsgPacker Msg(NETMSG_INFO, true);
|
|
Msg.AddString(GameClient()->NetVersion(), 128);
|
|
Msg.AddString(g_Config.m_Password, 128);
|
|
SendMsg(&Msg, MSGFLAG_VITAL|MSGFLAG_FLUSH);
|
|
}
|
|
|
|
|
|
void CClient::SendEnterGame()
|
|
{
|
|
CMsgPacker Msg(NETMSG_ENTERGAME, true);
|
|
SendMsg(&Msg, MSGFLAG_VITAL|MSGFLAG_FLUSH);
|
|
}
|
|
|
|
void CClient::SendReady()
|
|
{
|
|
CMsgPacker Msg(NETMSG_READY, true);
|
|
SendMsg(&Msg, MSGFLAG_VITAL|MSGFLAG_FLUSH);
|
|
}
|
|
|
|
void CClient::RconAuth(const char *pName, const char *pPassword)
|
|
{
|
|
if(RconAuthed())
|
|
return;
|
|
|
|
CMsgPacker Msg(NETMSG_RCON_AUTH, true);
|
|
Msg.AddString(pPassword, 32);
|
|
SendMsg(&Msg, MSGFLAG_VITAL);
|
|
}
|
|
|
|
void CClient::Rcon(const char *pCmd)
|
|
{
|
|
CMsgPacker Msg(NETMSG_RCON_CMD, true);
|
|
Msg.AddString(pCmd, 256);
|
|
SendMsg(&Msg, MSGFLAG_VITAL);
|
|
}
|
|
|
|
bool CClient::ConnectionProblems() const
|
|
{
|
|
return m_NetClient.GotProblems() != 0;
|
|
}
|
|
|
|
void CClient::DirectInput(int *pInput, int Size)
|
|
{
|
|
CMsgPacker Msg(NETMSG_INPUT, true);
|
|
Msg.AddInt(m_AckGameTick);
|
|
Msg.AddInt(m_PredTick);
|
|
Msg.AddInt(Size);
|
|
|
|
for(int i = 0; i < Size/4; i++)
|
|
Msg.AddInt(pInput[i]);
|
|
|
|
SendMsg(&Msg, 0);
|
|
}
|
|
|
|
|
|
void CClient::SendInput()
|
|
{
|
|
int64 Now = time_get();
|
|
|
|
if(m_PredTick <= 0)
|
|
return;
|
|
|
|
// fetch input
|
|
int Size = GameClient()->OnSnapInput(m_aInputs[m_CurrentInput].m_aData);
|
|
|
|
if(!Size)
|
|
return;
|
|
|
|
// pack input
|
|
CMsgPacker Msg(NETMSG_INPUT, true);
|
|
Msg.AddInt(m_AckGameTick);
|
|
Msg.AddInt(m_PredTick);
|
|
Msg.AddInt(Size);
|
|
|
|
m_aInputs[m_CurrentInput].m_Tick = m_PredTick;
|
|
m_aInputs[m_CurrentInput].m_PredictedTime = m_PredictedTime.Get(Now);
|
|
m_aInputs[m_CurrentInput].m_Time = Now;
|
|
|
|
// pack it
|
|
for(int i = 0; i < Size/4; i++)
|
|
Msg.AddInt(m_aInputs[m_CurrentInput].m_aData[i]);
|
|
|
|
m_CurrentInput++;
|
|
m_CurrentInput%=200;
|
|
|
|
SendMsg(&Msg, MSGFLAG_FLUSH);
|
|
}
|
|
|
|
const char *CClient::LatestVersion() const
|
|
{
|
|
return m_aVersionStr;
|
|
}
|
|
|
|
// TODO: OPT: do this alot smarter!
|
|
const int *CClient::GetInput(int Tick) const
|
|
{
|
|
int Best = -1;
|
|
for(int i = 0; i < 200; i++)
|
|
{
|
|
if(m_aInputs[i].m_Tick <= Tick && (Best == -1 || m_aInputs[Best].m_Tick < m_aInputs[i].m_Tick))
|
|
Best = i;
|
|
}
|
|
|
|
if(Best != -1)
|
|
return (const int *)m_aInputs[Best].m_aData;
|
|
return 0;
|
|
}
|
|
|
|
// ------ 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);
|
|
}
|
|
|
|
// 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[i].m_Tick = -1;
|
|
m_CurrentInput = 0;
|
|
|
|
// reset snapshots
|
|
m_aSnapshots[SNAP_CURRENT] = 0;
|
|
m_aSnapshots[SNAP_PREV] = 0;
|
|
m_SnapshotStorage.PurgeAll();
|
|
m_RecivedSnapshots = 0;
|
|
m_SnapshotParts = 0;
|
|
m_PredTick = 0;
|
|
m_CurrentRecvTick = 0;
|
|
m_CurGameTick = 0;
|
|
m_PrevGameTick = 0;
|
|
m_CurMenuTick = 0;
|
|
}
|
|
|
|
void CClient::EnterGame()
|
|
{
|
|
if(State() == IClient::STATE_DEMOPLAYBACK)
|
|
return;
|
|
|
|
// now we will wait for two snapshots
|
|
// to finish the connection
|
|
SendEnterGame();
|
|
OnEnterGame();
|
|
}
|
|
|
|
void CClient::Connect(const char *pAddress)
|
|
{
|
|
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);
|
|
|
|
mem_zero(&m_CurrentServerInfo, sizeof(m_CurrentServerInfo));
|
|
|
|
if(net_addr_from_str(&m_ServerAddress, m_aServerAddressStr) != 0 && net_host_lookup(m_aServerAddressStr, &m_ServerAddress, m_NetClient.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.NetType());
|
|
}
|
|
|
|
m_RconAuthed = 0;
|
|
if(m_ServerAddress.port == 0)
|
|
m_ServerAddress.port = Port;
|
|
m_NetClient.Connect(&m_ServerAddress);
|
|
SetState(IClient::STATE_CONNECTING);
|
|
|
|
if(m_DemoRecorder.IsRecording())
|
|
DemoRecorder_Stop();
|
|
|
|
m_InputtimeMarginGraph.Init(-150.0f, 150.0f);
|
|
m_GametimeMarginGraph.Init(-150.0f, 150.0f);
|
|
}
|
|
|
|
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();
|
|
DemoRecorder_Stop();
|
|
|
|
//
|
|
m_RconAuthed = 0;
|
|
m_UseTempRconCommands = 0;
|
|
m_pConsole->DeregisterTempAll();
|
|
m_NetClient.Disconnect(pReason);
|
|
SetState(IClient::STATE_OFFLINE);
|
|
m_pMap->Unload();
|
|
|
|
// disable all downloads
|
|
m_MapdownloadChunk = 0;
|
|
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[SNAP_CURRENT] = 0;
|
|
m_aSnapshots[SNAP_PREV] = 0;
|
|
m_RecivedSnapshots = 0;
|
|
}
|
|
|
|
void CClient::Disconnect()
|
|
{
|
|
DisconnectWithReason(0);
|
|
}
|
|
|
|
|
|
void CClient::GetServerInfo(CServerInfo *pServerInfo) const
|
|
{
|
|
mem_copy(pServerInfo, &m_CurrentServerInfo, sizeof(m_CurrentServerInfo));
|
|
}
|
|
|
|
int CClient::LoadData()
|
|
{
|
|
m_DebugFont = Graphics()->LoadTexture("ui/debug_font.png", IStorage::TYPE_ALL, CImageInfo::FORMAT_AUTO, IGraphics::TEXLOAD_NORESAMPLE);
|
|
return 1;
|
|
}
|
|
|
|
// ---
|
|
|
|
const void *CClient::SnapGetItem(int SnapID, int Index, CSnapItem *pItem) const
|
|
{
|
|
CSnapshotItem *i;
|
|
dbg_assert(SnapID >= 0 && SnapID < NUM_SNAPSHOT_TYPES, "invalid SnapID");
|
|
i = m_aSnapshots[SnapID]->m_pAltSnap->GetItem(Index);
|
|
pItem->m_DataSize = m_aSnapshots[SnapID]->m_pAltSnap->GetItemSize(Index);
|
|
pItem->m_Type = i->Type();
|
|
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[SnapID]->m_pAltSnap->GetItem(Index);
|
|
if(i)
|
|
{
|
|
if((char *)i < (char *)m_aSnapshots[SnapID]->m_pAltSnap || (char *)i > (char *)m_aSnapshots[SnapID]->m_pAltSnap + m_aSnapshots[SnapID]->m_SnapSize)
|
|
m_pConsole->Print(IConsole::OUTPUT_LEVEL_DEBUG, "client", "snap invalidate problem");
|
|
if((char *)i >= (char *)m_aSnapshots[SnapID]->m_pSnap && (char *)i < (char *)m_aSnapshots[SnapID]->m_pSnap + m_aSnapshots[SnapID]->m_SnapSize)
|
|
m_pConsole->Print(IConsole::OUTPUT_LEVEL_DEBUG, "client", "snap invalidate problem");
|
|
i->m_TypeAndID = -1;
|
|
}
|
|
}
|
|
|
|
const void *CClient::SnapFindItem(int SnapID, int Type, int ID) const
|
|
{
|
|
// TODO: linear search. should be fixed.
|
|
int i;
|
|
|
|
if(!m_aSnapshots[SnapID])
|
|
return 0x0;
|
|
|
|
for(i = 0; i < m_aSnapshots[SnapID]->m_pSnap->NumItems(); i++)
|
|
{
|
|
CSnapshotItem *pItem = m_aSnapshots[SnapID]->m_pAltSnap->GetItem(i);
|
|
if(pItem->Type() == Type && pItem->ID() == ID)
|
|
return (void *)pItem->Data();
|
|
}
|
|
return 0x0;
|
|
}
|
|
|
|
int CClient::SnapNumItems(int SnapID) const
|
|
{
|
|
dbg_assert(SnapID >= 0 && SnapID < NUM_SNAPSHOT_TYPES, "invalid SnapID");
|
|
if(!m_aSnapshots[SnapID])
|
|
return 0;
|
|
return m_aSnapshots[SnapID]->m_pSnap->NumItems();
|
|
}
|
|
|
|
void *CClient::SnapNewItem(int Type, int ID, int Size)
|
|
{
|
|
dbg_assert(Type >= 0 && Type <=0xffff, "incorrect type");
|
|
dbg_assert(ID >= 0 && ID <=0xffff, "incorrect id");
|
|
return ID < 0 ? 0 : m_DemoRecSnapshotBuilder.NewItem(Type, ID, Size);
|
|
}
|
|
|
|
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;
|
|
int64 Now = time_get();
|
|
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, m_PredTick,
|
|
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",
|
|
(int)((m_PredictedTime.Get(Now)-m_GameTime.Get(Now))*1000/(float)time_freq()));
|
|
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.ScaleMin();
|
|
m_InputtimeMarginGraph.ScaleMax();
|
|
m_InputtimeMarginGraph.Render(Graphics(), m_DebugFont, x, sp*5+h+sp, w, h, "Prediction Margin");
|
|
m_GametimeMarginGraph.ScaleMin();
|
|
m_GametimeMarginGraph.ScaleMax();
|
|
m_GametimeMarginGraph.Render(Graphics(), m_DebugFont, x, sp*5+h+sp+h+sp, w, h, "Gametime Margin");
|
|
}
|
|
}
|
|
|
|
void CClient::Quit()
|
|
{
|
|
SetState(IClient::STATE_QUITING);
|
|
}
|
|
|
|
const char *CClient::ErrorString() const
|
|
{
|
|
return m_NetClient.ErrorString();
|
|
}
|
|
|
|
void CClient::Render()
|
|
{
|
|
if(g_Config.m_GfxClear)
|
|
Graphics()->Clear(1,1,0);
|
|
|
|
GameClient()->OnRender();
|
|
DebugRender();
|
|
}
|
|
|
|
const char *CClient::LoadMap(const char *pName, const char *pFilename, unsigned WantedCrc)
|
|
{
|
|
static char aErrorMsg[128];
|
|
|
|
SetState(IClient::STATE_LOADING);
|
|
|
|
if(!m_pMap->Load(pFilename))
|
|
{
|
|
str_format(aErrorMsg, sizeof(aErrorMsg), "map '%s' not found", pFilename);
|
|
return aErrorMsg;
|
|
}
|
|
|
|
// get the crc of the map
|
|
if(m_pMap->Crc() != WantedCrc)
|
|
{
|
|
str_format(aErrorMsg, sizeof(aErrorMsg), "map differs from the server. %08x != %08x", m_pMap->Crc(), WantedCrc);
|
|
m_pConsole->Print(IConsole::OUTPUT_LEVEL_ADDINFO, "client", aErrorMsg);
|
|
m_pMap->Unload();
|
|
return aErrorMsg;
|
|
}
|
|
|
|
// stop demo recording if we loaded a new map
|
|
DemoRecorder_Stop();
|
|
|
|
char aBuf[256];
|
|
str_format(aBuf, sizeof(aBuf), "loaded map '%s'", pFilename);
|
|
m_pConsole->Print(IConsole::OUTPUT_LEVEL_ADDINFO, "client", aBuf);
|
|
m_RecivedSnapshots = 0;
|
|
|
|
str_copy(m_aCurrentMap, pName, sizeof(m_aCurrentMap));
|
|
m_CurrentMapCrc = m_pMap->Crc();
|
|
|
|
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::PlayerScoreComp(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 0;
|
|
if(p0->m_Score < p1->m_Score)
|
|
return 1;
|
|
return -1;
|
|
}
|
|
|
|
int CClient::UnpackServerInfo(CUnpacker *pUnpacker, CServerInfo *pInfo, int *pToken)
|
|
{
|
|
if(pToken)
|
|
*pToken = pUnpacker->GetInt();
|
|
str_copy(pInfo->m_aVersion, pUnpacker->GetString(CUnpacker::SANITIZE_CC|CUnpacker::SKIP_START_WHITESPACES), sizeof(pInfo->m_aVersion));
|
|
str_copy(pInfo->m_aName, pUnpacker->GetString(CUnpacker::SANITIZE_CC|CUnpacker::SKIP_START_WHITESPACES), sizeof(pInfo->m_aName));
|
|
str_clean_whitespaces(pInfo->m_aName);
|
|
str_copy(pInfo->m_aHostname, pUnpacker->GetString(CUnpacker::SANITIZE_CC|CUnpacker::SKIP_START_WHITESPACES), sizeof(pInfo->m_aHostname));
|
|
if(pInfo->m_aHostname[0] == 0)
|
|
str_copy(pInfo->m_aHostname, pInfo->m_aAddress, sizeof(pInfo->m_aHostname));
|
|
str_copy(pInfo->m_aMap, pUnpacker->GetString(CUnpacker::SANITIZE_CC|CUnpacker::SKIP_START_WHITESPACES), sizeof(pInfo->m_aMap));
|
|
str_copy(pInfo->m_aGameType, pUnpacker->GetString(CUnpacker::SANITIZE_CC|CUnpacker::SKIP_START_WHITESPACES), sizeof(pInfo->m_aGameType));
|
|
pInfo->m_Flags = (pUnpacker->GetInt()&SERVERINFO_FLAG_PASSWORD) ? IServerBrowser::FLAG_PASSWORD : 0;
|
|
pInfo->m_ServerLevel = clamp<int>(pUnpacker->GetInt(), SERVERINFO_LEVEL_MIN, SERVERINFO_LEVEL_MAX);
|
|
pInfo->m_NumPlayers = pUnpacker->GetInt();
|
|
pInfo->m_MaxPlayers = pUnpacker->GetInt();
|
|
pInfo->m_NumClients = pUnpacker->GetInt();
|
|
pInfo->m_MaxClients = pUnpacker->GetInt();
|
|
|
|
// don't add invalid info to the server browser list
|
|
if(pInfo->m_NumClients < 0 || pInfo->m_NumClients > MAX_CLIENTS || pInfo->m_MaxClients < 0 || pInfo->m_MaxClients > MAX_CLIENTS ||
|
|
pInfo->m_NumPlayers < 0 || pInfo->m_NumPlayers > pInfo->m_NumClients || pInfo->m_MaxPlayers < 0 || pInfo->m_MaxPlayers > pInfo->m_MaxClients)
|
|
return -1;
|
|
|
|
// use short version
|
|
if(!pToken)
|
|
return 0;
|
|
|
|
for(int i = 0; i < pInfo->m_NumClients; i++)
|
|
{
|
|
str_copy(pInfo->m_aClients[i].m_aName, pUnpacker->GetString(CUnpacker::SANITIZE_CC|CUnpacker::SKIP_START_WHITESPACES), sizeof(pInfo->m_aClients[i].m_aName));
|
|
str_copy(pInfo->m_aClients[i].m_aClan, pUnpacker->GetString(CUnpacker::SANITIZE_CC|CUnpacker::SKIP_START_WHITESPACES), sizeof(pInfo->m_aClients[i].m_aClan));
|
|
pInfo->m_aClients[i].m_Country = pUnpacker->GetInt();
|
|
pInfo->m_aClients[i].m_Score = pUnpacker->GetInt();
|
|
pInfo->m_aClients[i].m_Player = pUnpacker->GetInt() != 0 ? true : false;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
void CClient::ProcessConnlessPacket(CNetChunk *pPacket)
|
|
{
|
|
// version server
|
|
if(m_VersionInfo.m_State == CVersionInfo::STATE_READY && net_addr_comp(&pPacket->m_Address, &m_VersionInfo.m_VersionServeraddr.m_Addr) == 0)
|
|
{
|
|
// version info
|
|
if(pPacket->m_DataSize == (int)(sizeof(VERSIONSRV_VERSION) + sizeof(GAME_RELEASE_VERSION)) &&
|
|
mem_comp(pPacket->m_pData, VERSIONSRV_VERSION, sizeof(VERSIONSRV_VERSION)) == 0)
|
|
|
|
{
|
|
char *pVersionData = (char*)pPacket->m_pData + sizeof(VERSIONSRV_VERSION);
|
|
int VersionMatch = !mem_comp(pVersionData, GAME_RELEASE_VERSION, sizeof(GAME_RELEASE_VERSION));
|
|
|
|
char aVersion[sizeof(GAME_RELEASE_VERSION)];
|
|
str_copy(aVersion, pVersionData, sizeof(aVersion));
|
|
|
|
char aBuf[256];
|
|
str_format(aBuf, sizeof(aBuf), "version does %s (%s)",
|
|
VersionMatch ? "match" : "NOT match",
|
|
aVersion);
|
|
m_pConsole->Print(IConsole::OUTPUT_LEVEL_ADDINFO, "client/version", aBuf);
|
|
|
|
// assume version is out of date when version-data doesn't match
|
|
if(!VersionMatch)
|
|
{
|
|
str_copy(m_aVersionStr, aVersion, sizeof(m_aVersionStr));
|
|
}
|
|
|
|
// request the map version list now
|
|
CNetChunk Packet;
|
|
mem_zero(&Packet, sizeof(Packet));
|
|
Packet.m_ClientID = -1;
|
|
Packet.m_Address = m_VersionInfo.m_VersionServeraddr.m_Addr;
|
|
Packet.m_pData = VERSIONSRV_GETMAPLIST;
|
|
Packet.m_DataSize = sizeof(VERSIONSRV_GETMAPLIST);
|
|
Packet.m_Flags = NETSENDFLAG_CONNLESS;
|
|
m_ContactClient.Send(&Packet);
|
|
}
|
|
|
|
// map version list
|
|
if(pPacket->m_DataSize >= (int)sizeof(VERSIONSRV_MAPLIST) &&
|
|
mem_comp(pPacket->m_pData, VERSIONSRV_MAPLIST, sizeof(VERSIONSRV_MAPLIST)) == 0)
|
|
{
|
|
int Size = pPacket->m_DataSize-sizeof(VERSIONSRV_MAPLIST);
|
|
int Num = Size/sizeof(CMapVersion);
|
|
m_MapChecker.AddMaplist((CMapVersion *)((char*)pPacket->m_pData+sizeof(VERSIONSRV_MAPLIST)), Num);
|
|
}
|
|
}
|
|
|
|
// 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_aIPV4Mapping[] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF};
|
|
|
|
// copy address
|
|
if(!mem_comp(s_aIPV4Mapping, pAddrs[i].m_aIp, sizeof(s_aIPV4Mapping)))
|
|
{
|
|
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, CServerBrowser::SET_MASTER_ADD, -1, 0x0);
|
|
}
|
|
}
|
|
|
|
// server info
|
|
if(pPacket->m_DataSize >= (int)sizeof(SERVERBROWSE_INFO) && mem_comp(pPacket->m_pData, SERVERBROWSE_INFO, sizeof(SERVERBROWSE_INFO)) == 0)
|
|
{
|
|
CUnpacker Up;
|
|
CServerInfo Info = {0};
|
|
Up.Reset((unsigned char*)pPacket->m_pData+sizeof(SERVERBROWSE_INFO), pPacket->m_DataSize-sizeof(SERVERBROWSE_INFO));
|
|
net_addr_str(&pPacket->m_Address, Info.m_aAddress, sizeof(Info.m_aAddress), true);
|
|
int Token;
|
|
if(!UnpackServerInfo(&Up, &Info, &Token) && !Up.Error())
|
|
{
|
|
qsort(Info.m_aClients, Info.m_NumClients, sizeof(*Info.m_aClients), PlayerScoreComp);
|
|
m_ServerBrowser.Set(pPacket->m_Address, CServerBrowser::SET_TOKEN, Token, &Info);
|
|
}
|
|
}
|
|
}
|
|
|
|
void CClient::ProcessServerPacket(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)
|
|
{
|
|
// 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();
|
|
int MapChunkNum = Unpacker.GetInt();
|
|
int MapChunkSize = Unpacker.GetInt();
|
|
const char *pError = 0;
|
|
|
|
if(Unpacker.Error())
|
|
return;
|
|
|
|
// check for valid standard map
|
|
if(!m_MapChecker.IsMapValid(pMap, MapCrc, MapSize))
|
|
pError = "invalid standard map";
|
|
|
|
// protect the player from nasty map names
|
|
for(int i = 0; pMap[i]; i++)
|
|
{
|
|
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
|
|
{
|
|
// start map download
|
|
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);
|
|
|
|
str_copy(m_aMapdownloadName, pMap, sizeof(m_aMapdownloadName));
|
|
if(m_MapdownloadFile)
|
|
io_close(m_MapdownloadFile);
|
|
m_MapdownloadFile = Storage()->OpenFile(m_aMapdownloadFilename, IOFLAG_WRITE, IStorage::TYPE_SAVE);
|
|
m_MapdownloadChunk = 0;
|
|
m_MapdownloadChunkNum = MapChunkNum;
|
|
m_MapDownloadChunkSize = MapChunkSize;
|
|
m_MapdownloadCrc = MapCrc;
|
|
m_MapdownloadTotalsize = MapSize;
|
|
m_MapdownloadAmount = 0;
|
|
|
|
// request first chunk package of map data
|
|
CMsgPacker Msg(NETMSG_REQUEST_MAP_DATA, true);
|
|
SendMsg(&Msg, MSGFLAG_VITAL|MSGFLAG_FLUSH);
|
|
|
|
if(g_Config.m_Debug)
|
|
m_pConsole->Print(IConsole::OUTPUT_LEVEL_DEBUG, "client/network", "requested first chunk package");
|
|
}
|
|
}
|
|
}
|
|
else if((pPacket->m_Flags&NET_CHUNKFLAG_VITAL) != 0 && Msg == NETMSG_MAP_DATA)
|
|
{
|
|
if(!m_MapdownloadFile)
|
|
return;
|
|
|
|
int Size = min(m_MapDownloadChunkSize, m_MapdownloadTotalsize-m_MapdownloadAmount);
|
|
const unsigned char *pData = Unpacker.GetRaw(Size);
|
|
if(Unpacker.Error())
|
|
return;
|
|
|
|
io_write(m_MapdownloadFile, pData, Size);
|
|
++m_MapdownloadChunk;
|
|
m_MapdownloadAmount += Size;
|
|
|
|
if(m_MapdownloadAmount == m_MapdownloadTotalsize)
|
|
{
|
|
// map download complete
|
|
m_pConsole->Print(IConsole::OUTPUT_LEVEL_ADDINFO, "client/network", "download complete, loading map");
|
|
|
|
if(m_MapdownloadFile)
|
|
io_close(m_MapdownloadFile);
|
|
m_MapdownloadFile = 0;
|
|
m_MapdownloadAmount = 0;
|
|
m_MapdownloadTotalsize = -1;
|
|
|
|
// load map
|
|
const char *pError = LoadMap(m_aMapdownloadName, m_aMapdownloadFilename, m_MapdownloadCrc);
|
|
if(!pError)
|
|
{
|
|
m_pConsole->Print(IConsole::OUTPUT_LEVEL_ADDINFO, "client/network", "loading done");
|
|
SendReady();
|
|
}
|
|
else
|
|
DisconnectWithReason(pError);
|
|
}
|
|
else if(m_MapdownloadChunk%m_MapdownloadChunkNum == 0)
|
|
{
|
|
// request next chunk package of map data
|
|
CMsgPacker Msg(NETMSG_REQUEST_MAP_DATA, true);
|
|
SendMsg(&Msg, MSGFLAG_VITAL|MSGFLAG_FLUSH);
|
|
|
|
if(g_Config.m_Debug)
|
|
m_pConsole->Print(IConsole::OUTPUT_LEVEL_DEBUG, "client/network", "requested next chunk package");
|
|
}
|
|
}
|
|
else if((pPacket->m_Flags&NET_CHUNKFLAG_VITAL) != 0 && Msg == NETMSG_SERVERINFO)
|
|
{
|
|
CServerInfo Info = {0};
|
|
net_addr_str(&pPacket->m_Address, Info.m_aAddress, sizeof(Info.m_aAddress), true);
|
|
if(!UnpackServerInfo(&Unpacker, &Info, 0) && !Unpacker.Error())
|
|
{
|
|
qsort(Info.m_aClients, Info.m_NumClients, sizeof(*Info.m_aClients), PlayerScoreComp);
|
|
mem_copy(&m_CurrentServerInfo, &Info, sizeof(m_CurrentServerInfo));
|
|
m_CurrentServerInfo.m_NetAddr = m_ServerAddress;
|
|
}
|
|
}
|
|
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, true);
|
|
SendMsg(&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_ON)
|
|
{
|
|
m_RconAuthed = 1;
|
|
m_UseTempRconCommands = 1;
|
|
}
|
|
else if((pPacket->m_Flags&NET_CHUNKFLAG_VITAL) != 0 && Msg == NETMSG_RCON_AUTH_OFF)
|
|
{
|
|
m_RconAuthed = 0;
|
|
if(m_UseTempRconCommands)
|
|
m_pConsole->DeregisterTempAll();
|
|
m_UseTempRconCommands = 0;
|
|
}
|
|
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();
|
|
|
|
// adjust our prediction time
|
|
int64 Target = 0;
|
|
for(int k = 0; k < 200; k++)
|
|
{
|
|
if(m_aInputs[k].m_Tick == InputPredTick)
|
|
{
|
|
Target = m_aInputs[k].m_PredictedTime + (time_get() - m_aInputs[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;
|
|
|
|
// 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 || NumParts > CSnapshot::MAX_PARTS || Part < 0 || Part >= NumParts || PartSize < 0 || PartSize > MAX_SNAPSHOT_PACKSIZE)
|
|
return;
|
|
|
|
if(GameTick >= m_CurrentRecvTick)
|
|
{
|
|
if(GameTick != m_CurrentRecvTick)
|
|
{
|
|
m_SnapshotParts = 0;
|
|
m_CurrentRecvTick = GameTick;
|
|
}
|
|
|
|
// TODO: clean this up abit
|
|
mem_copy((char*)m_aSnapshotIncommingData + Part*MAX_SNAPSHOT_PACKSIZE, pData, PartSize);
|
|
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.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 = -1;
|
|
return;
|
|
}
|
|
}
|
|
|
|
// decompress snapshot
|
|
pDeltaData = m_SnapshotDelta.EmptyDelta();
|
|
DeltaSize = sizeof(int)*3;
|
|
|
|
if(CompleteSize)
|
|
{
|
|
int IntSize = CVariableInt::Decompress(m_aSnapshotIncommingData, 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 = -1;
|
|
SendInput();
|
|
m_SnapCrcErrors = 0;
|
|
}
|
|
return;
|
|
}
|
|
else
|
|
{
|
|
if(m_SnapCrcErrors)
|
|
m_SnapCrcErrors--;
|
|
}
|
|
|
|
// purge old snapshots
|
|
PurgeTick = DeltaTick;
|
|
if(m_aSnapshots[SNAP_PREV] && m_aSnapshots[SNAP_PREV]->m_Tick < PurgeTick)
|
|
PurgeTick = m_aSnapshots[SNAP_PREV]->m_Tick;
|
|
if(m_aSnapshots[SNAP_CURRENT] && m_aSnapshots[SNAP_CURRENT]->m_Tick < PurgeTick)
|
|
PurgeTick = m_aSnapshots[SNAP_CURRENT]->m_Tick;
|
|
m_SnapshotStorage.PurgeUntil(PurgeTick);
|
|
|
|
// add new
|
|
m_SnapshotStorage.Add(GameTick, time_get(), SnapSize, pTmpBuffer3, 1);
|
|
|
|
// add snapshot to demo
|
|
if(m_DemoRecorder.IsRecording())
|
|
{
|
|
// build up snapshot and add local messages
|
|
m_DemoRecSnapshotBuilder.Init(pTmpBuffer3);
|
|
GameClient()->OnDemoRecSnap();
|
|
SnapSize = m_DemoRecSnapshotBuilder.Finish(pTmpBuffer3);
|
|
|
|
// write snapshot
|
|
m_DemoRecorder.RecordSnapshot(GameTick, pTmpBuffer3, SnapSize);
|
|
}
|
|
|
|
// apply snapshot, cycle pointers
|
|
m_RecivedSnapshots++;
|
|
|
|
m_CurrentRecvTick = GameTick;
|
|
|
|
// we got two snapshots until we see us self as connected
|
|
if(m_RecivedSnapshots == 2)
|
|
{
|
|
// start at 200ms and work from there
|
|
m_PredictedTime.Init(GameTick*time_freq()/50);
|
|
m_PredictedTime.SetAdjustSpeed(1, 1000.0f);
|
|
m_GameTime.Init((GameTick-1)*time_freq()/50);
|
|
m_aSnapshots[SNAP_PREV] = m_SnapshotStorage.m_pFirst;
|
|
m_aSnapshots[SNAP_CURRENT] = m_SnapshotStorage.m_pLast;
|
|
m_LocalStartTime = time_get();
|
|
SetState(IClient::STATE_ONLINE);
|
|
DemoRecorder_HandleAutoStart();
|
|
}
|
|
|
|
// adjust game time
|
|
if(m_RecivedSnapshots > 2)
|
|
{
|
|
int64 Now = m_GameTime.Get(time_get());
|
|
int64 TickStart = GameTick*time_freq()/50;
|
|
int64 TimeLeft = (TickStart-Now)*1000 / time_freq();
|
|
m_GameTime.Update(&m_GametimeMarginGraph, (GameTick-1)*time_freq()/50, TimeLeft, 0);
|
|
}
|
|
|
|
// ack snapshot
|
|
m_AckGameTick = GameTick;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if((pPacket->m_Flags&NET_CHUNKFLAG_VITAL) != 0)
|
|
{
|
|
// game message
|
|
GameClient()->OnMessage(Msg, &Unpacker);
|
|
|
|
if(m_RecordGameMessage && m_DemoRecorder.IsRecording())
|
|
m_DemoRecorder.RecordMessage(pPacket->m_pData, pPacket->m_DataSize);
|
|
}
|
|
}
|
|
}
|
|
|
|
void CClient::PumpNetwork()
|
|
{
|
|
m_NetClient.Update();
|
|
|
|
if(State() != IClient::STATE_DEMOPLAYBACK)
|
|
{
|
|
// check for errors
|
|
if(State() != IClient::STATE_OFFLINE && State() != IClient::STATE_QUITING && m_NetClient.State() == NETSTATE_OFFLINE)
|
|
{
|
|
SetState(IClient::STATE_OFFLINE);
|
|
Disconnect();
|
|
char aBuf[256];
|
|
str_format(aBuf, sizeof(aBuf), "offline error='%s'", m_NetClient.ErrorString());
|
|
m_pConsole->Print(IConsole::OUTPUT_LEVEL_STANDARD, "client", aBuf);
|
|
}
|
|
|
|
//
|
|
if(State() == IClient::STATE_CONNECTING && m_NetClient.State() == NETSTATE_ONLINE)
|
|
{
|
|
// we switched to online
|
|
m_pConsole->Print(IConsole::OUTPUT_LEVEL_STANDARD, "client", "connected, sending info");
|
|
SetState(IClient::STATE_LOADING);
|
|
SendInfo();
|
|
}
|
|
}
|
|
|
|
// process non-connless packets
|
|
CNetChunk Packet;
|
|
while(m_NetClient.Recv(&Packet))
|
|
{
|
|
if(!(Packet.m_Flags&NETSENDFLAG_CONNLESS))
|
|
ProcessServerPacket(&Packet);
|
|
}
|
|
|
|
// process connless packets data
|
|
m_ContactClient.Update();
|
|
while(m_ContactClient.Recv(&Packet))
|
|
{
|
|
if(Packet.m_Flags&NETSENDFLAG_CONNLESS)
|
|
ProcessConnlessPacket(&Packet);
|
|
}
|
|
}
|
|
|
|
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 = pInfo->m_Info.m_CurrentTick;
|
|
m_PrevGameTick = pInfo->m_PreviousTick;
|
|
|
|
// handle snapshots
|
|
pTemp = m_aSnapshots[SNAP_PREV];
|
|
m_aSnapshots[SNAP_PREV] = m_aSnapshots[SNAP_CURRENT];
|
|
m_aSnapshots[SNAP_CURRENT] = pTemp;
|
|
|
|
mem_copy(m_aSnapshots[SNAP_CURRENT]->m_pSnap, pData, Size);
|
|
mem_copy(m_aSnapshots[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 = pInfo->m_Info.m_CurrentTick;
|
|
m_PrevGameTick = pInfo->m_PreviousTick;
|
|
m_GameIntraTick = pInfo->m_IntraTick;
|
|
m_GameTickTime = pInfo->m_TickTime;
|
|
}
|
|
else
|
|
{
|
|
// disconnect on error
|
|
Disconnect();
|
|
}
|
|
}
|
|
else if(State() == IClient::STATE_ONLINE && m_RecivedSnapshots >= 3)
|
|
{
|
|
// switch snapshot
|
|
int Repredict = 0;
|
|
int64 Freq = time_freq();
|
|
int64 Now = m_GameTime.Get(time_get());
|
|
int64 PredNow = m_PredictedTime.Get(time_get());
|
|
|
|
while(1)
|
|
{
|
|
CSnapshotStorage::CHolder *pCur = m_aSnapshots[SNAP_CURRENT];
|
|
int64 TickStart = (pCur->m_Tick)*time_freq()/50;
|
|
|
|
if(TickStart < Now)
|
|
{
|
|
CSnapshotStorage::CHolder *pNext = m_aSnapshots[SNAP_CURRENT]->m_pNext;
|
|
if(pNext)
|
|
{
|
|
m_aSnapshots[SNAP_PREV] = m_aSnapshots[SNAP_CURRENT];
|
|
m_aSnapshots[SNAP_CURRENT] = pNext;
|
|
|
|
// set ticks
|
|
m_CurGameTick = m_aSnapshots[SNAP_CURRENT]->m_Tick;
|
|
m_PrevGameTick = m_aSnapshots[SNAP_PREV]->m_Tick;
|
|
|
|
if(m_aSnapshots[SNAP_CURRENT] && m_aSnapshots[SNAP_PREV])
|
|
{
|
|
GameClient()->OnNewSnapshot();
|
|
Repredict = 1;
|
|
}
|
|
}
|
|
else
|
|
break;
|
|
}
|
|
else
|
|
break;
|
|
}
|
|
|
|
if(m_aSnapshots[SNAP_CURRENT] && m_aSnapshots[SNAP_PREV])
|
|
{
|
|
int64 CurtickStart = (m_aSnapshots[SNAP_CURRENT]->m_Tick)*time_freq()/50;
|
|
int64 PrevtickStart = (m_aSnapshots[SNAP_PREV]->m_Tick)*time_freq()/50;
|
|
int PrevPredTick = (int)(PredNow*50/time_freq());
|
|
int NewPredTick = PrevPredTick+1;
|
|
|
|
m_GameIntraTick = (Now - PrevtickStart) / (float)(CurtickStart-PrevtickStart);
|
|
m_GameTickTime = (Now - PrevtickStart) / (float)Freq; //(float)SERVER_TICK_SPEED);
|
|
|
|
CurtickStart = NewPredTick*time_freq()/50;
|
|
PrevtickStart = PrevPredTick*time_freq()/50;
|
|
m_PredIntraTick = (PredNow - PrevtickStart) / (float)(CurtickStart-PrevtickStart);
|
|
|
|
if(NewPredTick < m_aSnapshots[SNAP_PREV]->m_Tick-SERVER_TICK_SPEED || NewPredTick > m_aSnapshots[SNAP_PREV]->m_Tick+SERVER_TICK_SPEED)
|
|
{
|
|
m_pConsole->Print(IConsole::OUTPUT_LEVEL_ADDINFO, "client", "prediction time reset!");
|
|
m_PredictedTime.Init(m_aSnapshots[SNAP_CURRENT]->m_Tick*time_freq()/50);
|
|
}
|
|
|
|
if(NewPredTick > m_PredTick)
|
|
{
|
|
m_PredTick = NewPredTick;
|
|
Repredict = 1;
|
|
|
|
// send input
|
|
SendInput();
|
|
}
|
|
}
|
|
|
|
// only do sane predictions
|
|
if(Repredict)
|
|
{
|
|
if(m_PredTick > m_CurGameTick && m_PredTick < m_CurGameTick+50)
|
|
GameClient()->OnPredict();
|
|
}
|
|
}
|
|
|
|
// STRESS TEST: join the server again
|
|
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;
|
|
}
|
|
}
|
|
}
|
|
|
|
// pump the network
|
|
PumpNetwork();
|
|
|
|
// 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();
|
|
}
|
|
|
|
void CClient::VersionUpdate()
|
|
{
|
|
if(m_VersionInfo.m_State == CVersionInfo::STATE_INIT)
|
|
{
|
|
Engine()->HostLookup(&m_VersionInfo.m_VersionServeraddr, g_Config.m_ClVersionServer, m_ContactClient.NetType());
|
|
m_VersionInfo.m_State = CVersionInfo::STATE_START;
|
|
}
|
|
else if(m_VersionInfo.m_State == CVersionInfo::STATE_START)
|
|
{
|
|
if(m_VersionInfo.m_VersionServeraddr.m_Job.Status() == CJob::STATE_DONE)
|
|
{
|
|
CNetChunk Packet;
|
|
|
|
mem_zero(&Packet, sizeof(Packet));
|
|
|
|
m_VersionInfo.m_VersionServeraddr.m_Addr.port = VERSIONSRV_PORT;
|
|
|
|
Packet.m_ClientID = -1;
|
|
Packet.m_Address = m_VersionInfo.m_VersionServeraddr.m_Addr;
|
|
Packet.m_pData = VERSIONSRV_GETVERSION;
|
|
Packet.m_DataSize = sizeof(VERSIONSRV_GETVERSION);
|
|
Packet.m_Flags = NETSENDFLAG_CONNLESS;
|
|
|
|
m_ContactClient.Send(&Packet);
|
|
m_VersionInfo.m_State = CVersionInfo::STATE_READY;
|
|
}
|
|
}
|
|
}
|
|
|
|
void CClient::RegisterInterfaces()
|
|
{
|
|
Kernel()->RegisterInterface(static_cast<IDemoRecorder*>(&m_DemoRecorder));
|
|
Kernel()->RegisterInterface(static_cast<IDemoPlayer*>(&m_DemoPlayer));
|
|
Kernel()->RegisterInterface(static_cast<IServerBrowser*>(&m_ServerBrowser));
|
|
Kernel()->RegisterInterface(static_cast<IFriends*>(&m_Friends));
|
|
}
|
|
|
|
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>();
|
|
m_pStorage = Kernel()->RequestInterface<IStorage>();
|
|
|
|
//
|
|
m_ServerBrowser.Init(&m_ContactClient, m_pGameClient->NetVersion());
|
|
m_Friends.Init();
|
|
}
|
|
|
|
void CClient::Run()
|
|
{
|
|
m_LocalStartTime = time_get();
|
|
m_SnapshotParts = 0;
|
|
|
|
// init SDL
|
|
{
|
|
if(SDL_Init(0) < 0)
|
|
{
|
|
dbg_msg("client", "unable to init SDL base: %s", SDL_GetError());
|
|
return;
|
|
}
|
|
|
|
atexit(SDL_Quit); // ignore_convention
|
|
}
|
|
|
|
m_MenuStartTime = time_get();
|
|
|
|
// 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));
|
|
|
|
if(RegisterFail || m_pGraphics->Init() != 0)
|
|
{
|
|
dbg_msg("client", "couldn't init graphics");
|
|
return;
|
|
}
|
|
}
|
|
|
|
// init sound, allowed to fail
|
|
m_SoundInitFailed = Sound()->Init() != 0;
|
|
Sound()->SetMaxDistance(1.5f*Graphics()->ScreenWidth()/2.0f);
|
|
|
|
// 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;
|
|
}
|
|
if(!m_NetClient.Open(BindAddr, BindAddr.port ? 0 : NETCREATE_FLAG_RANDOMPORT))
|
|
{
|
|
dbg_msg("client", "couldn't open socket(net)");
|
|
return;
|
|
}
|
|
BindAddr.port = 0;
|
|
if(!m_ContactClient.Open(BindAddr, 0))
|
|
{
|
|
dbg_msg("client", "couldn't open socket(contact)");
|
|
return;
|
|
}
|
|
}
|
|
|
|
// init font rendering
|
|
Kernel()->RequestInterface<IEngineTextRender>()->Init();
|
|
|
|
// init the input
|
|
Input()->Init();
|
|
|
|
// start refreshing addresses while we load
|
|
MasterServer()->RefreshAddresses(m_ContactClient.NetType());
|
|
|
|
// init the editor
|
|
m_pEditor->Init();
|
|
|
|
|
|
// 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, 120.0f);
|
|
|
|
// never start with the editor
|
|
g_Config.m_ClEditor = 0;
|
|
|
|
// process pending commands
|
|
m_pConsole->StoreCommands(false);
|
|
|
|
while (1)
|
|
{
|
|
//
|
|
VersionUpdate();
|
|
|
|
// 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;
|
|
}
|
|
|
|
// update input
|
|
if(Input()->Update())
|
|
break; // SDL_QUIT
|
|
|
|
// update sound
|
|
Sound()->Update();
|
|
|
|
// release focus
|
|
if(!m_pGraphics->WindowActive())
|
|
{
|
|
if(m_WindowMustRefocus == 0)
|
|
Input()->MouseModeAbsolute();
|
|
m_WindowMustRefocus = 1;
|
|
}
|
|
else if (g_Config.m_DbgFocus && Input()->KeyPress(KEY_ESCAPE, true))
|
|
{
|
|
Input()->MouseModeAbsolute();
|
|
m_WindowMustRefocus = 1;
|
|
}
|
|
|
|
// refocus
|
|
if(m_WindowMustRefocus && m_pGraphics->WindowActive())
|
|
{
|
|
if(m_WindowMustRefocus < 3)
|
|
{
|
|
Input()->MouseModeAbsolute();
|
|
m_WindowMustRefocus++;
|
|
}
|
|
|
|
if(m_WindowMustRefocus >= 3 || Input()->KeyPress(KEY_MOUSE_1, true))
|
|
{
|
|
Input()->MouseModeRelative();
|
|
m_WindowMustRefocus = 0;
|
|
|
|
// update screen in case it got moved
|
|
int ActScreen = Graphics()->GetWindowScreen();
|
|
if(ActScreen >= 0 && ActScreen != g_Config.m_GfxScreen)
|
|
g_Config.m_GfxScreen = ActScreen;
|
|
}
|
|
}
|
|
|
|
// panic quit button
|
|
if(Input()->KeyIsPressed(KEY_LCTRL) && Input()->KeyIsPressed(KEY_LSHIFT) && Input()->KeyPress(KEY_Q, true))
|
|
{
|
|
Quit();
|
|
break;
|
|
}
|
|
|
|
if(Input()->KeyIsPressed(KEY_LCTRL) && Input()->KeyIsPressed(KEY_LSHIFT) && Input()->KeyPress(KEY_D, true))
|
|
g_Config.m_Debug ^= 1;
|
|
|
|
if(Input()->KeyIsPressed(KEY_LCTRL) && Input()->KeyIsPressed(KEY_LSHIFT) && Input()->KeyPress(KEY_G, true))
|
|
g_Config.m_DbgGraphs ^= 1;
|
|
|
|
if(Input()->KeyIsPressed(KEY_LCTRL) && Input()->KeyIsPressed(KEY_LSHIFT) && Input()->KeyPress(KEY_E, true))
|
|
{
|
|
g_Config.m_ClEditor = g_Config.m_ClEditor^1;
|
|
Input()->MouseModeRelative();
|
|
}
|
|
|
|
// render
|
|
{
|
|
if(g_Config.m_ClEditor)
|
|
{
|
|
if(!m_EditorActive)
|
|
{
|
|
GameClient()->OnActivateEditor();
|
|
m_EditorActive = true;
|
|
}
|
|
}
|
|
else if(m_EditorActive)
|
|
m_EditorActive = false;
|
|
|
|
Update();
|
|
|
|
if(!g_Config.m_GfxAsyncRender || m_pGraphics->IsIdle())
|
|
{
|
|
m_RenderFrames++;
|
|
|
|
// update frametime
|
|
int64 Now = time_get();
|
|
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);
|
|
|
|
m_LastRenderTime = Now;
|
|
|
|
// when we are stress testing only render every 10th frame
|
|
if(!g_Config.m_DbgStress || (m_RenderFrames%10) == 0 )
|
|
{
|
|
if(!m_EditorActive)
|
|
Render();
|
|
else
|
|
{
|
|
m_pEditor->UpdateAndRender();
|
|
DebugRender();
|
|
}
|
|
m_pGraphics->Swap();
|
|
}
|
|
}
|
|
}
|
|
|
|
AutoScreenshot_Cleanup();
|
|
|
|
// check conditions
|
|
if(State() == IClient::STATE_QUITING)
|
|
break;
|
|
|
|
// menu tick
|
|
if(State() == IClient::STATE_OFFLINE)
|
|
{
|
|
int64 t = time_get();
|
|
while(t > TickStartTime(m_CurMenuTick+1))
|
|
m_CurMenuTick++;
|
|
}
|
|
|
|
// beNice
|
|
if(g_Config.m_ClCpuThrottle)
|
|
thread_sleep(g_Config.m_ClCpuThrottle);
|
|
else if(g_Config.m_DbgStress || !m_pGraphics->WindowActive())
|
|
thread_sleep(5);
|
|
|
|
if(g_Config.m_DbgHitch)
|
|
{
|
|
thread_sleep(g_Config.m_DbgHitch);
|
|
g_Config.m_DbgHitch = 0;
|
|
}
|
|
|
|
/*
|
|
if(ReportTime < time_get())
|
|
{
|
|
if(0 && g_Config.m_Debug)
|
|
{
|
|
dbg_msg("client/report", "fps=%.02f (%.02f %.02f) netstate=%d",
|
|
m_Frames/(float)(ReportInterval/time_freq()),
|
|
1.0f/m_RenderFrameTimeHigh,
|
|
1.0f/m_RenderFrameTimeLow,
|
|
m_NetClient.State());
|
|
}
|
|
m_RenderFrameTimeLow = 1;
|
|
m_RenderFrameTimeHigh = 0;
|
|
m_RenderFrames = 0;
|
|
ReportTime += ReportInterval;
|
|
}*/
|
|
|
|
// update local time
|
|
m_LocalTime = (time_get()-m_LocalStartTime)/(float)time_freq();
|
|
}
|
|
|
|
GameClient()->OnShutdown();
|
|
Disconnect();
|
|
|
|
m_pGraphics->Shutdown();
|
|
m_pSound->Shutdown();
|
|
|
|
// shutdown SDL
|
|
{
|
|
SDL_Quit();
|
|
}
|
|
}
|
|
|
|
int64 CClient::TickStartTime(int Tick)
|
|
{
|
|
return m_MenuStartTime + (time_freq()*Tick)/m_GameTickSpeed;
|
|
}
|
|
|
|
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_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, true);
|
|
pSelf->SendMsg(&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::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::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));
|
|
}
|
|
|
|
const char *CClient::DemoPlayer_Play(const char *pFilename, int StorageType)
|
|
{
|
|
int Crc;
|
|
|
|
Disconnect();
|
|
m_NetClient.ResetErrorString();
|
|
|
|
// try to start playback
|
|
m_DemoPlayer.SetListner(this);
|
|
|
|
const char *pError = m_DemoPlayer.Load(Storage(), m_pConsole, pFilename, StorageType, GameClient()->NetVersion());
|
|
if(pError)
|
|
return pError;
|
|
|
|
// 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[SNAP_CURRENT] = &m_aDemorecSnapshotHolders[SNAP_CURRENT];
|
|
m_aSnapshots[SNAP_PREV] = &m_aDemorecSnapshotHolders[SNAP_PREV];
|
|
|
|
m_aSnapshots[SNAP_CURRENT]->m_pSnap = (CSnapshot *)m_aDemorecSnapshotData[SNAP_CURRENT][0];
|
|
m_aSnapshots[SNAP_CURRENT]->m_pAltSnap = (CSnapshot *)m_aDemorecSnapshotData[SNAP_CURRENT][1];
|
|
m_aSnapshots[SNAP_CURRENT]->m_SnapSize = 0;
|
|
m_aSnapshots[SNAP_CURRENT]->m_Tick = -1;
|
|
|
|
m_aSnapshots[SNAP_PREV]->m_pSnap = (CSnapshot *)m_aDemorecSnapshotData[SNAP_PREV][0];
|
|
m_aSnapshots[SNAP_PREV]->m_pAltSnap = (CSnapshot *)m_aDemorecSnapshotData[SNAP_PREV][1];
|
|
m_aSnapshots[SNAP_PREV]->m_SnapSize = 0;
|
|
m_aSnapshots[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::DemoRecorder_Start(const char *pFilename, bool WithTimestamp)
|
|
{
|
|
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.Start(Storage(), m_pConsole, aFilename, GameClient()->NetVersion(), m_aCurrentMap, m_CurrentMapCrc, "client");
|
|
}
|
|
}
|
|
|
|
void CClient::DemoRecorder_HandleAutoStart()
|
|
{
|
|
if(g_Config.m_ClAutoDemoRecord)
|
|
{
|
|
DemoRecorder_Stop();
|
|
DemoRecorder_Start("auto/autorecord", true);
|
|
if(g_Config.m_ClAutoDemoMax)
|
|
{
|
|
// clean up auto recorded demos
|
|
CFileCollection AutoDemos;
|
|
AutoDemos.Init(Storage(), "demos/auto", "autorecord", ".demo", g_Config.m_ClAutoDemoMax);
|
|
}
|
|
}
|
|
}
|
|
|
|
void CClient::DemoRecorder_Stop()
|
|
{
|
|
m_DemoRecorder.Stop();
|
|
}
|
|
|
|
void CClient::DemoRecorder_AddDemoMarker()
|
|
{
|
|
m_DemoRecorder.AddDemoMarker();
|
|
}
|
|
|
|
void CClient::Con_Record(IConsole::IResult *pResult, void *pUserData)
|
|
{
|
|
CClient *pSelf = (CClient *)pUserData;
|
|
if(pResult->NumArguments())
|
|
pSelf->DemoRecorder_Start(pResult->GetString(0), false);
|
|
else
|
|
pSelf->DemoRecorder_Start("demo", true);
|
|
}
|
|
|
|
void CClient::Con_StopRecord(IConsole::IResult *pResult, void *pUserData)
|
|
{
|
|
CClient *pSelf = (CClient *)pUserData;
|
|
pSelf->DemoRecorder_Stop();
|
|
}
|
|
|
|
void CClient::Con_AddDemoMarker(IConsole::IResult *pResult, void *pUserData)
|
|
{
|
|
CClient *pSelf = (CClient *)pUserData;
|
|
pSelf->DemoRecorder_AddDemoMarker();
|
|
}
|
|
|
|
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::RegisterCommands()
|
|
{
|
|
m_pConsole = Kernel()->RequestInterface<IConsole>();
|
|
|
|
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", 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", CFGFLAG_CLIENT, Con_Rcon, this, "Send specified command to rcon");
|
|
m_pConsole->Register("rcon_auth", "s", CFGFLAG_CLIENT, Con_RconAuth, this, "Authenticate to rcon");
|
|
m_pConsole->Register("play", "r", CFGFLAG_CLIENT|CFGFLAG_STORE, Con_Play, this, "Play the file specified");
|
|
m_pConsole->Register("record", "?s", 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");
|
|
|
|
// used for server browser update
|
|
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);
|
|
}
|
|
|
|
static CClient *CreateClient()
|
|
{
|
|
CClient *pClient = static_cast<CClient *>(mem_alloc(sizeof(CClient), 1));
|
|
mem_zero(pClient, sizeof(CClient));
|
|
return new(pClient) CClient;
|
|
}
|
|
|
|
static bool IsTeeworldsConnectLink(const char *str)
|
|
{
|
|
const char *starter = "teeworlds:";
|
|
|
|
while(*str && *starter && *str == *starter)
|
|
{
|
|
str++;
|
|
starter++;
|
|
}
|
|
|
|
return *starter == '\0';
|
|
}
|
|
|
|
void CClient::HandleTeeworldsConnectLink(const char *clink)
|
|
{
|
|
str_copy(m_aCmdConnect, clink + sizeof("teeworlds:") - 1, sizeof(m_aCmdConnect));
|
|
}
|
|
|
|
/*
|
|
Server Time
|
|
Client Mirror Time
|
|
Client Predicted Time
|
|
|
|
Snapshot Latency
|
|
Downstream latency
|
|
|
|
Prediction Latency
|
|
Upstream latency
|
|
*/
|
|
|
|
int main(int argc, const char **argv) // ignore_convention
|
|
{
|
|
#if defined(CONF_FAMILY_WINDOWS)
|
|
for(int i = 1; i < argc; i++) // ignore_convention
|
|
{
|
|
if(str_comp("-s", argv[i]) == 0 || str_comp("--silent", argv[i]) == 0) // ignore_convention
|
|
{
|
|
FreeConsole();
|
|
break;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
bool UseDefaultConfig = false;
|
|
for(int i = 1; i < argc; i++) // ignore_convention
|
|
{
|
|
if(str_comp("-d", argv[i]) == 0 || str_comp("--default", argv[i]) == 0) // ignore_convention
|
|
{
|
|
UseDefaultConfig = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
CClient *pClient = CreateClient();
|
|
IKernel *pKernel = IKernel::Create();
|
|
pKernel->RegisterInterface(pClient);
|
|
pClient->RegisterInterfaces();
|
|
|
|
// create the components
|
|
int FlagMask = CFGFLAG_CLIENT;
|
|
IEngine *pEngine = CreateEngine("Teeworlds");
|
|
IConsole *pConsole = CreateConsole(FlagMask);
|
|
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();
|
|
|
|
{
|
|
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));
|
|
|
|
RegisterFail = RegisterFail || !pKernel->RegisterInterface(static_cast<IEngineInput*>(pEngineInput)); // register as both
|
|
RegisterFail = RegisterFail || !pKernel->RegisterInterface(static_cast<IInput*>(pEngineInput));
|
|
|
|
RegisterFail = RegisterFail || !pKernel->RegisterInterface(static_cast<IEngineTextRender*>(pEngineTextRender)); // register as both
|
|
RegisterFail = RegisterFail || !pKernel->RegisterInterface(static_cast<ITextRender*>(pEngineTextRender));
|
|
|
|
RegisterFail = RegisterFail || !pKernel->RegisterInterface(static_cast<IEngineMap*>(pEngineMap)); // register as both
|
|
RegisterFail = RegisterFail || !pKernel->RegisterInterface(static_cast<IMap*>(pEngineMap));
|
|
|
|
RegisterFail = RegisterFail || !pKernel->RegisterInterface(static_cast<IEngineMasterServer*>(pEngineMasterServer)); // register as both
|
|
RegisterFail = RegisterFail || !pKernel->RegisterInterface(static_cast<IMasterServer*>(pEngineMasterServer));
|
|
|
|
RegisterFail = RegisterFail || !pKernel->RegisterInterface(CreateEditor());
|
|
RegisterFail = RegisterFail || !pKernel->RegisterInterface(CreateGameClient());
|
|
RegisterFail = RegisterFail || !pKernel->RegisterInterface(pStorage);
|
|
|
|
if(RegisterFail)
|
|
return -1;
|
|
}
|
|
|
|
pEngine->Init();
|
|
pConfig->Init(FlagMask);
|
|
pEngineMasterServer->Init();
|
|
pEngineMasterServer->Load();
|
|
|
|
// register all console commands
|
|
pClient->RegisterCommands();
|
|
|
|
// init client's interfaces
|
|
pClient->InitInterfaces();
|
|
|
|
pKernel->RequestInterface<IGameClient>()->OnConsoleInit();
|
|
|
|
if(!UseDefaultConfig)
|
|
{
|
|
// execute config file
|
|
pConsole->ExecuteFile("settings.cfg");
|
|
|
|
// execute autoexec file
|
|
pConsole->ExecuteFile("autoexec.cfg");
|
|
|
|
// parse the command line arguments
|
|
if(argc == 2 && IsTeeworldsConnectLink(argv[1]))
|
|
pClient->HandleTeeworldsConnectLink(argv[1]);
|
|
else if(argc > 1) // ignore_convention
|
|
pConsole->ParseArguments(argc-1, &argv[1]); // ignore_convention
|
|
}
|
|
|
|
// restore empty config strings to their defaults
|
|
pConfig->RestoreStrings();
|
|
|
|
pClient->Engine()->InitLogfile();
|
|
|
|
// run the client
|
|
dbg_msg("client", "starting...");
|
|
pClient->Run();
|
|
|
|
// write down the config and quit
|
|
pConfig->Save();
|
|
|
|
// free components
|
|
mem_free(pClient);
|
|
delete pKernel;
|
|
delete pEngine;
|
|
delete pConsole;
|
|
delete pStorage;
|
|
delete pConfig;
|
|
delete pEngineSound;
|
|
delete pEngineInput;
|
|
delete pEngineTextRender;
|
|
delete pEngineMap;
|
|
delete pEngineMasterServer;
|
|
|
|
return 0;
|
|
}
|