about summary refs log tree commit diff
path: root/src/engine
diff options
context:
space:
mode:
authorMagnus Auvinen <magnus.auvinen@gmail.com>2007-08-14 18:37:16 +0000
committerMagnus Auvinen <magnus.auvinen@gmail.com>2007-08-14 18:37:16 +0000
commit2cde04ddcec3f3c083527c464f93bf8c30b6e790 (patch)
tree2666b20bf713f7d5244af1aec9f2d2f54d193f35 /src/engine
parent8809084d253be4e9923307a13c8830c593dfefc0 (diff)
downloadzcatch-2cde04ddcec3f3c083527c464f93bf8c30b6e790.tar.gz
zcatch-2cde04ddcec3f3c083527c464f93bf8c30b6e790.zip
merged over all stuff from 0.2 to trunk
Diffstat (limited to 'src/engine')
-rw-r--r--src/engine/client/client.cpp203
-rw-r--r--src/engine/client/gfx.cpp111
-rw-r--r--src/engine/client/snd.cpp76
-rw-r--r--src/engine/compression.cpp4
-rw-r--r--src/engine/config.cpp2
-rw-r--r--src/engine/config_variables.h10
-rw-r--r--src/engine/interface.h7
-rw-r--r--src/engine/network.cpp151
-rw-r--r--src/engine/network.h15
-rw-r--r--src/engine/packet.h7
-rw-r--r--src/engine/server/server.cpp233
-rw-r--r--src/engine/snapshot.cpp13
-rw-r--r--src/engine/snapshot.h1
13 files changed, 570 insertions, 263 deletions
diff --git a/src/engine/client/client.cpp b/src/engine/client/client.cpp
index f3390c51..3a64db7e 100644
--- a/src/engine/client/client.cpp
+++ b/src/engine/client/client.cpp
@@ -29,7 +29,6 @@ static int info_request_end;
 static int snapshot_part;
 static int64 local_start_time;
 static int64 game_start_time;
-static int current_tick;
 static float latency = 0;
 static int extra_polating = 0;
 static int debug_font;
@@ -37,8 +36,12 @@ static float frametime = 0.0001f;
 static net_client net;
 static netaddr4 master_server;
 static netaddr4 server_address;
-static const char *server_spam_address=0;
 static int window_must_refocus = 0;
+static int snaploss = 0;
+
+static int current_tick = 0;
+static float intratick = 0;
+
 
 // --- input wrappers ---
 static int keyboard_state[2][input::last];
@@ -151,14 +154,9 @@ static void client_snapshot_purge_until(int tick)
 
 static snapshot_info *snapshots[NUM_SNAPSHOT_TYPES];
 static int recived_snapshots;
-static int64 snapshot_start_time;
 static char snapshot_incomming_data[MAX_SNAPSHOT_SIZE];
 
 // ---
-float client_localtime()
-{
-	return (time_get()-local_start_time)/(float)(time_freq());
-}
 
 const void *snap_get_item(int snapid, int index, snap_item *item)
 {
@@ -199,7 +197,7 @@ static void snap_init()
 // ------ time functions ------
 float client_intratick()
 {
-	return (time_get() - snapshot_start_time)/(float)(time_freq()/SERVER_TICK_SPEED);
+	return intratick;
 }
 
 int client_tick()
@@ -217,6 +215,10 @@ float client_frametime()
 	return frametime;
 }
 
+float client_localtime()
+{
+	return (time_get()-local_start_time)/(float)(time_freq());
+}
 
 int menu_loop(); // TODO: what is this?
 
@@ -312,9 +314,11 @@ void client_serverbrowse_refresh(int lan)
 	
 	if(serverlist_lan)
 	{
-		dbg_msg("client", "broadcasting for servers");
+		if(config.debug)
+			dbg_msg("client", "broadcasting for servers");
 		NETPACKET packet;
 		packet.client_id = -1;
+		mem_zero(&packet, sizeof(packet));
 		packet.address.ip[0] = 0;
 		packet.address.ip[1] = 0;
 		packet.address.ip[2] = 0;
@@ -330,8 +334,10 @@ void client_serverbrowse_refresh(int lan)
 	}
 	else
 	{
-		dbg_msg("client", "requesting server list");
+		if(config.debug)
+			dbg_msg("client", "requesting server list");
 		NETPACKET packet;
+		mem_zero(&packet, sizeof(packet));
 		packet.client_id = -1;
 		packet.address = master_server;
 		packet.flags = PACKETFLAG_CONNLESS;
@@ -347,9 +353,12 @@ void client_serverbrowse_refresh(int lan)
 
 static void client_serverbrowse_request(int id)
 {
-	dbg_msg("client", "requesting server info from %d.%d.%d.%d:%d",
-		servers.addresses[id].ip[0], servers.addresses[id].ip[1], servers.addresses[id].ip[2],
-		servers.addresses[id].ip[3], servers.addresses[id].port);
+	if(config.debug)
+	{
+		dbg_msg("client", "requesting server info from %d.%d.%d.%d:%d",
+			servers.addresses[id].ip[0], servers.addresses[id].ip[1], servers.addresses[id].ip[2],
+			servers.addresses[id].ip[3], servers.addresses[id].port);
+	}
 	NETPACKET packet;
 	packet.client_id = -1;
 	packet.address = servers.addresses[id];
@@ -388,7 +397,8 @@ static int state;
 int client_state() { return state; }
 static void client_set_state(int s)
 {
-	dbg_msg("game", "state change. last=%d current=%d", state, s);
+	if(config.debug)
+		dbg_msg("client", "state change. last=%d current=%d", state, s);
 	int old = state;
 	state = s;
 	if(old != s)
@@ -422,6 +432,7 @@ void client_connect(const char *server_address_str)
 	
 	net.connect(&server_address);
 	client_set_state(CLIENTSTATE_CONNECTING);	
+	current_tick = 0;
 }
 
 void client_disconnect()
@@ -459,9 +470,10 @@ static void client_debug_render()
 	static float frametime_avg = 0;
 	frametime_avg = frametime_avg*0.9f + frametime*0.1f;
 	char buffer[512];
-	sprintf(buffer, "send: %6d recv: %6d latency: %4.0f %c gfxmem: %6dk fps: %3d",
+	sprintf(buffer, "send: %6d recv: %6d snaploss: %4d latency: %4.0f %c gfxmem: %6dk fps: %3d",
 		(current.send_bytes-prev.send_bytes)*10,
 		(current.recv_bytes-prev.recv_bytes)*10,
+		snaploss,
 		latency*1000.0f, extra_polating?'E':' ',
 		gfx_memory_usage()/1024,
 		(int)(1.0f/frametime_avg));
@@ -488,7 +500,7 @@ static void client_render()
 
 static void client_error(const char *msg)
 {
-	dbg_msg("game", "error: %s", msg);
+	dbg_msg("client", "error: %s", msg);
 	client_send_error(msg);
 	client_set_state(CLIENTSTATE_QUITING);
 }
@@ -549,7 +561,8 @@ static void client_process_packet(NETPACKET *packet)
 						packet->address.ip[0], packet->address.ip[1], packet->address.ip[2],
 						packet->address.ip[3], packet->address.port);
 
-					dbg_msg("client", "got server info");
+					if(config.debug)
+						dbg_msg("client", "got server info");
 					servers.num++;
 					
 				}
@@ -565,7 +578,8 @@ static void client_process_packet(NETPACKET *packet)
 						servers.infos[i].max_players = unpacker.get_int();
 						servers.infos[i].num_players = unpacker.get_int();
 						servers.infos[i].latency = ((time_get() - servers.request_times[i])*1000)/time_freq();
-						dbg_msg("client", "got server info");
+						if(config.debug)
+							dbg_msg("client", "got server info");
 						break;
 					}
 				}
@@ -611,7 +625,7 @@ static void client_process_packet(NETPACKET *packet)
 				if(msg != NETMSG_SNAPEMPTY)
 					part_size = msg_unpack_int();
 				
