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.c23
-rw-r--r--src/engine/client/ec_gfx.c34
-rw-r--r--src/engine/client/ec_inp.c71
-rw-r--r--src/engine/client/ec_snd.c27
-rw-r--r--src/engine/client/ec_srvbrowse.c6
-rw-r--r--src/engine/e_config_variables.h5
-rw-r--r--src/engine/e_interface.h21
-rw-r--r--src/engine/server/es_server.c14
-rw-r--r--src/game/client/gc_client.cpp129
-rw-r--r--src/game/client/gc_menu.cpp159
-rw-r--r--src/game/g_variables.h2
-rw-r--r--src/game/g_version.h2
-rw-r--r--src/game/server/gs_common.h5
-rw-r--r--src/game/server/gs_server.cpp39
14 files changed, 377 insertions, 160 deletions
diff --git a/src/engine/client/ec_client.c b/src/engine/client/ec_client.c
index d35121de..b07edf78 100644
--- a/src/engine/client/ec_client.c
+++ b/src/engine/client/ec_client.c
@@ -304,6 +304,24 @@ int client_connection_problems()
 	return netclient_gotproblems(net);
 }
 
+void client_direct_input(int *input, int size)
+{
+	int i;
+	msg_pack_start_system(NETMSG_INPUT, 0);
+	msg_pack_int(ack_game_tick);
+	msg_pack_int(current_predtick);
+	msg_pack_int(size);
+	
+	for(i = 0; i < size/4; i++)
+		msg_pack_int(input[i]);	
+		
+	msg_pack_end();
+	client_send_msg();
+	
+	dbg_msg("client", "sent direct input");
+}
+
+
 static void client_send_input()
 {
 	int64 now = time_get();	
@@ -312,7 +330,7 @@ static void client_send_input()
 	if(current_predtick <= 0)
 		return;
 	
-	/* fetch input */	
+	/* fetch input */
 	size = modc_snap_input(inputs[current_input].data);
 	
 	msg_pack_start_system(NETMSG_INPUT, 0);
@@ -1006,6 +1024,9 @@ static void client_run()
 		
 		/* update input */
 		inp_update();
+
+		/* update sound */		
+		snd_update();
 		
 		/* refocus */
 		if(!gfx_window_active())
diff --git a/src/engine/client/ec_gfx.c b/src/engine/client/ec_gfx.c
index 0562a24f..70712ded 100644
--- a/src/engine/client/ec_gfx.c
+++ b/src/engine/client/ec_gfx.c
@@ -489,20 +489,22 @@ int gfx_load_texture_raw(int w, int h, int format, const void *data, int store_f
 	gluBuild2DMipmaps(GL_TEXTURE_2D, store_oglformat, w, h, oglformat, GL_UNSIGNED_BYTE, texdata);
 	
 	/* calculate memory usage */
-	int pixel_size = 4;
-	if(store_format == IMG_RGB)
-		pixel_size = 3;
-	else if(store_format == IMG_ALPHA)
-		pixel_size = 1;
-
-	textures[tex].memsize = w*h*pixel_size;
-	if(mipmap)
 	{
-		while(w > 2 && h > 2)
+		int pixel_size = 4;
+		if(store_format == IMG_RGB)
+			pixel_size = 3;
+		else if(store_format == IMG_ALPHA)
+			pixel_size = 1;
+
+		textures[tex].memsize = w*h*pixel_size;
+		if(mipmap)
 		{
-			w>>=1;
-			h>>=1;
-			textures[tex].memsize += w*h*pixel_size;
+			while(w > 2 && h > 2)
+			{
+				w>>=1;
+				h>>=1;
+				textures[tex].memsize += w*h*pixel_size;
+			}
 		}
 	}
 	
@@ -544,7 +546,9 @@ int gfx_load_mip_texture_raw(int w, int h, int format, const void *data)
 int gfx_load_texture(const char *filename, int store_format)
 {
 	int l = strlen(filename);
+	int id;
 	IMAGE_INFO img;
+	
 	if(l < 3)
 		return 0;
 	if(gfx_load_png(&img, filename))
@@ -552,7 +556,7 @@ int gfx_load_texture(const char *filename, int store_format)
 		if (store_format == IMG_AUTO)
 			store_format = img.format;
 
-		int id = gfx_load_texture_raw(img.width, img.height, img.format, img.data, store_format);
+		id = gfx_load_texture_raw(img.width, img.height, img.format, img.data, store_format);
 		mem_free(img.data);
 		return id;
 	}
@@ -1101,7 +1105,7 @@ void gfx_pretty_text(float x, float y, float size, const char *text, int max_wid
 float gfx_pretty_text_width(float size, const char *text_, int length)
 {
     return gfx_text_width(gfx_font_set, size, text_, length);
-
+	/*
 	const float spacing = 0.05f;
 	float w = 0.0f;
 	const unsigned char *text = (unsigned char *)text_;
@@ -1126,7 +1130,7 @@ float gfx_pretty_text_width(float size, const char *text_, int length)
 		text++;
 	}
 
-	return w;
+	return w;*/
 }
 
 
diff --git a/src/engine/client/ec_inp.c b/src/engine/client/ec_inp.c
index 154342f4..cac69669 100644
--- a/src/engine/client/ec_inp.c
+++ b/src/engine/client/ec_inp.c
@@ -48,19 +48,56 @@ void inp_mouse_relative(int *x, int *y)
 	last_y = ny;
 }
 
-static char last_c = 0;
-static int last_k = 0;
+enum
+{
+	INPUT_BUFFER_SIZE=32
+};
+
+static INPUTEVENT input_events[INPUT_BUFFER_SIZE];
+static int num_events = 0;
+
+static void add_event(char c, int key)
+{
+	if(num_events != INPUT_BUFFER_SIZE)
+	{
+		input_events[num_events].ch = c;
+		input_events[num_events].key = key;
+		num_events++;
+	}
+}
+
+int inp_num_events()
+{
+	return num_events;
+}
+
+void inp_clear_events()
+{
+	num_events = 0;
+}
+
+INPUTEVENT inp_get_event(int index)
+{
+	if(index < 0 || index >= num_events)
+	{
+		INPUTEVENT e = {0,0};
+		return e;
+	}
+	
+	return input_events[index];
+}
+
 
 static void char_callback(int character, int action)
 {
 	if(action == GLFW_PRESS && character < 256)
-		last_c = (char)character;
+		add_event((char)character, 0);
 }
 
 static void key_callback(int key, int action)
 {
 	if(action == GLFW_PRESS)
-		last_k = key;
+		add_event(0, key);
 	
 	if(action == GLFW_PRESS)
 		input_count[input_current^1][key].presses++;
@@ -72,7 +109,7 @@ static void key_callback(int key, int action)
 static void mousebutton_callback(int button, int action)
 {
 	if(action == GLFW_PRESS)
-		last_k = KEY_MOUSE_FIRST+button;
+		add_event(0, KEY_MOUSE_FIRST+button);
 		
 	if(action == GLFW_PRESS)
 		input_count[input_current^1][KEY_MOUSE_FIRST+button].presses++;
@@ -99,7 +136,7 @@ static void mousewheel_callback(int pos)
 			input_count[input_current^1][KEY_MOUSE_WHEEL_UP].releases++;
 		}
 		
-		last_k = KEY_MOUSE_WHEEL_UP;
+		add_event(0, KEY_MOUSE_WHEEL_UP);
 	}
 	else if(pos < 0)
 	{
@@ -109,7 +146,7 @@ static void mousewheel_callback(int pos)
 			input_count[input_current^1][KEY_MOUSE_WHEEL_DOWN].releases++;
 		}	
 
-		last_k = KEY_MOUSE_WHEEL_DOWN;
+		add_event(0, KEY_MOUSE_WHEEL_DOWN);
 	}
 	glfwSetMouseWheel(0);
 }
