about summary refs log tree commit diff
diff options
context:
space:
mode:
authoroy <Tom_Adams@web.de>2011-06-26 17:10:13 +0200
committeroy <Tom_Adams@web.de>2011-06-26 17:10:13 +0200
commitfb309436b4794743c86886fddf39bfae315fdc1a (patch)
treef3b98af0c833884a0b5644ffbf25fdfa65dc0852
parentc10c7d9ac3410e103b2d2e5b69fb6d7833f6a2e3 (diff)
downloadzcatch-fb309436b4794743c86886fddf39bfae315fdc1a.tar.gz
zcatch-fb309436b4794743c86886fddf39bfae315fdc1a.zip
improved friends feature
-rw-r--r--data/languages/bosnian.txt2
-rw-r--r--data/languages/bulgarian.txt11
-rw-r--r--data/languages/czech.txt2
-rw-r--r--data/languages/danish.txt5
-rw-r--r--data/languages/dutch.txt2
-rw-r--r--data/languages/finnish.txt2
-rw-r--r--data/languages/french.txt2
-rw-r--r--data/languages/german.txt2
-rw-r--r--data/languages/italian.txt2
-rw-r--r--data/languages/norwegian.txt2
-rw-r--r--data/languages/polish.txt2
-rw-r--r--data/languages/portuguese.txt8
-rw-r--r--data/languages/romanian.txt9
-rw-r--r--data/languages/russian.txt2
-rw-r--r--data/languages/serbian.txt2
-rw-r--r--data/languages/slovak.txt2
-rw-r--r--data/languages/spanish.txt11
-rw-r--r--data/languages/swedish.txt2
-rw-r--r--data/languages/turkish.txt2
-rw-r--r--data/languages/ukrainian.txt2
-rw-r--r--src/engine/client/friends.cpp38
-rw-r--r--src/engine/client/friends.h1
-rw-r--r--src/engine/client/serverbrowser.cpp157
-rw-r--r--src/engine/friends.h8
-rw-r--r--src/engine/serverbrowser.h5
-rw-r--r--src/game/client/components/menus.cpp6
-rw-r--r--src/game/client/components/menus.h29
-rw-r--r--src/game/client/components/menus_browser.cpp166
28 files changed, 322 insertions, 162 deletions
diff --git a/data/languages/bosnian.txt b/data/languages/bosnian.txt
index 0c87d4ba..d2c64522 100644
--- a/data/languages/bosnian.txt
+++ b/data/languages/bosnian.txt
@@ -508,7 +508,7 @@ Shotgun
 Show chat
 == Prikaži chat
 
-Show friends
+Show friends only
 == Prikaži prijatelje
 
 Show ingame HUD
diff --git a/data/languages/bulgarian.txt b/data/languages/bulgarian.txt
index 89b95af7..403941de 100644
--- a/data/languages/bulgarian.txt
+++ b/data/languages/bulgarian.txt
@@ -508,7 +508,7 @@ Shotgun
 Show chat
 == Показвай чата
 
-Show friends
+Show friends only
 == Покажи приятели
 
 Show ingame HUD
@@ -674,12 +674,3 @@ Strict gametype filter
 
 ##### old translations #####
 
-Page %d of %d
-== Страница %d/%d
-
-Next
-== Нататък
-
-Prev
-== Назад
-
diff --git a/data/languages/czech.txt b/data/languages/czech.txt
index 33aca462..b132f398 100644
--- a/data/languages/czech.txt
+++ b/data/languages/czech.txt
@@ -508,7 +508,7 @@ Shotgun
 Show chat
 == Zobrazit chat
 
-Show friends
+Show friends only
 == Zobrazit přátele
 
 Show ingame HUD
diff --git a/data/languages/danish.txt b/data/languages/danish.txt
index db69e06c..c78d848f 100644
--- a/data/languages/danish.txt
+++ b/data/languages/danish.txt
@@ -508,7 +508,7 @@ Shotgun
 Show chat
 == Vis chat
 
-Show friends
+Show friends only
 == Vis venner
 
 Show ingame HUD
@@ -674,6 +674,3 @@ Strict gametype filter
 
 ##### old translations #####
 
