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/components/chat.cpp76
-rw-r--r--src/game/client/components/chat.h18
-rw-r--r--src/game/client/components/console.cpp8
-rw-r--r--src/game/client/components/controls.cpp5
-rw-r--r--src/game/client/components/countryflags.cpp78
-rw-r--r--src/game/client/components/countryflags.h2
-rw-r--r--src/game/client/components/damageind.cpp31
-rw-r--r--src/game/client/components/damageind.h2
-rw-r--r--src/game/client/components/effects.cpp6
-rw-r--r--src/game/client/components/emoticon.cpp14
-rw-r--r--src/game/client/components/hud.cpp51
-rw-r--r--src/game/client/components/hud.h1
-rw-r--r--src/game/client/components/items.cpp66
-rw-r--r--src/game/client/components/items.h4
-rw-r--r--src/game/client/components/maplayers.cpp59
-rw-r--r--src/game/client/components/maplayers.h5
-rw-r--r--src/game/client/components/menus.cpp27
-rw-r--r--src/game/client/components/menus.h2
-rw-r--r--src/game/client/components/menus_browser.cpp20
-rw-r--r--src/game/client/components/menus_demo.cpp20
-rw-r--r--src/game/client/components/menus_ingame.cpp37
-rw-r--r--src/game/client/components/menus_settings.cpp43
-rw-r--r--src/game/client/components/particles.cpp10
-rw-r--r--src/game/client/components/players.cpp114
-rw-r--r--src/game/client/components/players.h1
-rw-r--r--src/game/client/components/scoreboard.cpp9
-rw-r--r--src/game/client/components/skins.cpp8
-rw-r--r--src/game/client/components/sounds.cpp71
-rw-r--r--src/game/client/components/sounds.h5
-rw-r--r--src/game/client/gameclient.cpp42
-rw-r--r--src/game/client/gameclient.h3
-rw-r--r--src/game/client/render.cpp2
-rw-r--r--src/game/client/ui.cpp5
-rw-r--r--src/game/client/ui.h1
-rw-r--r--src/game/editor/auto_map.cpp50
-rw-r--r--src/game/editor/auto_map.h8
-rw-r--r--src/game/editor/editor.cpp656
-rw-r--r--src/game/editor/editor.h47
-rw-r--r--src/game/editor/io.cpp46
-rw-r--r--src/game/editor/layer_tiles.cpp24
-rw-r--r--src/game/editor/popups.cpp128
-rw-r--r--src/game/gamecore.cpp6
-rw-r--r--src/game/layers.cpp2
-rw-r--r--src/game/mapitems.h19
-rw-r--r--src/game/server/entities/character.cpp20
-rw-r--r--src/game/server/entities/character.h1
-rw-r--r--src/game/server/entities/flag.cpp7
-rw-r--r--src/game/server/entities/flag.h1
-rw-r--r--src/game/server/entities/laser.cpp13
-rw-r--r--src/game/server/entities/laser.h1
-rw-r--r--src/game/server/entities/pickup.cpp6
-rw-r--r--src/game/server/entities/pickup.h1
-rw-r--r--src/game/server/entities/projectile.cpp5
-rw-r--r--src/game/server/entities/projectile.h1
-rw-r--r--src/game/server/entity.h6
-rw-r--r--src/game/server/gamecontext.cpp145
-rw-r--r--src/game/server/gamecontext.h9
-rw-r--r--src/game/server/gamecontroller.cpp19
-rw-r--r--src/game/server/gamecontroller.h1
-rw-r--r--src/game/server/gamemodes/ctf.cpp2
-rw-r--r--src/game/server/gameworld.cpp11
-rw-r--r--src/game/server/player.cpp50
-rw-r--r--src/game/server/player.h2
-rw-r--r--src/game/variables.h4
-rw-r--r--src/game/version.h3
65 files changed, 1625 insertions, 515 deletions
diff --git a/src/game/client/components/chat.cpp b/src/game/client/components/chat.cpp
index aba38bf6..340b9da1 100644
--- a/src/game/client/components/chat.cpp
+++ b/src/game/client/components/chat.cpp
@@ -41,6 +41,11 @@ void CChat::OnReset()
 	m_PlaceholderOffset = 0;
 	m_PlaceholderLength = 0;
 	m_pHistoryEntry = 0x0;
+	m_PendingChatCounter = 0;
+	m_LastChatSend = 0;
+
+	for(int i = 0; i < CHAT_NUM; ++i)
+		m_aLastSoundPlayed[i] = 0;
 }
 
 void CChat::OnRelease()
@@ -107,9 +112,13 @@ bool CChat::OnInput(IInput::CEvent Event)
 	{
 		if(m_Input.GetString()[0])
 		{
-			Say(m_Mode == MODE_ALL ? 0 : 1, m_Input.GetString());
-			char *pEntry = m_History.Allocate(m_Input.GetLength()+1);
-			mem_copy(pEntry, m_Input.GetString(), m_Input.GetLength()+1);
+			if(m_LastChatSend+time_freq() < time_get())
+				Say(m_Mode == MODE_ALL ? 0 : 1, m_Input.GetString());
+			else
+				++m_PendingChatCounter;
+			CHistoryEntry *pEntry = m_History.Allocate(sizeof(CHistoryEntry)+m_Input.GetLength());
+			pEntry->m_Team = m_Mode == MODE_ALL ? 0 : 1;
+			mem_copy(pEntry->m_aText, m_Input.GetString(), m_Input.GetLength()+1);
 		}
 		m_pHistoryEntry = 0x0;
 		m_Mode = MODE_NONE;
@@ -199,26 +208,26 @@ bool CChat::OnInput(IInput::CEvent Event)
 	}
 	if(Event.m_Flags&IInput::FLAG_PRESS && Event.m_Key == KEY_UP)
 	{
-		if (m_pHistoryEntry)
+		if(m_pHistoryEntry)
 		{
-			char *pTest = m_History.Prev(m_pHistoryEntry);
+			CHistoryEntry *pTest = m_History.Prev(m_pHistoryEntry);
 
-			if (pTest)
+			if(pTest)
 				m_pHistoryEntry = pTest;
 		}
 		else
 			m_pHistoryEntry = m_History.Last();
 
-		if (m_pHistoryEntry)
-			m_Input.Set(m_pHistoryEntry);
+		if(m_pHistoryEntry)
+			m_Input.Set(m_pHistoryEntry->m_aText);
 	}
 	else if (Event.m_Flags&IInput::FLAG_PRESS && Event.m_Key == KEY_DOWN)
 	{
-		if (m_pHistoryEntry)
+		if(m_pHistoryEntry)
 			m_pHistoryEntry = m_History.Next(m_pHistoryEntry);
 
 		if (m_pHistoryEntry)
-			m_Input.Set(m_pHistoryEntry);
+			m_Input.Set(m_pHistoryEntry->m_aText);
 		else
 			m_Input.Clear();
 	}
@@ -257,7 +266,8 @@ void CChat::OnMessage(int MsgType, void *pRawMsg)
 void CChat::AddLine(int ClientID, int Team, const char *pLine)
 {
 	if(ClientID != -1 && (m_pClient->m_aClients[ClientID].m_aName[0] == '\0' || // unknown client
-		m_pClient->m_aClients[ClientID].m_ChatIgnore))
+		m_pClient->m_aClients[ClientID].m_ChatIgnore ||
+		(m_pClient->m_Snap.m_LocalClientID != ClientID && g_Config.m_ClShowChatFriends && !m_pClient->m_aClients[ClientID].m_Friend)))
 		return;
 
 	bool Highlighted = false;
@@ -283,7 +293,7 @@ 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;
-		
+
 		// 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)
@@ -322,16 +332,50 @@ void CChat::AddLine(int ClientID, int Team, const char *pLine)
 	}
 
 	// play sound
+	int64 Now = time_get();
 	if(ClientID == -1)
-		m_pClient->m_pSounds->Play(CSounds::CHN_GUI, SOUND_CHAT_SERVER, 0, vec2(0,0));
+	{
+		if(Now-m_aLastSoundPlayed[CHAT_SERVER] >= time_freq()*3/10)
+		{
+			m_pClient->m_pSounds->Play(CSounds::CHN_GUI, SOUND_CHAT_SERVER, 0);
+			m_aLastSoundPlayed[CHAT_SERVER] = Now;
+		}
+	}
 	else if(Highlighted)
-		m_pClient->m_pSounds->Play(CSounds::CHN_GUI, SOUND_CHAT_HIGHLIGHT, 0, vec2(0.0f, 0.0f));
+	{
+		if(Now-m_aLastSoundPlayed[CHAT_HIGHLIGHT] >= time_freq()*3/10)
+		{
+			m_pClient->m_pSounds->Play(CSounds::CHN_GUI, SOUND_CHAT_CLIENT, 0);
+			m_aLastSoundPlayed[CHAT_HIGHLIGHT] = Now;
+		}
+	}
 	else
-		m_pClient->m_pSounds->Play(CSounds::CHN_GUI, SOUND_CHAT_CLIENT, 0, vec2(0,0));
+	{
+		if(Now-m_aLastSoundPlayed[CHAT_CLIENT] >= time_freq()*3/10)
+		{
+			m_pClient->m_pSounds->Play(CSounds::CHN_GUI, SOUND_CHAT_HIGHLIGHT, 0);
+			m_aLastSoundPlayed[CHAT_CLIENT] = Now;
+		}
+	}
 }
 
 void CChat::OnRender()
 {
+	// send pending chat messages
+	if(m_PendingChatCounter > 0 && m_LastChatSend+time_freq() < time_get())
+	{
+		CHistoryEntry *pEntry = m_History.Last();
+		for(int i = m_PendingChatCounter-1; pEntry; --i, pEntry = m_History.Prev(pEntry))
+		{
+			if(i == 0)
+			{
+				Say(pEntry->m_Team, pEntry->m_aText);
+				break;
+			}
+		}
+		--m_PendingChatCounter;
+	}
+
 	float Width = 300.0f*Graphics()->ScreenAspect();
 	Graphics()->MapScreen(0.0f, 0.0f, Width, 300.0f);
 	float x = 5.0f;
@@ -457,6 +501,8 @@ void CChat::OnRender()
 
 void CChat::Say(int Team, const char *pLine)
 {
+	m_LastChatSend = time_get();
+
 	// send chat message
 	CNetMsg_Cl_Say Msg;
 	Msg.m_Team = Team;
diff --git a/src/game/client/components/chat.h b/src/game/client/components/chat.h
index 60e18387..09519516 100644
--- a/src/game/client/components/chat.h
+++ b/src/game/client/components/chat.h
@@ -36,6 +36,11 @@ class CChat : public CComponent
 		MODE_NONE=0,
 		MODE_ALL,
 		MODE_TEAM,
+
+		CHAT_SERVER=0,
+		CHAT_HIGHLIGHT,
+		CHAT_CLIENT,
+		CHAT_NUM,
 	};
 
 	int m_Mode;
@@ -47,8 +52,17 @@ class CChat : public CComponent
 	char m_aCompletionBuffer[256];
 	int m_PlaceholderOffset;
 	int m_PlaceholderLength;
-	char *m_pHistoryEntry;
-	TStaticRingBuffer<char, 64*1024, CRingBufferBase::FLAG_RECYCLE> m_History;
+
+	struct CHistoryEntry
+	{
+		int m_Team;
+		char m_aText[1];
+	};
+	CHistoryEntry *m_pHistoryEntry;
+	TStaticRingBuffer<CHistoryEntry, 64*1024, CRingBufferBase::FLAG_RECYCLE> m_History;
+	int m_PendingChatCounter;
+	int64 m_LastChatSend;
+	int64 m_aLastSoundPlayed[CHAT_NUM];
 
 	static void ConSay(IConsole::IResult *pResult, void *pUserData);
 	static void ConSayTeam(IConsole::IResult *pResult, void *pUserData);
diff --git a/src/game/client/components/console.cpp b/src/game/client/components/console.cpp
index f2e9e65d..d16d56cd 100644
--- a/src/game/client/components/console.cpp
+++ b/src/game/client/components/console.cpp
@@ -612,16 +612,10 @@ void CGameConsole::Dump(int Type)
 	IOHANDLE io = Storage()->OpenFile(aFilename, IOFLAG_WRITE, IStorage::TYPE_SAVE);
 	if(io)
 	{
-		#if defined(CONF_FAMILY_WINDOWS)
-			static const char Newline[] = "\r\n";
-		#else
-			static const char Newline[] = "\n";
-		#endif
-
 		for(CInstance::CBacklogEntry *pEntry = pConsole->m_Backlog.First(); pEntry; pEntry = pConsole->m_Backlog.Next(pEntry))
 		{
 			io_write(io, pEntry->m_aText, str_length(pEntry->m_aText));
-			io_write(io, Newline, sizeof(Newline)-1);
+			io_write_newline(io);
 		}
 		io_close(io);
 	}
diff --git a/src/game/client/components/controls.cpp b/src/game/client/components/controls.cpp
index 81c4d5ae..c0790799 100644
--- a/src/game/client/components/controls.cpp
+++ b/src/game/client/components/controls.cpp
@@ -199,7 +199,7 @@ int CControls::SnapInput(int *pData)
 void CControls::OnRender()
 {
 	// update target pos
-	if(m_pClient->m_Snap.m_pGameInfoObj && !(m_pClient->m_Snap.m_pGameInfoObj->m_GameStateFlags&GAMESTATEFLAG_PAUSED || m_pClient->m_Snap.m_SpecInfo.m_Active))
+	if(m_pClient->m_Snap.m_pGameInfoObj && !m_pClient->m_Snap.m_SpecInfo.m_Active)
 		m_TargetPos = m_pClient->m_LocalCharacterPos + m_MousePos;
 	else if(m_pClient->m_Snap.m_SpecInfo.m_Active && m_pClient->m_Snap.m_SpecInfo.m_UsePosition)
 		m_TargetPos = m_pClient->m_Snap.m_SpecInfo.m_Position + m_MousePos;
@@ -209,7 +209,8 @@ void CControls::OnRender()
 
 bool CControls::OnMouseMove(float x, float y)
 {
-	if(m_pClient->m_Snap.m_pGameInfoObj && m_pClient->m_Snap.m_pGameInfoObj->m_GameStateFlags&GAMESTATEFLAG_PAUSED)
+	if((m_pClient->m_Snap.m_pGameInfoObj && m_pClient->m_Snap.m_pGameInfoObj->m_GameStateFlags&GAMESTATEFLAG_PAUSED) ||
+		(m_pClient->m_Snap.m_SpecInfo.m_Active && m_pClient->m_pChat->IsActive()))
 		return false;
 
 	m_MousePos += vec2(x, y); // TODO: ugly
diff --git a/src/game/client/components/countryflags.cpp b/src/game/client/components/countryflags.cpp
index ef350cd7..b63b126f 100644
--- a/src/game/client/components/countryflags.cpp
+++ b/src/game/client/components/countryflags.cpp
@@ -6,6 +6,8 @@
 #include <engine/console.h>
 #include <engine/graphics.h>
 #include <engine/storage.h>
+#include <engine/textrender.h>
+#include <engine/shared/config.h>
 #include <engine/shared/linereader.h>
 
 #include "countryflags.h"
@@ -56,31 +58,57 @@ void CCountryFlags::LoadCountryflagsIndexfile()
 
 		// load the graphic file
 		char aBuf[128];
-		str_format(aBuf, sizeof(aBuf), "countryflags/%s.png", aOrigin);
 		CImageInfo Info;
-		if(!Graphics()->LoadPNG(&Info, aBuf, IStorage::TYPE_ALL))
+		if(g_Config.m_ClLoadCountryFlags)
 		{
-			char aMsg[128];
-			str_format(aMsg, sizeof(aMsg), "failed to load '%s'", aBuf);
-			Console()->Print(IConsole::OUTPUT_LEVEL_ADDINFO, "countryflags", aMsg);
-			continue;
+			str_format(aBuf, sizeof(aBuf), "countryflags/%s.png", aOrigin);
+			if(!Graphics()->LoadPNG(&Info, aBuf, IStorage::TYPE_ALL))
+			{
+				char aMsg[128];
+				str_format(aMsg, sizeof(aMsg), "failed to load '%s'", aBuf);
+				Console()->Print(IConsole::OUTPUT_LEVEL_ADDINFO, "countryflags", aMsg);
+				continue;
+			}
 		}
 
 		// add entry
 		CCountryFlag CountryFlag;
 		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);
-		Console()->Print(IConsole::OUTPUT_LEVEL_ADDINFO, "countryflags", aBuf);
-		m_aCountryFlags.add(CountryFlag);
+		if(g_Config.m_ClLoadCountryFlags)
+		{
+			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);
+		}
+		else
+			CountryFlag.m_Texture = -1;
+		if(g_Config.m_Debug)
+		{
+			str_format(aBuf, sizeof(aBuf), "loaded country flag '%s'", aOrigin);
+			Console()->Print(IConsole::OUTPUT_LEVEL_ADDINFO, "countryflags", aBuf);
+		}
+		m_aCountryFlags.add_unsorted(CountryFlag);
 	}
 	io_close(File);
+	m_aCountryFlags.sort_range();
 
-	mem_zero(m_CodeIndexLUT, sizeof(m_CodeIndexLUT));
+	// find index of default item
+	int DefaultIndex = 0, Index = 0;
+	for(sorted_array<CCountryFlag>::range r = m_aCountryFlags.all(); !r.empty(); r.pop_front(), ++Index)
+		if(r.front().m_CountryCode == -1)
+		{
+			DefaultIndex = Index;
+			break;
+		}
+	
+	// init LUT
+	if(DefaultIndex != 0)
+		for(int i = 0; i < CODE_RANGE; ++i)
+			m_CodeIndexLUT[i] = DefaultIndex;
+	else
+		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;
+		m_CodeIndexLUT[max(0, (m_aCountryFlags[i].m_CountryCode-CODE_LB)%CODE_RANGE)] = i;
 }
 
 void CCountryFlags::OnInit()
@@ -94,6 +122,7 @@ void CCountryFlags::OnInit()
 		CCountryFlag DummyEntry;
 		DummyEntry.m_CountryCode = -1;
 		DummyEntry.m_Texture = -1;
+		mem_zero(DummyEntry.m_aCountryCodeString, sizeof(DummyEntry.m_aCountryCodeString));
 		m_aCountryFlags.add(DummyEntry);
 	}
 }
@@ -105,10 +134,31 @@ int CCountryFlags::Num() const
 
 const CCountryFlags::CCountryFlag *CCountryFlags::GetByCountryCode(int CountryCode) const
 {
-	return GetByIndex(m_CodeIndexLUT[max(0, (CountryCode-CODE_LB)%CODE_RANGE)]-1);
+	return GetByIndex(m_CodeIndexLUT[max(0, (CountryCode-CODE_LB)%CODE_RANGE)]);
 }
 
 const CCountryFlags::CCountryFlag *CCountryFlags::GetByIndex(int Index) const
 {
 	return &m_aCountryFlags[max(0, Index%m_aCountryFlags.size())];
 }
+
+void CCountryFlags::Render(int CountryCode, const vec4 *pColor, float x, float y, float w, float h)
+{
+	const CCountryFlag *pFlag = GetByCountryCode(CountryCode);
+	if(pFlag->m_Texture != -1)
+	{
+		Graphics()->TextureSet(pFlag->m_Texture);
+		Graphics()->QuadsBegin();
+		Graphics()->SetColor(pColor->r, pColor->g, pColor->b, pColor->a);
+		IGraphics::CQuadItem QuadItem(x, y, w, h);
+		Graphics()->QuadsDrawTL(&QuadItem, 1);
+		Graphics()->QuadsEnd();
+	}
+	else
+	{
+		CTextCursor Cursor;
+		TextRender()->SetCursor(&Cursor, x, y, 10.0f, TEXTFLAG_RENDER|TEXTFLAG_STOP_AT_END);
+		Cursor.m_LineWidth = w;
+		TextRender()->TextEx(&Cursor, pFlag->m_aCountryCodeString, -1);
+	}
+}
diff --git a/src/game/client/components/countryflags.h b/src/game/client/components/countryflags.h
index ad24a762..df934821 100644
--- a/src/game/client/components/countryflags.h
+++ b/src/game/client/components/countryflags.h
@@ -23,7 +23,7 @@ public:
 	int Num() const;
 	const CCountryFlag *GetByCountryCode(int CountryCode) const;
 	const CCountryFlag *GetByIndex(int Index) const;
-	//int Find(int CountryCode) const;
+	void Render(int CountryCode, const vec4 *pColor, float x, float y, float w, float h);
 
 private:
 	enum
