diff --git a/scripts/commands.pl b/scripts/commands.pl
new file mode 100755
index 0000000..a2ab7a5
--- /dev/null
+++ b/scripts/commands.pl
@@ -0,0 +1,212 @@
+#!/usr/bin/perl
+
+use Cwd;
+
+@int_data = qw(default min max description);
+@str_data = qw(default description);
+
+sub strip_quotes {
+ for ($_[0]) {
+  s/^"//;
+  s/"$//;
+  return $_;
+ };
+};
+
+sub cpp {
+ open my $result, 
+      '-|', 
+      'cpp '
+      . join ' ', map { "'$_'" } (
+         '-I', '.',
+         '-iquote', '/usr/include/SDL',
+         '-I', '/usr/include/freetype2',
+         @_
+        )
+  or die "cpp $_[-1]: $!\n";
+ return $result;
+};
+
+sub read_variables {
+ my %int;
+ my %str;
+ foreach my $file ('engine/shared/config_variables.h', 'game/variables.h') {
+  my $cpp = cpp 
+    '-DMACRO_CONFIG_INT(slot, command, default, min, max, flags, description)=flags@command@int@default@min@max@description',
+    '-DMACRO_CONFIG_STR(slot, command, len, default, flags, description)=flags@command@str@default@description',
+    $file;
+  while (<$cpp>) {
+   next if /^#/ || /^\s*$/;
+   chomp;
+   my @data = split /@/;
+   next unless shift(@data) =~ /CFGFLAG_SERVER/;
+   my $command = shift @data;
+   strip_quotes $data[-1];
+   if (shift @data eq 'int') {
+    @{$int{$command}}{@int_data} = @data;
+   } else {
+    strip_quotes $data[0];
+    @{$str{$command}}{@str_data} = @data;
+   };
+  };
+  close $cpp or exit $? >> 8;
+ };
+ return (\%int, \%str);
+};
+
+sub do_file_tree {
+ my ($tree, $function) = @_;
+ if (-d $tree) {
+  opendir my $d, $tree or die "Cannot open dir $tree: $!\n";
+  while ($_ = readdir $d) {
+   next if $_ eq '.' || $_ eq '..';
+   do_file_tree($tree . '/' . $_, $function);
+  };
+  closedir $d;
+ } else {
+  $function->($tree);
+ };
+};
+
+sub read_commands {
+ my %commands;
+ do_file_tree '.', sub {
+  my ($file) = @_;
+  return unless $file =~ /\.cpp$/;
+  my $cpp = cpp 
+    '-DRegister(command, params, flags, func, user, help)=!!REGISTER@flags@command@params@help@',
+    $file;
+  while (<$cpp>) {
+   next unless /!!REGISTER/;
+   split /@/;
+   shift;
+   next unless shift() =~ /CFGFLAG_SERVER/;
+   my ($command, $params, $help) = map { strip_quotes $_ } @_;
+   $commands{$command} = { args => $params, description => $help };
+  };
+  close $cpp or exit $? >> 8;
+ };
+ return \%commands;
+};
+
+sub max {
+ my $result = shift;
+ foreach (@_) {
+  $result = $_ if $_ > $result;
+ };
+ return $result;
+};
+
+sub print_table {
+ my ($header, $data) = @_;
+ my @keys = sort { $a cmp $b } keys %$data;
+
+ my $h0 = $header->[0];
+ my $s0 = length $h0;
+ my @h = @$header[1 .. $#{$header}];
+ my @sizes = map { length } @h;
+ foreach (@keys) {
+  for (my $i = 0; $i < @h; ++$i) {
+   $sizes[$i] = max $sizes[$i], length $data->{$_}{$h[$i]};
+  };
+  $s0 = max $s0, length;
+ };
+
+ my $f;
+ $f .= ' | %-' . $_ . 's' foreach @sizes;
+ my $h = sprintf '%-' . ($s0 + 2) . 's' . $f, 
+                 map { (my $x = $_) =~ s/./uc($&)/e; $x } @$header;
+ print $h, "\n";
+ $h =~ s/[^|]/-/g;
+ print $h, "\n";
+ $f .= "\n";
+ foreach (@keys) {
+  print '`', $_, '`', ' ' x ($s0 - length);
+  printf $f, @{$data->{$_}}{@h};
+ };
+};
+
+sub make_footnotes {
+ my $index = keys %footnote || 1;
+ while ($_ = shift) {
+  $footnote{$_}{ref}  = '[(' . $index++ . ")][$_]";
+  $footnote{$_}{text} = shift;
+ };
+};
+
+sub footnote {
+ my ($variable, $note) = @_;
+ exists $footnote{$note} or die "No such footnote: $note\n";
+ $variable->{description} .= ' ' . $footnote{$note}->{ref};
+};
+
+sub print_footnotes {
+ my $index = sub { $_[0] =~ /\[\((\d+)\)\]/; return $1 };
+ my @refs = sort {
+  $index->($footnote{$a}{ref}) <=> $index->($footnote{$b}{ref}) 
+ } keys %footnote;
+ my $w = 0;
+ $w = max $w, length foreach @refs;
+ print "[$_]: ", ' ' x ($w - length), '#', $_, "\n" foreach @refs;
+ print "\n";
+ print '<a name="', $_, '">(', $index->($footnote{$_}->{ref}), ')</a> ', 
+       ' ' x ($w - length), $footnote{$_}{text}, "\n\n"
+  foreach @refs;
+};
+
+$root = $0;
+$root =~ s#scripts/[^/]*##;
+$root .= 'src';
+chdir $root or die "Cannot chdir to $root: $!\n";
+
+($int, $str) = read_variables;
+$commands = read_commands '.';
+
+foreach (keys %$commands) {
+ delete $commands->{$_} if exists($int->{$_}) || exists($str->{$_});
+};
+
+make_footnotes
+ boolean   => 'This is a boolean value. 0 means disabled, 1&mdash;enabled.',
+ zero      => 'Zero value disables this option.';
+
+foreach (values %$int) {
+ if ($_->{min} == 0 && $_->{max} == 1) {
+  footnote $_, 'boolean';
+ } elsif ($_->{max} == 0) {
+  $_->{max} = '&mdash;';
+ }
+};
+
+footnote $int->{$_}, 'zero'
+  foreach qw(
+   sv_fb_owngoal_kick
+   sv_inactivekick_time
+   sv_scorelimit
+   sv_teambalance_time
+   sv_timelimit
+  );
+
+print <<END;
+title:flagball commands
+
+# Variables
+
+## Integer variables
+
+END
+print_table [ 'variable', @int_data ], $int;
+print <<END;
+
+## String variables
+
+END
+print_table [ 'variable', @str_data ], $str;
+print "\n";
+print_footnotes;
+print <<END;
+
+## Commands
+
+END
+print_table [ 'command', 'args', 'description' ], $commands;
diff --git a/scripts/multimarkdown b/scripts/multimarkdown
new file mode 100755
index 0000000..cacaa9b
--- /dev/null
+++ b/scripts/multimarkdown
@@ -0,0 +1,28 @@
+#!/usr/bin/perl
+
+use Text::MultiMarkdown 'markdown';
+
+$m = Text::MultiMarkdown->new(
+ use_metadata => 1,
+ strip_metadata => 1
+);
+
+$body = $m->markdown(join '', <>);
+
+print <<END;
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" 
+                 "http://www.w3.org/TR/html4/strict.dtd">
+<html lang="en">
+ <head>
+  <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+END
+
+print "  <title>$_</title>\n" if $_ = $m->{'_metadata'}->{'title'};
+
+print <<END;
+ </head>
+ <body>
+$body
+ </body>
+</html>
+END
diff --git a/src/game/server/entities/character.cpp b/src/game/server/entities/character.cpp
index 9df81e4..8d2a73f 100644
--- a/src/game/server/entities/character.cpp
+++ b/src/game/server/entities/character.cpp
@@ -61,10 +61,10 @@ bool CCharacter::Spawn(CPlayer *pPlayer, vec2 Pos)
 	m_LastWeapon = WEAPON_HAMMER;
 	m_QueuedWeapon = -1;
 	
- 	ball = NULL;
- 	goal_camp_tick = 0;
- 	camp_tick = 0;
- 	camp_pos = Pos;
+ 	m_pBall = NULL;
+	m_GoalCampTick = 0;
+ 	m_CampTick = 0;
+ 	m_CampPos = Pos;
 	
 	m_pPlayer = pPlayer;
 	m_Pos = Pos;
@@ -118,7 +118,7 @@ bool CCharacter::IsGrounded()
 
 void CCharacter::HandleNinja()
 {
-	if(m_ActiveWeapon != WEAPON_NINJA || ball)
+	if(m_ActiveWeapon != WEAPON_NINJA || m_pBall)
 		return;
 	
 	vec2 Direction = normalize(vec2(m_LatestInput.m_TargetX, m_LatestInput.m_TargetY));
@@ -126,12 +126,7 @@ void CCharacter::HandleNinja()
 	if ((Server()->Tick() - m_Ninja.m_ActivationTick) > (g_pData->m_Weapons.m_Ninja.m_Duration * Server()->TickSpeed() / 1000))
 	{
 		// time's up, return
-		m_aWeapons[WEAPON_NINJA].m_Got = false;
-		m_ActiveWeapon = m_LastWeapon;
-		if(m_ActiveWeapon == WEAPON_NINJA)
-			m_ActiveWeapon = WEAPON_GUN;
-			
-		SetWeapon(m_ActiveWeapon);
+		RevokeNinja();
 		return;
 	}
 	
@@ -197,49 +192,36 @@ void CCharacter::HandleNinja()
 	}
 
 	return;
+};
+ 
+void CCharacter::ReleaseBall()
+{
+	m_pBall->m_DropTick = Server()->Tick();
+	m_pBall->m_IdleTick = -1;
+	m_pBall->m_pLastCarrier    = GetPlayer();
+	m_pBall->m_LastCarrierTeam = m_pBall->m_pLastCarrier->GetTeam();
+	m_pBall->m_pCarryingCharacter = NULL;
+	m_pBall = NULL;
+
+	RevokeNinja();
 }
 
-//	handle ball as weapon
-void CCharacter::ShootBall(vec2 direction)
+void CCharacter::DropBall()
 {
- 	if (!ball) return;
- 
- 	//	drop ball
- 	GameServer()->CreateSound(m_Pos, SOUND_CTF_DROP);
- 	ball->drop_tick = Server()->Tick();
- 	ball->last_carrier = m_pPlayer;
- 	ball->last_carrier_team = m_pPlayer->GetTeam();
- 	ball->carrier = 0;
-	ball->vel = direction;
-    ball = NULL;
- 
- 	// stop ninja-ball-mode
- 	m_aWeapons[WEAPON_NINJA].m_Got = false;
- 	m_ActiveWeapon = m_LastWeapon;
- 	if (m_ActiveWeapon == WEAPON_NINJA)
- 		m_ActiveWeapon = WEAPON_GUN;
- 	SetWeapon(m_ActiveWeapon);
+	m_pBall->m_Vel = vec2(0, 0);
+	ReleaseBall();
 }
