diff --git a/CMakeLists.txt b/CMakeLists.txt
index ff44e9c262489ce7d1836357af81ce5b4c13e482..ba273a0dc656a17a61f66bf7a44891b78a2e423e 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -143,6 +143,7 @@ if(EXISTS ${MINETEST_GAME_SOURCE} AND IS_DIRECTORY ${MINETEST_GAME_SOURCE})
 	install(FILES ${MINETEST_GAME_SOURCE}/game.conf DESTINATION "${SHAREDIR}/games/minetest_game/")
 	install(FILES ${MINETEST_GAME_SOURCE}/README.txt DESTINATION "${SHAREDIR}/games/minetest_game/")
 	install(DIRECTORY ${MINETEST_GAME_SOURCE}/mods DESTINATION "${SHAREDIR}/games/minetest_game")
+	install(DIRECTORY ${MINETEST_GAME_SOURCE}/menu DESTINATION "${SHAREDIR}/games/minetest_game")
 endif()
 set(MINETEST_BUILD_GAME_SOURCE "${CMAKE_CURRENT_SOURCE_DIR}/games/build")
 if(EXISTS ${MINETEST_BUILD_GAME_SOURCE} AND IS_DIRECTORY ${MINETEST_BUILD_GAME_SOURCE})
diff --git a/README.txt b/README.txt
index 74940a1473c0d366d514715d988f14dd86f8f239..ae5003f77bf741533f38cfc10007b0eb9fbf0ed8 100644
--- a/README.txt
+++ b/README.txt
@@ -30,13 +30,17 @@ This game is not finished
 
 Default Controls
 -----------------
-- WASD: Move
-- Space: Jump
-- E: Go down
-- Shift: Sneak
-- Q: Drop item
-- I: Open inventory
-- Mouse: Turn/look
+- WASD: move
+- Space: jump/climb
+- Shift: sneak/go down
+- Q: drop item
+- I: inventory
+- Mouse: turn/look
+- Mouse left: dig/punch
+- Mouse right: place/use
+- Mouse wheel: select item
+- Esc: pause menu
+- T: chat
 - Settable in the configuration file, see the section below.
 
 Paths
@@ -277,7 +281,7 @@ the Free Software Foundation; either version 2.1 of the License, or
 This program 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.
+GNU Lesser General Public License for more details.
 
 You should have received a copy of the GNU Lesser General Public License along
 with this program; if not, write to the Free Software Foundation, Inc.,
diff --git a/builtin/features.lua b/builtin/features.lua
index 0eef2519dc22cd8231b37758543a2c40764ef023..9d00cfd996ddf3ff173a5e37f8f64269cd919e6a 100644
--- a/builtin/features.lua
+++ b/builtin/features.lua
@@ -1,11 +1,11 @@
 -- Minetest: builtin/features.lua
 
 minetest.features = {
-	"glasslike_framed" = true,
-	"nodebox_as_selectionbox" = true,
-	"chat_send_player_param3" = true,
-	"get_all_craft_recipes_works" = true,
-	"use_texture_alpha" = true,
+	glasslike_framed = true,
+	nodebox_as_selectionbox = true,
+	chat_send_player_param3 = true,
+	get_all_craft_recipes_works = true,
+	use_texture_alpha = true,
 }
 
 function minetest.has_feature(arg)
diff --git a/doc/lua_api.txt b/doc/lua_api.txt
index 597f98c2cb27bc9a5c00bbd81f0de42e8bc1a538..02ca7cba306dac78e7645e426186ef544302d254 100644
--- a/doc/lua_api.txt
+++ b/doc/lua_api.txt
@@ -120,6 +120,17 @@ depends.txt:
   List of mods that have to be loaded before loading this mod.
   A single line contains a single modname.
 
+  Optional dependencies can be defined by appending a question mark
+  to a single modname. Their meaning is that if the specified mod
+  is missing, that does not prevent this mod from being loaded.
+
+optdepends.txt:
+  An alternative way of specifying optional dependencies.
+  Like depends.txt, a single line contains a single modname.
+
+  NOTE: This file exists for compatibility purposes only and
+  support for it will be removed from the engine by the end of 2013.
+
 init.lua:
   The main Lua script. Running this script should register everything it
   wants to register. Subsequent execution depends on minetest calling the
diff --git a/doc/minetest.6 b/doc/minetest.6
index b3fdd94d938cb3f1d7f0c079b80e54b3cb2ea309..64dfdd149d2adf817d80967d5110dd2801a6054c 100644
--- a/doc/minetest.6
+++ b/doc/minetest.6
@@ -61,6 +61,9 @@ Run dedicated server
 \-\-speedtests
 Run speed tests
 .TP
+\-\-videomodes
+List available video modes
+.TP
 \-\-info
 Print more information to console
 .TP
diff --git a/games/minimal/menu/background.png b/games/minimal/menu/background.png
new file mode 100644
index 0000000000000000000000000000000000000000..e020f909d47609478325f9f6500e7dea37b81af3
Binary files /dev/null and b/games/minimal/menu/background.png differ
diff --git a/games/minimal/menu/icon.png b/games/minimal/menu/icon.png
new file mode 100644
index 0000000000000000000000000000000000000000..fa8d7cd7f7f74d46d0328eaf4c0196857e0bc89f
Binary files /dev/null and b/games/minimal/menu/icon.png differ
diff --git a/minetest.conf.example b/minetest.conf.example
index 36ef31459411ac11b1294024df568cd31e3cc973..7d56f99d80b77bc9762ab6c674c756307f18a166 100644
--- a/minetest.conf.example
+++ b/minetest.conf.example
@@ -151,6 +151,8 @@
 #crosshair_color = (255,255,255)
 # Cross alpha (opaqueness, between 0 and 255)
 #crosshair_alpha = 255
+# Sensitivity multiplier
+#mouse_sensitivity = 0.2
 # Sound settings
 #enable_sound = true
 #sound_volume = 0.7
diff --git a/src/client.cpp b/src/client.cpp
index 56505c66c07875ca525ee5ed03cb254d244bedcd..929ed0eabdab48358a9c20841b164f802e4e47cd 100644
--- a/src/client.cpp
+++ b/src/client.cpp
@@ -29,6 +29,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
 #include "mapblock.h"
 #include "settings.h"
 #include "profiler.h"
+#include "gettext.h"
 #include "log.h"
 #include "nodemetadata.h"
 #include "nodedef.h"
@@ -2555,6 +2556,9 @@ void Client::inventoryAction(InventoryAction *a)
 		Predict some local inventory changes
 	*/
 	a->clientApply(this, this);
+
+	// Remove it
+	delete a;
 }
 
 ClientActiveObject * Client::getSelectedActiveObject(
@@ -2801,7 +2805,10 @@ ClientEvent Client::getClientEvent()
 	return m_client_event_queue.pop_front();
 }
 
-void Client::afterContentReceived()
+void draw_load_screen(const std::wstring &text,
+		IrrlichtDevice* device, gui::IGUIFont* font,
+		float dtime=0 ,int percent=0, bool clouds=true);
+void Client::afterContentReceived(IrrlichtDevice *device, gui::IGUIFont* font)
 {
 	infostream<<"Client::afterContentReceived() started"<<std::endl;
 	assert(m_itemdef_received);
@@ -2836,13 +2843,23 @@ void Client::afterContentReceived()
 	if(g_settings->getBool("preload_item_visuals"))
 	{
 		verbosestream<<"Updating item textures and meshes"<<std::endl;
+		wchar_t* text = wgettext("Item textures...");
+		draw_load_screen(text,device,font,0,0);
 		std::set<std::string> names = m_itemdef->getAll();
+		size_t size = names.size();
+		size_t count = 0;
+		int percent = 0;
 		for(std::set<std::string>::const_iterator
 				i = names.begin(); i != names.end(); ++i){
 			// Asking for these caches the result
 			m_itemdef->getInventoryTexture(*i, this);
 			m_itemdef->getWieldMesh(*i, this);
+			count++;
+			percent = count*100/size;
+			if (count%50 == 0) // only update every 50 item
+				draw_load_screen(text,device,font,0,percent);
 		}
+		delete[] text;
 	}
 
 	// Start mesh update thread after setting up content definitions
diff --git a/src/client.h b/src/client.h
index 67ba6c5659f3172bdac1d47817688ad127fc3dce..f0cc5586816372c007532ef6101854491784ddca 100644
--- a/src/client.h
+++ b/src/client.h
@@ -385,7 +385,7 @@ class Client : public con::PeerHandler, public InventoryManager, public IGameDef
 	bool nodedefReceived()
 	{ return m_nodedef_received; }
 	
-	void afterContentReceived();
+	void afterContentReceived(IrrlichtDevice *device, gui::IGUIFont* font);
 
 	float getRTT(void);
 
diff --git a/src/clientserver.h b/src/clientserver.h
index cfa87ada7323aa4a9abab13eef37e534b238f09c..ee9271bbcdfe2cdfbffa4aac8ba49eea5e41de9d 100644
--- a/src/clientserver.h
+++ b/src/clientserver.h
@@ -91,10 +91,10 @@ SharedBuffer<u8> makePacket_TOCLIENT_TIME_OF_DAY(u16 time, float time_speed);
 	PROTOCOL_VERSION 19:
 		GENERIC_CMD_SET_PHYSICS_OVERRIDE
 	PROTOCOL_VERSION 20:
-		TOCLIENT_HUD_ADD
-		TOCLIENT_HUD_RM
-		TOCLIENT_HUD_CHANGE
-		TOCLIENT_HUD_BUILTIN_ENABLE
+		TOCLIENT_HUDADD
+		TOCLIENT_HUDRM
+		TOCLIENT_HUDCHANGE
+		TOCLIENT_HUD_SET_FLAGS
 */
 
 #define LATEST_PROTOCOL_VERSION 20
diff --git a/src/defaultsettings.cpp b/src/defaultsettings.cpp
index 09ec456971cd9cd3b0b6387b5950da1397508b57..f270a47aa4d175d5b36818a70313a820294065c2 100644
--- a/src/defaultsettings.cpp
+++ b/src/defaultsettings.cpp
@@ -117,6 +117,7 @@ void set_default_settings(Settings *settings)
 	settings->setDefault("selectionbox_color", "(0,0,0)");
 	settings->setDefault("crosshair_color", "(255,255,255)");
 	settings->setDefault("crosshair_alpha", "255");
+	settings->setDefault("mouse_sensitivity", "0.2");
 	settings->setDefault("enable_sound", "true");
 	settings->setDefault("sound_volume", "0.8");
 	settings->setDefault("desynchronize_mapblock_texture_animation", "true");
diff --git a/src/environment.cpp b/src/environment.cpp
index 438c9ef4f684e89c30e25719727ff23d318aa12c..e06b032f2f2d5cec3646f8a19b757f44379402f2 100644
--- a/src/environment.cpp
+++ b/src/environment.cpp
@@ -1084,8 +1084,8 @@ void ServerEnvironment::step(float dtime)
 		{
 			v3s16 p = *i;
 
-			/*infostream<<"Server: Block ("<<p.X<<","<<p.Y<<","<<p.Z
-					<<") became inactive"<<std::endl;*/
+			/* infostream<<"Server: Block " << PP(p)
+				<< " became inactive"<<std::endl; */
 			
 			MapBlock *block = m_map->getBlockNoCreateNoEx(p);
 			if(block==NULL)
@@ -1104,9 +1104,6 @@ void ServerEnvironment::step(float dtime)
 				i != blocks_added.end(); ++i)
 		{
 			v3s16 p = *i;
-			
-			/*infostream<<"Server: Block ("<<p.X<<","<<p.Y<<","<<p.Z
-					<<") became active"<<std::endl;*/
 
 			MapBlock *block = m_map->getBlockNoCreateNoEx(p);
 			if(block==NULL){
@@ -1117,6 +1114,8 @@ void ServerEnvironment::step(float dtime)
 			}
 
 			activateBlock(block);
+			/* infostream<<"Server: Block " << PP(p)
+				<< " became active"<<std::endl; */
 		}
 	}
 
