about summary refs log tree commit diff
path: root/src/engine
diff options
context:
space:
mode:
Diffstat (limited to 'src/engine')
-rw-r--r--src/engine/client/client.cpp514
-rw-r--r--src/engine/compression.cpp (renamed from src/engine/lzw.cpp)147
-rw-r--r--src/engine/compression.h12
-rw-r--r--src/engine/config.cpp2
-rw-r--r--src/engine/config_variables.h7
-rw-r--r--src/engine/interface.h49
-rw-r--r--src/engine/lzw.h2
-rw-r--r--src/engine/msg.cpp43
-rw-r--r--src/engine/network.cpp686
-rw-r--r--src/engine/network.h104
-rw-r--r--src/engine/packet.h626
-rw-r--r--src/engine/ringbuffer.h84
-rw-r--r--src/engine/server/server.cpp571
-rw-r--r--src/engine/snapshot.cpp235
-rw-r--r--src/engine/snapshot.h40
-rw-r--r--src/engine/versions.h2
16 files changed, 2106 insertions, 1018 deletions
diff --git a/src/engine/client/client.cpp b/src/engine/client/client.cpp
index 86cc044d..4fb869cd 100644
--- a/src/engine/client/client.cpp
+++ b/src/engine/client/client.cpp
@@ -6,6 +6,7 @@
 #include <string.h>
 #include <stdarg.h>
 #include <stdlib.h>
+#include <stdio.h>
 #include <math.h>
 #include <engine/interface.h>
 
@@ -13,59 +14,14 @@
 #include <engine/snapshot.h>
 #include "ui.h"
 
-#include <engine/lzw.h>
+#include <engine/compression.h>
 
 #include <engine/versions.h>
 #include <engine/config.h>
+#include <engine/network.h>
 
 using namespace baselib;
 
-// --- string handling (MOVE THESE!!) ---
-void snap_encode_string(const char *src, int *dst, int length, int max_length)
-{
-	const unsigned char *p = (const unsigned char *)src;
-	
-	// handle whole int
-	for(int i = 0; i < length/4; i++)
-	{
-		*dst = (p[0]<<24|p[1]<<16|p[2]<<8|p[3]);
-		p += 4;
-		dst++;
-	}
-	
-	// take care of the left overs
-	int left = length%4;
-	if(left)
-	{
-		unsigned last = 0;
-		switch(left)
-		{
-			case 3: last |= p[2]<<8;
-			case 2: last |= p[1]<<16;
-			case 1: last |= p[0]<<24;
-		}
-		*dst = last;
-	}
-}
-
-void snap_decode_string(const int *src, char *dst, int max_length)
-{
-	dbg_assert((max_length%4) == 0, "length must be power of 4");
-	for(int i = 0; i < max_length; i++)
-		dst[0] = 0;
-	
-	for(int i = 0; i < max_length/4; i++)
-	{
-		dst[0] = (*src>>24)&0xff;
-		dst[1] = (*src>>16)&0xff;
-		dst[2] = (*src>>8)&0xff;
-		dst[3] = (*src)&0xff;
-		src++;
-		dst+=4;
-	}
-	dst[-1] = 0; // make sure to zero terminate
-}
-
 // --- input wrappers ---
 static int keyboard_state[2][input::last];
 static int keyboard_current = 0;
@@ -92,7 +48,7 @@ void inp_update()
 }
 
 // --- input snapping ---
-static int input_data[MAX_INPUT_SIZE];
+static int input_data[MAX_INPUT_SIZE] = {0};
 static int input_data_size;
 static int input_is_changed = 1;
 void snap_input(void *data, int size)
@@ -110,6 +66,8 @@ enum
 	NUM_SNAPSHOT_TYPES=3,
 };
 
+static snapshot_storage snapshots_new;
+static int current_tick;
 static snapshot *snapshots[NUM_SNAPSHOT_TYPES];
 static char snapshot_data[NUM_SNAPSHOT_TYPES][MAX_SNAPSHOT_SIZE];
 static int recived_snapshots;
@@ -127,7 +85,7 @@ void *snap_get_item(int snapid, int index, snap_item *item)
 	snapshot::item *i = snapshots[snapid]->get_item(index);
 	item->type = i->type();
 	item->id = i->id();
-	return (void *)i->data;
+	return (void *)i->data();
 }
 
 int snap_num_items(int snapid)
@@ -145,11 +103,16 @@ static void snap_init()
 	recived_snapshots = 0;
 }
 
-float snap_intratick()
+float client_intratick()
 {
 	return (time_get() - snapshot_start_time)/(float)(time_freq()/SERVER_TICK_SPEED);
 }
 
+int client_tick()
+{
+	return current_tick;
+}
+
 void *snap_find_item(int snapid, int type, int id)
 {
 	// TODO: linear search. should be fixed.
@@ -157,123 +120,45 @@ void *snap_find_item(int snapid, int type, int id)
 	{
 		snapshot::item *itm = snapshots[snapid]->get_item(i);
 		if(itm->type() == type && itm->id() == id)
-			return (void *)itm->data;
+			return (void *)itm->data();
 	}
 	return 0x0;
 }
 
 
-int menu_loop();
-float frametime = 0.0001f;
+int menu_loop(); // TODO: what is this?
+static float frametime = 0.0001f;
 
 float client_frametime()
 {
 	return frametime;
 }
 
-void unpack(const char *src, const char *fmt, ...)
-{
-}
+static net_client net;
 
-/*int modc_onmsg(int msg)
-{
-	msg_get("iis")
-}*/
-
-/*
-	i = int (int i)
-	s = string (const char *str)
-	r = raw data (int size, void *data)
-*/
-
-/*
-class packet2
-{
-private:
-	// packet data
-	struct header
-	{
-		unsigned msg;
-		unsigned ack;
-		unsigned seq;
-	};
-	
-	unsigned char packet_data[MAX_PACKET_SIZE];
-	unsigned char *current;
-	
-	enum
-	{
-		MAX_PACKET_SIZE = 1024,
-	};
-	
-public:
-	packet2()
-	{
-		current = packet_data;
-		current += sizeof(header);
-	}
-
-	int pack(char *dst, const char *fmt, ...)
-	{
-		va_list arg_list;
-		va_start(arg_list, fmt);
-		while(*fmt)
-		{
-			if(*fmt == 's')
-			{
-				// pack string
-				const char *s = va_arg(arg_list, const char*);
-				*dst++ = 2;
-				while(*s)
-				{
-					*dst = *s;
-					dst++;
-					s++;
-				}
-				*dst = 0; // null terminate
-				dst++;
-				fmt++;
-			}
-			else if(*fmt == 'i')
-			{
-				// pack int
-				int i = va_arg(arg_list, int);
-				*dst++ = 1;
-				*dst++ = (i>>24)&0xff;
-				*dst++ = (i>>16)&0xff;
-				*dst++ = (i>>8)&0xff;
-				*dst++ = i&0xff;
-				fmt++;
-			}
-			else
-			{
-				dbg_break(); // error
-				break;
-			}
-		}
-		va_end(arg_list);	
-	}	
-};
-*/
-/*
-int msg_get(const char *fmt)
+int client_send_msg()
 {
+	const msg_info *info = msg_get_info();
+	NETPACKET packet;
+	packet.client_id = 0;
+	packet.data = info->data;
+	packet.data_size = info->size;
+
+	if(info->flags&MSGFLAG_VITAL)	
+		packet.flags = PACKETFLAG_VITAL;
 	
+	net.send(&packet);
+	return 0;
 }
 
-int client_msg_send(int msg, const char *fmt, ...)
-
-int server_msg_send(int msg, const char *fmt, ...)
-{
-
-}*/
-
 // --- client ---
+// TODO: remove this class
 class client
 {
 public:
-	socket_udp4 socket;
-	connection conn;
+	
+	//socket_udp4 socket;
+	//connection conn;
 	int64 reconnect_timer;
 	
 	int snapshot_part;
@@ -305,37 +190,24 @@ public:
 
 	void set_fullscreen(bool flag) { fullscreen = flag; }
 	
-	void send_packet(packet *p)
-	{
-		conn.send(p);
-	}
-	
-	void send_connect()
+	void send_info()
 	{
 		recived_snapshots = 0;
-		
-		/*
-		pack(NETMSG_CLIENT_CONNECT, "sssss",
-			TEEWARS_NETVERSION,
-			name,
-			"no clan",
-			"password",
-			"myskin");
-		*/
-		
-		packet p(NETMSG_CLIENT_CONNECT);
-		p.write_str(TEEWARS_VERSION); // payload
-		p.write_str(config.player_name);
-		p.write_str("no clan");
-		p.write_str("password");
-		p.write_str("myskin");
-		send_packet(&p);
+
+		msg_pack_start(NETMSG_INFO, MSGFLAG_VITAL);
+		msg_pack_string(config.player_name, 128);
+		msg_pack_string(config.clan_name, 128);
+		msg_pack_string(config.password, 128);
+		msg_pack_string("myskin", 128);
+		msg_pack_end();
+		client_send_msg();
 	}
 
-	void send_done()
+	void send_entergame()
 	{
-		packet p(NETMSG_CLIENT_DONE);
-		send_packet(&p);
+		msg_pack_start(NETMSG_ENTERGAME, MSGFLAG_VITAL);
+		msg_pack_end();
+		client_send_msg();
 	}
 
 	void send_error(const char *error)
@@ -343,40 +215,38 @@ public:
 		/*
 			pack(NETMSG_CLIENT_ERROR, "s", error);
 		*/
+		/*
 		packet p(NETMSG_CLIENT_ERROR);
 		p.write_str(error);
 		send_packet(&p);
 		//send_packet(&p);
 		//send_packet(&p);
+		*/
 	}	
 
 	void send_input()
 	{
-		/*
-			pack(NETMSG_CLIENT_ERROR, "s", error);
-		*/
-		packet p(NETMSG_CLIENT_INPUT);
-		p.write_int(input_data_size);
+		msg_pack_start(NETMSG_INPUT, 0);
+		msg_pack_int(input_data_size);
 		for(int i = 0; i < input_data_size/4; i++)
-			p.write_int(input_data[i]);
-		send_packet(&p);
+			msg_pack_int(input_data[i]);
+		msg_pack_end();
+		client_send_msg();
 	}
 	
 	void disconnect()
 	{
+		/*
 		send_error("disconnected");
 		set_state(STATE_OFFLINE);
 		map_unload();
+		*/
 	}
 	
 	void connect(netaddr4 *server_address)
 	{
-		conn.init(&socket, server_address);
-		
-		// start by sending connect
-		send_connect();
+		net.connect(server_address);
 		set_state(STATE_CONNECTING);
-		reconnect_timer = time_get()+time_freq();
 	}
 	
 	bool load_data()
@@ -385,14 +255,42 @@ public:
 		return true;
 	}
 	