@@ -124,22 +161,6 @@ void inp_init()
 	glfwSetMouseWheelCallback(mousewheel_callback);
 }
 
-char inp_last_char()
-{
-	return last_c;
-}
-
-int inp_last_key()
-{
-	return last_k;
-}
-
-void inp_clear()
-{
-	last_k = 0;
-	last_c = 0;
-}
-
 void inp_mouse_mode_absolute()
 {
 	glfwEnable(GLFW_MOUSE_CURSOR);
@@ -147,8 +168,8 @@ void inp_mouse_mode_absolute()
 
 void inp_mouse_mode_relative()
 {
-	//if (!config.gfx_debug_resizable)
-	//glfwDisable(GLFW_MOUSE_CURSOR);
+	/*if (!config.gfx_debug_resizable)*/
+	glfwDisable(GLFW_MOUSE_CURSOR);
 }
 
 int inp_mouse_doubleclick()
diff --git a/src/engine/client/ec_snd.c b/src/engine/client/ec_snd.c
index 152eac53..49f050c9 100644
--- a/src/engine/client/ec_snd.c
+++ b/src/engine/client/ec_snd.c
@@ -55,6 +55,7 @@ static int center_x = 0;
 static int center_y = 0;
 
 static int mixing_rate = 48000;
+static volatile int sound_volume = 100;
 
 void snd_set_channel(int cid, float vol, float pan)
 {
@@ -135,10 +136,13 @@ static void mix(short *final_out, unsigned frames)
 {
 	int mix_buffer[MAX_FRAMES*2] = {0};
 	int i, s;
+	int master_vol;
 
 	/* aquire lock while we are mixing */
 	lock_wait(sound_lock);
 	
+	master_vol = sound_volume;
+	
 	for(i = 0; i < NUM_VOICES; i++)
 	{
 		if(voices[i].snd)
@@ -208,12 +212,12 @@ static void mix(short *final_out, unsigned frames)
 			
 		}
 	}
+	
+	
 	/* release the lock */
 	lock_release(sound_lock);
 
 	{
-		int master_vol = config.snd_volume;
-		
 		/* clamp accumulated values */
 		/* TODO: this seams slow */
 		for(i = 0; i < frames; i++)
@@ -283,6 +287,25 @@ int snd_init()
 	err = Pa_StartStream(stream);
 
 	sound_enabled = 1;
+	snd_update(); /* update the volume */
+	return 0;
+}
+
+int snd_update()
+{
+	/* update volume */
+	int wanted_volume = config.snd_volume;
+	
+	if(!gfx_window_active() && config.snd_nonactive_mute)
+		wanted_volume = 0;
+	
+	if(wanted_volume != sound_volume)
+	{
+		lock_wait(sound_lock);
+		sound_volume = wanted_volume;
+		lock_release(sound_lock);
+	}
+	
 	return 0;
 }
 
diff --git a/src/engine/client/ec_srvbrowse.c b/src/engine/client/ec_srvbrowse.c
index d9b3f4ff..3a07d86a 100644
--- a/src/engine/client/ec_srvbrowse.c
+++ b/src/engine/client/ec_srvbrowse.c
@@ -149,6 +149,10 @@ static void client_serverbrowse_filter()
 			filtered = 1;
 		else if(config.b_filter_pw && serverlist[i]->info.flags&1)
 			filtered = 1;
+		else if(config.b_filter_ping < serverlist[i]->info.latency)
+			filtered = 1;
+		else if(!(config.b_filter_gametype&(1<<serverlist[i]->info.game_type)))
+			filtered = 1;
 		else if(config.b_filter_string[0] != 0)
 		{
 			if(strstr(serverlist[i]->info.name, config.b_filter_string) == 0)
@@ -167,6 +171,8 @@ static int client_serverbrowse_sorthash()
 	i |= config.b_filter_full<<5;
 	i |= config.b_filter_pw<<6;
 	i |= config.b_sort_order<<7;
+	i |= config.b_filter_gametype<<8;
+	i |= config.b_filter_ping<<16;
 	return i;
 }
 
diff --git a/src/engine/e_config_variables.h b/src/engine/e_config_variables.h
index 99c10c4b..c3bbc524 100644
--- a/src/engine/e_config_variables.h
+++ b/src/engine/e_config_variables.h
@@ -16,6 +16,9 @@ MACRO_CONFIG_STR(b_filter_string, 64, "")
 MACRO_CONFIG_INT(b_filter_full, 0, 0, 1)
 MACRO_CONFIG_INT(b_filter_empty, 0, 0, 1)
 MACRO_CONFIG_INT(b_filter_pw, 0, 0, 1)
+MACRO_CONFIG_INT(b_filter_ping, 999, 0, 999)
+MACRO_CONFIG_INT(b_filter_gametype, 0xf, 0, 0xf)
+
 MACRO_CONFIG_INT(b_sort, 0, 0, 256)
 MACRO_CONFIG_INT(b_sort_order, 0, 0, 1)
 MACRO_CONFIG_INT(b_max_requests, 10, 0, 1000)
@@ -25,6 +28,8 @@ MACRO_CONFIG_INT(snd_enable, 1, 0, 1)
 MACRO_CONFIG_INT(snd_volume, 100, 0, 100)
 MACRO_CONFIG_INT(snd_device, -1, 0, 0)
 
+MACRO_CONFIG_INT(snd_nonactive_mute, 0, 0, 1)
+
 MACRO_CONFIG_INT(gfx_screen_width, 800, 0, 0)
 MACRO_CONFIG_INT(gfx_screen_height, 600, 0, 0)
 MACRO_CONFIG_INT(gfx_fullscreen, 1, 0, 1)
diff --git a/src/engine/e_interface.h b/src/engine/e_interface.h
index 3f14745c..666d15e5 100644
--- a/src/engine/e_interface.h
+++ b/src/engine/e_interface.h
@@ -396,6 +396,7 @@ void gfx_quads_text(float x, float y, float size, const char *text);
 
 /* sound (client) */
 int snd_init();
+int snd_update();
 
 void snd_set_channel(int cid, float vol, float pan);
 
@@ -758,6 +759,9 @@ const char *mods_version();
 /* server */
 int server_getclientinfo(int client_id, CLIENT_INFO *info);
 const char *server_clientname(int client_id);
+
+/* grabs the latest input for the client. not withholding anything */
+int *server_latestinput(int client_id, int *size);
 void server_setclientname(int client_id, const char *name);
 void server_setclientscore(int client_id, int score);
 
@@ -770,9 +774,19 @@ int server_tickspeed();
 /* input */
 int inp_key_was_pressed(int key);
 int inp_key_down(int key);
-char inp_last_char();
-int inp_last_key();
-void inp_clear();
+
+
+
+typedef struct
+{
+	char ch;
+	int key;
+} INPUTEVENT;
+
+int inp_num_events();
+INPUTEVENT inp_get_event(int index);
+void inp_clear_events();
+
 void inp_update();
 void inp_init();
 void inp_mouse_mode_absolute();
@@ -831,6 +845,7 @@ float client_intrapredtick();
 int client_tickspeed();
 float client_frametime();
 float client_localtime();
+void client_direct_input(int *input, int size);
 
 int client_state();
 const char *client_error_string();
diff --git a/src/engine/server/es_server.c b/src/engine/server/es_server.c
index 7cdaa0dc..fda4b5d5 100644
--- a/src/engine/server/es_server.c
+++ b/src/engine/server/es_server.c
@@ -89,6 +89,7 @@ typedef struct
 	int last_acked_snapshot;
 	SNAPSTORAGE snapshots;
 	
+	CLIENT_INPUT latestinput;
 	CLIENT_INPUT inputs[200]; /* TODO: handle input better */
 	int current_input;
 	
@@ -184,6 +185,13 @@ void snap_free_id(int id)
 	}
 }
 
