about summary refs log tree commit diff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/engine/client/ec_client.c32
-rw-r--r--src/engine/client/ec_gfx.c48
-rw-r--r--src/engine/client/ec_srvbrowse.c164
-rw-r--r--src/engine/e_if_client.h31
-rw-r--r--src/engine/e_if_gfx.h4
-rw-r--r--src/engine/server/es_server.c22
-rw-r--r--src/game/client/components/menus.cpp32
-rw-r--r--src/game/client/components/menus_browser.cpp36
-rw-r--r--src/game/client/components/players.cpp18
-rw-r--r--src/mastersrv/mastersrv.h7
10 files changed, 284 insertions, 110 deletions
diff --git a/src/engine/client/ec_client.c b/src/engine/client/ec_client.c
index 7445974f..bc76edfd 100644
--- a/src/engine/client/ec_client.c
+++ b/src/engine/client/ec_client.c
@@ -43,7 +43,7 @@ const int prediction_margin = 7; /* magic network prediction value */
 NETCLIENT *net;
 
 /* TODO: ugly, fix me */
-extern void client_serverbrowse_set(NETADDR *addr, int request, SERVER_INFO *info);
+extern void client_serverbrowse_set(NETADDR *addr, int request, int token, SERVER_INFO *info);
 
 static int snapshot_part;
 static int64 local_start_time;
@@ -737,39 +737,21 @@ static void client_process_packet(NETCHUNK *packet)
 					addr.ip[0], addr.ip[1], addr.ip[2],
 					addr.ip[3], addr.port);
 				
-				client_serverbrowse_set(&addr, 1, &info);
+				client_serverbrowse_set(&addr, 1, -1, &info);
 			}
 		}
 
 		{
-			int got_info_packet = 0;
-			
-			if(client_serverbrowse_lan())
-			{
-				if(packet->data_size >= (int)sizeof(SERVERBROWSE_INFO_LAN) &&
-					memcmp(packet->data, SERVERBROWSE_INFO_LAN, sizeof(SERVERBROWSE_INFO_LAN)) == 0)
-				{
-					got_info_packet = 1;
-				}
-			}
-			else
-			{
-				if(packet->data_size >= (int)sizeof(SERVERBROWSE_INFO) &&
-					memcmp(packet->data, SERVERBROWSE_INFO, sizeof(SERVERBROWSE_INFO)) == 0)
-				{
-					got_info_packet = 1;
-				}
-			}
-
-			if(got_info_packet)
+			if(packet->data_size >= (int)sizeof(SERVERBROWSE_INFO) && memcmp(packet->data, SERVERBROWSE_INFO, sizeof(SERVERBROWSE_INFO)) == 0)
 			{
 				/* we got ze info */
 				UNPACKER up;
 				SERVER_INFO info = {0};
 				int i;
-
+				int token = -1;
+				
 				unpacker_reset(&up, (unsigned char*)packet->data+sizeof(SERVERBROWSE_INFO), packet->data_size-sizeof(SERVERBROWSE_INFO));
-
+				token = atol(unpacker_get_string(&up));
 				str_copy(info.version, unpacker_get_string(&up), sizeof(info.version));
 				str_copy(info.name, unpacker_get_string(&up), sizeof(info.name));
 				str_copy(info.map, unpacker_get_string(&up), sizeof(info.map));
@@ -792,7 +774,7 @@ static void client_process_packet(NETCHUNK *packet)
 				{
 					/* sort players */
 					qsort(info.players, info.num_players, sizeof(*info.players), player_score_comp);
-					client_serverbrowse_set(&packet->address, 0, &info);
+					client_serverbrowse_set(&packet->address, 0, token, &info);
 				}
 			}
 		}
diff --git a/src/engine/client/ec_gfx.c b/src/engine/client/ec_gfx.c
index 279194ef..b44f2676 100644
--- a/src/engine/client/ec_gfx.c
+++ b/src/engine/client/ec_gfx.c
@@ -958,6 +958,7 @@ void gfx_text_set_cursor(TEXT_CURSOR *cursor, float x, float y, float font_size,
 	cursor->line_count = 1;
 	cursor->line_width = -1;
 	cursor->flags = flags;
+	cursor->charcount = 0;
 }
 
 void gfx_text_ex(TEXT_CURSOR *cursor, const char *text, int length)
