about summary refs log tree commit diff
diff options
context:
space:
mode:
authorMagnus Auvinen <magnus.auvinen@gmail.com>2007-09-23 18:27:04 +0000
committerMagnus Auvinen <magnus.auvinen@gmail.com>2007-09-23 18:27:04 +0000
commitebbe51718e6b3ed81ee0932641e0bc4ddb805fcc (patch)
tree4eca71f96840157c674558ad3f6a24e6118445ca
parent1004b466c33199b4c6903bbc676b06b8a34a81d9 (diff)
downloadzcatch-ebbe51718e6b3ed81ee0932641e0bc4ddb805fcc.tar.gz
zcatch-ebbe51718e6b3ed81ee0932641e0bc4ddb805fcc.zip
loads of changes. better prediction. line drawing. some nice graphs :)
-rw-r--r--default.bam2
-rw-r--r--src/editor/editor.cpp24
-rw-r--r--src/engine/client/client.c253
-rw-r--r--src/engine/client/gfx.c154
-rw-r--r--src/engine/client/ui.c2
-rw-r--r--src/engine/interface.h8
-rw-r--r--src/engine/protocol.h1
-rw-r--r--src/engine/server/server.c12
-rw-r--r--src/engine/system.c4
-rw-r--r--src/game/client/game_client.cpp64
-rw-r--r--src/game/client/menu.cpp14
-rw-r--r--src/tools/crapnet.cpp6
12 files changed, 339 insertions, 205 deletions
diff --git a/default.bam b/default.bam
index 3eb185b8..9b3f8899 100644
--- a/default.bam
+++ b/default.bam
@@ -225,7 +225,7 @@ function build(settings)
 	game_server = Compile(settings, Collect("src/game/server/*.cpp"), serverdata.source, serverdata.cdata)
 	editor = Compile(settings, Collect("src/editor/*.cpp"))
 