+int *server_latestinput(int client_id, int *size)
+{
+	if(client_id < 0 || client_id > MAX_CLIENTS || clients[client_id].state < SRVCLIENT_STATE_READY)
+		return 0;
+	return clients[client_id].latestinput.data;
+}
+
 const char *server_clientname(int client_id)
 {
 	if(client_id < 0 || client_id > MAX_CLIENTS || clients[client_id].state < SRVCLIENT_STATE_READY)
@@ -489,6 +497,8 @@ static int new_client_callback(int cid, void *user)
 	}
 	clients[cid].current_input = 0;
 	
+	mem_zero(&clients[cid].latestinput, sizeof(clients[cid].latestinput));
+	
 	snapstorage_purge_all(&clients[cid].snapshots);
 	clients[cid].last_acked_snapshot = -1;
 	clients[cid].snap_rate = SRVCLIENT_SNAPRATE_INIT;
@@ -624,7 +634,9 @@ static void server_process_client_packet(NETPACKET *packet)
 			
 			for(i = 0; i < size/4; i++)
 				input->data[i] = msg_unpack_int();
-				
+			
+			mem_copy(clients[cid].latestinput.data, input->data, MAX_INPUT_SIZE*sizeof(int));
+			
 			clients[cid].current_input++;
 			clients[cid].current_input %= 200;
 		}
diff --git a/src/game/client/gc_client.cpp b/src/game/client/gc_client.cpp
index 5598c44f..68c6db00 100644
--- a/src/game/client/gc_client.cpp
+++ b/src/game/client/gc_client.cpp
@@ -33,6 +33,8 @@ data_container *data = 0x0;
 static player_input input_data = {0};
 static int input_target_lock = 0;
 
+static int64 debug_firedelay = 0;
+
 extern void modmenu_render();
 extern void menu_init();
 
@@ -79,8 +81,25 @@ static struct client_data
 	int emoticon_start;
 	player_core predicted;
 	
-	tee_render_info skin_info;
+	tee_render_info skin_info; // this is what the server reports
+	tee_render_info render_info; // this is what we use
 	
+	void update_render_info()
+	{
+		render_info = skin_info;
+
+		// force team colors
+		if(gameobj && gameobj->gametype != GAMETYPE_DM)
+		{
+			const int team_colors[2] = {65387, 10223467};
+			if(team >= 0 || team <= 1)
+			{
+				render_info.texture = skin_get(skin_id)->color_texture;
+				render_info.color_body = skin_get_color(team_colors[team]);
+				render_info.color_feet = skin_get_color(team_colors[team]);
+			}
+		}		
+	}
 } client_datas[MAX_CLIENTS];
 
 class client_effects
@@ -915,6 +934,9 @@ extern "C" void modc_newsnapshot()
 			if(item.type == OBJTYPE_PLAYER_INFO)
 			{
 				const obj_player_info *info = (const obj_player_info *)data;
+				
+				client_datas[info->clientid].team = info->team;
+				
 				if(info->local)
 				{
 					local_info = info;
@@ -938,6 +960,9 @@ extern "C" void modc_newsnapshot()
 			}
 		}
 	}