+	void debug_render()
+	{
+		gfx_blend_normal();
+		gfx_texture_set(debug_font);
+		gfx_mapscreen(0,0,gfx_screenwidth(),gfx_screenheight());
+		
+		static NETSTATS prev, current;
+		static int64 last_snap = 0;
+		if(time_get()-last_snap > time_freq()/10)
+		{
+			last_snap = time_get();
+			prev = current;
+			net.stats(&current);
+		}
+		
+		char buffer[512];
+		sprintf(buffer, "send: %8d recv: %8d",
+			(current.send_bytes-prev.send_bytes)*10,
+			(current.recv_bytes-prev.recv_bytes)*10);
+		gfx_quads_text(10, 10, 16, buffer);
+		
+	}
+	
 	void render()
 	{
 		gfx_clear(0.0f,0.0f,0.0f);
 		
 		// this should be moved around abit
+		// TODO: clean this shit up!
 		if(get_state() == STATE_ONLINE)
 		{
 			modc_render();
+			
+			// debug render stuff
+			debug_render();
+			
 		}
 		else if (get_state() != STATE_CONNECTING && get_state() != STATE_LOADING)
 		{
@@ -404,7 +302,7 @@ public:
 			else if (status)
 				connect(&server_address);
 		}
-		else if (get_state() == STATE_CONNECTING)
+		else if (get_state() == STATE_CONNECTING || get_state() == STATE_LOADING)
 		{
 			static int64 start = time_get();
 			static int tee_texture;
@@ -458,15 +356,18 @@ public:
 		// init menu
 		modmenu_init();
 		
+		net.open(0);
+		
 		// open socket
+		/*
 		if(!socket.open(0))
 		{
 			dbg_msg("network/client", "failed to open socket");
 			return;
-		}
+		}*/
 
 		// connect to the server if wanted
-		if (server_address)
+		if(server_address)
 			connect(server_address);
 		
 		//int64 inputs_per_second = 50;
@@ -504,6 +405,9 @@ public:
 				input::set_mouse_mode(input::mode_absolute);
 			if(input::pressed(input::f2))
 				input::set_mouse_mode(input::mode_relative);
+
+			if(input::pressed(input::lctrl) && input::pressed('Q'))
+				break;
 				
 			// pump the network
 			pump_network();
@@ -523,13 +427,13 @@ public:
 			
 			if(reporttime < time_get())
 			{
-				unsigned sent, recved;
-				conn.counter_get(&sent, &recved);
-				dbg_msg("client/report", "fps=%.02f",
-					frames/(float)(reportinterval/time_freq()));
+				//unsigned sent, recved;
+				//conn.counter_get(&sent, &recved);
+				dbg_msg("client/report", "fps=%.02f netstate=%d",
+					frames/(float)(reportinterval/time_freq()), net.state());
 				frames = 0;
 				reporttime += reportinterval;
-				conn.counter_reset();
+				//conn.counter_reset();
 			}
 			
 			if (input::pressed(input::esc))
@@ -556,117 +460,171 @@ public:
 		set_state(STATE_BROKEN);
 	}
 
-	void process_packet(packet *p)
+	void process_packet(NETPACKET *packet)
 	{
-		if(p->version() != TEEWARS_NETVERSION)
+		int msg = msg_unpack_start(packet->data, packet->data_size);
+		if(msg == NETMSG_MAP)
 		{
-			error("wrong version");
-		}
-		else if(p->msg() == NETMSG_SERVER_ACCEPT)
-		{
-			const char *map;
-			map = p->read_str();
+			const char *map = msg_unpack_string();
+			dbg_msg("client/network", "connection accepted, map=%s", map);
+			set_state(STATE_LOADING);
 			
-			if(p->is_good())
+			if(map_load(map))
 			{
-				dbg_msg("client/network", "connection accepted, map=%s", map);
-				set_state(STATE_LOADING);
-				
-				if(map_load(map))
-				{
-					modc_entergame();
-					send_done();
-					dbg_msg("client/network", "loading done");
-					// now we will wait for two snapshots
-					// to finish the connection
-				}
-				else
-				{
-					error("failure to load map");
-				}
+				modc_entergame();
+				send_entergame();
+				dbg_msg("client/network", "loading done");
+				// now we will wait for two snapshots
+				// to finish the connection
+			}
+			else
+			{
+				error("failure to load map");
 			}
 		}
-		else if(p->msg() == NETMSG_SERVER_SNAP)
+		else if(msg == NETMSG_SNAP || msg == NETMSG_SNAPSMALL || msg == NETMSG_SNAPEMPTY)
 		{
 			//dbg_msg("client/network", "got snapshot");
-			int num_parts = p->read_int();
-			int part = p->read_int();
-			int part_size = p->read_int();
+			int game_tick = msg_unpack_int();
+			int delta_tick = game_tick-msg_unpack_int();
+			int num_parts = 1;
+			int part = 0;
+			int part_size = 0;
 			
-			if(p->is_good())
+			if(msg == NETMSG_SNAP)
 			{
-				if(snapshot_part == part)
+				num_parts = msg_unpack_int();
+				part = msg_unpack_int();
+			}
+			
+			if(msg != NETMSG_SNAPEMPTY)
+				part_size = msg_unpack_int();
+			
+			if(snapshot_part == part)
+			{
+				// TODO: clean this up abit
+				const char *d = (const char *)msg_unpack_raw(part_size);
+				mem_copy((char*)snapshots[SNAP_INCOMMING] + part*MAX_SNAPSHOT_PACKSIZE, d, part_size);
+				snapshot_part++;
+			
+				if(snapshot_part == num_parts)
 				{
-					const char *d = p->read_raw(part_size);
-					mem_copy((char*)snapshots[SNAP_INCOMMING] + part*MAX_SNAPSHOT_PACKSIZE, d, part_size);
-					snapshot_part++;
-				
-					if(snapshot_part == num_parts)
+					snapshot *tmp = snapshots[SNAP_PREV];
+					snapshots[SNAP_PREV] = snapshots[SNAP_CURRENT];
+					snapshots[SNAP_CURRENT] = tmp;
+					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)
 					{
-						snapshot *tmp = snapshots[SNAP_PREV];
-						snapshots[SNAP_PREV] = snapshots[SNAP_CURRENT];
-						snapshots[SNAP_CURRENT] = tmp;
-
-						// decompress snapshot
-						lzw_decompress(snapshots[SNAP_INCOMMING], snapshots[SNAP_CURRENT]);
-						
-						// apply snapshot, cycle pointers
-						recived_snapshots++;
-						snapshot_start_time = time_get();
-						
-						// we got two snapshots until we see us self as connected
-						if(recived_snapshots == 2)
+						//int snapsize = lzw_decompress(snapshots[SNAP_INCOMMING], snapshots[SNAP_CURRENT]);
+						int compsize = zerobit_decompress(snapshots[SNAP_INCOMMING], part_size, tmpbuffer);
+						//int compsize = lzw_decompress(snapshots[SNAP_INCOMMING],tmpbuffer);
+						int intsize = intpack_decompress(tmpbuffer, compsize, tmpbuffer2);
+						deltadata = tmpbuffer2;
+						deltasize = intsize;
+					}
+
+					// find snapshot that we should use as delta 
+					static snapshot emptysnap;
+					emptysnap.data_size = 0;
+					emptysnap.num_items = 0;
+					
+					snapshot *deltashot = &emptysnap;
+					int deltashot_size;
+
+					if(delta_tick >= 0)
+					{
+						void *delta_data;
+						deltashot_size = snapshots_new.get(delta_tick, &delta_data);
+						if(deltashot_size >= 0)
 						{
-							local_start_time = time_get();
-							set_state(STATE_ONLINE);
+							deltashot = (snapshot *)delta_data;
+						}
+						else
+						{
+							// TODO: handle this
+							dbg_msg("client", "error, couldn't find the delta snapshot");
 						}
-						
-						if(recived_snapshots > 2)
-							modc_newsnapshot();
-						
-						snapshot_part = 0;
 					}
 
-				}
-				else
-				{
-					dbg_msg("client", "snapshot reset!");
+					int snapsize = snapshot_unpack_delta(deltashot, (snapshot*)snapshots[SNAP_CURRENT], deltadata, deltasize);
+					//snapshot *shot = (snapshot *)snapshots[SNAP_CURRENT];
+
+					// purge old snapshots					
+					snapshots_new.purge_until(delta_tick);
+					snapshots_new.purge_until(game_tick-50); // TODO: change this to server tickrate
+					
+					// add new
+					snapshots_new.add(game_tick, snapsize, snapshots[SNAP_CURRENT]);
+					
+					// apply snapshot, cycle pointers
+					recived_snapshots++;
+					snapshot_start_time = time_get();
+					
+					// we got two snapshots until we see us self as connected
+					if(recived_snapshots == 2)
+					{
+						local_start_time = time_get();
+						set_state(STATE_ONLINE);
+					}
+					
+					if(recived_snapshots > 2)
+						modc_newsnapshot();
+					
 					snapshot_part = 0;
+					
+					// ack snapshot
+					msg_pack_start(NETMSG_SNAPACK, 0);
+					msg_pack_int(game_tick);
+					msg_pack_end();
+					client_send_msg();
 				}
 			}
-		}
-		else
-		{
-			dbg_msg("server/client", "unknown packet %x", p->msg());
+			else
+			{
+				dbg_msg("client", "snapshot reset!");
+				snapshot_part = 0;
+			}
 		}
 	}
 	
 	void pump_network()
 	{
-		while(1)
+		net.update();
+
+		// check for errors		
+		if(get_state() != STATE_OFFLINE && net.state() == NETSTATE_OFFLINE)
 		{
-			packet p;
-			netaddr4 from;
-			int bytes = socket.recv(&from, p.data(), p.max_size());
-			
-			if(bytes <= 0)
-				break;
-			
-			process_packet(&p);
+			// TODO: add message to the user there
+			set_state(STATE_OFFLINE);
 		}
-		
+
 		//
-		if(get_state() == STATE_CONNECTING && time_get() > reconnect_timer)
+		if(get_state() == STATE_CONNECTING && net.state() == NETSTATE_ONLINE)
 		{
-			send_connect();
-			reconnect_timer = time_get() + time_freq();
+			// we switched to online
+			dbg_msg("client", "connected, sending info");
+			set_state(STATE_LOADING);
+			send_info();
 		}
+		
+		// process packets
+		NETPACKET packet;
+		while(net.recv(&packet))
+			process_packet(&packet);
 	}	
 };
 
 int main(int argc, char **argv)
 {
 	dbg_msg("client", "starting...");
+	
 	config_reset();
 	config_load("teewars.cfg");
 
@@ -683,9 +641,23 @@ int main(int argc, char **argv)
 	{
 		if(argv[i][0] == '-' && argv[i][1] == 'c' && argv[i][2] == 0 && argc - i > 1)
 		{
-			// -c SERVER
+			// -c SERVER:PORT
 			i++;
-			if(net_host_lookup(argv[i], 8303, &server_address) != 0)
+			const char *port_str = 0;
+			for(int k = 0; argv[i][k]; k++)
+			{
+				if(argv[i][k] == ':')
+				{
+					port_str = &(argv[i][k+1]);
+					argv[i][k] = 0;
+					break;
+				}
+			}
+			int port = 8303;
+			if(port_str)
+				port = atoi(port_str);
+				
+			if(net_host_lookup(argv[i], port, &server_address) != 0)
 				dbg_msg("main", "could not find the address of %s, connecting to localhost", argv[i]);
 			else
 				connect_at_once = true;
diff --git a/src/engine/lzw.cpp b/src/engine/compression.cpp
index 80dd1c22..c878b15b 100644
--- a/src/engine/lzw.cpp
+++ b/src/engine/compression.cpp
@@ -1,3 +1,4 @@
+#include <baselib/system.h>
 #include <string.h>
 
 // LZW Compressor
@@ -221,3 +222,149 @@ long lzw_decompress(const void *src_, void *dst_)
 
 	return 0;
 }
+
+// Format: ESDDDDDD EDDDDDDD EDD...  Extended, Data, Sign
+unsigned char *vint_pack(unsigned char *dst, int i) 
+{ 
+        *dst = (i>>25)&0x40; // set sign bit if i<0
+	i = i^(i>>31); // if(i<0) i = ~i
+ 
+        *dst |= i&0x3F; // pack 6bit into dst
+        i >>= 6; // discard 6 bits
+        if(i)
+	{
+                *dst |= 0x80; // set extend bit
+		while(1)
+		{
+			dst++;
+	                *dst = i&(0x7F); // pack 7bit
+        	        i >>= 7; // discard 7 bits
+			*dst |= (i!=0)<<7; // set extend bit (may branch)
+                	if(!i)
+				break;
+		}
+	}
+
+	dst++;
+        return dst; 
+} 
+ 
+const unsigned char *vint_unpack(const unsigned char *src, int *i)
+{ 
+        int sign = (*src>>6)&1; 
+        *i = *src&0x3F; 
+
+	while(1)
+	{ 
+	        if(!(*src&0x80)) break;
+		src++;
+                *i |= (*src&(0x7F))<<(6);
+
+	        if(!(*src&0x80)) break;
+		src++;
+                *i |= (*src&(0x7F))<<(6+7);
+
+	        if(!(*src&0x80)) break;
+		src++;
+                *i |= (*src&(0x7F))<<(6+7+7);
+
+	        if(!(*src&0x80)) break;
+		src++;
+                *i |= (*src&(0x7F))<<(6+7+7+7);
+	}
+
+	src++;
+	*i ^= -sign; // if(sign) *i = ~(*i)
+        return src; 
+} 
+
+
+long intpack_decompress(const void *src_, int size, void *dst_)
+{
+	const unsigned char *src = (unsigned char *)src_;
+	const unsigned char *end = src + size;
+	int *dst = (int *)dst_;
+	while(src < end)
+	{
+		src = vint_unpack(src, dst);
+		dst++;
+	}
+	return (long)((unsigned char *)dst-(unsigned char *)dst_);
+}
+
+long intpack_compress(const void *src_, int size, void *dst_)
+{
+	int *src = (int *)src_;
+	unsigned char *dst = (unsigned char *)dst_;
+	size /= 4;
+	while(size)
+	{
+		dst = vint_pack(dst, *src);
+		size--;
+		src++;
+	}
+	return (long)(dst-(unsigned char *)dst_);
+}
+
+
+long zerobit_compress(const void *src_, int size, void *dst_)
+{
+	unsigned char *src = (unsigned char *)src_;
+	unsigned char *dst = (unsigned char *)dst_;
+	
+	//int zerocount = 0 ;
+	while(size)
+	{
+		unsigned char bit = 0x80;
+		unsigned char mask = 0;
+		int dst_move = 1;
+		int chunk = size < 8 ? size : 8;
+		size -= chunk;
+		
+		for(int b = 0; b < chunk; b++, bit>>=1)
+		{
+			if(*src)
+			{
+				dst[dst_move] = *src;
+				mask |= bit;
+				dst_move++;
+			}
+			
+			src++;
+		}
+		
+		*dst = mask;
+		dst += dst_move;
+	}
+	
+	long l = (long)(dst-(unsigned char *)dst_);
+	//dbg_msg("zerobit", "%d", (int)l);
+	return l;
+}
+
+long zerobit_decompress(const void *src_, int size, void *dst_)
+{
+	unsigned char *src = (unsigned char *)src_;
+	unsigned char *dst = (unsigned char *)dst_;
+	unsigned char *end = src + size;
+	
+	//int zerocount = 0 ;
+	while(src != end)
+	{
+		unsigned char bit = 0x80;
+		unsigned char mask = *src++;
+		
+		for(int b = 0; b < 8; b++, bit>>=1)
+		{
+			if(mask&bit)
+				*dst++ = *src++;
+			else
+				*dst++ = 0;
+		}
+	}
+	
+	long l = (long)(dst-(unsigned char *)dst_);
+	//dbg_msg("zerobit", "%d", (int)l);
+	return l;
+}
+
diff --git a/src/engine/compression.h b/src/engine/compression.h
new file mode 100644
index 00000000..d1f13d48
--- /dev/null
+++ b/src/engine/compression.h
@@ -0,0 +1,12 @@
+// lzw is no longer in use
+long lzw_compress(const void *src, int size, void *dst);
+long lzw_decompress(const void *src, void *dst);
+
+unsigned char *vint_pack(unsigned char *dst, int i);
+const unsigned char *vint_unpack(const unsigned char *src, int *inout);
+
+long intpack_compress(const void *src, int size, void *dst);
+long intpack_decompress(const void *src, int size, void *dst);
+
+long zerobit_compress(const void *src, int size, void *dst);
+long zerobit_decompress(const void *src, int size, void *dst);
diff --git a/src/engine/config.cpp b/src/engine/config.cpp
index bedeb4d4..7694b159 100644
--- a/src/engine/config.cpp
+++ b/src/engine/config.cpp
@@ -29,7 +29,7 @@ void config_set(const char *line)
 	const char *val_str = strchr(line, '=');
 	if (val_str)
 	{
-		memcpy(var_str, line, val_str - line);
+		mem_copy(var_str, line, val_str - line);
 		var_str[val_str - line] = 0;
 		++val_str;
 
diff --git a/src/engine/config_variables.h b/src/engine/config_variables.h
index 852d26a9..27ca1931 100644
--- a/src/engine/config_variables.h
+++ b/src/engine/config_variables.h
@@ -1,2 +1,9 @@
 #include "../game/game_variables.h"
 
+MACRO_CONFIG_INT(screen_width, 800, 0, 0)
+MACRO_CONFIG_INT(screen_height, 600, 0, 0)
+MACRO_CONFIG_STR(player_name, 32, "nameless tee")
+MACRO_CONFIG_STR(clan_name, 32, "")
+MACRO_CONFIG_STR(password, 32, "")
+
+MACRO_CONFIG_INT(sv_port, 8303, 0, 0)
diff --git a/src/engine/interface.h b/src/engine/interface.h
index c8099247..27f73e17 100644
--- a/src/engine/interface.h
+++ b/src/engine/interface.h
@@ -520,7 +520,7 @@ void snap_input(void *data, int size);
 	Remarks:
 		DOCTODO: Explain how to use it.
 */
-float snap_intratick();
+//float snap_intratick();
 
 /*
 	Group: Server Callbacks
@@ -681,8 +681,13 @@ void modmenu_shutdown();
 */
 int modmenu_render(void *server_address);
 
-void snap_encode_string(const char *src, int *dst, int length, int max_length);
-void snap_decode_string(const int *src, char *dst, int length);
+
+
+
+
+
+//void snap_encode_string(const char *src, int *dst, int length, int max_length);
+//void snap_decode_string(const int *src, char *dst, int length);
 
 int server_getclientinfo(int client_id, client_info *info);
 int server_tick();
@@ -694,6 +699,44 @@ void inp_update();
 float client_frametime();
 float client_localtime();
 
+// message packing
+enum
+{
+	MSGFLAG_VITAL=1,
+};
+
+void msg_pack_start(int msg, int flags);
+void msg_pack_int(int i);
+void msg_pack_string(const char *p, int limit);
+void msg_pack_raw(const void *data, int size);
+void msg_pack_end();
+
+struct msg_info
+{
+	int msg;
+	int flags;
+	const unsigned char *data;
+	int size;
+};
+
+const msg_info *msg_get_info();
+
+// message unpacking
+int msg_unpack_start(const void *data, int data_size);
+int msg_unpack_int();
+const char *msg_unpack_string();
+const unsigned char *msg_unpack_raw(int size);
+
+// message sending
+int server_send_msg(int client_id);
+int client_send_msg();
+
+int client_tick();
+float client_intratick();
+
+
+int modc_message();
+
 #define MASTER_SERVER_ADDRESS "master.teewars.com"
 #define MASTER_SERVER_PORT 8300
 
diff --git a/src/engine/lzw.h b/src/engine/lzw.h
deleted file mode 100644
index af29665e..00000000
--- a/src/engine/lzw.h
+++ /dev/null
@@ -1,2 +0,0 @@
-long lzw_compress(const void *src, int size, void *dst);
-long lzw_decompress(const void *src, void *dst);
diff --git a/src/engine/msg.cpp b/src/engine/msg.cpp
new file mode 100644
index 00000000..024bab2c
--- /dev/null
+++ b/src/engine/msg.cpp
@@ -0,0 +1,43 @@
+
+#include "interface.h"
+#include "packet.h"
+
+// message packing
+static data_packer packer;
+static msg_info pack_info;
+
+void msg_pack_int(int i) { packer.add_int(i); }
+void msg_pack_string(const char *p, int limit) { packer.add_string(p, limit); }
+void msg_pack_raw(const void *data, int size) { packer.add_raw((const unsigned char *)data, size); }
+
+void msg_pack_start(int msg, int flags)
+{
+	packer.reset();
+	pack_info.msg = msg;
+	pack_info.flags = flags;
+	
+	msg_pack_int(msg);
+}
+
+void msg_pack_end()
+{
+	pack_info.size = packer.size();
+	pack_info.data = packer.data();
+}
+
+const msg_info *msg_get_info()
+{
+	return &pack_info;
+}
+
+// message unpacking
+static data_unpacker unpacker;
+int msg_unpack_start(const void *data, int data_size)
+{
+	unpacker.reset((const unsigned char *)data, data_size);
+	return msg_unpack_int();
+}
+
+int msg_unpack_int() { return unpacker.get_int(); }
+const char *msg_unpack_string() { return unpacker.get_string(); }
+const unsigned char *msg_unpack_raw(int size)  { return unpacker.get_raw(size); }
diff --git a/src/engine/network.cpp b/src/engine/network.cpp
new file mode 100644
index 00000000..5402aee7
--- /dev/null
+++ b/src/engine/network.cpp
@@ -0,0 +1,686 @@
+#include <baselib/system.h>
+
+#include "network.h"
+#include "ringbuffer.h"
+
+/*
+	header:
+		unsigned char ID[2]; 2 'T' 'W'
+		unsigned char version; 3 
+		unsigned char flags; 4 
+		unsigned short seq;  6
+		unsigned short ack;  8
+		unsigned crc;       12 bytes
+
+	header v2:
+		unsigned char flags;		1
+		unsigned char seq_ack[3];	4
+		unsigned char crc[2];		6
+*/
+
+
+#define NETWORK_HEADER_V2
+
+// move
+static int net_addr4_cmp(const NETADDR4 *a, const NETADDR4 *b)
+{
+	if(	a->ip[0] != b->ip[0] ||
+		a->ip[1] != b->ip[1] ||
+		a->ip[2] != b->ip[2] ||
+		a->ip[3] != b->ip[3] ||
+		a->port != b->port
+	)
+		return 1;
+	return 0;
+}
+
+enum
+{
+	NETWORK_VERSION = 1,
+	
+#ifdef NETWORK_HEADER_V2
+	NETWORK_HEADER_SIZE = 6,
+#else
+	NETWORK_HEADER_SIZE = 12,
+#endif
+	NETWORK_MAX_PACKET_SIZE = 1024,
+	NETWORK_MAX_CLIENTS = 16,
+	
+	NETWORK_CONNSTATE_OFFLINE=0,
+	NETWORK_CONNSTATE_CONNECT=1,
+	NETWORK_CONNSTATE_CONNECTACCEPTED=2,
+	NETWORK_CONNSTATE_ONLINE=3,
+	NETWORK_CONNSTATE_ERROR=4,
+	
+	NETWORK_PACKETFLAG_CONNECT=0x01,
+	NETWORK_PACKETFLAG_ACCEPT=0x02,
+	NETWORK_PACKETFLAG_CLOSE=0x04,
+	NETWORK_PACKETFLAG_VITAL=0x08,
+	NETWORK_PACKETFLAG_RESEND=0x10,
+	//NETWORK_PACKETFLAG_STATELESS=0x20,
+};
+
+struct NETPACKETDATA
+{
+	unsigned char ID[2];
+	unsigned char version;
+	unsigned char flags;
+	unsigned short seq;
+	unsigned short ack;
+	unsigned crc;
+	unsigned data_size;
+	int64 first_send_time;
+	unsigned char *data;
+};
+
+
+static void send_packet(NETSOCKET socket, NETADDR4 *addr, NETPACKETDATA *packet)
+{
+	unsigned char buffer[NETWORK_MAX_PACKET_SIZE];
+#ifdef NETWORK_HEADER_V2
+	buffer[0] = packet->flags;
+	buffer[1] = ((packet->seq>>4)&0xf0) | ((packet->ack>>8)&0x0f);
+	buffer[2] = packet->seq;
+	buffer[3] = packet->ack;
+	buffer[4] = packet->crc>>8;
+	buffer[5] = packet->crc&0xff;
+#else	
+	buffer[0] = packet->ID[0];
+	buffer[1] = packet->ID[1];
+	buffer[2] = packet->version;
+	buffer[3] = packet->flags;
+	buffer[4] = packet->seq>>8;
+	buffer[5] = packet->seq&0xff;
+	buffer[6] = packet->ack>>8;
+	buffer[7] = packet->ack&0xff;
+	buffer[8] = (packet->crc>>24)&0xff;
+	buffer[9] = (packet->crc>>16)&0xff;
+	buffer[10] = (packet->crc>>8)&0xff;
+	buffer[11] = packet->crc&0xff;
+#endif
+	mem_copy(buffer+NETWORK_HEADER_SIZE, packet->data, packet->data_size);
+	int send_size = NETWORK_HEADER_SIZE+packet->data_size;
+	//dbg_msg("network", "sending packet, size=%d (%d + %d)", send_size, NETWORK_HEADER_SIZE, packet->data_size);
+	net_udp4_send(socket, addr, buffer, send_size);
+}
+
+struct NETCONNECTION
+{
+	unsigned seq;
+	unsigned ack;
+	unsigned state;
+	
+	ring_buffer buffer;
+	
+	int64 last_recv_time;
+	int64 last_send_time;
+	const char *error_string;
+	
+	NETADDR4 peeraddr;
+	NETSOCKET socket;
+	NETSTATS stats;
+};
+
+struct NETSLOT
+{
+	int online;
+	NETCONNECTION conn;
+};
+
+struct NETSERVER
+{
+	NETSOCKET socket;
+	NETSLOT slots[NETWORK_MAX_CLIENTS];
+	unsigned char recv_buffer[NETWORK_MAX_PACKET_SIZE];
+};
+
+struct NETCLIENT
+{
+	NETADDR4 server_addr;
+	NETSOCKET socket;
+	unsigned char recv_buffer[NETWORK_MAX_PACKET_SIZE];
+	
+	NETCONNECTION conn;
+};
+
+static void conn_reset_stats(NETCONNECTION *conn)
+{
+	mem_zero(&conn->stats, sizeof(conn->stats));
+}
+
+static void conn_reset(NETCONNECTION *conn)
+{
+	conn->seq = 0;
+	conn->ack = 0;
+	conn->state = NETWORK_CONNSTATE_OFFLINE;
+	conn->error_string = 0;
+	conn->last_send_time = 0;
+	conn->last_recv_time = 0;
+	conn->buffer.reset();
+}
+
+static const char *conn_error(NETCONNECTION *conn)
+{
+	return conn->error_string;
+}
+/*
+static int conn_state(NETCONNECTION *conn)
+{
+	return conn->state;
+}*/
+
+static void conn_init(NETCONNECTION *conn, NETSOCKET socket)
+{
+	conn_reset(conn);
+	conn_reset_stats(conn);
+	conn->socket = socket;
+}
+
+static void conn_ack(NETCONNECTION *conn, int ack)
+{
+	while(1)
+	{
+		ring_buffer::item *i = conn->buffer.first();
+		if(!i)
+			break;
+			
+		NETPACKETDATA *resend = (NETPACKETDATA *)i->data();
+		if(resend->seq <= ack)
+			conn->buffer.pop_first();
+		else
+			break;
+	}
+}
+
+static void conn_send_raw(NETCONNECTION *conn, NETPACKETDATA *data)
+{
+	conn->last_send_time = time_get();
+	conn->stats.send_packets++;
+	conn->stats.send_bytes += data->data_size + NETWORK_HEADER_SIZE;
+	send_packet(conn->socket, &conn->peeraddr, data);
+}
+
+static void conn_resend(NETCONNECTION *conn)
+{
+	ring_buffer::item *i = conn->buffer.first();
+	while(i)
+	{
+		NETPACKETDATA *resend = (NETPACKETDATA *)i->data();
+		conn->stats.resend_packets++;
+		conn->stats.resend_bytes += resend->data_size + NETWORK_HEADER_SIZE;
+		conn_send_raw(conn, resend);
+		i = i->next;
+	}
+}
+
+static void conn_send(NETCONNECTION *conn, int flags, int data_size, const void *data)
+{
+	if(flags&NETWORK_PACKETFLAG_VITAL)
+		conn->seq++;
+	
+	NETPACKETDATA p;
+	p.ID[0] = 'T';
+	p.ID[1] = 'W';
+	p.version = NETWORK_VERSION;
+	p.flags = flags;
+	p.seq = conn->seq;
+	p.ack = conn->ack;
+	p.crc = 0;
+	p.data_size = data_size;
+	p.data = (unsigned char *)data;
+	p.first_send_time = time_get();
+	
+	if(flags&NETWORK_PACKETFLAG_VITAL)
+	{
+		// save packet if we need to resend
+		NETPACKETDATA *resend = (NETPACKETDATA *)conn->buffer.alloc(sizeof(NETPACKETDATA)+p.data_size);
+		*resend = p;
+		resend->data = (unsigned char *)(resend+1);
+		mem_copy(resend->data, p.data, p.data_size);
+	}
+	
+	// TODO: calc crc
+	conn_send_raw(conn, &p);
+}
+
+static int conn_connect(NETCONNECTION *conn, NETADDR4 *addr)
+{
+	if(conn->state != NETWORK_CONNSTATE_OFFLINE)
+		return -1;
+	
+	// init connection
+	conn_reset(conn);
+	conn->peeraddr = *addr;
+	conn->state = NETWORK_CONNSTATE_CONNECT;
+	conn_send(conn, NETWORK_PACKETFLAG_CONNECT, 0, 0);
+	return 0;
+}
+
+static int conn_feed(NETCONNECTION *conn, NETPACKETDATA *p, NETADDR4 *addr)
+{
+	conn->last_recv_time = time_get();
+	conn->stats.recv_packets++;
+	conn->stats.recv_bytes += p->data_size + NETWORK_HEADER_SIZE;
+	
+	if(conn->state == NETWORK_CONNSTATE_OFFLINE)
+	{
+		if(p->flags == NETWORK_PACKETFLAG_CONNECT)
+		{
+			// send response and init connection
+			conn->state = NETWORK_CONNSTATE_ONLINE;
+			conn->peeraddr = *addr;
+			conn_send(conn, NETWORK_PACKETFLAG_CONNECT|NETWORK_PACKETFLAG_ACCEPT, 0, 0);
+			dbg_msg("connection", "got connection, sending connect+accept");
+		}
+	}
+	else if(net_addr4_cmp(&conn->peeraddr, addr) == 0)
+	{
+		if(conn->state == NETWORK_CONNSTATE_ONLINE)
+		{
+			// remove packages that are acked
+			conn_ack(conn, p->ack);
+			
+			// 
+			if(p->flags&NETWORK_PACKETFLAG_RESEND)
+				conn_resend(conn);
+				
+			if(p->flags&NETWORK_PACKETFLAG_VITAL)
+			{
+				if(p->seq == conn->ack+1)
+				{
+					// in sequence
+					conn->ack++;
+				}
+				else
+				{
+					// out of sequence, request resend
+					dbg_msg("conn", "asking for resend");
+					conn_send(conn, NETWORK_PACKETFLAG_RESEND, 0, 0);
+					return 0;
+				}
+			}
+				
+			return 1;
+		}
+		else if(conn->state == NETWORK_CONNSTATE_CONNECT)
+		{
+			// connection made
+			if(p->flags == NETWORK_PACKETFLAG_CONNECT|NETWORK_PACKETFLAG_ACCEPT)
+			{
+				conn_send(conn, NETWORK_PACKETFLAG_ACCEPT, 0, 0);
+				conn->state = NETWORK_CONNSTATE_ONLINE;
+				dbg_msg("connection", "got connect+accept, sending accept. connection online");
+			}
+		}
+		/*
+		else if(conn->state == NETWORK_CONNSTATE_CONNECTACCEPTED)
+		{
+			// connection made
+			if(p->flags == NETWORK_PACKETFLAG_ACCEPT)
+			{
+				conn->state = NETWORK_CONNSTATE_ONLINE;
+				dbg_msg("connection", "got accept. connection online");
+			}
+		}*/
+		else
+		{
+			// strange packet, wrong state
+		}
+	}
+	else
+	{
+		// strange packet, not ment for me
+	}
+	
+	return 0;
+}
+
+
+
+static void conn_update(NETCONNECTION *conn)
+{
+	if(conn->state == NETWORK_CONNSTATE_ERROR)
+		return;
+	
+	// check for timeout
+	if(conn->state != NETWORK_CONNSTATE_OFFLINE &&
+		conn->state != NETWORK_CONNSTATE_CONNECT &&
+		(time_get()-conn->last_recv_time) > time_freq()*3)
+	{
+		conn->state = NETWORK_CONNSTATE_ERROR;
+		conn->error_string = "timeout";
+	}
+	
+	// check for large buffer errors
+	if(conn->buffer.size() > 1024*64)
+	{
+		conn->state = NETWORK_CONNSTATE_ERROR;
+		conn->error_string = "too weak connection (out of buffer)";
+	}
+	
+	if(conn->buffer.first())
+	{
+		NETPACKETDATA *resend = (NETPACKETDATA *)conn->buffer.first()->data();
+		if(time_get()-resend->first_send_time > time_freq()*3)
+		{
+			conn->state = NETWORK_CONNSTATE_ERROR;
+			conn->error_string = "too weak connection (not acked for 3 seconds)";
+		}
+	}
+	
+	// send keep alives if nothing has happend for 250ms
+	if(conn->state == NETWORK_CONNSTATE_ONLINE)
+	{
+		if(time_get()-conn->last_send_time> time_freq()/4)
+			conn_send(conn, NETWORK_PACKETFLAG_VITAL, 0, 0);
+	}
+	else if(conn->state == NETWORK_CONNSTATE_CONNECT)
+	{
+		if(time_get()-conn->last_send_time > time_freq()/2) // send a new connect every 500ms
+			conn_send(conn, NETWORK_PACKETFLAG_CONNECT, 0, 0);
+	}
+	else if(conn->state == NETWORK_CONNSTATE_CONNECTACCEPTED)
+	{
+		if(time_get()-conn->last_send_time > time_freq()/2) // send a new connect/accept every 500ms
+			conn_send(conn, NETWORK_PACKETFLAG_CONNECT|NETWORK_PACKETFLAG_ACCEPT, 0, 0);
+	}
+}
+
+
+static int check_packet(unsigned char *buffer, int size, NETPACKETDATA *packet)
+{
+	// check the size
+	if(size < NETWORK_HEADER_SIZE || size > NETWORK_MAX_PACKET_SIZE)
+		return -1;
+	
+	// read the packet
+#ifdef NETWORK_HEADER_V2	
+	packet->ID[0] = 'T';
+	packet->ID[1] = 'W';
+	packet->version = NETWORK_VERSION;
+	packet->flags = buffer[0];
+	packet->seq = ((buffer[1]&0xf0)<<4)|buffer[2];
+	packet->ack = ((buffer[1]&0x0f)<<8)|buffer[3];
+	packet->crc = (buffer[8]<<24)|(buffer[9]<<16)|(buffer[10]<<8)|buffer[11];
+#else
+	packet->ID[0] = buffer[0];
+	packet->ID[1] = buffer[1];
+	packet->version = buffer[2];
+	packet->flags = buffer[3];
+	packet->seq = (buffer[4]<<8)|buffer[5];
+	packet->ack = (buffer[6]<<8)|buffer[7];
+	packet->crc = (buffer[8]<<24)|(buffer[9]<<16)|(buffer[10]<<8)|buffer[11];
+#endif
+	packet->data_size = size - NETWORK_HEADER_SIZE;
+	packet->data = buffer+NETWORK_HEADER_SIZE;
+	
+	// check the packet
+	if(packet->ID[0] != 'T' || packet->ID[1] != 'W')
+		return 1;
+	
+	if(packet->version != NETWORK_VERSION)
+		return 1;
+
+	// TODO: perform crc check
+	
+	// return success
+	return 0;
+}
+
+NETSERVER *net_server_open(int port, int max_clients, int flags)
+{
+	NETSERVER *server = (NETSERVER *)mem_alloc(sizeof(NETSERVER), 1);
+	mem_zero(server, sizeof(NETSERVER));
+	server->socket = net_udp4_create(port);
+	
+	for(int i = 0; i < NETWORK_MAX_CLIENTS; i++)
+		conn_init(&server->slots[i].conn, server->socket);
+
+	return server;
+}
+
+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++)
+	{
+		if(!s->slots[i].online && s->slots[i].conn.state == NETWORK_CONNSTATE_ONLINE)
+		{
+			s->slots[i].online = 1;
+			return i;
+		}
+	}
+	
+	return -1;
+}
+
+int net_server_delclient(NETSERVER *s)
+{
+	for(int i = 0; i < NETWORK_MAX_CLIENTS; i++)
+	{
+		if(s->slots[i].online && s->slots[i].conn.state != NETWORK_CONNSTATE_ONLINE)
+		{
+			s->slots[i].online = 0;
+			return i;
+		}
+	}
+	
+	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_reset(&s->slots[client_id].conn);
+	return 0;
+}
+
+int net_server_update(NETSERVER *s)
+{
+	for(int i = 0; i < NETWORK_MAX_CLIENTS; i++)
+	{
+		conn_update(&s->slots[i].conn);
+		if(conn_error(&s->slots[i].conn))
+			net_server_drop(s, i, conn_error(&s->slots[i].conn));
+	}
+	return 0;
+}
+
+int net_server_recv(NETSERVER *s, NETPACKET *packet)
+{
+	while(1)
+	{
+		NETADDR4 addr;
+		int bytes = net_udp4_recv(s->socket, &addr, s->recv_buffer, NETWORK_MAX_PACKET_SIZE);
+
+		// no more packets for now
+		if(bytes <= 0)
+			break;
+		
+		NETPACKETDATA data;
+		int r = check_packet(s->recv_buffer, bytes, &data);
+		if(r == 0)
+		{
+			// ok packet, process it
+			if(data.flags == NETWORK_PACKETFLAG_CONNECT)
+			{
+				// client that wants to connect
+				int found = 0;
+				for(int i = 0; i < NETWORK_MAX_CLIENTS; i++)
+				{
+					if(s->slots[i].conn.state == NETWORK_CONNSTATE_OFFLINE)
+					{
+						conn_feed(&s->slots[i].conn, &data, &addr);
+						found = 1;
+						break;
+					}
+				}
+				
+				if(!found)
+				{
+					// TODO: send error
+				}
+			}
+			else
+			{
+				// find matching slot
+				for(int i = 0; i < NETWORK_MAX_CLIENTS; i++)
+				{
+					if(net_addr4_cmp(&s->slots[i].conn.peeraddr, &addr) == 0)
+					{
+						if(conn_feed(&s->slots[i].conn, &data, &addr))
+						{
+							if(data.data_size)
+							{
+								packet->client_id = i;	
+								packet->address = addr;
+								packet->flags = 0;
+								packet->data_size = data.data_size;
+								packet->data = data.data;
+								return 1;
+							}
+						}
+					}
+				}
+			}
+		}
+		else
+		{
+			// errornous packet, drop it
+		}
+		
+		// read header
+		// do checksum
+	}	
+	
+	return 0;
+}
+
+int net_server_send(NETSERVER *s, NETPACKET *packet)
+{
+	// TODO: insert stuff for stateless stuff
+	dbg_assert(packet->client_id >= 0, "errornous client id");
+	dbg_assert(packet->client_id < NETWORK_MAX_CLIENTS, "errornous client id");
+	conn_send(&s->slots[packet->client_id].conn, 0, packet->data_size, packet->data);
+	return 0;
+}
+
+void net_server_stats(NETSERVER *s, NETSTATS *stats)
+{
+	mem_zero(stats, sizeof(NETSTATS));
+	
+	int num_stats = sizeof(NETSTATS)/sizeof(int);
+	int *istats = (int *)stats;
+	
+	for(int c = 0; c < NETWORK_MAX_CLIENTS; c++)
+	{
+		int *sstats = (int *)(&(s->slots[c].conn.stats));
+		for(int i = 0; i < num_stats; i++)
+			istats[i] += sstats[i];
+	}
+}
+
+//
+NETCLIENT *net_client_open(int flags)
+{
+	NETCLIENT *client = (NETCLIENT *)mem_alloc(sizeof(NETCLIENT), 1);
+	mem_zero(client, sizeof(NETCLIENT));
+	client->socket = net_udp4_create(0);
+	conn_init(&client->conn, client->socket);
+	return client;
+}
+
+int net_client_close(NETCLIENT *c)
+{
+	// TODO: implement me
+	return 0;
+}
+
+int net_client_update(NETCLIENT *c)
+{
+	// TODO: implement me
+	conn_update(&c->conn);
+	if(conn_error(&c->conn))
+		net_client_disconnect(c, conn_error(&c->conn));
+	return 0;
+}
+
+int net_client_disconnect(NETCLIENT *c, const char *reason)
+{
+	// TODO: do this more graceful
+	dbg_msg("net_client", "disconnected. reason=\"%s\"", reason);
+	conn_reset(&c->conn);
+	return 0;
+}
+
+int net_client_connect(NETCLIENT *c, NETADDR4 *addr)
+{
+	//net_client_disconnect(c);
+	conn_connect(&c->conn, addr);
+	return 0;
+}
+
+int net_client_recv(NETCLIENT *c, NETPACKET *packet)
+{
+	while(1)
+	{
+		NETADDR4 addr;
+		int bytes = net_udp4_recv(c->socket, &addr, c->recv_buffer, NETWORK_MAX_PACKET_SIZE);
+
+		// no more packets for now
+		if(bytes <= 0)
+			break;
+		
+		NETPACKETDATA data;
+		int r = check_packet(c->recv_buffer, bytes, &data);
+		if(r == 0)
+		{
+			// ok packet, process it
+			conn_feed(&c->conn, &data, &addr);
+			// fill in packet
+			packet->client_id = 0;
+			packet->address = addr;
+			packet->flags = 0;
+			packet->data_size = data.data_size;
+			packet->data = data.data;
+			return 1;
+		}
+		else
+		{
+			// errornous packet, drop it
+		}
+		
+		// read header
+		// do checksum
+	}
+	
+	return 0;
+}
+
+int net_client_send(NETCLIENT *c, NETPACKET *packet)
+{
+	// TODO: insert stuff for stateless stuff
+	dbg_assert(packet->client_id == 0, "errornous client id");
+	conn_send(&c->conn, 0, packet->data_size, packet->data);
+	return 0;
+}
+
+int net_client_state(NETCLIENT *c)
+{
+	if(c->conn.state == NETWORK_CONNSTATE_ONLINE)
+		return NETSTATE_ONLINE;
+	if(c->conn.state == NETWORK_CONNSTATE_OFFLINE)
+		return NETSTATE_OFFLINE;
+	return NETSTATE_CONNECTING;
+}
+
+void net_client_stats(NETCLIENT *c, NETSTATS *stats)
+{
+	*stats = c->conn.stats;
+}
diff --git a/src/engine/network.h b/src/engine/network.h
new file mode 100644
index 00000000..de035888
--- /dev/null
+++ b/src/engine/network.h
@@ -0,0 +1,104 @@
+
+struct NETPACKET
+{
+	// -1 means that it's a stateless packet
+	// 0 on the client means the server
+	int client_id;
+	NETADDR4 address; // only used when client_id == -1
+	int flags;
+	int data_size;
+	const void *data;
+};
+
+struct NETSTATS
+{
+	int send_bytes;
+	int recv_bytes;
+	int send_packets;
+	int recv_packets;
+	
+	int resend_packets;
+	int resend_bytes;
+};
+
+struct NETSERVER;
+struct NETCLIENT;
+
+enum
+{
+	NETFLAG_ALLOWSTATELESS=1,
+	PACKETFLAG_VITAL=1,
+	
+	NETSTATE_OFFLINE=0,
+	NETSTATE_CONNECTING,
+	NETSTATE_ONLINE,
+};
+
+// server side
+NETSERVER *net_server_open(int port, int max_clients, int flags);
+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
+void net_server_stats(NETSERVER *s, NETSTATS *stats);
+
+// client side
+NETCLIENT *net_client_open(int flags);
+int net_client_disconnect(NETCLIENT *c, const char *reason);
+int net_client_connect(NETCLIENT *c, NETADDR4 *addr);
+int net_client_recv(NETCLIENT *c, NETPACKET *packet);
+int net_client_send(NETCLIENT *c, NETPACKET *packet);
+int net_client_close(NETCLIENT *c);
+int net_client_update(NETCLIENT *c);
+int net_client_state(NETCLIENT *c);
+void net_client_stats(NETCLIENT *c, NETSTATS *stats);
+
+
+// wrapper classes for c++
+#ifdef __cplusplus
+class net_server
+{
+	NETSERVER *ptr;
+public:
+	net_server() : ptr(0) {}
+	~net_server() { close(); }
+	
+	int open(int port, int max, int flags) { ptr = net_server_open(port, max, flags); return ptr != 0; }
+	int close() { int r = net_server_close(ptr); ptr = 0; return r; }
+	
+	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); }
+	
+	void stats(NETSTATS *stats) { net_server_stats(ptr, stats); }
+};
+
+
+class net_client
+{
+	NETCLIENT *ptr;
+public:
+	net_client() : ptr(0) {}
+	~net_client() { close(); }
+	
+	int open(int flags) { ptr = net_client_open(flags); return ptr != 0; }
+	int close() { int r = net_client_close(ptr); ptr = 0; return r; }
+	
+	int connect(NETADDR4 *addr) { return net_client_connect(ptr, addr); }
+	int disconnect(const char *reason) { return net_client_disconnect(ptr, reason); }
+	
+	int recv(NETPACKET *packet) { return net_client_recv(ptr, packet); }
+	int send(NETPACKET *packet) { return net_client_send(ptr, packet); }
+	int update() { return net_client_update(ptr); }
+	
+	int state() { return net_client_state(ptr); }
+	void stats(NETSTATS *stats) { net_client_stats(ptr, stats); }
+};
+#endif
diff --git a/src/engine/packet.h b/src/engine/packet.h
index fd93d744..6c92bf31 100644
--- a/src/engine/packet.h
+++ b/src/engine/packet.h
@@ -1,442 +1,356 @@
+#include <stdarg.h>
 #include <baselib/stream/file.h>
 #include <baselib/network.h>
 
 #include "versions.h"
