diff -Nru teeworlds-0.5.1-src/src/game/server/entities/character.cpp teeworlds-flagball-0.51/src/game/server/entities/character.cpp
--- teeworlds-0.5.1-src/src/game/server/entities/character.cpp	2009-01-25 14:50:32.000000000 +0100
+++ teeworlds-flagball-0.51/src/game/server/entities/character.cpp	2009-02-13 19:56:58.000000000 +0100
@@ -7,6 +7,7 @@
 #include "character.hpp"
 #include "laser.hpp"
 #include "projectile.hpp"
+#include <game/server/gamemodes/fb.hpp>
 
 struct INPUT_COUNT
 {
@@ -56,6 +57,11 @@
 	last_weapon = WEAPON_HAMMER;
 	queued_weapon = -1;
 	
+	has_ball = false;
+	goal_camp_tick = 0;
+	camp_tick = 0;
+	camp_pos = pos;
+
 	//clear();
 	this->player = player;
 	this->pos = pos;
@@ -111,7 +117,7 @@
 
 int CHARACTER::handle_ninja()
 {
-	if(active_weapon != WEAPON_NINJA)
+	if(active_weapon != WEAPON_NINJA || has_ball)
 		return 0;
 	
 	vec2 direction = normalize(vec2(latest_input.target_x, latest_input.target_y));
@@ -194,6 +200,35 @@
 	return 0;
 }
 
+//	handle ball as weapon
+void CHARACTER::shoot_ball (vec2 direction)
+{
+	//	shoot the corresponding ball
+	for (int i = 0; i < FB_MAX_BALLS; ++i)
+	{
+		BALL *b = static_cast<GAMECONTROLLER_FB *> (game.controller)->balls[i];
+		if (b && b->carrying_character == this)
+		{
+			//	drop ball
+			game.create_sound_global (SOUND_CTF_DROP);
+			b->drop_tick = server_tick ();
+			b->last_carrying_player = player;
+			b->last_carrying_player_team = team;
+			b->carrying_character = 0;
+			has_ball = false;
+			b->vel = direction;
+		
+			// stop ninja-ball-mode
+			weapons[WEAPON_NINJA].got = false;
+			active_weapon = last_weapon;
+			if (active_weapon == WEAPON_NINJA)
+				active_weapon = WEAPON_GUN;
+			set_weapon (active_weapon);
+
+			break;
+		}
+	}
+}
 
 void CHARACTER::do_weaponswitch()
 {
@@ -412,16 +447,26 @@
 		
 		case WEAPON_NINJA:
 		{
-			attack_tick = server_tick();
-			ninja.activationdir = direction;
-			ninja.currentmovetime = data->weapons.ninja.movetime * server_tickspeed() / 1000;
+			if (has_ball)
+			{
+				shoot_ball(normalize (vec2 (latest_input.target_x, latest_input.target_y)) *
+					static_cast<float> (config.sv_fb_ball_velocity / 10.0f));  //20.75f;
+				reload_timer = data->weapons.id[active_weapon].firedelay * server_tickspeed() / 1000;	//	prevent shooting
+				return;
+			}
+			else
+			{
+				attack_tick = server_tick();
+				ninja.activationdir = direction;
+				ninja.currentmovetime = data->weapons.ninja.movetime * server_tickspeed() / 1000;
 
-			//reload_timer = data->weapons.ninja.base->firedelay * server_tickspeed() / 1000 + server_tick();
+				//reload_timer = data->weapons.ninja.base->firedelay * server_tickspeed() / 1000 + server_tick();
 			
-			// reset hit objects
-			numobjectshit = 0;
+				// reset hit objects
+				numobjectshit = 0;
 
-			game.create_sound(pos, SOUND_NINJA_FIRE);
+				game.create_sound(pos, SOUND_NINJA_FIRE);
+			}
 			
 		} break;
 		
@@ -524,6 +569,44 @@
 		fire_weapon();
 	}
 	
+	//	direct-pass of ball
+	if (has_ball && config.sv_fb_direct_pass && latest_input.hook && core.hook_state == HOOK_IDLE)
+	{
+		//	find catching character
+		vec2 at;
+		CHARACTER *catcher = game.world.intersect_character (pos, 
+			pos + normalize (vec2 (latest_input.target_x, latest_input.target_y)) * config.sv_fb_direct_pass_distance, 0.0f, at, this);
+		if (catcher && catcher->team != -1 && !catcher->has_ball && !col_intersect_line (pos, catcher->pos, NULL, NULL))
+		{
+			//	find corresponding ball
+			for (int i = 0; i < FB_MAX_BALLS; ++i)
+			{
+				BALL *b = static_cast<GAMECONTROLLER_FB *> (game.controller)->balls[i];
+				if (b && b->carrying_character == this)
+				{
+					//	thrower drops ball
+					shoot_ball (vec2 (0, 0));
+
+					//	catcher gets ball
+					b->carrying_character = catcher;
+					b->last_carrying_player = 0;
+					b->pos = catcher->pos;
+					catcher->has_ball = true;
+
+					// catcher switches to ninja-ball-mode
+					catcher->ninja.activationtick = server_tick();
+					catcher->weapons[WEAPON_NINJA].got = true;
+					catcher->weapons[WEAPON_NINJA].ammo = -1;
+					catcher->last_weapon = catcher->active_weapon;
+					catcher->active_weapon = WEAPON_NINJA;
+					
+					core.hook_state = HOOK_RETRACTED;	//	HOOK_IDLE;
+					break;
+				}
+			}
+		}
+	}
+
 	mem_copy(&latest_previnput, &latest_input, sizeof(latest_input));
 }
 
