about summary refs log tree commit diff
path: root/src/game
diff options
context:
space:
mode:
Diffstat (limited to 'src/game')
-rw-r--r--src/game/client/animstate.cpp12
-rw-r--r--src/game/client/animstate.h20
-rw-r--r--src/game/client/components/chat.cpp66
-rw-r--r--src/game/client/components/console.cpp58
-rw-r--r--src/game/client/components/console.h7
-rw-r--r--src/game/client/components/countryflags.cpp29
-rw-r--r--src/game/client/components/countryflags.h15
-rw-r--r--src/game/client/components/debughud.cpp8
-rw-r--r--src/game/client/components/effects.cpp2
-rw-r--r--src/game/client/components/emoticon.cpp8
-rw-r--r--src/game/client/components/hud.cpp8
-rw-r--r--src/game/client/components/maplayers.cpp20
-rw-r--r--src/game/client/components/menus.cpp84
-rw-r--r--src/game/client/components/menus.h30
-rw-r--r--src/game/client/components/menus_browser.cpp215
-rw-r--r--src/game/client/components/menus_demo.cpp18
-rw-r--r--src/game/client/components/menus_ingame.cpp2
-rw-r--r--src/game/client/components/menus_settings.cpp69
-rw-r--r--src/game/client/components/scoreboard.cpp2
-rw-r--r--src/game/client/components/sounds.cpp19
-rw-r--r--src/game/client/components/sounds.h1
-rw-r--r--src/game/client/components/spectator.cpp29
-rw-r--r--src/game/client/gameclient.cpp44
-rw-r--r--src/game/client/gameclient.h2
-rw-r--r--src/game/client/lineinput.cpp2
-rw-r--r--src/game/client/render.cpp4
-rw-r--r--src/game/client/render.h7
-rw-r--r--src/game/client/render_map.cpp18
-rw-r--r--src/game/client/ui.cpp7
-rw-r--r--src/game/client/ui.h1
-rw-r--r--src/game/editor/auto_map.cpp202
-rw-r--r--src/game/editor/auto_map.h54
-rw-r--r--src/game/editor/editor.cpp (renamed from src/game/editor/ed_editor.cpp)446
-rw-r--r--src/game/editor/editor.h (renamed from src/game/editor/ed_editor.h)49
-rw-r--r--src/game/editor/io.cpp (renamed from src/game/editor/ed_io.cpp)55
-rw-r--r--src/game/editor/layer_game.cpp (renamed from src/game/editor/ed_layer_game.cpp)4
-rw-r--r--src/game/editor/layer_quads.cpp (renamed from src/game/editor/ed_layer_quads.cpp)24
-rw-r--r--src/game/editor/layer_tiles.cpp (renamed from src/game/editor/ed_layer_tiles.cpp)45
-rw-r--r--src/game/editor/popups.cpp (renamed from src/game/editor/ed_popups.cpp)76
-rw-r--r--src/game/gamecore.cpp48
-rw-r--r--src/game/mapitems.h9
-rw-r--r--src/game/server/entities/character.cpp107
-rw-r--r--src/game/server/entities/character.h2
-rw-r--r--src/game/server/entities/laser.cpp1
-rw-r--r--src/game/server/eventhandler.cpp2
-rw-r--r--src/game/server/gamecontext.cpp382
-rw-r--r--src/game/server/gamecontext.h24
-rw-r--r--src/game/server/gamecontroller.cpp218
-rw-r--r--src/game/server/gamecontroller.h10
-rw-r--r--src/game/server/gamemodes/ctf.cpp41
-rw-r--r--src/game/server/gamemodes/ctf.h1
-rw-r--r--src/game/server/gamemodes/dm.cpp1
-rw-r--r--src/game/server/gamemodes/mod.cpp2
-rw-r--r--src/game/server/gamemodes/tdm.cpp1
-rw-r--r--src/game/server/gamemodes/zcatch.cpp230
-rw-r--r--src/game/server/gamemodes/zcatch.hpp33
-rw-r--r--src/game/server/gameworld.cpp1
-rw-r--r--src/game/server/player.cpp137
-rw-r--r--src/game/server/player.h23
-rw-r--r--src/game/variables.h18
-rw-r--r--src/game/version.h4
61 files changed, 2453 insertions, 604 deletions
diff --git a/src/game/client/animstate.cpp b/src/game/client/animstate.cpp
index 1289126b..ce595359 100644
--- a/src/game/client/animstate.cpp
+++ b/src/game/client/animstate.cpp
@@ -7,7 +7,7 @@
 
 #include "animstate.h"
 
-static void AnimSeqEval(ANIM_SEQUENCE *pSeq, float Time, ANIM_KEYFRAME *pFrame)
+static void AnimSeqEval(CAnimSequence *pSeq, float Time, CAnimKeyframe *pFrame)
 {
 	if(pSeq->m_NumFrames == 0)
 	{
@@ -23,8 +23,8 @@ static void AnimSeqEval(ANIM_SEQUENCE *pSeq, float Time, ANIM_KEYFRAME *pFrame)
 	else
 	{
 		//time = max(0.0f, min(1.0f, time / duration)); // TODO: use clamp
-		ANIM_KEYFRAME *pFrame1 = 0;
-		ANIM_KEYFRAME *pFrame2 = 0;
+		CAnimKeyframe *pFrame1 = 0;
+		CAnimKeyframe *pFrame2 = 0;
 		float Blend = 0.0f;
 
 		// TODO: make this smarter.. binary search
@@ -49,7 +49,7 @@ static void AnimSeqEval(ANIM_SEQUENCE *pSeq, float Time, ANIM_KEYFRAME *pFrame)
 	}
 }
 
-static void AnimAddKeyframe(ANIM_KEYFRAME *pSeq, ANIM_KEYFRAME *pAdded, float Amount)
+static void AnimAddKeyframe(CAnimKeyframe *pSeq, CAnimKeyframe *pAdded, float Amount)
 {
 	pSeq->m_X += pAdded->m_X*Amount;
 	pSeq->m_Y += pAdded->m_Y*Amount;
@@ -65,7 +65,7 @@ static void AnimAdd(CAnimState *pState, CAnimState *pAdded, float Amount)
 }
 
 
-void CAnimState::Set(ANIMATION *pAnim, float Time)
+void CAnimState::Set(CAnimation *pAnim, float Time)
 {
 	AnimSeqEval(&pAnim->m_Body, Time, &m_Body);
 	AnimSeqEval(&pAnim->m_BackFoot, Time, &m_BackFoot);
@@ -73,7 +73,7 @@ void CAnimState::Set(ANIMATION *pAnim, float Time)
 	AnimSeqEval(&pAnim->m_Attach, Time, &m_Attach);
 }
 
-void CAnimState::Add(ANIMATION *pAnim, float Time, float Amount)
+void CAnimState::Add(CAnimation *pAnim, float Time, float Amount)
 {
 	CAnimState Add;
 	Add.Set(pAnim, Time);
diff --git a/src/game/client/animstate.h b/src/game/client/animstate.h
index 63b6a80a..fbc0a2f8 100644
--- a/src/game/client/animstate.h
+++ b/src/game/client/animstate.h
@@ -5,18 +5,18 @@
 
 class CAnimState
 {
-	ANIM_KEYFRAME m_Body;
-	ANIM_KEYFRAME m_BackFoot;
-	ANIM_KEYFRAME m_FrontFoot;
-	ANIM_KEYFRAME m_Attach;
+	CAnimKeyframe m_Body;
+	CAnimKeyframe m_BackFoot;
+	CAnimKeyframe m_FrontFoot;
+	CAnimKeyframe m_Attach;
 
 public:
-	ANIM_KEYFRAME *GetBody() { return &m_Body; };
-	ANIM_KEYFRAME *GetBackFoot() { return &m_BackFoot; };
-	ANIM_KEYFRAME *GetFrontFoot() { return &m_FrontFoot; };
-	ANIM_KEYFRAME *GetAttach() { return &m_Attach; };
-	void Set(ANIMATION *pAnim, float Time);
-	void Add(ANIMATION *pAdded, float Time, float Amount);
+	CAnimKeyframe *GetBody() { return &m_Body; };
+	CAnimKeyframe *GetBackFoot() { return &m_BackFoot; };
+	CAnimKeyframe *GetFrontFoot() { return &m_FrontFoot; };
+	CAnimKeyframe *GetAttach() { return &m_Attach; };
+	void Set(CAnimation *pAnim, float Time);
+	void Add(CAnimation *pAdded, float Time, float Amount);
 
 	static CAnimState *GetIdle();
 };
diff --git a/src/game/client/components/chat.cpp b/src/game/client/components/chat.cpp
index ee294dc4..aba38bf6 100644
--- a/src/game/client/components/chat.cpp
+++ b/src/game/client/components/chat.cpp
@@ -132,17 +132,28 @@ bool CChat::OnInput(IInput::CEvent Event)
 
 		// find next possible name
 		const char *pCompletionString = 0;
-		m_CompletionChosen = (m_CompletionChosen+1)%MAX_CLIENTS;
-		for(int i = 0; i < MAX_CLIENTS; ++i)
+		m_CompletionChosen = (m_CompletionChosen+1)%(2*MAX_CLIENTS);
+		for(int i = 0; i < 2*MAX_CLIENTS; ++i)
 		{
+			int SearchType = ((m_CompletionChosen+i)%(2*MAX_CLIENTS))/MAX_CLIENTS;
 			int Index = (m_CompletionChosen+i)%MAX_CLIENTS;
 			if(!m_pClient->m_Snap.m_paPlayerInfos[Index])
 				continue;
 
-			if(str_find_nocase(m_pClient->m_aClients[Index].m_aName, m_aCompletionBuffer))
+			bool Found = false;
+			if(SearchType == 1)
+			{
+				if(str_comp_nocase_num(m_pClient->m_aClients[Index].m_aName, m_aCompletionBuffer, str_length(m_aCompletionBuffer)) &&
+					str_find_nocase(m_pClient->m_aClients[Index].m_aName, m_aCompletionBuffer))
+					Found = true;
+			}
+			else if(!str_comp_nocase_num(m_pClient->m_aClients[Index].m_aName, m_aCompletionBuffer, str_length(m_aCompletionBuffer)))
+				Found = true;
+
+			if(Found)
 			{
 				pCompletionString = m_pClient->m_aClients[Index].m_aName;
-				m_CompletionChosen = Index;
+				m_CompletionChosen = Index+SearchType*MAX_CLIENTS;
 				break;
 			}
 		}
@@ -151,10 +162,25 @@ bool CChat::OnInput(IInput::CEvent Event)
 		if(pCompletionString)
 		{
 			char aBuf[256];
+			// add part before the name
 			str_copy(aBuf, m_Input.GetString(), min(static_cast<int>(sizeof(aBuf)), m_PlaceholderOffset+1));
+
+			// add the name
 			str_append(aBuf, pCompletionString, sizeof(aBuf));
+
+			// add seperator
+			const char *pSeparator = "";
+			if(*(m_Input.GetString()+m_PlaceholderOffset+m_PlaceholderLength) != ' ')
+				pSeparator = m_PlaceholderOffset == 0 ? ": " : " ";
+			else if(m_PlaceholderOffset == 0)
+				pSeparator = ":";
+			if(*pSeparator)
+				str_append(aBuf, pSeparator, sizeof(aBuf));
+
+			// add part after the name
 			str_append(aBuf, m_Input.GetString()+m_PlaceholderOffset+m_PlaceholderLength, sizeof(aBuf));
-			m_PlaceholderLength = str_length(pCompletionString);
+
+			m_PlaceholderLength = str_length(pSeparator)+str_length(pCompletionString);
 			m_OldChatStringLength = m_Input.GetLength();
 			m_Input.Set(aBuf);
 			m_Input.SetCursorOffset(m_PlaceholderOffset+m_PlaceholderLength);
@@ -184,11 +210,7 @@ bool CChat::OnInput(IInput::CEvent Event)
 			m_pHistoryEntry = m_History.Last();
 
 		if (m_pHistoryEntry)
-		{
-			unsigned int Len = str_length(m_pHistoryEntry);
-			if (Len < sizeof(m_Input) - 1) // TODO: WTF?
-				m_Input.Set(m_pHistoryEntry);
-		}
+			m_Input.Set(m_pHistoryEntry);
 	}
 	else if (Event.m_Flags&IInput::FLAG_PRESS && Event.m_Key == KEY_DOWN)
 	{
@@ -196,11 +218,7 @@ bool CChat::OnInput(IInput::CEvent Event)
 			m_pHistoryEntry = m_History.Next(m_pHistoryEntry);
 
 		if (m_pHistoryEntry)
-		{
-			unsigned int Len = str_length(m_pHistoryEntry);
-			if (Len < sizeof(m_Input) - 1) // TODO: WTF?
-				m_Input.Set(m_pHistoryEntry);
-		}
+			m_Input.Set(m_pHistoryEntry);
 		else
 			m_Input.Clear();
 	}
@@ -246,6 +264,7 @@ void CChat::AddLine(int ClientID, int Team, const char *pLine)
 	char *p = const_cast<char*>(pLine);
 	while(*p)
 	{
+		Highlighted = false;
 		pLine = p;
 		// find line seperator and strip multiline
 		while(*p)
@@ -264,9 +283,16 @@ void CChat::AddLine(int ClientID, int Team, const char *pLine)
 		m_aLines[m_CurrentLine].m_ClientID = ClientID;
 		m_aLines[m_CurrentLine].m_Team = Team;
 		m_aLines[m_CurrentLine].m_NameColor = -2;
-		m_aLines[m_CurrentLine].m_Highlighted = str_find_nocase(pLine, m_pClient->m_aClients[m_pClient->m_Snap.m_LocalClientID].m_aName) != 0;
-		if(m_aLines[m_CurrentLine].m_Highlighted)
-			Highlighted = true;
+		
+		// check for highlighted name
+		const char *pHL = str_find_nocase(pLine, m_pClient->m_aClients[m_pClient->m_Snap.m_LocalClientID].m_aName);
+		if(pHL)
+		{
+			int Length = str_length(m_pClient->m_aClients[m_pClient->m_Snap.m_LocalClientID].m_aName);
+			if((pLine == pHL || pHL[-1] == ' ') && (pHL[Length] == 0 || pHL[Length] == ' ' || (pHL[Length] == ':' && pHL[Length+1] == ' ')))
+				Highlighted = true;
+		}
+		m_aLines[m_CurrentLine].m_Highlighted =  Highlighted;
 
 		if(ClientID == -1) // server message
 		{
@@ -292,7 +318,7 @@ void CChat::AddLine(int ClientID, int Team, const char *pLine)
 
 		char aBuf[1024];
 		str_format(aBuf, sizeof(aBuf), "%s%s", m_aLines[m_CurrentLine].m_aName, m_aLines[m_CurrentLine].m_aText);
-		Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "chat", aBuf);
+		Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, m_aLines[m_CurrentLine].m_Team?"teamchat":"chat", aBuf);
 	}
 
 	// play sound
@@ -354,7 +380,9 @@ void CChat::OnRender()
 		}
 
 		TextRender()->TextEx(&Cursor, m_Input.GetString()+m_ChatStringOffset, m_Input.GetCursorOffset()-m_ChatStringOffset);
+		static float MarkerOffset = TextRender()->TextWidth(0, 8.0f, "|", -1)/3;
 		CTextCursor Marker = Cursor;
+		Marker.m_X -= MarkerOffset;
 		TextRender()->TextEx(&Marker, "|", -1);
 		TextRender()->TextEx(&Cursor, m_Input.GetString()+m_Input.GetCursorOffset(), -1);
 	}
diff --git a/src/game/client/components/console.cpp b/src/game/client/components/console.cpp
index 33c6db43..f2e9e65d 100644
--- a/src/game/client/components/console.cpp
+++ b/src/game/client/components/console.cpp
@@ -51,7 +51,7 @@ CGameConsole::CInstance::CInstance(int Type)
 	m_CompletionChosen = -1;
 	m_CompletionRenderOffset = 0.0f;
 
-	m_pCommand = 0x0;
+	m_IsCommand = false;
 }
 
 void CGameConsole::CInstance::Init(CGameConsole *pGameConsole)
@@ -127,11 +127,7 @@ void CGameConsole::CInstance::OnInput(IInput::CEvent Event)
 				m_pHistoryEntry = m_History.Last();
 
 			if (m_pHistoryEntry)
-			{
-				unsigned int Len = str_length(m_pHistoryEntry);
-				if (Len < sizeof(m_Input) - 1) // TODO: WTF?
-					m_Input.Set(m_pHistoryEntry);
-			}
+				m_Input.Set(m_pHistoryEntry);
 			Handled = true;
 		}
 		else if (Event.m_Key == KEY_DOWN)
@@ -140,11 +136,7 @@ void CGameConsole::CInstance::OnInput(IInput::CEvent Event)
 				m_pHistoryEntry = m_History.Next(m_pHistoryEntry);
 
 			if (m_pHistoryEntry)
-			{
-				unsigned int Len = str_length(m_pHistoryEntry);
-				if (Len < sizeof(m_Input) - 1) // TODO: WTF?
-					m_Input.Set(m_pHistoryEntry);
-			}
+				m_Input.Set(m_pHistoryEntry);
 			else
 				m_Input.Clear();
 			Handled = true;
@@ -155,14 +147,16 @@ void CGameConsole::CInstance::OnInput(IInput::CEvent Event)
 			{
 				m_CompletionChosen++;
 				m_CompletionEnumerationCount = 0;
-				m_pGameConsole->m_pConsole->PossibleCommands(m_aCompletionBuffer, m_CompletionFlagmask, PossibleCommandsCompleteCallback, this);
+				m_pGameConsole->m_pConsole->PossibleCommands(m_aCompletionBuffer, m_CompletionFlagmask, m_Type != CGameConsole::CONSOLETYPE_LOCAL &&
+					m_pGameConsole->Client()->RconAuthed() && m_pGameConsole->Client()->UseTempRconCommands(),	PossibleCommandsCompleteCallback, this);
 
 				// handle wrapping
 				if(m_CompletionEnumerationCount && m_CompletionChosen >= m_CompletionEnumerationCount)
 				{
 					m_CompletionChosen %= m_CompletionEnumerationCount;
 					m_CompletionEnumerationCount = 0;
-					m_pGameConsole->m_pConsole->PossibleCommands(m_aCompletionBuffer, m_CompletionFlagmask, PossibleCommandsCompleteCallback, this);
+					m_pGameConsole->m_pConsole->PossibleCommands(m_aCompletionBuffer, m_CompletionFlagmask, m_Type != CGameConsole::CONSOLETYPE_LOCAL &&
+						m_pGameConsole->Client()->RconAuthed() && m_pGameConsole->Client()->UseTempRconCommands(),	PossibleCommandsCompleteCallback, this);
 				}
 			}
 		}
@@ -198,7 +192,17 @@ void CGameConsole::CInstance::OnInput(IInput::CEvent Event)
 				aBuf[i] = *pSrc;
 			aBuf[i] = 0;
 
-			m_pCommand = m_pGameConsole->m_pConsole->GetCommandInfo(aBuf, m_CompletionFlagmask);
+			const IConsole::CCommandInfo *pCommand = m_pGameConsole->m_pConsole->GetCommandInfo(aBuf, m_CompletionFlagmask,
+				m_Type != CGameConsole::CONSOLETYPE_LOCAL && m_pGameConsole->Client()->RconAuthed() && m_pGameConsole->Client()->UseTempRconCommands());
+			if(pCommand)
+			{
+				m_IsCommand = true;
+				str_copy(m_aCommandName, pCommand->m_pName, IConsole::TEMPCMD_NAME_LENGTH);
+				str_copy(m_aCommandHelp, pCommand->m_pHelp, IConsole::TEMPCMD_HELP_LENGTH);
+				str_copy(m_aCommandParams, pCommand->m_pParams, IConsole::TEMPCMD_PARAMS_LENGTH);
+			}
+			else
+				m_IsCommand = false;
 		}
 	}
 }
@@ -446,7 +450,9 @@ void CGameConsole::OnRender()
 		}
 
 		TextRender()->TextEx(&Cursor, aInputString, pConsole->m_Input.GetCursorOffset());
+		static float MarkerOffset = TextRender()->TextWidth(0, FontSize, "|", -1)/3;
 		CTextCursor Marker = Cursor;
+		Marker.m_X -= MarkerOffset;
 		TextRender()->TextEx(&Marker, "|", -1);
 		TextRender()->TextEx(&Cursor, aInputString+pConsole->m_Input.GetCursorOffset(), -1);
 
@@ -455,19 +461,19 @@ void CGameConsole::OnRender()
 		{
 			if(pConsole->m_Input.GetString()[0] != 0)
 			{
-				m_pConsole->PossibleCommands(pConsole->m_aCompletionBuffer, pConsole->m_CompletionFlagmask, PossibleCommandsRenderCallback, &Info);
+				m_pConsole->PossibleCommands(pConsole->m_aCompletionBuffer, pConsole->m_CompletionFlagmask, m_ConsoleType != CGameConsole::CONSOLETYPE_LOCAL &&
+					Client()->RconAuthed() && Client()->UseTempRconCommands(), PossibleCommandsRenderCallback, &Info);
 				pConsole->m_CompletionRenderOffset = Info.m_Offset;
 
 				if(Info.m_EnumCount <= 0)
 				{
-					if(pConsole->m_pCommand)
+					if(pConsole->m_IsCommand)
 					{
-
 						char aBuf[512];
-						str_format(aBuf, sizeof(aBuf), "Help: %s ", pConsole->m_pCommand->m_pHelp);
+						str_format(aBuf, sizeof(aBuf), "Help: %s ", pConsole->m_aCommandHelp);
 						TextRender()->TextEx(&Info.m_Cursor, aBuf, -1);
 						TextRender()->TextColor(0.75f, 0.75f, 0.75f, 1);
-						str_format(aBuf, sizeof(aBuf), "Syntax: %s %s", pConsole->m_pCommand->m_pName, pConsole->m_pCommand->m_pParams);
+						str_format(aBuf, sizeof(aBuf), "Syntax: %s %s", pConsole->m_aCommandName, pConsole->m_aCommandParams);
 						TextRender()->TextEx(&Info.m_Cursor, aBuf, -1);
 					}
 				}
@@ -656,6 +662,16 @@ void CGameConsole::ClientConsolePrintCallback(const char *pStr, void *pUserData)
 	((CGameConsole *)pUserData)->m_LocalConsole.PrintLine(pStr);
 }
 
+void CGameConsole::ConchainConsoleOutputLevelUpdate(IConsole::IResult *pResult, void *pUserData, IConsole::FCommandCallback pfnCallback, void *pCallbackUserData)
+{
+	pfnCallback(pResult, pCallbackUserData);
+	if(pResult->NumArguments() == 1)
+	{
+		CGameConsole *pThis = static_cast<CGameConsole *>(pUserData);
+		pThis->Console()->SetPrintOutputLevel(pThis->m_PrintCBIndex, pResult->GetInteger(0));
+	}
+}
+
 void CGameConsole::PrintLine(int Type, const char *pLine)
 {
 	if(Type == CONSOLETYPE_LOCAL)
@@ -673,7 +689,7 @@ void CGameConsole::OnConsoleInit()
 	m_pConsole = Kernel()->RequestInterface<IConsole>();
 
 	//
-	Console()->RegisterPrintCallback(ClientConsolePrintCallback, this);
+	m_PrintCBIndex = Console()->RegisterPrintCallback(g_Config.m_ConsoleOutputLevel, ClientConsolePrintCallback, this);
 
 	Console()->Register("toggle_local_console", "", CFGFLAG_CLIENT, ConToggleLocalConsole, this, "Toggle local console");
 	Console()->Register("toggle_remote_console", "", CFGFLAG_CLIENT, ConToggleRemoteConsole, this, "Toggle remote console");
@@ -681,6 +697,8 @@ void CGameConsole::OnConsoleInit()
 	Console()->Register("clear_remote_console", "", CFGFLAG_CLIENT, ConClearRemoteConsole, this, "Clear remote console");
 	Console()->Register("dump_local_console", "", CFGFLAG_CLIENT, ConDumpLocalConsole, this, "Dump local console");
 	Console()->Register("dump_remote_console", "", CFGFLAG_CLIENT, ConDumpRemoteConsole, this, "Dump remote console");
+
+	Console()->Chain("console_output_level", ConchainConsoleOutputLevelUpdate, this);
 }
 
 void CGameConsole::OnStateChange(int NewState, int OldState)
diff --git a/src/game/client/components/console.h b/src/game/client/components/console.h
index 003a9423..6bcc75a6 100644
--- a/src/game/client/components/console.h
+++ b/src/game/client/components/console.h
@@ -33,7 +33,10 @@ class CGameConsole : public CComponent
 		int m_CompletionFlagmask;
 		float m_CompletionRenderOffset;
 
-		IConsole::CCommandInfo *m_pCommand;
+		bool m_IsCommand;
+		char m_aCommandName[IConsole::TEMPCMD_NAME_LENGTH];
+		char m_aCommandHelp[IConsole::TEMPCMD_HELP_LENGTH];
+		char m_aCommandParams[IConsole::TEMPCMD_PARAMS_LENGTH];
 
 		CInstance(int t);
 		void Init(CGameConsole *pGameConsole);
@@ -57,6 +60,7 @@ class CGameConsole : public CComponent
 
 	CInstance *CurrentConsole();
 	float TimeNow();
+	int m_PrintCBIndex;
 
 	int m_ConsoleType;
 	int m_ConsoleState;
@@ -74,6 +78,7 @@ class CGameConsole : public CComponent
 	static void ConClearRemoteConsole(IConsole::IResult *pResult, void *pUserData);
 	static void ConDumpLocalConsole(IConsole::IResult *pResult, void *pUserData);
 	static void ConDumpRemoteConsole(IConsole::IResult *pResult, void *pUserData);
+	static void ConchainConsoleOutputLevelUpdate(IConsole::IResult *pResult, void *pUserData, IConsole::FCommandCallback pfnCallback, void *pCallbackUserData);
 
 public:
 	enum
diff --git a/src/game/client/components/countryflags.cpp b/src/game/client/components/countryflags.cpp
index 2429ad3f..ef350cd7 100644
--- a/src/game/client/components/countryflags.cpp
+++ b/src/game/client/components/countryflags.cpp
@@ -45,6 +45,15 @@ void CCountryFlags::LoadCountryflagsIndexfile()
 			continue;
 		}
 
+		int CountryCode = str_toint(pReplacement+3);
+		if(CountryCode < CODE_LB || CountryCode > CODE_UB)
+		{
+			char aBuf[128];
+			str_format(aBuf, sizeof(aBuf), "country code '%i' not within valid code range [%i..%i]", CountryCode, CODE_LB, CODE_UB);
+			Console()->Print(IConsole::OUTPUT_LEVEL_ADDINFO, "countryflags", aBuf);
+			continue;
+		}
+
 		// load the graphic file
 		char aBuf[128];
 		str_format(aBuf, sizeof(aBuf), "countryflags/%s.png", aOrigin);
@@ -59,7 +68,8 @@ void CCountryFlags::LoadCountryflagsIndexfile()
 
 		// add entry
 		CCountryFlag CountryFlag;
-		CountryFlag.m_CountryCode = str_toint(pReplacement+3);
+		CountryFlag.m_CountryCode = CountryCode;
+		str_copy(CountryFlag.m_aCountryCodeString, aOrigin, sizeof(CountryFlag.m_aCountryCodeString));
 		CountryFlag.m_Texture = Graphics()->LoadTextureRaw(Info.m_Width, Info.m_Height, Info.m_Format, Info.m_pData, Info.m_Format, 0);
 		mem_free(Info.m_pData);
 		str_format(aBuf, sizeof(aBuf), "loaded country flag '%s'", aOrigin);
@@ -67,6 +77,10 @@ void CCountryFlags::LoadCountryflagsIndexfile()
 		m_aCountryFlags.add(CountryFlag);
 	}
 	io_close(File);
+
+	mem_zero(m_CodeIndexLUT, sizeof(m_CodeIndexLUT));
+	for(int i = 0; i < m_aCountryFlags.size(); ++i)
+		m_CodeIndexLUT[max(0, (m_aCountryFlags[i].m_CountryCode-CODE_LB)%CODE_RANGE)] = i+1;
 }
 
 void CCountryFlags::OnInit()
@@ -89,17 +103,12 @@ int CCountryFlags::Num() const
 	return m_aCountryFlags.size();
 }
 
-const CCountryFlags::CCountryFlag *CCountryFlags::Get(int Index) const
+const CCountryFlags::CCountryFlag *CCountryFlags::GetByCountryCode(int CountryCode) const
 {
-	return &m_aCountryFlags[max(0, Index%m_aCountryFlags.size())];
+	return GetByIndex(m_CodeIndexLUT[max(0, (CountryCode-CODE_LB)%CODE_RANGE)]-1);
 }
 
-int CCountryFlags::Find(int CountryCode) const
+const CCountryFlags::CCountryFlag *CCountryFlags::GetByIndex(int Index) const
 {
-	for(int i = 0; i < m_aCountryFlags.size(); ++i)
-	{
-		if(m_aCountryFlags[i].m_CountryCode == CountryCode)
-			return i;
-	}
-	return -1;
+	return &m_aCountryFlags[max(0, Index%m_aCountryFlags.size())];
 }
diff --git a/src/game/client/components/countryflags.h b/src/game/client/components/countryflags.h
index cd629094..ad24a762 100644
--- a/src/game/client/components/countryflags.h
+++ b/src/game/client/components/countryflags.h
@@ -12,19 +12,28 @@ public:
 	struct CCountryFlag
 	{
 		int m_CountryCode;
+		char m_aCountryCodeString[8];
 		int m_Texture;
 
-		bool operator<(const CCountryFlag &Other) { return m_CountryCode < Other.m_CountryCode; }
+		bool operator<(const CCountryFlag &Other) { return str_comp(m_aCountryCodeString, Other.m_aCountryCodeString) < 0; }
 	};
 
 	void OnInit();
 
 	int Num() const;
-	const CCountryFlag *Get(int Index) const;
-	int Find(int CountryCode) const;
+	const CCountryFlag *GetByCountryCode(int CountryCode) const;
+	const CCountryFlag *GetByIndex(int Index) const;
+	//int Find(int CountryCode) const;
 
 private:
+	enum
+	{
+		CODE_LB=-1,
+		CODE_UB=999,
+		CODE_RANGE=CODE_UB-CODE_LB+1,
+	};
 	sorted_array<CCountryFlag> m_aCountryFlags;
+	int m_CodeIndexLUT[CODE_RANGE];
 
 	void LoadCountryflagsIndexfile();
 };
diff --git a/src/game/client/components/debughud.cpp b/src/game/client/components/debughud.cpp
index 7145705c..6adc61b2 100644
--- a/src/game/client/components/debughud.cpp
+++ b/src/game/client/components/debughud.cpp
@@ -42,11 +42,11 @@ void CDebugHud::RenderNetCorrections()
 
 	x = Width-10.0f;
 	char aBuf[128];
-	str_format(aBuf, sizeof(aBuf), "%.0f", Velspeed);
+	str_format(aBuf, sizeof(aBuf), "%.0f", Velspeed/32);
 	float w = TextRender()->TextWidth(0, Fontsize, aBuf, -1);
 	TextRender()->Text(0, x-w, y, Fontsize, aBuf, -1);
 	y += LineHeight;
-	str_format(aBuf, sizeof(aBuf), "%.0f", Velspeed*Ramp);
+	str_format(aBuf, sizeof(aBuf), "%.0f", Velspeed/32*Ramp);
 	w = TextRender()->TextWidth(0, Fontsize, aBuf, -1);
 	TextRender()->Text(0, x-w, y, Fontsize, aBuf, -1);
 	y += LineHeight;
@@ -54,11 +54,11 @@ void CDebugHud::RenderNetCorrections()
 	w = TextRender()->TextWidth(0, Fontsize, aBuf, -1);
 	TextRender()->Text(0, x-w, y, Fontsize, aBuf, -1);
 	y += 2*LineHeight;
-	str_format(aBuf, sizeof(aBuf), "%d", m_pClient->m_Snap.m_pLocalCharacter->m_X);
+	str_format(aBuf, sizeof(aBuf), "%d", m_pClient->m_Snap.m_pLocalCharacter->m_X/32);
 	w = TextRender()->TextWidth(0, Fontsize, aBuf, -1);
 	TextRender()->Text(0, x-w, y, Fontsize, aBuf, -1);
 	y += LineHeight;
-	str_format(aBuf, sizeof(aBuf), "%d", m_pClient->m_Snap.m_pLocalCharacter->m_Y);
+	str_format(aBuf, sizeof(aBuf), "%d", m_pClient->m_Snap.m_pLocalCharacter->m_Y/32);
 	w = TextRender()->TextWidth(0, Fontsize, aBuf, -1);
 	TextRender()->Text(0, x-w, y, Fontsize, aBuf, -1);
 	y += 2*LineHeight;
diff --git a/src/game/client/components/effects.cpp b/src/game/client/components/effects.cpp
index 573ac410..8554b7ba 100644
--- a/src/game/client/components/effects.cpp
+++ b/src/game/client/components/effects.cpp
@@ -86,7 +86,7 @@ void CEffects::SmokeTrail(vec2 Pos, vec2 Vel)
 	p.m_LifeSpan = 0.5f + frandom()*0.5f;
 	p.m_StartSize = 12.0f + frandom()*8;
 	p.m_EndSize = 0;
-	p.m_Friction = 0.7;
+	p.m_Friction = 0.7f;
 	p.m_Gravity = frandom()*-500.0f;
 	m_pClient->m_pParticles->Add(CParticles::GROUP_PROJECTILE_TRAIL, &p);
 }
diff --git a/src/game/client/components/emoticon.cpp b/src/game/client/components/emoticon.cpp
index 741a604f..b2f48b80 100644
--- a/src/game/client/components/emoticon.cpp
+++ b/src/game/client/components/emoticon.cpp
@@ -54,6 +54,7 @@ bool CEmoticon::OnMouseMove(float x, float y)
 	if(!m_Active)
 		return false;
 
+	UI()->ConvertMouseMove(&x, &y);
 	m_SelectorMouse += vec2(x,y);
 	return true;
 }
@@ -101,6 +102,13 @@ void CEmoticon::OnRender()
 		return;
 	}
 
+	if(m_pClient->m_Snap.m_SpecInfo.m_Active)
+	{
+		m_Active = false;
+		m_WasActive = false;
+		return;
+	}
+
 	m_WasActive = true;
 
 	if (length(m_SelectorMouse) > 140)