+#include "ringbuffer.h"
+#include "compression.h"
+#include "snapshot.h"
 
-#define MACRO_MAKEINT(a,b,c,d) ((a<<24)|(b<<16)|(c<<8)|d)
+enum
+{
+	NETMSG_NULL=0,
+	
+	// sent by server
+	NETMSG_MAP,
+	NETMSG_SNAP,
+	NETMSG_SNAPEMPTY,
+	NETMSG_SNAPSMALL,
+	
+	// sent by client
+	NETMSG_INFO,
+	NETMSG_ENTERGAME,
+	NETMSG_INPUT,
+	NETMSG_SNAPACK,
+	
+	// sent by both
+	NETMSG_ERROR,
+};
 
-// TODO: this is not KISS
-class packet
+
+// this should be revised
+enum
 {
-	friend class connection;
-protected:
-	enum
-	{
-		MAX_PACKET_SIZE = 1024,
-	};
-	
-	// packet data
-	struct header
+	MAX_NAME_LENGTH=32,
+	MAX_CLANNAME_LENGTH=32,
+	MAX_INPUT_SIZE=128,
+	MAX_SNAPSHOT_SIZE=64*1024,
+	MAX_SNAPSHOT_PACKSIZE=768
+};
+
+
+class snapshot_storage
+{
+	struct holder
 	{
-		unsigned id;
-		unsigned version;
-		unsigned size_and_flags;
-		unsigned crc;
-		
-		unsigned msg;
-		unsigned ack;
-		unsigned seq;
+		int tick;
+		int data_size;
+		int *data() { return (int *)(this+1); }
 	};
 	
-	unsigned char packet_data[MAX_PACKET_SIZE];
-	unsigned char *current;
+	ring_buffer buffer;
 	
-	// these are used to prepend data in the packet
-	// used for debugging so we have checks that we 
-	// pack and unpack the same way
-	enum
+public:
+	void purge_until(int tick)
 	{
-		DEBUG_TYPE_INT=0x1,
-		DEBUG_TYPE_STR=0x2,
-		DEBUG_TYPE_RAW=0x3,
-	};
+		while(1)
+		{
+			ring_buffer::item *i = buffer.first();
+			if(!i)
+				break;
+			holder *h = (holder *)i->data();
+			if(h->tick < tick)
+				buffer.pop_first();
+			else
+				break;
+		}
+	}
 	