@@ -768,6 +851,12 @@
 	
 		return false;
 	}
+	else
+	{
+		// lose flag on every damage
+		if (has_ball && config.sv_fb_ball_damage_lose)
+			shoot_ball (vec2(0.0f,3.0f) * 3.75f);
+	}
 
 	if (dmg > 2)
 		game.create_sound(pos, SOUND_PLAYER_PAIN_LONG);
diff -Nru teeworlds-0.5.1-src/src/game/server/entities/character.hpp teeworlds-flagball-0.51/src/game/server/entities/character.hpp
--- teeworlds-0.5.1-src/src/game/server/entities/character.hpp	2009-01-25 14:50:32.000000000 +0100
+++ teeworlds-flagball-0.51/src/game/server/entities/character.hpp	2009-02-13 02:11:40.000000000 +0100
@@ -129,6 +129,13 @@
 	
 	bool increase_health(int amount);
 	bool increase_armor(int amount);
+
+	//	flagball
+	bool has_ball;
+	int goal_camp_tick;	//	goal camp counter
+	int camp_tick;	//	camp counter
+	vec2 camp_pos;
+	void shoot_ball (vec2 direction);
 };
 
 #endif
diff -Nru teeworlds-0.5.1-src/src/game/server/gamemodes/fb.cpp teeworlds-flagball-0.51/src/game/server/gamemodes/fb.cpp
--- teeworlds-0.5.1-src/src/game/server/gamemodes/fb.cpp	1970-01-01 01:00:00.000000000 +0100
+++ teeworlds-flagball-0.51/src/game/server/gamemodes/fb.cpp	2009-02-17 19:24:44.000000000 +0100
@@ -0,0 +1,483 @@
+#include <string.h>
+#include <engine/e_config.h>
+#include <engine/e_server_interface.h>
+#include <game/mapitems.hpp>
+#include <game/server/entities/character.hpp>
+#include <game/server/player.hpp>
+#include <game/server/gamecontext.hpp>
+#include "fb.hpp"
+
+GAMECONTROLLER_FB::GAMECONTROLLER_FB ()
+{
+	balls[0] = 0;
+	balls[1] = 0;
+	gametype = config.sv_fb_instagib ? "iFB" : "FB";
+	game_flags = GAMEFLAG_TEAMS | GAMEFLAG_FLAGS;
+	goal_pos[0] = vec2 (0, 0);
+	goal_pos[1] = vec2 (0, 0);
+	num_goals = 0;
+	tuning.laser_damage = config.sv_fb_instagib ? 10 : 5;
+}
+
+bool GAMECONTROLLER_FB::on_entity (int index, vec2 pos)
+{
+	//	don't add pickups in instagib
+	if (strcmp (gametype, "iFB") == 0 && index > ENTITY_FLAGSTAND_BLUE)
+		return false;
+		
+	if (GAMECONTROLLER::on_entity (index, pos))
+		return true;
+	
+	//	add goal positions and balls
+	switch (index)
+	{
+	case ENTITY_SPAWN: if (num_goals < FB_MAX_GOALS) goal_pos[num_goals++] = pos; break;
+	case ENTITY_FLAGSTAND_RED: if (!balls[0]) game.world.insert_entity (balls[0] = new BALL (0, pos)); break;
+	case ENTITY_FLAGSTAND_BLUE: if (!balls[1]) game.world.insert_entity (balls[1] = new BALL (1, pos)); break;
+	default: return false;
+	}
+
+	return true;
+}
+
+int GAMECONTROLLER_FB::on_character_death(class CHARACTER *victim, class PLAYER *killer, int weaponid)
+{
+	// do scoreing
+	if (weaponid == WEAPON_SELF || weaponid == WEAPON_WORLD)
+		victim->player->score--;	// suicide or death-tile
+	else if (killer && weaponid != WEAPON_GAME)
+		{
+			if (is_teamplay() && victim->team == killer->team)
+				killer->score--;	// teamkill
+			else
+				killer->score++;	// normal kill
+		}
+
+	//	add fb-score and killing message flags
+	int had_flag = 0;
+	for (int i = 0; i < FB_MAX_BALLS; i++)
+	{
+		BALL *b = balls[i];
+		if (!b || !b->carrying_character)
+			continue;
+
+		if (killer && b->carrying_character == killer->get_character())
+			had_flag |= 2;
+		if (b->carrying_character == victim)
+		{
+			//	drop ball
+			game.create_sound_global (SOUND_CTF_DROP);
+			b->drop_tick = server_tick ();
+			b->last_carrying_player = victim->player;
+			b->last_carrying_player_team = victim->team;
+			b->carrying_character = 0;
+			victim->has_ball = false;
+			b->vel = vec2 (0, 0);
+
+			if (killer && killer->team != victim->team)
+				killer->score++;
+				
+			had_flag |= 1;
+		}
+	}
+
+	return had_flag;
+}
+
+void GAMECONTROLLER_FB::on_character_spawn(class CHARACTER *chr)
+{
+	// default health
+	chr->health = 10;
+	
+	if (strcmp (gametype, "iFB") == 0)
+	{
+		// give instagib weapon
+		chr->weapons[WEAPON_RIFLE].got = 1;
+		chr->weapons[WEAPON_RIFLE].ammo = -1;
+		chr->active_weapon = WEAPON_RIFLE;
+		chr->last_weapon = WEAPON_RIFLE;
+	}
+	else
+	{
+		// give default weapons
+		chr->weapons[WEAPON_HAMMER].got = 1;
+		chr->weapons[WEAPON_HAMMER].ammo = -1;
+		chr->weapons[WEAPON_GUN].got = 1;
+		chr->weapons[WEAPON_GUN].ammo = 10;
+	}
+}
+
+bool GAMECONTROLLER_FB::handle_goal (BALL *b, int goal)
+{
+	PLAYER *const p = b->carrying_character ? b->carrying_character->player : b->last_carrying_player;
+	const int team = b->carrying_character ? p->team : b->last_carrying_player_team;
+	const bool own_goal = team == goal;
+	const bool tee_goal = b->carrying_character ? true : false;
+	
+	//	update scores
+	if (own_goal)
+	{
+		if (!config.sv_fb_owngoal)
+			return false;
+		
+		if (p)
+		{
+			p->score -= config.sv_fb_owngoal_penalty;
+			p->num_owngoals++;
+		
+			// send warning to user
+			char chat_msg[256];
+			str_format (chat_msg, sizeof (chat_msg), "!!! Hey %s, you scored an own-goal! Put the ball into the OTHER goal !!!",
+				server_clientname (p->client_id));
+			game.send_chat_target (p->client_id, chat_msg);
+		}
+		// there's no sound like "wah...wah" ;)
+		game.create_sound_global (SOUND_NINJA_HIT);  //SOUND_PLAYER_PAIN_LONG
+	}
+	else
+	{
+		if (p)
+			p->score += tee_goal ? config.sv_fb_score_player_tee : config.sv_fb_score_player_ball;
+		game.create_sound_global (SOUND_CTF_CAPTURE);
+	}
+	teamscore[goal ^ 1] += tee_goal ? config.sv_fb_score_team_tee : config.sv_fb_score_team_ball;
+		
+	// let the tee die if it jumped into goal... or when dumb own-goal
+	if (p && (tee_goal || own_goal))
+		p->kill_character (WEAPON_GAME);
+	b->reset();
+	
+	// Cut off long nicknames... the message would jut out on screen!
+	const int nick_length = 18;
+	char nick[64] = {0};
+	str_format (nick, sizeof (nick), "%s", p ? server_clientname (p->client_id) : "player left");
+	if (str_length (nick) > nick_length)
+	{
+		nick[nick_length] = 0;
+		str_append (nick, "...", sizeof (nick));
+	}
+
+	// Build broadcast-message
+	char bc_msg[512] = {0};
+	str_format (bc_msg, sizeof (bc_msg), "%s team (%s) scored with %s!", (goal ^ 1) == 0 ? "Red" : "Blue",
+		nick, tee_goal ? "tee": "ball");
+
+	// wired workaround for multiline broadcast-message center alignment
+	// FIXME: can be done smarter!
+	if (own_goal)
+	{
+		char line2[] = "\nPwned... that was an own-goal!";
+		const int fillup = 2 * (str_length (bc_msg) - (str_length (line2) - 1));
+		str_append (bc_msg, line2, sizeof (bc_msg));
+
+		if (fillup > 0)
+		{
+			char *cur_pos = bc_msg + str_length (bc_msg);
+			for (int l = 0; l < fillup; l++)
+				*cur_pos++ = ' ';
+			*cur_pos = 0;
+		}
+	}
+	// send everyone the scoring-message
+	game.send_broadcast(bc_msg, -1);
+
+	// kick unteachable player from server
+	if (config.sv_fb_owngoal_kick && p->num_owngoals >= config.sv_fb_owngoal_kick)
+	{
+		char chat_msg[256];
+		str_format (chat_msg, sizeof (chat_msg), "%s was automatically kicked. Cause: Scored too many own-goals (%d).",
+			server_clientname (p->client_id), config.sv_fb_owngoal_kick);
+			
+		server_kick (p->client_id,
+			"You scored too many own-goals! Please play the "
+			"game as it is supposed to be. Thank you!");
+		
+		game.send_chat (-1, GAMECONTEXT::CHAT_ALL, chat_msg);
+	}
+
+	return true;
+}
+
+void GAMECONTROLLER_FB::on_player_disconnect (const PLAYER *const p)
+{
+	for (int i = 0; i < FB_MAX_BALLS; ++i)
+	{
+		if (balls[i] && !balls[i]->at_stand && p == balls[i]->last_carrying_player)
+			balls[i]->last_carrying_player = 0;
+	}
+}
+
+void GAMECONTROLLER_FB::tick()
+{
+	GAMECONTROLLER::tick();
+
+	do_team_score_wincheck();
+
+	if (game_over_tick == -1 && round_start_tick != server_tick ())
+	{
+		
+		for (int i = 0; i < MAX_CLIENTS; ++i)
+		{
+			if (!game.players[i] || game.players[i]->team == -1)
+				continue;
+
+			//	auto-kick if idle
+			if (config.sv_fb_idle_kick &&
+				game.players[i]->last_mousemove_tick + config.sv_fb_idle_kick * server_tickspeed () <= server_tick ())
+			{	
+				char chat_msg[256];
+				str_format (chat_msg, sizeof (chat_msg), "%s was automatically kicked. Cause: Has been idling too long (%d).",
+					server_clientname (game.players[i]->client_id), config.sv_fb_idle_kick);
+
+				server_kick (game.players[i]->client_id,
+					"You have been idling too long! When idle please join spectators "
+					"or leave to free slots!");
+
+				game.send_chat(-1, -2, chat_msg);
+				continue;
+			}
+
+			CHARACTER *ch = game.players[i]->get_character ();
+			if (!ch || !ch->alive)
+				continue;
+
+			// Check for a goal-camper
+			if (config.sv_fb_goalcamp)
+			{
+				bool in_range = false;
+				for (int goal = 0; goal < num_goals; ++goal)
+				{
+					if (distance (ch->pos, goal_pos[goal]) <
+						config.sv_fb_goalsize * static_cast<float> (config.sv_fb_goalcamp_factor / 10.0f))  //	1.5f
+					{
+						// if in range, count up 2 ticks
+						ch->goal_camp_tick += 2;
+						in_range = true;
+						break;
+					}
+				}
+		
+				if (!in_range)
+				{
+					// if out of range, count down just 1 tick again (slower)
+					if (ch->goal_camp_tick)
+						ch->goal_camp_tick--;
+				}
+				else
+				{	
+					//	kill goal camping player
+					if ((ch->goal_camp_tick >> 1) >= server_tickspeed () * config.sv_fb_goalcamp_time)
+					{
+						game.players[i]->kill_character (WEAPON_GAME);
+				
+						char chat_msg[128];
+						str_format (chat_msg, sizeof (chat_msg), "%s was caught camping near a goal...",
+							server_clientname(game.players[i]->client_id));
+						game.send_chat(-1, -2, chat_msg);
+						continue;
+					}
+				}
+			}
+
+			// anti-camping
+			if (config.sv_fb_camp_maxtime)
+			{
+				if (distance (ch->pos, ch->camp_pos) <= config.sv_fb_camp_threshold && !ch->has_ball)
+				{
+					// if in range, count up 2 ticks
+					ch->camp_tick += 2;
+				}
+				else
+				{
+					// if out of range, count down just 1 tick again (slower)
+					if (ch->camp_tick)
+						ch->camp_tick--;
+					else
+						ch->camp_pos = ch->pos;
+				}
+	
+				//	kill camping player
+				if ((ch->camp_tick >> 1) >= server_tickspeed () * config.sv_fb_camp_maxtime)
+				{
+					game.players[i]->kill_character (WEAPON_GAME);
+					game.send_chat(-1, -2, "I'm a bloody camper");
+					continue;
+				}
+			}
+		}
+
+		//	update balls
+		for (int bi = 0; bi < FB_MAX_BALLS; ++bi)
+		{
+			BALL *b = balls[bi];
+
+			if (!b)
+				continue;
+
+			//	update ball position
+			if (b->carrying_character)
+				b->pos = b->carrying_character->pos;
+			else if (!b->at_stand)
+				{	
+					//	ball hits death tile
+					if (b->idle_tick == -1 && col_get (static_cast<int> (b->pos.x), static_cast<int> (b->pos.y)) & COLFLAG_DEATH)
+						b->idle_tick = config.sv_fb_ball_deathtile_idle * server_tickspeed ();
+
+					//	flag reset delay (after hitting a death tile)
+					if (b->idle_tick > 0)
+					{
+						b->idle_tick--;
+						continue;
+					}
+
+					if (server_tick () >= b->drop_tick + server_tickspeed () * config.sv_fb_ball_reset_time || !b->idle_tick)
+					{
+						//	reset ball
+						game.create_sound_global (SOUND_CTF_RETURN);
+						b->reset ();
+					}
+					else
+					{
+						// do ball physics
+						b->vel.y += game.world.core.tuning.gravity;
+
+						if (b->vel.x > -0.1f && b->vel.x < 0.1f) 
+							b->vel.x = 0.0f;
+						else if (b->vel.x < 0)
+							b->vel.x += 0.055f;
+						else
+							b->vel.x -= 0.055f;
+
+						move_box (&b->pos, &b->vel, vec2 (b->phys_size, b->phys_size), 0.5f);
+					}
+
+				}
+
+			//	check/handle goal
+			bool is_goal = false;
+			for (int g = 0; g < num_goals; g++)
+			{
+				if (distance(b->pos, goal_pos[g]) < config.sv_fb_goalsize)
+				{
+					is_goal = handle_goal(b, g);
+					break;
+				}
+			}
+			if (is_goal)
+				continue;
+
+			if (b->carrying_character)
+			{	//	ball is carried by player
+
+				//	warn ball-carrying player once when he is near his own goal
+				if (config.sv_fb_owngoal_warn && !b->carrying_character->player->owngoal_warned && b->carrying_character->team < num_goals &&
+					(distance(b->carrying_character->pos, goal_pos[b->carrying_character->team]) < config.sv_fb_goalsize * 5))
+				{
+					game.send_broadcast(
+						"Attention! This is your team's goal\n"
+						"you are approaching. You probably\n"
+						"want to go to the other side!",
+						b->carrying_character->player->client_id);
+
+					// play the server-chat sound
+					game.create_sound(b->carrying_character->pos, SOUND_CHAT_SERVER, cmask_one(b->carrying_character->player->client_id));
+
+					b->carrying_character->player->owngoal_warned = true;
+				}
+			}
+			else
+			{	//	ball is carried by none
+
+				//	small delay for throwing ball a bit away before fetching again
+				if (b->at_stand || server_tick () >= b->drop_tick + server_tickspeed () * 0.2f)
+				{
+					//	find all players that can grab the ball
+					CHARACTER *close_characters[MAX_CLIENTS];
+					const int max_num = game.world.find_entities (b->pos, 32.0f, reinterpret_cast<ENTITY**> (close_characters), MAX_CLIENTS, NETOBJTYPE_CHARACTER);
+					int curr_num = 0;
+					for (int i = 0; i < max_num; ++i)
+					{
+						if (!close_characters[i]->alive || close_characters[i]->team == -1 || col_intersect_line (b->pos, close_characters[i]->pos, NULL, NULL) ||
+							balls[bi ^ 1]->carrying_character == close_characters[i]) // don't take flag if already have one (might be useful with two flags)
+							continue;
+						close_characters[curr_num++] = close_characters[i];
+					}
+					
+					if (!curr_num)
+						continue;
+
+					//	grab the ball
+					CHARACTER *ch = close_characters[server_tick () % curr_num];
+					if (b->at_stand)
+					{
+						teamscore[bi ^ 1] += config.sv_fb_score_team_stand;
+						ch->player->score += config.sv_fb_score_player_stand;
+						b->at_stand = 0;
+					}				
+					b->carrying_character = ch;
+					b->last_carrying_player = 0;
+					ch->has_ball = true;
+
+					// switch to ninja-ball-mode
+					ch->ninja.activationtick = server_tick();
+					ch->weapons[WEAPON_NINJA].got = true;
+					ch->weapons[WEAPON_NINJA].ammo = -1;
+					ch->last_weapon = ch->active_weapon;
+					ch->active_weapon = WEAPON_NINJA;
+
+					for (int c = 0; c < MAX_CLIENTS; c++)
+					{
+						if (!game.players[c])
+							continue;
+							
+						if (game.players[c]->team == bi)
+							game.create_sound_global (SOUND_CTF_GRAB_EN, game.players[c]->client_id);
+						else
+							game.create_sound_global (SOUND_CTF_GRAB_PL, game.players[c]->client_id);
+					}
+				}
+			}
+		}
+	}
+}
+
+//	Ball
+BALL::BALL (int _team, vec2 _pos)
+:	ENTITY (NETOBJTYPE_FLAG),
+	stand_pos (_pos),
+	team (_team)
+{
+	proximity_radius = phys_size;
+	
+	reset();
+}
+
+void BALL::reset ()
+{
+	carrying_character = 0;
+	last_carrying_player = 0;
+	last_carrying_player_team = team;
+	at_stand = 1;
+	pos = stand_pos;
+	vel = vec2 (0, 0);
+	grab_tick = 0;
+	drop_tick = server_tick ();
+	idle_tick = -1;
+}
+
+void BALL::snap (int snapping_client)
+{
+	if (idle_tick != -1)
+		return;
+
+	NETOBJ_FLAG *ball = static_cast<NETOBJ_FLAG *> (snap_new_item (NETOBJTYPE_FLAG, team, sizeof (NETOBJ_FLAG)));
+	ball->x = static_cast<int> (pos.x);
+	ball->y = static_cast<int> (pos.y);
+	ball->team = team;
+	ball->carried_by = -1;
+	
+	if (at_stand)
+		ball->carried_by = -2;
+	else if (carrying_character && carrying_character->player)
+		ball->carried_by = carrying_character->player->client_id;
+}
diff -Nru teeworlds-0.5.1-src/src/game/server/gamemodes/fb.hpp teeworlds-flagball-0.51/src/game/server/gamemodes/fb.hpp
--- teeworlds-0.5.1-src/src/game/server/gamemodes/fb.hpp	1970-01-01 01:00:00.000000000 +0100
+++ teeworlds-flagball-0.51/src/game/server/gamemodes/fb.hpp	2009-02-14 03:00:32.000000000 +0100
@@ -0,0 +1,46 @@
+#include <game/server/gamecontroller.hpp>
+#include <game/server/entity.hpp>
+
+enum {
+	FB_MAX_GOALS = 2,
+	FB_MAX_BALLS = 2
+};
+
+class GAMECONTROLLER_FB : public GAMECONTROLLER
+{
+public:
+	class BALL *balls[FB_MAX_BALLS];
+	vec2 goal_pos[FB_MAX_GOALS];
+	int num_goals;
+	
+	GAMECONTROLLER_FB ();
+	virtual void tick ();
+	
+	virtual bool on_entity (int index, vec2 pos);
+	virtual int on_character_death (class CHARACTER *victim, class PLAYER *killer, int weapon);
+	virtual void on_character_spawn (class CHARACTER *chr);
+	virtual bool handle_goal (BALL *b, int goal);
+	virtual void on_player_disconnect (const PLAYER *const p);
+};
+
+class BALL : public ENTITY
+{
+public:
+	static const int phys_size = 14;
+	CHARACTER *carrying_character;
+	PLAYER *last_carrying_player;
+	int last_carrying_player_team;
+	vec2 vel;
+	vec2 stand_pos;
+	
+	int team;
+	int at_stand;
+	int drop_tick;
+	int grab_tick;
+	int idle_tick;
+	
+	BALL (int _team, vec2 _pos);
+
+	virtual void reset ();
+	virtual void snap (int snapping_client);
+};
diff -Nru teeworlds-0.5.1-src/src/game/server/hooks.cpp teeworlds-flagball-0.51/src/game/server/hooks.cpp
--- teeworlds-0.5.1-src/src/game/server/hooks.cpp	2009-01-25 14:50:32.000000000 +0100
+++ teeworlds-flagball-0.51/src/game/server/hooks.cpp	2009-02-14 12:55:06.000000000 +0100
@@ -22,6 +22,7 @@
 #include "gamemodes/tdm.hpp"
 #include "gamemodes/ctf.hpp"
 #include "gamemodes/mod.hpp"