diff --git a/src/game/client/components/damageind.cpp b/src/game/client/components/damageind.cpp
index cba47666..0f521753 100644
--- a/src/game/client/components/damageind.cpp
+++ b/src/game/client/components/damageind.cpp
@@ -1,5 +1,6 @@
 /* (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/demo.h>
 #include <engine/graphics.h>
 #include <game/generated/protocol.h>
 #include <game/generated/client_data.h>
@@ -38,7 +39,7 @@ void CDamageInd::Create(vec2 Pos, vec2 Dir)
 	if (i)
 	{
 		i->m_Pos = Pos;
-		i->m_Life = 0.75f;
+		i->m_StartTime = Client()->LocalTime();
 		i->m_Dir = Dir*-1;
 		i->m_StartAngle = (( (float)rand()/(float)RAND_MAX) - 1.0f) * 2.0f * pi;
 	}
@@ -48,21 +49,39 @@ void CDamageInd::OnRender()
 {
 	Graphics()->TextureSet(g_pData->m_aImages[IMAGE_GAME].m_Id);
 	Graphics()->QuadsBegin();
+	static float s_LastLocalTime = Client()->LocalTime();
 	for(int i = 0; i < m_NumItems;)
 	{
-		vec2 Pos = mix(m_aItems[i].m_Pos+m_aItems[i].m_Dir*75.0f, m_aItems[i].m_Pos, clamp((m_aItems[i].m_Life-0.60f)/0.15f, 0.0f, 1.0f));
+		if(Client()->State() == IClient::STATE_DEMOPLAYBACK)
+		{
+			const IDemoPlayer::CInfo *pInfo = DemoPlayer()->BaseInfo();
+			if(pInfo->m_Paused)
+				m_aItems[i].m_StartTime += Client()->LocalTime()-s_LastLocalTime;
+			else
+				m_aItems[i].m_StartTime += (Client()->LocalTime()-s_LastLocalTime)*(1.0f-pInfo->m_Speed);
+		}
+		else
+		{
+			if(m_pClient->m_Snap.m_pGameInfoObj && m_pClient->m_Snap.m_pGameInfoObj->m_GameStateFlags&GAMESTATEFLAG_PAUSED)
+				m_aItems[i].m_StartTime += Client()->LocalTime()-s_LastLocalTime;
+		}
 
-		m_aItems[i].m_Life -= Client()->FrameTime();
-		if(m_aItems[i].m_Life < 0.0f)
+		float Life = 0.75f - (Client()->LocalTime() - m_aItems[i].m_StartTime);
+		if(Life < 0.0f)
 			DestroyI(&m_aItems[i]);
 		else
 		{
-			Graphics()->SetColor(1.0f,1.0f,1.0f, m_aItems[i].m_Life/0.1f);
-			Graphics()->QuadsSetRotation(m_aItems[i].m_StartAngle + m_aItems[i].m_Life * 2.0f);
+			vec2 Pos = mix(m_aItems[i].m_Pos+m_aItems[i].m_Dir*75.0f, m_aItems[i].m_Pos, clamp((Life-0.60f)/0.15f, 0.0f, 1.0f));
+			Graphics()->SetColor(1.0f,1.0f,1.0f, Life/0.1f);
+			Graphics()->QuadsSetRotation(m_aItems[i].m_StartAngle + Life * 2.0f);
 			RenderTools()->SelectSprite(SPRITE_STAR1);
 			RenderTools()->DrawSprite(Pos.x, Pos.y, 48.0f);
 			i++;
 		}
 	}
+	s_LastLocalTime = Client()->LocalTime();
 	Graphics()->QuadsEnd();
 }
+
+
+
diff --git a/src/game/client/components/damageind.h b/src/game/client/components/damageind.h
index 2f89e422..4a5975a8 100644
--- a/src/game/client/components/damageind.h
+++ b/src/game/client/components/damageind.h
@@ -12,7 +12,7 @@ class CDamageInd : public CComponent
 	{
 		vec2 m_Pos;
 		vec2 m_Dir;
-		float m_Life;
+		float m_StartTime;
 		float m_StartAngle;
 	};
 
diff --git a/src/game/client/components/effects.cpp b/src/game/client/components/effects.cpp
index 8554b7ba..933e7fe6 100644
--- a/src/game/client/components/effects.cpp
+++ b/src/game/client/components/effects.cpp
@@ -44,7 +44,7 @@ void CEffects::AirJump(vec2 Pos)
 	p.m_Pos = Pos + vec2(6.0f, 16.0f);
 	m_pClient->m_pParticles->Add(CParticles::GROUP_GENERAL, &p);
 
-	m_pClient->m_pSounds->Play(CSounds::CHN_WORLD, SOUND_PLAYER_AIRJUMP, 1.0f, Pos);
+	m_pClient->m_pSounds->PlayAt(CSounds::CHN_WORLD, SOUND_PLAYER_AIRJUMP, 1.0f, Pos);
 }
 
 void CEffects::DamageIndicator(vec2 Pos, vec2 Dir)
@@ -147,7 +147,7 @@ void CEffects::PlayerSpawn(vec2 Pos)
 		m_pClient->m_pParticles->Add(CParticles::GROUP_GENERAL, &p);
 
 	}
-	m_pClient->m_pSounds->Play(CSounds::CHN_WORLD, SOUND_PLAYER_SPAWN, 1.0f, Pos);
+	m_pClient->m_pSounds->PlayAt(CSounds::CHN_WORLD, SOUND_PLAYER_SPAWN, 1.0f, Pos);
 }
 
 void CEffects::PlayerDeath(vec2 Pos, int ClientID)
@@ -242,7 +242,7 @@ void CEffects::HammerHit(vec2 Pos)
 	p.m_EndSize = 0;
 	p.m_Rot = frandom()*pi*2;
 	m_pClient->m_pParticles->Add(CParticles::GROUP_EXPLOSIONS, &p);
-	m_pClient->m_pSounds->Play(CSounds::CHN_WORLD, SOUND_HAMMER_HIT, 1.0f, Pos);
+	m_pClient->m_pSounds->PlayAt(CSounds::CHN_WORLD, SOUND_HAMMER_HIT, 1.0f, Pos);
 }
 
 void CEffects::OnRender()
diff --git a/src/game/client/components/emoticon.cpp b/src/game/client/components/emoticon.cpp
index b2f48b80..9e2a80cf 100644
--- a/src/game/client/components/emoticon.cpp
+++ b/src/game/client/components/emoticon.cpp
@@ -111,14 +111,14 @@ void CEmoticon::OnRender()
 
 	m_WasActive = true;
 
-	if (length(m_SelectorMouse) > 140)
-		m_SelectorMouse = normalize(m_SelectorMouse) * 140;
+	if (length(m_SelectorMouse) > 170.0f)
+		m_SelectorMouse = normalize(m_SelectorMouse) * 170.0f;
 
 	float SelectedAngle = GetAngle(m_SelectorMouse) + 2*pi/24;
 	if (SelectedAngle < 0)
 		SelectedAngle += 2*pi;
 
-	if (length(m_SelectorMouse) > 100)
+	if (length(m_SelectorMouse) > 110.0f)
 		m_SelectedEmote = (int)(SelectedAngle / (2*pi) * NUM_EMOTICONS);
 
 	CUIRect Screen = *UI()->Screen();
@@ -130,7 +130,7 @@ void CEmoticon::OnRender()
 	Graphics()->TextureSet(-1);
 	Graphics()->QuadsBegin();
 	Graphics()->SetColor(0,0,0,0.3f);
-	DrawCircle(Screen.w/2, Screen.h/2, 160, 64);
+	DrawCircle(Screen.w/2, Screen.h/2, 190.0f, 64);
 	Graphics()->QuadsEnd();
 
 	Graphics()->TextureSet(g_pData->m_aImages[IMAGE_EMOTICONS].m_Id);
@@ -144,10 +144,10 @@ void CEmoticon::OnRender()
 
 		bool Selected = m_SelectedEmote == i;
 
-		float Size = Selected ? 80 : 32;
+		float Size = Selected ? 80.0f : 50.0f;
 
-		float NudgeX = 120 * cosf(Angle);
-		float NudgeY = 120 * sinf(Angle);
+		float NudgeX = 150.0f * cosf(Angle);
+		float NudgeY = 150.0f * sinf(Angle);
 		RenderTools()->SelectSprite(SPRITE_OOP + i);
 		IGraphics::CQuadItem QuadItem(Screen.w/2 + NudgeX, Screen.h/2 + NudgeY, Size, Size);
 		Graphics()->QuadsDraw(&QuadItem, 1);
diff --git a/src/game/client/components/hud.cpp b/src/game/client/components/hud.cpp
index 11343912..6881728c 100644
--- a/src/game/client/components/hud.cpp
+++ b/src/game/client/components/hud.cpp
@@ -59,6 +59,18 @@ void CHud::RenderGameTimer()
 	}
 }
 
+void CHud::RenderPauseNotification()
+{
+	if(m_pClient->m_Snap.m_pGameInfoObj->m_GameStateFlags&GAMESTATEFLAG_PAUSED &&
+		!(m_pClient->m_Snap.m_pGameInfoObj->m_GameStateFlags&GAMESTATEFLAG_GAMEOVER))
+	{
+		const char *pText = Localize("Game paused");
+		float FontSize = 20.0f;
+		float w = TextRender()->TextWidth(0, FontSize,pText, -1);
+		TextRender()->Text(0, 150.0f*Graphics()->ScreenAspect()+-w/2.0f, 50.0f, FontSize, pText, -1);
+	}
+}
+
 void CHud::RenderSuddenDeath()
 {
 	if(m_pClient->m_Snap.m_pGameInfoObj->m_GameStateFlags&GAMESTATEFLAG_SUDDENDEATH)
@@ -78,6 +90,7 @@ void CHud::RenderScoreHud()
 	{
 		int GameFlags = m_pClient->m_Snap.m_pGameInfoObj->m_GameFlags;
 		float Whole = 300*Graphics()->ScreenAspect();
+		float StartY = 229.0f;
 
 		if(GameFlags&GAMEFLAG_TEAMS && m_pClient->m_Snap.m_pGameDataObj)
 		{
@@ -100,15 +113,15 @@ void CHud::RenderScoreHud()
 					Graphics()->SetColor(1.0f, 0.0f, 0.0f, 0.25f);
 				else
 					Graphics()->SetColor(0.0f, 0.0f, 1.0f, 0.25f);
-				RenderTools()->DrawRoundRectExt(Whole-ScoreWidthMax-ImageSize-2*Split, 245.0f+t*20, ScoreWidthMax+ImageSize+2*Split, 18.0f, 5.0f, CUI::CORNER_L);
+				RenderTools()->DrawRoundRectExt(Whole-ScoreWidthMax-ImageSize-2*Split, StartY+t*20, ScoreWidthMax+ImageSize+2*Split, 18.0f, 5.0f, CUI::CORNER_L);
 				Graphics()->QuadsEnd();
 
 				// draw score
-				TextRender()->Text(0, Whole-ScoreWidthMax+(ScoreWidthMax-aScoreTeamWidth[t])/2-Split, 245.0f+t*20, 14.0f, aScoreTeam[t], -1);
+				TextRender()->Text(0, Whole-ScoreWidthMax+(ScoreWidthMax-aScoreTeamWidth[t])/2-Split, StartY+t*20, 14.0f, aScoreTeam[t], -1);
 
 				if(GameFlags&GAMEFLAG_FLAGS)
 				{
-					int BlinkTimer = (m_pClient->m_FlagDropTick[t] != 0 && 
+					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)))
 					{
@@ -117,7 +130,7 @@ void CHud::RenderScoreHud()
 						Graphics()->TextureSet(g_pData->m_aImages[IMAGE_GAME].m_Id);
 						Graphics()->QuadsBegin();
 						RenderTools()->SelectSprite(t==0?SPRITE_FLAG_RED:SPRITE_FLAG_BLUE);
-						IGraphics::CQuadItem QuadItem(Whole-ScoreWidthMax-ImageSize, 246.0f+t*20, ImageSize/2, ImageSize);
+						IGraphics::CQuadItem QuadItem(Whole-ScoreWidthMax-ImageSize, StartY+1.0f+t*20, ImageSize/2, ImageSize);
 						Graphics()->QuadsDrawTL(&QuadItem, 1);
 						Graphics()->QuadsEnd();
 					}
@@ -126,16 +139,17 @@ void CHud::RenderScoreHud()
 						// draw name of the flag holder
 						int ID = FlagCarrier[t]%MAX_CLIENTS;
 						const char *pName = m_pClient->m_aClients[ID].m_aName;
-						float w = TextRender()->TextWidth(0, 10.0f, pName, -1);
-						TextRender()->Text(0, Whole-ScoreWidthMax-ImageSize-3*Split-w, 247.0f+t*20, 10.0f, pName, -1);
+						float w = TextRender()->TextWidth(0, 8.0f, pName, -1);
+						TextRender()->Text(0, min(Whole-w-1.0f, Whole-ScoreWidthMax-ImageSize-2*Split), StartY+(t+1)*20.0f-3.0f, 8.0f, pName, -1);
 
 						// draw tee of the flag holder
 						CTeeRenderInfo Info = m_pClient->m_aClients[ID].m_RenderInfo;
 						Info.m_Size = 18.0f;
 						RenderTools()->RenderTee(CAnimState::GetIdle(), &Info, EMOTE_NORMAL, vec2(1,0),
-							vec2(Whole-ScoreWidthMax-Info.m_Size/2-Split, 246.0f+Info.m_Size/2+t*20));
+							vec2(Whole-ScoreWidthMax-Info.m_Size/2-Split, StartY+1.0f+Info.m_Size/2+t*20));
 					}
 				}
+				StartY += 8.0f;
 			}
 		}
 		else
@@ -191,25 +205,33 @@ void CHud::RenderScoreHud()
 					Graphics()->SetColor(1.0f, 1.0f, 1.0f, 0.25f);
 				else
 					Graphics()->SetColor(0.0f, 0.0f, 0.0f, 0.25f);
-				RenderTools()->DrawRoundRectExt(Whole-ScoreWidthMax-ImageSize-2*Split-PosSize, 245.0f+t*20, ScoreWidthMax+ImageSize+2*Split+PosSize, 18.0f, 5.0f, CUI::CORNER_L);
+				RenderTools()->DrawRoundRectExt(Whole-ScoreWidthMax-ImageSize-2*Split-PosSize, StartY+t*20, ScoreWidthMax+ImageSize+2*Split+PosSize, 18.0f, 5.0f, CUI::CORNER_L);
 				Graphics()->QuadsEnd();
 
 				// draw score
-				TextRender()->Text(0, Whole-ScoreWidthMax+(ScoreWidthMax-aScoreWidth[t])/2-Split, 245.0f+t*20, 14.0f, aScore[t], -1);
+				TextRender()->Text(0, Whole-ScoreWidthMax+(ScoreWidthMax-aScoreWidth[t])/2-Split, StartY+t*20, 14.0f, aScore[t], -1);
 
-				// draw tee
 				if(apPlayerInfo[t])
  				{
-					CTeeRenderInfo Info = m_pClient->m_aClients[apPlayerInfo[t]->m_ClientID].m_RenderInfo;
+					// draw name
+					int ID = apPlayerInfo[t]->m_ClientID;
+					const char *pName = m_pClient->m_aClients[ID].m_aName;
+					float w = TextRender()->TextWidth(0, 8.0f, pName, -1);
+					TextRender()->Text(0, min(Whole-w-1.0f, Whole-ScoreWidthMax-ImageSize-2*Split-PosSize), StartY+(t+1)*20.0f-3.0f, 8.0f, pName, -1);
+
+					// draw tee
+					CTeeRenderInfo Info = m_pClient->m_aClients[ID].m_RenderInfo;
  					Info.m_Size = 18.0f;
  					RenderTools()->RenderTee(CAnimState::GetIdle(), &Info, EMOTE_NORMAL, vec2(1,0),
- 						vec2(Whole-ScoreWidthMax-Info.m_Size/2-Split, 246.0f+Info.m_Size/2+t*20));
+ 						vec2(Whole-ScoreWidthMax-Info.m_Size/2-Split, StartY+1.0f+Info.m_Size/2+t*20));
 				}
 
 				// draw position
 				char aBuf[32];
 				str_format(aBuf, sizeof(aBuf), "%d.", aPos[t]);
-				TextRender()->Text(0, Whole-ScoreWidthMax-ImageSize-Split-PosSize, 247.0f+t*20, 10.0f, aBuf, -1);
+				TextRender()->Text(0, Whole-ScoreWidthMax-ImageSize-Split-PosSize, StartY+2.0f+t*20, 10.0f, aBuf, -1);
+
+				StartY += 8.0f;
 			}
 		}
 	}
@@ -248,7 +270,7 @@ void CHud::RenderFps()
 	if(g_Config.m_ClShowfps)
 	{
 		// calculate avg. fps
-		float FPS = 1.0f / Client()->FrameTime();
+		float FPS = 1.0f / Client()->RenderFrameTime();
 		m_AverageFPS = (m_AverageFPS*(1.0f-(1.0f/m_AverageFPS))) + (FPS*(1.0f/m_AverageFPS));
 		char Buf[512];
 		str_format(Buf, sizeof(Buf), "%d", (int)m_AverageFPS);
@@ -440,6 +462,7 @@ void CHud::OnRender()
 		}
 
 		RenderGameTimer();
+		RenderPauseNotification();
 		RenderSuddenDeath();
 		RenderScoreHud();
 		RenderWarmupTimer();
diff --git a/src/game/client/components/hud.h b/src/game/client/components/hud.h
index f0250c7b..34720854 100644
--- a/src/game/client/components/hud.h
+++ b/src/game/client/components/hud.h
@@ -17,6 +17,7 @@ class CHud : public CComponent
 	void RenderVoting();
 	void RenderHealthAndAmmo(const CNetObj_Character *pCharacter);
 	void RenderGameTimer();
+	void RenderPauseNotification();
 	void RenderSuddenDeath();
 	void RenderScoreHud();
 	void RenderSpectatorHud();
diff --git a/src/game/client/components/items.cpp b/src/game/client/components/items.cpp
index 827b28a7..e1032a51 100644
--- a/src/game/client/components/items.cpp
+++ b/src/game/client/components/items.cpp
@@ -17,12 +17,11 @@
 
 void CItems::OnReset()
 {
-	ExtraProjectilesNum = 0;
+	m_NumExtraProjectiles = 0;
 }
 
 void CItems::RenderProjectile(const CNetObj_Projectile *pCurrent, int ItemID)
 {
-
 	// get positions
 	float Curvature = 0;
 	float Speed = 0;
@@ -42,7 +41,10 @@ void CItems::RenderProjectile(const CNetObj_Projectile *pCurrent, int ItemID)
 		Speed = m_pClient->m_Tuning.m_GunSpeed;
 	}
 
-	float Ct = (Client()->PrevGameTick()-pCurrent->m_StartTick)/(float)SERVER_TICK_SPEED + Client()->GameTickTime();
+	static float s_LastGameTickTime = Client()->GameTickTime();
+	if(m_pClient->m_Snap.m_pGameInfoObj && !(m_pClient->m_Snap.m_pGameInfoObj->m_GameStateFlags&GAMESTATEFLAG_PAUSED))
+		s_LastGameTickTime = Client()->GameTickTime();
+	float Ct = (Client()->PrevGameTick()-pCurrent->m_StartTick)/(float)SERVER_TICK_SPEED + s_LastGameTickTime;
 	if(Ct < 0)
 		return; // projectile havn't been shot yet
 
@@ -64,28 +66,27 @@ void CItems::RenderProjectile(const CNetObj_Projectile *pCurrent, int ItemID)
 	if(pCurrent->m_Type == WEAPON_GRENADE)
 	{
 		m_pClient->m_pEffects->SmokeTrail(Pos, Vel*-1);
-		m_pClient->m_pFlow->Add(Pos, Vel*1000*Client()->FrameTime(), 10.0f);
+		static float s_Time = 0.0f;
+		static float s_LastLocalTime = Client()->LocalTime();
 
 		if(Client()->State() == IClient::STATE_DEMOPLAYBACK)
 		{
 			const IDemoPlayer::CInfo *pInfo = DemoPlayer()->BaseInfo();
-			static float Time = 0;
-			static float LastLocalTime = Client()->LocalTime();
-
 			if(!pInfo->m_Paused)
-				Time += (Client()->LocalTime()-LastLocalTime)*pInfo->m_Speed;
-
-			Graphics()->QuadsSetRotation(Time*pi*2*2 + ItemID);
-
-			LastLocalTime = Client()->LocalTime();
+				s_Time += (Client()->LocalTime()-s_LastLocalTime)*pInfo->m_Speed;
 		}
 		else
-			Graphics()->QuadsSetRotation(Client()->LocalTime()*pi*2*2 + ItemID);
+		{
+			if(m_pClient->m_Snap.m_pGameInfoObj && !(m_pClient->m_Snap.m_pGameInfoObj->m_GameStateFlags&GAMESTATEFLAG_PAUSED))
+				s_Time += Client()->LocalTime()-s_LastLocalTime;
+		}
+
+		Graphics()->QuadsSetRotation(s_Time*pi*2*2 + ItemID);
+		s_LastLocalTime = Client()->LocalTime();
 	}
 	else
 	{
 		m_pClient->m_pEffects->BulletTrail(Pos);
-		m_pClient->m_pFlow->Add(Pos, Vel*1000*Client()->FrameTime(), 10.0f);
 
 		if(length(Vel) > 0.00001f)
 			Graphics()->QuadsSetRotation(GetAngle(Vel));
@@ -133,26 +134,23 @@ void CItems::RenderPickup(const CNetObj_Pickup *pPrev, const CNetObj_Pickup *pCu
 
 	Graphics()->QuadsSetRotation(Angle);
 
+	static float s_Time = 0.0f;
+	static float s_LastLocalTime = Client()->LocalTime();
 	float Offset = Pos.y/32.0f + Pos.x/32.0f;
 	if(Client()->State() == IClient::STATE_DEMOPLAYBACK)
 	{
 		const IDemoPlayer::CInfo *pInfo = DemoPlayer()->BaseInfo();
-		static float Time = 0;
-		static float LastLocalTime = Client()->LocalTime();
-
 		if(!pInfo->m_Paused)
-			Time += (Client()->LocalTime()-LastLocalTime)*pInfo->m_Speed;
-
-		Pos.x += cosf(Time*2.0f+Offset)*2.5f;
-		Pos.y += sinf(Time*2.0f+Offset)*2.5f;
-
-		LastLocalTime = Client()->LocalTime();
+			s_Time += (Client()->LocalTime()-s_LastLocalTime)*pInfo->m_Speed;
 	}
 	else
 	{
-		Pos.x += cosf(Client()->LocalTime()*2.0f+Offset)*2.5f;
-		Pos.y += sinf(Client()->LocalTime()*2.0f+Offset)*2.5f;
-	}
+		if(m_pClient->m_Snap.m_pGameInfoObj && !(m_pClient->m_Snap.m_pGameInfoObj->m_GameStateFlags&GAMESTATEFLAG_PAUSED))
+			s_Time += Client()->LocalTime()-s_LastLocalTime;
+ 	}
+	Pos.x += cosf(s_Time*2.0f+Offset)*2.5f;
+	Pos.y += sinf(s_Time*2.0f+Offset)*2.5f;
+	s_LastLocalTime = Client()->LocalTime();
 	RenderTools()->DrawSprite(Pos.x, Pos.y, Size);
 	Graphics()->QuadsEnd();
 }
@@ -310,23 +308,23 @@ void CItems::OnRender()
 	}
 
 	// render extra projectiles
-	for(int i = 0; i < ExtraProjectilesNum; i++)
+	for(int i = 0; i < m_NumExtraProjectiles; i++)
 	{
-		if(aExtraProjectiles[i].m_StartTick < Client()->GameTick())
+		if(m_aExtraProjectiles[i].m_StartTick < Client()->GameTick())
 		{
-			aExtraProjectiles[i] = aExtraProjectiles[ExtraProjectilesNum-1];
-			ExtraProjectilesNum--;
+			m_aExtraProjectiles[i] = m_aExtraProjectiles[m_NumExtraProjectiles-1];
+			m_NumExtraProjectiles--;
 		}
 		else
-			RenderProjectile(&aExtraProjectiles[i], 0);
+			RenderProjectile(&m_aExtraProjectiles[i], 0);
 	}
 }
 
 void CItems::AddExtraProjectile(CNetObj_Projectile *pProj)
 {
-	if(ExtraProjectilesNum != MAX_EXTRA_PROJECTILES)
+	if(m_NumExtraProjectiles != MAX_EXTRA_PROJECTILES)
 	{
-		aExtraProjectiles[ExtraProjectilesNum] = *pProj;
-		ExtraProjectilesNum++;
+		m_aExtraProjectiles[m_NumExtraProjectiles] = *pProj;
+		m_NumExtraProjectiles++;
 	}
 }
diff --git a/src/game/client/components/items.h b/src/game/client/components/items.h
index 604c1b1c..caf6176b 100644
--- a/src/game/client/components/items.h
+++ b/src/game/client/components/items.h
@@ -11,8 +11,8 @@ class CItems : public CComponent
 		MAX_EXTRA_PROJECTILES=32,
 	};
 
-	CNetObj_Projectile aExtraProjectiles[MAX_EXTRA_PROJECTILES];
-	int ExtraProjectilesNum;
+	CNetObj_Projectile m_aExtraProjectiles[MAX_EXTRA_PROJECTILES];
+	int m_NumExtraProjectiles;
 
 	void RenderProjectile(const CNetObj_Projectile *pCurrent, int ItemID);
 	void RenderPickup(const CNetObj_Pickup *pPrev, const CNetObj_Pickup *pCurrent);
diff --git a/src/game/client/components/maplayers.cpp b/src/game/client/components/maplayers.cpp
index 096f9cc5..db74b165 100644
--- a/src/game/client/components/maplayers.cpp
+++ b/src/game/client/components/maplayers.cpp
@@ -22,6 +22,9 @@ CMapLayers::CMapLayers(int t)
 {
 	m_Type = t;
 	m_pLayers = 0;
+	m_CurrentLocalTick = 0;
+	m_LastLocalTick = 0;
+	m_EnvelopeUpdate = false;
 }
 
 void CMapLayers::OnInit()
@@ -29,6 +32,17 @@ void CMapLayers::OnInit()
 	m_pLayers = Layers();
 }
 
+void CMapLayers::EnvelopeUpdate()
+{
+	if(Client()->State() == IClient::STATE_DEMOPLAYBACK)
+	{
+		const IDemoPlayer::CInfo *pInfo = DemoPlayer()->BaseInfo();
+		m_CurrentLocalTick = pInfo->m_CurrentTick;
+		m_LastLocalTick = pInfo->m_CurrentTick;
+		m_EnvelopeUpdate = true;
+	}
+}
+
 
 void CMapLayers::MapScreenToGroup(float CenterX, float CenterY, CMapItemGroup *pGroup)
 {
@@ -63,24 +77,42 @@ 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;
+	static float s_Time = 0.0f;
+	static float s_LastLocalTime = pThis->Client()->LocalTime();
 	if(pThis->Client()->State() == IClient::STATE_DEMOPLAYBACK)
 	{
 		const IDemoPlayer::CInfo *pInfo = pThis->DemoPlayer()->BaseInfo();
-		static int LastLocalTick = pInfo->m_CurrentTick;
-
-		if(!pInfo->m_Paused)
-			Time += (pInfo->m_CurrentTick-LastLocalTick) / (float)pThis->Client()->GameTickSpeed() * pInfo->m_Speed;
+		
+		if(!pInfo->m_Paused || pThis->m_EnvelopeUpdate)
+		{
+			if(pThis->m_CurrentLocalTick != pInfo->m_CurrentTick)
+			{
+				pThis->m_LastLocalTick = pThis->m_CurrentLocalTick;
+				pThis->m_CurrentLocalTick = pInfo->m_CurrentTick;
+			}
 
-		pThis->RenderTools()->RenderEvalEnvelope(pPoints+pItem->m_StartPoint, pItem->m_NumPoints, 4, Time+TimeOffset, pChannels);
+			s_Time = mix(pThis->m_LastLocalTick / (float)pThis->Client()->GameTickSpeed(),
+						pThis->m_CurrentLocalTick / (float)pThis->Client()->GameTickSpeed(),
+						pThis->Client()->IntraGameTick());
+		}
 
-		LastLocalTick = pInfo->m_CurrentTick;
+		pThis->RenderTools()->RenderEvalEnvelope(pPoints+pItem->m_StartPoint, pItem->m_NumPoints, 4, s_Time+TimeOffset, pChannels);
 	}
 	else
 	{
-		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);
+		if(pThis->m_pClient->m_Snap.m_pGameInfoObj && !(pThis->m_pClient->m_Snap.m_pGameInfoObj->m_GameStateFlags&GAMESTATEFLAG_PAUSED))
+		{
+			if(pItem->m_Version < 2 || pItem->m_Synchronized)
+			{
+				s_Time = mix((pThis->Client()->PrevGameTick()-pThis->m_pClient->m_Snap.m_pGameInfoObj->m_RoundStartTick) / (float)pThis->Client()->GameTickSpeed(),
+							(pThis->Client()->GameTick()-pThis->m_pClient->m_Snap.m_pGameInfoObj->m_RoundStartTick) / (float)pThis->Client()->GameTickSpeed(),
+							pThis->Client()->IntraGameTick());
+			}
+			else
+				s_Time += pThis->Client()->LocalTime()-s_LastLocalTime;
+		}
+		pThis->RenderTools()->RenderEvalEnvelope(pPoints+pItem->m_StartPoint, pItem->m_NumPoints, 4, s_Time+TimeOffset, pChannels);
+		s_LastLocalTime = pThis->Client()->LocalTime();
 	}
 }
 
@@ -160,16 +192,11 @@ void CMapLayers::OnRender()
 				IOHANDLE File = Storage()->OpenFile(aFilename, IOFLAG_WRITE, IStorage::TYPE_SAVE);
 				if(File)
 				{
-					#if defined(CONF_FAMILY_WINDOWS)
-						static const char Newline[] = "\r\n";
-					#else
-						static const char Newline[] = "\n";
-					#endif
 					for(int y = 0; y < pTMap->m_Height; y++)
 					{
 						for(int x = 0; x < pTMap->m_Width; x++)
 							io_write(File, &(pTiles[y*pTMap->m_Width + x].m_Index), sizeof(pTiles[y*pTMap->m_Width + x].m_Index));
-						io_write(File, Newline, sizeof(Newline)-1);
+						io_write_newline(File);
 					}
 					io_close(File);
 				}
diff --git a/src/game/client/components/maplayers.h b/src/game/client/components/maplayers.h
index 694633ee..d0efcfc7 100644
--- a/src/game/client/components/maplayers.h
+++ b/src/game/client/components/maplayers.h
@@ -8,6 +8,9 @@ class CMapLayers : public CComponent
 {
 	CLayers *m_pLayers;	// todo refactor: maybe remove it and access it through client*
 	int m_Type;
+	int m_CurrentLocalTick;
+	int m_LastLocalTick;
+	bool m_EnvelopeUpdate;
 
 	void MapScreenToGroup(float CenterX, float CenterY, CMapItemGroup *pGroup);
 	static void EnvelopeEval(float TimeOffset, int Env, float *pChannels, void *pUser);
@@ -21,6 +24,8 @@ public:
 	CMapLayers(int Type);
 	virtual void OnInit();
 	virtual void OnRender();
+
+	void EnvelopeUpdate();
 };
 
 #endif
diff --git a/src/game/client/components/menus.cpp b/src/game/client/components/menus.cpp
index 8f330f78..d27307f4 100644
--- a/src/game/client/components/menus.cpp
+++ b/src/game/client/components/menus.cpp
@@ -35,11 +35,10 @@ vec4 CMenus::ms_GuiColor;
 vec4 CMenus::ms_ColorTabbarInactiveOutgame;
 vec4 CMenus::ms_ColorTabbarActiveOutgame;
 vec4 CMenus::ms_ColorTabbarInactive;
-vec4 CMenus::ms_ColorTabbarActive;
+vec4 CMenus::ms_ColorTabbarActive = vec4(0,0,0,0.5f);
 vec4 CMenus::ms_ColorTabbarInactiveIngame;
 vec4 CMenus::ms_ColorTabbarActiveIngame;
 
-
 float CMenus::ms_ButtonHeight = 25.0f;
 float CMenus::ms_ListheaderHeight = 17.0f;
 float CMenus::ms_FontmodHeight = 0.8f;
@@ -95,14 +94,16 @@ int CMenus::DoButton_Icon(int ImageId, int SpriteId, const CUIRect *pRect)
 	return 0;
 }
 
-int CMenus::DoButton_Toggle(const void *pID, int Checked, const CUIRect *pRect)
+int CMenus::DoButton_Toggle(const void *pID, int Checked, const CUIRect *pRect, bool Active)
 {
 	Graphics()->TextureSet(g_pData->m_aImages[IMAGE_GUIBUTTONS].m_Id);
 	Graphics()->QuadsBegin();
+	if(!Active)
+		Graphics()->SetColor(1.0f, 1.0f, 1.0f, 0.5f);
 	RenderTools()->SelectSprite(Checked?SPRITE_GUIBUTTON_ON:SPRITE_GUIBUTTON_OFF);
 	IGraphics::CQuadItem QuadItem(pRect->x, pRect->y, pRect->w, pRect->h);
 	Graphics()->QuadsDrawTL(&QuadItem, 1);
-	if((UI()->HotItem() == pID))
+	if(UI()->HotItem() == pID && Active)
 	{
 		RenderTools()->SelectSprite(SPRITE_GUIBUTTON_HOVER);
 		IGraphics::CQuadItem QuadItem(pRect->x, pRect->y, pRect->w, pRect->h);
@@ -110,7 +111,7 @@ int CMenus::DoButton_Toggle(const void *pID, int Checked, const CUIRect *pRect)
 	}
 	Graphics()->QuadsEnd();
 
-	return UI()->DoButtonLogic(pID, "", Checked, pRect);
+	return Active ? UI()->DoButtonLogic(pID, "", Checked, pRect) : 0;
 }
 
 int CMenus::DoButton_Menu(const void *pID, const char *pText, int Checked, const CUIRect *pRect)
@@ -628,6 +629,8 @@ int CMenus::RenderMenubar(CUIRect r)
 
 void CMenus::RenderLoading()
 {
+	// TODO: not supported right now due to separate render thread
+
 	static int64 LastLoadRender = 0;
 	float Percent = m_LoadCurrent++/(float)m_LoadTotal;
 
@@ -1108,7 +1111,7 @@ int CMenus::Render()
 			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;
@@ -1131,12 +1134,8 @@ int CMenus::Render()
 					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();
+					vec4 Color(1.0f, 1.0f, 1.0f, 1.0f);
+					m_pClient->m_pCountryFlags->Render(pEntry->m_CountryCode, &Color, Item.m_Rect.x, Item.m_Rect.y, Item.m_Rect.w, Item.m_Rect.h);
 					UI()->DoLabel(&Label, pEntry->m_aCountryCodeString, 10.0f, 0);
 				}
 			}
@@ -1402,8 +1401,8 @@ void CMenus::OnStateChange(int NewState, int OldState)
 
 	if(NewState == IClient::STATE_OFFLINE)
 	{
-		if(OldState >= IClient::STATE_ONLINE)
-			m_pClient->m_pSounds->Play(CSounds::CHN_MUSIC, SOUND_MENU, 1.0f, vec2(0, 0));
+		if(OldState >= IClient::STATE_ONLINE && NewState < IClient::STATE_QUITING)
+			m_pClient->m_pSounds->Play(CSounds::CHN_MUSIC, SOUND_MENU, 1.0f);
 		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 585fb91f..0125278c 100644
--- a/src/game/client/components/menus.h
+++ b/src/game/client/components/menus.h
@@ -40,7 +40,7 @@ class CMenus : public CComponent
 
 	int DoButton_DemoPlayer(const void *pID, const char *pText, int Checked, const CUIRect *pRect);
 	int DoButton_Sprite(const void *pID, int ImageID, int SpriteID, int Checked, const CUIRect *pRect, int Corners);
-	int DoButton_Toggle(const void *pID, int Checked, const CUIRect *pRect);
+	int DoButton_Toggle(const void *pID, int Checked, const CUIRect *pRect, bool Active);
 	int DoButton_Menu(const void *pID, const char *pText, int Checked, const CUIRect *pRect);
 	int DoButton_MenuTab(const void *pID, const char *pText, int Checked, const CUIRect *pRect, int Corners);
 
diff --git a/src/game/client/components/menus_browser.cpp b/src/game/client/components/menus_browser.cpp
index 8501c67d..a9c434b3 100644
--- a/src/game/client/components/menus_browser.cpp
+++ b/src/game/client/components/menus_browser.cpp
@@ -516,7 +516,7 @@ 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;
@@ -563,16 +563,12 @@ void CMenus::RenderServerbrowserFilters(CUIRect View)
 		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();
+		vec4 Color(1.0f, 1.0f, 1.0f, g_Config.m_BrFilterCountry?1.0f: 0.5f);
+		m_pClient->m_pCountryFlags->Render(g_Config.m_BrFilterCountryIndex, &Color, Rect.x, Rect.y, Rect.w, Rect.h);
 
 		if(g_Config.m_BrFilterCountry && UI()->DoButtonLogic(&g_Config.m_BrFilterCountryIndex, "", 0, &Rect))
 			m_Popup = POPUP_COUNTRY;
@@ -766,12 +762,8 @@ void CMenus::RenderServerbrowserServerDetail(CUIRect View)
 				TextRender()->TextEx(&Cursor, pClan, -1);
 
 			// flag
-			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);
-			Graphics()->QuadsDrawTL(&QuadItem, 1);
-			Graphics()->QuadsEnd();
+			vec4 Color(1.0f, 1.0f, 1.0f, 0.5f);
+			m_pClient->m_pCountryFlags->Render(pSelectedServer->m_aClients[i].m_Country, &Color, Flag.x, Flag.y, Flag.w, Flag.h);
 		}
 	}
 }
diff --git a/src/game/client/components/menus_demo.cpp b/src/game/client/components/menus_demo.cpp
index f3a75f0c..40a9e5b5 100644
--- a/src/game/client/components/menus_demo.cpp
+++ b/src/game/client/components/menus_demo.cpp
@@ -17,6 +17,7 @@
 
 #include <game/generated/client_data.h>
 
+#include "maplayers.h"
 #include "menus.h"
 
 int CMenus::DoButton_DemoPlayer(const void *pID, const char *pText, int Checked, const CUIRect *pRect)
@@ -85,15 +86,28 @@ void CMenus::RenderDemoPlayer(CUIRect MainView)
 		void *id = &s_SeekBarID;
 		char aBuffer[128];
 
+		// draw seek bar
 		RenderTools()->DrawUIRect(&SeekBar, vec4(0,0,0,0.5f), CUI::CORNER_ALL, 5.0f);
 
+		// draw filled bar
 		float Amount = CurrentTick/(float)TotalTicks;
-
 		CUIRect FilledBar = SeekBar;
 		FilledBar.w = 10.0f + (FilledBar.w-10.0f)*Amount;
-
 		RenderTools()->DrawUIRect(&FilledBar, vec4(1,1,1,0.5f), CUI::CORNER_ALL, 5.0f);
 
+		// draw markers
+		for(int i = 0; i < pInfo->m_NumTimelineMarkers; i++)
+		{
+			float Ratio = (pInfo->m_aTimelineMarkers[i]-pInfo->m_FirstTick) / (float)TotalTicks;
+			Graphics()->TextureSet(-1);
+			Graphics()->QuadsBegin();
+			Graphics()->SetColor(1.0f, 1.0f, 1.0f, 1.0f);
+			IGraphics::CQuadItem QuadItem(SeekBar.x + (SeekBar.w-10.0f)*Ratio, SeekBar.y, UI()->PixelSize(), SeekBar.h);
+			Graphics()->QuadsDrawTL(&QuadItem, 1);
+			Graphics()->QuadsEnd();
+		}
+
+		// draw time
 		str_format(aBuffer, sizeof(aBuffer), "%d:%02d / %d:%02d",
 			CurrentTick/SERVER_TICK_SPEED/60, (CurrentTick/SERVER_TICK_SPEED)%60,
 			TotalTicks/SERVER_TICK_SPEED/60, (TotalTicks/SERVER_TICK_SPEED)%60);
@@ -117,6 +131,8 @@ void CMenus::RenderDemoPlayer(CUIRect MainView)
 					m_pClient->m_SuppressEvents = true;
 					DemoPlayer()->SetPos(Amount);
 					m_pClient->m_SuppressEvents = false;
+					m_pClient->m_pMapLayersBackGround->EnvelopeUpdate();
+					m_pClient->m_pMapLayersForeGround->EnvelopeUpdate();
 				}
 			}
 		}
diff --git a/src/game/client/components/menus_ingame.cpp b/src/game/client/components/menus_ingame.cpp
index 1afef922..a9cf35e9 100644
--- a/src/game/client/components/menus_ingame.cpp
+++ b/src/game/client/components/menus_ingame.cpp
@@ -149,7 +149,11 @@ void CMenus::RenderPlayers(CUIRect MainView)
 	static int s_aPlayerIDs[MAX_CLIENTS][2] = {{0}};
 	for(int i = 0, Count = 0; i < MAX_CLIENTS; ++i)
 	{
-		if(!m_pClient->m_Snap.m_paPlayerInfos[i] || i == m_pClient->m_Snap.m_LocalClientID)
+		if(!m_pClient->m_Snap.m_paInfoByTeam[i])
+			continue;
+
+		int Index = m_pClient->m_Snap.m_paInfoByTeam[i]->m_ClientID;
+		if(Index == m_pClient->m_Snap.m_LocalClientID)
 			continue;
 
 		Options.HSplitTop(28.0f, &ButtonBar, &Options);
@@ -159,7 +163,7 @@ void CMenus::RenderPlayers(CUIRect MainView)
 
 		// player info
 		Player.VSplitLeft(28.0f, &Button, &Player);
-		CTeeRenderInfo Info = m_pClient->m_aClients[i].m_RenderInfo;
+		CTeeRenderInfo Info = m_pClient->m_aClients[Index].m_RenderInfo;
 		Info.m_Size = Button.h;
 		RenderTools()->RenderTee(CAnimState::GetIdle(), &Info, EMOTE_NORMAL, vec2(1.0f, 0.0f), vec2(Button.x+Button.h/2, Button.y+Button.h/2));
 
@@ -168,31 +172,34 @@ void CMenus::RenderPlayers(CUIRect MainView)
 		CTextCursor Cursor;
 		TextRender()->SetCursor(&Cursor, Player.x, Player.y, 14.0f, TEXTFLAG_RENDER|TEXTFLAG_STOP_AT_END);
 		Cursor.m_LineWidth = Player.w;
-		TextRender()->TextEx(&Cursor, m_pClient->m_aClients[i].m_aName, -1);
+		TextRender()->TextEx(&Cursor, m_pClient->m_aClients[Index].m_aName, -1);
 
 		TextRender()->SetCursor(&Cursor, Button.x,Button.y, 14.0f, TEXTFLAG_RENDER|TEXTFLAG_STOP_AT_END);
 		Cursor.m_LineWidth = Button.w;
-		TextRender()->TextEx(&Cursor, m_pClient->m_aClients[i].m_aClan, -1);
+		TextRender()->TextEx(&Cursor, m_pClient->m_aClients[Index].m_aClan, -1);
 
 		// ignore button
 		ButtonBar.HMargin(2.0f, &ButtonBar);
 		ButtonBar.VSplitLeft(Width, &Button, &ButtonBar);
 		Button.VSplitLeft((Width-Button.h)/4.0f, 0, &Button);
 		Button.VSplitLeft(Button.h, &Button, 0);
-		if(DoButton_Toggle(&s_aPlayerIDs[i][0], m_pClient->m_aClients[i].m_ChatIgnore, &Button))
-			m_pClient->m_aClients[i].m_ChatIgnore ^= 1;
+		if(&g_Config.m_ClShowChatFriends && !m_pClient->m_aClients[Index].m_Friend)
+			DoButton_Toggle(&s_aPlayerIDs[Index][0], 1, &Button, false);
+		else
+			if(DoButton_Toggle(&s_aPlayerIDs[Index][0], m_pClient->m_aClients[Index].m_ChatIgnore, &Button, true))
+				m_pClient->m_aClients[Index].m_ChatIgnore ^= 1;
 
 		// friend button
 		ButtonBar.VSplitLeft(20.0f, &Button, &ButtonBar);
 		ButtonBar.VSplitLeft(Width, &Button, &ButtonBar);
 		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(DoButton_Toggle(&s_aPlayerIDs[Index][1], m_pClient->m_aClients[Index].m_Friend, &Button, true))
 		{
-			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);
+			if(m_pClient->m_aClients[Index].m_Friend)
+				m_pClient->Friends()->RemoveFriend(m_pClient->m_aClients[Index].m_aName, m_pClient->m_aClients[Index].m_aClan);
 			else
-				m_pClient->Friends()->AddFriend(m_pClient->m_aClients[i].m_aName, m_pClient->m_aClients[i].m_aClan);
+				m_pClient->Friends()->AddFriend(m_pClient->m_aClients[Index].m_aName, m_pClient->m_aClients[Index].m_aClan);
 		}
 	}
 
@@ -385,13 +392,15 @@ void CMenus::RenderServerControlKick(CUIRect MainView, bool FilterSpectators)
 	static int aPlayerIDs[MAX_CLIENTS];
 	for(int i = 0; i < MAX_CLIENTS; i++)
 	{
-		if(!m_pClient->m_Snap.m_paPlayerInfos[i] || i == m_pClient->m_Snap.m_LocalClientID)
+		if(!m_pClient->m_Snap.m_paInfoByTeam[i])
 			continue;
-		if(FilterSpectators && m_pClient->m_Snap.m_paPlayerInfos[i]->m_Team == TEAM_SPECTATORS)
+
+		int Index = m_pClient->m_Snap.m_paInfoByTeam[i]->m_ClientID;
+		if(Index == m_pClient->m_Snap.m_LocalClientID || (FilterSpectators && m_pClient->m_Snap.m_paInfoByTeam[i]->m_Team == TEAM_SPECTATORS))
 			continue;
-		if(m_CallvoteSelectedPlayer == i)
+		if(m_CallvoteSelectedPlayer == Index)
 			Selected = NumOptions;
-		aPlayerIDs[NumOptions++] = i;
+		aPlayerIDs[NumOptions++] = Index;
 	}
 
 	static int s_VoteList = 0;
diff --git a/src/game/client/components/menus_settings.cpp b/src/game/client/components/menus_settings.cpp
index 51fdbd29..553195b1 100644
--- a/src/game/client/components/menus_settings.cpp
+++ b/src/game/client/components/menus_settings.cpp
@@ -96,6 +96,12 @@ void CMenus::RenderSettingsGeneral(CUIRect MainView)
 		if(DoButton_CheckBox(&g_Config.m_ClShowhud, Localize("Show ingame HUD"), g_Config.m_ClShowhud, &Button))
 			g_Config.m_ClShowhud ^= 1;
 
+		// chat messages
+		Left.HSplitTop(5.0f, 0, &Left);
+		Left.HSplitTop(20.0f, &Button, &Left);
+		if(DoButton_CheckBox(&g_Config.m_ClShowChatFriends, Localize("Show only chat messages from friends"), g_Config.m_ClShowChatFriends, &Button))
+			g_Config.m_ClShowChatFriends ^= 1;
+
 		// name plates
 		Right.HSplitTop(20.0f, &Button, &Right);
 		if(DoButton_CheckBox(&g_Config.m_ClNameplates, Localize("Show name plates"), g_Config.m_ClNameplates, &Button))
@@ -221,13 +227,10 @@ void CMenus::RenderSettingsPlayer(CUIRect MainView)
 			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);
+			vec4 Color(1.0f, 1.0f, 1.0f, 1.0f);
+			m_pClient->m_pCountryFlags->Render(pEntry->m_CountryCode, &Color, Item.m_Rect.x, Item.m_Rect.y, Item.m_Rect.w, Item.m_Rect.h);
+			if(pEntry->m_Texture != -1)
+				UI()->DoLabel(&Label, pEntry->m_aCountryCodeString, 10.0f, 0);
 		}
 	}
 
@@ -446,13 +449,14 @@ static CKeyInfo gs_aKeys[] =
 	{ "Remote console", "toggle_remote_console", 0 },
 	{ "Screenshot", "screenshot", 0 },
 	{ "Scoreboard", "+scoreboard", 0 },
+	{ "Respawn", "kill", 0 },
 };
 
 /*	This is for scripts/update_localization.py to work, don't remove!
 	Localize("Move left");Localize("Move right");Localize("Jump");Localize("Fire");Localize("Hook");Localize("Hammer");
 	Localize("Pistol");Localize("Shotgun");Localize("Grenade");Localize("Rifle");Localize("Next weapon");Localize("Prev. weapon");
 	Localize("Vote yes");Localize("Vote no");Localize("Chat");Localize("Team chat");Localize("Show chat");Localize("Emoticon");
-	Localize("Spectator mode");Localize("Spectate next");Localize("Spectate previous");Localize("Console");Localize("Remote console");Localize("Screenshot");Localize("Scoreboard");
+	Localize("Spectator mode");Localize("Spectate next");Localize("Spectate previous");Localize("Console");Localize("Remote console");Localize("Screenshot");Localize("Scoreboard");Localize("Respawn");
 */
 
 const int g_KeyCount = sizeof(gs_aKeys) / sizeof(CKeyInfo);
