about summary refs log tree commit diff
path: root/src/engine/client
diff options
context:
space:
mode:
Diffstat (limited to 'src/engine/client')
-rw-r--r--src/engine/client/client.cpp236
-rw-r--r--src/engine/client/client.h35
-rw-r--r--src/engine/client/friends.cpp1
-rw-r--r--src/engine/client/graphics.cpp11
-rw-r--r--src/engine/client/serverbrowser.cpp68
-rw-r--r--src/engine/client/sound.cpp84
-rw-r--r--src/engine/client/sound.h4
7 files changed, 152 insertions, 287 deletions
diff --git a/src/engine/client/client.cpp b/src/engine/client/client.cpp
index ad51c1ad..bec7d4d6 100644
--- a/src/engine/client/client.cpp
+++ b/src/engine/client/client.cpp
@@ -1,5 +1,6 @@
 /* (c) Magnus Auvinen. See licence.txt in the root of the distribution for more information. */
 /* If you are missing that file, acquire a complete release at teeworlds.com.                */
+#include <new>
 
 #include <stdlib.h> // qsort
 #include <stdarg.h>
@@ -26,6 +27,7 @@
 #include <engine/shared/compression.h>
 #include <engine/shared/datafile.h>
 #include <engine/shared/demo.h>
+#include <engine/shared/filecollection.h>
 #include <engine/shared/mapchecker.h>
 #include <engine/shared/network.h>
 #include <engine/shared/packer.h>
@@ -231,185 +233,6 @@ void CSmoothTime::Update(CGraph *pGraph, int64 Target, int TimeLeft, int AdjustD
 }
 
 
