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 d9d0fd2..8d2a73f 100644
--- a/src/game/server/entities/character.cpp
+++ b/src/game/server/entities/character.cpp
@@ -196,11 +196,10 @@ void CCharacter::HandleNinja()
  
 void CCharacter::ReleaseBall()
 {
-	GameServer()->CreateSoundGlobal(SOUND_CTF_DROP);
 	m_pBall->m_DropTick = Server()->Tick();
 	m_pBall->m_IdleTick = -1;
-	m_pBall->m_pLastCarrierCharacter = this;
-	m_pBall->m_LastCarrierTeam = GetPlayer()->GetTeam();
+	m_pBall->m_pLastCarrier    = GetPlayer();
+	m_pBall->m_LastCarrierTeam = m_pBall->m_pLastCarrier->GetTeam();
 	m_pBall->m_pCarryingCharacter = NULL;
 	m_pBall = NULL;
 
@@ -443,8 +442,8 @@ void CCharacter::FireWeapon()
 		{			
  			if (m_pBall)
  			{
- 				ThrowBall(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;
  			}
@@ -605,14 +604,9 @@ void CCharacter::Tick()
     // direct pass of ball
  	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)
- 		{
+		CCharacter *catcher = GameServer()->m_apPlayers[m_Core.m_HookedPlayer]->GetCharacter();
+		if (!catcher->m_pBall)
+		{
 			CBall *ball = m_pBall;
 			ReleaseBall();
 			catcher->CaptureBall(ball);
@@ -621,7 +615,7 @@ void CCharacter::Tick()
  			m_Core.m_HookedPlayer = -1;
  			m_Core.m_HookState = HOOK_RETRACTED;
  			m_Core.m_HookPos = m_Core.m_Pos;
- 		};
+		}
  	};
  
  	// hook the flag
@@ -811,6 +805,10 @@ 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);
@@ -862,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)
 	{
diff --git a/src/game/server/entities/character.h b/src/game/server/entities/character.h
index 4d3f4a2..f94ae2c 100644
--- a/src/game/server/entities/character.h
+++ b/src/game/server/entities/character.h
@@ -87,6 +87,7 @@ public:
 	int m_ActiveWeapon;
 	int m_LastWeapon;
 	
+	int m_ReloadTimer;
 private:
 	// player controlling this character
 	class CPlayer *m_pPlayer;
@@ -99,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 01c6e26..34a3c15 100644
--- a/src/game/server/entities/laser.cpp
+++ b/src/game/server/entities/laser.cpp
@@ -47,7 +47,7 @@ void CLaser::hit_ball(vec2 from, vec2 to) {
  		{
  			ball->m_Vel += normalize(to - from) * g_Config.m_SvfbLaserMomentum * 0.1f;
  			ball->m_DropTick = Server()->Tick();
- 			ball->m_pLastCarrierCharacter = GameServer()->m_apPlayers[m_Owner]->GetCharacter();
+ 			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 c60ac70..917028f 100644
--- a/src/game/server/gamecontext.cpp
+++ b/src/game/server/gamecontext.cpp
@@ -1292,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);
@@ -1328,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 f6fdfcc..0c93e07 100644
--- a/src/game/server/gamemodes/fb.cpp
+++ b/src/game/server/gamemodes/fb.cpp
@@ -17,9 +17,8 @@ CBall::CBall(CGameWorld *pGameWorld, int Team, vec2 Pos)
 
 void CBall::Reset()
 {
-	GameServer()->CreateSoundGlobal(SOUND_CTF_RETURN);
 	CFlag::Reset();
-	m_pLastCarrierCharacter = NULL;
+	m_pLastCarrier = NULL;
 	m_DropTick = 0;
 	m_IdleTick = -1;
 	m_LastCarrierTeam = !m_Team;
@@ -27,15 +26,13 @@ void CBall::Reset()
 
 void CBall::Tick()
 {
-	//	update ball position
-	if (m_pCarryingCharacter)
-	{
-		m_Pos = m_pCarryingCharacter->m_Pos;
-	}
-	else if (!m_AtStand)
+	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
@@ -120,28 +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)
 	{
-		CBall *b = m_apBalls[i];
-		if (!b || !b->m_pCarryingCharacter)
-			continue;
-
-		if (killer && b->m_pCarryingCharacter == killer->GetCharacter())
-			had_flag |= 2;
-		if (b->m_pCarryingCharacter == victim)
-		{
-			victim->DropBall();
-
-			if (killer && killer->GetTeam() != victim->GetPlayer()->GetTeam())
-				killer->m_Score++;
-				
-			had_flag |= 1;
-		}
+		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);
+		if (killer && killer->GetTeam() != victim->GetPlayer()->GetTeam())
+			killer->m_Score++;
+		mode |= 1;
 	}
 
-	return had_flag;
+	return mode;
 	
 }
 
@@ -158,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
 	{
@@ -173,8 +167,7 @@ void CGameControllerFB::OnCharacterSpawn(class CCharacter *chr)
 bool CGameControllerFB::HandleGoal (CBall *b, int goal)
 {
 	
-	CCharacter *const ch = b->m_pCarryingCharacter ? b->m_pCarryingCharacter : b->m_pLastCarrierCharacter;
-	CPlayer *const p = ch ? ch->GetPlayer() : NULL;
+	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->m_pCarryingCharacter;
@@ -211,6 +204,7 @@ bool CGameControllerFB::HandleGoal (CBall *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;
@@ -271,10 +265,10 @@ void CGameControllerFB::OnDisconnect (CPlayer *const p)
 	{
 		if (m_apBalls[i])
 		{
-			if (m_apBalls[i]->m_pCarryingCharacter == ch)
+			if (ch && m_apBalls[i]->m_pCarryingCharacter == ch)
 				ch->ReleaseBall();
-			if (m_apBalls[i]->m_pLastCarrierCharacter == ch)
-				m_apBalls[i]->m_pLastCarrierCharacter = NULL;
+			if (m_apBalls[i]->m_pLastCarrier == p)
+				m_apBalls[i]->m_pLastCarrier = NULL;
 		}
 	}
 }
@@ -299,19 +293,32 @@ void CGameControllerFB::SetBallColor(CBall *&ball, int team)
 	CBall *b = m_apBalls[team];
 	if (!b->m_AtStand) return;
 
-	swap(b->m_AtStand,               ball->m_AtStand);
-	swap(b->m_StandPos,              ball->m_StandPos);
-	swap(b->m_Pos,                   ball->m_Pos);
-	swap(b->m_Vel,                   ball->m_Vel);
-	swap(b->m_pCarryingCharacter,    ball->m_pCarryingCharacter);
-	swap(b->m_pLastCarrierCharacter, ball->m_pLastCarrierCharacter);
-	swap(b->m_LastCarrierTeam,       ball->m_LastCarrierTeam);
-	swap(b->m_GrabTick,              ball->m_GrabTick);
-	swap(b->m_DropTick,              ball->m_DropTick);
-
+	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();
@@ -323,21 +330,11 @@ void CGameControllerFB::GrantBall(CCharacter *ch, CBall *b)
 	}
 	b->m_pCarryingCharacter = ch;
 	b->m_Pos = ch->m_Pos;
-
 	if (g_Config.m_SvfbLaserMomentum) 
 		SetBallColor(b, team);
-
 	ch->m_pBall = b;
-
-	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());
-	}
-
 	ch->BecomeNinja();