@@ -504,10 +508,11 @@ void CMenus::RenderSettingsControls(CUIRect MainView)
 	}
 
 	CUIRect MovementSettings, WeaponSettings, VotingSettings, ChatSettings, MiscSettings, ResetButton;
-	MainView.VSplitLeft(MainView.w/2-5.0f, &MovementSettings, &VotingSettings);
+	MainView.VSplitMid(&MovementSettings, &VotingSettings);
 
 	// movement settings
 	{
+		MovementSettings.VMargin(5.0f, &MovementSettings);
 		MovementSettings.HSplitTop(MainView.h/3+60.0f, &MovementSettings, &WeaponSettings);
 		RenderTools()->DrawUIRect(&MovementSettings, vec4(1,1,1,0.25f), CUI::CORNER_ALL, 10.0f);
 		MovementSettings.Margin(10.0f, &MovementSettings);
@@ -543,21 +548,22 @@ void CMenus::RenderSettingsControls(CUIRect MainView)
 		WeaponSettings.HSplitTop(14.0f+5.0f+10.0f, 0, &WeaponSettings);
 		UiDoGetButtons(5, 12, WeaponSettings);
 	}
-	
+
 	// defaults
 	{
 		ResetButton.HSplitTop(10.0f, 0, &ResetButton);
 		RenderTools()->DrawUIRect(&ResetButton, vec4(1,1,1,0.25f), CUI::CORNER_ALL, 10.0f);
 		ResetButton.HMargin(10.0f, &ResetButton);
 		ResetButton.VMargin(30.0f, &ResetButton);
+		ResetButton.HSplitTop(20.0f, &ResetButton, 0);
 		static int s_DefaultButton = 0;
 		if(DoButton_Menu((void*)&s_DefaultButton, Localize("Reset to defaults"), 0, &ResetButton))
 			m_pClient->m_pBinds->SetDefaults();
 	}
-	
+
 	// voting settings
 	{
-		VotingSettings.VSplitLeft(10.0f, 0, &VotingSettings);
+		VotingSettings.VMargin(5.0f, &VotingSettings);
 		VotingSettings.HSplitTop(MainView.h/3-75.0f, &VotingSettings, &ChatSettings);
 		RenderTools()->DrawUIRect(&VotingSettings, vec4(1,1,1,0.25f), CUI::CORNER_ALL, 10.0f);
 		VotingSettings.Margin(10.0f, &VotingSettings);
@@ -590,7 +596,7 @@ void CMenus::RenderSettingsControls(CUIRect MainView)
 		TextRender()->Text(0, MiscSettings.x, MiscSettings.y, 14.0f*UI()->Scale(), Localize("Miscellaneous"), -1);
 
 		MiscSettings.HSplitTop(14.0f+5.0f+10.0f, 0, &MiscSettings);
-		UiDoGetButtons(17, 25, MiscSettings);
+		UiDoGetButtons(17, 26, MiscSettings);
 	}
 
 }
@@ -761,7 +767,7 @@ void CMenus::RenderSettingsSound(CUIRect MainView)
 		if(g_Config.m_SndEnable)
 		{
 			if(g_Config.m_SndMusic)
-				m_pClient->m_pSounds->Play(CSounds::CHN_MUSIC, SOUND_MENU, 1.0f, vec2(0, 0));
+				m_pClient->m_pSounds->Play(CSounds::CHN_MUSIC, SOUND_MENU, 1.0f);
 		}
 		else
 			m_pClient->m_pSounds->Stop(SOUND_MENU);
@@ -776,7 +782,7 @@ void CMenus::RenderSettingsSound(CUIRect MainView)
 	{
 		g_Config.m_SndMusic ^= 1;
 		if(g_Config.m_SndMusic)
-			m_pClient->m_pSounds->Play(CSounds::CHN_MUSIC, SOUND_MENU, 1.0f, vec2(0, 0));
+			m_pClient->m_pSounds->Play(CSounds::CHN_MUSIC, SOUND_MENU, 1.0f);
 		else
 			m_pClient->m_pSounds->Stop(SOUND_MENU);
 	}
@@ -917,11 +923,8 @@ void CMenus::RenderLanguageSelection(CUIRect MainView)
 			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();
+			vec4 Color(1.0f, 1.0f, 1.0f, 1.0f);
+			m_pClient->m_pCountryFlags->Render(r.front().m_CountryCode, &Color, Rect.x, Rect.y, Rect.w, Rect.h);
 			Item.m_Rect.HSplitTop(2.0f, 0, &Item.m_Rect);
  			UI()->DoLabelScaled(&Item.m_Rect, r.front().m_Name, 16.0f, -1);
 		}
diff --git a/src/game/client/components/particles.cpp b/src/game/client/components/particles.cpp
index c4583cb1..66a62f1c 100644
--- a/src/game/client/components/particles.cpp
+++ b/src/game/client/components/particles.cpp
@@ -43,6 +43,11 @@ void CParticles::Add(int Group, CParticle *pPart)
 		if(pInfo->m_Paused)
 			return;
 	}
+	else
+	{
+		if(m_pClient->m_Snap.m_pGameInfoObj && m_pClient->m_Snap.m_pGameInfoObj->m_GameStateFlags&GAMESTATEFLAG_PAUSED)
+			return;
+	}
 
 	if (m_FirstFree == -1)
 		return;
@@ -142,7 +147,10 @@ void CParticles::OnRender()
 			Update((float)((t-LastTime)/(double)time_freq())*pInfo->m_Speed);
 	}
 	else
-		Update((float)((t-LastTime)/(double)time_freq()));
+	{
+		if(m_pClient->m_Snap.m_pGameInfoObj && !(m_pClient->m_Snap.m_pGameInfoObj->m_GameStateFlags&GAMESTATEFLAG_PAUSED))
+			Update((float)((t-LastTime)/(double)time_freq()));
+	}
 
 	LastTime = t;
 }
