about summary refs log tree commit diff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/engine/client/client.c51
-rw-r--r--src/engine/client/gfx.c4
-rw-r--r--src/engine/config_variables.h2
-rw-r--r--src/engine/interface.h7
-rw-r--r--src/engine/protocol.h24
-rw-r--r--src/engine/server/server.c32
-rw-r--r--src/game/client/game_client.cpp274
-rw-r--r--src/game/client/menu.cpp9
-rw-r--r--src/game/game_protocol.h12
-rw-r--r--src/game/game_variables.h3
-rw-r--r--src/game/server/game_server.cpp119
-rw-r--r--src/game/server/srv_common.cpp1
-rw-r--r--src/game/server/srv_common.h7
-rw-r--r--src/game/server/srv_ctf.cpp2
-rw-r--r--src/game/server/srv_tdm.cpp5
-rw-r--r--src/game/server/srv_tdm.h1
-rw-r--r--src/tools/crapnet.cpp4
17 files changed, 406 insertions, 151 deletions
diff --git a/src/engine/client/client.c b/src/engine/client/client.c
index 9445e6d7..df53d142 100644
--- a/src/engine/client/client.c
+++ b/src/engine/client/client.c
@@ -19,7 +19,7 @@
 
 #include <mastersrv/mastersrv.h>
 
-const int prediction_margin = 5; /* magic network prediction value */
+const int prediction_margin = 10; /* magic network prediction value */
 
 /*
 	Server Time
@@ -182,6 +182,7 @@ SMOOTHTIME game_time;
 SMOOTHTIME predicted_time;
 
 GRAPH intra_graph;
+GRAPH predict_graph;
 
 /* --- input snapping --- */
 static int input_data[MAX_INPUT_SIZE] = {0};
@@ -272,11 +273,11 @@ static void client_send_info()
 	msg_pack_string(config.player_name, 128);
 	msg_pack_string(config.clan_name, 128);
 	msg_pack_string(config.password, 128);
-	msg_pack_string("myskin", 128);
 	msg_pack_end();
 	client_send_msg();
 }
 
+
 static void client_send_entergame()
 {
 	msg_pack_start_system(NETMSG_ENTERGAME, MSGFLAG_VITAL);
@@ -284,6 +285,13 @@ static void client_send_entergame()
 	client_send_msg();
 }
 
+static void client_send_ready()
+{
+	msg_pack_start_system(NETMSG_READY, MSGFLAG_VITAL);
+	msg_pack_end();
+	client_send_msg();
+}
+
 static void client_send_error(const char *error)
 {
 	/*
@@ -380,6 +388,14 @@ static void client_on_enter_game()
 	current_recv_tick = 0;
 }
 
+void client_entergame()
+{
+	/* now we will wait for two snapshots */
+	/* to finish the connection */
+	client_send_entergame();
+	client_on_enter_game();
+}
+
 void client_connect(const char *server_address_str)
 {
 	char buf[512];
@@ -412,6 +428,8 @@ void client_connect(const char *server_address_str)
 	
 	graph_init(&intra_graph, 0.0f, 1.0f);
 	graph_init(&input_late_graph, 0.0f, 1.0f);
+	graph_init(&predict_graph, 0.0f, 200.0f);
+	
 }
 
 void client_disconnect()
@@ -463,8 +481,9 @@ static void client_debug_render()
 	
 	/* render graphs */
 	gfx_mapscreen(0,0,400.0f,300.0f);
-	graph_render(&game_time.graph, 300, 10, 90, 50);
+	graph_render(&predict_graph, 300, 10, 90, 50);
 	graph_render(&predicted_time.graph, 300, 10+50+10, 90, 50);
+	
 	graph_render(&intra_graph, 300, 10+50+10+50+10, 90, 50);
 	graph_render(&input_late_graph, 300, 10+50+10+50+10+50+10, 90, 50);
 	
@@ -574,13 +593,15 @@ static void client_process_packet(NETPACKET *packet)
 				
 				if(map_load(map))
 				{
-					modc_entergame();
-					client_send_entergame();
 					dbg_msg("client/network", "loading done");
-					/* now we will wait for two snapshots */
-					/* to finish the connection */
+					client_send_ready();
+					modc_connected();
 					
-					client_on_enter_game();
+					/*
+					modc_entergame();
+					client_send_entergame();
+					*/
+					/*client_on_enter_game();*/
 				}
 				else
 				{
@@ -736,7 +757,7 @@ static void client_process_packet(NETPACKET *packet)
 						if(recived_snapshots == 2)
 						{
 							/* start at 200ms and work from there */
-							st_init(&predicted_time, (game_tick+10)*time_freq()/50);
+							st_init(&predicted_time, game_tick*time_freq()/50);
 							st_init(&game_time, (game_tick-1)*time_freq()/50);
 							snapshots[SNAP_PREV] = snapshot_storage.first;
 							snapshots[SNAP_CURRENT] = snapshot_storage.last;
@@ -744,6 +765,11 @@ static void client_process_packet(NETPACKET *packet)
 							client_set_state(CLIENTSTATE_ONLINE);
 						}
 						
+						{
+							int64 now = time_get();
+							graph_add(&predict_graph, (st_get(&predicted_time, now)-st_get(&game_time, now))/(float)time_freq());
+						}
+						
 						st_update(&game_time, (game_tick-1)*time_freq()/50);
 						
 						/* ack snapshot */
@@ -950,15 +976,16 @@ static void client_run(const char *direct_connect_server)
 			if(inp_key_pressed(KEY_F2))
 				inp_mouse_mode_relative();
 
-			if(inp_key_pressed(KEY_LCTRL) && inp_key_pressed('Q'))
-				break;
-		
 			if(inp_key_pressed(KEY_F5))
 			{
 				ack_game_tick = -1;
 				client_send_input();
 			}
 		}
+
+		/* panic quit button */
+		if(inp_key_pressed(KEY_LCTRL) && inp_key_pressed(KEY_LSHIFT) && inp_key_pressed('Q'))
+			break;
 			
 		/* pump the network */
 		client_pump_network();
