about summary refs log tree commit diff
diff options
context:
space:
mode:
authorMagnus Auvinen <magnus.auvinen@gmail.com>2007-07-13 13:40:04 +0000
committerMagnus Auvinen <magnus.auvinen@gmail.com>2007-07-13 13:40:04 +0000
commit125d04e51f4e444a38cf038d3ea095d92d3c6dbb (patch)
tree2288bbe4b923ab4d695e9f852c177a12f74ea799
parent7be0ae1b2929a3c5dfedf542bce886deefa0f862 (diff)
downloadzcatch-125d04e51f4e444a38cf038d3ea095d92d3c6dbb.tar.gz
zcatch-125d04e51f4e444a38cf038d3ea095d92d3c6dbb.zip
large rewrite and code cleanup
-rw-r--r--datasrc/client.dts80
-rw-r--r--datasrc/server.dts5
-rw-r--r--datasrc/teewars.ds415
-rw-r--r--datasrc/teewars.dsd24
-rw-r--r--default.bam70
-rwxr-xr-xscripts/compiler.py598
-rw-r--r--src/crapnet/crapnet.cpp126
-rw-r--r--src/engine/client/client.cpp514
-rw-r--r--src/engine/compression.cpp (renamed from src/engine/lzw.cpp)147
-rw-r--r--src/engine/compression.h12
-rw-r--r--src/engine/config.cpp2
-rw-r--r--src/engine/config_variables.h7
-rw-r--r--src/engine/interface.h49
-rw-r--r--src/engine/lzw.h2
-rw-r--r--src/engine/msg.cpp43
-rw-r--r--src/engine/network.cpp686
-rw-r--r--src/engine/network.h104
-rw-r--r--src/engine/packet.h626
-rw-r--r--src/engine/ringbuffer.h84
-rw-r--r--src/engine/server/server.cpp571
-rw-r--r--src/engine/snapshot.cpp235
-rw-r--r--src/engine/snapshot.h40
-rw-r--r--src/engine/versions.h2
-rw-r--r--src/game/client/game_client.cpp1559
-rw-r--r--src/game/client/mapres_tilemap.cpp12
-rw-r--r--src/game/client/menu.cpp9
-rw-r--r--src/game/game.h77
-rw-r--r--src/game/game_variables.h5
-rw-r--r--src/game/mapres_col.cpp25
-rw-r--r--src/game/mapres_col.h2
-rw-r--r--src/game/server/game_server.cpp1680
31 files changed, 4106 insertions, 3705 deletions
diff --git a/datasrc/client.dts b/datasrc/client.dts
new file mode 100644
index 00000000..12189231
--- /dev/null
+++ b/datasrc/client.dts
@@ -0,0 +1,80 @@
+struct image {
+	int id = 0
+	string filename = filename@1
+}
+
+struct spriteset {
+	ptr:image img = @1
+	int gridx = @2
+	int gridy = @3
+}
+
+struct sprite {
+	ptr:spriteset set = parent
+	int x = @1
+	int y = @2
+	int w = @3
+	int h = @4
+}
+
+struct sound {
+	int id = 0
+	string filename = @0
+}
+
+struct soundset {
+	int last = 0
+	array:sound sounds = *
+}
+
+struct particleinfo {
+	ptr:sprite spr = sprite@1
+	float color_r = color@1
+	float color_g = color@2
+	float color_b = color@3
+	float color_a = color@4
+	int lifemod = life@1
+}
+
+struct weapon {
+	ptr:sprite sprite_body = sprite_body@1
+	ptr:sprite sprite_cursor = sprite_cursor@1
+	ptr:sprite sprite_proj = sprite_proj@1
+	int recoil = recoil@1
+	int visual_size = visual_size@1
+}
+
+struct keyframe {
+	float time =  @0
+	float x = @1
+	float y = @2
+	float angle = @3
+}
+
+struct sequence {
+	array:keyframe frames = *
+}
+
+struct animation {
+	instance:sequence body = body
+	instance:sequence back_foot = back_foot
+	instance:sequence front_foot = front_foot
+	instance:sequence attach = attach
+}
+
+struct data_container {
+	array:image images = images.*
+	array:spriteset spritesets = sprites.*
+	array:sprite sprites = sprites.*.*
+	array:weapon weapons = weapons.*
+	array:particleinfo particles = particles.*
+
+	array:soundset sounds = sounds.*
+	array:animation animations = animations.*
+}
+
+const array:int weapon = weapons.*
+const array:int sound = sounds.*
+const array:int image = images.*
+const array:int sprite = sprites.*.*
+const array:int anim = animations.*
diff --git a/datasrc/server.dts b/datasrc/server.dts
new file mode 100644
index 00000000..41277b60
--- /dev/null
+++ b/datasrc/server.dts
@@ -0,0 +1,5 @@
+struct data_container {
+}
+
+const array:int sound = sounds.*
+const array:int weapon = weapons.*
diff --git a/datasrc/teewars.ds b/datasrc/teewars.ds
new file mode 100644
index 00000000..7e083d62
--- /dev/null
+++ b/datasrc/teewars.ds
@@ -0,0 +1,415 @@
+sounds {
+	gun_fire {
+		"data/audio/wp_gun_fire-01.wav"
+		"data/audio/wp_gun_fire-02.wav"
+		"data/audio/wp_gun_fire-03.wav"
+	}
+
+	shotgun_fire {
+		"data/audio/wp_shotty_fire-01.wav"
+		"data/audio/wp_shotty_fire-02.wav"
+		"data/audio/wp_shotty_fire-03.wav"
+	}
+	
+	rocket_fire {
+		"data/audio/wp_flump_launch-01.wav"
+		"data/audio/wp_flump_launch-02.wav"
+		"data/audio/wp_flump_launch-03.wav"
+	}
+	
+	hammer_fire {
+		"data/audio/wp_hammer_swing-01.wav"
+		"data/audio/wp_hammer_swing-02.wav"
+		"data/audio/wp_hammer_swing-03.wav"
+	}
+	
+	ninja_fire { 
+		"data/audio/wp_ninja_attack-01.wav"
+		"data/audio/wp_ninja_attack-02.wav"
+		"data/audio/wp_ninja_attack-03.wav"
+	}
+
+	rocket_explode {
+		"data/audio/wp_flump_explo-01.wav"
+		"data/audio/wp_flump_explo-02.wav"
+		"data/audio/wp_flump_explo-03.wav"
+	}
+	
+	ninja_hit {
+		"data/audio/wp_ninja_hit-01.wav"
+		"data/audio/wp_ninja_hit-02.wav"
+		"data/audio/wp_ninja_hit-03.wav"
+	}
+
+	weapon_switch { 
+		"data/audio/wp_switch-01.wav"
+		"data/audio/wp_switch-02.wav"
+		"data/audio/wp_switch-03.wav"
+	}
+
+	player_pain_short {
+		"data/audio/vo_teefault_pain_short-01.wav"
+		"data/audio/vo_teefault_pain_short-02.wav"
+		"data/audio/vo_teefault_pain_short-03.wav"
+		"data/audio/vo_teefault_pain_short-04.wav"
+		"data/audio/vo_teefault_pain_short-05.wav"
+		"data/audio/vo_teefault_pain_short-06.wav"
+		"data/audio/vo_teefault_pain_short-07.wav"
+		"data/audio/vo_teefault_pain_short-08.wav"
+		"data/audio/vo_teefault_pain_short-09.wav"
+		"data/audio/vo_teefault_pain_short-10.wav"
+		"data/audio/vo_teefault_pain_short-11.wav"
+		"data/audio/vo_teefault_pain_short-12.wav"
+	}
+
+	player_pain_long {
+		"data/audio/vo_teefault_pain_long-01.wav"
+		"data/audio/vo_teefault_pain_long-02.wav"
+	}
+
+	body_land {
+		"data/audio/foley_land-01.wav"
+		"data/audio/foley_land-02.wav"
+		"data/audio/foley_land-03.wav"
+		"data/audio/foley_land-04.wav"
+	}
+	
+	player_jump {
+		"data/audio/foley_foot_left-01.wav"
+		"data/audio/foley_foot_left-02.wav"
+		"data/audio/foley_foot_left-03.wav"
+		"data/audio/foley_foot_left-04.wav"
+		"data/audio/foley_foot_right-01.wav"
+		"data/audio/foley_foot_right-02.wav"
+		"data/audio/foley_foot_right-03.wav"
+		"data/audio/foley_foot_right-04.wav"
+	}
+
+	player_die {
+		"data/audio/foley_body_splat-02.wav"
+		"data/audio/foley_body_splat-03.wav"
+		"data/audio/foley_body_splat-04.wav"
+	}
+	
+	player_spawn {
+		"data/audio/vo_teefault_spawn-01.wav"
+		"data/audio/vo_teefault_spawn-02.wav"
+		"data/audio/vo_teefault_spawn-03.wav"
+		"data/audio/vo_teefault_spawn-04.wav"
+		"data/audio/vo_teefault_spawn-05.wav"
+		"data/audio/vo_teefault_spawn-06.wav"
+		"data/audio/vo_teefault_spawn-07.wav"
+	}
+
+	tee_cry {
+		"data/audio/vo_teefault_cry-01.wav"
+		"data/audio/vo_teefault_cry-02.wav"
+	}
+
+	hook_loop { 
+		"data/audio/hook_loop-01.wav"
+		"data/audio/hook_loop-02.wav"
+	}
+	
+	hook_attach {
+		"data/audio/hook_attach-01.wav"
+		"data/audio/hook_attach-02.wav"
+		"data/audio/hook_attach-03.wav"
+	}
+}
+
+
+images {
+	weapons {
+		filename "data/tileset_weapons.png"
+	}
+	
+	game {
+		filename "data/game_main.png"
+	}
+
+	particles {
+		filename "data/tileset_particles.png"
+	}
+
+	sun {
+		filename "data/sun.png"
+	}
+	
+	char_default {
+		filename "data/char_teefault.png"
+	}
+}
+
+particles {
+	part1 {
+		sprite sprites.particles.part1
+		color 0.7 0.7 0.7 1.0
+		life 50
+	}
+
+	part2 {
+		sprite sprites.particles.part2
+		color 1.0 1.0 1.0 1.0
+		life 50
+	}
+
+	part3 {
+		sprite sprites.particles.part3
+		color 0.8 0.8 0.8 1.0
+		life 50
+	}
+
+	part4 {
+		sprite sprites.particles.part4
+		color 0.98 0.1 0.16 1.0
+		life 70
+	}
+
+	part5 {
+		sprite sprites.particles.part5
+		color 1.0 1.0 1.0 1.0
+		life 70
+	}
+
+	part6 {
+		sprite sprites.particles.part6
+		color 0.6 0.6 0.6 1.0
+		life 100
+	}
+
+	part7 {
+		sprite sprites.particles.part7
+		color 1.0 1.0 1.0 1.0
+		life 100
+	}
+	
+	part8 {
+		sprite sprites.particles.part8
+		color 0.7 0.7 0.7 1.0
+		life 150
+	}
+	
+	part9 {
+		sprite sprites.particles.part9
+		color 1.0 1.0 1.0 1.0
+		life 40
+	}
+}
+
+
+weapons {
+	gun {
+		sprite_body sprites.weapons.weapon_gun_body
+		sprite_cursor sprites.weapons.weapon_gun_cursor
+		sprite_proj sprites.weapons.weapon_gun_proj
+
+		recoil 10
+		reloadtime 100
+		visual_size 64
+	}
+	
+	rocket {
+		sprite_body sprites.weapons.weapon_rocket_body
+		sprite_cursor sprites.weapons.weapon_rocket_cursor
+		sprite_proj sprites.weapons.weapon_rocket_proj
+		
+		recoil 10
+		reloadtime 600
+		visual_size 96
+	}
+	
+	shotgun {
+		sprite_body sprites.weapons.weapon_shotgun_body
+		sprite_cursor sprites.weapons.weapon_shotgun_cursor
+		sprite_proj sprites.weapons.weapon_shotgun_proj
+		
+		recoil 10
+		reloadtime 800
+		visual_size 96
+	}
+	
+	hammer {
+		sprite_body sprites.weapons.weapon_hammer_body
+		sprite_cursor sprites.weapons.weapon_hammer_cursor
+		sprite_proj sprites.weapons.weapon_hammer_proj
+		
+		recoil 10
+		reloadtime 100
+		visual_size 96
+	}
+}
+	
+sprites {
+
+	particles images.particles 16 16 {
+		part1 2 0 2 2
+		part2 4 0 2 2
+		part3 6 0 2 2
+		part4 8 0 2 2
+		part5 10 0 2 2
+		part6 2 2 2 2
+		part7 4 2 2 2
+		part8 6 2 2 2
+		part9 8 2 2 2
+		
+		star1 0 0 2 2
+		star2 0 2 2 2
+	}
+	
+	hud images.game 32 16 {
+		health_full 0 0 4 4
+		health_empty 5 0 4 4
+		armor_full 0 5 4 4
+		armor_empty 5 5 4 4
+	}
+	
+	weapons images.weapons 32 32 {
+		weapon_gun_body 2 4 4 2
+		weapon_gun_cursor 0 4 2 2
+		weapon_gun_proj 6 4 2 2
+		weapon_gun_muzzle1 8 4 3 2
+		weapon_gun_muzzle2 12 4 3 2
+		weapon_gun_muzzle3 16 4 3 2
+		
+		weapon_shotgun_body 2 6 8 2
+		weapon_shotgun_cursor 0 6 2 2
+		weapon_shotgun_proj 10 6 2 2
+		weapon_shotgun_muzzle1 12 6 3 2
+		weapon_shotgun_muzzle2 16 6 3 2
+		weapon_shotgun_muzzle3 20 6 3 2
+		
+		weapon_rocket_body 2 8 7 2
+		weapon_rocket_cursor 0 8 2 2
+		weapon_rocket_proj 10 8 2 2
+		
+		weapon_hammer_body 2 1 4 3
+		weapon_hammer_cursor 0 0 2 2
+		weapon_hammer_proj 0 0 0 0
+		
+		weapon_ninja_body 2 10 7 2
+		weapon_ninja_cursor 0 10 2 2
+		weapon_ninja_proj 0 0 0 0
+		
+		hook_chain 2 0 1 1
+		hook_head 3 0 2 1
+		
+		hadoken1 1 12 7 4
+		hadoken2 8 12 8 4
+		hadoken3 17 12 7 4
+	}
+
+	powerups images.weapons 32 32 {
+		powerup_health 10 2 2 2
+		powerup_armor 12 2 2 2
+		powerup_weapon 3 0 6 2
+		powerup_ninja 3 10 7 2
+		powerup_timefield 3 0 6 2
+	}
+	
+	tees images.char_default 16 64 {
+		tee_body 0 0 4 4
+		tee_body_outline 4 0 4 4
+		tee_foot 8 3 2 1
+		tee_foot_outline 8 2 2 1
+		
+		tee_eye_normal 10 2 1 1
+	}
+}
+
+animations {
+	base {
+		body {
+			0.0 0 -4 0
+		}
+		
+		back_foot {
+			0.0 0 10 0
+		}
+		
+		front_foot {
+			0.0 0 10 0
+		}
+		
+		attach {
+		}
+	}
+
+	idle {
+		body {
+		}
+		
+		back_foot {
+			0.0 -7 0 0
+		}
+		
+		front_foot {
+			0.0  7 0 0
+		}
+		
+		attach {
+			0.0  0 0 0
+		}
+	}
+	
+	inair {
+		body {
+		}
+		
+		back_foot {
+			0.0 -3 0 -0.1
+		}
+		
+		front_foot {
+			0.0 3 0 -0.1
+		}
+		
+		attach {
+		}	
+	}
+
+	walk {
+		body {
+		}
+		
+		front_foot {
+			0.0   8  0 0
+			0.2  -8  0 0
+			0.4 -10 -4 0.2
+			0.6  -8 -8 0.3
+			0.8   4 -4 -0.2
+			1.0   8  0 0
+		}
+	
+		back_foot {
+			0.0 -10 -4 0.2
+			0.2  -8 -8 0.3
+			0.4  -4 -4 -0.2
+			0.6   0  0 0
+			0.8  -8  0 0
+			1.0 -10 -4 0.2
+		}
+		
+		attach {
+		}
+	}
+	
+	hammer_swing {
+		body {
+		}
+		
+		front_foot {
+		}
+	
+		back_foot {
+		}
+		
+		attach {
+			0.0 0 0 -0.10
+			0.3 0 0 0.25
+			0.4 0 0 0.30
+			0.5 0 0 0.25
+			1.0 0 0 -0.10
+		}
+	}	
+}
diff --git a/datasrc/teewars.dsd b/datasrc/teewars.dsd
new file mode 100644
index 00000000..a95b960c
--- /dev/null
+++ b/datasrc/teewars.dsd
@@ -0,0 +1,24 @@
+tag:images {
+	ident:name * {
+		tag:filename string:filename
+	}
+}
+
+tag:sounds {
+	ident:name * {
+		tag:filename string:path
+	}
+}
+
+tag:weapons {
+	ident:name * {
+		tag:sprite_gun ptr:sprite
+		tag:sprite_cursor ptr:sprite
+	}
+}
+
+tag:sprites {
+	ident:name ptr:image int:gridx int:gridy * {
+		ident:name int:x int:y int:w int:h *
+	}
+}
diff --git a/default.bam b/default.bam
index 01128ae2..956dbbaf 100644
--- a/default.bam
+++ b/default.bam
@@ -31,7 +31,60 @@ function Copy(outputdir, ...)
 	return outputs
 end
 
---
+function dc_header(output, data, script)
+	print("dc_header " .. PathFilename(output) .. " = " .. PathFilename(data) .. " ~ " .. PathFilename(script))
+    return os.execute("scripts/compiler.py " .. data .. " " .. script .. " -h " .. output)
+end
+
+function dc_source(output, data, script)
+	print("dc_source " .. PathFilename(output) .. " = " .. PathFilename(data) .. " ~ " .. PathFilename(script))
+    return os.execute("scripts/compiler.py " .. data .. " " .. script .. " -s " .. output)
+end
+
+function dc_data(output, data, script)
+	print("dc_data " .. PathFilename(output) .. " = " .. PathFilename(data) .. " ~ " .. PathFilename(script))
+    return os.execute("scripts/compiler.py " .. data .. " " .. script .. " -d " .. output)
+end
+
+
+function DataCompile_Header(datafile, scriptfile, outputfile)
+	datafile = Path(datafile)
+	scriptfile = Path(scriptfile)
+	outputfile = Path(outputfile)
+	bam_add_job("dc_header", outputfile, datafile, scriptfile)
+	bam_add_dependency(outputfile, datafile)
+	bam_add_dependency(outputfile, scriptfile)
+	return outputfile
+end
+
+function DataCompile_Source(datafile, scriptfile, outputfile)
+	datafile = Path(datafile)
+	scriptfile = Path(scriptfile)
+	outputfile = Path(outputfile)
+	bam_add_job("dc_source", outputfile, datafile, scriptfile)
+	bam_add_dependency(outputfile, datafile)
+	bam_add_dependency(outputfile, scriptfile)
+	return outputfile
+end
+
+function DataCompile(datafile, scriptfile, headerfile, sourcefile, outputdatafile)
+	datafile = Path(datafile)
+	scriptfile = Path(scriptfile)
+	headerfile = Path(headerfile)
+	sourcefile = Path(sourcefile)
+	outputdatafile = Path(outputdatafile)
+	bam_add_job("dc_source", sourcefile, datafile, scriptfile)
+	bam_add_job("dc_header", headerfile, datafile, scriptfile)
+	bam_add_job("dc_data", outputdatafile, datafile, scriptfile)
+	bam_add_dependency(sourcefile, datafile)
+	bam_add_dependency(sourcefile, scriptfile)
+	bam_add_dependency(sourcefile, headerfile)
+	bam_add_dependency(headerfile, datafile)
+	bam_add_dependency(headerfile, scriptfile)
+	bam_add_dependency(outputdatafile, datafile)
+	bam_add_dependency(outputdatafile, scriptfile)
+	return {data = outputdatafile, header=headerfile, source=sourcefile}
+end
 
 --baselib = Import("src/baselib/baselib.bam")
 baselib = Import("../baselib/baselib.bam")
@@ -47,18 +100,25 @@ settings.cc.flags = "-Wall"
 settings.cc.includes:add("src")
 settings.cc.includes:add("../baselib/src/external/zlib")
 
+serverdata = DataCompile("datasrc/teewars.ds", "datasrc/server.dts", "src/game/server/data.h", "src/game/server/data/data.cpp", "data/server.dat")
+clientdata = DataCompile("datasrc/teewars.ds", "datasrc/client.dts", "src/game/client/data.h", "src/game/client/data/data.cpp", "data/client.dat")
+
 engine = Compile(settings, Collect("src/engine/*.cpp"))
 client = Compile(settings, Collect("src/engine/client/*.cpp", "src/engine/client/pnglite/*.c"))
 server = Compile(settings, Collect("src/engine/server/*.cpp"))
 game_shared = Compile(settings, Collect("src/game/*.cpp"))
-game_client = Compile(settings, Collect("src/game/client/*.cpp"))
-game_server = Compile(settings, Collect("src/game/server/*.cpp"))
+game_client = Compile(settings, Collect("src/game/client/*.cpp"), clientdata.source)
+game_server = Compile(settings, Collect("src/game/server/*.cpp"), serverdata.source)
 editor = Compile(settings, Collect("src/editor/*.cpp"))
 
+crapnet = Compile(settings, Collect("src/crapnet/*.cpp"))
+
 client_exe = Link(settings, "teewars", engine, client, game_shared, game_client)
 server_exe = Link(server_settings, "teewars_srv", engine, server, game_shared, game_server)
 -- editor_exe = Link(settings, "editor", engine, game_shared, editor)
+crapnet_exe = Link(server_settings, "crapnet", crapnet)
 
-Target(PseudoTarget("client", client_exe))
-Target(PseudoTarget("server", server_exe))
+Target(PseudoTarget("client", client_exe, clientdata.data))
+Target(PseudoTarget("server", server_exe, serverdata.data))
+Target(PseudoTarget("tools", crapnet_exe))
 -- Target(PseudoTarget("editor", editor_exe))
diff --git a/scripts/compiler.py b/scripts/compiler.py
new file mode 100755
index 00000000..be7e4ce0
--- /dev/null
+++ b/scripts/compiler.py
@@ -0,0 +1,598 @@
+#!/usr/bin/python
+
+import sys
+import struct
+
+option_ptrsize = struct.calcsize("P")
+option_intsize = struct.calcsize("l")
+option_floatsize = struct.calcsize("f")
+option_inttype = "long"
+option_floattype = "float"
+
+class node:
+	def __init__(self):
+		self.values = []
+		self.children = []
+		self.parent = 0
+		
+	def name(self):
+		if len(self.values):
+			return self.values[0]
+		return ""
+
+	def debug_print(self, level):
+		print ("  "*level) + " ".join(self.values),
+		if len(self.children):
+			print "{"
+			for c in self.children:
+				c.debug_print(level+1)
+			print ("  "*level)+"}"
+		else:
+			print ""
+		
+	def debug_root(self):
+		for c in self.children:
+			c.debug_print(0)
+
+	# TODO: should return list of items in the tree,
+	def gather(self, str):
+		def recurse(parts, path, node):
+			if not len(parts):
+				r = {}
+				path = path + "." + node.values[0]
+				r = [node]
+				#print "found", path
+				return r
+				
+			l = []
+			for c in node.children:
+				if parts[0] == "*" or c.values[0] == parts[0]:
+					if len(node.values):
+						if len(path):
+							l += recurse(parts[1:], path+"."+node.values[0], c)
+						else:
+							l += recurse(parts[1:], node.values[0], c)
+					else:
+						l += recurse(parts[1:], path, c)
+			return l
+		
+		parts = str.split(".")
+		return recurse(parts, "", self)	
+		
+	def find_node(self, str):
+		parts = str.split(".")
+		node = self
+		for part in parts:
+			if len(part) == 0:
+				continue
+			if part == "parent":
+				node = node.parent
+			else:
+				found = 0
+				for c in node.children:
+					if part == c.values[0]:
+						node = c
+						found = 1
+						break
+						
+		if node == self:
+			return
+		return node
+		
+	def get_single(self, str):
+		parts = str.split("@")
+		index = -1
+		if len(parts) == 2:
+			index = int(parts[1])
+		
+		node = self
+		if len(parts[0]):
+			node = self.find_node(parts[0])
+		
+		if not node:
+			print "failed to get", str
+			return Null
+		
+		if index == -1:
+			return node.get_path()[1:]
+		return node.values[index]
+
+	def get_path(self):
+		if self.parent == 0:
+			return ""
+		return self.parent.get_path() + "." + self.values[0]
+
+	def get_single_name(self, str):
+		return self.get_path()[1:] + "." + str
+
+class parser:
+	lines = []
+		
+	def parse_node(self, this_node):
+		while len(self.lines):
+			line = self.lines.pop(0)		# grab line
+				
+			fields = line.strip().split() # TODO: improve this to handle strings with spaces
+			if not len(fields):
+				continue
+			
+			if fields[-1] == '{':
+				new_node = node()
+				new_node.parent = this_node
+				new_node.values = fields[:-1]
+				this_node.children += [new_node]
+				self.parse_node(new_node)
+			elif fields[-1] == '}':
+				break
+			else:
+				new_node = node()
+				new_node.parent = this_node
+				new_node.values = fields
+				this_node.children += [new_node]
+
+	def parse_file(self, filename):
+		self.lines = file(filename).readlines()
+		n = node()
+		self.parse_node(n)
+		return n
+	
+def parse_file(filename):
+	return parser().parse_file(filename)
+
+class pointer:
+	def __init__(self, index, target):
+		self.index = index
+		self.target = target
+
+class data_constructor:
+	def __init__(self):
+		self.data = ""
+		self.trans = 0
+		self.pointers = []
+		self.targets = {}
+
+	def get_type(self, s):
+		return self.trans.types[s]
+
+	def allocate(self, size):
+		index = len(self.data)
+		self.data += "\0"*size
+		return index
+
+	def add_pointer(self, index, target):
+		self.pointers += [pointer(index, target)]
+
+	def add_target(self, target, index):
+		# TODO: warn about duplicates
+		#print "add_target(target='%s' index=%d)" % (target, index)
+		self.targets[target] = index
+	
+	def write(self, index, size, data):
+		try:
+			self.data = self.data[:index] + data + self.data[index+size:]
+		except:
+			print "write error:"
+			print "\tself.data =", self.data
+			print "\tdata =", data
+			
+	def patch_pointers(self):
+		for p in self.pointers:
+			if p.target in self.targets:
+				i = self.targets[p.target]
+				#print "ptr @ %d -> %s -> %d" % (p.index, p.target, i)
+				self.write(p.index, 8, struct.pack("P", i)) # TODO: fix me
+			else:
+				print "ERROR: couldn't find target '%s' for pointer at %d" % (p.target, p.index)
+
+class type:
+	def __init__(self):
+		self.name = ""
+		
+	def size(self):
+		pass
+
+class structure:
+	def __init__(self):
+		self.name = ""
+		self.members = []
+
+	def size(self):
+		s = 0
+		for m in self.members:
+			s += m.size()
+		return s
+	
+	def emit_header_code(self, out):
+		print >>out, "struct", self.name
+		print >>out, "{"
+		for m in self.members:
+			for l in m.get_code():
+				print >>out, "\t" + l
+		print >>out, "};"
+		print >>out, ""
+
+	def emit_source_code(self, out):
+		print >>out, "static void patch_ptr_%s(%s *self, char *base)" % (self.name, self.name)
+		print >>out, "{"
+		for m in self.members:
+			for l in m.get_patch_code("self", "base"):
+				print >>out, "\t" + l
+		print >>out, "}"
+		print >>out, ""
+
+	def emit_data(self, cons, index, src_data):
+		#print self.name+":"
+		member_index = index
+		for m in self.members:
+			#print "\t" + m.name
+			m.emit_data(cons, member_index, src_data)
+			member_index += m.size()
+
+class variable:
+	def __init__(self):
+		self.expr = ""
+		self.type = ""
+		self.subtype = ""
+	
+	def get_code(self):
+		return []
+
+	def get_patch_code(self, ptrname, basename):
+		return []
+	
+	def emit_data(self, cons, index, src_data):
+		pass
+
+class variable_int(variable):
+	def get_code(self):
+		return ["%s %s;" % (option_inttype, self.name)]
+	def size(self):
+		return option_intsize
+	def emit_data(self, cons, index, src_data):
+		try:
+			value = int(self.expr)
+		except:
+			value = int(src_data.get_single(self.expr))
+		#print "int", self.name, "=", value, "@", index
+		data = struct.pack("l", value)
+		cons.write(index, len(data), data)
+
+class variable_float(variable):
+	def get_code(self):
+		return ["%s %s;" % (option_floattype, self.name)]
+	def size(self):
+		return option_floatsize
+	def emit_data(self, cons, index, src_data):
+		try:
+			value = float(self.expr)
+		except:
+			value = float(src_data.get_single(self.expr))
+		#print "int", self.name, "=", value, "@", index
+		data = struct.pack("f", value)
+		cons.write(index, len(data), data)
+		
+class variable_string(variable):
+	def get_code(self):
+		return ["char *%s;" % (self.name)]
+	def get_patch_code(self, ptrname, basename):
+		return ["patch_ptr((char **)&(%s->%s), %s);" % (ptrname, self.name, basename)]
+	def size(self):
+		return option_ptrsize
+	def emit_data(self, cons, index, src_data):
+		string = src_data.get_single(self.expr)
+		string = string.strip()[1:-1] # skip " and "
+
+		string_index = cons.allocate(len(string)+1)
+		cons.write(string_index, len(string), string)
+
+		data = struct.pack("P", string_index) # TODO: solve this
+		cons.write(index, len(data), data)
+
+class variable_ptr(variable):
+	def get_code(self):
+		return ["%s *%s;" % (self.subtype, self.name)]
+	def get_patch_code(self, ptrname, basename):
+		return ["patch_ptr((char**)&(%s->%s), %s);" % (ptrname, self.name, basename)]
+	def size(self):
+		return option_ptrsize
+	def emit_data(self, cons, index, src_data):
+		target = src_data.get_single(self.expr)
+		cons.add_pointer(index, target)
+
+class variable_instance(variable):
+	def get_code(self):
+		return ["%s %s;" % (self.subtype, self.name)]
+	def get_patch_code(self, ptrname, basename):
+		return ["patch_ptr_%s(&(%s->%s), %s);" % (self.subtype, ptrname, self.name, basename)]
+	def size(self):
+		return translator.types[self.subtype].size()
+	def emit_data(self, cons, index, src_data):
+		print self.expr
+		target = src_data.find_node(self.expr)
+		print target
+		translator.types[self.subtype].emit_data(cons, index, target)
+		#target = 
+		#cons.add_pointer(index, target)
+
+class variable_array(variable):
+	def get_code(self):
+		return ["long num_%s;" % self.name,
+			"%s *%s;" % (self.subtype, self.name)]
+	def get_patch_code(self, ptrname, baseptr):
+		code = []
+		code += ["patch_ptr((char**)&(%s->%s), %s);" % (ptrname, self.name, baseptr)]
+		code += ["for(int i = 0; i < %s->num_%s; i++)" % (ptrname, self.name)]
+		code += ["\tpatch_ptr_%s(%s->%s+i, %s);" % (self.subtype, ptrname, self.name, baseptr)]
+		return code
+	def emit_data(self, cons, index, src_data):
+		array_data = src_data.gather(self.expr)
+		array_type = cons.get_type(self.subtype)
+		size = array_type.size()*len(array_data)
+
+		#print "packing array", self.name
+		#print "\ttype =", array_type.name
+		#print "\tsize =", array_type.size()
+		array_index = cons.allocate(size)
+		data = struct.pack("lP", len(array_data), array_index) # TODO: solve this
+		cons.write(index, len(data), data)
+
+		member_index = array_index
+		for node in array_data:
+			cons.add_target(node.get_path()[1:], member_index)
+			array_type.emit_data(cons, member_index, node)
+			member_index += array_type.size()
+			#print "array", member_index
+
+	def size(self):
+		return option_ptrsize+option_intsize
+
+class const_arrayint:
+	def __init__(self):
+		self.name = ""
+		self.values = []
+		
+	def emit_header_code(self, out):
+		print >>out, "enum"
+		print >>out, "{"
+		for i in xrange(0, len(self.values)):
+			print >>out, "\t%s_%s = %d," % (self.name.upper(), self.values[i].upper(), i)
+		
+		print >>out, "\tNUM_%sS = %d" % (self.name.upper(), len(self.values))
+		print >>out, "};"
+		print >>out, ""
+		
+class translator:
+	def __init__(self):
+		self.types = {}
+		self.structs = []
+		self.constants = []
+		self.data = 0
+		self.srcdata = 0
+
+		self.types["int"] = variable_int()
+		self.types["float"] = variable_float()
+		self.types["string"] = variable_string()
+		self.types["ptr"] = variable_ptr()
+		self.types["array"] = variable_array()
+
+	def parse_variable(self, node):
+		if len(node.values) != 4:
+			print node.values
+			raise "error parsing variable"
+			
+		type = node.values[0]
+		subtype = ""
+		if type == "int":
+			v = variable_int()
+		elif type == "float":
+			v = variable_float()
+		elif type == "string":
+			v = variable_string()
+		else:
+			# complex type
+			parts = type.split(":")
+			if len(parts) != 2:
+				raise "can't emit code for variable %s of type %s" % (self.name, self.type)
+			elif parts[0] == "ptr":
+				subtype = parts[1]
+				v = variable_ptr()
+			elif parts[0] == "instance":
+				subtype = parts[1]
+				v = variable_instance()
+			elif parts[0] == "array":
+				subtype = parts[1]
+				v = variable_array()
+			else:
+				raise "can't emit code for variable %s of type %s" % (self.name, self.type)
+
+		v.translator = self
+		v.type = node.values[0]
+		v.subtype = subtype
+		v.name = node.values[1]
+		assignment = node.values[2]
+		v.expr = node.values[3]
+		if assignment != "=":
+			raise "error parsing variable. expected ="
+		return v
+
+	def parse_struct(self, node):
+		if len(node.values) != 2:
+			raise "error parsing struct"
+		s = structure()
+		s.name = node.values[1]
+		
+		for statement in node.children:
+			s.members += [self.parse_variable(statement)]
+		return s
+	
+	def parse_constant(self, node):
+		if len(node.values) != 5:
+			print node.values
+			raise "error parsing constant"
+		
+		type = node.values[1]
+		name = node.values[2]
+		assignment = node.values[3]
+		expression = node.values[4]
+		
+		if assignment != "=":
+			print node.values
+			raise "error parsing constant"
+			
+		ints = const_arrayint()
+		ints.name = name
+		
+		items = self.srcdata.gather(expression)
+		for c in items:
+			ints.values += [c.name()]
+		self.constants += [ints]
+		
+	def parse(self, script, srcdata):
+		self.srcdata = srcdata
+		for statement in script.children:
+			if statement.values[0] == "struct":
+				s = self.parse_struct(statement)
+				self.structs += [s]
+				self.types[s.name] = s
+			elif statement.values[0] == "const":
+				self.parse_constant(statement)
+			else:
+				raise "unknown statement:" + statement
+				
+	def emit_header_code(self, out):
+		for c in self.constants:
+			c.emit_header_code(out)
+		
+		for s in self.structs:
+			s.emit_header_code(out)
+		print >>out, ""
+		print >>out, "data_container *load_data_container(const char *filename);"
+		print >>out, ""
+		
+
+	def emit_source_code(self, out):
+		print >>out, '''
+
+#include "../data.h"
+#include <stdio.h>
+#include <stdlib.h>
+
+void patch_ptr(char **ptr, char *base)
+{
+	*ptr = base+(size_t)(*ptr);
+}
+'''
+
+		for s in self.structs:
+			s.emit_source_code(out)
+		print >>out, '''
+data_container *load_data_container(const char *filename)
+{
+	data_container *con = 0;
+	int size;
+
+	/* open file */
+	FILE *f = fopen(filename, "rb");
+
+	/* get size */
+	fseek(f, 0, SEEK_END);
+	size = ftell(f);
+	fseek(f, 0, SEEK_SET);
+
+	/* allocate, read data and close file */
+	con = (data_container*)malloc(size);
+	fread(con, 1, size, f);
+	fclose(f);
+
+	/* patch all pointers */
+	patch_ptr_data_container(con, (char *)con);
+	return con;
+}
+
+'''
+
+	def emit_data(self):
+		for s in self.structs:
+			if s.name == "data_container":
+				#print "found data_container"
+				cons = data_constructor()
+				cons.trans = self
+				i = cons.allocate(s.size())
+				s.emit_data(cons, i, self.srcdata)
+				cons.patch_pointers()
+				return cons.data
+			
+def create_translator(script, srcdata):
+	t = translator()
+	t.parse(script, srcdata)
+	return t
+	
+def validate(script, validator):
+	def validate_values(values, check):
+		if not len(check) or check[0] == "*":
+			print "too many values"
+			return
+		p = check[0].split(":")
+		type = p[0]
+		name = p[1]
+		
+		# TODO: check type and stuff
+	
+		# recurse
+		if len(values) > 1:
+			if not len(check):
+				print "unexpected value"
+			validate_values(values[1:], check[1:])
+		else:
+			if len(check) > 1 and check[1] != "*":
+				print "to few values"
+	
+	if len(script.values):
+		validate_values(script.values, validator.values)
+	
+	for child in script.children:
+		tag = child.values[0]
+		n = validator.find_node("tag:"+tag)
+		if not n:
+			found = 0
+			for vc in validator.children:
+				if "ident:" in vc.values[0]:
+					validate(child, vc)
+					print vc.values[0]
+					found = 1
+					break
+					
+			if not found:
+				print "error:", tag, "not found"
+		else:
+			print "tag:"+tag
+			validate(child, n)
+
+input_filename = sys.argv[1]
+script_filename = sys.argv[2]
+
+output_filename = 0
+header_filename = 0
+source_filename = 0
+
+if sys.argv[3] == '-h':
+	header_filename = sys.argv[4]
+elif sys.argv[3] == '-s':
+	source_filename = sys.argv[4]
+elif sys.argv[3] == '-d':
+	output_filename = sys.argv[4]
+
+srcdata = parse_file(input_filename)
+script = parse_file(script_filename)
+
+translator = create_translator(script, srcdata)
+
+if header_filename:
+	translator.emit_header_code(file(header_filename, "w"))
+if source_filename:
+	translator.emit_source_code(file(source_filename, "w"))
+
+if output_filename:
+	rawdata = translator.emit_data()
+	file(output_filename, "wb").write(rawdata)
+	#print "filesize:", len(rawdata)	
diff --git a/src/crapnet/crapnet.cpp b/src/crapnet/crapnet.cpp
new file mode 100644
index 00000000..b441a350
--- /dev/null
+++ b/src/crapnet/crapnet.cpp
@@ -0,0 +1,126 @@
+#include <baselib/system.h>
+#include <baselib/network.h>
+
+#include <cstdlib>
+
+using namespace baselib;
+
+struct packet
+{
+	packet *prev;
+	packet *next;
+	
+	netaddr4 send_to;
+	int64 timestamp;
+	int id;
+	int data_size;
+	char data[1];
+};
+
+static packet *first = (packet *)0;
+static packet *last = (packet *)0;
+static int current_latency = 0;
+static int debug = 0;
+
+int run(int port, netaddr4 dest)
+{
+	netaddr4 src(0,0,0,0,0);
+	socket_udp4 socket;
+	socket.open(port);
+	char buffer[1024*2];
+	int id = 0;
+	
+	while(1)
+	{
+		// handle incomming packets
+		while(1)
+		{
+			// fetch data
+			netaddr4 from;
+			int bytes = socket.recv(&from, buffer, 1024*2);
+			if(bytes <= 0)
+				break;
+
+			// create new packet				
+			packet *p = (packet *)mem_alloc(sizeof(packet)+bytes, 1);
+
+			if(from == dest)
+				p->send_to = src;
+			else
+			{
+				src = from;
+				p->send_to = dest;
+			}
+
+			// queue packet
+			p->prev = last;
+			p->next = 0;
+			if(last)
+				last->next = p;
+			else
+			{
+				first = p;
+				last = p;
+			}
+			last = p;
+
+			// set data in packet			
+			p->timestamp = time_get();
+			p->data_size = bytes;
+			p->id = id++;
+			mem_copy(p->data, buffer, bytes);
+
+			if(debug)
+				dbg_msg("crapnet", "<< %08d %d.%d.%d.%d:%5d (%d)", p->id, from.ip[0], from.ip[1], from.ip[2], from.ip[3], from.port, p->data_size);
+		}
+		
+		//
+		while(1)
+		{
+			//dbg_msg("crapnet", "%p", first);
+			if(first && (time_get()-first->timestamp) > current_latency)
+			{
+				packet *p = first;
+				first = first->next;
+				if(first)
+					first->prev = 0;
+				else
+					last = 0;
+				
+				if(debug)
+				{
+					dbg_msg("crapnet", ">> %08d %d.%d.%d.%d:%5d (%d)", p->id,
+						p->send_to.ip[0], p->send_to.ip[1],
+						p->send_to.ip[2], p->send_to.ip[3],
+						p->send_to.port, p->data_size);
+				}
+				
+				// send and remove packet
+				if((rand()%10) != 0) // heavy packetloss
+					socket.send(&p->send_to, p->data, p->data_size);
+				
+				// update lag
+				double flux = rand()/(double)RAND_MAX;
+				int ms_spike = 250;
+				int ms_flux = 100;
+				int ms_ping = 50;
+				current_latency = ((time_freq()*ms_ping)/1000) + (int64)(((time_freq()*ms_flux)/1000)*flux); // 50ms
+				
+				if((p->id%100) == 0)
+					current_latency += (time_freq()*ms_spike)/1000;
+
+				mem_free(p);
+			}
+			else
+				break;
+		}
+		
+		thread_sleep(1);
+	}
+}
+
+int main(int argc, char **argv)
+{
+	run(8302, netaddr4(127,0,0,1,8303));
+	return 0;
+}
diff --git a/src/engine/client/client.cpp b/src/engine/client/client.cpp
index 86cc044d..4fb869cd 100644
--- a/src/engine/client/client.cpp
+++ b/src/engine/client/client.cpp
@@ -6,6 +6,7 @@
 #include <string.h>
 #include <stdarg.h>
 #include <stdlib.h>