diff --git a/src/game/client/components/players.cpp b/src/game/client/components/players.cpp
index 6811c2ad..2eddd8a9 100644
--- a/src/game/client/components/players.cpp
+++ b/src/game/client/components/players.cpp
@@ -95,30 +95,7 @@ void CPlayers::RenderHook(
 	Player = *pPlayerChar;
 
 	CNetObj_PlayerInfo pInfo = *pPlayerInfo;
-	CTeeRenderInfo RenderInfo = m_pClient->m_aClients[pInfo.m_ClientID].m_RenderInfo;
-
-	// check for teamplay modes
-	bool IsTeamplay = false;
-	if(m_pClient->m_Snap.m_pGameInfoObj)
-		IsTeamplay = (m_pClient->m_Snap.m_pGameInfoObj->m_GameFlags&GAMEFLAG_TEAMS) != 0;
-
-	// check for ninja
-	if (Player.m_Weapon == WEAPON_NINJA)
-	{
-		// change the skin for the player to the ninja
-		int Skin = m_pClient->m_pSkins->Find("x_ninja");
-		if(Skin != -1)
-		{
-			if(IsTeamplay)
-				RenderInfo.m_Texture = m_pClient->m_pSkins->Get(Skin)->m_ColorTexture;
-			else
-			{
-				RenderInfo.m_Texture = m_pClient->m_pSkins->Get(Skin)->m_OrgTexture;
-				RenderInfo.m_ColorBody = vec4(1,1,1,1);
-				RenderInfo.m_ColorFeet = vec4(1,1,1,1);
-			}
-		}
-	}
+	CTeeRenderInfo RenderInfo = m_aRenderInfo[pInfo.m_ClientID];
 
 	float IntraTick = Client()->IntraGameTick();
 
@@ -218,31 +195,9 @@ void CPlayers::RenderPlayer(
 	Player = *pPlayerChar;
 
 	CNetObj_PlayerInfo pInfo = *pPlayerInfo;
-	CTeeRenderInfo RenderInfo = m_pClient->m_aClients[pInfo.m_ClientID].m_RenderInfo;
+	CTeeRenderInfo RenderInfo = m_aRenderInfo[pInfo.m_ClientID];
 
-	// check for teamplay modes
-	bool IsTeamplay = false;
 	bool NewTick = m_pClient->m_NewTick;
-	if(m_pClient->m_Snap.m_pGameInfoObj)
-		IsTeamplay = (m_pClient->m_Snap.m_pGameInfoObj->m_GameFlags&GAMEFLAG_TEAMS) != 0;
-
-	// check for ninja
-	if (Player.m_Weapon == WEAPON_NINJA)
-	{
-		// change the skin for the player to the ninja
-		int Skin = m_pClient->m_pSkins->Find("x_ninja");
-		if(Skin != -1)
-		{
-			if(IsTeamplay)
-				RenderInfo.m_Texture = m_pClient->m_pSkins->Get(Skin)->m_ColorTexture;
-			else
-			{
-				RenderInfo.m_Texture = m_pClient->m_pSkins->Get(Skin)->m_OrgTexture;
-				RenderInfo.m_ColorBody = vec4(1,1,1,1);
-				RenderInfo.m_ColorFeet = vec4(1,1,1,1);
-			}
-		}
-	}
 
 	// set size
 	RenderInfo.m_Size = 64.0f;
@@ -330,14 +285,17 @@ void CPlayers::RenderPlayer(
 	else if(!WantOtherDir)
 		State.Add(&g_pData->m_aAnimations[ANIM_WALK], WalkTime, 1.0f);
 
+	static float s_LastGameTickTime = Client()->GameTickTime();
+	if(m_pClient->m_Snap.m_pGameInfoObj && !(m_pClient->m_Snap.m_pGameInfoObj->m_GameStateFlags&GAMESTATEFLAG_PAUSED))
+		s_LastGameTickTime = Client()->GameTickTime();
 	if (Player.m_Weapon == WEAPON_HAMMER)
 	{
-		float ct = (Client()->PrevGameTick()-Player.m_AttackTick)/(float)SERVER_TICK_SPEED + Client()->GameTickTime();
+		float ct = (Client()->PrevGameTick()-Player.m_AttackTick)/(float)SERVER_TICK_SPEED + s_LastGameTickTime;
 		State.Add(&g_pData->m_aAnimations[ANIM_HAMMER_SWING], clamp(ct*5.0f,0.0f,1.0f), 1.0f);
 	}
 	if (Player.m_Weapon == WEAPON_NINJA)
 	{
-		float ct = (Client()->PrevGameTick()-Player.m_AttackTick)/(float)SERVER_TICK_SPEED + Client()->GameTickTime();
+		float ct = (Client()->PrevGameTick()-Player.m_AttackTick)/(float)SERVER_TICK_SPEED + s_LastGameTickTime;
 		State.Add(&g_pData->m_aAnimations[ANIM_NINJA_SWING], clamp(ct*2.0f,0.0f,1.0f), 1.0f);
 	}
 
@@ -347,7 +305,7 @@ void CPlayers::RenderPlayer(
 		static int64 SkidSoundTime = 0;
 		if(time_get()-SkidSoundTime > time_freq()/10)
 		{
-			m_pClient->m_pSounds->Play(CSounds::CHN_WORLD, SOUND_PLAYER_SKID, 0.25f, Position);
+			m_pClient->m_pSounds->PlayAt(CSounds::CHN_WORLD, SOUND_PLAYER_SKID, 0.25f, Position);
 			SkidSoundTime = time_get();
 		}
 
@@ -409,15 +367,22 @@ void CPlayers::RenderPlayer(
 			if ((Client()->GameTick()-Player.m_AttackTick) <= (SERVER_TICK_SPEED / 6) && g_pData->m_Weapons.m_aId[iw].m_NumSpriteMuzzles)
 			{
 				int IteX = rand() % g_pData->m_Weapons.m_aId[iw].m_NumSpriteMuzzles;
+				static int s_LastIteX = IteX;
 				if(Client()->State() == IClient::STATE_DEMOPLAYBACK)
 				{
-					static int s_LastIteX = IteX;
 					const IDemoPlayer::CInfo *pInfo = DemoPlayer()->BaseInfo();
 					if(pInfo->m_Paused)
 						IteX = s_LastIteX;
 					else
 						s_LastIteX = IteX;
 				}
+				else
+				{
+					if(m_pClient->m_Snap.m_pGameInfoObj && m_pClient->m_Snap.m_pGameInfoObj->m_GameStateFlags&GAMESTATEFLAG_PAUSED)
+						IteX = s_LastIteX;
+					else
+						s_LastIteX = IteX;
+				}
 				if(g_pData->m_Weapons.m_aId[iw].m_aSpriteMuzzles[IteX])
 				{
 					vec2 Dir = vec2(pPlayerChar->m_X,pPlayerChar->m_Y) - vec2(pPrevChar->m_X, pPrevChar->m_Y);
@@ -438,7 +403,11 @@ void CPlayers::RenderPlayer(
 		{
 			// TODO: should be an animation
 			Recoil = 0;
-			float a = (Client()->GameTick()-Player.m_AttackTick+IntraTick)/5.0f;
+			static float s_LastIntraTick = IntraTick;
+			if(m_pClient->m_Snap.m_pGameInfoObj && !(m_pClient->m_Snap.m_pGameInfoObj->m_GameStateFlags&GAMESTATEFLAG_PAUSED))
+				s_LastIntraTick = IntraTick;
+
+			float a = (Client()->GameTick()-Player.m_AttackTick+s_LastIntraTick)/5.0f;
 			if(a < 1)
 				Recoil = sinf(a*pi);
 			p = Position + Dir * g_pData->m_Weapons.m_aId[iw].m_Offsetx - Dir*Recoil*10.0f;
@@ -460,6 +429,22 @@ void CPlayers::RenderPlayer(
 				}
 
 				int IteX = rand() % g_pData->m_Weapons.m_aId[iw].m_NumSpriteMuzzles;
+				static int s_LastIteX = IteX;
+				if(Client()->State() == IClient::STATE_DEMOPLAYBACK)
+				{
+					const IDemoPlayer::CInfo *pInfo = DemoPlayer()->BaseInfo();
+					if(pInfo->m_Paused)
+						IteX = s_LastIteX;
+					else
+						s_LastIteX = IteX;
+				}
+				else
+				{
+					if(m_pClient->m_Snap.m_pGameInfoObj && m_pClient->m_Snap.m_pGameInfoObj->m_GameStateFlags&GAMESTATEFLAG_PAUSED)
+						IteX = s_LastIteX;
+					else
+						s_LastIteX = IteX;
+				}
 				if (Alpha > 0.0f && g_pData->m_Weapons.m_aId[iw].m_aSpriteMuzzles[IteX])
 				{
 					float OffsetY = -g_pData->m_Weapons.m_aId[iw].m_Muzzleoffsety;
@@ -546,6 +531,31 @@ void CPlayers::RenderPlayer(
 
 void CPlayers::OnRender()
 {
+	// update RenderInfo for ninja
+	bool IsTeamplay = false;
+	if(m_pClient->m_Snap.m_pGameInfoObj)
+		IsTeamplay = (m_pClient->m_Snap.m_pGameInfoObj->m_GameFlags&GAMEFLAG_TEAMS) != 0;
+	for(int i = 0; i < MAX_CLIENTS; ++i)
+	{
+		m_aRenderInfo[i] = m_pClient->m_aClients[i].m_RenderInfo;
+		if(m_pClient->m_Snap.m_aCharacters[i].m_Cur.m_Weapon == WEAPON_NINJA)
+		{
+			// change the skin for the player to the ninja
+			int Skin = m_pClient->m_pSkins->Find("x_ninja");
+			if(Skin != -1)
+			{
+				if(IsTeamplay)
+					m_aRenderInfo[i].m_Texture = m_pClient->m_pSkins->Get(Skin)->m_ColorTexture;
+				else
+				{
+					m_aRenderInfo[i].m_Texture = m_pClient->m_pSkins->Get(Skin)->m_OrgTexture;
+					m_aRenderInfo[i].m_ColorBody = vec4(1,1,1,1);
+					m_aRenderInfo[i].m_ColorFeet = vec4(1,1,1,1);
+				}
+			}
+		}
+	}
+
 	// render other players in two passes, first pass we render the other, second pass we render our self
 	for(int p = 0; p < 4; p++)
 	{
diff --git a/src/game/client/components/players.h b/src/game/client/components/players.h
index cedad0ff..9f23a0e6 100644
--- a/src/game/client/components/players.h
+++ b/src/game/client/components/players.h
@@ -6,6 +6,7 @@
 
 class CPlayers : public CComponent
 {
+	CTeeRenderInfo m_aRenderInfo[MAX_CLIENTS];
 	void RenderHand(class CTeeRenderInfo *pInfo, vec2 CenterPos, vec2 Dir, float AngleOffset, vec2 PostRotOffset);
 	void RenderPlayer(
 		const CNetObj_Character *pPrevChar,
diff --git a/src/game/client/components/scoreboard.cpp b/src/game/client/components/scoreboard.cpp
index ae11c7ea..935f7bad 100644
--- a/src/game/client/components/scoreboard.cpp
+++ b/src/game/client/components/scoreboard.cpp
@@ -273,12 +273,9 @@ 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->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);
-		Graphics()->QuadsDrawTL(&QuadItem, 1);
-		Graphics()->QuadsEnd();
+		vec4 Color(1.0f, 1.0f, 1.0f, 0.5f);
+		m_pClient->m_pCountryFlags->Render(m_pClient->m_aClients[pInfo->m_ClientID].m_Country, &Color,
+											CountryOffset, y+(Spacing+TeeSizeMod*5.0f)/2.0f, CountryLength, LineHeight-Spacing-TeeSizeMod*5.0f);
 
 		// ping
 		str_format(aBuf, sizeof(aBuf), "%d", clamp(pInfo->m_Latency, 0, 1000));
diff --git a/src/game/client/components/skins.cpp b/src/game/client/components/skins.cpp
index dd38e9ea..babf49bb 100644
--- a/src/game/client/components/skins.cpp
+++ b/src/game/client/components/skins.cpp
@@ -7,6 +7,7 @@
 
 #include <engine/graphics.h>
 #include <engine/storage.h>
+#include <engine/shared/config.h>
 
 #include "skins.h"
 
@@ -103,8 +104,11 @@ int CSkins::SkinScan(const char *pName, int IsDir, int DirType, void *pUser)
 
 	// set skin data
 	str_copy(Skin.m_aName, pName, min((int)sizeof(Skin.m_aName),l-3));
-	str_format(aBuf, sizeof(aBuf), "load skin %s", Skin.m_aName);
-	pSelf->Console()->Print(IConsole::OUTPUT_LEVEL_ADDINFO, "game", aBuf);
+	if(g_Config.m_Debug)
+	{
+		str_format(aBuf, sizeof(aBuf), "load skin %s", Skin.m_aName);
+		pSelf->Console()->Print(IConsole::OUTPUT_LEVEL_ADDINFO, "game", aBuf);
+	}
 	pSelf->m_aSkins.add(Skin);
 
 	return 0;
diff --git a/src/game/client/components/sounds.cpp b/src/game/client/components/sounds.cpp
index c4ade00e..be63415c 100644
--- a/src/game/client/components/sounds.cpp
+++ b/src/game/client/components/sounds.cpp
@@ -34,6 +34,30 @@ static int LoadSoundsThread(void *pUser)
 
 	return 0;
 }
+
+int CSounds::GetSampleId(int SetId)
+{
+	if(!g_Config.m_SndEnable || !Sound()->IsSoundEnabled() || m_WaitForSoundJob || SetId < 0 || SetId >= g_pData->m_NumSounds)
+		return -1;
+	
+	CDataSoundset *pSet = &g_pData->m_aSounds[SetId];
+	if(!pSet->m_NumSounds)
+		return -1;
+
+	if(pSet->m_NumSounds == 1)
+		return pSet->m_aSounds[0].m_Id;
+
+	// return random one
+	int Id;
+	do
+	{
+		Id = rand() % pSet->m_NumSounds;
+	}
+	while(Id == pSet->m_Last);
+	pSet->m_Last = Id;
+	return pSet->m_aSounds[Id].m_Id;
+}
+
 void CSounds::OnInit()
 {
 	// setup sound channels
@@ -98,7 +122,7 @@ void CSounds::OnRender()
 		int64 Now = time_get();
 		if(m_QueueWaitTime <= Now)
 		{
-			Play(m_aQueue[0].m_Channel, m_aQueue[0].m_SetId, 1.0f, vec2(0,0));
+			Play(m_aQueue[0].m_Channel, m_aQueue[0].m_SetId, 1.0f);
 			m_QueueWaitTime = Now+time_freq()*3/10; // wait 300ms before playing the next one
 			if(--m_QueuePos > 0)
 				mem_move(m_aQueue, m_aQueue+1, m_QueuePos*sizeof(QueueEntry));
@@ -132,47 +156,48 @@ void CSounds::PlayAndRecord(int Chn, int SetId, float Vol, vec2 Pos)
 	Msg.m_SoundID = SetId;
 	Client()->SendPackMsg(&Msg, MSGFLAG_NOSEND|MSGFLAG_RECORD);
 
-	Play(Chn, SetId, Vol, Pos);
+	Play(Chn, SetId, Vol);
 }
 
-void CSounds::Play(int Chn, int SetId, float Vol, vec2 Pos)
+void CSounds::Play(int Chn, int SetId, float Vol)
 {
-	if(!g_Config.m_SndEnable || !Sound()->IsSoundEnabled() || (Chn == CHN_MUSIC && !g_Config.m_SndMusic) || m_WaitForSoundJob || SetId < 0 || SetId >= g_pData->m_NumSounds)
+	if(Chn == CHN_MUSIC && !g_Config.m_SndMusic)
 		return;
 
-	CDataSoundset *pSet = &g_pData->m_aSounds[SetId];
-
-	if(!pSet->m_NumSounds)
+	int SampleId = GetSampleId(SetId);
+	if(SampleId == -1)
 		return;
-	
+
 	int Flags = 0;
 	if(Chn == CHN_MUSIC)
 		Flags = ISound::FLAG_LOOP;
 
-	if(pSet->m_NumSounds == 1)
-	{
-		Sound()->PlayAt(Chn, pSet->m_aSounds[0].m_Id, Flags, Pos.x, Pos.y);
+	Sound()->Play(Chn, SampleId, Flags);
+}
+
+void CSounds::PlayAt(int Chn, int SetId, float Vol, vec2 Pos)
+{
+	if(Chn == CHN_MUSIC && !g_Config.m_SndMusic)
+		return;
+	
+	int SampleId = GetSampleId(SetId);
+	if(SampleId == -1)
 		return;
-	}
 
-	// play a random one
-	int Id;
-	do
-	{
-		Id = rand() % pSet->m_NumSounds;
-	}
-	while(Id == pSet->m_Last);
-	Sound()->PlayAt(Chn, pSet->m_aSounds[Id].m_Id, Flags, Pos.x, Pos.y);
-	pSet->m_Last = Id;
+	int Flags = 0;
+	if(Chn == CHN_MUSIC)
+		Flags = ISound::FLAG_LOOP;
+
+	Sound()->PlayAt(Chn, SampleId, Flags, Pos.x, Pos.y);
 }
 
 void CSounds::Stop(int SetId)
 {
 	if(m_WaitForSoundJob || SetId < 0 || SetId >= g_pData->m_NumSounds)
 		return;
-	
+
 	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 ab9cc8e6..9d647398 100644
--- a/src/game/client/components/sounds.h
+++ b/src/game/client/components/sounds.h
@@ -19,6 +19,8 @@ class CSounds : public CComponent
 	int64 m_QueueWaitTime;
 	class CJob m_SoundJob;
 	bool m_WaitForSoundJob;
+	
+	int GetSampleId(int SetId);
 
 public:
 	// sound channels
@@ -37,7 +39,8 @@ public:
 
 	void ClearQueue();
 	void Enqueue(int Channel, int SetId);
-	void Play(int Channel, int SetId, float Vol, vec2 Pos);
+	void Play(int Channel, int SetId, float Vol);
+	void PlayAt(int Channel, int SetId, float Vol, vec2 Pos);
 	void PlayAndRecord(int Channel, int SetId, float Vol, vec2 Pos);
 	void Stop(int SetId);
 };
diff --git a/src/game/client/gameclient.cpp b/src/game/client/gameclient.cpp
index 7b6b1192..86fb5ad0 100644
--- a/src/game/client/gameclient.cpp
+++ b/src/game/client/gameclient.cpp
@@ -95,7 +95,6 @@ void CGameClient::OnConsoleInit()
 {
 	m_pEngine = Kernel()->RequestInterface<IEngine>();
 	m_pClient = Kernel()->RequestInterface<IClient>();
-	m_pGraphics = Kernel()->RequestInterface<IGraphics>();
 	m_pTextRender = Kernel()->RequestInterface<ITextRender>();
 	m_pSound = Kernel()->RequestInterface<ISound>();
 	m_pInput = Kernel()->RequestInterface<IInput>();
@@ -126,6 +125,8 @@ void CGameClient::OnConsoleInit()
 	m_pVoting = &::gs_Voting;
 	m_pScoreboard = &::gs_Scoreboard;
 	m_pItems = &::gs_Items;
+	m_pMapLayersBackGround = &::gs_MapLayersBackGround;
+	m_pMapLayersForeGround = &::gs_MapLayersForeGround;
 
 	// make a list of all the systems, make sure to add them in the corrent render order
 	m_All.Add(m_pSkins);
@@ -192,12 +193,10 @@ void CGameClient::OnConsoleInit()
 	Console()->Register("force_vote", "ss?r", CFGFLAG_SERVER, 0, 0, "Force a voting option");
 	Console()->Register("clear_votes", "", CFGFLAG_SERVER, 0, 0, "Clears the voting options");
 	Console()->Register("vote", "r", CFGFLAG_SERVER, 0, 0, "Force a vote to yes/no");
+	Console()->Register("swap_teams", "", CFGFLAG_SERVER, 0, 0, "Swap the current teams");
+	Console()->Register("shuffle_teams", "", CFGFLAG_SERVER, 0, 0, "Shuffle the current teams");
 
 
-	// propagate pointers
-	m_UI.SetGraphics(Graphics(), TextRender());
-	m_RenderTools.m_pGraphics = Graphics();
-	m_RenderTools.m_pUI = UI();
 	for(int i = 0; i < m_All.m_Num; i++)
 		m_All.m_paComponents[i]->m_pClient = this;
 
@@ -221,6 +220,13 @@ void CGameClient::OnConsoleInit()
 
 void CGameClient::OnInit()
 {
+	m_pGraphics = Kernel()->RequestInterface<IGraphics>();
+
+	// propagate pointers
+	m_UI.SetGraphics(Graphics(), TextRender());
+	m_RenderTools.m_pGraphics = Graphics();
+	m_RenderTools.m_pUI = UI();
+	
 	int64 Start = time_get();
 
 	// set the language
@@ -443,7 +449,7 @@ void CGameClient::OnRender()
 	m_NewPredictedTick = false;
 
 	// check if client info has to be resent
-	if(m_LastSendInfo && Client()->State() == IClient::STATE_ONLINE && !m_pMenus->IsActive() && m_LastSendInfo+time_freq()*5 < time_get())
+	if(m_LastSendInfo && Client()->State() == IClient::STATE_ONLINE && m_Snap.m_LocalClientID >= 0 && !m_pMenus->IsActive() && m_LastSendInfo+time_freq()*5 < time_get())
 	{
 		// resend if client info differs
 		if(str_comp(g_Config.m_PlayerName, m_aClients[m_Snap.m_LocalClientID].m_aName) ||
@@ -545,7 +551,7 @@ void CGameClient::OnMessage(int MsgId, CUnpacker *pUnpacker)
 			pMsg->m_SoundID == SOUND_CTF_GRAB_PL)
 			g_GameClient.m_pSounds->Enqueue(CSounds::CHN_GLOBAL, pMsg->m_SoundID);
 		else
-			g_GameClient.m_pSounds->Play(CSounds::CHN_GLOBAL, pMsg->m_SoundID, 1.0f, vec2(0,0));
+			g_GameClient.m_pSounds->Play(CSounds::CHN_GLOBAL, pMsg->m_SoundID, 1.0f);
 	}
 }
 
@@ -620,7 +626,7 @@ void CGameClient::ProcessEvents()
 		else if(Item.m_Type == NETEVENTTYPE_SOUNDWORLD)
 		{
 			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));
+			g_GameClient.m_pSounds->PlayAt(CSounds::CHN_WORLD, ev->m_SoundID, 1.0f, vec2(ev->m_X, ev->m_Y));
 		}
 	}
 }
@@ -862,6 +868,17 @@ void CGameClient::OnNewSnapshot()
 			}
 		}
 	}
+	// sort player infos by team
+	int Teams[3] = { TEAM_RED, TEAM_BLUE, TEAM_SPECTATORS };
+	int Index = 0;
+	for(int Team = 0; Team < 3; ++Team)
+	{
+		for(int i = 0; i < MAX_CLIENTS && Index < MAX_CLIENTS; ++i)
+		{
+			if(m_Snap.m_paPlayerInfos[i] && m_Snap.m_paPlayerInfos[i]->m_Team == Teams[Team])
+				m_Snap.m_paInfoByTeam[Index++] = m_Snap.m_paPlayerInfos[i];
+		}
+	}
 
 	CTuningParams StandardTuning;
 	CServerInfo CurrentServerInfo;
@@ -876,6 +893,15 @@ void CGameClient::OnNewSnapshot()
 			m_ServerMode = SERVERMODE_PUREMOD;
 	}
 
+	// add tuning to demo
+	if(DemoRecorder()->IsRecording() && mem_comp(&StandardTuning, &m_Tuning, sizeof(CTuningParams)) != 0)
+	{
+		CMsgPacker Msg(NETMSGTYPE_SV_TUNEPARAMS);
+		int *pParams = (int *)&m_Tuning;
+		for(unsigned i = 0; i < sizeof(m_Tuning)/sizeof(int); i++)
+			Msg.AddInt(pParams[i]);
+		Client()->SendMsg(&Msg, MSGFLAG_RECORD|MSGFLAG_NOSEND);
+	}
 }
 
 void CGameClient::OnPredict()
diff --git a/src/game/client/gameclient.h b/src/game/client/gameclient.h
index 4783f8b4..e8c7e7bd 100644
--- a/src/game/client/gameclient.h
+++ b/src/game/client/gameclient.h
@@ -125,6 +125,7 @@ public:
 
 		const CNetObj_PlayerInfo *m_paPlayerInfos[MAX_CLIENTS];
 		const CNetObj_PlayerInfo *m_paInfoByScore[MAX_CLIENTS];
+		const CNetObj_PlayerInfo *m_paInfoByTeam[MAX_CLIENTS];
 
 		int m_LocalClientID;
 		int m_NumPlayers;
@@ -241,6 +242,8 @@ public:
 	class CVoting *m_pVoting;
 	class CScoreboard *m_pScoreboard;
 	class CItems *m_pItems;
+	class CMapLayers *m_pMapLayersBackGround;
+	class CMapLayers *m_pMapLayersForeGround;
 };
 
 
diff --git a/src/game/client/render.cpp b/src/game/client/render.cpp
index 93909d0c..278ed51a 100644
--- a/src/game/client/render.cpp
+++ b/src/game/client/render.cpp
@@ -75,7 +75,7 @@ void CRenderTools::SelectSprite(CDataSprite *pSpr, int Flags, int sx, int sy)
 
 void CRenderTools::SelectSprite(int Id, int Flags, int sx, int sy)
 {
-	if(Id < 0 || Id > g_pData->m_NumSprites)
+	if(Id < 0 || Id >= g_pData->m_NumSprites)
 		return;
 	SelectSprite(&g_pData->m_aSprites[Id], Flags, sx, sy);
 }
diff --git a/src/game/client/ui.cpp b/src/game/client/ui.cpp
index 00a30c15..c5219575 100644
--- a/src/game/client/ui.cpp
+++ b/src/game/client/ui.cpp
@@ -74,6 +74,11 @@ CUIRect *CUI::Screen()
 	return &m_Screen;
 }
 
+float CUI::PixelSize()
+{
+	return Screen()->w/Graphics()->ScreenWidth();
+}
+
 void CUI::SetScale(float s)
 {
 	g_Config.m_UiScale = (int)(s*100.0f);
diff --git a/src/game/client/ui.h b/src/game/client/ui.h
index 7cd78d6f..daba5d51 100644
--- a/src/game/client/ui.h
+++ b/src/game/client/ui.h
@@ -82,6 +82,7 @@ public:
 	void ConvertMouseMove(float *x, float *y);
 
 	CUIRect *Screen();
+	float PixelSize();
 	void ClipEnable(const CUIRect *pRect);
 	void ClipDisable();
 
diff --git a/src/game/editor/auto_map.cpp b/src/game/editor/auto_map.cpp
index 528e459b..3abcf0f4 100644
--- a/src/game/editor/auto_map.cpp
+++ b/src/game/editor/auto_map.cpp
@@ -20,15 +20,15 @@ void CAutoMapper::Load(const char* 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())
 	{
@@ -40,11 +40,11 @@ void CAutoMapper::Load(const char* pTileName)
 			{
 				// 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
@@ -54,15 +54,15 @@ void CAutoMapper::Load(const char* pTileName)
 					// 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"))
@@ -70,7 +70,7 @@ void CAutoMapper::Load(const char* pTileName)
 						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];
@@ -85,9 +85,9 @@ void CAutoMapper::Load(const char* pTileName)
 					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))
@@ -106,12 +106,12 @@ void CAutoMapper::Load(const char* pTileName)
 			}
 		}
 	}
-		
+
 	io_close(RulesFile);
-		
+
 	str_format(aBuf, sizeof(aBuf),"loaded %s", aPath);
 	m_pEditor->Console()->Print(IConsole::OUTPUT_LEVEL_DEBUG, "editor", aBuf);
-		
+
 	m_FileLoaded = true;
 }
 
@@ -127,14 +127,14 @@ 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)
 	{
@@ -144,7 +144,7 @@ void CAutoMapper::Proceed(CLayerTiles *pLayer, int ConfigID)
 			break;
 		}
 	}
-	
+
 	// auto map !
 	int MaxIndex = pLayer->m_Width*pLayer->m_Height;
 	for(int y = 0; y < pLayer->m_Height; y++)
@@ -159,7 +159,7 @@ void CAutoMapper::Proceed(CLayerTiles *pLayer, int ConfigID)
 
 			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)
@@ -170,7 +170,7 @@ void CAutoMapper::Proceed(CLayerTiles *pLayer, int ConfigID)
 				{
 					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
@@ -184,15 +184,15 @@ void CAutoMapper::Proceed(CLayerTiles *pLayer, int ConfigID)
 						{
 							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))