-	// writes an int to the packet
-	void write_int_raw(int i)
+	void purge_all()
 	{
-		// TODO: check for overflow
-		*(int*)current = i;
-		current += sizeof(int);
+		buffer.reset();
 	}
 
-	// reads an int from the packet
-	int read_int_raw()
-	{
-		// TODO: check for overflow
-		int i = *(int*)current;
-		current += sizeof(int);
-		return i;
-	}
-	
-	void debug_insert_mark(int type, int size)
+	void add(int tick, int data_size, void *data)
 	{
-		write_int_raw((type<<16)|size);
+		holder *h = (holder *)buffer.alloc(sizeof(holder)+data_size);
+		h->tick = tick;
+		h->data_size = data_size;
+		mem_copy(h->data(), data, data_size);
 	}
 	
-	void debug_verify_mark(int type, int size)
+	int get(int tick, void **data)
 	{
-		if(read_int_raw() != ((type<<16) | size))
-			dbg_assert(0, "error during packet disassembly");
+		ring_buffer::item *i = buffer.first();
+		while(i)
+		{
+			holder *h = (holder *)i->data();
+			if(h->tick == tick)
+			{
+				*data = h->data();
+				return h->data_size;
+			}
+				
+			i = i->next;
+		}
+		
+		return -1;
 	}