- 
-void CCharacter::CaptureBall(BALL* b) 
+
+void CCharacter::ThrowBall(vec2 direction)
 {
- 	ball = b;
- 	b->carrier = this;
- 	b->last_carrier_team = m_pPlayer->GetTeam();
- 	b->m_Pos = m_Core.m_Pos;
- 	b->at_stand = 0;
- 
- 	if (m_pPlayer->GetTeam() == b->team)
- 		GameServer()->CreateSound(m_Pos, SOUND_CTF_GRAB_EN);
- 	else
- 		GameServer()->CreateSound(m_Pos, SOUND_CTF_GRAB_PL);
- 
- 	//      switch to ninja-ball-mode
- 	m_Ninja.m_ActivationTick = Server()->Tick();
- 	m_aWeapons[WEAPON_NINJA].m_Got = true;
- 	m_aWeapons[WEAPON_NINJA].m_Ammo = -1;
- 	m_LastWeapon = m_ActiveWeapon;
-	m_ActiveWeapon = WEAPON_NINJA;
+	if (!m_pBall) return;
+	m_pBall->m_Vel = direction;
+	ReleaseBall();
+}
+
+void CCharacter::CaptureBall(CBall* ball)
+{
+	static_cast<CGameControllerFB *>(GameServer()->m_pController)->GrantBall(this, ball);
 }
 
 void CCharacter::DoWeaponSwitch()
@@ -458,10 +440,10 @@ void CCharacter::FireWeapon()
 		
 		case WEAPON_NINJA:
 		{			
- 			if (ball)
+ 			if (m_pBall)
  			{
- 				ShootBall(normalize (vec2 (m_LatestInput.m_TargetX, m_LatestInput.m_TargetY)) *
- 				static_cast<float> (g_Config.m_SvfbBallVelocity / 10.0f));  //20.75f;
+ 				ThrowBall(normalize (vec2 (m_LatestInput.m_TargetX, m_LatestInput.m_TargetY)) * static_cast<float> (g_Config.m_SvfbBallVelocity / 10.0f));  //20.75f;
+				GameServer()->CreateSoundGlobal(SOUND_CTF_DROP);
  				m_ReloadTimer = g_pData->m_Weapons.m_aId[m_ActiveWeapon].m_Firedelay * Server()->TickSpeed() / 1000;	//	prevent shooting
  				return;
  			}
@@ -545,17 +527,31 @@ bool CCharacter::GiveWeapon(int Weapon, int Ammo)
 	return false;
 }
 
-void CCharacter::GiveNinja()
+void CCharacter::BecomeNinja()
 {
 	m_Ninja.m_ActivationTick = Server()->Tick();
 	m_aWeapons[WEAPON_NINJA].m_Got = true;
 	m_aWeapons[WEAPON_NINJA].m_Ammo = -1;
 	m_LastWeapon = m_ActiveWeapon;
 	m_ActiveWeapon = WEAPON_NINJA;
-	
+}
+
+void CCharacter::GiveNinja()
+{
+	BecomeNinja();
 	GameServer()->CreateSound(m_Pos, SOUND_PICKUP_NINJA);
 }
 