@@ -999,7 +1000,7 @@ void gfx_text_ex(TEXT_CURSOR *cursor, const char *text, int length)
 	if(cursor->flags&TEXTFLAG_RENDER)
 		i = 0;
 
-	for (i = 0; i < 2; i++)
+	for(;i < 2; i++)
 	{
 		const unsigned char *current = (unsigned char *)text;
 		int to_render = length;
@@ -1022,8 +1023,9 @@ void gfx_text_ex(TEXT_CURSOR *cursor, const char *text, int length)
 
 		while(to_render > 0)
 		{
+			int new_line = 0;
 			int this_batch = to_render;
-			if(cursor->line_width > 0)
+			if(cursor->line_width > 0 && !(cursor->flags&TEXTFLAG_STOP_AT_END))
 			{
 				int wlen = word_length((char *)current);
 				TEXT_CURSOR compare = *cursor;
@@ -1033,12 +1035,27 @@ void gfx_text_ex(TEXT_CURSOR *cursor, const char *text, int length)
 				compare.line_width = -1;
 				gfx_text_ex(&compare, text, wlen);
 				
-				if(compare.x-cursor->start_x > cursor->line_width)
+				if(compare.x-draw_x > cursor->line_width)
 				{
-					draw_x = cursor->start_x;
-					draw_y += size;
-					draw_x = (int)(draw_x * fake_to_screen_x) / fake_to_screen_x; /* realign */
-					draw_y = (int)(draw_y * fake_to_screen_y) / fake_to_screen_y;
+					/* word can't be fitted in one line, cut it */
+					TEXT_CURSOR cutter = *cursor;
+					cutter.charcount = 0;
+					cutter.x = draw_x;
+					cutter.y = draw_y;
+					cutter.flags &= ~TEXTFLAG_RENDER;
+					cutter.flags |= TEXTFLAG_STOP_AT_END;
+					
+					gfx_text_ex(&cutter, (const char *)current, wlen);
+					wlen = cutter.charcount;
+					new_line = 1;
+					
+					if(wlen <= 3) /* if we can't place 3 chars of the word on this line, take the next */
+						wlen = 0;
+				}
+				else if(compare.x-cursor->start_x > cursor->line_width)
+				{
+					new_line = 1;
+					wlen = 0;
 				}
 				
 				this_batch = wlen;
@@ -1075,9 +1092,26 @@ void gfx_text_ex(TEXT_CURSOR *cursor, const char *text, int length)
 				}
 
 				advance = x_advance + font_kerning(font, *current, *(current+1));
+								
+				if(cursor->flags&TEXTFLAG_STOP_AT_END && draw_x+advance*size-cursor->start_x > cursor->line_width)
+				{
+					/* we hit the end of the line, no more to render or count */
+					to_render = 0;
+					break;
+				}
+
 				draw_x += advance*size;
+				cursor->charcount++;
 				current++;
 			}
+			
+			if(new_line)
+			{
+				draw_x = cursor->start_x;
+				draw_y += size;
+				draw_x = (int)(draw_x * fake_to_screen_x) / fake_to_screen_x; /* realign */
+				draw_y = (int)(draw_y * fake_to_screen_y) / fake_to_screen_y;				
+			}
 		}
 
 		if(cursor->flags&TEXTFLAG_RENDER)
diff --git a/src/engine/client/ec_srvbrowse.c b/src/engine/client/ec_srvbrowse.c
index e9d8dd75..f9807174 100644
--- a/src/engine/client/ec_srvbrowse.c
+++ b/src/engine/client/ec_srvbrowse.c
@@ -35,6 +35,14 @@ static HEAP *serverlist_heap = 0;
 static SERVERENTRY **serverlist = 0;
 static int *sorted_serverlist = 0;
 
+enum
+{
+	MAX_FAVORITES=256
+};
+
+static NETADDR favorite_servers[MAX_FAVORITES];
+static int num_favorite_servers = 0;
+
 static SERVERENTRY *serverlist_ip[256] = {0}; /* ip hash list */
 
 static SERVERENTRY *first_req_server = 0; /* request list */