-	
+};
+/*
+class snapshot_delta_builder
+{
 public:
+	static const int MAX_ITEMS = 512;
 
-	enum
-	{
-		FLAG_VITAL=1,
-		FLAG_RESEND=2
-	};
+	char data[MAX_SNAPSHOT_SIZE];
+	int data_size;
 
-	packet(unsigned msg=0)
-	{
-		current = packet_data;
-		current += sizeof(header);
+	int offsets[MAX_ITEMS];
+	int num_items;
+
+	int top_size;
+	int top_items;
 
-		((header*)packet_data)->id = MACRO_MAKEINT('K','M','A',1);
-		((header*)packet_data)->version = TEEWARS_NETVERSION;
-		((header*)packet_data)->msg = msg;
+	int snapnum;
+
+	snapshot_delta_builder()
+	{
+		top_size = 0;
+		top_items = 0;
+		snapnum = 0;
 	}
-	
-	void set_header(unsigned ack, unsigned seq)
+
+	void start()
 	{
-		((header*)packet_data)->ack = ack;
-		((header*)packet_data)->seq = seq;
+		data_size = 0;
+		num_items = 0;
 	}
-	
-	// writes an int to the packet
-	void write_int(int i)
+
+	int finish(void *snapdata)
 	{
-		debug_insert_mark(DEBUG_TYPE_INT, 4);
-		write_int_raw(i);
+		snapnum++;
+
+		// flattern and make the snapshot
+		snapshot *snap = (snapshot *)snapdata;
+		snap->data_size = data_size;
+		snap->num_items = num_items;
+		int offset_size = sizeof(int)*num_items;
+		mem_copy(snap->offsets, offsets, offset_size);
+		mem_copy(snap->data_start(), data, data_size);
+		return sizeof(int) + offset_size + data_size;
+	}
+
+	void *new_item(int type, int id, int size)
+	{
+		snapshot::item *obj = (snapshot::item *)(data+data_size);
+		obj->type_and_id = (type<<16)|id;
+		offsets[num_items] = data_size;
+		data_size += sizeof(int) + size;
+		num_items++;
+		dbg_assert(data_size < MAX_SNAPSHOT_SIZE, "too much data");
+		dbg_assert(num_items < MAX_ITEMS, "too many items");
+
+		return &obj->data;
 	}
+};
+*/
+
+class snapshot_builder
+{
+public:
+	static const int MAX_ITEMS = 512;
 
-	void write_raw(const char *raw, int size)
+	char data[MAX_SNAPSHOT_SIZE];
+	int data_size;
+
+	int offsets[MAX_ITEMS];
+	int num_items;
+
+	int top_size;
+	int top_items;
+
+	int snapnum;
+
+	snapshot_builder()
 	{
-		debug_insert_mark(DEBUG_TYPE_RAW, size);
-		while(size--)
-			*current++ = *raw++;
+		top_size = 0;
+		top_items = 0;
+		snapnum = 0;
 	}
 
-	// writes a string to the packet
-	void write_str(const char *str)
+	void start()
 	{
-		debug_insert_mark(DEBUG_TYPE_STR, 0);
-		int s = strlen(str)+1;
-		write_int_raw(s);
-		for(;*str; current++, str++)
-			*current = *str;
-		*current = 0;
-		current++;
+		data_size = 0;
+		num_items = 0;
 	}
 	
-	// reads an int from the packet
-	int read_int()
+	snapshot::item *get_item(int index) 
 	{
-		debug_verify_mark(DEBUG_TYPE_INT, 4);
-		return read_int_raw();
+		return (snapshot::item *)&(data[offsets[index]]);
 	}
 	
-	// reads a string from the packet
-	const char *read_str()
+	int *get_item_data(int key)
 	{
-		debug_verify_mark(DEBUG_TYPE_STR, 0);
-		int size = read_int_raw();
-		const char *s = (const char *)current;
-		//dbg_msg("packet", "reading string '%s' (%d)", s, size);
-		current += size;
-		return s;
+		for(int i = 0; i < num_items; i++)
+		{
+			if(get_item(i)->key() == key)
+				return (int *)get_item(i)->data();
+		}
+		return 0;
 	}
-	
-	const char *read_raw(int size)
+
+	int finish(void *snapdata)
 	{
-		debug_verify_mark(DEBUG_TYPE_RAW, size);
-		const char *d = (const char *)current;
-		current += size;
-		return d;
+		snapnum++;
+
+		// flattern and make the snapshot
+		snapshot *snap = (snapshot *)snapdata;
+		snap->data_size = data_size;
+		snap->num_items = num_items;
+		int offset_size = sizeof(int)*num_items;
+		mem_copy(snap->offsets(), offsets, offset_size);
+		mem_copy(snap->data_start(), data, data_size);
+		return sizeof(snapshot) + offset_size + data_size;
 	}
-	
-	// TODO: impelement this
-	bool is_good() const { return true; }
 
-	unsigned version() const { return ((header*)packet_data)->version; }
-	unsigned msg() const { return ((header*)packet_data)->msg; }
-	unsigned seq() const { return ((header*)packet_data)->seq; }
-	unsigned ack() const { return ((header*)packet_data)->ack; }
-	unsigned flags() const { return (((header*)packet_data)->size_and_flags) & 0xffff; }
-	
-	// access functions to get the size and data
-	int size() const { return (int)(current-(unsigned char*)packet_data); }
-	int max_size() const { return MAX_PACKET_SIZE; }
-	void *data() { return packet_data; }
+	void *new_item(int type, int id, int size)
+	{
+		snapshot::item *obj = (snapshot::item *)(data+data_size);
+		obj->type_and_id = (type<<16)|id;
+		offsets[num_items] = data_size;
+		data_size += sizeof(snapshot::item) + size;
+		num_items++;
+		dbg_assert(data_size < MAX_SNAPSHOT_SIZE, "too much data");
+		dbg_assert(num_items < MAX_ITEMS, "too many items");
+
+		return obj->data();
+	}
 };
 