+					(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
index ee570378..c5537d4f 100644
--- a/src/game/editor/auto_map.h
+++ b/src/game/editor/auto_map.h
@@ -11,7 +11,7 @@ class CAutoMapper
 		int m_Y;
 		int m_Value;
 		bool m_IndexValue;
-		
+
 		enum
 		{
 			EMPTY=0,
@@ -33,16 +33,16 @@ class CAutoMapper
 		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;
diff --git a/src/game/editor/editor.cpp b/src/game/editor/editor.cpp
index 500e600b..fa1024e0 100644
--- a/src/game/editor/editor.cpp
+++ b/src/game/editor/editor.cpp
@@ -164,7 +164,7 @@ void CEditorImage::AnalyseTileFlags()
 	int tw = m_Width/16; // tilesizes
 	int th = m_Height/16;
 	if ( tw == th )
-    {
+	{
 		unsigned char *pPixelData = (unsigned char *)m_pData;
 
 		int TileID = 0;
@@ -214,7 +214,6 @@ void CEditor::EnvelopeEval(float TimeOffset, int Env, float *pChannels, void *pU
 
 // 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, float *Offset, bool Hidden, int Corners)
 {
 	int Inside = UI()->MouseInside(pRect);
@@ -228,6 +227,7 @@ int CEditor::DoEditBox(void *pID, const CUIRect *pRect, char *pStr, unsigned Str
 
 	if(UI()->LastActiveItem() == pID)
 	{
+		m_EditBoxActive = 2;
 		int Len = str_length(pStr);
 		if(Len == 0)
 			s_AtIndex = 0;
@@ -381,8 +381,8 @@ float CEditor::UiDoScrollbarV(const void *pID, const CUIRect *pRect, float Curre
 	Handle.y += (pRect->h-Handle.h)*Current;
 
 	// logic
-    float Ret = Current;
-    int Inside = UI()->MouseInside(&Handle);
+	float Ret = Current;
+	int Inside = UI()->MouseInside(&Handle);
 
 	if(UI()->ActiveItem() == pID)
 	{
@@ -423,7 +423,7 @@ float CEditor::UiDoScrollbarV(const void *pID, const CUIRect *pRect, float Curre
 	Slider.Margin(5.0f, &Slider);
 	RenderTools()->DrawUIRect(&Slider, vec4(1,1,1,0.25f)*ButtonColorMul(pID), CUI::CORNER_ALL, 2.5f);
 
-    return Ret;
+	return Ret;
 }
 
 vec4 CEditor::GetButtonColor(const void *pID, int Checked)
@@ -490,7 +490,7 @@ int CEditor::DoButton_File(const void *pID, const char *pText, int Checked, cons
 int CEditor::DoButton_Menu(const void *pID, const char *pText, int Checked, const CUIRect *pRect, int Flags, const char *pToolTip)
 {
 	CUIRect r = *pRect;
-    RenderTools()->DrawUIRect(&r, vec4(0.5f, 0.5f, 0.5f, 1.0f), CUI::CORNER_T, 3.0f);
+	RenderTools()->DrawUIRect(&r, vec4(0.5f, 0.5f, 0.5f, 1.0f), CUI::CORNER_T, 3.0f);
 
 	r = *pRect;
 	r.VMargin(5.0f, &r);
@@ -515,18 +515,18 @@ int CEditor::DoButton_MenuItem(const void *pID, const char *pText, int Checked,
 int CEditor::DoButton_Tab(const void *pID, const char *pText, int Checked, const CUIRect *pRect, int Flags, const char *pToolTip)
 {
 	RenderTools()->DrawUIRect(pRect, GetButtonColor(pID, Checked), CUI::CORNER_T, 5.0f);
-    CUIRect NewRect = *pRect;
-    NewRect.y += NewRect.h/2.0f-7.0f;
-    UI()->DoLabel(&NewRect, pText, 10, 0, -1);
+	CUIRect NewRect = *pRect;
+	NewRect.y += NewRect.h/2.0f-7.0f;
+	UI()->DoLabel(&NewRect, pText, 10, 0, -1);
 	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, float FontSize)
 {
 	RenderTools()->DrawUIRect(pRect, GetButtonColor(pID, Checked), Corners, 3.0f);
-    CUIRect NewRect = *pRect;
-    NewRect.HMargin(NewRect.h/2.0f-FontSize/2.0f-1.0f, &NewRect);
-    UI()->DoLabel(&NewRect, pText, FontSize, 0, -1);
+	CUIRect NewRect = *pRect;
+	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);
 }
 
@@ -550,7 +550,7 @@ void CEditor::RenderGrid(CLayerGroup *pGroup)
 		return;
 
 	float aGroupPoints[4];
-	pGroup->Mapping(aGroupPoints); 
+	pGroup->Mapping(aGroupPoints);
 
 	float w = UI()->Screen()->w;
 	float h = UI()->Screen()->h;
@@ -562,7 +562,7 @@ void CEditor::RenderGrid(CLayerGroup *pGroup)
 	int XGridOffset = XOffset % m_GridFactor;
 	int YGridOffset = YOffset % m_GridFactor;
 
-	Graphics()->TextureSet(-1);	
+	Graphics()->TextureSet(-1);
 	Graphics()->LinesBegin();
 
 	for(int i = 0; i < (int)w; i++)
@@ -601,9 +601,9 @@ void CEditor::RenderBackground(CUIRect View, int Texture, float Size, float Brig
 
 int CEditor::UiDoValueSelector(void *pID, CUIRect *pRect, const char *pLabel, int Current, int Min, int Max, int Step, float Scale, const char *pToolTip)
 {
-    // logic
-    static float s_Value;
-    int Inside = UI()->MouseInside(pRect);
+	// logic
+	static float s_Value;
+	int Inside = UI()->MouseInside(pRect);
 
 	if(UI()->ActiveItem() == pID)
 	{
@@ -652,9 +652,9 @@ int CEditor::UiDoValueSelector(void *pID, CUIRect *pRect, const char *pLabel, in
 	char aBuf[128];
 	str_format(aBuf, sizeof(aBuf),"%s %d", pLabel, Current);
 	RenderTools()->DrawUIRect(pRect, GetButtonColor(pID, 0), CUI::CORNER_ALL, 5.0f);
-    pRect->y += pRect->h/2.0f-7.0f;
-    UI()->DoLabel(pRect, aBuf, 10, 0, -1);
-    
+	pRect->y += pRect->h/2.0f-7.0f;
+	UI()->DoLabel(pRect, aBuf, 10, 0, -1);
+
 	return Current;
 }
 
@@ -713,7 +713,7 @@ void CEditor::CallbackAppendMap(const char *pFileName, int StorageType, void *pU
 		pEditor->m_aFileName[0] = 0;
 	else
 		pEditor->SortImages();
-	
+
 	pEditor->m_Dialog = DIALOG_NONE;
 }
 void CEditor::CallbackSaveMap(const char *pFileName, int StorageType, void *pUser)
@@ -734,7 +734,7 @@ void CEditor::CallbackSaveMap(const char *pFileName, int StorageType, void *pUse
 		pEditor->m_ValidSaveFilename = StorageType == IStorage::TYPE_SAVE && pEditor->m_pFileDialogPath == pEditor->m_aFileDialogCurrentFolder;
 		pEditor->m_Map.m_Modified = false;
 	}
-	
+
 	pEditor->m_Dialog = DIALOG_NONE;
 }
 
@@ -742,32 +742,38 @@ void CEditor::DoToolbar(CUIRect ToolBar)
 {
 	CUIRect TB_Top, TB_Bottom;
 	CUIRect Button;
-	
+
 	ToolBar.HSplitTop(ToolBar.h/2.0f, &TB_Top, &TB_Bottom);
-	
-    TB_Top.HSplitBottom(2.5f, &TB_Top, 0);
-    TB_Bottom.HSplitTop(2.5f, 0, &TB_Bottom);
+
+	TB_Top.HSplitBottom(2.5f, &TB_Top, 0);
+	TB_Bottom.HSplitTop(2.5f, 0, &TB_Bottom);
 
 	// ctrl+o to open
-	if(Input()->KeyDown('o') && (Input()->KeyPressed(KEY_LCTRL) || Input()->KeyPressed(KEY_RCTRL)))
+	if(Input()->KeyDown('o') && (Input()->KeyPressed(KEY_LCTRL) || Input()->KeyPressed(KEY_RCTRL)) && m_Dialog == DIALOG_NONE)
 	{
 		if(HasUnsavedData())
 		{
-			m_PopupEventType = POPEVENT_LOAD;
-			m_PopupEventActivated = true;
+			if(!m_PopupEventWasActivated)
+			{
+				m_PopupEventType = POPEVENT_LOAD;
+				m_PopupEventActivated = true;
+			}
 		}
 		else
 			InvokeFileDialog(IStorage::TYPE_ALL, FILETYPE_MAP, "Load map", "Load", "maps", "", CallbackOpenMap, this);
 	}
 
 	// ctrl+s to save
-	if(Input()->KeyDown('s') && (Input()->KeyPressed(KEY_LCTRL) || Input()->KeyPressed(KEY_RCTRL)))
+	if(Input()->KeyDown('s') && (Input()->KeyPressed(KEY_LCTRL) || Input()->KeyPressed(KEY_RCTRL)) && m_Dialog == DIALOG_NONE)
 	{
-		if(m_aFileName[0] && m_ValidSaveFilename)	
+		if(m_aFileName[0] && m_ValidSaveFilename)
 		{
-			str_copy(m_aFileSaveName, m_aFileName, sizeof(m_aFileSaveName));
-			m_PopupEventType = POPEVENT_SAVE;
-			m_PopupEventActivated = true;
+			if(!m_PopupEventWasActivated)
+			{
+				str_copy(m_aFileSaveName, m_aFileName, sizeof(m_aFileSaveName));
+				m_PopupEventType = POPEVENT_SAVE;
+				m_PopupEventActivated = true;
+			}
 		}
 		else
 			InvokeFileDialog(IStorage::TYPE_SAVE, FILETYPE_MAP, "Save map", "Save", "maps", "", CallbackSaveMap, this);
@@ -814,6 +820,7 @@ void CEditor::DoToolbar(CUIRect ToolBar)
 		(Input()->KeyDown('i') && (Input()->KeyPressed(KEY_LCTRL) || Input()->KeyPressed(KEY_RCTRL))))
 	{
 		m_ShowTileInfo = !m_ShowTileInfo;
+		m_ShowEnvelopePreview = 0;
 	}
 
 	TB_Top.VSplitLeft(15.0f, 0, &TB_Top);
@@ -821,12 +828,12 @@ void CEditor::DoToolbar(CUIRect ToolBar)
 	// zoom group
 	TB_Top.VSplitLeft(30.0f, &Button, &TB_Top);
 	static int s_ZoomOutButton = 0;
-	if(DoButton_Ex(&s_ZoomOutButton, "ZO", 0, &Button, 0, "[NumPad-] Zoom out", CUI::CORNER_L) || Input()->KeyDown(KEY_KP_MINUS))
+	if(DoButton_Ex(&s_ZoomOutButton, "ZO", 0, &Button, 0, "[NumPad-] Zoom out", CUI::CORNER_L))
 		m_ZoomLevel += 50;
 
 	TB_Top.VSplitLeft(30.0f, &Button, &TB_Top);
 	static int s_ZoomNormalButton = 0;
-	if(DoButton_Ex(&s_ZoomNormalButton, "1:1", 0, &Button, 0, "[NumPad*] Zoom to normal and remove editor offset", 0) || Input()->KeyDown(KEY_KP_MULTIPLY))
+	if(DoButton_Ex(&s_ZoomNormalButton, "1:1", 0, &Button, 0, "[NumPad*] Zoom to normal and remove editor offset", 0))
 	{
 		m_EditorOffsetX = 0;
 		m_EditorOffsetY = 0;
@@ -835,7 +842,7 @@ void CEditor::DoToolbar(CUIRect ToolBar)
 
 	TB_Top.VSplitLeft(30.0f, &Button, &TB_Top);
 	static int s_ZoomInButton = 0;
-	if(DoButton_Ex(&s_ZoomInButton, "ZI", 0, &Button, 0, "[NumPad+] Zoom in", CUI::CORNER_R) || Input()->KeyDown(KEY_KP_PLUS))
+	if(DoButton_Ex(&s_ZoomInButton, "ZI", 0, &Button, 0, "[NumPad+] Zoom in", CUI::CORNER_R))
 		m_ZoomLevel -= 50;
 
 	TB_Top.VSplitLeft(10.0f, 0, &TB_Top);
@@ -859,8 +866,6 @@ void CEditor::DoToolbar(CUIRect ToolBar)
 			m_AnimateSpeed -= 0.5f;
 	}
 
-	m_WorldZoom = m_ZoomLevel/100.0f;
-
 	TB_Top.VSplitLeft(10.0f, &Button, &TB_Top);
 
 
@@ -947,17 +952,17 @@ void CEditor::DoToolbar(CUIRect ToolBar)
 			}
 		}
 	}
-    
+
 	// tile manipulation
 	{
 		TB_Bottom.VSplitLeft(40.0f, &Button, &TB_Bottom);
 		static int s_BorderBut = 0;
 		CLayerTiles *pT = (CLayerTiles *)GetSelectedLayerType(0, LAYERTYPE_TILES);
-		
+
 		if(DoButton_Editor(&s_BorderBut, "Border", pT?0:-1, &Button, 0, "Adds border tiles"))
 		{
 			if(pT)
-                DoMapBorder();
+				DoMapBorder();
 		}
 	}
 
@@ -966,7 +971,7 @@ void CEditor::DoToolbar(CUIRect ToolBar)
 	// refocus button
 	TB_Bottom.VSplitLeft(50.0f, &Button, &TB_Bottom);
 	static int s_RefocusButton = 0;
-	if(DoButton_Editor(&s_RefocusButton, "Refocus", m_WorldOffsetX&&m_WorldOffsetY?0:-1, &Button, 0, "[HOME] Restore map focus") || Input()->KeyDown(KEY_HOME))
+	if(DoButton_Editor(&s_RefocusButton, "Refocus", m_WorldOffsetX&&m_WorldOffsetY?0:-1, &Button, 0, "[HOME] Restore map focus") || (m_EditBoxActive == 0 && Input()->KeyDown(KEY_HOME)))
 	{
 		m_WorldOffsetX = 0;
 		m_WorldOffsetY = 0;
@@ -1046,6 +1051,12 @@ void CEditor::DoQuad(CQuad *q, int Index)
 	if(dx*dx+dy*dy < 50)
 		UI()->SetHotItem(pID);
 
+	bool IgnoreGrid;
+	if(Input()->KeyPressed(KEY_LALT) || Input()->KeyPressed(KEY_RALT))
+		IgnoreGrid = true;
+	else
+		IgnoreGrid = false;
+
 	// draw selection background
 	if(m_SelectedQuad == Index)
 	{
@@ -1056,58 +1067,82 @@ void CEditor::DoQuad(CQuad *q, int Index)
 
 	if(UI()->ActiveItem() == pID)
 	{
-		// check if we only should move pivot
-		if(s_Operation == OP_MOVE_PIVOT)
+		if(m_MouseDeltaWx*m_MouseDeltaWx+m_MouseDeltaWy*m_MouseDeltaWy > 0.5f)
 		{
-			q->m_aPoints[4].x += f2fx(wx-s_LastWx);
-			q->m_aPoints[4].y += f2fx(wy-s_LastWy);
-		}
-		else if(s_Operation == OP_MOVE_ALL)
-		{
-			// move all points including pivot
-			if(m_GridActive)
+			// check if we only should move pivot
+			if(s_Operation == OP_MOVE_PIVOT)
 			{
-				int LineDistance = GetLineDistance();
+				if(m_GridActive && !IgnoreGrid)
+				{
+					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);
+					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);
+
+					q->m_aPoints[4].x = f2fx(x);
+					q->m_aPoints[4].y = f2fx(y);
+				}
 				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;
+					q->m_aPoints[4].x += f2fx(wx-s_LastWx);
+					q->m_aPoints[4].y += f2fx(wy-s_LastWy);
 				}
 			}
-			else
+			else if(s_Operation == OP_MOVE_ALL)
 			{
-				for(int v = 0; v < 5; v++)
+				// move all points including pivot
+				if(m_GridActive && !IgnoreGrid)
 				{
-						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)
-		{
-			for(int v = 0; v < 4; v++)
+			else if(s_Operation == OP_ROTATE)
 			{
-				q->m_aPoints[v] = s_RotatePoints[v];
-				Rotate(&q->m_aPoints[4], &q->m_aPoints[v], s_RotateAngle);
+				for(int v = 0; v < 4; v++)
+				{
+					q->m_aPoints[v] = s_RotatePoints[v];
+					Rotate(&q->m_aPoints[4], &q->m_aPoints[v], s_RotateAngle);
+				}
 			}
 		}
 
@@ -1143,7 +1178,7 @@ void CEditor::DoQuad(CQuad *q, int Index)
 		ms_pUiGotContext = pID;
 
 		Graphics()->SetColor(1,1,1,1);
-		m_pTooltip = "Left mouse button to move. Hold shift to move pivot. Hold ctrl to rotate.";
+		m_pTooltip = "Left mouse button to move. Hold shift to move pivot. Hold ctrl to rotate. Hold alt to ignore grid.";
 
 		if(UI()->MouseButton(0))
 		{
@@ -1163,6 +1198,8 @@ void CEditor::DoQuad(CQuad *q, int Index)
 				s_Operation = OP_MOVE_ALL;
 
 			UI()->SetActiveItem(pID);
+			if(m_SelectedQuad != Index)
+				m_SelectedPoints = 0;
 			m_SelectedQuad = Index;
 			s_LastWx = wx;
 			s_LastWy = wy;
@@ -1170,6 +1207,8 @@ void CEditor::DoQuad(CQuad *q, int Index)
 
 		if(UI()->MouseButton(1))
 		{
+			if(m_SelectedQuad != Index)
+				m_SelectedPoints = 0;
 			m_SelectedQuad = Index;
 			s_Operation = OP_CONTEXT_MENU;
 			UI()->SetActiveItem(pID);
@@ -1216,6 +1255,12 @@ void CEditor::DoQuadPoint(CQuad *pQuad, int QuadIndex, int V)
 	static bool s_Moved;
 	static int s_Operation = OP_NONE;
 
+	bool IgnoreGrid;
+	if(Input()->KeyPressed(KEY_LALT) || Input()->KeyPressed(KEY_RALT))
+		IgnoreGrid = true;
+	else
+		IgnoreGrid = false;
+
 	if(UI()->ActiveItem() == pID)
 	{
 		float dx = m_MouseDeltaWx;
@@ -1230,7 +1275,7 @@ void CEditor::DoQuadPoint(CQuad *pQuad, int QuadIndex, int V)
 		{
 			if(s_Operation == OP_MOVEPOINT)
 			{
-				if(m_GridActive)
+				if(m_GridActive && !IgnoreGrid)
 				{
 					for(int m = 0; m < 4; m++)
 						if(m_SelectedPoints&(1<<m))
@@ -1267,12 +1312,12 @@ void CEditor::DoQuadPoint(CQuad *pQuad, int QuadIndex, int V)
 				for(int m = 0; m < 4; m++)
 					if(m_SelectedPoints&(1<<m))
 					{
-						// 0,2;1,3 - line x 
+						// 0,2;1,3 - line x
 						// 0,1;2,3 - line y
 
 						pQuad->m_aTexcoords[m].x += f2fx(dx*0.001f);
 						pQuad->m_aTexcoords[(m+2)%4].x += f2fx(dx*0.001f);
-						
+
 						pQuad->m_aTexcoords[m].y += f2fx(dy*0.001f);
 						pQuad->m_aTexcoords[m^1].y += f2fx(dy*0.001f);
 					}
@@ -1311,7 +1356,7 @@ void CEditor::DoQuadPoint(CQuad *pQuad, int QuadIndex, int V)
 		ms_pUiGotContext = pID;
 
 		Graphics()->SetColor(1,1,1,1);
-		m_pTooltip = "Left mouse button to move. Hold shift to move the texture.";
+		m_pTooltip = "Left mouse button to move. Hold shift to move the texture. Hold alt to ignore grid.";
 
 		if(UI()->MouseButton(0))
 		{
@@ -1358,6 +1403,218 @@ void CEditor::DoQuadPoint(CQuad *pQuad, int QuadIndex, int V)
 	Graphics()->QuadsDraw(&QuadItem, 1);
 }
 
+void CEditor::DoQuadEnvelopes(CQuad *pQuad, int Index, int TexID)
+{
+	CEnvelope *pEnvelope = 0x0;
+	if(pQuad->m_PosEnv >= 0 && pQuad->m_PosEnv < m_Map.m_lEnvelopes.size())
+		pEnvelope = m_Map.m_lEnvelopes[pQuad->m_PosEnv];
+	if (!pEnvelope)
+		return;
+
+	//QuadParams
+	CPoint *pPoints = pQuad->m_aPoints;
+
+	//Draw Lines
+	Graphics()->TextureSet(-1);
+	Graphics()->LinesBegin();
+		Graphics()->SetColor(80.0f/255, 150.0f/255, 230.f/255, 0.5f);
+		for(int i = 0; i < pEnvelope->m_lPoints.size()-1; i++)
+		{
+			float OffsetX =  fx2f(pEnvelope->m_lPoints[i].m_aValues[0]);
+			float OffsetY = fx2f(pEnvelope->m_lPoints[i].m_aValues[1]);
+			vec2 Pos0 = vec2(fx2f(pPoints[4].x)+OffsetX, fx2f(pPoints[4].y)+OffsetY);
+
+			OffsetX = fx2f(pEnvelope->m_lPoints[i+1].m_aValues[0]);
+			OffsetY = fx2f(pEnvelope->m_lPoints[i+1].m_aValues[1]);
+			vec2 Pos1 = vec2(fx2f(pPoints[4].x)+OffsetX, fx2f(pPoints[4].y)+OffsetY);
+
+			IGraphics::CLineItem Line = IGraphics::CLineItem(Pos0.x, Pos0.y, Pos1.x, Pos1.y);
+			Graphics()->LinesDraw(&Line, 1);
+		}
+		Graphics()->SetColor(1.0f, 1.0f, 1.0f, 1.0f);
+	Graphics()->LinesEnd();
+
+	//Draw Quads
+	for(int i = 0; i < pEnvelope->m_lPoints.size(); i++)
+	{
+		Graphics()->TextureSet(TexID);
+		Graphics()->QuadsBegin();
+		
+		//Calc Env Position
+		float OffsetX =  fx2f(pEnvelope->m_lPoints[i].m_aValues[0]);
+		float OffsetY = fx2f(pEnvelope->m_lPoints[i].m_aValues[1]);
+		float Rot = fx2f(pEnvelope->m_lPoints[i].m_aValues[2])/360.0f*pi*2;
+
+		//Set Colours
+		float Alpha = (m_SelectedQuadEnvelope == pQuad->m_PosEnv && m_SelectedEnvelopePoint == i) ? 0.65f : 0.35f;
+		IGraphics::CColorVertex aArray[4] = {
+			IGraphics::CColorVertex(0, pQuad->m_aColors[0].r, pQuad->m_aColors[0].g, pQuad->m_aColors[0].b, Alpha),
+			IGraphics::CColorVertex(1, pQuad->m_aColors[1].r, pQuad->m_aColors[1].g, pQuad->m_aColors[1].b, Alpha),
+			IGraphics::CColorVertex(2, pQuad->m_aColors[2].r, pQuad->m_aColors[2].g, pQuad->m_aColors[2].b, Alpha),
+			IGraphics::CColorVertex(3, pQuad->m_aColors[3].r, pQuad->m_aColors[3].g, pQuad->m_aColors[3].b, Alpha)};
+		Graphics()->SetColorVertex(aArray, 4);
+
+		//Rotation
+		if(Rot != 0)
+		{
+			static CPoint aRotated[4];
+			aRotated[0] = pQuad->m_aPoints[0];
+			aRotated[1] = pQuad->m_aPoints[1];
+			aRotated[2] = pQuad->m_aPoints[2];
+			aRotated[3] = pQuad->m_aPoints[3];
+			pPoints = aRotated;
+
+			Rotate(&pQuad->m_aPoints[4], &aRotated[0], Rot);
+			Rotate(&pQuad->m_aPoints[4], &aRotated[1], Rot);
+			Rotate(&pQuad->m_aPoints[4], &aRotated[2], Rot);
+			Rotate(&pQuad->m_aPoints[4], &aRotated[3], Rot);
+		}
+
+		//Set Texture Coords
+		Graphics()->QuadsSetSubsetFree(
+			fx2f(pQuad->m_aTexcoords[0].x), fx2f(pQuad->m_aTexcoords[0].y),
+			fx2f(pQuad->m_aTexcoords[1].x), fx2f(pQuad->m_aTexcoords[1].y),
+			fx2f(pQuad->m_aTexcoords[2].x), fx2f(pQuad->m_aTexcoords[2].y),
+			fx2f(pQuad->m_aTexcoords[3].x), fx2f(pQuad->m_aTexcoords[3].y)
+		);
+
+		//Set Quad Coords & Draw
+		IGraphics::CFreeformItem Freeform(
+			fx2f(pPoints[0].x)+OffsetX, fx2f(pPoints[0].y)+OffsetY,
+			fx2f(pPoints[1].x)+OffsetX, fx2f(pPoints[1].y)+OffsetY,
+			fx2f(pPoints[2].x)+OffsetX, fx2f(pPoints[2].y)+OffsetY,
+			fx2f(pPoints[3].x)+OffsetX, fx2f(pPoints[3].y)+OffsetY);
+		Graphics()->QuadsDrawFreeform(&Freeform, 1);
+
+		Graphics()->QuadsEnd();
+		
+		Graphics()->TextureSet(-1);
+		Graphics()->QuadsBegin();
+		DoQuadEnvPoint(pQuad, Index, i);
+		Graphics()->QuadsEnd();
+	}
+}
+
+void CEditor::DoQuadEnvPoint(CQuad *pQuad, int QIndex, int PIndex)
+{
+	enum
+	{
+		OP_NONE=0,
+		OP_MOVE,
+		OP_ROTATE,
+	};
+
+	// some basic values
+	static float s_LastWx;
+	static float s_LastWy;
+	static int s_Operation = OP_NONE;
+	float wx = UI()->MouseWorldX();
+	float wy = UI()->MouseWorldY();
+	CEnvelope *pEnvelope = m_Map.m_lEnvelopes[pQuad->m_PosEnv];
+	void *pID = &pEnvelope->m_lPoints[PIndex];
+	static int s_ActQIndex = -1;
+
+	// get pivot
+	float CenterX = fx2f(pQuad->m_aPoints[4].x)+fx2f(pEnvelope->m_lPoints[PIndex].m_aValues[0]);
+	float CenterY = fx2f(pQuad->m_aPoints[4].y)+fx2f(pEnvelope->m_lPoints[PIndex].m_aValues[1]);
+
+	float dx = (CenterX - wx)/m_WorldZoom;
+	float dy = (CenterY - wy)/m_WorldZoom;
+	if(dx*dx+dy*dy < 50.0f && UI()->ActiveItem() == 0)
+	{
+		UI()->SetHotItem(pID);
+		s_ActQIndex = QIndex;
+	}
+
+	bool IgnoreGrid;
+	if(Input()->KeyPressed(KEY_LALT) || Input()->KeyPressed(KEY_RALT))
+		IgnoreGrid = true;
+	else
+		IgnoreGrid = false;
+
+	if(UI()->ActiveItem() == pID && s_ActQIndex == QIndex)
+	{
+		if(s_Operation == OP_MOVE)
+		{
+			if(m_GridActive && !IgnoreGrid)
+			{
+				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);
+
+				pEnvelope->m_lPoints[PIndex].m_aValues[0] = f2fx(x);
+				pEnvelope->m_lPoints[PIndex].m_aValues[1] = f2fx(y);
+			}
+			else
+			{
+				pEnvelope->m_lPoints[PIndex].m_aValues[0] += f2fx(wx-s_LastWx);
+				pEnvelope->m_lPoints[PIndex].m_aValues[1] += f2fx(wy-s_LastWy);
+			}
+		}
+		else if(s_Operation == OP_ROTATE)
+			pEnvelope->m_lPoints[PIndex].m_aValues[2] += 10*m_MouseDeltaX;
+
+		s_LastWx = wx;
+		s_LastWy = wy;
+
+		if(!UI()->MouseButton(0))
+		{
+			m_LockMouse = false;
+			s_Operation = OP_NONE;
+			UI()->SetActiveItem(0);
+		}
+
+		Graphics()->SetColor(1.0f, 1.0f, 1.0f, 1.0f);
+	}
+	else if(UI()->HotItem() == pID && s_ActQIndex == QIndex)
+	{
+		ms_pUiGotContext = pID;
+
+		Graphics()->SetColor(1.0f, 1.0f, 1.0f, 1.0f);
+		m_pTooltip = "Left mouse button to move. Hold ctrl to rotate. Hold alt to ignore grid.";
+
+		if(UI()->MouseButton(0))
+		{
+			if(Input()->KeyPressed(KEY_LCTRL) || Input()->KeyPressed(KEY_RCTRL))
+			{
+				m_LockMouse = true;
+				s_Operation = OP_ROTATE;
+			}
+			else
+				s_Operation = OP_MOVE;
+
+			m_SelectedEnvelopePoint = PIndex;
+			m_SelectedQuadEnvelope = pQuad->m_PosEnv;
+
+			UI()->SetActiveItem(pID);
+			if(m_SelectedQuad != QIndex)
+				m_SelectedPoints = 0;
+			m_SelectedQuad = QIndex;
+			s_LastWx = wx;
+			s_LastWy = wy;
+		}
+		else
+		{
+			m_SelectedEnvelopePoint = -1;
+			m_SelectedQuadEnvelope = -1;
+		}
+	}
+	else
+		Graphics()->SetColor(0.0f, 1.0f, 0.0f, 1.0f);
+
+	IGraphics::CQuadItem QuadItem(CenterX, CenterY, 5.0f*m_WorldZoom, 5.0f*m_WorldZoom);
+	Graphics()->QuadsDraw(&QuadItem, 1);
+}
+
 void CEditor::DoMapEditor(CUIRect View, CUIRect ToolBar)
 {
 	// render all good stuff
@@ -1384,6 +1641,12 @@ void CEditor::DoMapEditor(CUIRect View, CUIRect ToolBar)
 			pT->ShowInfo();
 		}
 	}
+	else
+	{
+		// fix aspect ratio of the image in the picker
+		float Max = min(View.w, View.h);
+		View.w = View.h = Max;
+	}
 
 	static void *s_pEditorID = (void *)&s_pEditorID;
 	int Inside = UI()->MouseInside(&View);
@@ -1563,8 +1826,8 @@ void CEditor::DoMapEditor(CUIRect View, CUIRect ToolBar)
 				{
 					if(!UI()->MouseButton(0))
 					{
-                        for(int k = 0; k < NumEditLayers; k++)
-                            pEditLayers[k]->FillSelection(m_Brush.IsEmpty(), m_Brush.m_lLayers[0], r);
+						for(int k = 0; k < NumEditLayers; k++)
+							pEditLayers[k]->FillSelection(m_Brush.IsEmpty(), m_Brush.m_lLayers[0], r);
 					}
 					else
 					{
@@ -1596,10 +1859,10 @@ void CEditor::DoMapEditor(CUIRect View, CUIRect ToolBar)
 						}
 
 					}
-					
+
 					CLayerTiles *pLayer = (CLayerTiles*)GetSelectedLayerType(0, LAYERTYPE_TILES);
 					if((Input()->KeyPressed(KEY_LSHIFT) || Input()->KeyPressed(KEY_RSHIFT)) && pLayer)
-                        s_Operation = OP_BRUSH_PAINT;
+						s_Operation = OP_BRUSH_PAINT;
 				}
 
 				if(!m_Brush.IsEmpty())
@@ -1656,6 +1919,9 @@ void CEditor::DoMapEditor(CUIRect View, CUIRect ToolBar)
 					{
 						CLayerQuads *pLayer = (CLayerQuads *)pEditLayers[k];
 
+						if(!m_ShowEnvelopePreview)
+							m_ShowEnvelopePreview = 2;
+
 						Graphics()->TextureSet(-1);
 						Graphics()->QuadsBegin();
 						for(int i = 0; i < pLayer->m_lQuads.size(); i++)
@@ -1705,11 +1971,11 @@ void CEditor::DoMapEditor(CUIRect View, CUIRect ToolBar)
 		}
 	}
 
-	if(GetSelectedGroup() && GetSelectedGroup()->m_UseClipping)
+	if(!m_ShowPicker && GetSelectedGroup() && GetSelectedGroup()->m_UseClipping)
 	{
 		CLayerGroup *g = m_Map.m_pGameGroup;
 		g->MapScreen();
-	
+
 		Graphics()->TextureSet(-1);
 		Graphics()->LinesBegin();
 
@@ -1813,6 +2079,24 @@ void CEditor::DoMapEditor(CUIRect View, CUIRect ToolBar)
 		Graphics()->LinesEnd();
 	}
 
+	if (!m_ShowPicker && m_ShowTileInfo && m_ShowEnvelopePreview != 0 && GetSelectedLayer(0) && GetSelectedLayer(0)->m_Type == LAYERTYPE_QUADS)
+	{
+		GetSelectedGroup()->MapScreen();
+
+		CLayerQuads *pLayer = (CLayerQuads*)GetSelectedLayer(0);
+		int TexID = -1;
+		if(pLayer->m_Image >= 0 && pLayer->m_Image < m_Map.m_lImages.size())
+			TexID = m_Map.m_lImages[pLayer->m_Image]->m_TexID;
+
+		for(int i = 0; i < pLayer->m_lQuads.size(); i++)
+		{
+			if((m_ShowEnvelopePreview == 1 && pLayer->m_lQuads[i].m_PosEnv == m_SelectedEnvelope) || m_ShowEnvelopePreview == 2)
+				DoQuadEnvelopes(&pLayer->m_lQuads[i], i, TexID);
+		}
+
+		m_ShowEnvelopePreview = 0;
+    }
+
 	Graphics()->MapScreen(UI()->Screen()->x, UI()->Screen()->y, UI()->Screen()->w, UI()->Screen()->h);
 	//UI()->ClipDisable();
 }
@@ -1908,7 +2192,7 @@ int CEditor::DoProperties(CUIRect *pToolBox, CProperty *pProps, int *pIDs, int *
 			if(pProps[i].m_Value < 0)
 				str_copy(aBuf, "None", sizeof(aBuf));
 			else
-				str_format(aBuf, sizeof(aBuf),"%s",  m_Map.m_lImages[pProps[i].m_Value]->m_aName);
+				str_format(aBuf, sizeof(aBuf),"%s", m_Map.m_lImages[pProps[i].m_Value]->m_aName);
 
 			if(DoButton_Editor(&pIDs[i], aBuf, 0, &Shifter, 0, 0))
 				PopupSelectImageInvoke(pProps[i].m_Value, UI()->MouseX(), UI()->MouseY());
@@ -2052,7 +2336,7 @@ void CEditor::RenderLayers(CUIRect ToolBox, CUIRect ToolBar, CUIRect View)
 
 					static int s_GroupPopupId = 0;
 					if(Result == 2)
-						UiInvokePopupMenu(&s_GroupPopupId, 0, UI()->MouseX(), UI()->MouseY(), 120, 220, PopupGroup);
+						UiInvokePopupMenu(&s_GroupPopupId, 0, UI()->MouseX(), UI()->MouseY(), 145, 220, PopupGroup);
 
 					if(m_Map.m_lGroups[g]->m_lLayers.size() && Input()->MouseDoubleClick())
 						m_Map.m_lGroups[g]->m_Collapse ^= 1;
@@ -2147,8 +2431,8 @@ void CEditor::ReplaceImage(const char *pFileName, int StorageType, void *pUser)
 	pEditor->SortImages();
 	for(int i = 0; i < pEditor->m_Map.m_lImages.size(); ++i)
 	{
-	    if(!str_comp(pEditor->m_Map.m_lImages[i]->m_aName, pImg->m_aName))
-           pEditor->m_SelectedImage = i;
+		if(!str_comp(pEditor->m_Map.m_lImages[i]->m_aName, pImg->m_aName))
+			pEditor->m_SelectedImage = i;
 	}
 	pEditor->m_Dialog = DIALOG_NONE;
 }
@@ -2165,8 +2449,8 @@ void CEditor::AddImage(const char *pFileName, int StorageType, void *pUser)
 	ExtractName(pFileName, aBuf, sizeof(aBuf));
 	for(int i = 0; i < pEditor->m_Map.m_lImages.size(); ++i)
 	{
-	    if(!str_comp(pEditor->m_Map.m_lImages[i]->m_aName, aBuf))
-            return;
+		if(!str_comp(pEditor->m_Map.m_lImages[i]->m_aName, aBuf))
+			return;
 	}
 
 	CEditorImage *pImg = new CEditorImage(pEditor);
@@ -2292,7 +2576,7 @@ void CEditor::SortImages()
 		gs_pSortedIndex = 0;
 	}
 }
-	
+
 
 void CEditor::RenderImages(CUIRect ToolBox, CUIRect ToolBar, CUIRect View)
 {
@@ -2390,15 +2674,28 @@ void CEditor::RenderImages(CUIRect ToolBox, CUIRect ToolBar, CUIRect View)
 					r.w = r.h;
 				else
 					r.h = r.w;
+				float Max = (float)(max(m_Map.m_lImages[i]->m_Width, m_Map.m_lImages[i]->m_Height));
+				r.w *= m_Map.m_lImages[i]->m_Width/Max;
+				r.h *= m_Map.m_lImages[i]->m_Height/Max;
 				Graphics()->TextureSet(m_Map.m_lImages[i]->m_TexID);
 				Graphics()->BlendNormal();
+				Graphics()->WrapClamp();
 				Graphics()->QuadsBegin();
 				IGraphics::CQuadItem QuadItem(r.x, r.y, r.w, r.h);
 				Graphics()->QuadsDrawTL(&QuadItem, 1);
 				Graphics()->QuadsEnd();
-
+				Graphics()->WrapNormal();
 			}
 		}
+
+		// separator
+		ToolBox.HSplitTop(5.0f, &Slot, &ToolBox);
+		ImageCur += 5.0f;
+		IGraphics::CLineItem LineItem(Slot.x, Slot.y+Slot.h/2, Slot.x+Slot.w, Slot.y+Slot.h/2);
+		Graphics()->TextureSet(-1);
+		Graphics()->LinesBegin();
+		Graphics()->LinesDraw(&LineItem, 1);
+		Graphics()->LinesEnd();
 	}
 
 	if(ImageCur + 27.0f > ImageStopAt)
@@ -2409,7 +2706,6 @@ void CEditor::RenderImages(CUIRect ToolBox, CUIRect ToolBar, CUIRect View)
 
 	// new image
 	static int s_NewImageButton = 0;
-	ToolBox.HSplitTop(10.0f, &Slot, &ToolBox);
 	ToolBox.HSplitTop(12.0f, &Slot, &ToolBox);
 	if(DoButton_Editor(&s_NewImageButton, "Add", 0, &Slot, 0, "Load a new image to use in the map"))
 		InvokeFileDialog(IStorage::TYPE_ALL, FILETYPE_IMG, "Add Image", "Add", "mapres", "", AddImage, this);
@@ -2485,11 +2781,13 @@ void CEditor::RenderFileDialog()
 	RenderTools()->DrawUIRect(&View, vec4(0,0,0,0.75f), CUI::CORNER_ALL, 5.0f);
 	View.Margin(10.0f, &View);
 
-	CUIRect Title, FileBox, FileBoxLabel, ButtonBar, Scroll;
+	CUIRect Title, FileBox, FileBoxLabel, ButtonBar, Scroll, PathBox;
 	View.HSplitTop(18.0f, &Title, &View);
 	View.HSplitTop(5.0f, 0, &View); // some spacing
 	View.HSplitBottom(14.0f, &View, &ButtonBar);
 	View.HSplitBottom(10.0f, &View, 0); // some spacing
+	View.HSplitBottom(14.0f, &View, &PathBox);
+	View.HSplitBottom(5.0f, &View, 0); // some spacing
 	View.HSplitBottom(14.0f, &View, &FileBox);
 	FileBox.VSplitLeft(55.0f, &FileBoxLabel, &FileBox);
 	View.HSplitBottom(10.0f, &View, 0); // some spacing
@@ -2500,6 +2798,15 @@ void CEditor::RenderFileDialog()
 	Title.VMargin(10.0f, &Title);
 	UI()->DoLabel(&Title, m_pFileDialogTitle, 12.0f, -1, -1);
 
+	// pathbox
+	char aPath[128], aBuf[128];
+	if(m_FilesSelectedIndex != -1)
+		Storage()->GetCompletePath(m_FileList[m_FilesSelectedIndex].m_StorageType, m_pFileDialogPath, aPath, sizeof(aPath));
+	else
+		aPath[0] = 0;
+	str_format(aBuf, sizeof(aBuf), "Current path: %s", aPath);
+	UI()->DoLabel(&PathBox, aBuf, 10.0f, -1, -1);
+
 	// filebox
 	if(m_FileDialogStorageType == IStorage::TYPE_SAVE)
 	{
@@ -2596,6 +2903,7 @@ void CEditor::RenderFileDialog()
 	static int s_OkButton = 0;
 	static int s_CancelButton = 0;
 	static int s_NewFolderButton = 0;
+	static int s_MapInfoButton = 0;
 
 	CUIRect Button;
 	ButtonBar.VSplitRight(50.0f, &ButtonBar, &Button);
@@ -2671,6 +2979,22 @@ void CEditor::RenderFileDialog()
 			UI()->SetActiveItem(0);
 		}
 	}
+
+	if(m_FileDialogStorageType == IStorage::TYPE_SAVE)
+	{
+		ButtonBar.VSplitLeft(40.0f, 0, &ButtonBar);
+		ButtonBar.VSplitLeft(70.0f, &Button, &ButtonBar);
+		if(DoButton_Editor(&s_MapInfoButton, "Map details", 0, &Button, 0, 0))
+		{
+			str_copy(m_Map.m_MapInfo.m_aAuthorTmp, m_Map.m_MapInfo.m_aAuthor, sizeof(m_Map.m_MapInfo.m_aAuthorTmp));
+			str_copy(m_Map.m_MapInfo.m_aVersionTmp, m_Map.m_MapInfo.m_aVersion, sizeof(m_Map.m_MapInfo.m_aVersionTmp));
+			str_copy(m_Map.m_MapInfo.m_aCreditsTmp, m_Map.m_MapInfo.m_aCredits, sizeof(m_Map.m_MapInfo.m_aCreditsTmp));
+			str_copy(m_Map.m_MapInfo.m_aLicenseTmp, m_Map.m_MapInfo.m_aLicense, sizeof(m_Map.m_MapInfo.m_aLicenseTmp));
+			static int s_MapInfoPopupID = 0;
+			UiInvokePopupMenu(&s_MapInfoPopupID, 0, Width/2.0f-200.0f, Height/2.0f-100.0f, 400.0f, 200.0f, PopupMapInfo);
+			UI()->SetActiveItem(0);
+		}
+	}
 }
 
 void CEditor::FilelistPopulate(int StorageType)
@@ -2711,7 +3035,7 @@ void CEditor::InvokeFileDialog(int StorageType, int FileType, const char *pTitle
 		str_copy(m_aFileDialogFileName, pDefaultName, sizeof(m_aFileDialogFileName));
 	if(pBasePath)
 		str_copy(m_aFileDialogCurrentFolder, pBasePath, sizeof(m_aFileDialogCurrentFolder));
-	
+
 	FilelistPopulate(m_FileDialogStorageType);
 
 	m_Dialog = DIALOG_FILE;
@@ -2731,10 +3055,10 @@ void CEditor::RenderModebar(CUIRect View)
 		const char *pButName = m_Mode == MODE_LAYERS ? "Layers" : "Images";
 		if(DoButton_Tab(&s_Button, pButName, 0, &Button, 0, "Switch between images and layers managment."))
 		{
-		    if(m_Mode == MODE_LAYERS)
-                m_Mode = MODE_IMAGES;
-            else
-                m_Mode = MODE_LAYERS;
+			if(m_Mode == MODE_LAYERS)
+				m_Mode = MODE_IMAGES;
+			else
+				m_Mode = MODE_LAYERS;
 		}
 	}
 
@@ -2908,6 +3232,17 @@ void CEditor::RenderEnvelopeEditor(CUIRect View)
 				if(DoButton_Editor(&s_aChannelButtons[i], s_paNames[pEnvelope->m_Channels-3][i], s_ActiveChannels&Bit, &Button, 0, paDescriptions[pEnvelope->m_Channels-3][i]))
 					s_ActiveChannels ^= Bit;
 			}
+
+			// sync checkbox
+			ToolBar.VSplitLeft(15.0f, &Button, &ToolBar);
+			ToolBar.VSplitLeft(12.0f, &Button, &ToolBar);
+			static int s_SyncButton;
+			if(DoButton_Editor(&s_SyncButton, pEnvelope->m_Synchronized?"X":"", 0, &Button, 0, "Enable envelope synchronization between clients"))
+				pEnvelope->m_Synchronized = !pEnvelope->m_Synchronized;
+
+			ToolBar.VSplitLeft(4.0f, &Button, &ToolBar);
+			ToolBar.VSplitLeft(80.0f, &Button, &ToolBar);
+			UI()->DoLabel(&Button, "Synchronized", 10.0f, -1, -1);
 		}
 
 		float EndTime = pEnvelope->EndTime();
@@ -2947,6 +3282,7 @@ void CEditor::RenderEnvelopeEditor(CUIRect View)
 					m_Map.m_Modified = true;
 				}
 
+				m_ShowEnvelopePreview = 1;
 				m_pTooltip = "Press right mouse button to create a new point";
 			}
 		}
@@ -3085,10 +3421,13 @@ void CEditor::RenderEnvelopeEditor(CUIRect View)
 					{
 						if(!UI()->MouseButton(0))
 						{
+							m_SelectedQuadEnvelope = -1;
+							m_SelectedEnvelopePoint = -1;
+
 							UI()->SetActiveItem(0);
 						}
 						else
-						{							
+						{
 							if(Input()->KeyPressed(KEY_LSHIFT) || Input()->KeyPressed(KEY_RSHIFT))
 							{
 								if(i != 0)
@@ -3110,6 +3449,10 @@ void CEditor::RenderEnvelopeEditor(CUIRect View)
 								else
 									pEnvelope->m_lPoints[i].m_aValues[c] -= f2fx(m_MouseDeltaY*ValueScale);
 							}
+
+							m_SelectedQuadEnvelope = m_SelectedEnvelope;
+							m_ShowEnvelopePreview = 1;
+							m_SelectedEnvelopePoint = i;
 							m_Map.m_Modified = true;
 						}
 
@@ -3132,6 +3475,7 @@ void CEditor::RenderEnvelopeEditor(CUIRect View)
 							m_Map.m_Modified = true;
 						}
 
+						m_ShowEnvelopePreview = 1;
 						ColorMod = 100.0f;
 						Graphics()->SetColor(1,0.75f,0.75f,1);
 						m_pTooltip = "Left mouse to drag. Hold ctrl to be more precise. Hold shift to alter time point aswell. Right click to delete.";
@@ -3143,7 +3487,10 @@ void CEditor::RenderEnvelopeEditor(CUIRect View)
 						CurrentValue = pEnvelope->m_lPoints[i].m_aValues[c];
 					}
 
-					Graphics()->SetColor(aColors[c].r*ColorMod, aColors[c].g*ColorMod, aColors[c].b*ColorMod, 1.0f);
+					if (m_SelectedQuadEnvelope == m_SelectedEnvelope && m_SelectedEnvelopePoint == i)
+						Graphics()->SetColor(1.0f, 1.0f, 1.0f, 1.0f);
+					else
+						Graphics()->SetColor(aColors[c].r*ColorMod, aColors[c].g*ColorMod, aColors[c].b*ColorMod, 1.0f);
 					IGraphics::CQuadItem QuadItem(Final.x, Final.y, Final.w, Final.h);
 					Graphics()->QuadsDrawTL(&QuadItem, 1);
 				}
@@ -3266,10 +3613,15 @@ void CEditor::RenderMenubar(CUIRect MenuBar)
 		(void)0;
 		*/
 
+	CUIRect Info;
 	MenuBar.VSplitLeft(40.0f, 0, &MenuBar);
+	MenuBar.VSplitLeft(MenuBar.w*0.75f, &MenuBar, &Info);
 	char aBuf[128];
 	str_format(aBuf, sizeof(aBuf), "File: %s", m_aFileName);
 	UI()->DoLabel(&MenuBar, aBuf, 10.0f, -1, -1);
+
+	str_format(aBuf, sizeof(aBuf), "Z: %i, A: %.1f, G: %i", m_ZoomLevel, m_AnimateSpeed, m_GridFactor);
+	UI()->DoLabel(&Info, aBuf, 10.0f, 1, -1);
 }
 
 void CEditor::Render()
@@ -3285,6 +3637,9 @@ void CEditor::Render()
 	// reset tip
 	m_pTooltip = 0;
 
+	if(m_EditBoxActive)
+		--m_EditBoxActive;
+
 	// render checker
 	RenderBackground(View, ms_CheckerTexture, 32.0f, 1.0f);
 
@@ -3314,7 +3669,17 @@ void CEditor::Render()
 	if(m_Mode == MODE_LAYERS)
 		DoMapEditor(View, ToolBar);
 
-	// do the scrolling
+	// do zooming
+	if(Input()->KeyDown(KEY_KP_MINUS))
+		m_ZoomLevel += 50;
+	if(Input()->KeyDown(KEY_KP_PLUS))
+		m_ZoomLevel -= 50;
+	if(Input()->KeyDown(KEY_KP_MULTIPLY))
+	{
+		m_EditorOffsetX = 0;
+		m_EditorOffsetY = 0;
+		m_ZoomLevel = 100;
+	}
 	if(m_Dialog == DIALOG_NONE && UI()->MouseInside(&View))
 	{
 		if(Input()->KeyPresses(KEY_MOUSE_WHEEL_UP))
@@ -3322,9 +3687,9 @@ void CEditor::Render()
 
 		if(Input()->KeyPresses(KEY_MOUSE_WHEEL_DOWN))
 			m_ZoomLevel += 20;
-
-		m_ZoomLevel = clamp(m_ZoomLevel, 50, 2000);
 	}
+	m_ZoomLevel = clamp(m_ZoomLevel, 50, 2000);
+	m_WorldZoom = m_ZoomLevel/100.0f;
 
 	if(m_GuiActive)
 	{
@@ -3382,6 +3747,7 @@ void CEditor::Render()
 		static int s_PopupID = 0;
 		UiInvokePopupMenu(&s_PopupID, 0, Width/2.0f-200.0f, Height/2.0f-100.0f, 400.0f, 200.0f, PopupEvent);
 		m_PopupEventActivated = false;
+		m_PopupEventWasActivated = true;
 	}
 
 
@@ -3443,12 +3809,12 @@ void CEditor::Reset(bool CreateDefault)
 	m_SelectedPoints = 0;
 	m_SelectedEnvelope = 0;
 	m_SelectedImage = 0;
-	
+
 	m_WorldOffsetX = 0;
 	m_WorldOffsetY = 0;
 	m_EditorOffsetX = 0.0f;
 	m_EditorOffsetY = 0.0f;
-	
+
 	m_WorldZoom = 1.0f;
 	m_ZoomLevel = 200;
 
@@ -3458,6 +3824,8 @@ void CEditor::Reset(bool CreateDefault)
 	m_MouseDeltaWy = 0;
 
 	m_Map.m_Modified = false;
+
+	m_ShowEnvelopePreview = 0;
 }
 
 int CEditor::GetLineDistance()
@@ -3490,19 +3858,27 @@ void CEditorMap::DeleteEnvelope(int Index)
 		for(int j = 0; j < m_lGroups[i]->m_lLayers.size(); ++j)
 			if(m_lGroups[i]->m_lLayers[j]->m_Type == LAYERTYPE_QUADS)
 			{
-				CLayerQuads *Layer = static_cast<CLayerQuads *>(m_lGroups[i]->m_lLayers[j]);
-				for(int k = 0; k < Layer->m_lQuads.size(); ++k)
+				CLayerQuads *pLayer = static_cast<CLayerQuads *>(m_lGroups[i]->m_lLayers[j]);
+				for(int k = 0; k < pLayer->m_lQuads.size(); ++k)
 				{
-					if(Layer->m_lQuads[k].m_PosEnv == Index)
-						Layer->m_lQuads[k].m_PosEnv = -1;
-					else if(Layer->m_lQuads[k].m_PosEnv > Index)
-						Layer->m_lQuads[k].m_PosEnv--;
-					if(Layer->m_lQuads[k].m_ColorEnv == Index)
-						Layer->m_lQuads[k].m_ColorEnv = -1;
-					else if(Layer->m_lQuads[k].m_ColorEnv > Index)
-						Layer->m_lQuads[k].m_ColorEnv--;
+					if(pLayer->m_lQuads[k].m_PosEnv == Index)
+						pLayer->m_lQuads[k].m_PosEnv = -1;
+					else if(pLayer->m_lQuads[k].m_PosEnv > Index)
+						pLayer->m_lQuads[k].m_PosEnv--;
+					if(pLayer->m_lQuads[k].m_ColorEnv == Index)
+						pLayer->m_lQuads[k].m_ColorEnv = -1;
+					else if(pLayer->m_lQuads[k].m_ColorEnv > Index)
+						pLayer->m_lQuads[k].m_ColorEnv--;
 				}
 			}
+			else if(m_lGroups[i]->m_lLayers[j]->m_Type == LAYERTYPE_TILES)
+			{
+				CLayerTiles *pLayer = static_cast<CLayerTiles *>(m_lGroups[i]->m_lLayers[j]);
+				if(pLayer->m_ColorEnv == Index)
+					pLayer->m_ColorEnv = -1;
+				if(pLayer->m_ColorEnv > Index)
+					pLayer->m_ColorEnv--;
+			}
 
 	m_lEnvelopes.remove_index(Index);
 }