-	-- build tools
+	-- build tools (TODO: fix this so we don't get double _d_d stuff)
 	tools_src = Collect("src/tools/*.cpp", "src/tools/*.c")
 	
 	objs = Compile(settings, tools_src)
diff --git a/src/editor/editor.cpp b/src/editor/editor.cpp
index c2633778..6d6cea57 100644
--- a/src/editor/editor.cpp
+++ b/src/editor/editor.cpp
@@ -383,7 +383,7 @@ static void ui_do_frame(float x, float y, float w, float h)
 	gfx_texture_set(-1);
 	gfx_blend_normal();
 	gfx_quads_begin();
-	gfx_quads_setcolor(0.0f, 0.0f, 0.0f, 1.0f);
+	gfx_setcolor(0.0f, 0.0f, 0.0f, 1.0f);
 	gfx_quads_drawTL(x, y, w, h);
 	gfx_quads_end();
 	gfx_blend_normal();	
@@ -484,7 +484,7 @@ static int ui_do_tilemap(void *id, tilemap *tm, int flags, float x, float y, flo
 			gfx_texture_set(-1);
 			gfx_blend_additive();
 			gfx_quads_begin();
-			gfx_quads_setcolor(1.0f, 0.0f, 0.0f, 0.25f);
+			gfx_setcolor(1.0f, 0.0f, 0.0f, 0.25f);
 			gfx_quads_drawTL((tmx-brush.width/2)*scale, (tmy-brush.height/2)*scale, brush.width*scale, brush.height*scale);
 			gfx_quads_end();
 			gfx_blend_normal();
@@ -521,7 +521,7 @@ static int ui_do_tilemap(void *id, tilemap *tm, int flags, float x, float y, flo
 			gfx_texture_set(-1);
 			gfx_blend_additive();
 			gfx_quads_begin();
-			gfx_quads_setcolor(1.0f, 1.0f, 1.0f, 0.25f);
+			gfx_setcolor(1.0f, 1.0f, 1.0f, 0.25f);
 			gfx_quads_drawTL(select_wx, select_wy, select_ww, select_wh);
 			gfx_quads_end();
 			gfx_blend_normal();
@@ -597,11 +597,11 @@ static int ui_do_entity(void *id, entity *ent, int selected)
 	gfx_texture_set(-1);
 	gfx_quads_begin();
 	if(selected)
-		gfx_quads_setcolor(1.0f, 0.5f, 0.5f, 0.95f);
+		gfx_setcolor(1.0f, 0.5f, 0.5f, 0.95f);
 	else if(ui_hot_item() == id)
-		gfx_quads_setcolor(1.0f, 1.0f, 1.0f, 0.95f);
+		gfx_setcolor(1.0f, 1.0f, 1.0f, 0.95f);
 	else
-		gfx_quads_setcolor(0.75f, 0.75f, 0.75f, 0.95f);
+		gfx_setcolor(0.75f, 0.75f, 0.75f, 0.95f);
 		
 	gfx_quads_drawTL(x-w/2, y-w/2, w, h);
 	gfx_quads_end();
@@ -646,11 +646,11 @@ void draw_editor_button(void *id, const char *text, int checked, float x, float
     gfx_texture_set(-1);
     gfx_quads_begin(); 
     if(ui_hot_item() == id)
-        gfx_quads_setcolor(1,1,1,1);
+        gfx_setcolor(1,1,1,1);
     else if(checked)
-        gfx_quads_setcolor(0.75f,0.5f,0.5f,1);
+        gfx_setcolor(0.75f,0.5f,0.5f,1);
     else
-        gfx_quads_setcolor(0.5f,0.5f,0.5f,1);
+        gfx_setcolor(0.5f,0.5f,0.5f,1);
 
     gfx_quads_drawTL(x,y,w,h);
     gfx_quads_end();
@@ -872,7 +872,7 @@ static void editor_render()
 			gfx_texture_set(checker_texture);
 			gfx_blend_normal();
 			gfx_quads_begin();
-			gfx_quads_setcolor(1.0f, 1.0f, 1.0f, 1.0f);
+			gfx_setcolor(1.0f, 1.0f, 1.0f, 1.0f);
 			gfx_quads_setsubset(0,0,32.0f, 32.0f);
 			gfx_quads_drawTL(chooser_x, chooser_y, 16*16.0f, 16*16.0f);
 			gfx_quads_end();
@@ -1135,11 +1135,11 @@ static int editor_loop()
 		// render butt ugly mouse cursor
 		gfx_texture_set(-1);
 		gfx_quads_begin();
-		gfx_quads_setcolor(0,0,0,1);
+		gfx_setcolor(0,0,0,1);
 		gfx_quads_draw_freeform(mx,my,mx,my,
 								mx+7,my,
 								mx,my+7);
-		gfx_quads_setcolor(1,1,1,1);
+		gfx_setcolor(1,1,1,1);
 		gfx_quads_draw_freeform(mx+1,my+1,mx+1,my+1,
 								mx+5,my+1,
 								mx+1,my+5);
diff --git a/src/engine/client/client.c b/src/engine/client/client.c
index 63414f8e..26865f37 100644
--- a/src/engine/client/client.c
+++ b/src/engine/client/client.c
@@ -33,10 +33,6 @@ 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 snapshot_latency = 0;
-static float prediction_latency = 0;
 
 static int extra_polating = 0;
 static int debug_font;
@@ -48,6 +44,8 @@ static int window_must_refocus = 0;
 static int snaploss = 0;
 static int snapcrcerrors = 0;
 
+static int ack_game_tick = -1;
+
 static int current_recv_tick = 0;
 
 // current time
@@ -62,10 +60,123 @@ 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
+	int64 game_time; // prediction latency when we sent this input
+	int64 time;
 } inputs[200];
 static int current_input = 0;
 
+enum
+{
+	GRAPH_MAX=256,
+};
+
+typedef struct
+{
+	float min, max;
+	float values[GRAPH_MAX];
+	int index;
+} GRAPH;
+
+static void graph_add(GRAPH *g, float v)
+{
+	g->values[g->index] = v;
+	g->index = (g->index+1)&(GRAPH_MAX-1);
+}
+
+static void graph_render(GRAPH *g, float x, float y, float w, float h)
+{
+	int i;
+	gfx_texture_set(-1);
+	
+	gfx_quads_begin();
+	gfx_setcolor(0, 0, 0, 1);
+	gfx_quads_drawTL(x, y, w, h);
+	gfx_quads_end();
+		
+	gfx_lines_begin();
+	gfx_setcolor(0.5f, 0.5f, 0.5f, 1);
+	gfx_lines_draw(x, y+(h*3)/4, x+w, y+(h*3)/4);
+	gfx_lines_draw(x, y+h/2, x+w, y+h/2);
+	gfx_lines_draw(x, y+h/4, x+w, y+h/4);
+	for(i = 1; i < GRAPH_MAX; i++)
+	{
+		float a0 = (i-1)/(float)GRAPH_MAX;
+		float a1 = i/(float)GRAPH_MAX;
+		int i0 = (g->index+i-1)&(GRAPH_MAX-1);
+		int i1 = (g->index+i)&(GRAPH_MAX-1);
+		
+		float v0 = g->values[i0];
+		float v1 = g->values[i1];
+		
+		gfx_setcolor(0, 1, 0, 1);
+		gfx_lines_draw(x+a0*w, y+h-v0*h, x+a1*w, y+h-v1*h);
+	}
+	gfx_lines_end();
+}
+
+typedef struct
+{
+	int64 snap;
+	int64 current;
+	int64 target;
+	
+	int64 rlast;
+	int64 tlast;
+	GRAPH graph;
+} SMOOTHTIME;
+
+static void st_init(SMOOTHTIME *st, int64 target)
+{
+	st->snap = time_get();
+	st->current = target;
+	st->target = target;
+}
+
+static int64 st_get(SMOOTHTIME *st, int64 now)
+{
+	int64 c = st->current + (now - st->snap);
+	int64 t = st->target + (now - st->snap);
+	
+	// it's faster to adjust upward instead of downward
+	// we might need to adjust these abit
+	float adjust_speed = 0.3f;
+	if(t < c)
+		adjust_speed *= 5.0f;
+	
+	float a = ((now-st->snap)/(float)time_freq())*adjust_speed;
+	if(a > 1)
+		a = 1;
+		
+	int64 r = c + (int64)((t-c)*a);
+	
+	{
+		int64 drt = now - st->rlast;
+		int64 dtt = r - st->tlast;
+		
+		st->rlast = now;
+		st->tlast = r;
+		
+		if(drt == 0)
+			graph_add(&st->graph, 0.5f);
+		else
+			graph_add(&st->graph, (((dtt/(float)drt)-1.0f)*2.5f)+0.5f);
+	}
+	
+	return r;
+}
+
+static void st_update(SMOOTHTIME *st, int64 target)
+{
+	int64 now = time_get();
+	st->current = st_get(st, now);
+	st->snap = now;
+	st->target = target;
+}
+
+SMOOTHTIME game_time;
+SMOOTHTIME predicted_time;
+
+GRAPH intra_graph;
 
 // --- input snapping ---
 static int input_data[MAX_INPUT_SIZE] = {0};
@@ -125,8 +236,7 @@ static void snap_init()
 	snapshots[SNAP_CURRENT] = 0;
 	snapshots[SNAP_PREV] = 0;
 	recived_snapshots = 0;
-	game_start_time = -1;
-	
+	current_predtick = 0;
 }
 
 // ------ time functions ------
@@ -159,7 +269,6 @@ int client_send_msg()
 static void client_send_info()
 {
 	recived_snapshots = 0;
-	game_start_time = -1;
 
 	msg_pack_start_system(NETMSG_INFO, MSGFLAG_VITAL);
 	msg_pack_string(modc_net_version(), 128);
@@ -192,12 +301,18 @@ static void client_send_error(const char *error)
 
 static void client_send_input()
 {
+	if(current_predtick <= 0)
+		return;
+		
 	msg_pack_start_system(NETMSG_INPUT, 0);
+	msg_pack_int(ack_game_tick);
 	msg_pack_int(current_predtick);
 	msg_pack_int(input_data_size);
-	
+
+	int64 now = time_get();	
 	inputs[current_input].tick = current_predtick;
-	inputs[current_input].latency = prediction_latency;
+	inputs[current_input].game_time = st_get(&predicted_time, now);
+	inputs[current_input].time = now;
 	
 	int i;
 	for(i = 0; i < input_data_size/4; i++)
@@ -384,9 +499,6 @@ void client_connect(const char *server_address_str)
 	for(i = 0; i < 200; i++)
 		inputs[i].tick = -1;
 	current_input = 0;
-	
-	snapshot_latency = 0;
-	prediction_latency = 0;
 }
 
 void client_disconnect()
@@ -424,17 +536,23 @@ 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 snaplatency: %4.2f %c  predlatency: %4.2f  mem %dk   gfxmem: %dk  fps: %3d",
+	sprintf(buffer, "ticks: %8d %8d send: %6d recv: %6d snaploss: %d %c  mem %dk   gfxmem: %dk  fps: %3d",
+		current_tick, current_predtick,
 		(current.send_bytes-prev.send_bytes)*10,
 		(current.recv_bytes-prev.recv_bytes)*10,
 		snaploss,
-		snapshot_latency*1000.0f, extra_polating?'E':' ',
-		prediction_latency*1000.0f,
+		extra_polating?'E':' ',
 		mem_allocated()/1024,
 		gfx_memory_usage()/1024,
 		(int)(1.0f/frametime_avg));
 	gfx_quads_text(2, 2, 16, buffer);
 	
+	
+	// render graphs
+	gfx_mapscreen(0,0,400.0f,300.0f);
+	graph_render(&game_time.graph, 300, 10, 90, 50);
+	graph_render(&predicted_time.graph, 300, 10+50+10, 90, 50);
+	graph_render(&intra_graph, 300, 10+50+10+50+10, 90, 50);
 }
 
 void client_quit()
@@ -597,19 +715,15 @@ static void client_process_packet(NETPACKET *packet)
 					{
 						if(inputs[k].tick == input_predtick)
 						{
-							float wanted_latency = inputs[k].latency - time_left/1000.0f + 0.01f;
-							if(wanted_latency > prediction_latency)
-								prediction_latency = prediction_latency*0.90f + wanted_latency*0.10f;
-							else
-								prediction_latency = prediction_latency*0.95f + wanted_latency*0.05f;
-							//dbg_msg("DEBUG", "predlatency=%f", prediction_latency);
+							//-1000/50
+							int margin = 1000/50;
+							int64 target = inputs[k].game_time + (time_get() - inputs[k].time);
+							st_update(&predicted_time, target - (int64)(((time_left-margin)/1000.0f)*time_freq()));
 							break;
 						}
 					}
 				}
 				
-				//dbg_msg("DEBUG", "new predlatency=%f", prediction_latency);
-				
 				if(snapshot_part == part && game_tick > current_recv_tick)
 				{
 					// TODO: clean this up abit
@@ -642,10 +756,7 @@ static void client_process_packet(NETPACKET *packet)
 								
 								// ack snapshot
 								// TODO: combine this with the input message
-								msg_pack_start_system(NETMSG_SNAPACK, 0);
-								msg_pack_int(-1);
-								msg_pack_end();
-								client_send_msg();
+								ack_game_tick = -1;
 								return;
 							}
 						}
