about summary refs log tree commit diff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/engine/client/client.c200
-rw-r--r--src/engine/compression.c22
-rw-r--r--src/engine/config.c15
-rw-r--r--src/engine/datafile.c12
-rw-r--r--src/engine/datafile.h2
-rw-r--r--src/engine/interface.h10
-rw-r--r--src/engine/server/server.c216
-rw-r--r--src/engine/snapshot.c4
-rw-r--r--src/game/client/game_client.cpp169
-rw-r--r--src/game/game.h74
-rw-r--r--src/game/game_protocol.h37
-rw-r--r--src/game/game_variables.h3
-rw-r--r--src/game/server/game_server.cpp437
-rw-r--r--src/game/server/game_server.h29
-rw-r--r--src/tools/crapnet.cpp12
-rw-r--r--src/tools/map_resave.c35
16 files changed, 669 insertions, 608 deletions
diff --git a/src/engine/client/client.c b/src/engine/client/client.c
index 72f73741..d07c3aca 100644
--- a/src/engine/client/client.c
+++ b/src/engine/client/client.c
@@ -18,12 +18,26 @@
 
 #include <mastersrv/mastersrv.h>
 
+/*
+	Server Time
+	Client Mirror Time
+	Client Predicted Time
+	
+	Snapshot Latency
+		Downstream latency
+	
+	Prediction Latency
+		Upstream latency
+*/
 static int info_request_begin;
 static int info_request_end;
 static int snapshot_part;
 static int64 local_start_time;
 static int64 game_start_time;
-static float latency = 0;
+
+static float snapshot_latency = 0;
+static float prediction_latency = 0;
+
 static int extra_polating = 0;
 static int debug_font;
 static float frametime = 0.0001f;
@@ -34,9 +48,25 @@ static int window_must_refocus = 0;
 static int snaploss = 0;
 static int snapcrcerrors = 0;
 
+static int current_recv_tick = 0;
+
+// current time
 static int current_tick = 0;
 static float intratick = 0;
 
+// predicted time
+static int current_predtick = 0;
+static float intrapredtick = 0;
+
+static struct // TODO: handle input better
+{
+	int data[MAX_INPUT_SIZE]; // the input data
+	int tick; // the tick that the input is for
+	float latency; // prediction latency when we sent this input
+} inputs[200];
+static int current_input = 0;
+
+
 // --- input snapping ---
 static int input_data[MAX_INPUT_SIZE] = {0};
 static int input_data_size;
@@ -100,32 +130,13 @@ static void snap_init()
 }
 
 // ------ time functions ------
-float client_intratick()
-{
-	return intratick;
-}
-
-int client_tick()
-{
-	return current_tick;
-}
-
-int client_tickspeed()
-{
-	return SERVER_TICK_SPEED;
-}
-
-float client_frametime()
-{
-	return frametime;
-}
-
-float client_localtime()
-{
-	return (time_get()-local_start_time)/(float)(time_freq());
-}
-
-int menu_loop(); // TODO: what is this?
+float client_intratick() { return intratick; }
+float client_intrapredtick() { return intrapredtick; }
+int client_tick() { return current_tick; }
+int client_predtick() { return current_predtick; }
+int client_tickspeed() { return SERVER_TICK_SPEED; }
+float client_frametime() { return frametime; }
+float client_localtime() { return (time_get()-local_start_time)/(float)(time_freq()); }
 
 // ----- send functions -----
 int client_send_msg()
@@ -176,19 +187,46 @@ static void client_send_error(const char *error)
 	//send_packet(&p);
 	//send_packet(&p);
 	*/
-}	
+}
 
 static void client_send_input()
 {
 	msg_pack_start_system(NETMSG_INPUT, 0);
+	msg_pack_int(current_predtick);
 	msg_pack_int(input_data_size);
+	
+	inputs[current_input].tick = current_predtick;
+	inputs[current_input].latency = prediction_latency;
+	
 	int i;
 	for(i = 0; i < input_data_size/4; i++)
+	{
+		inputs[current_input].data[i] = input_data[i];
 		msg_pack_int(input_data[i]);
+	}
+	
+	current_input++;
+	current_input%=200;
+	
 	msg_pack_end();
 	client_send_msg();
 }
 
+/* TODO: OPT: do this alot smarter! */
+int *client_get_input(int tick)
+{
+	int i;
+	int best = -1;
+	for(i = 0; i < 200; i++)
+	{
+		if(inputs[i].tick <= tick && (best == -1 || inputs[best].tick < inputs[i].tick))
+			best = i;
+	}
+	
+	if(best != -1)
+		return (int *)inputs[best].data;
+	return 0;
+}
 
 // ------ server browse ----
 static struct 
@@ -337,7 +375,17 @@ void client_connect(const char *server_address_str)
 	
 	netclient_connect(net, &server_address);
 	client_set_state(CLIENTSTATE_CONNECTING);	
-	current_tick = 0;
+	
+	current_recv_tick = 0;
+	
+	// reset input
+	int i;
+	for(i = 0; i < 200; i++)
+		inputs[i].tick = -1;
+	current_input = 0;
+	
+	snapshot_latency = 0;
+	prediction_latency = 0;
 }
 
 void client_disconnect()
@@ -375,11 +423,12 @@ 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 snaploss: %d latency: %4.0f %c  mem %dk   gfxmem: %dk  fps: %3d",
+	sprintf(buffer, "send: %6d recv: %6d snaploss: %d snaplatency: %4.2f %c  predlatency: %4.2f  mem %dk   gfxmem: %dk  fps: %3d",
 		(current.send_bytes-prev.send_bytes)*10,
 		(current.recv_bytes-prev.recv_bytes)*10,
 		snaploss,
-		latency*1000.0f, extra_polating?'E':' ',
+		snapshot_latency*1000.0f, extra_polating?'E':' ',
+		prediction_latency*1000.0f,
 		mem_allocated()/1024,
 		gfx_memory_usage()/1024,
 		(int)(1.0f/frametime_avg));
@@ -526,10 +575,12 @@ static void client_process_packet(NETPACKET *packet)
 				//dbg_msg("client/network", "got snapshot");
 				int game_tick = msg_unpack_int();
 				int delta_tick = game_tick-msg_unpack_int();
+				int input_predtick = msg_unpack_int();
+				int time_left = msg_unpack_int();
 				int num_parts = 1;
 				int part = 0;
 				int part_size = 0;
-				int crc;
+				int crc = 0;
 				
 				if(msg != NETMSG_SNAPEMPTY)
 				{
@@ -537,7 +588,25 @@ static void client_process_packet(NETPACKET *packet)
 					part_size = msg_unpack_int();
 				}
 				