-##### translated strings #####
-== 
-
diff --git a/data/languages/dutch.txt b/data/languages/dutch.txt
index fbd488a1..bd03032a 100644
--- a/data/languages/dutch.txt
+++ b/data/languages/dutch.txt
@@ -511,7 +511,7 @@ Shotgun
 Show chat
 == Laat chat zien
 
-Show friends
+Show friends only
 == Laat vrienden zien
 
 Show ingame HUD
diff --git a/data/languages/finnish.txt b/data/languages/finnish.txt
index 6f418c4b..74f7d4f2 100644
--- a/data/languages/finnish.txt
+++ b/data/languages/finnish.txt
@@ -508,7 +508,7 @@ Shotgun
 Show chat
 == Näytä chatti
 
-Show friends
+Show friends only
 == Näytä ystävät
 
 Show ingame HUD
diff --git a/data/languages/french.txt b/data/languages/french.txt
index 137e3682..629a885c 100644
--- a/data/languages/french.txt
+++ b/data/languages/french.txt
@@ -508,7 +508,7 @@ Shotgun
 Show chat
 == Montrer le chat
 
-Show friends
+Show friends only
 == Montrer les amis
 
 Show ingame HUD
diff --git a/data/languages/german.txt b/data/languages/german.txt
index c3e74bbf..38fd30a6 100644
--- a/data/languages/german.txt
+++ b/data/languages/german.txt
@@ -514,7 +514,7 @@ Shotgun
 Show chat
 == Chat anzeigen
 
-Show friends
+Show friends only
 == Nur Freunde zeigen
 
 Show ingame HUD
diff --git a/data/languages/italian.txt b/data/languages/italian.txt
index 6ee03054..8c9fa07e 100644
--- a/data/languages/italian.txt
+++ b/data/languages/italian.txt
@@ -508,7 +508,7 @@ Shotgun
 Show chat
 == Mostra chat
 
-Show friends
+Show friends only
 == Mostra amici
 
 Show ingame HUD
diff --git a/data/languages/norwegian.txt b/data/languages/norwegian.txt
index a17365d5..7f04e19d 100644
--- a/data/languages/norwegian.txt
+++ b/data/languages/norwegian.txt
@@ -508,7 +508,7 @@ Shotgun
 Show chat
 == Vis samtale
 
-Show friends
+Show friends only
 == Vis venner
 
 Show ingame HUD
diff --git a/data/languages/polish.txt b/data/languages/polish.txt
index c3223a9c..b8e74a96 100644
--- a/data/languages/polish.txt
+++ b/data/languages/polish.txt
@@ -612,7 +612,7 @@ Server address:
 Server filter
 == 
 
-Show friends
+Show friends only
 == 
 
 Show ingame HUD
diff --git a/data/languages/portuguese.txt b/data/languages/portuguese.txt
index 14486247..1d2681ad 100644
--- a/data/languages/portuguese.txt
+++ b/data/languages/portuguese.txt
@@ -1,4 +1,4 @@
-
+
 ##### translated strings #####
 
 %d Bytes
@@ -514,7 +514,7 @@ Shotgun
 Show chat
 == Mostrar conversa
 
-Show friends
+Show friends only
 == Mostrar amigos
 
 Show ingame HUD
@@ -672,9 +672,5 @@ no limit
 
 ##### needs translation #####
 
-
 ##### old translations #####
 
-
-== ## translated strings #####
-
diff --git a/data/languages/romanian.txt b/data/languages/romanian.txt
index c7bbb695..6c6ce05f 100644
--- a/data/languages/romanian.txt
+++ b/data/languages/romanian.txt
@@ -1,3 +1,4 @@
+
 ##### translated strings #####
 
 %d Bytes
@@ -141,12 +142,12 @@ Delete
 Delete demo
 == Șterge demonstrația
 
-Demofile: %s
-== Fișier demo: %s
-
 Demo details
 == Detalii demo
 
+Demofile: %s
+== Fișier demo: %s
+
 Demos
 == Demo
 
@@ -513,7 +514,7 @@ Shotgun
 Show chat
 == Afișare chat
 
-Show friends
+Show friends only
 == Arată prietenii
 
 Show ingame HUD
diff --git a/data/languages/russian.txt b/data/languages/russian.txt
index 87cf3168..52e49eb0 100644
--- a/data/languages/russian.txt
+++ b/data/languages/russian.txt
@@ -511,7 +511,7 @@ Shotgun
 Show chat
 == Показать чат
 