@@ -668,18 +779,15 @@ static void client_process_packet(NETPACKET *packet)
 						
 						unsigned char tmpbuffer3[MAX_SNAPSHOT_SIZE];
 						int snapsize = snapshot_unpack_delta(deltashot, (SNAPSHOT*)tmpbuffer3, deltadata, deltasize);
-						if(snapshot_crc((SNAPSHOT*)tmpbuffer3) != crc)
+						if(msg != NETMSG_SNAPEMPTY && snapshot_crc((SNAPSHOT*)tmpbuffer3) != crc)
 						{
 							if(config.debug)
-								dbg_msg("client", "snapshot crc error\n");
+								dbg_msg("client", "snapshot crc error");
 							snapcrcerrors++;
 							if(snapcrcerrors > 25)
 							{
 								// to many errors, send reset
-								msg_pack_start_system(NETMSG_SNAPACK, 0);
-								msg_pack_int(-1);
-								msg_pack_end();
-								client_send_msg();
+								ack_game_tick = -1;
 								snapcrcerrors = 0;
 							}
 							return;
@@ -718,12 +826,21 @@ static void client_process_packet(NETPACKET *packet)
 						// we got two snapshots until we see us self as connected
 						if(recived_snapshots == 2)
 						{
+							// start at 200ms and work from there
+							st_init(&predicted_time, (game_tick+10)*time_freq()/50);
+							st_init(&game_time, (game_tick-2)*time_freq()/50);
 							snapshots[SNAP_PREV] = snapshot_storage.first;
 							snapshots[SNAP_CURRENT] = snapshot_storage.last;
 							local_start_time = time_get();
 							client_set_state(CLIENTSTATE_ONLINE);
 						}
+						
+						st_update(&game_time, (game_tick-2)*time_freq()/50);
+						//client_send_input();
 
+						//st_update(&predicted_time, game_tick*time_freq());
+
+						/*
 						int64 now = time_get();
 						int64 t = now - game_tick*time_freq()/50;
 						if(game_start_time == -1 || t < game_start_time)
@@ -736,17 +853,16 @@ 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();
 						snapshot_latency = snapshot_latency*0.95f+current_latency*0.05f;
+						*/
 						
 						// ack snapshot
-						msg_pack_start_system(NETMSG_SNAPACK, 0);
-						msg_pack_int(game_tick);
-						msg_pack_end();
-						client_send_msg();
+						//dbg_msg("snap!", "%d", game_tick);
+						ack_game_tick = game_tick;
 					}
 				}
 				else
 				{
-					dbg_msg("client", "snapshot reset!");
+					dbg_msg("client", "snapsht reset!");
 					snapshot_part = 0;
 				}
 			}