diff --git a/src/engine/client/gfx.c b/src/engine/client/gfx.c
index 3bf8cfe4..31a87611 100644
--- a/src/engine/client/gfx.c
+++ b/src/engine/client/gfx.c
@@ -536,8 +536,10 @@ void gfx_swap()
 
 			for(; index < 1000; index++)
 			{
-				IOHANDLE io = io_open(filename, IOFLAG_READ);
+				IOHANDLE io;
 				sprintf(filename, "screenshot%04d.png", index);
+				io = io_open(filename, IOFLAG_READ);
+				
 				if(io)
 					io_close(io);
 				else
diff --git a/src/engine/config_variables.h b/src/engine/config_variables.h
index 4ef8958e..f3820901 100644
--- a/src/engine/config_variables.h
+++ b/src/engine/config_variables.h
@@ -2,7 +2,9 @@
 
 MACRO_CONFIG_INT(volume, 200, 0, 255)
 MACRO_CONFIG_INT(cpu_throttle, 0, 0, 1)
+
 MACRO_CONFIG_STR(player_name, 32, "nameless tee")
+
 MACRO_CONFIG_STR(clan_name, 32, "")
 MACRO_CONFIG_STR(password, 32, "")
 
diff --git a/src/engine/interface.h b/src/engine/interface.h
index c57c170d..0eaabc20 100644
--- a/src/engine/interface.h
+++ b/src/engine/interface.h
@@ -743,9 +743,12 @@ int modmenu_render(int ingame);
 
 
 /* undocumented callbacks */
+void modc_connected();
 void modc_message(int msg);
 void modc_predict();
+
 void mods_message(int msg, int client_id);
+void mods_connected(int client_id);
 
 
 const char *modc_net_version();
@@ -753,6 +756,9 @@ const char *mods_net_version();
 
 /* server */
 int server_getclientinfo(int client_id, CLIENT_INFO *info);
+const char *server_clientname(int client_id);
+void server_setclientname(int client_id, const char *name);
+
 int server_tick();
 int server_tickspeed();
 
@@ -823,6 +829,7 @@ int *client_get_input(int tick);
 void client_connect(const char *address);
 void client_disconnect();
 void client_quit();
+void client_entergame();
 
 void client_rcon(const char *cmd);
 
diff --git a/src/engine/protocol.h b/src/engine/protocol.h
index 96ad1e6b..822a735f 100644
--- a/src/engine/protocol.h
+++ b/src/engine/protocol.h
@@ -1,5 +1,28 @@
 #include "system.h"
 
+/*
+	Connection diagram - How the initilization works.
+	
+	Client -> INFO -> Server
+		Contains version info, name, and some other info.
+		
+	Client <- MAP <- Server
+		Contains current map.
+	
+	Client -> READY -> Server
+		The client has loaded the map and is ready to go,
+		but the mod needs to send it's information aswell.
+		modc_connected is called on the client and
+		mods_connected is called on the server.
+		The client should call client_entergame when the
+		mod has done it's initilization.
+		
+	Client -> ENTERGAME -> Server
+		Tells the server to start sending snapshots.
+		client_entergame and server_client_enter is called.
+*/
+
+
 enum
 {
 	NETMSG_NULL=0,
@@ -15,6 +38,7 @@ enum
 	NETMSG_SNAPSMALL,
 	
 	/* sent by client */
+	NETMSG_READY,
 	NETMSG_ENTERGAME,
 	NETMSG_INPUT,
 	NETMSG_CMD,
diff --git a/src/engine/server/server.c b/src/engine/server/server.c
index 8fa10d7b..b1bf5e4d 100644
--- a/src/engine/server/server.c
+++ b/src/engine/server/server.c
@@ -57,8 +57,9 @@ static int snap_id_inited = 0;
 enum
 {
 	SRVCLIENT_STATE_EMPTY = 0,
-	SRVCLIENT_STATE_CONNECTING = 1,
-	SRVCLIENT_STATE_INGAME = 2
+	SRVCLIENT_STATE_CONNECTING,
+	SRVCLIENT_STATE_READY,
+	SRVCLIENT_STATE_INGAME
 };
 
 typedef struct 
@@ -161,6 +162,20 @@ void snap_free_id(int id)
 	}
 }
 