+void CCharacter::RevokeNinja()
+{
+	m_aWeapons[WEAPON_NINJA].m_Got = false;
+	m_ActiveWeapon = m_LastWeapon;
+	if(m_ActiveWeapon == WEAPON_NINJA)
+		m_ActiveWeapon = WEAPON_GUN;
+		
+	SetWeapon(m_ActiveWeapon);
+}
+
 void CCharacter::SetEmote(int Emote, int Tick)
 {
 	m_EmoteType = Emote;
@@ -606,42 +602,36 @@ void CCharacter::Tick()
 	m_Core.Tick(true);
 	
     // direct pass of ball
- 	if (g_Config.m_SvfbDirectPass && ball && m_Core.m_TriggeredEvents & COREEVENT_HOOK_ATTACH_PLAYER && m_Core.m_HookedPlayer != -1)
+ 	if (g_Config.m_SvfbDirectPass && m_pBall && m_Core.m_TriggeredEvents & COREEVENT_HOOK_ATTACH_PLAYER && m_Core.m_HookedPlayer != -1)
  	{
- 		// find catcher. Is there an easier way to do this?
- 		//CCharacter* catcher = (CCharacter *)GameServer()->m_World.FindFirst(NETOBJTYPE_CHARACTER);
- 		CCharacter* catcher = static_cast<CCharacter *>(GameServer()->m_World.FindFirst(CGameWorld::ENTTYPE_CHARACTER));	
- 		while (catcher && &catcher->m_Core != GameServer()->m_World.m_Core.m_apCharacters[m_Core.m_HookedPlayer])
- 			catcher = (CCharacter *)catcher->TypeNext();
-
- 		if (catcher)
- 		{
-
- 			BALL* b = ball;
- 			ShootBall(vec2(0, 0));
- 			catcher->CaptureBall(b);
+		CCharacter *catcher = GameServer()->m_apPlayers[m_Core.m_HookedPlayer]->GetCharacter();
+		if (!catcher->m_pBall)
+		{
+			CBall *ball = m_pBall;
+			ReleaseBall();
+			catcher->CaptureBall(ball);
  
  			// release hook
  			m_Core.m_HookedPlayer = -1;
  			m_Core.m_HookState = HOOK_RETRACTED;
  			m_Core.m_HookPos = m_Core.m_Pos;
- 		};
+		}
  	};
  
  	// hook the flag
- 	if (g_Config.m_SvfbHookBall && !ball && (m_Core.m_HookState == HOOK_FLYING || m_Core.m_TriggeredEvents & (COREEVENT_HOOK_ATTACH_GROUND + COREEVENT_HOOK_HIT_NOHOOK)))
+ 	if (g_Config.m_SvfbHookBall && !m_pBall && (m_Core.m_HookState == HOOK_FLYING || m_Core.m_TriggeredEvents & (COREEVENT_HOOK_ATTACH_GROUND + COREEVENT_HOOK_HIT_NOHOOK)))
  	{
  		// Find the closest ball
- 		BALL* closest_ball = NULL;
+ 		CBall *closest_ball = NULL;
  		for (int i = 0; i < FB_MAX_BALLS; ++i)
  		{
- 			BALL* b = static_cast<CGameControllerFB *>(GameServer()->m_pController)->balls[i];
- 			if (b && !b->carrier)
+ 			CBall *b = static_cast<CGameControllerFB *>(GameServer()->m_pController)->m_apBalls[i];
+ 			if (b && !b->m_pCarryingCharacter)
  			{
  				// It may be not a good idea to pass this to
  				// config, but we need some easy way to test
  				// these values
- 				/* static */ vec2 ball_offset(g_Config.m_SvfbBallOffsetX, -g_Config.m_SvfbBallOffsetY);
+ 				static vec2 ball_offset(0, -38);
  				vec2 ball_pos = b->m_Pos + ball_offset;
  				vec2 closest_point = closest_point_on_line(m_Core.m_Pos, m_Core.m_HookPos, ball_pos);
  				if (distance(ball_pos, closest_point) < g_Config.m_SvfbBallRadius && (!closest_ball || distance(m_Pos, ball_pos) < distance(m_Pos, closest_ball->m_Pos)))
@@ -815,6 +805,16 @@ bool CCharacter::TakeDamage(vec2 Force, int Dmg, int From, int Weapon)
 	if(GameServer()->m_pController->IsFriendlyFire(m_pPlayer->GetCID(), From) && !g_Config.m_SvTeamdamage)
 		return false;
 
+	// do damage Hit sound
+	if(From >= 0 && From != m_pPlayer->GetCID() && GameServer()->m_apPlayers[From])
+		GameServer()->CreateSound(GameServer()->m_apPlayers[From]->m_ViewPos, SOUND_HIT, CmaskOne(From));
+
+	if (g_Config.m_SvfbInstagib && Weapon == WEAPON_RIFLE)
+	{
+		Die(From, Weapon);
+		return true;
+	}
+
 	// m_pPlayer only inflicts half damage on self
 	if(From == m_pPlayer->GetCID())
 		Dmg = max(1, Dmg/2);
@@ -860,10 +860,6 @@ bool CCharacter::TakeDamage(vec2 Force, int Dmg, int From, int Weapon)
 
 	m_DamageTakenTick = Server()->Tick();
 
-	// do damage Hit sound
-	if(From >= 0 && From != m_pPlayer->GetCID() && GameServer()->m_apPlayers[From])
-		GameServer()->CreateSound(GameServer()->m_apPlayers[From]->m_ViewPos, SOUND_HIT, CmaskOne(From));
-
 	// check for death
 	if(m_Health <= 0)
 	{
@@ -885,8 +881,8 @@ bool CCharacter::TakeDamage(vec2 Force, int Dmg, int From, int Weapon)
  	else
  	{
 		// lose flag on every damage
- 		if (ball && g_Config.m_SvfbBallDamageLose)
- 			ShootBall(vec2(0.0f,3.0f) * 3.75f);
+ 		if (m_pBall && g_Config.m_SvfbBallDamageLose)
+			ReleaseBall();
  	}
 
 	if (Dmg > 2)
diff --git a/src/game/server/entities/character.h b/src/game/server/entities/character.h
index 7bc5f64..f94ae2c 100644
--- a/src/game/server/entities/character.h
+++ b/src/game/server/entities/character.h
@@ -55,16 +55,20 @@ public:
 	bool IncreaseArmor(int Amount);
 	
  	//	flagball
-	class BALL* ball;
- 	int goal_camp_tick;	//	goal camp counter
- 	int camp_tick;	//	camp counter
- 	vec2 camp_pos;
- 	void ShootBall (vec2 direction);
- 	void CaptureBall(BALL* ball);
+	class CBall* m_pBall;
+	int m_GoalCampTick; //	goal camp counter
+	int m_CampTick; //	camp counter
+	vec2 m_CampPos;
+	void ReleaseBall();
+	void DropBall();
+ 	void ThrowBall(vec2 direction);
+	void CaptureBall(CBall *);
 
 	int m_Health;
 	bool GiveWeapon(int Weapon, int Ammo);
-	void GiveNinja();
+	void GiveNinja(); // GiveNinja calls BecomeNinja and makes the sound
+	void BecomeNinja();
+	void RevokeNinja();
 	
 	void SetEmote(int Emote, int Tick);
 	
@@ -83,6 +87,7 @@ public:
 	int m_ActiveWeapon;
 	int m_LastWeapon;
 	
+	int m_ReloadTimer;
 private:
 	// player controlling this character
 	class CPlayer *m_pPlayer;
@@ -95,7 +100,6 @@ private:
 	
 	int m_QueuedWeapon;
 	
-	int m_ReloadTimer;
 	int m_AttackTick;
 	
 	int m_DamageTaken;
diff --git a/src/game/server/entities/laser.cpp b/src/game/server/entities/laser.cpp
index 56da037..34a3c15 100644
--- a/src/game/server/entities/laser.cpp
+++ b/src/game/server/entities/laser.cpp
@@ -40,15 +40,16 @@ void CLaser::hit_ball(vec2 from, vec2 to) {
  	if (g_Config.m_SvfbLaserMomentum == 0) return;
  	for (int i = 0; i < FB_MAX_BALLS; ++i)
  	{
- 		BALL* ball = static_cast<CGameControllerFB *>(GameServer()->m_pController)->balls[i];
- 		if (!ball || ball->carrier) continue;
+ 		CBall *ball = static_cast<CGameControllerFB *>(GameServer()->m_pController)->m_apBalls[i];
+ 		if (!ball || ball->m_pCarryingCharacter) continue;
  		vec2 ball_pos = ball->m_Pos + vec2(g_Config.m_SvfbBallOffsetX, -g_Config.m_SvfbBallOffsetY);
  		if (distance(ball_pos, closest_point_on_line(from, to, ball_pos)) < g_Config.m_SvfbBallRadius)
  		{
- 			ball->vel += normalize(to - from) * g_Config.m_SvfbLaserMomentum * 0.1f;
- 			ball->drop_tick = Server()->Tick();
- 			ball->last_carrier = GameServer()->m_apPlayers[m_Owner];
- 			ball->last_carrier_team = GameServer()->m_apPlayers[m_Owner]->GetTeam();
+ 			ball->m_Vel += normalize(to - from) * g_Config.m_SvfbLaserMomentum * 0.1f;
+ 			ball->m_DropTick = Server()->Tick();
+ 			ball->m_pLastCarrier = GameServer()->m_apPlayers[m_Owner];
+ 			ball->m_LastCarrierTeam = GameServer()->m_apPlayers[m_Owner]->GetTeam();
+                        static_cast<CGameControllerFB *>(GameServer()->m_pController)->SetBallColor(ball, ball->m_LastCarrierTeam);
  		}
  	};
 }; 
diff --git a/src/game/server/gamecontext.cpp b/src/game/server/gamecontext.cpp
index 72e56ce..917028f 100644
--- a/src/game/server/gamecontext.cpp
+++ b/src/game/server/gamecontext.cpp
@@ -515,10 +515,10 @@ void CGameContext::OnClientEnter(int ClientID)
 
 	str_format(aBuf, sizeof(aBuf), "team_join player='%d:%s' team=%d", ClientID, Server()->ClientName(ClientID), m_apPlayers[ClientID]->GetTeam());
 	
- 	if (str_comp(m_pController->m_pGameType, "FB")==0)
+ 	if (str_comp(m_pController->m_pGameType, "FB") == 0 || str_comp(m_pController->m_pGameType, "iFB") == 0)
  	{
  		// Send MOD-version string
- 		str_format(aBuf, sizeof (aBuf), "FlagBall-MOD version: %s", MOD_VERSION);
+ 		str_format(aBuf, sizeof (aBuf), "FlagBall version: %s", MOD_VERSION);
  		SendChatTarget(ClientID, aBuf);
  		
  		// Send welcome message
@@ -615,15 +615,6 @@ void CGameContext::OnMessage(int MsgID, CUnpacker *pUnpacker, int ClientID)
 		}
 		
 		SendChat(ClientID, Team, pMsg->m_pMessage);
-		
-		if(!str_comp(pMsg->m_pMessage, "/info"))
-		{
-			SendChatTarget(ClientID, "Flagball Mod 0.1.17.2 by Nilaya\nContact: johnb820@gmail.com");
-		}
-		else if(!str_comp(pMsg->m_pMessage, "/help"))
-		{
-			SendChatTarget(ClientID, "Score points by putting the flag in the other team's goal.");
-		}
 	}
 	else if(MsgID == NETMSGTYPE_CL_CALLVOTE)
 	{
@@ -966,7 +957,7 @@ void CGameContext::OnMessage(int MsgID, CUnpacker *pUnpacker, int ClientID)
 		
 		SendEmoticon(ClientID, pMsg->m_Emoticon);
 	}
-	else if (MsgID == NETMSGTYPE_CL_KILL && !m_World.m_Paused)
+	else if (MsgID == NETMSGTYPE_CL_KILL && !m_World.m_Paused && g_Config.m_SvfbSelfKill)
 	{
 		if(pPlayer->m_LastKill && pPlayer->m_LastKill+Server()->TickSpeed()*3 > Server()->Tick())
 			return;
@@ -1301,6 +1292,11 @@ void CGameContext::ConVote(IConsole::IResult *pResult, void *pUserData)
 	pSelf->Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "server", aBuf);
 }
 
+void CGameContext::ConNextMap(IConsole::IResult *pResult, void *pUserData)
+{
+	((CGameContext *)pUserData)->m_pController->NextMap();
+}
+
 void CGameContext::ConchainSpecialMotdupdate(IConsole::IResult *pResult, void *pUserData, IConsole::FCommandCallback pfnCallback, void *pCallbackUserData)
 {
 	pfnCallback(pResult, pCallbackUserData);
@@ -1337,6 +1333,8 @@ void CGameContext::OnConsoleInit()
 	Console()->Register("clear_votes", "", CFGFLAG_SERVER, ConClearVotes, this, "");
 	Console()->Register("vote", "r", CFGFLAG_SERVER, ConVote, this, "");
 
+	Console()->Register("next_map", "", CFGFLAG_SERVER, ConNextMap, this, "Switch to another random map");
+
 	Console()->Chain("sv_motd", ConchainSpecialMotdupdate, this);
 }
 
diff --git a/src/game/server/gamecontext.h b/src/game/server/gamecontext.h
index 576a1b1..e597ec3 100644
--- a/src/game/server/gamecontext.h
+++ b/src/game/server/gamecontext.h
@@ -59,6 +59,7 @@ class CGameContext : public IGameServer
 	static void ConForceVote(IConsole::IResult *pResult, void *pUserData);
 	static void ConClearVotes(IConsole::IResult *pResult, void *pUserData);
 	static void ConVote(IConsole::IResult *pResult, void *pUserData);
+	static void ConNextMap(IConsole::IResult *pResult, void *pUserData);
 	static void ConchainSpecialMotdupdate(IConsole::IResult *pResult, void *pUserData, IConsole::FCommandCallback pfnCallback, void *pCallbackUserData);
 	
 	CGameContext(int Resetting);
diff --git a/src/game/server/gamecontroller.cpp b/src/game/server/gamecontroller.cpp
index de3efd3..2ac12d2 100644
--- a/src/game/server/gamecontroller.cpp
+++ b/src/game/server/gamecontroller.cpp
@@ -8,6 +8,7 @@
 #include "entities/pickup.h"
 #include "gamecontroller.h"
 #include "gamecontext.h"
+#include "maprotation.h"
 
 IGameController::IGameController(class CGameContext *pGameServer)
 {
@@ -293,59 +294,76 @@ void IGameController::CycleMap()
 
 	if(m_RoundCount < g_Config.m_SvRoundsPerMap-1)
 		return;
-		
+
+	NextMap();
+}
+
+void IGameController::NextMap()
+{
 	// handle maprotation
 	const char *pMapRotation = g_Config.m_SvMaprotation;
 	const char *pCurrentMap = g_Config.m_SvMap;
-	
-	int CurrentMapLen = str_length(pCurrentMap);
-	const char *pNextMap = pMapRotation;
-	while(*pNextMap)
+	const char *pNextMap;
+	char aBuf[512];
+
+	if (str_comp_num(pMapRotation, "!random ", 8) == 0)
 	{
-		int WordLen = 0;
-		while(pNextMap[WordLen] && !IsSeparator(pNextMap[WordLen]))
-			WordLen++;
-		
-		if(WordLen == CurrentMapLen && str_comp_num(pNextMap, pCurrentMap, CurrentMapLen) == 0)
+		static IMapRotation *rotation = RandomMapRotation();
+		pNextMap = rotation->NextMap(pMapRotation + 8, pCurrentMap);
+	}
+	else
+	{
+		// standard rotation
+		pNextMap = pMapRotation;
+		int CurrentMapLen = str_length(pCurrentMap);
+		while(*pNextMap)
 		{
-			// map found
-			pNextMap += CurrentMapLen;
-			while(*pNextMap && IsSeparator(*pNextMap))
-				pNextMap++;
-				
-			break;
+			int WordLen = 0;
+			while(pNextMap[WordLen] && !IsSeparator(pNextMap[WordLen]))
+				WordLen++;
+			
+			if(WordLen == CurrentMapLen && str_comp_num(pNextMap, pCurrentMap, CurrentMapLen) == 0)
+			{
+				// map found
+				pNextMap += CurrentMapLen;
+				while(*pNextMap && IsSeparator(*pNextMap))
+					pNextMap++;
+					
+				break;
+			}
+			
+			pNextMap++;
 		}
 		
-		pNextMap++;
-	}
-	
-	// restart rotation
-	if(pNextMap[0] == 0)
-		pNextMap = pMapRotation;
+		// restart rotation
+		if(pNextMap[0] == 0)
+			pNextMap = pMapRotation;
 
-	// cut out the next map	
-	char aBuf[512];
-	for(int i = 0; i < 512; i++)
-	{
-		aBuf[i] = pNextMap[i];
-		if(IsSeparator(pNextMap[i]) || pNextMap[i] == 0)
+		// cut out the next map	
+		for(int i = 0; i < 512; i++)
 		{
-			aBuf[i] = 0;
-			break;
+			aBuf[i] = pNextMap[i];
+			if(IsSeparator(pNextMap[i]) || pNextMap[i] == 0)
+			{
+				aBuf[i] = 0;
+				break;
+			}
 		}
+		
+		// skip spaces
+		int i = 0;
+		while(IsSeparator(aBuf[i]))
+			i++;
+
+		pNextMap = &aBuf[i];
 	}
 	
-	// skip spaces
-	int i = 0;
-	while(IsSeparator(aBuf[i]))
-		i++;
-	
 	m_RoundCount = 0;
 	
 	char aBufMsg[256];
-	str_format(aBufMsg, sizeof(aBufMsg), "rotating map to %s", &aBuf[i]);
-	GameServer()->Console()->Print(IConsole::OUTPUT_LEVEL_DEBUG, "game", aBuf);
-	str_copy(g_Config.m_SvMap, &aBuf[i], sizeof(g_Config.m_SvMap));
+	str_format(aBufMsg, sizeof(aBufMsg), "rotating map to %s", pNextMap);
+	GameServer()->Console()->Print(IConsole::OUTPUT_LEVEL_DEBUG, "game", pNextMap);
+	str_copy(g_Config.m_SvMap, pNextMap, sizeof(g_Config.m_SvMap));
 }
 
 void IGameController::PostReset()
diff --git a/src/game/server/gamecontroller.h b/src/game/server/gamecontroller.h
index d6391a4..2cf9886 100644
--- a/src/game/server/gamecontroller.h
+++ b/src/game/server/gamecontroller.h
@@ -143,6 +143,7 @@ public:
 	bool CheckTeamBalance();
 	bool CanChangeTeam(CPlayer *pPplayer, int JoinTeam);
 	int ClampTeam(int Team);
+	void NextMap();
 
 	virtual void PostReset();
 };
diff --git a/src/game/server/gamemodes/fb.cpp b/src/game/server/gamemodes/fb.cpp
index 4d1c5c3..0c93e07 100644
--- a/src/game/server/gamemodes/fb.cpp
+++ b/src/game/server/gamemodes/fb.cpp
@@ -8,16 +8,65 @@
 #include <game/collision.h>
 #include "fb.h"
 
+CBall::CBall(CGameWorld *pGameWorld, int Team, vec2 Pos)
+: CFlag(pGameWorld, Team)
+{
+	m_StandPos = Pos;
+	m_Pos = Pos;
+}
+
+void CBall::Reset()
+{
+	CFlag::Reset();
+	m_pLastCarrier = NULL;
+	m_DropTick = 0;
+	m_IdleTick = -1;
+	m_LastCarrierTeam = !m_Team;
+}
+
+void CBall::Tick()
+{
+	if (!m_pCarryingCharacter && !m_AtStand)
+	{
+		if (GameLayerClipped(m_Pos) || (m_IdleTick >= 0 && Server()->Tick() > m_IdleTick + Server()->TickSpeed() * g_Config.m_SvfbBallResetTime))
+		{
+			Reset();
+			GameServer()->CreateSoundGlobal(SOUND_CTF_RETURN);
+		}
+		else
+		{
+			// do ball physics
+			m_Vel.y += GameServer()->m_World.m_Core.m_Tuning.m_Gravity;
+			if (m_Vel.x > -0.1f && m_Vel.x < 0.1f)
+				m_Vel.x = 0.0f;
+			else if (m_Vel.x < 0)
+				m_Vel.x += 0.055f;
+			else
+				m_Vel.x -= 0.055f;
+
+			float vy = m_Vel.y;
+			GameServer()->Collision()->MoveBox(&m_Pos, &m_Vel, vec2(ms_PhysSize, ms_PhysSize), 0.5f);
+			if (m_Vel.x == 0 && (vy > 0) != (m_Vel.y > 0) && abs(m_Vel.y) < GameServer()->m_World.m_Core.m_Tuning.m_Gravity)
+			{
+				if (m_IdleTick == -1)
+					m_IdleTick = Server()->Tick();
+			}
+			else
+				m_IdleTick = -1;
+		}
+	}
+}
+
 CGameControllerFB::CGameControllerFB(class CGameContext *pGameServer) 
 : IGameController(pGameServer)
 {
-	balls[0] = 0;
-	balls[1] = 0;
+	m_apBalls[0] = NULL;
+	m_apBalls[1] = NULL;
 	m_pGameType = g_Config.m_SvfbInstagib ? "iFB" : "FB";
 	m_GameFlags = GAMEFLAG_TEAMS | GAMEFLAG_FLAGS;
-	goal_pos[0] = vec2 (0, 0);
-	goal_pos[1] = vec2 (0, 0);
-	num_goals = 0;
+	m_apGoalPos[0] = vec2(0, 0);
+	m_apGoalPos[1] = vec2(0, 0);
+	m_NumGoals = 0;
 	GameServer()->Tuning()->m_LaserDamage = g_Config.m_SvfbInstagib ? 10 : 5;
 	
 }
@@ -35,12 +84,21 @@ bool CGameControllerFB::OnEntity (int index, vec2 pos)
 	//	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] == 0) GameServer()->m_World.InsertEntity(balls[0] = new BALL(&GameServer()->m_World, 0, pos)); break;
-	case ENTITY_FLAGSTAND_BLUE: if (balls[1] == 0) GameServer()->m_World.InsertEntity(balls[1] = new BALL(&GameServer()->m_World, 1, pos)); break;
-	default: return false;
+		case ENTITY_SPAWN:
+			if (m_NumGoals < FB_MAX_GOALS)
+				m_apGoalPos[m_NumGoals++] = pos;
+			break;
+		case ENTITY_FLAGSTAND_RED:
+			if (!m_apBalls[TEAM_RED])
+				GameServer()->m_World.InsertEntity(m_apBalls[TEAM_RED] = new CBall(&GameServer()->m_World, TEAM_RED, pos));
+			break;
+		case ENTITY_FLAGSTAND_BLUE:
+			if (!m_apBalls[TEAM_BLUE])
+				GameServer()->m_World.InsertEntity(m_apBalls[TEAM_BLUE] = new CBall(&GameServer()->m_World, TEAM_BLUE, pos));
+			break;
+		default:
+			return false;
 	}
-	
 	return true;
 	
 }