+#include "gamemodes/fb.hpp"
 
 TUNING_PARAMS tuning;
 
@@ -117,6 +118,17 @@
 	game.send_chat(-1, GAMECONTEXT::CHAT_ALL, buf); 
 
 	dbg_msg("game", "team_join player='%d:%s' team=%d", client_id, server_clientname(client_id), game.players[client_id]->team);
+
+	if (strstr (game.controller->gametype, "FB"))
+	{
+		// Send MOD-version string
+		str_format (buf, sizeof (buf), "FlagBall-MOD version: %s", MOD_VERSION);
+		game.send_chat_target (client_id, buf);
+		
+		// Send welcome message
+		if (str_length (config.sv_fb_welcome_message))
+			game.send_chat_target (client_id, config.sv_fb_welcome_message);
+	}
 }
 
 void mods_connected(int client_id)
@@ -340,7 +352,13 @@
 		char oldname[MAX_NAME_LENGTH];
 		str_copy(oldname, server_clientname(client_id), MAX_NAME_LENGTH);
 		
-		server_setclientname(client_id, msg->name);
+		if (strstr (game.controller->gametype, "FB"))
+		{
+			// check for empty nickname
+			server_setclientname(client_id, msg->name[0] == 0 ? "John Doe" : msg->name);
+		}
+		else
+			server_setclientname(client_id, msg->name);
 		if(msgtype == NETMSGTYPE_CL_CHANGEINFO && strcmp(oldname, server_clientname(client_id)) != 0)
 		{
 			char chattext[256];
@@ -531,7 +549,9 @@
 	//players = new PLAYER[MAX_CLIENTS];
 
 	// select gametype
-	if(strcmp(config.sv_gametype, "mod") == 0)
+	if (strcmp (config.sv_gametype, "fb") == 0)
+		game.controller = new GAMECONTROLLER_FB;
+	else if(strcmp(config.sv_gametype, "mod") == 0)
 		game.controller = new GAMECONTROLLER_MOD;
 	else if(strcmp(config.sv_gametype, "ctf") == 0)
 		game.controller = new GAMECONTROLLER_CTF;
diff -Nru teeworlds-0.5.1-src/src/game/server/player.cpp teeworlds-flagball-0.51/src/game/server/player.cpp
--- teeworlds-0.5.1-src/src/game/server/player.cpp	2009-01-25 14:50:32.000000000 +0100
+++ teeworlds-flagball-0.51/src/game/server/player.cpp	2009-02-13 19:43:02.000000000 +0100
@@ -5,6 +5,9 @@
 #include "player.hpp"
 #include "gamecontext.hpp"
 
+#include <string.h>
+#include "gamemodes/fb.hpp"
+
 MACRO_ALLOC_POOL_ID_IMPL(PLAYER, MAX_CLIENTS)
 
 PLAYER::PLAYER(int client_id)
@@ -12,6 +15,11 @@
 	respawn_tick = server_tick();
 	character = 0;
 	this->client_id = client_id;
+
+	num_owngoals = 0;
+	owngoal_warned = false;
+	last_mousemove_tick = server_tick ();
+	last_target_pos = vec2 (0, 0);
 }
 
 PLAYER::~PLAYER()
