Index: binaries/data/mods/public/gui/common/functions_utility.js
===================================================================
--- binaries/data/mods/public/gui/common/functions_utility.js	(revision 16242)
+++ binaries/data/mods/public/gui/common/functions_utility.js	(working copy)
@@ -240,3 +240,49 @@
 	// Limit the length to 20 characters
 	return sanitizedName.substr(0,20);
 }
+
+function tryAutoComplete(text, autoCompleteList)
+{
+	if (text.length == 0)
+		return text;
+
+	var wordSplit = text.split(/\s+/g);
+
+	if (!wordSplit.length)
+		return text;
+
+	var lastWord = wordSplit.pop();
+	for (var wordAutoCompletion of autoCompleteList)
+	{
+		if (wordAutoCompletion.toLowerCase().indexOf(lastWord.toLowerCase()) == 0)
+		{
+			text = wordSplit.join(" ")
+			if (text.length > 0)
+				text += " ";
+
+			text += wordAutoCompletion;
+			break;
+		}
+	}
+	return text;
+}
+
+//listPlayer parameter must be an array and every object with name property
+function autoCompleteNick(guiName, listPlayer)
+{
+	var input = Engine.GetGUIObjectByName(guiName);
+	var text = escapeText(input.caption);
+	if (!text.length)
+		return;
+
+	var autoCompleteList = [];
+	for (var player of listPlayer)
+		autoCompleteList.push(player.name);
+
+	var bufferPosition = input.buffer_position;
+	var textTillBufferPosition = text.substring(0, bufferPosition);
+	warn(textTillBufferPosition);
+	var newText = tryAutoComplete(textTillBufferPosition, autoCompleteList);
+	input.caption = newText + text.substring(bufferPosition);
+	input.buffer_position = bufferPosition + (newText.length - textTillBufferPosition.length);
+}
\ No newline at end of file
Index: binaries/data/mods/public/gui/lobby/lobby.js
===================================================================
--- binaries/data/mods/public/gui/lobby/lobby.js	(revision 16242)
+++ binaries/data/mods/public/gui/lobby/lobby.js	(working copy)
@@ -752,35 +752,6 @@
 	}
 }
 