+
+	for(int i = 0; i < MAX_CLIENTS; i++)
+		client_datas[i].update_render_info();
 }
 
 void send_info(bool start)
@@ -963,8 +988,15 @@ void send_emoticon(int emoticon)
 	client_send_msg();
 }
 
-static void render_projectile(const obj_projectile *prev, const obj_projectile *current, int itemid)
+static void render_projectile(const obj_projectile *current, int itemid)
 {
+	if(debug_firedelay)
+	{
+		debug_firedelay = time_get()-debug_firedelay;
+		dbg_msg("game", "firedelay=%.2f ms", debug_firedelay/(float)time_freq()*1000.0f);
+		debug_firedelay = 0;
+	}
+	
 	gfx_texture_set(data->images[IMAGE_GAME].id);
 	gfx_quads_begin();
 
@@ -1143,10 +1175,10 @@ void anim_eval_add(animstate *state, animation *anim, float time, float amount)
 	anim_add(state, &add, amount);
 }
 
-static void render_hand(int skin_id, vec2 center_pos, vec2 dir, float angle_offset, vec2 post_rot_offset)
+static void render_hand(tee_render_info *info, vec2 center_pos, vec2 dir, float angle_offset, vec2 post_rot_offset)
 {
 	// for drawing hand
-	const skin *s = skin_get(skin_id);
+	//const skin *s = skin_get(skin_id);
 	
 	float basesize = 10.0f;
 	//dir = normalize(hook_pos-pos);
@@ -1168,8 +1200,9 @@ static void render_hand(int skin_id, vec2 center_pos, vec2 dir, float angle_offs
 	hand_pos += diry * post_rot_offset.y;
 
 	//gfx_texture_set(data->images[IMAGE_CHAR_DEFAULT].id);
-	gfx_texture_set(s->color_texture);
+	gfx_texture_set(info->texture);
 	gfx_quads_begin();
+	gfx_setcolor(info->color_body.r, info->color_body.g, info->color_body.b, info->color_body.a);
 
 	// two passes
 	for (int i = 0; i < 2; i++)
@@ -1382,11 +1415,6 @@ static void render_player(
 		}
 	}
 
-	// TODO: proper skin selection
-	int skin_id = client_datas[info.clientid].skin_id; //charids[info.clientid];
-	//if(gametype != GAMETYPE_DM)
-		//skin_id = info.team*9; // 0 or 9
-
 	vec2 direction = get_direction(player.angle);
 	float angle = player.angle/256.0f;
 	vec2 position = mix(vec2(prev.x, prev.y), vec2(player.x, player.y), intratick);
@@ -1463,7 +1491,7 @@ static void render_player(
 		gfx_quads_setrotation(0);
 		gfx_quads_end();
 
-		render_hand(skin_id, position, normalize(hook_pos-pos), -pi/2, vec2(20, 0));
+		render_hand(&client_datas[info.clientid].render_info, position, normalize(hook_pos-pos), -pi/2, vec2(20, 0));
 	}
 
 	// draw gun
@@ -1581,9 +1609,9 @@ static void render_player(
 
 		switch (player.weapon)
 		{
-			case WEAPON_GUN: render_hand(skin_id, p, direction, -3*pi/4, vec2(-15, 4)); break;
-			case WEAPON_SHOTGUN: render_hand(skin_id, p, direction, -pi/2, vec2(-5, 4)); break;
-			case WEAPON_ROCKET: render_hand(skin_id, p, direction, -pi/2, vec2(-4, 7)); break;
+			case WEAPON_GUN: render_hand(&client_datas[info.clientid].render_info, p, direction, -3*pi/4, vec2(-15, 4)); break;
+			case WEAPON_SHOTGUN: render_hand(&client_datas[info.clientid].render_info, p, direction, -pi/2, vec2(-5, 4)); break;
+			case WEAPON_ROCKET: render_hand(&client_datas[info.clientid].render_info, p, direction, -pi/2, vec2(-4, 7)); break;
 		}
 
 	}
@@ -1592,14 +1620,14 @@ static void render_player(
 	if(info.local && config.debug)
 	{
 		vec2 ghost_position = mix(vec2(prev_char->x, prev_char->y), vec2(player_char->x, player_char->y), client_intratick());
-		tee_render_info ghost = client_datas[info.clientid].skin_info;
+		tee_render_info ghost = client_datas[info.clientid].render_info;
 		ghost.color_body.a = 0.5f;
 		ghost.color_feet.a = 0.5f;
 		render_tee(&state, &ghost, player.emote, direction, ghost_position); // render ghost
 	}
 
 	// render the tee
-	render_tee(&state, &client_datas[info.clientid].skin_info, player.emote, direction, position);
+	render_tee(&state, &client_datas[info.clientid].render_info, player.emote, direction, position);
 
 	if(player.state == STATE_CHATTING)
 	{
@@ -2103,7 +2131,7 @@ void render_scoreboard(float x, float y, float w, int team, const char *title)
 			gfx_quads_end();
 		}
 		
-		render_tee(&idlestate, &client_datas[info->clientid].skin_info, EMOTE_NORMAL, vec2(1,0), vec2(x+90, y+28));
+		render_tee(&idlestate, &client_datas[info->clientid].render_info, EMOTE_NORMAL, vec2(1,0), vec2(x+90, y+28));
 
 		
 		y += 50.0f;
@@ -2202,9 +2230,9 @@ void render_world(float center_x, float center_y, float zoom)
 
 			if(item.type == OBJTYPE_PROJECTILE)
 			{
-				const void *prev = snap_find_item(SNAP_PREV, item.type, item.id);
-				if(prev)
-					render_projectile((const obj_projectile *)prev, (const obj_projectile *)data, item.id);
+				//const void *prev = snap_find_item(SNAP_PREV, item.type, item.id);
+				//if(prev)
+				render_projectile((const obj_projectile *)data, item.id);
 			}
 			else if(item.type == OBJTYPE_POWERUP)
 			{
@@ -2235,8 +2263,6 @@ void render_world(float center_x, float center_y, float zoom)
 				const void *prev_info = snap_find_item(SNAP_PREV, OBJTYPE_PLAYER_INFO, item.id);
 				const void *info = snap_find_item(SNAP_CURRENT, OBJTYPE_PLAYER_INFO, item.id);
 
-				client_datas[((const obj_player_info *)info)->clientid].team = ((const obj_player_info *)info)->team;
-				
 				if(prev && prev_info && info)
 				{
 					render_player(
@@ -2364,28 +2390,29 @@ void render_game()
 				chat_mode = CHATMODE_NONE;
 			}
 
-			int c = inp_last_char();
-			int k = inp_last_key();
-
-			if (!(c >= 0 && c < 32))
+			for(int i = 0; i < inp_num_events(); i++)
 			{
-				if (chat_input_len < sizeof(chat_input) - 1)
+				INPUTEVENT e = inp_get_event(i);
+
+				if (!(e.ch >= 0 && e.ch < 32))
 				{
-					chat_input[chat_input_len] = c;
-					chat_input[chat_input_len+1] = 0;
-					chat_input_len++;
+					if (chat_input_len < sizeof(chat_input) - 1)
+					{
+						chat_input[chat_input_len] = e.ch;
+						chat_input[chat_input_len+1] = 0;
+						chat_input_len++;
+					}
 				}
-			}
 
-			if(k == KEY_BACKSPACE)
-			{
-				if(chat_input_len > 0)
+				if(e.key == KEY_BACKSPACE)
 				{
-					chat_input[chat_input_len-1] = 0;
-					chat_input_len--;
+					if(chat_input_len > 0)
+					{
+						chat_input[chat_input_len-1] = 0;
+						chat_input_len--;
+					}
 				}
 			}
-
 		}
 		else
 		{
@@ -2413,7 +2440,7 @@ void render_game()
 	}
 
 	if (!menu_active)
-		inp_clear();
+		inp_clear_events();
 
 	// fetch new input
 	if(!menu_active && !emoticon_selector_active)
@@ -2451,6 +2478,8 @@ void render_game()
 	// update some input
 	if(!menu_active && chat_mode == CHATMODE_NONE)
 	{
+		bool do_direct = false;
+		
 		if(!emoticon_selector_active)
 		{
 			if(do_input(&input_data.fire, config.key_fire))
@@ -2460,6 +2489,17 @@ void render_game()
 				input_data.target_x = (int)mouse_pos.x;
 				input_data.target_y = (int)mouse_pos.y;
 				input_target_lock = 1;
+				
+				if(inp_key_presses(config.key_fire))
+				{
+					if(config.dbg_firedelay)
+					{
+						if(debug_firedelay == 0)
+							debug_firedelay = time_get();
+					}
+					
+					do_direct = true;
+				}
 			}
 		}
 
@@ -2480,6 +2520,9 @@ void render_game()
 			if(inp_key_presses(config.key_weapon5)) input_data.wanted_weapon = 5;
 			if(inp_key_presses(config.key_weapon6)) input_data.wanted_weapon = 6;
 		}
+		
+		if(do_direct) // do direct input if wanted
+			client_direct_input((int *)&input_data, sizeof(input_data));
 	}
 
 
@@ -2720,7 +2763,7 @@ void render_game()
 				}
 			}
 			