@@ -91,6 +99,12 @@
 	kill_character(WEAPON_GAME);
 	
 	//game.controller->on_player_death(&game.players[client_id], 0, -1);
+	
+	//	update leaving player in gamecontroller (flagball)
+	if (strstr (game.controller->gametype, "FB"))
+	{
+		static_cast<GAMECONTROLLER_FB *> (game.controller)->on_player_disconnect (this);
+	}
 		
 	char buf[512];
 	str_format(buf, sizeof(buf),  "%s has left the game", server_clientname(client_id));
@@ -117,6 +131,14 @@
 	
 	if(!chr && team == -1)
 		view_pos = vec2(new_input->target_x, new_input->target_y);
+
+	//	update active player (idle kicking)
+	if (last_target_pos.x != new_input->target_x || last_target_pos.y != new_input->target_y)
+	{
+		last_target_pos.x = new_input->target_x;
+		last_target_pos.y = new_input->target_y;
+		last_mousemove_tick = server_tick ();
+	}
 }
 
 CHARACTER *PLAYER::get_character()
@@ -160,6 +182,9 @@
 	dbg_msg("game", "team_join player='%d:%s' team=%d", client_id, server_clientname(client_id), team);
 	
 	game.controller->on_player_info_change(game.players[client_id]);
+
+	//	update active player (idle kicking)
+	last_mousemove_tick = server_tick ();
 }
 
 void PLAYER::try_respawn()