-Show friends
+Show friends only
 == Показывать друзей
 
 Show ingame HUD
diff --git a/data/languages/serbian.txt b/data/languages/serbian.txt
index aaabeabd..516d50c8 100644
--- a/data/languages/serbian.txt
+++ b/data/languages/serbian.txt
@@ -612,7 +612,7 @@ Server address:
 Server filter
 == 
 
-Show friends
+Show friends only
 == 
 
 Show ingame HUD
diff --git a/data/languages/slovak.txt b/data/languages/slovak.txt
index 20394096..37754d85 100644
--- a/data/languages/slovak.txt
+++ b/data/languages/slovak.txt
@@ -508,7 +508,7 @@ Shotgun
 Show chat
 == Ukázať chat
 
-Show friends
+Show friends only
 == Ukázať priateľov
 
 Show ingame HUD
diff --git a/data/languages/spanish.txt b/data/languages/spanish.txt
index d587cf02..df508443 100644
--- a/data/languages/spanish.txt
+++ b/data/languages/spanish.txt
@@ -508,7 +508,7 @@ Shotgun
 Show chat
 == Mostrar chat
 
-Show friends
+Show friends only
 == Mostrar amigos
 
 Show ingame HUD
@@ -674,12 +674,3 @@ Strict gametype filter
 
 ##### old translations #####
 
-Page %d of %d
-== Página %d de %d
-
-Next
-== Siguiente
-
-Prev
-== Anterior
-
diff --git a/data/languages/swedish.txt b/data/languages/swedish.txt
index 6dea5ed4..6b1804aa 100644
--- a/data/languages/swedish.txt
+++ b/data/languages/swedish.txt
@@ -508,7 +508,7 @@ Shotgun
 Show chat
 == Visa chatt
 
-Show friends
+Show friends only
 == Visa kompisar
 
 Show ingame HUD
diff --git a/data/languages/turkish.txt b/data/languages/turkish.txt
index a62e6073..a3fa958b 100644
--- a/data/languages/turkish.txt
+++ b/data/languages/turkish.txt
@@ -508,7 +508,7 @@ Shotgun
 Show chat
 == Sohbeti göster
 
-Show friends
+Show friends only
 == Arkadaşları göster
 
 Show ingame HUD
diff --git a/data/languages/ukrainian.txt b/data/languages/ukrainian.txt
index 5019714b..f6e69f00 100644
--- a/data/languages/ukrainian.txt
+++ b/data/languages/ukrainian.txt
@@ -618,7 +618,7 @@ Server address:
 Server filter
 == 
 
-Show friends
+Show friends only
 == 
 
 Show ingame HUD
diff --git a/src/engine/client/friends.cpp b/src/engine/client/friends.cpp
index 9ef00ed1..99f82b50 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>
@@ -44,12 +45,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 +85,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/serverbrowser.cpp b/src/engine/client/serverbrowser.cpp
index 424acb22..c3ada10e 100644
--- a/src/engine/client/serverbrowser.cpp
+++ b/src/engine/client/serverbrowser.cpp
@@ -144,101 +144,98 @@ 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;
-			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
+		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_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;
-			}
-			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_BrFilterString[0] != 0)
-			{
-				int MatchFound = 0;
-
-				m_ppServerlist[i]->m_Info.m_QuickSearchHit = 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_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_BrFilterString[0] != 0)
+		{
+			int MatchFound = 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;
-				}
+			m_ppServerlist[i]->m_Info.m_QuickSearchHit = 0;
 
-				// 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;
-					}
-				}
+			// 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 map
-				if(str_find_nocase(m_ppServerlist[i]->m_Info.m_aMap, g_Config.m_BrFilterString))
+			// 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_MAPNAME;
+					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)
-			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;
+		}
 	}
 }
 
diff --git a/src/engine/friends.h b/src/engine/friends.h
index bf9df904..164e3461 100644
--- a/src/engine/friends.h
+++ b/src/engine/friends.h
@@ -11,6 +11,8 @@ struct CFriendInfo
 {
 	char m_aName[MAX_NAME_LENGTH];
 	char m_aClan[MAX_CLAN_LENGTH];
+	unsigned m_NameHash;
+	unsigned m_ClanHash;
 };
 
 class IFriends : public IInterface