-			render_tee(&idlestate, &client_datas[killmsgs[r].victim].skin_info, EMOTE_PAIN, vec2(-1,0), vec2(x, y+28));
+			render_tee(&idlestate, &client_datas[killmsgs[r].victim].render_info, EMOTE_PAIN, vec2(-1,0), vec2(x, y+28));
 			x -= 32.0f;
 			
 			// render weapon
@@ -2756,7 +2799,7 @@ void render_game()
 				
 				// render killer tee
 				x -= 24.0f;
-				render_tee(&idlestate, &client_datas[killmsgs[r].killer].skin_info, EMOTE_ANGRY, vec2(1,0), vec2(x, y+28));
+				render_tee(&idlestate, &client_datas[killmsgs[r].killer].render_info, EMOTE_ANGRY, vec2(1,0), vec2(x, y+28));
 				x -= 32.0f;
 
 				// render killer name
@@ -2913,7 +2956,7 @@ void render_game()
 							const char *name = client_datas[id].name;
 							float w = gfx_pretty_text_width(10, name, -1);
 							gfx_pretty_text(whole-40-5-w, 300-40-15+t*20+2, 10, name, -1);
-							tee_render_info info = client_datas[id].skin_info;
+							tee_render_info info = client_datas[id].render_info;
 							info.size = 18.0f;
 							
 							render_tee(&idlestate, &info, EMOTE_NORMAL, vec2(1,0),
@@ -3117,6 +3160,8 @@ extern "C" void modc_statechange(int state, int old)
 	 	menu_do_disconnected();
 	 	menu_game_active = false;
 	}
+	else if(state == CLIENTSTATE_LOADING)
+		menu_do_connecting();
 	else if(state == CLIENTSTATE_CONNECTING)
 		menu_do_connecting();
 	else if (state == CLIENTSTATE_ONLINE)
@@ -3171,6 +3216,8 @@ extern "C" void modc_message(int msg)
 			client_datas[cid].skin_info.color_body = vec4(1,1,1,1);
 			client_datas[cid].skin_info.color_feet = vec4(1,1,1,1);
 		}
