about summary refs log tree commit diff
diff options
context:
space:
mode:
authorMagnus Auvinen <magnus.auvinen@gmail.com>2008-09-23 07:43:41 +0000
committerMagnus Auvinen <magnus.auvinen@gmail.com>2008-09-23 07:43:41 +0000
commit33b50738e63a3c79861bcfd88cb39377f85776c4 (patch)
treea7857f0b219e02337da6d8a1a6b66693760b9b6e
parente21b6983abaefd0037435c76e9b41cfbbfbe51d5 (diff)
downloadzcatch-33b50738e63a3c79861bcfd88cb39377f85776c4.tar.gz
zcatch-33b50738e63a3c79861bcfd88cb39377f85776c4.zip
added dead reckoning to the characters
-rw-r--r--datasrc/network.py5
-rw-r--r--src/base/system.h4
-rw-r--r--src/engine/client/ec_client.c6
-rw-r--r--src/engine/e_if_client.h5
-rw-r--r--src/game/client/components/players.cpp44
-rw-r--r--src/game/client/gameclient.cpp183
-rw-r--r--src/game/client/gameclient.hpp17
-rw-r--r--src/game/gamecore.cpp200
-rw-r--r--src/game/gamecore.hpp5
-rw-r--r--src/game/server/entities/character.cpp59
-rw-r--r--src/game/server/entities/character.hpp5
-rw-r--r--src/game/server/gamecontext.cpp17
-rw-r--r--src/game/server/gamecontext.hpp2
13 files changed, 332 insertions, 220 deletions
diff --git a/datasrc/network.py b/datasrc/network.py
index bb3545af..0f1d3496 100644
--- a/datasrc/network.py
+++ b/datasrc/network.py
@@ -105,12 +105,14 @@ Objects = [
 	]),
 
 	NetObject("Character_Core", [
+		NetIntAny("tick"),
 		NetIntAny("x"),
 		NetIntAny("y"),
 		NetIntAny("vx"),
 		NetIntAny("vy"),
 
 		NetIntAny("angle"),
+		NetIntRange("direction", -1, 1),
 		
 		NetIntRange("jumped", 0, 3),
 		NetIntRange("hooked_player", 0, 'MAX_CLIENTS-1'),
@@ -125,7 +127,6 @@ Objects = [
 
 	NetObject("Character:Character_Core", [
 		NetIntRange("player_state", 0, 'NUM_PLAYERSTATES-1'),
-		NetIntRange("wanted_direction", -1, 1),
 		NetIntRange("health", 0, 10),
 		NetIntRange("armor", 0, 10),
 		NetIntRange("ammocount", 0, 10),
@@ -138,7 +139,7 @@ Objects = [
 		NetIntRange("local", 0, 1),
 		NetIntRange("cid", 0, 'MAX_CLIENTS-1'),
 		NetIntRange("team", -1, 1),
-		
+
 		NetIntAny("score"),
 		NetIntAny("latency"),
 		NetIntAny("latency_flux"),
diff --git a/src/base/system.h b/src/base/system.h
index 772ebf91..49ab26d6 100644
--- a/src/base/system.h
+++ b/src/base/system.h
@@ -632,7 +632,9 @@ int net_tcp_send(NETSOCKET sock, const void *data, int size);
 		max_size - Maximum of data to write to the buffer.
 		
 	Returns:
-		Number of bytes recvived. Negative value on failure.
+		Number of bytes recvived. Negative value on failure. When in
+		non-blocking mode, it returns 0 when there is no more data to
+		be fetched.
 */
 int net_tcp_recv(NETSOCKET sock, void *data, int maxsize);
 
diff --git a/src/engine/client/ec_client.c b/src/engine/client/ec_client.c
index af7cd9d5..a1f898cb 100644
--- a/src/engine/client/ec_client.c
+++ b/src/engine/client/ec_client.c
@@ -83,6 +83,8 @@ static int current_tick = 0;
 static float intratick = 0;
 static float ticktime = 0;
 
+static int prev_tick = 0;
+
 /* predicted time */
 static int current_predtick = 0;
 static float predintratick = 0;
@@ -280,6 +282,7 @@ float client_intratick() { return intratick; }
 float client_predintratick() { return predintratick; }
 float client_ticktime() { return ticktime; }
 int client_tick() { return current_tick; }
+int client_prevtick() { return current_tick; }
 int client_predtick() { return current_predtick; }
 int client_tickspeed() { return SERVER_TICK_SPEED; }
 float client_frametime() { return frametime; }
@@ -1193,8 +1196,9 @@ static void client_update()
 					snapshots[SNAP_PREV] = snapshots[SNAP_CURRENT];
 					snapshots[SNAP_CURRENT] = next;
 					
-					/* set tick */
+					/* set ticks */
 					current_tick = snapshots[SNAP_CURRENT]->tick;
+					prev_tick = snapshots[SNAP_PREV]->tick;
 					
 					if(snapshots[SNAP_CURRENT] && snapshots[SNAP_PREV])
 					{
diff --git a/src/engine/e_if_client.h b/src/engine/e_if_client.h
index 91ea09b2..8badb4c6 100644
--- a/src/engine/e_if_client.h
+++ b/src/engine/e_if_client.h
@@ -116,6 +116,11 @@ typedef struct
 */
 int client_tick();
 
+/*
+	Function: client_prevtick
+		Returns the tick of the previous snapshot.
+*/
+int client_prevtick();
 
 /*
 	Function: client_intratick
diff --git a/src/game/client/components/players.cpp b/src/game/client/components/players.cpp
index 4e315f32..43ff3e77 100644
--- a/src/game/client/components/players.cpp
+++ b/src/game/client/components/players.cpp
@@ -158,7 +158,7 @@ void PLAYERS::render_player(
 
 	bool stationary = player.vx < 1 && player.vx > -1;
 	bool inair = col_check_point(player.x, player.y+16) == 0;
-	bool want_other_dir = (player.wanted_direction == -1 && vel.x > 0) || (player.wanted_direction == 1 && vel.x < 0);
+	bool want_other_dir = (player.direction == -1 && vel.x > 0) || (player.direction == 1 && vel.x < 0);
 
 	// evaluate animation
 	float walk_time = fmod(position.x, 100.0f)/100.0f;
@@ -194,8 +194,8 @@ void PLAYERS::render_player(
 		}
 		
 		gameclient.effects->skidtrail(
-			position+vec2(-player.wanted_direction*6,12),
-			vec2(-player.wanted_direction*100*length(vel),-50)
+			position+vec2(-player.direction*6,12),
+			vec2(-player.direction*100*length(vel),-50)
 		);
 	}
 
@@ -425,27 +425,27 @@ void PLAYERS::render_player(
 
 void PLAYERS::on_render()
 {
-	int num = snap_num_items(SNAP_CURRENT);
-	for(int i = 0; i < num; i++)
+	//int num = snap_num_items(SNAP_CURRENT);
+	for(int i = 0; i < MAX_CLIENTS; i++)
 	{
-		SNAP_ITEM item;
-		const void *data = snap_get_item(SNAP_CURRENT, i, &item);
+		// only render active characters
+		if(!gameclient.snap.characters[i].active)
+			continue;
 
-		if(item.type == NETOBJTYPE_CHARACTER)
-		{
-			const void *prev = snap_find_item(SNAP_PREV, item.type, item.id);
-			const void *prev_info = snap_find_item(SNAP_PREV, NETOBJTYPE_PLAYER_INFO, item.id);
-			const void *info = snap_find_item(SNAP_CURRENT, NETOBJTYPE_PLAYER_INFO, item.id);
+		const void *prev_info = snap_find_item(SNAP_PREV, NETOBJTYPE_PLAYER_INFO, i);
+		const void *info = snap_find_item(SNAP_CURRENT, NETOBJTYPE_PLAYER_INFO, i);
 
-			if(prev && prev_info && info)
-			{
-				render_player(
-						(const NETOBJ_CHARACTER *)prev,
-						(const NETOBJ_CHARACTER *)data,
-						(const NETOBJ_PLAYER_INFO *)prev_info,
-						(const NETOBJ_PLAYER_INFO *)info
-					);
-			}
-		}
+		if(prev_info && info)
+		{
+			NETOBJ_CHARACTER prev_char = gameclient.snap.characters[i].prev;
+			NETOBJ_CHARACTER cur_char = gameclient.snap.characters[i].cur;
+
+			render_player(
+					&prev_char,
+					&cur_char,
+					(const NETOBJ_PLAYER_INFO *)prev_info,
+					(const NETOBJ_PLAYER_INFO *)info
+				);
+		}		
 	}
 }
diff --git a/src/game/client/gameclient.cpp b/src/game/client/gameclient.cpp
index fe9b0476..43627669 100644
--- a/src/game/client/gameclient.cpp
+++ b/src/game/client/gameclient.cpp
@@ -150,15 +150,14 @@ void GAMECLIENT::on_console_init()
 	// add the some console commands
 	MACRO_REGISTER_COMMAND("team", "", con_team, this);
 	MACRO_REGISTER_COMMAND("kill", "", con_kill, this);
-			
+	
+	// let all the other components register their console commands
 	for(int i = 0; i < all.num; i++)
 		all.components[i]->on_console_init();
 }
 
 void GAMECLIENT::on_init()
 {
-
-
 	// init all components
 	for(int i = 0; i < all.num; i++)
 		all.components[i]->on_init();
@@ -250,7 +249,6 @@ int GAMECLIENT::on_snapinput(int *data)
 	return controls->snapinput(data);
 }
 
-
 void GAMECLIENT::on_connected()
 {
 	layers_init();
@@ -479,14 +477,31 @@ void GAMECLIENT::process_events()
 	}
 }
 
+static void evolve(NETOBJ_CHARACTER *character, int tick)
+{
+	WORLD_CORE tempworld;
+	CHARACTER_CORE tempcore;
+	mem_zero(&tempcore, sizeof(tempcore));
+	tempcore.world = &tempworld;
+	tempcore.read(character);
+	//tempcore.input.direction = character->wanted_direction;
+	while(character->tick < tick)
+	{
+		character->tick++;
+		tempcore.tick(false);
+		tempcore.move();
+		tempcore.quantize();
+	}
+	
+	tempcore.write(character);
+}
+
 void GAMECLIENT::on_snapshot()
 {
 	// clear out the invalid pointers
 	mem_zero(&gameclient.snap, sizeof(gameclient.snap));
+	snap.local_cid = -1;
 
-	static int snapshot_count = 0;
-	snapshot_count++;
-	
 	// secure snapshot
 	{
 		int num = snap_num_items(SNAP_CURRENT);
@@ -517,11 +532,10 @@ void GAMECLIENT::on_snapshot()
 		}
 	}
 
-	// setup world view
+	// go trough all the items in the snapshot and gather the info we want
 	{
-		// 1. fetch local player
-		// 2. set him to the center
-		gameclient.snap.team_size[0] = gameclient.snap.team_size[1] = 0;
+		snap.team_size[0] = snap.team_size[1] = 0;
+		
 		int num = snap_num_items(SNAP_CURRENT);
 		for(int i = 0; i < num; i++)
 		{
@@ -532,88 +546,94 @@ void GAMECLIENT::on_snapshot()
 			{
 				const NETOBJ_PLAYER_INFO *info = (const NETOBJ_PLAYER_INFO *)data;
 				
-				gameclient.clients[info->cid].team = info->team;
+				clients[info->cid].team = info->team;
+				snap.player_infos[info->cid] = info;
 				
 				if(info->local)
 				{
-					gameclient.snap.local_info = info;
-					const void *data = snap_find_item(SNAP_CURRENT, NETOBJTYPE_CHARACTER, item.id);
-					if(data)
-					{
-						gameclient.snap.local_character = (const NETOBJ_CHARACTER *)data;
-						gameclient.local_character_pos = vec2(gameclient.snap.local_character->x, gameclient.snap.local_character->y);
-
-						const void *p = snap_find_item(SNAP_PREV, NETOBJTYPE_CHARACTER, item.id);
-						if(p)
-							gameclient.snap.local_prev_character = (NETOBJ_CHARACTER *)p;
-					}
+					snap.local_cid = item.id;
+					snap.local_info = info;
 					
 					if (info->team == -1)
-						gameclient.snap.spectate = true;
+						snap.spectate = true;
 				}
 				
 				// calculate team-balance
 				if(info->team != -1)
-					gameclient.snap.team_size[info->team]++;
+					snap.team_size[info->team]++;
 				
 			}
-			else if(item.type == NETOBJTYPE_GAME)
-				gameclient.snap.gameobj = (NETOBJ_GAME *)data;
-			else if(item.type == NETOBJTYPE_FLAG)
+			else if(item.type == NETOBJTYPE_CHARACTER)
 			{
-				gameclient.snap.flags[item.id%2] = (const NETOBJ_FLAG *)data;
+				const void *old = snap_find_item(SNAP_PREV, NETOBJTYPE_CHARACTER, item.id);
+				if(old)
+				{
+					snap.characters[item.id].active = true;
+					snap.characters[item.id].prev = *((const NETOBJ_CHARACTER *)old);
+					snap.characters[item.id].cur = *((const NETOBJ_CHARACTER *)data);
+					
+					// perform dead reckoning
+					if(snap.characters[item.id].prev.tick)
+						evolve(&snap.characters[item.id].prev, client_prevtick());
+					if(snap.characters[item.id].cur.tick)
+						evolve(&snap.characters[item.id].cur, client_tick());
+				}
 			}
+			else if(item.type == NETOBJTYPE_GAME)
+				snap.gameobj = (NETOBJ_GAME *)data;
+			else if(item.type == NETOBJTYPE_FLAG)
+				snap.flags[item.id%2] = (const NETOBJ_FLAG *)data;
+		}
+	}
+	
+	// setup local pointers
+	if(snap.local_cid >= 0)
+	{
+		SNAPSTATE::CHARACTERINFO *c = &snap.characters[snap.local_cid];
+		if(c->active)
+		{
+			snap.local_character = &c->cur;
+			snap.local_prev_character = &c->prev;
+			local_character_pos = vec2(snap.local_character->x, snap.local_character->y);
 		}
 	}
 
+	// update render info
 	for(int i = 0; i < MAX_CLIENTS; i++)
-		gameclient.clients[i].update_render_info();
-
+		clients[i].update_render_info();
 }
 
 void GAMECLIENT::on_predict()
 {
+	// store the previous values so we can detect prediction errors
 	CHARACTER_CORE before_prev_char = predicted_prev_char;
 	CHARACTER_CORE before_char = predicted_char;
 
+	// we can't predict without our own id or own character
+	if(snap.local_cid == -1 || !snap.characters[snap.local_cid].active)
+		return;
+
 	// repredict character
 	WORLD_CORE world;
 	world.tuning = tuning;
-	int local_cid = -1;
 
 	// search for players
-	for(int i = 0; i < snap_num_items(SNAP_CURRENT); i++)
+	for(int i = 0; i < MAX_CLIENTS; i++)
 	{
-		SNAP_ITEM item;
-		const void *data = snap_get_item(SNAP_CURRENT, i, &item);
-		int client_id = item.id;
-
-		if(item.type == NETOBJTYPE_CHARACTER)
-		{
-			const NETOBJ_CHARACTER *character = (const NETOBJ_CHARACTER *)data;
-			gameclient.clients[client_id].predicted.world = &world;
-			world.characters[client_id] = &gameclient.clients[client_id].predicted;
-
-			gameclient.clients[client_id].predicted.read(character);
-		}
-		else if(item.type == NETOBJTYPE_PLAYER_INFO)
-		{
-			const NETOBJ_PLAYER_INFO *info = (const NETOBJ_PLAYER_INFO *)data;
-			if(info->local)
-				local_cid = client_id;
-		}
+		if(!snap.characters[i].active)
+			continue;
+			
+		gameclient.clients[i].predicted.world = &world;
+		world.characters[i] = &gameclient.clients[i].predicted;
+		gameclient.clients[i].predicted.read(&snap.characters[i].cur);
 	}
 	
-	// we can't predict without our own id
-	if(local_cid == -1)
-		return;
-
 	// predict
 	for(int tick = client_tick()+1; tick <= client_predtick(); tick++)
 	{
 		// fetch the local
-		if(tick == client_predtick() && world.characters[local_cid])
-			predicted_prev_char = *world.characters[local_cid];
+		if(tick == client_predtick() && world.characters[snap.local_cid])
+			predicted_prev_char = *world.characters[snap.local_cid];
 		
 		// first calculate where everyone should move
 		for(int c = 0; c < MAX_CLIENTS; c++)
@@ -622,15 +642,17 @@ void GAMECLIENT::on_predict()
 				continue;
 
 			mem_zero(&world.characters[c]->input, sizeof(world.characters[c]->input));
-			if(local_cid == c)
+			if(snap.local_cid == c)
 			{
 				// apply player input
 				int *input = client_get_input(tick);
 				if(input)
 					world.characters[c]->input = *((NETOBJ_PLAYER_INPUT*)input);
+				world.characters[c]->tick(true);
 			}
+			else
+				world.characters[c]->tick(false);
 
-			world.characters[c]->tick();
 		}
 
 		// move all players and quantize their data
@@ -643,14 +665,15 @@ void GAMECLIENT::on_predict()
 			world.characters[c]->quantize();
 		}
 		
+		// check if we want to trigger effects
 		if(tick > last_new_predicted_tick)
 		{
 			last_new_predicted_tick = tick;
 			
-			if(local_cid != -1 && world.characters[local_cid])
+			if(snap.local_cid != -1 && world.characters[snap.local_cid])
 			{
-				vec2 pos = world.characters[local_cid]->pos;
-				int events = world.characters[local_cid]->triggered_events;
+				vec2 pos = world.characters[snap.local_cid]->pos;
+				int events = world.characters[snap.local_cid]->triggered_events;
 				if(events&COREEVENT_GROUND_JUMP) gameclient.sounds->play(SOUNDS::CHN_WORLD, SOUND_PLAYER_JUMP, 1.0f, pos);
 				if(events&COREEVENT_AIR_JUMP)
 				{
@@ -662,34 +685,26 @@ void GAMECLIENT::on_predict()
 				if(events&COREEVENT_HOOK_ATTACH_GROUND) gameclient.sounds->play(SOUNDS::CHN_WORLD, SOUND_HOOK_ATTACH_GROUND, 1.0f, pos);
 				//if(events&COREEVENT_HOOK_RETRACT) snd_play_random(CHN_WORLD, SOUND_PLAYER_JUMP, 1.0f, pos);
 			}
-
-
-			/*
-			dbg_msg("predict", "%d %d %d", tick,
-				(int)world.players[c]->pos.x, (int)world.players[c]->pos.y,
-				(int)world.players[c]->vel.x, (int)world.players[c]->vel.y);*/
 		}
 		
-		if(tick == client_predtick() && world.characters[local_cid])
-			predicted_char = *world.characters[local_cid];
+		if(tick == client_predtick() && world.characters[snap.local_cid])
+			predicted_char = *world.characters[snap.local_cid];
 	}
 	
-	if(config.debug && predicted_tick == client_predtick())
+	if(config.debug && config.cl_predict && predicted_tick == client_predtick())
 	{
-		if(predicted_char.pos.x != before_char.pos.x ||
-			predicted_char.pos.y != before_char.pos.y)
-		{
-			dbg_msg("client", "prediction error, (%d %d) (%d %d)", 
-				(int)before_char.pos.x, (int)before_char.pos.y,
-				(int)predicted_char.pos.x, (int)predicted_char.pos.y);
-		}
+		NETOBJ_CHARACTER_CORE before = {0}, now = {0};
+		before_char.write(&before);
+		predicted_char.write(&now);
 
-		if(predicted_prev_char.pos.x != before_prev_char.pos.x ||
-			predicted_prev_char.pos.y != before_prev_char.pos.y)
+		if(mem_comp(&before, &now, sizeof(NETOBJ_CHARACTER_CORE)) != 0)
 		{
-			dbg_msg("client", "prediction error, prev (%d %d) (%d %d)", 
-				(int)before_prev_char.pos.x, (int)before_prev_char.pos.y,
-				(int)predicted_prev_char.pos.x, (int)predicted_prev_char.pos.y);
+			dbg_msg("client", "prediction error");
+			for(unsigned i = 0; i < sizeof(NETOBJ_CHARACTER_CORE)/sizeof(int); i++)
+				if(((int *)&before)[i] != ((int *)&now)[i])
+				{
+					dbg_msg("", "\t%d %d %d", i, ((int *)&before)[i], ((int *)&now)[i]);
+				}
 		}
 	}
 	
@@ -753,8 +768,6 @@ void GAMECLIENT::send_kill(int client_id)
 	client_send_msg();
 }
 
-
-
 void GAMECLIENT::con_team(void *result, void *user_data)
 {
 	((GAMECLIENT*)user_data)->send_switch_team(console_arg_int(result, 0));
diff --git a/src/game/client/gameclient.hpp b/src/game/client/gameclient.hpp
index dc3532a7..4b397e86 100644
--- a/src/game/client/gameclient.hpp
+++ b/src/game/client/gameclient.hpp
@@ -56,9 +56,26 @@ public:
 
 		const NETOBJ_PLAYER_INFO *player_infos[MAX_CLIENTS];
 		const NETOBJ_PLAYER_INFO *info_by_score[MAX_CLIENTS];
+		
+		int local_cid;
 		int num_players;
 		int team_size[2];
 		bool spectate;
+		
+		//
+		struct CHARACTERINFO
+		{
+			bool active;
+			
+			// snapshots
+			NETOBJ_CHARACTER prev;
+			NETOBJ_CHARACTER cur;
+			
+			// interpolated position
+			vec2 position;
+		};
+		
+		CHARACTERINFO characters[MAX_CLIENTS];
 	};
 
 	SNAPSTATE snap;
diff --git a/src/game/gamecore.cpp b/src/game/gamecore.cpp
index 7c8dabe0..cc0f3748 100644
--- a/src/game/gamecore.cpp
+++ b/src/game/gamecore.cpp
@@ -179,18 +179,19 @@ void CHARACTER_CORE::reset()
 	triggered_events = 0;
 }
 
-void CHARACTER_CORE::tick()
+void CHARACTER_CORE::tick(bool use_input)
 {
 	float phys_size = 28.0f;
 	triggered_events = 0;
 	
+	// get ground state
 	bool grounded = false;
 	if(col_check_point((int)(pos.x+phys_size/2), (int)(pos.y+phys_size/2+5)))
 		grounded = true;
 	if(col_check_point((int)(pos.x-phys_size/2), (int)(pos.y+phys_size/2+5)))
 		grounded = true;
 	
-	vec2 direction = normalize(vec2(input.target_x, input.target_y));
+	vec2 target_direction = normalize(vec2(input.target_x, input.target_y));
 
 	vel.y += world->tuning.gravity;
 	
@@ -198,13 +199,72 @@ void CHARACTER_CORE::tick()
 	float accel = grounded ? world->tuning.ground_control_accel : world->tuning.air_control_accel;
 	float friction = grounded ? world->tuning.ground_friction : world->tuning.air_friction;
 	
-	// handle movement
-	if(input.direction < 0)
+	// handle input
+	if(use_input)
+	{
+		direction = input.direction;
+
+		// setup angle
+		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;
+			
+		angle = (int)(a*256.0f);
+
+		// handle jump
+		if(input.jump)
+		{
+			if(!(jumped&1))
+			{
+				if(grounded)
+				{
+					triggered_events |= COREEVENT_GROUND_JUMP;
+					vel.y = -world->tuning.ground_jump_impulse;
+					jumped |= 1;
+				}
+				else if(!(jumped&2))
+				{
+					triggered_events |= COREEVENT_AIR_JUMP;
+					vel.y = -world->tuning.air_jump_impulse;
+					jumped |= 3;
+				}
+			}
+		}
+		else
+			jumped &= ~1;
+
+		// handle hook
+		if(input.hook)
+		{
+			if(hook_state == HOOK_IDLE)
+			{
+				hook_state = HOOK_FLYING;
+				hook_pos = pos+target_direction*phys_size*1.5f;
+				hook_dir = target_direction;
+				hooked_player = -1;
+				hook_tick = 0;
+				triggered_events |= COREEVENT_HOOK_LAUNCH;
+			}		
+		}
+		else
+		{
+			hooked_player = -1;
+			hook_state = HOOK_IDLE;
+			hook_pos = pos;			
+		}		
+	}
+	
+	// add the speed modification according to players wanted direction
+	if(direction < 0)
 		vel.x = saturated_add(-max_speed, max_speed, vel.x, -accel);
-	if(input.direction > 0)
+	if(direction > 0)
 		vel.x = saturated_add(-max_speed, max_speed, vel.x, accel);
-		
-	if(input.direction == 0)
+	if(direction == 0)
 		vel.x *= friction;
 	
 	// handle jumping
@@ -213,64 +273,40 @@ void CHARACTER_CORE::tick()
 	if(grounded)
 		jumped &= ~2;
 	
-	if(input.jump)
+	// do hook
+	if(hook_state == HOOK_IDLE)
 	{
-		if(!(jumped&1))
-		{
-			if(grounded)
-			{
-				triggered_events |= COREEVENT_GROUND_JUMP;
-				vel.y = -world->tuning.ground_jump_impulse;
-				jumped |= 1;
-			}
-			else if(!(jumped&2))
-			{
-				triggered_events |= COREEVENT_AIR_JUMP;
-				vel.y = -world->tuning.air_jump_impulse;
-				jumped |= 3;
-			}
-		}
+		hooked_player = -1;
+		hook_state = HOOK_IDLE;
+		hook_pos = pos;
 	}
-	else
-		jumped &= ~1;
-	
-	// do hook
-	if(input.hook)
+	else if(hook_state >= HOOK_RETRACT_START && hook_state < HOOK_RETRACT_END)
 	{
-		if(hook_state == HOOK_IDLE)
-		{
-			hook_state = HOOK_FLYING;
-			hook_pos = pos+direction*phys_size*1.5f;
-			hook_dir = direction;
-			hooked_player = -1;
-			hook_tick = 0;
-			triggered_events |= COREEVENT_HOOK_LAUNCH;
-		}
-		else if(hook_state >= HOOK_RETRACT_START && hook_state < HOOK_RETRACT_END)
-		{
-			hook_state++;
-		}
-		else if(hook_state == HOOK_RETRACT_END)
+		hook_state++;
+	}
+	else if(hook_state == HOOK_RETRACT_END)
+	{
+		hook_state = HOOK_RETRACTED;
+		triggered_events |= COREEVENT_HOOK_RETRACT;
+		hook_state = HOOK_RETRACTED;
+	}
+	else if(hook_state == HOOK_FLYING)
+	{
+		vec2 new_pos = hook_pos+hook_dir*world->tuning.hook_fire_speed;
+		if(distance(pos, new_pos) > world->tuning.hook_length)
 		{
-			hook_state = HOOK_RETRACTED;
-			triggered_events |= COREEVENT_HOOK_RETRACT;
-			hook_state = HOOK_RETRACTED;
+			hook_state = HOOK_RETRACT_START;
+			new_pos = pos + normalize(new_pos-pos) * world->tuning.hook_length;
 		}
-		else if(hook_state == HOOK_FLYING)
-		{
-			vec2 new_pos = hook_pos+hook_dir*world->tuning.hook_fire_speed;
-			if(distance(pos, new_pos) > world->tuning.hook_length)
-			{
-				hook_state = HOOK_RETRACT_START;
-				new_pos = pos + normalize(new_pos-pos) * world->tuning.hook_length;
-			}
-			
-			// make sure that the hook doesn't go though the ground
-			bool going_to_hit_ground = false;
-			if(col_intersect_line(hook_pos, new_pos, &new_pos))
-				going_to_hit_ground = true;
+		
+		// make sure that the hook doesn't go though the ground
+		bool going_to_hit_ground = false;
+		if(col_intersect_line(hook_pos, new_pos, &new_pos))
+			going_to_hit_ground = true;
 
-			// Check against other players first
+		// Check against other players first
+		if(world)
+		{
 			for(int i = 0; i < MAX_CLIENTS; i++)
 			{
 				CHARACTER_CORE *p = world->characters[i];
@@ -286,27 +322,20 @@ void CHARACTER_CORE::tick()
 					break;
 				}
 			}
-			
-			if(hook_state == HOOK_FLYING)
+		}
+		
+		if(hook_state == HOOK_FLYING)
+		{
+			// check against ground
+			if(going_to_hit_ground)
 			{
-				// check against ground
-				if(going_to_hit_ground)
-				{
-					triggered_events |= COREEVENT_HOOK_ATTACH_GROUND;
-					hook_state = HOOK_GRABBED;
-				}
-				
-				hook_pos = new_pos;
+				triggered_events |= COREEVENT_HOOK_ATTACH_GROUND;
+				hook_state = HOOK_GRABBED;
 			}
+			
+			hook_pos = new_pos;
 		}
 	}
-	else
-	{
-		//release_hooked();
-		hooked_player = -1;
-		hook_state = HOOK_IDLE;
-		hook_pos = pos;
-	}
 	
 	if(hook_state == HOOK_GRABBED)
 	{
@@ -339,7 +368,7 @@ void CHARACTER_CORE::tick()
 			
 			// 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.direction < 0) || (hookvel.x > 0 && input.direction > 0)) 
+			if((hookvel.x < 0 && direction < 0) || (hookvel.x > 0 && direction > 0)) 
 				hookvel.x *= 0.95f;
 			else
 				hookvel.x *= 0.75f;
@@ -362,7 +391,7 @@ void CHARACTER_CORE::tick()
 		}
 	}
 	
-	if(true)
+	if(world)
 	{
 		for(int i = 0; i < MAX_CLIENTS; i++)
 		{
@@ -437,17 +466,8 @@ void CHARACTER_CORE::write(NETOBJ_CHARACTER_CORE *obj_core)
 	obj_core->hook_dy = (int)(hook_dir.y*256.0f);
 	obj_core->hooked_player = hooked_player;
 	obj_core->jumped = jumped;
-
-	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;
-		
-	obj_core->angle = (int)(a*256.0f);
+	obj_core->direction = direction;
+	obj_core->angle = angle;
 }
 
 void CHARACTER_CORE::read(const NETOBJ_CHARACTER_CORE *obj_core)
@@ -464,6 +484,8 @@ void CHARACTER_CORE::read(const NETOBJ_CHARACTER_CORE *obj_core)
 	hook_dir.y = obj_core->hook_dy/256.0f;
 	hooked_player = obj_core->hooked_player;
 	jumped = obj_core->jumped;
+	direction = obj_core->direction;
+	angle = obj_core->angle;
 }
 
 void CHARACTER_CORE::quantize()
diff --git a/src/game/gamecore.hpp b/src/game/gamecore.hpp
index 1f9dda81..08634909 100644
--- a/src/game/gamecore.hpp
+++ b/src/game/gamecore.hpp
@@ -137,12 +137,15 @@ public:
 	int hooked_player;
 	
 	int jumped;
+	
+	int direction;
+	int angle;
 	NETOBJ_PLAYER_INPUT input;
 	
 	int triggered_events;
 	
 	void reset();
-	void tick();
+	void tick(bool use_input);
 	void move();
 	
 	void read(const NETOBJ_CHARACTER_CORE *obj_core);
diff --git a/src/game/server/entities/character.cpp b/src/game/server/entities/character.cpp
index 463b6640..362eecf0 100644
--- a/src/game/server/entities/character.cpp
+++ b/src/game/server/entities/character.cpp
@@ -34,7 +34,8 @@ static INPUT_COUNT count_input(int prev, int cur)
 // player
 CHARACTER::CHARACTER()
 : ENTITY(NETOBJTYPE_CHARACTER)
-{}
+{
+}
 
 void CHARACTER::reset()
 {
@@ -59,6 +60,10 @@ bool CHARACTER::spawn(PLAYER *player, vec2 pos, int team)
 	core.world = &game.world.core;
 	core.pos = pos;
 	game.world.core.characters[player->client_id] = &core;
+
+	reckoning_tick = 0;
+	mem_zero(&sendcore, sizeof(sendcore));
+	mem_zero(&reckoningcore, sizeof(reckoningcore));
 	
 	game.world.insert_entity(this);
 	alive = true;
@@ -578,7 +583,7 @@ void CHARACTER::tick()
 	//core.pos = pos;
 	//core.jumped = jumped;
 	core.input = input;
-	core.tick();
+	core.tick(true);
 
 	// handle weapons
 	handle_weapons();
@@ -592,6 +597,16 @@ void CHARACTER::tick()
 
 void CHARACTER::tick_defered()
 {
+	// advance the dummy
+	{
+		WORLD_CORE tempworld;
+		reckoningcore.world = &tempworld;
+		reckoningcore.tick(false);
+		reckoningcore.move();
+		reckoningcore.quantize();
+	}
+	
+	//lastsentcore;
 	/*if(!dead)
 	{*/
 		vec2 start_pos = core.pos;
@@ -642,6 +657,23 @@ void CHARACTER::tick_defered()
 		pos.x = input.target_x;
 		pos.y = input.target_y;
 	}
+	
+	// update the sendcore if needed
+	{
+		NETOBJ_CHARACTER predicted;
+		NETOBJ_CHARACTER current;
+		mem_zero(&predicted, sizeof(predicted));
+		mem_zero(&current, sizeof(current));
+		reckoningcore.write(&predicted);
+		core.write(&current);
+		
+		if(mem_comp(&predicted, &current, sizeof(NETOBJ_CHARACTER)) != 0)
+		{
+			reckoning_tick = server_tick();
+			sendcore = core;
+			reckoningcore = core;
+		}
+	}
 }
 
 bool CHARACTER::increase_health(int amount)
@@ -784,16 +816,12 @@ void CHARACTER::snap(int snaping_client)
 		return;
 	
 	NETOBJ_CHARACTER *character = (NETOBJ_CHARACTER *)snap_new_item(NETOBJTYPE_CHARACTER, player->client_id, sizeof(NETOBJ_CHARACTER));
-
-	core.write(character);
-
-	// this is to make sure that players that are just standing still
-	// isn't sent. this is because the physics keep bouncing between
-	// 0-128 when just standing.
-	// TODO: fix the physics so this isn't needed
-	if(snaping_client != player->client_id && abs(character->vy) < 256.0f)
-		character->vy = 0;
-
+	
+	// write down the core
+	character->tick = reckoning_tick;
+	sendcore.write(character);
+	
+	// set emote
 	if (emote_stop < server_tick())
 	{
 		emote_type = EMOTE_NORMAL;
@@ -809,12 +837,7 @@ void CHARACTER::snap(int snaping_client)
 	character->weapon = active_weapon;
 	character->attacktick = attack_tick;
 
-	character->wanted_direction = input.direction;
-	/*
-	if(input.left && !input.right)
-		character->wanted_direction = -1;
-	else if(!input.left && input.right)
-		character->wanted_direction = 1;*/
+	character->direction = input.direction;
 
 	if(player->client_id == snaping_client)
 	{
diff --git a/src/game/server/entities/character.hpp b/src/game/server/entities/character.hpp
index 2536bb79..e9909c96 100644
--- a/src/game/server/entities/character.hpp
+++ b/src/game/server/entities/character.hpp
@@ -79,6 +79,11 @@ public:
 
 	// the player core for the physics	
 	CHARACTER_CORE core;
+	
+	// info for dead reckoning
+	int reckoning_tick; // tick that we are performing dead reckoning from
+	CHARACTER_CORE sendcore; // core that we should send
+	CHARACTER_CORE reckoningcore; // the dead reckoning core
 
 	//
 	CHARACTER();
diff --git a/src/game/server/gamecontext.cpp b/src/game/server/gamecontext.cpp
index dfea7e91..0f81167d 100644
--- a/src/game/server/gamecontext.cpp
+++ b/src/game/server/gamecontext.cpp
@@ -1,3 +1,4 @@
+#include <new>
 #include <engine/e_server_interface.h>
 #include "gamecontext.hpp"
 
@@ -5,14 +6,28 @@ GAMECONTEXT game;
 
 GAMECONTEXT::GAMECONTEXT()
 {
-	clear();
+	for(int i = 0; i < MAX_CLIENTS; i++)
+		players[i].init(-1);
+}
+
+GAMECONTEXT::~GAMECONTEXT()
+{
 }
 
 void GAMECONTEXT::clear()
 {
+	this->~GAMECONTEXT();
+	mem_zero(this, sizeof(*this));
+	new (this) GAMECONTEXT();
 	// reset all players
+	/*
 	for(int i = 0; i < MAX_CLIENTS; i++)
 		players[i].init(-1);
+	
+	world.~GAMEWORLD();
+	mem_zero(&world, sizeof(world));
+	world.GAMEWORLD();
+	*/
 }
 
 
diff --git a/src/game/server/gamecontext.hpp b/src/game/server/gamecontext.hpp
index 85414183..4ef495fd 100644
--- a/src/game/server/gamecontext.hpp
+++ b/src/game/server/gamecontext.hpp
@@ -32,6 +32,8 @@ class GAMECONTEXT
 {
 public:
 	GAMECONTEXT();
+	~GAMECONTEXT();
+	
 	void clear();
 	
 	EVENTHANDLER events;