@@ -50,6 +58,9 @@ static int sorthash = 0;
 static char filterstring[64] = {0};
 static char filtergametypestring[128] = {0};
 
+/* the token is to keep server refresh separated from each other */
+static int current_token = 1;
+
 static int serverlist_lan = 1;
 static int64 broadcast_time = 0;
 
@@ -267,32 +278,48 @@ static void client_serverbrowse_remove_request(SERVERENTRY *entry)
 	}
 }
 
-void client_serverbrowse_set(NETADDR *addr, int request, SERVER_INFO *info)
+static SERVERENTRY *client_serverbrowse_find(NETADDR *addr)
+{
+	SERVERENTRY *entry = serverlist_ip[addr->ip[0]];
+	
+	for(; entry; entry = entry->next_ip)
+	{
+		if(net_addr_comp(&entry->addr, addr) == 0)
+			return entry;
+	}
+	return (SERVERENTRY*)0;
+}
+
+void client_serverbrowse_set(NETADDR *addr, int request, int token, SERVER_INFO *info)
 {
 	int hash = addr->ip[0];
 	SERVERENTRY *entry = 0;
+	int i;
+	
+	if(token != -1 && token != current_token)
+		return;
 	
-	entry = serverlist_ip[hash];
-	while(entry)
+	entry = client_serverbrowse_find(addr);
+	if(entry)
 	{
-		if(net_addr_comp(&entry->addr, addr) == 0)
+		/* update the server that we already have */
+		if(!serverlist_lan)
 		{
-			/* update the server that we already have */
-			if(!serverlist_lan)
+			int fav = entry->info.favorite;
+			entry->info = *info;
+			entry->info.netaddr = *addr;
+			entry->info.favorite = fav;
+
+			if(!request)
 			{
-				entry->info = *info;
-				if(!request)
-				{
-					entry->info.latency = (time_get()-entry->request_time)*1000/time_freq();
-					client_serverbrowse_remove_request(entry);
-				}
-				
-				entry->got_info = 1;
-				client_serverbrowse_sort();
+				entry->info.latency = (time_get()-entry->request_time)*1000/time_freq();
+				client_serverbrowse_remove_request(entry);
 			}
-			return;
+			
+			entry->got_info = 1;
+			client_serverbrowse_sort();
 		}
-		entry = entry->next_ip;
+		return;
 	}
 
 	/* create new entry */	
@@ -302,10 +329,18 @@ void client_serverbrowse_set(NETADDR *addr, int request, SERVER_INFO *info)
 	/* set the info */
 	entry->addr = *addr;
 	entry->info = *info;
+	entry->info.netaddr = *addr;
 	
 	if(serverlist_lan)
 		entry->info.latency = (time_get()-broadcast_time)*1000/time_freq();
 
+	/* check if it's a favorite */
+	for(i = 0; i < num_favorite_servers; i++)
+	{
+		if(net_addr_comp(addr, &favorite_servers[i]) == 0)
+			entry->info.favorite = 1;
+	}
+	
 	/* add to the hash list */	
 	entry->next_ip = serverlist_ip[hash];
 	serverlist_ip[hash] = entry;
@@ -342,7 +377,7 @@ void client_serverbrowse_set(NETADDR *addr, int request, SERVER_INFO *info)
 	client_serverbrowse_sort();
 }
 