@@ -59,35 +117,24 @@ int CGameControllerFB::OnCharacterDeath(class CCharacter *victim, class CPlayer
 				killer->m_Score++;	// normal kill
 		}
 
-	//	add fb-score and killing message flags
-	int had_flag = 0;
-	for (int i = 0; i < FB_MAX_BALLS; i++)
+	int mode = 0;
+	if (killer)
 	{
-		BALL *b = balls[i];
-		if (!b || !b->carrier)
-			continue;
-
-		if (killer && b->carrier == killer->GetCharacter())
-			had_flag |= 2;
-		if (b->carrier == victim)
-		{
-			//	drop ball
+		CCharacter *const ch = killer->GetCharacter();
+		if (ch && ch->m_pBall)
+			mode |= 2;
+	};
+	if (victim->m_pBall)
+	{
+		victim->DropBall();
+		if (weaponid != WEAPON_GAME) // exclude tee goal
 			GameServer()->CreateSoundGlobal(SOUND_CTF_DROP);
-			b->drop_tick = Server()->Tick();
-			b->last_carrier = victim->GetPlayer();
-			b->last_carrier_team = victim->GetPlayer()->GetTeam();
-			b->carrier = 0;
-			victim->ball = NULL;
-			b->vel = vec2 (0, 0);
-
-			if (killer && killer->GetTeam() != victim->GetPlayer()->GetTeam())
-				killer->m_Score++;
-				
-			had_flag |= 1;
-		}
+		if (killer && killer->GetTeam() != victim->GetPlayer()->GetTeam())
+			killer->m_Score++;
+		mode |= 1;
 	}
 
-	return had_flag;
+	return mode;
 	
 }
 
@@ -104,6 +151,7 @@ void CGameControllerFB::OnCharacterSpawn(class CCharacter *chr)
 		chr->m_aWeapons[WEAPON_RIFLE].m_Ammo = -1;
 		chr->m_ActiveWeapon = WEAPON_RIFLE;
 		chr->m_LastWeapon = WEAPON_RIFLE;
+		chr->m_ReloadTimer = g_Config.m_SvfbSpawnWeaponDischarge * g_pData->m_Weapons.m_aId[WEAPON_RIFLE].m_Firedelay * Server()->TickSpeed() / (1000 * 100);
 	}
 	else
 	{
@@ -116,13 +164,13 @@ void CGameControllerFB::OnCharacterSpawn(class CCharacter *chr)
 	
 }
 