diff -Nru teeworlds-0.5.1-src/src/game/server/player.hpp teeworlds-flagball-0.51/src/game/server/player.hpp
--- teeworlds-0.5.1-src/src/game/server/player.hpp	2009-01-25 14:50:32.000000000 +0100
+++ teeworlds-flagball-0.51/src/game/server/player.hpp	2009-02-13 19:38:28.000000000 +0100
@@ -70,6 +70,12 @@
 	void on_direct_input(NETOBJ_PLAYER_INPUT *new_input);
 	void on_predicted_input(NETOBJ_PLAYER_INPUT *new_input);
 	void on_disconnect();
+
+	//	flagball
+	int num_owngoals;	//	counter for own-goals
+	bool owngoal_warned;	//	own goal warning (only shown once)
+	int last_mousemove_tick;	//	tick counter for idle kicking
+	vec2 last_target_pos;	//	used to check for mouse movements (activity)
 };
 
 #endif
diff -Nru teeworlds-0.5.1-src/src/game/variables.hpp teeworlds-flagball-0.51/src/game/variables.hpp
--- teeworlds-0.5.1-src/src/game/variables.hpp	2009-01-25 14:50:32.000000000 +0100
+++ teeworlds-flagball-0.51/src/game/variables.hpp	2009-02-14 13:43:58.000000000 +0100
@@ -52,7 +52,7 @@
 MACRO_CONFIG_INT(sv_powerups, 1, 0, 1, CFGFLAG_SERVER, "Allow powerups like ninja")
 MACRO_CONFIG_INT(sv_scorelimit, 20, 0, 1000, CFGFLAG_SERVER, "Score limit (0 disables)")
 MACRO_CONFIG_INT(sv_timelimit, 0, 0, 1000, CFGFLAG_SERVER, "Time limit in minutes (0 disables)")
