about summary refs log tree commit diff
path: root/src/engine
diff options
context:
space:
mode:
authorMagnus Auvinen <magnus.auvinen@gmail.com>2007-05-24 20:54:08 +0000
committerMagnus Auvinen <magnus.auvinen@gmail.com>2007-05-24 20:54:08 +0000
commit82023866ab4c7483652e9d4605290e39ced3bec3 (patch)
treecbff99cb472b4434d18e8e1fe3c556ca194096a6 /src/engine
parent34e3df396630e9bb271ea8965869d23260900a7d (diff)
downloadzcatch-82023866ab4c7483652e9d4605290e39ced3bec3.tar.gz
zcatch-82023866ab4c7483652e9d4605290e39ced3bec3.zip
large change. moved around all source. splitted server and client into separate files
Diffstat (limited to 'src/engine')
-rw-r--r--src/engine/client/client.cpp712
-rw-r--r--src/engine/client/gfx.cpp583
-rw-r--r--src/engine/client/snd.cpp520
-rw-r--r--src/engine/client/ui.cpp115
-rw-r--r--src/engine/client/ui.h33
-rw-r--r--src/engine/datafile.cpp444
-rw-r--r--src/engine/datafile.h20
-rw-r--r--src/engine/interface.h711
-rw-r--r--src/engine/lzw.cpp223
-rw-r--r--src/engine/lzw.h2
-rw-r--r--src/engine/map.cpp48
-rw-r--r--src/engine/packet.h288
-rw-r--r--src/engine/server/server.cpp5
-rw-r--r--src/engine/snapshot.h19
-rw-r--r--src/engine/versions.h2
15 files changed, 3725 insertions, 0 deletions
diff --git a/src/engine/client/client.cpp b/src/engine/client/client.cpp
new file mode 100644
index 00000000..4aceaf0f
--- /dev/null
+++ b/src/engine/client/client.cpp
@@ -0,0 +1,712 @@
+#include <baselib/system.h>
+#include <baselib/keys.h>
+#include <baselib/mouse.h>
+#include <baselib/audio.h>
+#include <baselib/stream/file.h>
+
+#include <string.h>
+#include <stdarg.h>
+#include <math.h>
+#include <engine/interface.h>
+
+#include <engine/packet.h>
+#include <engine/snapshot.h>
+#include "ui.h"
+
+#include <engine/lzw.h>
+
+#include <engine/versions.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][keys::last];
+static int keyboard_current = 0;
+static int keyboard_first = 1;
+
+void inp_mouse_relative(int *x, int *y) { mouse::position(x, y); }
+int inp_key_pressed(int key) { return keyboard_state[keyboard_current][key]; }
+int inp_key_was_pressed(int key) { return keyboard_state[keyboard_current^1][key]; }
+int inp_key_down(int key) { return inp_key_pressed(key)&&!inp_key_was_pressed(key); }
+int inp_mouse_button_pressed(int button) { return mouse::pressed(button); }
+
+void inp_update()
+{
+	if(keyboard_first)
+	{
+		// make sure to reset
+		keyboard_first = 0;
+		inp_update();
+	}
+	
+	keyboard_current = keyboard_current^1;
+	for(int i = 0; i < keys::last; i++)
+		keyboard_state[keyboard_current][i] = keys::pressed(i);
+}
+
+// --- input snapping ---
+static int input_data[MAX_INPUT_SIZE];
+static int input_data_size;
+static int input_is_changed = 1;
+void snap_input(void *data, int size)
+{
+	if(input_data_size != size || memcmp(input_data, data, size))
+		input_is_changed = 1;
+	mem_copy(input_data, data, size);
+	input_data_size = size;
+}
+
+// -- snapshot handling ---
+enum
+{
+	SNAP_INCOMMING=2,
+	NUM_SNAPSHOT_TYPES=3,
+};
+
+static snapshot *snapshots[NUM_SNAPSHOT_TYPES];
+static char snapshot_data[NUM_SNAPSHOT_TYPES][MAX_SNAPSHOT_SIZE];
+static int recived_snapshots;
+static int64 snapshot_start_time;
+static int64 local_start_time;
+
+float client_localtime()
+{
+	return (time_get()-local_start_time)/(float)(time_freq());
+}
+
+void *snap_get_item(int snapid, int index, snap_item *item)
+{
+	dbg_assert(snapid >= 0 && snapid < NUM_SNAPSHOT_TYPES, "invalid snapid");
+	snapshot::item *i = snapshots[snapid]->get_item(index);
+	item->type = i->type();
+	item->id = i->id();
+	return (void *)i->data;
+}
+
+int snap_num_items(int snapid)
+{
+	dbg_assert(snapid >= 0 && snapid < NUM_SNAPSHOT_TYPES, "invalid snapid");
+	return snapshots[snapid]->num_items;
+}
+
+static void snap_init()
+{
+	snapshots[SNAP_INCOMMING] = (snapshot*)snapshot_data[0];
+	snapshots[SNAP_CURRENT] = (snapshot*)snapshot_data[1];
+	snapshots[SNAP_PREV] = (snapshot*)snapshot_data[2];
+	mem_zero(snapshot_data, NUM_SNAPSHOT_TYPES*MAX_SNAPSHOT_SIZE);
+	recived_snapshots = 0;
+}
+
+float snap_intratick()
+{
+	return (time_get() - snapshot_start_time)/(float)(time_freq()/SERVER_TICK_SPEED);
+}
+
+void *snap_find_item(int snapid, int type, int id)
+{
+	// TODO: linear search. should be fixed.
+	for(int i = 0; i < snapshots[snapid]->num_items; i++)
+	{
+		snapshot::item *itm = snapshots[snapid]->get_item(i);
+		if(itm->type() == type && itm->id() == id)
+			return (void *)itm->data;
+	}
+	return 0x0;
+}
+
+
+int menu_loop();
+float frametime = 0.0001f;
+
+float client_frametime()
+{
+	return frametime;
+}
+
+void unpack(const char *src, const char *fmt, ...)
+{
+}
+
+/*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_msg_send(int msg, const char *fmt, ...)
+
+int server_msg_send(int msg, const char *fmt, ...)
+{
+
+}*/
+
+// --- client ---
+class client
+{
+public:
+	socket_udp4 socket;
+	connection conn;
+	int64 reconnect_timer;
+	
+	int snapshot_part;
+
+	int debug_font; // TODO: rfemove this line
+
+	// data to hold three snapshots
+	// previous, 
+	char name[MAX_NAME_LENGTH];
+
+	bool fullscreen;
+
+	enum
+	{
+		STATE_OFFLINE,
+		STATE_CONNECTING,
+		STATE_LOADING,
+		STATE_ONLINE,
+		STATE_BROKEN,
+		STATE_QUIT,
+	};
+	
+	int state;
+	int get_state() { return state; }
+	void set_state(int s)
+	{
+		dbg_msg("game", "state change. last=%d current=%d", state, s);
+		state = s;
+	}
+	
+	void set_name(const char *new_name)
+	{
+		mem_zero(name, MAX_NAME_LENGTH);
+		strncpy(name, new_name, MAX_NAME_LENGTH);
+		name[MAX_NAME_LENGTH-1] = 0;
+	}
+
+	void set_fullscreen(bool flag) { fullscreen = flag; }
+	
+	void send_packet(packet *p)
+	{
+		conn.send(p);
+	}
+	
+	void send_connect()
+	{
+		recived_snapshots = 0;
+		
+		/*
+		pack(NETMSG_CLIENT_CONNECT, "sssss",
+			TEEWARS_NETVERSION,
+			name,
+			"no clan",
+			"password",
+			"myskin");
+		*/
+		
+		packet p(NETMSG_CLIENT_CONNECT);
+		p.write_str(TEEWARS_NETVERSION); // payload
+		p.write_str(name);
+		p.write_str("no clan");
+		p.write_str("password");
+		p.write_str("myskin");
+		send_packet(&p);
+	}
+
+	void send_done()
+	{
+		packet p(NETMSG_CLIENT_DONE);
+		send_packet(&p);
+	}
+
+	void send_error(const char *error)
+	{
+		/*
+			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);
+		for(int i = 0; i < input_data_size/4; i++)
+			p.write_int(input_data[i]);
+		send_packet(&p);
+	}
+	
+	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();
+		set_state(STATE_CONNECTING);
+		reconnect_timer = time_get()+time_freq();
+	}
+	
+	bool load_data()
+	{
+		debug_font = gfx_load_texture_tga("data/debug_font.tga");
+		return true;
+	}
+	
+	void render()
+	{
+		gfx_clear(0.0f,0.0f,0.0f);
+		
+		// this should be moved around abit
+		if(get_state() == STATE_ONLINE)
+		{
+			modc_render();
+		}
+		else if (get_state() != STATE_CONNECTING && get_state() != STATE_LOADING)
+		{
+			netaddr4 server_address;
+			int status = modmenu_render(&server_address, name, MAX_NAME_LENGTH);
+
+			if (status == -1)
+				set_state(STATE_QUIT);
+			else if (status)
+				connect(&server_address);
+		}
+		else if (get_state() == STATE_CONNECTING)
+		{
+			static int64 start = time_get();
+			static int tee_texture;
+			static int connecting_texture;
+			static bool inited = false;
+			
+			if (!inited)
+			{
+				tee_texture = gfx_load_texture_tga("data/gui_tee.tga");
+				connecting_texture = gfx_load_texture_tga("data/gui/connecting.tga");
+					
+				inited = true;
+			}
+
+			gfx_mapscreen(0,0,400.0f,300.0f);
+
+			float t = (time_get() - start) / (double)time_freq();
+
+			float speed = 2*sin(t);
+
+			speed = 1.0f;
+
+			float x = 208 + sin(t*speed) * 32;
+			float w = sin(t*speed + 3.149) * 64;
+
+			ui_do_image(tee_texture, x, 95, w, 64);
+			ui_do_image(connecting_texture, 88, 150, 256, 64);
+		}
+	}
+	
+	void run(netaddr4 *server_address)
+	{
+		snapshot_part = 0;
+		
+		// init graphics and sound
+		if(!gfx_init(fullscreen))
+			return;
+
+		snd_init(); // sound is allowed to fail
+		
+		// load data
+		if(!load_data())
+			return;
+
+		// init snapshotting
+		snap_init();
+		
+		// init the mod
+		modc_init();
+
+		// init menu
+		modmenu_init();
+		
+		// open socket
+		if(!socket.open(0))
+		{
+			dbg_msg("network/client", "failed to open socket");
+			return;
+		}
+
+		// connect to the server if wanted
+		if (server_address)
+			connect(server_address);
+		
+		//int64 inputs_per_second = 50;
+		//int64 time_per_input = time_freq()/inputs_per_second;
+		int64 game_starttime = time_get();
+		int64 last_input = game_starttime;
+		
+		int64 reporttime = time_get();
+		int64 reportinterval = time_freq()*1;
+		int frames = 0;
+		
+		mouse::set_mode(mouse::mode_relative);
+		
+		while (1)
+		{	
+			frames++;
+			int64 frame_start_time = time_get();
+
+			// send input
+			if(get_state() == STATE_ONLINE)
+			{
+				if(input_is_changed || time_get() > last_input+time_freq())
+				{
+					send_input();
+					input_is_changed = 0;
+					last_input = time_get();
+				}
+			}
+			
+			// update input
+			inp_update();
+			
+			//
+			if(keys::pressed(keys::f1))
+				mouse::set_mode(mouse::mode_absolute);
+			if(keys::pressed(keys::f2))
+				mouse::set_mode(mouse::mode_relative);
+				
+			// pump the network
+			pump_network();
+			
+			// render
+			render();
+			
+			// swap the buffers
+			gfx_swap();
+			
+			// check conditions
+			if(get_state() == STATE_BROKEN || get_state() == STATE_QUIT)
+				break;
+
+			// be nice
+			//thread_sleep(1);
+			
+			if(reporttime < time_get())
+			{
+				unsigned sent, recved;
+				conn.counter_get(&sent, &recved);
+				dbg_msg("client/report", "fps=%.02f",
+					frames/(float)(reportinterval/time_freq()));
+				frames = 0;
+				reporttime += reportinterval;
+				conn.counter_reset();
+			}
+			
+			if (keys::pressed(keys::esc))
+				if (get_state() == STATE_CONNECTING || get_state() == STATE_ONLINE)
+					disconnect();
+
+			// update frametime
+			frametime = (time_get()-frame_start_time)/(float)time_freq();
+		}
+		
+		modc_shutdown();
+		disconnect();
+
+		modmenu_shutdown();
+		
+		gfx_shutdown();
+		snd_shutdown();
+	}
+	
+	void error(const char *msg)
+	{
+		dbg_msg("game", "error: %s", msg);
+		send_error(msg);
+		set_state(STATE_BROKEN);
+	}
+
+	void process_packet(packet *p)
+	{
+		if(p->msg() == NETMSG_SERVER_ACCEPT)
+		{
+			const char *map;
+			map = p->read_str();
+			
+			if(p->is_good())
+			{
+				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");
+				}
+			}
+		}
+		else if(p->msg() == NETMSG_SERVER_SNAP)
+		{
+			//dbg_msg("client/network", "got snapshot");
+			int num_parts = p->read_int();
+			int part = p->read_int();
+			int part_size = p->read_int();
+			
+			if(p->is_good())
+			{
+				if(snapshot_part == part)
+				{
+					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;
+
+						// 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)
+						{
+							local_start_time = time_get();
+							set_state(STATE_ONLINE);
+						}
+						
+						if(recived_snapshots > 2)
+							modc_newsnapshot();
+						
+						snapshot_part = 0;
+					}
+
+				}
+				else
+				{
+					dbg_msg("client", "snapshot reset!");
+					snapshot_part = 0;
+				}
+			}
+		}
+		else
+		{
+			dbg_msg("server/client", "unknown packet %x", p->msg());
+		}
+	}
+	
+	void pump_network()
+	{
+		while(1)
+		{
+			packet p;
+			netaddr4 from;
+			int bytes = socket.recv(&from, p.data(), p.max_size());
+			
+			if(bytes <= 0)
+				break;
+			
+			process_packet(&p);
+		}
+		
+		//
+		if(get_state() == STATE_CONNECTING && time_get() > reconnect_timer)
+		{
+			send_connect();
+			reconnect_timer = time_get() + time_freq();
+		}
+	}	
+};
+
+int main(int argc, char **argv)
+{
+	dbg_msg("client", "starting...");
+	netaddr4 server_address(127, 0, 0, 1, 8303);
+	const char *name = "nameless jerk";
+	bool connect_at_once = false;
+	bool fullscreen = true;
+
+	// init network, need to be done first so we can do lookups
+	net_init();
+
+	// parse arguments
+	for(int i = 1; i < argc; i++)
+	{
+		if(argv[i][0] == '-' && argv[i][1] == 'c' && argv[i][2] == 0 && argc - i > 1)
+		{
+			// -c SERVER
+			i++;
+			if(net_host_lookup(argv[i], 8303, &server_address) != 0)
+				dbg_msg("main", "could not find the address of %s, connecting to localhost", argv[i]);
+			else
+				connect_at_once = true;
+		}
+		else if(argv[i][0] == '-' && argv[i][1] == 'n' && argv[i][2] == 0 && argc - i > 1)
+		{
+			// -n NAME
+			i++;
+			name = argv[i];
+		}
+		else if(argv[i][0] == '-' && argv[i][1] == 'w' && argv[i][2] == 0)
+		{
+			// -w
+			fullscreen = false;
+		}
+	}
+	
+	// start the server
+	client c;
+	c.set_name(name);
+	c.set_fullscreen(fullscreen);
+	c.run(connect_at_once ? &server_address : 0x0);
+	return 0;
+}
diff --git a/src/engine/client/gfx.cpp b/src/engine/client/gfx.cpp
new file mode 100644
index 00000000..a824feac
--- /dev/null
+++ b/src/engine/client/gfx.cpp
@@ -0,0 +1,583 @@
+#include <baselib/opengl.h>
+#include <baselib/vmath.h>
+#include <baselib/stream/file.h>
+
+#include <engine/interface.h>
+
+using namespace baselib;
+
+static opengl::context context;
+
+struct custom_vertex
+{
+	vec3 pos;
+	vec2 tex;
+	vec4 color;
+};
+
+const int vertexBufferSize = 2048;
+//static custom_vertex vertices[4];
+static custom_vertex* g_pVertices = 0;
+static int g_iVertexStart = 0;
+static int g_iVertexEnd = 0;
+static vec4 g_QuadColor[4];
+static vec2 g_QuadTexture[4];
+
+static opengl::vertex_buffer vertex_buffer;
+//static int screen_width = 800;
+//static int screen_height = 600;
+static int screen_width = 1024;
+static int screen_height = 768;
+static float rotation = 0;
+static int quads_drawing = 0;
+
+
+struct texture_holder
+{
+	opengl::texture tex;
+	int flags;
+	int next;
+};
+
+static const int MAX_TEXTURES = 128;
+
+static texture_holder textures[MAX_TEXTURES];
+static int first_free_texture;
+
+static const char null_texture_data[] = {
+	0xff,0x00,0x00,0xff, 0xff,0x00,0x00,0xff, 0x00,0xff,0x00,0xff, 0x00,0xff,0x00,0xff, 
+	0xff,0x00,0x00,0xff, 0xff,0x00,0x00,0xff, 0x00,0xff,0x00,0xff, 0x00,0xff,0x00,0xff, 
+	0x00,0x00,0xff,0xff, 0x00,0x00,0xff,0xff, 0xff,0xff,0x00,0xff, 0xff,0xff,0x00,0xff, 
+	0x00,0x00,0xff,0xff, 0x00,0x00,0xff,0xff, 0xff,0xff,0x00,0xff, 0xff,0xff,0x00,0xff, 
+};
+
+static void draw_quad(bool _bflush = false)
+{
+	if (!_bflush && ((g_iVertexEnd + 4) < vertexBufferSize))
+	{
+		// Just add
+		g_iVertexEnd += 4;
+	}
+	else if (g_iVertexEnd)
+	{
+		if (!_bflush)
+			g_iVertexEnd += 4;
+		if(GLEW_VERSION_2_0)
+		{
+			// set the data
+			vertex_buffer.data(g_pVertices, vertexBufferSize * sizeof(custom_vertex), GL_DYNAMIC_DRAW);
+			opengl::stream_vertex(&vertex_buffer, 3, GL_FLOAT, sizeof(custom_vertex), 0);
+			opengl::stream_texcoord(&vertex_buffer, 0, 2, GL_FLOAT,
+					sizeof(custom_vertex),
+					sizeof(vec3));
+			opengl::stream_color(&vertex_buffer, 4, GL_FLOAT,
+					sizeof(custom_vertex),
+					sizeof(vec3)+sizeof(vec2));		
+			opengl::draw_arrays(GL_QUADS, 0, g_iVertexEnd);
+		}
+		else
+		{
+			glVertexPointer(3, GL_FLOAT,
+					sizeof(custom_vertex),
+					(char*)g_pVertices);
+			glTexCoordPointer(2, GL_FLOAT,
+					sizeof(custom_vertex),
+					(char*)g_pVertices + sizeof(vec3));
+			glColorPointer(4, GL_FLOAT,
+					sizeof(custom_vertex),
+					(char*)g_pVertices + sizeof(vec3) + sizeof(vec2));
+			glEnableClientState(GL_VERTEX_ARRAY);
+			glEnableClientState(GL_TEXTURE_COORD_ARRAY);
+			glEnableClientState(GL_COLOR_ARRAY);
+			glDrawArrays(GL_QUADS, 0, g_iVertexEnd);
+		}
+		// Reset pointer
+		g_iVertexEnd = 0;
+	}
+}
+	
+bool gfx_init(bool fullscreen)
+{
+	if(!context.create(screen_width, screen_height, 24, 8, 16, 0, fullscreen?opengl::context::FLAG_FULLSCREEN:0))
+	{
+		dbg_msg("game", "failed to create gl context");
+		return false;
+	}
+	// Init vertices
+	if (g_pVertices)
+		mem_free(g_pVertices);
+	g_pVertices = (custom_vertex*)mem_alloc(sizeof(custom_vertex) * vertexBufferSize, 1);
+	g_iVertexStart = 0;
+	g_iVertexEnd = 0;
+
+	context.set_title("---");
+
+	/*
+	dbg_msg("gfx", "OpenGL version %d.%d.%d", context.version_major(),
+											  context.version_minor(),
+											  context.version_rev());*/
+
+	gfx_mapscreen(0,0,screen_width, screen_height);
+	
+	// TODO: make wrappers for this
+	glEnable(GL_BLEND);
+	
+	// model
+	mat4 mat = mat4::identity;
+	opengl::matrix_modelview(&mat);
+	
+	// Set all z to -5.0f
+	for (int i = 0; i < vertexBufferSize; i++)
+		g_pVertices[i].pos.z = -5.0f;
+
+	if(GLEW_VERSION_2_0)
+	{
+		// set the streams
+		vertex_buffer.data(g_pVertices, sizeof(vertexBufferSize), GL_DYNAMIC_DRAW);
+		opengl::stream_vertex(&vertex_buffer, 3, GL_FLOAT, sizeof(custom_vertex), 0);
+		opengl::stream_texcoord(&vertex_buffer, 0, 2, GL_FLOAT,
+				sizeof(custom_vertex),
+				sizeof(vec3));
+		opengl::stream_color(&vertex_buffer, 4, GL_FLOAT,
+				sizeof(custom_vertex),
+				sizeof(vec3)+sizeof(vec2));		
+	}
+
+	// init textures
+	first_free_texture = 0;
+	for(int i = 0; i < MAX_TEXTURES; i++)
+		textures[i].next = i+1;
+	textures[MAX_TEXTURES-1].next = -1;
+	
+	// create null texture, will get id=0
+	gfx_load_texture_raw(4,4,null_texture_data);
+	
+	return true;
+}
+
+int gfx_unload_texture(int index)
+{
+	textures[index].tex.clear();
+	textures[index].next = first_free_texture;
+	first_free_texture = index;
+	return 0;
+}
+
+void gfx_blend_normal()
+{
+	// TODO: wrapper for this
+	glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
+}
+
+void gfx_blend_additive()
+{
+	// TODO: wrapper for this
+	glBlendFunc(GL_SRC_ALPHA, GL_ONE);
+}
+
+int gfx_load_texture_raw(int w, int h, const void *data)
+{
+	// grab texture
+	int tex = first_free_texture;
+	first_free_texture = textures[tex].next;
+	textures[tex].next = -1;
+	
+	// set data and return
+	// TODO: should be RGBA, not BGRA
+	dbg_msg("gfx", "%d = %dx%d", tex, w, h);
+	textures[tex].tex.data2d(w, h, GL_RGBA, GL_BGRA, GL_UNSIGNED_BYTE, data);
+	return tex;
+}
+
+// simple uncompressed RGBA loaders
+int gfx_load_texture_tga(const char *filename)
+{
+	image_info img;
+	
+	if(gfx_load_tga(&img, filename))
+	{
+		int id = gfx_load_texture_raw(img.width, img.height, img.data);
+		mem_free(img.data);
+		return id;
+	}
+	
+	return 0;
+}
+
+int gfx_load_tga(image_info *img, const char *filename)
+{
+	// open file for reading
+	file_stream file;
+	if(!file.open_r(filename))
+	{
+		dbg_msg("game/tga", "failed to open file. filename='%s'", filename);
+		return 0;
+	}
+	
+	// read header
+	unsigned char headers[18];
+	file.read(headers, sizeof(headers));
+	img->width = headers[12]+(headers[13]<<8);
+	img->height = headers[14]+(headers[15]<<8);
+
+	bool flipx = (headers[17] >> 4) & 1;
+	bool flipy = !((headers[17] >> 5) & 1);
+	
+	(void)flipx; // TODO: make use of this flag
+
+	if(headers[2] != 2) // needs to be uncompressed RGB
+	{
+		dbg_msg("game/tga", "tga not uncompressed rgb. filename='%s'", filename);
+		return 0;
+	}
+		
+	if(headers[16] != 32) // needs to be RGBA
+	{
+		dbg_msg("game/tga", "tga is 32bit. filename='%s'", filename);
+		return 0;
+	}
+	
+	// read data
+	int data_size = img->width*img->height*4;
+	img->data = mem_alloc(data_size, 1);
+
+	if (flipy)
+	{
+		for (int i = 0; i < img->height; i++)
+		{
+			file.read((char *)img->data + (img->height-i-1)*img->width*4, img->width*4);
+		}
+	}
+	else
+		file.read(img->data, data_size);
+	file.close();
+
+	return 1;
+}
+
+void gfx_shutdown()
+{
+	if (g_pVertices)
+		mem_free(g_pVertices);
+	context.destroy();
+}
+
+void gfx_swap()
+{
+	context.swap();
+}
+
+int gfx_screenwidth()
+{
+	return screen_width;
+}
+
+int gfx_screenheight()
+{
+	return screen_height;
+}
+
+void gfx_texture_set(int slot)
+{
+	dbg_assert(quads_drawing == 0, "called gfx_texture_set within quads_begin");
+	if(slot == -1)
+		opengl::texture_disable(0);
+	else
+		opengl::texture_2d(0, &textures[slot].tex);
+}
+
+void gfx_clear(float r, float g, float b)
+{
+	glClearColor(r,g,b,1.0f);
+	opengl::clear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
+}
+
+void gfx_mapscreen(float tl_x, float tl_y, float br_x, float br_y)
+{
+	mat4 mat;
+	mat.ortho(tl_x, br_x, br_y, tl_y, 1.0f, 10.f);
+	opengl::matrix_projection(&mat);
+}
+
+void gfx_setoffset(float x, float y)
+{
+	//const float scale = 0.75f;
+	const float scale = 1.0f;
+	mat4 mat = mat4::identity;
+	mat.m[0] = scale;
+	mat.m[5] = scale;
+	mat.m[10] = scale;
+	mat.m[12] = x*scale;
+	mat.m[13] = y*scale;
+	opengl::matrix_modelview(&mat);
+}
+
+void gfx_quads_begin()
+{
+	dbg_assert(quads_drawing == 0, "called quads_begin twice");
+	quads_drawing++;
+	
+	gfx_quads_setsubset(0,0,1,1);
+	gfx_quads_setrotation(0);
+	gfx_quads_setcolor(1,1,1,1);
+}
+
+void gfx_quads_end()
+{
+	dbg_assert(quads_drawing == 1, "called quads_end without quads_begin");
+	draw_quad(true);
+	quads_drawing--;
+}
+
+
+void gfx_quads_setrotation(float angle)
+{
+	dbg_assert(quads_drawing == 1, "called gfx_quads_setrotation without quads_begin");
+	rotation = angle;
+}
+
+void gfx_quads_setcolorvertex(int i, float r, float g, float b, float a)
+{
+	dbg_assert(quads_drawing == 1, "called gfx_quads_setcolorvertex without quads_begin");
+	g_QuadColor[i].r = r;
+	g_QuadColor[i].g = g;
+	g_QuadColor[i].b = b;
+	g_QuadColor[i].a = a;
+}
+
+void gfx_quads_setcolor(float r, float g, float b, float a)
+{
+	dbg_assert(quads_drawing == 1, "called gfx_quads_setcolor without quads_begin");
+	g_QuadColor[0] = vec4(r,g,b,a);
+	g_QuadColor[1] = vec4(r,g,b,a);
+	g_QuadColor[2] = vec4(r,g,b,a);
+	g_QuadColor[3] = vec4(r,g,b,a);
+	/*gfx_quads_setcolorvertex(0,r,g,b,a);
+	gfx_quads_setcolorvertex(1,r,g,b,a);
+	gfx_quads_setcolorvertex(2,r,g,b,a);
+	gfx_quads_setcolorvertex(3,r,g,b,a);*/
+}
+
+void gfx_quads_setsubset(float tl_u, float tl_v, float br_u, float br_v)
+{
+	dbg_assert(quads_drawing == 1, "called gfx_quads_setsubset without quads_begin");
+
+	g_QuadTexture[0].x = tl_u;
+	g_QuadTexture[0].y = tl_v;
+	//g_pVertices[g_iVertexEnd].tex.u = tl_u;
+	//g_pVertices[g_iVertexEnd].tex.v = tl_v;
+
+	g_QuadTexture[1].x = br_u;
+	g_QuadTexture[1].y = tl_v;
+	//g_pVertices[g_iVertexEnd + 2].tex.u = br_u;
+	//g_pVertices[g_iVertexEnd + 2].tex.v = tl_v;
+
+	g_QuadTexture[2].x = br_u;
+	g_QuadTexture[2].y = br_v;
+	//g_pVertices[g_iVertexEnd + 1].tex.u = tl_u;
+	//g_pVertices[g_iVertexEnd + 1].tex.v = br_v;
+
+	g_QuadTexture[3].x = tl_u;
+	g_QuadTexture[3].y = br_v;
+	//g_pVertices[g_iVertexEnd + 3].tex.u = br_u;
+	//g_pVertices[g_iVertexEnd + 3].tex.v = br_v;
+}
+
+static void rotate(vec3 &center, vec3 &point)
+{
+	vec3 p = point-center;
+	point.x = p.x * cosf(rotation) - p.y * sinf(rotation) + center.x;
+	point.y = p.x * sinf(rotation) + p.y * cosf(rotation) + center.y;
+}
+
+void gfx_quads_draw(float x, float y, float w, float h)
+{
+	gfx_quads_drawTL(x-w/2, y-h/2,w,h);
+}
+
+void gfx_quads_drawTL(float x, float y, float width, float height)
+{
+	dbg_assert(quads_drawing == 1, "called quads_draw without quads_begin");
+	
+	vec3 center;
+	center.x = x + width/2;
+	center.y = y + height/2;
+	center.z = 0;
+	
+	g_pVertices[g_iVertexEnd].pos.x = x;
+	g_pVertices[g_iVertexEnd].pos.y = y;
+	g_pVertices[g_iVertexEnd].tex.u = g_QuadTexture[0].x;
+	g_pVertices[g_iVertexEnd].tex.v = g_QuadTexture[0].y;
+	g_pVertices[g_iVertexEnd].color = g_QuadColor[0];
+	rotate(center, g_pVertices[g_iVertexEnd].pos);
+
+	g_pVertices[g_iVertexEnd + 1].pos.x = x+width;
+	g_pVertices[g_iVertexEnd + 1].pos.y = y;
+	g_pVertices[g_iVertexEnd + 1].tex.u = g_QuadTexture[1].x;
+	g_pVertices[g_iVertexEnd + 1].tex.v = g_QuadTexture[1].y;
+	g_pVertices[g_iVertexEnd + 1].color = g_QuadColor[1];
+	rotate(center, g_pVertices[g_iVertexEnd + 1].pos);
+
+	g_pVertices[g_iVertexEnd + 2].pos.x = x + width;
+	g_pVertices[g_iVertexEnd + 2].pos.y = y+height;
+	g_pVertices[g_iVertexEnd + 2].tex.u = g_QuadTexture[2].x;
+	g_pVertices[g_iVertexEnd + 2].tex.v = g_QuadTexture[2].y;
+	g_pVertices[g_iVertexEnd + 2].color = g_QuadColor[2];
+	rotate(center, g_pVertices[g_iVertexEnd + 2].pos);
+
+	g_pVertices[g_iVertexEnd + 3].pos.x = x;
+	g_pVertices[g_iVertexEnd + 3].pos.y = y+height;
+	g_pVertices[g_iVertexEnd + 3].tex.u = g_QuadTexture[3].x;
+	g_pVertices[g_iVertexEnd + 3].tex.v = g_QuadTexture[3].y;
+	g_pVertices[g_iVertexEnd + 3].color = g_QuadColor[3];
+	rotate(center, g_pVertices[g_iVertexEnd + 3].pos);
+	
+	draw_quad();
+}
+
+void gfx_quads_draw_freeform(
+	float x0, float y0,
+	float x1, float y1,
+	float x2, float y2,
+	float x3, float y3)
+{
+	dbg_assert(quads_drawing == 1, "called quads_draw_freeform without quads_begin");
+	
+	g_pVertices[g_iVertexEnd].pos.x = x0;
+	g_pVertices[g_iVertexEnd].pos.y = y0;
+	g_pVertices[g_iVertexEnd].tex.u = g_QuadTexture[0].x;
+	g_pVertices[g_iVertexEnd].tex.v = g_QuadTexture[0].y;
+	g_pVertices[g_iVertexEnd].color = g_QuadColor[0];
+
+	g_pVertices[g_iVertexEnd + 1].pos.x = x1;
+	g_pVertices[g_iVertexEnd + 1].pos.y = y1;
+	g_pVertices[g_iVertexEnd + 1].tex.u = g_QuadTexture[1].x;
+	g_pVertices[g_iVertexEnd + 1].tex.v = g_QuadTexture[1].y;
+	g_pVertices[g_iVertexEnd + 1].color = g_QuadColor[1];
+
+	g_pVertices[g_iVertexEnd + 2].pos.x = x3;
+	g_pVertices[g_iVertexEnd + 2].pos.y = y3;
+	g_pVertices[g_iVertexEnd + 2].tex.u = g_QuadTexture[2].x;
+	g_pVertices[g_iVertexEnd + 2].tex.v = g_QuadTexture[2].y;
+	g_pVertices[g_iVertexEnd + 2].color = g_QuadColor[2];
+
+	g_pVertices[g_iVertexEnd + 3].pos.x = x2;
+	g_pVertices[g_iVertexEnd + 3].pos.y = y2;
+	g_pVertices[g_iVertexEnd + 3].tex.u = g_QuadTexture[3].x;
+	g_pVertices[g_iVertexEnd + 3].tex.v = g_QuadTexture[3].y;
+	g_pVertices[g_iVertexEnd + 3].color = g_QuadColor[3];
+	
+	draw_quad();
+}
+
+void gfx_quads_text(float x, float y, float size, const char *text)
+{
+	gfx_quads_begin();
+	while(*text)
+	{
+		char c = *text;
+		text++;
+		
+		gfx_quads_setsubset(
+			(c%16)/16.0f,
+			(c/16)/16.0f,
+			(c%16)/16.0f+1.0f/16.0f,
+			(c/16)/16.0f+1.0f/16.0f);
+		
+		gfx_quads_drawTL(x,y,size,size);
+		x += size/2;
+	}
+	
+	gfx_quads_end();
+}
+
+struct pretty_font
+{
+	float m_CharStartTable[256];
+	float m_CharEndTable[256];
+	int font_texture;
+};
+
+pretty_font default_font = 
+{
+{
+        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 
+        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 
+        0, 0.421875, 0.359375, 0.265625, 0.25, 0.1875, 0.25, 0.4375, 0.390625, 0.390625, 0.34375, 0.28125, 0.421875, 0.390625, 0.4375, 0.203125, 
+        0.265625, 0.28125, 0.28125, 0.265625, 0.25, 0.28125, 0.28125, 0.265625, 0.28125, 0.265625, 0.4375, 0.421875, 0.3125, 0.28125, 0.3125, 0.3125, 
+        0.25, 0.234375, 0.28125, 0.265625, 0.265625, 0.296875, 0.3125, 0.25, 0.25, 0.421875, 0.28125, 0.265625, 0.328125, 0.171875, 0.234375, 0.25, 
+        0.28125, 0.234375, 0.265625, 0.265625, 0.28125, 0.265625, 0.234375, 0.09375, 0.234375, 0.234375, 0.265625, 0.390625, 0.203125, 0.390625, 0.296875, 0.28125, 
+        0.375, 0.3125, 0.3125, 0.3125, 0.296875, 0.3125, 0.359375, 0.296875, 0.3125, 0.4375, 0.390625, 0.328125, 0.4375, 0.203125, 0.3125, 0.296875, 
+        0.3125, 0.296875, 0.359375, 0.3125, 0.328125, 0.3125, 0.296875, 0.203125, 0.296875, 0.296875, 0.328125, 0.375, 0.421875, 0.375, 0.28125, 0.3125, 
+        0.3125, 0.3125, 0.3125, 0.3125, 0.3125, 0.3125, 0.3125, 0.3125, 0.3125, 0.3125, 0.3125, 0.3125, 0.3125, 0.3125, 0.3125, 0.3125, 
+        0.3125, 0.3125, 0.3125, 0.3125, 0.3125, 0.3125, 0.3125, 0.3125, 0.3125, 0.3125, 0.3125, 0.3125, 0.3125, 0.3125, 0.3125, 0.3125, 
+        0, 0.421875, 0.3125, 0.265625, 0.25, 0.25, 0.421875, 0.265625, 0.375, 0.21875, 0.375, 0.328125, 0.3125, 0, 0.21875, 0.28125, 
+        0.359375, 0.28125, 0.34375, 0.34375, 0.421875, 0.3125, 0.265625, 0.421875, 0.421875, 0.34375, 0.375, 0.328125, 0.125, 0.125, 0.125, 0.296875, 
+        0.234375, 0.234375, 0.234375, 0.234375, 0.234375, 0.234375, 0.109375, 0.265625, 0.296875, 0.296875, 0.296875, 0.296875, 0.375, 0.421875, 0.359375, 0.390625, 
+        0.21875, 0.234375, 0.25, 0.25, 0.25, 0.25, 0.25, 0.296875, 0.21875, 0.265625, 0.265625, 0.265625, 0.265625, 0.234375, 0.28125, 0.3125, 
+        0.3125, 0.3125, 0.3125, 0.3125, 0.3125, 0.3125, 0.1875, 0.3125, 0.3125, 0.3125, 0.3125, 0.3125, 0.375, 0.421875, 0.359375, 0.390625, 
+        0.3125, 0.3125, 0.296875, 0.296875, 0.296875, 0.296875, 0.296875, 0.28125, 0.28125, 0.3125, 0.3125, 0.3125, 0.3125, 0.296875, 0.3125, 0.296875, 
+},
+{
+        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 
+        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 
+        0.2, 0.5625, 0.625, 0.71875, 0.734375, 0.796875, 0.765625, 0.546875, 0.59375, 0.59375, 0.65625, 0.703125, 0.546875, 0.59375, 0.5625, 0.6875, 
+        0.71875, 0.609375, 0.703125, 0.703125, 0.71875, 0.703125, 0.703125, 0.6875, 0.703125, 0.703125, 0.5625, 0.546875, 0.671875, 0.703125, 0.671875, 0.671875, 
+        0.734375, 0.75, 0.734375, 0.734375, 0.734375, 0.6875, 0.6875, 0.734375, 0.71875, 0.5625, 0.65625, 0.765625, 0.703125, 0.8125, 0.75, 0.734375, 
+        0.734375, 0.765625, 0.71875, 0.71875, 0.703125, 0.71875, 0.75, 0.890625, 0.75, 0.75, 0.71875, 0.59375, 0.6875, 0.59375, 0.6875, 0.703125, 
+        0.5625, 0.671875, 0.6875, 0.671875, 0.671875, 0.671875, 0.625, 0.671875, 0.671875, 0.5625, 0.546875, 0.703125, 0.5625, 0.78125, 0.671875, 0.671875, 
+        0.6875, 0.671875, 0.65625, 0.671875, 0.65625, 0.671875, 0.6875, 0.78125, 0.6875, 0.671875, 0.65625, 0.609375, 0.546875, 0.609375, 0.703125, 0.671875, 
+        0.671875, 0.671875, 0.671875, 0.671875, 0.671875, 0.671875, 0.671875, 0.671875, 0.671875, 0.671875, 0.671875, 0.671875, 0.671875, 0.671875, 0.671875, 0.671875, 
+        0.671875, 0.671875, 0.671875, 0.671875, 0.671875, 0.671875, 0.671875, 0.671875, 0.671875, 0.671875, 0.671875, 0.671875, 0.671875, 0.671875, 0.671875, 0.671875, 
+        0, 0.5625, 0.671875, 0.734375, 0.734375, 0.734375, 0.546875, 0.71875, 0.609375, 0.765625, 0.609375, 0.65625, 0.671875, 0, 0.765625, 0.703125, 
+        0.625, 0.703125, 0.640625, 0.640625, 0.609375, 0.671875, 0.703125, 0.546875, 0.5625, 0.578125, 0.609375, 0.65625, 0.859375, 0.859375, 0.859375, 0.671875, 
+        0.75, 0.75, 0.75, 0.75, 0.75, 0.75, 0.84375, 0.734375, 0.6875, 0.6875, 0.6875, 0.6875, 0.5625, 0.609375, 0.640625, 0.59375, 
+        0.734375, 0.75, 0.734375, 0.734375, 0.734375, 0.734375, 0.734375, 0.6875, 0.765625, 0.71875, 0.71875, 0.71875, 0.71875, 0.75, 0.734375, 0.6875, 
+        0.671875, 0.671875, 0.671875, 0.671875, 0.671875, 0.671875, 0.796875, 0.671875, 0.671875, 0.671875, 0.671875, 0.671875, 0.5625, 0.609375, 0.625, 0.59375, 
+        0.6875, 0.671875, 0.671875, 0.671875, 0.671875, 0.671875, 0.671875, 0.703125, 0.703125, 0.671875, 0.671875, 0.671875, 0.671875, 0.671875, 0.6875, 0.671875, 
+},
+0
+};
+
+pretty_font *current_font = &default_font;
+
+void gfx_pretty_text(float x, float y, float size, const char *text)
+{
+	const float spacing = 0.05f;
+	
+	gfx_quads_begin();
+	
+	while (*text)
+	{
+		const int c = *text;
+		const float width = current_font->m_CharEndTable[c] - current_font->m_CharStartTable[c];
+		
+		text++;
+
+		gfx_quads_setsubset(
+			(c%16)/16.0f + current_font->m_CharStartTable[c]/16.0f, // startx
+			(c/16)/16.0f, // starty
+			(c%16)/16.0f + current_font->m_CharEndTable[c]/16.0f + 0.001, // endx
+			(c/16)/16.0f+1.0f/16.0f); // endy
+
+		gfx_quads_drawTL(x, y, width * size, size);
+
+		x += (width + spacing) * size;
+	}
+
+	gfx_quads_end();
+}
+
+float gfx_pretty_text_width(float size, const char *text)
+{
+	const float spacing = 0.05f;
+	float width = 0.0f;
+
+	while (*text)
+	{
+		const int c = *text++;
+		width += size * (current_font->m_CharEndTable[c] - current_font->m_CharStartTable[c] + spacing);
+	}
+
+	return width;
+}
diff --git a/src/engine/client/snd.cpp b/src/engine/client/snd.cpp
new file mode 100644
index 00000000..dd6baa9a
--- /dev/null
+++ b/src/engine/client/snd.cpp
@@ -0,0 +1,520 @@
+#include <baselib/system.h>
+#include <baselib/audio.h>
+#include <baselib/stream/file.h>
+
+#include <engine/interface.h>
+
+using namespace baselib;
+
+static const int NUM_FRAMES_STOP = 512;
+static const float NUM_FRAMES_STOP_INV = 1.0f/(float)NUM_FRAMES_STOP;
+static const int NUM_FRAMES_LERP = 512;
+static const float NUM_FRAMES_LERP_INV = 1.0f/(float)NUM_FRAMES_LERP;
+
+static const float GLOBAL_VOLUME_SCALE = 0.75f;
+
+static const int64 GLOBAL_SOUND_DELAY = 1000;
+
+// --- sound ---
+class sound_data
+{
+public:
+	short *data;
+	int num_samples;
+	int rate;
+	int channels;
+	int sustain_start;
+	int sustain_end;
+	int64 last_played;
+};
+
+inline short clamp(int i)
+{
+	if(i > 0x7fff)
+		return 0x7fff;
+	if(i < -0x7fff)
+		return -0x7fff;
+	return i;
+}
+
+class mixer : public audio_stream
+{
+public:
+	class channel
+	{
+	public:
+		channel()
+		{ data = 0; lerp = -1; stop = -1; }
+		
+		sound_data *data;
+		int tick;
+		int loop;
+		float pan;
+		float vol;
+		float old_vol;
+		float new_vol;
+		int lerp;
+		int stop;
+	};
+	
+	enum
+	{
+		MAX_CHANNELS=8,
+	};
+
+	channel channels[MAX_CHANNELS];
+
+	virtual void fill(void *output, unsigned long frames)
+	{
+		//dbg_msg("snd", "mixing!");
+		
+		short *out = (short*)output;
+		bool clamp_flag = false;
+		
+		int active_channels = 0;
+		for(unsigned long i = 0; i < frames; i++)
+		{
+			int left = 0;
+			int right = 0;
+			
+			for(int c = 0; c < MAX_CHANNELS; c++)
+			{
+				if(channels[c].data)
+				{
+					if(channels[c].data->channels == 1)
+					{
+						left += (1.0f-(channels[c].pan+1.0f)*0.5f) * channels[c].vol * channels[c].data->data[channels[c].tick];
+						right += (channels[c].pan+1.0f)*0.5f * channels[c].vol * channels[c].data->data[channels[c].tick];
+						channels[c].tick++;
+					}
+					else
+					{
+						float pl = channels[c].pan<0.0f?-channels[c].pan:1.0f;
+						float pr = channels[c].pan>0.0f?1.0f-channels[c].pan:1.0f;
+						left += pl*channels[c].vol * channels[c].data->data[channels[c].tick];
+						right += pr*channels[c].vol * channels[c].data->data[channels[c].tick + 1];
+						channels[c].tick += 2;
+					}
+				
+					if(channels[c].loop)
+					{
+						if(channels[c].data->sustain_start >= 0 && channels[c].tick >= channels[c].data->sustain_end)
+							channels[c].tick = channels[c].data->sustain_start;
+						else if(channels[c].tick > channels[c].data->num_samples)
+							channels[c].tick = 0;
+					}
+					else if(channels[c].tick > channels[c].data->num_samples)
+						channels[c].data = 0;
+
+					if(channels[c].stop == 0)
+					{
+						channels[c].stop = -1;
+						channels[c].data = 0;
+					}
+					else if(channels[c].stop > 0)
+					{
+						channels[c].vol = channels[c].old_vol * (float)channels[c].stop * NUM_FRAMES_STOP_INV;
+						channels[c].stop--;
+					}
+					if(channels[c].lerp > 0)
+					{
+						channels[c].vol = (1.0f - (float)channels[c].lerp * NUM_FRAMES_LERP_INV) * channels[c].new_vol +
+							(float)channels[c].lerp * NUM_FRAMES_LERP_INV * channels[c].old_vol;
+						channels[c].lerp--;
+					}
+					active_channels++;
+				}
+			}
+
+			// TODO: remove these
+
+			*out = clamp(left); // left
+			if(*out != left) clamp_flag = true;
+			out++;
+			*out = clamp(right); // right
+			if(*out != right) clamp_flag = true;
+			out++;
+		}
+
+		if(clamp_flag)
+			dbg_msg("snd", "CLAMPED!");
+	}
+	
+	int play(sound_data *sound, unsigned loop, float vol, float pan)
+	{
+		if(time_get() - sound->last_played < GLOBAL_SOUND_DELAY)
+			return -1;
+
+		for(int c = 0; c < MAX_CHANNELS; c++)
+		{
+			if(channels[c].data == 0)
+			{
+				channels[c].data = sound;
+				channels[c].tick = 0;
+				channels[c].loop = loop;
+				channels[c].vol = vol * GLOBAL_VOLUME_SCALE;
+				channels[c].pan = pan;
+				sound->last_played = time_get();
+				return c;
+			}
+		}
+
+		return -1;
+	}
+
+	void stop(int id)
+	{
+		dbg_assert(id >= 0 && id < MAX_CHANNELS, "id out of bounds");
+		channels[id].old_vol = channels[id].vol;
+		channels[id].stop = NUM_FRAMES_STOP;
+	}
+
+	void set_vol(int id, float vol)
+	{
+		dbg_assert(id >= 0 && id < MAX_CHANNELS, "id out of bounds");
+		channels[id].new_vol = vol * GLOBAL_VOLUME_SCALE;
+		channels[id].old_vol = channels[id].vol;
+		channels[id].lerp = NUM_FRAMES_LERP;
+	}
+};
+
+static mixer mixer;
+//static sound_data test_sound;
+
+/*
+extern "C" 
+{
+#include "wavpack/wavpack.h"
+}*/
+
+/*
+static file_stream *read_func_filestream;
+static int32_t read_func(void *buff, int32_t bcount)
+{
+    return read_func_filestream->read(buff, bcount);
+}
+static uchar *format_samples(int bps, uchar *dst, int32_t *src, uint32_t samcnt)
+{
+    int32_t temp;
+
+    switch (bps) {
+
+        case 1:
+            while (samcnt--)
+                *dst++ = *src++ + 128;
+
+            break;
+
+        case 2:
+            while (samcnt--) {
+                *dst++ = (uchar)(temp = *src++);
+                *dst++ = (uchar)(temp >> 8);
+            }
+
+            break;
+
+        case 3:
+            while (samcnt--) {
+                *dst++ = (uchar)(temp = *src++);
+                *dst++ = (uchar)(temp >> 8);
+                *dst++ = (uchar)(temp >> 16);
+            }
+
+            break;
+
+        case 4:
+            while (samcnt--) {
+                *dst++ = (uchar)(temp = *src++);
+                *dst++ = (uchar)(temp >> 8);
+                *dst++ = (uchar)(temp >> 16);
+                *dst++ = (uchar)(temp >> 24);
+            }
+
+            break;
+    }
+
+    return dst;
+}*/
+
+/*
+struct sound_holder
+{
+	sound_data sound;
+	int next;
+};
+
+static const int MAX_SOUNDS = 256;
+static sound_holder sounds[MAX_SOUNDS];
+static int first_free_sound;
+
+bool snd_load_wv(const char *filename, sound_data *snd)
+{
+	// open file
+	file_stream file;
+	if(!file.open_r(filename))
+	{
+		dbg_msg("sound/wv", "failed to open file. filename='%s'", filename);
+		return false;
+	}
+	read_func_filestream = &file;
+	
+	// get info
+	WavpackContext *wpc;
+	char error[128];
+	wpc = WavpackOpenFileInput(read_func, error);
+	if(!wpc)
+	{
+		dbg_msg("sound/wv", "failed to open file. err=%s filename='%s'", error, filename);
+		return false;
+	}
+
+
+	snd->num_samples = WavpackGetNumSamples(wpc);
+    int bps = WavpackGetBytesPerSample(wpc);
+	int channels = WavpackGetReducedChannels(wpc);
+	snd->rate = WavpackGetSampleRate(wpc);
+	int bits = WavpackGetBitsPerSample(wpc);
+	
+	(void)bps;
+	(void)channels;
+	(void)bits;
+	
+	// decompress
+	int datasize = snd->num_samples*2;
+	snd->data = (short*)mem_alloc(datasize, 1);
+	int totalsamples = 0;
+	while(1)
+	{
+		int buffer[1024*4];
+		int samples_unpacked = WavpackUnpackSamples(wpc, buffer, 1024*4);
+		totalsamples += samples_unpacked;
+		
+		if(samples_unpacked)
+		{
+			// convert
+		}
+	}
+	
+	if(snd->num_samples != totalsamples)
+	{
+		dbg_msg("sound/wv", "wrong amount of samples. filename='%s'", filename);
+		mem_free(snd->data);
+		return false;;
+	}
+		
+	return false;
+}*/
+
+struct sound_holder
+{
+	sound_data sound;
+	int next;
+};
+
+static const int MAX_SOUNDS = 1024;
+static sound_holder sounds[MAX_SOUNDS];
+static int first_free_sound;
+
+bool snd_init()
+{
+	first_free_sound = 0;
+	for(int i = 0; i < MAX_SOUNDS; i++)
+		sounds[i].next = i+1;
+	sounds[MAX_SOUNDS-1].next = -1;
+	return mixer.create();
+}
+
+bool snd_shutdown()
+{
+	mixer.destroy();
+	return true;
+}
+
+static int snd_alloc_sound()
+{
+	if(first_free_sound < 0)
+		return -1;
+	int id = first_free_sound;
+	first_free_sound = sounds[id].next;
+	sounds[id].next = -1;
+	return id;
+}
+
+int snd_load_wav(const char *filename)
+{
+	sound_data snd;
+	
+	// open file for reading
+	file_stream file;
+	if(!file.open_r(filename))
+	{
+		dbg_msg("sound/wav", "failed to open file. filename='%s'", filename);
+		return -1;
+	}
+
+	int id = -1;
+	int state = 0;
+	while(1)
+	{
+		// read chunk header
+		unsigned char head[8];
+		if(file.read(head, sizeof(head)) != 8)
+		{
+			break;
+		}
+		
+		int chunk_size = head[4] | (head[5]<<8) | (head[6]<<16) | (head[7]<<24);
+		head[4] = 0;
+			
+		if(state == 0)
+		{
+			// read the riff and wave headers
+			if(head[0] != 'R' || head[1] != 'I' || head[2] != 'F' || head[3] != 'F')
+			{
+				dbg_msg("sound/wav", "not a RIFF file. filename='%s'", filename);
+				return -1;
+			}
+			
+			unsigned char type[4];
+			file.read(type, 4);
+
+			if(type[0] != 'W' || type[1] != 'A' || type[2] != 'V' || type[3] != 'E')
+			{
+				dbg_msg("sound/wav", "RIFF file is not a WAVE. filename='%s'", filename);
+				return -1;
+			}
+			
+			state++;
+		}
+		else if(state == 1)
+		{
+			// read the format chunk
+			if(head[0] == 'f' && head[1] == 'm' && head[2] == 't' && head[3] == ' ')
+			{
+				unsigned char fmt[16];
+				if(file.read(fmt, sizeof(fmt)) !=  sizeof(fmt))
+				{
+					dbg_msg("sound/wav", "failed to read format. filename='%s'", filename);
+					return -1;
+				}
+				
+				// decode format
+				int compression_code = fmt[0] | (fmt[1]<<8);
+				snd.channels = fmt[2] | (fmt[3]<<8);
+				snd.rate = fmt[4] | (fmt[5]<<8) | (fmt[6]<<16) | (fmt[7]<<24);
+
+				if(compression_code != 1)
+				{
+					dbg_msg("sound/wav", "file is not uncompressed. filename='%s'", filename);
+					return -1;
+				}
+				
+				if(snd.channels > 2)
+				{
+					dbg_msg("sound/wav", "file is not mono or stereo. filename='%s'", filename);
+					return -1;
+				}
+
+				if(snd.rate != 44100)
+				{
+					dbg_msg("sound/wav", "file is %d Hz, not 44100 Hz. filename='%s'", snd.rate, filename);
+					return -1;
+				}
+				
+				int bps = fmt[14] | (fmt[15]<<8);
+				if(bps != 16)
+				{
+					dbg_msg("sound/wav", "bps is %d, not 16, filname='%s'", bps, filename);
+					return -1;
+				}
+				
+				// skip extra bytes (not used for uncompressed)
+				//int extra_bytes = fmt[14] | (fmt[15]<<8);
+				//dbg_msg("sound/wav", "%d", extra_bytes);
+				//file.skip(extra_bytes);
+				
+				// next state
+				state++;
+			}
+			else
+				file.skip(chunk_size);
+		}
+		else if(state == 2)
+		{
+			// read the data
+			if(head[0] == 'd' && head[1] == 'a' && head[2] == 't' && head[3] == 'a')
+			{
+				snd.data = (short*)mem_alloc(chunk_size, 1);
+				file.read(snd.data, chunk_size);
+				snd.num_samples = chunk_size/(2);
+				snd.sustain_start = -1;
+				snd.sustain_end = -1;
+				snd.last_played = 0;
+				id = snd_alloc_sound();
+				sounds[id].sound = snd;
+				state++;
+			}
+			else
+				file.skip(chunk_size);
+		}
+		else if(state == 3)
+		{
+			if(head[0] == 's' && head[1] == 'm' && head[2] == 'p' && head[3] == 'l')
+			{
+				int smpl[9];
+				int loop[6];
+
+				file.read(smpl, sizeof(smpl));
+
+				if(smpl[7] > 0)
+				{
+					file.read(loop, sizeof(loop));
+					sounds[id].sound.sustain_start = loop[2] * sounds[id].sound.channels;
+					sounds[id].sound.sustain_end = loop[3] * sounds[id].sound.channels;
+				}
+
+				if(smpl[7] > 1)
+					file.skip((smpl[7]-1) * sizeof(loop));
+
+				file.skip(smpl[8]);
+				state++;
+			}
+			else
+				file.skip(chunk_size);
+		}
+		else
+			file.skip(chunk_size);
+	}
+
+	if(id >= 0)
+		dbg_msg("sound/wav", "loaded %s", filename);
+	else
+		dbg_msg("sound/wav", "failed to load %s", filename);
+
+	return id;
+}
+
+int snd_play(int id, int loop, float vol, float pan)
+{
+	if(id < 0)
+	{
+		dbg_msg("snd", "bad sound id");
+		return -1;
+	}
+
+	dbg_assert(sounds[id].sound.data != 0, "null sound");
+	dbg_assert(sounds[id].next == -1, "sound isn't allocated");
+	return mixer.play(&sounds[id].sound, loop, vol, pan);
+}
+
+void snd_stop(int id)
+{
+	if(id >= 0)
+		mixer.stop(id);
+}
+
+void snd_set_vol(int id, float vol)
+{
+	if(id >= 0)
+		mixer.set_vol(id, vol);
+}
diff --git a/src/engine/client/ui.cpp b/src/engine/client/ui.cpp
new file mode 100644
index 00000000..3353feca
--- /dev/null
+++ b/src/engine/client/ui.cpp
@@ -0,0 +1,115 @@
+#include <engine/interface.h>
+#include "ui.h"
+
+/********************************************************
+ UI                                                      
+*********************************************************/
+//static unsigned mouse_buttons_last = 0;
+
+struct pretty_font
+{
+    char m_CharStartTable[256];
+	char m_CharEndTable[256];
+	int font_texture;
+};
+
+extern pretty_font *current_font;
+void gfx_pretty_text(float x, float y, float size, const char *text);
+
+
+static void *hot_item = 0;
+static void *active_item = 0;
+static void *becomming_hot_item = 0;
+static float mouse_x, mouse_y; // in gui space
+static float mouse_wx, mouse_wy; // in world space
+static unsigned mouse_buttons = 0;
+
+float ui_mouse_x() { return mouse_x; }
+float ui_mouse_y() { return mouse_y; }
+float ui_mouse_world_x() { return mouse_wx; }
+float ui_mouse_world_y() { return mouse_wy; }
+int ui_mouse_button(int index) { return (mouse_buttons>>index)&1; }
+
+void ui_set_hot_item(void *id) { becomming_hot_item = id; }
+void ui_set_active_item(void *id) { active_item = id; }
+void *ui_hot_item() { return hot_item; }
+void *ui_active_item() { return active_item; }
+
+int ui_update(float mx, float my, float mwx, float mwy, int buttons)
+{
+    //mouse_buttons_last = mouse_buttons;
+    mouse_x = mx;
+    mouse_y = my;
+    mouse_wx = mwx;
+    mouse_wy = mwy;
+    mouse_buttons = buttons;
+    hot_item = becomming_hot_item;
+    becomming_hot_item = 0;
+    return 0;
+}
+
+/*
+static int ui_mouse_button_released(int index)
+{
+    return ((mouse_buttons_last>>index)&1) && !();
+}*/
+
+int ui_mouse_inside(float x, float y, float w, float h)
+{
+    if(mouse_x >= x && mouse_x <= x+w && mouse_y >= y && mouse_y <= y+h)
+        return 1;
+    return 0;
+}
+
+void ui_do_image(int texture, float x, float y, float w, float h)
+{
+    gfx_blend_normal();
+    gfx_texture_set(texture);
+    gfx_quads_begin();
+    gfx_quads_setcolor(1,1,1,1);
+    gfx_quads_setsubset(
+        0.0f, // startx
+        0.0f, // starty
+        1.0f, // endx
+        1.0f); // endy                                
+    gfx_quads_drawTL(x,y,w,h);
+    gfx_quads_end();
+}
+
+void ui_do_label(float x, float y, char *text)
+{
+    gfx_blend_normal();
+    gfx_texture_set(current_font->font_texture);
+    gfx_pretty_text(x, y, 18.f, text);
+}
+
+int ui_do_button(void *id, const char *text, int checked, float x, float y, float w, float h, draw_button_callback draw_func, void *extra)
+{
+    // logic
+    int r = 0;
+    int inside = ui_mouse_inside(x,y,w,h);
+
+	if(inside)
+	{
+		ui_set_hot_item(id);
+
+		if(ui_mouse_button(0))
+			ui_set_active_item(id);
+	}
+
+	if(ui_active_item() == id && ui_hot_item() == id && !ui_mouse_button(0))
+	{
+		ui_set_active_item(0);
+		r = 1;
+	}
+
+    draw_func(id, text, checked, x, y, w, h, extra);
+
+    return r;
+}
+
+int ui_do_button(void *id, const char *text, int checked, float x, float y, float w, float h, draw_button_callback draw_func)
+{
+	return ui_do_button(id, text, checked, x, y, w, h, draw_func, 0x0);
+}
+
diff --git a/src/engine/client/ui.h b/src/engine/client/ui.h
new file mode 100644
index 00000000..1a420906
--- /dev/null
+++ b/src/engine/client/ui.h
@@ -0,0 +1,33 @@
+#ifndef _UI_H
+#define _UI_H
+/*
+extern void *hot_item;
+extern void *active_item;
+extern void *becomming_hot_item;
+extern float mouse_x, mouse_y; // in gui space
+extern float mouse_wx, mouse_wy; // in world space
+extern unsigned mouse_buttons;*/
+
+int ui_update(float mx, float my, float mwx, float mwy, int buttons);
+
+float ui_mouse_x();
+float ui_mouse_y();
+float ui_mouse_world_x();
+float ui_mouse_world_y();
+int ui_mouse_button(int index);
+
+void ui_set_hot_item(void *id);
+void ui_set_active_item(void *id);
+void *ui_hot_item();
+void *ui_active_item();
+
+int ui_mouse_inside(float x, float y, float w, float h);
+
+typedef void (*draw_button_callback)(void *id, const char *text, int checked, float x, float y, float w, float h, void *extra);
+
+void ui_do_image(int texture, float x, float y, float w, float h);
+void ui_do_label(float x, float y, char *text);
+int ui_do_button(void *id, const char *text, int checked, float x, float y, float w, float h, draw_button_callback draw_func, void *extra);
+int ui_do_button(void *id, const char *text, int checked, float x, float y, float w, float h, draw_button_callback draw_func);
+
+#endif
diff --git a/src/engine/datafile.cpp b/src/engine/datafile.cpp
new file mode 100644
index 00000000..789aa722
--- /dev/null
+++ b/src/engine/datafile.cpp
@@ -0,0 +1,444 @@
+#include <baselib/system.h>
+#include <baselib/stream/file.h>
+
+#include "datafile.h"
+
+static const int DEBUG=0;
+
+struct item_type
+{
+	int type;
+	int start;
+	int num;
+};
+
+struct item
+{
+	int type_and_id;
+	int size;
+};
+
+struct datafile_header
+{
+	int id;
+	int version;
+	int size;
+	int swaplen;
+	int num_item_types;
+	int num_items;
+	int num_raw_data;
+	int item_size;
+	int data_size;
+};
+
+struct datafile_data
+{
+	int num_item_types;
+	int num_items;
+	int num_raw_data;
+	int item_size;
+	int data_size;
+	char start[4];
+};
+
+struct datafile_info
+{
+	item_type *item_types;
+	int *item_offsets;
+	int *data_offsets;
+
+	char *item_start;
+	char *data_start;	
+};
+
+struct datafile
+{
+	datafile_info info;
+	datafile_data data;
+};
+
+datafile *datafile_load(const char *filename)
+{
+	dbg_msg("datafile", "datafile loading. filename='%s'", filename);
+
+	baselib::file_stream file;
+	if(!file.open_r(filename))
+		return 0;
+	
+	// TODO: change this header
+	int header[4];
+	file.read(header, sizeof(header));
+	if(((header[0]>>24)&0xff) != 'D' || ((header[0]>>16)&0xff) != 'A' || (header[0]>>8)&0xff != 'T' || (header[0]&0xff)!= 'A')
+	{
+		dbg_msg("datafile", "wrong signature. %x %x %x %x", header[0], header[1], header[2], header[3]);
+		return 0;
+	}
+	
+	int version = header[1];
+	if(version != 3)
+	{
+		dbg_msg("datafile", "wrong version. version=%x", version);
+		return 0;
+	}
+	
+	unsigned size = header[2];
+	unsigned swapsize = header[3];
+	
+	if(DEBUG)
+		dbg_msg("datafile", "loading. size=%d", size);
+	
+	// TODO: use this variable for good and awesome
+	(void)swapsize;
+	
+	//map_unload();
+	datafile *df = (datafile*)mem_alloc(size+sizeof(datafile_info), 1);
+	unsigned readsize = file.read(&df->data, size);
+	if(readsize != size)
+	{
+		dbg_msg("datafile", "couldn't load the whole thing, wanted=%d got=%d", size, readsize);
+		return 0;
+	}
+	
+	// TODO: byteswap
+	//map->byteswap();
+
+	if(DEBUG)
+		dbg_msg("datafile", "item_size=%d", df->data.item_size);
+	
+	
+	df->info.item_types = (item_type *)df->data.start;
+	df->info.item_offsets = (int *)&df->info.item_types[df->data.num_item_types];
+	df->info.data_offsets = (int *)&df->info.item_offsets[df->data.num_items];
+	
+	df->info.item_start = (char *)&df->info.data_offsets[df->data.num_raw_data];
+	df->info.data_start = df->info.item_start + df->data.item_size;
+
+	if(DEBUG)
+		dbg_msg("datafile", "datafile loading done. datafile='%s'", filename);
+
+	if(DEBUG)
+	{
+		for(int i = 0; i < df->data.num_raw_data; i++)
+		{
+			void *p = datafile_get_data(df, i);
+			dbg_msg("datafile", "%d %d", (int)((char*)p - (char*)(&df->data)), size);
+		}
+			
+		for(int i = 0; i < datafile_num_items(df); i++)
+		{
+			int type, id;
+			void *data = datafile_get_item(df, i, &type, &id);
+			dbg_msg("map", "\t%d: type=%x id=%x p=%p offset=%d", i, type, id, data, df->info.item_offsets[i]);
+			int *idata = (int*)data;
+			for(int k = 0; k < 3; k++)
+				dbg_msg("datafile", "\t\t%d=%d (%x)", k, idata[k], idata[k]);
+		}
+
+		for(int i = 0; i < df->data.num_item_types; i++)
+		{
+			dbg_msg("map", "\t%d: type=%x start=%d num=%d", i,
+				df->info.item_types[i].type,
+				df->info.item_types[i].start,
+				df->info.item_types[i].num);
+			for(int k = 0; k < df->info.item_types[i].num; k++)
+			{
+				int type, id;
+				datafile_get_item(df, df->info.item_types[i].start+k, &type, &id);
+				if(type != df->info.item_types[i].type)
+					dbg_msg("map", "\tERROR");
+			}
+		}
+	}
+		
+	return df;
+}
+
+void *datafile_get_data(datafile *df, int index)
+{
+	return df->info.data_start+df->info.data_offsets[index];
+}
+
+void *datafile_get_item(datafile *df, int index, int *type, int *id)
+{
+	item *i = (item *)(df->info.item_start+df->info.item_offsets[index]);
+	if(type)
+		*type = (i->type_and_id>>16)&0xffff; // remove sign extention
+	if(id)
+		*id = i->type_and_id&0xffff;
+	return (void *)(i+1);
+}
+
+void datafile_get_type(datafile *df, int type, int *start, int *num)
+{
+	for(int i = 0; i < df->data.num_item_types; i++)
+	{
+		if(df->info.item_types[i].type == type)
+		{
+			*start = df->info.item_types[i].start;
+			*num = df->info.item_types[i].num;
+			return;
+		}
+	}
+	
+	*start = 0;
+	*num = 0;
+}
+
+void *datafile_find_item(datafile *df, int type, int id)
+{
+	int start, num;
+	datafile_get_type(df, type, &start, &num);
+	for(int i = 0; i < num; i++)
+	{
+		int item_id;
+		void *item = datafile_get_item(df, start+i,0, &item_id);
+		if(id == item_id)
+			return item;
+	}
+	return 0;
+}
+
+int datafile_num_items(datafile *df)
+{
+	return df->data.num_items;
+}
+
+void datafile_unload(datafile *df)
+{
+	if(df)
+		mem_free(df);
+}
+
+// DATAFILE output
+struct data_info
+{
+	int size;
+	void *data;
+};
+
+struct item_info
+{
+	int type;
+	int id;
+	int size;
+	int next;
+	int prev;
+	void *data;
+};
+
+struct itemtype_info
+{
+	int num;
+	int first;
+	int last;
+};
+
+//
+struct datafile_out
+{
+	baselib::file_stream file;
+	int num_items;
+	int num_datas;
+	int num_item_types;
+	itemtype_info item_types[0xffff];
+	item_info items[1024];
+	data_info datas[1024];
+};
+
+datafile_out *datafile_create(const char *filename)
+{
+	datafile_out *df = new datafile_out;
+	if(!df->file.open_w(filename))
+	{
+		delete df;
+		return 0;
+	}
+	
+	df->num_items = 0;
+	df->num_datas = 0;
+	df->num_item_types = 0;
+	mem_zero(&df->item_types, sizeof(df->item_types));
+
+	for(int i = 0; i < 0xffff; i++)
+	{
+		df->item_types[i].first = -1;
+		df->item_types[i].last = -1;
+	}
+	
+	return df;
+}
+
+int datafile_add_item(datafile_out *df, int type, int id, int size, void *data)
+{
+	df->items[df->num_items].type = type;
+	df->items[df->num_items].id = id;
+	df->items[df->num_items].size = size;
+	
+	// copy data
+	df->items[df->num_items].data = mem_alloc(size, 1);
+	mem_copy(df->items[df->num_items].data, data, size);
+
+	if(!df->item_types[type].num) // count item types
+		df->num_item_types++;
+
+	// link
+	df->items[df->num_items].prev = df->item_types[type].last;
+	df->items[df->num_items].next = -1;
+	
+	if(df->item_types[type].last != -1)
+		df->items[df->item_types[type].last].next = df->num_items;
+	df->item_types[type].last = df->num_items;
+	
+	if(df->item_types[type].first == -1)
+		df->item_types[type].first = df->num_items;
+	
+	df->item_types[type].num++;
+		
+	df->num_items++;
+	return df->num_items-1;
+}
+
+int datafile_add_data(datafile_out *df, int size, void *data)
+{
+	df->datas[df->num_items].size = size;
+	df->datas[df->num_datas].data = data;
+	df->num_datas++;
+	return df->num_datas-1;
+}
+
+int datafile_finish(datafile_out *df)
+{
+	// we should now write this file!
+	if(DEBUG)
+		dbg_msg("datafile", "writing");
+
+	// calculate sizes
+	int itemsize = 0;
+	for(int i = 0; i < df->num_items; i++)
+	{
+		if(DEBUG)
+			dbg_msg("datafile", "item=%d size=%d (%d)", i, df->items[i].size, df->items[i].size+sizeof(item));
+		itemsize += df->items[i].size + sizeof(item);
+	}
+	
+	int datasize = 0;
+	for(int i = 0; i < df->num_datas; i++)
+		datasize += df->datas[i].size;
+	
+	// calculate the complete size
+	int typessize = df->num_item_types*sizeof(item_type);
+	int headersize = sizeof(datafile_header);
+	int offsetsize = df->num_items*sizeof(int) + df->num_datas*sizeof(int);
+	int filesize = headersize + typessize + offsetsize + itemsize + datasize;
+	int swapsize = filesize - datasize;
+	
+	if(DEBUG)
+		dbg_msg("datafile", "num_item_types=%d typessize=%d itemsize=%d datasize=%d", df->num_item_types, typessize, itemsize, datasize);
+	
+	// construct header
+	datafile_header header;
+	header.id = ('D'<<24) | ('A'<<16) | ('T'<<8) | ('A');
+	header.version = 3;
+	header.size = filesize - 16;
+	header.swaplen = swapsize - 16;
+	header.num_item_types = df->num_item_types;
+	header.num_items = df->num_items;
+	header.num_raw_data = df->num_datas;
+	header.item_size = itemsize;
+	header.data_size = datasize;
+	
+	// TODO: apply swapping
+	// write header
+	if(DEBUG)
+		dbg_msg("datafile", "headersize=%d", sizeof(header));
+	df->file.write(&header, sizeof(header));
+	
+	// write types
+	for(int i = 0, count = 0; i < 0xffff; i++)
+	{
+		if(df->item_types[i].num)
+		{
+			// write info
+			item_type info;
+			info.type = i;
+			info.start = count;
+			info.num = df->item_types[i].num;
+			if(DEBUG)
+				dbg_msg("datafile", "writing type=%x start=%d num=%d", info.type, info.start, info.num);
+			df->file.write(&info, sizeof(info));
+			
+			count += df->item_types[i].num;
+		}
+	}
+	
+	// write item offsets
+	for(int i = 0, offset = 0; i < 0xffff; i++)
+	{
+		if(df->item_types[i].num)
+		{
+			// write all items in of this type
+			int k = df->item_types[i].first;
+			while(k != -1)
+			{
+				if(DEBUG)
+					dbg_msg("datafile", "writing item offset num=%d offset=%d", k, offset);
+				df->file.write(&offset, sizeof(offset));
+				offset += df->items[k].size + sizeof(item);
+				
+				// next
+				k = df->items[k].next;
+			}
+		}
+	}
+	
+	// write data offsets
+	for(int i = 0, offset = 0; i < df->num_datas; i++)
+	{
+		if(DEBUG)
+			dbg_msg("datafile", "writing data offset num=%d offset=%d", i, offset);
+		df->file.write(&offset, sizeof(offset));
+		offset += df->datas[i].size;
+	}
+	
+	// write items
+	for(int i = 0; i < 0xffff; i++)
+	{
+		if(df->item_types[i].num)
+		{
+			// write all items in of this type
+			int k = df->item_types[i].first;
+			while(k != -1)
+			{
+				item itm;
+				itm.type_and_id = (i<<16)|df->items[k].id;
+				itm.size = df->items[k].size;
+				if(DEBUG)
+					dbg_msg("datafile", "writing item type=%x idx=%d id=%d size=%d", i, k, df->items[k].id, df->items[k].size);
+				df->file.write(&itm, sizeof(itm));
+				df->file.write(df->items[k].data, df->items[k].size);
+				
+				// next
+				k = df->items[k].next;
+			}
+		}
+	}
+	
+	// write data
+	for(int i = 0; i < df->num_datas; i++)
+	{
+		if(DEBUG)
+			dbg_msg("datafile", "writing data id=%d size=%d", i, df->datas[i].size);
+		df->file.write(df->datas[i].data, df->datas[i].size);
+	}
+
+	// free data
+	for(int i = 0; i < df->num_items; i++)
+		mem_free(df->items[i].data);
+
+	
+	delete df;
+
+	if(DEBUG)
+		dbg_msg("datafile", "done");
+	return 0;
+}
diff --git a/src/engine/datafile.h b/src/engine/datafile.h
new file mode 100644
index 00000000..4e49fa03
--- /dev/null
+++ b/src/engine/datafile.h
@@ -0,0 +1,20 @@
+
+// raw datafile access
+struct datafile;
+
+// read access
+datafile *datafile_load(const char *filename);
+datafile *datafile_load_old(const char *filename);
+void *datafile_get_data(datafile *df, int index);
+void *datafile_get_item(datafile *df, int index, int *type, int *id);
+void datafile_get_type(datafile *df, int type, int *start, int *num);
+void *datafile_find_item(datafile *df, int type, int id);
+int datafile_num_items(datafile *df);
+void datafile_unload(datafile *df);
+
+// write access
+struct datafile_out;
+datafile_out *datafile_create(const char *filename);
+int datafile_add_data(datafile_out *df, int size, void *data);
+int datafile_add_item(datafile_out *df, int type, int id, int size, void *data);
+int datafile_finish(datafile_out *df);
diff --git a/src/engine/interface.h b/src/engine/interface.h
new file mode 100644
index 00000000..95ea0252
--- /dev/null
+++ b/src/engine/interface.h
@@ -0,0 +1,711 @@
+#ifndef FILE_INTERFACE_H
+#define FILE_INTERFACE_H
+
+/*
+	Title: Engine Interface
+*/
+
+// TODO: Move the definitions of these keys here
+#include <baselib/keys.h>
+
+enum
+{
+	MAX_CLIENTS=8,
+	SERVER_TICK_SPEED=50,
+	SERVER_CLIENT_TIMEOUT=5,
+	SNAP_CURRENT=0,
+	SNAP_PREV=1,
+};
+
+struct snap_item
+{
+	int type;
+	int id;
+};
+
+struct client_info
+{
+public:
+	const char *name;
+	int latency;
+};
+
+struct image_info
+{
+	int width, height;
+	void *data;
+};
+
+int gfx_load_tga(image_info *img, const char *filename);
+
+
+/*
+	Group: Graphics
+*/
+
+// graphics
+bool gfx_init(bool fullscreen); // NOT EXPOSED
+void gfx_shutdown(); // NOT EXPOSED
+void gfx_swap(); // NOT EXPOSED
+
+// textures
+/*
+	Function: gfx_load_texture_tga
+		Loads a TGA from file.
+	
+	Arguments:
+		filename - Null terminated string to the file to load.
+	
+	Returns:
+		An ID to the texture. -1 on failure.
+
+	See Also:
+		<gfx_unload_texture>
+*/
+int gfx_load_texture_tga(const char *filename);
+
+/*
+	Function: gfx_load_texture_raw
+		Loads a texture from memory.
+	
+	Arguments:
+		w - Width of the texture.
+		h - Height of the texture.
+		data - Pointer to the pixel data.
+	
+	Returns:
+		An ID to the texture. -1 on failure.
+		
+	Remarks:
+		The pixel data should be in RGBA format with 8 bit per component.
+		So the total size of the data should be w*h*4.
+		
+	See Also:
+		<gfx_unload_texture>
+*/
+int gfx_load_texture_raw(int w, int h, const void *data);
+
+/*
+	Function: gfx_texture_set
+		Sets the active texture.
+	
+	Arguments:
+		id - ID to the texture to set.
+*/
+void gfx_texture_set(int id);
+
+/*
+	Function: gfx_unload_texture
+		Unloads a texture.
+	
+	Arguments:
+		id - ID to the texture to unload.
+		
+	See Also:
+		<gfx_load_texture_tga>, <gfx_load_texture_raw>
+		
+	Remarks:
+		NOT IMPLEMENTED
+*/
+int gfx_unload_texture(int id); // NOT IMPLEMENTED
+
+void gfx_clear(float r, float g, float b);
+
+/*
+	Function: gfx_screenwidth
+		Returns the screen width.
+	
+	See Also:
+		<gfx_screenheight>
+*/
+int gfx_screenwidth();
+
+/*
+	Function: gfx_screenheight
+		Returns the screen height.
+	
+	See Also:
+		<gfx_screenwidth>
+*/
+int gfx_screenheight();
+
+/*
+	Function: gfx_mapscreen
+		Specifies the coordinate system for the screen.
+		
+	Arguments:
+		tl_x - Top-left X
+		tl_y - Top-left Y
+		br_x - Bottom-right X
+		br_y - Bottom-right y
+*/
+void gfx_mapscreen(float tl_x, float tl_y, float br_x, float br_y);
+
+/*
+	Function: gfx_blend_normal
+		Set the active blending mode to normal (src, 1-src).
+
+	Remarks:
+		This must be used before calling <gfx_quads_begin>.
+		This is equal to glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA).
+	
+	See Also:
+		<gfx_blend_additive>
+*/
+void gfx_blend_normal();
+
+/*
+	Function: gfx_blend_additive
+		Set the active blending mode to additive (src, one).
+
+	Remarks:
+		This must be used before calling <gfx_quads_begin>.
+		This is equal to glBlendFunc(GL_SRC_ALPHA, GL_ONE).
+	
+	See Also:
+		<gfx_blend_normal>
+*/
+void gfx_blend_additive();
+
+/*
+	Function: gfx_quads_begin
+		Begins a quad drawing session.
+		
+	Remarks:
+		This functions resets the rotation, color and subset.
+		End the session by using <gfx_quads_end>.
+		You can't change texture or blending mode during a session.
+		
+	See Also:
+		<gfx_quads_end>
+*/
+void gfx_quads_begin();
+
+/*
+	Function: gfx_quads_end
+		Ends a quad session.
+		
+	See Also:
+		<gfx_quads_begin>
+*/
+void gfx_quads_end();
+
+/*
+	Function: gfx_quads_setrotation
+		Sets the rotation to use when drawing a quad.
+		
+	Arguments:
+		angle - Angle in radians.
+		
+	Remarks:
+		The angle is reset when <gfx_quads_begin> is called.
+*/
+void gfx_quads_setrotation(float angle);
+
+/*
+	Function: gfx_quads_setcolorvertex
+		Sets the color of a vertex.
+		
+	Arguments:
+		i - Index to the vertex.
+		r - Red value.
+		g - Green value.
+		b - Blue value.
+		a - Alpha value.
+		
+	Remarks:
+		The color values are from 0.0 to 1.0.
+		The color is reset when <gfx_quads_begin> is called.
+*/
+void gfx_quads_setcolorvertex(int i, float r, float g, float b, float a);
+
+/*
+	Function: gfx_quads_setcolor
+		Sets the color of all the vertices.
+		
+	Arguments:
+		r - Red value.
+		g - Green value.
+		b - Blue value.
+		a - Alpha value.
+		
+	Remarks:
+		The color values are from 0.0 to 1.0.
+		The color is reset when <gfx_quads_begin> is called.
+*/
+void gfx_quads_setcolor(float r, float g, float b, float a);
+
+/*
+	Function: gfx_quads_setsubset
+		Sets the uv coordinates to use.
+		
+	Arguments:
+		tl_u - Top-left U value.
+		tl_v - Top-left V value.
+		br_u - Bottom-right U value.
+		br_v - Bottom-right V value.
+		
+	Remarks:
+		O,0 is top-left of the texture and 1,1 is bottom-right.
+		The color is reset when <gfx_quads_begin> is called.
+*/
+void gfx_quads_setsubset(float tl_u, float tl_v, float br_u, float br_v);
+
+/*
+	Function: gfx_quads_drawTL
+		Draws a quad by specifying the top-left point.
+		
+	Arguments:
+		x - X coordinate of the top-left corner.
+		y - Y coordinate of the top-left corner.
+		width - Width of the quad.
+		height - Height of the quad.
+		
+	Remarks:
+		Rotation still occurs from the center of the quad.
+		You must call <gfx_quads_begin> before calling this function.
+
+	See Also:
+		<gfx_quads_draw>
+*/
+void gfx_quads_drawTL(float x, float y, float width, float height);
+
+/*
+	Function: gfx_quads_draw
+		Draws a quad by specifying the center point.
+		
+	Arguments:
+		x - X coordinate of the center.
+		y - Y coordinate of the center.
+		width - Width of the quad.
+		height - Height of the quad.
+
+	Remarks:
+		You must call <gfx_quads_begin> before calling this function.
+
+	See Also:
+		<gfx_quads_drawTL>
+*/
+void gfx_quads_draw(float x, float y, float w, float h);
+
+void gfx_quads_draw_freeform(
+	float x0, float y0,
+	float x1, float y1,
+	float x2, float y2,
+	float x3, float y3);
+
+void gfx_quads_text(float x, float y, float size, const char *text);
+
+// sound (client)
+enum
+{
+	SND_PLAY_ONCE = 0,
+	SND_LOOP
+};
+	
+bool snd_init();
+int snd_load_wav(const char *filename);
+int snd_play(int sound, int loop = SND_PLAY_ONCE, float vol = 1.0f, float pan = 0.0f);
+void snd_stop(int id);
+void snd_set_vol(int id, float vol);
+bool snd_shutdown();
+
+/*
+	Group: Input
+*/
+
+/*
+	Function: inp_mouse_relative
+		Fetches the mouse movements.
+		
+	Arguments:
+		x - Pointer to the variable that should get the X movement.
+		y - Pointer to the variable that should get the Y movement.
+*/
+void inp_mouse_relative(int *x, int *y);
+
+/*
+	Function: inp_mouse_button_pressed
+		Checks if a mouse button is pressed.
+		
+	Arguments:
+		button - Index to the button to check.
+			* 0 - Left mouse button.
+			* 1 - Right mouse button.
+			* 2 - Middle mouse button.
+			* Others over 2 is undefined mouse buttons.
+			
+	Returns:
+		Returns 1 if the button is pressed, otherwise 0.
+*/
+int inp_mouse_button_pressed(int button);
+
+/*
+	Function: inp_key_pressed
+		Checks if a key is pressed.
+		
+	Arguments:
+		key - Index to the key to check
+		
+	Returns:
+		Returns 1 if the button is pressed, otherwise 0.
+	
+	Remarks:
+		Check baselib/include/baselib/keys.h for the keys.
+*/
+int inp_key_pressed(int key);
+
+/*
+	Group: Map
+*/
+
+int map_load(const char *mapname); // NOT EXPOSED
+void map_unload(); // NOT EXPOSED
+
+/*
+	Function: map_is_loaded
+		Checks if a map is loaded.
+		
+	Returns:
+		Returns 1 if the button is pressed, otherwise 0.
+*/
+int map_is_loaded();
+
+/*
+	Function: map_num_items
+		Checks the number of items in the loaded map.
+		
+	Returns:
+		Returns the number of items. 0 if no map is loaded.
+*/
+int map_num_items();
+
+/*
+	Function: map_find_item
+		Searches the map for an item.
+	
+	Arguments:
+		type - Item type.
+		id - Item ID.
+	
+	Returns:
+		Returns a pointer to the item if it exists, otherwise it returns NULL.
+*/
+void *map_find_item(int type, int id);
+
+/*
+	Function: map_get_item
+		Gets an item from the loaded map from index.
+	
+	Arguments:
+		index - Item index.
+		type - Pointer that recives the item type (can be NULL).
+		id - Pointer that recives the item id (can be NULL).
+	
+	Returns:
+		Returns a pointer to the item if it exists, otherwise it returns NULL.
+*/
+void *map_get_item(int index, int *type, int *id);
+
+/*
+	Function: map_get_type
+		Gets the index range of an item type.
+	
+	Arguments:
+		type - Item type to search for.
+		start - Pointer that recives the starting index.
+		num - Pointer that recives the number of items.
+	
+	Returns:
+		If the item type is not in the map, start and num will be set to 0.
+*/
+void map_get_type(int type, int *start, int *num);
+
+/*
+	Function: map_get_data
+		Fetches a pointer to a raw data chunk in the map.
+	
+	Arguments:
+		index - Index to the data to fetch.
+	
+	Returns:
+		A pointer to the raw data, otherwise 0.
+*/
+void *map_get_data(int index);
+
+/*
+	Group: Network (Server)
+*/
+/*
+	Function: snap_new_item
+		Creates a new item that should be sent.
+	
+	Arguments:
+		type - Type of the item.
+		id - ID of the item.
+		size - Size of the item.
+	
+	Returns:
+		A pointer to the item data, otherwise 0.
+	
+	Remarks:
+		The item data should only consist pf 4 byte integers as
+		they are subject to byte swapping. This means that the size
+		argument should be dividable by 4.
+*/
+void *snap_new_item(int type, int id, int size);
+
+/*
+	Group: Network (Client)
+*/
+/*
+	Function: snap_num_items
+		Check the number of items in a snapshot.
+	
+	Arguments:
+		snapid - Snapshot ID to the data to fetch.
+			* SNAP_PREV for previous snapshot.
+			* SNAP_CUR for current snapshot.
+	
+	Returns:
+		The number of items in the snapshot.
+*/
+int snap_num_items(int snapid);
+
+/*
+	Function: snap_get_item
+		Gets an item from a snapshot.
+	
+	Arguments:
+		snapid - Snapshot ID to the data to fetch.
+			* SNAP_PREV for previous snapshot.
+			* SNAP_CUR for current snapshot.
+		index - Index of the item.
+		item - Pointer that recives the item info.
+	
+	Returns:
+		Returns a pointer to the item if it exists, otherwise NULL.
+*/
+void *snap_get_item(int snapid, int index, snap_item *item);
+
+/*
+	Function: snap_find_item
+		Searches a snapshot for an item.
+	
+	Arguments:
+		snapid - Snapshot ID to the data to fetch.
+			* SNAP_PREV for previous snapshot.
+			* SNAP_CUR for current snapshot.
+		type - Type of the item.
+		id - ID of the item.
+	
+	Returns:
+		Returns a pointer to the item if it exists, otherwise NULL.
+*/
+void *snap_find_item(int snapid, int type, int id);
+
+/*
+	Function: snap_input
+		Sets the input data to send to the server.
+	
+	Arguments:
+		data - Pointer to the data.
+		size - Size of the data.
+
+	Remarks:
+		The data should only consist of 4 bytes integer as they are
+		subject to byte swapping.
+*/
+void snap_input(void *data, int size);
+
+/*
+	Function: snap_intratick
+		Returns the intra-tick mixing value.
+
+	Returns:
+		Returns the mixing value between the previous snapshot
+		and the current snapshot. 
+
+	Remarks:
+		DOCTODO: Explain how to use it.
+*/
+float snap_intratick();
+
+/*
+	Group: Server Callbacks
+*/
+/*
+	Function: mods_init
+		Called when the server is started.
+	
+	Remarks:
+		It's called after the map is loaded so all map items are available.
+*/
+void mods_init();
+
+/*
+	Function: mods_shutdown
+		Called when the server quits.
+	
+	Remarks:
+		Should be used to clean up all resources used.
+*/
+void mods_shutdown();
+
+/*
+	Function: mods_client_enter
+		Called when a client has joined the game.
+		
+	Arguments:
+		cid - Client ID. Is 0 - MAX_CLIENTS.
+	
+	Remarks:
+		It's called when the client is finished loading and should enter gameplay.
+*/
+void mods_client_enter(int cid);
+
+/*
+	Function: mods_client_drop
+		Called when a client drops from the server.
+
+	Arguments:
+		cid - Client ID. Is 0 - MAX_CLIENTS
+*/
+void mods_client_drop(int cid);
+
+/*
+	Function: mods_client_input
+		Called when the server recives new input from a client.
+
+	Arguments:
+		cid - Client ID. Is 0 - MAX_CLIENTS.
+		input - Pointer to the input data.
+		size - Size of the data. (NOT IMPLEMENTED YET)
+*/
+void mods_client_input(int cid, void *input);
+
+/*
+	Function: mods_tick
+		Called with a regular interval to progress the gameplay.
+		
+	Remarks:
+		The SERVER_TICK_SPEED tells the number of ticks per second.
+*/
+void mods_tick();
+
+/*
+	Function: mods_presnap
+		Called before the server starts to construct snapshots for the clients.
+*/
+void mods_presnap();
+
+/*
+	Function: mods_snap
+		Called to create the snapshot for a client.
+	
+	Arguments:
+		cid - Client ID. Is 0 - MAX_CLIENTS.
+	
+	Remarks:
+		The game should make a series of calls to <snap_new_item> to construct
+		the snapshot for the client.
+*/
+void mods_snap(int cid);
+
+/*
+	Function: mods_postsnap
+		Called after the server is done sending the snapshots.
+*/
+void mods_postsnap();
+
+/*
+	Group: Client Callbacks
+*/
+/*
+	Function: modc_init
+		Called when the client starts.
+	
+	Remarks:
+		The game should load resources that are used during the entire
+		time of the game. No map is loaded.
+*/
+void modc_init();
+
+/*
+	Function: modc_newsnapshot
+		Called when the client progressed to a new snapshot.
+	
+	Remarks:
+		The client can check for items in the snapshot and perform one time
+		events like playing sounds, spawning client side effects etc.
+*/
+void modc_newsnapshot();
+
+/*
+	Function: modc_entergame
+		Called when the client has successfully connect to a server and
+		loaded a map.
+	
+	Remarks:
+		The client can check for items in the map and load them.
+*/
+void modc_entergame();
+
+/*
+	Function: modc_shutdown
+		Called when the client closes down.
+*/
+void modc_shutdown();
+
+/*
+	Function: modc_render
+		Called every frame to let the game render it self.
+*/
+void modc_render();
+
+
+
+/*
+    Group: Menu Callbacks
+*/
+/*
+    Function: modmenu_init
+        Called when the menu starts.
+    
+    Remarks:
+        The menu should load resources that are used during the entire
+        time of the menu use.
+*/
+void modmenu_init();
+
+/*
+    Function: modmenu_shutdown
+        Called when the menu closes down.
+*/
+void modmenu_shutdown();
+
+/*
+    Function: modmenu_render
+        Called every frame to let the menu render it self.
+*/
+int modmenu_render(void *server_address, char *name, int max_len);
+
+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();
+int server_tickspeed();
+
+int inp_key_was_pressed(int key);
+int inp_key_down(int key);
+void inp_update();
+float client_frametime();
+float client_localtime();
+
+#define MASTER_SERVER_ADDRESS "master.teewars.com"
+#define MASTER_SERVER_PORT 8300
+
+
+
+#endif
diff --git a/src/engine/lzw.cpp b/src/engine/lzw.cpp
new file mode 100644
index 00000000..80dd1c22
--- /dev/null
+++ b/src/engine/lzw.cpp
@@ -0,0 +1,223 @@
+#include <string.h>
+
+// LZW Compressor
+struct SYM
+{
+	unsigned char *data;
+	int size;
+	int next;
+};
+
+struct SYMBOLS
+{
+	SYM syms[512];
+	int jumptable[256];
+	int currentsym;
+};
+
+static SYMBOLS symbols;
+
+// symbol info
+inline int sym_size(int i) { return symbols.syms[i].size; }
+inline unsigned char *sym_data(int i) { return symbols.syms[i].data; }
+
+static void sym_index(int sym)
+{
+	int table = symbols.syms[sym].data[0];
+	symbols.syms[sym].next = symbols.jumptable[table];
+	symbols.jumptable[table] = sym;
+}
+
+static void sym_unindex(int sym)
+{
+	int table = symbols.syms[sym].data[0];
+	int prev = -1;
+	int current = symbols.jumptable[table];
+
+	while(current != -1)
+	{
+		if(current == sym)
+		{
+			if(prev != -1)
+				symbols.syms[prev].next = symbols.syms[current].next;
+			else
+				symbols.jumptable[table] = symbols.syms[current].next;
+			break;
+		}
+
+		prev = current;
+		current = symbols.syms[current].next;
+	}
+}
+
+static int sym_add(unsigned char *sym, long len)
+{
+	int i = 256+symbols.currentsym;
+	symbols.syms[i].data = sym;
+	symbols.syms[i].size = len;
+	symbols.currentsym = (symbols.currentsym+1)%255;
+	return i;
+}
+
+static int sym_add_and_index(unsigned char *sym, long len)
+{
+	if(symbols.syms[256+symbols.currentsym].size)
+		sym_unindex(256+symbols.currentsym);
+	int s = sym_add(sym, len);
+	sym_index( s);
+	return s;
+}
+
+static void sym_init()
+{
+	static unsigned char table[256];
+	for(int i = 0; i < 256; i++)
+	{
+		table[i] = i;
+		symbols.syms[i].data = &table[i];
+		symbols.syms[i].size = 1;
+		symbols.jumptable[i] = -1;
+	}
+
+	for(int i = 0; i < 512; i++)
+		symbols.syms[i].next = -1;
+
+	/*
+	// insert some symbols to start with
+	static unsigned char zeros[8] = {0,0,0,0,0,0,0,0};
+	//static unsigned char one1[4] = {0,0,0,1};
+	//static unsigned char one2[4] = {1,0,0,0};
+	sym_add_and_index(zeros, 2);
+	sym_add_and_index(zeros, 3);
+	sym_add_and_index(zeros, 4);
+	sym_add_and_index(zeros, 5);
+	sym_add_and_index(zeros, 6);
+	sym_add_and_index(zeros, 7);
+	sym_add_and_index(zeros, 8);
+	
+	//sym_add_and_index(one1, 4);
+	//sym_add_and_index(one2, 4);*/
+
+	symbols.currentsym = 0;
+}
+
+static int sym_find(unsigned char *data, int size, int avoid)
+{
+	int best = data[0];
+	int bestlen = 1;
+	int current = symbols.jumptable[data[0]];
+
+	while(current != -1)
+	{
+		if(current != avoid && symbols.syms[current].size <= size && memcmp(data, symbols.syms[current].data, symbols.syms[current].size) == 0)
+		{
+			if(bestlen < symbols.syms[current].size)
+			{
+				bestlen = symbols.syms[current].size;
+				best = current;
+			}
+		}
+
+		current = symbols.syms[current].next;
+	}		
+
+	return best;
+}
+
+//
+// compress
+//
+long lzw_compress(const void *src_, int size, void *dst_)
+{
+	unsigned char *src = (unsigned char *)src_;
+	unsigned char *end = (unsigned char *)src_+size;
+	unsigned char *dst = (unsigned char *)dst_;
+	long left = (end-src);
+	int lastsym = -1;
+
+	// init symboltable
+	sym_init();
+
+	bool done = false;
+	while(!done)
+	{
+		unsigned char *flagptr = dst;
+		unsigned char flagbits = 0;
+		int b = 0;
+
+		dst++; // skip a byte where the flags are
+
+		for(; b < 8; b++)
+		{
+			if(left <= 0) // check for EOF
+			{
+				// write EOF symbol
+				flagbits |= 1<<b;
+				*dst++ = 255;
+				done = true;
+				break;
+			}
+
+ 			int sym = sym_find(src, left, lastsym);
+			int symsize = sym_size( sym);
+
+			if(sym&0x100)
+				flagbits |= 1<<b; // add bit that says that its a symbol
+
+			*dst++ = sym&0xff; // set symbol
+
+			if(left > symsize+1) // create new symbol
+				lastsym = sym_add_and_index(src, symsize+1);
+			
+			src += symsize; // advance src
+			left -= symsize;
+		}
+
+		// write the flags
+		*flagptr = flagbits;
+	}
+
+	return (long)(dst-(unsigned char*)dst_);
+}
+
+//
+// decompress
+//
+long lzw_decompress(const void *src_, void *dst_)
+{
+	unsigned char *src = (unsigned char *)src_;
+	unsigned char *dst = (unsigned char *)dst_;
+	unsigned char *prevdst = 0;
+	int prevsize = -1;
+	int item;
+
+	sym_init();
+
+	while(1)
+	{
+		unsigned char flagbits = 0;
+		flagbits = *src++; // read flags
+
+		int b = 0;
+		for(; b < 8; b++)
+		{
+			item = *src++;
+			if(flagbits&(1<<b))
+				item |= 256;
+
+			if(item == 0x1ff) // EOF symbol
+				return (dst-(unsigned char *)dst_);
+
+			if(prevdst) // this one could be removed
+				sym_add(prevdst, prevsize+1);
+				
+			memcpy(dst, sym_data(item), sym_size(item));
+			prevdst = dst;
+			prevsize = sym_size(item);
+			dst += sym_size(item);
+		}
+
+	}
+
+	return 0;
+}
diff --git a/src/engine/lzw.h b/src/engine/lzw.h
new file mode 100644
index 00000000..af29665e
--- /dev/null
+++ b/src/engine/lzw.h
@@ -0,0 +1,2 @@
+long lzw_compress(const void *src, int size, void *dst);
+long lzw_decompress(const void *src, void *dst);
diff --git a/src/engine/map.cpp b/src/engine/map.cpp
new file mode 100644
index 00000000..3e76547e
--- /dev/null
+++ b/src/engine/map.cpp
@@ -0,0 +1,48 @@
+#include <baselib/system.h>
+#include <baselib/stream/file.h>
+
+#include "datafile.h"
+
+static datafile *map;
+
+void *map_get_data(int index)
+{
+	return datafile_get_data(map, index);
+}
+
+void *map_get_item(int index, int *type, int *id)
+{
+	return datafile_get_item(map, index, type, id);
+}
+
+void map_get_type(int type, int *start, int *num)
+{
+	datafile_get_type(map, type, start, num);
+}
+
+void *map_find_item(int type, int id)
+{
+	return datafile_find_item(map, type, id);
+}
+
+int map_num_items()
+{
+	return datafile_num_items(map);
+}
+
+void map_unload()
+{
+	datafile_unload(map);
+	map = 0x0;
+}
+
+int map_is_loaded()
+{
+	return map != 0;
+}
+
+int map_load(const char *mapname)
+{
+	map = datafile_load(mapname);
+	return map != 0;
+}
diff --git a/src/engine/packet.h b/src/engine/packet.h
new file mode 100644
index 00000000..6dc99043
--- /dev/null
+++ b/src/engine/packet.h
@@ -0,0 +1,288 @@
+#include <baselib/stream/file.h>
+#include <baselib/network.h>
+
+// TODO: this is not KISS
+class packet
+{
+protected:
+	enum
+	{
+		MAX_PACKET_SIZE = 1024,
+	};
+	
+	// packet data
+	struct header
+	{
+		unsigned msg;
+		unsigned ack;
+		unsigned seq;
+	};
+	
+	unsigned char packet_data[MAX_PACKET_SIZE];
+	unsigned char *current;
+	
+	// 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
+	{
+		DEBUG_TYPE_INT=0x1,
+		DEBUG_TYPE_STR=0x2,
+		DEBUG_TYPE_RAW=0x3,
+	};
+	
+	// writes an int to the packet
+	void write_int_raw(int i)
+	{
+		// TODO: check for overflow
+		*(int*)current = i;
+		current += sizeof(int);
+	}
+
+	// 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)
+	{
+		write_int_raw((type<<16)|size);
+	}
+	
+	void debug_verify_mark(int type, int size)
+	{
+		if(read_int_raw() != ((type<<16) | size))
+			dbg_assert(0, "error during packet disassembly");
+	}
+	
+public:
+	packet(unsigned msg=0)
+	{
+		current = packet_data;
+		current += sizeof(header);
+		((header*)packet_data)->msg = msg;
+	}
+	
+	void set_header(unsigned ack, unsigned seq)
+	{
+		((header*)packet_data)->ack = ack;
+		((header*)packet_data)->seq = seq;
+	}
+	
+	// writes an int to the packet
+	void write_int(int i)
+	{
+		debug_insert_mark(DEBUG_TYPE_INT, 4);
+		write_int_raw(i);
+	}
+
+	void write_raw(const char *raw, int size)
+	{
+		debug_insert_mark(DEBUG_TYPE_RAW, size);
+		while(size--)
+			*current++ = *raw++;
+	}
+
+	// writes a string to the packet
+	void write_str(const char *str)
+	{
+		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++;
+	}
+	
+	// reads an int from the packet
+	int read_int()
+	{
+		debug_verify_mark(DEBUG_TYPE_INT, 4);
+		return read_int_raw();
+	}
+	
+	// reads a string from the packet
+	const char *read_str()
+	{
+		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;
+	}
+	
+	const char *read_raw(int size)
+	{
+		debug_verify_mark(DEBUG_TYPE_RAW, size);
+		const char *d = (const char *)current;
+		current += size;
+		return d;
+	}
+	
+	// TODO: impelement this
+	bool is_good() const { return true; }
+
+	unsigned msg() const { return ((header*)packet_data)->msg; }
+	unsigned seq() const { return ((header*)packet_data)->seq; }
+	unsigned ack() const { return ((header*)packet_data)->ack; }
+	
+	// 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; }
+};
+
+//
+class connection
+{
+	baselib::socket_udp4 *socket;
+	baselib::netaddr4 addr;
+	unsigned seq;
+	unsigned ack;
+	unsigned last_ack;
+	
+	unsigned counter_sent_bytes;
+	unsigned counter_recv_bytes;
+	
+public:
+	void counter_reset()
+	{
+		counter_sent_bytes = 0;
+		counter_recv_bytes = 0;
+	}
+	
+	void counter_get(unsigned *sent, unsigned *recved)
+	{
+		*sent = counter_sent_bytes;
+		*recved = counter_recv_bytes;
+	}
+
+	void init(baselib::socket_udp4 *socket, const baselib::netaddr4 *addr)
+	{
+		this->addr = *addr;
+		this->socket = socket;
+		last_ack = 0;
+		ack = 0;
+		seq = 0;
+		counter_reset();
+	}
+	
+	void send(packet *p)
+	{
+		if(p->msg()&(31<<1))
+		{
+			// vital packet
+			seq++;
+			// TODO: save packet, we might need to retransmit
+		}
+		
+		p->set_header(ack, seq);
+		socket->send(&address(), p->data(), p->size());
+		counter_sent_bytes += p->size();
+	}
+	
+	packet *feed(packet *p)
+	{
+		counter_recv_bytes += p->size();
+		
+		if(p->msg()&(31<<1))
+		{
+			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)
+			{
+				// TODO: request resend
+				// packet loss
+				dbg_msg("network/connection", "packet loss! seq=%x ack=%x+1", p->seq(), ack);
+				return p;
+			}
+			else
+			{
+				// we already got this packet
+				return 0x0;
+			}
+		}
+		
+		if(last_ack != p->ack())
+		{
+			// TODO: remove acked packets
+		}
+		
+		return p;		
+	}
+	
+	const baselib::netaddr4 &address() const { return addr; }
+	
+	void update()
+	{
+	}
+};
+
+//const char *NETWORK_VERSION = "development";
+
+enum
+{
+	NETMSG_VITAL=0x80000000,
+	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_VITAL|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|NETMSG_VITAL|2,
+	NETMSG_CLIENT_EVENT = NETMSG_CONTEXT_GAME|NETMSG_VITAL|2,
+
+	NETMSG_CLIENT_CHECKALIVE = NETMSG_CONTEXT_GAME|NETMSG_VITAL|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/server/server.cpp b/src/engine/server/server.cpp
new file mode 100644
index 00000000..d9151a43
--- /dev/null
+++ b/src/engine/server/server.cpp
@@ -0,0 +1,5 @@
+#include <stdio.h>
#include <string.h>

#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/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;
	}
	
	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;
	}
};