+#include <stdio.h>
 #include <math.h>
 #include <engine/interface.h>
 
@@ -13,59 +14,14 @@
 #include <engine/snapshot.h>
 #include "ui.h"
 
-#include <engine/lzw.h>
+#include <engine/compression.h>
 
 #include <engine/versions.h>
 #include <engine/config.h>
+#include <engine/network.h>
 
 using namespace baselib;
 
-// --- string handling (MOVE THESE!!) ---
-void snap_encode_string(const char *src, int *dst, int length, int max_length)
-{
-	const unsigned char *p = (const unsigned char *)src;
-	
-	// handle whole int
-	for(int i = 0; i < length/4; i++)
-	{
-		*dst = (p[0]<<24|p[1]<<16|p[2]<<8|p[3]);
-		p += 4;
-		dst++;
-	}
-	
-	// take care of the left overs
-	int left = length%4;
-	if(left)
-	{
-		unsigned last = 0;
-		switch(left)
-		{
-			case 3: last |= p[2]<<8;
-			case 2: last |= p[1]<<16;
-			case 1: last |= p[0]<<24;
-		}
-		*dst = last;
-	}
-}
-
-void snap_decode_string(const int *src, char *dst, int max_length)
-{
-	dbg_assert((max_length%4) == 0, "length must be power of 4");
-	for(int i = 0; i < max_length; i++)
-		dst[0] = 0;
-	
-	for(int i = 0; i < max_length/4; i++)
-	{
-		dst[0] = (*src>>24)&0xff;
-		dst[1] = (*src>>16)&0xff;
-		dst[2] = (*src>>8)&0xff;
-		dst[3] = (*src)&0xff;
-		src++;
-		dst+=4;
-	}
-	dst[-1] = 0; // make sure to zero terminate
-}
-
 // --- input wrappers ---
 static int keyboard_state[2][input::last];
 static int keyboard_current = 0;
@@ -92,7 +48,7 @@ void inp_update()
 }
 
 // --- input snapping ---
-static int input_data[MAX_INPUT_SIZE];
+static int input_data[MAX_INPUT_SIZE] = {0};
 static int input_data_size;
 static int input_is_changed = 1;
 void snap_input(void *data, int size)
@@ -110,6 +66,8 @@ enum
 	NUM_SNAPSHOT_TYPES=3,
 };
 
+static snapshot_storage snapshots_new;
+static int current_tick;
 static snapshot *snapshots[NUM_SNAPSHOT_TYPES];
 static char snapshot_data[NUM_SNAPSHOT_TYPES][MAX_SNAPSHOT_SIZE];
 static int recived_snapshots;
@@ -127,7 +85,7 @@ void *snap_get_item(int snapid, int index, snap_item *item)
 	snapshot::item *i = snapshots[snapid]->get_item(index);
 	item->type = i->type();
 	item->id = i->id();
-	return (void *)i->data;
+	return (void *)i->data();
 }
 
 int snap_num_items(int snapid)
@@ -145,11 +103,16 @@ static void snap_init()
 	recived_snapshots = 0;
 }
 
-float snap_intratick()
+float client_intratick()
 {
 	return (time_get() - snapshot_start_time)/(float)(time_freq()/SERVER_TICK_SPEED);
 }
 
+int client_tick()
+{
+	return current_tick;
+}
+
 void *snap_find_item(int snapid, int type, int id)
 {
 	// TODO: linear search. should be fixed.
@@ -157,123 +120,45 @@ void *snap_find_item(int snapid, int type, int id)
 	{
 		snapshot::item *itm = snapshots[snapid]->get_item(i);
 		if(itm->type() == type && itm->id() == id)
-			return (void *)itm->data;
+			return (void *)itm->data();
 	}
 	return 0x0;
 }
 
 
-int menu_loop();
-float frametime = 0.0001f;
+int menu_loop(); // TODO: what is this?
+static float frametime = 0.0001f;
 
 float client_frametime()
 {
 	return frametime;
 }
 
-void unpack(const char *src, const char *fmt, ...)
-{
-}
+static net_client net;
 
-/*int modc_onmsg(int msg)
-{
-	msg_get("iis")
-}*/
-
-/*
-	i = int (int i)
-	s = string (const char *str)
-	r = raw data (int size, void *data)
-*/
-
-/*
-class packet2
-{
-private:
-	// packet data
-	struct header
-	{
-		unsigned msg;
-		unsigned ack;
-		unsigned seq;
-	};
-	
-	unsigned char packet_data[MAX_PACKET_SIZE];
-	unsigned char *current;
-	
-	enum
-	{
-		MAX_PACKET_SIZE = 1024,
-	};
-	
-public:
-	packet2()
-	{
-		current = packet_data;
-		current += sizeof(header);
-	}
-
-	int pack(char *dst, const char *fmt, ...)
-	{
-		va_list arg_list;
-		va_start(arg_list, fmt);
-		while(*fmt)
-		{
-			if(*fmt == 's')
-			{
-				// pack string
-				const char *s = va_arg(arg_list, const char*);
-				*dst++ = 2;
-				while(*s)
-				{
-					*dst = *s;
-					dst++;
-					s++;
-				}
-				*dst = 0; // null terminate
-				dst++;
-				fmt++;
-			}
-			else if(*fmt == 'i')
-			{
-				// pack int
-				int i = va_arg(arg_list, int);
-				*dst++ = 1;
-				*dst++ = (i>>24)&0xff;
-				*dst++ = (i>>16)&0xff;
-				*dst++ = (i>>8)&0xff;
-				*dst++ = i&0xff;
-				fmt++;
-			}
-			else
-			{
-				dbg_break(); // error
-				break;
-			}
-		}
-		va_end(arg_list);	
-	}	
-};
-*/
-/*
-int msg_get(const char *fmt)
+int client_send_msg()
 {
+	const msg_info *info = msg_get_info();
+	NETPACKET packet;
+	packet.client_id = 0;
+	packet.data = info->data;
+	packet.data_size = info->size;
+
+	if(info->flags&MSGFLAG_VITAL)	
+		packet.flags = PACKETFLAG_VITAL;
 	
+	net.send(&packet);
+	return 0;
 }
 
-int client_msg_send(int msg, const char *fmt, ...)
-
-int server_msg_send(int msg, const char *fmt, ...)
-{
-
-}*/
-
 // --- client ---
+// TODO: remove this class
 class client
 {
 public:
-	socket_udp4 socket;
-	connection conn;
+	
+	//socket_udp4 socket;
+	//connection conn;
 	int64 reconnect_timer;
 	
 	int snapshot_part;
@@ -305,37 +190,24 @@ public:
 
 	void set_fullscreen(bool flag) { fullscreen = flag; }
 	
-	void send_packet(packet *p)
-	{
-		conn.send(p);
-	}
-	
-	void send_connect()
+	void send_info()
 	{
 		recived_snapshots = 0;
-		
-		/*
-		pack(NETMSG_CLIENT_CONNECT, "sssss",
-			TEEWARS_NETVERSION,
-			name,
-			"no clan",
-			"password",
-			"myskin");
-		*/
-		
-		packet p(NETMSG_CLIENT_CONNECT);
-		p.write_str(TEEWARS_VERSION); // payload
-		p.write_str(config.player_name);
-		p.write_str("no clan");
-		p.write_str("password");
-		p.write_str("myskin");
-		send_packet(&p);
+
+		msg_pack_start(NETMSG_INFO, MSGFLAG_VITAL);
+		msg_pack_string(config.player_name, 128);
+		msg_pack_string(config.clan_name, 128);
+		msg_pack_string(config.password, 128);
+		msg_pack_string("myskin", 128);
+		msg_pack_end();
+		client_send_msg();
 	}
 
-	void send_done()
+	void send_entergame()
 	{
-		packet p(NETMSG_CLIENT_DONE);
-		send_packet(&p);
+		msg_pack_start(NETMSG_ENTERGAME, MSGFLAG_VITAL);
+		msg_pack_end();
+		client_send_msg();
 	}
 
 	void send_error(const char *error)
@@ -343,40 +215,38 @@ public:
 		/*
 			pack(NETMSG_CLIENT_ERROR, "s", error);
 		*/
+		/*
 		packet p(NETMSG_CLIENT_ERROR);
 		p.write_str(error);
 		send_packet(&p);
 		//send_packet(&p);
 		//send_packet(&p);
+		*/
 	}	
 
 	void send_input()
 	{
-		/*
-			pack(NETMSG_CLIENT_ERROR, "s", error);
-		*/
-		packet p(NETMSG_CLIENT_INPUT);
-		p.write_int(input_data_size);
+		msg_pack_start(NETMSG_INPUT, 0);
+		msg_pack_int(input_data_size);
 		for(int i = 0; i < input_data_size/4; i++)
-			p.write_int(input_data[i]);
-		send_packet(&p);
+			msg_pack_int(input_data[i]);
+		msg_pack_end();
+		client_send_msg();
 	}
 	
 	void disconnect()
 	{
+		/*
 		send_error("disconnected");
 		set_state(STATE_OFFLINE);
 		map_unload();
+		*/
 	}
 	
 	void connect(netaddr4 *server_address)
 	{
-		conn.init(&socket, server_address);
-		
-		// start by sending connect
-		send_connect();
+		net.connect(server_address);
 		set_state(STATE_CONNECTING);
-		reconnect_timer = time_get()+time_freq();
 	}
 	
 	bool load_data()
@@ -385,14 +255,42 @@ public:
 		return true;
 	}
 	
