about summary refs log tree commit diff
diff options
context:
space:
mode:
authorMagnus Auvinen <magnus.auvinen@gmail.com>2007-07-14 13:09:42 +0000
committerMagnus Auvinen <magnus.auvinen@gmail.com>2007-07-14 13:09:42 +0000
commit568b9f1f4a4f6cd269f026c10117d76329b44dd8 (patch)
treefe18d2ceb622c3b4c1f631b6736102c89927a330
parent7acef9eb864344b26cb78f22da369851cc31aa76 (diff)
downloadzcatch-568b9f1f4a4f6cd269f026c10117d76329b44dd8.tar.gz
zcatch-568b9f1f4a4f6cd269f026c10117d76329b44dd8.zip
added chat, better damage indicators
-rw-r--r--data/game_main.pngbin20039 -> 23881 bytes
-rw-r--r--datasrc/teewars.ds6
-rw-r--r--docs/articles/tasks.txt32
-rw-r--r--src/engine/client/client.cpp242
-rw-r--r--src/engine/client/gfx.cpp2
-rw-r--r--src/engine/interface.h10
-rw-r--r--src/engine/msg.cpp19
-rw-r--r--src/engine/server/server.cpp90
-rw-r--r--src/game/client/game_client.cpp346
-rw-r--r--src/game/game.h19
-rw-r--r--src/game/server/game_server.cpp158
11 files changed, 649 insertions, 275 deletions
diff --git a/data/game_main.png b/data/game_main.png
index 188068f3..a95fd69e 100644
--- a/data/game_main.png
+++ b/data/game_main.png
Binary files differdiff --git a/datasrc/teewars.ds b/datasrc/teewars.ds
index 7e083d62..45df436c 100644
--- a/datasrc/teewars.ds
+++ b/datasrc/teewars.ds
@@ -252,9 +252,6 @@ sprites {
 		part7 4 2 2 2
 		part8 6 2 2 2
 		part9 8 2 2 2
-		
-		star1 0 0 2 2
-		star2 0 2 2 2
 	}
 	
 	hud images.game 32 16 {
@@ -262,6 +259,9 @@ sprites {
 		health_empty 5 0 4 4
 		armor_full 0 5 4 4
 		armor_empty 5 5 4 4
+		star1 0 10 3 3
+		star2 3 10 3 3
+		star3 6 10 3 3
 	}
 	
 	weapons images.weapons 32 32 {
diff --git a/docs/articles/tasks.txt b/docs/articles/tasks.txt
index 1a471d2b..bbf3969c 100644
--- a/docs/articles/tasks.txt
+++ b/docs/articles/tasks.txt
@@ -6,30 +6,38 @@ Group: 0.1.1 - Awesome Alpha Bug Fixed (NEXT)
 	DONE * Bobbing powerups
 	DONE * Get -c working under windows
 	DONE * Bigger powerups
-* Pickup sounds
-* FAQ on the webpage
 	DONE * Private server (no heartbeats)
 	DONE * Can't pickup ammo, health or armor when not needed
 	DONE * Fix so that the homepage is in the SVN
 
 Group: 0.2.x - 
-* Cleanup: SVN
 * Cleanup: client.cpp / server.cpp
-* Cleanup: game_client.cpp / game_server.cpp
 * Cleanup: editor.cpp
-* Masterserver: Master server should only hold ips. Clients should ask servers for more info + ping
-* Masterserver: NAT/FW detection
-* Game: Respawn time
-* Game: Chat
-* Game: Bigger and better scoreboard
+
+* Web: FAQ on the webpage
 * Game: Events for kills and such
-	DONE * Client: Only send input when needed
-* Client: Should timeout from server
+* Game: Pickup sounds
+* Game: Add the sounds
+* Game: Fix the hammer again
+* Game: Tweak graphics
+
 * Client: More robust and smoother handling of snapshots
-* Client: Some sort of settings format (think KISS).
+	* Need to add some smoothed timer or something
+	
+* Masterserver: Master server should only hold ips. Clients should ask servers for more info + ping
+* Masterserver: NAT/FW detection
 * Server: LAN / Internet / Private servers.
 * Server: Answer to a getinfo message
 
+DONE * Game: Chat
+DONE * Game: Bigger and better scoreboard
+DONE * Cleanup: SVN
+DONE * Cleanup: game_client.cpp / game_server.cpp
+DONE * Game: Respawn time
+DONE * Client: Only send input when needed
+DONE * Client: Should timeout from server
+DONE * Client: Some sort of settings format (think KISS).
+
 Group: 0.3.0 - Editor Edition
 * Fix the editor
 
diff --git a/src/engine/client/client.cpp b/src/engine/client/client.cpp
index 4fb869cd..04c25bba 100644
--- a/src/engine/client/client.cpp
+++ b/src/engine/client/client.cpp
@@ -194,7 +194,7 @@ public:
 	{
 		recived_snapshots = 0;
 
-		msg_pack_start(NETMSG_INFO, MSGFLAG_VITAL);
+		msg_pack_start_system(NETMSG_INFO, MSGFLAG_VITAL);
 		msg_pack_string(config.player_name, 128);
 		msg_pack_string(config.clan_name, 128);
 		msg_pack_string(config.password, 128);
@@ -205,7 +205,7 @@ public:
 
 	void send_entergame()
 	{
-		msg_pack_start(NETMSG_ENTERGAME, MSGFLAG_VITAL);
+		msg_pack_start_system(NETMSG_ENTERGAME, MSGFLAG_VITAL);
 		msg_pack_end();
 		client_send_msg();
 	}
@@ -226,7 +226,7 @@ public:
 
 	void send_input()
 	{
-		msg_pack_start(NETMSG_INPUT, 0);
+		msg_pack_start_system(NETMSG_INPUT, 0);
 		msg_pack_int(input_data_size);
 		for(int i = 0; i < input_data_size/4; i++)
 			msg_pack_int(input_data[i]);
@@ -462,135 +462,145 @@ public:
 
 	void process_packet(NETPACKET *packet)
 	{
-		int msg = msg_unpack_start(packet->data, packet->data_size);
-		if(msg == NETMSG_MAP)
+		int sys;
+		int msg = msg_unpack_start(packet->data, packet->data_size, &sys);
+		if(sys)
 		{
-			const char *map = msg_unpack_string();
-			dbg_msg("client/network", "connection accepted, map=%s", map);
-			set_state(STATE_LOADING);
-			
-			if(map_load(map))
-			{
-				modc_entergame();
-				send_entergame();
-				dbg_msg("client/network", "loading done");
-				// now we will wait for two snapshots
-				// to finish the connection
-			}
-			else
-			{
-				error("failure to load map");
-			}
-		}
-		else if(msg == NETMSG_SNAP || msg == NETMSG_SNAPSMALL || msg == NETMSG_SNAPEMPTY)
-		{
-			//dbg_msg("client/network", "got snapshot");
-			int game_tick = msg_unpack_int();
-			int delta_tick = game_tick-msg_unpack_int();
-			int num_parts = 1;
-			int part = 0;
-			int part_size = 0;
-			
-			if(msg == NETMSG_SNAP)
+			// system message
+			if(msg == NETMSG_MAP)
 			{
-				num_parts = msg_unpack_int();
-				part = msg_unpack_int();
+				const char *map = msg_unpack_string();
+				dbg_msg("client/network", "connection accepted, map=%s", map);
+				set_state(STATE_LOADING);
+				
+				if(map_load(map))
+				{
+					modc_entergame();
+					send_entergame();
+					dbg_msg("client/network", "loading done");
+					// now we will wait for two snapshots
+					// to finish the connection
+				}
+				else
+				{
+					error("failure to load map");
+				}
 			}
-			
-			if(msg != NETMSG_SNAPEMPTY)
-				part_size = msg_unpack_int();
-			
-			if(snapshot_part == part)
+			else if(msg == NETMSG_SNAP || msg == NETMSG_SNAPSMALL || msg == NETMSG_SNAPEMPTY)
 			{
-				// TODO: clean this up abit
-				const char *d = (const char *)msg_unpack_raw(part_size);
-				mem_copy((char*)snapshots[SNAP_INCOMMING] + part*MAX_SNAPSHOT_PACKSIZE, d, part_size);
-				snapshot_part++;
-			
-				if(snapshot_part == num_parts)
+				//dbg_msg("client/network", "got snapshot");
+				int game_tick = msg_unpack_int();
+				int delta_tick = game_tick-msg_unpack_int();
+				int num_parts = 1;
+				int part = 0;
+				int part_size = 0;
+				
+				if(msg == NETMSG_SNAP)
+				{
+					num_parts = msg_unpack_int();
+					part = msg_unpack_int();
+				}
+				
+				if(msg != NETMSG_SNAPEMPTY)
+					part_size = msg_unpack_int();
+				
+				if(snapshot_part == part)
 				{
-					snapshot *tmp = snapshots[SNAP_PREV];
-					snapshots[SNAP_PREV] = snapshots[SNAP_CURRENT];
-					snapshots[SNAP_CURRENT] = tmp;
-					current_tick = game_tick;
-
-					// decompress snapshot
-					void *deltadata = snapshot_empty_delta();
-					int deltasize = sizeof(int)*3;
-
-					unsigned char tmpbuffer[MAX_SNAPSHOT_SIZE];
-					unsigned char tmpbuffer2[MAX_SNAPSHOT_SIZE];
-					if(part_size)
+					// TODO: clean this up abit
+					const char *d = (const char *)msg_unpack_raw(part_size);
+					mem_copy((char*)snapshots[SNAP_INCOMMING] + part*MAX_SNAPSHOT_PACKSIZE, d, part_size);
+					snapshot_part++;
+				
+					if(snapshot_part == num_parts)
 					{
-						//int snapsize = lzw_decompress(snapshots[SNAP_INCOMMING], snapshots[SNAP_CURRENT]);
-						int compsize = zerobit_decompress(snapshots[SNAP_INCOMMING], part_size, tmpbuffer);
-						//int compsize = lzw_decompress(snapshots[SNAP_INCOMMING],tmpbuffer);
-						int intsize = intpack_decompress(tmpbuffer, compsize, tmpbuffer2);
-						deltadata = tmpbuffer2;
-						deltasize = intsize;
-					}
+						snapshot *tmp = snapshots[SNAP_PREV];
+						snapshots[SNAP_PREV] = snapshots[SNAP_CURRENT];
+						snapshots[SNAP_CURRENT] = tmp;
+						current_tick = game_tick;
+
+						// decompress snapshot
+						void *deltadata = snapshot_empty_delta();
+						int deltasize = sizeof(int)*3;
+
+						unsigned char tmpbuffer[MAX_SNAPSHOT_SIZE];
+						unsigned char tmpbuffer2[MAX_SNAPSHOT_SIZE];
+						if(part_size)
+						{
+							//int snapsize = lzw_decompress(snapshots[SNAP_INCOMMING], snapshots[SNAP_CURRENT]);
+							int compsize = zerobit_decompress(snapshots[SNAP_INCOMMING], part_size, tmpbuffer);
+							//int compsize = lzw_decompress(snapshots[SNAP_INCOMMING],tmpbuffer);
+							int intsize = intpack_decompress(tmpbuffer, compsize, tmpbuffer2);
+							deltadata = tmpbuffer2;
+							deltasize = intsize;
+						}
 
-					// find snapshot that we should use as delta 
-					static snapshot emptysnap;
-					emptysnap.data_size = 0;
-					emptysnap.num_items = 0;
-					
-					snapshot *deltashot = &emptysnap;
-					int deltashot_size;
+						// find snapshot that we should use as delta 
+						static snapshot emptysnap;
+						emptysnap.data_size = 0;
+						emptysnap.num_items = 0;
+						
+						snapshot *deltashot = &emptysnap;
+						int deltashot_size;
 
-					if(delta_tick >= 0)
-					{
-						void *delta_data;
-						deltashot_size = snapshots_new.get(delta_tick, &delta_data);
-						if(deltashot_size >= 0)
+						if(delta_tick >= 0)
 						{
-							deltashot = (snapshot *)delta_data;
+							void *delta_data;
+							deltashot_size = snapshots_new.get(delta_tick, &delta_data);
+							if(deltashot_size >= 0)
+							{
+								deltashot = (snapshot *)delta_data;
+							}
+							else
+							{
+								// TODO: handle this
+								dbg_msg("client", "error, couldn't find the delta snapshot");
+							}
 						}
-						else
+
+						int snapsize = snapshot_unpack_delta(deltashot, (snapshot*)snapshots[SNAP_CURRENT], deltadata, deltasize);
+						//snapshot *shot = (snapshot *)snapshots[SNAP_CURRENT];
+
+						// purge old snapshots					
+						snapshots_new.purge_until(delta_tick);
+						snapshots_new.purge_until(game_tick-50); // TODO: change this to server tickrate
+						
+						// add new
+						snapshots_new.add(game_tick, snapsize, snapshots[SNAP_CURRENT]);
+						
+						// apply snapshot, cycle pointers
+						recived_snapshots++;
+						snapshot_start_time = time_get();
+						
+						// we got two snapshots until we see us self as connected
+						if(recived_snapshots == 2)
 						{
-							// TODO: handle this
-							dbg_msg("client", "error, couldn't find the delta snapshot");
+							local_start_time = time_get();
+							set_state(STATE_ONLINE);
 						}
+						
+						if(recived_snapshots > 2)
+							modc_newsnapshot();
+						
+						snapshot_part = 0;
+						
+						// ack snapshot
+						msg_pack_start_system(NETMSG_SNAPACK, 0);
+						msg_pack_int(game_tick);
+						msg_pack_end();
+						client_send_msg();
 					}
-
-					int snapsize = snapshot_unpack_delta(deltashot, (snapshot*)snapshots[SNAP_CURRENT], deltadata, deltasize);
-					//snapshot *shot = (snapshot *)snapshots[SNAP_CURRENT];
-
-					// purge old snapshots					
-					snapshots_new.purge_until(delta_tick);
-					snapshots_new.purge_until(game_tick-50); // TODO: change this to server tickrate
-					
-					// add new
-					snapshots_new.add(game_tick, snapsize, snapshots[SNAP_CURRENT]);
-					
-					// apply snapshot, cycle pointers
-					recived_snapshots++;
-					snapshot_start_time = time_get();
-					
-					// we got two snapshots until we see us self as connected
-					if(recived_snapshots == 2)
-					{
-						local_start_time = time_get();
-						set_state(STATE_ONLINE);
-					}
-					
-					if(recived_snapshots > 2)
-						modc_newsnapshot();
-					
+				}
+				else
+				{
+					dbg_msg("client", "snapshot reset!");
 					snapshot_part = 0;
-					
-					// ack snapshot
-					msg_pack_start(NETMSG_SNAPACK, 0);
-					msg_pack_int(game_tick);
-					msg_pack_end();
-					client_send_msg();
 				}
 			}
-			else
-			{
-				dbg_msg("client", "snapshot reset!");
-				snapshot_part = 0;
-			}
+		}
+		else
+		{
+			// game message
+			modc_message(msg);
 		}
 	}
 	
diff --git a/src/engine/client/gfx.cpp b/src/engine/client/gfx.cpp
index ba908c68..c30ad0f1 100644
--- a/src/engine/client/gfx.cpp
+++ b/src/engine/client/gfx.cpp
@@ -617,7 +617,7 @@ 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_texture_set(current_font->font_texture);
 	gfx_quads_begin();
 	
 	while (*text)
diff --git a/src/engine/interface.h b/src/engine/interface.h
index 27f73e17..a3bb2e15 100644
--- a/src/engine/interface.h
+++ b/src/engine/interface.h
@@ -705,6 +705,7 @@ enum
 	MSGFLAG_VITAL=1,
 };
 
+void msg_pack_start_system(int msg, int flags);
 void msg_pack_start(int msg, int flags);
 void msg_pack_int(int i);
 void msg_pack_string(const char *p, int limit);
@@ -722,20 +723,23 @@ struct msg_info
 const msg_info *msg_get_info();
 
 // message unpacking
-int msg_unpack_start(const void *data, int data_size);
+int msg_unpack_start(const void *data, int data_size, int *system);
 int msg_unpack_int();
 const char *msg_unpack_string();
 const unsigned char *msg_unpack_raw(int size);
 
 // message sending
-int server_send_msg(int client_id);
+int server_send_msg(int client_id); // client_id == -1 == broadcast
 int client_send_msg();
 
 int client_tick();
 float client_intratick();
 
+void gfx_pretty_text(float x, float y, float size, const char *text);
+float gfx_pretty_text_width(float size, const char *text);
 
-int modc_message();
+void mods_message(int msg, int client_id);
+void modc_message(int msg);
 
 #define MASTER_SERVER_ADDRESS "master.teewars.com"
 #define MASTER_SERVER_PORT 8300
diff --git a/src/engine/msg.cpp b/src/engine/msg.cpp
index 024bab2c..fa2dab34 100644
--- a/src/engine/msg.cpp
+++ b/src/engine/msg.cpp
@@ -10,13 +10,22 @@ void msg_pack_int(int i) { packer.add_int(i); }
 void msg_pack_string(const char *p, int limit) { packer.add_string(p, limit); }
 void msg_pack_raw(const void *data, int size) { packer.add_raw((const unsigned char *)data, size); }
 
+void msg_pack_start_system(int msg, int flags)
+{
+	packer.reset();
+	pack_info.msg = (msg<<1)|1;
+	pack_info.flags = flags;
+	
+	msg_pack_int(pack_info.msg);
+}
+
 void msg_pack_start(int msg, int flags)
 {
 	packer.reset();
-	pack_info.msg = msg;
+	pack_info.msg = msg<<1;
 	pack_info.flags = flags;
 	
-	msg_pack_int(msg);
+	msg_pack_int(pack_info.msg);
 }
 
 void msg_pack_end()
@@ -32,10 +41,12 @@ const msg_info *msg_get_info()
 
 // message unpacking
 static data_unpacker unpacker;
-int msg_unpack_start(const void *data, int data_size)
+int msg_unpack_start(const void *data, int data_size, int *system)
 {
 	unpacker.reset((const unsigned char *)data, data_size);
-	return msg_unpack_int();
+	int msg = msg_unpack_int();
+	*system = msg&1;
+	return msg>>1;
 }
 
 int msg_unpack_int() { return unpacker.get_int(); }
diff --git a/src/engine/server/server.cpp b/src/engine/server/server.cpp
index 5632689e..6fe84ade 100644
--- a/src/engine/server/server.cpp
+++ b/src/engine/server/server.cpp
@@ -107,8 +107,19 @@ int server_send_msg(int client_id)
 
 	if(info->flags&MSGFLAG_VITAL)	
 		packet.flags = PACKETFLAG_VITAL;
-	
-	net.send(&packet);
+			
+	if(client_id == -1)
+	{
+		// broadcast
+		for(int i = 0; i < MAX_CLIENTS; i++)
+			if(clients[i].is_ingame())
+			{
+				packet.client_id = i;
+				net.send(&packet);
+			}
+	}
+	else
+		net.send(&packet);
 	return 0;
 }
 	
@@ -337,9 +348,9 @@ public:
 						left -= chunk;
 
 						if(numpackets == 1)
-							msg_pack_start(NETMSG_SNAPSMALL, 0);
+							msg_pack_start_system(NETMSG_SNAPSMALL, 0);
 						else
-							msg_pack_start(NETMSG_SNAP, 0);
+							msg_pack_start_system(NETMSG_SNAP, 0);
 						msg_pack_int(current_tick);
 						msg_pack_int(current_tick-delta_tick); // compressed with
 						msg_pack_int(chunk);
@@ -352,7 +363,7 @@ public:
 				}
 				else
 				{
-					msg_pack_start(NETMSG_SNAPEMPTY, 0);
+					msg_pack_start_system(NETMSG_SNAPEMPTY, 0);
 					msg_pack_int(current_tick);
 					msg_pack_int(current_tick-delta_tick); // compressed with
 					msg_pack_end();
@@ -366,7 +377,7 @@ public:
 
 	void send_map(int cid)
 	{
-		msg_pack_start(NETMSG_MAP, MSGFLAG_VITAL);
+		msg_pack_start_system(NETMSG_MAP, MSGFLAG_VITAL);
 		msg_pack_string(map_name, 0);
 		msg_pack_end();
 		server_send_msg(cid);
@@ -385,40 +396,49 @@ public:
 	void process_client_packet(NETPACKET *packet)
 	{
 		int cid = packet->client_id;
-		int msg = msg_unpack_start(packet->data, packet->data_size);
-		if(msg == NETMSG_INFO)
-		{
-			strncpy(clients[cid].name, msg_unpack_string(), MAX_NAME_LENGTH);
-			strncpy(clients[cid].clan, msg_unpack_string(), MAX_CLANNAME_LENGTH);
-			const char *password = msg_unpack_string();
-			const char *skin = msg_unpack_string();
-			(void)password; // ignore these variables
-			(void)skin;
-			send_map(cid);
-		}
-		else if(msg == NETMSG_ENTERGAME)
-		{
-			dbg_msg("game", "player as entered the game. cid=%x", cid);
-			clients[cid].state = client::STATE_INGAME;
-			mods_client_enter(cid);
-		}
-		else if(msg == NETMSG_INPUT)
-		{
-			int input[MAX_INPUT_SIZE];
-			int size = msg_unpack_int();
-			for(int i = 0; i < size/4; i++)
-				input[i] = msg_unpack_int();
-			mods_client_input(cid, input);
-		}
-		else if(msg == NETMSG_SNAPACK)
+		int sys;
+		int msg = msg_unpack_start(packet->data, packet->data_size, &sys);
+		if(sys)
 		{
-			clients[cid].last_acked_snapshot = msg_unpack_int();
+			// system message
+			if(msg == NETMSG_INFO)
+			{
+				strncpy(clients[cid].name, msg_unpack_string(), MAX_NAME_LENGTH);
+				strncpy(clients[cid].clan, msg_unpack_string(), MAX_CLANNAME_LENGTH);
+				const char *password = msg_unpack_string();
+				const char *skin = msg_unpack_string();
+				(void)password; // ignore these variables
+				(void)skin;
+				send_map(cid);
+			}
+			else if(msg == NETMSG_ENTERGAME)
+			{
+				dbg_msg("game", "player as entered the game. cid=%x", cid);
+				clients[cid].state = client::STATE_INGAME;
+				mods_client_enter(cid);
+			}
+			else if(msg == NETMSG_INPUT)
+			{
+				int input[MAX_INPUT_SIZE];
+				int size = msg_unpack_int();
+				for(int i = 0; i < size/4; i++)
+					input[i] = msg_unpack_int();
+				mods_client_input(cid, input);
+			}
+			else if(msg == NETMSG_SNAPACK)
+			{
+				clients[cid].last_acked_snapshot = msg_unpack_int();
+			}
+			else
+			{
+				dbg_msg("server", "strange message cid=%d msg=%d data_size=%d", cid, msg, packet->data_size);
+			}
 		}
 		else
 		{
-			dbg_msg("server", "strange message cid=%d msg=%d data_size=%d", cid, msg, packet->data_size);
+			// game message
+			mods_message(msg, cid);
 		}
-		
 	}
 
 	void process_packet(NETPACKET *packet)
diff --git a/src/game/client/game_client.cpp b/src/game/client/game_client.cpp
index 2f05aa77..361d938b 100644
--- a/src/game/client/game_client.cpp
+++ b/src/game/client/game_client.cpp
@@ -1,6 +1,8 @@
 #include <baselib/math.h>
+//#include <baselib/keys.h>
 #include <stdlib.h>
 #include <stdio.h>
+#include <string.h>
 #include <engine/config.h>
 #include "../game.h"
 #include "mapres_image.h"
@@ -17,6 +19,11 @@ static vec2 mouse_pos;
 static vec2 local_player_pos;
 static obj_player *local_player;
 
+struct client_data
+{
+	char name[64];
+} client_datas[MAX_CLIENTS];
+
 inline float frandom() { return rand()/(float)(RAND_MAX); }
 
 void snd_play_random(int setid, float vol, float pan)
@@ -144,28 +151,27 @@ void move_point(vec2 *inout_pos, vec2 *inout_vel, float elasticity)
 	}
 }
 
-class health_texts
+class damage_indicators
 {
 public:
 	int64 lastupdate;
 	struct item
 	{
 		vec2 pos;
-		vec2 vel;
-		int amount;
-		int istar;
+		vec2 dir;
 		float life;
 		float startangle;
 	};
 	
 	enum
 	{
-		MAX_ITEMS=16,
+		MAX_ITEMS=64,
 	};
 
-	health_texts()
+	damage_indicators()
 	{
 		lastupdate = 0;
+		num_items = 0;
 	}
 	
 	item items[MAX_ITEMS];
@@ -188,54 +194,35 @@ public:
 		*i = items[num_items];
 	}
 	
-	void create(vec2 pos, int amount)
+	void create(vec2 pos, vec2 dir)
 	{
-		amount = max(1,amount);
-		for (int j = 0; j < amount; j++)
+		item *i = create_i();
+		if (i)
 		{
-			//float a = j/(float)amount-0.5f;
-			item *i = create_i();
-			if (i)
-			{
-				i->pos = pos;
-				i->pos.y -= 20.0f;
-				i->pos.x += ((float)rand()/(float)RAND_MAX) * 5.0f;
-				i->amount = amount;
-				i->life = 1.5f;
-				i->istar = rand()%2;
-				i->vel = vec2(((float)rand()/(float)RAND_MAX) * 50.0f,-150.0f);
-				i->startangle = (( (float)rand()/(float)RAND_MAX) - 1.0f) * 2.0f * pi;
-			}
+			i->pos = pos;
+			i->life = 0.75f;
+			i->dir = dir;
+			i->startangle = (( (float)rand()/(float)RAND_MAX) - 1.0f) * 2.0f * pi;
 		}
 	}
 	
 	void render()
 	{
-		if (!lastupdate)
-			lastupdate = time_get();
-
-		int64 lasttime = lastupdate;
-		lastupdate = time_get();
-
-		float delta = (float) (lastupdate - lasttime) / (float)time_freq();
-		gfx_texture_set(data->images[IMAGE_PARTICLES].id);
+		gfx_texture_set(data->images[IMAGE_GAME].id);
 		gfx_quads_begin();
 		for(int i = 0; i < num_items;)
 		{
-			items[i].vel += vec2(0,500.0f) * delta;
-			items[i].pos += items[i].vel * delta;
-			items[i].life -= delta;
-			//items[i].pos.y -= frametime*15.0f;
+			vec2 pos = mix(items[i].pos+items[i].dir*75.0f, items[i].pos, clamp((items[i].life-0.60f)/0.15f, 0.0f, 1.0f));
+			
+			items[i].life -= client_frametime();
 			if(items[i].life < 0.0f)
 				destroy_i(&items[i]);
 			else
 			{
-				gfx_quads_setcolor(1.0f,1.0f,1.0f, items[i].life / 1.5f);
+				gfx_quads_setcolor(1.0f,1.0f,1.0f, items[i].life/0.1f);
 				gfx_quads_setrotation(items[i].startangle + items[i].life * 2.0f);
-				float size = 64.0f;
-				const int stars[] = {SPRITE_STAR1, SPRITE_STAR2};
-				select_sprite(stars[items[i].istar]);
-				gfx_quads_draw(items[i].pos.x-size/2, items[i].pos.y-size/2, size, size);
+				select_sprite(SPRITE_STAR1);
+				draw_sprite(pos.x, pos.y, 48.0f);
 				i++;
 			}
 		}
@@ -244,7 +231,7 @@ public:
 	
 };
 
-static health_texts healthmods;
+static damage_indicators damageind;
 
 class particle_system
 {
@@ -350,6 +337,35 @@ public:
 
 static particle_system temp_system;
  
+ 
+static bool chat_active = false;
+static char chat_input[512];
+static unsigned chat_input_len;
+static const int chat_max_lines = 10;
+
+struct chatline
+{
+	int tick;
+	char text[512+64];
+};
+
+chatline chat_lines[chat_max_lines];
+static int chat_current_line = 0;
+
+void chat_reset()
+{
+	for(int i = 0; i < chat_max_lines; i++)
+		chat_lines[i].tick = -1000000;
+	chat_current_line = 0;
+}
+
+void chat_add_line(int client_id, const char *line)
+{
+	chat_current_line = (chat_current_line+1)%chat_max_lines;
+	chat_lines[chat_current_line].tick = client_tick();
+	sprintf(chat_lines[chat_current_line].text, "%s: %s", client_datas[client_id].name, line); // TODO: abit nasty
+}
+ 
 void modc_init()
 {
 	// load the data container
@@ -370,6 +386,10 @@ void modc_entergame()
 	col_init(32);
 	img_init();
 	tilemap_init();
+	chat_reset();
+	
+	for(int i = 0; i < MAX_CLIENTS; i++)
+		client_datas[i].name[0] = 0;
 }
 
 void modc_shutdown()
@@ -384,10 +404,10 @@ void modc_newsnapshot()
 		snap_item item;
 		void *data = snap_get_item(SNAP_CURRENT, i, &item);
 		
-		if(item.type == EVENT_HEALTHMOD)
+		if(item.type == EVENT_DAMAGEINDICATION)
 		{
-			ev_healthmod *ev = (ev_healthmod *)data;
-			healthmods.create(vec2(ev->x, ev->y), ev->amount);
+			ev_damageind *ev = (ev_damageind *)data;
+			damageind.create(vec2(ev->x, ev->y), get_direction(ev->angle));
 		}
 		else if(item.type == EVENT_EXPLOSION)
 		{
@@ -567,6 +587,8 @@ struct animstate
 	keyframe attach;
 };
 
+
+
 static void anim_eval(animation *anim, float time, animstate *state)
 {
 	anim_seq_eval(&anim->body, time, &state->body);
@@ -597,8 +619,64 @@ static void anim_eval_add(animstate *state, animation *anim, float time, float a
 	anim_add(state, &add, amount);
 }
 
+static void render_tee(animstate *anim, int skin, vec2 dir, vec2 pos)
+{
+	vec2 direction =  dir;
+	//float angle = info->angle;
+	vec2 position = pos;
+	
+	gfx_texture_set(data->images[IMAGE_CHAR_DEFAULT].id);
+	gfx_quads_begin();
+	
+	// draw foots
+	for(int p = 0; p < 2; p++)
+	{
+		// first pass we draw the outline
+		// second pass we draw the filling
+		
+		int outline = p==0 ? 1 : 0;
+		int shift = charids[skin%16];
+		
+		for(int f = 0; f < 2; f++)
+		{
+			float basesize = 10.0f;
+			if(f == 1)
+			{
+				// draw body
+				select_sprite(outline?SPRITE_TEE_BODY_OUTLINE:SPRITE_TEE_BODY, 0, 0, shift*4);
+				gfx_quads_draw(position.x+anim->body.x, position.y+anim->body.y, 4*basesize, 4*basesize);
+				
+				// draw eyes
+				if(p == 1)
+				{
+					// normal
+					select_sprite(SPRITE_TEE_EYE_NORMAL, 0, 0, shift*4);
+					gfx_quads_draw(position.x-4+direction.x*4, position.y-8+direction.y*3, basesize, basesize);
+					gfx_quads_draw(position.x+4+direction.x*4, position.y-8+direction.y*3, basesize, basesize);
+				}
+			}
+
+			// draw feet
+			select_sprite(outline?SPRITE_TEE_FOOT_OUTLINE:SPRITE_TEE_FOOT, 0, 0, shift*4);
+			
+			keyframe *foot = f ? &anim->front_foot : &anim->back_foot;
+			
+			float w = basesize*2.5f;
+			float h = basesize*1.425f;
+			
+			gfx_quads_setrotation(foot->angle);
+			gfx_quads_draw(position.x+foot->x, position.y+foot->y, w, h);
+		}
+	}
+	
+	gfx_quads_end();	
+}
+
 static void render_player(obj_player *prev, obj_player *player)
 {
+	if(player->health < 0) // dont render dead players
+		return;
+	
 	vec2 direction = get_direction(player->angle);
 	float angle = player->angle/256.0f;
 	vec2 position = mix(vec2(prev->x, prev->y), vec2(player->x, player->y), client_intratick());
@@ -692,6 +770,9 @@ static void render_player(obj_player *prev, obj_player *player)
 		gfx_quads_end();
 	}
 	
+	render_tee(&state, player->clientid, direction, position);
+	
+	/*
 	gfx_texture_set(data->images[IMAGE_CHAR_DEFAULT].id);
 	gfx_quads_begin();
 	
@@ -741,10 +822,60 @@ static void render_player(obj_player *prev, obj_player *player)
 	}
 	
 	gfx_quads_end();
+	*/
 }
 
+
+
 void modc_render()
 {	
+	if(inp_key_down(input::enter))
+	{
+		if(chat_active)
+		{
+			// send message
+			msg_pack_start(MSG_SAY, MSGFLAG_VITAL);
+			msg_pack_string(chat_input, 512);
+			msg_pack_end();
+			client_send_msg();
+		}
+		else
+		{
+			mem_zero(chat_input, sizeof(chat_input));
+			chat_input_len = 0;
+		}
+		chat_active = !chat_active;
+	}
+	
+	if(chat_active)
+	{
+		int c = input::last_char(); // TODO: bypasses the engine interface
+		int k = input::last_key(); // TODO: bypasses the engine interface
+	
+		if (c >= 32 && c < 255)
+		{
+			if (chat_input_len < sizeof(chat_input) - 1)
+			{
+				chat_input[chat_input_len] = c;
+				chat_input[chat_input_len+1] = 0;
+				chat_input_len++;
+			}
+		}
+
+		if(k == input::backspace)
+		{
+			if(chat_input_len > 0)
+			{
+				chat_input[chat_input_len-1] = 0;
+				chat_input_len--;
+			}
+		}
+		
+	}
+	
+	input::clear_char(); // TODO: bypasses the engine interface
+	input::clear_key(); // TODO: bypasses the engine interface
+	
 	// fetch new input
 	{
 		int x, y;
@@ -763,22 +894,26 @@ void modc_render()
 		float a = atan((float)mouse_pos.y/(float)mouse_pos.x);
 		if(mouse_pos.x < 0)
 			a = a+pi;
-
+			
 		input.angle = (int)(a*256.0f);
-		input.left = inp_key_pressed(config.key_move_left);
-		input.right = inp_key_pressed(config.key_move_right);
-		input.jump = inp_key_pressed(config.key_jump);
-		input.fire = inp_key_pressed(config.key_fire);
-		input.hook = inp_key_pressed(config.key_hook);
-
-		input.blink = inp_key_pressed('S');
-		
-		// Weapon switching
 		input.activeweapon = -1;
-		input.activeweapon = inp_key_pressed('1') ? 0 : input.activeweapon;
-		input.activeweapon = inp_key_pressed('2') ? 1 : input.activeweapon;
-		input.activeweapon = inp_key_pressed('3') ? 2 : input.activeweapon;
-		input.activeweapon = inp_key_pressed('4') ? 3 : input.activeweapon;
+		
+		if(!chat_active)
+		{
+			input.left = inp_key_pressed(config.key_move_left);
+			input.right = inp_key_pressed(config.key_move_right);
+			input.jump = inp_key_pressed(config.key_jump);
+			input.fire = inp_key_pressed(config.key_fire);
+			input.hook = inp_key_pressed(config.key_hook);
+
+			input.blink = inp_key_pressed('S');
+			
+			// Weapon switching
+			input.activeweapon = inp_key_pressed('1') ? 0 : input.activeweapon;
+			input.activeweapon = inp_key_pressed('2') ? 1 : input.activeweapon;
+			input.activeweapon = inp_key_pressed('3') ? 2 : input.activeweapon;
+			input.activeweapon = inp_key_pressed('4') ? 3 : input.activeweapon;
+		}
 
 		snap_input(&input, sizeof(input));
 	}
@@ -811,7 +946,7 @@ void modc_render()
 	}
 
 	// pseudo format
-	float zoom = inp_key_pressed('T') ? 1.0 : 3.0f;
+	float zoom = 3.0f;
 	
 	float width = 400*zoom;
 	float height = 300*zoom;
@@ -907,8 +1042,8 @@ void modc_render()
 
 	tilemap_render(32.0f, 1);
 	
-	// render health mods
-	healthmods.render();
+	// render damage indications
+	damageind.render();
 	
 	if(local_player)
 	{
@@ -958,14 +1093,73 @@ void modc_render()
 	// render gui stuff
 	gfx_mapscreen(0,0,400,300);
 	
+	{
+		float x = 10.0f;
+		float y = 300.0f-50.0f;
+		float starty = -1;
+		if(chat_active)
+		{
+			
+			gfx_texture_set(-1); // TODO: remove when the font looks better
+			gfx_quads_begin();
+			gfx_quads_setcolor(0,0,0,0.4f);
+			gfx_quads_drawTL(x-2, y+1, 300, 8);
+			gfx_quads_end();
+			
+			// render chat input
+			char buf[sizeof(chat_input)+16];
+			sprintf(buf, "Chat: %s_", chat_input);
+			gfx_pretty_text(x, y, 10, buf);
+			starty = y;
+		}
+		
+		y -= 10;
+		
+		int i;
+		for(i = 0; i < chat_max_lines; i++)
+		{
+			int r = ((chat_current_line-i)+chat_max_lines)%chat_max_lines;
+			if(client_tick() > chat_lines[r].tick+50*15)
+				break;
+
+			gfx_texture_set(-1); // TODO: remove when the font looks better
+			gfx_quads_begin();
+			gfx_quads_setcolor(0,0,0,0.4f);
+			gfx_quads_drawTL(x-2, y+1, gfx_pretty_text_width(10, chat_lines[r].text)+3, 8);
+			gfx_quads_end();
+
+			gfx_pretty_text(x, y, 10, chat_lines[r].text);
+			y -= 8;
+		}
+	}
+	
 	// render score board
 	if(inp_key_pressed(baselib::input::tab))
 	{
+		gfx_mapscreen(0, 0, width, height);
+
+		float x = 50.0f;
+		float y = 150.0f;
+
+		gfx_blend_normal();
+		
 		gfx_texture_set(-1);
-		gfx_quads_text(10, 50, 8, "Score Board");
+		gfx_quads_begin();
+		gfx_quads_setcolor(0,0,0,0.5f);
+		gfx_quads_drawTL(x-10.f, y-10.f, 400.0f, 600.0f);
+		gfx_quads_end();
+		
+		//gfx_texture_set(current_font->font_texture);
+		gfx_pretty_text(x, y, 64, "Score Board");
+		y += 100.0f;
+		
+		//gfx_texture_set(-1);
+		//gfx_quads_text(10, 50, 8, "Score Board");
+		animstate state;
+		anim_eval(&data->animations[ANIM_BASE], 0, &state);
+		anim_eval_add(&state, &data->animations[ANIM_IDLE], 0, 1.0f);
 
 		int num = snap_num_items(SNAP_CURRENT);
-		int row = 1;
 		for(int i = 0; i < num; i++)
 		{
 			snap_item item;
@@ -977,13 +1171,31 @@ void modc_render()
 				if(player)
 				{
 					char buf[128];
-					char name[32] = "tjo";
-					//snap_decode_string(player->name, name, 32);
-					sprintf(buf, "%4d %s", player->score, name);
-					gfx_quads_text(10, 50 + 10 * row, 8, buf);
-					row++;
+					sprintf(buf, "%4d", player->score);
+					gfx_pretty_text(x+60-gfx_pretty_text_width(48,buf), y, 48, buf);
+					gfx_pretty_text(x+128, y, 48, client_datas[player->clientid].name);
+
+					render_tee(&state, player->clientid, vec2(1,0), vec2(x+90, y+24));
+					y += 58.0f;
 				}
 			}
 		}
 	}
 }
+
+void modc_message(int msg)
+{
+	if(msg == MSG_CHAT)
+	{
+		int cid = msg_unpack_int();
+		const char *message = msg_unpack_string();
+		dbg_msg("message", "chat cid=%d msg='%s'", cid, message);
+		chat_add_line(cid, message);
+	}
+	else if(msg == MSG_SETNAME)
+	{
+		int cid = msg_unpack_int();
+		const char *name = msg_unpack_string();
+		strncpy(client_datas[cid].name, name, 64);
+	}
+}
diff --git a/src/game/game.h b/src/game/game.h
index 9b21a4ed..f9f41d13 100644
--- a/src/game/game.h
+++ b/src/game/game.h
@@ -30,13 +30,21 @@ enum
 	OBJTYPE_PROJECTILE,
 	OBJTYPE_POWERUP,
 	EVENT_EXPLOSION,
-	EVENT_HEALTHMOD,
+	EVENT_DAMAGEINDICATION,
 	EVENT_SOUND,
 	EVENT_SMOKE,
 };
 
 enum
 {
+	MSG_NULL=0,
+	MSG_SAY,
+	MSG_CHAT,
+	MSG_SETNAME,
+};
+
+enum
+{
 	EMOTE_NORMAL=0,
 	EMOTE_BLINK,
 	EMOTE_WINK,
@@ -67,11 +75,10 @@ struct ev_sound
 	int sound; // if (0x80000000 flag is set -> looping) if (0x40000000 is set -> stop looping
 };
 