-// TODO: remove all the allocations from this class
-class ring_buffer
+class data_packer
 {
-	struct item
+	enum
 	{
-		item *next;
-		item *prev;
-		int size;
+		BUFFER_SIZE=1024*2
 	};
 	
-	item *first;
-	item *last;
-	
-	unsigned buffer_size;
+	unsigned char buffer[BUFFER_SIZE];
+	unsigned char *current;
+	unsigned char *end;
+	int error;
 public:
-	ring_buffer()
-	{
-		first = 0;
-		last = 0;
-		buffer_size = 0;
-	}
-	
-	~ring_buffer()
-	{
-		reset();
-	}
-	
 	void reset()
 	{
-		// clear all
-		while(peek_data())
-			next();		
+		error = 0;
+		current = buffer;
+		end = current + BUFFER_SIZE;
 	}
 	
-	void *alloc(int size)
+	void add_int(int i)
 	{
-		item *i = (item*)mem_alloc(sizeof(item)+size, 1);
-		i->size = size;
-		
-		i->prev = last;
-		i->next = 0;
-		if(last)
-			last->next = i;
-		else
-			first = i;
-		last = i;
-		
-		buffer_size += size;
-		return (void*)(i+1);
-	}
-	
-	unsigned peek_size()
-	{
-		if(!first)
-			return 0;
-		return first->size;
+		// TODO: add space check
+		// TODO: variable length encoding perhaps
+		// TODO: add debug marker
+		current = vint_pack(current, i);
+		//*current++ = (i>>24)&0xff;
+		//*current++ = (i>>16)&0xff;
+		//*current++ = (i>>8)&0xff;
+		//*current++ = i&0xff;
 	}
 
-	void *peek_data()
+	void add_string(const char *p, int limit)
 	{
-		if(!first)
-			return 0;
-		return (void*)(first+1);
-	}
-	
-	void next()
-	{
-		if(first)
+		// TODO: add space check
+		// TODO: add debug marker
+		if(limit > 0)
 		{
-			item *next = first->next;
-			buffer_size += first->size;
-			mem_free(first);
-			first = next;
-			if(first)
-				first->prev = 0;
-			else
-				last = 0;
+			while(*p && limit != 0)
+			{
+				*current++ = *p++;
+				limit--;
+			}
+			*current++ = 0;
+		}
+		else
+		{
+			while(*p)
+				*current++ = *p++;
+			*current++ = 0;
 		}
 	}
 	
-	unsigned size() { return buffer_size; }
-};
-
-//
-class connection
-{
-	baselib::socket_udp4 *socket;
-	baselib::netaddr4 addr;
-	unsigned seq;
-	unsigned ack;
-	
-	unsigned counter_sent_bytes;
-	unsigned counter_recv_bytes;
-	
-	int needs_resend;
-	
-	/*
-	struct resend_packet
-	{
-		resend_packet *next;
-		unsigned seq;
-		unsigned msg;
-		unsigned size;
-		char data[1];
-	};
-	
-	resend_packet *first_resend;
-	resend_packet *last_resend;
-	*/
-	
-	ring_buffer resend_buffer;
-
-	void save_for_resend(packet *p)
-	{
-		/*
-		packet *n = (packet *)resend_buffer.alloc(p->size());
-		mem_copy(n->data(), p->data(), p->size());
-		n->current = (unsigned char*)n->data() + p->size();
-		*/
-	}
-	
-	void remove_resends(unsigned ack)
+	void add_raw(const unsigned char *data, int size)
 	{
-		/*
-		while(1)
+		// TODO: add space check
+		// TODO: add debug marker
+		//add_int(size);
+		while(size)
 		{
-			packet *p = (packet *)resend_buffer.peek_data();
-			if(!p)
-				break;
-			
-			if(p->seq() > ack)
-				break;
-			resend_buffer.next();
-		}*/
+			*current++ = *data++;
+			size--;
+		}
 	}
 	
-public:
-	void counter_reset()
+	int size() const
 	{
-		counter_sent_bytes = 0;
-		counter_recv_bytes = 0;
+		return (const unsigned char *)current-(const unsigned char *)buffer;
 	}
 	
-	void counter_get(unsigned *sent, unsigned *recved)
+	const unsigned char *data()
 	{
-		*sent = counter_sent_bytes;
-		*recved = counter_recv_bytes;
+		return (const unsigned char *)buffer;
 	}
+};
 
-	void init(baselib::socket_udp4 *socket, const baselib::netaddr4 *addr)
+class data_unpacker
+{
+	const unsigned char *current;
+	const unsigned char *start;
+	const unsigned char *end;
+	int error;
+	
+public:
+	void reset(const unsigned char *data, int size)
 	{
-		resend_buffer.reset();
-		
-		this->addr = *addr;
-		this->socket = socket;
-		ack = 0;
-		seq = 0;
-		needs_resend = 0;
-		counter_reset();
+		error = 0;
+		start = data;
+		end = start + size;
+		current = start;
 	}
 	
-	void send(packet *p)
+	int get_int()
 	{
-		if(p->flags()&packet::FLAG_VITAL)
-			seq++;
-		
-		p->set_header(ack, seq);
-
-		if(p->flags()&packet::FLAG_VITAL)
-			save_for_resend(p);
-
-		// TODO: request resend if needed, use needs_resend variable
-		
-		socket->send(&address(), p->data(), p->size());
-		counter_sent_bytes += p->size();
+		int i;
+		current = vint_unpack(current, &i);
+		// TODO: might be changed into variable width
+		// TODO: add range check
+		// TODO: add debug marker
+		//i = (current[0]<<24) | (current[1]<<16) | (current[2]<<8) | (current[3]);
+		//current += 4;
+		return i;
 	}
 	
-	packet *feed(packet *p)
+	const char *get_string()
 	{
-		counter_recv_bytes += p->size();
-		
-		if(p->flags()&packet::FLAG_VITAL)
-		{
-			if(p->seq() == ack+1)
-			{
-				// packet in seqence, ack it
-				ack++;
-				//dbg_msg("network/connection", "packet in sequence. seq/ack=%x", ack);
-				return p;
-			}
-			else if(p->seq() > ack)
-			{
-				// packet loss
-				needs_resend = 1;
-				dbg_msg("network/connection", "packet loss! seq=%x ack=%x+1", p->seq(), ack);
-				return p;
-			}
-			else
-			{
-				// we already got this packet
-				return 0x0;
-			}
-		}
-		
-		// remove resends
-		remove_resends(p->ack());
-		
-		// handle resends
-		if(p->flags()&packet::FLAG_RESEND)
-		{
-			// peer as requested a resend of all non acked packages.
-			
-		}
-		
-		return p;		
+		// TODO: add range check
+		// TODO: add debug marker
+		const char *ptr = (const char *)current;
+		while(*current) // skip the string
+			current++;
+		current++;
+		return ptr;
 	}
 	
-	const baselib::netaddr4 &address() const { return addr; }
-	
-	void update()
+	const unsigned char *get_raw(int size)
 	{
+		// TODO: add range check
+		// TODO: add debug marker
+		//int s = get_int();
+		//if(size)
+			//*size = s;
+		const unsigned char *ptr = current;
+		current += size;
+		return ptr;
 	}
 };
-
-//const char *NETWORK_VERSION = "development";
-
-enum
-{
-	NETMSG_CONTEXT_CONNECT=0x00010000,
-	NETMSG_CONTEXT_GAME=0x00020000,
-	NETMSG_CONTEXT_GLOBAL=0x00040000,
-	
-	// connection phase
-	NETMSG_CLIENT_CONNECT=NETMSG_CONTEXT_CONNECT|1,
-		// str32 name
-		// str32 clan
-		// str32 password
-		// str32 skin	
-	
-	// TODO: These should be implemented to make the server send the map to the client on connect
-	// NETMSG_CLIENT_FETCH,
-	// NETMSG_SERVER_MAPDATA,
-	
-	NETMSG_SERVER_ACCEPT=NETMSG_CONTEXT_CONNECT|2,
-		// str32 mapname
-
-	
-	NETMSG_CLIENT_DONE=NETMSG_CONTEXT_CONNECT|3,
-		// nothing
-	
-	// game phase
-	NETMSG_SERVER_SNAP = NETMSG_CONTEXT_GAME|1, // server will spam these
-		// int num_parts
-		// int part
-		// int size
-		// data *
-		
-	NETMSG_CLIENT_INPUT = NETMSG_CONTEXT_GAME|1, // client will spam these
-		// int input[MAX_INPUTS]
-	
-	NETMSG_SERVER_EVENT = NETMSG_CONTEXT_GAME|2,
-	NETMSG_CLIENT_EVENT = NETMSG_CONTEXT_GAME|2,
-
-	NETMSG_CLIENT_CHECKALIVE = NETMSG_CONTEXT_GAME|3, // check if client is alive
-	
-	NETMSG_CLIENT_ERROR=0x0fffffff,
-		// str128 reason
-		
-	NETMSG_SERVER_ERROR=0x0fffffff,
-		// str128 reason
-};
-
-enum
-{
-	MAX_NAME_LENGTH=32,
-	MAX_CLANNAME_LENGTH=32,
-	MAX_INPUT_SIZE=128,
-	MAX_SNAPSHOT_SIZE=64*1024,
-	MAX_SNAPSHOT_PACKSIZE=768
-};
diff --git a/src/engine/ringbuffer.h b/src/engine/ringbuffer.h
new file mode 100644
index 00000000..3208efbf
--- /dev/null
+++ b/src/engine/ringbuffer.h
@@ -0,0 +1,84 @@
+
+// TODO: remove all the allocations from this class
+class ring_buffer
+{
+public:
+	struct item
+	{
+		item *next;
+		item *prev;
+		int size;
+		unsigned char *data() { return (unsigned char *)(this+1); }
+	};
+	
+	item *first_item;
+	item *last_item;
+	
+	unsigned buffer_size;
+	
+	ring_buffer()
+	{
+		first_item = 0;
+		last_item = 0;
+		buffer_size = 0;
+	}
+	
+	~ring_buffer()
+	{
+		reset();
+	}
+	
+	void reset()
+	{
+		// clear all
+		while(first())
+			pop_first();
+	}
+	
+	void *alloc(int size)
+	{
+		item *i = (item*)mem_alloc(sizeof(item)+size, 1);
+		i->size = size;
+		
+		i->prev = last_item;
+		i->next = 0;
+		if(last_item)
+			last_item->next = i;
+		else
+			first_item = i;
+		last_item = i;
+		
+		buffer_size += size;
+		return i->data();
+	}
+	
+	item *first()
+	{
+		return first_item;
+	}
+
+	/*
+	void *peek_data()
+	{
+		if(!first)
+			return 0;
+		return (void*)(first+1);
+	}*/
+	
+	void pop_first()
+	{
+		if(first_item)
+		{
+			item *next = first_item->next;
+			buffer_size -= first_item->size;
+			mem_free(first_item);
+			first_item = next;
+			if(first_item)
+				first_item->prev = 0;
+			else
+				last_item = 0;
+		}
+	}
+	
+	unsigned size() { return buffer_size; }
+};
diff --git a/src/engine/server/server.cpp b/src/engine/server/server.cpp
index d0ae80f0..5632689e 100644
--- a/src/engine/server/server.cpp
+++ b/src/engine/server/server.cpp
@@ -1,138 +1,23 @@
-#include <stdio.h>
-#include <string.h>
+#include <cstdio>
+#include <cstring>
+#include <cstdlib>
 
 #include <baselib/system.h>
 
 #include <engine/interface.h>
 
-//#include "socket.h"
 #include <engine/packet.h>
 #include <engine/snapshot.h>
 
-#include <engine/lzw.h>
+#include <engine/compression.h>
 #include <engine/versions.h>
 
-namespace baselib {}
-using namespace baselib;
-
-int net_addr4_cmp(const NETADDR4 *a, const NETADDR4 *b)
-{
-	if(
-		a->ip[0] != b->ip[0] ||
-		a->ip[1] != b->ip[1] ||
-		a->ip[2] != b->ip[2] ||
-		a->ip[3] != b->ip[3] ||
-		a->port != b->port
-	)
-		return 1;
-	return 0;
-}
-
-// --- string handling (MOVE THESE!!) ---
-void snap_encode_string(const char *src, int *dst, int length, int max_length)
-{
-        const unsigned char *p = (const unsigned char *)src;
-
-        // handle whole int
-        for(int i = 0; i < length/4; i++)
-        {
-                *dst = (p[0]<<24|p[1]<<16|p[2]<<8|p[3]);
-                p += 4;
-                dst++;
-        }
-
-        // take care of the left overs
-        int left = length%4;
-        if(left)
-        {
-                unsigned last = 0;
-                switch(left)
-                {
-                        case 3: last |= p[2]<<8;
-                        case 2: last |= p[1]<<16;
-                        case 1: last |= p[0]<<24;
-                }
-                *dst = last;
-        }
-}
-
-
-class snapshot_builder
-{
-public:
-	static const int MAX_ITEMS = 512;
-	//static const int MAX_DATA_SIZE=1*1024;
-
-	char data[MAX_SNAPSHOT_SIZE];
-	int data_size;
-
-	int offsets[MAX_ITEMS];
-	int num_items;
-
-	int top_size;
-	int top_items;
-
-	int snapnum;
-
-	snapshot_builder()
-	{
-		top_size = 0;
-		top_items = 0;
-		snapnum = 0;
-	}
-
-	void start()
-	{
-		data_size = 0;
-		num_items = 0;
-	}
+#include <engine/network.h>
+#include <engine/config.h>
 
-	int finish(void *snapdata)
-	{
-		snapnum++;
 
-		// collect some data
-		/*
-		int change = 0;
-		if(data_size > top_size)
-		{
-			change++;
-			top_size = data_size;
-		}
-
-		if(num_items > top_items)
-		{
-			change++;
-			top_items = num_items;
-		}
-
-		if(change)
-		{
-			dbg_msg("snapshot", "new top, items=%d size=%d", top_items, top_size);
-		}*/
-
-		// flattern and make the snapshot
-		snapshot *snap = (snapshot *)snapdata;
-		snap->num_items = num_items;
-		int offset_size = sizeof(int)*num_items;
-		mem_copy(snap->offsets, offsets, offset_size);
-		mem_copy(snap->data_start(), data, data_size);
-		return sizeof(int) + offset_size + data_size;
-	}
-
-	void *new_item(int type, int id, int size)
-	{
-		snapshot::item *obj = (snapshot::item *)(data+data_size);
-		obj->type_and_id = (type<<16)|id;
-		offsets[num_items] = data_size;
-		data_size += sizeof(int) + size;
-		num_items++;
-		dbg_assert(data_size < MAX_SNAPSHOT_SIZE, "too much data");
-		dbg_assert(num_items < MAX_ITEMS, "too many items");
-
-		return &obj->data;
-	}
-};
+namespace baselib {}
+using namespace baselib;
 
 static snapshot_builder builder;
 