-function completeNick()
-{
-	var input = Engine.GetGUIObjectByName("chatInput");
-	var text = escapeText(input.caption);
-	if (text.length)
-	{
-		var matched = false;
-		for each (var playerObj in Engine.GetPlayerList())
-		{
-			var player = playerObj.name;
-			var breaks = text.match(/(\s+)/g) || [];
-			text.split(/\s+/g).reduceRight(function (wordsSoFar, word, index)
-			{
-				if (matched)
-					return null;
-				var matchCandidate = word + (breaks[index - 1] || "") + wordsSoFar;
-				if (player.toUpperCase().indexOf(matchCandidate.toUpperCase().trim()) == 0)
-				{
-					input.caption = text.replace(matchCandidate.trim(), player);
-					matched = true;
-				}
-				return matchCandidate;
-			}, "");
-		if (matched)
-			break;
-		}
-	}
-}
-
 function isValidNick(nick)
 {
 	var prohibitedNicks = ["system"];
Index: binaries/data/mods/public/gui/lobby/lobby.xml
===================================================================
--- binaries/data/mods/public/gui/lobby/lobby.xml	(revision 16242)
+++ binaries/data/mods/public/gui/lobby/lobby.xml	(working copy)
@@ -231,7 +231,7 @@
 				<object name="chatText" size="0 0 100% 94%" type="text" style="ChatPanel" font="sans-13"/>
 				<object name="chatInput" size="0 94% 100% 100%" type="input" style="ModernInput" font="sans-13">
 					<action on="Press">submitChatInput();</action>
-					<action on="Tab">completeNick();</action>
+					<action on="Tab">autoCompleteNick("chatInput", Engine.GetPlayerList());</action>
 				</object>
 			</object>
 		</object>
Index: binaries/data/mods/public/gui/session/messages.js
===================================================================
--- binaries/data/mods/public/gui/session/messages.js	(revision 16242)
+++ binaries/data/mods/public/gui/session/messages.js	(working copy)
@@ -613,6 +614,16 @@
 			msg.hide = true;
 		recurse = true;
 		break;
+	case "/allies":
+		var player = g_Players[Engine.GetPlayerID()];
+
+		if (player && player.isAlly[sender])
+			msg.context = translate("Ally");
+		else
+			msg.hide = true;
+
+		recurse = true;
+		break;
 	case "/enemy":
 		// Check if we are in a team.
 		if (g_Players[Engine.GetPlayerID()] && g_Players[Engine.GetPlayerID()].team != -1)
Index: source/gui/CInput.cpp
===================================================================
--- source/gui/CInput.cpp	(revision 16242)
+++ source/gui/CInput.cpp	(working copy)
@@ -1,4 +1,4 @@
-/* Copyright (C) 2014 Wildfire Games.
+/* Copyright (C) 2015 Wildfire Games.
  * This file is part of 0 A.D.
  *
  * 0 A.D. is free software: you can redistribute it and/or modify
@@ -42,6 +42,8 @@
 
 extern int g_yres;
 
+#define UpdateBufferPositionSetting int* bufferPos = (int*)m_Settings["buffer_position"].m_pSetting; *bufferPos = m_iBufferPos;
+
 //-------------------------------------------------------------------
 //  Constructor / Destructor
 //-------------------------------------------------------------------
@@ -52,6 +54,7 @@
 {
 	AddSetting(GUIST_float,					"buffer_zone");
 	AddSetting(GUIST_CStrW,					"caption");
+	AddSetting(GUIST_int,					"buffer_position");
 	AddSetting(GUIST_int,					"cell_id");
 	AddSetting(GUIST_CStrW,					"font");
 	AddSetting(GUIST_CStrW,					"mask_char");
@@ -84,6 +87,7 @@
 	CStrW *pCaption = (CStrW*)m_Settings["caption"].m_pSetting;
 	pCaption->erase(m_iInsertPos, m_iComposedLength);
 	m_iBufferPos = m_iInsertPos;
+	UpdateBufferPositionSetting;
 	m_iComposedLength = 0;
 	m_iComposedPos = 0;
 }
@@ -124,10 +128,11 @@
 			pCaption->insert(m_iBufferPos, text);
 
 		UpdateText(m_iBufferPos, m_iBufferPos, m_iBufferPos+1);
-		
+
 		m_iBufferPos += text.length();
+		UpdateBufferPositionSetting;
 		m_iBufferPos_Tail = -1;
-		
+
 		UpdateAutoScroll();
 
 		return IN_HANDLED;
@@ -140,7 +145,7 @@
 		const char *rawText = ev->ev.edit.text;
 		int rawLength = strlen(rawText);
 		std::wstring wtext = wstring_from_utf8(rawText);
-		
+
 		debug_printf(L"SDL_TEXTEDITING: text=%hs, start=%d, length=%d\n", rawText, ev->ev.edit.start, ev->ev.edit.length);
 		m_WantedX=0.0f;
 
@@ -166,11 +171,12 @@
 			m_iComposedLength = wtext.length();
 			m_iComposedPos = ev->ev.edit.start < m_iComposedLength ? ev->ev.edit.start : m_iComposedLength;
 			m_iBufferPos = m_iInsertPos + m_iComposedPos;
-		
+
 			// TODO: composed text selection - what does ev.edit.length do?
 			m_iBufferPos_Tail = -1;
 		}
-		
+
+		UpdateBufferPositionSetting;
 		UpdateText(m_iBufferPos, m_iBufferPos, m_iBufferPos+1);
 
 		UpdateAutoScroll();
@@ -228,7 +234,7 @@
 										pCaption->Right( (long) pCaption->length()-m_iBufferPos );
 
 						--m_iBufferPos;
-					
+
 						UpdateText(m_iBufferPos, m_iBufferPos+1, m_iBufferPos);
 					}
 				}
@@ -372,7 +378,7 @@
 						m_iBufferPos = m_iBufferPos_Tail;
 
 					m_iBufferPos_Tail = -1;
-				}			
+				}
 
 				UpdateAutoScroll();
 				break;
@@ -414,7 +420,7 @@
 					if (m_iBufferPos >= current->m_ListStart &&
 						m_iBufferPos <= current->m_ListStart+(int)current->m_ListOfX.size())
 						break;
-					
+
 					++current;
 				}
 
@@ -437,7 +443,7 @@
 					m_iBufferPos = current->m_ListStart + GetXTextPosition(current, pos_x, m_WantedX);
 				}
 				// else we can't move up
-				
+
 				UpdateAutoScroll();
 			}
 				break;
@@ -459,7 +465,7 @@
 					if (m_iBufferPos >= current->m_ListStart &&
 						m_iBufferPos <= current->m_ListStart+(int)current->m_ListOfX.size())
 						break;
-					
+
 					++current;
 				}
 
@@ -551,6 +557,7 @@
 				break;
 		}
 
+		UpdateBufferPositionSetting;
 		return IN_HANDLED;
 	}
 
@@ -585,6 +592,7 @@
 			UpdateText(m_iBufferPos, m_iBufferPos, m_iBufferPos+1);
 
 			m_iBufferPos += (int)wcslen(text);
+			UpdateBufferPositionSetting;
 
 			sys_clipboard_free(text);
 		}
@@ -644,7 +652,7 @@
 
 				m_iBufferPos--;
 			}
-				
+
 			// If we end up on a puctuation char we just delete it (treat punct like a word)
 			if (iswpunct(searchString[m_iBufferPos - 1]))
 				m_iBufferPos--;
@@ -660,6 +668,7 @@
 				}
 			}
 
+			UpdateBufferPositionSetting;
 			DeleteCurSelection();
 		}
 		return IN_HANDLED;
@@ -692,14 +701,15 @@
 
 				m_iBufferPos++;
 			}
+			UpdateBufferPositionSetting;
 			DeleteCurSelection();
 		}
-		return IN_HANDLED;			
+		return IN_HANDLED;
 	}
 	else if (hotkey == "text.move.left")
 	{
 		m_WantedX=0.0f;
-				
+
 		if (shiftKeyPressed || !SelectingText())
 		{
 			if (!shiftKeyPressed)
@@ -723,7 +733,7 @@
 
 					m_iBufferPos--;
 				}
-				
+
 				// If we end up on a puctuation char we just select it (treat punct like a word)
 				if (iswpunct(searchString[m_iBufferPos - 1]))
 					m_iBufferPos--;
@@ -748,6 +758,7 @@
 			m_iBufferPos_Tail = -1;
 		}
 
+		UpdateBufferPositionSetting;
 		UpdateAutoScroll();
 
 		return IN_HANDLED;
@@ -794,8 +805,9 @@
 				m_iBufferPos = m_iBufferPos_Tail;
 
 			m_iBufferPos_Tail = -1;
-		}			
+		}
 
+		UpdateBufferPositionSetting;
 		UpdateAutoScroll();
 
 		return IN_HANDLED;
@@ -825,7 +837,7 @@
 			(Message.value == CStr("size") || 
 			 Message.value == CStr("z") ||
 			 Message.value == CStr("absolute")))
-		{		
+		{
 			GetScrollBar(0).SetX(m_CachedActualSize.right);
 			GetScrollBar(0).SetY(m_CachedActualSize.top);
 			GetScrollBar(0).SetZ(GetBufferedZ());
@@ -841,6 +853,11 @@
 			GetScrollBar(0).SetScrollBarStyle(scrollbar_style);
 		}
 
+		if (Message.value == CStr("buffer_position"))
+		{
+			GUI<int>::GetSetting(this, Message.value, m_iBufferPos);
+		}
+
 		if (Message.value == CStr("size") || 
 			Message.value == CStr("z") ||
 			Message.value == CStr("font") || 
@@ -868,7 +885,7 @@
 
 			UpdateText();
 		}
-		
+
 		}break;
 
 	case GUIM_MOUSE_PRESS_LEFT:
@@ -902,7 +919,7 @@
 		}
 
 		m_SelectingText = true;
-		
+
 		UpdateAutoScroll();
 
 		// If we immediately release the button it will just be seen as a click
@@ -1098,6 +1115,7 @@
 	default:
 		break;
 	}
+	UpdateBufferPositionSetting;
 }
 
 void CInput::UpdateCachedSize()
@@ -1155,7 +1173,7 @@
 	}
 
 	if (GetGUI())
-	{	
+	{
 		CStrW font_name_w;
 		CColor color, color_selected;
 		//CStrW caption;
@@ -1163,7 +1181,7 @@
 		GUI<CColor>::GetSetting(this, "textcolor", color);
 		GUI<CColor>::GetSetting(this, "textcolor_selected", color_selected);
 		CStrIntern font_name(font_name_w.ToUTF8());
-		
+
 		// Get pointer of caption, it might be very large, and we don't
 		//  want to copy it continuously.
 		CStrW *pCaption = NULL;
@@ -1180,10 +1198,10 @@
 
 		CGUISpriteInstance *sprite=NULL, *sprite_selectarea=NULL;
 		int cell_id;
-		
+
 		GUI<CGUISpriteInstance>::GetSettingPointer(this, "sprite", sprite);
 		GUI<CGUISpriteInstance>::GetSettingPointer(this, "sprite_selectarea", sprite_selectarea);
-	
+
 		GUI<int>::GetSetting(this, "cell_id", cell_id);
 
 		GetGUI()->DrawSprite(*sprite, cell_id, bz, m_CachedActualSize);
@@ -1244,7 +1262,7 @@
 		float ls = (float)font.GetLineSpacing();
 
 		CShaderTechniquePtr tech = g_Renderer.GetShaderManager().LoadEffect(str_gui_text);
-		
+
 		CTextRenderer textRenderer(tech->GetShader());
 		textRenderer.Font(font_name);
 
@@ -1254,7 +1272,7 @@
 			(float)(int)(m_CachedActualSize.left) + buffer_zone, 
 			(float)(int)(m_CachedActualSize.top+h) + buffer_zone,
 			bz+0.1f);
-		
+
 		// U+FE33: PRESENTATION FORM FOR VERTICAL LOW LINE
 		// (sort of like a | which is aligned to the left of most characters)
 
@@ -1411,7 +1429,7 @@
 
 		// Reset some from previous run
 		buffered_y = -scroll;
-		
+
 		// Setup initial color (then it might change and change back, when drawing selected area)
 		textRenderer.Color(color);
 
@@ -1418,7 +1436,7 @@
 		tech->BeginPass();
 
 		bool using_selected_color = false;
-		
+
 		for (std::list<SRow>::const_iterator it = m_CharacterPositions.begin();
 			 it != m_CharacterPositions.end();
 			 ++it, buffered_y += ls)
@@ -1432,7 +1450,7 @@
 				}
 
 				CMatrix3D savedTransform = textRenderer.GetTransform();
-				
+
 				// Text must always be drawn in integer values. So we have to convert scroll
 				if (multiline)
 					textRenderer.Translate(0.f, -(float)(int)scroll, 0.f);
@@ -1492,7 +1510,7 @@
 					}
 
 					// check it's now outside a one-liner, then we'll break
-					if (!multiline && i < (int)it->m_ListOfX.size())				
+					if (!multiline && i < (int)it->m_ListOfX.size())
 					{
 						if (it->m_ListOfX[i] - m_HorizontalScroll > m_CachedActualSize.GetWidth()-buffer_zone)
 							break;
@@ -1514,7 +1532,7 @@
 				textRenderer.SetTransform(savedTransform);
 			}
 
-			textRenderer.Translate(0.f, ls, 0.f);	
+			textRenderer.Translate(0.f, ls, 0.f);
 		}
 
 		textRenderer.Render();
@@ -1552,6 +1570,7 @@
 	// Ensure positions are valid after caption changes
 	m_iBufferPos = std::min(m_iBufferPos, (int)caption.size());
 	m_iBufferPos_Tail = std::min(m_iBufferPos_Tail, (int)caption.size());
+	UpdateBufferPositionSetting;
 
 	if (font_name.empty())
 	{
@@ -1563,7 +1582,7 @@
 
 	SRow row;
 	row.m_ListStart = 0;
-	
+
 	int to = 0;	// make sure it's initialized
 
 	if (to_before == -1)
@@ -1670,7 +1689,7 @@
 		// set 'from' to the row we'll destroy from
 		//  and 'to' to the row we'll destroy to
 		from = destroy_row_from->m_ListStart;
-		
+
 		if (destroy_row_to != m_CharacterPositions.end())
 			to = destroy_row_to->m_ListStart; // notice it will iterate [from, to), so it will never reach to.
 		else
@@ -1684,7 +1703,7 @@
 		--temp_it;
 
 		current_line = m_CharacterPositions.erase(destroy_row_from, destroy_row_to);
-		
+
 		// If there has been a change in number of characters
 		//  we need to change all m_ListStart that comes after
 		//  the interval we just destroyed. We'll change all
@@ -1707,7 +1726,7 @@
 				to += delta;
 		}
 	}
-	
+
 	int last_word_started=from;
 	//int last_list_start=-1;	// unused
 	float x_pos = 0.f;
@@ -1721,7 +1740,7 @@
 		{
 			if (i==to-1 && to != (int)caption.length())
 				break; // it will be added outside
-			
+
 			current_line = m_CharacterPositions.insert( current_line, row );
 			++current_line;
 
@@ -1760,7 +1779,7 @@
 					// regular word-wrap
 					row.m_ListOfX.resize(row.m_ListOfX.size() - (i-last_word_started+1));
 				}
-				
+
 				// Now, create a new line:
 				//  notice: when we enter a newline, you can stand with the cursor
 				//  both before and after that character, being on different
@@ -1868,7 +1887,7 @@
 				// set 'from' to the from row we'll destroy
 				//  and 'to' to 'to_after'
 				from = destroy_row_from->m_ListStart;
-				
+
 				if (destroy_row_to != m_CharacterPositions.end())
 					to = destroy_row_to->m_ListStart; // notice it will iterate [from, to[, so it will never reach to.
 				else
@@ -1936,7 +1955,7 @@
 		GUI<CStrW>::GetSetting(this, "font", font_name_w);
 		GUI<bool>::GetSetting(this, "scrollbar", scrollbar);
 		CStrIntern font_name(font_name_w.ToUTF8());
-		
+
 		float scroll=0.f;
 		if (scrollbar)
 		{
@@ -1985,7 +2004,7 @@
 
 	//m_iBufferPos = m_CharacterPositions.get.m_ListStart;
 	retPosition = current->m_ListStart;
-	
+
 	// Okay, now loop through the glyphs to find the appropriate X position
 	float dummy;
 	retPosition += GetXTextPosition(current, mouse.x, dummy);
@@ -2053,6 +2072,7 @@
 	// Remove selection
 	m_iBufferPos_Tail = -1;
 	m_iBufferPos = virtualFrom;
+	UpdateBufferPositionSetting;
 }
 
 bool CInput::SelectingText() const
@@ -2089,13 +2109,13 @@
 		GUI<CStrW>::GetSetting(this, "font", font_name_w);
 		GUI<bool>::GetSetting(this, "scrollbar", scrollbar);
 		CStrIntern font_name(font_name_w.ToUTF8());
-		
+
 		float scroll=0.f;
 		if (!scrollbar)
 			return;
 
 		scroll = GetScrollBar(0).GetPos();
-		
+
 		// Now get the height of the font.
 						// TODO: Get the real font
 		CFontMetrics font(font_name);
@@ -2112,7 +2132,7 @@
 			if (m_iBufferPos >= current->m_ListStart &&
 				m_iBufferPos <= current->m_ListStart+(int)current->m_ListOfX.size())
 				break;
-			
+
 			++current;
 			++row;
 		}