diff --git a/src/game/client/components/hud.cpp b/src/game/client/components/hud.cpp
index 188691a1..11343912 100644
--- a/src/game/client/components/hud.cpp
+++ b/src/game/client/components/hud.cpp
@@ -35,7 +35,7 @@ void CHud::RenderGameTimer()
 	{
 		char Buf[32];
 		int Time = 0;
-		if(m_pClient->m_Snap.m_pGameInfoObj->m_TimeLimit)
+		if(m_pClient->m_Snap.m_pGameInfoObj->m_TimeLimit && !m_pClient->m_Snap.m_pGameInfoObj->m_WarmupTimer)
 		{
 			Time = m_pClient->m_Snap.m_pGameInfoObj->m_TimeLimit*60 - ((Client()->GameTick()-m_pClient->m_Snap.m_pGameInfoObj->m_RoundStartTick)/Client()->GameTickSpeed());
 
@@ -49,7 +49,7 @@ void CHud::RenderGameTimer()
 		float FontSize = 10.0f;
 		float w = TextRender()->TextWidth(0, FontSize, Buf, -1);
 		// last 60 sec red, last 10 sec blink
-		if(m_pClient->m_Snap.m_pGameInfoObj->m_TimeLimit && Time <= 60)
+		if(m_pClient->m_Snap.m_pGameInfoObj->m_TimeLimit && Time <= 60 && !m_pClient->m_Snap.m_pGameInfoObj->m_WarmupTimer)
 		{
 			float Alpha = Time <= 10 && (2*time_get()/time_freq()) % 2 ? 0.5f : 1.0f;
 			TextRender()->TextColor(1.0f, 0.25f, 0.25f, Alpha);
@@ -108,7 +108,9 @@ void CHud::RenderScoreHud()
 
 				if(GameFlags&GAMEFLAG_FLAGS)
 				{
-					if(FlagCarrier[t] == FLAG_ATSTAND || (FlagCarrier[t] == FLAG_TAKEN && ((Client()->GameTick()/10)&1)))
+					int BlinkTimer = (m_pClient->m_FlagDropTick[t] != 0 && 
+										(Client()->GameTick()-m_pClient->m_FlagDropTick[t])/Client()->GameTickSpeed() >= 25) ? 10 : 20;
+					if(FlagCarrier[t] == FLAG_ATSTAND || (FlagCarrier[t] == FLAG_TAKEN && ((Client()->GameTick()/BlinkTimer)&1)))
 					{
 						// draw flag
 						Graphics()->BlendNormal();
diff --git a/src/game/client/components/maplayers.cpp b/src/game/client/components/maplayers.cpp
index 848651ca..096f9cc5 100644
--- a/src/game/client/components/maplayers.cpp
+++ b/src/game/client/components/maplayers.cpp
@@ -63,21 +63,25 @@ void CMapLayers::EnvelopeEval(float TimeOffset, int Env, float *pChannels, void
 
 	CMapItemEnvelope *pItem = (CMapItemEnvelope *)pThis->m_pLayers->Map()->GetItem(Start+Env, 0, 0);
 
+	static float Time = 0;
 	if(pThis->Client()->State() == IClient::STATE_DEMOPLAYBACK)
 	{
 		const IDemoPlayer::CInfo *pInfo = pThis->DemoPlayer()->BaseInfo();
-		static float Time = 0;
-		static float LastLocalTime = pThis->Client()->LocalTime();
+		static int LastLocalTick = pInfo->m_CurrentTick;
 
 		if(!pInfo->m_Paused)
-			Time += (pThis->Client()->LocalTime()-LastLocalTime)*pInfo->m_Speed;
+			Time += (pInfo->m_CurrentTick-LastLocalTick) / (float)pThis->Client()->GameTickSpeed() * pInfo->m_Speed;
 
 		pThis->RenderTools()->RenderEvalEnvelope(pPoints+pItem->m_StartPoint, pItem->m_NumPoints, 4, Time+TimeOffset, pChannels);
 
-		LastLocalTime = pThis->Client()->LocalTime();
+		LastLocalTick = pInfo->m_CurrentTick;
 	}
 	else
-		pThis->RenderTools()->RenderEvalEnvelope(pPoints+pItem->m_StartPoint, pItem->m_NumPoints, 4, pThis->Client()->LocalTime()+TimeOffset, pChannels);
+	{
+		if(pThis->m_pClient->m_Snap.m_pGameInfoObj)
+			Time = (pThis->Client()->GameTick()-pThis->m_pClient->m_Snap.m_pGameInfoObj->m_RoundStartTick) / (float)pThis->Client()->GameTickSpeed();
+		pThis->RenderTools()->RenderEvalEnvelope(pPoints+pItem->m_StartPoint, pItem->m_NumPoints, 4, Time+TimeOffset, pChannels);
+	}
 }
 
 void CMapLayers::OnRender()
@@ -186,9 +190,11 @@ void CMapLayers::OnRender()
 					CTile *pTiles = (CTile *)m_pLayers->Map()->GetData(pTMap->m_Data);
 					Graphics()->BlendNone();
 					vec4 Color = vec4(pTMap->m_Color.r/255.0f, pTMap->m_Color.g/255.0f, pTMap->m_Color.b/255.0f, pTMap->m_Color.a/255.0f);
-					RenderTools()->RenderTilemap(pTiles, pTMap->m_Width, pTMap->m_Height, 32.0f, Color, TILERENDERFLAG_EXTEND|LAYERRENDERFLAG_OPAQUE);
+					RenderTools()->RenderTilemap(pTiles, pTMap->m_Width, pTMap->m_Height, 32.0f, Color, TILERENDERFLAG_EXTEND|LAYERRENDERFLAG_OPAQUE,
+													EnvelopeEval, this, pTMap->m_ColorEnv, pTMap->m_ColorEnvOffset);
 					Graphics()->BlendNormal();
-					RenderTools()->RenderTilemap(pTiles, pTMap->m_Width, pTMap->m_Height, 32.0f, Color, TILERENDERFLAG_EXTEND|LAYERRENDERFLAG_TRANSPARENT);
+					RenderTools()->RenderTilemap(pTiles, pTMap->m_Width, pTMap->m_Height, 32.0f, Color, TILERENDERFLAG_EXTEND|LAYERRENDERFLAG_TRANSPARENT,
+													EnvelopeEval, this, pTMap->m_ColorEnv, pTMap->m_ColorEnvOffset);
 				}
 				else if(pLayer->m_Type == LAYERTYPE_QUADS)
 				{
diff --git a/src/game/client/components/menus.cpp b/src/game/client/components/menus.cpp
index 53192714..8f330f78 100644
--- a/src/game/client/components/menus.cpp
+++ b/src/game/client/components/menus.cpp
@@ -27,6 +27,7 @@
 #include <game/localization.h>
 #include <mastersrv/mastersrv.h>
 
+#include "countryflags.h"
 #include "menus.h"
 #include "skins.h"
 
@@ -209,7 +210,7 @@ int CMenus::DoEditBox(void *pID, const CUIRect *pRect, char *pStr, unsigned StrS
 
 			for(int i = 1; i <= Len; i++)
 			{
-				if(TextRender()->TextWidth(0, FontSize, pStr, i) - *Offset + 10 > MxRel)
+				if(TextRender()->TextWidth(0, FontSize, pStr, i) - *Offset > MxRel)
 				{
 					s_AtIndex = i - 1;
 					break;
@@ -251,6 +252,7 @@ int CMenus::DoEditBox(void *pID, const CUIRect *pRect, char *pStr, unsigned StrS
 	{
 		if(!UI()->MouseButton(0))
 		{
+			s_AtIndex = min(s_AtIndex, str_length(pStr));
 			s_DoScroll = false;
 			UI()->SetActiveItem(0);
 		}
@@ -645,8 +647,6 @@ void CMenus::RenderLoading()
 
 	RenderBackground();
 
-	float tw;
-
 	float w = 700;
 	float h = 200;
 	float x = Screen.w/2-w/2;
@@ -663,7 +663,6 @@ void CMenus::RenderLoading()
 
 	const char *pCaption = Localize("Loading");
 
-	tw = TextRender()->TextWidth(0, 48.0f, pCaption, -1);
 	CUIRect r;
 	r.x = x;
 	r.y = y+20;
@@ -737,6 +736,8 @@ void CMenus::OnInit()
 
 	Console()->Chain("add_favorite", ConchainServerbrowserUpdate, this);
 	Console()->Chain("remove_favorite", ConchainServerbrowserUpdate, this);
+	Console()->Chain("add_friend", ConchainFriendlistUpdate, this);
+	Console()->Chain("remove_friend", ConchainFriendlistUpdate, this);
 
 	// setup load amount
 	m_LoadCurrent = 0;
@@ -960,7 +961,7 @@ int CMenus::Render()
 			Box.VMargin(20.f/UI()->Scale(), &Box);
 			if(m_pClient->Editor()->HasUnsavedData())
 			{
-				char aBuf[128];
+				char aBuf[256];
 				str_format(aBuf, sizeof(aBuf), "%s\n%s", Localize("There's an unsaved map in the editor, you might want to save it before you quit the game."), Localize("Quit anyway?"));
 				UI()->DoLabelScaled(&Box, aBuf, 20.f, -1, Part.w-20.0f);
 			}
@@ -1058,7 +1059,7 @@ int CMenus::Render()
 
 				// time left
 				const char *pTimeLeftString;
-				int TimeLeft = m_DownloadSpeed > 0.0f ? (Client()->MapDownloadTotalsize()-Client()->MapDownloadAmount())/m_DownloadSpeed : 0.0f;
+				int TimeLeft = max(1, m_DownloadSpeed > 0.0f ? static_cast<int>((Client()->MapDownloadTotalsize()-Client()->MapDownloadAmount())/m_DownloadSpeed) : 1);
 				if(TimeLeft >= 60)
 				{
 					TimeLeft /= 60;
@@ -1097,6 +1098,69 @@ int CMenus::Render()
 			if(DoButton_Menu(&s_Button, Localize("Ok"), 0, &Part) || m_EscapePressed || m_EnterPressed)
 				m_Popup = POPUP_FIRST_LAUNCH;
 		}
+		else if(m_Popup == POPUP_COUNTRY)
+		{
+			Box = Screen;
+			Box.VMargin(150.0f, &Box);
+			Box.HMargin(150.0f, &Box);
+			Box.HSplitTop(20.f, &Part, &Box);
+			Box.HSplitBottom(20.f, &Box, &Part);
+			Box.HSplitBottom(24.f, &Box, &Part);
+			Box.HSplitBottom(20.f, &Box, 0);
+			Box.VMargin(20.0f, &Box);
+			
+			static int ActSelection = -2;
+			if(ActSelection == -2)
+				ActSelection = g_Config.m_BrFilterCountryIndex;
+			static float s_ScrollValue = 0.0f;
+			int OldSelected = -1;
+			UiDoListboxStart(&s_ScrollValue, &Box, 50.0f, Localize("Country"), "", m_pClient->m_pCountryFlags->Num(), 6, OldSelected, s_ScrollValue);
+
+			for(int i = 0; i < m_pClient->m_pCountryFlags->Num(); ++i)
+			{
+				const CCountryFlags::CCountryFlag *pEntry = m_pClient->m_pCountryFlags->GetByIndex(i);
+				if(pEntry->m_CountryCode == ActSelection)
+					OldSelected = i;
+
+				CListboxItem Item = UiDoListboxNextItem(&pEntry->m_CountryCode, OldSelected == i);
+				if(Item.m_Visible)
+				{
+					CUIRect Label;
+					Item.m_Rect.Margin(5.0f, &Item.m_Rect);
+					Item.m_Rect.HSplitBottom(10.0f, &Item.m_Rect, &Label);
+					float OldWidth = Item.m_Rect.w;
+					Item.m_Rect.w = Item.m_Rect.h*2;
+					Item.m_Rect.x += (OldWidth-Item.m_Rect.w)/ 2.0f;
+					Graphics()->TextureSet(pEntry->m_Texture);
+					Graphics()->QuadsBegin();
+					Graphics()->SetColor(1.0f, 1.0f, 1.0f, 1.0f);
+					IGraphics::CQuadItem QuadItem(Item.m_Rect.x, Item.m_Rect.y, Item.m_Rect.w, Item.m_Rect.h);
+					Graphics()->QuadsDrawTL(&QuadItem, 1);
+					Graphics()->QuadsEnd();
+					UI()->DoLabel(&Label, pEntry->m_aCountryCodeString, 10.0f, 0);
+				}
+			}
+
+			const int NewSelected = UiDoListboxEnd(&s_ScrollValue, 0);
+			if(OldSelected != NewSelected)
+				ActSelection = m_pClient->m_pCountryFlags->GetByIndex(NewSelected)->m_CountryCode;
+
+			Part.VMargin(120.0f, &Part);
+
+			static int s_Button = 0;
+			if(DoButton_Menu(&s_Button, Localize("Ok"), 0, &Part) || m_EnterPressed)
+			{
+				g_Config.m_BrFilterCountryIndex = ActSelection;
+				Client()->ServerBrowserUpdate();
+				m_Popup = POPUP_NONE;
+			}
+
+			if(m_EscapePressed)
+			{
+				ActSelection = g_Config.m_BrFilterCountryIndex;
+				m_Popup = POPUP_NONE;
+			}
+		}
 		else if(m_Popup == POPUP_DELETE_DEMO)
 		{
 			CUIRect Yes, No;
@@ -1208,7 +1272,9 @@ int CMenus::Render()
 				// remove friend
 				if(m_FriendlistSelectedIndex >= 0)
 				{
-					m_pClient->Friends()->RemoveFriend(m_FriendlistSelectedIndex);
+					m_pClient->Friends()->RemoveFriend(m_lFriends[m_FriendlistSelectedIndex].m_pFriendInfo->m_aName,
+						m_lFriends[m_FriendlistSelectedIndex].m_pFriendInfo->m_aClan);
+					FriendlistOnUpdate();
 					Client()->ServerBrowserUpdate();
 				}
 			}
@@ -1285,6 +1351,7 @@ bool CMenus::OnMouseMove(float x, float y)
 	if(!m_MenuActive)
 		return false;
 
+	UI()->ConvertMouseMove(&x, &y);
 	m_MousePos.x += x;
 	m_MousePos.y += y;
 	if(m_MousePos.x < 0) m_MousePos.x = 0;
@@ -1335,7 +1402,8 @@ void CMenus::OnStateChange(int NewState, int OldState)
 
 	if(NewState == IClient::STATE_OFFLINE)
 	{
-		m_pClient->m_pSounds->Play(CSounds::CHN_MUSIC, SOUND_MENU, 1.0f, vec2(0, 0));
+		if(OldState >= IClient::STATE_ONLINE)
+			m_pClient->m_pSounds->Play(CSounds::CHN_MUSIC, SOUND_MENU, 1.0f, vec2(0, 0));
 		m_Popup = POPUP_NONE;
 		if(Client()->ErrorString() && Client()->ErrorString()[0] != 0)
 		{
diff --git a/src/game/client/components/menus.h b/src/game/client/components/menus.h
index 51b8a1f8..585fb91f 100644
--- a/src/game/client/components/menus.h
+++ b/src/game/client/components/menus.h
@@ -7,6 +7,7 @@
 #include <base/tl/sorted_array.h>
 
 #include <engine/demo.h>
+#include <engine/friends.h>
 
 #include <game/voting.h>
 #include <game/client/component.h>
@@ -100,6 +101,7 @@ class CMenus : public CComponent
 		POPUP_DISCONNECTED,
 		POPUP_PURE,
 		POPUP_LANGUAGE,
+		POPUP_COUNTRY,
 		POPUP_DELETE_DEMO,
 		POPUP_RENAME_DEMO,
 		POPUP_REMOVE_FRIEND,
@@ -202,8 +204,34 @@ class CMenus : public CComponent
 	void DemolistPopulate();
 	static int DemolistFetchCallback(const char *pName, int IsDir, int StorageType, void *pUser);
 
+	// friends
+	struct CFriendItem
+	{
+		const CFriendInfo *m_pFriendInfo;
+		int m_NumFound;
+
+		bool operator<(const CFriendItem &Other)
+		{
+			if(m_NumFound && !Other.m_NumFound)
+				return true;
+			else if(!m_NumFound && Other.m_NumFound)
+				return false;
+			else
+			{
+				int Result = str_comp(m_pFriendInfo->m_aName, Other.m_pFriendInfo->m_aName);
+				if(Result)
+					return Result < 0;
+				else
+					return str_comp(m_pFriendInfo->m_aClan, Other.m_pFriendInfo->m_aClan) < 0;
+			}
+		}
+	};
+
+	sorted_array<CFriendItem> m_lFriends;
 	int m_FriendlistSelectedIndex;
 
+	void FriendlistOnUpdate();
+
 	// found in menus.cpp
 	int Render();
 	//void render_background();
@@ -225,11 +253,13 @@ class CMenus : public CComponent
 
 	// found in menus_browser.cpp
 	int m_SelectedIndex;
+	int m_ScrollOffset;
 	void RenderServerbrowserServerList(CUIRect View);
 	void RenderServerbrowserServerDetail(CUIRect View);
 	void RenderServerbrowserFilters(CUIRect View);
 	void RenderServerbrowserFriends(CUIRect View);
 	void RenderServerbrowser(CUIRect MainView);
+	static void ConchainFriendlistUpdate(IConsole::IResult *pResult, void *pUserData, IConsole::FCommandCallback pfnCallback, void *pCallbackUserData);
 	static void ConchainServerbrowserUpdate(IConsole::IResult *pResult, void *pUserData, IConsole::FCommandCallback pfnCallback, void *pCallbackUserData);
 
 	// found in menus_settings.cpp
diff --git a/src/game/client/components/menus_browser.cpp b/src/game/client/components/menus_browser.cpp
index 3ab02db8..8501c67d 100644
--- a/src/game/client/components/menus_browser.cpp
+++ b/src/game/client/components/menus_browser.cpp
@@ -154,9 +154,14 @@ void CMenus::RenderServerbrowserServerList(CUIRect View)
 	int ScrollNum = NumServers-Num+1;
 	if(ScrollNum > 0)
 	{
-		if(Input()->KeyPresses(KEY_MOUSE_WHEEL_UP))
+		if(m_ScrollOffset)
+		{
+			s_ScrollValue = (float)(m_ScrollOffset)/ScrollNum;
+			m_ScrollOffset = 0;
+		}
+		if(Input()->KeyPresses(KEY_MOUSE_WHEEL_UP) && UI()->MouseInside(&View))
 			s_ScrollValue -= 3.0f/ScrollNum;
-		if(Input()->KeyPresses(KEY_MOUSE_WHEEL_DOWN))
+		if(Input()->KeyPresses(KEY_MOUSE_WHEEL_DOWN) && UI()->MouseInside(&View))
 			s_ScrollValue += 3.0f/ScrollNum;
 	}
 	else
@@ -213,16 +218,14 @@ void CMenus::RenderServerbrowserServerList(CUIRect View)
 
 	m_SelectedIndex = -1;
 
-	for (int i = 0; i < NumServers; i++)
-	{
-		const CServerInfo *pItem = ServerBrowser()->SortedGet(i);
-		NumPlayers += pItem->m_NumPlayers;
-	}
+	// reset friend counter
+	for(int i = 0; i < m_lFriends.size(); m_lFriends[i++].m_NumFound = 0);
 
 	for (int i = 0; i < NumServers; i++)
 	{
 		int ItemIndex = i;
 		const CServerInfo *pItem = ServerBrowser()->SortedGet(ItemIndex);
+		NumPlayers += pItem->m_NumPlayers;
 		CUIRect Row;
 		CUIRect SelectHitBox;
 
@@ -234,6 +237,29 @@ void CMenus::RenderServerbrowserServerList(CUIRect View)
 		if(Selected)
 			m_SelectedIndex = i;
 
+		// update friend counter
+		if(pItem->m_FriendState != IFriends::FRIEND_NO)
+		{
+			for(int j = 0; j < pItem->m_NumClients; ++j)
+			{
+				if(pItem->m_aClients[j].m_FriendState != IFriends::FRIEND_NO)
+				{
+					unsigned NameHash = str_quickhash(pItem->m_aClients[j].m_aName);
+					unsigned ClanHash = str_quickhash(pItem->m_aClients[j].m_aClan);
+					for(int f = 0; f < m_lFriends.size(); ++f)
+					{
+						if(ClanHash == m_lFriends[f].m_pFriendInfo->m_ClanHash &&
+							(!m_lFriends[f].m_pFriendInfo->m_aName[0] || NameHash == m_lFriends[f].m_pFriendInfo->m_NameHash))
+						{
+							m_lFriends[f].m_NumFound++;
+							if(m_lFriends[f].m_pFriendInfo->m_aName[0])
+								break;
+						}
+					}
+				}
+			}
+		}
+
 		// make sure that only those in view can be selected
 		if(Row.y+Row.h > OriginalView.y && Row.y < OriginalView.y+OriginalView.h)
 		{
@@ -353,6 +379,15 @@ void CMenus::RenderServerbrowserServerList(CUIRect View)
 			}
 			else if(ID == COL_PLAYERS)
 			{
+				CUIRect Icon;
+				Button.VMargin(4.0f, &Button);
+				if(pItem->m_FriendState != IFriends::FRIEND_NO)
+				{
+					Button.VSplitLeft(Button.h, &Icon, &Button);
+					Icon.Margin(2.0f, &Icon);
+					DoButton_Icon(IMAGE_BROWSEICONS, SPRITE_BROWSE_HEART, &Icon);
+				}
+
 				if(g_Config.m_BrFilterSpectators)
 					str_format(aTemp, sizeof(aTemp), "%i/%i", pItem->m_NumPlayers, pItem->m_MaxPlayers);
 				else
@@ -463,7 +498,7 @@ void CMenus::RenderServerbrowserFilters(CUIRect View)
 		g_Config.m_BrFilterFull ^= 1;
 
 	ServerFilter.HSplitTop(20.0f, &Button, &ServerFilter);
-	if (DoButton_CheckBox(&g_Config.m_BrFilterFriends, Localize("Show friends"), g_Config.m_BrFilterFriends, &Button))
+	if (DoButton_CheckBox(&g_Config.m_BrFilterFriends, Localize("Show friends only"), g_Config.m_BrFilterFriends, &Button))
 		g_Config.m_BrFilterFriends ^= 1;
 
 	ServerFilter.HSplitTop(20.0f, &Button, &ServerFilter);
@@ -481,6 +516,10 @@ void CMenus::RenderServerbrowserFilters(CUIRect View)
 	ServerFilter.HSplitTop(20.0f, &Button, &ServerFilter);
 	if (DoButton_CheckBox((char *)&g_Config.m_BrFilterPureMap, Localize("Standard map"), g_Config.m_BrFilterPureMap, &Button))
 		g_Config.m_BrFilterPureMap ^= 1;
+	
+	ServerFilter.HSplitTop(20.0f, &Button, &ServerFilter);
+	if (DoButton_CheckBox((char *)&g_Config.m_BrFilterGametypeStrict, Localize("Strict gametype filter"), g_Config.m_BrFilterGametypeStrict, &Button))
+		g_Config.m_BrFilterGametypeStrict ^= 1;
 
 	ServerFilter.HSplitTop(5.0f, 0, &ServerFilter);
 
@@ -515,21 +554,50 @@ void CMenus::RenderServerbrowserFilters(CUIRect View)
 	if(DoEditBox(&g_Config.m_BrFilterServerAddress, &Button, g_Config.m_BrFilterServerAddress, sizeof(g_Config.m_BrFilterServerAddress), FontSize, &OffsetAddr))
 		Client()->ServerBrowserUpdate();
 
+	// player country
+	{
+		CUIRect Rect;
+		ServerFilter.HSplitTop(3.0f, 0, &ServerFilter);
+		ServerFilter.HSplitTop(26.0f, &Button, &ServerFilter);
+		Button.VSplitRight(60.0f, &Button, &Rect);
+		Button.HMargin(3.0f, &Button);
+		if(DoButton_CheckBox(&g_Config.m_BrFilterCountry, Localize("Player country:"), g_Config.m_BrFilterCountry, &Button))
+			g_Config.m_BrFilterCountry ^= 1;
+		
+		float OldWidth = Rect.w;
+		Rect.w = Rect.h*2;
+		Rect.x += (OldWidth-Rect.w)/2.0f;
+		Graphics()->TextureSet(m_pClient->m_pCountryFlags->GetByCountryCode(g_Config.m_BrFilterCountryIndex)->m_Texture);
+		Graphics()->QuadsBegin();
+		Graphics()->SetColor(1.0f, 1.0f, 1.0f, g_Config.m_BrFilterCountry?1.0f: 0.5f);
+		IGraphics::CQuadItem QuadItem(Rect.x, Rect.y, Rect.w, Rect.h);
+		Graphics()->QuadsDrawTL(&QuadItem, 1);
+		Graphics()->QuadsEnd();
+
+		if(g_Config.m_BrFilterCountry && UI()->DoButtonLogic(&g_Config.m_BrFilterCountryIndex, "", 0, &Rect))
+			m_Popup = POPUP_COUNTRY;
+	}
+
 	ServerFilter.HSplitBottom(5.0f, &ServerFilter, 0);
 	ServerFilter.HSplitBottom(ms_ButtonHeight-2.0f, &ServerFilter, &Button);
 	static int s_ClearButton = 0;
 	if(DoButton_Menu(&s_ClearButton, Localize("Reset filter"), 0, &Button))
 	{
+		g_Config.m_BrFilterString[0] = 0;
 		g_Config.m_BrFilterFull = 0;
 		g_Config.m_BrFilterEmpty = 0;
+		g_Config.m_BrFilterSpectators = 0;
+		g_Config.m_BrFilterFriends = 0;
+		g_Config.m_BrFilterCountry = 0;
+		g_Config.m_BrFilterCountryIndex = -1;
 		g_Config.m_BrFilterPw = 0;
 		g_Config.m_BrFilterPing = 999;
 		g_Config.m_BrFilterGametype[0] = 0;
+		g_Config.m_BrFilterGametypeStrict = 0;
 		g_Config.m_BrFilterServerAddress[0] = 0;
-		g_Config.m_BrFilterCompatversion = 1;
-		g_Config.m_BrFilterString[0] = 0;
 		g_Config.m_BrFilterPure = 1;
 		g_Config.m_BrFilterPureMap = 1;
+		g_Config.m_BrFilterCompatversion = 1;
 		Client()->ServerBrowserUpdate();
 	}
 }
@@ -617,25 +685,38 @@ void CMenus::RenderServerbrowserServerDetail(CUIRect View)
 	RenderTools()->DrawUIRect(&ServerScoreBoard, vec4(0,0,0,0.15f), CUI::CORNER_B, 4.0f);
 	UI()->DoLabelScaled(&ServerHeader, Localize("Scoreboard"), FontSize+2.0f, 0);
 
-	if (pSelectedServer)
+	if(pSelectedServer)
 	{
-		ServerScoreBoard.VSplitLeft(5.0f, 0, &ServerScoreBoard);
 		ServerScoreBoard.Margin(3.0f, &ServerScoreBoard);
 		for (int i = 0; i < pSelectedServer->m_NumClients; i++)
 		{
 			CUIRect Name, Clan, Score, Flag;
 			ServerScoreBoard.HSplitTop(25.0f, &Name, &ServerScoreBoard);
+			if(UI()->DoButtonLogic(&pSelectedServer->m_aClients[i], "", 0, &Name))
+			{
+				if(pSelectedServer->m_aClients[i].m_FriendState == IFriends::FRIEND_PLAYER)
+					m_pClient->Friends()->RemoveFriend(pSelectedServer->m_aClients[i].m_aName, pSelectedServer->m_aClients[i].m_aClan);
+				else
+					m_pClient->Friends()->AddFriend(pSelectedServer->m_aClients[i].m_aName, pSelectedServer->m_aClients[i].m_aClan);
+				FriendlistOnUpdate();
+				Client()->ServerBrowserUpdate();
+			}
+
+			vec4 Colour = pSelectedServer->m_aClients[i].m_FriendState == IFriends::FRIEND_NO ? vec4(1.0f, 1.0f, 1.0f, (i%2+1)*0.05f) :
+																								vec4(0.5f, 1.0f, 0.5f, 0.15f+(i%2+1)*0.05f);
+			RenderTools()->DrawUIRect(&Name, Colour, CUI::CORNER_ALL, 4.0f);
+			Name.VSplitLeft(5.0f, 0, &Name);
 			Name.VSplitLeft(30.0f, &Score, &Name);
 			Name.VSplitRight(34.0f, &Name, &Flag);
 			Flag.HMargin(4.0f, &Flag);
-			Name.HSplitTop(12.0f, &Name, &Clan);
+			Name.HSplitTop(11.0f, &Name, &Clan);
 
 			// score
 			if(pSelectedServer->m_aClients[i].m_Player)
 			{
 				char aTemp[16];
 				str_format(aTemp, sizeof(aTemp), "%d", pSelectedServer->m_aClients[i].m_Score);
-				TextRender()->SetCursor(&Cursor, Score.x, Score.y+(Score.h-FontSize)/2.0f, FontSize, TEXTFLAG_RENDER|TEXTFLAG_STOP_AT_END);
+				TextRender()->SetCursor(&Cursor, Score.x, Score.y+(Score.h-FontSize)/4.0f, FontSize, TEXTFLAG_RENDER|TEXTFLAG_STOP_AT_END);
 				Cursor.m_LineWidth = Score.w;
 				TextRender()->TextEx(&Cursor, aTemp, -1);
 			}
@@ -685,7 +766,7 @@ void CMenus::RenderServerbrowserServerDetail(CUIRect View)
 				TextRender()->TextEx(&Cursor, pClan, -1);
 
 			// flag
-			Graphics()->TextureSet(m_pClient->m_pCountryFlags->Get(pSelectedServer->m_aClients[i].m_Country)->m_Texture);
+			Graphics()->TextureSet(m_pClient->m_pCountryFlags->GetByCountryCode(pSelectedServer->m_aClients[i].m_Country)->m_Texture);
 			Graphics()->QuadsBegin();
 			Graphics()->SetColor(1.0f, 1.0f, 1.0f, 0.5f);
 			IGraphics::CQuadItem QuadItem(Flag.x, Flag.y, Flag.w, Flag.h);
@@ -695,45 +776,102 @@ void CMenus::RenderServerbrowserServerDetail(CUIRect View)
 	}
 }
 
+void CMenus::FriendlistOnUpdate()
+{
+	m_lFriends.clear();
+	for(int i = 0; i < m_pClient->Friends()->NumFriends(); ++i)
+	{
+		CFriendItem Item;
+		Item.m_pFriendInfo = m_pClient->Friends()->GetFriend(i);
+		Item.m_NumFound = 0;
+		m_lFriends.add_unsorted(Item);
+	}
+	m_lFriends.sort_range();
+}
+
 void CMenus::RenderServerbrowserFriends(CUIRect View)
 {
+	static int s_Inited = 0;
+	if(!s_Inited)
+	{
+		FriendlistOnUpdate();
+		s_Inited = 1;
+	}
+
 	CUIRect ServerFriends = View, FilterHeader;
-	const float FontSize = 12.0f;
+	const float FontSize = 10.0f;
 
 	// header
 	ServerFriends.HSplitTop(ms_ListheaderHeight, &FilterHeader, &ServerFriends);
 	RenderTools()->DrawUIRect(&FilterHeader, vec4(1,1,1,0.25f), CUI::CORNER_T, 4.0f);
 	RenderTools()->DrawUIRect(&ServerFriends, vec4(0,0,0,0.15f), 0, 4.0f);
-	UI()->DoLabelScaled(&FilterHeader, Localize("Friends"), FontSize+2.0f, 0);
+	UI()->DoLabelScaled(&FilterHeader, Localize("Friends"), FontSize+4.0f, 0);
 	CUIRect Button, List;
 
-	ServerFriends.VSplitLeft(5.0f, 0, &ServerFriends);
 	ServerFriends.Margin(3.0f, &ServerFriends);
-	ServerFriends.VMargin(5.0f, &ServerFriends);
+	ServerFriends.VMargin(3.0f, &ServerFriends);
 	ServerFriends.HSplitBottom(100.0f, &List, &ServerFriends);
 
 	// friends list(remove friend)
-	static int s_FriendList = 0;
 	static float s_ScrollValue = 0;
-	UiDoListboxStart(&s_FriendList, &List, 40.0f, "", "", m_pClient->Friends()->NumFriends(), 1, m_FriendlistSelectedIndex, s_ScrollValue);
+	UiDoListboxStart(&m_lFriends, &List, 30.0f, "", "", m_lFriends.size(), 1, m_FriendlistSelectedIndex, s_ScrollValue);
 
-	for(int i = 0; i < m_pClient->Friends()->NumFriends(); ++i)
+	m_lFriends.sort_range();
+	for(int i = 0; i < m_lFriends.size(); ++i)
 	{
-		const CFriendInfo *pFriend = m_pClient->Friends()->GetFriend(i);
-		CListboxItem Item = UiDoListboxNextItem(pFriend);
+		CListboxItem Item = UiDoListboxNextItem(&m_lFriends[i]);
 
 		if(Item.m_Visible)
 		{
-			Item.m_Rect.Margin(2.5f, &Item.m_Rect);
-			RenderTools()->DrawUIRect(&Item.m_Rect, vec4(1.0f, 1.0f, 1.0f, 0.1f), CUI::CORNER_ALL, 4.0f);
-			Item.m_Rect.Margin(2.5f, &Item.m_Rect);
-			Item.m_Rect.HSplitTop(14.0f, &Item.m_Rect, &Button);
-			UI()->DoLabelScaled(&Item.m_Rect, pFriend->m_aName, FontSize, -1);
-			UI()->DoLabelScaled(&Button, pFriend->m_aClan, FontSize, -1);
+			Item.m_Rect.Margin(1.5f, &Item.m_Rect);
+			CUIRect OnState;
+			Item.m_Rect.VSplitRight(30.0f, &Item.m_Rect, &OnState);
+			RenderTools()->DrawUIRect(&Item.m_Rect, vec4(1.0f, 1.0f, 1.0f, 0.1f), CUI::CORNER_L, 4.0f);
+
+			Item.m_Rect.VMargin(2.5f, &Item.m_Rect);
+			Item.m_Rect.HSplitTop(12.0f, &Item.m_Rect, &Button);
+			UI()->DoLabelScaled(&Item.m_Rect, m_lFriends[i].m_pFriendInfo->m_aName, FontSize, -1);
+			UI()->DoLabelScaled(&Button, m_lFriends[i].m_pFriendInfo->m_aClan, FontSize, -1);
+
+			RenderTools()->DrawUIRect(&OnState, m_lFriends[i].m_NumFound ? vec4(0.0f, 1.0f, 0.0f, 0.25f) : vec4(1.0f, 0.0f, 0.0f, 0.25f), CUI::CORNER_R, 4.0f);
+			OnState.HMargin((OnState.h-FontSize)/3, &OnState);
+			OnState.VMargin(5.0f, &OnState);
+			char aBuf[64];
+			str_format(aBuf, sizeof(aBuf), "%i", m_lFriends[i].m_NumFound);
+			UI()->DoLabelScaled(&OnState, aBuf, FontSize+2, 1);
 		}
 	}
 
-	m_FriendlistSelectedIndex = UiDoListboxEnd(&s_ScrollValue, 0);
+	bool Activated = false;
+	m_FriendlistSelectedIndex = UiDoListboxEnd(&s_ScrollValue, &Activated);
+
+	// activate found server with friend
+	if(Activated && !m_EnterPressed && m_lFriends[m_FriendlistSelectedIndex].m_NumFound)
+	{
+		bool Found = false;
+		int NumServers = ServerBrowser()->NumSortedServers();
+		for (int i = 0; i < NumServers && !Found; i++)
+		{
+			int ItemIndex = m_SelectedIndex != -1 ? (m_SelectedIndex+i+1)%NumServers : i;
+			const CServerInfo *pItem = ServerBrowser()->SortedGet(ItemIndex);
+			if(pItem->m_FriendState != IFriends::FRIEND_NO)
+			{
+				for(int j = 0; j < pItem->m_NumClients && !Found; ++j)
+				{
+					if(pItem->m_aClients[j].m_FriendState != IFriends::FRIEND_NO &&
+						str_quickhash(pItem->m_aClients[j].m_aClan) == m_lFriends[m_FriendlistSelectedIndex].m_pFriendInfo->m_ClanHash &&
+						(!m_lFriends[m_FriendlistSelectedIndex].m_pFriendInfo->m_aName[0] ||
+						str_quickhash(pItem->m_aClients[j].m_aName) == m_lFriends[m_FriendlistSelectedIndex].m_pFriendInfo->m_NameHash))
+					{
+						str_copy(g_Config.m_UiServerAddress, pItem->m_aAddress, sizeof(g_Config.m_UiServerAddress));
+						m_ScrollOffset = ItemIndex;
+						m_SelectedIndex = ItemIndex;
+						Found = true;
+					}
+				}
+			}
+		}
+	}
 
 	ServerFriends.HSplitTop(2.5f, 0, &ServerFriends);
 	ServerFriends.HSplitTop(20.0f, &Button, &ServerFriends);
@@ -768,10 +906,11 @@ void CMenus::RenderServerbrowserFriends(CUIRect View)
 
 		ServerFriends.HSplitTop(3.0f, 0, &ServerFriends);
 		ServerFriends.HSplitTop(20.0f, &Button, &ServerFriends);
-		static int s_RemoveButton = 0;
-		if(DoButton_Menu(&s_RemoveButton, Localize("Add Friend"), 0, &Button))
+		static int s_AddButton = 0;
+		if(DoButton_Menu(&s_AddButton, Localize("Add Friend"), 0, &Button))
 		{
 			m_pClient->Friends()->AddFriend(s_aName, s_aClan);
+			FriendlistOnUpdate();
 			Client()->ServerBrowserUpdate();
 		}
 	}
@@ -910,6 +1049,16 @@ void CMenus::RenderServerbrowser(CUIRect MainView)
 	}
 }
 
+void CMenus::ConchainFriendlistUpdate(IConsole::IResult *pResult, void *pUserData, IConsole::FCommandCallback pfnCallback, void *pCallbackUserData)
+{
+	pfnCallback(pResult, pCallbackUserData);
+	if(pResult->NumArguments() == 2 && ((CMenus *)pUserData)->Client()->State() == IClient::STATE_OFFLINE)
+	{
+		((CMenus *)pUserData)->FriendlistOnUpdate();
+		((CMenus *)pUserData)->Client()->ServerBrowserUpdate();
+	}
+}
+
 void CMenus::ConchainServerbrowserUpdate(IConsole::IResult *pResult, void *pUserData, IConsole::FCommandCallback pfnCallback, void *pCallbackUserData)
 {
 	pfnCallback(pResult, pCallbackUserData);
diff --git a/src/game/client/components/menus_demo.cpp b/src/game/client/components/menus_demo.cpp
index 7fcfab99..f3a75f0c 100644
--- a/src/game/client/components/menus_demo.cpp
+++ b/src/game/client/components/menus_demo.cpp
@@ -200,8 +200,10 @@ void CMenus::RenderDemoPlayer(CUIRect MainView)
 			Client()->Disconnect();
 
 		// demo name
+		char aDemoName[64] = {0};
+		DemoPlayer()->GetDemoName(aDemoName, sizeof(aDemoName));
 		char aBuf[128];
-		str_format(aBuf, sizeof(aBuf), "Demofile: %s", DemoPlayer()->GetDemoName());
+		str_format(aBuf, sizeof(aBuf), Localize("Demofile: %s"), aDemoName);
 		CTextCursor Cursor;
 		TextRender()->SetCursor(&Cursor, NameBar.x, NameBar.y, Button.h*0.5f, TEXTFLAG_RENDER|TEXTFLAG_STOP_AT_END);
 		Cursor.m_LineWidth = MainView.w;
@@ -289,9 +291,9 @@ void CMenus::UiDoListboxStart(const void *pID, const CUIRect *pRect, float RowHe
 		Num = 0;
 	if(Num > 0)
 	{
-		if(Input()->KeyPresses(KEY_MOUSE_WHEEL_UP))
+		if(Input()->KeyPresses(KEY_MOUSE_WHEEL_UP) && UI()->MouseInside(&View))
 			gs_ListBoxScrollValue -= 3.0f/Num;
-		if(Input()->KeyPresses(KEY_MOUSE_WHEEL_DOWN))
+		if(Input()->KeyPresses(KEY_MOUSE_WHEEL_DOWN) && UI()->MouseInside(&View))
 			gs_ListBoxScrollValue += 3.0f/Num;
 
 		if(gs_ListBoxScrollValue < 0.0f) gs_ListBoxScrollValue = 0.0f;
@@ -371,7 +373,7 @@ CMenus::CListboxItem CMenus::UiDoListboxNextItem(const void *pId, bool Selected)
 		{
 			gs_ListBoxDoneEvents = 1;
 
-			if(m_EnterPressed || (Input()->MouseDoubleClick() && UI()->ActiveItem() == pId))
+			if(m_EnterPressed || (UI()->ActiveItem() == pId && Input()->MouseDoubleClick()))
 			{
 				gs_ListBoxItemActivated = true;
 				UI()->SetActiveItem(0);
@@ -553,8 +555,8 @@ void CMenus::RenderDemoList(CUIRect MainView)
 		Labels.HSplitTop(20.0f, &Left, &Labels);
 		Left.VSplitLeft(150.0f, &Left, &Right);
 		UI()->DoLabelScaled(&Left, Localize("Length:"), 14.0f, -1);
-		int Length = (m_lDemos[m_DemolistSelectedIndex].m_Info.m_aLength[0]<<24) | (m_lDemos[m_DemolistSelectedIndex].m_Info.m_aLength[1]<<16) |
-					(m_lDemos[m_DemolistSelectedIndex].m_Info.m_aLength[2]<<8) | (m_lDemos[m_DemolistSelectedIndex].m_Info.m_aLength[3]);
+		int Length = ((m_lDemos[m_DemolistSelectedIndex].m_Info.m_aLength[0]<<24)&0xFF000000) | ((m_lDemos[m_DemolistSelectedIndex].m_Info.m_aLength[1]<<16)&0xFF0000) |
+					((m_lDemos[m_DemolistSelectedIndex].m_Info.m_aLength[2]<<8)&0xFF00) | (m_lDemos[m_DemolistSelectedIndex].m_Info.m_aLength[3]&0xFF);
 		char aBuf[64];
 		str_format(aBuf, sizeof(aBuf), "%d:%02d", Length/60, Length%60);
 		UI()->DoLabelScaled(&Right, aBuf, 14.0f, -1);
@@ -576,9 +578,9 @@ void CMenus::RenderDemoList(CUIRect MainView)
 		Left.VSplitLeft(20.0f, 0, &Left);
 		Left.VSplitLeft(130.0f, &Left, &Right);
 		UI()->DoLabelScaled(&Left, Localize("Size:"), 14.0f, -1);
-		Length = (m_lDemos[m_DemolistSelectedIndex].m_Info.m_aMapSize[0]<<24) | (m_lDemos[m_DemolistSelectedIndex].m_Info.m_aMapSize[1]<<16) |
+		unsigned Size = (m_lDemos[m_DemolistSelectedIndex].m_Info.m_aMapSize[0]<<24) | (m_lDemos[m_DemolistSelectedIndex].m_Info.m_aMapSize[1]<<16) |
 					(m_lDemos[m_DemolistSelectedIndex].m_Info.m_aMapSize[2]<<8) | (m_lDemos[m_DemolistSelectedIndex].m_Info.m_aMapSize[3]);
-		str_format(aBuf, sizeof(aBuf), Localize("%d Bytes"), Length);
+		str_format(aBuf, sizeof(aBuf), Localize("%d Bytes"), Size);
 		UI()->DoLabelScaled(&Right, aBuf, 14.0f, -1);
 		Labels.HSplitTop(5.0f, 0, &Labels);
 		Labels.HSplitTop(20.0f, &Left, &Labels);
diff --git a/src/game/client/components/menus_ingame.cpp b/src/game/client/components/menus_ingame.cpp
index 33aaa14f..1afef922 100644
--- a/src/game/client/components/menus_ingame.cpp
+++ b/src/game/client/components/menus_ingame.cpp
@@ -188,10 +188,12 @@ void CMenus::RenderPlayers(CUIRect MainView)
 		Button.VSplitLeft((Width-Button.h)/4.0f, 0, &Button);
 		Button.VSplitLeft(Button.h, &Button, 0);
 		if(DoButton_Toggle(&s_aPlayerIDs[i][1], m_pClient->m_aClients[i].m_Friend, &Button))
+		{
 			if(m_pClient->m_aClients[i].m_Friend)
 				m_pClient->Friends()->RemoveFriend(m_pClient->m_aClients[i].m_aName, m_pClient->m_aClients[i].m_aClan);
 			else
 				m_pClient->Friends()->AddFriend(m_pClient->m_aClients[i].m_aName, m_pClient->m_aClients[i].m_aClan);
+		}
 	}
 
 	/*
diff --git a/src/game/client/components/menus_settings.cpp b/src/game/client/components/menus_settings.cpp
index 28f3559d..51fdbd29 100644
--- a/src/game/client/components/menus_settings.cpp
+++ b/src/game/client/components/menus_settings.cpp
@@ -208,17 +208,16 @@ void CMenus::RenderSettingsPlayer(CUIRect MainView)
 
 	for(int i = 0; i < m_pClient->m_pCountryFlags->Num(); ++i)
 	{
-		const CCountryFlags::CCountryFlag *pEntry = m_pClient->m_pCountryFlags->Get(i);
-		if(pEntry == 0)
-			continue;
-
+		const CCountryFlags::CCountryFlag *pEntry = m_pClient->m_pCountryFlags->GetByIndex(i);
 		if(pEntry->m_CountryCode == g_Config.m_PlayerCountry)
 			OldSelected = i;
 
 		CListboxItem Item = UiDoListboxNextItem(&pEntry->m_CountryCode, OldSelected == i);
 		if(Item.m_Visible)
 		{
-			Item.m_Rect.Margin(10.0f, &Item.m_Rect);
+			CUIRect Label;
+			Item.m_Rect.Margin(5.0f, &Item.m_Rect);
+			Item.m_Rect.HSplitBottom(10.0f, &Item.m_Rect, &Label);
 			float OldWidth = Item.m_Rect.w;
 			Item.m_Rect.w = Item.m_Rect.h*2;
 			Item.m_Rect.x += (OldWidth-Item.m_Rect.w)/ 2.0f;
@@ -228,13 +227,14 @@ void CMenus::RenderSettingsPlayer(CUIRect MainView)
 			IGraphics::CQuadItem QuadItem(Item.m_Rect.x, Item.m_Rect.y, Item.m_Rect.w, Item.m_Rect.h);
 			Graphics()->QuadsDrawTL(&QuadItem, 1);
 			Graphics()->QuadsEnd();
+			UI()->DoLabel(&Label, pEntry->m_aCountryCodeString, 10.0f, 0);
 		}
 	}
 
 	const int NewSelected = UiDoListboxEnd(&s_ScrollValue, 0);
 	if(OldSelected != NewSelected)
 	{
-		g_Config.m_PlayerCountry = m_pClient->m_pCountryFlags->Get(NewSelected)->m_CountryCode;
+		g_Config.m_PlayerCountry = m_pClient->m_pCountryFlags->GetByIndex(NewSelected)->m_CountryCode;
 		m_NeedSendinfo = true;
 	}
 }
@@ -627,7 +627,8 @@ void CMenus::RenderSettingsGraphics(CUIRect MainView)
 	// display mode list
 	static float s_ScrollValue = 0;
 	int OldSelected = -1;
-	str_format(aBuf, sizeof(aBuf), "%s: %dx%d %d bit", Localize("Current"), s_GfxScreenWidth, s_GfxScreenHeight, s_GfxColorDepth);
+	int G = gcd(s_GfxScreenWidth, s_GfxScreenHeight);
+	str_format(aBuf, sizeof(aBuf), "%s: %dx%d %d bit (%d:%d)", Localize("Current"), s_GfxScreenWidth, s_GfxScreenHeight, s_GfxColorDepth, s_GfxScreenWidth/G, s_GfxScreenHeight/G);
 	UiDoListboxStart(&s_NumNodes , &ModeList, 24.0f, Localize("Display Modes"), aBuf, s_NumNodes, 1, OldSelected, s_ScrollValue);
 
 	for(int i = 0; i < s_NumNodes; ++i)
@@ -643,7 +644,8 @@ void CMenus::RenderSettingsGraphics(CUIRect MainView)
 		CListboxItem Item = UiDoListboxNextItem(&s_aModes[i], OldSelected == i);
 		if(Item.m_Visible)
 		{
-			str_format(aBuf, sizeof(aBuf), " %dx%d %d bit", s_aModes[i].m_Width, s_aModes[i].m_Height, Depth);
+			int G = gcd(s_aModes[i].m_Width, s_aModes[i].m_Height);
+			str_format(aBuf, sizeof(aBuf), " %dx%d %d bit (%d:%d)", s_aModes[i].m_Width, s_aModes[i].m_Height, Depth, s_aModes[i].m_Width/G, s_aModes[i].m_Height/G);
 			UI()->DoLabelScaled(&Item.m_Rect, aBuf, 16.0f, -1);
 		}
 	}
@@ -757,7 +759,10 @@ void CMenus::RenderSettingsSound(CUIRect MainView)
 	{
 		g_Config.m_SndEnable ^= 1;
 		if(g_Config.m_SndEnable)
-			m_pClient->m_pSounds->Play(CSounds::CHN_MUSIC, SOUND_MENU, 1.0f, vec2(0, 0));
+		{
+			if(g_Config.m_SndMusic)
+				m_pClient->m_pSounds->Play(CSounds::CHN_MUSIC, SOUND_MENU, 1.0f, vec2(0, 0));
+		}
 		else
 			m_pClient->m_pSounds->Stop(SOUND_MENU);
 		m_NeedRestartSound = g_Config.m_SndEnable && (!s_SndEnable || s_SndRate != g_Config.m_SndRate);
@@ -810,10 +815,11 @@ class CLanguage
 {
 public:
 	CLanguage() {}
-	CLanguage(const char *n, const char *f) : m_Name(n), m_FileName(f) {}
+	CLanguage(const char *n, const char *f, int Code) : m_Name(n), m_FileName(f), m_CountryCode(Code) {}
 
 	string m_Name;
 	string m_FileName;
+	int m_CountryCode;
 
 	bool operator<(const CLanguage &Other) { return m_Name < Other.m_Name; }
 };
@@ -828,6 +834,7 @@ void LoadLanguageIndexfile(IStorage *pStorage, IConsole *pConsole, sorted_array<
 	}
 
 	char aOrigin[128];
+	char aReplacement[128];
 	CLineReader LineReader;
 	LineReader.Init(File);
 	char *pLine;
@@ -837,14 +844,32 @@ void LoadLanguageIndexfile(IStorage *pStorage, IConsole *pConsole, sorted_array<
 			continue;
 
 		str_copy(aOrigin, pLine, sizeof(aOrigin));
-		char *pReplacement = LineReader.Get();
-		if(!pReplacement)
+
+		pLine = LineReader.Get();
+		if(!pLine)
 		{
 			pConsole->Print(IConsole::OUTPUT_LEVEL_ADDINFO, "localization", "unexpected end of index file");
 			break;
 		}
 
-		if(pReplacement[0] != '=' || pReplacement[1] != '=' || pReplacement[2] != ' ')
+		if(pLine[0] != '=' || pLine[1] != '=' || pLine[2] != ' ')
+		{
+			char aBuf[128];
+			str_format(aBuf, sizeof(aBuf), "malform replacement for index '%s'", aOrigin);
+			pConsole->Print(IConsole::OUTPUT_LEVEL_ADDINFO, "localization", aBuf);
+			(void)LineReader.Get();
+			continue;
+		}
+		str_copy(aReplacement, pLine+3, sizeof(aReplacement));
+
+		pLine = LineReader.Get();
+		if(!pLine)
+		{
+			pConsole->Print(IConsole::OUTPUT_LEVEL_ADDINFO, "localization", "unexpected end of index file");
+			break;
+		}
+
+		if(pLine[0] != '=' || pLine[1] != '=' || pLine[2] != ' ')
 		{
 			char aBuf[128];
 			str_format(aBuf, sizeof(aBuf), "malform replacement for index '%s'", aOrigin);
@@ -854,7 +879,7 @@ void LoadLanguageIndexfile(IStorage *pStorage, IConsole *pConsole, sorted_array<
 
 		char aFileName[128];
 		str_format(aFileName, sizeof(aFileName), "languages/%s.txt", aOrigin);
-		pLanguages->add(CLanguage(pReplacement+3, aFileName));
+		pLanguages->add(CLanguage(aReplacement, aFileName, str_toint(pLine+3)));
 	}
 	io_close(File);
 }
@@ -868,7 +893,7 @@ void CMenus::RenderLanguageSelection(CUIRect MainView)
 
 	if(s_Languages.size() == 0)
 	{
-		s_Languages.add(CLanguage("English", ""));
+		s_Languages.add(CLanguage("English", "", 826));
 		LoadLanguageIndexfile(Storage(), Console(), &s_Languages);
 		for(int i = 0; i < s_Languages.size(); i++)
 			if(str_comp(s_Languages[i].m_FileName, g_Config.m_ClLanguagefile) == 0)
@@ -887,7 +912,19 @@ void CMenus::RenderLanguageSelection(CUIRect MainView)
 		CListboxItem Item = UiDoListboxNextItem(&r.front());
 
 		if(Item.m_Visible)
-			UI()->DoLabelScaled(&Item.m_Rect, r.front().m_Name, 16.0f, -1);
+		{
+			CUIRect Rect;
+			Item.m_Rect.VSplitLeft(Item.m_Rect.h*2.0f, &Rect, &Item.m_Rect);
+			Rect.VMargin(6.0f, &Rect);
+			Rect.HMargin(3.0f, &Rect);
+			Graphics()->TextureSet(m_pClient->m_pCountryFlags->GetByCountryCode(r.front().m_CountryCode)->m_Texture);
+			Graphics()->QuadsBegin();
+			IGraphics::CQuadItem QuadItem(Rect.x, Rect.y, Rect.w, Rect.h);
+			Graphics()->QuadsDrawTL(&QuadItem, 1);
+			Graphics()->QuadsEnd();
+			Item.m_Rect.HSplitTop(2.0f, 0, &Item.m_Rect);
+ 			UI()->DoLabelScaled(&Item.m_Rect, r.front().m_Name, 16.0f, -1);
+		}
 	}
 
 	s_SelectedLanguage = UiDoListboxEnd(&s_ScrollValue, 0);
diff --git a/src/game/client/components/scoreboard.cpp b/src/game/client/components/scoreboard.cpp
index 2cec5e62..ae11c7ea 100644
--- a/src/game/client/components/scoreboard.cpp
+++ b/src/game/client/components/scoreboard.cpp
@@ -273,7 +273,7 @@ void CScoreboard::RenderScoreboard(float x, float y, float w, int Team, const ch
 		TextRender()->TextEx(&Cursor, m_pClient->m_aClients[pInfo->m_ClientID].m_aClan, -1);
 
 		// country flag
-		Graphics()->TextureSet(m_pClient->m_pCountryFlags->Get(m_pClient->m_aClients[pInfo->m_ClientID].m_Country)->m_Texture);
+		Graphics()->TextureSet(m_pClient->m_pCountryFlags->GetByCountryCode(m_pClient->m_aClients[pInfo->m_ClientID].m_Country)->m_Texture);
 		Graphics()->QuadsBegin();
 		Graphics()->SetColor(1.0f, 1.0f, 1.0f, 0.5f);
 		IGraphics::CQuadItem QuadItem(CountryOffset, y+(Spacing+TeeSizeMod*5.0f)/2.0f, CountryLength, LineHeight-Spacing-TeeSizeMod*5.0f);
diff --git a/src/game/client/components/sounds.cpp b/src/game/client/components/sounds.cpp
index ffafa128..c4ade00e 100644
--- a/src/game/client/components/sounds.cpp
+++ b/src/game/client/components/sounds.cpp
@@ -65,8 +65,17 @@ void CSounds::OnInit()
 
 void CSounds::OnReset()
 {
-	Sound()->StopAll();
-	ClearQueue();
+	if(Client()->State() >= IClient::STATE_ONLINE)
+	{
+		Sound()->StopAll();
+		ClearQueue();
+	}
+}
+
+void CSounds::OnStateChange(int NewState, int OldState)
+{
+	if(NewState == IClient::STATE_ONLINE || NewState == IClient::STATE_DEMOPLAYBACK)
+		OnReset();
 }
 
 void CSounds::OnRender()
@@ -128,10 +137,10 @@ void CSounds::PlayAndRecord(int Chn, int SetId, float Vol, vec2 Pos)
 
 void CSounds::Play(int Chn, int SetId, float Vol, vec2 Pos)
 {
-	if(!g_Config.m_SndEnable || (Chn == CHN_MUSIC && !g_Config.m_SndMusic) || m_WaitForSoundJob || SetId < 0 || SetId >= g_pData->m_NumSounds)
+	if(!g_Config.m_SndEnable || !Sound()->IsSoundEnabled() || (Chn == CHN_MUSIC && !g_Config.m_SndMusic) || m_WaitForSoundJob || SetId < 0 || SetId >= g_pData->m_NumSounds)
 		return;
 
-	SOUNDSET *pSet = &g_pData->m_aSounds[SetId];
+	CDataSoundset *pSet = &g_pData->m_aSounds[SetId];
 
 	if(!pSet->m_NumSounds)
 		return;
@@ -162,7 +171,7 @@ void CSounds::Stop(int SetId)
 	if(m_WaitForSoundJob || SetId < 0 || SetId >= g_pData->m_NumSounds)
 		return;
 	
-	SOUNDSET *pSet = &g_pData->m_aSounds[SetId];
+	CDataSoundset *pSet = &g_pData->m_aSounds[SetId];
 	
 	for(int i = 0; i < pSet->m_NumSounds; i++)
 		Sound()->Stop(pSet->m_aSounds[i].m_Id);
diff --git a/src/game/client/components/sounds.h b/src/game/client/components/sounds.h
index 2670f793..ab9cc8e6 100644
--- a/src/game/client/components/sounds.h
+++ b/src/game/client/components/sounds.h
@@ -32,6 +32,7 @@ public:
 
 	virtual void OnInit();
 	virtual void OnReset();
+	virtual void OnStateChange(int NewState, int OldState);
 	virtual void OnRender();
 
 	void ClearQueue();
diff --git a/src/game/client/components/spectator.cpp b/src/game/client/components/spectator.cpp
index 41c7b48f..c09b2ee2 100644
--- a/src/game/client/components/spectator.cpp
+++ b/src/game/client/components/spectator.cpp
@@ -64,8 +64,8 @@ void CSpectator::ConSpectateNext(IConsole::IResult *pResult, void *pUserData)
 				if(!pSelf->m_pClient->m_Snap.m_paPlayerInfos[i] || pSelf->m_pClient->m_Snap.m_paPlayerInfos[i]->m_Team == TEAM_SPECTATORS)
 					continue;
 
-			NewSpectatorID = i;
-			GotNewSpectatorID = true;
+				NewSpectatorID = i;
+				GotNewSpectatorID = true;
 				break;
 			}
 		}
@@ -139,6 +139,7 @@ bool CSpectator::OnMouseMove(float x, float y)
 	if(!m_Active)
 		return false;
 
+	UI()->ConvertMouseMove(&x, &y);
 	m_SelectorMouse += vec2(x,y);
 	return true;
 }
@@ -161,6 +162,13 @@ void CSpectator::OnRender()
 		return;
 	}
 
+	if(!m_pClient->m_Snap.m_SpecInfo.m_Active)
+	{
+		m_Active = false;
+		m_WasActive = false;
+		return;
+	}
+
 	m_WasActive = true;
 	m_SelectedSpectatorID = NO_SELECTION;
 
@@ -236,6 +244,23 @@ void CSpectator::OnRender()
 		TextRender()->TextColor(1.0f, 1.0f, 1.0f, Selected?1.0f:0.5f);
 		TextRender()->Text(0, Width/2.0f+x+50.0f, Height/2.0f+y+5.0f, FontSize, m_pClient->m_aClients[i].m_aName, 220.0f);
 
+		// flag
+		if(m_pClient->m_Snap.m_pGameInfoObj->m_GameFlags&GAMEFLAG_FLAGS &&
+			m_pClient->m_Snap.m_pGameDataObj && (m_pClient->m_Snap.m_pGameDataObj->m_FlagCarrierRed == m_pClient->m_Snap.m_paPlayerInfos[i]->m_ClientID ||
+			m_pClient->m_Snap.m_pGameDataObj->m_FlagCarrierBlue == m_pClient->m_Snap.m_paPlayerInfos[i]->m_ClientID))
+		{
+			Graphics()->BlendNormal();
+			Graphics()->TextureSet(g_pData->m_aImages[IMAGE_GAME].m_Id);
+			Graphics()->QuadsBegin();
+
+			RenderTools()->SelectSprite(m_pClient->m_Snap.m_paPlayerInfos[i]->m_Team==TEAM_RED ? SPRITE_FLAG_BLUE : SPRITE_FLAG_RED, SPRITE_FLAG_FLIP_X);
+
+			float Size = LineHeight;
+			IGraphics::CQuadItem QuadItem(Width/2.0f+x-LineHeight/5.0f, Height/2.0f+y-LineHeight/3.0f, Size/2.0f, Size);
+			Graphics()->QuadsDrawTL(&QuadItem, 1);
+			Graphics()->QuadsEnd();
+		}
+
 		CTeeRenderInfo TeeInfo = m_pClient->m_aClients[i].m_RenderInfo;
 		RenderTools()->RenderTee(CAnimState::GetIdle(), &TeeInfo, EMOTE_NORMAL, vec2(1.0f, 0.0f), vec2(Width/2.0f+x+20.0f, Height/2.0f+y+20.0f));
 
diff --git a/src/game/client/gameclient.cpp b/src/game/client/gameclient.cpp
index 511cf894..7b6b1192 100644
--- a/src/game/client/gameclient.cpp
+++ b/src/game/client/gameclient.cpp
@@ -90,17 +90,6 @@ void CGameClient::CStack::Add(class CComponent *pComponent) { m_paComponents[m_N
 const char *CGameClient::Version() { return GAME_VERSION; }
 const char *CGameClient::NetVersion() { return GAME_NETVERSION; }
 const char *CGameClient::GetItemName(int Type) { return m_NetObjHandler.GetObjName(Type); }
-int CGameClient::GetCountryIndex(int Code)
-{
-	int Index = g_GameClient.m_pCountryFlags->Find(Code);
-	if(Index < 0)
-	{
-		Index = g_GameClient.m_pCountryFlags->Find(-1);
-		if(Index < 0)
-			Index = 0;
-	}
-	return Index;
-}
 
 void CGameClient::OnConsoleInit()
 {
@@ -196,7 +185,7 @@ void CGameClient::OnConsoleInit()
 	Console()->Register("restart", "?i", CFGFLAG_SERVER, 0, 0, "Restart in x seconds");
 	Console()->Register("broadcast", "r", CFGFLAG_SERVER, 0, 0, "Broadcast message");
 	Console()->Register("say", "r", CFGFLAG_SERVER, 0, 0, "Say in chat");
-	Console()->Register("set_team", "ii", CFGFLAG_SERVER, 0, 0, "Set team of player to team");
+	Console()->Register("set_team", "ii?i", CFGFLAG_SERVER, 0, 0, "Set team of player to team");
 	Console()->Register("set_team_all", "i", CFGFLAG_SERVER, 0, 0, "Set team of all players to team");
 	Console()->Register("add_vote", "sr", CFGFLAG_SERVER, 0, 0, "Add a voting option");
 	Console()->Register("remove_vote", "s", CFGFLAG_SERVER, 0, 0, "remove a voting option");
@@ -352,6 +341,9 @@ void CGameClient::OnReset()
 		m_All.m_paComponents[i]->OnReset();
 
 	m_DemoSpecID = SPEC_FREEVIEW;
+	m_FlagDropTick[TEAM_RED] = 0;
+	m_FlagDropTick[TEAM_BLUE] = 0;
+	m_Tuning = CTuningParams();
 }
 
 
@@ -602,32 +594,32 @@ void CGameClient::ProcessEvents()
 
 		if(Item.m_Type == NETEVENTTYPE_DAMAGEIND)
 		{
-			NETEVENT_DAMAGEIND *ev = (NETEVENT_DAMAGEIND *)pData;
+			CNetEvent_DamageInd *ev = (CNetEvent_DamageInd *)pData;
 			g_GameClient.m_pEffects->DamageIndicator(vec2(ev->m_X, ev->m_Y), GetDirection(ev->m_Angle));
 		}
 		else if(Item.m_Type == NETEVENTTYPE_EXPLOSION)
 		{
-			NETEVENT_EXPLOSION *ev = (NETEVENT_EXPLOSION *)pData;
+			CNetEvent_Explosion *ev = (CNetEvent_Explosion *)pData;
 			g_GameClient.m_pEffects->Explosion(vec2(ev->m_X, ev->m_Y));
 		}
 		else if(Item.m_Type == NETEVENTTYPE_HAMMERHIT)
 		{
-			NETEVENT_HAMMERHIT *ev = (NETEVENT_HAMMERHIT *)pData;
+			CNetEvent_HammerHit *ev = (CNetEvent_HammerHit *)pData;
 			g_GameClient.m_pEffects->HammerHit(vec2(ev->m_X, ev->m_Y));
 		}
 		else if(Item.m_Type == NETEVENTTYPE_SPAWN)
 		{
-			NETEVENT_SPAWN *ev = (NETEVENT_SPAWN *)pData;
+			CNetEvent_Spawn *ev = (CNetEvent_Spawn *)pData;
 			g_GameClient.m_pEffects->PlayerSpawn(vec2(ev->m_X, ev->m_Y));
 		}
 		else if(Item.m_Type == NETEVENTTYPE_DEATH)
 		{
-			NETEVENT_DEATH *ev = (NETEVENT_DEATH *)pData;
+			CNetEvent_Death *ev = (CNetEvent_Death *)pData;
 			g_GameClient.m_pEffects->PlayerDeath(vec2(ev->m_X, ev->m_Y), ev->m_ClientID);
 		}
 		else if(Item.m_Type == NETEVENTTYPE_SOUNDWORLD)
 		{
-			NETEVENT_SOUNDWORLD *ev = (NETEVENT_SOUNDWORLD *)pData;
+			CNetEvent_SoundWorld *ev = (CNetEvent_SoundWorld *)pData;
 			g_GameClient.m_pSounds->Play(CSounds::CHN_WORLD, ev->m_SoundID, 1.0f, vec2(ev->m_X, ev->m_Y));
 		}
 	}
@@ -696,7 +688,7 @@ void CGameClient::OnNewSnapshot()
 				int ClientID = Item.m_ID;
 				IntsToStr(&pInfo->m_Name0, 4, m_aClients[ClientID].m_aName);
 				IntsToStr(&pInfo->m_Clan0, 3, m_aClients[ClientID].m_aClan);
-				m_aClients[ClientID].m_Country = GetCountryIndex(pInfo->m_Country);
+				m_aClients[ClientID].m_Country = pInfo->m_Country;
 				IntsToStr(&pInfo->m_Skin0, 6, m_aClients[ClientID].m_aSkinName);
 
 				m_aClients[ClientID].m_UseCustomColor = pInfo->m_UseCustomColor;
@@ -794,6 +786,20 @@ void CGameClient::OnNewSnapshot()
 			{
 				m_Snap.m_pGameDataObj = (const CNetObj_GameData *)pData;
 				m_Snap.m_GameDataSnapID = Item.m_ID;
+				if(m_Snap.m_pGameDataObj->m_FlagCarrierRed == FLAG_TAKEN)
+				{
+					if(m_FlagDropTick[TEAM_RED] == 0)
+						m_FlagDropTick[TEAM_RED] = Client()->GameTick();
+				}
+				else if(m_FlagDropTick[TEAM_RED] != 0)
+						m_FlagDropTick[TEAM_RED] = 0;
+				if(m_Snap.m_pGameDataObj->m_FlagCarrierBlue == FLAG_TAKEN)
+				{
+					if(m_FlagDropTick[TEAM_BLUE] == 0)
+						m_FlagDropTick[TEAM_BLUE] = Client()->GameTick();
+				}
+				else if(m_FlagDropTick[TEAM_BLUE] != 0)
+						m_FlagDropTick[TEAM_BLUE] = 0;
 			}
 			else if(Item.m_Type == NETOBJTYPE_FLAG)
 				m_Snap.m_paFlags[Item.m_ID%2] = (const CNetObj_Flag *)pData;
diff --git a/src/game/client/gameclient.h b/src/game/client/gameclient.h
index a89f4e86..4783f8b4 100644
--- a/src/game/client/gameclient.h
+++ b/src/game/client/gameclient.h
@@ -89,6 +89,7 @@ public:
 	bool m_SuppressEvents;
 	bool m_NewTick;
 	bool m_NewPredictedTick;
+	int m_FlagDropTick[2];
 
 	// TODO: move this
 	CTuningParams m_Tuning;
@@ -211,7 +212,6 @@ public:
 	virtual void OnStartGame();
 
 	virtual const char *GetItemName(int Type);
-	virtual int GetCountryIndex(int Code);
 	virtual const char *Version();
 	virtual const char *NetVersion();
 
diff --git a/src/game/client/lineinput.cpp b/src/game/client/lineinput.cpp
index 29b891c2..2de85d66 100644
--- a/src/game/client/lineinput.cpp
+++ b/src/game/client/lineinput.cpp
@@ -42,7 +42,7 @@ bool CLineInput::Manipulate(IInput::CEvent e, char *pStr, int StrMaxSize, int *p
 
 		if (Len < StrMaxSize - CharSize && CursorPos < StrMaxSize - CharSize)
 		{
-			mem_move(pStr + CursorPos + CharSize, pStr + CursorPos, Len - CursorPos + CharSize);
+			mem_move(pStr + CursorPos + CharSize, pStr + CursorPos, Len-CursorPos+1); // +1 == null term
 			for(int i = 0; i < CharSize; i++)
 				pStr[CursorPos+i] = Tmp[i];
 			CursorPos += CharSize;
diff --git a/src/game/client/render.cpp b/src/game/client/render.cpp
index 5dbc3842..93909d0c 100644
--- a/src/game/client/render.cpp
+++ b/src/game/client/render.cpp
@@ -37,7 +37,7 @@ static void layershot_end()
 	config.cl_layershot++;
 }*/
 
-void CRenderTools::SelectSprite(SPRITE *pSpr, int Flags, int sx, int sy)
+void CRenderTools::SelectSprite(CDataSprite *pSpr, int Flags, int sx, int sy)
 {
 	int x = pSpr->m_X+sx;
 	int y = pSpr->m_Y+sy;
@@ -232,7 +232,7 @@ void CRenderTools::RenderTee(CAnimState *pAnim, CTeeRenderInfo *pInfo, int Emote
 			}
 
 			// draw feet
-			ANIM_KEYFRAME *pFoot = f ? pAnim->GetFrontFoot() : pAnim->GetBackFoot();
+			CAnimKeyframe *pFoot = f ? pAnim->GetFrontFoot() : pAnim->GetBackFoot();
 
 			float w = BaseSize;
 			float h = BaseSize/2;
diff --git a/src/game/client/render.h b/src/game/client/render.h
index 10705e56..d3d7fc40 100644
--- a/src/game/client/render.h
+++ b/src/game/client/render.h
@@ -39,6 +39,7 @@ enum
 	TILERENDERFLAG_EXTEND=4,
 };
 
+typedef void (*ENVELOPE_EVAL)(float TimeOffset, int Env, float *pChannels, void *pUser);
 
 class CRenderTools
 {
@@ -51,7 +52,7 @@ public:
 
 	//typedef struct SPRITE;
 
-	void SelectSprite(struct SPRITE *pSprite, int Flags=0, int sx=0, int sy=0);
+	void SelectSprite(struct CDataSprite *pSprite, int Flags=0, int sx=0, int sy=0);
 	void SelectSprite(int id, int Flags=0, int sx=0, int sy=0);
 
 	void DrawSprite(float x, float y, float size);
@@ -70,8 +71,8 @@ public:
 
 	// map render methods (gc_render_map.cpp)
 	static void RenderEvalEnvelope(CEnvPoint *pPoints, int NumPoints, int Channels, float Time, float *pResult);
-	void RenderQuads(CQuad *pQuads, int NumQuads, int Flags, void (*pfnEval)(float TimeOffset, int Env, float *pChannels, void *pUser), void *pUser);
-	void RenderTilemap(CTile *pTiles, int w, int h, float Scale, vec4 Color, int Flags);
+	void RenderQuads(CQuad *pQuads, int NumQuads, int Flags, ENVELOPE_EVAL pfnEval, void *pUser);
+	void RenderTilemap(CTile *pTiles, int w, int h, float Scale, vec4 Color, int RenderFlags, ENVELOPE_EVAL pfnEval, void *pUser, int ColorEnv, int ColorEnvOffset);
 
 	// helpers
 	void MapscreenToWorld(float CenterX, float CenterY, float ParallaxX, float ParallaxY,
diff --git a/src/game/client/render_map.cpp b/src/game/client/render_map.cpp
index 33cc1c7d..23fa42e0 100644
--- a/src/game/client/render_map.cpp
+++ b/src/game/client/render_map.cpp
@@ -78,7 +78,7 @@ static void Rotate(CPoint *pCenter, CPoint *pPoint, float Rotation)
 	pPoint->y = (int)(x * sinf(Rotation) + y * cosf(Rotation) + pCenter->y);
 }
 
-void CRenderTools::RenderQuads(CQuad *pQuads, int NumQuads, int RenderFlags, void (*pfnEval)(float TimeOffset, int Env, float *pChannels, void *pUser), void *pUser)
+void CRenderTools::RenderQuads(CQuad *pQuads, int NumQuads, int RenderFlags, ENVELOPE_EVAL pfnEval, void *pUser)
 {
 	Graphics()->QuadsBegin();
 	float Conv = 1/255.0f;
@@ -162,7 +162,8 @@ void CRenderTools::RenderQuads(CQuad *pQuads, int NumQuads, int RenderFlags, voi
 	Graphics()->QuadsEnd();
 }
 
-void CRenderTools::RenderTilemap(CTile *pTiles, int w, int h, float Scale, vec4 Color, int RenderFlags)
+void CRenderTools::RenderTilemap(CTile *pTiles, int w, int h, float Scale, vec4 Color, int RenderFlags,
+									ENVELOPE_EVAL pfnEval, void *pUser, int ColorEnv, int ColorEnvOffset)
 {
 	//Graphics()->TextureSet(img_get(tmap->image));
 	float ScreenX0, ScreenY0, ScreenX1, ScreenY1;
@@ -174,8 +175,19 @@ void CRenderTools::RenderTilemap(CTile *pTiles, int w, int h, float Scale, vec4
 	float FinalTileSize = Scale/(ScreenX1-ScreenX0) * Graphics()->ScreenWidth();
 	float FinalTilesetScale = FinalTileSize/TilePixelSize;
 
+	float r=1, g=1, b=1, a=1;
+	if(ColorEnv >= 0)
+	{
+		float aChannels[4];
+		pfnEval(ColorEnvOffset/1000.0f, ColorEnv, aChannels, pUser);
+		r = aChannels[0];
+		g = aChannels[1];
+		b = aChannels[2];
+		a = aChannels[3];
+	}
+
 	Graphics()->QuadsBegin();
-	Graphics()->SetColor(Color.r, Color.g, Color.b, Color.a);
+	Graphics()->SetColor(Color.r*r, Color.g*g, Color.b*b, Color.a*a);
 
 	int StartY = (int)(ScreenY0/Scale)-1;
 	int StartX = (int)(ScreenX0/Scale)-1;
diff --git a/src/game/client/ui.cpp b/src/game/client/ui.cpp
index 2161bc77..00a30c15 100644
--- a/src/game/client/ui.cpp
+++ b/src/game/client/ui.cpp
@@ -53,6 +53,13 @@ int CUI::MouseInside(const CUIRect *r)
 	return 0;
 }
 
+void CUI::ConvertMouseMove(float *x, float *y)
+{
+	float Fac = (float)(g_Config.m_UiMousesens)/g_Config.m_InpMousesens;
+	*x = *x*Fac;
+	*y = *y*Fac;
+}
+
 CUIRect *CUI::Screen()
 {
 	float Aspect = Graphics()->ScreenAspect();
diff --git a/src/game/client/ui.h b/src/game/client/ui.h
index 017abf7c..7cd78d6f 100644
--- a/src/game/client/ui.h
+++ b/src/game/client/ui.h
@@ -79,6 +79,7 @@ public:
 	const void *LastActiveItem() const { return m_pLastActiveItem; }
 
 	int MouseInside(const CUIRect *pRect);
+	void ConvertMouseMove(float *x, float *y);
 
 	CUIRect *Screen();
 	void ClipEnable(const CUIRect *pRect);
diff --git a/src/game/editor/auto_map.cpp b/src/game/editor/auto_map.cpp
new file mode 100644
index 00000000..528e459b
--- /dev/null
+++ b/src/game/editor/auto_map.cpp
@@ -0,0 +1,202 @@
+#include <stdio.h>	// sscanf
+
+#include <engine/console.h>
+#include <engine/storage.h>
+#include <engine/shared/linereader.h>
+
+#include "auto_map.h"
+#include "editor.h"
+
+CAutoMapper::CAutoMapper(CEditor *pEditor)
+{
+	m_pEditor = pEditor;
+	m_FileLoaded = false;
+}
+
+void CAutoMapper::Load(const char* pTileName)
+{
+	char aPath[256];
+	str_format(aPath, sizeof(aPath), "editor/%s.rules", pTileName);
+	IOHANDLE RulesFile = m_pEditor->Storage()->OpenFile(aPath, IOFLAG_READ, IStorage::TYPE_ALL);
+	if(!RulesFile)
+		return;
+	
+	CLineReader LineReader;
+	LineReader.Init(RulesFile);
+		
+	CConfiguration *pCurrentConf = 0;
+	CIndexRule *pCurrentIndex = 0;
+		
+	char aBuf[256];
+		
+	// read each line
+	while(char *pLine = LineReader.Get())
+	{
+		// skip blank/empty lines as well as comments
+		if(str_length(pLine) > 0 && pLine[0] != '#' && pLine[0] != '\n' && pLine[0] != '\r'
+			&& pLine[0] != '\t' && pLine[0] != '\v' && pLine[0] != ' ')
+		{
+			if(pLine[0]== '[')
+			{
+				// new configuration, get the name
+				pLine++;
+					
+				CConfiguration NewConf;
+				int ID = m_lConfigs.add(NewConf);
+				pCurrentConf = &m_lConfigs[ID];
+					
+				str_copy(pCurrentConf->m_aName, pLine, str_length(pLine));
+			}
+			else
+			{
+				if(!str_comp_num(pLine, "Index", 5))
+				{
+					// new index
+					int ID = 0;
+					char aFlip[128] = "";
+						
+					sscanf(pLine, "Index %d %127s", &ID, aFlip);
+						
+					CIndexRule NewIndexRule;
+					NewIndexRule.m_ID = ID;
+					NewIndexRule.m_Flag = 0;
+					NewIndexRule.m_RandomValue = 0;
+					NewIndexRule.m_BaseTile = false;
+						
+					if(str_length(aFlip) > 0)
+					{
+						if(!str_comp(aFlip, "XFLIP"))
+							NewIndexRule.m_Flag = TILEFLAG_VFLIP;
+						else if(!str_comp(aFlip, "YFLIP"))
+							NewIndexRule.m_Flag = TILEFLAG_HFLIP;
+					}
+						
+					// add the index rule object and make it current
+					int ArrayID = pCurrentConf->m_aIndexRules.add(NewIndexRule);
+					pCurrentIndex = &pCurrentConf->m_aIndexRules[ArrayID];
+				}
+				else if(!str_comp_num(pLine, "BaseTile", 8) && pCurrentIndex)
+				{
+					pCurrentIndex->m_BaseTile = true;
+				}
+				else if(!str_comp_num(pLine, "Pos", 3) && pCurrentIndex)
+				{
+					int x = 0, y = 0;
+					char aValue[128];
+					int Value = CPosRule::EMPTY;
+					bool IndexValue = false;
+						
+					sscanf(pLine, "Pos %d %d %127s", &x, &y, aValue);
+						
+					if(!str_comp(aValue, "FULL"))
+						Value = CPosRule::FULL;
+					else if(!str_comp_num(aValue, "INDEX", 5))
+					{
+						sscanf(pLine, "Pos %*d %*d INDEX %d", &Value);
+						IndexValue = true;
+					}
+
+					CPosRule NewPosRule = {x, y, Value, IndexValue};
+					pCurrentIndex->m_aRules.add(NewPosRule);
+				}
+				else if(!str_comp_num(pLine, "Random", 6) && pCurrentIndex)
+				{
+					sscanf(pLine, "Random %d", &pCurrentIndex->m_RandomValue);
+				}
+			}
+		}
+	}
+		
+	io_close(RulesFile);
+		
+	str_format(aBuf, sizeof(aBuf),"loaded %s", aPath);
+	m_pEditor->Console()->Print(IConsole::OUTPUT_LEVEL_DEBUG, "editor", aBuf);
+		
+	m_FileLoaded = true;
+}
+
+const char* CAutoMapper::GetConfigName(int Index)
+{
+	if(Index < 0 || Index >= m_lConfigs.size())
+		return "";
+
+	return m_lConfigs[Index].m_aName;
+}
+
+void CAutoMapper::Proceed(CLayerTiles *pLayer, int ConfigID)
+{
+	if(!m_FileLoaded || pLayer->m_Readonly || ConfigID < 0 || ConfigID >= m_lConfigs.size())
+		return;
+	
+	CConfiguration *pConf = &m_lConfigs[ConfigID];
+	
+	if(!pConf->m_aIndexRules.size())
+		return;
+	
+	int BaseTile = 1;
+	
+	// find base tile if there is one
+	for(int i = 0; i < pConf->m_aIndexRules.size(); ++i)
+	{
+		if(pConf->m_aIndexRules[i].m_BaseTile)
+		{
+			BaseTile = pConf->m_aIndexRules[i].m_ID;
+			break;
+		}
+	}
+	
+	// auto map !
+	int MaxIndex = pLayer->m_Width*pLayer->m_Height;
+	for(int y = 0; y < pLayer->m_Height; y++)
+		for(int x = 0; x < pLayer->m_Width; x++)
+		{
+			CTile *pTile = &(pLayer->m_pTiles[y*pLayer->m_Width+x]);
+			if(pTile->m_Index == 0)
+				continue;
+
+			pTile->m_Index = BaseTile;
+			m_pEditor->m_Map.m_Modified = true;
+
+			if(y == 0 || y == pLayer->m_Height-1 || x == 0 || x == pLayer->m_Width-1)
+				continue;
+				
+			for(int i = 0; i < pConf->m_aIndexRules.size(); ++i)
+			{
+				if(pConf->m_aIndexRules[i].m_BaseTile)
+					continue;
+
+				bool RespectRules = true;
+				for(int j = 0; j < pConf->m_aIndexRules[i].m_aRules.size() && RespectRules; ++j)
+				{
+					CPosRule *pRule = &pConf->m_aIndexRules[i].m_aRules[j];
+					int CheckIndex = (y+pRule->m_Y)*pLayer->m_Width+(x+pRule->m_X);
+							
+					if(CheckIndex < 0 || CheckIndex >= MaxIndex)
+						RespectRules = false;
+					else
+					{
+ 						if(pRule->m_IndexValue)
+						{
+							if(pLayer->m_pTiles[CheckIndex].m_Index != pRule->m_Value)
+								RespectRules = false;
+						}
+						else
+						{
+							if(pLayer->m_pTiles[CheckIndex].m_Index > 0 && pRule->m_Value == CPosRule::EMPTY)
+								RespectRules = false;
+									
+							if(pLayer->m_pTiles[CheckIndex].m_Index == 0 && pRule->m_Value == CPosRule::FULL)
+								RespectRules = false;
+						}
+					}
+				}
+					
+				if(RespectRules &&
+					(pConf->m_aIndexRules[i].m_RandomValue <= 1 || (int)((float)rand() / ((float)RAND_MAX + 1) * pConf->m_aIndexRules[i].m_RandomValue)  == 1))
+				{
+					pTile->m_Index = pConf->m_aIndexRules[i].m_ID;
+					pTile->m_Flags = pConf->m_aIndexRules[i].m_Flag;
+				}
+			}
+		}
+}
diff --git a/src/game/editor/auto_map.h b/src/game/editor/auto_map.h
new file mode 100644
index 00000000..ee570378
--- /dev/null
+++ b/src/game/editor/auto_map.h
@@ -0,0 +1,54 @@
+#ifndef GAME_EDITOR_AUTO_MAP_H
+#define GAME_EDITOR_AUTO_MAP_H
+
+#include <base/tl/array.h>
+
+class CAutoMapper
+{
+	struct CPosRule
+	{
+		int m_X;
+		int m_Y;
+		int m_Value;
+		bool m_IndexValue;
+		
+		enum
+		{
+			EMPTY=0,
+			FULL
+		};
+	};
+
+	struct CIndexRule
+	{
+		int m_ID;
+		array<CPosRule> m_aRules;
+		int m_Flag;
+		int m_RandomValue;
+		bool m_BaseTile;
+	};
+
+	struct CConfiguration
+	{
+		array<CIndexRule> m_aIndexRules;
+		char m_aName[128];
+	};
+	
+public:
+	CAutoMapper(class CEditor *pEditor);
+	
+	void Load(const char* pTileName);
+	void Proceed(class CLayerTiles *pLayer, int ConfigID);
+
+	int ConfigNamesNum() { return m_lConfigs.size(); }
+	const char* GetConfigName(int Index);
+	
+	const bool IsLoaded() { return m_FileLoaded; }
+private:
+	array<CConfiguration> m_lConfigs;
+	class CEditor *m_pEditor;
+	bool m_FileLoaded;
+};
+
+
+#endif
diff --git a/src/game/editor/ed_editor.cpp b/src/game/editor/editor.cpp
index 1163dc4a..500e600b 100644
--- a/src/game/editor/ed_editor.cpp
+++ b/src/game/editor/editor.cpp
@@ -8,20 +8,20 @@
 #include <engine/client.h>
 #include <engine/console.h>
 #include <engine/graphics.h>
-#include <engine/textrender.h>
 #include <engine/input.h>
 #include <engine/keys.h>
 #include <engine/storage.h>
+#include <engine/textrender.h>
 
-#include <game/client/ui.h>
 #include <game/gamecore.h>
+#include <game/localization.h>
+#include <game/client/lineinput.h>
 #include <game/client/render.h>
+#include <game/client/ui.h>
 #include <game/generated/client_data.h>
 
-#include "ed_editor.h"
-#include <game/client/lineinput.h>
-
-#include <game/localization.h>
+#include "auto_map.h"
+#include "editor.h"
 
 int CEditor::ms_CheckerTexture;
 int CEditor::ms_BackgroundTexture;
@@ -41,9 +41,10 @@ CEditorImage::~CEditorImage()
 
 CLayerGroup::CLayerGroup()
 {
-	m_pName = "";
+	m_aName[0] = 0;
 	m_Visible = true;
 	m_SaveToMap = true;
+	m_Collapse = false;
 	m_GameGroup = false;
 	m_OffsetX = 0;
 	m_OffsetY = 0;
@@ -189,6 +190,24 @@ void CEditorImage::AnalyseTileFlags()
 
 }
 
+void CEditor::EnvelopeEval(float TimeOffset, int Env, float *pChannels, void *pUser)
+{
+	CEditor *pThis = (CEditor *)pUser;
+	if(Env < 0 || Env >= pThis->m_Map.m_lEnvelopes.size())
+	{
+		pChannels[0] = 0;
+		pChannels[1] = 0;
+		pChannels[2] = 0;
+		pChannels[3] = 0;
+		return;
+	}
+
+	CEnvelope *e = pThis->m_Map.m_lEnvelopes[Env];
+	float t = pThis->m_AnimateTime+TimeOffset;
+	t *= pThis->m_AnimateSpeed;
+	e->Eval(t, pChannels);
+}
+
 /********************************************************
  OTHER
 *********************************************************/
@@ -196,32 +215,59 @@ void CEditorImage::AnalyseTileFlags()
 // copied from gc_menu.cpp, should be more generalized
 //extern int ui_do_edit_box(void *id, const CUIRect *rect, char *str, int str_size, float font_size, bool hidden=false);
 
-int CEditor::DoEditBox(void *pID, const CUIRect *pRect, char *pStr, unsigned StrSize, float FontSize, bool Hidden)
+int CEditor::DoEditBox(void *pID, const CUIRect *pRect, char *pStr, unsigned StrSize, float FontSize, float *Offset, bool Hidden, int Corners)
 {
-    int Inside = UI()->MouseInside(pRect);
+	int Inside = UI()->MouseInside(pRect);
 	bool ReturnValue = false;
+	bool UpdateOffset = false;
 	static int s_AtIndex = 0;
+	static bool s_DoScroll = false;
+	static float s_ScrollStart = 0.0f;
+
+	FontSize *= UI()->Scale();
 
 	if(UI()->LastActiveItem() == pID)
 	{
 		int Len = str_length(pStr);
+		if(Len == 0)
+			s_AtIndex = 0;
 
 		if(Inside && UI()->MouseButton(0))
 		{
+			s_DoScroll = true;
+			s_ScrollStart = UI()->MouseX();
 			int MxRel = (int)(UI()->MouseX() - pRect->x);
 
-			for (int i = 1; i <= Len; i++)
+			for(int i = 1; i <= Len; i++)
 			{
-				if (TextRender()->TextWidth(0, FontSize, pStr, i) + 10 > MxRel)
+				if(TextRender()->TextWidth(0, FontSize, pStr, i) - *Offset > MxRel)
 				{
 					s_AtIndex = i - 1;
 					break;
 				}
 
-				if (i == Len)
+				if(i == Len)
 					s_AtIndex = Len;
 			}
 		}
+		else if(!UI()->MouseButton(0))
+			s_DoScroll = false;
+		else if(s_DoScroll)
+		{
+			// do scrolling
+			if(UI()->MouseX() < pRect->x && s_ScrollStart-UI()->MouseX() > 10.0f)
+			{
+				s_AtIndex = max(0, s_AtIndex-1);
+				s_ScrollStart = UI()->MouseX();
+				UpdateOffset = true;
+			}
+			else if(UI()->MouseX() > pRect->x+pRect->w && UI()->MouseX()-s_ScrollStart > 10.0f)
+			{
+				s_AtIndex = min(Len, s_AtIndex+1);
+				s_ScrollStart = UI()->MouseX();
+				UpdateOffset = true;
+			}
+		}
 
 		for(int i = 0; i < Input()->NumEvents(); i++)
 		{
@@ -235,7 +281,11 @@ int CEditor::DoEditBox(void *pID, const CUIRect *pRect, char *pStr, unsigned Str
 	if(UI()->ActiveItem() == pID)
 	{
 		if(!UI()->MouseButton(0))
+		{
+			s_AtIndex = min(s_AtIndex, str_length(pStr));
+			s_DoScroll = false;
 			UI()->SetActiveItem(0);
+		}
 	}
 	else if(UI()->HotItem() == pID)
 	{
@@ -251,8 +301,8 @@ int CEditor::DoEditBox(void *pID, const CUIRect *pRect, char *pStr, unsigned Str
 		UI()->SetHotItem(pID);
 
 	CUIRect Textbox = *pRect;
-	RenderTools()->DrawUIRect(&Textbox, vec4(1,1,1,0.5f), CUI::CORNER_ALL, 3.0f);
-	Textbox.VMargin(3.0f, &Textbox);
+	RenderTools()->DrawUIRect(&Textbox, vec4(1, 1, 1, 0.5f), Corners, 3.0f);
+	Textbox.VMargin(2.0f, &Textbox);
 
 	const char *pDisplayStr = pStr;
 	char aStars[128];
@@ -268,19 +318,47 @@ int CEditor::DoEditBox(void *pID, const CUIRect *pRect, char *pStr, unsigned Str
 		pDisplayStr = aStars;
 	}
 
+	// check if the text has to be moved
+	if(UI()->LastActiveItem() == pID && !JustGotActive && (UpdateOffset || Input()->NumEvents()))
+	{
+		float w = TextRender()->TextWidth(0, FontSize, pDisplayStr, s_AtIndex);
+		if(w-*Offset > Textbox.w)
+		{
+			// move to the left
+			float wt = TextRender()->TextWidth(0, FontSize, pDisplayStr, -1);
+			do
+			{
+				*Offset += min(wt-*Offset-Textbox.w, Textbox.w/3);
+			}
+			while(w-*Offset > Textbox.w);
+		}
+		else if(w-*Offset < 0.0f)
+		{
+			// move to the right
+			do
+			{
+				*Offset = max(0.0f, *Offset-Textbox.w/3);
+			}
+			while(w-*Offset < 0.0f);
+		}
+	}
+	UI()->ClipEnable(pRect);
+	Textbox.x -= *Offset;
+
 	UI()->DoLabel(&Textbox, pDisplayStr, FontSize, -1);
-	
-	//TODO: make it blink
+
+	// render the cursor
 	if(UI()->LastActiveItem() == pID && !JustGotActive)
 	{
 		float w = TextRender()->TextWidth(0, FontSize, pDisplayStr, s_AtIndex);
 		Textbox = *pRect;
 		Textbox.VSplitLeft(2.0f, 0, &Textbox);
-		Textbox.x += w*UI()->Scale();
-		Textbox.y -= FontSize/10.f;
-		
-		UI()->DoLabel(&Textbox, "|", FontSize*1.1f, -1);
+		Textbox.x += (w-*Offset-TextRender()->TextWidth(0, FontSize, "|", -1)/2);
+
+		if((2*time_get()/time_freq()) % 2)	// make it blink
+			UI()->DoLabel(&Textbox, "|", FontSize, -1);
 	}
+	UI()->ClipDisable();
 
 	return ReturnValue;
 }
@@ -443,12 +521,12 @@ int CEditor::DoButton_Tab(const void *pID, const char *pText, int Checked, const
 	return DoButton_Editor_Common(pID, pText, Checked, pRect, Flags, pToolTip);
 }
 
-int CEditor::DoButton_Ex(const void *pID, const char *pText, int Checked, const CUIRect *pRect, int Flags, const char *pToolTip, int Corners)
+int CEditor::DoButton_Ex(const void *pID, const char *pText, int Checked, const CUIRect *pRect, int Flags, const char *pToolTip, int Corners, float FontSize)
 {
 	RenderTools()->DrawUIRect(pRect, GetButtonColor(pID, Checked), Corners, 3.0f);
     CUIRect NewRect = *pRect;
-    NewRect.y += NewRect.h/2.0f-7.0f;
-    UI()->DoLabel(&NewRect, pText, 10, 0, -1);
+    NewRect.HMargin(NewRect.h/2.0f-FontSize/2.0f-1.0f, &NewRect);
+    UI()->DoLabel(&NewRect, pText, FontSize, 0, -1);
 	return DoButton_Editor_Common(pID, pText, Checked, pRect, Flags, pToolTip);
 }
 
@@ -466,6 +544,49 @@ int CEditor::DoButton_ButtonDec(const void *pID, const char *pText, int Checked,
 	return DoButton_Editor_Common(pID, pText, Checked, pRect, Flags, pToolTip);
 }
 
+void CEditor::RenderGrid(CLayerGroup *pGroup)
+{
+	if(!m_GridActive)
+		return;
+
+	float aGroupPoints[4];
+	pGroup->Mapping(aGroupPoints); 
+
+	float w = UI()->Screen()->w;
+	float h = UI()->Screen()->h;
+
+	int LineDistance = GetLineDistance();
+
+	int XOffset = aGroupPoints[0]/LineDistance;
+	int YOffset = aGroupPoints[1]/LineDistance;
+	int XGridOffset = XOffset % m_GridFactor;
+	int YGridOffset = YOffset % m_GridFactor;
+
+	Graphics()->TextureSet(-1);	
+	Graphics()->LinesBegin();
+
+	for(int i = 0; i < (int)w; i++)
+	{
+		if((i+YGridOffset) % m_GridFactor == 0)
+			Graphics()->SetColor(1.0f, 0.3f, 0.3f, 0.3f);
+		else
+			Graphics()->SetColor(1.0f, 1.0f, 1.0f, 0.15f);
+
+		IGraphics::CLineItem Line = IGraphics::CLineItem(LineDistance*XOffset, LineDistance*i+LineDistance*YOffset, w+aGroupPoints[2], LineDistance*i+LineDistance*YOffset);
+		Graphics()->LinesDraw(&Line, 1);
+
+		if((i+XGridOffset) % m_GridFactor == 0)
+			Graphics()->SetColor(1.0f, 0.3f, 0.3f, 0.3f);
+		else
+			Graphics()->SetColor(1.0f, 1.0f, 1.0f, 0.15f);
+
+		Line = IGraphics::CLineItem(LineDistance*i+LineDistance*XOffset, LineDistance*YOffset, LineDistance*i+LineDistance*XOffset, h+aGroupPoints[3]);
+		Graphics()->LinesDraw(&Line, 1);
+	}
+	Graphics()->SetColor(1.0f, 1.0f, 1.0f, 1.0f);
+	Graphics()->LinesEnd();
+}
+
 void CEditor::RenderBackground(CUIRect View, int Texture, float Size, float Brightness)
 {
 	Graphics()->TextureSet(Texture);
@@ -482,15 +603,12 @@ int CEditor::UiDoValueSelector(void *pID, CUIRect *pRect, const char *pLabel, in
 {
     // logic
     static float s_Value;
-    int Ret = 0;
     int Inside = UI()->MouseInside(pRect);
 
 	if(UI()->ActiveItem() == pID)
 	{
 		if(!UI()->MouseButton(0))
 		{
-			if(Inside)
-				Ret = 1;
 			m_LockMouse = false;
 			UI()->SetActiveItem(0);
 		}
@@ -741,14 +859,6 @@ void CEditor::DoToolbar(CUIRect ToolBar)
 			m_AnimateSpeed -= 0.5f;
 	}
 
-	if(Input()->KeyPresses(KEY_MOUSE_WHEEL_UP) && m_Dialog == DIALOG_NONE)
-		m_ZoomLevel -= 20;
-
-	if(Input()->KeyPresses(KEY_MOUSE_WHEEL_DOWN) && m_Dialog == DIALOG_NONE)
-		m_ZoomLevel += 20;
-
-	if(m_ZoomLevel < 50)
-		m_ZoomLevel = 50;
 	m_WorldZoom = m_ZoomLevel/100.0f;
 
 	TB_Top.VSplitLeft(10.0f, &Button, &TB_Top);
@@ -861,6 +971,41 @@ void CEditor::DoToolbar(CUIRect ToolBar)
 		m_WorldOffsetX = 0;
 		m_WorldOffsetY = 0;
 	}
+
+	TB_Bottom.VSplitLeft(5.0f, 0, &TB_Bottom);
+
+	// grid button
+	TB_Bottom.VSplitLeft(50.0f, &Button, &TB_Bottom);
+	static int s_GridButton = 0;
+	if(DoButton_Editor(&s_GridButton, "Grid", m_GridActive, &Button, 0, "Toggle Grid"))
+	{
+		m_GridActive = !m_GridActive;
+	}
+
+	TB_Bottom.VSplitLeft(30.0f, 0, &TB_Bottom);
+
+	// grid zoom
+	TB_Bottom.VSplitLeft(30.0f, &Button, &TB_Bottom);
+	static int s_GridIncreaseButton = 0;
+	if(DoButton_Ex(&s_GridIncreaseButton, "G-", 0, &Button, 0, "Decrease grid", CUI::CORNER_L))
+	{
+		if(m_GridFactor > 1)
+			m_GridFactor--;
+	}
+
+	TB_Bottom.VSplitLeft(30.0f, &Button, &TB_Bottom);
+	static int s_GridNormalButton = 0;
+	if(DoButton_Ex(&s_GridNormalButton, "1", 0, &Button, 0, "Normal grid", 0))
+		m_GridFactor = 1;
+
+	TB_Bottom.VSplitLeft(30.0f, &Button, &TB_Bottom);
+
+	static int s_GridDecreaseButton = 0;
+	if(DoButton_Ex(&s_GridDecreaseButton, "G+", 0, &Button, 0, "Increase grid", CUI::CORNER_R))
+	{
+		if(m_GridFactor < 15)
+			m_GridFactor++;
+	}
 }
 
 static void Rotate(CPoint *pCenter, CPoint *pPoint, float Rotation)
@@ -920,10 +1065,41 @@ void CEditor::DoQuad(CQuad *q, int Index)
 		else if(s_Operation == OP_MOVE_ALL)
 		{
 			// move all points including pivot
-			for(int v = 0; v < 5; v++)
+			if(m_GridActive)
 			{
-				q->m_aPoints[v].x += f2fx(wx-s_LastWx);
-				q->m_aPoints[v].y += f2fx(wy-s_LastWy);
+				int LineDistance = GetLineDistance();
+
+				float x = 0.0f;
+				float y = 0.0f;
+				if(wx >= 0)
+					x = (int)((wx+(LineDistance/2)*m_GridFactor)/(LineDistance*m_GridFactor)) * (LineDistance*m_GridFactor);
+				else
+					x = (int)((wx-(LineDistance/2)*m_GridFactor)/(LineDistance*m_GridFactor)) * (LineDistance*m_GridFactor);
+				if(wy >= 0)
+					y = (int)((wy+(LineDistance/2)*m_GridFactor)/(LineDistance*m_GridFactor)) * (LineDistance*m_GridFactor);
+				else
+					y = (int)((wy-(LineDistance/2)*m_GridFactor)/(LineDistance*m_GridFactor)) * (LineDistance*m_GridFactor);
+				
+				int OldX = q->m_aPoints[4].x;
+				int OldY = q->m_aPoints[4].y;
+				q->m_aPoints[4].x = f2fx(x);
+				q->m_aPoints[4].y = f2fx(y);
+				int DiffX = q->m_aPoints[4].x - OldX;
+				int DiffY = q->m_aPoints[4].y - OldY;
+				
+				for(int v = 0; v < 4; v++)
+				{
+					q->m_aPoints[v].x += DiffX;
+					q->m_aPoints[v].y += DiffY;
+				}
+			}
+			else
+			{
+				for(int v = 0; v < 5; v++)
+				{
+						q->m_aPoints[v].x += f2fx(wx-s_LastWx);
+						q->m_aPoints[v].y += f2fx(wy-s_LastWy);
+				}
 			}
 		}
 		else if(s_Operation == OP_ROTATE)
@@ -1054,12 +1230,37 @@ void CEditor::DoQuadPoint(CQuad *pQuad, int QuadIndex, int V)
 		{
 			if(s_Operation == OP_MOVEPOINT)
 			{
-				for(int m = 0; m < 4; m++)
-					if(m_SelectedPoints&(1<<m))
-					{
-						pQuad->m_aPoints[m].x += f2fx(dx);
-						pQuad->m_aPoints[m].y += f2fx(dy);
-					}
+				if(m_GridActive)
+				{
+					for(int m = 0; m < 4; m++)
+						if(m_SelectedPoints&(1<<m))
+						{
+							int LineDistance = GetLineDistance();
+
+							float x = 0.0f;
+							float y = 0.0f;
+							if(wx >= 0)
+								x = (int)((wx+(LineDistance/2)*m_GridFactor)/(LineDistance*m_GridFactor)) * (LineDistance*m_GridFactor);
+							else
+								x = (int)((wx-(LineDistance/2)*m_GridFactor)/(LineDistance*m_GridFactor)) * (LineDistance*m_GridFactor);
+							if(wy >= 0)
+								y = (int)((wy+(LineDistance/2)*m_GridFactor)/(LineDistance*m_GridFactor)) * (LineDistance*m_GridFactor);
+							else
+								y = (int)((wy-(LineDistance/2)*m_GridFactor)/(LineDistance*m_GridFactor)) * (LineDistance*m_GridFactor);
+
+							pQuad->m_aPoints[m].x = f2fx(x);
+							pQuad->m_aPoints[m].y = f2fx(y);
+						}
+				}
+				else
+				{
+					for(int m = 0; m < 4; m++)
+						if(m_SelectedPoints&(1<<m))
+						{
+							pQuad->m_aPoints[m].x += f2fx(dx);
+							pQuad->m_aPoints[m].y += f2fx(dy);
+						}
+				}
 			}
 			else if(s_Operation == OP_MOVEUV)
 			{
@@ -1159,12 +1360,8 @@ void CEditor::DoQuadPoint(CQuad *pQuad, int QuadIndex, int V)
 
 void CEditor::DoMapEditor(CUIRect View, CUIRect ToolBar)
 {
-	//UI()->ClipEnable(&view);
-
-	bool ShowPicker = Input()->KeyPressed(KEY_SPACE) != 0 && m_Dialog == DIALOG_NONE;
-
 	// render all good stuff
-	if(!ShowPicker)
+	if(!m_ShowPicker)
 	{
 		for(int g = 0; g < m_Map.m_lGroups.size(); g++)
 		{
@@ -1182,7 +1379,10 @@ void CEditor::DoMapEditor(CUIRect View, CUIRect ToolBar)
 
 		CLayerTiles *pT = static_cast<CLayerTiles *>(GetSelectedLayerType(0, LAYERTYPE_TILES));
 		if(m_ShowTileInfo && pT && pT->m_Visible && m_ZoomLevel <= 300)
+		{
+			GetSelectedGroup()->MapScreen();
 			pT->ShowInfo();
+		}
 	}
 
 	static void *s_pEditorID = (void *)&s_pEditorID;
@@ -1196,8 +1396,6 @@ void CEditor::DoMapEditor(CUIRect View, CUIRect ToolBar)
 
 	static float s_StartWx = 0;
 	static float s_StartWy = 0;
-	static float s_StartMx = 0;
-	static float s_StartMy = 0;
 
 	enum
 	{
@@ -1210,7 +1408,7 @@ void CEditor::DoMapEditor(CUIRect View, CUIRect ToolBar)
 	};
 
 	// remap the screen so it can display the whole tileset
-	if(ShowPicker)
+	if(m_ShowPicker)
 	{
 		CUIRect Screen = *UI()->Screen();
 		float Size = 32.0*16.0f;
@@ -1239,7 +1437,7 @@ void CEditor::DoMapEditor(CUIRect View, CUIRect ToolBar)
 	int NumEditLayers = 0;
 	NumEditLayers = 0;
 
-	if(ShowPicker)
+	if(m_ShowPicker)
 	{
 		pEditLayers[0] = &m_TilesetPicker;
 		NumEditLayers++;
@@ -1255,6 +1453,8 @@ void CEditor::DoMapEditor(CUIRect View, CUIRect ToolBar)
 		{
 			g->MapScreen();
 
+			RenderGrid(g);
+
 			for(int i = 0; i < NumEditLayers; i++)
 			{
 				if(pEditLayers[i]->m_Type != LAYERTYPE_TILES)
@@ -1285,8 +1485,6 @@ void CEditor::DoMapEditor(CUIRect View, CUIRect ToolBar)
 		{
 			s_StartWx = wx;
 			s_StartWy = wy;
-			s_StartMx = mx;
-			s_StartMy = my;
 
 			if(Input()->KeyPressed(KEY_LCTRL) || Input()->KeyPressed(KEY_RCTRL) || UI()->MouseButton(2))
 			{
@@ -1445,7 +1643,7 @@ void CEditor::DoMapEditor(CUIRect View, CUIRect ToolBar)
 
 		// quad editing
 		{
-			if(!ShowPicker && m_Brush.IsEmpty())
+			if(!m_ShowPicker && m_Brush.IsEmpty())
 			{
 				// fetch layers
 				CLayerGroup *g = GetSelectedGroup();
@@ -1772,22 +1970,18 @@ void CEditor::RenderLayers(CUIRect ToolBox, CUIRect ToolBar, CUIRect View)
 	CUIRect Slot, Button;
 	char aBuf[64];
 
-	int ValidGroup = 0;
-	int ValidLayer = 0;
-	if(m_SelectedGroup >= 0 && m_SelectedGroup < m_Map.m_lGroups.size())
-		ValidGroup = 1;
-
-	if(ValidGroup && m_SelectedLayer >= 0 && m_SelectedLayer < m_Map.m_lGroups[m_SelectedGroup]->m_lLayers.size())
-		ValidLayer = 1;
-
 	float LayersHeight = 12.0f;	 // Height of AddGroup button
 	static int s_ScrollBar = 0;
 	static float s_ScrollValue = 0;
 
 	for(int g = 0; g < m_Map.m_lGroups.size(); g++)
+	{
 		// Each group is 19.0f
 		// Each layer is 14.0f
-		LayersHeight += 19.0f + m_Map.m_lGroups[g]->m_lLayers.size() * 14.0f;
+		LayersHeight += 19.0f;
+		if(!m_Map.m_lGroups[g]->m_Collapse)
+			LayersHeight += m_Map.m_lGroups[g]->m_lLayers.size() * 14.0f;
+	}
 
 	float ScrollDifference = LayersHeight - LayersBox.h;
 
@@ -1798,6 +1992,20 @@ void CEditor::RenderLayers(CUIRect ToolBox, CUIRect ToolBar, CUIRect View)
 		LayersBox.VSplitRight(3.0f, &LayersBox, 0);	// extra spacing
 		Scroll.HMargin(5.0f, &Scroll);
 		s_ScrollValue = UiDoScrollbarV(&s_ScrollBar, &Scroll, s_ScrollValue);
+
+		if(UI()->MouseInside(&Scroll) || UI()->MouseInside(&LayersBox))
+		{
+			int ScrollNum = (int)((LayersHeight-LayersBox.h)/15.0f)+1;
+			if(ScrollNum > 0)
+			{
+				if(Input()->KeyPresses(KEY_MOUSE_WHEEL_UP))
+					s_ScrollValue = clamp(s_ScrollValue - 1.0f/ScrollNum, 0.0f, 1.0f);
+				if(Input()->KeyPresses(KEY_MOUSE_WHEEL_DOWN))
+					s_ScrollValue = clamp(s_ScrollValue + 1.0f/ScrollNum, 0.0f, 1.0f);
+			}
+			else
+				ScrollNum = 0;
+		}
 	}
 
 	float LayerStartAt = ScrollDifference * s_ScrollValue;
@@ -1824,7 +2032,7 @@ void CEditor::RenderLayers(CUIRect ToolBox, CUIRect ToolBar, CUIRect View)
 			{
 				LayersBox.HSplitTop(12.0f, &Slot, &LayersBox);
 				Slot.VSplitLeft(12, &VisibleToggle, &Slot);
-				if(DoButton_Ex(&m_Map.m_lGroups[g]->m_Visible, m_Map.m_lGroups[g]->m_Visible?"V":"H", 0, &VisibleToggle, 0, "Toggle group visibility", CUI::CORNER_L))
+				if(DoButton_Ex(&m_Map.m_lGroups[g]->m_Visible, m_Map.m_lGroups[g]->m_Visible?"V":"H", m_Map.m_lGroups[g]->m_Collapse ? 1 : 0, &VisibleToggle, 0, "Toggle group visibility", CUI::CORNER_L))
 					m_Map.m_lGroups[g]->m_Visible = !m_Map.m_lGroups[g]->m_Visible;
 
 				Slot.VSplitRight(12.0f, &Slot, &SaveCheck);
@@ -1832,16 +2040,22 @@ void CEditor::RenderLayers(CUIRect ToolBox, CUIRect ToolBar, CUIRect View)
 					if(!m_Map.m_lGroups[g]->m_GameGroup)
 						m_Map.m_lGroups[g]->m_SaveToMap = !m_Map.m_lGroups[g]->m_SaveToMap;
 
-				str_format(aBuf, sizeof(aBuf),"#%d %s", g, m_Map.m_lGroups[g]->m_pName);
+				str_format(aBuf, sizeof(aBuf),"#%d %s", g, m_Map.m_lGroups[g]->m_aName);
+				float FontSize = 10.0f;
+				while(TextRender()->TextWidth(0, FontSize, aBuf, -1) > Slot.w)
+					FontSize--;
 				if(int Result = DoButton_Ex(&m_Map.m_lGroups[g], aBuf, g==m_SelectedGroup, &Slot,
-					BUTTON_CONTEXT, "Select group. Right click for properties.", 0))
+					BUTTON_CONTEXT, m_Map.m_lGroups[g]->m_Collapse ? "Select group. Double click to expand." : "Select group. Double click to collapse.", 0, FontSize))
 				{
 					m_SelectedGroup = g;
 					m_SelectedLayer = 0;
 
 					static int s_GroupPopupId = 0;
 					if(Result == 2)
-						UiInvokePopupMenu(&s_GroupPopupId, 0, UI()->MouseX(), UI()->MouseY(), 120, 200, PopupGroup);
+						UiInvokePopupMenu(&s_GroupPopupId, 0, UI()->MouseX(), UI()->MouseY(), 120, 220, PopupGroup);
+
+					if(m_Map.m_lGroups[g]->m_lLayers.size() && Input()->MouseDoubleClick())
+						m_Map.m_lGroups[g]->m_Collapse ^= 1;
 				}
 				LayersBox.HSplitTop(2.0f, &Slot, &LayersBox);
 			}
@@ -1857,6 +2071,9 @@ void CEditor::RenderLayers(CUIRect ToolBox, CUIRect ToolBar, CUIRect View)
 					continue;
 				}
 
+				if(m_Map.m_lGroups[g]->m_Collapse)
+					continue;
+
 				//visible
 				LayersBox.HSplitTop(12.0f, &Slot, &LayersBox);
 				Slot.VSplitLeft(12.0f, 0, &Button);
@@ -1870,15 +2087,24 @@ void CEditor::RenderLayers(CUIRect ToolBox, CUIRect ToolBar, CUIRect View)
 					if(m_Map.m_lGroups[g]->m_lLayers[i] != m_Map.m_pGameLayer)
 						m_Map.m_lGroups[g]->m_lLayers[i]->m_SaveToMap = !m_Map.m_lGroups[g]->m_lLayers[i]->m_SaveToMap;
 
-				str_format(aBuf, sizeof(aBuf),"#%d %s ", i, m_Map.m_lGroups[g]->m_lLayers[i]->m_pTypeName);
+				if(m_Map.m_lGroups[g]->m_lLayers[i]->m_aName[0])
+					str_format(aBuf, sizeof(aBuf), "%s", m_Map.m_lGroups[g]->m_lLayers[i]->m_aName);
+				else if(m_Map.m_lGroups[g]->m_lLayers[i]->m_Type == LAYERTYPE_TILES)
+					str_copy(aBuf, "Tiles", sizeof(aBuf));
+				else
+					str_copy(aBuf, "Quads", sizeof(aBuf));
+
+				float FontSize = 10.0f;
+				while(TextRender()->TextWidth(0, FontSize, aBuf, -1) > Button.w)
+					FontSize--;
 				if(int Result = DoButton_Ex(m_Map.m_lGroups[g]->m_lLayers[i], aBuf, g==m_SelectedGroup&&i==m_SelectedLayer, &Button,
-					BUTTON_CONTEXT, "Select layer. Right click for properties.", 0))
+					BUTTON_CONTEXT, "Select layer.", 0, FontSize))
 				{
 					m_SelectedLayer = i;
 					m_SelectedGroup = g;
 					static int s_LayerPopupID = 0;
 					if(Result == 2)
-						UiInvokePopupMenu(&s_LayerPopupID, 0, UI()->MouseX(), UI()->MouseY(), 120, 220, PopupLayer);
+						UiInvokePopupMenu(&s_LayerPopupID, 0, UI()->MouseX(), UI()->MouseY(), 120, 245, PopupLayer);
 				}
 
 				LayerCur += 14.0f;
@@ -1916,6 +2142,7 @@ void CEditor::ReplaceImage(const char *pFileName, int StorageType, void *pUser)
 	*pImg = ImgInfo;
 	pImg->m_External = External;
 	pEditor->ExtractName(pFileName, pImg->m_aName, sizeof(pImg->m_aName));
+	pImg->m_AutoMapper.Load(pImg->m_aName);
 	pImg->m_TexID = pEditor->Graphics()->LoadTextureRaw(ImgInfo.m_Width, ImgInfo.m_Height, ImgInfo.m_Format, ImgInfo.m_pData, CImageInfo::FORMAT_AUTO, 0);
 	pEditor->SortImages();
 	for(int i = 0; i < pEditor->m_Map.m_lImages.size(); ++i)
@@ -1947,6 +2174,7 @@ void CEditor::AddImage(const char *pFileName, int StorageType, void *pUser)
 	pImg->m_TexID = pEditor->Graphics()->LoadTextureRaw(ImgInfo.m_Width, ImgInfo.m_Height, ImgInfo.m_Format, ImgInfo.m_pData, CImageInfo::FORMAT_AUTO, 0);
 	pImg->m_External = 1;	// external by default
 	str_copy(pImg->m_aName, aBuf, sizeof(pImg->m_aName));
+	pImg->m_AutoMapper.Load(pImg->m_aName);
 	pEditor->m_Map.m_lImages.add(pImg);
 	pEditor->SortImages();
 	if(pEditor->m_SelectedImage > -1 && pEditor->m_SelectedImage < pEditor->m_Map.m_lImages.size())
@@ -2080,6 +2308,20 @@ void CEditor::RenderImages(CUIRect ToolBox, CUIRect ToolBar, CUIRect View)
 		ToolBox.VSplitRight(3.0f, &ToolBox, 0);	// extra spacing
 		Scroll.HMargin(5.0f, &Scroll);
 		s_ScrollValue = UiDoScrollbarV(&s_ScrollBar, &Scroll, s_ScrollValue);
+
+		if(UI()->MouseInside(&Scroll) || UI()->MouseInside(&ToolBox))
+		{
+			int ScrollNum = (int)((ImagesHeight-ToolBox.h)/14.0f)+1;
+			if(ScrollNum > 0)
+			{
+				if(Input()->KeyPresses(KEY_MOUSE_WHEEL_UP))
+					s_ScrollValue = clamp(s_ScrollValue - 1.0f/ScrollNum, 0.0f, 1.0f);
+				if(Input()->KeyPresses(KEY_MOUSE_WHEEL_DOWN))
+					s_ScrollValue = clamp(s_ScrollValue + 1.0f/ScrollNum, 0.0f, 1.0f);
+			}
+			else
+				ScrollNum = 0;
+		}
 	}
 
 	float ImageStartAt = ScrollDifference * s_ScrollValue;
@@ -2217,7 +2459,7 @@ void CEditor::AddFileDialogEntry(int Index, CUIRect *pView)
 	Graphics()->QuadsDrawTL(&QuadItem, 1);
 	Graphics()->QuadsEnd();
 
-	if(DoButton_File((void*)(10+(int)Button.y), m_FileList[Index].m_aName, m_FilesSelectedIndex == Index, &Button, 0, 0))
+	if(DoButton_File(&m_FileList[Index], m_FileList[Index].m_aName, m_FilesSelectedIndex == Index, &Button, 0, 0))
 	{
 		if(!m_FileList[Index].m_IsDir)
 			str_copy(m_aFileDialogFileName, m_FileList[Index].m_aFilename, sizeof(m_aFileDialogFileName));
@@ -2261,9 +2503,9 @@ void CEditor::RenderFileDialog()
 	// filebox
 	if(m_FileDialogStorageType == IStorage::TYPE_SAVE)
 	{
-		static int s_FileBoxID = 0;
+		static float s_FileBoxID = 0;
 		UI()->DoLabel(&FileBoxLabel, "Filename:", 10.0f, -1, -1);
-		if(DoEditBox(&s_FileBoxID, &FileBox, m_aFileDialogFileName, sizeof(m_aFileDialogFileName), 10.0f))
+		if(DoEditBox(&s_FileBoxID, &FileBox, m_aFileDialogFileName, sizeof(m_aFileDialogFileName), 10.0f, &s_FileBoxID))
 		{
 			// remove '/' and '\'
 			for(int i = 0; m_aFileDialogFileName[i]; ++i)
@@ -2612,8 +2854,8 @@ void CEditor::RenderEnvelopeEditor(CUIRect View)
 
 			ToolBar.VSplitLeft(80.0f, &Button, &ToolBar);
 
-			static int s_NameBox = 0;
-			if(DoEditBox(&s_NameBox, &Button, pEnvelope->m_aName, sizeof(pEnvelope->m_aName), 10.0f))
+			static float s_NameBox = 0;
+			if(DoEditBox(&s_NameBox, &Button, pEnvelope->m_aName, sizeof(pEnvelope->m_aName), 10.0f, &s_NameBox))
 				m_Map.m_Modified = true;
 		}
 	}
@@ -2811,8 +3053,6 @@ void CEditor::RenderEnvelopeEditor(CUIRect View)
 
 		// render handles
 		{
-			static bool s_Move = false;
-
 			int CurrentValue = 0, CurrentTime = 0;
 
 			Graphics()->TextureSet(-1);
@@ -2846,7 +3086,6 @@ void CEditor::RenderEnvelopeEditor(CUIRect View)
 						if(!UI()->MouseButton(0))
 						{
 							UI()->SetActiveItem(0);
-							s_Move = false;
 						}
 						else
 						{							
@@ -3050,6 +3289,7 @@ void CEditor::Render()
 	RenderBackground(View, ms_CheckerTexture, 32.0f, 1.0f);
 
 	CUIRect MenuBar, CModeBar, ToolBar, StatusBar, EnvelopeEditor, ToolBox;
+	m_ShowPicker = Input()->KeyPressed(KEY_SPACE) != 0 && m_Dialog == DIALOG_NONE;
 
 	if(m_GuiActive)
 	{
@@ -3059,7 +3299,7 @@ void CEditor::Render()
 		View.VSplitLeft(100.0f, &ToolBox, &View);
 		View.HSplitBottom(16.0f, &View, &StatusBar);
 
-		if(m_ShowEnvelopeEditor)
+		if(m_ShowEnvelopeEditor && !m_ShowPicker)
 		{
 			float size = 125.0f;
 			if(m_ShowEnvelopeEditor == 2)
@@ -3074,6 +3314,18 @@ void CEditor::Render()
 	if(m_Mode == MODE_LAYERS)
 		DoMapEditor(View, ToolBar);
 
+	// do the scrolling
+	if(m_Dialog == DIALOG_NONE && UI()->MouseInside(&View))
+	{
+		if(Input()->KeyPresses(KEY_MOUSE_WHEEL_UP))
+			m_ZoomLevel -= 20;
+
+		if(Input()->KeyPresses(KEY_MOUSE_WHEEL_DOWN))
+			m_ZoomLevel += 20;
+
+		m_ZoomLevel = clamp(m_ZoomLevel, 50, 2000);
+	}
+
 	if(m_GuiActive)
 	{
 		float Brightness = 0.25f;
@@ -3171,7 +3423,6 @@ void CEditor::Render()
 		Graphics()->QuadsDrawTL(&QuadItem, 1);
 		Graphics()->QuadsEnd();
 	}
-
 }
 
 void CEditor::Reset(bool CreateDefault)
@@ -3209,6 +3460,24 @@ void CEditor::Reset(bool CreateDefault)
 	m_Map.m_Modified = false;
 }
 
+int CEditor::GetLineDistance()
+{
+	int LineDistance = 512;
+
+	if(m_ZoomLevel <= 100)
+		LineDistance = 16;
+	else if(m_ZoomLevel <= 250)
+		LineDistance = 32;
+	else if(m_ZoomLevel <= 450)
+		LineDistance = 64;
+	else if(m_ZoomLevel <= 850)
+		LineDistance = 128;
+	else if(m_ZoomLevel <= 1550)
+		LineDistance = 256;
+
+	return LineDistance;
+}
+
 void CEditorMap::DeleteEnvelope(int Index)
 {
 	if(Index < 0 || Index >= m_lEnvelopes.size())
@@ -3249,7 +3518,7 @@ void CEditorMap::MakeGameGroup(CLayerGroup *pGroup)
 {
 	m_pGameGroup = pGroup;
 	m_pGameGroup->m_GameGroup = true;
-	m_pGameGroup->m_pName = "Game";
+	str_copy(m_pGameGroup->m_aName, "Game", sizeof(m_pGameGroup->m_aName));
 }
 
 
@@ -3356,6 +3625,7 @@ void CEditor::UpdateAndRender()
 	float rx, ry;
 	{
 		Input()->MouseRelative(&rx, &ry);
+		UI()->ConvertMouseMove(&rx, &ry);
 		m_MouseDeltaX = rx;
 		m_MouseDeltaY = ry;
 
@@ -3402,18 +3672,6 @@ void CEditor::UpdateAndRender()
 	if(Input()->KeyDown(KEY_TAB))
 		m_GuiActive = !m_GuiActive;
 
-	if(Input()->KeyDown(KEY_F5))
-		Save("maps/debug_test2.map");
-
-	if(Input()->KeyDown(KEY_F6))
-		Load("maps/debug_test2.map", IStorage::TYPE_ALL);
-	
-	if(Input()->KeyDown(KEY_F8))
-		Load("maps/debug_test.map", IStorage::TYPE_ALL);
-	
-	if(Input()->KeyDown(KEY_F7))
-		Save("maps/quicksave.map");
-
 	if(Input()->KeyDown(KEY_F10))
 		m_ShowMousePointer = false;
 
diff --git a/src/game/editor/ed_editor.h b/src/game/editor/editor.h
index aab757e1..1a904953 100644
--- a/src/game/editor/ed_editor.h
+++ b/src/game/editor/editor.h
@@ -1,25 +1,28 @@
 /* (c) Magnus Auvinen. See licence.txt in the root of the distribution for more information. */
 /* If you are missing that file, acquire a complete release at teeworlds.com.                */
-#ifndef GAME_EDITOR_ED_EDITOR_H
-#define GAME_EDITOR_ED_EDITOR_H
+#ifndef GAME_EDITOR_EDITOR_H
+#define GAME_EDITOR_EDITOR_H
+
+#include <math.h>
 
-#include <base/system.h>
 #include <base/math.h>
-#include <base/tl/array.h>
+#include <base/system.h>
+
 #include <base/tl/algorithm.h>
+#include <base/tl/array.h>
 #include <base/tl/sorted_array.h>
 #include <base/tl/string.h>
 
-#include <math.h>
+#include <game/client/ui.h>
 #include <game/mapitems.h>
 #include <game/client/render.h>
 
-#include <engine/shared/datafile.h>
 #include <engine/shared/config.h>
+#include <engine/shared/datafile.h>
 #include <engine/editor.h>
 #include <engine/graphics.h>
 
-#include <game/client/ui.h>
+#include "auto_map.h"
 
 typedef void (*INDEX_MODIFY_FUNC)(int *pIndex);
 
@@ -123,7 +126,7 @@ public:
 	CLayer()
 	{
 		m_Type = LAYERTYPE_INVALID;
-		m_pTypeName = "(invalid)";
+		str_copy(m_aName, "(invalid)", sizeof(m_aName));
 		m_Visible = true;
 		m_Readonly = false;
 		m_SaveToMap = true;
@@ -153,7 +156,7 @@ public:
 
 	virtual void GetSize(float *w, float *h) { *w = 0; *h = 0;}
 
-	const char *m_pTypeName;
+	char m_aName[12];
 	int m_Type;
 	int m_Flags;
 
@@ -181,10 +184,11 @@ public:
 	int m_ClipW;
 	int m_ClipH;
 
-	const char *m_pName;
+	char m_aName[12];
 	bool m_GameGroup;
 	bool m_Visible;
 	bool m_SaveToMap;
+	bool m_Collapse;
 
 	CLayerGroup();
 	~CLayerGroup();
@@ -230,6 +234,7 @@ public:
 	CEditor *m_pEditor;
 
 	CEditorImage(CEditor *pEditor)
+	: m_AutoMapper(pEditor)
 	{
 		m_pEditor = pEditor;
 		m_TexID = -1;
@@ -249,6 +254,7 @@ public:
 	int m_External;
 	char m_aName[128];
 	unsigned char m_aTileFlags[256];
+	class CAutoMapper m_AutoMapper;
 };
 
 class CEditorMap
@@ -400,6 +406,8 @@ public:
 	int m_Width;
 	int m_Height;
 	CColor m_Color;
+	int m_ColorEnv;
+	int m_ColorEnvOffset;
 	CTile *m_pTiles;
 };
 
@@ -470,6 +478,9 @@ public:
 		m_Dialog = 0;
 		m_pTooltip = 0;
 
+		m_GridActive = false;
+		m_GridFactor = 1;
+
 		m_aFileName[0] = 0;
 		m_aFileSaveName[0] = 0;
 		m_ValidSaveFilename = false;
@@ -551,6 +562,9 @@ public:
 	int m_Dialog;
 	const char *m_pTooltip;
 
+	bool m_GridActive;
+	int m_GridFactor;
+
 	char m_aFileName[512];
 	char m_aFileSaveName[512];
 	bool m_ValidSaveFilename;
@@ -630,6 +644,7 @@ public:
 	float m_AnimateSpeed;
 
 	int m_ShowEnvelopeEditor;
+	bool m_ShowPicker;
 
 	int m_SelectedLayer;
 	int m_SelectedGroup;
@@ -650,12 +665,14 @@ public:
 
 	CEditorMap m_Map;
 
+	static void EnvelopeEval(float TimeOffset, int Env, float *pChannels, void *pUser);
+
 	void DoMapBorder();
 	int DoButton_Editor_Common(const void *pID, const char *pText, int Checked, const CUIRect *pRect, int Flags, const char *pToolTip);
 	int DoButton_Editor(const void *pID, const char *pText, int Checked, const CUIRect *pRect, int Flags, const char *pToolTip);
 
 	int DoButton_Tab(const void *pID, const char *pText, int Checked, const CUIRect *pRect, int Flags, const char *pToolTip);
-	int DoButton_Ex(const void *pID, const char *pText, int Checked, const CUIRect *pRect, int Flags, const char *pToolTip, int Corners);
+	int DoButton_Ex(const void *pID, const char *pText, int Checked, const CUIRect *pRect, int Flags, const char *pToolTip, int Corners, float FontSize=10.0f);
 	int DoButton_ButtonDec(const void *pID, const char *pText, int Checked, const CUIRect *pRect, int Flags, const char *pToolTip);
 	int DoButton_ButtonInc(const void *pID, const char *pText, int Checked, const CUIRect *pRect, int Flags, const char *pToolTip);
 
@@ -664,10 +681,12 @@ public:
 	int DoButton_Menu(const void *pID, const char *pText, int Checked, const CUIRect *pRect, int Flags, const char *pToolTip);
 	int DoButton_MenuItem(const void *pID, const char *pText, int Checked, const CUIRect *pRect, int Flags=0, const char *pToolTip=0);
 
-	int DoEditBox(void *pID, const CUIRect *pRect, char *pStr, unsigned StrSize, float FontSize, bool Hidden=false);
+	int DoEditBox(void *pID, const CUIRect *pRect, char *pStr, unsigned StrSize, float FontSize, float *Offset, bool Hidden=false, int Corners=CUI::CORNER_ALL);
 
 	void RenderBackground(CUIRect View, int Texture, float Size, float Brightness);
 
+	void RenderGrid(CLayerGroup *pGroup);
+
 	void UiInvokePopupMenu(void *pID, int Flags, float X, float Y, float W, float H, int (*pfnFunc)(CEditor *pEditor, CUIRect Rect), void *pExtra=0);
 	void UiDoPopupMenu();
 
@@ -683,6 +702,7 @@ public:
 	static int PopupSelectGametileOp(CEditor *pEditor, CUIRect View);
 	static int PopupImage(CEditor *pEditor, CUIRect View);
 	static int PopupMenuFile(CEditor *pEditor, CUIRect View);
+	static int PopupSelectConfigAutoMap(CEditor *pEditor, CUIRect View);
 
 	static void CallbackOpenMap(const char *pFileName, int StorageType, void *pUser);
 	static void CallbackAppendMap(const char *pFileName, int StorageType, void *pUser);
@@ -693,6 +713,9 @@ public:
 
 	void PopupSelectGametileOpInvoke(float x, float y);
 	int PopupSelectGameTileOpResult();
+	
+	void PopupSelectConfigAutoMapInvoke(float x, float y);
+	int PopupSelectConfigAutoMapResult();
 
 	vec4 ButtonColorMul(const void *pID);
 
@@ -732,6 +755,8 @@ public:
 		int Length = pEnd > pExtractedName ? min(BufferSize, (int)(pEnd-pExtractedName+1)) : BufferSize;
 		str_copy(pName, pExtractedName, Length);
 	}
+
+	int GetLineDistance();
 };
 
 // make sure to inline this function
diff --git a/src/game/editor/ed_io.cpp b/src/game/editor/io.cpp
index a5ead97a..68330f03 100644
--- a/src/game/editor/ed_io.cpp
+++ b/src/game/editor/io.cpp
@@ -6,7 +6,7 @@
 #include <engine/serverbrowser.h>
 #include <engine/storage.h>
 #include <game/gamecore.h>
-#include "ed_editor.h"
+#include "editor.h"
 
 template<typename T>
 static int MakeVersion(int i, const T &v)
@@ -266,6 +266,9 @@ int CEditorMap::Save(class IStorage *pStorage, const char *pFileName)
 		GItem.m_StartLayer = LayerCount;
 		GItem.m_NumLayers = 0;
 
+		// save group name
+		StrToInts(GItem.m_aName, sizeof(GItem.m_aName)/sizeof(int), pGroup->m_aName);
+
 		for(int l = 0; l < pGroup->m_lLayers.size(); l++)
 		{
 			if(!pGroup->m_lLayers[l]->m_SaveToMap)
@@ -278,23 +281,24 @@ int CEditorMap::Save(class IStorage *pStorage, const char *pFileName)
 				pLayer->PrepareForSave();
 
 				CMapItemLayerTilemap Item;
-				Item.m_Version = 2;
+				Item.m_Version = 3;
 
 				Item.m_Layer.m_Flags = pLayer->m_Flags;
 				Item.m_Layer.m_Type = pLayer->m_Type;
 
-				Item.m_Color.r = pLayer->m_Color.r;
-				Item.m_Color.g = pLayer->m_Color.g;
-				Item.m_Color.b = pLayer->m_Color.b;
-				Item.m_Color.a = pLayer->m_Color.a;
-				Item.m_ColorEnv = -1; // not in use right now
-				Item.m_ColorEnvOffset = 0;
+				Item.m_Color = pLayer->m_Color;
+				Item.m_ColorEnv = pLayer->m_ColorEnv;
+				Item.m_ColorEnvOffset = pLayer->m_ColorEnvOffset;
 
 				Item.m_Width = pLayer->m_Width;
 				Item.m_Height = pLayer->m_Height;
-				Item.m_Flags = pLayer->m_Game;
+				Item.m_Flags = pLayer->m_Game ? TILESLAYERFLAG_GAME : 0;
 				Item.m_Image = pLayer->m_Image;
 				Item.m_Data = df.AddData(pLayer->m_Width*pLayer->m_Height*sizeof(CTile), pLayer->m_pTiles);
+
+				// save layer name
+				StrToInts(Item.m_aName, sizeof(Item.m_aName)/sizeof(int), pLayer->m_aName);
+
 				df.AddItem(MAPITEMTYPE_LAYER, LayerCount, sizeof(Item), &Item);
 
 				GItem.m_NumLayers++;
@@ -307,7 +311,7 @@ int CEditorMap::Save(class IStorage *pStorage, const char *pFileName)
 				if(pLayer->m_lQuads.size())
 				{
 					CMapItemLayerQuads Item;
-					Item.m_Version = 1;
+					Item.m_Version = 2;
 					Item.m_Layer.m_Flags = pLayer->m_Flags;
 					Item.m_Layer.m_Type = pLayer->m_Type;
 					Item.m_Image = pLayer->m_Image;
@@ -315,6 +319,10 @@ int CEditorMap::Save(class IStorage *pStorage, const char *pFileName)
 					// add the data
 					Item.m_NumQuads = pLayer->m_lQuads.size();
 					Item.m_Data = df.AddDataSwapped(pLayer->m_lQuads.size()*sizeof(CQuad), pLayer->m_lQuads.base_ptr());
+
+					// save layer name
+					StrToInts(Item.m_aName, sizeof(Item.m_aName)/sizeof(int), pLayer->m_aName);
+
 					df.AddItem(MAPITEMTYPE_LAYER, LayerCount, sizeof(Item), &Item);
 
 					// clean up
@@ -449,6 +457,9 @@ int CEditorMap::Load(class IStorage *pStorage, const char *pFileName, int Storag
 				if(pName)
 					str_copy(pImg->m_aName, pName, 128);
 
+				// load auto mapper file
+				pImg->m_AutoMapper.Load(pImg->m_aName);
+
 				m_lImages.add(pImg);
 
 				// unload image
@@ -486,6 +497,10 @@ int CEditorMap::Load(class IStorage *pStorage, const char *pFileName, int Storag
 					pGroup->m_ClipH = pGItem->m_ClipH;
 				}
 
+				// load group name
+				if(pGItem->m_Version >= 3)
+					IntsToStr(pGItem->m_aName, sizeof(pGroup->m_aName)/sizeof(int), pGroup->m_aName);
+
 				for(int l = 0; l < pGItem->m_NumLayers; l++)
 				{
 					CLayer *pLayer = 0;
@@ -498,7 +513,7 @@ int CEditorMap::Load(class IStorage *pStorage, const char *pFileName, int Storag
 						CMapItemLayerTilemap *pTilemapItem = (CMapItemLayerTilemap *)pLayerItem;
 						CLayerTiles *pTiles = 0;
 
-						if(pTilemapItem->m_Flags&1)
+						if(pTilemapItem->m_Flags&TILESLAYERFLAG_GAME)
 						{
 							pTiles = new CLayerGame(pTilemapItem->m_Width, pTilemapItem->m_Height);
 							MakeGameLayer(pTiles);
@@ -508,10 +523,9 @@ int CEditorMap::Load(class IStorage *pStorage, const char *pFileName, int Storag
 						{
 							pTiles = new CLayerTiles(pTilemapItem->m_Width, pTilemapItem->m_Height);
 							pTiles->m_pEditor = m_pEditor;
-							pTiles->m_Color.r = pTilemapItem->m_Color.r;
-							pTiles->m_Color.g = pTilemapItem->m_Color.g;
-							pTiles->m_Color.b = pTilemapItem->m_Color.b;
-							pTiles->m_Color.a = pTilemapItem->m_Color.a;
+							pTiles->m_Color = pTilemapItem->m_Color;
+							pTiles->m_ColorEnv = pTilemapItem->m_ColorEnv;
+							pTiles->m_ColorEnvOffset = pTilemapItem->m_ColorEnvOffset;
 						}
 
 						pLayer = pTiles;
@@ -519,7 +533,11 @@ int CEditorMap::Load(class IStorage *pStorage, const char *pFileName, int Storag
 						pGroup->AddLayer(pTiles);
 						void *pData = DataFile.GetData(pTilemapItem->m_Data);
 						pTiles->m_Image = pTilemapItem->m_Image;
-						pTiles->m_Game = pTilemapItem->m_Flags&1;
+						pTiles->m_Game = pTilemapItem->m_Flags&TILESLAYERFLAG_GAME;
+
+						// load layer name
+						if(pTilemapItem->m_Version >= 3)
+							IntsToStr(pTilemapItem->m_aName, sizeof(pTiles->m_aName)/sizeof(int), pTiles->m_aName);
 
 						mem_copy(pTiles->m_pTiles, pData, pTiles->m_Width*pTiles->m_Height*sizeof(CTile));
 
@@ -543,6 +561,11 @@ int CEditorMap::Load(class IStorage *pStorage, const char *pFileName, int Storag
 						pQuads->m_Image = pQuadsItem->m_Image;
 						if(pQuads->m_Image < -1 || pQuads->m_Image >= m_lImages.size())
 							pQuads->m_Image = -1;
+
+						// load layer name
+						if(pQuadsItem->m_Version >= 2)
+							IntsToStr(pQuadsItem->m_aName, sizeof(pQuads->m_aName)/sizeof(int), pQuads->m_aName);
+
 						void *pData = DataFile.GetDataSwapped(pQuadsItem->m_Data);
 						pGroup->AddLayer(pQuads);
 						pQuads->m_lQuads.set_size(pQuadsItem->m_NumQuads);
diff --git a/src/game/editor/ed_layer_game.cpp b/src/game/editor/layer_game.cpp
index cf48845e..7e879c3e 100644
--- a/src/game/editor/ed_layer_game.cpp
+++ b/src/game/editor/layer_game.cpp
@@ -1,12 +1,12 @@
 /* (c) Magnus Auvinen. See licence.txt in the root of the distribution for more information. */
 /* If you are missing that file, acquire a complete release at teeworlds.com.                */
-#include "ed_editor.h"
+#include "editor.h"
 
 
 CLayerGame::CLayerGame(int w, int h)
 : CLayerTiles(w, h)
 {
-	m_pTypeName = "Game";
+	str_copy(m_aName, "Game", sizeof(m_aName));
 	m_Game = 1;
 }
 
diff --git a/src/game/editor/ed_layer_quads.cpp b/src/game/editor/layer_quads.cpp
index 680a54cd..d0b66405 100644
--- a/src/game/editor/ed_layer_quads.cpp
+++ b/src/game/editor/layer_quads.cpp
@@ -5,7 +5,7 @@
 #include <engine/console.h>
 #include <engine/graphics.h>
 
-#include "ed_editor.h"
+#include "editor.h"
 #include <game/generated/client_data.h>
 #include <game/client/render.h>
 #include <game/localization.h>
@@ -13,7 +13,7 @@
 CLayerQuads::CLayerQuads()
 {
 	m_Type = LAYERTYPE_QUADS;
-	m_pTypeName = "Quads";
+	str_copy(m_aName, "Quads", sizeof(m_aName));
 	m_Image = -1;
 }
 
@@ -21,31 +21,13 @@ CLayerQuads::~CLayerQuads()
 {
 }
 
-static void EnvelopeEval(float TimeOffset, int Env, float *pChannels, void *pUser)
-{
-	CEditor *pEditor = (CEditor *)pUser;
-	if(Env < 0 || Env > pEditor->m_Map.m_lEnvelopes.size())
-	{
-		pChannels[0] = 0;
-		pChannels[1] = 0;
-		pChannels[2] = 0;
-		pChannels[3] = 0;
-		return;
-	}
-
-	CEnvelope *e = pEditor->m_Map.m_lEnvelopes[Env];
-	float t = pEditor->m_AnimateTime+TimeOffset;
-	t *= pEditor->m_AnimateSpeed;
-	e->Eval(t, pChannels);
-}
-
 void CLayerQuads::Render()
 {
 	Graphics()->TextureSet(-1);
 	if(m_Image >= 0 && m_Image < m_pEditor->m_Map.m_lImages.size())
 		Graphics()->TextureSet(m_pEditor->m_Map.m_lImages[m_Image]->m_TexID);
 
-	m_pEditor->RenderTools()->RenderQuads(m_lQuads.base_ptr(), m_lQuads.size(), LAYERRENDERFLAG_OPAQUE|LAYERRENDERFLAG_TRANSPARENT, EnvelopeEval, m_pEditor);
+	m_pEditor->RenderTools()->RenderQuads(m_lQuads.base_ptr(), m_lQuads.size(), LAYERRENDERFLAG_OPAQUE|LAYERRENDERFLAG_TRANSPARENT, m_pEditor->EnvelopeEval, m_pEditor);
 }
 
 CQuad *CLayerQuads::NewQuad()
diff --git a/src/game/editor/ed_layer_tiles.cpp b/src/game/editor/layer_tiles.cpp
index d0c9041c..5662613c 100644
--- a/src/game/editor/ed_layer_tiles.cpp
+++ b/src/game/editor/layer_tiles.cpp
@@ -8,14 +8,14 @@
 
 #include <game/generated/client_data.h>
 #include <game/client/render.h>
-#include "ed_editor.h"
+#include "editor.h"
 
 #include <game/localization.h>
 
 CLayerTiles::CLayerTiles(int w, int h)
 {
 	m_Type = LAYERTYPE_TILES;
-	m_pTypeName = "Tiles";
+	str_copy(m_aName, "Tiles", sizeof(m_aName));
 	m_Width = w;
 	m_Height = h;
 	m_Image = -1;
@@ -25,6 +25,8 @@ CLayerTiles::CLayerTiles(int w, int h)
 	m_Color.g = 255;
 	m_Color.b = 255;
 	m_Color.a = 255;
+	m_ColorEnv = -1;
+	m_ColorEnvOffset = 0;
 
 	m_pTiles = new CTile[m_Width*m_Height];
 	mem_zero(m_pTiles, m_Width*m_Height*sizeof(CTile));
@@ -62,7 +64,8 @@ void CLayerTiles::Render()
 		m_TexID = m_pEditor->m_Map.m_lImages[m_Image]->m_TexID;
 	Graphics()->TextureSet(m_TexID);
 	vec4 Color = vec4(m_Color.r/255.0f, m_Color.g/255.0f, m_Color.b/255.0f, m_Color.a/255.0f);
-	m_pEditor->RenderTools()->RenderTilemap(m_pTiles, m_Width, m_Height, 32.0f, Color, LAYERRENDERFLAG_OPAQUE|LAYERRENDERFLAG_TRANSPARENT);
+	m_pEditor->RenderTools()->RenderTilemap(m_pTiles, m_Width, m_Height, 32.0f, Color, LAYERRENDERFLAG_OPAQUE|LAYERRENDERFLAG_TRANSPARENT,
+												m_pEditor->EnvelopeEval, m_pEditor, m_ColorEnv, m_ColorEnvOffset);
 }
 
 int CLayerTiles::ConvertX(float x) const { return (int)(x/32.0f); }
@@ -123,7 +126,7 @@ void CLayerTiles::BrushSelecting(CUIRect Rect)
 	m_pEditor->Graphics()->QuadsEnd();
 	char aBuf[16];
 	str_format(aBuf, sizeof(aBuf), "%d,%d", ConvertX(Rect.w), ConvertY(Rect.h));
-	TextRender()->Text(0, Rect.x+3.0f, Rect.y+3.0f, 15.0f*m_pEditor->m_WorldZoom, aBuf, -1);
+	TextRender()->Text(0, Rect.x+3.0f, Rect.y+3.0f, m_pEditor->m_ShowPicker?15.0f:15.0f*m_pEditor->m_WorldZoom, aBuf, -1);
 }
 
 int CLayerTiles::BrushGrab(CLayerGroup *pBrush, CUIRect Rect)
@@ -353,19 +356,39 @@ void CLayerTiles::ShowInfo()
 			}
 			x += m_pTiles[c].m_Skip;
 		}
+
+	Graphics()->MapScreen(ScreenX0, ScreenY0, ScreenX1, ScreenY1);
 }
 
 int CLayerTiles::RenderProperties(CUIRect *pToolBox)
 {
 	CUIRect Button;
-	pToolBox->HSplitBottom(12.0f, pToolBox, &Button);
-
+	
 	bool InGameGroup = !find_linear(m_pEditor->m_Map.m_pGameGroup->m_lLayers.all(), this).empty();
-	if(m_pEditor->m_Map.m_pGameLayer == this)
+	if(m_pEditor->m_Map.m_pGameLayer != this)
+	{
+		if(m_Image >= 0 && m_Image < m_pEditor->m_Map.m_lImages.size() && m_pEditor->m_Map.m_lImages[m_Image]->m_AutoMapper.IsLoaded())
+		{
+			static int s_AutoMapperButton = 0;
+			pToolBox->HSplitBottom(12.0f, pToolBox, &Button);
+			if(m_pEditor->DoButton_Editor(&s_AutoMapperButton, "Auto map", 0, &Button, 0, ""))
+				m_pEditor->PopupSelectConfigAutoMapInvoke(m_pEditor->UI()->MouseX(), m_pEditor->UI()->MouseY());
+			
+			int Result = m_pEditor->PopupSelectConfigAutoMapResult();
+			if(Result > -1)
+			{
+				m_pEditor->m_Map.m_lImages[m_Image]->m_AutoMapper.Proceed(this, Result);
+				return 1;
+			}
+		}
+	}
+	else
 		InGameGroup = false;
 
 	if(InGameGroup)
 	{
+		pToolBox->HSplitBottom(2.0f, pToolBox, 0);
+		pToolBox->HSplitBottom(12.0f, pToolBox, &Button);
 		static int s_ColclButton = 0;
 		if(m_pEditor->DoButton_Editor(&s_ColclButton, "Game tiles", 0, &Button, 0, "Constructs game tiles from this layer"))
 			m_pEditor->PopupSelectGametileOpInvoke(m_pEditor->UI()->MouseX(), m_pEditor->UI()->MouseY());
@@ -392,6 +415,8 @@ int CLayerTiles::RenderProperties(CUIRect *pToolBox)
 		PROP_SHIFT,
 		PROP_IMAGE,
 		PROP_COLOR,
+		PROP_COLOR_ENV,
+		PROP_COLOR_ENV_OFFSET,
 		NUM_PROPS,
 	};
 
@@ -407,6 +432,8 @@ int CLayerTiles::RenderProperties(CUIRect *pToolBox)
 		{"Shift", 0, PROPTYPE_SHIFT, 0, 0},
 		{"Image", m_Image, PROPTYPE_IMAGE, 0, 0},
 		{"Color", Color, PROPTYPE_COLOR, 0, 0},
+		{"Color Env", m_ColorEnv+1, PROPTYPE_INT_STEP, 0, m_pEditor->m_Map.m_lEnvelopes.size()+1},
+		{"Color TO", m_ColorEnvOffset, PROPTYPE_INT_SCROLL, -1000000, 1000000},
 		{0},
 	};
 
@@ -445,6 +472,10 @@ int CLayerTiles::RenderProperties(CUIRect *pToolBox)
 		m_Color.b = (NewVal>>8)&0xff;
 		m_Color.a = NewVal&0xff;
 	}
+	if(Prop == PROP_COLOR_ENV)
+		m_ColorEnv = clamp(NewVal-1, -1, m_pEditor->m_Map.m_lEnvelopes.size()-1);
+	if(Prop == PROP_COLOR_ENV_OFFSET)
+		m_ColorEnvOffset = NewVal;
 
 	return 0;
 }
diff --git a/src/game/editor/ed_popups.cpp b/src/game/editor/popups.cpp
index f572e43c..f29ae7e2 100644
--- a/src/game/editor/ed_popups.cpp
+++ b/src/game/editor/popups.cpp
@@ -1,11 +1,15 @@
 /* (c) Magnus Auvinen. See licence.txt in the root of the distribution for more information. */
 /* If you are missing that file, acquire a complete release at teeworlds.com.                */
+
+#include <base/tl/array.h>
+
 #include <engine/console.h>
 #include <engine/graphics.h>
 #include <engine/input.h>
 #include <engine/keys.h>
 #include <engine/storage.h>
-#include "ed_editor.h"
+
+#include "editor.h"
 
 
 // popup menu handling
@@ -147,6 +151,7 @@ int CEditor::PopupGroup(CEditor *pEditor, CUIRect View)
 		l->m_pEditor = pEditor;
 		pEditor->m_Map.m_lGroups[pEditor->m_SelectedGroup]->AddLayer(l);
 		pEditor->m_SelectedLayer = pEditor->m_Map.m_lGroups[pEditor->m_SelectedGroup]->m_lLayers.size()-1;
+		pEditor->m_Map.m_lGroups[pEditor->m_SelectedGroup]->m_Collapse = false;
 		return 1;
 	}
 
@@ -160,9 +165,22 @@ int CEditor::PopupGroup(CEditor *pEditor, CUIRect View)
 		l->m_pEditor = pEditor;
 		pEditor->m_Map.m_lGroups[pEditor->m_SelectedGroup]->AddLayer(l);
 		pEditor->m_SelectedLayer = pEditor->m_Map.m_lGroups[pEditor->m_SelectedGroup]->m_lLayers.size()-1;
+		pEditor->m_Map.m_lGroups[pEditor->m_SelectedGroup]->m_Collapse = false;
 		return 1;
 	}
 
+	// group name
+	if(!pEditor->GetSelectedGroup()->m_GameGroup)
+	{
+		View.HSplitBottom(5.0f, &View, &Button);
+		View.HSplitBottom(12.0f, &View, &Button);
+		static float s_Name = 0;
+		pEditor->UI()->DoLabel(&Button, "Name:", 10.0f, -1, -1);
+		Button.VSplitLeft(40.0f, 0, &Button);
+		if(pEditor->DoEditBox(&s_Name, &Button, pEditor->m_Map.m_lGroups[pEditor->m_SelectedGroup]->m_aName, sizeof(pEditor->m_Map.m_lGroups[pEditor->m_SelectedGroup]->m_aName), 10.0f, &s_Name))
+			pEditor->m_Map.m_Modified = true;
+	}
+
 	enum
 	{
 		PROP_ORDER=0,
@@ -239,6 +257,18 @@ int CEditor::PopupLayer(CEditor *pEditor, CUIRect View)
 		return 1;
 	}
 
+	// layer name
+	if(pEditor->m_Map.m_pGameLayer != pEditor->GetSelectedLayer(0))
+	{
+		View.HSplitBottom(5.0f, &View, &Button);
+		View.HSplitBottom(12.0f, &View, &Button);
+		static float s_Name = 0;
+		pEditor->UI()->DoLabel(&Button, "Name:", 10.0f, -1, -1);
+		Button.VSplitLeft(40.0f, 0, &Button);
+		if(pEditor->DoEditBox(&s_Name, &Button, pEditor->GetSelectedLayer(0)->m_aName, sizeof(pEditor->GetSelectedLayer(0)->m_aName), 10.0f, &s_Name))
+			pEditor->m_Map.m_Modified = true;
+	}
+
 	View.HSplitBottom(10.0f, &View, 0);
 
 	CLayerGroup *pCurrentGroup = pEditor->m_Map.m_lGroups[pEditor->m_SelectedGroup];
@@ -528,8 +558,8 @@ int CEditor::PopupNewFolder(CEditor *pEditor, CUIRect View)
 		View.HSplitBottom(40.0f, &View, 0);
 		View.VMargin(40.0f, &View);
 		View.HSplitBottom(20.0f, &View, &Label);
-		static int s_FolderBox = 0;
-		pEditor->DoEditBox(&s_FolderBox, &Label, pEditor->m_FileDialogNewFolderName, sizeof(pEditor->m_FileDialogNewFolderName), 15.0f);
+		static float s_FolderBox = 0;
+		pEditor->DoEditBox(&s_FolderBox, &Label, pEditor->m_FileDialogNewFolderName, sizeof(pEditor->m_FileDialogNewFolderName), 15.0f, &s_FolderBox);
 		View.HSplitBottom(20.0f, &View, &Label);
 		pEditor->UI()->DoLabel(&Label, "Name:", 10.0f, -1);
 
@@ -737,3 +767,43 @@ int CEditor::PopupSelectGameTileOpResult()
 	s_GametileOpSelected = -1;
 	return Result;
 }
+
+static int s_AutoMapConfigSelected = -1;
+
+int CEditor::PopupSelectConfigAutoMap(CEditor *pEditor, CUIRect View)
+{
+	CLayerTiles *pLayer = static_cast<CLayerTiles*>(pEditor->GetSelectedLayer(0));
+	CUIRect Button;
+	static int s_AutoMapperConfigButtons[256];
+	CAutoMapper *pAutoMapper = &pEditor->m_Map.m_lImages[pLayer->m_Image]->m_AutoMapper;
+	
+	for(int i = 0; i < pAutoMapper->ConfigNamesNum(); ++i)
+	{
+		View.HSplitTop(2.0f, 0, &View);
+		View.HSplitTop(12.0f, &Button, &View);
+		if(pEditor->DoButton_Editor(&s_AutoMapperConfigButtons[i], pAutoMapper->GetConfigName(i), 0, &Button, 0, 0))
+			s_AutoMapConfigSelected = i;
+	}
+
+	return 0;
+}
+
+void CEditor::PopupSelectConfigAutoMapInvoke(float x, float y)
+{
+	static int s_AutoMapConfigSelectID = 0;
+	s_AutoMapConfigSelected = -1;
+	CLayerTiles *pLayer = static_cast<CLayerTiles*>(GetSelectedLayer(0));
+	if(pLayer && pLayer->m_Image >= 0 && pLayer->m_Image < m_Map.m_lImages.size() &&
+		m_Map.m_lImages[pLayer->m_Image]->m_AutoMapper.ConfigNamesNum())
+		UiInvokePopupMenu(&s_AutoMapConfigSelectID, 0, x, y, 120.0f, 12.0f+14.0f*m_Map.m_lImages[pLayer->m_Image]->m_AutoMapper.ConfigNamesNum(), PopupSelectConfigAutoMap);
+}
+
+int CEditor::PopupSelectConfigAutoMapResult()
+{
+	if(s_AutoMapConfigSelected < 0)
+		return -1;
+	
+	int Result = s_AutoMapConfigSelected;
+	s_AutoMapConfigSelected = -1;
+	return Result;
+}
diff --git a/src/game/gamecore.cpp b/src/game/gamecore.cpp
index d2a1652c..af086df2 100644
--- a/src/game/gamecore.cpp
+++ b/src/game/gamecore.cpp
@@ -362,35 +362,37 @@ void CCharacterCore::Move()
 
 	m_Vel.x = m_Vel.x*RampValue;
 
-	vec2 NewPos = m_Pos;

+	vec2 NewPos = m_Pos;
 	m_pCollision->MoveBox(&NewPos, &m_Vel, vec2(28.0f, 28.0f), 0);
 
 	m_Vel.x = m_Vel.x*(1.0f/RampValue);
 
 	if(m_pWorld && m_pWorld->m_Tuning.m_PlayerCollision)
 	{
-		// check player collision

-		float Distance = distance(m_Pos, NewPos);

-		int End = Distance+1;

-		for(int i = 0; i < End; i++)

-		{

-			float a = i/Distance;

-			vec2 Pos = mix(m_Pos, NewPos, a);

-			for(int p = 0; p < MAX_CLIENTS; p++)

-			{

-				CCharacterCore *pCharCore = m_pWorld->m_apCharacters[p];

-				if(!pCharCore || pCharCore == this)

-					continue;

-				float D = distance(Pos, pCharCore->m_Pos);

-				if(D < 28.0f*1.25f && D > 0.0f)

-				{

-					if(a > 0.0f)

-						m_Pos = Pos;

-					else

-						m_Pos = NewPos;

-					return;

-				}

-			}

+		// check player collision
+		float Distance = distance(m_Pos, NewPos);
+		int End = Distance+1;
+		vec2 LastPos = m_Pos;
+		for(int i = 0; i < End; i++)
+		{
+			float a = i/Distance;
+			vec2 Pos = mix(m_Pos, NewPos, a);
+			for(int p = 0; p < MAX_CLIENTS; p++)
+			{
+				CCharacterCore *pCharCore = m_pWorld->m_apCharacters[p];
+				if(!pCharCore || pCharCore == this)
+					continue;
+				float D = distance(Pos, pCharCore->m_Pos);
+				if(D < 28.0f && D > 0.0f)
+				{
+					if(a > 0.0f)
+						m_Pos = LastPos;
+					else if(distance(NewPos, pCharCore->m_Pos) > D)
+						m_Pos = NewPos;
+					return;
+				}
+			}
+			LastPos = Pos;
 		}
 	}
 
diff --git a/src/game/mapitems.h b/src/game/mapitems.h
index d99d6724..fb66e12d 100644
--- a/src/game/mapitems.h
+++ b/src/game/mapitems.h
@@ -53,6 +53,7 @@ enum
 	TILEFLAG_ROTATE=8,
 
 	LAYERFLAG_DETAIL=1,
+	TILESLAYERFLAG_GAME=1,
 
 	ENTITY_OFFSET=255-16*4,
 };
@@ -114,13 +115,15 @@ struct CMapItemGroup_v1
 
 struct CMapItemGroup : public CMapItemGroup_v1
 {
-	enum { CURRENT_VERSION=2 };
+	enum { CURRENT_VERSION=3 };
 
 	int m_UseClipping;
 	int m_ClipX;
 	int m_ClipY;
 	int m_ClipW;
 	int m_ClipH;
+
+	int m_aName[3];
 } ;
 
 struct CMapItemLayer
@@ -145,6 +148,8 @@ struct CMapItemLayerTilemap
 
 	int m_Image;
 	int m_Data;
+
+	int m_aName[3];
 } ;
 
 struct CMapItemLayerQuads
@@ -155,6 +160,8 @@ struct CMapItemLayerQuads
 	int m_NumQuads;
 	int m_Data;
 	int m_Image;
+
+	int m_aName[3];
 } ;
 
 struct CMapItemVersion
diff --git a/src/game/server/entities/character.cpp b/src/game/server/entities/character.cpp
index 821aade5..06c1c913 100644
--- a/src/game/server/entities/character.cpp
+++ b/src/game/server/entities/character.cpp
@@ -56,8 +56,30 @@ bool CCharacter::Spawn(CPlayer *pPlayer, vec2 Pos)
 {
 	m_EmoteStop = -1;
 	m_LastAction = -1;
-	m_ActiveWeapon = WEAPON_GUN;
-	m_LastWeapon = WEAPON_HAMMER;
+	
+	/*zCatch */
+	if(GameServer()->m_pController->IsZCatch() && g_Config.m_SvMode == 1)
+	{
+		m_ActiveWeapon = WEAPON_RIFLE;
+		m_LastWeapon = WEAPON_RIFLE;
+	}
+	else if(GameServer()->m_pController->IsZCatch() && g_Config.m_SvMode == 3)
+	{
+		m_ActiveWeapon = WEAPON_HAMMER;
+		m_LastWeapon = WEAPON_HAMMER;
+	}
+	else if(GameServer()->m_pController->IsZCatch() && g_Config.m_SvMode == 4)
+	{
+		m_ActiveWeapon = WEAPON_GRENADE;
+		m_LastWeapon = WEAPON_GRENADE;
+	}
+	else
+	{
+		m_ActiveWeapon = WEAPON_GUN;
+		m_LastWeapon = WEAPON_HAMMER;
+	}
+    /* end zCatch */
+	
 	m_QueuedWeapon = -1;
 
 	m_pPlayer = pPlayer;
@@ -115,20 +137,21 @@ void CCharacter::HandleNinja()
 	if(m_ActiveWeapon != WEAPON_NINJA)
 		return;
 
-	vec2 Direction = normalize(vec2(m_LatestInput.m_TargetX, m_LatestInput.m_TargetY));
-
-	if ((Server()->Tick() - m_Ninja.m_ActivationTick) > (g_pData->m_Weapons.m_Ninja.m_Duration * Server()->TickSpeed() / 1000))
+	/* zCatch */
+	if(GameServer()->m_pController->IsZCatch() == false || (GameServer()->m_pController->IsZCatch() && g_Config.m_SvMode == 0))
 	{
-		// time's up, return
-		m_aWeapons[WEAPON_NINJA].m_Got = false;
-		m_ActiveWeapon = m_LastWeapon;
-		if(m_ActiveWeapon == WEAPON_NINJA)
-			m_ActiveWeapon = WEAPON_GUN;
+		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;
 
-		SetWeapon(m_ActiveWeapon);
-		return;
+			SetWeapon(m_ActiveWeapon);
+			return;
+		}
 	}
-
+	/* zCatch end*/
+	
 	// force ninja Weapon
 	SetWeapon(WEAPON_NINJA);
 
@@ -183,7 +206,7 @@ void CCharacter::HandleNinja()
 				if(m_NumObjectsHit < 10)
 					m_apHitObjects[m_NumObjectsHit++] = aEnts[i];
 
-				aEnts[i]->TakeDamage(vec2(0, 10.0f), g_pData->m_Weapons.m_Ninja.m_pBase->m_Damage, m_pPlayer->GetCID(), WEAPON_NINJA);
+				aEnts[i]->TakeDamage(vec2(0, -10.0f), g_pData->m_Weapons.m_Ninja.m_pBase->m_Damage, m_pPlayer->GetCID(), WEAPON_NINJA);
 			}
 		}
 
@@ -436,8 +459,6 @@ void CCharacter::HandleWeapons()
 	//ninja
 	HandleNinja();
 
-	vec2 Direction = normalize(vec2(m_LatestInput.m_TargetX, m_LatestInput.m_TargetY));
-
 	// check reload timer
 	if(m_ReloadTimer)
 	{
@@ -490,7 +511,8 @@ void CCharacter::GiveNinja()
 	m_Ninja.m_ActivationTick = Server()->Tick();
 	m_aWeapons[WEAPON_NINJA].m_Got = true;
 	m_aWeapons[WEAPON_NINJA].m_Ammo = -1;
-	m_LastWeapon = m_ActiveWeapon;
+	if (m_ActiveWeapon != WEAPON_NINJA)
+		m_LastWeapon = m_ActiveWeapon;
 	m_ActiveWeapon = WEAPON_NINJA;
 
 	GameServer()->CreateSound(m_Pos, SOUND_PICKUP_NINJA);
@@ -531,6 +553,18 @@ void CCharacter::OnDirectInput(CNetObj_PlayerInput *pNewInput)
 	mem_copy(&m_LatestPrevInput, &m_LatestInput, sizeof(m_LatestInput));
 }
 
+void CCharacter::ResetInput()
+{
+	m_Input.m_Direction = 0;
+	m_Input.m_Hook = 0;
+	// simulate releasing the fire button
+	if((m_Input.m_Fire&1) != 0)
+		m_Input.m_Fire++;
+	m_Input.m_Fire &= INPUT_STATE_MASK;
+	m_Input.m_Jump = 0;
+	m_LatestPrevInput = m_LatestInput = m_Input;
+}
+
 void CCharacter::Tick()
 {
 	if(m_pPlayer->m_ForceBalanced)
@@ -700,11 +734,18 @@ 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;
-
-	// m_pPlayer only inflicts half damage on self
+	
+	/* zCatch */
+	bool Is_zCatch = GameServer()->m_pController->IsZCatch();
+	
 	if(From == m_pPlayer->GetCID())
-		Dmg = max(1, Dmg/2);
-
+		if(Is_zCatch && g_Config.m_SvMode != 1)
+			Dmg = 0;	//No selfdamage, except in vanilla-mode
+		// m_pPlayer only inflicts half damage on self
+		else
+			Dmg = max(1, Dmg/2);
+	/* end zCatch */
+	
 	m_DamageTaken++;
 
 	// create healthmod indicator
@@ -718,8 +759,15 @@ bool CCharacter::TakeDamage(vec2 Force, int Dmg, int From, int Weapon)
 		m_DamageTaken = 0;
 		GameServer()->CreateDamageInd(m_Pos, 0, Dmg);
 	}
-
-	if(Dmg)
+	/* zCatch*/
+	//One-Shot-One-Kill
+	if(Is_zCatch && (g_Config.m_SvMode != 0 && g_Config.m_SvMode != 2)) // all except vanilla-mode and all weapons
+	{
+		m_Health = 0;
+		m_Armor = 0;
+	}
+	/* end zCatch*/
+	else if(Dmg)
 	{
 		if(m_Armor)
 		{
@@ -748,7 +796,15 @@ bool CCharacter::TakeDamage(vec2 Force, int Dmg, int From, int Weapon)
 
 	// 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));
+	{
+		int Mask = CmaskOne(From);
+		for(int i = 0; i < MAX_CLIENTS; i++)
+		{
+			if(GameServer()->m_apPlayers[i] && GameServer()->m_apPlayers[i]->GetTeam() == TEAM_SPECTATORS && GameServer()->m_apPlayers[i]->m_SpectatorID == From)
+				Mask |= CmaskOne(i);
+		}
+		GameServer()->CreateSound(GameServer()->m_apPlayers[From]->m_ViewPos, SOUND_HIT, Mask);
+	}
 
 	// check for death
 	if(m_Health <= 0)
@@ -821,7 +877,8 @@ void CCharacter::Snap(int SnappingClient)
 
 	pCharacter->m_Direction = m_Input.m_Direction;
 
-	if(m_pPlayer->GetCID() == SnappingClient || SnappingClient == -1 || m_pPlayer->GetCID() == GameServer()->m_apPlayers[SnappingClient]->m_SpectatorID)
+	if(m_pPlayer->GetCID() == SnappingClient || SnappingClient == -1 ||
+		(!g_Config.m_SvStrictSpectateMode && m_pPlayer->GetCID() == GameServer()->m_apPlayers[SnappingClient]->m_SpectatorID))
 	{
 		pCharacter->m_Health = m_Health;
 		pCharacter->m_Armor = m_Armor;
diff --git a/src/game/server/entities/character.h b/src/game/server/entities/character.h
index 611dc427..a311a6f9 100644
--- a/src/game/server/entities/character.h
+++ b/src/game/server/entities/character.h
@@ -11,6 +11,7 @@
 
 enum
 {
+	WEAPON_ANTICAMPER = -4,
 	WEAPON_GAME = -3, // team switching etc
 	WEAPON_SELF = -2, // console kill command
 	WEAPON_WORLD = -1, // death tiles etc
@@ -43,6 +44,7 @@ public:
 
 	void OnPredictedInput(CNetObj_PlayerInput *pNewInput);
 	void OnDirectInput(CNetObj_PlayerInput *pNewInput);
+	void ResetInput();
 	void FireWeapon();
 
 	void Die(int Killer, int Weapon);
diff --git a/src/game/server/entities/laser.cpp b/src/game/server/entities/laser.cpp
index 30ed0a9b..af66fe0c 100644
--- a/src/game/server/entities/laser.cpp
+++ b/src/game/server/entities/laser.cpp
@@ -44,7 +44,6 @@ void CLaser::DoBounce()
 	}
 
 	vec2 To = m_Pos + m_Dir * m_Energy;
-	vec2 OrgTo = To;
 
 	if(GameServer()->Collision()->IntersectLine(m_Pos, To, 0x0, &To))
 	{
diff --git a/src/game/server/eventhandler.cpp b/src/game/server/eventhandler.cpp
index deb1ca4e..354bd4ab 100644
--- a/src/game/server/eventhandler.cpp
+++ b/src/game/server/eventhandler.cpp
@@ -46,7 +46,7 @@ void CEventHandler::Snap(int SnappingClient)
 	{
 		if(SnappingClient == -1 || CmaskIsSet(m_aClientMasks[i], SnappingClient))
 		{
-			NETEVENT_COMMON *ev = (NETEVENT_COMMON *)&m_aData[m_aOffsets[i]];
+			CNetEvent_Common *ev = (CNetEvent_Common *)&m_aData[m_aOffsets[i]];
 			if(SnappingClient == -1 || distance(GameServer()->m_apPlayers[SnappingClient]->m_ViewPos, vec2(ev->m_X, ev->m_Y)) < 1500.0f)
 			{
 				void *d = GameServer()->Server()->SnapNewItem(m_aTypes[i], i, m_aSizes[i]);
diff --git a/src/game/server/gamecontext.cpp b/src/game/server/gamecontext.cpp
index 82adcbef..a34890e5 100644
--- a/src/game/server/gamecontext.cpp
+++ b/src/game/server/gamecontext.cpp
@@ -13,6 +13,7 @@
 #include "gamemodes/tdm.h"
 #include "gamemodes/ctf.h"
 #include "gamemodes/mod.h"
+#include "gamemodes/zcatch.hpp"
 
 enum
 {
@@ -36,6 +37,9 @@ void CGameContext::Construct(int Resetting)
 
 	if(Resetting==NO_RESET)
 		m_pVoteOptionHeap = new CHeap();
+	
+	for(int i = 0; i < MAX_MUTES; i++)
+		m_aMutes[i].m_IP[0] = 0;
 }
 
 CGameContext::CGameContext(int Resetting)
@@ -93,7 +97,7 @@ void CGameContext::CreateDamageInd(vec2 Pos, float Angle, int Amount)
 	for(int i = 0; i < Amount; i++)
 	{
 		float f = mix(s, e, float(i+1)/float(Amount+2));
-		NETEVENT_DAMAGEIND *pEvent = (NETEVENT_DAMAGEIND *)m_Events.Create(NETEVENTTYPE_DAMAGEIND, sizeof(NETEVENT_DAMAGEIND));
+		CNetEvent_DamageInd *pEvent = (CNetEvent_DamageInd *)m_Events.Create(NETEVENTTYPE_DAMAGEIND, sizeof(CNetEvent_DamageInd));
 		if(pEvent)
 		{
 			pEvent->m_X = (int)Pos.x;
@@ -106,7 +110,7 @@ void CGameContext::CreateDamageInd(vec2 Pos, float Angle, int Amount)
 void CGameContext::CreateHammerHit(vec2 Pos)
 {
 	// create the event
-	NETEVENT_HAMMERHIT *pEvent = (NETEVENT_HAMMERHIT *)m_Events.Create(NETEVENTTYPE_HAMMERHIT, sizeof(NETEVENT_HAMMERHIT));
+	CNetEvent_HammerHit *pEvent = (CNetEvent_HammerHit *)m_Events.Create(NETEVENTTYPE_HAMMERHIT, sizeof(CNetEvent_HammerHit));
 	if(pEvent)
 	{
 		pEvent->m_X = (int)Pos.x;
@@ -118,7 +122,7 @@ void CGameContext::CreateHammerHit(vec2 Pos)
 void CGameContext::CreateExplosion(vec2 Pos, int Owner, int Weapon, bool NoDamage)
 {
 	// create the event
-	NETEVENT_EXPLOSION *pEvent = (NETEVENT_EXPLOSION *)m_Events.Create(NETEVENTTYPE_EXPLOSION, sizeof(NETEVENT_EXPLOSION));
+	CNetEvent_Explosion *pEvent = (CNetEvent_Explosion *)m_Events.Create(NETEVENTTYPE_EXPLOSION, sizeof(CNetEvent_Explosion));
 	if(pEvent)
 	{
 		pEvent->m_X = (int)Pos.x;
@@ -162,7 +166,7 @@ void create_smoke(vec2 Pos)
 void CGameContext::CreatePlayerSpawn(vec2 Pos)
 {
 	// create the event
-	NETEVENT_SPAWN *ev = (NETEVENT_SPAWN *)m_Events.Create(NETEVENTTYPE_SPAWN, sizeof(NETEVENT_SPAWN));
+	CNetEvent_Spawn *ev = (CNetEvent_Spawn *)m_Events.Create(NETEVENTTYPE_SPAWN, sizeof(CNetEvent_Spawn));
 	if(ev)
 	{
 		ev->m_X = (int)Pos.x;
@@ -173,7 +177,7 @@ void CGameContext::CreatePlayerSpawn(vec2 Pos)
 void CGameContext::CreateDeath(vec2 Pos, int ClientID)
 {
 	// create the event
-	NETEVENT_DEATH *pEvent = (NETEVENT_DEATH *)m_Events.Create(NETEVENTTYPE_DEATH, sizeof(NETEVENT_DEATH));
+	CNetEvent_Death *pEvent = (CNetEvent_Death *)m_Events.Create(NETEVENTTYPE_DEATH, sizeof(CNetEvent_Death));
 	if(pEvent)
 	{
 		pEvent->m_X = (int)Pos.x;
@@ -188,7 +192,7 @@ void CGameContext::CreateSound(vec2 Pos, int Sound, int Mask)
 		return;
 
 	// create a sound
-	NETEVENT_SOUNDWORLD *pEvent = (NETEVENT_SOUNDWORLD *)m_Events.Create(NETEVENTTYPE_SOUNDWORLD, sizeof(NETEVENT_SOUNDWORLD), Mask);
+	CNetEvent_SoundWorld *pEvent = (CNetEvent_SoundWorld *)m_Events.Create(NETEVENTTYPE_SOUNDWORLD, sizeof(CNetEvent_SoundWorld), Mask);
 	if(pEvent)
 	{
 		pEvent->m_X = (int)Pos.x;
@@ -225,7 +229,7 @@ void CGameContext::SendChat(int ChatterClientID, int Team, const char *pText)
 		str_format(aBuf, sizeof(aBuf), "%d:%d:%s: %s", ChatterClientID, Team, Server()->ClientName(ChatterClientID), pText);
 	else
 		str_format(aBuf, sizeof(aBuf), "*** %s", pText);
-	Console()->Print(IConsole::OUTPUT_LEVEL_ADDINFO, "chat", aBuf);
+	Console()->Print(IConsole::OUTPUT_LEVEL_ADDINFO, Team!=CHAT_ALL?"teamchat":"chat", aBuf);
 
 	if(Team == CHAT_ALL)
 	{
@@ -343,7 +347,8 @@ void CGameContext::SendVoteStatus(int ClientID, int Total, int Yes, int No)
 
 void CGameContext::AbortVoteKickOnDisconnect(int ClientID)
 {
-	if(m_VoteCloseTime && !str_comp_num(m_aVoteCommand, "kick ", 5) && str_toint(&m_aVoteCommand[5]) == ClientID)
+	if(m_VoteCloseTime && ((!str_comp_num(m_aVoteCommand, "kick ", 5) && str_toint(&m_aVoteCommand[5]) == ClientID) ||
+		(!str_comp_num(m_aVoteCommand, "set_team ", 9) && str_toint(&m_aVoteCommand[9]) == ClientID)))
 		m_VoteCloseTime = -1;
 }
 
@@ -421,7 +426,10 @@ void CGameContext::OnTick()
 				bool aVoteChecked[MAX_CLIENTS] = {0};
 				for(int i = 0; i < MAX_CLIENTS; i++)
 				{
-					if(!m_apPlayers[i] || m_apPlayers[i]->GetTeam() == TEAM_SPECTATORS || aVoteChecked[i])	// don't count in votes by spectators
+					/* zCatch - Allow voting from players in spectators (needed or the last 2 players ingame can kick the whole server),
+					 * but deny votes from players who are explicit in spec
+					*/
+					if(!m_apPlayers[i] || m_apPlayers[i]->m_SpecExplicit == 1 || aVoteChecked[i])	// don't count in votes by spectators
 						continue;
 
 					int ActVote = m_apPlayers[i]->m_Vote;
@@ -507,6 +515,74 @@ void CGameContext::OnClientEnter(int ClientID)
 {
 	//world.insert_entity(&players[client_id]);
 	m_apPlayers[ClientID]->Respawn();
+	
+	/* begin zCatch */
+	int leader_id = -1;
+	int StartTeam = m_pController->ClampTeam(1);
+	
+	if(m_pController->IsZCatch())
+	{
+		int num = 0;
+		
+		for(int i=0; i<MAX_CLIENTS; i++)
+		{
+			if(IsClientReady(i))
+				num++;
+		}
+		if(num < 3)
+			m_pController->EndRound();
+		
+		if(g_Config.m_SvAllowJoin == 1)
+		{
+			m_apPlayers[ClientID]->m_CatchedBy = ZCATCH_NOT_CATCHED;
+			m_apPlayers[ClientID]->m_SpecExplicit = (num < 3) ? 0 : 1;
+			StartTeam = (num < 3) ? m_pController->ClampTeam(1) : TEAM_SPECTATORS;
+			SendBroadcast("You can join the game", ClientID);
+			
+		}
+		else if(g_Config.m_SvAllowJoin == 2)
+		{
+			int num2 = 0, num_prev = 0;
+			
+			for(int i = 0; i < MAX_CLIENTS; i++)
+			{
+				if(m_apPlayers[i])
+				{
+					num2 = 0;
+					for(int j = 0; j < MAX_CLIENTS; j++)
+		   			{
+			    			if(m_apPlayers[j] && m_apPlayers[j]->m_CatchedBy == i)
+				   			num2++;
+		    			}
+		    			if(num2 > num_prev)
+		   	 		{
+			    			leader_id = i;
+			    			num_prev = num2;
+		    			}
+		    		}
+		    	}
+		    	
+		    	if(leader_id > -1)
+			{
+				m_apPlayers[ClientID]->m_CatchedBy = leader_id;
+				m_apPlayers[ClientID]->m_SpecExplicit = 0;
+				m_apPlayers[ClientID]->m_SpectatorID = leader_id;
+				StartTeam = TEAM_SPECTATORS;
+			}
+			else
+			{
+				m_apPlayers[ClientID]->m_CatchedBy = ZCATCH_NOT_CATCHED;
+				m_apPlayers[ClientID]->m_SpecExplicit = 0;
+			}
+		}
+		else
+			StartTeam = m_pController->GetAutoTeam(ClientID);
+	}
+	
+	m_apPlayers[ClientID]->SetTeamDirect(StartTeam);
+	
+	/* end zCatch */
+	
 	char aBuf[512];
 	str_format(aBuf, sizeof(aBuf), "'%s' entered and joined the %s", Server()->ClientName(ClientID), m_pController->GetTeamName(m_apPlayers[ClientID]->GetTeam()));
 	SendChat(-1, CGameContext::CHAT_ALL, aBuf);
@@ -515,6 +591,21 @@ void CGameContext::OnClientEnter(int ClientID)
 	Console()->Print(IConsole::OUTPUT_LEVEL_DEBUG, "game", aBuf);
 
 	m_VoteUpdate = true;
+	
+	/* zCatch begin */
+   	if(m_pController->IsZCatch())
+	{
+	    SendChatTarget(ClientID, "Welcome to zCatch!");
+	    SendChatTarget(ClientID, "type /cmdlist to get all commands");
+	    SendChatTarget(ClientID, "type /help for instructions");
+	    if(g_Config.m_SvAllowJoin == 2 && leader_id > -1)
+	    {
+	    	char buf[128];
+	    	str_format(buf, sizeof(buf), "You will join the game when %s dies", Server()->ClientName(leader_id));
+	    	SendChatTarget(ClientID, buf);	
+	    }
+	}
+	/* zCatch end */
 }
 
 void CGameContext::OnClientConnected(int ClientID)
@@ -599,8 +690,66 @@ void CGameContext::OnMessage(int MsgID, CUnpacker *pUnpacker, int ClientID)
 				*pMessage = ' ';
 			pMessage++;
 		}
+		
+		//Check if the player is muted
+		char aAddrStr[NETADDR_MAXSTRSIZE] = {0};
+		Server()->GetClientAddr(ClientID, aAddrStr, sizeof(aAddrStr));
+		int Pos;
+        if((Pos = Muted(aAddrStr)) > -1)
+		{
+			char aBuf[128];
+			int Expires = (m_aMutes[Pos].m_Expires - Server()->Tick())/Server()->TickSpeed(); 
+			str_format(aBuf, sizeof(aBuf), "You are muted for %d minutes and %d seconds.", Expires/60, Expires%60);
+			SendChatTarget(ClientID, aBuf);
+			return;
+		}
+		else if(g_Config.m_SvMuteDuration && ((pPlayer->m_ChatTicks += g_Config.m_SvChatValue) > g_Config.m_SvChatThreshold)) //is he spamming?
+		{
+			AddMute(ClientID, g_Config.m_SvMuteDuration);
+			pPlayer->m_ChatTicks = 0;
+			return;
+		}
 
-		SendChat(ClientID, Team, pMsg->m_pMessage);
+		/* begin zCatch*/
+		if(!str_comp("/info", pMsg->m_pMessage) || !str_comp("/about", pMsg->m_pMessage))
+		{
+			char aBuf[128];
+			str_format(aBuf, sizeof(aBuf), "zCatch version %s by erd and Teetime. Type /cmdlist for all commands.", ZCATCH_VERSION);
+			SendChatTarget(ClientID, " ");
+			SendChatTarget(ClientID, aBuf);
+		}
+		else if(!str_comp("/cmdlist", pMsg->m_pMessage))
+		{
+			SendChatTarget(ClientID, " ");
+			SendChatTarget(ClientID, "/info or /about - see information about author.");
+			SendChatTarget(ClientID, "/help - learn how to play.");
+			SendChatTarget(ClientID, "/follow 1 or /follow 0 - Enables/Disables following of the catcher.");
+		}
+		else if(!str_comp("/help", pMsg->m_pMessage))
+		{
+			SendChatTarget(ClientID, " ");
+			SendChatTarget(ClientID, "The winner is the tee which is left over at the end.");
+			SendChatTarget(ClientID, "If you die, all players that you killed will respawn.");
+			SendChatTarget(ClientID, "So the only way to win is to kill every player without beeing killed.");
+			SendChatTarget(ClientID, "Have fun!");
+		}
+		else if(!str_comp("/follow 0", pMsg->m_pMessage))
+		{
+			pPlayer->m_PlayerWantToFollowCatcher = 0;
+			pPlayer->m_SpectatorID = SPEC_FREEVIEW;
+			SendChatTarget(ClientID, "Follow of catcher disabled.");
+		}
+		else if(!str_comp("/follow 1", pMsg->m_pMessage))
+		{
+			pPlayer->m_PlayerWantToFollowCatcher = 1;
+			SendChatTarget(ClientID, "Follow of catcher enabled.");
+		}	
+		else if(!str_comp_num("/", pMsg->m_pMessage, 1))
+			SendChatTarget(ClientID, "Unknown command.");
+		else
+			SendChat(ClientID, Team, pMsg->m_pMessage);
+
+		/* end zCatch */
 	}
 	else if(MsgID == NETMSGTYPE_CL_CALLVOTE)
 	{
@@ -609,7 +758,8 @@ void CGameContext::OnMessage(int MsgID, CUnpacker *pUnpacker, int ClientID)
 
 		int64 Now = Server()->Tick();
 		pPlayer->m_LastVoteTry = Now;
-		if(pPlayer->GetTeam() == TEAM_SPECTATORS)
+		// zCatch - Only People who are explicit in Spectators can't vote!
+		if(pPlayer->m_SpecExplicit == 1) //zCatch
 		{
 			SendChatTarget(ClientID, "Spectators aren't allowed to start a vote.");
 			return;
@@ -672,7 +822,7 @@ void CGameContext::OnMessage(int MsgID, CUnpacker *pUnpacker, int ClientID)
 			{
 				int PlayerNum = 0;
 				for(int i = 0; i < MAX_CLIENTS; ++i)
-					if(m_apPlayers[i] && m_apPlayers[i]->GetTeam() != TEAM_SPECTATORS)
+					if(m_apPlayers[i] && m_apPlayers[i]->m_SpecExplicit != 1) // zCatch - Count all Players who are not explicit in spectator
 						++PlayerNum;
 
 				if(PlayerNum < g_Config.m_SvVoteKickMin)
@@ -691,12 +841,12 @@ void CGameContext::OnMessage(int MsgID, CUnpacker *pUnpacker, int ClientID)
 			}
 			if(KickID == ClientID)
 			{
-				SendChatTarget(ClientID, "You cant kick yourself");
+				SendChatTarget(ClientID, "You can't kick yourself");
 				return;
 			}
 			if(Server()->IsAuthed(KickID))
 			{
-				SendChatTarget(ClientID, "You cant kick admins");
+				SendChatTarget(ClientID, "You can't kick admins");
 				char aBufKick[128];
 				str_format(aBufKick, sizeof(aBufKick), "'%s' called for vote to kick you", Server()->ClientName(ClientID));
 				SendChatTarget(KickID, aBufKick);
@@ -731,13 +881,13 @@ void CGameContext::OnMessage(int MsgID, CUnpacker *pUnpacker, int ClientID)
 			}
 			if(SpectateID == ClientID)
 			{
-				SendChatTarget(ClientID, "You cant move yourself");
+				SendChatTarget(ClientID, "You can't move yourself");
 				return;
 			}
 
 			str_format(aChatmsg, sizeof(aChatmsg), "'%s' called for vote to move '%s' to spectators (%s)", Server()->ClientName(ClientID), Server()->ClientName(SpectateID), pReason);
 			str_format(aDesc, sizeof(aDesc), "move '%s' to spectators", Server()->ClientName(SpectateID));
-			str_format(aCmd, sizeof(aCmd), "set_team %d -1", SpectateID);
+			str_format(aCmd, sizeof(aCmd), "set_team %d -1 %d", SpectateID, g_Config.m_SvVoteSpectateRejoindelay);
 		}
 
 		if(aCmd[0])
@@ -773,17 +923,45 @@ void CGameContext::OnMessage(int MsgID, CUnpacker *pUnpacker, int ClientID)
 		if(pPlayer->GetTeam() == pMsg->m_Team || (g_Config.m_SvSpamprotection && pPlayer->m_LastSetTeam && pPlayer->m_LastSetTeam+Server()->TickSpeed()*3 > Server()->Tick()))
 			return;
 
+		if(pPlayer->m_TeamChangeTick > Server()->Tick())
+		{
+			pPlayer->m_LastSetTeam = Server()->Tick();
+			int TimeLeft = (pPlayer->m_TeamChangeTick - Server()->Tick())/Server()->TickSpeed();
+			char aBuf[128];
+			str_format(aBuf, sizeof(aBuf), "Time to wait before changing team: %02d:%02d", TimeLeft/60, TimeLeft%60);
+			SendBroadcast(aBuf, ClientID);
+			return;
+		}
+
 		// Switch team on given client and kill/respawn him
 		if(m_pController->CanJoinTeam(pMsg->m_Team, ClientID))
 		{
-			if(m_pController->CanChangeTeam(pPlayer, pMsg->m_Team))
+			if(m_pController->CanChangeTeam(pPlayer, pMsg->m_Team) && !m_pController->IsZCatch()) //zCatch)
 			{
 				pPlayer->m_LastSetTeam = Server()->Tick();
 				if(pPlayer->GetTeam() == TEAM_SPECTATORS || pMsg->m_Team == TEAM_SPECTATORS)
 					m_VoteUpdate = true;
 				pPlayer->SetTeam(pMsg->m_Team);
 				(void)m_pController->CheckTeamBalance();
+				pPlayer->m_TeamChangeTick = Server()->Tick();
+			}
+			/* begin zCatch*/			
+			else if(m_pController->IsZCatch())
+			{	
+				if(pPlayer->m_CatchedBy >= 0)
+				{
+					char buf[256];
+					str_format(buf, sizeof(buf), "You will join automatically when \"%s\" dies.", Server()->ClientName(pPlayer->m_CatchedBy));
+					SendChatTarget(ClientID, buf);
+					return;
+				}
+				else if(pPlayer->m_CatchedBy == ZCATCH_NOT_CATCHED)
+				{
+					pPlayer->m_LastSetTeam = Server()->Tick();
+					pPlayer->SetTeam(pMsg->m_Team);
+				}
 			}
+            /* end zCatch*/
 			else
 				SendBroadcast("Teams must be balanced, please join other team", ClientID);
 		}
@@ -945,14 +1123,75 @@ void CGameContext::OnMessage(int MsgID, CUnpacker *pUnpacker, int ClientID)
 	}
 	else if (MsgID == NETMSGTYPE_CL_KILL && !m_World.m_Paused)
 	{
-		if(pPlayer->m_LastKill && pPlayer->m_LastKill+Server()->TickSpeed()*3 > Server()->Tick())
-			return;
-
-		pPlayer->m_LastKill = Server()->Tick();
-		pPlayer->KillCharacter(WEAPON_SELF);
+		/* begin zCatch*/
+		if(pPlayer->m_LastKill && pPlayer->m_LastKill + Server()->TickSpeed()*15 > Server()->Tick())
+		{	
+			if((pPlayer->GetTeam() == TEAM_SPECTATORS) || (pPlayer->m_LastKillTry && pPlayer->m_LastKillTry+Server()->TickSpeed()*2 > Server()->Tick()))
+				return;			
+			SendChatTarget(ClientID, "Only one kill in 15sec is allowed.");
+			pPlayer->m_LastKillTry = Server()->Tick();
+		}
+		else
+		{
+			pPlayer->m_LastKill = Server()->Tick();
+			pPlayer->KillCharacter(WEAPON_SELF);
+			pPlayer->m_Deaths++;
+		}
+		/* end zCatch*/
 	}
 }
 
+void CGameContext::AddMute(const char* IP, int Secs)
+{
+	int Pos = Muted(IP);
+	if(Pos > -1)	
+		m_aMutes[Pos].m_Expires = Server()->TickSpeed() * Secs + Server()->Tick();	// overwrite mute
+	else
+		for(int i = 0; i < MAX_MUTES; i++)	// find free slot
+			if(!m_aMutes[i].m_IP[0])
+			{
+				str_copy(m_aMutes[i].m_IP, IP, sizeof(m_aMutes[i].m_IP));
+				m_aMutes[i].m_Expires = Server()->TickSpeed() * Secs + Server()->Tick();
+				break;
+			}
+}
+
+void CGameContext::AddMute(int ClientID, int Secs)
+{
+	char aAddrStr[NETADDR_MAXSTRSIZE] = {0};
+	Server()->GetClientAddr(ClientID, aAddrStr, sizeof(aAddrStr));
+	AddMute(aAddrStr, Secs);
+	
+	char aBuf[128];
+	if(Secs > 0)
+		str_format(aBuf, sizeof(aBuf), "\"%s\" has been muted for %d seconds.", Server()->ClientName(ClientID), Secs); 
+	else
+		str_format(aBuf, sizeof(aBuf), "\"%s\" has been unmuted.", Server()->ClientName(ClientID));
+	SendChatTarget(-1, aBuf);
+}
+
+int CGameContext::Muted(const char* IP)
+{	
+	CleanMutes();
+	int Pos = -1;
+	if(!IP[0])
+		return -1;
+	for(int i = 0; i < MAX_MUTES; i++)
+		if(!str_comp_num(IP, m_aMutes[i].m_IP, sizeof(m_aMutes[i].m_IP)))
+		{
+			Pos = i;
+			break;
+		}
+	return Pos;
+}
+
+void CGameContext::CleanMutes()
+{
+	for(int i = 0; i < MAX_MUTES; i++)
+		if(m_aMutes[i].m_Expires < Server()->Tick())
+			m_aMutes[i].m_IP[0] = 0;
+}
+
 void CGameContext::ConTuneParam(IConsole::IResult *pResult, void *pUserData)
 {
 	CGameContext *pSelf = (CGameContext *)pUserData;
@@ -1024,6 +1263,9 @@ void CGameContext::ConSetTeam(IConsole::IResult *pResult, void *pUserData)
 	CGameContext *pSelf = (CGameContext *)pUserData;
 	int ClientID = clamp(pResult->GetInteger(0), 0, (int)MAX_CLIENTS-1);
 	int Team = clamp(pResult->GetInteger(1), -1, 1);
+	int Delay = 0;
+	if(pResult->NumArguments() > 2)
+		Delay = pResult->GetInteger(2);
 
 	char aBuf[256];
 	str_format(aBuf, sizeof(aBuf), "moved client %d to team %d", ClientID, Team);
@@ -1032,6 +1274,7 @@ void CGameContext::ConSetTeam(IConsole::IResult *pResult, void *pUserData)
 	if(!pSelf->m_apPlayers[ClientID])
 		return;
 
+	pSelf->m_apPlayers[ClientID]->m_TeamChangeTick = pSelf->Server()->Tick()+pSelf->Server()->TickSpeed()*Delay*60;
 	pSelf->m_apPlayers[ClientID]->SetTeam(Team);
 	(void)pSelf->m_pController->CheckTeamBalance();
 }
@@ -1248,7 +1491,9 @@ void CGameContext::ConForceVote(IConsole::IResult *pResult, void *pUserData)
 			return;
 		}
 
-		str_format(aBuf, sizeof(aBuf), "set_team %d -1", SpectateID);
+		str_format(aBuf, sizeof(aBuf), "admin moved '%s' to spectator (%s)", pSelf->Server()->ClientName(SpectateID), pReason);
+		pSelf->SendChatTarget(-1, aBuf);
+		str_format(aBuf, sizeof(aBuf), "set_team %d -1 %d", SpectateID, g_Config.m_SvVoteSpectateRejoindelay);
 		pSelf->Console()->ExecuteLine(aBuf);
 	}
 }
@@ -1274,6 +1519,8 @@ void CGameContext::ConVote(IConsole::IResult *pResult, void *pUserData)
 	else if(str_comp_nocase(pResult->GetString(0), "no") == 0)
 		pSelf->m_VoteEnforce = CGameContext::VOTE_ENFORCE_NO;
 	char aBuf[256];
+	str_format(aBuf, sizeof(aBuf), "admin forced vote %s", pResult->GetString(0));
+	pSelf->SendChatTarget(-1, aBuf);
 	str_format(aBuf, sizeof(aBuf), "forcing vote %s", pResult->GetString(0));
 	pSelf->Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "server", aBuf);
 }
@@ -1292,28 +1539,81 @@ void CGameContext::ConchainSpecialMotdupdate(IConsole::IResult *pResult, void *p
 	}
 }
 
-void CGameContext::OnConsoleInit()
+void CGameContext::ConMute(IConsole::IResult *pResult, void *pUserData)
 {
-	m_pServer = Kernel()->RequestInterface<IServer>();
-	m_pConsole = Kernel()->RequestInterface<IConsole>();
+	CGameContext *pSelf = (CGameContext *)pUserData;
+	int CID = pResult->GetInteger(0);
+	if(CID < 0 || CID >= MAX_CLIENTS || !pSelf->m_apPlayers[CID])
+		return;
+		
+	pSelf->AddMute(CID, pResult->GetInteger(1));
+}
 
-	Console()->Register("tune", "si", CFGFLAG_SERVER, ConTuneParam, this, "");
-	Console()->Register("tune_reset", "", CFGFLAG_SERVER, ConTuneReset, this, "");
-	Console()->Register("tune_dump", "", CFGFLAG_SERVER, ConTuneDump, this, "");
+void CGameContext::ConMutes(IConsole::IResult *pResult, void *pUserData)
+{
+	CGameContext *pSelf = (CGameContext *)pUserData;
+	char aBuf[128];
+	int Sec, Count = 0;
+	pSelf->CleanMutes();
+	for(int i = 0; i < MAX_MUTES; i++)
+	{
+		if(pSelf->m_aMutes[i].m_IP[0] == 0)
+			continue;
+		
+		Sec = (pSelf->m_aMutes[i].m_Expires - pSelf->Server()->Tick())/pSelf->Server()->TickSpeed();
+		str_format(aBuf, sizeof(aBuf), "#%d: %s for %d minutes and %d sec", i, pSelf->m_aMutes[i].m_IP, Sec/60, Sec%60);
+		pSelf->Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "Server", aBuf);
+		Count++;
+	}
+	str_format(aBuf, sizeof(aBuf), "%d mute(s)", Count);
+	pSelf->Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "Server", aBuf);	
+}
 
-	Console()->Register("change_map", "?r", CFGFLAG_SERVER|CFGFLAG_STORE, ConChangeMap, this, "");
-	Console()->Register("restart", "?i", CFGFLAG_SERVER|CFGFLAG_STORE, ConRestart, this, "");
-	Console()->Register("broadcast", "r", CFGFLAG_SERVER, ConBroadcast, this, "");
-	Console()->Register("say", "r", CFGFLAG_SERVER, ConSay, this, "");
-	Console()->Register("set_team", "ii", CFGFLAG_SERVER, ConSetTeam, this, "");
-	Console()->Register("set_team_all", "i", CFGFLAG_SERVER, ConSetTeamAll, this, "");
+void CGameContext::ConUnmute(IConsole::IResult *pResult, void *pUserData)
+{
+	CGameContext *pSelf = (CGameContext *)pUserData;
+	int MuteID = pResult->GetInteger(0);
+	char aBuf[128];
+	
+	if(MuteID < 0 || MuteID >= MAX_MUTES)
+		return;
+	
+	if(pSelf->Muted(pSelf->m_aMutes[MuteID].m_IP) > -1)
+	{
+		str_format(aBuf, sizeof(aBuf), "unmuted %s", pSelf->m_aMutes[MuteID].m_IP);
+		pSelf->Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "Server", aBuf);
+		pSelf->AddMute(pSelf->m_aMutes[MuteID].m_IP, 0);
+	}
+	else
+		pSelf->Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "Server", "mute not found");
+}
 
-	Console()->Register("add_vote", "sr", CFGFLAG_SERVER, ConAddVote, this, "");
-	Console()->Register("remove_vote", "s", CFGFLAG_SERVER, ConRemoveVote, this, "");
-	Console()->Register("force_vote", "ss?r", CFGFLAG_SERVER, ConForceVote, this, "");
-	Console()->Register("clear_votes", "", CFGFLAG_SERVER, ConClearVotes, this, "");
-	Console()->Register("vote", "r", CFGFLAG_SERVER, ConVote, this, "");
+void CGameContext::OnConsoleInit()
+{
+	m_pServer = Kernel()->RequestInterface<IServer>();
+	m_pConsole = Kernel()->RequestInterface<IConsole>();
 
+	Console()->Register("tune", "si", CFGFLAG_SERVER, ConTuneParam, this, "Tune variable to value");
+	Console()->Register("tune_reset", "", CFGFLAG_SERVER, ConTuneReset, this, "Reset tuning");
+	Console()->Register("tune_dump", "", CFGFLAG_SERVER, ConTuneDump, this, "Dump tuning");
+
+	Console()->Register("change_map", "?r", CFGFLAG_SERVER|CFGFLAG_STORE, ConChangeMap, this, "Change map");
+	Console()->Register("restart", "?i", CFGFLAG_SERVER|CFGFLAG_STORE, ConRestart, this, "Restart in x seconds");
+	Console()->Register("broadcast", "r", CFGFLAG_SERVER, ConBroadcast, this, "Broadcast message");
+	Console()->Register("say", "r", CFGFLAG_SERVER, ConSay, this, "Say in chat");
+	Console()->Register("set_team", "ii?i", CFGFLAG_SERVER, ConSetTeam, this, "Set team of player to team");
+	Console()->Register("set_team_all", "i", CFGFLAG_SERVER, ConSetTeamAll, this, "Set team of all players to team");
+
+	Console()->Register("add_vote", "sr", CFGFLAG_SERVER, ConAddVote, this, "Add a voting option");
+	Console()->Register("remove_vote", "s", CFGFLAG_SERVER, ConRemoveVote, this, "remove a voting option");
+	Console()->Register("force_vote", "ss?r", CFGFLAG_SERVER, ConForceVote, this, "Force a voting option");
+	Console()->Register("clear_votes", "", CFGFLAG_SERVER, ConClearVotes, this, "Clears the voting options");
+	Console()->Register("vote", "r", CFGFLAG_SERVER, ConVote, this, "Force a vote to yes/no");
+
+	Console()->Register("mute", "ii", CFGFLAG_SERVER, ConMute, this, "Mutes a player for x sec");
+	Console()->Register("unmute", "i", CFGFLAG_SERVER, ConUnmute, this, "Removes a mute by its index");
+	Console()->Register("mutes", "", CFGFLAG_SERVER, ConMutes, this, "Show all mutes");
+		
 	Console()->Chain("sv_motd", ConchainSpecialMotdupdate, this);
 }
 
@@ -1344,6 +1644,8 @@ void CGameContext::OnInit(/*class IKernel *pKernel*/)
 		m_pController = new CGameControllerCTF(this);
 	else if(str_comp(g_Config.m_SvGametype, "tdm") == 0)
 		m_pController = new CGameControllerTDM(this);
+	else if(str_comp_nocase(g_Config.m_SvGametype, "zcatch") == 0)
+		m_pController = new CGameController_zCatch(this);
 	else
 		m_pController = new CGameControllerDM(this);
 
diff --git a/src/game/server/gamecontext.h b/src/game/server/gamecontext.h
index 6288850d..267c388a 100644
--- a/src/game/server/gamecontext.h
+++ b/src/game/server/gamecontext.h
@@ -15,6 +15,9 @@
 #include "gameworld.h"
 #include "player.h"
 
+#define MAX_MUTES 25
+#define ZCATCH_VERSION "0.4.2 BETA"
+
 /*
 	Tick
 		Game Context (CGameContext::tick)
@@ -60,6 +63,10 @@ class CGameContext : public IGameServer
 	static void ConClearVotes(IConsole::IResult *pResult, void *pUserData);
 	static void ConVote(IConsole::IResult *pResult, void *pUserData);
 	static void ConchainSpecialMotdupdate(IConsole::IResult *pResult, void *pUserData, IConsole::FCommandCallback pfnCallback, void *pCallbackUserData);
+	
+	static void ConMute(IConsole::IResult *pResult, void *pUserData);
+	static void ConUnmute(IConsole::IResult *pResult, void *pUserData);
+	static void ConMutes(IConsole::IResult *pResult, void *pUserData);
 
 	CGameContext(int Resetting);
 	void Construct(int Resetting);
@@ -128,6 +135,23 @@ public:
 		CHAT_RED=0,
 		CHAT_BLUE=1
 	};
+	
+	enum
+	{
+		ZCATCH_NOT_CATCHED = -1,
+	}; 
+	
+	struct CMutes
+	{
+		char m_IP[NETADDR_MAXSTRSIZE];
+		int m_Expires;
+	}; 
+	CMutes m_aMutes[MAX_MUTES];
+	// helper functions
+	void AddMute(const char* IP, int Secs);
+	void AddMute(int ClientID, int Secs);
+	int Muted(const char* IP);
+	void CleanMutes();
 
 	// network
 	void SendChatTarget(int To, const char *pText);
diff --git a/src/game/server/gamecontroller.cpp b/src/game/server/gamecontroller.cpp
index 6685bba3..eb9dc9b5 100644
--- a/src/game/server/gamecontroller.cpp
+++ b/src/game/server/gamecontroller.cpp
@@ -63,10 +63,25 @@ void IGameController::EvaluateSpawnType(CSpawnEval *pEval, int Type)
 	for(int i = 0; i < m_aNumSpawnPoints[Type]; i++)
 	{
 		// check if the position is occupado
-		if(GameServer()->m_World.FindEntities(m_aaSpawnPoints[Type][i], 64, 0, 1, CGameWorld::ENTTYPE_CHARACTER))
-			continue;
+		CCharacter *aEnts[MAX_CLIENTS];
+		int Num = GameServer()->m_World.FindEntities(m_aaSpawnPoints[Type][i], 64, (CEntity**)aEnts, MAX_CLIENTS, CGameWorld::ENTTYPE_CHARACTER);
+		vec2 Positions[5] = { vec2(0.0f, 0.0f), vec2(-32.0f, 0.0f), vec2(0.0f, -32.0f), vec2(32.0f, 0.0f), vec2(0.0f, 32.0f) };	// start, left, up, right, down
+		int Result = -1;
+		for(int Index = 0; Index < 5 && Result == -1; ++Index)
+		{
+			Result = Index;
+			for(int c = 0; c < Num; ++c)
+				if(GameServer()->Collision()->CheckPoint(m_aaSpawnPoints[Type][i]+Positions[Index]) ||
+					distance(aEnts[c]->m_Pos, m_aaSpawnPoints[Type][i]+Positions[Index]) <= aEnts[c]->m_ProximityRadius)
+				{
+					Result = -1;
+					break;
+				}
+		}
+		if(Result == -1)
+			continue;	// try next spawn point
 
-		vec2 P = m_aaSpawnPoints[Type][i];
+		vec2 P = m_aaSpawnPoints[Type][i]+Positions[Result];
 		float S = EvaluateSpawnPos(pEval, P);
 		if(!pEval->m_Got || pEval->m_Score > S)
 		{
@@ -77,47 +92,6 @@ void IGameController::EvaluateSpawnType(CSpawnEval *pEval, int Type)
 	}
 }
 
-void IGameController::FindFreeSpawn(CSpawnEval *pEval, int Type)
-{
-	// pick the spawn point that is least occupied and has free space for spawning around it
-	for(int i = 0; i < m_aNumSpawnPoints[Type]; i++)
-	{
-
-		CCharacter *aEnts[MAX_CLIENTS];
-		int Num = GameServer()->m_World.FindEntities(m_aaSpawnPoints[Type][i], 64, (CEntity**)aEnts, MAX_CLIENTS, CGameWorld::ENTTYPE_CHARACTER);
-		float Score = 0.0f;
-		for(int c = 0; c < Num; ++c)
-			Score += 96.0f - distance(aEnts[c]->m_Pos, m_aaSpawnPoints[Type][i]);
-
-		if(!pEval->m_Got || pEval->m_Score > Score)
-		{
-			// start, left, up, right, down
-			vec2 Positions[5] = { vec2(0.0f, 0.0f), vec2(-32.0f, 0.0f), vec2(0.0f, -32.0f), vec2(32.0f, 0.0f), vec2(0.0f, 32.0f) };
-
-			// check for free space
-			int Result = -1;
-			for(int Index = 0; Index < 5 && Result == -1; ++Index)
-			{
-				Result = Index;
-				for(int c = 0; c < Num; ++c)
-					if(GameServer()->Collision()->CheckPoint(m_aaSpawnPoints[Type][i]+Positions[Index]) ||
-						distance(aEnts[c]->m_Pos, m_aaSpawnPoints[Type][i]+Positions[Index]) <= aEnts[c]->m_ProximityRadius)
-					{
-						Result = -1;
-						break;
-					}
-			}
-
-			if(Result == -1)
-				continue;	// try next spawn point
-
-			pEval->m_Got = true;
-			pEval->m_Score = Score;
-			pEval->m_Pos = m_aaSpawnPoints[Type][i]+Positions[Result];
-		}
-	}
-}
-
 bool IGameController::CanSpawn(int Team, vec2 *pOutPos)
 {
 	CSpawnEval Eval;
@@ -146,28 +120,6 @@ bool IGameController::CanSpawn(int Team, vec2 *pOutPos)
 		EvaluateSpawnType(&Eval, 2);
 	}
 
-	// handle crappy maps
-	if(!Eval.m_Got)
-	{
-		if(IsTeamplay())
-		{
-			// first try own team spawn, then normal spawn and then enemy
-			FindFreeSpawn(&Eval, 1+(Team&1));
-			if(!Eval.m_Got)
-			{
-				FindFreeSpawn(&Eval, 0);
-				if(!Eval.m_Got)
-					FindFreeSpawn(&Eval, 1+((Team+1)&1));
-			}
-		}
-		else
-		{
-			FindFreeSpawn(&Eval, 0);
-			FindFreeSpawn(&Eval, 1);
-			FindFreeSpawn(&Eval, 2);
-		}
-	}
-
 	*pOutPos = Eval.m_Pos;
 	return Eval.m_Got;
 }
@@ -184,30 +136,35 @@ bool IGameController::OnEntity(int Index, vec2 Pos)
 		m_aaSpawnPoints[1][m_aNumSpawnPoints[1]++] = Pos;
 	else if(Index == ENTITY_SPAWN_BLUE)
 		m_aaSpawnPoints[2][m_aNumSpawnPoints[2]++] = Pos;
-	else if(Index == ENTITY_ARMOR_1)
-		Type = POWERUP_ARMOR;
-	else if(Index == ENTITY_HEALTH_1)
-		Type = POWERUP_HEALTH;
-	else if(Index == ENTITY_WEAPON_SHOTGUN)
-	{
-		Type = POWERUP_WEAPON;
-		SubType = WEAPON_SHOTGUN;
-	}
-	else if(Index == ENTITY_WEAPON_GRENADE)
-	{
-		Type = POWERUP_WEAPON;
-		SubType = WEAPON_GRENADE;
-	}
-	else if(Index == ENTITY_WEAPON_RIFLE)
-	{
-		Type = POWERUP_WEAPON;
-		SubType = WEAPON_RIFLE;
-	}
-	else if(Index == ENTITY_POWERUP_NINJA && g_Config.m_SvPowerups)
-	{
-		Type = POWERUP_NINJA;
-		SubType = WEAPON_NINJA;
-	}
+	/* zCatch */
+   	else if(!GameServer()->m_pController->IsZCatch() || g_Config.m_SvMode == 0)
+    	{
+        if(Index == ENTITY_ARMOR_1)
+            Type = POWERUP_ARMOR;
+        else if(Index == ENTITY_HEALTH_1)
+            Type = POWERUP_HEALTH;
+        else if(Index == ENTITY_WEAPON_SHOTGUN)
+        {
+            Type = POWERUP_WEAPON;
+            SubType = WEAPON_SHOTGUN;
+        }
+        else if(Index == ENTITY_WEAPON_GRENADE)
+        {
+            Type = POWERUP_WEAPON;
+            SubType = WEAPON_GRENADE;
+        }
+        else if(Index == ENTITY_WEAPON_RIFLE)
+        {
+            Type = POWERUP_WEAPON;
+            SubType = WEAPON_RIFLE;
+        }
+        else if(Index == ENTITY_POWERUP_NINJA && g_Config.m_SvPowerups)
+        {
+            Type = POWERUP_NINJA;
+            SubType = WEAPON_NINJA;
+        }
+    }
+    /* end zCatch*/
 
 	if(Type != -1)
 	{
@@ -219,6 +176,11 @@ bool IGameController::OnEntity(int Index, vec2 Pos)
 	return false;
 }
 
+bool IGameController::IsZCatch()
+{
+	return false;
+}
+
 void IGameController::EndRound()
 {
 	if(m_Warmup) // game can't end when we are running warmup
@@ -265,6 +227,7 @@ void IGameController::StartRound()
 	m_aTeamscore[TEAM_RED] = 0;
 	m_aTeamscore[TEAM_BLUE] = 0;
 	m_ForceBalanced = false;
+	Server()->DemoRecorder_HandleAutoStart();
 	char aBuf[256];
 	str_format(aBuf, sizeof(aBuf), "start round type='%s' teamplay='%d'", m_pGameType, m_GameFlags&GAMEFLAG_TEAMS);
 	GameServer()->Console()->Print(IConsole::OUTPUT_LEVEL_DEBUG, "game", aBuf);
@@ -568,6 +531,8 @@ void IGameController::Tick()
 			}
 		}
 	}
+
+	DoWincheck();
 }
 
 
@@ -706,51 +671,50 @@ bool IGameController::CanChangeTeam(CPlayer *pPlayer, int JoinTeam)
 		return true;
 }
 
-void IGameController::DoPlayerScoreWincheck()
+void IGameController::DoWincheck()
 {
-	if(m_GameOverTick == -1 && !m_Warmup)
+	if(m_GameOverTick == -1 && !m_Warmup && !GameServer()->m_World.m_ResetRequested)
 	{
-		// gather some stats
-		int Topscore = 0;
-		int TopscoreCount = 0;
-		for(int i = 0; i < MAX_CLIENTS; i++)
+		if(IsTeamplay())
 		{
-			if(GameServer()->m_apPlayers[i])
+			// check score win condition
+			if((g_Config.m_SvScorelimit > 0 && (m_aTeamscore[TEAM_RED] >= g_Config.m_SvScorelimit || m_aTeamscore[TEAM_BLUE] >= g_Config.m_SvScorelimit)) ||
+				(g_Config.m_SvTimelimit > 0 && (Server()->Tick()-m_RoundStartTick) >= g_Config.m_SvTimelimit*Server()->TickSpeed()*60))
 			{
-				if(GameServer()->m_apPlayers[i]->m_Score > Topscore)
-				{
-					Topscore = GameServer()->m_apPlayers[i]->m_Score;
-					TopscoreCount = 1;
-				}
-				else if(GameServer()->m_apPlayers[i]->m_Score == Topscore)
-					TopscoreCount++;
+				if(m_aTeamscore[TEAM_RED] != m_aTeamscore[TEAM_BLUE])
+					EndRound();
+				else
+					m_SuddenDeath = 1;
 			}
 		}
-
-		// check score win condition
-		if((g_Config.m_SvScorelimit > 0 && Topscore >= g_Config.m_SvScorelimit) ||
-			(g_Config.m_SvTimelimit > 0 && (Server()->Tick()-m_RoundStartTick) >= g_Config.m_SvTimelimit*Server()->TickSpeed()*60))
+		else
 		{
-			if(TopscoreCount == 1)
-				EndRound();
-			else
-				m_SuddenDeath = 1;
-		}
-	}
-}
+			// gather some stats
+			int Topscore = 0;
+			int TopscoreCount = 0;
+			for(int i = 0; i < MAX_CLIENTS; i++)
+			{
+				if(GameServer()->m_apPlayers[i])
+				{
+					if(GameServer()->m_apPlayers[i]->m_Score > Topscore)
+					{
+						Topscore = GameServer()->m_apPlayers[i]->m_Score;
+						TopscoreCount = 1;
+					}
+					else if(GameServer()->m_apPlayers[i]->m_Score == Topscore)
+						TopscoreCount++;
+				}
+			}
 
-void IGameController::DoTeamScoreWincheck()
-{
-	if(m_GameOverTick == -1 && !m_Warmup)
-	{
-		// check score win condition
-		if((g_Config.m_SvScorelimit > 0 && (m_aTeamscore[TEAM_RED] >= g_Config.m_SvScorelimit || m_aTeamscore[TEAM_BLUE] >= g_Config.m_SvScorelimit)) ||
-			(g_Config.m_SvTimelimit > 0 && (Server()->Tick()-m_RoundStartTick) >= g_Config.m_SvTimelimit*Server()->TickSpeed()*60))
-		{
-			if(m_aTeamscore[TEAM_RED] != m_aTeamscore[TEAM_BLUE])
-				EndRound();
-			else
-				m_SuddenDeath = 1;
+			// check score win condition
+			if((g_Config.m_SvScorelimit > 0 && Topscore >= g_Config.m_SvScorelimit) ||
+				(g_Config.m_SvTimelimit > 0 && (Server()->Tick()-m_RoundStartTick) >= g_Config.m_SvTimelimit*Server()->TickSpeed()*60))
+			{
+				if(TopscoreCount == 1)
+					EndRound();
+				else
+					m_SuddenDeath = 1;
+			}
 		}
 	}
 }
diff --git a/src/game/server/gamecontroller.h b/src/game/server/gamecontroller.h
index 6ccfe977..914f7985 100644
--- a/src/game/server/gamecontroller.h
+++ b/src/game/server/gamecontroller.h
@@ -39,7 +39,6 @@ protected:
 
 	float EvaluateSpawnPos(CSpawnEval *pEval, vec2 Pos);
 	void EvaluateSpawnType(CSpawnEval *pEval, int Type);
-	void FindFreeSpawn(CSpawnEval *pEval, int Type);
 	bool EvaluateSpawn(class CPlayer *pP, vec2 *pPos);
 
 	void CycleMap();
@@ -69,13 +68,12 @@ public:
 	IGameController(class CGameContext *pGameServer);
 	virtual ~IGameController();
 
-	void DoTeamScoreWincheck();
-	void DoPlayerScoreWincheck();
+	virtual void DoWincheck();
 
 	void DoWarmup(int Seconds);
 
-	void StartRound();
-	void EndRound();
+	virtual void StartRound();
+	virtual void EndRound();
 	void ChangeMap(const char *pToMap);
 
 	bool IsFriendlyFire(int ClientID1, int ClientID2);
@@ -143,6 +141,8 @@ public:
 	int ClampTeam(int Team);
 
 	virtual void PostReset();
+	
+	virtual bool IsZCatch();
 };
 
 #endif
diff --git a/src/game/server/gamemodes/ctf.cpp b/src/game/server/gamemodes/ctf.cpp
index b1d3d2fa..66cc4c2c 100644
--- a/src/game/server/gamemodes/ctf.cpp
+++ b/src/game/server/gamemodes/ctf.cpp
@@ -1,6 +1,9 @@
 /* (c) Magnus Auvinen. See licence.txt in the root of the distribution for more information. */
 /* If you are missing that file, acquire a complete release at teeworlds.com.                */
+#include <engine/shared/config.h>
+
 #include <game/mapitems.h>
+
 #include <game/server/entities/character.h>
 #include <game/server/entities/flag.h>
 #include <game/server/player.h>
@@ -63,6 +66,30 @@ int CGameControllerCTF::OnCharacterDeath(class CCharacter *pVictim, class CPlaye
 	return HadFlag;
 }
 
+void CGameControllerCTF::DoWincheck()
+{
+	if(m_GameOverTick == -1 && !m_Warmup)
+	{
+		// check score win condition
+		if((g_Config.m_SvScorelimit > 0 && (m_aTeamscore[TEAM_RED] >= g_Config.m_SvScorelimit || m_aTeamscore[TEAM_BLUE] >= g_Config.m_SvScorelimit)) ||
+			(g_Config.m_SvTimelimit > 0 && (Server()->Tick()-m_RoundStartTick) >= g_Config.m_SvTimelimit*Server()->TickSpeed()*60))
+		{
+			if(m_SuddenDeath)
+			{
+				if(m_aTeamscore[TEAM_RED]/100 != m_aTeamscore[TEAM_BLUE]/100)
+					EndRound();
+			}
+			else
+			{
+				if(m_aTeamscore[TEAM_RED] != m_aTeamscore[TEAM_BLUE])
+					EndRound();
+				else
+					m_SuddenDeath = 1;
+			}
+		}
+	}
+}
+
 bool CGameControllerCTF::CanBeMovedOnBalance(int ClientID)
 {
 	CCharacter* Character = GameServer()->m_apPlayers[ClientID]->GetCharacter();
@@ -117,7 +144,8 @@ void CGameControllerCTF::Tick()
 {
 	IGameController::Tick();
 
-	DoTeamScoreWincheck();
+	if(GameServer()->m_World.m_ResetRequested || GameServer()->m_World.m_Paused)
+		return;
 
 	for(int fi = 0; fi < 2; fi++)
 	{
@@ -220,13 +248,16 @@ void CGameControllerCTF::Tick()
 
 					for(int c = 0; c < MAX_CLIENTS; c++)
 					{
-						if(!GameServer()->m_apPlayers[c])
+						CPlayer *pPlayer = GameServer()->m_apPlayers[c];
+						if(!pPlayer)
 							continue;
 
-						if(GameServer()->m_apPlayers[c]->GetTeam() == fi)
-							GameServer()->CreateSoundGlobal(SOUND_CTF_GRAB_EN, GameServer()->m_apPlayers[c]->GetCID());
+						if(pPlayer->GetTeam() == TEAM_SPECTATORS && pPlayer->m_SpectatorID != SPEC_FREEVIEW && GameServer()->m_apPlayers[pPlayer->m_SpectatorID] && GameServer()->m_apPlayers[pPlayer->m_SpectatorID]->GetTeam() == fi)
+							GameServer()->CreateSoundGlobal(SOUND_CTF_GRAB_EN, c);
+						else if(pPlayer->GetTeam() == fi)
+							GameServer()->CreateSoundGlobal(SOUND_CTF_GRAB_EN, c);
 						else
-							GameServer()->CreateSoundGlobal(SOUND_CTF_GRAB_PL, GameServer()->m_apPlayers[c]->GetCID());
+							GameServer()->CreateSoundGlobal(SOUND_CTF_GRAB_PL, c);
 					}
 					break;
 				}
diff --git a/src/game/server/gamemodes/ctf.h b/src/game/server/gamemodes/ctf.h
index 44bd9e8e..72747ed7 100644
--- a/src/game/server/gamemodes/ctf.h
+++ b/src/game/server/gamemodes/ctf.h
@@ -11,6 +11,7 @@ public:
 	class CFlag *m_apFlags[2];
 
 	CGameControllerCTF(class CGameContext *pGameServer);
+	virtual void DoWincheck();
 	virtual bool CanBeMovedOnBalance(int ClientID);
 	virtual void Snap(int SnappingClient);
 	virtual void Tick();
diff --git a/src/game/server/gamemodes/dm.cpp b/src/game/server/gamemodes/dm.cpp
index d2b69b43..bdca4c9a 100644
--- a/src/game/server/gamemodes/dm.cpp
+++ b/src/game/server/gamemodes/dm.cpp
@@ -11,6 +11,5 @@ CGameControllerDM::CGameControllerDM(class CGameContext *pGameServer)
 
 void CGameControllerDM::Tick()
 {
-	DoPlayerScoreWincheck();
 	IGameController::Tick();
 }
diff --git a/src/game/server/gamemodes/mod.cpp b/src/game/server/gamemodes/mod.cpp
index 127be2bc..eb8fd7c8 100644
--- a/src/game/server/gamemodes/mod.cpp
+++ b/src/game/server/gamemodes/mod.cpp
@@ -15,8 +15,6 @@ CGameControllerMOD::CGameControllerMOD(class CGameContext *pGameServer)
 void CGameControllerMOD::Tick()
 {
 	// this is the main part of the gamemode, this function is run every tick
-	DoPlayerScoreWincheck(); // checks for winners, no teams version
-	//DoTeamScoreWincheck(); // checks for winners, two teams version
 
 	IGameController::Tick();
 }
diff --git a/src/game/server/gamemodes/tdm.cpp b/src/game/server/gamemodes/tdm.cpp
index 54e645b3..50ecd93e 100644
--- a/src/game/server/gamemodes/tdm.cpp
+++ b/src/game/server/gamemodes/tdm.cpp
@@ -48,6 +48,5 @@ void CGameControllerTDM::Snap(int SnappingClient)
 
 void CGameControllerTDM::Tick()
 {
-	DoTeamScoreWincheck();
 	IGameController::Tick();
 }
diff --git a/src/game/server/gamemodes/zcatch.cpp b/src/game/server/gamemodes/zcatch.cpp
new file mode 100644
index 00000000..eac8a4d3
--- /dev/null
+++ b/src/game/server/gamemodes/zcatch.cpp
@@ -0,0 +1,230 @@
+/* (c) Magnus Auvinen. See licence.txt in the root of the distribution for more information. */
+/* If you are missing that file, acquire a complete release at teeworlds.com.                */
+/* zCatch by erd and Teetime */
+
+#include <engine/shared/config.h>
+#include <game/server/gamecontext.h>
+#include "zcatch.hpp"
+
+CGameController_zCatch::CGameController_zCatch(class CGameContext *pGameServer) 
+: IGameController(pGameServer)
+{
+	m_pGameType = "zCatch";
+	m_OldMode = g_Config.m_SvMode;
+}
+
+void CGameController_zCatch::Tick()
+{
+	DoWincheck();
+	IGameController::Tick();
+	
+	if(m_OldMode != g_Config.m_SvMode)
+	{
+		Server()->MapReload();
+		m_OldMode = g_Config.m_SvMode;
+	}
+}
+
+bool CGameController_zCatch::IsZCatch()
+{
+	return true;
+}
+
+void CGameController_zCatch::DoWincheck()
+{
+	int Players = 0, Players_Spec = 0, Players_SpecExplicit = 0;	
+	
+	for(int i = 0; i < MAX_CLIENTS; i++)
+	{
+		if(GameServer()->m_apPlayers[i])
+		{
+			Players++;
+			if(GameServer()->m_apPlayers[i]->GetTeam() == TEAM_SPECTATORS)
+				Players_Spec++;
+			if(GameServer()->m_apPlayers[i]->m_SpecExplicit == 1)
+				Players_SpecExplicit++;
+		}
+	}
+	
+	if(Players == 1)
+	{
+		//Do nothing
+	}
+	else if((Players - Players_Spec == 1) && (Players != Players_Spec) && (Players - Players_SpecExplicit != 1)) 
+	{
+		for(int i = 0; i < MAX_CLIENTS; i++)
+		{
+			if(GameServer()->m_apPlayers[i] && GameServer()->m_apPlayers[i]->GetTeam() != TEAM_SPECTATORS)
+				GameServer()->m_apPlayers[i]->m_Score += g_Config.m_SvBonus;
+		}
+		EndRound();
+	}
+	
+	IGameController::DoWincheck(); //do also usual wincheck
+}
+
+int CGameController_zCatch::OnCharacterDeath(class CCharacter *pVictim, class CPlayer *pKiller, int WeaponID)
+{
+	int VictimID =  pVictim->GetPlayer()->GetCID();
+	char buf[256];
+	if(pKiller !=  pVictim->GetPlayer())
+	{
+		pKiller->m_Kills++;
+		pVictim->GetPlayer()->m_Deaths++; 
+		
+		pKiller->m_Score++;
+		pVictim->GetPlayer()->m_Score--;
+		
+		/* Check if the killer is already killed and in spectator (victim may died through wallshot) */
+		if(pKiller->GetTeam() != TEAM_SPECTATORS)
+		{
+			pVictim->GetPlayer()->m_CatchedBy = pKiller->GetCID();
+			pVictim->GetPlayer()->SetTeamDirect(TEAM_SPECTATORS);
+		
+			if(pVictim->GetPlayer()->m_PlayerWantToFollowCatcher)
+				pVictim->GetPlayer()->m_SpectatorID = pKiller->GetCID(); // Let the victim follow his catcher
+		
+			str_format(buf, sizeof(buf), "Caught by \"%s\". You will join the game automatically when \"%s\" dies.", Server()->ClientName(pKiller->GetCID()), Server()->ClientName(pKiller->GetCID()));	
+			GameServer()->SendChatTarget(VictimID, buf);
+		}
+	}
+	else
+	{
+		//Punish selfkill/death
+		if(WeaponID == WEAPON_SELF || WeaponID == WEAPON_WORLD)
+			pVictim->GetPlayer()->m_Score -= 15;
+	}
+	
+	for(int i=0; i < MAX_CLIENTS; i++)
+	{
+		if(GameServer()->m_apPlayers[i])
+		{
+			if(GameServer()->m_apPlayers[i]->m_CatchedBy == VictimID)
+			{
+				GameServer()->m_apPlayers[i]->m_CatchedBy = ZCATCH_NOT_CATCHED;
+				GameServer()->m_apPlayers[i]->SetTeamDirect(GameServer()->m_pController->ClampTeam(1));
+				
+				if(pKiller != pVictim->GetPlayer())
+					pKiller->m_Score++;
+			}
+		}
+	}
+		
+	// Update color of the killer
+	OnPlayerInfoChange(pKiller);
+	
+	return 0;
+}
+
+void CGameController_zCatch::OnPlayerInfoChange(class CPlayer *pP)
+{
+	if(g_Config.m_SvColorIndicator)
+	{
+		int Num = 161;
+		for(int i = 0; i < MAX_CLIENTS; i++)
+			if(GameServer()->m_apPlayers[i] && GameServer()->m_apPlayers[i]->m_CatchedBy == pP->GetCID())
+				Num -= 10;
+		pP->m_TeeInfos.m_ColorBody = Num * 0x010000 + 0xff00;
+		pP->m_TeeInfos.m_ColorFeet = Num * 0x010000 + 0xff00;
+		pP->m_TeeInfos.m_UseCustomColor = 1;
+	}
+}
+
+void CGameController_zCatch::StartRound()
+{
+	ResetGame();
+
+	m_RoundStartTick = Server()->Tick();
+	m_SuddenDeath = 0;
+	m_GameOverTick = -1;
+	GameServer()->m_World.m_Paused = false;
+	m_aTeamscore[TEAM_RED] = 0;
+	m_aTeamscore[TEAM_BLUE] = 0;
+	m_UnbalancedTick = -1;
+	m_ForceBalanced = false;
+	for(int i=0; i<MAX_CLIENTS; i++)
+	{
+		if(GameServer()->m_apPlayers[i])
+		{		
+			GameServer()->m_apPlayers[i]->m_CatchedBy = ZCATCH_NOT_CATCHED;
+			GameServer()->m_apPlayers[i]->m_Kills = 0;
+			GameServer()->m_apPlayers[i]->m_Deaths = 0;
+			GameServer()->m_apPlayers[i]->m_TicksSpec = 0;
+			GameServer()->m_apPlayers[i]->m_TicksIngame = 0;
+		}
+	}
+	char aBufMsg[256];
+	str_format(aBufMsg, sizeof(aBufMsg), "start round type='%s' teamplay='%d'", m_pGameType, m_GameFlags&GAMEFLAG_TEAMS);
+	GameServer()->Console()->Print(IConsole::OUTPUT_LEVEL_DEBUG, "game", aBufMsg);
+}
+
+void CGameController_zCatch::OnCharacterSpawn(class CCharacter *pChr)
+{
+	// default health and armor
+	pChr->IncreaseHealth(10);
+	if(g_Config.m_SvMode == 2)
+		pChr->IncreaseArmor(10);
+	// give default weapons
+	switch(g_Config.m_SvMode)
+		{
+			case 0:
+				pChr->GiveWeapon(WEAPON_HAMMER, -1);
+				pChr->GiveWeapon(WEAPON_GUN, 10);
+				break;
+			case 1:
+				pChr->GiveWeapon(WEAPON_RIFLE, -1);
+				break;
+			case 2:
+				pChr->GiveWeapon(WEAPON_HAMMER, -1);
+				pChr->GiveWeapon(WEAPON_GUN, -1);
+				pChr->GiveWeapon(WEAPON_GRENADE, -1);
+				pChr->GiveWeapon(WEAPON_SHOTGUN, -1);
+				pChr->GiveWeapon(WEAPON_RIFLE, -1);
+				break;
+			case 3:
+				pChr->GiveWeapon(WEAPON_HAMMER, -1);
+				break;
+			case 4:
+				pChr->GiveWeapon(WEAPON_GRENADE, -1);
+				break;
+			case 5:
+				pChr->GiveNinja();
+				break;
+		}
+	//Update color of spawning tees
+	OnPlayerInfoChange(pChr->GetPlayer());
+}
+
+void CGameController_zCatch::EndRound()
+{
+	for(int i = 0; i < MAX_CLIENTS; i++)
+	{
+		if(GameServer()->m_apPlayers[i])
+		{
+				
+			if(GameServer()->m_apPlayers[i]->m_SpecExplicit == 0)
+			{
+				GameServer()->m_apPlayers[i]->SetTeamDirect(GameServer()->m_pController->ClampTeam(1));
+				
+				char abuf[128];
+				str_format(abuf, sizeof(abuf), "Kills: %d | Deaths: %d", GameServer()->m_apPlayers[i]->m_Kills, GameServer()->m_apPlayers[i]->m_Deaths);				
+				GameServer()->SendChatTarget(i, abuf);
+				
+				if(GameServer()->m_apPlayers[i]->m_TicksSpec != 0 || GameServer()->m_apPlayers[i]->m_TicksIngame != 0)
+				{
+					double TimeInSpec = (GameServer()->m_apPlayers[i]->m_TicksSpec*100.0) / (GameServer()->m_apPlayers[i]->m_TicksIngame + GameServer()->m_apPlayers[i]->m_TicksSpec);
+					str_format(abuf, sizeof(abuf), "Spec: %.2f%% | Ingame: %.2f%%", (double)TimeInSpec, (double)(100.0 - TimeInSpec));
+					GameServer()->SendChatTarget(i, abuf);	
+				}
+				GameServer()->m_apPlayers[i]->m_CatchedBy = ZCATCH_NOT_CATCHED; //Set all players in server as non-catched
+			}
+		}
+	}
+
+	if(m_Warmup) // game can't end when we are running warmup
+		return;
+
+	GameServer()->m_World.m_Paused = true;
+	m_GameOverTick = Server()->Tick();
+	m_SuddenDeath = 0;
+}
diff --git a/src/game/server/gamemodes/zcatch.hpp b/src/game/server/gamemodes/zcatch.hpp
new file mode 100644
index 00000000..58e43f61
--- /dev/null
+++ b/src/game/server/gamemodes/zcatch.hpp
@@ -0,0 +1,33 @@
+/* (c) Magnus Auvinen. See licence.txt in the root of the distribution for more information. */
+/* If you are missing that file, acquire a complete release at teeworlds.com.                */
+/* zCatch by erd and Teetime */
+
+#ifndef GAME_SERVER_GAMEMODES_ZCATCH_H
+#define GAME_SERVER_GAMEMODES_ZCATCH_H
+
+#include <game/server/gamecontroller.h>
+
+class CGameController_zCatch : public IGameController
+{
+	private:
+	int m_OldMode;
+	
+	public:
+	CGameController_zCatch(class CGameContext *pGameServer);
+	virtual void Tick();
+	virtual void DoWincheck();
+	virtual bool IsZCatch();
+	
+	enum
+	{
+		ZCATCH_NOT_CATCHED = -1,
+	}; 
+	
+	virtual int OnCharacterDeath(class CCharacter *pVictim, class CPlayer *pKiller, int WeaponID);
+	virtual void StartRound();
+	virtual void OnCharacterSpawn(class CCharacter *pChr);
+	virtual void OnPlayerInfoChange(class CPlayer *pP);
+	virtual void EndRound();
+};
+
+#endif
diff --git a/src/game/server/gameworld.cpp b/src/game/server/gameworld.cpp
index 6444cce5..89f4808d 100644
--- a/src/game/server/gameworld.cpp
+++ b/src/game/server/gameworld.cpp
@@ -183,7 +183,6 @@ CCharacter *CGameWorld::IntersectCharacter(vec2 Pos0, vec2 Pos1, float Radius, v
 {
 	// Find other players
 	float ClosestLen = distance(Pos0, Pos1) * 100.0f;
-	vec2 LineDir = normalize(Pos1-Pos0);
 	CCharacter *pClosest = 0;
 
 	CCharacter *p = (CCharacter *)FindFirst(ENTTYPE_CHARACTER);
diff --git a/src/game/server/player.cpp b/src/game/server/player.cpp
index aeafe3a4..7464e3c7 100644
--- a/src/game/server/player.cpp
+++ b/src/game/server/player.cpp
@@ -15,17 +15,29 @@ CPlayer::CPlayer(CGameContext *pGameServer, int ClientID, int Team)
 	m_RespawnTick = Server()->Tick();
 	m_DieTick = Server()->Tick();
 	m_ScoreStartTick = Server()->Tick();
-	Character = 0;
-	this->m_ClientID = ClientID;
+	m_pCharacter = 0;
+	m_ClientID = ClientID;
 	m_Team = GameServer()->m_pController->ClampTeam(Team);
 	m_SpectatorID = SPEC_FREEVIEW;
 	m_LastActionTick = Server()->Tick();
+	m_TeamChangeTick = Server()->Tick();
+	
+	//zCatch
+	m_CatchedBy = -1;
+	m_SpecExplicit = 0;
+	m_Kills = 0;
+	m_Deaths = 0;
+	m_PlayerWantToFollowCatcher = g_Config.m_SvFollowCatcher;
+	m_LastKillTry = Server()->Tick();
+	m_TicksSpec = 0;
+	m_TicksIngame = 0;
+	m_ChatTicks = 0;
 }
 
 CPlayer::~CPlayer()
 {
-	delete Character;
-	Character = 0;
+	delete m_pCharacter;
+	m_pCharacter = 0;
 }
 
 void CPlayer::Tick()
@@ -37,6 +49,20 @@ void CPlayer::Tick()
 		return;
 
 	Server()->SetClientScore(m_ClientID, m_Score);
+	
+	/* begin zCatch*/
+	
+	if(m_Team == TEAM_SPECTATORS)
+		m_TicksSpec++;
+	else
+		m_TicksIngame++;
+	
+	if(m_ChatTicks > 0)
+		m_ChatTicks--;
+	
+	if(g_Config.m_SvAnticamper && m_pCharacter && !GameServer()->m_World.m_Paused)
+		Anticamper();
+	/* end zCatch*/
 
 	// do latency stuff
 	{
@@ -59,19 +85,22 @@ void CPlayer::Tick()
 		}
 	}
 
-	if(!Character && m_DieTick+Server()->TickSpeed()*3 <= Server()->Tick())
+	if(!m_pCharacter && m_Team == TEAM_SPECTATORS && m_SpectatorID == SPEC_FREEVIEW)
+		m_ViewPos -= vec2(clamp(m_ViewPos.x-m_LatestActivity.m_TargetX, -500.0f, 500.0f), clamp(m_ViewPos.y-m_LatestActivity.m_TargetY, -400.0f, 400.0f));
+
+	if(!m_pCharacter && m_DieTick+Server()->TickSpeed()*3 <= Server()->Tick())
 		m_Spawning = true;
 
-	if(Character)
+	if(m_pCharacter)
 	{
-		if(Character->IsAlive())
+		if(m_pCharacter->IsAlive())
 		{
-			m_ViewPos = Character->m_Pos;
+			m_ViewPos = m_pCharacter->m_Pos;
 		}
 		else
 		{
-			delete Character;
-			Character = 0;
+			delete m_pCharacter;
+			m_pCharacter = 0;
 		}
 	}
 	else if(m_Spawning && m_RespawnTick <= Server()->Tick())
@@ -160,23 +189,38 @@ void CPlayer::OnDisconnect(const char *pReason)
 
 void CPlayer::OnPredictedInput(CNetObj_PlayerInput *NewInput)
 {
-	if(Character)
-		Character->OnPredictedInput(NewInput);
+	// skip the input if chat is active
+	if((m_PlayerFlags&PLAYERFLAG_CHATTING) && (NewInput->m_PlayerFlags&PLAYERFLAG_CHATTING))
+		return;
+
+	if(m_pCharacter)
+		m_pCharacter->OnPredictedInput(NewInput);
 }
 
 void CPlayer::OnDirectInput(CNetObj_PlayerInput *NewInput)
 {
+	if(NewInput->m_PlayerFlags&PLAYERFLAG_CHATTING)
+	{
+		// skip the input if chat is active
+		if(m_PlayerFlags&PLAYERFLAG_CHATTING)
+			return;
+
+		// reset input
+		if(m_pCharacter)
+			m_pCharacter->ResetInput();
+
+		m_PlayerFlags = NewInput->m_PlayerFlags;
+ 		return;
+	}
+
 	m_PlayerFlags = NewInput->m_PlayerFlags;
 
-	if(Character)
-		Character->OnDirectInput(NewInput);
+	if(m_pCharacter)
+		m_pCharacter->OnDirectInput(NewInput);
 
-	if(!Character && m_Team != TEAM_SPECTATORS && (NewInput->m_Fire&1))
+	if(!m_pCharacter && m_Team != TEAM_SPECTATORS && (NewInput->m_Fire&1))
 		m_Spawning = true;
 
-	if(!Character && m_Team == TEAM_SPECTATORS && m_SpectatorID == SPEC_FREEVIEW)
-		m_ViewPos = vec2(NewInput->m_TargetX, NewInput->m_TargetY);
-
 	// check for activity
 	if(NewInput->m_Direction || m_LatestActivity.m_TargetX != NewInput->m_TargetX ||
 		m_LatestActivity.m_TargetY != NewInput->m_TargetY || NewInput->m_Jump ||
@@ -190,18 +234,18 @@ void CPlayer::OnDirectInput(CNetObj_PlayerInput *NewInput)
 
 CCharacter *CPlayer::GetCharacter()
 {
-	if(Character && Character->IsAlive())
-		return Character;
+	if(m_pCharacter && m_pCharacter->IsAlive())
+		return m_pCharacter;
 	return 0;
 }
 
 void CPlayer::KillCharacter(int Weapon)
 {
-	if(Character)
+	if(m_pCharacter)
 	{
-		Character->Die(m_ClientID, Weapon);
-		delete Character;
-		Character = 0;
+		m_pCharacter->Die(m_ClientID, Weapon);
+		delete m_pCharacter;
+		m_pCharacter = 0;
 	}
 }
 
@@ -244,6 +288,11 @@ void CPlayer::SetTeam(int Team)
 	}
 }
 
+void CPlayer::SetTeamDirect(int Team)
+{
+	m_Team = Team;
+}
+
 void CPlayer::TryRespawn()
 {
 	vec2 SpawnPos;
@@ -252,7 +301,43 @@ void CPlayer::TryRespawn()
 		return;
 
 	m_Spawning = false;
-	Character = new(m_ClientID) CCharacter(&GameServer()->m_World);
-	Character->Spawn(this, SpawnPos);
+	m_pCharacter = new(m_ClientID) CCharacter(&GameServer()->m_World);
+	m_pCharacter->Spawn(this, SpawnPos);
 	GameServer()->CreatePlayerSpawn(SpawnPos);
 }
+
+int CPlayer::Anticamper()
+{
+	int AnticamperTime = g_Config.m_SvAnticamperTime;
+	int AnticamperRange = g_Config.m_SvAnticamperRange;
+
+	if(m_CampTick == -1)
+	{
+		m_CampPos = m_pCharacter->m_Pos;
+		m_CampTick = Server()->Tick() + Server()->TickSpeed()*AnticamperTime;
+	}
+
+	// Check if the player is moving
+	if((m_CampPos.x - m_pCharacter->m_Pos.x >= (float)AnticamperRange || m_CampPos.x - m_pCharacter->m_Pos.x <= -(float)AnticamperRange)
+	|| (m_CampPos.y - m_pCharacter->m_Pos.y >= (float)AnticamperRange || m_CampPos.y - m_pCharacter->m_Pos.y <= -(float)AnticamperRange))
+		{
+			m_CampTick = -1;
+		}
+
+	// Send warning to the player
+	if(m_CampTick <= Server()->Tick() + Server()->TickSpeed() * AnticamperTime/2 && m_CampTick != -1 && !m_SentCampMsg)
+	{
+		GameServer()->SendBroadcast("ANTICAMPER: Move or die", m_ClientID);
+		m_SentCampMsg = true;
+	}
+
+	// Kill him
+	if((m_CampTick <= Server()->Tick()) && (m_CampTick > 0))
+	{
+		m_pCharacter->Die(m_ClientID, WEAPON_ANTICAMPER);
+		m_CampTick = -1;
+		m_SentCampMsg = false;
+		return 1;
+	}
+	return 0;
+}
diff --git a/src/game/server/player.h b/src/game/server/player.h
index 9d5e462c..99de2952 100644
--- a/src/game/server/player.h
+++ b/src/game/server/player.h
@@ -21,6 +21,7 @@ public:
 	void TryRespawn();
 	void Respawn();
 	void SetTeam(int Team);
+	void SetTeamDirect(int Team); //zCatch
 	int GetTeam() const { return m_Team; };
 	int GetCID() const { return m_ClientID; };
 
@@ -78,6 +79,7 @@ public:
 	int m_ScoreStartTick;
 	bool m_ForceBalanced;
 	int m_LastActionTick;
+	int m_TeamChangeTick;
 	struct
 	{
 		int m_TargetX;
@@ -94,9 +96,26 @@ public:
 		int m_Min;
 		int m_Max;
 	} m_Latency;
-
+	
+		//zCatch:
+	int m_CatchedBy;
+	int m_SpecExplicit;
+	int m_Deaths;
+	int m_Kills;
+	int m_LastKillTry;
+	bool m_PlayerWantToFollowCatcher;
+	
+	int m_TicksSpec;
+	int m_TicksIngame;
+	int m_ChatTicks;
+	//Anticamper
+	int Anticamper();
+	bool m_SentCampMsg;
+	int m_CampTick;
+	vec2 m_CampPos;
+	
 private:
-	CCharacter *Character;
+	CCharacter *m_pCharacter;
 	CGameContext *m_pGameServer;
 
 	CGameContext *GameServer() const { return m_pGameServer; }
diff --git a/src/game/variables.h b/src/game/variables.h
index ee8cb74a..3f0d7bf9 100644
--- a/src/game/variables.h
+++ b/src/game/variables.h
@@ -45,6 +45,7 @@ MACRO_CONFIG_INT(UiPage, ui_page, 5, 0, 10, CFGFLAG_CLIENT|CFGFLAG_SAVE, "Interf
 MACRO_CONFIG_INT(UiToolboxPage, ui_toolbox_page, 0, 0, 2, CFGFLAG_CLIENT|CFGFLAG_SAVE, "Toolbox page")
 MACRO_CONFIG_STR(UiServerAddress, ui_server_address, 64, "localhost:8303", CFGFLAG_CLIENT|CFGFLAG_SAVE, "Interface server address")
 MACRO_CONFIG_INT(UiScale, ui_scale, 100, 50, 150, CFGFLAG_CLIENT|CFGFLAG_SAVE, "Interface scale")
+MACRO_CONFIG_INT(UiMousesens, ui_mousesens, 100, 5, 100000, CFGFLAG_SAVE|CFGFLAG_CLIENT, "Mouse sensitivity for menus/editor")
 
 MACRO_CONFIG_INT(UiColorHue, ui_color_hue, 160, 0, 255, CFGFLAG_CLIENT|CFGFLAG_SAVE, "Interface color hue")
 MACRO_CONFIG_INT(UiColorSat, ui_color_sat, 70, 0, 255, CFGFLAG_CLIENT|CFGFLAG_SAVE, "Interface color saturation")
@@ -73,7 +74,9 @@ MACRO_CONFIG_INT(SvTeambalanceTime, sv_teambalance_time, 1, 0, 1000, CFGFLAG_SER
 MACRO_CONFIG_INT(SvInactiveKickTime, sv_inactivekick_time, 3, 0, 1000, CFGFLAG_SERVER, "How many minutes to wait before taking care of inactive players")
 MACRO_CONFIG_INT(SvInactiveKick, sv_inactivekick, 1, 0, 2, CFGFLAG_SERVER, "How to deal with inactive players (0=move to spectator, 1=move to free spectator slot/kick, 2=kick)")
 
+MACRO_CONFIG_INT(SvStrictSpectateMode, sv_strict_spectate_mode, 0, 0, 1, CFGFLAG_SERVER, "Restricts information in spectator mode")
 MACRO_CONFIG_INT(SvVoteSpectate, sv_vote_spectate, 1, 0, 1, CFGFLAG_SERVER, "Allow voting to move players to spectators")
+MACRO_CONFIG_INT(SvVoteSpectateRejoindelay, sv_vote_spectate_rejoindelay, 3, 0, 1000, CFGFLAG_SERVER, "How many minutes to wait before a player can rejoin after being moved to spectators by vote")
 MACRO_CONFIG_INT(SvVoteKick, sv_vote_kick, 1, 0, 1, CFGFLAG_SERVER, "Allow voting to kick players")
 MACRO_CONFIG_INT(SvVoteKickMin, sv_vote_kick_min, 0, 0, MAX_CLIENTS, CFGFLAG_SERVER, "Minimum number of players required to start a kick vote")
 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")
@@ -85,4 +88,19 @@ MACRO_CONFIG_INT(SvVoteKickBantime, sv_vote_kick_bantime, 5, 0, 1440, CFGFLAG_SE
 
 MACRO_CONFIG_INT(DbgFocus, dbg_focus, 0, 0, 1, CFGFLAG_CLIENT, "")
 MACRO_CONFIG_INT(DbgTuning, dbg_tuning, 0, 0, 1, CFGFLAG_CLIENT, "")
+
+//zCatch:
+MACRO_CONFIG_INT(SvMode, sv_mode, 1, 0, 5, CFGFLAG_SERVER, "0 - Normal; 1 - Instagib; 2 - Rocket area; 3 - Hammerparty; 4 - Grenade; 5 - Ninja")
+MACRO_CONFIG_INT(SvAllowJoin, sv_allow_join, 2, 1, 2, CFGFLAG_SERVER, "Allow new Players to join without waiting for the next round")
+//1 = Allowed to join; 2 = Will join when person with the most kills die
+MACRO_CONFIG_INT(SvColorIndicator, sv_color_indicator, 1, 0, 1, CFGFLAG_SERVER, "Color tees apropriate to the number of currently catched players")
+MACRO_CONFIG_INT(SvBonus, sv_bonus, 0, 0, 10000, CFGFLAG_SERVER, "Give the last player extra points")
+MACRO_CONFIG_INT(SvFollowCatcher, sv_follow_catcher, 1, 0, 1, CFGFLAG_SERVER, "If a victim should follow his catcher")
+
+MACRO_CONFIG_INT(SvChatValue, sv_chat_value, 250, 100, 1000, CFGFLAG_SERVER, "A value wich is added on each message and decreased on each tick")
+MACRO_CONFIG_INT(SvChatThreshold, sv_chat_threshold, 1000, 250, 10000, CFGFLAG_SERVER, "If this threshold will exceed by too many messages the player will be muted")
+MACRO_CONFIG_INT(SvMuteDuration, sv_mute_duration, 60, 0, 3600, CFGFLAG_SERVER, "How long the player will be muted (in seconds)")
+MACRO_CONFIG_INT(SvAnticamper, sv_anticamper, 1, 0, 1, CFGFLAG_SERVER, "Toggle to enable/disable Anticamper")
+MACRO_CONFIG_INT(SvAnticamperTime, sv_anticamper_time, 10, 5, 120, CFGFLAG_SERVER, "How long to wait till the player dies/freezes")
+MACRO_CONFIG_INT(SvAnticamperRange, sv_anticamper_range, 200, 0, 1000, CFGFLAG_SERVER, "Distance how far away the player must move to escape anticamper")
 #endif
diff --git a/src/game/version.h b/src/game/version.h
index 8cf1a7c0..76b6d4ab 100644
--- a/src/game/version.h
+++ b/src/game/version.h
@@ -2,7 +2,7 @@
 /* If you are missing that file, acquire a complete release at teeworlds.com.                */
 #ifndef GAME_VERSION_H
 #define GAME_VERSION_H
-#include "generated/nethash.c"
-#define GAME_VERSION "0.6 trunk"
+#include "generated/nethash.cpp"
+#define GAME_VERSION "0.6.1"
 #define GAME_NETVERSION "0.6 " GAME_NETVERSION_HASH
 #endif