-				if(snapshot_part == part)
+				if(snapshot_part == part && game_tick > current_tick)
 				{
 					// TODO: clean this up abit
 					const char *d = (const char *)msg_unpack_raw(part_size);
@@ -620,29 +634,16 @@ static void client_process_packet(NETPACKET *packet)
 				
 					if(snapshot_part == num_parts)
 					{
-						current_tick = game_tick;
-
-						// decompress snapshot
-						void *deltadata = snapshot_empty_delta();
-						int deltasize = sizeof(int)*3;
-
-						unsigned char tmpbuffer[MAX_SNAPSHOT_SIZE];
-						unsigned char tmpbuffer2[MAX_SNAPSHOT_SIZE];
-						if(part_size)
-						{
-							int compsize = zerobit_decompress(snapshot_incomming_data, part_size, tmpbuffer);
-							int intsize = intpack_decompress(tmpbuffer, compsize, tmpbuffer2);
-							deltadata = tmpbuffer2;
-							deltasize = intsize;
-						}
-
+						snapshot_part = 0;
+						
 						// find snapshot that we should use as delta 
 						static snapshot emptysnap;
 						emptysnap.data_size = 0;
 						emptysnap.num_items = 0;
 						
 						snapshot *deltashot = &emptysnap;
-
+						
+						// find delta
 						if(delta_tick >= 0)
 						{
 							//void *delta_data;
@@ -652,11 +653,34 @@ static void client_process_packet(NETPACKET *packet)
 								deltashot = delta_info->snap;
 							else
 							{
-								// TODO: handle this
-								dbg_msg("client", "error, couldn't find the delta snapshot");
+								// couldn't find the delta snapshots that the server used
+								// to compress this snapshot. force the server to resync
+								if(config.debug)
+									dbg_msg("client", "error, couldn't find the delta snapshot");
+								
+								// ack snapshot
+								msg_pack_start_system(NETMSG_SNAPACK, 0);
+								msg_pack_int(-1);
+								msg_pack_end();
+								client_send_msg();
+								return;
 							}
 						}
 
+						// decompress snapshot
+						void *deltadata = snapshot_empty_delta();
+						int deltasize = sizeof(int)*3;
+
+						unsigned char tmpbuffer[MAX_SNAPSHOT_SIZE];
+						unsigned char tmpbuffer2[MAX_SNAPSHOT_SIZE];
+						if(part_size)
+						{
+							int compsize = zerobit_decompress(snapshot_incomming_data, part_size, tmpbuffer);
+							int intsize = intpack_decompress(tmpbuffer, compsize, tmpbuffer2);
+							deltadata = tmpbuffer2;
+							deltasize = intsize;
+						}
+
 						//dbg_msg("UNPACK", "%d unpacked with %d", game_tick, delta_tick);
 						
 						unsigned char tmpbuffer3[MAX_SNAPSHOT_SIZE];
@@ -681,12 +705,17 @@ static void client_process_packet(NETPACKET *packet)
 						// apply snapshot, cycle pointers
 						recived_snapshots++;
 						
+
+						if(current_tick > 0)
+							snaploss += game_tick-current_tick-1;
+						
+						current_tick = game_tick;
+						
 						// we got two snapshots until we see us self as connected
 						if(recived_snapshots <= 2)
 						{
 							snapshots[SNAP_PREV] = snapshots[SNAP_CURRENT];
 							snapshots[SNAP_CURRENT] = snap;
-							snapshot_start_time = time_get();
 						}
 						
 						if(recived_snapshots == 2)
@@ -699,7 +728,8 @@ static void client_process_packet(NETPACKET *packet)
 						int64 t = now - game_tick*time_freq()/50;
 						if(game_start_time == -1 || t < game_start_time)
 						{
-							dbg_msg("client", "adjusted time");
+							if(config.debug)
+								dbg_msg("client", "adjusted time");
 							game_start_time = t;
 						}
 						
@@ -707,11 +737,6 @@ static void client_process_packet(NETPACKET *packet)
 						float current_latency = (now-wanted)/(float)time_freq();
 						latency = latency*0.95f+current_latency*0.05f;
 						
-						//if(recived_snapshots > 2)
-						//	modc_newsnapshot();
-						
-						snapshot_part = 0;
-						
 						// ack snapshot
 						msg_pack_start_system(NETMSG_SNAPACK, 0);
 						msg_pack_int(game_tick);
@@ -781,14 +806,14 @@ static void client_run(const char *direct_connect_server)
 	if(!client_load_data())
 		return;
 
+	// init menu
+	modmenu_init(); // TODO: remove
+	
 	// init snapshotting
 	snap_init();
 	
 	// init the mod
 	modc_init();
-
-	// init menu
-	modmenu_init(); // TODO: remove
 	
 	// open socket
 	NETADDR4 bindaddr;
@@ -837,7 +862,8 @@ static void client_run(const char *direct_connect_server)
 					{
 						snapshots[SNAP_PREV] = snapshots[SNAP_CURRENT];
 						snapshots[SNAP_CURRENT] = next;
-						snapshot_start_time = t;
+						if(snapshots[SNAP_CURRENT] && snapshots[SNAP_PREV])
+							modc_newsnapshot();
 
 						if(snapshots[SNAP_CURRENT] && snapshots[SNAP_PREV])
 							modc_newsnapshot();
@@ -854,12 +880,25 @@ static void client_run(const char *direct_connect_server)
 					break;
 				}
 			}
+			
+			if(snapshots[SNAP_CURRENT] && snapshots[SNAP_PREV])
+			{
+				int64 curtick_start = game_start_time + (snapshots[SNAP_CURRENT]->tick+1)*time_freq()/50;
+				if(latency > 0)
+					curtick_start += (int64)(time_freq()*(latency*1.1f));
+					
+				int64 prevtick_start = game_start_time + (snapshots[SNAP_PREV]->tick+1)*time_freq()/50;
+				if(latency > 0)
+					prevtick_start += (int64)(time_freq()*(latency*1.1f));
+					
+				intratick = (now - prevtick_start) / (float)(curtick_start-prevtick_start);
+			}
 		}
 
 		// send input
 		if(client_state() == CLIENTSTATE_ONLINE)
 		{
-			if(server_spam_address)
+			if(config.stress&1 && client_localtime() > 10.0f)
 				client_disconnect();
 			
 			if(input_is_changed || time_get() > last_input+time_freq())
@@ -870,8 +909,8 @@ static void client_run(const char *direct_connect_server)
 			}
 		}
 		
-		if(client_state() == CLIENTSTATE_OFFLINE && server_spam_address)
-			client_connect(server_spam_address);
+		if(client_state() == CLIENTSTATE_OFFLINE && config.stress && (frames%100) == 0)
+			client_connect(config.cl_stress_server);
 		
 		// update input
 		inp_update();
@@ -933,10 +972,19 @@ static void client_run(const char *direct_connect_server)
 		client_serverbrowse_update();
 		
 		// render
-		client_render();
-		
-		// swap the buffers
-		gfx_swap();
+		if(config.stress)
+		{
+			if((frames%10) == 0)
+			{
+				client_render();
+				gfx_swap();
+			}
+		}
+		else
+		{
+			client_render();
+			gfx_swap();
+		}
 		
 		// check conditions
 		if(client_state() == CLIENTSTATE_QUITING)
@@ -948,8 +996,11 @@ static void client_run(const char *direct_connect_server)
 		
 		if(reporttime < time_get())
 		{
-			dbg_msg("client/report", "fps=%.02f netstate=%d",
-				frames/(float)(reportinterval/time_freq()), net.state());
+			if(config.debug)
+			{
+				dbg_msg("client/report", "fps=%.02f netstate=%d",
+					frames/(float)(reportinterval/time_freq()), net.state());
+			}
 			frames = 0;
 			reporttime += reportinterval;
 		}
@@ -977,12 +1028,24 @@ int main(int argc, char **argv)
 	dbg_msg("client", "starting...");
 	
 	config_reset();
+
 #ifdef CONF_PLATFORM_MACOSX
-		config_load("~/.teewars");
+	const char *config_filename = "~/.teewars";
 #else
-		config_load("default.cfg");
+	const char *config_filename = "default.cfg";
 #endif
 
+	for(int i = 1; i < argc; i++)
+	{
+		if(argv[i][0] == '-' && argv[i][1] == 'f' && argv[i][2] == 0 && argc - i > 1)
+		{
+			config_filename = argv[i+1];
+			i++;
+		}
+	}
+
+	config_load(config_filename);
+
 	const char *direct_connect_server = 0x0;
 	snd_set_master_volume(config.volume / 255.0f);
 	bool editor = false;
@@ -999,28 +1062,12 @@ int main(int argc, char **argv)
 			i++;
 			direct_connect_server = argv[i];
 		}
