Index: build/premake/extern_libs4.lua
===================================================================
--- build/premake/extern_libs4.lua	(revision 12046)
+++ build/premake/extern_libs4.lua	(working copy)
@@ -252,6 +252,20 @@
 			})
 		end,
 	},
+	gloox = {
+		compile_settings = function()
+			add_default_include_paths("gloox")
+		end,
+		link_settings = function()
+			if os.is("windows") then
+				add_default_lib_paths("gloox")
+			end
+			add_default_links({
+				win_names  = { "gloox-1.0" },
+				unix_names = { "gloox" },
+			})
+		end,
+	},
 	cxxtest = {
 		compile_settings = function()
 			add_default_include_paths("cxxtest")
Index: build/premake/premake4.lua
===================================================================
--- build/premake/premake4.lua	(revision 12046)
+++ build/premake/premake4.lua	(working copy)
@@ -486,6 +486,17 @@
 
 
 	source_dirs = {
+	"lobby",
+	}
+	extern_libs = {
+	"spidermonkey",
+	"gloox",
+	"boost",
+	}
+	setup_static_lib_project("lobby", source_dirs, extern_libs, {})
+
+
+	source_dirs = {
 		"simulation2",
 		"simulation2/components",
 		"simulation2/helpers",
@@ -705,6 +716,8 @@
 	"libcurl",
 
 	"valgrind",
+
+	"gloox",
 }
 
 if not os.is("windows") and not _OPTIONS["android"] and not os.is("macosx") then
Index: source/ps/GameSetup/GameSetup.cpp
===================================================================
--- source/ps/GameSetup/GameSetup.cpp	(revision 12046)
+++ source/ps/GameSetup/GameSetup.cpp	(working copy)
@@ -91,6 +91,8 @@
 #include "network/NetServer.h"
 #include "network/NetClient.h"
 
+#include "lobby/XmppClient.h"
+
 #include "ps/Pyrogenesis.h"	// psSetLogDir
 #include "ps/GameSetup/Atlas.h"
 #include "ps/GameSetup/GameSetup.h"
@@ -653,6 +655,8 @@
 {
 	EndGame();
 
+	SAFE_DELETE(g_XmppClient);
+
 	ShutdownPs(); // Must delete g_GUI before g_ScriptingHost
 
 	in_reset_handlers();
@@ -972,6 +976,12 @@
 
 	ogl_WarnIfError();
 
+  if (args.Has("looping"))
+  {
+    InitPs(true, L"page_multiplayerlobby.xml", JSVAL_VOID);
+    return;
+  }
+
 	try
 	{
 		if (!Autostart(args))
Index: source/gui/COList.h
===================================================================
--- source/gui/COList.h	(revision 0)
+++ source/gui/COList.h	(revision 0)
@@ -0,0 +1,69 @@
+#ifndef INCLUDED_COLIST
+#define INCLUDED_COLIST
+
+//--------------------------------------------------------
+//  Includes / Compiler directives
+//--------------------------------------------------------
+#include "GUI.h"
+#include "CList.h"
+
+//--------------------------------------------------------
+//  Macros
+//--------------------------------------------------------
+
+//--------------------------------------------------------
+//  Types
+//--------------------------------------------------------
+
+//--------------------------------------------------------
+//  Declarations
+//--------------------------------------------------------
+
+struct ObjectDef
+{
+  CColor m_TextColor;
+  CStr m_Id;
+  int m_Width;
+  CStrW m_Heading;
+
+};
+
+/**
+ *  Todo : add description
+ *
+ */
+class COList : public CList
+{
+  GUI_OBJECT(COList)
+
+public:
+    COList();
+
+protected:
+	void SetupText();
+	void HandleMessage(SGUIMessage &Message);
+
+	/**
+	 * Handle the \<item\> tag.
+	 */
+	virtual bool HandleAdditionalChildren(const XMBElement& child, CXeromyces* pFile);
+
+	void DrawList(const int &selected, const CStr& _sprite,
+					const CStr& _sprite_selected, const CStr& _textcolor);
+
+	virtual CRect GetListRect() const {
+		return m_CachedActualSize + CRect(0, m_HeadingHeight, 0, 0);
+
+				CRect(m_CachedActualSize.left,
+								 m_CachedActualSize.top+m_HeadingHeight,
+								 m_CachedActualSize.right,
+								 m_CachedActualSize.bottom); }
+
+	std::vector<ObjectDef> m_ObjectsDefs;
+
+private:
+	float m_HeadingHeight;
+
+};
+
+#endif // INCLUDED_COLIST
Index: source/gui/scripting/ScriptFunctions.cpp
===================================================================
--- source/gui/scripting/ScriptFunctions.cpp	(revision 12046)
+++ source/gui/scripting/ScriptFunctions.cpp	(working copy)
@@ -43,7 +43,9 @@
 #include "ps/UserReport.h"
 #include "ps/GameSetup/Atlas.h"
 #include "ps/GameSetup/Config.h"
+#include "ps/ConfigDB.h"
 #include "tools/atlas/GameInterface/GameLoop.h"
+#include "lobby/XmppClient.h"
 
 #include "simulation2/Simulation2.h"
 #include "simulation2/components/ICmpAIManager.h"
@@ -556,6 +558,133 @@
 	g_Game->GetTurnManager()->RewindTimeWarp();
 }
 
+/* Begin lobby related functions */
+
+void StartXmppClient(void* cbdata, std::string sUsername, std::string sPassword, std::string sRoom, std::string sNick)
+{
+	CGUIManager* guiManager = static_cast<CGUIManager*> (cbdata);
+
+	ENSURE(!g_XmppClient);
+
+	g_XmppClient = new XmppClient(guiManager->GetScriptInterface(), sUsername, sPassword, sRoom, sNick);
+}
+
+void StartRegisterXmppClient(void* cbdata, std::string sUsername, std::string sPassword)
+{
+	CGUIManager* guiManager = static_cast<CGUIManager*> (cbdata);
+
+	ENSURE(!g_XmppClient);
+
+	g_XmppClient = new XmppClient(guiManager->GetScriptInterface(), sUsername, sPassword, "", "", true);
+}
+
+void StopXmppClient(void* UNUSED(cbdata))
+{
+	ENSURE(g_XmppClient);
+	SAFE_DELETE(g_XmppClient);
+}
+
+void ConnectXmppClient(void* UNUSED(cbdata))
+{
+	ENSURE(g_XmppClient);
+	g_XmppClient->connect();
+}
+
+void DisconnectXmppClient(void* UNUSED(cbdata))
+{  
+	ENSURE(g_XmppClient);
+	g_XmppClient->disconnect();
+}
+
+void RecvXmppClient(void* UNUSED(cbdata))
+{
+	if(!g_XmppClient)
+		return;
+	g_XmppClient->recv();
+}
+
+void SendGetGameList(void* UNUSED(cbdata))
+{
+	if(!g_XmppClient)
+		return;
+	g_XmppClient->SendIqGetGameList();
+}
+
+void SendRegisterGame(void* UNUSED(cbdata), CScriptVal data)
+{
+	if(!g_XmppClient)
+		return;
+	g_XmppClient->SendIqRegisterGame(data);
+}
+
+void SendUnregisterGame(void* UNUSED(cbdata), std::string name)
+{
+	if(!g_XmppClient)
+		return;
+	g_XmppClient->SendIqUnregisterGame(name);
+}
+
+CScriptVal GetPlayerList(void* UNUSED(cbdata))
+{
+	if(!g_XmppClient)
+		return CScriptVal();
+
+	CScriptValRooted playerList = g_XmppClient->GUIGetPlayerList();
+
+	return playerList.get();
+}
+
+CScriptVal GetGameList(void* UNUSED(cbdata))
+{
+	if(!g_XmppClient)
+		return CScriptVal();
+
+	CScriptValRooted gameList = g_XmppClient->GUIGetGameList();
+
+	return gameList.get();
+}
+
+CScriptVal LobbyGuiPollMessage(void* UNUSED(cbdata))
+{
+	if(!g_XmppClient)
+		return CScriptVal();
+
+	CScriptValRooted poll = g_XmppClient->GuiPollMessage();
+
+	return poll.get();
+}
+
+void LobbySendMessage(void* UNUSED(cbdata), std::string message)
+{
+	if(!g_XmppClient)
+		return;
+
+	g_XmppClient->SendMUCMessage(message);
+}
+
+std::string GetDefaultLobbyPlayerUsername(void* UNUSED(cbdata))
+{
+	std::string username;
+	CFG_GET_USER_VAL("lobby.login", String, username);
+	return username;
+}
+
+std::string GetDefaultLobbyPlayerPassword(void* UNUSED(cbdata))
+{
+	std::string password;
+	CFG_GET_USER_VAL("lobby.password", String, password);
+	return password;
+}
+
+void SetDefaultLobbyPlayerPair(void * UNUSED(cbdata), std::string username, std::string password)
+{
+	g_ConfigDB.CreateValue(CFG_USER, "lobby.login")->m_String = username;
+	g_ConfigDB.CreateValue(CFG_USER, "lobby.password")->m_String = password;
+	g_ConfigDB.WriteFile(CFG_USER);
+}
+
+/* End lobby related functions */
+
 void QuickSave(void* UNUSED(cbdata))
 {
 	g_Game->GetTurnManager()->QuickSave();
@@ -648,4 +777,22 @@
 	scriptInterface.RegisterFunction<void, unsigned int, &EnableTimeWarpRecording>("EnableTimeWarpRecording");
 	scriptInterface.RegisterFunction<void, &RewindTimeWarp>("RewindTimeWarp");
 	scriptInterface.RegisterFunction<void, bool, &SetBoundingBoxDebugOverlay>("SetBoundingBoxDebugOverlay");
+
+	// Lobby functions
+	scriptInterface.RegisterFunction<void, std::string, std::string, std::string, std::string, &StartXmppClient>("StartXmppClient");
+	scriptInterface.RegisterFunction<void, std::string, std::string, &StartRegisterXmppClient>("StartRegisterXmppClient");
+	scriptInterface.RegisterFunction<void, &StopXmppClient>("StopXmppClient");
+	scriptInterface.RegisterFunction<void, &ConnectXmppClient>("ConnectXmppClient");
+	scriptInterface.RegisterFunction<void, &DisconnectXmppClient>("DisconnectXmppClient");
+	scriptInterface.RegisterFunction<void, &RecvXmppClient>("RecvXmppClient");
+	scriptInterface.RegisterFunction<void, &SendGetGameList>("SendGetGameList");
+	scriptInterface.RegisterFunction<void, CScriptVal, &SendRegisterGame>("SendRegisterGame");
+	scriptInterface.RegisterFunction<void, std::string, &SendUnregisterGame>("SendUnregisterGame");
+	scriptInterface.RegisterFunction<CScriptVal, &GetPlayerList>("GetPlayerList");
+	scriptInterface.RegisterFunction<CScriptVal, &GetGameList>("GetGameList");
+	scriptInterface.RegisterFunction<CScriptVal, &LobbyGuiPollMessage>("LobbyGuiPollMessage");
+	scriptInterface.RegisterFunction<void, std::string, &LobbySendMessage>("LobbySendMessage");
+	scriptInterface.RegisterFunction<std::string, &GetDefaultLobbyPlayerUsername>("GetDefaultLobbyPlayerUsername");
+	scriptInterface.RegisterFunction<std::string, &GetDefaultLobbyPlayerPassword>("GetDefaultLobbyPlayerPassword");
+	scriptInterface.RegisterFunction<void, std::string, std::string, &SetDefaultLobbyPlayerPair>("SetDefaultLobbyPlayerPair");
 }
Index: source/gui/CList.h
===================================================================
--- source/gui/CList.h	(revision 12046)
+++ source/gui/CList.h	(working copy)
@@ -86,7 +86,7 @@
 	 * Sets up text, should be called every time changes has been
 	 * made that can change the visual.
 	 */
-	void SetupText();
+	virtual void SetupText();
 
 	/**
 	 * @see IGUIObject#HandleMessage()
@@ -121,7 +121,7 @@
 
 	// Extended drawing interface, this is so that classes built on the this one
 	//  can use other sprite names.
-	void DrawList(const int &selected, const CStr& _sprite, 
+	virtual void DrawList(const int &selected, const CStr& _sprite,
 				  const CStr& _sprite_selected, const CStr& _textcolor);
 
 	// Get the area of the list. This is so that i can easily be changed, like in CDropDown
Index: source/gui/CGUI.cpp
===================================================================
--- source/gui/CGUI.cpp	(revision 12046)
+++ source/gui/CGUI.cpp	(working copy)
@@ -35,6 +35,7 @@
 #include "CRadioButton.h"
 #include "CInput.h"
 #include "CList.h"
+#include "COList.h"
 #include "CDropDown.h"
 #include "CProgressBar.h"
 #include "CTooltip.h"
@@ -442,6 +443,7 @@
 	AddObjectType("minimap",        &CMiniMap::ConstructObject);
 	AddObjectType("input",			&CInput::ConstructObject);
 	AddObjectType("list",			&CList::ConstructObject);
+	AddObjectType("olist",			&COList::ConstructObject);
 	AddObjectType("dropdown",		&CDropDown::ConstructObject);
 	AddObjectType("tooltip",		&CTooltip::ConstructObject);
 }
Index: source/gui/IGUIObject.h
===================================================================
--- source/gui/IGUIObject.h	(revision 12046)
+++ source/gui/IGUIObject.h	(working copy)
@@ -424,7 +424,7 @@
 	 * have any additional children (and this function should never be called).
 	 */
 	virtual bool HandleAdditionalChildren(const XMBElement& UNUSED(child), 
-										  CXeromyces* UNUSED(pFile)) { return false; }
+											CXeromyces* UNUSED(pFile)) { return false; }
 
 	/**
 	 * Cached size, real size m_Size is actually dependent on resolution
Index: source/gui/COList.cpp
===================================================================
--- source/gui/COList.cpp	(revision 0)
+++ source/gui/COList.cpp	(revision 0)
@@ -0,0 +1,294 @@
+#include "COList.h"
+
+#include "ps/CLogger.h"
+
+COList::COList() : CList(),m_HeadingHeight(30.f)
+{
+	AddSetting(GUIST_CGUISpriteInstance,	"sprite_heading");
+}
+
+void COList::SetupText()
+{
+	if (!GetGUI())
+		return;
+
+	CGUIList *pList;
+	GUI<CGUIList>::GetSettingPointer(this, "list_ip", pList);
+
+	//ENSURE(m_GeneratedTexts.size()>=1);
+
+	m_ItemsYPositions.resize( pList->m_Items.size()+1 );
+
+	// Delete all generated texts. Some could probably be saved,
+	//  but this is easier, and this function will never be called
+	//  continuously, or even often, so it'll probably be okay.
+	std::vector<SGUIText*>::iterator it;
+	for (it=m_GeneratedTexts.begin(); it!=m_GeneratedTexts.end(); ++it)
+	{
+		if (*it)
+			delete *it;
+	}
+	m_GeneratedTexts.clear();
+
+	CStrW font;
+	if (GUI<CStrW>::GetSetting(this, "font", font) != PSRETURN_OK || font.empty())
+		// Use the default if none is specified
+		// TODO Gee: (2004-08-14) Don't define standard like this. Do it with the default style.
+		font = L"default";
+
+	//CGUIString caption;
+	bool scrollbar;
+	//GUI<CGUIString>::GetSetting(this, "caption", caption);
+	GUI<bool>::GetSetting(this, "scrollbar", scrollbar);
+
+	float width = GetListRect().GetWidth();
+	// remove scrollbar if applicable
+	if (scrollbar && GetScrollBar(0).GetStyle())
+		width -= GetScrollBar(0).GetStyle()->m_Width;
+
+	float buffer_zone=0.f;
+	GUI<float>::GetSetting(this, "buffer_zone", buffer_zone);
+
+
+	for (unsigned int c=0; c<m_ObjectsDefs.size(); ++c)
+	{
+		SGUIText *text = new SGUIText();
+		CGUIString gui_string;
+		gui_string.SetValue(m_ObjectsDefs[c].m_Heading);
+		*text = GetGUI()->GenerateText(gui_string, font, width, buffer_zone, this);
+		AddText(text);
+	}
+
+
+	// Generate texts
+	float buffered_y = 0.f;
+
+	for (int i=0; i<(int)pList->m_Items.size(); ++i)
+	{
+		m_ItemsYPositions[i] = buffered_y;
+		for (unsigned int c=0; c<m_ObjectsDefs.size(); ++c)
+		{
+			CGUIList * pList_c;
+			GUI<CGUIList>::GetSettingPointer(this, m_ObjectsDefs[c].m_Id, pList_c);
+			SGUIText *text = new SGUIText();
+			*text = GetGUI()->GenerateText(pList_c->m_Items[i], font, width, buffer_zone, this);
+			if (c==0)
+				buffered_y += text->m_Size.cy;
+			AddText(text);
+		}
+	}
+
+	m_ItemsYPositions[pList->m_Items.size()] = buffered_y;
+
+	//if (! scrollbar)
+	//	CalculateTextPosition(m_CachedActualSize, m_TextPos, *m_GeneratedTexts[0]);
+
+	// Setup scrollbar
+	if (scrollbar)
+	{
+		GetScrollBar(0).SetScrollRange( m_ItemsYPositions.back() );
+		GetScrollBar(0).SetScrollSpace( GetListRect().GetHeight() );
+
+		CRect rect = GetListRect();
+		GetScrollBar(0).SetX( rect.right );
+		GetScrollBar(0).SetY( rect.top );
+		GetScrollBar(0).SetZ( GetBufferedZ() );
+		GetScrollBar(0).SetLength( rect.bottom - rect.top );
+	}
+}
+
+void COList::HandleMessage(SGUIMessage &Message)
+{
+	CList::HandleMessage(Message);
+//	switch (Message.type)
+//	{
+//	case GUIM_SETTINGS_UPDATED:
+//		if (Message.value.Find("list_") != -1)
+//		{
+//			SetupText();
+//		}
+//	}
+}
+
+bool COList::HandleAdditionalChildren(const XMBElement& child, CXeromyces* pFile)
+{
+	int elmt_item = pFile->GetElementID("item");
+	int elmt_heading = pFile->GetElementID("heading");
+	int elmt_def = pFile->GetElementID("def");
+
+	if (child.GetNodeName() == elmt_item)
+	{
+		AddItem(child.GetText().FromUTF8(), child.GetText().FromUTF8());
+		return true;
+	}
+	else if (child.GetNodeName() == elmt_heading)
+	{
+		CStrW text (child.GetText().FromUTF8());
+
+		return true;
+	}
+	else if (child.GetNodeName() == elmt_def)
+	{
+		ObjectDef oDef;
+
+		XMBAttributeList attributes = child.GetAttributes();
+		for (int i=0; i<attributes.Count; ++i)
+		{
+			XMBAttribute attr = attributes.Item(i);
+			CStr attr_name (pFile->GetAttributeString(attr.Name));
+			CStr attr_value (attr.Value);
+
+			if (attr_name == "color")
+			{
+				CColor color;
+				if (!GUI<CColor>::ParseString(attr_value.FromUTF8(), color))
+					LOGERROR(L"GUI: Error parsing '%hs' (\"%ls\")", attr_name.c_str(), attr_value.c_str());
+				else oDef.m_TextColor = color;
+			}
+			else if (attr_name == "id")
+			{
+				oDef.m_Id = "list_"+attr_value;
+			}
+			else if (attr_name == "width")
+			{
+				int width;
+				if (!GUI<int>::ParseString(attr_value.FromUTF8(), width))
+					LOGERROR(L"GUI: Error parsing '%hs' (\"%ls\")", attr_name.c_str(), attr_value.c_str());
+				else
+					oDef.m_Width = width;
+			}
+			else if (attr_name == "heading")
+			{
+				oDef.m_Heading = attr_value.FromUTF8();
+			}
+
+		}
+
+		m_ObjectsDefs.push_back(oDef);
+
+		AddSetting(GUIST_CGUIList, oDef.m_Id);
+		SetupText();
+
+		return true;
+	}
+	else
+	{
+		return false;
+	}
+}
+
+void COList::DrawList(const int &selected,
+					 const CStr& _sprite,
+					 const CStr& _sprite_selected,
+					 const CStr& _textcolor)
+{
+	float bz = GetBufferedZ();
+
+	// First call draw on ScrollBarOwner
+	bool scrollbar;
+	GUI<bool>::GetSetting(this, "scrollbar", scrollbar);
+
+	if (scrollbar)
+	{
+		// Draw scrollbar
+		IGUIScrollBarOwner::Draw();
+	}
+
+	if (GetGUI())
+	{
+		CRect rect = GetListRect();
+
+		CGUISpriteInstance *sprite=NULL, *sprite_selectarea=NULL;
+		int cell_id;
+		GUI<CGUISpriteInstance>::GetSettingPointer(this, _sprite, sprite);
+		GUI<CGUISpriteInstance>::GetSettingPointer(this, _sprite_selected, sprite_selectarea);
+		GUI<int>::GetSetting(this, "cell_id", cell_id);
+
+		CGUIList *pList;
+		GUI<CGUIList>::GetSettingPointer(this, "list_name", pList);
+
+		GetGUI()->DrawSprite(*sprite, cell_id, bz, rect);
+
+		float scroll=0.f;
+		if (scrollbar)
+		{
+			scroll = GetScrollBar(0).GetPos();
+		}
+
+		if (selected != -1)
+		{
+			ENSURE(selected >= 0 && selected+1 < (int)m_ItemsYPositions.size());
+
+			// Get rectangle of selection:
+			CRect rect_sel(rect.left, rect.top + m_ItemsYPositions[selected] - scroll,
+								 rect.right, rect.top + m_ItemsYPositions[selected+1] - scroll);
+
+			if (rect_sel.top <= rect.bottom &&
+				rect_sel.bottom >= rect.top)
+			{
+				if (rect_sel.bottom > rect.bottom)
+					rect_sel.bottom = rect.bottom;
+				if (rect_sel.top < rect.top)
+					rect_sel.top = rect.top;
+
+				if (scrollbar)
+				{
+					// Remove any overlapping area of the scrollbar.
+					if (rect_sel.right > GetScrollBar(0).GetOuterRect().left &&
+						rect_sel.right <= GetScrollBar(0).GetOuterRect().right)
+						rect_sel.right = GetScrollBar(0).GetOuterRect().left;
+
+					if (rect_sel.left >= GetScrollBar(0).GetOuterRect().left &&
+						rect_sel.left < GetScrollBar(0).GetOuterRect().right)
+						rect_sel.left = GetScrollBar(0).GetOuterRect().right;
+				}
+
+				GetGUI()->DrawSprite(*sprite_selectarea, cell_id, bz+0.05f, rect_sel);
+			}
+		}
+
+		CColor color;
+		GUI<CColor>::GetSetting(this, _textcolor, color);
+
+		CGUISpriteInstance *sprite_heading=NULL;
+		GUI<CGUISpriteInstance>::GetSettingPointer(this, "sprite_heading", sprite_heading);
+		CRect rect_head(m_CachedActualSize.left, m_CachedActualSize.top, m_CachedActualSize.right,
+										m_CachedActualSize.top + m_HeadingHeight);
+		GetGUI()->DrawSprite(*sprite_heading, cell_id, bz, rect_head);
+
+		float xpos = 0;
+		for (unsigned int def=0; def< m_ObjectsDefs.size(); ++def)
+		{
+			DrawText(def, color, m_CachedActualSize.TopLeft() + CPos(xpos, 4), bz+0.1f, rect_head);
+			xpos += m_ObjectsDefs[def].m_Width;
+		}
+
+		for (int i=0; i<(int)pList->m_Items.size(); ++i)
+		{
+			if (m_ItemsYPositions[i+1] - scroll < 0 ||
+				m_ItemsYPositions[i] - scroll > rect.GetHeight())
+				continue;
+
+			// Clipping area (we'll have to substract the scrollbar)
+			CRect cliparea = GetListRect();
+
+			if (scrollbar)
+			{
+				if (cliparea.right > GetScrollBar(0).GetOuterRect().left &&
+					cliparea.right <= GetScrollBar(0).GetOuterRect().right)
+					cliparea.right = GetScrollBar(0).GetOuterRect().left;
+
+				if (cliparea.left >= GetScrollBar(0).GetOuterRect().left &&
+					cliparea.left < GetScrollBar(0).GetOuterRect().right)
+					cliparea.left = GetScrollBar(0).GetOuterRect().right;
+			}
+
+			xpos = 0;
+			for (unsigned int def=0; def< m_ObjectsDefs.size(); ++def)
+			{
+				DrawText(m_ObjectsDefs.size() * (i+/*Heading*/1) + def, m_ObjectsDefs[def].m_TextColor, rect.TopLeft() + CPos(xpos, -scroll + m_ItemsYPositions[i]), bz+0.1f, cliparea);
+				xpos += m_ObjectsDefs[def].m_Width;
+			}
+		}
+	}
+}
Index: source/tools/XpartaMuPP/XpartaMuPP.py
===================================================================
--- source/tools/XpartaMuPP/XpartaMuPP.py	(revision 0)
+++ source/tools/XpartaMuPP/XpartaMuPP.py	(revision 0)
@@ -0,0 +1,256 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+
+"""
+    XpartaMuPP by :
+	Badmadblacksad
+	..
+    For : 0ad
+    License : GPL
+"""
+
+import sys
+import logging
+import time
+from optparse import OptionParser
+
+import sleekxmpp
+from sleekxmpp.stanza import Iq
+from sleekxmpp.stanza import *
+from sleekxmpp.xmlstream import ElementBase, register_stanza_plugin, ET
+from sleekxmpp.xmlstream.handler import Callback
+from sleekxmpp.xmlstream.matcher import StanzaPath
+
+from xml.dom.minidom import Document
+
+## Configuration ##
+configDEMOModOn = False 
+## !Configuration ##
+
+class BasicGameList(ElementBase):
+  name = 'query'
+  namespace = 'jabber:iq:gamelist'
+  interfaces = set(('game', 'command'))
+  sub_interfaces = interfaces
+  plugin_attrib = 'gamelist'
+
+  def addGame(self, name, ip, mapName, mapSize, victoryCondition, nbp, tnbp):
+    itemXml = ET.Element("game", {"name":name, "ip":ip, "nbp":nbp, "tnbp":tnbp, "mapName":mapName, "mapSize":mapSize, "victoryCondition":victoryCondition})
+    self.xml.append(itemXml)
+
+  def getGame(self):
+    game = self.xml.find('{%s}game' % self.namespace)
+    return game.get("name"), game.get("ip"), game.get("mapName"), game.get("mapSize"), game.get("victoryCondition"), game.get("nbp"), game.get("tnbp")   
+
+  def getCommand(self):
+    command = self.xml.find('{%s}command' % self.namespace)
+    return command
+
+class XpartaMuPP(sleekxmpp.ClientXMPP):
+  """
+  A simple game list provider
+  """
+
+  def __init__(self, sjid, password, room, nick):
+    sleekxmpp.ClientXMPP.__init__(self, sjid, password)
+    self.sjid = sjid
+    self.room = room
+    self.nick = nick
+
+    # Game management
+    self.m_gameList = {}
+    
+    #DEMO
+    if configDEMOModOn:
+      self.addGame("666.666.666.666", "Unplayable map", "666.666.666.666", "Serengeti", "128", "gold rush", "4", "4")
+      self.addGame("666.666.666.667", "Unreachale liberty", "666.666.666.667", "oasis", "256", "conquest", "2", "4")
+      #for i in range(50):
+      #  self.addGame("666.666.666."+str(i), "X"+str(i), "666.666.666."+str(i), "Oasis", "large", "conquest", "1", "4")   
+
+    register_stanza_plugin(Iq, BasicGameList)
+    self.register_handler(Callback('Iq Gamelist',
+                                       StanzaPath('iq/gamelist'),
+                                       self.iqhandler,
+                                       instream=True))
+
+    self.add_event_handler("session_start", self.start)
+    self.add_event_handler("message", self.message)
+    self.add_event_handler("muc::%s::got_online" % self.room, self.muc_online)
+    self.add_event_handler("muc::%s::got_offline" % self.room, self.muc_offline)
+
+  def start(self, event):
+    """
+    Process the session_start event
+    """
+    self.plugin['xep_0045'].joinMUC(self.room, self.nick)
+
+    self.send_presence()
+    self.get_roster()
+    logging.info("xpartamupp started")
+
+    #DEMO
+    self.DEMOrequestGameList()
+    #self.DEMOregisterGame()
+
+  def message(self, msg):
+    """
+    Process incoming message stanzas
+    """
+    if msg['type'] == 'chat':
+      msg.reply("Thanks for sending\n%(body)s" % msg).send()
+      logging.comm("receive message"+msg['body'])
+
+  def muc_online(self, presence):
+    """
+    Process presence stanza from a chat room. 
+    """
+    if presence['muc']['nick'] != self.nick:
+      self.send_message(mto=presence['from'], mbody="Hello %s, welcome in the 0ad alpha chatroom. Polishes your weapons and get ready to fight!" %(presence['muc']['nick']), mtype='')
+
+  def muc_offline(self, presence):
+    """
+    Process presence stanza from a chat room. 
+    """
+    if presence['muc']['nick'] != self.nick:
+      self.removeGame(presence['muc']['jid'].bare)
+
+  def iqhandler(self, iq):
+    """
+    Handle the custom <gamelist> stanza
+    TODO: This method should be very robust because
+    we could receive anything
+    """
+    if iq['type'] == 'error':
+      logging.error('iqhandler error' + iq['error']['condition'])
+      #self.disconnect()
+    elif iq['type'] == 'get':
+      """
+      Request a gamelist
+      """
+      self.sendGameList(iq['from'])
+    elif iq['type'] == 'result':
+      """
+      Iq successfully received
+      """
+      pass
+    elif iq['type'] == 'set':
+      """
+      Register-update / unregister a game
+      """
+      command = iq['gamelist']['command'].text
+      if command == 'register':
+        try:
+          name, ip, mapName, mapSize, victoryCondition, nbp, tnbp = iq['gamelist']['game']
+          logging.debug("ip " + ip)
+          self.addGame(iq['from'].bare,name,ip,mapName,mapSize,victoryCondition,nbp,tnbp)
+        except:
+          logging.error("Failed to process game data")
+      elif command == 'unregister':
+        self.removeGame(iq['from'].bare)
+
+  def sendGameList(self, to):
+    """
+    Send a massive stanza with the whole game list
+    """
+    stz = BasicGameList()
+    for k in self.m_gameList:
+      g = self.m_gameList[k]
+      stz.addGame(g['name'], g['ip'], g['mapName'], g['mapSize'], g['victoryCondition'], g['nbp'], g['tnbp'])
+    iq = self.Iq()
+    iq['type'] = 'result'
+    iq['to'] = to
+    iq.setPayload(stz)
+    try:
+      iq.send()
+    except:
+      logging.error("Failed to send game list")
+
+  def DEMOrequestGameList(self):
+    """
+    Test function
+    """
+    iq = self.Iq()
+    iq['type'] = 'get'
+    iq['gamelist']['field'] = 'x'
+    iq['to'] = self.boundjid.full
+    iq.send(now=True, block=False)
+    return True
+
+  def DEMOregisterGame(self):
+    """
+    Test function
+    """
+    stz = BasicGameList()
+    stz.addGame("DEMOregister","DEMOip","DEMOmap","","","","")
+    stz['command'] = 'register'
+    iq = self.Iq()
+    iq['type'] = 'set'
+    iq['to'] = self.boundjid.full
+    iq.setPayload(stz)
+    iq.send(now=True, block=False)
+    return True
+
+  # Game management
+
+  def addGame(self, sid, name, ip, mapName, mapSize, victoryCondition, nbp, tnbp):
+    game = { 'name':name, 'ip':ip, 'nbp':nbp, 'tnbp':tnbp, 'mapName':mapName, 'mapSize':mapSize, 'victoryCondition':victoryCondition } 
+    self.m_gameList[sid] = game
+
+  def removeGame(self, sid):
+    if sid in self.m_gameList:
+      del self.m_gameList[sid]
+
+if __name__ == '__main__':
+  # Setup the command line arguments.
+  optp = OptionParser()
+
+  # Output verbosity options.
+  optp.add_option('-q', '--quiet', help='set logging to ERROR',
+                  action='store_const', dest='loglevel',
+                  const=logging.ERROR, default=logging.INFO)
+  optp.add_option('-d', '--debug', help='set logging to DEBUG',
+                  action='store_const', dest='loglevel',
+                  const=logging.DEBUG, default=logging.INFO)
+  optp.add_option('-v', '--verbose', help='set logging to COMM',
+                  action='store_const', dest='loglevel',
+                  const=5, default=logging.INFO)
+
+  # XpartaMuPP configuration options
+  optp.add_option('-m', '--domain', help='set xpartamupp domain',
+                  action='store', dest='xdomain',
+                  default="localhost")
+  optp.add_option('-l', '--login', help='set xpartamupp login',
+                  action='store', dest='xlogin',
+                  default="xpartamupp")
+  optp.add_option('-p', '--password', help='set xpartamupp password',
+                  action='store', dest='xpassword',
+                  default="XXXXXX")
+  optp.add_option('-n', '--nickname', help='set xpartamupp nickname',
+                  action='store', dest='xnickname',
+                  default="XpartaMuCC")
+  optp.add_option('-D', '--demo', help='set xpartamupp in DEMO mode (add a few fake games)',
+                  action='store_true', dest='xdemomode',
+                  default=False)
+
+  opts, args = optp.parse_args()
+
+  # Set DEMO mode
+  configDEMOModOn = opts.xdemomode
+
+  # Setup logging.
+  logging.basicConfig(level=opts.loglevel,
+                      format='%(levelname)-8s %(message)s')
+
+  # XpartaMuPP
+  xmpp = XpartaMuPP(opts.xlogin+'@'+opts.xdomain+'/CC', opts.xpassword, 'arena@conference.'+opts.xdomain, opts.xnickname)
+  xmpp.register_plugin('xep_0030') # Service Discovery
+  xmpp.register_plugin('xep_0004') # Data Forms
+  xmpp.register_plugin('xep_0045') # Multi-User Chat	# used
+  xmpp.register_plugin('xep_0060') # PubSub
+  xmpp.register_plugin('xep_0199') # XMPP Ping
+
+  if xmpp.connect():
+    xmpp.process(threaded=False)
+  else:
+    logging.error("Unable to connect")
+