+	void debug_render()
+	{
+		gfx_blend_normal();
+		gfx_texture_set(debug_font);
+		gfx_mapscreen(0,0,gfx_screenwidth(),gfx_screenheight());
+		
+		static NETSTATS prev, current;
+		static int64 last_snap = 0;
+		if(time_get()-last_snap > time_freq()/10)
+		{
+			last_snap = time_get();
+			prev = current;
+			net.stats(&current);
+		}
+		
+		char buffer[512];
+		sprintf(buffer, "send: %8d recv: %8d",
+			(current.send_bytes-prev.send_bytes)*10,
+			(current.recv_bytes-prev.recv_bytes)*10);
+		gfx_quads_text(10, 10, 16, buffer);
+		
+	}
+	
 	void render()
 	{
 		gfx_clear(0.0f,0.0f,0.0f);
 		
 		// this should be moved around abit
+		// TODO: clean this shit up!
 		if(get_state() == STATE_ONLINE)
 		{
 			modc_render();
+			
+			// debug render stuff
+			debug_render();
+			
 		}
 		else if (get_state() != STATE_CONNECTING && get_state() != STATE_LOADING)
 		{
@@ -404,7 +302,7 @@ public:
 			else if (status)
 				connect(&server_address);
 		}
-		else if (get_state() == STATE_CONNECTING)
+		else if (get_state() == STATE_CONNECTING || get_state() == STATE_LOADING)
 		{
 			static int64 start = time_get();
 			static int tee_texture;
@@ -458,15 +356,18 @@ public:
 		// init menu
 		modmenu_init();
 		
+		net.open(0);
+		
 		// open socket
+		/*
 		if(!socket.open(0))
 		{
 			dbg_msg("network/client", "failed to open socket");
 			return;
-		}
+		}*/
 
 		// connect to the server if wanted
-		if (server_address)
+		if(server_address)
 			connect(server_address);
 		
 		//int64 inputs_per_second = 50;
@@ -504,6 +405,9 @@ public:
 				input::set_mouse_mode(input::mode_absolute);
 			if(input::pressed(input::f2))
 				input::set_mouse_mode(input::mode_relative);
+
+			if(input::pressed(input::lctrl) && input::pressed('Q'))
+				break;
 				
 			// pump the network
 			pump_network();
@@ -523,13 +427,13 @@ public:
 			
 			if(reporttime < time_get())
 			{
-				unsigned sent, recved;
-				conn.counter_get(&sent, &recved);
-				dbg_msg("client/report", "fps=%.02f",
-					frames/(float)(reportinterval/time_freq()));
+				//unsigned sent, recved;
+				//conn.counter_get(&sent, &recved);
+				dbg_msg("client/report", "fps=%.02f netstate=%d",
+					frames/(float)(reportinterval/time_freq()), net.state());
 				frames = 0;
 				reporttime += reportinterval;
-				conn.counter_reset();
+				//conn.counter_reset();
 			}
 			
 			if (input::pressed(input::esc))
@@ -556,117 +460,171 @@ public:
 		set_state(STATE_BROKEN);
 	}
 
-	void process_packet(packet *p)
+	void process_packet(NETPACKET *packet)
 	{
-		if(p->version() != TEEWARS_NETVERSION)
+		int msg = msg_unpack_start(packet->data, packet->data_size);
+		if(msg == NETMSG_MAP)
 		{
-			error("wrong version");
-		}
-		else if(p->msg() == NETMSG_SERVER_ACCEPT)
-		{
-			const char *map;
-			map = p->read_str();
+			const char *map = msg_unpack_string();
+			dbg_msg("client/network", "connection accepted, map=%s", map);
+			set_state(STATE_LOADING);
 			
-			if(p->is_good())
+			if(map_load(map))
 			{
-				dbg_msg("client/network", "connection accepted, map=%s", map);
-				set_state(STATE_LOADING);
-				
-				if(map_load(map))
-				{
-					modc_entergame();
-					send_done();
-					dbg_msg("client/network", "loading done");
-					// now we will wait for two snapshots
-					// to finish the connection
-				}
-				else
-				{
-					error("failure to load map");
-				}
+				modc_entergame();
+				send_entergame();
+				dbg_msg("client/network", "loading done");
+				// now we will wait for two snapshots
+				// to finish the connection
+			}
+			else
+			{
+				error("failure to load map");
 			}
 		}
-		else if(p->msg() == NETMSG_SERVER_SNAP)
+		else if(msg == NETMSG_SNAP || msg == NETMSG_SNAPSMALL || msg == NETMSG_SNAPEMPTY)
 		{
 			//dbg_msg("client/network", "got snapshot");
-			int num_parts = p->read_int();
-			int part = p->read_int();
-			int part_size = p->read_int();
+			int game_tick = msg_unpack_int();
+			int delta_tick = game_tick-msg_unpack_int();
+			int num_parts = 1;
+			int part = 0;
+			int part_size = 0;
 			
-			if(p->is_good())
+			if(msg == NETMSG_SNAP)
 			{
-				if(snapshot_part == part)
+				num_parts = msg_unpack_int();
+				part = msg_unpack_int();
+			}
+			
+			if(msg != NETMSG_SNAPEMPTY)
+				part_size = msg_unpack_int();
+			
+			if(snapshot_part == part)
+			{
+				// TODO: clean this up abit
+				const char *d = (const char *)msg_unpack_raw(part_size);
+				mem_copy((char*)snapshots[SNAP_INCOMMING] + part*MAX_SNAPSHOT_PACKSIZE, d, part_size);
+				snapshot_part++;
+			
+				if(snapshot_part == num_parts)
 				{
-					const char *d = p->read_raw(part_size);
-					mem_copy((char*)snapshots[SNAP_INCOMMING] + part*MAX_SNAPSHOT_PACKSIZE, d, part_size);
-					snapshot_part++;
-				
-					if(snapshot_part == num_parts)
+					snapshot *tmp = snapshots[SNAP_PREV];
+					snapshots[SNAP_PREV] = snapshots[SNAP_CURRENT];
+					snapshots[SNAP_CURRENT] = tmp;
+					current_tick = game_tick;
+
+					// decompress snapshot
+					void *deltadata = snapshot_empty_delta();
+					int deltasize = sizeof(int)*3;
+
+					unsigned char tmpbuffer[MAX_SNAPSHOT_SIZE];
+					unsigned char tmpbuffer2[MAX_SNAPSHOT_SIZE];
+					if(part_size)
 					{
-						snapshot *tmp = snapshots[SNAP_PREV];
-						snapshots[SNAP_PREV] = snapshots[SNAP_CURRENT];
-						snapshots[SNAP_CURRENT] = tmp;
-
-						// decompress snapshot
-						lzw_decompress(snapshots[SNAP_INCOMMING], snapshots[SNAP_CURRENT]);
-						
-						// apply snapshot, cycle pointers
-						recived_snapshots++;
-						snapshot_start_time = time_get();
-						
-						// we got two snapshots until we see us self as connected
-						if(recived_snapshots == 2)
+						//int snapsize = lzw_decompress(snapshots[SNAP_INCOMMING], snapshots[SNAP_CURRENT]);
+						int compsize = zerobit_decompress(snapshots[SNAP_INCOMMING], part_size, tmpbuffer);
+						//int compsize = lzw_decompress(snapshots[SNAP_INCOMMING],tmpbuffer);
+						int intsize = intpack_decompress(tmpbuffer, compsize, tmpbuffer2);
+						deltadata = tmpbuffer2;
+						deltasize = intsize;
+					}
+
+					// find snapshot that we should use as delta 
+					static snapshot emptysnap;
+					emptysnap.data_size = 0;
+					emptysnap.num_items = 0;
+					
+					snapshot *deltashot = &emptysnap;
+					int deltashot_size;
+
+					if(delta_tick >= 0)
+					{
+						void *delta_data;
+						deltashot_size = snapshots_new.get(delta_tick, &delta_data);
+						if(deltashot_size >= 0)
 						{
-							local_start_time = time_get();
-							set_state(STATE_ONLINE);
+							deltashot = (snapshot *)delta_data;
+						}
+						else
+						{
+							// TODO: handle this
+							dbg_msg("client", "error, couldn't find the delta snapshot");
 						}
-						
-						if(recived_snapshots > 2)
-							modc_newsnapshot();
-						
-						snapshot_part = 0;
 					}
 
-				}
-				else
-				{
-					dbg_msg("client", "snapshot reset!");
+					int snapsize = snapshot_unpack_delta(deltashot, (snapshot*)snapshots[SNAP_CURRENT], deltadata, deltasize);
+					//snapshot *shot = (snapshot *)snapshots[SNAP_CURRENT];
+
+					// purge old snapshots					
+					snapshots_new.purge_until(delta_tick);
+					snapshots_new.purge_until(game_tick-50); // TODO: change this to server tickrate
+					
+					// add new
+					snapshots_new.add(game_tick, snapsize, snapshots[SNAP_CURRENT]);
+					
+					// apply snapshot, cycle pointers
+					recived_snapshots++;
+					snapshot_start_time = time_get();
+					
+					// we got two snapshots until we see us self as connected
+					if(recived_snapshots == 2)
+					{
+						local_start_time = time_get();
+						set_state(STATE_ONLINE);
+					}
+					
+					if(recived_snapshots > 2)
+						modc_newsnapshot();
+					
 					snapshot_part = 0;
+					
+					// ack snapshot
+					msg_pack_start(NETMSG_SNAPACK, 0);
+					msg_pack_int(game_tick);
+					msg_pack_end();
+					client_send_msg();
 				}
 			}
-		}
-		else
-		{
-			dbg_msg("server/client", "unknown packet %x", p->msg());
+			else
+			{
+				dbg_msg("client", "snapshot reset!");
+				snapshot_part = 0;
+			}
 		}
 	}
 	
 	void pump_network()
 	{
-		while(1)
+		net.update();
+
+		// check for errors		
+		if(get_state() != STATE_OFFLINE && net.state() == NETSTATE_OFFLINE)
 		{
-			packet p;
-			netaddr4 from;
-			int bytes = socket.recv(&from, p.data(), p.max_size());
-			
-			if(bytes <= 0)
-				break;
-			
-			process_packet(&p);
+			// TODO: add message to the user there
+			set_state(STATE_OFFLINE);
 		}
-		
+
 		//
-		if(get_state() == STATE_CONNECTING && time_get() > reconnect_timer)
+		if(get_state() == STATE_CONNECTING && net.state() == NETSTATE_ONLINE)
 		{
-			send_connect();
-			reconnect_timer = time_get() + time_freq();
+			// we switched to online
+			dbg_msg("client", "connected, sending info");
+			set_state(STATE_LOADING);
+			send_info();
 		}
+		
+		// process packets
+		NETPACKET packet;
+		while(net.recv(&packet))
+			process_packet(&packet);
 	}	
 };
 
 int main(int argc, char **argv)
 {
 	dbg_msg("client", "starting...");
+	
 	config_reset();
 	config_load("teewars.cfg");
 
@@ -683,9 +641,23 @@ int main(int argc, char **argv)
 	{
 		if(argv[i][0] == '-' && argv[i][1] == 'c' && argv[i][2] == 0 && argc - i > 1)
 		{
-			// -c SERVER
+			// -c SERVER:PORT
 			i++;
-			if(net_host_lookup(argv[i], 8303, &server_address) != 0)
+			const char *port_str = 0;
+			for(int k = 0; argv[i][k]; k++)
+			{
+				if(argv[i][k] == ':')
+				{
+					port_str = &(argv[i][k+1]);
+					argv[i][k] = 0;
+					break;
+				}
+			}
+			int port = 8303;
+			if(port_str)
+				port = atoi(port_str);
+				
+			if(net_host_lookup(argv[i], port, &server_address) != 0)
 				dbg_msg("main", "could not find the address of %s, connecting to localhost", argv[i]);
 			else
 				connect_at_once = true;
diff --git a/src/engine/lzw.cpp b/src/engine/compression.cpp
index 80dd1c22..c878b15b 100644
--- a/src/engine/lzw.cpp
+++ b/src/engine/compression.cpp
@@ -1,3 +1,4 @@
+#include <baselib/system.h>
 #include <string.h>
 
 // LZW Compressor
@@ -221,3 +222,149 @@ long lzw_decompress(const void *src_, void *dst_)
 
 	return 0;
 }
+
+// Format: ESDDDDDD EDDDDDDD EDD...  Extended, Data, Sign
+unsigned char *vint_pack(unsigned char *dst, int i) 
+{ 
+        *dst = (i>>25)&0x40; // set sign bit if i<0
+	i = i^(i>>31); // if(i<0) i = ~i
+ 
+        *dst |= i&0x3F; // pack 6bit into dst
+        i >>= 6; // discard 6 bits
+        if(i)
+	{
+                *dst |= 0x80; // set extend bit
+		while(1)
+		{
+			dst++;
+	                *dst = i&(0x7F); // pack 7bit
+        	        i >>= 7; // discard 7 bits
+			*dst |= (i!=0)<<7; // set extend bit (may branch)
+                	if(!i)
+				break;
+		}
+	}
+
+	dst++;
+        return dst; 
+} 
+ 
+const unsigned char *vint_unpack(const unsigned char *src, int *i)
+{ 
+        int sign = (*src>>6)&1; 
+        *i = *src&0x3F; 
+
+	while(1)
+	{ 
+	        if(!(*src&0x80)) break;
+		src++;
+                *i |= (*src&(0x7F))<<(6);
+
+	        if(!(*src&0x80)) break;
+		src++;
+                *i |= (*src&(0x7F))<<(6+7);
+
+	        if(!(*src&0x80)) break;
+		src++;
+                *i |= (*src&(0x7F))<<(6+7+7);
+
+	        if(!(*src&0x80)) break;
+		src++;
+                *i |= (*src&(0x7F))<<(6+7+7+7);
+	}
+
+	src++;
+	*i ^= -sign; // if(sign) *i = ~(*i)
+        return src; 
+} 
+
+
+long intpack_decompress(const void *src_, int size, void *dst_)
+{
+	const unsigned char *src = (unsigned char *)src_;
+	const unsigned char *end = src + size;
+	int *dst = (int *)dst_;
+	while(src < end)
+	{
+		src = vint_unpack(src, dst);
+		dst++;
+	}
+	return (long)((unsigned char *)dst-(unsigned char *)dst_);
+}
+
+long intpack_compress(const void *src_, int size, void *dst_)
+{
+	int *src = (int *)src_;
+	unsigned char *dst = (unsigned char *)dst_;
+	size /= 4;
+	while(size)
+	{
+		dst = vint_pack(dst, *src);
+		size--;
+		src++;
+	}
+	return (long)(dst-(unsigned char *)dst_);
+}
+
+
+long zerobit_compress(const void *src_, int size, void *dst_)
+{
+	unsigned char *src = (unsigned char *)src_;
+	unsigned char *dst = (unsigned char *)dst_;
+	
+	//int zerocount = 0 ;
+	while(size)
+	{
+		unsigned char bit = 0x80;
+		unsigned char mask = 0;
+		int dst_move = 1;
+		int chunk = size < 8 ? size : 8;
+		size -= chunk;
+		
+		for(int b = 0; b < chunk; b++, bit>>=1)
+		{
+			if(*src)
+			{
+				dst[dst_move] = *src;
+				mask |= bit;
+				dst_move++;
+			}
+			
+			src++;
+		}
+		
+		*dst = mask;
+		dst += dst_move;
+	}
+	
+	long l = (long)(dst-(unsigned char *)dst_);
+	//dbg_msg("zerobit", "%d", (int)l);
+	return l;
+}
+
+long zerobit_decompress(const void *src_, int size, void *dst_)
+{
+	unsigned char *src = (unsigned char *)src_;
+	unsigned char *dst = (unsigned char *)dst_;
+	unsigned char *end = src + size;
+	
+	//int zerocount = 0 ;
+	while(src != end)
+	{
+		unsigned char bit = 0x80;
+		unsigned char mask = *src++;
+		
+		for(int b = 0; b < 8; b++, bit>>=1)
+		{
+			if(mask&bit)
+				*dst++ = *src++;
+			else
+				*dst++ = 0;
+		}
+	}
+	
+	long l = (long)(dst-(unsigned char *)dst_);
+	//dbg_msg("zerobit", "%d", (int)l);
+	return l;
+}
+
diff --git a/src/engine/compression.h b/src/engine/compression.h
new file mode 100644
index 00000000..d1f13d48
--- /dev/null
+++ b/src/engine/compression.h
@@ -0,0 +1,12 @@
+// lzw is no longer in use
+long lzw_compress(const void *src, int size, void *dst);
+long lzw_decompress(const void *src, void *dst);
+
+unsigned char *vint_pack(unsigned char *dst, int i);
+const unsigned char *vint_unpack(const unsigned char *src, int *inout);
+
+long intpack_compress(const void *src, int size, void *dst);
+long intpack_decompress(const void *src, int size, void *dst);
+
+long zerobit_compress(const void *src, int size, void *dst);
+long zerobit_decompress(const void *src, int size, void *dst);
diff --git a/src/engine/config.cpp b/src/engine/config.cpp
index bedeb4d4..7694b159 100644
--- a/src/engine/config.cpp
+++ b/src/engine/config.cpp
@@ -29,7 +29,7 @@ void config_set(const char *line)
 	const char *val_str = strchr(line, '=');
 	if (val_str)
 	{
-		memcpy(var_str, line, val_str - line);
+		mem_copy(var_str, line, val_str - line);
 		var_str[val_str - line] = 0;
 		++val_str;
 
diff --git a/src/engine/config_variables.h b/src/engine/config_variables.h
index 852d26a9..27ca1931 100644
--- a/src/engine/config_variables.h
+++ b/src/engine/config_variables.h
@@ -1,2 +1,9 @@
 #include "../game/game_variables.h"
 
+MACRO_CONFIG_INT(screen_width, 800, 0, 0)
+MACRO_CONFIG_INT(screen_height, 600, 0, 0)
+MACRO_CONFIG_STR(player_name, 32, "nameless tee")
+MACRO_CONFIG_STR(clan_name, 32, "")
+MACRO_CONFIG_STR(password, 32, "")
+
+MACRO_CONFIG_INT(sv_port, 8303, 0, 0)
diff --git a/src/engine/interface.h b/src/engine/interface.h
index c8099247..27f73e17 100644
--- a/src/engine/interface.h
+++ b/src/engine/interface.h
@@ -520,7 +520,7 @@ void snap_input(void *data, int size);
 	Remarks:
 		DOCTODO: Explain how to use it.
 */
-float snap_intratick();
+//float snap_intratick();
 
 /*
 	Group: Server Callbacks
@@ -681,8 +681,13 @@ void modmenu_shutdown();
 */
 int modmenu_render(void *server_address);
 
-void snap_encode_string(const char *src, int *dst, int length, int max_length);
-void snap_decode_string(const int *src, char *dst, int length);
+
+
+
+
+
+//void snap_encode_string(const char *src, int *dst, int length, int max_length);
+//void snap_decode_string(const int *src, char *dst, int length);
 
 int server_getclientinfo(int client_id, client_info *info);
 int server_tick();
@@ -694,6 +699,44 @@ void inp_update();
 float client_frametime();
 float client_localtime();
 
+// message packing
+enum
+{
+	MSGFLAG_VITAL=1,
+};
+
+void msg_pack_start(int msg, int flags);
+void msg_pack_int(int i);
+void msg_pack_string(const char *p, int limit);
+void msg_pack_raw(const void *data, int size);
+void msg_pack_end();
+
+struct msg_info
+{
+	int msg;
+	int flags;
+	const unsigned char *data;
+	int size;
+};
+
+const msg_info *msg_get_info();
+
+// message unpacking
+int msg_unpack_start(const void *data, int data_size);
+int msg_unpack_int();
+const char *msg_unpack_string();
+const unsigned char *msg_unpack_raw(int size);
+
+// message sending
+int server_send_msg(int client_id);
+int client_send_msg();
+
+int client_tick();
+float client_intratick();
+
+
+int modc_message();
+
 #define MASTER_SERVER_ADDRESS "master.teewars.com"
 #define MASTER_SERVER_PORT 8300
 
diff --git a/src/engine/lzw.h b/src/engine/lzw.h
deleted file mode 100644
index af29665e..00000000
--- a/src/engine/lzw.h
+++ /dev/null
@@ -1,2 +0,0 @@
-long lzw_compress(const void *src, int size, void *dst);
-long lzw_decompress(const void *src, void *dst);
diff --git a/src/engine/msg.cpp b/src/engine/msg.cpp
new file mode 100644
index 00000000..024bab2c
--- /dev/null
+++ b/src/engine/msg.cpp
@@ -0,0 +1,43 @@
+
+#include "interface.h"
+#include "packet.h"
+
+// message packing
+static data_packer packer;
+static msg_info pack_info;
+
+void msg_pack_int(int i) { packer.add_int(i); }
+void msg_pack_string(const char *p, int limit) { packer.add_string(p, limit); }
+void msg_pack_raw(const void *data, int size) { packer.add_raw((const unsigned char *)data, size); }
+
+void msg_pack_start(int msg, int flags)
+{
+	packer.reset();
+	pack_info.msg = msg;
+	pack_info.flags = flags;
+	
+	msg_pack_int(msg);
+}
+
+void msg_pack_end()
+{
+	pack_info.size = packer.size();
+	pack_info.data = packer.data();
+}
+
+const msg_info *msg_get_info()
+{
+	return &pack_info;
+}
+
+// message unpacking
+static data_unpacker unpacker;
+int msg_unpack_start(const void *data, int data_size)
+{
+	unpacker.reset((const unsigned char *)data, data_size);
+	return msg_unpack_int();
+}
+
+int msg_unpack_int() { return unpacker.get_int(); }
+const char *msg_unpack_string() { return unpacker.get_string(); }
+const unsigned char *msg_unpack_raw(int size)  { return unpacker.get_raw(size); }
diff --git a/src/engine/network.cpp b/src/engine/network.cpp
new file mode 100644
index 00000000..5402aee7
--- /dev/null
+++ b/src/engine/network.cpp
@@ -0,0 +1,686 @@
+#include <baselib/system.h>
+
+#include "network.h"
+#include "ringbuffer.h"
+
+/*
+	header:
+		unsigned char ID[2]; 2 'T' 'W'
+		unsigned char version; 3 
+		unsigned char flags; 4 
+		unsigned short seq;  6
+		unsigned short ack;  8
+		unsigned crc;       12 bytes
+
+	header v2:
+		unsigned char flags;		1
+		unsigned char seq_ack[3];	4
+		unsigned char crc[2];		6
+*/
+
+
+#define NETWORK_HEADER_V2
+
+// move
+static int net_addr4_cmp(const NETADDR4 *a, const NETADDR4 *b)
+{
+	if(	a->ip[0] != b->ip[0] ||
+		a->ip[1] != b->ip[1] ||
+		a->ip[2] != b->ip[2] ||
+		a->ip[3] != b->ip[3] ||
+		a->port != b->port
+	)
+		return 1;
+	return 0;
+}
+
+enum
+{
+	NETWORK_VERSION = 1,
+	
+#ifdef NETWORK_HEADER_V2
+	NETWORK_HEADER_SIZE = 6,
+#else
+	NETWORK_HEADER_SIZE = 12,
+#endif
+	NETWORK_MAX_PACKET_SIZE = 1024,
+	NETWORK_MAX_CLIENTS = 16,
+	
+	NETWORK_CONNSTATE_OFFLINE=0,
+	NETWORK_CONNSTATE_CONNECT=1,
+	NETWORK_CONNSTATE_CONNECTACCEPTED=2,
+	NETWORK_CONNSTATE_ONLINE=3,
+	NETWORK_CONNSTATE_ERROR=4,
+	
+	NETWORK_PACKETFLAG_CONNECT=0x01,
+	NETWORK_PACKETFLAG_ACCEPT=0x02,
+	NETWORK_PACKETFLAG_CLOSE=0x04,
+	NETWORK_PACKETFLAG_VITAL=0x08,
+	NETWORK_PACKETFLAG_RESEND=0x10,
+	//NETWORK_PACKETFLAG_STATELESS=0x20,
+};
+
+struct NETPACKETDATA
+{
+	unsigned char ID[2];
+	unsigned char version;
+	unsigned char flags;
+	unsigned short seq;
+	unsigned short ack;
+	unsigned crc;
+	unsigned data_size;
+	int64 first_send_time;
+	unsigned char *data;
+};
+
+
+static void send_packet(NETSOCKET socket, NETADDR4 *addr, NETPACKETDATA *packet)
+{
+	unsigned char buffer[NETWORK_MAX_PACKET_SIZE];
+#ifdef NETWORK_HEADER_V2
+	buffer[0] = packet->flags;
+	buffer[1] = ((packet->seq>>4)&0xf0) | ((packet->ack>>8)&0x0f);
+	buffer[2] = packet->seq;
+	buffer[3] = packet->ack;
+	buffer[4] = packet->crc>>8;
+	buffer[5] = packet->crc&0xff;
+#else	
+	buffer[0] = packet->ID[0];
+	buffer[1] = packet->ID[1];
+	buffer[2] = packet->version;
+	buffer[3] = packet->flags;
+	buffer[4] = packet->seq>>8;
+	buffer[5] = packet->seq&0xff;
+	buffer[6] = packet->ack>>8;
+	buffer[7] = packet->ack&0xff;
+	buffer[8] = (packet->crc>>24)&0xff;
+	buffer[9] = (packet->crc>>16)&0xff;
+	buffer[10] = (packet->crc>>8)&0xff;
+	buffer[11] = packet->crc&0xff;
+#endif
+	mem_copy(buffer+NETWORK_HEADER_SIZE, packet->data, packet->data_size);
+	int send_size = NETWORK_HEADER_SIZE+packet->data_size;
+	//dbg_msg("network", "sending packet, size=%d (%d + %d)", send_size, NETWORK_HEADER_SIZE, packet->data_size);
+	net_udp4_send(socket, addr, buffer, send_size);
+}
+
+struct NETCONNECTION
+{
+	unsigned seq;
+	unsigned ack;
+	unsigned state;
+	
+	ring_buffer buffer;
+	
+	int64 last_recv_time;
+	int64 last_send_time;
+	const char *error_string;
+	
+	NETADDR4 peeraddr;
+	NETSOCKET socket;
+	NETSTATS stats;
+};
+
+struct NETSLOT
+{
+	int online;
+	NETCONNECTION conn;
+};
+
+struct NETSERVER
+{
+	NETSOCKET socket;
+	NETSLOT slots[NETWORK_MAX_CLIENTS];
+	unsigned char recv_buffer[NETWORK_MAX_PACKET_SIZE];
+};
+
+struct NETCLIENT
+{
+	NETADDR4 server_addr;
+	NETSOCKET socket;
+	unsigned char recv_buffer[NETWORK_MAX_PACKET_SIZE];
+	
+	NETCONNECTION conn;
+};
+
+static void conn_reset_stats(NETCONNECTION *conn)
+{
+	mem_zero(&conn->stats, sizeof(conn->stats));
+}
+
+static void conn_reset(NETCONNECTION *conn)
+{
+	conn->seq = 0;
+	conn->ack = 0;
+	conn->state = NETWORK_CONNSTATE_OFFLINE;
+	conn->error_string = 0;
+	conn->last_send_time = 0;
+	conn->last_recv_time = 0;
+	conn->buffer.reset();
+}
+
+static const char *conn_error(NETCONNECTION *conn)
+{
+	return conn->error_string;
+}
+/*
+static int conn_state(NETCONNECTION *conn)
+{
+	return conn->state;
+}*/
+
+static void conn_init(NETCONNECTION *conn, NETSOCKET socket)
+{
+	conn_reset(conn);
+	conn_reset_stats(conn);
+	conn->socket = socket;
+}
+
+static void conn_ack(NETCONNECTION *conn, int ack)
+{
+	while(1)
+	{
+		ring_buffer::item *i = conn->buffer.first();
+		if(!i)
+			break;
+			
+		NETPACKETDATA *resend = (NETPACKETDATA *)i->data();
+		if(resend->seq <= ack)
+			conn->buffer.pop_first();
+		else
+			break;
+	}
+}
+
+static void conn_send_raw(NETCONNECTION *conn, NETPACKETDATA *data)
+{
+	conn->last_send_time = time_get();
+	conn->stats.send_packets++;
+	conn->stats.send_bytes += data->data_size + NETWORK_HEADER_SIZE;
+	send_packet(conn->socket, &conn->peeraddr, data);
+}
+
+static void conn_resend(NETCONNECTION *conn)
+{
+	ring_buffer::item *i = conn->buffer.first();
+	while(i)
+	{
+		NETPACKETDATA *resend = (NETPACKETDATA *)i->data();
+		conn->stats.resend_packets++;
+		conn->stats.resend_bytes += resend->data_size + NETWORK_HEADER_SIZE;
+		conn_send_raw(conn, resend);
+		i = i->next;
+	}
+}
+
+static void conn_send(NETCONNECTION *conn, int flags, int data_size, const void *data)
+{
+	if(flags&NETWORK_PACKETFLAG_VITAL)
+		conn->seq++;
+	
+	NETPACKETDATA p;
+	p.ID[0] = 'T';
+	p.ID[1] = 'W';
+	p.version = NETWORK_VERSION;
+	p.flags = flags;
+	p.seq = conn->seq;
+	p.ack = conn->ack;
+	p.crc = 0;
+	p.data_size = data_size;
+	p.data = (unsigned char *)data;
+	p.first_send_time = time_get();
+	
+	if(flags&NETWORK_PACKETFLAG_VITAL)
+	{
+		// save packet if we need to resend
+		NETPACKETDATA *resend = (NETPACKETDATA *)conn->buffer.alloc(sizeof(NETPACKETDATA)+p.data_size);
+		*resend = p;
+		resend->data = (unsigned char *)(resend+1);
+		mem_copy(resend->data, p.data, p.data_size);
+	}
+	
+	// TODO: calc crc
+	conn_send_raw(conn, &p);
+}
+
+static int conn_connect(NETCONNECTION *conn, NETADDR4 *addr)
+{
+	if(conn->state != NETWORK_CONNSTATE_OFFLINE)
+		return -1;
+	
+	// init connection
+	conn_reset(conn);
+	conn->peeraddr = *addr;
+	conn->state = NETWORK_CONNSTATE_CONNECT;
+	conn_send(conn, NETWORK_PACKETFLAG_CONNECT, 0, 0);
+	return 0;
+}
+
+static int conn_feed(NETCONNECTION *conn, NETPACKETDATA *p, NETADDR4 *addr)
+{
+	conn->last_recv_time = time_get();
+	conn->stats.recv_packets++;
+	conn->stats.recv_bytes += p->data_size + NETWORK_HEADER_SIZE;
+	
+	if(conn->state == NETWORK_CONNSTATE_OFFLINE)
+	{
+		if(p->flags == NETWORK_PACKETFLAG_CONNECT)
+		{
+			// send response and init connection
+			conn->state = NETWORK_CONNSTATE_ONLINE;
+			conn->peeraddr = *addr;
+			conn_send(conn, NETWORK_PACKETFLAG_CONNECT|NETWORK_PACKETFLAG_ACCEPT, 0, 0);
+			dbg_msg("connection", "got connection, sending connect+accept");
+		}
+	}
+	else if(net_addr4_cmp(&conn->peeraddr, addr) == 0)
+	{
+		if(conn->state == NETWORK_CONNSTATE_ONLINE)
+		{
+			// remove packages that are acked
+			conn_ack(conn, p->ack);
+			
+			// 
+			if(p->flags&NETWORK_PACKETFLAG_RESEND)
+				conn_resend(conn);
+				
+			if(p->flags&NETWORK_PACKETFLAG_VITAL)
+			{
+				if(p->seq == conn->ack+1)
+				{
+					// in sequence
+					conn->ack++;
+				}
+				else
+				{
+					// out of sequence, request resend
+					dbg_msg("conn", "asking for resend");
+					conn_send(conn, NETWORK_PACKETFLAG_RESEND, 0, 0);
+					return 0;
+				}
+			}
+				
+			return 1;
+		}
+		else if(conn->state == NETWORK_CONNSTATE_CONNECT)
+		{
+			// connection made
+			if(p->flags == NETWORK_PACKETFLAG_CONNECT|NETWORK_PACKETFLAG_ACCEPT)
+			{
+				conn_send(conn, NETWORK_PACKETFLAG_ACCEPT, 0, 0);
+				conn->state = NETWORK_CONNSTATE_ONLINE;
+				dbg_msg("connection", "got connect+accept, sending accept. connection online");
+			}
+		}
+		/*
+		else if(conn->state == NETWORK_CONNSTATE_CONNECTACCEPTED)
+		{
+			// connection made
+			if(p->flags == NETWORK_PACKETFLAG_ACCEPT)
+			{
+				conn->state = NETWORK_CONNSTATE_ONLINE;
+				dbg_msg("connection", "got accept. connection online");
+			}
+		}*/
+		else
+		{
+			// strange packet, wrong state
+		}
+	}
+	else
+	{
+		// strange packet, not ment for me
+	}
+	
+	return 0;
+}
+
+
+
+static void conn_update(NETCONNECTION *conn)
+{
+	if(conn->state == NETWORK_CONNSTATE_ERROR)
+		return;
+	
+	// check for timeout
+	if(conn->state != NETWORK_CONNSTATE_OFFLINE &&
+		conn->state != NETWORK_CONNSTATE_CONNECT &&
+		(time_get()-conn->last_recv_time) > time_freq()*3)
+	{
+		conn->state = NETWORK_CONNSTATE_ERROR;
+		conn->error_string = "timeout";
+	}
+	
+	// check for large buffer errors
+	if(conn->buffer.size() > 1024*64)
+	{
+		conn->state = NETWORK_CONNSTATE_ERROR;
+		conn->error_string = "too weak connection (out of buffer)";
+	}
+	
+	if(conn->buffer.first())
+	{
+		NETPACKETDATA *resend = (NETPACKETDATA *)conn->buffer.first()->data();
+		if(time_get()-resend->first_send_time > time_freq()*3)
+		{
+			conn->state = NETWORK_CONNSTATE_ERROR;
+			conn->error_string = "too weak connection (not acked for 3 seconds)";
+		}
+	}
+	
+	// send keep alives if nothing has happend for 250ms
+	if(conn->state == NETWORK_CONNSTATE_ONLINE)
+	{
+		if(time_get()-conn->last_send_time> time_freq()/4)
+			conn_send(conn, NETWORK_PACKETFLAG_VITAL, 0, 0);
+	}
+	else if(conn->state == NETWORK_CONNSTATE_CONNECT)
+	{
+		if(time_get()-conn->last_send_time > time_freq()/2) // send a new connect every 500ms
+			conn_send(conn, NETWORK_PACKETFLAG_CONNECT, 0, 0);
+	}
+	else if(conn->state == NETWORK_CONNSTATE_CONNECTACCEPTED)
+	{
+		if(time_get()-conn->last_send_time > time_freq()/2) // send a new connect/accept every 500ms
+			conn_send(conn, NETWORK_PACKETFLAG_CONNECT|NETWORK_PACKETFLAG_ACCEPT, 0, 0);
+	}
+}
+
+
+static int check_packet(unsigned char *buffer, int size, NETPACKETDATA *packet)
+{
+	// check the size
+	if(size < NETWORK_HEADER_SIZE || size > NETWORK_MAX_PACKET_SIZE)
+		return -1;
+	
+	// read the packet
+#ifdef NETWORK_HEADER_V2	
+	packet->ID[0] = 'T';
+	packet->ID[1] = 'W';
+	packet->version = NETWORK_VERSION;
+	packet->flags = buffer[0];
+	packet->seq = ((buffer[1]&0xf0)<<4)|buffer[2];
+	packet->ack = ((buffer[1]&0x0f)<<8)|buffer[3];
+	packet->crc = (buffer[8]<<24)|(buffer[9]<<16)|(buffer[10]<<8)|buffer[11];
+#else
+	packet->ID[0] = buffer[0];
+	packet->ID[1] = buffer[1];
+	packet->version = buffer[2];
+	packet->flags = buffer[3];
+	packet->seq = (buffer[4]<<8)|buffer[5];
+	packet->ack = (buffer[6]<<8)|buffer[7];
+	packet->crc = (buffer[8]<<24)|(buffer[9]<<16)|(buffer[10]<<8)|buffer[11];
+#endif
+	packet->data_size = size - NETWORK_HEADER_SIZE;
+	packet->data = buffer+NETWORK_HEADER_SIZE;
+	
+	// check the packet
+	if(packet->ID[0] != 'T' || packet->ID[1] != 'W')
+		return 1;
+	
+	if(packet->version != NETWORK_VERSION)
+		return 1;
+
+	// TODO: perform crc check
+	
+	// return success
+	return 0;
+}
+
+NETSERVER *net_server_open(int port, int max_clients, int flags)
+{
+	NETSERVER *server = (NETSERVER *)mem_alloc(sizeof(NETSERVER), 1);
+	mem_zero(server, sizeof(NETSERVER));
+	server->socket = net_udp4_create(port);
+	
+	for(int i = 0; i < NETWORK_MAX_CLIENTS; i++)
+		conn_init(&server->slots[i].conn, server->socket);
+
+	return server;
+}
+
+int net_server_close(NETSERVER *s)
+{
+	// TODO: implement me
+	return 0;
+}
+
+int net_server_newclient(NETSERVER *s)
+{
+	for(int i = 0; i < NETWORK_MAX_CLIENTS; i++)
+	{
+		if(!s->slots[i].online && s->slots[i].conn.state == NETWORK_CONNSTATE_ONLINE)
+		{
+			s->slots[i].online = 1;
+			return i;
+		}
+	}
+	
+	return -1;
+}
+
+int net_server_delclient(NETSERVER *s)
+{
+	for(int i = 0; i < NETWORK_MAX_CLIENTS; i++)
+	{
+		if(s->slots[i].online && s->slots[i].conn.state != NETWORK_CONNSTATE_ONLINE)
+		{
+			s->slots[i].online = 0;
+			return i;
+		}
+	}
+	
+	return -1;
+}
+
+int net_server_drop(NETSERVER *s, int client_id, const char *reason)
+{
+	// TODO: insert lots of checks here
+	dbg_msg("net_server", "client dropped. cid=%d reason=\"%s\"", client_id, reason);
+	conn_reset(&s->slots[client_id].conn);
+	return 0;
+}
+
+int net_server_update(NETSERVER *s)
+{
+	for(int i = 0; i < NETWORK_MAX_CLIENTS; i++)
+	{
+		conn_update(&s->slots[i].conn);
+		if(conn_error(&s->slots[i].conn))
+			net_server_drop(s, i, conn_error(&s->slots[i].conn));
+	}
+	return 0;
+}
+
+int net_server_recv(NETSERVER *s, NETPACKET *packet)
+{
+	while(1)
+	{
+		NETADDR4 addr;
+		int bytes = net_udp4_recv(s->socket, &addr, s->recv_buffer, NETWORK_MAX_PACKET_SIZE);
+
+		// no more packets for now
+		if(bytes <= 0)
+			break;
+		
+		NETPACKETDATA data;
+		int r = check_packet(s->recv_buffer, bytes, &data);
+		if(r == 0)
+		{
+			// ok packet, process it
+			if(data.flags == NETWORK_PACKETFLAG_CONNECT)
+			{
+				// client that wants to connect
+				int found = 0;
+				for(int i = 0; i < NETWORK_MAX_CLIENTS; i++)
+				{
+					if(s->slots[i].conn.state == NETWORK_CONNSTATE_OFFLINE)
+					{
+						conn_feed(&s->slots[i].conn, &data, &addr);
+						found = 1;
+						break;
+					}
+				}
+				
+				if(!found)
+				{
+					// TODO: send error
+				}
+			}
+			else
+			{
+				// find matching slot
+				for(int i = 0; i < NETWORK_MAX_CLIENTS; i++)
+				{
+					if(net_addr4_cmp(&s->slots[i].conn.peeraddr, &addr) == 0)
+					{
+						if(conn_feed(&s->slots[i].conn, &data, &addr))
+						{
+							if(data.data_size)
+							{
+								packet->client_id = i;	
+								packet->address = addr;
+								packet->flags = 0;
+								packet->data_size = data.data_size;
+								packet->data = data.data;
+								return 1;
+							}
+						}
+					}
+				}
+			}
+		}
+		else
+		{
+			// errornous packet, drop it
+		}
+		
+		// read header
+		// do checksum
+	}	
+	
+	return 0;
+}
+
+int net_server_send(NETSERVER *s, NETPACKET *packet)
+{
+	// TODO: insert stuff for stateless stuff
+	dbg_assert(packet->client_id >= 0, "errornous client id");
+	dbg_assert(packet->client_id < NETWORK_MAX_CLIENTS, "errornous client id");
+	conn_send(&s->slots[packet->client_id].conn, 0, packet->data_size, packet->data);
+	return 0;
+}
+
+void net_server_stats(NETSERVER *s, NETSTATS *stats)
+{
+	mem_zero(stats, sizeof(NETSTATS));
+	
+	int num_stats = sizeof(NETSTATS)/sizeof(int);
+	int *istats = (int *)stats;
+	
+	for(int c = 0; c < NETWORK_MAX_CLIENTS; c++)
+	{
+		int *sstats = (int *)(&(s->slots[c].conn.stats));
+		for(int i = 0; i < num_stats; i++)
+			istats[i] += sstats[i];
+	}
+}
+
+//
+NETCLIENT *net_client_open(int flags)
+{
+	NETCLIENT *client = (NETCLIENT *)mem_alloc(sizeof(NETCLIENT), 1);
+	mem_zero(client, sizeof(NETCLIENT));
+	client->socket = net_udp4_create(0);
+	conn_init(&client->conn, client->socket);
+	return client;
+}
+
+int net_client_close(NETCLIENT *c)
+{
+	// TODO: implement me
+	return 0;
+}
+
+int net_client_update(NETCLIENT *c)
+{
+	// TODO: implement me
+	conn_update(&c->conn);
+	if(conn_error(&c->conn))
+		net_client_disconnect(c, conn_error(&c->conn));
+	return 0;
+}
+
+int net_client_disconnect(NETCLIENT *c, const char *reason)
+{
+	// TODO: do this more graceful
+	dbg_msg("net_client", "disconnected. reason=\"%s\"", reason);
+	conn_reset(&c->conn);
+	return 0;
+}
+
+int net_client_connect(NETCLIENT *c, NETADDR4 *addr)
+{
+	//net_client_disconnect(c);
+	conn_connect(&c->conn, addr);
+	return 0;
+}
+
+int net_client_recv(NETCLIENT *c, NETPACKET *packet)
+{
+	while(1)
+	{
+		NETADDR4 addr;
+		int bytes = net_udp4_recv(c->socket, &addr, c->recv_buffer, NETWORK_MAX_PACKET_SIZE);
+
+		// no more packets for now
+		if(bytes <= 0)
+			break;
+		
+		NETPACKETDATA data;
+		int r = check_packet(c->recv_buffer, bytes, &data);
+		if(r == 0)
+		{
+			// ok packet, process it
+			conn_feed(&c->conn, &data, &addr);
+			// fill in packet
+			packet->client_id = 0;
+			packet->address = addr;
+			packet->flags = 0;
+			packet->data_size = data.data_size;
+			packet->data = data.data;
+			return 1;
+		}
+		else
+		{
+			// errornous packet, drop it
+		}
+		
+		// read header
+		// do checksum
+	}
+	
+	return 0;
+}
+
+int net_client_send(NETCLIENT *c, NETPACKET *packet)
+{
+	// TODO: insert stuff for stateless stuff
+	dbg_assert(packet->client_id == 0, "errornous client id");
+	conn_send(&c->conn, 0, packet->data_size, packet->data);
+	return 0;
+}
+
+int net_client_state(NETCLIENT *c)
+{
+	if(c->conn.state == NETWORK_CONNSTATE_ONLINE)
+		return NETSTATE_ONLINE;
+	if(c->conn.state == NETWORK_CONNSTATE_OFFLINE)
+		return NETSTATE_OFFLINE;
+	return NETSTATE_CONNECTING;
+}
+
+void net_client_stats(NETCLIENT *c, NETSTATS *stats)
+{
+	*stats = c->conn.stats;
+}
diff --git a/src/engine/network.h b/src/engine/network.h
new file mode 100644
index 00000000..de035888
--- /dev/null
+++ b/src/engine/network.h
@@ -0,0 +1,104 @@
+
+struct NETPACKET
+{
+	// -1 means that it's a stateless packet
+	// 0 on the client means the server
+	int client_id;
+	NETADDR4 address; // only used when client_id == -1
+	int flags;
+	int data_size;
+	const void *data;
+};
+
+struct NETSTATS
+{
+	int send_bytes;
+	int recv_bytes;
+	int send_packets;
+	int recv_packets;
+	
+	int resend_packets;
+	int resend_bytes;
+};
+
+struct NETSERVER;
+struct NETCLIENT;
+
+enum
+{
+	NETFLAG_ALLOWSTATELESS=1,
+	PACKETFLAG_VITAL=1,
+	
+	NETSTATE_OFFLINE=0,
+	NETSTATE_CONNECTING,
+	NETSTATE_ONLINE,
+};
+
+// server side
+NETSERVER *net_server_open(int port, int max_clients, int flags);
+int net_server_recv(NETSERVER *s, NETPACKET *packet);
+int net_server_send(NETSERVER *s, NETPACKET *packet);
+int net_server_close(NETSERVER *s);
+int net_server_update(NETSERVER *s);
+int net_server_drop(NETSERVER *s, int client_id, const char *reason);
+int net_server_newclient(NETSERVER *s); // -1 when no more, else, client id
+int net_server_delclient(NETSERVER *s); // -1 when no more, else, client id
+void net_server_stats(NETSERVER *s, NETSTATS *stats);
+
+// client side
+NETCLIENT *net_client_open(int flags);
+int net_client_disconnect(NETCLIENT *c, const char *reason);
+int net_client_connect(NETCLIENT *c, NETADDR4 *addr);
+int net_client_recv(NETCLIENT *c, NETPACKET *packet);
+int net_client_send(NETCLIENT *c, NETPACKET *packet);
+int net_client_close(NETCLIENT *c);
+int net_client_update(NETCLIENT *c);
+int net_client_state(NETCLIENT *c);
+void net_client_stats(NETCLIENT *c, NETSTATS *stats);
+
+
+// wrapper classes for c++
+#ifdef __cplusplus
+class net_server
+{
+	NETSERVER *ptr;
+public:
+	net_server() : ptr(0) {}
+	~net_server() { close(); }
+	
+	int open(int port, int max, int flags) { ptr = net_server_open(port, max, flags); return ptr != 0; }
+	int close() { int r = net_server_close(ptr); ptr = 0; return r; }
+	
+	int recv(NETPACKET *packet) { return net_server_recv(ptr, packet); }
+	int send(NETPACKET *packet) { return net_server_send(ptr, packet); }
+	int update() { return net_server_update(ptr); }
+	
+	int drop(int client_id, const char *reason) { return net_server_drop(ptr, client_id, reason); } 
+	int newclient() { return net_server_newclient(ptr); }
+	int delclient() { return net_server_delclient(ptr); }
+	
+	void stats(NETSTATS *stats) { net_server_stats(ptr, stats); }
+};
+
+
+class net_client
+{
+	NETCLIENT *ptr;
+public:
+	net_client() : ptr(0) {}
+	~net_client() { close(); }
+	
+	int open(int flags) { ptr = net_client_open(flags); return ptr != 0; }
+	int close() { int r = net_client_close(ptr); ptr = 0; return r; }
+	
+	int connect(NETADDR4 *addr) { return net_client_connect(ptr, addr); }
+	int disconnect(const char *reason) { return net_client_disconnect(ptr, reason); }
+	
+	int recv(NETPACKET *packet) { return net_client_recv(ptr, packet); }
+	int send(NETPACKET *packet) { return net_client_send(ptr, packet); }
+	int update() { return net_client_update(ptr); }
+	
+	int state() { return net_client_state(ptr); }
+	void stats(NETSTATS *stats) { net_client_stats(ptr, stats); }
+};
+#endif
diff --git a/src/engine/packet.h b/src/engine/packet.h
index fd93d744..6c92bf31 100644
--- a/src/engine/packet.h
+++ b/src/engine/packet.h
@@ -1,442 +1,356 @@
+#include <stdarg.h>
 #include <baselib/stream/file.h>
 #include <baselib/network.h>
 
 #include "versions.h"
+#include "ringbuffer.h"
+#include "compression.h"
+#include "snapshot.h"
 
-#define MACRO_MAKEINT(a,b,c,d) ((a<<24)|(b<<16)|(c<<8)|d)
+enum
+{
+	NETMSG_NULL=0,
+	
+	// sent by server
+	NETMSG_MAP,
+	NETMSG_SNAP,
+	NETMSG_SNAPEMPTY,
+	NETMSG_SNAPSMALL,
+	
+	// sent by client
+	NETMSG_INFO,
+	NETMSG_ENTERGAME,
+	NETMSG_INPUT,
+	NETMSG_SNAPACK,
+	
+	// sent by both
+	NETMSG_ERROR,
+};
 
-// TODO: this is not KISS
-class packet
+
+// this should be revised
+enum
 {
-	friend class connection;
-protected:
-	enum
-	{
-		MAX_PACKET_SIZE = 1024,
-	};
-	
-	// packet data
-	struct header
+	MAX_NAME_LENGTH=32,
+	MAX_CLANNAME_LENGTH=32,
+	MAX_INPUT_SIZE=128,
+	MAX_SNAPSHOT_SIZE=64*1024,
+	MAX_SNAPSHOT_PACKSIZE=768
+};
+
+
+class snapshot_storage
+{
+	struct holder
 	{
-		unsigned id;
-		unsigned version;
-		unsigned size_and_flags;
-		unsigned crc;
-		
-		unsigned msg;
-		unsigned ack;
-		unsigned seq;
+		int tick;
+		int data_size;
+		int *data() { return (int *)(this+1); }
 	};
 	
-	unsigned char packet_data[MAX_PACKET_SIZE];
-	unsigned char *current;
+	ring_buffer buffer;
 	
-	// these are used to prepend data in the packet
-	// used for debugging so we have checks that we 
-	// pack and unpack the same way
-	enum
+public:
+	void purge_until(int tick)
 	{
-		DEBUG_TYPE_INT=0x1,
-		DEBUG_TYPE_STR=0x2,
-		DEBUG_TYPE_RAW=0x3,
-	};
+		while(1)
+		{
+			ring_buffer::item *i = buffer.first();
+			if(!i)
+				break;
+			holder *h = (holder *)i->data();
+			if(h->tick < tick)
+				buffer.pop_first();
+			else
+				break;
+		}
+	}
 	
-	// writes an int to the packet
-	void write_int_raw(int i)
+	void purge_all()
 	{
-		// TODO: check for overflow
-		*(int*)current = i;
-		current += sizeof(int);
+		buffer.reset();
 	}
 
-	// reads an int from the packet
-	int read_int_raw()
-	{
-		// TODO: check for overflow
-		int i = *(int*)current;
-		current += sizeof(int);
-		return i;
-	}
-	
-	void debug_insert_mark(int type, int size)
+	void add(int tick, int data_size, void *data)
 	{
-		write_int_raw((type<<16)|size);
+		holder *h = (holder *)buffer.alloc(sizeof(holder)+data_size);
+		h->tick = tick;
+		h->data_size = data_size;
+		mem_copy(h->data(), data, data_size);
 	}
 	
-	void debug_verify_mark(int type, int size)
+	int get(int tick, void **data)
 	{
-		if(read_int_raw() != ((type<<16) | size))
-			dbg_assert(0, "error during packet disassembly");
+		ring_buffer::item *i = buffer.first();
+		while(i)
+		{
+			holder *h = (holder *)i->data();
+			if(h->tick == tick)
+			{
+				*data = h->data();
+				return h->data_size;
+			}
+				
+			i = i->next;
+		}
+		
+		return -1;
 	}
-	
+};
+/*
+class snapshot_delta_builder
+{
 public:
+	static const int MAX_ITEMS = 512;
 
-	enum
-	{
-		FLAG_VITAL=1,
-		FLAG_RESEND=2
-	};
+	char data[MAX_SNAPSHOT_SIZE];
+	int data_size;
 
-	packet(unsigned msg=0)
-	{
-		current = packet_data;
-		current += sizeof(header);
+	int offsets[MAX_ITEMS];
+	int num_items;
+
+	int top_size;
+	int top_items;
 
-		((header*)packet_data)->id = MACRO_MAKEINT('K','M','A',1);
-		((header*)packet_data)->version = TEEWARS_NETVERSION;
-		((header*)packet_data)->msg = msg;
+	int snapnum;
+
+	snapshot_delta_builder()
+	{
+		top_size = 0;
+		top_items = 0;
+		snapnum = 0;
 	}
-	
-	void set_header(unsigned ack, unsigned seq)
+
+	void start()
 	{
-		((header*)packet_data)->ack = ack;
-		((header*)packet_data)->seq = seq;
+		data_size = 0;
+		num_items = 0;
 	}
-	
-	// writes an int to the packet
-	void write_int(int i)
+
+	int finish(void *snapdata)
 	{
-		debug_insert_mark(DEBUG_TYPE_INT, 4);
-		write_int_raw(i);
+		snapnum++;
+
+		// flattern and make the snapshot
+		snapshot *snap = (snapshot *)snapdata;
+		snap->data_size = data_size;
+		snap->num_items = num_items;
+		int offset_size = sizeof(int)*num_items;
+		mem_copy(snap->offsets, offsets, offset_size);
+		mem_copy(snap->data_start(), data, data_size);
+		return sizeof(int) + offset_size + data_size;
+	}
+
+	void *new_item(int type, int id, int size)
+	{
+		snapshot::item *obj = (snapshot::item *)(data+data_size);
+		obj->type_and_id = (type<<16)|id;
+		offsets[num_items] = data_size;
+		data_size += sizeof(int) + size;
+		num_items++;
+		dbg_assert(data_size < MAX_SNAPSHOT_SIZE, "too much data");
+		dbg_assert(num_items < MAX_ITEMS, "too many items");
+
+		return &obj->data;
 	}
+};
+*/
+
+class snapshot_builder
+{
+public:
+	static const int MAX_ITEMS = 512;
 
-	void write_raw(const char *raw, int size)
+	char data[MAX_SNAPSHOT_SIZE];
+	int data_size;
+
+	int offsets[MAX_ITEMS];
+	int num_items;
+
+	int top_size;
+	int top_items;
+
+	int snapnum;
+
+	snapshot_builder()
 	{
-		debug_insert_mark(DEBUG_TYPE_RAW, size);
-		while(size--)
-			*current++ = *raw++;
+		top_size = 0;
+		top_items = 0;
+		snapnum = 0;
 	}
 
-	// writes a string to the packet
-	void write_str(const char *str)
+	void start()
 	{
-		debug_insert_mark(DEBUG_TYPE_STR, 0);
-		int s = strlen(str)+1;
-		write_int_raw(s);
-		for(;*str; current++, str++)
-			*current = *str;
-		*current = 0;
-		current++;
+		data_size = 0;
+		num_items = 0;
 	}
 	
-	// reads an int from the packet
-	int read_int()
+	snapshot::item *get_item(int index) 
 	{
-		debug_verify_mark(DEBUG_TYPE_INT, 4);
-		return read_int_raw();
+		return (snapshot::item *)&(data[offsets[index]]);
 	}
 	
-	// reads a string from the packet
-	const char *read_str()
+	int *get_item_data(int key)
 	{
-		debug_verify_mark(DEBUG_TYPE_STR, 0);
-		int size = read_int_raw();
-		const char *s = (const char *)current;
-		//dbg_msg("packet", "reading string '%s' (%d)", s, size);
-		current += size;
-		return s;
+		for(int i = 0; i < num_items; i++)
+		{
+			if(get_item(i)->key() == key)
+				return (int *)get_item(i)->data();
+		}
+		return 0;
 	}
-	
-	const char *read_raw(int size)
+
+	int finish(void *snapdata)
 	{
-		debug_verify_mark(DEBUG_TYPE_RAW, size);
-		const char *d = (const char *)current;
-		current += size;
-		return d;
+		snapnum++;
+
+		// flattern and make the snapshot
+		snapshot *snap = (snapshot *)snapdata;
+		snap->data_size = data_size;
+		snap->num_items = num_items;
+		int offset_size = sizeof(int)*num_items;
+		mem_copy(snap->offsets(), offsets, offset_size);
+		mem_copy(snap->data_start(), data, data_size);
+		return sizeof(snapshot) + offset_size + data_size;
 	}
-	
-	// TODO: impelement this
-	bool is_good() const { return true; }
 
-	unsigned version() const { return ((header*)packet_data)->version; }
-	unsigned msg() const { return ((header*)packet_data)->msg; }
-	unsigned seq() const { return ((header*)packet_data)->seq; }
-	unsigned ack() const { return ((header*)packet_data)->ack; }
-	unsigned flags() const { return (((header*)packet_data)->size_and_flags) & 0xffff; }
-	
-	// access functions to get the size and data
-	int size() const { return (int)(current-(unsigned char*)packet_data); }
-	int max_size() const { return MAX_PACKET_SIZE; }
-	void *data() { return packet_data; }
+	void *new_item(int type, int id, int size)
+	{
+		snapshot::item *obj = (snapshot::item *)(data+data_size);
+		obj->type_and_id = (type<<16)|id;
+		offsets[num_items] = data_size;
+		data_size += sizeof(snapshot::item) + size;
+		num_items++;
+		dbg_assert(data_size < MAX_SNAPSHOT_SIZE, "too much data");
+		dbg_assert(num_items < MAX_ITEMS, "too many items");
+
+		return obj->data();
+	}
 };
 
-// TODO: remove all the allocations from this class
-class ring_buffer
+class data_packer
 {
-	struct item
+	enum
 	{
-		item *next;
-		item *prev;
-		int size;
+		BUFFER_SIZE=1024*2
 	};
 	
-	item *first;
-	item *last;
-	
-	unsigned buffer_size;
+	unsigned char buffer[BUFFER_SIZE];
+	unsigned char *current;
+	unsigned char *end;
+	int error;
 public:
-	ring_buffer()
-	{
-		first = 0;
-		last = 0;
-		buffer_size = 0;
-	}
-	
-	~ring_buffer()
-	{
-		reset();
-	}
-	
 	void reset()
 	{
-		// clear all
-		while(peek_data())
-			next();		
+		error = 0;
+		current = buffer;
+		end = current + BUFFER_SIZE;
 	}
 	
-	void *alloc(int size)
+	void add_int(int i)
 	{
-		item *i = (item*)mem_alloc(sizeof(item)+size, 1);
-		i->size = size;
-		
-		i->prev = last;
-		i->next = 0;
-		if(last)
-			last->next = i;
-		else
-			first = i;
-		last = i;
-		
-		buffer_size += size;
-		return (void*)(i+1);
-	}
-	
-	unsigned peek_size()
-	{
-		if(!first)
-			return 0;
-		return first->size;
+		// TODO: add space check
+		// TODO: variable length encoding perhaps
+		// TODO: add debug marker
+		current = vint_pack(current, i);
+		//*current++ = (i>>24)&0xff;
+		//*current++ = (i>>16)&0xff;
+		//*current++ = (i>>8)&0xff;
+		//*current++ = i&0xff;
 	}
 
-	void *peek_data()
+	void add_string(const char *p, int limit)
 	{
-		if(!first)
-			return 0;
-		return (void*)(first+1);
-	}
-	
-	void next()
-	{
-		if(first)
+		// TODO: add space check
+		// TODO: add debug marker
+		if(limit > 0)
 		{
-			item *next = first->next;
-			buffer_size += first->size;
-			mem_free(first);
-			first = next;
-			if(first)
-				first->prev = 0;
-			else
-				last = 0;
+			while(*p && limit != 0)
+			{
+				*current++ = *p++;
+				limit--;
+			}
+			*current++ = 0;
+		}
+		else
+		{
+			while(*p)
+				*current++ = *p++;
+			*current++ = 0;
 		}
 	}
 	
-	unsigned size() { return buffer_size; }
-};
-
-//
-class connection
-{
-	baselib::socket_udp4 *socket;
-	baselib::netaddr4 addr;
-	unsigned seq;
-	unsigned ack;
-	
-	unsigned counter_sent_bytes;
-	unsigned counter_recv_bytes;
-	
-	int needs_resend;
-	
-	/*
-	struct resend_packet
-	{
-		resend_packet *next;
-		unsigned seq;
-		unsigned msg;
-		unsigned size;
-		char data[1];
-	};
-	
-	resend_packet *first_resend;
-	resend_packet *last_resend;
-	*/
-	
-	ring_buffer resend_buffer;
-
-	void save_for_resend(packet *p)
-	{
-		/*
-		packet *n = (packet *)resend_buffer.alloc(p->size());
-		mem_copy(n->data(), p->data(), p->size());
-		n->current = (unsigned char*)n->data() + p->size();
-		*/
-	}
-	
-	void remove_resends(unsigned ack)
+	void add_raw(const unsigned char *data, int size)
 	{
-		/*
-		while(1)
+		// TODO: add space check
+		// TODO: add debug marker
+		//add_int(size);
+		while(size)
 		{
-			packet *p = (packet *)resend_buffer.peek_data();
-			if(!p)
-				break;
-			
-			if(p->seq() > ack)
-				break;
-			resend_buffer.next();
-		}*/
+			*current++ = *data++;
+			size--;
+		}
 	}
 	
-public:
-	void counter_reset()
+	int size() const
 	{
-		counter_sent_bytes = 0;
-		counter_recv_bytes = 0;
+		return (const unsigned char *)current-(const unsigned char *)buffer;
 	}
 	
-	void counter_get(unsigned *sent, unsigned *recved)
+	const unsigned char *data()
 	{
-		*sent = counter_sent_bytes;
-		*recved = counter_recv_bytes;
+		return (const unsigned char *)buffer;
 	}
+};
 
-	void init(baselib::socket_udp4 *socket, const baselib::netaddr4 *addr)
+class data_unpacker
+{
+	const unsigned char *current;
+	const unsigned char *start;
+	const unsigned char *end;
+	int error;
+	
+public:
+	void reset(const unsigned char *data, int size)
 	{
-		resend_buffer.reset();
-		
-		this->addr = *addr;
-		this->socket = socket;
-		ack = 0;
-		seq = 0;
-		needs_resend = 0;
-		counter_reset();
+		error = 0;
+		start = data;
+		end = start + size;
+		current = start;
 	}
 	
-	void send(packet *p)
+	int get_int()
 	{
-		if(p->flags()&packet::FLAG_VITAL)
-			seq++;
-		
-		p->set_header(ack, seq);
-
-		if(p->flags()&packet::FLAG_VITAL)
-			save_for_resend(p);
-
-		// TODO: request resend if needed, use needs_resend variable
-		
-		socket->send(&address(), p->data(), p->size());
-		counter_sent_bytes += p->size();
+		int i;
+		current = vint_unpack(current, &i);
+		// TODO: might be changed into variable width
+		// TODO: add range check
+		// TODO: add debug marker
+		//i = (current[0]<<24) | (current[1]<<16) | (current[2]<<8) | (current[3]);
+		//current += 4;
+		return i;
 	}
 	
-	packet *feed(packet *p)
+	const char *get_string()
 	{
-		counter_recv_bytes += p->size();
-		
-		if(p->flags()&packet::FLAG_VITAL)
-		{
-			if(p->seq() == ack+1)
-			{
-				// packet in seqence, ack it
-				ack++;
-				//dbg_msg("network/connection", "packet in sequence. seq/ack=%x", ack);
-				return p;
-			}
-			else if(p->seq() > ack)
-			{
-				// packet loss
-				needs_resend = 1;
-				dbg_msg("network/connection", "packet loss! seq=%x ack=%x+1", p->seq(), ack);
-				return p;
-			}
-			else
-			{
-				// we already got this packet
-				return 0x0;
-			}
-		}
-		
-		// remove resends
-		remove_resends(p->ack());
-		
-		// handle resends
-		if(p->flags()&packet::FLAG_RESEND)
-		{
-			// peer as requested a resend of all non acked packages.
-			
-		}
-		
-		return p;		
+		// TODO: add range check
+		// TODO: add debug marker
+		const char *ptr = (const char *)current;
+		while(*current) // skip the string
+			current++;
+		current++;
+		return ptr;
 	}
 	
-	const baselib::netaddr4 &address() const { return addr; }
-	
-	void update()
+	const unsigned char *get_raw(int size)
 	{
+		// TODO: add range check
+		// TODO: add debug marker
+		//int s = get_int();
+		//if(size)
+			//*size = s;
+		const unsigned char *ptr = current;
+		current += size;
+		return ptr;
 	}
 };
-
-//const char *NETWORK_VERSION = "development";
-
-enum
-{
-	NETMSG_CONTEXT_CONNECT=0x00010000,
-	NETMSG_CONTEXT_GAME=0x00020000,
-	NETMSG_CONTEXT_GLOBAL=0x00040000,
-	
-	// connection phase
-	NETMSG_CLIENT_CONNECT=NETMSG_CONTEXT_CONNECT|1,
-		// str32 name
-		// str32 clan
-		// str32 password
-		// str32 skin	
-	
-	// TODO: These should be implemented to make the server send the map to the client on connect
-	// NETMSG_CLIENT_FETCH,
-	// NETMSG_SERVER_MAPDATA,
-	
-	NETMSG_SERVER_ACCEPT=NETMSG_CONTEXT_CONNECT|2,
-		// str32 mapname
-
-	
-	NETMSG_CLIENT_DONE=NETMSG_CONTEXT_CONNECT|3,
-		// nothing
-	
-	// game phase
-	NETMSG_SERVER_SNAP = NETMSG_CONTEXT_GAME|1, // server will spam these
-		// int num_parts
-		// int part
-		// int size
-		// data *
-		
-	NETMSG_CLIENT_INPUT = NETMSG_CONTEXT_GAME|1, // client will spam these
-		// int input[MAX_INPUTS]
-	
-	NETMSG_SERVER_EVENT = NETMSG_CONTEXT_GAME|2,
-	NETMSG_CLIENT_EVENT = NETMSG_CONTEXT_GAME|2,
-
-	NETMSG_CLIENT_CHECKALIVE = NETMSG_CONTEXT_GAME|3, // check if client is alive
-	
-	NETMSG_CLIENT_ERROR=0x0fffffff,
-		// str128 reason
-		
-	NETMSG_SERVER_ERROR=0x0fffffff,
-		// str128 reason
-};
-
-enum
-{
-	MAX_NAME_LENGTH=32,
-	MAX_CLANNAME_LENGTH=32,
-	MAX_INPUT_SIZE=128,
-	MAX_SNAPSHOT_SIZE=64*1024,
-	MAX_SNAPSHOT_PACKSIZE=768
-};
diff --git a/src/engine/ringbuffer.h b/src/engine/ringbuffer.h
new file mode 100644
index 00000000..3208efbf
--- /dev/null
+++ b/src/engine/ringbuffer.h
@@ -0,0 +1,84 @@
+
+// TODO: remove all the allocations from this class
+class ring_buffer
+{
+public:
+	struct item
+	{
+		item *next;
+		item *prev;
+		int size;
+		unsigned char *data() { return (unsigned char *)(this+1); }
+	};
+	
+	item *first_item;
+	item *last_item;
+	
+	unsigned buffer_size;
+	
+	ring_buffer()
+	{
+		first_item = 0;
+		last_item = 0;
+		buffer_size = 0;
+	}
+	
+	~ring_buffer()
+	{
+		reset();
+	}
+	
+	void reset()
+	{
+		// clear all
+		while(first())
+			pop_first();
+	}
+	
+	void *alloc(int size)
+	{
+		item *i = (item*)mem_alloc(sizeof(item)+size, 1);
+		i->size = size;
+		
+		i->prev = last_item;
+		i->next = 0;
+		if(last_item)
+			last_item->next = i;
+		else
+			first_item = i;
+		last_item = i;
+		
+		buffer_size += size;
+		return i->data();
+	}
+	
+	item *first()
+	{
+		return first_item;
+	}
+
+	/*
+	void *peek_data()
+	{
+		if(!first)
+			return 0;
+		return (void*)(first+1);
+	}*/
+	
+	void pop_first()
+	{
+		if(first_item)
+		{
+			item *next = first_item->next;
+			buffer_size -= first_item->size;
+			mem_free(first_item);
+			first_item = next;
+			if(first_item)
+				first_item->prev = 0;
+			else
+				last_item = 0;
+		}
+	}
+	
+	unsigned size() { return buffer_size; }
+};
diff --git a/src/engine/server/server.cpp b/src/engine/server/server.cpp
index d0ae80f0..5632689e 100644
--- a/src/engine/server/server.cpp
+++ b/src/engine/server/server.cpp
@@ -1,138 +1,23 @@
-#include <stdio.h>
-#include <string.h>
+#include <cstdio>
+#include <cstring>
+#include <cstdlib>
 
 #include <baselib/system.h>
 
 #include <engine/interface.h>
 
-//#include "socket.h"
 #include <engine/packet.h>
 #include <engine/snapshot.h>
 
-#include <engine/lzw.h>
+#include <engine/compression.h>
 #include <engine/versions.h>
 
-namespace baselib {}
-using namespace baselib;
-
-int net_addr4_cmp(const NETADDR4 *a, const NETADDR4 *b)
-{
-	if(
-		a->ip[0] != b->ip[0] ||
-		a->ip[1] != b->ip[1] ||
-		a->ip[2] != b->ip[2] ||
-		a->ip[3] != b->ip[3] ||
-		a->port != b->port
-	)
-		return 1;
-	return 0;
-}
-
-// --- string handling (MOVE THESE!!) ---
-void snap_encode_string(const char *src, int *dst, int length, int max_length)
-{
-        const unsigned char *p = (const unsigned char *)src;
-
-        // handle whole int
-        for(int i = 0; i < length/4; i++)
-        {
-                *dst = (p[0]<<24|p[1]<<16|p[2]<<8|p[3]);
-                p += 4;
-                dst++;
-        }
-
-        // take care of the left overs
-        int left = length%4;
-        if(left)
-        {
-                unsigned last = 0;
-                switch(left)
-                {
-                        case 3: last |= p[2]<<8;
-                        case 2: last |= p[1]<<16;
-                        case 1: last |= p[0]<<24;
-                }
-                *dst = last;
-        }
-}
-
-
-class snapshot_builder
-{
-public:
-	static const int MAX_ITEMS = 512;
-	//static const int MAX_DATA_SIZE=1*1024;
-
-	char data[MAX_SNAPSHOT_SIZE];
-	int data_size;
-
-	int offsets[MAX_ITEMS];
-	int num_items;
-
-	int top_size;
-	int top_items;
-
-	int snapnum;
-
-	snapshot_builder()
-	{
-		top_size = 0;
-		top_items = 0;
-		snapnum = 0;
-	}
-
-	void start()
-	{
-		data_size = 0;
-		num_items = 0;
-	}
+#include <engine/network.h>
+#include <engine/config.h>
 
-	int finish(void *snapdata)
-	{
-		snapnum++;
 
-		// collect some data
-		/*
-		int change = 0;
-		if(data_size > top_size)
-		{
-			change++;
-			top_size = data_size;
-		}
-
-		if(num_items > top_items)
-		{
-			change++;
-			top_items = num_items;
-		}
-
-		if(change)
-		{
-			dbg_msg("snapshot", "new top, items=%d size=%d", top_items, top_size);
-		}*/
-
-		// flattern and make the snapshot
-		snapshot *snap = (snapshot *)snapdata;
-		snap->num_items = num_items;
-		int offset_size = sizeof(int)*num_items;
-		mem_copy(snap->offsets, offsets, offset_size);
-		mem_copy(snap->data_start(), data, data_size);
-		return sizeof(int) + offset_size + data_size;
-	}
-
-	void *new_item(int type, int id, int size)
-	{
-		snapshot::item *obj = (snapshot::item *)(data+data_size);
-		obj->type_and_id = (type<<16)|id;
-		offsets[num_items] = data_size;
-		data_size += sizeof(int) + size;
-		num_items++;
-		dbg_assert(data_size < MAX_SNAPSHOT_SIZE, "too much data");
-		dbg_assert(num_items < MAX_ITEMS, "too many items");
-
-		return &obj->data;
-	}
-};
+namespace baselib {}
+using namespace baselib;
 
 static snapshot_builder builder;
 
@@ -143,7 +28,6 @@ void *snap_new_item(int type, int id, int size)
 	return builder.new_item(type, id, size);
 }
 
