diff options
| author | Magnus Auvinen <magnus.auvinen@gmail.com> | 2010-05-29 07:25:38 +0000 |
|---|---|---|
| committer | Magnus Auvinen <magnus.auvinen@gmail.com> | 2010-05-29 07:25:38 +0000 |
| commit | 72c06a258940696093f255fb1061beb58e1cdd0b (patch) | |
| tree | 36b9a7712eec2d4f07837eab9c38ef1c5af85319 /src/engine/client | |
| parent | e56feb597bc743677633432f77513b02907fd169 (diff) | |
| download | zcatch-72c06a258940696093f255fb1061beb58e1cdd0b.tar.gz zcatch-72c06a258940696093f255fb1061beb58e1cdd0b.zip | |
copied refactor to trunk
Diffstat (limited to 'src/engine/client')
| -rw-r--r-- | src/engine/client/client.cpp | 2062 | ||||
| -rw-r--r-- | src/engine/client/client.h | 286 | ||||
| -rw-r--r-- | src/engine/client/ec_client.cpp | 2087 | ||||
| -rw-r--r-- | src/engine/client/ec_gfx.cpp | 992 | ||||
| -rw-r--r-- | src/engine/client/ec_gfx_text.cpp | 669 | ||||
| -rw-r--r-- | src/engine/client/ec_inp.cpp | 234 | ||||
| -rw-r--r-- | src/engine/client/ec_snd.cpp | 471 | ||||
| -rw-r--r-- | src/engine/client/ec_srvbrowse.cpp | 736 | ||||
| -rw-r--r-- | src/engine/client/editor.h | 10 | ||||
| -rw-r--r-- | src/engine/client/graphics.cpp | 938 | ||||
| -rw-r--r-- | src/engine/client/graphics.h | 180 | ||||
| -rw-r--r-- | src/engine/client/input.cpp | 208 | ||||
| -rw-r--r-- | src/engine/client/input.h | 37 | ||||
| -rw-r--r-- | src/engine/client/keynames.h | 524 | ||||
| -rw-r--r-- | src/engine/client/sound.cpp | 485 | ||||
| -rw-r--r-- | src/engine/client/sound.h | 39 | ||||
| -rw-r--r-- | src/engine/client/srvbrowse.cpp | 721 | ||||
| -rw-r--r-- | src/engine/client/srvbrowse.h | 111 | ||||
| -rw-r--r-- | src/engine/client/text.cpp | 718 |
19 files changed, 6250 insertions, 5258 deletions
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/client/keynames.h b/src/engine/client/keynames.h new file mode 100644 index 00000000..2f159a5a --- /dev/null +++ b/src/engine/client/keynames.h @@ -0,0 +1,524 @@ +/* AUTO GENERATED! DO NOT EDIT MANUALLY! */ + +#ifndef KEYS_INCLUDE +#error do not include this header! +#endif + +#include <string.h> + +const char g_aaKeyStrings[512][16] = +{ + "first", + "&1", + "&2", + "&3", + "&4", + "&5", + "&6", + "&7", + "backspace", + "tab", + "&10", + "&11", + "clear", + "return", + "&14", + "&15", + "&16", + "&17", + "&18", + "pause", + "&20", + "&21", + "&22", + "&23", + "&24", + "&25", + "&26", + "escape", + "&28", + "&29", + "&30", + "&31", + "space", + "exclaim", + "quotedbl", + "hash", + "dollar", + "&37", + "ampersand", + "quote", + "leftparen", + "rightparen", + "asterisk", + "plus", + "comma", + "minus", + "period", + "slash", + "0", + "1", + "2", + "3", + "4", + "5", + "6", + "7", + "8", + "9", + "colon", + "semicolon", + "less", + "equals", + "greater", + "question", + "at", + "&65", + "&66", + "&67", + "&68", + "&69", + "&70", + "&71", + "&72", + "&73", + "&74", + "&75", + "&76", + "&77", + "&78", + "&79", + "&80", + "&81", + "&82", + "&83", + "&84", + "&85", + "&86", + "&87", + "&88", + "&89", + "&90", + "leftbracket", + "backslash", + "rightbracket", + "caret", + "underscore", + "backquote", + "a", + "b", + "c", + "d", + "e", + "f", + "g", + "h", + "i", + "j", + "k", + "l", + "m", + "n", + "o", + "p", + "q", + "r", + "s", + "t", + "u", + "v", + "w", + "x", + "y", + "z", + "&123", + "&124", + "&125", + "&126", + "delete", + "&128", + "&129", + "&130", + "&131", + "&132", + "&133", + "&134", + "&135", + "&136", + "&137", + "&138", + "&139", + "&140", + "&141", + "&142", + "&143", + "&144", + "&145", + "&146", + "&147", + "&148", + "&149", + "&150", + "&151", + "&152", + "&153", + "&154", + "&155", + "&156", + "&157", + "&158", + "&159", + "world_0", + "world_1", + "world_2", + "world_3", + "world_4", + "world_5", + "world_6", + "world_7", + "world_8", + "world_9", + "world_10", + "world_11", + "world_12", + "world_13", + "world_14", + "world_15", + "world_16", + "world_17", + "world_18", + "world_19", + "world_20", + "world_21", + "world_22", + "world_23", + "world_24", + "world_25", + "world_26", + "world_27", + "world_28", + "world_29", + "world_30", + "world_31", + "world_32", + "world_33", + "world_34", + "world_35", + "world_36", + "world_37", + "world_38", + "world_39", + "world_40", + "world_41", + "world_42", + "world_43", + "world_44", + "world_45", + "world_46", + "world_47", + "world_48", + "world_49", + "world_50", + "world_51", + "world_52", + "world_53", + "world_54", + "world_55", + "world_56", + "world_57", + "world_58", + "world_59", + "world_60", + "world_61", + "world_62", + "world_63", + "world_64", + "world_65", + "world_66", + "world_67", + "world_68", + "world_69", + "world_70", + "world_71", + "world_72", + "world_73", + "world_74", + "world_75", + "world_76", + "world_77", + "world_78", + "world_79", + "world_80", + "world_81", + "world_82", + "world_83", + "world_84", + "world_85", + "world_86", + "world_87", + "world_88", + "world_89", + "world_90", + "world_91", + "world_92", + "world_93", + "world_94", + "world_95", + "kp0", + "kp1", + "kp2", + "kp3", + "kp4", + "kp5", + "kp6", + "kp7", + "kp8", + "kp9", + "kp_period", + "kp_divide", + "kp_multiply", + "kp_minus", + "kp_plus", + "kp_enter", + "kp_equals", + "up", + "down", + "right", + "left", + "insert", + "home", + "end", + "pageup", + "pagedown", + "f1", + "f2", + "f3", + "f4", + "f5", + "f6", + "f7", + "f8", + "f9", + "f10", + "f11", + "f12", + "f13", + "f14", + "f15", + "&297", + "&298", + "&299", + "numlock", + "capslock", + "scrollock", + "rshift", + "lshift", + "rctrl", + "lctrl", + "ralt", + "lalt", + "rmeta", + "lmeta", + "lsuper", + "rsuper", + "mode", + "compose", + "help", + "print", + "sysreq", + "break", + "menu", + "power", + "euro", + "undo", + "mouse1", + "mouse2", + "mouse3", + "mouse4", + "mouse5", + "mouse6", + "mouse7", + "mouse8", + "mousewheelup", + "mousewheeldown", + "&333", + "&334", + "&335", + "&336", + "&337", + "&338", + "&339", + "&340", + "&341", + "&342", + "&343", + "&344", + "&345", + "&346", + "&347", + "&348", + "&349", + "&350", + "&351", + "&352", + "&353", + "&354", + "&355", + "&356", + "&357", + "&358", + "&359", + "&360", + "&361", + "&362", + "&363", + "&364", + "&365", + "&366", + "&367", + "&368", + "&369", + "&370", + "&371", + "&372", + "&373", + "&374", + "&375", + "&376", + "&377", + "&378", + "&379", + "&380", + "&381", + "&382", + "&383", + "&384", + "&385", + "&386", + "&387", + "&388", + "&389", + "&390", + "&391", + "&392", + "&393", + "&394", + "&395", + "&396", + "&397", + "&398", + "&399", + "&400", + "&401", + "&402", + "&403", + "&404", + "&405", + "&406", + "&407", + "&408", + "&409", + "&410", + "&411", + "&412", + "&413", + "&414", + "&415", + "&416", + "&417", + "&418", + "&419", + "&420", + "&421", + "&422", + "&423", + "&424", + "&425", + "&426", + "&427", + "&428", + "&429", + "&430", + "&431", + "&432", + "&433", + "&434", + "&435", + "&436", + "&437", + "&438", + "&439", + "&440", + "&441", + "&442", + "&443", + "&444", + "&445", + "&446", + "&447", + "&448", + "&449", + "&450", + "&451", + "&452", + "&453", + "&454", + "&455", + "&456", + "&457", + "&458", + "&459", + "&460", + "&461", + "&462", + "&463", + "&464", + "&465", + "&466", + "&467", + "&468", + "&469", + "&470", + "&471", + "&472", + "&473", + "&474", + "&475", + "&476", + "&477", + "&478", + "&479", + "&480", + "&481", + "&482", + "&483", + "&484", + "&485", + "&486", + "&487", + "&488", + "&489", + "&490", + "&491", + "&492", + "&493", + "&494", + "&495", + "&496", + "&497", + "&498", + "&499", + "&500", + "&501", + "&502", + "&503", + "&504", + "&505", + "&506", + "&507", + "&508", + "&509", + "&510", + "&511", +}; + 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; } |