-		else if(argv[i][0] == '-' && argv[i][1] == 's' && argv[i][2] == 0 && argc - i > 1)
-		{
-			// -s SERVER:PORT
-			i++;
-			server_spam_address = argv[i];
-		}
-		else if(argv[i][0] == '-' && argv[i][1] == 'n' && argv[i][2] == 0 && argc - i > 1)
-		{
-			// -n NAME
-			i++;
-			config_set_player_name(&config, argv[i]);
-		}
-		else if(argv[i][0] == '-' && argv[i][1] == 'w' && argv[i][2] == 0)
-		{
-			// -w
-			config.gfx_fullscreen = 0;
-		}
-		
 		else if(argv[i][0] == '-' && argv[i][1] == 'e' && argv[i][2] == 0)
 		{
 			editor = true;
 		}
+		else
+			config_set(argv[i]);
 	}
 	
 	if(editor)
diff --git a/src/engine/client/gfx.cpp b/src/engine/client/gfx.cpp
index dafdfa06..6a096139 100644
--- a/src/engine/client/gfx.cpp
+++ b/src/engine/client/gfx.cpp
@@ -122,6 +122,12 @@ bool gfx_init()
 	screen_width = config.gfx_screen_width;
 	screen_height = config.gfx_screen_height;
 
+	if(config.stress)
+	{
+		screen_width = 320;
+		screen_height = 240;
+	}
+	
 	if(config.gfx_fullscreen)
 	{
 		if(!context.create(screen_width, screen_height, 24, 0, 0, 0, opengl::context::FLAG_FULLSCREEN))
@@ -138,7 +144,12 @@ bool gfx_init()
 			return false;
 		}
 	}
-
+	
+	context.set_title("Teewars");
+	
+	// We don't want to see the window when we run the stress testing
+	if(config.stress)
+		context.iconify();
 	
 	// Init vertices
 	if (vertices)
@@ -146,7 +157,6 @@ bool gfx_init()
 	vertices = (custom_vertex*)mem_alloc(sizeof(custom_vertex) * vertex_buffer_size, 1);
 	num_vertices = 0;
 
