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.cpp240
-rw-r--r--src/engine/client/client.h35
-rw-r--r--src/engine/client/friends.cpp39
-rw-r--r--src/engine/client/friends.h1
-rw-r--r--src/engine/client/graphics.cpp100
-rw-r--r--src/engine/client/graphics.h3
-rw-r--r--src/engine/client/serverbrowser.cpp116
-rw-r--r--src/engine/client/sound.cpp22
-rw-r--r--src/engine/client/sound.h4
-rw-r--r--src/engine/client/text.cpp3
10 files changed, 238 insertions, 325 deletions
diff --git a/src/engine/client/client.cpp b/src/engine/client/client.cpp
index 61a96eee..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)
 		{
@@ -1718,7 +1556,6 @@ void CClient::Update()
 			int64 PrevtickStart = (m_aSnapshots[SNAP_PREV]->m_Tick)*time_freq()/50;
 			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);
@@ -1735,15 +1572,12 @@ void CClient::Update()
 
 			if(NewPredTick > m_PredTick)
 			{
-				LastPredintra = m_PredIntraTick;
 				m_PredTick = NewPredTick;
 				Repredict = 1;
 
 				// send input
 				SendInput();
 			}
-
-			LastPredintra = m_PredIntraTick;
 		}
 
 		// only do sane predictions
@@ -1803,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)
@@ -1885,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();
@@ -2332,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
@@ -2363,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");
@@ -2418,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");
@@ -2438,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 9ef00ed1..eca39edb 100644
--- a/src/engine/client/friends.cpp
+++ b/src/engine/client/friends.cpp
@@ -1,6 +1,7 @@
 /* (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 <base/math.h>
+#include <base/system.h>
 
 #include <engine/config.h>
 #include <engine/console.h>
@@ -11,6 +12,7 @@
 CFriends::CFriends()
 {
 	mem_zero(m_aFriends, sizeof(m_aFriends));
+	m_NumFriends = 0;
 }
 
 void CFriends::ConAddFriend(IConsole::IResult *pResult, void *pUserData)
@@ -44,12 +46,35 @@ const CFriendInfo *CFriends::GetFriend(int Index) const
 	return &m_aFriends[max(0, Index%m_NumFriends)];
 }
 
+int CFriends::GetFriendState(const char *pName, const char *pClan) const
+{
+	int Result = FRIEND_NO;
+	unsigned NameHash = str_quickhash(pName);
+	unsigned ClanHash = str_quickhash(pClan);
+	for(int i = 0; i < m_NumFriends; ++i)
+	{
+		if(m_aFriends[i].m_ClanHash == ClanHash)
+		{
+			if(m_aFriends[i].m_aName[0] == 0)
+				Result = FRIEND_CLAN;
+			else if(m_aFriends[i].m_NameHash == NameHash)
+			{
+				Result = FRIEND_PLAYER;
+				break;
+			}
+		}
+	}
+	return Result;
+}
+
 bool CFriends::IsFriend(const char *pName, const char *pClan, bool PlayersOnly) const
 {
+	unsigned NameHash = str_quickhash(pName);
+	unsigned ClanHash = str_quickhash(pClan);
 	for(int i = 0; i < m_NumFriends; ++i)
 	{
-		if(!str_comp(m_aFriends[i].m_aClan, pClan) &&
-			((!PlayersOnly && m_aFriends[i].m_aName[0] == 0) || !str_comp(m_aFriends[i].m_aName, pName)))
+		if(m_aFriends[i].m_ClanHash == ClanHash &&
+			((!PlayersOnly && m_aFriends[i].m_aName[0] == 0) || m_aFriends[i].m_NameHash == NameHash))
 			return true;
 	}
 	return false;
@@ -61,22 +86,28 @@ void CFriends::AddFriend(const char *pName, const char *pClan)
 		return;
 
 	// make sure we don't have the friend already
+	unsigned NameHash = str_quickhash(pName);
+	unsigned ClanHash = str_quickhash(pClan);
 	for(int i = 0; i < m_NumFriends; ++i)
 	{
-		if(!str_comp(m_aFriends[i].m_aName, pName) && !str_comp(m_aFriends[i].m_aClan, pClan))
+		if(m_aFriends[i].m_NameHash == NameHash && m_aFriends[i].m_ClanHash == ClanHash)
 			return;
 	}
 
 	str_copy(m_aFriends[m_NumFriends].m_aName, pName, sizeof(m_aFriends[m_NumFriends].m_aName));
 	str_copy(m_aFriends[m_NumFriends].m_aClan, pClan, sizeof(m_aFriends[m_NumFriends].m_aClan));
+	m_aFriends[m_NumFriends].m_NameHash = NameHash;
+	m_aFriends[m_NumFriends].m_ClanHash = ClanHash;
 	++m_NumFriends;
 }
 
 void CFriends::RemoveFriend(const char *pName, const char *pClan)
 {
+	unsigned NameHash = str_quickhash(pName);
+	unsigned ClanHash = str_quickhash(pClan);
 	for(int i = 0; i < m_NumFriends; ++i)
 	{
-		if(!str_comp(m_aFriends[i].m_aName, pName) && !str_comp(m_aFriends[i].m_aClan, pClan))
+		if(m_aFriends[i].m_NameHash == NameHash && m_aFriends[i].m_ClanHash == ClanHash)
 		{
 			RemoveFriend(i);
 			return;
diff --git a/src/engine/client/friends.h b/src/engine/client/friends.h
index be0cfa49..d4c539e1 100644
--- a/src/engine/client/friends.h
+++ b/src/engine/client/friends.h
@@ -22,6 +22,7 @@ public:
 
 	int NumFriends() const { return m_NumFriends; }
 	const CFriendInfo *GetFriend(int Index) const;
+	int GetFriendState(const char *pName, const char *pClan) const;
 	bool IsFriend(const char *pName, const char *pClan, bool PlayersOnly) const;
 
 	void AddFriend(const char *pName, const char *pClan);
diff --git a/src/engine/client/graphics.cpp b/src/engine/client/graphics.cpp
index 641f9dfb..d1f0b8a8 100644
--- a/src/engine/client/graphics.cpp
+++ b/src/engine/client/graphics.cpp
@@ -2,6 +2,7 @@
 /* If you are missing that file, acquire a complete release at teeworlds.com.                */
 
 #include <base/detect.h>