@@ -19,6 +21,10 @@ class IFriends : public IInterface
 public:
 	enum
 	{
+		FRIEND_NO=0,
+		FRIEND_CLAN,
+		FRIEND_PLAYER,
+
 		MAX_FRIENDS=128,
 	};
 
@@ -26,11 +32,11 @@ public:
 
 	virtual int NumFriends() const = 0;
 	virtual const CFriendInfo *GetFriend(int Index) const = 0;
+	virtual int GetFriendState(const char *pName, const char *pClan) const = 0;
 	virtual bool IsFriend(const char *pName, const char *pClan, bool PlayersOnly) const = 0;
 
 	virtual void AddFriend(const char *pName, const char *pClan) = 0;
 	virtual void RemoveFriend(const char *pName, const char *pClan) = 0;
-	virtual void RemoveFriend(int Index) = 0;
 };
 
 #endif
diff --git a/src/engine/serverbrowser.h b/src/engine/serverbrowser.h
index 3ca59f9c..1a49eaf0 100644
--- a/src/engine/serverbrowser.h
+++ b/src/engine/serverbrowser.h
@@ -24,7 +24,9 @@ public:
 		int m_Country;
 		int m_Score;
 		bool m_Player;
-	} ;
+		
+		int m_FriendState;
+	};
 
 	int m_SortedIndex;
 	int m_ServerIndex;
@@ -32,6 +34,7 @@ public:
 	NETADDR m_NetAddr;
 
 	int m_QuickSearchHit;
+	int m_FriendState;
 
 	int m_MaxClients;
 	int m_NumClients;
diff --git a/src/game/client/components/menus.cpp b/src/game/client/components/menus.cpp
index 64754c68..6ec18ce1 100644
--- a/src/game/client/components/menus.cpp
+++ b/src/game/client/components/menus.cpp
@@ -734,6 +734,8 @@ void CMenus::OnInit()
 
 	Console()->Chain("add_favorite", ConchainServerbrowserUpdate, this);
 	Console()->Chain("remove_favorite", ConchainServerbrowserUpdate, this);
+	Console()->Chain("add_friend", ConchainFriendlistUpdate, this);
+	Console()->Chain("remove_friend", ConchainFriendlistUpdate, this);
 
 	// setup load amount
 	m_LoadCurrent = 0;
@@ -1205,7 +1207,9 @@ int CMenus::Render()
 				// remove friend
 				if(m_FriendlistSelectedIndex >= 0)
 				{
-					m_pClient->Friends()->RemoveFriend(m_FriendlistSelectedIndex);
+					m_pClient->Friends()->RemoveFriend(m_lFriends[m_FriendlistSelectedIndex].m_pFriendInfo->m_aName,
+						m_lFriends[m_FriendlistSelectedIndex].m_pFriendInfo->m_aClan);
+					FriendlistOnUpdate();
 					Client()->ServerBrowserUpdate();
 				}
 			}
diff --git a/src/game/client/components/menus.h b/src/game/client/components/menus.h
index 51b8a1f8..959c366e 100644
--- a/src/game/client/components/menus.h
+++ b/src/game/client/components/menus.h
@@ -7,6 +7,7 @@
 #include <base/tl/sorted_array.h>
 
 #include <engine/demo.h>
+#include <engine/friends.h>
 
 #include <game/voting.h>
 #include <game/client/component.h>
@@ -202,8 +203,34 @@ class CMenus : public CComponent
 	void DemolistPopulate();
 	static int DemolistFetchCallback(const char *pName, int IsDir, int StorageType, void *pUser);
 
+	// friends
+	struct CFriendItem
+	{
+		const CFriendInfo *m_pFriendInfo;
+		int m_NumFound;
+
+		bool operator<(const CFriendItem &Other)
+		{
+			if(m_NumFound && !Other.m_NumFound)
+				return true;
+			else if(!m_NumFound && Other.m_NumFound)
+				return false;
+			else
+			{
+				int Result = str_comp(m_pFriendInfo->m_aName, Other.m_pFriendInfo->m_aName);
+				if(Result)
+					return Result < 0;
+				else
+					return str_comp(m_pFriendInfo->m_aClan, Other.m_pFriendInfo->m_aClan) < 0;
+			}
+		}
+	};
+
+	sorted_array<CFriendItem> m_lFriends;
 	int m_FriendlistSelectedIndex;
 