-	context.set_title("---");
 
 	/*
 	dbg_msg("gfx", "OpenGL version %d.%d.%d", context.version_major(),
@@ -250,6 +260,7 @@ int gfx_get_video_modes(video_mode *list, int maxcount)
 		mem_copy(list, fakemodes, sizeof(fakemodes));
 		return min((int)(sizeof(fakemodes)/sizeof(video_mode)), maxcount);
 	}
+	
 	return context.getvideomodes((opengl::videomode *)list, maxcount);
 }
 
@@ -321,7 +332,8 @@ int gfx_load_texture_raw(int w, int h, int format, const void *data)
 		}
 	}
 	
-	dbg_msg("gfx", "%d = %dx%d", tex, w, h);
+	if(config.debug)
+		dbg_msg("gfx", "%d = %dx%d", tex, w, h);
 	
 	// set data and return
 	if(config.gfx_texture_compression && GLEW_ARB_texture_compression)
@@ -790,64 +802,93 @@ double extra_kerning[256*256] = {0};
 
 pretty_font *current_font = &default_font;
 
-void gfx_pretty_text(float x, float y, float size, const char *text, int max_width)
+static int word_length(const char *text)
 {
+	int s = 1;
+	while(1)
+	{
+		if(*text == 0)
+			return s-1;
+		if(*text == '\n' || *text == '\t' || *text == ' ')
+			return s;
+		text++;
+		s++;
+	}
+}
+
+float gfx_pretty_text_raw(float x, float y, float size, const char *text_, int length)
+{
+	const unsigned char *text = (unsigned char *)text_;
 	const float spacing = 0.05f;
 	gfx_texture_set(current_font->font_texture);
 	gfx_quads_begin();
 	
-	float startx = x;
+	if(length < 0)
+		length = strlen(text_);
 	
-	while (*text)
+	while(length)
 	{
 		const int c = *text;
+		text++;
+	
+		const float width = current_font->m_CharEndTable[c] - current_font->m_CharStartTable[c];
+
+		x -= size * current_font->m_CharStartTable[c];
 		
-		if(c == '\n')
-		{
-			x = startx;
-			y += size;
-		}
-		else
-		{
-			const float width = current_font->m_CharEndTable[c] - current_font->m_CharStartTable[c];
+		gfx_quads_setsubset(
+			(c%16)/16.0f, // startx
+			(c/16)/16.0f, // starty
+			(c%16)/16.0f+1.0f/16.0f, // endx
+			(c/16)/16.0f+1.0f/16.0f); // endy
 
-			x -= size * current_font->m_CharStartTable[c];
-			
-			gfx_quads_setsubset(
-				(c%16)/16.0f, // startx
-				(c/16)/16.0f, // starty
-				(c%16)/16.0f+1.0f/16.0f, // endx
-				(c/16)/16.0f+1.0f/16.0f); // endy
+		gfx_quads_drawTL(x, y, size, size);
 
-			gfx_quads_drawTL(x, y, size, size);
+		double x_nudge = 0;
+		if(length > 1 && text[1])
+			x_nudge = extra_kerning[text[0] + text[1] * 256];
 
-			double x_nudge = 0;
-			if (text[1])
-				x_nudge = extra_kerning[text[0] + text[1] * 256];
+		x += (width + current_font->m_CharStartTable[c] + spacing + x_nudge) * size;
+		length--;
+	}
 
-			x += (width + current_font->m_CharStartTable[c] + spacing + x_nudge) * size;
+	gfx_quads_end();
+	
+	return x;
+}
 
-			if (max_width != -1 && x - startx > max_width)
+void gfx_pretty_text(float x, float y, float size, const char *text, int max_width)
+{
+	if(max_width == -1)
+		gfx_pretty_text_raw(x, y, size, text, -1);
+	else
+	{
+		float startx = x;
+		while(*text)
+		{
+			int wlen = word_length(text);
+			float w = gfx_pretty_text_width(size, text, wlen);
+			if(x+w-startx > max_width)
 			{
+				y += size-2;
 				x = startx;
-				y += size - 2;
 			}
+			
+			x = gfx_pretty_text_raw(x, y, size, text, wlen);
+			
+			text += wlen;
 		}
-
-		text++;
 	}
-
-	gfx_quads_end();
 }
 
-float gfx_pretty_text_width(float size, const char *text, int length)
+float gfx_pretty_text_width(float size, const char *text_, int length)
 {
 	const float spacing = 0.05f;
 	float w = 0.0f;
+	const unsigned char *text = (unsigned char *)text_;
 
-	const char *stop;
+	const unsigned char *stop;
 	if (length == -1)
-		stop = text + strlen(text);
+		stop = text + strlen((char*)text);
 	else
 		stop = text + length;
 
diff --git a/src/engine/client/snd.cpp b/src/engine/client/snd.cpp
index c28efe05..7b9efaa2 100644
--- a/src/engine/client/snd.cpp
+++ b/src/engine/client/snd.cpp
@@ -3,6 +3,7 @@
 #include <baselib/stream/file.h>
 
 #include <engine/interface.h>
+#include <engine/config.h>
 
 extern "C" {
 #include "../../wavpack/wavpack.h"
@@ -24,6 +25,16 @@ static const float GLOBAL_SOUND_DELAY = 0.05f;
 class sound_data
 {
 public:
+	sound_data() :
+		data(0x0),
+		num_samples(0),
+		rate(0),
+		channels(0),
+		sustain_start(-1),
+		sustain_end(-1),
+		last_played(0)
+	{ }
+
 	short *data;
 	int num_samples;
 	int rate;
@@ -33,13 +44,14 @@ public:
 	int64 last_played;
 };
 
-inline short clamp(int i)
+template<typename T>
+inline const T clamp(const T val, const T lower, const T upper)
 {
-	if(i > 0x7fff)
-		return 0x7fff;
-	if(i < -0x7fff)
-		return -0x7fff;
-	return i;
+	if(val > upper)
+		return upper;
+	if(val < lower)
+		return lower;
+	return val;
 }
 
 static class mixer : public audio_stream
@@ -65,34 +77,40 @@ public:
 	enum
 	{
 		MAX_CHANNELS=32,
+		MAX_FILL_FRAMES=256,
 	};
 
 	channel channels[MAX_CHANNELS];
+	int buffer[MAX_FILL_FRAMES*2];
 
-	void fill_mono(short *out, unsigned long frames, channel *c, float dv = 0.0f)
+	void fill_mono(int *out, unsigned long frames, channel *c, float dv = 0.0f)
 	{
+		float pl = clamp(1.0f - c->pan, 0.0f, 1.0f);
+		float pr = clamp(1.0f + c->pan, 0.0f, 1.0f);
+
 		for(unsigned long i = 0; i < frames; i++)
 		{
-			float p = (1.0f-(c->pan+1.0f)*0.5f);
-			int val = (int)(p*c->vol * master_volume * c->data->data[c->tick]);
-			out[i<<1] += (short)val;
-			out[(i<<1)+1] += (short)val;
+			float val = c->vol * master_volume * c->data->data[c->tick];
+
+			out[i<<1] += (int)(pl*val);
+			out[(i<<1)+1] += (int)(pr*val);
 			c->tick++;
 			c->vol += dv;
 			if(c->vol < 0.0f) c->vol = 0.0f;
 		}
 	}
 
-	void fill_stereo(short *out, unsigned long frames, channel *c, float dv = 0.0f)
+	void fill_stereo(int *out, unsigned long frames, channel *c, float dv = 0.0f)
 	{
+		float pl = clamp(1.0f - c->pan, 0.0f, 1.0f);
+		float pr = clamp(1.0f + c->pan, 0.0f, 1.0f);
+
 		for(unsigned long i = 0; i < frames; i++)
 		{
-			float pl = c->pan<0.0f?-c->pan:1.0f;
-			float pr = c->pan>0.0f?1.0f-c->pan:1.0f;
 			int vl = (int)(pl*c->vol * master_volume * c->data->data[c->tick]);
 			int vr = (int)(pr*c->vol * master_volume * c->data->data[c->tick + 1]);
-			out[i<<1] += (short)vl;
-			out[(i<<1)+1] += (short)vr;
+			out[i<<1] += vl;
+			out[(i<<1)+1] += vr;
 			c->tick += 2;
 			c->vol += dv;
 			if(c->vol < 0.0f) c->vol = 0.0f;
@@ -103,10 +121,12 @@ public:
 	{
 		short *out = (short*)output;
 
+		dbg_assert(frames <= MAX_FILL_FRAMES, "not enough fill frames in buffer");
+
 		for(unsigned long i = 0; i < frames; i++)
 		{
-			out[i<<1] = 0;
-			out[(i<<1)+1] = 0;
+			buffer[i<<1] = 0;
+			buffer[(i<<1)+1] = 0;
 		}
 
 		for(int c = 0; c < MAX_CHANNELS; c++)
@@ -133,9 +153,9 @@ public:
 				}
 
 				if(channels[c].data->channels == 1)
-					fill_mono(out, to_fill, &channels[c], dv);
+					fill_mono(buffer, to_fill, &channels[c], dv);
 				else
-					fill_stereo(out, to_fill, &channels[c], dv);
+					fill_stereo(buffer, to_fill, &channels[c], dv);
 
 				if(channels[c].loop >= 0 &&
 						channels[c].data->sustain_start >= 0 &&
@@ -156,6 +176,12 @@ public:
 				filled += to_fill;
 			}
 		}
+
+		for(unsigned long i = 0; i < frames; i++)
+		{
+			out[i<<1] = (short)clamp(buffer[i<<1], -0x7fff, 0x7fff);
+			out[(i<<1)+1] = (short)clamp(buffer[(i<<1)+1], -0x7fff, 0x7fff);
+		}
 	}
 	
 	int play(sound_data *sound, unsigned loop, float vol, float pan)
@@ -262,7 +288,7 @@ int snd_load_wv(const char *filename)
 
 	char error[100];
 
-	file = fopen(filename, "r");
+	file = fopen(filename, "rb");
 
 	WavpackContext *context = WavpackOpenFileInput(read_data, error);
 	if (context)
@@ -453,7 +479,8 @@ int snd_load_wav(const char *filename)
 			{
 				unsigned char smpl[36];
 				unsigned char loop[24];
-				dbg_msg("sound/wav", "got sustain");
+				if(config.debug)
+					dbg_msg("sound/wav", "got sustain");
 
 				file.read(smpl, sizeof(smpl));
 				unsigned num_loops = (smpl[28] | (smpl[29]<<8) | (smpl[30]<<16) | (smpl[31]<<24));
@@ -482,7 +509,10 @@ int snd_load_wav(const char *filename)
 	}
 
 	if(id >= 0)
-		dbg_msg("sound/wav", "loaded %s", filename);
+	{
+		if(config.debug)
+			dbg_msg("sound/wav", "loaded %s", filename);
+	}
 	else
 		dbg_msg("sound/wav", "failed to load %s", filename);
 
diff --git a/src/engine/compression.cpp b/src/engine/compression.cpp
index 5c812d23..427eceb5 100644
--- a/src/engine/compression.cpp
+++ b/src/engine/compression.cpp
@@ -32,7 +32,7 @@ const unsigned char *vint_unpack(const unsigned char *src, int *i)
         int sign = (*src>>6)&1; 
         *i = *src&0x3F; 
 
-	while(1)
+	do
 	{ 
 	        if(!(*src&0x80)) break;
 		src++;
@@ -49,7 +49,7 @@ const unsigned char *vint_unpack(const unsigned char *src, int *i)
 	        if(!(*src&0x80)) break;
 		src++;
                 *i |= (*src&(0x7F))<<(6+7+7+7);
-	}
+	} while(0);
 
 	src++;
 	*i ^= -sign; // if(sign) *i = ~(*i)
diff --git a/src/engine/config.cpp b/src/engine/config.cpp
index fbfce3e9..508f6368 100644
--- a/src/engine/config.cpp
+++ b/src/engine/config.cpp
@@ -119,7 +119,7 @@ void config_save(const char *filename)
 #else
 		const char newline[] = "\n";
 #endif
-		const int newline_len = sizeof(newline);
+		const int newline_len = sizeof(newline)-1;
 		
     	#define MACRO_CONFIG_INT(name,def,min,max) { char str[256]; sprintf(str, "%s=%i", #name, config.name); file.write(str, strlen(str)); file.write(newline, newline_len); }
     	#define MACRO_CONFIG_STR(name,len,def) { file.write(#name, strlen(#name)); file.write("=", 1); file.write(config.name, strlen(config.name)); file.write(newline, newline_len); }
diff --git a/src/engine/config_variables.h b/src/engine/config_variables.h
index b8f11c32..ba9852aa 100644
--- a/src/engine/config_variables.h
+++ b/src/engine/config_variables.h
@@ -1,12 +1,16 @@
 #include "../game/game_variables.h"
 
-MACRO_CONFIG_INT(debug, 0, 0, 1)
 MACRO_CONFIG_INT(volume, 200, 0, 255)
 MACRO_CONFIG_INT(cpu_throttle, 0, 0, 1)
 MACRO_CONFIG_STR(player_name, 32, "nameless tee")
 MACRO_CONFIG_STR(clan_name, 32, "")
 MACRO_CONFIG_STR(password, 32, "")
 
+
+MACRO_CONFIG_INT(debug, 0, 0, 1)
+MACRO_CONFIG_INT(stress, 0, 0, 0)
+MACRO_CONFIG_STR(cl_stress_server, 32, "localhost")
+
 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)
@@ -25,3 +29,7 @@ MACRO_CONFIG_STR(sv_name, 128, "unnamed server")
 MACRO_CONFIG_STR(sv_bindaddr, 128, "")
 MACRO_CONFIG_INT(sv_port, 8303, 0, 0)
 MACRO_CONFIG_INT(sv_sendheartbeats, 1, 0, 1)
+MACRO_CONFIG_STR(sv_map, 128, "dm1")
+
+MACRO_CONFIG_INT(sv_max_clients, 8, 1, 8)
+
diff --git a/src/engine/interface.h b/src/engine/interface.h
index 7fa1fedb..31b985b7 100644
--- a/src/engine/interface.h
+++ b/src/engine/interface.h
@@ -12,15 +12,11 @@ enum
 {
 	MAX_CLIENTS=8,
 	SERVER_TICK_SPEED=50,
-	SERVER_CLIENT_TIMEOUT=5,
 	SNAP_CURRENT=0,
 	SNAP_PREV=1,
 	
 	IMG_RGB=0,
 	IMG_RGBA=1,
-	/*
-	IMG_BGR,
-	IMG_BGRA,*/
 	
 	CLIENTSTATE_OFFLINE=0,
 	CLIENTSTATE_CONNECTING,
@@ -795,4 +791,7 @@ void client_quit();
 void client_serverbrowse_refresh(int lan);
 int client_serverbrowse_getlist(server_info **servers);
 
+int snap_new_id();
+void snap_free_id(int id);
+
 #endif
diff --git a/src/engine/network.cpp b/src/engine/network.cpp
index d38b825e..ac28002e 100644
--- a/src/engine/network.cpp
+++ b/src/engine/network.cpp
@@ -1,6 +1,8 @@
 #include <baselib/system.h>
 #include <string.h>