+const char *server_clientname(int client_id)
+{
+	if(client_id < 0 || client_id > MAX_CLIENTS || clients[client_id].state < SRVCLIENT_STATE_READY)
+		return "(invalid client)";
+	return clients[client_id].name;
+}
+
+void server_setclientname(int client_id, const char *name)
+{
+	if(client_id < 0 || client_id > MAX_CLIENTS || clients[client_id].state < SRVCLIENT_STATE_READY)
+		return;
+	strncpy(clients[client_id].name, name, MAX_NAME_LENGTH);
+}
+
 int server_tick()
 {
 	return current_tick;
@@ -404,7 +419,6 @@ static void server_process_client_packet(NETPACKET *packet)
 		{
 			char version[64];
 			const char *password;
-			const char *skin;
 			strncpy(version, msg_unpack_string(), 64);
 			if(strcmp(version, mods_net_version()) != 0)
 			{
@@ -418,9 +432,6 @@ static void server_process_client_packet(NETPACKET *packet)
 			strncpy(clients[cid].name, msg_unpack_string(), MAX_NAME_LENGTH);
 			strncpy(clients[cid].clan, msg_unpack_string(), MAX_CLANNAME_LENGTH);
 			password = msg_unpack_string();
-			skin = msg_unpack_string();
-			(void)password; /* ignore these variables */
-			(void)skin;
 			
 			if(config.password[0] != 0 && strcmp(config.password, password) != 0)
 			{
@@ -431,6 +442,15 @@ static void server_process_client_packet(NETPACKET *packet)
 			
 			server_send_map(cid);
 		}
+		else if(msg == NETMSG_READY)
+		{
+			if(clients[cid].state == SRVCLIENT_STATE_CONNECTING)
+			{
+				dbg_msg("server", "player is ready. cid=%x", cid);
+				clients[cid].state = SRVCLIENT_STATE_READY;
+				mods_connected(cid);
+			}
+		}
 		else if(msg == NETMSG_ENTERGAME)
 		{
 			if(clients[cid].state != SRVCLIENT_STATE_INGAME)
diff --git a/src/game/client/game_client.cpp b/src/game/client/game_client.cpp
index 471cb604..e966287e 100644
--- a/src/game/client/game_client.cpp
+++ b/src/game/client/game_client.cpp
@@ -26,10 +26,8 @@ enum
 
 data_container *data = 0x0;
 
-static int charids[16] = {2,10,0,4,12,6,9,1,3,15,13,11,7,5,8,14};
-
 int gametype = GAMETYPE_DM;
-static int skinseed = 0;
+//static int skinseed = 0;
 
 static int music_menu = -1;
 static int music_menu_id = -1;
@@ -53,13 +51,42 @@ static const obj_player_character *local_prev_character = 0;
 static const obj_player_info *local_info = 0;
 static const obj_game *gameobj = 0;
 
-struct client_data
+// do this better and nicer
+struct skin
+{
+	int org_texture;
+	int color_texture;
+	char name[31];
+	const char term[1];
+};
+
+enum
+{
+	MAX_SKINS=256,
+};
+
+struct tee_render_info
+{
+	int texture;
+	vec4 color;
+};
+
+static skin skins[MAX_SKINS] = {{-1, -1, {0}, {0}}};
+static int num_skins = 0;
+
+static struct client_data
 {
 	char name[64];
+	char skin_name[64];
+	int skin_id;
+	int skin_color;
 	int team;
 	int emoticon;
 	int emoticon_start;
 	player_core predicted;
+	
+	tee_render_info skin_info;
+	
 } client_datas[MAX_CLIENTS];
 
 class client_effects
@@ -514,6 +541,46 @@ static void render_loading(float percent)
 	gfx_swap();
 }
 
+static void skinscan(const char *name, int is_dir, void *user)
+{
+	int l = strlen(name);
+	if(l < 4 || is_dir || num_skins == MAX_SKINS)
+		return;
+	if(strcmp(name+l-4, ".png") != 0)
+		return;
+		
+	char buf[512];
+	sprintf(buf, "data/skins/%s", name);
+	IMAGE_INFO info;
+	if(!gfx_load_png(&info, buf))
+	{
+		dbg_msg("game", "failed to load skin from %s", name);
+		return;
+	}
+	
+	skins[num_skins].org_texture = gfx_load_texture_raw(info.width, info.height, info.format, info.data);
+	
+	// create colorless version
+	unsigned char *d = (unsigned char *)info.data;
+	int step = info.format == IMG_RGBA ? 4 : 3;
+	
+	for(int i = 0; i < info.width*info.height; i++)
+	{
+		int v = (d[i*step]+d[i*step+1]+d[i*step+2])/3;
+		d[i*step] = v;
+		d[i*step+1] = v;
+		d[i*step+2] = v;
+	}
+	
+	skins[num_skins].color_texture = gfx_load_texture_raw(info.width, info.height, info.format, info.data);
+	mem_free(info.data);
+
+	// set skin data	
+	strncpy(skins[num_skins].name, name, min((int)sizeof(skins[num_skins].name),l-4));
+	dbg_msg("game", "load skin %s", skins[num_skins].name);
+	num_skins++;
+}
+
 extern "C" void modc_init()
 {
 	// setup sound channels
@@ -556,27 +623,13 @@ extern "C" void modc_init()
 		data->images[i].id = gfx_load_texture(data->images[i].filename);
 		current++;
 	}
+	
+	// load skins
+	fs_listdir("data/skins", skinscan, 0);
 }
 
 extern "C" void modc_entergame()
 {
-	col_init(32);
-	img_init();
-	tilemap_init();
-	chat_reset();
-
-	proj_particles.reset();
-
-	for(int i = 0; i < MAX_CLIENTS; i++)
-	{
-		client_datas[i].name[0] = 0;
-		client_datas[i].team = 0;
-		client_datas[i].emoticon = 0;
-		client_datas[i].emoticon_start = -1;
-	}
-
-	for(int i = 0; i < killmsg_max; i++)
-		killmsgs[i].tick = -100000;
 }
 
 extern "C" void modc_shutdown()
@@ -755,6 +808,10 @@ extern "C" void modc_predict()
 	// predict
 	for(int tick = client_tick()+1; tick <= client_predtick(); tick++)
 	{
+		// fetch the local
+		if(tick == client_predtick() && world.players[local_cid])
+			predicted_prev_player = *world.players[local_cid];
+		
 		// first calculate where everyone should move
 		for(int c = 0; c < MAX_CLIENTS; c++)
 		{
@@ -781,16 +838,10 @@ extern "C" void modc_predict()
 
 			world.players[c]->move();
 			world.players[c]->quantize();
-			
-			// get the data from the local player
-			if(local_cid == c && world.players[local_cid])
-			{
-				if(tick == client_predtick())
-					predicted_player = *world.players[local_cid];
-				else if(tick == client_predtick()-1)
-					predicted_prev_player = *world.players[local_cid];
-			}
 		}
+		
+		if(tick == client_predtick() && world.players[local_cid])
+			predicted_player = *world.players[local_cid];
 	}
 }
 
@@ -850,10 +901,15 @@ extern "C" void modc_newsnapshot()
 	}
 }
 
-void send_changename_request(const char *name)
+void send_info(bool start)
 {
-	msg_pack_start(MSG_CHANGENAME, MSGFLAG_VITAL);
-	msg_pack_string(name, 64);
+	if(start)
+		msg_pack_start(MSG_STARTINFO, MSGFLAG_VITAL);
+	else
+		msg_pack_start(MSG_CHANGEINFO, MSGFLAG_VITAL);
+	msg_pack_string(config.player_name, 64);
+	msg_pack_string(config.player_skin, 64);
+	msg_pack_int(config.player_color);
 	msg_pack_end();
 	client_send_msg();
 }
@@ -1043,10 +1099,11 @@ static void anim_eval_add(animstate *state, animation *anim, float time, float a
 	anim_add(state, &add, amount);
 }
 