-struct ev_healthmod
+struct ev_damageind
 {
-	int x, y; // could perhaps be an client id
-	int vx, vy; // should be an angle instead
-	int amount;
+	int x, y;
+	int angle;
 };
 
 struct obj_projectile
@@ -91,7 +98,6 @@ struct obj_powerup
 struct obj_player
 {
 	//int name[8];
-	
 	int local;
 	int clientid;
 
@@ -106,7 +112,6 @@ struct obj_player
 	// current active weapon
 	int weapon;
 	// current active modifier
-	//int modifier;
 
 	// num attack ticks left of current attack
 	int attacktick;
diff --git a/src/game/server/game_server.cpp b/src/game/server/game_server.cpp
index f86b56af..4b06470e 100644
--- a/src/game/server/game_server.cpp
+++ b/src/game/server/game_server.cpp
@@ -5,8 +5,8 @@
 
 using namespace baselib;
 
-// ---------
-const bool debug_bots = false;
+// --------- DEBUG STUFF ---------
+const bool debug_bots = true;
 
 // --------- PHYSICS TWEAK! --------
 const float ground_control_speed = 7.0f;
@@ -23,7 +23,7 @@ const float hook_drag_speed = 15.0f;
 const float gravity = 0.5f;
 
 class player* get_player(int index);
-void create_healthmod(vec2 p, int amount);
+void create_damageind(vec2 p, vec2 dir, int amount);
 void create_explosion(vec2 p, int owner = -1, bool bnodamage = false);
 void create_smoke(vec2 p);
 void create_sound(vec2 pos, int sound, int loopflags = 0);
@@ -175,6 +175,10 @@ private:
 	friend class player;
 	entity *prev_entity;
 	entity *next_entity;
+
+	entity *prev_type_entity;
+	entity *next_type_entity;
+
 	int index;
 	static int current_id;
 protected:
@@ -189,19 +193,28 @@ public:
 	enum
 	{
 		FLAG_DESTROY=0x00000001,
+		FLAG_ALIVE=0x00000002,
 	};
-
+	
 	entity(int objtype)
 	{
 		this->objtype = objtype;
 		pos = vec2(0,0);
-		flags = 0;
+		flags = FLAG_ALIVE;
 		proximity_radius = 0;
 		
 		current_id++;
 		id = current_id;
+		
+		next_entity = 0;
+		prev_entity = 0;
+		prev_type_entity = 0;
+		next_type_entity = 0;
 	}
 	
+	void set_flag(unsigned flag) { flags |= flag; }
+	void clear_flag(unsigned flag) { flags &= ~flag; }
+	
 	virtual ~entity()
 	{
 	}
@@ -221,10 +234,19 @@ int entity::current_id = 1;
 class game_world
 {
 public:
+	enum
+	{
+		NUM_ENT_TYPES=10,
+	};
+
 	entity *first_entity;
+	entity *first_entity_types[NUM_ENT_TYPES];
+	
 	game_world()
 	{
 		first_entity = 0x0;
+		for(int i = 0; i < NUM_ENT_TYPES; i++)
+			first_entity_types[i] = 0;
 	}
 	
 	int find_entities(vec2 pos, float radius, entity **ents, int max)
@@ -232,6 +254,9 @@ public:
 		int num = 0;
 		for(entity *ent = first_entity; ent; ent = ent->next_entity)
 		{
+			if(!(ent->flags&entity::FLAG_ALIVE))
+				continue;
+				
 			if(distance(ent->pos, pos) < radius+ent->proximity_radius)
 			{
 				ents[num] = ent;
@@ -247,14 +272,13 @@ public:
 	int find_entities(vec2 pos, float radius, entity **ents, int max, const int* types, int maxtypes)
 	{
 		int num = 0;
-		for(entity *ent = first_entity; ent; ent = ent->next_entity)
+		for(int t = 0; t < maxtypes; t++)
 		{
-			for (int i = 0; i < maxtypes; i++)
+			for(entity *ent = first_entity_types[types[t]]; ent; ent = ent->next_type_entity)
 			{
-				if (ent->objtype != types[i])
+				if(!(ent->flags&entity::FLAG_ALIVE))
 					continue;
-
-				// TODO: this seams like it could be done several times unnessesary
+				
 				if(distance(ent->pos, pos) < radius+ent->proximity_radius)
 				{
 					ents[num] = ent;
@@ -276,11 +300,18 @@ public:
 		ent->next_entity = first_entity;
 		ent->prev_entity = 0x0;
 		first_entity = ent;
+
+		// into typelist aswell
+		if(first_entity_types[ent->objtype])
+			first_entity_types[ent->objtype]->prev_type_entity = ent;
+		ent->next_type_entity = first_entity_types[ent->objtype];
+		ent->prev_type_entity = 0x0;
+		first_entity_types[ent->objtype] = ent;
 	}
 	
 	void destroy_entity(entity *ent)
 	{
-		ent->flags |= entity::FLAG_DESTROY;
+		ent->set_flag(entity::FLAG_DESTROY);
 	}
 	
 	void remove_entity(entity *ent)
@@ -292,6 +323,13 @@ public:
 			first_entity = ent->next_entity;
 		if(ent->next_entity)
 			ent->next_entity->prev_entity = ent->prev_entity;
+
+		if(ent->prev_type_entity)
+			ent->prev_type_entity->next_type_entity = ent->next_type_entity;
+		else
+			first_entity_types[ent->objtype] = ent->next_type_entity;
+		if(ent->next_type_entity)
+			ent->next_type_entity->prev_type_entity = ent->prev_type_entity;
 	}
 	
 	//
@@ -394,7 +432,7 @@ public:
 			if (flags & PROJECTILE_FLAGS_EXPLODE)
 				create_explosion(oldpos, owner);
 			else if (targetplayer)
-				targetplayer->take_damage(normalize(vel) * force, damage, owner);
+				targetplayer->take_damage(normalize(vel) * max(0.001f, force), damage, owner);
 				
 			world.destroy_entity(this);
 		}
@@ -442,7 +480,7 @@ public:
 
 	//
 	int client_id;
-	char name[32];
+	char name[64];
 
 	// input	
 	player_input previnput;
@@ -455,6 +493,9 @@ public:
 	int armor;
 
 	int score;
+	
+	bool dead;
+	int die_tick;
 
 	// hooking stuff
 	enum
@@ -496,6 +537,8 @@ public:
 		direction = vec2(0.0f, 1.0f);
 		client_id = -1;
 		score = 0;
+		dead = true;
+		die_tick = 0;
 	}
 	
 	virtual void destroy() {  }
@@ -505,6 +548,8 @@ public:
 		health = PLAYER_MAXHEALTH;
 		armor = 0;
 		jumped = 0;
+		dead = false;
+		set_flag(entity::FLAG_ALIVE);
 		
 		mem_zero(&input, sizeof(input));
 		vel = vec2(0.0f, 0.0f);
@@ -520,6 +565,7 @@ public:
 		}
 		else
 			pos = vec2(100.0f, -60.0f);
+		defered_pos = pos;
 			
 		// init weapons
 		mem_zero(&weapons, sizeof(weapons));
@@ -638,6 +684,14 @@ public:
 	{
 		// TODO: rework the input to be more robust
 		// TODO: remove this tick count, it feels weird
+		if(dead)
+		{
+			if(server_tick()-die_tick >= server_tickspeed()*3) // auto respawn after 3 sec
+				respawn();
+			if(input.fire && server_tick()-die_tick >= server_tickspeed()/2) // auto respawn after 0.5 sec
+				respawn();
+			return;
+		}
 		
 		// fetch some info
 		bool grounded = is_grounded();
@@ -796,7 +850,9 @@ public:
 		release_hooks();
 		
 		// TODO: insert timer here
-		respawn();
+		dead = true;
+		die_tick = server_tick();
+		clear_flag(entity::FLAG_ALIVE);
 	}
 	
 	virtual bool take_damage(vec2 force, int dmg, int from)
@@ -819,7 +875,7 @@ public:
 			armor -= dmg;
 		
 		// create healthmod indicator
-		create_healthmod(pos, dmg);
+		create_damageind(pos, normalize(force), dmg);
 		
 		damage_taken_tick = server_tick()+50;
 		
@@ -876,6 +932,9 @@ public:
 			player->armor = armor;
 		}
 		
+		if(dead)
+			player->health = -1;
+		
 		if(length(vel) > 15.0f)
 			player->emote = EMOTE_HAPPY;
 		
@@ -981,20 +1040,26 @@ void powerup::snap(int snapping_client)
 
 // POWERUP END ///////////////////////
 
-static const int NUM_BOTS = 1;
-static player players[MAX_CLIENTS+NUM_BOTS];
+static player players[MAX_CLIENTS];
 
 player *get_player(int index)
 {
 	return &players[index];
 }
 
-void create_healthmod(vec2 p, int amount)
+void create_damageind(vec2 p, vec2 dir, int amount)
 {
-	ev_healthmod *ev = (ev_healthmod *)events.create(EVENT_HEALTHMOD, sizeof(ev_healthmod));
-	ev->x = (int)p.x;
-	ev->y = (int)p.y;
-	ev->amount = amount;
+	float a = get_angle(dir);
+	float s = a-pi/3;
+	float e = a+pi/3;
+	for(int i = 0; i < amount; i++)
+	{
+		float f = mix(s, e, float(i+1)/float(amount+2));
+		ev_damageind *ev = (ev_damageind *)events.create(EVENT_DAMAGEINDICATION, sizeof(ev_damageind));
+		ev->x = (int)p.x;
+		ev->y = (int)p.y;
+		ev->angle = (int)(f*256.0f);
+	}
 }
 
 void create_explosion(vec2 p, int owner, bool bnodamage)
@@ -1052,13 +1117,13 @@ player* intersect_player(vec2 pos0, vec2 pos1, vec2& new_pos, entity* notthis)
 	vec2 dir = pos1 - pos0;
 	float radius = length(dir * 0.5f);
 	vec2 center = pos0 + dir * 0.5f;
-	int num = world.find_entities(center, radius, ents, 64);
+	const int types[] = {OBJTYPE_PLAYER};
+	int num = world.find_entities(center, radius, ents, 64, types, 1);
 	for (int i = 0; i < num; i++)
 	{
 		// Check if entity is a player
-		if (ents[i] != notthis && ents[i]->objtype == OBJTYPE_PLAYER)
+		if (ents[i] != notthis)
 		{
-			// temp, set hook pos to our position
 			new_pos = ents[i]->pos;
 			return (player*)ents[i];
 		}
@@ -1082,8 +1147,11 @@ void mods_tick()
 			count++;
 			if(count == 10)
 			{
-				for(int i = 0; i < NUM_BOTS; i++)
-					mods_client_enter(MAX_CLIENTS+i);
+				for(int i = 0; i < 1; i++)
+				{
+					mods_client_enter(MAX_CLIENTS-i-1);
+					strcpy(players[MAX_CLIENTS-i-1].name, "(bot)");
+				}
 				count = -1;
 			}
 		}
@@ -1108,6 +1176,30 @@ void mods_client_enter(int client_id)
 	players[client_id].client_id = client_id;
 	players[client_id].respawn();
 	world.insert_entity(&players[client_id]);
+	
+	client_info info; // fetch login name
+	if(server_getclientinfo(client_id, &info))
+		strcpy(players[client_id].name, info.name);
+	else
+		strcpy(players[client_id].name, "(bot)");
+	
+	msg_pack_start(MSG_SETNAME, MSGFLAG_VITAL);
+	msg_pack_int(client_id);
+	msg_pack_string(players[client_id].name, 64);
+	msg_pack_end();
+	server_send_msg(-1);
+	
+	for(int i = 0; i < MAX_CLIENTS; i++)
+	{
+		if(players[client_id].client_id != -1)
+		{
+			msg_pack_start(MSG_SETNAME, MSGFLAG_VITAL);
+			msg_pack_int(i);
+			msg_pack_string(players[i].name, 64);
+			msg_pack_end();
+			server_send_msg(client_id);
+		}
+	}
 }
 
 void mods_client_drop(int client_id)
@@ -1116,6 +1208,18 @@ void mods_client_drop(int client_id)
 	world.remove_entity(&players[client_id]);
 }
 
+void mods_message(int msg, int client_id)
+{
+	if(msg == MSG_SAY)
+	{
+		msg_pack_start(MSG_CHAT, MSGFLAG_VITAL);
+		msg_pack_int(client_id);
+		msg_pack_string(msg_unpack_string(), 512);
+		msg_pack_end();
+		server_send_msg(-1);
+	}
+}
+
 void mods_init()
 {
 	col_init(32);