-MACRO_CONFIG_STR(sv_gametype, 32, "dm", CFGFLAG_SERVER, "Game type (dm, tdm, ctf)")
+MACRO_CONFIG_STR(sv_gametype, 32, "fb", CFGFLAG_SERVER, "Game type (dm, tdm, ctf)")
 MACRO_CONFIG_INT(sv_tournament_mode, 0, 0, 1, CFGFLAG_SERVER, "Tournament mode. When enabled, players joins the server as spectator")
 MACRO_CONFIG_INT(sv_spamprotection, 1, 0, 1, CFGFLAG_SERVER, "Spam protection")
 
@@ -64,6 +64,33 @@
 MACRO_CONFIG_INT(sv_vote_scorelimit, 0, 0, 1, CFGFLAG_SERVER, "Allow voting to change score limit")
 MACRO_CONFIG_INT(sv_vote_timelimit, 0, 0, 1, CFGFLAG_SERVER, "Allow voting to change time limit")
 
+/*	flagball	*/
+MACRO_CONFIG_INT(sv_fb_ball_damage_lose, 1, 0, 1, CFGFLAG_SERVER, "Turns on/off turnover on any damage (0=off, 1=on (default=1)).")
+MACRO_CONFIG_INT(sv_fb_ball_deathtile_idle, 3, 0, 60, CFGFLAG_SERVER, "Number of seconds needed to reset a ball after hitting a death tile (0=off (default=3)).")
+MACRO_CONFIG_INT(sv_fb_ball_reset_time, 30, 1, 500, CFGFLAG_SERVER, "Number of seconds needed to reset an unused ball (default=30).")
+MACRO_CONFIG_INT(sv_fb_ball_velocity, 207, 10, 3000, CFGFLAG_SERVER, "Velocity of the ball after it got shot (value/10.0 (default=207)).")
+MACRO_CONFIG_INT(sv_fb_camp_maxtime, 0, 0, 100, CFGFLAG_SERVER, "Number of seconds allowed lingering at the same place (0=off (default=0)).")
+MACRO_CONFIG_INT(sv_fb_camp_threshold, 200, 64, 1000, CFGFLAG_SERVER, "Threshold for anti-camp distance (default=200).")
+MACRO_CONFIG_INT(sv_fb_direct_pass, 1, 0, 1, CFGFLAG_SERVER, "Turns on/off direct-passing of ball with hook-key (0=off, 1=on (default=1)).")
+MACRO_CONFIG_INT(sv_fb_direct_pass_distance, 380, 32, 3200, CFGFLAG_SERVER, "Maximum direct-passing distance (default=380).")
+MACRO_CONFIG_INT(sv_fb_goalsize, 64, 32, 500, CFGFLAG_SERVER, "Radius defining goal (default=64).")
+MACRO_CONFIG_INT(sv_fb_goalcamp, 1, 0, 1, CFGFLAG_SERVER, "Turns on/off goal camp protection (0=off, 1=on (default=1)).")
+MACRO_CONFIG_INT(sv_fb_goalcamp_time, 3, 1, 600, CFGFLAG_SERVER, "Number of seconds allowed lingering near a goal (default=3).")
+MACRO_CONFIG_INT(sv_fb_goalcamp_factor, 40, 10, 100, CFGFLAG_SERVER, "Goal camp protection radius (value/10.0*sv_fb_goalsize (default=40)).")
+MACRO_CONFIG_INT(sv_fb_idle_kick, 90, 0, 600, CFGFLAG_SERVER, "Number of seconds after an idling player gets kicked (0=off (default=90)).")
+MACRO_CONFIG_INT(sv_fb_instagib, 0, 0, 1, CFGFLAG_SERVER, "Turns on/off instagib modification (0=off, 1=on (default=0)).")
+MACRO_CONFIG_INT(sv_fb_owngoal, 1, 0, 1, CFGFLAG_SERVER, "Turns on/off own goals (0=off, 1=on (default=1)).")
+MACRO_CONFIG_INT(sv_fb_owngoal_kick, 2, 0, 20, CFGFLAG_SERVER, "Number of own goals allowed before kicking the player (0=infinite (default=2)).")
+MACRO_CONFIG_INT(sv_fb_owngoal_penalty, 30, 0, 999, CFGFLAG_SERVER, "Negative score for own goal player (default=30).")
+MACRO_CONFIG_INT(sv_fb_owngoal_warn, 1, 0, 1, CFGFLAG_SERVER, "Turns on/off warning when player with ball is near own goal (0=off, 1=on (default=1)).")
+MACRO_CONFIG_INT(sv_fb_score_player_ball, 5, 0, 100, CFGFLAG_SERVER, "Player score on ball-goal (default=5).")
+MACRO_CONFIG_INT(sv_fb_score_player_stand, 1, 0, 10, CFGFLAG_SERVER, "Player score for taking ball from stand (default=1).")
+MACRO_CONFIG_INT(sv_fb_score_player_tee, 10, 0, 100, CFGFLAG_SERVER, "Player-score on tee-goal (default=10).")
+MACRO_CONFIG_INT(sv_fb_score_team_ball, 50, 1, 100, CFGFLAG_SERVER, "Team-score on ball-goal (default=50).")
+MACRO_CONFIG_INT(sv_fb_score_team_stand, 1, 0, 10, CFGFLAG_SERVER, " Team score for taking ball from stand (default=1).")
+MACRO_CONFIG_INT(sv_fb_score_team_tee, 100, 1, 100, CFGFLAG_SERVER, "Team-score on tee-goal (default=100).")
+MACRO_CONFIG_STR(sv_fb_welcome_message, 512, "", CFGFLAG_SERVER, "Message to display when clients join (default="").")
+
 /* debug */
 #ifdef CONF_DEBUG /* this one can crash the server if not used correctly */
 	MACRO_CONFIG_INT(dbg_dummies, 0, 0, 15, CFGFLAG_SERVER, "")
diff -Nru teeworlds-0.5.1-src/src/game/version.hpp teeworlds-flagball-0.51/src/game/version.hpp
--- teeworlds-0.5.1-src/src/game/version.hpp	2009-01-25 14:50:32.000000000 +0100
+++ teeworlds-flagball-0.51/src/game/version.hpp	2009-02-17 19:27:06.000000000 +0100
@@ -2,3 +2,6 @@
 #include "generated/nethash.c"
 #define GAME_VERSION "0.5.1"
 #define GAME_NETVERSION "0.5 " GAME_NETVERSION_HASH
+
+/*	FlagBall-MOD version	*/
+#define MOD_VERSION "0.1.14"