@@ -3529,6 +3905,8 @@ void CEditorMap::Clean()
 	m_lEnvelopes.delete_all();
 	m_lImages.delete_all();
 
+	m_MapInfo.Reset();
+
 	m_pGameLayer = 0x0;
 	m_pGameGroup = 0x0;
 
@@ -3594,19 +3972,19 @@ void CEditor::Init()
 
 void CEditor::DoMapBorder()
 {
-    CLayerTiles *pT = (CLayerTiles *)GetSelectedLayerType(0, LAYERTYPE_TILES);
-    
-    for(int i = 0; i < pT->m_Width*2; ++i)
-        pT->m_pTiles[i].m_Index = 1;
-        
-    for(int i = 0; i < pT->m_Width*pT->m_Height; ++i)
-    {
-        if(i%pT->m_Width < 2 || i%pT->m_Width > pT->m_Width-3)
-            pT->m_pTiles[i].m_Index = 1;
-    }
-    
-    for(int i = (pT->m_Width*(pT->m_Height-2)); i < pT->m_Width*pT->m_Height; ++i)
-        pT->m_pTiles[i].m_Index = 1;
+	CLayerTiles *pT = (CLayerTiles *)GetSelectedLayerType(0, LAYERTYPE_TILES);
+
+	for(int i = 0; i < pT->m_Width*2; ++i)
+		pT->m_pTiles[i].m_Index = 1;
+
+	for(int i = 0; i < pT->m_Width*pT->m_Height; ++i)
+	{
+		if(i%pT->m_Width < 2 || i%pT->m_Width > pT->m_Width-3)
+			pT->m_pTiles[i].m_Index = 1;
+	}
+
+	for(int i = (pT->m_Width*(pT->m_Height-2)); i < pT->m_Width*pT->m_Height; ++i)
+		pT->m_pTiles[i].m_Index = 1;
 }
 
 void CEditor::UpdateAndRender()
diff --git a/src/game/editor/editor.h b/src/game/editor/editor.h
index 1a904953..a81474d9 100644
--- a/src/game/editor/editor.h
+++ b/src/game/editor/editor.h
@@ -51,6 +51,7 @@ public:
 	array<CEnvPoint> m_lPoints;
 	char m_aName[32];
 	float m_Bottom, m_Top;
+	bool m_Synchronized;
 
 	CEnvelope(int Chan)
 	{
@@ -58,6 +59,7 @@ public:
 		m_aName[0] = 0;
 		m_Bottom = 0;
 		m_Top = 0;
+		m_Synchronized = true;
 	}
 
 	void Resort()
@@ -274,6 +276,34 @@ public:
 	array<CEditorImage*> m_lImages;
 	array<CEnvelope*> m_lEnvelopes;
 
+	class CMapInfo
+	{
+	public:
+		char m_aAuthorTmp[32];
+		char m_aVersionTmp[16];
+		char m_aCreditsTmp[128];
+		char m_aLicenseTmp[32];
+
+		char m_aAuthor[32];
+		char m_aVersion[16];
+		char m_aCredits[128];
+		char m_aLicense[32];
+
+		void Reset()
+		{
+			m_aAuthorTmp[0] = 0;
+			m_aVersionTmp[0] = 0;
+			m_aCreditsTmp[0] = 0;
+			m_aLicenseTmp[0] = 0;
+
+			m_aAuthor[0] = 0;
+			m_aVersion[0] = 0;
+			m_aCredits[0] = 0;
+			m_aLicense[0] = 0;
+		}
+	};
+	CMapInfo m_MapInfo;
+
 	class CLayerGame *m_pGameLayer;
 	CLayerGroup *m_pGameGroup;
 
@@ -476,6 +506,7 @@ public:
 
 		m_Mode = MODE_LAYERS;
 		m_Dialog = 0;
+		m_EditBoxActive = 0;
 		m_pTooltip = 0;
 
 		m_GridActive = false;
@@ -486,6 +517,7 @@ public:
 		m_ValidSaveFilename = false;
 
 		m_PopupEventActivated = false;
+		m_PopupEventWasActivated = false;
 
 		m_FileDialogStorageType = 0;
 		m_pFileDialogTitle = 0;
@@ -528,6 +560,10 @@ public:
 
 		m_ShowEnvelopeEditor = 0;
 
+		m_ShowEnvelopePreview = 0;
+		m_SelectedQuadEnvelope = -1;
+		m_SelectedEnvelopePoint = -1;
+
 		ms_CheckerTexture = 0;
 		ms_BackgroundTexture = 0;
 		ms_CursorTexture = 0;
@@ -560,6 +596,7 @@ public:
 
 	int m_Mode;
 	int m_Dialog;
+	int m_EditBoxActive;
 	const char *m_pTooltip;
 
 	bool m_GridActive;
@@ -579,6 +616,7 @@ public:
 
 	int m_PopupEventType;
 	int m_PopupEventActivated;