+	CreateBallGrabSound(team);
 }
 
 
@@ -431,6 +428,10 @@ void CGameControllerFB::Tick()
 			if (!b)
 				continue;
 
+			//	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 < m_NumGoals; g++)
@@ -543,3 +544,24 @@ void CGameControllerFB::Snap(int SnappingClient)
 		pGameDataObj->m_FlagCarrierBlue = FLAG_MISSING;
 	}
 }
+
+bool CGameControllerFB::CanSpawn(int Team, vec2 *pOutPos)
+{
+	CSpawnEval Eval;
+	
+	// spectators can't spawn
+	if(Team == TEAM_SPECTATORS)
+		return false;
+	
+	Eval.m_FriendlyTeam = 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 25d75ef..4979314 100644
--- a/src/game/server/gamemodes/fb.h
+++ b/src/game/server/gamemodes/fb.h
@@ -12,7 +12,7 @@ enum {
 class CBall: public CFlag
 {
 public:
-	CCharacter* m_pLastCarrierCharacter;
+	CPlayer* m_pLastCarrier;
 	int m_LastCarrierTeam;
 	int m_IdleTick;
 
@@ -35,11 +35,13 @@ public:
 	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 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/variables.h b/src/game/variables.h
index abb0d51..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,35 +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(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_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(SvfbSelfKill, sv_fb_self_kill, 0, 0, 1, CFGFLAG_SERVER, "Allow self kill");
+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