-void client_serverbrowse_refresh(int lan)
+void client_serverbrowse_refresh(int type)
 {
 	/* clear out everything */
 	if(serverlist_heap)
@@ -355,13 +390,22 @@ void client_serverbrowse_refresh(int lan)
 	last_req_server = 0;
 	num_requests = 0;
 	
+	/* next token */
+	current_token = (current_token+1)&0xff;
 	
 	/* */
-	serverlist_lan = lan;
-	
-	if(serverlist_lan)
+	serverlist_lan = 0;
+	if(type == BROWSETYPE_LAN)
+		serverlist_lan = 1;
+
+	if(type == BROWSETYPE_LAN)
 	{
+		unsigned char buffer[sizeof(SERVERBROWSE_GETINFO)+1];
 		NETCHUNK packet;
+		
+		mem_copy(buffer, SERVERBROWSE_GETINFO, sizeof(SERVERBROWSE_GETINFO));
+		buffer[sizeof(SERVERBROWSE_GETINFO)] = current_token;
+			
 		packet.client_id = -1;
 		mem_zero(&packet, sizeof(packet));
 		packet.address.ip[0] = 255;
@@ -370,15 +414,15 @@ void client_serverbrowse_refresh(int lan)
 		packet.address.ip[3] = 255;
 		packet.address.port = 8303;
 		packet.flags = NETSENDFLAG_CONNLESS;
-		packet.data_size = sizeof(SERVERBROWSE_GETINFO_LAN);
-		packet.data = SERVERBROWSE_GETINFO_LAN;
+		packet.data_size = sizeof(buffer);
+		packet.data = buffer;
 		broadcast_time = time_get();
 		netclient_send(net, &packet);
 
 		if(config.debug)
 			dbg_msg("client", "broadcasting for servers");
 	}
-	else
+	else if(type == BROWSETYPE_INTERNET)
 	{
 		NETADDR addr;
 		NETCHUNK p;
@@ -405,10 +449,31 @@ void client_serverbrowse_refresh(int lan)
 		if(config.debug)
 			dbg_msg("client", "requesting server list");
 	}
+	else if(type == BROWSETYPE_FAVORITES)
+	{
+		int i;
+		
+		for(i = 0; i < num_favorite_servers; i++)
+		{
+			SERVER_INFO info = {0};
+			NETADDR addr = favorite_servers[i];
+			
+			info.latency = 999;
+			str_format(info.address, sizeof(info.address), "%d.%d.%d.%d:%d",
+				addr.ip[0], addr.ip[1], addr.ip[2],
+				addr.ip[3], addr.port);
+			str_format(info.name, sizeof(info.name), "\255%d.%d.%d.%d:%d", /* the \255 is to make sure that it's sorted last */
+				addr.ip[0], addr.ip[1], addr.ip[2],
+				addr.ip[3], addr.port);
+			
+			client_serverbrowse_set(&addr, 1, current_token, &info);
+		}
+	}
 }
 
 static void client_serverbrowse_request(SERVERENTRY *entry)
 {
+	unsigned char buffer[sizeof(SERVERBROWSE_GETINFO)+1];
 	NETCHUNK p;
 
 	if(config.debug)
@@ -418,11 +483,14 @@ static void client_serverbrowse_request(SERVERENTRY *entry)
 			entry->addr.ip[3], entry->addr.port);
 	}
 	
+	mem_copy(buffer, SERVERBROWSE_GETINFO, sizeof(SERVERBROWSE_GETINFO));
+	buffer[sizeof(SERVERBROWSE_GETINFO)] = current_token;
+	
 	p.client_id = -1;
 	p.address = entry->addr;
 	p.flags = NETSENDFLAG_CONNLESS;
-	p.data_size = sizeof(SERVERBROWSE_GETINFO);
-	p.data = SERVERBROWSE_GETINFO;
+	p.data_size = sizeof(buffer);
+	p.data = buffer;
 	netclient_send(net, &p);
 	entry->request_time = time_get();
 }
@@ -477,3 +545,47 @@ void client_serverbrowse_update()
 	if(sorthash != client_serverbrowse_sorthash() || strcmp(filterstring, config.b_filter_string) != 0 || strcmp(filtergametypestring, config.b_filter_gametype) != 0)
 		client_serverbrowse_sort();
 }