@@ -143,7 +28,6 @@ void *snap_new_item(int type, int id, int size)
 	return builder.new_item(type, id, size);
 }
 
-
 //
 class client
 {
@@ -157,34 +41,21 @@ public:
 
 	// connection state info
 	int state;
-
-	// (ticks) if lastactivity > 5 seconds kick him
-	int64 lastactivity;
-	connection conn;
+	
+	int last_acked_snapshot;
+	snapshot_storage snapshots;
 
 	char name[MAX_NAME_LENGTH];
 	char clan[MAX_CLANNAME_LENGTH];
-	/*
-	client()
-	{
-		state = STATE_EMPTY;
-		name[0] = 0;
-		clan[0] = 0;
-	}
-
-	~client()
-	{
-		dbg_assert(state == STATE_EMPTY, "client destoyed while in use");
-	}*/
 
 	bool is_empty() const { return state == STATE_EMPTY; }
 	bool is_ingame() const { return state == STATE_INGAME; }
-	const netaddr4 &address() const { return conn.address(); }
 };
 
 static client clients[MAX_CLIENTS];
 static int current_tick = 0;
 static int send_heartbeats = 1;
+static net_server net;
 
 int server_tick()
 {
@@ -193,7 +64,7 @@ int server_tick()
 
 int server_tickspeed()
 {
-	return 50;
+	return SERVER_TICK_SPEED;
 }
 
 int server_init()
@@ -203,7 +74,7 @@ int server_init()
 		clients[i].state = client::STATE_EMPTY;
 		clients[i].name[0] = 0;
 		clients[i].clan[0] = 0;
-		clients[i].lastactivity = 0;
+		//clients[i].lastactivity = 0;
 	}
 
 	current_tick = 0;
@@ -225,12 +96,27 @@ int server_getclientinfo(int client_id, client_info *info)
 	return 0;
 }
 
-//
+
+int server_send_msg(int client_id)
+{
+	const msg_info *info = msg_get_info();
+	NETPACKET packet;
+	packet.client_id = client_id;
+	packet.data = info->data;
+	packet.data_size = info->size;
+
+	if(info->flags&MSGFLAG_VITAL)	
+		packet.flags = PACKETFLAG_VITAL;
+	
+	net.send(&packet);
+	return 0;
+}
+	
+// TODO: remove this class
 class server
 {
 public:
-
-	socket_udp4 game_socket;
+	//socket_udp4 game_socket;
 
 	const char *map_name;
 	const char *server_name;
@@ -256,14 +142,14 @@ public:
 		}
 
 		// start server
-		if(!game_socket.open(8303))
+		if(!net.open(8303, 0, 0))
 		{
 			dbg_msg("network/server", "couldn't open socket");
 			return false;
 		}
 
-		for(int i = 0; i < MAX_CLIENTS; i++)
-			dbg_msg("network/server", "\t%d: %d", i, clients[i].state);
+		//for(int i = 0; i < MAX_CLIENTS; i++)
+			//dbg_msg("network/server", "\t%d: %d", i, clients[i].state);
 
 		if (net_host_lookup(MASTER_SERVER_ADDRESS, MASTER_SERVER_PORT, &master_server) != 0)
 		{
@@ -304,17 +190,6 @@ public:
 					snaptime += time_get()-start;
 				}
 
-				// Check for client timeouts
-				for (int i = 0; i < MAX_CLIENTS; i++)
-				{
-					if (clients[i].state != client::STATE_EMPTY)
-					{
-						// check last activity time
-						if (((lasttick - clients[i].lastactivity) / time_freq()) > SERVER_CLIENT_TIMEOUT)
-							client_timeout(i);
-					}
-				}
-
 				lasttick += time_per_tick;
 			}
 
@@ -332,8 +207,7 @@ public:
 
 						// TODO: fix me
 						netaddr4 me(127, 0, 0, 0, 8303);
-
-						send_heartbeat(0, &me, players, MAX_CLIENTS, server_name, mapname);
+						//send_heartbeat(0, &me, players, MAX_CLIENTS, server_name, mapname);
 					}
 
 					lastheartbeat = t+time_per_heartbeat;
@@ -357,6 +231,7 @@ public:
 					(simulationtime+snaptime+networktime)/(float)reportinterval*100.0f);
 
 				unsigned sent_total=0, recv_total=0;
+				/*
 				for (int i = 0; i < MAX_CLIENTS; i++)
 					if (!clients[i].is_empty())
 					{
@@ -366,7 +241,7 @@ public:
 						sent_total += s;
 						recv_total += r;
 					}
-
+				*/
 
 				dbg_msg("server/report", "biggestsnap=%d send=%d recv=%d",
 					biggest_snapshot, sent_total/3, recv_total/3);
@@ -393,6 +268,8 @@ public:
 
 	void snap()
 	{
+		//if(current_tick&1)
+		//	return;
 		mods_presnap();
 
 		for(int i = 0; i < MAX_CLIENTS; i++)
@@ -400,33 +277,86 @@ public:
 			if(clients[i].is_ingame())
 			{
 				char data[MAX_SNAPSHOT_SIZE];
+				char deltadata[MAX_SNAPSHOT_SIZE];
 				char compdata[MAX_SNAPSHOT_SIZE];
+				//char intdata[MAX_SNAPSHOT_SIZE];
 				builder.start();
 				mods_snap(i);
 
 				// finish snapshot
 				int snapshot_size = builder.finish(data);
 
-				// compress it
-				int compsize = lzw_compress(data, snapshot_size, compdata);
-				snapshot_size = compsize;
-
-				if(snapshot_size > biggest_snapshot)
-					biggest_snapshot = snapshot_size;
-
-				const int max_size = MAX_SNAPSHOT_PACKSIZE;
-				int numpackets = (snapshot_size+max_size-1)/max_size;
-				for(int n = 0, left = snapshot_size; left; n++)
+				// remove old snapshos
+				// keep 1 seconds worth of snapshots
+				clients[i].snapshots.purge_until(current_tick-SERVER_TICK_SPEED);
+				
+				// save it the snapshot
+				clients[i].snapshots.add(current_tick, snapshot_size, data);
+				
+				// find snapshot that we can preform delta against
+				static snapshot emptysnap;
+				emptysnap.data_size = 0;
+				emptysnap.num_items = 0;
+				
+				snapshot *deltashot = &emptysnap;
+				int deltashot_size;
+				int delta_tick = -1;
+				{
+					void *delta_data;
+					deltashot_size = clients[i].snapshots.get(clients[i].last_acked_snapshot, (void **)&delta_data);
+					if(deltashot_size >= 0)
+					{
+						delta_tick = clients[i].last_acked_snapshot;
+						deltashot = (snapshot *)delta_data;
+					}
+				}
+				
+				// create delta
+				int deltasize = snapshot_create_delta(deltashot, (snapshot*)data, deltadata);
+				
+				if(deltasize)
 				{
-					int chunk = left < max_size ? left : max_size;
-					left -= chunk;
-
-					packet p(NETMSG_SERVER_SNAP);
-					p.write_int(numpackets);
-					p.write_int(n);
-					p.write_int(chunk);
-					p.write_raw(&compdata[n*max_size], chunk);
-					clients[i].conn.send(&p);
+					// compress it
+					//int intsize = -1;
+					unsigned char intdata[MAX_SNAPSHOT_SIZE];
+					int intsize = intpack_compress(deltadata, deltasize, intdata);
+					
+					int compsize = zerobit_compress(intdata, intsize, compdata);
+					//dbg_msg("compress", "%5d --delta-> %5d --int-> %5d --zero-> %5d %5d",
+						//snapshot_size, deltasize, intsize, compsize, intsize-compsize);
+					snapshot_size = compsize;
+
+					if(snapshot_size > biggest_snapshot)
+						biggest_snapshot = snapshot_size;
+
+					const int max_size = MAX_SNAPSHOT_PACKSIZE;
+					int numpackets = (snapshot_size+max_size-1)/max_size;
+					for(int n = 0, left = snapshot_size; left; n++)
+					{
+						int chunk = left < max_size ? left : max_size;
+						left -= chunk;
+
+						if(numpackets == 1)
+							msg_pack_start(NETMSG_SNAPSMALL, 0);
+						else
+							msg_pack_start(NETMSG_SNAP, 0);
+						msg_pack_int(current_tick);
+						msg_pack_int(current_tick-delta_tick); // compressed with
+						msg_pack_int(chunk);
+						msg_pack_raw(&compdata[n*max_size], chunk);
+						msg_pack_end();
+						//const msg_info *info = msg_get_info();
+						//dbg_msg("server", "size=%d", info->size);
+						server_send_msg(i);
+					}
+				}
+				else
+				{
+					msg_pack_start(NETMSG_SNAPEMPTY, 0);
+					msg_pack_int(current_tick);
+					msg_pack_int(current_tick-delta_tick); // compressed with
+					msg_pack_end();
+					server_send_msg(i);
 				}
 			}
 		}
@@ -434,11 +364,12 @@ public:
 		mods_postsnap();
 	}
 
-	void send_accept(client *client, const char *map)
+	void send_map(int cid)
 	{
-		packet p(NETMSG_SERVER_ACCEPT);
-		p.write_str(map);
-		client->conn.send(&p);
+		msg_pack_start(NETMSG_MAP, MSGFLAG_VITAL);
+		msg_pack_string(map_name, 0);
+		msg_pack_end();
+		server_send_msg(cid);
 	}
 
 	void drop(int cid, const char *reason)
@@ -451,161 +382,47 @@ public:
 		dbg_msg("game", "player dropped. reason='%s' cid=%x name='%s'", reason, cid, clients[cid].name);
 	}
 
-	int find_client(const netaddr4 *addr)
+	void process_client_packet(NETPACKET *packet)
 	{
-		// fetch client
-		for(int i = 0; i < MAX_CLIENTS; i++)
+		int cid = packet->client_id;
+		int msg = msg_unpack_start(packet->data, packet->data_size);
+		if(msg == NETMSG_INFO)
 		{
-			if(!clients[i].is_empty() && clients[i].address() == *addr)
-				return i;
+			strncpy(clients[cid].name, msg_unpack_string(), MAX_NAME_LENGTH);
+			strncpy(clients[cid].clan, msg_unpack_string(), MAX_CLANNAME_LENGTH);
+			const char *password = msg_unpack_string();
+			const char *skin = msg_unpack_string();
+			(void)password; // ignore these variables
+			(void)skin;
+			send_map(cid);
 		}
-		return -1;
-	}
-
-	void client_process_packet(int cid, packet *p)
-	{
-		clients[cid].lastactivity = lasttick;
-		if(p->msg() == NETMSG_CLIENT_DONE)
+		else if(msg == NETMSG_ENTERGAME)
 		{
 			dbg_msg("game", "player as entered the game. cid=%x", cid);
 			clients[cid].state = client::STATE_INGAME;
 			mods_client_enter(cid);
 		}
-		else if(p->msg() == NETMSG_CLIENT_INPUT)
+		else if(msg == NETMSG_INPUT)
 		{
 			int input[MAX_INPUT_SIZE];
-			int size = p->read_int();
+			int size = msg_unpack_int();
 			for(int i = 0; i < size/4; i++)
-				input[i] = p->read_int();
-			if(p->is_good())
-			{
-				//dbg_msg("network/server", "applying input %d %d %d", input[0], input[1], input[2]);
-				mods_client_input(cid, input);
-			}
+				input[i] = msg_unpack_int();
+			mods_client_input(cid, input);
 		}
-		else if(p->msg() == NETMSG_CLIENT_ERROR)
+		else if(msg == NETMSG_SNAPACK)
 		{
-			const char *reason = p->read_str();
-			if(p->is_good())
-				dbg_msg("network/server", "client error. cid=%x reason='%s'", cid, reason);
-			else
-				dbg_msg("network/server", "client error. cid=%x", cid);
-			drop(cid, "client error");
+			clients[cid].last_acked_snapshot = msg_unpack_int();
 		}
 		else
 		{
-			dbg_msg("network/server", "invalid message. cid=%x msg=%x", cid, p->msg());
-			drop(cid, "invalid message");
+			dbg_msg("server", "strange message cid=%d msg=%d data_size=%d", cid, msg, packet->data_size);
 		}
+		
 	}
 