+		
+		client_datas[cid].update_render_info();
 	}
     else if(msg == MSG_WEAPON_PICKUP)
     {
diff --git a/src/game/client/gc_menu.cpp b/src/game/client/gc_menu.cpp
index 9101fc93..17716bd6 100644
--- a/src/game/client/gc_menu.cpp
+++ b/src/game/client/gc_menu.cpp
@@ -451,6 +451,7 @@ static void ui2_draw_checkbox(const void *id, const char *text, int checked, con
 	ui2_draw_checkbox_common(id, text, checked?"X":"", r);
 }
 
+
 static void ui2_draw_checkbox_number(const void *id, const char *text, int checked, const RECT *r, void *extra)
 {
 	char buf[16];
@@ -466,8 +467,6 @@ int ui2_do_edit_box(void *id, const RECT *rect, char *str, int str_size, bool hi
 
 	if(ui_last_active_item() == id)
 	{
-		int c = inp_last_char();
-		int k = inp_last_key();
 		int len = strlen(str);
 
 		if (inside && ui_mouse_button(0))
@@ -489,35 +488,42 @@ int ui2_do_edit_box(void *id, const RECT *rect, char *str, int str_size, bool hi
 
 		if (at_index > len)
 			at_index = len;
-
-		if (!(c >= 0 && c < 32))
+			
+		for(int i = 0; i < inp_num_events(); i++)
 		{
-			if (len < str_size - 1 && at_index < str_size - 1)
+			INPUTEVENT e = inp_get_event(i);
+			char c = e.ch;
+			int k = e.key;
+
+			if (!(c >= 0 && c < 32))
 			{
-				memmove(str + at_index + 1, str + at_index, len - at_index + 1);
-				str[at_index] = c;
-				at_index++;
+				if (len < str_size - 1 && at_index < str_size - 1)
+				{
+					memmove(str + at_index + 1, str + at_index, len - at_index + 1);
+					str[at_index] = c;
+					at_index++;
+				}
 			}
-		}
 
-		if (k == KEY_BACKSPACE && at_index > 0)
-		{
-			memmove(str + at_index - 1, str + at_index, len - at_index + 1);
-			at_index--;
+			if (k == KEY_BACKSPACE && at_index > 0)
+			{
+				memmove(str + at_index - 1, str + at_index, len - at_index + 1);
+				at_index--;
+			}
+			else if (k == KEY_DEL && at_index < len)
+				memmove(str + at_index, str + at_index + 1, len - at_index);
+			else if (k == KEY_ENTER)
+				ui_clear_last_active_item();
+			else if (k == KEY_LEFT && at_index > 0)
+				at_index--;
+			else if (k == KEY_RIGHT && at_index < len)
+				at_index++;
+			else if (k == KEY_HOME)
+				at_index = 0;
+			else if (k == KEY_END)
+				at_index = len;
 		}
-		else if (k == KEY_DEL && at_index < len)
-			memmove(str + at_index, str + at_index + 1, len - at_index);
-		else if (k == KEY_ENTER)
-			ui_clear_last_active_item();
-		else if (k == KEY_LEFT && at_index > 0)
-			at_index--;
-		else if (k == KEY_RIGHT && at_index < len)
-			at_index++;
-		else if (k == KEY_HOME)
-			at_index = 0;
-		else if (k == KEY_END)
-			at_index = len;
-
+		
 		r = 1;
 	}
 
@@ -698,14 +704,17 @@ int ui2_do_key_reader(void *id, const RECT *rect, int key)
 
 	if(ui_active_item() == id)
 	{
-		int k = inp_last_key();
-		if (k)
+		for(int i = 0; i < inp_num_events(); i++)
 		{
-			if(k != KEY_ESC)
-				new_key = k;
-				
-			ui_set_active_item(0);
-			mouse_released = false;
+			INPUTEVENT e = inp_get_event(i);
+			if(e.key && e.key != KEY_ESC)
+			{
+				new_key = e.key;
+				ui_set_active_item(0);
+				mouse_released = false;
+				inp_clear_events();
+				break;
+			}
 		}
 	}
 	else if(ui_hot_item() == id)
@@ -912,14 +921,15 @@ static void menu2_render_serverbrowser(RECT main_view)
 	RECT server_scoreboard;
 
 	//ui2_hsplit_t(&view, 20.0f, &status, &view);
-	ui2_hsplit_b(&view, 90.0f, &view, &filters);
+	ui2_hsplit_b(&view, 110.0f, &view, &filters);
 
 	// split off a piece for details and scoreboard
 	ui2_vsplit_r(&view, 200.0f, &view, &server_details);
 
 	// server list
 	ui2_hsplit_t(&view, 20.0f, &headers, &view);
-	ui2_hsplit_b(&view, 5.0f, &view, 0x0);
+	//ui2_hsplit_b(&view, 110.0f, &view, &filters);
+	ui2_hsplit_b(&view, 5.0f, &view, 0);
 	ui2_hsplit_b(&view, 20.0f, &view, &status);
 
 	//ui2_vsplit_r(&filters, 300.0f, &filters, &toolbox);
@@ -1031,7 +1041,7 @@ static void menu2_render_serverbrowser(RECT main_view)
 	int num = (int)(view.h/cols[0].rect.h);
 	static int scrollbar = 0;
 	static float scrollvalue = 0;
-	static int selected_index = -1;
+	//static int selected_index = -1;
 	ui2_hmargin(&scroll, 5.0f, &scroll);
 	scrollvalue = ui2_do_scrollbar_v(&scrollbar, &scroll, scrollvalue);
 	
@@ -1059,8 +1069,8 @@ static void menu2_render_serverbrowser(RECT main_view)
 	RECT original_view = view;
 	view.y -= scrollvalue*scrollnum*cols[0].rect.h;
 	
-	//int r = -1;
-	int new_selected = selected_index;
+	int new_selected = -1;
+	int selected_index = -1;
 	
 	for (int i = 0; i < num_servers; i++)
 	{
@@ -1069,10 +1079,12 @@ static void menu2_render_serverbrowser(RECT main_view)
 		RECT row;
         RECT select_hit_box;
 			
-		int l = selected_index==item_index;
+		int selected = strcmp(item->address, config.ui_server_address) == 0; //selected_index==item_index;
 		
-		if(l)
+		if(selected)
 		{
+			selected_index = i;
+			
 			// selected server, draw the players on it
 			RECT whole;
 			int h = (item->num_players+2)/3;
@@ -1129,13 +1141,9 @@ static void menu2_render_serverbrowser(RECT main_view)
 				select_hit_box.y = original_view.y;
 			}
 			
-			if(ui2_do_button(item, "", l, &select_hit_box, 0, 0))
+			if(ui2_do_button(item, "", selected, &select_hit_box, 0, 0))
 			{
 				new_selected = item_index;
-				dbg_msg("dbg", "addr = %s", item->address);
-				strncpy(config.ui_server_address, item->address, sizeof(config.ui_server_address));
-				if(inp_mouse_doubleclick())
-					client_connect(config.ui_server_address);
 			}
 		}
 		
@@ -1197,21 +1205,20 @@ static void menu2_render_serverbrowser(RECT main_view)
 				else if(item->game_type == GAMETYPE_CTF) type = "CTF";
 				ui2_do_label(&button, type, 15.0f, 0);
 			}
-			/*
-			if(s)
-			{
-				new_selected = item_index;
-				dbg_msg("dbg", "addr = %s", item->address);
-				strncpy(config.ui_server_address, item->address, sizeof(config.ui_server_address));
-			}*/
 		}
 	}
 
 	ui2_clip_disable();
 	
-	selected_index = new_selected;
+	if(new_selected != -1)
+	{
+		// select the new server
+		SERVER_INFO *item = client_serverbrowse_sorted_get(new_selected);
+		strncpy(config.ui_server_address, item->address, sizeof(config.ui_server_address));
+		if(inp_mouse_doubleclick())
+			client_connect(config.ui_server_address);
+	}
 	
-
 	SERVER_INFO *selected_server = client_serverbrowse_sorted_get(selected_index);
 	RECT server_header;
 
@@ -1267,7 +1274,10 @@ static void menu2_render_serverbrowser(RECT main_view)
 
 		char temp[16];
 
-		sprintf(temp, "%d%%", selected_server->progression);
+		if(selected_server->progression < 0)
+			sprintf(temp, "N/A");
+		else
+			sprintf(temp, "%d%%", selected_server->progression);
 		ui2_hsplit_t(&right_column, 15.0f, &row, &right_column);
 		ui2_do_label(&row, temp, 13.0f, -1);
 
@@ -1304,13 +1314,15 @@ static void menu2_render_serverbrowser(RECT main_view)
 		}
 	}
 	