+#include <base/math.h>
 
 #include "SDL.h"
 
@@ -119,12 +120,39 @@ void CGraphics_OpenGL::Rotate4(const CPoint &rCenter, CVertex *pPoints)
 	}
 }
 
-unsigned char CGraphics_OpenGL::Sample(int w, int h, const unsigned char *pData, int u, int v, int Offset)
+unsigned char CGraphics_OpenGL::Sample(int w, int h, const unsigned char *pData, int u, int v, int Offset, int ScaleW, int ScaleH, int Bpp)
 {
-	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;
+	int Value = 0;
+	for(int x = 0; x < ScaleW; x++)
+		for(int y = 0; y < ScaleH; y++)
+			Value += pData[((v+y)*w+(u+x))*Bpp+Offset];
+	return Value/(ScaleW*ScaleH);
+}
+
+unsigned char *CGraphics_OpenGL::Rescale(int Width, int Height, int NewWidth, int NewHeight, int Format, const unsigned char *pData)
+{
+	unsigned char *pTmpData;
+	int ScaleW = Width/NewWidth;
+	int ScaleH = Height/NewHeight;
+
+	int Bpp = 3;
+	if(Format == CImageInfo::FORMAT_RGBA)
+		Bpp = 4;
+
+	pTmpData = (unsigned char *)mem_alloc(NewWidth*NewHeight*Bpp, 1);
+
+	int c = 0;
+	for(int y = 0; y < NewHeight; y++)
+		for(int x = 0; x < NewWidth; x++, c++)
+		{
+			pTmpData[c*Bpp] = Sample(Width, Height, pData, x*ScaleW, y*ScaleH, 0, ScaleW, ScaleH, Bpp);
+			pTmpData[c*Bpp+1] = Sample(Width, Height, pData, x*ScaleW, y*ScaleH, 1, ScaleW, ScaleH, Bpp);
+			pTmpData[c*Bpp+2] = Sample(Width, Height, pData, x*ScaleW, y*ScaleH, 2, ScaleW, ScaleH, Bpp);
+			if(Bpp == 4)
+				pTmpData[c*Bpp+3] = Sample(Width, Height, pData, x*ScaleW, y*ScaleH, 3, ScaleW, ScaleH, Bpp);
+		}
+
+	return pTmpData;
 }
 
 CGraphics_OpenGL::CGraphics_OpenGL()