+	int m_PopupEventWasActivated;
 
 	enum
 	{
@@ -644,6 +682,7 @@ public:
 	float m_AnimateSpeed;
 
 	int m_ShowEnvelopeEditor;
+	int m_ShowEnvelopePreview; //Values: 0-Off|1-Selected Envelope|2-All
 	bool m_ShowPicker;
 
 	int m_SelectedLayer;
@@ -651,6 +690,8 @@ public:
 	int m_SelectedQuad;
 	int m_SelectedPoints;
 	int m_SelectedEnvelope;
+	int m_SelectedEnvelopePoint;
+    int m_SelectedQuadEnvelope;
 	int m_SelectedImage;
 
 	static int ms_CheckerTexture;
@@ -697,6 +738,7 @@ public:
 	static int PopupQuad(CEditor *pEditor, CUIRect View);
 	static int PopupPoint(CEditor *pEditor, CUIRect View);
 	static int PopupNewFolder(CEditor *pEditor, CUIRect View);
+	static int PopupMapInfo(CEditor *pEditor, CUIRect View);
 	static int PopupEvent(CEditor *pEditor, CUIRect View);
 	static int PopupSelectImage(CEditor *pEditor, CUIRect View);
 	static int PopupSelectGametileOp(CEditor *pEditor, CUIRect View);
@@ -713,13 +755,16 @@ public:
 
 	void PopupSelectGametileOpInvoke(float x, float y);
 	int PopupSelectGameTileOpResult();
-	
+
 	void PopupSelectConfigAutoMapInvoke(float x, float y);
 	int PopupSelectConfigAutoMapResult();
 
 	vec4 ButtonColorMul(const void *pID);
 
+	void DoQuadEnvelopes(CQuad *pQuad, int Index, int TexID = -1);
+	void DoQuadEnvPoint(CQuad *pQuad, int QIndex, int pIndex);
 	void DoQuadPoint(CQuad *pQuad, int QuadIndex, int v);
+
 	void DoMapEditor(CUIRect View, CUIRect Toolbar);
 	void DoToolbar(CUIRect Toolbar);
 	void DoQuad(CQuad *pQuad, int Index);
diff --git a/src/game/editor/io.cpp b/src/game/editor/io.cpp
index 68330f03..463147e1 100644
--- a/src/game/editor/io.cpp
+++ b/src/game/editor/io.cpp
@@ -220,6 +220,31 @@ int CEditorMap::Save(class IStorage *pStorage, const char *pFileName)
 		df.AddItem(MAPITEMTYPE_VERSION, 0, sizeof(Item), &Item);
 	}
 
+	// save map info
+	{
+		CMapItemInfo Item;
+		Item.m_Version = 1;
+
+		if(m_MapInfo.m_aAuthor[0])
+			Item.m_Author = df.AddData(str_length(m_MapInfo.m_aAuthor)+1, m_MapInfo.m_aAuthor);
+		else
+			Item.m_Author = -1;
+		if(m_MapInfo.m_aVersion[0])
+			Item.m_MapVersion = df.AddData(str_length(m_MapInfo.m_aVersion)+1, m_MapInfo.m_aVersion);
+		else
+			Item.m_MapVersion = -1;
+		if(m_MapInfo.m_aCredits[0])
+			Item.m_Credits = df.AddData(str_length(m_MapInfo.m_aCredits)+1, m_MapInfo.m_aCredits);
+		else
+			Item.m_Credits = -1;
+		if(m_MapInfo.m_aLicense[0])
+			Item.m_License = df.AddData(str_length(m_MapInfo.m_aLicense)+1, m_MapInfo.m_aLicense);
+		else
+			Item.m_License = -1;
+
+		df.AddItem(MAPITEMTYPE_INFO, 0, sizeof(Item), &Item);
+	}
+
 	// save images
 	for(int i = 0; i < m_lImages.size(); i++)
 	{
@@ -342,10 +367,11 @@ int CEditorMap::Save(class IStorage *pStorage, const char *pFileName)
 	for(int e = 0; e < m_lEnvelopes.size(); e++)
 	{
 		CMapItemEnvelope Item;
-		Item.m_Version = 1;
+		Item.m_Version = CMapItemEnvelope::CURRENT_VERSION;
 		Item.m_Channels = m_lEnvelopes[e]->m_Channels;
 		Item.m_StartPoint = PointCount;
 		Item.m_NumPoints = m_lEnvelopes[e]->m_lPoints.size();
+		Item.m_Synchronized = m_lEnvelopes[e]->m_Synchronized;
 		StrToInts(Item.m_aName, sizeof(Item.m_aName)/sizeof(int), m_lEnvelopes[e]->m_aName);
 
 		df.AddItem(MAPITEMTYPE_ENVELOPE, e, sizeof(Item), &Item);
@@ -413,6 +439,22 @@ int CEditorMap::Load(class IStorage *pStorage, const char *pFileName, int Storag
 	{
 		//editor.reset(false);
 
+		// load map info
+		{
+			CMapItemInfo *pItem = (CMapItemInfo *)DataFile.FindItem(MAPITEMTYPE_INFO, 0);
+			if(pItem && pItem->m_Version == 1)
+			{
+				if(pItem->m_Author > -1)
+					str_copy(m_MapInfo.m_aAuthor, (char *)DataFile.GetData(pItem->m_Author), sizeof(m_MapInfo.m_aAuthor));
+				if(pItem->m_MapVersion > -1)
+					str_copy(m_MapInfo.m_aVersion, (char *)DataFile.GetData(pItem->m_MapVersion), sizeof(m_MapInfo.m_aVersion));
+				if(pItem->m_Credits > -1)
+					str_copy(m_MapInfo.m_aCredits, (char *)DataFile.GetData(pItem->m_Credits), sizeof(m_MapInfo.m_aCredits));
+				if(pItem->m_License > -1)
+					str_copy(m_MapInfo.m_aLicense, (char *)DataFile.GetData(pItem->m_License), sizeof(m_MapInfo.m_aLicense));
+			}
+		}
+
 		// load images
 		{
 			int Start, Num;
@@ -601,6 +643,8 @@ int CEditorMap::Load(class IStorage *pStorage, const char *pFileName, int Storag
 				if(pItem->m_aName[0] != -1)	// compatibility with old maps
 					IntsToStr(pItem->m_aName, sizeof(pItem->m_aName)/sizeof(int), pEnv->m_aName);
 				m_lEnvelopes.add(pEnv);
+				if(pItem->m_Version >= 2)
+					pEnv->m_Synchronized = pItem->m_Synchronized;
 			}
 		}
 	}
diff --git a/src/game/editor/layer_tiles.cpp b/src/game/editor/layer_tiles.cpp
index 5662613c..9a21e5ce 100644
--- a/src/game/editor/layer_tiles.cpp
+++ b/src/game/editor/layer_tiles.cpp
@@ -159,6 +159,8 @@ void CLayerTiles::FillSelection(bool Empty, CLayer *pBrush, CUIRect Rect)
 	if(m_Readonly)
 		return;
 
+	Snap(&Rect);
+
 	int sx = ConvertX(Rect.x);
 	int sy = ConvertY(Rect.y);
 	int w = ConvertX(Rect.w);
@@ -166,9 +168,9 @@ void CLayerTiles::FillSelection(bool Empty, CLayer *pBrush, CUIRect Rect)
 
 	CLayerTiles *pLt = static_cast<CLayerTiles*>(pBrush);
 
-	for(int y = 0; y <= h; y++)
+	for(int y = 0; y < h; y++)
 	{
-		for(int x = 0; x <= w; x++)
+		for(int x = 0; x < w; x++)
 		{
 			int fx = x+sx;
 			int fy = y+sy;
@@ -363,7 +365,7 @@ void CLayerTiles::ShowInfo()
 int CLayerTiles::RenderProperties(CUIRect *pToolBox)
 {
 	CUIRect Button;
-	
+
 	bool InGameGroup = !find_linear(m_pEditor->m_Map.m_pGameGroup->m_lLayers.all(), this).empty();
 	if(m_pEditor->m_Map.m_pGameLayer != this)
 	{
@@ -373,7 +375,7 @@ int CLayerTiles::RenderProperties(CUIRect *pToolBox)
 			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)
 			{
@@ -473,7 +475,19 @@ int CLayerTiles::RenderProperties(CUIRect *pToolBox)
 		m_Color.a = NewVal&0xff;
 	}
 	if(Prop == PROP_COLOR_ENV)
-		m_ColorEnv = clamp(NewVal-1, -1, m_pEditor->m_Map.m_lEnvelopes.size()-1);
+	{
+		int Index = clamp(NewVal-1, -1, m_pEditor->m_Map.m_lEnvelopes.size()-1);
+		int Step = (Index-m_ColorEnv)%2;
+		if(Step != 0)
+		{
+			for(; Index >= -1 && Index < m_pEditor->m_Map.m_lEnvelopes.size(); Index += Step)
+				if(Index == -1 || m_pEditor->m_Map.m_lEnvelopes[Index]->m_Channels == 4)
+				{
+					m_ColorEnv = Index;
+					break;
+				}
+		}
+	}
 	if(Prop == PROP_COLOR_ENV_OFFSET)
 		m_ColorEnvOffset = NewVal;
 
diff --git a/src/game/editor/popups.cpp b/src/game/editor/popups.cpp
index f29ae7e2..2382823d 100644
--- a/src/game/editor/popups.cpp
+++ b/src/game/editor/popups.cpp
@@ -458,9 +458,35 @@ int CEditor::PopupQuad(CEditor *pEditor, CUIRect View)
 		for(int k = 0; k < 5; ++k)
 			pQuad->m_aPoints[k].y += Offset;
 	}
-	if(Prop == PROP_POS_ENV) pQuad->m_PosEnv = clamp(NewVal-1, -1, pEditor->m_Map.m_lEnvelopes.size()-1);
+	if(Prop == PROP_POS_ENV)
+	{
+		int Index = clamp(NewVal-1, -1, pEditor->m_Map.m_lEnvelopes.size()-1);
+		int Step = (Index-pQuad->m_PosEnv)%2;
+		if(Step != 0)
+		{
+			for(; Index >= -1 && Index < pEditor->m_Map.m_lEnvelopes.size(); Index += Step)
+				if(Index == -1 || pEditor->m_Map.m_lEnvelopes[Index]->m_Channels == 3)
+				{
+					pQuad->m_PosEnv = Index;
+					break;
+				}
+		}
+	}
 	if(Prop == PROP_POS_ENV_OFFSET) pQuad->m_PosEnvOffset = NewVal;
-	if(Prop == PROP_COLOR_ENV) pQuad->m_ColorEnv = clamp(NewVal-1, -1, pEditor->m_Map.m_lEnvelopes.size()-1);
+	if(Prop == PROP_COLOR_ENV)
+	{
+		int Index = clamp(NewVal-1, -1, pEditor->m_Map.m_lEnvelopes.size()-1);
+		int Step = (Index-pQuad->m_ColorEnv)%2;
+		if(Step != 0)
+		{
+			for(; Index >= -1 && Index < pEditor->m_Map.m_lEnvelopes.size(); Index += Step)
+				if(Index == -1 || pEditor->m_Map.m_lEnvelopes[Index]->m_Channels == 4)
+				{
+					pQuad->m_ColorEnv = Index;
+					break;
+				}
+		}
+	}
 	if(Prop == PROP_COLOR_ENV_OFFSET) pQuad->m_ColorEnvOffset = NewVal;
 
 	return 0;
@@ -609,6 +635,74 @@ int CEditor::PopupNewFolder(CEditor *pEditor, CUIRect View)
 	return 0;
 }
 
+int CEditor::PopupMapInfo(CEditor *pEditor, CUIRect View)
+{
+	CUIRect Label, ButtonBar, Button;
+
+	// title
+	View.HSplitTop(10.0f, 0, &View);
+	View.HSplitTop(30.0f, &Label, &View);
+	pEditor->UI()->DoLabel(&Label, "Map details", 20.0f, 0);
+
+	View.HSplitBottom(10.0f, &View, 0);
+	View.HSplitBottom(20.0f, &View, &ButtonBar);
+
+	View.VMargin(40.0f, &View);
+
+	// author box
+	View.HSplitTop(20.0f, &Label, &View);
+	pEditor->UI()->DoLabel(&Label, "Author:", 10.0f, -1);
+	Label.VSplitLeft(40.0f, 0, &Button);
+	Button.HSplitTop(12.0f, &Button, 0);
+	static float s_AuthorBox = 0;
+	pEditor->DoEditBox(&s_AuthorBox, &Button, pEditor->m_Map.m_MapInfo.m_aAuthorTmp, sizeof(pEditor->m_Map.m_MapInfo.m_aAuthorTmp), 10.0f, &s_AuthorBox);
+
+	// version box
+	View.HSplitTop(20.0f, &Label, &View);
+	pEditor->UI()->DoLabel(&Label, "Version:", 10.0f, -1);
+	Label.VSplitLeft(40.0f, 0, &Button);
+	Button.HSplitTop(12.0f, &Button, 0);
+	static float s_VersionBox = 0;
+	pEditor->DoEditBox(&s_VersionBox, &Button, pEditor->m_Map.m_MapInfo.m_aVersionTmp, sizeof(pEditor->m_Map.m_MapInfo.m_aVersionTmp), 10.0f, &s_VersionBox);
+
+	// credits box
+	View.HSplitTop(20.0f, &Label, &View);
+	pEditor->UI()->DoLabel(&Label, "Credits:", 10.0f, -1);
+	Label.VSplitLeft(40.0f, 0, &Button);
+	Button.HSplitTop(12.0f, &Button, 0);
+	static float s_CreditsBox = 0;
+	pEditor->DoEditBox(&s_CreditsBox, &Button, pEditor->m_Map.m_MapInfo.m_aCreditsTmp, sizeof(pEditor->m_Map.m_MapInfo.m_aCreditsTmp), 10.0f, &s_CreditsBox);
+
+	// license box
+	View.HSplitTop(20.0f, &Label, &View);
+	pEditor->UI()->DoLabel(&Label, "License:", 10.0f, -1);
+	Label.VSplitLeft(40.0f, 0, &Button);
+	Button.HSplitTop(12.0f, &Button, 0);
+	static float s_LicenseBox = 0;
+	pEditor->DoEditBox(&s_LicenseBox, &Button, pEditor->m_Map.m_MapInfo.m_aLicenseTmp, sizeof(pEditor->m_Map.m_MapInfo.m_aLicenseTmp), 10.0f, &s_LicenseBox);
+
+	// button bar
+	ButtonBar.VSplitLeft(30.0f, 0, &ButtonBar);
+	ButtonBar.VSplitLeft(110.0f, &Label, &ButtonBar);
+	static int s_CreateButton = 0;
+	if(pEditor->DoButton_Editor(&s_CreateButton, "Save", 0, &Label, 0, 0))
+	{
+		str_copy(pEditor->m_Map.m_MapInfo.m_aAuthor, pEditor->m_Map.m_MapInfo.m_aAuthorTmp, sizeof(pEditor->m_Map.m_MapInfo.m_aAuthor));
+		str_copy(pEditor->m_Map.m_MapInfo.m_aVersion, pEditor->m_Map.m_MapInfo.m_aVersionTmp, sizeof(pEditor->m_Map.m_MapInfo.m_aVersion));
+		str_copy(pEditor->m_Map.m_MapInfo.m_aCredits, pEditor->m_Map.m_MapInfo.m_aCreditsTmp, sizeof(pEditor->m_Map.m_MapInfo.m_aCredits));
+		str_copy(pEditor->m_Map.m_MapInfo.m_aLicense, pEditor->m_Map.m_MapInfo.m_aLicenseTmp, sizeof(pEditor->m_Map.m_MapInfo.m_aLicense));
+		return 1;
+	}
+
+	ButtonBar.VSplitRight(30.0f, &ButtonBar, 0);
+	ButtonBar.VSplitRight(110.0f, &ButtonBar, &Label);
+	static int s_AbortButton = 0;
+	if(pEditor->DoButton_Editor(&s_AbortButton, "Abort", 0, &Label, 0, 0))
+		return 1;
+
+	return 0;
+}
+
 int CEditor::PopupEvent(CEditor *pEditor, CUIRect View)
 {
 	CUIRect Label, ButtonBar;
@@ -658,13 +752,17 @@ int CEditor::PopupEvent(CEditor *pEditor, CUIRect View)
 		}
 		else if(pEditor->m_PopupEventType == POPEVENT_SAVE)
 			pEditor->CallbackSaveMap(pEditor->m_aFileSaveName, IStorage::TYPE_SAVE, pEditor);
+		pEditor->m_PopupEventWasActivated = false;
 		return 1;
 	}
 	ButtonBar.VSplitRight(30.0f, &ButtonBar, 0);
 	ButtonBar.VSplitRight(110.0f, &ButtonBar, &Label);
 	static int s_AbortButton = 0;
 	if(pEditor->DoButton_Editor(&s_AbortButton, "Abort", 0, &Label, 0, 0))
+	{
+		pEditor->m_PopupEventWasActivated = false;
 		return 1;
+	}
 
 	return 0;
 }
@@ -703,13 +801,23 @@ int CEditor::PopupSelectImage(CEditor *pEditor, CUIRect View)
 	}
 
 	if(ShowImage >= 0 && ShowImage < pEditor->m_Map.m_lImages.size())
+	{
+		if(ImageView.h < ImageView.w)
+			ImageView.w = ImageView.h;
+		else
+			ImageView.h = ImageView.w;
+		float Max = (float)(max(pEditor->m_Map.m_lImages[ShowImage]->m_Width, pEditor->m_Map.m_lImages[ShowImage]->m_Height));
+		ImageView.w *= pEditor->m_Map.m_lImages[ShowImage]->m_Width/Max;
+		ImageView.h *= pEditor->m_Map.m_lImages[ShowImage]->m_Height/Max;
 		pEditor->Graphics()->TextureSet(pEditor->m_Map.m_lImages[ShowImage]->m_TexID);
-	else
-		pEditor->Graphics()->TextureSet(-1);
-	pEditor->Graphics()->QuadsBegin();
-	IGraphics::CQuadItem QuadItem(ImageView.x, ImageView.y, ImageView.w, ImageView.h);
-	pEditor->Graphics()->QuadsDrawTL(&QuadItem, 1);
-	pEditor->Graphics()->QuadsEnd();
+		pEditor->Graphics()->BlendNormal();
+		pEditor->Graphics()->WrapClamp();
+		pEditor->Graphics()->QuadsBegin();
+		IGraphics::CQuadItem QuadItem(ImageView.x, ImageView.y, ImageView.w, ImageView.h);
+		pEditor->Graphics()->QuadsDrawTL(&QuadItem, 1);
+		pEditor->Graphics()->QuadsEnd();
+		pEditor->Graphics()->WrapNormal();
+	}
 
 	return 0;
 }
@@ -776,7 +884,7 @@ int CEditor::PopupSelectConfigAutoMap(CEditor *pEditor, CUIRect View)
 	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);
@@ -802,7 +910,7 @@ 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 af086df2..d43492ac 100644
--- a/src/game/gamecore.cpp
+++ b/src/game/gamecore.cpp
@@ -302,7 +302,7 @@ void CCharacterCore::Tick(bool UseInput)
 		}
 	}
 