-static void render_hand(int skin, vec2 center_pos, vec2 dir, float angle_offset, vec2 post_rot_offset)
+static void render_hand(int skin_id, vec2 center_pos, vec2 dir, float angle_offset, vec2 post_rot_offset)
 {
 	// for drawing hand
-	int shift = charids[skin%16];
+	skin_id = skin_id%num_skins;
+	
 	float basesize = 10.0f;
 	//dir = normalize(hook_pos-pos);
 
@@ -1066,7 +1123,8 @@ static void render_hand(int skin, vec2 center_pos, vec2 dir, float angle_offset,
 	hand_pos += dirx * post_rot_offset.x;
 	hand_pos += diry * post_rot_offset.y;
 
-	gfx_texture_set(data->images[IMAGE_CHAR_DEFAULT].id);
+	//gfx_texture_set(data->images[IMAGE_CHAR_DEFAULT].id);
+	gfx_texture_set(skins[skin_id].color_texture);
 	gfx_quads_begin();
 
 	// two passes
@@ -1074,7 +1132,7 @@ static void render_hand(int skin, vec2 center_pos, vec2 dir, float angle_offset,
 	{
 		bool outline = i == 0;
 
-		select_sprite(outline?SPRITE_TEE_HAND_OUTLINE:SPRITE_TEE_HAND, 0, 0, shift*4);
+		select_sprite(outline?SPRITE_TEE_HAND_OUTLINE:SPRITE_TEE_HAND, 0, 0, 0);
 		gfx_quads_setrotation(angle);
 		gfx_quads_draw(hand_pos.x, hand_pos.y, 2*basesize, 2*basesize);
 	}
@@ -1083,30 +1141,33 @@ static void render_hand(int skin, vec2 center_pos, vec2 dir, float angle_offset,
 	gfx_quads_end();
 }
 
-static void render_tee(animstate *anim, int skin, int emote, vec2 dir, vec2 pos)
+static void render_tee(animstate *anim, tee_render_info *info, int emote, vec2 dir, vec2 pos)
 {
-	vec2 direction =  dir;
+	vec2 direction = dir;
 	vec2 position = pos;
 
-	gfx_texture_set(data->images[IMAGE_CHAR_DEFAULT].id);
+	//gfx_texture_set(data->images[IMAGE_CHAR_DEFAULT].id);
+	gfx_texture_set(info->texture);
 	gfx_quads_begin();
+	gfx_setcolor(info->color.r, info->color.g, info->color.b, info->color.a);
+	//gfx_quads_draw(pos.x, pos.y-128, 128, 128);
 
-	// draw foots
+	// first pass we draw the outline
+	// second pass we draw the filling
 	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 = skin;
+		//int shift = skin;
 
 		for(int f = 0; f < 2; f++)
 		{
-			float basesize = 10.0f;
+			float basesize = 16.0f;
 			if(f == 1)
 			{
 				gfx_quads_setrotation(anim->body.angle*pi*2);
+
 				// draw body
-				select_sprite(outline?SPRITE_TEE_BODY_OUTLINE:SPRITE_TEE_BODY, 0, 0, shift*4);
+				select_sprite(outline?SPRITE_TEE_BODY_OUTLINE:SPRITE_TEE_BODY, 0, 0, 0);
 				gfx_quads_draw(position.x+anim->body.x, position.y+anim->body.y, 4*basesize, 4*basesize);
 
 				// draw eyes
@@ -1115,34 +1176,34 @@ static void render_tee(animstate *anim, int skin, int emote, vec2 dir, vec2 pos)
 					switch (emote)
 					{
 						case EMOTE_PAIN:
-							select_sprite(SPRITE_TEE_EYE_PAIN, 0, 0, shift*4);
+							select_sprite(SPRITE_TEE_EYE_PAIN, 0, 0, 0);
 							break;
 						case EMOTE_HAPPY:
-							select_sprite(SPRITE_TEE_EYE_HAPPY, 0, 0, shift*4);
+							select_sprite(SPRITE_TEE_EYE_HAPPY, 0, 0, 0);
 							break;
 						case EMOTE_SURPRISE:
-							select_sprite(SPRITE_TEE_EYE_SURPRISE, 0, 0, shift*4);
+							select_sprite(SPRITE_TEE_EYE_SURPRISE, 0, 0, 0);
 							break;
 						case EMOTE_ANGRY:
-							select_sprite(SPRITE_TEE_EYE_ANGRY, 0, 0, shift*4);
+							select_sprite(SPRITE_TEE_EYE_ANGRY, 0, 0, 0);
 							break;
 						default:
-							select_sprite(SPRITE_TEE_EYE_NORMAL, 0, 0, shift*4);
+							select_sprite(SPRITE_TEE_EYE_NORMAL, 0, 0, 0);
 							break;
 					}
 					int h = emote == EMOTE_BLINK ? (int)(basesize/3) : (int)(basesize);
-					gfx_quads_draw(position.x-4+direction.x*4, position.y-8+direction.y*3, basesize, h);
-					gfx_quads_draw(position.x+4+direction.x*4, position.y-8+direction.y*3, -basesize, h);
+					gfx_quads_draw(position.x-4+direction.x*4, position.y-8+direction.y*3, basesize*1.5f, h*1.5f);
+					gfx_quads_draw(position.x+4+direction.x*4, position.y-8+direction.y*3, -basesize*1.5f, h*1.5f);
 				}
 			}
 
 			// draw feet
-			select_sprite(outline?SPRITE_TEE_FOOT_OUTLINE:SPRITE_TEE_FOOT, 0, 0, shift*4);
+			select_sprite(outline?SPRITE_TEE_FOOT_OUTLINE:SPRITE_TEE_FOOT, 0, 0, 0);
 
 			keyframe *foot = f ? &anim->front_foot : &anim->back_foot;
 
-			float w = basesize*2.5f;
-			float h = basesize*1.425f;
+			float w = basesize*2.5f*1.5f;
+			float h = basesize*1.425f*1.5f;
 
 			gfx_quads_setrotation(foot->angle*pi*2);
 			gfx_quads_draw(position.x+foot->x, position.y+foot->y, w, h);
@@ -1249,10 +1310,10 @@ static void render_player(
 		intratick = client_intrapredtick();
 	}
 
-	int skin = charids[info.clientid];
-
-	if(gametype != GAMETYPE_DM)
-		skin = info.team*9; // 0 or 9
+	// TODO: proper skin selection
+	int skin_id = client_datas[info.clientid].skin_id; //charids[info.clientid];
+	//if(gametype != GAMETYPE_DM)
+		//skin_id = info.team*9; // 0 or 9
 
 	vec2 direction = get_direction(player.angle);
 	float angle = player.angle/256.0f;
@@ -1317,7 +1378,7 @@ static void render_player(
 		gfx_quads_setrotation(0);
 		gfx_quads_end();
 
-		render_hand(skin, position, normalize(hook_pos-pos), -pi/2, vec2(20, 0));
+		render_hand(skin_id, position, normalize(hook_pos-pos), -pi/2, vec2(20, 0));
 	}
 
 	// draw gun
@@ -1437,9 +1498,9 @@ static void render_player(
 
 		switch (player.weapon)
 		{
-			case WEAPON_GUN: render_hand(skin, p, direction, -3*pi/4, vec2(-15, 4)); break;
-			case WEAPON_SHOTGUN: render_hand(skin, p, direction, -pi/2, vec2(-5, 4)); break;
-			case WEAPON_ROCKET: render_hand(skin, p, direction, -pi/2, vec2(-4, 7)); break;
+			case WEAPON_GUN: render_hand(skin_id, p, direction, -3*pi/4, vec2(-15, 4)); break;
+			case WEAPON_SHOTGUN: render_hand(skin_id, p, direction, -pi/2, vec2(-5, 4)); break;
+			case WEAPON_ROCKET: render_hand(skin_id, p, direction, -pi/2, vec2(-4, 7)); break;
 		}
 
 	}
@@ -1448,11 +1509,13 @@ static void render_player(
 	if(info.local && config.debug)
 	{
 		vec2 ghost_position = mix(vec2(prev_char->x, prev_char->y), vec2(player_char->x, player_char->y), client_intratick());
-		render_tee(&state, 15, player.emote, direction, ghost_position); // render ghost
+		tee_render_info ghost = client_datas[info.clientid].skin_info;
+		ghost.color.a = 0.5f;
+		render_tee(&state, &ghost, player.emote, direction, ghost_position); // render ghost
 	}
 
 	// render the tee
-	render_tee(&state, skin, player.emote, direction, position);
+	render_tee(&state, &client_datas[info.clientid].skin_info, player.emote, direction, position);
 
 	if(player.state == STATE_CHATTING)
 	{
@@ -1785,7 +1848,7 @@ void render_scoreboard(float x, float y, float w, int team, const char *title)
 		gfx_pretty_text(x+w-tw-35, y, font_size, buf, -1);
 
 		// render avatar
-		render_tee(&idlestate, info->clientid, EMOTE_NORMAL, vec2(1,0), vec2(x+90, y+28));
+		render_tee(&idlestate, &client_datas[info->clientid].skin_info, EMOTE_NORMAL, vec2(1,0), vec2(x+90, y+28));
 		y += 50.0f;
 	}
 }
@@ -1882,7 +1945,7 @@ void render_world(float center_x, float center_y, float zoom)
 				const void *info = snap_find_item(SNAP_CURRENT, OBJTYPE_PLAYER_INFO, item.id);
 				if(prev && prev_info && info)
 				{
-					client_datas[((const obj_player_info *)data)->clientid].team = ((const obj_player_info *)data)->team;
+					client_datas[((const obj_player_info *)info)->clientid].team = ((const obj_player_info *)info)->team;
 					render_player(
 							(const obj_player_character *)prev,
 							(const obj_player_character *)data,
@@ -1905,6 +1968,22 @@ void render_world(float center_x, float center_y, float zoom)
 	damageind.render();
 }
 
+static void next_skin()
+{
+	int skin_id = 0;
+	for(int i = 0; i < num_skins; i++)
+	{
+		if(strcmp(config.player_skin, skins[i].name) == 0)
+		{
+			skin_id = (i+1)%num_skins;
+			break;
+		}
+	}
+	
+	config_set_player_skin(&config, skins[skin_id].name);
+	send_info(false);
+}
+
 static void do_input(int *v, int key)
 {
 	*v += inp_key_presses(key) + inp_key_releases(key);
@@ -1915,6 +1994,9 @@ static void do_input(int *v, int key)
 
 void render_game()
 {
+	if(inp_key_down('L'))
+		next_skin();
+	
 	float width = 400*3.0f;
 	float height = 300*3.0f;
 
@@ -2319,8 +2401,8 @@ void render_game()
 
 			// render victim tee
 			x -= 24.0f;
-			int skin = gametype == GAMETYPE_TDM ? skinseed + client_datas[killmsgs[r].victim].team : killmsgs[r].victim;
-			render_tee(&idlestate, skin, EMOTE_PAIN, vec2(-1,0), vec2(x, y+28));
+			//int skin = gametype == GAMETYPE_TDM ? skinseed + client_datas[killmsgs[r].victim].team : killmsgs[r].victim;
+			render_tee(&idlestate, &client_datas[killmsgs[r].victim].skin_info, EMOTE_PAIN, vec2(-1,0), vec2(x, y+28));
 			x -= 32.0f;
 
 			// render weapon
@@ -2337,8 +2419,8 @@ void render_game()
 
 			// render killer tee
 			x -= 24.0f;
-			skin = gametype == GAMETYPE_TDM ? skinseed + client_datas[killmsgs[r].killer].team : killmsgs[r].killer;
-			render_tee(&idlestate, skin, EMOTE_ANGRY, vec2(1,0), vec2(x, y+28));
+			//skin = gametype == GAMETYPE_TDM ? skinseed + client_datas[killmsgs[r].killer].team : killmsgs[r].killer;
+			render_tee(&idlestate, &client_datas[killmsgs[r].killer].skin_info, EMOTE_ANGRY, vec2(1,0), vec2(x, y+28));
 			x -= 32.0f;
 
 			// render killer name
@@ -2532,6 +2614,7 @@ extern "C" void modc_render()
 		render_game();
 
 		// handle team switching
+		// TODO: FUGLY!!!
 		if(config.team != -10)
 		{
 			msg_pack_start(MSG_SETTEAM, MSGFLAG_VITAL);
@@ -2586,11 +2669,33 @@ extern "C" void modc_message(int msg)
 		else
 			snd_play(CHN_GUI, data->sounds[SOUND_CHAT_SERVER].sounds[0].id, 0);
 	}
-	else if(msg == MSG_SETNAME)
+	else if(msg == MSG_SETINFO)
 	{
 		int cid = msg_unpack_int();
 		const char *name = msg_unpack_string();
+		const char *skinname = msg_unpack_string();
+		int color = msg_unpack_int();
+		(void)color;
 		strncpy(client_datas[cid].name, name, 64);
+		strncpy(client_datas[cid].skin_name, skinname, 64);
+		client_datas[cid].skin_info.color = vec4(1,1,1,1); //color;
+		
+		// find new skin
+		client_datas[cid].skin_id = 0;
+		for(int i = 0; i < num_skins; i++)
+		{
+			if(strcmp(skins[i].name, client_datas[cid].skin_name) == 0)
+			{
+				client_datas[cid].skin_id = i;
+				break;
+			}
+		}
+		
+		client_datas[cid].skin_info.texture = skins[client_datas[cid].skin_id].org_texture;
+	}
+	else if(msg == MSG_READY_TO_ENTER)
+	{
+		client_entergame();
 	}
 	else if(msg == MSG_KILLMSG)
 	{
@@ -2609,5 +2714,28 @@ extern "C" void modc_message(int msg)
 	}
 }
 
+extern "C" void modc_connected()
+{
+	// init some stuff
+	col_init(32);
+	img_init();
+	tilemap_init();
+	chat_reset();
+
+	proj_particles.reset();
+
+	for(int i = 0; i < MAX_CLIENTS; i++)
+	{
+		client_datas[i].name[0] = 0;
+		client_datas[i].team = 0;
+		client_datas[i].emoticon = 0;
+		client_datas[i].emoticon_start = -1;
+	}
+
+	for(int i = 0; i < killmsg_max; i++)
+		killmsgs[i].tick = -100000;
+		
+	send_info(true);
+}
 
 extern "C" const char *modc_net_version() { return TEEWARS_NETVERSION; }
diff --git a/src/game/client/menu.cpp b/src/game/client/menu.cpp
index 48f7147c..afcb1cfe 100644
--- a/src/game/client/menu.cpp
+++ b/src/game/client/menu.cpp
@@ -1303,7 +1303,7 @@ static int settings_sound_render()
 }
 
 extern void draw_round_rect(float x, float y, float w, float h, float r);
-void send_changename_request(const char *name);
+extern void send_info(bool);
 
 static int settings_render(bool ingame)
 {
@@ -1344,10 +1344,13 @@ static int settings_render(bool ingame)
 	if (ui_do_button(&save_button, "Save", 0, 482, 490, 128, 48, draw_teewars_button, 0))
 	{
 		// did we change our name?
-		if (ingame && strcmp(config.player_name, config_copy.player_name) != 0)
-			send_changename_request(config_copy.player_name);
+		bool name_changed = strcmp(config.player_name, config_copy.player_name) != 0;
 
 		config = config_copy;
+
+		if (ingame && name_changed)
+			send_info(false);
+
 #ifdef CONF_PLATFORM_MACOSX
 		config_save("~/.teewars");
 #else
diff --git a/src/game/game_protocol.h b/src/game/game_protocol.h
index 6be89e35..982a14b2 100644
--- a/src/game/game_protocol.h
+++ b/src/game/game_protocol.h
@@ -38,15 +38,17 @@ enum
 enum
 {
 	MSG_NULL=0,
-	MSG_SAY,
-	MSG_CHAT,
-	MSG_SETNAME,
-	MSG_KILLMSG,
+	MSG_SAY, // client -> server
+	MSG_CHAT, // server -> client
+	MSG_SETINFO, // server -> client - contains name, skin and color info
+	MSG_KILLMSG, // server -> client
 	MSG_SETTEAM,
 	MSG_JOIN,
 	MSG_QUIT,
 	MSG_EMOTICON,
-	MSG_CHANGENAME,
+	MSG_STARTINFO, // client -> server
+	MSG_CHANGEINFO, // client -> server
+	MSG_READY_TO_ENTER // server -> client
 };
 
 enum
diff --git a/src/game/game_variables.h b/src/game/game_variables.h
index 352675ba..32e277f1 100644
--- a/src/game/game_variables.h
+++ b/src/game/game_variables.h
@@ -39,6 +39,9 @@ MACRO_CONFIG_INT(dynamic_camera, 1, 0, 1)
 MACRO_CONFIG_INT(warmup, 0, 0, 0)
 MACRO_CONFIG_INT(team, -10, -1, 0)
 
+MACRO_CONFIG_INT(player_color, -1, -1, 256)
+MACRO_CONFIG_STR(player_skin, 64, "default")
+
 MACRO_CONFIG_INT(dbg_new_gui, 0, 0, 1)
 
 
diff --git a/src/game/server/game_server.cpp b/src/game/server/game_server.cpp
index 8aaa9ab2..f88eefa9 100644
--- a/src/game/server/game_server.cpp
+++ b/src/game/server/game_server.cpp
@@ -355,11 +355,6 @@ player::player()
 void player::init()
 {
 	proximity_radius = phys_size;
-	name[0] = 'n';
-	name[1] = 'o';
-	name[2] = 'o';
-	name[3] = 'b';
-	name[4] = 0;
 	client_id = -1;
 	team = -1; // -1 == spectator
 	extrapowerflags = 0;
@@ -406,7 +401,6 @@ void player::set_weapon(int w)
 	active_weapon = w;
 }
 
-
 void player::respawn()
 {
 	spawning = true;
@@ -504,9 +498,8 @@ void player::try_respawn()
 	weapons[WEAPON_GUN].got = true;
 	weapons[WEAPON_GUN].ammo = data->weapons[WEAPON_GUN].maxammo;
 
-	weapons[WEAPON_SNIPER].got = true;
-	weapons[WEAPON_SNIPER].ammo = data->weapons[WEAPON_SNIPER].maxammo;
-
+	//weapons[WEAPON_SNIPER].got = true;
+	//weapons[WEAPON_SNIPER].ammo = data->weapons[WEAPON_SNIPER].maxammo;
 
 	active_weapon = WEAPON_GUN;
 	last_weapon = WEAPON_HAMMER;
@@ -1006,7 +999,9 @@ void player::die(int killer, int weapon)
 {
 	gameobj->on_player_death(this, get_player(killer), weapon);
 
-	dbg_msg("game", "kill killer='%d:%s' victim='%d:%s' weapon=%d", killer, players[killer].name, client_id, name, weapon);
+	dbg_msg("game", "kill killer='%d:%s' victim='%d:%s' weapon=%d",
+		killer, server_clientname(killer),
+		client_id, server_clientname(client_id), weapon);
 
 	// send the kill message
 	msg_pack_start(MSG_KILLMSG, MSGFLAG_VITAL);
@@ -1292,7 +1287,8 @@ void powerup::tick()
 
 		if(respawntime >= 0)
 		{
-			dbg_msg("game", "pickup player='%d:%s' item=%d/%d", pplayer->client_id, pplayer->name, type, subtype);
+			dbg_msg("game", "pickup player='%d:%s' item=%d/%d",
+				pplayer->client_id, server_clientname(pplayer->client_id), type, subtype);
 			spawntick = server_tick() + server_tickspeed() * respawntime;
 		}
 	}
@@ -1448,7 +1444,7 @@ player* intersect_player(vec2 pos0, vec2 pos1, vec2& new_pos, entity* notthis)
 void send_chat(int cid, int team, const char *msg)
 {
 	if(cid >= 0 && cid < MAX_CLIENTS)
-		dbg_msg("chat", "%d:%d:%s: %s", cid, team, players[cid].name, msg);
+		dbg_msg("chat", "%d:%d:%s: %s", cid, team, server_clientname(cid), msg);
 	else
 		dbg_msg("chat", "*** %s", msg);
 
@@ -1523,17 +1519,15 @@ void mods_client_input(int client_id, void *input)
 	}
 }
 
-void send_set_name(int cid, const char *old_name, const char *new_name)
+void send_info(int who, int to_who)
 {
-	msg_pack_start(MSG_SETNAME, MSGFLAG_VITAL);
-	msg_pack_int(cid);
-	msg_pack_string(new_name, 64);
+	msg_pack_start(MSG_SETINFO, MSGFLAG_VITAL);
+	msg_pack_int(who);
+	msg_pack_string(server_clientname(who), 64);
+	msg_pack_string(players[who].skin_name, 64);
+	msg_pack_int(players[who].skin_color);
 	msg_pack_end();
-	server_send_msg(-1);
-
-	char msg[256];
-	sprintf(msg, "*** %s changed name to %s", old_name, new_name);
-	send_chat(-1, -1, msg);
+	server_send_msg(to_who);
 }
 
 void send_emoticon(int cid, int emoticon)
@@ -1547,21 +1541,21 @@ void send_emoticon(int cid, int emoticon)
 
 void mods_client_enter(int client_id)
 {
-	players[client_id].init();
-	players[client_id].client_id = client_id;
 	world->insert_entity(&players[client_id]);
 	players[client_id].respawn();
+	dbg_msg("game", "join player='%d:%s'", client_id, server_clientname(client_id));
+	
+	char buf[512];
+	sprintf(buf, "%s has joined the game", server_clientname(client_id));
+	send_chat(-1, -1, buf);	
+}
 
-	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)");
-
+void mods_connected(int client_id)
+{
+	players[client_id].init();
+	players[client_id].client_id = client_id;
 
-	dbg_msg("game", "join player='%d:%s'", client_id, players[client_id].name);
+	//dbg_msg("game", "join player='%d:%s'", client_id, server_clientname(client_id));
 
 	// Check which team the player should be on
 	if(gameobj->gametype == GAMETYPE_DM)
@@ -1570,9 +1564,10 @@ void mods_client_enter(int client_id)
 		players[client_id].team = gameobj->getteam(client_id);
 
 	//
-	msg_pack_start(MSG_SETNAME, MSGFLAG_VITAL);
+	/*
+	msg_pack_start(MSG_SETINFO, MSGFLAG_VITAL);
 	msg_pack_int(client_id);
-	msg_pack_string(players[client_id].name, 64);
+	msg_pack_string(server_clientname(client_id), 64);
 	msg_pack_end();
 	server_send_msg(-1);
 
@@ -1580,26 +1575,23 @@ void mods_client_enter(int client_id)
 	{
 		if(players[client_id].client_id != -1)
 		{
-			msg_pack_start(MSG_SETNAME, MSGFLAG_VITAL);
+			msg_pack_start(MSG_SETINFO, MSGFLAG_VITAL);
 			msg_pack_int(i);
-			msg_pack_string(players[i].name, 64);
+			msg_pack_string(server_clientname(i), 64);
 			msg_pack_end();
 			server_send_msg(client_id);
 		}
-	}
+	}*/
 
-	char buf[512];
-	sprintf(buf, "%s has joined the game", players[client_id].name);
-	send_chat(-1, -1, buf);
 }
 
 void mods_client_drop(int client_id)
 {
 	char buf[512];
-	sprintf(buf, "%s has left the game", players[client_id].name);
+	sprintf(buf, "%s has left the game", server_clientname(client_id));
 	send_chat(-1, -1, buf);
 
-	dbg_msg("game", "leave player='%d:%s'", client_id, players[client_id].name);
+	dbg_msg("game", "leave player='%d:%s'", client_id, server_clientname(client_id));
 
 	gameobj->on_player_death(&players[client_id], 0, -1);
 	world->remove_entity(&players[client_id]);
@@ -1623,18 +1615,50 @@ void mods_message(int msg, int client_id)
 		// Switch team on given client and kill/respawn him
 		players[client_id].set_team(msg_unpack_int());
 	}
-	else if (msg == MSG_CHANGENAME)
+	else if (msg == MSG_CHANGEINFO || msg == MSG_STARTINFO)
 	{
 		const char *name = msg_unpack_string();
+		const char *skin_name = msg_unpack_string();
+		int skin_color = msg_unpack_int();
 
 		// check for invalid chars
 		const char *p = name;
 		while (*p)
-			if (*p++ < 32)
+		{
+			if(*p < 32)
 				return;
+			p++;
+		}
+
 
-		send_set_name(client_id, players[client_id].name, name);
-		strcpy(players[client_id].name, name);
+		//
+		if(msg == MSG_CHANGEINFO && strcmp(name, server_clientname(client_id)) != 0)
+		{
+			char msg[256];
+			sprintf(msg, "*** %s changed name to %s", server_clientname(client_id), name);
+			send_chat(-1, -1, msg);
+		}
+
+		//send_set_name(client_id, players[client_id].name, name);
+		strncpy(players[client_id].skin_name, skin_name, 64);
+		server_setclientname(client_id, name);
+		players[client_id].skin_color = skin_color;
+		
+		if(msg == MSG_STARTINFO)
+		{
+			// send all info to this client
+			for(int i = 0; i < MAX_CLIENTS; i++)
+			{
+				if(players[i].client_id != -1)
+					send_info(i, client_id);
+			}
+			
+			msg_pack_start(MSG_READY_TO_ENTER, MSGFLAG_VITAL);
+			msg_pack_end();
+			server_send_msg(client_id);			
+		}
+		
+		send_info(client_id, -1);
 	}
 	else if (msg == MSG_EMOTICON)
 	{
@@ -1742,8 +1766,9 @@ void mods_init()
 			{*/
 				for(int i = 0; i < config.dbg_bots ; i++)
 				{
+					mods_connected(MAX_CLIENTS-i-1);
 					mods_client_enter(MAX_CLIENTS-i-1);
-					strcpy(players[MAX_CLIENTS-i-1].name, "(bot)");
+					//strcpy(players[MAX_CLIENTS-i-1].name, "(bot)");
 					if(gameobj->gametype != GAMETYPE_DM)
 						players[MAX_CLIENTS-i-1].team = i&1;
 				}
diff --git a/src/game/server/srv_common.cpp b/src/game/server/srv_common.cpp
index 6d2abf0c..638d31c6 100644
--- a/src/game/server/srv_common.cpp
+++ b/src/game/server/srv_common.cpp
@@ -28,6 +28,7 @@ gameobject::gameobject()
 	sudden_death = 0;
 	round_start_tick = server_tick();
 	round_count = 0;
+	is_teamplay = false;
 }
 
 void gameobject::endround()
diff --git a/src/game/server/srv_common.h b/src/game/server/srv_common.h
index 1e544753..46c17ed8 100644
--- a/src/game/server/srv_common.h
+++ b/src/game/server/srv_common.h
@@ -121,6 +121,8 @@ protected:
 	int warmup;
 	int round_count;
 	
+	bool is_teamplay;
+	
 public:
 	int gametype;
 	gameobject();
@@ -237,7 +239,8 @@ public:
 	
 	//
 	int client_id;
-	char name[64];
+	char skin_name[64];
+	int skin_color;
 
 	// input	
 	player_input previnput;
@@ -287,7 +290,7 @@ public:
 	void respawn();
 	
 	void set_team(int team);
-
+	
 	bool is_grounded();
 	
 	void set_weapon(int w);
diff --git a/src/game/server/srv_ctf.cpp b/src/game/server/srv_ctf.cpp
index 57d81aef..c4cbd574 100644
--- a/src/game/server/srv_ctf.cpp
+++ b/src/game/server/srv_ctf.cpp
@@ -21,6 +21,8 @@ gameobject_ctf::gameobject_ctf()
 			// report massive failure
 		}
 	}
+
+	is_teamplay = true;
 }
 
 void gameobject_ctf::on_player_spawn(class player *p)
diff --git a/src/game/server/srv_tdm.cpp b/src/game/server/srv_tdm.cpp
index ba3ff238..c9e4c686 100644
--- a/src/game/server/srv_tdm.cpp
+++ b/src/game/server/srv_tdm.cpp
@@ -2,6 +2,11 @@
 #include "srv_common.h"
 #include "srv_tdm.h"
 
+gameobject_tdm::gameobject_tdm()
+{
+	is_teamplay = true;
+}
+
 void gameobject_tdm::tick()
 {
 	if(game_over_tick == -1)
diff --git a/src/game/server/srv_tdm.h b/src/game/server/srv_tdm.h
index 748c2e9e..5caa00c3 100644
--- a/src/game/server/srv_tdm.h
+++ b/src/game/server/srv_tdm.h
@@ -2,5 +2,6 @@
 class gameobject_tdm : public gameobject
 {
 public:
+	gameobject_tdm();
 	virtual void tick();
 };
diff --git a/src/tools/crapnet.cpp b/src/tools/crapnet.cpp
index 842a602d..68e76fa4 100644
--- a/src/tools/crapnet.cpp
+++ b/src/tools/crapnet.cpp
@@ -97,12 +97,12 @@ int run(int port, NETADDR4 dest)
 				
 				// send and remove packet
 				//if((rand()%20) != 0) // heavy packetloss
-				//	net_udp4_send(socket, &p->send_to, p->data, p->data_size);
+				net_udp4_send(socket, &p->send_to, p->data, p->data_size);
 				
 				// update lag
 				double flux = rand()/(double)RAND_MAX;
 				int ms_spike = 0;
-				int ms_flux = 50;
+				int ms_flux = 20;
 				int ms_ping = 50;
 				current_latency = ((time_freq()*ms_ping)/1000) + (int64)(((time_freq()*ms_flux)/1000)*flux); // 50ms