-
 //
 class client
 {
@@ -157,34 +41,21 @@ public:
 
 	// connection state info
 	int state;
-
-	// (ticks) if lastactivity > 5 seconds kick him
-	int64 lastactivity;
-	connection conn;
+	
+	int last_acked_snapshot;
+	snapshot_storage snapshots;
 
 	char name[MAX_NAME_LENGTH];
 	char clan[MAX_CLANNAME_LENGTH];
-	/*
-	client()
-	{
-		state = STATE_EMPTY;
-		name[0] = 0;
-		clan[0] = 0;
-	}
-
-	~client()
-	{
-		dbg_assert(state == STATE_EMPTY, "client destoyed while in use");
-	}*/
 
 	bool is_empty() const { return state == STATE_EMPTY; }
 	bool is_ingame() const { return state == STATE_INGAME; }
-	const netaddr4 &address() const { return conn.address(); }
 };
 
 static client clients[MAX_CLIENTS];
 static int current_tick = 0;
 static int send_heartbeats = 1;
+static net_server net;
 
 int server_tick()
 {
@@ -193,7 +64,7 @@ int server_tick()
 
 int server_tickspeed()
 {
-	return 50;
+	return SERVER_TICK_SPEED;
 }
 
 int server_init()
@@ -203,7 +74,7 @@ int server_init()
 		clients[i].state = client::STATE_EMPTY;
 		clients[i].name[0] = 0;
 		clients[i].clan[0] = 0;
-		clients[i].lastactivity = 0;
+		//clients[i].lastactivity = 0;
 	}
 
 	current_tick = 0;
@@ -225,12 +96,27 @@ int server_getclientinfo(int client_id, client_info *info)
 	return 0;
 }
 
-//
+
+int server_send_msg(int client_id)
+{
+	const msg_info *info = msg_get_info();
+	NETPACKET packet;
+	packet.client_id = client_id;
+	packet.data = info->data;
+	packet.data_size = info->size;
+
+	if(info->flags&MSGFLAG_VITAL)	
+		packet.flags = PACKETFLAG_VITAL;
+	
+	net.send(&packet);
+	return 0;
+}
+	
+// TODO: remove this class
 class server
 {
 public:
-
-	socket_udp4 game_socket;
+	//socket_udp4 game_socket;
 
 	const char *map_name;
 	const char *server_name;
@@ -256,14 +142,14 @@ public:
 		}
 
 		// start server
-		if(!game_socket.open(8303))
+		if(!net.open(8303, 0, 0))
 		{
 			dbg_msg("network/server", "couldn't open socket");
 			return false;
 		}
 
-		for(int i = 0; i < MAX_CLIENTS; i++)
-			dbg_msg("network/server", "\t%d: %d", i, clients[i].state);
+		//for(int i = 0; i < MAX_CLIENTS; i++)
+			//dbg_msg("network/server", "\t%d: %d", i, clients[i].state);
 
 		if (net_host_lookup(MASTER_SERVER_ADDRESS, MASTER_SERVER_PORT, &master_server) != 0)
 		{
@@ -304,17 +190,6 @@ public:
 					snaptime += time_get()-start;
 				}
 
-				// Check for client timeouts
-				for (int i = 0; i < MAX_CLIENTS; i++)
-				{
-					if (clients[i].state != client::STATE_EMPTY)
-					{
-						// check last activity time
-						if (((lasttick - clients[i].lastactivity) / time_freq()) > SERVER_CLIENT_TIMEOUT)
-							client_timeout(i);
-					}
-				}
-
 				lasttick += time_per_tick;
 			}
 
@@ -332,8 +207,7 @@ public:
 
 						// TODO: fix me
 						netaddr4 me(127, 0, 0, 0, 8303);
-
-						send_heartbeat(0, &me, players, MAX_CLIENTS, server_name, mapname);
+						//send_heartbeat(0, &me, players, MAX_CLIENTS, server_name, mapname);
 					}
 
 					lastheartbeat = t+time_per_heartbeat;
@@ -357,6 +231,7 @@ public:
 					(simulationtime+snaptime+networktime)/(float)reportinterval*100.0f);
 
 				unsigned sent_total=0, recv_total=0;
+				/*
 				for (int i = 0; i < MAX_CLIENTS; i++)
 					if (!clients[i].is_empty())
 					{
@@ -366,7 +241,7 @@ public:
 						sent_total += s;
 						recv_total += r;
 					}
-
+				*/
 
 				dbg_msg("server/report", "biggestsnap=%d send=%d recv=%d",
 					biggest_snapshot, sent_total/3, recv_total/3);
@@ -393,6 +268,8 @@ public:
 
 	void snap()
 	{
+		//if(current_tick&1)
+		//	return;
 		mods_presnap();
 
 		for(int i = 0; i < MAX_CLIENTS; i++)
@@ -400,33 +277,86 @@ public:
 			if(clients[i].is_ingame())
 			{
 				char data[MAX_SNAPSHOT_SIZE];
+				char deltadata[MAX_SNAPSHOT_SIZE];
 				char compdata[MAX_SNAPSHOT_SIZE];
+				//char intdata[MAX_SNAPSHOT_SIZE];
 				builder.start();
 				mods_snap(i);
 
 				// finish snapshot
 				int snapshot_size = builder.finish(data);
 
-				// compress it
-				int compsize = lzw_compress(data, snapshot_size, compdata);
-				snapshot_size = compsize;
-
-				if(snapshot_size > biggest_snapshot)
-					biggest_snapshot = snapshot_size;
-
-				const int max_size = MAX_SNAPSHOT_PACKSIZE;
-				int numpackets = (snapshot_size+max_size-1)/max_size;
-				for(int n = 0, left = snapshot_size; left; n++)
+				// remove old snapshos
+				// keep 1 seconds worth of snapshots
+				clients[i].snapshots.purge_until(current_tick-SERVER_TICK_SPEED);
+				
+				// save it the snapshot
+				clients[i].snapshots.add(current_tick, snapshot_size, data);
+				
+				// find snapshot that we can preform delta against
+				static snapshot emptysnap;
+				emptysnap.data_size = 0;
+				emptysnap.num_items = 0;
+				
+				snapshot *deltashot = &emptysnap;
+				int deltashot_size;
+				int delta_tick = -1;
+				{
+					void *delta_data;
+					deltashot_size = clients[i].snapshots.get(clients[i].last_acked_snapshot, (void **)&delta_data);
+					if(deltashot_size >= 0)
+					{
+						delta_tick = clients[i].last_acked_snapshot;
+						deltashot = (snapshot *)delta_data;
+					}
+				}
+				
+				// create delta
+				int deltasize = snapshot_create_delta(deltashot, (snapshot*)data, deltadata);
+				
+				if(deltasize)
 				{
-					int chunk = left < max_size ? left : max_size;
-					left -= chunk;
-
-					packet p(NETMSG_SERVER_SNAP);
-					p.write_int(numpackets);
-					p.write_int(n);
-					p.write_int(chunk);
-					p.write_raw(&compdata[n*max_size], chunk);
-					clients[i].conn.send(&p);
+					// compress it
+					//int intsize = -1;
+					unsigned char intdata[MAX_SNAPSHOT_SIZE];
+					int intsize = intpack_compress(deltadata, deltasize, intdata);
+					
+					int compsize = zerobit_compress(intdata, intsize, compdata);
+					//dbg_msg("compress", "%5d --delta-> %5d --int-> %5d --zero-> %5d %5d",
+						//snapshot_size, deltasize, intsize, compsize, intsize-compsize);
+					snapshot_size = compsize;
+
+					if(snapshot_size > biggest_snapshot)
+						biggest_snapshot = snapshot_size;
+
+					const int max_size = MAX_SNAPSHOT_PACKSIZE;
+					int numpackets = (snapshot_size+max_size-1)/max_size;
+					for(int n = 0, left = snapshot_size; left; n++)
+					{
+						int chunk = left < max_size ? left : max_size;
+						left -= chunk;
+
+						if(numpackets == 1)
+							msg_pack_start(NETMSG_SNAPSMALL, 0);
+						else
+							msg_pack_start(NETMSG_SNAP, 0);
+						msg_pack_int(current_tick);
+						msg_pack_int(current_tick-delta_tick); // compressed with
+						msg_pack_int(chunk);
+						msg_pack_raw(&compdata[n*max_size], chunk);
+						msg_pack_end();
+						//const msg_info *info = msg_get_info();
+						//dbg_msg("server", "size=%d", info->size);
+						server_send_msg(i);
+					}
+				}
+				else
+				{
+					msg_pack_start(NETMSG_SNAPEMPTY, 0);
+					msg_pack_int(current_tick);
+					msg_pack_int(current_tick-delta_tick); // compressed with
+					msg_pack_end();
+					server_send_msg(i);
 				}
 			}
 		}
@@ -434,11 +364,12 @@ public:
 		mods_postsnap();
 	}
 
-	void send_accept(client *client, const char *map)
+	void send_map(int cid)
 	{
-		packet p(NETMSG_SERVER_ACCEPT);
-		p.write_str(map);
-		client->conn.send(&p);
+		msg_pack_start(NETMSG_MAP, MSGFLAG_VITAL);
+		msg_pack_string(map_name, 0);
+		msg_pack_end();
+		server_send_msg(cid);
 	}
 
 	void drop(int cid, const char *reason)
@@ -451,161 +382,47 @@ public:
 		dbg_msg("game", "player dropped. reason='%s' cid=%x name='%s'", reason, cid, clients[cid].name);
 	}
 
-	int find_client(const netaddr4 *addr)
+	void process_client_packet(NETPACKET *packet)
 	{
-		// fetch client
-		for(int i = 0; i < MAX_CLIENTS; i++)
+		int cid = packet->client_id;
+		int msg = msg_unpack_start(packet->data, packet->data_size);
+		if(msg == NETMSG_INFO)
 		{
-			if(!clients[i].is_empty() && clients[i].address() == *addr)
-				return i;
+			strncpy(clients[cid].name, msg_unpack_string(), MAX_NAME_LENGTH);
+			strncpy(clients[cid].clan, msg_unpack_string(), MAX_CLANNAME_LENGTH);
+			const char *password = msg_unpack_string();
+			const char *skin = msg_unpack_string();
+			(void)password; // ignore these variables
+			(void)skin;
+			send_map(cid);
 		}
-		return -1;
-	}
-
-	void client_process_packet(int cid, packet *p)
-	{
-		clients[cid].lastactivity = lasttick;
-		if(p->msg() == NETMSG_CLIENT_DONE)
+		else if(msg == NETMSG_ENTERGAME)
 		{
 			dbg_msg("game", "player as entered the game. cid=%x", cid);
 			clients[cid].state = client::STATE_INGAME;
 			mods_client_enter(cid);
 		}
-		else if(p->msg() == NETMSG_CLIENT_INPUT)
+		else if(msg == NETMSG_INPUT)
 		{
 			int input[MAX_INPUT_SIZE];
-			int size = p->read_int();
+			int size = msg_unpack_int();
 			for(int i = 0; i < size/4; i++)
-				input[i] = p->read_int();
-			if(p->is_good())
-			{
-				//dbg_msg("network/server", "applying input %d %d %d", input[0], input[1], input[2]);
-				mods_client_input(cid, input);
-			}
+				input[i] = msg_unpack_int();
+			mods_client_input(cid, input);
 		}
-		else if(p->msg() == NETMSG_CLIENT_ERROR)
+		else if(msg == NETMSG_SNAPACK)
 		{
-			const char *reason = p->read_str();
-			if(p->is_good())
-				dbg_msg("network/server", "client error. cid=%x reason='%s'", cid, reason);
-			else
-				dbg_msg("network/server", "client error. cid=%x", cid);
-			drop(cid, "client error");
+			clients[cid].last_acked_snapshot = msg_unpack_int();
 		}
 		else
 		{
-			dbg_msg("network/server", "invalid message. cid=%x msg=%x", cid, p->msg());
-			drop(cid, "invalid message");
+			dbg_msg("server", "strange message cid=%d msg=%d data_size=%d", cid, msg, packet->data_size);
 		}
+		
 	}
 
-	void process_packet(packet *p, netaddr4 *from)
+	void process_packet(NETPACKET *packet)
 	{
-		// do version check
-		if(p->version() != TEEWARS_NETVERSION)
-		{
-			// send an empty packet back.
-			// this will allow the client to check the version
-			packet p;
-			game_socket.send(from, p.data(), p.size());
-			return;
-		}
-
-		if(p->msg() == NETMSG_CLIENT_CONNECT)
-		{
-			// we got no state for this client yet
-			const char *version;
-			const char *name;
-			const char *clan;
-			const char *password;
-			const char *skin;
-
-			version = p->read_str();
-			name = p->read_str();
-			clan = p->read_str();
-			password = p->read_str();
-			skin = p->read_str();
-
-			if(p->is_good())
-			{
-				/*
-				// check version
-				if(strcmp(version, TEEWARS_NETVERSION) != 0)
-				{
-					dbg_msg("network/server", "wrong version connecting '%s'", version);
-					// TODO: send error
-					return;
-				}*/
-
-				// look for empty slot, linear search
-				int id = -1;
-				for(int i = 0; i < MAX_CLIENTS; i++)
-					if(clients[i].is_empty())
-					{
-						id = i;
-						break;
-					}
-
-				if(id != -1)
-				{
-					// slot found
-					// TODO: perform correct copy here
-					mem_copy(clients[id].name, name, MAX_NAME_LENGTH);
-					mem_copy(clients[id].clan, clan, MAX_CLANNAME_LENGTH);
-					clients[id].state = client::STATE_CONNECTING;
-					clients[id].conn.init(&game_socket, from);
-
-					clients[id].lastactivity = lasttick;
-					clients[id].name[MAX_NAME_LENGTH-1] = 0;
-					clients[id].clan[MAX_CLANNAME_LENGTH-1] = 0;
-
-					dbg_msg("network/server", "client connected. '%s' on slot %d", name, id);
-
-					// TODO: return success
-					send_accept(&clients[id], map_name);
-				}
-				else
-				{
-					// no slot found
-					// TODO: send error
-					dbg_msg("network/server", "client connected but server is full");
-
-					for(int i = 0; i < MAX_CLIENTS; i++)
-						dbg_msg("network/server", "\t%d: %d", i, clients[i].state);
-				}
-			}
-		}
-		else
-		{
-			int cid = find_client(from);
-			if(cid >= 0)
-			{
-				if(clients[cid].conn.feed(p))
-				{
-					// packet is ok
-					unsigned msg = p->msg();
-
-					// client found, check state
-					if(((msg>>16)&0xff)&clients[cid].state)
-					{
-						// state is ok
-						client_process_packet(cid, p);
-					}
-					else
-					{
-						// invalid state, disconnect the client
-						drop(cid, "invalid message at this state");
-					}
-				}
-				else
-				{
-					drop(cid, "connection error");
-				}
-
-			}
-			else
-				dbg_msg("network/server", "packet from strange address.");
-		}
 	}
 
 	void client_timeout(int clientId)
@@ -615,67 +432,53 @@ public:
 
 	void pump_network()
 	{
+		net.update();
+		
+		// process packets
+		NETPACKET packet;
+		while(net.recv(&packet))
+		{
+			
+			if(packet.client_id == -1)
+			{
+				// stateless
+			}
+			else
+				process_client_packet(&packet);
+		}
+		
+		// check for removed clients
 		while(1)
 		{
-			packet p;
-			netaddr4 from;
-
-			//int bytes = net_udp4_recv(
-			int bytes = game_socket.recv(&from, p.data(), p.max_size());
-			//int bytes = game_socket.recv(&from, p.data(), p.max_size());
-			if(bytes <= 0)
+			int cid = net.delclient();
+			if(cid == -1)
 				break;
-
-			process_packet(&p, &from);
+			
+			clients[cid].state = client::STATE_EMPTY;
+			clients[cid].name[0] = 0;
+			clients[cid].clan[0] = 0;
+			clients[cid].snapshots.purge_all();
+			
+			mods_client_drop(cid);
+			
+			dbg_msg("server", "del client %d", cid);
+		}
+		
+		// check for new clients
+		while(1)
+		{
+			int cid = net.newclient();
+			if(cid == -1)
+				break;
+			
+			clients[cid].state = client::STATE_CONNECTING;
+			clients[cid].name[0] = 0;
+			clients[cid].clan[0] = 0;
+			clients[cid].snapshots.purge_all();
+			clients[cid].last_acked_snapshot = -1;
+			
+			dbg_msg("server", "new client %d", cid);
 		}
-		// TODO: check for client timeouts
-	}
-
-	char *write_int(char *buffer, int integer)
-	{
-		*buffer++ = integer >> 24;
-		*buffer++ = integer >> 16;
-		*buffer++ = integer >> 8;
-		*buffer++ = integer;
-
-		return buffer;
-	}
-
-	char *write_netaddr4(char *buffer, NETADDR4 *address)
-	{
-		*buffer++ = address->ip[0];
-		*buffer++ = address->ip[1];
-		*buffer++ = address->ip[2];
-		*buffer++ = address->ip[3];
-
-		return write_int(buffer, address->port);
-	}
-
-	void send_heartbeat(int version, netaddr4 *address, int players, int max_players, const char *name, const char *map_name)
-	{
-		char buffer[216] = {0};
-		char *d = buffer;
-
-		d = write_int(d, 'TWHB');
-		d = write_int(d, version);
-		d = write_netaddr4(d, address);
-		d = write_int(d,players);
-		d = write_int(d, max_players);
-
-		int len = strlen(name);
-		if (len > 128)
-			len = 128;
-
-		memcpy(d, name, len);
-		d += 128;
-
-		len = strlen(map_name);
-		if (len > 64)
-			len = 64;
-
-		memcpy(d, map_name, len);
-		d += 64;
-		game_socket.send(&master_server, buffer, sizeof(buffer));
 	}
 };
 
@@ -683,8 +486,14 @@ int main(int argc, char **argv)
 {
 	dbg_msg("server", "starting...");
 
+	dbg_msg("server", "%d %d", sizeof(snapshot), sizeof(snapshot::item));
+
+	config_reset();
+	config_load("server.cfg");
+
 	const char *mapname = "data/demo.map";
 	const char *servername = 0;
+	
 	// parse arguments
 	for(int i = 1; i < argc; i++)
 	{
@@ -705,6 +514,12 @@ int main(int argc, char **argv)
 			// -p (private server)
 			send_heartbeats = 0;
 		}
+		else if(argv[i][0] == '-' && argv[i][1] == 'o' && argv[i][2] == 0)
+		{
+			// -o port
+			i++;
+			config_set_sv_port(&config, atol(argv[i]));
+		}
 	}
 
 	if(!mapname)
diff --git a/src/engine/snapshot.cpp b/src/engine/snapshot.cpp
new file mode 100644
index 00000000..2f6f1f36
--- /dev/null
+++ b/src/engine/snapshot.cpp
@@ -0,0 +1,235 @@
+
+#include "packet.h"
+#include "snapshot.h"
+
+struct snapshot_delta
+{
+	int num_deleted_items;
+	int num_update_items;
+	int num_temp_items; // needed?
+	int data[1];
+	
+	/*
+	char *data_start() { return (char *)&offsets[num_deleted_items+num_update_items+num_temp_items]; }
+	
+	int deleted_item(int index) { return offsets[index]; }
+	item *update_item(int index) { return (item *)(data_start() + offsets[num_deleted_items+index]); }
+	item *temp_item(int index) { return (item *)(data_start() + offsets[num_deleted_items+num_update_items+index]); }
+	* */
+};
+
+
+static const int MAX_ITEMS = 512;
+static snapshot_delta empty = {0,0,0,{0}};
+
+void *snapshot_empty_delta()
+{
+	return &empty;
+}
+
+static int diff_item(int *past, int *current, int *out, int size)
+{
+	/*
+	int needed = 0;
+	while(size)
+	{
+		*out = *current-*past;
+		if(*out)
+			needed = 1;
+		out++;
+		current++;
+		past++;
+		size--;
+	}*/
+
+	int needed = 0;
+	while(size)
+	{
+		*out = *current-*past;
+		if(*out)
+			needed = 1;
+		
+		out++;
+		past++;
+		current++;
+		size--;
+	}
+	
+	return needed;
+}
+
+// 1 = 4-3
+// d = n-p
+
+// n(4) = p(3)+d(1)
+
+static void undiff_item(int *past, int *diff, int *out, int size)
+{
+	while(size)
+	{
+		*out = *past+*diff;
+		out++;
+		past++;
+		diff++;
+		size--;
+	}
+}
+
+int snapshot_create_delta(snapshot *from, snapshot *to, void *dstdata)
+{
+	//int deleted[MAX_ITEMS];
+	//int update[MAX_ITEMS];
+	//int mark[MAX_ITEMS];
+	//char data[MAX_SNAPSHOT_SIZE];
+	
+	snapshot_delta *delta = (snapshot_delta *)dstdata;
+	int *data = (int *)delta->data;
+	
+	
+	delta->num_deleted_items = 0;
+	delta->num_update_items = 0;
+	delta->num_temp_items = 0;
+
+	// pack deleted stuff
+	for(int i = 0; i < from->num_items; i++)
+	{
+		snapshot::item *fromitem = from->get_item(i);
+		if(to->get_item_index(fromitem->key()) == -1)
+		{
+			// deleted
+			delta->num_deleted_items++;
+			*data = fromitem->key();
+			data++;
+		}
+	}
+	
+	// pack updated stuff
+	int count = 0, size_count = 0;
+	for(int i = 0; i < to->num_items; i++)
+	{
+		// do delta
+		int itemsize = to->get_item_datasize(i);
+		
+		snapshot::item *curitem = to->get_item(i);
+		int pastindex = from->get_item_index(curitem->key());
+		if(pastindex != -1)
+		{
+			snapshot::item *pastitem = from->get_item(pastindex);
+			if(diff_item((int*)pastitem->data(), (int*)curitem->data(), data+3, itemsize/4))
+			{
+				*data++ = itemsize;
+				*data++ = curitem->type();
+				*data++ = curitem->id();
+				//*data++ = curitem->key();
+				data += itemsize/4;
+				delta->num_update_items++;
+			}
+		}
+		else
+		{
+			*data++ = itemsize;
+			*data++ = curitem->type();
+			*data++ = curitem->id();
+			//*data++ = curitem->key();
+			
+			mem_copy(data, curitem->data(), itemsize);
+			size_count += itemsize;
+			data += itemsize/4;
+			delta->num_update_items++;
+			count++;
+		}
+	}
+	
+	if(0)
+	{
+		dbg_msg("snapshot", "%d %d %d",
+			delta->num_deleted_items,
+			delta->num_update_items,
+			delta->num_temp_items);
+	}
+
+	// TODO: pack temp stuff
+	
+	// finish
+	//mem_copy(delta->offsets, deleted, delta->num_deleted_items*sizeof(int));
+	//mem_copy(&(delta->offsets[delta->num_deleted_items]), update, delta->num_update_items*sizeof(int));
+	//mem_copy(&(delta->offsets[delta->num_deleted_items+delta->num_update_items]), temp, delta->num_temp_items*sizeof(int));
+	//mem_copy(delta->data_start(), data, data_size);
+	//delta->data_size = data_size;
+	
+	if(!delta->num_deleted_items && !delta->num_update_items && !delta->num_temp_items)
+		return 0;
+	
+	return (int)((char*)data-(char*)dstdata);
+}
+
+int snapshot_unpack_delta(snapshot *from, snapshot *to, void *srcdata, int data_size)
+{
+	snapshot_builder builder;
+	snapshot_delta *delta = (snapshot_delta *)srcdata;
+	int *data = (int *)delta->data;
+	
+	builder.start();
+	
+	// unpack deleted stuff
+	int *deleted = data;
+	data += delta->num_deleted_items;
+
+	// copy all non deleted stuff
+	for(int i = 0; i < from->num_items; i++)
+	{
+		//dbg_assert(0, "fail!");
+		snapshot::item *fromitem = from->get_item(i);
+		int itemsize = from->get_item_datasize(i);
+		int keep = 1;
+		for(int d = 0; d < delta->num_deleted_items; d++)
+		{
+			if(deleted[d] == fromitem->key())
+			{
+				keep = 0;
+				break;
+			}
+		}
+		
+		if(keep)
+		{
+			// keep it
+			int *newdata = (int *)(snapshot::item *)builder.new_item(fromitem->type(), fromitem->id(), itemsize);
+			mem_copy(newdata, fromitem->data(), itemsize);
+		}
+	}
+		
+	// unpack updated stuff
+	for(int i = 0; i < delta->num_update_items; i++)
+	{
+		int itemsize, id, type, key;
+		itemsize = *data++;
+		//key = *data++;
+		type = *data++;
+		id = *data++;
+		
+		key = (type<<16)|id;
+		
+		// create the item if needed
+		int *newdata = builder.get_item_data(key);
+		if(!newdata)
+			newdata = (int *)builder.new_item(key>>16, key&0xffff, itemsize);
+			
+		int fromindex = from->get_item_index(key);
+		if(fromindex != -1)
+		{
+			// we got an update so we need to apply the diff
+			int *pastdata = (int *)from->get_item(fromindex)->data();
+			undiff_item(pastdata, data, newdata, itemsize/4);
+		}
+		else // no previous, just copy the data
+			mem_copy(newdata, data, itemsize);
+			
+		data += itemsize/4;
+	}
+	
+	// TODO: unpack temp stuff
+	
+	// finish up
+	return builder.finish(to);
+}
diff --git a/src/engine/snapshot.h b/src/engine/snapshot.h
index 9d803486..9af94a3b 100644
--- a/src/engine/snapshot.h
+++ b/src/engine/snapshot.h
@@ -1,19 +1,47 @@
+#ifndef FILE_SNAPSHOT_H
+#define FILE_SNAPSHOT_H
 
 struct snapshot
 {
+	int data_size;
 	int num_items;
-	int offsets[1];
-
+	
 	struct item
 	{
 		int type_and_id;
-		char data[1];
-		
+
+		int *data() { return (int *)(this+1); }		
 		int type() { return type_and_id>>16; }
 		int id() { return type_and_id&(0xffff); }
+		int key() { return type_and_id; }
 	};
+
+	int *offsets() { return (int *)(this+1); }		
+	char *data_start() { return (char *)(offsets() + num_items); }
+	item *get_item(int index) { return (item *)(data_start() + offsets()[index]); };
 	
-	char *data_start() { return (char *)&offsets[num_items]; }
-	item *get_item(int index) { return (item *)(data_start() + offsets[index]); };
+	// returns the number of ints in the item data
+	int get_item_datasize(int index)
+	{
+		if(index == num_items-1)
+			return (data_size - offsets()[index]) - sizeof(item);
+		return (offsets()[index+1] - offsets()[index]) - sizeof(item);
+	}
+	
+	int get_item_index(int key)
+	{
+		// TODO: this should not be a linear search. very bad
+		for(int i = 0; i < num_items; i++)
+		{
+			if(get_item(i)->key() == key)
+				return i;
+		}
+		return -1;
+	}
 };
 
+void *snapshot_empty_delta();
+int snapshot_create_delta(snapshot *from, snapshot *to, void *data);
+int snapshot_unpack_delta(snapshot *from, snapshot *to, void *data, int data_size);
+
+#endif // FILE_SNAPSHOT_H
diff --git a/src/engine/versions.h b/src/engine/versions.h
index f5fceb48..7b4fdc2a 100644
--- a/src/engine/versions.h
+++ b/src/engine/versions.h
@@ -1,3 +1,3 @@
 #define TEEWARS_NETVERSION 0xffffffff
 //#define TEEWARS_NETVERSION_STRING "dev v2"
-#define TEEWARS_VERSION "0.2.1-dev"
+#define TEEWARS_VERSION "0.2.0-dev"
diff --git a/src/game/client/game_client.cpp b/src/game/client/game_client.cpp
index 2e95f187..2f05aa77 100644
--- a/src/game/client/game_client.cpp
+++ b/src/game/client/game_client.cpp
@@ -1,128 +1,46 @@
+#include <baselib/math.h>
 #include <stdlib.h>
 #include <stdio.h>
 #include <engine/config.h>
 #include "../game.h"
 #include "mapres_image.h"
 #include "mapres_tilemap.h"
+#include "data.h"
 
 using namespace baselib;
 
-static int texture_char_default = 0;
-static int texture_game = 0;
-static int texture_weapon = 0;
-static int texture_sun = 0;
-static int texture_particles = 0;
+static data_container *data;
 
-struct weapontexcell
-{
-	float x;
-	float y;
-	float w;
-	float h;
-};
-struct renderparams
-{
-	float sizex;
-	float sizey;
-	float offsetx;
-	float offsety;
-};
-int numcellsx = 32;
-int numcellsy = 32;
-renderparams weaponrenderparams[WEAPON_NUMWEAPONS];
-renderparams modifierrenderparams[WEAPON_NUMWEAPONS];
-
-weapontexcell weaponprojtexcoord[WEAPON_NUMWEAPONS];
-weapontexcell weapontexcoord[WEAPON_NUMWEAPONS];
-weapontexcell weapontexcoordcursor[WEAPON_NUMWEAPONS];
-
-weapontexcell poweruptexcoord[POWERUP_TYPE_NUMPOWERUPS];
-
-weapontexcell modifiertexcoord[MODIFIER_NUMMODIFIERS];
-weapontexcell modifiertexcoordcursor[MODIFIER_NUMMODIFIERS];
-
-int nummuzzletex[WEAPON_NUMWEAPONS];
-weapontexcell muzzletexcoord[WEAPON_NUMWEAPONS][3];
-renderparams muzzleparams[WEAPON_NUMWEAPONS];
-
-#define NUMHADOKENS 6
-#define NUMSTARS 2
-#define NUMPARTICLES 9
-int particlesnumcellsx = 16;
-int particlesnumcellsy = 16;
-weapontexcell chaintexcoord;
-weapontexcell chainheadtexcoord;
-weapontexcell stars[NUMSTARS];
-
-float lifemodifier[NUMPARTICLES];
-vec4 particlecolors[NUMPARTICLES];
-weapontexcell particlestexcoord[NUMPARTICLES];
+int charids[16] = {2,10,0,4,12,6,14,1,9,15,13,11,7,5,8,3};
 
-int charnumcellsx = 8;
-int charnumcellsy = 32;
-int charoffsety = 2;
-weapontexcell body[2];
-weapontexcell leye;
-weapontexcell reye;
-weapontexcell feet[2];
-
-int charids[16] = { 2,10,0,4,12,6,14,1,9,15,13,11,7,5,8,3 };
-
-renderparams hadokenparams[6];
-weapontexcell hadoken[6];
-
-float recoils[WEAPON_NUMWEAPONS] = { 10.0f, 10.0f, 10.0f, 10.0f };
-
-static int font_texture = 0;
 static vec2 mouse_pos;
-
-
 static vec2 local_player_pos;
 static obj_player *local_player;
 
-float frandom()
-{
-	return rand()/(float)(RAND_MAX);
-}
+inline float frandom() { return rand()/(float)(RAND_MAX); }
 
-float sign(float f)
+void snd_play_random(int setid, float vol, float pan)
 {
-	return f<0.0f?-1.0f:1.0f;
-}
-
-// sound helpers
-template<int N>
-class sound_kit
-{
-private:
-	int sounds[N];
-	int last_id;
-public:
-	sound_kit() : last_id(-1) { }
+	soundset *set = &data->sounds[setid];
 	
-	int& operator[](int id) { return sounds[id]; }
-
-	inline void play_random(float vol = 1.0f, float pan = 0.0f);
-};
-
-template<>
-inline void sound_kit<1>::play_random(float vol, float pan)
-{
-	snd_play(sounds[0], SND_PLAY_ONCE, vol, pan);
-}
-
-template<int N>
-inline void sound_kit<N>::play_random(float vol, float pan)
-{
+	if(!set->num_sounds)
+		return;
+		
+	if(set->num_sounds == 1)
+	{
+		snd_play(set->sounds[0].id, SND_PLAY_ONCE, vol, pan);
+		return;
+	}
+	
+	// play a random one
 	int id;
 	do {
-		id = rand() % N;
-	} while(id == last_id);
-	snd_play(sounds[id], SND_PLAY_ONCE, vol, pan);
-	last_id = id;
+		id = rand() % set->num_sounds;
+	} while(id == set->last);
+	snd_play(set->sounds[id].id, SND_PLAY_ONCE, vol, pan);
+	set->last = id;
 }
 
-
 // sound volume tweak
 static const float stereo_separation = 0.01f;
 static const float stereo_separation_deadzone = 512.0f;
@@ -133,31 +51,6 @@ static const float volume_tee = 0.5f;
 static const float volume_hit = 0.5f;
 static const float volume_music = 0.8f;
 