@@ -759,7 +875,6 @@ static void client_process_packet(NETPACKET *packet)
 	}
 }
 
-
 static void client_pump_network()
 {
 	netclient_update(net);
@@ -842,16 +957,14 @@ static void client_run(const char *direct_connect_server)
 		if(recived_snapshots >= 3)
 		{
 			int repredict = 0;
-			int64 now = time_get();
+			//int64 now = time_get();
+			int64 now = st_get(&game_time, 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(snapshot_latency > 0)
-					t += (int64)(time_freq()*(snapshot_latency*1.1f));
+				int64 tickstart = (cur->tick)*time_freq()/50;
 
-				if(t < now)
+				if(tickstart < now)
 				{
 					SNAPSTORAGE_HOLDER *next = snapshots[SNAP_CURRENT]->next;
 					if(next)
@@ -880,36 +993,28 @@ static void client_run(const char *direct_connect_server)
 					break;
 				}
 			}
+
+			//tg_add(&game_time_graph, now, extra_polating);
 			
 			if(snapshots[SNAP_CURRENT] && snapshots[SNAP_PREV])
 			{
-				int64 curtick_start = game_start_time + (snapshots[SNAP_CURRENT]->tick+1)*time_freq()/50;
-				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(snapshot_latency > 0)
-					prevtick_start += (int64)(time_freq()*(snapshot_latency*1.1f));
-					
+				int64 curtick_start = (snapshots[SNAP_CURRENT]->tick)*time_freq()/50;
+				int64 prevtick_start = (snapshots[SNAP_PREV]->tick)*time_freq()/50;
 				intratick = (now - prevtick_start) / (float)(curtick_start-prevtick_start);
-
-				// 25 frames ahead
-				int64 last_pred_game_time = 0;
-				int64 predicted_game_time = (now - game_start_time) + (int64)(time_freq()*prediction_latency);
-				if(predicted_game_time < last_pred_game_time)
-					predicted_game_time = last_pred_game_time;
-				last_pred_game_time = predicted_game_time;
 				
-				//int64 predictiontime = game_start_time + time_freq()+ prediction_latency*SERVER_TICK_SPEED
-				int new_predtick = (predicted_game_time*SERVER_TICK_SPEED) / time_freq();
+				graph_add(&intra_graph, intratick*0.25f);
 				
-				int64 predtick_start_time = (new_predtick*time_freq())/SERVER_TICK_SPEED;
-				intrapredtick = (predicted_game_time - predtick_start_time)*SERVER_TICK_SPEED/(float)time_freq();
+				int64 pred_now = st_get(&predicted_time, time_get());
+				//tg_add(&predicted_time_graph, pred_now, 0);
+				int prev_pred_tick = (int)(pred_now*50/time_freq());
+				int new_pred_tick = prev_pred_tick+1;
+				curtick_start = new_pred_tick*time_freq()/50;
+				prevtick_start = prev_pred_tick*time_freq()/50;
+				intrapredtick = (pred_now - prevtick_start) / (float)(curtick_start-prevtick_start);
 				
-				if(new_predtick > current_predtick)
+				if(new_pred_tick > current_predtick)
 				{
-					//dbg_msg("")
-					current_predtick = new_predtick;
+					current_predtick = new_pred_tick;
 					repredict = 1;
 					
 					// send input
@@ -918,9 +1023,13 @@ static void client_run(const char *direct_connect_server)
 			}
 			
 			//intrapredtick = current_predtick
-			
+
+			// only do sane predictions			
 			if(repredict)
-				modc_predict();
+			{
+				if(current_predtick > current_tick && current_predtick < current_tick+50)
+					modc_predict();
+			}
 		}
 
 		// STRESS TEST: join the server again
@@ -970,11 +1079,15 @@ static void client_run(const char *direct_connect_server)
 		
 			if(inp_key_pressed(KEY_F5))
 			{
+				ack_game_tick = -1;
+				client_send_input();
+				/*
 				// ack snapshot
 				msg_pack_start_system(NETMSG_SNAPACK, 0);
 				msg_pack_int(-1);
 				msg_pack_end();
 				client_send_msg();
+				*/
 			}
 		}
 			
diff --git a/src/engine/client/gfx.c b/src/engine/client/gfx.c
index b48740b6..e2aa1c81 100644
--- a/src/engine/client/gfx.c
+++ b/src/engine/client/gfx.c
@@ -15,6 +15,12 @@
 #define GL_COMPRESSED_RGB_ARB 0x84ED
 #define GL_COMPRESSED_RGBA_ARB 0x84EE
 
+enum
+{
+	DRAWING_QUADS=1,
+	DRAWING_LINES=2,
+};
+
 //
 typedef struct { float x, y, z; } VEC3;
 typedef struct { float u, v; } TEXCOORD;
@@ -39,7 +45,7 @@ static int do_screenshot = 0;
 static int screen_width = -1;
 static int screen_height = -1;
 static float rotation = 0;
-static int quads_drawing = 0;
+static int drawing = 0;
 
 static float screen_x0 = 0;
 static float screen_y0 = 0;
@@ -71,39 +77,48 @@ static const unsigned char null_texture_data[] = {
 	0x00,0x00,0xff,0xff, 0x00,0x00,0xff,0xff, 0xff,0xff,0x00,0xff, 0xff,0xff,0x00,0xff, 
 };
 
-static void draw_quad(int _bflush)
+static void flush()
 {
-	if (!_bflush && ((num_vertices + 4) < vertex_buffer_size))
-	{
-		// Just add
-		num_vertices += 4;
-	}
-	else if (num_vertices)
-	{
-		if (!_bflush)
-			num_vertices += 4;
-			
-		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
-		//glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
-		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
-
-		glVertexPointer(3, GL_FLOAT,
-				sizeof(VERTEX),
-				(char*)vertices);
-		glTexCoordPointer(2, GL_FLOAT,
-				sizeof(VERTEX),
-				(char*)vertices + sizeof(float)*3);
-		glColorPointer(4, GL_FLOAT,
-				sizeof(VERTEX),
-				(char*)vertices + sizeof(float)*5);
-		glEnableClientState(GL_VERTEX_ARRAY);
-		glEnableClientState(GL_TEXTURE_COORD_ARRAY);
-		glEnableClientState(GL_COLOR_ARRAY);
-		glDrawArrays(GL_QUADS, 0, num_vertices);
+	if(num_vertices == 0)
+		return;
 		
-		// Reset pointer
-		num_vertices = 0;
-	}
+	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
+	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
+
+	glVertexPointer(3, GL_FLOAT,
+			sizeof(VERTEX),
+			(char*)vertices);
+	glTexCoordPointer(2, GL_FLOAT,
+			sizeof(VERTEX),
+			(char*)vertices + sizeof(float)*3);
+	glColorPointer(4, GL_FLOAT,
+			sizeof(VERTEX),
+			(char*)vertices + sizeof(float)*5);
+	glEnableClientState(GL_VERTEX_ARRAY);
+	glEnableClientState(GL_TEXTURE_COORD_ARRAY);
+	glEnableClientState(GL_COLOR_ARRAY);
+	
+	if(drawing == DRAWING_QUADS)
+		glDrawArrays(GL_QUADS, 0, num_vertices);
+	else if(drawing == DRAWING_LINES)
+		glDrawArrays(GL_LINES, 0, num_vertices);
+	
+	// Reset pointer
+	num_vertices = 0;	
+}
+
+static void draw_line()
+{
+	num_vertices += 2;
+	if((num_vertices + 2) >= vertex_buffer_size)
+		flush();
+}
+
+static void draw_quad()
+{
+	num_vertices += 4;
+	if((num_vertices + 4) >= vertex_buffer_size)
+		flush();
 }
 
 int gfx_init()
@@ -499,7 +514,7 @@ int gfx_screenheight()
 
 void gfx_texture_set(int slot)
 {
-	dbg_assert(quads_drawing == 0, "called gfx_texture_set within quads_begin");
+	dbg_assert(drawing == 0, "called gfx_texture_set within begin");
 	if(slot == -1)
 		glDisable(GL_TEXTURE_2D);
 	else
@@ -551,49 +566,49 @@ void gfx_setoffset(float x, float y)
 
 void gfx_quads_begin()
 {
-	dbg_assert(quads_drawing == 0, "called quads_begin twice");
-	quads_drawing++;
+	dbg_assert(drawing == 0, "called quads_begin twice");
+	drawing = DRAWING_QUADS;
 	
 	gfx_quads_setsubset(0,0,1,1);
 	gfx_quads_setrotation(0);
-	gfx_quads_setcolor(1,1,1,1);
+	gfx_setcolor(1,1,1,1);
 }
 
 void gfx_quads_end()
 {
-	dbg_assert(quads_drawing == 1, "called quads_end without quads_begin");
-	draw_quad(1);
-	quads_drawing--;
+	dbg_assert(drawing == DRAWING_QUADS, "called quads_end without begin");
+	flush();
+	drawing = 0;
 }
 
 
 void gfx_quads_setrotation(float angle)
 {
-	dbg_assert(quads_drawing == 1, "called gfx_quads_setrotation without quads_begin");
+	dbg_assert(drawing == DRAWING_QUADS, "called gfx_quads_setrotation without begin");
 	rotation = angle;
 }
 
-void gfx_quads_setcolorvertex(int i, float r, float g, float b, float a)
+void gfx_setcolorvertex(int i, float r, float g, float b, float a)
 {
-	dbg_assert(quads_drawing == 1, "called gfx_quads_setcolorvertex without quads_begin");
+	dbg_assert(drawing != 0, "called gfx_quads_setcolorvertex without begin");
 	color[i].r = r;
 	color[i].g = g;
 	color[i].b = b;
 	color[i].a = a;
 }
 
-void gfx_quads_setcolor(float r, float g, float b, float a)
+void gfx_setcolor(float r, float g, float b, float a)
 {
-	dbg_assert(quads_drawing == 1, "called gfx_quads_setcolor without quads_begin");
-	gfx_quads_setcolorvertex(0, r, g, b, a);
-	gfx_quads_setcolorvertex(1, r, g, b, a);
-	gfx_quads_setcolorvertex(2, r, g, b, a);
-	gfx_quads_setcolorvertex(3, r, g, b, a);
+	dbg_assert(drawing != 0, "called gfx_quads_setcolor without begin");
+	gfx_setcolorvertex(0, r, g, b, a);
+	gfx_setcolorvertex(1, r, g, b, a);
+	gfx_setcolorvertex(2, r, g, b, a);
+	gfx_setcolorvertex(3, r, g, b, a);
 }
 
 void gfx_quads_setsubset(float tl_u, float tl_v, float br_u, float br_v)
 {
-	dbg_assert(quads_drawing == 1, "called gfx_quads_setsubset without quads_begin");
+	dbg_assert(drawing == DRAWING_QUADS, "called gfx_quads_setsubset without begin");
 
 	texture[0].u = tl_u;
 	texture[0].v = tl_v;
@@ -631,7 +646,7 @@ void gfx_quads_draw(float x, float y, float w, float h)
 
 void gfx_quads_drawTL(float x, float y, float width, float height)
 {
-	dbg_assert(quads_drawing == 1, "called quads_draw without quads_begin");
+	dbg_assert(drawing == DRAWING_QUADS, "called quads_draw without begin");
 	
 	VEC3 center;
 	center.x = x + width/2;
@@ -662,7 +677,7 @@ void gfx_quads_drawTL(float x, float y, float width, float height)
 	vertices[num_vertices + 3].color = color[3];
 	rotate(&center, &vertices[num_vertices + 3].pos);
 	
-	draw_quad(0);
+	draw_quad();
 }
 
 void gfx_quads_draw_freeform(
@@ -671,7 +686,7 @@ void gfx_quads_draw_freeform(
 	float x2, float y2,
 	float x3, float y3)
 {
-	dbg_assert(quads_drawing == 1, "called quads_draw_freeform without quads_begin");
+	dbg_assert(drawing == DRAWING_QUADS, "called quads_draw_freeform without begin");
 	
 	vertices[num_vertices].pos.x = x0;
 	vertices[num_vertices].pos.y = y0;
@@ -693,7 +708,7 @@ void gfx_quads_draw_freeform(
 	vertices[num_vertices + 3].tex = texture[3];
 	vertices[num_vertices + 3].color = color[3];
 	
-	draw_quad(0);
+	draw_quad();
 }
 
 void gfx_quads_text(float x, float y, float size, const char *text)
@@ -884,3 +899,36 @@ float gfx_pretty_text_width(float size, const char *text_, int length)
 
 	return w;
 }
+
+
+
+void gfx_lines_begin()
+{
+	dbg_assert(drawing == 0, "called begin twice");
+	drawing = DRAWING_LINES;
+	gfx_setcolor(1,1,1,1);
+}
+
+void gfx_lines_end()
+{
+	dbg_assert(drawing == DRAWING_LINES, "called end without begin");
+	flush();
+	drawing = 0;
+}
+
+void gfx_lines_draw(float x0, float y0, float x1, float y1)
+{
+	dbg_assert(drawing == DRAWING_LINES, "called draw without begin");
+	
+	vertices[num_vertices].pos.x = x0;
+	vertices[num_vertices].pos.y = y0;
+	vertices[num_vertices].tex = texture[0];
+	vertices[num_vertices].color = color[0];
+
+	vertices[num_vertices + 1].pos.x = x1;
+	vertices[num_vertices + 1].pos.y = y1;
+	vertices[num_vertices + 1].tex = texture[1];
+	vertices[num_vertices + 1].color = color[1];
+	
+	draw_line();
+}
diff --git a/src/engine/client/ui.c b/src/engine/client/ui.c
index c67b1b13..eb07e7b5 100644
--- a/src/engine/client/ui.c
+++ b/src/engine/client/ui.c
@@ -70,7 +70,7 @@ void ui_do_image(int texture, float x, float y, float w, float h)
     gfx_blend_normal();
     gfx_texture_set(texture);
     gfx_quads_begin();
-    gfx_quads_setcolor(1,1,1,1);
+    gfx_setcolor(1,1,1,1);
     gfx_quads_setsubset(
         0.0f, // startx
         0.0f, // starty
diff --git a/src/engine/interface.h b/src/engine/interface.h
index e1608cff..4bf25bd2 100644
--- a/src/engine/interface.h
+++ b/src/engine/interface.h
@@ -250,7 +250,7 @@ void gfx_quads_setrotation(float angle);
 		The color values are from 0.0 to 1.0.
 		The color is reset when <gfx_quads_begin> is called.
 */
-void gfx_quads_setcolorvertex(int i, float r, float g, float b, float a);
+void gfx_setcolorvertex(int i, float r, float g, float b, float a);
 
 /*
 	Function: gfx_quads_setcolor
@@ -266,7 +266,7 @@ void gfx_quads_setcolorvertex(int i, float r, float g, float b, float a);
 		The color values are from 0.0 to 1.0.
 		The color is reset when <gfx_quads_begin> is called.
 */
-void gfx_quads_setcolor(float r, float g, float b, float a);
+void gfx_setcolor(float r, float g, float b, float a);
 
 /*
 	Function: gfx_quads_setsubset
@@ -793,6 +793,10 @@ void gfx_getscreen(float *tl_x, float *tl_y, float *br_x, float *br_y);
 int gfx_memory_usage();
 void gfx_screenshot();
 
+void gfx_lines_begin();
+void gfx_lines_draw(float x0, float y0, float x1, float y1);
+void gfx_lines_end();
+
 /* server snap id */
 int snap_new_id();
 void snap_free_id(int id);
diff --git a/src/engine/protocol.h b/src/engine/protocol.h
index 81c0480c..1a41d66a 100644
--- a/src/engine/protocol.h
+++ b/src/engine/protocol.h
@@ -17,7 +17,6 @@ enum
 	/* sent by client */
 	NETMSG_ENTERGAME,
 	NETMSG_INPUT,
-	NETMSG_SNAPACK,
 	
 	/* sent by both */
 	NETMSG_ERROR,
diff --git a/src/engine/server/server.c b/src/engine/server/server.c
index edb3f644..293d4870 100644
--- a/src/engine/server/server.c
+++ b/src/engine/server/server.c
@@ -436,6 +436,11 @@ static void server_process_client_packet(NETPACKET *packet)
 		}
 		else if(msg == NETMSG_INPUT)
 		{
+			clients[cid].last_acked_snapshot = msg_unpack_int();
+			int64 tagtime;
+			if(snapstorage_get(&clients[cid].snapshots, clients[cid].last_acked_snapshot, &tagtime, 0) >= 0)
+				clients[cid].latency = (int)(((time_get()-tagtime)*1000)/time_freq());
+			
 			int tick = msg_unpack_int();
 			int size = msg_unpack_int();
 			int i;
@@ -460,13 +465,6 @@ static void server_process_client_packet(NETPACKET *packet)
 			clients[cid].current_input++;
 			clients[cid].current_input %= 200;
 		}
-		else if(msg == NETMSG_SNAPACK)
-		{
-			clients[cid].last_acked_snapshot = msg_unpack_int();
-			int64 tagtime;
-			if(snapstorage_get(&clients[cid].snapshots, clients[cid].last_acked_snapshot, &tagtime, 0) >= 0)
-				clients[cid].latency = (int)(((time_get()-tagtime)*1000)/time_freq());
-		}
 		else
 		{
 			dbg_msg("server", "strange message cid=%d msg=%d data_size=%d", cid, msg, packet->data_size);
diff --git a/src/engine/system.c b/src/engine/system.c
index f5696b91..6226d4d2 100644
--- a/src/engine/system.c
+++ b/src/engine/system.c
@@ -258,8 +258,12 @@ int64 time_get()
 	gettimeofday(&val, NULL);
 	return (int64)val.tv_sec*(int64)1000000+(int64)val.tv_usec;
 #elif defined(CONF_FAMILY_WINDOWS)
+	static int64 last = 0;
 	int64 t;
 	QueryPerformanceCounter((PLARGE_INTEGER)&t);
+	if(t<last) /* for some reason, QPC can return values in the past */
+		return last;
+	last = t;
 	return t;
 #else
 	#error not implemented
diff --git a/src/game/client/game_client.cpp b/src/game/client/game_client.cpp
index 42bd119c..178330c2 100644
--- a/src/game/client/game_client.cpp
+++ b/src/game/client/game_client.cpp
@@ -138,38 +138,6 @@ 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;
-	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;
-			affected++;
-		}
-
-		if(col_check_point(pos.x, pos.y + vel.y))
-		{
-			inout_vel->y *= -elasticity;
-			affected++;
-		}
-		
-		if(affected == 0)
-		{
-			inout_vel->x *= -elasticity;
-			inout_vel->y *= -elasticity;
-		}
-	}
-	else
-	{
-		*inout_pos = pos + vel;
-	}
-}*/
-
 class damage_indicators
 {
 public:
@@ -238,7 +206,7 @@ public:
 				destroy_i(&items[i]);
 			else
 			{
-				gfx_quads_setcolor(1.0f,1.0f,1.0f, items[i].life/0.1f);
+				gfx_setcolor(1.0f,1.0f,1.0f, items[i].life/0.1f);
 				gfx_quads_setrotation(items[i].startangle + items[i].life * 2.0f);
 				select_sprite(SPRITE_STAR1);
 				draw_sprite(pos.x, pos.y, 48.0f);
@@ -341,7 +309,7 @@ public:
 			
 			gfx_quads_setrotation(particles[i].rot);
 			
-			gfx_quads_setcolor(
+			gfx_setcolor(
 				data->particles[type].color_r,
 				data->particles[type].color_g,
 				data->particles[type].color_b,
@@ -461,7 +429,7 @@ static void render_loading(float percent)
 	
 	gfx_texture_set(-1);
 	gfx_quads_begin();
-	gfx_quads_setcolor(0,0,0,0.50f);
+	gfx_setcolor(0,0,0,0.50f);
 	draw_round_rect(x, y, w, h, 40.0f);
 	gfx_quads_end();
 
@@ -472,7 +440,7 @@ static void render_loading(float percent)
 
 	gfx_texture_set(-1);
 	gfx_quads_begin();
-	gfx_quads_setcolor(1,1,1,1.0f);
+	gfx_setcolor(1,1,1,1.0f);
 	draw_round_rect(x+40, y+h-75, (w-80)*percent, 25, 5.0f);
 	gfx_quads_end();
 
@@ -870,7 +838,7 @@ static void render_flag(const obj_flag *prev, const obj_flag *current)
 	pos.x += cosf(client_localtime()*2.0f+offset)*2.5f;
 	pos.y += sinf(client_localtime()*2.0f+offset)*2.5f;
 
-    gfx_quads_setcolor(current->team ? 1 : 0,0,current->team ? 0 : 1,1);
+    gfx_setcolor(current->team ? 1 : 0,0,current->team ? 0 : 1,1);
 	gfx_quads_setsubset(
 		0, // startx
 		0, // starty
@@ -1338,7 +1306,7 @@ static void render_player(const obj_player *prev_obj, const obj_player *player_o
 					p += dir * data->weapons[iw].muzzleoffsetx + diry * offsety;
 
 					draw_sprite(p.x, p.y, data->weapons[iw].visual_size);
-					/*gfx_quads_setcolor(1.0f,1.0f,1.0f,alpha);
+					/*gfx_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);*/
@@ -1399,7 +1367,7 @@ static void render_player(const obj_player *prev_obj, const obj_player *player_o
 
 		gfx_quads_setrotation(pi/6*wiggle_angle);
 
-		gfx_quads_setcolor(1.0f,1.0f,1.0f,a);
+		gfx_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);
 		gfx_quads_draw(position.x, position.y - 23 - 32*h, 64, 64*h);
@@ -1415,7 +1383,7 @@ void render_sun(float x, float y)
 	gfx_blend_additive();
 	gfx_quads_begin();
 	const int rays = 10;
-	gfx_quads_setcolor(1.0f,1.0f,1.0f,0.025f);
+	gfx_setcolor(1.0f,1.0f,1.0f,0.025f);
 	for(int r = 0; r < rays; r++)
 	{
 		float a = r/(float)rays + client_localtime()*0.025f;
@@ -1423,10 +1391,10 @@ void render_sun(float x, float y)
 		vec2 dir0(sinf((a-size)*pi*2.0f), cosf((a-size)*pi*2.0f));
 		vec2 dir1(sinf((a+size)*pi*2.0f), cosf((a+size)*pi*2.0f));
 		
-		gfx_quads_setcolorvertex(0, 1.0f,1.0f,1.0f,0.025f);
-		gfx_quads_setcolorvertex(1, 1.0f,1.0f,1.0f,0.025f);
-		gfx_quads_setcolorvertex(2, 1.0f,1.0f,1.0f,0.0f);
-		gfx_quads_setcolorvertex(3, 1.0f,1.0f,1.0f,0.0f);
+		gfx_setcolorvertex(0, 1.0f,1.0f,1.0f,0.025f);
+		gfx_setcolorvertex(1, 1.0f,1.0f,1.0f,0.025f);
+		gfx_setcolorvertex(2, 1.0f,1.0f,1.0f,0.0f);
+		gfx_setcolorvertex(3, 1.0f,1.0f,1.0f,0.0f);
 		const float range = 1000.0f;
 		gfx_quads_draw_freeform(
 			pos.x+dir0.x, pos.y+dir0.y,
@@ -1497,7 +1465,7 @@ int emoticon_selector_render()
 	
 	gfx_texture_set(-1);
 	gfx_quads_begin();
-	gfx_quads_setcolor(0,0,0,0.3f);
+	gfx_setcolor(0,0,0,0.3f);
 	draw_circle(200, 150, 80, 64);
 	gfx_quads_end();
 
@@ -1524,7 +1492,7 @@ int emoticon_selector_render()
 
     gfx_texture_set(data->images[IMAGE_CURSOR].id);
     gfx_quads_begin();
-    gfx_quads_setcolor(1,1,1,1);
+    gfx_setcolor(1,1,1,1);
     gfx_quads_drawTL(emoticon_selector_mouse.x+200,emoticon_selector_mouse.y+150,12,12);
     gfx_quads_end();
 
@@ -1550,7 +1518,7 @@ void render_scoreboard(obj_game *gameobj, float x, float y, float w, int team, c
 	
 	gfx_texture_set(-1);
 	gfx_quads_begin();
-	gfx_quads_setcolor(0,0,0,0.5f);
+	gfx_setcolor(0,0,0,0.5f);
 	draw_round_rect(x-10.f, y-10.f, w, h, 40.0f);
 	gfx_quads_end();
 	
@@ -1620,7 +1588,7 @@ void render_scoreboard(obj_game *gameobj, float x, float y, float w, int team, c
 			// background so it's easy to find the local player
 			gfx_texture_set(-1);
 			gfx_quads_begin();
-			gfx_quads_setcolor(1,1,1,0.25f);
+			gfx_setcolor(1,1,1,0.25f);
 			draw_round_rect(x, y, w-20, 48, 20.0f);
 			gfx_quads_end();
 		}
diff --git a/src/game/client/menu.cpp b/src/game/client/menu.cpp
index a307d176..43b11142 100644
--- a/src/game/client/menu.cpp
+++ b/src/game/client/menu.cpp
@@ -61,7 +61,7 @@ void draw_area(gui_tileset_enum tileset, int areax, int areay, int areaw, int ar
     gfx_blend_normal();
     gfx_texture_set(data->images[IMAGE_GUI_WIDGETS].id);
     gfx_quads_begin();
-    gfx_quads_setcolor(1,1,1,1);
+    gfx_setcolor(1,1,1,1);
 	gfx_quads_setsubset(
 		ts_x, // startx
 		ts_y, // starty
@@ -180,7 +180,7 @@ void draw_background(float t)
 
     gfx_texture_set(data->images[IMAGE_MENU_BUTTERFLY].id);
     gfx_quads_begin();
-    gfx_quads_setcolor(1, 1, 1, 1);
+    gfx_setcolor(1, 1, 1, 1);
 	gfx_quads_setsubset(
 		flip ? (frame + 1) * 0.25f : frame * 0.25f, // startx
 		0.0f, // starty
@@ -983,7 +983,7 @@ static int settings_render(bool ingame)
 		
 		gfx_texture_set(-1);
 		gfx_quads_begin();
-		gfx_quads_setcolor(0,0,0,0.5f);
+		gfx_setcolor(0,0,0,0.5f);
 		draw_round_rect(10, 120, 780, 460, 30.0f);
 		gfx_quads_end();
 	}
@@ -1066,7 +1066,7 @@ static int ingame_main_render()
 	
 	gfx_texture_set(-1);
 	gfx_quads_begin();
-	gfx_quads_setcolor(0,0,0,0.5f);
+	gfx_setcolor(0,0,0,0.5f);
 	draw_round_rect(170, 120, 460, 360, 30.0f);
 	gfx_quads_end();
 	
@@ -1179,7 +1179,7 @@ static int kerning_render()
     	gfx_blend_normal();
     	gfx_texture_set(-1);
     	gfx_quads_begin();
-    	gfx_quads_setcolor(0,0,0,0.5);
+    	gfx_setcolor(0,0,0,0.5);
     	gfx_quads_drawTL(700,35*i+20,1,30);
 		gfx_quads_drawTL(700+45*(current_font->m_CharEndTable[(int)s[0]]-current_font->m_CharStartTable[(int)s[0]]),35*i+20,1,30);
     	gfx_quads_end();
@@ -1281,7 +1281,7 @@ int render_popup(const char *caption, const char *text, const char *button_text)
 	
 	gfx_texture_set(-1);
 	gfx_quads_begin();
-	gfx_quads_setcolor(0,0,0,0.50f);
+	gfx_setcolor(0,0,0,0.50f);
 	draw_round_rect(x, y, w, h, 40.0f);
 	gfx_quads_end();
 
@@ -1435,7 +1435,7 @@ extern "C" int modmenu_render(int ingame) // TODO: nastyness
 
     gfx_texture_set(data->images[IMAGE_CURSOR].id);
     gfx_quads_begin();
-    gfx_quads_setcolor(1,1,1,1);
+    gfx_setcolor(1,1,1,1);
     gfx_quads_drawTL(mx,my,24,24);
     gfx_quads_end();
 
diff --git a/src/tools/crapnet.cpp b/src/tools/crapnet.cpp
index 1c5e3dc3..a12b4ba4 100644
--- a/src/tools/crapnet.cpp
+++ b/src/tools/crapnet.cpp
@@ -100,10 +100,10 @@ int run(int port, NETADDR4 dest)
 				net_udp4_send(socket, &p->send_to, p->data, p->data_size);
 				
 				// update lag
-				double flux = 0; //rand()/(double)RAND_MAX;
+				double flux = rand()/(double)RAND_MAX;
 				int ms_spike = 0;
-				int ms_flux = 00;
-				int ms_ping = 100;
+				int ms_flux = 50;
+				int ms_ping = 50;
 				current_latency = ((time_freq()*ms_ping)/1000) + (int64)(((time_freq()*ms_flux)/1000)*flux); // 50ms
 				
 				if(ms_spike && (p->id%100) == 0)