@@ -151,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);
 }
@@ -205,21 +242,21 @@ void CGraphics_OpenGL::GetScreen(float *pTopLeftX, float *pTopLeftY, float *pBot
 
 void CGraphics_OpenGL::LinesBegin()
 {
-	dbg_assert(m_Drawing == 0, "called begin twice");
+	dbg_assert(m_Drawing == 0, "called Graphics()->LinesBegin twice");
 	m_Drawing = DRAWING_LINES;
 	SetColor(1,1,1,1);
 }
 
 void CGraphics_OpenGL::LinesEnd()
 {
-	dbg_assert(m_Drawing == DRAWING_LINES, "called end without begin");
+	dbg_assert(m_Drawing == DRAWING_LINES, "called Graphics()->LinesEnd 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");
+	dbg_assert(m_Drawing == DRAWING_LINES, "called Graphics()->LinesDraw without begin");
 
 	for(int i = 0; i < Num; ++i)
 	{
@@ -272,28 +309,23 @@ int CGraphics_OpenGL::LoadTextureRaw(int Width, int Height, int Format, const vo
 	m_aTextures[Tex].m_Next = -1;
 
 	// resample if needed
-	if(!(Flags&TEXLOAD_NORESAMPLE) && g_Config.m_GfxTextureQuality==0)
+	if(!(Flags&TEXLOAD_NORESAMPLE) && (Format == CImageInfo::FORMAT_RGBA || Format == CImageInfo::FORMAT_RGB))
 	{
-		if(Width > 16 && Height > 16 && Format == CImageInfo::FORMAT_RGBA)
+		if(Width > GL_MAX_TEXTURE_SIZE || Height > GL_MAX_TEXTURE_SIZE)
+		{
+			int NewWidth = min(Width, GL_MAX_TEXTURE_SIZE);
+			int NewHeight = min(Height, GL_MAX_TEXTURE_SIZE);
+			pTmpData = Rescale(Width, Height, NewWidth, NewHeight, Format, pTexData);
+			pTexData = pTmpData;
+			Width = NewWidth;
+			Height = NewHeight;
+		}
+		else if(Width > 16 && Height > 16 && g_Config.m_GfxTextureQuality == 0)
 		{
-			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);
-				}
+			pTmpData = Rescale(Width, Height, Width/2, Height/2, Format, pTexData);
 			pTexData = pTmpData;
+			Width /= 2;
+			Height /= 2;
 		}
 	}
 
@@ -488,7 +520,7 @@ void CGraphics_OpenGL::Clear(float r, float g, float b)
 
 void CGraphics_OpenGL::QuadsBegin()
 {
-	dbg_assert(m_Drawing == 0, "called quads_begin twice");
+	dbg_assert(m_Drawing == 0, "called Graphics()->QuadsBegin twice");
 	m_Drawing = DRAWING_QUADS;
 
 	QuadsSetSubset(0,0,1,1);
@@ -498,7 +530,7 @@ void CGraphics_OpenGL::QuadsBegin()
 
 void CGraphics_OpenGL::QuadsEnd()
 {
-	dbg_assert(m_Drawing == DRAWING_QUADS, "called quads_end without begin");
+	dbg_assert(m_Drawing == DRAWING_QUADS, "called Graphics()->QuadsEnd without begin");
 	Flush();
 	m_Drawing = 0;
 }
@@ -511,7 +543,7 @@ void CGraphics_OpenGL::QuadsSetRotation(float Angle)
 
 void CGraphics_OpenGL::SetColorVertex(const CColorVertex *pArray, int Num)
 {
-	dbg_assert(m_Drawing != 0, "called gfx_quads_setcolorvertex without begin");
+	dbg_assert(m_Drawing != 0, "called Graphics()->SetColorVertex without begin");
 
 	for(int i = 0; i < Num; ++i)
 	{
@@ -524,7 +556,7 @@ void CGraphics_OpenGL::SetColorVertex(const CColorVertex *pArray, int Num)
 
 void CGraphics_OpenGL::SetColor(float r, float g, float b, float a)
 {
-	dbg_assert(m_Drawing != 0, "called gfx_quads_setcolor without begin");
+	dbg_assert(m_Drawing != 0, "called Graphics()->SetColor without begin");
 	CColorVertex Array[4] = {
 		CColorVertex(0, r, g, b, a),
 		CColorVertex(1, r, g, b, a),
@@ -570,7 +602,7 @@ void CGraphics_OpenGL::QuadsDrawTL(const CQuadItem *pArray, int Num)
 	CPoint Center;
 	Center.z = 0;
 
-	dbg_assert(m_Drawing == DRAWING_QUADS, "called quads_draw without begin");
+	dbg_assert(m_Drawing == DRAWING_QUADS, "called Graphics()->QuadsDrawTL without begin");
 
 	for(int i = 0; i < Num; ++i)
 	{
@@ -608,7 +640,7 @@ void CGraphics_OpenGL::QuadsDrawTL(const CQuadItem *pArray, int Num)
 
 void CGraphics_OpenGL::QuadsDrawFreeform(const CFreeformItem *pArray, int Num)
 {
-	dbg_assert(m_Drawing == DRAWING_QUADS, "called quads_draw_freeform without begin");
+	dbg_assert(m_Drawing == DRAWING_QUADS, "called Graphics()->QuadsDrawFreeform without begin");
 
 	for(int i = 0; i < Num; ++i)
 	{
diff --git a/src/engine/client/graphics.h b/src/engine/client/graphics.h
index 4367ccfc..95e9769a 100644
--- a/src/engine/client/graphics.h
+++ b/src/engine/client/graphics.h
@@ -66,7 +66,8 @@ protected:
 	void AddVertices(int Count);
 	void Rotate4(const CPoint &rCenter, CVertex *pPoints);
 
-	static unsigned char Sample(int w, int h, const unsigned char *pData, int u, int v, int Offset);
+	static unsigned char Sample(int w, int h, const unsigned char *pData, int u, int v, int Offset, int ScaleW, int ScaleH, int Bpp);
+	static unsigned char *Rescale(int Width, int Height, int NewWidth, int NewHeight, int Format, const unsigned char *pData);
 public:
 	CGraphics_OpenGL();
 
diff --git a/src/engine/client/serverbrowser.cpp b/src/engine/client/serverbrowser.cpp
index 64d06116..ddfc7597 100644
--- a/src/engine/client/serverbrowser.cpp
+++ b/src/engine/client/serverbrowser.cpp
@@ -144,61 +144,65 @@ void CServerBrowser::Filter()
 	{
 		int Filtered = 0;
 
-		if(g_Config.m_BrFilterFriends)
+		if(g_Config.m_BrFilterEmpty && ((g_Config.m_BrFilterSpectators && m_ppServerlist[i]->m_Info.m_NumPlayers == 0) || m_ppServerlist[i]->m_Info.m_NumClients == 0))
+			Filtered = 1;
+		else if(g_Config.m_BrFilterFull && ((g_Config.m_BrFilterSpectators && m_ppServerlist[i]->m_Info.m_NumPlayers == m_ppServerlist[i]->m_Info.m_MaxPlayers) ||
+				m_ppServerlist[i]->m_Info.m_NumClients == m_ppServerlist[i]->m_Info.m_MaxClients))
+			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 ||
+			str_comp(m_ppServerlist[i]->m_Info.m_aMap, "ctf6") == 0 ||
+			str_comp(m_ppServerlist[i]->m_Info.m_aMap, "ctf7") == 0)
+		)
 		{
 			Filtered = 1;
-			for(p = 0; p < m_ppServerlist[i]->m_Info.m_NumClients; p++)
-			{
-				if(m_pFriends->IsFriend(m_ppServerlist[i]->m_Info.m_aClients[p].m_aName, m_ppServerlist[i]->m_Info.m_aClients[p].m_aClan, false))
-				{
-					Filtered = 0;
-					break;
-				}
-			}
 		}
+		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_BrFilterServerAddress[0] && !str_find_nocase(m_ppServerlist[i]->m_Info.m_aAddress, g_Config.m_BrFilterServerAddress))
+			Filtered = 1;
+		else if(g_Config.m_BrFilterGametypeStrict && g_Config.m_BrFilterGametype[0] && str_comp_nocase(m_ppServerlist[i]->m_Info.m_aGameType, g_Config.m_BrFilterGametype))
+			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_BrFilterEmpty && ((g_Config.m_BrFilterSpectators && m_ppServerlist[i]->m_Info.m_NumPlayers == 0) || m_ppServerlist[i]->m_Info.m_NumClients == 0))
-				Filtered = 1;
-			else if(g_Config.m_BrFilterFull && ((g_Config.m_BrFilterSpectators && m_ppServerlist[i]->m_Info.m_NumPlayers == m_ppServerlist[i]->m_Info.m_MaxPlayers) ||
-					m_ppServerlist[i]->m_Info.m_NumClients == m_ppServerlist[i]->m_Info.m_MaxClients))
-				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 ||
-				str_comp(m_ppServerlist[i]->m_Info.m_aMap, "ctf6") == 0 ||
-				str_comp(m_ppServerlist[i]->m_Info.m_aMap, "ctf7") == 0)
-			)
+			if(g_Config.m_BrFilterCountry)
 			{
 				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;
+					}
+				}
 			}
-			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_BrFilterServerAddress[0] && !str_find_nocase(m_ppServerlist[i]->m_Info.m_aAddress, g_Config.m_BrFilterServerAddress))
-				Filtered = 1;
-			else if(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)
+
+			if(!Filtered && g_Config.m_BrFilterString[0] != 0)
 			{
 				int MatchFound = 0;
 
@@ -236,7 +240,19 @@ void CServerBrowser::Filter()
 		}
 
 		if(Filtered == 0)
-			m_pSortedServerlist[m_NumSortedServers++] = i;
+		{
+			// check for friend
+			m_ppServerlist[i]->m_Info.m_FriendState = IFriends::FRIEND_NO;
+			for(p = 0; p < m_ppServerlist[i]->m_Info.m_NumClients; p++)
+			{
+				m_ppServerlist[i]->m_Info.m_aClients[p].m_FriendState = m_pFriends->GetFriendState(m_ppServerlist[i]->m_Info.m_aClients[p].m_aName,
+					m_ppServerlist[i]->m_Info.m_aClients[p].m_aClan);
+				m_ppServerlist[i]->m_Info.m_FriendState = max(m_ppServerlist[i]->m_Info.m_FriendState, m_ppServerlist[i]->m_Info.m_aClients[p].m_FriendState);
+			}
+
+			if(!g_Config.m_BrFilterFriends || m_ppServerlist[i]->m_Info.m_FriendState != IFriends::FRIEND_NO)
+				m_pSortedServerlist[m_NumSortedServers++] = i;
+		}
 	}
 }
 