-// sounds
-sound_kit<3> sound_gun_fire;
-sound_kit<3> sound_shotty_fire;
-sound_kit<3> sound_flump_launch;
-sound_kit<3> sound_hammer_swing;
-sound_kit<3> sound_ninja_attack;
-
-sound_kit<3> sound_flump_explode;
-sound_kit<4> sound_ninja_hit;
-
-sound_kit<3> sound_weapon_switch;
-
-sound_kit<12> sound_pain_short;
-sound_kit<2> sound_pain_long;
-
-sound_kit<4> sound_body_jump;
-sound_kit<4> sound_body_land;
-sound_kit<2> sound_body_splat;
-
-sound_kit<7> sound_spawn;
-sound_kit<2> sound_tee_cry;
-
-sound_kit<1> sound_hook_loop;
-sound_kit<3> sound_hook_attach;
-
 void sound_vol_pan(const vec2& p, float *vol, float *pan)
 {
 	vec2 player_to_ev = p - local_player_pos;
@@ -181,56 +74,45 @@ void sound_vol_pan(const vec2& p, float *vol, float *pan)
 	}
 }
 
-// TODO: we should do something nicer then this
-static void cell_select_ex(int cx, int cy, float x, float y, float w, float h)
+enum
 {
-	gfx_quads_setsubset(x/(float)cx,y/(float)cy,(x+w)/(float)cx,(y+h)/(float)cy);
-}
+	SPRITE_FLAG_FLIP_Y=1,
+};
 
-/*
-static void cell_select_ex_flip_x(int cx, int cy, float x, float y, float w, float h)
-{
-	gfx_quads_setsubset((x+w)/(float)cx,y/(float)cy,x /(float)cx,(y+h)/(float)cy);
-}*/
+static float sprite_w_scale;
+static float sprite_h_scale;
 
-static void cell_select_ex_flip_y(int cx, int cy, float x, float y, float w, float h)
+static void select_sprite(sprite *spr, int flags=0, int sx=0, int sy=0)
 {
-	gfx_quads_setsubset(x/(float)cx,(y+h)/(float)cy,(x+w)/(float)cx,y/(float)cy);
+	int x = spr->x+sx;
+	int y = spr->y+sy;
+	int w = spr->w;
+	int h = spr->h;
+	int cx = spr->set->gridx;
+	int cy = spr->set->gridy;
+	
+	float f = sqrtf(h*h + w*w);
+	sprite_w_scale = w/f;
+	sprite_h_scale = h/f;
+	
+	if(flags&SPRITE_FLAG_FLIP_Y)
+		gfx_quads_setsubset(x/(float)cx,(y+h)/(float)cy,(x+w)/(float)cx,y/(float)cy);
+	else
+		gfx_quads_setsubset(x/(float)cx,y/(float)cy,(x+w)/(float)cx,(y+h)/(float)cy);
 }
-/*
-static void cell_select(int x, int y, int w, int h)
-{
-	gfx_quads_setsubset(x/16.0f,y/16.0f,(x+w)/16.0f,(y+h)/16.0f);
-}*/
 
-inline void cell_select_flip_x(int x, int y, int w, int h)
+static void select_sprite(int id, int flags=0, int sx=0, int sy=0)
 {
-	gfx_quads_setsubset((x+w)/16.0f,y/16.0f,(x)/16.0f,(y+h)/16.0f);
+	if(id < 0 || id > data->num_sprites)
+		return;
+	select_sprite(&data->sprites[id], flags, sx, sy);
 }
 
-inline void cell_select_flip_y(int x, int y, int w, int h)
+static void draw_sprite(float x, float y, float size)
 {
-	gfx_quads_setsubset(x/16.0f,(y+h)/16.0f,(x+w)/16.0f,(y)/16.0f);
+	gfx_quads_draw(x, y, size*sprite_w_scale, size*sprite_h_scale);
 }
 
-struct particle
-{
-	vec2 pos;
-	vec2 vel;
-	float life;
-	float max_life;
-	float size;
-	
-	float rot;
-	float rotspeed;
-	
-	float gravity;
-	float friction;
-	int iparticle;
-	
-	vec4 color;
-};
-
 void move_point(vec2 *inout_pos, vec2 *inout_vel, float elasticity)
 {
 	vec2 pos = *inout_pos;
@@ -320,7 +202,7 @@ public:
 				i->pos.x += ((float)rand()/(float)RAND_MAX) * 5.0f;
 				i->amount = amount;
 				i->life = 1.5f;
-				i->istar = rand() % NUMSTARS;
+				i->istar = rand()%2;
 				i->vel = vec2(((float)rand()/(float)RAND_MAX) * 50.0f,-150.0f);
 				i->startangle = (( (float)rand()/(float)RAND_MAX) - 1.0f) * 2.0f * pi;
 			}
@@ -336,7 +218,7 @@ public:
 		lastupdate = time_get();
 
 		float delta = (float) (lastupdate - lasttime) / (float)time_freq();
-		gfx_texture_set(texture_particles);
+		gfx_texture_set(data->images[IMAGE_PARTICLES].id);
 		gfx_quads_begin();
 		for(int i = 0; i < num_items;)
 		{
@@ -351,21 +233,9 @@ public:
 				gfx_quads_setcolor(1.0f,1.0f,1.0f, items[i].life / 1.5f);
 				gfx_quads_setrotation(items[i].startangle + items[i].life * 2.0f);
 				float size = 64.0f;
-				cell_select_ex(particlesnumcellsx,particlesnumcellsy, stars[items[i].istar].x,stars[items[i].istar].y, stars[items[i].istar].w, stars[items[i].istar].h);
+				const int stars[] = {SPRITE_STAR1, SPRITE_STAR2};
+				select_sprite(stars[items[i].istar]);
 				gfx_quads_draw(items[i].pos.x-size/2, items[i].pos.y-size/2, size, size);
-				/*char buf[32];
-				if(items[i].amount < 0)
-				{
-					sprintf(buf, "%d", items[i].amount*-1);
-				}
-				else
-				{
-					sprintf(buf, "%d", items[i].amount);
-				}
-				float size = 42.0f;
-				if(items[i].life > 1.25f)
-					size += 42.0f * ((items[i].life - 1.25f) * 4);
-				gfx_quads_text(items[i].pos.x-size/2, items[i].pos.y, size, buf);*/
 				i++;
 			}
 		}
@@ -374,202 +244,29 @@ public:
 	
 };
 