+#include <stdio.h>
 
+#include "config.h"
 #include "network.h"
 #include "ringbuffer.h"
 
@@ -39,6 +41,8 @@ enum
 	NETWORK_PACKETFLAG_VITAL=0x08,
 	NETWORK_PACKETFLAG_RESEND=0x10,
 	NETWORK_PACKETFLAG_CONNLESS=0x20,
+	
+	NETWORK_MAX_SEQACK=0x1000,
 };
 
 static int current_token = 1;
@@ -75,19 +79,23 @@ static void send_packet(NETSOCKET socket, NETADDR4 *addr, NETPACKETDATA *packet)
 
 struct NETCONNECTION
 {
-	unsigned seq;
-	unsigned ack;
+	unsigned short seq;
+	unsigned short ack;
 	unsigned state;
 	
 	int token;
 	
+	int remote_closed;
+	
 	int connected;
 	int disconnected;
 	
 	ring_buffer buffer;
 	
+	int64 last_update_time;
 	int64 last_recv_time;
 	int64 last_send_time;
+	
 	char error_string[256];
 	
 	NETADDR4 peeraddr;
@@ -104,6 +112,10 @@ struct NETSERVER
 {
 	NETSOCKET socket;
 	NETSLOT slots[NETWORK_MAX_CLIENTS];
+	int max_clients;
+	NETFUNC_NEWCLIENT new_client;
+	NETFUNC_NEWCLIENT del_client;
+	void *user_ptr;
 	unsigned char recv_buffer[NETWORK_MAX_PACKET_SIZE];
 };
 
@@ -125,6 +137,7 @@ static void conn_reset(NETCONNECTION *conn)
 {
 	conn->seq = 0;
 	conn->ack = 0;
+	conn->remote_closed = 0;
 	//dbg_msg("connection", "state = %d->%d", conn->state, NETWORK_CONNSTATE_OFFLINE);
 	
 	if(conn->state == NETWORK_CONNSTATE_ONLINE ||
@@ -136,6 +149,7 @@ static void conn_reset(NETCONNECTION *conn)
 	conn->state = NETWORK_CONNSTATE_OFFLINE;
 	conn->last_send_time = 0;
 	conn->last_recv_time = 0;
+	conn->last_update_time = 0;
 	conn->token = -1;
 	conn->buffer.reset();
 }
@@ -176,7 +190,7 @@ static void conn_ack(NETCONNECTION *conn, int ack)
 			break;
 			
 		NETPACKETDATA *resend = (NETPACKETDATA *)i->data();
-		if(resend->seq <= ack)
+		if(resend->seq <= ack || (ack < NETWORK_MAX_SEQACK/3 && resend->seq > NETWORK_MAX_SEQACK/2))
 			conn->buffer.pop_first();
 		else
 			break;
@@ -207,7 +221,9 @@ static void conn_resend(NETCONNECTION *conn)
 static void conn_send(NETCONNECTION *conn, int flags, int data_size, const void *data)
 {
 	if(flags&NETWORK_PACKETFLAG_VITAL)
-		conn->seq++;
+	{
+		conn->seq = (conn->seq+1)%NETWORK_MAX_SEQACK;
+	}
 	
 	NETPACKETDATA p;
 	p.ID[0] = 'T';
@@ -244,6 +260,7 @@ static int conn_connect(NETCONNECTION *conn, NETADDR4 *addr)
 	conn_reset(conn);
 	conn->peeraddr = *addr;
 	conn->token = current_token++;
+	mem_zero(conn->error_string, sizeof(conn->error_string));
 	//dbg_msg("connection", "state = %d->%d", conn->state, NETWORK_CONNSTATE_CONNECT);
 	conn->state = NETWORK_CONNSTATE_CONNECT;
 	conn_send(conn, NETWORK_PACKETFLAG_CONNECT, 0, 0);
@@ -252,10 +269,13 @@ static int conn_connect(NETCONNECTION *conn, NETADDR4 *addr)
 
 static void conn_disconnect(NETCONNECTION *conn, const char *reason)
 {
-	if(reason)
-		conn_send(conn, NETWORK_PACKETFLAG_CLOSE, strlen(reason)+1, reason);
-	else
-		conn_send(conn, NETWORK_PACKETFLAG_CLOSE, 0, 0);
+	if(conn->remote_closed == 0)
+	{
+		if(reason)
+			conn_send(conn, NETWORK_PACKETFLAG_CLOSE, strlen(reason)+1, reason);
+		else
+			conn_send(conn, NETWORK_PACKETFLAG_CLOSE, 0, 0);
+	}
 	conn_reset(conn);
 }
 
@@ -267,12 +287,16 @@ static int conn_feed(NETCONNECTION *conn, NETPACKETDATA *p, NETADDR4 *addr)
 	
 	if(p->flags&NETWORK_PACKETFLAG_CLOSE)
 	{
-		conn_reset(conn);
+		conn->state = NETWORK_CONNSTATE_ERROR;
+		conn->remote_closed = 1;
+		
+		//conn_reset(conn);
 		if(p->data_size)
 			conn_set_error(conn, (char *)p->data);
 		else
 			conn_set_error(conn, "no reason given");
-		dbg_msg("conn", "closed reason='%s'", conn_error(conn));
+		if(config.debug)
+			dbg_msg("conn", "closed reason='%s'", conn_error(conn));
 		return 0;
 	}
 	
@@ -288,7 +312,8 @@ static int conn_feed(NETCONNECTION *conn, NETPACKETDATA *p, NETADDR4 *addr)
 			conn->token = p->token;
 			//dbg_msg("connection", "token set to %d", p->token);
 			conn_send(conn, NETWORK_PACKETFLAG_CONNECT|NETWORK_PACKETFLAG_ACCEPT, 0, 0);
-			dbg_msg("connection", "got connection, sending connect+accept");
+			if(config.debug)
+				dbg_msg("connection", "got connection, sending connect+accept");
 		}
 	}
 	else if(net_addr4_cmp(&conn->peeraddr, addr) == 0)
@@ -310,15 +335,15 @@ static int conn_feed(NETCONNECTION *conn, NETPACKETDATA *p, NETADDR4 *addr)
 				
 			if(p->flags&NETWORK_PACKETFLAG_VITAL)
 			{
-				if(p->seq == conn->ack+1)
+				if(p->seq == (conn->ack+1)%NETWORK_MAX_SEQACK)
 				{
 					// in sequence
-					conn->ack++;
+					conn->ack = (conn->ack+1)%NETWORK_MAX_SEQACK;
 				}
 				else
 				{
 					// out of sequence, request resend
-					//dbg_msg("conn", "asking for resend");
+					dbg_msg("conn", "asking for resend %d %d", p->seq, (conn->ack+1)%NETWORK_MAX_SEQACK);
 					conn_send(conn, NETWORK_PACKETFLAG_RESEND, 0, 0);
 					return 0;
 				}
@@ -361,8 +386,10 @@ static int conn_feed(NETCONNECTION *conn, NETPACKETDATA *p, NETADDR4 *addr)
 		}*/
 		else
 		{
-			conn_reset(conn);
+			//conn_reset(conn);
 			// strange packet, wrong state
+			conn->state = NETWORK_CONNSTATE_ERROR;
+			conn_set_error(conn, "strange state and packet");
 		}
 	}
 	else
@@ -379,15 +406,37 @@ static int conn_update(NETCONNECTION *conn)
 {
 	if(conn->state == NETWORK_CONNSTATE_OFFLINE || conn->state == NETWORK_CONNSTATE_ERROR)
 		return 0;
+
+	// watch out for major hitches		
+	int64 now = time_get();
+	int64 delta = now-conn->last_update_time;
+	if(conn->last_update_time && delta > time_freq()/2)
+	{
+		dbg_msg("conn", "hitch %d", (int)((delta*1000)/time_freq()));
+
+		conn->last_recv_time += delta;
+
+		ring_buffer::item *i = conn->buffer.first();
+		while(i)
+		{
+			NETPACKETDATA *resend = (NETPACKETDATA *)i->data();
+			resend->first_send_time += delta;
+			i = i->next;
+		}
+	}
+		
+	conn->last_update_time = now;
 	
 	// check for timeout
 	if(conn->state != NETWORK_CONNSTATE_OFFLINE &&
 		conn->state != NETWORK_CONNSTATE_CONNECT &&
-		(time_get()-conn->last_recv_time) > time_freq()*3)
+		(now-conn->last_recv_time) > time_freq()*10)
 	{
 		//dbg_msg("connection", "state = %d->%d", conn->state, NETWORK_CONNSTATE_ERROR);
 		conn->state = NETWORK_CONNSTATE_ERROR;
-		conn_set_error(conn, "timeout");
+		char buf[128];
+		sprintf(buf, "timeout %lld %lld %lld %lld", now-conn->last_recv_time, now, conn->last_recv_time, time_freq()*10);
+		conn_set_error(conn, buf);
 	}
 	
 	// check for large buffer errors