-	// render quick search
 	RECT button;
+	RECT types;
 	ui2_hsplit_t(&filters, 20.0f, &button, &filters);
 	ui2_do_label(&button, "Quick search: ", 14.0f, -1);
 	ui2_vsplit_l(&button, 95.0f, 0, &button);
 	ui2_do_edit_box(&config.b_filter_string, &button, config.b_filter_string, sizeof(config.b_filter_string));
 
+	ui2_vsplit_l(&filters, 180.0f, &filters, &types);
+
 	// render filters
 	ui2_hsplit_t(&filters, 20.0f, &button, &filters);
 	if (ui2_do_button(&config.b_filter_empty, "Has people playing", config.b_filter_empty, &button, ui2_draw_checkbox, 0))
@@ -1321,9 +1333,36 @@ static void menu2_render_serverbrowser(RECT main_view)
 		config.b_filter_full ^= 1;
 
 	ui2_hsplit_t(&filters, 20.0f, &button, &filters);
-	if (ui2_do_button(&config.b_filter_pw, "Is not password protected", config.b_filter_pw, &button, ui2_draw_checkbox, 0))
+	if (ui2_do_button(&config.b_filter_pw, "No password", config.b_filter_pw, &button, ui2_draw_checkbox, 0))
 		config.b_filter_pw ^= 1;
 
+	ui2_hsplit_t(&filters, 2.0f, &button, &filters); // ping
+	ui2_hsplit_t(&filters, 20.0f, &button, &filters);
+	{
+		RECT editbox;
+		ui2_vsplit_l(&button, 40.0f, &editbox, &button);
+		ui2_vsplit_l(&button, 5.0f, &button, &button);
+		
+		char buf[8];
+		sprintf(buf, "%d", config.b_filter_ping);
+		ui2_do_edit_box(&config.b_filter_ping, &editbox, buf, sizeof(buf));
+		config.b_filter_ping = atoi(buf);
+		
+		ui2_do_label(&button, "Maximum ping", 14.0f, -1);
+	}
+
+	ui2_hsplit_t(&types, 20.0f, &button, &types);
+	if (ui2_do_button(&config.b_filter_gametype, "DM", config.b_filter_gametype&(1<<GAMETYPE_DM), &button, ui2_draw_checkbox, 0))
+		config.b_filter_gametype ^= (1<<GAMETYPE_DM);
+
+	ui2_hsplit_t(&types, 20.0f, &button, &types);
+	if (ui2_do_button((char *)&config.b_filter_gametype + 1, "TDM", config.b_filter_gametype&(1<<GAMETYPE_TDM), &button, ui2_draw_checkbox, 0))
+		config.b_filter_gametype ^= (1<<GAMETYPE_TDM);
+
+	ui2_hsplit_t(&types, 20.0f, &button, &types);
+	if (ui2_do_button((char *)&config.b_filter_gametype + 2, "CTF", config.b_filter_gametype&(1<<GAMETYPE_CTF), &button, ui2_draw_checkbox, 0))
+		config.b_filter_gametype ^= (1<<GAMETYPE_CTF);
+
 	// render status
 	ui2_draw_rect(&status, vec4(1,1,1,0.25f), CORNER_B, 5.0f);
 	ui2_vmargin(&status, 50.0f, &status);
@@ -1749,6 +1788,10 @@ static void menu2_render_settings_sound(RECT main_view)
 	if(!config.snd_enable)
 		return;
 	
+	ui2_hsplit_t(&main_view, 20.0f, &button, &main_view);
+	if (ui2_do_button(&config.snd_nonactive_mute, "Mute when not active", config.snd_nonactive_mute, &button, ui2_draw_checkbox, 0))
+		config.snd_nonactive_mute ^= 1;
+		
 	// sample rate box
 	{
 		char buf[64];
@@ -2273,5 +2316,5 @@ void modmenu_render()
     gfx_quads_drawTL(mx,my,24,24);
     gfx_quads_end();
 
-	inp_clear();
+	inp_clear_events();
 }
diff --git a/src/game/g_variables.h b/src/game/g_variables.h
index 594d5cc5..6a9fb35d 100644
--- a/src/game/g_variables.h
+++ b/src/game/g_variables.h
@@ -41,7 +41,7 @@ MACRO_CONFIG_INT(player_color_body, 65408, 0, 0)
 MACRO_CONFIG_INT(player_color_feet, 65408, 0, 0)
 MACRO_CONFIG_STR(player_skin, 64, "default")
 
-MACRO_CONFIG_INT(dbg_new_gui, 0, 0, 1)
+MACRO_CONFIG_INT(dbg_firedelay, 0, 0, 1)
 
 MACRO_CONFIG_INT(ui_page, 1, 0, 5)
 MACRO_CONFIG_STR(ui_server_address, 128, "localhost:8303")