+
+
+void client_serverbrowse_addfavorite(NETADDR addr)
+{
+	int i;
+	SERVERENTRY *entry;
+	
+	if(num_favorite_servers == MAX_FAVORITES)
+		return;
+
+	/* make sure that we don't already have the server in our list */
+	for(i = 0; i < num_favorite_servers; i++)
+	{
+		if(net_addr_comp(&addr, &favorite_servers[i]) == 0)
+			return;
+	}
+	
+	/* add the server to the list */
+	favorite_servers[num_favorite_servers++] = addr;
+	entry = client_serverbrowse_find(&addr);
+	if(entry)
+		entry->info.favorite = 1;
+}
+
+void client_serverbrowse_removefavorite(NETADDR addr)
+{
+	int i;
+	SERVERENTRY *entry;
+	
+	for(i = 0; i < num_favorite_servers; i++)
+	{
+		if(net_addr_comp(&addr, &favorite_servers[i]) == 0)
+		{
+			mem_move(&favorite_servers[i], &favorite_servers[i+1], num_favorite_servers-(i+1));
+			num_favorite_servers--;
+
+			entry = client_serverbrowse_find(&addr);
+			if(entry)
+				entry->info.favorite = 0;
+
+			return;
+		}
+	}
+}
diff --git a/src/engine/e_if_client.h b/src/engine/e_if_client.h
index af5df1e4..98d92a64 100644
--- a/src/engine/e_if_client.h
+++ b/src/engine/e_if_client.h
@@ -57,7 +57,11 @@ enum
 	BROWSESORT_NUMPLAYERS,
 	
 	BROWSEQUICK_SERVERNAME=1,
-	BROWSEQUICK_PLAYERNAME=2
+	BROWSEQUICK_PLAYERNAME=2,
+	
+	BROWSETYPE_INTERNET = 0,
+	BROWSETYPE_LAN = 1,
+	BROWSETYPE_FAVORITES = 2
 };
 
 /*
@@ -81,12 +85,15 @@ typedef struct
 	int sorted_index;
 	int server_index;
 	
+	NETADDR netaddr;
+	
 	int quicksearch_hit;
 	
 	int progression;
 	int max_players;
 	int num_players;
 	int flags;
+	int favorite;
 	int latency; /* in ms */
 	char gametype[16];
 	char name[64];
@@ -192,13 +199,13 @@ float client_localtime();
 		Issues a refresh of the server browser.
 	
 	Arguments:
-		lan - Tells the function if it should do a LAN listing or an Internet listing.
+		type - What type of servers to browse, internet, lan or favorites.
 	
 	Remarks:
 		This will cause a broadcast on the local network if the lan argument is set.
 		Otherwise it call ask all the master servers for their servers lists.
 */
-void client_serverbrowse_refresh(int lan);
+void client_serverbrowse_refresh(int type);
 
 /*
 	Function: client_serverbrowse_sorted_get
@@ -291,6 +298,24 @@ void client_serverbrowse_update();
 */
 int client_serverbrowse_lan();
 
+/*
+	Function: client_serverbrowse_addfavorite
+		Adds a server to the favorite list
+	
+	Arguments:
+		addr - Address of the favorite server.
+*/
+void client_serverbrowse_addfavorite(NETADDR addr);
+
+/*
+	Function: client_serverbrowse_removefavorite
+		Removes a server to the favorite list
+	
+	Arguments:
+		addr - Address of the favorite server.
+*/
+void client_serverbrowse_removefavorite(NETADDR addr);
+
 /**********************************************************************************
 	Group: Actions
 **********************************************************************************/
diff --git a/src/engine/e_if_gfx.h b/src/engine/e_if_gfx.h
index 73b1aa22..acb004e8 100644
--- a/src/engine/e_if_gfx.h
+++ b/src/engine/e_if_gfx.h
@@ -640,13 +640,15 @@ void gfx_clip_disable();
 enum
 {
 	TEXTFLAG_RENDER=1,
-	TEXTFLAG_ALLOW_NEWLINE=2
+	TEXTFLAG_ALLOW_NEWLINE=2,
+	TEXTFLAG_STOP_AT_END=4
 };
 
 typedef struct
 {
 	int flags;
 	int line_count;
+	int charcount;
 	
 	float start_x;
 	float start_y;
diff --git a/src/engine/server/es_server.c b/src/engine/server/es_server.c
index afd0b16b..c6ff39c9 100644
--- a/src/engine/server/es_server.c
+++ b/src/engine/server/es_server.c
@@ -785,7 +785,7 @@ static void server_process_client_packet(NETCHUNK *packet)
 	}
 }
 
-static void server_send_serverinfo(NETADDR *addr, int lan)
+static void server_send_serverinfo(NETADDR *addr, int token)
 {
 	NETCHUNK packet;
 	PACKER p;
@@ -801,16 +801,17 @@ static void server_send_serverinfo(NETADDR *addr, int lan)
 	}
 	
 	packer_reset(&p);