@@ -401,11 +450,11 @@ static int conn_update(NETCONNECTION *conn)
 	if(conn->buffer.first())
 	{
 		NETPACKETDATA *resend = (NETPACKETDATA *)conn->buffer.first()->data();
-		if(time_get()-resend->first_send_time > time_freq()*3)
+		if(now-resend->first_send_time > time_freq()*10)
 		{
 			//dbg_msg("connection", "state = %d->%d", conn->state, NETWORK_CONNSTATE_ERROR);
 			conn->state = NETWORK_CONNSTATE_ERROR;
-			conn_set_error(conn, "too weak connection (not acked for 3 seconds)");
+			conn_set_error(conn, "too weak connection (not acked for 10 seconds)");
 		}
 	}
 	
@@ -463,9 +512,18 @@ static int check_packet(unsigned char *buffer, int size, NETPACKETDATA *packet)
 
 NETSERVER *net_server_open(NETADDR4 bindaddr, int max_clients, int flags)
 {
+	NETSOCKET socket = net_udp4_create(bindaddr);
+	if(socket == NETSOCKET_INVALID)
+		return 0;
+	
 	NETSERVER *server = (NETSERVER *)mem_alloc(sizeof(NETSERVER), 1);
 	mem_zero(server, sizeof(NETSERVER));
-	server->socket = net_udp4_create(bindaddr);
+	server->socket = socket;
+	server->max_clients = max_clients;
+	if(server->max_clients > NETWORK_MAX_CLIENTS)
+		server->max_clients = NETWORK_MAX_CLIENTS;
+	if(server->max_clients < 1)
+		server->max_clients = 1;
 	
 	for(int i = 0; i < NETWORK_MAX_CLIENTS; i++)
 		conn_init(&server->slots[i].conn, server->socket);
@@ -473,15 +531,24 @@ NETSERVER *net_server_open(NETADDR4 bindaddr, int max_clients, int flags)
 	return server;
 }
 
+int net_server_set_callbacks(NETSERVER *s, NETFUNC_NEWCLIENT new_client, NETFUNC_DELCLIENT del_client, void *user)
+{
+	s->new_client = new_client;
+	s->del_client = del_client;
+	s->user_ptr = user;
+	return 0;
+}
+
 int net_server_close(NETSERVER *s)
 {
 	// TODO: implement me
 	return 0;
 }
 