-/*class texture_animator
-{
-public:
-	
-	int texture;
-	int numframes;
-	float duration;
-	float* framereltime;
-	weapontexcell* params;
-	texture_animator()
-	{
-		texture = -1;
-		numframes = 0;
-		duration = 0;
-		framereltime = 0;
-		params = 0;
-	}
-
-	~texture_animator()
-	{
-		if (params)
-			mem_free(params);
-		if (framereltime)
-			mem_free(framereltime);
-	}
-
-	void create_anim(int texture, int numframes, float duration)
-	{
-		framereltime = 0;
-		params = 0;
-		this->texture = texture;
-		this->numframes = numframes;
-		this->duration = duration;
-		if (numframes)
-		{
-			framereltime = (float*)mem_alloc(sizeof(float) * numframes,1);
-			params = (weapontexcell*)mem_alloc(sizeof(renderparams) * numframes,1);
-			float delta = 1.0f / (float)(numframes - 1);
-			for (int i = 0; i < numframes; i++)
-			{
-				framereltime[i] = delta * i;
-			}
-		}	
-	}
-
-	static void create_gunmuzzle(texture_animator& anim, int texture, float duration)
-	{
-		anim.create_anim(texture, 3, duration);
-		anim.params[0].x = 8;
-		anim.params[0].y = 4;
-		anim.params[0].w = 3;
-		anim.params[0].h = 2;
-		anim.params[1].x = 12;
-		anim.params[1].y = 4;
-		anim.params[1].w = 3;
-		anim.params[1].h = 2;
-		anim.params[2].x = 16;
-		anim.params[2].y = 4;
-		anim.params[2].w = 3;
-		anim.params[2].h = 2;
-	}
-
-	static void create_shotgunmuzzle()
-	{
-
-	}
-};*/
-
-class keyframe
-{
-public:
-	vec2 pos;
-	float angle;
-	float relativetime;
-};
-
-class anim
-{
-public:
-	keyframe* keyframes;
-	int numframes;
-	float duration;
-	anim()
-	{
-		numframes = 0;
-		keyframes = 0;
-	}
-	~anim()
-	{
-		if (keyframes)
-			mem_free(keyframes);
-	}
-
-	void create_anim(int numframes, float duration)
-	{
-		if (keyframes)
-			mem_free(keyframes);
-
-		this->numframes = numframes;
-		this->duration = duration;
-		keyframes = (keyframe*)mem_alloc(sizeof(keyframe) * numframes,1);
-		float delta = 1.0f / (float) (numframes - 1);
-		for (int i = 0; i < numframes; i++)
-		{
-			keyframes[i].pos = vec2(0.0f,0.0f);
-			keyframes[i].angle = 0;
-			keyframes[i].relativetime = delta * (float)i;
-		}
-	}
-
-	void getframes(float relativetime, keyframe*& frame1, keyframe*& frame2, float& blend)
-	{
-		for (int i = 1; i < numframes; i++)
-		{
-			if (keyframes[i-1].relativetime <= relativetime && keyframes[i].relativetime >= relativetime)
-			{
-				frame1 = &keyframes[i-1];
-				frame2 = &keyframes[i];
-				blend = (relativetime - frame1->relativetime) / (frame2->relativetime - frame1->relativetime);
-			}
-		}
-	}
-
-	void evalanim(float time, vec2& pos, float& angle)
-	{
-		float reltime = max(0.0f, min(1.0f, time / duration));
-		keyframe* frame1 = 0;
-		keyframe* frame2 = 0;
-		float blend = 0.0f;
-		getframes(reltime, frame1, frame2, blend);
-
-		if (frame1 && frame2)
-		{
-			pos = mix(frame1->pos, frame2->pos, blend);
-			angle = LERP(frame1->angle, frame2->angle, blend);
-		}
-	}
-
-	static void setup_hammer(anim& hammeranim)
-	{
-		// straight up = -0.25
-		// frame0 = standard pose		time 0
-		// frame1 = back a little		time 0.3
-		// frame2 = over head			time 0.4
-		// frame3 = on ground smashed	time 0.5
-		// frame4 = back to standard pose time 1.0
-		hammeranim.create_anim(5, 1.0f);
-		// only angles... (for now...)
-		hammeranim.keyframes[0].angle = -0.35f * pi * 2.0f;
-		hammeranim.keyframes[1].angle = -0.4f * pi * 2.0f;
-		hammeranim.keyframes[1].relativetime = 0.3f;
-		hammeranim.keyframes[2].angle = -0.25f;
-		hammeranim.keyframes[2].relativetime = 0.4f;
-		hammeranim.keyframes[3].angle = 0.0f * pi * 2.0f;
-		hammeranim.keyframes[3].relativetime = 0.5f;
-		hammeranim.keyframes[4].angle = -0.35f * pi * 2.0f;
-		hammeranim.keyframes[4].relativetime = 1.0f;
-	}
-
-	static void setup_ninja(anim& ninjanim)
-	{
-		// (straight up = -0.25)
-		// frame0 = standard pose straight back		time 0.0
-		// frame1 = overhead attack frame 1			time 0.1
-		// frame2 = attack end frame				time 0.15
-		// frame3 = attack hold frame (a bit up)	time 0.4
-		// frame4 = attack hold frame end			time 0.7
-		// frame5 = endframe						time 1.0
-		ninjanim.create_anim(6, 1.0f);
-		// only angles... (for now...)
-		ninjanim.keyframes[0].angle = -0.5f * pi * 2.0f;
-
-		ninjanim.keyframes[1].angle = -0.3f * pi * 2.0f;
-		ninjanim.keyframes[1].relativetime = 0.1f;
-
-		ninjanim.keyframes[2].angle = 0.1f * pi * 2.0f;
-		ninjanim.keyframes[2].relativetime = 0.15f;
-
-		ninjanim.keyframes[3].angle = -0.05f * pi * 2.0f;
-		ninjanim.keyframes[3].relativetime = 0.42;
-
-		ninjanim.keyframes[4].angle = -0.05f * pi * 2.0f;
-		ninjanim.keyframes[4].relativetime = 0.5f;
-
-		ninjanim.keyframes[5].angle = -0.5f * pi * 2.0f;
-		ninjanim.keyframes[5].relativetime = 1.0f;
-	}
-};
-
-static anim hammeranim;
-static anim ninjaanim;
 static health_texts healthmods;
 
 class particle_system
 {
 public:
+	struct particle
+	{
+		vec2 pos;
+		vec2 vel;
+		float life;
+		float max_life;
+		float size;
+		
+		float rot;
+		float rotspeed;
+		
+		float gravity;
+		float friction;
+		int iparticle;
+		
+		vec4 color;
+	};
+
 	enum
 	{
 		MAX_PARTICLES=1024,
@@ -588,10 +285,10 @@ public:
 		if (num_particles >= MAX_PARTICLES)
 			return;
 
-		particles[num_particles].iparticle = rand() % NUMPARTICLES;
+		particles[num_particles].iparticle = rand() % data->num_particles;
 		particles[num_particles].pos = pos;
 		particles[num_particles].vel = vel;
-		particles[num_particles].life = life - lifemodifier[particles[num_particles].iparticle] * life;
+		particles[num_particles].life = life - (data->particles[particles[num_particles].iparticle].lifemod/100.0f) * life;
 		particles[num_particles].size = size;
 		particles[num_particles].max_life = life;
 		particles[num_particles].gravity = gravity;
@@ -626,23 +323,24 @@ public:
 	void render()
 	{
 		gfx_blend_additive();
-		gfx_texture_set(texture_particles);
+		gfx_texture_set(data->images[IMAGE_PARTICLES].id);
 		gfx_quads_begin();
-		//cell_select(4,1,1,1);
-		//cell_select(0,6,2,2);
-		//gfx_quads_setrotation(get_angle(vec2(proj->vx, proj->vy)));
+		
 		for(int i = 0; i < num_particles; i++)
 		{
 			int type = particles[i].iparticle;
-			cell_select_ex(particlesnumcellsx,particlesnumcellsy,particlestexcoord[type].x, particlestexcoord[type].y, particlestexcoord[type].w, particlestexcoord[type].h);
+			select_sprite(data->particles[type].spr);
 			float a = 1 - particles[i].life / particles[i].max_life;
 			vec2 p = particles[i].pos;
-			//a *= length(particles[i].vel) * 0.01f;
+			
 			gfx_quads_setrotation(particles[i].rot);
-			gfx_quads_setcolor(particlecolors[type].x,particlecolors[type].y,particlecolors[type].z,pow(a,0.75f));
-			//gfx_quads_setcolor(particlecolors[type].x * 0.5,particlecolors[type].y * 0.5,particlecolors[type].z* 0.5,pow(a,0.75f));
-			//gfx_quads_setcolor(particlecolors[type].x * 0.0,particlecolors[type].y * 0.0,particlecolors[type].z* 0.0,pow(a,0.75f));
-			//gfx_quads_setcolor(0.64f*2,0.28f*2,0.16f*2,pow(a,0.75f));
+			
+			gfx_quads_setcolor(
+				data->particles[type].color_r,
+				data->particles[type].color_g,
+				data->particles[type].color_b,
+				pow(a, 0.75f));
+				
 			gfx_quads_draw(p.x, p.y,particles[i].size,particles[i].size);
 		}
 		gfx_quads_end();		
@@ -654,447 +352,17 @@ static particle_system temp_system;
  
 void modc_init()
 {
-	// load textures
-	texture_weapon = gfx_load_texture("data/tileset_weapons.png");
-	texture_game = gfx_load_texture("data/game_main.png");
-	texture_char_default = gfx_load_texture("data/char_teefault.png");
-	texture_sun = gfx_load_texture("data/sun.png");
-	texture_particles = gfx_load_texture("data/tileset_particles.png");
-	font_texture = gfx_load_texture("data/debug_font.png");
-
+	// load the data container
+	data = load_data_container("data/client.dat");
 	
 	// load sounds
-	sound_gun_fire[0] = snd_load_wav("data/audio/wp_gun_fire-01.wav");
-	sound_gun_fire[0] = snd_load_wav("data/audio/wp_gun_fire-01.wav");
-	sound_gun_fire[1] = snd_load_wav("data/audio/wp_gun_fire-02.wav");
-	sound_shotty_fire[0] = snd_load_wav("data/audio/wp_shotty_fire-01.wav");
-	sound_shotty_fire[1] = snd_load_wav("data/audio/wp_shotty_fire-02.wav");
-	sound_shotty_fire[2] = snd_load_wav("data/audio/wp_shotty_fire-03.wav");
-	sound_flump_launch[0] = snd_load_wav("data/audio/wp_flump_launch-01.wav");
-	sound_flump_launch[1] = snd_load_wav("data/audio/wp_flump_launch-02.wav");
-	sound_flump_launch[2] = snd_load_wav("data/audio/wp_flump_launch-03.wav");
-	sound_hammer_swing[0] = snd_load_wav("data/audio/wp_hammer_swing-01.wav");
-	sound_hammer_swing[1] = snd_load_wav("data/audio/wp_hammer_swing-02.wav");
-	sound_hammer_swing[2] = snd_load_wav("data/audio/wp_hammer_swing-03.wav");
-	sound_ninja_attack[0] = snd_load_wav("data/audio/wp_ninja_attack-01.wav");
-	sound_ninja_attack[1] = snd_load_wav("data/audio/wp_ninja_attack-02.wav");
-	sound_ninja_attack[2] = snd_load_wav("data/audio/wp_ninja_attack-03.wav");
-
-	sound_flump_explode[0] = snd_load_wav("data/audio/wp_flump_explo-01.wav");
-	sound_flump_explode[1] = snd_load_wav("data/audio/wp_flump_explo-02.wav");
-	sound_flump_explode[2] = snd_load_wav("data/audio/wp_flump_explo-03.wav");
-	sound_ninja_hit[0] = snd_load_wav("data/audio/wp_ninja_hit-01.wav");
-	sound_ninja_hit[1] = snd_load_wav("data/audio/wp_ninja_hit-02.wav");
-	sound_ninja_hit[2] = snd_load_wav("data/audio/wp_ninja_hit-03.wav");
-	sound_ninja_hit[3] = snd_load_wav("data/audio/wp_ninja_hit-04.wav");
-
-	sound_weapon_switch[0] = snd_load_wav("data/audio/wp_switch-01.wav");
-	sound_weapon_switch[1] = snd_load_wav("data/audio/wp_switch-02.wav");
-	sound_weapon_switch[2] = snd_load_wav("data/audio/wp_switch-03.wav");
-
-	sound_pain_short[0] = snd_load_wav("data/audio/vo_teefault_pain_short-01.wav");
-	sound_pain_short[1] = snd_load_wav("data/audio/vo_teefault_pain_short-02.wav");
-	sound_pain_short[2] = snd_load_wav("data/audio/vo_teefault_pain_short-03.wav");
-	sound_pain_short[3] = snd_load_wav("data/audio/vo_teefault_pain_short-04.wav");
-	sound_pain_short[4] = snd_load_wav("data/audio/vo_teefault_pain_short-05.wav");
-	sound_pain_short[5] = snd_load_wav("data/audio/vo_teefault_pain_short-06.wav");
-	sound_pain_short[6] = snd_load_wav("data/audio/vo_teefault_pain_short-07.wav");
-	sound_pain_short[7] = snd_load_wav("data/audio/vo_teefault_pain_short-08.wav");
-	sound_pain_short[8] = snd_load_wav("data/audio/vo_teefault_pain_short-09.wav");
-	sound_pain_short[9] = snd_load_wav("data/audio/vo_teefault_pain_short-10.wav");
-	sound_pain_short[10] = snd_load_wav("data/audio/vo_teefault_pain_short-11.wav");
-	sound_pain_short[11] = snd_load_wav("data/audio/vo_teefault_pain_short-12.wav");
-
-	sound_pain_long[0] = snd_load_wav("data/audio/vo_teefault_pain_long-01.wav");
-	sound_pain_long[1] = snd_load_wav("data/audio/vo_teefault_pain_long-02.wav");
-
-	sound_body_land[0] = snd_load_wav("data/audio/foley_land-01.wav");
-	sound_body_land[1] = snd_load_wav("data/audio/foley_land-02.wav");
-	sound_body_land[2] = snd_load_wav("data/audio/foley_land-03.wav");
-	sound_body_land[3] = snd_load_wav("data/audio/foley_land-04.wav");
-	sound_body_jump[0] = snd_load_wav("data/audio/foley_foot_left-01.wav");
-	sound_body_jump[1] = snd_load_wav("data/audio/foley_foot_left-02.wav");
-	sound_body_jump[2] = snd_load_wav("data/audio/foley_foot_left-03.wav");
-	sound_body_jump[3] = snd_load_wav("data/audio/foley_foot_left-04.wav");
-	sound_body_jump[4] = snd_load_wav("data/audio/foley_foot_right-01.wav");
-	sound_body_jump[5] = snd_load_wav("data/audio/foley_foot_right-02.wav");
-	sound_body_jump[6] = snd_load_wav("data/audio/foley_foot_right-03.wav");
-	sound_body_jump[7] = snd_load_wav("data/audio/foley_foot_right-04.wav");
-
-	sound_body_splat[1] = snd_load_wav("data/audio/foley_body_splat-02.wav");
-	sound_body_splat[2] = snd_load_wav("data/audio/foley_body_splat-03.wav");
-	sound_body_splat[3] = snd_load_wav("data/audio/foley_body_splat-04.wav");
-
-	sound_spawn[0] = snd_load_wav("data/audio/vo_teefault_spawn-01.wav");
-	sound_spawn[1] = snd_load_wav("data/audio/vo_teefault_spawn-02.wav");
-	sound_spawn[2] = snd_load_wav("data/audio/vo_teefault_spawn-03.wav");
-	sound_spawn[3] = snd_load_wav("data/audio/vo_teefault_spawn-04.wav");
-	sound_spawn[4] = snd_load_wav("data/audio/vo_teefault_spawn-05.wav");
-	sound_spawn[5] = snd_load_wav("data/audio/vo_teefault_spawn-06.wav");
-	sound_spawn[6] = snd_load_wav("data/audio/vo_teefault_spawn-07.wav");
-
-	sound_tee_cry[0] = snd_load_wav("data/audio/vo_teefault_cry-01.wav");
-	sound_tee_cry[1] = snd_load_wav("data/audio/vo_teefault_cry-02.wav");
-
-	//sound_hook_loop[0] = snd_load_wav("data/audio/hook_loop-01.wav");
-	sound_hook_loop[0] = snd_load_wav("data/audio/hook_loop-02.wav");
-	sound_hook_attach[0] = snd_load_wav("data/audio/hook_attach-01.wav");
-	sound_hook_attach[1] = snd_load_wav("data/audio/hook_attach-02.wav");
-	sound_hook_attach[2] = snd_load_wav("data/audio/hook_attach-03.wav");
-
-	poweruptexcoord[POWERUP_TYPE_HEALTH].x = 10;
-	poweruptexcoord[POWERUP_TYPE_HEALTH].y = 2;
-	poweruptexcoord[POWERUP_TYPE_HEALTH].w = 2;
-	poweruptexcoord[POWERUP_TYPE_HEALTH].h = 2;
-
-	poweruptexcoord[POWERUP_TYPE_ARMOR].x = 12;
-	poweruptexcoord[POWERUP_TYPE_ARMOR].y = 2;
-	poweruptexcoord[POWERUP_TYPE_ARMOR].w = 2;
-	poweruptexcoord[POWERUP_TYPE_ARMOR].h = 2;
-	
-	poweruptexcoord[POWERUP_TYPE_WEAPON].x = 3;
-	poweruptexcoord[POWERUP_TYPE_WEAPON].y = 0;
-	poweruptexcoord[POWERUP_TYPE_WEAPON].w = 6;
-	poweruptexcoord[POWERUP_TYPE_WEAPON].h = 2;
-
-	poweruptexcoord[POWERUP_TYPE_NINJA].x = 3;
-	poweruptexcoord[POWERUP_TYPE_NINJA].y = 10;
-	poweruptexcoord[POWERUP_TYPE_NINJA].w = 7;
-	poweruptexcoord[POWERUP_TYPE_NINJA].h = 2;
-
-	poweruptexcoord[POWERUP_TYPE_TIMEFIELD].x = 3;
-	poweruptexcoord[POWERUP_TYPE_TIMEFIELD].y = 0;
-	poweruptexcoord[POWERUP_TYPE_TIMEFIELD].w = 6;
-	poweruptexcoord[POWERUP_TYPE_TIMEFIELD].h = 2;
-
-	// Setup weapon cell coords
-	float sizemodifier = 1.0f;
-	weaponrenderparams[WEAPON_TYPE_GUN].sizex = 60.0f * sizemodifier;
-	weaponrenderparams[WEAPON_TYPE_GUN].sizey = 30.0f * sizemodifier;
-	weaponrenderparams[WEAPON_TYPE_GUN].offsetx = 32.0f;
-	weaponrenderparams[WEAPON_TYPE_GUN].offsety = 4.0f;
-	weapontexcoordcursor[WEAPON_TYPE_GUN].x = 0;
-	weapontexcoordcursor[WEAPON_TYPE_GUN].y = 4;
-	weapontexcoordcursor[WEAPON_TYPE_GUN].w = 2;
-	weapontexcoordcursor[WEAPON_TYPE_GUN].h = 2;
-	weapontexcoord[WEAPON_TYPE_GUN].x = 2;
-	weapontexcoord[WEAPON_TYPE_GUN].y = 4;
-	weapontexcoord[WEAPON_TYPE_GUN].w = 4;
-	weapontexcoord[WEAPON_TYPE_GUN].h = 2;
-	weaponprojtexcoord[WEAPON_TYPE_GUN].x = 6;
-	weaponprojtexcoord[WEAPON_TYPE_GUN].y = 4;
-	weaponprojtexcoord[WEAPON_TYPE_GUN].w = 2;
-	weaponprojtexcoord[WEAPON_TYPE_GUN].h = 2;
-
-	nummuzzletex[WEAPON_TYPE_GUN] = 3;
-	muzzletexcoord[WEAPON_TYPE_GUN][0].x = 8;
-	muzzletexcoord[WEAPON_TYPE_GUN][0].y = 4;
-	muzzletexcoord[WEAPON_TYPE_GUN][0].w = 3;
-	muzzletexcoord[WEAPON_TYPE_GUN][0].h = 2;
-	muzzletexcoord[WEAPON_TYPE_GUN][1].x = 12;
-	muzzletexcoord[WEAPON_TYPE_GUN][1].y = 4;
-	muzzletexcoord[WEAPON_TYPE_GUN][1].w = 3;
-	muzzletexcoord[WEAPON_TYPE_GUN][1].h = 2;
-	muzzletexcoord[WEAPON_TYPE_GUN][2].x = 16;
-	muzzletexcoord[WEAPON_TYPE_GUN][2].y = 4;
-	muzzletexcoord[WEAPON_TYPE_GUN][2].w = 3;
-	muzzletexcoord[WEAPON_TYPE_GUN][2].h = 2;
-
-	muzzleparams[WEAPON_TYPE_GUN].sizex = 60.0f * sizemodifier;
-	muzzleparams[WEAPON_TYPE_GUN].sizey = 40.0f * sizemodifier;
-	muzzleparams[WEAPON_TYPE_GUN].offsetx = 50.0f * sizemodifier;
-	muzzleparams[WEAPON_TYPE_GUN].offsety = 6.0f * sizemodifier;
-
-	sizemodifier = 1.3f;
-	weaponrenderparams[WEAPON_TYPE_ROCKET].sizex = 70.0f * sizemodifier;
-	weaponrenderparams[WEAPON_TYPE_ROCKET].sizey = 20.0f * sizemodifier;
-	weaponrenderparams[WEAPON_TYPE_ROCKET].offsetx = 24.0f;
-	weaponrenderparams[WEAPON_TYPE_ROCKET].offsety = -2.0f;
-	weapontexcoordcursor[WEAPON_TYPE_ROCKET].x = 0;
-	weapontexcoordcursor[WEAPON_TYPE_ROCKET].y = 8;
-	weapontexcoordcursor[WEAPON_TYPE_ROCKET].w = 2;
-	weapontexcoordcursor[WEAPON_TYPE_ROCKET].h = 2;
-	weapontexcoord[WEAPON_TYPE_ROCKET].x = 2;
-	weapontexcoord[WEAPON_TYPE_ROCKET].y = 8;
-	weapontexcoord[WEAPON_TYPE_ROCKET].w = 7;
-	weapontexcoord[WEAPON_TYPE_ROCKET].h = 2;
-	weaponprojtexcoord[WEAPON_TYPE_ROCKET].x = 10;
-	weaponprojtexcoord[WEAPON_TYPE_ROCKET].y = 8;
-	weaponprojtexcoord[WEAPON_TYPE_ROCKET].w = 2;
-	weaponprojtexcoord[WEAPON_TYPE_ROCKET].h = 2;
-
-	/*weaponrenderparams[WEAPON_TYPE_SNIPER].sizex = 60.0f;
-	weaponrenderparams[WEAPON_TYPE_SNIPER].sizey = 20.0f;
-	weaponrenderparams[WEAPON_TYPE_SNIPER].offsetx = 16.0f;
-	weaponrenderparams[WEAPON_TYPE_SNIPER].offsety = 4.0f;
-	weapontexcoordcursor[WEAPON_TYPE_SNIPER].x = 0;
-	weapontexcoordcursor[WEAPON_TYPE_SNIPER].y = 6;
-	weapontexcoordcursor[WEAPON_TYPE_SNIPER].w = 2;
-	weapontexcoordcursor[WEAPON_TYPE_SNIPER].h = 2;
-	weapontexcoord[WEAPON_TYPE_SNIPER].x = 3;
-	weapontexcoord[WEAPON_TYPE_SNIPER].y = 6;
-	weapontexcoord[WEAPON_TYPE_SNIPER].w = 6;
-	weapontexcoord[WEAPON_TYPE_SNIPER].h = 2;
-	weaponprojtexcoord[WEAPON_TYPE_SNIPER].x = 10;
-	weaponprojtexcoord[WEAPON_TYPE_SNIPER].y = 6;
-	weaponprojtexcoord[WEAPON_TYPE_SNIPER].w = 1;
-	weaponprojtexcoord[WEAPON_TYPE_SNIPER].h = 1;*/
-
-	weaponrenderparams[WEAPON_TYPE_SHOTGUN].sizex = 80.0f * sizemodifier;
-	weaponrenderparams[WEAPON_TYPE_SHOTGUN].sizey = 20.0f * sizemodifier;
-	weaponrenderparams[WEAPON_TYPE_SHOTGUN].offsetx = 24.0f;
-	weaponrenderparams[WEAPON_TYPE_SHOTGUN].offsety = -2.0f;
-	weapontexcoordcursor[WEAPON_TYPE_SHOTGUN].x = 0;
-	weapontexcoordcursor[WEAPON_TYPE_SHOTGUN].y = 6;
-	weapontexcoordcursor[WEAPON_TYPE_SHOTGUN].w = 2;
-	weapontexcoordcursor[WEAPON_TYPE_SHOTGUN].h = 2;
-	weapontexcoord[WEAPON_TYPE_SHOTGUN].x = 2;
-	weapontexcoord[WEAPON_TYPE_SHOTGUN].y = 6;
-	weapontexcoord[WEAPON_TYPE_SHOTGUN].w = 8;
-	weapontexcoord[WEAPON_TYPE_SHOTGUN].h = 2;
-	weaponprojtexcoord[WEAPON_TYPE_SHOTGUN].x = 10;
-	weaponprojtexcoord[WEAPON_TYPE_SHOTGUN].y = 6;
-	weaponprojtexcoord[WEAPON_TYPE_SHOTGUN].w = 2;
-	weaponprojtexcoord[WEAPON_TYPE_SHOTGUN].h = 2;
-
-	nummuzzletex[WEAPON_TYPE_SHOTGUN] = 3;
-	muzzletexcoord[WEAPON_TYPE_SHOTGUN][0].x = 12;
-	muzzletexcoord[WEAPON_TYPE_SHOTGUN][0].y = 6;
-	muzzletexcoord[WEAPON_TYPE_SHOTGUN][0].w = 3;
-	muzzletexcoord[WEAPON_TYPE_SHOTGUN][0].h = 2;
-	muzzletexcoord[WEAPON_TYPE_SHOTGUN][1].x = 16;
-	muzzletexcoord[WEAPON_TYPE_SHOTGUN][1].y = 6;
-	muzzletexcoord[WEAPON_TYPE_SHOTGUN][1].w = 3;
-	muzzletexcoord[WEAPON_TYPE_SHOTGUN][1].h = 2;
-	muzzletexcoord[WEAPON_TYPE_SHOTGUN][2].x = 20;
-	muzzletexcoord[WEAPON_TYPE_SHOTGUN][2].y = 6;
-	muzzletexcoord[WEAPON_TYPE_SHOTGUN][2].w = 3;
-	muzzletexcoord[WEAPON_TYPE_SHOTGUN][2].h = 2;
-
-	muzzleparams[WEAPON_TYPE_SHOTGUN].sizex = 60.0f * sizemodifier;
-	muzzleparams[WEAPON_TYPE_SHOTGUN].sizey = 40.0f * sizemodifier;
-	muzzleparams[WEAPON_TYPE_SHOTGUN].offsetx = 50.0f * sizemodifier;
-	muzzleparams[WEAPON_TYPE_SHOTGUN].offsety = 6.0f * sizemodifier;
-
-
-
-	weaponrenderparams[WEAPON_TYPE_MELEE].sizex = 60.0f * sizemodifier;
-	weaponrenderparams[WEAPON_TYPE_MELEE].sizey = 50.0f * sizemodifier;
-	weaponrenderparams[WEAPON_TYPE_MELEE].offsetx = 20.0f;
-	weaponrenderparams[WEAPON_TYPE_MELEE].offsety = -4.0f;
-	weapontexcoordcursor[WEAPON_TYPE_MELEE].x = 0;
-	weapontexcoordcursor[WEAPON_TYPE_MELEE].y = 0;
-	weapontexcoordcursor[WEAPON_TYPE_MELEE].w = 2;
-	weapontexcoordcursor[WEAPON_TYPE_MELEE].h = 2;
-	weapontexcoord[WEAPON_TYPE_MELEE].x = 2;
-	weapontexcoord[WEAPON_TYPE_MELEE].y = 1;
-	weapontexcoord[WEAPON_TYPE_MELEE].w = 4;
-	weapontexcoord[WEAPON_TYPE_MELEE].h = 3;
-	weaponprojtexcoord[WEAPON_TYPE_MELEE].x = 0;
-	weaponprojtexcoord[WEAPON_TYPE_MELEE].y = 0;
-	weaponprojtexcoord[WEAPON_TYPE_MELEE].w = 0;
-	weaponprojtexcoord[WEAPON_TYPE_MELEE].h = 0;
-
-
-	// MODIFIERS
-	sizemodifier = 2.0;
-	modifierrenderparams[MODIFIER_TYPE_NINJA].sizex = 60.0f * sizemodifier;
-	modifierrenderparams[MODIFIER_TYPE_NINJA].sizey = 20.0f * sizemodifier;
-	modifierrenderparams[MODIFIER_TYPE_NINJA].offsetx = 20.0f;
-	modifierrenderparams[MODIFIER_TYPE_NINJA].offsety = 4.0f;
-	modifiertexcoord[MODIFIER_TYPE_NINJA].x = 2;
-	modifiertexcoord[MODIFIER_TYPE_NINJA].y = 10;
-	modifiertexcoord[MODIFIER_TYPE_NINJA].w = 7;
-	modifiertexcoord[MODIFIER_TYPE_NINJA].h = 2;
-	modifiertexcoordcursor[MODIFIER_TYPE_NINJA].x = 0;
-	modifiertexcoordcursor[MODIFIER_TYPE_NINJA].y = 10;
-	modifiertexcoordcursor[MODIFIER_TYPE_NINJA].w = 2;
-	modifiertexcoordcursor[MODIFIER_TYPE_NINJA].h = 2;
-
-	modifierrenderparams[MODIFIER_TYPE_TIMEFIELD].sizex = 60.0f * sizemodifier;
-	modifierrenderparams[MODIFIER_TYPE_TIMEFIELD].sizey = 20.0f * sizemodifier;
-	modifierrenderparams[MODIFIER_TYPE_TIMEFIELD].offsetx = 16.0f;
-	modifierrenderparams[MODIFIER_TYPE_TIMEFIELD].offsety = 4.0f;
-	modifiertexcoord[MODIFIER_TYPE_TIMEFIELD].x = 0;
-	modifiertexcoord[MODIFIER_TYPE_TIMEFIELD].y = 0;
-	modifiertexcoord[MODIFIER_TYPE_TIMEFIELD].w = 0;
-	modifiertexcoord[MODIFIER_TYPE_TIMEFIELD].h = 0;
-
-	stars[0].x = 0;
-	stars[0].y = 0;
-	stars[0].w = 2;
-	stars[0].h = 2;
-
-	stars[1].x = 0;
-	stars[1].y = 2;
-	stars[1].w = 2;
-	stars[1].h = 2;
-
-	particlecolors[0].x = 0.7f;
-	particlecolors[0].y = 0.7f;
-	particlecolors[0].z = 0.7f;
-	particlecolors[0].w = 1.0f;
-	particlestexcoord[0].x = 2;
-	particlestexcoord[0].y = 0;
-	particlestexcoord[0].w = 2;
-	particlestexcoord[0].h = 2;
-	particlecolors[1].x = 1.0f;
-	particlecolors[1].y = 1.0f;
-	particlecolors[1].z = 1.0f; 
-	particlecolors[1].w = 1.0f;
-	particlestexcoord[1].x = 4;
-	particlestexcoord[1].y = 0;
-	particlestexcoord[1].w = 2;
-	particlestexcoord[1].h = 2;
-	particlecolors[2].x = 0.8f;
-	particlecolors[2].y = 0.8f;
-	particlecolors[2].z = 0.8f;
-	particlecolors[2].w = 1.0f;
-	particlestexcoord[2].x = 6;
-	particlestexcoord[2].y = 0;
-	particlestexcoord[2].w = 2;
-	particlestexcoord[2].h = 2;
-	particlecolors[3].x = 0.988f;
-	particlecolors[3].y = 1.0f;
-	particlecolors[3].z = 0.16f;
-	particlecolors[3].w = 1.0f;
-	particlestexcoord[3].x = 8;
-	particlestexcoord[3].y = 0;
-	particlestexcoord[3].w = 2;
-	particlestexcoord[3].h = 2;
-	particlecolors[4].x = 1.0f;
-	particlecolors[4].y = 1.0f;
-	particlecolors[4].z = 1.0f;
-	particlecolors[4].w = 1.0f;
-	particlestexcoord[4].x = 10;
-	particlestexcoord[4].y = 0;
-	particlestexcoord[4].w = 2;
-	particlestexcoord[4].h = 2;
-	particlecolors[5].x = 0.6f;
-	particlecolors[5].y = 0.6f;
-	particlecolors[5].z = 0.6f;
-	particlecolors[5].w = 1.0f;
-	particlestexcoord[5].x = 2;
-	particlestexcoord[5].y = 2;
-	particlestexcoord[5].w = 2;
-	particlestexcoord[5].h = 2;
-	particlecolors[6].x = 1.0f;
-	particlecolors[6].y = 1.0f;
-	particlecolors[6].z = 1.0f; 
-	particlecolors[6].w = 1.0f;
-	particlestexcoord[6].x = 4;
-	particlestexcoord[6].y = 2;
-	particlestexcoord[6].w = 2;
-	particlestexcoord[6].h = 2;
-	particlecolors[5].x = 0.9f;
-	particlecolors[5].y = 0.9f;
-	particlecolors[5].z = 0.9f;
-	particlecolors[5].w = 1.0f;
-	particlestexcoord[7].x = 6;
-	particlestexcoord[7].y = 2;
-	particlestexcoord[7].w = 2;
-	particlestexcoord[7].h = 2;
-	particlecolors[8].x = 1.0f;
-	particlecolors[8].y = 1.0f;
-	particlecolors[8].z = 1.0f;
-	particlecolors[8].w = 1.0f;
-	particlestexcoord[8].x = 8;
-	particlestexcoord[8].y = 2;
-	particlestexcoord[8].w = 2;
-	particlestexcoord[8].h = 2;
-	lifemodifier[0] = 0.5f;
-	lifemodifier[1] = 0.5f;
-	lifemodifier[2] = 0.5f;
-	lifemodifier[3] = 0.7f;
-	lifemodifier[4] = 0.7f;
-	lifemodifier[5] = 1.0f;
-	lifemodifier[6] = 1.0f;
-	lifemodifier[7] = 1.5f;
-	lifemodifier[8] = 0.4f;
-
-	chaintexcoord.x = 2;
-	chaintexcoord.y = 0;
-	chaintexcoord.w = 1;
-	chaintexcoord.h = 1;
-
-	chainheadtexcoord.x = 3;
-	chainheadtexcoord.y = 0;
-	chainheadtexcoord.w = 2;
-	chainheadtexcoord.h = 1;
-
-
-	// anims
-	anim::setup_hammer(hammeranim);
-	anim::setup_ninja(ninjaanim);
-
-	for (int i = 0; i < NUMHADOKENS; i++)
-	{
-		hadoken[i].x = 1;
-		hadoken[i].y = 12;
-		hadoken[i].w = 7;
-		hadoken[i].h = 4;
-		hadokenparams[i].sizex = 0.0f;
-		hadokenparams[i].sizey = 0.0f;
-		hadokenparams[i].offsetx = 0.0f;
-		hadokenparams[i].offsety = 0.0f;//-hadokenparams[0].sizey * 0.15f;
-	}
-
-	// hadoken
-	hadoken[0].x = 1;
-	hadoken[0].y = 12;
-	hadoken[0].w = 7;
-	hadoken[0].h = 4;
-	hadokenparams[0].sizex = 70.0f * 2.5f;
-	hadokenparams[0].sizey = 40.0f * 2.5f;
-	hadokenparams[0].offsetx = -60.0f;
-	hadokenparams[0].offsety = 0;//-hadokenparams[0].sizey * 0.15f;
-
-	hadoken[2].x = 8;
-	hadoken[2].y = 12;
-	hadoken[2].w = 8;
-	hadoken[2].h = 4;
-	hadokenparams[2].sizex = 80.0f * 2.5f;
-	hadokenparams[2].sizey = 40.0f * 2.5f;
-	hadokenparams[2].offsetx = -60.0f;
-	hadokenparams[2].offsety = 0;//-hadokenparams[1].sizey * 0.5f;
-
-	hadoken[4].x = 17;
-	hadoken[4].y = 12;
-	hadoken[4].w = 7;
-	hadoken[4].h = 4;
-	hadokenparams[4].sizex = 70.0f * 2.5f;
-	hadokenparams[4].sizey = 40.0f * 2.5f;
-	hadokenparams[4].offsetx = -60.0f;
-	hadokenparams[4].offsety = 0;//-hadokenparams[2].sizey * 0.5f;
-
-	// 0 = outline, 1 = body
-	body[0].x = 2;
-	body[0].y = 0;
-	body[0].w = 2;
-	body[0].h = 2;
-	body[1].x = 0;
-	body[1].y = 0;
-	body[1].w = 2;
-	body[1].h = 2;
-
-	feet[0].x = 4;
-	feet[0].y = 1;
-	feet[0].w = 1;
-	feet[0].h = 0.5;
-	feet[1].x = 4;
-	feet[1].y = 1.52;
-	feet[1].w = 1;
-	feet[1].h = 0.48;
+	for(int s = 0; s < data->num_sounds; s++)
+		for(int i = 0; i < data->sounds[s].num_sounds; i++)
+			data->sounds[s].sounds[i].id = snd_load_wav(data->sounds[s].sounds[i].filename);
 	
-	leye.x = 5;
-	leye.y = 1;
-	leye.w = 0.5;
-	leye.h = 0.5;
-	
-	reye.x = 5;
-	reye.y = 1.0;
-	reye.w = 0.5;
-	reye.h = 0.5;
+	// load textures
+	for(int i = 0; i < data->num_images; i++)
+		data->images[i].id = gfx_load_texture(data->images[i].filename);
 }
 
 void modc_entergame()
@@ -1179,77 +447,17 @@ void modc_newsnapshot()
 		{
 			ev_sound *ev = (ev_sound *)data;
 			vec2 p(ev->x, ev->y);
-			int sound = (ev->sound & SOUND_MASK);
+			int soundid = ev->sound; //(ev->sound & SOUND_MASK);
 			//bool bstartloop = (ev->sound & SOUND_LOOPFLAG_STARTLOOP) != 0;
 			//bool bstoploop = (ev->sound & SOUND_LOOPFLAG_STOPLOOP) != 0;
 			float vol, pan;
 			sound_vol_pan(p, &vol, &pan);
-
-			switch(sound)
+			
+			if(soundid >= 0 && soundid < NUM_SOUNDS)
 			{
-
-			// FIRE!
-			case SOUND_FIRE_GUN:
-				sound_gun_fire.play_random(volume_gun*vol, pan);
-				break;
-			case SOUND_FIRE_SHOTGUN:
-				sound_shotty_fire.play_random(volume_gun*vol, pan);
-				break;
-			case SOUND_FIRE_ROCKET:
-				sound_flump_launch.play_random(volume_gun*vol, pan);
-				break;
-			case SOUND_FIRE_MELEE:
-				sound_hammer_swing.play_random(volume_gun*vol, pan);
-				break;
-			case SOUND_FIRE_NINJA:
-				sound_ninja_attack.play_random(volume_gun*vol, pan);
-				break;
-
-			// IMPACT
-			case SOUND_IMPACT_PROJECTILE_GUN:
-				break;
-			case SOUND_IMPACT_PROJECTILE_SHOTGUN:
-				break;
-			case SOUND_IMPACT_PROJECTILE_ROCKET:
-				sound_flump_explode.play_random(volume_hit*vol, pan);
-				break;
-
-			// PLAYER
-			case SOUND_PLAYER_JUMP:
-				sound_body_jump.play_random(volume_tee*vol, pan);
-				break;
-			case SOUND_PLAYER_HURT_SHORT:
-				sound_pain_short.play_random(volume_tee*vol, pan);
-				break;
-			case SOUND_PLAYER_HURT_LONG:
-				sound_pain_long.play_random(volume_tee*vol, pan);
-				break;
-			case SOUND_PLAYER_SPAWN:
-				sound_spawn.play_random(volume_tee*vol, pan);
-				break;
-			case SOUND_PLAYER_CHAIN_LOOP:
-				sound_hook_loop.play_random(volume_gun*vol, pan);
-				break;
-			case SOUND_PLAYER_CHAIN_IMPACT:
-				sound_hook_attach.play_random(volume_gun*vol, pan);
-				break;
-			case SOUND_PLAYER_IMPACT:
-				sound_body_land.play_random(volume_hit*vol, pan);
-				break;
-			case SOUND_PLAYER_IMPACT_NINJA:
-				sound_ninja_hit.play_random(volume_hit*vol, pan);
-				break;
-			case SOUND_PLAYER_DIE:
-				sound_body_splat.play_random(volume_tee*vol, pan);
-				break;
-			case SOUND_PLAYER_SWITCHWEAPON:
-				sound_weapon_switch.play_random(volume_gun*vol, pan);
-				break;
-			case SOUND_PLAYER_EQUIP:
-				break;
-			case SOUND_PLAYER_LAND:
-				sound_body_land.play_random(volume_tee*vol, pan);
-				break;
+				// TODO: we need to control the volume of the diffrent sounds
+				// 		depening on the category
+				snd_play_random(soundid, vol, pan);
 			}
 		}
 	}
@@ -1257,9 +465,10 @@ void modc_newsnapshot()
 
 static void render_projectile(obj_projectile *prev, obj_projectile *current)
 {
-	gfx_texture_set(texture_weapon);
+	gfx_texture_set(data->images[IMAGE_WEAPONS].id);
 	gfx_quads_begin();
-	cell_select_ex(numcellsx,numcellsy,weaponprojtexcoord[current->type].x, weaponprojtexcoord[current->type].y, weaponprojtexcoord[current->type].w, weaponprojtexcoord[current->type].h);
+	
+	select_sprite(data->weapons[current->type%data->num_weapons].sprite_proj);
 	vec2 vel(current->vx, current->vy);
 	
 	// TODO: interpolare angle aswell
@@ -1268,7 +477,7 @@ static void render_projectile(obj_projectile *prev, obj_projectile *current)
 	else
 		gfx_quads_setrotation(0);
 	
-	vec2 pos = mix(vec2(prev->x, prev->y), vec2(current->x, current->y), snap_intratick());
+	vec2 pos = mix(vec2(prev->x, prev->y), vec2(current->x, current->y), client_intratick());
 	gfx_quads_draw(pos.x, pos.y,32,32);
 	gfx_quads_setrotation(0);
 	gfx_quads_end();
@@ -1276,163 +485,155 @@ static void render_projectile(obj_projectile *prev, obj_projectile *current)
 
 static void render_powerup(obj_powerup *prev, obj_powerup *current)
 {
-	//dbg_msg("client", "rendering powerup at %d,%d", current->x, current->y);
-	
-	gfx_texture_set(texture_weapon);
+	gfx_texture_set(data->images[IMAGE_WEAPONS].id);
 	gfx_quads_begin();
 	float angle = 0.0f;
-	float sizex = 64.0f;
-	float sizey = 64.0f;
+	float size = 64.0f;
 	if (current->type == POWERUP_TYPE_WEAPON)
 	{
 		angle = -0.25f * pi * 2.0f;
-		cell_select_ex(numcellsx,numcellsy,weapontexcoord[current->subtype].x, weapontexcoord[current->subtype].y, weapontexcoord[current->subtype].w, weapontexcoord[current->subtype].h);
-		sizex = weaponrenderparams[current->subtype].sizex;
-		sizey = weaponrenderparams[current->subtype].sizey;
+		select_sprite(data->weapons[current->subtype%data->num_weapons].sprite_body);
+		size = data->weapons[current->subtype%data->num_weapons].visual_size;
 	}
 	else
-		cell_select_ex(numcellsx,numcellsy,poweruptexcoord[current->type].x, poweruptexcoord[current->type].y, poweruptexcoord[current->type].w, poweruptexcoord[current->type].h);
-	vec2 vel(current->vx, current->vy);
+	{
+		const int c[] = {
+			SPRITE_POWERUP_HEALTH,
+			SPRITE_POWERUP_ARMOR,
+			SPRITE_POWERUP_WEAPON,
+			SPRITE_POWERUP_NINJA,
+			SPRITE_POWERUP_TIMEFIELD
+			};
+		select_sprite(c[current->type]);
+	}
 	
 	gfx_quads_setrotation(angle);
-	// TODO: interpolare angle aswell
-	/*if(length(vel) > 0.00001f)
-		gfx_quads_setrotation(get_angle(vel));
-	else
-		gfx_quads_setrotation(0);*/
 	
-	vec2 pos = mix(vec2(prev->x, prev->y), vec2(current->x, current->y), snap_intratick());
+	vec2 pos = mix(vec2(prev->x, prev->y), vec2(current->x, current->y), client_intratick());
 	float offset = pos.y/32.0f + pos.x/32.0f;
-	gfx_quads_draw(pos.x+cosf(client_localtime()*2.0f+offset)*2.5f, pos.y+sinf(client_localtime()*2.0f+offset)*2.5f,sizex * 0.65f,sizey * 0.65f);
-	gfx_quads_setrotation(0);
+	pos.x += cosf(client_localtime()*2.0f+offset)*2.5f;
+	pos.y += sinf(client_localtime()*2.0f+offset)*2.5f;
+	draw_sprite(pos.x, pos.y, size);
 	gfx_quads_end();
 }
 
-float getmeleeangle(vec2 direction, obj_player* prev, obj_player* player)
+static void anim_seq_eval(sequence *seq, float time, keyframe *frame)
 {
-	vec2 meleedir(0.53, -0.84);
-	meleedir = normalize(meleedir);
-	vec2 meleedirattack(0.95, -0.3);
-	meleedirattack = normalize(meleedirattack);
-
-	if(direction.x < 0)
+	if(seq->num_frames == 0)
 	{
-		meleedir.x = -meleedir.x;
-		meleedirattack.x = -meleedirattack.x;
+		frame->time = 0;
+		frame->x = 0;
+		frame->y = 0;
+		frame->angle = 0;
 	}
-
-	// 0 -> visualtimeattack go to end pose, (len - visualime) -> go back to normal pose
-
-	float angle = get_angle(meleedir);
-	if (prev->attackticks)
+	else if(seq->num_frames == 1)
+	{
+		*frame = seq->frames[0];
+	}
+	else
 	{
-		float angleattack = get_angle(meleedirattack);
-		int phase1tick = (player->attacklen - player->attackticks);
-		if (phase1tick < player->visualtimeattack)
+		//time = max(0.0f, min(1.0f, time / duration)); // TODO: use clamp
+		keyframe *frame1 = 0;
+		keyframe *frame2 = 0;
+		float blend = 0.0f;
+
+		// TODO: make this smarter.. binary search
+		for (int i = 1; i < seq->num_frames; i++)
 		{
-			float intratick = snap_intratick();
-			float t = ((((float)phase1tick) + intratick)/(float)player->visualtimeattack);
-			angle = LERP(angle, angleattack, min(1.0f,max(0.0f,t)));
-		}
-		else
+			if (seq->frames[i-1].time <= time && seq->frames[i].time >= time)
+			{
+				frame1 = &seq->frames[i-1];
+				frame2 = &seq->frames[i];
+				blend = (time - frame1->time) / (frame2->time - frame1->time);
+				break;
+			}
+		}		
+
+		if (frame1 && frame2)
 		{
-			// go back to normal pose
-			int phase2tick = (player->attacklen - player->visualtimeattack - player->attackticks);
-			float intratick = snap_intratick();
-			float t = ((((float)phase2tick) + intratick)/(float)player->visualtimeattack);
-			angle = LERP(angleattack, angle, min(1.0f,max(0.0f,t)));
+			frame->time = time;
+			frame->x = mix(frame1->x, frame2->x, blend);
+			frame->y = mix(frame1->y, frame2->y, blend);
+			frame->angle = mix(frame1->angle, frame2->angle, blend);
 		}
 	}
-	/*if (prev->attackticks && !player->attackticks)
-	{
-		// blend back to normal
-		float angleattack = get_angle(meleedirattack);
-		angle = LERP(angleattack, angle, min(1.0f,max(0.0f,snap_intratick())));
-	}
-	else if (player->attackticks)
-	{
-		float angleattack = get_angle(meleedirattack);
-		float intratick = snap_intratick();
-		float t = ((((float)player->attackticks) - intratick)/(float)player->attacklen);
-		angle = LERP(angleattack, angle, min(1.0f,max(0.0f,t)));
-	}*/
-
-	return angle;
 }
 
-float gethammereangle(vec2 direction, obj_player* prev, obj_player* player)
+struct animstate
 {
-	float t = 0.0f;
-	if (prev->attackticks)
-		t = 1.0f - ((((float)player->attackticks) - snap_intratick())/(float)player->attacklen);
+	keyframe body;
+	keyframe back_foot;
+	keyframe front_foot;
+	keyframe attach;
+};
 
-	vec2 pos;
-	float angle = 0.0f;
-	hammeranim.evalanim(t,pos,angle);
-	if(direction.x < 0)
-		angle = pi -angle;// + ;
-	//dbg_msg("anim", "Time: %f", t);
-	return angle;
+static void anim_eval(animation *anim, float time, animstate *state)
+{
+	anim_seq_eval(&anim->body, time, &state->body);
+	anim_seq_eval(&anim->back_foot, time, &state->back_foot);
+	anim_seq_eval(&anim->front_foot, time, &state->front_foot);
+	anim_seq_eval(&anim->attach, time, &state->attach);
 }
 
-float getninjaangle(vec2 direction, obj_player* prev, obj_player* player)
+static void anim_add_keyframe(keyframe *seq, keyframe *added, float amount)
 {
-	float t = 0.0f;
-	if (prev->attackticks)
-		t = 1.0f - ((((float)player->attackticks) - snap_intratick())/(float)player->attacklen);
-
-	vec2 pos;
-	float angle = 0.0f;
-	ninjaanim.evalanim(t,pos,angle);
-	if(direction.x < 0)
-		angle = pi -angle;// + ;
-	//dbg_msg("anim", "Time: %f", t);
-	return angle;
+	seq->x += added->x*amount;
+	seq->y += added->y*amount;
+	seq->angle += added->angle*amount;
 }
 
+static void anim_add(animstate *state, animstate *added, float amount)
+{
+	anim_add_keyframe(&state->body, &added->body, amount);
+	anim_add_keyframe(&state->back_foot, &added->back_foot, amount);
+	anim_add_keyframe(&state->front_foot, &added->front_foot, amount);
+	anim_add_keyframe(&state->attach, &added->attach, amount);
+}
 
-float getrecoil(obj_player* prev, obj_player* player)
+static void anim_eval_add(animstate *state, animation *anim, float time, float amount)
 {
-	// attack = -10
-	float recoil = 0.0f;
-	if (prev->attackticks)
-	{
-		float attackrecoil = recoils[player->weapon];
-		int phase1tick = (player->attacklen - player->attackticks);
-		if (phase1tick < player->visualtimeattack)
-		{
-			float intratick = snap_intratick();
-			float t = ((((float)phase1tick) + intratick)/(float)player->visualtimeattack);
-			recoil = LERP(0, attackrecoil, min(1.0f,max(0.0f,t)));
-		}
-		else
-		{
-			// go back to normal pose
-			int phase2tick = (player->attacklen - player->visualtimeattack - player->attackticks);
-			float intratick = snap_intratick();
-			float t = ((((float)phase2tick) + intratick)/(float)(player->attacklen - player->visualtimeattack));
-			recoil = LERP(attackrecoil, 0.0f, min(1.0f,max(0.0f,t)));
-		}
-	}
-	return recoil;
+	animstate add;
+	anim_eval(anim, time, &add);
+	anim_add(state, &add, amount);
 }
 
 static void render_player(obj_player *prev, obj_player *player)
 {
 	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), snap_intratick());
+	vec2 position = mix(vec2(prev->x, prev->y), vec2(player->x, player->y), client_intratick());
 	
+	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;
+	animstate state;
+	anim_eval(&data->animations[ANIM_BASE], 0, &state);
+
+	if(inair)
+		anim_eval_add(&state, &data->animations[ANIM_INAIR], 0, 1.0f); // TODO: some sort of time here
+	else if(stationary)
+		anim_eval_add(&state, &data->animations[ANIM_IDLE], 0, 1.0f); // TODO: some sort of time here
+	else
+		anim_eval_add(&state, &data->animations[ANIM_WALK], walk_time, 1.0f);
+
+	if (player->weapon == WEAPON_HAMMER)
+	{
+		float a = clamp((client_tick()-player->attacktick+client_intratick())/7.5f, 0.0f, 1.0f);
+		anim_eval_add(&state, &data->animations[ANIM_HAMMER_SWING], a, 1.0f);
+	}
+
+		
 	// draw hook
 	if(player->hook_active)
 	{
-		gfx_texture_set(texture_weapon);
+		gfx_texture_set(data->images[IMAGE_WEAPONS].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), snap_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);
@@ -1440,11 +641,11 @@ static void render_player(obj_player *prev, obj_player *player)
 		gfx_quads_setrotation(get_angle(dir)+pi);
 		
 		// render head
-		cell_select_ex(numcellsx,numcellsy, chainheadtexcoord.x,chainheadtexcoord.y, chainheadtexcoord.w, chainheadtexcoord.h);
+		select_sprite(SPRITE_HOOK_HEAD);
 		gfx_quads_draw(hook_pos.x, hook_pos.y, 24,16);
 		
 		// render chain
-		cell_select_ex(numcellsx,numcellsy, chaintexcoord.x, chaintexcoord.y, chaintexcoord.w, chaintexcoord.h);
+		select_sprite(SPRITE_HOOK_CHAIN);
 		for(float f = 24; f < d; f += 24)
 		{
 			vec2 p = hook_pos + dir*f;
@@ -1457,131 +658,51 @@ static void render_player(obj_player *prev, obj_player *player)
 
 	// draw gun
 	{
-		gfx_texture_set(texture_weapon);
+		gfx_texture_set(data->images[IMAGE_WEAPONS].id);
 		gfx_quads_begin();
-		gfx_quads_setrotation(angle);
+		gfx_quads_setrotation(state.attach.angle*pi*2+angle);
+
+		// normal weapons
+		int iw = clamp(player->weapon, 0, NUM_WEAPONS-1);
+		select_sprite(data->weapons[iw].sprite_body, direction.x < 0 ? SPRITE_FLAG_FLIP_Y : 0);
 
-		if (player->modifier & (1 << MODIFIER_TYPE_NINJA))
+		vec2 dir = direction;
+		float recoil = 0.0f;
+		if (player->weapon == WEAPON_HAMMER)
 		{
-			float playerangle = angle;
-			// render NINJA!!! (0.53, 0.84) when idle to -> (0.95, 0.3) at the end of attack
+			// if attack is under way, bash stuffs
 			if(direction.x < 0)
-				cell_select_ex_flip_y(numcellsx, numcellsy, modifiertexcoord[MODIFIER_TYPE_NINJA].x, modifiertexcoord[MODIFIER_TYPE_NINJA].y, modifiertexcoord[MODIFIER_TYPE_NINJA].w, modifiertexcoord[MODIFIER_TYPE_NINJA].h);
+				gfx_quads_setrotation(-pi/2-state.attach.angle*pi*2);
 			else
-				cell_select_ex(numcellsx, numcellsy, modifiertexcoord[MODIFIER_TYPE_NINJA].x, modifiertexcoord[MODIFIER_TYPE_NINJA].y, modifiertexcoord[MODIFIER_TYPE_NINJA].w, modifiertexcoord[MODIFIER_TYPE_NINJA].h);
-
-			angle = getninjaangle(direction, prev, player);//getmeleeangle(direction, prev, player);
-			vec2 ninjadir = get_direction((int)(angle * 256.0f)); // TODO: ugly, fix this
-			gfx_quads_setrotation(angle);
-			vec2 p = position + vec2(0,modifierrenderparams[MODIFIER_TYPE_NINJA].offsety)+ ninjadir * modifierrenderparams[MODIFIER_TYPE_NINJA].offsetx;
-			// if attack is active hold it differently and draw speedlines behind us?
-			gfx_quads_draw(p.x,p.y/*+bob*/,modifierrenderparams[MODIFIER_TYPE_NINJA].sizex, modifierrenderparams[MODIFIER_TYPE_NINJA].sizey);
-
-			if ((player->attacklen - player->attackticks) <= (SERVER_TICK_SPEED / 5))
-			{
-				gfx_quads_setrotation(playerangle);
-				int ihadoken = rand() % NUMHADOKENS;
-				cell_select_ex(numcellsx, numcellsy, hadoken[ihadoken].x, hadoken[ihadoken].y, hadoken[ihadoken].w, hadoken[ihadoken].h);
-				vec2 p = position + vec2(0,hadokenparams[ihadoken].offsety)+ direction * hadokenparams[ihadoken].offsetx;
-				gfx_quads_draw(p.x,p.y/*+bob*/,hadokenparams[ihadoken].sizex, hadokenparams[ihadoken].sizey);
-			}
+				gfx_quads_setrotation(-pi/2+state.attach.angle*pi*2);
 		}
 		else
 		{
-			// normal weapons
-			if(direction.x < 0)
-				cell_select_ex_flip_y(numcellsx, numcellsy, weapontexcoord[player->weapon].x, weapontexcoord[player->weapon].y, weapontexcoord[player->weapon].w, weapontexcoord[player->weapon].h);
-			else
-				cell_select_ex(numcellsx, numcellsy, weapontexcoord[player->weapon].x, weapontexcoord[player->weapon].y, weapontexcoord[player->weapon].w, weapontexcoord[player->weapon].h);
-
-			vec2 dir = direction;
-			float recoil = 0.0f;
-			if (player->weapon == WEAPON_TYPE_MELEE)
-			{
-				// if attack is under way, bash stuffs
-				//angle = getmeleeangle(direction, prev, player);
-				angle = gethammereangle(direction, prev, player);
-				gfx_quads_setrotation(angle);
-				dir = get_direction((int)(angle * 256.0f)); // TODO: ugly, fix this
-			}
-			else
-			{
-				recoil = getrecoil(prev, player);
-			}
-
-			vec2 p = position + vec2(0,weaponrenderparams[player->weapon].offsety) + dir * weaponrenderparams[player->weapon].offsetx - dir * recoil;
-			gfx_quads_draw(p.x,p.y/*+bob*/,weaponrenderparams[player->weapon].sizex, weaponrenderparams[player->weapon].sizey);
-			// draw muzzleflare
-			if (player->weapon == WEAPON_TYPE_GUN || player->weapon == WEAPON_TYPE_SHOTGUN)
-			{
-				// check if we're firing stuff
-				if (true)///prev->attackticks)
-				{
-					float alpha = 0.0f;
-					int phase1tick = (player->attacklen - player->attackticks);
-					if (phase1tick < (player->visualtimeattack + 3))
-					{
-						float intratick = snap_intratick();
-						float t = ((((float)phase1tick) + intratick)/(float)player->visualtimeattack);
-						alpha = LERP(2.0, 0.0f, min(1.0f,max(0.0f,t)));
-					}
-
-					if (alpha > 0.0f)
-					{
-						float offsety = -muzzleparams[player->weapon].offsety;
-						int itex = rand() % nummuzzletex[player->weapon];
-						if(direction.x < 0)
-						{
-							offsety = -offsety;
-							cell_select_ex_flip_y(numcellsx, numcellsy, muzzletexcoord[player->weapon][itex].x, muzzletexcoord[player->weapon][itex].y, muzzletexcoord[player->weapon][itex].w, muzzletexcoord[player->weapon][itex].h);
-						}
-						else
-							cell_select_ex(numcellsx, numcellsy, muzzletexcoord[player->weapon][itex].x, muzzletexcoord[player->weapon][itex].y, muzzletexcoord[player->weapon][itex].w, muzzletexcoord[player->weapon][itex].h);
-
-						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/*+bob*/,muzzleparams[player->weapon].sizex, muzzleparams[player->weapon].sizey);
-					}
-				}
-			}
+			// TODO: should be an animation
+			recoil = 0;
+			float a = (client_tick()-player->attacktick+client_intratick())/5.0f;
+			if(a < 1)
+				recoil = sinf(a*pi);
 		}
-		/*else
-		{
-			// minigun
-			if(direction.x < 0)
-				cell_select_flip_y(4,4,8,2);
-			else
-				cell_select(4,4,8,2);
-			vec2 p = position + vec2(0,3);
-			gfx_quads_draw(p.x,p.y,8*8,8*2);
-		}*/
-			
-		gfx_quads_setrotation(0);
+
+		vec2 p = position + dir*20.0f - dir*recoil*10.0f;
+		draw_sprite(p.x, p.y, data->weapons[iw].visual_size);
+		
+		// TODO: draw muzzleflare
 		gfx_quads_end();
 	}
 	
-	
-	gfx_texture_set(texture_char_default);
+	gfx_texture_set(data->images[IMAGE_CHAR_DEFAULT].id);
 	gfx_quads_begin();
 	
-	float bob = 0;
-	
 	// draw foots
-	const float cyclelength = 128.0f;
-	const float steplength = 26;
-	const float lift = 4.0f;
-	bool stationary = player->vx < 1 && player->vx > -1;
-	bool inair = col_check_point(player->x, player->y+16) == 0;
-	
 	for(int p = 0; p < 2; p++)
 	{
 		// first pass we draw the outline
 		// second pass we draw the filling
 		
-		//int v_offset = p?0:5;
-		int outline = p;// ? 1 : 0;
-		float offsety = charids[player->clientid % 16] * 2.0f;
+		int outline = p==0 ? 1 : 0;
+		int shift = charids[player->clientid%16];
 		
 		for(int f = 0; f < 2; f++)
 		{
@@ -1589,138 +710,41 @@ static void render_player(obj_player *prev, obj_player *player)
 			if(f == 1)
 			{
 				// draw body
-				float t = fmod(position.x, cyclelength/2)/(cyclelength/2);
-				bob = -sinf(pow(t,2)*pi) * 3;
-				cell_select_ex(charnumcellsx,charnumcellsy, body[outline].x,body[outline].y + offsety,body[outline].w,body[outline].h);
-				//cell_select_ex(16,16, 0,0+v_offset,4,4);
-				//const float size = 64.0f;
-				if(stationary || inair)
-					bob = 0;
-				gfx_quads_draw(position.x, position.y-5+bob, 4*basesize, 4*basesize);
+				select_sprite(outline?SPRITE_TEE_BODY_OUTLINE:SPRITE_TEE_BODY, 0, 0, shift*4);
+				gfx_quads_draw(position.x+state.body.x, position.y+state.body.y, 4*basesize, 4*basesize);
 				
 				// draw eyes
 				if(p == 1)
 				{
-					//cell_select_ex(16,16, 8,3,1,1);
 					vec2 md = get_direction(player->angle);
 					float mouse_dir_x = md.x;
 					float mouse_dir_y = md.y;
 					
 					// normal
-					cell_select_ex(charnumcellsx,charnumcellsy, leye.x,leye.y + offsety,leye.w,leye.h);
-					gfx_quads_draw(position.x-4+mouse_dir_x*4, position.y-8+mouse_dir_y*3+bob, basesize, basesize);
-					cell_select_ex(charnumcellsx,charnumcellsy, reye.x,reye.y + offsety,reye.w,reye.h);
-					gfx_quads_draw(position.x+4+mouse_dir_x*4, position.y-8+mouse_dir_y*3+bob, basesize, basesize);
+					select_sprite(SPRITE_TEE_EYE_NORMAL, 0, 0, shift*4);
+					gfx_quads_draw(position.x-4+mouse_dir_x*4, position.y-8+mouse_dir_y*3, basesize, basesize);
+					gfx_quads_draw(position.x+4+mouse_dir_x*4, position.y-8+mouse_dir_y*3, basesize, basesize);
 				}
 			}
 
 			// draw feet
-			//cell_select_ex(16,16, 5,2+v_offset, 2,2);
-			cell_select_ex(charnumcellsx,charnumcellsy, feet[outline].x,feet[outline].y + offsety, feet[outline].w,feet[outline].h);
+			select_sprite(outline?SPRITE_TEE_FOOT_OUTLINE:SPRITE_TEE_FOOT, 0, 0, shift*4);
+			
+			keyframe *foot = f ? &state.front_foot : &state.back_foot;
+			
 			float w = basesize*2.5f;
 			float h = basesize*1.425f;
-			if(inair)
-			{
-				float r = 0.0f;
-				if(player->vy < 0.0f)
-					r = player->vy/3.0f;
-				else
-					r = player->vy/15.0f;
-
-				// clamp the rotation
-				if(r > 0.5f) r = 0.5f;
-				if(r < -0.5f) r = -0.5f;
-				
-				if(player->vx > 0.0f)
-					r *= -1.0f;
-				gfx_quads_setrotation(r);
-				gfx_quads_drawTL(position.x-4+f*7-w/2, position.y+16 - h, w, h);
-				gfx_quads_setrotation(0);
-			}
-			else if(stationary)
-			{
-				// stationary
-				gfx_quads_drawTL(position.x-7+f*14-w/2, position.y+16 - h, w, h);
-			}
-			else
-			{
-				/*
-					The walk cycle, 2 parts
-					
-						  111  
-						 1   1   
-						2     1
-						 2     1
-						  2222221 
-					GROUND GROUND GROUND
-				*/
-				
-				// moving
-				float tx = position.x+f*(cyclelength/2);
-				float t = fmod(tx, cyclelength) / cyclelength;
-				if(player->vx < 0)
-					t = 1.0f-t;
-				
-				float y;
-				float x = 0;
-				float r = 0;
-				float r_back = 1.5f;
-				
-				if(t < 0.5f)
-				{
-					// stomp down foot (part 1)
-					float st = t*2;
-					y = 1.0f-pow(st, 0.5f) + sinf(pow(st,2)*pi)*0.5f;
-					x = -steplength/2 + st*steplength;
-					r = r_back*(1-st) + sinf(pow(st,1.5f)*pi*2);
-				}
-				else
-				{
-					// lift foot up again (part 2)
-					float st = (t-0.5f)*2;
-					y = pow(st, 5.0f);
-					x = steplength/2 - st*steplength;
-					r = y*r_back;
-				}
-				
-
-				if(player->vx > 0)
-				{
-					gfx_quads_setrotation(r);
-					gfx_quads_drawTL(position.x+x-w/2, position.y+16-y*lift - h, w, h);
-				}
-				else
-				{
-					gfx_quads_setrotation(-r);
-					gfx_quads_drawTL(position.x-x-w/2, position.y+16-y*lift - h, w, h);
-				}
-				gfx_quads_setrotation(0);
-			}
-
+			
+			gfx_quads_setrotation(foot->angle);
+			gfx_quads_draw(position.x+foot->x, position.y+foot->y, w, h);
 		}
 	}
 	
 	gfx_quads_end();
-	
-	
 }
 
