diff options
Diffstat (limited to 'src/engine')
117 files changed, 13225 insertions, 14660 deletions
diff --git a/src/engine/client.h b/src/engine/client.h new file mode 100644 index 00000000..8e5a5577 --- /dev/null +++ b/src/engine/client.h @@ -0,0 +1,159 @@ +#ifndef ENGINE_CLIENT_H +#define ENGINE_CLIENT_H +#include "kernel.h" + +#include "message.h" + +class IClient : public IInterface +{ + MACRO_INTERFACE("client", 0) +protected: + // quick access to state of the client + int m_State; + + // quick access to time variables + int m_PrevGameTick; + int m_CurGameTick; + float m_GameIntraTick; + float m_GameTickTime; + + int m_PredTick; + float m_PredIntraTick; + + float m_LocalTime; + float m_FrameTime; + + int m_GameTickSpeed; +public: + + class CSnapItem + { + public: + int m_Type; + int m_Id; + int m_DataSize; + }; + + /* Constants: Client States + STATE_OFFLINE - The client is offline. + STATE_CONNECTING - The client is trying to connect to a server. + STATE_LOADING - The client has connected to a server and is loading resources. + STATE_ONLINE - The client is connected to a server and running the game. + STATE_DEMOPLAYBACK - The client is playing a demo + STATE_QUITING - The client is quiting. + */ + + enum + { + STATE_OFFLINE=0, + STATE_CONNECTING, + STATE_LOADING, + STATE_ONLINE, + STATE_DEMOPLAYBACK, + STATE_QUITING, + }; + + // + inline int State() const { return m_State; } + + // tick time access + inline int PrevGameTick() const { return m_PrevGameTick; } + inline int GameTick() const { return m_CurGameTick; } + inline int PredGameTick() const { return m_PredTick; } + inline float IntraGameTick() const { return m_GameIntraTick; } + inline float PredIntraGameTick() const { return m_PredIntraTick; } + inline float GameTickTime() const { return m_GameTickTime; } + inline int GameTickSpeed() const { return m_GameTickSpeed; } + + // other time access + inline float FrameTime() const { return m_FrameTime; } + inline float LocalTime() const { return m_LocalTime; } + + // actions + virtual void Connect(const char *pAddress) = 0; + virtual void Disconnect() = 0; + virtual void Quit() = 0; + virtual const char *DemoPlayer_Play(const char *pFilename) = 0; + + // networking + virtual void EnterGame() = 0; + + // + virtual int MapDownloadAmount() = 0; + virtual int MapDownloadTotalsize() = 0; + + // input + virtual int *GetInput(int Tick) = 0; + + // remote console + virtual void RconAuth(const char *pUsername, const char *pPassword) = 0; + virtual bool RconAuthed() = 0; + virtual void Rcon(const char *pLine) = 0; + + // server info + virtual void GetServerInfo(class CServerInfo *pServerInfo) = 0; + + // snapshot interface + + enum + { + SNAP_CURRENT=0, + SNAP_PREV=1 + }; + + // TODO: Refactor: should redo this a bit i think, too many virtual calls + virtual int SnapNumItems(int SnapId) = 0; + virtual void *SnapFindItem(int SnapId, int Type, int Id) = 0; + virtual void *SnapGetItem(int SnapId, int Index, CSnapItem *pItem) = 0; + virtual void SnapInvalidateItem(int SnapId, int Index) = 0; + + virtual void SnapSetStaticsize(int ItemType, int Size) = 0; + + virtual int SendMsg(CMsgPacker *pMsg, int Flags) = 0; + + template<class T> + int SendPackMsg(T *pMsg, int Flags) + { + CMsgPacker Packer(pMsg->MsgID()); + if(pMsg->Pack(&Packer)) + return -1; + return SendMsg(&Packer, Flags); + } + + // + virtual const char *UserDirectory() = 0; + + // + virtual const char *ErrorString() = 0; + virtual const char *LatestVersion() = 0; + virtual bool ConnectionProblems() = 0; +}; + +class IGameClient : public IInterface +{ + MACRO_INTERFACE("gameclient", 0) +protected: +public: + virtual void OnConsoleInit() = 0; + + virtual void OnRconLine(const char *pLine) = 0; + virtual void OnInit() = 0; + virtual void OnNewSnapshot() = 0; + virtual void OnEnterGame() = 0; + virtual void OnShutdown() = 0; + virtual void OnRender() = 0; + virtual void OnStateChange(int NewState, int OldState) = 0; + virtual void OnConnected() = 0; + virtual void OnMessage(int MsgId, CUnpacker *pUnpacker) = 0; + virtual void OnPredict() = 0; + + virtual int OnSnapInput(int *pData) = 0; + + virtual const char *GetItemName(int Type) = 0; + virtual const char *Version() = 0; + virtual const char *NetVersion() = 0; + +}; + +extern IGameClient *CreateGameClient(); +#endif diff --git a/src/engine/client/client.cpp b/src/engine/client/client.cpp new file mode 100644 index 00000000..dbcaa1ed --- /dev/null +++ b/src/engine/client/client.cpp @@ -0,0 +1,2062 @@ +// copyright (c) 2007 magnus auvinen, see licence.txt for more info + +#include <stdlib.h> // qsort +#include <stdarg.h> +#include <math.h> + +#include <base/system.h> +#include <engine/shared/engine.h> + +#include <engine/shared/protocol.h> +#include <engine/shared/snapshot.h> +#include <engine/shared/compression.h> +#include <engine/shared/network.h> +#include <engine/shared/config.h> +#include <engine/shared/packer.h> +#include <engine/shared/memheap.h> +#include <engine/shared/datafile.h> +#include <engine/shared/ringbuffer.h> +#include <engine/shared/protocol.h> + +#include <engine/shared/demorec.h> + +#include <mastersrv/mastersrv.h> +#include <versionsrv/versionsrv.h> + +#include "client.h" + + +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->QuadsText(x+2, y+h-16, 16, 1,1,1,1, pDescription); + + char aBuf[32]; + str_format(aBuf, sizeof(aBuf), "%.2f", m_Max); + pGraphics->QuadsText(x+w-8*str_length(aBuf)-8, y+2, 16, 1,1,1,1, aBuf); + + str_format(aBuf, sizeof(aBuf), "%.2f", m_Min); + pGraphics->QuadsText(x+w-8*str_length(aBuf)-8, y+h-16, 16, 1,1,1,1, aBuf); +} + + +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_FrameTime = 0.0001f; + m_FrameTimeLow = 1.0f; + m_FrameTimeHigh = 0.0f; + m_Frames = 0; + + m_GameTickSpeed = SERVER_TICK_SPEED; + + m_WindowMustRefocus = 0; + m_SnapCrcErrors = 0; + + 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_CurrentServerInfoRequestTime = -1; + + m_CurrentInput = 0; + + m_State = IClient::STATE_OFFLINE; + m_aServerAddressStr[0] = 0; + + mem_zero(m_aSnapshots, sizeof(m_aSnapshots)); + m_RecivedSnapshots = 0; + + m_VersionInfo.m_State = 0; +} + +// ----- 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) + { + 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); + Msg.AddString(GameClient()->NetVersion(), 128); + Msg.AddString(g_Config.m_PlayerName, 128); + Msg.AddString(g_Config.m_ClanName, 128); + Msg.AddString(g_Config.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); +} + +bool CClient::RconAuthed() +{ + return m_RconAuthed; +} + +void CClient::RconAuth(const char *pName, const char *pPassword) +{ + if(RconAuthed()) + return; + + CMsgPacker Msg(NETMSG_RCON_AUTH); + Msg.AddString(pName, 32); + Msg.AddString(pPassword, 32); + 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.GotProblems() != 0; +} + +void CClient::DirectInput(int *pInput, int Size) +{ + int i; + CMsgPacker Msg(NETMSG_INPUT); + Msg.AddInt(m_AckGameTick); + Msg.AddInt(m_PredTick); + 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 <= 0) + return; + + // fetch input + int Size = GameClient()->OnSnapInput(m_aInputs[m_CurrentInput].m_aData); + + if(!Size) + return; + + // pack input + CMsgPacker Msg(NETMSG_INPUT); + 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; + + SendMsgEx(&Msg, MSGFLAG_FLUSH); +} + +const char *CClient::LatestVersion() +{ + return m_aVersionStr; +} + +// TODO: OPT: do this alot smarter! +int *CClient::GetInput(int Tick) +{ + 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 (int *)m_aInputs[Best].m_aData; + return 0; +} + +// ------ state handling ----- +void CClient::SetState(int s) +{ + int Old = m_State; + if(g_Config.m_Debug) + dbg_msg("client", "state change. last=%d current=%d", m_State, s); + 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; +} + +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]; + const char *pPortStr = 0; + int Port = 8303; + + Disconnect(); + + str_copy(m_aServerAddressStr, pAddress, sizeof(m_aServerAddressStr)); + + dbg_msg("client", "connecting to '%s'", m_aServerAddressStr); + + ServerInfoRequest(); + str_copy(aBuf, m_aServerAddressStr, sizeof(aBuf)); + + for(int k = 0; aBuf[k]; k++) + { + if(aBuf[k] == ':') + { + pPortStr = &(aBuf[k+1]); + aBuf[k] = 0; + break; + } + } + + if(pPortStr) + Port = str_toint(pPortStr); + + // TODO: IPv6 support + if(net_host_lookup(aBuf, &m_ServerAddress, NETTYPE_IPV4) != 0) + dbg_msg("client", "could not find the address of %s, connecting to localhost", aBuf); + + m_RconAuthed = 0; + m_ServerAddress.port = Port; + m_NetClient.Connect(&m_ServerAddress); + SetState(IClient::STATE_CONNECTING); + + if(m_DemoRecorder.IsRecording()) + m_DemoRecorder.Stop(); + + m_InputtimeMarginGraph.Init(-150.0f, 150.0f); + m_GametimeMarginGraph.Init(-150.0f, 150.0f); +} + +void CClient::DisconnectWithReason(const char *pReason) +{ + // stop demo playback and recorder + m_DemoPlayer.Stop(); + m_DemoRecorder.Stop(); + + // + m_RconAuthed = 0; + 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) +{ + mem_copy(pServerInfo, &m_CurrentServerInfo, sizeof(m_CurrentServerInfo)); +} + +void CClient::ServerInfoRequest() +{ + mem_zero(&m_CurrentServerInfo, sizeof(m_CurrentServerInfo)); + m_CurrentServerInfoRequestTime = 0; +} + +int CClient::LoadData() +{ + m_DebugFont = Graphics()->LoadTexture("debug_font.png", 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[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) + dbg_msg("ASDFASDFASdf", "ASDFASDFASDF"); + if((char *)i >= (char *)m_aSnapshots[SnapId]->m_pSnap && (char *)i < (char *)m_aSnapshots[SnapId]->m_pSnap + m_aSnapshots[SnapId]->m_SnapSize) + dbg_msg("ASDFASDFASdf", "ASDFASDFASDF"); + i->m_TypeAndID = -1; + } +} + +void *CClient::SnapFindItem(int SnapId, int Type, int Id) +{ + // 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) +{ + 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::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()); + + 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_FrameTime*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)); + Graphics()->QuadsText(2, 2, 16, 1,1,1,1, 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, 1,1,1,1, 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, 1,1,1,1, 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, 1,1,1,1, aBuffer); + + // 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::Quit() +{ + SetState(IClient::STATE_QUITING); +} + +const char *CClient::ErrorString() +{ + 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) + { + m_pMap->Unload(); + str_format(aErrorMsg, sizeof(aErrorMsg), "map differs from the server. %08x != %08x", m_pMap->Crc(), WantedCrc); + return aErrorMsg; + } + + // stop demo recording if we loaded a new map + m_DemoRecorder.Stop(); + + dbg_msg("client", "loaded map '%s'", pFilename); + 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]; + dbg_msg("client", "loading map, map=%s wanted crc=%08x", pMapName, WantedCrc); + 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); + return pError; +} + +int CClient::PlayerScoreComp(const void *a, const void *b) +{ + CServerInfo::CPlayer *p0 = (CServerInfo::CPlayer *)a; + CServerInfo::CPlayer *p1 = (CServerInfo::CPlayer *)b; + if(p0->m_Score == p1->m_Score) + return 0; + if(p0->m_Score < p1->m_Score) + return 1; + return -1; +} + +void CClient::ProcessPacket(CNetChunk *pPacket) +{ + if(pPacket->m_ClientID == -1) + { + // connectionlesss + if(pPacket->m_DataSize == (int)(sizeof(VERSIONSRV_VERSION) + sizeof(VERSION_DATA)) && + mem_comp(pPacket->m_pData, VERSIONSRV_VERSION, sizeof(VERSIONSRV_VERSION)) == 0) + { + unsigned char *pVersionData = (unsigned char*)pPacket->m_pData + sizeof(VERSIONSRV_VERSION); + int VersionMatch = !mem_comp(pVersionData, VERSION_DATA, sizeof(VERSION_DATA)); + + dbg_msg("client/version", "version does %s (%d.%d.%d)", + VersionMatch ? "match" : "NOT match", + pVersionData[1], pVersionData[2], pVersionData[3]); + + // assume version is out of date when version-data doesn't match + if (!VersionMatch) + { + str_format(m_aVersionStr, sizeof(m_aVersionStr), "%d.%d.%d", pVersionData[1], pVersionData[2], pVersionData[3]); + } + } + + if(pPacket->m_DataSize >= (int)sizeof(SERVERBROWSE_LIST) && + mem_comp(pPacket->m_pData, SERVERBROWSE_LIST, sizeof(SERVERBROWSE_LIST)) == 0) + { + int Size = pPacket->m_DataSize-sizeof(SERVERBROWSE_LIST); + int Num = Size/sizeof(MASTERSRV_ADDR); + MASTERSRV_ADDR *pAddrs = (MASTERSRV_ADDR *)((char*)pPacket->m_pData+sizeof(SERVERBROWSE_LIST)); + int i; + + for(i = 0; i < Num; i++) + { + NETADDR Addr; + + // convert address + mem_zero(&Addr, sizeof(Addr)); + Addr.type = NETTYPE_IPV4; + Addr.ip[0] = pAddrs[i].m_aIp[0]; + Addr.ip[1] = pAddrs[i].m_aIp[1]; + Addr.ip[2] = pAddrs[i].m_aIp[2]; + Addr.ip[3] = pAddrs[i].m_aIp[3]; + Addr.port = (pAddrs[i].m_aPort[1]<<8) | pAddrs[i].m_aPort[0]; + + m_ServerBrowser.Set(Addr, IServerBrowser::SET_MASTER_ADD, -1, 0x0); + } + } + + { + int PacketType = 0; + if(pPacket->m_DataSize >= (int)sizeof(SERVERBROWSE_INFO) && mem_comp(pPacket->m_pData, SERVERBROWSE_INFO, sizeof(SERVERBROWSE_INFO)) == 0) + PacketType = 2; + + if(pPacket->m_DataSize >= (int)sizeof(SERVERBROWSE_OLD_INFO) && mem_comp(pPacket->m_pData, SERVERBROWSE_OLD_INFO, sizeof(SERVERBROWSE_OLD_INFO)) == 0) + PacketType = 1; + + if(PacketType) + { + // we got ze info + CUnpacker Up; + CServerInfo Info = {0}; + int Token = -1; + + Up.Reset((unsigned char*)pPacket->m_pData+sizeof(SERVERBROWSE_INFO), pPacket->m_DataSize-sizeof(SERVERBROWSE_INFO)); + if(PacketType >= 2) + Token = str_toint(Up.GetString()); + str_copy(Info.m_aVersion, Up.GetString(), sizeof(Info.m_aVersion)); + str_copy(Info.m_aName, Up.GetString(), sizeof(Info.m_aName)); + str_copy(Info.m_aMap, Up.GetString(), sizeof(Info.m_aMap)); + str_copy(Info.m_aGameType, Up.GetString(), sizeof(Info.m_aGameType)); + Info.m_Flags = str_toint(Up.GetString()); + Info.m_Progression = str_toint(Up.GetString()); + Info.m_NumPlayers = str_toint(Up.GetString()); + Info.m_MaxPlayers = str_toint(Up.GetString()); + + // don't add invalid info to the server browser list + if(Info.m_NumPlayers > MAX_CLIENTS || Info.m_MaxPlayers > MAX_CLIENTS) + return; + + str_format(Info.m_aAddress, sizeof(Info.m_aAddress), "%d.%d.%d.%d:%d", + pPacket->m_Address.ip[0], pPacket->m_Address.ip[1], pPacket->m_Address.ip[2], + pPacket->m_Address.ip[3], pPacket->m_Address.port); + + for(int i = 0; i < Info.m_NumPlayers; i++) + { + str_copy(Info.m_aPlayers[i].m_aName, Up.GetString(), sizeof(Info.m_aPlayers[i].m_aName)); + Info.m_aPlayers[i].m_Score = str_toint(Up.GetString()); + } + + if(!Up.Error()) + { + // sort players + qsort(Info.m_aPlayers, Info.m_NumPlayers, sizeof(*Info.m_aPlayers), PlayerScoreComp); + + if(net_addr_comp(&m_ServerAddress, &pPacket->m_Address) == 0) + { + mem_copy(&m_CurrentServerInfo, &Info, sizeof(m_CurrentServerInfo)); + m_CurrentServerInfo.m_NetAddr = m_ServerAddress; + m_CurrentServerInfoRequestTime = -1; + } + else + { + if(PacketType == 2) + m_ServerBrowser.Set(pPacket->m_Address, IServerBrowser::SET_TOKEN, Token, &Info); + else + m_ServerBrowser.Set(pPacket->m_Address, IServerBrowser::SET_OLD_INTERNET, -1, &Info); + } + } + } + } + } + else + { + 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(Msg == NETMSG_MAP_CHANGE) + { + const char *pMap = Unpacker.GetString(); + int MapCrc = Unpacker.GetInt(); + const char *pError = 0; + + if(Unpacker.Error()) + return; + + 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(pError) + DisconnectWithReason(pError); + else + { + pError = LoadMapSearch(pMap, MapCrc); + + if(!pError) + { + dbg_msg("client/network", "loading done"); + SendReady(); + GameClient()->OnConnected(); + } + else + { + str_format(m_aMapdownloadFilename, sizeof(m_aMapdownloadFilename), "downloadedmaps/%s_%08x.map", pMap, MapCrc); + + dbg_msg("client/network", "starting to download map to '%s'", m_aMapdownloadFilename); + + m_MapdownloadChunk = 0; + str_copy(m_aMapdownloadName, pMap, sizeof(m_aMapdownloadName)); + m_MapdownloadFile = Storage()->OpenFile(m_aMapdownloadFilename, IOFLAG_WRITE); + m_MapdownloadCrc = MapCrc; + m_MapdownloadTotalsize = -1; + m_MapdownloadAmount = 0; + + CMsgPacker Msg(NETMSG_REQUEST_MAP_DATA); + Msg.AddInt(m_MapdownloadChunk); + SendMsgEx(&Msg, MSGFLAG_VITAL|MSGFLAG_FLUSH); + + if(g_Config.m_Debug) + dbg_msg("client/network", "requested chunk %d", m_MapdownloadChunk); + } + } + } + else if(Msg == NETMSG_MAP_DATA) + { + int Last = Unpacker.GetInt(); + int TotalSize = Unpacker.GetInt(); + int Size = Unpacker.GetInt(); + const unsigned char *pData = Unpacker.GetRaw(Size); + + // check fior errors + if(Unpacker.Error() || Size <= 0 || TotalSize <= 0 || !m_MapdownloadFile) + return; + + io_write(m_MapdownloadFile, pData, Size); + + m_MapdownloadTotalsize = TotalSize; + m_MapdownloadAmount += Size; + + if(Last) + { + const char *pError; + dbg_msg("client/network", "download complete, loading map"); + + io_close(m_MapdownloadFile); + m_MapdownloadFile = 0; + m_MapdownloadAmount = 0; + m_MapdownloadTotalsize = -1; + + // load map + pError = LoadMap(m_aMapdownloadName, m_aMapdownloadFilename, m_MapdownloadCrc); + if(!pError) + { + dbg_msg("client/network", "loading done"); + SendReady(); + GameClient()->OnConnected(); + } + else + DisconnectWithReason(pError); + } + 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) + dbg_msg("client/network", "requested chunk %d", m_MapdownloadChunk); + } + } + else if(Msg == NETMSG_PING) + { + CMsgPacker Msg(NETMSG_PING_REPLY); + SendMsgEx(&Msg, 0); + } + else if(Msg == NETMSG_RCON_AUTH_STATUS) + { + int Result = Unpacker.GetInt(); + if(Unpacker.Error() == 0) + m_RconAuthed = Result; + } + else if(Msg == NETMSG_RCON_LINE) + { + const char *pLine = Unpacker.GetString(); + if(Unpacker.Error() == 0) + { + //dbg_msg("remote", "%s", line); + GameClient()->OnRconLine(pLine); + } + } + else if(Msg == NETMSG_PING_REPLY) + dbg_msg("client/network", "latency %.2f", (time_get() - m_PingStartTime)*1000 / (float)time_freq()); + 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()); + //st_update(&predicted_time, ); + break; + } + } + + if(Target) + m_PredictedTime.Update(&m_InputtimeMarginGraph, Target, TimeLeft, 1); + } + else if(Msg == NETMSG_SNAP || Msg == NETMSG_SNAPSINGLE || Msg == NETMSG_SNAPEMPTY) + { + //dbg_msg("client/network", "got snapshot"); + 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()) + 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]; + 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) + dbg_msg("client", "error, couldn't find the delta snapshot"); + + // 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); + + if(IntSize < 0) // failure during decompression, bail + return; + + pDeltaData = aTmpBuffer2; + DeltaSize = IntSize; + } + + // unpack delta + PurgeTick = DeltaTick; + SnapSize = m_SnapshotDelta.UnpackDelta(pDeltaShot, (CSnapshot*)aTmpBuffer3, pDeltaData, DeltaSize); + if(SnapSize < 0) + { + dbg_msg("client", "delta unpack failed!"); + return; + } + + if(Msg != NETMSG_SNAPEMPTY && ((CSnapshot*)aTmpBuffer3)->Crc() != Crc) + { + if(g_Config.m_Debug) + { + dbg_msg("client", "snapshot crc error #%d - tick=%d wantedcrc=%d gotcrc=%d compressed_size=%d delta_tick=%d", + m_SnapCrcErrors, GameTick, Crc, ((CSnapshot*)aTmpBuffer3)->Crc(), CompleteSize, DeltaTick); + } + + 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_PREV]->m_Tick; + m_SnapshotStorage.PurgeUntil(PurgeTick); + + // add new + m_SnapshotStorage.Add(GameTick, time_get(), SnapSize, (CSnapshot*)aTmpBuffer3, 1); + + // add snapshot to demo + if(m_DemoRecorder.IsRecording()) + { + + // write tick marker + /* + DEMOREC_TICKMARKER marker; + marker.tick = game_tick; + swap_endian(&marker, sizeof(int), sizeof(marker)/sizeof(int)); + demorec_record_write("TICK", sizeof(marker), &marker); + demorec_record_write("SNAP", snapsize, tmpbuffer3); + */ + + // write snapshot + m_DemoRecorder.RecordSnapshot(GameTick, aTmpBuffer3, 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); + } + + // adjust game time + { + int64 Now = m_GameTime.Get(time_get()); + int64 TickStart = GameTick*time_freq()/50; + int64 TimeLeft = (TickStart-Now)*1000 / time_freq(); + //st_update(&game_time, (game_tick-1)*time_freq()/50); + m_GameTime.Update(&m_GametimeMarginGraph, (GameTick-1)*time_freq()/50, TimeLeft, 0); + } + + // ack snapshot + m_AckGameTick = GameTick; + } + } + } + } + else + { + // game message + if(m_DemoRecorder.IsRecording()) + m_DemoRecorder.RecordMessage(pPacket->m_pData, pPacket->m_DataSize); + // demorec_record_write("MESG", pPacket->data_size, ); + + GameClient()->OnMessage(Msg, &Unpacker); + } + } +} + +void CClient::PumpNetwork() +{ + m_NetClient.Update(); + + if(State() != IClient::STATE_DEMOPLAYBACK) + { + // check for errors + if(State() != IClient::STATE_OFFLINE && m_NetClient.State() == NETSTATE_OFFLINE) + { + SetState(IClient::STATE_OFFLINE); + Disconnect(); + dbg_msg("client", "offline error='%s'", m_NetClient.ErrorString()); + } + + // + if(State() == IClient::STATE_CONNECTING && m_NetClient.State() == NETSTATE_ONLINE) + { + // we switched to online + dbg_msg("client", "connected, sending info"); + SetState(IClient::STATE_LOADING); + SendInfo(); + } + } + + // process packets + CNetChunk Packet; + while(m_NetClient.Recv(&Packet)) + ProcessPacket(&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_OFFLINE && 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; + //tg_add(&predicted_time_graph, pred_now, 0); + int PrevPredTick = (int)(PredNow*50/time_freq()); + int NewPredTick = PrevPredTick+1; + static float LastPredintra = 0; + + 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) + { + dbg_msg("client", "prediction time reset!"); + m_PredictedTime.Init(m_aSnapshots[SNAP_CURRENT]->m_Tick*time_freq()/50); + } + + if(NewPredTick > m_PredTick) + { + LastPredintra = m_PredIntraTick; + m_PredTick = NewPredTick; + Repredict = 1; + + // send input + SendInput(); + } + + LastPredintra = m_PredIntraTick; + } + + // only do sane predictions + if(Repredict) + { + if(m_PredTick > m_CurGameTick && m_PredTick < m_CurGameTick+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.Request(m_ServerAddress); + m_CurrentServerInfoRequestTime = time_get()+time_freq()*2; + } + } + + // 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) + { + dbg_msg("stress", "reconnecting!"); + Connect(g_Config.m_DbgStressServer); + ActionTaken = Now; + } + } + else + { + /*if(now > action_taken+time_freq()*(10+config.dbg_stress)) + { + dbg_msg("stress", "disconnecting!"); + Disconnect(); + action_taken = now; + }*/ + } + } + + // pump the network + PumpNetwork(); + + // update the maser server registry + MasterServer()->Update(); + + // update the server browser + m_ServerBrowser.Update(); +} + +const char *CClient::UserDirectory() +{ + static char saPath[1024] = {0}; + fs_storage_path("Teeworlds", saPath, sizeof(saPath)); + return saPath; +} + +void CClient::VersionUpdate() +{ + if(m_VersionInfo.m_State == 0) + { + m_Engine.HostLookup(&m_VersionInfo.m_VersionServeraddr, g_Config.m_ClVersionServer); + m_VersionInfo.m_State++; + } + else if(m_VersionInfo.m_State == 1) + { + 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_NetClient.Send(&Packet); + m_VersionInfo.m_State++; + } + } +} + +void CClient::InitEngine(const char *pAppname) +{ + m_Engine.Init(pAppname); +} + +void CClient::RegisterInterfaces() +{ + Kernel()->RegisterInterface(static_cast<IDemoPlayer*>(&m_DemoPlayer)); + Kernel()->RegisterInterface(static_cast<IServerBrowser*>(&m_ServerBrowser)); +} + +void CClient::InitInterfaces() +{ + // fetch interfaces + 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.SetBaseInfo(&m_NetClient, m_pGameClient->NetVersion()); +} + +void CClient::Run() +{ + int64 ReportTime = time_get(); + int64 ReportInterval = time_freq()*1; + + m_LocalStartTime = time_get(); + m_SnapshotParts = 0; + + // init graphics + if(m_pGraphics->Init() != 0) + return; + + // init font rendering + Kernel()->RequestInterface<IEngineTextRender>()->Init(); + + // init the input + Input()->Init(); + + // start refreshing addresses while we load + MasterServer()->RefreshAddresses(); + + // init the editor + m_pEditor->Init(); + + // init sound, allowed to fail + Sound()->Init(); + + // load data + if(!LoadData()) + return; + + GameClient()->OnInit(); + dbg_msg("client", "version %s", GameClient()->NetVersion()); + + // open socket + { + NETADDR BindAddr; + mem_zero(&BindAddr, sizeof(BindAddr)); + m_NetClient.Open(BindAddr, 0); + } + + // 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; + + Input()->MouseModeRelative(); + + while (1) + { + int64 FrameStartTime = time_get(); + m_Frames++; + + // + 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 + Input()->Update(); + + // 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()->KeyPressed(KEY_ESCAPE)) + { + Input()->MouseModeAbsolute(); + m_WindowMustRefocus = 1; + } + + // refocus + if(m_WindowMustRefocus && m_pGraphics->WindowActive()) + { + if(m_WindowMustRefocus < 3) + { + Input()->MouseModeAbsolute(); + m_WindowMustRefocus++; + } + + if(Input()->KeyPressed(KEY_MOUSE_1)) + { + Input()->MouseModeRelative(); + m_WindowMustRefocus = 0; + } + } + + // panic quit button + if(Input()->KeyPressed(KEY_LCTRL) && Input()->KeyPressed(KEY_LSHIFT) && Input()->KeyPressed('q')) + break; + + if(Input()->KeyPressed(KEY_LCTRL) && Input()->KeyPressed(KEY_LSHIFT) && Input()->KeyDown('d')) + g_Config.m_Debug ^= 1; + + if(Input()->KeyPressed(KEY_LCTRL) && Input()->KeyPressed(KEY_LSHIFT) && Input()->KeyDown('g')) + g_Config.m_DbgGraphs ^= 1; + + if(Input()->KeyPressed(KEY_LCTRL) && Input()->KeyPressed(KEY_LSHIFT) && Input()->KeyDown('e')) + { + g_Config.m_ClEditor = g_Config.m_ClEditor^1; + Input()->MouseModeRelative(); + } + + /* + if(!gfx_window_open()) + break; + */ + + // render + if(g_Config.m_ClEditor) + { + Update(); + m_pEditor->UpdateAndRender(); + m_pGraphics->Swap(); + } + else + { + Update(); + + if(g_Config.m_DbgStress) + { + if((m_Frames%10) == 0) + { + Render(); + m_pGraphics->Swap(); + } + } + else + { + Render(); + m_pGraphics->Swap(); + } + } + + // check conditions + if(State() == IClient::STATE_QUITING) + break; + + // beNice + if(g_Config.m_DbgStress) + thread_sleep(5); + else if(g_Config.m_ClCpuThrottle || !m_pGraphics->WindowActive()) + thread_sleep(1); + + 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_FrameTimeHigh, + 1.0f/m_FrameTimeLow, + m_NetClient.State()); + } + m_FrameTimeLow = 1; + m_FrameTimeHigh = 0; + m_Frames = 0; + ReportTime += ReportInterval; + } + + // update frametime + m_FrameTime = (time_get()-FrameStartTime)/(float)time_freq(); + if(m_FrameTime < m_FrameTimeLow) + m_FrameTimeLow = m_FrameTime; + if(m_FrameTime > m_FrameTimeHigh) + m_FrameTimeHigh = m_FrameTime; + + m_LocalTime = (time_get()-m_LocalStartTime)/(float)time_freq(); + + m_FpsGraph.Add(1.0f/m_FrameTime, 1,1,1); + } + + GameClient()->OnShutdown(); + Disconnect(); + + m_pGraphics->Shutdown(); + m_pSound->Shutdown(); +} + + +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); + pSelf->SendMsgEx(&Msg, 0); + pSelf->m_PingStartTime = time_get(); +} + +void CClient::Con_Screenshot(IConsole::IResult *pResult, void *pUserData) +{ + CClient *pSelf = (CClient *)pUserData; + pSelf->Graphics()->TakeScreenshot(); +} + +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_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); +} + +const char *CClient::DemoPlayer_Play(const char *pFilename) +{ + int Crc; + const char *pError; + Disconnect(); + m_NetClient.ResetErrorString(); + + // try to start playback + m_DemoPlayer.SetListner(this); + + if(m_DemoPlayer.Load(Storage(), pFilename)) + return "error loading demo"; + + // load map + Crc = (m_DemoPlayer.Info()->m_Header.m_aCrc[0]<<24)| + (m_DemoPlayer.Info()->m_Header.m_aCrc[1]<<16)| + (m_DemoPlayer.Info()->m_Header.m_aCrc[2]<<8)| + (m_DemoPlayer.Info()->m_Header.m_aCrc[3]); + pError = LoadMapSearch(m_DemoPlayer.Info()->m_Header.m_aMap, 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)); +} + +void CClient::Con_Record(IConsole::IResult *pResult, void *pUserData) +{ + CClient *pSelf = (CClient *)pUserData; + if(pSelf->State() != IClient::STATE_ONLINE) + dbg_msg("demorec/record", "client is not online"); + else + { + char aFilename[512]; + str_format(aFilename, sizeof(aFilename), "demos/%s.demo", pResult->GetString(0)); + pSelf->m_DemoRecorder.Start(pSelf->Storage(), aFilename, pSelf->GameClient()->NetVersion(), pSelf->m_aCurrentMap, pSelf->m_CurrentMapCrc, "client"); + } +} + +void CClient::Con_StopRecord(IConsole::IResult *pResult, void *pUserData) +{ + CClient *pSelf = (CClient *)pUserData; + pSelf->m_DemoRecorder.Stop(); +} + +void CClient::Con_ServerDummy(IConsole::IResult *pResult, void *pUserData) +{ + dbg_msg("client", "this command is not available on the client"); +} + +void CClient::RegisterCommands() +{ + m_pConsole = Kernel()->RequestInterface<IConsole>(); + // register server dummy commands for tab completion + m_pConsole->Register("kick", "i", CFGFLAG_SERVER, Con_ServerDummy, this, "Kick player with specified id"); + m_pConsole->Register("ban", "s?i", CFGFLAG_SERVER, Con_ServerDummy, this, "Ban player with ip/id for x minutes"); + m_pConsole->Register("unban", "s", CFGFLAG_SERVER, Con_ServerDummy, this, "Unban ip"); + m_pConsole->Register("bans", "", CFGFLAG_SERVER, Con_ServerDummy, this, "Show banlist"); + m_pConsole->Register("status", "", CFGFLAG_SERVER, Con_ServerDummy, this, "List players"); + m_pConsole->Register("shutdown", "", CFGFLAG_SERVER, Con_ServerDummy, this, "Shut down"); + m_pConsole->Register("record", "s", CFGFLAG_SERVER, Con_ServerDummy, this, "Record to a file"); + m_pConsole->Register("stoprecord", "", CFGFLAG_SERVER, Con_ServerDummy, this, "Stop recording"); + + m_pConsole->Register("quit", "", CFGFLAG_CLIENT, Con_Quit, this, "Quit Teeworlds"); + m_pConsole->Register("exit", "", CFGFLAG_CLIENT, Con_Quit, this, "Quit Teeworlds"); + m_pConsole->Register("minimize", "", CFGFLAG_CLIENT, 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, 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_favorite", "s", CFGFLAG_CLIENT, Con_AddFavorite, this, "Add a server as a favorite"); +} + +static CClient m_Client; + +/* + Server Time + Client Mirror Time + Client Predicted Time + + Snapshot Latency + Downstream latency + + Prediction Latency + Upstream latency +*/ + +#if defined(CONF_PLATFORM_MACOSX) +int SDL_main(int argc, const char **argv) // ignore_convention +#else +int main(int argc, const char **argv) // ignore_convention +#endif +{ + // init the engine + dbg_msg("client", "starting..."); + m_Client.InitEngine("Teeworlds"); + + IKernel *pKernel = IKernel::Create(); + pKernel->RegisterInterface(&m_Client); + m_Client.RegisterInterfaces(); + + // create the components + IConsole *pConsole = CreateConsole(); + IStorage *pStorage = CreateStorage("Teeworlds", argv[0]); // ignore_convention + IConfig *pConfig = CreateConfig(); + IEngineGraphics *pEngineGraphics = CreateEngineGraphics(); + IEngineSound *pEngineSound = CreateEngineSound(); + IEngineInput *pEngineInput = CreateEngineInput(); + IEngineTextRender *pEngineTextRender = CreateEngineTextRender(); + IEngineMap *pEngineMap = CreateEngineMap(); + IEngineMasterServer *pEngineMasterServer = CreateEngineMasterServer(); + + { + bool RegisterFail = false; + + RegisterFail = RegisterFail || !pKernel->RegisterInterface(static_cast<IConsole*>(pConsole)); + RegisterFail = RegisterFail || !pKernel->RegisterInterface(static_cast<IConfig*>(pConfig)); + + RegisterFail = RegisterFail || !pKernel->RegisterInterface(static_cast<IEngineGraphics*>(pEngineGraphics)); // register graphics as both + RegisterFail = RegisterFail || !pKernel->RegisterInterface(static_cast<IGraphics*>(pEngineGraphics)); + + 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; + } + + pConfig->Init(); + pEngineMasterServer->Init(m_Client.Engine()); + pEngineMasterServer->Load(); + + // register all console commands + m_Client.RegisterCommands(); + + pKernel->RequestInterface<IGameClient>()->OnConsoleInit(); + + // init client's interfaces + m_Client.InitInterfaces(); + + // execute autoexec file + pConsole->ExecuteFile("autoexec.cfg"); + + // parse the command line arguments + if(argc > 1) // ignore_convention + pConsole->ParseArguments(argc-1, &argv[1]); // ignore_convention + + // execute config file + pConsole->ExecuteFile("settings.cfg"); + + // run the client + m_Client.Run(); + + // write down the config and quit + pConfig->Save(); + + return 0; +} diff --git a/src/engine/client/client.h b/src/engine/client/client.h index 64ef6d9b..c7174f85 100644 --- a/src/engine/client/client.h +++ b/src/engine/client/client.h @@ -1,17 +1,291 @@ +#ifndef ENGINE_CLIENT_CLIENT_H +#define ENGINE_CLIENT_CLIENT_H -class IEngine +#include <engine/console.h> +#include <engine/editor.h> +#include <engine/graphics.h> +#include <engine/textrender.h> +#include <engine/client.h> +#include <engine/config.h> +#include <engine/serverbrowser.h> +#include <engine/sound.h> +#include <engine/input.h> +#include <engine/keys.h> +#include <engine/map.h> +#include <engine/masterserver.h> +#include <engine/storage.h> + +#include <engine/shared/engine.h> +#include <engine/shared/protocol.h> +#include <engine/shared/demorec.h> +#include <engine/shared/network.h> + +#include "srvbrowse.h" + +class CGraph { public: - virtual ~IEngine() {} - virtual class IGraphics *Graphics() = 0; + enum + { + // restrictions: Must be power of two + MAX_VALUES=128, + }; + + float m_Min, m_Max; + float m_aValues[MAX_VALUES]; + float m_aColors[MAX_VALUES][3]; + int m_Index; + + void Init(float Min, float Max); + + void ScaleMax(); + void ScaleMin(); + + void Add(float v, float r, float g, float b); + void Render(IGraphics *pGraphics, int Font, float x, float y, float w, float h, const char *pDescription); }; -class IGameClient +class CSmoothTime { + int64 m_Snap; + int64 m_Current; + int64 m_Target; + + int64 m_RLast; + int64 m_TLast; + CGraph m_Graph; + + int m_SpikeCounter; + + float m_aAdjustSpeed[2]; // 0 = down, 1 = up public: - virtual ~IGameClient() {} + void Init(int64 Target); + void SetAdjustSpeed(int Direction, float Value); + + int64 Get(int64 Now); + + void UpdateInt(int64 Target); + void Update(CGraph *pGraph, int64 Target, int TimeLeft, int AdjustDirection); }; -extern IGameClient *CreateGameClient(IEngine *pEngine); + +class CClient : public IClient, public CDemoPlayer::IListner +{ + // needed interfaces + IEditor *m_pEditor; + IEngineInput *m_pInput; + IEngineGraphics *m_pGraphics; + IEngineSound *m_pSound; + IGameClient *m_pGameClient; + IEngineMap *m_pMap; + IConsole *m_pConsole; + IStorage *m_pStorage; + IEngineMasterServer *m_pMasterServer; + + enum + { + NUM_SNAPSHOT_TYPES=2, + PREDICTION_MARGIN=1000/50/2, // magic network prediction value + }; + + CNetClient m_NetClient; + CDemoPlayer m_DemoPlayer; + CDemoRecorder m_DemoRecorder; + CEngine m_Engine; + CServerBrowser m_ServerBrowser; + + char m_aServerAddressStr[256]; + + unsigned m_SnapshotParts; + int64 m_LocalStartTime; + + int m_DebugFont; + float m_FrameTimeLow; + float m_FrameTimeHigh; + int m_Frames; + NETADDR m_ServerAddress; + int m_WindowMustRefocus; + int m_SnapCrcErrors; + + int m_AckGameTick; + int m_CurrentRecvTick; + int m_RconAuthed; + + // version-checking + char m_aVersionStr[10]; + + // pinging + int64 m_PingStartTime; + + // + char m_aCurrentMap[256]; + int m_CurrentMapCrc; + + // + char m_aCmdConnect[256]; + + // map download + char m_aMapdownloadFilename[256]; + char m_aMapdownloadName[256]; + IOHANDLE m_MapdownloadFile; + int m_MapdownloadChunk; + int m_MapdownloadCrc; + int m_MapdownloadAmount; + int m_MapdownloadTotalsize; + + // time + CSmoothTime m_GameTime; + CSmoothTime m_PredictedTime; + + // input + struct // TODO: handle input better + { + int m_aData[MAX_INPUT_SIZE]; // the input data + int m_Tick; // the tick that the input is for + int64 m_PredictedTime; // prediction latency when we sent this input + int64 m_Time; + } m_aInputs[200]; + + int m_CurrentInput; + + // graphs + CGraph m_InputtimeMarginGraph; + CGraph m_GametimeMarginGraph; + CGraph m_FpsGraph; + + // the game snapshots are modifiable by the game + CSnapshotStorage m_SnapshotStorage; + CSnapshotStorage::CHolder *m_aSnapshots[NUM_SNAPSHOT_TYPES]; + + int m_RecivedSnapshots; + char m_aSnapshotIncommingData[CSnapshot::MAX_SIZE]; + + CSnapshotStorage::CHolder m_aDemorecSnapshotHolders[NUM_SNAPSHOT_TYPES]; + char *m_aDemorecSnapshotData[NUM_SNAPSHOT_TYPES][2][CSnapshot::MAX_SIZE]; + + CSnapshotDelta m_SnapshotDelta; + + // + CServerInfo m_CurrentServerInfo; + int64 m_CurrentServerInfoRequestTime; // >= 0 should request, == -1 got info + + // version info + struct + { + int m_State; + CHostLookup m_VersionServeraddr; + } m_VersionInfo; +public: + IEngineGraphics *Graphics() { return m_pGraphics; } + IEngineInput *Input() { return m_pInput; } + IEngineSound *Sound() { return m_pSound; } + IGameClient *GameClient() { return m_pGameClient; } + IEngineMasterServer *MasterServer() { return m_pMasterServer; } + IStorage *Storage() { return m_pStorage; } + + CClient(); + + // ----- send functions ----- + virtual int SendMsg(CMsgPacker *pMsg, int Flags); + + int SendMsgEx(CMsgPacker *pMsg, int Flags, bool System=true); + void SendInfo(); + void SendEnterGame(); + void SendReady(); + + virtual bool RconAuthed(); + void RconAuth(const char *pName, const char *pPassword); + virtual void Rcon(const char *pCmd); + + virtual bool ConnectionProblems(); + + void DirectInput(int *pInput, int Size); + void SendInput(); + + // TODO: OPT: do this alot smarter! + virtual int *GetInput(int Tick); + + const char *LatestVersion(); + void VersionUpdate(); + + // ------ state handling ----- + void SetState(int s); + + // called when the map is loaded and we should init for a new round + void OnEnterGame(); + virtual void EnterGame(); + + virtual void Connect(const char *pAddress); + void DisconnectWithReason(const char *pReason); + virtual void Disconnect(); + + + virtual void GetServerInfo(CServerInfo *pServerInfo); + void ServerInfoRequest(); + + int LoadData(); + + // --- + + void *SnapGetItem(int SnapId, int Index, CSnapItem *pItem); + void SnapInvalidateItem(int SnapId, int Index); + void *SnapFindItem(int SnapId, int Type, int Id); + int SnapNumItems(int SnapId); + void SnapSetStaticsize(int ItemType, int Size); + + void Render(); + void DebugRender(); + + virtual void Quit(); + + virtual const char *ErrorString(); + + const char *LoadMap(const char *pName, const char *pFilename, unsigned WantedCrc); + const char *LoadMapSearch(const char *pMapName, int WantedCrc); + + static int PlayerScoreComp(const void *a, const void *b); + + void ProcessPacket(CNetChunk *pPacket); + + virtual int MapDownloadAmount() { return m_MapdownloadAmount; } + virtual int MapDownloadTotalsize() { return m_MapdownloadTotalsize; } + + void PumpNetwork(); + + virtual void OnDemoPlayerSnapshot(void *pData, int Size); + virtual void OnDemoPlayerMessage(void *pData, int Size); + + void Update(); + + virtual const char *UserDirectory(); + + void InitEngine(const char *pAppname); + void RegisterInterfaces(); + void InitInterfaces(); + + void Run(); + + + static void Con_Connect(IConsole::IResult *pResult, void *pUserData); + static void Con_Disconnect(IConsole::IResult *pResult, void *pUserData); + static void Con_Quit(IConsole::IResult *pResult, void *pUserData); + static void Con_Minimize(IConsole::IResult *pResult, void *pUserData); + static void Con_Ping(IConsole::IResult *pResult, void *pUserData); + static void Con_Screenshot(IConsole::IResult *pResult, void *pUserData); + static void Con_Rcon(IConsole::IResult *pResult, void *pUserData); + static void Con_RconAuth(IConsole::IResult *pResult, void *pUserData); + static void Con_AddFavorite(IConsole::IResult *pResult, void *pUserData); + static void Con_Play(IConsole::IResult *pResult, void *pUserData); + static void Con_Record(IConsole::IResult *pResult, void *pUserData); + static void Con_StopRecord(IConsole::IResult *pResult, void *pUserData); + static void Con_ServerDummy(IConsole::IResult *pResult, void *pUserData); + + void RegisterCommands(); + + const char *DemoPlayer_Play(const char *pFilename); + + virtual class CEngine *Engine() { return &m_Engine; } +}; +#endif diff --git a/src/engine/client/ec_client.cpp b/src/engine/client/ec_client.cpp deleted file mode 100644 index 7ba0a2bb..00000000 --- a/src/engine/client/ec_client.cpp +++ /dev/null @@ -1,2087 +0,0 @@ -/* copyright (c) 2007 magnus auvinen, see licence.txt for more info */ - -#include <string.h> -#include <stdarg.h> -#include <stdlib.h> -#include <stdio.h> -#include <math.h> - -#include <base/system.h> -#include <engine/e_engine.h> -#include <engine/e_client_interface.h> - -#include <engine/e_protocol.h> -#include <engine/e_snapshot.h> -#include <engine/e_compression.h> -#include <engine/e_network.h> -#include <engine/e_config.h> -#include <engine/e_packer.h> -#include <engine/e_memheap.h> -#include <engine/e_datafile.h> -#include <engine/e_console.h> -#include <engine/e_ringbuffer.h> - -#include <engine/e_huffman.h> - -#include <engine/e_demorec.h> - -#include <mastersrv/mastersrv.h> -#include <versionsrv/versionsrv.h> - -#include "editor.h" -#include "graphics.h" -#include "client.h" - -static IEditor *m_pEditor = 0; -static IEngineGraphics *m_pGraphics = 0; -IEngineGraphics *Graphics() { return m_pGraphics; } - -static IGameClient *m_pGameClient = 0; - - -class CClient : public IEngine -{ -public: - virtual class IGraphics *Graphics() - { - return m_pGraphics; - } -}; - -static CClient m_Client; - -const int prediction_margin = 1000/50/2; /* magic network prediction value */ - -/* - Server Time - Client Mirror Time - Client Predicted Time - - Snapshot Latency - Downstream latency - - Prediction Latency - Upstream latency -*/ - -/* network client, must be accessible from other parts like the server browser */ -CNetClient m_NetClient; - -/* TODO: ugly, fix me */ -extern void client_serverbrowse_set(NETADDR *addr, int request, int token, SERVER_INFO *info); -extern void client_serverbrowse_save(); - -static unsigned snapshot_parts; -static int64 local_start_time; - -static int debug_font; -static float frametime = 0.0001f; -static float frametime_low = 1.0f; -static float frametime_high = 0.0f; -static int frames = 0; -static NETADDR server_address; -static int window_must_refocus = 0; -static int snapcrcerrors = 0; - -static int ack_game_tick = -1; -static int current_recv_tick = 0; -static int rcon_authed = 0; - -/* version-checking */ -static char versionstr[10] = "0"; - -/* pinging */ -static int64 ping_start_time = 0; - -/* */ -static char current_map[256] = {0}; -static int current_map_crc = 0; - -/* */ -static char cmd_connect[256] = {0}; - -/* map download */ -static char mapdownload_filename[256] = {0}; -static char mapdownload_name[256] = {0}; -static IOHANDLE mapdownload_file = 0; -static int mapdownload_chunk = 0; -static int mapdownload_crc = 0; -static int mapdownload_amount = -1; -static int mapdownload_totalsize = -1; - -/* */ -static SERVER_INFO current_server_info = {0}; -static int64 current_server_info_requesttime = -1; /* >= 0 should request, == -1 got info */ - -/* current time */ -static int current_tick = 0; -static float intratick = 0; -static float ticktime = 0; -static int prev_tick = 0; - -/* */ -/*static int predictiontime_pingspikecounter = 0; -static int gametime_pingspikecounter = 0;*/ - -/* predicted time */ -static int current_predtick = 0; -static float predintratick = 0; -static int last_input_timeleft = 0; - -static struct /* TODO: handle input better */ -{ - int data[MAX_INPUT_SIZE]; /* the input data */ - int tick; /* the tick that the input is for */ - int64 predicted_time; /* prediction latency when we sent this input */ - int64 time; -} inputs[200]; -static int current_input = 0; - -enum -{ - GRAPH_MAX=128 -}; - -typedef struct -{ - float min, max; - float values[GRAPH_MAX]; - float colors[GRAPH_MAX][3]; - int index; -} GRAPH; - -static void graph_init(GRAPH *g, float min, float max) -{ - g->min = min; - g->max = max; - g->index = 0; -} - -static void graph_scale_max(GRAPH *g) -{ - int i = 0; - g->max = 0; - for(i = 0; i < GRAPH_MAX; i++) - { - if(g->values[i] > g->max) - g->max = g->values[i]; - } -} - -static void graph_scale_min(GRAPH *g) -{ - int i = 0; - g->min = g->max; - for(i = 0; i < GRAPH_MAX; i++) - { - if(g->values[i] < g->min) - g->min = g->values[i]; - } -} - -static void graph_add(GRAPH *graph, float v, float r, float g, float b) -{ - graph->index = (graph->index+1)&(GRAPH_MAX-1); - graph->values[graph->index] = v; - graph->colors[graph->index][0] = r; - graph->colors[graph->index][1] = g; - graph->colors[graph->index][2] = b; -} - -static void graph_render(GRAPH *g, float x, float y, float w, float h, const char *description) -{ - char buf[32]; - int i; - - //m_pGraphics->BlendNormal(); - - - Graphics()->TextureSet(-1); - - m_pGraphics->QuadsBegin(); - Graphics()->SetColor(0, 0, 0, 0.75f); - Graphics()->QuadsDrawTL(x, y, w, h); - m_pGraphics->QuadsEnd(); - - Graphics()->LinesBegin(); - Graphics()->SetColor(0.95f, 0.95f, 0.95f, 1.00f); - Graphics()->LinesDraw(x, y+h/2, x+w, y+h/2); - Graphics()->SetColor(0.5f, 0.5f, 0.5f, 0.75f); - Graphics()->LinesDraw(x, y+(h*3)/4, x+w, y+(h*3)/4); - Graphics()->LinesDraw(x, y+h/4, x+w, y+h/4); - for(i = 1; i < GRAPH_MAX; i++) - { - float a0 = (i-1)/(float)GRAPH_MAX; - float a1 = i/(float)GRAPH_MAX; - int i0 = (g->index+i-1)&(GRAPH_MAX-1); - int i1 = (g->index+i)&(GRAPH_MAX-1); - - float v0 = (g->values[i0]-g->min) / (g->max-g->min); - float v1 = (g->values[i1]-g->min) / (g->max-g->min); - - Graphics()->SetColorVertex(0, g->colors[i0][0], g->colors[i0][1], g->colors[i0][2], 0.75f); - Graphics()->SetColorVertex(1, g->colors[i1][0], g->colors[i1][1], g->colors[i1][2], 0.75f); - Graphics()->LinesDraw(x+a0*w, y+h-v0*h, x+a1*w, y+h-v1*h); - - } - Graphics()->LinesEnd(); - - - Graphics()->TextureSet(debug_font); - Graphics()->QuadsText(x+2, y+h-16, 16, 1,1,1,1, description); - - str_format(buf, sizeof(buf), "%.2f", g->max); - Graphics()->QuadsText(x+w-8*strlen(buf)-8, y+2, 16, 1,1,1,1, buf); - - str_format(buf, sizeof(buf), "%.2f", g->min); - Graphics()->QuadsText(x+w-8*strlen(buf)-8, y+h-16, 16, 1,1,1,1, buf); - -} - -typedef struct -{ - int64 snap; - int64 current; - int64 target; - - int64 rlast; - int64 tlast; - GRAPH graph; - - int spikecounter; - - float adjustspeed[2]; /* 0 = down, 1 = up */ -} SMOOTHTIME; - -static void st_init(SMOOTHTIME *st, int64 target) -{ - st->snap = time_get(); - st->current = target; - st->target = target; - st->adjustspeed[0] = 0.3f; - st->adjustspeed[1] = 0.3f; - graph_init(&st->graph, 0.0f, 0.5f); -} - -static int64 st_get(SMOOTHTIME *st, int64 now) -{ - float adjust_speed, a; - int64 c = st->current + (now - st->snap); - int64 t = st->target + (now - st->snap); - int64 r; - - /* it's faster to adjust upward instead of downward */ - /* we might need to adjust these abit */ - - adjust_speed = st->adjustspeed[0]; - if(t > c) - adjust_speed = st->adjustspeed[1]; - - a = ((now-st->snap)/(float)time_freq()) * adjust_speed; - if(a > 1.0f) - a = 1.0f; - - r = c + (int64)((t-c)*a); - - graph_add(&st->graph, a+0.5f,1,1,1); - - return r; -} - -static void st_update_int(SMOOTHTIME *st, int64 target) -{ - int64 now = time_get(); - st->current = st_get(st, now); - st->snap = now; - st->target = target; -} - -static void st_update(SMOOTHTIME *st, GRAPH *graph, int64 target, int time_left, int adjust_direction) -{ - int update_timer = 1; - - if(time_left < 0) - { - int is_spike = 0; - if(time_left < -50) - { - is_spike = 1; - - st->spikecounter += 5; - if(st->spikecounter > 50) - st->spikecounter = 50; - } - - if(is_spike && st->spikecounter < 15) - { - /* ignore this ping spike */ - update_timer = 0; - graph_add(graph, time_left, 1,1,0); - } - else - { - graph_add(graph, time_left, 1,0,0); - if(st->adjustspeed[adjust_direction] < 30.0f) - st->adjustspeed[adjust_direction] *= 2.0f; - } - } - else - { - if(st->spikecounter) - st->spikecounter--; - - graph_add(graph, time_left, 0,1,0); - - st->adjustspeed[adjust_direction] *= 0.95f; - if(st->adjustspeed[adjust_direction] < 2.0f) - st->adjustspeed[adjust_direction] = 2.0f; - } - - last_input_timeleft = time_left; - - if(update_timer) - st_update_int(st, target); -} - -static SMOOTHTIME game_time; -static SMOOTHTIME predicted_time; - -/* graphs */ -static GRAPH inputtime_margin_graph; -static GRAPH gametime_margin_graph; -static GRAPH fps_graph; - -/* -- snapshot handling --- */ -enum -{ - NUM_SNAPSHOT_TYPES=2 -}; - -/* the game snapshots are modifiable by the game */ -CSnapshotStorage snapshot_storage; -static CSnapshotStorage::CHolder *snapshots[NUM_SNAPSHOT_TYPES] = {0, 0}; - -static int recived_snapshots = 0; -static char snapshot_incomming_data[CSnapshot::MAX_SIZE]; - -static CSnapshotStorage::CHolder demorec_snapshotholders[NUM_SNAPSHOT_TYPES]; -static char *demorec_snapshotdata[NUM_SNAPSHOT_TYPES][2][CSnapshot::MAX_SIZE]; - -/* --- */ - -void *snap_get_item(int snapid, int index, SNAP_ITEM *item) -{ - CSnapshotItem *i; - dbg_assert(snapid >= 0 && snapid < NUM_SNAPSHOT_TYPES, "invalid snapid"); - i = snapshots[snapid]->m_pAltSnap->GetItem(index); - item->datasize = snapshots[snapid]->m_pAltSnap->GetItemSize(index); - item->type = i->Type(); - item->id = i->ID(); - return (void *)i->Data(); -} - -void snap_invalidate_item(int snapid, int index) -{ - CSnapshotItem *i; - dbg_assert(snapid >= 0 && snapid < NUM_SNAPSHOT_TYPES, "invalid snapid"); - i = snapshots[snapid]->m_pAltSnap->GetItem(index); - if(i) - { - if((char *)i < (char *)snapshots[snapid]->m_pAltSnap || (char *)i > (char *)snapshots[snapid]->m_pAltSnap + snapshots[snapid]->m_SnapSize) - dbg_msg("ASDFASDFASdf", "ASDFASDFASDF"); - if((char *)i >= (char *)snapshots[snapid]->m_pSnap && (char *)i < (char *)snapshots[snapid]->m_pSnap + snapshots[snapid]->m_SnapSize) - dbg_msg("ASDFASDFASdf", "ASDFASDFASDF"); - i->m_TypeAndID = -1; - } -} - -void *snap_find_item(int snapid, int type, int id) -{ - /* TODO: linear search. should be fixed. */ - int i; - - if(!snapshots[snapid]) - return 0x0; - - for(i = 0; i < snapshots[snapid]->m_pSnap->m_NumItems; i++) - { - CSnapshotItem *itm = snapshots[snapid]->m_pAltSnap->GetItem(i); - if(itm->Type() == type && itm->ID() == id) - return (void *)itm->Data(); - } - return 0x0; -} - -int snap_num_items(int snapid) -{ - dbg_assert(snapid >= 0 && snapid < NUM_SNAPSHOT_TYPES, "invalid snapid"); - if(!snapshots[snapid]) - return 0; - return snapshots[snapid]->m_pSnap->m_NumItems; -} - -/* ------ time functions ------ */ -float client_intratick() { return intratick; } -float client_predintratick() { return predintratick; } -float client_ticktime() { return ticktime; } -int client_tick() { return current_tick; } -int client_prevtick() { return prev_tick; } -int client_predtick() { return current_predtick; } -int client_tickspeed() { return SERVER_TICK_SPEED; } -float client_frametime() { return frametime; } -float client_localtime() { return (time_get()-local_start_time)/(float)(time_freq()); } - -/* ----- send functions ----- */ -int client_send_msg() -{ - const MSG_INFO *pInfo = msg_get_info(); - CNetChunk Packet; - - if(!pInfo) - return -1; - - if(client_state() == CLIENTSTATE_OFFLINE) - return 0; - - mem_zero(&Packet, sizeof(CNetChunk)); - - Packet.m_ClientID = 0; - Packet.m_pData = pInfo->data; - Packet.m_DataSize = pInfo->size; - - if(pInfo->flags&MSGFLAG_VITAL) - Packet.m_Flags |= NETSENDFLAG_VITAL; - if(pInfo->flags&MSGFLAG_FLUSH) - Packet.m_Flags |= NETSENDFLAG_FLUSH; - - if(pInfo->flags&MSGFLAG_RECORD) - { - if(demorec_isrecording()) - demorec_record_message(Packet.m_pData, Packet.m_DataSize); - } - - if(!(pInfo->flags&MSGFLAG_NOSEND)) - m_NetClient.Send(&Packet); - return 0; -} - -static void client_send_info() -{ - msg_pack_start_system(NETMSG_INFO, MSGFLAG_VITAL|MSGFLAG_FLUSH); - msg_pack_string(modc_net_version(), 128); - msg_pack_string(config.player_name, 128); - msg_pack_string(config.clan_name, 128); - msg_pack_string(config.password, 128); - msg_pack_end(); - client_send_msg(); -} - - -static void client_send_entergame() -{ - msg_pack_start_system(NETMSG_ENTERGAME, MSGFLAG_VITAL|MSGFLAG_FLUSH); - msg_pack_end(); - client_send_msg(); -} - -static void client_send_ready() -{ - msg_pack_start_system(NETMSG_READY, MSGFLAG_VITAL|MSGFLAG_FLUSH); - msg_pack_end(); - client_send_msg(); -} - -int client_rcon_authed() -{ - return rcon_authed; -} - -void client_rcon_auth(const char *name, const char *password) -{ - msg_pack_start_system(NETMSG_RCON_AUTH, MSGFLAG_VITAL); - msg_pack_string(name, 32); - msg_pack_string(password, 32); - msg_pack_end(); - client_send_msg(); -} - -void client_rcon(const char *cmd) -{ - msg_pack_start_system(NETMSG_RCON_CMD, MSGFLAG_VITAL); - msg_pack_string(cmd, 256); - msg_pack_end(); - client_send_msg(); -} - -int client_connection_problems() -{ - return m_NetClient.GotProblems(); -} - -void client_direct_input(int *input, int size) -{ - int i; - msg_pack_start_system(NETMSG_INPUT, 0); - msg_pack_int(ack_game_tick); - msg_pack_int(current_predtick); - msg_pack_int(size); - - for(i = 0; i < size/4; i++) - msg_pack_int(input[i]); - - msg_pack_end(); - client_send_msg(); -} - - -static void client_send_input() -{ - int64 now = time_get(); - int i, size; - - if(current_predtick <= 0) - return; - - /* fetch input */ - size = modc_snap_input(inputs[current_input].data); - - if(!size) - return; - - /* pack input */ - msg_pack_start_system(NETMSG_INPUT, MSGFLAG_FLUSH); - msg_pack_int(ack_game_tick); - msg_pack_int(current_predtick); - msg_pack_int(size); - - inputs[current_input].tick = current_predtick; - inputs[current_input].predicted_time = st_get(&predicted_time, now); - inputs[current_input].time = now; - - /* pack it */ - for(i = 0; i < size/4; i++) - msg_pack_int(inputs[current_input].data[i]); - - current_input++; - current_input%=200; - - msg_pack_end(); - client_send_msg(); -} - -const char *client_latestversion() -{ - return versionstr; -} - -/* TODO: OPT: do this alot smarter! */ -int *client_get_input(int tick) -{ - int i; - int best = -1; - for(i = 0; i < 200; i++) - { - if(inputs[i].tick <= tick && (best == -1 || inputs[best].tick < inputs[i].tick)) - best = i; - } - - if(best != -1) - return (int *)inputs[best].data; - return 0; -} - -/* ------ state handling ----- */ -static int state = CLIENTSTATE_OFFLINE; -int client_state() { return state; } -static void client_set_state(int s) -{ - int old = state; - if(config.debug) - dbg_msg("client", "state change. last=%d current=%d", state, s); - state = s; - if(old != s) - modc_statechange(state, old); -} - -/* called when the map is loaded and we should init for a new round */ -static void client_on_enter_game() -{ - /* reset input */ - int i; - for(i = 0; i < 200; i++) - inputs[i].tick = -1; - current_input = 0; - - /* reset snapshots */ - snapshots[SNAP_CURRENT] = 0; - snapshots[SNAP_PREV] = 0; - snapshot_storage.PurgeAll(); - recived_snapshots = 0; - snapshot_parts = 0; - current_predtick = 0; - current_recv_tick = 0; -} - -void client_entergame() -{ - if(state == CLIENTSTATE_DEMOPLAYBACK) - return; - - /* now we will wait for two snapshots */ - /* to finish the connection */ - client_send_entergame(); - client_on_enter_game(); -} - -void client_connect(const char *server_address_str) -{ - char buf[512]; - const char *port_str = 0; - int k; - int port = 8303; - - client_disconnect(); - - dbg_msg("client", "connecting to '%s'", server_address_str); - - //client_serverinfo_request(); - str_copy(buf, server_address_str, sizeof(buf)); - - for(k = 0; buf[k]; k++) - { - if(buf[k] == ':') - { - port_str = &(buf[k+1]); - buf[k] = 0; - break; - } - } - - if(port_str) - port = atoi(port_str); - - /* TODO: IPv6 support */ - if(net_host_lookup(buf, &server_address, NETTYPE_IPV4) != 0) - dbg_msg("client", "could not find the address of %s, connecting to localhost", buf); - - rcon_authed = 0; - server_address.port = port; - m_NetClient.Connect(&server_address); - client_set_state(CLIENTSTATE_CONNECTING); - - graph_init(&inputtime_margin_graph, -150.0f, 150.0f); - graph_init(&gametime_margin_graph, -150.0f, 150.0f); -} - -void client_disconnect_with_reason(const char *reason) -{ - /* stop demo playback */ - demorec_playback_stop(); - - /* */ - rcon_authed = 0; - m_NetClient.Disconnect(reason); - client_set_state(CLIENTSTATE_OFFLINE); - map_unload(); - - /* disable all downloads */ - mapdownload_chunk = 0; - if(mapdownload_file) - io_close(mapdownload_file); - mapdownload_file = 0; - mapdownload_crc = 0; - mapdownload_totalsize = -1; - mapdownload_amount = 0; - - /* clear the current server info */ - mem_zero(¤t_server_info, sizeof(current_server_info)); - mem_zero(&server_address, sizeof(server_address)); - - /* clear snapshots */ - snapshots[SNAP_CURRENT] = 0; - snapshots[SNAP_PREV] = 0; - recived_snapshots = 0; -} - -void client_disconnect() -{ - client_disconnect_with_reason(0); -} - - -void client_serverinfo(SERVER_INFO *serverinfo) -{ - mem_copy(serverinfo, ¤t_server_info, sizeof(current_server_info)); -} - -void client_serverinfo_request() -{ - mem_zero(¤t_server_info, sizeof(current_server_info)); - current_server_info_requesttime = 0; -} - -static int client_load_data() -{ - debug_font = Graphics()->LoadTexture("debug_font.png", IMG_AUTO, TEXLOAD_NORESAMPLE); - return 1; -} - -extern int snapshot_data_rate[0xffff]; -extern int snapshot_data_updates[0xffff]; - -const char *modc_getitemname(int type); - -static void client_debug_render() -{ - static NETSTATS prev, current; - static int64 last_snap = 0; - static float frametime_avg = 0; - int64 now = time_get(); - char buffer[512]; - - if(!config.debug) - return; - - //m_pGraphics->BlendNormal(); - Graphics()->TextureSet(debug_font); - Graphics()->MapScreen(0,0,Graphics()->ScreenWidth(),Graphics()->ScreenHeight()); - - if(time_get()-last_snap > time_freq()) - { - last_snap = time_get(); - prev = current; - net_stats(¤t); - } - - /* - eth = 14 - ip = 20 - udp = 8 - total = 42 - */ - frametime_avg = frametime_avg*0.9f + frametime*0.1f; - str_format(buffer, sizeof(buffer), "ticks: %8d %8d mem %dk %d gfxmem: N/A fps: %3d", - current_tick, current_predtick, - mem_stats()->allocated/1024, - mem_stats()->total_allocations, - /*gfx_memory_usage()/1024, */ // TODO: Refactor: Reenable this - (int)(1.0f/frametime_avg)); - Graphics()->QuadsText(2, 2, 16, 1,1,1,1, buffer); - - - { - int send_packets = (current.sent_packets-prev.sent_packets); - int send_bytes = (current.sent_bytes-prev.sent_bytes); - int send_total = send_bytes + send_packets*42; - int recv_packets = (current.recv_packets-prev.recv_packets); - int recv_bytes = (current.recv_bytes-prev.recv_bytes); - int recv_total = recv_bytes + recv_packets*42; - - if(!send_packets) send_packets++; - if(!recv_packets) recv_packets++; - str_format(buffer, sizeof(buffer), "send: %3d %5d+%4d=%5d (%3d kbps) avg: %5d\nrecv: %3d %5d+%4d=%5d (%3d kbps) avg: %5d", - send_packets, send_bytes, send_packets*42, send_total, (send_total*8)/1024, send_bytes/send_packets, - recv_packets, recv_bytes, recv_packets*42, recv_total, (recv_total*8)/1024, recv_bytes/recv_packets); - Graphics()->QuadsText(2, 14, 16, 1,1,1,1, buffer); - } - - /* render rates */ - { - int y = 0; - int i; - for(i = 0; i < 256; i++) - { - if(snapshot_data_rate[i]) - { - str_format(buffer, sizeof(buffer), "%4d %20s: %8d %8d %8d", i, modc_getitemname(i), snapshot_data_rate[i]/8, snapshot_data_updates[i], - (snapshot_data_rate[i]/snapshot_data_updates[i])/8); - Graphics()->QuadsText(2, 100+y*12, 16, 1,1,1,1, buffer); - y++; - } - } - } - - str_format(buffer, sizeof(buffer), "pred: %d ms %3.2f", - (int)((st_get(&predicted_time, now)-st_get(&game_time, now))*1000/(float)time_freq()), - predicted_time.adjustspeed[1]); - Graphics()->QuadsText(2, 70, 16, 1,1,1,1, buffer); - - /* render graphs */ - if(config.dbg_graphs) - { - //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; - - graph_scale_max(&fps_graph); - graph_scale_min(&fps_graph); - graph_render(&fps_graph, x, sp*5, w, h, "FPS"); - graph_render(&inputtime_margin_graph, x, sp*5+h+sp, w, h, "Prediction Margin"); - graph_render(&gametime_margin_graph, x, sp*5+h+sp+h+sp, w, h, "Gametime Margin"); - } -} - -void client_quit() -{ - client_set_state(CLIENTSTATE_QUITING); -} - -const char *client_error_string() -{ - return m_NetClient.ErrorString(); -} - -static void client_render() -{ - if(config.gfx_clear) - Graphics()->Clear(1,1,0); - - modc_render(); - client_debug_render(); -} - -static const char *client_load_map(const char *name, const char *filename, int wanted_crc) -{ - static char errormsg[128]; - DATAFILE *df; - int crc; - - client_set_state(CLIENTSTATE_LOADING); - - df = datafile_load(filename); - if(!df) - { - str_format(errormsg, sizeof(errormsg), "map '%s' not found", filename); - return errormsg; - } - - /* get the crc of the map */ - crc = datafile_crc(filename); - if(crc != wanted_crc) - { - datafile_unload(df); - str_format(errormsg, sizeof(errormsg), "map differs from the server. %08x != %08x", crc, wanted_crc); - return errormsg; - } - - // stop demo recording if we loaded a new map - demorec_record_stop(); - - dbg_msg("client", "loaded map '%s'", filename); - recived_snapshots = 0; - map_set(df); - - str_copy(current_map, name, sizeof(current_map)); - current_map_crc = crc; - - return NULL; -} - -static const char *client_load_map_search(const char *mapname, int wanted_crc) -{ - const char *error = 0; - char buf[512]; - dbg_msg("client", "loading map, map=%s wanted crc=%08x", mapname, wanted_crc); - client_set_state(CLIENTSTATE_LOADING); - - /* try the normal maps folder */ - str_format(buf, sizeof(buf), "maps/%s.map", mapname); - error = client_load_map(mapname, buf, wanted_crc); - if(!error) - return error; - - /* try the downloaded maps */ - str_format(buf, sizeof(buf), "downloadedmaps/%s_%8x.map", mapname, wanted_crc); - error = client_load_map(mapname, buf, wanted_crc); - return error; -} - -static int player_score_comp(const void *a, const void *b) -{ - SERVER_INFO_PLAYER *p0 = (SERVER_INFO_PLAYER *)a; - SERVER_INFO_PLAYER *p1 = (SERVER_INFO_PLAYER *)b; - if(p0->score == p1->score) - return 0; - if(p0->score < p1->score) - return 1; - return -1; -} - -static void client_process_packet(CNetChunk *pPacket) -{ - if(pPacket->m_ClientID == -1) - { - /* connectionlesss */ - if(pPacket->m_DataSize == (int)(sizeof(VERSIONSRV_VERSION) + sizeof(VERSION_DATA)) && - memcmp(pPacket->m_pData, VERSIONSRV_VERSION, sizeof(VERSIONSRV_VERSION)) == 0) - { - unsigned char *versiondata = (unsigned char*)pPacket->m_pData + sizeof(VERSIONSRV_VERSION); - int version_match = !memcmp(versiondata, VERSION_DATA, sizeof(VERSION_DATA)); - - dbg_msg("client/version", "version does %s (%d.%d.%d)", - version_match ? "match" : "NOT match", - versiondata[1], versiondata[2], versiondata[3]); - - /* assume version is out of date when version-data doesn't match */ - if (!version_match) - { - sprintf(versionstr, "%d.%d.%d", versiondata[1], versiondata[2], versiondata[3]); - } - } - - if(pPacket->m_DataSize >= (int)sizeof(SERVERBROWSE_LIST) && - memcmp(pPacket->m_pData, SERVERBROWSE_LIST, sizeof(SERVERBROWSE_LIST)) == 0) - { - int size = pPacket->m_DataSize-sizeof(SERVERBROWSE_LIST); - int num = size/sizeof(MASTERSRV_ADDR); - MASTERSRV_ADDR *addrs = (MASTERSRV_ADDR *)((char*)pPacket->m_pData+sizeof(SERVERBROWSE_LIST)); - int i; - - for(i = 0; i < num; i++) - { - NETADDR addr; - - /* convert address */ - mem_zero(&addr, sizeof(addr)); - addr.type = NETTYPE_IPV4; - addr.ip[0] = addrs[i].ip[0]; - addr.ip[1] = addrs[i].ip[1]; - addr.ip[2] = addrs[i].ip[2]; - addr.ip[3] = addrs[i].ip[3]; - addr.port = (addrs[i].port[1]<<8) | addrs[i].port[0]; - - client_serverbrowse_set(&addr, BROWSESET_MASTER_ADD, -1, NULL); - } - } - - { - int packet_type = 0; - if(pPacket->m_DataSize >= (int)sizeof(SERVERBROWSE_INFO) && memcmp(pPacket->m_pData, SERVERBROWSE_INFO, sizeof(SERVERBROWSE_INFO)) == 0) - packet_type = 2; - - if(pPacket->m_DataSize >= (int)sizeof(SERVERBROWSE_OLD_INFO) && memcmp(pPacket->m_pData, SERVERBROWSE_OLD_INFO, sizeof(SERVERBROWSE_OLD_INFO)) == 0) - packet_type = 1; - - if(packet_type) - { - /* we got ze info */ - CUnpacker up; - SERVER_INFO info = {0}; - int i; - int token = -1; - - up.Reset((unsigned char*)pPacket->m_pData+sizeof(SERVERBROWSE_INFO), pPacket->m_DataSize-sizeof(SERVERBROWSE_INFO)); - if(packet_type >= 2) - token = atol(up.GetString()); - str_copy(info.version, up.GetString(), sizeof(info.version)); - str_copy(info.name, up.GetString(), sizeof(info.name)); - str_copy(info.map, up.GetString(), sizeof(info.map)); - str_copy(info.gametype, up.GetString(), sizeof(info.gametype)); - info.flags = atol(up.GetString()); - info.progression = atol(up.GetString()); - info.num_players = atol(up.GetString()); - info.max_players = atol(up.GetString()); - str_format(info.address, sizeof(info.address), "%d.%d.%d.%d:%d", - pPacket->m_Address.ip[0], pPacket->m_Address.ip[1], pPacket->m_Address.ip[2], - pPacket->m_Address.ip[3], pPacket->m_Address.port); - - for(i = 0; i < info.num_players; i++) - { - str_copy(info.players[i].name, up.GetString(), sizeof(info.players[i].name)); - info.players[i].score = atol(up.GetString()); - } - - if(!up.Error()) - { - /* sort players */ - qsort(info.players, info.num_players, sizeof(*info.players), player_score_comp); - - if(net_addr_comp(&server_address, &pPacket->m_Address) == 0) - { - mem_copy(¤t_server_info, &info, sizeof(current_server_info)); - current_server_info.netaddr = server_address; - current_server_info_requesttime = -1; - } - else - { - if(packet_type == 2) - client_serverbrowse_set(&pPacket->m_Address, BROWSESET_TOKEN, token, &info); - else - client_serverbrowse_set(&pPacket->m_Address, BROWSESET_OLD_INTERNET, -1, &info); - } - } - } - } - } - else - { - int sys; - int msg = msg_unpack_start(pPacket->m_pData, pPacket->m_DataSize, &sys); - - if(sys) - { - /* system message */ - if(msg == NETMSG_MAP_CHANGE) - { - const char *map = msg_unpack_string(); - int map_crc = msg_unpack_int(); - const char *error = 0; - int i; - - if(msg_unpack_error()) - return; - - for(i = 0; map[i]; i++) /* protect the player from nasty map names */ - { - if(map[i] == '/' || map[i] == '\\') - error = "strange character in map name"; - } - - if(error) - client_disconnect_with_reason(error); - else - { - error = client_load_map_search(map, map_crc); - - if(!error) - { - dbg_msg("client/network", "loading done"); - client_send_ready(); - modc_connected(); - } - else - { - str_format(mapdownload_filename, sizeof(mapdownload_filename), "downloadedmaps/%s_%08x.map", map, map_crc); - - dbg_msg("client/network", "starting to download map to '%s'", mapdownload_filename); - - mapdownload_chunk = 0; - str_copy(mapdownload_name, map, sizeof(mapdownload_name)); - mapdownload_file = engine_openfile(mapdownload_filename, IOFLAG_WRITE); - mapdownload_crc = map_crc; - mapdownload_totalsize = -1; - mapdownload_amount = 0; - - msg_pack_start_system(NETMSG_REQUEST_MAP_DATA, MSGFLAG_VITAL|MSGFLAG_FLUSH); - msg_pack_int(mapdownload_chunk); - msg_pack_end(); - client_send_msg(); - - if(config.debug) - dbg_msg("client/network", "requested chunk %d", mapdownload_chunk); - } - } - } - else if(msg == NETMSG_MAP_DATA) - { - int last = msg_unpack_int(); - int total_size = msg_unpack_int(); - int size = msg_unpack_int(); - const unsigned char *data = msg_unpack_raw(size); - - /* check fior errors */ - if(msg_unpack_error() || size <= 0 || total_size <= 0 || !mapdownload_file) - return; - - io_write(mapdownload_file, data, size); - - mapdownload_totalsize = total_size; - mapdownload_amount += size; - - if(last) - { - const char *error; - dbg_msg("client/network", "download complete, loading map"); - - io_close(mapdownload_file); - mapdownload_file = 0; - mapdownload_amount = 0; - mapdownload_totalsize = -1; - - /* load map */ - error = client_load_map(mapdownload_name, mapdownload_filename, mapdownload_crc); - if(!error) - { - dbg_msg("client/network", "loading done"); - client_send_ready(); - modc_connected(); - } - else - client_disconnect_with_reason(error); - } - else - { - /* request new chunk */ - mapdownload_chunk++; - msg_pack_start_system(NETMSG_REQUEST_MAP_DATA, MSGFLAG_VITAL|MSGFLAG_FLUSH); - msg_pack_int(mapdownload_chunk); - msg_pack_end(); - client_send_msg(); - - if(config.debug) - dbg_msg("client/network", "requested chunk %d", mapdownload_chunk); - } - } - else if(msg == NETMSG_PING) - { - msg_pack_start_system(NETMSG_PING_REPLY, 0); - msg_pack_end(); - client_send_msg(); - } - else if(msg == NETMSG_RCON_AUTH_STATUS) - { - int result = msg_unpack_int(); - if(msg_unpack_error() == 0) - rcon_authed = result; - } - else if(msg == NETMSG_RCON_LINE) - { - const char *line = msg_unpack_string(); - if(msg_unpack_error() == 0) - { - /*dbg_msg("remote", "%s", line);*/ - modc_rcon_line(line); - } - } - else if(msg == NETMSG_PING_REPLY) - dbg_msg("client/network", "latency %.2f", (time_get() - ping_start_time)*1000 / (float)time_freq()); - else if(msg == NETMSG_INPUTTIMING) - { - int input_predtick = msg_unpack_int(); - int time_left = msg_unpack_int(); - - /* adjust our prediction time */ - int k; - int64 target = 0; - for(k = 0; k < 200; k++) - { - if(inputs[k].tick == input_predtick) - { - target = inputs[k].predicted_time + (time_get() - inputs[k].time); - target = target - (int64)(((time_left-prediction_margin)/1000.0f)*time_freq()); - //st_update(&predicted_time, ); - break; - } - } - - if(target) - st_update(&predicted_time, &inputtime_margin_graph, target, time_left, 1); - } - else if(msg == NETMSG_SNAP || msg == NETMSG_SNAPSINGLE || msg == NETMSG_SNAPEMPTY) - { - /*dbg_msg("client/network", "got snapshot"); */ - int num_parts = 1; - int part = 0; - int game_tick = msg_unpack_int(); - int delta_tick = game_tick-msg_unpack_int(); - int part_size = 0; - int crc = 0; - int complete_size = 0; - const char *data = 0; - - /* we are not allowed to process snapshot yet */ - if(client_state() < CLIENTSTATE_LOADING) - return; - - if(msg == NETMSG_SNAP) - { - num_parts = msg_unpack_int(); - part = msg_unpack_int(); - } - - if(msg != NETMSG_SNAPEMPTY) - { - crc = msg_unpack_int(); - part_size = msg_unpack_int(); - } - - data = (const char *)msg_unpack_raw(part_size); - - if(msg_unpack_error()) - return; - - if(game_tick >= current_recv_tick) - { - if(game_tick != current_recv_tick) - { - snapshot_parts = 0; - current_recv_tick = game_tick; - } - - /* TODO: clean this up abit */ - mem_copy((char*)snapshot_incomming_data + part*MAX_SNAPSHOT_PACKSIZE, data, part_size); - snapshot_parts |= 1<<part; - - if(snapshot_parts == (unsigned)((1<<num_parts)-1)) - { - static CSnapshot emptysnap; - CSnapshot *deltashot = &emptysnap; - int purgetick; - void *deltadata; - int deltasize; - unsigned char tmpbuffer2[CSnapshot::MAX_SIZE]; - unsigned char tmpbuffer3[CSnapshot::MAX_SIZE]; - int snapsize; - - complete_size = (num_parts-1) * MAX_SNAPSHOT_PACKSIZE + part_size; - - /* reset snapshoting */ - snapshot_parts = 0; - - /* find snapshot that we should use as delta */ - emptysnap.m_DataSize = 0; - emptysnap.m_NumItems = 0; - - /* find delta */ - if(delta_tick >= 0) - { - int deltashot_size = snapshot_storage.Get(delta_tick, 0, &deltashot, 0); - - if(deltashot_size < 0) - { - /* couldn't find the delta snapshots that the server used */ - /* to compress this snapshot. force the server to resync */ - if(config.debug) - dbg_msg("client", "error, couldn't find the delta snapshot"); - - /* ack snapshot */ - /* TODO: combine this with the input message */ - ack_game_tick = -1; - return; - } - } - - /* decompress snapshot */ - deltadata = CSnapshot::EmptyDelta(); - deltasize = sizeof(int)*3; - - if(complete_size) - { - int intsize = intpack_decompress(snapshot_incomming_data, complete_size, tmpbuffer2); - - if(intsize < 0) /* failure during decompression, bail */ - return; - - deltadata = tmpbuffer2; - deltasize = intsize; - } - - /* unpack delta */ - purgetick = delta_tick; - snapsize = CSnapshot::UnpackDelta(deltashot, (CSnapshot*)tmpbuffer3, deltadata, deltasize); - if(snapsize < 0) - { - dbg_msg("client", "delta unpack failed!"); - return; - } - - if(msg != NETMSG_SNAPEMPTY && ((CSnapshot*)tmpbuffer3)->Crc() != crc) - { - if(config.debug) - { - dbg_msg("client", "snapshot crc error #%d - tick=%d wantedcrc=%d gotcrc=%d compressed_size=%d delta_tick=%d", - snapcrcerrors, game_tick, crc, ((CSnapshot*)tmpbuffer3)->Crc(), complete_size, delta_tick); - } - - snapcrcerrors++; - if(snapcrcerrors > 10) - { - /* to many errors, send reset */ - ack_game_tick = -1; - client_send_input(); - snapcrcerrors = 0; - } - return; - } - else - { - if(snapcrcerrors) - snapcrcerrors--; - } - - /* purge old snapshots */ - purgetick = delta_tick; - if(snapshots[SNAP_PREV] && snapshots[SNAP_PREV]->m_Tick < purgetick) - purgetick = snapshots[SNAP_PREV]->m_Tick; - if(snapshots[SNAP_CURRENT] && snapshots[SNAP_CURRENT]->m_Tick < purgetick) - purgetick = snapshots[SNAP_PREV]->m_Tick; - snapshot_storage.PurgeUntil(purgetick); - - /* add new */ - snapshot_storage.Add(game_tick, time_get(), snapsize, (CSnapshot*)tmpbuffer3, 1); - - /* add snapshot to demo */ - if(demorec_isrecording()) - { - - /* write tick marker */ - /* - DEMOREC_TICKMARKER marker; - marker.tick = game_tick; - swap_endian(&marker, sizeof(int), sizeof(marker)/sizeof(int)); - demorec_record_write("TICK", sizeof(marker), &marker); - demorec_record_write("SNAP", snapsize, tmpbuffer3); - */ - - /* write snapshot */ - demorec_record_snapshot(game_tick, tmpbuffer3, snapsize); - } - - /* apply snapshot, cycle pointers */ - recived_snapshots++; - - current_recv_tick = game_tick; - - /* we got two snapshots until we see us self as connected */ - if(recived_snapshots == 2) - { - /* start at 200ms and work from there */ - st_init(&predicted_time, game_tick*time_freq()/50); - predicted_time.adjustspeed[1] = 1000.0f; - st_init(&game_time, (game_tick-1)*time_freq()/50); - snapshots[SNAP_PREV] = snapshot_storage.m_pFirst; - snapshots[SNAP_CURRENT] = snapshot_storage.m_pLast; - local_start_time = time_get(); - client_set_state(CLIENTSTATE_ONLINE); - } - - /* adjust game time */ - { - int64 now = st_get(&game_time, time_get()); - int64 tickstart = game_tick*time_freq()/50; - int64 time_left = (tickstart-now)*1000 / time_freq(); - /*st_update(&game_time, (game_tick-1)*time_freq()/50);*/ - st_update(&game_time, &gametime_margin_graph, (game_tick-1)*time_freq()/50, time_left, 0); - } - - /* ack snapshot */ - ack_game_tick = game_tick; - } - } - } - } - else - { - /* game message */ - if(demorec_isrecording()) - demorec_record_message(pPacket->m_pData, pPacket->m_DataSize); - /* demorec_record_write("MESG", pPacket->data_size, ); */ - - modc_message(msg); - } - } -} - -int client_mapdownload_amount() { return mapdownload_amount; } -int client_mapdownload_totalsize() { return mapdownload_totalsize; } - -static void client_pump_network() -{ - - m_NetClient.Update(); - - if(client_state() != CLIENTSTATE_DEMOPLAYBACK) - { - /* check for errors */ - if(client_state() != CLIENTSTATE_OFFLINE && m_NetClient.State() == NETSTATE_OFFLINE) - { - client_set_state(CLIENTSTATE_OFFLINE); - client_disconnect(); - dbg_msg("client", "offline error='%s'", m_NetClient.ErrorString()); - } - - /* */ - if(client_state() == CLIENTSTATE_CONNECTING && m_NetClient.State() == NETSTATE_ONLINE) - { - /* we switched to online */ - dbg_msg("client", "connected, sending info"); - client_set_state(CLIENTSTATE_LOADING); - client_send_info(); - } - } - - /* process packets */ - CNetChunk Packet; - while(m_NetClient.Recv(&Packet)) - client_process_packet(&Packet); -} - -static void client_democallback_snapshot(void *pData, int Size) -{ - /* update ticks, they could have changed */ - const DEMOREC_PLAYBACKINFO *info = demorec_playback_info(); - CSnapshotStorage::CHolder *temp; - current_tick = info->current_tick; - prev_tick = info->previous_tick; - - /* handle snapshots */ - temp = snapshots[SNAP_PREV]; - snapshots[SNAP_PREV] = snapshots[SNAP_CURRENT]; - snapshots[SNAP_CURRENT] = temp; - - mem_copy(snapshots[SNAP_CURRENT]->m_pSnap, pData, Size); - mem_copy(snapshots[SNAP_CURRENT]->m_pAltSnap, pData, Size); - - modc_newsnapshot(); - /*modc_predict();*/ -} - -static void client_democallback_message(void *data, int size) -{ - int sys = 0; - int msg = msg_unpack_start(data, size, &sys); - if(!sys) - modc_message(msg); -} - - -const DEMOPLAYBACK_INFO *client_demoplayer_getinfo() -{ - static DEMOPLAYBACK_INFO ret; - const DEMOREC_PLAYBACKINFO *info = demorec_playback_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 client_demoplayer_setpos(float percent) -{ - demorec_playback_set(percent); -} - -void client_demoplayer_setspeed(float speed) -{ - demorec_playback_setspeed(speed); -} - -void client_demoplayer_setpause(int paused) -{ - if(paused) - demorec_playback_pause(); - else - demorec_playback_unpause(); -} - -static void client_update() -{ - if(client_state() == CLIENTSTATE_DEMOPLAYBACK) - { - demorec_playback_update(); - if(demorec_isplaying()) - { - /* update timers */ - const DEMOREC_PLAYBACKINFO *info = demorec_playback_info(); - current_tick = info->current_tick; - prev_tick = info->previous_tick; - intratick = info->intratick; - ticktime = info->ticktime; - } - else - { - /* disconnect on error */ - client_disconnect(); - } - } - else if(client_state() != CLIENTSTATE_OFFLINE && recived_snapshots >= 3) - { - /* switch snapshot */ - int repredict = 0; - int64 freq = time_freq(); - int64 now = st_get(&game_time, time_get()); - int64 pred_now = st_get(&predicted_time, time_get()); - - while(1) - { - CSnapshotStorage::CHolder *cur = snapshots[SNAP_CURRENT]; - int64 tickstart = (cur->m_Tick)*time_freq()/50; - - if(tickstart < now) - { - CSnapshotStorage::CHolder *next = snapshots[SNAP_CURRENT]->m_pNext; - if(next) - { - snapshots[SNAP_PREV] = snapshots[SNAP_CURRENT]; - snapshots[SNAP_CURRENT] = next; - - /* set ticks */ - current_tick = snapshots[SNAP_CURRENT]->m_Tick; - prev_tick = snapshots[SNAP_PREV]->m_Tick; - - if(snapshots[SNAP_CURRENT] && snapshots[SNAP_PREV]) - { - modc_newsnapshot(); - repredict = 1; - } - } - else - break; - } - else - break; - } - - if(snapshots[SNAP_CURRENT] && snapshots[SNAP_PREV]) - { - int64 curtick_start = (snapshots[SNAP_CURRENT]->m_Tick)*time_freq()/50; - int64 prevtick_start = (snapshots[SNAP_PREV]->m_Tick)*time_freq()/50; - /*tg_add(&predicted_time_graph, pred_now, 0); */ - int prev_pred_tick = (int)(pred_now*50/time_freq()); - int new_pred_tick = prev_pred_tick+1; - static float last_predintra = 0; - - intratick = (now - prevtick_start) / (float)(curtick_start-prevtick_start); - ticktime = (now - prevtick_start) / (float)freq; /*(float)SERVER_TICK_SPEED);*/ - - curtick_start = new_pred_tick*time_freq()/50; - prevtick_start = prev_pred_tick*time_freq()/50; - predintratick = (pred_now - prevtick_start) / (float)(curtick_start-prevtick_start); - - if(new_pred_tick < snapshots[SNAP_PREV]->m_Tick-SERVER_TICK_SPEED || new_pred_tick > snapshots[SNAP_PREV]->m_Tick+SERVER_TICK_SPEED) - { - dbg_msg("client", "prediction time reset!"); - st_init(&predicted_time, snapshots[SNAP_CURRENT]->m_Tick*time_freq()/50); - } - - if(new_pred_tick > current_predtick) - { - last_predintra = predintratick; - current_predtick = new_pred_tick; - repredict = 1; - - /* send input */ - client_send_input(); - } - - last_predintra = predintratick; - } - - /* only do sane predictions */ - if(repredict) - { - if(current_predtick > current_tick && current_predtick < current_tick+50) - modc_predict(); - } - - /* fetch server info if we don't have it */ - if(client_state() >= CLIENTSTATE_LOADING && - current_server_info_requesttime >= 0 && - time_get() > current_server_info_requesttime) - { - client_serverbrowse_request(&server_address); - current_server_info_requesttime = time_get()+time_freq()*2; - } - } - - /* STRESS TEST: join the server again */ - if(config.dbg_stress) - { - static int64 action_taken = 0; - int64 now = time_get(); - if(client_state() == CLIENTSTATE_OFFLINE) - { - if(now > action_taken+time_freq()*2) - { - dbg_msg("stress", "reconnecting!"); - client_connect(config.dbg_stress_server); - action_taken = now; - } - } - else - { - /*if(now > action_taken+time_freq()*(10+config.dbg_stress)) - { - dbg_msg("stress", "disconnecting!"); - client_disconnect(); - action_taken = now; - }*/ - } - } - - /* pump the network */ - client_pump_network(); - - /* update the maser server registry */ - mastersrv_update(); - - /* update the server browser */ - client_serverbrowse_update(); -} - - -static void client_versionupdate() -{ - static int state = 0; - static HOSTLOOKUP version_serveraddr; - - if(state == 0) - { - engine_hostlookup(&version_serveraddr, config.cl_version_server); - state++; - } - else if(state == 1) - { - if(jobs_status(&version_serveraddr.job) == JOBSTATUS_DONE) - { - CNetChunk Packet; - - mem_zero(&Packet, sizeof(Packet)); - - version_serveraddr.addr.port = VERSIONSRV_PORT; - - Packet.m_ClientID = -1; - Packet.m_Address = version_serveraddr.addr; - Packet.m_pData = VERSIONSRV_GETVERSION; - Packet.m_DataSize = sizeof(VERSIONSRV_GETVERSION); - Packet.m_Flags = NETSENDFLAG_CONNLESS; - - m_NetClient.Send(&Packet); - state++; - } - } -} - -static void client_run() -{ - NETADDR bindaddr; - int64 reporttime = time_get(); - int64 reportinterval = time_freq()*1; - - static PERFORMACE_INFO rootscope = {"root", 0}; - perf_start(&rootscope); - - local_start_time = time_get(); - snapshot_parts = 0; - - /* init graphics and sound */ - m_pGraphics = CreateEngineGraphics(); - if(m_pGraphics->Init() != 0) - return; - - /* start refreshing addresses while we load */ - mastersrv_refresh_addresses(); - - /* init the editor */ - m_pEditor = CreateEditor(); - m_pEditor->Init(m_pGraphics); - - /* sound is allowed to fail */ - snd_init(); - - /* load data */ - if(!client_load_data()) - return; - - /* init the mod */ - m_pGameClient = CreateGameClient(&m_Client); - modc_init(); - dbg_msg("client", "version %s", modc_net_version()); - - /* open socket */ - mem_zero(&bindaddr, sizeof(bindaddr)); - m_NetClient.Open(bindaddr, 0); - - /* connect to the server if wanted */ - /* - if(config.cl_connect[0] != 0) - client_connect(config.cl_connect); - config.cl_connect[0] = 0; - */ - - /* */ - graph_init(&fps_graph, 0.0f, 200.0f); - - /* never start with the editor */ - config.cl_editor = 0; - - inp_mouse_mode_relative(); - - while (1) - { - static PERFORMACE_INFO rootscope = {"root", 0}; - int64 frame_start_time = time_get(); - frames++; - - perf_start(&rootscope); - - /* */ - client_versionupdate(); - - /* handle pending connects */ - if(cmd_connect[0]) - { - client_connect(cmd_connect); - cmd_connect[0] = 0; - } - - /* update input */ - { - static PERFORMACE_INFO scope = {"inp_update", 0}; - perf_start(&scope); - inp_update(); - perf_end(); - } - - /* update sound */ - { - static PERFORMACE_INFO scope = {"snd_update", 0}; - perf_start(&scope); - snd_update(); - perf_end(); - } - - /* release focus */ - if(!Graphics()->WindowActive()) - { - if(window_must_refocus == 0) - inp_mouse_mode_absolute(); - window_must_refocus = 1; - } - else if (config.dbg_focus && inp_key_pressed(KEY_ESCAPE)) - { - inp_mouse_mode_absolute(); - window_must_refocus = 1; - } - - /* refocus */ - if(window_must_refocus && Graphics()->WindowActive()) - { - if(window_must_refocus < 3) - { - inp_mouse_mode_absolute(); - window_must_refocus++; - } - - if(inp_key_pressed(KEY_MOUSE_1)) - { - inp_mouse_mode_relative(); - window_must_refocus = 0; - } - } - - /* panic quit button */ - if(inp_key_pressed(KEY_LCTRL) && inp_key_pressed(KEY_LSHIFT) && inp_key_pressed('q')) - break; - - if(inp_key_pressed(KEY_LCTRL) && inp_key_pressed(KEY_LSHIFT) && inp_key_down('d')) - config.debug ^= 1; - - if(inp_key_pressed(KEY_LCTRL) && inp_key_pressed(KEY_LSHIFT) && inp_key_down('g')) - config.dbg_graphs ^= 1; - - if(inp_key_pressed(KEY_LCTRL) && inp_key_pressed(KEY_LSHIFT) && inp_key_down('e')) - { - config.cl_editor = config.cl_editor^1; - inp_mouse_mode_relative(); - } - - /* - if(!gfx_window_open()) - break; - */ - - /* render */ - if(config.cl_editor) - { - client_update(); - m_pEditor->UpdateAndRender(); - m_pGraphics->Swap(); - } - else - { - { - static PERFORMACE_INFO scope = {"client_update", 0}; - perf_start(&scope); - client_update(); - perf_end(); - } - - if(config.dbg_stress) - { - if((frames%10) == 0) - { - client_render(); - m_pGraphics->Swap(); - } - } - else - { - { - static PERFORMACE_INFO scope = {"client_render", 0}; - perf_start(&scope); - client_render(); - perf_end(); - } - - { - static PERFORMACE_INFO scope = {"gfx_swap", 0}; - perf_start(&scope); - m_pGraphics->Swap(); - perf_end(); - } - } - } - - perf_end(); - - - /* check conditions */ - if(client_state() == CLIENTSTATE_QUITING) - break; - - /* be nice */ - if(config.dbg_stress) - thread_sleep(5); - else if(config.cl_cpu_throttle || !Graphics()->WindowActive()) - thread_sleep(1); - - if(config.dbg_hitch) - { - thread_sleep(config.dbg_hitch); - config.dbg_hitch = 0; - } - - if(reporttime < time_get()) - { - if(0 && config.debug) - { - dbg_msg("client/report", "fps=%.02f (%.02f %.02f) netstate=%d", - frames/(float)(reportinterval/time_freq()), - 1.0f/frametime_high, - 1.0f/frametime_low, - m_NetClient.State()); - } - frametime_low = 1; - frametime_high = 0; - frames = 0; - reporttime += reportinterval; - perf_next(); - - if(config.dbg_pref) - perf_dump(&rootscope); - } - - /* update frametime */ - frametime = (time_get()-frame_start_time)/(float)time_freq(); - if(frametime < frametime_low) - frametime_low = frametime; - if(frametime > frametime_high) - frametime_high = frametime; - - graph_add(&fps_graph, 1.0f/frametime, 1,1,1); - } - - modc_shutdown(); - client_disconnect(); - - m_pGraphics->Shutdown(); - snd_shutdown(); -} - -void gfx_swap() -{ - m_pGraphics->Swap(); -} - -static void con_connect(void *result, void *user_data) -{ - str_copy(cmd_connect, console_arg_string(result, 0), sizeof(cmd_connect)); -} - -static void con_disconnect(void *result, void *user_data) -{ - client_disconnect(); -} - -static void con_quit(void *result, void *user_data) -{ - client_quit(); -} - -static void con_ping(void *result, void *user_data) -{ - msg_pack_start_system(NETMSG_PING, 0); - msg_pack_end(); - client_send_msg(); - ping_start_time = time_get(); -} - -static void con_screenshot(void *result, void *user_data) -{ - Graphics()->TakeScreenshot(); -} - -static void con_rcon(void *result, void *user_data) -{ - client_rcon(console_arg_string(result, 0)); -} - -static void con_rcon_auth(void *result, void *user_data) -{ - client_rcon_auth("", console_arg_string(result, 0)); -} - -static void con_addfavorite(void *result, void *user_data) -{ - NETADDR addr; - if(net_addr_from_str(&addr, console_arg_string(result, 0)) == 0) - client_serverbrowse_addfavorite(addr); -} - -const char *client_demoplayer_play(const char *filename) -{ - int crc; - const char *error; - client_disconnect(); - m_NetClient.ResetErrorString(); - - /* try to start playback */ - demorec_playback_registercallbacks(client_democallback_snapshot, client_democallback_message); - - if(demorec_playback_load(filename)) - return "error loading demo"; - - /* load map */ - crc = (demorec_playback_info()->header.crc[0]<<24)| - (demorec_playback_info()->header.crc[1]<<16)| - (demorec_playback_info()->header.crc[2]<<8)| - (demorec_playback_info()->header.crc[3]); - error = client_load_map_search(demorec_playback_info()->header.map, crc); - if(error) - { - client_disconnect_with_reason(error); - return error; - } - - modc_connected(); - - /* setup buffers */ - mem_zero(demorec_snapshotdata, sizeof(demorec_snapshotdata)); - - snapshots[SNAP_CURRENT] = &demorec_snapshotholders[SNAP_CURRENT]; - snapshots[SNAP_PREV] = &demorec_snapshotholders[SNAP_PREV]; - - snapshots[SNAP_CURRENT]->m_pSnap = (CSnapshot *)demorec_snapshotdata[SNAP_CURRENT][0]; - snapshots[SNAP_CURRENT]->m_pAltSnap = (CSnapshot *)demorec_snapshotdata[SNAP_CURRENT][1]; - snapshots[SNAP_CURRENT]->m_SnapSize = 0; - snapshots[SNAP_CURRENT]->m_Tick = -1; - - snapshots[SNAP_PREV]->m_pSnap = (CSnapshot *)demorec_snapshotdata[SNAP_PREV][0]; - snapshots[SNAP_PREV]->m_pAltSnap = (CSnapshot *)demorec_snapshotdata[SNAP_PREV][1]; - snapshots[SNAP_PREV]->m_SnapSize = 0; - snapshots[SNAP_PREV]->m_Tick = -1; - - /* enter demo playback state */ - client_set_state(CLIENTSTATE_DEMOPLAYBACK); - - demorec_playback_play(); - modc_entergame(); - - return 0; -} - -static void con_play(void *result, void *user_data) -{ - client_demoplayer_play(console_arg_string(result, 0)); -} - -static void con_record(void *result, void *user_data) -{ - if(state != CLIENTSTATE_ONLINE) - dbg_msg("demorec/record", "client is not online"); - else - { - char filename[512]; - str_format(filename, sizeof(filename), "demos/%s.demo", console_arg_string(result, 0)); - demorec_record_start(filename, modc_net_version(), current_map, current_map_crc, "client"); - } -} - -static void con_stoprecord(void *result, void *user_data) -{ - demorec_record_stop(); -} - -static void con_serverdummy(void *result, void *user_data) -{ - dbg_msg("client", "this command is not available on the client"); -} - -static void client_register_commands() -{ - MACRO_REGISTER_COMMAND("quit", "", CFGFLAG_CLIENT, con_quit, 0x0, "Quit Teeworlds"); - MACRO_REGISTER_COMMAND("exit", "", CFGFLAG_CLIENT, con_quit, 0x0, "Quit Teeworlds"); - MACRO_REGISTER_COMMAND("connect", "s", CFGFLAG_CLIENT, con_connect, 0x0, "Connect to the specified host/ip"); - MACRO_REGISTER_COMMAND("disconnect", "", CFGFLAG_CLIENT, con_disconnect, 0x0, "Disconnect from the server"); - MACRO_REGISTER_COMMAND("ping", "", CFGFLAG_CLIENT, con_ping, 0x0, "Ping the current server"); - MACRO_REGISTER_COMMAND("screenshot", "", CFGFLAG_CLIENT, con_screenshot, 0x0, "Take a screenshot"); - MACRO_REGISTER_COMMAND("rcon", "r", CFGFLAG_CLIENT, con_rcon, 0x0, "Send specified command to rcon"); - MACRO_REGISTER_COMMAND("rcon_auth", "s", CFGFLAG_CLIENT, con_rcon_auth, 0x0, "Authenticate to rcon"); - - MACRO_REGISTER_COMMAND("play", "r", CFGFLAG_CLIENT, con_play, 0x0, "Play the file specified"); - MACRO_REGISTER_COMMAND("record", "s", CFGFLAG_CLIENT, con_record, 0, "Record to the file"); - MACRO_REGISTER_COMMAND("stoprecord", "", CFGFLAG_CLIENT, con_stoprecord, 0, "Stop recording"); - - MACRO_REGISTER_COMMAND("add_favorite", "s", CFGFLAG_CLIENT, con_addfavorite, 0x0, "Add a server as a favorite"); - - /* register server dummy commands for tab completion */ - MACRO_REGISTER_COMMAND("kick", "i", CFGFLAG_SERVER, con_serverdummy, 0, "Kick player with specified id"); - MACRO_REGISTER_COMMAND("ban", "s?i", CFGFLAG_SERVER, con_serverdummy, 0, "Ban player with ip/id for x minutes"); - MACRO_REGISTER_COMMAND("unban", "s", CFGFLAG_SERVER, con_serverdummy, 0, "Unban ip"); - MACRO_REGISTER_COMMAND("bans", "", CFGFLAG_SERVER, con_serverdummy, 0, "Show banlist"); - MACRO_REGISTER_COMMAND("status", "", CFGFLAG_SERVER, con_serverdummy, 0, "List players"); - MACRO_REGISTER_COMMAND("shutdown", "", CFGFLAG_SERVER, con_serverdummy, 0, "Shut down"); - /*MACRO_REGISTER_COMMAND("record", "", CFGFLAG_SERVER, con_serverdummy, 0); - MACRO_REGISTER_COMMAND("stoprecord", "", CFGFLAG_SERVER, con_serverdummy, 0);*/ -} - -void client_save_line(const char *line) -{ - engine_config_write_line(line); -} - -const char *client_user_directory() -{ - static char path[1024] = {0}; - fs_storage_path("Teeworlds", path, sizeof(path)); - return path; -} - -#if defined(CONF_PLATFORM_MACOSX) -int SDL_main(int argc, char **argv) -#else -int main(int argc, char **argv) -#endif -{ - /* init the engine */ - dbg_msg("client", "starting..."); - engine_init("Teeworlds"); - - /* register all console commands */ - client_register_commands(); - modc_console_init(); - - /* parse the command line arguments */ - engine_parse_arguments(argc, argv); - - /* execute config file */ - console_execute_file("settings.cfg"); - - /* run the client*/ - client_run(); - - /* write down the config and quit */ - if(engine_config_write_start() == 0) - { - config_save(); - client_serverbrowse_save(); - modc_save_config(); - engine_config_write_stop(); - } - - return 0; -} diff --git a/src/engine/client/ec_gfx.cpp b/src/engine/client/ec_gfx.cpp deleted file mode 100644 index 5632581a..00000000 --- a/src/engine/client/ec_gfx.cpp +++ /dev/null @@ -1,992 +0,0 @@ -/* copyright (c) 2007 magnus auvinen, see licence.txt for more info */ - -#include <base/detect.h> - -#include "SDL.h" - -#ifdef CONF_FAMILY_WINDOWS - #define WIN32_LEAN_AND_MEAN - #include <windows.h> -#endif - -#ifdef CONF_PLATFORM_MACOSX - #include <OpenGL/gl.h> - #include <OpenGL/glu.h> -#else - #include <GL/gl.h> - #include <GL/glu.h> -#endif - -#include <base/system.h> -#include <engine/external/pnglite/pnglite.h> - -#include <engine/e_client_interface.h> -#include <engine/e_engine.h> -#include <engine/e_config.h> -#include <engine/e_keys.h> - -#include <string.h> -#include <stdio.h> -#include <math.h> - -/* compressed textures */ -#define GL_COMPRESSED_RGB_ARB 0x84ED -#define GL_COMPRESSED_RGBA_ARB 0x84EE -#define GL_COMPRESSED_ALPHA_ARB 0x84E9 - -#define TEXTURE_MAX_ANISOTROPY_EXT 0x84FE - - -void gfx_font_init(); - -VIDEO_MODE fakemodes[] = { - {320,240,8,8,8}, {400,300,8,8,8}, {640,480,8,8,8}, - {720,400,8,8,8}, {768,576,8,8,8}, {800,600,8,8,8}, - {1024,600,8,8,8}, {1024,768,8,8,8}, {1152,864,8,8,8}, - {1280,768,8,8,8}, {1280,800,8,8,8}, {1280,960,8,8,8}, - {1280,1024,8,8,8}, {1368,768,8,8,8}, {1400,1050,8,8,8}, - {1440,900,8,8,8}, {1440,1050,8,8,8}, {1600,1000,8,8,8}, - {1600,1200,8,8,8}, {1680,1050,8,8,8}, {1792,1344,8,8,8}, - {1800,1440,8,8,8}, {1856,1392,8,8,8}, {1920,1080,8,8,8}, - {1920,1200,8,8,8}, {1920,1440,8,8,8}, {1920,2400,8,8,8}, - {2048,1536,8,8,8}, - - {320,240,5,6,5}, {400,300,5,6,5}, {640,480,5,6,5}, - {720,400,5,6,5}, {768,576,5,6,5}, {800,600,5,6,5}, - {1024,600,5,6,5}, {1024,768,5,6,5}, {1152,864,5,6,5}, - {1280,768,5,6,5}, {1280,800,5,6,5}, {1280,960,5,6,5}, - {1280,1024,5,6,5}, {1368,768,5,6,5}, {1400,1050,5,6,5}, - {1440,900,5,6,5}, {1440,1050,5,6,5}, {1600,1000,5,6,5}, - {1600,1200,5,6,5}, {1680,1050,5,6,5}, {1792,1344,5,6,5}, - {1800,1440,5,6,5}, {1856,1392,5,6,5}, {1920,1080,5,6,5}, - {1920,1200,5,6,5}, {1920,1440,5,6,5}, {1920,2400,5,6,5}, - {2048,1536,5,6,5} -}; - -int gfx_get_video_modes(VIDEO_MODE *list, int maxcount) -{ - int num_modes = sizeof(fakemodes)/sizeof(VIDEO_MODE); - SDL_Rect **modes; - - if(config.gfx_display_all_modes) - { - int count = sizeof(fakemodes)/sizeof(VIDEO_MODE); - mem_copy(list, fakemodes, sizeof(fakemodes)); - if(maxcount < count) - count = maxcount; - return count; - } - - /* TODO: fix this code on osx or windows */ - - modes = SDL_ListModes(NULL, SDL_OPENGL|SDL_GL_DOUBLEBUFFER|SDL_FULLSCREEN); - if(modes == NULL) - { - /* no modes */ - num_modes = 0; - } - else if(modes == (SDL_Rect**)-1) - { - /* all modes */ - } - else - { - int i; - num_modes = 0; - for(i = 0; modes[i]; ++i) - { - if(num_modes == maxcount) - break; - list[num_modes].width = modes[i]->w; - list[num_modes].height = modes[i]->h; - list[num_modes].red = 8; - list[num_modes].green = 8; - list[num_modes].blue = 8; - num_modes++; - } - } - - return num_modes; -} - - -#include "graphics.h" - -class CGraphics_OpenGL : public IEngineGraphics -{ -protected: - /* */ - typedef struct { float x, y, z; } CPoint; - typedef struct { float u, v; } CTexCoord; - typedef struct { float r, g, b, a; } CColor; - - typedef struct - { - CPoint m_Pos; - CTexCoord m_Tex; - CColor m_Color; - } CVertex; - - enum - { - MAX_VERTICES = 32*1024, - MAX_TEXTURES = 1024*4, - - DRAWING_QUADS=1, - DRAWING_LINES=2 - }; - - CVertex m_aVertices[MAX_VERTICES]; - int m_NumVertices; - - CColor m_aColor[4]; - CTexCoord m_aTexture[4]; - - bool m_RenderEnable; - - float m_Rotation; - int m_Drawing; - bool m_DoScreenshot; - - float m_ScreenX0; - float m_ScreenY0; - float m_ScreenX1; - float m_ScreenY1; - - int m_InvalidTexture; - - struct CTexture - { - GLuint tex; - int memsize; - int flags; - int next; - }; - - enum - { - - }; - - CTexture m_aTextures[MAX_TEXTURES]; - int m_FirstFreeTexture; - int m_TextureMemoryUsage; - - - void Flush() - { - if(m_NumVertices == 0) - return; - - //glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_NEAREST); - //glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); - - glVertexPointer(3, GL_FLOAT, - sizeof(CVertex), - (char*)m_aVertices); - glTexCoordPointer(2, GL_FLOAT, - sizeof(CVertex), - (char*)m_aVertices + sizeof(float)*3); - glColorPointer(4, GL_FLOAT, - sizeof(CVertex), - (char*)m_aVertices + sizeof(float)*5); - glEnableClientState(GL_VERTEX_ARRAY); - glEnableClientState(GL_TEXTURE_COORD_ARRAY); - glEnableClientState(GL_COLOR_ARRAY); - - if(m_RenderEnable) - { - if(m_Drawing == DRAWING_QUADS) - glDrawArrays(GL_QUADS, 0, m_NumVertices); - else if(m_Drawing == DRAWING_LINES) - glDrawArrays(GL_LINES, 0, m_NumVertices); - } - - /* Reset pointer */ - m_NumVertices = 0; - } - - void AddVertices(int count) - { - m_NumVertices += count; - if((m_NumVertices + count) >= MAX_VERTICES) - Flush(); - } - - void Rotate(CPoint *pCenter, CPoint *pPoint) - { - float x = pPoint->x - pCenter->x; - float y = pPoint->y - pCenter->y; - pPoint->x = x * cosf(m_Rotation) - y * sinf(m_Rotation) + pCenter->x; - pPoint->y = x * sinf(m_Rotation) + y * cosf(m_Rotation) + pCenter->y; - } - - - - - static unsigned char sample(int w, int h, const unsigned char *data, int u, int v, int offset) - { - return (data[(v*w+u)*4+offset]+ - data[(v*w+u+1)*4+offset]+ - data[((v+1)*w+u)*4+offset]+ - data[((v+1)*w+u+1)*4+offset])/4; - } -public: - CGraphics_OpenGL() - { - m_NumVertices = 0; - - m_ScreenX0 = 0; - m_ScreenY0 = 0; - m_ScreenX1 = 0; - m_ScreenY1 = 0; - - m_ScreenWidth = -1; - m_ScreenHeight = -1; - - m_Rotation = 0; - m_Drawing = 0; - m_InvalidTexture = 0; - - m_TextureMemoryUsage = 0; - - m_RenderEnable = true; - m_DoScreenshot = false; - } - - - virtual void ClipEnable(int x, int y, int w, int h) - { - //if(no_gfx) return; - glScissor(x, ScreenHeight()-(y+h), w, h); - glEnable(GL_SCISSOR_TEST); - } - - virtual void ClipDisable() - { - //if(no_gfx) return; - glDisable(GL_SCISSOR_TEST); - } - - - virtual void BlendNone() - { - glDisable(GL_BLEND); - } - - virtual void BlendNormal() - { - glEnable(GL_BLEND); - glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); - } - - virtual void BlendAdditive() - { - glEnable(GL_BLEND); - glBlendFunc(GL_SRC_ALPHA, GL_ONE); - } - - //int gfx_memory_usage() { return m_MemoryUsage; } - - virtual void MapScreen(float tl_x, float tl_y, float br_x, float br_y) - { - m_ScreenX0 = tl_x; - m_ScreenY0 = tl_y; - m_ScreenX1 = br_x; - m_ScreenY1 = br_y; - glMatrixMode(GL_PROJECTION); - glLoadIdentity(); - glOrtho(tl_x, br_x, br_y, tl_y, 1.0f, 10.f); - } - - virtual void GetScreen(float *tl_x, float *tl_y, float *br_x, float *br_y) - { - *tl_x = m_ScreenX0; - *tl_y = m_ScreenY0; - *br_x = m_ScreenX1; - *br_y = m_ScreenY1; - } - - virtual void LinesBegin() - { - dbg_assert(m_Drawing == 0, "called begin twice"); - m_Drawing = DRAWING_LINES; - SetColor(1,1,1,1); - } - - virtual void LinesEnd() - { - dbg_assert(m_Drawing == DRAWING_LINES, "called end without begin"); - Flush(); - m_Drawing = 0; - } - - virtual void LinesDraw(float x0, float y0, float x1, float y1) - { - dbg_assert(m_Drawing == DRAWING_LINES, "called draw without begin"); - - m_aVertices[m_NumVertices].m_Pos.x = x0; - m_aVertices[m_NumVertices].m_Pos.y = y0; - m_aVertices[m_NumVertices].m_Tex = m_aTexture[0]; - m_aVertices[m_NumVertices].m_Color = m_aColor[0]; - - m_aVertices[m_NumVertices + 1].m_Pos.x = x1; - m_aVertices[m_NumVertices + 1].m_Pos.y = y1; - m_aVertices[m_NumVertices + 1].m_Tex = m_aTexture[1]; - m_aVertices[m_NumVertices + 1].m_Color = m_aColor[1]; - - AddVertices(2); - } - - - - virtual int UnloadTexture(int Index) - { - if(Index == m_InvalidTexture) - return 0; - - if(Index < 0) - return 0; - - glDeleteTextures(1, &m_aTextures[Index].tex); - m_aTextures[Index].next = m_FirstFreeTexture; - m_TextureMemoryUsage -= m_aTextures[Index].memsize; - m_FirstFreeTexture = Index; - return 0; - } - - - virtual int LoadTextureRaw(int Width, int Height, int Format, const void *pData, int StoreFormat, int Flags) - { - int mipmap = 1; - unsigned char *texdata = (unsigned char *)pData; - unsigned char *tmpdata = 0; - int oglformat = 0; - int store_oglformat = 0; - int tex = 0; - - /* don't waste memory on texture if we are stress testing */ - if(config.dbg_stress) - return m_InvalidTexture; - - /* grab texture */ - tex = m_FirstFreeTexture; - m_FirstFreeTexture = m_aTextures[tex].next; - m_aTextures[tex].next = -1; - - /* resample if needed */ - if(!(Flags&TEXLOAD_NORESAMPLE) && config.gfx_texture_quality==0) - { - if(Width > 16 && Height > 16 && Format == IMG_RGBA) - { - unsigned char *tmpdata; - int c = 0; - int x, y; - - tmpdata = (unsigned char *)mem_alloc(Width*Height*4, 1); - - Width/=2; - Height/=2; - - for(y = 0; y < Height; y++) - for(x = 0; x < Width; x++, c++) - { - tmpdata[c*4] = sample(Width*2, Height*2, texdata, x*2,y*2, 0); - tmpdata[c*4+1] = sample(Width*2, Height*2, texdata, x*2,y*2, 1); - tmpdata[c*4+2] = sample(Width*2, Height*2, texdata, x*2,y*2, 2); - tmpdata[c*4+3] = sample(Width*2, Height*2, texdata, x*2,y*2, 3); - } - texdata = tmpdata; - } - } - - oglformat = GL_RGBA; - if(Format == IMG_RGB) - oglformat = GL_RGB; - else if(Format == IMG_ALPHA) - oglformat = GL_ALPHA; - - /* upload texture */ - if(config.gfx_texture_compression) - { - store_oglformat = GL_COMPRESSED_RGBA_ARB; - if(StoreFormat == IMG_RGB) - store_oglformat = GL_COMPRESSED_RGB_ARB; - else if(StoreFormat == IMG_ALPHA) - store_oglformat = GL_COMPRESSED_ALPHA_ARB; - } - else - { - store_oglformat = GL_RGBA; - if(StoreFormat == IMG_RGB) - store_oglformat = GL_RGB; - else if(StoreFormat == IMG_ALPHA) - store_oglformat = GL_ALPHA; - } - - glGenTextures(1, &m_aTextures[tex].tex); - glBindTexture(GL_TEXTURE_2D, m_aTextures[tex].tex); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_NEAREST); - gluBuild2DMipmaps(GL_TEXTURE_2D, store_oglformat, Width, Height, oglformat, GL_UNSIGNED_BYTE, texdata); - - /* calculate memory usage */ - { - int pixel_size = 4; - if(StoreFormat == IMG_RGB) - pixel_size = 3; - else if(StoreFormat == IMG_ALPHA) - pixel_size = 1; - - m_aTextures[tex].memsize = Width*Height*pixel_size; - if(mipmap) - { - while(Width > 2 && Height > 2) - { - Width>>=1; - Height>>=1; - m_aTextures[tex].memsize += Width*Height*pixel_size; - } - } - } - - m_TextureMemoryUsage += m_aTextures[tex].memsize; - mem_free(tmpdata); - return tex; - } - - /* simple uncompressed RGBA loaders */ - virtual int LoadTexture(const char *pFilename, int StoreFormat, int Flags) - { - int l = strlen(pFilename); - int id; - IMAGE_INFO img; - - if(l < 3) - return -1; - if(LoadPNG(&img, pFilename)) - { - if (StoreFormat == IMG_AUTO) - StoreFormat = img.format; - - id = LoadTextureRaw(img.width, img.height, img.format, img.data, StoreFormat, Flags); - mem_free(img.data); - return id; - } - - return m_InvalidTexture; - } - - virtual int LoadPNG(IMAGE_INFO *pImg, const char *pFilename) - { - char aCompleteFilename[512]; - unsigned char *pBuffer; - png_t png; - - /* open file for reading */ - png_init(0,0); - - engine_getpath(aCompleteFilename, sizeof(aCompleteFilename), pFilename, IOFLAG_READ); - - if(png_open_file(&png, aCompleteFilename) != PNG_NO_ERROR) - { - dbg_msg("game/png", "failed to open file. filename='%s'", aCompleteFilename); - return 0; - } - - if(png.depth != 8 || (png.color_type != PNG_TRUECOLOR && png.color_type != PNG_TRUECOLOR_ALPHA)) - { - dbg_msg("game/png", "invalid format. filename='%s'", aCompleteFilename); - png_close_file(&png); - return 0; - } - - pBuffer = (unsigned char *)mem_alloc(png.width * png.height * png.bpp, 1); - png_get_data(&png, pBuffer); - png_close_file(&png); - - pImg->width = png.width; - pImg->height = png.height; - if(png.color_type == PNG_TRUECOLOR) - pImg->format = IMG_RGB; - else if(png.color_type == PNG_TRUECOLOR_ALPHA) - pImg->format = IMG_RGBA; - pImg->data = pBuffer; - return 1; - } - - void ScreenshotDirect(const char *filename) - { - /* fetch image data */ - int y; - int w = m_ScreenWidth; - int h = m_ScreenHeight; - unsigned char *pixel_data = (unsigned char *)mem_alloc(w*(h+1)*4, 1); - unsigned char *temp_row = pixel_data+w*h*4; - glReadPixels(0,0, w, h, GL_RGBA, GL_UNSIGNED_BYTE, pixel_data); - - /* flip the pixel because opengl works from bottom left corner */ - for(y = 0; y < h/2; y++) - { - mem_copy(temp_row, pixel_data+y*w*4, w*4); - mem_copy(pixel_data+y*w*4, pixel_data+(h-y-1)*w*4, w*4); - mem_copy(pixel_data+(h-y-1)*w*4, temp_row,w*4); - } - - /* find filename */ - { - char wholepath[1024]; - png_t png; - - engine_savepath(filename, wholepath, sizeof(wholepath)); - - /* save png */ - dbg_msg("client", "saved screenshot to '%s'", wholepath); - png_open_file_write(&png, wholepath); - png_set_data(&png, w, h, 8, PNG_TRUECOLOR_ALPHA, (unsigned char *)pixel_data); - png_close_file(&png); - } - - /* clean up */ - mem_free(pixel_data); - } - - virtual void TextureSet(int TextureID) - { - dbg_assert(m_Drawing == 0, "called Graphics()->TextureSet within begin"); - if(TextureID == -1) - { - glDisable(GL_TEXTURE_2D); - } - else - { - glEnable(GL_TEXTURE_2D); - glBindTexture(GL_TEXTURE_2D, m_aTextures[TextureID].tex); - } - } - - virtual void Clear(float r, float g, float b) - { - glClearColor(r,g,b,0.0f); - glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); - } - - virtual void QuadsBegin() - { - dbg_assert(m_Drawing == 0, "called quads_begin twice"); - m_Drawing = DRAWING_QUADS; - - QuadsSetSubset(0,0,1,1); - QuadsSetRotation(0); - SetColor(1,1,1,1); - } - - virtual void QuadsEnd() - { - dbg_assert(m_Drawing == DRAWING_QUADS, "called quads_end without begin"); - Flush(); - m_Drawing = 0; - } - - virtual void QuadsSetRotation(float Angle) - { - dbg_assert(m_Drawing == DRAWING_QUADS, "called Graphics()->QuadsSetRotation without begin"); - m_Rotation = Angle; - } - - virtual void SetColorVertex(int i, float r, float g, float b, float a) - { - dbg_assert(m_Drawing != 0, "called gfx_quads_setcolorvertex without begin"); - m_aColor[i].r = r; - m_aColor[i].g = g; - m_aColor[i].b = b; - m_aColor[i].a = a; - } - - virtual void SetColor(float r, float g, float b, float a) - { - dbg_assert(m_Drawing != 0, "called gfx_quads_setcolor without begin"); - SetColorVertex(0, r, g, b, a); - SetColorVertex(1, r, g, b, a); - SetColorVertex(2, r, g, b, a); - SetColorVertex(3, r, g, b, a); - } - - virtual void QuadsSetSubset(float tl_u, float tl_v, float br_u, float br_v) - { - dbg_assert(m_Drawing == DRAWING_QUADS, "called Graphics()->QuadsSetSubset without begin"); - - m_aTexture[0].u = tl_u; m_aTexture[1].u = br_u; - m_aTexture[0].v = tl_v; m_aTexture[1].v = tl_v; - - m_aTexture[3].u = tl_u; m_aTexture[2].u = br_u; - m_aTexture[3].v = br_v; m_aTexture[2].v = br_v; - } - - virtual void QuadsSetSubsetFree( - float x0, float y0, float x1, float y1, - float x2, float y2, float x3, float y3) - { - m_aTexture[0].u = x0; m_aTexture[0].v = y0; - m_aTexture[1].u = x1; m_aTexture[1].v = y1; - m_aTexture[2].u = x2; m_aTexture[2].v = y2; - m_aTexture[3].u = x3; m_aTexture[3].v = y3; - } - - virtual void QuadsDraw(float x, float y, float w, float h) - { - QuadsDrawTL(x-w/2, y-h/2,w,h); - } - - virtual void QuadsDrawTL(float x, float y, float w, float h) - { - CPoint Center; - - dbg_assert(m_Drawing == DRAWING_QUADS, "called quads_draw without begin"); - - Center.x = x + w/2; - Center.y = y + h/2; - Center.z = 0; - - m_aVertices[m_NumVertices].m_Pos.x = x; - m_aVertices[m_NumVertices].m_Pos.y = y; - m_aVertices[m_NumVertices].m_Tex = m_aTexture[0]; - m_aVertices[m_NumVertices].m_Color = m_aColor[0]; - Rotate(&Center, &m_aVertices[m_NumVertices].m_Pos); - - m_aVertices[m_NumVertices + 1].m_Pos.x = x+w; - m_aVertices[m_NumVertices + 1].m_Pos.y = y; - m_aVertices[m_NumVertices + 1].m_Tex = m_aTexture[1]; - m_aVertices[m_NumVertices + 1].m_Color = m_aColor[1]; - Rotate(&Center, &m_aVertices[m_NumVertices + 1].m_Pos); - - m_aVertices[m_NumVertices + 2].m_Pos.x = x + w; - m_aVertices[m_NumVertices + 2].m_Pos.y = y+h; - m_aVertices[m_NumVertices + 2].m_Tex = m_aTexture[2]; - m_aVertices[m_NumVertices + 2].m_Color = m_aColor[2]; - Rotate(&Center, &m_aVertices[m_NumVertices + 2].m_Pos); - - m_aVertices[m_NumVertices + 3].m_Pos.x = x; - m_aVertices[m_NumVertices + 3].m_Pos.y = y+h; - m_aVertices[m_NumVertices + 3].m_Tex = m_aTexture[3]; - m_aVertices[m_NumVertices + 3].m_Color = m_aColor[3]; - Rotate(&Center, &m_aVertices[m_NumVertices + 3].m_Pos); - - AddVertices(4); - } - - void QuadsDrawFreeform( - float x0, float y0, float x1, float y1, - float x2, float y2, float x3, float y3) - { - dbg_assert(m_Drawing == DRAWING_QUADS, "called quads_draw_freeform without begin"); - - m_aVertices[m_NumVertices].m_Pos.x = x0; - m_aVertices[m_NumVertices].m_Pos.y = y0; - m_aVertices[m_NumVertices].m_Tex = m_aTexture[0]; - m_aVertices[m_NumVertices].m_Color = m_aColor[0]; - - m_aVertices[m_NumVertices + 1].m_Pos.x = x1; - m_aVertices[m_NumVertices + 1].m_Pos.y = y1; - m_aVertices[m_NumVertices + 1].m_Tex = m_aTexture[1]; - m_aVertices[m_NumVertices + 1].m_Color = m_aColor[1]; - - m_aVertices[m_NumVertices + 2].m_Pos.x = x3; - m_aVertices[m_NumVertices + 2].m_Pos.y = y3; - m_aVertices[m_NumVertices + 2].m_Tex = m_aTexture[3]; - m_aVertices[m_NumVertices + 2].m_Color = m_aColor[3]; - - m_aVertices[m_NumVertices + 3].m_Pos.x = x2; - m_aVertices[m_NumVertices + 3].m_Pos.y = y2; - m_aVertices[m_NumVertices + 3].m_Tex = m_aTexture[2]; - m_aVertices[m_NumVertices + 3].m_Color = m_aColor[2]; - - AddVertices(4); - } - - virtual void QuadsText(float x, float y, float Size, float r, float g, float b, float a, const char *pText) - { - float startx = x; - - QuadsBegin(); - SetColor(r,g,b,a); - - while(*pText) - { - char c = *pText; - pText++; - - if(c == '\n') - { - x = startx; - y += Size; - } - else - { - QuadsSetSubset( - (c%16)/16.0f, - (c/16)/16.0f, - (c%16)/16.0f+1.0f/16.0f, - (c/16)/16.0f+1.0f/16.0f); - - QuadsDrawTL(x,y,Size,Size); - x += Size/2; - } - } - - QuadsEnd(); - } - - virtual bool Init() - { - /* Set all z to -5.0f */ - for(int i = 0; i < MAX_VERTICES; i++) - m_aVertices[i].m_Pos.z = -5.0f; - - /* init textures */ - m_FirstFreeTexture = 0; - for(int i = 0; i < MAX_TEXTURES; i++) - m_aTextures[i].next = i+1; - m_aTextures[MAX_TEXTURES-1].next = -1; - - /* set some default settings */ - glEnable(GL_BLEND); - glDisable(GL_CULL_FACE); - glDisable(GL_DEPTH_TEST); - glMatrixMode(GL_MODELVIEW); - glLoadIdentity(); - - glAlphaFunc(GL_GREATER, 0); - glEnable(GL_ALPHA_TEST); - glDepthMask(0); - - /* create null texture, will get id=0 */ - static const unsigned char aNullTextureData[] = { - 0xff,0x00,0x00,0xff, 0xff,0x00,0x00,0xff, 0x00,0xff,0x00,0xff, 0x00,0xff,0x00,0xff, - 0xff,0x00,0x00,0xff, 0xff,0x00,0x00,0xff, 0x00,0xff,0x00,0xff, 0x00,0xff,0x00,0xff, - 0x00,0x00,0xff,0xff, 0x00,0x00,0xff,0xff, 0xff,0xff,0x00,0xff, 0xff,0xff,0x00,0xff, - 0x00,0x00,0xff,0xff, 0x00,0x00,0xff,0xff, 0xff,0xff,0x00,0xff, 0xff,0xff,0x00,0xff, - }; - - m_InvalidTexture = LoadTextureRaw(4,4,IMG_RGBA,aNullTextureData,IMG_RGBA,TEXLOAD_NORESAMPLE); - dbg_msg("", "invalid texture id: %d %d", m_InvalidTexture, m_aTextures[m_InvalidTexture].tex); - - return true; - } -}; - -class CGraphics_SDL : public CGraphics_OpenGL -{ - SDL_Surface *m_pScreenSurface; - - int TryInit() - { - const SDL_VideoInfo *pInfo; - int Flags = SDL_OPENGL; - - m_ScreenWidth = config.gfx_screen_width; - m_ScreenHeight = config.gfx_screen_height; - - pInfo = SDL_GetVideoInfo(); - - /* set flags */ - Flags = SDL_OPENGL; - Flags |= SDL_GL_DOUBLEBUFFER; - Flags |= SDL_HWPALETTE; - if(config.dbg_resizable) - Flags |= SDL_RESIZABLE; - - if(pInfo->hw_available) - Flags |= SDL_HWSURFACE; - else - Flags |= SDL_SWSURFACE; - - if(pInfo->blit_hw) - Flags |= SDL_HWACCEL; - - if(config.gfx_fullscreen) - Flags |= SDL_FULLSCREEN; - - /* set gl attributes */ - if(config.gfx_fsaa_samples) - { - SDL_GL_SetAttribute(SDL_GL_MULTISAMPLEBUFFERS, 1); - SDL_GL_SetAttribute(SDL_GL_MULTISAMPLESAMPLES, config.gfx_fsaa_samples); - } - else - { - SDL_GL_SetAttribute(SDL_GL_MULTISAMPLEBUFFERS, 0); - SDL_GL_SetAttribute(SDL_GL_MULTISAMPLESAMPLES, 0); - } - - SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1); - SDL_GL_SetAttribute(SDL_GL_SWAP_CONTROL, config.gfx_vsync); - - /* set caption */ - SDL_WM_SetCaption("Teeworlds", "Teeworlds"); - - /* create window */ - m_pScreenSurface = SDL_SetVideoMode(m_ScreenWidth, m_ScreenHeight, 0, Flags); - if(m_pScreenSurface == NULL) - { - dbg_msg("gfx", "unable to set video mode: %s", SDL_GetError()); - return -1; - } - - return 0; - } - - - int InitWindow() - { - if(TryInit() == 0) - return 0; - - /* try disabling fsaa */ - while(config.gfx_fsaa_samples) - { - config.gfx_fsaa_samples--; - - if(config.gfx_fsaa_samples) - dbg_msg("gfx", "lowering FSAA to %d and trying again", config.gfx_fsaa_samples); - else - dbg_msg("gfx", "disabling FSAA and trying again"); - - if(TryInit() == 0) - return 0; - } - - /* try lowering the resolution */ - if(config.gfx_screen_width != 640 || config.gfx_screen_height != 480) - { - dbg_msg("gfx", "setting resolution to 640x480 and trying again"); - config.gfx_screen_width = 640; - config.gfx_screen_height = 480; - - if(TryInit() == 0) - return 0; - } - - dbg_msg("gfx", "out of ideas. failed to init graphics"); - - return -1; - } - - -public: - CGraphics_SDL() - { - m_pScreenSurface = 0; - } - - virtual bool Init() - { - { - int Systems = SDL_INIT_VIDEO; - - if(config.snd_enable) - Systems |= SDL_INIT_AUDIO; - - if(config.cl_eventthread) - Systems |= SDL_INIT_EVENTTHREAD; - - if(SDL_Init(Systems) < 0) - { - dbg_msg("gfx", "unable to init SDL: %s", SDL_GetError()); - return -1; - } - } - - atexit(SDL_Quit); - - #ifdef CONF_FAMILY_WINDOWS - if(!getenv("SDL_VIDEO_WINDOW_POS") && !getenv("SDL_VIDEO_CENTERED")) - putenv("SDL_VIDEO_WINDOW_POS=8,27"); - #endif - - if(InitWindow() != 0) - return -1; - - SDL_ShowCursor(0); - - CGraphics_OpenGL::Init(); - - MapScreen(0,0,config.gfx_screen_width, config.gfx_screen_height); - - /* init input */ - inp_init(); - - /* font init */ - gfx_font_init(); - - return 0; - } - - virtual void Shutdown() - { - /* TODO: SDL, is this correct? */ - SDL_Quit(); - } - - virtual void Minimize() - { - SDL_WM_IconifyWindow(); - } - - virtual void Maximize() - { - /* TODO: SDL */ - } - - virtual int WindowActive() - { - return SDL_GetAppState()&SDL_APPINPUTFOCUS; - } - - virtual int WindowOpen() - { - return SDL_GetAppState()&SDL_APPACTIVE; - - } - - virtual void TakeScreenshot() - { - m_DoScreenshot = true; - } - - virtual void Swap() - { - if(m_DoScreenshot) - { - /* find filename */ - char filename[128]; - static int index = 1; - - for(; index < 1000; index++) - { - IOHANDLE io; - str_format(filename, sizeof(filename), "screenshots/screenshot%04d.png", index); - io = engine_openfile(filename, IOFLAG_READ); - if(io) - io_close(io); - else - break; - } - - ScreenshotDirect(filename); - m_DoScreenshot = false; - } - - { - static PERFORMACE_INFO pscope = {"glfwSwapBuffers", 0}; - perf_start(&pscope); - SDL_GL_SwapBuffers(); - perf_end(); - } - - if(config.gfx_finish) - glFinish(); - } -}; - -extern IEngineGraphics *CreateEngineGraphics() { return new CGraphics_SDL(); } diff --git a/src/engine/client/ec_gfx_text.cpp b/src/engine/client/ec_gfx_text.cpp deleted file mode 100644 index d17d1bed..00000000 --- a/src/engine/client/ec_gfx_text.cpp +++ /dev/null @@ -1,669 +0,0 @@ -#include <base/system.h> -#include <string.h> -#include <engine/e_client_interface.h> -#include <engine/client/graphics.h> - -extern IEngineGraphics *Graphics(); - -#ifdef CONF_PLATFORM_MACOSX - #include <OpenGL/gl.h> - #include <OpenGL/glu.h> -#else - #include <GL/gl.h> - #include <GL/glu.h> -#endif - -static int word_length(const char *text) -{ - int s = 1; - while(1) - { - if(*text == 0) - return s-1; - if(*text == '\n' || *text == '\t' || *text == ' ') - return s; - text++; - s++; - } -} - -static float text_r=1; -static float text_g=1; -static float text_b=1; -static float text_a=1; - -static struct FONT *default_font = 0; -void gfx_text_set_default_font(struct FONT *font) -{ - default_font = font; -} - - -void gfx_text_set_cursor(TEXT_CURSOR *cursor, float x, float y, float font_size, int flags) -{ - mem_zero(cursor, sizeof(*cursor)); - cursor->font_size = font_size; - cursor->start_x = x; - cursor->start_y = y; - cursor->x = x; - cursor->y = y; - cursor->line_count = 1; - cursor->line_width = -1; - cursor->flags = flags; - cursor->charcount = 0; -} - - -void gfx_text(void *font_set_v, float x, float y, float size, const char *text, int max_width) -{ - TEXT_CURSOR cursor; - gfx_text_set_cursor(&cursor, x, y, size, TEXTFLAG_RENDER); - cursor.line_width = max_width; - gfx_text_ex(&cursor, text, -1); -} - -float gfx_text_width(void *font_set_v, float size, const char *text, int length) -{ - TEXT_CURSOR cursor; - gfx_text_set_cursor(&cursor, 0, 0, size, 0); - gfx_text_ex(&cursor, text, length); - return cursor.x; -} - -void gfx_text_color(float r, float g, float b, float a) -{ - text_r = r; - text_g = g; - text_b = b; - text_a = a; -} - -/* ft2 texture */ -#include <ft2build.h> -#include FT_FREETYPE_H - -static FT_Library ft_library; - -#define MAX_CHARACTERS 64 - - -/* GL_LUMINANCE can be good for debugging*/ -static int font_texture_format = GL_ALPHA; - - -static int font_sizes[] = {8,9,10,11,12,13,14,15,16,17,18,19,20,36}; -#define NUM_FONT_SIZES (sizeof(font_sizes)/sizeof(int)) - - -typedef struct FONTCHAR -{ - int id; - - /* these values are scaled to the font size */ - /* width * font_size == real_size */ - float width; - float height; - float offset_x; - float offset_y; - float advance_x; - - float uvs[4]; - int64 touch_time; -} FONTCHAR; - -typedef struct FONTSIZEDATA -{ - int font_size; - FT_Face *face; - - unsigned textures[2]; - int texture_width; - int texture_height; - - int num_x_chars; - int num_y_chars; - - int char_max_width; - int char_max_height; - - FONTCHAR characters[MAX_CHARACTERS*MAX_CHARACTERS]; - - int current_character; -} FONTSIZEDATA; - -typedef struct FONT -{ - char filename[128]; - FT_Face ft_face; - FONTSIZEDATA sizes[NUM_FONT_SIZES]; -} FONT; - -static int font_get_index(int pixelsize) -{ - for(unsigned i = 0; i < NUM_FONT_SIZES; i++) - { - if(font_sizes[i] >= pixelsize) - return i; - } - - return NUM_FONT_SIZES-1; -} - -FONT *gfx_font_load(const char *filename) -{ - FONT *font = (FONT *)mem_alloc(sizeof(FONT), 1); - - mem_zero(font, sizeof(*font)); - str_copy(font->filename, filename, sizeof(font->filename)); - - if(FT_New_Face(ft_library, font->filename, 0, &font->ft_face)) - { - mem_free(font); - return NULL; - } - - for(unsigned i = 0; i < NUM_FONT_SIZES; i++) - font->sizes[i].font_size = -1; - - return font; -}; - -void gfx_font_destroy(FONT *font) -{ - mem_free(font); -} - -void gfx_font_init() -{ - FT_Init_FreeType(&ft_library); -} - -static void grow(unsigned char *in, unsigned char *out, int w, int h) -{ - int y, x; - for(y = 0; y < h; y++) - for(x = 0; x < w; x++) - { - int c = in[y*w+x]; - int s_y, s_x; - - for(s_y = -1; s_y <= 1; s_y++) - for(s_x = -1; s_x <= 1; s_x++) - { - int get_x = x+s_x; - int get_y = y+s_y; - if (get_x >= 0 && get_y >= 0 && get_x < w && get_y < h) - { - int index = get_y*w+get_x; - if(in[index] > c) - c = in[index]; - } - } - - out[y*w+x] = c; - } -} - -static void font_init_texture(FONTSIZEDATA *sizedata, int charwidth, int charheight, int xchars, int ychars) -{ - static int font_memory_usage = 0; - int i; - int width = charwidth*xchars; - int height = charheight*ychars; - void *mem = mem_alloc(width*height, 1); - mem_zero(mem, width*height); - - if(sizedata->textures[0] == 0) - glGenTextures(2, sizedata->textures); - else - font_memory_usage -= sizedata->texture_width*sizedata->texture_height*2; - - sizedata->num_x_chars = xchars; - sizedata->num_y_chars = ychars; - sizedata->texture_width = width; - sizedata->texture_height = height; - sizedata->current_character = 0; - - for(i = 0; i < 2; i++) - { - glBindTexture(GL_TEXTURE_2D, sizedata->textures[i]); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); - glTexImage2D(GL_TEXTURE_2D, 0, font_texture_format, width, height, 0, font_texture_format, GL_UNSIGNED_BYTE, mem); - font_memory_usage += width*height; - } - - dbg_msg("", "font memory usage: %d", font_memory_usage); - - mem_free(mem); -} - -static void font_increase_texture_size(FONTSIZEDATA *sizedata) -{ - if(sizedata->texture_width < sizedata->texture_height) - sizedata->num_x_chars <<= 1; - else - sizedata->num_y_chars <<= 1; - font_init_texture(sizedata, sizedata->char_max_width, sizedata->char_max_height, sizedata->num_x_chars, sizedata->num_y_chars); -} - -static void font_init_index(FONT *font, int index) -{ - int outline_thickness = 1; - FONTSIZEDATA *sizedata = &font->sizes[index]; - - sizedata->font_size = font_sizes[index]; - FT_Set_Pixel_Sizes(font->ft_face, 0, sizedata->font_size); - - if(sizedata->font_size >= 18) - outline_thickness = 2; - - { - unsigned glyph_index; - int charcode; - int max_h = 0; - int max_w = 0; - - charcode = FT_Get_First_Char(font->ft_face, &glyph_index); - while(glyph_index != 0) - { - /* do stuff */ - FT_Load_Glyph(font->ft_face, glyph_index, FT_LOAD_DEFAULT); - - if(font->ft_face->glyph->metrics.width > max_w) max_w = font->ft_face->glyph->metrics.width; - if(font->ft_face->glyph->metrics.height > max_h) max_h = font->ft_face->glyph->metrics.height; - charcode = FT_Get_Next_Char(font->ft_face, charcode, &glyph_index); - } - - max_w = (max_w>>6)+2+outline_thickness*2; - max_h = (max_h>>6)+2+outline_thickness*2; - - for(sizedata->char_max_width = 1; sizedata->char_max_width < max_w; sizedata->char_max_width <<= 1); - for(sizedata->char_max_height = 1; sizedata->char_max_height < max_h; sizedata->char_max_height <<= 1); - } - - //dbg_msg("font", "init size %d, texture size %d %d", font->sizes[index].font_size, w, h); - //FT_New_Face(ft_library, "data/fonts/vera.ttf", 0, &font->ft_face); - font_init_texture(sizedata, sizedata->char_max_width, sizedata->char_max_height, 8, 8); -} - -static FONTSIZEDATA *font_get_size(FONT *font, int pixelsize) -{ - int index = font_get_index(pixelsize); - if(font->sizes[index].font_size != font_sizes[index]) - font_init_index(font, index); - return &font->sizes[index]; -} - - -static void font_upload_glyph(FONTSIZEDATA *sizedata, int texnum, int slot_id, int chr, const void *data) -{ - int x = (slot_id%sizedata->num_x_chars) * (sizedata->texture_width/sizedata->num_x_chars); - int y = (slot_id/sizedata->num_x_chars) * (sizedata->texture_height/sizedata->num_y_chars); - - glBindTexture(GL_TEXTURE_2D, sizedata->textures[texnum]); - glTexSubImage2D(GL_TEXTURE_2D, 0, x, y, - sizedata->texture_width/sizedata->num_x_chars, - sizedata->texture_height/sizedata->num_y_chars, - font_texture_format, GL_UNSIGNED_BYTE, data); -} - -/* 8k of data used for rendering glyphs */ -static unsigned char glyphdata[(4096/64) * (4096/64)]; -static unsigned char glyphdata_outlined[(4096/64) * (4096/64)]; - -static int font_get_slot(FONTSIZEDATA *sizedata) -{ - int char_count = sizedata->num_x_chars*sizedata->num_y_chars; - if(sizedata->current_character < char_count) - { - int i = sizedata->current_character; - sizedata->current_character++; - return i; - } - - /* kick out the oldest */ - /* TODO: remove this linear search */ - { - int oldest = 0; - int i; - for(i = 1; i < char_count; i++) - { - if(sizedata->characters[i].touch_time < sizedata->characters[oldest].touch_time) - oldest = i; - } - - if(time_get()-sizedata->characters[oldest].touch_time < time_freq()) - { - font_increase_texture_size(sizedata); - return font_get_slot(sizedata); - } - - return oldest; - } -} - -static int font_render_glyph(FONT *font, FONTSIZEDATA *sizedata, int chr) -{ - FT_Bitmap *bitmap; - int slot_id = 0; - int slot_w = sizedata->texture_width / sizedata->num_x_chars; - int slot_h = sizedata->texture_height / sizedata->num_y_chars; - int slot_size = slot_w*slot_h; - int outline_thickness = 1; - int x = 1; - int y = 1; - int px, py; - - FT_Set_Pixel_Sizes(font->ft_face, 0, sizedata->font_size); - - if(FT_Load_Char(font->ft_face, chr, FT_LOAD_RENDER|FT_LOAD_NO_BITMAP)) - { - dbg_msg("font", "error loading glyph %d", chr); - return -1; - } - - bitmap = &font->ft_face->glyph->bitmap; - - /* fetch slot */ - slot_id = font_get_slot(sizedata); - if(slot_id < 0) - return -1; - - /* adjust spacing */ - if(sizedata->font_size >= 18) - outline_thickness = 2; - x += outline_thickness; - y += outline_thickness; - - /* prepare glyph data */ - mem_zero(glyphdata, slot_size); - - if(bitmap->pixel_mode == FT_PIXEL_MODE_GRAY) - { - for(py = 0; py < bitmap->rows; py++) - for(px = 0; px < bitmap->width; px++) - glyphdata[(py+y)*slot_w+px+x] = bitmap->buffer[py*bitmap->pitch+px]; - } - else if(bitmap->pixel_mode == FT_PIXEL_MODE_MONO) - { - for(py = 0; py < bitmap->rows; py++) - for(px = 0; px < bitmap->width; px++) - { - if(bitmap->buffer[py*bitmap->pitch+px/8]&(1<<(7-(px%8)))) - glyphdata[(py+y)*slot_w+px+x] = 255; - } - } - - if(0) for(py = 0; py < slot_w; py++) - for(px = 0; px < slot_h; px++) - glyphdata[py*slot_w+px] = 255; - - /* upload the glyph */ - font_upload_glyph(sizedata, 0, slot_id, chr, glyphdata); - - if(outline_thickness == 1) - { - grow(glyphdata, glyphdata_outlined, slot_w, slot_h); - font_upload_glyph(sizedata, 1, slot_id, chr, glyphdata_outlined); - } - else - { - grow(glyphdata, glyphdata_outlined, slot_w, slot_h); - grow(glyphdata_outlined, glyphdata, slot_w, slot_h); - font_upload_glyph(sizedata, 1, slot_id, chr, glyphdata); - } - - /* set char info */ - { - FONTCHAR *fontchr = &sizedata->characters[slot_id]; - float scale = 1.0f/sizedata->font_size; - float uscale = 1.0f/sizedata->texture_width; - float vscale = 1.0f/sizedata->texture_height; - int height = bitmap->rows + outline_thickness*2 + 2; - int width = bitmap->width + outline_thickness*2 + 2; - - fontchr->id = chr; - fontchr->height = height * scale; - fontchr->width = width * scale; - fontchr->offset_x = (font->ft_face->glyph->bitmap_left-1) * scale; - fontchr->offset_y = (sizedata->font_size - font->ft_face->glyph->bitmap_top) * scale; - fontchr->advance_x = (font->ft_face->glyph->advance.x>>6) * scale; - - fontchr->uvs[0] = (slot_id%sizedata->num_x_chars) / (float)(sizedata->num_x_chars); - fontchr->uvs[1] = (slot_id/sizedata->num_x_chars) / (float)(sizedata->num_y_chars); - fontchr->uvs[2] = fontchr->uvs[0] + width*uscale; - fontchr->uvs[3] = fontchr->uvs[1] + height*vscale; - } - - return slot_id; -} - -static FONTCHAR *font_get_char(FONT *font, FONTSIZEDATA *sizedata, int chr) -{ - FONTCHAR *fontchr = NULL; - - /* search for the character */ - /* TODO: remove this linear search */ - int i; - for(i = 0; i < sizedata->current_character; i++) - { - if(sizedata->characters[i].id == chr) - { - fontchr = &sizedata->characters[i]; - break; - } - } - - /* check if we need to render the character */ - if(!fontchr) - { - int index = font_render_glyph(font, sizedata, chr); - if(index >= 0) - fontchr = &sizedata->characters[index]; - } - - /* touch the character */ - /* TODO: don't call time_get here */ - if(fontchr) - fontchr->touch_time = time_get(); - - return fontchr; -} - -/* must only be called from the rendering function as the font must be set to the correct size */ -static void font_render_setup(FONT *font, int size) -{ - FT_Set_Pixel_Sizes(font->ft_face, 0, size); -} - -static float font_kerning(FONT *font, int left, int right) -{ - FT_Vector kerning = {0,0}; - FT_Get_Kerning(font->ft_face, left, right, FT_KERNING_DEFAULT, &kerning); - return (kerning.x>>6); -} - - -void gfx_text_ex(TEXT_CURSOR *cursor, const char *text, int length) -{ - FONT *font = cursor->font; - FONTSIZEDATA *sizedata = NULL; - - float screen_x0, screen_y0, screen_x1, screen_y1; - float fake_to_screen_x, fake_to_screen_y; - int actual_x, actual_y; - - int actual_size; - int i; - int got_new_line = 0; - float draw_x, draw_y; - float cursor_x, cursor_y; - const char *end; - - float size = cursor->font_size; - - /* to correct coords, convert to screen coords, round, and convert back */ - Graphics()->GetScreen(&screen_x0, &screen_y0, &screen_x1, &screen_y1); - - fake_to_screen_x = (Graphics()->ScreenWidth()/(screen_x1-screen_x0)); - fake_to_screen_y = (Graphics()->ScreenHeight()/(screen_y1-screen_y0)); - actual_x = cursor->x * fake_to_screen_x; - actual_y = cursor->y * fake_to_screen_y; - - cursor_x = actual_x / fake_to_screen_x; - cursor_y = actual_y / fake_to_screen_y; - - /* same with size */ - actual_size = size * fake_to_screen_y; - size = actual_size / fake_to_screen_y; - - /* fetch font data */ - if(!font) - font = default_font; - - if(!font) - return; - - sizedata = font_get_size(font, actual_size); - font_render_setup(font, actual_size); - - /* set length */ - if(length < 0) - length = strlen(text); - - end = text + length; - - /* if we don't want to render, we can just skip the first outline pass */ - i = 1; - if(cursor->flags&TEXTFLAG_RENDER) - i = 0; - - for(;i < 2; i++) - { - const char *current = (char *)text; - const char *end = current+length; - draw_x = cursor_x; - draw_y = cursor_y; - - if(cursor->flags&TEXTFLAG_RENDER) - { - // TODO: Make this better - glEnable(GL_TEXTURE_2D); - if (i == 0) - glBindTexture(GL_TEXTURE_2D, sizedata->textures[1]); - else - glBindTexture(GL_TEXTURE_2D, sizedata->textures[0]); - - Graphics()->QuadsBegin(); - if (i == 0) - Graphics()->SetColor(0.0f, 0.0f, 0.0f, 0.3f*text_a); - else - Graphics()->SetColor(text_r, text_g, text_b, text_a); - } - - while(current < end) - { - int new_line = 0; - const char *batch_end = end; - if(cursor->line_width > 0 && !(cursor->flags&TEXTFLAG_STOP_AT_END)) - { - int wlen = word_length((char *)current); - TEXT_CURSOR compare = *cursor; - compare.x = draw_x; - compare.y = draw_y; - compare.flags &= ~TEXTFLAG_RENDER; - compare.line_width = -1; - gfx_text_ex(&compare, text, wlen); - - if(compare.x-draw_x > cursor->line_width) - { - /* word can't be fitted in one line, cut it */ - TEXT_CURSOR cutter = *cursor; - cutter.charcount = 0; - cutter.x = draw_x; - cutter.y = draw_y; - cutter.flags &= ~TEXTFLAG_RENDER; - cutter.flags |= TEXTFLAG_STOP_AT_END; - - gfx_text_ex(&cutter, (const char *)current, wlen); - wlen = cutter.charcount; - new_line = 1; - - if(wlen <= 3) /* if we can't place 3 chars of the word on this line, take the next */ - wlen = 0; - } - else if(compare.x-cursor->start_x > cursor->line_width) - { - new_line = 1; - wlen = 0; - } - - batch_end = current + wlen; - } - - while(current < batch_end) - { - const char *tmp; - float advance = 0; - int character = 0; - int nextcharacter = 0; - FONTCHAR *chr; - - // TODO: UTF-8 decode - character = str_utf8_decode(¤t); - tmp = current; - nextcharacter = str_utf8_decode(&tmp); - - if(character == '\n') - { - draw_x = cursor->start_x; - draw_y += size; - draw_x = (int)(draw_x * fake_to_screen_x) / fake_to_screen_x; /* realign */ - draw_y = (int)(draw_y * fake_to_screen_y) / fake_to_screen_y; - continue; - } - - chr = font_get_char(font, sizedata, character); - - if(chr) - { - if(cursor->flags&TEXTFLAG_RENDER) - { - Graphics()->QuadsSetSubset(chr->uvs[0], chr->uvs[1], chr->uvs[2], chr->uvs[3]); - Graphics()->QuadsDrawTL(draw_x+chr->offset_x*size, draw_y+chr->offset_y*size, chr->width*size, chr->height*size); - } - - advance = chr->advance_x + font_kerning(font, character, nextcharacter)/size; - } - - if(cursor->flags&TEXTFLAG_STOP_AT_END && draw_x+advance*size-cursor->start_x > cursor->line_width) - { - /* we hit the end of the line, no more to render or count */ - current = end; - break; - } - - draw_x += advance*size; - cursor->charcount++; - } - - if(new_line) - { - draw_x = cursor->start_x; - draw_y += size; - got_new_line = 1; - draw_x = (int)(draw_x * fake_to_screen_x) / fake_to_screen_x; /* realign */ - draw_y = (int)(draw_y * fake_to_screen_y) / fake_to_screen_y; - } - } - - if(cursor->flags&TEXTFLAG_RENDER) - Graphics()->QuadsEnd(); - } - - cursor->x = draw_x; - - if(got_new_line) - cursor->y = draw_y; -} diff --git a/src/engine/client/ec_inp.cpp b/src/engine/client/ec_inp.cpp deleted file mode 100644 index cf956471..00000000 --- a/src/engine/client/ec_inp.cpp +++ /dev/null @@ -1,234 +0,0 @@ -/* copyright (c) 2007 magnus auvinen, see licence.txt for more info */ -#include <string.h> -#include "SDL.h" - -#include <base/system.h> -#include <engine/e_client_interface.h> -#include <engine/e_config.h> -#include <engine/client/graphics.h> - -static struct -{ - unsigned char presses; - unsigned char releases; -} input_count[2][1024] = {{{0}}, {{0}}}; - -static unsigned char input_state[2][1024] = {{0}, {0}}; - -static int input_current = 0; -static int input_grabbed = 0; - -static unsigned int last_release = 0; -static unsigned int release_delta = -1; - -// TODO: Refactor: Remove this -extern IEngineGraphics *Graphics(); - -void inp_mouse_relative(int *x, int *y) -{ - int nx = 0, ny = 0; - float sens = config.inp_mousesens/100.0f; - - if(config.inp_grab) - SDL_GetRelativeMouseState(&nx, &ny); - else - { - if(input_grabbed) - { - SDL_GetMouseState(&nx,&ny); - SDL_WarpMouse(Graphics()->ScreenWidth()/2,Graphics()->ScreenHeight()/2); - nx -= Graphics()->ScreenWidth()/2; ny -= Graphics()->ScreenHeight()/2; - } - } - - *x = nx*sens; - *y = ny*sens; -} - -enum -{ - INPUT_BUFFER_SIZE=32 -}; - -static INPUT_EVENT input_events[INPUT_BUFFER_SIZE]; -static int num_events = 0; - -static void add_event(int unicode, int key, int flags) -{ - if(num_events != INPUT_BUFFER_SIZE) - { - input_events[num_events].unicode = unicode; - input_events[num_events].key = key; - input_events[num_events].flags = flags; - num_events++; - } -} - -int inp_num_events() -{ - return num_events; -} - -void inp_clear_events() -{ - num_events = 0; -} - -INPUT_EVENT inp_get_event(int index) -{ - if(index < 0 || index >= num_events) - { - INPUT_EVENT e = {0,0}; - return e; - } - - return input_events[index]; -} - -void inp_init() -{ - SDL_EnableUNICODE(1); - SDL_EnableKeyRepeat(SDL_DEFAULT_REPEAT_DELAY, SDL_DEFAULT_REPEAT_INTERVAL); -} - -void inp_mouse_mode_absolute() -{ - SDL_ShowCursor(1); - input_grabbed = 0; - if(config.inp_grab) - SDL_WM_GrabInput(SDL_GRAB_OFF); -} - -void inp_mouse_mode_relative() -{ - SDL_ShowCursor(0); - input_grabbed = 1; - if(config.inp_grab) - SDL_WM_GrabInput(SDL_GRAB_ON); -} - -int inp_mouse_doubleclick() -{ - return release_delta < (time_freq() >> 2); -} - -void inp_clear_key_states() -{ - mem_zero(input_state, sizeof(input_state)); - mem_zero(input_count, sizeof(input_count)); -} - -int inp_key_presses(int key) -{ - return input_count[input_current][key].presses; -} - -int inp_key_releases(int key) -{ - return input_count[input_current][key].releases; -} - -int inp_key_state(int key) -{ - return input_state[input_current][key]; -} - -int inp_key_pressed(int key) { return input_state[input_current][key]; } -int inp_key_was_pressed(int key) { return input_state[input_current^1][key]; } -int inp_key_down(int key) { return inp_key_pressed(key)&&!inp_key_was_pressed(key); } -int inp_button_pressed(int button) { return input_state[input_current][button]; } - -void inp_update() -{ - int i; - - if(input_grabbed && !Graphics()->WindowActive()) - inp_mouse_mode_absolute(); - - /*if(!input_grabbed && Graphics()->WindowActive()) - inp_mouse_mode_relative();*/ - - /* clear and begin count on the other one */ - input_current^=1; - mem_zero(&input_count[input_current], sizeof(input_count[input_current])); - mem_zero(&input_state[input_current], sizeof(input_state[input_current])); - - { - Uint8 *state = SDL_GetKeyState(&i); - if(i >= KEY_LAST) - i = KEY_LAST-1; - mem_copy(input_state[input_current], state, i); - } - - /* these states must always be updated manually because they are not in the GetKeyState from SDL */ - i = SDL_GetMouseState(NULL, NULL); - if(i&SDL_BUTTON(1)) input_state[input_current][KEY_MOUSE_1] = 1; /* 1 is left */ - if(i&SDL_BUTTON(3)) input_state[input_current][KEY_MOUSE_2] = 1; /* 3 is right */ - if(i&SDL_BUTTON(2)) input_state[input_current][KEY_MOUSE_3] = 1; /* 2 is middle */ - if(i&SDL_BUTTON(4)) input_state[input_current][KEY_MOUSE_4] = 1; - if(i&SDL_BUTTON(5)) input_state[input_current][KEY_MOUSE_5] = 1; - if(i&SDL_BUTTON(6)) input_state[input_current][KEY_MOUSE_6] = 1; - if(i&SDL_BUTTON(7)) input_state[input_current][KEY_MOUSE_7] = 1; - if(i&SDL_BUTTON(8)) input_state[input_current][KEY_MOUSE_8] = 1; - - { - SDL_Event event; - - while(SDL_PollEvent(&event)) - { - int key = -1; - int action = INPFLAG_PRESS; - switch (event.type) - { - /* handle keys */ - case SDL_KEYDOWN: - /*if(event.key.keysym.unicode < 255) */ - add_event(event.key.keysym.unicode, 0, 0); - key = event.key.keysym.sym; - break; - case SDL_KEYUP: - action = INPFLAG_RELEASE; - key = event.key.keysym.sym; - break; - - /* handle mouse buttons */ - case SDL_MOUSEBUTTONUP: - action = INPFLAG_RELEASE; - - if(event.button.button == 1) - { - release_delta = time_get() - last_release; - last_release = time_get(); - } - - /* fall through */ - case SDL_MOUSEBUTTONDOWN: - if(event.button.button == SDL_BUTTON_LEFT) key = KEY_MOUSE_1; - if(event.button.button == SDL_BUTTON_RIGHT) key = KEY_MOUSE_2; - if(event.button.button == SDL_BUTTON_MIDDLE) key = KEY_MOUSE_3; - if(event.button.button == SDL_BUTTON_WHEELUP) key = KEY_MOUSE_WHEEL_UP; - if(event.button.button == SDL_BUTTON_WHEELDOWN) key = KEY_MOUSE_WHEEL_DOWN; - if(event.button.button == 6) key = KEY_MOUSE_6; - if(event.button.button == 7) key = KEY_MOUSE_7; - if(event.button.button == 8) key = KEY_MOUSE_8; - break; - - /* other messages */ - case SDL_QUIT: - /* TODO: cleaner exit */ - exit(0); - break; - } - - /* */ - if(key != -1) - { - input_count[input_current][key].presses++; - if(action == INPFLAG_PRESS) - input_state[input_current][key] = 1; - add_event(0, key, action); - } - - } - } -} diff --git a/src/engine/client/ec_snd.cpp b/src/engine/client/ec_snd.cpp deleted file mode 100644 index 3baea982..00000000 --- a/src/engine/client/ec_snd.cpp +++ /dev/null @@ -1,471 +0,0 @@ -/* copyright (c) 2007 magnus auvinen, see licence.txt for more info */ -#include <base/system.h> -#include <engine/e_client_interface.h> -#include <engine/client/graphics.h> -#include <engine/e_config.h> -#include <engine/e_engine.h> - -#include "SDL.h" - -extern "C" { // wavpack - #include <engine/external/wavpack/wavpack.h> -} -#include <stdio.h> -#include <stdlib.h> -#include <math.h> - -enum -{ - NUM_SAMPLES = 512, - NUM_VOICES = 64, - NUM_CHANNELS = 16, - - MAX_FRAMES = 1024 -}; - -typedef struct -{ - short *data; - int num_frames; - int rate; - int channels; - int loop_start; - int loop_end; -} SAMPLE; - -typedef struct -{ - int vol; - int pan; -} CHANNEL; - -typedef struct -{ - SAMPLE *snd; - CHANNEL *channel; - int tick; - int vol; /* 0 - 255 */ - int flags; - int x, y; -} VOICE; - -static SAMPLE samples[NUM_SAMPLES] = { {0} }; -static VOICE voices[NUM_VOICES] = { {0} }; -static CHANNEL channels[NUM_CHANNELS] = { {255, 0} }; - -static LOCK sound_lock = 0; -static int sound_enabled = 0; - -static int center_x = 0; -static int center_y = 0; - -static int mixing_rate = 48000; -static volatile int sound_volume = 100; - -static int next_voice = 0; - -void snd_set_channel(int cid, float vol, float pan) -{ - channels[cid].vol = (int)(vol*255.0f); - channels[cid].pan = (int)(pan*255.0f); /* TODO: this is only on and off right now */ -} - -static int play(int cid, int sid, int flags, float x, float y) -{ - int vid = -1; - int i; - - lock_wait(sound_lock); - - /* search for voice */ - for(i = 0; i < NUM_VOICES; i++) - { - int id = (next_voice + i) % NUM_VOICES; - if(!voices[id].snd) - { - vid = id; - next_voice = id+1; - break; - } - } - - /* voice found, use it */ - if(vid != -1) - { - voices[vid].snd = &samples[sid]; - voices[vid].channel = &channels[cid]; - voices[vid].tick = 0; - voices[vid].vol = 255; - voices[vid].flags = flags; - voices[vid].x = (int)x; - voices[vid].y = (int)y; - } - - lock_release(sound_lock); - return vid; -} - -int snd_play_at(int cid, int sid, int flags, float x, float y) -{ - return play(cid, sid, flags|SNDFLAG_POS, x, y); -} - -int snd_play(int cid, int sid, int flags) -{ - return play(cid, sid, flags, 0, 0); -} - -void snd_stop(int vid) -{ - /* TODO: a nice fade out */ - lock_wait(sound_lock); - voices[vid].snd = 0; - lock_release(sound_lock); -} - -/* TODO: there should be a faster way todo this */ -static short int2short(int i) -{ - if(i > 0x7fff) - return 0x7fff; - else if(i < -0x7fff) - return -0x7fff; - return i; -} - -static int iabs(int i) -{ - if(i<0) - return -i; - return i; -} - -static void mix(short *final_out, unsigned frames) -{ - int mix_buffer[MAX_FRAMES*2] = {0}; - int master_vol; - - /* aquire lock while we are mixing */ - lock_wait(sound_lock); - - master_vol = sound_volume; - - for(unsigned i = 0; i < NUM_VOICES; i++) - { - if(voices[i].snd) - { - /* mix voice */ - VOICE *v = &voices[i]; - int *out = mix_buffer; - - int step = v->snd->channels; /* setup input sources */ - short *in_l = &v->snd->data[v->tick*step]; - short *in_r = &v->snd->data[v->tick*step+1]; - - unsigned end = v->snd->num_frames-v->tick; - - int rvol = v->channel->vol; - int lvol = v->channel->vol; - - /* make sure that we don't go outside the sound data */ - if(frames < end) - end = frames; - - /* check if we have a mono sound */ - if(v->snd->channels == 1) - in_r = in_l; - - /* volume calculation */ - if(v->flags&SNDFLAG_POS && v->channel->pan) - { - /* TODO: we should respect the channel panning value */ - const int range = 1500; /* magic value, remove */ - int dx = v->x - center_x; - int dy = v->y - center_y; - int dist = sqrt(dx*dx+dy*dy); /* double here. nasty */ - int p = iabs(dx); - if(dist < range) - { - /* panning */ - if(dx > 0) - lvol = ((range-p)*lvol)/range; - else - rvol = ((range-p)*rvol)/range; - - /* falloff */ - lvol = (lvol*(range-dist))/range; - rvol = (rvol*(range-dist))/range; - } - else - { - lvol = 0; - rvol = 0; - } - } - - /* process all frames */ - for(unsigned s = 0; s < end; s++) - { - *out++ += (*in_l)*lvol; - *out++ += (*in_r)*rvol; - in_l += step; - in_r += step; - v->tick++; - } - - /* free voice if not used any more */ - if(v->tick == v->snd->num_frames) - v->snd = 0; - - } - } - - - /* release the lock */ - lock_release(sound_lock); - - { - /* clamp accumulated values */ - /* TODO: this seams slow */ - for(unsigned i = 0; i < frames; i++) - { - int j = i<<1; - int vl = ((mix_buffer[j]*master_vol)/101)>>8; - int vr = ((mix_buffer[j+1]*master_vol)/101)>>8; - - final_out[j] = int2short(vl); - final_out[j+1] = int2short(vr); - } - } - -#if defined(CONF_ARCH_ENDIAN_BIG) - swap_endian(final_out, sizeof(short), frames * 2); -#endif -} - -static void sdlcallback(void *unused, Uint8 *stream, int len) -{ - mix((short *)stream, len/2/2); -} - -int snd_init() -{ - SDL_AudioSpec format; - - sound_lock = lock_create(); - - if(!config.snd_enable) - return 0; - - mixing_rate = config.snd_rate; - - /* Set 16-bit stereo audio at 22Khz */ - format.freq = config.snd_rate; - format.format = AUDIO_S16; - format.channels = 2; - format.samples = config.snd_buffer_size; - format.callback = sdlcallback; - format.userdata = NULL; - - /* Open the audio device and start playing sound! */ - if(SDL_OpenAudio(&format, NULL) < 0) - { - dbg_msg("client/sound", "unable to open audio: %s", SDL_GetError()); - return -1; - } - else - dbg_msg("client/sound", "sound init successful"); - - SDL_PauseAudio(0); - - sound_enabled = 1; - snd_update(); /* update the volume */ - return 0; -} - -// TODO: Refactor: Remove this -extern IEngineGraphics *Graphics(); - - -int snd_update() -{ - /* update volume */ - int wanted_volume = config.snd_volume; - - if(!Graphics()->WindowActive() && config.snd_nonactive_mute) - wanted_volume = 0; - - if(wanted_volume != sound_volume) - { - lock_wait(sound_lock); - sound_volume = wanted_volume; - lock_release(sound_lock); - } - - return 0; -} - -int snd_shutdown() -{ - SDL_CloseAudio(); - lock_destroy(sound_lock); - return 0; -} - -int snd_alloc_id() -{ - /* TODO: linear search, get rid of it */ - unsigned sid; - for(sid = 0; sid < NUM_SAMPLES; sid++) - { - if(samples[sid].data == 0x0) - return sid; - } - - return -1; -} - -static void rate_convert(int sid) -{ - SAMPLE *snd = &samples[sid]; - int num_frames = 0; - short *new_data = 0; - int i; - - /* make sure that we need to convert this sound */ - if(!snd->data || snd->rate == mixing_rate) - return; - - /* allocate new data */ - num_frames = (int)((snd->num_frames/(float)snd->rate)*mixing_rate); - new_data = (short *)mem_alloc(num_frames*snd->channels*sizeof(short), 1); - - for(i = 0; i < num_frames; i++) - { - /* resample TODO: this should be done better, like linear atleast */ - float a = i/(float)num_frames; - int f = (int)(a*snd->num_frames); - if(f >= snd->num_frames) - f = snd->num_frames-1; - - /* set new data */ - if(snd->channels == 1) - new_data[i] = snd->data[f]; - else if(snd->channels == 2) - { - new_data[i*2] = snd->data[f*2]; - new_data[i*2+1] = snd->data[f*2+1]; - } - } - - /* free old data and apply new */ - mem_free(snd->data); - snd->data = new_data; - snd->num_frames = num_frames; -} - - -static IOHANDLE file = NULL; - -static int read_data(void *buffer, int size) -{ - return io_read(file, buffer, size); -} - -int snd_load_wv(const char *filename) -{ - SAMPLE *snd; - int sid = -1; - char error[100]; - WavpackContext *context; - - /* don't waste memory on sound when we are stress testing */ - if(config.dbg_stress) - return -1; - - /* no need to load sound when we are running with no sound */ - if(!sound_enabled) - return 1; - - file = engine_openfile(filename, IOFLAG_READ); /* TODO: use system.h stuff for this */ - if(!file) - { - dbg_msg("sound/wv", "failed to open %s", filename); - return -1; - } - - sid = snd_alloc_id(); - if(sid < 0) - return -1; - snd = &samples[sid]; - - context = WavpackOpenFileInput(read_data, error); - if (context) - { - int samples = WavpackGetNumSamples(context); - int bitspersample = WavpackGetBitsPerSample(context); - unsigned int samplerate = WavpackGetSampleRate(context); - int channels = WavpackGetNumChannels(context); - int *data; - int *src; - short *dst; - int i; - - snd->channels = channels; - snd->rate = samplerate; - - if(snd->channels > 2) - { - dbg_msg("sound/wv", "file is not mono or stereo. filename='%s'", filename); - return -1; - } - - /* - if(snd->rate != 44100) - { - dbg_msg("sound/wv", "file is %d Hz, not 44100 Hz. filename='%s'", snd->rate, filename); - return -1; - }*/ - - if(bitspersample != 16) - { - dbg_msg("sound/wv", "bps is %d, not 16, filname='%s'", bitspersample, filename); - return -1; - } - - data = (int *)mem_alloc(4*samples*channels, 1); - WavpackUnpackSamples(context, data, samples); /* TODO: check return value */ - src = data; - - snd->data = (short *)mem_alloc(2*samples*channels, 1); - dst = snd->data; - - for (i = 0; i < samples*channels; i++) - *dst++ = (short)*src++; - - mem_free(data); - - snd->num_frames = samples; - snd->loop_start = -1; - snd->loop_end = -1; - } - else - { - dbg_msg("sound/wv", "failed to open %s: %s", filename, error); - } - - io_close(file); - file = NULL; - - if(config.debug) - dbg_msg("sound/wv", "loaded %s", filename); - - rate_convert(sid); - return sid; -} - -void snd_set_listener_pos(float x, float y) -{ - center_x = (int)x; - center_y = (int)y; -} diff --git a/src/engine/client/ec_srvbrowse.cpp b/src/engine/client/ec_srvbrowse.cpp deleted file mode 100644 index 1b04937a..00000000 --- a/src/engine/client/ec_srvbrowse.cpp +++ /dev/null @@ -1,736 +0,0 @@ -/* copyright (c) 2007 magnus auvinen, see licence.txt for more info */ -#include <base/system.h> -#include <engine/e_network.h> -#include <engine/e_client_interface.h> -#include <engine/e_config.h> -#include <engine/e_memheap.h> -#include <engine/e_engine.h> - -#include <mastersrv/mastersrv.h> - -#include <string.h> -#include <stdlib.h> - -extern CNetClient m_NetClient; - - -/* ------ server browse ---- */ -/* TODO: move all this to a separate file */ - -typedef struct SERVERENTRY_t SERVERENTRY; -struct SERVERENTRY_t -{ - NETADDR addr; - int64 request_time; - int got_info; - SERVER_INFO info; - - SERVERENTRY *next_ip; /* ip hashed list */ - - SERVERENTRY *prev_req; /* request list */ - SERVERENTRY *next_req; -}; - -static HEAP *serverlist_heap = 0; -static SERVERENTRY **serverlist = 0; -static int *sorted_serverlist = 0; - -enum -{ - MAX_FAVORITES=256 -}; - -static NETADDR favorite_servers[MAX_FAVORITES]; -static int num_favorite_servers = 0; - -static SERVERENTRY *serverlist_ip[256] = {0}; /* ip hash list */ - -static SERVERENTRY *first_req_server = 0; /* request list */ -static SERVERENTRY *last_req_server = 0; -static int num_requests = 0; - -static int need_refresh = 0; - -static int num_sorted_servers = 0; -static int num_sorted_servers_capacity = 0; -static int num_servers = 0; -static int num_server_capacity = 0; - -static int sorthash = 0; -static char filterstring[64] = {0}; -static char filtergametypestring[128] = {0}; - -/* the token is to keep server refresh separated from each other */ -static int current_token = 1; - -static int serverlist_type = 0; -static int64 broadcast_time = 0; - -int client_serverbrowse_lan() { return serverlist_type == BROWSETYPE_LAN; } -int client_serverbrowse_num() { return num_servers; } - -SERVER_INFO *client_serverbrowse_get(int index) -{ - if(index < 0 || index >= num_servers) - return 0; - return &serverlist[index]->info; -} - -int client_serverbrowse_sorted_num() { return num_sorted_servers; } - -SERVER_INFO *client_serverbrowse_sorted_get(int index) -{ - if(index < 0 || index >= num_sorted_servers) - return 0; - return &serverlist[sorted_serverlist[index]]->info; -} - - -int client_serverbrowse_num_requests() -{ - return num_requests; -} - -static int client_serverbrowse_sort_compare_name(const void *ai, const void *bi) -{ - SERVERENTRY *a = serverlist[*(const int*)ai]; - SERVERENTRY *b = serverlist[*(const int*)bi]; - return strcmp(a->info.name, b->info.name); -} - -static int client_serverbrowse_sort_compare_map(const void *ai, const void *bi) -{ - SERVERENTRY *a = serverlist[*(const int*)ai]; - SERVERENTRY *b = serverlist[*(const int*)bi]; - return strcmp(a->info.map, b->info.map); -} - -static int client_serverbrowse_sort_compare_ping(const void *ai, const void *bi) -{ - SERVERENTRY *a = serverlist[*(const int*)ai]; - SERVERENTRY *b = serverlist[*(const int*)bi]; - if(a->info.latency > b->info.latency) return 1; - if(a->info.latency < b->info.latency) return -1; - return 0; -} - -static int client_serverbrowse_sort_compare_gametype(const void *ai, const void *bi) -{ - SERVERENTRY *a = serverlist[*(const int*)ai]; - SERVERENTRY *b = serverlist[*(const int*)bi]; - return strcmp(a->info.gametype, b->info.gametype); -} - -static int client_serverbrowse_sort_compare_progression(const void *ai, const void *bi) -{ - SERVERENTRY *a = serverlist[*(const int*)ai]; - SERVERENTRY *b = serverlist[*(const int*)bi]; - if(a->info.progression > b->info.progression) return 1; - if(a->info.progression < b->info.progression) return -1; - return 0; -} - -static int client_serverbrowse_sort_compare_numplayers(const void *ai, const void *bi) -{ - SERVERENTRY *a = serverlist[*(const int*)ai]; - SERVERENTRY *b = serverlist[*(const int*)bi]; - if(a->info.num_players > b->info.num_players) return 1; - if(a->info.num_players < b->info.num_players) return -1; - return 0; -} - -static void client_serverbrowse_filter() -{ - int i = 0, p = 0; - num_sorted_servers = 0; - - /* allocate the sorted list */ - if(num_sorted_servers_capacity < num_servers) - { - if(sorted_serverlist) - mem_free(sorted_serverlist); - num_sorted_servers_capacity = num_servers; - sorted_serverlist = (int *)mem_alloc(num_sorted_servers_capacity*sizeof(int), 1); - } - - /* filter the servers */ - for(i = 0; i < num_servers; i++) - { - int filtered = 0; - - if(config.b_filter_empty && serverlist[i]->info.num_players == 0) - filtered = 1; - else if(config.b_filter_full && serverlist[i]->info.num_players == serverlist[i]->info.max_players) - filtered = 1; - else if(config.b_filter_pw && serverlist[i]->info.flags&SRVFLAG_PASSWORD) - filtered = 1; - else if(config.b_filter_pure && (strcmp(serverlist[i]->info.gametype, "DM") != 0 && strcmp(serverlist[i]->info.gametype, "TDM") != 0 && strcmp(serverlist[i]->info.gametype, "CTF") != 0)) - filtered = 1; - else if(config.b_filter_pure_map && - !(strcmp(serverlist[i]->info.map, "dm1") == 0 || - strcmp(serverlist[i]->info.map, "dm2") == 0 || - strcmp(serverlist[i]->info.map, "dm6") == 0 || - strcmp(serverlist[i]->info.map, "dm7") == 0 || - strcmp(serverlist[i]->info.map, "dm8") == 0 || - strcmp(serverlist[i]->info.map, "dm9") == 0 || - strcmp(serverlist[i]->info.map, "ctf1") == 0 || - strcmp(serverlist[i]->info.map, "ctf2") == 0 || - strcmp(serverlist[i]->info.map, "ctf3") == 0 || - strcmp(serverlist[i]->info.map, "ctf4") == 0 || - strcmp(serverlist[i]->info.map, "ctf5") == 0) - ) - { - filtered = 1; - } - else if(config.b_filter_ping < serverlist[i]->info.latency) - filtered = 1; - else if(config.b_filter_compatversion && strncmp(serverlist[i]->info.version, modc_net_version(), 3) != 0) - filtered = 1; - else - { - if(config.b_filter_string[0] != 0) - { - int matchfound = 0; - - serverlist[i]->info.quicksearch_hit = 0; - - /* match against server name */ - if(str_find_nocase(serverlist[i]->info.name, config.b_filter_string)) - { - matchfound = 1; - serverlist[i]->info.quicksearch_hit |= BROWSEQUICK_SERVERNAME; - } - - /* match against players */ - for(p = 0; p < serverlist[i]->info.num_players; p++) - { - if(str_find_nocase(serverlist[i]->info.players[p].name, config.b_filter_string)) - { - matchfound = 1; - serverlist[i]->info.quicksearch_hit |= BROWSEQUICK_PLAYERNAME; - break; - } - } - - /* match against map */ - if(str_find_nocase(serverlist[i]->info.map, config.b_filter_string)) - { - matchfound = 1; - serverlist[i]->info.quicksearch_hit |= BROWSEQUICK_MAPNAME; - } - - if(!matchfound) - filtered = 1; - } - - if(!filtered && config.b_filter_gametype[0] != 0) - { - /* match against game type */ - if(!str_find_nocase(serverlist[i]->info.gametype, config.b_filter_gametype)) - filtered = 1; - } - } - - if(filtered == 0) - sorted_serverlist[num_sorted_servers++] = i; - } -} - -static int client_serverbrowse_sorthash() -{ - int i = config.b_sort&0xf; - i |= config.b_filter_empty<<4; - i |= config.b_filter_full<<5; - i |= config.b_filter_pw<<6; - i |= config.b_sort_order<<7; - i |= config.b_filter_compatversion<<8; - i |= config.b_filter_pure<<9; - i |= config.b_filter_pure_map<<10; - i |= config.b_filter_ping<<16; - return i; -} - -static void client_serverbrowse_sort() -{ - int i; - - /* create filtered list */ - client_serverbrowse_filter(); - - /* sort */ - if(config.b_sort == BROWSESORT_NAME) - qsort(sorted_serverlist, num_sorted_servers, sizeof(int), client_serverbrowse_sort_compare_name); - else if(config.b_sort == BROWSESORT_PING) - qsort(sorted_serverlist, num_sorted_servers, sizeof(int), client_serverbrowse_sort_compare_ping); - else if(config.b_sort == BROWSESORT_MAP) - qsort(sorted_serverlist, num_sorted_servers, sizeof(int), client_serverbrowse_sort_compare_map); - else if(config.b_sort == BROWSESORT_NUMPLAYERS) - qsort(sorted_serverlist, num_sorted_servers, sizeof(int), client_serverbrowse_sort_compare_numplayers); - else if(config.b_sort == BROWSESORT_GAMETYPE) - qsort(sorted_serverlist, num_sorted_servers, sizeof(int), client_serverbrowse_sort_compare_gametype); - else if(config.b_sort == BROWSESORT_PROGRESSION) - qsort(sorted_serverlist, num_sorted_servers, sizeof(int), client_serverbrowse_sort_compare_progression); - - /* invert the list if requested */ - if(config.b_sort_order) - { - for(i = 0; i < num_sorted_servers/2; i++) - { - int temp = sorted_serverlist[i]; - sorted_serverlist[i] = sorted_serverlist[num_sorted_servers-i-1]; - sorted_serverlist[num_sorted_servers-i-1] = temp; - } - } - - /* set indexes */ - for(i = 0; i < num_sorted_servers; i++) - serverlist[sorted_serverlist[i]]->info.sorted_index = i; - - str_copy(filtergametypestring, config.b_filter_gametype, sizeof(filtergametypestring)); - str_copy(filterstring, config.b_filter_string, sizeof(filterstring)); - sorthash = client_serverbrowse_sorthash(); -} - -static void client_serverbrowse_remove_request(SERVERENTRY *entry) -{ - if(entry->prev_req || entry->next_req || first_req_server == entry) - { - if(entry->prev_req) - entry->prev_req->next_req = entry->next_req; - else - first_req_server = entry->next_req; - - if(entry->next_req) - entry->next_req->prev_req = entry->prev_req; - else - last_req_server = entry->prev_req; - - entry->prev_req = 0; - entry->next_req = 0; - num_requests--; - } -} - -static SERVERENTRY *client_serverbrowse_find(NETADDR *addr) -{ - SERVERENTRY *entry = serverlist_ip[addr->ip[0]]; - - for(; entry; entry = entry->next_ip) - { - if(net_addr_comp(&entry->addr, addr) == 0) - return entry; - } - return (SERVERENTRY*)0; -} - -void client_serverbrowse_queuerequest(SERVERENTRY *entry) -{ - /* add it to the list of servers that we should request info from */ - entry->prev_req = last_req_server; - if(last_req_server) - last_req_server->next_req = entry; - else - first_req_server = entry; - last_req_server = entry; - - num_requests++; -} - -void client_serverbrowse_setinfo(SERVERENTRY *entry, SERVER_INFO *info) -{ - int fav = entry->info.favorite; - entry->info = *info; - entry->info.favorite = fav; - entry->info.netaddr = entry->addr; - - // all these are just for nice compability - if(entry->info.gametype[0] == '0' && entry->info.gametype[1] == 0) - str_copy(entry->info.gametype, "DM", sizeof(entry->info.gametype)); - else if(entry->info.gametype[0] == '1' && entry->info.gametype[1] == 0) - str_copy(entry->info.gametype, "TDM", sizeof(entry->info.gametype)); - else if(entry->info.gametype[0] == '2' && entry->info.gametype[1] == 0) - str_copy(entry->info.gametype, "CTF", sizeof(entry->info.gametype)); - - /*if(!request) - { - entry->info.latency = (time_get()-entry->request_time)*1000/time_freq(); - client_serverbrowse_remove_request(entry); - }*/ - - entry->got_info = 1; - client_serverbrowse_sort(); -} - -SERVERENTRY *client_serverbrowse_add(NETADDR *addr) -{ - int hash = addr->ip[0]; - SERVERENTRY *entry = 0; - int i; - - /* create new entry */ - entry = (SERVERENTRY *)memheap_allocate(serverlist_heap, sizeof(SERVERENTRY)); - mem_zero(entry, sizeof(SERVERENTRY)); - - /* set the info */ - entry->addr = *addr; - entry->info.netaddr = *addr; - - entry->info.latency = 999; - str_format(entry->info.address, sizeof(entry->info.address), "%d.%d.%d.%d:%d", - addr->ip[0], addr->ip[1], addr->ip[2], - addr->ip[3], addr->port); - str_format(entry->info.name, sizeof(entry->info.name), "\255%d.%d.%d.%d:%d", /* the \255 is to make sure that it's sorted last */ - addr->ip[0], addr->ip[1], addr->ip[2], - addr->ip[3], addr->port); - - /*if(serverlist_type == BROWSETYPE_LAN) - entry->info.latency = (time_get()-broadcast_time)*1000/time_freq();*/ - - /* check if it's a favorite */ - for(i = 0; i < num_favorite_servers; i++) - { - if(net_addr_comp(addr, &favorite_servers[i]) == 0) - entry->info.favorite = 1; - } - - /* add to the hash list */ - entry->next_ip = serverlist_ip[hash]; - serverlist_ip[hash] = entry; - - if(num_servers == num_server_capacity) - { - SERVERENTRY **newlist; - num_server_capacity += 100; - newlist = (SERVERENTRY **)mem_alloc(num_server_capacity*sizeof(SERVERENTRY*), 1); - mem_copy(newlist, serverlist, num_servers*sizeof(SERVERENTRY*)); - mem_free(serverlist); - serverlist = newlist; - } - - /* add to list */ - serverlist[num_servers] = entry; - entry->info.server_index = num_servers; - num_servers++; - - return entry; -} - -void client_serverbrowse_set(NETADDR *addr, int type, int token, SERVER_INFO *info) -{ - SERVERENTRY *entry = 0; - if(type == BROWSESET_MASTER_ADD) - { - if(serverlist_type != BROWSETYPE_INTERNET) - return; - - if(!client_serverbrowse_find(addr)) - { - entry = client_serverbrowse_add(addr); - client_serverbrowse_queuerequest(entry); - } - } - else if(type == BROWSESET_FAV_ADD) - { - if(serverlist_type != BROWSETYPE_FAVORITES) - return; - - if(!client_serverbrowse_find(addr)) - { - entry = client_serverbrowse_add(addr); - client_serverbrowse_queuerequest(entry); - } - } - else if(type == BROWSESET_TOKEN) - { - if(token != current_token) - return; - - entry = client_serverbrowse_find(addr); - if(!entry) - entry = client_serverbrowse_add(addr); - if(entry) - { - client_serverbrowse_setinfo(entry, info); - if(serverlist_type == BROWSETYPE_LAN) - entry->info.latency = (time_get()-broadcast_time)*1000/time_freq(); - else - entry->info.latency = (time_get()-entry->request_time)*1000/time_freq(); - client_serverbrowse_remove_request(entry); - } - } - else if(type == BROWSESET_OLD_INTERNET) - { - entry = client_serverbrowse_find(addr); - if(entry) - { - client_serverbrowse_setinfo(entry, info); - - if(serverlist_type == BROWSETYPE_LAN) - entry->info.latency = (time_get()-broadcast_time)*1000/time_freq(); - else - entry->info.latency = (time_get()-entry->request_time)*1000/time_freq(); - client_serverbrowse_remove_request(entry); - } - } - else if(type == BROWSESET_OLD_LAN) - { - entry = client_serverbrowse_find(addr); - if(entry) - if(!entry) - entry = client_serverbrowse_add(addr); - if(entry) - client_serverbrowse_setinfo(entry, info); - } - - client_serverbrowse_sort(); -} - -void client_serverbrowse_refresh(int type) -{ - /* clear out everything */ - if(serverlist_heap) - memheap_destroy(serverlist_heap); - serverlist_heap = memheap_create(); - num_servers = 0; - num_sorted_servers = 0; - mem_zero(serverlist_ip, sizeof(serverlist_ip)); - first_req_server = 0; - last_req_server = 0; - num_requests = 0; - - /* next token */ - current_token = (current_token+1)&0xff; - - /* */ - serverlist_type = type; - - if(type == BROWSETYPE_LAN) - { - unsigned char Buffer[sizeof(SERVERBROWSE_GETINFO)+1]; - CNetChunk Packet; - int i; - - mem_copy(Buffer, SERVERBROWSE_GETINFO, sizeof(SERVERBROWSE_GETINFO)); - Buffer[sizeof(SERVERBROWSE_GETINFO)] = current_token; - - Packet.m_ClientID = -1; - mem_zero(&Packet, sizeof(Packet)); - Packet.m_Address.ip[0] = 255; - Packet.m_Address.ip[1] = 255; - Packet.m_Address.ip[2] = 255; - Packet.m_Address.ip[3] = 255; - Packet.m_Flags = NETSENDFLAG_CONNLESS; - Packet.m_DataSize = sizeof(Buffer); - Packet.m_pData = Buffer; - broadcast_time = time_get(); - - for(i = 8303; i <= 8310; i++) - { - Packet.m_Address.port = i; - m_NetClient.Send(&Packet); - } - - if(config.debug) - dbg_msg("client", "broadcasting for servers"); - } - else if(type == BROWSETYPE_INTERNET) - need_refresh = 1; - else if(type == BROWSETYPE_FAVORITES) - { - for(int i = 0; i < num_favorite_servers; i++) - client_serverbrowse_set(&favorite_servers[i], BROWSESET_FAV_ADD, -1, 0); - } -} - -static void client_serverbrowse_request_impl(NETADDR *addr, SERVERENTRY *entry) -{ - /*unsigned char buffer[sizeof(SERVERBROWSE_GETINFO)+1];*/ - CNetChunk Packet; - - if(config.debug) - { - dbg_msg("client", "requesting server info from %d.%d.%d.%d:%d", - addr->ip[0], addr->ip[1], addr->ip[2], - addr->ip[3], addr->port); - } - - /*mem_copy(buffer, SERVERBROWSE_GETINFO, sizeof(SERVERBROWSE_GETINFO)); - buffer[sizeof(SERVERBROWSE_GETINFO)] = current_token;*/ - - Packet.m_ClientID = -1; - Packet.m_Address = *addr; - Packet.m_Flags = NETSENDFLAG_CONNLESS; - /*p.data_size = sizeof(buffer); - p.data = buffer; - netclient_send(net, &p);*/ - - /* send old requtest style aswell */ - Packet.m_DataSize = sizeof(SERVERBROWSE_OLD_GETINFO); - Packet.m_pData = SERVERBROWSE_OLD_GETINFO; - m_NetClient.Send(&Packet); - - if(entry) - entry->request_time = time_get(); -} - -void client_serverbrowse_request(NETADDR *addr) -{ - client_serverbrowse_request_impl(addr, 0); -} - - -void client_serverbrowse_update() -{ - int64 timeout = time_freq(); - int64 now = time_get(); - int count; - SERVERENTRY *entry, *next; - - /* do server list requests */ - if(need_refresh && !mastersrv_refreshing()) - { - NETADDR addr; - CNetChunk Packet; - int i; - - need_refresh = 0; - - mem_zero(&Packet, sizeof(Packet)); - Packet.m_ClientID = -1; - Packet.m_Flags = NETSENDFLAG_CONNLESS; - Packet.m_DataSize = sizeof(SERVERBROWSE_GETLIST); - Packet.m_pData = SERVERBROWSE_GETLIST; - - for(i = 0; i < MAX_MASTERSERVERS; i++) - { - addr = mastersrv_get(i); - if(!addr.ip[0] && !addr.ip[1] && !addr.ip[2] && !addr.ip[3]) - continue; - - Packet.m_Address = addr; - m_NetClient.Send(&Packet); - } - - if(config.debug) - dbg_msg("client", "requesting server list"); - } - - /* do timeouts */ - entry = first_req_server; - while(1) - { - if(!entry) /* no more entries */ - break; - - next = entry->next_req; - - if(entry->request_time && entry->request_time+timeout < now) - { - /* timeout */ - client_serverbrowse_remove_request(entry); - num_requests--; - } - - entry = next; - } - - /* do timeouts */ - entry = first_req_server; - count = 0; - while(1) - { - if(!entry) /* no more entries */ - break; - - /* no more then 10 concurrent requests */ - if(count == config.b_max_requests) - break; - - if(entry->request_time == 0) - client_serverbrowse_request_impl(&entry->addr, entry); - - count++; - entry = entry->next_req; - } - - /* check if we need to resort */ - /* TODO: remove the strcmp */ - if(sorthash != client_serverbrowse_sorthash() || strcmp(filterstring, config.b_filter_string) != 0 || strcmp(filtergametypestring, config.b_filter_gametype) != 0) - client_serverbrowse_sort(); -} - - -int client_serverbrowse_isfavorite(NETADDR addr) -{ - /* search for the address */ - int i; - for(i = 0; i < num_favorite_servers; i++) - { - if(net_addr_comp(&addr, &favorite_servers[i]) == 0) - return 1; - } - return 0; -} - -void client_serverbrowse_addfavorite(NETADDR addr) -{ - int i; - SERVERENTRY *entry; - - if(num_favorite_servers == MAX_FAVORITES) - return; - - /* make sure that we don't already have the server in our list */ - for(i = 0; i < num_favorite_servers; i++) - { - if(net_addr_comp(&addr, &favorite_servers[i]) == 0) - return; - } - - /* add the server to the list */ - favorite_servers[num_favorite_servers++] = addr; - entry = client_serverbrowse_find(&addr); - if(entry) - entry->info.favorite = 1; - dbg_msg("", "added fav, %p", entry); -} - -void client_serverbrowse_removefavorite(NETADDR addr) -{ - int i; - SERVERENTRY *entry; - - for(i = 0; i < num_favorite_servers; i++) - { - if(net_addr_comp(&addr, &favorite_servers[i]) == 0) - { - mem_move(&favorite_servers[i], &favorite_servers[i+1], num_favorite_servers-(i+1)); - num_favorite_servers--; - - entry = client_serverbrowse_find(&addr); - if(entry) - entry->info.favorite = 0; - - return; - } - } -} - -void client_serverbrowse_save() -{ - int i; - char addrstr[128]; - char buffer[256]; - for(i = 0; i < num_favorite_servers; i++) - { - net_addr_str(&favorite_servers[i], addrstr, sizeof(addrstr)); - str_format(buffer, sizeof(buffer), "add_favorite %s", addrstr); - engine_config_write_line(buffer); - } -} - - -int client_serverbrowse_refreshingmasters() -{ - return mastersrv_refreshing(); -} diff --git a/src/engine/client/editor.h b/src/engine/client/editor.h deleted file mode 100644 index 336a46d4..00000000 --- a/src/engine/client/editor.h +++ /dev/null @@ -1,10 +0,0 @@ - -class IEditor -{ -public: - virtual ~IEditor() {} - virtual void Init(class IGraphics *pGraphics) = 0; - virtual void UpdateAndRender() = 0; -}; - -extern IEditor *CreateEditor(); diff --git a/src/engine/client/graphics.cpp b/src/engine/client/graphics.cpp new file mode 100644 index 00000000..7c355fb8 --- /dev/null +++ b/src/engine/client/graphics.cpp @@ -0,0 +1,938 @@ +// copyright (c) 2007 magnus auvinen, see licence.txt for more info + +#include <base/detect.h> + +#include "SDL.h" + +#ifdef CONF_FAMILY_WINDOWS + #define WIN32_LEAN_AND_MEAN + #include <windows.h> +#endif + +#ifdef CONF_PLATFORM_MACOSX + #include <OpenGL/gl.h> + #include <OpenGL/glu.h> +#else + #include <GL/gl.h> + #include <GL/glu.h> +#endif + +#include <base/system.h> +#include <engine/external/pnglite/pnglite.h> + +#include <engine/shared/engine.h> +#include <engine/shared/config.h> +#include <engine/graphics.h> +#include <engine/storage.h> +#include <engine/keys.h> + +#include <math.h> + +#include "graphics.h" + +// compressed textures +#define GL_COMPRESSED_RGB_ARB 0x84ED +#define GL_COMPRESSED_RGBA_ARB 0x84EE +#define GL_COMPRESSED_ALPHA_ARB 0x84E9 + +#define TEXTURE_MAX_ANISOTROPY_EXT 0x84FE + + +static CVideoMode g_aFakeModes[] = { + {320,240,8,8,8}, {400,300,8,8,8}, {640,480,8,8,8}, + {720,400,8,8,8}, {768,576,8,8,8}, {800,600,8,8,8}, + {1024,600,8,8,8}, {1024,768,8,8,8}, {1152,864,8,8,8}, + {1280,768,8,8,8}, {1280,800,8,8,8}, {1280,960,8,8,8}, + {1280,1024,8,8,8}, {1368,768,8,8,8}, {1400,1050,8,8,8}, + {1440,900,8,8,8}, {1440,1050,8,8,8}, {1600,1000,8,8,8}, + {1600,1200,8,8,8}, {1680,1050,8,8,8}, {1792,1344,8,8,8}, + {1800,1440,8,8,8}, {1856,1392,8,8,8}, {1920,1080,8,8,8}, + {1920,1200,8,8,8}, {1920,1440,8,8,8}, {1920,2400,8,8,8}, + {2048,1536,8,8,8}, + + {320,240,5,6,5}, {400,300,5,6,5}, {640,480,5,6,5}, + {720,400,5,6,5}, {768,576,5,6,5}, {800,600,5,6,5}, + {1024,600,5,6,5}, {1024,768,5,6,5}, {1152,864,5,6,5}, + {1280,768,5,6,5}, {1280,800,5,6,5}, {1280,960,5,6,5}, + {1280,1024,5,6,5}, {1368,768,5,6,5}, {1400,1050,5,6,5}, + {1440,900,5,6,5}, {1440,1050,5,6,5}, {1600,1000,5,6,5}, + {1600,1200,5,6,5}, {1680,1050,5,6,5}, {1792,1344,5,6,5}, + {1800,1440,5,6,5}, {1856,1392,5,6,5}, {1920,1080,5,6,5}, + {1920,1200,5,6,5}, {1920,1440,5,6,5}, {1920,2400,5,6,5}, + {2048,1536,5,6,5} +}; + +void CGraphics_OpenGL::Flush() +{ + if(m_NumVertices == 0) + return; + + //glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_NEAREST); + //glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + + glVertexPointer(3, GL_FLOAT, + sizeof(CVertex), + (char*)m_aVertices); + glTexCoordPointer(2, GL_FLOAT, + sizeof(CVertex), + (char*)m_aVertices + sizeof(float)*3); + glColorPointer(4, GL_FLOAT, + sizeof(CVertex), + (char*)m_aVertices + sizeof(float)*5); + glEnableClientState(GL_VERTEX_ARRAY); + glEnableClientState(GL_TEXTURE_COORD_ARRAY); + glEnableClientState(GL_COLOR_ARRAY); + + if(m_RenderEnable) + { + if(m_Drawing == DRAWING_QUADS) + glDrawArrays(GL_QUADS, 0, m_NumVertices); + else if(m_Drawing == DRAWING_LINES) + glDrawArrays(GL_LINES, 0, m_NumVertices); + } + + // Reset pointer + m_NumVertices = 0; +} + +void CGraphics_OpenGL::AddVertices(int Count) +{ + m_NumVertices += Count; + if((m_NumVertices + Count) >= MAX_VERTICES) + Flush(); +} + +void CGraphics_OpenGL::Rotate4(CPoint *pCenter, CVertex *pPoints) +{ + float c = cosf(m_Rotation); + float s = sinf(m_Rotation); + float x, y; + int i; + + for(i = 0; i < 4; i++) + { + x = pPoints[i].m_Pos.x - pCenter->x; + y = pPoints[i].m_Pos.y - pCenter->y; + pPoints[i].m_Pos.x = x * c - y * s + pCenter->x; + pPoints[i].m_Pos.y = x * s + y * c + pCenter->y; + } +} + +unsigned char CGraphics_OpenGL::Sample(int w, int h, const unsigned char *pData, int u, int v, int Offset) +{ + return (pData[(v*w+u)*4+Offset]+ + pData[(v*w+u+1)*4+Offset]+ + pData[((v+1)*w+u)*4+Offset]+ + pData[((v+1)*w+u+1)*4+Offset])/4; +} + +CGraphics_OpenGL::CGraphics_OpenGL() +{ + m_NumVertices = 0; + + m_ScreenX0 = 0; + m_ScreenY0 = 0; + m_ScreenX1 = 0; + m_ScreenY1 = 0; + + m_ScreenWidth = -1; + m_ScreenHeight = -1; + + m_Rotation = 0; + m_Drawing = 0; + m_InvalidTexture = 0; + + m_TextureMemoryUsage = 0; + + m_RenderEnable = true; + m_DoScreenshot = false; +} + +void CGraphics_OpenGL::ClipEnable(int x, int y, int w, int h) +{ + //if(no_gfx) return; + glScissor(x, ScreenHeight()-(y+h), w, h); + glEnable(GL_SCISSOR_TEST); +} + +void CGraphics_OpenGL::ClipDisable() +{ + //if(no_gfx) return; + glDisable(GL_SCISSOR_TEST); +} + +void CGraphics_OpenGL::BlendNone() +{ + glDisable(GL_BLEND); +} + +void CGraphics_OpenGL::BlendNormal() +{ + glEnable(GL_BLEND); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); +} + +void CGraphics_OpenGL::BlendAdditive() +{ + glEnable(GL_BLEND); + glBlendFunc(GL_SRC_ALPHA, GL_ONE); +} + +int CGraphics_OpenGL::MemoryUsage() const +{ + return m_TextureMemoryUsage; +} + +void CGraphics_OpenGL::MapScreen(float TopLeftX, float TopLeftY, float BottomRightX, float BottomRightY) +{ + m_ScreenX0 = TopLeftX; + m_ScreenY0 = TopLeftY; + m_ScreenX1 = BottomRightX; + m_ScreenY1 = BottomRightY; + glMatrixMode(GL_PROJECTION); + glLoadIdentity(); + glOrtho(TopLeftX, BottomRightX, BottomRightY, TopLeftY, 1.0f, 10.f); +} + +void CGraphics_OpenGL::GetScreen(float *pTopLeftX, float *pTopLeftY, float *pBottomRightX, float *pBottomRightY) +{ + *pTopLeftX = m_ScreenX0; + *pTopLeftY = m_ScreenY0; + *pBottomRightX = m_ScreenX1; + *pBottomRightY = m_ScreenY1; +} + +void CGraphics_OpenGL::LinesBegin() +{ + dbg_assert(m_Drawing == 0, "called begin twice"); + m_Drawing = DRAWING_LINES; + SetColor(1,1,1,1); +} + +void CGraphics_OpenGL::LinesEnd() +{ + dbg_assert(m_Drawing == DRAWING_LINES, "called end without begin"); + Flush(); + m_Drawing = 0; +} + +void CGraphics_OpenGL::LinesDraw(const CLineItem *pArray, int Num) +{ + dbg_assert(m_Drawing == DRAWING_LINES, "called draw without begin"); + + for(int i = 0; i < Num; ++i) + { + m_aVertices[m_NumVertices + 2*i].m_Pos.x = pArray[i].m_X0; + m_aVertices[m_NumVertices + 2*i].m_Pos.y = pArray[i].m_Y0; + m_aVertices[m_NumVertices + 2*i].m_Tex = m_aTexture[0]; + m_aVertices[m_NumVertices + 2*i].m_Color = m_aColor[0]; + + m_aVertices[m_NumVertices + 2*i + 1].m_Pos.x = pArray[i].m_X1; + m_aVertices[m_NumVertices + 2*i + 1].m_Pos.y = pArray[i].m_Y1; + m_aVertices[m_NumVertices + 2*i + 1].m_Tex = m_aTexture[1]; + m_aVertices[m_NumVertices + 2*i + 1].m_Color = m_aColor[1]; + } + + AddVertices(2*Num); +} + +int CGraphics_OpenGL::UnloadTexture(int Index) +{ + if(Index == m_InvalidTexture) + return 0; + + if(Index < 0) + return 0; + + glDeleteTextures(1, &m_aTextures[Index].m_Tex); + m_aTextures[Index].m_Next = m_FirstFreeTexture; + m_TextureMemoryUsage -= m_aTextures[Index].m_MemSize; + m_FirstFreeTexture = Index; + return 0; +} + + +int CGraphics_OpenGL::LoadTextureRaw(int Width, int Height, int Format, const void *pData, int StoreFormat, int Flags) +{ + int Mipmap = 1; + unsigned char *pTexData = (unsigned char *)pData; + unsigned char *pTmpData = 0; + int Oglformat = 0; + int StoreOglformat = 0; + int Tex = 0; + + // don't waste memory on texture if we are stress testing + if(g_Config.m_DbgStress) + return m_InvalidTexture; + + // grab texture + Tex = m_FirstFreeTexture; + m_FirstFreeTexture = m_aTextures[Tex].m_Next; + m_aTextures[Tex].m_Next = -1; + + // resample if needed + if(!(Flags&TEXLOAD_NORESAMPLE) && g_Config.m_GfxTextureQuality==0) + { + if(Width > 16 && Height > 16 && Format == CImageInfo::FORMAT_RGBA) + { + unsigned char *pTmpData; + int c = 0; + int x, y; + + pTmpData = (unsigned char *)mem_alloc(Width*Height*4, 1); + + Width/=2; + Height/=2; + + for(y = 0; y < Height; y++) + for(x = 0; x < Width; x++, c++) + { + pTmpData[c*4] = Sample(Width*2, Height*2, pTexData, x*2,y*2, 0); + pTmpData[c*4+1] = Sample(Width*2, Height*2, pTexData, x*2,y*2, 1); + pTmpData[c*4+2] = Sample(Width*2, Height*2, pTexData, x*2,y*2, 2); + pTmpData[c*4+3] = Sample(Width*2, Height*2, pTexData, x*2,y*2, 3); + } + pTexData = pTmpData; + } + } + + Oglformat = GL_RGBA; + if(Format == CImageInfo::FORMAT_RGB) + Oglformat = GL_RGB; + else if(Format == CImageInfo::FORMAT_ALPHA) + Oglformat = GL_ALPHA; + + // upload texture + if(g_Config.m_GfxTextureCompression) + { + StoreOglformat = GL_COMPRESSED_RGBA_ARB; + if(StoreFormat == CImageInfo::FORMAT_RGB) + StoreOglformat = GL_COMPRESSED_RGB_ARB; + else if(StoreFormat == CImageInfo::FORMAT_ALPHA) + StoreOglformat = GL_COMPRESSED_ALPHA_ARB; + } + else + { + StoreOglformat = GL_RGBA; + if(StoreFormat == CImageInfo::FORMAT_RGB) + StoreOglformat = GL_RGB; + else if(StoreFormat == CImageInfo::FORMAT_ALPHA) + StoreOglformat = GL_ALPHA; + } + + glGenTextures(1, &m_aTextures[Tex].m_Tex); + glBindTexture(GL_TEXTURE_2D, m_aTextures[Tex].m_Tex); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_NEAREST); + gluBuild2DMipmaps(GL_TEXTURE_2D, StoreOglformat, Width, Height, Oglformat, GL_UNSIGNED_BYTE, pTexData); + + // calculate memory usage + { + int PixelSize = 4; + if(StoreFormat == CImageInfo::FORMAT_RGB) + PixelSize = 3; + else if(StoreFormat == CImageInfo::FORMAT_ALPHA) + PixelSize = 1; + + m_aTextures[Tex].m_MemSize = Width*Height*PixelSize; + if(Mipmap) + { + while(Width > 2 && Height > 2) + { + Width>>=1; + Height>>=1; + m_aTextures[Tex].m_MemSize += Width*Height*PixelSize; + } + } + } + + m_TextureMemoryUsage += m_aTextures[Tex].m_MemSize; + mem_free(pTmpData); + return Tex; +} + +// simple uncompressed RGBA loaders +int CGraphics_OpenGL::LoadTexture(const char *pFilename, int StoreFormat, int Flags) +{ + int l = str_length(pFilename); + int Id; + CImageInfo Img; + + if(l < 3) + return -1; + if(LoadPNG(&Img, pFilename)) + { + if (StoreFormat == CImageInfo::FORMAT_AUTO) + StoreFormat = Img.m_Format; + + Id = LoadTextureRaw(Img.m_Width, Img.m_Height, Img.m_Format, Img.m_pData, StoreFormat, Flags); + mem_free(Img.m_pData); + return Id; + } + + return m_InvalidTexture; +} + +int CGraphics_OpenGL::LoadPNG(CImageInfo *pImg, const char *pFilename) +{ + char aCompleteFilename[512]; + unsigned char *pBuffer; + png_t Png; // ignore_convention + + // open file for reading + png_init(0,0); // ignore_convention + + IOHANDLE File = m_pStorage->OpenFile(pFilename, IOFLAG_READ, aCompleteFilename, sizeof(aCompleteFilename)); + if(File) + io_close(File); + + if(png_open_file(&Png, aCompleteFilename) != PNG_NO_ERROR) // ignore_convention + { + dbg_msg("game/png", "failed to open file. filename='%s'", aCompleteFilename); + return 0; + } + + if(Png.depth != 8 || (Png.color_type != PNG_TRUECOLOR && Png.color_type != PNG_TRUECOLOR_ALPHA)) // ignore_convention + { + dbg_msg("game/png", "invalid format. filename='%s'", aCompleteFilename); + png_close_file(&Png); // ignore_convention + return 0; + } + + pBuffer = (unsigned char *)mem_alloc(Png.width * Png.height * Png.bpp, 1); // ignore_convention + png_get_data(&Png, pBuffer); // ignore_convention + png_close_file(&Png); // ignore_convention + + pImg->m_Width = Png.width; // ignore_convention + pImg->m_Height = Png.height; // ignore_convention + if(Png.color_type == PNG_TRUECOLOR) // ignore_convention + pImg->m_Format = CImageInfo::FORMAT_RGB; + else if(Png.color_type == PNG_TRUECOLOR_ALPHA) // ignore_convention + pImg->m_Format = CImageInfo::FORMAT_RGBA; + pImg->m_pData = pBuffer; + return 1; +} + +void CGraphics_OpenGL::ScreenshotDirect(const char *pFilename) +{ + // fetch image data + int y; + int w = m_ScreenWidth; + int h = m_ScreenHeight; + unsigned char *pPixelData = (unsigned char *)mem_alloc(w*(h+1)*4, 1); + unsigned char *pTempRow = pPixelData+w*h*4; + glReadPixels(0,0, w, h, GL_RGBA, GL_UNSIGNED_BYTE, pPixelData); + + // flip the pixel because opengl works from bottom left corner + for(y = 0; y < h/2; y++) + { + mem_copy(pTempRow, pPixelData+y*w*4, w*4); + mem_copy(pPixelData+y*w*4, pPixelData+(h-y-1)*w*4, w*4); + mem_copy(pPixelData+(h-y-1)*w*4, pTempRow,w*4); + } + + // find filename + { + char aWholePath[1024]; + png_t Png; // ignore_convention + + IOHANDLE File = m_pStorage->OpenFile(pFilename, IOFLAG_WRITE, aWholePath, sizeof(aWholePath)); + if(File) + io_close(File); + + // save png + dbg_msg("client", "saved screenshot to '%s'", aWholePath); + png_open_file_write(&Png, aWholePath); // ignore_convention + png_set_data(&Png, w, h, 8, PNG_TRUECOLOR_ALPHA, (unsigned char *)pPixelData); // ignore_convention + png_close_file(&Png); // ignore_convention + } + + // clean up + mem_free(pPixelData); +} + +void CGraphics_OpenGL::TextureSet(int TextureID) +{ + dbg_assert(m_Drawing == 0, "called Graphics()->TextureSet within begin"); + if(TextureID == -1) + { + glDisable(GL_TEXTURE_2D); + } + else + { + glEnable(GL_TEXTURE_2D); + glBindTexture(GL_TEXTURE_2D, m_aTextures[TextureID].m_Tex); + } +} + +void CGraphics_OpenGL::Clear(float r, float g, float b) +{ + glClearColor(r,g,b,0.0f); + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); +} + +void CGraphics_OpenGL::QuadsBegin() +{ + dbg_assert(m_Drawing == 0, "called quads_begin twice"); + m_Drawing = DRAWING_QUADS; + + QuadsSetSubset(0,0,1,1); + QuadsSetRotation(0); + SetColor(1,1,1,1); +} + +void CGraphics_OpenGL::QuadsEnd() +{ + dbg_assert(m_Drawing == DRAWING_QUADS, "called quads_end without begin"); + Flush(); + m_Drawing = 0; +} + +void CGraphics_OpenGL::QuadsSetRotation(float Angle) +{ + dbg_assert(m_Drawing == DRAWING_QUADS, "called Graphics()->QuadsSetRotation without begin"); + m_Rotation = Angle; +} + +void CGraphics_OpenGL::SetColorVertex(const CColorVertex *pArray, int Num) +{ + dbg_assert(m_Drawing != 0, "called gfx_quads_setcolorvertex without begin"); + + for(int i = 0; i < Num; ++i) + { + m_aColor[pArray[i].m_Index].r = pArray[i].m_R; + m_aColor[pArray[i].m_Index].g = pArray[i].m_G; + m_aColor[pArray[i].m_Index].b = pArray[i].m_B; + m_aColor[pArray[i].m_Index].a = pArray[i].m_A; + } +} + +void CGraphics_OpenGL::SetColor(float r, float g, float b, float a) +{ + dbg_assert(m_Drawing != 0, "called gfx_quads_setcolor without begin"); + CColorVertex Array[4] = { + CColorVertex(0, r, g, b, a), + CColorVertex(1, r, g, b, a), + CColorVertex(2, r, g, b, a), + CColorVertex(3, r, g, b, a)}; + SetColorVertex(Array, 4); +} + +void CGraphics_OpenGL::QuadsSetSubset(float TlU, float TlV, float BrU, float BrV) +{ + dbg_assert(m_Drawing == DRAWING_QUADS, "called Graphics()->QuadsSetSubset without begin"); + + m_aTexture[0].u = TlU; m_aTexture[1].u = BrU; + m_aTexture[0].v = TlV; m_aTexture[1].v = TlV; + + m_aTexture[3].u = TlU; m_aTexture[2].u = BrU; + m_aTexture[3].v = BrV; m_aTexture[2].v = BrV; +} + +void CGraphics_OpenGL::QuadsSetSubsetFree( + float x0, float y0, float x1, float y1, + float x2, float y2, float x3, float y3) +{ + m_aTexture[0].u = x0; m_aTexture[0].v = y0; + m_aTexture[1].u = x1; m_aTexture[1].v = y1; + m_aTexture[2].u = x2; m_aTexture[2].v = y2; + m_aTexture[3].u = x3; m_aTexture[3].v = y3; +} + +void CGraphics_OpenGL::QuadsDraw(CQuadItem *pArray, int Num) +{ + for(int i = 0; i < Num; ++i) + { + pArray[i].m_X -= pArray[i].m_Width/2; + pArray[i].m_Y -= pArray[i].m_Height/2; + } + + QuadsDrawTL(pArray, Num); +} + +void CGraphics_OpenGL::QuadsDrawTL(const CQuadItem *pArray, int Num) +{ + CPoint Center; + + dbg_assert(m_Drawing == DRAWING_QUADS, "called quads_draw without begin"); + + for(int i = 0; i < Num; ++i) + { + Center.x = pArray[i].m_X + pArray[i].m_Width/2; + Center.y = pArray[i].m_Y + pArray[i].m_Height/2; + Center.z = 0; + + m_aVertices[m_NumVertices + 4*i].m_Pos.x = pArray[i].m_X; + m_aVertices[m_NumVertices + 4*i].m_Pos.y = pArray[i].m_Y; + m_aVertices[m_NumVertices + 4*i].m_Tex = m_aTexture[0]; + m_aVertices[m_NumVertices + 4*i].m_Color = m_aColor[0]; + + m_aVertices[m_NumVertices + 4*i + 1].m_Pos.x = pArray[i].m_X + pArray[i].m_Width; + m_aVertices[m_NumVertices + 4*i + 1].m_Pos.y = pArray[i].m_Y; + m_aVertices[m_NumVertices + 4*i + 1].m_Tex = m_aTexture[1]; + m_aVertices[m_NumVertices + 4*i + 1].m_Color = m_aColor[1]; + + m_aVertices[m_NumVertices + 4*i + 2].m_Pos.x = pArray[i].m_X + pArray[i].m_Width; + m_aVertices[m_NumVertices + 4*i + 2].m_Pos.y = pArray[i].m_Y + pArray[i].m_Height; + m_aVertices[m_NumVertices + 4*i + 2].m_Tex = m_aTexture[2]; + m_aVertices[m_NumVertices + 4*i + 2].m_Color = m_aColor[2]; + + m_aVertices[m_NumVertices + 4*i + 3].m_Pos.x = pArray[i].m_X; + m_aVertices[m_NumVertices + 4*i + 3].m_Pos.y = pArray[i].m_Y + pArray[i].m_Height; + m_aVertices[m_NumVertices + 4*i + 3].m_Tex = m_aTexture[3]; + m_aVertices[m_NumVertices + 4*i + 3].m_Color = m_aColor[3]; + + if(m_Rotation != 0) + Rotate4(&Center, &m_aVertices[m_NumVertices + 4*i]); + } + + AddVertices(4*Num); +} + +void CGraphics_OpenGL::QuadsDrawFreeform(const CFreeformItem *pArray, int Num) +{ + dbg_assert(m_Drawing == DRAWING_QUADS, "called quads_draw_freeform without begin"); + + for(int i = 0; i < Num; ++i) + { + m_aVertices[m_NumVertices + 4*i].m_Pos.x = pArray[i].m_X0; + m_aVertices[m_NumVertices + 4*i].m_Pos.y = pArray[i].m_Y0; + m_aVertices[m_NumVertices + 4*i].m_Tex = m_aTexture[0]; + m_aVertices[m_NumVertices + 4*i].m_Color = m_aColor[0]; + + m_aVertices[m_NumVertices + 4*i + 1].m_Pos.x = pArray[i].m_X1; + m_aVertices[m_NumVertices + 4*i + 1].m_Pos.y = pArray[i].m_Y1; + m_aVertices[m_NumVertices + 4*i + 1].m_Tex = m_aTexture[1]; + m_aVertices[m_NumVertices + 4*i + 1].m_Color = m_aColor[1]; + + m_aVertices[m_NumVertices + 4*i + 2].m_Pos.x = pArray[i].m_X3; + m_aVertices[m_NumVertices + 4*i + 2].m_Pos.y = pArray[i].m_Y3; + m_aVertices[m_NumVertices + 4*i + 2].m_Tex = m_aTexture[3]; + m_aVertices[m_NumVertices + 4*i + 2].m_Color = m_aColor[3]; + + m_aVertices[m_NumVertices + 4*i + 3].m_Pos.x = pArray[i].m_X2; + m_aVertices[m_NumVertices + 4*i + 3].m_Pos.y = pArray[i].m_Y2; + m_aVertices[m_NumVertices + 4*i + 3].m_Tex = m_aTexture[2]; + m_aVertices[m_NumVertices + 4*i + 3].m_Color = m_aColor[2]; + } + + AddVertices(4*Num); +} + +void CGraphics_OpenGL::QuadsText(float x, float y, float Size, float r, float g, float b, float a, const char *pText) +{ + float StartX = x; + + QuadsBegin(); + SetColor(r,g,b,a); + + while(*pText) + { + char c = *pText; + pText++; + + if(c == '\n') + { + x = StartX; + y += Size; + } + else + { + QuadsSetSubset( + (c%16)/16.0f, + (c/16)/16.0f, + (c%16)/16.0f+1.0f/16.0f, + (c/16)/16.0f+1.0f/16.0f); + + CQuadItem QuadItem(x, y, Size, Size); + QuadsDrawTL(&QuadItem, 1); + x += Size/2; + } + } + + QuadsEnd(); +} + +bool CGraphics_OpenGL::Init() +{ + m_pStorage = Kernel()->RequestInterface<IStorage>(); + + // Set all z to -5.0f + for(int i = 0; i < MAX_VERTICES; i++) + m_aVertices[i].m_Pos.z = -5.0f; + + // init textures + m_FirstFreeTexture = 0; + for(int i = 0; i < MAX_TEXTURES; i++) + m_aTextures[i].m_Next = i+1; + m_aTextures[MAX_TEXTURES-1].m_Next = -1; + + // set some default settings + glEnable(GL_BLEND); + glDisable(GL_CULL_FACE); + glDisable(GL_DEPTH_TEST); + glMatrixMode(GL_MODELVIEW); + glLoadIdentity(); + + glAlphaFunc(GL_GREATER, 0); + glEnable(GL_ALPHA_TEST); + glDepthMask(0); + + // create null texture, will get id=0 + static const unsigned char aNullTextureData[] = { + 0xff,0x00,0x00,0xff, 0xff,0x00,0x00,0xff, 0x00,0xff,0x00,0xff, 0x00,0xff,0x00,0xff, + 0xff,0x00,0x00,0xff, 0xff,0x00,0x00,0xff, 0x00,0xff,0x00,0xff, 0x00,0xff,0x00,0xff, + 0x00,0x00,0xff,0xff, 0x00,0x00,0xff,0xff, 0xff,0xff,0x00,0xff, 0xff,0xff,0x00,0xff, + 0x00,0x00,0xff,0xff, 0x00,0x00,0xff,0xff, 0xff,0xff,0x00,0xff, 0xff,0xff,0x00,0xff, + }; + + m_InvalidTexture = LoadTextureRaw(4,4,CImageInfo::FORMAT_RGBA,aNullTextureData,CImageInfo::FORMAT_RGBA,TEXLOAD_NORESAMPLE); + dbg_msg("", "invalid texture id: %d %d", m_InvalidTexture, m_aTextures[m_InvalidTexture].m_Tex); + + return true; +} + +int CGraphics_SDL::TryInit() +{ + const SDL_VideoInfo *pInfo; + int Flags = SDL_OPENGL; + + m_ScreenWidth = g_Config.m_GfxScreenWidth; + m_ScreenHeight = g_Config.m_GfxScreenHeight; + + pInfo = SDL_GetVideoInfo(); + SDL_EventState(SDL_MOUSEMOTION, SDL_IGNORE); + + // set flags + Flags = SDL_OPENGL; + Flags |= SDL_GL_DOUBLEBUFFER; + Flags |= SDL_HWPALETTE; + if(g_Config.m_DbgResizable) + Flags |= SDL_RESIZABLE; + + if(pInfo->hw_available) // ignore_convention + Flags |= SDL_HWSURFACE; + else + Flags |= SDL_SWSURFACE; + + if(pInfo->blit_hw) // ignore_convention + Flags |= SDL_HWACCEL; + + if(g_Config.m_GfxFullscreen) + Flags |= SDL_FULLSCREEN; + + // set gl attributes + if(g_Config.m_GfxFsaaSamples) + { + SDL_GL_SetAttribute(SDL_GL_MULTISAMPLEBUFFERS, 1); + SDL_GL_SetAttribute(SDL_GL_MULTISAMPLESAMPLES, g_Config.m_GfxFsaaSamples); + } + else + { + SDL_GL_SetAttribute(SDL_GL_MULTISAMPLEBUFFERS, 0); + SDL_GL_SetAttribute(SDL_GL_MULTISAMPLESAMPLES, 0); + } + + SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1); + SDL_GL_SetAttribute(SDL_GL_SWAP_CONTROL, g_Config.m_GfxVsync); + + // set caption + SDL_WM_SetCaption("Teeworlds", "Teeworlds"); + + // create window + m_pScreenSurface = SDL_SetVideoMode(m_ScreenWidth, m_ScreenHeight, 0, Flags); + if(m_pScreenSurface == NULL) + { + dbg_msg("gfx", "unable to set video mode: %s", SDL_GetError()); + return -1; + } + + return 0; +} + + +int CGraphics_SDL::InitWindow() +{ + if(TryInit() == 0) + return 0; + + // try disabling fsaa + while(g_Config.m_GfxFsaaSamples) + { + g_Config.m_GfxFsaaSamples--; + + if(g_Config.m_GfxFsaaSamples) + dbg_msg("gfx", "lowering FSAA to %d and trying again", g_Config.m_GfxFsaaSamples); + else + dbg_msg("gfx", "disabling FSAA and trying again"); + + if(TryInit() == 0) + return 0; + } + + // try lowering the resolution + if(g_Config.m_GfxScreenWidth != 640 || g_Config.m_GfxScreenHeight != 480) + { + dbg_msg("gfx", "setting resolution to 640x480 and trying again"); + g_Config.m_GfxScreenWidth = 640; + g_Config.m_GfxScreenHeight = 480; + + if(TryInit() == 0) + return 0; + } + + dbg_msg("gfx", "out of ideas. failed to init graphics"); + + return -1; +} + + +CGraphics_SDL::CGraphics_SDL() +{ + m_pScreenSurface = 0; +} + +bool CGraphics_SDL::Init() +{ + { + int Systems = SDL_INIT_VIDEO; + + if(g_Config.m_SndEnable) + Systems |= SDL_INIT_AUDIO; + + if(g_Config.m_ClEventthread) + Systems |= SDL_INIT_EVENTTHREAD; + + if(SDL_Init(Systems) < 0) + { + dbg_msg("gfx", "unable to init SDL: %s", SDL_GetError()); + return true; + } + } + + atexit(SDL_Quit); // ignore_convention + + #ifdef CONF_FAMILY_WINDOWS + if(!getenv("SDL_VIDEO_WINDOW_POS") && !getenv("SDL_VIDEO_CENTERED")) // ignore_convention + putenv("SDL_VIDEO_WINDOW_POS=8,27"); // ignore_convention + #endif + + if(InitWindow() != 0) + return true; + + SDL_ShowCursor(0); + + CGraphics_OpenGL::Init(); + + MapScreen(0,0,g_Config.m_GfxScreenWidth, g_Config.m_GfxScreenHeight); + return false; +} + +void CGraphics_SDL::Shutdown() +{ + // TODO: SDL, is this correct? + SDL_Quit(); +} + +void CGraphics_SDL::Minimize() +{ + SDL_WM_IconifyWindow(); +} + +void CGraphics_SDL::Maximize() +{ + // TODO: SDL +} + +int CGraphics_SDL::WindowActive() +{ + return SDL_GetAppState()&SDL_APPINPUTFOCUS; +} + +int CGraphics_SDL::WindowOpen() +{ + return SDL_GetAppState()&SDL_APPACTIVE; + +} + +void CGraphics_SDL::TakeScreenshot() +{ + m_DoScreenshot = true; +} + +void CGraphics_SDL::Swap() +{ + if(m_DoScreenshot) + { + // find filename + char aFilename[128]; + static int Index = 1; + + for(; Index < 10000; Index++) + { + IOHANDLE io; + str_format(aFilename, sizeof(aFilename), "screenshots/screenshot%05d.png", Index); + io = m_pStorage->OpenFile(aFilename, IOFLAG_READ); + if(io) + io_close(io); + else + break; + } + + ScreenshotDirect(aFilename); + m_DoScreenshot = false; + } + + SDL_GL_SwapBuffers(); + + if(g_Config.m_GfxFinish) + glFinish(); +} + + +int CGraphics_SDL::GetVideoModes(CVideoMode *pModes, int MaxModes) +{ + int NumModes = sizeof(g_aFakeModes)/sizeof(CVideoMode); + SDL_Rect **ppModes; + + if(g_Config.m_GfxDisplayAllModes) + { + int Count = sizeof(g_aFakeModes)/sizeof(CVideoMode); + mem_copy(pModes, g_aFakeModes, sizeof(g_aFakeModes)); + if(MaxModes < Count) + Count = MaxModes; + return Count; + } + + // TODO: fix this code on osx or windows + + ppModes = SDL_ListModes(NULL, SDL_OPENGL|SDL_GL_DOUBLEBUFFER|SDL_FULLSCREEN); + if(ppModes == NULL) + { + // no modes + NumModes = 0; + } + else if(ppModes == (SDL_Rect**)-1) + { + // all modes + } + else + { + NumModes = 0; + for(int i = 0; ppModes[i]; ++i) + { + if(NumModes == MaxModes) + break; + pModes[NumModes].m_Width = ppModes[i]->w; + pModes[NumModes].m_Height = ppModes[i]->h; + pModes[NumModes].m_Red = 8; + pModes[NumModes].m_Green = 8; + pModes[NumModes].m_Blue = 8; + NumModes++; + } + } + + return NumModes; +} + +extern IEngineGraphics *CreateEngineGraphics() { return new CGraphics_SDL(); } diff --git a/src/engine/client/graphics.h b/src/engine/client/graphics.h index 80dbf1b9..ff4c3562 100644 --- a/src/engine/client/graphics.h +++ b/src/engine/client/graphics.h @@ -1,69 +1,143 @@ -#include "../e_if_gfx.h" +#ifndef ENGINE_CLIENT_GRAPHICS_H +#define ENGINE_CLIENT_GRAPHICS_H -class IGraphics +class CGraphics_OpenGL : public IEngineGraphics { protected: - int m_ScreenWidth; - int m_ScreenHeight; -public: - virtual ~IGraphics() {} - - int ScreenWidth() const { return m_ScreenWidth; } - int ScreenHeight() const { return m_ScreenHeight; } - float ScreenAspect() const { return (float)ScreenWidth()/(float)ScreenHeight(); } - - virtual void Clear(float r, float g, float b) = 0; - - virtual void ClipEnable(int x, int y, int w, int h) = 0; - virtual void ClipDisable() = 0; - - virtual void MapScreen(float tl_x, float tl_y, float br_x, float br_y) = 0; - virtual void GetScreen(float *tl_x, float *tl_y, float *br_x, float *br_y) = 0; + class IStorage *m_pStorage; - virtual void BlendNone() = 0; - virtual void BlendNormal() = 0; - virtual void BlendAdditive() = 0; - - virtual int LoadPNG(IMAGE_INFO *pImg, const char *pFilename) =0; - virtual int UnloadTexture(int Index) = 0; - virtual int LoadTextureRaw(int Width, int Height, int Format, const void *pData, int StoreFormat, int Flags) = 0; - virtual int LoadTexture(const char *pFilename, int StoreFormat, int Flags) = 0; - virtual void TextureSet(int TextureID) = 0; + // + typedef struct { float x, y, z; } CPoint; + typedef struct { float u, v; } CTexCoord; + typedef struct { float r, g, b, a; } CColor; + + typedef struct + { + CPoint m_Pos; + CTexCoord m_Tex; + CColor m_Color; + } CVertex; - virtual void LinesBegin() = 0; - virtual void LinesEnd() = 0; - virtual void LinesDraw(float x0, float y0, float x1, float y1) = 0; + enum + { + MAX_VERTICES = 32*1024, + MAX_TEXTURES = 1024*4, + + DRAWING_QUADS=1, + DRAWING_LINES=2 + }; + + CVertex m_aVertices[MAX_VERTICES]; + int m_NumVertices; + + CColor m_aColor[4]; + CTexCoord m_aTexture[4]; + + bool m_RenderEnable; + + float m_Rotation; + int m_Drawing; + bool m_DoScreenshot; + + float m_ScreenX0; + float m_ScreenY0; + float m_ScreenX1; + float m_ScreenY1; + + int m_InvalidTexture; + + struct CTexture + { + GLuint m_Tex; + int m_MemSize; + int m_Flags; + int m_Next; + }; + + CTexture m_aTextures[MAX_TEXTURES]; + int m_FirstFreeTexture; + int m_TextureMemoryUsage; + + void Flush(); + void AddVertices(int Count); + void Rotate4(CPoint *pCenter, CVertex *pPoints); - virtual void QuadsBegin() = 0; - virtual void QuadsEnd() = 0; - virtual void QuadsSetRotation(float Angle) = 0; - virtual void QuadsSetSubset(float tl_u, float tl_v, float br_u, float br_v) = 0; - virtual void QuadsSetSubsetFree(float x0, float y0, float x1, float y1, float x2, float y2, float x3, float y3) = 0; + static unsigned char Sample(int w, int h, const unsigned char *pData, int u, int v, int Offset); +public: + CGraphics_OpenGL(); - virtual void QuadsDraw(float x, float y, float w, float h) = 0; - virtual void QuadsDrawTL(float x, float y, float w, float h) = 0; - virtual void QuadsDrawFreeform(float x0, float y0, float x1, float y1, float x2, float y2, float x3, float y3) = 0; - virtual void QuadsText(float x, float y, float Size, float r, float g, float b, float a, const char *pText) = 0; + virtual void ClipEnable(int x, int y, int w, int h); + virtual void ClipDisable(); + + virtual void BlendNone(); + virtual void BlendNormal(); + virtual void BlendAdditive(); + + virtual int MemoryUsage() const; + + virtual void MapScreen(float TopLeftX, float TopLeftY, float BottomRightX, float BottomRightY); + virtual void GetScreen(float *pTopLeftX, float *pTopLeftY, float *pBottomRightX, float *pBottomRightY); + + virtual void LinesBegin(); + virtual void LinesEnd(); + virtual void LinesDraw(const CLineItem *pArray, int Num); - virtual void SetColorVertex(int i, float r, float g, float b, float a) = 0; - virtual void SetColor(float r, float g, float b, float a) = 0; + virtual int UnloadTexture(int Index); + virtual int LoadTextureRaw(int Width, int Height, int Format, const void *pData, int StoreFormat, int Flags); + + // simple uncompressed RGBA loaders + virtual int LoadTexture(const char *pFilename, int StoreFormat, int Flags); + virtual int LoadPNG(CImageInfo *pImg, const char *pFilename); + + void ScreenshotDirect(const char *pFilename); + + virtual void TextureSet(int TextureID); + + virtual void Clear(float r, float g, float b); + + virtual void QuadsBegin(); + virtual void QuadsEnd(); + virtual void QuadsSetRotation(float Angle); + + virtual void SetColorVertex(const CColorVertex *pArray, int Num); + virtual void SetColor(float r, float g, float b, float a); + + virtual void QuadsSetSubset(float TlU, float TlV, float BrU, float BrV); + virtual void QuadsSetSubsetFree( + float x0, float y0, float x1, float y1, + float x2, float y2, float x3, float y3); + + virtual void QuadsDraw(CQuadItem *pArray, int Num); + virtual void QuadsDrawTL(const CQuadItem *pArray, int Num); + virtual void QuadsDrawFreeform(const CFreeformItem *pArray, int Num); + virtual void QuadsText(float x, float y, float Size, float r, float g, float b, float a, const char *pText); - virtual void TakeScreenshot() = 0; + virtual bool Init(); }; -class IEngineGraphics : public IGraphics +class CGraphics_SDL : public CGraphics_OpenGL { -public: - virtual bool Init() = 0; - virtual void Shutdown() = 0; - virtual void Swap() = 0; - - virtual void Minimize() = 0; - virtual void Maximize() = 0; + SDL_Surface *m_pScreenSurface; - virtual int WindowActive() = 0; - virtual int WindowOpen() = 0; + int TryInit(); + int InitWindow(); +public: + CGraphics_SDL(); + + virtual bool Init(); + virtual void Shutdown(); + + virtual void Minimize(); + virtual void Maximize(); + + virtual int WindowActive(); + virtual int WindowOpen(); + + virtual void TakeScreenshot(); + virtual void Swap(); + + virtual int GetVideoModes(CVideoMode *pModes, int MaxModes); }; -extern IEngineGraphics *CreateEngineGraphics(); +#endif diff --git a/src/engine/client/input.cpp b/src/engine/client/input.cpp new file mode 100644 index 00000000..9f546226 --- /dev/null +++ b/src/engine/client/input.cpp @@ -0,0 +1,208 @@ +// copyright (c) 2007 magnus auvinen, see licence.txt for more info +#include "SDL.h" + +#include <base/system.h> +#include <engine/shared/config.h> +#include <engine/graphics.h> +#include <engine/input.h> +#include <engine/keys.h> + +#include "input.h" + +//print >>f, "int inp_key_code(const char *key_name) { int i; if (!strcmp(key_name, \"-?-\")) return -1; else for (i = 0; i < 512; i++) if (!strcmp(key_strings[i], key_name)) return i; return -1; }" + +// this header is protected so you don't include it from anywere +#define KEYS_INCLUDE +#include "keynames.h" +#undef KEYS_INCLUDE + +void CInput::AddEvent(int Unicode, int Key, int Flags) +{ + if(m_NumEvents != INPUT_BUFFER_SIZE) + { + m_aInputEvents[m_NumEvents].m_Unicode = Unicode; + m_aInputEvents[m_NumEvents].m_Key = Key; + m_aInputEvents[m_NumEvents].m_Flags = Flags; + m_NumEvents++; + } +} + +CInput::CInput() +{ + mem_zero(m_aInputCount, sizeof(m_aInputCount)); + mem_zero(m_aInputState, sizeof(m_aInputState)); + mem_zero(m_Keys, sizeof(m_Keys)); + + m_InputCurrent = 0; + m_InputGrabbed = 0; + + m_LastRelease = 0; + m_ReleaseDelta = -1; + + m_NumEvents = 0; +} + +void CInput::Init() +{ + m_pGraphics = Kernel()->RequestInterface<IEngineGraphics>(); + SDL_EnableUNICODE(1); + SDL_EnableKeyRepeat(SDL_DEFAULT_REPEAT_DELAY, SDL_DEFAULT_REPEAT_INTERVAL); +} + +void CInput::MouseRelative(int *x, int *y) +{ + int nx = 0, ny = 0; + float Sens = g_Config.m_InpMousesens/100.0f; + + if(g_Config.m_InpGrab) + SDL_GetRelativeMouseState(&nx, &ny); + else + { + if(m_InputGrabbed) + { + SDL_GetMouseState(&nx,&ny); + SDL_WarpMouse(Graphics()->ScreenWidth()/2,Graphics()->ScreenHeight()/2); + nx -= Graphics()->ScreenWidth()/2; ny -= Graphics()->ScreenHeight()/2; + } + } + + *x = nx*Sens; + *y = ny*Sens; +} + +void CInput::MouseModeAbsolute() +{ + SDL_ShowCursor(1); + m_InputGrabbed = 0; + if(g_Config.m_InpGrab) + SDL_WM_GrabInput(SDL_GRAB_OFF); +} + +void CInput::MouseModeRelative() +{ + SDL_ShowCursor(0); + m_InputGrabbed = 1; + if(g_Config.m_InpGrab) + SDL_WM_GrabInput(SDL_GRAB_ON); +} + +int CInput::MouseDoubleClick() +{ + return m_ReleaseDelta < (time_freq() >> 2); +} + +void CInput::ClearKeyStates() +{ + mem_zero(m_aInputState, sizeof(m_aInputState)); + mem_zero(m_aInputCount, sizeof(m_aInputCount)); +} + +int CInput::KeyState(int Key) +{ + return m_aInputState[m_InputCurrent][Key]; +} + +void CInput::Update() +{ + if(m_InputGrabbed && !Graphics()->WindowActive()) + MouseModeAbsolute(); + + /*if(!input_grabbed && Graphics()->WindowActive()) + Input()->MouseModeRelative();*/ + + // clear and begin count on the other one + m_InputCurrent^=1; + mem_zero(&m_aInputCount[m_InputCurrent], sizeof(m_aInputCount[m_InputCurrent])); + mem_zero(&m_aInputState[m_InputCurrent], sizeof(m_aInputState[m_InputCurrent])); + + { + int i; + Uint8 *pState = SDL_GetKeyState(&i); + if(i >= KEY_LAST) + i = KEY_LAST-1; + mem_copy(m_aInputState[m_InputCurrent], pState, i); + } + + // these states must always be updated manually because they are not in the GetKeyState from SDL + int i = SDL_GetMouseState(NULL, NULL); + if(i&SDL_BUTTON(1)) m_aInputState[m_InputCurrent][KEY_MOUSE_1] = 1; // 1 is left + if(i&SDL_BUTTON(3)) m_aInputState[m_InputCurrent][KEY_MOUSE_2] = 1; // 3 is right + if(i&SDL_BUTTON(2)) m_aInputState[m_InputCurrent][KEY_MOUSE_3] = 1; // 2 is middle + if(i&SDL_BUTTON(4)) m_aInputState[m_InputCurrent][KEY_MOUSE_4] = 1; + if(i&SDL_BUTTON(5)) m_aInputState[m_InputCurrent][KEY_MOUSE_5] = 1; + if(i&SDL_BUTTON(6)) m_aInputState[m_InputCurrent][KEY_MOUSE_6] = 1; + if(i&SDL_BUTTON(7)) m_aInputState[m_InputCurrent][KEY_MOUSE_7] = 1; + if(i&SDL_BUTTON(8)) m_aInputState[m_InputCurrent][KEY_MOUSE_8] = 1; + + { + SDL_Event Event; + + while(SDL_PollEvent(&Event)) + { + int Key = -1; + int Action = IInput::FLAG_PRESS; + switch (Event.type) + { + // handle keys + case SDL_KEYDOWN: + AddEvent(Event.key.keysym.unicode, 0, 0); // ignore_convention + if(Event.key.keysym.unicode != 0 && Event.key.keysym.unicode < 256) // ignore_convention + { + Key = Event.key.keysym.unicode; // ignore_convention + m_Keys[Event.key.keysym.sym] = Event.key.keysym.unicode; // ignore_convention + } + else + Key = Event.key.keysym.sym; // ignore_convention + break; + case SDL_KEYUP: + Action = IInput::FLAG_RELEASE; + if(m_Keys[Event.key.keysym.sym] != 0) // ignore_convention + Key = m_Keys[Event.key.keysym.sym]; // ignore_convention + else + Key = Event.key.keysym.sym; // ignore_convention + break; + + // handle mouse buttons + case SDL_MOUSEBUTTONUP: + Action = IInput::FLAG_RELEASE; + + if(Event.button.button == 1) // ignore_convention + { + m_ReleaseDelta = time_get() - m_LastRelease; + m_LastRelease = time_get(); + } + + // fall through + case SDL_MOUSEBUTTONDOWN: + if(Event.button.button == SDL_BUTTON_LEFT) Key = KEY_MOUSE_1; // ignore_convention + if(Event.button.button == SDL_BUTTON_RIGHT) Key = KEY_MOUSE_2; // ignore_convention + if(Event.button.button == SDL_BUTTON_MIDDLE) Key = KEY_MOUSE_3; // ignore_convention + if(Event.button.button == SDL_BUTTON_WHEELUP) Key = KEY_MOUSE_WHEEL_UP; // ignore_convention + if(Event.button.button == SDL_BUTTON_WHEELDOWN) Key = KEY_MOUSE_WHEEL_DOWN; // ignore_convention + if(Event.button.button == 6) Key = KEY_MOUSE_6; // ignore_convention + if(Event.button.button == 7) Key = KEY_MOUSE_7; // ignore_convention + if(Event.button.button == 8) Key = KEY_MOUSE_8; // ignore_convention + break; + + // other messages + case SDL_QUIT: + // TODO: cleaner exit + exit(0); // ignore_convention + break; + } + + // + if(Key != -1) + { + m_aInputCount[m_InputCurrent][Key].m_Presses++; + if(Action == IInput::FLAG_PRESS) + m_aInputState[m_InputCurrent][Key] = 1; + AddEvent(0, Key, Action); + } + + } + } +} + + +IEngineInput *CreateEngineInput() { return new CInput; } diff --git a/src/engine/client/input.h b/src/engine/client/input.h new file mode 100644 index 00000000..bf7739ab --- /dev/null +++ b/src/engine/client/input.h @@ -0,0 +1,37 @@ +#ifndef ENGINE_CLIENT_INPUT_H +#define ENGINE_CLIENT_INPUT_H + +class CInput : public IEngineInput +{ + IEngineGraphics *m_pGraphics; + + int m_InputGrabbed; + + unsigned int m_LastRelease; + unsigned int m_ReleaseDelta; + + int m_Keys[1024]; + + void AddEvent(int Unicode, int Key, int Flags); + + IEngineGraphics *Graphics() { return m_pGraphics; } + +public: + CInput(); + + virtual void Init(); + + virtual void MouseRelative(int *x, int *y); + virtual void MouseModeAbsolute(); + virtual void MouseModeRelative(); + virtual int MouseDoubleClick(); + + void ClearKeyStates(); + int KeyState(int Key); + + int ButtonPressed(int Button) { return m_aInputState[m_InputCurrent][Button]; } + + virtual void Update(); +}; + +#endif diff --git a/src/engine/e_keynames.cpp b/src/engine/client/keynames.h index c81744b9..2f159a5a 100644 --- a/src/engine/e_keynames.cpp +++ b/src/engine/client/keynames.h @@ -1,10 +1,13 @@ /* AUTO GENERATED! DO NOT EDIT MANUALLY! */ +#ifndef KEYS_INCLUDE +#error do not include this header! +#endif + #include <string.h> -static const char key_strings[512][16] = +const char g_aaKeyStrings[512][16] = { - "first", "&1", "&2", @@ -519,6 +522,3 @@ static const char key_strings[512][16] = "&511", }; -const char *inp_key_name(int k) { if (k >= 0 && k < 512) return key_strings[k]; else return key_strings[0]; } -int inp_key_code(const char *key_name) { int i; if (!strcmp(key_name, "-?-")) return -1; else for (i = 0; i < 512; i++) if (!strcmp(key_strings[i], key_name)) return i; return -1; } - diff --git a/src/engine/client/sound.cpp b/src/engine/client/sound.cpp new file mode 100644 index 00000000..df8fa66b --- /dev/null +++ b/src/engine/client/sound.cpp @@ -0,0 +1,485 @@ +// copyright (c) 2007 magnus auvinen, see licence.txt for more info +#include <base/system.h> +#include <engine/shared/config.h> + +#include "SDL.h" + +#include "sound.h" + +extern "C" { // wavpack + #include <engine/external/wavpack/wavpack.h> +} +#include <math.h> + +enum +{ + NUM_SAMPLES = 512, + NUM_VOICES = 64, + NUM_CHANNELS = 16, + + MAX_FRAMES = 1024 +}; + +struct CSample +{ + short *m_pData; + int m_NumFrames; + int m_Rate; + int m_Channels; + int m_LoopStart; + int m_LoopEnd; +}; + +struct CChannel +{ + int m_Vol; + int m_Pan; +} ; + +struct CVoice +{ + CSample *m_pSample; + CChannel *m_pChannel; + int m_Tick; + int m_Vol; // 0 - 255 + int m_Flags; + int m_X, m_Y; +} ; + +static CSample m_aSamples[NUM_SAMPLES] = { {0} }; +static CVoice m_aVoices[NUM_VOICES] = { {0} }; +static CChannel m_aChannels[NUM_CHANNELS] = { {255, 0} }; + +static LOCK m_SoundLock = 0; +static int m_SoundEnabled = 0; + +static int m_CenterX = 0; +static int m_CenterY = 0; + +static int m_MixingRate = 48000; +static volatile int m_SoundVolume = 100; + +static int m_NextVoice = 0; + + +// TODO: there should be a faster way todo this +static short Int2Short(int i) +{ + if(i > 0x7fff) + return 0x7fff; + else if(i < -0x7fff) + return -0x7fff; + return i; +} + +static int IntAbs(int i) +{ + if(i<0) + return -i; + return i; +} + +static void Mix(short *pFinalOut, unsigned Frames) +{ + int aMixBuffer[MAX_FRAMES*2] = {0}; + int MasterVol; + + // aquire lock while we are mixing + lock_wait(m_SoundLock); + + MasterVol = m_SoundVolume; + + for(unsigned i = 0; i < NUM_VOICES; i++) + { + if(m_aVoices[i].m_pSample) + { + // mix voice + CVoice *v = &m_aVoices[i]; + int *pOut = aMixBuffer; + + int Step = v->m_pSample->m_Channels; // setup input sources + short *pInL = &v->m_pSample->m_pData[v->m_Tick*Step]; + short *pInR = &v->m_pSample->m_pData[v->m_Tick*Step+1]; + + unsigned End = v->m_pSample->m_NumFrames-v->m_Tick; + + int Rvol = v->m_pChannel->m_Vol; + int Lvol = v->m_pChannel->m_Vol; + + // make sure that we don't go outside the sound data + if(Frames < End) + End = Frames; + + // check if we have a mono sound + if(v->m_pSample->m_Channels == 1) + pInR = pInL; + + // volume calculation + if(v->m_Flags&ISound::FLAG_POS && v->m_pChannel->m_Pan) + { + // TODO: we should respect the channel panning value + const int Range = 1500; // magic value, remove + int dx = v->m_X - m_CenterX; + int dy = v->m_Y - m_CenterY; + int Dist = sqrtf((float)dx*dx+dy*dy); // float here. nasty + int p = IntAbs(dx); + if(Dist < Range) + { + // panning + if(dx > 0) + Lvol = ((Range-p)*Lvol)/Range; + else + Rvol = ((Range-p)*Rvol)/Range; + + // falloff + Lvol = (Lvol*(Range-Dist))/Range; + Rvol = (Rvol*(Range-Dist))/Range; + } + else + { + Lvol = 0; + Rvol = 0; + } + } + + // process all frames + for(unsigned s = 0; s < End; s++) + { + *pOut++ += (*pInL)*Lvol; + *pOut++ += (*pInR)*Rvol; + pInL += Step; + pInR += Step; + v->m_Tick++; + } + + // free voice if not used any more + if(v->m_Tick == v->m_pSample->m_NumFrames) + v->m_pSample = 0; + + } + } + + + // release the lock + lock_release(m_SoundLock); + + { + // clamp accumulated values + // TODO: this seams slow + for(unsigned i = 0; i < Frames; i++) + { + int j = i<<1; + int vl = ((aMixBuffer[j]*MasterVol)/101)>>8; + int vr = ((aMixBuffer[j+1]*MasterVol)/101)>>8; + + pFinalOut[j] = Int2Short(vl); + pFinalOut[j+1] = Int2Short(vr); + } + } + +#if defined(CONF_ARCH_ENDIAN_BIG) + swap_endian(pFinalOut, sizeof(short), Frames * 2); +#endif +} + +static void SdlCallback(void *pUnused, Uint8 *pStream, int Len) +{ + (void)pUnused; + Mix((short *)pStream, Len/2/2); +} + + +int CSound::Init() +{ + m_pGraphics = Kernel()->RequestInterface<IEngineGraphics>(); + m_pStorage = Kernel()->RequestInterface<IStorage>(); + + SDL_AudioSpec Format; + + m_SoundLock = lock_create(); + + if(!g_Config.m_SndEnable) + return 0; + + m_MixingRate = g_Config.m_SndRate; + + // Set 16-bit stereo audio at 22Khz + Format.freq = g_Config.m_SndRate; // ignore_convention + Format.format = AUDIO_S16; // ignore_convention + Format.channels = 2; // ignore_convention + Format.samples = g_Config.m_SndBufferSize; // ignore_convention + Format.callback = SdlCallback; // ignore_convention + Format.userdata = NULL; // ignore_convention + + // Open the audio device and start playing sound! + if(SDL_OpenAudio(&Format, NULL) < 0) + { + dbg_msg("client/sound", "unable to open audio: %s", SDL_GetError()); + return -1; + } + else + dbg_msg("client/sound", "sound init successful"); + + SDL_PauseAudio(0); + + m_SoundEnabled = 1; + Update(); // update the volume + return 0; +} + +int CSound::Update() +{ + // update volume + int WantedVolume = g_Config.m_SndVolume; + + if(!m_pGraphics->WindowActive() && g_Config.m_SndNonactiveMute) + WantedVolume = 0; + + if(WantedVolume != m_SoundVolume) + { + lock_wait(m_SoundLock); + m_SoundVolume = WantedVolume; + lock_release(m_SoundLock); + } + + return 0; +} + +int CSound::Shutdown() +{ + SDL_CloseAudio(); + lock_destroy(m_SoundLock); + return 0; +} + +int CSound::AllocId() +{ + // TODO: linear search, get rid of it + for(unsigned SampleId = 0; SampleId < NUM_SAMPLES; SampleId++) + { + if(m_aSamples[SampleId].m_pData == 0x0) + return SampleId; + } + + return -1; +} + +void CSound::RateConvert(int SampleId) +{ + CSample *pSample = &m_aSamples[SampleId]; + int NumFrames = 0; + short *pNewData = 0; + + // make sure that we need to convert this sound + if(!pSample->m_pData || pSample->m_Rate == m_MixingRate) + return; + + // allocate new data + NumFrames = (int)((pSample->m_NumFrames/(float)pSample->m_Rate)*m_MixingRate); + pNewData = (short *)mem_alloc(NumFrames*pSample->m_Channels*sizeof(short), 1); + + for(int i = 0; i < NumFrames; i++) + { + // resample TODO: this should be done better, like linear atleast + float a = i/(float)NumFrames; + int f = (int)(a*pSample->m_NumFrames); + if(f >= pSample->m_NumFrames) + f = pSample->m_NumFrames-1; + + // set new data + if(pSample->m_Channels == 1) + pNewData[i] = pSample->m_pData[f]; + else if(pSample->m_Channels == 2) + { + pNewData[i*2] = pSample->m_pData[f*2]; + pNewData[i*2+1] = pSample->m_pData[f*2+1]; + } + } + + // free old data and apply new + mem_free(pSample->m_pData); + pSample->m_pData = pNewData; + pSample->m_NumFrames = NumFrames; +} + +int CSound::ReadData(void *pBuffer, int Size) +{ + return io_read(ms_File, pBuffer, Size); +} + +int CSound::LoadWV(const char *pFilename) +{ + CSample *pSample; + int SampleId = -1; + char aError[100]; + WavpackContext *pContext; + + // don't waste memory on sound when we are stress testing + if(g_Config.m_DbgStress) + return -1; + + // no need to load sound when we are running with no sound + if(!m_SoundEnabled) + return 1; + + if(!m_pStorage) + return -1; + + ms_File = m_pStorage->OpenFile(pFilename, IOFLAG_READ); // TODO: use system.h stuff for this + if(!ms_File) + { + dbg_msg("sound/wv", "failed to open %s", pFilename); + return -1; + } + + SampleId = AllocId(); + if(SampleId < 0) + return -1; + pSample = &m_aSamples[SampleId]; + + pContext = WavpackOpenFileInput(ReadData, aError); + if (pContext) + { + int m_aSamples = WavpackGetNumSamples(pContext); + int BitsPerSample = WavpackGetBitsPerSample(pContext); + unsigned int SampleRate = WavpackGetSampleRate(pContext); + int m_aChannels = WavpackGetNumChannels(pContext); + int *pData; + int *pSrc; + short *pDst; + int i; + + pSample->m_Channels = m_aChannels; + pSample->m_Rate = SampleRate; + + if(pSample->m_Channels > 2) + { + dbg_msg("sound/wv", "file is not mono or stereo. filename='%s'", pFilename); + return -1; + } + + /* + if(snd->rate != 44100) + { + dbg_msg("sound/wv", "file is %d Hz, not 44100 Hz. filename='%s'", snd->rate, filename); + return -1; + }*/ + + if(BitsPerSample != 16) + { + dbg_msg("sound/wv", "bps is %d, not 16, filname='%s'", BitsPerSample, pFilename); + return -1; + } + + pData = (int *)mem_alloc(4*m_aSamples*m_aChannels, 1); + WavpackUnpackSamples(pContext, pData, m_aSamples); // TODO: check return value + pSrc = pData; + + pSample->m_pData = (short *)mem_alloc(2*m_aSamples*m_aChannels, 1); + pDst = pSample->m_pData; + + for (i = 0; i < m_aSamples*m_aChannels; i++) + *pDst++ = (short)*pSrc++; + + mem_free(pData); + + pSample->m_NumFrames = m_aSamples; + pSample->m_LoopStart = -1; + pSample->m_LoopEnd = -1; + } + else + { + dbg_msg("sound/wv", "failed to open %s: %s", pFilename, aError); + } + + io_close(ms_File); + ms_File = NULL; + + if(g_Config.m_Debug) + dbg_msg("sound/wv", "loaded %s", pFilename); + + RateConvert(SampleId); + return SampleId; +} + +void CSound::SetListenerPos(float x, float y) +{ + m_CenterX = (int)x; + m_CenterY = (int)y; +} + + +void CSound::SetChannel(int ChannelId, float Vol, float Pan) +{ + m_aChannels[ChannelId].m_Vol = (int)(Vol*255.0f); + m_aChannels[ChannelId].m_Pan = (int)(Pan*255.0f); // TODO: this is only on and off right now +} + +int CSound::Play(int ChannelId, int SampleId, int Flags, float x, float y) +{ + int VoiceId = -1; + int i; + + lock_wait(m_SoundLock); + + // search for voice + for(i = 0; i < NUM_VOICES; i++) + { + int id = (m_NextVoice + i) % NUM_VOICES; + if(!m_aVoices[id].m_pSample) + { + VoiceId = id; + m_NextVoice = id+1; + break; + } + } + + // voice found, use it + if(VoiceId != -1) + { + m_aVoices[VoiceId].m_pSample = &m_aSamples[SampleId]; + m_aVoices[VoiceId].m_pChannel = &m_aChannels[ChannelId]; + m_aVoices[VoiceId].m_Tick = 0; + m_aVoices[VoiceId].m_Vol = 255; + m_aVoices[VoiceId].m_Flags = Flags; + m_aVoices[VoiceId].m_X = (int)x; + m_aVoices[VoiceId].m_Y = (int)y; + } + + lock_release(m_SoundLock); + return VoiceId; +} + +int CSound::PlayAt(int ChannelId, int SampleId, int Flags, float x, float y) +{ + return Play(ChannelId, SampleId, Flags|ISound::FLAG_POS, x, y); +} + +int CSound::Play(int ChannelId, int SampleId, int Flags) +{ + return Play(ChannelId, SampleId, Flags, 0, 0); +} + +void CSound::Stop(int VoiceId) +{ + // TODO: a nice fade out + lock_wait(m_SoundLock); + m_aVoices[VoiceId].m_pSample = 0; + lock_release(m_SoundLock); +} + +void CSound::StopAll() +{ + // TODO: a nice fade out + lock_wait(m_SoundLock); + for(int i = 0; i < NUM_VOICES; i++) + { + m_aVoices[i].m_pSample = 0; + } + lock_release(m_SoundLock); +} + +IOHANDLE CSound::ms_File = 0; + +IEngineSound *CreateEngineSound() { return new CSound; } + diff --git a/src/engine/client/sound.h b/src/engine/client/sound.h new file mode 100644 index 00000000..9c94c6ad --- /dev/null +++ b/src/engine/client/sound.h @@ -0,0 +1,39 @@ +#ifndef ENGINE_CLIENT_SOUND_H +#define ENGINE_CLIENT_SOUND_H + +#include <engine/sound.h> +#include <engine/storage.h> +#include <engine/graphics.h> +#include <engine/shared/engine.h> + +class CSound : public IEngineSound +{ +public: + IEngineGraphics *m_pGraphics; + IStorage *m_pStorage; + + virtual int Init(); + + int Update(); + int Shutdown(); + int AllocId(); + + static void RateConvert(int SampleId); + + // TODO: Refactor: clean this mess up + static IOHANDLE ms_File; + static int ReadData(void *pBuffer, int Size); + + virtual int LoadWV(const char *pFilename); + + virtual void SetListenerPos(float x, float y); + virtual void SetChannel(int ChannelId, float Vol, float Pan); + + int Play(int ChannelId, int SampleId, int Flags, float x, float y); + virtual int PlayAt(int ChannelId, int SampleId, int Flags, float x, float y); + virtual int Play(int ChannelId, int SampleId, int Flags); + virtual void Stop(int VoiceId); + virtual void StopAll(); +}; + +#endif diff --git a/src/engine/client/srvbrowse.cpp b/src/engine/client/srvbrowse.cpp new file mode 100644 index 00000000..e0997467 --- /dev/null +++ b/src/engine/client/srvbrowse.cpp @@ -0,0 +1,721 @@ +// copyright (c) 2007 magnus auvinen, see licence.txt for more info +#include <algorithm> // sort + +#include <base/system.h> +#include <engine/shared/network.h> +#include <engine/shared/protocol.h> +#include <engine/shared/config.h> +#include <engine/shared/memheap.h> +#include <engine/shared/engine.h> + +#include <engine/masterserver.h> +#include <engine/config.h> + +#include <mastersrv/mastersrv.h> + +#include "srvbrowse.h" + +class SortWrap +{ + typedef bool (CServerBrowser::*SortFunc)(int, int) const; + SortFunc m_pfnSort; + CServerBrowser *m_pThis; +public: + SortWrap(CServerBrowser *t, SortFunc f) : m_pfnSort(f), m_pThis(t) {} + bool operator()(int a, int b) { return (m_pThis->*m_pfnSort)(a, b); } +}; + +CServerBrowser::CServerBrowser() +{ + m_pMasterServer = 0; + m_ppServerlist = 0; + m_pSortedServerlist = 0; + + m_NumFavoriteServers = 0; + + mem_zero(m_aServerlistIp, sizeof(m_aServerlistIp)); + + m_pFirstReqServer = 0; // request list + m_pLastReqServer = 0; + m_NumRequests = 0; + + m_NeedRefresh = 0; + + m_NumSortedServers = 0; + m_NumSortedServersCapacity = 0; + m_NumServers = 0; + m_NumServerCapacity = 0; + + m_Sorthash = 0; + m_aFilterString[0] = 0; + m_aFilterGametypeString[0] = 0; + + // the token is to keep server refresh separated from each other + m_CurrentToken = 1; + + m_ServerlistType = 0; + m_BroadcastTime = 0; +} + +void CServerBrowser::SetBaseInfo(class CNetClient *pClient, const char *pNetVersion) +{ + m_pNetClient = pClient; + str_copy(m_aNetVersion, pNetVersion, sizeof(m_aNetVersion)); + m_pMasterServer = Kernel()->RequestInterface<IMasterServer>(); + IConfig *pConfig = Kernel()->RequestInterface<IConfig>(); + if(pConfig) + pConfig->RegisterCallback(ConfigSaveCallback, this); +} + +const CServerInfo *CServerBrowser::SortedGet(int Index) const +{ + if(Index < 0 || Index >= m_NumSortedServers) + return 0; + return &m_ppServerlist[m_pSortedServerlist[Index]]->m_Info; +} + + +bool CServerBrowser::SortCompareName(int Index1, int Index2) const +{ + CServerEntry *a = m_ppServerlist[Index1]; + CServerEntry *b = m_ppServerlist[Index2]; + return str_comp(a->m_Info.m_aName, b->m_Info.m_aName) < 0; +} + +bool CServerBrowser::SortCompareMap(int Index1, int Index2) const +{ + CServerEntry *a = m_ppServerlist[Index1]; + CServerEntry *b = m_ppServerlist[Index2]; + return str_comp(a->m_Info.m_aMap, b->m_Info.m_aMap) < 0; +} + +bool CServerBrowser::SortComparePing(int Index1, int Index2) const +{ + CServerEntry *a = m_ppServerlist[Index1]; + CServerEntry *b = m_ppServerlist[Index2]; + return a->m_Info.m_Latency < b->m_Info.m_Latency; +} + +bool CServerBrowser::SortCompareGametype(int Index1, int Index2) const +{ + CServerEntry *a = m_ppServerlist[Index1]; + CServerEntry *b = m_ppServerlist[Index2]; + return str_comp(a->m_Info.m_aGameType, b->m_Info.m_aGameType) < 0; +} + +bool CServerBrowser::SortCompareProgression(int Index1, int Index2) const +{ + CServerEntry *a = m_ppServerlist[Index1]; + CServerEntry *b = m_ppServerlist[Index2]; + return a->m_Info.m_Progression < b->m_Info.m_Progression; +} + +bool CServerBrowser::SortCompareNumPlayers(int Index1, int Index2) const +{ + CServerEntry *a = m_ppServerlist[Index1]; + CServerEntry *b = m_ppServerlist[Index2]; + return a->m_Info.m_NumPlayers < b->m_Info.m_NumPlayers; +} + +void CServerBrowser::Filter() +{ + int i = 0, p = 0; + m_NumSortedServers = 0; + + // allocate the sorted list + if(m_NumSortedServersCapacity < m_NumServers) + { + if(m_pSortedServerlist) + mem_free(m_pSortedServerlist); + m_NumSortedServersCapacity = m_NumServers; + m_pSortedServerlist = (int *)mem_alloc(m_NumSortedServersCapacity*sizeof(int), 1); + } + + // filter the servers + for(i = 0; i < m_NumServers; i++) + { + int Filtered = 0; + + if(g_Config.m_BrFilterEmpty && m_ppServerlist[i]->m_Info.m_NumPlayers == 0) + Filtered = 1; + else if(g_Config.m_BrFilterFull && m_ppServerlist[i]->m_Info.m_NumPlayers == m_ppServerlist[i]->m_Info.m_MaxPlayers) + Filtered = 1; + else if(g_Config.m_BrFilterPw && m_ppServerlist[i]->m_Info.m_Flags&SERVER_FLAG_PASSWORD) + Filtered = 1; + else if(g_Config.m_BrFilterPure && + (str_comp(m_ppServerlist[i]->m_Info.m_aGameType, "DM") != 0 && + str_comp(m_ppServerlist[i]->m_Info.m_aGameType, "TDM") != 0 && + str_comp(m_ppServerlist[i]->m_Info.m_aGameType, "CTF") != 0)) + { + Filtered = 1; + } + else if(g_Config.m_BrFilterPureMap && + !(str_comp(m_ppServerlist[i]->m_Info.m_aMap, "dm1") == 0 || + str_comp(m_ppServerlist[i]->m_Info.m_aMap, "dm2") == 0 || + str_comp(m_ppServerlist[i]->m_Info.m_aMap, "dm6") == 0 || + str_comp(m_ppServerlist[i]->m_Info.m_aMap, "dm7") == 0 || + str_comp(m_ppServerlist[i]->m_Info.m_aMap, "dm8") == 0 || + str_comp(m_ppServerlist[i]->m_Info.m_aMap, "dm9") == 0 || + str_comp(m_ppServerlist[i]->m_Info.m_aMap, "ctf1") == 0 || + str_comp(m_ppServerlist[i]->m_Info.m_aMap, "ctf2") == 0 || + str_comp(m_ppServerlist[i]->m_Info.m_aMap, "ctf3") == 0 || + str_comp(m_ppServerlist[i]->m_Info.m_aMap, "ctf4") == 0 || + str_comp(m_ppServerlist[i]->m_Info.m_aMap, "ctf5") == 0) + ) + { + Filtered = 1; + } + else if(g_Config.m_BrFilterPing < m_ppServerlist[i]->m_Info.m_Latency) + Filtered = 1; + else if(g_Config.m_BrFilterCompatversion && str_comp_num(m_ppServerlist[i]->m_Info.m_aVersion, m_aNetVersion, 3) != 0) + Filtered = 1; + else + { + if(g_Config.m_BrFilterString[0] != 0) + { + int MatchFound = 0; + + m_ppServerlist[i]->m_Info.m_QuickSearchHit = 0; + + // match against server name + if(str_find_nocase(m_ppServerlist[i]->m_Info.m_aName, g_Config.m_BrFilterString)) + { + MatchFound = 1; + m_ppServerlist[i]->m_Info.m_QuickSearchHit |= IServerBrowser::QUICK_SERVERNAME; + } + + // match against players + for(p = 0; p < m_ppServerlist[i]->m_Info.m_NumPlayers; p++) + { + if(str_find_nocase(m_ppServerlist[i]->m_Info.m_aPlayers[p].m_aName, g_Config.m_BrFilterString)) + { + MatchFound = 1; + m_ppServerlist[i]->m_Info.m_QuickSearchHit |= IServerBrowser::QUICK_PLAYERNAME; + break; + } + } + + // match against map + if(str_find_nocase(m_ppServerlist[i]->m_Info.m_aMap, g_Config.m_BrFilterString)) + { + MatchFound = 1; + m_ppServerlist[i]->m_Info.m_QuickSearchHit |= IServerBrowser::QUICK_MAPNAME; + } + + if(!MatchFound) + Filtered = 1; + } + + if(!Filtered && g_Config.m_BrFilterGametype[0] != 0) + { + // match against game type + if(!str_find_nocase(m_ppServerlist[i]->m_Info.m_aGameType, g_Config.m_BrFilterGametype)) + Filtered = 1; + } + } + + if(Filtered == 0) + m_pSortedServerlist[m_NumSortedServers++] = i; + } +} + +int CServerBrowser::SortHash() const +{ + int i = g_Config.m_BrSort&0xf; + i |= g_Config.m_BrFilterEmpty<<4; + i |= g_Config.m_BrFilterFull<<5; + i |= g_Config.m_BrFilterPw<<6; + i |= g_Config.m_BrSortOrder<<7; + i |= g_Config.m_BrFilterCompatversion<<8; + i |= g_Config.m_BrFilterPure<<9; + i |= g_Config.m_BrFilterPureMap<<10; + i |= g_Config.m_BrFilterPing<<16; + return i; +} + +void CServerBrowser::Sort() +{ + int i; + + // create filtered list + Filter(); + + // sort + if(g_Config.m_BrSort == IServerBrowser::SORT_NAME) + std::sort(m_pSortedServerlist, m_pSortedServerlist+m_NumSortedServers, SortWrap(this, &CServerBrowser::SortCompareName)); + else if(g_Config.m_BrSort == IServerBrowser::SORT_PING) + std::sort(m_pSortedServerlist, m_pSortedServerlist+m_NumSortedServers, SortWrap(this, &CServerBrowser::SortComparePing)); + else if(g_Config.m_BrSort == IServerBrowser::SORT_MAP) + std::sort(m_pSortedServerlist, m_pSortedServerlist+m_NumSortedServers, SortWrap(this, &CServerBrowser::SortCompareMap)); + else if(g_Config.m_BrSort == IServerBrowser::SORT_NUMPLAYERS) + std::sort(m_pSortedServerlist, m_pSortedServerlist+m_NumSortedServers, SortWrap(this, &CServerBrowser::SortCompareNumPlayers)); + else if(g_Config.m_BrSort == IServerBrowser::SORT_GAMETYPE) + std::sort(m_pSortedServerlist, m_pSortedServerlist+m_NumSortedServers, SortWrap(this, &CServerBrowser::SortCompareGametype)); + else if(g_Config.m_BrSort == IServerBrowser::SORT_PROGRESSION) + std::sort(m_pSortedServerlist, m_pSortedServerlist+m_NumSortedServers, SortWrap(this, &CServerBrowser::SortCompareProgression)); + + // invert the list if requested + if(g_Config.m_BrSortOrder) + { + for(i = 0; i < m_NumSortedServers/2; i++) + { + int Temp = m_pSortedServerlist[i]; + m_pSortedServerlist[i] = m_pSortedServerlist[m_NumSortedServers-i-1]; + m_pSortedServerlist[m_NumSortedServers-i-1] = Temp; + } + } + + // set indexes + for(i = 0; i < m_NumSortedServers; i++) + m_ppServerlist[m_pSortedServerlist[i]]->m_Info.m_SortedIndex = i; + + str_copy(m_aFilterGametypeString, g_Config.m_BrFilterGametype, sizeof(m_aFilterGametypeString)); + str_copy(m_aFilterString, g_Config.m_BrFilterString, sizeof(m_aFilterString)); + m_Sorthash = SortHash(); +} + +void CServerBrowser::RemoveRequest(CServerEntry *pEntry) +{ + if(pEntry->m_pPrevReq || pEntry->m_pNextReq || m_pFirstReqServer == pEntry) + { + if(pEntry->m_pPrevReq) + pEntry->m_pPrevReq->m_pNextReq = pEntry->m_pNextReq; + else + m_pFirstReqServer = pEntry->m_pNextReq; + + if(pEntry->m_pNextReq) + pEntry->m_pNextReq->m_pPrevReq = pEntry->m_pPrevReq; + else + m_pLastReqServer = pEntry->m_pPrevReq; + + pEntry->m_pPrevReq = 0; + pEntry->m_pNextReq = 0; + m_NumRequests--; + } +} + +CServerBrowser::CServerEntry *CServerBrowser::Find(const NETADDR &Addr) +{ + CServerEntry *pEntry = m_aServerlistIp[Addr.ip[0]]; + + for(; pEntry; pEntry = pEntry->m_pNextIp) + { + if(net_addr_comp(&pEntry->m_Addr, &Addr) == 0) + return pEntry; + } + return (CServerEntry*)0; +} + +void CServerBrowser::QueueRequest(CServerEntry *pEntry) +{ + // add it to the list of servers that we should request info from + pEntry->m_pPrevReq = m_pLastReqServer; + if(m_pLastReqServer) + m_pLastReqServer->m_pNextReq = pEntry; + else + m_pFirstReqServer = pEntry; + m_pLastReqServer = pEntry; + + m_NumRequests++; +} + +void CServerBrowser::SetInfo(CServerEntry *pEntry, const CServerInfo &Info) +{ + int Fav = pEntry->m_Info.m_Favorite; + pEntry->m_Info = Info; + pEntry->m_Info.m_Favorite = Fav; + pEntry->m_Info.m_NetAddr = pEntry->m_Addr; + + // all these are just for nice compability + if(pEntry->m_Info.m_aGameType[0] == '0' && pEntry->m_Info.m_aGameType[1] == 0) + str_copy(pEntry->m_Info.m_aGameType, "DM", sizeof(pEntry->m_Info.m_aGameType)); + else if(pEntry->m_Info.m_aGameType[0] == '1' && pEntry->m_Info.m_aGameType[1] == 0) + str_copy(pEntry->m_Info.m_aGameType, "TDM", sizeof(pEntry->m_Info.m_aGameType)); + else if(pEntry->m_Info.m_aGameType[0] == '2' && pEntry->m_Info.m_aGameType[1] == 0) + str_copy(pEntry->m_Info.m_aGameType, "CTF", sizeof(pEntry->m_Info.m_aGameType)); + + /*if(!request) + { + pEntry->m_Info.latency = (time_get()-pEntry->request_time)*1000/time_freq(); + RemoveRequest(pEntry); + }*/ + + pEntry->m_GotInfo = 1; + Sort(); +} + +CServerBrowser::CServerEntry *CServerBrowser::Add(const NETADDR &Addr) +{ + int Hash = Addr.ip[0]; + CServerEntry *pEntry = 0; + int i; + + // create new pEntry + pEntry = (CServerEntry *)m_ServerlistHeap.Allocate(sizeof(CServerEntry)); + mem_zero(pEntry, sizeof(CServerEntry)); + + // set the info + pEntry->m_Addr = Addr; + pEntry->m_Info.m_NetAddr = Addr; + + pEntry->m_Info.m_Latency = 999; + str_format(pEntry->m_Info.m_aAddress, sizeof(pEntry->m_Info.m_aAddress), "%d.%d.%d.%d:%d", + Addr.ip[0], Addr.ip[1], Addr.ip[2], + Addr.ip[3], Addr.port); + str_format(pEntry->m_Info.m_aName, sizeof(pEntry->m_Info.m_aName), "\255%d.%d.%d.%d:%d", // the \255 is to make sure that it's sorted last + Addr.ip[0], Addr.ip[1], Addr.ip[2], + Addr.ip[3], Addr.port); + + /*if(serverlist_type == IServerBrowser::TYPE_LAN) + pEntry->m_Info.latency = (time_get()-broadcast_time)*1000/time_freq();*/ + + // check if it's a favorite + for(i = 0; i < m_NumFavoriteServers; i++) + { + if(net_addr_comp(&Addr, &m_aFavoriteServers[i]) == 0) + pEntry->m_Info.m_Favorite = 1; + } + + // add to the hash list + pEntry->m_pNextIp = m_aServerlistIp[Hash]; + m_aServerlistIp[Hash] = pEntry; + + if(m_NumServers == m_NumServerCapacity) + { + CServerEntry **ppNewlist; + m_NumServerCapacity += 100; + ppNewlist = (CServerEntry **)mem_alloc(m_NumServerCapacity*sizeof(CServerEntry*), 1); + mem_copy(ppNewlist, m_ppServerlist, m_NumServers*sizeof(CServerEntry*)); + mem_free(m_ppServerlist); + m_ppServerlist = ppNewlist; + } + + // add to list + m_ppServerlist[m_NumServers] = pEntry; + pEntry->m_Info.m_ServerIndex = m_NumServers; + m_NumServers++; + + return pEntry; +} + +void CServerBrowser::Set(const NETADDR &Addr, int Type, int Token, const CServerInfo *pInfo) +{ + CServerEntry *pEntry = 0; + if(Type == IServerBrowser::SET_MASTER_ADD) + { + if(m_ServerlistType != IServerBrowser::TYPE_INTERNET) + return; + + if(!Find(Addr)) + { + pEntry = Add(Addr); + QueueRequest(pEntry); + } + } + else if(Type == IServerBrowser::SET_FAV_ADD) + { + if(m_ServerlistType != IServerBrowser::TYPE_FAVORITES) + return; + + if(!Find(Addr)) + { + pEntry = Add(Addr); + QueueRequest(pEntry); + } + } + else if(Type == IServerBrowser::SET_TOKEN) + { + if(Token != m_CurrentToken) + return; + + pEntry = Find(Addr); + if(!pEntry) + pEntry = Add(Addr); + if(pEntry) + { + SetInfo(pEntry, *pInfo); + if(m_ServerlistType == IServerBrowser::TYPE_LAN) + pEntry->m_Info.m_Latency = (time_get()-m_BroadcastTime)*1000/time_freq(); + else + pEntry->m_Info.m_Latency = (time_get()-pEntry->m_RequestTime)*1000/time_freq(); + RemoveRequest(pEntry); + } + } + else if(Type == IServerBrowser::SET_OLD_INTERNET) + { + pEntry = Find(Addr); + if(pEntry) + { + SetInfo(pEntry, *pInfo); + + if(m_ServerlistType == IServerBrowser::TYPE_LAN) + pEntry->m_Info.m_Latency = (time_get()-m_BroadcastTime)*1000/time_freq(); + else + pEntry->m_Info.m_Latency = (time_get()-pEntry->m_RequestTime)*1000/time_freq(); + RemoveRequest(pEntry); + } + } + else if(Type == IServerBrowser::SET_OLD_LAN) + { + pEntry = Find(Addr); + if(pEntry) + if(!pEntry) + pEntry = Add(Addr); + if(pEntry) + SetInfo(pEntry, *pInfo); + } + + Sort(); +} + +void CServerBrowser::Refresh(int Type) +{ + // clear out everything + m_ServerlistHeap.Reset(); + m_NumServers = 0; + m_NumSortedServers = 0; + mem_zero(m_aServerlistIp, sizeof(m_aServerlistIp)); + m_pFirstReqServer = 0; + m_pLastReqServer = 0; + m_NumRequests = 0; + + // next token + m_CurrentToken = (m_CurrentToken+1)&0xff; + + // + m_ServerlistType = Type; + + if(Type == IServerBrowser::TYPE_LAN) + { + unsigned char Buffer[sizeof(SERVERBROWSE_GETINFO)+1]; + CNetChunk Packet; + int i; + + mem_copy(Buffer, SERVERBROWSE_GETINFO, sizeof(SERVERBROWSE_GETINFO)); + Buffer[sizeof(SERVERBROWSE_GETINFO)] = m_CurrentToken; + + Packet.m_ClientID = -1; + mem_zero(&Packet, sizeof(Packet)); + Packet.m_Address.ip[0] = 255; + Packet.m_Address.ip[1] = 255; + Packet.m_Address.ip[2] = 255; + Packet.m_Address.ip[3] = 255; + Packet.m_Flags = NETSENDFLAG_CONNLESS; + Packet.m_DataSize = sizeof(Buffer); + Packet.m_pData = Buffer; + m_BroadcastTime = time_get(); + + for(i = 8303; i <= 8310; i++) + { + Packet.m_Address.port = i; + m_pNetClient->Send(&Packet); + } + + if(g_Config.m_Debug) + dbg_msg("client", "broadcasting for servers"); + } + else if(Type == IServerBrowser::TYPE_INTERNET) + m_NeedRefresh = 1; + else if(Type == IServerBrowser::TYPE_FAVORITES) + { + for(int i = 0; i < m_NumFavoriteServers; i++) + Set(m_aFavoriteServers[i], IServerBrowser::SET_FAV_ADD, -1, 0); + } +} + +void CServerBrowser::RequestImpl(const NETADDR &Addr, CServerEntry *pEntry) const +{ + //unsigned char buffer[sizeof(SERVERBROWSE_GETINFO)+1]; + CNetChunk Packet; + + if(g_Config.m_Debug) + { + dbg_msg("client", "requesting server info from %d.%d.%d.%d:%d", + Addr.ip[0], Addr.ip[1], Addr.ip[2], + Addr.ip[3], Addr.port); + } + + /*mem_copy(buffer, SERVERBROWSE_GETINFO, sizeof(SERVERBROWSE_GETINFO)); + buffer[sizeof(SERVERBROWSE_GETINFO)] = current_token;*/ + + Packet.m_ClientID = -1; + Packet.m_Address = Addr; + Packet.m_Flags = NETSENDFLAG_CONNLESS; + /*p.data_size = sizeof(buffer); + p.data = buffer; + netclient_send(net, &p);*/ + + // send old request style aswell + Packet.m_DataSize = sizeof(SERVERBROWSE_OLD_GETINFO); + Packet.m_pData = SERVERBROWSE_OLD_GETINFO; + m_pNetClient->Send(&Packet); + + if(pEntry) + pEntry->m_RequestTime = time_get(); +} + +void CServerBrowser::Request(const NETADDR &Addr) const +{ + RequestImpl(Addr, 0); +} + + +void CServerBrowser::Update() +{ + int64 Timeout = time_freq(); + int64 Now = time_get(); + int Count; + CServerEntry *pEntry, *pNext; + + // do server list requests + if(m_NeedRefresh && !m_pMasterServer->IsRefreshing()) + { + NETADDR Addr; + CNetChunk Packet; + int i; + + m_NeedRefresh = 0; + + mem_zero(&Packet, sizeof(Packet)); + Packet.m_ClientID = -1; + Packet.m_Flags = NETSENDFLAG_CONNLESS; + Packet.m_DataSize = sizeof(SERVERBROWSE_GETLIST); + Packet.m_pData = SERVERBROWSE_GETLIST; + + for(i = 0; i < IMasterServer::MAX_MASTERSERVERS; i++) + { + Addr = m_pMasterServer->GetAddr(i); + if(!Addr.ip[0] && !Addr.ip[1] && !Addr.ip[2] && !Addr.ip[3]) + continue; + + Packet.m_Address = Addr; + m_pNetClient->Send(&Packet); + } + + if(g_Config.m_Debug) + dbg_msg("client", "requesting server list"); + } + + // do timeouts + pEntry = m_pFirstReqServer; + while(1) + { + if(!pEntry) // no more entries + break; + + pNext = pEntry->m_pNextReq; + + if(pEntry->m_RequestTime && pEntry->m_RequestTime+Timeout < Now) + { + // timeout + RemoveRequest(pEntry); + m_NumRequests--; + } + + pEntry = pNext; + } + + // do timeouts + pEntry = m_pFirstReqServer; + Count = 0; + while(1) + { + if(!pEntry) // no more entries + break; + + // no more then 10 concurrent requests + if(Count == g_Config.m_BrMaxRequests) + break; + + if(pEntry->m_RequestTime == 0) + RequestImpl(pEntry->m_Addr, pEntry); + + Count++; + pEntry = pEntry->m_pNextReq; + } + + // check if we need to resort + // TODO: remove the str_comp + if(m_Sorthash != SortHash() || str_comp(m_aFilterString, g_Config.m_BrFilterString) != 0 || str_comp(m_aFilterGametypeString, g_Config.m_BrFilterGametype) != 0) + Sort(); +} + + +bool CServerBrowser::IsFavorite(const NETADDR &Addr) const +{ + // search for the address + int i; + for(i = 0; i < m_NumFavoriteServers; i++) + { + if(net_addr_comp(&Addr, &m_aFavoriteServers[i]) == 0) + return true; + } + return false; +} + +void CServerBrowser::AddFavorite(const NETADDR &Addr) +{ + CServerEntry *pEntry; + + if(m_NumFavoriteServers == MAX_FAVORITES) + return; + + // make sure that we don't already have the server in our list + for(int i = 0; i < m_NumFavoriteServers; i++) + { + if(net_addr_comp(&Addr, &m_aFavoriteServers[i]) == 0) + return; + } + + // add the server to the list + m_aFavoriteServers[m_NumFavoriteServers++] = Addr; + pEntry = Find(Addr); + if(pEntry) + pEntry->m_Info.m_Favorite = 1; + + if(g_Config.m_Debug) + dbg_msg("", "added fav, %d.%d.%d.%d:%d", Addr.ip[0], Addr.ip[1], Addr.ip[2], Addr.ip[3], Addr.port); +} + +void CServerBrowser::RemoveFavorite(const NETADDR &Addr) +{ + int i; + CServerEntry *pEntry; + + for(i = 0; i < m_NumFavoriteServers; i++) + { + if(net_addr_comp(&Addr, &m_aFavoriteServers[i]) == 0) + { + mem_move(&m_aFavoriteServers[i], &m_aFavoriteServers[i+1], sizeof(NETADDR)*(m_NumFavoriteServers-(i+1))); + m_NumFavoriteServers--; + + pEntry = Find(Addr); + if(pEntry) + pEntry->m_Info.m_Favorite = 0; + + return; + } + } +} + + +bool CServerBrowser::IsRefreshingMasters() const +{ + return m_pMasterServer->IsRefreshing(); +} + + +void CServerBrowser::ConfigSaveCallback(IConfig *pConfig, void *pUserData) +{ + CServerBrowser *pSelf = (CServerBrowser *)pUserData; + + int i; + char aAddrStr[128]; + char aBuffer[256]; + for(i = 0; i < pSelf->m_NumFavoriteServers; i++) + { + net_addr_str(&pSelf->m_aFavoriteServers[i], aAddrStr, sizeof(aAddrStr)); + str_format(aBuffer, sizeof(aBuffer), "add_favorite %s", aAddrStr); + pConfig->WriteLine(aBuffer); + } +} diff --git a/src/engine/client/srvbrowse.h b/src/engine/client/srvbrowse.h new file mode 100644 index 00000000..1c255792 --- /dev/null +++ b/src/engine/client/srvbrowse.h @@ -0,0 +1,111 @@ +#ifndef ENGINE_CLIENT_SRVBROWSE_H +#define ENGINE_CLIENT_SRVBROWSE_H + +#include <engine/serverbrowser.h> + +class CServerBrowser : public IServerBrowser +{ +public: + class CServerEntry + { + public: + NETADDR m_Addr; + int64 m_RequestTime; + int m_GotInfo; + CServerInfo m_Info; + + CServerEntry *m_pNextIp; // ip hashed list + + CServerEntry *m_pPrevReq; // request list + CServerEntry *m_pNextReq; + }; + + enum + { + MAX_FAVORITES=256 + }; + + CServerBrowser(); + + // interface functions + void Refresh(int Type); + bool IsRefreshingMasters() const; + + int NumServers() const { return m_NumServers; } + + int NumSortedServers() const { return m_NumSortedServers; } + const CServerInfo *SortedGet(int Index) const; + + bool IsFavorite(const NETADDR &Addr) const; + void AddFavorite(const NETADDR &Addr); + void RemoveFavorite(const NETADDR &Addr); + + // + void Update(); + void Set(const NETADDR &Addr, int Type, int Token, const CServerInfo *pInfo); + void Request(const NETADDR &Addr) const; + + void SetBaseInfo(class CNetClient *pClient, const char *pNetVersion); + +private: + CNetClient *m_pNetClient; + IMasterServer *m_pMasterServer; + char m_aNetVersion[128]; + + CHeap m_ServerlistHeap; + CServerEntry **m_ppServerlist; + int *m_pSortedServerlist; + + NETADDR m_aFavoriteServers[MAX_FAVORITES]; + int m_NumFavoriteServers; + + CServerEntry *m_aServerlistIp[256]; // ip hash list + + CServerEntry *m_pFirstReqServer; // request list + CServerEntry *m_pLastReqServer; + int m_NumRequests; + + int m_NeedRefresh; + + int m_NumSortedServers; + int m_NumSortedServersCapacity; + int m_NumServers; + int m_NumServerCapacity; + + int m_Sorthash; + char m_aFilterString[64]; + char m_aFilterGametypeString[128]; + + // the token is to keep server refresh separated from each other + int m_CurrentToken; + + int m_ServerlistType; + int64 m_BroadcastTime; + + // sorting criterions + bool SortCompareName(int Index1, int Index2) const; + bool SortCompareMap(int Index1, int Index2) const; + bool SortComparePing(int Index1, int Index2) const; + bool SortCompareGametype(int Index1, int Index2) const; + bool SortCompareProgression(int Index1, int Index2) const; + bool SortCompareNumPlayers(int Index1, int Index2) const; + + // + void Filter(); + void Sort(); + int SortHash() const; + + CServerEntry *Find(const NETADDR &Addr); + CServerEntry *Add(const NETADDR &Addr); + + void RemoveRequest(CServerEntry *pEntry); + void QueueRequest(CServerEntry *pEntry); + + void RequestImpl(const NETADDR &Addr, CServerEntry *pEntry) const; + + void SetInfo(CServerEntry *pEntry, const CServerInfo &Info); + + static void ConfigSaveCallback(IConfig *pConfig, void *pUserData); +}; + +#endif diff --git a/src/engine/client/text.cpp b/src/engine/client/text.cpp new file mode 100644 index 00000000..b05d49f8 --- /dev/null +++ b/src/engine/client/text.cpp @@ -0,0 +1,718 @@ +#include <base/system.h> +#include <base/math.h> +#include <engine/graphics.h> +#include <engine/textrender.h> + +#ifdef CONF_FAMILY_WINDOWS + #include <windows.h> +#endif + +#ifdef CONF_PLATFORM_MACOSX + #include <OpenGL/gl.h> + #include <OpenGL/glu.h> +#else + #include <GL/gl.h> + #include <GL/glu.h> +#endif + +// ft2 texture +#include <ft2build.h> +#include FT_FREETYPE_H + +// TODO: Refactor: clean this up + + +enum +{ + MAX_CHARACTERS = 64, +}; + + +static int aFontSizes[] = {8,9,10,11,12,13,14,15,16,17,18,19,20,36}; +#define NUM_FONT_SIZES (sizeof(aFontSizes)/sizeof(int)) + +struct CFontChar +{ + int m_Id; + + // these values are scaled to the pFont size + // width * font_size == real_size + float m_Width; + float m_Height; + float m_OffsetX; + float m_OffsetY; + float m_AdvanceX; + + float m_aUvs[4]; + int64 m_TouchTime; +}; + +struct CFontSizeData +{ + int m_FontSize; + FT_Face *m_pFace; + + unsigned m_aTextures[2]; + int m_TextureWidth; + int m_TextureHeight; + + int m_NumXChars; + int m_NumYChars; + + int m_CharMaxWidth; + int m_CharMaxHeight; + + CFontChar m_aCharacters[MAX_CHARACTERS*MAX_CHARACTERS]; + + int m_CurrentCharacter; +}; + +struct CFont +{ + char m_aFilename[128]; + FT_Face m_FtFace; + CFontSizeData m_aSizes[NUM_FONT_SIZES]; +}; + + +class CTextRender : public IEngineTextRender +{ + IGraphics *m_pGraphics; + IGraphics *Graphics() { return m_pGraphics; } + + int WordLength(const char *pText) + { + int s = 1; + while(1) + { + if(*pText == 0) + return s-1; + if(*pText == '\n' || *pText == '\t' || *pText == ' ') + return s; + pText++; + s++; + } + } + + float m_TextR; + float m_TextG; + float m_TextB; + float m_TextA; + + int m_FontTextureFormat; + + struct CFont *m_pDefaultFont; + + FT_Library m_FTLibrary; + + int GetFontSizeIndex(int Pixelsize) + { + for(unsigned i = 0; i < NUM_FONT_SIZES; i++) + { + if(aFontSizes[i] >= Pixelsize) + return i; + } + + return NUM_FONT_SIZES-1; + } + + + + void Grow(unsigned char *pIn, unsigned char *pOut, int w, int h) + { + for(int y = 0; y < h; y++) + for(int x = 0; x < w; x++) + { + int c = pIn[y*w+x]; + + for(int sy = -1; sy <= 1; sy++) + for(int sx = -1; sx <= 1; sx++) + { + int GetX = x+sx; + int GetY = y+sy; + if (GetX >= 0 && GetY >= 0 && GetX < w && GetY < h) + { + int Index = GetY*w+GetX; + if(pIn[Index] > c) + c = pIn[Index]; + } + } + + pOut[y*w+x] = c; + } + } + + void InitTexture(CFontSizeData *pSizeData, int CharWidth, int CharHeight, int Xchars, int Ychars) + { + static int FontMemoryUsage = 0; + int Width = CharWidth*Xchars; + int Height = CharHeight*Ychars; + void *pMem = mem_alloc(Width*Height, 1); + mem_zero(pMem, Width*Height); + + if(pSizeData->m_aTextures[0] == 0) + glGenTextures(2, pSizeData->m_aTextures); + else + FontMemoryUsage -= pSizeData->m_TextureWidth*pSizeData->m_TextureHeight*2; + + pSizeData->m_NumXChars = Xchars; + pSizeData->m_NumYChars = Ychars; + pSizeData->m_TextureWidth = Width; + pSizeData->m_TextureHeight = Height; + pSizeData->m_CurrentCharacter = 0; + + for(int i = 0; i < 2; i++) + { + glBindTexture(GL_TEXTURE_2D, pSizeData->m_aTextures[i]); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexImage2D(GL_TEXTURE_2D, 0, m_FontTextureFormat, Width, Height, 0, m_FontTextureFormat, GL_UNSIGNED_BYTE, pMem); + FontMemoryUsage += Width*Height; + } + + dbg_msg("", "pFont memory usage: %d", FontMemoryUsage); + + mem_free(pMem); + } + + void IncreaseTextureSize(CFontSizeData *pSizeData) + { + if(pSizeData->m_TextureWidth < pSizeData->m_TextureHeight) + pSizeData->m_NumXChars <<= 1; + else + pSizeData->m_NumYChars <<= 1; + InitTexture(pSizeData, pSizeData->m_CharMaxWidth, pSizeData->m_CharMaxHeight, pSizeData->m_NumXChars, pSizeData->m_NumYChars); + } + + + // TODO: Refactor: move this into a pFont class + void InitIndex(CFont *pFont, int Index) + { + int OutlineThickness = 1; + CFontSizeData *pSizeData = &pFont->m_aSizes[Index]; + + pSizeData->m_FontSize = aFontSizes[Index]; + FT_Set_Pixel_Sizes(pFont->m_FtFace, 0, pSizeData->m_FontSize); + + if(pSizeData->m_FontSize >= 18) + OutlineThickness = 2; + + { + unsigned GlyphIndex; + int MaxH = 0; + int MaxW = 0; + + int Charcode = FT_Get_First_Char(pFont->m_FtFace, &GlyphIndex); + while(GlyphIndex != 0) + { + // do stuff + FT_Load_Glyph(pFont->m_FtFace, GlyphIndex, FT_LOAD_DEFAULT); + + if(pFont->m_FtFace->glyph->metrics.width > MaxW) MaxW = pFont->m_FtFace->glyph->metrics.width; // ignore_convention + if(pFont->m_FtFace->glyph->metrics.height > MaxH) MaxH = pFont->m_FtFace->glyph->metrics.height; // ignore_convention + Charcode = FT_Get_Next_Char(pFont->m_FtFace, Charcode, &GlyphIndex); + } + + MaxW = (MaxW>>6)+2+OutlineThickness*2; + MaxH = (MaxH>>6)+2+OutlineThickness*2; + + for(pSizeData->m_CharMaxWidth = 1; pSizeData->m_CharMaxWidth < MaxW; pSizeData->m_CharMaxWidth <<= 1); + for(pSizeData->m_CharMaxHeight = 1; pSizeData->m_CharMaxHeight < MaxH; pSizeData->m_CharMaxHeight <<= 1); + } + + //dbg_msg("pFont", "init size %d, texture size %d %d", pFont->sizes[index].font_size, w, h); + //FT_New_Face(m_FTLibrary, "data/fonts/vera.ttf", 0, &pFont->ft_face); + InitTexture(pSizeData, pSizeData->m_CharMaxWidth, pSizeData->m_CharMaxHeight, 8, 8); + } + + CFontSizeData *GetSize(CFont *pFont, int Pixelsize) + { + int Index = GetFontSizeIndex(Pixelsize); + if(pFont->m_aSizes[Index].m_FontSize != aFontSizes[Index]) + InitIndex(pFont, Index); + return &pFont->m_aSizes[Index]; + } + + + void UploadGlyph(CFontSizeData *pSizeData, int Texnum, int SlotId, int Chr, const void *pData) + { + int x = (SlotId%pSizeData->m_NumXChars) * (pSizeData->m_TextureWidth/pSizeData->m_NumXChars); + int y = (SlotId/pSizeData->m_NumXChars) * (pSizeData->m_TextureHeight/pSizeData->m_NumYChars); + + glBindTexture(GL_TEXTURE_2D, pSizeData->m_aTextures[Texnum]); + glTexSubImage2D(GL_TEXTURE_2D, 0, x, y, + pSizeData->m_TextureWidth/pSizeData->m_NumXChars, + pSizeData->m_TextureHeight/pSizeData->m_NumYChars, + m_FontTextureFormat, GL_UNSIGNED_BYTE, pData); + } + + // 8k of data used for rendering glyphs + unsigned char ms_aGlyphData[(4096/64) * (4096/64)]; + unsigned char ms_aGlyphDataOutlined[(4096/64) * (4096/64)]; + + int GetSlot(CFontSizeData *pSizeData) + { + int CharCount = pSizeData->m_NumXChars*pSizeData->m_NumYChars; + if(pSizeData->m_CurrentCharacter < CharCount) + { + int i = pSizeData->m_CurrentCharacter; + pSizeData->m_CurrentCharacter++; + return i; + } + + // kick out the oldest + // TODO: remove this linear search + { + int Oldest = 0; + for(int i = 1; i < CharCount; i++) + { + if(pSizeData->m_aCharacters[i].m_TouchTime < pSizeData->m_aCharacters[Oldest].m_TouchTime) + Oldest = i; + } + + if(time_get()-pSizeData->m_aCharacters[Oldest].m_TouchTime < time_freq()) + { + IncreaseTextureSize(pSizeData); + return GetSlot(pSizeData); + } + + return Oldest; + } + } + + int RenderGlyph(CFont *pFont, CFontSizeData *pSizeData, int Chr) + { + FT_Bitmap *pBitmap; + int SlotId = 0; + int SlotW = pSizeData->m_TextureWidth / pSizeData->m_NumXChars; + int SlotH = pSizeData->m_TextureHeight / pSizeData->m_NumYChars; + int SlotSize = SlotW*SlotH; + int OutlineThickness = 1; + int x = 1; + int y = 1; + int px, py; + + FT_Set_Pixel_Sizes(pFont->m_FtFace, 0, pSizeData->m_FontSize); + + if(FT_Load_Char(pFont->m_FtFace, Chr, FT_LOAD_RENDER|FT_LOAD_NO_BITMAP)) + { + dbg_msg("pFont", "error loading glyph %d", Chr); + return -1; + } + + pBitmap = &pFont->m_FtFace->glyph->bitmap; // ignore_convention + + // fetch slot + SlotId = GetSlot(pSizeData); + if(SlotId < 0) + return -1; + + // adjust spacing + if(pSizeData->m_FontSize >= 18) + OutlineThickness = 2; + x += OutlineThickness; + y += OutlineThickness; + + // prepare glyph data + mem_zero(ms_aGlyphData, SlotSize); + + if(pBitmap->pixel_mode == FT_PIXEL_MODE_GRAY) // ignore_convention + { + for(py = 0; py < pBitmap->rows; py++) // ignore_convention + for(px = 0; px < pBitmap->width; px++) // ignore_convention + ms_aGlyphData[(py+y)*SlotW+px+x] = pBitmap->buffer[py*pBitmap->pitch+px]; // ignore_convention + } + else if(pBitmap->pixel_mode == FT_PIXEL_MODE_MONO) // ignore_convention + { + for(py = 0; py < pBitmap->rows; py++) // ignore_convention + for(px = 0; px < pBitmap->width; px++) // ignore_convention + { + if(pBitmap->buffer[py*pBitmap->pitch+px/8]&(1<<(7-(px%8)))) // ignore_convention + ms_aGlyphData[(py+y)*SlotW+px+x] = 255; + } + } + + if(0) for(py = 0; py < SlotW; py++) + for(px = 0; px < SlotH; px++) + ms_aGlyphData[py*SlotW+px] = 255; + + // upload the glyph + UploadGlyph(pSizeData, 0, SlotId, Chr, ms_aGlyphData); + + if(OutlineThickness == 1) + { + Grow(ms_aGlyphData, ms_aGlyphDataOutlined, SlotW, SlotH); + UploadGlyph(pSizeData, 1, SlotId, Chr, ms_aGlyphDataOutlined); + } + else + { + Grow(ms_aGlyphData, ms_aGlyphDataOutlined, SlotW, SlotH); + Grow(ms_aGlyphDataOutlined, ms_aGlyphData, SlotW, SlotH); + UploadGlyph(pSizeData, 1, SlotId, Chr, ms_aGlyphData); + } + + // set char info + { + CFontChar *pFontchr = &pSizeData->m_aCharacters[SlotId]; + float Scale = 1.0f/pSizeData->m_FontSize; + float Uscale = 1.0f/pSizeData->m_TextureWidth; + float Vscale = 1.0f/pSizeData->m_TextureHeight; + int Height = pBitmap->rows + OutlineThickness*2 + 2; // ignore_convention + int Width = pBitmap->width + OutlineThickness*2 + 2; // ignore_convention + + pFontchr->m_Id = Chr; + pFontchr->m_Height = Height * Scale; + pFontchr->m_Width = Width * Scale; + pFontchr->m_OffsetX = (pFont->m_FtFace->glyph->bitmap_left-1) * Scale; // ignore_convention + pFontchr->m_OffsetY = (pSizeData->m_FontSize - pFont->m_FtFace->glyph->bitmap_top) * Scale; // ignore_convention + pFontchr->m_AdvanceX = (pFont->m_FtFace->glyph->advance.x>>6) * Scale; // ignore_convention + + pFontchr->m_aUvs[0] = (SlotId%pSizeData->m_NumXChars) / (float)(pSizeData->m_NumXChars); + pFontchr->m_aUvs[1] = (SlotId/pSizeData->m_NumXChars) / (float)(pSizeData->m_NumYChars); + pFontchr->m_aUvs[2] = pFontchr->m_aUvs[0] + Width*Uscale; + pFontchr->m_aUvs[3] = pFontchr->m_aUvs[1] + Height*Vscale; + } + + return SlotId; + } + + CFontChar *GetChar(CFont *pFont, CFontSizeData *pSizeData, int Chr) + { + CFontChar *pFontchr = NULL; + + // search for the character + // TODO: remove this linear search + int i; + for(i = 0; i < pSizeData->m_CurrentCharacter; i++) + { + if(pSizeData->m_aCharacters[i].m_Id == Chr) + { + pFontchr = &pSizeData->m_aCharacters[i]; + break; + } + } + + // check if we need to render the character + if(!pFontchr) + { + int Index = RenderGlyph(pFont, pSizeData, Chr); + if(Index >= 0) + pFontchr = &pSizeData->m_aCharacters[Index]; + } + + // touch the character + // TODO: don't call time_get here + if(pFontchr) + pFontchr->m_TouchTime = time_get(); + + return pFontchr; + } + + // must only be called from the rendering function as the pFont must be set to the correct size + void RenderSetup(CFont *pFont, int size) + { + FT_Set_Pixel_Sizes(pFont->m_FtFace, 0, size); + } + + float Kerning(CFont *pFont, int Left, int Right) + { + FT_Vector Kerning = {0,0}; + FT_Get_Kerning(pFont->m_FtFace, Left, Right, FT_KERNING_DEFAULT, &Kerning); + return (Kerning.x>>6); + } + + +public: + CTextRender() + { + m_pGraphics = 0; + + m_TextR = 1; + m_TextG = 1; + m_TextB = 1; + m_TextA = 1; + + m_pDefaultFont = 0; + + // GL_LUMINANCE can be good for debugging + m_FontTextureFormat = GL_ALPHA; + } + + virtual void Init() + { + m_pGraphics = Kernel()->RequestInterface<IGraphics>(); + FT_Init_FreeType(&m_FTLibrary); + } + + + virtual CFont *LoadFont(const char *pFilename) + { + CFont *pFont = (CFont *)mem_alloc(sizeof(CFont), 1); + + mem_zero(pFont, sizeof(*pFont)); + str_copy(pFont->m_aFilename, pFilename, sizeof(pFont->m_aFilename)); + + if(FT_New_Face(m_FTLibrary, pFont->m_aFilename, 0, &pFont->m_FtFace)) + { + mem_free(pFont); + return NULL; + } + + for(unsigned i = 0; i < NUM_FONT_SIZES; i++) + pFont->m_aSizes[i].m_FontSize = -1; + + dbg_msg("textrender", "loaded pFont from '%s'", pFilename); + return pFont; + }; + + virtual void DestroyFont(CFont *pFont) + { + mem_free(pFont); + } + + virtual void SetDefaultFont(struct CFont *pFont) + { + dbg_msg("textrender", "default pFont set %p", pFont); + m_pDefaultFont = pFont; + } + + + virtual void SetCursor(CTextCursor *pCursor, float x, float y, float FontSize, int Flags) + { + mem_zero(pCursor, sizeof(*pCursor)); + pCursor->m_FontSize = FontSize; + pCursor->m_StartX = x; + pCursor->m_StartY = y; + pCursor->m_X = x; + pCursor->m_Y = y; + pCursor->m_LineCount = 1; + pCursor->m_LineWidth = -1; + pCursor->m_Flags = Flags; + pCursor->m_CharCount = 0; + } + + + virtual void Text(void *pFontSetV, float x, float y, float Size, const char *pText, int MaxWidth) + { + CTextCursor Cursor; + SetCursor(&Cursor, x, y, Size, TEXTFLAG_RENDER); + Cursor.m_LineWidth = MaxWidth; + TextEx(&Cursor, pText, -1); + } + + virtual float TextWidth(void *pFontSetV, float Size, const char *pText, int Length) + { + CTextCursor Cursor; + SetCursor(&Cursor, 0, 0, Size, 0); + TextEx(&Cursor, pText, Length); + return Cursor.m_X; + } + + virtual float TextLineCount(void *pFontSetV, float Size, const char *pText, int LineWidth) + { + CTextCursor Cursor; + SetCursor(&Cursor, 0, 0, Size, 0); + Cursor.m_LineWidth = LineWidth; + TextEx(&Cursor, pText, -1); + return Cursor.m_LineCount; + } + + virtual void TextColor(float r, float g, float b, float a) + { + m_TextR = r; + m_TextG = g; + m_TextB = b; + m_TextA = a; + } + + virtual void TextEx(CTextCursor *pCursor, const char *pText, int Length) + { + CFont *pFont = pCursor->m_pFont; + CFontSizeData *pSizeData = NULL; + + //dbg_msg("textrender", "rendering text '%s'", text); + + float ScreenX0, ScreenY0, ScreenX1, ScreenY1; + float FakeToScreenX, FakeToScreenY; + int ActualX, ActualY; + + int ActualSize; + int i; + int GotNewLine = 0; + float DrawX, DrawY; + float CursorX, CursorY; + const char *pEnd; + + float Size = pCursor->m_FontSize; + + // to correct coords, convert to screen coords, round, and convert back + Graphics()->GetScreen(&ScreenX0, &ScreenY0, &ScreenX1, &ScreenY1); + + FakeToScreenX = (Graphics()->ScreenWidth()/(ScreenX1-ScreenX0)); + FakeToScreenY = (Graphics()->ScreenHeight()/(ScreenY1-ScreenY0)); + ActualX = pCursor->m_X * FakeToScreenX; + ActualY = pCursor->m_Y * FakeToScreenY; + + CursorX = ActualX / FakeToScreenX; + CursorY = ActualY / FakeToScreenY; + + // same with size + ActualSize = Size * FakeToScreenY; + Size = ActualSize / FakeToScreenY; + + // fetch pFont data + if(!pFont) + pFont = m_pDefaultFont; + + if(!pFont) + return; + + pSizeData = GetSize(pFont, ActualSize); + RenderSetup(pFont, ActualSize); + + // set length + if(Length < 0) + Length = str_length(pText); + + pEnd = pText + Length; + + // if we don't want to render, we can just skip the first outline pass + i = 1; + if(pCursor->m_Flags&TEXTFLAG_RENDER) + i = 0; + + for(;i < 2; i++) + { + const char *pCurrent = (char *)pText; + const char *pEnd = pCurrent+Length; + DrawX = CursorX; + DrawY = CursorY; + + if(pCursor->m_Flags&TEXTFLAG_RENDER) + { + // TODO: Make this better + glEnable(GL_TEXTURE_2D); + if (i == 0) + glBindTexture(GL_TEXTURE_2D, pSizeData->m_aTextures[1]); + else + glBindTexture(GL_TEXTURE_2D, pSizeData->m_aTextures[0]); + + Graphics()->QuadsBegin(); + if (i == 0) + Graphics()->SetColor(0.0f, 0.0f, 0.0f, 0.3f*m_TextA); + else + Graphics()->SetColor(m_TextR, m_TextG, m_TextB, m_TextA); + } + + while(pCurrent < pEnd) + { + int NewLine = 0; + const char *pBatchEnd = pEnd; + if(pCursor->m_LineWidth > 0 && !(pCursor->m_Flags&TEXTFLAG_STOP_AT_END)) + { + int Wlen = min(WordLength((char *)pCurrent), (int)(pEnd-pCurrent)); + CTextCursor Compare = *pCursor; + Compare.m_X = DrawX; + Compare.m_Y = DrawY; + Compare.m_Flags &= ~TEXTFLAG_RENDER; + Compare.m_LineWidth = -1; + TextEx(&Compare, pText, Wlen); + + if(Compare.m_X-DrawX > pCursor->m_LineWidth) + { + // word can't be fitted in one line, cut it + CTextCursor Cutter = *pCursor; + Cutter.m_CharCount = 0; + Cutter.m_X = DrawX; + Cutter.m_Y = DrawY; + Cutter.m_Flags &= ~TEXTFLAG_RENDER; + Cutter.m_Flags |= TEXTFLAG_STOP_AT_END; + + TextEx(&Cutter, (const char *)pCurrent, Wlen); + Wlen = Cutter.m_CharCount; + NewLine = 1; + + if(Wlen <= 3) // if we can't place 3 chars of the word on this line, take the next + Wlen = 0; + } + else if(Compare.m_X-pCursor->m_StartX > pCursor->m_LineWidth) + { + NewLine = 1; + Wlen = 0; + } + + pBatchEnd = pCurrent + Wlen; + } + + while(pCurrent < pBatchEnd) + { + const char *pTmp; + float Advance = 0; + int Character = 0; + int Nextcharacter = 0; + CFontChar *pChr; + + // TODO: UTF-8 decode + Character = str_utf8_decode(&pCurrent); + pTmp = pCurrent; + Nextcharacter = str_utf8_decode(&pTmp); + + if(Character == '\n') + { + DrawX = pCursor->m_StartX; + DrawY += Size; + DrawX = (int)(DrawX * FakeToScreenX) / FakeToScreenX; // realign + DrawY = (int)(DrawY * FakeToScreenY) / FakeToScreenY; + ++pCursor->m_LineCount; + continue; + } + + pChr = GetChar(pFont, pSizeData, Character); + + if(pChr) + { + if(pCursor->m_Flags&TEXTFLAG_RENDER) + { + Graphics()->QuadsSetSubset(pChr->m_aUvs[0], pChr->m_aUvs[1], pChr->m_aUvs[2], pChr->m_aUvs[3]); + IGraphics::CQuadItem QuadItem(DrawX+pChr->m_OffsetX*Size, DrawY+pChr->m_OffsetY*Size, pChr->m_Width*Size, pChr->m_Height*Size); + Graphics()->QuadsDrawTL(&QuadItem, 1); + } + + Advance = pChr->m_AdvanceX + Kerning(pFont, Character, Nextcharacter)/Size; + } + + if(pCursor->m_Flags&TEXTFLAG_STOP_AT_END && DrawX+Advance*Size-pCursor->m_StartX > pCursor->m_LineWidth) + { + // we hit the end of the line, no more to render or count + pCurrent = pEnd; + break; + } + + DrawX += Advance*Size; + pCursor->m_CharCount++; + } + + if(NewLine) + { + DrawX = pCursor->m_StartX; + DrawY += Size; + GotNewLine = 1; + DrawX = (int)(DrawX * FakeToScreenX) / FakeToScreenX; // realign + DrawY = (int)(DrawY * FakeToScreenY) / FakeToScreenY; + ++pCursor->m_LineCount; + } + } + + if(pCursor->m_Flags&TEXTFLAG_RENDER) + Graphics()->QuadsEnd(); + } + + pCursor->m_X = DrawX; + + if(GotNewLine) + pCursor->m_Y = DrawY; + } + +}; + +IEngineTextRender *CreateEngineTextRender() { return new CTextRender; } diff --git a/src/engine/config.h b/src/engine/config.h new file mode 100644 index 00000000..967d3593 --- /dev/null +++ b/src/engine/config.h @@ -0,0 +1,23 @@ +#ifndef ENGINE_CONFIG_H +#define ENGINE_CONFIG_H + +#include "kernel.h" + +class IConfig : public IInterface +{ + MACRO_INTERFACE("config", 0) +public: + typedef void (*SAVECALLBACKFUNC)(IConfig *pConfig, void *pUserData); + + virtual void Init() = 0; + virtual void Reset() = 0; + virtual void Save() = 0; + + virtual void RegisterCallback(SAVECALLBACKFUNC pfnFunc, void *pUserData) = 0; + + virtual void WriteLine(const char *pLine) = 0; +}; + +extern IConfig *CreateConfig(); + +#endif diff --git a/src/engine/console.h b/src/engine/console.h new file mode 100644 index 00000000..74d789e9 --- /dev/null +++ b/src/engine/console.h @@ -0,0 +1,58 @@ +#ifndef ENGINE_CONSOLE_H +#define ENGINE_CONSOLE_H + +#include "kernel.h" + +class IConsole : public IInterface +{ + MACRO_INTERFACE("console", 0) +public: + + // TODO: rework this interface to reduce the amount of virtual calls + class IResult + { + protected: + unsigned m_NumArgs; + public: + IResult() { m_NumArgs = 0; } + virtual ~IResult() {} + + virtual int GetInteger(unsigned Index) = 0; + virtual float GetFloat(unsigned Index) = 0; + virtual const char *GetString(unsigned Index) = 0; + + int NumArguments() const { return m_NumArgs; } + }; + + class CCommandInfo + { + public: + const char *m_pName; + const char *m_pHelp; + const char *m_pParams; + }; + + typedef void (*FPrintCallback)(const char *pStr, void *pUser); + typedef void (*FPossibleCallback)(const char *pCmd, void *pUser); + typedef void (*FCommandCallback)(IResult *pResult, void *pUserData); + typedef void (*FChainCommandCallback)(IResult *pResult, void *pUserData, FCommandCallback pfnCallback, void *pCallbackUserData); + + virtual CCommandInfo *GetCommandInfo(const char *pName) = 0; + virtual void PossibleCommands(const char *pStr, int FlagMask, FPossibleCallback pfnCallback, void *pUser) = 0; + virtual void ParseArguments(int NumArgs, const char **ppArguments) = 0; + + virtual void Register(const char *pName, const char *pParams, + int Flags, FCommandCallback pfnFunc, void *pUser, const char *pHelp) = 0; + virtual void Chain(const char *pName, FChainCommandCallback pfnChainFunc, void *pUser) = 0; + + virtual void ExecuteLine(const char *Sptr) = 0; + virtual void ExecuteLineStroked(int Stroke, const char *pStr) = 0; + virtual void ExecuteFile(const char *pFilename) = 0; + + virtual void RegisterPrintCallback(FPrintCallback pfnPrintCallback, void *pUserData) = 0; + virtual void Print(const char *pStr) = 0; +}; + +extern IConsole *CreateConsole(); + +#endif // FILE_ENGINE_CONSOLE_H diff --git a/src/engine/demo.h b/src/engine/demo.h new file mode 100644 index 00000000..7b353e7c --- /dev/null +++ b/src/engine/demo.h @@ -0,0 +1,29 @@ +#ifndef ENGINE_DEMO_H +#define ENGINE_DEMO_H + +#include "kernel.h" + +class IDemoPlayer : public IInterface +{ + MACRO_INTERFACE("demoplayer", 0) +public: + class CInfo + { + public: + bool m_Paused; + float m_Speed; + + int m_FirstTick; + int m_CurrentTick; + int m_LastTick; + }; + + ~IDemoPlayer() {} + virtual void SetSpeed(float Speed) = 0; + virtual int SetPos(float Precent) = 0; + virtual void Pause() = 0; + virtual void Unpause() = 0; + virtual const CInfo *BaseInfo() const = 0; +}; + +#endif diff --git a/src/engine/e_client_interface.h b/src/engine/e_client_interface.h deleted file mode 100644 index 079eabca..00000000 --- a/src/engine/e_client_interface.h +++ /dev/null @@ -1,16 +0,0 @@ -/* copyright (c) 2007 magnus auvinen, see licence.txt for more info */ -#ifndef ENGINE_CLIENT_INTERFACE_H -#define ENGINE_CLIENT_INTERFACE_H - -#include "e_if_other.h" -#include "e_if_client.h" -#include "e_if_snd.h" -#include "e_if_gfx.h" -#include "e_if_inp.h" -#include "e_if_msg.h" -#include "e_if_modc.h" - -#include "e_console.h" -#include "e_config.h" - -#endif diff --git a/src/engine/e_common_interface.h b/src/engine/e_common_interface.h deleted file mode 100644 index 9c95b48b..00000000 --- a/src/engine/e_common_interface.h +++ /dev/null @@ -1,8 +0,0 @@ -/* copyright (c) 2007 magnus auvinen, see licence.txt for more info */ -#ifndef ENGINE_COMMON_INTERFACE_H -#define ENGINE_COMMON_INTERFACE_H - -#include "e_if_other.h" -#include "e_if_msg.h" - -#endif diff --git a/src/engine/e_compression.cpp b/src/engine/e_compression.cpp deleted file mode 100644 index f4d6e0c0..00000000 --- a/src/engine/e_compression.cpp +++ /dev/null @@ -1,86 +0,0 @@ -/* copyright (c) 2007 magnus auvinen, see licence.txt for more info */ -#include <base/system.h> - -/* Format: ESDDDDDD EDDDDDDD EDD... Extended, Data, Sign */ -unsigned char *vint_pack(unsigned char *dst, int i) -{ - *dst = (i>>25)&0x40; /* set sign bit if i<0 */ - i = i^(i>>31); /* if(i<0) i = ~i */ - - *dst |= i&0x3F; /* pack 6bit into dst */ - i >>= 6; /* discard 6 bits */ - if(i) - { - *dst |= 0x80; /* set extend bit */ - while(1) - { - dst++; - *dst = i&(0x7F); /* pack 7bit */ - i >>= 7; /* discard 7 bits */ - *dst |= (i!=0)<<7; /* set extend bit (may branch) */ - if(!i) - break; - } - } - - dst++; - return dst; -} - -const unsigned char *vint_unpack(const unsigned char *src, int *i) -{ - int sign = (*src>>6)&1; - *i = *src&0x3F; - - do - { - if(!(*src&0x80)) break; - src++; - *i |= (*src&(0x7F))<<(6); - - if(!(*src&0x80)) break; - src++; - *i |= (*src&(0x7F))<<(6+7); - - if(!(*src&0x80)) break; - src++; - *i |= (*src&(0x7F))<<(6+7+7); - - if(!(*src&0x80)) break; - src++; - *i |= (*src&(0x7F))<<(6+7+7+7); - } while(0); - - src++; - *i ^= -sign; /* if(sign) *i = ~(*i) */ - return src; -} - - -long intpack_decompress(const void *src_, int size, void *dst_) -{ - const unsigned char *src = (unsigned char *)src_; - const unsigned char *end = src + size; - int *dst = (int *)dst_; - while(src < end) - { - src = vint_unpack(src, dst); - dst++; - } - return (long)((unsigned char *)dst-(unsigned char *)dst_); -} - -long intpack_compress(const void *src_, int size, void *dst_) -{ - int *src = (int *)src_; - unsigned char *dst = (unsigned char *)dst_; - size /= 4; - while(size) - { - dst = vint_pack(dst, *src); - size--; - src++; - } - return (long)(dst-(unsigned char *)dst_); -} - diff --git a/src/engine/e_compression.h b/src/engine/e_compression.h deleted file mode 100644 index be5bf78f..00000000 --- a/src/engine/e_compression.h +++ /dev/null @@ -1,7 +0,0 @@ -/* copyright (c) 2007 magnus auvinen, see licence.txt for more info */ - -/* variable int packing */ -unsigned char *vint_pack(unsigned char *dst, int i); -const unsigned char *vint_unpack(const unsigned char *src, int *inout); -long intpack_compress(const void *src, int size, void *dst); -long intpack_decompress(const void *src, int size, void *dst); diff --git a/src/engine/e_config.cpp b/src/engine/e_config.cpp deleted file mode 100644 index 67a4c81a..00000000 --- a/src/engine/e_config.cpp +++ /dev/null @@ -1,49 +0,0 @@ -/* copyright (c) 2007 magnus auvinen, see licence.txt for more info */ - -#include <string.h> -#include <stdio.h> -#include <stdlib.h> - -#include <base/system.h> -#include "e_if_other.h" -#include "e_config.h" -#include "e_linereader.h" -#include "e_engine.h" - -CONFIGURATION config; - -void config_reset() -{ - #define MACRO_CONFIG_INT(name,def,min,max,flags,desc) config.name = def; - #define MACRO_CONFIG_STR(name,len,def,flags,desc) str_copy(config.name, def, len); - - #include "e_config_variables.h" - - #undef MACRO_CONFIG_INT - #undef MACRO_CONFIG_STR -} - -void config_save() -{ - char linebuf[1024*2]; - - #define MACRO_CONFIG_INT(name,def,min,max,flags,desc) if((flags)&CFGFLAG_SAVE){ str_format(linebuf, sizeof(linebuf), "%s %i", #name, config.name); engine_config_write_line(linebuf); } - #define MACRO_CONFIG_STR(name,len,def,flags,desc) if((flags)&CFGFLAG_SAVE){ str_format(linebuf, sizeof(linebuf), "%s %s", #name, config.name); engine_config_write_line(linebuf); } - - #include "e_config_variables.h" - - #undef MACRO_CONFIG_INT - #undef MACRO_CONFIG_STR -} - -#define MACRO_CONFIG_INT(name,def,min,max,flags,desc) int config_get_ ## name (CONFIGURATION *c) { return c->name; } -#define MACRO_CONFIG_STR(name,len,def,flags,desc) const char *config_get_ ## name (CONFIGURATION *c) { return c->name; } -#include "e_config_variables.h" -#undef MACRO_CONFIG_INT -#undef MACRO_CONFIG_STR - -#define MACRO_CONFIG_INT(name,def,min,max,flags,desc) void config_set_ ## name (CONFIGURATION *c, int val) { if(min != max) { if (val < min) val = min; if (max != 0 && val > max) val = max; } c->name = val; } -#define MACRO_CONFIG_STR(name,len,def,flags,desc) void config_set_ ## name (CONFIGURATION *c, const char *str) { str_copy(c->name, str, len-1); c->name[sizeof(c->name)-1] = 0; } -#include "e_config_variables.h" -#undef MACRO_CONFIG_INT -#undef MACRO_CONFIG_STR diff --git a/src/engine/e_config.h b/src/engine/e_config.h deleted file mode 100644 index 6ca7a8ee..00000000 --- a/src/engine/e_config.h +++ /dev/null @@ -1,44 +0,0 @@ -/* copyright (c) 2007 magnus auvinen, see licence.txt for more info */ -#ifndef _CONFIG_H -#define _CONFIG_H - -typedef struct -{ - #define MACRO_CONFIG_INT(name,def,min,max,save,desc) int name; - #define MACRO_CONFIG_STR(name,len,def,save,desc) char name[len]; /* Flawfinder: ignore */ - #include "e_config_variables.h" - #undef MACRO_CONFIG_INT - #undef MACRO_CONFIG_STR -} CONFIGURATION; - -extern CONFIGURATION config; - -void config_init(); -void config_reset(); -void config_save(); - -enum -{ - CFGFLAG_SAVE=1, - CFGFLAG_CLIENT=2, - CFGFLAG_SERVER=4 -}; - -typedef int (*CONFIG_INT_GETTER)(CONFIGURATION *c); -typedef const char *(*CONFIG_STR_GETTER)(CONFIGURATION *c); -typedef void (*CONFIG_INT_SETTER)(CONFIGURATION *c, int val); -typedef void (*CONFIG_STR_SETTER)(CONFIGURATION *c, const char *str); - -#define MACRO_CONFIG_INT(name,def,min,max,flags,desc) int config_get_ ## name (CONFIGURATION *c); -#define MACRO_CONFIG_STR(name,len,def,flags,desc) const char *config_get_ ## name (CONFIGURATION *c); -#include "e_config_variables.h" -#undef MACRO_CONFIG_INT -#undef MACRO_CONFIG_STR - -#define MACRO_CONFIG_INT(name,def,min,max,flags,desc) void config_set_ ## name (CONFIGURATION *c, int val); -#define MACRO_CONFIG_STR(name,len,def,flags,desc) void config_set_ ## name (CONFIGURATION *c, const char *str); -#include "e_config_variables.h" -#undef MACRO_CONFIG_INT -#undef MACRO_CONFIG_STR - -#endif diff --git a/src/engine/e_config_variables.h b/src/engine/e_config_variables.h deleted file mode 100644 index aa3da3f6..00000000 --- a/src/engine/e_config_variables.h +++ /dev/null @@ -1,77 +0,0 @@ -/* copyright (c) 2007 magnus auvinen, see licence.txt for more info */ - -/* TODO: remove this */ -#include "../game/variables.hpp" - - -MACRO_CONFIG_STR(player_name, 32, "nameless tee", CFGFLAG_SAVE|CFGFLAG_CLIENT, "Name of the player") -MACRO_CONFIG_STR(clan_name, 32, "", CFGFLAG_SAVE|CFGFLAG_CLIENT, "(not used)") -MACRO_CONFIG_STR(password, 32, "", CFGFLAG_CLIENT, "Password to the server") -MACRO_CONFIG_STR(logfile, 128, "", CFGFLAG_SAVE|CFGFLAG_CLIENT, "Filename to log all output to") - -MACRO_CONFIG_INT(cl_cpu_throttle, 0, 0, 1, CFGFLAG_SAVE|CFGFLAG_CLIENT, "") -MACRO_CONFIG_INT(cl_editor, 0, 0, 1, CFGFLAG_CLIENT, "") - -MACRO_CONFIG_INT(cl_eventthread, 0, 0, 1, CFGFLAG_CLIENT, "Enables the usage of a thread to pump the events") - -MACRO_CONFIG_INT(inp_grab, 0, 0, 1, CFGFLAG_CLIENT, "Use forceful input grabbing method") - -MACRO_CONFIG_STR(b_filter_string, 64, "", CFGFLAG_SAVE|CFGFLAG_CLIENT, "Server browser filtering string") - -MACRO_CONFIG_INT(b_filter_full, 0, 0, 1, CFGFLAG_SAVE|CFGFLAG_CLIENT, "Filter out full server in browser") -MACRO_CONFIG_INT(b_filter_empty, 0, 0, 1, CFGFLAG_SAVE|CFGFLAG_CLIENT, "Filter out empty server in browser") -MACRO_CONFIG_INT(b_filter_pw, 0, 0, 1, CFGFLAG_SAVE|CFGFLAG_CLIENT, "Filter out password protected servers in browser") -MACRO_CONFIG_INT(b_filter_ping, 999, 0, 999, CFGFLAG_SAVE|CFGFLAG_CLIENT, "Ping to filter by in the server browser") -MACRO_CONFIG_STR(b_filter_gametype, 128, "", CFGFLAG_SAVE|CFGFLAG_CLIENT, "Game types to filter") -MACRO_CONFIG_INT(b_filter_pure, 1, 0, 1, CFGFLAG_SAVE|CFGFLAG_CLIENT, "Filter out non-standard servers in browser") -MACRO_CONFIG_INT(b_filter_pure_map, 1, 0, 1, CFGFLAG_SAVE|CFGFLAG_CLIENT, "Filter out non-standard maps in browser") -MACRO_CONFIG_INT(b_filter_compatversion, 1, 0, 1, CFGFLAG_SAVE|CFGFLAG_CLIENT, "Filter out non-compatible servers in browser") - -MACRO_CONFIG_INT(b_sort, 0, 0, 256, CFGFLAG_SAVE|CFGFLAG_CLIENT, "") -MACRO_CONFIG_INT(b_sort_order, 0, 0, 1, CFGFLAG_SAVE|CFGFLAG_CLIENT, "") -MACRO_CONFIG_INT(b_max_requests, 10, 0, 1000, CFGFLAG_SAVE|CFGFLAG_CLIENT, "Number of requests to use when refreshing server browser") - -MACRO_CONFIG_INT(snd_buffer_size, 512, 0, 0, CFGFLAG_SAVE|CFGFLAG_CLIENT, "Sound buffer size") -MACRO_CONFIG_INT(snd_rate, 48000, 0, 0, CFGFLAG_SAVE|CFGFLAG_CLIENT, "Sound mixing rate") -MACRO_CONFIG_INT(snd_enable, 1, 0, 1, CFGFLAG_SAVE|CFGFLAG_CLIENT, "Sound enable") -MACRO_CONFIG_INT(snd_volume, 100, 0, 100, CFGFLAG_SAVE|CFGFLAG_CLIENT, "Sound volume") -MACRO_CONFIG_INT(snd_device, -1, 0, 0, CFGFLAG_SAVE|CFGFLAG_CLIENT, "(deprecated) Sound device to use") - -MACRO_CONFIG_INT(snd_nonactive_mute, 0, 0, 1, CFGFLAG_SAVE|CFGFLAG_CLIENT, "") - -MACRO_CONFIG_INT(gfx_screen_width, 800, 0, 0, CFGFLAG_SAVE|CFGFLAG_CLIENT, "Screen resolution width") -MACRO_CONFIG_INT(gfx_screen_height, 600, 0, 0, CFGFLAG_SAVE|CFGFLAG_CLIENT, "Screen resolution height") -MACRO_CONFIG_INT(gfx_fullscreen, 1, 0, 1, CFGFLAG_SAVE|CFGFLAG_CLIENT, "Fullscreen") -MACRO_CONFIG_INT(gfx_alphabits, 0, 0, 0, CFGFLAG_SAVE|CFGFLAG_CLIENT, "Alpha bits for framebuffer (fullscreen only)") -MACRO_CONFIG_INT(gfx_color_depth, 24, 16, 24, CFGFLAG_SAVE|CFGFLAG_CLIENT, "Colors bits for framebuffer (fullscreen only)") -MACRO_CONFIG_INT(gfx_clear, 0, 0, 1, CFGFLAG_SAVE|CFGFLAG_CLIENT, "Clear screen before rendering") -MACRO_CONFIG_INT(gfx_vsync, 1, 0, 1, CFGFLAG_SAVE|CFGFLAG_CLIENT, "Vertical sync") -MACRO_CONFIG_INT(gfx_display_all_modes, 0, 0, 1, CFGFLAG_SAVE|CFGFLAG_CLIENT, "") -MACRO_CONFIG_INT(gfx_texture_compression, 0, 0, 1, CFGFLAG_SAVE|CFGFLAG_CLIENT, "Use texture compression") -MACRO_CONFIG_INT(gfx_high_detail, 1, 0, 1, CFGFLAG_SAVE|CFGFLAG_CLIENT, "High detail") -MACRO_CONFIG_INT(gfx_texture_quality, 1, 0, 1, CFGFLAG_SAVE|CFGFLAG_CLIENT, "") -MACRO_CONFIG_INT(gfx_fsaa_samples, 0, 0, 16, CFGFLAG_SAVE|CFGFLAG_CLIENT, "FSAA Samples") -MACRO_CONFIG_INT(gfx_refresh_rate, 0, 0, 0, CFGFLAG_SAVE|CFGFLAG_CLIENT, "Screen refresh rate") -MACRO_CONFIG_INT(gfx_finish, 1, 0, 1, CFGFLAG_SAVE|CFGFLAG_CLIENT, "") - -MACRO_CONFIG_INT(inp_mousesens, 100, 5, 100000, CFGFLAG_SAVE|CFGFLAG_CLIENT, "Mouse sensitivity") - -MACRO_CONFIG_STR(sv_name, 128, "unnamed server", CFGFLAG_SERVER, "Server name") -MACRO_CONFIG_STR(sv_bindaddr, 128, "", CFGFLAG_SERVER, "Address to bind the server to") -MACRO_CONFIG_INT(sv_port, 8303, 0, 0, CFGFLAG_SERVER, "Port to use for the server") -MACRO_CONFIG_INT(sv_external_port, 0, 0, 0, CFGFLAG_SERVER, "External port to report to the master servers") -MACRO_CONFIG_STR(sv_map, 128, "dm1", CFGFLAG_SERVER, "Map to use on the server") -MACRO_CONFIG_INT(sv_max_clients, 8, 1, MAX_CLIENTS, CFGFLAG_SERVER, "Maximum number of clients that are allowed on a server") -MACRO_CONFIG_INT(sv_high_bandwidth, 0, 0, 1, CFGFLAG_SERVER, "Use high bandwidth mode. Doubles the bandwidth required for the server. LAN use only") -MACRO_CONFIG_INT(sv_register, 1, 0, 1, CFGFLAG_SERVER, "Register server with master server for public listing") -MACRO_CONFIG_STR(sv_rcon_password, 32, "", CFGFLAG_SERVER, "Remote console password") -MACRO_CONFIG_INT(sv_map_reload, 0, 0, 1, CFGFLAG_SERVER, "Reload the current map") - -MACRO_CONFIG_INT(debug, 0, 0, 1, CFGFLAG_CLIENT|CFGFLAG_SERVER, "Debug mode") -MACRO_CONFIG_INT(dbg_stress, 0, 0, 0, CFGFLAG_CLIENT|CFGFLAG_SERVER, "Stress systems") -MACRO_CONFIG_INT(dbg_stress_network, 0, 0, 0, CFGFLAG_CLIENT|CFGFLAG_SERVER, "Stress network") -MACRO_CONFIG_INT(dbg_pref, 0, 0, 1, CFGFLAG_SERVER, "Performance outputs") -MACRO_CONFIG_INT(dbg_graphs, 0, 0, 1, CFGFLAG_CLIENT, "Performance graphs") -MACRO_CONFIG_INT(dbg_hitch, 0, 0, 0, CFGFLAG_SERVER, "Hitch warnings") -MACRO_CONFIG_STR(dbg_stress_server, 32, "localhost", CFGFLAG_CLIENT, "Server to stress") -MACRO_CONFIG_INT(dbg_resizable, 0, 0, 0, CFGFLAG_CLIENT, "Enables window resizing") diff --git a/src/engine/e_console.cpp b/src/engine/e_console.cpp deleted file mode 100644 index c641289d..00000000 --- a/src/engine/e_console.cpp +++ /dev/null @@ -1,464 +0,0 @@ -#include <base/system.h> -#include "e_if_other.h" -#include "e_console.h" -#include "e_config.h" -#include "e_engine.h" -#include "e_linereader.h" -#include <stdio.h> -#include <string.h> -#include <stdlib.h> - - -#define CONSOLE_MAX_STR_LENGTH 1024 -/* the maximum number of tokens occurs in a string of length CONSOLE_MAX_STR_LENGTH with tokens size 1 separated by single spaces */ -#define MAX_PARTS (CONSOLE_MAX_STR_LENGTH+1)/2 - -typedef struct -{ - char string_storage[CONSOLE_MAX_STR_LENGTH+1]; - char *args_start; - - const char *command; - const char *args[MAX_PARTS]; - unsigned int num_args; -} PARSE_RESULT; - -static char *str_skipblanks(char *str) -{ - while(*str && (*str == ' ' || *str == '\t' || *str == '\n')) - str++; - return str; -} - -static char *str_skiptoblank(char *str) -{ - while(*str && (*str != ' ' && *str != '\t' && *str != '\n')) - str++; - return str; -} - -/* static int digit(char c) { return '0' <= c && c <= '9'; } */ - -static int console_parse_start(PARSE_RESULT *result, const char *string, int length) -{ - char *str; - int len = sizeof(result->string_storage); - if(length < len) - len = length; - - str_copy(result->string_storage, string, length); - str = result->string_storage; - - /* get command */ - str = str_skipblanks(str); - result->command = str; - str = str_skiptoblank(str); - - if(*str) - { - str[0] = 0; - str++; - } - - result->args_start = str; - result->num_args = 0; - return 0; -} - -static int console_parse_args(PARSE_RESULT *result, const char *format) -{ - char command; - char *str; - int optional = 0; - int error = 0; - - str = result->args_start; - - while(1) - { - /* fetch command */ - command = *format; - format++; - - if(!command) - break; - - if(command == '?') - optional = 1; - else - { - str = str_skipblanks(str); - - if(!(*str)) /* error, non optional command needs value */ - { - if(!optional) - error = 1; - break; - } - - /* add token */ - if(*str == '"') - { - char *dst; - str++; - result->args[result->num_args++] = str; - - dst = str; /* we might have to process escape data */ - while(1) - { - if(str[0] == '"') - break; - else if(str[0] == '\\') - { - if(str[1] == '\\') - str++; /* skip due to escape */ - else if(str[1] == '"') - str++; /* skip due to escape */ - } - else if(str[0] == 0) - return 1; /* return error */ - - *dst = *str; - dst++; - str++; - } - - /* write null termination */ - *dst = 0; - } - else - { - result->args[result->num_args++] = str; - - if(command == 'r') /* rest of the string */ - break; - else if(command == 'i') /* validate int */ - str = str_skiptoblank(str); - else if(command == 'f') /* validate float */ - str = str_skiptoblank(str); - else if(command == 's') /* validate string */ - str = str_skiptoblank(str); - - if(str[0] != 0) /* check for end of string */ - { - str[0] = 0; - str++; - } - } - } - } - - return error; -} - -const char *console_arg_string(void *res, unsigned index) -{ - PARSE_RESULT *result = (PARSE_RESULT *)res; - if (index < 0 || index >= result->num_args) - return ""; - return result->args[index]; -} - -int console_arg_int(void *res, unsigned index) -{ - PARSE_RESULT *result = (PARSE_RESULT *)res; - if (index < 0 || index >= result->num_args) - return 0; - return atoi(result->args[index]); -} - -float console_arg_float(void *res, unsigned index) -{ - PARSE_RESULT *result = (PARSE_RESULT *)res; - if (index < 0 || index >= result->num_args) - return 0.0f; - return atof(result->args[index]); -} - -int console_arg_num(void *result) -{ - return ((PARSE_RESULT *)result)->num_args; -} - -static COMMAND *first_command = 0x0; - -COMMAND *console_find_command(const char *name) -{ - COMMAND *cmd; - for (cmd = first_command; cmd; cmd = cmd->next) - { - if (strcmp(cmd->name, name) == 0) - return cmd; - } - - return 0x0; -} - -void console_register(COMMAND *cmd) -{ - cmd->next = first_command; - first_command = cmd; -} - -static void (*print_callback)(const char *, void *) = 0x0; -static void *print_callback_userdata; - -void console_register_print_callback(void (*callback)(const char *, void *), void *user_data) -{ - print_callback = callback; - print_callback_userdata = user_data; -} - -void console_print(const char *str) -{ - if (print_callback) - print_callback(str, print_callback_userdata); -} - -void console_execute_line_stroked(int stroke, const char *str) -{ - PARSE_RESULT result; - COMMAND *command; - - char strokestr[2] = {'0', 0}; - if(stroke) - strokestr[0] = '1'; - - while(str) - { - const char *end = str; - const char *next_part = 0; - int in_string = 0; - - while(*end) - { - if(*end == '"') - in_string ^= 1; - else if(*end == '\\') /* escape sequences */ - { - if(end[1] == '"') - end++; - } - else if(!in_string) - { - if(*end == ';') /* command separator */ - { - next_part = end+1; - break; - } - else if(*end == '#') /* comment, no need to do anything more */ - break; - } - - end++; - } - - if(console_parse_start(&result, str, (end-str) + 1) != 0) - return; - - command = console_find_command(result.command); - - if(command) - { - int is_stroke_command = 0; - if(result.command[0] == '+') - { - /* insert the stroke direction token */ - result.args[result.num_args] = strokestr; - result.num_args++; - is_stroke_command = 1; - } - - if(stroke || is_stroke_command) - { - if(console_parse_args(&result, command->params)) - { - char buf[256]; - str_format(buf, sizeof(buf), "Invalid arguments... Usage: %s %s", command->name, command->params); - console_print(buf); - } - else - command->callback(&result, command->user_data); - } - } - else - { - char buf[256]; - str_format(buf, sizeof(buf), "No such command: %s.", result.command); - console_print(buf); - } - - str = next_part; - } -} - -void console_possible_commands(const char *str, int flagmask, void (*callback)(const char *cmd, void *user), void *user) -{ - COMMAND *cmd; - for (cmd = first_command; cmd; cmd = cmd->next) - { - if(cmd->flags&flagmask) - { - if(str_find_nocase(cmd->name, str)) - callback(cmd->name, user); - } - } -} - - -COMMAND *console_get_command(const char *str) -{ - COMMAND *cmd; - for (cmd = first_command; cmd; cmd = cmd->next) - { - if(str_comp_nocase(cmd->name, str) == 0) - return cmd; - } - - return 0x0; -} - -void console_execute_line(const char *str) -{ - console_execute_line_stroked(1, str); -} - -static void console_execute_file_real(const char *filename) -{ - IOHANDLE file; - file = engine_openfile(filename, IOFLAG_READ); - - if(file) - { - char *line; - LINEREADER lr; - - dbg_msg("console", "executing '%s'", filename); - linereader_init(&lr, file); - - while((line = linereader_get(&lr))) - console_execute_line(line); - - io_close(file); - } - else - dbg_msg("console", "failed to open '%s'", filename); -} - -struct EXECFILE -{ - const char *filename; - struct EXECFILE *next; -}; - -void console_execute_file(const char *filename) -{ - static struct EXECFILE *first = 0; - struct EXECFILE this_file; - struct EXECFILE *cur; - struct EXECFILE *prev; - - /* make sure that this isn't being executed already */ - for(cur = first; cur; cur = cur->next) - if(strcmp(filename, cur->filename) == 0) - return; - - /* push this one to the stack */ - prev = first; - this_file.filename = filename; - this_file.next = first; - first = &this_file; - - /* execute file */ - console_execute_file_real(filename); - - /* pop this one from the stack */ - first = prev; -} - -static void con_echo(void *result, void *user_data) -{ - console_print(console_arg_string(result, 0)); -} - -static void con_exec(void *result, void *user_data) -{ - console_execute_file(console_arg_string(result, 0)); - -} - - -typedef struct -{ - CONFIG_INT_GETTER getter; - CONFIG_INT_SETTER setter; -} INT_VARIABLE_DATA; - -typedef struct -{ - CONFIG_STR_GETTER getter; - CONFIG_STR_SETTER setter; -} STR_VARIABLE_DATA; - -static void int_variable_command(void *result, void *user_data) -{ - INT_VARIABLE_DATA *data = (INT_VARIABLE_DATA *)user_data; - - if(console_arg_num(result)) - data->setter(&config, console_arg_int(result, 0)); - else - { - char buf[1024]; - str_format(buf, sizeof(buf), "Value: %d", data->getter(&config)); - console_print(buf); - } -} - -static void str_variable_command(void *result, void *user_data) -{ - STR_VARIABLE_DATA *data = (STR_VARIABLE_DATA *)user_data; - - if(console_arg_num(result)) - data->setter(&config, console_arg_string(result, 0)); - else - { - char buf[1024]; - str_format(buf, sizeof(buf), "Value: %s", data->getter(&config)); - console_print(buf); - } -} - -static void console_chain(void *result, void *user_data) -{ - COMMANDCHAIN *info = (COMMANDCHAIN *)user_data; - info->chain_callback(result, info->user_data, info->callback, info->callback_user_data); -} - -void console_chain_command(const char *cmd, COMMANDCHAIN *chaininfo, CONSOLE_CHAIN_CALLBACK cb, void *user) -{ - COMMAND *command = console_get_command(cmd); - - /* store info */ - chaininfo->chain_callback = cb; - chaininfo->callback = command->callback; - chaininfo->callback_user_data = command->user_data; - chaininfo->user_data = user; - - /* chain */ - command->callback = console_chain; - command->user_data = chaininfo; -} - -void console_init() -{ - MACRO_REGISTER_COMMAND("echo", "r", CFGFLAG_SERVER|CFGFLAG_CLIENT, con_echo, 0x0, "Echo the text"); - MACRO_REGISTER_COMMAND("exec", "r", CFGFLAG_SERVER|CFGFLAG_CLIENT, con_exec, 0x0, "Execute the specified file"); - - #define MACRO_CONFIG_INT(name,def,min,max,flags,desc) { static INT_VARIABLE_DATA data = { &config_get_ ## name, &config_set_ ## name }; MACRO_REGISTER_COMMAND(#name, "?i", flags, int_variable_command, &data, desc) } - #define MACRO_CONFIG_STR(name,len,def,flags,desc) { static STR_VARIABLE_DATA data = { &config_get_ ## name, &config_set_ ## name }; MACRO_REGISTER_COMMAND(#name, "?r", flags, str_variable_command, &data, desc) } - - #include "e_config_variables.h" - - #undef MACRO_CONFIG_INT - #undef MACRO_CONFIG_STR -} diff --git a/src/engine/e_console.h b/src/engine/e_console.h deleted file mode 100644 index a45bac10..00000000 --- a/src/engine/e_console.h +++ /dev/null @@ -1,48 +0,0 @@ -#ifndef _CONSOLE_H -#define _CONSOLE_H - -typedef void (*CONSOLE_CALLBACK)(void *result, void *user_data); -typedef void (*CONSOLE_CHAIN_CALLBACK)(void *result, void *user_data, CONSOLE_CALLBACK cb, void *cbuser); - -typedef struct COMMAND_t -{ - const char *name; - const char *params; - int flags; - CONSOLE_CALLBACK callback; - void *user_data; - const char *help; - struct COMMAND_t *next; -} COMMAND; - -typedef struct COMMANDCHAIN_t -{ - CONSOLE_CHAIN_CALLBACK chain_callback; - CONSOLE_CALLBACK callback; - void *callback_user_data; - void *user_data; -} COMMANDCHAIN; - -void console_init(); -void console_register(COMMAND *cmd); -void console_execute_line(const char *str); -void console_execute_line_stroked(int stroke, const char *str); -void console_execute_file(const char *filename); -void console_possible_commands(const char *str, int flagmask, void (*callback)(const char *cmd, void *user), void *user); -COMMAND *console_get_command(const char *cmd); -void console_chain_command(const char *cmd, COMMANDCHAIN *chaininfo, CONSOLE_CHAIN_CALLBACK cb, void *user); -void console_print(const char *str); -void console_register_print_callback(void (*callback)(const char *, void *user_data), void *user_data); - -/*int console_result_string(void *result, int index, const char **str); -int console_result_int(void *result, int index, int *i); -int console_result_float(void *result, int index, float *f);*/ - -const char *console_arg_string(void *result, unsigned index); -int console_arg_int(void *result, unsigned index); -float console_arg_float(void *result, unsigned index); -int console_arg_num(void *result); - -#define MACRO_REGISTER_COMMAND(name, params, flags, func, ptr, help) { static COMMAND cmd = { name, params, flags, func, ptr, help, 0x0}; console_register(&cmd); } - -#endif diff --git a/src/engine/e_datafile.cpp b/src/engine/e_datafile.cpp deleted file mode 100644 index 0317f9d0..00000000 --- a/src/engine/e_datafile.cpp +++ /dev/null @@ -1,677 +0,0 @@ -/* copyright (c) 2007 magnus auvinen, see licence.txt for more info */ -#include <base/system.h> -#include "e_datafile.h" -#include "e_engine.h" -#include <zlib.h> - -static const int DEBUG=0; - -typedef struct -{ - int type; - int start; - int num; -} DATAFILE_ITEM_TYPE; - -typedef struct -{ - int type_and_id; - int size; -} DATAFILE_ITEM; - -typedef struct -{ - char id[4]; - int version; - int size; - int swaplen; - int num_item_types; - int num_items; - int num_raw_data; - int item_size; - int data_size; -} DATAFILE_HEADER; - -typedef struct -{ - int num_item_types; - int num_items; - int num_raw_data; - int item_size; - int data_size; - char start[4]; -} DATAFILE_DATA; - -typedef struct -{ - DATAFILE_ITEM_TYPE *item_types; - int *item_offsets; - int *data_offsets; - int *data_sizes; - - char *item_start; - char *data_start; -} DATAFILE_INFO; - -struct DATAFILE_t -{ - IOHANDLE file; - DATAFILE_INFO info; - DATAFILE_HEADER header; - int data_start_offset; - char **data_ptrs; - char *data; -}; - -DATAFILE *datafile_load(const char *filename) -{ - DATAFILE *df; - IOHANDLE file; - DATAFILE_HEADER header; - unsigned readsize; - - unsigned *dst; - unsigned char *src; - unsigned j; - int size = 0; - int allocsize = 0; - - (void)dst; - (void)src; - (void)j; - - - dbg_msg("datafile", "datafile loading. filename='%s'", filename); - - file = engine_openfile(filename, IOFLAG_READ); - if(!file) - return 0; - - /* TODO: change this header */ - io_read(file, &header, sizeof(header)); - if(header.id[0] != 'A' || header.id[1] != 'T' || header.id[2] != 'A' || header.id[3] != 'D') - { - if(header.id[0] != 'D' || header.id[1] != 'A' || header.id[2] != 'T' || header.id[3] != 'A') - { - dbg_msg("datafile", "wrong signature. %x %x %x %x", header.id[0], header.id[1], header.id[2], header.id[3]); - return 0; - } - } - -#if defined(CONF_ARCH_ENDIAN_BIG) - swap_endian(&header, sizeof(int), sizeof(header)/sizeof(int)); -#endif - if(header.version != 3 && header.version != 4) - { - dbg_msg("datafile", "wrong version. version=%x", header.version); - return 0; - } - - /* read in the rest except the data */ - size = 0; - size += header.num_item_types*sizeof(DATAFILE_ITEM_TYPE); - size += (header.num_items+header.num_raw_data)*sizeof(int); - if(header.version == 4) - size += header.num_raw_data*sizeof(int); /* v4 has uncompressed data sizes aswell */ - size += header.item_size; - - allocsize = size; - allocsize += sizeof(DATAFILE); /* add space for info structure */ - allocsize += header.num_raw_data*sizeof(void*); /* add space for data pointers */ - - df = (DATAFILE*)mem_alloc(allocsize, 1); - df->header = header; - df->data_start_offset = sizeof(DATAFILE_HEADER) + size; - df->data_ptrs = (char**)(df+1); - df->data = (char *)(df+1)+header.num_raw_data*sizeof(char *); - df->file = file; - - /* clear the data pointers */ - mem_zero(df->data_ptrs, header.num_raw_data*sizeof(void*)); - - /* read types, offsets, sizes and item data */ - readsize = io_read(file, df->data, size); - if(readsize != size) - { - dbg_msg("datafile", "couldn't load the whole thing, wanted=%d got=%d", size, readsize); - return 0; - } - -#if defined(CONF_ARCH_ENDIAN_BIG) - swap_endian(df->data, sizeof(int), header.swaplen / sizeof(int)); -#endif - - if(DEBUG) - { - dbg_msg("datafile", "allocsize=%d", allocsize); - dbg_msg("datafile", "readsize=%d", readsize); - dbg_msg("datafile", "swaplen=%d", header.swaplen); - dbg_msg("datafile", "item_size=%d", df->header.item_size); - } - - df->info.item_types = (DATAFILE_ITEM_TYPE *)df->data; - df->info.item_offsets = (int *)&df->info.item_types[df->header.num_item_types]; - df->info.data_offsets = (int *)&df->info.item_offsets[df->header.num_items]; - df->info.data_sizes = (int *)&df->info.data_offsets[df->header.num_raw_data]; - - if(header.version == 4) - df->info.item_start = (char *)&df->info.data_sizes[df->header.num_raw_data]; - else - df->info.item_start = (char *)&df->info.data_offsets[df->header.num_raw_data]; - df->info.data_start = df->info.item_start + df->header.item_size; - - if(DEBUG) - dbg_msg("datafile", "datafile loading done. datafile='%s'", filename); - - if(DEBUG) - { - /* - for(int i = 0; i < df->data.num_raw_data; i++) - { - void *p = datafile_get_data(df, i); - dbg_msg("datafile", "%d %d", (int)((char*)p - (char*)(&df->data)), size); - } - - for(int i = 0; i < datafile_num_items(df); i++) - { - int type, id; - void *data = datafile_get_item(df, i, &type, &id); - dbg_msg("map", "\t%d: type=%x id=%x p=%p offset=%d", i, type, id, data, df->info.item_offsets[i]); - int *idata = (int*)data; - for(int k = 0; k < 3; k++) - dbg_msg("datafile", "\t\t%d=%d (%x)", k, idata[k], idata[k]); - } - - for(int i = 0; i < df->data.num_item_types; i++) - { - dbg_msg("map", "\t%d: type=%x start=%d num=%d", i, - df->info.item_types[i].type, - df->info.item_types[i].start, - df->info.item_types[i].num); - for(int k = 0; k < df->info.item_types[i].num; k++) - { - int type, id; - datafile_get_item(df, df->info.item_types[i].start+k, &type, &id); - if(type != df->info.item_types[i].type) - dbg_msg("map", "\tERROR"); - } - } - */ - } - - return df; -} - -int datafile_num_data(DATAFILE *df) -{ - return df->header.num_raw_data; -} - -/* always returns the size in the file */ -int datafile_get_datasize(DATAFILE *df, int index) -{ - if(index == df->header.num_raw_data-1) - return df->header.data_size-df->info.data_offsets[index]; - return df->info.data_offsets[index+1]-df->info.data_offsets[index]; -} - -static void *datafile_get_data_impl(DATAFILE *df, int index, int swap) -{ - /* load it if needed */ - if(!df->data_ptrs[index]) - { - /* fetch the data size */ - int datasize = datafile_get_datasize(df, index); - int swapsize = datasize; - - if(df->header.version == 4) - { - /* v4 has compressed data */ - void *temp = (char *)mem_alloc(datasize, 1); - unsigned long uncompressed_size = df->info.data_sizes[index]; - unsigned long s; - - dbg_msg("datafile", "loading data index=%d size=%d uncompressed=%d", index, datasize, uncompressed_size); - df->data_ptrs[index] = (char *)mem_alloc(uncompressed_size, 1); - - /* read the compressed data */ - io_seek(df->file, df->data_start_offset+df->info.data_offsets[index], IOSEEK_START); - io_read(df->file, temp, datasize); - - /* decompress the data, TODO: check for errors */ - s = uncompressed_size; - uncompress((Bytef*)df->data_ptrs[index], &s, (Bytef*)temp, datasize); - swapsize = s; - - /* clean up the temporary buffers */ - mem_free(temp); - } - else - { - /* load the data */ - dbg_msg("datafile", "loading data index=%d size=%d", index, datasize); - df->data_ptrs[index] = (char *)mem_alloc(datasize, 1); - io_seek(df->file, df->data_start_offset+df->info.data_offsets[index], IOSEEK_START); - io_read(df->file, df->data_ptrs[index], datasize); - } - -#if defined(CONF_ARCH_ENDIAN_BIG) - if(swap && swapsize) - swap_endian(df->data_ptrs[index], sizeof(int), swapsize/sizeof(int)); -#endif - } - - return df->data_ptrs[index]; -} - -void *datafile_get_data(DATAFILE *df, int index) -{ - return datafile_get_data_impl(df, index, 0); -} - -void *datafile_get_data_swapped(DATAFILE *df, int index) -{ - return datafile_get_data_impl(df, index, 1); -} - -void datafile_unload_data(DATAFILE *df, int index) -{ - if(index < 0) - return; - - /* */ - mem_free(df->data_ptrs[index]); - df->data_ptrs[index] = 0x0; -} - -int datafile_get_itemsize(DATAFILE *df, int index) -{ - if(index == df->header.num_items-1) - return df->header.item_size-df->info.item_offsets[index]; - return df->info.item_offsets[index+1]-df->info.item_offsets[index]; -} - -void *datafile_get_item(DATAFILE *df, int index, int *type, int *id) -{ - DATAFILE_ITEM *i = (DATAFILE_ITEM *)(df->info.item_start+df->info.item_offsets[index]); - if(type) - *type = (i->type_and_id>>16)&0xffff; /* remove sign extention */ - if(id) - *id = i->type_and_id&0xffff; - return (void *)(i+1); -} - -void datafile_get_type(DATAFILE *df, int type, int *start, int *num) -{ - int i; - for(i = 0; i < df->header.num_item_types; i++) - { - if(df->info.item_types[i].type == type) - { - *start = df->info.item_types[i].start; - *num = df->info.item_types[i].num; - return; - } - } - - *start = 0; - *num = 0; -} - -void *datafile_find_item(DATAFILE *df, int type, int id) -{ - int start, num,i ; - int item_id; - void *item; - - datafile_get_type(df, type, &start, &num); - for(i = 0; i < num; i++) - { - item = datafile_get_item(df, start+i,0, &item_id); - if(id == item_id) - return item; - } - return 0; -} - -int datafile_num_items(DATAFILE *df) -{ - return df->header.num_items; -} - -void datafile_unload(DATAFILE *df) -{ - if(df) - { - /* free the data that is loaded */ - int i; - for(i = 0; i < df->header.num_raw_data; i++) - mem_free(df->data_ptrs[i]); - - io_close(df->file); - mem_free(df); - } -} - -/* DATAFILE output */ -typedef struct -{ - int uncompressed_size; - int compressed_size; - void *compressed_data; -} DATA_INFO; - -typedef struct -{ - int type; - int id; - int size; - int next; - int prev; - void *data; -} ITEM_INFO; - -typedef struct -{ - int num; - int first; - int last; -} ITEMTYPE_INFO; - -/* */ -struct DATAFILE_OUT_t -{ - IOHANDLE file; - int num_items; - int num_datas; - int num_item_types; - ITEMTYPE_INFO item_types[0xffff]; - ITEM_INFO items[1024]; - DATA_INFO datas[1024]; -}; - -DATAFILE_OUT *datafile_create(const char *filename) -{ - int i; - DATAFILE_OUT *df = (DATAFILE_OUT*)mem_alloc(sizeof(DATAFILE_OUT), 1); - df->file = engine_openfile(filename, IOFLAG_WRITE); - if(!df->file) - { - mem_free(df); - return 0; - } - - df->num_items = 0; - df->num_datas = 0; - df->num_item_types = 0; - mem_zero(&df->item_types, sizeof(df->item_types)); - - for(i = 0; i < 0xffff; i++) - { - df->item_types[i].first = -1; - df->item_types[i].last = -1; - } - - return df; -} - -int datafile_add_item(DATAFILE_OUT *df, int type, int id, int size, void *data) -{ - df->items[df->num_items].type = type; - df->items[df->num_items].id = id; - df->items[df->num_items].size = size; - - /* - dbg_msg("datafile", "added item type=%d id=%d size=%d", type, id, size); - int i; - for(i = 0; i < size/4; i++) - dbg_msg("datafile", "\t%d: %08x %d", i, ((int*)data)[i], ((int*)data)[i]); - */ - - /* copy data */ - df->items[df->num_items].data = mem_alloc(size, 1); - mem_copy(df->items[df->num_items].data, data, size); - - if(!df->item_types[type].num) /* count item types */ - df->num_item_types++; - - /* link */ - df->items[df->num_items].prev = df->item_types[type].last; - df->items[df->num_items].next = -1; - - if(df->item_types[type].last != -1) - df->items[df->item_types[type].last].next = df->num_items; - df->item_types[type].last = df->num_items; - - if(df->item_types[type].first == -1) - df->item_types[type].first = df->num_items; - - df->item_types[type].num++; - - df->num_items++; - return df->num_items-1; -} - -int datafile_add_data(DATAFILE_OUT *df, int size, void *data) -{ - DATA_INFO *info = &df->datas[df->num_datas]; - unsigned long s = compressBound(size); - void *compdata = mem_alloc(s, 1); /* temporary buffer that we use duing compression */ - - int result = compress((Bytef*)compdata, &s, (Bytef*)data, size); - if(result != Z_OK) - { - dbg_msg("datafile", "compression error %d", result); - dbg_assert(0, "zlib error"); - } - - info->uncompressed_size = size; - info->compressed_size = (int)s; - info->compressed_data = mem_alloc(info->compressed_size, 1); - mem_copy(info->compressed_data, compdata, info->compressed_size); - mem_free(compdata); - - df->num_datas++; - return df->num_datas-1; -} - -int datafile_add_data_swapped(DATAFILE_OUT *df, int size, void *data) -{ -#if defined(CONF_ARCH_ENDIAN_BIG) - void *swapped = mem_alloc(size, 1); /* temporary buffer that we use duing compression */ - int index; - mem_copy(swapped, data, size); - swap_endian(&swapped, sizeof(int), size/sizeof(int)); - index = datafile_add_data(df, size, swapped); - mem_free(swapped); - return index; -#else - return datafile_add_data(df, size, data); -#endif -} - - -int datafile_finish(DATAFILE_OUT *df) -{ - int itemsize = 0; - int i, count, offset; - int typessize, headersize, offsetsize, filesize, swapsize; - int datasize = 0; - DATAFILE_ITEM_TYPE info; - DATAFILE_ITEM itm; - DATAFILE_HEADER header; - - /* we should now write this file! */ - if(DEBUG) - dbg_msg("datafile", "writing"); - - /* calculate sizes */ - for(i = 0; i < df->num_items; i++) - { - if(DEBUG) - dbg_msg("datafile", "item=%d size=%d (%d)", i, df->items[i].size, df->items[i].size+sizeof(DATAFILE_ITEM)); - itemsize += df->items[i].size + sizeof(DATAFILE_ITEM); - } - - - for(i = 0; i < df->num_datas; i++) - datasize += df->datas[i].compressed_size; - - /* calculate the complete size */ - typessize = df->num_item_types*sizeof(DATAFILE_ITEM_TYPE); - headersize = sizeof(DATAFILE_HEADER); - offsetsize = df->num_items*sizeof(int) + df->num_datas*sizeof(int); - filesize = headersize + typessize + offsetsize + itemsize + datasize; - swapsize = filesize - datasize; - - (void)swapsize; - - if(DEBUG) - dbg_msg("datafile", "num_item_types=%d typessize=%d itemsize=%d datasize=%d", df->num_item_types, typessize, itemsize, datasize); - - /* construct header */ - { - header.id[0] = 'D'; - header.id[1] = 'A'; - header.id[2] = 'T'; - header.id[3] = 'A'; - header.version = 4; - header.size = filesize - 16; - header.swaplen = swapsize - 16; - header.num_item_types = df->num_item_types; - header.num_items = df->num_items; - header.num_raw_data = df->num_datas; - header.item_size = itemsize; - header.data_size = datasize; - - /* TODO: apply swapping */ - /* write header */ - if(DEBUG) - dbg_msg("datafile", "headersize=%d", sizeof(header)); - io_write(df->file, &header, sizeof(header)); - } - - /* write types */ - for(i = 0, count = 0; i < 0xffff; i++) - { - if(df->item_types[i].num) - { - /* write info */ - info.type = i; - info.start = count; - info.num = df->item_types[i].num; - if(DEBUG) - dbg_msg("datafile", "writing type=%x start=%d num=%d", info.type, info.start, info.num); - io_write(df->file, &info, sizeof(info)); - count += df->item_types[i].num; - } - } - - /* write item offsets */ - for(i = 0, offset = 0; i < 0xffff; i++) - { - if(df->item_types[i].num) - { - /* write all items in of this type */ - int k = df->item_types[i].first; - while(k != -1) - { - if(DEBUG) - dbg_msg("datafile", "writing item offset num=%d offset=%d", k, offset); - io_write(df->file, &offset, sizeof(offset)); - offset += df->items[k].size + sizeof(DATAFILE_ITEM); - - /* next */ - k = df->items[k].next; - } - } - } - - /* write data offsets */ - for(i = 0, offset = 0; i < df->num_datas; i++) - { - if(DEBUG) - dbg_msg("datafile", "writing data offset num=%d offset=%d", i, offset); - io_write(df->file, &offset, sizeof(offset)); - offset += df->datas[i].compressed_size; - } - - /* write data uncompressed sizes */ - for(i = 0, offset = 0; i < df->num_datas; i++) - { - /* - if(DEBUG) - dbg_msg("datafile", "writing data offset num=%d offset=%d", i, offset); - */ - io_write(df->file, &df->datas[i].uncompressed_size, sizeof(int)); - } - - /* write items */ - for(i = 0; i < 0xffff; i++) - { - if(df->item_types[i].num) - { - /* write all items in of this type */ - int k = df->item_types[i].first; - while(k != -1) - { - itm.type_and_id = (i<<16)|df->items[k].id; - itm.size = df->items[k].size; - if(DEBUG) - dbg_msg("datafile", "writing item type=%x idx=%d id=%d size=%d", i, k, df->items[k].id, df->items[k].size); - - io_write(df->file, &itm, sizeof(itm)); - io_write(df->file, df->items[k].data, df->items[k].size); - - /* next */ - k = df->items[k].next; - } - } - } - - /* write data */ - for(i = 0; i < df->num_datas; i++) - { - if(DEBUG) - dbg_msg("datafile", "writing data id=%d size=%d", i, df->datas[i].compressed_size); - io_write(df->file, df->datas[i].compressed_data, df->datas[i].compressed_size); - } - - /* free data */ - for(i = 0; i < df->num_items; i++) - mem_free(df->items[i].data); - - - io_close(df->file); - mem_free(df); - - if(DEBUG) - dbg_msg("datafile", "done"); - return 0; -} - -#define BUFFER_SIZE 64*1024 - -int datafile_crc(const char *filename) -{ - unsigned char buffer[BUFFER_SIZE]; - IOHANDLE file; - int crc = 0; - unsigned bytes = 0; - - file = engine_openfile(filename, IOFLAG_READ); - if(!file) - return 0; - - while(1) - { - bytes = io_read(file, buffer, BUFFER_SIZE); - if(bytes <= 0) - break; - crc = crc32(crc, buffer, bytes); - } - - io_close(file); - - return crc; -} diff --git a/src/engine/e_datafile.h b/src/engine/e_datafile.h deleted file mode 100644 index 203f415c..00000000 --- a/src/engine/e_datafile.h +++ /dev/null @@ -1,29 +0,0 @@ -/* copyright (c) 2007 magnus auvinen, see licence.txt for more info */ - -/* raw datafile access */ -typedef struct DATAFILE_t DATAFILE; - -/* read access */ -DATAFILE *datafile_load(const char *filename); -DATAFILE *datafile_load_old(const char *filename); -void *datafile_get_data(DATAFILE *df, int index); -void *datafile_get_data_swapped(DATAFILE *df, int index); /* makes sure that the data is 32bit LE ints when saved */ -int datafile_get_datasize(DATAFILE *df, int index); -void datafile_unload_data(DATAFILE *df, int index); -void *datafile_get_item(DATAFILE *df, int index, int *type, int *id); -int datafile_get_itemsize(DATAFILE *df, int index); -void datafile_get_type(DATAFILE *df, int type, int *start, int *num); -void *datafile_find_item(DATAFILE *df, int type, int id); -int datafile_num_items(DATAFILE *df); -int datafile_num_data(DATAFILE *df); -void datafile_unload(DATAFILE *df); - -int datafile_crc(const char *filename); - -/* write access */ -typedef struct DATAFILE_OUT_t DATAFILE_OUT; -DATAFILE_OUT *datafile_create(const char *filename); -int datafile_add_data(DATAFILE_OUT *df, int size, void *data); -int datafile_add_data_swapped(DATAFILE_OUT *df, int size, void *data); -int datafile_add_item(DATAFILE_OUT *df, int type, int id, int size, void *data); -int datafile_finish(DATAFILE_OUT *df); diff --git a/src/engine/e_demorec.cpp b/src/engine/e_demorec.cpp deleted file mode 100644 index 1bab1b8d..00000000 --- a/src/engine/e_demorec.cpp +++ /dev/null @@ -1,640 +0,0 @@ -#include <base/system.h> -#include "e_demorec.h" -#include "e_memheap.h" -#include "e_snapshot.h" -#include "e_compression.h" -#include "e_network.h" -#include "e_engine.h" -#include "e_if_other.h" - -static IOHANDLE record_file = 0; -static const unsigned char header_marker[8] = {'T', 'W', 'D', 'E', 'M', 'O', 0, 1}; - -/* Record */ -static int record_lasttickmarker = -1; -static int record_lastkeyframe; -static unsigned char record_lastsnapshotdata[CSnapshot::MAX_SIZE]; - -int demorec_isrecording() { return record_file != 0; } - -int demorec_record_start(const char *filename, const char *netversion, const char *map, int crc, const char *type) -{ - DEMOREC_HEADER header; - if(record_file) - return -1; - - record_file = engine_openfile(filename, IOFLAG_WRITE); - - if(!record_file) - { - dbg_msg("demorec/record", "Unable to open '%s' for recording", filename); - return -1; - } - - /* write header */ - mem_zero(&header, sizeof(header)); - mem_copy(header.marker, header_marker, sizeof(header.marker)); - str_copy(header.netversion, netversion, sizeof(header.netversion)); - str_copy(header.map, map, sizeof(header.map)); - str_copy(header.type, type, sizeof(header.type)); - header.crc[0] = (crc>>24)&0xff; - header.crc[1] = (crc>>16)&0xff; - header.crc[2] = (crc>>8)&0xff; - header.crc[3] = (crc)&0xff; - io_write(record_file, &header, sizeof(header)); - - record_lastkeyframe = -1; - record_lasttickmarker = -1; - - dbg_msg("demorec/record", "Recording to '%s'", filename); - return 0; -} - -/* - Tickmarker - 7 = Always set - 6 = Keyframe flag - 0-5 = Delta tick - - Normal - 7 = Not set - 5-6 = Type - 0-4 = Size -*/ - -enum -{ - CHUNKTYPEFLAG_TICKMARKER = 0x80, - CHUNKTICKFLAG_KEYFRAME = 0x40, /* only when tickmarker is set*/ - - CHUNKMASK_TICK = 0x3f, - CHUNKMASK_TYPE = 0x60, - CHUNKMASK_SIZE = 0x1f, - - CHUNKTYPE_SNAPSHOT = 1, - CHUNKTYPE_MESSAGE = 2, - CHUNKTYPE_DELTA = 3, - - CHUNKFLAG_BIGSIZE = 0x10 -}; - -static void demorec_record_write_tickmarker(int tick, int keyframe) -{ - if(record_lasttickmarker == -1 || tick-record_lasttickmarker > 63 || keyframe) - { - unsigned char chunk[5]; - chunk[0] = CHUNKTYPEFLAG_TICKMARKER; - chunk[1] = (tick>>24)&0xff; - chunk[2] = (tick>>16)&0xff; - chunk[3] = (tick>>8)&0xff; - chunk[4] = (tick)&0xff; - - if(keyframe) - chunk[0] |= CHUNKTICKFLAG_KEYFRAME; - - io_write(record_file, chunk, sizeof(chunk)); - } - else - { - unsigned char chunk[1]; - chunk[0] = CHUNKTYPEFLAG_TICKMARKER | (tick-record_lasttickmarker); - io_write(record_file, chunk, sizeof(chunk)); - } - - record_lasttickmarker = tick; -} - -static void demorec_record_write(int type, const void *data, int size) -{ - char buffer[64*1024]; - char buffer2[64*1024]; - unsigned char chunk[3]; - - if(!record_file) - return; - - - /* pad the data with 0 so we get an alignment of 4, - else the compression won't work and miss some bytes */ - mem_copy(buffer2, data, size); - while(size&3) - buffer2[size++] = 0; - size = intpack_compress(buffer2, size, buffer); /* buffer2 -> buffer */ - size = CNetBase::Compress(buffer, size, buffer2, sizeof(buffer2)); /* buffer -> buffer2 */ - - - chunk[0] = ((type&0x3)<<5); - if(size < 30) - { - chunk[0] |= size; - io_write(record_file, chunk, 1); - } - else - { - if(size < 256) - { - chunk[0] |= 30; - chunk[1] = size&0xff; - io_write(record_file, chunk, 2); - } - else - { - chunk[0] |= 31; - chunk[1] = size&0xff; - chunk[2] = size>>8; - io_write(record_file, chunk, 3); - } - } - - io_write(record_file, buffer2, size); -} - -void demorec_record_snapshot(int tick, const void *data, int size) -{ - if(record_lastkeyframe == -1 || (tick-record_lastkeyframe) > SERVER_TICK_SPEED*5) - { - /* write full tickmarker */ - demorec_record_write_tickmarker(tick, 1); - - /* write snapshot */ - demorec_record_write(CHUNKTYPE_SNAPSHOT, data, size); - - record_lastkeyframe = tick; - mem_copy(record_lastsnapshotdata, data, size); - } - else - { - /* create delta, prepend tick */ - char delta_data[CSnapshot::MAX_SIZE+sizeof(int)]; - int delta_size; - - /* write tickmarker */ - demorec_record_write_tickmarker(tick, 0); - - delta_size = CSnapshot::CreateDelta((CSnapshot*)record_lastsnapshotdata, (CSnapshot*)data, &delta_data); - if(delta_size) - { - /* record delta */ - demorec_record_write(CHUNKTYPE_DELTA, delta_data, delta_size); - mem_copy(record_lastsnapshotdata, data, size); - } - } -} - -void demorec_record_message(const void *data, int size) -{ - demorec_record_write(CHUNKTYPE_MESSAGE, data, size); -} - -int demorec_record_stop() -{ - if(!record_file) - return -1; - - dbg_msg("demorec/record", "Stopped recording"); - io_close(record_file); - record_file = 0; - return 0; -} - -/* Playback */ -typedef struct KEYFRAME -{ - long filepos; - int tick; -} KEYFRAME; - -typedef struct KEYFRAME_SEARCH -{ - KEYFRAME frame; - struct KEYFRAME_SEARCH *next; -} KEYFRAME_SEARCH; - -static IOHANDLE play_file = 0; -static DEMOREC_PLAYCALLBACK play_callback_snapshot = 0; -static DEMOREC_PLAYCALLBACK play_callback_message = 0; -static KEYFRAME *keyframes = 0; - -static DEMOREC_PLAYBACKINFO playbackinfo; -static unsigned char playback_lastsnapshotdata[CSnapshot::MAX_SIZE]; -static int playback_lastsnapshotdata_size = -1; - - -const DEMOREC_PLAYBACKINFO *demorec_playback_info() { return &playbackinfo; } -int demorec_isplaying() { return play_file != 0; } - -int demorec_playback_registercallbacks(DEMOREC_PLAYCALLBACK snapshot_cb, DEMOREC_PLAYCALLBACK message_cb) -{ - play_callback_snapshot = snapshot_cb; - play_callback_message = message_cb; - return 0; -} - -static int read_chunk_header(int *type, int *size, int *tick) -{ - unsigned char chunk = 0; - - *size = 0; - *type = 0; - - if(io_read(play_file, &chunk, sizeof(chunk)) != sizeof(chunk)) - return -1; - - if(chunk&CHUNKTYPEFLAG_TICKMARKER) - { - /* decode tick marker */ - int tickdelta = chunk&(CHUNKMASK_TICK); - *type = chunk&(CHUNKTYPEFLAG_TICKMARKER|CHUNKTICKFLAG_KEYFRAME); - - if(tickdelta == 0) - { - unsigned char tickdata[4]; - if(io_read(play_file, tickdata, sizeof(tickdata)) != sizeof(tickdata)) - return -1; - *tick = (tickdata[0]<<24) | (tickdata[1]<<16) | (tickdata[2]<<8) | tickdata[3]; - } - else - { - *tick += tickdelta; - } - - } - else - { - /* decode normal chunk */ - *type = (chunk&CHUNKMASK_TYPE)>>5; - *size = chunk&CHUNKMASK_SIZE; - - if(*size == 30) - { - unsigned char sizedata[1]; - if(io_read(play_file, sizedata, sizeof(sizedata)) != sizeof(sizedata)) - return -1; - *size = sizedata[0]; - - } - else if(*size == 31) - { - unsigned char sizedata[2]; - if(io_read(play_file, sizedata, sizeof(sizedata)) != sizeof(sizedata)) - return -1; - *size = (sizedata[1]<<8) | sizedata[0]; - } - } - - return 0; -} - -static void scan_file() -{ - long start_pos; - HEAP *heap = 0; - KEYFRAME_SEARCH *first_key = 0; - KEYFRAME_SEARCH *current_key = 0; - /*DEMOREC_CHUNK chunk;*/ - int chunk_size, chunk_type, chunk_tick = 0; - int i; - - heap = memheap_create(); - - start_pos = io_tell(play_file); - playbackinfo.seekable_points = 0; - - while(1) - { - long current_pos = io_tell(play_file); - - if(read_chunk_header(&chunk_type, &chunk_size, &chunk_tick)) - break; - - /* read the chunk */ - if(chunk_type&CHUNKTYPEFLAG_TICKMARKER) - { - if(chunk_type&CHUNKTICKFLAG_KEYFRAME) - { - KEYFRAME_SEARCH *key; - - /* save the position */ - key = (KEYFRAME_SEARCH *)memheap_allocate(heap, sizeof(KEYFRAME_SEARCH)); - key->frame.filepos = current_pos; - key->frame.tick = chunk_tick; - key->next = 0; - if(current_key) - current_key->next = key; - if(!first_key) - first_key = key; - current_key = key; - playbackinfo.seekable_points++; - } - - if(playbackinfo.first_tick == -1) - playbackinfo.first_tick = chunk_tick; - playbackinfo.last_tick = chunk_tick; - } - else if(chunk_size) - io_skip(play_file, chunk_size); - - } - - /* copy all the frames to an array instead for fast access */ - keyframes = (KEYFRAME*)mem_alloc(playbackinfo.seekable_points*sizeof(KEYFRAME), 1); - for(current_key = first_key, i = 0; current_key; current_key = current_key->next, i++) - keyframes[i] = current_key->frame; - - /* destroy the temporary heap and seek back to the start */ - memheap_destroy(heap); - io_seek(play_file, start_pos, IOSEEK_START); -} - -static void do_tick() -{ - static char compresseddata[CSnapshot::MAX_SIZE]; - static char decompressed[CSnapshot::MAX_SIZE]; - static char data[CSnapshot::MAX_SIZE]; - int chunk_type, chunk_tick, chunk_size; - int data_size; - int got_snapshot = 0; - - /* update ticks */ - playbackinfo.previous_tick = playbackinfo.current_tick; - playbackinfo.current_tick = playbackinfo.next_tick; - chunk_tick = playbackinfo.current_tick; - - while(1) - { - if(read_chunk_header(&chunk_type, &chunk_size, &chunk_tick)) - { - /* stop on error or eof */ - dbg_msg("demorec", "end of file"); - demorec_playback_pause(); - break; - } - - /* read the chunk */ - if(chunk_size) - { - if(io_read(play_file, compresseddata, chunk_size) != (unsigned)chunk_size) - { - /* stop on error or eof */ - dbg_msg("demorec", "error reading chunk"); - demorec_playback_stop(); - break; - } - - data_size = CNetBase::Decompress(compresseddata, chunk_size, decompressed, sizeof(decompressed)); - if(data_size < 0) - { - /* stop on error or eof */ - dbg_msg("demorec", "error during network decompression"); - demorec_playback_stop(); - break; - } - - data_size = intpack_decompress(decompressed, data_size, data); - - if(data_size < 0) - { - dbg_msg("demorec", "error during intpack decompression"); - demorec_playback_stop(); - break; - } - } - - if(chunk_type == CHUNKTYPE_DELTA) - { - /* process delta snapshot */ - static char newsnap[CSnapshot::MAX_SIZE]; - - got_snapshot = 1; - - data_size = CSnapshot::UnpackDelta((CSnapshot*)playback_lastsnapshotdata, (CSnapshot*)newsnap, data, data_size); - - if(data_size >= 0) - { - if(play_callback_snapshot) - play_callback_snapshot(newsnap, data_size); - - playback_lastsnapshotdata_size = data_size; - mem_copy(playback_lastsnapshotdata, newsnap, data_size); - } - else - dbg_msg("demorec", "error duing unpacking of delta, err=%d", data_size); - } - else if(chunk_type == CHUNKTYPE_SNAPSHOT) - { - /* process full snapshot */ - got_snapshot = 1; - - playback_lastsnapshotdata_size = data_size; - mem_copy(playback_lastsnapshotdata, data, data_size); - if(play_callback_snapshot) - play_callback_snapshot(data, data_size); - } - else - { - /* if there were no snapshots in this tick, replay the last one */ - if(!got_snapshot && play_callback_snapshot && playback_lastsnapshotdata_size != -1) - { - got_snapshot = 1; - play_callback_snapshot(playback_lastsnapshotdata, playback_lastsnapshotdata_size); - } - - /* check the remaining types */ - if(chunk_type&CHUNKTYPEFLAG_TICKMARKER) - { - playbackinfo.next_tick = chunk_tick; - break; - } - else if(chunk_type == CHUNKTYPE_MESSAGE) - { - if(play_callback_message) - play_callback_message(data, data_size); - } - } - } -} - -void demorec_playback_pause() -{ - playbackinfo.paused = 1; -} - -void demorec_playback_unpause() -{ - if(playbackinfo.paused) - { - /*playbackinfo.start_tick = playbackinfo.current_tick; - playbackinfo.start_time = time_get();*/ - playbackinfo.paused = 0; - } -} - -int demorec_playback_load(const char *filename) -{ - play_file = engine_openfile(filename, IOFLAG_READ); - if(!play_file) - { - dbg_msg("demorec/playback", "could not open '%s'", filename); - return -1; - } - - /* clear the playback info */ - mem_zero(&playbackinfo, sizeof(playbackinfo)); - playbackinfo.first_tick = -1; - playbackinfo.last_tick = -1; - /*playbackinfo.start_tick = -1;*/ - playbackinfo.next_tick = -1; - playbackinfo.current_tick = -1; - playbackinfo.previous_tick = -1; - playbackinfo.speed = 1; - - playback_lastsnapshotdata_size = -1; - - /* read the header */ - io_read(play_file, &playbackinfo.header, sizeof(playbackinfo.header)); - if(mem_comp(playbackinfo.header.marker, header_marker, sizeof(header_marker)) != 0) - { - dbg_msg("demorec/playback", "'%s' is not a demo file", filename); - io_close(play_file); - play_file = 0; - return -1; - } - - /* scan the file for interessting points */ - scan_file(); - - /* ready for playback */ - return 0; -} - -int demorec_playback_nextframe() -{ - do_tick(); - return demorec_isplaying(); -} - -int demorec_playback_play() -{ - /* fill in previous and next tick */ - while(playbackinfo.previous_tick == -1 && demorec_isplaying()) - do_tick(); - - /* set start info */ - /*playbackinfo.start_tick = playbackinfo.previous_tick; - playbackinfo.start_time = time_get();*/ - playbackinfo.current_time = playbackinfo.previous_tick*time_freq()/SERVER_TICK_SPEED; - playbackinfo.last_update = time_get(); - return 0; -} - -int demorec_playback_set(float percent) -{ - int keyframe; - int wanted_tick; - if(!play_file) - return -1; - - /* -5 because we have to have a current tick and previous tick when we do the playback */ - wanted_tick = playbackinfo.first_tick + (int)((playbackinfo.last_tick-playbackinfo.first_tick)*percent) - 5; - - keyframe = (int)(playbackinfo.seekable_points*percent); - - if(keyframe < 0 || keyframe >= playbackinfo.seekable_points) - return -1; - - /* get correct key frame */ - if(keyframes[keyframe].tick < wanted_tick) - while(keyframe < playbackinfo.seekable_points-1 && keyframes[keyframe].tick < wanted_tick) - keyframe++; - - while(keyframe && keyframes[keyframe].tick > wanted_tick) - keyframe--; - - /* seek to the correct keyframe */ - io_seek(play_file, keyframes[keyframe].filepos, IOSEEK_START); - - /*playbackinfo.start_tick = -1;*/ - playbackinfo.next_tick = -1; - playbackinfo.current_tick = -1; - playbackinfo.previous_tick = -1; - - /* playback everything until we hit our tick */ - while(playbackinfo.previous_tick < wanted_tick) - do_tick(); - - demorec_playback_play(); - - return 0; -} - -void demorec_playback_setspeed(float speed) -{ - playbackinfo.speed = speed; -} - -int demorec_playback_update() -{ - int64 now = time_get(); - int64 deltatime = now-playbackinfo.last_update; - playbackinfo.last_update = now; - - if(!demorec_isplaying()) - return 0; - - if(playbackinfo.paused) - { - - } - else - { - int64 freq = time_freq(); - playbackinfo.current_time += (int64)(deltatime*(double)playbackinfo.speed); - - while(1) - { - int64 curtick_start = (playbackinfo.current_tick)*freq/SERVER_TICK_SPEED; - - /* break if we are ready */ - if(curtick_start > playbackinfo.current_time) - break; - - /* do one more tick */ - do_tick(); - - if(playbackinfo.paused) - return 0; - } - - /* update intratick */ - { - int64 curtick_start = (playbackinfo.current_tick)*freq/SERVER_TICK_SPEED; - int64 prevtick_start = (playbackinfo.previous_tick)*freq/SERVER_TICK_SPEED; - playbackinfo.intratick = (playbackinfo.current_time - prevtick_start) / (float)(curtick_start-prevtick_start); - playbackinfo.ticktime = (playbackinfo.current_time - prevtick_start) / (float)freq; - } - - if(playbackinfo.current_tick == playbackinfo.previous_tick || - playbackinfo.current_tick == playbackinfo.next_tick) - { - dbg_msg("demorec/playback", "tick error prev=%d cur=%d next=%d", - playbackinfo.previous_tick, playbackinfo.current_tick, playbackinfo.next_tick); - } - } - - return 0; -} - -int demorec_playback_stop() -{ - if(!play_file) - return -1; - - dbg_msg("demorec/playback", "Stopped playback"); - io_close(play_file); - play_file = 0; - mem_free(keyframes); - keyframes = 0; - return 0; -} - - diff --git a/src/engine/e_demorec.h b/src/engine/e_demorec.h deleted file mode 100644 index 9716b463..00000000 --- a/src/engine/e_demorec.h +++ /dev/null @@ -1,69 +0,0 @@ -/* copyright (c) 2007 magnus auvinen, see licence.txt for more info */ -#ifndef _DEMOREC_H -#define _DEMOREC_H - -typedef struct DEMOREC_HEADER -{ - char marker[8]; - char netversion[64]; - char map[64]; - unsigned char crc[4]; - char type[8]; -} DEMOREC_HEADER; - -typedef struct DEMOREC_CHUNK -{ - char type[2]; - unsigned short size; -} DEMOREC_CHUNK; - -typedef struct DEMOREC_TICKMARKER -{ - int tick; -} DEMOREC_TICKMARKER; - -typedef struct DEMOREC_PLAYBACKINFO -{ - DEMOREC_HEADER header; - - int paused; - float speed; - - int64 last_update; - int64 current_time; - - int first_tick; - int last_tick; - - int seekable_points; - - int next_tick; - int current_tick; - int previous_tick; - - float intratick; - float ticktime; -} DEMOREC_PLAYBACKINFO; - -int demorec_record_start(const char *filename, const char *netversion, const char *map, int map_crc, const char *type); -int demorec_isrecording(); -void demorec_record_snapshot(int tick, const void *data, int size); -void demorec_record_message(const void *data, int size); -int demorec_record_stop(); - -typedef void (*DEMOREC_PLAYCALLBACK)(void *data, int size); - -int demorec_playback_registercallbacks(DEMOREC_PLAYCALLBACK snapshot_cb, DEMOREC_PLAYCALLBACK message_cb); -int demorec_playback_load(const char *filename); -int demorec_playback_nextframe(); -int demorec_playback_play(); -void demorec_playback_pause(); -void demorec_playback_unpause(); -void demorec_playback_setspeed(float speed); -int demorec_playback_set(float precent); -int demorec_playback_update(); -const DEMOREC_PLAYBACKINFO *demorec_playback_info(); -int demorec_isplaying(); -int demorec_playback_stop(); - -#endif diff --git a/src/engine/e_engine.cpp b/src/engine/e_engine.cpp deleted file mode 100644 index 4475478a..00000000 --- a/src/engine/e_engine.cpp +++ /dev/null @@ -1,596 +0,0 @@ -/* copyright (c) 2007 magnus auvinen, see licence.txt for more info */ -#include <string.h> -#include <stdio.h> -#include <stdlib.h> - -#include <base/system.h> - -#include <engine/e_server_interface.h> -#include <engine/e_config.h> -#include <engine/e_console.h> -#include <engine/e_engine.h> -#include <engine/e_network.h> -#include "e_linereader.h" - -/* compiled-in data-dir path */ -#define DATA_DIR "data" - -static JOBPOOL hostlookuppool; -static int engine_find_datadir(char *argv0); - -static void con_dbg_dumpmem(void *result, void *user_data) -{ - mem_debug_dump(); -} - -static void con_dbg_lognetwork(void *result, void *user_data) -{ - CNetBase::OpenLog("network_sent.dat", "network_recv.dat"); -} - - -static char application_save_path[512] = {0}; -static char datadir[512] = {0}; - -const char *engine_savepath(const char *filename, char *buffer, int max) -{ - str_format(buffer, max, "%s/%s", application_save_path, filename); - return buffer; -} - -void engine_init(const char *appname) -{ - dbg_logger_stdout(); - dbg_logger_debugger(); - - /* */ - dbg_msg("engine", "running on %s-%s-%s", CONF_FAMILY_STRING, CONF_PLATFORM_STRING, CONF_ARCH_STRING); -#ifdef CONF_ARCH_ENDIAN_LITTLE - dbg_msg("engine", "arch is little endian"); -#elif defined(CONF_ARCH_ENDIAN_BIG) - dbg_msg("engine", "arch is big endian"); -#else - dbg_msg("engine", "unknown endian"); -#endif - - /* init the network */ - net_init(); - CNetBase::Init(); - - /* create storage location */ - { - char path[1024] = {0}; - fs_storage_path(appname, application_save_path, sizeof(application_save_path)); - if(fs_makedir(application_save_path) == 0) - { - str_format(path, sizeof(path), "%s/screenshots", application_save_path); - fs_makedir(path); - - str_format(path, sizeof(path), "%s/maps", application_save_path); - fs_makedir(path); - - str_format(path, sizeof(path), "%s/downloadedmaps", application_save_path); - fs_makedir(path); - - str_format(path, sizeof(path), "%s/demos", application_save_path); - fs_makedir(path); - } - } - - /* init console and add the console logger */ - console_init(); - dbg_logger(console_print); - - jobs_initpool(&hostlookuppool, 1); - - MACRO_REGISTER_COMMAND("dbg_dumpmem", "", CFGFLAG_SERVER|CFGFLAG_CLIENT, con_dbg_dumpmem, 0x0, "Dump the memory"); - MACRO_REGISTER_COMMAND("dbg_lognetwork", "", CFGFLAG_SERVER|CFGFLAG_CLIENT, con_dbg_lognetwork, 0x0, "Log the network"); - - /* reset the config */ - config_reset(); -} - - -void engine_listdir(int types, const char *path, FS_LISTDIR_CALLBACK cb, void *user) -{ - char buffer[1024]; - - /* list current directory */ - if(types&LISTDIRTYPE_CURRENT) - { - fs_listdir(path, cb, user); - } - - /* list users directory */ - if(types&LISTDIRTYPE_SAVE) - { - engine_savepath(path, buffer, sizeof(buffer)); - fs_listdir(buffer, cb, user); - } - - /* list datadir directory */ - if(types&LISTDIRTYPE_DATA) - { - str_format(buffer, sizeof(buffer), "%s/%s", datadir, path); - fs_listdir(buffer, cb, user); - } -} - -void engine_getpath(char *buffer, int buffer_size, const char *filename, int flags) -{ - if(flags&IOFLAG_WRITE) - engine_savepath(filename, buffer, buffer_size); - else - { - IOHANDLE handle = 0; - - /* check current directory */ - handle = io_open(filename, flags); - if(handle) - { - str_copy(buffer, filename, buffer_size); - io_close(handle); - return; - } - - /* check user directory */ - engine_savepath(filename, buffer, buffer_size); - handle = io_open(buffer, flags); - if(handle) - { - io_close(handle); - return; - } - - /* check normal data directory */ - str_format(buffer, buffer_size, "%s/%s", datadir, filename); - handle = io_open(buffer, flags); - if(handle) - { - io_close(handle); - return; - } - } - - buffer[0] = 0; -} - -IOHANDLE engine_openfile(const char *filename, int flags) -{ - char buffer[1024]; - - if(flags&IOFLAG_WRITE) - { - engine_savepath(filename, buffer, sizeof(buffer)); - return io_open(buffer, flags); - } - else - { - IOHANDLE handle = 0; - - /* check current directory */ - handle = io_open(filename, flags); - if(handle) - return handle; - - /* check user directory */ - engine_savepath(filename, buffer, sizeof(buffer)); - handle = io_open(buffer, flags); - if(handle) - return handle; - - /* check normal data directory */ - str_format(buffer, sizeof(buffer), "%s/%s", datadir, filename); - handle = io_open(buffer, flags); - if(handle) - return handle; - } - return 0; -} - -void engine_parse_arguments(int argc, char **argv) -{ - /* load the configuration */ - int i; - - /* check for datadir override */ - for(i = 1; i < argc; i++) - { - if(argv[i][0] == '-' && argv[i][1] == 'd' && argv[i][2] == 0 && argc - i > 1) - { - str_copy(datadir, argv[i+1], sizeof(datadir)); - i++; - } - } - - /* search for data directory */ - engine_find_datadir(argv[0]); - - dbg_msg("engine/datadir", "paths used:"); - dbg_msg("engine/datadir", "\t."); - dbg_msg("engine/datadir", "\t%s", application_save_path); - dbg_msg("engine/datadir", "\t%s", datadir); - dbg_msg("engine/datadir", "saving files to: %s", application_save_path); - - - /* check for scripts to execute */ - for(i = 1; i < argc; i++) - { - if(argv[i][0] == '-' && argv[i][1] == 'f' && argv[i][2] == 0 && argc - i > 1) - { - console_execute_file(argv[i+1]); - i++; - } - } - - /* search arguments for overrides */ - { - int i; - for(i = 1; i < argc; i++) - console_execute_line(argv[i]); - } - - console_execute_file("autoexec.cfg"); - - /* open logfile if needed */ - if(config.logfile[0]) - dbg_logger_file(config.logfile); - - /* set default servers and load from disk*/ - mastersrv_default(); - mastersrv_load(); -} - - -static IOHANDLE config_file = 0; - -int engine_config_write_start() -{ - config_save(); - config_file = engine_openfile("settings.cfg", IOFLAG_WRITE); - if(config_file == 0) - return -1; - return 0; -} - -void engine_config_write_line(const char *line) -{ - if(config_file) - { -#if defined(CONF_FAMILY_WINDOWS) - static const char newline[] = "\r\n"; -#else - static const char newline[] = "\n"; -#endif - io_write(config_file, line, strlen(line)); - io_write(config_file, newline, sizeof(newline)-1); - } -} - -void engine_config_write_stop() -{ - io_close(config_file); - config_file = 0; -} -/* -void engine_writeconfig() -{ -}*/ - -static int perf_tick = 1; -static PERFORMACE_INFO *current = 0; - -void perf_init() -{ -} - -void perf_next() -{ - perf_tick++; - current = 0; -} - -void perf_start(PERFORMACE_INFO *info) -{ - if(info->tick != perf_tick) - { - info->parent = current; - info->first_child = 0; - info->next_child = 0; - - if(info->parent) - { - info->next_child = info->parent->first_child; - info->parent->first_child = info; - } - - info->tick = perf_tick; - info->biggest = 0; - info->total = 0; - } - - current = info; - current->start = time_get(); -} - -void perf_end() -{ - if(!current) - return; - - current->last_delta = time_get()-current->start; - current->total += current->last_delta; - - if(current->last_delta > current->biggest) - current->biggest = current->last_delta; - - current = current->parent; -} - -static void perf_dump_imp(PERFORMACE_INFO *info, int indent) -{ - char buf[512] = {0}; - int64 freq = time_freq(); - int i; - - for(i = 0; i < indent; i++) - buf[i] = ' '; - - str_format(&buf[indent], sizeof(buf)-indent, "%-20s %8.2f %8.2f", info->name, info->total*1000/(float)freq, info->biggest*1000/(float)freq); - dbg_msg("perf", "%s", buf); - - info = info->first_child; - while(info) - { - perf_dump_imp(info, indent+2); - info = info->next_child; - } -} - -void perf_dump(PERFORMACE_INFO *top) -{ - perf_dump_imp(top, 0); -} - -/* master server functions */ -typedef struct -{ - char hostname[128]; - NETADDR addr; - - HOSTLOOKUP lookup; -} MASTER_INFO; - -static MASTER_INFO master_servers[MAX_MASTERSERVERS] = {{{0}}}; -static int needs_update = -1; - -int mastersrv_refresh_addresses() -{ - int i; - - if(needs_update != -1) - return 0; - - dbg_msg("engine/mastersrv", "refreshing master server addresses"); - - /* add lookup jobs */ - for(i = 0; i < MAX_MASTERSERVERS; i++) - engine_hostlookup(&master_servers[i].lookup, master_servers[i].hostname); - - needs_update = 1; - return 0; -} - -void mastersrv_update() -{ - int i; - - /* check if we need to update */ - if(needs_update != 1) - return; - needs_update = 0; - - for(i = 0; i < MAX_MASTERSERVERS; i++) - { - if(jobs_status(&master_servers[i].lookup.job) != JOBSTATUS_DONE) - needs_update = 1; - else - { - master_servers[i].addr = master_servers[i].lookup.addr; - master_servers[i].addr.port = 8300; - } - } - - if(!needs_update) - { - dbg_msg("engine/mastersrv", "saving addresses"); - mastersrv_save(); - } -} - -int mastersrv_refreshing() -{ - return needs_update; -} - -NETADDR mastersrv_get(int index) -{ - return master_servers[index].addr; -} - -const char *mastersrv_name(int index) -{ - return master_servers[index].hostname; -} - -void mastersrv_dump_servers() -{ - int i; - for(i = 0; i < MAX_MASTERSERVERS; i++) - { - dbg_msg("mastersrv", "#%d = %d.%d.%d.%d", i, - master_servers[i].addr.ip[0], master_servers[i].addr.ip[1], - master_servers[i].addr.ip[2], master_servers[i].addr.ip[3]); - } -} - -void mastersrv_default() -{ - int i; - mem_zero(master_servers, sizeof(master_servers)); - for(i = 0; i < MAX_MASTERSERVERS; i++) - sprintf(master_servers[i].hostname, "master%d.teeworlds.com", i+1); -} - -int mastersrv_load() -{ - LINEREADER lr; - IOHANDLE file; - int count = 0; - - /* try to open file */ - file = engine_openfile("masters.cfg", IOFLAG_READ); - if(!file) - return -1; - - linereader_init(&lr, file); - while(1) - { - MASTER_INFO info = {{0}}; - int ip[4]; - const char *line = linereader_get(&lr); - if(!line) - break; - - /* parse line */ - if(sscanf(line, "%s %d.%d.%d.%d", info.hostname, &ip[0], &ip[1], &ip[2], &ip[3]) == 5) - { - info.addr.ip[0] = (unsigned char)ip[0]; - info.addr.ip[1] = (unsigned char)ip[1]; - info.addr.ip[2] = (unsigned char)ip[2]; - info.addr.ip[3] = (unsigned char)ip[3]; - info.addr.port = 8300; - if(count != MAX_MASTERSERVERS) - { - master_servers[count] = info; - count++; - } - else - dbg_msg("engine/mastersrv", "warning: skipped master server '%s' due to limit of %d", line, MAX_MASTERSERVERS); - } - else - dbg_msg("engine/mastersrv", "warning: couldn't parse master server '%s'", line); - } - - io_close(file); - return 0; -} - -int mastersrv_save() -{ - IOHANDLE file; - int i; - - /* try to open file */ - file = engine_openfile("masters.cfg", IOFLAG_WRITE); - if(!file) - return -1; - - for(i = 0; i < MAX_MASTERSERVERS; i++) - { - char buf[1024]; - str_format(buf, sizeof(buf), "%s %d.%d.%d.%d\n", master_servers[i].hostname, - master_servers[i].addr.ip[0], master_servers[i].addr.ip[1], - master_servers[i].addr.ip[2], master_servers[i].addr.ip[3]); - - io_write(file, buf, strlen(buf)); - } - - io_close(file); - return 0; -} - - -int hostlookup_thread(void *user) -{ - HOSTLOOKUP *lookup = (HOSTLOOKUP *)user; - net_host_lookup(lookup->hostname, &lookup->addr, NETTYPE_IPV4); - return 0; -} - -void engine_hostlookup(HOSTLOOKUP *lookup, const char *hostname) -{ - str_copy(lookup->hostname, hostname, sizeof(lookup->hostname)); - jobs_add(&hostlookuppool, &lookup->job, hostlookup_thread, lookup); -} - -static int engine_find_datadir(char *argv0) -{ - /* 1) use provided data-dir override */ - if(datadir[0]) - { - if(fs_is_dir(datadir)) - return 0; - else - { - dbg_msg("engine/datadir", "specified data-dir '%s' does not exist", datadir); - return -1; - } - } - - /* 2) use data-dir in PWD if present */ - if(fs_is_dir("data")) - { - strcpy(datadir, "data"); - return 0; - } - - /* 3) use compiled-in data-dir if present */ - if (fs_is_dir(DATA_DIR)) - { - strcpy(datadir, DATA_DIR); - return 0; - } - - /* 4) check for usable path in argv[0] */ - { - unsigned int pos = strrchr(argv0, '/') - argv0; - - if (pos < sizeof(datadir)) - { - char basedir[sizeof(datadir)]; - strncpy(basedir, argv0, pos); - basedir[pos] = '\0'; - str_format(datadir, sizeof(datadir), "%s/data", basedir); - - if (fs_is_dir(datadir)) - return 0; - } - } - -#if defined(CONF_FAMILY_UNIX) - /* 5) check for all default locations */ - { - const char *sdirs[] = { - "/usr/share/teeworlds", - "/usr/local/share/teeworlds" - "/opt/teeworlds" - }; - const int sdirs_count = sizeof(sdirs) / sizeof(sdirs[0]); - - int i; - for (i = 0; i < sdirs_count; i++) - { - if (fs_is_dir(sdirs[i])) - { - strcpy(datadir, sdirs[i]); - return 0; - } - } - } -#endif - - /* no data-dir found */ - dbg_msg("engine/datadir", "warning no data directory found"); - return -1; -} diff --git a/src/engine/e_engine.h b/src/engine/e_engine.h deleted file mode 100644 index 94d49171..00000000 --- a/src/engine/e_engine.h +++ /dev/null @@ -1,51 +0,0 @@ -/* copyright (c) 2007 magnus auvinen, see licence.txt for more info */ - -#include "e_jobs.h" - -const char *engine_savepath(const char *filename, char *buffer, int max); -void engine_init(const char *appname); -void engine_parse_arguments(int argc, char **argv); - -int engine_config_write_start(); -void engine_config_write_line(const char *line); -void engine_config_write_stop(); - - -enum -{ - LISTDIRTYPE_SAVE=1, - LISTDIRTYPE_CURRENT=2, - LISTDIRTYPE_DATA=4, - LISTDIRTYPE_ALL = ~0 -}; - -void engine_listdir(int types, const char *path, FS_LISTDIR_CALLBACK cb, void *user); -IOHANDLE engine_openfile(const char *filename, int flags); -void engine_getpath(char *buffer, int buffer_size, const char *filename, int flags); - -int engine_stress(float probability); - -typedef struct HOSTLOOKUP -{ - JOB job; - char hostname[128]; - NETADDR addr; -} HOSTLOOKUP; - -void engine_hostlookup(HOSTLOOKUP *lookup, const char *hostname); - -enum -{ - MAX_MASTERSERVERS=4 -}; - -void mastersrv_default(); -int mastersrv_load(); -int mastersrv_save(); - -int mastersrv_refresh_addresses(); -void mastersrv_update(); -int mastersrv_refreshing(); -void mastersrv_dump_servers(); -NETADDR mastersrv_get(int index); -const char *mastersrv_name(int index); diff --git a/src/engine/e_huffman.cpp b/src/engine/e_huffman.cpp deleted file mode 100644 index 43914010..00000000 --- a/src/engine/e_huffman.cpp +++ /dev/null @@ -1,276 +0,0 @@ -#include <memory.h> /* memset */ -#include "e_huffman.h" - -typedef struct HUFFMAN_CONSTRUCT_NODE -{ - unsigned short node_id; - int frequency; -} HUFFMAN_CONSTRUCT_NODE; - -static void huffman_setbits_r(HUFFMAN_STATE *huff, HUFFMAN_NODE *node, int bits, int depth) -{ - if(node->leafs[1] != 0xffff) - huffman_setbits_r(huff, &huff->nodes[node->leafs[1]], bits|(1<<depth), depth+1); - if(node->leafs[0] != 0xffff) - huffman_setbits_r(huff, &huff->nodes[node->leafs[0]], bits, depth+1); - - if(node->num_bits) - { - node->bits = bits; - node->num_bits = depth; - } -} - -/* TODO: this should be something faster, but it's enough for now */ -void bubblesort(HUFFMAN_CONSTRUCT_NODE **list, int size) -{ - int i, changed = 1; - HUFFMAN_CONSTRUCT_NODE *temp; - - while(changed) - { - changed = 0; - for(i = 0; i < size-1; i++) - { - if(list[i]->frequency < list[i+1]->frequency) - { - temp = list[i]; - list[i] = list[i+1]; - list[i+1] = temp; - changed = 1; - } - } - } -} - -static void huffman_construct_tree(HUFFMAN_STATE *huff, const unsigned *frequencies) -{ - HUFFMAN_CONSTRUCT_NODE nodes_left_storage[HUFFMAN_MAX_SYMBOLS]; - HUFFMAN_CONSTRUCT_NODE *nodes_left[HUFFMAN_MAX_SYMBOLS]; - int num_nodes_left = HUFFMAN_MAX_SYMBOLS; - int i; - - /* add the symbols */ - for(i = 0; i < HUFFMAN_MAX_SYMBOLS; i++) - { - huff->nodes[i].num_bits = -1; - huff->nodes[i].symbol = i; - huff->nodes[i].leafs[0] = -1; - huff->nodes[i].leafs[1] = -1; - - if(i == HUFFMAN_EOF_SYMBOL) - nodes_left_storage[i].frequency = 1; - else - nodes_left_storage[i].frequency = frequencies[i]; - nodes_left_storage[i].node_id = i; - nodes_left[i] = &nodes_left_storage[i]; - - } - huff->num_nodes = HUFFMAN_MAX_SYMBOLS; - - /* construct the table */ - while(num_nodes_left > 1) - { - /* we can't rely on stdlib's qsort for this, it can generate different results on different implementations */ - bubblesort(nodes_left, num_nodes_left); - - huff->nodes[huff->num_nodes].num_bits = 0; - huff->nodes[huff->num_nodes].leafs[0] = nodes_left[num_nodes_left-1]->node_id; - huff->nodes[huff->num_nodes].leafs[1] = nodes_left[num_nodes_left-2]->node_id; - nodes_left[num_nodes_left-2]->node_id = huff->num_nodes; - nodes_left[num_nodes_left-2]->frequency = nodes_left[num_nodes_left-1]->frequency + nodes_left[num_nodes_left-2]->frequency; - - huff->num_nodes++; - num_nodes_left--; - } - - /* set start node */ - huff->start_node = &huff->nodes[huff->num_nodes-1]; - - /* build symbol bits */ - huffman_setbits_r(huff, huff->start_node, 0, 0); -} - -void huffman_init(HUFFMAN_STATE *huff, const unsigned *frequencies) -{ - int i; - - /* make sure to cleanout every thing */ - memset(huff, 0, sizeof(HUFFMAN_STATE)); - - /* construct the tree */ - huffman_construct_tree(huff, frequencies); - - /* build decode LUT */ - for(i = 0; i < HUFFMAN_LUTSIZE; i++) - { - unsigned bits = i; - int k; - HUFFMAN_NODE *node = huff->start_node; - for(k = 0; k < HUFFMAN_LUTBITS; k++) - { - node = &huff->nodes[node->leafs[bits&1]]; - bits >>= 1; - - if(!node) - break; - - if(node->num_bits) - { - huff->decode_lut[i] = node; - break; - } - } - - if(k == HUFFMAN_LUTBITS) - huff->decode_lut[i] = node; - } - -} - -/*****************************************************************/ -int huffman_compress(HUFFMAN_STATE *huff, const void *input, int input_size, void *output, int output_size) -{ - /* this macro loads a symbol for a byte into bits and bitcount */ -#define HUFFMAN_MACRO_LOADSYMBOL(sym) \ - bits |= huff->nodes[sym].bits << bitcount; \ - bitcount += huff->nodes[sym].num_bits; - - /* this macro writes the symbol stored in bits and bitcount to the dst pointer */ -#define HUFFMAN_MACRO_WRITE() \ - while(bitcount >= 8) \ - { \ - *dst++ = (unsigned char)(bits&0xff); \ - if(dst == dst_end) \ - return -1; \ - bits >>= 8; \ - bitcount -= 8; \ - } - - /* setup buffer pointers */ - const unsigned char *src = (const unsigned char *)input; - const unsigned char *src_end = src + input_size; - unsigned char *dst = (unsigned char *)output; - unsigned char *dst_end = dst + output_size; - - /* symbol variables */ - unsigned bits = 0; - unsigned bitcount = 0; - - /* make sure that we have data that we want to compress */ - if(input_size) - { - /* {A} load the first symbol */ - int symbol = *src++; - - while(src != src_end) - { - /* {B} load the symbol */ - HUFFMAN_MACRO_LOADSYMBOL(symbol) - - /* {C} fetch next symbol, this is done here because it will reduce dependency in the code */ - symbol = *src++; - - /* {B} write the symbol loaded at */ - HUFFMAN_MACRO_WRITE() - } - - /* write the last symbol loaded from {C} or {A} in the case of only 1 byte input buffer */ - HUFFMAN_MACRO_LOADSYMBOL(symbol) - HUFFMAN_MACRO_WRITE() - } - - /* write EOF symbol */ - HUFFMAN_MACRO_LOADSYMBOL(HUFFMAN_EOF_SYMBOL) - HUFFMAN_MACRO_WRITE() - - /* write out the last bits */ - *dst++ = bits; - - /* return the size of the output */ - return (int)(dst - (const unsigned char *)output); - - /* remove macros */ -#undef HUFFMAN_MACRO_LOADSYMBOL -#undef HUFFMAN_MACRO_WRITE -} - -/*****************************************************************/ -int huffman_decompress(HUFFMAN_STATE *huff, const void *input, int input_size, void *output, int output_size) -{ - /* setup buffer pointers */ - unsigned char *dst = (unsigned char *)output; - unsigned char *src = (unsigned char *)input; - unsigned char *dst_end = dst + output_size; - unsigned char *src_end = src + input_size; - - unsigned bits = 0; - unsigned bitcount = 0; - - HUFFMAN_NODE *eof = &huff->nodes[HUFFMAN_EOF_SYMBOL]; - HUFFMAN_NODE *node = 0; - - while(1) - { - /* {A} try to load a node now, this will reduce dependency at location {D} */ - node = 0; - if(bitcount >= HUFFMAN_LUTBITS) - node = huff->decode_lut[bits&HUFFMAN_LUTMASK]; - - /* {B} fill with new bits */ - while(bitcount < 24 && src != src_end) - { - bits |= (*src++) << bitcount; - bitcount += 8; - } - - /* {C} load symbol now if we didn't that earlier at location {A} */ - if(!node) - node = huff->decode_lut[bits&HUFFMAN_LUTMASK]; - - /* {D} check if we hit a symbol already */ - if(node->num_bits) - { - /* remove the bits for that symbol */ - bits >>= node->num_bits; - bitcount -= node->num_bits; - } - else - { - /* remove the bits that the lut checked up for us */ - bits >>= HUFFMAN_LUTBITS; - bitcount -= HUFFMAN_LUTBITS; - - /* walk the tree bit by bit */ - while(1) - { - /* traverse tree */ - node = &huff->nodes[node->leafs[bits&1]]; - - /* remove bit */ - bitcount--; - bits >>= 1; - - /* check if we hit a symbol */ - if(node->num_bits) - break; - - /* no more bits, decoding error */ - if(bitcount == 0) - return -1; - } - } - - /* check for eof */ - if(node == eof) - break; - - /* output character */ - if(dst == dst_end) - return -1; - *dst++ = node->symbol; - } - - /* return the size of the decompressed buffer */ - return (int)(dst - (const unsigned char *)output); -} diff --git a/src/engine/e_huffman.h b/src/engine/e_huffman.h deleted file mode 100644 index 635c74a1..00000000 --- a/src/engine/e_huffman.h +++ /dev/null @@ -1,83 +0,0 @@ -#ifndef __HUFFMAN_HEADER__ -#define __HUFFMAN_HEADER__ - -enum -{ - HUFFMAN_EOF_SYMBOL = 256, - - HUFFMAN_MAX_SYMBOLS=HUFFMAN_EOF_SYMBOL+1, - HUFFMAN_MAX_NODES=HUFFMAN_MAX_SYMBOLS*2-1, - - HUFFMAN_LUTBITS = 10, - HUFFMAN_LUTSIZE = (1<<HUFFMAN_LUTBITS), - HUFFMAN_LUTMASK = (HUFFMAN_LUTSIZE-1) -}; - -typedef struct HUFFMAN_NODE -{ - /* symbol */ - unsigned bits; - unsigned num_bits; - - /* don't use pointers for this. shorts are smaller so we can fit more data into the cache */ - unsigned short leafs[2]; - - /* what the symbol represents */ - unsigned char symbol; -} HUFFMAN_NODE; - -typedef struct HUFFMAN_STATE -{ - HUFFMAN_NODE nodes[HUFFMAN_MAX_NODES]; - HUFFMAN_NODE *decode_lut[HUFFMAN_LUTSIZE]; - HUFFMAN_NODE *start_node; - int num_nodes; -} HUFFMAN_STATE; - -/* - Function: huffman_init - Inits the compressor/decompressor. - - Parameters: - huff - Pointer to the state to init - frequencies - A pointer to an array of 256 entries of the frequencies of the bytes - - Remarks: - - Does no allocation what so ever. - - You don't have to call any cleanup functions when you are done with it -*/ -void huffman_init(HUFFMAN_STATE *huff, const unsigned *frequencies); - -/* - Function: huffman_compress - Compresses a buffer and outputs a compressed buffer. - - Parameters: - huff - Pointer to the huffman state - input - Buffer to compress - input_size - Size of the buffer to compress - output - Buffer to put the compressed data into - output_size - Size of the output buffer - - Returns: - Returns the size of the compressed data. Negative value on failure. -*/ -int huffman_compress(HUFFMAN_STATE *huff, const void *input, int input_size, void *output, int output_size); - -/* - Function: huffman_decompress - Decompresses a buffer - - Parameters: - huff - Pointer to the huffman state - input - Buffer to decompress - input_size - Size of the buffer to decompress - output - Buffer to put the uncompressed data into - output_size - Size of the output buffer - - Returns: - Returns the size of the uncompressed data. Negative value on failure. -*/ -int huffman_decompress(HUFFMAN_STATE *huff, const void *input, int input_size, void *output, int output_size); - -#endif /* __HUFFMAN_HEADER__ */ diff --git a/src/engine/e_if_client.h b/src/engine/e_if_client.h deleted file mode 100644 index 86c6f5fc..00000000 --- a/src/engine/e_if_client.h +++ /dev/null @@ -1,579 +0,0 @@ -/* copyright (c) 2007 magnus auvinen, see licence.txt for more info */ -#ifndef ENGINE_IF_CLIENT_H -#define ENGINE_IF_CLIENT_H - -/* - Title: Client Interface -*/ - -/* - Section: Constants -*/ - -enum -{ - /* Constants: Client States - CLIENTSTATE_OFFLINE - The client is offline. - CLIENTSTATE_CONNECTING - The client is trying to connect to a server. - CLIENTSTATE_LOADING - The client has connected to a server and is loading resources. - CLIENTSTATE_ONLINE - The client is connected to a server and running the game. - CLIENTSTATE_DEMOPLAYBACK - The client is playing a demo - CLIENTSTATE_QUITING - The client is quiting. - */ - CLIENTSTATE_OFFLINE=0, - CLIENTSTATE_CONNECTING, - CLIENTSTATE_LOADING, - CLIENTSTATE_ONLINE, - CLIENTSTATE_DEMOPLAYBACK, - CLIENTSTATE_QUITING, - - /* Constants: Image Formats - IMG_AUTO - Lets the engine choose the format. - IMG_RGB - 8-Bit uncompressed RGB - IMG_RGBA - 8-Bit uncompressed RGBA - IMG_ALPHA - 8-Bit uncompressed alpha - */ - IMG_AUTO=-1, - IMG_RGB=0, - IMG_RGBA=1, - IMG_ALPHA=2, - - /* Constants: Texture Loading Flags - TEXLOAD_NORESAMPLE - Prevents the texture from any resampling - */ - TEXLOAD_NORESAMPLE=1, - - /* Constants: Server Browser Sorting - BROWSESORT_NAME - Sort by name. - BROWSESORT_PING - Sort by ping. - BROWSESORT_MAP - Sort by map - BROWSESORT_GAMETYPE - Sort by game type. DM, TDM etc. - BROWSESORT_PROGRESSION - Sort by progression. - BROWSESORT_NUMPLAYERS - Sort after how many players there are on the server. - */ - BROWSESORT_NAME = 0, - BROWSESORT_PING, - BROWSESORT_MAP, - BROWSESORT_GAMETYPE, - BROWSESORT_PROGRESSION, - BROWSESORT_NUMPLAYERS, - - BROWSEQUICK_SERVERNAME=1, - BROWSEQUICK_PLAYERNAME=2, - BROWSEQUICK_MAPNAME=4, - - BROWSETYPE_INTERNET = 0, - BROWSETYPE_LAN = 1, - BROWSETYPE_FAVORITES = 2 -}; - -/* - Section: Structures -*/ - -/* - Structure: SERVER_INFO_PLAYER -*/ -typedef struct -{ - char name[48]; - int score; -} SERVER_INFO_PLAYER; - -/* - Structure: SERVER_INFO -*/ -typedef struct -{ - int sorted_index; - int server_index; - - NETADDR netaddr; - - int quicksearch_hit; - - int progression; - int max_players; - int num_players; - int flags; - int favorite; - int latency; /* in ms */ - char gametype[16]; - char name[64]; - char map[32]; - char version[32]; - char address[24]; - SERVER_INFO_PLAYER players[16]; -} SERVER_INFO; - -/* - Section: Functions -*/ - -/********************************************************************************** - Group: Time -**********************************************************************************/ -/* - Function: client_tick - Returns the tick of the current snapshot. -*/ -int client_tick(); - -/* - Function: client_prevtick - Returns the tick of the previous snapshot. -*/ -int client_prevtick(); - -/* - Function: client_intratick - Returns the current intratick. - - Remarks: - The intratick is how far gone the time is from the previous snapshot to the current. - 0.0 means that it on the previous snapshot. 0.5 means that it's halfway to the current, - and 1.0 means that it is on the current snapshot. It can go beyond 1.0 which means that - the client has started to extrapolate due to lack of data from the server. - - See Also: - <client_tick> -*/ -float client_intratick(); - -/* - Function: client_predtick - Returns the current predicted tick. -*/ -int client_predtick(); - -/* - Function: client_predintratick - Returns the current preticted intra tick. - - Remarks: - This is the same as <client_intratick> but for the current predicted tick and - previous predicted tick. - - See Also: - <client_intratick> -*/ -float client_predintratick(); - -/* - Function: client_ticktime - TODO - - Arguments: - arg1 - desc - - Returns: - - See Also: - <other_func> -*/ -float client_ticktime(); - -/* - Function: client_tickspeed - Returns how many ticks per second the client is doing. - - Remarks: - This will be the same as the server tick speed. -*/ -int client_tickspeed(); - -/* - Function: client_frametime - Returns how long time the last frame took to process. -*/ -float client_frametime(); - -/* - Function: client_localtime - Returns the clients local time. - - Remarks: - The local time is set to 0 when the client starts and when - it connects to a server. Can be used for client side effects. -*/ -float client_localtime(); - -/********************************************************************************** - Group: Server Browser -**********************************************************************************/ - -/* - Function: client_serverbrowse_refresh - Issues a refresh of the server browser. - - Arguments: - type - What type of servers to browse, internet, lan or favorites. - - Remarks: - This will cause a broadcast on the local network if the lan argument is set. - Otherwise it call ask all the master servers for their servers lists. -*/ -void client_serverbrowse_refresh(int type); - -/* - Function: client_serverbrowse_sorted_get - Returns server info from the sorted list. - - Arguments: - index - Zero based index into the sorted list. - - See Also: - <client_serverbrowse_sorted_num> -*/ -SERVER_INFO *client_serverbrowse_sorted_get(int index); - -/* - Function: client_serverbrowse_sorted_num - Returns how many servers there are in the sorted list. - - See Also: - <client_serverbrowse_sorted_get> -*/ -int client_serverbrowse_sorted_num(); - -/* - Function: client_serverbrowse_get - TODO - - Arguments: - arg1 - desc - - Returns: - - See Also: - <other_func> -*/ -SERVER_INFO *client_serverbrowse_get(int index); - -/* - Function: client_serverbrowse_num - TODO - - Arguments: - arg1 - desc - - Returns: - - See Also: - <other_func> -*/ -int client_serverbrowse_num(); - -/* - Function: client_serverbrowse_num_requests - TODO - - Arguments: - arg1 - desc - - Returns: - - See Also: - <other_func> -*/ -int client_serverbrowse_num_requests(); - -/* - Function: client_serverbrowse_update - TODO - - Arguments: - arg1 - desc - - Returns: - - See Also: - <other_func> -*/ -void client_serverbrowse_update(); - -/* - Function: client_serverbrowse_lan - TODO - - Arguments: - arg1 - desc - - Returns: - - See Also: - <other_func> -*/ -int client_serverbrowse_lan(); - -/* - Function: client_serverbrowse_isfavorite - Asks the server browser is a netaddr is in the favorite list - - Arguments: - addr - Address of the server to ask about. - - Returns: - Returns zero if it's not in the list, non-zero if it is. -*/ -int client_serverbrowse_isfavorite(NETADDR addr); - -/* - Function: client_serverbrowse_addfavorite - Adds a server to the favorite list - - Arguments: - addr - Address of the favorite server. -*/ -void client_serverbrowse_addfavorite(NETADDR addr); - -/* - Function: client_serverbrowse_removefavorite - Removes a server to the favorite list - - Arguments: - addr - Address of the favorite server. -*/ -void client_serverbrowse_removefavorite(NETADDR addr); - -/********************************************************************************** - Group: Actions -**********************************************************************************/ - - -/* - Function: client_connect - Connects to a server at the specified address. - - Arguments: - address - Address of the server to connect to. - - See Also: - <client_disconnect> -*/ -void client_connect(const char *address); - -/* - Function: client_disconnect - Disconnects from the current server. - - Remarks: - Does nothing if not connected to a server. - - See Also: - <client_connect, client_quit> -*/ -void client_disconnect(); - -/* - Function: client_quit - Tells to client to shutdown. - - See Also: - <client_disconnect> -*/ -void client_quit(); - -void client_entergame(); - -/* - Function: client_rcon - Sends a command to the server to execute on it's console. - - Arguments: - cmd - The command to send. - - Remarks: - The client must have the correct rcon password to connect. - - See Also: - <client_rcon_auth, client_rcon_authed> -*/ -void client_rcon(const char *cmd); - -/* - Function: client_rcon_auth - TODO - - Arguments: - arg1 - desc - - Returns: - - See Also: - <client_rcon, client_rcon_authed> -*/ -void client_rcon_auth(const char *name, const char *password); - -/* - Function: client_rcon_authed - TODO - - Arguments: - arg1 - desc - - Returns: - - See Also: - <client_rcon, client_rcon_auth> -*/ -int client_rcon_authed(); - -/********************************************************************************** - Group: Other -**********************************************************************************/ -/* - Function: client_latestversion - Returns 0 if there's no version difference - - Arguments: - arg1 - desc - - Returns: - - See Also: - <other_func> -*/ -const char *client_latestversion(); - -/* - Function: client_get_input - TODO - - Arguments: - arg1 - desc - - Returns: - - See Also: - <other_func> -*/ -int *client_get_input(int tick); - -/* - Function: client_direct_input - TODO - - Arguments: - arg1 - desc - - Returns: - - See Also: - <other_func> -*/ -void client_direct_input(int *input, int size); - -/* - Function: client_error_string - TODO - - Arguments: - arg1 - desc - - Returns: - - See Also: - <other_func> -*/ -const char *client_error_string(); - -/* - Function: client_connection_problems - Returns 1 if the client is connection problems. - - Remarks: - Connections problems usually means that the client havn't recvived any data - from the server in a while. -*/ -int client_connection_problems(); - -/* - Function: client_state - Returns the state of the client. - - See Also: - <Client States> -*/ -int client_state(); - -/* - Function: client_mapdownload_amount - TODO - - Arguments: - arg1 - desc - - Returns: - - See Also: - <other_func> -*/ -int client_mapdownload_amount(); - -/* - Function: client_mapdownload_totalsize - TODO - - Arguments: - arg1 - desc - - Returns: - - See Also: - <other_func> -*/ -int client_mapdownload_totalsize(); - -/* - Function: client_save_line - TODO - - Arguments: - arg1 - desc - - Returns: - - See Also: - <other_func> -*/ -void client_save_line(const char *line); - - - - - - -enum -{ - BROWSESET_MASTER_ADD=1, - BROWSESET_FAV_ADD, - BROWSESET_TOKEN, - BROWSESET_OLD_INTERNET, - BROWSESET_OLD_LAN -}; - -void client_serverbrowse_set(NETADDR *addr, int type, int token, SERVER_INFO *info); - -int client_serverbrowse_refreshingmasters(); - - -typedef struct DEMOPLAYBACK_INFO -{ - int first_tick; - int last_tick; - int current_tick; - int paused; - float speed; -} DEMOPLAYBACK_INFO; - -const char *client_demoplayer_play(const char *filename); -const DEMOPLAYBACK_INFO *client_demoplayer_getinfo(); -void client_demoplayer_setpos(float percent); -void client_demoplayer_setpause(int paused); -void client_demoplayer_setspeed(float speed); -const char *client_user_directory(); -void client_serverinfo(SERVER_INFO *serverinfo); -void client_serverinfo_request(); -void client_serverbrowse_request(NETADDR *addr); -#endif diff --git a/src/engine/e_if_gfx.h b/src/engine/e_if_gfx.h deleted file mode 100644 index 3d048e85..00000000 --- a/src/engine/e_if_gfx.h +++ /dev/null @@ -1,674 +0,0 @@ -/* copyright (c) 2007 magnus auvinen, see licence.txt for more info */ -#ifndef ENGINE_IF_GFX_H -#define ENGINE_IF_GFX_H - -/* - Title: Graphics -*/ - -/* - Section: Structures -*/ - -/* - Structure: FONT -*/ -struct FONT; - -/* - Structure: IMAGE_INFO -*/ -typedef struct -{ - /* Variable: width - Contains the width of the image */ - int width; - - /* Variable: height - Contains the height of the image */ - int height; - - /* Variable: format - Contains the format of the image. See <Image Formats> for more information. */ - int format; - - /* Variable: data - Pointer to the image data. */ - void *data; -} IMAGE_INFO; - -/* - Structure: VIDEO_MODE -*/ -typedef struct -{ - int width, height; - int red, green, blue; -} VIDEO_MODE; - -/* - Section: Functions -*/ - -/* - Group: Quads -*/ - -/* - Function: gfx_quads_begin - Begins a quad drawing session. - - Remarks: - This functions resets the rotation, color and subset. - End the session by using <gfx_quads_end>. - You can't change texture or blending mode during a session. - - See Also: - <gfx_quads_end> -*/ -void gfx_quads_begin(); - -/* - Function: gfx_quads_end - Ends a quad session. - - See Also: - <gfx_quads_begin> -*/ -void gfx_quads_end(); - -/* - Function: gfx_quads_setrotation - Sets the rotation to use when drawing a quad. - - Arguments: - angle - Angle in radians. - - Remarks: - The angle is reset when <gfx_quads_begin> is called. -*/ -void gfx_quads_setrotation(float angle); - -/* - Function: gfx_quads_setsubset - Sets the uv coordinates to use. - - Arguments: - tl_u - Top-left U value. - tl_v - Top-left V value. - br_u - Bottom-right U value. - br_v - Bottom-right V value. - - Remarks: - O,0 is top-left of the texture and 1,1 is bottom-right. - The color is reset when <gfx_quads_begin> is called. -*/ -void gfx_quads_setsubset(float tl_u, float tl_v, float br_u, float br_v); - -/* - Function: gfx_quads_setsubset_free - TODO - - Arguments: - arg1 - desc - - Returns: - - See Also: - <other_func> -*/ -void gfx_quads_setsubset_free( - float x0, float y0, - float x1, float y1, - float x2, float y2, - float x3, float y3); - -/* - Function: gfx_quads_drawTL - Draws a quad by specifying the top-left point. - - Arguments: - x - X coordinate of the top-left corner. - y - Y coordinate of the top-left corner. - width - Width of the quad. - height - Height of the quad. - - Remarks: - Rotation still occurs from the center of the quad. - You must call <gfx_quads_begin> before calling this function. - - See Also: - <gfx_quads_draw, gfx_quads_draw_freeform> -*/ -void gfx_quads_drawTL(float x, float y, float width, float height); - -/* - Function: gfx_quads_draw - Draws a quad by specifying the center point. - - Arguments: - x - X coordinate of the center. - y - Y coordinate of the center. - width - Width of the quad. - height - Height of the quad. - - Remarks: - You must call <gfx_quads_begin> before calling this function. - - See Also: - <gfx_quads_drawTL, gfx_quads_draw_freeform> -*/ -void gfx_quads_draw(float x, float y, float w, float h); - -/* - Function: gfx_quads_draw_freeform - Draws a quad by specifying the corner points. - - Arguments: - x0, y0 - Coordinates of the upper left corner. - x1, y1 - Coordinates of the upper right corner. - x2, y2 - Coordinates of the lower left corner. // TODO: DOUBLE CHECK!!! - x3, y3 - Coordinates of the lower right corner. // TODO: DOUBLE CHECK!!! - - See Also: - <gfx_quads_draw, gfx_quads_drawTL> -*/ -void gfx_quads_draw_freeform( - float x0, float y0, - float x1, float y1, - float x2, float y2, - float x3, float y3); - -/* - Function: gfx_quads_text - TODO - - Arguments: - arg1 - desc - - Returns: - - See Also: - <other_func> -*/ -void gfx_quads_text(float x, float y, float size, float r, float g, float b, float a, const char *text); - -/* - Group: Lines -*/ - -/* - Function: gfx_lines_begin - TODO - - Arguments: - arg1 - desc - - Returns: - - See Also: - <other_func> -*/ -void gfx_lines_begin(); - -/* - Function: gfx_lines_draw - TODO - - Arguments: - arg1 - desc - - Returns: - - See Also: - <other_func> -*/ -void gfx_lines_draw(float x0, float y0, float x1, float y1); - -/* - Function: gfx_minimize - Minimizes the window. - - Arguments: - arg1 - desc - - Returns: - - See Also: - <other_func> -*/ -void gfx_minimize(); - -/* - Function: gfx_minimize - Maximizes the window. - - Arguments: - arg1 - desc - - Returns: - - See Also: - <other_func> -*/ -void gfx_maximize(); - -/* - Function: gfx_lines_end - TODO - - Arguments: - arg1 - desc - - Returns: - - See Also: - <other_func> -*/ -void gfx_lines_end(); - -/* - Group: Text -*/ - - -/* - Function: gfx_text - TODO - - Arguments: - arg1 - desc - - Returns: - returns the number of lines written - See Also: - <other_func> -*/ -void gfx_text(void *font, float x, float y, float size, const char *text, int max_width); - -/* - Function: gfx_text_width - TODO - - Arguments: - arg1 - desc - - Returns: - - See Also: - <other_func> -*/ -float gfx_text_width(void *font, float size, const char *text, int length); - -/* - Function: gfx_text_color - TODO - - Arguments: - arg1 - desc - - Returns: - - See Also: - <other_func> -*/ -void gfx_text_color(float r, float g, float b, float a); - -/* - Function: gfx_text_set_default_font - TODO - - Arguments: - arg1 - desc - - Returns: - - See Also: - <other_func> -*/ -void gfx_text_set_default_font(struct FONT *font); - -/* - Group: Other -*/ - -/* - Function: gfx_get_video_modes - Fetches a list of all the available video modes. - - Arguments: - list - An array to recive the modes. Must be able to contain maxcount number of modes. - maxcount - The maximum number of modes to fetch. - - Returns: - The number of video modes that was fetched. -*/ -int gfx_get_video_modes(VIDEO_MODE *list, int maxcount); - -/* image loaders */ - -/* - Function: gfx_load_png - Loads a PNG image from disk. - - Arguments: - img - Pointer to an structure to be filled out with the image information. - filename - Filename of the image to load. - - Returns: - Returns non-zero on success and zero on error. - - Remarks: - The caller are responsible for cleaning up the allocated memory in the IMAGE_INFO structure. - - See Also: - <gfx_load_texture> -*/ -int gfx_load_png(IMAGE_INFO *img, const char *filename); - -/* textures */ -/* - Function: gfx_load_texture - Loads a texture from a file. TGA and PNG supported. - - Arguments: - filename - Null terminated string to the file to load. - store_format - What format to store on gfx card as. - flags - controls how the texture is uploaded - - Returns: - An ID to the texture. -1 on failure. - - See Also: - <gfx_unload_texture, gfx_load_png> -*/ -int gfx_load_texture(const char *filename, int store_format, int flags); - -/* - Function: gfx_load_texture_raw - Loads a texture from memory. - - Arguments: - w - Width of the texture. - h - Height of the texture. - data - Pointer to the pixel data. - format - Format of the pixel data. - store_format - The format to store the texture on the graphics card. - flags - controls how the texture is uploaded - - Returns: - An ID to the texture. -1 on failure. - - Remarks: - The pixel data should be in RGBA format with 8 bit per component. - So the total size of the data should be w*h*4. - - See Also: - <gfx_unload_texture> -*/ -int gfx_load_texture_raw(int w, int h, int format, const void *data, int store_format, int flags); - -/* - Function: gfx_texture_set - Sets the active texture. - - Arguments: - id - ID to the texture to set. -*/ -void gfx_texture_set(int id); - -/* - Function: gfx_unload_texture - Unloads a texture. - - Arguments: - id - ID to the texture to unload. - - See Also: - <gfx_load_texture_tga>, <gfx_load_texture_raw> - - Remarks: - NOT IMPLEMENTED -*/ -int gfx_unload_texture(int id); - -/* - Function: gfx_clear - Clears the screen with the specified color. - - Arguments: - r - Red component. - g - Green component. - b - Red component. - - Remarks: - The value of the components are given in 0.0 - 1.0 ranges. -*/ -void gfx_clear(float r, float g, float b); - -/* - Function: gfx_screenaspect - Returns the aspect ratio between width and height. - - See Also: - <gfx_screenwidth>, <gfx_screenheight> -*/ -float gfx_screenaspect(); - -/* - Function: gfx_screenwidth - Returns the screen width. - - See Also: - <gfx_screenheight> -*/ -int gfx_screenwidth(); - -/* - Function: gfx_screenheight - Returns the screen height. - - See Also: - <gfx_screenwidth> -*/ -int gfx_screenheight(); - -/* - Function: gfx_mapscreen - Specifies the coordinate system for the screen. - - Arguments: - tl_x - Top-left X - tl_y - Top-left Y - br_x - Bottom-right X - br_y - Bottom-right y -*/ -void gfx_mapscreen(float tl_x, float tl_y, float br_x, float br_y); - -/* - Function: gfx_blend_normal - Set the active blending mode to normal (src, 1-src). - - Remarks: - This must be used before calling <gfx_quads_begin>. - This is equal to glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA). - - See Also: - <gfx_blend_additive,gfx_blend_none> -*/ -void gfx_blend_normal(); - -/* - Function: gfx_blend_additive - Set the active blending mode to additive (src, one). - - Remarks: - This must be used before calling <gfx_quads_begin>. - This is equal to glBlendFunc(GL_SRC_ALPHA, GL_ONE). - - See Also: - <gfx_blend_normal,gfx_blend_none> -*/ -void gfx_blend_additive(); - -/* - Function: gfx_blend_none - Disables blending - - Remarks: - This must be used before calling <gfx_quads_begin>. - - See Also: - <gfx_blend_normal,gfx_blend_additive> -*/ -void gfx_blend_none(); - - -/* - Function: gfx_setcolorvertex - Sets the color of a vertex. - - Arguments: - i - Index to the vertex. - r - Red value. - g - Green value. - b - Blue value. - a - Alpha value. - - Remarks: - The color values are from 0.0 to 1.0. - The color is reset when <gfx_quads_begin> is called. -*/ -void gfx_setcolorvertex(int i, float r, float g, float b, float a); - -/* - Function: gfx_setcolor - Sets the color of all the vertices. - - Arguments: - r - Red value. - g - Green value. - b - Blue value. - a - Alpha value. - - Remarks: - The color values are from 0.0 to 1.0. - The color is reset when <gfx_quads_begin> is called. -*/ -void gfx_setcolor(float r, float g, float b, float a); - -/* - Function: gfx_getscreen - TODO - - Arguments: - arg1 - desc - - Returns: - - See Also: - <other_func> -*/ -void gfx_getscreen(float *tl_x, float *tl_y, float *br_x, float *br_y); - -/* - Function: gfx_memory_usage - TODO - - Arguments: - arg1 - desc - - Returns: - - See Also: - <other_func> -*/ -int gfx_memory_usage(); - -/* - Function: gfx_screenshot - TODO - - Arguments: - filename - desc - - Returns: - - See Also: - <other_func> -*/ -void gfx_screenshot(); - -/* - Function: gfx_screenshot_direct - TODO - - Arguments: - filename - desc - - Returns: - - See Also: - <other_func> -*/ -void gfx_screenshot_direct(const char *filename); - -/* - Function: gfx_clip_enable - TODO - - Arguments: - arg1 - desc - - Returns: - - See Also: - <other_func> -*/ -void gfx_clip_enable(int x, int y, int w, int h); - -/* - Function: gfx_clip_disable - TODO - - Arguments: - arg1 - desc - - Returns: - - See Also: - <other_func> -*/ -void gfx_clip_disable(); - - -enum -{ - TEXTFLAG_RENDER=1, - TEXTFLAG_ALLOW_NEWLINE=2, - TEXTFLAG_STOP_AT_END=4 -}; - -typedef struct -{ - int flags; - int line_count; - int charcount; - - float start_x; - float start_y; - float line_width; - float x, y; - - struct FONT *font; - float font_size; -} TEXT_CURSOR; - -void gfx_text_set_cursor(TEXT_CURSOR *cursor, float x, float y, float font_size, int flags); -void gfx_text_ex(TEXT_CURSOR *cursor, const char *text, int length); - - -struct FONT *gfx_font_load(const char *filename); -void gfx_font_destroy(struct FONT *font); - -#endif diff --git a/src/engine/e_if_inp.h b/src/engine/e_if_inp.h deleted file mode 100644 index 919ac0eb..00000000 --- a/src/engine/e_if_inp.h +++ /dev/null @@ -1,244 +0,0 @@ -/* copyright (c) 2007 magnus auvinen, see licence.txt for more info */ -#ifndef ENGINE_IF_INP_H -#define ENGINE_IF_INP_H - -/* - Section: Input -*/ - - -/* - Structure: INPUT_EVENT -*/ -enum -{ - INPFLAG_PRESS=1, - INPFLAG_RELEASE=2, - INPFLAG_REPEAT=4 -}; - -typedef struct -{ - int flags; - int unicode; - int key; -} INPUT_EVENT; - -/* - Function: inp_mouse_relative - Fetches the mouse movements. - - Arguments: - x - Pointer to the variable that should get the X movement. - y - Pointer to the variable that should get the Y movement. -*/ -void inp_mouse_relative(int *x, int *y); - -/* - Function: inp_mouse_scroll - TODO - - Arguments: - arg1 - desc - - Returns: - - See Also: - <other_func> -*/ -int inp_mouse_scroll(); - -/* - Function: inp_key_pressed - Checks if a key is pressed. - - Arguments: - key - Index to the key to check - - Returns: - Returns 1 if the button is pressed, otherwise 0. - - Remarks: - Check keys.h for the keys. -*/ -int inp_key_pressed(int key); - - -/* input */ -/* - Function: inp_key_was_pressed - TODO - - Arguments: - arg1 - desc - - Returns: - - See Also: - <other_func> -*/ -int inp_key_was_pressed(int key); - -/* - Function: inp_key_down - TODO - - Arguments: - arg1 - desc - - Returns: - - See Also: - <other_func> -*/ -int inp_key_down(int key); - - -/* - Function: inp_num_events - TODO - - Arguments: - arg1 - desc - - Returns: - - See Also: - <other_func> -*/ -int inp_num_events(); - -/* - Function: inp_get_event - TODO - - Arguments: - arg1 - desc - - Returns: - - See Also: - <other_func> -*/ -INPUT_EVENT inp_get_event(int index); - -/* - Function: inp_clear_events - TODO - - Arguments: - arg1 - desc - - Returns: - - See Also: - <other_func> -*/ -void inp_clear_events(); - -/* - Function: inp_mouse_doubleclick - TODO - - Arguments: - arg1 - desc - - Returns: - - See Also: - <other_func> -*/ -int inp_mouse_doubleclick(); - -/* - Function: inp_key_presses - TODO - - Arguments: - arg1 - desc - - Returns: - - See Also: - <other_func> -*/ -int inp_key_presses(int key); - -/* - Function: inp_key_releases - TODO - - Arguments: - arg1 - desc - - Returns: - - See Also: - <other_func> -*/ -int inp_key_releases(int key); - -/* - Function: inp_key_state - TODO - - Arguments: - arg1 - desc - - Returns: - - See Also: - <other_func> -*/ -int inp_key_state(int key); - -/* - Function: inp_key_name - TODO - - Arguments: - arg1 - desc - - Returns: - - See Also: - <other_func> -*/ -const char *inp_key_name(int k); - -/* - Function: inp_key_code - TODO - - Arguments: - arg1 - desc - - Returns: - - See Also: - <other_func> -*/ -int inp_key_code(const char *key_name); - - - -/* - Function: inp_clear_key_states - TODO - - Arguments: - arg1 - desc - - Returns: - - See Also: - <other_func> -*/ -void inp_clear_key_states(); - -void inp_update(); -void inp_init(); -void inp_mouse_mode_absolute(); -void inp_mouse_mode_relative(); - -#endif diff --git a/src/engine/e_if_modc.h b/src/engine/e_if_modc.h deleted file mode 100644 index 8839d5f1..00000000 --- a/src/engine/e_if_modc.h +++ /dev/null @@ -1,145 +0,0 @@ -/* copyright (c) 2007 magnus auvinen, see licence.txt for more info */ -#ifndef ENGINE_IF_MODC_H -#define ENGINE_IF_MODC_H - -/********************************************************************************** - Section: Client Hooks -*********************************************************************************/ -/* - Function: modc_console_init - TODO -*/ -void modc_console_init(); - -/* - Function: modc_rcon_line - TODO -*/ -void modc_rcon_line(const char *line); - -/* - Function: modc_save_config - TODO -*/ -void modc_save_config(); - -/* - Function: modc_init - Called when the client starts. - - Remarks: - The game should load resources that are used during the entire - time of the game. No map is loaded. -*/ -void modc_init(); - -/* - Function: modc_newsnapshot - Called when the client progressed to a new snapshot. - - Remarks: - The client can check for items in the snapshot and perform one time - events like playing sounds, spawning client side effects etc. -*/ -void modc_newsnapshot(); - -/* - Function: modc_entergame - Called when the client has successfully connect to a server and - loaded a map. - - Remarks: - The client can check for items in the map and load them. -*/ -void modc_entergame(); - -/* - Function: modc_shutdown - Called when the client closes down. -*/ -void modc_shutdown(); - -/* - Function: modc_render - Called every frame to let the game render it self. -*/ -void modc_render(); - -/* - Function: modc_statechange - Called every time client changes state. -*/ -void modc_statechange(int new_state, int old_state); - -/* undocumented callbacks */ -/* - Function: modc_connected - TODO - - Arguments: - arg1 - desc - - Returns: - - See Also: - <other_func> -*/ -void modc_connected(); - -/* - Function: modc_message - TODO - - Arguments: - arg1 - desc - - Returns: - - See Also: - <other_func> -*/ -void modc_message(int msg); - -/* - Function: modc_predict - TODO - - Arguments: - arg1 - desc - - Returns: - - See Also: - <other_func> -*/ -void modc_predict(); - -/* - Function: modc_snap_input - TODO - - Arguments: - arg1 - desc - - Returns: - - See Also: - <other_func> -*/ -int modc_snap_input(int *data); - -/* - Function: modc_net_version - TODO - - Arguments: - arg1 - desc - - Returns: - - See Also: - <other_func> -*/ -const char *modc_net_version(); - -#endif diff --git a/src/engine/e_if_mods.h b/src/engine/e_if_mods.h deleted file mode 100644 index 08d0ec37..00000000 --- a/src/engine/e_if_mods.h +++ /dev/null @@ -1,168 +0,0 @@ -/* copyright (c) 2007 magnus auvinen, see licence.txt for more info */ -#ifndef ENGINE_IF_MOD_H -#define ENGINE_IF_MOD_H - -/********************************************************************************** - Section: Server Hooks -**********************************************************************************/ -/* - Function: mods_console_init - TODO -*/ -void mods_console_init(); - -/* - Function: mods_init - Called when the server is started. - - Remarks: - It's called after the map is loaded so all map items are available. -*/ -void mods_init(); - -/* - Function: mods_shutdown - Called when the server quits. - - Remarks: - Should be used to clean up all resources used. -*/ -void mods_shutdown(); - -/* - Function: mods_client_enter - Called when a client has joined the game. - - Arguments: - cid - Client ID. Is 0 - MAX_CLIENTS. - - Remarks: - It's called when the client is finished loading and should enter gameplay. -*/ -void mods_client_enter(int cid); - -/* - Function: mods_client_drop - Called when a client drops from the server. - - Arguments: - cid - Client ID. Is 0 - MAX_CLIENTS -*/ -void mods_client_drop(int cid); - -/* - Function: mods_client_direct_input - Called when the server recives new input from a client. - - Arguments: - cid - Client ID. Is 0 - MAX_CLIENTS. - input - Pointer to the input data. - size - Size of the data. (NOT IMPLEMENTED YET) -*/ -void mods_client_direct_input(int cid, void *input); - -/* - Function: mods_client_predicted_input - Called when the server applys the predicted input on the client. - - Arguments: - cid - Client ID. Is 0 - MAX_CLIENTS. - input - Pointer to the input data. - size - Size of the data. (NOT IMPLEMENTED YET) -*/ -void mods_client_predicted_input(int cid, void *input); - - -/* - Function: mods_tick - Called with a regular interval to progress the gameplay. - - Remarks: - The SERVER_TICK_SPEED tells the number of ticks per second. -*/ -void mods_tick(); - -/* - Function: mods_presnap - Called before the server starts to construct snapshots for the clients. -*/ -void mods_presnap(); - -/* - Function: mods_snap - Called to create the snapshot for a client. - - Arguments: - cid - Client ID. Is 0 - MAX_CLIENTS. - - Remarks: - The game should make a series of calls to <snap_new_item> to construct - the snapshot for the client. -*/ -void mods_snap(int cid); - -/* - Function: mods_postsnap - Called after the server is done sending the snapshots. -*/ -void mods_postsnap(); - - -/* - Function: mods_connected - TODO - - Arguments: - arg1 - desc - - Returns: - - See Also: - <other_func> -*/ -void mods_connected(int client_id); - - -/* - Function: mods_net_version - TODO - - Arguments: - arg1 - desc - - Returns: - - See Also: - <other_func> -*/ -const char *mods_net_version(); - -/* - Function: mods_version - TODO - - Arguments: - arg1 - desc - - Returns: - - See Also: - <other_func> -*/ -const char *mods_version(); - -/* - Function: mods_message - TODO - - Arguments: - arg1 - desc - - Returns: - - See Also: - <other_func> -*/ -void mods_message(int msg, int client_id); - -#endif diff --git a/src/engine/e_if_msg.h b/src/engine/e_if_msg.h deleted file mode 100644 index d03a64a6..00000000 --- a/src/engine/e_if_msg.h +++ /dev/null @@ -1,151 +0,0 @@ -/* copyright (c) 2007 magnus auvinen, see licence.txt for more info */ -#ifndef ENGINE_IF_MSG_H -#define ENGINE_IF_MSG_H - -/* - Section: Messaging -*/ - -void msg_pack_start_system(int msg, int flags); - -/* - Function: msg_pack_start - TODO - - Arguments: - arg1 - desc - - Returns: - - See Also: - <other_func> -*/ -void msg_pack_start(int msg, int flags); - -/* - Function: msg_pack_int - TODO - - Arguments: - arg1 - desc - - Returns: - - See Also: - <other_func> -*/ -void msg_pack_int(int i); - -/* - Function: msg_pack_string - TODO - - Arguments: - arg1 - desc - - Returns: - - See Also: - <other_func> -*/ -void msg_pack_string(const char *p, int limit); - -/* - Function: msg_pack_raw - TODO - - Arguments: - arg1 - desc - - Returns: - - See Also: - <other_func> -*/ -void msg_pack_raw(const void *data, int size); - -/* - Function: msg_pack_end - TODO - - Arguments: - arg1 - desc - - Returns: - - See Also: - <other_func> -*/ -void msg_pack_end(); - -typedef struct -{ - int msg; - int flags; - const unsigned char *data; - int size; -} MSG_INFO; - -const MSG_INFO *msg_get_info(); - -/* message unpacking */ -int msg_unpack_start(const void *data, int data_size, int *is_system); - -/* - Function: msg_unpack_int - TODO - - Arguments: - arg1 - desc - - Returns: - - See Also: - <other_func> -*/ -int msg_unpack_int(); - -/* - Function: msg_unpack_string - TODO - - Arguments: - arg1 - desc - - Returns: - - See Also: - <other_func> -*/ -const char *msg_unpack_string(); - -/* - Function: msg_unpack_raw - TODO - - Arguments: - arg1 - desc - - Returns: - - See Also: - <other_func> -*/ -const unsigned char *msg_unpack_raw(int size); - -/* - Function: msg_unpack_error - TODO - - Arguments: - arg1 - desc - - Returns: - - See Also: - <other_func> -*/ -int msg_unpack_error(); - - -#endif diff --git a/src/engine/e_if_other.h b/src/engine/e_if_other.h deleted file mode 100644 index 3fbbd81e..00000000 --- a/src/engine/e_if_other.h +++ /dev/null @@ -1,385 +0,0 @@ -/* copyright (c) 2007 magnus auvinen, see licence.txt for more info */ -#ifndef ENGINE_IF_OTHER_H -#define ENGINE_IF_OTHER_H - -/* - Title: Engine Interface -*/ - -#include <base/system.h> -#include "e_keys.h" - -enum -{ - SERVER_TICK_SPEED=50, - MAX_CLIENTS=16, - - SNAP_CURRENT=0, - SNAP_PREV=1, - - MASK_NONE=0, - MASK_SET, - MASK_ZERO, - - SNDFLAG_LOOP=1, - SNDFLAG_POS=2, - SNDFLAG_ALL=3, - - MAX_NAME_LENGTH=32 -}; - -enum -{ - SRVFLAG_PASSWORD = 0x1, -}; - -/* - Structure: SNAP_ITEM -*/ -typedef struct -{ - int type; - int id; - int datasize; -} SNAP_ITEM; - -/* - Structure: CLIENT_INFO -*/ -typedef struct -{ - const char *name; - int latency; -} CLIENT_INFO; - -typedef struct PERFORMACE_INFO_t -{ - const char *name; - struct PERFORMACE_INFO_t *parent; - struct PERFORMACE_INFO_t *first_child; - struct PERFORMACE_INFO_t *next_child; - int tick; - int64 start; - int64 total; - int64 biggest; - int64 last_delta; -} PERFORMACE_INFO; - -void perf_init(); -void perf_next(); -void perf_start(PERFORMACE_INFO *info); -void perf_end(); -void perf_dump(PERFORMACE_INFO *top); - -int gfx_init(); -void gfx_shutdown(); -void gfx_swap(); - -int gfx_window_active(); -int gfx_window_open(); - -void gfx_set_vsync(int val); - -int snd_init(); -int snd_shutdown(); -int snd_update(); - -int map_load(const char *mapname); -void map_unload(); - -void map_set(void *m); - -/* -#include "e_if_client.h" -#include "e_if_server.h" -#include "e_if_snd.h" -#include "e_if_gfx.h" -#include "e_if_inp.h" -#include "e_if_msg.h" -#include "e_if_mod.h"*/ - -/* - Section: Map -*/ - -/* - Function: map_is_loaded - Checks if a map is loaded. - - Returns: - Returns 1 if the button is pressed, otherwise 0. -*/ -int map_is_loaded(); - -/* - Function: map_num_items - Checks the number of items in the loaded map. - - Returns: - Returns the number of items. 0 if no map is loaded. -*/ -int map_num_items(); - -/* - Function: map_find_item - Searches the map for an item. - - Arguments: - type - Item type. - id - Item ID. - - Returns: - Returns a pointer to the item if it exists, otherwise it returns NULL. -*/ -void *map_find_item(int type, int id); - -/* - Function: map_get_item - Gets an item from the loaded map from index. - - Arguments: - index - Item index. - type - Pointer that recives the item type (can be NULL). - id - Pointer that recives the item id (can be NULL). - - Returns: - Returns a pointer to the item if it exists, otherwise it returns NULL. -*/ -void *map_get_item(int index, int *type, int *id); - -/* - Function: map_get_type - Gets the index range of an item type. - - Arguments: - type - Item type to search for. - start - Pointer that recives the starting index. - num - Pointer that recives the number of items. - - Returns: - If the item type is not in the map, start and num will be set to 0. -*/ -void map_get_type(int type, int *start, int *num); - -/* - Function: map_get_data - Fetches a pointer to a raw data chunk in the map. - - Arguments: - index - Index to the data to fetch. - - Returns: - A pointer to the raw data, otherwise 0. -*/ -void *map_get_data(int index); - -/* - Function: map_get_data_swapped - TODO - - Arguments: - arg1 - desc - - Returns: - - See Also: - <other_func> -*/ -void *map_get_data_swapped(int index); - -/* - Section: Network (Server) -*/ -/* - Function: snap_new_item - Creates a new item that should be sent. - - Arguments: - type - Type of the item. - id - ID of the item. - size - Size of the item. - - Returns: - A pointer to the item data, otherwise 0. - - Remarks: - The item data should only consist pf 4 byte integers as - they are subject to byte swapping. This means that the size - argument should be dividable by 4. -*/ -void *snap_new_item(int type, int id, int size); - -/* - Section:Section: Network (Client) -*/ -/* - Function: snap_num_items - Check the number of items in a snapshot. - - Arguments: - snapid - Snapshot ID to the data to fetch. - * SNAP_PREV for previous snapshot. - * SNAP_CUR for current snapshot. - - Returns: - The number of items in the snapshot. -*/ -int snap_num_items(int snapid); - -/* - Function: snap_get_item - Gets an item from a snapshot. - - Arguments: - snapid - Snapshot ID to the data to fetch. - * SNAP_PREV for previous snapshot. - * SNAP_CUR for current snapshot. - index - Index of the item. - item - Pointer that recives the item info. - - Returns: - Returns a pointer to the item if it exists, otherwise NULL. -*/ -void *snap_get_item(int snapid, int index, SNAP_ITEM *item); - -/* - Function: snap_find_item - Searches a snapshot for an item. - - Arguments: - snapid - Snapshot ID to the data to fetch. - * SNAP_PREV for previous snapshot. - * SNAP_CUR for current snapshot. - type - Type of the item. - id - ID of the item. - - Returns: - Returns a pointer to the item if it exists, otherwise NULL. -*/ -void *snap_find_item(int snapid, int type, int id); - -/* - Function: snap_invalidate_item - Marks an item as invalid byt setting type and id to 0xffffffff. - - Arguments: - snapid - Snapshot ID to the data to fetch. - * SNAP_PREV for previous snapshot. - * SNAP_CUR for current snapshot. - index - Index of the item. -*/ -void snap_invalidate_item(int snapid, int index); - -/* - Function: snap_input - Sets the input data to send to the server. - - Arguments: - data - Pointer to the data. - size - Size of the data. - - Remarks: - The data should only consist of 4 bytes integer as they are - subject to byte swapping. -*/ -void snap_input(void *data, int size); - -/* - Function: snap_set_staticsize - Tells the engine how big a specific item always will be. This - helps the engine to compress snapshots better. - - Arguments: - type - Item type - size - Size of the data. - - Remarks: - Size must be in a multiple of 4. -*/ -void snap_set_staticsize(int type, int size); - -/* message packing */ -enum -{ - MSGFLAG_VITAL=1, - MSGFLAG_FLUSH=2, - MSGFLAG_NORECORD=4, - MSGFLAG_RECORD=8, - MSGFLAG_NOSEND=16 -}; - -/* message sending */ -/* - Function: server_send_msg - TODO - - Arguments: - arg1 - desc - - Returns: - - See Also: - <other_func> -*/ -int server_send_msg(int client_id); /* client_id == -1 == broadcast */ - -/* - Function: client_send_msg - TODO - - Arguments: - arg1 - desc - - Returns: - - See Also: - <other_func> -*/ -int client_send_msg(); -/* undocumented graphics stuff */ - -/* server snap id */ -/* - Function: snap_new_id - TODO - - Arguments: - arg1 - desc - - Returns: - - See Also: - <other_func> -*/ -int snap_new_id(); - -/* - Function: snap_free_id - TODO - - Arguments: - arg1 - desc - - Returns: - - See Also: - <other_func> -*/ -void snap_free_id(int id); - -/* other */ -/* - Function: map_unload_data - TODO - - Arguments: - arg1 - desc - - Returns: - - See Also: - <other_func> -*/ -void map_unload_data(int index); - -#endif diff --git a/src/engine/e_if_server.h b/src/engine/e_if_server.h deleted file mode 100644 index 1acd184c..00000000 --- a/src/engine/e_if_server.h +++ /dev/null @@ -1,140 +0,0 @@ -/* copyright (c) 2007 magnus auvinen, see licence.txt for more info */ -#ifndef ENGINE_IF_SERVER_H -#define ENGINE_IF_SERVER_H - -/* - Section: Server Interface -*/ - -/* server */ -/* - Function: server_getclientinfo - TODO - - Arguments: - arg1 - desc - - Returns: - - See Also: - <other_func> -*/ -int server_getclientinfo(int client_id, CLIENT_INFO *info); - -/* - Function: server_clientname - TODO - - Arguments: - arg1 - desc - - Returns: - - See Also: - <other_func> -*/ -const char *server_clientname(int client_id); - -/* grabs the latest input for the client. not withholding anything */ - -/* - Function: server_latestinput - TODO - - Arguments: - arg1 - desc - - Returns: - - See Also: - <other_func> -*/ -int *server_latestinput(int client_id, int *size); - -/* - Function: server_setclientname - TODO - - Arguments: - arg1 - desc - - Returns: - - See Also: - <other_func> -*/ -void server_setclientname(int client_id, const char *name); - -/* - Function: server_setclientscore - TODO - - Arguments: - arg1 - desc - - Returns: - - See Also: - <other_func> -*/ -void server_setclientscore(int client_id, int score); - -/* - Function: server_setbrowseinfo - TODO - - Arguments: - arg1 - desc - - Returns: - - See Also: - <other_func> -*/ -void server_setbrowseinfo(const char *game_type, int progression); - -/* - Function: server_kick - TODO - - Arguments: - arg1 - desc - - Returns: - - See Also: - <other_func> -*/ -void server_kick(int client_id, const char *reason); - -/* - Function: server_tick - TODO - - Arguments: - arg1 - desc - - Returns: - - See Also: - <other_func> -*/ -int server_tick(); - -/* - Function: server_tickspeed - TODO - - Arguments: - arg1 - desc - - Returns: - - See Also: - <other_func> -*/ -int server_tickspeed(); - -int server_ban_add(NETADDR addr, int seconds); -int server_ban_remove(NETADDR addr); -#endif diff --git a/src/engine/e_if_snd.h b/src/engine/e_if_snd.h deleted file mode 100644 index 48376bad..00000000 --- a/src/engine/e_if_snd.h +++ /dev/null @@ -1,91 +0,0 @@ -/* copyright (c) 2007 magnus auvinen, see licence.txt for more info */ -#ifndef ENGINE_IF_SND_H -#define ENGINE_IF_SND_H - -/* - Section: Sound -*/ - -/* - Function: snd_set_channel - Sets the parameters for a sound channel. - - Arguments: - cid - Channel ID - vol - Volume for the channel. 0.0 to 1.0. - pan - Panning for the channel. -1.0 is all left. 0.0 is equal distribution. 1.0 is all right. -*/ -void snd_set_channel(int cid, float vol, float pan); - -/* - Function: snd_load_wv - Loads a wavpack compressed sound. - - Arguments: - filename - Filename of the file to load - - Returns: - The id of the loaded sound. -1 on failure. -*/ -int snd_load_wv(const char *filename); - -/* - Function: snd_play_at - Plays a sound at a specified postition. - - Arguments: - cid - Channel id of the channel to use. - sid - Sound id of the sound to play. - flags - TODO - x - TODO - y - TODO - - Returns: - An id to the voice. -1 on failure. - - See Also: - <snd_play, snd_stop> -*/ -int snd_play_at(int cid, int sid, int flags, float x, float y); - -/* - Function: snd_play - Plays a sound. - - Arguments: - Arguments: - cid - Channel id of the channel to use. - sid - Sound id of the sound to play. - flags - TODO - - Returns: - An id to the voice. -1 on failure. - - See Also: - <snd_play_at, snd_stop> -*/ -int snd_play(int cid, int sid, int flags); - -/* - Function: snd_stop - Stops a currenly playing sound. - - Arguments: - id - The ID of the voice to stop. - - See Also: - <snd_play, snd_play_at> -*/ -void snd_stop(int id); - -/* - Function: snd_set_listener_pos - Sets the listener posititon. - - Arguments: - x - TODO - y - TODO -*/ -void snd_set_listener_pos(float x, float y); - -#endif diff --git a/src/engine/e_jobs.cpp b/src/engine/e_jobs.cpp deleted file mode 100644 index e87b395a..00000000 --- a/src/engine/e_jobs.cpp +++ /dev/null @@ -1,76 +0,0 @@ -/* copyright (c) 2007 magnus auvinen, see licence.txt for more info */ -#include <base/system.h> -#include "e_jobs.h" - -void worker_thread(void *user) -{ - JOBPOOL *pool = (JOBPOOL *)user; - - while(1) - { - JOB *job = 0; - - /* fetch job from queue */ - lock_wait(pool->lock); - if(pool->first_job) - { - job = pool->first_job; - pool->first_job = pool->first_job->next; - if(pool->first_job) - pool->first_job->prev = 0; - else - pool->last_job = 0; - } - lock_release(pool->lock); - - /* do the job if we have one */ - if(job) - { - job->status = JOBSTATUS_RUNNING; - job->result = job->func(job->func_data); - job->status = JOBSTATUS_DONE; - } - else - thread_sleep(10); - } - -} - -int jobs_initpool(JOBPOOL *pool, int num_threads) -{ - int i; - - /* empty the pool */ - mem_zero(pool, sizeof(JOBPOOL)); - pool->lock = lock_create(); - - /* start threads */ - for(i = 0; i < num_threads; i++) - thread_create(worker_thread, pool); - return 0; -} - -int jobs_add(JOBPOOL *pool, JOB *job, JOBFUNC func, void *data) -{ - mem_zero(job, sizeof(JOB)); - job->func = func; - job->func_data = data; - - lock_wait(pool->lock); - - /* add job to queue */ - job->prev = pool->last_job; - if(pool->last_job) - pool->last_job->next = job; - pool->last_job = job; - if(!pool->first_job) - pool->first_job = job; - - lock_release(pool->lock); - return 0; -} - -int jobs_status(JOB *job) -{ - return job->status; -} diff --git a/src/engine/e_jobs.h b/src/engine/e_jobs.h deleted file mode 100644 index 2b04a1e4..00000000 --- a/src/engine/e_jobs.h +++ /dev/null @@ -1,33 +0,0 @@ - -typedef int (*JOBFUNC)(void *data); - -typedef struct JOB -{ - struct JOBPOOL *pool; - struct JOB *prev; - struct JOB *next; - volatile int status; - volatile int result; - JOBFUNC func; - void *func_data; -} JOB; - -typedef struct JOBPOOL -{ - LOCK lock; - JOB *first_job; - JOB *last_job; -} JOBPOOL; - -enum -{ - JOBSTATUS_PENDING=0, - JOBSTATUS_RUNNING, - JOBSTATUS_DONE - /*JOBSTATUS_ABORTING,*/ - /*JOBSTATUS_ABORTED,*/ -}; - -int jobs_initpool(JOBPOOL *pool, int num_threads); -int jobs_add(JOBPOOL *pool, JOB *job, JOBFUNC func, void *data); -int jobs_status(JOB *job); diff --git a/src/engine/e_linereader.cpp b/src/engine/e_linereader.cpp deleted file mode 100644 index 57ba9a85..00000000 --- a/src/engine/e_linereader.cpp +++ /dev/null @@ -1,62 +0,0 @@ -#include "e_linereader.h" - -void linereader_init(LINEREADER *lr, IOHANDLE io) -{ - lr->buffer_max_size = 4*1024; - lr->buffer_size = 0; - lr->buffer_pos = 0; - lr->io = io; -} - -char *linereader_get(LINEREADER *lr) -{ - unsigned line_start = lr->buffer_pos; - - while(1) - { - if(lr->buffer_pos >= lr->buffer_size) - { - /* fetch more */ - - /* move the remaining part to the front */ - unsigned read; - unsigned left = lr->buffer_size - line_start; - - if(line_start > lr->buffer_size) - left = 0; - if(left) - mem_move(lr->buffer, &lr->buffer[line_start], left); - lr->buffer_pos = left; - - /* fill the buffer */ - read = io_read(lr->io, &lr->buffer[lr->buffer_pos], lr->buffer_max_size-lr->buffer_pos); - lr->buffer_size = left + read; - line_start = 0; - - if(!read) - { - if(left) - { - lr->buffer[left] = 0; /* return the last line */ - lr->buffer_pos = left; - lr->buffer_size = left; - return lr->buffer; - } - else - return 0x0; /* we are done! */ - } - } - else - { - if(lr->buffer[lr->buffer_pos] == '\n' || lr->buffer[lr->buffer_pos] == '\r') - { - /* line found */ - lr->buffer[lr->buffer_pos] = 0; - lr->buffer_pos++; - return &lr->buffer[line_start]; - } - else - lr->buffer_pos++; - } - } -} diff --git a/src/engine/e_linereader.h b/src/engine/e_linereader.h deleted file mode 100644 index ca84960a..00000000 --- a/src/engine/e_linereader.h +++ /dev/null @@ -1,14 +0,0 @@ -#include <base/system.h> - -/* buffered stream for reading lines, should perhaps be something smaller */ -typedef struct -{ - char buffer[4*1024]; - unsigned buffer_pos; - unsigned buffer_size; - unsigned buffer_max_size; - IOHANDLE io; -} LINEREADER; - -void linereader_init(LINEREADER *lr, IOHANDLE io); -char *linereader_get(LINEREADER *lr); diff --git a/src/engine/e_map.cpp b/src/engine/e_map.cpp deleted file mode 100644 index a2048310..00000000 --- a/src/engine/e_map.cpp +++ /dev/null @@ -1,66 +0,0 @@ -/* copyright (c) 2007 magnus auvinen, see licence.txt for more info */ -#include <base/system.h> -#include "e_datafile.h" - -static DATAFILE *map = 0; - -void *map_get_data(int index) -{ - return datafile_get_data(map, index); -} - -void *map_get_data_swapped(int index) -{ - return datafile_get_data_swapped(map, index); -} - -void map_unload_data(int index) -{ - datafile_unload_data(map, index); -} - -void *map_get_item(int index, int *type, int *id) -{ - return datafile_get_item(map, index, type, id); -} - -void map_get_type(int type, int *start, int *num) -{ - datafile_get_type(map, type, start, num); -} - -void *map_find_item(int type, int id) -{ - return datafile_find_item(map, type, id); -} - -int map_num_items() -{ - return datafile_num_items(map); -} - -void map_unload() -{ - datafile_unload(map); - map = 0x0; -} - -int map_is_loaded() -{ - return map != 0; -} - -int map_load(const char *mapname) -{ - char buf[512]; - str_format(buf, sizeof(buf), "maps/%s.map", mapname); - map = datafile_load(buf); - return map != 0; -} - -void map_set(void *m) -{ - if(map) - map_unload(); - map = (DATAFILE*)m; -} diff --git a/src/engine/e_memheap.cpp b/src/engine/e_memheap.cpp deleted file mode 100644 index fe157e86..00000000 --- a/src/engine/e_memheap.cpp +++ /dev/null @@ -1,102 +0,0 @@ -/* copyright (c) 2007 magnus auvinen, see licence.txt for more info */ -#include <base/system.h> - -struct CHUNK -{ - char *memory; - char *current; - char *end; - CHUNK *next; -}; - -struct HEAP -{ - CHUNK *current; -}; - -/* how large each chunk should be */ -static const int chunksize = 1024*64; - -/* allocates a new chunk to be used */ -static CHUNK *memheap_newchunk() -{ - CHUNK *chunk; - char *mem; - - /* allocate memory */ - mem = (char*)mem_alloc(sizeof(CHUNK)+chunksize, 1); - if(!mem) - return 0x0; - - /* the chunk structure is located in the begining of the chunk */ - /* init it and return the chunk */ - chunk = (CHUNK*)mem; - chunk->memory = (char*)(chunk+1); - chunk->current = chunk->memory; - chunk->end = chunk->memory + chunksize; - chunk->next = (CHUNK *)0x0; - return chunk; -} - -/******************/ -static void *memheap_allocate_from_chunk(CHUNK *chunk, unsigned int size) -{ - char *mem; - - /* check if we need can fit the allocation */ - if(chunk->current + size > chunk->end) - return (void*)0x0; - - /* get memory and move the pointer forward */ - mem = chunk->current; - chunk->current += size; - return mem; -} - -/* creates a heap */ -HEAP *memheap_create() -{ - CHUNK *chunk; - HEAP *heap; - - /* allocate a chunk and allocate the heap structure on that chunk */ - chunk = memheap_newchunk(); - heap = (HEAP *)memheap_allocate_from_chunk(chunk, sizeof(HEAP)); - heap->current = chunk; - return heap; -} - -/* destroys the heap */ -void memheap_destroy(HEAP *heap) -{ - CHUNK *chunk = heap->current; - CHUNK *next; - - while(chunk) - { - next = chunk->next; - mem_free(chunk); - chunk = next; - } -} - -/* */ -void *memheap_allocate(HEAP *heap, unsigned int size) -{ - char *mem; - - /* try to allocate from current chunk */ - mem = (char *)memheap_allocate_from_chunk(heap->current, size); - if(!mem) - { - /* allocate new chunk and add it to the heap */ - CHUNK *chunk = memheap_newchunk(); - chunk->next = heap->current; - heap->current = chunk; - - /* try to allocate again */ - mem = (char *)memheap_allocate_from_chunk(heap->current, size); - } - - return mem; -} diff --git a/src/engine/e_memheap.h b/src/engine/e_memheap.h deleted file mode 100644 index b4391ec7..00000000 --- a/src/engine/e_memheap.h +++ /dev/null @@ -1,6 +0,0 @@ -/* copyright (c) 2007 magnus auvinen, see licence.txt for more info */ - -struct HEAP; -HEAP *memheap_create(); -void memheap_destroy(HEAP *heap); -void *memheap_allocate(HEAP *heap, unsigned int size); diff --git a/src/engine/e_msg.cpp b/src/engine/e_msg.cpp deleted file mode 100644 index 999a0ff0..00000000 --- a/src/engine/e_msg.cpp +++ /dev/null @@ -1,70 +0,0 @@ -/* copyright (c) 2007 magnus auvinen, see licence.txt for more info */ -#include "e_common_interface.h" -#include "e_packer.h" - -/* message packing */ -static CPacker msg_packer; -static MSG_INFO pack_info; -static int packer_failed = 0; - -void msg_pack_int(int i) { msg_packer.AddInt(i); } -void msg_pack_string(const char *p, int limit) { msg_packer.AddString(p, limit); } -void msg_pack_raw(const void *data, int size) { msg_packer.AddRaw((const unsigned char *)data, size); } - -void msg_pack_start_system(int msg, int flags) -{ - msg_packer.Reset(); - pack_info.msg = (msg<<1)|1; - pack_info.flags = flags; - packer_failed = 0; - - msg_pack_int(pack_info.msg); -} - -void msg_pack_start(int msg, int flags) -{ - msg_packer.Reset(); - pack_info.msg = msg<<1; - pack_info.flags = flags; - packer_failed = 0; - - msg_pack_int(pack_info.msg); -} - -void msg_pack_end() -{ - if(msg_packer.Error()) - { - packer_failed = 1; - pack_info.size = 0; - pack_info.data = (unsigned char *)""; - } - else - { - pack_info.size = msg_packer.Size(); - pack_info.data = msg_packer.Data(); - } -} - -const MSG_INFO *msg_get_info() -{ - if(packer_failed) - return 0; - return &pack_info; -} - -/* message unpacking */ -static CUnpacker msg_unpacker; -int msg_unpack_start(const void *data, int data_size, int *system) -{ - int msg; - msg_unpacker.Reset((const unsigned char *)data, data_size); - msg = msg_unpack_int(); - *system = msg&1; - return msg>>1; -} - -int msg_unpack_int() { return msg_unpacker.GetInt(); } -const char *msg_unpack_string() { return msg_unpacker.GetString(); } -const unsigned char *msg_unpack_raw(int size) { return msg_unpacker.GetRaw(size); } -int msg_unpack_error() { return msg_unpacker.Error(); } diff --git a/src/engine/e_protocol.h b/src/engine/e_protocol.h deleted file mode 100644 index a1feb4cc..00000000 --- a/src/engine/e_protocol.h +++ /dev/null @@ -1,73 +0,0 @@ -/* copyright (c) 2007 magnus auvinen, see licence.txt for more info */ -#include <base/system.h> - -/* - Connection diagram - How the initilization works. - - Client -> INFO -> Server - Contains version info, name, and some other info. - - Client <- MAP <- Server - Contains current map. - - Client -> READY -> Server - The client has loaded the map and is ready to go, - but the mod needs to send it's information aswell. - modc_connected is called on the client and - mods_connected is called on the server. - The client should call client_entergame when the - mod has done it's initilization. - - Client -> ENTERGAME -> Server - Tells the server to start sending snapshots. - client_entergame and server_client_enter is called. -*/ - - -enum -{ - NETMSG_NULL=0, - - /* the first thing sent by the client - contains the version info for the client */ - NETMSG_INFO=1, - - /* sent by server */ - NETMSG_MAP_CHANGE, /* sent when client should switch map */ - NETMSG_MAP_DATA, /* map transfer, contains a chunk of the map file */ - NETMSG_SNAP, /* normal snapshot, multiple parts */ - NETMSG_SNAPEMPTY, /* empty snapshot */ - NETMSG_SNAPSINGLE, /* ? */ - NETMSG_SNAPSMALL, /* */ - NETMSG_INPUTTIMING, /* reports how off the input was */ - NETMSG_RCON_AUTH_STATUS,/* result of the authentication */ - NETMSG_RCON_LINE, /* line that should be printed to the remote console */ - - NETMSG_AUTH_CHALLANGE, /* */ - NETMSG_AUTH_RESULT, /* */ - - /* sent by client */ - NETMSG_READY, /* */ - NETMSG_ENTERGAME, - NETMSG_INPUT, /* contains the inputdata from the client */ - NETMSG_RCON_CMD, /* */ - NETMSG_RCON_AUTH, /* */ - NETMSG_REQUEST_MAP_DATA,/* */ - - NETMSG_AUTH_START, /* */ - NETMSG_AUTH_RESPONSE, /* */ - - /* sent by both */ - NETMSG_PING, - NETMSG_PING_REPLY, - NETMSG_ERROR -}; - - -/* this should be revised */ -enum -{ - MAX_CLANNAME_LENGTH=32, - MAX_INPUT_SIZE=128, - MAX_SNAPSHOT_PACKSIZE=900 -}; diff --git a/src/engine/e_server_interface.h b/src/engine/e_server_interface.h deleted file mode 100644 index c325b9a1..00000000 --- a/src/engine/e_server_interface.h +++ /dev/null @@ -1,12 +0,0 @@ -/* copyright (c) 2007 magnus auvinen, see licence.txt for more info */ -#ifndef ENGINE_SERVER_INTERFACE_H -#define ENGINE_SERVER_INTERFACE_H - -#include "e_if_other.h" -#include "e_if_server.h" -#include "e_if_msg.h" -#include "e_if_mods.h" - -#include "e_console.h" /* TODO: clean this up*/ - -#endif diff --git a/src/engine/e_snapshot.cpp b/src/engine/e_snapshot.cpp deleted file mode 100644 index 65487665..00000000 --- a/src/engine/e_snapshot.cpp +++ /dev/null @@ -1,571 +0,0 @@ -/* copyright (c) 2007 magnus auvinen, see licence.txt for more info */ -#include <stdlib.h> -#include "e_snapshot.h" -#include "e_engine.h" -#include "e_compression.h" -#include "e_common_interface.h" - - -/* TODO: strange arbitrary number */ -static short item_sizes[64] = {0}; - -void snap_set_staticsize(int itemtype, int size) -{ - item_sizes[itemtype] = size; -} - -CSnapshotItem *CSnapshot::GetItem(int Index) -{ - return (CSnapshotItem *)(DataStart() + Offsets()[Index]); -} - -int CSnapshot::GetItemSize(int Index) -{ - if(Index == m_NumItems-1) - return (m_DataSize - Offsets()[Index]) - sizeof(CSnapshotItem); - return (Offsets()[Index+1] - Offsets()[Index]) - sizeof(CSnapshotItem); -} - -int CSnapshot::GetItemIndex(int Key) -{ - /* TODO: OPT: this should not be a linear search. very bad */ - for(int i = 0; i < m_NumItems; i++) - { - if(GetItem(i)->Key() == Key) - return i; - } - return -1; -} - -// TODO: clean up this -typedef struct -{ - int num; - int keys[64]; - int index[64]; -} ITEMLIST; - -static ITEMLIST sorted[256]; - -int CSnapshot::GenerateHash() -{ - for(int i = 0; i < 256; i++) - sorted[i].num = 0; - - for(int i = 0; i < m_NumItems; i++) - { - int Key = GetItem(i)->Key(); - int HashID = ((Key>>8)&0xf0) | (Key&0xf); - if(sorted[HashID].num != 64) - { - sorted[HashID].index[sorted[HashID].num] = i; - sorted[HashID].keys[sorted[HashID].num] = Key; - sorted[HashID].num++; - } - } - return 0; -} - -int CSnapshot::GetItemIndexHashed(int Key) -{ - int HashID = ((Key>>8)&0xf0) | (Key&0xf); - for(int i = 0; i < sorted[HashID].num; i++) - { - if(sorted[HashID].keys[i] == Key) - return sorted[HashID].index[i]; - } - - return -1; -} - - - -static const int MAX_ITEMS = 512; -static CSnapshotDelta empty = {0,0,0,{0}}; - -CSnapshotDelta *CSnapshot::EmptyDelta() -{ - return ∅ -} - -int CSnapshot::Crc() -{ - int crc = 0; - - for(int i = 0; i < m_NumItems; i++) - { - CSnapshotItem *pItem = GetItem(i); - int Size = GetItemSize(i); - - for(int b = 0; b < Size/4; b++) - crc += pItem->Data()[b]; - } - return crc; -} - -void CSnapshot::DebugDump() -{ - dbg_msg("snapshot", "data_size=%d num_items=%d", m_DataSize, m_NumItems); - for(int i = 0; i < m_NumItems; i++) - { - CSnapshotItem *pItem = GetItem(i); - int Size = GetItemSize(i); - dbg_msg("snapshot", "\ttype=%d id=%d", pItem->Type(), pItem->ID()); - for(int b = 0; b < Size/4; b++) - dbg_msg("snapshot", "\t\t%3d %12d\t%08x", b, pItem->Data()[b], pItem->Data()[b]); - } -} - - -// TODO: remove these -int snapshot_data_rate[0xffff] = {0}; -int snapshot_data_updates[0xffff] = {0}; -static int snapshot_current = 0; - -static int diff_item(int *past, int *current, int *out, int size) -{ - int needed = 0; - while(size) - { - *out = *current-*past; - needed |= *out; - out++; - past++; - current++; - size--; - } - - return needed; -} - -static void undiff_item(int *past, int *diff, int *out, int size) -{ - while(size) - { - *out = *past+*diff; - - if(*diff == 0) - snapshot_data_rate[snapshot_current] += 1; - else - { - unsigned char buf[16]; - unsigned char *end = vint_pack(buf, *diff); - snapshot_data_rate[snapshot_current] += (int)(end - (unsigned char*)buf) * 8; - } - - out++; - past++; - diff++; - size--; - } -} - - -/* TODO: OPT: this should be made much faster */ -int CSnapshot::CreateDelta(CSnapshot *from, CSnapshot *to, void *dstdata) -{ - static PERFORMACE_INFO hash_scope = {"hash", 0}; - CSnapshotDelta *delta = (CSnapshotDelta *)dstdata; - int *data = (int *)delta->m_pData; - int i, itemsize, pastindex; - CSnapshotItem *pFromItem; - CSnapshotItem *pCurItem; - CSnapshotItem *pPastItem; - int count = 0; - int size_count = 0; - - delta->m_NumDeletedItems = 0; - delta->m_NumUpdateItems = 0; - delta->m_NumTempItems = 0; - - perf_start(&hash_scope); - to->GenerateHash(); - perf_end(); - - /* pack deleted stuff */ - { - static PERFORMACE_INFO scope = {"delete", 0}; - perf_start(&scope); - - for(i = 0; i < from->m_NumItems; i++) - { - pFromItem = from->GetItem(i); - if(to->GetItemIndexHashed(pFromItem->Key()) == -1) - { - /* deleted */ - delta->m_NumDeletedItems++; - *data = pFromItem->Key(); - data++; - } - } - - perf_end(); - } - - perf_start(&hash_scope); - from->GenerateHash(); - perf_end(); - - /* pack updated stuff */ - { - static PERFORMACE_INFO scope = {"update", 0}; - int pastindecies[1024]; - perf_start(&scope); - - /* fetch previous indices */ - /* we do this as a separate pass because it helps the cache */ - { - static PERFORMACE_INFO scope = {"find", 0}; - perf_start(&scope); - for(i = 0; i < to->m_NumItems; i++) - { - pCurItem = to->GetItem(i); /* O(1) .. O(n) */ - pastindecies[i] = from->GetItemIndexHashed(pCurItem->Key()); /* O(n) .. O(n^n)*/ - } - perf_end(); - } - - for(i = 0; i < to->m_NumItems; i++) - { - /* do delta */ - itemsize = to->GetItemSize(i); /* O(1) .. O(n) */ - pCurItem = to->GetItem(i); /* O(1) .. O(n) */ - pastindex = pastindecies[i]; - - if(pastindex != -1) - { - static PERFORMACE_INFO scope = {"diff", 0}; - int *item_data_dst = data+3; - perf_start(&scope); - - pPastItem = from->GetItem(pastindex); - - if(item_sizes[pCurItem->Type()]) - item_data_dst = data+2; - - if(diff_item((int*)pPastItem->Data(), (int*)pCurItem->Data(), item_data_dst, itemsize/4)) - { - - *data++ = pCurItem->Type(); - *data++ = pCurItem->ID(); - if(!item_sizes[pCurItem->Type()]) - *data++ = itemsize/4; - data += itemsize/4; - delta->m_NumUpdateItems++; - } - perf_end(); - } - else - { - static PERFORMACE_INFO scope = {"copy", 0}; - perf_start(&scope); - - *data++ = pCurItem->Type(); - *data++ = pCurItem->ID(); - if(!item_sizes[pCurItem->Type()]) - *data++ = itemsize/4; - - mem_copy(data, pCurItem->Data(), itemsize); - size_count += itemsize; - data += itemsize/4; - delta->m_NumUpdateItems++; - count++; - - perf_end(); - } - } - - perf_end(); - } - - if(0) - { - dbg_msg("snapshot", "%d %d %d", - delta->m_NumDeletedItems, - delta->m_NumUpdateItems, - delta->m_NumTempItems); - } - - /* - // TODO: pack temp stuff - - // finish - //mem_copy(delta->offsets, deleted, delta->num_deleted_items*sizeof(int)); - //mem_copy(&(delta->offsets[delta->num_deleted_items]), update, delta->num_update_items*sizeof(int)); - //mem_copy(&(delta->offsets[delta->num_deleted_items+delta->num_update_items]), temp, delta->num_temp_items*sizeof(int)); - //mem_copy(delta->data_start(), data, data_size); - //delta->data_size = data_size; - * */ - - if(!delta->m_NumDeletedItems && !delta->m_NumUpdateItems && !delta->m_NumTempItems) - return 0; - - return (int)((char*)data-(char*)dstdata); -} - -static int range_check(void *end, void *ptr, int size) -{ - if((const char *)ptr + size > (const char *)end) - return -1; - return 0; -} - -int CSnapshot::UnpackDelta(CSnapshot *from, CSnapshot *to, void *srcdata, int data_size) -{ - CSnapshotBuilder builder; - CSnapshotDelta *delta = (CSnapshotDelta *)srcdata; - int *data = (int *)delta->m_pData; - int *end = (int *)(((char *)srcdata + data_size)); - - CSnapshotItem *fromitem; - int i, d, keep, itemsize; - int *deleted; - int id, type, key; - int fromindex; - int *newdata; - - builder.Init(); - - /* unpack deleted stuff */ - deleted = data; - data += delta->m_NumDeletedItems; - if(data > end) - return -1; - - /* copy all non deleted stuff */ - for(i = 0; i < from->m_NumItems; i++) - { - /* dbg_assert(0, "fail!"); */ - fromitem = from->GetItem(i); - itemsize = from->GetItemSize(i); - keep = 1; - for(d = 0; d < delta->m_NumDeletedItems; d++) - { - if(deleted[d] == fromitem->Key()) - { - keep = 0; - break; - } - } - - if(keep) - { - /* keep it */ - mem_copy( - builder.NewItem(fromitem->Type(), fromitem->ID(), itemsize), - fromitem->Data(), itemsize); - } - } - - /* unpack updated stuff */ - for(i = 0; i < delta->m_NumUpdateItems; i++) - { - if(data+2 > end) - return -1; - - type = *data++; - id = *data++; - if(item_sizes[type]) - itemsize = item_sizes[type]; - else - { - if(data+1 > end) - return -2; - itemsize = (*data++) * 4; - } - snapshot_current = type; - - if(range_check(end, data, itemsize) || itemsize < 0) return -3; - - key = (type<<16)|id; - - /* create the item if needed */ - newdata = builder.GetItemData(key); - if(!newdata) - newdata = (int *)builder.NewItem(key>>16, key&0xffff, itemsize); - - /*if(range_check(end, newdata, itemsize)) return -4;*/ - - fromindex = from->GetItemIndex(key); - if(fromindex != -1) - { - /* we got an update so we need to apply the diff */ - undiff_item((int *)from->GetItem(fromindex)->Data(), data, newdata, itemsize/4); - snapshot_data_updates[snapshot_current]++; - } - else /* no previous, just copy the data */ - { - mem_copy(newdata, data, itemsize); - snapshot_data_rate[snapshot_current] += itemsize*8; - snapshot_data_updates[snapshot_current]++; - } - - data += itemsize/4; - } - - /* finish up */ - return builder.Finish(to); -} - -/* CSnapshotStorage */ - -void CSnapshotStorage::Init() -{ - m_pFirst = 0; - m_pLast = 0; -} - -void CSnapshotStorage::PurgeAll() -{ - CHolder *pHolder = m_pFirst; - CHolder *pNext; - - while(pHolder) - { - pNext = pHolder->m_pNext; - mem_free(pHolder); - pHolder = pNext; - } - - /* no more snapshots in storage */ - m_pFirst = 0; - m_pLast = 0; -} - -void CSnapshotStorage::PurgeUntil(int Tick) -{ - CHolder *pHolder = m_pFirst; - CHolder *pNext; - - while(pHolder) - { - pNext = pHolder->m_pNext; - if(pHolder->m_Tick >= Tick) - return; /* no more to remove */ - mem_free(pHolder); - - /* did we come to the end of the list? */ - if (!pNext) - break; - - m_pFirst = pNext; - pNext->m_pPrev = 0x0; - - pHolder = pNext; - } - - /* no more snapshots in storage */ - m_pFirst = 0; - m_pLast = 0; -} - -void CSnapshotStorage::Add(int Tick, int64 Tagtime, int DataSize, void *pData, int CreateAlt) -{ - /* allocate memory for holder + snapshot_data */ - int TotalSize = sizeof(CHolder)+DataSize; - - if(CreateAlt) - TotalSize += DataSize; - - CHolder *pHolder = (CHolder *)mem_alloc(TotalSize, 1); - - /* set data */ - pHolder->m_Tick = Tick; - pHolder->m_Tagtime = Tagtime; - pHolder->m_SnapSize = DataSize; - pHolder->m_pSnap = (CSnapshot*)(pHolder+1); - mem_copy(pHolder->m_pSnap, pData, DataSize); - - if(CreateAlt) /* create alternative if wanted */ - { - pHolder->m_pAltSnap = (CSnapshot*)(((char *)pHolder->m_pSnap) + DataSize); - mem_copy(pHolder->m_pAltSnap, pData, DataSize); - } - else - pHolder->m_pAltSnap = 0; - - - /* link */ - pHolder->m_pNext = 0; - pHolder->m_pPrev = m_pLast; - if(m_pLast) - m_pLast->m_pNext = pHolder; - else - m_pFirst = pHolder; - m_pLast = pHolder; -} - -int CSnapshotStorage::Get(int Tick, int64 *pTagtime, CSnapshot **ppData, CSnapshot **ppAltData) -{ - CHolder *pHolder = m_pFirst; - - while(pHolder) - { - if(pHolder->m_Tick == Tick) - { - if(pTagtime) - *pTagtime = pHolder->m_Tagtime; - if(ppData) - *ppData = pHolder->m_pSnap; - if(ppAltData) - *ppData = pHolder->m_pAltSnap; - return pHolder->m_SnapSize; - } - - pHolder = pHolder->m_pNext; - } - - return -1; -} - -/* CSnapshotBuilder */ - -void CSnapshotBuilder::Init() -{ - m_DataSize = 0; - m_NumItems = 0; -} - -CSnapshotItem *CSnapshotBuilder::GetItem(int Index) -{ - return (CSnapshotItem *)&(m_aData[m_aOffsets[Index]]); -} - -int *CSnapshotBuilder::GetItemData(int key) -{ - int i; - for(i = 0; i < m_NumItems; i++) - { - if(GetItem(i)->Key() == key) - return (int *)GetItem(i)->Data(); - } - return 0; -} - -int CSnapshotBuilder::Finish(void *snapdata) -{ - /* flattern and make the snapshot */ - CSnapshot *pSnap = (CSnapshot *)snapdata; - int OffsetSize = sizeof(int)*m_NumItems; - pSnap->m_DataSize = m_DataSize; - pSnap->m_NumItems = m_NumItems; - mem_copy(pSnap->Offsets(), m_aOffsets, OffsetSize); - mem_copy(pSnap->DataStart(), m_aData, m_DataSize); - return sizeof(CSnapshot) + OffsetSize + m_DataSize; -} - -void *CSnapshotBuilder::NewItem(int Type, int ID, int Size) -{ - CSnapshotItem *pObj = (CSnapshotItem *)(m_aData + m_DataSize); - - mem_zero(pObj, sizeof(CSnapshotItem) + Size); - pObj->m_TypeAndID = (Type<<16)|ID; - m_aOffsets[m_NumItems] = m_DataSize; - m_DataSize += sizeof(CSnapshotItem) + Size; - m_NumItems++; - - dbg_assert(m_DataSize < CSnapshot::MAX_SIZE, "too much data"); - dbg_assert(m_NumItems < MAX_ITEMS, "too many items"); - - return pObj->Data(); -} diff --git a/src/engine/editor.h b/src/engine/editor.h new file mode 100644 index 00000000..32a5cc93 --- /dev/null +++ b/src/engine/editor.h @@ -0,0 +1,16 @@ +#ifndef ENGINE_EDITOR_H +#define ENGINE_EDITOR_H +#include "kernel.h" + +class IEditor : public IInterface +{ + MACRO_INTERFACE("editor", 0) +public: + + virtual ~IEditor() {} + virtual void Init() = 0; + virtual void UpdateAndRender() = 0; +}; + +extern IEditor *CreateEditor(); +#endif diff --git a/src/engine/graphics.h b/src/engine/graphics.h new file mode 100644 index 00000000..4d3c0bc0 --- /dev/null +++ b/src/engine/graphics.h @@ -0,0 +1,151 @@ +#ifndef ENGINE_GRAPHICS_H +#define ENGINE_GRAPHICS_H + +#include "kernel.h" + +class CImageInfo +{ +public: + enum + { + FORMAT_AUTO=-1, + FORMAT_RGB=0, + FORMAT_RGBA=1, + FORMAT_ALPHA=2, + }; + + /* Variable: width + Contains the width of the image */ + int m_Width; + + /* Variable: height + Contains the height of the image */ + int m_Height; + + /* Variable: format + Contains the format of the image. See <Image Formats> for more information. */ + int m_Format; + + /* Variable: data + Pointer to the image data. */ + void *m_pData; +}; + +/* + Structure: CVideoMode +*/ +class CVideoMode +{ +public: + int m_Width, m_Height; + int m_Red, m_Green, m_Blue; +}; + +class IGraphics : public IInterface +{ + MACRO_INTERFACE("graphics", 0) +protected: + int m_ScreenWidth; + int m_ScreenHeight; +public: + /* Constants: Texture Loading Flags + TEXLOAD_NORESAMPLE - Prevents the texture from any resampling + */ + enum + { + TEXLOAD_NORESAMPLE=1, + }; + + int ScreenWidth() const { return m_ScreenWidth; } + int ScreenHeight() const { return m_ScreenHeight; } + float ScreenAspect() const { return (float)ScreenWidth()/(float)ScreenHeight(); } + + virtual void Clear(float r, float g, float b) = 0; + + virtual void ClipEnable(int x, int y, int w, int h) = 0; + virtual void ClipDisable() = 0; + + virtual void MapScreen(float TopLeftX, float TopLeftY, float BottomRightX, float BottomRightY) = 0; + virtual void GetScreen(float *pTopLeftX, float *pTopLeftY, float *pBottomRightX, float *pBottomRightY) = 0; + + // TODO: These should perhaps not be virtuals + virtual void BlendNone() = 0; + virtual void BlendNormal() = 0; + virtual void BlendAdditive() = 0; + virtual int MemoryUsage() const = 0; + + virtual int LoadPNG(CImageInfo *pImg, const char *pFilename) =0; + virtual int UnloadTexture(int Index) = 0; + virtual int LoadTextureRaw(int Width, int Height, int Format, const void *pData, int StoreFormat, int Flags) = 0; + virtual int LoadTexture(const char *pFilename, int StoreFormat, int Flags) = 0; + virtual void TextureSet(int TextureID) = 0; + + struct CLineItem + { + float m_X0, m_Y0, m_X1, m_Y1; + CLineItem() {} + CLineItem(float x0, float y0, float x1, float y1) : m_X0(x0), m_Y0(y0), m_X1(x1), m_Y1(y1) {} + }; + virtual void LinesBegin() = 0; + virtual void LinesEnd() = 0; + virtual void LinesDraw(const CLineItem *pArray, int Num) = 0; + + virtual void QuadsBegin() = 0; + virtual void QuadsEnd() = 0; + virtual void QuadsSetRotation(float Angle) = 0; + virtual void QuadsSetSubset(float TopLeftY, float TopLeftV, float BottomRightU, float BottomRightV) = 0; + virtual void QuadsSetSubsetFree(float x0, float y0, float x1, float y1, float x2, float y2, float x3, float y3) = 0; + + struct CQuadItem + { + float m_X, m_Y, m_Width, m_Height; + CQuadItem() {} + CQuadItem(float x, float y, float w, float h) : m_X(x), m_Y(y), m_Width(w), m_Height(h) {} + }; + virtual void QuadsDraw(CQuadItem *pArray, int Num) = 0; + virtual void QuadsDrawTL(const CQuadItem *pArray, int Num) = 0; + + struct CFreeformItem + { + float m_X0, m_Y0, m_X1, m_Y1, m_X2, m_Y2, m_X3, m_Y3; + CFreeformItem() {} + CFreeformItem(float x0, float y0, float x1, float y1, float x2, float y2, float x3, float y3) + : m_X0(x0), m_Y0(y0), m_X1(x1), m_Y1(y1), m_X2(x2), m_Y2(y2), m_X3(x3), m_Y3(y3) {} + }; + virtual void QuadsDrawFreeform(const CFreeformItem *pArray, int Num) = 0; + virtual void QuadsText(float x, float y, float Size, float r, float g, float b, float a, const char *pText) = 0; + + struct CColorVertex + { + int m_Index; + float m_R, m_G, m_B, m_A; + CColorVertex() {} + CColorVertex(int i, float r, float g, float b, float a) : m_Index(i), m_R(r), m_G(g), m_B(b), m_A(a) {} + }; + virtual void SetColorVertex(const CColorVertex *pArray, int Num) = 0; + virtual void SetColor(float r, float g, float b, float a) = 0; + + virtual void TakeScreenshot() = 0; + virtual int GetVideoModes(CVideoMode *pModes, int MaxModes) = 0; + + virtual void Swap() = 0; +}; + +class IEngineGraphics : public IGraphics +{ + MACRO_INTERFACE("enginegraphics", 0) +public: + virtual bool Init() = 0; + virtual void Shutdown() = 0; + + virtual void Minimize() = 0; + virtual void Maximize() = 0; + + virtual int WindowActive() = 0; + virtual int WindowOpen() = 0; + +}; + +extern IEngineGraphics *CreateEngineGraphics(); + +#endif diff --git a/src/engine/input.h b/src/engine/input.h new file mode 100644 index 00000000..168614c8 --- /dev/null +++ b/src/engine/input.h @@ -0,0 +1,89 @@ +#ifndef ENGINE_INPUT_H +#define ENGINE_INPUT_H + +#include "kernel.h" + +extern const char g_aaKeyStrings[512][16]; + +class IInput : public IInterface +{ + MACRO_INTERFACE("input", 0) +public: + class CEvent + { + public: + int m_Flags; + int m_Unicode; + int m_Key; + }; + +protected: + enum + { + INPUT_BUFFER_SIZE=32 + }; + + // quick access to events + int m_NumEvents; + IInput::CEvent m_aInputEvents[INPUT_BUFFER_SIZE]; + + //quick access to input + struct + { + unsigned char m_Presses; + unsigned char m_Releases; + } m_aInputCount[2][1024]; + + unsigned char m_aInputState[2][1024]; + int m_InputCurrent; + + int KeyWasPressed(int Key) { return m_aInputState[m_InputCurrent^1][Key]; } + +public: + enum + { + FLAG_PRESS=1, + FLAG_RELEASE=2, + FLAG_REPEAT=4 + }; + + // events + int NumEvents() const { return m_NumEvents; } + void ClearEvents() { m_NumEvents = 0; } + CEvent GetEvent(int Index) const + { + if(Index < 0 || Index >= m_NumEvents) + { + IInput::CEvent e = {0,0}; + return e; + } + return m_aInputEvents[Index]; + } + + // keys + int KeyPressed(int Key) { return m_aInputState[m_InputCurrent][Key]; } + int KeyReleases(int Key) { return m_aInputCount[m_InputCurrent][Key].m_Releases; } + int KeyPresses(int Key) { return m_aInputCount[m_InputCurrent][Key].m_Presses; } + int KeyDown(int Key) { return KeyPressed(Key)&&!KeyWasPressed(Key); } + const char *KeyName(int Key) { return (Key >= 0 && Key < 512) ? g_aaKeyStrings[Key] : g_aaKeyStrings[0]; } + + // + virtual void MouseModeRelative() = 0; + virtual void MouseModeAbsolute() = 0; + virtual int MouseDoubleClick() = 0; + + virtual void MouseRelative(int *x, int *y) = 0; +}; + + +class IEngineInput : public IInput +{ + MACRO_INTERFACE("engineinput", 0) +public: + virtual void Init() = 0; + virtual void Update() = 0; +}; + +extern IEngineInput *CreateEngineInput(); + +#endif diff --git a/src/engine/kernel.h b/src/engine/kernel.h new file mode 100644 index 00000000..6a72690f --- /dev/null +++ b/src/engine/kernel.h @@ -0,0 +1,66 @@ +#ifndef ENGINE_KERNEL_H +#define ENGINE_KERNEL_H + +#include <base/system.h> + +class IKernel; +class IInterface; + +class IInterface +{ + // friend with the kernel implementation + friend class CKernel; + IKernel *m_pKernel; +protected: + IKernel *Kernel() { return m_pKernel; } +public: + IInterface() : m_pKernel(0) {} + virtual ~IInterface() {} + + //virtual unsigned InterfaceID() = 0; + //virtual const char *InterfaceName() = 0; +}; + +#define MACRO_INTERFACE(Name, ver) \ + public: \ + static const char *InterfaceName() { return Name; } \ + private: + + //virtual unsigned InterfaceID() { return INTERFACE_ID; } + //virtual const char *InterfaceName() { return name; } + + +// This kernel thingie makes the structure very flat and basiclly singletons. +// I'm not sure if this is a good idea but it works for now. +class IKernel +{ + // hide the implementation + virtual bool RegisterInterfaceImpl(const char *InterfaceName, IInterface *pInterface) = 0; + virtual bool ReregisterInterfaceImpl(const char *InterfaceName, IInterface *pInterface) = 0; + virtual IInterface *RequestInterfaceImpl(const char *InterfaceName) = 0; +public: + static IKernel *Create(); + virtual ~IKernel() {} + + // templated access to handle pointer convertions and interface names + template<class TINTERFACE> + bool RegisterInterface(TINTERFACE *pInterface) + { + return RegisterInterfaceImpl(TINTERFACE::InterfaceName(), pInterface); + } + template<class TINTERFACE> + bool ReregisterInterface(TINTERFACE *pInterface) + { + return ReregisterInterfaceImpl(TINTERFACE::InterfaceName(), pInterface); + } + + // Usage example: + // IMyInterface *pMyHandle = Kernel()->RequestInterface<IMyInterface>() + template<class TINTERFACE> + TINTERFACE *RequestInterface() + { + return reinterpret_cast<TINTERFACE *>(RequestInterfaceImpl(TINTERFACE::InterfaceName())); + } +}; + +#endif diff --git a/src/engine/e_keys.h b/src/engine/keys.h index cb4ca371..cb4ca371 100644 --- a/src/engine/e_keys.h +++ b/src/engine/keys.h diff --git a/src/engine/map.h b/src/engine/map.h new file mode 100644 index 00000000..2c285f26 --- /dev/null +++ b/src/engine/map.h @@ -0,0 +1,32 @@ +#ifndef ENGINE_MAP_H +#define ENGINE_MAP_H + +#include "kernel.h" + +class IMap : public IInterface +{ + MACRO_INTERFACE("map", 0) +public: + virtual void *GetData(int Index) = 0; + virtual void *GetDataSwapped(int Index) = 0; + virtual void UnloadData(int Index) = 0; + virtual void *GetItem(int Index, int *Type, int *pId) = 0; + virtual void GetType(int Type, int *pStart, int *pNum) = 0; + virtual void *FindItem(int Type, int Id) = 0; + virtual int NumItems() = 0; +}; + + +class IEngineMap : public IMap +{ + MACRO_INTERFACE("enginemap", 0) +public: + virtual bool Load(const char *pMapName) = 0; + virtual bool IsLoaded() = 0; + virtual void Unload() = 0; + virtual unsigned Crc() = 0; +}; + +extern IEngineMap *CreateEngineMap(); + +#endif diff --git a/src/engine/masterserver.h b/src/engine/masterserver.h new file mode 100644 index 00000000..9d16e85f --- /dev/null +++ b/src/engine/masterserver.h @@ -0,0 +1,40 @@ +#ifndef ENGINE_MASTERSERVER_H +#define ENGINE_MASTERSERVER_H + +#include "kernel.h" + +class IMasterServer : public IInterface +{ + MACRO_INTERFACE("masterserver", 0) +public: + + enum + { + MAX_MASTERSERVERS=4 + }; + + virtual void Init(class CEngine *pEngine) = 0; + virtual void SetDefault() = 0; + virtual int Load() = 0; + virtual int Save() = 0; + + virtual int RefreshAddresses() = 0; + virtual void Update() = 0; + virtual int IsRefreshing() = 0; + virtual void DumpServers() = 0; + virtual NETADDR GetAddr(int Index) = 0; + virtual const char *GetName(int Index) = 0; +}; + +class IEngineMasterServer : public IMasterServer +{ + MACRO_INTERFACE("enginemasterserver", 0) +public: +}; + +extern IEngineMasterServer *CreateEngineMasterServer(); + +#endif + + + diff --git a/src/engine/message.h b/src/engine/message.h index 6de407ed..3ffc488f 100644 --- a/src/engine/message.h +++ b/src/engine/message.h @@ -1,8 +1,16 @@ +#ifndef ENGINE_MESSAGE_H +#define ENGINE_MESSAGE_H +#include <engine/shared/packer.h> -class CMessage +class CMsgPacker : public CPacker { public: - virtual bool Pack(void *pData, unsigned MaxDataSize); - virtual bool Unpack(const void *pData, unsigned DataSize); + CMsgPacker(int Type) + { + Reset(); + AddInt(Type); + } }; + +#endif diff --git a/src/engine/server.h b/src/engine/server.h new file mode 100644 index 00000000..52e6ec6a --- /dev/null +++ b/src/engine/server.h @@ -0,0 +1,81 @@ +#ifndef ENGINE_SERVER_H +#define ENGINE_SERVER_H +#include "kernel.h" +#include "message.h" + +class IServer : public IInterface +{ + MACRO_INTERFACE("server", 0) +protected: + int m_CurrentGameTick; + int m_TickSpeed; + +public: + /* + Structure: CClientInfo + */ + struct CClientInfo + { + const char *m_pName; + int m_Latency; + }; + + int Tick() const { return m_CurrentGameTick; } + int TickSpeed() const { return m_TickSpeed; } + + virtual const char *ClientName(int ClientID) = 0; + virtual bool ClientIngame(int ClientID) = 0; + virtual int GetClientInfo(int ClientID, CClientInfo *pInfo) = 0; + virtual void GetClientIP(int ClientID, char *pIPString, int Size) = 0; + virtual int *LatestInput(int ClientID, int *pSize) = 0; + + virtual int SendMsg(CMsgPacker *pMsg, int Flags, int ClientID) = 0; + + template<class T> + int SendPackMsg(T *pMsg, int Flags, int ClientID) + { + CMsgPacker Packer(pMsg->MsgID()); + if(pMsg->Pack(&Packer)) + return -1; + return SendMsg(&Packer, Flags, ClientID); + } + + virtual void SetBrowseInfo(char const *pGameType, int Progression) = 0; + virtual void SetClientName(int ClientID, char const *pName) = 0; + virtual void SetClientScore(int ClientID, int Score) = 0; + + virtual int SnapNewID() = 0; + virtual void SnapFreeID(int ID) = 0; + virtual void *SnapNewItem(int Type, int Id, int Size) = 0; + + virtual void SnapSetStaticsize(int ItemType, int Size) = 0; +}; + +class IGameServer : public IInterface +{ + MACRO_INTERFACE("gameserver", 0) +protected: +public: + virtual void OnInit() = 0; + virtual void OnConsoleInit() = 0; + virtual void OnShutdown() = 0; + + virtual void OnTick() = 0; + virtual void OnPreSnap() = 0; + virtual void OnSnap(int ClientID) = 0; + virtual void OnPostSnap() = 0; + + virtual void OnMessage(int MsgId, CUnpacker *pUnpacker, int ClientID) = 0; + + virtual void OnClientConnected(int ClientID) = 0; + virtual void OnClientEnter(int ClientID) = 0; + virtual void OnClientDrop(int ClientID) = 0; + virtual void OnClientDirectInput(int ClientID, void *pInput) = 0; + virtual void OnClientPredictedInput(int ClientID, void *pInput) = 0; + + virtual const char *Version() = 0; + virtual const char *NetVersion() = 0; +}; + +extern IGameServer *CreateGameServer(); +#endif diff --git a/src/engine/server/es_register.cpp b/src/engine/server/es_register.cpp deleted file mode 100644 index 0eb5dba5..00000000 --- a/src/engine/server/es_register.cpp +++ /dev/null @@ -1,271 +0,0 @@ -#include <string.h> -#include <base/system.h> -#include <engine/e_network.h> -#include <engine/e_config.h> -#include <engine/e_engine.h> - -#include <mastersrv/mastersrv.h> - -extern CNetServer *m_pNetServer; - -enum -{ - REGISTERSTATE_START=0, - REGISTERSTATE_UPDATE_ADDRS, - REGISTERSTATE_QUERY_COUNT, - REGISTERSTATE_HEARTBEAT, - REGISTERSTATE_REGISTERED, - REGISTERSTATE_ERROR -}; - -static int register_state = REGISTERSTATE_START; -static int64 register_state_start = 0; -static int register_first = 1; -static int register_count = 0; - -static void register_new_state(int state) -{ - register_state = state; - register_state_start = time_get(); -} - -static void register_send_fwcheckresponse(NETADDR *pAddr) -{ - CNetChunk Packet; - Packet.m_ClientID = -1; - Packet.m_Address = *pAddr; - Packet.m_Flags = NETSENDFLAG_CONNLESS; - Packet.m_DataSize = sizeof(SERVERBROWSE_FWRESPONSE); - Packet.m_pData = SERVERBROWSE_FWRESPONSE; - m_pNetServer->Send(&Packet); -} - -static void register_send_heartbeat(NETADDR addr) -{ - static unsigned char data[sizeof(SERVERBROWSE_HEARTBEAT) + 2]; - unsigned short port = config.sv_port; - CNetChunk Packet; - - mem_copy(data, SERVERBROWSE_HEARTBEAT, sizeof(SERVERBROWSE_HEARTBEAT)); - - Packet.m_ClientID = -1; - Packet.m_Address = addr; - Packet.m_Flags = NETSENDFLAG_CONNLESS; - Packet.m_DataSize = sizeof(SERVERBROWSE_HEARTBEAT) + 2; - Packet.m_pData = &data; - - /* supply the set port that the master can use if it has problems */ - if(config.sv_external_port) - port = config.sv_external_port; - data[sizeof(SERVERBROWSE_HEARTBEAT)] = port >> 8; - data[sizeof(SERVERBROWSE_HEARTBEAT)+1] = port&0xff; - m_pNetServer->Send(&Packet); -} - -static void register_send_count_request(NETADDR Addr) -{ - CNetChunk Packet; - Packet.m_ClientID = -1; - Packet.m_Address = Addr; - Packet.m_Flags = NETSENDFLAG_CONNLESS; - Packet.m_DataSize = sizeof(SERVERBROWSE_GETCOUNT); - Packet.m_pData = SERVERBROWSE_GETCOUNT; - m_pNetServer->Send(&Packet); -} - -typedef struct -{ - NETADDR addr; - int count; - int valid; - int64 last_send; -} MASTERSERVER_INFO; - -static MASTERSERVER_INFO masterserver_info[MAX_MASTERSERVERS] = {{{0}}}; -static int register_registered_server = -1; - -void register_update() -{ - int64 now = time_get(); - int64 freq = time_freq(); - - if(!config.sv_register) - return; - - mastersrv_update(); - - if(register_state == REGISTERSTATE_START) - { - register_count = 0; - register_first = 1; - register_new_state(REGISTERSTATE_UPDATE_ADDRS); - mastersrv_refresh_addresses(); - dbg_msg("register", "refreshing ip addresses"); - } - else if(register_state == REGISTERSTATE_UPDATE_ADDRS) - { - register_registered_server = -1; - - if(!mastersrv_refreshing()) - { - int i; - for(i = 0; i < MAX_MASTERSERVERS; i++) - { - NETADDR addr = mastersrv_get(i); - masterserver_info[i].addr = addr; - masterserver_info[i].count = 0; - - if(!addr.ip[0] && !addr.ip[1] && !addr.ip[2] && !addr.ip[3]) - masterserver_info[i].valid = 0; - else - { - masterserver_info[i].valid = 1; - masterserver_info[i].count = -1; - masterserver_info[i].last_send = 0; - } - } - - dbg_msg("register", "fetching server counts"); - register_new_state(REGISTERSTATE_QUERY_COUNT); - } - } - else if(register_state == REGISTERSTATE_QUERY_COUNT) - { - int i; - int left = 0; - for(i = 0; i < MAX_MASTERSERVERS; i++) - { - if(!masterserver_info[i].valid) - continue; - - if(masterserver_info[i].count == -1) - { - left++; - if(masterserver_info[i].last_send+freq < now) - { - masterserver_info[i].last_send = now; - register_send_count_request(masterserver_info[i].addr); - } - } - } - - /* check if we are done or timed out */ - if(left == 0 || now > register_state_start+freq*3) - { - /* choose server */ - int best = -1; - int i; - for(i = 0; i < MAX_MASTERSERVERS; i++) - { - if(!masterserver_info[i].valid || masterserver_info[i].count == -1) - continue; - - if(best == -1 || masterserver_info[i].count < masterserver_info[best].count) - best = i; - } - - /* server chosen */ - register_registered_server = best; - if(register_registered_server == -1) - { - dbg_msg("register", "WARNING: No master servers. Retrying in 60 seconds"); - register_new_state(REGISTERSTATE_ERROR); - } - else - { - dbg_msg("register", "choosen '%s' as master, sending heartbeats", mastersrv_name(register_registered_server)); - masterserver_info[register_registered_server].last_send = 0; - register_new_state(REGISTERSTATE_HEARTBEAT); - } - } - } - else if(register_state == REGISTERSTATE_HEARTBEAT) - { - /* check if we should send heartbeat */ - if(now > masterserver_info[register_registered_server].last_send+freq*15) - { - masterserver_info[register_registered_server].last_send = now; - register_send_heartbeat(masterserver_info[register_registered_server].addr); - } - - if(now > register_state_start+freq*60) - { - dbg_msg("register", "WARNING: Master server is not responding, switching master"); - register_new_state(REGISTERSTATE_START); - } - } - else if(register_state == REGISTERSTATE_REGISTERED) - { - if(register_first) - dbg_msg("register", "server registered"); - - register_first = 0; - - /* check if we should send new heartbeat again */ - if(now > register_state_start+freq) - { - if(register_count == 120) /* redo the whole process after 60 minutes to balance out the master servers */ - register_new_state(REGISTERSTATE_START); - else - { - register_count++; - register_new_state(REGISTERSTATE_HEARTBEAT); - } - } - } - else if(register_state == REGISTERSTATE_ERROR) - { - /* check for restart */ - if(now > register_state_start+freq*60) - register_new_state(REGISTERSTATE_START); - } -} - -static void register_got_count(CNetChunk *pChunk) -{ - unsigned char *pData = (unsigned char *)pChunk->m_pData; - int Count = (pData[sizeof(SERVERBROWSE_COUNT)]<<8) | pData[sizeof(SERVERBROWSE_COUNT)+1]; - - for(int i = 0; i < MAX_MASTERSERVERS; i++) - { - if(net_addr_comp(&masterserver_info[i].addr, &pChunk->m_Address) == 0) - { - masterserver_info[i].count = Count; - break; - } - } -} - -int register_process_packet(CNetChunk *pPacket) -{ - if(pPacket->m_DataSize == sizeof(SERVERBROWSE_FWCHECK) && - memcmp(pPacket->m_pData, SERVERBROWSE_FWCHECK, sizeof(SERVERBROWSE_FWCHECK)) == 0) - { - register_send_fwcheckresponse(&pPacket->m_Address); - return 1; - } - else if(pPacket->m_DataSize == sizeof(SERVERBROWSE_FWOK) && - memcmp(pPacket->m_pData, SERVERBROWSE_FWOK, sizeof(SERVERBROWSE_FWOK)) == 0) - { - if(register_first) - dbg_msg("register", "no firewall/nat problems detected"); - register_new_state(REGISTERSTATE_REGISTERED); - return 1; - } - else if(pPacket->m_DataSize == sizeof(SERVERBROWSE_FWERROR) && - memcmp(pPacket->m_pData, SERVERBROWSE_FWERROR, sizeof(SERVERBROWSE_FWERROR)) == 0) - { - dbg_msg("register", "ERROR: the master server reports that clients can not connect to this server."); - dbg_msg("register", "ERROR: configure your firewall/nat to let through udp on port %d.", config.sv_port); - register_new_state(REGISTERSTATE_ERROR); - return 1; - } - else if(pPacket->m_DataSize == sizeof(SERVERBROWSE_COUNT)+2 && - memcmp(pPacket->m_pData, SERVERBROWSE_COUNT, sizeof(SERVERBROWSE_COUNT)) == 0) - { - register_got_count(pPacket); - return 1; - } - - return 0; -} diff --git a/src/engine/server/es_server.cpp b/src/engine/server/es_server.cpp deleted file mode 100644 index cc0e6e0f..00000000 --- a/src/engine/server/es_server.cpp +++ /dev/null @@ -1,1969 +0,0 @@ -/* copyright (c) 2007 magnus auvinen, see licence.txt for more info */ -#include <stdio.h> -#include <string.h> -#include <stdlib.h> - -#include <base/system.h> - -#include <engine/e_config.h> -#include <engine/e_engine.h> -#include <engine/e_server_interface.h> - -#include <engine/e_protocol.h> -#include <engine/e_snapshot.h> - -#include <engine/e_compression.h> - -#include <engine/e_network.h> -#include <engine/e_config.h> -#include <engine/e_packer.h> -#include <engine/e_datafile.h> -#include <engine/e_demorec.h> - -#include <mastersrv/mastersrv.h> - -#if defined(CONF_FAMILY_WINDOWS) - #define _WIN32_WINNT 0x0500 - #include <windows.h> -#endif - - -extern int register_process_packet(CNetChunk *pPacket); -extern int register_update(); - -static const char *str_ltrim(const char *str) -{ - while(*str && *str <= 32) - str++; - return str; -} - -static void str_rtrim(char *str) -{ - int i = str_length(str); - while(i >= 0) - { - if(str[i] > 32) - break; - str[i] = 0; - i--; - } -} - - -class CSnapIDPool -{ - enum - { - MAX_IDS = 16*1024, - }; - - class CID - { - public: - short m_Next; - short m_State; /* 0 = free, 1 = alloced, 2 = timed */ - int m_Timeout; - }; - - CID m_aIDs[MAX_IDS]; - - int m_FirstFree; - int m_FirstTimed; - int m_LastTimed; - int m_Usage; - int m_InUsage; - -public: - - CSnapIDPool() - { - Reset(); - } - - void Reset() - { - for(int i = 0; i < MAX_IDS; i++) - { - m_aIDs[i].m_Next = i+1; - m_aIDs[i].m_State = 0; - } - - m_aIDs[MAX_IDS-1].m_Next = -1; - m_FirstFree = 0; - m_FirstTimed = -1; - m_LastTimed = -1; - m_Usage = 0; - m_InUsage = 0; - } - - - void RemoveFirstTimeout() - { - int NextTimed = m_aIDs[m_FirstTimed].m_Next; - - /* add it to the free list */ - m_aIDs[m_FirstTimed].m_Next = m_FirstFree; - m_aIDs[m_FirstTimed].m_State = 0; - m_FirstFree = m_FirstTimed; - - /* remove it from the timed list */ - m_FirstTimed = NextTimed; - if(m_FirstTimed == -1) - m_LastTimed = -1; - - m_Usage--; - } - - int NewID() - { - int64 now = time_get(); - - /* process timed ids */ - while(m_FirstTimed != -1 && m_aIDs[m_FirstTimed].m_Timeout < now) - RemoveFirstTimeout(); - - int id = m_FirstFree; - dbg_assert(id != -1, "id error"); - m_FirstFree = m_aIDs[m_FirstFree].m_Next; - m_aIDs[id].m_State = 1; - m_Usage++; - m_InUsage++; - return id; - } - - void TimeoutIDs() - { - /* process timed ids */ - while(m_FirstTimed != -1) - RemoveFirstTimeout(); - } - - void FreeID(int id) - { - dbg_assert(m_aIDs[id].m_State == 1, "id is not alloced"); - - m_InUsage--; - m_aIDs[id].m_State = 2; - m_aIDs[id].m_Timeout = time_get()+time_freq()*5; - m_aIDs[id].m_Next = -1; - - if(m_LastTimed != -1) - { - m_aIDs[m_LastTimed].m_Next = id; - m_LastTimed = id; - } - else - { - m_FirstTimed = id; - m_LastTimed = id; - } - } -}; - -#if 0 -class IGameServer -{ -public: - /* - Function: mods_console_init - TODO - */ - virtual void ConsoleInit(); - - /* - Function: Init - Called when the server is started. - - Remarks: - It's called after the map is loaded so all map items are available. - */ - void Init() = 0; - - /* - Function: Shutdown - Called when the server quits. - - Remarks: - Should be used to clean up all resources used. - */ - void Shutdown() = 0; - - /* - Function: ClientEnter - Called when a client has joined the game. - - Arguments: - cid - Client ID. Is 0 - MAX_CLIENTS. - - Remarks: - It's called when the client is finished loading and should enter gameplay. - */ - void ClientEnter(int cid) = 0; - - /* - Function: ClientDrop - Called when a client drops from the server. - - Arguments: - cid - Client ID. Is 0 - MAX_CLIENTS - */ - void ClientDrop(int cid) = 0; - - /* - Function: ClientDirectInput - Called when the server recives new input from a client. - - Arguments: - cid - Client ID. Is 0 - MAX_CLIENTS. - input - Pointer to the input data. - size - Size of the data. (NOT IMPLEMENTED YET) - */ - void ClientDirectInput(int cid, void *input) = 0; - - /* - Function: ClientPredictedInput - Called when the server applys the predicted input on the client. - - Arguments: - cid - Client ID. Is 0 - MAX_CLIENTS. - input - Pointer to the input data. - size - Size of the data. (NOT IMPLEMENTED YET) - */ - void ClientPredictedInput(int cid, void *input) = 0; - - - /* - Function: Tick - Called with a regular interval to progress the gameplay. - - Remarks: - The SERVER_TICK_SPEED tells the number of ticks per second. - */ - void Tick() = 0; - - /* - Function: Presnap - Called before the server starts to construct snapshots for the clients. - */ - void Presnap() = 0; - - /* - Function: Snap - Called to create the snapshot for a client. - - Arguments: - cid - Client ID. Is 0 - MAX_CLIENTS. - - Remarks: - The game should make a series of calls to <snap_new_item> to construct - the snapshot for the client. - */ - void Snap(int cid) = 0; - - /* - Function: PostSnap - Called after the server is done sending the snapshots. - */ - void PostSnap() = 0; - - /* - Function: ClientConnected - TODO - - Arguments: - arg1 - desc - - Returns: - - See Also: - <other_func> - */ - void ClientConnected(int client_id) = 0; - - - /* - Function: NetVersion - TODO - - Arguments: - arg1 - desc - - Returns: - - See Also: - <other_func> - */ - const char *NetVersion() = 0; - - /* - Function: Version - TODO - - Arguments: - arg1 - desc - - Returns: - - See Also: - <other_func> - */ - const char *Version() = 0; - - /* - Function: Message - TODO - - Arguments: - arg1 - desc - - Returns: - - See Also: - <other_func> - */ - void Message(int msg, int client_id) = 0; -}; - -#endif -/* -class IServer -{ -public: - BanAdd - BanRemove - TickSpeed - Tick - Kick - SetBrowseInfo - SetClientScore - SetClientName - GetClientInfo - LatestInput - ClientName - - SendMessage() - - Map - - virtual int NewSnapID() = 0; - virtual int FreeSnapID(int i) = 0; -};*/ - -class CServer -{ -public: - /* */ - class CClient - { - public: - - enum - { - STATE_EMPTY = 0, - STATE_AUTH, - STATE_CONNECTING, - STATE_READY, - STATE_INGAME, - - SNAPRATE_INIT=0, - SNAPRATE_FULL, - SNAPRATE_RECOVER - }; - - class CInput - { - public: - int m_aData[MAX_INPUT_SIZE]; - int m_GameTick; /* the tick that was chosen for the input */ - }; - - /* connection state info */ - int m_State; - int m_Latency; - int m_SnapRate; - - int m_LastAckedSnapshot; - int m_LastInputTick; - CSnapshotStorage m_Snapshots; - - CInput m_LatestInput; - CInput m_aInputs[200]; /* TODO: handle input better */ - int m_CurrentInput; - - char m_aName[MAX_NAME_LENGTH]; - char m_aClan[MAX_CLANNAME_LENGTH]; - int m_Score; - int m_Authed; - - void Reset() - { - /* reset input */ - for(int i = 0; i < 200; i++) - m_aInputs[i].m_GameTick = -1; - m_CurrentInput = 0; - mem_zero(&m_LatestInput, sizeof(m_LatestInput)); - - m_Snapshots.PurgeAll(); - m_LastAckedSnapshot = -1; - m_LastInputTick = -1; - m_SnapRate = CClient::SNAPRATE_INIT; - m_Score = 0; - } - }; - - CClient m_aClients[MAX_CLIENTS]; - - CSnapshotBuilder m_SnapshotBuilder; - CSnapIDPool m_IDPool; - CNetServer m_NetServer; - - int64 m_GameStartTime; - int m_CurrentTick; - int m_RunServer; - - char m_aBrowseinfoGametype[16]; - int m_BrowseinfoProgression; - - int64 m_Lastheartbeat; - /*static NETADDR4 master_server;*/ - - char m_aCurrentMap[64]; - int m_CurrentMapCrc; - unsigned char *m_pCurrentMapData; - int m_CurrentMapSize; - - CServer() - { - m_CurrentTick = 0; - m_RunServer = 1; - - mem_zero(m_aBrowseinfoGametype, sizeof(m_aBrowseinfoGametype)); - m_BrowseinfoProgression = -1; - - m_pCurrentMapData = 0; - m_CurrentMapSize = 0; - } - - - int TrySetClientName(int ClientID, const char *pName) - { - int i; - char aTrimmedName[64]; - - /* trim the name */ - str_copy(aTrimmedName, str_ltrim(pName), sizeof(aTrimmedName)); - str_rtrim(aTrimmedName); - dbg_msg("", "'%s' -> '%s'", pName, aTrimmedName); - pName = aTrimmedName; - - - /* check for empty names */ - if(!pName[0]) - return -1; - - /* make sure that two clients doesn't have the same name */ - for(i = 0; i < MAX_CLIENTS; i++) - if(i != ClientID && m_aClients[i].m_State >= CClient::STATE_READY) - { - if(strcmp(pName, m_aClients[i].m_aName) == 0) - return -1; - } - - /* set the client name */ - str_copy(m_aClients[ClientID].m_aName, pName, MAX_NAME_LENGTH); - return 0; - } - - - - void SetClientName(int ClientID, const char *pName) - { - if(ClientID < 0 || ClientID > MAX_CLIENTS || m_aClients[ClientID].m_State < CClient::STATE_READY) - return; - - if(!pName) - return; - - char aNameTry[MAX_NAME_LENGTH]; - str_copy(aNameTry, pName, MAX_NAME_LENGTH); - if(TrySetClientName(ClientID, aNameTry)) - { - /* auto rename */ - for(int i = 1;; i++) - { - str_format(aNameTry, MAX_NAME_LENGTH, "(%d)%s", i, pName); - if(TrySetClientName(ClientID, aNameTry) == 0) - break; - } - } - } - - void SetClientScore(int ClientID, int Score) - { - if(ClientID < 0 || ClientID > MAX_CLIENTS || m_aClients[ClientID].m_State < CClient::STATE_READY) - return; - m_aClients[ClientID].m_Score = Score; - } - - void SetBrowseInfo(const char *pGameType, int Progression) - { - str_copy(m_aBrowseinfoGametype, pGameType, sizeof(m_aBrowseinfoGametype)); - m_BrowseinfoProgression = Progression; - if(m_BrowseinfoProgression > 100) - m_BrowseinfoProgression = 100; - if(m_BrowseinfoProgression < -1) - m_BrowseinfoProgression = -1; - } - - void Kick(int ClientID, const char *pReason) - { - if(ClientID < 0 || ClientID > MAX_CLIENTS) - return; - - if(m_aClients[ClientID].m_State != CClient::STATE_EMPTY) - m_NetServer.Drop(ClientID, pReason); - } - - int Tick() - { - return m_CurrentTick; - } - - int64 TickStartTime(int Tick) - { - return m_GameStartTime + (time_freq()*Tick)/SERVER_TICK_SPEED; - } - - int TickSpeed() - { - return SERVER_TICK_SPEED; - } - - int Init() - { - int i; - for(i = 0; i < MAX_CLIENTS; i++) - { - m_aClients[i].m_State = CClient::STATE_EMPTY; - m_aClients[i].m_aName[0] = 0; - m_aClients[i].m_aClan[0] = 0; - m_aClients[i].m_Snapshots.Init(); - } - - m_CurrentTick = 0; - - return 0; - } - - int GetClientInfo(int ClientID, CLIENT_INFO *pInfo) - { - dbg_assert(ClientID >= 0 && ClientID < MAX_CLIENTS, "client_id is not valid"); - dbg_assert(pInfo != 0, "info can not be null"); - - if(m_aClients[ClientID].m_State == CClient::STATE_INGAME) - { - pInfo->name = m_aClients[ClientID].m_aName; - pInfo->latency = m_aClients[ClientID].m_Latency; - return 1; - } - return 0; - } - - int SendMsg(int ClientID) - { - const MSG_INFO *pInfo = msg_get_info(); - CNetChunk Packet; - if(!pInfo) - return -1; - - mem_zero(&Packet, sizeof(CNetChunk)); - - Packet.m_ClientID = ClientID; - Packet.m_pData = pInfo->data; - Packet.m_DataSize = pInfo->size; - - if(pInfo->flags&MSGFLAG_VITAL) - Packet.m_Flags |= NETSENDFLAG_VITAL; - if(pInfo->flags&MSGFLAG_FLUSH) - Packet.m_Flags |= NETSENDFLAG_FLUSH; - - /* write message to demo recorder */ - if(!(pInfo->flags&MSGFLAG_NORECORD)) - demorec_record_message(pInfo->data, pInfo->size); - - if(!(pInfo->flags&MSGFLAG_NOSEND)) - { - if(ClientID == -1) - { - /* broadcast */ - int i; - for(i = 0; i < MAX_CLIENTS; i++) - if(m_aClients[i].m_State == CClient::STATE_INGAME) - { - Packet.m_ClientID = i; - m_NetServer.Send(&Packet); - } - } - else - m_NetServer.Send(&Packet); - } - return 0; - } - - void DoSnapshot() - { - int i; - - { - static PERFORMACE_INFO scope = {"presnap", 0}; - perf_start(&scope); - mods_presnap(); - perf_end(); - } - - /* create snapshot for demo recording */ - if(demorec_isrecording()) - { - char data[CSnapshot::MAX_SIZE]; - int snapshot_size; - - /* build snap and possibly add some messages */ - m_SnapshotBuilder.Init(); - mods_snap(-1); - snapshot_size = m_SnapshotBuilder.Finish(data); - - /* write snapshot */ - demorec_record_snapshot(Tick(), data, snapshot_size); - } - - /* create snapshots for all clients */ - for(i = 0; i < MAX_CLIENTS; i++) - { - /* client must be ingame to recive snapshots */ - if(m_aClients[i].m_State != CClient::STATE_INGAME) - continue; - - /* this client is trying to recover, don't spam snapshots */ - if(m_aClients[i].m_SnapRate == CClient::SNAPRATE_RECOVER && (Tick()%50) != 0) - continue; - - /* this client is trying to recover, don't spam snapshots */ - if(m_aClients[i].m_SnapRate == CClient::SNAPRATE_INIT && (Tick()%10) != 0) - continue; - - { - char data[CSnapshot::MAX_SIZE]; - char deltadata[CSnapshot::MAX_SIZE]; - char compdata[CSnapshot::MAX_SIZE]; - int snapshot_size; - int crc; - static CSnapshot emptysnap; - CSnapshot *deltashot = &emptysnap; - int deltashot_size; - int delta_tick = -1; - int deltasize; - static PERFORMACE_INFO scope = {"build", 0}; - perf_start(&scope); - - m_SnapshotBuilder.Init(); - - { - static PERFORMACE_INFO scope = {"modsnap", 0}; - perf_start(&scope); - mods_snap(i); - perf_end(); - } - - /* finish snapshot */ - snapshot_size = m_SnapshotBuilder.Finish(data); - crc = ((CSnapshot*)data)->Crc(); - - /* remove old snapshos */ - /* keep 3 seconds worth of snapshots */ - m_aClients[i].m_Snapshots.PurgeUntil(m_CurrentTick-SERVER_TICK_SPEED*3); - - /* save it the snapshot */ - m_aClients[i].m_Snapshots.Add(m_CurrentTick, time_get(), snapshot_size, data, 0); - - /* find snapshot that we can preform delta against */ - emptysnap.m_DataSize = 0; - emptysnap.m_NumItems = 0; - - { - deltashot_size = m_aClients[i].m_Snapshots.Get(m_aClients[i].m_LastAckedSnapshot, 0, &deltashot, 0); - if(deltashot_size >= 0) - delta_tick = m_aClients[i].m_LastAckedSnapshot; - else - { - /* no acked package found, force client to recover rate */ - if(m_aClients[i].m_SnapRate == CClient::SNAPRATE_FULL) - m_aClients[i].m_SnapRate = CClient::SNAPRATE_RECOVER; - } - } - - /* create delta */ - { - static PERFORMACE_INFO scope = {"delta", 0}; - perf_start(&scope); - deltasize = CSnapshot::CreateDelta(deltashot, (CSnapshot*)data, deltadata); - perf_end(); - } - - - if(deltasize) - { - /* compress it */ - int snapshot_size; - const int max_size = MAX_SNAPSHOT_PACKSIZE; - int numpackets; - int n, left; - - { - static PERFORMACE_INFO scope = {"compress", 0}; - /*char buffer[512];*/ - perf_start(&scope); - snapshot_size = intpack_compress(deltadata, deltasize, compdata); - - /*str_hex(buffer, sizeof(buffer), compdata, snapshot_size); - dbg_msg("", "deltasize=%d -> %d : %s", deltasize, snapshot_size, buffer);*/ - - perf_end(); - } - - numpackets = (snapshot_size+max_size-1)/max_size; - - for(n = 0, left = snapshot_size; left; n++) - { - int chunk = left < max_size ? left : max_size; - left -= chunk; - - if(numpackets == 1) - msg_pack_start_system(NETMSG_SNAPSINGLE, MSGFLAG_FLUSH); - else - msg_pack_start_system(NETMSG_SNAP, MSGFLAG_FLUSH); - - msg_pack_int(m_CurrentTick); - msg_pack_int(m_CurrentTick-delta_tick); /* compressed with */ - - if(numpackets != 1) - { - msg_pack_int(numpackets); - msg_pack_int(n); - } - - msg_pack_int(crc); - msg_pack_int(chunk); - msg_pack_raw(&compdata[n*max_size], chunk); - msg_pack_end(); - SendMsg(i); - } - } - else - { - msg_pack_start_system(NETMSG_SNAPEMPTY, MSGFLAG_FLUSH); - msg_pack_int(m_CurrentTick); - msg_pack_int(m_CurrentTick-delta_tick); /* compressed with */ - msg_pack_end(); - SendMsg(i); - } - - perf_end(); - } - } - - mods_postsnap(); - } - - - static int NewClientCallback(int cid, void *pUser) - { - CServer *pThis = (CServer *)pUser; - pThis->m_aClients[cid].m_State = CClient::STATE_AUTH; - pThis->m_aClients[cid].m_aName[0] = 0; - pThis->m_aClients[cid].m_aClan[0] = 0; - pThis->m_aClients[cid].m_Authed = 0; - pThis->m_aClients[cid].Reset(); - return 0; - } - - static int DelClientCallback(int cid, void *pUser) - { - CServer *pThis = (CServer *)pUser; - - /* notify the mod about the drop */ - if(pThis->m_aClients[cid].m_State >= CClient::STATE_READY) - mods_client_drop(cid); - - pThis->m_aClients[cid].m_State = CClient::STATE_EMPTY; - pThis->m_aClients[cid].m_aName[0] = 0; - pThis->m_aClients[cid].m_aClan[0] = 0; - pThis->m_aClients[cid].m_Authed = 0; - pThis->m_aClients[cid].m_Snapshots.PurgeAll(); - return 0; - } - - void SendMap(int cid) - { - msg_pack_start_system(NETMSG_MAP_CHANGE, MSGFLAG_VITAL|MSGFLAG_FLUSH); - msg_pack_string(config.sv_map, 0); - msg_pack_int(m_CurrentMapCrc); - msg_pack_end(); - server_send_msg(cid); - } - - void SendRconLine(int cid, const char *pLine) - { - msg_pack_start_system(NETMSG_RCON_LINE, MSGFLAG_VITAL); - msg_pack_string(pLine, 512); - msg_pack_end(); - server_send_msg(cid); - } - - static void SendRconLineAuthed(const char *pLine, void *pUser) - { - CServer *pThis = (CServer *)pUser; - static volatile int reentry_guard = 0; - int i; - - if(reentry_guard) return; - reentry_guard++; - - for(i = 0; i < MAX_CLIENTS; i++) - { - if(pThis->m_aClients[i].m_State != CClient::STATE_EMPTY && pThis->m_aClients[i].m_Authed) - pThis->SendRconLine(i, pLine); - } - - reentry_guard--; - } - - void ProcessClientPacket(CNetChunk *pPacket) - { - int cid = pPacket->m_ClientID; - NETADDR addr; - - int sys; - int msg = msg_unpack_start(pPacket->m_pData, pPacket->m_DataSize, &sys); - - if(m_aClients[cid].m_State == CClient::STATE_AUTH) - { - if(sys && msg == NETMSG_INFO) - { - char version[64]; - const char *password; - str_copy(version, msg_unpack_string(), 64); - if(strcmp(version, mods_net_version()) != 0) - { - /* OH FUCK! wrong version, drop him */ - char reason[256]; - str_format(reason, sizeof(reason), "wrong version. server is running '%s' and client '%s'.", mods_net_version(), version); - m_NetServer.Drop(cid, reason); - return; - } - - str_copy(m_aClients[cid].m_aName, msg_unpack_string(), MAX_NAME_LENGTH); - str_copy(m_aClients[cid].m_aClan, msg_unpack_string(), MAX_CLANNAME_LENGTH); - password = msg_unpack_string(); - - if(config.password[0] != 0 && strcmp(config.password, password) != 0) - { - /* wrong password */ - m_NetServer.Drop(cid, "wrong password"); - return; - } - - m_aClients[cid].m_State = CClient::STATE_CONNECTING; - SendMap(cid); - } - } - else - { - if(sys) - { - /* system message */ - if(msg == NETMSG_REQUEST_MAP_DATA) - { - int chunk = msg_unpack_int(); - int chunk_size = 1024-128; - int offset = chunk * chunk_size; - int last = 0; - - /* drop faulty map data requests */ - if(chunk < 0 || offset > m_CurrentMapSize) - return; - - if(offset+chunk_size >= m_CurrentMapSize) - { - chunk_size = m_CurrentMapSize-offset; - if(chunk_size < 0) - chunk_size = 0; - last = 1; - } - - msg_pack_start_system(NETMSG_MAP_DATA, MSGFLAG_VITAL|MSGFLAG_FLUSH); - msg_pack_int(last); - msg_pack_int(m_CurrentMapSize); - msg_pack_int(chunk_size); - msg_pack_raw(&m_pCurrentMapData[offset], chunk_size); - msg_pack_end(); - server_send_msg(cid); - - if(config.debug) - dbg_msg("server", "sending chunk %d with size %d", chunk, chunk_size); - } - else if(msg == NETMSG_READY) - { - if(m_aClients[cid].m_State == CClient::STATE_CONNECTING) - { - addr = m_NetServer.ClientAddr(cid); - - dbg_msg("server", "player is ready. cid=%x ip=%d.%d.%d.%d", - cid, - addr.ip[0], addr.ip[1], addr.ip[2], addr.ip[3] - ); - m_aClients[cid].m_State = CClient::STATE_READY; - mods_connected(cid); - } - } - else if(msg == NETMSG_ENTERGAME) - { - if(m_aClients[cid].m_State == CClient::STATE_READY) - { - addr = m_NetServer.ClientAddr(cid); - - dbg_msg("server", "player has entered the game. cid=%x ip=%d.%d.%d.%d", - cid, - addr.ip[0], addr.ip[1], addr.ip[2], addr.ip[3] - ); - m_aClients[cid].m_State = CClient::STATE_INGAME; - mods_client_enter(cid); - } - } - else if(msg == NETMSG_INPUT) - { - int tick, size, i; - CClient::CInput *pInput; - int64 tagtime; - - m_aClients[cid].m_LastAckedSnapshot = msg_unpack_int(); - tick = msg_unpack_int(); - size = msg_unpack_int(); - - /* check for errors */ - if(msg_unpack_error() || size/4 > MAX_INPUT_SIZE) - return; - - if(m_aClients[cid].m_LastAckedSnapshot > 0) - m_aClients[cid].m_SnapRate = CClient::SNAPRATE_FULL; - - if(m_aClients[cid].m_Snapshots.Get(m_aClients[cid].m_LastAckedSnapshot, &tagtime, 0, 0) >= 0) - m_aClients[cid].m_Latency = (int)(((time_get()-tagtime)*1000)/time_freq()); - - /* add message to report the input timing */ - /* skip packets that are old */ - if(tick > m_aClients[cid].m_LastInputTick) - { - int time_left = ((TickStartTime(tick)-time_get())*1000) / time_freq(); - msg_pack_start_system(NETMSG_INPUTTIMING, 0); - msg_pack_int(tick); - msg_pack_int(time_left); - msg_pack_end(); - server_send_msg(cid); - } - - m_aClients[cid].m_LastInputTick = tick; - - pInput = &m_aClients[cid].m_aInputs[m_aClients[cid].m_CurrentInput]; - - if(tick <= server_tick()) - tick = server_tick()+1; - - pInput->m_GameTick = tick; - - for(i = 0; i < size/4; i++) - pInput->m_aData[i] = msg_unpack_int(); - - mem_copy(m_aClients[cid].m_LatestInput.m_aData, pInput->m_aData, MAX_INPUT_SIZE*sizeof(int)); - - m_aClients[cid].m_CurrentInput++; - m_aClients[cid].m_CurrentInput %= 200; - - /* call the mod with the fresh input data */ - if(m_aClients[cid].m_State == CClient::STATE_INGAME) - mods_client_direct_input(cid, m_aClients[cid].m_LatestInput.m_aData); - } - else if(msg == NETMSG_RCON_CMD) - { - const char *cmd = msg_unpack_string(); - - if(msg_unpack_error() == 0 && m_aClients[cid].m_Authed) - { - dbg_msg("server", "cid=%d rcon='%s'", cid, cmd); - console_execute_line(cmd); - } - } - else if(msg == NETMSG_RCON_AUTH) - { - const char *pw; - msg_unpack_string(); /* login name, not used */ - pw = msg_unpack_string(); - - if(msg_unpack_error() == 0) - { - if(config.sv_rcon_password[0] == 0) - { - SendRconLine(cid, "No rcon password set on server. Set sv_rcon_password to enable the remote console."); - } - else if(strcmp(pw, config.sv_rcon_password) == 0) - { - msg_pack_start_system(NETMSG_RCON_AUTH_STATUS, MSGFLAG_VITAL); - msg_pack_int(1); - msg_pack_end(); - server_send_msg(cid); - - m_aClients[cid].m_Authed = 1; - SendRconLine(cid, "Authentication successful. Remote console access granted."); - dbg_msg("server", "cid=%d authed", cid); - } - else - { - SendRconLine(cid, "Wrong password."); - } - } - } - else if(msg == NETMSG_PING) - { - msg_pack_start_system(NETMSG_PING_REPLY, 0); - msg_pack_end(); - server_send_msg(cid); - } - else - { - char hex[] = "0123456789ABCDEF"; - char buf[512]; - int b; - - for(b = 0; b < pPacket->m_DataSize && b < 32; b++) - { - buf[b*3] = hex[((const unsigned char *)pPacket->m_pData)[b]>>4]; - buf[b*3+1] = hex[((const unsigned char *)pPacket->m_pData)[b]&0xf]; - buf[b*3+2] = ' '; - buf[b*3+3] = 0; - } - - dbg_msg("server", "strange message cid=%d msg=%d data_size=%d", cid, msg, pPacket->m_DataSize); - dbg_msg("server", "%s", buf); - - } - } - else - { - /* game message */ - if(m_aClients[cid].m_State >= CClient::STATE_READY) - mods_message(msg, cid); - } - } - } - - void SendServerInfo(NETADDR *addr, int token) - { - CNetChunk Packet; - CPacker p; - char buf[128]; - - /* count the players */ - int player_count = 0; - int i; - for(i = 0; i < MAX_CLIENTS; i++) - { - if(m_aClients[i].m_State != CClient::STATE_EMPTY) - player_count++; - } - - p.Reset(); - - if(token >= 0) - { - /* new token based format */ - p.AddRaw(SERVERBROWSE_INFO, sizeof(SERVERBROWSE_INFO)); - str_format(buf, sizeof(buf), "%d", token); - p.AddString(buf, 6); - } - else - { - /* old format */ - p.AddRaw(SERVERBROWSE_OLD_INFO, sizeof(SERVERBROWSE_OLD_INFO)); - } - - p.AddString(mods_version(), 32); - p.AddString(config.sv_name, 64); - p.AddString(config.sv_map, 32); - - /* gametype */ - p.AddString(m_aBrowseinfoGametype, 16); - - /* flags */ - i = 0; - if(config.password[0]) /* password set */ - i |= SRVFLAG_PASSWORD; - str_format(buf, sizeof(buf), "%d", i); - p.AddString(buf, 2); - - /* progression */ - str_format(buf, sizeof(buf), "%d", m_BrowseinfoProgression); - p.AddString(buf, 4); - - str_format(buf, sizeof(buf), "%d", player_count); p.AddString(buf, 3); /* num players */ - str_format(buf, sizeof(buf), "%d", m_NetServer.MaxClients()); p.AddString(buf, 3); /* max players */ - - for(i = 0; i < MAX_CLIENTS; i++) - { - if(m_aClients[i].m_State != CClient::STATE_EMPTY) - { - p.AddString(m_aClients[i].m_aName, 48); /* player name */ - str_format(buf, sizeof(buf), "%d", m_aClients[i].m_Score); p.AddString(buf, 6); /* player score */ - } - } - - - Packet.m_ClientID = -1; - Packet.m_Address = *addr; - Packet.m_Flags = NETSENDFLAG_CONNLESS; - Packet.m_DataSize = p.Size(); - Packet.m_pData = p.Data(); - m_NetServer.Send(&Packet); - } - - int BanAdd(NETADDR Addr, int Seconds) - { - return m_NetServer.BanAdd(Addr, Seconds); - } - - int BanRemove(NETADDR Addr) - { - return m_NetServer.BanRemove(Addr); - } - - - void PumpNetwork() - { - CNetChunk Packet; - - m_NetServer.Update(); - - /* process packets */ - while(m_NetServer.Recv(&Packet)) - { - if(Packet.m_ClientID == -1) - { - /* stateless */ - if(!register_process_packet(&Packet)) - { - if(Packet.m_DataSize == sizeof(SERVERBROWSE_GETINFO)+1 && - memcmp(Packet.m_pData, SERVERBROWSE_GETINFO, sizeof(SERVERBROWSE_GETINFO)) == 0) - { - SendServerInfo(&Packet.m_Address, ((unsigned char *)Packet.m_pData)[sizeof(SERVERBROWSE_GETINFO)]); - } - - - if(Packet.m_DataSize == sizeof(SERVERBROWSE_OLD_GETINFO) && - memcmp(Packet.m_pData, SERVERBROWSE_OLD_GETINFO, sizeof(SERVERBROWSE_OLD_GETINFO)) == 0) - { - SendServerInfo(&Packet.m_Address, -1); - } - } - } - else - ProcessClientPacket(&Packet); - } - } - - int LoadMap(const char *pMapName) - { - DATAFILE *df; - char buf[512]; - str_format(buf, sizeof(buf), "maps/%s.map", pMapName); - df = datafile_load(buf); - if(!df) - return 0; - - /* stop recording when we change map */ - demorec_record_stop(); - - /* reinit snapshot ids */ - m_IDPool.TimeoutIDs(); - - /* get the crc of the map */ - m_CurrentMapCrc = datafile_crc(buf); - dbg_msg("server", "%s crc is %08x", buf, m_CurrentMapCrc); - - str_copy(m_aCurrentMap, pMapName, sizeof(m_aCurrentMap)); - map_set(df); - - /* load compelate map into memory for download */ - { - IOHANDLE file = engine_openfile(buf, IOFLAG_READ); - m_CurrentMapSize = (int)io_length(file); - if(m_pCurrentMapData) - mem_free(m_pCurrentMapData); - m_pCurrentMapData = (unsigned char *)mem_alloc(m_CurrentMapSize, 1); - io_read(file, m_pCurrentMapData, m_CurrentMapSize); - io_close(file); - } - return 1; - } - - int Run() - { - NETADDR bindaddr; - - //snap_init_id(); - net_init(); - - /* */ - console_register_print_callback(SendRconLineAuthed, this); - - /* load map */ - if(!LoadMap(config.sv_map)) - { - dbg_msg("server", "failed to load map. mapname='%s'", config.sv_map); - return -1; - } - - /* start server */ - /* TODO: IPv6 support */ - if(config.sv_bindaddr[0] && net_host_lookup(config.sv_bindaddr, &bindaddr, NETTYPE_IPV4) == 0) - { - /* sweet! */ - bindaddr.port = config.sv_port; - } - else - { - mem_zero(&bindaddr, sizeof(bindaddr)); - bindaddr.port = config.sv_port; - } - - - if(!m_NetServer.Open(bindaddr, config.sv_max_clients, 0)) - { - dbg_msg("server", "couldn't open socket. port might already be in use"); - return -1; - } - - m_NetServer.SetCallbacks(NewClientCallback, DelClientCallback, this); - - dbg_msg("server", "server name is '%s'", config.sv_name); - - mods_init(); - dbg_msg("server", "version %s", mods_net_version()); - - /* start game */ - { - int64 reporttime = time_get(); - int reportinterval = 3; - - m_Lastheartbeat = 0; - m_GameStartTime = time_get(); - - if(config.debug) - dbg_msg("server", "baseline memory usage %dk", mem_stats()->allocated/1024); - - while(m_RunServer) - { - static PERFORMACE_INFO rootscope = {"root", 0}; - int64 t = time_get(); - int new_ticks = 0; - - perf_start(&rootscope); - - /* load new map TODO: don't poll this */ - if(strcmp(config.sv_map, m_aCurrentMap) != 0 || config.sv_map_reload) - { - config.sv_map_reload = 0; - - /* load map */ - if(LoadMap(config.sv_map)) - { - int c; - - /* new map loaded */ - mods_shutdown(); - - for(c = 0; c < MAX_CLIENTS; c++) - { - if(m_aClients[c].m_State == CClient::STATE_EMPTY) - continue; - - SendMap(c); - m_aClients[c].Reset(); - m_aClients[c].m_State = CClient::STATE_CONNECTING; - } - - m_GameStartTime = time_get(); - m_CurrentTick = 0; - mods_init(); - } - else - { - dbg_msg("server", "failed to load map. mapname='%s'", config.sv_map); - config_set_sv_map(&config, m_aCurrentMap); - } - } - - while(t > TickStartTime(m_CurrentTick+1)) - { - m_CurrentTick++; - new_ticks++; - - /* apply new input */ - { - static PERFORMACE_INFO scope = {"input", 0}; - int c, i; - - perf_start(&scope); - - for(c = 0; c < MAX_CLIENTS; c++) - { - if(m_aClients[c].m_State == CClient::STATE_EMPTY) - continue; - for(i = 0; i < 200; i++) - { - if(m_aClients[c].m_aInputs[i].m_GameTick == server_tick()) - { - if(m_aClients[c].m_State == CClient::STATE_INGAME) - mods_client_predicted_input(c, m_aClients[c].m_aInputs[i].m_aData); - break; - } - } - } - - perf_end(); - } - - /* progress game */ - { - static PERFORMACE_INFO scope = {"tick", 0}; - perf_start(&scope); - mods_tick(); - perf_end(); - } - } - - /* snap game */ - if(new_ticks) - { - if(config.sv_high_bandwidth || (m_CurrentTick%2) == 0) - { - static PERFORMACE_INFO scope = {"snap", 0}; - perf_start(&scope); - DoSnapshot(); - perf_end(); - } - } - - /* master server stuff */ - register_update(); - - - { - static PERFORMACE_INFO scope = {"net", 0}; - perf_start(&scope); - PumpNetwork(); - perf_end(); - } - - perf_end(); - - if(reporttime < time_get()) - { - if(config.debug) - { - /* - static NETSTATS prev_stats; - NETSTATS stats; - netserver_stats(net, &stats); - - perf_next(); - - if(config.dbg_pref) - perf_dump(&rootscope); - - dbg_msg("server", "send=%8d recv=%8d", - (stats.send_bytes - prev_stats.send_bytes)/reportinterval, - (stats.recv_bytes - prev_stats.recv_bytes)/reportinterval); - - prev_stats = stats; - */ - } - - reporttime += time_freq()*reportinterval; - } - - /* wait for incomming data */ - net_socket_read_wait(m_NetServer.Socket(), 5); - } - } - - mods_shutdown(); - map_unload(); - - if(m_pCurrentMapData) - mem_free(m_pCurrentMapData); - return 0; - } - - static void ConKick(void *pResult, void *pUser) - { - ((CServer *)pUser)->Kick(console_arg_int(pResult, 0), "kicked by console"); - } - - static int str_allnum(const char *str) - { - while(*str) - { - if(!(*str >= '0' && *str <= '9')) - return 0; - str++; - } - return 1; - } - - static void ConBan(void *pResult, void *pUser) - { - NETADDR addr; - char addrstr[128]; - const char *str = console_arg_string(pResult, 0); - int minutes = 30; - - if(console_arg_num(pResult) > 1) - minutes = console_arg_int(pResult, 1); - - if(net_addr_from_str(&addr, str) == 0) - ((CServer *)pUser)->BanAdd(addr, minutes*60); - else if(str_allnum(str)) - { - NETADDR addr; - int cid = atoi(str); - - if(cid < 0 || cid > MAX_CLIENTS || ((CServer *)pUser)->m_aClients[cid].m_State == CClient::STATE_EMPTY) - { - dbg_msg("server", "invalid client id"); - return; - } - - addr = ((CServer *)pUser)->m_NetServer.ClientAddr(cid); - ((CServer *)pUser)->BanAdd(addr, minutes*60); - } - - addr.port = 0; - net_addr_str(&addr, addrstr, sizeof(addrstr)); - - if(minutes) - dbg_msg("server", "banned %s for %d minutes", addrstr, minutes); - else - dbg_msg("server", "banned %s for life", addrstr); - } - - static void ConUnban(void *result, void *pUser) - { - NETADDR addr; - const char *str = console_arg_string(result, 0); - - if(net_addr_from_str(&addr, str) == 0) - ((CServer *)pUser)->BanRemove(addr); - else - dbg_msg("server", "invalid network address"); - } - - static void ConBans(void *pResult, void *pUser) - { - unsigned now = time_timestamp(); - - int num = ((CServer *)pUser)->m_NetServer.BanNum(); - for(int i = 0; i < num; i++) - { - CNetServer::CBanInfo Info; - ((CServer *)pUser)->m_NetServer.BanGet(i, &Info); - NETADDR Addr = Info.m_Addr; - - if(Info.m_Expires == -1) - { - dbg_msg("server", "#%d %d.%d.%d.%d for life", i, Addr.ip[0], Addr.ip[1], Addr.ip[2], Addr.ip[3]); - } - else - { - unsigned t = Info.m_Expires - now; - dbg_msg("server", "#%d %d.%d.%d.%d for %d minutes and %d seconds", i, Addr.ip[0], Addr.ip[1], Addr.ip[2], Addr.ip[3], t/60, t%60); - } - } - dbg_msg("server", "%d ban(s)", num); - } - - static void ConStatus(void *pResult, void *pUser) - { - int i; - NETADDR addr; - for(i = 0; i < MAX_CLIENTS; i++) - { - if(((CServer *)pUser)->m_aClients[i].m_State == CClient::STATE_INGAME) - { - addr = ((CServer *)pUser)->m_NetServer.ClientAddr(i); - dbg_msg("server", "id=%d addr=%d.%d.%d.%d:%d name='%s' score=%d", - i, addr.ip[0], addr.ip[1], addr.ip[2], addr.ip[3], addr.port, - ((CServer *)pUser)->m_aClients[i].m_aName, ((CServer *)pUser)->m_aClients[i].m_Score); - } - } - } - - static void ConShutdown(void *pResult, void *pUser) - { - ((CServer *)pUser)->m_RunServer = 0; - } - - static void ConRecord(void *pResult, void *pUser) - { - char filename[512]; - str_format(filename, sizeof(filename), "demos/%s.demo", console_arg_string(pResult, 0)); - demorec_record_start(filename, mods_net_version(), ((CServer *)pUser)->m_aCurrentMap, ((CServer *)pUser)->m_CurrentMapCrc, "server"); - } - - static void ConStopRecord(void *pResult, void *pUser) - { - demorec_record_stop(); - } - - - void RegisterCommands() - { - MACRO_REGISTER_COMMAND("kick", "i", CFGFLAG_SERVER, ConKick, this, ""); - MACRO_REGISTER_COMMAND("ban", "s?i", CFGFLAG_SERVER, ConBan, this, ""); - MACRO_REGISTER_COMMAND("unban", "s", CFGFLAG_SERVER, ConUnban, this, ""); - MACRO_REGISTER_COMMAND("bans", "", CFGFLAG_SERVER, ConBans, this, ""); - MACRO_REGISTER_COMMAND("status", "", CFGFLAG_SERVER, ConStatus, this, ""); - MACRO_REGISTER_COMMAND("shutdown", "", CFGFLAG_SERVER, ConShutdown, this, ""); - - MACRO_REGISTER_COMMAND("record", "s", CFGFLAG_SERVER, ConRecord, this, ""); - MACRO_REGISTER_COMMAND("stoprecord", "", CFGFLAG_SERVER, ConStopRecord, this, ""); - } - -}; - - -// UGLY UGLY HACK for now -CServer g_Server; -CNetServer *m_pNetServer; - -int server_tick() { return g_Server.Tick(); } -int server_tickspeed() { return g_Server.TickSpeed(); } -int snap_new_id() { return g_Server.m_IDPool.NewID(); } -void snap_free_id(int id) { return g_Server.m_IDPool.FreeID(id); } -int server_send_msg(int client_id) { return g_Server.SendMsg(client_id); } -void server_setbrowseinfo(const char *game_type, int progression) { g_Server.SetBrowseInfo(game_type, progression); } -void server_setclientname(int client_id, const char *name) { g_Server.SetClientName(client_id, name); } -void server_setclientscore(int client_id, int score) { g_Server.SetClientScore(client_id, score); } - -int server_getclientinfo(int client_id, CLIENT_INFO *info) { return g_Server.GetClientInfo(client_id, info); } - -void *snap_new_item(int type, int id, int size) -{ - dbg_assert(type >= 0 && type <=0xffff, "incorrect type"); - dbg_assert(id >= 0 && id <=0xffff, "incorrect id"); - return g_Server.m_SnapshotBuilder.NewItem(type, id, size); -} - -int *server_latestinput(int client_id, int *size) -{ - if(client_id < 0 || client_id > MAX_CLIENTS || g_Server.m_aClients[client_id].m_State < CServer::CClient::STATE_READY) - return 0; - return g_Server.m_aClients[client_id].m_LatestInput.m_aData; -} - -const char *server_clientname(int client_id) -{ - if(client_id < 0 || client_id > MAX_CLIENTS || g_Server.m_aClients[client_id].m_State < CServer::CClient::STATE_READY) - return "(invalid client)"; - return g_Server.m_aClients[client_id].m_aName; -} - -#if 0 - - -static void reset_client(int cid) -{ - /* reset input */ - int i; - for(i = 0; i < 200; i++) - m_aClients[cid].m_aInputs[i].m_GameTick = -1; - m_aClients[cid].m_CurrentInput = 0; - mem_zero(&m_aClients[cid].m_Latestinput, sizeof(m_aClients[cid].m_Latestinput)); - - m_aClients[cid].m_Snapshots.PurgeAll(); - m_aClients[cid].m_LastAckedSnapshot = -1; - m_aClients[cid].m_LastInputTick = -1; - m_aClients[cid].m_SnapRate = CClient::SNAPRATE_INIT; - m_aClients[cid].m_Score = 0; - -} - - -static int new_client_callback(int cid, void *user) -{ - m_aClients[cid].state = CClient::STATE_AUTH; - m_aClients[cid].name[0] = 0; - m_aClients[cid].clan[0] = 0; - m_aClients[cid].authed = 0; - reset_client(cid); - return 0; -} - -static int del_client_callback(int cid, void *user) -{ - /* notify the mod about the drop */ - if(m_aClients[cid].state >= CClient::STATE_READY) - mods_client_drop(cid); - - m_aClients[cid].state = CClient::STATE_EMPTY; - m_aClients[cid].name[0] = 0; - m_aClients[cid].clan[0] = 0; - m_aClients[cid].authed = 0; - m_aClients[cid].snapshots.PurgeAll(); - return 0; -} -static void server_send_map(int cid) -{ - msg_pack_start_system(NETMSG_MAP_CHANGE, MSGFLAG_VITAL|MSGFLAG_FLUSH); - msg_pack_string(config.sv_map, 0); - msg_pack_int(current_map_crc); - msg_pack_end(); - server_send_msg(cid); -} - -static void server_send_rcon_line(int cid, const char *line) -{ - msg_pack_start_system(NETMSG_RCON_LINE, MSGFLAG_VITAL); - msg_pack_string(line, 512); - msg_pack_end(); - server_send_msg(cid); -} - -static void server_send_rcon_line_authed(const char *line, void *user_data) -{ - static volatile int reentry_guard = 0; - int i; - - if(reentry_guard) return; - reentry_guard++; - - for(i = 0; i < MAX_CLIENTS; i++) - { - if(m_aClients[i].state != CClient::STATE_EMPTY && m_aClients[i].authed) - server_send_rcon_line(i, line); - } - - reentry_guard--; -} - -static void server_pump_network() -{ - CNetChunk Packet; - - m_NetServer.Update(); - - /* process packets */ - while(m_NetServer.Recv(&Packet)) - { - if(Packet.m_ClientID == -1) - { - /* stateless */ - if(!register_process_packet(&Packet)) - { - if(Packet.m_DataSize == sizeof(SERVERBROWSE_GETINFO)+1 && - memcmp(Packet.m_pData, SERVERBROWSE_GETINFO, sizeof(SERVERBROWSE_GETINFO)) == 0) - { - server_send_serverinfo(&Packet.m_Address, ((unsigned char *)Packet.m_pData)[sizeof(SERVERBROWSE_GETINFO)]); - } - - - if(Packet.m_DataSize == sizeof(SERVERBROWSE_OLD_GETINFO) && - memcmp(Packet.m_pData, SERVERBROWSE_OLD_GETINFO, sizeof(SERVERBROWSE_OLD_GETINFO)) == 0) - { - server_send_serverinfo(&Packet.m_Address, -1); - } - } - } - else - server_process_client_packet(&Packet); - } -} - -static int server_load_map(const char *mapname) -{ - DATAFILE *df; - char buf[512]; - str_format(buf, sizeof(buf), "maps/%s.map", mapname); - df = datafile_load(buf); - if(!df) - return 0; - - /* stop recording when we change map */ - demorec_record_stop(); - - /* reinit snapshot ids */ - snap_timeout_ids(); - - /* get the crc of the map */ - current_map_crc = datafile_crc(buf); - dbg_msg("server", "%s crc is %08x", buf, current_map_crc); - - str_copy(current_map, mapname, sizeof(current_map)); - map_set(df); - - /* load compelate map into memory for download */ - { - IOHANDLE file = engine_openfile(buf, IOFLAG_READ); - current_map_size = (int)io_length(file); - if(current_map_data) - mem_free(current_map_data); - current_map_data = (unsigned char *)mem_alloc(current_map_size, 1); - io_read(file, current_map_data, current_map_size); - io_close(file); - } - return 1; -} - -static int server_run() -{ - NETADDR bindaddr; - - snap_init_id(); - net_init(); - - /* */ - console_register_print_callback(server_send_rcon_line_authed, 0); - - /* load map */ - if(!server_load_map(config.sv_map)) - { - dbg_msg("server", "failed to load map. mapname='%s'", config.sv_map); - return -1; - } - - /* start server */ - /* TODO: IPv6 support */ - if(config.sv_bindaddr[0] && net_host_lookup(config.sv_bindaddr, &bindaddr, NETTYPE_IPV4) == 0) - { - /* sweet! */ - bindaddr.port = config.sv_port; - } - else - { - mem_zero(&bindaddr, sizeof(bindaddr)); - bindaddr.port = config.sv_port; - } - - - if(!m_NetServer.Open(bindaddr, config.sv_max_clients, 0)) - { - dbg_msg("server", "couldn't open socket. port might already be in use"); - return -1; - } - - m_NetServer.SetCallbacks(new_client_callback, del_client_callback, this); - - dbg_msg("server", "server name is '%s'", config.sv_name); - - mods_init(); - dbg_msg("server", "version %s", mods_net_version()); - - /* start game */ - { - int64 reporttime = time_get(); - int reportinterval = 3; - - lastheartbeat = 0; - game_start_time = time_get(); - - if(config.debug) - dbg_msg("server", "baseline memory usage %dk", mem_stats()->allocated/1024); - - while(run_server) - { - static PERFORMACE_INFO rootscope = {"root", 0}; - int64 t = time_get(); - int new_ticks = 0; - - perf_start(&rootscope); - - /* load new map TODO: don't poll this */ - if(strcmp(config.sv_map, current_map) != 0 || config.sv_map_reload) - { - config.sv_map_reload = 0; - - /* load map */ - if(server_load_map(config.sv_map)) - { - int c; - - /* new map loaded */ - mods_shutdown(); - - for(c = 0; c < MAX_CLIENTS; c++) - { - if(m_aClients[c].state == CClient::STATE_EMPTY) - continue; - - server_send_map(c); - reset_client(c); - m_aClients[c].state = CClient::STATE_CONNECTING; - } - - game_start_time = time_get(); - current_tick = 0; - mods_init(); - } - else - { - dbg_msg("server", "failed to load map. mapname='%s'", config.sv_map); - config_set_sv_map(&config, current_map); - } - } - - while(t > server_tick_start_time(current_tick+1)) - { - current_tick++; - new_ticks++; - - /* apply new input */ - { - static PERFORMACE_INFO scope = {"input", 0}; - int c, i; - - perf_start(&scope); - - for(c = 0; c < MAX_CLIENTS; c++) - { - if(m_aClients[c].state == CClient::STATE_EMPTY) - continue; - for(i = 0; i < 200; i++) - { - if(m_aClients[c].inputs[i].game_tick == server_tick()) - { - if(m_aClients[c].state == CClient::STATE_INGAME) - mods_client_predicted_input(c, m_aClients[c].inputs[i].data); - break; - } - } - } - - perf_end(); - } - - /* progress game */ - { - static PERFORMACE_INFO scope = {"tick", 0}; - perf_start(&scope); - mods_tick(); - perf_end(); - } - } - - /* snap game */ - if(new_ticks) - { - if(config.sv_high_bandwidth || (current_tick%2) == 0) - { - static PERFORMACE_INFO scope = {"snap", 0}; - perf_start(&scope); - server_do_snap(); - perf_end(); - } - } - - /* master server stuff */ - register_update(); - - - { - static PERFORMACE_INFO scope = {"net", 0}; - perf_start(&scope); - server_pump_network(); - perf_end(); - } - - perf_end(); - - if(reporttime < time_get()) - { - if(config.debug) - { - /* - static NETSTATS prev_stats; - NETSTATS stats; - netserver_stats(net, &stats); - - perf_next(); - - if(config.dbg_pref) - perf_dump(&rootscope); - - dbg_msg("server", "send=%8d recv=%8d", - (stats.send_bytes - prev_stats.send_bytes)/reportinterval, - (stats.recv_bytes - prev_stats.recv_bytes)/reportinterval); - - prev_stats = stats; - */ - } - - reporttime += time_freq()*reportinterval; - } - - /* wait for incomming data */ - net_socket_read_wait(m_NetServer.Socket(), 5); - } - } - - mods_shutdown(); - map_unload(); - - if(current_map_data) - mem_free(current_map_data); - return 0; -} - - -#endif - -int main(int argc, char **argv) -{ - - m_pNetServer = &g_Server.m_NetServer; -#if defined(CONF_FAMILY_WINDOWS) - int i; - for(i = 1; i < argc; i++) - { - if(strcmp("-s", argv[i]) == 0 || strcmp("--silent", argv[i]) == 0) - { - ShowWindow(GetConsoleWindow(), SW_HIDE); - break; - } - } -#endif - - /* init the engine */ - dbg_msg("server", "starting..."); - engine_init("Teeworlds"); - - /* register all console commands */ - - g_Server.RegisterCommands(); - mods_console_init(); - - /* parse the command line arguments */ - engine_parse_arguments(argc, argv); - - /* run the server */ - g_Server.Run(); - return 0; -} - diff --git a/src/engine/server/register.cpp b/src/engine/server/register.cpp new file mode 100644 index 00000000..959b9288 --- /dev/null +++ b/src/engine/server/register.cpp @@ -0,0 +1,264 @@ +#include <base/system.h> +#include <engine/shared/network.h> +#include <engine/shared/config.h> +#include <engine/shared/engine.h> +#include <engine/masterserver.h> + +#include <mastersrv/mastersrv.h> + +#include "register.h" + +CRegister::CRegister() +{ + m_pNetServer = 0; + m_pMasterServer = 0; + + m_RegisterState = REGISTERSTATE_START; + m_RegisterStateStart = 0; + m_RegisterFirst = 1; + m_RegisterCount = 0; + + mem_zero(m_aMasterserverInfo, sizeof(m_aMasterserverInfo)); + m_RegisterRegisteredServer = -1; +} + +void CRegister::RegisterNewState(int State) +{ + m_RegisterState = State; + m_RegisterStateStart = time_get(); +} + +void CRegister::RegisterSendFwcheckresponse(NETADDR *pAddr) +{ + CNetChunk Packet; + Packet.m_ClientID = -1; + Packet.m_Address = *pAddr; + Packet.m_Flags = NETSENDFLAG_CONNLESS; + Packet.m_DataSize = sizeof(SERVERBROWSE_FWRESPONSE); + Packet.m_pData = SERVERBROWSE_FWRESPONSE; + m_pNetServer->Send(&Packet); +} + +void CRegister::RegisterSendHeartbeat(NETADDR Addr) +{ + static unsigned char aData[sizeof(SERVERBROWSE_HEARTBEAT) + 2]; + unsigned short Port = g_Config.m_SvPort; + CNetChunk Packet; + + mem_copy(aData, SERVERBROWSE_HEARTBEAT, sizeof(SERVERBROWSE_HEARTBEAT)); + + Packet.m_ClientID = -1; + Packet.m_Address = Addr; + Packet.m_Flags = NETSENDFLAG_CONNLESS; + Packet.m_DataSize = sizeof(SERVERBROWSE_HEARTBEAT) + 2; + Packet.m_pData = &aData; + + // supply the set port that the master can use if it has problems + if(g_Config.m_SvExternalPort) + Port = g_Config.m_SvExternalPort; + aData[sizeof(SERVERBROWSE_HEARTBEAT)] = Port >> 8; + aData[sizeof(SERVERBROWSE_HEARTBEAT)+1] = Port&0xff; + m_pNetServer->Send(&Packet); +} + +void CRegister::RegisterSendCountRequest(NETADDR Addr) +{ + CNetChunk Packet; + Packet.m_ClientID = -1; + Packet.m_Address = Addr; + Packet.m_Flags = NETSENDFLAG_CONNLESS; + Packet.m_DataSize = sizeof(SERVERBROWSE_GETCOUNT); + Packet.m_pData = SERVERBROWSE_GETCOUNT; + m_pNetServer->Send(&Packet); +} + +void CRegister::RegisterGotCount(CNetChunk *pChunk) +{ + unsigned char *pData = (unsigned char *)pChunk->m_pData; + int Count = (pData[sizeof(SERVERBROWSE_COUNT)]<<8) | pData[sizeof(SERVERBROWSE_COUNT)+1]; + + for(int i = 0; i < IMasterServer::MAX_MASTERSERVERS; i++) + { + if(net_addr_comp(&m_aMasterserverInfo[i].m_Addr, &pChunk->m_Address) == 0) + { + m_aMasterserverInfo[i].m_Count = Count; + break; + } + } +} + +void CRegister::Init(CNetServer *pNetServer, IEngineMasterServer *pMasterServer) +{ + m_pNetServer = pNetServer; + m_pMasterServer = pMasterServer; +} + +void CRegister::RegisterUpdate() +{ + int64 Now = time_get(); + int64 Freq = time_freq(); + + if(!g_Config.m_SvRegister) + return; + + m_pMasterServer->Update(); + + if(m_RegisterState == REGISTERSTATE_START) + { + m_RegisterCount = 0; + m_RegisterFirst = 1; + RegisterNewState(REGISTERSTATE_UPDATE_ADDRS); + m_pMasterServer->RefreshAddresses(); + dbg_msg("register", "refreshing ip addresses"); + } + else if(m_RegisterState == REGISTERSTATE_UPDATE_ADDRS) + { + m_RegisterRegisteredServer = -1; + + if(!m_pMasterServer->IsRefreshing()) + { + int i; + for(i = 0; i < IMasterServer::MAX_MASTERSERVERS; i++) + { + NETADDR addr = m_pMasterServer->GetAddr(i); + m_aMasterserverInfo[i].m_Addr = addr; + m_aMasterserverInfo[i].m_Count = 0; + + if(!addr.ip[0] && !addr.ip[1] && !addr.ip[2] && !addr.ip[3]) + m_aMasterserverInfo[i].m_Valid = 0; + else + { + m_aMasterserverInfo[i].m_Valid = 1; + m_aMasterserverInfo[i].m_Count = -1; + m_aMasterserverInfo[i].m_LastSend = 0; + } + } + + dbg_msg("register", "fetching server counts"); + RegisterNewState(REGISTERSTATE_QUERY_COUNT); + } + } + else if(m_RegisterState == REGISTERSTATE_QUERY_COUNT) + { + int Left = 0; + for(int i = 0; i < IMasterServer::MAX_MASTERSERVERS; i++) + { + if(!m_aMasterserverInfo[i].m_Valid) + continue; + + if(m_aMasterserverInfo[i].m_Count == -1) + { + Left++; + if(m_aMasterserverInfo[i].m_LastSend+Freq < Now) + { + m_aMasterserverInfo[i].m_LastSend = Now; + RegisterSendCountRequest(m_aMasterserverInfo[i].m_Addr); + } + } + } + + // check if we are done or timed out + if(Left == 0 || Now > m_RegisterStateStart+Freq*3) + { + // choose server + int Best = -1; + int i; + for(i = 0; i < IMasterServer::MAX_MASTERSERVERS; i++) + { + if(!m_aMasterserverInfo[i].m_Valid || m_aMasterserverInfo[i].m_Count == -1) + continue; + + if(Best == -1 || m_aMasterserverInfo[i].m_Count < m_aMasterserverInfo[Best].m_Count) + Best = i; + } + + // server chosen + m_RegisterRegisteredServer = Best; + if(m_RegisterRegisteredServer == -1) + { + dbg_msg("register", "WARNING: No master servers. Retrying in 60 seconds"); + RegisterNewState(REGISTERSTATE_ERROR); + } + else + { + dbg_msg("register", "choosen '%s' as master, sending heartbeats", m_pMasterServer->GetName(m_RegisterRegisteredServer)); + m_aMasterserverInfo[m_RegisterRegisteredServer].m_LastSend = 0; + RegisterNewState(REGISTERSTATE_HEARTBEAT); + } + } + } + else if(m_RegisterState == REGISTERSTATE_HEARTBEAT) + { + // check if we should send heartbeat + if(Now > m_aMasterserverInfo[m_RegisterRegisteredServer].m_LastSend+Freq*15) + { + m_aMasterserverInfo[m_RegisterRegisteredServer].m_LastSend = Now; + RegisterSendHeartbeat(m_aMasterserverInfo[m_RegisterRegisteredServer].m_Addr); + } + + if(Now > m_RegisterStateStart+Freq*60) + { + dbg_msg("register", "WARNING: Master server is not responding, switching master"); + RegisterNewState(REGISTERSTATE_START); + } + } + else if(m_RegisterState == REGISTERSTATE_REGISTERED) + { + if(m_RegisterFirst) + dbg_msg("register", "server registered"); + + m_RegisterFirst = 0; + + // check if we should send new heartbeat again + if(Now > m_RegisterStateStart+Freq) + { + if(m_RegisterCount == 120) // redo the whole process after 60 minutes to balance out the master servers + RegisterNewState(REGISTERSTATE_START); + else + { + m_RegisterCount++; + RegisterNewState(REGISTERSTATE_HEARTBEAT); + } + } + } + else if(m_RegisterState == REGISTERSTATE_ERROR) + { + // check for restart + if(Now > m_RegisterStateStart+Freq*60) + RegisterNewState(REGISTERSTATE_START); + } +} + +int CRegister::RegisterProcessPacket(CNetChunk *pPacket) +{ + if(pPacket->m_DataSize == sizeof(SERVERBROWSE_FWCHECK) && + mem_comp(pPacket->m_pData, SERVERBROWSE_FWCHECK, sizeof(SERVERBROWSE_FWCHECK)) == 0) + { + RegisterSendFwcheckresponse(&pPacket->m_Address); + return 1; + } + else if(pPacket->m_DataSize == sizeof(SERVERBROWSE_FWOK) && + mem_comp(pPacket->m_pData, SERVERBROWSE_FWOK, sizeof(SERVERBROWSE_FWOK)) == 0) + { + if(m_RegisterFirst) + dbg_msg("register", "no firewall/nat problems detected"); + RegisterNewState(REGISTERSTATE_REGISTERED); + return 1; + } + else if(pPacket->m_DataSize == sizeof(SERVERBROWSE_FWERROR) && + mem_comp(pPacket->m_pData, SERVERBROWSE_FWERROR, sizeof(SERVERBROWSE_FWERROR)) == 0) + { + dbg_msg("register", "ERROR: the master server reports that clients can not connect to this server."); + dbg_msg("register", "ERROR: configure your firewall/nat to let through udp on port %d.", g_Config.m_SvPort); + RegisterNewState(REGISTERSTATE_ERROR); + return 1; + } + else if(pPacket->m_DataSize == sizeof(SERVERBROWSE_COUNT)+2 && + mem_comp(pPacket->m_pData, SERVERBROWSE_COUNT, sizeof(SERVERBROWSE_COUNT)) == 0) + { + RegisterGotCount(pPacket); + return 1; + } + + return 0; +} diff --git a/src/engine/server/register.h b/src/engine/server/register.h new file mode 100644 index 00000000..a800ec1e --- /dev/null +++ b/src/engine/server/register.h @@ -0,0 +1,48 @@ +#ifndef ENGINE_SERVER_REGISTER_H +#define ENGINE_SERVER_REGISTER_H + +class CRegister +{ + enum + { + REGISTERSTATE_START=0, + REGISTERSTATE_UPDATE_ADDRS, + REGISTERSTATE_QUERY_COUNT, + REGISTERSTATE_HEARTBEAT, + REGISTERSTATE_REGISTERED, + REGISTERSTATE_ERROR + }; + + struct CMasterserverInfo + { + NETADDR m_Addr; + int m_Count; + int m_Valid; + int64 m_LastSend; + }; + + class CNetServer *m_pNetServer; + class IEngineMasterServer *m_pMasterServer; + + int m_RegisterState; + int64 m_RegisterStateStart; + int m_RegisterFirst; + int m_RegisterCount; + + class CMasterserverInfo m_aMasterserverInfo[IMasterServer::MAX_MASTERSERVERS]; + int m_RegisterRegisteredServer; + + void RegisterNewState(int State); + void RegisterSendFwcheckresponse(NETADDR *pAddr); + void RegisterSendHeartbeat(NETADDR Addr); + void RegisterSendCountRequest(NETADDR Addr); + void RegisterGotCount(class CNetChunk *pChunk); + +public: + CRegister(); + void Init(class CNetServer *pNetServer, class IEngineMasterServer *pMasterServer); + void RegisterUpdate(); + int RegisterProcessPacket(class CNetChunk *pPacket); +}; + +#endif diff --git a/src/engine/server/server.cpp b/src/engine/server/server.cpp new file mode 100644 index 00000000..45cec1e4 --- /dev/null +++ b/src/engine/server/server.cpp @@ -0,0 +1,1400 @@ +// copyright (c) 2007 magnus auvinen, see licence.txt for more info + +#include <base/system.h> + +#include <engine/shared/config.h> +#include <engine/shared/engine.h> + +#include <engine/shared/protocol.h> +#include <engine/shared/snapshot.h> + +#include <engine/shared/compression.h> + +#include <engine/shared/network.h> +#include <engine/shared/config.h> +#include <engine/shared/packer.h> +#include <engine/shared/datafile.h> +#include <engine/shared/demorec.h> + +#include <engine/server.h> +#include <engine/map.h> +#include <engine/console.h> +#include <engine/storage.h> +#include <engine/masterserver.h> +#include <engine/config.h> + +#include <mastersrv/mastersrv.h> + +#include "register.h" +#include "server.h" + +#if defined(CONF_FAMILY_WINDOWS) + #define _WIN32_WINNT 0x0500 + #define NOGDI + #include <windows.h> +#endif + +static const char *StrLtrim(const char *pStr) +{ + while(*pStr && *pStr <= 32) + pStr++; + return pStr; +} + +static void StrRtrim(char *pStr) +{ + int i = str_length(pStr); + while(i >= 0) + { + if(pStr[i] > 32) + break; + pStr[i] = 0; + i--; + } +} + + +static int StrAllnum(const char *pStr) +{ + while(*pStr) + { + if(!(*pStr >= '0' && *pStr <= '9')) + return 0; + pStr++; + } + return 1; +} + +CSnapIDPool::CSnapIDPool() +{ + Reset(); +} + +void CSnapIDPool::Reset() +{ + for(int i = 0; i < MAX_IDS; i++) + { + m_aIDs[i].m_Next = i+1; + m_aIDs[i].m_State = 0; + } + + m_aIDs[MAX_IDS-1].m_Next = -1; + m_FirstFree = 0; + m_FirstTimed = -1; + m_LastTimed = -1; + m_Usage = 0; + m_InUsage = 0; +} + + +void CSnapIDPool::RemoveFirstTimeout() +{ + int NextTimed = m_aIDs[m_FirstTimed].m_Next; + + // add it to the free list + m_aIDs[m_FirstTimed].m_Next = m_FirstFree; + m_aIDs[m_FirstTimed].m_State = 0; + m_FirstFree = m_FirstTimed; + + // remove it from the timed list + m_FirstTimed = NextTimed; + if(m_FirstTimed == -1) + m_LastTimed = -1; + + m_Usage--; +} + +int CSnapIDPool::NewID() +{ + int64 Now = time_get(); + + // process timed ids + while(m_FirstTimed != -1 && m_aIDs[m_FirstTimed].m_Timeout < Now) + RemoveFirstTimeout(); + + int Id = m_FirstFree; + dbg_assert(Id != -1, "id error"); + m_FirstFree = m_aIDs[m_FirstFree].m_Next; + m_aIDs[Id].m_State = 1; + m_Usage++; + m_InUsage++; + return Id; +} + +void CSnapIDPool::TimeoutIDs() +{ + // process timed ids + while(m_FirstTimed != -1) + RemoveFirstTimeout(); +} + +void CSnapIDPool::FreeID(int Id) +{ + dbg_assert(m_aIDs[Id].m_State == 1, "id is not alloced"); + + m_InUsage--; + m_aIDs[Id].m_State = 2; + m_aIDs[Id].m_Timeout = time_get()+time_freq()*5; + m_aIDs[Id].m_Next = -1; + + if(m_LastTimed != -1) + { + m_aIDs[m_LastTimed].m_Next = Id; + m_LastTimed = Id; + } + else + { + m_FirstTimed = Id; + m_LastTimed = Id; + } +} + +void CServer::CClient::Reset() +{ + // reset input + for(int i = 0; i < 200; i++) + m_aInputs[i].m_GameTick = -1; + m_CurrentInput = 0; + mem_zero(&m_LatestInput, sizeof(m_LatestInput)); + + m_Snapshots.PurgeAll(); + m_LastAckedSnapshot = -1; + m_LastInputTick = -1; + m_SnapRate = CClient::SNAPRATE_INIT; + m_Score = 0; +} + +CServer::CServer() : m_DemoRecorder(&m_SnapshotDelta) +{ + m_TickSpeed = SERVER_TICK_SPEED; + + m_pGameServer = 0; + + m_CurrentGameTick = 0; + m_RunServer = 1; + + mem_zero(m_aBrowseinfoGametype, sizeof(m_aBrowseinfoGametype)); + m_BrowseinfoProgression = -1; + + m_pCurrentMapData = 0; + m_CurrentMapSize = 0; + + Init(); +} + + +int CServer::TrySetClientName(int ClientID, const char *pName) +{ + char aTrimmedName[64]; + + // trim the name + str_copy(aTrimmedName, StrLtrim(pName), sizeof(aTrimmedName)); + StrRtrim(aTrimmedName); + dbg_msg("", "'%s' -> '%s'", pName, aTrimmedName); + pName = aTrimmedName; + + + // check for empty names + if(!pName[0]) + return -1; + + // make sure that two clients doesn't have the same name + for(int i = 0; i < MAX_CLIENTS; i++) + if(i != ClientID && m_aClients[i].m_State >= CClient::STATE_READY) + { + if(str_comp(pName, m_aClients[i].m_aName) == 0) + return -1; + } + + // set the client name + str_copy(m_aClients[ClientID].m_aName, pName, MAX_NAME_LENGTH); + return 0; +} + + + +void CServer::SetClientName(int ClientID, const char *pName) +{ + if(ClientID < 0 || ClientID >= MAX_CLIENTS || m_aClients[ClientID].m_State < CClient::STATE_READY) + return; + + if(!pName) + return; + + char aNameTry[MAX_NAME_LENGTH]; + str_copy(aNameTry, pName, MAX_NAME_LENGTH); + if(TrySetClientName(ClientID, aNameTry)) + { + // auto rename + for(int i = 1;; i++) + { + str_format(aNameTry, MAX_NAME_LENGTH, "(%d)%s", i, pName); + if(TrySetClientName(ClientID, aNameTry) == 0) + break; + } + } +} + +void CServer::SetClientScore(int ClientID, int Score) +{ + if(ClientID < 0 || ClientID >= MAX_CLIENTS || m_aClients[ClientID].m_State < CClient::STATE_READY) + return; + m_aClients[ClientID].m_Score = Score; +} + +void CServer::SetBrowseInfo(const char *pGameType, int Progression) +{ + str_copy(m_aBrowseinfoGametype, pGameType, sizeof(m_aBrowseinfoGametype)); + m_BrowseinfoProgression = Progression; + if(m_BrowseinfoProgression > 100) + m_BrowseinfoProgression = 100; + if(m_BrowseinfoProgression < -1) + m_BrowseinfoProgression = -1; +} + +void CServer::Kick(int ClientID, const char *pReason) +{ + if(ClientID < 0 || ClientID >= MAX_CLIENTS) + return; + + if(m_aClients[ClientID].m_State != CClient::STATE_EMPTY) + m_NetServer.Drop(ClientID, pReason); +} + +/*int CServer::Tick() +{ + return m_CurrentGameTick; +}*/ + +int64 CServer::TickStartTime(int Tick) +{ + return m_GameStartTime + (time_freq()*Tick)/SERVER_TICK_SPEED; +} + +/*int CServer::TickSpeed() +{ + return SERVER_TICK_SPEED; +}*/ + +int CServer::Init() +{ + for(int i = 0; i < MAX_CLIENTS; i++) + { + m_aClients[i].m_State = CClient::STATE_EMPTY; + m_aClients[i].m_aName[0] = 0; + m_aClients[i].m_aClan[0] = 0; + m_aClients[i].m_Snapshots.Init(); + } + + m_CurrentGameTick = 0; + + return 0; +} + +int CServer::GetClientInfo(int ClientID, CClientInfo *pInfo) +{ + dbg_assert(ClientID >= 0 && ClientID < MAX_CLIENTS, "client_id is not valid"); + dbg_assert(pInfo != 0, "info can not be null"); + + if(m_aClients[ClientID].m_State == CClient::STATE_INGAME) + { + pInfo->m_pName = m_aClients[ClientID].m_aName; + pInfo->m_Latency = m_aClients[ClientID].m_Latency; + return 1; + } + return 0; +} + +void CServer::GetClientIP(int ClientID, char *pIPString, int Size) +{ + if(ClientID >= 0 && ClientID < MAX_CLIENTS && m_aClients[ClientID].m_State == CClient::STATE_INGAME) + { + NETADDR Addr = m_NetServer.ClientAddr(ClientID); + str_format(pIPString, Size, "%d.%d.%d.%d", Addr.ip[0], Addr.ip[1], Addr.ip[2], Addr.ip[3]); + } +} + + +int *CServer::LatestInput(int ClientId, int *size) +{ + if(ClientId < 0 || ClientId >= MAX_CLIENTS || m_aClients[ClientId].m_State < CServer::CClient::STATE_READY) + return 0; + return m_aClients[ClientId].m_LatestInput.m_aData; +} + +const char *CServer::ClientName(int ClientId) +{ + if(ClientId < 0 || ClientId >= MAX_CLIENTS || m_aClients[ClientId].m_State < CServer::CClient::STATE_READY) + return "(invalid client)"; + return m_aClients[ClientId].m_aName; +} + +bool CServer::ClientIngame(int ClientID) +{ + return ClientID >= 0 && ClientID < MAX_CLIENTS && m_aClients[ClientID].m_State == CServer::CClient::STATE_INGAME; +} + +int CServer::SendMsg(CMsgPacker *pMsg, int Flags, int ClientId) +{ + return SendMsgEx(pMsg, Flags, ClientId, false); +} + +int CServer::SendMsgEx(CMsgPacker *pMsg, int Flags, int ClientID, bool System) +{ + CNetChunk Packet; + if(!pMsg) + return -1; + + mem_zero(&Packet, sizeof(CNetChunk)); + + Packet.m_ClientID = ClientID; + Packet.m_pData = pMsg->Data(); + Packet.m_DataSize = pMsg->Size(); + + // HACK: modify the message id in the packet and store the system flag + *((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; + + // write message to demo recorder + if(!(Flags&MSGFLAG_NORECORD)) + m_DemoRecorder.RecordMessage(pMsg->Data(), pMsg->Size()); + + if(!(Flags&MSGFLAG_NOSEND)) + { + if(ClientID == -1) + { + // broadcast + int i; + for(i = 0; i < MAX_CLIENTS; i++) + if(m_aClients[i].m_State == CClient::STATE_INGAME) + { + Packet.m_ClientID = i; + m_NetServer.Send(&Packet); + } + } + else + m_NetServer.Send(&Packet); + } + return 0; +} + +void CServer::DoSnapshot() +{ + GameServer()->OnPreSnap(); + + // create snapshot for demo recording + if(m_DemoRecorder.IsRecording()) + { + char aData[CSnapshot::MAX_SIZE]; + int SnapshotSize; + + // build snap and possibly add some messages + m_SnapshotBuilder.Init(); + GameServer()->OnSnap(-1); + SnapshotSize = m_SnapshotBuilder.Finish(aData); + + // write snapshot + m_DemoRecorder.RecordSnapshot(Tick(), aData, SnapshotSize); + } + + // create snapshots for all clients + for(int i = 0; i < MAX_CLIENTS; i++) + { + // client must be ingame to recive snapshots + if(m_aClients[i].m_State != CClient::STATE_INGAME) + continue; + + // this client is trying to recover, don't spam snapshots + if(m_aClients[i].m_SnapRate == CClient::SNAPRATE_RECOVER && (Tick()%50) != 0) + continue; + + // this client is trying to recover, don't spam snapshots + if(m_aClients[i].m_SnapRate == CClient::SNAPRATE_INIT && (Tick()%10) != 0) + continue; + + { + char aData[CSnapshot::MAX_SIZE]; + char aDeltaData[CSnapshot::MAX_SIZE]; + char aCompData[CSnapshot::MAX_SIZE]; + int SnapshotSize; + int Crc; + static CSnapshot EmptySnap; + CSnapshot *pDeltashot = &EmptySnap; + int DeltashotSize; + int DeltaTick = -1; + int DeltaSize; + + m_SnapshotBuilder.Init(); + + GameServer()->OnSnap(i); + + // finish snapshot + SnapshotSize = m_SnapshotBuilder.Finish(aData); + Crc = ((CSnapshot*)aData)->Crc(); + + // remove old snapshos + // keep 3 seconds worth of snapshots + m_aClients[i].m_Snapshots.PurgeUntil(m_CurrentGameTick-SERVER_TICK_SPEED*3); + + // save it the snapshot + m_aClients[i].m_Snapshots.Add(m_CurrentGameTick, time_get(), SnapshotSize, aData, 0); + + // find snapshot that we can preform delta against + EmptySnap.Clear(); + + { + DeltashotSize = m_aClients[i].m_Snapshots.Get(m_aClients[i].m_LastAckedSnapshot, 0, &pDeltashot, 0); + if(DeltashotSize >= 0) + DeltaTick = m_aClients[i].m_LastAckedSnapshot; + else + { + // no acked package found, force client to recover rate + if(m_aClients[i].m_SnapRate == CClient::SNAPRATE_FULL) + m_aClients[i].m_SnapRate = CClient::SNAPRATE_RECOVER; + } + } + + // create delta + DeltaSize = m_SnapshotDelta.CreateDelta(pDeltashot, (CSnapshot*)aData, aDeltaData); + + if(DeltaSize) + { + // compress it + int SnapshotSize; + const int MaxSize = MAX_SNAPSHOT_PACKSIZE; + int NumPackets; + + SnapshotSize = CVariableInt::Compress(aDeltaData, DeltaSize, aCompData); + NumPackets = (SnapshotSize+MaxSize-1)/MaxSize; + + for(int n = 0, Left = SnapshotSize; Left; n++) + { + int Chunk = Left < MaxSize ? Left : MaxSize; + Left -= Chunk; + + if(NumPackets == 1) + { + CMsgPacker Msg(NETMSG_SNAPSINGLE); + Msg.AddInt(m_CurrentGameTick); + Msg.AddInt(m_CurrentGameTick-DeltaTick); + Msg.AddInt(Crc); + Msg.AddInt(Chunk); + Msg.AddRaw(&aCompData[n*MaxSize], Chunk); + SendMsgEx(&Msg, MSGFLAG_FLUSH, i, true); + } + else + { + CMsgPacker Msg(NETMSG_SNAP); + Msg.AddInt(m_CurrentGameTick); + Msg.AddInt(m_CurrentGameTick-DeltaTick); + Msg.AddInt(NumPackets); + Msg.AddInt(n); + Msg.AddInt(Crc); + Msg.AddInt(Chunk); + Msg.AddRaw(&aCompData[n*MaxSize], Chunk); + SendMsgEx(&Msg, MSGFLAG_FLUSH, i, true); + } + } + } + else + { + CMsgPacker Msg(NETMSG_SNAPEMPTY); + Msg.AddInt(m_CurrentGameTick); + Msg.AddInt(m_CurrentGameTick-DeltaTick); + SendMsgEx(&Msg, MSGFLAG_FLUSH, i, true); + } + } + } + + GameServer()->OnPostSnap(); +} + + +int CServer::NewClientCallback(int ClientId, void *pUser) +{ + CServer *pThis = (CServer *)pUser; + pThis->m_aClients[ClientId].m_State = CClient::STATE_AUTH; + pThis->m_aClients[ClientId].m_aName[0] = 0; + pThis->m_aClients[ClientId].m_aClan[0] = 0; + pThis->m_aClients[ClientId].m_Authed = 0; + pThis->m_aClients[ClientId].Reset(); + return 0; +} + +int CServer::DelClientCallback(int ClientId, void *pUser) +{ + CServer *pThis = (CServer *)pUser; + + // notify the mod about the drop + if(pThis->m_aClients[ClientId].m_State >= CClient::STATE_READY) + pThis->GameServer()->OnClientDrop(ClientId); + + pThis->m_aClients[ClientId].m_State = CClient::STATE_EMPTY; + pThis->m_aClients[ClientId].m_aName[0] = 0; + pThis->m_aClients[ClientId].m_aClan[0] = 0; + pThis->m_aClients[ClientId].m_Authed = 0; + pThis->m_aClients[ClientId].m_Snapshots.PurgeAll(); + return 0; +} + +void CServer::SendMap(int ClientId) +{ + CMsgPacker Msg(NETMSG_MAP_CHANGE); + Msg.AddString(g_Config.m_SvMap, 0); + Msg.AddInt(m_CurrentMapCrc); + SendMsgEx(&Msg, MSGFLAG_VITAL|MSGFLAG_FLUSH, ClientId, true); +} + +void CServer::SendRconLine(int ClientId, const char *pLine) +{ + CMsgPacker Msg(NETMSG_RCON_LINE); + Msg.AddString(pLine, 512); + SendMsgEx(&Msg, MSGFLAG_VITAL, ClientId, true); +} + +void CServer::SendRconLineAuthed(const char *pLine, void *pUser) +{ + CServer *pThis = (CServer *)pUser; + static volatile int ReentryGuard = 0; + int i; + + if(ReentryGuard) return; + ReentryGuard++; + + for(i = 0; i < MAX_CLIENTS; i++) + { + if(pThis->m_aClients[i].m_State != CClient::STATE_EMPTY && pThis->m_aClients[i].m_Authed) + pThis->SendRconLine(i, pLine); + } + + ReentryGuard--; +} + +void CServer::ProcessClientPacket(CNetChunk *pPacket) +{ + int ClientId = pPacket->m_ClientID; + NETADDR Addr; + 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(m_aClients[ClientId].m_State == CClient::STATE_AUTH) + { + if(Sys && Msg == NETMSG_INFO) + { + char aVersion[64]; + const char *pPassword; + str_copy(aVersion, Unpacker.GetString(), 64); + if(str_comp(aVersion, GameServer()->NetVersion()) != 0) + { + // OH FUCK! wrong version, drop him + char aReason[256]; + str_format(aReason, sizeof(aReason), "wrong version. server is running '%s' and client '%s'.", GameServer()->NetVersion(), aVersion); + m_NetServer.Drop(ClientId, aReason); + return; + } + + str_copy(m_aClients[ClientId].m_aName, Unpacker.GetString(), MAX_NAME_LENGTH); + str_copy(m_aClients[ClientId].m_aClan, Unpacker.GetString(), MAX_CLANNAME_LENGTH); + pPassword = Unpacker.GetString(); + + if(g_Config.m_Password[0] != 0 && str_comp(g_Config.m_Password, pPassword) != 0) + { + // wrong password + m_NetServer.Drop(ClientId, "wrong password"); + return; + } + + m_aClients[ClientId].m_State = CClient::STATE_CONNECTING; + SendMap(ClientId); + } + } + else + { + if(Sys) + { + // system message + if(Msg == NETMSG_REQUEST_MAP_DATA) + { + int Chunk = Unpacker.GetInt(); + int ChunkSize = 1024-128; + int Offset = Chunk * ChunkSize; + int Last = 0; + + // drop faulty map data requests + if(Chunk < 0 || Offset > m_CurrentMapSize) + return; + + if(Offset+ChunkSize >= m_CurrentMapSize) + { + ChunkSize = m_CurrentMapSize-Offset; + if(ChunkSize < 0) + ChunkSize = 0; + Last = 1; + } + + CMsgPacker Msg(NETMSG_MAP_DATA); + Msg.AddInt(Last); + Msg.AddInt(m_CurrentMapSize); + Msg.AddInt(ChunkSize); + Msg.AddRaw(&m_pCurrentMapData[Offset], ChunkSize); + SendMsgEx(&Msg, MSGFLAG_VITAL|MSGFLAG_FLUSH, ClientId, true); + + if(g_Config.m_Debug) + dbg_msg("server", "sending chunk %d with size %d", Chunk, ChunkSize); + } + else if(Msg == NETMSG_READY) + { + if(m_aClients[ClientId].m_State == CClient::STATE_CONNECTING) + { + Addr = m_NetServer.ClientAddr(ClientId); + + dbg_msg("server", "player is ready. ClientId=%x ip=%d.%d.%d.%d", + ClientId, Addr.ip[0], Addr.ip[1], Addr.ip[2], Addr.ip[3]); + m_aClients[ClientId].m_State = CClient::STATE_READY; + GameServer()->OnClientConnected(ClientId); + } + } + else if(Msg == NETMSG_ENTERGAME) + { + if(m_aClients[ClientId].m_State == CClient::STATE_READY) + { + Addr = m_NetServer.ClientAddr(ClientId); + + dbg_msg("server", "player has entered the game. ClientId=%x ip=%d.%d.%d.%d", + ClientId, Addr.ip[0], Addr.ip[1], Addr.ip[2], Addr.ip[3]); + m_aClients[ClientId].m_State = CClient::STATE_INGAME; + GameServer()->OnClientEnter(ClientId); + } + } + else if(Msg == NETMSG_INPUT) + { + CClient::CInput *pInput; + int64 TagTime; + + m_aClients[ClientId].m_LastAckedSnapshot = Unpacker.GetInt(); + int IntendedTick = Unpacker.GetInt(); + int Size = Unpacker.GetInt(); + + // check for errors + if(Unpacker.Error() || Size/4 > MAX_INPUT_SIZE) + return; + + if(m_aClients[ClientId].m_LastAckedSnapshot > 0) + m_aClients[ClientId].m_SnapRate = CClient::SNAPRATE_FULL; + + if(m_aClients[ClientId].m_Snapshots.Get(m_aClients[ClientId].m_LastAckedSnapshot, &TagTime, 0, 0) >= 0) + m_aClients[ClientId].m_Latency = (int)(((time_get()-TagTime)*1000)/time_freq()); + + // add message to report the input timing + // skip packets that are old + if(IntendedTick > m_aClients[ClientId].m_LastInputTick) + { + int TimeLeft = ((TickStartTime(IntendedTick)-time_get())*1000) / time_freq(); + + CMsgPacker Msg(NETMSG_INPUTTIMING); + Msg.AddInt(IntendedTick); + Msg.AddInt(TimeLeft); + SendMsgEx(&Msg, 0, ClientId, true); + } + + m_aClients[ClientId].m_LastInputTick = IntendedTick; + + pInput = &m_aClients[ClientId].m_aInputs[m_aClients[ClientId].m_CurrentInput]; + + if(IntendedTick <= Tick()) + IntendedTick = Tick()+1; + + pInput->m_GameTick = IntendedTick; + + for(int i = 0; i < Size/4; i++) + pInput->m_aData[i] = Unpacker.GetInt(); + + mem_copy(m_aClients[ClientId].m_LatestInput.m_aData, pInput->m_aData, MAX_INPUT_SIZE*sizeof(int)); + + m_aClients[ClientId].m_CurrentInput++; + m_aClients[ClientId].m_CurrentInput %= 200; + + // call the mod with the fresh input data + if(m_aClients[ClientId].m_State == CClient::STATE_INGAME) + GameServer()->OnClientDirectInput(ClientId, m_aClients[ClientId].m_LatestInput.m_aData); + } + else if(Msg == NETMSG_RCON_CMD) + { + const char *pCmd = Unpacker.GetString(); + + if(Unpacker.Error() == 0 && m_aClients[ClientId].m_Authed) + { + dbg_msg("server", "ClientId=%d rcon='%s'", ClientId, pCmd); + Console()->ExecuteLine(pCmd); + } + } + else if(Msg == NETMSG_RCON_AUTH) + { + const char *pPw; + Unpacker.GetString(); // login name, not used + pPw = Unpacker.GetString(); + + if(Unpacker.Error() == 0) + { + if(g_Config.m_SvRconPassword[0] == 0) + { + SendRconLine(ClientId, "No rcon password set on server. Set sv_rcon_password to enable the remote console."); + } + else if(str_comp(pPw, g_Config.m_SvRconPassword) == 0) + { + CMsgPacker Msg(NETMSG_RCON_AUTH_STATUS); + Msg.AddInt(1); + SendMsgEx(&Msg, MSGFLAG_VITAL, ClientId, true); + + m_aClients[ClientId].m_Authed = 1; + SendRconLine(ClientId, "Authentication successful. Remote console access granted."); + dbg_msg("server", "ClientId=%d authed", ClientId); + } + else + { + SendRconLine(ClientId, "Wrong password."); + } + } + } + else if(Msg == NETMSG_PING) + { + CMsgPacker Msg(NETMSG_PING_REPLY); + SendMsgEx(&Msg, 0, ClientId, true); + } + else + { + char aHex[] = "0123456789ABCDEF"; + char aBuf[512]; + + for(int b = 0; b < pPacket->m_DataSize && b < 32; b++) + { + aBuf[b*3] = aHex[((const unsigned char *)pPacket->m_pData)[b]>>4]; + aBuf[b*3+1] = aHex[((const unsigned char *)pPacket->m_pData)[b]&0xf]; + aBuf[b*3+2] = ' '; + aBuf[b*3+3] = 0; + } + + dbg_msg("server", "strange message ClientId=%d msg=%d data_size=%d", ClientId, Msg, pPacket->m_DataSize); + dbg_msg("server", "%s", aBuf); + + } + } + else + { + // game message + if(m_aClients[ClientId].m_State >= CClient::STATE_READY) + GameServer()->OnMessage(Msg, &Unpacker, ClientId); + } + } +} + +void CServer::SendServerInfo(NETADDR *pAddr, int Token) +{ + CNetChunk Packet; + CPacker p; + char aBuf[128]; + + // count the players + int PlayerCount = 0; + for(int i = 0; i < MAX_CLIENTS; i++) + { + if(m_aClients[i].m_State != CClient::STATE_EMPTY) + PlayerCount++; + } + + p.Reset(); + + if(Token >= 0) + { + // new token based format + p.AddRaw(SERVERBROWSE_INFO, sizeof(SERVERBROWSE_INFO)); + str_format(aBuf, sizeof(aBuf), "%d", Token); + p.AddString(aBuf, 6); + } + else + { + // old format + p.AddRaw(SERVERBROWSE_OLD_INFO, sizeof(SERVERBROWSE_OLD_INFO)); + } + + p.AddString(GameServer()->Version(), 32); + p.AddString(g_Config.m_SvName, 64); + p.AddString(g_Config.m_SvMap, 32); + + // gametype + p.AddString(m_aBrowseinfoGametype, 16); + + // flags + int i = 0; + if(g_Config.m_Password[0]) // password set + i |= SERVER_FLAG_PASSWORD; + str_format(aBuf, sizeof(aBuf), "%d", i); + p.AddString(aBuf, 2); + + // progression + str_format(aBuf, sizeof(aBuf), "%d", m_BrowseinfoProgression); + p.AddString(aBuf, 4); + + str_format(aBuf, sizeof(aBuf), "%d", PlayerCount); p.AddString(aBuf, 3); // num players + str_format(aBuf, sizeof(aBuf), "%d", m_NetServer.MaxClients()); p.AddString(aBuf, 3); // max players + + for(i = 0; i < MAX_CLIENTS; i++) + { + if(m_aClients[i].m_State != CClient::STATE_EMPTY) + { + p.AddString(m_aClients[i].m_aName, 48); // player name + str_format(aBuf, sizeof(aBuf), "%d", m_aClients[i].m_Score); p.AddString(aBuf, 6); // player score + } + } + + + Packet.m_ClientID = -1; + Packet.m_Address = *pAddr; + Packet.m_Flags = NETSENDFLAG_CONNLESS; + Packet.m_DataSize = p.Size(); + Packet.m_pData = p.Data(); + m_NetServer.Send(&Packet); +} + +void CServer::UpdateServerInfo() +{ + for(int i = 0; i < MAX_CLIENTS; ++i) + { + if(m_aClients[i].m_State != CClient::STATE_EMPTY) + { + NETADDR Addr = m_NetServer.ClientAddr(i); + SendServerInfo(&Addr, -1); // SERVERBROWSE_OLD_INFO + } + } +} + +int CServer::BanAdd(NETADDR Addr, int Seconds) +{ + return m_NetServer.BanAdd(Addr, Seconds); +} + +int CServer::BanRemove(NETADDR Addr) +{ + return m_NetServer.BanRemove(Addr); +} + + +void CServer::PumpNetwork() +{ + CNetChunk Packet; + + m_NetServer.Update(); + + // process packets + while(m_NetServer.Recv(&Packet)) + { + if(Packet.m_ClientID == -1) + { + // stateless + if(!m_Register.RegisterProcessPacket(&Packet)) + { + if(Packet.m_DataSize == sizeof(SERVERBROWSE_GETINFO)+1 && + mem_comp(Packet.m_pData, SERVERBROWSE_GETINFO, sizeof(SERVERBROWSE_GETINFO)) == 0) + { + SendServerInfo(&Packet.m_Address, ((unsigned char *)Packet.m_pData)[sizeof(SERVERBROWSE_GETINFO)]); + } + + + if(Packet.m_DataSize == sizeof(SERVERBROWSE_OLD_GETINFO) && + mem_comp(Packet.m_pData, SERVERBROWSE_OLD_GETINFO, sizeof(SERVERBROWSE_OLD_GETINFO)) == 0) + { + SendServerInfo(&Packet.m_Address, -1); + } + } + } + else + ProcessClientPacket(&Packet); + } +} + +int CServer::LoadMap(const char *pMapName) +{ + //DATAFILE *df; + char aBuf[512]; + str_format(aBuf, sizeof(aBuf), "maps/%s.map", pMapName); + + /*df = datafile_load(buf); + if(!df) + return 0;*/ + + if(!m_pMap->Load(aBuf)) + return 0; + + // stop recording when we change map + m_DemoRecorder.Stop(); + + // reinit snapshot ids + m_IDPool.TimeoutIDs(); + + // get the crc of the map + m_CurrentMapCrc = m_pMap->Crc(); + dbg_msg("server", "%s crc is %08x", aBuf, m_CurrentMapCrc); + + str_copy(m_aCurrentMap, pMapName, sizeof(m_aCurrentMap)); + //map_set(df); + + // load compelate map into memory for download + { + IOHANDLE File = Storage()->OpenFile(aBuf, IOFLAG_READ); + m_CurrentMapSize = (int)io_length(File); + if(m_pCurrentMapData) + mem_free(m_pCurrentMapData); + m_pCurrentMapData = (unsigned char *)mem_alloc(m_CurrentMapSize, 1); + io_read(File, m_pCurrentMapData, m_CurrentMapSize); + io_close(File); + } + return 1; +} + +void CServer::InitEngine(const char *pAppname) +{ + m_Engine.Init(pAppname); +} + +void CServer::InitRegister(CNetServer *pNetServer, IEngineMasterServer *pMasterServer) +{ + m_Register.Init(pNetServer, pMasterServer); +} + +int CServer::Run() +{ + m_pGameServer = Kernel()->RequestInterface<IGameServer>(); + m_pMap = Kernel()->RequestInterface<IEngineMap>(); + m_pStorage = Kernel()->RequestInterface<IStorage>(); + + //snap_init_id(); + net_init(); + + // + Console()->RegisterPrintCallback(SendRconLineAuthed, this); + + // load map + if(!LoadMap(g_Config.m_SvMap)) + { + dbg_msg("server", "failed to load map. mapname='%s'", g_Config.m_SvMap); + return -1; + } + + // start server + // TODO: IPv6 support + NETADDR BindAddr; + if(g_Config.m_SvBindaddr[0] && net_host_lookup(g_Config.m_SvBindaddr, &BindAddr, NETTYPE_IPV4) == 0) + { + // sweet! + BindAddr.port = g_Config.m_SvPort; + } + else + { + mem_zero(&BindAddr, sizeof(BindAddr)); + BindAddr.port = g_Config.m_SvPort; + } + + + if(!m_NetServer.Open(BindAddr, g_Config.m_SvMaxClients, 0)) + { + dbg_msg("server", "couldn't open socket. port might already be in use"); + return -1; + } + + m_NetServer.SetCallbacks(NewClientCallback, DelClientCallback, this); + + dbg_msg("server", "server name is '%s'", g_Config.m_SvName); + + GameServer()->OnInit(); + dbg_msg("server", "version %s", GameServer()->NetVersion()); + + // start game + { + int64 ReportTime = time_get(); + int ReportInterval = 3; + + m_Lastheartbeat = 0; + m_GameStartTime = time_get(); + + if(g_Config.m_Debug) + dbg_msg("server", "baseline memory usage %dk", mem_stats()->allocated/1024); + + while(m_RunServer) + { + int64 t = time_get(); + int NewTicks = 0; + + // load new map TODO: don't poll this + if(str_comp(g_Config.m_SvMap, m_aCurrentMap) != 0 || g_Config.m_SvMapReload) + { + g_Config.m_SvMapReload = 0; + + // load map + if(LoadMap(g_Config.m_SvMap)) + { + // new map loaded + GameServer()->OnShutdown(); + + for(int c = 0; c < MAX_CLIENTS; c++) + { + if(m_aClients[c].m_State == CClient::STATE_EMPTY) + continue; + + SendMap(c); + m_aClients[c].Reset(); + m_aClients[c].m_State = CClient::STATE_CONNECTING; + } + + m_GameStartTime = time_get(); + m_CurrentGameTick = 0; + Kernel()->ReregisterInterface(GameServer()); + GameServer()->OnInit(); + } + else + { + dbg_msg("server", "failed to load map. mapname='%s'", g_Config.m_SvMap); + str_copy(g_Config.m_SvMap, m_aCurrentMap, sizeof(g_Config.m_SvMap)); + } + } + + while(t > TickStartTime(m_CurrentGameTick+1)) + { + m_CurrentGameTick++; + NewTicks++; + + // apply new input + for(int c = 0; c < MAX_CLIENTS; c++) + { + if(m_aClients[c].m_State == CClient::STATE_EMPTY) + continue; + for(int i = 0; i < 200; i++) + { + if(m_aClients[c].m_aInputs[i].m_GameTick == Tick()) + { + if(m_aClients[c].m_State == CClient::STATE_INGAME) + GameServer()->OnClientPredictedInput(c, m_aClients[c].m_aInputs[i].m_aData); + break; + } + } + } + + GameServer()->OnTick(); + } + + // snap game + if(NewTicks) + { + if(g_Config.m_SvHighBandwidth || (m_CurrentGameTick%2) == 0) + DoSnapshot(); + } + + // master server stuff + m_Register.RegisterUpdate(); + + PumpNetwork(); + + if(ReportTime < time_get()) + { + if(g_Config.m_Debug) + { + /* + static NETSTATS prev_stats; + NETSTATS stats; + netserver_stats(net, &stats); + + perf_next(); + + if(config.dbg_pref) + perf_dump(&rootscope); + + dbg_msg("server", "send=%8d recv=%8d", + (stats.send_bytes - prev_stats.send_bytes)/reportinterval, + (stats.recv_bytes - prev_stats.recv_bytes)/reportinterval); + + prev_stats = stats; + */ + } + + ReportTime += time_freq()*ReportInterval; + } + + // wait for incomming data + net_socket_read_wait(m_NetServer.Socket(), 5); + } + } + // disconnect all clients on shutdown + for(int i = 0; i < MAX_CLIENTS; ++i) + { + if(m_aClients[i].m_State != CClient::STATE_EMPTY) + m_NetServer.Drop(i, "server shutdown"); + } + + GameServer()->OnShutdown(); + m_pMap->Unload(); + + if(m_pCurrentMapData) + mem_free(m_pCurrentMapData); + return 0; +} + +void CServer::ConKick(IConsole::IResult *pResult, void *pUser) +{ + ((CServer *)pUser)->Kick(pResult->GetInteger(0), "kicked by console"); +} + +void CServer::ConBan(IConsole::IResult *pResult, void *pUser) +{ + NETADDR Addr; + char aAddrStr[128]; + const char *pStr = pResult->GetString(0); + int Minutes = 30; + + if(pResult->NumArguments() > 1) + Minutes = pResult->GetInteger(1); + + if(net_addr_from_str(&Addr, pStr) == 0) + ((CServer *)pUser)->BanAdd(Addr, Minutes*60); + else if(StrAllnum(pStr)) + { + int ClientId = str_toint(pStr); + + if(ClientId < 0 || ClientId >= MAX_CLIENTS || ((CServer *)pUser)->m_aClients[ClientId].m_State == CClient::STATE_EMPTY) + { + dbg_msg("server", "invalid client id"); + return; + } + + NETADDR Addr = ((CServer *)pUser)->m_NetServer.ClientAddr(ClientId); + ((CServer *)pUser)->BanAdd(Addr, Minutes*60); + } + + Addr.port = 0; + net_addr_str(&Addr, aAddrStr, sizeof(aAddrStr)); + + if(Minutes) + dbg_msg("server", "banned %s for %d minutes", aAddrStr, Minutes); + else + dbg_msg("server", "banned %s for life", aAddrStr); +} + +void CServer::ConUnban(IConsole::IResult *pResult, void *pUser) +{ + NETADDR Addr; + const char *pStr = pResult->GetString(0); + + if(net_addr_from_str(&Addr, pStr) == 0) + ((CServer *)pUser)->BanRemove(Addr); + else + dbg_msg("server", "invalid network address"); +} + +void CServer::ConBans(IConsole::IResult *pResult, void *pUser) +{ + unsigned Now = time_timestamp(); + char aBuf[1024]; + CServer* pServer = (CServer *)pUser; + + int Num = pServer->m_NetServer.BanNum(); + for(int i = 0; i < Num; i++) + { + CNetServer::CBanInfo Info; + pServer->m_NetServer.BanGet(i, &Info); + NETADDR Addr = Info.m_Addr; + + if(Info.m_Expires == -1) + { + str_format(aBuf, sizeof(aBuf), "#%d %d.%d.%d.%d for life", i, Addr.ip[0], Addr.ip[1], Addr.ip[2], Addr.ip[3]); + } + else + { + unsigned t = Info.m_Expires - Now; + str_format(aBuf, sizeof(aBuf), "#%d %d.%d.%d.%d for %d minutes and %d seconds", i, Addr.ip[0], Addr.ip[1], Addr.ip[2], Addr.ip[3], t/60, t%60); + } + pServer->Console()->Print(aBuf); + dbg_msg("server", "%s", aBuf); + } + str_format(aBuf, sizeof(aBuf), "%d ban(s)", Num); + pServer->Console()->Print(aBuf); + dbg_msg("server", "%s", aBuf); +} + +void CServer::ConStatus(IConsole::IResult *pResult, void *pUser) +{ + int i; + NETADDR Addr; + char aBuf[1024]; + CServer* pServer = (CServer *)pUser; + + for(i = 0; i < MAX_CLIENTS; i++) + { + if(pServer->m_aClients[i].m_State == CClient::STATE_INGAME) + { + Addr = pServer->m_NetServer.ClientAddr(i); + str_format(aBuf, sizeof(aBuf), "id=%d addr=%d.%d.%d.%d:%d name='%s' score=%d", + i, Addr.ip[0], Addr.ip[1], Addr.ip[2], Addr.ip[3], Addr.port, + pServer->m_aClients[i].m_aName, pServer->m_aClients[i].m_Score); + pServer->Console()->Print(aBuf); + dbg_msg("server", "%s", aBuf); + } + } +} + +void CServer::ConShutdown(IConsole::IResult *pResult, void *pUser) +{ + ((CServer *)pUser)->m_RunServer = 0; +} + +void CServer::ConRecord(IConsole::IResult *pResult, void *pUser) +{ + char aFilename[512]; + str_format(aFilename, sizeof(aFilename), "demos/%s.demo", pResult->GetString(0)); + ((CServer *)pUser)->m_DemoRecorder.Start(((CServer *)pUser)->Storage(), aFilename, ((CServer *)pUser)->GameServer()->NetVersion(), ((CServer *)pUser)->m_aCurrentMap, ((CServer *)pUser)->m_CurrentMapCrc, "server"); +} + +void CServer::ConStopRecord(IConsole::IResult *pResult, void *pUser) +{ + ((CServer *)pUser)->m_DemoRecorder.Stop(); +} + +void CServer::ConchainSpecialInfoupdate(IConsole::IResult *pResult, void *pUserData, IConsole::FCommandCallback pfnCallback, void *pCallbackUserData) +{ + pfnCallback(pResult, pCallbackUserData); + if(pResult->NumArguments()) + ((CServer *)pUserData)->UpdateServerInfo(); +} + +void CServer::RegisterCommands() +{ + m_pConsole = Kernel()->RequestInterface<IConsole>(); + + Console()->Register("kick", "i", CFGFLAG_SERVER, ConKick, this, ""); + Console()->Register("ban", "s?i", CFGFLAG_SERVER, ConBan, this, ""); + Console()->Register("unban", "s", CFGFLAG_SERVER, ConUnban, this, ""); + Console()->Register("bans", "", CFGFLAG_SERVER, ConBans, this, ""); + Console()->Register("status", "", CFGFLAG_SERVER, ConStatus, this, ""); + Console()->Register("shutdown", "", CFGFLAG_SERVER, ConShutdown, this, ""); + + Console()->Register("record", "s", CFGFLAG_SERVER, ConRecord, this, ""); + Console()->Register("stoprecord", "", CFGFLAG_SERVER, ConStopRecord, this, ""); + + Console()->Chain("sv_name", ConchainSpecialInfoupdate, this); + Console()->Chain("password", ConchainSpecialInfoupdate, this); +} + + +int CServer::SnapNewID() +{ + return m_IDPool.NewID(); +} + +void CServer::SnapFreeID(int ID) +{ + m_IDPool.FreeID(ID); +} + + +void *CServer::SnapNewItem(int Type, int Id, int Size) +{ + dbg_assert(Type >= 0 && Type <=0xffff, "incorrect type"); + dbg_assert(Id >= 0 && Id <=0xffff, "incorrect id"); + return m_SnapshotBuilder.NewItem(Type, Id, Size); +} + +void CServer::SnapSetStaticsize(int ItemType, int Size) +{ + m_SnapshotDelta.SetStaticsize(ItemType, Size); +} + +static CServer *CreateServer() { return new CServer(); } + +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 + { + ShowWindow(GetConsoleWindow(), SW_HIDE); + break; + } + } +#endif + + // init the engine + dbg_msg("server", "starting..."); + CServer *pServer = CreateServer(); + pServer->InitEngine("Teeworlds"); + + IKernel *pKernel = IKernel::Create(); + + // create the components + IEngineMap *pEngineMap = CreateEngineMap(); + IGameServer *pGameServer = CreateGameServer(); + IConsole *pConsole = CreateConsole(); + IEngineMasterServer *pEngineMasterServer = CreateEngineMasterServer(); + IStorage *pStorage = CreateStorage("Teeworlds", argv[0]); // ignore_convention + IConfig *pConfig = CreateConfig(); + + pServer->InitRegister(&pServer->m_NetServer, pEngineMasterServer); + + { + bool RegisterFail = false; + + RegisterFail = RegisterFail || !pKernel->RegisterInterface(pServer); // register as both + RegisterFail = RegisterFail || !pKernel->RegisterInterface(static_cast<IEngineMap*>(pEngineMap)); // register as both + RegisterFail = RegisterFail || !pKernel->RegisterInterface(static_cast<IMap*>(pEngineMap)); + RegisterFail = RegisterFail || !pKernel->RegisterInterface(pGameServer); + RegisterFail = RegisterFail || !pKernel->RegisterInterface(pConsole); + RegisterFail = RegisterFail || !pKernel->RegisterInterface(pStorage); + RegisterFail = RegisterFail || !pKernel->RegisterInterface(pConfig); + RegisterFail = RegisterFail || !pKernel->RegisterInterface(static_cast<IEngineMasterServer*>(pEngineMasterServer)); // register as both + RegisterFail = RegisterFail || !pKernel->RegisterInterface(static_cast<IMasterServer*>(pEngineMasterServer)); + + if(RegisterFail) + return -1; + } + + pConfig->Init(); + pEngineMasterServer->Init(pServer->Engine()); + pEngineMasterServer->Load(); + + // register all console commands + pServer->RegisterCommands(); + pGameServer->OnConsoleInit(); + + // execute autoexec file + pConsole->ExecuteFile("autoexec.cfg"); + + // parse the command line arguments + if(argc > 1) // ignore_convention + pConsole->ParseArguments(argc-1, &argv[1]); // ignore_convention + + // run the server + pServer->Run(); + + // free + delete pServer; + delete pKernel; + delete pEngineMap; + delete pGameServer; + delete pConsole; + delete pEngineMasterServer; + delete pStorage; + delete pConfig; + return 0; +} + diff --git a/src/engine/server/server.h b/src/engine/server/server.h new file mode 100644 index 00000000..6904085a --- /dev/null +++ b/src/engine/server/server.h @@ -0,0 +1,195 @@ +#ifndef ENGINE_SERVER_SERVER_H +#define ENGINE_SERVER_SERVER_H + +#include <engine/server.h> + +class CSnapIDPool +{ + enum + { + MAX_IDS = 16*1024, + }; + + class CID + { + public: + short m_Next; + short m_State; // 0 = free, 1 = alloced, 2 = timed + int m_Timeout; + }; + + CID m_aIDs[MAX_IDS]; + + int m_FirstFree; + int m_FirstTimed; + int m_LastTimed; + int m_Usage; + int m_InUsage; + +public: + + CSnapIDPool(); + + void Reset(); + void RemoveFirstTimeout(); + int NewID(); + void TimeoutIDs(); + void FreeID(int Id); +}; + +class CServer : public IServer +{ + class IGameServer *m_pGameServer; + class IConsole *m_pConsole; + class IStorage *m_pStorage; +public: + class IGameServer *GameServer() { return m_pGameServer; } + class IConsole *Console() { return m_pConsole; } + class IStorage *Storage() { return m_pStorage; } + class CEngine *Engine() { return &m_Engine; } + + class CClient + { + public: + + enum + { + STATE_EMPTY = 0, + STATE_AUTH, + STATE_CONNECTING, + STATE_READY, + STATE_INGAME, + + SNAPRATE_INIT=0, + SNAPRATE_FULL, + SNAPRATE_RECOVER + }; + + class CInput + { + public: + int m_aData[MAX_INPUT_SIZE]; + int m_GameTick; // the tick that was chosen for the input + }; + + // connection state info + int m_State; + int m_Latency; + int m_SnapRate; + + int m_LastAckedSnapshot; + int m_LastInputTick; + CSnapshotStorage m_Snapshots; + + CInput m_LatestInput; + CInput m_aInputs[200]; // TODO: handle input better + int m_CurrentInput; + + char m_aName[MAX_NAME_LENGTH]; + char m_aClan[MAX_CLANNAME_LENGTH]; + int m_Score; + int m_Authed; + + void Reset(); + }; + + CClient m_aClients[MAX_CLIENTS]; + + CSnapshotDelta m_SnapshotDelta; + CSnapshotBuilder m_SnapshotBuilder; + CSnapIDPool m_IDPool; + CNetServer m_NetServer; + + IEngineMap *m_pMap; + + int64 m_GameStartTime; + //int m_CurrentGameTick; + int m_RunServer; + + char m_aBrowseinfoGametype[16]; + int m_BrowseinfoProgression; + + int64 m_Lastheartbeat; + //static NETADDR4 master_server; + + char m_aCurrentMap[64]; + int m_CurrentMapCrc; + unsigned char *m_pCurrentMapData; + int m_CurrentMapSize; + + CDemoRecorder m_DemoRecorder; + CEngine m_Engine; + CRegister m_Register; + + CServer(); + + int TrySetClientName(int ClientID, const char *pName); + + virtual void SetClientName(int ClientID, const char *pName); + virtual void SetClientScore(int ClientID, int Score); + virtual void SetBrowseInfo(const char *pGameType, int Progression); + + void Kick(int ClientID, const char *pReason); + + //int Tick() + int64 TickStartTime(int Tick); + //int TickSpeed() + + int Init(); + + int GetClientInfo(int ClientID, CClientInfo *pInfo); + void GetClientIP(int ClientID, char *pIPString, int Size); + const char *ClientName(int ClientId); + bool ClientIngame(int ClientID); + + int *LatestInput(int ClientId, int *size); + + virtual int SendMsg(CMsgPacker *pMsg, int Flags, int ClientId); + int SendMsgEx(CMsgPacker *pMsg, int Flags, int ClientID, bool System); + + void DoSnapshot(); + + static int NewClientCallback(int ClientId, void *pUser); + static int DelClientCallback(int ClientId, void *pUser); + + void SendMap(int ClientId); + void SendRconLine(int ClientId, const char *pLine); + static void SendRconLineAuthed(const char *pLine, void *pUser); + + void ProcessClientPacket(CNetChunk *pPacket); + + void SendServerInfo(NETADDR *pAddr, int Token); + void UpdateServerInfo(); + + int BanAdd(NETADDR Addr, int Seconds); + int BanRemove(NETADDR Addr); + + + void PumpNetwork(); + + int LoadMap(const char *pMapName); + + void InitEngine(const char *pAppname); + void InitRegister(CNetServer *pNetServer, IEngineMasterServer *pMasterServer); + int Run(); + + static void ConKick(IConsole::IResult *pResult, void *pUser); + static void ConBan(IConsole::IResult *pResult, void *pUser); + static void ConUnban(IConsole::IResult *pResult, void *pUser); + static void ConBans(IConsole::IResult *pResult, void *pUser); + static void ConStatus(IConsole::IResult *pResult, void *pUser); + static void ConShutdown(IConsole::IResult *pResult, void *pUser); + static void ConRecord(IConsole::IResult *pResult, void *pUser); + static void ConStopRecord(IConsole::IResult *pResult, void *pUser); + static void ConchainSpecialInfoupdate(IConsole::IResult *pResult, void *pUserData, IConsole::FCommandCallback pfnCallback, void *pCallbackUserData); + + void RegisterCommands(); + + + virtual int SnapNewID(); + virtual void SnapFreeID(int ID); + virtual void *SnapNewItem(int Type, int Id, int Size); + void SnapSetStaticsize(int ItemType, int Size); +}; + +#endif diff --git a/src/engine/serverbrowser.h b/src/engine/serverbrowser.h new file mode 100644 index 00000000..43732f13 --- /dev/null +++ b/src/engine/serverbrowser.h @@ -0,0 +1,93 @@ +#ifndef ENGINE_SERVERBROWSER_H +#define ENGINE_SERVERBROWSER_H + +#include "kernel.h" + +/* + Structure: CServerInfo +*/ +class CServerInfo +{ +public: + /* + Structure: CInfoPlayer + */ + class CPlayer + { + public: + char m_aName[48]; + int m_Score; + } ; + + int m_SortedIndex; + int m_ServerIndex; + + NETADDR m_NetAddr; + + int m_QuickSearchHit; + + int m_Progression; + int m_MaxPlayers; + int m_NumPlayers; + int m_Flags; + int m_Favorite; + int m_Latency; // in ms + char m_aGameType[16]; + char m_aName[64]; + char m_aMap[32]; + char m_aVersion[32]; + char m_aAddress[24]; + CPlayer m_aPlayers[16]; +}; + +class IServerBrowser : public IInterface +{ + MACRO_INTERFACE("serverbrowser", 0) +public: + + /* Constants: Server Browser Sorting + SORT_NAME - Sort by name. + SORT_PING - Sort by ping. + SORT_MAP - Sort by map + SORT_GAMETYPE - Sort by game type. DM, TDM etc. + SORT_PROGRESSION - Sort by progression. + SORT_NUMPLAYERS - Sort after how many players there are on the server. + */ + enum{ + SORT_NAME = 0, + SORT_PING, + SORT_MAP, + SORT_GAMETYPE, + SORT_PROGRESSION, + SORT_NUMPLAYERS, + + QUICK_SERVERNAME=1, + QUICK_PLAYERNAME=2, + QUICK_MAPNAME=4, + + TYPE_INTERNET = 0, + TYPE_LAN = 1, + TYPE_FAVORITES = 2, + + // TODO: clean this up + SET_MASTER_ADD=1, + SET_FAV_ADD, + SET_TOKEN, + SET_OLD_INTERNET, + SET_OLD_LAN + }; + + virtual void Refresh(int Type) = 0; + virtual bool IsRefreshingMasters() const = 0; + + virtual int NumServers() const = 0; + + virtual int NumSortedServers() const = 0; + virtual const CServerInfo *SortedGet(int Index) const = 0; + + virtual bool IsFavorite(const NETADDR &Addr) const = 0; + virtual void AddFavorite(const NETADDR &Addr) = 0; + virtual void RemoveFavorite(const NETADDR &Addr) = 0; +}; + +#endif diff --git a/src/engine/shared/compression.cpp b/src/engine/shared/compression.cpp new file mode 100644 index 00000000..63e44699 --- /dev/null +++ b/src/engine/shared/compression.cpp @@ -0,0 +1,88 @@ +// copyright (c) 2007 magnus auvinen, see licence.txt for more info +#include <base/system.h> + +#include "compression.h" + +// Format: ESDDDDDD EDDDDDDD EDD... Extended, Data, Sign +unsigned char *CVariableInt::Pack(unsigned char *pDst, int i) +{ + *pDst = (i>>25)&0x40; // set sign bit if i<0 + i = i^(i>>31); // if(i<0) i = ~i + + *pDst |= i&0x3F; // pack 6bit into dst + i >>= 6; // discard 6 bits + if(i) + { + *pDst |= 0x80; // set extend bit + while(1) + { + pDst++; + *pDst = i&(0x7F); // pack 7bit + i >>= 7; // discard 7 bits + *pDst |= (i!=0)<<7; // set extend bit (may branch) + if(!i) + break; + } + } + + pDst++; + return pDst; +} + +const unsigned char *CVariableInt::Unpack(const unsigned char *pSrc, int *pInOut) +{ + int Sign = (*pSrc>>6)&1; + *pInOut = *pSrc&0x3F; + + do + { + if(!(*pSrc&0x80)) break; + pSrc++; + *pInOut |= (*pSrc&(0x7F))<<(6); + + if(!(*pSrc&0x80)) break; + pSrc++; + *pInOut |= (*pSrc&(0x7F))<<(6+7); + + if(!(*pSrc&0x80)) break; + pSrc++; + *pInOut |= (*pSrc&(0x7F))<<(6+7+7); + + if(!(*pSrc&0x80)) break; + pSrc++; + *pInOut |= (*pSrc&(0x7F))<<(6+7+7+7); + } while(0); + + pSrc++; + *pInOut ^= -Sign; // if(sign) *i = ~(*i) + return pSrc; +} + + +long CVariableInt::Decompress(const void *pSrc_, int Size, void *pDst_) +{ + const unsigned char *pSrc = (unsigned char *)pSrc_; + const unsigned char *pEnd = pSrc + Size; + int *pDst = (int *)pDst_; + while(pSrc < pEnd) + { + pSrc = CVariableInt::Unpack(pSrc, pDst); + pDst++; + } + return (long)((unsigned char *)pDst-(unsigned char *)pDst_); +} + +long CVariableInt::Compress(const void *pSrc_, int Size, void *pDst_) +{ + int *pSrc = (int *)pSrc_; + unsigned char *pDst = (unsigned char *)pDst_; + Size /= 4; + while(Size) + { + pDst = CVariableInt::Pack(pDst, *pSrc); + Size--; + pSrc++; + } + return (long)(pDst-(unsigned char *)pDst_); +} + diff --git a/src/engine/shared/compression.h b/src/engine/shared/compression.h new file mode 100644 index 00000000..9bd9e61a --- /dev/null +++ b/src/engine/shared/compression.h @@ -0,0 +1,12 @@ +#ifndef ENGINE_SHARED_COMPRESSION_H +#define ENGINE_SHARED_COMPRESSION_H +// variable int packing +class CVariableInt +{ +public: + static unsigned char *Pack(unsigned char *pDst, int i); + static const unsigned char *Unpack(const unsigned char *pSrc, int *pInOut); + static long Compress(const void *pSrc, int Size, void *pDst); + static long Decompress(const void *pSrc, int Size, void *pDst); +}; +#endif diff --git a/src/engine/shared/config.cpp b/src/engine/shared/config.cpp new file mode 100644 index 00000000..ca12e8b7 --- /dev/null +++ b/src/engine/shared/config.cpp @@ -0,0 +1,111 @@ +#include <engine/config.h> +#include <engine/storage.h> +#include <engine/shared/config.h> + +CConfiguration g_Config; + +class CConfig : public IConfig +{ + IStorage *m_pStorage; + IOHANDLE m_ConfigFile; + + struct CCallback + { + SAVECALLBACKFUNC m_pfnFunc; + void *m_pUserData; + }; + + enum + { + MAX_CALLBACKS = 16 + }; + + CCallback m_aCallbacks[MAX_CALLBACKS]; + int m_NumCallbacks; + + void EscapeParam(char *pDst, const char *pSrc, int size) + { + for(int i = 0; *pSrc && i < size - 1; ++i) + { + if(*pSrc == '"' || *pSrc == '\\') // escape \ and " + *pDst++ = '\\'; + *pDst++ = *pSrc++; + } + *pDst = 0; + } + +public: + + CConfig() + { + m_ConfigFile = 0; + m_NumCallbacks = 0; + } + + virtual void Init() + { + m_pStorage = Kernel()->RequestInterface<IStorage>(); + Reset(); + } + + virtual void Reset() + { + #define MACRO_CONFIG_INT(Name,ScriptName,def,min,max,flags,desc) g_Config.m_##Name = def; + #define MACRO_CONFIG_STR(Name,ScriptName,len,def,flags,desc) str_copy(g_Config.m_##Name, def, len); + + #include "config_variables.h" + + #undef MACRO_CONFIG_INT + #undef MACRO_CONFIG_STR + } + + virtual void Save() + { + if(!m_pStorage) + return; + m_ConfigFile = m_pStorage->OpenFile("settings.cfg", IOFLAG_WRITE); + + if(!m_ConfigFile) + return; + + char aLineBuf[1024*2]; + char aEscapeBuf[1024*2]; + + #define MACRO_CONFIG_INT(Name,ScriptName,def,min,max,flags,desc) if((flags)&CFGFLAG_SAVE){ str_format(aLineBuf, sizeof(aLineBuf), "%s %i", #ScriptName, g_Config.m_##Name); WriteLine(aLineBuf); } + #define MACRO_CONFIG_STR(Name,ScriptName,len,def,flags,desc) if((flags)&CFGFLAG_SAVE){ EscapeParam(aEscapeBuf, g_Config.m_##Name, sizeof(aEscapeBuf)); str_format(aLineBuf, sizeof(aLineBuf), "%s \"%s\"", #ScriptName, aEscapeBuf); WriteLine(aLineBuf); } + + #include "config_variables.h" + + #undef MACRO_CONFIG_INT + #undef MACRO_CONFIG_STR + + for(int i = 0; i < m_NumCallbacks; i++) + m_aCallbacks[i].m_pfnFunc(this, m_aCallbacks[i].m_pUserData); + + io_close(m_ConfigFile); + m_ConfigFile = 0; + } + + virtual void RegisterCallback(SAVECALLBACKFUNC pfnFunc, void *pUserData) + { + dbg_assert(m_NumCallbacks < MAX_CALLBACKS, "too many config callbacks"); + m_aCallbacks[m_NumCallbacks].m_pfnFunc = pfnFunc; + m_aCallbacks[m_NumCallbacks].m_pUserData = pUserData; + m_NumCallbacks++; + } + + virtual void WriteLine(const char *pLine) + { + if(!m_ConfigFile) + return; +#if defined(CONF_FAMILY_WINDOWS) + static const char Newline[] = "\r\n"; +#else + static const char Newline[] = "\n"; +#endif + io_write(m_ConfigFile, pLine, str_length(pLine)); + io_write(m_ConfigFile, Newline, sizeof(Newline)-1); + } +}; + +IConfig *CreateConfig() { return new CConfig; } diff --git a/src/engine/shared/config.h b/src/engine/shared/config.h new file mode 100644 index 00000000..10a54004 --- /dev/null +++ b/src/engine/shared/config.h @@ -0,0 +1,22 @@ +#ifndef ENGINE_SHARED_E_CONFIG_H +#define ENGINE_SHARED_E_CONFIG_H + +struct CConfiguration +{ + #define MACRO_CONFIG_INT(Name,ScriptName,Def,Min,Max,Save,Desc) int m_##Name; + #define MACRO_CONFIG_STR(Name,ScriptName,Len,Def,Save,Desc) char m_##Name[Len]; // Flawfinder: ignore + #include "config_variables.h" + #undef MACRO_CONFIG_INT + #undef MACRO_CONFIG_STR +}; + +extern CConfiguration g_Config; + +enum +{ + CFGFLAG_SAVE=1, + CFGFLAG_CLIENT=2, + CFGFLAG_SERVER=4 +}; + +#endif diff --git a/src/engine/shared/config_variables.h b/src/engine/shared/config_variables.h new file mode 100644 index 00000000..e5541911 --- /dev/null +++ b/src/engine/shared/config_variables.h @@ -0,0 +1,80 @@ +#ifndef ENGINE_SHARED_E_CONFIG_VARIABLES_H +#define ENGINE_SHARED_E_CONFIG_VARIABLES_H +#undef ENGINE_SHARED_E_CONFIG_VARIABLES_H // this file will be included several times + +// TODO: remove this +#include "././game/variables.h" + + +MACRO_CONFIG_STR(PlayerName, player_name, 24, "nameless tee", CFGFLAG_SAVE|CFGFLAG_CLIENT, "Name of the player") +MACRO_CONFIG_STR(ClanName, clan_name, 32, "", CFGFLAG_SAVE|CFGFLAG_CLIENT, "(not used)") +MACRO_CONFIG_STR(Password, password, 32, "", CFGFLAG_CLIENT|CFGFLAG_SERVER, "Password to the server") +MACRO_CONFIG_STR(Logfile, logfile, 128, "", CFGFLAG_SAVE|CFGFLAG_CLIENT, "Filename to log all output to") + +MACRO_CONFIG_INT(ClCpuThrottle, cl_cpu_throttle, 0, 0, 1, CFGFLAG_SAVE|CFGFLAG_CLIENT, "") +MACRO_CONFIG_INT(ClEditor, cl_editor, 0, 0, 1, CFGFLAG_CLIENT, "") + +MACRO_CONFIG_INT(ClEventthread, cl_eventthread, 0, 0, 1, CFGFLAG_CLIENT, "Enables the usage of a thread to pump the events") + +MACRO_CONFIG_INT(InpGrab, inp_grab, 0, 0, 1, CFGFLAG_SAVE|CFGFLAG_CLIENT, "Use forceful input grabbing method") + +MACRO_CONFIG_STR(BrFilterString, br_filter_string, 25, "", CFGFLAG_SAVE|CFGFLAG_CLIENT, "Server browser filtering string") + +MACRO_CONFIG_INT(BrFilterFull, br_filter_full, 0, 0, 1, CFGFLAG_SAVE|CFGFLAG_CLIENT, "Filter out full server in browser") +MACRO_CONFIG_INT(BrFilterEmpty, br_filter_empty, 0, 0, 1, CFGFLAG_SAVE|CFGFLAG_CLIENT, "Filter out empty server in browser") +MACRO_CONFIG_INT(BrFilterPw, br_filter_pw, 0, 0, 1, CFGFLAG_SAVE|CFGFLAG_CLIENT, "Filter out password protected servers in browser") +MACRO_CONFIG_INT(BrFilterPing, br_filter_ping, 999, 0, 999, CFGFLAG_SAVE|CFGFLAG_CLIENT, "Ping to filter by in the server browser") +MACRO_CONFIG_STR(BrFilterGametype, br_filter_gametype, 128, "", CFGFLAG_SAVE|CFGFLAG_CLIENT, "Game types to filter") +MACRO_CONFIG_INT(BrFilterPure, br_filter_pure, 1, 0, 1, CFGFLAG_SAVE|CFGFLAG_CLIENT, "Filter out non-standard servers in browser") +MACRO_CONFIG_INT(BrFilterPureMap, br_filter_pure_map, 1, 0, 1, CFGFLAG_SAVE|CFGFLAG_CLIENT, "Filter out non-standard maps in browser") +MACRO_CONFIG_INT(BrFilterCompatversion, br_filter_compatversion, 1, 0, 1, CFGFLAG_SAVE|CFGFLAG_CLIENT, "Filter out non-compatible servers in browser") + +MACRO_CONFIG_INT(BrSort, br_sort, 0, 0, 256, CFGFLAG_SAVE|CFGFLAG_CLIENT, "") +MACRO_CONFIG_INT(BrSortOrder, br_sort_order, 0, 0, 1, CFGFLAG_SAVE|CFGFLAG_CLIENT, "") +MACRO_CONFIG_INT(BrMaxRequests, br_max_requests, 10, 0, 1000, CFGFLAG_SAVE|CFGFLAG_CLIENT, "Number of requests to use when refreshing server browser") + +MACRO_CONFIG_INT(SndBufferSize, snd_buffer_size, 512, 0, 0, CFGFLAG_SAVE|CFGFLAG_CLIENT, "Sound buffer size") +MACRO_CONFIG_INT(SndRate, snd_rate, 48000, 0, 0, CFGFLAG_SAVE|CFGFLAG_CLIENT, "Sound mixing rate") +MACRO_CONFIG_INT(SndEnable, snd_enable, 1, 0, 1, CFGFLAG_SAVE|CFGFLAG_CLIENT, "Sound enable") +MACRO_CONFIG_INT(SndVolume, snd_volume, 100, 0, 100, CFGFLAG_SAVE|CFGFLAG_CLIENT, "Sound volume") +MACRO_CONFIG_INT(SndDevice, snd_device, -1, 0, 0, CFGFLAG_SAVE|CFGFLAG_CLIENT, "(deprecated) Sound device to use") + +MACRO_CONFIG_INT(SndNonactiveMute, snd_nonactive_mute, 0, 0, 1, CFGFLAG_SAVE|CFGFLAG_CLIENT, "") + +MACRO_CONFIG_INT(GfxScreenWidth, gfx_screen_width, 800, 0, 0, CFGFLAG_SAVE|CFGFLAG_CLIENT, "Screen resolution width") +MACRO_CONFIG_INT(GfxScreenHeight, gfx_screen_height, 600, 0, 0, CFGFLAG_SAVE|CFGFLAG_CLIENT, "Screen resolution height") +MACRO_CONFIG_INT(GfxFullscreen, gfx_fullscreen, 1, 0, 1, CFGFLAG_SAVE|CFGFLAG_CLIENT, "Fullscreen") +MACRO_CONFIG_INT(GfxAlphabits, gfx_alphabits, 0, 0, 0, CFGFLAG_SAVE|CFGFLAG_CLIENT, "Alpha bits for framebuffer (fullscreen only)") +MACRO_CONFIG_INT(GfxColorDepth, gfx_color_depth, 24, 16, 24, CFGFLAG_SAVE|CFGFLAG_CLIENT, "Colors bits for framebuffer (fullscreen only)") +MACRO_CONFIG_INT(GfxClear, gfx_clear, 0, 0, 1, CFGFLAG_SAVE|CFGFLAG_CLIENT, "Clear screen before rendering") +MACRO_CONFIG_INT(GfxVsync, gfx_vsync, 1, 0, 1, CFGFLAG_SAVE|CFGFLAG_CLIENT, "Vertical sync") +MACRO_CONFIG_INT(GfxDisplayAllModes, gfx_display_all_modes, 0, 0, 1, CFGFLAG_SAVE|CFGFLAG_CLIENT, "") +MACRO_CONFIG_INT(GfxTextureCompression, gfx_texture_compression, 0, 0, 1, CFGFLAG_SAVE|CFGFLAG_CLIENT, "Use texture compression") +MACRO_CONFIG_INT(GfxHighDetail, gfx_high_detail, 1, 0, 1, CFGFLAG_SAVE|CFGFLAG_CLIENT, "High detail") +MACRO_CONFIG_INT(GfxTextureQuality, gfx_texture_quality, 1, 0, 1, CFGFLAG_SAVE|CFGFLAG_CLIENT, "") +MACRO_CONFIG_INT(GfxFsaaSamples, gfx_fsaa_samples, 0, 0, 16, CFGFLAG_SAVE|CFGFLAG_CLIENT, "FSAA Samples") +MACRO_CONFIG_INT(GfxRefreshRate, gfx_refresh_rate, 0, 0, 0, CFGFLAG_SAVE|CFGFLAG_CLIENT, "Screen refresh rate") +MACRO_CONFIG_INT(GfxFinish, gfx_finish, 1, 0, 1, CFGFLAG_SAVE|CFGFLAG_CLIENT, "") + +MACRO_CONFIG_INT(InpMousesens, inp_mousesens, 100, 5, 100000, CFGFLAG_SAVE|CFGFLAG_CLIENT, "Mouse sensitivity") + +MACRO_CONFIG_STR(SvName, sv_name, 128, "unnamed server", CFGFLAG_SERVER, "Server name") +MACRO_CONFIG_STR(SvBindaddr, sv_bindaddr, 128, "", CFGFLAG_SERVER, "Address to bind the server to") +MACRO_CONFIG_INT(SvPort, sv_port, 8303, 0, 0, CFGFLAG_SERVER, "Port to use for the server") +MACRO_CONFIG_INT(SvExternalPort, sv_external_port, 0, 0, 0, CFGFLAG_SERVER, "External port to report to the master servers") +MACRO_CONFIG_STR(SvMap, sv_map, 128, "dm1", CFGFLAG_SERVER, "Map to use on the server") +MACRO_CONFIG_INT(SvMaxClients, sv_max_clients, 8, 1, MAX_CLIENTS, CFGFLAG_SERVER, "Maximum number of clients that are allowed on a server") +MACRO_CONFIG_INT(SvHighBandwidth, sv_high_bandwidth, 0, 0, 1, CFGFLAG_SERVER, "Use high bandwidth mode. Doubles the bandwidth required for the server. LAN use only") +MACRO_CONFIG_INT(SvRegister, sv_register, 1, 0, 1, CFGFLAG_SERVER, "Register server with master server for public listing") +MACRO_CONFIG_STR(SvRconPassword, sv_rcon_password, 32, "", CFGFLAG_SERVER, "Remote console password") +MACRO_CONFIG_INT(SvMapReload, sv_map_reload, 0, 0, 1, CFGFLAG_SERVER, "Reload the current map") + +MACRO_CONFIG_INT(Debug, debug, 0, 0, 1, CFGFLAG_CLIENT|CFGFLAG_SERVER, "Debug mode") +MACRO_CONFIG_INT(DbgStress, dbg_stress, 0, 0, 0, CFGFLAG_CLIENT|CFGFLAG_SERVER, "Stress systems") +MACRO_CONFIG_INT(DbgStressNetwork, dbg_stress_network, 0, 0, 0, CFGFLAG_CLIENT|CFGFLAG_SERVER, "Stress network") +MACRO_CONFIG_INT(DbgPref, dbg_pref, 0, 0, 1, CFGFLAG_SERVER, "Performance outputs") +MACRO_CONFIG_INT(DbgGraphs, dbg_graphs, 0, 0, 1, CFGFLAG_CLIENT, "Performance graphs") +MACRO_CONFIG_INT(DbgHitch, dbg_hitch, 0, 0, 0, CFGFLAG_SERVER, "Hitch warnings") +MACRO_CONFIG_STR(DbgStressServer, dbg_stress_server, 32, "localhost", CFGFLAG_CLIENT, "Server to stress") +MACRO_CONFIG_INT(DbgResizable, dbg_resizable, 0, 0, 0, CFGFLAG_CLIENT, "Enables window resizing") +#endif diff --git a/src/engine/shared/console.cpp b/src/engine/shared/console.cpp new file mode 100644 index 00000000..c545b7db --- /dev/null +++ b/src/engine/shared/console.cpp @@ -0,0 +1,489 @@ +#include <base/system.h> +#include <engine/shared/protocol.h> +#include <engine/storage.h> +#include "console.h" +#include "config.h" +#include "engine.h" +#include "linereader.h" + +const char *CConsole::CResult::GetString(unsigned Index) +{ + if (Index < 0 || Index >= m_NumArgs) + return ""; + return m_apArgs[Index]; +} + +int CConsole::CResult::GetInteger(unsigned Index) +{ + if (Index < 0 || Index >= m_NumArgs) + return 0; + return str_toint(m_apArgs[Index]); +} + +float CConsole::CResult::GetFloat(unsigned Index) +{ + if (Index < 0 || Index >= m_NumArgs) + return 0.0f; + return str_tofloat(m_apArgs[Index]); +} + +// the maximum number of tokens occurs in a string of length CONSOLE_MAX_STR_LENGTH with tokens size 1 separated by single spaces +static char *SkipBlanks(char *pStr) +{ + while(*pStr && (*pStr == ' ' || *pStr == '\t' || *pStr == '\n')) + pStr++; + return pStr; +} + +static char *SkipToBlank(char *pStr) +{ + while(*pStr && (*pStr != ' ' && *pStr != '\t' && *pStr != '\n')) + pStr++; + return pStr; +} + + +int CConsole::ParseStart(CResult *pResult, const char *pString, int Length) +{ + char *pStr; + int Len = sizeof(pResult->m_aStringStorage); + if(Length < Len) + Len = Length; + + str_copy(pResult->m_aStringStorage, pString, Length); + pStr = pResult->m_aStringStorage; + + // get command + pStr = SkipBlanks(pStr); + pResult->m_pCommand = pStr; + pStr = SkipToBlank(pStr); + + if(*pStr) + { + pStr[0] = 0; + pStr++; + } + + pResult->m_pArgsStart = pStr; + return 0; +} + +int CConsole::ParseArgs(CResult *pResult, const char *pFormat) +{ + char Command; + char *pStr; + int Optional = 0; + int Error = 0; + + pStr = pResult->m_pArgsStart; + + while(1) + { + // fetch command + Command = *pFormat; + pFormat++; + + if(!Command) + break; + + if(Command == '?') + Optional = 1; + else + { + pStr = SkipBlanks(pStr); + + if(!(*pStr)) // error, non optional command needs value + { + if(!Optional) + Error = 1; + break; + } + + // add token + if(*pStr == '"') + { + char *pDst; + pStr++; + pResult->AddArgument(pStr); + + pDst = pStr; // we might have to process escape data + while(1) + { + if(pStr[0] == '"') + break; + else if(pStr[0] == '\\') + { + if(pStr[1] == '\\') + pStr++; // skip due to escape + else if(pStr[1] == '"') + pStr++; // skip due to escape + } + else if(pStr[0] == 0) + return 1; // return error + + *pDst = *pStr; + pDst++; + pStr++; + } + + // write null termination + *pDst = 0; + + + pStr++; + } + else + { + pResult->AddArgument(pStr); + + if(Command == 'r') // rest of the string + break; + else if(Command == 'i') // validate int + pStr = SkipToBlank(pStr); + else if(Command == 'f') // validate float + pStr = SkipToBlank(pStr); + else if(Command == 's') // validate string + pStr = SkipToBlank(pStr); + + if(pStr[0] != 0) // check for end of string + { + pStr[0] = 0; + pStr++; + } + } + } + } + + return Error; +} + +void CConsole::RegisterPrintCallback(FPrintCallback pfnPrintCallback, void *pUserData) +{ + m_pfnPrintCallback = pfnPrintCallback; + m_pPrintCallbackUserdata = pUserData; +} + +void CConsole::Print(const char *pStr) +{ + dbg_msg("console" ,"%s", pStr); + if (m_pfnPrintCallback) + m_pfnPrintCallback(pStr, m_pPrintCallbackUserdata); +} + +void CConsole::ExecuteLineStroked(int Stroke, const char *pStr) +{ + CResult Result; + + char aStrokeStr[2] = {'0', 0}; + if(Stroke) + aStrokeStr[0] = '1'; + + while(pStr && *pStr) + { + const char *pEnd = pStr; + const char *pNextPart = 0; + int InString = 0; + + while(*pEnd) + { + if(*pEnd == '"') + InString ^= 1; + else if(*pEnd == '\\') // escape sequences + { + if(pEnd[1] == '"') + pEnd++; + } + else if(!InString) + { + if(*pEnd == ';') // command separator + { + pNextPart = pEnd+1; + break; + } + else if(*pEnd == '#') // comment, no need to do anything more + break; + } + + pEnd++; + } + + if(ParseStart(&Result, pStr, (pEnd-pStr) + 1) != 0) + return; + + CCommand *pCommand = FindCommand(Result.m_pCommand); + + if(pCommand) + { + int IsStrokeCommand = 0; + if(Result.m_pCommand[0] == '+') + { + // insert the stroke direction token + Result.AddArgument(aStrokeStr); + IsStrokeCommand = 1; + } + + if(Stroke || IsStrokeCommand) + { + if(ParseArgs(&Result, pCommand->m_pParams)) + { + char aBuf[256]; + str_format(aBuf, sizeof(aBuf), "Invalid arguments... Usage: %s %s", pCommand->m_pName, pCommand->m_pParams); + Print(aBuf); + } + else + pCommand->m_pfnCallback(&Result, pCommand->m_pUserData); + } + } + else + { + char aBuf[256]; + str_format(aBuf, sizeof(aBuf), "No such command: %s.", Result.m_pCommand); + Print(aBuf); + } + + pStr = pNextPart; + } +} + +void CConsole::PossibleCommands(const char *pStr, int FlagMask, FPossibleCallback pfnCallback, void *pUser) +{ + CCommand *pCommand; + for(pCommand = m_pFirstCommand; pCommand; pCommand = pCommand->m_pNext) + { + if(pCommand->m_Flags&FlagMask) + { + if(str_find_nocase(pCommand->m_pName, pStr)) + pfnCallback(pCommand->m_pName, pUser); + } + } +} + +// TODO: this should regard the commands flag +CConsole::CCommand *CConsole::FindCommand(const char *pName) +{ + CCommand *pCommand; + for (pCommand = m_pFirstCommand; pCommand; pCommand = pCommand->m_pNext) + { + if(str_comp_nocase(pCommand->m_pName, pName) == 0) + return pCommand; + } + + return 0x0; +} + +void CConsole::ExecuteLine(const char *pStr) +{ + CConsole::ExecuteLineStroked(1, pStr); +} + + +void CConsole::ExecuteFile(const char *pFilename) +{ + // make sure that this isn't being executed already + for(CExecFile *pCur = m_pFirstExec; pCur; pCur = pCur->m_pPrev) + if(str_comp(pFilename, pCur->m_pFilename) == 0) + return; + + if(!m_pStorage) + m_pStorage = Kernel()->RequestInterface<IStorage>(); + if(!m_pStorage) + return; + + // push this one to the stack + CExecFile ThisFile; + CExecFile *pPrev = m_pFirstExec; + ThisFile.m_pFilename = pFilename; + ThisFile.m_pPrev = m_pFirstExec; + m_pFirstExec = &ThisFile; + + // exec the file + IOHANDLE File = m_pStorage->OpenFile(pFilename, IOFLAG_READ); + + if(File) + { + char *pLine; + CLineReader lr; + + dbg_msg("console", "executing '%s'", pFilename); + lr.Init(File); + + while((pLine = lr.Get())) + ExecuteLine(pLine); + + io_close(File); + } + else + dbg_msg("console", "failed to open '%s'", pFilename); + + m_pFirstExec = pPrev; +} + +void CConsole::Con_Echo(IResult *pResult, void *pUserData) +{ + ((CConsole*)pUserData)->Print(pResult->GetString(0)); +} + +void CConsole::Con_Exec(IResult *pResult, void *pUserData) +{ + ((CConsole*)pUserData)->ExecuteFile(pResult->GetString(0)); +} + +struct CIntVariableData +{ + IConsole *m_pConsole; + int *m_pVariable; + int m_Min; + int m_Max; +}; + +struct CStrVariableData +{ + IConsole *m_pConsole; + char *m_pStr; + int m_MaxSize; +}; + +static void IntVariableCommand(IConsole::IResult *pResult, void *pUserData) +{ + CIntVariableData *pData = (CIntVariableData *)pUserData; + + if(pResult->NumArguments()) + { + int Val = pResult->GetInteger(0); + + // do clamping + if(pData->m_Min != pData->m_Max) + { + if (Val < pData->m_Min) + Val = pData->m_Min; + if (pData->m_Max != 0 && Val > pData->m_Max) + Val = pData->m_Max; + } + + *(pData->m_pVariable) = Val; + } + else + { + char aBuf[1024]; + str_format(aBuf, sizeof(aBuf), "Value: %d", *(pData->m_pVariable)); + pData->m_pConsole->Print(aBuf); + } +} + +static void StrVariableCommand(IConsole::IResult *pResult, void *pUserData) +{ + CStrVariableData *pData = (CStrVariableData *)pUserData; + + if(pResult->NumArguments()) + str_copy(pData->m_pStr, pResult->GetString(0), pData->m_MaxSize); + else + { + char aBuf[1024]; + str_format(aBuf, sizeof(aBuf), "Value: %s", pData->m_pStr); + pData->m_pConsole->Print(aBuf); + } +} + +CConsole::CConsole() +{ + m_pFirstCommand = 0; + m_pFirstExec = 0; + m_pPrintCallbackUserdata = 0; + m_pfnPrintCallback = 0; + + m_pStorage = 0; + + // register some basic commands + Register("echo", "r", CFGFLAG_SERVER|CFGFLAG_CLIENT, Con_Echo, this, "Echo the text"); + Register("exec", "r", CFGFLAG_SERVER|CFGFLAG_CLIENT, Con_Exec, this, "Execute the specified file"); + + // TODO: this should disappear + #define MACRO_CONFIG_INT(Name,ScriptName,Def,Min,Max,Flags,Desc) \ + { \ + static CIntVariableData Data = { this, &g_Config.m_##Name, Min, Max }; \ + Register(#ScriptName, "?i", Flags, IntVariableCommand, &Data, Desc); \ + } + + #define MACRO_CONFIG_STR(Name,ScriptName,Len,Def,Flags,Desc) \ + { \ + static CStrVariableData Data = { this, g_Config.m_##Name, Len }; \ + Register(#ScriptName, "?r", Flags, StrVariableCommand, &Data, Desc); \ + } + + #include "config_variables.h" + + #undef MACRO_CONFIG_INT + #undef MACRO_CONFIG_STR +} + +void CConsole::ParseArguments(int NumArgs, const char **ppArguments) +{ + for(int i = 0; i < NumArgs; i++) + { + // check for scripts to execute + if(ppArguments[i][0] == '-' && ppArguments[i][1] == 'f' && ppArguments[i][2] == 0 && NumArgs - i > 1) + { + ExecuteFile(ppArguments[i+1]); + i++; + } + else + { + // search arguments for overrides + ExecuteLine(ppArguments[i]); + } + } +} + +void CConsole::Register(const char *pName, const char *pParams, + int Flags, FCommandCallback pfnFunc, void *pUser, const char *pHelp) +{ + CCommand *pCommand = (CCommand *)mem_alloc(sizeof(CCommand), sizeof(void*)); + pCommand->m_pfnCallback = pfnFunc; + pCommand->m_pUserData = pUser; + pCommand->m_pHelp = pHelp; + pCommand->m_pName = pName; + pCommand->m_pParams = pParams; + pCommand->m_Flags = Flags; + + + pCommand->m_pNext = m_pFirstCommand; + m_pFirstCommand = pCommand; +} + +void CConsole::Con_Chain(IResult *pResult, void *pUserData) +{ + CChain *pInfo = (CChain *)pUserData; + pInfo->m_pfnChainCallback(pResult, pInfo->m_pUserData, pInfo->m_pfnCallback, pInfo->m_pCallbackUserData); +} + +void CConsole::Chain(const char *pName, FChainCommandCallback pfnChainFunc, void *pUser) +{ + CCommand *pCommand = FindCommand(pName); + + if(!pCommand) + { + dbg_msg("console", "failed to chain '%s'", pName); + return; + } + + CChain *pChainInfo = (CChain *)mem_alloc(sizeof(CChain), sizeof(void*)); + + // store info + pChainInfo->m_pfnChainCallback = pfnChainFunc; + pChainInfo->m_pUserData = pUser; + pChainInfo->m_pfnCallback = pCommand->m_pfnCallback; + pChainInfo->m_pCallbackUserData = pCommand->m_pUserData; + + // chain + pCommand->m_pfnCallback = Con_Chain; + pCommand->m_pUserData = pChainInfo; +} + + +IConsole::CCommandInfo *CConsole::GetCommandInfo(const char *pName) +{ + return FindCommand(pName); +} + + +extern IConsole *CreateConsole() { return new CConsole(); } diff --git a/src/engine/shared/console.h b/src/engine/shared/console.h new file mode 100644 index 00000000..93d23547 --- /dev/null +++ b/src/engine/shared/console.h @@ -0,0 +1,96 @@ +#ifndef ENGINE_SHARED_CONSOLE_H +#define ENGINE_SHARED_CONSOLE_H + +#include <engine/console.h> + +class CConsole : public IConsole +{ + class CCommand : public CCommandInfo + { + public: + CCommand *m_pNext; + int m_Flags; + FCommandCallback m_pfnCallback; + void *m_pUserData; + }; + + + class CChain + { + public: + FChainCommandCallback m_pfnChainCallback; + FCommandCallback m_pfnCallback; + void *m_pCallbackUserData; + void *m_pUserData; + }; + + CCommand *m_pFirstCommand; + + class CExecFile + { + public: + const char *m_pFilename; + struct CExecFile *m_pPrev; + }; + + CExecFile *m_pFirstExec; + class IStorage *m_pStorage; + + static void Con_Chain(IResult *pResult, void *pUserData); + static void Con_Echo(IResult *pResult, void *pUserData); + static void Con_Exec(IResult *pResult, void *pUserData); + + void ExecuteFileRecurse(const char *pFilename); + void ExecuteLineStroked(int Stroke, const char *pStr); + + FPrintCallback m_pfnPrintCallback; + void *m_pPrintCallbackUserdata; + + enum + { + CONSOLE_MAX_STR_LENGTH = 1024, + MAX_PARTS = (CONSOLE_MAX_STR_LENGTH+1)/2 + }; + + class CResult : public IResult + { + public: + char m_aStringStorage[CONSOLE_MAX_STR_LENGTH+1]; + char *m_pArgsStart; + + const char *m_pCommand; + const char *m_apArgs[MAX_PARTS]; + + void AddArgument(const char *pArg) + { + m_apArgs[m_NumArgs++] = pArg; + } + + virtual const char *GetString(unsigned Index); + virtual int GetInteger(unsigned Index); + virtual float GetFloat(unsigned Index); + }; + + int ParseStart(CResult *pResult, const char *pString, int Length); + int ParseArgs(CResult *pResult, const char *pFormat); + + CCommand *FindCommand(const char *pName); + +public: + CConsole(); + + virtual CCommandInfo *GetCommandInfo(const char *pName); + virtual void PossibleCommands(const char *pStr, int FlagMask, FPossibleCallback pfnCallback, void *pUser) ; + + virtual void ParseArguments(int NumArgs, const char **ppArguments); + virtual void Register(const char *pName, const char *pParams, int Flags, FCommandCallback pfnFunc, void *pUser, const char *pHelp); + virtual void Chain(const char *pName, FChainCommandCallback pfnChainFunc, void *pUser); + + virtual void ExecuteLine(const char *pStr); + virtual void ExecuteFile(const char *pFilename); + + virtual void RegisterPrintCallback(FPrintCallback pfnPrintCallback, void *pUserData); + virtual void Print(const char *pStr); +}; + +#endif diff --git a/src/engine/shared/datafile.cpp b/src/engine/shared/datafile.cpp new file mode 100644 index 00000000..dcc32ef2 --- /dev/null +++ b/src/engine/shared/datafile.cpp @@ -0,0 +1,643 @@ +// copyright (c) 2007 magnus auvinen, see licence.txt for more info +#include <base/system.h> +#include <engine/storage.h> +#include "datafile.h" +#include "engine.h" +#include <zlib.h> + +static const int DEBUG=0; + +struct CDatafileItemType +{ + int m_Type; + int m_Start; + int m_Num; +} ; + +struct CDatafileItem +{ + int m_TypeAndId; + int m_Size; +}; + +struct CDatafileHeader +{ + char m_aId[4]; + int m_Version; + int m_Size; + int m_Swaplen; + int m_NumItemTypes; + int m_NumItems; + int m_NumRawData; + int m_ItemSize; + int m_DataSize; +}; + +struct CDatafileData +{ + int m_NumItemTypes; + int m_NumItems; + int m_NumRawData; + int m_ItemSize; + int m_DataSize; + char m_aStart[4]; +}; + +struct CDatafileInfo +{ + CDatafileItemType *m_pItemTypes; + int *m_pItemOffsets; + int *m_pDataOffsets; + int *m_pDataSizes; + + char *m_pItemStart; + char *m_pDataStart; +}; + +struct CDatafile +{ + IOHANDLE m_File; + unsigned m_Crc; + CDatafileInfo m_Info; + CDatafileHeader m_Header; + int m_DataStartOffset; + char **m_ppDataPtrs; + char *m_pData; +}; + +bool CDataFileReader::Open(class IStorage *pStorage, const char *pFilename) +{ + dbg_msg("datafile", "loading. filename='%s'", pFilename); + + IOHANDLE File = pStorage->OpenFile(pFilename, IOFLAG_READ); + if(!File) + { + dbg_msg("datafile", "could not open '%s'", pFilename); + return false; + } + + + // take the CRC of the file and store it + unsigned Crc = 0; + { + enum + { + BUFFER_SIZE = 64*1024 + }; + + unsigned char aBuffer[BUFFER_SIZE]; + + while(1) + { + unsigned Bytes = io_read(File, aBuffer, BUFFER_SIZE); + if(Bytes <= 0) + break; + Crc = crc32(Crc, aBuffer, Bytes); // ignore_convention + } + + io_seek(File, 0, IOSEEK_START); + } + + + // TODO: change this header + CDatafileHeader Header; + io_read(File, &Header, sizeof(Header)); + if(Header.m_aId[0] != 'A' || Header.m_aId[1] != 'T' || Header.m_aId[2] != 'A' || Header.m_aId[3] != 'D') + { + if(Header.m_aId[0] != 'D' || Header.m_aId[1] != 'A' || Header.m_aId[2] != 'T' || Header.m_aId[3] != 'A') + { + dbg_msg("datafile", "wrong signature. %x %x %x %x", Header.m_aId[0], Header.m_aId[1], Header.m_aId[2], Header.m_aId[3]); + return 0; + } + } + +#if defined(CONF_ARCH_ENDIAN_BIG) + swap_endian(&Header, sizeof(int), sizeof(Header)/sizeof(int)); +#endif + if(Header.m_Version != 3 && Header.m_Version != 4) + { + dbg_msg("datafile", "wrong version. version=%x", Header.m_Version); + return 0; + } + + // read in the rest except the data + unsigned Size = 0; + Size += Header.m_NumItemTypes*sizeof(CDatafileItemType); + Size += (Header.m_NumItems+Header.m_NumRawData)*sizeof(int); + if(Header.m_Version == 4) + Size += Header.m_NumRawData*sizeof(int); // v4 has uncompressed data sizes aswell + Size += Header.m_ItemSize; + + unsigned AllocSize = Size; + AllocSize += sizeof(CDatafile); // add space for info structure + AllocSize += Header.m_NumRawData*sizeof(void*); // add space for data pointers + + m_pDataFile = (CDatafile*)mem_alloc(AllocSize, 1); + m_pDataFile->m_Header = Header; + m_pDataFile->m_DataStartOffset = sizeof(CDatafileHeader) + Size; + m_pDataFile->m_ppDataPtrs = (char**)(m_pDataFile+1); + m_pDataFile->m_pData = (char *)(m_pDataFile+1)+Header.m_NumRawData*sizeof(char *); + m_pDataFile->m_File = File; + m_pDataFile->m_Crc = Crc; + + // clear the data pointers + mem_zero(m_pDataFile->m_ppDataPtrs, Header.m_NumRawData*sizeof(void*)); + + // read types, offsets, sizes and item data + unsigned ReadSize = io_read(File, m_pDataFile->m_pData, Size); + if(ReadSize != Size) + { + mem_free(m_pDataFile); + m_pDataFile = 0; + dbg_msg("datafile", "couldn't load the whole thing, wanted=%d got=%d", Size, ReadSize); + return false; + } + +#if defined(CONF_ARCH_ENDIAN_BIG) + swap_endian(m_pDataFile->m_pData, sizeof(int), Header.Swaplen / sizeof(int)); +#endif + + //if(DEBUG) + { + dbg_msg("datafile", "allocsize=%d", AllocSize); + dbg_msg("datafile", "readsize=%d", ReadSize); + dbg_msg("datafile", "swaplen=%d", Header.m_Swaplen); + dbg_msg("datafile", "item_size=%d", m_pDataFile->m_Header.m_ItemSize); + } + + m_pDataFile->m_Info.m_pItemTypes = (CDatafileItemType *)m_pDataFile->m_pData; + m_pDataFile->m_Info.m_pItemOffsets = (int *)&m_pDataFile->m_Info.m_pItemTypes[m_pDataFile->m_Header.m_NumItemTypes]; + m_pDataFile->m_Info.m_pDataOffsets = (int *)&m_pDataFile->m_Info.m_pItemOffsets[m_pDataFile->m_Header.m_NumItems]; + m_pDataFile->m_Info.m_pDataSizes = (int *)&m_pDataFile->m_Info.m_pDataOffsets[m_pDataFile->m_Header.m_NumRawData]; + + if(Header.m_Version == 4) + m_pDataFile->m_Info.m_pItemStart = (char *)&m_pDataFile->m_Info.m_pDataSizes[m_pDataFile->m_Header.m_NumRawData]; + else + m_pDataFile->m_Info.m_pItemStart = (char *)&m_pDataFile->m_Info.m_pDataOffsets[m_pDataFile->m_Header.m_NumRawData]; + m_pDataFile->m_Info.m_pDataStart = m_pDataFile->m_Info.m_pItemStart + m_pDataFile->m_Header.m_ItemSize; + + dbg_msg("datafile", "loading done. datafile='%s'", pFilename); + + if(DEBUG) + { + /* + for(int i = 0; i < m_pDataFile->data.num_raw_data; i++) + { + void *p = datafile_get_data(df, i); + dbg_msg("datafile", "%d %d", (int)((char*)p - (char*)(&m_pDataFile->data)), size); + } + + for(int i = 0; i < datafile_num_items(df); i++) + { + int type, id; + void *data = datafile_get_item(df, i, &type, &id); + dbg_msg("map", "\t%d: type=%x id=%x p=%p offset=%d", i, type, id, data, m_pDataFile->info.item_offsets[i]); + int *idata = (int*)data; + for(int k = 0; k < 3; k++) + dbg_msg("datafile", "\t\t%d=%d (%x)", k, idata[k], idata[k]); + } + + for(int i = 0; i < m_pDataFile->data.num_m_aItemTypes; i++) + { + dbg_msg("map", "\t%d: type=%x start=%d num=%d", i, + m_pDataFile->info.m_aItemTypes[i].type, + m_pDataFile->info.m_aItemTypes[i].start, + m_pDataFile->info.m_aItemTypes[i].num); + for(int k = 0; k < m_pDataFile->info.m_aItemTypes[i].num; k++) + { + int type, id; + datafile_get_item(df, m_pDataFile->info.m_aItemTypes[i].start+k, &type, &id); + if(type != m_pDataFile->info.m_aItemTypes[i].type) + dbg_msg("map", "\tERROR"); + } + } + */ + } + + return true; +} + +int CDataFileReader::NumData() +{ + if(!m_pDataFile) { return 0; } + return m_pDataFile->m_Header.m_NumRawData; +} + +// always returns the size in the file +int CDataFileReader::GetDataSize(int Index) +{ + if(!m_pDataFile) { return 0; } + + if(Index == m_pDataFile->m_Header.m_NumRawData-1) + return m_pDataFile->m_Header.m_DataSize-m_pDataFile->m_Info.m_pDataOffsets[Index]; + return m_pDataFile->m_Info.m_pDataOffsets[Index+1]-m_pDataFile->m_Info.m_pDataOffsets[Index]; +} + +void *CDataFileReader::GetDataImpl(int Index, int Swap) +{ + if(!m_pDataFile) { return 0; } + + // load it if needed + if(!m_pDataFile->m_ppDataPtrs[Index]) + { + // fetch the data size + int DataSize = GetDataSize(Index); + int SwapSize = DataSize; + + if(m_pDataFile->m_Header.m_Version == 4) + { + // v4 has compressed data + void *pTemp = (char *)mem_alloc(DataSize, 1); + unsigned long UncompressedSize = m_pDataFile->m_Info.m_pDataSizes[Index]; + unsigned long s; + + dbg_msg("datafile", "loading data index=%d size=%d uncompressed=%d", Index, DataSize, UncompressedSize); + m_pDataFile->m_ppDataPtrs[Index] = (char *)mem_alloc(UncompressedSize, 1); + + // read the compressed data + io_seek(m_pDataFile->m_File, m_pDataFile->m_DataStartOffset+m_pDataFile->m_Info.m_pDataOffsets[Index], IOSEEK_START); + io_read(m_pDataFile->m_File, pTemp, DataSize); + + // decompress the data, TODO: check for errors + s = UncompressedSize; + uncompress((Bytef*)m_pDataFile->m_ppDataPtrs[Index], &s, (Bytef*)pTemp, DataSize); // ignore_convention + SwapSize = s; + + // clean up the temporary buffers + mem_free(pTemp); + } + else + { + // load the data + dbg_msg("datafile", "loading data index=%d size=%d", Index, DataSize); + m_pDataFile->m_ppDataPtrs[Index] = (char *)mem_alloc(DataSize, 1); + io_seek(m_pDataFile->m_File, m_pDataFile->m_DataStartOffset+m_pDataFile->m_Info.m_pDataOffsets[Index], IOSEEK_START); + io_read(m_pDataFile->m_File, m_pDataFile->m_ppDataPtrs[Index], DataSize); + } + +#if defined(CONF_ARCH_ENDIAN_BIG) + if(Swap && SwapSize) + swap_endian(m_pDataFile->m_ppDataPtrs[Index], sizeof(int), SwapSize/sizeof(int)); +#endif + } + + return m_pDataFile->m_ppDataPtrs[Index]; +} + +void *CDataFileReader::GetData(int Index) +{ + return GetDataImpl(Index, 0); +} + +void *CDataFileReader::GetDataSwapped(int Index) +{ + return GetDataImpl(Index, 1); +} + +void CDataFileReader::UnloadData(int Index) +{ + if(Index < 0) + return; + + // + mem_free(m_pDataFile->m_ppDataPtrs[Index]); + m_pDataFile->m_ppDataPtrs[Index] = 0x0; +} + +int CDataFileReader::GetItemSize(int Index) +{ + if(!m_pDataFile) { return 0; } + if(Index == m_pDataFile->m_Header.m_NumItems-1) + return m_pDataFile->m_Header.m_ItemSize-m_pDataFile->m_Info.m_pItemOffsets[Index]; + return m_pDataFile->m_Info.m_pItemOffsets[Index+1]-m_pDataFile->m_Info.m_pItemOffsets[Index]; +} + +void *CDataFileReader::GetItem(int Index, int *pType, int *pId) +{ + if(!m_pDataFile) { if(pType) *pType = 0; if(pId) *pId = 0; return 0; } + + CDatafileItem *i = (CDatafileItem *)(m_pDataFile->m_Info.m_pItemStart+m_pDataFile->m_Info.m_pItemOffsets[Index]); + if(pType) + *pType = (i->m_TypeAndId>>16)&0xffff; // remove sign extention + if(pId) + *pId = i->m_TypeAndId&0xffff; + return (void *)(i+1); +} + +void CDataFileReader::GetType(int Type, int *pStart, int *pNum) +{ + *pStart = 0; + *pNum = 0; + + if(!m_pDataFile) + return; + + for(int i = 0; i < m_pDataFile->m_Header.m_NumItemTypes; i++) + { + if(m_pDataFile->m_Info.m_pItemTypes[i].m_Type == Type) + { + *pStart = m_pDataFile->m_Info.m_pItemTypes[i].m_Start; + *pNum = m_pDataFile->m_Info.m_pItemTypes[i].m_Num; + return; + } + } +} + +void *CDataFileReader::FindItem(int Type, int Id) +{ + if(!m_pDataFile) return 0; + + int Start, Num; + GetType(Type, &Start, &Num); + for(int i = 0; i < Num; i++) + { + int ItemId; + void *pItem = GetItem(Start+i,0, &ItemId); + if(Id == ItemId) + return pItem; + } + return 0; +} + +int CDataFileReader::NumItems() +{ + if(!m_pDataFile) return 0; + return m_pDataFile->m_Header.m_NumItems; +} + +bool CDataFileReader::Close() +{ + if(!m_pDataFile) + return true; + + // free the data that is loaded + int i; + for(i = 0; i < m_pDataFile->m_Header.m_NumRawData; i++) + mem_free(m_pDataFile->m_ppDataPtrs[i]); + + io_close(m_pDataFile->m_File); + mem_free(m_pDataFile); + m_pDataFile = 0; + return true; +} + +unsigned CDataFileReader::Crc() +{ + if(!m_pDataFile) return -1; + return m_pDataFile->m_Crc; +} + +bool CDataFileWriter::Open(class IStorage *pStorage, const char *pFilename) +{ + int i; + //DATAFILE_OUT *df = (DATAFILE_OUT*)mem_alloc(sizeof(DATAFILE_OUT), 1); + m_File = pStorage->OpenFile(pFilename, IOFLAG_WRITE); + if(!m_File) + return false; + + m_NumItems = 0; + m_NumDatas = 0; + m_NumItemTypes = 0; + mem_zero(&m_aItemTypes, sizeof(m_aItemTypes)); + + for(i = 0; i < 0xffff; i++) + { + m_aItemTypes[i].m_First = -1; + m_aItemTypes[i].m_Last = -1; + } + + return true; +} + +int CDataFileWriter::AddItem(int Type, int Id, int Size, void *pData) +{ + m_aItems[m_NumItems].m_Type = Type; + m_aItems[m_NumItems].m_Id = Id; + m_aItems[m_NumItems].m_Size = Size; + + /* + dbg_msg("datafile", "added item type=%d id=%d size=%d", type, id, size); + int i; + for(i = 0; i < size/4; i++) + dbg_msg("datafile", "\t%d: %08x %d", i, ((int*)data)[i], ((int*)data)[i]); + */ + + // copy data + m_aItems[m_NumItems].m_pData = mem_alloc(Size, 1); + mem_copy(m_aItems[m_NumItems].m_pData, pData, Size); + + if(!m_aItemTypes[Type].m_Num) // count item types + m_NumItemTypes++; + + // link + m_aItems[m_NumItems].m_Prev = m_aItemTypes[Type].m_Last; + m_aItems[m_NumItems].m_Next = -1; + + if(m_aItemTypes[Type].m_Last != -1) + m_aItems[m_aItemTypes[Type].m_Last].m_Next = m_NumItems; + m_aItemTypes[Type].m_Last = m_NumItems; + + if(m_aItemTypes[Type].m_First == -1) + m_aItemTypes[Type].m_First = m_NumItems; + + m_aItemTypes[Type].m_Num++; + + m_NumItems++; + return m_NumItems-1; +} + +int CDataFileWriter::AddData(int Size, void *pData) +{ + CDataInfo *pInfo = &m_aDatas[m_NumDatas]; + unsigned long s = compressBound(Size); + void *pCompData = mem_alloc(s, 1); // temporary buffer that we use duing compression + + int Result = compress((Bytef*)pCompData, &s, (Bytef*)pData, Size); // ignore_convention + if(Result != Z_OK) + { + dbg_msg("datafile", "compression error %d", Result); + dbg_assert(0, "zlib error"); + } + + pInfo->m_UncompressedSize = Size; + pInfo->m_CompressedSize = (int)s; + pInfo->m_pCompressedData = mem_alloc(pInfo->m_CompressedSize, 1); + mem_copy(pInfo->m_pCompressedData, pCompData, pInfo->m_CompressedSize); + mem_free(pCompData); + + m_NumDatas++; + return m_NumDatas-1; +} + +int CDataFileWriter::AddDataSwapped(int Size, void *pData) +{ +#if defined(CONF_ARCH_ENDIAN_BIG) + void *pSwapped = mem_alloc(Size, 1); // temporary buffer that we use duing compression + int Index; + mem_copy(pSwapped, pData, Size); + swap_endian(&pSwapped, sizeof(int), Size/sizeof(int)); + Index = AddData(Size, Swapped); + mem_free(pSwapped); + return Index; +#else + return AddData(Size, pData); +#endif +} + + +int CDataFileWriter::Finish() +{ + int ItemSize = 0; + int TypesSize, HeaderSize, OffsetSize, FileSize, SwapSize; + int DataSize = 0; + CDatafileHeader Header; + + // we should now write this file! + if(DEBUG) + dbg_msg("datafile", "writing"); + + // calculate sizes + for(int i = 0; i < m_NumItems; i++) + { + if(DEBUG) + dbg_msg("datafile", "item=%d size=%d (%d)", i, m_aItems[i].m_Size, m_aItems[i].m_Size+sizeof(CDatafileItem)); + ItemSize += m_aItems[i].m_Size + sizeof(CDatafileItem); + } + + + for(int i = 0; i < m_NumDatas; i++) + DataSize += m_aDatas[i].m_CompressedSize; + + // calculate the complete size + TypesSize = m_NumItemTypes*sizeof(CDatafileItemType); + HeaderSize = sizeof(CDatafileHeader); + OffsetSize = m_NumItems*sizeof(int) + m_NumDatas*sizeof(int); + FileSize = HeaderSize + TypesSize + OffsetSize + ItemSize + DataSize; + SwapSize = FileSize - DataSize; + + (void)SwapSize; + + if(DEBUG) + dbg_msg("datafile", "num_m_aItemTypes=%d TypesSize=%d m_aItemsize=%d DataSize=%d", m_NumItemTypes, TypesSize, ItemSize, DataSize); + + // construct Header + { + Header.m_aId[0] = 'D'; + Header.m_aId[1] = 'A'; + Header.m_aId[2] = 'T'; + Header.m_aId[3] = 'A'; + Header.m_Version = 4; + Header.m_Size = FileSize - 16; + Header.m_Swaplen = SwapSize - 16; + Header.m_NumItemTypes = m_NumItemTypes; + Header.m_NumItems = m_NumItems; + Header.m_NumRawData = m_NumDatas; + Header.m_ItemSize = ItemSize; + Header.m_DataSize = DataSize; + + // TODO: apply swapping + // write Header + if(DEBUG) + dbg_msg("datafile", "HeaderSize=%d", sizeof(Header)); + io_write(m_File, &Header, sizeof(Header)); + } + + // write types + for(int i = 0, Count = 0; i < 0xffff; i++) + { + if(m_aItemTypes[i].m_Num) + { + // write info + CDatafileItemType Info; + Info.m_Type = i; + Info.m_Start = Count; + Info.m_Num = m_aItemTypes[i].m_Num; + if(DEBUG) + dbg_msg("datafile", "writing type=%x start=%d num=%d", Info.m_Type, Info.m_Start, Info.m_Num); + io_write(m_File, &Info, sizeof(Info)); + Count += m_aItemTypes[i].m_Num; + } + } + + // write item offsets + for(int i = 0, Offset = 0; i < 0xffff; i++) + { + if(m_aItemTypes[i].m_Num) + { + // write all m_aItems in of this type + int k = m_aItemTypes[i].m_First; + while(k != -1) + { + if(DEBUG) + dbg_msg("datafile", "writing item offset num=%d offset=%d", k, Offset); + io_write(m_File, &Offset, sizeof(Offset)); + Offset += m_aItems[k].m_Size + sizeof(CDatafileItem); + + // next + k = m_aItems[k].m_Next; + } + } + } + + // write data offsets + for(int i = 0, Offset = 0; i < m_NumDatas; i++) + { + if(DEBUG) + dbg_msg("datafile", "writing data offset num=%d offset=%d", i, Offset); + io_write(m_File, &Offset, sizeof(Offset)); + Offset += m_aDatas[i].m_CompressedSize; + } + + // write data uncompressed sizes + for(int i = 0; i < m_NumDatas; i++) + { + /* + if(DEBUG) + dbg_msg("datafile", "writing data offset num=%d offset=%d", i, offset); + */ + io_write(m_File, &m_aDatas[i].m_UncompressedSize, sizeof(int)); + } + + // write m_aItems + for(int i = 0; i < 0xffff; i++) + { + if(m_aItemTypes[i].m_Num) + { + // write all m_aItems in of this type + int k = m_aItemTypes[i].m_First; + while(k != -1) + { + CDatafileItem Item; + Item.m_TypeAndId = (i<<16)|m_aItems[k].m_Id; + Item.m_Size = m_aItems[k].m_Size; + if(DEBUG) + dbg_msg("datafile", "writing item type=%x idx=%d id=%d size=%d", i, k, m_aItems[k].m_Id, m_aItems[k].m_Size); + + io_write(m_File, &Item, sizeof(Item)); + io_write(m_File, m_aItems[k].m_pData, m_aItems[k].m_Size); + + // next + k = m_aItems[k].m_Next; + } + } + } + + // write data + for(int i = 0; i < m_NumDatas; i++) + { + if(DEBUG) + dbg_msg("datafile", "writing data id=%d size=%d", i, m_aDatas[i].m_CompressedSize); + io_write(m_File, m_aDatas[i].m_pCompressedData, m_aDatas[i].m_CompressedSize); + } + + // free data + for(int i = 0; i < m_NumItems; i++) + mem_free(m_aItems[i].m_pData); + + + io_close(m_File); + + if(DEBUG) + dbg_msg("datafile", "done"); + return 0; +} diff --git a/src/engine/shared/datafile.h b/src/engine/shared/datafile.h new file mode 100644 index 00000000..eddce611 --- /dev/null +++ b/src/engine/shared/datafile.h @@ -0,0 +1,77 @@ +#ifndef ENGINE_SHARED_DATAFILE_H +#define ENGINE_SHARED_DATAFILE_H + +// raw datafile access +class CDataFileReader +{ + class CDatafile *m_pDataFile; + void *GetDataImpl(int Index, int Swap); +public: + CDataFileReader() : m_pDataFile(0) {} + ~CDataFileReader() { Close(); } + + bool IsOpen() const { return m_pDataFile != 0; } + + bool Open(class IStorage *pStorage, const char *pFilename); + bool Close(); + + void *GetData(int Index); + void *GetDataSwapped(int Index); // makes sure that the data is 32bit LE ints when saved + int GetDataSize(int Index); + void UnloadData(int Index); + void *GetItem(int Index, int *pType, int *pId); + int GetItemSize(int Index); + void GetType(int Type, int *pStart, int *pNum); + void *FindItem(int Type, int Id); + int NumItems(); + int NumData(); + void Unload(); + + unsigned Crc(); +}; + +// write access +class CDataFileWriter +{ + struct CDataInfo + { + int m_UncompressedSize; + int m_CompressedSize; + void *m_pCompressedData; + } ; + + struct CItemInfo + { + int m_Type; + int m_Id; + int m_Size; + int m_Next; + int m_Prev; + void *m_pData; + }; + + struct CItemTypeInfo + { + int m_Num; + int m_First; + int m_Last; + }; + + IOHANDLE m_File; + int m_NumItems; + int m_NumDatas; + int m_NumItemTypes; + CItemTypeInfo m_aItemTypes[0xffff]; + CItemInfo m_aItems[1024]; + CDataInfo m_aDatas[1024]; + +public: + bool Open(class IStorage *pStorage, const char *Filename); + int AddData(int Size, void *pData); + int AddDataSwapped(int Size, void *pData); + int AddItem(int Type, int Id, int Size, void *pData); + int Finish(); +}; + + +#endif diff --git a/src/engine/shared/demorec.cpp b/src/engine/shared/demorec.cpp new file mode 100644 index 00000000..48b06e9a --- /dev/null +++ b/src/engine/shared/demorec.cpp @@ -0,0 +1,623 @@ +#include <base/system.h> +#include <engine/shared/protocol.h> +#include <engine/storage.h> +#include "demorec.h" +#include "memheap.h" +#include "snapshot.h" +#include "compression.h" +#include "network.h" +#include "engine.h" + +static const unsigned char gs_aHeaderMarker[8] = {'T', 'W', 'D', 'E', 'M', 'O', 0, 1}; + +CDemoRecorder::CDemoRecorder(class CSnapshotDelta *pSnapshotDelta) +{ + m_File = 0; + m_LastTickMarker = -1; + m_pSnapshotDelta = pSnapshotDelta; +} + +//static IOHANDLE m_File = 0; + +// Record +int CDemoRecorder::Start(class IStorage *pStorage, const char *pFilename, const char *pNetVersion, const char *pMap, int Crc, const char *pType) +{ + CDemoHeader Header; + if(m_File) + return -1; + + m_File = pStorage->OpenFile(pFilename, IOFLAG_WRITE); + + if(!m_File) + { + dbg_msg("demorec/record", "Unable to open '%s' for recording", pFilename); + return -1; + } + + // write header + mem_zero(&Header, sizeof(Header)); + mem_copy(Header.m_aMarker, gs_aHeaderMarker, sizeof(Header.m_aMarker)); + str_copy(Header.m_aNetversion, pNetVersion, sizeof(Header.m_aNetversion)); + str_copy(Header.m_aMap, pMap, sizeof(Header.m_aMap)); + str_copy(Header.m_aType, pType, sizeof(Header.m_aType)); + Header.m_aCrc[0] = (Crc>>24)&0xff; + Header.m_aCrc[1] = (Crc>>16)&0xff; + Header.m_aCrc[2] = (Crc>>8)&0xff; + Header.m_aCrc[3] = (Crc)&0xff; + io_write(m_File, &Header, sizeof(Header)); + + m_LastKeyFrame = -1; + m_LastTickMarker = -1; + + dbg_msg("demorec/record", "Recording to '%s'", pFilename); + return 0; +} + +/* + Tickmarker + 7 = Always set + 6 = Keyframe flag + 0-5 = Delta tick + + Normal + 7 = Not set + 5-6 = Type + 0-4 = Size +*/ + +enum +{ + CHUNKTYPEFLAG_TICKMARKER = 0x80, + CHUNKTICKFLAG_KEYFRAME = 0x40, // only when tickmarker is set + + CHUNKMASK_TICK = 0x3f, + CHUNKMASK_TYPE = 0x60, + CHUNKMASK_SIZE = 0x1f, + + CHUNKTYPE_SNAPSHOT = 1, + CHUNKTYPE_MESSAGE = 2, + CHUNKTYPE_DELTA = 3, + + CHUNKFLAG_BIGSIZE = 0x10 +}; + +void CDemoRecorder::WriteTickMarker(int Tick, int Keyframe) +{ + if(m_LastTickMarker == -1 || Tick-m_LastTickMarker > 63 || Keyframe) + { + unsigned char aChunk[5]; + aChunk[0] = CHUNKTYPEFLAG_TICKMARKER; + aChunk[1] = (Tick>>24)&0xff; + aChunk[2] = (Tick>>16)&0xff; + aChunk[3] = (Tick>>8)&0xff; + aChunk[4] = (Tick)&0xff; + + if(Keyframe) + aChunk[0] |= CHUNKTICKFLAG_KEYFRAME; + + io_write(m_File, aChunk, sizeof(aChunk)); + } + else + { + unsigned char aChunk[1]; + aChunk[0] = CHUNKTYPEFLAG_TICKMARKER | (Tick-m_LastTickMarker); + io_write(m_File, aChunk, sizeof(aChunk)); + } + + m_LastTickMarker = Tick; +} + +void CDemoRecorder::Write(int Type, const void *pData, int Size) +{ + char aBuffer[64*1024]; + char aBuffer2[64*1024]; + unsigned char aChunk[3]; + + if(!m_File) + return; + + /* pad the data with 0 so we get an alignment of 4, + else the compression won't work and miss some bytes */ + mem_copy(aBuffer2, pData, Size); + while(Size&3) + aBuffer2[Size++] = 0; + Size = CVariableInt::Compress(aBuffer2, Size, aBuffer); // buffer2 -> buffer + Size = CNetBase::Compress(aBuffer, Size, aBuffer2, sizeof(aBuffer2)); // buffer -> buffer2 + + + aChunk[0] = ((Type&0x3)<<5); + if(Size < 30) + { + aChunk[0] |= Size; + io_write(m_File, aChunk, 1); + } + else + { + if(Size < 256) + { + aChunk[0] |= 30; + aChunk[1] = Size&0xff; + io_write(m_File, aChunk, 2); + } + else + { + aChunk[0] |= 31; + aChunk[1] = Size&0xff; + aChunk[2] = Size>>8; + io_write(m_File, aChunk, 3); + } + } + + io_write(m_File, aBuffer2, Size); +} + +void CDemoRecorder::RecordSnapshot(int Tick, const void *pData, int Size) +{ + if(m_LastKeyFrame == -1 || (Tick-m_LastKeyFrame) > SERVER_TICK_SPEED*5) + { + // write full tickmarker + WriteTickMarker(Tick, 1); + + // write snapshot + Write(CHUNKTYPE_SNAPSHOT, pData, Size); + + m_LastKeyFrame = Tick; + mem_copy(m_aLastSnapshotData, pData, Size); + } + else + { + // create delta, prepend tick + char aDeltaData[CSnapshot::MAX_SIZE+sizeof(int)]; + int DeltaSize; + + // write tickmarker + WriteTickMarker(Tick, 0); + + DeltaSize = m_pSnapshotDelta->CreateDelta((CSnapshot*)m_aLastSnapshotData, (CSnapshot*)pData, &aDeltaData); + if(DeltaSize) + { + // record delta + Write(CHUNKTYPE_DELTA, aDeltaData, DeltaSize); + mem_copy(m_aLastSnapshotData, pData, Size); + } + } +} + +void CDemoRecorder::RecordMessage(const void *pData, int Size) +{ + Write(CHUNKTYPE_MESSAGE, pData, Size); +} + +int CDemoRecorder::Stop() +{ + if(!m_File) + return -1; + + dbg_msg("demorec/record", "Stopped recording"); + io_close(m_File); + m_File = 0; + return 0; +} + + + +CDemoPlayer::CDemoPlayer(class CSnapshotDelta *pSnapshotDelta) +{ + m_File = 0; + m_pKeyFrames = 0; + + m_pSnapshotDelta = pSnapshotDelta; + m_LastSnapshotDataSize = -1; +} + +void CDemoPlayer::SetListner(IListner *pListner) +{ + m_pListner = pListner; +} + + +int CDemoPlayer::ReadChunkHeader(int *pType, int *pSize, int *pTick) +{ + unsigned char Chunk = 0; + + *pSize = 0; + *pType = 0; + + if(io_read(m_File, &Chunk, sizeof(Chunk)) != sizeof(Chunk)) + return -1; + + if(Chunk&CHUNKTYPEFLAG_TICKMARKER) + { + // decode tick marker + int Tickdelta = Chunk&(CHUNKMASK_TICK); + *pType = Chunk&(CHUNKTYPEFLAG_TICKMARKER|CHUNKTICKFLAG_KEYFRAME); + + if(Tickdelta == 0) + { + unsigned char aTickdata[4]; + if(io_read(m_File, aTickdata, sizeof(aTickdata)) != sizeof(aTickdata)) + return -1; + *pTick = (aTickdata[0]<<24) | (aTickdata[1]<<16) | (aTickdata[2]<<8) | aTickdata[3]; + } + else + { + *pTick += Tickdelta; + } + + } + else + { + // decode normal chunk + *pType = (Chunk&CHUNKMASK_TYPE)>>5; + *pSize = Chunk&CHUNKMASK_SIZE; + + if(*pSize == 30) + { + unsigned char aSizedata[1]; + if(io_read(m_File, aSizedata, sizeof(aSizedata)) != sizeof(aSizedata)) + return -1; + *pSize = aSizedata[0]; + + } + else if(*pSize == 31) + { + unsigned char aSizedata[2]; + if(io_read(m_File, aSizedata, sizeof(aSizedata)) != sizeof(aSizedata)) + return -1; + *pSize = (aSizedata[1]<<8) | aSizedata[0]; + } + } + + return 0; +} + +void CDemoPlayer::ScanFile() +{ + long StartPos; + CHeap Heap; + CKeyFrameSearch *pFirstKey = 0; + CKeyFrameSearch *pCurrentKey = 0; + //DEMOREC_CHUNK chunk; + int ChunkSize, ChunkType, ChunkTick = 0; + int i; + + StartPos = io_tell(m_File); + m_Info.m_SeekablePoints = 0; + + while(1) + { + long CurrentPos = io_tell(m_File); + + if(ReadChunkHeader(&ChunkType, &ChunkSize, &ChunkTick)) + break; + + // read the chunk + if(ChunkType&CHUNKTYPEFLAG_TICKMARKER) + { + if(ChunkType&CHUNKTICKFLAG_KEYFRAME) + { + CKeyFrameSearch *pKey; + + // save the position + pKey = (CKeyFrameSearch *)Heap.Allocate(sizeof(CKeyFrameSearch)); + pKey->m_Frame.m_Filepos = CurrentPos; + pKey->m_Frame.m_Tick = ChunkTick; + pKey->m_pNext = 0; + if(pCurrentKey) + pCurrentKey->m_pNext = pKey; + if(!pFirstKey) + pFirstKey = pKey; + pCurrentKey = pKey; + m_Info.m_SeekablePoints++; + } + + if(m_Info.m_Info.m_FirstTick == -1) + m_Info.m_Info.m_FirstTick = ChunkTick; + m_Info.m_Info.m_LastTick = ChunkTick; + } + else if(ChunkSize) + io_skip(m_File, ChunkSize); + + } + + // copy all the frames to an array instead for fast access + m_pKeyFrames = (CKeyFrame*)mem_alloc(m_Info.m_SeekablePoints*sizeof(CKeyFrame), 1); + for(pCurrentKey = pFirstKey, i = 0; pCurrentKey; pCurrentKey = pCurrentKey->m_pNext, i++) + m_pKeyFrames[i] = pCurrentKey->m_Frame; + + // destroy the temporary heap and seek back to the start + io_seek(m_File, StartPos, IOSEEK_START); +} + +void CDemoPlayer::DoTick() +{ + static char aCompresseddata[CSnapshot::MAX_SIZE]; + static char aDecompressed[CSnapshot::MAX_SIZE]; + static char aData[CSnapshot::MAX_SIZE]; + int ChunkType, ChunkTick, ChunkSize; + int DataSize; + int GotSnapshot = 0; + + // update ticks + m_Info.m_PreviousTick = m_Info.m_Info.m_CurrentTick; + m_Info.m_Info.m_CurrentTick = m_Info.m_NextTick; + ChunkTick = m_Info.m_Info.m_CurrentTick; + + while(1) + { + if(ReadChunkHeader(&ChunkType, &ChunkSize, &ChunkTick)) + { + // stop on error or eof + dbg_msg("demorec", "end of file"); + Pause(); + break; + } + + // read the chunk + if(ChunkSize) + { + if(io_read(m_File, aCompresseddata, ChunkSize) != (unsigned)ChunkSize) + { + // stop on error or eof + dbg_msg("demorec", "error reading chunk"); + Stop(); + break; + } + + DataSize = CNetBase::Decompress(aCompresseddata, ChunkSize, aDecompressed, sizeof(aDecompressed)); + if(DataSize < 0) + { + // stop on error or eof + dbg_msg("demorec", "error during network decompression"); + Stop(); + break; + } + + DataSize = CVariableInt::Decompress(aDecompressed, DataSize, aData); + + if(DataSize < 0) + { + dbg_msg("demorec", "error during intpack decompression"); + Stop(); + break; + } + } + + if(ChunkType == CHUNKTYPE_DELTA) + { + // process delta snapshot + static char aNewsnap[CSnapshot::MAX_SIZE]; + + GotSnapshot = 1; + + DataSize = m_pSnapshotDelta->UnpackDelta((CSnapshot*)m_aLastSnapshotData, (CSnapshot*)aNewsnap, aData, DataSize); + + if(DataSize >= 0) + { + if(m_pListner) + m_pListner->OnDemoPlayerSnapshot(aNewsnap, DataSize); + + m_LastSnapshotDataSize = DataSize; + mem_copy(m_aLastSnapshotData, aNewsnap, DataSize); + } + else + dbg_msg("demorec", "error duing unpacking of delta, err=%d", DataSize); + } + else if(ChunkType == CHUNKTYPE_SNAPSHOT) + { + // process full snapshot + GotSnapshot = 1; + + m_LastSnapshotDataSize = DataSize; + mem_copy(m_aLastSnapshotData, aData, DataSize); + if(m_pListner) + m_pListner->OnDemoPlayerSnapshot(aData, DataSize); + } + else + { + // if there were no snapshots in this tick, replay the last one + if(!GotSnapshot && m_pListner && m_LastSnapshotDataSize != -1) + { + GotSnapshot = 1; + m_pListner->OnDemoPlayerSnapshot(m_aLastSnapshotData, m_LastSnapshotDataSize); + } + + // check the remaining types + if(ChunkType&CHUNKTYPEFLAG_TICKMARKER) + { + m_Info.m_NextTick = ChunkTick; + break; + } + else if(ChunkType == CHUNKTYPE_MESSAGE) + { + if(m_pListner) + m_pListner->OnDemoPlayerMessage(aData, DataSize); + } + } + } +} + +void CDemoPlayer::Pause() +{ + m_Info.m_Info.m_Paused = 1; +} + +void CDemoPlayer::Unpause() +{ + if(m_Info.m_Info.m_Paused) + { + /*m_Info.start_tick = m_Info.current_tick; + m_Info.start_time = time_get();*/ + m_Info.m_Info.m_Paused = 0; + } +} + +int CDemoPlayer::Load(class IStorage *pStorage, const char *pFilename) +{ + m_File = pStorage->OpenFile(pFilename, IOFLAG_READ); + if(!m_File) + { + dbg_msg("demorec/playback", "could not open '%s'", pFilename); + return -1; + } + + // clear the playback info + mem_zero(&m_Info, sizeof(m_Info)); + m_Info.m_Info.m_FirstTick = -1; + m_Info.m_Info.m_LastTick = -1; + //m_Info.start_tick = -1; + m_Info.m_NextTick = -1; + m_Info.m_Info.m_CurrentTick = -1; + m_Info.m_PreviousTick = -1; + m_Info.m_Info.m_Speed = 1; + + m_LastSnapshotDataSize = -1; + + // read the header + io_read(m_File, &m_Info.m_Header, sizeof(m_Info.m_Header)); + if(mem_comp(m_Info.m_Header.m_aMarker, gs_aHeaderMarker, sizeof(gs_aHeaderMarker)) != 0) + { + dbg_msg("demorec/playback", "'%s' is not a demo file", pFilename); + io_close(m_File); + m_File = 0; + return -1; + } + + // scan the file for interessting points + ScanFile(); + + // ready for playback + return 0; +} + +int CDemoPlayer::NextFrame() +{ + DoTick(); + return IsPlaying(); +} + +int CDemoPlayer::Play() +{ + // fill in previous and next tick + while(m_Info.m_PreviousTick == -1 && IsPlaying()) + DoTick(); + + // set start info + /*m_Info.start_tick = m_Info.previous_tick; + m_Info.start_time = time_get();*/ + m_Info.m_CurrentTime = m_Info.m_PreviousTick*time_freq()/SERVER_TICK_SPEED; + m_Info.m_LastUpdate = time_get(); + return 0; +} + +int CDemoPlayer::SetPos(float Percent) +{ + int Keyframe; + int WantedTick; + if(!m_File) + return -1; + + // -5 because we have to have a current tick and previous tick when we do the playback + WantedTick = m_Info.m_Info.m_FirstTick + (int)((m_Info.m_Info.m_LastTick-m_Info.m_Info.m_FirstTick)*Percent) - 5; + + Keyframe = (int)(m_Info.m_SeekablePoints*Percent); + + if(Keyframe < 0 || Keyframe >= m_Info.m_SeekablePoints) + return -1; + + // get correct key frame + if(m_pKeyFrames[Keyframe].m_Tick < WantedTick) + while(Keyframe < m_Info.m_SeekablePoints-1 && m_pKeyFrames[Keyframe].m_Tick < WantedTick) + Keyframe++; + + while(Keyframe && m_pKeyFrames[Keyframe].m_Tick > WantedTick) + Keyframe--; + + // seek to the correct keyframe + io_seek(m_File, m_pKeyFrames[Keyframe].m_Filepos, IOSEEK_START); + + //m_Info.start_tick = -1; + m_Info.m_NextTick = -1; + m_Info.m_Info.m_CurrentTick = -1; + m_Info.m_PreviousTick = -1; + + // playback everything until we hit our tick + while(m_Info.m_PreviousTick < WantedTick) + DoTick(); + + Play(); + + return 0; +} + +void CDemoPlayer::SetSpeed(float Speed) +{ + m_Info.m_Info.m_Speed = Speed; +} + +int CDemoPlayer::Update() +{ + int64 Now = time_get(); + int64 Deltatime = Now-m_Info.m_LastUpdate; + m_Info.m_LastUpdate = Now; + + if(!IsPlaying()) + return 0; + + if(m_Info.m_Info.m_Paused) + { + + } + else + { + int64 Freq = time_freq(); + m_Info.m_CurrentTime += (int64)(Deltatime*(double)m_Info.m_Info.m_Speed); + + while(1) + { + int64 CurtickStart = (m_Info.m_Info.m_CurrentTick)*Freq/SERVER_TICK_SPEED; + + // break if we are ready + if(CurtickStart > m_Info.m_CurrentTime) + break; + + // do one more tick + DoTick(); + + if(m_Info.m_Info.m_Paused) + return 0; + } + + // update intratick + { + int64 CurtickStart = (m_Info.m_Info.m_CurrentTick)*Freq/SERVER_TICK_SPEED; + int64 PrevtickStart = (m_Info.m_PreviousTick)*Freq/SERVER_TICK_SPEED; + m_Info.m_IntraTick = (m_Info.m_CurrentTime - PrevtickStart) / (float)(CurtickStart-PrevtickStart); + m_Info.m_TickTime = (m_Info.m_CurrentTime - PrevtickStart) / (float)Freq; + } + + if(m_Info.m_Info.m_CurrentTick == m_Info.m_PreviousTick || + m_Info.m_Info.m_CurrentTick == m_Info.m_NextTick) + { + dbg_msg("demorec/playback", "tick error prev=%d cur=%d next=%d", + m_Info.m_PreviousTick, m_Info.m_Info.m_CurrentTick, m_Info.m_NextTick); + } + } + + return 0; +} + +int CDemoPlayer::Stop() +{ + if(!m_File) + return -1; + + dbg_msg("demorec/playback", "Stopped playback"); + io_close(m_File); + m_File = 0; + mem_free(m_pKeyFrames); + m_pKeyFrames = 0; + return 0; +} + + diff --git a/src/engine/shared/demorec.h b/src/engine/shared/demorec.h new file mode 100644 index 00000000..0936c30c --- /dev/null +++ b/src/engine/shared/demorec.h @@ -0,0 +1,117 @@ +#ifndef ENGINE_SHARED_DEMOREC_H +#define ENGINE_SHARED_DEMOREC_H + +#include <engine/demo.h> +#include "snapshot.h" + +struct CDemoHeader +{ + char m_aMarker[8]; + char m_aNetversion[64]; + char m_aMap[64]; + unsigned char m_aCrc[4]; + char m_aType[8]; +}; + +class CDemoRecorder +{ + IOHANDLE m_File; + int m_LastTickMarker; + int m_LastKeyFrame; + unsigned char m_aLastSnapshotData[CSnapshot::MAX_SIZE]; + class CSnapshotDelta *m_pSnapshotDelta; + + void WriteTickMarker(int Tick, int Keyframe); + void Write(int Type, const void *pData, int Size); +public: + CDemoRecorder(class CSnapshotDelta *pSnapshotDelta); + + int Start(class IStorage *pStorage, const char *pFilename, const char *pNetversion, const char *pMap, int MapCrc, const char *pType); + int Stop(); + + void RecordSnapshot(int Tick, const void *pData, int Size); + void RecordMessage(const void *pData, int Size); + + bool IsRecording() const { return m_File != 0; } +}; + +class CDemoPlayer : public IDemoPlayer +{ +public: + class IListner + { + public: + virtual void OnDemoPlayerSnapshot(void *pData, int Size) = 0; + virtual void OnDemoPlayerMessage(void *pData, int Size) = 0; + }; + + struct CPlaybackInfo + { + CDemoHeader m_Header; + + IDemoPlayer::CInfo m_Info; + + int64 m_LastUpdate; + int64 m_CurrentTime; + + int m_SeekablePoints; + + int m_NextTick; + int m_PreviousTick; + + float m_IntraTick; + float m_TickTime; + }; + +private: + IListner *m_pListner; + + + // Playback + struct CKeyFrame + { + long m_Filepos; + int m_Tick; + }; + + struct CKeyFrameSearch + { + CKeyFrame m_Frame; + CKeyFrameSearch *m_pNext; + }; + + IOHANDLE m_File; + CKeyFrame *m_pKeyFrames; + + CPlaybackInfo m_Info; + unsigned char m_aLastSnapshotData[CSnapshot::MAX_SIZE]; + int m_LastSnapshotDataSize; + class CSnapshotDelta *m_pSnapshotDelta; + + int ReadChunkHeader(int *pType, int *pSize, int *pTick); + void DoTick(); + void ScanFile(); + int NextFrame(); + +public: + + CDemoPlayer(class CSnapshotDelta *m_pSnapshotDelta); + + void SetListner(IListner *pListner); + + int Load(class IStorage *pStorage, const char *pFilename); + int Play(); + void Pause(); + void Unpause(); + int Stop(); + void SetSpeed(float Speed); + int SetPos(float Precent); + const CInfo *BaseInfo() const { return &m_Info.m_Info; } + + int Update(); + + const CPlaybackInfo *Info() const { return &m_Info; } + int IsPlaying() const { return m_File != 0; } +}; + +#endif diff --git a/src/engine/shared/engine.cpp b/src/engine/shared/engine.cpp new file mode 100644 index 00000000..5cd50cf0 --- /dev/null +++ b/src/engine/shared/engine.cpp @@ -0,0 +1,76 @@ +// copyright (c) 2007 magnus auvinen, see licence.txt for more info + +#include <base/system.h> + +#include <engine/shared/config.h> +#include <engine/shared/engine.h> +#include <engine/shared/network.h> +#include <engine/console.h> +#include "linereader.h" + +// compiled-in data-dir path +#define DATA_DIR "data" + +//static int engine_find_datadir(char *argv0); +/* +static void con_dbg_dumpmem(IConsole::IResult *result, void *user_data) +{ + mem_debug_dump(); +} + +static void con_dbg_lognetwork(IConsole::IResult *result, void *user_data) +{ + CNetBase::OpenLog("network_sent.dat", "network_recv.dat"); +}*/ + +/* +static char application_save_path[512] = {0}; +static char datadir[512] = {0}; + +const char *engine_savepath(const char *filename, char *buffer, int max) +{ + str_format(buffer, max, "%s/%s", application_save_path, filename); + return buffer; +}*/ + +void CEngine::Init(const char *pAppname) +{ + dbg_logger_stdout(); + dbg_logger_debugger(); + + // + dbg_msg("engine", "running on %s-%s-%s", CONF_FAMILY_STRING, CONF_PLATFORM_STRING, CONF_ARCH_STRING); +#ifdef CONF_ARCH_ENDIAN_LITTLE + dbg_msg("engine", "arch is little endian"); +#elif defined(CONF_ARCH_ENDIAN_BIG) + dbg_msg("engine", "arch is big endian"); +#else + dbg_msg("engine", "unknown endian"); +#endif + + // init the network + net_init(); + CNetBase::Init(); + + m_HostLookupPool.Init(1); + + //MACRO_REGISTER_COMMAND("dbg_dumpmem", "", CFGFLAG_SERVER|CFGFLAG_CLIENT, con_dbg_dumpmem, 0x0, "Dump the memory"); + //MACRO_REGISTER_COMMAND("dbg_lognetwork", "", CFGFLAG_SERVER|CFGFLAG_CLIENT, con_dbg_lognetwork, 0x0, "Log the network"); + + // reset the config + //config_reset(); +} + + +static int HostLookupThread(void *pUser) +{ + CHostLookup *pLookup = (CHostLookup *)pUser; + net_host_lookup(pLookup->m_aHostname, &pLookup->m_Addr, NETTYPE_IPV4); + return 0; +} + +void CEngine::HostLookup(CHostLookup *pLookup, const char *pHostname) +{ + str_copy(pLookup->m_aHostname, pHostname, sizeof(pLookup->m_aHostname)); + m_HostLookupPool.Add(&pLookup->m_Job, HostLookupThread, pLookup); +} diff --git a/src/engine/shared/engine.h b/src/engine/shared/engine.h new file mode 100644 index 00000000..ad266ae4 --- /dev/null +++ b/src/engine/shared/engine.h @@ -0,0 +1,23 @@ +#ifndef ENGINE_SHARED_E_ENGINE_H +#define ENGINE_SHARED_E_ENGINE_H + +#include "jobs.h" + +class CHostLookup +{ +public: + CJob m_Job; + char m_aHostname[128]; + NETADDR m_Addr; +}; + +class CEngine +{ + class CJobPool m_HostLookupPool; + +public: + void Init(const char *pAppname); + void HostLookup(CHostLookup *pLookup, const char *pHostname); +}; + +#endif diff --git a/src/engine/shared/huffman.cpp b/src/engine/shared/huffman.cpp new file mode 100644 index 00000000..8b0c1cd0 --- /dev/null +++ b/src/engine/shared/huffman.cpp @@ -0,0 +1,276 @@ +#include <base/system.h> +#include "huffman.h" + +struct CHuffmanConstructNode +{ + unsigned short m_NodeId; + int m_Frequency; +}; + +void CHuffman::Setbits_r(CNode *pNode, int Bits, int Depth) +{ + if(pNode->m_aLeafs[1] != 0xffff) + Setbits_r(&m_aNodes[pNode->m_aLeafs[1]], Bits|(1<<Depth), Depth+1); + if(pNode->m_aLeafs[0] != 0xffff) + Setbits_r(&m_aNodes[pNode->m_aLeafs[0]], Bits, Depth+1); + + if(pNode->m_NumBits) + { + pNode->m_Bits = Bits; + pNode->m_NumBits = Depth; + } +} + +// TODO: this should be something faster, but it's enough for now +static void BubbleSort(CHuffmanConstructNode **ppList, int Size) +{ + int Changed = 1; + CHuffmanConstructNode *pTemp; + + while(Changed) + { + Changed = 0; + for(int i = 0; i < Size-1; i++) + { + if(ppList[i]->m_Frequency < ppList[i+1]->m_Frequency) + { + pTemp = ppList[i]; + ppList[i] = ppList[i+1]; + ppList[i+1] = pTemp; + Changed = 1; + } + } + } +} + +void CHuffman::ConstructTree(const unsigned *pFrequencies) +{ + CHuffmanConstructNode aNodesLeftStorage[HUFFMAN_MAX_SYMBOLS]; + CHuffmanConstructNode *apNodesLeft[HUFFMAN_MAX_SYMBOLS]; + int NumNodesLeft = HUFFMAN_MAX_SYMBOLS; + + // add the symbols + for(int i = 0; i < HUFFMAN_MAX_SYMBOLS; i++) + { + m_aNodes[i].m_NumBits = -1; + m_aNodes[i].m_Symbol = i; + m_aNodes[i].m_aLeafs[0] = -1; + m_aNodes[i].m_aLeafs[1] = -1; + + if(i == HUFFMAN_EOF_SYMBOL) + aNodesLeftStorage[i].m_Frequency = 1; + else + aNodesLeftStorage[i].m_Frequency = pFrequencies[i]; + aNodesLeftStorage[i].m_NodeId = i; + apNodesLeft[i] = &aNodesLeftStorage[i]; + + } + + m_NumNodes = HUFFMAN_MAX_SYMBOLS; + + // construct the table + while(NumNodesLeft > 1) + { + // we can't rely on stdlib's qsort for this, it can generate different results on different implementations + BubbleSort(apNodesLeft, NumNodesLeft); + + m_aNodes[m_NumNodes].m_NumBits = 0; + m_aNodes[m_NumNodes].m_aLeafs[0] = apNodesLeft[NumNodesLeft-1]->m_NodeId; + m_aNodes[m_NumNodes].m_aLeafs[1] = apNodesLeft[NumNodesLeft-2]->m_NodeId; + apNodesLeft[NumNodesLeft-2]->m_NodeId = m_NumNodes; + apNodesLeft[NumNodesLeft-2]->m_Frequency = apNodesLeft[NumNodesLeft-1]->m_Frequency + apNodesLeft[NumNodesLeft-2]->m_Frequency; + + m_NumNodes++; + NumNodesLeft--; + } + + // set start node + m_pStartNode = &m_aNodes[m_NumNodes-1]; + + // build symbol bits + Setbits_r(m_pStartNode, 0, 0); +} + +void CHuffman::Init(const unsigned *pFrequencies) +{ + int i; + + // make sure to cleanout every thing + mem_zero(this, sizeof(*this)); + + // construct the tree + ConstructTree(pFrequencies); + + // build decode LUT + for(i = 0; i < HUFFMAN_LUTSIZE; i++) + { + unsigned Bits = i; + int k; + CNode *pNode = m_pStartNode; + for(k = 0; k < HUFFMAN_LUTBITS; k++) + { + pNode = &m_aNodes[pNode->m_aLeafs[Bits&1]]; + Bits >>= 1; + + if(!pNode) + break; + + if(pNode->m_NumBits) + { + m_apDecodeLut[i] = pNode; + break; + } + } + + if(k == HUFFMAN_LUTBITS) + m_apDecodeLut[i] = pNode; + } + +} + +//*************************************************************** +int CHuffman::Compress(const void *pInput, int InputSize, void *pOutput, int OutputSize) +{ + // this macro loads a symbol for a byte into bits and bitcount +#define HUFFMAN_MACRO_LOADSYMBOL(Sym) \ + Bits |= m_aNodes[Sym].m_Bits << Bitcount; \ + Bitcount += m_aNodes[Sym].m_NumBits; + + // this macro writes the symbol stored in bits and bitcount to the dst pointer +#define HUFFMAN_MACRO_WRITE() \ + while(Bitcount >= 8) \ + { \ + *pDst++ = (unsigned char)(Bits&0xff); \ + if(pDst == pDstEnd) \ + return -1; \ + Bits >>= 8; \ + Bitcount -= 8; \ + } + + // setup buffer pointers + const unsigned char *pSrc = (const unsigned char *)pInput; + const unsigned char *pSrcEnd = pSrc + InputSize; + unsigned char *pDst = (unsigned char *)pOutput; + unsigned char *pDstEnd = pDst + OutputSize; + + // symbol variables + unsigned Bits = 0; + unsigned Bitcount = 0; + + // make sure that we have data that we want to compress + if(InputSize) + { + // {A} load the first symbol + int Symbol = *pSrc++; + + while(pSrc != pSrcEnd) + { + // {B} load the symbol + HUFFMAN_MACRO_LOADSYMBOL(Symbol) + + // {C} fetch next symbol, this is done here because it will reduce dependency in the code + Symbol = *pSrc++; + + // {B} write the symbol loaded at + HUFFMAN_MACRO_WRITE() + } + + // write the last symbol loaded from {C} or {A} in the case of only 1 byte input buffer + HUFFMAN_MACRO_LOADSYMBOL(Symbol) + HUFFMAN_MACRO_WRITE() + } + + // write EOF symbol + HUFFMAN_MACRO_LOADSYMBOL(HUFFMAN_EOF_SYMBOL) + HUFFMAN_MACRO_WRITE() + + // write out the last bits + *pDst++ = Bits; + + // return the size of the output + return (int)(pDst - (const unsigned char *)pOutput); + + // remove macros +#undef HUFFMAN_MACRO_LOADSYMBOL +#undef HUFFMAN_MACRO_WRITE +} + +//*************************************************************** +int CHuffman::Decompress(const void *pInput, int InputSize, void *pOutput, int OutputSize) +{ + // setup buffer pointers + unsigned char *pDst = (unsigned char *)pOutput; + unsigned char *pSrc = (unsigned char *)pInput; + unsigned char *pDstEnd = pDst + OutputSize; + unsigned char *pSrcEnd = pSrc + InputSize; + + unsigned Bits = 0; + unsigned Bitcount = 0; + + CNode *pEof = &m_aNodes[HUFFMAN_EOF_SYMBOL]; + CNode *pNode = 0; + + while(1) + { + // {A} try to load a node now, this will reduce dependency at location {D} + pNode = 0; + if(Bitcount >= HUFFMAN_LUTBITS) + pNode = m_apDecodeLut[Bits&HUFFMAN_LUTMASK]; + + // {B} fill with new bits + while(Bitcount < 24 && pSrc != pSrcEnd) + { + Bits |= (*pSrc++) << Bitcount; + Bitcount += 8; + } + + // {C} load symbol now if we didn't that earlier at location {A} + if(!pNode) + pNode = m_apDecodeLut[Bits&HUFFMAN_LUTMASK]; + + // {D} check if we hit a symbol already + if(pNode->m_NumBits) + { + // remove the bits for that symbol + Bits >>= pNode->m_NumBits; + Bitcount -= pNode->m_NumBits; + } + else + { + // remove the bits that the lut checked up for us + Bits >>= HUFFMAN_LUTBITS; + Bitcount -= HUFFMAN_LUTBITS; + + // walk the tree bit by bit + while(1) + { + // traverse tree + pNode = &m_aNodes[pNode->m_aLeafs[Bits&1]]; + + // remove bit + Bitcount--; + Bits >>= 1; + + // check if we hit a symbol + if(pNode->m_NumBits) + break; + + // no more bits, decoding error + if(Bitcount == 0) + return -1; + } + } + + // check for eof + if(pNode == pEof) + break; + + // output character + if(pDst == pDstEnd) + return -1; + *pDst++ = pNode->m_Symbol; + } + + // return the size of the decompressed buffer + return (int)(pDst - (const unsigned char *)pOutput); +} diff --git a/src/engine/shared/huffman.h b/src/engine/shared/huffman.h new file mode 100644 index 00000000..5aa56c8f --- /dev/null +++ b/src/engine/shared/huffman.h @@ -0,0 +1,89 @@ +#ifndef ENGINE_SHARED_HUFFMAN_H +#define ENGINE_SHARED_HUFFMAN_H + + + +class CHuffman +{ + enum + { + HUFFMAN_EOF_SYMBOL = 256, + + HUFFMAN_MAX_SYMBOLS=HUFFMAN_EOF_SYMBOL+1, + HUFFMAN_MAX_NODES=HUFFMAN_MAX_SYMBOLS*2-1, + + HUFFMAN_LUTBITS = 10, + HUFFMAN_LUTSIZE = (1<<HUFFMAN_LUTBITS), + HUFFMAN_LUTMASK = (HUFFMAN_LUTSIZE-1) + }; + + struct CNode + { + // symbol + unsigned m_Bits; + unsigned m_NumBits; + + // don't use pointers for this. shorts are smaller so we can fit more data into the cache + unsigned short m_aLeafs[2]; + + // what the symbol represents + unsigned char m_Symbol; + }; + + CNode m_aNodes[HUFFMAN_MAX_NODES]; + CNode *m_apDecodeLut[HUFFMAN_LUTSIZE]; + CNode *m_pStartNode; + int m_NumNodes; + + void Setbits_r(CNode *pNode, int Bits, int Depth); + void ConstructTree(const unsigned *pFrequencies); + +public: + /* + Function: huffman_init + Inits the compressor/decompressor. + + Parameters: + huff - Pointer to the state to init + frequencies - A pointer to an array of 256 entries of the frequencies of the bytes + + Remarks: + - Does no allocation what so ever. + - You don't have to call any cleanup functions when you are done with it + */ + void Init(const unsigned *pFrequencies); + + /* + Function: huffman_compress + Compresses a buffer and outputs a compressed buffer. + + Parameters: + huff - Pointer to the huffman state + input - Buffer to compress + input_size - Size of the buffer to compress + output - Buffer to put the compressed data into + output_size - Size of the output buffer + + Returns: + Returns the size of the compressed data. Negative value on failure. + */ + int Compress(const void *pInput, int InputSize, void *pOutput, int OutputSize); + + /* + Function: huffman_decompress + Decompresses a buffer + + Parameters: + huff - Pointer to the huffman state + input - Buffer to decompress + input_size - Size of the buffer to decompress + output - Buffer to put the uncompressed data into + output_size - Size of the output buffer + + Returns: + Returns the size of the uncompressed data. Negative value on failure. + */ + int Decompress(const void *pInput, int InputSize, void *pOutput, int OutputSize); + +}; +#endif // __HUFFMAN_HEADER__ diff --git a/src/engine/shared/jobs.cpp b/src/engine/shared/jobs.cpp new file mode 100644 index 00000000..83d7290b --- /dev/null +++ b/src/engine/shared/jobs.cpp @@ -0,0 +1,74 @@ +// copyright (c) 2007 magnus auvinen, see licence.txt for more info +#include <base/system.h> +#include "jobs.h" + +CJobPool::CJobPool() +{ + // empty the pool + m_Lock = lock_create(); + m_pFirstJob = 0; + m_pLastJob = 0; +} + +void CJobPool::WorkerThread(void *pUser) +{ + CJobPool *pPool = (CJobPool *)pUser; + + while(1) + { + CJob *pJob = 0; + + // fetch job from queue + lock_wait(pPool->m_Lock); + if(pPool->m_pFirstJob) + { + pJob = pPool->m_pFirstJob; + pPool->m_pFirstJob = pPool->m_pFirstJob->m_pNext; + if(pPool->m_pFirstJob) + pPool->m_pFirstJob->m_pPrev = 0; + else + pPool->m_pLastJob = 0; + } + lock_release(pPool->m_Lock); + + // do the job if we have one + if(pJob) + { + pJob->m_Status = CJob::STATE_RUNNING; + pJob->m_Result = pJob->m_pfnFunc(pJob->m_pFuncData); + pJob->m_Status = CJob::STATE_DONE; + } + else + thread_sleep(10); + } + +} + +int CJobPool::Init(int NumThreads) +{ + // start threads + for(int i = 0; i < NumThreads; i++) + thread_create(WorkerThread, this); + return 0; +} + +int CJobPool::Add(CJob *pJob, JOBFUNC pfnFunc, void *pData) +{ + mem_zero(pJob, sizeof(CJob)); + pJob->m_pfnFunc = pfnFunc; + pJob->m_pFuncData = pData; + + lock_wait(m_Lock); + + // add job to queue + pJob->m_pPrev = m_pLastJob; + if(m_pLastJob) + m_pLastJob->m_pNext = pJob; + m_pLastJob = pJob; + if(!m_pFirstJob) + m_pFirstJob = pJob; + + lock_release(m_Lock); + return 0; +} + diff --git a/src/engine/shared/jobs.h b/src/engine/shared/jobs.h new file mode 100644 index 00000000..d04815b0 --- /dev/null +++ b/src/engine/shared/jobs.h @@ -0,0 +1,51 @@ +#ifndef ENGINE_SHARED_JOBS_H +#define ENGINE_SHARED_JOBS_H +typedef int (*JOBFUNC)(void *pData); + +class CJobPool; + +class CJob +{ + friend class CJobPool; + + CJobPool *m_pPool; + CJob *m_pPrev; + CJob *m_pNext; + + volatile int m_Status; + volatile int m_Result; + + JOBFUNC m_pfnFunc; + void *m_pFuncData; +public: + CJob() + { + m_Status = STATE_DONE; + m_pFuncData = 0; + } + + enum + { + STATE_PENDING=0, + STATE_RUNNING, + STATE_DONE + }; + + int Status() const { return m_Status; } +}; + +class CJobPool +{ + LOCK m_Lock; + CJob *m_pFirstJob; + CJob *m_pLastJob; + + static void WorkerThread(void *pUser); + +public: + CJobPool(); + + int Init(int NumThreads); + int Add(CJob *pJob, JOBFUNC pfnFunc, void *pData); +}; +#endif diff --git a/src/engine/shared/kernel.cpp b/src/engine/shared/kernel.cpp new file mode 100644 index 00000000..9f6850ba --- /dev/null +++ b/src/engine/shared/kernel.cpp @@ -0,0 +1,93 @@ +#include <base/system.h> +#include <engine/kernel.h> + +class CKernel : public IKernel +{ + enum + { + MAX_INTERFACES=32, + }; + + class CInterfaceInfo + { + public: + CInterfaceInfo() + { + m_aName[0] = 0; + m_pInterface = 0x0; + } + + char m_aName[64]; + IInterface *m_pInterface; + }; + + CInterfaceInfo m_aInterfaces[MAX_INTERFACES]; + int m_NumInterfaces; + + CInterfaceInfo *FindInterfaceInfo(const char *pName) + { + for(int i = 0; i < m_NumInterfaces; i++) + { + if(str_comp(pName, m_aInterfaces[i].m_aName) == 0) + return &m_aInterfaces[i]; + } + return 0x0; + } + +public: + + CKernel() + { + m_NumInterfaces = 0; + } + + + virtual bool RegisterInterfaceImpl(const char *pName, IInterface *pInterface) + { + // TODO: More error checks here + if(m_NumInterfaces == MAX_INTERFACES) + { + dbg_msg("kernel", "ERROR: couldn't register interface '%s'. maximum of interfaces reached", pName); + return false; + } + + if(FindInterfaceInfo(pName) != 0) + { + dbg_msg("kernel", "ERROR: couldn't register interface '%s'. interface already exists"); + return false; + } + + pInterface->m_pKernel = this; + m_aInterfaces[m_NumInterfaces].m_pInterface = pInterface; + str_copy(m_aInterfaces[m_NumInterfaces].m_aName, pName, sizeof(m_aInterfaces[m_NumInterfaces].m_aName)); + m_NumInterfaces++; + + return true; + } + + virtual bool ReregisterInterfaceImpl(const char *pName, IInterface *pInterface) + { + if(FindInterfaceInfo(pName) == 0) + { + dbg_msg("kernel", "ERROR: couldn't reregister interface '%s'. interface doesn't exist"); + return false; + } + + pInterface->m_pKernel = this; + + return true; + } + + virtual IInterface *RequestInterfaceImpl(const char *pName) + { + CInterfaceInfo *pInfo = FindInterfaceInfo(pName); + if(!pInfo) + { + dbg_msg("kernel", "failed to find interface with the name '%s'", pName); + return 0; + } + return pInfo->m_pInterface; + } +}; + +IKernel *IKernel::Create() { return new CKernel; } diff --git a/src/engine/shared/linereader.cpp b/src/engine/shared/linereader.cpp new file mode 100644 index 00000000..b3de233b --- /dev/null +++ b/src/engine/shared/linereader.cpp @@ -0,0 +1,62 @@ +#include "linereader.h" + +void CLineReader::Init(IOHANDLE io) +{ + m_BufferMaxSize = 4*1024; + m_BufferSize = 0; + m_BufferPos = 0; + m_IO = io; +} + +char *CLineReader::Get() +{ + unsigned LineStart = m_BufferPos; + + while(1) + { + if(m_BufferPos >= m_BufferSize) + { + // fetch more + + // move the remaining part to the front + unsigned Read; + unsigned Left = m_BufferSize - LineStart; + + if(LineStart > m_BufferSize) + Left = 0; + if(Left) + mem_move(m_aBuffer, &m_aBuffer[LineStart], Left); + m_BufferPos = Left; + + // fill the buffer + Read = io_read(m_IO, &m_aBuffer[m_BufferPos], m_BufferMaxSize-m_BufferPos); + m_BufferSize = Left + Read; + LineStart = 0; + + if(!Read) + { + if(Left) + { + m_aBuffer[Left] = 0; // return the last line + m_BufferPos = Left; + m_BufferSize = Left; + return m_aBuffer; + } + else + return 0x0; // we are done! + } + } + else + { + if(m_aBuffer[m_BufferPos] == '\n' || m_aBuffer[m_BufferPos] == '\r') + { + // line found + m_aBuffer[m_BufferPos] = 0; + m_BufferPos++; + return &m_aBuffer[LineStart]; + } + else + m_BufferPos++; + } + } +} diff --git a/src/engine/shared/linereader.h b/src/engine/shared/linereader.h new file mode 100644 index 00000000..f28d42f6 --- /dev/null +++ b/src/engine/shared/linereader.h @@ -0,0 +1,17 @@ +#ifndef ENGINE_SHARED_LINEREADER_H +#define ENGINE_SHARED_LINEREADER_H +#include <base/system.h> + +// buffered stream for reading lines, should perhaps be something smaller +class CLineReader +{ + char m_aBuffer[4*1024]; + unsigned m_BufferPos; + unsigned m_BufferSize; + unsigned m_BufferMaxSize; + IOHANDLE m_IO; +public: + void Init(IOHANDLE IoHandle); + char *Get(); +}; +#endif diff --git a/src/engine/shared/map.cpp b/src/engine/shared/map.cpp new file mode 100644 index 00000000..505d18e9 --- /dev/null +++ b/src/engine/shared/map.cpp @@ -0,0 +1,45 @@ +// copyright (c) 2007 magnus auvinen, see licence.txt for more info +#include <base/system.h> +#include <engine/map.h> +#include <engine/storage.h> +#include "datafile.h" + +class CMap : public IEngineMap +{ + CDataFileReader m_DataFile; +public: + CMap() {} + + virtual void *GetData(int Index) { return m_DataFile.GetData(Index); } + virtual void *GetDataSwapped(int Index) { return m_DataFile.GetDataSwapped(Index); } + virtual void UnloadData(int Index) { m_DataFile.UnloadData(Index); } + virtual void *GetItem(int Index, int *pType, int *pId) { return m_DataFile.GetItem(Index, pType, pId); } + virtual void GetType(int Type, int *pStart, int *pNum) { m_DataFile.GetType(Type, pStart, pNum); } + virtual void *FindItem(int Type, int Id) { return m_DataFile.FindItem(Type, Id); } + virtual int NumItems() { return m_DataFile.NumItems(); } + + virtual void Unload() + { + m_DataFile.Close(); + } + + virtual bool Load(const char *pMapName) + { + IStorage *pStorage = Kernel()->RequestInterface<IStorage>(); + if(!pStorage) + return false; + return m_DataFile.Open(pStorage, pMapName); + } + + virtual bool IsLoaded() + { + return m_DataFile.IsOpen(); + } + + virtual unsigned Crc() + { + return m_DataFile.Crc(); + } +}; + +extern IEngineMap *CreateEngineMap() { return new CMap; } diff --git a/src/engine/shared/masterserver.cpp b/src/engine/shared/masterserver.cpp new file mode 100644 index 00000000..beade5bf --- /dev/null +++ b/src/engine/shared/masterserver.cpp @@ -0,0 +1,187 @@ +// copyright (c) 2007 magnus auvinen, see licence.txt for more info +#include <stdio.h> + +#include <base/system.h> +#include <engine/masterserver.h> +#include <engine/storage.h> +#include "engine.h" +#include "linereader.h" + +class CMasterServer : public IEngineMasterServer +{ +public: + // master server functions + struct CMasterInfo + { + char m_aHostname[128]; + NETADDR m_Addr; + + CHostLookup m_Lookup; + } ; + + CMasterInfo m_aMasterServers[MAX_MASTERSERVERS]; + int m_NeedsUpdate; + CEngine *m_pEngine; + + CMasterServer() + { + SetDefault(); + m_NeedsUpdate = -1; + m_pEngine = 0; + } + + virtual int RefreshAddresses() + { + int i; + + if(m_NeedsUpdate != -1) + return 0; + + dbg_msg("engine/mastersrv", "refreshing master server addresses"); + + // add lookup jobs + for(i = 0; i < MAX_MASTERSERVERS; i++) + m_pEngine->HostLookup(&m_aMasterServers[i].m_Lookup, m_aMasterServers[i].m_aHostname); + + m_NeedsUpdate = 1; + return 0; + } + + virtual void Update() + { + // check if we need to update + if(m_NeedsUpdate != 1) + return; + m_NeedsUpdate = 0; + + for(int i = 0; i < MAX_MASTERSERVERS; i++) + { + if(m_aMasterServers[i].m_Lookup.m_Job.Status() != CJob::STATE_DONE) + m_NeedsUpdate = 1; + else + { + m_aMasterServers[i].m_Addr = m_aMasterServers[i].m_Lookup.m_Addr; + m_aMasterServers[i].m_Addr.port = 8300; + } + } + + if(!m_NeedsUpdate) + { + dbg_msg("engine/mastersrv", "saving addresses"); + Save(); + } + } + + virtual int IsRefreshing() + { + return m_NeedsUpdate; + } + + virtual NETADDR GetAddr(int Index) + { + return m_aMasterServers[Index].m_Addr; + } + + virtual const char *GetName(int Index) + { + return m_aMasterServers[Index].m_aHostname; + } + + virtual void DumpServers() + { + for(int i = 0; i < MAX_MASTERSERVERS; i++) + { + dbg_msg("mastersrv", "#%d = %d.%d.%d.%d", i, + m_aMasterServers[i].m_Addr.ip[0], m_aMasterServers[i].m_Addr.ip[1], + m_aMasterServers[i].m_Addr.ip[2], m_aMasterServers[i].m_Addr.ip[3]); + } + } + + virtual void Init(class CEngine *pEngine) + { + m_pEngine = pEngine; + } + + virtual void SetDefault() + { + mem_zero(m_aMasterServers, sizeof(m_aMasterServers)); + for(int i = 0; i < MAX_MASTERSERVERS; i++) + str_format(m_aMasterServers[i].m_aHostname, sizeof(m_aMasterServers[i].m_aHostname), "master%d.teeworlds.com", i+1); + } + + virtual int Load() + { + CLineReader LineReader; + IOHANDLE File; + int Count = 0; + IStorage *pStorage = Kernel()->RequestInterface<IStorage>(); + if(!pStorage) + return -1; + + // try to open file + File = pStorage->OpenFile("masters.cfg", IOFLAG_READ); + if(!File) + return -1; + + LineReader.Init(File); + while(1) + { + CMasterInfo Info = {{0}}; + int aIp[4]; + const char *pLine = LineReader.Get(); + if(!pLine) + break; + + // parse line + if(sscanf(pLine, "%s %d.%d.%d.%d", Info.m_aHostname, &aIp[0], &aIp[1], &aIp[2], &aIp[3]) == 5) + { + Info.m_Addr.ip[0] = (unsigned char)aIp[0]; + Info.m_Addr.ip[1] = (unsigned char)aIp[1]; + Info.m_Addr.ip[2] = (unsigned char)aIp[2]; + Info.m_Addr.ip[3] = (unsigned char)aIp[3]; + Info.m_Addr.port = 8300; + if(Count != MAX_MASTERSERVERS) + { + m_aMasterServers[Count] = Info; + Count++; + } + //else + // dbg_msg("engine/mastersrv", "warning: skipped master server '%s' due to limit of %d", pLine, MAX_MASTERSERVERS); + } + //else + // dbg_msg("engine/mastersrv", "warning: couldn't parse master server '%s'", pLine); + } + + io_close(File); + return 0; + } + + virtual int Save() + { + IOHANDLE File; + + IStorage *pStorage = Kernel()->RequestInterface<IStorage>(); + if(!pStorage) + return -1; + + // try to open file + File = pStorage->OpenFile("masters.cfg", IOFLAG_WRITE); + if(!File) + return -1; + + for(int i = 0; i < MAX_MASTERSERVERS; i++) + { + char aBuf[1024]; + str_format(aBuf, sizeof(aBuf), "%s %d.%d.%d.%d\n", m_aMasterServers[i].m_aHostname, + m_aMasterServers[i].m_Addr.ip[0], m_aMasterServers[i].m_Addr.ip[1], + m_aMasterServers[i].m_Addr.ip[2], m_aMasterServers[i].m_Addr.ip[3]); + + io_write(File, aBuf, str_length(aBuf)); + } + + io_close(File); + return 0; + } +}; + +IEngineMasterServer *CreateEngineMasterServer() { return new CMasterServer; } diff --git a/src/engine/shared/memheap.cpp b/src/engine/shared/memheap.cpp new file mode 100644 index 00000000..6661962b --- /dev/null +++ b/src/engine/shared/memheap.cpp @@ -0,0 +1,96 @@ +// copyright (c) 2007 magnus auvinen, see licence.txt for more info +#include <base/system.h> +#include "memheap.h" + +static const int CHUNK_SIZE = 1024*64; + +// allocates a new chunk to be used +void CHeap::NewChunk() +{ + CChunk *pChunk; + char *pMem; + + // allocate memory + pMem = (char*)mem_alloc(sizeof(CChunk)+CHUNK_SIZE, 1); + if(!pMem) + return; + + // the chunk structure is located in the begining of the chunk + // init it and return the chunk + pChunk = (CChunk*)pMem; + pChunk->m_pMemory = (char*)(pChunk+1); + pChunk->m_pCurrent = pChunk->m_pMemory; + pChunk->m_pEnd = pChunk->m_pMemory + CHUNK_SIZE; + pChunk->m_pNext = (CChunk *)0x0; + + pChunk->m_pNext = m_pCurrent; + m_pCurrent = pChunk; +} + +//**************** +void *CHeap::AllocateFromChunk(unsigned int Size) +{ + char *pMem; + + // check if we need can fit the allocation + if(m_pCurrent->m_pCurrent + Size > m_pCurrent->m_pEnd) + return (void*)0x0; + + // get memory and move the pointer forward + pMem = m_pCurrent->m_pCurrent; + m_pCurrent->m_pCurrent += Size; + return pMem; +} + +// creates a heap +CHeap::CHeap() +{ + m_pCurrent = 0x0; + Reset(); +} + +CHeap::~CHeap() +{ + Clear(); +} + +void CHeap::Reset() +{ + Clear(); + NewChunk(); +} + +// destroys the heap +void CHeap::Clear() +{ + CChunk *pChunk = m_pCurrent; + CChunk *pNext; + + while(pChunk) + { + pNext = pChunk->m_pNext; + mem_free(pChunk); + pChunk = pNext; + } + + m_pCurrent = 0x0; +} + +// +void *CHeap::Allocate(unsigned Size) +{ + char *pMem; + + // try to allocate from current chunk + pMem = (char *)AllocateFromChunk(Size); + if(!pMem) + { + // allocate new chunk and add it to the heap + NewChunk(); + + // try to allocate again + pMem = (char *)AllocateFromChunk(Size); + } + + return pMem; +} diff --git a/src/engine/shared/memheap.h b/src/engine/shared/memheap.h new file mode 100644 index 00000000..706395f2 --- /dev/null +++ b/src/engine/shared/memheap.h @@ -0,0 +1,32 @@ +#ifndef ENGINE_SHARED_MEMHEAP_H +#define ENGINE_SHARED_MEMHEAP_H +class CHeap +{ + struct CChunk + { + char *m_pMemory; + char *m_pCurrent; + char *m_pEnd; + CChunk *m_pNext; + }; + + enum + { + // how large each chunk should be + CHUNK_SIZE = 1025*64, + }; + + CChunk *m_pCurrent; + + + void Clear(); + void NewChunk(); + void *AllocateFromChunk(unsigned int Size); + +public: + CHeap(); + ~CHeap(); + void Reset(); + void *Allocate(unsigned Size); +}; +#endif diff --git a/src/engine/shared/message.h b/src/engine/shared/message.h new file mode 100644 index 00000000..4e67a8e1 --- /dev/null +++ b/src/engine/shared/message.h @@ -0,0 +1,9 @@ +#ifndef ENGINE_SHARED_MESSAGE_H +#define ENGINE_SHARED_MESSAGE_H +class CMessage +{ +public: + virtual bool Pack(void *pData, unsigned MaxDataSize); + virtual bool Unpack(const void *pData, unsigned DataSize); +}; +#endif diff --git a/src/engine/e_network.cpp b/src/engine/shared/network.cpp index ac753e50..0305ffff 100644 --- a/src/engine/e_network.cpp +++ b/src/engine/shared/network.cpp @@ -1,12 +1,11 @@ -/* copyright (c) 2007 magnus auvinen, see licence.txt for more info */ +// copyright (c) 2007 magnus auvinen, see licence.txt for more info #include <base/system.h> -#include <string.h> /* strlen */ -#include "e_config.h" -#include "e_engine.h" -#include "e_network.h" -#include "e_huffman.h" +#include "config.h" +#include "engine.h" +#include "network.h" +#include "huffman.h" void CNetRecvUnpacker::Clear() { @@ -22,7 +21,7 @@ void CNetRecvUnpacker::Start(const NETADDR *pAddr, CNetConnection *pConnection, m_Valid = true; } -/* TODO: rename this function */ +// TODO: rename this function int CNetRecvUnpacker::FetchChunk(CNetChunk *pChunk) { CNetChunkHeader Header; @@ -32,21 +31,21 @@ int CNetRecvUnpacker::FetchChunk(CNetChunk *pChunk) { unsigned char *pData = m_Data.m_aChunkData; - /* check for old data to unpack */ + // check for old data to unpack if(!m_Valid || m_CurrentChunk >= m_Data.m_NumChunks) { Clear(); return 0; } - /* TODO: add checking here so we don't read too far */ + // TODO: add checking here so we don't read too far for(int i = 0; i < m_CurrentChunk; i++) { pData = Header.Unpack(pData); pData += Header.m_Size; } - /* unpack the header */ + // unpack the header pData = Header.Unpack(pData); m_CurrentChunk++; @@ -56,29 +55,29 @@ int CNetRecvUnpacker::FetchChunk(CNetChunk *pChunk) return 0; } - /* handle sequence stuff */ + // handle sequence stuff if(m_pConnection && (Header.m_Flags&NET_CHUNKFLAG_VITAL)) { if(Header.m_Sequence == (m_pConnection->m_Ack+1)%NET_MAX_SEQUENCE) { - /* in sequence */ + // in sequence m_pConnection->m_Ack = (m_pConnection->m_Ack+1)%NET_MAX_SEQUENCE; } else { - /* old packet that we already got */ + // old packet that we already got if(CNetBase::IsSeqInBackroom(Header.m_Sequence, m_pConnection->m_Ack)) continue; - /* out of sequence, request resend */ - if(config.debug) + // out of sequence, request resend + if(g_Config.m_Debug) dbg_msg("conn", "asking for resend %d %d", Header.m_Sequence, (m_pConnection->m_Ack+1)%NET_MAX_SEQUENCE); m_pConnection->SignalResend(); - continue; /* take the next chunk in the packet */ + continue; // take the next chunk in the packet } } - /* fill in the info */ + // fill in the info pChunk->m_ClientID = m_ClientID; pChunk->m_Address = m_Addr; pChunk->m_Flags = 0; @@ -88,7 +87,7 @@ int CNetRecvUnpacker::FetchChunk(CNetChunk *pChunk) } } -/* packs the data tight and sends it */ +// packs the data tight and sends it void CNetBase::SendPacketConnless(NETSOCKET Socket, NETADDR *pAddr, const void *pData, int DataSize) { unsigned char aBuffer[NET_MAX_PACKETSIZE]; @@ -108,20 +107,20 @@ void CNetBase::SendPacket(NETSOCKET Socket, NETADDR *pAddr, CNetPacketConstruct int CompressedSize = -1; int FinalSize = -1; - /* log the data */ + // log the data if(ms_DataLogSent) { - int type = 1; - io_write(ms_DataLogSent, &type, sizeof(type)); + int Type = 1; + io_write(ms_DataLogSent, &Type, sizeof(Type)); io_write(ms_DataLogSent, &pPacket->m_DataSize, sizeof(pPacket->m_DataSize)); io_write(ms_DataLogSent, &pPacket->m_aChunkData, pPacket->m_DataSize); io_flush(ms_DataLogSent); } - /* compress */ - CompressedSize = huffman_compress(&ms_HuffmanState, pPacket->m_aChunkData, pPacket->m_DataSize, &aBuffer[3], NET_MAX_PACKETSIZE-4); + // compress + CompressedSize = ms_Huffman.Compress(pPacket->m_aChunkData, pPacket->m_DataSize, &aBuffer[3], NET_MAX_PACKETSIZE-4); - /* check if the compression was enabled, successful and good enough */ + // check if the compression was enabled, successful and good enough if(CompressedSize > 0 && CompressedSize < pPacket->m_DataSize) { FinalSize = CompressedSize; @@ -129,13 +128,13 @@ void CNetBase::SendPacket(NETSOCKET Socket, NETADDR *pAddr, CNetPacketConstruct } else { - /* use uncompressed data */ + // use uncompressed data FinalSize = pPacket->m_DataSize; mem_copy(&aBuffer[3], pPacket->m_aChunkData, pPacket->m_DataSize); pPacket->m_Flags &= ~NET_PACKETFLAG_COMPRESSION; } - /* set header and send the packet if all things are good */ + // set header and send the packet if all things are good if(FinalSize >= 0) { FinalSize += NET_PACKETHEADERSIZE; @@ -144,11 +143,11 @@ void CNetBase::SendPacket(NETSOCKET Socket, NETADDR *pAddr, CNetPacketConstruct aBuffer[2] = pPacket->m_NumChunks; net_udp_send(Socket, pAddr, aBuffer, FinalSize); - /* log raw socket data */ + // log raw socket data if(ms_DataLogSent) { - int type = 0; - io_write(ms_DataLogSent, &type, sizeof(type)); + int Type = 0; + io_write(ms_DataLogSent, &Type, sizeof(Type)); io_write(ms_DataLogSent, &FinalSize, sizeof(FinalSize)); io_write(ms_DataLogSent, aBuffer, FinalSize); io_flush(ms_DataLogSent); @@ -156,27 +155,27 @@ void CNetBase::SendPacket(NETSOCKET Socket, NETADDR *pAddr, CNetPacketConstruct } } -/* TODO: rename this function */ +// TODO: rename this function int CNetBase::UnpackPacket(unsigned char *pBuffer, int Size, CNetPacketConstruct *pPacket) { - /* check the size */ + // check the size if(Size < NET_PACKETHEADERSIZE || Size > NET_MAX_PACKETSIZE) { dbg_msg("", "packet too small, %d", Size); return -1; } - /* log the data */ + // log the data if(ms_DataLogRecv) { - int type = 0; - io_write(ms_DataLogRecv, &type, sizeof(type)); + int Type = 0; + io_write(ms_DataLogRecv, &Type, sizeof(Type)); io_write(ms_DataLogRecv, &Size, sizeof(Size)); io_write(ms_DataLogRecv, pBuffer, Size); io_flush(ms_DataLogRecv); } - /* read the packet */ + // read the packet pPacket->m_Flags = pBuffer[0]>>4; pPacket->m_Ack = ((pBuffer[0]&0xf)<<8) | pBuffer[1]; pPacket->m_NumChunks = pBuffer[2]; @@ -199,30 +198,30 @@ int CNetBase::UnpackPacket(unsigned char *pBuffer, int Size, CNetPacketConstruct else { if(pPacket->m_Flags&NET_PACKETFLAG_COMPRESSION) - pPacket->m_DataSize = huffman_decompress(&ms_HuffmanState, &pBuffer[3], pPacket->m_DataSize, pPacket->m_aChunkData, sizeof(pPacket->m_aChunkData)); + pPacket->m_DataSize = ms_Huffman.Decompress(&pBuffer[3], pPacket->m_DataSize, pPacket->m_aChunkData, sizeof(pPacket->m_aChunkData)); else mem_copy(pPacket->m_aChunkData, &pBuffer[3], pPacket->m_DataSize); } - /* check for errors */ + // check for errors if(pPacket->m_DataSize < 0) { - if(config.debug) + if(g_Config.m_Debug) dbg_msg("network", "error during packet decoding"); return -1; } - /* log the data */ + // log the data if(ms_DataLogRecv) { - int type = 1; - io_write(ms_DataLogRecv, &type, sizeof(type)); + int Type = 1; + io_write(ms_DataLogRecv, &Type, sizeof(Type)); io_write(ms_DataLogRecv, &pPacket->m_DataSize, sizeof(pPacket->m_DataSize)); io_write(ms_DataLogRecv, pPacket->m_aChunkData, pPacket->m_DataSize); io_flush(ms_DataLogRecv); } - /* return success */ + // return success return 0; } @@ -237,7 +236,7 @@ void CNetBase::SendControlMsg(NETSOCKET Socket, NETADDR *pAddr, int Ack, int Con Construct.m_aChunkData[0] = ControlMsg; mem_copy(&Construct.m_aChunkData[1], pExtra, ExtraSize); - /* send the control message */ + // send the control message CNetBase::SendPacket(Socket, pAddr, &Construct); } @@ -291,11 +290,12 @@ int CNetBase::IsSeqInBackroom(int Seq, int Ack) IOHANDLE CNetBase::ms_DataLogSent = 0; IOHANDLE CNetBase::ms_DataLogRecv = 0; -HUFFMAN_STATE CNetBase::ms_HuffmanState; +CHuffman CNetBase::ms_Huffman; void CNetBase::OpenLog(const char *pSentLog, const char *pRecvLog) { + /* if(pSentLog) { ms_DataLogSent = engine_openfile(pSentLog, IOFLAG_WRITE); @@ -312,17 +312,17 @@ void CNetBase::OpenLog(const char *pSentLog, const char *pRecvLog) dbg_msg("network", "logging recv packages to '%s'", pRecvLog); else dbg_msg("network", "failed to open for logging '%s'", pRecvLog); - } + }*/ } int CNetBase::Compress(const void *pData, int DataSize, void *pOutput, int OutputSize) { - return huffman_compress(&ms_HuffmanState, pData, DataSize, pOutput, OutputSize); + return ms_Huffman.Compress(pData, DataSize, pOutput, OutputSize); } int CNetBase::Decompress(const void *pData, int DataSize, void *pOutput, int OutputSize) { - return huffman_decompress(&ms_HuffmanState, pData, DataSize, pOutput, OutputSize); + return ms_Huffman.Decompress(pData, DataSize, pOutput, OutputSize); } @@ -343,5 +343,5 @@ static const unsigned gs_aFreqTable[256+1] = { void CNetBase::Init() { - huffman_init(&ms_HuffmanState, gs_aFreqTable); + ms_Huffman.Init(gs_aFreqTable); } diff --git a/src/engine/e_network.h b/src/engine/shared/network.h index 19eca8da..11a1b70d 100644 --- a/src/engine/e_network.h +++ b/src/engine/shared/network.h @@ -1,9 +1,8 @@ -#ifndef ENGINE_NETWORK_H -#define ENGINE_NETWORK_H -/* copyright (c) 2007 magnus auvinen, see licence.txt for more info */ +#ifndef ENGINE_SHARED_NETWORK_H +#define ENGINE_SHARED_NETWORK_H -#include "e_ringbuffer.h" -#include "e_huffman.h" +#include "ringbuffer.h" +#include "huffman.h" /* @@ -80,15 +79,15 @@ enum }; -typedef int (*NETFUNC_DELCLIENT)(int cid, void *user); -typedef int (*NETFUNC_NEWCLIENT)(int cid, void *user); +typedef int (*NETFUNC_DELCLIENT)(int ClientID, void *pUser); +typedef int (*NETFUNC_NEWCLIENT)(int ClientID, void *pUser); struct CNetChunk { - /* -1 means that it's a stateless packet */ - /* 0 on the client means the server */ + // -1 means that it's a stateless packet + // 0 on the client means the server int m_ClientID; - NETADDR m_Address; /* only used when client_id == -1 */ + NETADDR m_Address; // only used when client_id == -1 int m_Flags; int m_DataSize; const void *m_pData; @@ -162,7 +161,7 @@ private: void SetError(const char *pString); void AckChunks(int Ack); - void QueueChunkEx(int Flags, int DataSize, const void *pData, int Sequence); + int QueueChunkEx(int Flags, int DataSize, const void *pData, int Sequence); void SendControl(int ControlMsg, const void *pExtra, int ExtraSize); void ResendChunk(CNetChunkResend *pResend); void Resend(); @@ -176,7 +175,7 @@ public: int Flush(); int Feed(CNetPacketConstruct *pPacket, NETADDR *pAddr); - void QueueChunk(int Flags, int DataSize, const void *pData); + int QueueChunk(int Flags, int DataSize, const void *pData); const char *ErrorString(); void SignalResend(); @@ -210,7 +209,7 @@ public: int FetchChunk(CNetChunk *pChunk); }; -/* server side */ +// server side class CNetServer { public: @@ -232,11 +231,11 @@ private: public: CBanInfo m_Info; - /* hash list */ + // hash list CBan *m_pHashNext; CBan *m_pHashPrev; - /* used or free list */ + // used or free list CBan *m_pNext; CBan *m_pPrev; }; @@ -257,13 +256,13 @@ private: CNetRecvUnpacker m_RecvUnpacker; - void BanRemoveByObject(CBan *ban); + void BanRemoveByObject(CBan *pBan); public: int SetCallbacks(NETFUNC_NEWCLIENT pfnNewClient, NETFUNC_DELCLIENT pfnDelClient, void *pUser); // - bool Open(NETADDR bindaddr, int MaxClients, int Flags); + bool Open(NETADDR BindAddr, int MaxClients, int Flags); int Close(); // @@ -277,8 +276,8 @@ public: // banning int BanAdd(NETADDR Addr, int Seconds); int BanRemove(NETADDR Addr); - int BanNum(); /* caution, slow */ - int BanGet(int Index, CBanInfo *pInfo); /* caution, slow */ + int BanNum(); // caution, slow + int BanGet(int Index, CBanInfo *pInfo); // caution, slow // status requests NETADDR ClientAddr(int ClientID) const { return m_aSlots[ClientID].m_Connection.PeerAddress(); } @@ -288,7 +287,7 @@ public: -/* client side */ +// client side class CNetClient { NETADDR m_ServerAddr; @@ -327,7 +326,7 @@ class CNetBase { static IOHANDLE ms_DataLogSent; static IOHANDLE ms_DataLogRecv; - static HUFFMAN_STATE ms_HuffmanState; + static CHuffman ms_Huffman; public: static void OpenLog(const char *pSentlog, const char *pRecvlog); static void Init(); @@ -339,7 +338,7 @@ public: static void SendPacket(NETSOCKET Socket, NETADDR *pAddr, CNetPacketConstruct *pPacket); static int UnpackPacket(unsigned char *pBuffer, int Size, CNetPacketConstruct *pPacket); - /* The backroom is ack-NET_MAX_SEQUENCE/2. Used for knowing if we acked a packet or not */ + // The backroom is ack-NET_MAX_SEQUENCE/2. Used for knowing if we acked a packet or not static int IsSeqInBackroom(int Seq, int Ack); }; diff --git a/src/engine/e_network_client.cpp b/src/engine/shared/network_client.cpp index ce243b32..f7859c0a 100644 --- a/src/engine/e_network_client.cpp +++ b/src/engine/shared/network_client.cpp @@ -1,5 +1,5 @@ #include <base/system.h> -#include "e_network.h" +#include "network.h" bool CNetClient::Open(NETADDR BindAddr, int Flags) { @@ -14,7 +14,7 @@ bool CNetClient::Open(NETADDR BindAddr, int Flags) int CNetClient::Close() { - /* TODO: implement me */ + // TODO: implement me return 0; } @@ -50,15 +50,15 @@ int CNetClient::Recv(CNetChunk *pChunk) { while(1) { - /* check for a chunk */ + // check for a chunk if(m_RecvUnpacker.FetchChunk(pChunk)) return 1; - /* TODO: empty the recvinfo */ + // TODO: empty the recvinfo NETADDR Addr; int Bytes = net_udp_recv(m_Socket, &Addr, m_RecvUnpacker.m_aBuffer, NET_MAX_PACKETSIZE); - /* no more packets for now */ + // no more packets for now if(Bytes <= 0) break; @@ -93,7 +93,7 @@ int CNetClient::Send(CNetChunk *pChunk) if(pChunk->m_Flags&NETSENDFLAG_CONNLESS) { - /* send connectionless packet */ + // send connectionless packet CNetBase::SendPacketConnless(m_Socket, &pChunk->m_Address, pChunk->m_pData, pChunk->m_DataSize); } else diff --git a/src/engine/e_network_conn.cpp b/src/engine/shared/network_conn.cpp index a54c9ce3..4ed157eb 100644 --- a/src/engine/e_network_conn.cpp +++ b/src/engine/shared/network_conn.cpp @@ -1,7 +1,6 @@ #include <base/system.h> -#include <string.h> -#include "e_config.h" -#include "e_network.h" +#include "config.h" +#include "network.h" void CNetConnection::ResetStats() { @@ -71,27 +70,27 @@ int CNetConnection::Flush() if(!NumChunks && !m_Construct.m_Flags) return 0; - /* send of the packets */ + // send of the packets m_Construct.m_Ack = m_Ack; CNetBase::SendPacket(m_Socket, &m_PeerAddr, &m_Construct); - /* update send times */ + // update send times m_LastSendTime = time_get(); - /* clear construct so we can start building a new package */ + // clear construct so we can start building a new package mem_zero(&m_Construct, sizeof(m_Construct)); return NumChunks; } -void CNetConnection::QueueChunkEx(int Flags, int DataSize, const void *pData, int Sequence) +int CNetConnection::QueueChunkEx(int Flags, int DataSize, const void *pData, int Sequence) { unsigned char *pChunkData; - /* check if we have space for it, if not, flush the connection */ + // check if we have space for it, if not, flush the connection if(m_Construct.m_DataSize + DataSize + NET_MAX_CHUNKHEADERSIZE > (int)sizeof(m_Construct.m_aChunkData)) Flush(); - /* pack all the data */ + // pack all the data CNetChunkHeader Header; Header.m_Flags = Flags; Header.m_Size = DataSize; @@ -101,15 +100,15 @@ void CNetConnection::QueueChunkEx(int Flags, int DataSize, const void *pData, in mem_copy(pChunkData, pData, DataSize); pChunkData += DataSize; - /* */ + // m_Construct.m_NumChunks++; m_Construct.m_DataSize = (int)(pChunkData-m_Construct.m_aChunkData); - /* set packet flags aswell */ + // set packet flags aswell if(Flags&NET_CHUNKFLAG_VITAL && !(Flags&NET_CHUNKFLAG_RESEND)) { - /* save packet if we need to resend */ + // save packet if we need to resend CNetChunkResend *pResend = m_Buffer.Allocate(sizeof(CNetChunkResend)+DataSize); if(pResend) { @@ -123,22 +122,25 @@ void CNetConnection::QueueChunkEx(int Flags, int DataSize, const void *pData, in } else { - /* out of buffer */ + // out of buffer Disconnect("too weak connection (out of buffer)"); + return -1; } } + + return 0; } -void CNetConnection::QueueChunk(int Flags, int DataSize, const void *pData) +int CNetConnection::QueueChunk(int Flags, int DataSize, const void *pData) { if(Flags&NET_CHUNKFLAG_VITAL) m_Sequence = (m_Sequence+1)%NET_MAX_SEQUENCE; - QueueChunkEx(Flags, DataSize, pData, m_Sequence); + return QueueChunkEx(Flags, DataSize, pData, m_Sequence); } void CNetConnection::SendControl(int ControlMsg, const void *pExtra, int ExtraSize) { - /* send the control message */ + // send the control message m_LastSendTime = time_get(); CNetBase::SendControlMsg(m_Socket, &m_PeerAddr, m_Ack, ControlMsg, pExtra, ExtraSize); } @@ -151,7 +153,7 @@ void CNetConnection::ResendChunk(CNetChunkResend *pResend) void CNetConnection::Resend() { - for(CNetChunkResend *pResend = m_Buffer.First(); pResend; m_Buffer.Next(pResend)) + for(CNetChunkResend *pResend = m_Buffer.First(); pResend; pResend = m_Buffer.Next(pResend)) ResendChunk(pResend); } @@ -160,7 +162,7 @@ int CNetConnection::Connect(NETADDR *pAddr) if(State() != NET_CONNSTATE_OFFLINE) return -1; - /* init connection */ + // init connection Reset(); m_PeerAddr = *pAddr; mem_zero(m_ErrorString, sizeof(m_ErrorString)); @@ -177,7 +179,7 @@ void CNetConnection::Disconnect(const char *pReason) if(m_RemoteClosed == 0) { if(pReason) - SendControl(NET_CTRLMSG_CLOSE, pReason, strlen(pReason)+1); + SendControl(NET_CTRLMSG_CLOSE, pReason, str_length(pReason)+1); else SendControl(NET_CTRLMSG_CLOSE, 0, 0); @@ -191,41 +193,44 @@ void CNetConnection::Disconnect(const char *pReason) int CNetConnection::Feed(CNetPacketConstruct *pPacket, NETADDR *pAddr) { - int64 now = time_get(); - m_LastRecvTime = now; + int64 Now = time_get(); + m_LastRecvTime = Now; - /* check if resend is requested */ + // check if resend is requested if(pPacket->m_Flags&NET_PACKETFLAG_RESEND) Resend(); - /* */ + // if(pPacket->m_Flags&NET_PACKETFLAG_CONTROL) { int CtrlMsg = pPacket->m_aChunkData[0]; if(CtrlMsg == NET_CTRLMSG_CLOSE) { - m_State = NET_CONNSTATE_ERROR; - m_RemoteClosed = 1; - - if(pPacket->m_DataSize) + if(net_addr_comp(&m_PeerAddr, pAddr) == 0) { - /* make sure to sanitize the error string form the other party*/ - char Str[128]; - if(pPacket->m_DataSize < 128) - str_copy(Str, (char *)pPacket->m_aChunkData, pPacket->m_DataSize); - else - str_copy(Str, (char *)pPacket->m_aChunkData, sizeof(Str)); - str_sanitize_strong(Str); + m_State = NET_CONNSTATE_ERROR; + m_RemoteClosed = 1; - /* set the error string */ - SetError(Str); + if(pPacket->m_DataSize) + { + // make sure to sanitize the error string form the other party + char Str[128]; + if(pPacket->m_DataSize < 128) + str_copy(Str, (char *)pPacket->m_aChunkData, pPacket->m_DataSize); + else + str_copy(Str, (char *)pPacket->m_aChunkData, sizeof(Str)); + str_sanitize_strong(Str); + + // set the error string + SetError(Str); + } + else + SetError("no reason given"); + + if(g_Config.m_Debug) + dbg_msg("conn", "closed reason='%s'", ErrorString()); } - else - SetError("no reason given"); - - if(config.debug) - dbg_msg("conn", "closed reason='%s'", ErrorString()); return 0; } else @@ -234,32 +239,32 @@ int CNetConnection::Feed(CNetPacketConstruct *pPacket, NETADDR *pAddr) { if(CtrlMsg == NET_CTRLMSG_CONNECT) { - /* send response and init connection */ + // send response and init connection Reset(); m_State = NET_CONNSTATE_PENDING; m_PeerAddr = *pAddr; - m_LastSendTime = now; - m_LastRecvTime = now; - m_LastUpdateTime = now; + m_LastSendTime = Now; + m_LastRecvTime = Now; + m_LastUpdateTime = Now; SendControl(NET_CTRLMSG_CONNECTACCEPT, 0, 0); - if(config.debug) + if(g_Config.m_Debug) dbg_msg("connection", "got connection, sending connect+accept"); } } else if(State() == NET_CONNSTATE_CONNECT) { - /* connection made */ + // connection made if(CtrlMsg == NET_CTRLMSG_CONNECTACCEPT) { SendControl(NET_CTRLMSG_ACCEPT, 0, 0); m_State = NET_CONNSTATE_ONLINE; - if(config.debug) + if(g_Config.m_Debug) dbg_msg("connection", "got connect+accept, sending accept. connection online"); } } else if(State() == NET_CONNSTATE_ONLINE) { - /* connection made */ + // connection made /* if(ctrlmsg == NET_CTRLMSG_CONNECTACCEPT) { @@ -273,7 +278,7 @@ int CNetConnection::Feed(CNetPacketConstruct *pPacket, NETADDR *pAddr) if(State() == NET_CONNSTATE_PENDING) { m_State = NET_CONNSTATE_ONLINE; - if(config.debug) + if(g_Config.m_Debug) dbg_msg("connection", "connecting online"); } } @@ -288,46 +293,46 @@ int CNetConnection::Feed(CNetPacketConstruct *pPacket, NETADDR *pAddr) int CNetConnection::Update() { - int64 now = time_get(); + int64 Now = time_get(); if(State() == NET_CONNSTATE_OFFLINE || State() == NET_CONNSTATE_ERROR) return 0; - /* check for timeout */ + // check for timeout if(State() != NET_CONNSTATE_OFFLINE && State() != NET_CONNSTATE_CONNECT && - (now-m_LastRecvTime) > time_freq()*10) + (Now-m_LastRecvTime) > time_freq()*10) { m_State = NET_CONNSTATE_ERROR; SetError("timeout"); } - /* fix resends */ + // fix resends if(m_Buffer.First()) { CNetChunkResend *pResend = m_Buffer.First(); - /* check if we have some really old stuff laying around and abort if not acked */ - if(now-pResend->m_FirstSendTime > time_freq()*10) + // check if we have some really old stuff laying around and abort if not acked + if(Now-pResend->m_FirstSendTime > time_freq()*10) { m_State = NET_CONNSTATE_ERROR; SetError("too weak connection (not acked for 10 seconds)"); } else { - /* resend packet if we havn't got it acked in 1 second */ - if(now-pResend->m_LastSendTime > time_freq()) + // resend packet if we havn't got it acked in 1 second + if(Now-pResend->m_LastSendTime > time_freq()) ResendChunk(pResend); } } - /* send keep alives if nothing has happend for 250ms */ + // send keep alives if nothing has happend for 250ms if(State() == NET_CONNSTATE_ONLINE) { - if(time_get()-m_LastSendTime > time_freq()/2) /* flush connection after 500ms if needed */ + if(time_get()-m_LastSendTime > time_freq()/2) // flush connection after 500ms if needed { int NumFlushedChunks = Flush(); - if(NumFlushedChunks && config.debug) + if(NumFlushedChunks && g_Config.m_Debug) dbg_msg("connection", "flushed connection due to timeout. %d chunks.", NumFlushedChunks); } @@ -336,12 +341,12 @@ int CNetConnection::Update() } else if(State() == NET_CONNSTATE_CONNECT) { - if(time_get()-m_LastSendTime > time_freq()/2) /* send a new connect every 500ms */ + if(time_get()-m_LastSendTime > time_freq()/2) // send a new connect every 500ms SendControl(NET_CTRLMSG_CONNECT, 0, 0); } else if(State() == NET_CONNSTATE_PENDING) { - if(time_get()-m_LastSendTime > time_freq()/2) /* send a new connect/accept every 500ms */ + if(time_get()-m_LastSendTime > time_freq()/2) // send a new connect/accept every 500ms SendControl(NET_CTRLMSG_CONNECTACCEPT, 0, 0); } diff --git a/src/engine/e_network_server.cpp b/src/engine/shared/network_server.cpp index 995290ef..32b08bf6 100644 --- a/src/engine/e_network_server.cpp +++ b/src/engine/shared/network_server.cpp @@ -1,28 +1,28 @@ #include <base/system.h> -#include "e_network.h" +#include "network.h" -#define MACRO_LIST_LINK_FIRST(object, first, prev, next) \ - { if(first) first->prev = object; \ - object->prev = (struct CBan *)0; \ - object->next = first; \ - first = object; } +#define MACRO_LIST_LINK_FIRST(Object, First, Prev, Next) \ + { if(First) First->Prev = Object; \ + Object->Prev = (struct CBan *)0; \ + Object->Next = First; \ + First = Object; } -#define MACRO_LIST_LINK_AFTER(object, after, prev, next) \ - { object->prev = after; \ - object->next = after->next; \ - after->next = object; \ - if(object->next) \ - object->next->prev = object; \ +#define MACRO_LIST_LINK_AFTER(Object, After, Prev, Next) \ + { Object->Prev = After; \ + Object->Next = After->Next; \ + After->Next = Object; \ + if(Object->Next) \ + Object->Next->Prev = Object; \ } -#define MACRO_LIST_UNLINK(object, first, prev, next) \ - { if(object->next) object->next->prev = object->prev; \ - if(object->prev) object->prev->next = object->next; \ - else first = object->next; \ - object->next = 0; object->prev = 0; } +#define MACRO_LIST_UNLINK(Object, First, Prev, Next) \ + { if(Object->Next) Object->Next->Prev = Object->Prev; \ + if(Object->Prev) Object->Prev->Next = Object->Next; \ + else First = Object->Next; \ + Object->Next = 0; Object->Prev = 0; } -#define MACRO_LIST_FIND(start, next, expression) \ - { while(start && !(expression)) start = start->next; } +#define MACRO_LIST_FIND(Start, Next, Expression) \ + { while(Start && !(Expression)) Start = Start->Next; } bool CNetServer::Open(NETADDR BindAddr, int MaxClients, int Flags) { @@ -44,7 +44,7 @@ bool CNetServer::Open(NETADDR BindAddr, int MaxClients, int Flags) for(int i = 0; i < NET_MAX_CLIENTS; i++) m_aSlots[i].m_Connection.Init(m_Socket); - /* setup all pointers for bans */ + // setup all pointers for bans for(int i = 1; i < NET_SERVER_MAXBANS-1; i++) { m_BanPool[i].m_pNext = &m_BanPool[i+1]; @@ -68,13 +68,13 @@ int CNetServer::SetCallbacks(NETFUNC_NEWCLIENT pfnNewClient, NETFUNC_DELCLIENT p int CNetServer::Close() { - /* TODO: implement me */ + // TODO: implement me return 0; } int CNetServer::Drop(int ClientID, const char *pReason) { - /* TODO: insert lots of checks here */ + // TODO: insert lots of checks here NETADDR Addr = ClientAddr(ClientID); dbg_msg("net_server", "client dropped. cid=%d ip=%d.%d.%d.%d reason=\"%s\"", @@ -114,18 +114,18 @@ int CNetServer::BanNum() void CNetServer::BanRemoveByObject(CBan *pBan) { - int iphash = (pBan->m_Info.m_Addr.ip[0]+pBan->m_Info.m_Addr.ip[1]+pBan->m_Info.m_Addr.ip[2]+pBan->m_Info.m_Addr.ip[3])&0xff; + int IpHash = (pBan->m_Info.m_Addr.ip[0]+pBan->m_Info.m_Addr.ip[1]+pBan->m_Info.m_Addr.ip[2]+pBan->m_Info.m_Addr.ip[3])&0xff; dbg_msg("netserver", "removing ban on %d.%d.%d.%d", pBan->m_Info.m_Addr.ip[0], pBan->m_Info.m_Addr.ip[1], pBan->m_Info.m_Addr.ip[2], pBan->m_Info.m_Addr.ip[3]); MACRO_LIST_UNLINK(pBan, m_BanPool_FirstUsed, m_pPrev, m_pNext); - MACRO_LIST_UNLINK(pBan, m_aBans[iphash], m_pHashPrev, m_pHashNext); + MACRO_LIST_UNLINK(pBan, m_aBans[IpHash], m_pHashPrev, m_pHashNext); MACRO_LIST_LINK_FIRST(pBan, m_BanPool_FirstFree, m_pPrev, m_pNext); } int CNetServer::BanRemove(NETADDR Addr) { - int iphash = (Addr.ip[0]+Addr.ip[1]+Addr.ip[2]+Addr.ip[3])&0xff; - CBan *pBan = m_aBans[iphash]; + int IpHash = (Addr.ip[0]+Addr.ip[1]+Addr.ip[2]+Addr.ip[3])&0xff; + CBan *pBan = m_aBans[IpHash]; MACRO_LIST_FIND(pBan, m_pHashNext, net_addr_comp(&pBan->m_Info.m_Addr, &Addr) == 0); @@ -140,22 +140,22 @@ int CNetServer::BanRemove(NETADDR Addr) int CNetServer::BanAdd(NETADDR Addr, int Seconds) { - int iphash = (Addr.ip[0]+Addr.ip[1]+Addr.ip[2]+Addr.ip[3])&0xff; + int IpHash = (Addr.ip[0]+Addr.ip[1]+Addr.ip[2]+Addr.ip[3])&0xff; int Stamp = -1; CBan *pBan; - /* remove the port */ + // remove the port Addr.port = 0; if(Seconds) Stamp = time_timestamp() + Seconds; - /* search to see if it already exists */ - pBan = m_aBans[iphash]; + // search to see if it already exists + pBan = m_aBans[IpHash]; MACRO_LIST_FIND(pBan, m_pHashNext, net_addr_comp(&pBan->m_Info.m_Addr, &Addr) == 0); if(pBan) { - /* adjust the ban */ + // adjust the ban pBan->m_Info.m_Expires = Stamp; return 0; } @@ -163,18 +163,18 @@ int CNetServer::BanAdd(NETADDR Addr, int Seconds) if(!m_BanPool_FirstFree) return -1; - /* fetch and clear the new ban */ + // fetch and clear the new ban pBan = m_BanPool_FirstFree; MACRO_LIST_UNLINK(pBan, m_BanPool_FirstFree, m_pPrev, m_pNext); - /* setup the ban info */ + // setup the ban info pBan->m_Info.m_Expires = Stamp; pBan->m_Info.m_Addr = Addr; - /* add it to the ban hash */ - MACRO_LIST_LINK_FIRST(pBan, m_aBans[iphash], m_pHashPrev, m_pHashNext); + // add it to the ban hash + MACRO_LIST_LINK_FIRST(pBan, m_aBans[IpHash], m_pHashPrev, m_pHashNext); - /* insert it into the used list */ + // insert it into the used list { if(m_BanPool_FirstUsed) { @@ -185,7 +185,7 @@ int CNetServer::BanAdd(NETADDR Addr, int Seconds) pInsertAfter = pInsertAfter->m_pPrev; else { - /* add to last */ + // add to last pInsertAfter = m_BanPool_FirstUsed; while(pInsertAfter->m_pNext) pInsertAfter = pInsertAfter->m_pNext; @@ -206,7 +206,7 @@ int CNetServer::BanAdd(NETADDR Addr, int Seconds) } } - /* drop banned clients */ + // drop banned clients { char Buf[128]; NETADDR BanAddr; @@ -238,7 +238,7 @@ int CNetServer::Update() Drop(i, m_aSlots[i].m_Connection.ErrorString()); } - /* remove expired bans */ + // remove expired bans while(m_BanPool_FirstUsed && m_BanPool_FirstUsed->m_Info.m_Expires < Now) { CBan *pBan = m_BanPool_FirstUsed; @@ -253,20 +253,20 @@ int CNetServer::Update() */ int CNetServer::Recv(CNetChunk *pChunk) { - unsigned now = time_timestamp(); + unsigned Now = time_timestamp(); while(1) { NETADDR Addr; - /* check for a chunk */ + // check for a chunk if(m_RecvUnpacker.FetchChunk(pChunk)) return 1; - /* TODO: empty the recvinfo */ + // TODO: empty the recvinfo int Bytes = net_udp_recv(m_Socket, &Addr, m_RecvUnpacker.m_aBuffer, NET_MAX_PACKETSIZE); - /* no more packets for now */ + // no more packets for now if(Bytes <= 0) break; @@ -274,25 +274,25 @@ int CNetServer::Recv(CNetChunk *pChunk) { CBan *pBan = 0; NETADDR BanAddr = Addr; - int iphash = (BanAddr.ip[0]+BanAddr.ip[1]+BanAddr.ip[2]+BanAddr.ip[3])&0xff; + int IpHash = (BanAddr.ip[0]+BanAddr.ip[1]+BanAddr.ip[2]+BanAddr.ip[3])&0xff; int Found = 0; BanAddr.port = 0; - /* search a ban */ - for(pBan = m_aBans[iphash]; pBan; pBan = pBan->m_pHashNext) + // search a ban + for(pBan = m_aBans[IpHash]; pBan; pBan = pBan->m_pHashNext) { if(net_addr_comp(&pBan->m_Info.m_Addr, &BanAddr) == 0) break; } - /* check if we just should drop the packet */ + // check if we just should drop the packet if(pBan) { // banned, reply with a message char BanStr[128]; if(pBan->m_Info.m_Expires) { - int Mins = ((pBan->m_Info.m_Expires - now)+59)/60; + int Mins = ((pBan->m_Info.m_Expires - Now)+59)/60; if(Mins == 1) str_format(BanStr, sizeof(BanStr), "banned for %d minute", Mins); else @@ -315,24 +315,24 @@ int CNetServer::Recv(CNetChunk *pChunk) } else { - /* TODO: check size here */ + // TODO: check size here if(m_RecvUnpacker.m_Data.m_Flags&NET_PACKETFLAG_CONTROL && m_RecvUnpacker.m_Data.m_aChunkData[0] == NET_CTRLMSG_CONNECT) { Found = 0; - /* check if we already got this client */ + // check if we already got this client for(int i = 0; i < MaxClients(); i++) { NETADDR PeerAddr = m_aSlots[i].m_Connection.PeerAddress(); if(m_aSlots[i].m_Connection.State() != NET_CONNSTATE_OFFLINE && net_addr_comp(&PeerAddr, &Addr) == 0) { - Found = 1; /* silent ignore.. we got this client already */ + Found = 1; // silent ignore.. we got this client already break; } } - /* client that wants to connect */ + // client that wants to connect if(!Found) { for(int i = 0; i < MaxClients(); i++) @@ -356,7 +356,7 @@ int CNetServer::Recv(CNetChunk *pChunk) } else { - /* normal packet, find matching slot */ + // normal packet, find matching slot for(int i = 0; i < MaxClients(); i++) { NETADDR PeerAddr = m_aSlots[i].m_Connection.PeerAddress(); @@ -386,7 +386,7 @@ int CNetServer::Send(CNetChunk *pChunk) if(pChunk->m_Flags&NETSENDFLAG_CONNLESS) { - /* send connectionless packet */ + // send connectionless packet CNetBase::SendPacketConnless(m_Socket, &pChunk->m_Address, pChunk->m_pData, pChunk->m_DataSize); } else @@ -398,10 +398,15 @@ int CNetServer::Send(CNetChunk *pChunk) if(pChunk->m_Flags&NETSENDFLAG_VITAL) Flags = NET_CHUNKFLAG_VITAL; - m_aSlots[pChunk->m_ClientID].m_Connection.QueueChunk(Flags, pChunk->m_DataSize, pChunk->m_pData); - - if(pChunk->m_Flags&NETSENDFLAG_FLUSH) - m_aSlots[pChunk->m_ClientID].m_Connection.Flush(); + if(m_aSlots[pChunk->m_ClientID].m_Connection.QueueChunk(Flags, pChunk->m_DataSize, pChunk->m_pData) == 0) + { + if(pChunk->m_Flags&NETSENDFLAG_FLUSH) + m_aSlots[pChunk->m_ClientID].m_Connection.Flush(); + } + else + { + Drop(pChunk->m_ClientID, "error sending data"); + } } return 0; } diff --git a/src/engine/e_packer.cpp b/src/engine/shared/packer.cpp index 0d8aeab3..3e1d8dd6 100644 --- a/src/engine/e_packer.cpp +++ b/src/engine/shared/packer.cpp @@ -1,11 +1,10 @@ -/* copyright (c) 2007 magnus auvinen, see licence.txt for more info */ -#include <stdlib.h> /* rand() */ +// copyright (c) 2007 magnus auvinen, see licence.txt for more info #include <base/system.h> -#include "e_packer.h" -#include "e_compression.h" -#include "e_engine.h" -#include "e_config.h" +#include "packer.h" +#include "compression.h" +#include "engine.h" +#include "config.h" void CPacker::Reset() { @@ -19,14 +18,14 @@ void CPacker::AddInt(int i) if(m_Error) return; - /* make sure that we have space enough */ + // make sure that we have space enough if(m_pEnd - m_pCurrent < 6) { dbg_break(); m_Error = 1; } else - m_pCurrent = vint_pack(m_pCurrent, i); + m_pCurrent = CVariableInt::Pack(m_pCurrent, i); } void CPacker::AddString(const char *pStr, int Limit) @@ -34,7 +33,7 @@ void CPacker::AddString(const char *pStr, int Limit) if(m_Error) return; - /* */ + // if(Limit > 0) { while(*pStr && Limit != 0) @@ -66,7 +65,7 @@ void CPacker::AddString(const char *pStr, int Limit) } } -void CPacker::AddRaw(const unsigned char *pData, int Size) +void CPacker::AddRaw(const void *pData, int Size) { if(m_Error) return; @@ -77,18 +76,19 @@ void CPacker::AddRaw(const unsigned char *pData, int Size) return; } + const unsigned char *pSrc = (const unsigned char *)pData; while(Size) { - *m_pCurrent++ = *pData++; + *m_pCurrent++ = *pSrc++; Size--; } } -void CUnpacker::Reset(const unsigned char *pData, int Size) +void CUnpacker::Reset(const void *pData, int Size) { m_Error = 0; - m_pStart = pData; + m_pStart = (const unsigned char *)pData; m_pEnd = m_pStart + Size; m_pCurrent = m_pStart; } @@ -105,7 +105,7 @@ int CUnpacker::GetInt() } int i; - m_pCurrent = vint_unpack(m_pCurrent, &i); + m_pCurrent = CVariableInt::Unpack(m_pCurrent, &i); if(m_pCurrent > m_pEnd) { m_Error = 1; @@ -120,7 +120,7 @@ const char *CUnpacker::GetString() return ""; char *pPtr = (char *)m_pCurrent; - while(*m_pCurrent) /* skip the string */ + while(*m_pCurrent) // skip the string { m_pCurrent++; if(m_pCurrent == m_pEnd) @@ -131,7 +131,7 @@ const char *CUnpacker::GetString() } m_pCurrent++; - /* sanitize all strings */ + // sanitize all strings str_sanitize(pPtr); return pPtr; } @@ -142,14 +142,14 @@ const unsigned char *CUnpacker::GetRaw(int Size) if(m_Error) return 0; - /* check for nasty sizes */ + // check for nasty sizes if(Size < 0 || m_pCurrent+Size > m_pEnd) { m_Error = 1; return 0; } - /* "unpack" the data */ + // "unpack" the data m_pCurrent += Size; return pPtr; } diff --git a/src/engine/e_packer.h b/src/engine/shared/packer.h index 5dc80e7a..7a98501a 100644 --- a/src/engine/e_packer.h +++ b/src/engine/shared/packer.h @@ -1,4 +1,7 @@ -/* copyright (c) 2007 magnus auvinen, see licence.txt for more info */ +#ifndef ENGINE_SHARED_PACKER_H +#define ENGINE_SHARED_PACKER_H + + class CPacker { @@ -15,7 +18,7 @@ public: void Reset(); void AddInt(int i); void AddString(const char *pStr, int Limit); - void AddRaw(const unsigned char *pData, int Size); + void AddRaw(const void *pData, int Size); int Size() const { return (int)(m_pCurrent-m_aBuffer); } const unsigned char *Data() const { return m_aBuffer; } @@ -29,9 +32,11 @@ class CUnpacker const unsigned char *m_pEnd; int m_Error; public: - void Reset(const unsigned char *pData, int Size); + void Reset(const void *pData, int Size); int GetInt(); const char *GetString(); const unsigned char *GetRaw(int Size); bool Error() const { return m_Error; } }; + +#endif diff --git a/src/engine/shared/protocol.h b/src/engine/shared/protocol.h new file mode 100644 index 00000000..d09cff5a --- /dev/null +++ b/src/engine/shared/protocol.h @@ -0,0 +1,91 @@ +#ifndef ENGINE_SHARED_PROTOCOL_H +#define ENGINE_SHARED_PROTOCOL_H + +#include <base/system.h> + +/* + Connection diagram - How the initilization works. + + Client -> INFO -> Server + Contains version info, name, and some other info. + + Client <- MAP <- Server + Contains current map. + + Client -> READY -> Server + The client has loaded the map and is ready to go, + but the mod needs to send it's information aswell. + modc_connected is called on the client and + mods_connected is called on the server. + The client should call client_entergame when the + mod has done it's initilization. + + Client -> ENTERGAME -> Server + Tells the server to start sending snapshots. + client_entergame and server_client_enter is called. +*/ + + +enum +{ + NETMSG_NULL=0, + + // the first thing sent by the client + // contains the version info for the client + NETMSG_INFO=1, + + // sent by server + NETMSG_MAP_CHANGE, // sent when client should switch map + NETMSG_MAP_DATA, // map transfer, contains a chunk of the map file + NETMSG_SNAP, // normal snapshot, multiple parts + NETMSG_SNAPEMPTY, // empty snapshot + NETMSG_SNAPSINGLE, // ? + NETMSG_SNAPSMALL, // + NETMSG_INPUTTIMING, // reports how off the input was + NETMSG_RCON_AUTH_STATUS,// result of the authentication + NETMSG_RCON_LINE, // line that should be printed to the remote console + + NETMSG_AUTH_CHALLANGE, // + NETMSG_AUTH_RESULT, // + + // sent by client + NETMSG_READY, // + NETMSG_ENTERGAME, + NETMSG_INPUT, // contains the inputdata from the client + NETMSG_RCON_CMD, // + NETMSG_RCON_AUTH, // + NETMSG_REQUEST_MAP_DATA,// + + NETMSG_AUTH_START, // + NETMSG_AUTH_RESPONSE, // + + // sent by both + NETMSG_PING, + NETMSG_PING_REPLY, + NETMSG_ERROR +}; + +// this should be revised +enum +{ + SERVER_TICK_SPEED=50, + SERVER_FLAG_PASSWORD = 0x1, + + MAX_CLIENTS=16, + + MAX_INPUT_SIZE=128, + MAX_SNAPSHOT_PACKSIZE=900, + + MAX_CLANNAME_LENGTH=32, + MAX_NAME_LENGTH=24, + + + // message packing + MSGFLAG_VITAL=1, + MSGFLAG_FLUSH=2, + MSGFLAG_NORECORD=4, + MSGFLAG_RECORD=8, + MSGFLAG_NOSEND=16 +}; + +#endif diff --git a/src/engine/e_ringbuffer.cpp b/src/engine/shared/ringbuffer.cpp index eb8a8af4..45a845ee 100644 --- a/src/engine/e_ringbuffer.cpp +++ b/src/engine/shared/ringbuffer.cpp @@ -1,6 +1,6 @@ #include <base/system.h> -#include "e_ringbuffer.h" +#include "ringbuffer.h" CRingBufferBase::CItem *CRingBufferBase::NextBlock(CItem *pItem) { @@ -18,15 +18,15 @@ CRingBufferBase::CItem *CRingBufferBase::PrevBlock(CItem *pItem) CRingBufferBase::CItem *CRingBufferBase::MergeBack(CItem *pItem) { - /* make sure that this block and previous block is free */ + // make sure that this block and previous block is free if(!pItem->m_Free || !pItem->m_pPrev || !pItem->m_pPrev->m_Free) return pItem; - /* merge the blocks */ + // merge the blocks pItem->m_pPrev->m_Size += pItem->m_Size; pItem->m_pPrev->m_pNext = pItem->m_pNext; - /* fixup pointers */ + // fixup pointers if(pItem->m_pNext) pItem->m_pNext->m_pPrev = pItem->m_pPrev; else @@ -38,7 +38,7 @@ CRingBufferBase::CItem *CRingBufferBase::MergeBack(CItem *pItem) if(pItem == m_pConsume) m_pConsume = pItem->m_pPrev; - /* return the current block */ + // return the current block return pItem->m_pPrev; } @@ -61,20 +61,20 @@ void *CRingBufferBase::Allocate(int Size) int WantedSize = (Size+sizeof(CItem)+sizeof(CItem)-1)/sizeof(CItem)*sizeof(CItem); CItem *pBlock = 0; - /* check if we even can fit this block */ + // check if we even can fit this block if(WantedSize > m_Size) return 0; while(1) { - /* check for space */ + // check for space if(m_pProduce->m_Free) { if(m_pProduce->m_Size >= WantedSize) pBlock = m_pProduce; else { - /* wrap around to try to find a block */ + // wrap around to try to find a block if(m_pFirst->m_Free && m_pFirst->m_Size >= WantedSize) pBlock = m_pFirst; } @@ -84,7 +84,7 @@ void *CRingBufferBase::Allocate(int Size) break; else { - /* we have no block, check our policy and see what todo */ + // we have no block, check our policy and see what todo if(m_Flags&FLAG_RECYCLE) { if(!PopFirst()) @@ -95,10 +95,10 @@ void *CRingBufferBase::Allocate(int Size) } } - /* okey, we have our block */ + // okey, we have our block - /* split the block if needed */ - if(pBlock->m_Size > WantedSize) + // split the block if needed + if(pBlock->m_Size > WantedSize+sizeof(CItem)) { CItem *pNewItem = (CItem *)((char *)pBlock + WantedSize); pNewItem->m_pPrev = pBlock; @@ -116,10 +116,10 @@ void *CRingBufferBase::Allocate(int Size) } - /* set next block */ + // set next block m_pProduce = NextBlock(pBlock); - /* set as used and return the item pointer */ + // set as used and return the item pointer pBlock->m_Free = 0; return (void *)(pBlock+1); } @@ -129,13 +129,13 @@ int CRingBufferBase::PopFirst() if(m_pConsume->m_Free) return 0; - /* set the free flag */ + // set the free flag m_pConsume->m_Free = 1; - /* previous block is also free, merge them */ + // previous block is also free, merge them m_pConsume = MergeBack(m_pConsume); - /* advance the consume pointer */ + // advance the consume pointer m_pConsume = NextBlock(m_pConsume); while(m_pConsume->m_Free && m_pConsume != m_pProduce) { @@ -143,8 +143,8 @@ int CRingBufferBase::PopFirst() m_pConsume = NextBlock(m_pConsume); } - /* in the case that we have catched up with the produce pointer */ - /* we might stand on a free block so merge em */ + // in the case that we have catched up with the produce pointer + // we might stand on a free block so merge em MergeBack(m_pConsume); return 1; } @@ -187,8 +187,6 @@ void *CRingBufferBase::First() void *CRingBufferBase::Last() { - if(!m_pProduce->m_Free) - return m_pProduce+1; return Prev(m_pProduce+1); } diff --git a/src/engine/e_ringbuffer.h b/src/engine/shared/ringbuffer.h index b9f7219c..aa02b8d9 100644 --- a/src/engine/e_ringbuffer.h +++ b/src/engine/shared/ringbuffer.h @@ -1,5 +1,5 @@ -#ifndef _RINGBUFFER_H -#define _RINGBUFFER_H +#ifndef ENGINE_SHARED_RINGBUFFER_H +#define ENGINE_SHARED_RINGBUFFER_H typedef struct RINGBUFFER RINGBUFFER; @@ -40,7 +40,7 @@ protected: public: enum { - /* Will start to destroy items to try to fit the next one */ + // Will start to destroy items to try to fit the next one FLAG_RECYCLE=1 }; }; diff --git a/src/engine/shared/snapshot.cpp b/src/engine/shared/snapshot.cpp new file mode 100644 index 00000000..d566d3a3 --- /dev/null +++ b/src/engine/shared/snapshot.cpp @@ -0,0 +1,537 @@ +// copyright (c) 2007 magnus auvinen, see licence.txt for more info +#include "snapshot.h" +#include "engine.h" +#include "compression.h" + +// CSnapshot + +CSnapshotItem *CSnapshot::GetItem(int Index) +{ + return (CSnapshotItem *)(DataStart() + Offsets()[Index]); +} + +int CSnapshot::GetItemSize(int Index) +{ + if(Index == m_NumItems-1) + return (m_DataSize - Offsets()[Index]) - sizeof(CSnapshotItem); + return (Offsets()[Index+1] - Offsets()[Index]) - sizeof(CSnapshotItem); +} + +int CSnapshot::GetItemIndex(int Key) +{ + // TODO: OPT: this should not be a linear search. very bad + for(int i = 0; i < m_NumItems; i++) + { + if(GetItem(i)->Key() == Key) + return i; + } + return -1; +} + +int CSnapshot::Crc() +{ + int Crc = 0; + + for(int i = 0; i < m_NumItems; i++) + { + CSnapshotItem *pItem = GetItem(i); + int Size = GetItemSize(i); + + for(int b = 0; b < Size/4; b++) + Crc += pItem->Data()[b]; + } + return Crc; +} + +void CSnapshot::DebugDump() +{ + dbg_msg("snapshot", "data_size=%d num_items=%d", m_DataSize, m_NumItems); + for(int i = 0; i < m_NumItems; i++) + { + CSnapshotItem *pItem = GetItem(i); + int Size = GetItemSize(i); + dbg_msg("snapshot", "\ttype=%d id=%d", pItem->Type(), pItem->ID()); + for(int b = 0; b < Size/4; b++) + dbg_msg("snapshot", "\t\t%3d %12d\t%08x", b, pItem->Data()[b], pItem->Data()[b]); + } +} + + +// CSnapshotDelta + +struct CItemList +{ + int m_Num; + int m_aKeys[64]; + int m_aIndex[64]; +}; + +enum +{ + HASHLIST_SIZE = 256, +}; + +static void GenerateHash(CItemList *pHashlist, CSnapshot *pSnapshot) +{ + for(int i = 0; i < HASHLIST_SIZE; i++) + pHashlist[i].m_Num = 0; + + for(int i = 0; i < pSnapshot->NumItems(); i++) + { + int Key = pSnapshot->GetItem(i)->Key(); + int HashID = ((Key>>8)&0xf0) | (Key&0xf); + if(pHashlist[HashID].m_Num != 64) + { + pHashlist[HashID].m_aIndex[pHashlist[HashID].m_Num] = i; + pHashlist[HashID].m_aKeys[pHashlist[HashID].m_Num] = Key; + pHashlist[HashID].m_Num++; + } + } +} + +static int GetItemIndexHashed(int Key, const CItemList *pHashlist) +{ + int HashID = ((Key>>8)&0xf0) | (Key&0xf); + for(int i = 0; i < pHashlist[HashID].m_Num; i++) + { + if(pHashlist[HashID].m_aKeys[i] == Key) + return pHashlist[HashID].m_aIndex[i]; + } + + return -1; +} + +static int DiffItem(int *pPast, int *pCurrent, int *pOut, int Size) +{ + int Needed = 0; + while(Size) + { + *pOut = *pCurrent-*pPast; + Needed |= *pOut; + pOut++; + pPast++; + pCurrent++; + Size--; + } + + return Needed; +} + +void CSnapshotDelta::UndiffItem(int *pPast, int *pDiff, int *pOut, int Size) +{ + while(Size) + { + *pOut = *pPast+*pDiff; + + if(*pDiff == 0) + m_aSnapshotDataRate[m_SnapshotCurrent] += 1; + else + { + unsigned char aBuf[16]; + unsigned char *pEnd = CVariableInt::Pack(aBuf, *pDiff); + m_aSnapshotDataRate[m_SnapshotCurrent] += (int)(pEnd - (unsigned char*)aBuf) * 8; + } + + pOut++; + pPast++; + pDiff++; + Size--; + } +} + +CSnapshotDelta::CSnapshotDelta() +{ + mem_zero(m_aItemSizes, sizeof(m_aItemSizes)); + mem_zero(m_aSnapshotDataRate, sizeof(m_aSnapshotDataRate)); + mem_zero(m_aSnapshotDataUpdates, sizeof(m_aSnapshotDataUpdates)); + m_SnapshotCurrent = 0; + mem_zero(&m_Empty, sizeof(m_Empty)); +} + +void CSnapshotDelta::SetStaticsize(int ItemType, int Size) +{ + m_aItemSizes[ItemType] = Size; +} + +CSnapshotDelta::CData *CSnapshotDelta::EmptyDelta() +{ + return &m_Empty; +} + +// TODO: OPT: this should be made much faster +int CSnapshotDelta::CreateDelta(CSnapshot *pFrom, CSnapshot *pTo, void *pDstData) +{ + CData *pDelta = (CData *)pDstData; + int *pData = (int *)pDelta->m_pData; + int i, ItemSize, PastIndex; + CSnapshotItem *pFromItem; + CSnapshotItem *pCurItem; + CSnapshotItem *pPastItem; + int Count = 0; + int SizeCount = 0; + + pDelta->m_NumDeletedItems = 0; + pDelta->m_NumUpdateItems = 0; + pDelta->m_NumTempItems = 0; + + CItemList Hashlist[HASHLIST_SIZE]; + GenerateHash(Hashlist, pTo); + + // pack deleted stuff + for(i = 0; i < pFrom->NumItems(); i++) + { + pFromItem = pFrom->GetItem(i); + if(GetItemIndexHashed(pFromItem->Key(), Hashlist) == -1) + { + // deleted + pDelta->m_NumDeletedItems++; + *pData = pFromItem->Key(); + pData++; + } + } + + GenerateHash(Hashlist, pFrom); + int aPastIndecies[1024]; + + // fetch previous indices + // we do this as a separate pass because it helps the cache + for(i = 0; i < pTo->NumItems(); i++) + { + pCurItem = pTo->GetItem(i); // O(1) .. O(n) + aPastIndecies[i] = GetItemIndexHashed(pCurItem->Key(), Hashlist); // O(n) .. O(n^n) + } + + for(i = 0; i < pTo->NumItems(); i++) + { + // do delta + ItemSize = pTo->GetItemSize(i); // O(1) .. O(n) + pCurItem = pTo->GetItem(i); // O(1) .. O(n) + PastIndex = aPastIndecies[i]; + + if(PastIndex != -1) + { + int *pItemDataDst = pData+3; + + pPastItem = pFrom->GetItem(PastIndex); + + if(m_aItemSizes[pCurItem->Type()]) + pItemDataDst = pData+2; + + if(DiffItem((int*)pPastItem->Data(), (int*)pCurItem->Data(), pItemDataDst, ItemSize/4)) + { + + *pData++ = pCurItem->Type(); + *pData++ = pCurItem->ID(); + if(!m_aItemSizes[pCurItem->Type()]) + *pData++ = ItemSize/4; + pData += ItemSize/4; + pDelta->m_NumUpdateItems++; + } + } + else + { + *pData++ = pCurItem->Type(); + *pData++ = pCurItem->ID(); + if(!m_aItemSizes[pCurItem->Type()]) + *pData++ = ItemSize/4; + + mem_copy(pData, pCurItem->Data(), ItemSize); + SizeCount += ItemSize; + pData += ItemSize/4; + pDelta->m_NumUpdateItems++; + Count++; + } + } + + if(0) + { + dbg_msg("snapshot", "%d %d %d", + pDelta->m_NumDeletedItems, + pDelta->m_NumUpdateItems, + pDelta->m_NumTempItems); + } + + /* + // TODO: pack temp stuff + + // finish + //mem_copy(pDelta->offsets, deleted, pDelta->num_deleted_items*sizeof(int)); + //mem_copy(&(pDelta->offsets[pDelta->num_deleted_items]), update, pDelta->num_update_items*sizeof(int)); + //mem_copy(&(pDelta->offsets[pDelta->num_deleted_items+pDelta->num_update_items]), temp, pDelta->num_temp_items*sizeof(int)); + //mem_copy(pDelta->data_start(), data, data_size); + //pDelta->data_size = data_size; + * */ + + if(!pDelta->m_NumDeletedItems && !pDelta->m_NumUpdateItems && !pDelta->m_NumTempItems) + return 0; + + return (int)((char*)pData-(char*)pDstData); +} + +static int RangeCheck(void *pEnd, void *pPtr, int Size) +{ + if((const char *)pPtr + Size > (const char *)pEnd) + return -1; + return 0; +} + +int CSnapshotDelta::UnpackDelta(CSnapshot *pFrom, CSnapshot *pTo, void *pSrcData, int DataSize) +{ + CSnapshotBuilder Builder; + CData *pDelta = (CData *)pSrcData; + int *pData = (int *)pDelta->m_pData; + int *pEnd = (int *)(((char *)pSrcData + DataSize)); + + CSnapshotItem *pFromItem; + int Keep, ItemSize; + int *pDeleted; + int Id, Type, Key; + int FromIndex; + int *pNewData; + + Builder.Init(); + + // unpack deleted stuff + pDeleted = pData; + pData += pDelta->m_NumDeletedItems; + if(pData > pEnd) + return -1; + + // copy all non deleted stuff + for(int i = 0; i < pFrom->NumItems(); i++) + { + // dbg_assert(0, "fail!"); + pFromItem = pFrom->GetItem(i); + ItemSize = pFrom->GetItemSize(i); + Keep = 1; + for(int d = 0; d < pDelta->m_NumDeletedItems; d++) + { + if(pDeleted[d] == pFromItem->Key()) + { + Keep = 0; + break; + } + } + + if(Keep) + { + // keep it + mem_copy( + Builder.NewItem(pFromItem->Type(), pFromItem->ID(), ItemSize), + pFromItem->Data(), ItemSize); + } + } + + // unpack updated stuff + for(int i = 0; i < pDelta->m_NumUpdateItems; i++) + { + if(pData+2 > pEnd) + return -1; + + Type = *pData++; + Id = *pData++; + if(m_aItemSizes[Type]) + ItemSize = m_aItemSizes[Type]; + else + { + if(pData+1 > pEnd) + return -2; + ItemSize = (*pData++) * 4; + } + m_SnapshotCurrent = Type; + + if(RangeCheck(pEnd, pData, ItemSize) || ItemSize < 0) return -3; + + Key = (Type<<16)|Id; + + // create the item if needed + pNewData = Builder.GetItemData(Key); + if(!pNewData) + pNewData = (int *)Builder.NewItem(Key>>16, Key&0xffff, ItemSize); + + //if(range_check(pEnd, pNewData, ItemSize)) return -4; + + FromIndex = pFrom->GetItemIndex(Key); + if(FromIndex != -1) + { + // we got an update so we need pTo apply the diff + UndiffItem((int *)pFrom->GetItem(FromIndex)->Data(), pData, pNewData, ItemSize/4); + m_aSnapshotDataUpdates[m_SnapshotCurrent]++; + } + else // no previous, just copy the pData + { + mem_copy(pNewData, pData, ItemSize); + m_aSnapshotDataRate[m_SnapshotCurrent] += ItemSize*8; + m_aSnapshotDataUpdates[m_SnapshotCurrent]++; + } + + pData += ItemSize/4; + } + + // finish up + return Builder.Finish(pTo); +} + + +// CSnapshotStorage + +void CSnapshotStorage::Init() +{ + m_pFirst = 0; + m_pLast = 0; +} + +void CSnapshotStorage::PurgeAll() +{ + CHolder *pHolder = m_pFirst; + CHolder *pNext; + + while(pHolder) + { + pNext = pHolder->m_pNext; + mem_free(pHolder); + pHolder = pNext; + } + + // no more snapshots in storage + m_pFirst = 0; + m_pLast = 0; +} + +void CSnapshotStorage::PurgeUntil(int Tick) +{ + CHolder *pHolder = m_pFirst; + CHolder *pNext; + + while(pHolder) + { + pNext = pHolder->m_pNext; + if(pHolder->m_Tick >= Tick) + return; // no more to remove + mem_free(pHolder); + + // did we come to the end of the list? + if (!pNext) + break; + + m_pFirst = pNext; + pNext->m_pPrev = 0x0; + + pHolder = pNext; + } + + // no more snapshots in storage + m_pFirst = 0; + m_pLast = 0; +} + +void CSnapshotStorage::Add(int Tick, int64 Tagtime, int DataSize, void *pData, int CreateAlt) +{ + // allocate memory for holder + snapshot_data + int TotalSize = sizeof(CHolder)+DataSize; + + if(CreateAlt) + TotalSize += DataSize; + + CHolder *pHolder = (CHolder *)mem_alloc(TotalSize, 1); + + // set data + pHolder->m_Tick = Tick; + pHolder->m_Tagtime = Tagtime; + pHolder->m_SnapSize = DataSize; + pHolder->m_pSnap = (CSnapshot*)(pHolder+1); + mem_copy(pHolder->m_pSnap, pData, DataSize); + + if(CreateAlt) // create alternative if wanted + { + pHolder->m_pAltSnap = (CSnapshot*)(((char *)pHolder->m_pSnap) + DataSize); + mem_copy(pHolder->m_pAltSnap, pData, DataSize); + } + else + pHolder->m_pAltSnap = 0; + + + // link + pHolder->m_pNext = 0; + pHolder->m_pPrev = m_pLast; + if(m_pLast) + m_pLast->m_pNext = pHolder; + else + m_pFirst = pHolder; + m_pLast = pHolder; +} + +int CSnapshotStorage::Get(int Tick, int64 *pTagtime, CSnapshot **ppData, CSnapshot **ppAltData) +{ + CHolder *pHolder = m_pFirst; + + while(pHolder) + { + if(pHolder->m_Tick == Tick) + { + if(pTagtime) + *pTagtime = pHolder->m_Tagtime; + if(ppData) + *ppData = pHolder->m_pSnap; + if(ppAltData) + *ppData = pHolder->m_pAltSnap; + return pHolder->m_SnapSize; + } + + pHolder = pHolder->m_pNext; + } + + return -1; +} + +// CSnapshotBuilder + +void CSnapshotBuilder::Init() +{ + m_DataSize = 0; + m_NumItems = 0; +} + +CSnapshotItem *CSnapshotBuilder::GetItem(int Index) +{ + return (CSnapshotItem *)&(m_aData[m_aOffsets[Index]]); +} + +int *CSnapshotBuilder::GetItemData(int Key) +{ + int i; + for(i = 0; i < m_NumItems; i++) + { + if(GetItem(i)->Key() == Key) + return (int *)GetItem(i)->Data(); + } + return 0; +} + +int CSnapshotBuilder::Finish(void *SpnapData) +{ + // flattern and make the snapshot + CSnapshot *pSnap = (CSnapshot *)SpnapData; + int OffsetSize = sizeof(int)*m_NumItems; + pSnap->m_DataSize = m_DataSize; + pSnap->m_NumItems = m_NumItems; + mem_copy(pSnap->Offsets(), m_aOffsets, OffsetSize); + mem_copy(pSnap->DataStart(), m_aData, m_DataSize); + return sizeof(CSnapshot) + OffsetSize + m_DataSize; +} + +void *CSnapshotBuilder::NewItem(int Type, int ID, int Size) +{ + CSnapshotItem *pObj = (CSnapshotItem *)(m_aData + m_DataSize); + + mem_zero(pObj, sizeof(CSnapshotItem) + Size); + pObj->m_TypeAndID = (Type<<16)|ID; + m_aOffsets[m_NumItems] = m_DataSize; + m_DataSize += sizeof(CSnapshotItem) + Size; + m_NumItems++; + + dbg_assert(m_DataSize < CSnapshot::MAX_SIZE, "too much data"); + dbg_assert(m_NumItems < MAX_ITEMS, "too many items"); + + return pObj->Data(); +} diff --git a/src/engine/e_snapshot.h b/src/engine/shared/snapshot.h index 60dc254c..ec27d004 100644 --- a/src/engine/e_snapshot.h +++ b/src/engine/shared/snapshot.h @@ -1,12 +1,9 @@ -/* copyright (c) 2007 magnus auvinen, see licence.txt for more info */ -#ifndef ENGINE_SNAPSHOT_H -#define ENGINE_SNAPSHOT_H +#ifndef ENGINE_SHARED_SNAPSHOT_H +#define ENGINE_SHARED_SNAPSHOT_H #include <base/system.h> -/* CSnapshot */ - - +// CSnapshot class CSnapshotItem { @@ -19,48 +16,69 @@ public: int Key() { return m_TypeAndID; } }; -class CSnapshotDelta -{ -public: - int m_NumDeletedItems; - int m_NumUpdateItems; - int m_NumTempItems; /* needed? */ - int m_pData[1]; -}; -// TODO: hide a lot of these members class CSnapshot { + friend class CSnapshotBuilder; + int m_DataSize; + int m_NumItems; + + int *Offsets() const { return (int *)(this+1); } + char *DataStart() const { return (char*)(Offsets()+m_NumItems); } + public: enum { MAX_SIZE=64*1024 }; - int m_DataSize; - int m_NumItems; - - int *Offsets() const { return (int *)(this+1); } - char *DataStart() const { return (char*)(Offsets()+m_NumItems); } + void Clear() { m_DataSize = 0; m_NumItems = 0; } + int NumItems() const { return m_NumItems; } CSnapshotItem *GetItem(int Index); int GetItemSize(int Index); int GetItemIndex(int Key); int Crc(); void DebugDump(); +}; - // TODO: move these - int GetItemIndexHashed(int Key); - int GenerateHash(); - - // - static CSnapshotDelta *EmptyDelta(); - static int CreateDelta(CSnapshot *pFrom, CSnapshot *pTo, void *pData); - static int UnpackDelta(CSnapshot *pFrom, CSnapshot *pTo, void *pData, int DataSize); + +// CSnapshotDelta + +class CSnapshotDelta +{ +public: + class CData + { + public: + int m_NumDeletedItems; + int m_NumUpdateItems; + int m_NumTempItems; // needed? + int m_pData[1]; + }; + +private: + // TODO: strange arbitrary number + short m_aItemSizes[64]; + int m_aSnapshotDataRate[0xffff]; + int m_aSnapshotDataUpdates[0xffff]; + int m_SnapshotCurrent; + CData m_Empty; + + void UndiffItem(int *pPast, int *pDiff, int *pOut, int Size); + +public: + CSnapshotDelta(); + int GetDataRate(int Index) { return m_aSnapshotDataRate[Index]; } + int GetDataUpdates(int Index) { return m_aSnapshotDataUpdates[Index]; } + void SetStaticsize(int ItemType, int Size); + CData *EmptyDelta(); + int CreateDelta(class CSnapshot *pFrom, class CSnapshot *pTo, void *pData); + int UnpackDelta(class CSnapshot *pFrom, class CSnapshot *pTo, void *pData, int DataSize); }; -/* CSnapshotStorage */ +// CSnapshotStorage class CSnapshotStorage { @@ -115,4 +133,4 @@ public: }; -#endif /* ENGINE_SNAPSHOT_H */ +#endif // ENGINE_SNAPSHOT_H diff --git a/src/engine/shared/storage.cpp b/src/engine/shared/storage.cpp new file mode 100644 index 00000000..491795ad --- /dev/null +++ b/src/engine/shared/storage.cpp @@ -0,0 +1,196 @@ +// copyright (c) 2007 magnus auvinen, see licence.txt for more info +#include <base/system.h> +#include <engine/storage.h> +#include "engine.h" + +// compiled-in data-dir path +#define DATA_DIR "data" + +class CStorage : public IStorage +{ +public: + char m_aApplicationSavePath[512]; + char m_aDatadir[512]; + + CStorage() + { + m_aApplicationSavePath[0] = 0; + m_aDatadir[0] = 0; + } + + int Init(const char *pApplicationName, const char *pArgv0) + { + char aPath[1024] = {0}; + fs_storage_path(pApplicationName, m_aApplicationSavePath, sizeof(m_aApplicationSavePath)); + if(fs_makedir(m_aApplicationSavePath) == 0) + { + str_format(aPath, sizeof(aPath), "%s/screenshots", m_aApplicationSavePath); + fs_makedir(aPath); + + str_format(aPath, sizeof(aPath), "%s/maps", m_aApplicationSavePath); + fs_makedir(aPath); + + str_format(aPath, sizeof(aPath), "%s/downloadedmaps", m_aApplicationSavePath); + fs_makedir(aPath); + + str_format(aPath, sizeof(aPath), "%s/demos", m_aApplicationSavePath); + fs_makedir(aPath); + } + + return FindDatadir(pArgv0); + } + + int FindDatadir(const char *pArgv0) + { + // 1) use provided data-dir override + if(m_aDatadir[0]) + { + if(fs_is_dir(m_aDatadir)) + return 0; + else + { + dbg_msg("engine/datadir", "specified data-dir '%s' does not exist", m_aDatadir); + return -1; + } + } + + // 2) use data-dir in PWD if present + if(fs_is_dir("data/mapres")) + { + str_copy(m_aDatadir, "data", sizeof(m_aDatadir)); + return 0; + } + + // 3) use compiled-in data-dir if present + if (fs_is_dir(DATA_DIR "/mapres")) + { + str_copy(m_aDatadir, DATA_DIR, sizeof(m_aDatadir)); + return 0; + } + + // 4) check for usable path in argv[0] + { + unsigned int Pos = ~0U; + for(unsigned i = 0; pArgv0[i]; i++) + if(pArgv0[i] == '/') + Pos = i; + + if (Pos < sizeof(m_aDatadir)) + { + char aBaseDir[sizeof(m_aDatadir)]; + str_copy(aBaseDir, pArgv0, Pos); + aBaseDir[Pos] = '\0'; + str_format(m_aDatadir, sizeof(m_aDatadir), "%s/data", aBaseDir); + + if (fs_is_dir(m_aDatadir)) + return 0; + } + } + + #if defined(CONF_FAMILY_UNIX) + // 5) check for all default locations + { + const char *aDirs[] = { + "/usr/share/teeworlds/data", + "/usr/share/games/teeworlds/data", + "/usr/local/share/teeworlds/data", + "/usr/local/share/games/teeworlds/data", + "/opt/teeworlds/data" + }; + const int DirsCount = sizeof(aDirs) / sizeof(aDirs[0]); + + int i; + for (i = 0; i < DirsCount; i++) + { + if (fs_is_dir(aDirs[i])) + { + str_copy(m_aDatadir, aDirs[i], sizeof(m_aDatadir)); + return 0; + } + } + } + #endif + + // no data-dir found + dbg_msg("engine/datadir", "warning no data directory found"); + return -1; + } + + virtual void ListDirectory(int Types, const char *pPath, FS_LISTDIR_CALLBACK pfnCallback, void *pUser) + { + char aBuffer[1024]; + + // list current directory + if(Types&TYPE_CURRENT) + { + fs_listdir(pPath, pfnCallback, pUser); + } + + // list users directory + if(Types&TYPE_SAVE) + { + str_format(aBuffer, sizeof(aBuffer), "%s/%s", m_aApplicationSavePath, pPath); + fs_listdir(aBuffer, pfnCallback, pUser); + } + + // list datadir directory + if(Types&TYPE_DATA) + { + str_format(aBuffer, sizeof(aBuffer), "%s/%s", m_aDatadir, pPath); + fs_listdir(aBuffer, pfnCallback, pUser); + } + } + + virtual IOHANDLE OpenFile(const char *pFilename, int Flags, char *pBuffer = 0, int BufferSize = 0) + { + char aBuffer[1024]; + if(!pBuffer) + { + pBuffer = aBuffer; + BufferSize = sizeof(aBuffer); + } + + if(Flags&IOFLAG_WRITE) + { + str_format(pBuffer, BufferSize, "%s/%s", m_aApplicationSavePath, pFilename); + return io_open(pBuffer, Flags); + } + else + { + IOHANDLE Handle = 0; + + // check current directory + Handle = io_open(pFilename, Flags); + if(Handle) + return Handle; + + // check user directory + str_format(pBuffer, BufferSize, "%s/%s", m_aApplicationSavePath, pFilename); + Handle = io_open(pBuffer, Flags); + if(Handle) + return Handle; + + // check normal data directory + str_format(pBuffer, BufferSize, "%s/%s", m_aDatadir, pFilename); + Handle = io_open(pBuffer, Flags); + if(Handle) + return Handle; + } + + pBuffer[0] = 0; + return 0; + } + + static IStorage *Create(const char *pApplicationName, const char *pArgv0) + { + CStorage *p = new CStorage(); + if(p->Init(pApplicationName, pArgv0)) + { + delete p; + p = 0; + } + return p; + } +}; + +IStorage *CreateStorage(const char *pApplicationName, const char *pArgv0) { return CStorage::Create(pApplicationName, pArgv0); } diff --git a/src/engine/sound.h b/src/engine/sound.h new file mode 100644 index 00000000..bbbc4288 --- /dev/null +++ b/src/engine/sound.h @@ -0,0 +1,40 @@ +#ifndef ENGINE_SOUND_H +#define ENGINE_SOUND_H + +#include "kernel.h" + +class ISound : public IInterface +{ + MACRO_INTERFACE("sound", 0) +public: + enum + { + FLAG_LOOP=1, + FLAG_POS=2, + FLAG_ALL=3 + }; + + virtual int LoadWV(const char *pFilename) = 0; + + virtual void SetChannel(int ChannelId, float Volume, float Panning) = 0; + virtual void SetListenerPos(float x, float y) = 0; + + virtual int PlayAt(int ChannelId, int SoundId, int Flags, float x, float y) = 0; + virtual int Play(int ChannelId, int SoundId, int Flags) = 0; + virtual void Stop(int VoiceId) = 0; + virtual void StopAll() = 0; +}; + + +class IEngineSound : public ISound +{ + MACRO_INTERFACE("enginesound", 0) +public: + virtual int Init() = 0; + virtual int Update() = 0; + virtual int Shutdown() = 0; +}; + +extern IEngineSound *CreateEngineSound(); + +#endif diff --git a/src/engine/storage.h b/src/engine/storage.h new file mode 100644 index 00000000..4f875d40 --- /dev/null +++ b/src/engine/storage.h @@ -0,0 +1,25 @@ +#ifndef ENGINE_STORAGE_H +#define ENGINE_STORAGE_H + +#include "kernel.h" + +class IStorage : public IInterface +{ + MACRO_INTERFACE("storage", 0) +public: + enum + { + TYPE_SAVE = 1, + TYPE_CURRENT = 2, + TYPE_DATA = 4, + TYPE_ALL = ~0 + }; + + virtual void ListDirectory(int Types, const char *pPath, FS_LISTDIR_CALLBACK pfnCallback, void *pUser) = 0; + virtual IOHANDLE OpenFile(const char *pFilename, int Flags, char *pBuffer = 0, int BufferSize = 0) = 0; +}; + +extern IStorage *CreateStorage(const char *pApplicationName, const char *pArgv0); + + +#endif diff --git a/src/engine/textrender.h b/src/engine/textrender.h new file mode 100644 index 00000000..7c7e036b --- /dev/null +++ b/src/engine/textrender.h @@ -0,0 +1,60 @@ +#ifndef ENGINE_TEXTRENDER_H +#define ENGINE_TEXTRENDER_H +#include "kernel.h" + +enum +{ + TEXTFLAG_RENDER=1, + TEXTFLAG_ALLOW_NEWLINE=2, + TEXTFLAG_STOP_AT_END=4 +}; + +class CFont; + +class CTextCursor +{ +public: + int m_Flags; + int m_LineCount; + int m_CharCount; + + float m_StartX; + float m_StartY; + float m_LineWidth; + float m_X, m_Y; + + struct CFont *m_pFont; + float m_FontSize; +}; + +class ITextRender : public IInterface +{ + MACRO_INTERFACE("textrender", 0) +public: + virtual void SetCursor(CTextCursor *pCursor, float x, float y, float FontSize, int Flags) = 0; + + virtual CFont *LoadFont(const char *pFilename) = 0; + virtual void DestroyFont(CFont *pFont) = 0; + + virtual void SetDefaultFont(struct CFont *pFont) = 0; + + // + virtual void TextEx(CTextCursor *pCursor, const char *pText, int Length) = 0; + + // old foolish interface + virtual void TextColor(float r, float g, float b, float a) = 0; + virtual void Text(void *pFontSetV, float x, float y, float Size, const char *pText, int MaxWidth) = 0; + virtual float TextWidth(void *pFontSetV, float Size, const char *pText, int Length) = 0; + virtual float TextLineCount(void *pFontSetV, float Size, const char *pText, int LineWidth) = 0; +}; + +class IEngineTextRender : public ITextRender +{ + MACRO_INTERFACE("enginetextrender", 0) +public: + virtual void Init() = 0; +}; + +extern IEngineTextRender *CreateEngineTextRender(); + +#endif |