+/*
 int net_server_newclient(NETSERVER *s)
 {
-	for(int i = 0; i < NETWORK_MAX_CLIENTS; i++)
+	for(int i = 0; i < s->max_clients; i++)
 	{
 		if(s->slots[i].conn.connected)
 		{
@@ -495,7 +562,7 @@ int net_server_newclient(NETSERVER *s)
 
 int net_server_delclient(NETSERVER *s)
 {
-	for(int i = 0; i < NETWORK_MAX_CLIENTS; i++)
+	for(int i = 0; i < s->max_clients; i++)
 	{
 		if(s->slots[i].conn.disconnected)
 		{
@@ -505,20 +572,24 @@ int net_server_delclient(NETSERVER *s)
 	}
 	
 	return -1;
-}
+}*/
 
 int net_server_drop(NETSERVER *s, int client_id, const char *reason)
 {
 	// TODO: insert lots of checks here
 	dbg_msg("net_server", "client dropped. cid=%d reason=\"%s\"", client_id, reason);
 	conn_disconnect(&s->slots[client_id].conn, reason);
+	
+	if(s->del_client)
+		s->del_client(client_id, s->user_ptr);
+		
 	//conn_reset(&s->slots[client_id].conn);
 	return 0;
 }
 
 int net_server_update(NETSERVER *s)
 {
-	for(int i = 0; i < NETWORK_MAX_CLIENTS; i++)
+	for(int i = 0; i < s->max_clients; i++)
 	{
 		conn_update(&s->slots[i].conn);
 		if(s->slots[i].conn.state == NETWORK_CONNSTATE_ERROR)
@@ -560,7 +631,7 @@ int net_server_recv(NETSERVER *s, NETPACKET *packet)
 					int found = 0;
 					
 					// check if we already got this client
-					for(int i = 0; i < NETWORK_MAX_CLIENTS; i++)
+					for(int i = 0; i < s->max_clients; i++)
 					{
 						if(s->slots[i].conn.state != NETWORK_CONNSTATE_OFFLINE &&
 							net_addr4_cmp(&s->slots[i].conn.peeraddr, &addr) == 0)
@@ -574,27 +645,42 @@ int net_server_recv(NETSERVER *s, NETPACKET *packet)
 					// client that wants to connect
 					if(!found)
 					{
-						for(int i = 0; i < NETWORK_MAX_CLIENTS; i++)
+						for(int i = 0; i < s->max_clients; i++)
 						{
 							if(s->slots[i].conn.state == NETWORK_CONNSTATE_OFFLINE)
 							{
 								//dbg_msg("netserver", "connection started %d", i);
-								conn_feed(&s->slots[i].conn, &data, &addr);
 								found = 1;
+								conn_feed(&s->slots[i].conn, &data, &addr);
+								if(s->new_client)
+									s->new_client(i, s->user_ptr);
 								break;
 							}
 						}
 					}
 					
-					if(found)
+					if(!found)
 					{
-						// TODO: send error
+						// send connectionless packet
+						const char errstring[] = "server full";
+						NETPACKETDATA p;
+						p.ID[0] = 'T';
+						p.ID[1] = 'W';
+						p.version = NETWORK_VERSION;
+						p.flags = NETWORK_PACKETFLAG_CLOSE;
+						p.seq = 0;
+						p.ack = 0;
+						p.crc = 0;
+						p.token = data.token;
+						p.data_size = sizeof(errstring);
+						p.data = (unsigned char *)errstring;
+						send_packet(s->socket, &addr, &p);
 					}
 				}
 				else
 				{
 					// find matching slot
-					for(int i = 0; i < NETWORK_MAX_CLIENTS; i++)
+					for(int i = 0; i < s->max_clients; i++)
 					{
 						if(net_addr4_cmp(&s->slots[i].conn.peeraddr, &addr) == 0)
 						{
@@ -648,7 +734,7 @@ int net_server_send(NETSERVER *s, NETPACKET *packet)
 	else
 	{
 		dbg_assert(packet->client_id >= 0, "errornous client id");
-		dbg_assert(packet->client_id < NETWORK_MAX_CLIENTS, "errornous client id");
+		dbg_assert(packet->client_id < s->max_clients, "errornous client id");
 		int flags  = 0;
 		if(packet->flags&PACKETFLAG_VITAL)
 			flags |= NETWORK_PACKETFLAG_VITAL;
@@ -664,7 +750,7 @@ void net_server_stats(NETSERVER *s, NETSTATS *stats)
 	int num_stats = sizeof(NETSTATS)/sizeof(int);
 	int *istats = (int *)stats;
 	
-	for(int c = 0; c < NETWORK_MAX_CLIENTS; c++)
+	for(int c = 0; c < s->max_clients; c++)
 	{
 		int *sstats = (int *)(&(s->slots[c].conn.stats));
 		for(int i = 0; i < num_stats; i++)
@@ -774,6 +860,7 @@ int net_client_send(NETCLIENT *c, NETPACKET *packet)
 		p.seq = 0;
 		p.ack = 0;
 		p.crc = 0;
+		p.token = 0;
 		p.data_size = packet->data_size;
 		p.data = (unsigned char *)packet->data;
 		send_packet(c->socket, &packet->address, &p);
diff --git a/src/engine/network.h b/src/engine/network.h
index 1bb02c1a..afd0dcd0 100644
--- a/src/engine/network.h
+++ b/src/engine/network.h
@@ -35,15 +35,19 @@ enum
 	NETSTATE_ONLINE,
 };
 
+typedef int (*NETFUNC_DELCLIENT)(int cid, void *user);
+typedef int (*NETFUNC_NEWCLIENT)(int cid, void *user);
+
 // server side
 NETSERVER *net_server_open(NETADDR4 bindaddr, int max_clients, int flags);
+int net_server_set_callbacks(NETSERVER *s, NETFUNC_NEWCLIENT new_client, NETFUNC_DELCLIENT del_client, void *user);
 int net_server_recv(NETSERVER *s, NETPACKET *packet);
 int net_server_send(NETSERVER *s, NETPACKET *packet);
 int net_server_close(NETSERVER *s);
 int net_server_update(NETSERVER *s);
 int net_server_drop(NETSERVER *s, int client_id, const char *reason);
-int net_server_newclient(NETSERVER *s); // -1 when no more, else, client id
-int net_server_delclient(NETSERVER *s); // -1 when no more, else, client id
+//int net_server_newclient(NETSERVER *s); // -1 when no more, else, client id
+//int net_server_delclient(NETSERVER *s); // -1 when no more, else, client id
 void net_server_stats(NETSERVER *s, NETSTATS *stats);
 
 // client side
@@ -71,13 +75,16 @@ public:
 	int open(NETADDR4 bindaddr, int max, int flags) { ptr = net_server_open(bindaddr, max, flags); return ptr != 0; }
 	int close() { int r = net_server_close(ptr); ptr = 0; return r; }
 	
+	int set_callbacks(NETFUNC_NEWCLIENT new_client, NETFUNC_DELCLIENT del_client, void *user)
+	{ return net_server_set_callbacks(ptr, new_client, del_client, user); }
+	
 	int recv(NETPACKET *packet) { return net_server_recv(ptr, packet); }
 	int send(NETPACKET *packet) { return net_server_send(ptr, packet); }
 	int update() { return net_server_update(ptr); }
 	
 	int drop(int client_id, const char *reason) { return net_server_drop(ptr, client_id, reason); } 
-	int newclient() { return net_server_newclient(ptr); }
-	int delclient() { return net_server_delclient(ptr); }
+	//int newclient() { return net_server_newclient(ptr); }
+	//int delclient() { return net_server_delclient(ptr); }
 	
 	void stats(NETSTATS *stats) { net_server_stats(ptr, stats); }
 };
diff --git a/src/engine/packet.h b/src/engine/packet.h
index 79c969c2..1dd3c3ab 100644
--- a/src/engine/packet.h
+++ b/src/engine/packet.h
@@ -1,4 +1,5 @@
 #include <stdarg.h>
+#include <string.h>
 #include <baselib/stream/file.h>
 #include <baselib/network.h>
 
@@ -273,6 +274,9 @@ public:
 	
 	int get_int()
 	{
+		if(current >= end)
+			return 0;
+			
 		int i;
 		current = vint_unpack(current, &i);
 		// TODO: might be changed into variable width
@@ -285,6 +289,9 @@ public:
 	
 	const char *get_string()
 	{
+		if(current >= end)
+			return "";
+			
 		// TODO: add range check
 		// TODO: add debug marker
 		const char *ptr = (const char *)current;
diff --git a/src/engine/server/server.cpp b/src/engine/server/server.cpp
index 3769157e..984292e9 100644
--- a/src/engine/server/server.cpp
+++ b/src/engine/server/server.cpp
@@ -30,6 +30,97 @@ void *snap_new_item(int type, int id, int size)
 	return builder.new_item(type, id, size);
 }
 
+
+struct snap_id
+{
+	short next;
+	short state; // 0 = free, 1 = alloced, 2 = timed
+	int timeout_tick;
+};
+
+static const int MAX_IDS = 8*1024; // should be lowered
+static snap_id snap_ids[8*1024];
+static int snap_first_free_id;
+static int snap_first_timed_id;
+static int snap_last_timed_id;
+static int snap_id_usage;
+static int snap_id_inusage;
+
+static int snap_id_inited = 0;
+
+void snap_init_id()
+{
+	for(int i = 0; i < MAX_IDS; i++)
+	{
+		snap_ids[i].next = i+1;
+		snap_ids[i].state = 0;
+	}
+		
+	snap_ids[MAX_IDS-1].next = -1;
+	snap_first_free_id = 0;
+	snap_first_timed_id = -1;
+	snap_last_timed_id = -1;
+	snap_id_usage = 0;
+	snap_id_inusage = 0;
+	
+	snap_id_inited = 1;
+}
+
+int snap_new_id()
+{
+	dbg_assert(snap_id_inited == 1, "requesting id too soon");
+	
+	// process timed ids
+	while(snap_first_timed_id != -1 && snap_ids[snap_first_timed_id].timeout_tick < server_tick())
+	{
+		int next_timed = snap_ids[snap_first_timed_id].next;
+		
+		// add it to the free list
+		snap_ids[snap_first_timed_id].next = snap_first_free_id;
+		snap_ids[snap_first_timed_id].state = 0;
+		snap_first_free_id = snap_first_timed_id;
+		
+		// remove it from the timed list
+		snap_first_timed_id = next_timed;
+		if(snap_first_timed_id == -1)
+			snap_last_timed_id = -1;
+			
+		snap_id_usage--;
+	}
+	
+	int id = snap_first_free_id;
+	dbg_assert(id != -1, "id error");
+	snap_first_free_id = snap_ids[snap_first_free_id].next;
+	snap_ids[id].state = 1;
+	snap_id_usage++;
+	snap_id_inusage++;
+	return id;
+}
+
+void snap_free_id(int id)
+{
+	dbg_assert(snap_ids[id].state == 1, "id is not alloced");
+
+	snap_id_inusage--;
+	snap_ids[id].state = 2;
+	snap_ids[id].timeout_tick = server_tick() + server_tickspeed()*5;
+	snap_ids[id].next = -1;
+	
+	if(snap_last_timed_id != -1)
+	{
+		snap_ids[snap_last_timed_id].next = id;
+		snap_last_timed_id = id;
+	}
+	else
+	{
+		snap_first_timed_id = id;
+		snap_last_timed_id = id;
+	}
+}
+
+
+
+
 //
 class client
 {
@@ -132,31 +223,51 @@ int server_send_msg(int client_id)
 		net.send(&packet);
 	return 0;
 }
+
+static int new_client_callback(int cid, void *user)
+{
+	clients[cid].state = client::STATE_CONNECTING;
+	clients[cid].name[0] = 0;
+	clients[cid].clan[0] = 0;
+	clients[cid].snapshots.purge_all();
+	clients[cid].last_acked_snapshot = -1;
+	return 0;
+}
+
+static int del_client_callback(int cid, void *user)
+{
+	clients[cid].state = client::STATE_EMPTY;
+	clients[cid].name[0] = 0;
+	clients[cid].clan[0] = 0;
+	clients[cid].snapshots.purge_all();
 	
+	mods_client_drop(cid);
+	return 0;
+}
+		
 // TODO: remove this class
 class server
 {
 public:
 	//socket_udp4 game_socket;
-
-	const char *map_name;
 	int64 lasttick;
 	int64 lastheartbeat;
 	netaddr4 master_server;
 
 	int biggest_snapshot;
 
-	bool run(const char *mapname)
+	bool run()
 	{
 		biggest_snapshot = 0;
 
 		net_init(); // For Windows compatibility.
-		map_name = mapname;
+		
+		snap_init_id();
 
 		// load map
-		if(!map_load(mapname))
+		if(!map_load(config.sv_map))
 		{
-			dbg_msg("server", "failed to load map. mapname='%s'", mapname);
+			dbg_msg("server", "failed to load map. mapname='%s'", config.sv_map);
 			return false;
 		}
 		
@@ -173,12 +284,14 @@ public:
 			bindaddr.port = config.sv_port;
 		}
 		
-		if(!net.open(bindaddr, 0, 0))
+		if(!net.open(bindaddr, config.sv_max_clients, 0))
 		{
-			dbg_msg("network/server", "couldn't open socket");
+			dbg_msg("server", "couldn't open socket. port might already be in use");
 			return false;
 		}
-
+		
+		net.set_callbacks(new_client_callback, del_client_callback, 0);
+		
 		dbg_msg("server", "server name is '%s'", config.sv_name);
 		dbg_msg("server", "masterserver is '%s'", config.masterserver);
 		if (net_host_lookup(config.masterserver, MASTERSERVER_PORT, &master_server) != 0)
@@ -242,12 +355,16 @@ public:
 
 			if(reporttime < time_get())
 			{
-				dbg_msg("server/report", "sim=%.02fms snap=%.02fms net=%.02fms total=%.02fms load=%.02f%%",
-					(simulationtime/reportinterval)/(double)time_freq()*1000,
-					(snaptime/reportinterval)/(double)time_freq()*1000,
-					(networktime/reportinterval)/(double)time_freq()*1000,
-					(totaltime/reportinterval)/(double)time_freq()*1000,
-					(totaltime)/reportinterval/(double)time_freq()*100.0f);
+				if(config.debug)
+				{
+					dbg_msg("server", "sim=%.02fms snap=%.02fms net=%.02fms total=%.02fms load=%.02f%% ids=%d/%d",
+						(simulationtime/reportinterval)/(double)time_freq()*1000,
+						(snaptime/reportinterval)/(double)time_freq()*1000,
+						(networktime/reportinterval)/(double)time_freq()*1000,
+						(totaltime/reportinterval)/(double)time_freq()*1000,
+						(totaltime)/reportinterval/(double)time_freq()*100.0f,
+						snap_id_inusage, snap_id_usage);
+				}
 
 				simulationtime = 0;
 				snaptime = 0;
@@ -313,8 +430,6 @@ public:
 						delta_tick = clients[i].last_acked_snapshot;
 						deltashot = (snapshot *)delta_data;
 					}
-					else
-						dbg_msg("server", "no delta, sending full snapshot");
 				}
 				
 				// create delta
@@ -337,6 +452,7 @@ public:
 
 					const int max_size = MAX_SNAPSHOT_PACKSIZE;
 					int numpackets = (snapshot_size+max_size-1)/max_size;
+					(void)numpackets;
 					for(int n = 0, left = snapshot_size; left; n++)
 					{
 						int chunk = left < max_size ? left : max_size;
@@ -373,14 +489,13 @@ public:
 	void send_map(int cid)
 	{
 		msg_pack_start_system(NETMSG_MAP, MSGFLAG_VITAL);
-		msg_pack_string(map_name, 0);
+		msg_pack_string(config.sv_map, 0);
 		msg_pack_end();
 		server_send_msg(cid);
 	}
 	
 	void send_heartbeat()
 	{
-		dbg_msg("server", "sending heartbeat");
 		NETPACKET packet;
 		packet.client_id = -1;
 		packet.address = master_server;
@@ -397,7 +512,7 @@ public:
 
 		clients[cid].state = client::STATE_EMPTY;
 		mods_client_drop(cid);
-		dbg_msg("game", "player dropped. reason='%s' cid=%x name='%s'", reason, cid, clients[cid].name);
+		dbg_msg("server", "player dropped. reason='%s' cid=%x name='%s'", reason, cid, clients[cid].name);
 	}
 
 	void process_client_packet(NETPACKET *packet)
@@ -434,7 +549,7 @@ public:
 			{
 				if(clients[cid].state != client::STATE_INGAME)
 				{
-					dbg_msg("game", "player as entered the game. cid=%x", cid);
+					dbg_msg("server", "player as entered the game. cid=%x", cid);
 					clients[cid].state = client::STATE_INGAME;
 					mods_client_enter(cid);
 				}
@@ -483,7 +598,7 @@ public:
 		packer.reset();
 		packer.add_raw(SERVERBROWSE_INFO, sizeof(SERVERBROWSE_INFO));
 		packer.add_string(config.sv_name, 128);
-		packer.add_string(map_name, 128);
+		packer.add_string(config.sv_map, 128);
 		packer.add_int(MAX_CLIENTS); // max_players
 		int c = 0;
 		for(int i = 0; i < MAX_CLIENTS; i++)
@@ -537,7 +652,8 @@ public:
 				else if(packet.data_size == sizeof(SERVERBROWSE_FWOK) &&
 					memcmp(packet.data, SERVERBROWSE_FWOK, sizeof(SERVERBROWSE_FWOK)) == 0)
 				{
-					dbg_msg("server", "no firewall/nat problems detected");
+					if(config.debug)
+						dbg_msg("server", "no firewall/nat problems detected");
 				}
 				else if(packet.data_size == sizeof(SERVERBROWSE_FWERROR) &&
 					memcmp(packet.data, SERVERBROWSE_FWERROR, sizeof(SERVERBROWSE_FWERROR)) == 0)
@@ -549,35 +665,6 @@ public:
 			else
 				process_client_packet(&packet);
 		}
-		
-		// check for removed clients
-		while(1)
-		{
-			int cid = net.delclient();
-			if(cid == -1)
-				break;
-			
-			clients[cid].state = client::STATE_EMPTY;
-			clients[cid].name[0] = 0;
-			clients[cid].clan[0] = 0;
-			clients[cid].snapshots.purge_all();
-			
-			mods_client_drop(cid);
-		}
-		
-		// check for new clients
-		while(1)
-		{
-			int cid = net.newclient();
-			if(cid == -1)
-				break;
-			
-			clients[cid].state = client::STATE_CONNECTING;
-			clients[cid].name[0] = 0;
-			clients[cid].clan[0] = 0;
-			clients[cid].snapshots.purge_all();
-			clients[cid].last_acked_snapshot = -1;
-		}
 	}
 };
 
@@ -586,50 +673,30 @@ int main(int argc, char **argv)
 	dbg_msg("server", "starting...");
 
 	config_reset();
+
 #ifdef CONF_PLATFORM_MACOSX
-		config_load("~/.teewars");
+	const char *config_filename = "~/.teewars";
 #else
-		config_load("default.cfg");
+	const char *config_filename = "default.cfg";
 #endif
 
-	const char *mapname = "dm1";
-	
-	// parse arguments
 	for(int i = 1; i < argc; i++)
 	{
-		if(argv[i][0] == '-' && argv[i][1] == 'm' && argv[i][2] == 0 && argc - i > 1)
+		if(argv[i][0] == '-' && argv[i][1] == 'f' && argv[i][2] == 0 && argc - i > 1)
 		{
-			// -m map
+			config_filename = argv[i+1];
 			i++;
-			mapname = argv[i];
-		}
-		else if(argv[i][0] == '-' && argv[i][1] == 'n' && argv[i][2] == 0 && argc - i > 1)
-		{
-			// -n server name
-			i++;
-			config_set_sv_name(&config, argv[i]);
-		}
-		else if(argv[i][0] == '-' && argv[i][1] == 'p' && argv[i][2] == 0)
-		{
-			// -p (private server)
-			config_set_sv_sendheartbeats(&config, 0);
-		}
-		else if(argv[i][0] == '-' && argv[i][1] == 'o' && argv[i][2] == 0)
-		{
-			// -o port
-			i++;
-			config_set_sv_port(&config, atol(argv[i]));
 		}
 	}
 
-	if(!mapname)
-	{
-		dbg_msg("server", "no map given (-m MAPNAME)");
-		return 0;
-	}
+	config_load(config_filename);
+
+	// parse arguments
+	for(int i = 1; i < argc; i++)
+		config_set(argv[i]);
 
 	server_init();
 	server s;
-	s.run(mapname);
+	s.run();
 	return 0;
 }
diff --git a/src/engine/snapshot.cpp b/src/engine/snapshot.cpp
index 2e68df72..e8b75d36 100644
--- a/src/engine/snapshot.cpp
+++ b/src/engine/snapshot.cpp
@@ -42,6 +42,19 @@ int snapshot_crc(snapshot *snap)
 	return crc;
 }
 
+void snapshot_debug_dump(snapshot *snap)
+{
+	dbg_msg("snapshot", "data_size=%d num_items=%d", snap->data_size, snap->num_items);
+	for(int i = 0; i < snap->num_items; i++)
+	{
+		snapshot::item *item = snap->get_item(i);
+		int size = snap->get_item_datasize(i);
+		dbg_msg("snapshot", "\ttype=%d id=%d", item->type(), item->id());
+		for(int b = 0; b < size/4; b++)
+			dbg_msg("snapshot", "\t\t%3d %12d\t%08x", b, item->data()[b], item->data()[b]);
+	}
+}
+
 static int diff_item(int *past, int *current, int *out, int size)
 {
 	/*
diff --git a/src/engine/snapshot.h b/src/engine/snapshot.h
index b0ffc768..e3358b1a 100644
--- a/src/engine/snapshot.h
+++ b/src/engine/snapshot.h
@@ -42,6 +42,7 @@ struct snapshot
 
 void *snapshot_empty_delta();
 int snapshot_crc(snapshot *snap);
+void snapshot_debug_dump(snapshot *snap);
 int snapshot_create_delta(snapshot *from, snapshot *to, void *data);
 int snapshot_unpack_delta(snapshot *from, snapshot *to, void *data, int data_size);