-				if(snapshot_part == part && game_tick > current_tick)
+				/* TODO: adjust our prediction time */
+				if(time_left)
+				{
+					int k;
+					for(k = 0; k < 200; k++) /* TODO: do this better */
+					{
+						if(inputs[k].tick == input_predtick)
+						{
+							float wanted_latency = inputs[k].latency - time_left/1000.0f + 0.01f;
+							prediction_latency = prediction_latency*0.95f + wanted_latency*0.05f;
+							//dbg_msg("DEBUG", "predlatency=%f", prediction_latency);
+							break;
+						}
+					}
+				}
+				
+				//dbg_msg("DEBUG", "new predlatency=%f", prediction_latency);
+				
+				if(snapshot_part == part && game_tick > current_recv_tick)
 				{
 					// TODO: clean this up abit
 					const char *d = (const char *)msg_unpack_raw(part_size);
@@ -568,6 +637,7 @@ static void client_process_packet(NETPACKET *packet)
 									dbg_msg("client", "error, couldn't find the delta snapshot");
 								
 								// ack snapshot
+								// TODO: combine this with the input message
 								msg_pack_start_system(NETMSG_SNAPACK, 0);
 								msg_pack_int(-1);
 								msg_pack_end();
@@ -637,9 +707,9 @@ static void client_process_packet(NETPACKET *packet)
 						recived_snapshots++;
 						
 
-						if(current_tick > 0)
-							snaploss += game_tick-current_tick-1;
-						current_tick = game_tick;
+						if(current_recv_tick > 0)
+							snaploss += game_tick-current_recv_tick-1;
+						current_recv_tick = game_tick;
 						
 						// we got two snapshots until we see us self as connected
 						if(recived_snapshots == 2)
@@ -661,7 +731,7 @@ static void client_process_packet(NETPACKET *packet)
 						
 						int64 wanted = game_start_time+(game_tick*time_freq())/50;
 						float current_latency = (now-wanted)/(float)time_freq();
-						latency = latency*0.95f+current_latency*0.05f;
+						snapshot_latency = snapshot_latency*0.95f+current_latency*0.05f;
 						
 						// ack snapshot
 						msg_pack_start_system(NETMSG_SNAPACK, 0);
@@ -693,7 +763,6 @@ static void client_pump_network()
 	// check for errors		
 	if(client_state() != CLIENTSTATE_OFFLINE && netclient_state(net) == NETSTATE_OFFLINE)
 	{
-		// TODO: add message to the user there
 		client_set_state(CLIENTSTATE_OFFLINE);
 		dbg_msg("client", "offline error='%s'", netclient_error_string(net));
 	}
@@ -754,9 +823,6 @@ static void client_run(const char *direct_connect_server)
 	if(direct_connect_server)
 		client_connect(direct_connect_server);
 		
-	int64 game_starttime = time_get();
-	int64 last_input = game_starttime;
-	
 	int64 reporttime = time_get();
 	int64 reportinterval = time_freq()*1;
 	int frames = 0;
@@ -771,14 +837,15 @@ static void client_run(const char *direct_connect_server)
 		// switch snapshot
 		if(recived_snapshots >= 3)
 		{
+			int repredict = 0;
 			int64 now = time_get();
 			while(1)
 			{
 				SNAPSTORAGE_HOLDER *cur = snapshots[SNAP_CURRENT];
 				int64 tickstart = game_start_time + (cur->tick+1)*time_freq()/50;
 				int64 t = tickstart;
-				if(latency > 0)
-					t += (int64)(time_freq()*(latency*1.1f));
+				if(snapshot_latency > 0)
+					t += (int64)(time_freq()*(snapshot_latency*1.1f));
 
 				if(t < now)
 				{
@@ -787,8 +854,15 @@ static void client_run(const char *direct_connect_server)
 					{
 						snapshots[SNAP_PREV] = snapshots[SNAP_CURRENT];
 						snapshots[SNAP_CURRENT] = next;
+						
+						// set tick
+						current_tick = snapshots[SNAP_CURRENT]->tick;
+						
 						if(snapshots[SNAP_CURRENT] && snapshots[SNAP_PREV])
+						{
 							modc_newsnapshot();
+							repredict = 1;
+						}
 					}
 					else
 					{
@@ -806,31 +880,33 @@ static void client_run(const char *direct_connect_server)
 			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));
+				if(snapshot_latency > 0)
+					curtick_start += (int64)(time_freq()*(snapshot_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));
+				if(snapshot_latency > 0)
+					prevtick_start += (int64)(time_freq()*(snapshot_latency*1.1f));
 					
 				intratick = (now - prevtick_start) / (float)(curtick_start-prevtick_start);
-			}
-		}
 
-		// send input
-		if(client_state() == CLIENTSTATE_ONLINE)
-		{
-			if(config.stress&1 && client_localtime() > 10.0f)
-				client_disconnect();
-			
-			if(input_is_changed || time_get() > last_input+time_freq())
-			{
-				client_send_input();
-				input_is_changed = 0;
-				last_input = time_get();
+				// 25 frames ahead
+				int new_predtick = current_tick+prediction_latency*SERVER_TICK_SPEED;
+				if(new_predtick > current_predtick)
+				{
+					//dbg_msg("")
+					current_predtick = new_predtick;
+					repredict = 1;
+					
+					// send input
+					client_send_input();
+				}
 			}
+			
+			if(repredict)
+				modc_predict();
 		}
-		
+
+		// STRESS TEST: join the server again
 		if(client_state() == CLIENTSTATE_OFFLINE && config.stress && (frames%100) == 0)
 			client_connect(config.cl_stress_server);
 		
@@ -838,8 +914,6 @@ static void client_run(const char *direct_connect_server)
 		inp_update();
 		
 		// refocus
-		// TODO: fixme
-		
 		if(!gfx_window_active())
 		{
 			if(window_must_refocus == 0)
diff --git a/src/engine/compression.c b/src/engine/compression.c
index 2f3e37ca..e89cdadf 100644
--- a/src/engine/compression.c
+++ b/src/engine/compression.c
@@ -1,23 +1,23 @@
 #include "system.h"
 #include <string.h>
 
-// Format: ESDDDDDD EDDDDDDD EDD...  Extended, Data, Sign
+/* 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>>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
+	*dst |= i&0x3F; /* pack 6bit into dst */
+	i >>= 6; /* discard 6 bits */
 	if(i)
 	{
-		*dst |= 0x80; // set extend bit
+		*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)
+			*dst = i&(0x7F); /* pack 7bit */
+			i >>= 7; /* discard 7 bits */
+			*dst |= (i!=0)<<7; /* set extend bit (may branch) */
 			if(!i)
 				break;
 		}
@@ -52,7 +52,7 @@ const unsigned char *vint_unpack(const unsigned char *src, int *i)
 	} while(0);
 
 	src++;
-	*i ^= -sign; // if(sign) *i = ~(*i)
+	*i ^= -sign; /* if(sign) *i = ~(*i) */
 	return src; 
 } 
 
@@ -85,7 +85,7 @@ long intpack_compress(const void *src_, int size, void *dst_)
 }
 
 