-static player_input oldinput;
-static bool bfirst = true;
 void modc_render()
 {	
-	if (bfirst)
-	{
-		bfirst = false;
-		oldinput.activeweapon = 0;
-		oldinput.angle = 0;
-		oldinput.blink = 0;
-		oldinput.fire = 0;
-		oldinput.hook = 0;
-		oldinput.jump = 0;
-		oldinput.left = 0;
-		oldinput.right = 0;
-	}
 	// fetch new input
 	{
 		int x, y;
@@ -1734,31 +758,27 @@ void modc_render()
 	// snap input
 	{
 		player_input input;
-		input.left = inp_key_pressed(config.key_move_left);
-		input.right = inp_key_pressed(config.key_move_right);
+		mem_zero(&input, sizeof(input));
+
 		float a = atan((float)mouse_pos.y/(float)mouse_pos.x);
 		if(mouse_pos.x < 0)
 			a = a+pi;
+
 		input.angle = (int)(a*256.0f);
+		input.left = inp_key_pressed(config.key_move_left);
+		input.right = inp_key_pressed(config.key_move_right);
 		input.jump = inp_key_pressed(config.key_jump);
-		
-		input.fire = inp_key_pressed(config.key_fire);// | (oldinput.fire << 16);
-		//oldinput.fire = input.fire & 0x0000ffff;
-		
+		input.fire = inp_key_pressed(config.key_fire);
 		input.hook = inp_key_pressed(config.key_hook);
 
 		input.blink = inp_key_pressed('S');
 		
 		// Weapon switching
-		input.activeweapon = inp_key_pressed('1') ? 0x80000000 : 0;
-		if (!input.activeweapon)
-			input.activeweapon = inp_key_pressed('2') ? 0x80000000 | 1 : 0;
-		if (!input.activeweapon)
-			input.activeweapon = inp_key_pressed('3') ? 0x80000000 | 2 : 0;
-		if (!input.activeweapon)
-			input.activeweapon = inp_key_pressed('4') ? 0x80000000 | 3 : 0;
-		/*if (!input.activeweapon)
-			input.activeweapon = inp_key_pressed('5') ? 0x80000000 | 4 : 0;*/
+		input.activeweapon = -1;
+		input.activeweapon = inp_key_pressed('1') ? 0 : input.activeweapon;
+		input.activeweapon = inp_key_pressed('2') ? 1 : input.activeweapon;
+		input.activeweapon = inp_key_pressed('3') ? 2 : input.activeweapon;
+		input.activeweapon = inp_key_pressed('4') ? 3 : input.activeweapon;
 
 		snap_input(&input, sizeof(input));
 	}
@@ -1767,7 +787,6 @@ void modc_render()
 	{
 		// 1. fetch local player
 		// 2. set him to the center
-		
 		int num = snap_num_items(SNAP_CURRENT);
 		for(int i = 0; i < num; i++)
 		{
@@ -1784,7 +803,7 @@ void modc_render()
 
 					void *p = snap_find_item(SNAP_PREV, item.type, item.id);
 					if(p)
-						local_player_pos = mix(vec2(((obj_player *)p)->x, ((obj_player *)p)->y), local_player_pos, snap_intratick());
+						local_player_pos = mix(vec2(((obj_player *)p)->x, ((obj_player *)p)->y), local_player_pos, client_intratick());
 					break;
 				}
 			}
@@ -1816,9 +835,9 @@ void modc_render()
 	
 	// draw background
 	gfx_clear(0.65f,0.78f,0.9f);
-	
-	{
 
+	// draw the sun	
+	{
 		vec2 pos(local_player_pos.x*0.5f, local_player_pos.y*0.5f);
 
 		gfx_texture_set(-1);
@@ -1833,8 +852,6 @@ void modc_render()
 			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_draw_freeform(0,0, -100,0, -100,-100, 0,-100);
-			
 			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);
@@ -1849,7 +866,7 @@ void modc_render()
 		gfx_quads_end();
 		gfx_blend_normal();
 		
-		gfx_texture_set(texture_sun);
+		gfx_texture_set(data->images[IMAGE_SUN].id);
 		gfx_quads_begin();
 		gfx_quads_draw(pos.x, pos.y, 256, 256);
 		gfx_quads_end();
@@ -1857,9 +874,6 @@ void modc_render()
 	
 	// render map
 	tilemap_render(32.0f, 0);
-#ifdef _DEBUG
-	float speed = 0.0f;
-#endif
 	// render items
 	int num = snap_num_items(SNAP_CURRENT);
 	for(int i = 0; i < num; i++)
@@ -1871,18 +885,7 @@ void modc_render()
 		{
 			void *prev = snap_find_item(SNAP_PREV, item.type, item.id);
 			if(prev)
-			{
 				render_player((obj_player *)prev, (obj_player *)data);
-/*#ifdef _DEBUG
-				{
-					obj_player *p = (obj_player *)prev;
-					obj_player *c = (obj_player *)data;
-					vec2 positionold = vec2(p->x, p->y);
-					vec2 poscur = vec2(c->x, c->y);
-					speed = distance(positionold,poscur);
-				}
-#endif*/
-			}
 		}
 		else if(item.type == OBJTYPE_PROJECTILE)
 		{
@@ -1907,68 +910,58 @@ void modc_render()
 	// render health mods
 	healthmods.render();
 	
-	// render cursor 
-	// FIXME CURSOR!!!
-	
 	if(local_player)
 	{
-		gfx_texture_set(texture_weapon);
+		gfx_texture_set(data->images[IMAGE_WEAPONS].id);
 		gfx_quads_begin();
-		if (local_player->modifier & (1 << MODIFIER_TYPE_NINJA))
-			cell_select_ex(numcellsx,numcellsy, modifiertexcoordcursor[MODIFIER_TYPE_NINJA].x, modifiertexcoordcursor[MODIFIER_TYPE_NINJA].y, modifiertexcoordcursor[MODIFIER_TYPE_NINJA].w, modifiertexcoordcursor[MODIFIER_TYPE_NINJA].h);
-		else
-			cell_select_ex(numcellsx,numcellsy, weapontexcoordcursor[local_player->weapon].x, weapontexcoordcursor[local_player->weapon].y, weapontexcoordcursor[local_player->weapon].w, weapontexcoordcursor[local_player->weapon].h);
+		
+		// render cursor
+		select_sprite(data->weapons[local_player->weapon%data->num_weapons].sprite_cursor);
 		float cursorsize = 64;
-		gfx_quads_draw(local_player_pos.x+mouse_pos.x, local_player_pos.y+mouse_pos.y,cursorsize,cursorsize);
-
+		draw_sprite(local_player_pos.x+mouse_pos.x, local_player_pos.y+mouse_pos.y, cursorsize);
 
 		// render ammo count
 		// render gui stuff
 		gfx_quads_end();
 		gfx_quads_begin();
 		gfx_mapscreen(0,0,400,300);
-		cell_select_ex(numcellsx,numcellsy, weaponprojtexcoord[local_player->weapon].x, weaponprojtexcoord[local_player->weapon].y, weaponprojtexcoord[local_player->weapon].w, weaponprojtexcoord[local_player->weapon].h);
+		select_sprite(data->weapons[local_player->weapon%data->num_weapons].sprite_proj);
 		for (int i = 0; i < local_player->ammocount; i++)
-		{
 			gfx_quads_drawTL(10+i*12,34,10,10);
-		}
 		gfx_quads_end();
 
-		gfx_texture_set(texture_game);
+		gfx_texture_set(data->images[IMAGE_GAME].id);
 		gfx_quads_begin();
 		int h = 0;
-		cell_select_ex(32,16, 0,0, 4,4);
+		
+		// render health
+		select_sprite(SPRITE_HEALTH_FULL);
 		for(; h < local_player->health; h++)
 			gfx_quads_drawTL(10+h*12,10,10,10);
 		
-		cell_select_ex(32,16, 5,0, 4,4);
+		select_sprite(SPRITE_HEALTH_EMPTY);
 		for(; h < 10; h++)
 			gfx_quads_drawTL(10+h*12,10,10,10);
 
+		// render armor meter
 		h = 0;
-		cell_select_ex(32,16, 0,5, 4,4);
+		select_sprite(SPRITE_ARMOR_FULL);
 		for(; h < local_player->armor; h++)
 			gfx_quads_drawTL(10+h*12,22,10,10);
 		
-		cell_select_ex(32,16, 5,5, 4,4);
+		select_sprite(SPRITE_ARMOR_EMPTY);
 		for(; h < 10; h++)
 			gfx_quads_drawTL(10+h*12,22,10,10);
 		gfx_quads_end();
-
-		// render speed
-/*#ifdef _DEBUG
-		gfx_texture_set(font_texture);
-		char text[256];
-		sprintf(text,"speed: %f",speed);
-		gfx_quads_text(300,20,10,text);
-#endif*/
 	}
+	
 	// render gui stuff
 	gfx_mapscreen(0,0,400,300);
+	
 	// render score board
 	if(inp_key_pressed(baselib::input::tab))
 	{
-		gfx_texture_set(font_texture);
+		gfx_texture_set(-1);
 		gfx_quads_text(10, 50, 8, "Score Board");
 
 		int num = snap_num_items(SNAP_CURRENT);
@@ -1984,8 +977,8 @@ void modc_render()
 				if(player)
 				{
 					char buf[128];
-					char name[32];
-					snap_decode_string(player->name, name, 32);
+					char name[32] = "tjo";
+					//snap_decode_string(player->name, name, 32);
 					sprintf(buf, "%4d %s", player->score, name);
 					gfx_quads_text(10, 50 + 10 * row, 8, buf);
 					row++;
diff --git a/src/game/client/mapres_tilemap.cpp b/src/game/client/mapres_tilemap.cpp
index 36302d0a..52f2a9b1 100644
--- a/src/game/client/mapres_tilemap.cpp
+++ b/src/game/client/mapres_tilemap.cpp
@@ -33,7 +33,8 @@ void tilemap_render(float scale, int fg)
 			gfx_quads_begin();
 			
 			int c = 0;
-			float frac = (1.0f/1024.0f); //2.0f;
+			float frac = (1.0f/1024.0f);//2.0f; //2.0f;
+			const float s = 1.0f;
 			for(int y = 0; y < tmap->height; y++)
 				for(int x = 0; x < tmap->width; x++, c++)
 				{
@@ -41,13 +42,14 @@ void tilemap_render(float scale, int fg)
 					if(d)
 					{
 						gfx_quads_setsubset(
-							(d%16)/16.0f+frac,
-							(d/16)/16.0f+frac,
-							(d%16)/16.0f+1.0f/16.0f-frac,
-							(d/16)/16.0f+1.0f/16.0f-frac);
+							(d%16)/16.0f*s+frac,
+							(d/16)/16.0f*s+frac,
+							((d%16)/16.0f+1.0f/16.0f)*s-frac,
+							((d/16)/16.0f+1.0f/16.0f)*s-frac);
 						gfx_quads_drawTL(x*scale, y*scale, scale, scale);
 					}
 				}
+			
 			gfx_quads_end();
 		}
 	}
diff --git a/src/game/client/menu.cpp b/src/game/client/menu.cpp
index 12896585..c8c41e4b 100644
--- a/src/game/client/menu.cpp
+++ b/src/game/client/menu.cpp
@@ -374,13 +374,14 @@ void draw_single_part_button(void *id, const char *text, int checked, float x, f
 	else
 		tileset = tileset_regular;
 
-	draw_part((gui_parts_enum)(int)(int *)extra, tileset, x, y, w, h);
+	
+	draw_part((gui_parts_enum)((char*)extra-(char*)0), tileset, x, y, w, h);
 }
 
 void draw_menu_button(void *id, const char *text, int checked, float x, float y, float w, float h, void *extra)
 {
 	gui_composite_box_enum box_style;
-	if ((int)extra)
+	if ((int)((char*)extra-(char*)0))
 		box_style = screen_info_box;
 	else
 		box_style = screen_list_box;
@@ -401,7 +402,7 @@ void draw_teewars_button(void *id, const char *text, int checked, float x, float
 	else
 		tileset = tileset_regular;
 
-	if ((int)(int *)extra == 1)
+	if ((int)((char*)extra-(char*)0) == 1)
 		tileset = tileset_inactive;
 
 	draw_box(button_big_box, tileset, x, y, w, h);
@@ -486,7 +487,7 @@ int ui_do_combo_box(void *id, float x, float y, float w, char *lines, int line_c
 	float line_height = 36.0f;
 
 	int inside = (ui_active_item() == id) ? ui_mouse_inside(x, y, w, line_count * line_height) : ui_mouse_inside(x, y, w, line_height);
-	int hover_index = (ui_mouse_y() - y) / line_height;
+	int hover_index = (int)((ui_mouse_y() - y) / line_height);
 
 	if (ui_active_item() == id)
 	{
diff --git a/src/game/game.h b/src/game/game.h
index 2696b74d..a1471887 100644
--- a/src/game/game.h
+++ b/src/game/game.h
@@ -1,16 +1,9 @@
 #include <baselib/system.h>
-#include <baselib/vmath.h>
+#include <baselib/math.h>
 #include <math.h>
 #include "../engine/interface.h"
 #include "mapres_col.h"
 
-// Don't tweak :)
-const float pi = 3.14159265358979f;
-
-#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)
-
 inline baselib::vec2 get_direction(int angle)
 {
 	float a = angle/256.0f;
@@ -21,7 +14,7 @@ inline float get_angle(baselib::vec2 dir)
 {
 	float a = atan(dir.y/dir.x);
 	if(dir.x < 0)
-		a = a+pi;
+		a = a+baselib::pi;
 	return a;
 }
 
@@ -76,7 +69,8 @@ struct ev_sound
 
 struct ev_healthmod
 {
-	int x, y;
+	int x, y; // could perhaps be an client id
+	int vx, vy; // should be an angle instead
 	int amount;
 };
 
@@ -84,20 +78,19 @@ struct obj_projectile
 {
 	int type;
 	int x, y;
-	int vx, vy;
+	int vx, vy; // should be an angle instead
 };
 
 struct obj_powerup
 {
-	int type;
-	int subtype;
 	int x, y;
-	int vx, vy;
+	int type; // why do we need two types?
+	int subtype;
 };
 
 struct obj_player
 {
-	int name[8];
+	//int name[8];
 	
 	int local;
 	int clientid;
@@ -113,12 +106,10 @@ struct obj_player
 	// current active weapon
 	int weapon;
 	// current active modifier
-	int modifier;
+	//int modifier;
 
-	// num attack ticks left of current attck
-	int attackticks;
-	int attacklen;
-	int visualtimeattack;
+	// num attack ticks left of current attack
+	int attacktick;
 	
 	int score;
 	int emote;
@@ -183,49 +174,3 @@ enum
 	ITEM_ARMOR_10=0x00030010,
 	ITEM_NINJA=0x00040001,
 };
-
-// sound categories and stuff
-enum
-{
-	SOUND_FIRE_GUN = 0,
-	SOUND_FIRE_SHOTGUN,
-	SOUND_FIRE_ROCKET,
-	SOUND_FIRE_MELEE,
-	SOUND_FIRE_NINJA,
-
-
-	// impacts with world
-	SOUND_IMPACT_PROJECTILE_GUN,
-	SOUND_IMPACT_PROJECTILE_SHOTGUN,
-	SOUND_IMPACT_PROJECTILE_ROCKET,
-
-
-	// chain ?
-
-	// Player movement
-	SOUND_PLAYER_JUMP,
-	SOUND_PLAYER_HURT_SHORT,
-	SOUND_PLAYER_HURT_LONG,
-	SOUND_PLAYER_SPAWN,
-	SOUND_PLAYER_CHAIN_LOOP,
-	SOUND_PLAYER_CHAIN_IMPACT,
-	SOUND_PLAYER_IMPACT,
-	SOUND_PLAYER_IMPACT_NINJA,
-	SOUND_PLAYER_DIE,
-	SOUND_PLAYER_SWITCHWEAPON,
-	SOUND_PLAYER_EQUIP,
-	SOUND_PLAYER_LAND,
-
-	SOUND_NUMSOUNDS,
-
-
-	// extra defs (for future?)
-	SOUND_EQUIP_GUN = SOUND_PLAYER_EQUIP,
-	SOUND_EQUIP_ROCKET = SOUND_PLAYER_EQUIP,
-	SOUND_EQUIP_SHOTGUN = SOUND_PLAYER_EQUIP,
-	SOUND_EQUIP_MELEE = SOUND_PLAYER_EQUIP,
-
-	SOUND_LOOPFLAG_STARTLOOP = 0x80000000,
-	SOUND_LOOPFLAG_STOPLOOP = 0x40000000,
-	SOUND_MASK = ~(SOUND_LOOPFLAG_STARTLOOP | SOUND_LOOPFLAG_STOPLOOP),
-};
diff --git a/src/game/game_variables.h b/src/game/game_variables.h
index bd94106a..4414d5f2 100644
--- a/src/game/game_variables.h
+++ b/src/game/game_variables.h
@@ -1,8 +1,7 @@
-MACRO_CONFIG_INT(screen_width, 800, 0, 0)
-MACRO_CONFIG_INT(screen_height, 600, 0, 0)
-MACRO_CONFIG_STR(player_name, 32, "nameless tee")
 MACRO_CONFIG_INT(key_move_left, 65, 32, 512)
 MACRO_CONFIG_INT(key_move_right, 68, 32, 512)
 MACRO_CONFIG_INT(key_jump, 32, 32, 512)
 MACRO_CONFIG_INT(key_fire, 384, 32, 512)
 MACRO_CONFIG_INT(key_hook, 385, 32, 512)
+//MACRO_CONFIG_INT(key_fire, 'E', 32, 512)
+//MACRO_CONFIG_INT(key_hook, 'Q', 32, 512)
diff --git a/src/game/mapres_col.cpp b/src/game/mapres_col.cpp
index 29215d7d..d2f9d0c6 100644
--- a/src/game/mapres_col.cpp
+++ b/src/game/mapres_col.cpp
@@ -1,8 +1,12 @@
 #include <baselib/system.h>
+#include <baselib/vmath.h>
+#include <baselib/math.h>
 #include "../engine/interface.h"
 #include "mapres_col.h"
 #include "mapres.h"
 
+using namespace baselib;
+
 /*
 	Simple collision rutines!
 */
@@ -42,3 +46,24 @@ int col_check_point(int x, int y)
 	
 	return col.data[ny*col.w+nx];
 }
+
+// TODO: rewrite this smarter!
+bool col_intersect_line(vec2 pos0, vec2 pos1, vec2 *out)
+{
+	float d = distance(pos0, pos1);
+	
+	for(float f = 0; f < d; f++)
+	{
+		float a = f/d;
+		vec2 pos = mix(pos0, pos1, a);
+		if(col_check_point((int)pos.x, (int)pos.y))
+		{
+			if(out)
+				*out = pos;
+			return true;
+		}
+	}
+	if(out)
+		*out = pos1;
+	return false;
+}
diff --git a/src/game/mapres_col.h b/src/game/mapres_col.h
index 2afad439..83690577 100644
--- a/src/game/mapres_col.h
+++ b/src/game/mapres_col.h
@@ -1,3 +1,4 @@
+#include <baselib/vmath.h>
 
 struct mapres_collision
 {
@@ -8,3 +9,4 @@ struct mapres_collision
 
 int col_init(int dividor);
 int col_check_point(int x, int y);
+bool col_intersect_line(baselib::vec2 pos0, baselib::vec2 pos1, baselib::vec2 *out);
diff --git a/src/game/server/game_server.cpp b/src/game/server/game_server.cpp
index df17169c..88cbb471 100644
--- a/src/game/server/game_server.cpp
+++ b/src/game/server/game_server.cpp
@@ -1,9 +1,13 @@
 #include <stdlib.h>
 #include <string.h>
 #include "../game.h"
+#include "data.h"
 
 using namespace baselib;
 
+// ---------
+const bool debug_bots = false;
+
 // --------- PHYSICS TWEAK! --------
 const float ground_control_speed = 7.0f;
 const float ground_control_accel = 2.0f;
@@ -25,6 +29,30 @@ void create_smoke(vec2 p);
 void create_sound(vec2 pos, int sound, 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_box(vec2 *inout_pos, vec2 *inout_vel, vec2 size, float elasticity)
 {
@@ -91,27 +119,6 @@ void move_box(vec2 *inout_pos, vec2 *inout_vel, vec2 size, float elasticity)
 	*inout_vel = vel;
 }
 
-// TODO: rewrite this smarter!
-bool intersect_line(vec2 pos0, vec2 pos1, vec2 *out)
-{
-	float d = distance(pos0, pos1);
-	
-	for(float f = 0; f < d; f++)
-	{
-		float a = f/d;
-		vec2 pos = mix(pos0, pos1, a);
-		if(col_check_point(pos))
-		{
-			if(out)
-				*out = pos;
-			return true;
-		}
-	}
-	if(out)
-		*out = pos1;
-	return false;
-}
-
 //
 class event_handler
 {
@@ -128,7 +135,7 @@ class event_handler
 public:
 	event_handler()
 	{
-		num_events = 0;
+		clear();
 	}
 	
 	void *create(int type, int size)
@@ -159,27 +166,6 @@ public:
 };
 
 static event_handler events;
-/*
-template<typename T, int SIZE>
-class pool
-{
-	struct element
-	{
-		int next_free;
-		T data;
-	};
-	
-	element elements[SIZE];
-	int first_free;
-public:
-	pool()
-	{
-		first_free = 0;
-		for(int i = 0; i < SIZE; i++)
-			elements[i].next_free = i+1;
-		elements[SIZE-1].next_free = -1;
-	}
-};*/
 
 // a basic entity
 class entity
@@ -190,12 +176,15 @@ private:
 	entity *prev_entity;
 	entity *next_entity;
 	int index;
+	static int current_id;
+protected:
+	int id;
 	
 public:
-	vec2 pos;
 	float proximity_radius;
 	unsigned flags;
 	int objtype;
+	vec2 pos;
 
 	enum
 	{
@@ -208,6 +197,9 @@ public:
 		pos = vec2(0,0);
 		flags = 0;
 		proximity_radius = 0;
+		
+		current_id++;
+		id = current_id;
 	}
 	
 	virtual ~entity()
@@ -216,35 +208,14 @@ public:
 
 	virtual void destroy() { delete this; }
 	virtual void tick() {}
+	virtual void tick_defered() {}
+		
 	virtual void snap(int snapping_client) {}
 		
 	virtual bool take_damage(vec2 force, int dmg, int from) { return true; }
 };
 
-class powerup : public entity
-{
-public:
-	static const int phys_size = 14;
-	enum
-	{
-		POWERUP_FLAG_HOOKABLE = 1 << 0,
-	};
-	vec2 vel;
-	class player* playerhooked;
-	int type;
-	int id;
-	int subtype; // weapon type for instance?
-	int numitems; // number off powerup items
-	int flags;
-	int spawntick;
-	powerup(int _type, int _subtype = 0, int _numitems = 0, int _flags = 0);
-
-	static void spawnrandom(int _lifespan);
-
-	void tick();
-
-	void snap(int snapping_client);
-};
+int entity::current_id = 1;
 
 // game world. handles all entities
 class game_world
@@ -283,6 +254,7 @@ public:
 				if (ent->objtype != types[i])
 					continue;
 
+				// TODO: this seams like it could be done several times unnessesary
 				if(distance(ent->pos, pos) < radius+ent->proximity_radius)
 				{
 					ents[num] = ent;
@@ -309,9 +281,6 @@ public:
 	void destroy_entity(entity *ent)
 	{
 		ent->flags |= entity::FLAG_DESTROY;
-		// call destroy
-		//remove_entity(ent);
-		//ent->destroy();
 	}
 	
 	void remove_entity(entity *ent)
@@ -337,6 +306,9 @@ public:
 		// update all objects
 		for(entity *ent = first_entity; ent; ent = ent->next_entity)
 			ent->tick();
+			
+		for(entity *ent = first_entity; ent; ent = ent->next_entity)
+			ent->tick_defered();
 		
 		// destroy objects marked for destruction
 		entity *ent = first_entity;
@@ -355,6 +327,21 @@ public:
 
 static game_world world;
 
+// TODO: move to seperate file
+class powerup : public entity
+{
+public:
+	static const int phys_size = 14;
+	
+	int type;
+	int subtype; // weapon type for instance?
+	int spawntick;
+	powerup(int _type, int _subtype = 0);
+	
+	virtual void tick();
+	virtual void snap(int snapping_client);
+};
+
 // projectile entity
 class projectile : public entity
 {
@@ -363,10 +350,10 @@ public:
 	{
 		PROJECTILE_FLAGS_EXPLODE = 1 << 0,
 	};
+	
 	vec2 vel;
-	entity* powner;
+	entity *powner; // this is nasty, could be removed when client quits
 	int lifespan;
-	int id;
 	int owner;
 	int type;
 	int flags;
@@ -374,11 +361,9 @@ public:
 	int sound_impact;
 	float force;
 	
-	projectile(int type, int owner, vec2 pos, vec2 vel, int span, entity* powner, int damage, int flags = 0, float force = 0.0f, int sound_impact = -1) :
-		entity(OBJTYPE_PROJECTILE)
+	projectile(int type, int owner, vec2 pos, vec2 vel, int span, entity* powner, int damage, int flags = 0, float force = 0.0f, int sound_impact = -1)
+	: entity(OBJTYPE_PROJECTILE)
 	{
-		static int current_id = 0;
-		this->id = current_id++;
 		this->type = type;
 		this->pos = pos;
 		this->vel = vel;
@@ -398,20 +383,19 @@ public:
 		vel.y += 0.25f;
 		pos += vel;
 		lifespan--;
+		
 		// check player intersection as well
-		entity* targetplayer = (entity*)intersect_player(oldpos, pos, oldpos, powner);
+		entity *targetplayer = (entity*)intersect_player(oldpos, pos, oldpos, powner);
 		if(targetplayer || lifespan < 0 || col_check_point((int)pos.x, (int)pos.y))
 		{
 			if (lifespan >= 0)
 				create_sound(pos, sound_impact);
+				
 			if (flags & PROJECTILE_FLAGS_EXPLODE)
-			{
 				create_explosion(oldpos, owner);
-			}
 			else if (targetplayer)
-			{
 				targetplayer->take_damage(normalize(vel) * force, damage, owner);
-			}
+				
 			world.destroy_entity(this);
 		}
 	}
@@ -421,639 +405,85 @@ public:
 		obj_projectile *proj = (obj_projectile *)snap_new_item(OBJTYPE_PROJECTILE, id, sizeof(obj_projectile));
 		proj->x = (int)pos.x;
 		proj->y = (int)pos.y;
-		proj->vx = (int)vel.x;
+		proj->vx = (int)vel.x; // TODO: should be an angle
 		proj->vy = (int)vel.y;
 		proj->type = type;
 	}
 };
 
 // player entity
+// TODO: move to separate file
 class player : public entity
 {
 public:
 	static const int phys_size = 28;
-	enum
+	
+	enum // what are these?
 	{
-		WEAPON_NEEDRELOAD		= 1 << 0,
-		WEAPON_DROPONUNEQUIP	= 1 << 1,
-		WEAPON_DRAWSAMMO		= 1 << 2,
-		WEAPON_HASSECONDARY		= 1 << 3,
-		WEAPON_ISACTIVE			= 1 << 4, // has the item
-		WEAPON_AUTOFIRE			= 1 << 5,
-
 		WEAPON_PROJECTILETYPE_GUN		= 0,
 		WEAPON_PROJECTILETYPE_ROCKET	= 1,
 		WEAPON_PROJECTILETYPE_SHOTGUN	= 2,
-
-		// Gun
-
-
-		// modifiers
-		MODIFIER_HASACTIVATIONS			= 1 << 0,
-		MODIFIER_TIMELIMITED			= 1 << 1,
-		MODIFIER_ISACTIVE				= 1 << 2,
-		MODIFIER_NEEDSACTIVATION		= 1 << 3,
-
-		MODIFIER_RETURNFLAGS_OVERRIDEWEAPON		= 1 << 0,
-		MODIFIER_RETURNFLAGS_OVERRIDEVELOCITY	= 1 << 1,
-		MODIFIER_RETURNFLAGS_OVERRIDEPOSITION	= 1 << 2,
-		MODIFIER_RETURNFLAGS_OVERRIDEGRAVITY	= 1 << 3,
 	};
-	class weapon
-	{
-	public:
-		entity* hitobjects[10];
-		int numobjectshit; // for melee, so we don't hit the same object more than once per bash
-		int weapontype;
-		int equiptime;
-		int unequiptime;
-		int numammo;
-		int magsize;
-		int nummagazines;
-		int flags;
-		int firetime;
-		int reloadtime;
-		int projectileclass;
-		int damage;
-		int sound_fire;
-		int sound_equip;
-		int sound_impact;
-		int sound_impact_projectile;
-		int visualtimeattack;
-		float projectilevel;
-		float projectilespan;
-		float reach; // for melee
-		float force;
-		float recoilforce;
-		float projoffsety;
-		float projoffsetx;
-		
-		weapon()
-		{
-			weapontype = 0;
-			numammo = 0;
-			flags = 0;
-			reloadtime = 0;
-			projectileclass = 0;
-			numobjectshit = 0;
-			reach = 0.0f;
-			force = 5.0f;
-			damage = 1;
-			sound_fire = -1;
-			sound_equip = -1;
-			sound_impact = -1;
-			sound_impact_projectile = -1,
-			visualtimeattack = 3;
-			recoilforce = 0.0f;
-			projoffsety = 0.0f;
-			projoffsetx = 0.0f;
-		}
-
-		void setgun(int ammo = 10)
-		{
-			weapontype = WEAPON_TYPE_GUN;
-			flags = 0;//WEAPON_DRAWSAMMO;
-			numammo = ammo;
-			projectileclass = WEAPON_PROJECTILETYPE_GUN;
-			firetime = SERVER_TICK_SPEED/10;
-			magsize = 0;
-			projectilevel = 30.0f;
-			projectilespan = 50.0f * 1.0f;
-			sound_fire = SOUND_FIRE_GUN;
-			sound_equip = SOUND_EQUIP_GUN;
-			sound_impact_projectile = SOUND_IMPACT_PROJECTILE_GUN;
-			projoffsety = -10.0f;
-			projoffsetx = 24.0f;
-		}
-
-		void setrocket(int ammo = 10)
-		{
-			weapontype = WEAPON_TYPE_ROCKET;
-			flags = WEAPON_DRAWSAMMO;
-			numammo = ammo;
-			projectileclass = WEAPON_PROJECTILETYPE_ROCKET;
-			projectilevel = 15.0f;
-			projectilespan = 50.0f * 5.0f;
-			firetime = SERVER_TICK_SPEED * 4/5;
-			magsize = 0;
-			recoilforce = 5.0f;
-			sound_fire = SOUND_FIRE_ROCKET;
-			sound_equip = SOUND_EQUIP_ROCKET;
-			sound_impact_projectile = SOUND_IMPACT_PROJECTILE_ROCKET;
-			projoffsety = -17.0f;
-			projoffsetx = 24.0f;
-		}
-
-		/*void setsniper(int ammo = 10)
-		{
-			weapontype = WEAPON_TYPE_SNIPER;
-			flags = WEAPON_DRAWSAMMO | WEAPON_HASSECONDARY | WEAPON_NEEDRELOAD;
-			numammo = ammo;
-			projectileclass = WEAPON_PROJECTILETYPE_SNIPER;
-			projectilevel = 30.0f;
-			projectilespan = 50.0f * 5.0f;
-			firetime = SERVER_TICK_SPEED;
-			reloadtime = SERVER_TICK_SPEED/2;
-			magsize = 2;
-			recoilforce = 20.0f;
-		}*/
-
-		void setshotgun(int ammo = 10)
-		{
-			weapontype = WEAPON_TYPE_SHOTGUN;
-			flags = WEAPON_DRAWSAMMO | WEAPON_NEEDRELOAD;
-			numammo = ammo;
-			projectileclass = WEAPON_PROJECTILETYPE_SHOTGUN;
-			projectilevel = 30.0f;
-			projectilespan = 50.0f * 5.0f;
-			firetime = SERVER_TICK_SPEED/2;
-			reloadtime = SERVER_TICK_SPEED/2;
-			magsize = 2;
-			damage = 3;
-			recoilforce = 5.0f;
-			sound_fire = SOUND_FIRE_SHOTGUN;
-			sound_equip = SOUND_EQUIP_SHOTGUN;
-			sound_impact_projectile = SOUND_IMPACT_PROJECTILE_SHOTGUN;
-			projoffsety = -17.0f;
-			projoffsetx = 24.0f;
-		}
-
-		void setmelee(int ammo = 10)
-		{
-			weapontype = WEAPON_TYPE_MELEE;
-			flags = 0;//WEAPON_AUTOFIRE;
-			numammo = ammo;
-			projectileclass = -1;
-			firetime = SERVER_TICK_SPEED/5;
-			reloadtime = 0;
-			magsize = 2;
-			numobjectshit = 0;
-			reach = 15.0f;
-			damage = 1;
-			sound_fire = SOUND_FIRE_MELEE;
-			sound_equip = SOUND_EQUIP_MELEE;
-			sound_impact = SOUND_PLAYER_IMPACT;
-		}
-
-		void settype()
-		{
-			switch(weapontype)
-			{
-			case WEAPON_TYPE_GUN:
-				{
-					setgun();
-					break;
-				}
-			case WEAPON_TYPE_ROCKET:
-				{
-					setrocket();
-					break;
-				}
-			/*case WEAPON_TYPE_SNIPER:
-				{
-					setsniper();
-					break;
-				}*/
-			case WEAPON_TYPE_SHOTGUN:
-				{
-					setshotgun();
-					break;
-				}
-			case WEAPON_TYPE_MELEE:
-				{
-					setmelee();
-					break;
-				}
-			default:
-				break;
-			}
-		}
-
-		int activate(player* player)
-		{
-			// create sound event for fire
-			int projectileflags = 0;
-			create_sound(player->pos, sound_fire);
-
-			switch (weapontype)
-			{
-			case WEAPON_TYPE_ROCKET:
-				projectileflags |= projectile::PROJECTILE_FLAGS_EXPLODE;
-			case WEAPON_TYPE_GUN:
-			//case WEAPON_TYPE_SNIPER:
-			case WEAPON_TYPE_SHOTGUN:
-				{
-					if (flags & WEAPON_DRAWSAMMO)
-						numammo--;
-					// Create projectile
-					new projectile(projectileclass,
-						player->client_id,
-						player->pos+vec2(0,projoffsety)+player->direction*projoffsetx,
-						player->direction*projectilevel,
-						(int)projectilespan,
-						player,
-						damage,
-						projectileflags,
-						force,
-						sound_impact_projectile);
-					// recoil force if any
-					if (recoilforce > 0.0f)
-					{
-						vec2 dir(player->direction.x,0.5);
-						if (dir.x == 0.0f)
-							dir.x = 0.5f;
-						else
-							dir = normalize(dir);
-						player->vel -= dir * recoilforce;
-					}
-					return firetime;
-				}
-			case WEAPON_TYPE_MELEE:
-				{
-					// Start bash sequence
-					numobjectshit = 0;
-					return firetime;
-				}
-			default:
-				return 0;
-			}
-		}
-
-		void update(player* owner, int fire_timeout)
-		{
-			switch(weapontype)
-			{
-			case WEAPON_TYPE_MELEE:
-				{
-					// No more melee
-					if (fire_timeout <= 0)
-						return;
-
-					// only one that needs update (for now)
-					// do selection for the weapon and bash anything in it
-					// check if we hit anything along the way
-					int type = OBJTYPE_PLAYER;
-					entity *ents[64];
-					vec2 dir = owner->pos + owner->direction * reach;
-					float radius = length(dir * 0.5f);
-					vec2 center = owner->pos + dir * 0.5f;
-					int num = world.find_entities(center, radius, ents, 64, &type, 1);
-					
-					for (int i = 0; i < num; i++)
-					{
-						// Check if entity is a player
-						if (ents[i] == owner)
-							continue;
-						// make sure we haven't hit this object before
-						bool balreadyhit = false;
-						for (int j = 0; j < numobjectshit; j++)
-						{
-							if (hitobjects[j] == ents[i])
-								balreadyhit = true;
-						}
-						if (balreadyhit)
-							continue;
-
-						// check so we are sufficiently close
-						if (distance(ents[i]->pos, owner->pos) > (owner->phys_size * 2.0f))
-							continue;
-					
-						// hit a player, give him damage and stuffs...
-						// create sound for bash
-						create_sound(ents[i]->pos, sound_impact);
-
-						// set his velocity to fast upward (for now)
-						create_smoke(ents[i]->pos);
-						hitobjects[numobjectshit++] = ents[i];
-						ents[i]->take_damage(vec2(0,10.0f), damage, owner->client_id);
-						player* target = (player*)ents[i];
-						vec2 dir;
-						if (length(target->pos - owner->pos) > 0.0f)
-							dir = normalize(target->pos - owner->pos);
-						else
-							dir = vec2(0,-1);
-						target->vel += dir * 10.0f + vec2(0,-10.0f);
-					}
-					break;
-				}
-			default:
-				break;
-			}
-		}
-	};
-
-	class modifier
-	{
-	public:
-		vec2 activationdir;
-		entity* hitobjects[10];
-		int numobjectshit;
-		float velocity;
-		int modifiertype;
-		int duration;
-		int numactivations;
-		int activationtime;
-		int cooldown;
-		int movetime;
-		int visualtimeattack;
-		int currentactivation;
-		int currentmovetime;
-		int currentcooldown;
-		int damage;
-		int flags;
-		int sound_impact;
-		int sound_activate;
-
-		modifier()
-		{
-			modifiertype = 0;
-			duration = 0;
-			numobjectshit = 0;
-			numactivations = 0;
-			activationtime = 0;
-			cooldown = 0;
-			movetime = 0;
-			currentactivation = 0;
-			currentmovetime = 0;
-			currentcooldown =0;
-			damage = 0;
-			flags = 0;
-			activationdir = vec2(0.0f, 1.0f);
-			velocity = 0.0f;
-			visualtimeattack = 0;
-			sound_impact = -1;
-		}
-
-		void setninja()
-		{
-			modifiertype = MODIFIER_TYPE_NINJA;
-			duration = SERVER_TICK_SPEED * 15;
-			numactivations = -1;
-			movetime = SERVER_TICK_SPEED / 5;
-			activationtime = SERVER_TICK_SPEED / 2;
-			cooldown = SERVER_TICK_SPEED;
-			currentactivation = 0;
-			currentmovetime = 0;
-			numobjectshit = 0;
-			damage = 3;
-			flags = MODIFIER_TIMELIMITED | MODIFIER_NEEDSACTIVATION;
-			velocity = 50.0f;
-			visualtimeattack = 3;
-			sound_impact = SOUND_PLAYER_IMPACT_NINJA;
-			sound_activate = SOUND_FIRE_NINJA;
-		}
-
-		void settimefield()
-		{
-			modifiertype = MODIFIER_TYPE_TIMEFIELD;
-			duration = SERVER_TICK_SPEED * 10;
-			numactivations = -1;
-			activationtime = SERVER_TICK_SPEED;
-			numobjectshit = 0;
-			currentactivation = 0;
-			flags = MODIFIER_TIMELIMITED;
-			velocity = 0.0f;
-		}
-
-		void settype()
-		{
-			switch (modifiertype)
-			{
-			case MODIFIER_TYPE_NINJA:
-				{
-					setninja();
-					break;
-				}
-			case MODIFIER_TYPE_TIMEFIELD:
-				{
-					settimefield();
-					break;
-				}
-			default:
-				break;
-			}
-		}
-
-		int updateninja(player* player)
-		{
-			if (currentactivation <= 0)
-				return MODIFIER_RETURNFLAGS_OVERRIDEWEAPON;
-			currentactivation--;
-			currentmovetime--;
-			
-			if (currentmovetime == 0)
-			{	
-				// reset player velocity
-				player->vel *= 0.2f;
-				//return MODIFIER_RETURNFLAGS_OVERRIDEWEAPON;
-			}
-			
-			if (currentmovetime > 0)
-			{
-				// Set player velocity
-				player->vel = activationdir * velocity;
-				vec2 oldpos = player->pos;
-				move_box(&player->pos, &player->vel, vec2(player->phys_size, player->phys_size), 0.0f);
-				// reset velocity so the client doesn't predict stuff
-				player->vel = vec2(0.0f,0.0f);
-				if ((currentmovetime % 2) == 0)
-				{
-					create_smoke(player->pos);
-				}
-				
-				// check if we hit anything along the way
-				{
-					int type = OBJTYPE_PLAYER;
-					entity *ents[64];
-					vec2 dir = player->pos - oldpos;
-					float radius = length(dir * 0.5f);
-					vec2 center = oldpos + dir * 0.5f;
-					int num = world.find_entities(center, radius, ents, 64, &type, 1);
-					
-					for (int i = 0; i < num; i++)
-					{
-						// Check if entity is a player
-						if (ents[i] == player)
-							continue;
-						// make sure we haven't hit this object before
-						bool balreadyhit = false;
-						for (int j = 0; j < numobjectshit; j++)
-						{
-							if (hitobjects[j] == ents[i])
-								balreadyhit = true;
-						}
-						if (balreadyhit)
-							continue;
-
-						// check so we are sufficiently close
-						if (distance(ents[i]->pos, player->pos) > (player->phys_size * 2.0f))
-							continue;
-					
-						// hit a player, give him damage and stuffs...
-						create_sound(ents[i]->pos, sound_impact);
-						// set his velocity to fast upward (for now)
-						hitobjects[numobjectshit++] = ents[i];
-						ents[i]->take_damage(vec2(0,10.0f), damage, player->client_id);
-					}
-				}
-				return MODIFIER_RETURNFLAGS_OVERRIDEWEAPON | MODIFIER_RETURNFLAGS_OVERRIDEVELOCITY | MODIFIER_RETURNFLAGS_OVERRIDEPOSITION|MODIFIER_RETURNFLAGS_OVERRIDEGRAVITY;
-			}
-
-
-			// move char, and check intersection from us to target
-			return MODIFIER_RETURNFLAGS_OVERRIDEWEAPON | MODIFIER_RETURNFLAGS_OVERRIDEVELOCITY;
-		}
-
-		int activateninja(player* player)
-		{
-			// ok then, activate ninja
-			activationdir = player->direction;
-			currentactivation = activationtime;
-			currentmovetime = movetime;
-			currentcooldown = cooldown;
-			// reset hit objects
-			numobjectshit = 0;
-
-			create_sound(player->pos, SOUND_FIRE_NINJA);
-
-			return MODIFIER_RETURNFLAGS_OVERRIDEWEAPON;
-		}
-
-		int activate(player* player)
-		{
-			if (flags & MODIFIER_NEEDSACTIVATION)
-			{
-				if (!numactivations)
-					return 0;
-				numactivations--;
-			}
-
-			switch (modifiertype)
-			{
-			case MODIFIER_TYPE_NINJA:
-				{
-					return activateninja(player);
-				}
-			/*case MODIFIER_TYPE_TIMEFIELD:
-				{
-					updatetimefield();
-					break;
-				}*/
-			default:
-				return 0;
-			}
-		}
-		int update(player* player)
-		{
-			switch (modifiertype)
-			{
-			case MODIFIER_TYPE_NINJA:
-				{
-					return updateninja(player);
-				}
-			/*case MODIFIER_TYPE_TIMEFIELD:
-				{
-					updatetimefield();
-					break;
-				}*/
-			default:
-				return 0;
-			}
-		}
-	};
-
-	enum
+	
+	// weapon info
+	struct weaponstat
 	{
-		PLAYER_FLAGS_ISRELOADING = 1 << 0,
-		PLAYER_FLAGS_ISEQUIPPING = 1 << 1,
-	};
-
-	weapon lweapons[WEAPON_NUMWEAPONS];
-	modifier modifiers[MODIFIER_NUMMODIFIERS];
-	int iactiveweapon;
-	int inextweapon;
-	int equip_time;
+		bool got;
+		int ammo;
+	} weapons[NUM_WEAPONS];
+	int active_weapon;
+	int reload_timer;
+	int attack_tick;
+	
+	// we need a defered position so we can handle the physics correctly
+	vec2 defered_pos;
+	vec2 vel;
+	vec2 direction;
 
+	//
 	int client_id;
-	int flags;
-
 	char name[32];
+
+	// input	
 	player_input previnput;
 	player_input input;
-	int tick_count;
-	int damage_taken_tick;
-
-	vec2 vel;
-	vec2 direction;
-
 	int jumped;
-	int airjumped;
-
-	//int firing;
-	int hooking;
-
-	int fire_timeout;
-	int reload_timeout;
+	
+	int damage_taken_tick;
 
 	int health;
 	int armor;
 
 	int score;
 
-	// sounds
-	int sound_player_jump;
-	int sound_player_land;
-	int sound_player_hurt_short;
-	int sound_player_hurt_long;
-	int sound_player_spawn;
-	int sound_player_chain_loop;
-	int sound_player_chain_impact;
-	int sound_player_impact;
-	int sound_player_impact_ninja;
-	int sound_player_die;
-	int sound_player_switchweapon;
-
-	player* phookedplayer;
-	powerup* phookedpowerup;
-	int numhooked;
+	// hooking stuff
+	enum
+	{
+		HOOK_RETRACTED=-1,
+		HOOK_IDLE=0,
+		HOOK_FLYING,
+		HOOK_GRABBED
+	};
+	
+	int hook_state; 
+	player *hooked_player;
 	vec2 hook_pos;
 	vec2 hook_dir;
 
-	player() :
-		entity(OBJTYPE_PLAYER)
+	//
+	player()
+	: entity(OBJTYPE_PLAYER)
 	{
 		reset();
-		
-		//firing = 0;
-		// setup weaponflags and stuff
-		lweapons[WEAPON_TYPE_GUN].setgun();
-		lweapons[WEAPON_TYPE_ROCKET].setrocket();
-		//lweapons[WEAPON_TYPE_SNIPER].setsniper();
-		lweapons[WEAPON_TYPE_SHOTGUN].setshotgun();
-		lweapons[WEAPON_TYPE_MELEE].setmelee();
-
-		modifiers[MODIFIER_TYPE_NINJA].setninja();
-		modifiers[MODIFIER_TYPE_TIMEFIELD].settimefield();
-		//modifiers[MODIFIER_TYPE_NINJA].flags |= MODIFIER_ISACTIVE;
-
-		sound_player_jump = SOUND_PLAYER_JUMP;
-		sound_player_hurt_short = SOUND_PLAYER_HURT_SHORT;
-		sound_player_hurt_long = SOUND_PLAYER_HURT_LONG;
-		sound_player_spawn = SOUND_PLAYER_SPAWN;
-		sound_player_chain_loop = SOUND_PLAYER_CHAIN_LOOP;
-		sound_player_chain_impact = SOUND_PLAYER_CHAIN_IMPACT;
-		sound_player_impact = SOUND_PLAYER_IMPACT;
-		sound_player_impact_ninja = SOUND_PLAYER_IMPACT_NINJA;
-		sound_player_die = SOUND_PLAYER_DIE;
-		sound_player_switchweapon = SOUND_PLAYER_SWITCHWEAPON;
-		sound_player_land = SOUND_PLAYER_LAND;
 	}
 	
 	void reset()
 	{
-		equip_time = 0;
-		phookedplayer = 0;
-		numhooked = 0;
+		//equip_time = 0;
+		
+		release_hooked();
+		release_hooks();
+		
 		proximity_radius = phys_size;
 		name[0] = 'n';
 		name[1] = 'o';
@@ -1065,32 +495,21 @@ public:
 		vel = vec2(0.0f, 0.0f);
 		direction = vec2(0.0f, 1.0f);
 		client_id = -1;
-		tick_count = 0;
 		score = 0;
-		flags = 0;		
 	}
 	
-	virtual void destroy() { flags = 0; }
+	virtual void destroy() {  }
 		
 	void respawn()
 	{
 		health = PLAYER_MAXHEALTH;
 		armor = 0;
-		
-		hooking = 0;
-		phookedplayer = 0;
-		phookedpowerup = 0;
-		numhooked = 0;
-		fire_timeout = 0;
-		reload_timeout = 0;
-		iactiveweapon = 0;
-		inextweapon = -1;
-		equip_time = 0;
 		jumped = 0;
-		airjumped = 0;
+		
 		mem_zero(&input, sizeof(input));
 		vel = vec2(0.0f, 0.0f);
 		
+		// get spawn point
 		int start, num;
 		map_get_type(1, &start, &num);
 		
@@ -1101,30 +520,17 @@ public:
 		}
 		else
 			pos = vec2(100.0f, -60.0f);
-
-		// reset active flags
-		for (int i = 0; i < WEAPON_NUMWEAPONS; i++)
-		{
-			// reset and remove
-			lweapons[i].settype();
-			lweapons[i].flags &= ~WEAPON_ISACTIVE;
-		}
-
-
-		// TEMP REMOVE
+			
+		// init weapons
+		mem_zero(&weapons, sizeof(weapons));
+		weapons[WEAPON_HAMMER].got = true;
+		weapons[WEAPON_HAMMER].ammo = -1;
+		weapons[WEAPON_GUN].got = true;
+		weapons[WEAPON_GUN].ammo = 10;
+		active_weapon = WEAPON_GUN;
+		reload_timer = 0;
 		
-		/*for (int i = 0; i < WEAPON_NUMWEAPONS; i++)
-		{
-			lweapons[i].settype();
-			lweapons[i].flags |= WEAPON_ISACTIVE;
-		}*/
-		lweapons[WEAPON_TYPE_MELEE].flags |= WEAPON_ISACTIVE;
-		// Add gun as default weapon
-		iactiveweapon = WEAPON_TYPE_GUN;
-		lweapons[WEAPON_TYPE_GUN].numammo = 10;
-		lweapons[WEAPON_TYPE_GUN].flags |= WEAPON_ISACTIVE;
-
-		create_sound(pos, sound_player_spawn);
+		create_sound(pos, SOUND_PLAYER_SPAWN);
 	}
 	
 	bool is_grounded()
@@ -1136,470 +542,261 @@ public:
 		return false;
 	}
 
-	// Disable weapon activation if this returns true
-	int handlemodifiers()
+	// releases the hooked player
+	void release_hooked()
 	{
-		int returnflags = 0;
-		for (int i = 0; i < MODIFIER_NUMMODIFIERS; i++)
-		{
-			if (modifiers[i].flags & MODIFIER_ISACTIVE)
-			{
-				modifiers[i].duration--;
-				modifiers[i].currentcooldown--;
-
-				// Check if it should activate
-				if (modifiers[i].currentcooldown <= 0 && 
-					(modifiers[i].flags & MODIFIER_NEEDSACTIVATION) &&
-					input.fire && !(previnput.fire))
-				{
-					returnflags |= modifiers[i].activate(this);
-				}
-
-				returnflags |= modifiers[i].update(this);
-
-				// remove active if timed out
-				if (modifiers[i].duration <= 0 && modifiers[i].currentactivation <= 0)
-					modifiers[i].flags &= ~MODIFIER_ISACTIVE;
-			}
-		}
-		return returnflags;
+		hook_state = HOOK_IDLE;
+		hooked_player = 0x0;
 	}
 
-	void handleweapon()
+	// release all hooks to this player	
+	void release_hooks()
 	{
-		// handle weapon
-		if(input.fire && (!previnput.fire || lweapons[iactiveweapon].flags & WEAPON_AUTOFIRE) && 
-			!(flags & PLAYER_FLAGS_ISEQUIPPING) && !reload_timeout)
+		// TODO: loop thru players only
+		for(entity *ent = world.first_entity; ent; ent = ent->next_entity)
 		{
-			if(fire_timeout == 0)
+			if(ent && ent->objtype == OBJTYPE_PLAYER)
 			{
-				if (lweapons[iactiveweapon].numammo || !(lweapons[iactiveweapon].flags & WEAPON_DRAWSAMMO))
-				{
-					// Decrease ammo
-					fire_timeout = lweapons[iactiveweapon].activate(this);
-				}
-				else if ((lweapons[iactiveweapon].flags & WEAPON_NEEDRELOAD) && lweapons[iactiveweapon].nummagazines)
-				{
-					// reload
-					reload_timeout = lweapons[iactiveweapon].reloadtime;
-					lweapons[iactiveweapon].nummagazines--;
-					lweapons[iactiveweapon].numammo = lweapons[iactiveweapon].magsize;
-				}
+				player *p = (player*)ent;
+				if(p->hooked_player == this)
+					p->release_hooked();
 			}
 		}
-
-		// update active weapon
-		lweapons[iactiveweapon].update(this, fire_timeout);
 	}
-
-	void handlehook()
+	
+	void handle_weapons()
 	{
-		// handle hook
-		if(input.hook)
+		// check reload timer
+		if(reload_timer)
 		{
-			if(hooking == 0)
-			{
-				hooking = 1;
-				hook_pos = pos;
-				hook_dir = direction;
-				// Sound
-				create_sound(pos, sound_player_chain_loop, SOUND_LOOPFLAG_STARTLOOP);
-			}
-			else if(hooking == 1)
-			{
-				vec2 new_pos = hook_pos+hook_dir*hook_fire_speed;
-
-				// Check against other players and powerups first
-				player* targetplayer = 0;
-				powerup* targetpowerup = 0;
-				{
-					static const int typelist[2] = { OBJTYPE_PLAYER, OBJTYPE_POWERUP};
-					entity *ents[64];
-					vec2 dir = new_pos - hook_pos;
-					float radius = length(dir * 0.5f);
-					vec2 center = hook_pos + dir * 0.5f;
-					int num = world.find_entities(center, radius, ents, 64,typelist,2);
-					
-					for (int i = 0; i < num; i++)
-					{
-						// Check if entity is a player
-						if (ents[i] == this || (targetplayer && targetpowerup))
-							continue;
-
-						if (!targetplayer && ents[i]->objtype == OBJTYPE_PLAYER)
-						{
-							// temp, set hook pos to our position
-							if (((player*)ents[i])->phookedplayer != this)
-								targetplayer = (player*)ents[i];
-						}
-						else if (!targetpowerup && ents[i]->objtype == OBJTYPE_POWERUP && 
-							(((powerup*)ents[i])->flags & powerup::POWERUP_FLAG_HOOKABLE))
-						{
-							targetpowerup = (powerup*)ents[i];
-						}
-					}
-				}
+			reload_timer--;
+			return;
+		}
 
-				//player* targetplayer = intersect_player(hook_pos, hook_pos, new_pos, this);
-				if (targetplayer)
-				{
-					// So he can't move "normally"
-					new_pos = targetplayer->pos;
-					phookedplayer = targetplayer;
-					targetplayer->numhooked++;
-					hooking = 3;
-					create_sound(pos, sound_player_chain_impact);
-
-					// stop looping chain sound
-					create_sound(pos, sound_player_chain_loop, SOUND_LOOPFLAG_STOPLOOP);
-				}
-				else if (targetpowerup)
-				{
-					new_pos = targetpowerup->pos;
-					phookedpowerup = targetpowerup;
-					phookedpowerup->playerhooked = this;
-					hooking = 4;
-					create_sound(pos, sound_player_chain_impact);
-
-					// stop looping chain sound
-					create_sound(pos, sound_player_chain_loop, SOUND_LOOPFLAG_STOPLOOP);
-				}
-				else if(intersect_line(hook_pos, new_pos, &new_pos))
-				{
-					hooking = 2;
-					create_sound(pos, sound_player_chain_impact);
+		// switch weapon if wanted		
+		if(input.activeweapon >= 0 && input.activeweapon < NUM_WEAPONS && weapons[input.activeweapon].got)
+			active_weapon = input.activeweapon;
 
-					// stop looping chain sound
-					create_sound(pos, sound_player_chain_loop, SOUND_LOOPFLAG_STOPLOOP);
-				}
-				else if(distance(pos, new_pos) > hook_length)
-				{
-					hooking = -1;
-					create_sound(pos, sound_player_chain_loop, SOUND_LOOPFLAG_STOPLOOP);
-				}
-				
-				hook_pos = new_pos;
-			}
-			else if(hooking == 2)
-			{
-				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
-			}
-			else if (hooking == 3)
+		if(input.fire)
+		{
+			if(reload_timer == 0)
 			{
-				// hmm, force the targetplayer towards us if possible, otherwise us towards them if they are already hooked
-				if (phookedplayer)
+				// fire!
+				if(weapons[active_weapon].ammo)
 				{
-					if (phookedplayer->hooking > 1)
+					switch(active_weapon)
 					{
-						// Drag us towards target player
-						vec2 hookvel = normalize(hook_pos-pos)*hook_drag_accel;
-						if((hookvel.x < 0 && input.left) || (hookvel.x > 0 && input.right)) 
-							hookvel.x *= 0.95f;
-						else
-							hookvel.x *= 0.75f;
-
-						// Apply the velocity
-						// the hook will boost it's power if the player wants to move
-						// in that direction. otherwise it will dampen everything abit
-						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
-					}
-					else
-					{
-						// Drag targetplayer towards us
-						vec2 hookvel = normalize(pos-hook_pos)*hook_drag_accel;
-
-						// Apply the velocity
-						// the hook will boost it's power if the player wants to move
-						// in that direction. otherwise it will dampen everything abit
-						vec2 new_vel = phookedplayer->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))
-							phookedplayer->vel = new_vel; // no problem. apply
+						case WEAPON_HAMMER:
+							break;
+
+						case WEAPON_GUN:
+							new projectile(WEAPON_PROJECTILETYPE_GUN,
+								client_id,
+								pos+vec2(0,0),
+								direction*30.0f,
+								100,
+								this,
+								1, 0, 0, -1);
+							break;
+						case WEAPON_ROCKET:
+							new projectile(WEAPON_PROJECTILETYPE_ROCKET,
+								client_id,
+								pos+vec2(0,0),
+								direction*15.0f,
+								100,
+								this,
+								1, projectile::PROJECTILE_FLAGS_EXPLODE, 0, -1);						
+							break;
+						case WEAPON_SHOTGUN:
+							for(int i = 0; i < 3; i++)
+							{
+								new projectile(WEAPON_PROJECTILETYPE_SHOTGUN,
+									client_id,
+									pos+vec2(0,0),
+									direction*(20.0f+(i+1)*2.0f),
+									100,
+									this,
+									1, 0, 0, -1);
+							}
+							break;
 					}
-					hook_pos = phookedplayer->pos;
-					// if hooked player dies, release the hook
+					
+					weapons[active_weapon].ammo--;
 				}
 				else
 				{
-					hooking = -1;
-					phookedplayer = 0;
+					// click!!! click
 				}
-			}
-			else if (hooking == 4)
-			{
-				// Have a powerup, drag it towards us
-				vec2 hookvel = normalize(pos-hook_pos)*hook_drag_accel;
-
-				// Apply the velocity
-				// the hook will boost it's power if the player wants to move
-				// in that direction. otherwise it will dampen everything abit
-				vec2 new_vel = phookedpowerup->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))
-					phookedpowerup->vel = new_vel; // no problem. apply
-				hook_pos = phookedpowerup->pos;
-			}
-		}
-		else
-		{
-			hooking = 0;
-			hook_pos = pos;
-			if (phookedplayer)
-			{
-				phookedplayer->numhooked--;
-				phookedplayer = 0;
+				attack_tick = server_tick();
+				reload_timer = 10; // make this variable depending on weapon
 			}
 		}
 	}
-
-	void getattackticks(int& curattack, int& attacklen, int& visualtimeattack)
-	{
-		// time left from current attack (if any)
-		// first check modifiers (ninja...)
-		for (int i = 0; i < MODIFIER_NUMMODIFIERS; i++)
-		{
-			if ((modifiers[i].flags & (MODIFIER_ISACTIVE | MODIFIER_NEEDSACTIVATION)) == (MODIFIER_ISACTIVE | MODIFIER_NEEDSACTIVATION))
-			{
-				curattack = modifiers[i].currentactivation;
-				attacklen = modifiers[i].activationtime;
-				visualtimeattack = modifiers[i].visualtimeattack;
-				return;
-			}
-		}
-
-		// otherwise current fire timeout
-		curattack = fire_timeout;
-		attacklen = lweapons[iactiveweapon].firetime;
-		visualtimeattack = lweapons[iactiveweapon].visualtimeattack;
-	}
 	
 	virtual void tick()
 	{
-		tick_count++;
+		// TODO: rework the input to be more robust
+		// TODO: remove this tick count, it feels weird
 		
 		// fetch some info
 		bool grounded = is_grounded();
 		direction = get_direction(input.angle);
 		
-		// decrease reload timer
-		if(fire_timeout)
-			fire_timeout--;
-		if (reload_timeout)
-			reload_timeout--;
-
-		// Switch weapons
-		if (flags & PLAYER_FLAGS_ISEQUIPPING)
+		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;
+		
+		// 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)
 		{
-			equip_time--;
-			if (equip_time <= 0)
+			if(!jumped && grounded)
 			{
-				if (inextweapon >= 0)
-				{
-					equip_time = SERVER_TICK_SPEED * lweapons[inextweapon].equiptime;
-					iactiveweapon = inextweapon;
-					inextweapon = -1;
-
-					// Send switch weapon event to client?
-				}
-				else
-				{
-					flags &= ~PLAYER_FLAGS_ISEQUIPPING;
-				}
+				create_sound(pos, SOUND_PLAYER_JUMP);
+				vel.y = -ground_jump_speed;
+				jumped++;
 			}
 		}
-		else if (input.activeweapon && (unsigned int)iactiveweapon != (input.activeweapon & ~0x80000000))
+		else
+			jumped = 0;
+			
+		// do hook
+		if(input.hook)
 		{
-			input.activeweapon &= ~0x80000000;
-			// check which weapon to activate
-			if (input.activeweapon >= 0 && input.activeweapon < WEAPON_NUMWEAPONS && 
-				(lweapons[input.activeweapon].flags & WEAPON_ISACTIVE))
+			if(hook_state == HOOK_IDLE)
 			{
-				inextweapon = input.activeweapon;
-				equip_time = SERVER_TICK_SPEED * lweapons[iactiveweapon].unequiptime;
-				// unequip current
-				flags |= PLAYER_FLAGS_ISEQUIPPING;
-
-				create_sound(pos, sound_player_switchweapon);
+				hook_state = HOOK_FLYING;
+				hook_pos = pos;
+				hook_dir = direction;
 			}
-		}
-
-		// don't do any weapon activations if modifier is currently overriding
-		int modifierflags = handlemodifiers();
-		if (!(modifierflags & MODIFIER_RETURNFLAGS_OVERRIDEWEAPON))
-			handleweapon();
-
-		handlehook();
-		
-		// handle movement
-		if(grounded)
-		{
-			if (airjumped)
-				create_sound(pos, SOUND_PLAYER_LAND);
-			airjumped = 0;
-		}
-		
-		float elast = 0.0f;
-
-		if (!numhooked)
-		{
-			// I'm hooked by someone, so don't do any movement plz (temp)
-			if (!(modifierflags & MODIFIER_RETURNFLAGS_OVERRIDEVELOCITY))
+			else if(hook_state == HOOK_FLYING)
 			{
-				if(grounded)
+				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)
 				{
-					// ground movement
-					if(input.left)
-					{
-						if(vel.x > -ground_control_speed)
-						{
-							vel.x -= ground_control_accel; 
-							if(vel.x < -ground_control_speed)
-								vel.x = -ground_control_speed;
-						}
-					}
-					else if(input.right)
+					if(ent && ent->objtype == OBJTYPE_PLAYER)
 					{
-						if(vel.x < ground_control_speed)
+						player *p = (player*)ent;
+						if(p != this && distance(p->pos, new_pos) < p->phys_size)
 						{
-							vel.x += ground_control_accel;
-							if(vel.x > ground_control_speed)
-								vel.x = ground_control_speed;
+							hook_state = HOOK_GRABBED;
+							hooked_player = p;
+							break;
 						}
 					}
-					else
-						vel.x *= ground_friction; // ground fiction
 				}
-				else
+				
+				if(hook_state == HOOK_FLYING)
 				{
-					// air movement
-					if(input.left)
+					// check against ground
+					if(col_intersect_line(hook_pos, new_pos, &new_pos))
 					{
-						if(vel.x > -air_control_speed)
-							vel.x -= air_control_accel;
+						hook_state = HOOK_GRABBED;
+						hook_pos = new_pos;	
 					}
-					else if(input.right)
+					else if(distance(pos, new_pos) > hook_length)
 					{
-						if(vel.x < air_control_speed)
-							vel.x += air_control_accel;
+						hook_state = HOOK_RETRACTED;
 					}
 					else
-						vel.x *= air_friction; // air fiction
+						hook_pos = new_pos;
 				}
 				
-				if(input.jump)
+				if(hook_state == HOOK_GRABBED)
+					create_sound(pos, SOUND_HOOK_ATTACH);
+			}
+		}
+		else
+		{
+			release_hooked();
+			hook_pos = pos;
+		}
+			
+		if(hook_state == HOOK_GRABBED)
+		{
+			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);
+			}
+		}
+			
+		// 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)
+					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)
 				{
-					if(jumped == 0)
-					{
-						if(grounded)
-						{
-							create_sound(pos, sound_player_jump);
-							vel.y = -ground_jump_speed;
-							jumped++;
-						}
-						/*
-						else if(airjumped == 0)
-						{
-							vel.y = -12;
-							airjumped++;
-							jumped++;
-						}*/
-					}
-					else if (!grounded)
-					{
-						airjumped++;
-					}
+					float a = phys_size*1.25f - d;
+					vel = vel + dir*a;
 				}