diff --git a/src/game/g_version.h b/src/game/g_version.h
index f9bbcf92..d77410dd 100644
--- a/src/game/g_version.h
+++ b/src/game/g_version.h
@@ -1,4 +1,4 @@
 /* copyright (c) 2007 magnus auvinen, see licence.txt for more info */
 #include "generated/nethash.c"
-#define TEEWARS_VERSION "0.3.1"
+#define TEEWARS_VERSION "0.3.3"
 #define TEEWARS_NETVERSION "0.3 " TEEWARS_NETVERSION_HASH
diff --git a/src/game/server/gs_common.h b/src/game/server/gs_common.h
index 6a18a66b..605bbadc 100644
--- a/src/game/server/gs_common.h
+++ b/src/game/server/gs_common.h
@@ -261,6 +261,11 @@ public:
 	int color_body;
 	int color_feet;
 
+
+	// these are non-heldback inputs
+	player_input latest_previnput;
+	player_input latest_input;
+
 	// input	
 	player_input previnput;
 	player_input input;
diff --git a/src/game/server/gs_server.cpp b/src/game/server/gs_server.cpp
index 3648788d..565e699a 100644
--- a/src/game/server/gs_server.cpp
+++ b/src/game/server/gs_server.cpp
@@ -589,9 +589,10 @@ static float evaluate_spawn(spawneval *eval, vec2 pos)
 		if(!(players[c].flags&entity::FLAG_PHYSICS))
 			continue;
 		
-		// don't count friends
+		// team mates are not as dangerous as enemies
+		float scoremod = 1.0f;
 		if(eval->friendly_team != -1 && players[c].team == eval->friendly_team)
-			continue;
+			scoremod = 0.5f;
 			
 		float d = distance(pos, players[c].pos);
 		if(d == 0)
@@ -734,7 +735,7 @@ bool player::is_grounded()
 
 int player::handle_ninja()
 {
-	vec2 direction = normalize(vec2(input.target_x, input.target_y));
+	vec2 direction = normalize(vec2(latest_input.target_x, latest_input.target_y));
 
 	if ((server_tick() - ninja_activationtick) > (data->weapons[WEAPON_NINJA].duration * server_tickspeed() / 1000))
 	{
@@ -746,7 +747,7 @@ int player::handle_ninja()
 	}
 
 	// Check if it should activate
-	if (count_input(previnput.fire, input.fire).presses && (server_tick() > currentcooldown))
+	if (count_input(latest_previnput.fire, latest_input.fire).presses && (server_tick() > currentcooldown))
 	{
 		// ok then, activate ninja
 		attack_tick = server_tick();
@@ -958,7 +959,7 @@ int player::handle_bomb()
 
 int player::handle_weapons()
 {
-	vec2 direction = normalize(vec2(input.target_x, input.target_y));
+	vec2 direction = normalize(vec2(latest_input.target_x, latest_input.target_y));
 
 	if(config.dbg_stress)
 	{
@@ -986,8 +987,8 @@ int player::handle_weapons()
 	}
 
 	// select weapon
-	int next = count_input(previnput.next_weapon, input.next_weapon).presses;
-	int prev = count_input(previnput.prev_weapon, input.prev_weapon).presses;
+	int next = count_input(latest_previnput.next_weapon, latest_input.next_weapon).presses;
+	int prev = count_input(latest_previnput.prev_weapon, latest_input.prev_weapon).presses;
 	
 	if(next < 128) // make sure we only try sane stuff
 	{
@@ -1009,7 +1010,7 @@ int player::handle_weapons()
 		}
 	}
 
-	if(input.wanted_weapon) // direct weapon selection
+	if(latest_input.wanted_weapon) // direct weapon selection
 		wanted_weapon = input.wanted_weapon-1;
 
 	if(wanted_weapon < 0 || wanted_weapon >= NUM_WEAPONS)
@@ -1041,7 +1042,7 @@ int player::handle_weapons()
 		if(active_weapon == WEAPON_ROCKET || active_weapon == WEAPON_SHOTGUN)
 			fullauto = true;
 		
-		if(count_input(previnput.fire, input.fire).presses || ((fullauto && input.fire&1) && weapons[active_weapon].ammo))
+		if(count_input(latest_previnput.fire, latest_input.fire).presses || ((fullauto && latest_input.fire&1) && weapons[active_weapon].ammo))
 		{
 			// fire!
 			if(weapons[active_weapon].ammo)
@@ -1211,12 +1212,26 @@ int player::handle_weapons()
 void player::tick()
 {
 	server_setclientscore(client_id, score);
+
+	// grab latest input
+	{
+		int size = 0;
+		int *input = server_latestinput(client_id, &size);
+		if(input)
+		{
+			mem_copy(&latest_previnput, &latest_input, sizeof(latest_input));
+			mem_copy(&latest_input, input, sizeof(latest_input));
+		}
+	}
 	
 	// check if we have enough input
 	// this is to prevent initial weird clicks
 	if(num_inputs < 2)
+	{
+		latest_previnput = latest_input;
 		previnput = input;
-
+	}
+	
 	// do latency stuff
 	{
 		CLIENT_INFO info;
@@ -1254,7 +1269,7 @@ void player::tick()
 	// TODO: rework the input to be more robust
 	if(dead)
 	{
-		if(server_tick()-die_tick >= server_tickspeed()/2 && count_input(previnput.fire, input.fire).presses)
+		if(server_tick()-die_tick >= server_tickspeed()/2 && count_input(latest_previnput.fire, latest_input.fire).presses)
 			die_tick = -1;
 		if(server_tick()-die_tick >= server_tickspeed()*5) // auto respawn after 3 sec
 			respawn();
@@ -1466,7 +1481,7 @@ void player::snap(int snaping_client)
 			info->local = 1;
 	}
 
-	if(health > 0 && distance(players[snaping_client].pos, pos) < 1000.0f)
+	if(health > 0 && team >= 0 && distance(players[snaping_client].pos, pos) < 1000.0f)
 	{
 		obj_player_character *character = (obj_player_character *)snap_new_item(OBJTYPE_PLAYER_CHARACTER, client_id, sizeof(obj_player_character));