+	void FriendlistOnUpdate();
+
 	// found in menus.cpp
 	int Render();
 	//void render_background();
@@ -225,11 +252,13 @@ class CMenus : public CComponent
 
 	// found in menus_browser.cpp
 	int m_SelectedIndex;
+	int m_ScrollOffset;
 	void RenderServerbrowserServerList(CUIRect View);
 	void RenderServerbrowserServerDetail(CUIRect View);
 	void RenderServerbrowserFilters(CUIRect View);
 	void RenderServerbrowserFriends(CUIRect View);
 	void RenderServerbrowser(CUIRect MainView);
+	static void ConchainFriendlistUpdate(IConsole::IResult *pResult, void *pUserData, IConsole::FCommandCallback pfnCallback, void *pCallbackUserData);
 	static void ConchainServerbrowserUpdate(IConsole::IResult *pResult, void *pUserData, IConsole::FCommandCallback pfnCallback, void *pCallbackUserData);
 
 	// found in menus_settings.cpp
diff --git a/src/game/client/components/menus_browser.cpp b/src/game/client/components/menus_browser.cpp
index 09c8c494..5a0ebfb6 100644
--- a/src/game/client/components/menus_browser.cpp
+++ b/src/game/client/components/menus_browser.cpp
@@ -154,6 +154,11 @@ void CMenus::RenderServerbrowserServerList(CUIRect View)
 	int ScrollNum = NumServers-Num+1;
 	if(ScrollNum > 0)
 	{
+		if(m_ScrollOffset)
+		{
+			s_ScrollValue = (float)(m_ScrollOffset)/ScrollNum;
+			m_ScrollOffset = 0;
+		}
 		if(Input()->KeyPresses(KEY_MOUSE_WHEEL_UP) && UI()->MouseInside(&View))
 			s_ScrollValue -= 3.0f/ScrollNum;
 		if(Input()->KeyPresses(KEY_MOUSE_WHEEL_DOWN) && UI()->MouseInside(&View))
@@ -213,16 +218,14 @@ void CMenus::RenderServerbrowserServerList(CUIRect View)
 
 	m_SelectedIndex = -1;
 
-	for (int i = 0; i < NumServers; i++)
-	{
-		const CServerInfo *pItem = ServerBrowser()->SortedGet(i);
-		NumPlayers += pItem->m_NumPlayers;
-	}
+	// reset friend counter
+	for(int i = 0; i < m_lFriends.size(); m_lFriends[i++].m_NumFound = 0);
 
 	for (int i = 0; i < NumServers; i++)
 	{
 		int ItemIndex = i;
 		const CServerInfo *pItem = ServerBrowser()->SortedGet(ItemIndex);
+		NumPlayers += pItem->m_NumPlayers;
 		CUIRect Row;
 		CUIRect SelectHitBox;
 
@@ -234,6 +237,28 @@ void CMenus::RenderServerbrowserServerList(CUIRect View)
 		if(Selected)
 			m_SelectedIndex = i;
 
+		// update friend counter
+		if(pItem->m_FriendState != IFriends::FRIEND_NO)
+		{
+			for(int j = 0; j < pItem->m_NumClients; ++j)
+			{
+				if(pItem->m_aClients[j].m_FriendState != IFriends::FRIEND_NO)
+				{
+					unsigned NameHash = str_quickhash(pItem->m_aClients[j].m_aName);
+					unsigned ClanHash = str_quickhash(pItem->m_aClients[j].m_aClan);
+					for(int f = 0; f < m_lFriends.size(); ++f)
+					{
+						if(ClanHash == m_lFriends[f].m_pFriendInfo->m_ClanHash &&
+							(!m_lFriends[f].m_pFriendInfo->m_aName[0] || NameHash == m_lFriends[f].m_pFriendInfo->m_NameHash))
+						{
+							m_lFriends[f].m_NumFound++;
+							break;
+						}
+					}
+				}
+			}
+		}
+
 		// make sure that only those in view can be selected
 		if(Row.y+Row.h > OriginalView.y && Row.y < OriginalView.y+OriginalView.h)
 		{
@@ -353,6 +378,15 @@ void CMenus::RenderServerbrowserServerList(CUIRect View)
 			}
 			else if(ID == COL_PLAYERS)
 			{
+				CUIRect Icon;
+				Button.VMargin(4.0f, &Button);
+				if(pItem->m_FriendState != IFriends::FRIEND_NO)
+				{
+					Button.VSplitLeft(Button.h, &Icon, &Button);
+					Icon.Margin(2.0f, &Icon);
+					DoButton_Icon(IMAGE_BROWSEICONS, SPRITE_BROWSE_HEART, &Icon);
+				}
+
 				if(g_Config.m_BrFilterSpectators)
 					str_format(aTemp, sizeof(aTemp), "%i/%i", pItem->m_NumPlayers, pItem->m_MaxPlayers);
 				else
@@ -463,7 +497,7 @@ void CMenus::RenderServerbrowserFilters(CUIRect View)
 		g_Config.m_BrFilterFull ^= 1;
 
 	ServerFilter.HSplitTop(20.0f, &Button, &ServerFilter);
-	if (DoButton_CheckBox(&g_Config.m_BrFilterFriends, Localize("Show friends"), g_Config.m_BrFilterFriends, &Button))
+	if (DoButton_CheckBox(&g_Config.m_BrFilterFriends, Localize("Show friends only"), g_Config.m_BrFilterFriends, &Button))
 		g_Config.m_BrFilterFriends ^= 1;
 
 	ServerFilter.HSplitTop(20.0f, &Button, &ServerFilter);
@@ -622,14 +656,26 @@ void CMenus::RenderServerbrowserServerDetail(CUIRect View)
 	RenderTools()->DrawUIRect(&ServerScoreBoard, vec4(0,0,0,0.15f), CUI::CORNER_B, 4.0f);
 	UI()->DoLabelScaled(&ServerHeader, Localize("Scoreboard"), FontSize+2.0f, 0);
 
-	if (pSelectedServer)
+	if(pSelectedServer)
 	{
 		ServerScoreBoard.Margin(3.0f, &ServerScoreBoard);
 		for (int i = 0; i < pSelectedServer->m_NumClients; i++)
 		{
 			CUIRect Name, Clan, Score, Flag;
 			ServerScoreBoard.HSplitTop(25.0f, &Name, &ServerScoreBoard);
-			RenderTools()->DrawUIRect(&Name, vec4(1,1,1,(i%2+1)*0.05f), CUI::CORNER_ALL, 4.0f);
+			if(UI()->DoButtonLogic(&pSelectedServer->m_aClients[i], "", 0, &Name))
+			{
+				if(pSelectedServer->m_aClients[i].m_FriendState == IFriends::FRIEND_PLAYER)
+					m_pClient->Friends()->RemoveFriend(pSelectedServer->m_aClients[i].m_aName, pSelectedServer->m_aClients[i].m_aClan);
+				else
+					m_pClient->Friends()->AddFriend(pSelectedServer->m_aClients[i].m_aName, pSelectedServer->m_aClients[i].m_aClan);
+				FriendlistOnUpdate();
+				Client()->ServerBrowserUpdate();
+			}
+
+			vec4 Colour = pSelectedServer->m_aClients[i].m_FriendState == IFriends::FRIEND_NO ? vec4(1.0f, 1.0f, 1.0f, (i%2+1)*0.05f) :
+																								vec4(0.5f, 1.0f, 0.5f, 0.15f+(i%2+1)*0.05f);
+			RenderTools()->DrawUIRect(&Name, Colour, CUI::CORNER_ALL, 4.0f);
 			Name.VSplitLeft(5.0f, 0, &Name);
 			Name.VSplitLeft(30.0f, &Score, &Name);
 			Name.VSplitRight(34.0f, &Name, &Flag);
@@ -701,45 +747,102 @@ void CMenus::RenderServerbrowserServerDetail(CUIRect View)
 	}
 }
 
+void CMenus::FriendlistOnUpdate()
+{
+	m_lFriends.clear();
+	for(int i = 0; i < m_pClient->Friends()->NumFriends(); ++i)
+	{
+		CFriendItem Item;
+		Item.m_pFriendInfo = m_pClient->Friends()->GetFriend(i);
+		Item.m_NumFound = 0;
+		m_lFriends.add_unsorted(Item);
+	}
+	m_lFriends.sort_range();
+}
+
 void CMenus::RenderServerbrowserFriends(CUIRect View)
 {
+	static int s_Inited = 0;
+	if(!s_Inited)
+	{
+		FriendlistOnUpdate();
+		s_Inited = 1;
+	}
+
 	CUIRect ServerFriends = View, FilterHeader;
-	const float FontSize = 12.0f;
+	const float FontSize = 10.0f;
 
 	// header
 	ServerFriends.HSplitTop(ms_ListheaderHeight, &FilterHeader, &ServerFriends);
 	RenderTools()->DrawUIRect(&FilterHeader, vec4(1,1,1,0.25f), CUI::CORNER_T, 4.0f);
 	RenderTools()->DrawUIRect(&ServerFriends, vec4(0,0,0,0.15f), 0, 4.0f);
-	UI()->DoLabelScaled(&FilterHeader, Localize("Friends"), FontSize+2.0f, 0);
+	UI()->DoLabelScaled(&FilterHeader, Localize("Friends"), FontSize+4.0f, 0);
 	CUIRect Button, List;
 
-	ServerFriends.VSplitLeft(5.0f, 0, &ServerFriends);
 	ServerFriends.Margin(3.0f, &ServerFriends);
-	ServerFriends.VMargin(5.0f, &ServerFriends);
+	ServerFriends.VMargin(3.0f, &ServerFriends);
 	ServerFriends.HSplitBottom(100.0f, &List, &ServerFriends);
 
 	// friends list(remove friend)
-	static int s_FriendList = 0;
 	static float s_ScrollValue = 0;
-	UiDoListboxStart(&s_FriendList, &List, 40.0f, "", "", m_pClient->Friends()->NumFriends(), 1, m_FriendlistSelectedIndex, s_ScrollValue);
+	UiDoListboxStart(&m_lFriends, &List, 30.0f, "", "", m_lFriends.size(), 1, m_FriendlistSelectedIndex, s_ScrollValue);
 
-	for(int i = 0; i < m_pClient->Friends()->NumFriends(); ++i)
+	m_lFriends.sort_range();
+	for(int i = 0; i < m_lFriends.size(); ++i)
 	{
-		const CFriendInfo *pFriend = m_pClient->Friends()->GetFriend(i);
-		CListboxItem Item = UiDoListboxNextItem(pFriend);
+		CListboxItem Item = UiDoListboxNextItem(&m_lFriends[i]);
 
 		if(Item.m_Visible)
 		{
-			Item.m_Rect.Margin(2.5f, &Item.m_Rect);
-			RenderTools()->DrawUIRect(&Item.m_Rect, vec4(1.0f, 1.0f, 1.0f, 0.1f), CUI::CORNER_ALL, 4.0f);
-			Item.m_Rect.Margin(2.5f, &Item.m_Rect);
-			Item.m_Rect.HSplitTop(14.0f, &Item.m_Rect, &Button);
-			UI()->DoLabelScaled(&Item.m_Rect, pFriend->m_aName, FontSize, -1);
-			UI()->DoLabelScaled(&Button, pFriend->m_aClan, FontSize, -1);
+			Item.m_Rect.Margin(1.5f, &Item.m_Rect);
+			CUIRect OnState;
+			Item.m_Rect.VSplitRight(30.0f, &Item.m_Rect, &OnState);
+			RenderTools()->DrawUIRect(&Item.m_Rect, vec4(1.0f, 1.0f, 1.0f, 0.1f), CUI::CORNER_L, 4.0f);
+
+			Item.m_Rect.VMargin(2.5f, &Item.m_Rect);
+			Item.m_Rect.HSplitTop(12.0f, &Item.m_Rect, &Button);
+			UI()->DoLabelScaled(&Item.m_Rect, m_lFriends[i].m_pFriendInfo->m_aName, FontSize, -1);
+			UI()->DoLabelScaled(&Button, m_lFriends[i].m_pFriendInfo->m_aClan, FontSize, -1);
+
+			RenderTools()->DrawUIRect(&OnState, m_lFriends[i].m_NumFound ? vec4(0.0f, 1.0f, 0.0f, 0.25f) : vec4(1.0f, 0.0f, 0.0f, 0.25f), CUI::CORNER_R, 4.0f);
+			OnState.HMargin((OnState.h-FontSize)/3, &OnState);
+			OnState.VMargin(5.0f, &OnState);
+			char aBuf[64];
+			str_format(aBuf, sizeof(aBuf), "%i", m_lFriends[i].m_NumFound);
+			UI()->DoLabelScaled(&OnState, aBuf, FontSize+2, 1);
 		}
 	}
 
-	m_FriendlistSelectedIndex = UiDoListboxEnd(&s_ScrollValue, 0);
+	bool Activated = false;
+	m_FriendlistSelectedIndex = UiDoListboxEnd(&s_ScrollValue, &Activated);
+
+	// activate found server with friend
+	if(Activated && !m_EnterPressed && m_lFriends[m_FriendlistSelectedIndex].m_NumFound)
+	{
+		bool Found = false;
+		int NumServers = ServerBrowser()->NumSortedServers();
+		for (int i = 0; i < NumServers && !Found; i++)
+		{
+			int ItemIndex = m_SelectedIndex != -1 ? (m_SelectedIndex+i+1)%NumServers : i;
+			const CServerInfo *pItem = ServerBrowser()->SortedGet(ItemIndex);
+			if(pItem->m_FriendState != IFriends::FRIEND_NO)
+			{
+				for(int j = 0; j < pItem->m_NumClients && !Found; ++j)
+				{
+					if(pItem->m_aClients[j].m_FriendState != IFriends::FRIEND_NO &&
+						str_quickhash(pItem->m_aClients[j].m_aClan) == m_lFriends[m_FriendlistSelectedIndex].m_pFriendInfo->m_ClanHash &&
+						(!m_lFriends[m_FriendlistSelectedIndex].m_pFriendInfo->m_aName[0] ||
+						str_quickhash(pItem->m_aClients[j].m_aName) == m_lFriends[m_FriendlistSelectedIndex].m_pFriendInfo->m_NameHash))
+					{
+						str_copy(g_Config.m_UiServerAddress, pItem->m_aAddress, sizeof(g_Config.m_UiServerAddress));
+						m_ScrollOffset = ItemIndex;
+						m_SelectedIndex = ItemIndex;
+						Found = true;
+					}
+				}
+			}
+		}
+	}
 
 	ServerFriends.HSplitTop(2.5f, 0, &ServerFriends);
 	ServerFriends.HSplitTop(20.0f, &Button, &ServerFriends);
@@ -774,10 +877,11 @@ void CMenus::RenderServerbrowserFriends(CUIRect View)
 
 		ServerFriends.HSplitTop(3.0f, 0, &ServerFriends);
 		ServerFriends.HSplitTop(20.0f, &Button, &ServerFriends);
-		static int s_RemoveButton = 0;
-		if(DoButton_Menu(&s_RemoveButton, Localize("Add Friend"), 0, &Button))
+		static int s_AddButton = 0;
+		if(DoButton_Menu(&s_AddButton, Localize("Add Friend"), 0, &Button))
 		{
 			m_pClient->Friends()->AddFriend(s_aName, s_aClan);
+			FriendlistOnUpdate();
 			Client()->ServerBrowserUpdate();
 		}
 	}
@@ -916,6 +1020,16 @@ void CMenus::RenderServerbrowser(CUIRect MainView)
 	}
 }
 
+void CMenus::ConchainFriendlistUpdate(IConsole::IResult *pResult, void *pUserData, IConsole::FCommandCallback pfnCallback, void *pCallbackUserData)
+{
+	pfnCallback(pResult, pCallbackUserData);
+	if(pResult->NumArguments() == 2 && ((CMenus *)pUserData)->Client()->State() == IClient::STATE_OFFLINE)
+	{
+		((CMenus *)pUserData)->FriendlistOnUpdate();
+		((CMenus *)pUserData)->Client()->ServerBrowserUpdate();
+	}
+}
+
 void CMenus::ConchainServerbrowserUpdate(IConsole::IResult *pResult, void *pUserData, IConsole::FCommandCallback pfnCallback, void *pCallbackUserData)
 {
 	pfnCallback(pResult, pCallbackUserData);