@@ -1850,17 +1849,17 @@ void ServerEnvironment::deactivateFarObjects(bool force_delete)
 							<<" Forcing delete."<<std::endl;
 					force_delete = true;
 				} else {
-					u16 new_id = pending_delete ? id : 0;
 					// If static counterpart already exists, remove it first.
 					// This shouldn't happen, but happens rarely for some
 					// unknown reason. Unsuccessful attempts have been made to
 					// find said reason.
-					if(new_id && block->m_static_objects.m_active.find(new_id) != block->m_static_objects.m_active.end()){
+					if(id && block->m_static_objects.m_active.find(id) != block->m_static_objects.m_active.end()){
 						infostream<<"ServerEnv: WARNING: Performing hack #83274"
 								<<std::endl;
-						block->m_static_objects.remove(new_id);
+						block->m_static_objects.remove(id);
 					}
-					block->m_static_objects.insert(new_id, s_obj);
+					//store static data
+					block->m_static_objects.insert(0, s_obj);
 					
 					// Only mark block as modified if data changed considerably
 					if(shall_be_written)
diff --git a/src/game.cpp b/src/game.cpp
index 189003e4caa262489f84faef07eff6e1d79ba900..f63a4a400228d1cb78e99704570924a1725d7e23 100644
--- a/src/game.cpp
+++ b/src/game.cpp
@@ -395,11 +395,14 @@ PointedThing getPointedThing(Client *client, v3f player_position,
 /*
 	Draws a screen with a single text on it.
 	Text will be removed when the screen is drawn the next time.
+	Additionally, a progressbar can be drawn when percent is set between 0 and 100.
 */
 /*gui::IGUIStaticText **/
 void draw_load_screen(const std::wstring &text,
-		video::IVideoDriver* driver, gui::IGUIFont* font)
+		IrrlichtDevice* device, gui::IGUIFont* font,
+		float dtime=0 ,int percent=0, bool clouds=true)
 {
+	video::IVideoDriver* driver = device->getVideoDriver();
 	v2u32 screensize = driver->getScreenSize();
 	const wchar_t *loadingtext = text.c_str();
 	core::vector2d<u32> textsize_u = font->getDimension(loadingtext);
@@ -411,7 +414,30 @@ void draw_load_screen(const std::wstring &text,
 			loadingtext, textrect, false, false);
 	guitext->setTextAlignment(gui::EGUIA_CENTER, gui::EGUIA_UPPERLEFT);
 
-	driver->beginScene(true, true, video::SColor(255,0,0,0));
+	bool cloud_menu_background = clouds && g_settings->getBool("menu_clouds");
+	if (cloud_menu_background)
+	{
+		g_menuclouds->step(dtime*3);
+		g_menuclouds->render();
+		driver->beginScene(true, true, video::SColor(255,140,186,250));
+		g_menucloudsmgr->drawAll();
+	}
+	else
+		driver->beginScene(true, true, video::SColor(255,0,0,0));
+	if (percent >= 0 && percent <= 100) // draw progress bar
+	{
+		core::vector2d<s32> barsize(256,32);
+		core::rect<s32> barrect(center-barsize/2, center+barsize/2);
+		driver->draw2DRectangle(video::SColor(255,255,255,255),barrect, NULL); // border
+		driver->draw2DRectangle(video::SColor(255,64,64,64), core::rect<s32> (
+				barrect.UpperLeftCorner+1,
+				barrect.LowerRightCorner-1), NULL); // black inside the bar
+		driver->draw2DRectangle(video::SColor(255,128,128,128), core::rect<s32> (
+				barrect.UpperLeftCorner+1,
+				core::vector2d<s32>(
+					barrect.LowerRightCorner.X-(barsize.X-1)+percent*(barsize.X-2)/100,
+					barrect.LowerRightCorner.Y-1)), NULL); // the actual progress
+	}
 	guienv->drawAll();
 	driver->endScene();
 	
@@ -789,6 +815,67 @@ class GameGlobalShaderConstantSetter : public IShaderConstantSetter
 	}
 };
 
+void nodePlacementPrediction(Client &client,
+		const ItemDefinition &playeritem_def,
+		v3s16 nodepos, v3s16 neighbourpos)
+{
+	std::string prediction = playeritem_def.node_placement_prediction;
+	INodeDefManager *nodedef = client.ndef();
+	ClientMap &map = client.getEnv().getClientMap();
+
+	if(prediction != "" && !nodedef->get(map.getNode(nodepos)).rightclickable)
+	{
+		verbosestream<<"Node placement prediction for "
+				<<playeritem_def.name<<" is "
+				<<prediction<<std::endl;
+		v3s16 p = neighbourpos;
+		// Place inside node itself if buildable_to
+		try{
+			MapNode n_under = map.getNode(nodepos);
+			if(nodedef->get(n_under).buildable_to)
+				p = nodepos;
+			else if (!nodedef->get(map.getNode(p)).buildable_to)
+				return;
+		}catch(InvalidPositionException &e){}
+		// Find id of predicted node
+		content_t id;
+		bool found = nodedef->getId(prediction, id);
+		if(!found){
+			errorstream<<"Node placement prediction failed for "
+					<<playeritem_def.name<<" (places "
+					<<prediction
+					<<") - Name not known"<<std::endl;
+			return;
+		}
+		// Predict param2
+		u8 param2 = 0;
+		if(nodedef->get(id).param_type_2 == CPT2_WALLMOUNTED){
+			v3s16 dir = nodepos - neighbourpos;
+			if(abs(dir.Y) > MYMAX(abs(dir.X), abs(dir.Z))){
+				param2 = dir.Y < 0 ? 1 : 0;
+			} else if(abs(dir.X) > abs(dir.Z)){
+				param2 = dir.X < 0 ? 3 : 2;
+			} else {
+				param2 = dir.Z < 0 ? 5 : 4;
+			}
+		}
+		// TODO: Facedir prediction
+		// TODO: If predicted node is in attached_node group, check attachment
+		// Add node to client map
+		MapNode n(id, 0, param2);
+		try{
+			// This triggers the required mesh update too
+			client.addNode(p, n);
+		}catch(InvalidPositionException &e){
+			errorstream<<"Node placement prediction failed for "
+					<<playeritem_def.name<<" (places "
+					<<prediction
+					<<") - Position not loaded"<<std::endl;
+		}
+	}
+}
+
+
 void the_game(
 	bool &kill,
 	bool random_input,
@@ -821,7 +908,11 @@ void the_game(
 		Draw "Loading" screen
 	*/
 
-	draw_load_screen(L"Loading...", driver, font);
+	{
+		wchar_t* text = wgettext("Loading...");
+		draw_load_screen(text, device, font,0,0);
+		delete[] text;
+	}
 	
 	// Create texture source
 	IWritableTextureSource *tsrc = createTextureSource(device);
@@ -878,7 +969,9 @@ void the_game(
 	*/
 
 	if(address == ""){
-		draw_load_screen(L"Creating server...", driver, font);
+		wchar_t* text = wgettext("Creating server....");
+		draw_load_screen(text, device, font,0,25);
+		delete[] text;
 		infostream<<"Creating server"<<std::endl;
 		server = new Server(map_dir, configpath, gamespec,
 				simple_singleplayer_mode);
@@ -891,7 +984,11 @@ void the_game(
 		Create client
 	*/
 
-	draw_load_screen(L"Creating client...", driver, font);
+	{
+		wchar_t* text = wgettext("Creating client...");
+		draw_load_screen(text, device, font,0,50);
+		delete[] text;
+	}
 	infostream<<"Creating client"<<std::endl;
 	
 	MapDrawControl draw_control;
@@ -901,8 +998,12 @@ void the_game(
 	
 	// Client acts as our GameDef
 	IGameDef *gamedef = &client;
-			
-	draw_load_screen(L"Resolving address...", driver, font);
+	
+	{
+		wchar_t* text = wgettext("Resolving address...");
+		draw_load_screen(text, device, font,0,75);
+		delete[] text;
+	}
 	Address connect_address(0,0,0,0, port);
 	try{
 		if(address == "")
@@ -934,15 +1035,26 @@ void the_game(
 	bool could_connect = false;
 	bool connect_aborted = false;
 	try{
-		float frametime = 0.033;
 		float time_counter = 0.0;
 		input->clear();
+		float fps_max = g_settings->getFloat("fps_max");
+		bool cloud_menu_background = g_settings->getBool("menu_clouds");
+		u32 lasttime = device->getTimer()->getTime();
 		while(device->run())
 		{
+			f32 dtime=0; // in seconds
+			if (cloud_menu_background) {
+				u32 time = device->getTimer()->getTime();
+				if(time > lasttime)
+					dtime = (time - lasttime) / 1000.0;
+				else
+					dtime = 0;
+				lasttime = time;
+			}
 			// Update client and server
-			client.step(frametime);
+			client.step(dtime);
 			if(server != NULL)
-				server->step(frametime);
+				server->step(dtime);
 			
 			// End condition
 			if(client.connectedAndInitialized()){
@@ -963,15 +1075,37 @@ void the_game(
 			}
 			
 			// Display status
-			std::wostringstream ss;
-			ss<<L"Connecting to server... (press Escape to cancel)\n";
-			std::wstring animation = L"/-\\|";
-			ss<<animation[(int)(time_counter/0.2)%4];
-			draw_load_screen(ss.str(), driver, font);
+			{
+				wchar_t* text = wgettext("Connecting to server...");
+				draw_load_screen(text, device, font, dtime, 100);
+				delete[] text;
+			}
 			
-			// Delay a bit
-			sleep_ms(1000*frametime);
-			time_counter += frametime;
+			// On some computers framerate doesn't seem to be
+			// automatically limited
+			if (cloud_menu_background) {
+				// Time of frame without fps limit
+				float busytime;
+				u32 busytime_u32;
+				// not using getRealTime is necessary for wine
+				u32 time = device->getTimer()->getTime();
+				if(time > lasttime)
+					busytime_u32 = time - lasttime;
+				else
+					busytime_u32 = 0;
+				busytime = busytime_u32 / 1000.0;
+
+				// FPS limiter
+				u32 frametime_min = 1000./fps_max;
+
+				if(busytime_u32 < frametime_min) {
+					u32 sleeptime = frametime_min - busytime_u32;
+					device->sleep(sleeptime);
+				}
+			} else {
+				sleep_ms(25);
+			}
+			time_counter += dtime;
 		}
 	}
 	catch(con::PeerNotFoundException &e)
@@ -995,15 +1129,26 @@ void the_game(
 	bool got_content = false;
 	bool content_aborted = false;
 	{
-		float frametime = 0.033;
 		float time_counter = 0.0;
 		input->clear();
+		float fps_max = g_settings->getFloat("fps_max");
+		bool cloud_menu_background = g_settings->getBool("menu_clouds");
+		u32 lasttime = device->getTimer()->getTime();
 		while(device->run())
 		{
+			f32 dtime=0; // in seconds
+			if (cloud_menu_background) {
+				u32 time = device->getTimer()->getTime();
+				if(time > lasttime)
+					dtime = (time - lasttime) / 1000.0;
+				else
+					dtime = 0;
+				lasttime = time;
+			}
 			// Update client and server
-			client.step(frametime);
+			client.step(dtime);
 			if(server != NULL)
-				server->step(frametime);
+				server->step(dtime);
 			
 			// End condition
 			if(client.texturesReceived() &&
@@ -1025,21 +1170,52 @@ void the_game(
 			}
 			
 			// Display status
-			std::wostringstream ss;
-			ss<<L"Waiting content... (press Escape to cancel)\n";
+			std::ostringstream ss;
+			int progress=0;
+			if (!client.itemdefReceived())
+			{
+				ss << "Item definitions...";
+				progress = 0;
+			}
+			else if (!client.nodedefReceived())
+			{
+				ss << "Node definitions...";
+				progress = 25;
+			}
+			else
+			{
+				ss << "Media...";
+				progress = 50+client.mediaReceiveProgress()*50+0.5;
+			}
+			wchar_t* text = wgettext(ss.str().c_str());
+			draw_load_screen(text, device, font, dtime, progress);
+			delete[] text;
+			
+			// On some computers framerate doesn't seem to be
+			// automatically limited
+			if (cloud_menu_background) {
+				// Time of frame without fps limit
+				float busytime;
+				u32 busytime_u32;
+				// not using getRealTime is necessary for wine
+				u32 time = device->getTimer()->getTime();
+				if(time > lasttime)
+					busytime_u32 = time - lasttime;
+				else
+					busytime_u32 = 0;
+				busytime = busytime_u32 / 1000.0;
 
-			ss<<(client.itemdefReceived()?L"[X]":L"[  ]");
-			ss<<L" Item definitions\n";
-			ss<<(client.nodedefReceived()?L"[X]":L"[  ]");
-			ss<<L" Node definitions\n";
-			ss<<L"["<<(int)(client.mediaReceiveProgress()*100+0.5)<<L"%] ";
-			ss<<L" Media\n";
+				// FPS limiter
+				u32 frametime_min = 1000./fps_max;
 
-			draw_load_screen(ss.str(), driver, font);
-			
-			// Delay a bit
-			sleep_ms(1000*frametime);
-			time_counter += frametime;
+				if(busytime_u32 < frametime_min) {
+					u32 sleeptime = frametime_min - busytime_u32;
+					device->sleep(sleeptime);
+				}
+			} else {
+				sleep_ms(25);
+			}
+			time_counter += dtime;
 		}
 	}
 
@@ -1056,7 +1232,7 @@ void the_game(
 		After all content has been received:
 		Update cached textures, meshes and materials
 	*/
-	client.afterContentReceived();
+	client.afterContentReceived(device,font);
 
 	/*
 		Create the camera node
@@ -1885,7 +2061,8 @@ void the_game(
 				if(input->isKeyDown(irr::KEY_RIGHT))
 					dx += dtime * keyspeed;*/
 				
-				float d = 0.2;
+				float d = g_settings->getFloat("mouse_sensitivity");
+				d = rangelim(d, 0.01, 100.0);
 				camera_yaw -= dx*d;
 				camera_pitch += dy*d;
 				if(camera_pitch < -89.5) camera_pitch = -89.5;
@@ -2198,17 +2375,15 @@ void the_game(
 			- Can it point to liquids?
 		*/
 		ItemStack playeritem;
-		bool playeritem_usable = false;
-		bool playeritem_liquids_pointable = false;
 		{
 			InventoryList *mlist = local_inventory.getList("main");
 			if(mlist != NULL)
 			{
 				playeritem = mlist->getItem(client.getPlayerItem());
-				playeritem_usable = playeritem.getDefinition(itemdef).usable;
-				playeritem_liquids_pointable = playeritem.getDefinition(itemdef).liquids_pointable;
 			}
 		}
+		const ItemDefinition &playeritem_def =
+				playeritem.getDefinition(itemdef);
 		ToolCapabilities playeritem_toolcap =
 				playeritem.getToolCapabilities(itemdef);
 		
@@ -2267,7 +2442,7 @@ void the_game(
 				// input
 				&client, player_position, camera_direction,
 				camera_position, shootline, d,
-				playeritem_liquids_pointable, !ldown_for_dig,
+				playeritem_def.liquids_pointable, !ldown_for_dig,
 				// output
 				hilightboxes,
 				selected_object);
@@ -2327,7 +2502,7 @@ void the_game(
 		else
 			repeat_rightclick_timer = 0;
 
-		if(playeritem_usable && input->getLeftState())
+		if(playeritem_def.usable && input->getLeftState())
 		{
 			if(input->getLeftClicked())
 				client.interact(4, pointed);
@@ -2534,46 +2709,13 @@ void the_game(
 					
 					// If the wielded item has node placement prediction,
 					// make that happen
-					const ItemDefinition &def =
-							playeritem.getDefinition(itemdef);
-					if(def.node_placement_prediction != ""
-							&& !nodedef->get(map.getNode(nodepos)).rightclickable)
-					do{ // breakable
-						verbosestream<<"Node placement prediction for "
-								<<playeritem.name<<" is "
-								<<def.node_placement_prediction<<std::endl;
-						v3s16 p = neighbourpos;
-						// Place inside node itself if buildable_to
-						try{
-							MapNode n_under = map.getNode(nodepos);
-							if(nodedef->get(n_under).buildable_to)
-								p = nodepos;
-						}catch(InvalidPositionException &e){}
-						// Find id of predicted node
-						content_t id;
-						bool found =
-							nodedef->getId(def.node_placement_prediction, id);
-						if(!found){
-							errorstream<<"Node placement prediction failed for "
-									<<playeritem.name<<" (places "
-									<<def.node_placement_prediction
-									<<") - Name not known"<<std::endl;
-							break;
-						}
-						MapNode n(id);
-						try{
-							// This triggers the required mesh update too
-							client.addNode(p, n);
-						}catch(InvalidPositionException &e){
-							errorstream<<"Node placement prediction failed for "
-									<<playeritem.name<<" (places "
-									<<def.node_placement_prediction
-									<<") - Position not loaded"<<std::endl;
-						}
-					}while(0);
+					nodePlacementPrediction(client,
+							playeritem_def,
+							nodepos, neighbourpos);
 					
 					// Read the sound
-					soundmaker.m_player_rightpunch_sound = def.sound_place;
+					soundmaker.m_player_rightpunch_sound =
+							playeritem_def.sound_place;
 				}
 			}
 		}
@@ -3193,6 +3335,8 @@ void the_game(
 		clouds->drop();
 	if (gui_chat_console)
 		gui_chat_console->drop();
+	if (sky)
+		sky->drop();
 	clear_particles();
 	
 	/*
@@ -3201,7 +3345,9 @@ void the_game(
 	*/
 	{
 		/*gui::IGUIStaticText *gui_shuttingdowntext = */
-		draw_load_screen(L"Shutting down stuff...", driver, font);
+		wchar_t* text = wgettext("Shutting down stuff...");
+		draw_load_screen(text, device, font, 0, -1, false);
+		delete[] text;
 		/*driver->beginScene(true, true, video::SColor(255,0,0,0));
 		guienv->drawAll();
 		driver->endScene();
diff --git a/src/guiChatConsole.cpp b/src/guiChatConsole.cpp
index ec23648f86cad4d155d76eda76ed672fc39c6ba7..f31e599dc7eeb6f017a4d3c567e4346eb3d4acfa 100644
--- a/src/guiChatConsole.cpp
+++ b/src/guiChatConsole.cpp
@@ -121,6 +121,7 @@ GUIChatConsole::GUIChatConsole(
 
 GUIChatConsole::~GUIChatConsole()
 {
+	delete m_font;
 }
 
 void GUIChatConsole::openConsole(f32 height)
diff --git a/src/guiConfigureWorld.cpp b/src/guiConfigureWorld.cpp
index b2debfbd2d5afcc6a3f57cc202324c0cdeceb62e..f94ed7d17b4913cfb05259391fe3e5f79a07f78a 100644
--- a/src/guiConfigureWorld.cpp
+++ b/src/guiConfigureWorld.cpp
@@ -407,14 +407,26 @@ bool GUIConfigureWorld::OnEvent(const SEvent& event)
 				delete[] text;
 				menu->drop();
 
-				ModConfiguration modconf(m_wspec.path);
-				if(!modconf.isConsistent())
+				try
 				{
-					wchar_t* text = wgettext("Warning: Configuration not consistent.  ");
+					ModConfiguration modconf(m_wspec.path);
+					if(!modconf.isConsistent())
+					{
+						wchar_t* text = wgettext("Warning: Configuration not consistent.  ");
+						GUIMessageMenu *menu =
+							new GUIMessageMenu(Environment, Parent, -1, m_menumgr, 
+										 text );
+						delete[] text;
+						menu->drop();
+					}
+				}
+				catch(ModError &err)
+				{
+					errorstream<<err.what()<<std::endl;
+					std::wstring text = narrow_to_wide(err.what()) + wgettext("\nCheck debug.txt for details.");
 					GUIMessageMenu *menu =
 						new GUIMessageMenu(Environment, Parent, -1, m_menumgr, 
-										 text );
-					delete[] text;
+									 text );
 					menu->drop();
 				}
 
diff --git a/src/guiCreateWorld.cpp b/src/guiCreateWorld.cpp
index 09b18fb3c7e690708294c31cdeecd8f8522f5365..caa884bc05e7beb36c5cc0e2e4d87946686fec86 100644
--- a/src/guiCreateWorld.cpp
+++ b/src/guiCreateWorld.cpp
@@ -42,13 +42,22 @@ GUICreateWorld::GUICreateWorld(gui::IGUIEnvironment* env,
 		gui::IGUIElement* parent, s32 id,
 		IMenuManager *menumgr,
 		CreateWorldDest *dest,
-		const std::vector<SubgameSpec> &games
+		const std::vector<SubgameSpec> &games,
+		const std::string &initial_game
 ):
 	GUIModalMenu(env, parent, id, menumgr),
 	m_dest(dest),
-	m_games(games)
+	m_games(games),
+	m_initial_game_i(0)
 {
 	assert(games.size() > 0);
+
+	for(size_t i=0; i<games.size(); i++){
+		if(games[i].id == initial_game){
+			m_initial_game_i = i;
+			break;
+		}
+	}
 }
 
 GUICreateWorld::~GUICreateWorld()
@@ -151,7 +160,7 @@ void GUICreateWorld::regenerateGui(v2u32 screensize)
 			os<<L"]";
 			e->addItem(os.str().c_str());
 		}
-		e->setSelected(0);
+		e->setSelected(m_initial_game_i);
 	}
 	changeCtype("");
 	{
diff --git a/src/guiCreateWorld.h b/src/guiCreateWorld.h
index d9bc3638a86a60da244ff79e1ce223d7f56bd071..2765dc2bd523c20f5b38531b633b5f7fc2071a5d 100644
--- a/src/guiCreateWorld.h
+++ b/src/guiCreateWorld.h
@@ -38,7 +38,8 @@ class GUICreateWorld : public GUIModalMenu
 			gui::IGUIElement* parent, s32 id,
 			IMenuManager *menumgr,
 			CreateWorldDest *dest,
-			const std::vector<SubgameSpec> &games);
+			const std::vector<SubgameSpec> &games,
+			const std::string &initial_game);
 	~GUICreateWorld();
 	
 	void removeChildren();
@@ -56,6 +57,7 @@ class GUICreateWorld : public GUIModalMenu
 private:
 	CreateWorldDest *m_dest;
 	std::vector<SubgameSpec> m_games;
+	int m_initial_game_i;
 };
 
 #endif
diff --git a/src/guiMainMenu.cpp b/src/guiMainMenu.cpp
index 06e7cdff75a02e5e9b463b8e00ac54ff5114c4cf..36685db847acc0ed8a2efcd1e34d1a19ed200b43 100644
--- a/src/guiMainMenu.cpp
+++ b/src/guiMainMenu.cpp
@@ -53,12 +53,12 @@ const wchar_t *contrib_core_strs[] = {
 	L"Ilya Zhuravlev (thexyz) <xyz@minetest.net>",
 	L"Lisa Milne (darkrose) <lisa@ltmnet.com>",
 	L"Maciej Kasatkin (RealBadAngel) <mk@realbadangel.pl>",
-	L"proller <proler@gmail.com>"
+	L"proller <proler@gmail.com>",
+	L"sfan5 <sfan5@live.de>"
 };
 
 const wchar_t *contrib_active_strs[] = {
 	L"kahrl <kahrl@gmx.net>",
-	L"sfan5 <sfan5@live.de>",
 	L"sapier <sapier@gmx.net>",
 	L"Vanessa Ezekowitz (VanessaE) <vanessaezekowitz@gmail.com>",
 	L"Jurgen Doser (doserj) <jurgen.doser@gmail.com>",
@@ -162,15 +162,8 @@ enum
 	GUI_ID_SERVERLIST_TOGGLE,
 	GUI_ID_SERVERLIST_DELETE,
 	GUI_ID_SERVERLIST_TITLE,
-};
-
-enum
-{
-	TAB_SINGLEPLAYER=0,
-	TAB_MULTIPLAYER,
-	TAB_ADVANCED,
-	TAB_SETTINGS,
-	TAB_CREDITS
+	GUI_ID_GAME_BUTTON_FIRST = 130,
+	GUI_ID_GAME_BUTTON_MAX = 150,
 };
 
 GUIMainMenu::GUIMainMenu(gui::IGUIEnvironment* env,
@@ -255,8 +248,13 @@ void GUIMainMenu::regenerateGui(v2u32 screensize)
 	{
 		core::rect<s32> rect(0, 0, size.X, 40);
 		rect += v2s32(4, 0);
-		Environment->addStaticText(narrow_to_wide(
-				"Minetest " VERSION_STRING).c_str(),
+		std::string t = "Minetest " VERSION_STRING;
+		if(m_data->selected_game_name != "" &&
+				m_data->selected_tab == TAB_SINGLEPLAYER){
+			t += "/";
+			t += m_data->selected_game_name;
+		}
+		Environment->addStaticText(narrow_to_wide(t).c_str(),
 				rect, false, true, this, -1);
 	}
 
@@ -342,11 +340,17 @@ void GUIMainMenu::regenerateGui(v2u32 screensize)
 			gui::IGUIListBox *e = Environment->addListBox(rect, this,
 					GUI_ID_WORLD_LISTBOX);
 			e->setDrawBackground(true);
-			for(std::vector<WorldSpec>::const_iterator i = m_data->worlds.begin();
-					i != m_data->worlds.end(); i++){
-				e->addItem(narrow_to_wide(i->name+" ["+i->gameid+"]").c_str());
+			m_world_indices.clear();
+			for(size_t wi = 0; wi < m_data->worlds.size(); wi++){
+				const WorldSpec &spec = m_data->worlds[wi];
+				if(spec.gameid == m_data->selected_game){
+					//e->addItem(narrow_to_wide(spec.name+" ["+spec.gameid+"]").c_str());
+					e->addItem(narrow_to_wide(spec.name).c_str());
+					m_world_indices.push_back(wi);
+					if(m_data->selected_world == (int)wi)
+						e->setSelected(m_world_indices.size()-1);
+				}
 			}
-			e->setSelected(m_data->selected_world);
 			Environment->setFocus(e);
 		}
 		// Delete world button
@@ -416,6 +420,26 @@ void GUIMainMenu::regenerateGui(v2u32 screensize)
 			delete[] text;
 		}
 		changeCtype("C");
+
+		/* Add game selection buttons */
+		video::IVideoDriver* driver = Environment->getVideoDriver();
+		for(size_t i=0; i<m_data->games.size(); i++){
+			const SubgameSpec *spec = &m_data->games[i];
+			v2s32 p(8 + i*(48+8), screensize.Y - (48+8));
+			core::rect<s32> rect(0, 0, 48, 48);
+			rect += p;
+			video::ITexture *bgtexture = NULL;
+			if(spec->menuicon_path != "")
+				bgtexture = driver->getTexture(spec->menuicon_path.c_str());
+			gui::IGUIButton *b = Environment->addButton(rect, this,
+					GUI_ID_GAME_BUTTON_FIRST+i, narrow_to_wide(wrap_rows(spec->id, 4)).c_str());
+			if(bgtexture){
+				b->setImage(bgtexture);
+				b->setText(L"");
+				b->setDrawBorder(false);
+				b->setUseAlphaChannel(true);
+			}
+		}
 	}
 	else if(m_data->selected_tab == TAB_MULTIPLAYER)
 	{
@@ -471,16 +495,20 @@ void GUIMainMenu::regenerateGui(v2u32 screensize)
 				{
 					core::rect<s32> rect(0, 0, 390, 20);
 					rect += m_topleft_client + v2s32(50, 10);
-					Environment->addStaticText(wgettext("Favorites:"),
+					wchar_t* text = wgettext("Favorites:");
+					Environment->addStaticText(text,
 						rect, false, true, this, GUI_ID_SERVERLIST_TITLE);
+					delete[] text;
 				}
 			} else {
 				m_data->servers = ServerList::getOnline();
 				{
 					core::rect<s32> rect(0, 0, 390, 20);
 					rect += m_topleft_client + v2s32(50, 10);
-					Environment->addStaticText(wgettext("Public Server List:"),
+					wchar_t* text = wgettext("Public Server List:");
+					Environment->addStaticText(text,
 						rect, false, true, this, GUI_ID_SERVERLIST_TITLE);
+					delete[] text;
 				}
 			}
 #else
@@ -488,8 +516,10 @@ void GUIMainMenu::regenerateGui(v2u32 screensize)
 			{
 				core::rect<s32> rect(0, 0, 390, 20);
 				rect += m_topleft_client + v2s32(50, 10);
-				Environment->addStaticText(wgettext("Favorites:"),
+				wchar_t* text = wgettext("Favorites:");
+				Environment->addStaticText(text,
 					rect, false, true, this, GUI_ID_SERVERLIST_TITLE);
+				delete[] text;
 			}
 #endif
 			updateGuiServerList();
@@ -714,9 +744,11 @@ void GUIMainMenu::regenerateGui(v2u32 screensize)
 			gui::IGUIListBox *e = Environment->addListBox(rect, this,
 					GUI_ID_WORLD_LISTBOX);
 			e->setDrawBackground(true);
-			for(std::vector<WorldSpec>::const_iterator i = m_data->worlds.begin();
-					i != m_data->worlds.end(); i++){
-				e->addItem(narrow_to_wide(i->name+" ["+i->gameid+"]").c_str());
+			m_world_indices.clear();
+			for(size_t wi = 0; wi < m_data->worlds.size(); wi++){
+				const WorldSpec &spec = m_data->worlds[wi];
+				e->addItem(narrow_to_wide(spec.name+" ["+spec.gameid+"]").c_str());
+				m_world_indices.push_back(wi);
 			}
 			e->setSelected(m_data->selected_world);
 		}
@@ -909,7 +941,9 @@ void GUIMainMenu::drawMenu()
 	if (!skin)
 		return;
 	video::IVideoDriver* driver = Environment->getVideoDriver();
-	
+
+	/* Draw menu background */
+
 	/*video::SColor bgcolor(140,0,0,0);
 	driver->draw2DRectangle(bgcolor, AbsoluteRect, &AbsoluteClippingRect);*/
 
@@ -976,6 +1010,8 @@ void GUIMainMenu::drawMenu()
 		}
 	}
 
+	/* Draw UI elements */
+
 	gui::IGUIElement::draw();
 }
 
@@ -1100,8 +1136,13 @@ void GUIMainMenu::readInput(MainMenuData *dst)
 
 	{
 		gui::IGUIElement *e = getElementFromId(GUI_ID_WORLD_LISTBOX);
-		if(e != NULL && e->getType() == gui::EGUIET_LIST_BOX)
-			dst->selected_world = ((gui::IGUIListBox*)e)->getSelected();
+		if(e != NULL && e->getType() == gui::EGUIET_LIST_BOX){
+			int list_i = ((gui::IGUIListBox*)e)->getSelected();
+			if(list_i == -1)
+				dst->selected_world = -1;
+			else
+				dst->selected_world = m_world_indices[list_i];
+		}
 	}
 	{
 		ServerListSpec server =
@@ -1221,7 +1262,7 @@ bool GUIMainMenu::OnEvent(const SEvent& event)
 				return true;
 			}
 			case GUI_ID_CREATE_WORLD_BUTTON: {
-				std::vector<SubgameSpec> games = getAvailableGames();
+				const std::vector<SubgameSpec> &games = m_data->games;
 				if(games.size() == 0){
 					wchar_t* text = wgettext("Cannot create world: No games found");
 					GUIMessageMenu *menu = new GUIMessageMenu(env, parent,
@@ -1232,7 +1273,7 @@ bool GUIMainMenu::OnEvent(const SEvent& event)
 				} else {
 					CreateWorldDest *dest = new CreateWorldDestMainMenu(this);
 					GUICreateWorld *menu = new GUICreateWorld(env, parent, -1,
-							menumgr, dest, games);
+							menumgr, dest, games, m_data->selected_game);
 					menu->drop();
 				}
 				return true;
@@ -1308,6 +1349,16 @@ bool GUIMainMenu::OnEvent(const SEvent& event)
 			}
 			#endif
 			}
+			/* Game buttons */
+			int eid = event.GUIEvent.Caller->getID();
+			if(eid >= GUI_ID_GAME_BUTTON_FIRST &&
+					eid <= GUI_ID_GAME_BUTTON_MAX){
+				m_data->selected_game =
+						m_data->games[eid - GUI_ID_GAME_BUTTON_FIRST].id;
+				m_data->selected_game_name =
+						m_data->games[eid - GUI_ID_GAME_BUTTON_FIRST].name;
+				regenerateGui(m_screensize_old);
+			}
 		}
 		if(event.GUIEvent.EventType==gui::EGET_EDITBOX_ENTER)
 		{
@@ -1318,9 +1369,11 @@ bool GUIMainMenu::OnEvent(const SEvent& event)
 				readInput(&cur);
 				if (getTab() == TAB_MULTIPLAYER && cur.address == L"")
 				{
+					wchar_t* text = wgettext("Address required.");
 					(new GUIMessageMenu(env, parent, -1, menumgr,
-							wgettext("Address required."))
+							text)
 							)->drop();
+					delete[] text;
 					return true;
 				}
 				acceptInput();
@@ -1328,6 +1381,10 @@ bool GUIMainMenu::OnEvent(const SEvent& event)
 				return true;
 			}
 		}
+		if(event.GUIEvent.EventType==gui::EGET_LISTBOX_CHANGED)
+		{
+			readInput(m_data);
+		}
 		if(event.GUIEvent.EventType==gui::EGET_LISTBOX_SELECTED_AGAIN)
 		{
 			switch(event.GUIEvent.Caller->getID())
diff --git a/src/guiMainMenu.h b/src/guiMainMenu.h
index a594ccd418bac8c2b9a052b5740ab6ccc1245440..8697344c84e763b493daa0b76e4a50951981a36b 100644
--- a/src/guiMainMenu.h
+++ b/src/guiMainMenu.h
@@ -34,11 +34,22 @@ enum {
 	SERVERLIST_PUBLIC,
 };
 
+enum
+{
+	TAB_SINGLEPLAYER=0,
+	TAB_MULTIPLAYER,
+	TAB_ADVANCED,
+	TAB_SETTINGS,
+	TAB_CREDITS
+};
+
 struct MainMenuData
 {
 	// These are in the native format of the gui elements
 	// Generic
 	int selected_tab;
+	std::string selected_game;
+	std::string selected_game_name;
 	// Client options
 	std::string servername;
 	std::string serverdescription;
@@ -78,6 +89,8 @@ struct MainMenuData
 	MainMenuData():
 		// Generic
 		selected_tab(0),
+		selected_game("minetest"),
+		selected_game_name("Minetest"),
 		// Client opts
 		fancy_trees(false),
 		smooth_lighting(false),
@@ -128,6 +141,8 @@ class GUIMainMenu : public GUIModalMenu
 	s32 id;
 	IMenuManager *menumgr;
 
+	std::vector<int> m_world_indices;
+
 	bool m_is_regenerating;
 	v2s32 m_topleft_client;
 	v2s32 m_size_client;
diff --git a/src/guiPauseMenu.cpp b/src/guiPauseMenu.cpp
index b57b4a1d15bcd196f28d88725626eee689fe7824..f5d323a9b40d8b45a4ea4138db435e37a5002ca9 100644
--- a/src/guiPauseMenu.cpp
+++ b/src/guiPauseMenu.cpp
@@ -168,16 +168,16 @@ void GUIPauseMenu::regenerateGui(v2u32 screensize)
 		core::rect<s32> rect(0, 0, 180, 240);
 		rect = rect + v2s32(size.X/2 + 90, size.Y/2-rect.getHeight()/2);
 		wchar_t* text = wgettext("Default Controls:\n"
-				"- WASD: Walk\n"
-				"- Mouse left: dig/hit\n"
+				"- WASD: move\n"
+				"- Space: jump/climb\n"
+				"- Shift: sneak/go down\n"
+				"- Q: drop item\n"
+				"- I: inventory\n"
+				"- Mouse: turn/look\n"
+				"- Mouse left: dig/punch\n"
 				"- Mouse right: place/use\n"
 				"- Mouse wheel: select item\n"
-				"- 0...9: select item\n"
-				"- Shift: sneak\n"
-				"- R: Toggle viewing all loaded chunks\n"
-				"- I: Inventory menu\n"
-				"- ESC: This menu\n"
-				"- T: Chat\n"
+				"- T: chat\n"
 				);
 		Environment->addStaticText(text, rect, false, true, this, 258);
 		delete[] text;
diff --git a/src/itemdef.cpp b/src/itemdef.cpp
index 05328ea486e4f85118625981330ce5cbedda3d91..d660db77fd850de2f6bc4a78e14fa666d5606b2a 100644
--- a/src/itemdef.cpp
+++ b/src/itemdef.cpp
@@ -226,17 +226,11 @@ class CItemDefManager: public IWritableItemDefManager
 public:
 	CItemDefManager()
 	{
-		for (std::map<std::string, ItemDefinition*>::iterator iter =
-				m_item_definitions.begin(); iter != m_item_definitions.end();
-				iter ++) {
-			delete iter->second;
-		}
-		m_item_definitions.clear();
+
 #ifndef SERVER
 		m_main_thread = get_current_thread_id();
 		m_driver = NULL;
 #endif
-	
 		clear();
 	}
 	virtual ~CItemDefManager()
@@ -260,6 +254,12 @@ class CItemDefManager: public IWritableItemDefManager
 		}
 		m_driver = NULL;
 #endif
+		for (std::map<std::string, ItemDefinition*>::iterator iter =
+				m_item_definitions.begin(); iter != m_item_definitions.end();
+				iter ++) {
+			delete iter->second;
+		}
+		m_item_definitions.clear();
 	}
 	virtual const ItemDefinition& get(const std::string &name_) const
 	{
diff --git a/src/main.cpp b/src/main.cpp
index eef65cdb2ab77eee7b75630b01a3ff68d7576d13..7f9ec1ace13385a808787888e3946e559207d493 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -76,6 +76,8 @@ with this program; if not, write to the Free Software Foundation, Inc.,
 #include "subgame.h"
 #include "quicktune.h"
 #include "serverlist.h"
+#include "sound.h"
+#include "sound_openal.h"
 
 /*
 	Settings.
@@ -88,6 +90,10 @@ Settings *g_settings = &main_settings;
 Profiler main_profiler;
 Profiler *g_profiler = &main_profiler;
 
+// Menu clouds are created later
+Clouds *g_menuclouds = 0;
+irr::scene::ISceneManager *g_menucloudsmgr = 0;
+
 /*
 	Debug streams
 */
@@ -196,7 +202,49 @@ u32 getTime(TimePrecision prec) {
 		return 0;
 	return g_timegetter->getTime(prec);
 }
+#endif
+
+//Client side main menu music fetcher
+#ifndef SERVER
+class MenuMusicFetcher: public OnDemandSoundFetcher
+{
+	std::set<std::string> m_fetched;
+public:
 
+	void fetchSounds(const std::string &name,
+			std::set<std::string> &dst_paths,
+			std::set<std::string> &dst_datas)
+	{
+		if(m_fetched.count(name))
+			return;
+		m_fetched.insert(name);
+		std::string base;
+		base = porting::path_share + DIR_DELIM + "sounds";
+		dst_paths.insert(base + DIR_DELIM + name + ".ogg");
+		dst_paths.insert(base + DIR_DELIM + name + ".0.ogg");
+		dst_paths.insert(base + DIR_DELIM + name + ".1.ogg");
+		dst_paths.insert(base + DIR_DELIM + name + ".2.ogg");
+		dst_paths.insert(base + DIR_DELIM + name + ".3.ogg");
+		dst_paths.insert(base + DIR_DELIM + name + ".4.ogg");
+		dst_paths.insert(base + DIR_DELIM + name + ".5.ogg");
+		dst_paths.insert(base + DIR_DELIM + name + ".6.ogg");
+		dst_paths.insert(base + DIR_DELIM + name + ".7.ogg");
+		dst_paths.insert(base + DIR_DELIM + name + ".8.ogg");
+		dst_paths.insert(base + DIR_DELIM + name + ".9.ogg");
+		base = porting::path_user + DIR_DELIM + "sounds";
+		dst_paths.insert(base + DIR_DELIM + name + ".ogg");
+		dst_paths.insert(base + DIR_DELIM + name + ".0.ogg");
+		dst_paths.insert(base + DIR_DELIM + name + ".1.ogg");
+		dst_paths.insert(base + DIR_DELIM + name + ".2.ogg");
+		dst_paths.insert(base + DIR_DELIM + name + ".3.ogg");
+		dst_paths.insert(base + DIR_DELIM + name + ".4.ogg");
+		dst_paths.insert(base + DIR_DELIM + name + ".5.ogg");
+		dst_paths.insert(base + DIR_DELIM + name + ".6.ogg");
+		dst_paths.insert(base + DIR_DELIM + name + ".7.ogg");
+		dst_paths.insert(base + DIR_DELIM + name + ".8.ogg");
+		dst_paths.insert(base + DIR_DELIM + name + ".9.ogg");
+		}
+};
 #endif
 
 class StderrLogOutput: public ILogOutput
@@ -612,122 +660,181 @@ class RandomInputHandler : public InputHandler
 	bool rightreleased;
 };
 
-//Draw the tiled menu background
-void drawMenuBackground(video::IVideoDriver* driver) {
-	core::dimension2d<u32> screensize = driver->getScreenSize();
-
-	std::string path = getTexturePath("menubg.png");
-	if (path[0]) {
-		static const video::ITexture *bgtexture =
-			driver->getTexture(path.c_str());
+struct MenuTextures
+{
+	std::string current_gameid;
+	bool global_textures;
+	video::ITexture *background;
+	video::ITexture *overlay;
+	video::ITexture *header;
+	video::ITexture *footer;
+
+	MenuTextures():
+		global_textures(false),
+		background(NULL),
+		overlay(NULL),
+		header(NULL),
+		footer(NULL)
+	{}
 
-		if (bgtexture) {
-			s32 scaledsize = 128;
-		
-			// The important difference between destsize and screensize is
-			// that destsize is rounded to whole scaled pixels.
-			// These formulas use component-wise multiplication and division of v2u32.
-			v2u32 texturesize = bgtexture->getSize();
-			v2u32 sourcesize = texturesize * screensize / scaledsize + v2u32(1,1);
-			v2u32 destsize = scaledsize * sourcesize / texturesize;
-		
-			// Default texture wrapping mode in Irrlicht is ETC_REPEAT.
-			driver->draw2DImage(bgtexture,
-				core::rect<s32>(0, 0, destsize.X, destsize.Y),
-				core::rect<s32>(0, 0, sourcesize.X, sourcesize.Y),
-				NULL, NULL, true);
+	static video::ITexture* getMenuTexture(const std::string &tname,
+			video::IVideoDriver* driver, const SubgameSpec *spec)
+	{
+		if(spec){
+			std::string path;
+			// eg. minetest_menu_background.png (for texture packs)
+			std::string pack_tname = spec->id + "_menu_" + tname + ".png";
+			path = getTexturePath(pack_tname);
+			if(path != "")
+				return driver->getTexture(path.c_str());
+			// eg. games/minetest_game/menu/background.png
+			path = getImagePath(spec->path + DIR_DELIM + "menu" + DIR_DELIM + tname + ".png");
+			if(path != "")
+				return driver->getTexture(path.c_str());
+		} else {
+			std::string path;
+			// eg. menu_background.png
+			std::string pack_tname = "menu_" + tname + ".png";
+			path = getTexturePath(pack_tname);
+			if(path != "")
+				return driver->getTexture(path.c_str());
 		}
+		return NULL;
 	}
-}
 
-//Draw the footer at the bottom of the window
-void drawMenuFooter(video::IVideoDriver* driver, bool clouds) {
-	core::dimension2d<u32> screensize = driver->getScreenSize();
-	std::string path = getTexturePath(clouds ?
-						"menufooter_clouds.png" : "menufooter.png");
-	if (path[0]) {
-		static const video::ITexture *footertexture =
-			driver->getTexture(path.c_str());
-
-		if (footertexture) {
-			f32 mult = (((f32)screensize.Width)) /
-				((f32)footertexture->getOriginalSize().Width);
-
-			v2s32 footersize(((f32)footertexture->getOriginalSize().Width) * mult,
-					((f32)footertexture->getOriginalSize().Height) * mult);
-
-			// Don't draw the footer if there isn't enough room
-			s32 free_space = (((s32)screensize.Height)-320)/2;
-			if (free_space > footersize.Y) {
-				core::rect<s32> rect(0,0,footersize.X,footersize.Y);
-				rect += v2s32(screensize.Width/2,screensize.Height-footersize.Y);
-				rect -= v2s32(footersize.X/2, 0);
-
-				driver->draw2DImage(footertexture, rect,
-					core::rect<s32>(core::position2d<s32>(0,0),
-					core::dimension2di(footertexture->getSize())),
-					NULL, NULL, true);
-			}
+	void update(video::IVideoDriver* driver, const SubgameSpec *spec, int tab)
+	{
+		if(tab == TAB_SINGLEPLAYER){
+			if(spec->id == current_gameid)
+				return;
+			current_gameid = spec->id;
+			global_textures = false;
+			background = getMenuTexture("background", driver, spec);
+			overlay = getMenuTexture("overlay", driver, spec);
+			header = getMenuTexture("header", driver, spec);
+			footer = getMenuTexture("footer", driver, spec);
+		} else {
+			if(global_textures)
+				return;
+			current_gameid = "";
+			global_textures = true;
+			background = getMenuTexture("background", driver, NULL);
+			overlay = getMenuTexture("overlay", driver, NULL);
+			header = getMenuTexture("header", driver, NULL);
+			footer = getMenuTexture("footer", driver, NULL);
 		}
 	}
+};
+
+void drawMenuBackground(video::IVideoDriver* driver, const MenuTextures &menutextures)
+{
+	v2u32 screensize = driver->getScreenSize();
+	video::ITexture *texture = menutextures.background;
+
+	/* If no texture, draw background of solid color */
+	if(!texture){
+		video::SColor color(255,80,58,37);
+		core::rect<s32> rect(0, 0, screensize.X, screensize.Y);
+		driver->draw2DRectangle(color, rect, NULL);
+		return;
+	}
+
+	/* Draw background texture */
+	v2u32 sourcesize = texture->getSize();
+	driver->draw2DImage(texture,
+		core::rect<s32>(0, 0, screensize.X, screensize.Y),
+		core::rect<s32>(0, 0, sourcesize.X, sourcesize.Y),
+		NULL, NULL, true);
+}
+
+void drawMenuOverlay(video::IVideoDriver* driver, const MenuTextures &menutextures)
+{
+	v2u32 screensize = driver->getScreenSize();
+	video::ITexture *texture = menutextures.overlay;
+
+	/* If no texture, draw nothing */
+	if(!texture)
+		return;
+
+	/* Draw overlay texture */
+	v2u32 sourcesize = texture->getSize();
+	driver->draw2DImage(texture,
+		core::rect<s32>(0, 0, screensize.X, screensize.Y),
+		core::rect<s32>(0, 0, sourcesize.X, sourcesize.Y),
+		NULL, NULL, true);
 }
 
-// Draw the Header over the main menu
-void drawMenuHeader(video::IVideoDriver* driver) {
+void drawMenuHeader(video::IVideoDriver* driver, const MenuTextures &menutextures)
+{
 	core::dimension2d<u32> screensize = driver->getScreenSize();
+	video::ITexture *texture = menutextures.header;
 
-	std::string path = getTexturePath("menuheader.png");
-	if (path[0]) {
-		static const video::ITexture *splashtexture =
-		driver->getTexture(path.c_str());
+	/* If no texture, draw nothing */
+	if(!texture)
+		return;
 
-		if(splashtexture) {
-			f32 mult = (((f32)screensize.Width / 2)) /
-				((f32)splashtexture->getOriginalSize().Width);
+	f32 mult = (((f32)screensize.Width / 2)) /
+		((f32)texture->getOriginalSize().Width);
 
-			v2s32 splashsize(((f32)splashtexture->getOriginalSize().Width) * mult,
-					((f32)splashtexture->getOriginalSize().Height) * mult);
+	v2s32 splashsize(((f32)texture->getOriginalSize().Width) * mult,
+			((f32)texture->getOriginalSize().Height) * mult);
 
-			// Don't draw the header is there isn't enough room
-			s32 free_space = (((s32)screensize.Height)-320)/2;
-			if (free_space > splashsize.Y) {
-				core::rect<s32> splashrect(0, 0, splashsize.X, splashsize.Y);
-				splashrect += v2s32((screensize.Width/2)-(splashsize.X/2),
-					((free_space/2)-splashsize.Y/2)+10);
+	// Don't draw the header is there isn't enough room
+	s32 free_space = (((s32)screensize.Height)-320)/2;
+	if (free_space > splashsize.Y) {
+		core::rect<s32> splashrect(0, 0, splashsize.X, splashsize.Y);
+		splashrect += v2s32((screensize.Width/2)-(splashsize.X/2),
+			((free_space/2)-splashsize.Y/2)+10);
 
-				video::SColor bgcolor(255,50,50,50);
+		video::SColor bgcolor(255,50,50,50);
 
-				driver->draw2DImage(splashtexture, splashrect,
-					core::rect<s32>(core::position2d<s32>(0,0),
-					core::dimension2di(splashtexture->getSize())),
-					NULL, NULL, true);
-			}
-		}
+		driver->draw2DImage(texture, splashrect,
+			core::rect<s32>(core::position2d<s32>(0,0),
+			core::dimension2di(texture->getSize())),
+			NULL, NULL, true);
 	}
 }
 
-// Draw the Splash over the clouds and under the main menu
-void drawMenuSplash(video::IVideoDriver* driver) {
+void drawMenuFooter(video::IVideoDriver* driver, const MenuTextures &menutextures)
+{
 	core::dimension2d<u32> screensize = driver->getScreenSize();
-	std::string path = getTexturePath("menusplash.png");
-	if (path[0]) {
-		static const video::ITexture *splashtexture =
-			driver->getTexture(path.c_str());
+	video::ITexture *texture = menutextures.footer;
+
+	/* If no texture, draw nothing */
+	if(!texture)
+		return;
+
+	f32 mult = (((f32)screensize.Width)) /
+		((f32)texture->getOriginalSize().Width);
 
-		if(splashtexture) {
-			core::rect<s32> splashrect(0, 0, screensize.Width, screensize.Height);
+	v2s32 footersize(((f32)texture->getOriginalSize().Width) * mult,
+			((f32)texture->getOriginalSize().Height) * mult);
 
-			video::SColor bgcolor(255,50,50,50);
+	// Don't draw the footer if there isn't enough room
+	s32 free_space = (((s32)screensize.Height)-320)/2;
+	if (free_space > footersize.Y) {
+		core::rect<s32> rect(0,0,footersize.X,footersize.Y);
+		rect += v2s32(screensize.Width/2,screensize.Height-footersize.Y);
+		rect -= v2s32(footersize.X/2, 0);
+
+		driver->draw2DImage(texture, rect,
+			core::rect<s32>(core::position2d<s32>(0,0),
+			core::dimension2di(texture->getSize())),
+			NULL, NULL, true);
+	}
+}
 
-			driver->draw2DImage(splashtexture, splashrect,
-				core::rect<s32>(core::position2d<s32>(0,0),
-				core::dimension2di(splashtexture->getSize())),
-				NULL, NULL, true);
+static const SubgameSpec* getMenuGame(const MainMenuData &menudata)
+{
+	for(size_t i=0; i<menudata.games.size(); i++){
+		if(menudata.games[i].id == menudata.selected_game){
+			return &menudata.games[i];
 		}
 	}
+	return NULL;
 }
 
-#endif
+#endif // !SERVER
 
 // These are defined global so that they're not optimized too much.
 // Can't change them to volatile.
@@ -900,6 +1007,8 @@ int main(int argc, char *argv[])
 	allowed_options.insert(std::make_pair("gameid", ValueSpec(VALUETYPE_STRING,
 			_("Set gameid (\"--gameid list\" prints available ones)"))));
 #ifndef SERVER
+	allowed_options.insert(std::make_pair("videomodes", ValueSpec(VALUETYPE_FLAG,
+			_("Show available video modes"))));
 	allowed_options.insert(std::make_pair("speedtests", ValueSpec(VALUETYPE_FLAG,
 			_("Run speed tests"))));
 	allowed_options.insert(std::make_pair("address", ValueSpec(VALUETYPE_STRING,
@@ -999,7 +1108,7 @@ int main(int argc, char *argv[])
 		print_worldspecs(worldspecs, dstream);
 		return 0;
 	}
-	
+
 	// Print startup message
 	infostream<<PROJECT_NAME<<
 			" "<<_("with")<<" SER_FMT_VER_HIGHEST="<<(int)SER_FMT_VER_HIGHEST
@@ -1379,11 +1488,64 @@ int main(int argc, char *argv[])
 	}
 
 	/*
-		Create device and exit if creation failed
+		List video modes if requested
 	*/
 
 	MyEventReceiver receiver;
 
+	if(cmd_args.getFlag("videomodes")){
+		IrrlichtDevice *nulldevice;
+
+		SIrrlichtCreationParameters params = SIrrlichtCreationParameters();
+		params.DriverType    = video::EDT_NULL;
+		params.WindowSize    = core::dimension2d<u32>(640, 480);
+		params.Bits          = 24;
+		params.AntiAlias     = fsaa;
+		params.Fullscreen    = false;
+		params.Stencilbuffer = false;
+		params.Vsync         = vsync;
+		params.EventReceiver = &receiver;
+
+		nulldevice = createDeviceEx(params);
+
+		if(nulldevice == 0)
+			return 1;
+
+		dstream<<_("Available video modes (WxHxD):")<<std::endl;
+
+		video::IVideoModeList *videomode_list =
+				nulldevice->getVideoModeList();
+
+		if(videomode_list == 0){
+			nulldevice->drop();
+			return 1;
+		}
+
+		s32 videomode_count = videomode_list->getVideoModeCount();
+		core::dimension2d<u32> videomode_res;
+		s32 videomode_depth;
+		for (s32 i = 0; i < videomode_count; ++i){
+			videomode_res = videomode_list->getVideoModeResolution(i);
+			videomode_depth = videomode_list->getVideoModeDepth(i);
+			dstream<<videomode_res.Width<<"x"<<videomode_res.Height
+					<<"x"<<videomode_depth<<std::endl;
+		}
+
+		dstream<<_("Active video mode (WxHxD):")<<std::endl;
+		videomode_res = videomode_list->getDesktopResolution();
+		videomode_depth = videomode_list->getDesktopDepth();
+		dstream<<videomode_res.Width<<"x"<<videomode_res.Height
+				<<"x"<<videomode_depth<<std::endl;
+
+		nulldevice->drop();
+
+		return 0;
+	}
+
+	/*
+		Create device and exit if creation failed
+	*/
+
 	IrrlichtDevice *device;
 
 	SIrrlichtCreationParameters params = SIrrlichtCreationParameters();
@@ -1426,6 +1588,7 @@ int main(int argc, char *argv[])
 	{
 		dstream<<"Running speed tests"<<std::endl;
 		SpeedTests();
+		device->drop();
 		return 0;
 	}
 	
@@ -1477,6 +1640,19 @@ int main(int argc, char *argv[])
 	skin->setColor(gui::EGDC_FOCUSED_EDITABLE, video::SColor(255,96,134,49));
 #endif
 
+
+	// Create the menu clouds
+	if (!g_menucloudsmgr)
+		g_menucloudsmgr = smgr->createNewSceneManager();
+	if (!g_menuclouds)
+		g_menuclouds = new Clouds(g_menucloudsmgr->getRootSceneNode(),
+			g_menucloudsmgr, -1, rand(), 100);
+	g_menuclouds->update(v2f(0, 0), video::SColor(255,200,200,255));
+	scene::ICameraSceneNode* camera;
+	camera = g_menucloudsmgr->addCameraSceneNode(0,
+				v3f(0,0,0), v3f(0, 60, 100));
+	camera->setFarValue(10000);
+
 	/*
 		GUI stuff
 	*/
@@ -1558,6 +1734,10 @@ int main(int argc, char *argv[])
 					menudata.selected_tab = g_settings->getS32("selected_mainmenu_tab");
 				if(g_settings->exists("selected_serverlist"))
 					menudata.selected_serverlist = g_settings->getS32("selected_serverlist");
+				if(g_settings->exists("selected_mainmenu_game")){
+					menudata.selected_game = g_settings->get("selected_mainmenu_game");
+					menudata.selected_game_name = findSubgame(menudata.selected_game).name;
+				}
 				menudata.address = narrow_to_wide(address);
 				menudata.name = narrow_to_wide(playername);
 				menudata.port = narrow_to_wide(itos(port));
@@ -1611,6 +1791,17 @@ int main(int argc, char *argv[])
 				}
 				// Copy worldspecs to menu
 				menudata.worlds = worldspecs;
+				// Get game listing
+				menudata.games = getAvailableGames();
+				// If selected game doesn't exist, take first from list
+				if(findSubgame(menudata.selected_game).id == "" &&
+						!menudata.games.empty()){
+					menudata.selected_game = menudata.games[0].id;
+				}
+				const SubgameSpec *menugame = getMenuGame(menudata);
+
+				MenuTextures menutextures;
+				menutextures.update(driver, menugame, menudata.selected_tab);
 
 				if(skip_main_menu == false)
 				{
@@ -1623,7 +1814,7 @@ int main(int argc, char *argv[])
 							break;
 						driver->beginScene(true, true,
 								video::SColor(255,128,128,128));
-						drawMenuBackground(driver);
+						drawMenuBackground(driver, menutextures);
 						guienv->drawAll();
 						driver->endScene();
 						// On some computers framerate doesn't seem to be
@@ -1637,22 +1828,6 @@ int main(int argc, char *argv[])
 								&g_menumgr, &menudata, g_gamecallback);
 					menu->allowFocusRemoval(true);
 
-					// Clouds for the main menu
-					bool cloud_menu_background = false;
-					Clouds *clouds = NULL;
-					if (g_settings->getBool("menu_clouds")) {
-						cloud_menu_background = true;
-						clouds = new Clouds(smgr->getRootSceneNode(),
-											smgr, -1, rand(), 100);
-						clouds->update(v2f(0, 0), video::SColor(255,200,200,255));
-
-						// A camera to see the clouds
-						scene::ICameraSceneNode* camera;
-						camera = smgr->addCameraSceneNode(0,
-									v3f(0,0,0), v3f(0, 60, 100));
-						camera->setFarValue(10000);
-					}
-
 					if(error_message != L"")
 					{
 						verbosestream<<"error_message = "
@@ -1668,6 +1843,16 @@ int main(int argc, char *argv[])
 					// Time is in milliseconds, for clouds
 					u32 lasttime = device->getTimer()->getTime();
 
+					MenuMusicFetcher soundfetcher;
+					ISoundManager *sound = NULL;
+					sound = createOpenALSoundManager(&soundfetcher);
+					if(!sound)
+						sound = &dummySoundManager;
+					SimpleSoundSpec spec;
+					spec.name = "main_menu";
+					spec.gain = 1;
+					s32 handle = sound->playSound(spec, true);
+
 					infostream<<"Created main menu"<<std::endl;
 
 					while(device->run() && kill == false)
@@ -1675,8 +1860,25 @@ int main(int argc, char *argv[])
 						if(menu->getStatus() == true)
 							break;
 
+						// Game can be selected in the menu
+						menugame = getMenuGame(menudata);
+						menutextures.update(driver, menugame, menu->getTab());
+						// Clouds for the main menu
+						bool cloud_menu_background = g_settings->getBool("menu_clouds");
+						if(menugame){
+							// If game has regular background and no overlay, don't use clouds
+							if(cloud_menu_background && menutextures.background &&
+									!menutextures.overlay){
+								cloud_menu_background = false;
+							}
+							// If game game has overlay and no regular background, always draw clouds
+							else if(menutextures.overlay && !menutextures.background){
+								cloud_menu_background = true;
+							}
+						}
+
 						// Time calc for the clouds
-						f32 dtime; // in seconds
+						f32 dtime=0; // in seconds
 						if (cloud_menu_background) {
 							u32 time = device->getTimer()->getTime();
 							if(time > lasttime)
@@ -1691,15 +1893,16 @@ int main(int argc, char *argv[])
 
 						if (cloud_menu_background) {
 							// *3 otherwise the clouds would move very slowly
-							clouds->step(dtime*3); 
-							clouds->render();
-							smgr->drawAll();
-							drawMenuSplash(driver);
-							drawMenuFooter(driver, true);
-							drawMenuHeader(driver);
+							g_menuclouds->step(dtime*3); 
+							g_menuclouds->render();
+							g_menucloudsmgr->drawAll();
+							drawMenuOverlay(driver, menutextures);
+							drawMenuHeader(driver, menutextures);
+							drawMenuFooter(driver, menutextures);
 						} else {
-							drawMenuBackground(driver);
-							drawMenuFooter(driver, false);
+							drawMenuBackground(driver, menutextures);
+							drawMenuHeader(driver, menutextures);
+							drawMenuFooter(driver, menutextures);
 						}
 
 						guienv->drawAll();
@@ -1731,14 +1934,15 @@ int main(int argc, char *argv[])
 							sleep_ms(25);
 						}
 					}
-					
+					sound->stopSound(handle);
+					if(sound != &dummySoundManager){
+						delete sound;
+						sound = NULL;
+					}
+
 					infostream<<"Dropping main menu"<<std::endl;
 
 					menu->drop();
-					if (cloud_menu_background) {
-						clouds->drop();
-						smgr->clear();
-					}
 				}
 
 				playername = wide_to_narrow(menudata.name);
@@ -1755,6 +1959,7 @@ int main(int argc, char *argv[])
 				// Save settings
 				g_settings->setS32("selected_mainmenu_tab", menudata.selected_tab);
 				g_settings->setS32("selected_serverlist", menudata.selected_serverlist);
+				g_settings->set("selected_mainmenu_game", menudata.selected_game);
 				g_settings->set("new_style_leaves", itos(menudata.fancy_trees));
 				g_settings->set("smooth_lighting", itos(menudata.smooth_lighting));
 				g_settings->set("enable_3d_clouds", itos(menudata.clouds_3d));
@@ -1832,6 +2037,7 @@ int main(int argc, char *argv[])
 						continue;
 					}
 					g_settings->set("selected_world_path", path);
+					g_settings->set("selected_mainmenu_game", menudata.create_world_gameid);
 					continue;
 				}
 
@@ -1897,6 +2103,7 @@ int main(int argc, char *argv[])
 				gamespec,
 				simple_singleplayer_mode
 			);
+			smgr->clear();
 
 		} //try
 		catch(con::PeerNotFoundException &e)
@@ -1927,6 +2134,10 @@ int main(int argc, char *argv[])
 		}
 	} // Menu-game loop
 	
+	
+	g_menuclouds->drop();
+	g_menucloudsmgr->drop();
+	
 	delete input;
 
 	/*
@@ -1934,6 +2145,8 @@ int main(int argc, char *argv[])
 	*/
 	device->drop();
 
+	delete font;
+
 #endif // !SERVER
 	
 	// Update configuration file
diff --git a/src/main.h b/src/main.h
index daa8c70d28be2a61f7216646e85020ced5717a03..df67a634848bfecd28d534749cf82018bc39c404 100644
--- a/src/main.h
+++ b/src/main.h
@@ -28,6 +28,14 @@ extern Settings *g_settings;
 class Profiler;
 extern Profiler *g_profiler;
 
+// Menu clouds
+class Clouds;
+extern Clouds *g_menuclouds;
+
+// Scene manager used for menu clouds
+namespace irr{namespace scene{class ISceneManager;}}
+extern irr::scene::ISceneManager *g_menucloudsmgr;
+
 // Debug streams
 
 #include <fstream>
diff --git a/src/mapblock_mesh.cpp b/src/mapblock_mesh.cpp
index f8a0b5f0631a412f612ee4a3bb6424823bd259bf..0f83e863c0bbaf44f1218618d1538a4b158e7fbb 100644
--- a/src/mapblock_mesh.cpp
+++ b/src/mapblock_mesh.cpp
@@ -1338,12 +1338,20 @@ void MeshCollector::append(const TileSpec &tile,
 		const video::S3DVertex *vertices, u32 numVertices,
 		const u16 *indices, u32 numIndices)
 {
+	if(numIndices > 65535)
+	{
+		dstream<<"FIXME: MeshCollector::append() called with numIndices="<<numIndices<<" (limit 65535)"<<std::endl;
+		return;
+	}
+
 	PreMeshBuffer *p = NULL;
 	for(u32 i=0; i<prebuffers.size(); i++)
 	{
 		PreMeshBuffer &pp = prebuffers[i];
 		if(pp.tile != tile)
 			continue;
+		if(pp.indices.size() + numIndices > 65535)
+			continue;
 
 		p = &pp;
 		break;
@@ -1361,11 +1369,6 @@ void MeshCollector::append(const TileSpec &tile,
 	for(u32 i=0; i<numIndices; i++)
 	{
 		u32 j = indices[i] + vertex_count;
-		if(j > 65535)
-		{
-			dstream<<"FIXME: Meshbuffer ran out of indices"<<std::endl;
-			// NOTE: Fix is to just add an another MeshBuffer
-		}
 		p->indices.push_back(j);
 	}
 	for(u32 i=0; i<numVertices; i++)
diff --git a/src/mesh.cpp b/src/mesh.cpp
index da0dbe44235d3b049d1a273c96d09865184a2100..fd35a3a062d6b348fa58b7eb202de48d2d222409 100644
--- a/src/mesh.cpp
+++ b/src/mesh.cpp
@@ -284,7 +284,6 @@ scene::IAnimatedMesh* createExtrudedMesh(video::ITexture *texture,
 		if (img2 != NULL)
 		{
 			img1->copyTo(img2);
-			img1->drop();
 
 			mesh = extrudeARGB(size.Width, size.Height, (u8*) img2->lock());
 			img2->unlock();
diff --git a/src/mods.cpp b/src/mods.cpp
index 6a7ab79aade2870804b74119d46f5bb381d3b64c..64c3199928392d0f09b0cec2f97e98fb685918b4 100644
--- a/src/mods.cpp
+++ b/src/mods.cpp
@@ -24,9 +24,74 @@ with this program; if not, write to the Free Software Foundation, Inc.,
 #include "subgame.h"
 #include "settings.h"
 #include "strfnd.h"
+#include <cctype>
 
-std::map<std::string, ModSpec> getModsInPath(std::string path)
+static bool parseDependsLine(std::istream &is,
+		std::string &dep, std::set<char> &symbols)
 {
+	std::getline(is, dep);
+	dep = trim(dep);
+	symbols.clear();
+	size_t pos = dep.size();
+	while(pos > 0 && !string_allowed(dep.substr(pos-1, 1), MODNAME_ALLOWED_CHARS)){
+		// last character is a symbol, not part of the modname
+		symbols.insert(dep[pos-1]);
+		--pos;
+	}
+	dep = trim(dep.substr(0, pos));
+	return dep != "";
+}
+
+void parseModContents(ModSpec &spec)
+{
+	// NOTE: this function works in mutual recursion with getModsInPath
+
+	spec.depends.clear();
+	spec.optdepends.clear();
+	spec.is_modpack = false;
+	spec.modpack_content.clear();
+
+	// Handle modpacks (defined by containing modpack.txt)
+	std::ifstream modpack_is((spec.path+DIR_DELIM+"modpack.txt").c_str());
+	if(modpack_is.good()){ //a modpack, recursively get the mods in it
+		modpack_is.close(); // We don't actually need the file
+		spec.is_modpack = true;
+		spec.modpack_content = getModsInPath(spec.path, true);
+
+		// modpacks have no dependencies; they are defined and
+		// tracked separately for each mod in the modpack
+	}
+	else{ // not a modpack, parse the dependencies
+		std::ifstream is((spec.path+DIR_DELIM+"depends.txt").c_str());
+		while(is.good()){
+			std::string dep;
+			std::set<char> symbols;
+			if(parseDependsLine(is, dep, symbols)){
+				if(symbols.count('?') != 0){
+					spec.optdepends.insert(dep);
+				}
+				else{
+					spec.depends.insert(dep);
+				}
+			}
+		}
+
+		// FIXME: optdepends.txt is deprecated
+		// remove this code at some point in the future
+		std::ifstream is2((spec.path+DIR_DELIM+"optdepends.txt").c_str());
+		while(is2.good()){
+			std::string dep;
+			std::set<char> symbols;
+			if(parseDependsLine(is2, dep, symbols))
+				spec.optdepends.insert(dep);
+		}
+	}
+}
+
+std::map<std::string, ModSpec> getModsInPath(std::string path, bool part_of_modpack)
+{
+	// NOTE: this function works in mutual recursion with parseModContents
+
 	std::map<std::string, ModSpec> result;
 	std::vector<fs::DirListNode> dirlist = fs::GetDirListing(path);
 	for(u32 j=0; j<dirlist.size(); j++){
@@ -39,36 +104,32 @@ std::map<std::string, ModSpec> getModsInPath(std::string path)
 			continue;
 		std::string modpath = path + DIR_DELIM + modname;
 
-		// Handle modpacks (defined by containing modpack.txt)
-		std::ifstream modpack_is((modpath+DIR_DELIM+"modpack.txt").c_str(),
-					std::ios_base::binary);
-		if(modpack_is.good()) //a modpack, recursively get the mods in it
-		{
-			modpack_is.close(); // We don't actually need the file
-			ModSpec spec(modname,modpath);
-			spec.modpack_content = getModsInPath(modpath);
-			spec.is_modpack = true;
-			result.insert(std::make_pair(modname,spec));
-		}
-		else // not a modpack, add the modspec
-		{
-			std::set<std::string> depends;
-			std::ifstream is((modpath+DIR_DELIM+"depends.txt").c_str(),
-				std::ios_base::binary);
-			while(is.good())
-			{
-				std::string dep;
-				std::getline(is, dep);
-				dep = trim(dep);	
-				if(dep != "")
-					depends.insert(dep);
-			}
+		ModSpec spec(modname, modpath);
+		spec.part_of_modpack = part_of_modpack;
+		parseModContents(spec);
+		result.insert(std::make_pair(modname, spec));
+	}
+	return result;
+}
 
-			ModSpec spec(modname, modpath, depends);
-			result.insert(std::make_pair(modname,spec));
+ModSpec findCommonMod(const std::string &modname)
+{
+	// Try to find in {$user,$share}/games/common/$modname
+	std::vector<std::string> find_paths;
+	find_paths.push_back(porting::path_user + DIR_DELIM + "games" +
+			DIR_DELIM + "common" + DIR_DELIM + "mods" + DIR_DELIM + modname);
+	find_paths.push_back(porting::path_share + DIR_DELIM + "games" +
+			DIR_DELIM + "common" + DIR_DELIM + "mods" + DIR_DELIM + modname);
+	for(u32 i=0; i<find_paths.size(); i++){
+		const std::string &try_path = find_paths[i];
+		if(fs::PathExists(try_path)){
+			ModSpec spec(modname, try_path);
+			parseModContents(spec);
+			return spec;
 		}
 	}
-	return result;
+	// Failed to find mod
+	return ModSpec();
 }
 
 std::map<std::string, ModSpec> flattenModTree(std::map<std::string, ModSpec> mods)
@@ -109,109 +170,18 @@ std::vector<ModSpec> flattenMods(std::map<std::string, ModSpec> mods)
 		} 
 		else //not a modpack
 		{
-			// infostream << "inserting mod " << mod.name << std::endl;
 			result.push_back(mod);
 		}
 	}
 	return result;
 }
 
-std::vector<ModSpec> filterMods(std::vector<ModSpec> mods,
-								std::set<std::string> exclude_mod_names)
-{
-	std::vector<ModSpec> result;
-	for(std::vector<ModSpec>::iterator it = mods.begin();
-		it != mods.end(); ++it)
-	{
-		ModSpec& mod = *it;
-		if(exclude_mod_names.count(mod.name) == 0)
-			result.push_back(mod);
-	}	
-	return result;
-}
-
-void ModConfiguration::addModsInPathFiltered(std::string path, std::set<std::string> exclude_mods)
-{
-	addMods(filterMods(flattenMods(getModsInPath(path)),exclude_mods));
-}
-
-
-void ModConfiguration::addMods(std::vector<ModSpec> new_mods)
-{
-	// Step 1: remove mods in sorted_mods from unmet dependencies
-	// of new_mods. new mods without unmet dependencies are
-	// temporarily stored in satisfied_mods
-	std::vector<ModSpec> satisfied_mods;
-	for(std::vector<ModSpec>::iterator it = m_sorted_mods.begin();
-		it != m_sorted_mods.end(); ++it)
-	{
-		ModSpec mod = *it;
-		for(std::vector<ModSpec>::iterator it_new = new_mods.begin();
-			it_new != new_mods.end(); ++it_new)
-		{
-			ModSpec& mod_new = *it_new;
-			//infostream << "erasing dependency " << mod.name << " from " << mod_new.name << std::endl;
-			mod_new.unsatisfied_depends.erase(mod.name);
-		}
-	}
-
-	// split new mods into satisfied and unsatisfied
-	for(std::vector<ModSpec>::iterator it = new_mods.begin();
-		it != new_mods.end(); ++it)
-	{
-		ModSpec mod_new = *it;
-		if(mod_new.unsatisfied_depends.empty())
-			satisfied_mods.push_back(mod_new);
-		else
-			m_unsatisfied_mods.push_back(mod_new);
-	}
-
-	// Step 2: mods without unmet dependencies can be appended to
-	// the sorted list.
-	while(!satisfied_mods.empty())
-	{
-		ModSpec mod = satisfied_mods.back();
-		m_sorted_mods.push_back(mod);
-		satisfied_mods.pop_back();
-		for(std::list<ModSpec>::iterator it = m_unsatisfied_mods.begin();
-			it != m_unsatisfied_mods.end(); )
-		{
-			ModSpec& mod2 = *it;
-			mod2.unsatisfied_depends.erase(mod.name);
-			if(mod2.unsatisfied_depends.empty())
-			{
-				satisfied_mods.push_back(mod2);
-				it = m_unsatisfied_mods.erase(it);
-			}
-			else
-				++it;
-		}	
-	}
-}
-
-// If failed, returned modspec has name==""
-static ModSpec findCommonMod(const std::string &modname)
-{
-	// Try to find in {$user,$share}/games/common/$modname
-	std::vector<std::string> find_paths;
-	find_paths.push_back(porting::path_user + DIR_DELIM + "games" +
-			DIR_DELIM + "common" + DIR_DELIM + "mods" + DIR_DELIM + modname);
-	find_paths.push_back(porting::path_share + DIR_DELIM + "games" +
-			DIR_DELIM + "common" + DIR_DELIM + "mods" + DIR_DELIM + modname);
-	for(u32 i=0; i<find_paths.size(); i++){
-		const std::string &try_path = find_paths[i];
-		if(fs::PathExists(try_path))
-			return ModSpec(modname, try_path);
-	}
-	// Failed to find mod
-	return ModSpec();
-}
-
 ModConfiguration::ModConfiguration(std::string worldpath)
 {
 	SubgameSpec gamespec = findWorldSubgame(worldpath);
 
-	// Add common mods without dependency handling
+	// Add common mods
+	std::map<std::string, ModSpec> common_mods;
 	std::vector<std::string> inexistent_common_mods;
 	Settings gameconf;
 	if(getGameConfig(gamespec.path, gameconf)){
@@ -225,7 +195,7 @@ ModConfiguration::ModConfiguration(std::string worldpath)
 				if(spec.name.empty())
 					inexistent_common_mods.push_back(modname);
 				else
-					m_sorted_mods.push_back(spec);
+					common_mods.insert(std::make_pair(modname, spec));
 			}
 		}
 	}
@@ -238,10 +208,11 @@ ModConfiguration::ModConfiguration(std::string worldpath)
 		s += " could not be found.";
 		throw ModError(s);
 	}
+	addMods(flattenMods(common_mods));
 
-	// Add all world mods and all game mods
-	addModsInPath(worldpath + DIR_DELIM + "worldmods");
+	// Add all game mods and all world mods
 	addModsInPath(gamespec.gamemods_path);
+	addModsInPath(worldpath + DIR_DELIM + "worldmods");
 
 	// check world.mt file for mods explicitely declared to be
 	// loaded or not by a load_mod_<modname> = ... line.
@@ -264,7 +235,155 @@ ModConfiguration::ModConfiguration(std::string worldpath)
 		}
 	}
 
-	for(std::set<std::string>::const_iterator i = gamespec.addon_mods_paths.begin();
-			i != gamespec.addon_mods_paths.end(); ++i)
-		addModsInPathFiltered((*i),exclude_mod_names);
+	// Collect all mods in gamespec.addon_mods_paths,
+	// excluding those in the set exclude_mod_names
+	std::vector<ModSpec> addon_mods;
+	for(std::set<std::string>::const_iterator it_path = gamespec.addon_mods_paths.begin();
+			it_path != gamespec.addon_mods_paths.end(); ++it_path)
+	{
+		std::vector<ModSpec> addon_mods_in_path = flattenMods(getModsInPath(*it_path));
+		for(std::vector<ModSpec>::iterator it = addon_mods_in_path.begin();
+			it != addon_mods_in_path.end(); ++it)
+		{
+			ModSpec& mod = *it;
+			if(exclude_mod_names.count(mod.name) == 0)
+				addon_mods.push_back(mod);
+		}
+	}
+
+	addMods(addon_mods);
+
+	// report on name conflicts
+	if(!m_name_conflicts.empty()){
+		std::string s = "Unresolved name conflicts for mods ";
+		for(std::set<std::string>::const_iterator it = m_name_conflicts.begin();
+				it != m_name_conflicts.end(); ++it)
+		{
+			if(it != m_name_conflicts.begin()) s += ", ";
+			s += std::string("\"") + (*it) + "\"";
+		}
+		s += ".";
+		throw ModError(s);
+	}
+
+	// get the mods in order
+	resolveDependencies();
+}
+
+void ModConfiguration::addModsInPath(std::string path)
+{
+	addMods(flattenMods(getModsInPath(path)));
+}
+
+void ModConfiguration::addMods(std::vector<ModSpec> new_mods)
+{
+	// Maintain a map of all existing m_unsatisfied_mods.
+	// Keys are mod names and values are indices into m_unsatisfied_mods.
+	std::map<std::string, u32> existing_mods;
+	for(u32 i = 0; i < m_unsatisfied_mods.size(); ++i){
+		existing_mods[m_unsatisfied_mods[i].name] = i;
+	}
+
+	// Add new mods
+	for(int want_from_modpack = 1; want_from_modpack >= 0; --want_from_modpack){
+		// First iteration:
+		// Add all the mods that come from modpacks
+		// Second iteration:
+		// Add all the mods that didn't come from modpacks
+		
+		std::set<std::string> seen_this_iteration;
+
+		for(std::vector<ModSpec>::const_iterator it = new_mods.begin();
+				it != new_mods.end(); ++it){
+			const ModSpec &mod = *it;
+			if(mod.part_of_modpack != want_from_modpack)
+				continue;
+			if(existing_mods.count(mod.name) == 0){
+				// GOOD CASE: completely new mod.
+				m_unsatisfied_mods.push_back(mod);
+				existing_mods[mod.name] = m_unsatisfied_mods.size() - 1;
+			}
+			else if(seen_this_iteration.count(mod.name) == 0){
+				// BAD CASE: name conflict in different levels.
+				u32 oldindex = existing_mods[mod.name];
+				const ModSpec &oldmod = m_unsatisfied_mods[oldindex];
+				errorstream<<"WARNING: Mod name conflict detected: \""
+					<<mod.name<<"\""<<std::endl
+					<<"Will not load: "<<oldmod.path<<std::endl
+					<<"Overridden by: "<<mod.path<<std::endl;
+				m_unsatisfied_mods[oldindex] = mod;
+
+				// If there was a "VERY BAD CASE" name conflict
+				// in an earlier level, ignore it.
+				m_name_conflicts.erase(mod.name);
+			}
+			else{
+				// VERY BAD CASE: name conflict in the same level.
+				u32 oldindex = existing_mods[mod.name];
+				const ModSpec &oldmod = m_unsatisfied_mods[oldindex];
+				errorstream<<"WARNING: Mod name conflict detected: \""
+					<<mod.name<<"\""<<std::endl
+					<<"Will not load: "<<oldmod.path<<std::endl
+					<<"Will not load: "<<mod.path<<std::endl;
+				m_unsatisfied_mods[oldindex] = mod;
+				m_name_conflicts.insert(mod.name);
+			}
+			seen_this_iteration.insert(mod.name);
+		}
+	}
+}
+
+void ModConfiguration::resolveDependencies()
+{
+	// Step 1: Compile a list of the mod names we're working with
+	std::set<std::string> modnames;
+	for(std::vector<ModSpec>::iterator it = m_unsatisfied_mods.begin();
+		it != m_unsatisfied_mods.end(); ++it){
+		modnames.insert((*it).name);
+	}
+
+	// Step 2: get dependencies (including optional dependencies)
+	// of each mod, split mods into satisfied and unsatisfied
+	std::list<ModSpec> satisfied;
+	std::list<ModSpec> unsatisfied;
+	for(std::vector<ModSpec>::iterator it = m_unsatisfied_mods.begin();
+			it != m_unsatisfied_mods.end(); ++it){
+		ModSpec mod = *it;
+		mod.unsatisfied_depends = mod.depends;
+		// check which optional dependencies actually exist
+		for(std::set<std::string>::iterator it_optdep = mod.optdepends.begin();
+				it_optdep != mod.optdepends.end(); ++it_optdep){
+			std::string optdep = *it_optdep;
+			if(modnames.count(optdep) != 0)
+				mod.unsatisfied_depends.insert(optdep);
+		}
+		// if a mod has no depends it is initially satisfied
+		if(mod.unsatisfied_depends.empty())
+			satisfied.push_back(mod);
+		else
+			unsatisfied.push_back(mod);
+	}
+
+	// Step 3: mods without unmet dependencies can be appended to
+	// the sorted list.
+	while(!satisfied.empty()){
+		ModSpec mod = satisfied.back();
+		m_sorted_mods.push_back(mod);
+		satisfied.pop_back();
+		for(std::list<ModSpec>::iterator it = unsatisfied.begin();
+				it != unsatisfied.end(); ){
+			ModSpec& mod2 = *it;
+			mod2.unsatisfied_depends.erase(mod.name);
+			if(mod2.unsatisfied_depends.empty()){
+				satisfied.push_back(mod2);
+				it = unsatisfied.erase(it);
+			}
+			else{
+				++it;
+			}
+		}	
+	}
+
+	// Step 4: write back list of unsatisfied mods
+	m_unsatisfied_mods.assign(unsatisfied.begin(), unsatisfied.end());
 }
diff --git a/src/mods.h b/src/mods.h
index 32bcfb471e57e4824b59c3a15640c9b9c2ff1fb8..a8100fcfd8f0ca27c1f93bfcaa126197c5d6ac73 100644
--- a/src/mods.h
+++ b/src/mods.h
@@ -30,6 +30,8 @@ with this program; if not, write to the Free Software Foundation, Inc.,
 #include <exception>
 #include <list>
 
+#define MODNAME_ALLOWED_CHARS "abcdefghijklmnopqrstuvwxyz0123456789_"
+
 class ModError : public std::exception
 {
 public:
@@ -53,23 +55,32 @@ struct ModSpec
 	std::string path;
 	//if normal mod:
 	std::set<std::string> depends;
+	std::set<std::string> optdepends;
 	std::set<std::string> unsatisfied_depends;
 
+	bool part_of_modpack;
 	bool is_modpack;
 	// if modpack:
 	std::map<std::string,ModSpec> modpack_content;
-	ModSpec(const std::string name_="", const std::string path_="",
-			const std::set<std::string> depends_=std::set<std::string>()):
+	ModSpec(const std::string name_="", const std::string path_=""):
 		name(name_),
 		path(path_),
-		depends(depends_),
-		unsatisfied_depends(depends_),
-		is_modpack(false),	
-		modpack_content()	
+		depends(),
+		optdepends(),
+		unsatisfied_depends(),
+		part_of_modpack(false),
+		is_modpack(false),
+		modpack_content()
 	{}
 };
 
-std::map<std::string,ModSpec> getModsInPath(std::string path);
+// Retrieves depends, optdepends, is_modpack and modpack_content
+void parseModContents(ModSpec &mod);
+
+std::map<std::string,ModSpec> getModsInPath(std::string path, bool part_of_modpack = false);
+
+// If failed, returned modspec has name==""
+ModSpec findCommonMod(const std::string &modname);
 
 // expands modpack contents, but does not replace them.
 std::map<std::string, ModSpec> flattenModTree(std::map<std::string, ModSpec> mods);
@@ -77,10 +88,6 @@ std::map<std::string, ModSpec> flattenModTree(std::map<std::string, ModSpec> mod
 // replaces modpack Modspecs with their content
 std::vector<ModSpec> flattenMods(std::map<std::string,ModSpec> mods);
 
-// removes Mods mentioned in exclude_mod_names
-std::vector<ModSpec> filterMods(std::vector<ModSpec> mods,
-								std::set<std::string> exclude_mod_names);
-
 // a ModConfiguration is a subset of installed mods, expected to have
 // all dependencies fullfilled, so it can be used as a list of mods to
 // load when the game starts.
@@ -89,26 +96,13 @@ class ModConfiguration
 public:
 	ModConfiguration():
 		m_unsatisfied_mods(),
-		m_sorted_mods()
+		m_sorted_mods(),
+		m_name_conflicts()
 	{}
 
 		
 	ModConfiguration(std::string worldpath);
 
-	// adds all mods in the given path. used for games, modpacks
-	// and world-specific mods (worldmods-folders)
-	void addModsInPath(std::string path)
-	{
-		addMods(flattenMods(getModsInPath(path)));
-	}
-
-	// adds all mods in the given path whose name does not appear
-	// in the exclude_mods set. 
-	void addModsInPathFiltered(std::string path, std::set<std::string> exclude_mods);
-
-	// adds all mods in the set.
-	void addMods(std::vector<ModSpec> mods);
-
 	// checks if all dependencies are fullfilled.
 	bool isConsistent()
 	{
@@ -120,17 +114,27 @@ class ModConfiguration
 		return m_sorted_mods;
 	}
 
-	std::list<ModSpec> getUnsatisfiedMods()
+	std::vector<ModSpec> getUnsatisfiedMods()
 	{
 		return m_unsatisfied_mods;
 	}
 
 private:
+	// adds all mods in the given path. used for games, modpacks
+	// and world-specific mods (worldmods-folders)
+	void addModsInPath(std::string path);
 
-	// mods with unmet dependencies. This is a list and not a
-	// vector because we want easy removal of elements at every
-	// position.
-	std::list<ModSpec> m_unsatisfied_mods;
+	// adds all mods in the set.
+	void addMods(std::vector<ModSpec> new_mods);
+
+	// move mods from m_unsatisfied_mods to m_sorted_mods
+	// in an order that satisfies dependencies
+	void resolveDependencies();
+
+	// mods with unmet dependencies. Before dependencies are resolved,
+	// this is where all mods are stored. Afterwards this contains
+	// only the ones with really unsatisfied dependencies.
+	std::vector<ModSpec> m_unsatisfied_mods;
 
 	// list of mods sorted such that they can be loaded in the
 	// given order with all dependencies being fullfilled. I.e.,
@@ -138,6 +142,16 @@ class ModConfiguration
 	// appear earlier in the vector.
 	std::vector<ModSpec> m_sorted_mods;
 
+	// set of mod names for which an unresolved name conflict
+	// exists. A name conflict happens when two or more mods
+	// at the same level have the same name but different paths.
+	// Levels (mods in higher levels override mods in lower levels):
+	// 1. common mod in modpack; 2. common mod;
+	// 3. game mod in modpack; 4. game mod;
+	// 5. world mod in modpack; 6. world mod;
+	// 7. addon mod in modpack; 8. addon mod.
+	std::set<std::string> m_name_conflicts;
+
 };
 
 #endif
diff --git a/src/scriptapi.cpp b/src/scriptapi.cpp
index f0fe1950e0127b270fb58a0e8908cf5c5dc4079a..26759a05958957b5d7e498f834a41ae62d7394fe 100644
--- a/src/scriptapi.cpp
+++ b/src/scriptapi.cpp
@@ -76,8 +76,7 @@ bool scriptapi_loadmod(lua_State *L, const std::string &scriptpath,
 {
 	ModNameStorer modnamestorer(L, modname);
 
-	if(!string_allowed(modname, "abcdefghijklmnopqrstuvwxyz"
-			"0123456789_")){
+	if(!string_allowed(modname, MODNAME_ALLOWED_CHARS)){
 		errorstream<<"Error loading mod \""<<modname
 				<<"\": modname does not follow naming conventions: "
 				<<"Only chararacters [a-z0-9_] are allowed."<<std::endl;
diff --git a/src/server.cpp b/src/server.cpp
index d5e5051905a28fa2410b1178d26c1a113251d6f8..40a4f8a024bd675bfc70d9d091cf580be38add00 100644
--- a/src/server.cpp
+++ b/src/server.cpp
@@ -707,11 +707,11 @@ Server::Server(
 
 	ModConfiguration modconf(m_path_world);
 	m_mods = modconf.getMods();
-	std::list<ModSpec> unsatisfied_mods = modconf.getUnsatisfiedMods();
+	std::vector<ModSpec> unsatisfied_mods = modconf.getUnsatisfiedMods();
 	// complain about mods with unsatisfied dependencies
 	if(!modconf.isConsistent())	
 	{
-		for(std::list<ModSpec>::iterator it = unsatisfied_mods.begin();
+		for(std::vector<ModSpec>::iterator it = unsatisfied_mods.begin();
 			it != unsatisfied_mods.end(); ++it)
 		{
 			ModSpec mod = *it;
@@ -745,7 +745,7 @@ Server::Server(
 	for(std::vector<ModSpec>::iterator it = m_mods.begin();
 			it != m_mods.end(); ++it)
 		load_mod_names.erase((*it).name);
-	for(std::list<ModSpec>::iterator it = unsatisfied_mods.begin();
+	for(std::vector<ModSpec>::iterator it = unsatisfied_mods.begin();
 			it != unsatisfied_mods.end(); ++it)
 		load_mod_names.erase((*it).name);
 	if(!load_mod_names.empty())
@@ -2980,12 +2980,16 @@ void Server::ProcessData(u8 *data, u32 datasize, u16 peer_id)
 				playersao->setWieldedItem(item);
 			}
 
-			// If item has node placement prediction, always send the above
-			// node to make sure the client knows what exactly happened
+			// If item has node placement prediction, always send the
+			// blocks to make sure the client knows what exactly happened
 			if(item.getDefinition(m_itemdef).node_placement_prediction != ""){
 				RemoteClient *client = getClient(peer_id);
 				v3s16 blockpos = getNodeBlockPos(floatToInt(pointed_pos_above, BS));
 				client->SetBlockNotSent(blockpos);
+				v3s16 blockpos2 = getNodeBlockPos(floatToInt(pointed_pos_under, BS));
+				if(blockpos2 != blockpos){
+					client->SetBlockNotSent(blockpos2);
+				}
 			}
 		} // action == 3
 
@@ -5073,6 +5077,9 @@ PlayerSAO* Server::emergePlayer(const char *name, u16 peer_id)
 			getPlayerEffectivePrivs(player->getName()),
 			isSingleplayer());
 
+	/* Clean up old HUD elements from previous sessions */
+	player->hud.clear();
+
 	/* Add object to environment */
 	m_env->addActiveObject(playersao);
 
diff --git a/src/serverlist.cpp b/src/serverlist.cpp
index 7053436d055e42b7dc2e06e7c93820a567110201..ea5a616c249d8bad66cd9792b16d708f6a270ab5 100644
--- a/src/serverlist.cpp
+++ b/src/serverlist.cpp
@@ -53,7 +53,7 @@ std::vector<ServerListSpec> getLocal()
 	std::string liststring;
 	if(fs::PathExists(path))
 	{
-		std::ifstream istream(path.c_str(), std::ios::binary);
+		std::ifstream istream(path.c_str());
 		if(istream.is_open())
 		{
 			std::ostringstream ostream;
diff --git a/src/shader.cpp b/src/shader.cpp
index 62b7c99a936f71d0bd7081b6b3e852a4613e64a0..9cef7f353d321a22cc08fe592ab643324afd4fca 100644
--- a/src/shader.cpp
+++ b/src/shader.cpp
@@ -4,16 +4,16 @@ Copyright (C) 2013 celeron55, Perttu Ahola <celeron55@gmail.com>
 Copyright (C) 2013 Kahrl <kahrl@gmx.net>
 
 This program 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
+it under the terms of the GNU Lesser General Public License as published by
+the Free Software Foundation; either version 2.1 of the License, or
 (at your option) any later version.
 
 This program 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.
+GNU Lesser General Public License for more details.
 
-You should have received a copy of the GNU General Public License along
+You should have received a copy of the GNU Lesser General Public License along
 with this program; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 */
diff --git a/src/shader.h b/src/shader.h
index a62569602e1ac94dc3c329edda3a2be01841b05f..579cef635b346998c01bc2299fc86ac0c5147ec4 100644
--- a/src/shader.h
+++ b/src/shader.h
@@ -4,16 +4,16 @@ Copyright (C) 2013 celeron55, Perttu Ahola <celeron55@gmail.com>
 Copyright (C) 2013 Kahrl <kahrl@gmx.net>
 
 This program 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
+it under the terms of the GNU Lesser General Public License as published by
+the Free Software Foundation; either version 2.1 of the License, or
 (at your option) any later version.
 
 This program 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.
+GNU Lesser General Public License for more details.
 
-You should have received a copy of the GNU General Public License along
+You should have received a copy of the GNU Lesser General Public License along
 with this program; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 */
diff --git a/src/subgame.cpp b/src/subgame.cpp
index 19ad4e636be379fdd38171d175f72f42b49b455e..cdb5466197554cab4f411f1901536a8822ad6c09 100644
--- a/src/subgame.cpp
+++ b/src/subgame.cpp
@@ -22,6 +22,9 @@ with this program; if not, write to the Free Software Foundation, Inc.,
 #include "filesys.h"
 #include "settings.h"
 #include "log.h"
+#ifndef SERVER
+#include "tile.h" // getImagePath
+#endif
 #include "util/string.h"
 
 bool getGameMinetestConfig(const std::string &game_path, Settings &conf)
@@ -94,7 +97,12 @@ SubgameSpec findSubgame(const std::string &id)
 	std::string game_name = getGameName(game_path);
 	if(game_name == "")
 		game_name = id;
-	return SubgameSpec(id, game_path, gamemod_path, mods_paths, game_name);
+	std::string menuicon_path;
+#ifndef SERVER
+	menuicon_path = getImagePath(game_path + DIR_DELIM + "menu" + DIR_DELIM + "icon.png");
+#endif
+	return SubgameSpec(id, game_path, gamemod_path, mods_paths, game_name,
+			menuicon_path);
 }
 
 SubgameSpec findWorldSubgame(const std::string &world_path)
diff --git a/src/subgame.h b/src/subgame.h
index b120b75425482d8d5ee1e43dd54611734a63942b..4b15faa8d570546fe1c30ee0696b39271bf22fd3 100644
--- a/src/subgame.h
+++ b/src/subgame.h
@@ -35,17 +35,20 @@ struct SubgameSpec
 	std::string gamemods_path; //path to mods of the game
 	std::set<std::string> addon_mods_paths; //paths to addon mods for this game
 	std::string name;
+	std::string menuicon_path;
 
 	SubgameSpec(const std::string &id_="",
 			const std::string &path_="",	
 			const std::string &gamemods_path_="",
 			const std::set<std::string> &addon_mods_paths_=std::set<std::string>(),
-			const std::string &name_=""):
+			const std::string &name_="",
+			const std::string &menuicon_path_=""):
 		id(id_),
 		path(path_),
 		gamemods_path(gamemods_path_),		
 		addon_mods_paths(addon_mods_paths_),
-		name(name_)
+		name(name_),
+		menuicon_path(menuicon_path_)
 	{}
 
 	bool isValid() const
diff --git a/src/tile.cpp b/src/tile.cpp
index 39f47962e9da676f07fd09c5216b08ff0499f984..5f25e123bdd21556029e407cd81304a7e3cbee91 100644
--- a/src/tile.cpp
+++ b/src/tile.cpp
@@ -77,7 +77,7 @@ static bool replace_ext(std::string &path, const char *ext)
 
 	If failed, return "".
 */
-static std::string getImagePath(std::string path)
+std::string getImagePath(std::string path)
 {
 	// A NULL-ended list of possible image extensions
 	const char *extensions[] = {
diff --git a/src/tile.h b/src/tile.h
index ea5c4be54b4598d1aeadede016ed27a114448205..531a93172894a3639f33fdcf95a9874b677b8d1e 100644
--- a/src/tile.h
+++ b/src/tile.h
@@ -34,6 +34,17 @@ class IGameDef;
 	tile.{h,cpp}: Texture handling stuff.
 */
 
+/*
+	Find out the full path of an image by trying different filename
+	extensions.
+
+	If failed, return "".
+
+	TODO: Should probably be moved out from here, because things needing
+	      this function do not need anything else from this header
+*/
+std::string getImagePath(std::string path);
+
 /*
 	Gets the path to a texture by first checking if the texture exists
 	in texture_path and if not, using the data path.