static snapshot_builder builder;

void *snap_new_item(int type, int id, int size)
{
	dbg_assert(type >= 0 && type <=0xffff, "incorrect type");
	dbg_assert(id >= 0 && id <=0xffff, "incorrect id");
	return builder.new_item(type, id, size);
}


//
class client
{
public:
	enum
	{
		STATE_EMPTY = 0,
		STATE_CONNECTING = 1,
		STATE_INGAME = 2,
	};

	// connection state info
	int state;

	// (ticks) if lastactivity > 5 seconds kick him
	int64 lastactivity;
	connection conn;
	
	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;

int server_tick()
{
	return current_tick;
}

int server_tickspeed()
{
	return 50;
}

int server_init()
{
	for(int i = 0; i < MAX_CLIENTS; i++)
	{
		clients[i].state = client::STATE_EMPTY;
		clients[i].name[0] = 0;
		clients[i].clan[0] = 0;
		clients[i].lastactivity = 0;
	}
	
	current_tick = 0;
	
	return 0;
}

int server_getclientinfo(int client_id, client_info *info)
{
	dbg_assert(client_id >= 0 && client_id < MAX_CLIENTS, "client_id is not valid");
	dbg_assert(info != 0, "info can not be null");
	
	if(clients[client_id].is_ingame())
	{
		info->name = clients[client_id].name;
		info->latency = 0;
		return 1;
	}
	return 0;
}

//
class server
{
public:

	socket_udp4 game_socket;
+
+	const char *map_name;
	const char *server_name;
	int64 lasttick;
	int64 lastheartbeat;
	netaddr4 master_server;

	int biggest_snapshot;
	
	bool run(const char *servername, const char *mapname)
	{
		biggest_snapshot = 0;
		
		net_init(); // For Windows compatibility.
		map_name = mapname;
		server_name = servername;

		// load map
		if(!map_load(mapname))
		{
			dbg_msg("server", "failed to load map. mapname='%s'");
			return false;
		}
		
		// start server
		if(!game_socket.open(8303))
		{
			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);

		if (net_host_lookup(MASTER_SERVER_ADDRESS, MASTER_SERVER_PORT, &master_server) != 0)
		{
			// TODO: fix me
			//master_server = netaddr4(0, 0, 0, 0, 0);
		}
		
		mods_init();
		
		int64 time_per_tick = time_freq()/SERVER_TICK_SPEED;
		int64 time_per_heartbeat = time_freq() * 30;
		int64 starttime = time_get();
		//int64 lasttick = starttime;
		lasttick = starttime;
		lastheartbeat = 0;

		int64 reporttime = time_get();
		int64 reportinterval = time_freq()*3;
		
		int64 simulationtime = 0;
		int64 snaptime = 0;
		int64 networktime = 0;
		
		while(1)
		{
			int64 t = time_get();
			if(t-lasttick > time_per_tick)
			{
				{
					int64 start = time_get();
					tick();
					simulationtime += time_get()-start;
				}
				
				{
					int64 start = time_get();
					snap();
					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;
			}

			if(send_heartbeats)
			{
				if (t > lastheartbeat+time_per_heartbeat)
				{
					if (master_server.port != 0)
					{
						int players = 0;
						
						for (int i = 0; i < MAX_CLIENTS; i++)
							if (!clients[i].is_empty())
								players++;
						
						// TODO: fix me
						netaddr4 me(127, 0, 0, 0, 8303);
						
						send_heartbeat(0, &me, players, MAX_CLIENTS, server_name, mapname);
					}

					lastheartbeat = t+time_per_heartbeat;
				}
			}
			
			{
				int64 start = time_get();
				pump_network();
				networktime += time_get()-start;
			}
			
			if(reporttime < time_get())
			{
				int64 totaltime = simulationtime+snaptime+networktime;
				dbg_msg("server/report", "sim=%.02fms snap=%.02fms net=%.02fms total=%.02fms load=%.02f%%",
					simulationtime/(float)reportinterval*1000,
					snaptime/(float)reportinterval*1000,
					networktime/(float)reportinterval*1000,
					totaltime/(float)reportinterval*1000,
					(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())
					{
						unsigned s,r;
						clients[i].conn.counter_get(&s,&r);
						clients[i].conn.counter_reset();
						sent_total += s;
						recv_total += r;
					}

				
				dbg_msg("server/report", "biggestsnap=%d send=%d recv=%d",
					biggest_snapshot, sent_total/3, recv_total/3);

				simulationtime = 0;
				snaptime = 0;
				networktime = 0;
				
				reporttime += reportinterval;
			}
			
			thread_sleep(1);
		}
		
		mods_shutdown();
		map_unload();
	}
	
	void tick()
	{
		current_tick++;
		mods_tick();
	}

	void snap()
	{
		mods_presnap();
	
		for(int i = 0; i < MAX_CLIENTS; i++)
		{
			if(clients[i].is_ingame())
			{
				char data[MAX_SNAPSHOT_SIZE];
				char compdata[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++)
				{
					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);
				}
			}
		}
		
		mods_postsnap();
	}	

	void send_accept(client *client, const char *map)
	{
		packet p(NETMSG_SERVER_ACCEPT);
		p.write_str(map);
		client->conn.send(&p);
	}
	
	void drop(int cid, const char *reason)
	{
		if(clients[cid].state == client::STATE_EMPTY)
			return;
		
		clients[cid].state = client::STATE_EMPTY;
		mods_client_drop(cid);
		dbg_msg("game", "player dropped. reason='%s' cid=%x name='%s'", reason, cid, clients[cid].name);
	}
	
	int find_client(const netaddr4 *addr)
	{
		// fetch client
		for(int i = 0; i < MAX_CLIENTS; i++)
		{
			if(!clients[i].is_empty() && clients[i].address() == *addr)
				return i;
		}		
		return -1;
	}
	
	void client_process_packet(int cid, packet *p)
	{
		clients[cid].lastactivity = lasttick;
		if(p->msg() == NETMSG_CLIENT_DONE)
		{
			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)
		{
			int input[MAX_INPUT_SIZE];
			int size = p->read_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);
			}
		}
		else if(p->msg() == NETMSG_CLIENT_ERROR)
		{
			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");
		}
		else
		{
			dbg_msg("network/server", "invalid message. cid=%x msg=%x", cid, p->msg());
			drop(cid, "invalid message");
		}
	}
	