-bool CFileCollection::IsFilenameValid(const char *pFilename)
-{
-	if(str_length(pFilename) != m_FileDescLength+TIMESTAMP_LENGTH+m_FileExtLength ||
-		str_comp_num(pFilename, m_aFileDesc, m_FileDescLength) ||
-		str_comp(pFilename+m_FileDescLength+TIMESTAMP_LENGTH, m_aFileExt))
-		return false;
-
-	pFilename += m_FileDescLength;
-	if(pFilename[0] == '_' &&
-		pFilename[1] >= '0' && pFilename[1] <= '9' &&
-		pFilename[2] >= '0' && pFilename[2] <= '9' &&
-		pFilename[3] >= '0' && pFilename[3] <= '9' &&
-		pFilename[4] >= '0' && pFilename[4] <= '9' &&
-		pFilename[5] == '-' &&
-		pFilename[6] >= '0' && pFilename[6] <= '9' &&
-		pFilename[7] >= '0' && pFilename[7] <= '9' &&
-		pFilename[8] == '-' &&
-		pFilename[9] >= '0' && pFilename[9] <= '9' &&
-		pFilename[10] >= '0' && pFilename[10] <= '9' &&
-		pFilename[11] == '_' &&
-		pFilename[12] >= '0' && pFilename[12] <= '9' &&
-		pFilename[13] >= '0' && pFilename[13] <= '9' &&
-		pFilename[14] == '-' &&
-		pFilename[15] >= '0' && pFilename[15] <= '9' &&
-		pFilename[16] >= '0' && pFilename[16] <= '9' &&
-		pFilename[17] == '-' &&
-		pFilename[18] >= '0' && pFilename[18] <= '9' &&
-		pFilename[19] >= '0' && pFilename[19] <= '9')
-		return true;
-
-	return false;
-}
-
-int64 CFileCollection::ExtractTimestamp(const char *pTimestring)
-{
-	int64 Timestamp = pTimestring[0]-'0'; Timestamp <<= 4;
-	Timestamp += pTimestring[1]-'0'; Timestamp <<= 4;
-	Timestamp += pTimestring[2]-'0'; Timestamp <<= 4;
-	Timestamp += pTimestring[3]-'0'; Timestamp <<= 4;
-	Timestamp += pTimestring[5]-'0'; Timestamp <<= 4;
-	Timestamp += pTimestring[6]-'0'; Timestamp <<= 4;
-	Timestamp += pTimestring[8]-'0'; Timestamp <<= 4;
-	Timestamp += pTimestring[9]-'0'; Timestamp <<= 4;
-	Timestamp += pTimestring[11]-'0'; Timestamp <<= 4;
-	Timestamp += pTimestring[12]-'0'; Timestamp <<= 4;
-	Timestamp += pTimestring[14]-'0'; Timestamp <<= 4;
-	Timestamp += pTimestring[15]-'0'; Timestamp <<= 4;
-	Timestamp += pTimestring[17]-'0'; Timestamp <<= 4;
-	Timestamp += pTimestring[18]-'0';
-
-	return Timestamp;
-}
-
-void CFileCollection::BuildTimestring(int64 Timestamp, char *pTimestring)
-{
-	pTimestring[19] = 0;
-	pTimestring[18] = (Timestamp&0xF)+'0'; Timestamp >>= 4;
-	pTimestring[17] = (Timestamp&0xF)+'0'; Timestamp >>= 4;
-	pTimestring[16] = '-';
-	pTimestring[15] = (Timestamp&0xF)+'0'; Timestamp >>= 4;
-	pTimestring[14] = (Timestamp&0xF)+'0'; Timestamp >>= 4;
-	pTimestring[13] = '-';
-	pTimestring[12] = (Timestamp&0xF)+'0'; Timestamp >>= 4;
-	pTimestring[11] = (Timestamp&0xF)+'0'; Timestamp >>= 4;
-	pTimestring[10] = '_';
-	pTimestring[9] = (Timestamp&0xF)+'0'; Timestamp >>= 4;
-	pTimestring[8] = (Timestamp&0xF)+'0'; Timestamp >>= 4;
-	pTimestring[7] = '-';
-	pTimestring[6] = (Timestamp&0xF)+'0'; Timestamp >>= 4;
-	pTimestring[5] = (Timestamp&0xF)+'0'; Timestamp >>= 4;
-	pTimestring[4] = '-';
-	pTimestring[3] = (Timestamp&0xF)+'0'; Timestamp >>= 4;
-	pTimestring[2] = (Timestamp&0xF)+'0'; Timestamp >>= 4;
-	pTimestring[1] = (Timestamp&0xF)+'0'; Timestamp >>= 4;
-	pTimestring[0] = (Timestamp&0xF)+'0';
-}
-
-void CFileCollection::Init(IStorage *pStorage, const char *pPath, const char *pFileDesc, const char *pFileExt, int MaxEntries)
-{
-	mem_zero(m_aTimestamps, sizeof(m_aTimestamps));
-	m_NumTimestamps = 0;
-	m_MaxEntries = clamp(MaxEntries, 1, static_cast<int>(MAX_ENTRIES));
-	str_copy(m_aFileDesc, pFileDesc, sizeof(m_aFileDesc));
-	m_FileDescLength = str_length(m_aFileDesc);
-	str_copy(m_aFileExt, pFileExt, sizeof(m_aFileExt));
-	m_FileExtLength = str_length(m_aFileExt);
-	str_copy(m_aPath, pPath, sizeof(m_aPath));
-	m_pStorage = pStorage;
-
-	m_pStorage->ListDirectory(IStorage::TYPE_SAVE, m_aPath, FilelistCallback, this);
-}
-
-void CFileCollection::AddEntry(int64 Timestamp)
-{
-	if(m_NumTimestamps == 0)
-	{
-		// empty list
-		m_aTimestamps[m_NumTimestamps++] = Timestamp;
-	}
-	else
-	{
-		// remove old file
-		if(m_NumTimestamps == m_MaxEntries)
-		{
-			char aBuf[512];
-			char aTimestring[TIMESTAMP_LENGTH];
-			BuildTimestring(m_aTimestamps[0], aTimestring);
-			str_format(aBuf, sizeof(aBuf), "%s/%s_%s%s", m_aPath, m_aFileDesc, aTimestring, m_aFileExt);
-			m_pStorage->RemoveFile(aBuf, IStorage::TYPE_SAVE);
-		}
-
-		// add entry to the sorted list
-		if(m_aTimestamps[0] > Timestamp)
-		{
-			// first entry
-			if(m_NumTimestamps < m_MaxEntries)
-			{
-				mem_move(m_aTimestamps+1, m_aTimestamps, m_NumTimestamps*sizeof(int64));
-				m_aTimestamps[0] = Timestamp;
-				++m_NumTimestamps;
-			}
-		}
-		else if(m_aTimestamps[m_NumTimestamps-1] <= Timestamp)
-		{
-			// last entry
-			if(m_NumTimestamps == m_MaxEntries)
-			{
-				mem_move(m_aTimestamps, m_aTimestamps+1, (m_NumTimestamps-1)*sizeof(int64));
-				m_aTimestamps[m_NumTimestamps-1] = Timestamp;
-			}
-			else
-				m_aTimestamps[m_NumTimestamps++] = Timestamp;
-		}
-		else
-		{
-			// middle entry
-			int Left = 0, Right = m_NumTimestamps-1;
-			while(Right-Left > 1)
-			{
-				int Mid = (Left+Right)/2;
-				if(m_aTimestamps[Mid] > Timestamp)
-					Right = Mid;
-				else
-					Left = Mid;
-			}
-
-			if(m_NumTimestamps == m_MaxEntries)
-			{
-				mem_move(m_aTimestamps, m_aTimestamps+1, (Right-1)*sizeof(int64));
-				m_aTimestamps[Right-1] = Timestamp;
-			}
-			else
-			{
-				mem_move(m_aTimestamps+Right+1, m_aTimestamps+Right, (m_NumTimestamps-Right)*sizeof(int64));
-				m_aTimestamps[Right] = Timestamp;
-				++m_NumTimestamps;
-			}
-		}
-	}
-}
-
-int CFileCollection::FilelistCallback(const char *pFilename, int IsDir, int StorageType, void *pUser)
-{
-	CFileCollection *pThis = static_cast<CFileCollection *>(pUser);
-
-	// check for valid file name format
-	if(IsDir || !pThis->IsFilenameValid(pFilename))
-		return 0;
-
-	// extract the timestamp
-	int64 Timestamp = pThis->ExtractTimestamp(pFilename+pThis->m_FileDescLength+1);
-
-	// add the entry
-	pThis->AddEntry(Timestamp);
-
-	return 0;
-}
-
-
 CClient::CClient() : m_DemoPlayer(&m_SnapshotDelta), m_DemoRecorder(&m_SnapshotDelta)
 {
 	m_pEditor = 0;
@@ -467,6 +290,7 @@ CClient::CClient() : m_DemoPlayer(&m_SnapshotDelta), m_DemoRecorder(&m_SnapshotD
 	m_aServerAddressStr[0] = 0;
 
 	mem_zero(m_aSnapshots, sizeof(m_aSnapshots));
+	m_SnapshotStorage.Init();
 	m_RecivedSnapshots = 0;
 
 	m_VersionInfo.m_State = CVersionInfo::STATE_INIT;
@@ -536,11 +360,6 @@ void CClient::SendReady()
 	SendMsgEx(&Msg, MSGFLAG_VITAL|MSGFLAG_FLUSH);
 }
 
-bool CClient::RconAuthed()
-{
-	return m_RconAuthed;
-}
-
 void CClient::RconAuth(const char *pName, const char *pPassword)
 {
 	if(RconAuthed())
@@ -549,6 +368,7 @@ void CClient::RconAuth(const char *pName, const char *pPassword)
 	CMsgPacker Msg(NETMSG_RCON_AUTH);
 	Msg.AddString(pName, 32);
 	Msg.AddString(pPassword, 32);
+	Msg.AddInt(1);
 	SendMsgEx(&Msg, MSGFLAG_VITAL);
 }
 
@@ -693,12 +513,12 @@ void CClient::Connect(const char *pAddress)
 
 	ServerInfoRequest();
 
-	if(net_host_lookup(m_aServerAddressStr, &m_ServerAddress, NETTYPE_ALL) != 0)
+	if(net_host_lookup(m_aServerAddressStr, &m_ServerAddress, m_NetClient.NetType()) != 0)
 	{
 		char aBufMsg[256];
 		str_format(aBufMsg, sizeof(aBufMsg), "could not find the address of %s, connecting to localhost", aBuf);
 		m_pConsole->Print(IConsole::OUTPUT_LEVEL_STANDARD, "client", aBufMsg);
-		net_host_lookup("localhost", &m_ServerAddress, NETTYPE_ALL);
+		net_host_lookup("localhost", &m_ServerAddress, m_NetClient.NetType());
 	}
 
 	m_RconAuthed = 0;
@@ -726,6 +546,7 @@ void CClient::DisconnectWithReason(const char *pReason)
 
 	//
 	m_RconAuthed = 0;
+	m_pConsole->DeregisterTempAll();
 	m_NetClient.Disconnect(pReason);
 	SetState(IClient::STATE_OFFLINE);
 	m_pMap->Unload();
@@ -1149,7 +970,7 @@ void CClient::ProcessConnlessPacket(CNetChunk *pPacket)
 		{
 			str_copy(Info.m_aClients[i].m_aName, Up.GetString(CUnpacker::SANITIZE_CC|CUnpacker::SKIP_START_WHITESPACES), sizeof(Info.m_aClients[i].m_aName));
 			str_copy(Info.m_aClients[i].m_aClan, Up.GetString(CUnpacker::SANITIZE_CC|CUnpacker::SKIP_START_WHITESPACES), sizeof(Info.m_aClients[i].m_aClan));
-			Info.m_aClients[i].m_Country = GameClient()->GetCountryIndex(str_toint(Up.GetString()));
+			Info.m_aClients[i].m_Country = str_toint(Up.GetString());
 			Info.m_aClients[i].m_Score = str_toint(Up.GetString());
 			Info.m_aClients[i].m_Player = str_toint(Up.GetString()) != 0 ? true : false;
 		}
@@ -1313,11 +1134,28 @@ void CClient::ProcessServerPacket(CNetChunk *pPacket)
 			CMsgPacker Msg(NETMSG_PING_REPLY);
 			SendMsgEx(&Msg, 0);
 		}