-	if(lan)
-		packer_add_raw(&p, SERVERBROWSE_INFO_LAN, sizeof(SERVERBROWSE_INFO_LAN));
-	else
-		packer_add_raw(&p, SERVERBROWSE_INFO, sizeof(SERVERBROWSE_INFO));
+	packer_add_raw(&p, SERVERBROWSE_INFO, sizeof(SERVERBROWSE_INFO));
+
+	/* token */
+	str_format(buf, sizeof(buf), "%d", token);
+	packer_add_string(&p, buf, 6);
+	
 	packer_add_string(&p, mods_version(), 32);
 	packer_add_string(&p, config.sv_name, 64);
 	packer_add_string(&p, config.sv_map, 32);
 
 	/* gametype */
-	str_format(buf, sizeof(buf), "%d", browseinfo_gametype);
 	packer_add_string(&p, browseinfo_gametype, 16);
 
 	/* flags */
@@ -862,15 +863,10 @@ static void server_pump_network()
 			/* stateless */
 			if(!register_process_packet(&packet))
 			{
-				if(packet.data_size == sizeof(SERVERBROWSE_GETINFO) &&
+				if(packet.data_size == sizeof(SERVERBROWSE_GETINFO)+1 &&
 					memcmp(packet.data, SERVERBROWSE_GETINFO, sizeof(SERVERBROWSE_GETINFO)) == 0)
 				{
-					server_send_serverinfo(&packet.address, 0);
-				}
-				else if(packet.data_size == sizeof(SERVERBROWSE_GETINFO_LAN) &&
-					memcmp(packet.data, SERVERBROWSE_GETINFO_LAN, sizeof(SERVERBROWSE_GETINFO_LAN)) == 0)
-				{
-					server_send_serverinfo(&packet.address, 1);
+					server_send_serverinfo(&packet.address, ((unsigned char *)packet.data)[sizeof(SERVERBROWSE_GETINFO)]);
 				}
 			}
 		}
diff --git a/src/game/client/components/menus.cpp b/src/game/client/components/menus.cpp
index 70fdc767..de2e5711 100644
--- a/src/game/client/components/menus.cpp
+++ b/src/game/client/components/menus.cpp
@@ -104,21 +104,7 @@ void MENUS::ui_draw_browse_icon(int what, const RECT *r)
 {
 	gfx_texture_set(data->images[IMAGE_BROWSEICONS].id);
 	gfx_quads_begin();
-	select_sprite(SPRITE_BROWSE_PROGRESS1); // default
-	if(what == -1)
-	{
-	}
-	else if(what <= 100)
-	{
-		if(what < 66)
-			select_sprite(SPRITE_BROWSE_PROGRESS2);
-		else
-			select_sprite(SPRITE_BROWSE_PROGRESS3);
-	}
-	else if(what&0x100)
-	{
-		select_sprite(SPRITE_BROWSE_LOCK);
-	}
+	select_sprite(what);
 	gfx_quads_drawTL(r->x,r->y,r->w,r->h);
 	gfx_quads_end();
 }
@@ -509,7 +495,7 @@ int MENUS::render_menubar(RECT r)
 		static int internet_button=0;
 		if (ui_do_button(&internet_button, "Internet", active_page==PAGE_INTERNET, &button, ui_draw_menu_tab_button, 0))
 		{
-			client_serverbrowse_refresh(0);
+			client_serverbrowse_refresh(BROWSETYPE_INTERNET);
 			new_page = PAGE_INTERNET;
 		}
 
@@ -518,17 +504,17 @@ int MENUS::render_menubar(RECT r)
 		static int lan_button=0;
 		if (ui_do_button(&lan_button, "LAN", active_page==PAGE_LAN, &button, ui_draw_menu_tab_button, 0))
 		{
-			client_serverbrowse_refresh(1);
+			client_serverbrowse_refresh(BROWSETYPE_LAN);
 			new_page = PAGE_LAN;
 		}
 