	void process_packet(packet *p, netaddr4 *from)
	{
		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)
	{
		drop(clientId, "client timedout");
	}
	
	void pump_network()
	{
		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)
				break;
			
			process_packet(&p, &from);
		}
		// 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));
	}
};

int main(int argc, char **argv)
{
	dbg_msg("server", "starting...");
	
	const char *mapname = "data/demo.map";
	const char *servername = 0;
	// parse arguments
	for(int i = 1; i < argc; i++)
	{
		if(argv[i][0] == '-' && argv[i][1] == 'm' && argv[i][2] == 0 && argc - i > 1)
		{
			// -m map
			i++;
			mapname = argv[i];
		}
		else if(argv[i][0] == '-' && argv[i][1] == 'n' && argv[i][2] == 0 && argc - i > 1)
		{
			// -n server name
			i++;
			servername = argv[i];
		}
		else if(argv[i][0] == '-' && argv[i][1] == 'p' && argv[i][2] == 0)
		{
			// -p (private server)
			send_heartbeats = 0;
		}
	}
	
	if(!mapname)
	{
		dbg_msg("server", "no map given (-m MAPNAME)");
		return 0;
	}

	if(!servername)
	{
		dbg_msg("server", "no server name given (-n \"server name\")");
		return 0;
	}
		
	server_init();
	server s;
	s.run(servername, mapname);
	return 0;
}
\ No newline at end of file
diff --git a/src/engine/snapshot.h b/src/engine/snapshot.h
new file mode 100644
index 00000000..9d803486
--- /dev/null
+++ b/src/engine/snapshot.h
@@ -0,0 +1,19 @@
+
+struct snapshot
+{
+	int num_items;
+	int offsets[1];
+
+	struct item
+	{
+		int type_and_id;
+		char data[1];
+		
+		int type() { return type_and_id>>16; }
+		int id() { return type_and_id&(0xffff); }
+	};
+	
+	char *data_start() { return (char *)&offsets[num_items]; }
+	item *get_item(int index) { return (item *)(data_start() + offsets[index]); };
+};
+
diff --git a/src/engine/versions.h b/src/engine/versions.h
new file mode 100644
index 00000000..d70ee721
--- /dev/null
+++ b/src/engine/versions.h
@@ -0,0 +1,2 @@
+#define TEEWARS_NETVERSION "dev v2"
+#define TEEWARS_VERSION "0.2.1-dev"