-bool CGameControllerFB::HandleGoal (BALL *b, int goal)
+bool CGameControllerFB::HandleGoal (CBall *b, int goal)
 {
 	
-	CPlayer *const p = b->carrier ? b->carrier->GetPlayer() : b->last_carrier;
-	const int team = b->last_carrier_team;
+	CPlayer *const p = b->m_pCarryingCharacter ? b->m_pCarryingCharacter->GetPlayer() : b->m_pLastCarrier;
+	const int team = p ? p->GetTeam() : b->m_LastCarrierTeam;
 	const bool own_goal = team == goal;
-	const bool tee_goal = b->carrier ? true : false;
+	const bool tee_goal = b->m_pCarryingCharacter;
 	
 	//	update scores
 	if (own_goal)
@@ -133,7 +181,7 @@ bool CGameControllerFB::HandleGoal (BALL *b, int goal)
 		if (p)
 		{
 			p->m_Score -= g_Config.m_SvfbOwngoalPenalty;
-			p->num_owngoals++;
+			p->m_NumOwngoals++;
 		
 			// send warning to user
 			char chat_msg[256];
@@ -156,6 +204,7 @@ bool CGameControllerFB::HandleGoal (BALL *b, int goal)
 	if (p && (tee_goal || own_goal))
 		p->KillCharacter (WEAPON_GAME);
 	b->Reset();
+	GameServer()->CreateSoundGlobal(SOUND_CTF_RETURN);
 	
 	// Cut off long nicknames... the message would jut out on screen!
 	const int nick_length = 18;
@@ -192,7 +241,7 @@ bool CGameControllerFB::HandleGoal (BALL *b, int goal)
 	GameServer()->SendBroadcast(bc_msg, -1);
 
 	// kick unteachable player from server
-	if (g_Config.m_SvfbOwngoalKick && p && p->num_owngoals >= g_Config.m_SvfbOwngoalKick)
+	if (g_Config.m_SvfbOwngoalKick && p && p->m_NumOwngoals >= g_Config.m_SvfbOwngoalKick)
 	{
 		char chat_msg[256];
 		str_format (chat_msg, sizeof (chat_msg), "%s was automatically kicked. Cause: Scored too many own-goals (%d).",
@@ -209,22 +258,86 @@ bool CGameControllerFB::HandleGoal (BALL *b, int goal)
 	
 }
 
-void CGameControllerFB::OnDisconnect (const CPlayer *const p)
+void CGameControllerFB::OnDisconnect (CPlayer *const p)
 {
-	
+	CCharacter* ch = p->GetCharacter();
 	for (int i = 0; i < FB_MAX_BALLS; ++i)
 	{
-		if (balls[i] && !balls[i]->at_stand)
+		if (m_apBalls[i])
 		{
-			if (balls[i]->carrier && p == balls[i]->carrier->GetPlayer())
-				balls[i]->carrier = NULL;
-			if (p == balls[i]->last_carrier)
-				balls[i]->last_carrier = NULL;
+			if (ch && m_apBalls[i]->m_pCarryingCharacter == ch)
+				ch->ReleaseBall();
+			if (m_apBalls[i]->m_pLastCarrier == p)
+				m_apBalls[i]->m_pLastCarrier = NULL;
 		}
 	}
-	
 }
 
+template <typename T> void swap(T& x, T& y)
+{
+	T z = x;
+	x = y;
+	y = z;
+}
+
+void CGameControllerFB::SetBallColor(CBall *&ball, int team)
+{
+	/* The flag color cannot be changed by mere assignment to m_Team. When
+	 * someone carries a repainted flag, it causes lags due to the way flag
+	 * position interpolation is implemented in CItems::RenderFlag in
+	 * game/client/components/items.cpp. A hackish workaround is to swap
+	 * the flags when the other color is requested, but we should take
+	 * special care for the maps where both flags are available */
+
+	if (!ball || ball->m_Team == team) return;
+	CBall *b = m_apBalls[team];
+	if (!b->m_AtStand) return;
+
+	swap(b->m_StandPos, ball->m_StandPos);
+	b->m_AtStand            = false;
+	b->m_Pos                = ball->m_Pos;
+	b->m_Vel                = ball->m_Vel;
+	b->m_pCarryingCharacter = ball->m_pCarryingCharacter;
+	b->m_pLastCarrier       = ball->m_pLastCarrier;
+	b->m_LastCarrierTeam    = ball->m_LastCarrierTeam;
+	b->m_DropTick           = ball->m_DropTick;
+	b->m_GrabTick           = ball->m_GrabTick;
+	b->m_IdleTick           = ball->m_IdleTick;
+
+	ball->Reset();
+	ball = b;
+}
+
+void CGameControllerFB::CreateBallGrabSound(int team)
+{
+	for (int c = 0; c < MAX_CLIENTS; ++c)
+	{
+		CPlayer *p = GameServer()->m_apPlayers[c];
+		if (!p)
+			continue;
+		GameServer()->CreateSoundGlobal(p->GetTeam() == team ? SOUND_CTF_GRAB_PL : SOUND_CTF_GRAB_EN, p->GetCID());
+	}
+}
+
+void CGameControllerFB::GrantBall(CCharacter *ch, CBall *b)
+{
+	int team = ch->GetPlayer()->GetTeam();
+	if (b->m_AtStand)
+	{
+		m_aTeamscore[team] += g_Config.m_SvfbScoreTeamStand;
+		ch->GetPlayer()->m_Score += g_Config.m_SvfbScorePlayerStand;
+		b->m_AtStand = 0;
+	}
+	b->m_pCarryingCharacter = ch;
+	b->m_Pos = ch->m_Pos;
+	if (g_Config.m_SvfbLaserMomentum) 
+		SetBallColor(b, team);
+	ch->m_pBall = b;
+	ch->BecomeNinja();
+	CreateBallGrabSound(team);
+}
+
+
 void CGameControllerFB::Tick()
 {
 	
@@ -238,37 +351,6 @@ void CGameControllerFB::Tick()
 		{
 			if (!GameServer()->m_apPlayers[i]) continue;
 
-			//	auto-kick if idle
-			if (GameServer()->m_apPlayers[i]->GetTeam() == -1)
-               {
-				if (g_Config.m_SvfbSpectatorIdleTime && GameServer()->m_apPlayers[i]->last_mousemove_tick + g_Config.m_SvfbSpectatorIdleTime * 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(GameServer()->m_apPlayers[i]->m_ClientID), g_Config.m_SvfbIdleTime);
-					Server()->Kick(GameServer()->m_apPlayers[i]->m_ClientID, "You have been idling too long!");
-				};
-				continue;
-			};
-			if (g_Config.m_SvfbIdleTime &&	GameServer()->m_apPlayers[i]->last_mousemove_tick + g_Config.m_SvfbIdleTime * Server()->TickSpeed() <= Server()->Tick())
-			{	
-				if (strcmp(g_Config.m_SvfbOnIdle, "kick") == 0)
-				{
-					char chat_msg[256];
-					str_format (chat_msg, sizeof (chat_msg), "%s was automatically kicked. Cause: Has been idling too long (%d).", Server()->ClientName(GameServer()->m_apPlayers[i]->m_ClientID), g_Config.m_SvfbIdleTime);
-	                Server()->Kick (GameServer()->m_apPlayers[i]->m_ClientID, "You have been idling too long! When idle please join spectators or leave to free slots!");
-					continue;
-				}
-				else if (strcmp(g_Config.m_SvfbOnIdle, "spectate") == 0)
-				{
-					char chat_msg[256];
-					str_format (chat_msg, sizeof (chat_msg), "%s was automatically joined to spectators. Cause: Has been idling too long (%d).", Server()->ClientName(GameServer()->m_apPlayers[i]->m_ClientID), g_Config.m_SvfbIdleTime);
-					GameServer()->m_apPlayers[i]->SetTeam(-1);
-					CheckTeamBalance();
-					GameServer()->SendChat(-1, -2, chat_msg);
-					continue;
-				}       
-			}
-
 			CCharacter *ch = GameServer()->m_apPlayers[i]->GetCharacter();
 			if (!ch || !ch->IsAlive())
 				continue;
@@ -277,13 +359,13 @@ void CGameControllerFB::Tick()
 			if (g_Config.m_SvfbGoalcamp)
 			{
 				bool in_range = false;
-				for (int goal = 0; goal < num_goals; ++goal)
+				for (int goal = 0; goal < m_NumGoals; ++goal)
 				{
-					if (distance (ch->m_Pos, goal_pos[goal]) <
+					if (distance (ch->m_Pos, m_apGoalPos[goal]) <
 						g_Config.m_SvfbGoalsize * static_cast<float> (g_Config.m_SvfbGoalcampFactor / 10.0f))  //	1.5f
 					{
 						// if in range, count up 2 ticks
-						ch->goal_camp_tick += 2;
+						ch->m_GoalCampTick += 2;
 						in_range = true;
 						break;
 					}
@@ -292,13 +374,13 @@ void CGameControllerFB::Tick()
 				if (!in_range)
 				{
 					// if out of range, count down just 1 tick again (slower)
-					if (ch->goal_camp_tick)
-						ch->goal_camp_tick--;
+					if (ch->m_GoalCampTick)
+						ch->m_GoalCampTick--;
 				}
 				else
 				{	
 					//	kill goal camping player
-					if ((ch->goal_camp_tick >> 1) >= Server()->TickSpeed() * g_Config.m_SvfbGoalcampTime)
+					if ((ch->m_GoalCampTick >> 1) >= Server()->TickSpeed() * g_Config.m_SvfbGoalcampTime)
 					{
 						GameServer()->m_apPlayers[i]->KillCharacter(WEAPON_GAME);
 				
@@ -314,22 +396,22 @@ void CGameControllerFB::Tick()
 			// anti-camping
 			if (g_Config.m_SvfbCampMaxtime)
 			{
-				if (distance (ch->m_Pos, ch->camp_pos) <= g_Config.m_SvfbCampThreshold && !ch->ball)
+				if (distance (ch->m_Pos, ch->m_CampPos) <= g_Config.m_SvfbCampThreshold && !ch->m_pBall)
 				{
 					// if in range, count up 2 ticks
-					ch->camp_tick += 2;
+					ch->m_CampTick += 2;
 				}
 				else
 				{
 					// if out of range, count down just 1 tick again (slower)
-					if (ch->camp_tick)
-						ch->camp_tick--;
+					if (ch->m_CampTick)
+						ch->m_CampTick--;
 					else
-						ch->camp_pos = ch->m_Pos;
+						ch->m_CampPos = ch->m_Pos;
 				}
 	
 				//	kill camping player
-				if ((ch->camp_tick >> 1) >= Server()->TickSpeed () * g_Config.m_SvfbCampMaxtime)
+				if ((ch->m_CampTick >> 1) >= Server()->TickSpeed () * g_Config.m_SvfbCampMaxtime)
 				{
 					GameServer()->m_apPlayers[i]->KillCharacter(WEAPON_GAME);
 					GameServer()->SendChat(-1, -2, "I'm a bloody camper");
@@ -341,54 +423,20 @@ void CGameControllerFB::Tick()
 		//	update balls
 		for (int bi =  0; bi < FB_MAX_BALLS; ++bi)
 		{
-			BALL *b = balls[bi];
+			CBall *b = m_apBalls[bi];
 
 			if (!b)
 				continue;
 
-			//	update ball position
-			if (b->carrier)
-			{
-				b->m_Pos = b->carrier->m_Pos;
-			}
-			else if (!b->at_stand)
-			{	
-				//	ball hits death tile
-				if (b->idle_tick == -1 && GameServer()->Collision()->GetCollisionAt(static_cast<int> (b->m_Pos.x), static_cast<int> (b->m_Pos.y)) & CCollision::COLFLAG_DEATH)
-					b->idle_tick = g_Config.m_SvfbBallDeathtileIdle * 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 () * g_Config.m_SvfbBallResetTime || !b->idle_tick)
-				{
-					//	reset ball
-					GameServer()->CreateSoundGlobal(SOUND_CTF_RETURN);
-					b->Reset ();
-				}
-				else
-				{
-					// do ball physics
-					b->vel.y += GameServer()->m_World.m_Core.m_Tuning.m_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;
-
-					GameServer()->Collision()->MoveBox(&b->m_Pos, &b->vel, vec2 (b->phys_size, b->phys_size), 0.5f);
-				}
-
-			}
+			//	update position here and not in CBall::Tick because balls tend to tick before characters
+			if (b->m_pCarryingCharacter)
+				b->m_Pos = b->m_pCarryingCharacter->m_Pos;
 
 			//	check/handle goal
 			bool is_goal = false;
-			for (int g = 0; g < num_goals; g++)
+			for (int g = 0; g < m_NumGoals; g++)
 			{
-				if (distance(b->m_Pos, goal_pos[g]) < g_Config.m_SvfbGoalsize)
+				if (distance(b->m_Pos, m_apGoalPos[g]) < g_Config.m_SvfbGoalsize)
 				{
 					is_goal = HandleGoal(b, g);
 					break;
@@ -397,36 +445,39 @@ void CGameControllerFB::Tick()
 			if (is_goal)
 				continue;
 
-			if (b->carrier)
+			if (b->m_pCarryingCharacter)
 			{	//	ball is carried by player
 				//	warn ball-carrying player once when he is near his own goal
-				if (g_Config.m_SvfbOwngoalWarn && !b->carrier->GetPlayer()->owngoal_warned && b->carrier->GetPlayer()->GetTeam() < num_goals &&
-					(distance(b->carrier->m_Pos, goal_pos[b->carrier->GetPlayer()->GetTeam()]) < g_Config.m_SvfbGoalsize * 5))
+				CCharacter *ch = b->m_pCarryingCharacter;
+				CPlayer *p = ch->GetPlayer();
+				int t = p->GetTeam();
+				if (g_Config.m_SvfbOwngoalWarn && !p->m_OwngoalWarned && t < m_NumGoals && distance(ch->m_Pos, m_apGoalPos[t]) < g_Config.m_SvfbGoalsize * 5)
 				{
 					GameServer()->SendBroadcast(
 						"Attention! This is your team's goal\n"
 						"you are approaching. You probably\n"
 						"want to go to the other side!",
-						b->carrier->GetPlayer()->m_ClientID);
+						p->m_ClientID);
 
 					// play the server-chat sound
-					GameServer()->CreateSound(b->carrier->m_Pos, SOUND_CHAT_SERVER, CmaskOne(b->carrier->GetPlayer()->m_ClientID));
-					b->carrier->GetPlayer()->owngoal_warned = true;
+					GameServer()->CreateSound(ch->m_Pos, SOUND_CHAT_SERVER, CmaskOne(p->m_ClientID));
+					p->m_OwngoalWarned = 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)
+				if (b->m_AtStand || Server()->Tick() >= b->m_DropTick + Server()->TickSpeed() * 0.2f)
 				{
 					//	find all players that can grab the ball
 					CCharacter *close_characters[MAX_CLIENTS];
-					int max_num = GameServer()->m_World.FindEntities(b->m_Pos, b->phys_size, (CEntity**)close_characters, MAX_CLIENTS, CGameWorld::ENTTYPE_CHARACTER);
+					int max_num = GameServer()->m_World.FindEntities(b->m_Pos, b->ms_PhysSize, (CEntity**)close_characters, MAX_CLIENTS, CGameWorld::ENTTYPE_CHARACTER);
 					int curr_num = 0;
 					for (int i = 0; i < max_num; ++i)
 					{
 						if (!close_characters[i]->IsAlive() || close_characters[i]->GetPlayer()->GetTeam() == -1 || GameServer()->Collision()->IntersectLine(b->m_Pos, close_characters[i]->m_Pos, NULL, NULL) ||
-							balls[bi ^ 1]->carrier == close_characters[i]) // don't take flag if already have one (might be useful with two flags)
+							// don't take flag if already have one (might be useful with two flags)
+							m_apBalls[bi ^ 1]->m_pCarryingCharacter == close_characters[i]) 
 							continue;
 						close_characters[curr_num++] = close_characters[i];
 					}
@@ -434,32 +485,11 @@ void CGameControllerFB::Tick()
 					if (!curr_num)
 						continue;
 
-					//	grab the ball
-					CCharacter *ch = close_characters[Server()->Tick() % curr_num];
-					if (b->at_stand)
-					{
-						m_aTeamscore[bi ^ 1] += g_Config.m_SvfbScoreTeamStand;
-						ch->GetPlayer()->m_Score += g_Config.m_SvfbScorePlayerStand;
-						b->at_stand = 0;
-					}				
-
-					ch->CaptureBall(b);
-
-					for (int c = 0; c < MAX_CLIENTS; c++)
-					{
-						if (!GameServer()->m_apPlayers[c])
-							continue;
-							
-						if (GameServer()->m_apPlayers[c]->GetTeam() == bi)
-							GameServer()->CreateSoundGlobal(SOUND_CTF_GRAB_EN, GameServer()->m_apPlayers[c]->m_ClientID);
-						else
-							GameServer()->CreateSoundGlobal(SOUND_CTF_GRAB_PL, GameServer()->m_apPlayers[c]->m_ClientID);
-					}
+					GrantBall(close_characters[Server()->Tick() % curr_num], b);
 				}
 			}
 		}
 	}
-	
 }
 
 void CGameControllerFB::Snap(int SnappingClient)
@@ -474,15 +504,15 @@ void CGameControllerFB::Snap(int SnappingClient)
 	pGameDataObj->m_TeamscoreRed = m_aTeamscore[TEAM_RED];
 	pGameDataObj->m_TeamscoreBlue = m_aTeamscore[TEAM_BLUE];
 	
-	if(balls[TEAM_RED])
+	if(m_apBalls[TEAM_RED])
 	{
-		if(balls[TEAM_RED]->at_stand)
+		if(m_apBalls[TEAM_RED]->m_AtStand)
 		{
 			pGameDataObj->m_FlagCarrierRed = FLAG_ATSTAND;
 		}
-		else if(balls[TEAM_RED]->carrier && balls[TEAM_RED]->carrier->GetPlayer())
+		else if(m_apBalls[TEAM_RED]->m_pCarryingCharacter)
 		{
-			pGameDataObj->m_FlagCarrierRed = balls[TEAM_RED]->carrier->GetPlayer()->GetCID();
+			pGameDataObj->m_FlagCarrierRed = m_apBalls[TEAM_RED]->m_pCarryingCharacter->GetPlayer()->GetCID();
 		}
 		else
 		{
@@ -494,15 +524,15 @@ void CGameControllerFB::Snap(int SnappingClient)
 		pGameDataObj->m_FlagCarrierRed = FLAG_MISSING;	
 	}
 	
-	if(balls[TEAM_BLUE])
+	if(m_apBalls[TEAM_BLUE])
 	{
-		if(balls[TEAM_BLUE]->at_stand)
+		if(m_apBalls[TEAM_BLUE]->m_AtStand)
 		{
 			pGameDataObj->m_FlagCarrierBlue = FLAG_ATSTAND;
 		}
-		else if(balls[TEAM_BLUE]->carrier && balls[TEAM_BLUE]->carrier->GetPlayer())
+		else if(m_apBalls[TEAM_BLUE]->m_pCarryingCharacter)
 		{
-			pGameDataObj->m_FlagCarrierBlue = balls[TEAM_BLUE]->carrier->GetPlayer()->GetCID();
+			pGameDataObj->m_FlagCarrierBlue = m_apBalls[TEAM_BLUE]->m_pCarryingCharacter->GetPlayer()->GetCID();
 		}
 		else
 		{
@@ -515,51 +545,23 @@ void CGameControllerFB::Snap(int SnappingClient)
 	}
 }
 
-//	Ball
-BALL::BALL (CGameWorld *pGameWorld, int _team, vec2 _pos)
-:	CEntity (pGameWorld, CGameWorld::ENTTYPE_FLAG)
+bool CGameControllerFB::CanSpawn(int Team, vec2 *pOutPos)
 {
-	m_ProximityRadius = phys_size;
-	team = _team;
-	stand_pos = _pos;
+	CSpawnEval Eval;
 	
-	Reset();
-}
-
-void BALL::Reset ()
-{
-	carrier = 0;
-	last_carrier = 0;
-	last_carrier_team = team;
-	at_stand = 1;
-	m_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;
-			
-	if(NetworkClipped(snapping_client))
-		return;
-
-	CNetObj_Flag *ball = (CNetObj_Flag *)Server()->SnapNewItem(NETOBJTYPE_FLAG, team, sizeof(CNetObj_Flag));
-	if(!ball)
-		return;
+	// spectators can't spawn
+	if(Team == TEAM_SPECTATORS)
+		return false;
 	
-	ball->m_X = (int)m_Pos.x;
-	ball->m_Y = (int)m_Pos.y;
+	Eval.m_FriendlyTeam = Team;
 	
-	/* Was causing strange lag when a player of one team picked up the opposite flag
-	 * Laser Momentum still works as it should but the color of the flag does not change
-	 */
-	//if (g_Config.m_SvfbLaserMomentum)
-	//	ball->m_Team = last_carrier_team;
-	//else
-		ball->m_Team = team;
-}
+	// try own team only
+	EvaluateSpawnType(&Eval, 1+(Team&1));
 
+	// try harder
+	if(!Eval.m_Got)
+		FindFreeSpawn(&Eval, 1+(Team&1));
+	
+	*pOutPos = Eval.m_Pos;
+	return Eval.m_Got;
+}
diff --git a/src/game/server/gamemodes/fb.h b/src/game/server/gamemodes/fb.h
index fbc7185..4979314 100644
--- a/src/game/server/gamemodes/fb.h
+++ b/src/game/server/gamemodes/fb.h
@@ -1,48 +1,47 @@
+#ifndef GAME_SERVER_GAMEMODES_FB_H
+#define GAME_SERVER_GAMEMODES_FB_H
+
 #include <game/server/gamecontroller.h>
-#include <game/server/entity.h>
+#include <game/server/entities/flag.h>
 
 enum {
 	FB_MAX_GOALS = 2,
 	FB_MAX_BALLS = 2
 };
 
+class CBall: public CFlag
+{
+public:
+	CPlayer* m_pLastCarrier;
+	int m_LastCarrierTeam;
+	int m_IdleTick;
+
+	CBall(CGameWorld *pGameWorld, int Team, vec2 Pos);
+
+	virtual void Reset();
+	virtual void Tick();
+};
+
 class CGameControllerFB : public IGameController
 {
 public:
-	class BALL *balls[FB_MAX_BALLS];
-	vec2 goal_pos[FB_MAX_GOALS];
-	int num_goals;
-	
+	class CBall *m_apBalls[FB_MAX_BALLS];
+	vec2 m_apGoalPos[FB_MAX_GOALS];
+	int m_NumGoals;
+
 	CGameControllerFB(class CGameContext *pGameServer);
 	virtual void Tick();
 	
 	virtual bool OnEntity (int index, vec2 pos);
 	virtual int OnCharacterDeath (class CCharacter *victim, class CPlayer *killer, int weapon);
 	virtual void OnCharacterSpawn (class CCharacter *chr);
-	virtual bool HandleGoal (BALL *b, int goal);
-	virtual void OnDisconnect (const CPlayer *const p);
-	virtual void Snap(int SnappingClient);
-};
-
-class BALL : public CEntity
-{
-public:
-	static const int phys_size = 14;
-	CCharacter *carrier;
-	CPlayer *last_carrier;
-	int last_carrier_team;
-	vec2 vel;
-	vec2 stand_pos;
-	
-	int team;
-	int at_stand;
-	int drop_tick;
-	int grab_tick;
-	int idle_tick;
-	
-	BALL (CGameWorld *pGameWorld, int _team, vec2 _pos);
-
-	virtual void Reset();
+	virtual void CreateBallGrabSound(int team);
+	virtual void GrantBall(CCharacter *, CBall *);
+	virtual void SetBallColor(CBall *&, int team);
+	virtual bool HandleGoal (CBall *, int goal);
+	virtual void OnDisconnect (CPlayer *const p);
 	virtual void Snap(int SnappingClient);
+        virtual bool CanSpawn(int Team, vec2 *pOutPos);
 };
 
+#endif
diff --git a/src/game/server/maprotation.cpp b/src/game/server/maprotation.cpp
new file mode 100644
index 0000000..8454d23
--- /dev/null
+++ b/src/game/server/maprotation.cpp
@@ -0,0 +1,123 @@
+#include "maprotation.h"
+
+#include <vector>
+#include <iostream>
+#include <fstream>
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <string.h>
+#include <errno.h>
+
+#include <base/system.h>
+#include <base/math.h>
+
+class CRandomMapRotation: public IMapRotation 
+{
+public: 
+	class CMap
+	{
+	public:
+		const char* m_pName;
+		float m_Probability;
+
+		CMap(const char* name, float weight);
+	};
+	std::vector<CMap> m_MapList;
+	const char* m_pMapFile;
+	time_t m_MapFileMTime;
+
+	CRandomMapRotation();
+
+	virtual const char* NextMap(const char* rotation, const char* current);
+	void UpdateList(const char* rotation);
+	void ClearList();
+};
+
+time_t GetMTime(const char* file)
+{
+	struct stat st;
+	if (stat(file, &st) < 0)
+	{
+		dbg_msg("rotation", "Cannot stat %s: %s", file, strerror(errno));
+		return -1;
+	}
+	return st.st_mtime;
+}
+
+CRandomMapRotation::CMap::CMap(const char* name, float probability)
+: m_pName(name), m_Probability(probability) {}
+
+CRandomMapRotation::CRandomMapRotation()
+: m_pMapFile(NULL) {};
+
+void CRandomMapRotation::UpdateList(const char* rotation)
+{
+	if (m_pMapFile && str_comp(rotation, m_pMapFile) == 0)
+	{
+		time_t mtime = GetMTime(m_pMapFile);
+		if (mtime < 0 || mtime == m_MapFileMTime)
+			return;
+		m_MapFileMTime = mtime;
+	}
+	else
+	{
+		time_t mtime = GetMTime(rotation);
+		if (mtime < 0) return;
+		m_pMapFile = rotation;
+		m_MapFileMTime = mtime;
+	}
+
+	ClearList();
+
+	std::ifstream f(m_pMapFile);
+	while (1)
+	{
+		char buf[128];
+		float weight;
+		f >> buf >> weight;
+		if (!f) break;
+		char *name = new char[strlen(buf) + 1];
+		if (!name) break;
+		strcpy(name, buf);
+		m_MapList.push_back(CMap(name, weight));
+	}
+
+	// normalize probabilities
+	float norm = 0;
+	for (std::vector<CMap>::const_iterator m = m_MapList.begin(); m != m_MapList.end(); ++m)
+		norm += m->m_Probability;
+	for (std::vector<CMap>::iterator m = m_MapList.begin(); m != m_MapList.end(); ++m)
+		m->m_Probability /= norm;
+
+	for (std::vector<CMap>::const_iterator m = m_MapList.begin(); m != m_MapList.end(); ++m)
+		dbg_msg("rotation", "registering map %s with probability %g", m->m_pName, m->m_Probability);
+}
+
+void CRandomMapRotation::ClearList()
+{
+	for (std::vector<CMap>::const_iterator m = m_MapList.begin(); m != m_MapList.end(); ++m)
+		delete[] m->m_pName;
+	m_MapList.clear();
+}
+
+const char* CRandomMapRotation::NextMap(const char* rotation, const char* current)
+{
+	UpdateList(rotation);
+	if (m_MapList.empty()) return current;
+	float w = frandom();
+	float x = 0;
+	for (std::vector<CMap>::const_iterator m = m_MapList.begin(); m != m_MapList.end(); ++m)
+	{
+		x += m->m_Probability;
+		if (x >= w) return m->m_pName;
+	}
+	// should never happen
+	return current;
+}
+
+IMapRotation *RandomMapRotation()
+{
+	return new CRandomMapRotation;
+}
diff --git a/src/game/server/maprotation.h b/src/game/server/maprotation.h
new file mode 100644
index 0000000..d589b2d
--- /dev/null
+++ b/src/game/server/maprotation.h
@@ -0,0 +1,12 @@
+#ifndef GAME_SERVER_MAPROTATION_H
+#define GAME_SERVER_MAPROTATION_H
+
+class IMapRotation {
+ public:
+  virtual const char* NextMap(const char* rotation, const char* current) = 0;
+  virtual ~IMapRotation() {};
+};
+
+IMapRotation *RandomMapRotation();
+
+#endif
diff --git a/src/game/server/player.cpp b/src/game/server/player.cpp
index 5d435c4..770a426 100644
--- a/src/game/server/player.cpp
+++ b/src/game/server/player.cpp
@@ -21,10 +21,8 @@ CPlayer::CPlayer(CGameContext *pGameServer, int ClientID, int Team)
 	Character = 0;
 	this->m_ClientID = ClientID;
 	
- 	num_owngoals = 0;
- 	owngoal_warned = false;
- 	last_mousemove_tick = Server()->Tick();
- 	last_target_pos = vec2 (0, 0);
+ 	m_NumOwngoals = 0;
+ 	m_OwngoalWarned = false;
 
 	m_Team = GameServer()->m_pController->ClampTeam(Team);
 	m_SpectatorID = SPEC_FREEVIEW;
@@ -191,17 +189,6 @@ void CPlayer::OnDirectInput(CNetObj_PlayerInput *NewInput)
 	if(!Character && m_Team == TEAM_SPECTATORS && m_SpectatorID == SPEC_FREEVIEW)
 		m_ViewPos = vec2(NewInput->m_TargetX, NewInput->m_TargetY);
 
-    // 	Looks like this is done anyway now, still working on it
- 	//	update active player (idle kicking)
- 	if (m_LatestActivity.m_TargetX != NewInput->m_TargetX || m_LatestActivity.m_TargetY != NewInput->m_TargetY)
- 	{
- 		m_LatestActivity.m_TargetX = NewInput->m_TargetX;
- 		last_target_pos.y = NewInput->m_TargetY;
- 		last_mousemove_tick = Server()->Tick();
- 		
- 		//Flagball stuff could maybe be added down below in new activity check
- 	}
-
 	// check for activity
 	if(NewInput->m_Direction || m_LatestActivity.m_TargetX != NewInput->m_TargetX ||
 		m_LatestActivity.m_TargetY != NewInput->m_TargetY || NewInput->m_Jump ||
diff --git a/src/game/server/player.h b/src/game/server/player.h
index aeac33d..c9c1dcb 100644
--- a/src/game/server/player.h
+++ b/src/game/server/player.h
@@ -33,10 +33,8 @@ public:
 	void OnDisconnect(const char *pReason);
 	
  	//	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) 	
+	int m_NumOwngoals; //	counter for own-goals
+ 	bool m_OwngoalWarned;	//	own goal warning (only shown once)
 	
 	void KillCharacter(int Weapon = WEAPON_GAME);
 	CCharacter *GetCharacter();
diff --git a/src/game/variables.h b/src/game/variables.h
index 092c218..ce9695c 100644
--- a/src/game/variables.h
+++ b/src/game/variables.h
@@ -60,9 +60,9 @@ MACRO_CONFIG_INT(SvTeamdamage, sv_teamdamage, 0, 0, 1, CFGFLAG_SERVER, "Team dam
 MACRO_CONFIG_STR(SvMaprotation, sv_maprotation, 768, "", CFGFLAG_SERVER, "Maps to rotate between")
 MACRO_CONFIG_INT(SvRoundsPerMap, sv_rounds_per_map, 1, 1, 100, CFGFLAG_SERVER, "Number of rounds on each map before rotating")
 MACRO_CONFIG_INT(SvPowerups, sv_powerups, 1, 0, 1, CFGFLAG_SERVER, "Allow powerups like ninja")
-MACRO_CONFIG_INT(SvScorelimit, sv_scorelimit, 20, 0, 1000, CFGFLAG_SERVER, "Score limit (0 disables)")
-MACRO_CONFIG_INT(SvTimelimit, sv_timelimit, 0, 0, 1000, CFGFLAG_SERVER, "Time limit in minutes (0 disables)")
-MACRO_CONFIG_STR(SvGametype, sv_gametype, 32, "fb", CFGFLAG_SERVER, "Game type (dm, tdm, ctf)")
+MACRO_CONFIG_INT(SvScorelimit, sv_scorelimit, 20, 0, 1000, CFGFLAG_SERVER, "Score limit")
+MACRO_CONFIG_INT(SvTimelimit, sv_timelimit, 0, 0, 1000, CFGFLAG_SERVER, "Time limit in minutes")
+MACRO_CONFIG_STR(SvGametype, sv_gametype, 32, "fb", CFGFLAG_SERVER, "Game type (dm, tdm, ctf, fb, ifb)")
 MACRO_CONFIG_INT(SvTournamentMode, sv_tournament_mode, 0, 0, 1, CFGFLAG_SERVER, "Tournament mode. When enabled, players joins the server as spectator")
 MACRO_CONFIG_INT(SvSpamprotection, sv_spamprotection, 1, 0, 1, CFGFLAG_SERVER, "Spam protection")
 
@@ -79,38 +79,35 @@ MACRO_CONFIG_INT(SvVoteKickMin, sv_vote_kick_min, 0, 0, MAX_CLIENTS, CFGFLAG_SER
 MACRO_CONFIG_INT(SvVoteKickBantime, sv_vote_kick_bantime, 5, 0, 1440, CFGFLAG_SERVER, "The time to ban a player if kicked by vote. 0 makes it just use kick")
 
 /*	flagball	*/
-MACRO_CONFIG_INT(SvfbBallDamageLose, 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(SvfbBallDeathtileIdle, 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(SvfbBallResetTime, sv_fb_ball_reset_time, 30, 1, 500, CFGFLAG_SERVER, "Number of seconds needed to reset an unused ball (default=30).")
-MACRO_CONFIG_INT(SvfbBallVelocity, 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(SvfbCampMaxtime, 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(SvfbCampThreshold, sv_fb_camp_threshold, 200, 64, 1000, CFGFLAG_SERVER, "Threshold for anti-camp distance (default=200).")
-MACRO_CONFIG_INT(SvfbDirectPass, 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(SvfbDirectPassDistance, sv_fb_direct_pass_distance, 380, 32, 3200, CFGFLAG_SERVER, "Maximum direct-passing distance (default=380).")
-MACRO_CONFIG_INT(SvfbBallOffsetX, sv_fb_ball_offset_x, 0, 0, 1000, CFGFLAG_SERVER, "X offset of the hookable center of the ball with respect to the lower left corner of the image (default=0)")
-MACRO_CONFIG_INT(SvfbBallOffsetY, sv_fb_ball_offset_y, 38, 0, 1000, CFGFLAG_SERVER, "Y offset of the hookable center of the ball with respect to the lower left corner of the image (default=38)")
-MACRO_CONFIG_INT(SvfbBallRadius, sv_fb_ball_radius, 40, 0, 1000, CFGFLAG_SERVER, "Flag hookable radius (default=40)")
-MACRO_CONFIG_INT(SvfbLaserMomentum, sv_fb_laser_momentum, 207, -3000, 3000, CFGFLAG_SERVER, "Momentum passed to a flag when it is hit by laser (default=207)")
-MACRO_CONFIG_INT(SvfbGoalsize, sv_fb_goalsize, 64, 32, 500, CFGFLAG_SERVER, "Radius defining goal (default=64).")
-MACRO_CONFIG_INT(SvfbGoalcamp, sv_fb_goalcamp, 1, 0, 1, CFGFLAG_SERVER, "Turns on/off goal camp protection (0=off, 1=on (default=1)).")
-MACRO_CONFIG_INT(SvfbGoalcampTime, sv_fb_goalcamp_time, 3, 1, 600, CFGFLAG_SERVER, "Number of seconds allowed lingering near a goal (default=3).")
-MACRO_CONFIG_INT(SvfbGoalcampFactor, sv_fb_goalcamp_factor, 40, 10, 100, CFGFLAG_SERVER, "Goal camp protection radius (value/10.0*sv_fb_goalsize (default=40)).")
-MACRO_CONFIG_INT(SvfbHookBall, sv_fb_hook_ball, 1, 0, 1, CFGFLAG_SERVER, "Turns on/off the possibily to capture the ball by hook (0=off, 1=on (default=1))")
-MACRO_CONFIG_STR(SvfbOnIdle, sv_fb_on_idle, 10, ".", CFGFLAG_SERVER, "What to do when player idling too long. Possible values: spectate, kick. Default is nothing")
-MACRO_CONFIG_INT(SvfbIdleTime, sv_fb_idle_time, 0, 0, 600, CFGFLAG_SERVER, "Number of seconds after an idling player gets kicked or being forced to join to spectators (0=off (default=90)).")
-MACRO_CONFIG_INT(SvfbSpectatorIdleTime, sv_fb_spectator_idle_time, 0, 0, 10000, CFGFLAG_SERVER, "Number of seconds after an idling spectator gets kicked")
-MACRO_CONFIG_INT(SvfbInstagib, sv_fb_instagib, 1, 0, 1, CFGFLAG_SERVER, "Turns on/off instagib modification (0=off, 1=on (default=1)).")
-MACRO_CONFIG_INT(SvfbOwngoal, sv_fb_owngoal, 0, 0, 1, CFGFLAG_SERVER, "Turns on/off own goals (0=off, 1=on (default=0)).")
-MACRO_CONFIG_INT(SvfbOwngoalKick, 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(SvfbOwngoalPenalty, sv_fb_owngoal_penalty, 30, 0, 999, CFGFLAG_SERVER, "Negative score for own goal player (default=30).")
-MACRO_CONFIG_INT(SvfbOwngoalWarn, sv_fb_owngoal_warn, 0, 0, 1, CFGFLAG_SERVER, "Turns on/off warning when player with ball is near own goal (0=off, 1=on (default=0)).")
-MACRO_CONFIG_INT(SvfbScorePlayerBall, sv_fb_score_player_ball, 5, 0, 100, CFGFLAG_SERVER, "Player score on ball-goal (default=5).")
-MACRO_CONFIG_INT(SvfbScorePlayerStand, sv_fb_score_player_stand, 0, 0, 10, CFGFLAG_SERVER, "Player score for taking ball from stand (default=0).")
-MACRO_CONFIG_INT(SvfbScorePlayerTee, sv_fb_score_player_tee, 10, 0, 100, CFGFLAG_SERVER, "Player-score on tee-goal (default=10).")
-MACRO_CONFIG_INT(SvfbScoreTeamBall, sv_fb_score_team_ball, 50, 1, 100, CFGFLAG_SERVER, "Team-score on ball-goal (default=50).")
-MACRO_CONFIG_INT(SvfbScoreTeamStand, sv_fb_score_team_stand, 0, 0, 10, CFGFLAG_SERVER, " Team score for taking ball from stand (default=0).")
-MACRO_CONFIG_INT(SvfbScoreTeamTee, sv_fb_score_team_tee, 100, 1, 100, CFGFLAG_SERVER, "Team-score on tee-goal (default=100).")
-MACRO_CONFIG_STR(SvfbWelcomeMessage, sv_fb_welcome_message, 512, "", CFGFLAG_SERVER, "Message to display when clients join (default="").")
+MACRO_CONFIG_INT(SvfbBallDamageLose, sv_fb_ball_damage_lose, 1, 0, 1, CFGFLAG_SERVER, "Lose ball on any damage")
+MACRO_CONFIG_INT(SvfbBallResetTime, sv_fb_ball_reset_time, 30, 1, 500, CFGFLAG_SERVER, "Number of seconds needed to reset a lying ball")
+MACRO_CONFIG_INT(SvfbBallVelocity, sv_fb_ball_velocity, 207, 10, 3000, CFGFLAG_SERVER, "Velocity of the ball after it got shot")
+MACRO_CONFIG_INT(SvfbCampMaxtime, sv_fb_camp_maxtime, 0, 0, 100, CFGFLAG_SERVER, "Number of seconds allowed lingering at the same place")
+MACRO_CONFIG_INT(SvfbCampThreshold, sv_fb_camp_threshold, 200, 64, 1000, CFGFLAG_SERVER, "Threshold for anti-camp distance")
+MACRO_CONFIG_INT(SvfbDirectPass, sv_fb_direct_pass, 1, 0, 1, CFGFLAG_SERVER, "Let players pass ball with hook")
+MACRO_CONFIG_INT(SvfbBallOffsetX, sv_fb_ball_offset_x, 0, 0, 1000, CFGFLAG_SERVER, "X offset of the hookable center of the ball with respect to the lower left corner of the image")
+MACRO_CONFIG_INT(SvfbBallOffsetY, sv_fb_ball_offset_y, 38, 0, 1000, CFGFLAG_SERVER, "Y offset of the hookable center of the ball with respect to the lower left corner of the image")
+MACRO_CONFIG_INT(SvfbBallRadius, sv_fb_ball_radius, 40, 0, 1000, CFGFLAG_SERVER, "Flag hookable radius")
+MACRO_CONFIG_INT(SvfbLaserMomentum, sv_fb_laser_momentum, 207, -3000, 3000, CFGFLAG_SERVER, "Momentum passed to a flag when it is hit by laser")
+MACRO_CONFIG_INT(SvfbGoalsize, sv_fb_goalsize, 64, 32, 500, CFGFLAG_SERVER, "Radius defining goal")
+MACRO_CONFIG_INT(SvfbGoalcamp, sv_fb_goalcamp, 1, 0, 1, CFGFLAG_SERVER, "Activate goal camp protection")
+MACRO_CONFIG_INT(SvfbGoalcampTime, sv_fb_goalcamp_time, 3, 1, 600, CFGFLAG_SERVER, "Number of seconds allowed lingering near a goal")
+MACRO_CONFIG_INT(SvfbGoalcampFactor, sv_fb_goalcamp_factor, 40, 10, 100, CFGFLAG_SERVER, "Goal camp protection radius")
+MACRO_CONFIG_INT(SvfbHookBall, sv_fb_hook_ball, 1, 0, 1, CFGFLAG_SERVER, "Make the ball hookable")
+MACRO_CONFIG_INT(SvfbInstagib, sv_fb_instagib, 1, 0, 1, CFGFLAG_SERVER, "Activate instagib")
+MACRO_CONFIG_INT(SvfbOwngoal, sv_fb_owngoal, 0, 0, 1, CFGFLAG_SERVER, "Allow own goals")
+MACRO_CONFIG_INT(SvfbOwngoalKick, sv_fb_owngoal_kick, 2, 0, 20, CFGFLAG_SERVER, "Number of own goals allowed before kicking the player")
+MACRO_CONFIG_INT(SvfbOwngoalPenalty, sv_fb_owngoal_penalty, 30, 0, 999, CFGFLAG_SERVER, "Negative score for own goal player")
+MACRO_CONFIG_INT(SvfbOwngoalWarn, sv_fb_owngoal_warn, 0, 0, 1, CFGFLAG_SERVER, "Warn when player with ball is near own goal")
+MACRO_CONFIG_INT(SvfbScorePlayerBall, sv_fb_score_player_ball, 5, 0, 100, CFGFLAG_SERVER, "Player score for ball goal")
+MACRO_CONFIG_INT(SvfbScorePlayerStand, sv_fb_score_player_stand, 0, 0, 10, CFGFLAG_SERVER, "Player score for taking ball from stand")
+MACRO_CONFIG_INT(SvfbScorePlayerTee, sv_fb_score_player_tee, 10, 0, 100, CFGFLAG_SERVER, "Player score for tee goal")
+MACRO_CONFIG_INT(SvfbScoreTeamBall, sv_fb_score_team_ball, 50, 1, 100, CFGFLAG_SERVER, "Team score for ball goal")
+MACRO_CONFIG_INT(SvfbScoreTeamStand, sv_fb_score_team_stand, 0, 0, 10, CFGFLAG_SERVER, "Team score for taking ball from stand")
+MACRO_CONFIG_INT(SvfbScoreTeamTee, sv_fb_score_team_tee, 100, 1, 100, CFGFLAG_SERVER, "Team score for tee goal")
+MACRO_CONFIG_STR(SvfbWelcomeMessage, sv_fb_welcome_message, 512, "", CFGFLAG_SERVER, "Message to display when clients join")
+MACRO_CONFIG_INT(SvfbSelfKill, sv_fb_self_kill, 0, 0, 1, CFGFLAG_SERVER, "Allow self kill")
+MACRO_CONFIG_INT(SvfbSpawnWeaponDischarge, sv_fb_spawn_weapon_discharge, 50, 0, 100, CFGFLAG_SERVER, "Amount of time players cannot fire after respawn (in percents of laser reloading time)")
 
 // debug
 #ifdef CONF_DEBUG // this one can crash the server if not used correctly
diff --git a/src/game/version.h b/src/game/version.h
index afe37af..f8f4c50 100644
--- a/src/game/version.h
+++ b/src/game/version.h
@@ -6,5 +6,5 @@
 #define GAME_VERSION "0.6 trunk"
 #define GAME_NETVERSION "0.6 " GAME_NETVERSION_HASH
 /*	FlagBall-MOD version	*/
-#define MOD_VERSION "0.1.17.2"
+#define MOD_VERSION "0.2.0"
 #endif