-	if(m_pWorld && m_pWorld->m_Tuning.m_PlayerCollision)
+	if(m_pWorld)
 	{
 		for(int i = 0; i < MAX_CLIENTS; i++)
 		{
@@ -317,7 +317,7 @@ void CCharacterCore::Tick(bool UseInput)
 			// handle player <-> player collision
 			float Distance = distance(m_Pos, pCharCore->m_Pos);
 			vec2 Dir = normalize(m_Pos - pCharCore->m_Pos);
-			if(Distance < PhysSize*1.25f && Distance > 0.0f)
+			if(m_pWorld->m_Tuning.m_PlayerCollision && Distance < PhysSize*1.25f && Distance > 0.0f)
 			{
 				float a = (PhysSize*1.45f - Distance);
 				float Velocity = 0.5f;
@@ -332,7 +332,7 @@ void CCharacterCore::Tick(bool UseInput)
 			}
 
 			// handle hook influence
-			if(m_HookedPlayer == i)
+			if(m_HookedPlayer == i && m_pWorld->m_Tuning.m_PlayerHooking)
 			{
 				if(Distance > PhysSize*1.50f) // TODO: fix tweakable variable
 				{
diff --git a/src/game/layers.cpp b/src/game/layers.cpp
index 82c0a61b..6deb0829 100644
--- a/src/game/layers.cpp
+++ b/src/game/layers.cpp
@@ -29,7 +29,7 @@ void CLayers::Init(class IKernel *pKernel)
 			if(pLayer->m_Type == LAYERTYPE_TILES)
 			{
 				CMapItemLayerTilemap *pTilemap = reinterpret_cast<CMapItemLayerTilemap *>(pLayer);
-				if(pTilemap->m_Flags&1)
+				if(pTilemap->m_Flags&TILESLAYERFLAG_GAME)
 				{
 					m_pGameLayer = pTilemap;
 					m_pGameGroup = pGroup;
diff --git a/src/game/mapitems.h b/src/game/mapitems.h
index fb66e12d..e1dda4ee 100644
--- a/src/game/mapitems.h
+++ b/src/game/mapitems.h
@@ -7,7 +7,7 @@
 enum
 {
 	LAYERTYPE_INVALID=0,
-	LAYERTYPE_GAME, // not used
+	LAYERTYPE_GAME,
 	LAYERTYPE_TILES,
 	LAYERTYPE_QUADS,
 
@@ -90,6 +90,15 @@ public:
 	unsigned char m_Reserved;
 };
 
+struct CMapItemInfo
+{
+	int m_Version;
+	int m_Author;
+	int m_MapVersion;
+	int m_Credits;
+	int m_License;
+} ;
+
 struct CMapItemImage
 {
 	int m_Version;
@@ -178,7 +187,7 @@ struct CEnvPoint
 	bool operator<(const CEnvPoint &Other) { return m_Time < Other.m_Time; }
 } ;
 
-struct CMapItemEnvelope
+struct CMapItemEnvelope_v1
 {
 	int m_Version;
 	int m_Channels;
@@ -187,4 +196,10 @@ struct CMapItemEnvelope
 	int m_aName[8];
 } ;
 
+struct CMapItemEnvelope : public CMapItemEnvelope_v1
+{
+	enum { CURRENT_VERSION=2 };
+	int m_Synchronized;
+};
+
 #endif
diff --git a/src/game/server/entities/character.cpp b/src/game/server/entities/character.cpp
index aab489bf..f64a16e3 100644
--- a/src/game/server/entities/character.cpp
+++ b/src/game/server/entities/character.cpp
@@ -538,7 +538,7 @@ void CCharacter::OnPredictedInput(CNetObj_PlayerInput *pNewInput)
 	mem_copy(&m_Input, pNewInput, sizeof(m_Input));
 	m_NumInputs++;
 
-	// or are not allowed to aim in the center
+	// it is not allowed to aim in the center
 	if(m_Input.m_TargetX == 0 && m_Input.m_TargetY == 0)
 		m_Input.m_TargetY = -1;
 }
@@ -551,6 +551,10 @@ void CCharacter::OnDirectInput(CNetObj_PlayerInput *pNewInput)
 	mem_copy(&m_LatestPrevInput, &m_LatestInput, sizeof(m_LatestInput));
 	mem_copy(&m_LatestInput, pNewInput, sizeof(m_LatestInput));
 
+	// it is not allowed to aim in the center
+	if(m_LatestInput.m_TargetX == 0 && m_LatestInput.m_TargetY == 0)
+		m_LatestInput.m_TargetY = -1;
+
 	if(m_NumInputs > 2 && m_pPlayer->GetTeam() != TEAM_SPECTATORS)
 	{
 		HandleWeaponSwitch();
@@ -705,6 +709,20 @@ void CCharacter::TickDefered()
 	}
 }
 
+void CCharacter::TickPaused()
+{
+	++m_AttackTick;
+	++m_DamageTakenTick;
+	++m_Ninja.m_ActivationTick;
+	++m_ReckoningTick;
+	if(m_LastAction != -1)
+		++m_LastAction;
+	if(m_aWeapons[m_ActiveWeapon].m_AmmoRegenStart > -1)
+		++m_aWeapons[m_ActiveWeapon].m_AmmoRegenStart;
+	if(m_EmoteStop > -1)
+		++m_EmoteStop;
+}
+
 bool CCharacter::IncreaseHealth(int Amount)
 {
 	if(m_Health >= 10)
diff --git a/src/game/server/entities/character.h b/src/game/server/entities/character.h
index a3a7e9d2..dae01bd4 100644
--- a/src/game/server/entities/character.h
+++ b/src/game/server/entities/character.h
@@ -30,6 +30,7 @@ public:
 	virtual void Destroy();
 	virtual void Tick();
 	virtual void TickDefered();
+	virtual void TickPaused();
 	virtual void Snap(int SnappingClient);
 
 	bool IsGrounded();
diff --git a/src/game/server/entities/flag.cpp b/src/game/server/entities/flag.cpp
index d279e4df..558ee154 100644
--- a/src/game/server/entities/flag.cpp
+++ b/src/game/server/entities/flag.cpp
@@ -23,6 +23,13 @@ void CFlag::Reset()
 	m_GrabTick = 0;
 }
 
+void CFlag::TickPaused()
+{
+	++m_DropTick;
+	if(m_GrabTick)
+		++m_GrabTick;
+}
+
 void CFlag::Snap(int SnappingClient)
 {
 	if(NetworkClipped(SnappingClient))
diff --git a/src/game/server/entities/flag.h b/src/game/server/entities/flag.h
index 2f91bc02..be22d32b 100644
--- a/src/game/server/entities/flag.h
+++ b/src/game/server/entities/flag.h
@@ -21,6 +21,7 @@ public:
 	CFlag(CGameWorld *pGameWorld, int Team);
 
 	virtual void Reset();
+	virtual void TickPaused();
 	virtual void Snap(int SnappingClient);
 };
 
diff --git a/src/game/server/entities/laser.cpp b/src/game/server/entities/laser.cpp
index af66fe0c..eb40c4e1 100644
--- a/src/game/server/entities/laser.cpp
+++ b/src/game/server/entities/laser.cpp
@@ -21,15 +21,15 @@ CLaser::CLaser(CGameWorld *pGameWorld, vec2 Pos, vec2 Direction, float StartEner
 bool CLaser::HitCharacter(vec2 From, vec2 To)
 {
 	vec2 At;
-	CCharacter *OwnerChar = GameServer()->GetPlayerChar(m_Owner);
-	CCharacter *Hit = GameServer()->m_World.IntersectCharacter(m_Pos, To, 0.f, At, OwnerChar);
-	if(!Hit)
+	CCharacter *pOwnerChar = GameServer()->GetPlayerChar(m_Owner);
+	CCharacter *pHit = GameServer()->m_World.IntersectCharacter(m_Pos, To, 0.f, At, pOwnerChar);
+	if(!pHit)
 		return false;
 
 	m_From = From;
 	m_Pos = At;
 	m_Energy = -1;
-	Hit->TakeDamage(vec2(0.f, 0.f), GameServer()->Tuning()->m_LaserDamage, m_Owner, WEAPON_RIFLE);
+	pHit->TakeDamage(vec2(0.f, 0.f), GameServer()->Tuning()->m_LaserDamage, m_Owner, WEAPON_RIFLE);
 	return true;
 }
 
@@ -91,6 +91,11 @@ void CLaser::Tick()
 		DoBounce();
 }
 
+void CLaser::TickPaused()
+{
+	++m_EvalTick;
+}
+
 void CLaser::Snap(int SnappingClient)
 {
 	if(NetworkClipped(SnappingClient))
diff --git a/src/game/server/entities/laser.h b/src/game/server/entities/laser.h
index 1d7fa227..8ae6f792 100644
--- a/src/game/server/entities/laser.h
+++ b/src/game/server/entities/laser.h
@@ -12,6 +12,7 @@ public:
 
 	virtual void Reset();
 	virtual void Tick();
+	virtual void TickPaused();
 	virtual void Snap(int SnappingClient);
 
 protected:
diff --git a/src/game/server/entities/pickup.cpp b/src/game/server/entities/pickup.cpp
index ba26d85b..1aff5750 100644
--- a/src/game/server/entities/pickup.cpp
+++ b/src/game/server/entities/pickup.cpp
@@ -117,6 +117,12 @@ void CPickup::Tick()
 	}
 }
 
+void CPickup::TickPaused()
+{
+	if(m_SpawnTick != -1)
+		++m_SpawnTick;
+}
+
 void CPickup::Snap(int SnappingClient)
 {
 	if(m_SpawnTick != -1 || NetworkClipped(SnappingClient))
diff --git a/src/game/server/entities/pickup.h b/src/game/server/entities/pickup.h
index 77347de2..fe45b5ae 100644
--- a/src/game/server/entities/pickup.h
+++ b/src/game/server/entities/pickup.h
@@ -14,6 +14,7 @@ public:
 
 	virtual void Reset();
 	virtual void Tick();
+	virtual void TickPaused();
 	virtual void Snap(int SnappingClient);
 
 private:
diff --git a/src/game/server/entities/projectile.cpp b/src/game/server/entities/projectile.cpp
index 2baa24b1..e89e0e6f 100644
--- a/src/game/server/entities/projectile.cpp
+++ b/src/game/server/entities/projectile.cpp
@@ -82,6 +82,11 @@ void CProjectile::Tick()
 	}
 }
 
+void CProjectile::TickPaused()
+{
+	++m_StartTick;
+}
+
 void CProjectile::FillInfo(CNetObj_Projectile *pProj)
 {
 	pProj->m_X = (int)m_Pos.x;
diff --git a/src/game/server/entities/projectile.h b/src/game/server/entities/projectile.h
index 5df04bcd..7b5c5e0e 100644
--- a/src/game/server/entities/projectile.h
+++ b/src/game/server/entities/projectile.h
@@ -14,6 +14,7 @@ public:
 
 	virtual void Reset();
 	virtual void Tick();
+	virtual void TickPaused();
 	virtual void Snap(int SnappingClient);
 
 private:
diff --git a/src/game/server/entity.h b/src/game/server/entity.h
index b9b33eb7..0f838730 100644
--- a/src/game/server/entity.h
+++ b/src/game/server/entity.h
@@ -106,6 +106,12 @@ public:
 	virtual void TickDefered() {}
 
 	/*
+		Function: TickPaused
+			Called when the game is paused, to freeze the state and position of the entity.
+	*/
+	virtual void TickPaused() {}
+
+	/*
 		Function: snap
 			Called when a new snapshot is being generated for a specific
 			client.
diff --git a/src/game/server/gamecontext.cpp b/src/game/server/gamecontext.cpp
index 3f40bfcd..9e8afea8 100644
--- a/src/game/server/gamecontext.cpp
+++ b/src/game/server/gamecontext.cpp
@@ -34,6 +34,7 @@ void CGameContext::Construct(int Resetting)
 	m_pVoteOptionFirst = 0;
 	m_pVoteOptionLast = 0;
 	m_NumVoteOptions = 0;
+	m_LockTeams = 0;
 
 	if(Resetting==NO_RESET)
 		m_pVoteOptionHeap = new CHeap();
@@ -208,7 +209,15 @@ void CGameContext::CreateSoundGlobal(int Sound, int Target)
 
 	CNetMsg_Sv_SoundGlobal Msg;
 	Msg.m_SoundID = Sound;
-	Server()->SendPackMsg(&Msg, MSGFLAG_VITAL, Target);
+	if(Target == -2)
+		Server()->SendPackMsg(&Msg, MSGFLAG_NOSEND, -1);
+	else
+	{
+		int Flag = MSGFLAG_VITAL;
+		if(Target != -1)
+			Flag |= MSGFLAG_NORECORD;
+		Server()->SendPackMsg(&Msg, Flag, Target);
+	}
 }
 
 
@@ -383,6 +392,22 @@ void CGameContext::SendTuningParams(int ClientID)
 	Server()->SendMsg(&Msg, MSGFLAG_VITAL, ClientID);
 }
 
+void CGameContext::SwapTeams()
+{
+	if(!m_pController->IsTeamplay())
+		return;
+	
+	SendChat(-1, CGameContext::CHAT_ALL, "Teams were swapped");
+
+	for(int i = 0; i < MAX_CLIENTS; ++i)
+	{
+		if(m_apPlayers[i] && m_apPlayers[i]->GetTeam() != TEAM_SPECTATORS)
+			m_apPlayers[i]->SetTeam(m_apPlayers[i]->GetTeam()^1, false);
+	}
+
+	(void)m_pController->CheckTeamBalance();
+}
+
 void CGameContext::OnTick()
 {
 	// check tuning
@@ -464,7 +489,9 @@ void CGameContext::OnTick()
 
 			if(m_VoteEnforce == VOTE_ENFORCE_YES)
 			{
+				Server()->SetRconCID(IServer::RCON_CID_VOTE);
 				Console()->ExecuteLine(m_aVoteCommand);
+				Server()->SetRconCID(IServer::RCON_CID_SERV);
 				EndVote();
 				SendChat(-1, CGameContext::CHAT_ALL, "Vote passed");
 
@@ -868,7 +895,6 @@ void CGameContext::OnMessage(int MsgID, CUnpacker *pUnpacker, int ClientID)
 				char aAddrStr[NETADDR_MAXSTRSIZE] = {0};
 				Server()->GetClientAddr(KickID, aAddrStr, sizeof(aAddrStr));
 				str_format(aCmd, sizeof(aCmd), "ban %s %d Banned by vote", aAddrStr, g_Config.m_SvVoteKickBantime);
-				Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "server", aCmd);
 			}
 		}
 		else if(str_comp_nocase(pMsg->m_Type, "spectate") == 0)
@@ -929,6 +955,13 @@ 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(pMsg->m_Team != TEAM_SPECTATORS && m_LockTeams)
+		{
+			pPlayer->m_LastSetTeam = Server()->Tick();
+			SendBroadcast("Teams are locked", ClientID);
+			return;
+		}
+
 		if(pPlayer->m_TeamChangeTick > Server()->Tick())
 		{
 			pPlayer->m_LastSetTeam = Server()->Tick();
@@ -974,7 +1007,7 @@ void CGameContext::OnMessage(int MsgID, CUnpacker *pUnpacker, int ClientID)
 		else
 		{
 			char aBuf[128];
-			str_format(aBuf, sizeof(aBuf), "Only %d active players are allowed", g_Config.m_SvMaxClients-g_Config.m_SvSpectatorSlots);
+			str_format(aBuf, sizeof(aBuf), "Only %d active players are allowed", Server()->MaxClients()-g_Config.m_SvSpectatorSlots);
 			SendBroadcast(aBuf, ClientID);
 		}
 	}
@@ -1250,6 +1283,16 @@ void CGameContext::ConTuneDump(IConsole::IResult *pResult, void *pUserData)
 	}
 }
 
+void CGameContext::ConPause(IConsole::IResult *pResult, void *pUserData)
+{
+	CGameContext *pSelf = (CGameContext *)pUserData;
+
+	if(pSelf->m_pController->IsGameOver())
+		return;
+
+	pSelf->m_World.m_Paused ^= 1;
+}
+
 void CGameContext::ConChangeMap(IConsole::IResult *pResult, void *pUserData)
 {
 	CGameContext *pSelf = (CGameContext *)pUserData;
@@ -1282,17 +1325,14 @@ 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);
+	int Delay = pResult->NumArguments()>2 ? pResult->GetInteger(2) : 0;
+	if(!pSelf->m_apPlayers[ClientID])
+		return;
 
 	char aBuf[256];
 	str_format(aBuf, sizeof(aBuf), "moved client %d to team %d", ClientID, Team);
 	pSelf->Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "server", aBuf);
 
-	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();
@@ -1304,16 +1344,75 @@ void CGameContext::ConSetTeamAll(IConsole::IResult *pResult, void *pUserData)
 	int Team = clamp(pResult->GetInteger(0), -1, 1);
 
 	char aBuf[256];
-	str_format(aBuf, sizeof(aBuf), "moved all clients to team %d", Team);
-	pSelf->Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "server", aBuf);
+	str_format(aBuf, sizeof(aBuf), "All players were moved to the %s", pSelf->m_pController->GetTeamName(Team));
+	pSelf->SendChat(-1, CGameContext::CHAT_ALL, aBuf);
 
 	for(int i = 0; i < MAX_CLIENTS; ++i)
 		if(pSelf->m_apPlayers[i])
-			pSelf->m_apPlayers[i]->SetTeam(Team);
+			pSelf->m_apPlayers[i]->SetTeam(Team, false);
+
+	(void)pSelf->m_pController->CheckTeamBalance();
+}
+
+void CGameContext::ConSwapTeams(IConsole::IResult *pResult, void *pUserData)
+{
+	CGameContext *pSelf = (CGameContext *)pUserData;
+	pSelf->SwapTeams();
+}
+
+void CGameContext::ConShuffleTeams(IConsole::IResult *pResult, void *pUserData)
+{
+	CGameContext *pSelf = (CGameContext *)pUserData;
+	if(!pSelf->m_pController->IsTeamplay())
+		return;
+
+	int CounterRed = 0;
+	int CounterBlue = 0;
+	int PlayerTeam = 0;
+	for(int i = 0; i < MAX_CLIENTS; ++i)
+		if(pSelf->m_apPlayers[i] && pSelf->m_apPlayers[i]->GetTeam() != TEAM_SPECTATORS)
+			++PlayerTeam;
+	PlayerTeam = (PlayerTeam+1)/2;
+	
+	pSelf->SendChat(-1, CGameContext::CHAT_ALL, "Teams were shuffled");
+
+	for(int i = 0; i < MAX_CLIENTS; ++i)
+	{
+		if(pSelf->m_apPlayers[i] && pSelf->m_apPlayers[i]->GetTeam() != TEAM_SPECTATORS)
+		{
+			if(CounterRed == PlayerTeam)
+				pSelf->m_apPlayers[i]->SetTeam(TEAM_BLUE, false);
+			else if(CounterBlue == PlayerTeam)
+				pSelf->m_apPlayers[i]->SetTeam(TEAM_RED, false);
+			else
+			{	
+				if(rand() % 2)
+				{
+					pSelf->m_apPlayers[i]->SetTeam(TEAM_BLUE, false);
+					++CounterBlue;
+				}
+				else
+				{
+					pSelf->m_apPlayers[i]->SetTeam(TEAM_RED, false);
+					++CounterRed;
+				}
+			}
+		}
+	}
 
 	(void)pSelf->m_pController->CheckTeamBalance();
 }
 
+void CGameContext::ConLockTeams(IConsole::IResult *pResult, void *pUserData)
+{
+	CGameContext *pSelf = (CGameContext *)pUserData;
+	pSelf->m_LockTeams ^= 1;
+	if(pSelf->m_LockTeams)
+		pSelf->SendChat(-1, CGameContext::CHAT_ALL, "Teams were locked");
+	else
+		pSelf->SendChat(-1, CGameContext::CHAT_ALL, "Teams were unlocked");
+}
+
 void CGameContext::ConAddVote(IConsole::IResult *pResult, void *pUserData)
 {
 	CGameContext *pSelf = (CGameContext *)pUserData;
@@ -1533,6 +1632,11 @@ void CGameContext::ConClearVotes(IConsole::IResult *pResult, void *pUserData)
 void CGameContext::ConVote(IConsole::IResult *pResult, void *pUserData)
 {
 	CGameContext *pSelf = (CGameContext *)pUserData;
+
+	// check if there is a vote running
+	if(!pSelf->m_VoteCloseTime)
+		return;
+
 	if(str_comp_nocase(pResult->GetString(0), "yes") == 0)
 		pSelf->m_VoteEnforce = CGameContext::VOTE_ENFORCE_YES;
 	else if(str_comp_nocase(pResult->GetString(0), "no") == 0)
@@ -1625,12 +1729,16 @@ void CGameContext::OnConsoleInit()
 	Console()->Register("tune_reset", "", CFGFLAG_SERVER, ConTuneReset, this, "Reset tuning");
 	Console()->Register("tune_dump", "", CFGFLAG_SERVER, ConTuneDump, this, "Dump tuning");
 
+	Console()->Register("pause", "", CFGFLAG_SERVER, ConPause, this, "Pause/unpause game");
 	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("restart", "?i", CFGFLAG_SERVER|CFGFLAG_STORE, ConRestart, this, "Restart in x seconds (0 = abort)");
 	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("swap_teams", "", CFGFLAG_SERVER, ConSwapTeams, this, "Swap the current teams");
+	Console()->Register("shuffle_teams", "", CFGFLAG_SERVER, ConShuffleTeams, this, "Shuffle the current teams");
+	Console()->Register("lock_teams", "", CFGFLAG_SERVER, ConLockTeams, this, "Lock/unlock teams");
 
 	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");
@@ -1731,6 +1839,17 @@ void CGameContext::OnShutdown()
 
 void CGameContext::OnSnap(int ClientID)
 {
+	// add tuning to demo
+	CTuningParams StandardTuning;
+	if(ClientID == -1 && Server()->DemoRecorder_IsRecording() && mem_comp(&StandardTuning, &m_Tuning, sizeof(CTuningParams)) != 0)
+	{
+		CMsgPacker Msg(NETMSGTYPE_SV_TUNEPARAMS);
+		int *pParams = (int *)&m_Tuning;
+		for(unsigned i = 0; i < sizeof(m_Tuning)/sizeof(int); i++)
+			Msg.AddInt(pParams[i]);
+		Server()->SendMsg(&Msg, MSGFLAG_RECORD|MSGFLAG_NOSEND, ClientID);
+	}
+
 	m_World.Snap(ClientID);
 	m_pController->Snap(ClientID);
 	m_Events.Snap(ClientID);
diff --git a/src/game/server/gamecontext.h b/src/game/server/gamecontext.h
index 4e09cac2..fa85151c 100644
--- a/src/game/server/gamecontext.h
+++ b/src/game/server/gamecontext.h
@@ -51,12 +51,16 @@ class CGameContext : public IGameServer
 	static void ConTuneParam(IConsole::IResult *pResult, void *pUserData);
 	static void ConTuneReset(IConsole::IResult *pResult, void *pUserData);
 	static void ConTuneDump(IConsole::IResult *pResult, void *pUserData);
+	static void ConPause(IConsole::IResult *pResult, void *pUserData);
 	static void ConChangeMap(IConsole::IResult *pResult, void *pUserData);
 	static void ConRestart(IConsole::IResult *pResult, void *pUserData);
 	static void ConBroadcast(IConsole::IResult *pResult, void *pUserData);
 	static void ConSay(IConsole::IResult *pResult, void *pUserData);
 	static void ConSetTeam(IConsole::IResult *pResult, void *pUserData);
 	static void ConSetTeamAll(IConsole::IResult *pResult, void *pUserData);
+	static void ConSwapTeams(IConsole::IResult *pResult, void *pUserData);
+	static void ConShuffleTeams(IConsole::IResult *pResult, void *pUserData);
+	static void ConLockTeams(IConsole::IResult *pResult, void *pUserData);
 	static void ConAddVote(IConsole::IResult *pResult, void *pUserData);
 	static void ConRemoveVote(IConsole::IResult *pResult, void *pUserData);
 	static void ConForceVote(IConsole::IResult *pResult, void *pUserData);
@@ -93,6 +97,8 @@ public:
 	// helper functions
 	class CCharacter *GetPlayerChar(int ClientID);
 
+	int m_LockTeams;
+
 	// voting
 	void StartVote(const char *pDesc, const char *pCommand, const char *pReason);
 	void EndVote();
@@ -166,6 +172,9 @@ public:
 	void CheckPureTuning();
 	void SendTuningParams(int ClientID);
 
+	//
+	void SwapTeams();
+
 	// engine events
 	virtual void OnInit();
 	virtual void OnConsoleInit();
diff --git a/src/game/server/gamecontroller.cpp b/src/game/server/gamecontroller.cpp
index 5f330110..58c4d9f0 100644
--- a/src/game/server/gamecontroller.cpp
+++ b/src/game/server/gamecontroller.cpp
@@ -255,7 +255,11 @@ void IGameController::CycleMap()
 		return;
 
 	if(m_RoundCount < g_Config.m_SvRoundsPerMap-1)
+	{
+		if(g_Config.m_SvRoundSwap)
+			GameServer()->SwapTeams();
 		return;
+	}
 
 	// handle maprotation
 	const char *pMapRotation = g_Config.m_SvMaprotation;
@@ -436,8 +440,12 @@ void IGameController::Tick()
 		}
 	}
 
+	// game is Paused
+	if(GameServer()->m_World.m_Paused)
+		++m_RoundStartTick;
+
 	// do team-balancing
-	if (IsTeamplay() && m_UnbalancedTick != -1 && Server()->Tick() > m_UnbalancedTick+g_Config.m_SvTeambalanceTime*Server()->TickSpeed()*60)
+	if(IsTeamplay() && m_UnbalancedTick != -1 && Server()->Tick() > m_UnbalancedTick+g_Config.m_SvTeambalanceTime*Server()->TickSpeed()*60)
 	{
 		GameServer()->Console()->Print(IConsole::OUTPUT_LEVEL_DEBUG, "game", "Balancing teams");
 
@@ -496,6 +504,13 @@ void IGameController::Tick()
 	{
 		for(int i = 0; i < MAX_CLIENTS; ++i)
 		{
+		#ifdef CONF_DEBUG
+			if(g_Config.m_DbgDummies)
+			{
+				if(i >= MAX_CLIENTS-g_Config.m_DbgDummies)
+					break;
+			}
+		#endif
 			if(GameServer()->m_apPlayers[i] && GameServer()->m_apPlayers[i]->GetTeam() != TEAM_SPECTATORS && !Server()->IsAuthed(i))
 			{
 				if(Server()->Tick() > GameServer()->m_apPlayers[i]->m_LastActionTick+g_Config.m_SvInactiveKickTime*Server()->TickSpeed()*60)
@@ -605,7 +620,7 @@ bool IGameController::CanJoinTeam(int Team, int NotThisID)
 		}
 	}
 
-	return (aNumplayers[0] + aNumplayers[1]) < g_Config.m_SvMaxClients-g_Config.m_SvSpectatorSlots;
+	return (aNumplayers[0] + aNumplayers[1]) < Server()->MaxClients()-g_Config.m_SvSpectatorSlots;
 }
 
 bool IGameController::CheckTeamBalance()
diff --git a/src/game/server/gamecontroller.h b/src/game/server/gamecontroller.h
index 914f7985..1be24509 100644
--- a/src/game/server/gamecontroller.h
+++ b/src/game/server/gamecontroller.h
@@ -64,6 +64,7 @@ public:
 	const char *m_pGameType;
 
 	bool IsTeamplay() const;
+	bool IsGameOver() const { return m_GameOverTick != -1; }
 
 	IGameController(class CGameContext *pGameServer);
 	virtual ~IGameController();
diff --git a/src/game/server/gamemodes/ctf.cpp b/src/game/server/gamemodes/ctf.cpp
index 66cc4c2c..9e45c1fe 100644
--- a/src/game/server/gamemodes/ctf.cpp
+++ b/src/game/server/gamemodes/ctf.cpp
@@ -259,6 +259,8 @@ void CGameControllerCTF::Tick()
 						else
 							GameServer()->CreateSoundGlobal(SOUND_CTF_GRAB_PL, c);
 					}
+					// demo record entry
+					GameServer()->CreateSoundGlobal(SOUND_CTF_GRAB_EN, -2);
 					break;
 				}
 			}
diff --git a/src/game/server/gameworld.cpp b/src/game/server/gameworld.cpp
index 89f4808d..d803ae67 100644
--- a/src/game/server/gameworld.cpp
+++ b/src/game/server/gameworld.cpp
@@ -173,6 +173,17 @@ void CGameWorld::Tick()
 				pEnt = m_pNextTraverseEntity;
 			}
 	}
+	else
+	{
+		// update all objects
+		for(int i = 0; i < NUM_ENTTYPES; i++)
+			for(CEntity *pEnt = m_apFirstEntityTypes[i]; pEnt; )
+			{
+				m_pNextTraverseEntity = pEnt->m_pNextTypeEntity;
+				pEnt->TickPaused();
+				pEnt = m_pNextTraverseEntity;
+			}
+	}
 
 	RemoveEntities();
 }
diff --git a/src/game/server/player.cpp b/src/game/server/player.cpp
index 39590c4e..09518a78 100644
--- a/src/game/server/player.cpp
+++ b/src/game/server/player.cpp
@@ -85,26 +85,37 @@ void CPlayer::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(!GameServer()->m_World.m_Paused)
+	{
+		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(!m_pCharacter && m_DieTick+Server()->TickSpeed()*3 <= Server()->Tick())
+			m_Spawning = true;
 
-	if(m_pCharacter)
-	{
-		if(m_pCharacter->IsAlive())
-		{
-			m_ViewPos = m_pCharacter->m_Pos;
-		}
-		else
+		if(m_pCharacter)
 		{
-			delete m_pCharacter;
-			m_pCharacter = 0;
+			if(m_pCharacter->IsAlive())
+			{
+				m_ViewPos = m_pCharacter->m_Pos;
+			}
+			else
+			{
+				delete m_pCharacter;
+				m_pCharacter = 0;
+			}
 		}
+		else if(m_Spawning && m_RespawnTick <= Server()->Tick())
+			TryRespawn();
 	}
-	else if(m_Spawning && m_RespawnTick <= Server()->Tick())
-		TryRespawn();
+	else
+	{
+		++m_RespawnTick;
+		++m_DieTick;
+		++m_ScoreStartTick;
+		++m_LastActionTick;
+		++m_TeamChangeTick;
+ 	}
 }
 
 void CPlayer::PostTick()
@@ -261,7 +272,7 @@ void CPlayer::Respawn()
 		m_Spawning = true;
 }
 
-void CPlayer::SetTeam(int Team)
+void CPlayer::SetTeam(int Team, bool DoChatMsg)
 {
 	// clamp the team
 	Team = GameServer()->m_pController->ClampTeam(Team);
@@ -269,8 +280,11 @@ void CPlayer::SetTeam(int Team)
 		return;
 
 	char aBuf[512];
-	str_format(aBuf, sizeof(aBuf), "'%s' joined the %s", Server()->ClientName(m_ClientID), GameServer()->m_pController->GetTeamName(Team));
-	GameServer()->SendChat(-1, CGameContext::CHAT_ALL, aBuf);
+	if(DoChatMsg)
+	{
+		str_format(aBuf, sizeof(aBuf), "'%s' joined the %s", Server()->ClientName(m_ClientID), GameServer()->m_pController->GetTeamName(Team));
+		GameServer()->SendChat(-1, CGameContext::CHAT_ALL, aBuf);
+	}
 
 	KillCharacter();
 
diff --git a/src/game/server/player.h b/src/game/server/player.h
index 99de2952..92e2ccc8 100644
--- a/src/game/server/player.h
+++ b/src/game/server/player.h
@@ -20,7 +20,7 @@ public:
 
 	void TryRespawn();
 	void Respawn();
-	void SetTeam(int Team);
+	void SetTeam(int Team, bool DoChatMsg=true);
 	void SetTeamDirect(int Team); //zCatch
 	int GetTeam() const { return m_Team; };
 	int GetCID() const { return m_ClientID; };
diff --git a/src/game/variables.h b/src/game/variables.h
index 79df19fb..99377a64 100644
--- a/src/game/variables.h
+++ b/src/game/variables.h
@@ -14,6 +14,7 @@ MACRO_CONFIG_INT(ClNameplatesSize, cl_nameplates_size, 50, 0, 100, CFGFLAG_CLIEN
 MACRO_CONFIG_INT(ClAutoswitchWeapons, cl_autoswitch_weapons, 0, 0, 1, CFGFLAG_CLIENT|CFGFLAG_SAVE, "Auto switch weapon on pickup")
 
 MACRO_CONFIG_INT(ClShowhud, cl_showhud, 1, 0, 1, CFGFLAG_CLIENT|CFGFLAG_SAVE, "Show ingame HUD")
+MACRO_CONFIG_INT(ClShowChatFriends, cl_show_chat_friends, 0, 0, 1, CFGFLAG_CLIENT|CFGFLAG_SAVE, "Show only chat messages from friends")
 MACRO_CONFIG_INT(ClShowfps, cl_showfps, 0, 0, 1, CFGFLAG_CLIENT|CFGFLAG_SAVE, "Show ingame FPS counter")
 
 MACRO_CONFIG_INT(ClAirjumpindicator, cl_airjumpindicator, 1, 0, 1, CFGFLAG_CLIENT|CFGFLAG_SAVE, "")
@@ -41,7 +42,7 @@ MACRO_CONFIG_INT(PlayerColorBody, player_color_body, 65408, 0, 0xFFFFFF, CFGFLAG
 MACRO_CONFIG_INT(PlayerColorFeet, player_color_feet, 65408, 0, 0xFFFFFF, CFGFLAG_CLIENT|CFGFLAG_SAVE, "Player feet color")
 MACRO_CONFIG_STR(PlayerSkin, player_skin, 24, "default", CFGFLAG_CLIENT|CFGFLAG_SAVE, "Player skin")
 
-MACRO_CONFIG_INT(UiPage, ui_page, 5, 0, 10, CFGFLAG_CLIENT|CFGFLAG_SAVE, "Interface page")
+MACRO_CONFIG_INT(UiPage, ui_page, 6, 0, 10, CFGFLAG_CLIENT|CFGFLAG_SAVE, "Interface page")
 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")
@@ -60,6 +61,7 @@ MACRO_CONFIG_STR(SvMotd, sv_motd, 900, "", CFGFLAG_SERVER, "Message of the day t
 MACRO_CONFIG_INT(SvTeamdamage, sv_teamdamage, 0, 0, 1, CFGFLAG_SERVER, "Team damage")
 MACRO_CONFIG_STR(SvMaprotation, sv_maprotation, 768, "", CFGFLAG_SERVER, "Maps to rotate between")
 MACRO_CONFIG_INT(SvRoundsPerMap, sv_rounds_per_map, 1, 1, 100, CFGFLAG_SERVER, "Number of rounds on each map before rotating")
+MACRO_CONFIG_INT(SvRoundSwap, sv_round_swap, 1, 0, 1, CFGFLAG_SERVER, "Swap teams between rounds")
 MACRO_CONFIG_INT(SvPowerups, sv_powerups, 1, 0, 1, CFGFLAG_SERVER, "Allow powerups like ninja")
 MACRO_CONFIG_INT(SvScorelimit, sv_scorelimit, 20, 0, 1000, CFGFLAG_SERVER, "Score limit (0 disables)")
 MACRO_CONFIG_INT(SvTimelimit, sv_timelimit, 0, 0, 1000, CFGFLAG_SERVER, "Time limit in minutes (0 disables)")
diff --git a/src/game/version.h b/src/game/version.h
index 76b6d4ab..3d909e36 100644
--- a/src/game/version.h
+++ b/src/game/version.h
@@ -3,6 +3,7 @@
 #ifndef GAME_VERSION_H
 #define GAME_VERSION_H
 #include "generated/nethash.cpp"
-#define GAME_VERSION "0.6.1"
+#define GAME_VERSION "0.6 trunk"
 #define GAME_NETVERSION "0.6 " GAME_NETVERSION_HASH
+static const char GAME_RELEASE_VERSION[8] = {'0', '.', '6', '1', 0};
 #endif