+		else if(Msg == NETMSG_RCON_CMD_ADD)
+		{
+			const char *pName = Unpacker.GetString(CUnpacker::SANITIZE_CC);
+			const char *pHelp = Unpacker.GetString(CUnpacker::SANITIZE_CC);
+			const char *pParams = Unpacker.GetString(CUnpacker::SANITIZE_CC);
+			if(Unpacker.Error() == 0)
+				m_pConsole->RegisterTemp(pName, pParams, CFGFLAG_SERVER, pHelp);
+		}
+		else if(Msg == NETMSG_RCON_CMD_REM)
+		{
+			const char *pName = Unpacker.GetString(CUnpacker::SANITIZE_CC);
+			if(Unpacker.Error() == 0)
+				m_pConsole->DeregisterTemp(pName);
+		}
 		else if(Msg == NETMSG_RCON_AUTH_STATUS)
 		{
 			int Result = Unpacker.GetInt();
 			if(Unpacker.Error() == 0)
 				m_RconAuthed = Result;
+			m_UseTempRconCommands = Unpacker.GetInt();
+			if(Unpacker.Error() != 0)
+				m_UseTempRconCommands = 0;
 		}
 		else if(Msg == NETMSG_RCON_LINE)
 		{
@@ -1799,7 +1637,7 @@ void CClient::VersionUpdate()
 {
 	if(m_VersionInfo.m_State == CVersionInfo::STATE_INIT)
 	{
-		Engine()->HostLookup(&m_VersionInfo.m_VersionServeraddr, g_Config.m_ClVersionServer, m_BindAddr.type);
+		Engine()->HostLookup(&m_VersionInfo.m_VersionServeraddr, g_Config.m_ClVersionServer, m_NetClient.NetType());
 		m_VersionInfo.m_State = CVersionInfo::STATE_START;
 	}
 	else if(m_VersionInfo.m_State == CVersionInfo::STATE_START)
@@ -1881,7 +1719,7 @@ void CClient::Run()
 	Input()->Init();
 
 	// start refreshing addresses while we load
-	MasterServer()->RefreshAddresses(m_BindAddr.type);
+	MasterServer()->RefreshAddresses(m_NetClient.NetType());
 
 	// init the editor
 	m_pEditor->Init();
@@ -2328,7 +2166,12 @@ void CClient::RegisterCommands()
 	m_pConsole->Chain("br_filter_serveraddress", ConchainServerBrowserUpdate, this);
 }
 
-static CClient m_Client;
+static CClient *CreateClient()
+{
+	CClient *pClient = static_cast<CClient *>(mem_alloc(sizeof(CClient), 1));
+	mem_zero(pClient, sizeof(CClient));
+	return new(pClient) CClient;
+}
 
 /*
 	Server Time
@@ -2359,9 +2202,10 @@ int main(int argc, const char **argv) // ignore_convention
 	}
 #endif
 
+	CClient *pClient = CreateClient();
 	IKernel *pKernel = IKernel::Create();
-	pKernel->RegisterInterface(&m_Client);
-	m_Client.RegisterInterfaces();
+	pKernel->RegisterInterface(pClient);
+	pClient->RegisterInterfaces();
 
 	// create the components
 	IEngine *pEngine = CreateEngine("Teeworlds");
@@ -2414,12 +2258,12 @@ int main(int argc, const char **argv) // ignore_convention
 	pEngineMasterServer->Load();
 
 	// register all console commands
-	m_Client.RegisterCommands();
+	pClient->RegisterCommands();
 
 	pKernel->RequestInterface<IGameClient>()->OnConsoleInit();
 
 	// init client's interfaces
-	m_Client.InitInterfaces();
+	pClient->InitInterfaces();
 
 	// execute config file
 	pConsole->ExecuteFile("settings.cfg");
@@ -2434,11 +2278,11 @@ int main(int argc, const char **argv) // ignore_convention
 	// restore empty config strings to their defaults
 	pConfig->RestoreStrings();
 
-	m_Client.Engine()->InitLogfile();
+	pClient->Engine()->InitLogfile();
 
 	// run the client
 	dbg_msg("client", "starting...");
-	m_Client.Run();
+	pClient->Run();
 
 	// write down the config and quit
 	pConfig->Save();
diff --git a/src/engine/client/client.h b/src/engine/client/client.h
index ddcd1167..1504a4e4 100644
--- a/src/engine/client/client.h
+++ b/src/engine/client/client.h
@@ -51,36 +51,6 @@ public:
 };
 
 
-class CFileCollection
-{
-	enum
-	{
-		MAX_ENTRIES=1000,
-		TIMESTAMP_LENGTH=20,	// _YYYY-MM-DD_HH-MM-SS
-	};
-
-	int64 m_aTimestamps[MAX_ENTRIES];
-	int m_NumTimestamps;
-	int m_MaxEntries;
-	char m_aFileDesc[128];
-	int m_FileDescLength;
-	char m_aFileExt[32];
-	int m_FileExtLength;
-	char m_aPath[512];
-	IStorage *m_pStorage;
-
-	bool IsFilenameValid(const char *pFilename);
-	int64 ExtractTimestamp(const char *pTimestring);
-	void BuildTimestring(int64 Timestamp, char *pTimestring);
-
-public:
-	void Init(IStorage *pStorage, const char *pPath, const char *pFileDesc, const char *pFileExt, int MaxEntries);
-	void AddEntry(int64 Timestamp);
-
-	static int FilelistCallback(const char *pFilename, int IsDir, int StorageType, void *pUser);
-};
-
-
 class CClient : public IClient, public CDemoPlayer::IListner
 {
 	// needed interfaces
@@ -118,7 +88,6 @@ class CClient : public IClient, public CDemoPlayer::IListner
 	float m_FrameTimeHigh;
 	int m_Frames;
 	NETADDR m_ServerAddress;
-	NETADDR m_BindAddr;
 	int m_WindowMustRefocus;
 	int m_SnapCrcErrors;
 	bool m_AutoScreenshotRecycle;
@@ -129,6 +98,7 @@ class CClient : public IClient, public CDemoPlayer::IListner
 	int m_AckGameTick;
 	int m_CurrentRecvTick;
 	int m_RconAuthed;
+	int m_UseTempRconCommands;
 
 	// version-checking
 	char m_aVersionStr[10];
@@ -221,7 +191,8 @@ public:
 	void SendEnterGame();
 	void SendReady();
 
-	virtual bool RconAuthed();
+	virtual bool RconAuthed() { return m_RconAuthed != 0; }
+	virtual bool UseTempRconCommands() { return m_UseTempRconCommands != 0; }
 	void RconAuth(const char *pName, const char *pPassword);
 	virtual void Rcon(const char *pCmd);
 
diff --git a/src/engine/client/friends.cpp b/src/engine/client/friends.cpp
index 99f82b50..eca39edb 100644
--- a/src/engine/client/friends.cpp
+++ b/src/engine/client/friends.cpp
@@ -12,6 +12,7 @@
 CFriends::CFriends()
 {
 	mem_zero(m_aFriends, sizeof(m_aFriends));
+	m_NumFriends = 0;
 }
 
 void CFriends::ConAddFriend(IConsole::IResult *pResult, void *pUserData)
diff --git a/src/engine/client/graphics.cpp b/src/engine/client/graphics.cpp
index 76fd3400..d1f0b8a8 100644
--- a/src/engine/client/graphics.cpp
+++ b/src/engine/client/graphics.cpp
@@ -179,7 +179,16 @@ CGraphics_OpenGL::CGraphics_OpenGL()
 
 void CGraphics_OpenGL::ClipEnable(int x, int y, int w, int h)
 {
-	//if(no_gfx) return;
+	if(x < 0)
+		w += x;
+	if(y < 0)
+		h += y;
+
+	x = clamp(x, 0, ScreenWidth());
+	y = clamp(y, 0, ScreenHeight());
+	w = clamp(w, 0, ScreenWidth()-x);
+	h = clamp(h, 0, ScreenHeight()-y);
+
 	glScissor(x, ScreenHeight()-(y+h), w, h);
 	glEnable(GL_SCISSOR_TEST);
 }
diff --git a/src/engine/client/serverbrowser.cpp b/src/engine/client/serverbrowser.cpp
index c3ada10e..ddfc7597 100644
--- a/src/engine/client/serverbrowser.cpp
+++ b/src/engine/client/serverbrowser.cpp
@@ -186,40 +186,57 @@ void CServerBrowser::Filter()
 			Filtered = 1;
 		else if(!g_Config.m_BrFilterGametypeStrict && g_Config.m_BrFilterGametype[0] && !str_find_nocase(m_ppServerlist[i]->m_Info.m_aGameType, g_Config.m_BrFilterGametype))
 			Filtered = 1;
-		else if(g_Config.m_BrFilterString[0] != 0)
+		else
 		{
-			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))
+			if(g_Config.m_BrFilterCountry)
 			{
-				MatchFound = 1;
-				m_ppServerlist[i]->m_Info.m_QuickSearchHit |= IServerBrowser::QUICK_SERVERNAME;
+				Filtered = 1;
+				// match against player country
+				for(p = 0; p < m_ppServerlist[i]->m_Info.m_NumClients; p++)
+				{
+					if(m_ppServerlist[i]->m_Info.m_aClients[p].m_Country == g_Config.m_BrFilterCountryIndex)
+					{
+						Filtered = 0;
+						break;
+					}
+				}
 			}
 
-			// match against players
-			for(p = 0; p < m_ppServerlist[i]->m_Info.m_NumClients; p++)
+			if(!Filtered && g_Config.m_BrFilterString[0] != 0)
 			{
-				if(str_find_nocase(m_ppServerlist[i]->m_Info.m_aClients[p].m_aName, g_Config.m_BrFilterString) ||
-					str_find_nocase(m_ppServerlist[i]->m_Info.m_aClients[p].m_aClan, g_Config.m_BrFilterString))
+				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_PLAYER;
-					break;
+					m_ppServerlist[i]->m_Info.m_QuickSearchHit |= IServerBrowser::QUICK_SERVERNAME;
 				}
-			}
 
-			// 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;
-			}
+				// match against players
+				for(p = 0; p < m_ppServerlist[i]->m_Info.m_NumClients; p++)
+				{
+					if(str_find_nocase(m_ppServerlist[i]->m_Info.m_aClients[p].m_aName, g_Config.m_BrFilterString) ||
+						str_find_nocase(m_ppServerlist[i]->m_Info.m_aClients[p].m_aClan, g_Config.m_BrFilterString))
+					{
+						MatchFound = 1;
+						m_ppServerlist[i]->m_Info.m_QuickSearchHit |= IServerBrowser::QUICK_PLAYER;
+						break;
+					}
+				}
 
-			if(!MatchFound)
-				Filtered = 1;
+				// 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 == 0)
@@ -252,7 +269,8 @@ int CServerBrowser::SortHash() const
 	i |= g_Config.m_BrFilterPure<<11;
 	i |= g_Config.m_BrFilterPureMap<<12;
 	i |= g_Config.m_BrFilterGametypeStrict<<13;
-	i |= g_Config.m_BrFilterPing<<18;
+	i |= g_Config.m_BrFilterCountry<<14;
+	i |= g_Config.m_BrFilterPing<<15;
 	return i;
 }
 
diff --git a/src/engine/client/sound.cpp b/src/engine/client/sound.cpp
index 4678bb8a..45404d18 100644
--- a/src/engine/client/sound.cpp
+++ b/src/engine/client/sound.cpp
@@ -19,7 +19,7 @@ enum
 	NUM_SAMPLES = 512,
 	NUM_VOICES = 64,
 	NUM_CHANNELS = 16,
-	
+
 	MAX_FRAMES = 1024
 };
 
@@ -31,6 +31,7 @@ struct CSample
 	int m_Channels;
 	int m_LoopStart;
 	int m_LoopEnd;
+	int m_PausedAt;
 };
 
 struct CChannel
@@ -54,7 +55,6 @@ 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;
@@ -89,9 +89,9 @@ static void Mix(short *pFinalOut, unsigned Frames)
 
 	// 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)
@@ -103,7 +103,7 @@ static void Mix(short *pFinalOut, unsigned Frames)
 			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;
@@ -112,7 +112,7 @@ static void Mix(short *pFinalOut, unsigned Frames)
 			// 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;
@@ -126,14 +126,14 @@ static void Mix(short *pFinalOut, unsigned Frames)
 				int dy = v->m_Y - m_CenterY;
 				int Dist = (int)sqrtf((float)dx*dx+dy*dy); // float here. nasty
 				int p = IntAbs(dx);
-				if(Dist < Range)
+				if(Dist >= 0 && 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;
@@ -154,7 +154,7 @@ static void Mix(short *pFinalOut, unsigned Frames)
 				pInR += Step;
 				v->m_Tick++;
 			}
-			
+
 			// free voice if not used any more
 			if(v->m_Tick == v->m_pSample->m_NumFrames)
 			{
@@ -165,8 +165,8 @@ static void Mix(short *pFinalOut, unsigned Frames)
 			}
 		}
 	}
-	
-	
+
+
 	// release the lock
 	lock_release(m_SoundLock);
 
@@ -198,16 +198,17 @@ static void SdlCallback(void *pUnused, Uint8 *pStream, int Len)
 
 int CSound::Init()
 {
+	m_SoundEnabled = 0;
 	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
@@ -228,7 +229,7 @@ int CSound::Init()
 		dbg_msg("client/sound", "sound init successful");
 
 	SDL_PauseAudio(0);
-	
+
 	m_SoundEnabled = 1;
 	Update(); // update the volume
 	return 0;
@@ -238,17 +239,17 @@ 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;
 }
 
@@ -276,7 +277,7 @@ 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;
@@ -284,7 +285,7 @@ void CSound::RateConvert(int SampleID)
 	// 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
@@ -292,7 +293,7 @@ void CSound::RateConvert(int SampleID)
 		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];
@@ -302,7 +303,7 @@ void CSound::RateConvert(int SampleID)
 			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;
@@ -320,15 +321,15 @@ int CSound::LoadWV(const char *pFilename)
 	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;
 
@@ -371,7 +372,7 @@ int CSound::LoadWV(const char *pFilename)
 			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);
@@ -381,7 +382,7 @@ int CSound::LoadWV(const char *pFilename)
 		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;
 
@@ -393,6 +394,7 @@ int CSound::LoadWV(const char *pFilename)
 		pSample->m_NumFrames = m_aSamples;
 		pSample->m_LoopStart = -1;
 		pSample->m_LoopEnd = -1;
+		pSample->m_PausedAt = 0;
 	}
 	else
 	{
@@ -426,9 +428,9 @@ 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++)
 	{
@@ -440,19 +442,22 @@ int CSound::Play(int ChannelID, int SampleID, int Flags, float x, float y)
 			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;
+		if(Flags & FLAG_LOOP)
+			m_aVoices[VoiceID].m_Tick = m_aSamples[SampleID].m_PausedAt;
+		else
+			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;
 }
@@ -475,7 +480,13 @@ void CSound::Stop(int SampleID)
 	for(int i = 0; i < NUM_VOICES; i++)
 	{
 		if(m_aVoices[i].m_pSample == pSample)
+		{
+			if(m_aVoices[i].m_Flags & FLAG_LOOP)
+				m_aVoices[i].m_pSample->m_PausedAt = m_aVoices[i].m_Tick;
+			else
+				m_aVoices[i].m_pSample->m_PausedAt = 0;
 			m_aVoices[i].m_pSample = 0;
+		}
 	}
 	lock_release(m_SoundLock);
 }
@@ -486,6 +497,13 @@ void CSound::StopAll()
 	lock_wait(m_SoundLock);
 	for(int i = 0; i < NUM_VOICES; i++)
 	{
+		if(m_aVoices[i].m_pSample)
+		{
+			if(m_aVoices[i].m_Flags & FLAG_LOOP)
+				m_aVoices[i].m_pSample->m_PausedAt = m_aVoices[i].m_Tick;
+			else
+				m_aVoices[i].m_pSample->m_PausedAt = 0;
+		}
 		m_aVoices[i].m_pSample = 0;
 	}
 	lock_release(m_SoundLock);
diff --git a/src/engine/client/sound.h b/src/engine/client/sound.h
index 3cc84d4d..8112427c 100644
--- a/src/engine/client/sound.h
+++ b/src/engine/client/sound.h
@@ -7,6 +7,8 @@
 
 class CSound : public IEngineSound
 {
+	int m_SoundEnabled;
+
 public:
 	IEngineGraphics *m_pGraphics;
 	IStorage *m_pStorage;
@@ -23,6 +25,8 @@ public:
 	static IOHANDLE ms_File;
 	static int ReadData(void *pBuffer, int Size);
 
+	virtual bool IsSoundEnabled() { return m_SoundEnabled != 0; }
+
 	virtual int LoadWV(const char *pFilename);
 
 	virtual void SetListenerPos(float x, float y);