@@ -252,7 +268,9 @@ int CServerBrowser::SortHash() const
 	i |= g_Config.m_BrFilterCompatversion<<10;
 	i |= g_Config.m_BrFilterPure<<11;
 	i |= g_Config.m_BrFilterPureMap<<12;
-	i |= g_Config.m_BrFilterPing<<18;
+	i |= g_Config.m_BrFilterGametypeStrict<<13;
+	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..c2ca91f7 100644
--- a/src/engine/client/sound.cpp
+++ b/src/engine/client/sound.cpp
@@ -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;
@@ -198,6 +198,7 @@ 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>();
 	
@@ -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
 	{
@@ -446,7 +448,10 @@ int CSound::Play(int ChannelID, int SampleID, int Flags, float x, float y)
 	{
 		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;
@@ -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);
diff --git a/src/engine/client/text.cpp b/src/engine/client/text.cpp
index b0896b92..4a3a2eb3 100644
--- a/src/engine/client/text.cpp
+++ b/src/engine/client/text.cpp
@@ -570,7 +570,6 @@ public:
 		float DrawX = 0.0f, DrawY = 0.0f;
 		int LineCount = 0;
 		float CursorX, CursorY;
-		const char *pEnd;
 
 		float Size = pCursor->m_FontSize;
 
@@ -605,8 +604,6 @@ public:
 		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)