-		if(0) // this one is not done yet
+		ui_vsplit_l(&box, 4.0f, 0, &box);
+		ui_vsplit_l(&box, 120.0f, &button, &box);
+		static int favorites_button=0;
+		if (ui_do_button(&favorites_button, "Favorites", active_page==PAGE_FAVORITES, &button, ui_draw_menu_tab_button, 0))
 		{
-			ui_vsplit_l(&box, 4.0f, 0, &box);
-			ui_vsplit_l(&box, 120.0f, &button, &box);
-			static int favorites_button=0;
-			if (ui_do_button(&favorites_button, "Favorites", active_page==PAGE_FAVORITES, &button, ui_draw_menu_tab_button, 0))
-				new_page  = PAGE_FAVORITES;
+			client_serverbrowse_refresh(BROWSETYPE_FAVORITES);
+			new_page  = PAGE_FAVORITES;
 		}
 	}
 	else
diff --git a/src/game/client/components/menus_browser.cpp b/src/game/client/components/menus_browser.cpp
index be0e1904..0d1effb8 100644
--- a/src/game/client/components/menus_browser.cpp
+++ b/src/game/client/components/menus_browser.cpp
@@ -68,7 +68,8 @@ void MENUS::render_serverbrowser(RECT main_view)
 		FIXED=1,
 		SPACER=2,
 		
-		COL_FLAGS=0,
+		COL_FLAG_LOCK=0,
+		COL_FLAG_FAV,
 		COL_NAME,
 		COL_GAMETYPE,
 		COL_MAP,