-				else
-					jumped = 0;
-			}
 				
-			// meh, go through all players and stop their hook on me
-			/*
-			for(entity *ent = world.first_entity; ent; ent = ent->next_entity)
-			{
-				if (ent && ent->objtype == OBJTYPE_PLAYER)
+				// handle hook influence
+				if(p->hooked_player == this)
 				{
-					player *p = (player*)ent;
-					if(p != this)
+					if(d > phys_size*1.50f) // TODO: fix tweakable variable
 					{
-						float d = distance(pos, p->pos);
-						vec2 dir = normalize(pos - p->pos);
-						if(d < phys_size*1.5f)
-						{
-							float a = phys_size*1.5f - d;
-							vel = vel + dir*a;
-						}
-						
-						if(p->phookedplayer == this)
-						{
-							if(d > phys_size*2.5f)
-							{
-								elast = 0.0f;
-								vel = vel - dir*2.5f;
-							}
-						}
+						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);
 					}
 				}
-			}*/
-			
-			// gravity
-			if (!(modifierflags & MODIFIER_RETURNFLAGS_OVERRIDEGRAVITY))
-				vel.y += gravity;
+			}
 		}
 		
-		if (!(modifierflags & MODIFIER_RETURNFLAGS_OVERRIDEPOSITION))
-			move_box(&pos, &vel, vec2(phys_size, phys_size), elast);
+		// handle weapons
+		handle_weapons();
+		
+		// add gravity
+		vel.y += gravity;
+		
+		// do the move
+		defered_pos = pos;
+		move_box(&defered_pos, &vel, vec2(phys_size, phys_size), 0);
+		return;
+	}
+	
+	virtual void tick_defered()
+	{
+		// apply the new position
+		pos = defered_pos;
 	}
 	
 	void die()
 	{
-		create_sound(pos, sound_player_die);
-		// release our hooked player
-		if (phookedplayer)
-		{
-			phookedplayer->numhooked--;
-			phookedplayer = 0;
-			hooking = -1;
-			numhooked = 0;
-		}
+		create_sound(pos, SOUND_PLAYER_DIE);
+		
+		release_hooked();
+		release_hooks();
+		
+		// TODO: insert timer here
 		respawn();
-
-		// meh, go through all players and stop their hook on me
-		for(entity *ent = world.first_entity; ent; ent = ent->next_entity)
-		{
-			if (ent && ent->objtype == OBJTYPE_PLAYER)
-			{
-				player* p = (player*)ent;
-				if (p->phookedplayer == this)
-				{
-					p->phookedplayer = 0;
-					p->hooking = -1;
-					//p->numhooked--;
-				}
-			}
-		}
 	}
 	
 	virtual bool take_damage(vec2 force, int dmg, int from)
@@ -1620,24 +817,11 @@ public:
 		}
 		else
 			armor -= dmg;
-		/*
-		int armordmg = (dmg+1)/2;
-		int healthdmg = dmg-armordmg;
-		if(armor < armordmg)
-		{
-			healthdmg += armordmg - armor;
-			armor = 0;
-		}
-		else
-			armor -= armordmg;
-			
-		health -= healthdmg;
-		*/
 		
 		// create healthmod indicator
 		create_healthmod(pos, dmg);
 		
-		damage_taken_tick = tick_count+50;
+		damage_taken_tick = server_tick()+50;
 		
 		// check for death
 		if(health <= 0)
@@ -1659,12 +843,11 @@ public:
 		}
 
 		if (dmg > 2)
-			create_sound(pos, sound_player_hurt_long);
+			create_sound(pos, SOUND_PLAYER_PAIN_LONG);
 		else
-			create_sound(pos, sound_player_hurt_short);
+			create_sound(pos, SOUND_PLAYER_PAIN_SHORT);
 
 		// spawn blood?
-
 		return true;
 	}
 
@@ -1672,33 +855,20 @@ public:
 	{
 		obj_player *player = (obj_player *)snap_new_item(OBJTYPE_PLAYER, client_id, sizeof(obj_player));
 
-		client_info info;
-		if(server_getclientinfo(client_id, &info))
-			snap_encode_string(info.name, player->name, strlen(info.name), 32);
-		
 		player->x = (int)pos.x;
 		player->y = (int)pos.y;
 		player->vx = (int)vel.x;
 		player->vy = (int)vel.y;
 		player->emote = EMOTE_NORMAL;
 
-		player->ammocount = lweapons[iactiveweapon].numammo;
+		player->ammocount = weapons[active_weapon].ammo;
 		player->health = 0;
 		player->armor = 0;
 		player->local = 0;
 		player->clientid = client_id;
-		player->weapon = iactiveweapon;
-		player->modifier = 0;
-		for (int i = 0; i < MODIFIER_NUMMODIFIERS; i++)
-		{
-			// add active modifiers
-			if (modifiers[i].flags & MODIFIER_ISACTIVE)
-				player->modifier |= 1 << i;
-		}
-		// get current attack ticks
-		getattackticks(player->attackticks, player->attacklen, player->visualtimeattack);
+		player->weapon = active_weapon;
+		player->attacktick = attack_tick;
 		
-
 		if(client_id == snaping_client)
 		{
 			player->local = 1;
@@ -1709,16 +879,16 @@ public:
 		if(length(vel) > 15.0f)
 			player->emote = EMOTE_HAPPY;
 		
-		if(damage_taken_tick > tick_count)
+		if(damage_taken_tick > server_tick())
 			player->emote = EMOTE_PAIN;
 		
 		if(player->emote == EMOTE_NORMAL)
 		{
-			if((tick_count%(50*5)) < 10)
+			if((server_tick()%(50*5)) < 10)
 				player->emote = EMOTE_BLINK;
 		}
 		
-		player->hook_active = hooking>0?1:0;
+		player->hook_active = hook_state>0?1:0;
 		player->hook_x = (int)hook_pos.x;
 		player->hook_y = (int)hook_pos.y;
 			
@@ -1729,64 +899,18 @@ public:
 
 // POWERUP ///////////////////////
 
-powerup::powerup(int _type, int _subtype, int _numitems, int _flags) : 
-	entity(OBJTYPE_POWERUP)
+powerup::powerup(int _type, int _subtype)
+: entity(OBJTYPE_POWERUP)
 {
-	static int current_id = 0;
-	playerhooked = 0;
-	id = current_id++;
-	vel = vec2(0.0f,0.0f);
+	//static int current_id = 0;
 	type = _type;
 	subtype = _subtype;
-	numitems = _numitems;
-	flags = _flags;
 	// set radius (so it can collide and be hooked and stuff)
 	proximity_radius = phys_size;
 	spawntick = -1;
-	world.insert_entity(this);
-}
-
-void powerup::spawnrandom(int _lifespan)
-{
-	return;
-	/*
-	vec2 pos;
-	int start, num;
-	map_get_type(1, &start, &num);
 	
-	if(!num)
-		return;
-	
-	mapres_spawnpoint *sp = (mapres_spawnpoint*)map_get_item(start + (rand()%num), NULL, NULL);
-	pos = vec2(sp->x, sp->y);
-
-	// Check if there already is a powerup at that location
-	{
-		int type = OBJTYPE_POWERUP;
-		entity *ents[64];
-		int num = world.find_entities(pos, 5.0f, ents, 64,&type,1);
-		for (int i = 0; i < num; i++)
-		{
-			if (ents[i]->objtype == OBJTYPE_POWERUP)
-			{
-				// location busy
-				return;
-			}
-		}
-	}
-
-	powerup* ppower = new powerup(rand() % POWERUP_TYPE_NUMPOWERUPS,_lifespan);
-	ppower->pos = pos;
-	if (ppower->type == POWERUP_TYPE_WEAPON)
-	{
-		ppower->subtype = rand() % WEAPON_NUMWEAPONS;
-		ppower->numitems = 10;
-	}
-	else if (ppower->type == POWERUP_TYPE_HEALTH || ppower->type == POWERUP_TYPE_ARMOR)
-	{
-		ppower->numitems = rand() % 5;
-	}
-	ppower->flags |= POWERUP_FLAG_HOOKABLE;*/
+	// TODO: should this be done here?
+	world.insert_entity(this);
 }
 
 void powerup::tick()
@@ -1799,84 +923,47 @@ void powerup::tick()
 		else
 			return;
 	}
-	
-	vec2 oldpos = pos;
-	//vel.y += 0.25f;
-	pos += vel;
-	move_box(&pos, &vel, vec2(phys_size, phys_size), 0.0f);
-
 	// Check if a player intersected us
 	vec2 meh;
 	player* pplayer = intersect_player(pos, pos + vec2(0,16), meh, 0);
 	if (pplayer)
 	{
 		// player picked us up, is someone was hooking us, let them go
-		if (playerhooked)
-			playerhooked->hooking = -1;
 		int respawntime = -1;
 		switch (type)
 		{
 		case POWERUP_TYPE_HEALTH:
+			if(pplayer->health < PLAYER_MAXHEALTH)
 			{
-				if(pplayer->health < PLAYER_MAXHEALTH)
-				{
-					pplayer->health = min(PLAYER_MAXHEALTH, pplayer->health + numitems);
-					respawntime = 20;
-				}
-				break;
+				pplayer->health = min((int)PLAYER_MAXHEALTH, pplayer->health + 1);
+				respawntime = 20;
 			}
+			break;
 		case POWERUP_TYPE_ARMOR:
+			if(pplayer->armor < PLAYER_MAXARMOR)
 			{
-				if(pplayer->armor < PLAYER_MAXARMOR)
-				{
-					pplayer->armor = min(PLAYER_MAXARMOR, pplayer->armor + numitems);
-					respawntime = 20;
-				}
-				break;
+				pplayer->armor = min((int)PLAYER_MAXARMOR, pplayer->armor + 1);
+				respawntime = 20;
 			}
+			break;
+				
 		case POWERUP_TYPE_WEAPON:
+			if(subtype >= 0 && subtype < NUM_WEAPONS)
 			{
-				if (pplayer->lweapons[subtype].flags & player::WEAPON_ISACTIVE)
-				{
-					// add ammo
-					/*
-					if (pplayer->lweapons[subtype].flags & player::WEAPON_NEEDRELOAD)
-					{
-						int numtoadd = min(numitems, pplayer->lweapons[subtype].magsize);
-						pplayer->lweapons[subtype].numammo = min(10, pplayer->lweapons[subtype].numammo + numtoadd);
-						pplayer->lweapons[subtype].nummagazines += (numitems - numtoadd) % pplayer->lweapons[subtype].magsize;
-					}
-					else*/
-					if(pplayer->lweapons[subtype].numammo < 10)
-					{
-						respawntime = 20;
-						pplayer->lweapons[subtype].numammo = min(10, pplayer->lweapons[subtype].numammo + numitems);
-					}
-				}
-				else
+				if(pplayer->weapons[subtype].ammo < 10 || !pplayer->weapons[subtype].got)
 				{
-					pplayer->lweapons[subtype].settype();
-					pplayer->lweapons[subtype].flags |= player::WEAPON_ISACTIVE;
+					pplayer->weapons[subtype].got = true;
+					pplayer->weapons[subtype].ammo = min(10, pplayer->weapons[subtype].ammo + 5);
 					respawntime = 20;
 				}
-				break;
 			}
-		case POWERUP_TYPE_NINJA:
-			{
-				respawntime = 60;
-				// reset and activate
-				pplayer->modifiers[MODIFIER_TYPE_NINJA].settype();
-				pplayer->modifiers[MODIFIER_TYPE_NINJA].flags |= player::MODIFIER_ISACTIVE;
-				break;
-			}
-		//POWERUP_TYPE_TIMEFIELD		= 4,
+			break;
 		default:
 			break;
 		};
 		
 		if(respawntime >= 0)
 			spawntick = server_tick() + server_tickspeed() * respawntime;
-		//world.destroy_entity(this);
 	}
 }
 
@@ -1885,18 +972,17 @@ void powerup::snap(int snapping_client)
 	if(spawntick != -1)
 		return;
 
-	obj_powerup *powerup = (obj_powerup *)snap_new_item(OBJTYPE_POWERUP, id, sizeof(obj_powerup));
-	powerup->x = (int)pos.x;
-	powerup->y = (int)pos.y;
-	powerup->vx = (int)vel.x;
-	powerup->vy = (int)vel.y;
-	powerup->type = type;
-	powerup->subtype = subtype;
+	obj_powerup *up = (obj_powerup *)snap_new_item(OBJTYPE_POWERUP, id, sizeof(obj_powerup));
+	up->x = (int)pos.x;
+	up->y = (int)pos.y;
+	up->type = type; // TODO: two diffrent types? what gives?
+	up->subtype = subtype;
 }
 
 // POWERUP END ///////////////////////
 
-static player players[MAX_CLIENTS];
+static const int NUM_BOTS = 1;
+static player players[MAX_CLIENTS+NUM_BOTS];
 
 player *get_player(int index)
 {
@@ -1933,19 +1019,7 @@ void create_explosion(vec2 p, int owner, bool bnodamage)
 			float l = length(diff);
 			float dmg = 5 * (1 - (l/radius));
 			if((int)dmg)
-			{
-				ents[i]->take_damage(forcedir*dmg*2, (int)dmg, owner);/* && 
-						ents[i]->objtype == OBJTYPE_PLAYER &&
-						owner >= 0)
-				{
-					player *p = (player*)ents[i];
-					if(p->client_id == owner)
-						p->score--;
-					else
-						((player*)ents[owner])->score++;
-
-				}*/
-			}
+				ents[i]->take_damage(forcedir*dmg*2, (int)dmg, owner);
 		}
 	}
 }
@@ -1970,6 +1044,7 @@ void create_sound(vec2 pos, int sound, int loopingflags)
 	ev->sound = sound | loopingflags;
 }
 
+// TODO: should be more general
 player* intersect_player(vec2 pos0, vec2 pos1, vec2& new_pos, entity* notthis)
 {
 	// Find other players
@@ -1993,19 +1068,26 @@ player* intersect_player(vec2 pos0, vec2 pos1, vec2& new_pos, entity* notthis)
 }
 
 // Server hooks
-static int addtick = SERVER_TICK_SPEED * 5;
 void mods_tick()
 {
 	// clear all events
 	events.clear();
 	world.tick();
 
-	if (addtick <= 0)
+	if(debug_bots)
 	{
-		powerup::spawnrandom(SERVER_TICK_SPEED * 5);
-		addtick = SERVER_TICK_SPEED * 5;
+		static int count = 0;
+		if(count >= 0)
+		{
+			count++;
+			if(count == 10)
+			{
+				for(int i = 0; i < NUM_BOTS; i++)
+					mods_client_enter(MAX_CLIENTS+i);
+				count = -1;
+			}
+		}
 	}
-	addtick--;
 }
 
 void mods_snap(int client_id)
@@ -2026,7 +1108,6 @@ void mods_client_enter(int client_id)
 	players[client_id].client_id = client_id;
 	players[client_id].respawn();
 	world.insert_entity(&players[client_id]);
-	
 }
 
 void mods_client_drop(int client_id)
@@ -2042,13 +1123,13 @@ void mods_init()
 	int start, num;
 	map_get_type(MAPRES_ITEM, &start, &num);
 	
+	// TODO: this is way more complicated then it should be
 	for(int i = 0; i < num; i++)
 	{
 		mapres_item *it = (mapres_item *)map_get_item(start+i, 0, 0);
 		
 		int type = -1;
 		int subtype = -1;
-		int numitems = 1;
 		
 		switch(it->type)
 		{
@@ -2059,17 +1140,11 @@ void mods_init()
 		case ITEM_WEAPON_SHOTGUN:
 			type = POWERUP_TYPE_WEAPON;
 			subtype = WEAPON_TYPE_SHOTGUN;
-			numitems = 5;
 			break;
 		case ITEM_WEAPON_ROCKET:
 			type = POWERUP_TYPE_WEAPON;
 			subtype = WEAPON_TYPE_ROCKET;
-			numitems = 5;
 			break;
-		/*case ITEM_WEAPON_SNIPER:
-			type = POWERUP_TYPE_WEAPON;
-			subtype = WEAPON_TYPE_ROCKET;
-			break;*/
 		case ITEM_WEAPON_HAMMER:
 			type = POWERUP_TYPE_WEAPON;
 			subtype = WEAPON_TYPE_MELEE;
@@ -2077,53 +1152,16 @@ void mods_init()
 		
 		case ITEM_HEALTH_1:
 			type = POWERUP_TYPE_HEALTH;
-			numitems = 1;
-			break;
-		case ITEM_HEALTH_5:
-			type = POWERUP_TYPE_HEALTH;
-			numitems = 5;
-			break;
-		case ITEM_HEALTH_10:
-			type = POWERUP_TYPE_HEALTH;
-			numitems = 10;
 			break;
 		
 		case ITEM_ARMOR_1:
 			type = POWERUP_TYPE_ARMOR;
-			numitems = 1;
-			break;
-		case ITEM_ARMOR_5:
-			type = POWERUP_TYPE_ARMOR;
-			numitems = 5;
-			break;
-		case ITEM_ARMOR_10:
-			type = POWERUP_TYPE_ARMOR;
-			numitems = 10;
 			break;
 		};
 		
-		powerup* ppower = new powerup(type, subtype, numitems);
-		ppower->pos.x = it->x;
-		ppower->pos.y = it->y;
+		powerup *ppower = new powerup(type, subtype);
+		ppower->pos = vec2(it->x, it->y);
 	}
-		
-	
-	/*
-	powerup* ppower = new powerup(rand() % POWERUP_TYPE_NUMPOWERUPS,_lifespan);
-	ppower->pos = pos;
-	if (ppower->type == POWERUP_TYPE_WEAPON)
-	{
-		ppower->subtype = rand() % WEAPON_NUMWEAPONS;
-		ppower->numitems = 10;
-	}
-	else if (ppower->type == POWERUP_TYPE_HEALTH || ppower->type == POWERUP_TYPE_ARMOR)
-	{
-		ppower->numitems = rand() % 5;
-	}
-	ppower->flags |= POWERUP_FLAG_HOOKABLE;
-	*/
-	
-	//powerup::spawnrandom(SERVER_TICK_SPEED * 5);
 }
 
 void mods_shutdown() {}