-	void process_packet(packet *p, netaddr4 *from)
+	void process_packet(NETPACKET *packet)
 	{
-		// do version check
-		if(p->version() != TEEWARS_NETVERSION)
-		{
-			// send an empty packet back.
-			// this will allow the client to check the version
-			packet p;
-			game_socket.send(from, p.data(), p.size());
-			return;
-		}
-
-		if(p->msg() == NETMSG_CLIENT_CONNECT)
-		{
-			// we got no state for this client yet
-			const char *version;
-			const char *name;
-			const char *clan;
-			const char *password;
-			const char *skin;
-
-			version = p->read_str();
-			name = p->read_str();
-			clan = p->read_str();
-			password = p->read_str();
-			skin = p->read_str();
-
-			if(p->is_good())
-			{
-				/*
-				// check version
-				if(strcmp(version, TEEWARS_NETVERSION) != 0)
-				{
-					dbg_msg("network/server", "wrong version connecting '%s'", version);
-					// TODO: send error
-					return;
-				}*/
-
-				// look for empty slot, linear search
-				int id = -1;
-				for(int i = 0; i < MAX_CLIENTS; i++)
-					if(clients[i].is_empty())
-					{
-						id = i;
-						break;
-					}
-
-				if(id != -1)
-				{
-					// slot found
-					// TODO: perform correct copy here
-					mem_copy(clients[id].name, name, MAX_NAME_LENGTH);
-					mem_copy(clients[id].clan, clan, MAX_CLANNAME_LENGTH);
-					clients[id].state = client::STATE_CONNECTING;
-					clients[id].conn.init(&game_socket, from);
-
-					clients[id].lastactivity = lasttick;
-					clients[id].name[MAX_NAME_LENGTH-1] = 0;
-					clients[id].clan[MAX_CLANNAME_LENGTH-1] = 0;
-
-					dbg_msg("network/server", "client connected. '%s' on slot %d", name, id);
-
-					// TODO: return success
-					send_accept(&clients[id], map_name);
-				}
-				else
-				{
-					// no slot found
-					// TODO: send error
-					dbg_msg("network/server", "client connected but server is full");
-
-					for(int i = 0; i < MAX_CLIENTS; i++)
-						dbg_msg("network/server", "\t%d: %d", i, clients[i].state);
-				}
-			}
-		}
-		else
-		{
-			int cid = find_client(from);
-			if(cid >= 0)
-			{
-				if(clients[cid].conn.feed(p))
-				{
-					// packet is ok
-					unsigned msg = p->msg();
-
-					// client found, check state
-					if(((msg>>16)&0xff)&clients[cid].state)
-					{
-						// state is ok
-						client_process_packet(cid, p);
-					}
-					else
-					{
-						// invalid state, disconnect the client
-						drop(cid, "invalid message at this state");
-					}
-				}
-				else
-				{
-					drop(cid, "connection error");
-				}
-
-			}
-			else
-				dbg_msg("network/server", "packet from strange address.");
-		}
 	}
 
 	void client_timeout(int clientId)
@@ -615,67 +432,53 @@ public:
 
 	void pump_network()
 	{
+		net.update();
+		
+		// process packets
+		NETPACKET packet;
+		while(net.recv(&packet))
+		{
+			
+			if(packet.client_id == -1)
+			{
+				// stateless
+			}
+			else
+				process_client_packet(&packet);
+		}
+		
+		// check for removed clients
 		while(1)
 		{
-			packet p;
-			netaddr4 from;
-
-			//int bytes = net_udp4_recv(
-			int bytes = game_socket.recv(&from, p.data(), p.max_size());
-			//int bytes = game_socket.recv(&from, p.data(), p.max_size());
-			if(bytes <= 0)
+			int cid = net.delclient();
+			if(cid == -1)
 				break;
-
-			process_packet(&p, &from);
+			
+			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);
+			
+			dbg_msg("server", "del client %d", 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;
+			
+			dbg_msg("server", "new client %d", cid);
 		}
-		// TODO: check for client timeouts
-	}
-
-	char *write_int(char *buffer, int integer)
-	{
-		*buffer++ = integer >> 24;
-		*buffer++ = integer >> 16;
-		*buffer++ = integer >> 8;
-		*buffer++ = integer;
-
-		return buffer;
-	}
-
-	char *write_netaddr4(char *buffer, NETADDR4 *address)
-	{
-		*buffer++ = address->ip[0];
-		*buffer++ = address->ip[1];
-		*buffer++ = address->ip[2];
-		*buffer++ = address->ip[3];
-
-		return write_int(buffer, address->port);
-	}
-
-	void send_heartbeat(int version, netaddr4 *address, int players, int max_players, const char *name, const char *map_name)
-	{
-		char buffer[216] = {0};
-		char *d = buffer;
-
-		d = write_int(d, 'TWHB');
-		d = write_int(d, version);
-		d = write_netaddr4(d, address);
-		d = write_int(d,players);
-		d = write_int(d, max_players);
-
-		int len = strlen(name);
-		if (len > 128)
-			len = 128;
-
-		memcpy(d, name, len);
-		d += 128;
-
-		len = strlen(map_name);
-		if (len > 64)
-			len = 64;
-
-		memcpy(d, map_name, len);
-		d += 64;
-		game_socket.send(&master_server, buffer, sizeof(buffer));
 	}
 };
 
@@ -683,8 +486,14 @@ int main(int argc, char **argv)
 {
 	dbg_msg("server", "starting...");
 
+	dbg_msg("server", "%d %d", sizeof(snapshot), sizeof(snapshot::item));
+
+	config_reset();
+	config_load("server.cfg");
+
 	const char *mapname = "data/demo.map";
 	const char *servername = 0;
+	
 	// parse arguments
 	for(int i = 1; i < argc; i++)
 	{
@@ -705,6 +514,12 @@ int main(int argc, char **argv)
 			// -p (private server)
 			send_heartbeats = 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)
diff --git a/src/engine/snapshot.cpp b/src/engine/snapshot.cpp
new file mode 100644
index 00000000..2f6f1f36
--- /dev/null
+++ b/src/engine/snapshot.cpp
@@ -0,0 +1,235 @@
+
+#include "packet.h"
+#include "snapshot.h"
+
+struct snapshot_delta
+{
+	int num_deleted_items;
+	int num_update_items;
+	int num_temp_items; // needed?
+	int data[1];
+	
+	/*
+	char *data_start() { return (char *)&offsets[num_deleted_items+num_update_items+num_temp_items]; }
+	
+	int deleted_item(int index) { return offsets[index]; }
+	item *update_item(int index) { return (item *)(data_start() + offsets[num_deleted_items+index]); }
+	item *temp_item(int index) { return (item *)(data_start() + offsets[num_deleted_items+num_update_items+index]); }
+	* */
+};
+
+
+static const int MAX_ITEMS = 512;
+static snapshot_delta empty = {0,0,0,{0}};
+
+void *snapshot_empty_delta()
+{
+	return &empty;
+}
+
+static int diff_item(int *past, int *current, int *out, int size)
+{
+	/*
+	int needed = 0;
+	while(size)
+	{
+		*out = *current-*past;
+		if(*out)
+			needed = 1;
+		out++;
+		current++;
+		past++;
+		size--;
+	}*/
+
+	int needed = 0;
+	while(size)
+	{
+		*out = *current-*past;
+		if(*out)
+			needed = 1;
+		
+		out++;
+		past++;
+		current++;
+		size--;
+	}
+	
+	return needed;
+}
+
+// 1 = 4-3
+// d = n-p
+
+// n(4) = p(3)+d(1)
+
+static void undiff_item(int *past, int *diff, int *out, int size)
+{
+	while(size)
+	{
+		*out = *past+*diff;
+		out++;
+		past++;
+		diff++;
+		size--;
+	}
+}
+
+int snapshot_create_delta(snapshot *from, snapshot *to, void *dstdata)
+{
+	//int deleted[MAX_ITEMS];
+	//int update[MAX_ITEMS];
+	//int mark[MAX_ITEMS];
+	//char data[MAX_SNAPSHOT_SIZE];
+	
+	snapshot_delta *delta = (snapshot_delta *)dstdata;
+	int *data = (int *)delta->data;
+	
+	
+	delta->num_deleted_items = 0;
+	delta->num_update_items = 0;
+	delta->num_temp_items = 0;
+
+	// pack deleted stuff
+	for(int i = 0; i < from->num_items; i++)
+	{
+		snapshot::item *fromitem = from->get_item(i);
+		if(to->get_item_index(fromitem->key()) == -1)
+		{
+			// deleted
+			delta->num_deleted_items++;
+			*data = fromitem->key();
+			data++;
+		}
+	}
+	
+	// pack updated stuff
+	int count = 0, size_count = 0;
+	for(int i = 0; i < to->num_items; i++)
+	{
+		// do delta
+		int itemsize = to->get_item_datasize(i);
+		
+		snapshot::item *curitem = to->get_item(i);
+		int pastindex = from->get_item_index(curitem->key());
+		if(pastindex != -1)
+		{
+			snapshot::item *pastitem = from->get_item(pastindex);
+			if(diff_item((int*)pastitem->data(), (int*)curitem->data(), data+3, itemsize/4))
+			{
+				*data++ = itemsize;
+				*data++ = curitem->type();
+				*data++ = curitem->id();
+				//*data++ = curitem->key();
+				data += itemsize/4;
+				delta->num_update_items++;
+			}
+		}
+		else
+		{
+			*data++ = itemsize;
+			*data++ = curitem->type();
+			*data++ = curitem->id();
+			//*data++ = curitem->key();
+			
+			mem_copy(data, curitem->data(), itemsize);
+			size_count += itemsize;
+			data += itemsize/4;
+			delta->num_update_items++;
+			count++;
+		}
+	}
+	
+	if(0)
+	{
+		dbg_msg("snapshot", "%d %d %d",
+			delta->num_deleted_items,
+			delta->num_update_items,
+			delta->num_temp_items);
+	}
+
+	// TODO: pack temp stuff
+	
+	// finish
+	//mem_copy(delta->offsets, deleted, delta->num_deleted_items*sizeof(int));
+	//mem_copy(&(delta->offsets[delta->num_deleted_items]), update, delta->num_update_items*sizeof(int));
+	//mem_copy(&(delta->offsets[delta->num_deleted_items+delta->num_update_items]), temp, delta->num_temp_items*sizeof(int));
+	//mem_copy(delta->data_start(), data, data_size);
+	//delta->data_size = data_size;
+	
+	if(!delta->num_deleted_items && !delta->num_update_items && !delta->num_temp_items)
+		return 0;
+	
+	return (int)((char*)data-(char*)dstdata);
+}
+
+int snapshot_unpack_delta(snapshot *from, snapshot *to, void *srcdata, int data_size)
+{
+	snapshot_builder builder;
+	snapshot_delta *delta = (snapshot_delta *)srcdata;
+	int *data = (int *)delta->data;
+	
+	builder.start();
+	
+	// unpack deleted stuff
+	int *deleted = data;
+	data += delta->num_deleted_items;
+
+	// copy all non deleted stuff
+	for(int i = 0; i < from->num_items; i++)
+	{
+		//dbg_assert(0, "fail!");
+		snapshot::item *fromitem = from->get_item(i);
+		int itemsize = from->get_item_datasize(i);
+		int keep = 1;
+		for(int d = 0; d < delta->num_deleted_items; d++)
+		{
+			if(deleted[d] == fromitem->key())
+			{
+				keep = 0;
+				break;
+			}
+		}
+		
+		if(keep)
+		{
+			// keep it
+			int *newdata = (int *)(snapshot::item *)builder.new_item(fromitem->type(), fromitem->id(), itemsize);
+			mem_copy(newdata, fromitem->data(), itemsize);
+		}
+	}
+		
+	// unpack updated stuff
+	for(int i = 0; i < delta->num_update_items; i++)
+	{
+		int itemsize, id, type, key;
+		itemsize = *data++;
+		//key = *data++;
+		type = *data++;
+		id = *data++;
+		
+		key = (type<<16)|id;
+		
+		// create the item if needed
+		int *newdata = builder.get_item_data(key);
+		if(!newdata)
+			newdata = (int *)builder.new_item(key>>16, key&0xffff, itemsize);
+			
+		int fromindex = from->get_item_index(key);
+		if(fromindex != -1)
+		{
+			// we got an update so we need to apply the diff
+			int *pastdata = (int *)from->get_item(fromindex)->data();
+			undiff_item(pastdata, data, newdata, itemsize/4);
+		}
+		else // no previous, just copy the data
+			mem_copy(newdata, data, itemsize);
+			
+		data += itemsize/4;
+	}
+	
+	// TODO: unpack temp stuff
+	
+	// finish up
+	return builder.finish(to);
+}
diff --git a/src/engine/snapshot.h b/src/engine/snapshot.h
index 9d803486..9af94a3b 100644
--- a/src/engine/snapshot.h
+++ b/src/engine/snapshot.h
@@ -1,19 +1,47 @@
+#ifndef FILE_SNAPSHOT_H
+#define FILE_SNAPSHOT_H
 
 struct snapshot
 {
+	int data_size;
 	int num_items;
-	int offsets[1];
-
+	
 	struct item
 	{
 		int type_and_id;
-		char data[1];
-		
+
+		int *data() { return (int *)(this+1); }		
 		int type() { return type_and_id>>16; }
 		int id() { return type_and_id&(0xffff); }
+		int key() { return type_and_id; }
 	};
+
+	int *offsets() { return (int *)(this+1); }		
+	char *data_start() { return (char *)(offsets() + num_items); }
+	item *get_item(int index) { return (item *)(data_start() + offsets()[index]); };
 	
-	char *data_start() { return (char *)&offsets[num_items]; }
-	item *get_item(int index) { return (item *)(data_start() + offsets[index]); };
+	// returns the number of ints in the item data
+	int get_item_datasize(int index)
+	{
+		if(index == num_items-1)
+			return (data_size - offsets()[index]) - sizeof(item);
+		return (offsets()[index+1] - offsets()[index]) - sizeof(item);
+	}
+	
+	int get_item_index(int key)
+	{
+		// TODO: this should not be a linear search. very bad
+		for(int i = 0; i < num_items; i++)
+		{
+			if(get_item(i)->key() == key)
+				return i;
+		}
+		return -1;
+	}
 };
 
+void *snapshot_empty_delta();
+int snapshot_create_delta(snapshot *from, snapshot *to, void *data);
+int snapshot_unpack_delta(snapshot *from, snapshot *to, void *data, int data_size);
+
+#endif // FILE_SNAPSHOT_H
diff --git a/src/engine/versions.h b/src/engine/versions.h
index f5fceb48..7b4fdc2a 100644
--- a/src/engine/versions.h
+++ b/src/engine/versions.h
@@ -1,3 +1,3 @@
 #define TEEWARS_NETVERSION 0xffffffff
 //#define TEEWARS_NETVERSION_STRING "dev v2"
-#define TEEWARS_VERSION "0.2.1-dev"
+#define TEEWARS_VERSION "0.2.0-dev"