@@ -79,8 +80,9 @@ void MENUS::render_serverbrowser(RECT main_view)
 	};
 	
 	static column cols[] = {
-		{-1,			-1,						" ",		-1, 10.0f, 0, {0}, {0}},
-		{COL_FLAGS,		-1,						" ",		-1, 20.0f, 0, {0}, {0}},
+		{-1,			-1,						" ",		-1, 2.0f, 0, {0}, {0}},
+		{COL_FLAG_LOCK,	-1,						" ",		-1, 14.0f, 0, {0}, {0}},
+		{COL_FLAG_FAV,	-1,						" ",		-1, 14.0f, 0, {0}, {0}},
 		{COL_NAME,		BROWSESORT_NAME,		"Name",		0, 300.0f, 0, {0}, {0}},
 		{COL_GAMETYPE,	BROWSESORT_GAMETYPE,	"Type",		1, 50.0f, 0, {0}, {0}},
 		{COL_MAP,		BROWSESORT_MAP,			"Map", 		1, 100.0f, 0, {0}, {0}},
@@ -239,15 +241,21 @@ void MENUS::render_serverbrowser(RECT main_view)
 
 			//s = ui_do_button(item, "L", l, &button, ui_draw_browse_icon, 0);
 			
-			if(id == COL_FLAGS)
+			if(id == COL_FLAG_LOCK)
 			{
 				if(item->flags&1)
-					ui_draw_browse_icon(0x100, &button);
+					ui_draw_browse_icon(SPRITE_BROWSE_LOCK, &button);
+			}	
+			else if(id == COL_FLAG_FAV)
+			{
+				if(item->favorite)
+					ui_draw_browse_icon(SPRITE_BROWSE_HEART, &button);
 			}
 			else if(id == COL_NAME)
 			{
 				TEXT_CURSOR cursor;
-				gfx_text_set_cursor(&cursor, button.x, button.y, 12.0f, TEXTFLAG_RENDER);
+				gfx_text_set_cursor(&cursor, button.x, button.y, 12.0f, TEXTFLAG_RENDER|TEXTFLAG_STOP_AT_END);
+				cursor.line_width = button.w;
 				
 				if(config.b_filter_string[0] && (item->quicksearch_hit&BROWSEQUICK_SERVERNAME))
 				{
@@ -350,6 +358,21 @@ void MENUS::render_serverbrowser(RECT main_view)
 		RECT left_column;
 		RECT right_column;
 
+		// 
+		{
+			RECT button;
+			ui_hsplit_b(&server_details, 20.0f, &server_details, &button);
+			static int add_fav_button = 0;
+			if (ui_do_button(&add_fav_button, "Favorite", selected_server->favorite, &button, ui_draw_checkbox, 0))
+			{
+				if(selected_server->favorite)
+					client_serverbrowse_removefavorite(selected_server->netaddr);
+				else
+					client_serverbrowse_addfavorite(selected_server->netaddr);
+			}
+		}
+		//ui_do_label(&row, temp, font_size, -1);		
+
 		ui_vsplit_l(&server_details, 5.0f, 0x0, &server_details);
 		ui_vsplit_l(&server_details, 80.0f, &left_column, &right_column);
 
@@ -377,6 +400,7 @@ void MENUS::render_serverbrowser(RECT main_view)
 		str_format(temp, sizeof(temp), "%d", selected_server->latency);
 		ui_hsplit_t(&right_column, 15.0f, &row, &right_column);
 		ui_do_label(&row, temp, font_size, -1);
+
 	}
 	
 	// server scoreboard
diff --git a/src/game/client/components/players.cpp b/src/game/client/components/players.cpp
index f2d81323..68cf032b 100644
--- a/src/game/client/components/players.cpp
+++ b/src/game/client/components/players.cpp
@@ -58,6 +58,22 @@ void PLAYERS::render_hand(TEE_RENDER_INFO *info, vec2 center_pos, vec2 dir, floa
 	gfx_quads_end();
 }
 
+inline float normalize_angular(float f)
+{
+	return fmod(f, pi*2);
+}
+
+inline float mix_angular(float src, float dst, float amount)
+{
+	src = normalize_angular(src);
+	dst = normalize_angular(dst);
+	float d0 = dst-src;
+	float d1 = dst-(src+pi*2);
+	if(fabs(d0) < fabs(d1))
+		return src+d0*amount;
+	return src+d1*amount;
+}
+
 void PLAYERS::render_player(
 	const NETOBJ_CHARACTER *prev_char,
 	const NETOBJ_CHARACTER *player_char,
@@ -112,7 +128,7 @@ void PLAYERS::render_player(
 	if(player.attacktick != prev.attacktick)
 		mixspeed = 0.1f;
 	
-	float angle = mix(gameclient.clients[info.cid].angle, player.angle/256.0f, mixspeed);
+	float angle = mix_angular(gameclient.clients[info.cid].angle, player.angle/256.0f, mixspeed);
 	gameclient.clients[info.cid].angle = angle;
 	vec2 direction = get_direction((int)(angle*256.0f));
 	
diff --git a/src/mastersrv/mastersrv.h b/src/mastersrv/mastersrv.h
index 86cd28fe..e9e756d4 100644
--- a/src/mastersrv/mastersrv.h
+++ b/src/mastersrv/mastersrv.h
@@ -19,11 +19,8 @@ static const unsigned char SERVERBROWSE_LIST[] = {255, 255, 255, 255, 'l', 'i',
 static const unsigned char SERVERBROWSE_GETCOUNT[] = {255, 255, 255, 255, 'c', 'o', 'u', 'n'};
 static const unsigned char SERVERBROWSE_COUNT[] = {255, 255, 255, 255, 's', 'i', 'z', 'e'};
 
-static const unsigned char SERVERBROWSE_GETINFO[] = {255, 255, 255, 255, 'g', 'i', 'e', 'f'};
-static const unsigned char SERVERBROWSE_INFO[] = {255, 255, 255, 255, 'i', 'n', 'f', 'o'};
-
-static const unsigned char SERVERBROWSE_GETINFO_LAN[] = {255, 255, 255, 255, 'g', 'i', 'e', 'L'};
-static const unsigned char SERVERBROWSE_INFO_LAN[] = {255, 255, 255, 255, 'i', 'n', 'f', 'L'};
+static const unsigned char SERVERBROWSE_GETINFO[] = {255, 255, 255, 255, 'g', 'i', 'e', '2'};
+static const unsigned char SERVERBROWSE_INFO[] = {255, 255, 255, 255, 'i', 'n', 'f', '2'};
 
 static const unsigned char SERVERBROWSE_FWCHECK[] = {255, 255, 255, 255, 'f', 'w', '?', '?'};
 static const unsigned char SERVERBROWSE_FWRESPONSE[] = {255, 255, 255, 255, 'f', 'w', '!', '!'};