Property changes on: source/tools/XpartaMuPP/XpartaMuPP.py
___________________________________________________________________
Added: svn:executable
   + *

Index: source/tools/XpartaMuPP/mod_ipstamp.erl
===================================================================
--- source/tools/XpartaMuPP/mod_ipstamp.erl	(revision 0)
+++ source/tools/XpartaMuPP/mod_ipstamp.erl	(revision 0)
@@ -0,0 +1,54 @@
+
+-module(mod_ipstamp).
+
+-behaviour(gen_mod).
+
+-include("ejabberd.hrl").
+
+-export([start/2, stop/1, on_filter_packet/1]).
+
+start(_Host, _Opts) ->
+    ?INFO_MSG("mod_ipip starting", []),
+    ejabberd_hooks:add(filter_packet, global, ?MODULE, on_filter_packet, 50),
+    ok.
+
+stop(_Host) ->
+    ejabberd_hooks:delete(filter_packet, global, ?MODULE, on_filter_packet, 50),
+    ok.
+
+on_filter_packet({From, To, Packet} = Input) ->
+    {_,STo,_,_,_,_,_} = To,
+    if STo == "xpartamupp" ->
+      {_,SElement,LPacketInfo,LPacketQuery} = Packet,
+      if SElement == "iq" -> 
+        {_, SType} = lists:keyfind("type",1,LPacketInfo),
+        if SType == "set" ->
+	  {_,_,LXmlns,LGame} = lists:keyfind("query",2,LPacketQuery),
+          {_,SXmlns} = lists:keyfind("xmlns",1,LXmlns),
+          if SXmlns == "jabber:iq:gamelist" ->
+            {_,_,_,LCommand} = lists:keyfind("command",2,LGame),
+            {_,SCommand} = lists:keyfind(xmlcdata,1,LCommand),
+            if SCommand == <<"register">> ->
+              {_,_,KGame,_} = lists:keyfind("game",2,LGame),
+              Info = ejabberd_sm:get_user_info("xpartamupp","localhost","CC"),
+              {ip, {Ploc, _Port}} = lists:keyfind(ip, 1, Info),
+              SIp = inet_parse:ntoa(Ploc),
+              ?INFO_MSG(string:concat("stamp ip: ",SIp), []),
+              {From,To,{xmlelement,"iq",LPacketInfo,[
+                {xmlelement,"query",[{"xmlns","jabber:iq:gamelist"}],[
+                  {xmlelement,"game",lists:keyreplace("ip",1,KGame,{"ip",SIp}),[]},
+                  {xmlelement,"command",[],[{xmlcdata,<<"register">>}]}
+                  ]
+                }
+              ]}}
+            ; true -> Input
+            end 
+          ; true -> Input
+          end
+        ; true -> Input
+        end
+      ; true -> Input
+      end
+    ; true -> Input 
+    end.
+
Index: source/tools/XpartaMuPP/Makefile
===================================================================
--- source/tools/XpartaMuPP/Makefile	(revision 0)
+++ source/tools/XpartaMuPP/Makefile	(revision 0)
@@ -0,0 +1,18 @@
+include_dir = /usr/lib/ejabberd/include/
+ebin_dir = /usr/lib/ejabberd/ebin/
+
+module = mod_ipstamp.beam
+all : ${module}
+
+%.beam : %.erl
+	erlc -I ${include_dir} -pz ${ebin_dir} $<
+
+install : 
+	mv ${module} ${ebin_dir}
+
+clean :
+	rm ${module}
+
+restart :
+	rm -f /var/log/ejabberd/ejabberd.log
+	/etc/init.d/ejabberd restart
Index: source/tools/XpartaMuPP/README
===================================================================
--- source/tools/XpartaMuPP/README	(revision 0)
+++ source/tools/XpartaMuPP/README	(revision 0)
@@ -0,0 +1,74 @@
+@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
+@@ Install ejabberd and the erlang compiler @@
+@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
+# apt-get install ejabberd erlang-dev?
+
+Configure it
+# dpkg-reconfigure ejabberd 
+set the domain name (e.g. localhost if you installed it on your development computer)
+and a login / password
+
+You should now be able to connect to this XMPP server using a normal XMPP client (e.g. Empathy).
+
+@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
+@@ Installation of the custom XMPP module @@
+@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
+Go to it's source directory
+$ cd source/tools/XpartaMuPP
+
+Build and install it
+$ make
+# make install
+
+Tell ejabberd that you want it to load the module
+In /etc/ejabberd/ejabberd.cfg, add {mod_ipstamp, []}
+in the Modules list "Modules enabled in all ejabberd virtual hosts"
+
+Restart ejabberd
+# service ejabberd restart
+
+If something goes wrong read /var/log/ejabberd/ejabberd.log
+
+@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
+@@ Ejabberd administration @@
+@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
+Go http://localhost:5280/admin and connect 
+(the login looks like login@domain (e.g. adminjabber@localhost))
+
+In "Acces rules" check that "register" is set to "[{allow,all}]"
+
+You can see the list of registered / connected users in
+"Virtual Hosts" >> domain name >> "users"
+
+You must manually add a new user for XpartaMuPP.
+Enter a login (use "xpartamupp" since that's what clients expect) 
+and password, then press "Add user"
+
+@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
+@@ Run XpartaMuPP - XMPP Multiplayer Game Manager @@
+@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
+You need to have python 3 installed
+$ sudo apt-get install python3
+
+You also need the SleekXmpp Python library
+Go to to github and follow their instructions
+https://github.com/fritzy/SleekXMPP
+
+Then execute the following command to run the script with default options
+$ ./source/tools/XpartaMuPP.py
+
+or rather a similar command to run a properly configured program
+$ ./source/tools/XpartaMuPP.py --domain localhost --login xpartamupp --password XXXXXX --nickname XpartaMuCC
+
+Run ./source/tools/XpartaMuPP.py -h for the full list of options
+
+If everything is fine you should see something along these lines in your console
+<<<< 
+INFO     Negotiating TLS
+INFO     Using SSL version: 3
+INFO     Node set to: xpartamupp@localhost/CC
+xpartamupp started
+<<<<
+
+Congratulations, you are running XpartaMuPP - the 0ad Multiplayer Game Manager.
+
Index: source/lobby/XmppClient.cpp
===================================================================
--- source/lobby/XmppClient.cpp	(revision 0)
+++ source/lobby/XmppClient.cpp	(revision 0)
@@ -0,0 +1,574 @@
+#include "precompiled.h"
+#include "XmppClient.h"
+#include "GameItemData.h"
+#include "precompiled.h"
+
+//debug
+#include <iostream>
+
+//Gloox
+#include <gloox/rostermanager.h>
+#include <gloox/rosteritem.h>
+#include <gloox/error.h>
+
+//Game - script
+#include "scriptinterface/ScriptInterface.h"
+
+//Configuration
+#include "ps/ConfigDB.h"
+
+using namespace gloox;
+
+//global
+XmppClient *g_XmppClient = NULL;
+
+//debug
+#if 1
+#define DbgXMPP(x)
+#else
+#define DbgXMPP(x) std::cout << x << std::endl;
+#endif
+
+//utils
+std::string StanzaErrorToString(StanzaError& err)
+{
+  std::string msg;
+#define CASE(X, Y) case X: return Y;
+  switch (err)
+  {
+    CASE(StanzaErrorBadRequest, "Bad request")
+    CASE(StanzaErrorConflict, "Player name already used")
+    CASE(StanzaErrorFeatureNotImplemented, "Feature not implemented")
+    CASE(StanzaErrorForbidden, "Forbidden")
+    CASE(StanzaErrorGone, "Recipient or server gone")
+    CASE(StanzaErrorInternalServerError, "Internal server error")
+    CASE(StanzaErrorItemNotFound, "Item not found")
+    CASE(StanzaErrorJidMalformed, "Jid malformed")
+    CASE(StanzaErrorNotAcceptable, "Not acceptable")
+    CASE(StanzaErrorNotAllowed, "Not allowed")
+    CASE(StanzaErrorNotAuthorized, "Not authorized")
+    CASE(StanzaErrorNotModified, "Not modified")
+    CASE(StanzaErrorPaymentRequired, "Payment required")
+    CASE(StanzaErrorRecipientUnavailable, "Recipient unavailable")
+    CASE(StanzaErrorRedirect, "Redirect")
+    CASE(StanzaErrorRegistrationRequired, "Registration required")
+    CASE(StanzaErrorRemoteServerNotFound, "Remote server not found")
+    CASE(StanzaErrorRemoteServerTimeout, "Remote server timeout")
+    CASE(StanzaErrorResourceConstraint, "Resource constraint")
+    CASE(StanzaErrorServiceUnavailable, "Service unavailable")
+    CASE(StanzaErrorSubscribtionRequired, "Subscribtion Required")
+    CASE(StanzaErrorUndefinedCondition, "Undefined condition")
+    CASE(StanzaErrorUnexpectedRequest, "Unexpected request")
+    CASE(StanzaErrorUnknownSender, "Unknown sender") 
+    default:
+      return "Error undefined";
+  }
+#undef CASE
+}
+
+XmppClient::XmppClient(ScriptInterface& scriptInterface, std::string sUsername, std::string sPassword, std::string sRoom, std::string sNick, bool regOpt)
+  : m_ScriptInterface(scriptInterface), _client(NULL), _mucRoom(NULL), _registration(NULL), _username(sUsername), _password(sPassword), _nick(sNick)
+{
+  // Read lobby configuration from default.cfg
+  std::string sServer;
+  std::string sXpartamupp;
+  CFG_GET_USER_VAL("lobby.server", String, sServer);
+  CFG_GET_USER_VAL("lobby.xpartamupp", String, sXpartamupp);
+
+  _xpartamuppId = sXpartamupp+std::string("@")+sServer+std::string("/CC");
+  JID clientJid(sUsername+std::string("@")+sServer+std::string("/0ad"));
+  JID roomJid(sRoom+std::string("@conference.")+sServer+std::string("/")+sNick);
+
+  // If we are connecting, use the full jid and a password
+  // If we are registering, only use the server name
+  if(!regOpt)
+    _client = new Client(clientJid, sPassword);
+  else
+    _client = new Client(sServer);
+
+  _client->registerConnectionListener( this );
+  _client->setPresence(Presence::Available, -1);
+  _client->disco()->setVersion( "TestProg", "1.0" );
+  _client->disco()->setIdentity( "client", "bot" );
+  _client->setCompression(false);
+
+  _client->registerStanzaExtension( new GameListQuery() );
+  _client->registerIqHandler( this, ExtGameListQuery);
+
+  _client->registerMessageHandler( this );
+
+  StringList ca;
+  ca.push_back( "/path/to/cacert.crt" );
+  _client->setCACerts( ca );
+
+  // Uncomment to see the raw stanzas
+  //_client->logInstance().registerLogHandler( LogLevelDebug, LogAreaAll, this );
+
+  if (!regOpt)
+  {
+    // Create a Multi User Chat Room
+    _mucRoom = new MUCRoom(_client, roomJid, this, 0);
+    // Disable the history because its anoying
+    _mucRoom->setRequestHistory(0, MUCRoom::HistoryMaxStanzas);
+  }
+  else
+  {
+    // Registration
+    _registration = new Registration( _client );
+    _registration->registerRegistrationHandler( this );
+  }
+}
+
+XmppClient::~XmppClient()
+{
+  DbgXMPP("XmppClient destroyed");
+  delete _registration;
+  delete _mucRoom;
+  delete _client;
+}
+
+// Game - script
+ScriptInterface& XmppClient::GetScriptInterface()
+{
+  return m_ScriptInterface;
+}
+
+//Network
+void XmppClient::connect()
+{
+  _client->connect(false);
+}
+
+void XmppClient::disconnect()
+{
+  _client->disconnect();
+}
+
+void XmppClient::recv()
+{
+  _client->recv(1);
+}
+
+/*
+ *  MUC Handlers
+ */
+void XmppClient::handleMUCParticipantPresence(gloox::MUCRoom*, gloox::MUCRoomParticipant participant, const gloox::Presence& presence)
+{  
+  //std::string jid = participant.jid->full();
+  std::string nick = participant.nick->resource();
+  gloox::Presence::PresenceType presenceType = presence.presence();
+  if (presenceType == Presence::Unavailable)
+  {
+    DbgXMPP(nick << " left the room");
+    m_PlayerMap.erase(nick);
+  }
+  else
+  {
+    DbgXMPP(nick << " is in the room");
+    m_PlayerMap[nick] = std::pair<std::string, int>(nick, (int)presenceType);
+  }
+  CreateSimpleMessage("system", "playerlist updated", "internal");
+}
+
+void XmppClient::handleMUCMessage( MUCRoom*, const Message& msg, bool )
+{
+  DbgXMPP(msg.from().resource() << " said " << msg.body());
+  std::string nick = msg.from().resource();
+  std::string body = msg.body();
+
+  CScriptValRooted message;
+  GetScriptInterface().Eval("({ 'type':'mucmessage'})", message);
+  GetScriptInterface().SetProperty(message.get(), "from", nick);
+  GetScriptInterface().SetProperty(message.get(), "text", body);
+  PushGuiMessage(message);
+}
+
+void XmppClient::handleMUCError(gloox::MUCRoom*, gloox::StanzaError err)
+{
+  std::string msg = StanzaErrorToString(err);
+  CreateSimpleMessage("system", msg, "error");
+}
+
+void XmppClient::handleMUCItems( MUCRoom * /*room*/, const Disco::ItemList& items )
+{
+  m_PlayerMap.clear();
+
+  // Add the received items
+  Disco::ItemList::const_iterator it = items.begin();
+  for( ; it != items.end(); ++it )
+  {
+    std::string jid = (*it)->jid().full().c_str();
+    std::string nick = (*it)->name().c_str();
+    m_PlayerMap[nick] = std::pair<std::string, int>(nick, Presence::Available);
+    //printf( "%s -- %s is an item here\n", (*it)->jid().full().c_str(), (*it)->name().c_str() );
+  }
+  CreateSimpleMessage("system", "playerlist updated", "internal");
+}
+
+/*
+ *  Log (debug) Handler
+ */
+void XmppClient::handleLog( LogLevel level, LogArea area, const std::string& message )
+{
+  std::cout << "log: level: " <<  level << ", area: " << area << ", message: " << message << std::endl;
+}
+
+/*
+ *  IQ Handler
+ */
+bool XmppClient::handleIq( const IQ& iq )
+{
+  DbgXMPP("handleIq [" << iq.tag()->xml() << "]");
+
+  if(iq.subtype() == gloox::IQ::Result)
+  {
+    const GameListQuery* q = iq.findExtension<GameListQuery>( ExtGameListQuery );
+    if(q)
+    {
+      m_GameList.clear();
+      std::list<GameItemData*>::const_iterator it = q->gameList().begin();
+      for(; it != q->gameList().end(); ++it)
+      {
+        m_GameList.push_back(**it);
+      }
+      CreateSimpleMessage("system", "gamelist updated", "internal");
+    }
+  }
+  else if(iq.subtype() == gloox::IQ::Error)
+  {
+    StanzaError err = iq.error()->error();
+    std::string msg = StanzaErrorToString(err);
+    CreateSimpleMessage("system", msg, "error");
+  }
+  else
+  {
+    CreateSimpleMessage("system", std::string("unknown subtype : ") + iq.tag()->name(), "error");
+  }
+
+  return true;
+}
+
+/*
+ *  Connection Handlers
+ */
+void XmppClient::onConnect()
+{
+  if (_mucRoom)
+  {
+    CreateSimpleMessage("system", "connected");
+    _mucRoom->join();
+    //_mucRoom->getRoomInfo();
+    _mucRoom->getRoomItems();
+    SendIqGetGameList();
+  }
+
+  if (_registration)
+  {
+    _registration->fetchRegistrationFields();
+  }
+}
+
+void XmppClient::onDisconnect( ConnectionError e )
+{
+  // Make sure we properly leave the room so than
+  // everything work if we decide to come back later
+  if (_mucRoom)
+    _mucRoom->leave();
+
+  if( e == ConnAuthenticationFailed )
+    CreateSimpleMessage("system", "authentication failed", "error");
+  else
+    CreateSimpleMessage("system", "disconnected");
+
+  m_PlayerMap.clear();
+  m_GameList.clear();
+  CreateSimpleMessage("system", "playerlist updated", "internal");
+  CreateSimpleMessage("system", "gamelist updated", "internal");
+}
+
+bool XmppClient::onTLSConnect( const CertInfo& )
+{
+  return true;
+}
+
+/*
+ *  Requests
+ */
+
+/* Request GameList from cloud */
+void XmppClient::SendIqGetGameList()
+{
+  JID xpartamuppJid(_xpartamuppId);
+
+  // Send IQ
+  IQ iq(gloox::IQ::Get, xpartamuppJid);
+  iq.addExtension( new GameListQuery() );
+  DbgXMPP("SendIqGetGameList [" << iq.tag()->xml() << "]");
+  _client->send(iq);
+}
+
+/* Register a game */
+void XmppClient::SendIqRegisterGame(CScriptVal data)
+{
+  JID xpartamuppJid(_xpartamuppId);
+
+  std::string name, mapName, mapSize, victoryCondition, nbp, tnbp;
+  GetScriptInterface().GetProperty(data.get(), "name", name);
+  GetScriptInterface().GetProperty(data.get(), "mapName", mapName);
+  GetScriptInterface().GetProperty(data.get(), "mapSize", mapSize);
+  GetScriptInterface().GetProperty(data.get(), "victoryCondition", victoryCondition);
+  GetScriptInterface().GetProperty(data.get(), "nbp", nbp);
+  GetScriptInterface().GetProperty(data.get(), "tnbp", tnbp);
+
+  // Send IQ
+  GameListQuery* g = new GameListQuery();
+  g->m_command = "register";
+  /* This "x" fake ip will be overwritten by the ip stamp XMPP module */
+  GameItemData *pItemData = new GameItemData(name, "x");
+  pItemData->m_mapName = mapName;
+  pItemData->m_mapSize = mapSize;
+  pItemData->m_victoryCondition = victoryCondition;
+  pItemData->m_nbp = nbp;
+  pItemData->m_tnbp = tnbp;
+  g->m_gameList.push_back( pItemData );
+
+  IQ iq(gloox::IQ::Set, xpartamuppJid);
+  iq.addExtension( g );
+  DbgXMPP("SendIqRegisterGame [" << iq.tag()->xml() << "]");
+  _client->send(iq);
+}
+
+/* Unregister a game */
+void XmppClient::SendIqUnregisterGame(std::string name)
+{
+  JID xpartamuppJid(_xpartamuppId);
+
+  // Send IQ
+  GameListQuery* g = new GameListQuery();
+  g->m_command = "unregister";
+  g->m_gameList.push_back( new GameItemData(name) );
+
+  IQ iq(gloox::IQ::Set, xpartamuppJid);
+  iq.addExtension( g );
+  DbgXMPP("SendIqUnregisterGame [" << iq.tag()->xml() << "]");
+  _client->send(iq);
+}
+
+/*
+ *  Registration
+ */
+void XmppClient::handleRegistrationFields( const JID& /*from*/, int fields, std::string )
+{
+  RegistrationFields vals;
+  vals.username = _username;
+  vals.password = _password;
+  _registration->createAccount( fields, vals );
+}
+
+void XmppClient::handleRegistrationResult( const JID& /*from*/, RegistrationResult result )
+{
+  if (result == gloox::RegistrationSuccess)
+  {
+    CreateSimpleMessage("system", "Registered");
+  }
+  else
+  {
+    std::string msg;
+#define CASE(X, Y) case X: msg = Y; break;
+    switch(result)
+    {
+      CASE(RegistrationNotAcceptable, "Registration not acceptable")
+      CASE(RegistrationConflict, "Registration conflict")
+      CASE(RegistrationNotAuthorized, "Registration not authorized")
+      CASE(RegistrationBadRequest, "Registration bad request")
+      CASE(RegistrationForbidden, "Registration forbidden")
+      CASE(RegistrationRequired, "Registration required")
+      CASE(RegistrationUnexpectedRequest, "Registration unexpected request")
+      CASE(RegistrationNotAllowed, "Registration not allowed")
+      default: msg = "Registration unknown error";
+    }
+#undef CASE
+    CreateSimpleMessage("system", msg, "error");
+  }
+  disconnect();
+}
+
+void XmppClient::handleAlreadyRegistered( const JID& /*from*/ )
+{
+  DbgXMPP("the account already exists");
+}
+
+void XmppClient::handleDataForm( const JID& /*from*/, const DataForm& /*form*/ )
+{
+  DbgXMPP("dataForm received");
+}
+
+void XmppClient::handleOOB( const JID& /*from*/, const OOB& /* oob */ )
+{
+  DbgXMPP("OOB registration requested");
+}
+
+/**
+  * Message
+  */
+void XmppClient::handleMessage( const Message& msg, MessageSession * /*session*/ )
+{
+  DbgXMPP("type " << msg.subtype() << ", subject " << msg.subject().c_str() 
+    << ", message " << msg.body().c_str() << ", thread id " << msg.thread().c_str());
+
+  std::string nick = msg.from().resource();
+  std::string body = msg.body();
+
+  CScriptValRooted message;
+  GetScriptInterface().Eval("({ 'type':'message'})", message);
+  GetScriptInterface().SetProperty(message.get(), "from", nick);
+  GetScriptInterface().SetProperty(message.get(), "text", body);
+  PushGuiMessage(message);
+}
+
+
+
+/* Requests from GUI */
+CScriptValRooted XmppClient::GUIGetPlayerList()
+{
+  CScriptValRooted playerList;
+  GetScriptInterface().Eval("({})", playerList);
+  for(std::map<std::string, std::pair<std::string, int> >::iterator it = m_PlayerMap.begin(); it != m_PlayerMap.end(); ++it)
+  {
+    CScriptValRooted player;
+    GetScriptInterface().Eval("({})", player);
+    GetScriptInterface().SetProperty(player.get(), "name", it->second.first.c_str());
+    GetScriptInterface().SetProperty(player.get(), "presenceType", it->second.first);
+
+    GetScriptInterface().SetProperty(playerList.get(), it->second.first.c_str(), player);
+  }
+
+  return playerList;
+}
+
+CScriptValRooted XmppClient::GUIGetGameList()
+{
+  CScriptValRooted gameList;
+  //GetScriptInterface().Eval("({})", gameList);
+  GetScriptInterface().Eval("([])", gameList);
+  for(std::list<GameItemData>::iterator it = m_GameList.begin(); it !=m_GameList.end(); ++it)
+  {
+    CScriptValRooted game;
+    GetScriptInterface().Eval("({})", game);
+
+#define ITEM(param)\
+    GetScriptInterface().SetProperty(game.get(), #param, it->m_##param .c_str());
+    ITEMS
+#undef ITEM
+
+    //GetScriptInterface().SetProperty(gameList.get(), it->m_name.c_str(), game);
+    GetScriptInterface().CallFunctionVoid(gameList.get(), "push", game);
+  }
+
+  return gameList;
+}
+
+/* Messages */
+CScriptValRooted XmppClient::GuiPollMessage()
+{
+  if (m_GuiMessageQueue.empty())
+    return CScriptValRooted();
+
+  CScriptValRooted r = m_GuiMessageQueue.front();
+  m_GuiMessageQueue.pop_front();
+  return r;
+}
+
+void XmppClient::SendMUCMessage(std::string message)
+{
+  _mucRoom->send(message);
+}
+
+void XmppClient::PushGuiMessage(const CScriptValRooted& message)
+{
+  ENSURE(!message.undefined());
+
+  m_GuiMessageQueue.push_back(message);
+}
+
+void XmppClient::CreateSimpleMessage(std::string type, std::string text, std::string level)
+{
+  CScriptValRooted message;
+  GetScriptInterface().Eval("({})", message);
+  GetScriptInterface().SetProperty(message.get(), "type", type);
+  GetScriptInterface().SetProperty(message.get(), "level", level);
+  GetScriptInterface().SetProperty(message.get(), "text", text);
+  PushGuiMessage(message);
+}
+
+/*
+ *  GameListQuery, custom IQ Stanza
+ */
+
+GameListQuery::GameListQuery( const Tag* tag )
+: StanzaExtension( ExtGameListQuery )
+{
+  if( !tag || tag->name() != "query" || tag->xmlns() != XMLNS_GAMELIST )
+    return;
+
+  const Tag* c = tag->findTag( "query/game" );
+  if (c)
+    m_command = c->cdata();
+
+  const ConstTagList& l = tag->findTagList( "query/game" );
+  ConstTagList::const_iterator it = l.begin();
+  for( ; it != l.end(); ++it )
+  {
+    GameItemData *pItem = new GameItemData();
+#define ITEM(param)\
+    const std::string param = (*it)->findAttribute( #param ); \
+    pItem->m_##param = param;
+    ITEMS
+#undef ITEM
+    m_gameList.push_back( pItem );
+  }
+}
+
+GameListQuery::~GameListQuery()
+{
+  util::clearList( m_gameList );
+}
+
+const std::string& GameListQuery::filterString() const
+{
+  static const std::string filter = "/iq/query[@xmlns='" + XMLNS_GAMELIST + "']";
+  return filter;
+}
+
+Tag* GameListQuery::tag() const
+{
+  Tag* t = new Tag( "query" );
+  t->setXmlns( XMLNS_GAMELIST );
+/*
+  RosterData::const_iterator it = m_roster.begin();
+  for( ; it != m_roster.end(); ++it )
+  t->addChild( (*it)->tag() );
+*/
+
+  // register / unregister command
+  if(!m_command.empty())
+    t->addChild(new Tag("command", m_command));
+
+  std::list<GameItemData*>::const_iterator it = m_gameList.begin();
+  for( ; it != m_gameList.end(); ++it )
+    t->addChild( (*it)->tag() );
+
+  return t;
+}
+
+StanzaExtension* GameListQuery::clone() const
+{
+  GameListQuery* q = new GameListQuery();
+
+  return q;
+}
+
+const std::list<GameItemData*>& GameListQuery::gameList() const
+{
+  return m_gameList;
+}
Index: source/lobby/GameItemData.h
===================================================================
--- source/lobby/GameItemData.h	(revision 0)
+++ source/lobby/GameItemData.h	(revision 0)
@@ -0,0 +1,46 @@
+#ifndef GAMEITEMDATA_H
+#define GAMEITEMDATA_H
+
+#include <string>
+
+#define ITEMS \
+  ITEM(name)      \
+  ITEM(ip)        \
+  ITEM(nbp)       \
+  ITEM(tnbp)      \
+  ITEM(mapName)   \
+  ITEM(mapSize)   \
+  ITEM(victoryCondition)
+
+class GameItemData
+{
+  friend class XmppClient;
+  friend class GameListQuery;
+public:
+  GameItemData(std::string name= "", std::string ip = "")
+  : m_name(name), m_ip(ip)
+  {
+  }
+
+  virtual ~GameItemData() {}
+
+  gloox::Tag* tag() const
+  {
+    gloox::Tag* i = new gloox::Tag( "game" );
+
+#define ITEM(param)\
+    i->addAttribute( #param, m_##param );
+    ITEMS
+#undef ITEM
+
+    return i;
+  }
+
+protected:
+#define ITEM(param)\
+  std::string m_##param ;
+  ITEMS
+#undef ITEM
+};
+
+#endif
Index: source/lobby/XmppClient.h
===================================================================
--- source/lobby/XmppClient.h	(revision 0)
+++ source/lobby/XmppClient.h	(revision 0)
@@ -0,0 +1,149 @@
+#ifndef XXXMPPCLIENT_H
+#define XXXMPPCLIENT_H
+
+//network
+#include <gloox/jid.h>
+#include <gloox/client.h>
+#include <gloox/mucroom.h>
+#include <gloox/message.h>
+
+#include <gloox/messagehandler.h>
+#include <gloox/presencehandler.h>
+#include <gloox/mucroomhandler.h>
+#include <gloox/loghandler.h>
+#include <gloox/connectionlistener.h>
+#include <gloox/registration.h>
+
+#include <gloox/iq.h>
+#include <gloox/iqhandler.h>
+
+//game - script
+#include <deque>
+#include "scriptinterface/ScriptVal.h"
+
+//Global Gamelist Extension
+#define ExtGameListQuery 1403
+const std::string XMLNS_GAMELIST = "jabber:iq:gamelist";
+
+//Game - script
+class ScriptInterface;
+class GameItemData;
+
+class XmppClient : public gloox::ConnectionListener, public gloox::MUCRoomHandler, public gloox::IqHandler, public gloox::LogHandler, public gloox::RegistrationHandler, public gloox::MessageHandler
+{
+private:
+  //Game - script
+  ScriptInterface& m_ScriptInterface;
+  //Components
+  gloox::Client* _client;
+  gloox::MUCRoom* _mucRoom;
+  gloox::Registration* _registration;
+  //Account infos
+  std::string _username;
+  std::string _password;
+  std::string _nick;
+  std::string _xpartamuppId;
+
+public:
+  //Basic
+  XmppClient(ScriptInterface& scriptInterface, std::string sUsername, std::string sPassword, std::string sRoom, std::string sNick, bool regOpt = false);
+  virtual ~XmppClient();
+
+  //Network
+  void connect();
+  void disconnect();
+  void recv();
+  void SendIqGetGameList();
+  void SendIqRegisterGame(CScriptVal data);
+  void SendIqUnregisterGame(std::string name);
+
+  CScriptValRooted GUIGetPlayerList();
+  CScriptValRooted GUIGetGameList();
+
+  //Script
+  ScriptInterface& GetScriptInterface();
+
+protected:
+  /* Xmpp handlers */
+  /* MUC handlers */
+  virtual void handleMUCParticipantPresence(gloox::MUCRoom*, gloox::MUCRoomParticipant, const gloox::Presence&);
+  virtual bool handleMUCRoomCreation(gloox::MUCRoom*) {return false;}
+  virtual void handleMUCSubject(gloox::MUCRoom*, const std::string&, const std::string&) {}
+  virtual void handleMUCInviteDecline(gloox::MUCRoom*, const gloox::JID&, const std::string&) {}
+  virtual void handleMUCError(gloox::MUCRoom*, gloox::StanzaError);
+  virtual void handleMUCInfo(gloox::MUCRoom*, int, const std::string&, const gloox::DataForm*) {}
+  virtual void handleMUCItems(gloox::MUCRoom*, const std::list<gloox::Disco::Item*, std::allocator<gloox::Disco::Item*> >&);
+  virtual void handleMUCMessage(gloox::MUCRoom* room, const gloox::Message& msg, bool priv);
+
+  /* Log handler */
+  virtual void handleLog(gloox::LogLevel level, gloox::LogArea area, const std::string& message);
+
+  /* ConnectionListener handlers*/
+  virtual void onConnect();
+  virtual void onDisconnect(gloox::ConnectionError e);
+  virtual bool onTLSConnect(const gloox::CertInfo& info);
+
+  /* Iq Handlers */
+  virtual bool handleIq(const gloox::IQ& iq);
+  virtual void handleIqID(const gloox::IQ&, int) {}
+
+  /* Registration Handlers */
+  virtual void handleRegistrationFields(const gloox::JID& /*from*/, int fields, std::string instructions );
+  virtual void handleRegistrationResult(const gloox::JID& /*from*/, gloox::RegistrationResult result);
+  virtual void handleAlreadyRegistered(const gloox::JID& /*from*/);
+  virtual void handleDataForm(const gloox::JID& /*from*/, const gloox::DataForm& /*form*/);
+  virtual void handleOOB(const gloox::JID& /*from*/, const gloox::OOB& oob);
+
+  /* Message Handler */
+  virtual void handleMessage(const gloox::Message& msg, gloox::MessageSession * session);
+
+  /* Messages */
+public:
+  CScriptValRooted GuiPollMessage();
+  void SendMUCMessage(std::string message);
+protected:
+  void PushGuiMessage(const CScriptValRooted& message);
+  void CreateSimpleMessage(std::string type, std::string text, std::string level = "standard");
+
+private:
+  /// Map of players
+  std::map<std::string, std::pair<std::string, int> > m_PlayerMap;
+  /// List of games
+  std::list< GameItemData > m_GameList;
+  /// Queue of messages
+  std::deque<CScriptValRooted> m_GuiMessageQueue;
+};
+
+class GameListQuery : public gloox::StanzaExtension
+{
+  friend class XmppClient;
+public:
+  GameListQuery(const gloox::Tag* tag = 0);
+
+  ~GameListQuery();
+
+  // reimplemented from StanzaExtension
+  virtual const std::string& filterString() const;
+
+  // reimplemented from StanzaExtension
+  virtual StanzaExtension* newInstance(const gloox::Tag* tag) const
+  {
+    return new GameListQuery( tag );
+  }
+
+  // reimplemented from StanzaExtension
+  virtual gloox::Tag* tag() const;
+
+  // reimplemented from StanzaExtension
+  virtual gloox::StanzaExtension* clone() const;
+
+  const std::list<GameItemData*>& gameList() const;
+
+private:
+  std::string m_command;
+  std::list<GameItemData*> m_gameList;
+};
+
+extern XmppClient *g_XmppClient;
+
+#endif // XMPPCLIENT_H
Index: source/pch/lobby/precompiled.cpp
===================================================================
--- source/pch/lobby/precompiled.cpp	(revision 0)
+++ source/pch/lobby/precompiled.cpp	(revision 0)
@@ -0,0 +1,18 @@
+/* Copyright (C) 2009 Wildfire Games.
+ * This file is part of 0 A.D.
+ *
+ * 0 A.D. is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * 0 A.D. is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with 0 A.D.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "precompiled.h"
Index: source/pch/lobby/precompiled.h
===================================================================
--- source/pch/lobby/precompiled.h	(revision 0)
+++ source/pch/lobby/precompiled.h	(revision 0)
@@ -0,0 +1,18 @@
+/* Copyright (C) 2009 Wildfire Games.
+ * This file is part of 0 A.D.
+ *
+ * 0 A.D. is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * 0 A.D. is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with 0 A.D.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "lib/precompiled.h"	// common precompiled header
Index: binaries/data/mods/public/gui/pregame/mainmenu.xml
===================================================================
--- binaries/data/mods/public/gui/pregame/mainmenu.xml	(revision 12046)
+++ binaries/data/mods/public/gui/pregame/mainmenu.xml	(working copy)
@@ -196,7 +196,7 @@
 					Matches
 					<action on="Press">
 						closeMenu();
-						Engine.PushGuiPage("page_gamesetup.xml", { type: "offline" });
+						Engine.PushGuiPage("page_gamesetup.xml", { type: "offline", source: "mainmenu" });
 					</action>
 				</object>
 
@@ -253,7 +253,7 @@
 					<action on="Press">
 						closeMenu();
 						// Open Multiplayer connection window with join option.
-						Engine.PushGuiPage("page_gamesetup_mp.xml", "join");
+						Engine.PushGuiPage("page_gamesetup_mp.xml", { multiplayerGameType: "join", source: "mainmenu" });
 					</action>
 				</object>
 
@@ -268,11 +268,26 @@
 					<action on="Press">
 						closeMenu();
 						// Open Multiplayer connection window with host option.
-						Engine.PushGuiPage("page_gamesetup_mp.xml", "host");
+						Engine.PushGuiPage("page_gamesetup_mp.xml", { multiplayerGameType: "host", source: "mainmenu" });
 					</action>
 				</object>
-			</object>
 
+				<object name="subMenuMultiplayerLobbyButton"
+					type="button"
+					style="StoneButtonFancy"
+					size="0 64 100% 92"
+					tooltip_style="pgToolTip"
+					tooltip="Launch the multiplayer lobby."
+				>
+					Game Lobby
+					<action on="Press">
+						closeMenu();
+						// Open Multiplayer game lobby.
+						Engine.PushGuiPage("page_prelobby.xml", []);
+					</action>
+				</object>
+				</object>
+
 			<!-- submenuToolsAndOptions -->
 			<object name="submenuToolsAndOptions"
 				type="image"
@@ -402,7 +417,7 @@
 					Multiplayer
 					<action on="Press">
 						closeMenu();
-						openMenu("submenuMultiplayer", (this.parent.size.top+this.size.top), (this.size.bottom-this.size.top), 2);
+						openMenu("submenuMultiplayer", (this.parent.size.top+this.size.top), (this.size.bottom-this.size.top), 3);
 					</action>
 				</object>
 
Index: binaries/data/mods/public/gui/gamesetup/gamesetup_mp.js
===================================================================
--- binaries/data/mods/public/gui/gamesetup/gamesetup_mp.js	(revision 12046)
+++ binaries/data/mods/public/gui/gamesetup/gamesetup_mp.js	(working copy)
@@ -1,33 +1,57 @@
 var g_IsConnecting = false;
 var g_GameType; // "server" or "client"
+var g_Source; // "mainmenu" or "lobby"
+var g_ServerName = "";
+var g_Name;
+var g_Ip;
 
 var g_IsRejoining = false;
 var g_GameAttributes; // used when rejoining
 var g_PlayerAssignments; // used when rejoining
 
-function init(multiplayerGameType)
+function init(attribs)
 {
-	switch (multiplayerGameType)
+        switch (attribs.multiplayerGameType)
+        {
+                case "join":
+                        getGUIObjectByName("pageJoin").hidden = false;
+                        getGUIObjectByName("pageHost").hidden = true;
+                        break;
+                case "host":
+                        getGUIObjectByName("pageJoin").hidden = true;
+                        getGUIObjectByName("pageHost").hidden = false;
+                        break;
+                default:
+                        error("Unrecognised multiplayer game type : " + attribs.multiplayerGameType);
+                        break;
+        }
+	
+	switch (attribs.source)
 	{
-	case "join":
-		getGUIObjectByName("pageJoin").hidden = false;
-		getGUIObjectByName("pageHost").hidden = true;
-		break;
-	case "host":
-		getGUIObjectByName("pageJoin").hidden = true;
-		getGUIObjectByName("pageHost").hidden = false;
-		break;
-	default:
-		error("Unrecognised multiplayer game type : " + multiplayerGameType);
-		break;
+		case "mainmenu":
+			g_Source = "mainmenu";
+			break;
+		case "lobby":
+			g_Source = "lobby";
+			getGUIObjectByName("hostServerNameWrapper").hidden = false;
+			break;
+		default:
+			error("Unrecognised source : " + attribs.source);
 	}
+
+	if (attribs.name)
+	{
+		getGUIObjectByName("hostPlayerName").caption = attribs.name;
+		getGUIObjectByName("joinPlayerName").caption = attribs.name;
+	}
+	if (attribs.ip) getGUIObjectByName("joinIP").caption = attribs.ip;
 }
 
 function cancelSetup()
 {
 	if (g_IsConnecting)
 		Engine.DisconnectNetworkGame();
-	Engine.PopGuiPage();	
+	Engine.PopGuiPage();
 }
 
 function startConnectionStatus(type)
@@ -43,6 +67,11 @@
 	if (!g_IsConnecting)
 		return;
 
+	pollAndHandleNetworkClient();
+}
+
+function pollAndHandleNetworkClient()
+{
 	while (true)
 	{
 		var message = Engine.PollNetworkClient();
@@ -119,7 +148,7 @@
 					else
 					{
 						Engine.PopGuiPage();
-						Engine.PushGuiPage("page_gamesetup.xml", { "type": g_GameType });
+						Engine.PushGuiPage("page_gamesetup.xml", { "type": g_GameType, "source": g_Source, "serverName": g_ServerName });
 						return; // don't process any more messages - leave them for the game GUI loop
 					}
 
@@ -163,7 +192,7 @@
 	}
 
 	startConnectionStatus("server");
-	// TODO: ought to do something(?) with servername
+	g_ServerName = servername;
 
 	return true;
 }
Index: binaries/data/mods/public/gui/gamesetup/gamesetup.js
===================================================================
--- binaries/data/mods/public/gui/gamesetup/gamesetup.js	(revision 12046)
+++ binaries/data/mods/public/gui/gamesetup/gamesetup.js	(working copy)
@@ -19,6 +19,12 @@
 // Is this user in control of game settings (i.e. is a network server, or offline player)
 var g_IsController;
 
+// Where this user come from ("mainmenu" or "lobby")
+var g_Source;
+
+//Server name, if user is a server, connected to the multiplayer lobby
+var g_ServerName;
+
 // Are we currently updating the GUI in response to network messages instead of user input
 // (and therefore shouldn't send further messages to the network)
 var g_IsInGuiUpdate;
@@ -69,6 +75,21 @@
 	default:
 		error("Unexpected 'type' in gamesetup init: "+attribs.type);
 	}
+
+	switch (attribs.source)
+	{
+	case "mainmenu":
+		g_Source = "mainmenu";
+		break;
+	case "lobby":
+		g_Source = "lobby";
+		break;
+	default:
+		error("Unrecognised source : " + attribs.source);
+	}
+
+        if (attribs.serverName)
+		g_ServerName = attribs.serverName;
 }
 
 // Called after the map data is loaded and cached
@@ -156,6 +177,7 @@
 				updateGameAttributes();
 			}
 		};
+		mapSize.selected = 0;
 
 		getGUIObjectByName("revealMap").onPress = function()
 		{	// Update attributes so other players can see change
@@ -281,6 +303,7 @@
 		{
 		case "disconnected":
 			Engine.DisconnectNetworkGame();
+			if (isServerOnLobby()) Engine.SendUnregisterGame(g_ServerName);
 			Engine.PopGuiPage();
 			reportDisconnect(message.reason);
 			break;
@@ -319,9 +342,15 @@
 		// Update the player list
 		g_PlayerAssignments = message.hosts;
 		updatePlayerList();
+
+		if (isServerOnLobby())
+		{
+			sendRegisterGameStanza();
+		}
 		break;
 
 	case "start":
+		if (g_Source == "lobby") Engine.StopXmppClient();
 		Engine.SwitchGuiPage("page_loading.xml", {
 			"attribs": g_GameAttributes,
 			"isNetworked" : g_IsNetworked,
@@ -942,6 +971,10 @@
 	if (g_IsNetworked)
 	{
 		Engine.SetNetworkGameAttributes(g_GameAttributes);
+		if (isServerOnLobby() && g_LoadingState >= 2)
+		{
+			sendRegisterGameStanza();
+		}
 	}
 	else
 	{
@@ -1259,3 +1292,37 @@
 	}
 	return false;
 }
+
+function isServerOnLobby()
+{
+	return (g_IsController && g_Source == "lobby") ? true : false;
+}
+
+function sendRegisterGameStanza()
+{
+	var selectedMapSize = getGUIObjectByName("mapSize").selected;
+	var selectedVictoryCondition = getGUIObjectByName("victoryCondition").selected;
+
+	var mapSize = getGUIObjectByName("mapSize").list_data[selectedMapSize];
+	var victoryCondition = getGUIObjectByName("victoryCondition").list[selectedVictoryCondition];
+
+	var numberOfPlayers = 0;
+	for (var guid in g_PlayerAssignments)
+	{	
+		numberOfPlayers++;
+	}
+        print(numberOfPlayers);
+
+	var nbp = numberOfPlayers ? numberOfPlayers : 1;
+	var tnbp = g_GameAttributes.settings.PlayerData.length;
+
+	gameData = {
+		"name":g_ServerName,
+		"mapName":g_GameAttributes.map,
+		"mapSize":mapSize,
+		"victoryCondition":victoryCondition, 
+		"nbp":nbp,
+		"tnbp":tnbp
+	};
+	Engine.SendRegisterGame(gameData);
+}
Index: binaries/data/mods/public/gui/gamesetup/gamesetup_mp.xml
===================================================================
--- binaries/data/mods/public/gui/gamesetup/gamesetup_mp.xml	(revision 12046)
+++ binaries/data/mods/public/gui/gamesetup/gamesetup_mp.xml	(working copy)
@@ -72,7 +72,7 @@
 				]]></action>
 			</object>
 
-			<object hidden="true"> <!-- TODO: restore this when the server name is actually used -->
+			<object name="hostServerNameWrapper" hidden="true">
 			<object type="text" size="0 80 200 110" style="RightLabelText">
 				Server name:
 			</object>
Index: binaries/data/mods/public/gui/gamesetup/gamesetup.xml
===================================================================
--- binaries/data/mods/public/gui/gamesetup/gamesetup.xml	(revision 12046)
+++ binaries/data/mods/public/gui/gamesetup/gamesetup.xml	(working copy)
@@ -240,12 +240,23 @@
 			style="StoneButton"
 			size="100%-164 100%-52 100%-24 100%-24"
 			tooltip_style="onscreenToolTip"
-			tooltip="Return to the main menu."
 		>
-			Main menu
+			<action on="Load"><![CDATA[
+			  if(!isServerOnLobby())
+			  {
+			    this.caption = "Main menu";
+			    this.tooltip = "Return to the main menu."
+			  }
+			  else
+			  {
+			    this.caption = "Quit";
+			    this.tooltip = "Return to the lobby."
+			  }
+			]]></action>
 			<action on="Press">
 				<![CDATA[
 					cancelSetup();
+					if(isServerOnLobby()) Engine.SendUnregisterGame(g_ServerName);
 					Engine.PopGuiPage();
 				]]>
 			</action>
Index: binaries/data/mods/public/gui/page_lobby.xml
===================================================================
--- binaries/data/mods/public/gui/page_lobby.xml	(revision 0)
+++ binaries/data/mods/public/gui/page_lobby.xml	(revision 0)
@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="utf-8"?>
+<page>
+  <include>common/setup.xml</include>
+  <include>common/styles.xml</include>
+  <include>common/sprite1.xml</include>
+
+<include>common/common_sprites.xml</include>
+<include>common/common_styles.xml</include>
+
+  <include>lobby/styles.xml</include>
+  <include>lobby/lobby.xml</include>
+</page>
Index: binaries/data/mods/public/gui/page_prelobby.xml
===================================================================
--- binaries/data/mods/public/gui/page_prelobby.xml	(revision 0)
+++ binaries/data/mods/public/gui/page_prelobby.xml	(revision 0)
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="utf-8"?>
+<page>
+  <include>common/setup.xml</include>
+  <include>common/styles.xml</include>
+  <include>common/sprite1.xml</include>
+
+<include>common/common_sprites.xml</include>
+<include>common/common_styles.xml</include>
+
+  <include>gamesetup/styles.xml</include>
+  <include>summary/sprites.xml</include>
+  <include>lobby/prelobby.xml</include>
+</page>
+
Index: binaries/data/config/default.cfg
===================================================================
--- binaries/data/config/default.cfg	(revision 12046)
+++ binaries/data/config/default.cfg	(working copy)
@@ -241,3 +241,7 @@
 joystick.camera.rotate.y = 2
 joystick.camera.zoom.in = 5
 joystick.camera.zoom.out = 4
+
+; Multiplayer lobby preferences
+lobby.server = "localhost"
+lobby.xpartamupp = "xpartamupp"