-//
+/* */
 long zerobit_compress(const void *src_, int size, void *dst_)
 {
 	unsigned char *src = (unsigned char *)src_;
diff --git a/src/engine/config.c b/src/engine/config.c
index 59c07f3e..ba46d073 100644
--- a/src/engine/config.c
+++ b/src/engine/config.c
@@ -7,7 +7,7 @@
 #include "config.h"
 
 
-// buffered stream for reading lines, should perhaps be something smaller
+/* buffered stream for reading lines, should perhaps be something smaller */
 typedef struct
 {
 	char buffer[4*1024];
@@ -33,9 +33,9 @@ char *linereader_get(LINEREADER *lr)
 	{
 		if(lr->buffer_pos >= lr->buffer_size)
 		{
-			// fetch more
+			/* fetch more */
 
-			// move the remaining part to the front
+			/* move the remaining part to the front */
 			unsigned left = lr->buffer_size - line_start;
 			if(line_start > lr->buffer_size)
 				left = 0;
@@ -43,7 +43,7 @@ char *linereader_get(LINEREADER *lr)
 				mem_move(lr->buffer, &lr->buffer[line_start], left);
 			lr->buffer_pos = left;
 
-			// fill the buffer
+			/* fill the buffer */
 			unsigned read = io_read(lr->io, &lr->buffer[lr->buffer_pos], lr->buffer_max_size-lr->buffer_pos);
 			lr->buffer_size = left + read;
 			line_start = 0;
@@ -52,20 +52,20 @@ char *linereader_get(LINEREADER *lr)
 			{
 				if(left)
 				{
-					lr->buffer[left] = 0; // return the last line
+					lr->buffer[left] = 0; /* return the last line */
 					lr->buffer_pos = left;
 					lr->buffer_size = left;
 					return lr->buffer;
 				}
 				else
-					return 0x0; // we are done!
+					return 0x0; /* we are done! */
 			}
 		}
 		else
 		{
 			if(lr->buffer[lr->buffer_pos] == '\n' || lr->buffer[lr->buffer_pos] == '\r')
 			{
-				// line found
+				/* line found */
 				lr->buffer[lr->buffer_pos] = 0;
 				lr->buffer_pos++;
 				return &lr->buffer[line_start];
@@ -176,7 +176,6 @@ void config_save(const char *filename)
 
 	dbg_msg("config/save", "saving config to %s", filename);
 
-	//file_stream file;
 	IOHANDLE file = io_open(filename, IOFLAG_WRITE);
 
 	if(file)
diff --git a/src/engine/datafile.c b/src/engine/datafile.c
index ce835b46..fddd522d 100644
--- a/src/engine/datafile.c
+++ b/src/engine/datafile.c
@@ -100,9 +100,6 @@ DATAFILE *datafile_load(const char *filename)
 		return 0;
 	}
 	
-	//if(DEBUG)
-		//dbg_msg("datafile", "loading. size=%d", datafile.size);
-	
 	/* read in the rest except the data */
 	int size = 0;
 	size += header.num_item_types*sizeof(DATAFILE_ITEM_TYPE);
@@ -112,8 +109,8 @@ DATAFILE *datafile_load(const char *filename)
 	size += header.item_size;
 	
 	int allocsize = size;
-	allocsize += sizeof(DATAFILE); // add space for info structure
-	allocsize += header.num_raw_data*sizeof(void*); // add space for data pointers
+	allocsize += sizeof(DATAFILE); /* add space for info structure */
+	allocsize += header.num_raw_data*sizeof(void*); /* add space for data pointers */
 
 	df = (DATAFILE*)mem_alloc(allocsize, 1);
 	df->header = header;
@@ -207,9 +204,6 @@ int datafile_num_data(DATAFILE *df)
 /* always returns the size in the file */
 int datafile_get_datasize(DATAFILE *df, int index)
 {
-	//if(df->header.version == 4)
-	//	return df->info.data_sizes[index];
-	
 	if(index == df->header.num_raw_data-1)
 		return df->header.data_size-df->info.data_offsets[index];
 	return  df->info.data_offsets[index+1]-df->info.data_offsets[index];
@@ -217,7 +211,7 @@ int datafile_get_datasize(DATAFILE *df, int index)
 
 void *datafile_get_data(DATAFILE *df, int index)
 {
-	// load it if needed
+	/* load it if needed */
 	if(!df->data_ptrs[index])
 	{
 		/* fetch the data size */
diff --git a/src/engine/datafile.h b/src/engine/datafile.h
index e0f5380e..e83aa3c5 100644
--- a/src/engine/datafile.h
+++ b/src/engine/datafile.h
@@ -5,7 +5,7 @@ typedef struct DATAFILE_t DATAFILE;
 /* read access */
 DATAFILE *datafile_load(const char *filename);
 DATAFILE *datafile_load_old(const char *filename);
-void *datafile_get_data(DATAFILE *df, int index); // automaticly load the data for the item
+void *datafile_get_data(DATAFILE *df, int index);
 int datafile_get_datasize(DATAFILE *df, int index);
 void datafile_unload_data(DATAFILE *df, int index);
 void *datafile_get_item(DATAFILE *df, int index, int *type, int *id);
diff --git a/src/engine/interface.h b/src/engine/interface.h
index a00fb81d..e1608cff 100644
--- a/src/engine/interface.h
+++ b/src/engine/interface.h
@@ -706,15 +706,13 @@ int modmenu_render(int ingame);
 
 /* undocumented callbacks */
 void modc_message(int msg);
+void modc_predict();
 void mods_message(int msg, int client_id);
 
+
 const char *modc_net_version();
 const char *mods_net_version();
 
-// unused
-// const char *modc_version();
-// const char *mods_version();
-
 /* server */
 int server_getclientinfo(int client_id, CLIENT_INFO *info);
 int server_tick();
@@ -769,14 +767,16 @@ int client_send_msg();
 
 /* client */
 int client_tick();
+int client_predtick();
 float client_intratick();
+float client_intrapredtick();
 int client_tickspeed();
 float client_frametime();
 float client_localtime();
 
 int client_state();
 const char *client_error_string();
-
+int *client_get_input(int tick);
 
 void client_connect(const char *address);
 void client_disconnect();
diff --git a/src/engine/server/server.c b/src/engine/server/server.c
index ddc51c1d..edb3f644 100644
--- a/src/engine/server/server.c
+++ b/src/engine/server/server.c
@@ -20,7 +20,9 @@
 
 static SNAPBUILD builder;
 
-static int64 lasttick;
+static int64 game_start_time;
+static int current_tick = 0;
+
 static int64 lastheartbeat;
 static NETADDR4 master_server;
 
@@ -36,11 +38,11 @@ void *snap_new_item(int type, int id, int size)
 typedef struct
 {
 	short next;
-	short state; // 0 = free, 1 = alloced, 2 = timed
+	short state; /* 0 = free, 1 = alloced, 2 = timed */
 	int timeout_tick;
 } SNAP_ID;
 
-static const int MAX_IDS = 8*1024; // should be lowered
+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;
@@ -49,6 +51,42 @@ static int snap_id_usage;
 static int snap_id_inusage;
 static int snap_id_inited = 0;
 
+
+enum
+{
+	SRVCLIENT_STATE_EMPTY = 0,
+	SRVCLIENT_STATE_CONNECTING = 1,
+	SRVCLIENT_STATE_INGAME = 2,
+};
+
+typedef struct 
+{
+	int data[MAX_INPUT_SIZE];
+	int pred_tick; /* tick that the client predicted for the input */
+	int game_tick; /* the tick that was chosen for the input */
+	int64 timeleft; /* how much time in ms there were left before this should be applied */
+} CLIENT_INPUT;
+	
+/* */
+typedef struct
+{
+	/* connection state info */
+	int state;
+	int latency;
+	
+	int last_acked_snapshot;
+	SNAPSTORAGE snapshots;
+	
+	CLIENT_INPUT inputs[200]; /* TODO: handle input better */
+	int current_input;
+	
+	char name[MAX_NAME_LENGTH];
+	char clan[MAX_CLANNAME_LENGTH];
+} CLIENT;
+
+static CLIENT clients[MAX_CLIENTS];
+static NETSERVER *net;
+
 static void snap_init_id()
 {
 	int i;
@@ -72,17 +110,17 @@ int snap_new_id()
 {
 	dbg_assert(snap_id_inited == 1, "requesting id too soon");
 	
-	// process timed ids
+	/* 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
+		/* 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
+		/* remove it from the timed list */
 		snap_first_timed_id = next_timed;
 		if(snap_first_timed_id == -1)
 			snap_last_timed_id = -1;
@@ -120,36 +158,16 @@ void snap_free_id(int id)
 	}
 }
 
-enum
-{
-	SRVCLIENT_STATE_EMPTY = 0,
-	SRVCLIENT_STATE_CONNECTING = 1,
-	SRVCLIENT_STATE_INGAME = 2,
-};
-
-//
-typedef struct
-{
-	// connection state info
-	int state;
-	int latency;
-	
-	int last_acked_snapshot;
-	SNAPSTORAGE snapshots;
-
-	char name[MAX_NAME_LENGTH];
-	char clan[MAX_CLANNAME_LENGTH];
-} CLIENT;
-
-static CLIENT clients[MAX_CLIENTS];
-static int current_tick = 0;
-static NETSERVER *net;
-
 int server_tick()
 {
 	return current_tick;
 }
 
+int64 server_tick_start_time(int tick)
+{
+	return game_start_time + (time_freq()*tick)/SERVER_TICK_SPEED;
+}
+
 int server_tickspeed()
 {
 	return SERVER_TICK_SPEED;
@@ -201,7 +219,7 @@ int server_send_msg(int client_id)
 			
 	if(client_id == -1)
 	{
-		// broadcast
+		/* broadcast */
 		int i;
 		for(i = 0; i < MAX_CLIENTS; i++)
 			if(clients[i].state == SRVCLIENT_STATE_INGAME)
@@ -224,10 +242,10 @@ static void server_do_tick()
 
 static void server_do_snap()
 {
+	int i, k;
 	mods_presnap();
 
-	int i;
-	for( i = 0; i < MAX_CLIENTS; i++)
+	for(i = 0; i < MAX_CLIENTS; i++)
 	{
 		if(clients[i].state == SRVCLIENT_STATE_INGAME)
 		{
@@ -237,18 +255,18 @@ static void server_do_snap()
 			snapbuild_init(&builder);
 			mods_snap(i);
 
-			// finish snapshot
+			/* finish snapshot */
 			int snapshot_size = snapbuild_finish(&builder, data);
 			int crc = snapshot_crc((SNAPSHOT*)data);
 
-			// remove old snapshos
-			// keep 1 seconds worth of snapshots
+			/* remove old snapshos */
+			/* keep 1 seconds worth of snapshots */
 			snapstorage_purge_until(&clients[i].snapshots, current_tick-SERVER_TICK_SPEED);
 			
-			// save it the snapshot
+			/* save it the snapshot */
 			snapstorage_add(&clients[i].snapshots, current_tick, time_get(), snapshot_size, data);
 			
-			// find snapshot that we can preform delta against
+			/* find snapshot that we can preform delta against */
 			static SNAPSHOT emptysnap;
 			emptysnap.data_size = 0;
 			emptysnap.num_items = 0;
@@ -262,12 +280,24 @@ static void server_do_snap()
 					delta_tick = clients[i].last_acked_snapshot;
 			}
 			
-			// create delta
+			int input_predtick = -1;
+			int64 timeleft = 0;
+			for(k = 0; k < 200; k++) // TODO: do this better
+			{
+				if(clients[i].inputs[k].game_tick == current_tick)
+				{
+					timeleft = clients[i].inputs[k].timeleft;
+					input_predtick = clients[i].inputs[k].pred_tick;
+					break;
+				}
+			}
+			
+			/* create delta */
 			int deltasize = snapshot_create_delta(deltashot, (SNAPSHOT*)data, deltadata);
 			
 			if(deltasize)
 			{
-				// compress it
+				/* compress it */
 				unsigned char intdata[MAX_SNAPSHOT_SIZE];
 				int intsize = intpack_compress(deltadata, deltasize, intdata);
 				
@@ -288,7 +318,9 @@ static void server_do_snap()
 
 					msg_pack_start_system(NETMSG_SNAP, 0);
 					msg_pack_int(current_tick);
-					msg_pack_int(current_tick-delta_tick); // compressed with
+					msg_pack_int(current_tick-delta_tick); /* compressed with */
+					msg_pack_int(input_predtick);
+					msg_pack_int((timeleft*1000)/time_freq());
 					msg_pack_int(crc);
 					msg_pack_int(chunk);
 					msg_pack_raw(&compdata[n*max_size], chunk);
@@ -300,7 +332,9 @@ static void server_do_snap()
 			{
 				msg_pack_start_system(NETMSG_SNAPEMPTY, 0);
 				msg_pack_int(current_tick);
-				msg_pack_int(current_tick-delta_tick); // compressed with
+				msg_pack_int(current_tick-delta_tick); /* compressed with */
+				msg_pack_int(input_predtick);
+				msg_pack_int((timeleft*1000)/time_freq());
 				msg_pack_end();
 				server_send_msg(i);
 			}
@@ -313,9 +347,19 @@ static void server_do_snap()
 
 static int new_client_callback(int cid, void *user)
 {
+	int i;
 	clients[cid].state = SRVCLIENT_STATE_CONNECTING;
 	clients[cid].name[0] = 0;
 	clients[cid].clan[0] = 0;
+	
+	/* reset input */
+	for(i = 0; i < 200; i++)
+	{
+		clients[cid].inputs[i].game_tick = -1;
+		clients[cid].inputs[i].pred_tick = -1;
+	}
+	clients[cid].current_input = 0;
+	
 	snapstorage_purge_all(&clients[cid].snapshots);
 	clients[cid].last_acked_snapshot = -1;
 	return 0;
@@ -359,14 +403,14 @@ static void server_process_client_packet(NETPACKET *packet)
 	int msg = msg_unpack_start(packet->data, packet->data_size, &sys);
 	if(sys)
 	{
-		// system message
+		/* system message */
 		if(msg == NETMSG_INFO)
 		{
 			char version[64];
 			strncpy(version, msg_unpack_string(), 64);
 			if(strcmp(version, mods_net_version()) != 0)
 			{
-				// OH FUCK! wrong version, drop him
+				/* OH FUCK! wrong version, drop him */
 				char reason[256];
 				sprintf(reason, "wrong version. server is running %s.", mods_net_version());
 				netserver_drop(net, cid, reason);
@@ -377,7 +421,7 @@ static void server_process_client_packet(NETPACKET *packet)
 			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)password; /* ignore these variables */
 			(void)skin;
 			server_send_map(cid);
 		}
@@ -392,12 +436,29 @@ static void server_process_client_packet(NETPACKET *packet)
 		}
 		else if(msg == NETMSG_INPUT)
 		{
-			int input[MAX_INPUT_SIZE];
+			int tick = msg_unpack_int();
 			int size = msg_unpack_int();
 			int i;
+			
+			CLIENT_INPUT *input = &clients[cid].inputs[clients[cid].current_input];
+			input->timeleft = server_tick_start_time(tick)-time_get();
+			input->pred_tick = tick;
+			
+			if(tick < server_tick())
+			{
+				/* TODO: how should we handle this */
+				dbg_msg("server", "input got in late for=%d cur=%d", tick, server_tick());
+				tick = server_tick()+1;
+			}
+
+			input->game_tick = tick;
+			
 			for(i = 0; i < size/4; i++)
-				input[i] = msg_unpack_int();
-			mods_client_input(cid, input);
+				input->data[i] = msg_unpack_int();
+				
+			//time_get()
+			clients[cid].current_input++;
+			clients[cid].current_input %= 200;
 		}
 		else if(msg == NETMSG_SNAPACK)
 		{
@@ -413,7 +474,7 @@ static void server_process_client_packet(NETPACKET *packet)
 	}
 	else
 	{
-		// game message
+		/* game message */
 		mods_message(msg, cid);
 	}
 }
@@ -427,7 +488,7 @@ static void server_send_serverinfo(NETADDR4 *addr)
 	packer_add_raw(&p, SERVERBROWSE_INFO, sizeof(SERVERBROWSE_INFO));
 	packer_add_string(&p, config.sv_name, 128);
 	packer_add_string(&p, config.sv_map, 128);
-	packer_add_int(&p, netserver_max_clients(net)); // max_players
+	packer_add_int(&p, netserver_max_clients(net)); /* max_players */
 	int c = 0;
 	int i;
 	for(i = 0; i < MAX_CLIENTS; i++)
@@ -435,7 +496,7 @@ static void server_send_serverinfo(NETADDR4 *addr)
 		if(!clients[i].state != SRVCLIENT_STATE_EMPTY)
 			c++;
 	}
-	packer_add_int(&p, c); // num_players
+	packer_add_int(&p, c); /* num_players */
 	
 	packet.client_id = -1;
 	packet.address = *addr;
@@ -461,13 +522,13 @@ static void server_pump_network()
 {
 	netserver_update(net);
 	
-	// process packets
+	/* process packets */
 	NETPACKET packet;
 	while(netserver_recv(net, &packet))
 	{
 		if(packet.client_id == -1)
 		{
-			// stateless
+			/* stateless */
 			if(packet.data_size == sizeof(SERVERBROWSE_GETINFO) &&
 				memcmp(packet.data, SERVERBROWSE_GETINFO, sizeof(SERVERBROWSE_GETINFO)) == 0)
 			{
@@ -501,23 +562,23 @@ static int server_run()
 {
 	biggest_snapshot = 0;
 
-	net_init(); // For Windows compatibility.
+	net_init(); /* For Windows compatibility. */
 	
 	snap_init_id();
 
-	// load map
+	/* load map */
 	if(!map_load(config.sv_map))
 	{
 		dbg_msg("server", "failed to load map. mapname='%s'", config.sv_map);
 		return -1;
 	}
 	
-	// start server
+	/* start server */
 	NETADDR4 bindaddr;
 
 	if(strlen(config.sv_bindaddr) && net_host_lookup(config.sv_bindaddr, config.sv_port, &bindaddr) != 0)
 	{
-		// sweet!
+		/* sweet! */
 	}
 	else
 	{
@@ -536,19 +597,16 @@ static int server_run()
 	
 	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)
+	if(net_host_lookup(config.masterserver, MASTERSERVER_PORT, &master_server) != 0)
 	{
-		// TODO: fix me
-		//master_server = netaddr4(0, 0, 0, 0, 0);
+		/* TODO: fix me */
+		/*master_server = netaddr4(0, 0, 0, 0, 0); */
 	}
 
 	mods_init();
 	dbg_msg("server", "version %s", mods_net_version());
 
-	int64 time_per_tick = time_freq()/SERVER_TICK_SPEED;
 	int64 time_per_heartbeat = time_freq() * 30;
-	int64 starttime = time_get();
-	lasttick = starttime;
 	lastheartbeat = 0;
 
 	int64 reporttime = time_get();
@@ -558,28 +616,48 @@ static int server_run()
 	int64 snaptime = 0;
 	int64 networktime = 0;
 	int64 totaltime = 0;
+	
+	game_start_time = time_get();
 
 	if(config.debug)
-	dbg_msg("server", "baseline memory usage %dk", mem_allocated()/1024);
+		dbg_msg("server", "baseline memory usage %dk", mem_allocated()/1024);
 
 	while(1)
 	{
 		int64 t = time_get();
-		if(t-lasttick > time_per_tick)
+		if(t > server_tick_start_time(current_tick+1))
 		{
+			/* apply new input */
+			{
+				int c, i;
+				for(c = 0; c < MAX_CLIENTS; c++)
+				{
+					if(clients[c].state == SRVCLIENT_STATE_EMPTY)
+						continue;
+					for(i = 0; i < 200; i++)
+					{
+						if(clients[c].inputs[i].game_tick == server_tick())
+						{
+							mods_client_input(c, clients[c].inputs[i].data);
+							break;
+						}
+					}
+				}
+			}
+			
+			/* progress game */
 			{
 				int64 start = time_get();
 				server_do_tick();
 				simulationtime += time_get()-start;
 			}
 
+			/* snap game */
 			{
 				int64 start = time_get();
 				server_do_snap();
 				snaptime += time_get()-start;
 			}
-
-			lasttick += time_per_tick;
 		}
 
 		if(config.sv_sendheartbeats)
@@ -652,7 +730,7 @@ int main(int argc, char **argv)
 
 	config_load(config_filename);
 
-	// parse arguments
+	/* parse arguments */
 	for(i = 1; i < argc; i++)
 		config_set(argv[i]);
 
diff --git a/src/engine/snapshot.c b/src/engine/snapshot.c
index b9c832a2..b42b0157 100644
--- a/src/engine/snapshot.c
+++ b/src/engine/snapshot.c
@@ -21,7 +21,7 @@ int snapshot_get_item_datasize(SNAPSHOT *snap, int index)
 
 int snapshot_get_item_index(SNAPSHOT *snap, int key)
 {
-    /* TODO: this should not be a linear search. very bad */
+    /* TODO: OPT: this should not be a linear search. very bad */
     int i;
     for(i = 0; i < snap->num_items; i++)
     {
@@ -135,6 +135,8 @@ static void undiff_item(int *past, int *diff, int *out, int size)
 	}
 }
 
+
+// TODO: OPT: this should be made much faster
 int snapshot_create_delta(SNAPSHOT *from, SNAPSHOT *to, void *dstdata)
 {
 	SNAPSHOT_DELTA *delta = (SNAPSHOT_DELTA *)dstdata;
diff --git a/src/game/client/game_client.cpp b/src/game/client/game_client.cpp
index 87fe767c..cc91c923 100644
--- a/src/game/client/game_client.cpp
+++ b/src/game/client/game_client.cpp
@@ -39,6 +39,7 @@ struct client_data
 	int team;
 	int emoticon;
 	int emoticon_start;
+	player_core predicted;
 } client_datas[MAX_CLIENTS];
 
 inline float frandom() { return rand()/(float)(RAND_MAX); }
@@ -137,6 +138,7 @@ void draw_sprite(float x, float y, float size)
 	gfx_quads_draw(x, y, size*sprite_w_scale, size*sprite_h_scale);
 }
 
+/*
 void move_point(vec2 *inout_pos, vec2 *inout_vel, float elasticity)
 {
 	vec2 pos = *inout_pos;
@@ -166,7 +168,7 @@ void move_point(vec2 *inout_pos, vec2 *inout_vel, float elasticity)
 	{
 		*inout_pos = pos + vel;
 	}
-}
+}*/
 
 class damage_indicators
 {
@@ -309,7 +311,7 @@ public:
 			particles[i].vel.y += particles[i].gravity*time_passed;
 			particles[i].vel *= particles[i].friction;
 			vec2 vel = particles[i].vel*time_passed;
-			move_point(&particles[i].pos, &vel, 0.1f+0.9f*frandom());
+			move_point(&particles[i].pos, &vel, 0.1f+0.9f*frandom(), NULL);
 			particles[i].vel = vel* (1.0f/time_passed);
 			particles[i].life += time_passed;
 			particles[i].rot += time_passed * particles[i].rotspeed;
@@ -681,6 +683,75 @@ static void process_events(int s)
 	must_process_events = false;
 }
 
+static player_core predicted_prev_player;
+static player_core predicted_player;
+
+extern "C" void modc_predict()
+{
+	// repredict player
+	{
+		world_core world;
+		int local_cid = -1;
+		
+		// search for players
+		for(int i = 0; i < snap_num_items(SNAP_CURRENT); i++)
+		{
+			SNAP_ITEM item;
+			const void *data = snap_get_item(SNAP_CURRENT, i, &item);
+			
+			if(item.type == OBJTYPE_PLAYER)
+			{
+				const obj_player *player = (const obj_player *)data;
+				client_datas[player->clientid].predicted.world = &world;
+				world.players[player->clientid] = &client_datas[player->clientid].predicted;
+				
+				client_datas[player->clientid].predicted.read(player);
+				if(player->local)
+					local_cid = player->clientid;
+			}
+		}
+		
+		// predict
+		for(int tick = client_tick(); tick <= client_predtick(); tick++)
+		{
+			// first calculate where everyone should move
+			for(int c = 0; c < MAX_CLIENTS; c++)
+			{
+				if(!world.players[c])
+					continue;
+				
+				mem_zero(&world.players[c]->input, sizeof(world.players[c]->input));
+				if(local_cid == c)
+				{
+					// apply player input
+					int *input = client_get_input(tick);
+					if(input)
+						world.players[c]->input = *((player_input*)input);
+				}
+				
+				world.players[c]->tick();
+			}
+			
+			// move all players and quantize their data
+			for(int c = 0; c < MAX_CLIENTS; c++)
+			{
+				if(!world.players[c])
+					continue;
+					
+				world.players[c]->move();
+				world.players[c]->quantize();
+			}
+		}
+		
+		// get the data from the local player
+		if(local_cid != -1)
+		{
+			predicted_prev_player = predicted_player;
+			predicted_player = *world.players[local_cid];
+		}
+	}
+}
+
 extern "C" void modc_newsnapshot()
 {
 	if(must_process_events)
@@ -945,7 +1016,7 @@ static void render_tee(animstate *anim, int skin, int emote, vec2 dir, vec2 pos)
 		// first pass we draw the outline
 		// second pass we draw the filling
 		int outline = p==0 ? 1 : 0;
-		int shift = charids[skin%16];
+		int shift = skin;
 		
 		for(int f = 0; f < 2; f++)
 		{
@@ -1070,22 +1141,37 @@ void draw_round_rect(float x, float y, float w, float h, float r)
 	gfx_quads_drawTL(x+w-r, y+r, r, h-r*2); // right
 }
 
-static void render_player(const obj_player *prev, const obj_player *player)
+static void render_player(const obj_player *prev_obj, const obj_player *player_obj)
 {
-	if(player->health < 0) // dont render dead players
+	obj_player prev;
+	obj_player player;
+	prev = *prev_obj;
+	player = *player_obj;
+	
+	if(player.health < 0) // dont render dead players
 		return;
+	
+	if(player.local)
+	{
+		// apply predicted results
+		predicted_player.write(&player);
+		predicted_prev_player.write(&prev);
+	}
 		
-	int skin = gametype == GAMETYPE_DM ? player->clientid : skinseed + player->team;
+	int skin = charids[player.clientid];
+	
+	if(gametype != GAMETYPE_DM)
+		skin = player.team*9; // 0 or 9
 
-	vec2 direction = get_direction(player->angle);
-	float angle = player->angle/256.0f;
-	vec2 position = mix(vec2(prev->x, prev->y), vec2(player->x, player->y), client_intratick());
+	vec2 direction = get_direction(player.angle);
+	float angle = player.angle/256.0f;
+	vec2 position = mix(vec2(prev.x, prev.y), vec2(player.x, player.y), client_intratick());
 	
-	if(prev->health < 0) // Don't flicker from previous position
-		position = vec2(player->x, player->y);
+	if(prev.health < 0) // Don't flicker from previous position
+		position = vec2(player.x, player.y);
 	
-	bool stationary = player->vx < 1 && player->vx > -1;
-	bool inair = col_check_point(player->x, player->y+16) == 0;
+	bool stationary = player.vx < 1 && player.vx > -1;
+	bool inair = col_check_point(player.x, player.y+16) == 0;
 	
 	// evaluate animation
 	float walk_time = fmod(position.x, 100.0f)/100.0f;
@@ -1099,26 +1185,26 @@ static void render_player(const obj_player *prev, const obj_player *player)
 	else
 		anim_eval_add(&state, &data->animations[ANIM_WALK], walk_time, 1.0f);
 
-	if (player->weapon == WEAPON_HAMMER)
+	if (player.weapon == WEAPON_HAMMER)
 	{
-		float a = clamp((client_tick()-player->attacktick+client_intratick())/10.0f, 0.0f, 1.0f);
+		float a = clamp((client_tick()-player.attacktick+client_intratick())/10.0f, 0.0f, 1.0f);
 		anim_eval_add(&state, &data->animations[ANIM_HAMMER_SWING], a, 1.0f);
 	}
-	if (player->weapon == WEAPON_NINJA)
+	if (player.weapon == WEAPON_NINJA)
 	{
-		float a = clamp((client_tick()-player->attacktick+client_intratick())/40.0f, 0.0f, 1.0f);
+		float a = clamp((client_tick()-player.attacktick+client_intratick())/40.0f, 0.0f, 1.0f);
 		anim_eval_add(&state, &data->animations[ANIM_NINJA_SWING], a, 1.0f);
 	}
 		
 	// draw hook
-	if (prev->hook_active && player->hook_active)
+	if (prev.hook_state>0 && player.hook_state>0)
 	{
 		gfx_texture_set(data->images[IMAGE_GAME].id);
 		gfx_quads_begin();
 		//gfx_quads_begin();
 
 		vec2 pos = position;
-		vec2 hook_pos = mix(vec2(prev->hook_x, prev->hook_y), vec2(player->hook_x, player->hook_y), client_intratick());
+		vec2 hook_pos = mix(vec2(prev.hook_x, prev.hook_y), vec2(player.hook_x, player.hook_y), client_intratick());
 		
 		float d = distance(pos, hook_pos);
 		vec2 dir = normalize(pos-hook_pos);
@@ -1150,13 +1236,13 @@ static void render_player(const obj_player *prev, const obj_player *player)
 		gfx_quads_setrotation(state.attach.angle*pi*2+angle);
 
 		// normal weapons
-		int iw = clamp(player->weapon, 0, NUM_WEAPONS-1);
+		int iw = clamp(player.weapon, 0, NUM_WEAPONS-1);
 		select_sprite(data->weapons[iw].sprite_body, direction.x < 0 ? SPRITE_FLAG_FLIP_Y : 0);
 
 		vec2 dir = direction;
 		float recoil = 0.0f;
 		vec2 p;
-		if (player->weapon == WEAPON_HAMMER)
+		if (player.weapon == WEAPON_HAMMER)
 		{
 			// Static position for hammer
 			p = position;
@@ -1173,7 +1259,7 @@ static void render_player(const obj_player *prev, const obj_player *player)
 			}
 			draw_sprite(p.x, p.y, data->weapons[iw].visual_size);
 		}
-		else if (player->weapon == WEAPON_NINJA)
+		else if (player.weapon == WEAPON_NINJA)
 		{
 			p = position;
 			p.y += data->weapons[iw].offsety;
@@ -1190,13 +1276,13 @@ static void render_player(const obj_player *prev, const obj_player *player)
 			draw_sprite(p.x, p.y, data->weapons[iw].visual_size);
 
 			// HADOKEN
-			if ((client_tick()-player->attacktick) <= (SERVER_TICK_SPEED / 6) && data->weapons[iw].nummuzzlesprites)
+			if ((client_tick()-player.attacktick) <= (SERVER_TICK_SPEED / 6) && data->weapons[iw].nummuzzlesprites)
 			{
 				int itex = rand() % data->weapons[iw].nummuzzlesprites;
 				float alpha = 1.0f;
 				if (alpha > 0.0f && data->weapons[iw].sprite_muzzle[itex].psprite)
 				{
-					vec2 dir = vec2(player->x,player->y) - vec2(prev->x, prev->y);
+					vec2 dir = vec2(player.x,player.y) - vec2(prev.x, prev.y);
 					dir = normalize(dir);
 					float hadokenangle = atan(dir.y/dir.x);
 					if (dir.x < 0.0f)
@@ -1216,7 +1302,7 @@ static void render_player(const obj_player *prev, const obj_player *player)
 		{
 			// TODO: should be an animation
 			recoil = 0;
-			float a = (client_tick()-player->attacktick+client_intratick())/5.0f;
+			float a = (client_tick()-player.attacktick+client_intratick())/5.0f;
 			if(a < 1)
 				recoil = sinf(a*pi);
 			p = position + dir * data->weapons[iw].offsetx - dir*recoil*10.0f;
@@ -1224,13 +1310,13 @@ static void render_player(const obj_player *prev, const obj_player *player)
 			draw_sprite(p.x, p.y, data->weapons[iw].visual_size);
 		}
 		
-		if (player->weapon == WEAPON_GUN || player->weapon == WEAPON_SHOTGUN)
+		if (player.weapon == WEAPON_GUN || player.weapon == WEAPON_SHOTGUN)
 		{
 			// check if we're firing stuff
-			if (true)///prev->attackticks)
+			if (true)///prev.attackticks)
 			{
 				float alpha = 0.0f;
-				int phase1tick = (client_tick() - player->attacktick);
+				int phase1tick = (client_tick() - player.attacktick);
 				if (phase1tick < (data->weapons[iw].muzzleduration + 3))
 				{
 					float intratick = client_intratick();
@@ -1252,14 +1338,14 @@ static void render_player(const obj_player *prev, const obj_player *player)
 					draw_sprite(p.x, p.y, data->weapons[iw].visual_size);
 					/*gfx_quads_setcolor(1.0f,1.0f,1.0f,alpha);
 					vec2 diry(-dir.y,dir.x);
-					p += dir * muzzleparams[player->weapon].offsetx + diry * offsety;
-					gfx_quads_draw(p.x,p.y,muzzleparams[player->weapon].sizex, muzzleparams[player->weapon].sizey);*/
+					p += dir * muzzleparams[player.weapon].offsetx + diry * offsety;
+					gfx_quads_draw(p.x,p.y,muzzleparams[player.weapon].sizex, muzzleparams[player.weapon].sizey);*/
 				}
 			}
 		}
 		gfx_quads_end();
 
-		switch (player->weapon)
+		switch (player.weapon)
 		{
 			case WEAPON_GUN: render_hand(skin, p, direction, -3*pi/4, vec2(-15, 4)); break;
 			case WEAPON_SHOTGUN: render_hand(skin, p, direction, -pi/2, vec2(-5, 4)); break;
@@ -1269,9 +1355,15 @@ static void render_player(const obj_player *prev, const obj_player *player)
 	}
 
 	// render the tee
-	render_tee(&state, skin, player->emote, direction, position);
+	if(player.local && config.debug)
+	{
+		vec2 ghost_position = mix(vec2(prev_obj->x, prev_obj->y), vec2(player_obj->x, player_obj->y), client_intratick());
+		render_tee(&state, 15, player.emote, direction, ghost_position); // render ghost
+	}
+	
+	render_tee(&state, skin, player.emote, direction, position);
 
-	if(player->state == STATE_CHATTING)
+	if(player.state == STATE_CHATTING)
 	{
 		gfx_texture_set(data->images[IMAGE_CHAT_BUBBLES].id);
 		gfx_quads_begin();
@@ -1280,13 +1372,13 @@ static void render_player(const obj_player *prev, const obj_player *player)
 		gfx_quads_end();
 	}
 
-	if (client_datas[player->clientid].emoticon_start != -1 && client_datas[player->clientid].emoticon_start + 2 * client_tickspeed() > client_tick())
+	if (client_datas[player.clientid].emoticon_start != -1 && client_datas[player.clientid].emoticon_start + 2 * client_tickspeed() > client_tick())
 	{
 		gfx_texture_set(data->images[IMAGE_EMOTICONS].id);
 		gfx_quads_begin();
 
-		int since_start = client_tick() - client_datas[player->clientid].emoticon_start;
-		int from_end = client_datas[player->clientid].emoticon_start + 2 * client_tickspeed() - client_tick();
+		int since_start = client_tick() - client_datas[player.clientid].emoticon_start;
+		int from_end = client_datas[player.clientid].emoticon_start + 2 * client_tickspeed() - client_tick();
 
 		float a = 1;
 
@@ -1307,7 +1399,7 @@ static void render_player(const obj_player *prev, const obj_player *player)
 
 		gfx_quads_setcolor(1.0f,1.0f,1.0f,a);
 		// client_datas::emoticon is an offset from the first emoticon
-		select_sprite(SPRITE_OOP + client_datas[player->clientid].emoticon);
+		select_sprite(SPRITE_OOP + client_datas[player.clientid].emoticon);
 		gfx_quads_draw(position.x, position.y - 23 - 32*h, 64, 64*h);
 		gfx_quads_end();
 	}
@@ -1726,6 +1818,9 @@ void render_game()
 		}
 	}
 	
+	local_player_pos = mix(predicted_prev_player.pos, predicted_player.pos, client_intratick());
+	//local_player_pos = predicted_player.pos;
+	
 	// everything updated, do events
 	if(must_process_events)
 		process_events(SNAP_PREV);
diff --git a/src/game/game.h b/src/game/game.h
index e3e4e99e..2a35bf97 100644
--- a/src/game/game.h
+++ b/src/game/game.h
@@ -20,6 +20,80 @@ inline float get_angle(vec2 dir)
 	return a;
 }
 
+
+template<typename T>
+inline T saturated_add(T min, T max, T current, T modifier)
+{
+	if(modifier < 0)
+	{
+		if(current < min)
+			return current;
+		current += modifier;
+		if(current < min)
+			current = min;
+		return current;
+	}
+	else
+	{
+		if(current > max)
+			return current;
+		current += modifier;
+		if(current > max)
+			current = max;
+		return current;
+	}
+}
+
+void move_point(vec2 *inout_pos, vec2 *inout_vel, float elasticity, int *bounces);
+void move_box(vec2 *inout_pos, vec2 *inout_vel, vec2 size, float elasticity);
+
+
+// hooking stuff
+enum
+{
+	HOOK_RETRACTED=-1,
+	HOOK_IDLE=0,
+	HOOK_FLYING,
+	HOOK_GRABBED
+};
+
+class world_core
+{
+public:
+	world_core()
+	{
+		mem_zero(players, sizeof(players));
+	}
+		
+	class player_core *players[MAX_CLIENTS];
+};
+
+class player_core
+{
+public:
+	world_core *world;
+	
+	vec2 pos;
+	vec2 vel;
+	
+	vec2 hook_pos;
+	vec2 hook_dir;
+	int hook_tick;
+	int hook_state;
+	int hooked_player;
+	
+	int jumped;
+	player_input input;
+	
+	void tick();
+	void move();
+	
+	void read(const obj_player_core *obj_core);
+	void write(obj_player_core *obj_core);
+	void quantize();
+};
+
+
 #define LERP(a,b,t) (a + (b-a) * t)
 #define min(a, b) ( a > b ? b : a)
 #define max(a, b) ( a > b ? a : b)
diff --git a/src/game/game_protocol.h b/src/game/game_protocol.h
index cd28b2d5..3ef491ef 100644
--- a/src/game/game_protocol.h
+++ b/src/game/game_protocol.h
@@ -1,5 +1,22 @@
 // NOTE: Be very careful when editing this file as it will change the network version
 
+// --------- PHYSICS TWEAK! --------
+const float ground_control_speed = 7.0f;
+const float ground_control_accel = 2.0f;
+const float ground_friction = 0.5f;
+const float ground_jump_speed = 13.5f;
+const float air_control_speed = 3.5f;
+const float air_control_accel = 1.2f;
+const float air_friction = 0.95f;
+const float hook_length = 34*10.0f;
+const float hook_fire_speed = 45.0f;
+const float hook_drag_accel = 3.0f;
+const float hook_drag_speed = 15.0f;
+const float gravity = 0.5f;
+const float wall_friction = 0.80f;
+const float wall_jump_speed_up = ground_jump_speed*0.8f;
+const float wall_jump_speed_out = ground_jump_speed*0.8f;
+
 // Network stuff
 enum
 {
@@ -129,7 +146,19 @@ struct obj_flag
 	int team;
 };
 
-struct obj_player
+
+struct obj_player_core
+{
+	int x, y;
+	int vx, vy;
+	int angle;
+
+	int hook_state;
+	int hook_x, hook_y;
+	int hook_dx, hook_dy;
+};
+
+struct obj_player : public obj_player_core
 {
 	int local;
 	int clientid;
@@ -139,10 +168,6 @@ struct obj_player
 	int armor;
 	int ammocount;
 	
-	int x, y;
-	int vx, vy;
-	int angle;
-	
 	int weapon; // current active weapon
 
 	int attacktick; // num attack ticks left of current attack
@@ -152,7 +177,5 @@ struct obj_player
 	int latency_flux;
 	int emote;
 	
-	int hook_active;
-	int hook_x, hook_y;
 	int team;
 };
diff --git a/src/game/game_variables.h b/src/game/game_variables.h
index c8cf0150..429c18aa 100644
--- a/src/game/game_variables.h
+++ b/src/game/game_variables.h
@@ -14,6 +14,9 @@ MACRO_CONFIG_INT(scorelimit, 20, 0, 1000)
 MACRO_CONFIG_INT(timelimit, 0, 0, 1000)
 MACRO_CONFIG_STR(gametype, 32, "dm")
 
+MACRO_CONFIG_INT(dbg_bots, 0, 0, 7)
+MACRO_CONFIG_INT(cl_predict, 1, 0, 1)
+
 MACRO_CONFIG_INT(dynamic_camera, 1, 0, 1)
 
 
diff --git a/src/game/server/game_server.cpp b/src/game/server/game_server.cpp
index ebb35cf1..7ad4ec09 100644
--- a/src/game/server/game_server.cpp
+++ b/src/game/server/game_server.cpp
@@ -9,26 +9,6 @@
 
 data_container *data = 0x0;
 
-// --------- DEBUG STUFF ---------
-const int debug_bots = 3;
-
-// --------- PHYSICS TWEAK! --------
-const float ground_control_speed = 7.0f;
-const float ground_control_accel = 2.0f;
-const float ground_friction = 0.5f;
-const float ground_jump_speed = 13.5f;
-const float air_control_speed = 3.5f;
-const float air_control_accel = 1.2f;
-const float air_friction = 0.95f;
-const float hook_length = 34*10.0f;
-const float hook_fire_speed = 45.0f;
-const float hook_drag_accel = 3.0f;
-const float hook_drag_speed = 15.0f;
-const float gravity = 0.5f;
-const float wall_friction = 0.80f;
-const float wall_jump_speed_up = ground_jump_speed*0.8f;
-const float wall_jump_speed_out = ground_jump_speed*0.8f;
-
 class player* get_player(int index);
 void create_damageind(vec2 p, float angle_mod, int amount);
 void create_explosion(vec2 p, int owner, int weapon, bool bnodamage);
@@ -39,134 +19,6 @@ void create_sound(vec2 pos, int sound, int loopflags = 0);
 void create_targetted_sound(vec2 pos, int sound, int target, int loopflags = 0);
 class player *intersect_player(vec2 pos0, vec2 pos1, vec2 &new_pos, class entity *notthis = 0);
 
-template<typename T>
-T saturated_add(T min, T max, T current, T modifier)
-{
-	if(modifier < 0)
-	{
-		if(current < min)
-			return current;
-		current += modifier;
-		if(current < min)
-			current = min;
-		return current;
-	}
-	else
-	{
-		if(current > max)
-			return current;
-		current += modifier;
-		if(current > max)
-			current = max;
-		return current;
-	}
-}
-
-// TODO: rewrite this smarter!
-void move_point(vec2 *inout_pos, vec2 *inout_vel, float elasticity, int *bounces)
-{
-	if(bounces)
-		*bounces = 0;
-	
-	vec2 pos = *inout_pos;
-	vec2 vel = *inout_vel;
-	if(col_check_point(pos + vel))
-	{
-		int affected = 0;
-		if(col_check_point(pos.x + vel.x, pos.y))
-		{
-			inout_vel->x *= -elasticity;
-			if(bounces)
-				(*bounces)++;			
-			affected++;
-		}
-
-		if(col_check_point(pos.x, pos.y + vel.y))
-		{
-			inout_vel->y *= -elasticity;
-			if(bounces)
-				(*bounces)++;			
-			affected++;
-		}
-		
-		if(affected == 0)
-		{
-			inout_vel->x *= -elasticity;
-			inout_vel->y *= -elasticity;
-		}
-	}
-	else
-	{
-		*inout_pos = pos + vel;
-	}
-}
-
-// TODO: rewrite this smarter!
-void move_box(vec2 *inout_pos, vec2 *inout_vel, vec2 size, float elasticity)
-{
-	// do the move
-	vec2 pos = *inout_pos;
-	vec2 vel = *inout_vel;
-	
-	float distance = length(vel);
-	int max = (int)distance;
-	
-	vec2 offsets[4] = {	vec2(-size.x/2, -size.y/2), vec2( size.x/2, -size.y/2),
-						vec2(-size.x/2,  size.y/2), vec2( size.x/2,  size.y/2)};
-	
-	if(distance > 0.00001f)
-	{
-		vec2 old_pos = pos;
-		for(int i = 0; i <= max; i++)
-		{
-			float amount = i/(float)max;
-			if(max == 0)
-				amount = 0;
-			
-			vec2 new_pos = pos + vel*amount; // TODO: this row is not nice
-
-			for(int p = 0; p < 4; p++)
-			{
-				vec2 np = new_pos+offsets[p];
-				vec2 op = old_pos+offsets[p];
-				if(col_check_point(np))
-				{
-					int affected = 0;
-					if(col_check_point(np.x, op.y))
-					{
-						vel.x = -vel.x*elasticity;
-						pos.x = old_pos.x;
-						new_pos.x = old_pos.x;
-						affected++;
-					}
-
-					if(col_check_point(op.x, np.y))
-					{
-						vel.y = -vel.y*elasticity;
-						pos.y = old_pos.y;
-						new_pos.y = old_pos.y;
-						affected++;
-					}
-					
-					if(!affected)
-					{
-						new_pos = old_pos;
-						pos = old_pos;
-						vel *= -elasticity;
-					}
-				}
-			}
-			
-			old_pos = new_pos;
-		}
-		
-		pos = old_pos;
-	}
-	
-	*inout_pos = pos;
-	*inout_vel = vel;
-}
-
 //////////////////////////////////////////////////
 // Event handler
 //////////////////////////////////////////////////
@@ -784,8 +636,8 @@ void player::reset()
 	release_hooks();
 	
 	pos = vec2(0.0f, 0.0f);
-	vel = vec2(0.0f, 0.0f);
-	direction = vec2(0.0f, 1.0f);
+	core.vel = vec2(0.0f, 0.0f);
+	//direction = vec2(0.0f, 1.0f);
 	score = 0;
 	dead = true;
 	spawning = false;
@@ -863,7 +715,9 @@ void player::try_respawn()
 	
 	spawning = false;
 	pos = spawnpos;
-	defered_pos = pos;
+	
+	core.pos = pos;
+	core.hooked_player = -1;
 	
 
 	health = 10;
@@ -873,8 +727,10 @@ void player::try_respawn()
 	set_flag(entity::FLAG_ALIVE);
 	state = STATE_PLAYING;
 	
+	core.hook_state = HOOK_IDLE;
+	
 	mem_zero(&input, sizeof(input));
-	vel = vec2(0.0f, 0.0f);
+	core.vel = vec2(0.0f, 0.0f);
 		
 	// init weapons
 	mem_zero(&weapons, sizeof(weapons));
@@ -905,13 +761,14 @@ bool player::is_grounded()
 // releases the hooked player
 void player::release_hooked()
 {
-	hook_state = HOOK_RETRACTED;
-	hooked_player = 0x0;
+	//hook_state = HOOK_RETRACTED;
+	//hooked_player = 0x0;
 }
 
 // release all hooks to this player	
 void player::release_hooks()
 {
+	/*
 	// TODO: loop thru players only
 	for(entity *ent = world.first_entity; ent; ent = ent->next_entity)
 	{
@@ -921,11 +778,13 @@ void player::release_hooks()
 			if(p->hooked_player == this)
 				p->release_hooked();
 		}
-	}
+	}*/
 }
 
 int player::handle_ninja()
 {
+	vec2 direction = normalize(vec2(input.target_x, input.target_y));
+	
 	if ((server_tick() - ninjaactivationtick) > (data->weapons[WEAPON_NINJA].duration * server_tickspeed() / 1000))
 	{
 		// time's up, return
@@ -956,18 +815,18 @@ int player::handle_ninja()
 	if (currentmovetime == 0)
 	{	
 		// reset player velocity
-		vel *= 0.2f;
+		core.vel *= 0.2f;
 		//return MODIFIER_RETURNFLAGS_OVERRIDEWEAPON;
 	}
 	
 	if (currentmovetime > 0)
 	{
 		// Set player velocity
-		vel = activationdir * data->weapons[WEAPON_NINJA].velocity;
+		core.vel = activationdir * data->weapons[WEAPON_NINJA].velocity;
 		vec2 oldpos = pos;
-		move_box(&defered_pos, &vel, vec2(phys_size, phys_size), 0.0f);
+		move_box(&core.pos, &core.vel, vec2(phys_size, phys_size), 0.0f);
 		// reset velocity so the client doesn't predict stuff
-		vel = vec2(0.0f,0.0f);
+		core.vel = vec2(0.0f,0.0f);
 		if ((currentmovetime % 2) == 0)
 		{
 			create_smoke(pos);
@@ -1015,6 +874,8 @@ int player::handle_ninja()
 
 int player::handle_weapons()
 {
+	vec2 direction = normalize(vec2(input.target_x, input.target_y));
+	
 	if(config.stress)
 	{
 		for(int i = 0; i < NUM_WEAPONS; i++)
@@ -1175,7 +1036,7 @@ int player::handle_weapons()
 				dir = normalize(target->pos - pos);
 			else
 				dir = vec2(0,-1);
-			target->vel += dir * 25.0f + vec2(0,-5.0f);
+			target->core.vel += dir * 25.0f + vec2(0,-5.0f);
 		}
 	}
 	if (data->weapons[active_weapon].ammoregentime)
@@ -1228,7 +1089,6 @@ void player::tick()
 		try_respawn();
 
 	// TODO: rework the input to be more robust
-	// TODO: remove this tick count, it feels weird
 	if(dead)
 	{
 		if(server_tick()-die_tick >= server_tickspeed()*5) // auto respawn after 3 sec
@@ -1238,205 +1098,30 @@ void player::tick()
 		return;
 	}
 	
-	// fetch some info
-	bool grounded = is_grounded();
-	int wall_sliding = 0;
-	direction = normalize(vec2(input.target_x, input.target_y));
+	//player_core core;
+	//core.pos = pos;
+	//core.jumped = jumped;
+	core.input = input;
+	core.tick();
 	
-	float max_speed = grounded ? ground_control_speed : air_control_speed;
-	float accel = grounded ? ground_control_accel : air_control_accel;
-	float friction = grounded ? ground_friction : air_friction;
-	
-	if(!grounded && vel.y > 0)
-	{
-		if(input.left && col_check_point((int)(pos.x-phys_size/2)-4, (int)(pos.y)))
-			wall_sliding = -1;
-		if(input.right && col_check_point((int)(pos.x+phys_size/2)+4, (int)(pos.y)))
-			wall_sliding = 1;
-	}
-
-	if(wall_sliding)
-		vel.y *= wall_friction;
-	
-	// handle movement
-	if(input.left)
-		vel.x = saturated_add(-max_speed, max_speed, vel.x, -accel);
-	if(input.right)
-		vel.x = saturated_add(-max_speed, max_speed, vel.x, accel);
-		
-	if(!input.left && !input.right)
-		vel.x *= friction;
-	
-	// handle jumping
-	if(input.jump)
-	{
-		if(!jumped && (grounded || wall_sliding))
-		{
-			create_sound(pos, SOUND_PLAYER_JUMP);
-			if(wall_sliding)
-			{
-				vel.y = -wall_jump_speed_up;
-				vel.x = -wall_jump_speed_out*wall_sliding;
-			}
-			else
-				vel.y = -ground_jump_speed;
-			jumped++;
-		}
-	}
-	else
-		jumped = 0;
-		
-	// do hook
-	if(input.hook)
-	{
-		if(hook_state == HOOK_IDLE)
-		{
-			hook_state = HOOK_FLYING;
-			hook_pos = pos;
-			hook_dir = direction;
-			hook_tick = -1;
-		}
-		else if(hook_state == HOOK_FLYING)
-		{
-			vec2 new_pos = hook_pos+hook_dir*hook_fire_speed;
-
-			// Check against other players first
-			for(entity *ent = world.first_entity; ent; ent = ent->next_entity)
-			{
-				if(ent && ent->objtype == OBJTYPE_PLAYER)
-				{
-					player *p = (player*)ent;
-					if(p != this && !p->dead && distance(p->pos, new_pos) < p->phys_size)
-					{
-						hook_state = HOOK_GRABBED;
-						hooked_player = p;
-						break;
-					}
-				}
-			}
-			
-			if(hook_state == HOOK_FLYING)
-			{
-				// check against ground
-				if(col_intersect_line(hook_pos, new_pos, &new_pos))
-				{
-					hook_state = HOOK_GRABBED;
-					hook_pos = new_pos;	
-				}
-				else if(distance(pos, new_pos) > hook_length)
-				{
-					hook_state = HOOK_RETRACTED;
-				}
-				else
-					hook_pos = new_pos;
-			}
-			
-			if(hook_state == HOOK_GRABBED)
-			{
-				create_sound(pos, SOUND_HOOK_ATTACH);
-				hook_tick = server_tick();
-			}
-		}
-	}
-	else
-	{
-		release_hooked();
-		hook_state = HOOK_IDLE;
-		hook_pos = pos;
-	}
-		
-	if(hook_state == HOOK_GRABBED)
-	{
-		if(hooked_player)
-		{
-			hook_pos = hooked_player->pos;
-			
-			// keep players hooked for a max of 1.5sec
-			if(server_tick() > hook_tick+(server_tickspeed()*3)/2)
-				release_hooked();
-		}
-			
-		/*if(hooked_player)
-			hook_pos = hooked_player->pos;
-
-		float d = distance(pos, hook_pos);
-		vec2 dir = normalize(pos - hook_pos);		
-		if(d > 10.0f) // TODO: fix tweakable variable
-		{
-			float accel = hook_drag_accel * (d/hook_length);
-			vel.x = saturated_add(-hook_drag_speed, hook_drag_speed, vel.x, -accel*dir.x*0.75f);
-			vel.y = saturated_add(-hook_drag_speed, hook_drag_speed, vel.y, -accel*dir.y);
-		}*/
-		
-		// Old version feels much better (to me atleast)
-		if(distance(hook_pos, pos) > 46.0f)
-		{
-			vec2 hookvel = normalize(hook_pos-pos)*hook_drag_accel;
-			// the hook as more power to drag you up then down.
-			// this makes it easier to get on top of an platform
-			if(hookvel.y > 0)
-				hookvel.y *= 0.3f;
-			
-			// the hook will boost it's power if the player wants to move
-			// in that direction. otherwise it will dampen everything abit
-			if((hookvel.x < 0 && input.left) || (hookvel.x > 0 && input.right)) 
-				hookvel.x *= 0.95f;
-			else
-				hookvel.x *= 0.75f;
-			
-			vec2 new_vel = vel+hookvel;
-			
-			// check if we are under the legal limit for the hook
-			if(length(new_vel) < hook_drag_speed || length(new_vel) < length(vel))
-				vel = new_vel; // no problem. apply
-		}
-	}
-		
-	// fix influence of other players, collision + hook
-	// TODO: loop thru players only
-	for(entity *ent = world.first_entity; ent; ent = ent->next_entity)
-	{
-		if(ent && ent->objtype == OBJTYPE_PLAYER)
-		{
-			player *p = (player*)ent;
-			if(p == this || !(p->flags&FLAG_ALIVE))
-				continue; // make sure that we don't nudge our self
-			
-			// handle player <-> player collision
-			float d = distance(pos, p->pos);
-			vec2 dir = normalize(pos - p->pos);
-			if(d < phys_size*1.25f)
-			{
-				float a = phys_size*1.25f - d;
-				vel = vel + dir*a;
-			}
-			
-			// handle hook influence
-			if(p->hooked_player == this)
-			{
-				if(d > phys_size*1.50f) // TODO: fix tweakable variable
-				{
-					float accel = hook_drag_accel * (d/hook_length);
-					vel.x = saturated_add(-hook_drag_speed, hook_drag_speed, vel.x, -accel*dir.x);
-					vel.y = saturated_add(-hook_drag_speed, hook_drag_speed, vel.y, -accel*dir.y);
-				}
-			}
-		}
-	}
 	
 	// handle weapons
 	int retflags = handle_weapons();
+	/*
 	if (!(retflags & (MODIFIER_RETURNFLAGS_OVERRIDEVELOCITY | MODIFIER_RETURNFLAGS_OVERRIDEPOSITION)))
 	{
 		// add gravity
-		if (!(retflags & MODIFIER_RETURNFLAGS_OVERRIDEGRAVITY))
-			vel.y += gravity;
+		//if (!(retflags & MODIFIER_RETURNFLAGS_OVERRIDEGRAVITY))
+			//vel.y += gravity;
 	
 		// do the move
 		defered_pos = pos;
-		move_box(&defered_pos, &vel, vec2(phys_size, phys_size), 0);
-	}
+		move_box(&core.pos, &vel, vec2(phys_size, phys_size), 0);
+	}*/
 
+	//defered_pos = core.pos;
+	//jumped = core.jumped;
+	
 	state = input.state;
 	
 	// Previnput
@@ -1446,8 +1131,12 @@ void player::tick()
 
 void player::tick_defered()
 {
+	core.move();
+	core.quantize();
+	pos = core.pos;
+	
 	// apply the new position
-	pos = defered_pos;
+	//pos = defered_pos;
 }
 
 void player::die(int killer, int weapon)
@@ -1478,13 +1167,14 @@ void player::die(int killer, int weapon)
 
 bool player::take_damage(vec2 force, int dmg, int from, int weapon)
 {
-	vel += force;
+	core.vel += force;
 
 	// player only inflicts half damage on self	
 	if(from == client_id)
 		dmg = max(1, dmg/2);
 
-	if (gameobj->gametype == GAMETYPE_TDM && from >= 0 && players[from].team == team)
+	// CTF and TDM,
+	if (gameobj->gametype != GAMETYPE_DM && from >= 0 && players[from].team == team)
 		return false;
 
 	damage_taken++;
@@ -1567,10 +1257,15 @@ void player::snap(int snaping_client)
 {
 	obj_player *player = (obj_player *)snap_new_item(OBJTYPE_PLAYER, client_id, sizeof(obj_player));
 
-	player->x = (int)pos.x;
-	player->y = (int)pos.y;
-	player->vx = (int)vel.x;
-	player->vy = (int)vel.y;
+	core.write(player);
+	
+	if(snaping_client != client_id)
+	{
+		player->vx = 0; // make sure that we don't send these to clients who don't need them
+		player->vy = 0;
+		player->hook_dx = 0;
+		player->hook_dy = 0;
+	}
 
 	if (emote_stop < server_tick())
 	{
@@ -1613,21 +1308,6 @@ void player::snap(int snaping_client)
 			player->emote = EMOTE_BLINK;
 	}
 	
-	player->hook_active = hook_state>0?1:0;
-	player->hook_x = (int)hook_pos.x;
-	player->hook_y = (int)hook_pos.y;
-
-	float a = 0;
-	if(input.target_x == 0)
-		a = atan((float)input.target_y);
-	else
-		a = atan((float)input.target_y/(float)input.target_x);
-		
-	if(input.target_x < 0)
-		a = a+pi;
-		
-	player->angle = (int)(a*256.0f);
-	
 	player->score = score;
 	player->team = team;
 
@@ -2011,7 +1691,7 @@ void mods_tick()
 	if(world.paused) // make sure that the game object always updates
 		gameobj->tick();
 
-	if(debug_bots)
+	if(config.dbg_bots)
 	{
 		static int count = 0;
 		if(count >= 0)
@@ -2019,13 +1699,14 @@ void mods_tick()
 			count++;
 			if(count == 10)
 			{
-				for(int i = 0; i < debug_bots ; i++)
+				for(int i = 0; i < config.dbg_bots ; i++)
 				{
 					mods_client_enter(MAX_CLIENTS-i-1);
 					strcpy(players[MAX_CLIENTS-i-1].name, "(bot)");
 					if(gameobj->gametype != GAMETYPE_DM)
-						players[MAX_CLIENTS-i-1].team = count&1;
+						players[MAX_CLIENTS-i-1].team = i&1;
 				}
+				
 				count = -1;
 			}
 		}
@@ -2187,7 +1868,15 @@ void mods_init()
 
 	players = new player[MAX_CLIENTS];
 	gameobj = new gameobject;
+	
+	// setup core world	
+	for(int i = 0; i < MAX_CLIENTS; i++)
+	{
+		players[i].core.world = &world.core;
+		world.core.players[i] = &players[i].core;
+	}
 
+	//
 	int start, num;
 	map_get_type(MAPRES_ITEM, &start, &num);
 	
diff --git a/src/game/server/game_server.h b/src/game/server/game_server.h
index b691e0de..56f668f6 100644
--- a/src/game/server/game_server.h
+++ b/src/game/server/game_server.h
@@ -85,6 +85,8 @@ public:
 	bool paused;
 	bool reset_requested;
 	
+	world_core core;
+	
 	game_world();
 	int find_entities(vec2 pos, float radius, entity **ents, int max);
 	int find_entities(vec2 pos, float radius, entity **ents, int max, const int* types, int maxtypes);
@@ -229,9 +231,9 @@ public:
 	int last_action;
 	
 	// we need a defered position so we can handle the physics correctly
-	vec2 defered_pos;
-	vec2 vel;
-	vec2 direction;
+	//vec2 defered_pos;
+	//vec2 vel;
+	//vec2 direction;
 
 	//
 	int client_id;
@@ -269,21 +271,14 @@ public:
 	int latency_avg;
 	int latency_min;
 	int latency_max;
-
-	// hooking stuff
-	enum
-	{
-		HOOK_RETRACTED=-1,
-		HOOK_IDLE=0,
-		HOOK_FLYING,
-		HOOK_GRABBED
-	};
 	
-	int hook_state;
-	int hook_tick;
-	player *hooked_player;
-	vec2 hook_pos;
-	vec2 hook_dir;
+	player_core core;
+	
+	//int hook_state;
+	//int hook_tick;
+	//player *hooked_player;
+	//vec2 hook_pos;
+	//vec2 hook_dir;
 
 	//
 	player();
diff --git a/src/tools/crapnet.cpp b/src/tools/crapnet.cpp
index 5b7adf52..1c5e3dc3 100644
--- a/src/tools/crapnet.cpp
+++ b/src/tools/crapnet.cpp
@@ -38,8 +38,8 @@ int run(int port, NETADDR4 dest)
 			if(bytes <= 0)
 				break;
 				
-			if((rand()%10) == 0) // drop the packet
-				continue;
+			//if((rand()%10) == 0) // drop the packet
+			//	continue;
 
 			// create new packet				
 			packet *p = (packet *)mem_alloc(sizeof(packet)+bytes, 1);
@@ -100,13 +100,13 @@ int run(int port, NETADDR4 dest)
 				net_udp4_send(socket, &p->send_to, p->data, p->data_size);
 				
 				// update lag
-				double flux = rand()/(double)RAND_MAX;
+				double flux = 0; //rand()/(double)RAND_MAX;
 				int ms_spike = 0;
-				int ms_flux = 20;
-				int ms_ping = 20;
+				int ms_flux = 00;
+				int ms_ping = 100;
 				current_latency = ((time_freq()*ms_ping)/1000) + (int64)(((time_freq()*ms_flux)/1000)*flux); // 50ms
 				
-				if((p->id%100) == 0)
+				if(ms_spike && (p->id%100) == 0)
 					current_latency += (time_freq()*ms_spike)/1000;
 
 				mem_free(p);
diff --git a/src/tools/map_resave.c b/src/tools/map_resave.c
new file mode 100644
index 00000000..91ed09a1
--- /dev/null
+++ b/src/tools/map_resave.c
@@ -0,0 +1,35 @@
+#include <engine/datafile.h>
+
+int main(int argc, char **argv)
+{
+	int i, id, type, size;
+	void *ptr;
+	DATAFILE *df;
+	DATAFILE_OUT *df_out;
+
+	if(argc != 3)
+		return -1;
+
+	df = datafile_load(argv[1]);
+	df_out = datafile_create(argv[2]);
+
+	/* add all items */
+	for(i = 0; i < datafile_num_items(df); i++)
+	{
+		ptr = datafile_get_item(df, i, &type, &id);
+		size = datafile_get_itemsize(df, i);
+		datafile_add_item(df_out, type, id, size, ptr);
+	}
+
+	/* add all data */
+	for(i = 0; i < datafile_num_data(df); i++)
+	{
+		ptr = datafile_get_data(df, i);
+		size = datafile_get_datasize(df, i);
+		datafile_add_data(df_out, size, ptr);
+	}
+
+	datafile_unload(df);
+	datafile_finish(df_out);
+	return 0;
+}