diff --git a/src/game.cpp b/src/game.cpp
index 2e4485e36cd8416fdc1a9720699373d0c24d726e..03f526166c098ef909fca3ce4cae5661b6a00884 100644
--- a/src/game.cpp
+++ b/src/game.cpp
@@ -61,7 +61,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
 #include "sky.h"
 #include "sound.h"
 #if USE_SOUND
-	#include "sound_openal.h"
+#include "sound_openal.h"
 #endif
 #include "event_manager.h"
 #include <iomanip>
@@ -79,8 +79,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
 	Text input system
 */
 
-struct TextDestNodeMetadata : public TextDest
-{
+struct TextDestNodeMetadata : public TextDest {
 	TextDestNodeMetadata(v3s16 p, Client *client)
 	{
 		m_p = p;
@@ -90,8 +89,8 @@ struct TextDestNodeMetadata : public TextDest
 	void gotText(std::wstring text)
 	{
 		std::string ntext = wide_to_narrow(text);
-		infostream<<"Submitting 'text' field of node at ("<<m_p.X<<","
-				<<m_p.Y<<","<<m_p.Z<<"): "<<ntext<<std::endl;
+		infostream << "Submitting 'text' field of node at (" << m_p.X << ","
+			   << m_p.Y << "," << m_p.Z << "): " << ntext << std::endl;
 		std::map<std::string, std::string> fields;
 		fields["text"] = ntext;
 		m_client->sendNodemetaFields(m_p, "", fields);
@@ -105,8 +104,7 @@ struct TextDestNodeMetadata : public TextDest
 	Client *m_client;
 };
 
-struct TextDestPlayerInventory : public TextDest
-{
+struct TextDestPlayerInventory : public TextDest {
 	TextDestPlayerInventory(Client *client)
 	{
 		m_client = client;
@@ -125,8 +123,7 @@ struct TextDestPlayerInventory : public TextDest
 	Client *m_client;
 };
 
-struct LocalFormspecHandler : public TextDest
-{
+struct LocalFormspecHandler : public TextDest {
 	LocalFormspecHandler();
 	LocalFormspecHandler(std::string formname) :
 		m_client(0)
@@ -140,7 +137,8 @@ struct LocalFormspecHandler : public TextDest
 		m_formname = formname;
 	}
 
-	void gotText(std::wstring message) {
+	void gotText(std::wstring message)
+	{
 		errorstream << "LocalFormspecHandler::gotText old style message received" << std::endl;
 	}
 
@@ -180,19 +178,23 @@ struct LocalFormspecHandler : public TextDest
 				return;
 			}
 		}
+
 		if (m_formname == "MT_CHAT_MENU") {
 			assert(m_client != 0);
+
 			if ((fields.find("btn_send") != fields.end()) ||
 					(fields.find("quit") != fields.end())) {
 				if (fields.find("f_text") != fields.end()) {
 					m_client->typeChatMessage(narrow_to_wide(fields["f_text"]));
 				}
+
 				return;
 			}
 		}
 
 		if (m_formname == "MT_DEATH_SCREEN") {
 			assert(m_client != 0);
+
 			if ((fields.find("btn_respawn") != fields.end())) {
 				m_client->sendRespawn();
 				return;
@@ -205,18 +207,19 @@ struct LocalFormspecHandler : public TextDest
 		}
 
 		// don't show error message for unhandled cursor keys
-		if ( (fields.find("key_up") != fields.end()) ||
-			(fields.find("key_down") != fields.end()) ||
-			(fields.find("key_left") != fields.end()) ||
-			(fields.find("key_right") != fields.end())) {
+		if ((fields.find("key_up") != fields.end()) ||
+				(fields.find("key_down") != fields.end()) ||
+				(fields.find("key_left") != fields.end()) ||
+				(fields.find("key_right") != fields.end())) {
 			return;
 		}
 
 		errorstream << "LocalFormspecHandler::gotText unhandled >" << m_formname << "< event" << std::endl;
 		int i = 0;
-		for (std::map<std::string,std::string>::iterator iter = fields.begin();
+
+		for (std::map<std::string, std::string>::iterator iter = fields.begin();
 				iter != fields.end(); iter++) {
-			errorstream << "\t"<< i << ": " << iter->first << "=" << iter->second << std::endl;
+			errorstream << "\t" << i << ": " << iter->first << "=" << iter->second << std::endl;
 			i++;
 		}
 	}
@@ -237,15 +240,19 @@ class NodeMetadataFormSource: public IFormSource
 	std::string getForm()
 	{
 		NodeMetadata *meta = m_map->getNodeMetadata(m_p);
-		if(!meta)
+
+		if (!meta)
 			return "";
+
 		return meta->getString("formspec");
 	}
 	std::string resolveText(std::string str)
 	{
 		NodeMetadata *meta = m_map->getNodeMetadata(m_p);
-		if(!meta)
+
+		if (!meta)
 			return str;
+
 		return meta->resolveString(str);
 	}
 
@@ -262,7 +269,7 @@ class PlayerInventoryFormSource: public IFormSource
 	}
 	std::string getForm()
 	{
-		LocalPlayer* player = m_client->getEnv().getLocalPlayer();
+		LocalPlayer *player = m_client->getEnv().getLocalPlayer();
 		return player->inventory_formspec;
 	}
 
@@ -272,12 +279,12 @@ class PlayerInventoryFormSource: public IFormSource
 /*
 	Check if a node is pointable
 */
-inline bool isPointableNode(const MapNode& n,
-		Client *client, bool liquids_pointable)
+inline bool isPointableNode(const MapNode &n,
+			    Client *client, bool liquids_pointable)
 {
 	const ContentFeatures &features = client->getNodeDefManager()->get(n);
 	return features.pointable ||
-		(liquids_pointable && features.isLiquid());
+	       (liquids_pointable && features.isLiquid());
 }
 
 /*
@@ -299,15 +306,12 @@ PointedThing getPointedThing(Client *client, v3f player_position,
 	f32 mindistance = BS * 1001;
 
 	// First try to find a pointed at active object
-	if(look_for_object)
-	{
-		selected_object = client->getSelectedActiveObject(d*BS,
-				camera_position, shootline);
+	if (look_for_object) {
+		selected_object = client->getSelectedActiveObject(d * BS,
+				  camera_position, shootline);
 
-		if(selected_object != NULL)
-		{
-			if(selected_object->doShowSelectionBox())
-			{
+		if (selected_object != NULL) {
+			if (selected_object->doShowSelectionBox()) {
 				aabb3f *selection_box = selected_object->getSelectionBox();
 				// Box should exist because object was
 				// returned in the first place
@@ -315,8 +319,8 @@ PointedThing getPointedThing(Client *client, v3f player_position,
 
 				v3f pos = selected_object->getPosition();
 				hilightboxes.push_back(aabb3f(
-						selection_box->MinEdge + pos - intToFloat(camera_offset, BS),
-						selection_box->MaxEdge + pos - intToFloat(camera_offset, BS)));
+							       selection_box->MinEdge + pos - intToFloat(camera_offset, BS),
+							       selection_box->MaxEdge + pos - intToFloat(camera_offset, BS)));
 			}
 
 			mindistance = (selected_object->getPosition() - camera_position).getLength();
@@ -335,98 +339,99 @@ PointedThing getPointedThing(Client *client, v3f player_position,
 			<<std::endl;*/
 
 	s16 a = d;
-	s16 ystart = pos_i.Y + 0 - (camera_direction.Y<0 ? a : 1);
-	s16 zstart = pos_i.Z - (camera_direction.Z<0 ? a : 1);
-	s16 xstart = pos_i.X - (camera_direction.X<0 ? a : 1);
-	s16 yend = pos_i.Y + 1 + (camera_direction.Y>0 ? a : 1);
-	s16 zend = pos_i.Z + (camera_direction.Z>0 ? a : 1);
-	s16 xend = pos_i.X + (camera_direction.X>0 ? a : 1);
+	s16 ystart = pos_i.Y + 0 - (camera_direction.Y < 0 ? a : 1);
+	s16 zstart = pos_i.Z - (camera_direction.Z < 0 ? a : 1);
+	s16 xstart = pos_i.X - (camera_direction.X < 0 ? a : 1);
+	s16 yend = pos_i.Y + 1 + (camera_direction.Y > 0 ? a : 1);
+	s16 zend = pos_i.Z + (camera_direction.Z > 0 ? a : 1);
+	s16 xend = pos_i.X + (camera_direction.X > 0 ? a : 1);
 
 	// Prevent signed number overflow
-	if(yend==32767)
-		yend=32766;
-	if(zend==32767)
-		zend=32766;
-	if(xend==32767)
-		xend=32766;
-
-	for(s16 y = ystart; y <= yend; y++)
-	for(s16 z = zstart; z <= zend; z++)
-	for(s16 x = xstart; x <= xend; x++)
-	{
-		MapNode n;
-		try
-		{
-			n = map.getNode(v3s16(x,y,z));
-		}
-		catch(InvalidPositionException &e)
-		{
-			continue;
-		}
-		if(!isPointableNode(n, client, liquids_pointable))
-			continue;
-
-		std::vector<aabb3f> boxes = n.getSelectionBoxes(nodedef);
-
-		v3s16 np(x,y,z);
-		v3f npf = intToFloat(np, BS);
-
-		for(std::vector<aabb3f>::const_iterator
-				i = boxes.begin();
-				i != boxes.end(); i++)
-		{
-			aabb3f box = *i;
-			box.MinEdge += npf;
-			box.MaxEdge += npf;
-
-			for(u16 j=0; j<6; j++)
-			{
-				v3s16 facedir = g_6dirs[j];
-				aabb3f facebox = box;
-
-				f32 d = 0.001*BS;
-				if(facedir.X > 0)
-					facebox.MinEdge.X = facebox.MaxEdge.X-d;
-				else if(facedir.X < 0)
-					facebox.MaxEdge.X = facebox.MinEdge.X+d;
-				else if(facedir.Y > 0)
-					facebox.MinEdge.Y = facebox.MaxEdge.Y-d;
-				else if(facedir.Y < 0)
-					facebox.MaxEdge.Y = facebox.MinEdge.Y+d;
-				else if(facedir.Z > 0)
-					facebox.MinEdge.Z = facebox.MaxEdge.Z-d;
-				else if(facedir.Z < 0)
-					facebox.MaxEdge.Z = facebox.MinEdge.Z+d;
-
-				v3f centerpoint = facebox.getCenter();
-				f32 distance = (centerpoint - camera_position).getLength();
-				if(distance >= mindistance)
+	if (yend == 32767)
+		yend = 32766;
+
+	if (zend == 32767)
+		zend = 32766;
+
+	if (xend == 32767)
+		xend = 32766;
+
+	for (s16 y = ystart; y <= yend; y++)
+		for (s16 z = zstart; z <= zend; z++)
+			for (s16 x = xstart; x <= xend; x++) {
+				MapNode n;
+
+				try {
+					n = map.getNode(v3s16(x, y, z));
+				} catch (InvalidPositionException &e) {
 					continue;
-				if(!facebox.intersectsWithLine(shootline))
+				}
+
+				if (!isPointableNode(n, client, liquids_pointable))
 					continue;
 
-				v3s16 np_above = np + facedir;
-
-				result.type = POINTEDTHING_NODE;
-				result.node_undersurface = np;
-				result.node_abovesurface = np_above;
-				mindistance = distance;
-
-				hilightboxes.clear();
-				if (!g_settings->getBool("enable_node_highlighting")) {
-					for(std::vector<aabb3f>::const_iterator
-							i2 = boxes.begin();
-							i2 != boxes.end(); i2++)
-					{
-						aabb3f box = *i2;
-						box.MinEdge += npf + v3f(-d,-d,-d) - intToFloat(camera_offset, BS);
-						box.MaxEdge += npf + v3f(d,d,d) - intToFloat(camera_offset, BS);
-						hilightboxes.push_back(box);
+				std::vector<aabb3f> boxes = n.getSelectionBoxes(nodedef);
+
+				v3s16 np(x, y, z);
+				v3f npf = intToFloat(np, BS);
+
+				for (std::vector<aabb3f>::const_iterator
+						i = boxes.begin();
+						i != boxes.end(); i++) {
+					aabb3f box = *i;
+					box.MinEdge += npf;
+					box.MaxEdge += npf;
+
+					for (u16 j = 0; j < 6; j++) {
+						v3s16 facedir = g_6dirs[j];
+						aabb3f facebox = box;
+
+						f32 d = 0.001 * BS;
+
+						if (facedir.X > 0)
+							facebox.MinEdge.X = facebox.MaxEdge.X - d;
+						else if (facedir.X < 0)
+							facebox.MaxEdge.X = facebox.MinEdge.X + d;
+						else if (facedir.Y > 0)
+							facebox.MinEdge.Y = facebox.MaxEdge.Y - d;
+						else if (facedir.Y < 0)
+							facebox.MaxEdge.Y = facebox.MinEdge.Y + d;
+						else if (facedir.Z > 0)
+							facebox.MinEdge.Z = facebox.MaxEdge.Z - d;
+						else if (facedir.Z < 0)
+							facebox.MaxEdge.Z = facebox.MinEdge.Z + d;
+
+						v3f centerpoint = facebox.getCenter();
+						f32 distance = (centerpoint - camera_position).getLength();
+
+						if (distance >= mindistance)
+							continue;
+
+						if (!facebox.intersectsWithLine(shootline))
+							continue;
+
+						v3s16 np_above = np + facedir;
+
+						result.type = POINTEDTHING_NODE;
+						result.node_undersurface = np;
+						result.node_abovesurface = np_above;
+						mindistance = distance;
+
+						hilightboxes.clear();
+
+						if (!g_settings->getBool("enable_node_highlighting")) {
+							for (std::vector<aabb3f>::const_iterator
+									i2 = boxes.begin();
+									i2 != boxes.end(); i2++) {
+								aabb3f box = *i2;
+								box.MinEdge += npf + v3f(-d, -d, -d) - intToFloat(camera_offset, BS);
+								box.MaxEdge += npf + v3f(d, d, d) - intToFloat(camera_offset, BS);
+								hilightboxes.push_back(box);
+							}
+						}
 					}
 				}
-			}
-		}
-	} // for coords
+			} // for coords
 
 	return result;
 }
@@ -437,12 +442,9 @@ void update_profiler_gui(gui::IGUIStaticText *guitext_profiler,
 		gui::IGUIFont *font, u32 text_height, u32 show_profiler,
 		u32 show_profiler_max)
 {
-	if(show_profiler == 0)
-	{
+	if (show_profiler == 0) {
 		guitext_profiler->setVisible(false);
-	}
-	else
-	{
+	} else {
 
 		std::ostringstream os(std::ios_base::binary);
 		g_profiler->printPage(os, show_profiler, show_profiler_max);
@@ -451,11 +453,13 @@ void update_profiler_gui(gui::IGUIStaticText *guitext_profiler,
 		guitext_profiler->setVisible(true);
 
 		s32 w = font->getDimension(text.c_str()).Width;
-		if(w < 400)
+
+		if (w < 400)
 			w = 400;
-		core::rect<s32> rect(6, 4+(text_height+5)*2, 12+w,
-				8+(text_height+5)*2 +
-				font->getDimension(text.c_str()).Height);
+
+		core::rect<s32> rect(6, 4 + (text_height + 5) * 2, 12 + w,
+				     8 + (text_height + 5) * 2 +
+				     font->getDimension(text.c_str()).Height);
 		guitext_profiler->setRelativePosition(rect);
 		guitext_profiler->setVisible(true);
 	}
@@ -464,15 +468,15 @@ void update_profiler_gui(gui::IGUIStaticText *guitext_profiler,
 class ProfilerGraph
 {
 private:
-	struct Piece{
+	struct Piece {
 		Profiler::GraphValues values;
 	};
-	struct Meta{
+	struct Meta {
 		float min;
 		float max;
 		video::SColor color;
-		Meta(float initial=0, video::SColor color=
-				video::SColor(255,255,255,255)):
+		Meta(float initial = 0,
+			video::SColor color = video::SColor(255, 255, 255, 255)):
 			min(initial),
 			max(initial),
 			color(color)
@@ -491,52 +495,60 @@ class ProfilerGraph
 		Piece piece;
 		piece.values = values;
 		m_log.push_back(piece);
-		while(m_log.size() > m_log_max_size)
+
+		while (m_log.size() > m_log_max_size)
 			m_log.erase(m_log.begin());
 	}
 
 	void draw(s32 x_left, s32 y_bottom, video::IVideoDriver *driver,
-			gui::IGUIFont* font) const
+		  gui::IGUIFont *font) const
 	{
 		std::map<std::string, Meta> m_meta;
-		for(std::list<Piece>::const_iterator k = m_log.begin();
-				k != m_log.end(); k++)
-		{
+
+		for (std::list<Piece>::const_iterator k = m_log.begin();
+				k != m_log.end(); k++) {
 			const Piece &piece = *k;
-			for(Profiler::GraphValues::const_iterator i = piece.values.begin();
-					i != piece.values.end(); i++){
+
+			for (Profiler::GraphValues::const_iterator i = piece.values.begin();
+					i != piece.values.end(); i++) {
 				const std::string &id = i->first;
 				const float &value = i->second;
 				std::map<std::string, Meta>::iterator j =
-						m_meta.find(id);
-				if(j == m_meta.end()){
+					m_meta.find(id);
+
+				if (j == m_meta.end()) {
 					m_meta[id] = Meta(value);
 					continue;
 				}
-				if(value < j->second.min)
+
+				if (value < j->second.min)
 					j->second.min = value;
-				if(value > j->second.max)
+
+				if (value > j->second.max)
 					j->second.max = value;
 			}
 		}
 
 		// Assign colors
 		static const video::SColor usable_colors[] = {
-			video::SColor(255,255,100,100),
-			video::SColor(255,90,225,90),
-			video::SColor(255,100,100,255),
-			video::SColor(255,255,150,50),
-			video::SColor(255,220,220,100)
+			video::SColor(255, 255, 100, 100),
+			video::SColor(255, 90, 225, 90),
+			video::SColor(255, 100, 100, 255),
+			video::SColor(255, 255, 150, 50),
+			video::SColor(255, 220, 220, 100)
 		};
 		static const u32 usable_colors_count =
-				sizeof(usable_colors) / sizeof(*usable_colors);
+			sizeof(usable_colors) / sizeof(*usable_colors);
 		u32 next_color_i = 0;
-		for(std::map<std::string, Meta>::iterator i = m_meta.begin();
-				i != m_meta.end(); i++){
+
+		for (std::map<std::string, Meta>::iterator i = m_meta.begin();
+				i != m_meta.end(); i++) {
 			Meta &meta = i->second;
-			video::SColor color(255,200,200,200);
-			if(next_color_i < usable_colors_count)
+			video::SColor color(255, 200, 200, 200);
+
+			if (next_color_i < usable_colors_count)
 				color = usable_colors[next_color_i++];
+
 			meta.color = color;
 		}
 
@@ -554,80 +566,92 @@ class ProfilerGraph
 		}*/
 
 		s32 meta_i = 0;
-		for(std::map<std::string, Meta>::const_iterator i = m_meta.begin();
-				i != m_meta.end(); i++){
+
+		for (std::map<std::string, Meta>::const_iterator i = m_meta.begin();
+				i != m_meta.end(); i++) {
 			const std::string &id = i->first;
 			const Meta &meta = i->second;
 			s32 x = x_left;
 			s32 y = y_bottom - meta_i * 50;
 			float show_min = meta.min;
 			float show_max = meta.max;
-			if(show_min >= -0.0001 && show_max >= -0.0001){
-				if(show_min <= show_max * 0.5)
+
+			if (show_min >= -0.0001 && show_max >= -0.0001) {
+				if (show_min <= show_max * 0.5)
 					show_min = 0;
 			}
+
 			s32 texth = 15;
 			char buf[10];
 			snprintf(buf, 10, "%.3g", show_max);
 			font->draw(narrow_to_wide(buf).c_str(),
 					core::rect<s32>(textx, y - graphh,
-					textx2, y - graphh + texth),
+						   textx2, y - graphh + texth),
 					meta.color);
 			snprintf(buf, 10, "%.3g", show_min);
 			font->draw(narrow_to_wide(buf).c_str(),
 					core::rect<s32>(textx, y - texth,
-					textx2, y),
+						   textx2, y),
 					meta.color);
 			font->draw(narrow_to_wide(id).c_str(),
-					core::rect<s32>(textx, y - graphh/2 - texth/2,
-					textx2, y - graphh/2 + texth/2),
+					core::rect<s32>(textx, y - graphh / 2 - texth / 2,
+						   textx2, y - graphh / 2 + texth / 2),
 					meta.color);
 			s32 graph1y = y;
 			s32 graph1h = graphh;
 			bool relativegraph = (show_min != 0 && show_min != show_max);
 			float lastscaledvalue = 0.0;
 			bool lastscaledvalue_exists = false;
-			for(std::list<Piece>::const_iterator j = m_log.begin();
-					j != m_log.end(); j++)
-			{
+
+			for (std::list<Piece>::const_iterator j = m_log.begin();
+					j != m_log.end(); j++) {
 				const Piece &piece = *j;
 				float value = 0;
 				bool value_exists = false;
 				Profiler::GraphValues::const_iterator k =
-						piece.values.find(id);
-				if(k != piece.values.end()){
+					piece.values.find(id);
+
+				if (k != piece.values.end()) {
 					value = k->second;
 					value_exists = true;
 				}
-				if(!value_exists){
+
+				if (!value_exists) {
 					x++;
 					lastscaledvalue_exists = false;
 					continue;
 				}
+
 				float scaledvalue = 1.0;
-				if(show_max != show_min)
+
+				if (show_max != show_min)
 					scaledvalue = (value - show_min) / (show_max - show_min);
-				if(scaledvalue == 1.0 && value == 0){
+
+				if (scaledvalue == 1.0 && value == 0) {
 					x++;
 					lastscaledvalue_exists = false;
 					continue;
 				}
-				if(relativegraph){
-					if(lastscaledvalue_exists){
+
+				if (relativegraph) {
+					if (lastscaledvalue_exists) {
 						s32 ivalue1 = lastscaledvalue * graph1h;
 						s32 ivalue2 = scaledvalue * graph1h;
-						driver->draw2DLine(v2s32(x-1, graph1y - ivalue1),
-								v2s32(x, graph1y - ivalue2), meta.color);
+						driver->draw2DLine(v2s32(x - 1, graph1y - ivalue1),
+								   v2s32(x, graph1y - ivalue2), meta.color);
 					}
+
 					lastscaledvalue = scaledvalue;
 					lastscaledvalue_exists = true;
-				} else{
+				} else {
 					s32 ivalue = scaledvalue * graph1h;
 					driver->draw2DLine(v2s32(x, graph1y),
-							v2s32(x, graph1y - ivalue), meta.color);
+							   v2s32(x, graph1y - ivalue), meta.color);
 				}
+
 				x++;
 			}
+
 			meta_i++;
 		}
 	}
@@ -643,8 +667,10 @@ class NodeDugEvent: public MtEvent
 		p(p),
 		n(n)
 	{}
-	const char* getType() const
-	{return "NodeDug";}
+	const char *getType() const
+	{
+		return "NodeDug";
+	}
 };
 
 class SoundMaker
@@ -667,7 +693,7 @@ class SoundMaker
 
 	void playPlayerStep()
 	{
-		if(m_player_step_timer <= 0 && m_player_step_sound.exists()){
+		if (m_player_step_timer <= 0 && m_player_step_sound.exists()) {
 			m_player_step_timer = 0.03;
 			m_sound->playSound(m_player_step_sound, false);
 		}
@@ -675,13 +701,13 @@ class SoundMaker
 
 	static void viewBobbingStep(MtEvent *e, void *data)
 	{
-		SoundMaker *sm = (SoundMaker*)data;
+		SoundMaker *sm = (SoundMaker *)data;
 		sm->playPlayerStep();
 	}
 
 	static void playerRegainGround(MtEvent *e, void *data)
 	{
-		SoundMaker *sm = (SoundMaker*)data;
+		SoundMaker *sm = (SoundMaker *)data;
 		sm->playPlayerStep();
 	}
 
@@ -692,32 +718,32 @@ class SoundMaker
 
 	static void cameraPunchLeft(MtEvent *e, void *data)
 	{
-		SoundMaker *sm = (SoundMaker*)data;
+		SoundMaker *sm = (SoundMaker *)data;
 		sm->m_sound->playSound(sm->m_player_leftpunch_sound, false);
 	}
 
 	static void cameraPunchRight(MtEvent *e, void *data)
 	{
-		SoundMaker *sm = (SoundMaker*)data;
+		SoundMaker *sm = (SoundMaker *)data;
 		sm->m_sound->playSound(sm->m_player_rightpunch_sound, false);
 	}
 
 	static void nodeDug(MtEvent *e, void *data)
 	{
-		SoundMaker *sm = (SoundMaker*)data;
-		NodeDugEvent *nde = (NodeDugEvent*)e;
+		SoundMaker *sm = (SoundMaker *)data;
+		NodeDugEvent *nde = (NodeDugEvent *)e;
 		sm->m_sound->playSound(sm->m_ndef->get(nde->n).sound_dug, false);
 	}
 
 	static void playerDamage(MtEvent *e, void *data)
 	{
-		SoundMaker *sm = (SoundMaker*)data;
+		SoundMaker *sm = (SoundMaker *)data;
 		sm->m_sound->playSound(SimpleSoundSpec("player_damage", 0.5), false);
 	}
 
 	static void playerFallingDamage(MtEvent *e, void *data)
 	{
-		SoundMaker *sm = (SoundMaker*)data;
+		SoundMaker *sm = (SoundMaker *)data;
 		sm->m_sound->playSound(SimpleSoundSpec("player_falling_damage", 0.5), false);
 	}
 
@@ -744,13 +770,13 @@ class GameOnDemandSoundFetcher: 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))
+		if (m_fetched.count(name))
 			return;
+
 		m_fetched.insert(name);
 		std::string base = porting::path_share + DIR_DELIM + "testsounds";
 		dst_paths.insert(base + DIR_DELIM + name + ".ogg");
@@ -776,7 +802,7 @@ class GameGlobalShaderConstantSetter : public IShaderConstantSetter
 
 public:
 	GameGlobalShaderConstantSetter(Sky *sky, bool *force_fog_off,
-			f32 *fog_range, Client *client):
+			f32 *fog_range, Client *client) :
 		m_sky(sky),
 		m_force_fog_off(force_fog_off),
 		m_fog_range(fog_range),
@@ -787,7 +813,7 @@ class GameGlobalShaderConstantSetter : public IShaderConstantSetter
 	virtual void onSetConstants(video::IMaterialRendererServices *services,
 			bool is_highlevel)
 	{
-		if(!is_highlevel)
+		if (!is_highlevel)
 			return;
 
 		// Background color
@@ -802,9 +828,11 @@ class GameGlobalShaderConstantSetter : public IShaderConstantSetter
 		services->setPixelShaderConstant("skyBgColor", bgcolorfa, 4);
 
 		// Fog distance
-		float fog_distance = 10000*BS;
-		if(g_settings->getBool("enable_fog") && !*m_force_fog_off)
+		float fog_distance = 10000 * BS;
+
+		if (g_settings->getBool("enable_fog") && !*m_force_fog_off)
 			fog_distance = *m_fog_range;
+
 		services->setPixelShaderConstant("fogDistance", &fog_distance, 1);
 
 		// Day-night ratio
@@ -817,10 +845,10 @@ class GameGlobalShaderConstantSetter : public IShaderConstantSetter
 		services->setPixelShaderConstant("animationTimer", &animation_timer_f, 1);
 		services->setVertexShaderConstant("animationTimer", &animation_timer_f, 1);
 
-		LocalPlayer* player = m_client->getEnv().getLocalPlayer();
+		LocalPlayer *player = m_client->getEnv().getLocalPlayer();
 		v3f eye_position = player->getEyePosition();
-		services->setPixelShaderConstant("eyePosition", (irr::f32*)&eye_position, 3);
-		services->setVertexShaderConstant("eyePosition", (irr::f32*)&eye_position, 3);
+		services->setPixelShaderConstant("eyePosition", (irr::f32 *)&eye_position, 3);
+		services->setVertexShaderConstant("eyePosition", (irr::f32 *)&eye_position, 3);
 
 		// Uniform sampler layers
 		int layer0 = 0;
@@ -828,13 +856,13 @@ class GameGlobalShaderConstantSetter : public IShaderConstantSetter
 		int layer2 = 2;
 		// before 1.8 there isn't a "integer interface", only float
 #if (IRRLICHT_VERSION_MAJOR == 1 && IRRLICHT_VERSION_MINOR < 8)
-		services->setPixelShaderConstant("baseTexture" , (irr::f32*)&layer0, 1);
-		services->setPixelShaderConstant("normalTexture" , (irr::f32*)&layer1, 1);
-		services->setPixelShaderConstant("useNormalmap" , (irr::f32*)&layer2, 1);
+		services->setPixelShaderConstant("baseTexture" , (irr::f32 *)&layer0, 1);
+		services->setPixelShaderConstant("normalTexture" , (irr::f32 *)&layer1, 1);
+		services->setPixelShaderConstant("useNormalmap" , (irr::f32 *)&layer2, 1);
 #else
-		services->setPixelShaderConstant("baseTexture" , (irr::s32*)&layer0, 1);
-		services->setPixelShaderConstant("normalTexture" , (irr::s32*)&layer1, 1);
-		services->setPixelShaderConstant("useNormalmap" , (irr::s32*)&layer2, 1);
+		services->setPixelShaderConstant("baseTexture" , (irr::s32 *)&layer0, 1);
+		services->setPixelShaderConstant("normalTexture" , (irr::s32 *)&layer1, 1);
+		services->setPixelShaderConstant("useNormalmap" , (irr::s32 *)&layer2, 1);
 #endif
 	}
 };
@@ -846,105 +874,120 @@ bool nodePlacementPrediction(Client &client,
 	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;
+	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{
+		try {
 			MapNode n_under = map.getNode(nodepos);
-			if(nodedef->get(n_under).buildable_to)
+
+			if (nodedef->get(n_under).buildable_to)
 				p = nodepos;
 			else if (!nodedef->get(map.getNode(p)).buildable_to)
 				return false;
-		}catch(InvalidPositionException &e){}
+		} 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;
+
+		if (!found) {
+			errorstream << "Node placement prediction failed for "
+				    << playeritem_def.name << " (places "
+				    << prediction
+				    << ") - Name not known" << std::endl;
 			return false;
 		}
+
 		// Predict param2 for facedir and wallmounted nodes
 		u8 param2 = 0;
-		if(nodedef->get(id).param_type_2 == CPT2_WALLMOUNTED){
+
+		if (nodedef->get(id).param_type_2 == CPT2_WALLMOUNTED) {
 			v3s16 dir = nodepos - neighbourpos;
-			if(abs(dir.Y) > MYMAX(abs(dir.X), abs(dir.Z))){
+
+			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)){
+			} else if (abs(dir.X) > abs(dir.Z)) {
 				param2 = dir.X < 0 ? 3 : 2;
 			} else {
 				param2 = dir.Z < 0 ? 5 : 4;
 			}
 		}
-		if(nodedef->get(id).param_type_2 == CPT2_FACEDIR){
+
+		if (nodedef->get(id).param_type_2 == CPT2_FACEDIR) {
 			v3s16 dir = nodepos - floatToInt(client.getEnv().getLocalPlayer()->getPosition(), BS);
-			if(abs(dir.X) > abs(dir.Z)){
+
+			if (abs(dir.X) > abs(dir.Z)) {
 				param2 = dir.X < 0 ? 3 : 1;
 			} else {
 				param2 = dir.Z < 0 ? 2 : 0;
 			}
 		}
-		assert(param2 >= 0 && param2 <= 5);
+
+		assert(param2 <= 5);
+
 		//Check attachment if node is in group attached_node
-		if(((ItemGroupList) nodedef->get(id).groups)["attached_node"] != 0){
+		if (((ItemGroupList) nodedef->get(id).groups)["attached_node"] != 0) {
 			static v3s16 wallmounted_dirs[8] = {
-				v3s16(0,1,0),
-				v3s16(0,-1,0),
-				v3s16(1,0,0),
-				v3s16(-1,0,0),
-				v3s16(0,0,1),
-				v3s16(0,0,-1),
+				v3s16(0, 1, 0),
+				v3s16(0, -1, 0),
+				v3s16(1, 0, 0),
+				v3s16(-1, 0, 0),
+				v3s16(0, 0, 1),
+				v3s16(0, 0, -1),
 			};
 			v3s16 pp;
-			if(nodedef->get(id).param_type_2 == CPT2_WALLMOUNTED)
+
+			if (nodedef->get(id).param_type_2 == CPT2_WALLMOUNTED)
 				pp = p + wallmounted_dirs[param2];
 			else
-				pp = p + v3s16(0,-1,0);
-			if(!nodedef->get(map.getNode(pp)).walkable)
+				pp = p + v3s16(0, -1, 0);
+
+			if (!nodedef->get(map.getNode(pp)).walkable)
 				return false;
 		}
+
 		// Add node to client map
 		MapNode n(id, 0, param2);
-		try{
-			LocalPlayer* player = client.getEnv().getLocalPlayer();
+
+		try {
+			LocalPlayer *player = client.getEnv().getLocalPlayer();
 
 			// Dont place node when player would be inside new node
 			// NOTE: This is to be eventually implemented by a mod as client-side Lua
 			if (!nodedef->get(n).walkable ||
-				(client.checkPrivilege("noclip") && g_settings->getBool("noclip")) ||
-				(nodedef->get(n).walkable &&
-				neighbourpos != player->getStandingNodePos() + v3s16(0,1,0) &&
-				neighbourpos != player->getStandingNodePos() + v3s16(0,2,0))) {
-
-					// This triggers the required mesh update too
-					client.addNode(p, n);
-					return true;
-				}
-		}catch(InvalidPositionException &e){
-			errorstream<<"Node placement prediction failed for "
-					<<playeritem_def.name<<" (places "
-					<<prediction
-					<<") - Position not loaded"<<std::endl;
+					(client.checkPrivilege("noclip") && g_settings->getBool("noclip")) ||
+					(nodedef->get(n).walkable &&
+					 neighbourpos != player->getStandingNodePos() + v3s16(0, 1, 0) &&
+					 neighbourpos != player->getStandingNodePos() + v3s16(0, 2, 0))) {
+
+				// This triggers the required mesh update too
+				client.addNode(p, n);
+				return true;
+			}
+		} catch (InvalidPositionException &e) {
+			errorstream << "Node placement prediction failed for "
+				    << playeritem_def.name << " (places "
+				    << prediction
+				    << ") - Position not loaded" << std::endl;
 		}
 	}
+
 	return false;
 }
 
-static inline void create_formspec_menu(GUIFormSpecMenu** cur_formspec,
+static inline void create_formspec_menu(GUIFormSpecMenu **cur_formspec,
 		InventoryManager *invmgr, IGameDef *gamedef,
-		IWritableTextureSource* tsrc, IrrlichtDevice * device,
-		IFormSource* fs_src, TextDest* txt_dest, Client* client
-		) {
+		IWritableTextureSource *tsrc, IrrlichtDevice *device,
+		IFormSource *fs_src, TextDest *txt_dest, Client *client)
+{
 
 	if (*cur_formspec == 0) {
 		*cur_formspec = new GUIFormSpecMenu(device, guiroot, -1, &g_menumgr,
-				invmgr, gamedef, tsrc, fs_src, txt_dest, client);
+						    invmgr, gamedef, tsrc, fs_src, txt_dest, client);
 		(*cur_formspec)->doPause = false;
 
 		/*
@@ -954,8 +997,8 @@ static inline void create_formspec_menu(GUIFormSpecMenu** cur_formspec,
 			remaining reference (i.e. the menu was removed)
 			and delete it in that case.
 		*/
-	}
-	else {
+
+	} else {
 		(*cur_formspec)->setFormSource(fs_src);
 		(*cur_formspec)->setTextDest(txt_dest);
 	}
@@ -967,10 +1010,10 @@ static inline void create_formspec_menu(GUIFormSpecMenu** cur_formspec,
 #define SIZE_TAG "size[11,5.5,true]"
 #endif
 
-static void show_chat_menu(GUIFormSpecMenu** cur_formspec,
+static void show_chat_menu(GUIFormSpecMenu **cur_formspec,
 		InventoryManager *invmgr, IGameDef *gamedef,
-		IWritableTextureSource* tsrc, IrrlichtDevice * device,
-		Client* client, std::string text)
+		IWritableTextureSource *tsrc, IrrlichtDevice *device,
+		Client *client, std::string text)
 {
 	std::string formspec =
 		FORMSPEC_VERSION_STRING
@@ -982,15 +1025,15 @@ static void show_chat_menu(GUIFormSpecMenu** cur_formspec,
 	/* Create menu */
 	/* Note: FormspecFormSource and LocalFormspecHandler
 	 * are deleted by guiFormSpecMenu                     */
-	FormspecFormSource* fs_src = new FormspecFormSource(formspec);
-	LocalFormspecHandler* txt_dst = new LocalFormspecHandler("MT_CHAT_MENU", client);
+	FormspecFormSource *fs_src = new FormspecFormSource(formspec);
+	LocalFormspecHandler *txt_dst = new LocalFormspecHandler("MT_CHAT_MENU", client);
 
 	create_formspec_menu(cur_formspec, invmgr, gamedef, tsrc, device, fs_src, txt_dst, NULL);
 }
 
-static void show_deathscreen(GUIFormSpecMenu** cur_formspec,
+static void show_deathscreen(GUIFormSpecMenu **cur_formspec,
 		InventoryManager *invmgr, IGameDef *gamedef,
-		IWritableTextureSource* tsrc, IrrlichtDevice * device, Client* client)
+		IWritableTextureSource *tsrc, IrrlichtDevice *device, Client *client)
 {
 	std::string formspec =
 		std::string(FORMSPEC_VERSION_STRING) +
@@ -1003,67 +1046,67 @@ static void show_deathscreen(GUIFormSpecMenu** cur_formspec,
 	/* Create menu */
 	/* Note: FormspecFormSource and LocalFormspecHandler
 	 * are deleted by guiFormSpecMenu                     */
-	FormspecFormSource* fs_src = new FormspecFormSource(formspec);
-	LocalFormspecHandler* txt_dst = new LocalFormspecHandler("MT_DEATH_SCREEN", client);
+	FormspecFormSource *fs_src = new FormspecFormSource(formspec);
+	LocalFormspecHandler *txt_dst = new LocalFormspecHandler("MT_DEATH_SCREEN", client);
 
 	create_formspec_menu(cur_formspec, invmgr, gamedef, tsrc, device,  fs_src, txt_dst, NULL);
 }
 
 /******************************************************************************/
-static void show_pause_menu(GUIFormSpecMenu** cur_formspec,
+static void show_pause_menu(GUIFormSpecMenu **cur_formspec,
 		InventoryManager *invmgr, IGameDef *gamedef,
-		IWritableTextureSource* tsrc, IrrlichtDevice * device,
+		IWritableTextureSource *tsrc, IrrlichtDevice *device,
 		bool singleplayermode)
 {
 #ifdef __ANDROID__
 	std::string control_text = wide_to_narrow(wstrgettext("Default Controls:\n"
-			"No menu visible:\n"
-			"- single tap: button activate\n"
-			"- double tap: place/use\n"
-			"- slide finger: look around\n"
-			"Menu/Inventory visible:\n"
-			"- double tap (outside):\n"
-			" -->close\n"
-			"- touch stack, touch slot:\n"
-			" --> move stack\n"
-			"- touch&drag, tap 2nd finger\n"
-			" --> place single item to slot\n"
-			));
+				   "No menu visible:\n"
+				   "- single tap: button activate\n"
+				   "- double tap: place/use\n"
+				   "- slide finger: look around\n"
+				   "Menu/Inventory visible:\n"
+				   "- double tap (outside):\n"
+				   " -->close\n"
+				   "- touch stack, touch slot:\n"
+				   " --> move stack\n"
+				   "- touch&drag, tap 2nd finger\n"
+				   " --> place single item to slot\n"
+							     ));
 #else
 	std::string control_text = wide_to_narrow(wstrgettext("Default Controls:\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"
-			"- T: chat\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"
+				   "- T: chat\n"
+							     ));
 #endif
 
 	float ypos = singleplayermode ? 0.5 : 0.1;
 	std::ostringstream os;
 
 	os << FORMSPEC_VERSION_STRING  << SIZE_TAG
-			<< "button_exit[4," << (ypos++) << ";3,0.5;btn_continue;"
-					<< wide_to_narrow(wstrgettext("Continue"))     << "]";
+	   << "button_exit[4," << (ypos++) << ";3,0.5;btn_continue;"
+	   << wide_to_narrow(wstrgettext("Continue"))     << "]";
 
 	if (!singleplayermode) {
 		os << "button_exit[4," << (ypos++) << ";3,0.5;btn_change_password;"
-					<< wide_to_narrow(wstrgettext("Change Password")) << "]";
-		}
+		   << wide_to_narrow(wstrgettext("Change Password")) << "]";
+	}
 
 	os		<< "button_exit[4," << (ypos++) << ";3,0.5;btn_sound;"
-					<< wide_to_narrow(wstrgettext("Sound Volume")) << "]";
+			<< wide_to_narrow(wstrgettext("Sound Volume")) << "]";
 	os		<< "button_exit[4," << (ypos++) << ";3,0.5;btn_key_config;"
-					<< wide_to_narrow(wstrgettext("Change Keys"))  << "]";
+			<< wide_to_narrow(wstrgettext("Change Keys"))  << "]";
 	os		<< "button_exit[4," << (ypos++) << ";3,0.5;btn_exit_menu;"
-					<< wide_to_narrow(wstrgettext("Exit to Menu")) << "]";
+			<< wide_to_narrow(wstrgettext("Exit to Menu")) << "]";
 	os		<< "button_exit[4," << (ypos++) << ";3,0.5;btn_exit_os;"
-					<< wide_to_narrow(wstrgettext("Exit to OS"))   << "]"
+			<< wide_to_narrow(wstrgettext("Exit to OS"))   << "]"
 			<< "textarea[7.5,0.25;3.9,6.25;;" << control_text << ";]"
 			<< "textarea[0.4,0.25;3.5,6;;" << "Minetest\n"
 			<< minetest_build_info << "\n"
@@ -1073,8 +1116,8 @@ static void show_pause_menu(GUIFormSpecMenu** cur_formspec,
 	/* Create menu */
 	/* Note: FormspecFormSource and LocalFormspecHandler  *
 	 * are deleted by guiFormSpecMenu                     */
-	FormspecFormSource* fs_src = new FormspecFormSource(os.str());
-	LocalFormspecHandler* txt_dst = new LocalFormspecHandler("MT_PAUSE_MENU");
+	FormspecFormSource *fs_src = new FormspecFormSource(os.str());
+	LocalFormspecHandler *txt_dst = new LocalFormspecHandler("MT_PAUSE_MENU");
 
 	create_formspec_menu(cur_formspec, invmgr, gamedef, tsrc, device,  fs_src, txt_dst, NULL);
 
@@ -1082,21 +1125,22 @@ static void show_pause_menu(GUIFormSpecMenu** cur_formspec,
 }
 
 /******************************************************************************/
-static void updateChat(Client& client, f32 dtime, bool show_debug,
-		const v2u32& screensize, bool show_chat, u32 show_profiler,
-		ChatBackend& chat_backend, gui::IGUIStaticText* guitext_chat,
-		gui::IGUIFont* font)
+static void updateChat(Client &client, f32 dtime, bool show_debug,
+		const v2u32 &screensize, bool show_chat, u32 show_profiler,
+		ChatBackend &chat_backend, gui::IGUIStaticText *guitext_chat,
+		gui::IGUIFont *font)
 {
 	// Add chat log output for errors to be shown in chat
 	static LogOutputBuffer chat_log_error_buf(LMT_ERROR);
 
 	// Get new messages from error log buffer
-	while(!chat_log_error_buf.empty()) {
+	while (!chat_log_error_buf.empty()) {
 		chat_backend.addMessage(L"", narrow_to_wide(chat_log_error_buf.get()));
 	}
 
 	// Get new messages from client
 	std::wstring message;
+
 	while (client.getChatMessage(message)) {
 		chat_backend.addUnparsedMessage(message);
 	}
@@ -1115,2486 +1159,2853 @@ static void updateChat(Client& client, f32 dtime, bool show_debug,
 
 	// Update gui element size and position
 	s32 chat_y = 5 + line_height;
+
 	if (show_debug)
 		chat_y += line_height;
 
 	// first pass to calculate height of text to be set
 	s32 width = std::min(font->getDimension(recent_chat.c_str()).Width + 10,
-			porting::getWindowSize().X - 20);
+			     porting::getWindowSize().X - 20);
 	core::rect<s32> rect(10, chat_y, width, chat_y + porting::getWindowSize().Y);
 	guitext_chat->setRelativePosition(rect);
 
-	//now use real height of text and adjust rect according to this size	
+	//now use real height of text and adjust rect according to this size
 	rect = core::rect<s32>(10, chat_y, width,
-			chat_y + guitext_chat->getTextHeight());
+			       chat_y + guitext_chat->getTextHeight());
 
 
 	guitext_chat->setRelativePosition(rect);
 	// Don't show chat if disabled or empty or profiler is enabled
 	guitext_chat->setVisible(
-			show_chat && recent_chat_count != 0 && !show_profiler);
+		show_chat && recent_chat_count != 0 && !show_profiler);
 }
 
-/******************************************************************************/
-void the_game(bool &kill, bool random_input, InputHandler *input,
-	IrrlichtDevice *device, gui::IGUIFont* font, std::string map_dir,
-	std::string playername, std::string password,
-	std::string address /* If "", local server is used */,
-	u16 port, std::wstring &error_message, ChatBackend &chat_backend,
-	const SubgameSpec &gamespec /* Used for local game */,
-	bool simple_singleplayer_mode)
-{
-	GUIFormSpecMenu* current_formspec = 0;
-	video::IVideoDriver* driver = device->getVideoDriver();
-	scene::ISceneManager* smgr = device->getSceneManager();
 
-	// Calculate text height using the font
-	u32 text_height = font->getDimension(L"Random test string").Height;
 
-	/*
-		Draw "Loading" screen
-	*/
+/****************************************************************************
+ THE GAME
+ ****************************************************************************/
 
-	{
-		wchar_t* text = wgettext("Loading...");
-		draw_load_screen(text, device, guienv, font, 0, 0);
-		delete[] text;
-	}
+const float object_hit_delay = 0.2;
 
-	// Create texture source
-	IWritableTextureSource *tsrc = createTextureSource(device);
+struct FpsControl {
+	u32 last_time, busy_time, sleep_time;
+};
 
-	// Create shader source
-	IWritableShaderSource *shsrc = createShaderSource(device);
 
-	// These will be filled by data received from the server
-	// Create item definition manager
-	IWritableItemDefManager *itemdef = createItemDefManager();
-	// Create node definition manager
-	IWritableNodeDefManager *nodedef = createNodeDefManager();
+/* The reason the following structs are not anonymous structs within the
+ * class is that they are not used by the majority of member functions and
+ * many functions that do require objects of thse types do not modify them
+ * (so they can be passed as a const qualified parameter)
+ */
 
-	// Sound fetcher (useful when testing)
-	GameOnDemandSoundFetcher soundfetcher;
+struct CameraOrientation {
+	f32 camera_yaw;    // "right/left"
+	f32 camera_pitch;  // "up/down"
+};
 
-	// Sound manager
-	ISoundManager *sound = NULL;
-	bool sound_is_dummy = false;
-#if USE_SOUND
-	if(g_settings->getBool("enable_sound")){
-		infostream<<"Attempting to use OpenAL audio"<<std::endl;
-		sound = createOpenALSoundManager(&soundfetcher);
-		if(!sound)
-			infostream<<"Failed to initialize OpenAL audio"<<std::endl;
-	} else {
-		infostream<<"Sound disabled."<<std::endl;
-	}
-#endif
-	if(!sound){
-		infostream<<"Using dummy audio."<<std::endl;
-		sound = &dummySoundManager;
-		sound_is_dummy = true;
-	}
+//TODO: Needs a better name because fog_range etc are included
+struct InteractParams {
+	u16 dig_index;
+	u16 new_playeritem;
+	PointedThing pointed_old;
+	bool digging;
+	bool ldown_for_dig;
+	bool left_punch;
+	float nodig_delay_timer;
+	float dig_time;
+	float dig_time_complete;
+	float repeat_rightclick_timer;
+	float object_hit_delay_timer;
+	float time_from_last_punch;
+	ClientActiveObject *selected_object;
+
+	float jump_timer;
+	float damage_flash;
+	float update_draw_list_timer;
+	float statustext_time;
+
+	f32 fog_range;
 
-	Server *server = NULL;
+	v3f update_draw_list_last_cam_dir;
 
-	try{
-	// Event manager
-	EventManager eventmgr;
+	u32 profiler_current_page;
+	u32 profiler_max_page;     // Number of pages
+};
 
-	// Sound maker
-	SoundMaker soundmaker(sound, nodedef);
-	soundmaker.registerReceiver(&eventmgr);
+struct Jitter {
+	f32 max, min, avg, counter, max_sample, min_sample, max_fraction;
+};
 
-	// Create UI for modifying quicktune values
-	QuicktuneShortcutter quicktune;
+struct RunStats {
+	u32 drawtime;
+	u32 beginscenetime;
+	u32 endscenetime;
 
-	/*
-		Create server.
-	*/
+	Jitter dtime_jitter, busy_time_jitter;
+};
 
-	if(address == ""){
-		wchar_t* text = wgettext("Creating server....");
-		draw_load_screen(text, device, guienv, font, 0, 25);
-		delete[] text;
-		infostream<<"Creating server"<<std::endl;
+/* Flags that can, or may, change during main game loop
+ */
+struct VolatileRunFlags {
+	bool invert_mouse;
+	bool show_chat;
+	bool show_hud;
+	bool force_fog_off;
+	bool show_debug;
+	bool show_profiler_graph;
+	bool disable_camera_update;
+	bool first_loop_after_window_activation;
+	bool camera_offset_changed;
+};
 
-		std::string bind_str = g_settings->get("bind_address");
-		Address bind_addr(0,0,0,0, port);
 
-		if (g_settings->getBool("ipv6_server")) {
-			bind_addr.setAddress((IPv6AddressBytes*) NULL);
-		}
-		try {
-			bind_addr.Resolve(bind_str.c_str());
-			address = bind_str;
-		} catch (ResolveError &e) {
-			infostream << "Resolving bind address \"" << bind_str
-					   << "\" failed: " << e.what()
-					   << " -- Listening on all addresses." << std::endl;
-		}
+/* This is not intended to be a public class. If a public class becomes
+ * desirable then it may be better to create another 'wrapper' class that
+ * hides most of the stuff in this class (nothing in this class is required
+ * by any other file) but exposes the public methods/data only.
+ */
+class MinetestApp
+{
+public:
+	MinetestApp();
+	~MinetestApp();
+
+	bool startup(bool *kill,
+			bool random_input,
+			InputHandler *input,
+			IrrlichtDevice *device,
+			gui::IGUIFont *font,
+			const std::string &map_dir,
+			const std::string &playername,
+			const std::string &password,
+			// If address is "", local server is used and address is updated
+			std::string *address,
+			u16 port,
+			std::wstring *error_message,
+			ChatBackend *chat_backend,
+			const SubgameSpec &gamespec,    // Used for local game
+			bool simple_singleplayer_mode);
+
+	void run();
+	void shutdown();
+
+protected:
+
+	void extendedResourceCleanup();
+
+	// Basic initialisation
+	bool init(const std::string &map_dir, std::string *address,
+			u16 port,
+			const SubgameSpec &gamespec);
+	bool initSound();
+	bool createSingleplayerServer(const std::string map_dir,
+			const SubgameSpec &gamespec, u16 port, std::string *address);
+
+	// Client creation
+	bool createClient(const std::string &playername,
+			const std::string &password, std::string *address, u16 port,
+			std::wstring *error_message);
+	bool initGui(std::wstring *error_message);
+
+	// Client connection
+	bool connectToServer(const std::string &playername,
+			const std::string &password, std::string *address, u16 port,
+			bool *connect_ok, bool *aborted);
+	bool getServerContent(bool *aborted);
+
+	// Main loop
+
+	void updateInteractTimers(InteractParams *args, f32 dtime);
+	bool checkConnection();
+	bool handleCallbacks();
+	void processQueues();
+	void addProfilerGraphs(const RunStats &stats, const FpsControl &draw_times,
+			f32 dtime);
+	void updateStats(RunStats *stats, const FpsControl &draw_times, f32 dtime);
+
+	void processUserInput(VolatileRunFlags *flags, InteractParams *interact_args,
+			f32 dtime);
+	void processKeyboardInput(VolatileRunFlags *flags,
+			float *statustext_time,
+			float *jump_timer,
+			u32 *profiler_current_page,
+			u32 profiler_max_page);
+	void processItemSelection(u16 *new_playeritem);
+
+	void dropSelectedItem();
+	void openInventory();
+	void openConsole();
+	void toggleFreeMove(float *statustext_time);
+	void toggleFreeMoveAlt(float *statustext_time, float *jump_timer);
+	void toggleFast(float *statustext_time);
+	void toggleNoClip(float *statustext_time);
+
+	void toggleChat(float *statustext_time, bool *flag);
+	void toggleHud(float *statustext_time, bool *flag);
+	void toggleFog(float *statustext_time, bool *flag);
+	void toggleDebug(float *statustext_time, bool *show_debug,
+			bool *show_profiler_graph);
+	void toggleUpdateCamera(float *statustext_time, bool *flag);
+	void toggleProfiler(float *statustext_time, u32 *profiler_current_page,
+			u32 profiler_max_page);
+
+	void increaseViewRange(float *statustext_time);
+	void decreaseViewRange(float *statustext_time);
+	void toggleFullViewRange(float *statustext_time);
+
+	void updateCameraDirection(CameraOrientation *cam, VolatileRunFlags *flags);
+	void updatePlayerControl(const CameraOrientation &cam);
+	void step(f32 *dtime);
+	void processClientEvents(CameraOrientation *cam, float *damage_flash);
+	void updateCamera(VolatileRunFlags *flags, u32 busy_time, f32 dtime,
+			float time_from_last_punch);
+	void updateSound(f32 dtime);
+	void processPlayerInteraction(std::vector<aabb3f> &highlight_boxes,
+			InteractParams *interactArgs, f32 dtime, bool show_hud,
+			bool show_debug);
+	void handlePointingAtNode(InteractParams *interactArgs,
+			const PointedThing &pointed, const ItemDefinition &playeritem_def,
+			const ToolCapabilities &playeritem_toolcap, f32 dtime);
+	void handlePointingAtObject(InteractParams *interactArgs,
+			const PointedThing &pointed, const ItemStack &playeritem,
+			const v3f &player_position, bool show_debug);
+	void handleDigging(InteractParams *interactArgs, const PointedThing &pointed,
+			const v3s16 &nodepos, const ToolCapabilities &playeritem_toolcap,
+			f32 dtime);
+	void updateFrame(std::vector<aabb3f> &highlight_boxes, ProfilerGraph *graph,
+			RunStats *stats, InteractParams *interactArgs,
+			f32 dtime, const VolatileRunFlags &flags, const CameraOrientation &cam);
+	void updateGui(float *statustext_time, const RunStats &stats, f32 dtime,
+			const VolatileRunFlags &flags, const CameraOrientation &cam);
+	void updateProfilerGraphs(ProfilerGraph *graph);
+
+	// Misc
+	void limitFps(FpsControl *params, f32 *dtime);
+
+	void showOverlayMessage(const char *msg, float dtime, int percent,
+			bool draw_clouds = true);
+
+	inline const char *boolToCStr(bool v);
 
-		if(bind_addr.isIPv6() && !g_settings->getBool("enable_ipv6")) {
-			error_message = L"Unable to listen on " +
-				narrow_to_wide(bind_addr.serializeString()) +
-				L" because IPv6 is disabled";
-			errorstream<<wide_to_narrow(error_message)<<std::endl;
-			// Break out of client scope
-			return;
-		}
+private:
+	InputHandler *input;
 
-		server = new Server(map_dir, gamespec,
-				simple_singleplayer_mode,
-				bind_addr.isIPv6());
+	Client *client;
+	Server *server;
 
-		server->start(bind_addr);
-	}
+	gui::IGUIFont *font;
 
-	do{ // Client scope (breakable do-while(0))
+	IWritableTextureSource *texture_src;
+	IWritableShaderSource *shader_src;
 
-	/*
-		Create client
-	*/
+	// When created, these will be filled with data received from the server
+	IWritableItemDefManager *itemdef_manager;
+	IWritableNodeDefManager *nodedef_manager;
 
-	{
-		wchar_t* text = wgettext("Creating client...");
-		draw_load_screen(text, device, guienv, font, 0, 50);
-		delete[] text;
-	}
-	infostream<<"Creating client"<<std::endl;
+	GameOnDemandSoundFetcher soundfetcher; // useful when testing
+	ISoundManager *sound;
+	bool sound_is_dummy;
+	SoundMaker *soundmaker;
 
-	MapDrawControl draw_control;
+	ChatBackend *chat_backend;
 
-	{
-		wchar_t* text = wgettext("Resolving address...");
-		draw_load_screen(text, device, guienv, font, 0, 75);
-		delete[] text;
-	}
-	Address connect_address(0,0,0,0, port);
-	try {
-		connect_address.Resolve(address.c_str());
-		if (connect_address.isZero()) { // i.e. INADDR_ANY, IN6ADDR_ANY
-			//connect_address.Resolve("localhost");
-			if (connect_address.isIPv6()) {
-				IPv6AddressBytes addr_bytes;
-				addr_bytes.bytes[15] = 1;
-				connect_address.setAddress(&addr_bytes);
-			} else {
-				connect_address.setAddress(127,0,0,1);
-			}
-		}
-	}
-	catch(ResolveError &e) {
-		error_message = L"Couldn't resolve address: " + narrow_to_wide(e.what());
-		errorstream<<wide_to_narrow(error_message)<<std::endl;
-		// Break out of client scope
-		break;
-	}
-	if(connect_address.isIPv6() && !g_settings->getBool("enable_ipv6")) {
-		error_message = L"Unable to connect to " +
-			narrow_to_wide(connect_address.serializeString()) +
-			L" because IPv6 is disabled";
-		errorstream<<wide_to_narrow(error_message)<<std::endl;
-		// Break out of client scope
-		break;
-	}
+	GUIFormSpecMenu *current_formspec;
 
-	/*
-		Create client
-	*/
-	Client client(device, playername.c_str(), password, draw_control,
-		tsrc, shsrc, itemdef, nodedef, sound, &eventmgr,
-		connect_address.isIPv6());
+	EventManager *eventmgr;
+	QuicktuneShortcutter *quicktune;
 
-	// Client acts as our GameDef
-	IGameDef *gamedef = &client;
+	GUIChatConsole *gui_chat_console; // Free using ->Drop()
+	MapDrawControl *draw_control;
+	Camera *camera;
+	Clouds *clouds;	                  // Free using ->Drop()
+	Sky *sky;                         // Free using ->Drop()
+	Inventory *local_inventory;
+	Hud *hud;
 
-	/*
-		Attempt to connect to the server
+	/* 'cache'
+	   This class does take ownership/responsibily for cleaning up etc of any of
+	   these items (e.g. device)
 	*/
+	IrrlichtDevice *device;
+	video::IVideoDriver *driver;
+	scene::ISceneManager *smgr;
+	u32 text_height;
+	bool *kill;
+	std::wstring *error_message;
+	IGameDef *gamedef;                     // Convenience (same as *client)
+	scene::ISceneNode *skybox;
+
+	bool random_input;
+	bool simple_singleplayer_mode;
+	/* End 'cache' */
+
+	/* Pre-calculated values
+	 */
+	int crack_animation_length;
+
+	/* GUI stuff
+	 */
+	gui::IGUIStaticText *guitext;          // First line of debug text
+	gui::IGUIStaticText *guitext2;         // Second line of debug text
+	gui::IGUIStaticText *guitext_info;     // At the middle of the screen
+	gui::IGUIStaticText *guitext_status;
+	gui::IGUIStaticText *guitext_chat;	   // Chat text
+	gui::IGUIStaticText *guitext_profiler; // Profiler text
 
-	infostream<<"Connecting to server at ";
-	connect_address.print(&infostream);
-	infostream<<std::endl;
-	client.connect(connect_address);
+	std::wstring infotext;
+	std::wstring statustext;
+};
 
-	/*
-		Wait for server to accept connection
-	*/
-	bool could_connect = false;
-	bool connect_aborted = false;
-	try{
-		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.033; // 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(dtime);
-			if(server != NULL)
-				server->step(dtime);
+MinetestApp::MinetestApp() :
+	client(NULL),
+	server(NULL),
+	font(NULL),
+	texture_src(NULL),
+	shader_src(NULL),
+	itemdef_manager(NULL),
+	nodedef_manager(NULL),
+	sound(NULL),
+	sound_is_dummy(false),
+	soundmaker(NULL),
+	chat_backend(NULL),
+	current_formspec(NULL),
+	eventmgr(NULL),
+	quicktune(NULL),
+	gui_chat_console(NULL),
+	draw_control(NULL),
+	camera(NULL),
+	clouds(NULL),
+	sky(NULL),
+	local_inventory(NULL),
+	hud(NULL)
+{
 
-			// End condition
-			if(client.getState() == LC_Init) {
-				could_connect = true;
-				break;
-			}
-			// Break conditions
-			if(client.accessDenied()) {
-				error_message = L"Access denied. Reason: "
-						+client.accessDeniedReason();
-				errorstream<<wide_to_narrow(error_message)<<std::endl;
-				break;
-			}
-			if(input->wasKeyDown(EscapeKey) || input->wasKeyDown(CancelKey)) {
-				connect_aborted = true;
-				infostream<<"Connect aborted [Escape]"<<std::endl;
-				break;
-			}
+}
 
-			// Display status
-			{
-				wchar_t* text = wgettext("Connecting to server...");
-				draw_load_screen(text, device, guienv, font, dtime, 100);
-				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;
-
-				// 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)
-	{}
 
-	/*
-		Handle failure to connect
-	*/
-	if(!could_connect) {
-		if(error_message == L"" && !connect_aborted) {
-			error_message = L"Connection failed";
-			errorstream<<wide_to_narrow(error_message)<<std::endl;
-		}
-		// Break out of client scope
-		break;
-	}
+/****************************************************************************
+ MinetestApp Public
+ ****************************************************************************/
 
-	/*
-		Wait until content has been received
-	*/
-	bool got_content = false;
-	bool content_aborted = false;
-	{
-		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.033; // 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(dtime);
-			if (server != NULL)
-				server->step(dtime);
+MinetestApp::~MinetestApp()
+{
+	delete client;
+	delete soundmaker;
+	if (!sound_is_dummy)
+		delete sound;
 
-			// End condition
-			if (client.mediaReceived() &&
-					client.itemdefReceived() &&
-					client.nodedefReceived()) {
-				got_content = true;
-				break;
-			}
-			// Break conditions
-			if (client.accessDenied()) {
-				error_message = L"Access denied. Reason: "
-						+client.accessDeniedReason();
-				errorstream<<wide_to_narrow(error_message)<<std::endl;
-				break;
-			}
-			if (client.getState() < LC_Init) {
-				error_message = L"Client disconnected";
-				errorstream<<wide_to_narrow(error_message)<<std::endl;
-				break;
-			}
-			if (input->wasKeyDown(EscapeKey) || input->wasKeyDown(CancelKey)) {
-				content_aborted = true;
-				infostream<<"Connect aborted [Escape]"<<std::endl;
-				break;
-			}
+	delete server; // deleted first to stop all server threads
 
-			// Display status
-			int progress=0;
-			if (!client.itemdefReceived())
-			{
-				wchar_t* text = wgettext("Item definitions...");
-				progress = 0;
-				draw_load_screen(text, device, guienv, font, dtime, progress);
-				delete[] text;
-			}
-			else if (!client.nodedefReceived())
-			{
-				wchar_t* text = wgettext("Node definitions...");
-				progress = 25;
-				draw_load_screen(text, device, guienv, font, dtime, progress);
-				delete[] text;
-			}
-			else
-			{
+	delete hud;
+	delete local_inventory;
+	delete camera;
+	delete quicktune;
+	delete eventmgr;
+	delete texture_src;
+	delete shader_src;
+	delete nodedef_manager;
+	delete itemdef_manager;
+	delete draw_control;
 
-				std::stringstream message;
-				message.precision(3);
-				message << gettext("Media...");
+	extendedResourceCleanup();
+}
 
-				if ( ( USE_CURL == 0) ||
-						(!g_settings->getBool("enable_remote_media_server"))) {
-					float cur = client.getCurRate();
-					std::string cur_unit = gettext(" KB/s");
+bool MinetestApp::startup(bool *kill,
+		bool random_input,
+		InputHandler *input,
+		IrrlichtDevice *device,
+		gui::IGUIFont *font,
+		const std::string &map_dir,
+		const std::string &playername,
+		const std::string &password,
+		std::string *address,     // can change if simple_singleplayer_mode
+		u16 port,
+		std::wstring *error_message,
+		ChatBackend *chat_backend,
+		const SubgameSpec &gamespec,
+		bool simple_singleplayer_mode)
+{
+	// "cache"
+	this->device        = device;
+	this->font          = font;
+	this->kill          = kill;
+	this->error_message = error_message;
+	this->random_input  = random_input;
+	this->input         = input;
+	this->chat_backend  = chat_backend;
+	this->simple_singleplayer_mode = simple_singleplayer_mode;
+
+	driver              = device->getVideoDriver();
+	smgr                = device->getSceneManager();
+	text_height         = font->getDimension(L"Random test string").Height;
+
+	if (!init(map_dir, address, port, gamespec))
+		return false;
+
+	if (!createClient(playername, password, address, port, error_message))
+		return false;
+
+	return true;
+}
 
-					if (cur > 900) {
-						cur /= 1024.0;
-						cur_unit = gettext(" MB/s");
-					}
-					message << " ( " << cur << cur_unit << " )";
-				}
-				progress = 50+client.mediaReceiveProgress()*50+0.5;
-				draw_load_screen(narrow_to_wide(message.str().c_str()), device,
-						guienv, font, dtime, progress);
-			}
 
-			// 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;
-		}
-	}
+void MinetestApp::run()
+{
+	ProfilerGraph graph;
+	RunStats stats              = { 0 };
+	CameraOrientation cam_view  = { 0 };
+	InteractParams interactArgs = { 0 };
+	FpsControl draw_times       = { 0 };
+	VolatileRunFlags flags      = { 0 };
+	f32 dtime; // in seconds
 
-	if(!got_content){
-		if(error_message == L"" && !content_aborted){
-			error_message = L"Something failed";
-			errorstream<<wide_to_narrow(error_message)<<std::endl;
-		}
-		// Break out of client scope
-		break;
-	}
+	interactArgs.time_from_last_punch  = 10.0;
+	interactArgs.profiler_max_page = 3;
 
-	/*
-		After all content has been received:
-		Update cached textures, meshes and materials
-	*/
-	client.afterContentReceived(device,font);
+	flags.show_chat = true;
+	flags.show_hud = true;
 
-	/*
-		Create the camera node
-	*/
-	Camera camera(smgr, draw_control, gamedef);
-	if (!camera.successfullyCreated(error_message))
-		return;
+	/* FIXME: This should be updated every iteration, or not stored locally
+	          now that key settings can be changed in-game */
+	flags.invert_mouse = g_settings->getBool("invert_mouse");
 
-	f32 camera_yaw = 0; // "right/left"
-	f32 camera_pitch = 0; // "up/down"
+	/* Clear the profiler */
+	Profiler::GraphValues dummyvalues;
+	g_profiler->graphGet(dummyvalues);
 
-	/*
-		Clouds
-	*/
+	draw_times.last_time = device->getTimer()->getTime();
 
-	Clouds *clouds = NULL;
-	if(g_settings->getBool("enable_clouds"))
-	{
-		clouds = new Clouds(smgr->getRootSceneNode(), smgr, -1, time(0));
-	}
+	shader_src->addGlobalConstantSetter(new GameGlobalShaderConstantSetter(
+			sky,
+			&flags.force_fog_off,
+			&interactArgs.fog_range,
+			client));
 
-	/*
-		Skybox thingy
-	*/
+	while (device->run() && !(*kill || g_gamecallback->shutdown_requested)) {
 
-	Sky *sky = NULL;
-	sky = new Sky(smgr->getRootSceneNode(), smgr, -1);
+		/* Must be called immediately after a device->run() call because it
+		 * uses device->getTimer()->getTime()
+		 */
+		limitFps(&draw_times, &dtime);
 
-	scene::ISceneNode* skybox = NULL;
+		updateStats(&stats, draw_times, dtime);
+		updateInteractTimers(&interactArgs, dtime);
 
-	/*
-		A copy of the local inventory
-	*/
-	Inventory local_inventory(itemdef);
+		if (!checkConnection())
+			break;
+		if (!handleCallbacks())
+			break;
 
-	/*
-		Find out size of crack animation
-	*/
-	int crack_animation_length = 5;
-	{
-		video::ITexture *t = tsrc->getTexture("crack_anylength.png");
-		v2u32 size = t->getOriginalSize();
-		crack_animation_length = size.Y / size.X;
+		processQueues();
+
+		std::vector<aabb3f> highlight_boxes;
+
+		infotext = L"";
+		hud->resizeHotbar();
+		addProfilerGraphs(stats, draw_times, dtime);
+		processUserInput(&flags, &interactArgs, dtime);
+		// Update camera before player movement to avoid camera lag of one frame
+		updateCameraDirection(&cam_view, &flags);
+		updatePlayerControl(cam_view);
+		step(&dtime);
+		processClientEvents(&cam_view, &interactArgs.damage_flash);
+		updateCamera(&flags, draw_times.busy_time, dtime,
+				interactArgs.time_from_last_punch);
+		updateSound(dtime);
+		processPlayerInteraction(highlight_boxes, &interactArgs, dtime,
+				flags.show_hud, flags.show_debug);
+		updateFrame(highlight_boxes, &graph, &stats, &interactArgs, dtime,
+				flags, cam_view);
+		updateProfilerGraphs(&graph);
 	}
+}
 
-	/*
-		Add some gui stuff
-	*/
 
-	// First line of debug text
-	gui::IGUIStaticText *guitext = guienv->addStaticText(
-			L"Minetest",
-			core::rect<s32>(0, 0, 0, 0),
-			false, false, guiroot);
-	// Second line of debug text
-	gui::IGUIStaticText *guitext2 = guienv->addStaticText(
+void MinetestApp::shutdown()
+{
+	showOverlayMessage("Shutting down...", 0, 0, false);
+
+	if (clouds)
+		clouds->drop();
+
+	if (gui_chat_console)
+		gui_chat_console->drop();
+
+	if (sky)
+		sky->drop();
+
+	clear_particles();
+
+	/* cleanup menus */
+	while (g_menumgr.menuCount() > 0) {
+		g_menumgr.m_stack.front()->setVisible(false);
+		g_menumgr.deletingMenu(g_menumgr.m_stack.front());
+	}
+
+	if (current_formspec) {
+		current_formspec->drop();
+		current_formspec = NULL;
+	}
+
+	chat_backend->addMessage(L"", L"# Disconnected.");
+	chat_backend->addMessage(L"", L"");
+
+	if (client) {
+		client->Stop();
+		while (!client->isShutdown()) {
+			assert(texture_src != NULL);
+			assert(shader_src != NULL);
+			texture_src->processQueue();
+			shader_src->processQueue();
+			sleep_ms(100);
+		}
+	}
+}
+
+
+
+/****************************************************************************
+ Startup
+ ****************************************************************************/
+
+bool MinetestApp::init(
+		const std::string &map_dir,
+		std::string *address,
+		u16 port,
+		const SubgameSpec &gamespec)
+{
+	showOverlayMessage("Loading...", 0, 0);
+
+	texture_src = createTextureSource(device);
+	shader_src = createShaderSource(device);
+
+	itemdef_manager = createItemDefManager();
+	nodedef_manager = createNodeDefManager();
+
+	eventmgr = new EventManager();
+	quicktune = new QuicktuneShortcutter();
+
+	if (!(texture_src && shader_src && itemdef_manager && nodedef_manager
+			&& eventmgr && quicktune))
+		return false;
+
+	if (!initSound())
+		return false;
+
+	// Create a server if not connecting to an existing one
+	if (*address == "") {
+		if (!createSingleplayerServer(map_dir, gamespec, port, address))
+			return false;
+	}
+
+	return true;
+}
+
+bool MinetestApp::initSound()
+{
+#if USE_SOUND
+	if (g_settings->getBool("enable_sound")) {
+		infostream << "Attempting to use OpenAL audio" << std::endl;
+		sound = createOpenALSoundManager(&soundfetcher);
+		if (!sound)
+			infostream << "Failed to initialize OpenAL audio" << std::endl;
+	} else
+		infostream << "Sound disabled." << std::endl;
+#endif
+
+	if (!sound) {
+		infostream << "Using dummy audio." << std::endl;
+		sound = &dummySoundManager;
+		sound_is_dummy = true;
+	}
+
+	soundmaker = new SoundMaker(sound, nodedef_manager);
+	if (!soundmaker)
+		return false;
+
+	soundmaker->registerReceiver(eventmgr);
+
+	return true;
+}
+
+bool MinetestApp::createSingleplayerServer(const std::string map_dir,
+		const SubgameSpec &gamespec, u16 port, std::string *address)
+{
+	showOverlayMessage("Creating server...", 0, 25);
+
+	std::string bind_str = g_settings->get("bind_address");
+	Address bind_addr(0, 0, 0, 0, port);
+
+	if (g_settings->getBool("ipv6_server")) {
+		bind_addr.setAddress((IPv6AddressBytes *) NULL);
+	}
+
+	try {
+		bind_addr.Resolve(bind_str.c_str());
+		*address = bind_str;
+	} catch (ResolveError &e) {
+		infostream << "Resolving bind address \"" << bind_str
+			   << "\" failed: " << e.what()
+			   << " -- Listening on all addresses." << std::endl;
+	}
+
+	if (bind_addr.isIPv6() && !g_settings->getBool("enable_ipv6")) {
+		*error_message = L"Unable to listen on " +
+				narrow_to_wide(bind_addr.serializeString()) +
+				L" because IPv6 is disabled";
+		errorstream << wide_to_narrow(*error_message) << std::endl;
+		return false;
+	}
+
+	server = new Server(map_dir, gamespec, simple_singleplayer_mode,
+			    bind_addr.isIPv6());
+
+	server->start(bind_addr);
+
+	return true;
+}
+
+bool MinetestApp::createClient(const std::string &playername,
+		const std::string &password, std::string *address, u16 port,
+		std::wstring *error_message)
+{
+	showOverlayMessage("Creating client...", 0, 50);
+
+	draw_control = new MapDrawControl;
+	if (!draw_control)
+		return false;
+
+	bool could_connect, connect_aborted;
+
+	if (!connectToServer(playername, password, address, port,
+			&could_connect, &connect_aborted))
+		return false;
+
+	if (!could_connect) {
+		if (*error_message == L"" && !connect_aborted) {
+			// Should not happen if error messages are set properly
+			*error_message = L"Connection failed for unknown reason";
+			errorstream << wide_to_narrow(*error_message) << std::endl;
+		}
+		return false;
+	}
+
+	if (!getServerContent(&connect_aborted)) {
+		if (*error_message == L"" && !connect_aborted) {
+			// Should not happen if error messages are set properly
+			*error_message = L"Connection failed for unknown reason";
+			errorstream << wide_to_narrow(*error_message) << std::endl;
+		}
+		return false;
+	}
+
+	// Update cached textures, meshes and materials
+	client->afterContentReceived(device, font);
+
+	/* Camera
+	 */
+	camera = new Camera(smgr, *draw_control, gamedef);
+	if (!camera || !camera->successfullyCreated(*error_message))
+		return false;
+
+	/* Clouds
+	 */
+	if (g_settings->getBool("enable_clouds")) {
+		clouds = new Clouds(smgr->getRootSceneNode(), smgr, -1, time(0));
+		if (!clouds) {
+			*error_message = L"Memory allocation error";
+			*error_message += narrow_to_wide(" (clouds)");
+			errorstream << wide_to_narrow(*error_message) << std::endl;
+			return false;
+		}
+	}
+
+	/* Skybox
+	 */
+	sky = new Sky(smgr->getRootSceneNode(), smgr, -1);
+	skybox = NULL;	// This is used/set later on in the main run loop
+
+	local_inventory = new Inventory(itemdef_manager);
+
+	if (!(sky && local_inventory)) {
+		*error_message = L"Memory allocation error";
+		*error_message += narrow_to_wide(" (sky or local inventory)");
+		errorstream << wide_to_narrow(*error_message) << std::endl;
+		return false;
+	}
+
+	/* Pre-calculated values
+	 */
+	video::ITexture *t = texture_src->getTexture("crack_anylength.png");
+	if (t) {
+		v2u32 size = t->getOriginalSize();
+		crack_animation_length = size.Y / size.X;
+	} else {
+		crack_animation_length = 5;
+	}
+
+	if (!initGui(error_message))
+		return false;
+
+	/* Set window caption
+	 */
+	core::stringw str = L"Minetest [";
+	str += driver->getName();
+	str += "]";
+	device->setWindowCaption(str.c_str());
+
+	LocalPlayer *player = client->getEnv().getLocalPlayer();
+	player->hurt_tilt_timer = 0;
+	player->hurt_tilt_strength = 0;
+
+	hud = new Hud(driver, smgr, guienv, font, text_height, gamedef,
+			player, local_inventory);
+
+	if (!hud) {
+		*error_message = L"Memory error: could not create HUD";
+		errorstream << wide_to_narrow(*error_message) << std::endl;
+		return false;
+	}
+
+	return true;
+}
+
+bool MinetestApp::initGui(std::wstring *error_message)
+{
+	// First line of debug text
+	guitext = guienv->addStaticText(
+			L"Minetest",
+			core::rect<s32>(0, 0, 0, 0),
+			false, false, guiroot);
+
+	// Second line of debug text
+	guitext2 = guienv->addStaticText(
 			L"",
 			core::rect<s32>(0, 0, 0, 0),
 			false, false, guiroot);
+
 	// At the middle of the screen
 	// Object infos are shown in this
-	gui::IGUIStaticText *guitext_info = guienv->addStaticText(
+	guitext_info = guienv->addStaticText(
 			L"",
-			core::rect<s32>(0,0,400,text_height*5+5) + v2s32(100,200),
+			core::rect<s32>(0, 0, 400, text_height * 5 + 5) + v2s32(100, 200),
 			false, true, guiroot);
 
 	// Status text (displays info when showing and hiding GUI stuff, etc.)
-	gui::IGUIStaticText *guitext_status = guienv->addStaticText(
+	guitext_status = guienv->addStaticText(
 			L"<Status>",
-			core::rect<s32>(0,0,0,0),
+			core::rect<s32>(0, 0, 0, 0),
 			false, false, guiroot);
 	guitext_status->setVisible(false);
 
-	std::wstring statustext;
-	float statustext_time = 0;
-
 	// Chat text
-	gui::IGUIStaticText *guitext_chat = guienv->addStaticText(
+	guitext_chat = guienv->addStaticText(
 			L"",
-			core::rect<s32>(0,0,0,0),
+			core::rect<s32>(0, 0, 0, 0),
 			//false, false); // Disable word wrap as of now
 			false, true, guiroot);
 	// Remove stale "recent" chat messages from previous connections
-	chat_backend.clearRecentChat();
+	chat_backend->clearRecentChat();
+
 	// Chat backend and console
-	GUIChatConsole *gui_chat_console = new GUIChatConsole(guienv, guienv->getRootGUIElement(), -1, &chat_backend, &client);
+	gui_chat_console = new GUIChatConsole(guienv, guienv->getRootGUIElement(),
+			-1, chat_backend, client);
+	if (!gui_chat_console) {
+		*error_message = L"Could not allocate memory for chat console";
+		errorstream << wide_to_narrow(*error_message) << std::endl;
+		return false;
+	}
 
 	// Profiler text (size is updated when text is updated)
-	gui::IGUIStaticText *guitext_profiler = guienv->addStaticText(
+	guitext_profiler = guienv->addStaticText(
 			L"<Profiler>",
-			core::rect<s32>(0,0,0,0),
+			core::rect<s32>(0, 0, 0, 0),
 			false, false, guiroot);
-	guitext_profiler->setBackgroundColor(video::SColor(120,0,0,0));
+	guitext_profiler->setBackgroundColor(video::SColor(120, 0, 0, 0));
 	guitext_profiler->setVisible(false);
 	guitext_profiler->setWordWrap(true);
 
 #ifdef HAVE_TOUCHSCREENGUI
+
 	if (g_touchscreengui)
-		g_touchscreengui->init(tsrc,porting::getDisplayDensity());
+		g_touchscreengui->init(tsrc, porting::getDisplayDensity());
+
 #endif
 
-	/*
-		Some statistics are collected in these
-	*/
-	u32 drawtime = 0;
-	u32 beginscenetime = 0;
-	u32 endscenetime = 0;
+	return true;
+}
 
-	float recent_turn_speed = 0.0;
+bool MinetestApp::connectToServer(const std::string &playername,
+		const std::string &password, std::string *address, u16 port,
+		bool *connect_ok, bool *aborted)
+{
+	showOverlayMessage("Resolving address...", 0, 75);
 
-	ProfilerGraph graph;
-	// Initially clear the profiler
-	Profiler::GraphValues dummyvalues;
-	g_profiler->graphGet(dummyvalues);
+	Address connect_address(0, 0, 0, 0, port);
 
-	float nodig_delay_timer = 0.0;
-	float dig_time = 0.0;
-	u16 dig_index = 0;
-	PointedThing pointed_old;
-	bool digging = false;
-	bool ldown_for_dig = false;
+	try {
+		connect_address.Resolve(address->c_str());
 
-	float damage_flash = 0;
+		if (connect_address.isZero()) { // i.e. INADDR_ANY, IN6ADDR_ANY
+			//connect_address.Resolve("localhost");
+			if (connect_address.isIPv6()) {
+				IPv6AddressBytes addr_bytes;
+				addr_bytes.bytes[15] = 1;
+				connect_address.setAddress(&addr_bytes);
+			} else {
+				connect_address.setAddress(127, 0, 0, 1);
+			}
+		}
+	} catch (ResolveError &e) {
+		*error_message = L"Couldn't resolve address: " + narrow_to_wide(e.what());
+		errorstream << wide_to_narrow(*error_message) << std::endl;
+		return false;
+	}
 
-	float jump_timer = 0;
-	bool reset_jump_timer = false;
+	if (connect_address.isIPv6() && !g_settings->getBool("enable_ipv6")) {
+		*error_message = L"Unable to connect to " +
+				narrow_to_wide(connect_address.serializeString()) +
+				L" because IPv6 is disabled";
+		errorstream << wide_to_narrow(*error_message) << std::endl;
+		return false;
+	}
 
-	const float object_hit_delay = 0.2;
-	float object_hit_delay_timer = 0.0;
-	float time_from_last_punch = 10;
+	client = new Client(device, playername.c_str(), password, *draw_control,
+		    texture_src, shader_src, itemdef_manager, nodedef_manager, sound,
+			eventmgr, connect_address.isIPv6());
 
-	float update_draw_list_timer = 0.0;
-	v3f update_draw_list_last_cam_dir;
+	if (!client)
+		return false;
 
-	bool invert_mouse = g_settings->getBool("invert_mouse");
+	gamedef = client;	// Client acts as our GameDef
 
-	bool update_wielded_item_trigger = true;
 
-	bool show_hud = true;
-	bool show_chat = true;
-	bool force_fog_off = false;
-	f32 fog_range = 100*BS;
-	bool disable_camera_update = false;
-	bool show_debug = g_settings->getBool("show_debug");
-	bool show_profiler_graph = false;
-	u32 show_profiler = 0;
-	u32 show_profiler_max = 3;  // Number of pages
+	infostream << "Connecting to server at ";
+	connect_address.print(&infostream);
+	infostream << std::endl;
 
-	float time_of_day = 0;
-	float time_of_day_smooth = 0;
+	client->connect(connect_address);
 
-	float repeat_rightclick_timer = 0;
 
 	/*
-		Shader constants
+		Wait for server to accept connection
 	*/
-	shsrc->addGlobalConstantSetter(new GameGlobalShaderConstantSetter(
-			sky, &force_fog_off, &fog_range, &client));
 
-	/*
-		Main loop
-	*/
+	try {
+		input->clear();
 
-	bool first_loop_after_window_activation = true;
+		FpsControl fps_control = { 0 };
+		f32 dtime; // in seconds
 
-	// TODO: Convert the static interval timers to these
-	// Interval limiter for profiler
-	IntervalLimiter m_profiler_interval;
+		while (device->run()) {
 
-	// Time is in milliseconds
-	// NOTE: getRealTime() causes strange problems in wine (imprecision?)
-	// NOTE: So we have to use getTime() and call run()s between them
-	u32 lasttime = device->getTimer()->getTime();
+			limitFps(&fps_control, &dtime);
 
-	LocalPlayer* player = client.getEnv().getLocalPlayer();
-	player->hurt_tilt_timer = 0;
-	player->hurt_tilt_strength = 0;
+			// Update client and server
+			client->step(dtime);
 
-	/*
-		HUD object
-	*/
-	Hud hud(driver, smgr, guienv, font, text_height,
-			gamedef, player, &local_inventory);
+			if (server != NULL)
+				server->step(dtime);
 
-	core::stringw str = L"Minetest [";
-	str += driver->getName();
-	str += "]";
-	device->setWindowCaption(str.c_str());
+			// End condition
+			if (client->getState() == LC_Init) {
+				*connect_ok = true;
+				break;
+			}
 
-	// Info text
-	std::wstring infotext;
+			// Break conditions
+			if (client->accessDenied()) {
+				*error_message = L"Access denied. Reason: "
+						+ client->accessDeniedReason();
+				errorstream << wide_to_narrow(*error_message) << std::endl;
+				break;
+			}
+
+			if (input->wasKeyDown(EscapeKey) || input->wasKeyDown(CancelKey)) {
+				*aborted = true;
+				infostream << "Connect aborted [Escape]" << std::endl;
+				break;
+			}
+
+			// Update status
+			showOverlayMessage("Connecting to server...", dtime, 100);
+		}
+	} catch (con::PeerNotFoundException &e) {
+		// TODO: Should something be done here? At least an info/error
+		// message?
+		return false;
+	}
+
+	return true;
+}
+
+bool MinetestApp::getServerContent(bool *aborted)
+{
+	input->clear();
+
+	FpsControl fps_control = { 0 };
+	f32 dtime; // in seconds
+
+	while (device->run()) {
+
+		limitFps(&fps_control, &dtime);
+
+		// Update client and server
+		client->step(dtime);
+
+		if (server != NULL)
+			server->step(dtime);
+
+		// End condition
+		if (client->mediaReceived() && client->itemdefReceived() &&
+				client->nodedefReceived()) {
+			break;
+		}
+
+		// Error conditions
+		if (client->accessDenied()) {
+			*error_message = L"Access denied. Reason: "
+					+ client->accessDeniedReason();
+			errorstream << wide_to_narrow(*error_message) << std::endl;
+			return false;
+		}
+
+		if (client->getState() < LC_Init) {
+			*error_message = L"Client disconnected";
+			errorstream << wide_to_narrow(*error_message) << std::endl;
+			return false;
+		}
+
+		if (input->wasKeyDown(EscapeKey) || input->wasKeyDown(CancelKey)) {
+			*aborted = true;
+			infostream << "Connect aborted [Escape]" << std::endl;
+			return false;
+		}
+
+		// Display status
+		int progress = 0;
+
+		if (!client->itemdefReceived()) {
+			wchar_t *text = wgettext("Item definitions...");
+			progress = 0;
+			draw_load_screen(text, device, guienv, font, dtime, progress);
+			delete[] text;
+		} else if (!client->nodedefReceived()) {
+			wchar_t *text = wgettext("Node definitions...");
+			progress = 25;
+			draw_load_screen(text, device, guienv, font, dtime, progress);
+			delete[] text;
+		} else {
+			std::stringstream message;
+			message.precision(3);
+			message << gettext("Media...");
+
+			if ((USE_CURL == 0) ||
+					(!g_settings->getBool("enable_remote_media_server"))) {
+				float cur = client->getCurRate();
+				std::string cur_unit = gettext(" KB/s");
+
+				if (cur > 900) {
+					cur /= 1024.0;
+					cur_unit = gettext(" MB/s");
+				}
+
+				message << " ( " << cur << cur_unit << " )";
+			}
+
+			progress = 50 + client->mediaReceiveProgress() * 50 + 0.5;
+			draw_load_screen(narrow_to_wide(message.str().c_str()), device,
+					guienv, font, dtime, progress);
+		}
+	}
+
+	return true;
+}
+
+
+
+/****************************************************************************
+ Run
+ ****************************************************************************/
+
+inline void MinetestApp::updateInteractTimers(InteractParams *args, f32 dtime)
+{
+	if (args->nodig_delay_timer >= 0)
+		args->nodig_delay_timer -= dtime;
+
+	if (args->object_hit_delay_timer >= 0)
+		args->object_hit_delay_timer -= dtime;
+
+	args->time_from_last_punch += dtime;
+}
+
+
+/* returns false if app should exit, otherwise true
+ */
+inline bool MinetestApp::checkConnection()
+{
+	if (client->accessDenied()) {
+		*error_message = L"Access denied. Reason: "
+				+ client->accessDeniedReason();
+		errorstream << wide_to_narrow(*error_message) << std::endl;
+		return false;
+	}
+
+	return true;
+}
+
+
+/* returns false if app should exit, otherwise true
+ */
+inline bool MinetestApp::handleCallbacks()
+{
+	if (g_gamecallback->disconnect_requested) {
+		g_gamecallback->disconnect_requested = false;
+		return false;
+	}
+
+	if (g_gamecallback->changepassword_requested) {
+		(new GUIPasswordChange(guienv, guiroot, -1,
+				       &g_menumgr, client))->drop();
+		g_gamecallback->changepassword_requested = false;
+	}
+
+	if (g_gamecallback->changevolume_requested) {
+		(new GUIVolumeChange(guienv, guiroot, -1,
+				     &g_menumgr, client))->drop();
+		g_gamecallback->changevolume_requested = false;
+	}
+
+	if (g_gamecallback->keyconfig_requested) {
+		(new GUIKeyChangeMenu(guienv, guiroot, -1,
+				      &g_menumgr))->drop();
+		g_gamecallback->keyconfig_requested = false;
+	}
+
+	return true;
+}
+
+
+void MinetestApp::processQueues()
+{
+	texture_src->processQueue();
+	itemdef_manager->processQueue(gamedef);
+	shader_src->processQueue();
+}
+
+
+void MinetestApp::addProfilerGraphs(const RunStats &stats,
+		const FpsControl &draw_times, f32 dtime)
+{
+	g_profiler->graphAdd("mainloop_other",
+			draw_times.busy_time / 1000.0f - stats.drawtime / 1000.0f);
+
+	if (draw_times.sleep_time != 0)
+		g_profiler->graphAdd("mainloop_sleep", draw_times.sleep_time / 1000.0f);
+	g_profiler->graphAdd("mainloop_dtime", dtime);
+
+	g_profiler->add("Elapsed time", dtime);
+	g_profiler->avg("FPS", 1. / dtime);
+}
+
+
+void MinetestApp::updateStats(RunStats *stats, const FpsControl &draw_times,
+		f32 dtime)
+{
+
+	f32 jitter;
+	Jitter *jp;
+
+	/* Time average and jitter calculation
+	 */
+	jp = &stats->dtime_jitter;
+	jp->avg = jp->avg * 0.96 + dtime * 0.04;
+
+	jitter = dtime - jp->avg;
+
+	if (jitter > jp->max)
+		jp->max = jitter;
+
+	jp->counter += dtime;
+
+	if (jp->counter > 0.0) {
+		jp->counter -= 3.0;
+		jp->max_sample = jp->max;
+		jp->max_fraction = jp->max_sample / (jp->avg + 0.001);
+		jp->max = 0.0;
+	}
+
+	/* Busytime average and jitter calculation
+	 */
+	jp = &stats->busy_time_jitter;
+	jp->avg = jp->avg + draw_times.busy_time * 0.02;
+
+	jitter = draw_times.busy_time - jp->avg;
+
+	if (jitter > jp->max)
+		jp->max = jitter;
+	if (jitter < jp->min)
+		jp->min = jitter;
+
+	jp->counter += dtime;
+
+	if (jp->counter > 0.0) {
+		jp->counter -= 3.0;
+		jp->max_sample = jp->max;
+		jp->min_sample = jp->min;
+		jp->max = 0.0;
+		jp->min = 0.0;
+	}
+}
+
+
+
+/****************************************************************************
+ Input handling
+ ****************************************************************************/
+
+void MinetestApp::processUserInput(VolatileRunFlags *flags,
+		InteractParams *interact_args, f32 dtime)
+{
+	// Reset input if window not active or some menu is active
+	if (device->isWindowActive() == false
+			|| noMenuActive() == false
+			|| guienv->hasFocus(gui_chat_console)) {
+		input->clear();
+	}
+
+	if (!guienv->hasFocus(gui_chat_console) && gui_chat_console->isOpen()) {
+		gui_chat_console->closeConsoleAtOnce();
+	}
+
+	// Input handler step() (used by the random input generator)
+	input->step(dtime);
+
+#ifdef HAVE_TOUCHSCREENGUI
+
+	if (g_touchscreengui) {
+		g_touchscreengui->step(dtime);
+	}
+
+#endif
+#ifdef __ANDROID__
+
+	if (current_formspec != 0)
+		current_formspec->getAndroidUIInput();
+
+#endif
+
+	// Increase timer for double tap of "keymap_jump"
+	if (g_settings->getBool("doubletap_jump") && interact_args->jump_timer <= 0.2)
+		interact_args->jump_timer += dtime;
+
+	processKeyboardInput(
+			flags,
+			&interact_args->statustext_time,
+			&interact_args->jump_timer,
+			&interact_args->profiler_current_page,
+			interact_args->profiler_max_page);
+
+	processItemSelection(&interact_args->new_playeritem);
+}
+
+
+void MinetestApp::processKeyboardInput(VolatileRunFlags *flags,
+		float *statustext_time,
+		float *jump_timer,
+		u32 *profiler_current_page,
+		u32 profiler_max_page)
+{
+	if (input->wasKeyDown(getKeySetting("keymap_drop"))) {
+		dropSelectedItem();
+	} else if (input->wasKeyDown(getKeySetting("keymap_inventory"))) {
+		openInventory();
+	} else if (input->wasKeyDown(EscapeKey) || input->wasKeyDown(CancelKey)) {
+		show_pause_menu(&current_formspec, client, gamedef, texture_src, device,
+				simple_singleplayer_mode);
+	} else if (input->wasKeyDown(getKeySetting("keymap_chat"))) {
+		show_chat_menu(&current_formspec, client, gamedef, texture_src, device,
+				client, "");
+	} else if (input->wasKeyDown(getKeySetting("keymap_cmd"))) {
+		show_chat_menu(&current_formspec, client, gamedef, texture_src, device,
+				client, "/");
+	} else if (input->wasKeyDown(getKeySetting("keymap_console"))) {
+		openConsole();
+	} else if (input->wasKeyDown(getKeySetting("keymap_freemove"))) {
+		toggleFreeMove(statustext_time);
+	} else if (input->wasKeyDown(getKeySetting("keymap_jump"))) {
+		toggleFreeMoveAlt(statustext_time, jump_timer);
+	} else if (input->wasKeyDown(getKeySetting("keymap_fastmove"))) {
+		toggleFast(statustext_time);
+	} else if (input->wasKeyDown(getKeySetting("keymap_noclip"))) {
+		toggleNoClip(statustext_time);
+	} else if (input->wasKeyDown(getKeySetting("keymap_screenshot"))) {
+		client->makeScreenshot(device);
+	} else if (input->wasKeyDown(getKeySetting("keymap_toggle_hud"))) {
+		toggleHud(statustext_time, &flags->show_hud);
+	} else if (input->wasKeyDown(getKeySetting("keymap_toggle_chat"))) {
+		toggleChat(statustext_time, &flags->show_chat);
+	} else if (input->wasKeyDown(getKeySetting("keymap_toggle_force_fog_off"))) {
+		toggleFog(statustext_time, &flags->force_fog_off);
+	} else if (input->wasKeyDown(getKeySetting("keymap_toggle_update_camera"))) {
+		toggleUpdateCamera(statustext_time, &flags->disable_camera_update);
+	} else if (input->wasKeyDown(getKeySetting("keymap_toggle_debug"))) {
+		toggleDebug(statustext_time, &flags->show_debug, &flags->show_profiler_graph);
+	} else if (input->wasKeyDown(getKeySetting("keymap_toggle_profiler"))) {
+		toggleProfiler(statustext_time, profiler_current_page, profiler_max_page);
+	} else if (input->wasKeyDown(getKeySetting("keymap_increase_viewing_range_min"))) {
+		increaseViewRange(statustext_time);
+	} else if (input->wasKeyDown(getKeySetting("keymap_decrease_viewing_range_min"))) {
+		decreaseViewRange(statustext_time);
+	} else if (input->wasKeyDown(getKeySetting("keymap_rangeselect"))) {
+		toggleFullViewRange(statustext_time);
+	}
+
+	// Handle QuicktuneShortcutter
+	if (input->wasKeyDown(getKeySetting("keymap_quicktune_next")))
+		quicktune->next();
+	else if (input->wasKeyDown(getKeySetting("keymap_quicktune_prev")))
+		quicktune->prev();
+	else if (input->wasKeyDown(getKeySetting("keymap_quicktune_inc")))
+		quicktune->inc();
+	else if (input->wasKeyDown(getKeySetting("keymap_quicktune_dec")))
+		quicktune->dec();
+
+	std::string msg = quicktune->getMessage();
+	if (msg != "") {
+		statustext = narrow_to_wide(msg);
+		*statustext_time = 0;
+	}
+
+	// Print debug stacks
+	if (input->wasKeyDown(getKeySetting("keymap_print_debug_stacks"))) {
+		dstream << "-----------------------------------------"
+		        << std::endl;
+		dstream << DTIME << "Printing debug stacks:" << std::endl;
+		dstream << "-----------------------------------------"
+		        << std::endl;
+		debug_stacks_print();
+	}
+}
+
+
+void MinetestApp::processItemSelection(u16 *new_playeritem)
+{
+	LocalPlayer *player = client->getEnv().getLocalPlayer();
+
+	/* Item selection using mouse wheel
+	 */
+	*new_playeritem = client->getPlayerItem();
+
+	s32 wheel = input->getMouseWheel();
+	u16 max_item = MYMIN(PLAYER_INVENTORY_SIZE - 1,
+		                 player->hud_hotbar_itemcount - 1);
+
+	if (wheel < 0)
+		*new_playeritem = *new_playeritem < max_item ? *new_playeritem + 1 : max_item;
+	else if (wheel > 0)
+		*new_playeritem = *new_playeritem > 0 ? *new_playeritem - 1 : max_item;
+	// else wheel == 0
+
+
+	/* Item selection using keyboard
+	 */
+	for (u16 i = 0; i < 10; i++) {
+		static const KeyPress *item_keys[10] = {
+			NumberKey + 1, NumberKey + 2, NumberKey + 3, NumberKey + 4,
+			NumberKey + 5, NumberKey + 6, NumberKey + 7, NumberKey + 8,
+			NumberKey + 9, NumberKey + 0,
+		};
+
+		if (input->wasKeyDown(*item_keys[i])) {
+			if (i < PLAYER_INVENTORY_SIZE && i < player->hud_hotbar_itemcount) {
+				*new_playeritem = i;
+				infostream << "Selected item: " << new_playeritem << std::endl;
+			}
+			break;
+		}
+	}
+}
+
+
+void MinetestApp::dropSelectedItem()
+{
+	IDropAction *a = new IDropAction();
+	a->count = 0;
+	a->from_inv.setCurrentPlayer();
+	a->from_list = "main";
+	a->from_i = client->getPlayerItem();
+	client->inventoryAction(a);
+}
+
+
+void MinetestApp::openInventory()
+{
+	infostream << "the_game: " << "Launching inventory" << std::endl;
+
+	PlayerInventoryFormSource *fs_src = new PlayerInventoryFormSource(client);
+	TextDest *txt_dst = new TextDestPlayerInventory(client);
+
+	create_formspec_menu(&current_formspec, client, gamedef, texture_src,
+			device, fs_src, txt_dst, client);
+
+	InventoryLocation inventoryloc;
+	inventoryloc.setCurrentPlayer();
+	current_formspec->setFormSpec(fs_src->getForm(), inventoryloc);
+}
+
+
+void MinetestApp::openConsole()
+{
+	if (!gui_chat_console->isOpenInhibited()) {
+		// Open up to over half of the screen
+		gui_chat_console->openConsole(0.6);
+		guienv->setFocus(gui_chat_console);
+	}
+}
+
+
+void MinetestApp::toggleFreeMove(float *statustext_time)
+{
+	static const wchar_t *msg[] = { L"free_move disabled", L"free_move enabled" };
+
+	bool free_move = !g_settings->getBool("free_move");
+	g_settings->set("free_move", boolToCStr(free_move));
+
+	*statustext_time = 0;
+	statustext = msg[free_move];
+	if (free_move && !client->checkPrivilege("fly"))
+		statustext += L" (note: no 'fly' privilege)";
+}
+
+
+void MinetestApp::toggleFreeMoveAlt(float *statustext_time, float *jump_timer)
+{
+	if (g_settings->getBool("doubletap_jump") && *jump_timer < 0.2f) {
+		toggleFreeMove(statustext_time);
+		*jump_timer = 0;
+	}
+}
+
+
+void MinetestApp::toggleFast(float *statustext_time)
+{
+	static const wchar_t *msg[] = { L"fast_move disabled", L"fast_move enabled" };
+	bool fast_move = !g_settings->getBool("fast_move");
+	g_settings->set("fast_move", boolToCStr(fast_move));
+
+	*statustext_time = 0;
+	statustext = msg[fast_move];
+
+	if (fast_move && !client->checkPrivilege("fast"))
+		statustext += L" (note: no 'fast' privilege)";
+}
+
+
+void MinetestApp::toggleNoClip(float *statustext_time)
+{
+	static const wchar_t *msg[] = { L"noclip disabled", L"noclip enabled" };
+	bool noclip = !g_settings->getBool("noclip");
+	g_settings->set("noclip", boolToCStr(noclip));
+
+	*statustext_time = 0;
+	statustext = msg[noclip];
+
+	if (noclip && !client->checkPrivilege("noclip"))
+		statustext += L" (note: no 'noclip' privilege)";
+}
+
+
+void MinetestApp::toggleChat(float *statustext_time, bool *flag)
+{
+	static const wchar_t *msg[] = { L"Chat hidden", L"Chat shown" };
+
+	*flag = !*flag;
+	*statustext_time = 0;
+	statustext = msg[*flag];
+}
+
+
+void MinetestApp::toggleHud(float *statustext_time, bool *flag)
+{
+	static const wchar_t *msg[] = { L"HUD hidden", L"HUD shown" };
+
+	*flag = !*flag;
+	*statustext_time = 0;
+	statustext = msg[*flag];
+	client->setHighlighted(client->getHighlighted(), *flag);
+}
+
+
+void MinetestApp::toggleFog(float *statustext_time, bool *flag)
+{
+	static const wchar_t *msg[] = { L"Fog enabled", L"Fog disabled" };
+
+	*flag = !*flag;
+	*statustext_time = 0;
+	statustext = msg[*flag];
+}
+
+
+void MinetestApp::toggleDebug(float *statustext_time, bool *show_debug,
+		bool *show_profiler_graph)
+{
+	// Initial / 3x toggle: Chat only
+	// 1x toggle: Debug text with chat
+	// 2x toggle: Debug text with profiler graph
+	if (!*show_debug) {
+		*show_debug = true;
+		*show_profiler_graph = false;
+		statustext = L"Debug info shown";
+	} else if (*show_profiler_graph) {
+		*show_debug = false;
+		*show_profiler_graph = false;
+		statustext = L"Debug info and profiler graph hidden";
+	} else {
+		*show_profiler_graph = true;
+		statustext = L"Profiler graph shown";
+	}
+	*statustext_time = 0;
+}
+
+
+void MinetestApp::toggleUpdateCamera(float *statustext_time, bool *flag)
+{
+	static const wchar_t *msg[] = {
+		L"Camera update enabled",
+		L"Camera update disabled"
+	};
+
+	*flag = !*flag;
+	*statustext_time = 0;
+	statustext = msg[*flag];
+}
+
+
+void MinetestApp::toggleProfiler(float *statustext_time, u32 *profiler_current_page,
+		u32 profiler_max_page)
+{
+	*profiler_current_page = (*profiler_current_page + 1) % (profiler_max_page + 1);
+
+	// FIXME: This updates the profiler with incomplete values
+	update_profiler_gui(guitext_profiler, font, text_height,
+			    *profiler_current_page, profiler_max_page);
+
+	if (*profiler_current_page != 0) {
+		std::wstringstream sstr;
+		sstr << "Profiler shown (page " << *profiler_current_page
+		     << " of " << profiler_max_page << ")";
+		statustext = sstr.str();
+	} else {
+		statustext = L"Profiler hidden";
+	}
+	*statustext_time = 0;
+}
+
+
+void MinetestApp::increaseViewRange(float *statustext_time)
+{
+	s16 range = g_settings->getS16("viewing_range_nodes_min");
+	s16 range_new = range + 10;
+	g_settings->set("viewing_range_nodes_min", itos(range_new));
+	statustext = narrow_to_wide("Minimum viewing range changed to "
+			+ itos(range_new));
+	*statustext_time = 0;
+}
+
+
+void MinetestApp::decreaseViewRange(float *statustext_time)
+{
+	s16 range = g_settings->getS16("viewing_range_nodes_min");
+	s16 range_new = range - 10;
+
+	if (range_new < 0)
+		range_new = range;
+
+	g_settings->set("viewing_range_nodes_min", itos(range_new));
+	statustext = narrow_to_wide("Minimum viewing range changed to "
+			+ itos(range_new));
+	*statustext_time = 0;
+}
+
+
+void MinetestApp::toggleFullViewRange(float *statustext_time)
+{
+	static const wchar_t *msg[] = {
+		L"Disabled full viewing range",
+		L"Enabled full viewing range"
+	};
+
+	draw_control->range_all = !draw_control->range_all;
+	infostream << msg[draw_control->range_all] << std::endl;
+	statustext = msg[draw_control->range_all];
+	*statustext_time = 0;
+}
+
+
+void MinetestApp::updateCameraDirection(CameraOrientation *cam,
+		VolatileRunFlags *flags)
+{
+	// float turn_amount = 0;	// Deprecated?
+
+	if (!(device->isWindowActive() && noMenuActive()) || random_input) {
+
+	// FIXME: Clean this up
+
+#ifndef ANDROID
+		// Mac OSX gets upset if this is set every frame
+		if (device->getCursorControl()->isVisible() == false)
+			device->getCursorControl()->setVisible(true);
+#endif
+
+		//infostream<<"window inactive"<<std::endl;
+		flags->first_loop_after_window_activation = true;
+		return;
+	}
+
+#ifndef __ANDROID__
+	if (!random_input) {
+		// Mac OSX gets upset if this is set every frame
+		if (device->getCursorControl()->isVisible())
+			device->getCursorControl()->setVisible(false);
+	}
+#endif
+
+	if (flags->first_loop_after_window_activation) {
+		//infostream<<"window active, first loop"<<std::endl;
+		flags->first_loop_after_window_activation = false;
+	} else {
+
+#ifdef HAVE_TOUCHSCREENGUI
+
+		if (g_touchscreengui) {
+			camera_yaw   = g_touchscreengui->getYaw();
+			camera_pitch = g_touchscreengui->getPitch();
+		} else {
+#endif
+			s32 dx = input->getMousePos().X - (driver->getScreenSize().Width / 2);
+			s32 dy = input->getMousePos().Y - (driver->getScreenSize().Height / 2);
+
+			if (flags->invert_mouse
+					|| (camera->getCameraMode() == CAMERA_MODE_THIRD_FRONT)) {
+				dy = -dy;
+			}
+
+			//infostream<<"window active, pos difference "<<dx<<","<<dy<<std::endl;
+
+			float d = g_settings->getFloat("mouse_sensitivity");
+			d = rangelim(d, 0.01, 100.0);
+			cam->camera_yaw -= dx * d;
+			cam->camera_pitch += dy * d;
+			// turn_amount = v2f(dx, dy).getLength() * d; // deprecated?
+
+#ifdef HAVE_TOUCHSCREENGUI
+			}
+#endif
+
+		if (cam->camera_pitch < -89.5)
+			cam->camera_pitch = -89.5;
+		else if (cam->camera_pitch > 89.5)
+			cam->camera_pitch = 89.5;
+	}
+
+	input->setMousePos(driver->getScreenSize().Width / 2,
+			driver->getScreenSize().Height / 2);
+
+	// Deprecated? Not used anywhere else
+	// recent_turn_speed = recent_turn_speed * 0.9 + turn_amount * 0.1;
+	// std::cerr<<"recent_turn_speed = "<<recent_turn_speed<<std::endl;
+}
+
+
+void MinetestApp::updatePlayerControl(const CameraOrientation &cam)
+{
+	PlayerControl control(
+		input->isKeyDown(getKeySetting("keymap_forward")),
+		input->isKeyDown(getKeySetting("keymap_backward")),
+		input->isKeyDown(getKeySetting("keymap_left")),
+		input->isKeyDown(getKeySetting("keymap_right")),
+		input->isKeyDown(getKeySetting("keymap_jump")),
+		input->isKeyDown(getKeySetting("keymap_special1")),
+		input->isKeyDown(getKeySetting("keymap_sneak")),
+		input->getLeftState(),
+		input->getRightState(),
+		cam.camera_pitch,
+		cam.camera_yaw
+	);
+	client->setPlayerControl(control);
+	LocalPlayer *player = client->getEnv().getLocalPlayer();
+	player->keyPressed =
+			( (u32)(input->isKeyDown(getKeySetting("keymap_forward"))  & 0x1) << 0) |
+			( (u32)(input->isKeyDown(getKeySetting("keymap_backward")) & 0x1) << 1) |
+			( (u32)(input->isKeyDown(getKeySetting("keymap_left"))     & 0x1) << 2) |
+			( (u32)(input->isKeyDown(getKeySetting("keymap_right"))    & 0x1) << 3) |
+			( (u32)(input->isKeyDown(getKeySetting("keymap_jump"))     & 0x1) << 4) |
+			( (u32)(input->isKeyDown(getKeySetting("keymap_special1")) & 0x1) << 5) |
+			( (u32)(input->isKeyDown(getKeySetting("keymap_sneak"))    & 0x1) << 6) |
+			( (u32)(input->getLeftState()                              & 0x1) << 7) |
+			( (u32)(input->getRightState()                             & 0x1) << 8
+	);
+
+}
+
+
+inline void MinetestApp::step(f32 *dtime)
+{
+	bool can_be_and_is_paused =
+			(simple_singleplayer_mode && g_menumgr.pausesGame());
+
+	if (can_be_and_is_paused) {	// This is for a singleplayer server
+		*dtime = 0;             // No time passes
+	} else {
+		if (server != NULL) {
+			//TimeTaker timer("server->step(dtime)");
+			server->step(*dtime);
+		}
+
+		//TimeTaker timer("client.step(dtime)");
+		client->step(*dtime);
+	}
+}
+
+
+void MinetestApp::processClientEvents(CameraOrientation *cam, float *damage_flash)
+{
+	ClientEvent event = client->getClientEvent();
+
+	LocalPlayer *player = client->getEnv().getLocalPlayer();
+
+	for ( ; event.type != CE_NONE; event = client->getClientEvent()) {
+
+		if (event.type == CE_PLAYER_DAMAGE &&
+				client->getHP() != 0) {
+			//u16 damage = event.player_damage.amount;
+			//infostream<<"Player damage: "<<damage<<std::endl;
+
+			*damage_flash += 100.0;
+			*damage_flash += 8.0 * event.player_damage.amount;
+
+			player->hurt_tilt_timer = 1.5;
+			player->hurt_tilt_strength = event.player_damage.amount / 4;
+			player->hurt_tilt_strength = rangelim(player->hurt_tilt_strength, 1.0, 4.0);
+
+			MtEvent *e = new SimpleTriggerEvent("PlayerDamage");
+			gamedef->event()->put(e);
+		} else if (event.type == CE_PLAYER_FORCE_MOVE) {
+			cam->camera_yaw = event.player_force_move.yaw;
+			cam->camera_pitch = event.player_force_move.pitch;
+		} else if (event.type == CE_DEATHSCREEN) {
+			show_deathscreen(&current_formspec, client, gamedef, texture_src,
+					 device, client);
+
+			chat_backend->addMessage(L"", L"You died.");
+
+			/* Handle visualization */
+			*damage_flash = 0;
+			player->hurt_tilt_timer = 0;
+			player->hurt_tilt_strength = 0;
+
+		} else if (event.type == CE_SHOW_FORMSPEC) {
+			FormspecFormSource *fs_src =
+				new FormspecFormSource(*(event.show_formspec.formspec));
+			TextDestPlayerInventory *txt_dst =
+				new TextDestPlayerInventory(client, *(event.show_formspec.formname));
+
+			create_formspec_menu(&current_formspec, client, gamedef,
+					     texture_src, device, fs_src, txt_dst, client);
+
+			delete(event.show_formspec.formspec);
+			delete(event.show_formspec.formname);
+		} else if (event.type == CE_SPAWN_PARTICLE) {
+			video::ITexture *texture =
+				gamedef->tsrc()->getTexture(*(event.spawn_particle.texture));
+
+			new Particle(gamedef, smgr, player, client->getEnv(),
+					*event.spawn_particle.pos,
+					*event.spawn_particle.vel,
+					*event.spawn_particle.acc,
+					event.spawn_particle.expirationtime,
+					event.spawn_particle.size,
+					event.spawn_particle.collisiondetection,
+					event.spawn_particle.vertical,
+					texture,
+					v2f(0.0, 0.0),
+					v2f(1.0, 1.0));
+		} else if (event.type == CE_ADD_PARTICLESPAWNER) {
+			video::ITexture *texture =
+				gamedef->tsrc()->getTexture(*(event.add_particlespawner.texture));
+
+			new ParticleSpawner(gamedef, smgr, player,
+					event.add_particlespawner.amount,
+					event.add_particlespawner.spawntime,
+					*event.add_particlespawner.minpos,
+					*event.add_particlespawner.maxpos,
+					*event.add_particlespawner.minvel,
+					*event.add_particlespawner.maxvel,
+					*event.add_particlespawner.minacc,
+					*event.add_particlespawner.maxacc,
+					event.add_particlespawner.minexptime,
+					event.add_particlespawner.maxexptime,
+					event.add_particlespawner.minsize,
+					event.add_particlespawner.maxsize,
+					event.add_particlespawner.collisiondetection,
+					event.add_particlespawner.vertical,
+					texture,
+					event.add_particlespawner.id);
+		} else if (event.type == CE_DELETE_PARTICLESPAWNER) {
+			delete_particlespawner(event.delete_particlespawner.id);
+		} else if (event.type == CE_HUDADD) {
+			u32 id = event.hudadd.id;
+
+			LocalPlayer *player = client->getEnv().getLocalPlayer();
+			HudElement *e = player->getHud(id);
+
+			if (e != NULL) {
+				delete event.hudadd.pos;
+				delete event.hudadd.name;
+				delete event.hudadd.scale;
+				delete event.hudadd.text;
+				delete event.hudadd.align;
+				delete event.hudadd.offset;
+				delete event.hudadd.world_pos;
+				delete event.hudadd.size;
+				continue;
+			}
+
+			e = new HudElement;
+			e->type   = (HudElementType)event.hudadd.type;
+			e->pos    = *event.hudadd.pos;
+			e->name   = *event.hudadd.name;
+			e->scale  = *event.hudadd.scale;
+			e->text   = *event.hudadd.text;
+			e->number = event.hudadd.number;
+			e->item   = event.hudadd.item;
+			e->dir    = event.hudadd.dir;
+			e->align  = *event.hudadd.align;
+			e->offset = *event.hudadd.offset;
+			e->world_pos = *event.hudadd.world_pos;
+			e->size = *event.hudadd.size;
+
+			u32 new_id = player->addHud(e);
+			//if this isn't true our huds aren't consistent
+			assert(new_id == id);
+
+			delete event.hudadd.pos;
+			delete event.hudadd.name;
+			delete event.hudadd.scale;
+			delete event.hudadd.text;
+			delete event.hudadd.align;
+			delete event.hudadd.offset;
+			delete event.hudadd.world_pos;
+			delete event.hudadd.size;
+		} else if (event.type == CE_HUDRM) {
+			HudElement *e = player->removeHud(event.hudrm.id);
+
+			if (e != NULL)
+				delete(e);
+		} else if (event.type == CE_HUDCHANGE) {
+			u32 id = event.hudchange.id;
+			HudElement *e = player->getHud(id);
+
+			if (e == NULL) {
+				delete event.hudchange.v3fdata;
+				delete event.hudchange.v2fdata;
+				delete event.hudchange.sdata;
+				delete event.hudchange.v2s32data;
+				continue;
+			}
+
+			switch (event.hudchange.stat) {
+			case HUD_STAT_POS:
+				e->pos = *event.hudchange.v2fdata;
+				break;
+
+			case HUD_STAT_NAME:
+				e->name = *event.hudchange.sdata;
+				break;
+
+			case HUD_STAT_SCALE:
+				e->scale = *event.hudchange.v2fdata;
+				break;
+
+			case HUD_STAT_TEXT:
+				e->text = *event.hudchange.sdata;
+				break;
 
-	for(;;)
-	{
-		if(device->run() == false || kill == true ||
-				g_gamecallback->shutdown_requested)
-			break;
+			case HUD_STAT_NUMBER:
+				e->number = event.hudchange.data;
+				break;
 
-		v2u32 screensize = driver->getScreenSize();
+			case HUD_STAT_ITEM:
+				e->item = event.hudchange.data;
+				break;
 
-		// 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;
-		}
+			case HUD_STAT_DIR:
+				e->dir = event.hudchange.data;
+				break;
 
-		g_profiler->graphAdd("mainloop_other", busytime - (float)drawtime/1000.0f);
+			case HUD_STAT_ALIGN:
+				e->align = *event.hudchange.v2fdata;
+				break;
 
-		// Necessary for device->getTimer()->getTime()
-		device->run();
+			case HUD_STAT_OFFSET:
+				e->offset = *event.hudchange.v2fdata;
+				break;
 
-		/*
-			FPS limiter
-		*/
+			case HUD_STAT_WORLD_POS:
+				e->world_pos = *event.hudchange.v3fdata;
+				break;
 
-		{
-			float fps_max = g_menumgr.pausesGame() ?
-					g_settings->getFloat("pause_fps_max") :
-					g_settings->getFloat("fps_max");
-			u32 frametime_min = 1000./fps_max;
-
-			if(busytime_u32 < frametime_min)
-			{
-				u32 sleeptime = frametime_min - busytime_u32;
-				device->sleep(sleeptime);
-				g_profiler->graphAdd("mainloop_sleep", (float)sleeptime/1000.0f);
+			case HUD_STAT_SIZE:
+				e->size = *event.hudchange.v2s32data;
+				break;
 			}
-		}
 
-		// Necessary for device->getTimer()->getTime()
-		device->run();
+			delete event.hudchange.v3fdata;
+			delete event.hudchange.v2fdata;
+			delete event.hudchange.sdata;
+			delete event.hudchange.v2s32data;
+		} else if (event.type == CE_SET_SKY) {
+			sky->setVisible(false);
+
+			if (skybox) {
+				skybox->remove();
+				skybox = NULL;
+			}
+
+			// Handle according to type
+			if (*event.set_sky.type == "regular") {
+				sky->setVisible(true);
+			} else if (*event.set_sky.type == "skybox" &&
+					event.set_sky.params->size() == 6) {
+				sky->setFallbackBgColor(*event.set_sky.bgcolor);
+				skybox = smgr->addSkyBoxSceneNode(
+						 texture_src->getTexture((*event.set_sky.params)[0]),
+						 texture_src->getTexture((*event.set_sky.params)[1]),
+						 texture_src->getTexture((*event.set_sky.params)[2]),
+						 texture_src->getTexture((*event.set_sky.params)[3]),
+						 texture_src->getTexture((*event.set_sky.params)[4]),
+						 texture_src->getTexture((*event.set_sky.params)[5]));
+			}
+			// Handle everything else as plain color
+			else {
+				if (*event.set_sky.type != "plain")
+					infostream << "Unknown sky type: "
+						   << (*event.set_sky.type) << std::endl;
 
-		/*
-			Time difference calculation
-		*/
-		f32 dtime; // in seconds
+				sky->setFallbackBgColor(*event.set_sky.bgcolor);
+			}
 
-		u32 time = device->getTimer()->getTime();
-		if(time > lasttime)
-			dtime = (time - lasttime) / 1000.0;
-		else
-			dtime = 0;
-		lasttime = time;
+			delete event.set_sky.bgcolor;
+			delete event.set_sky.type;
+			delete event.set_sky.params;
+		} else if (event.type == CE_OVERRIDE_DAY_NIGHT_RATIO) {
+			bool enable = event.override_day_night_ratio.do_override;
+			u32 value = event.override_day_night_ratio.ratio_f * 1000;
+			client->getEnv().setDayNightRatioOverride(enable, value);
+		}
+	}
+}
 
-		g_profiler->graphAdd("mainloop_dtime", dtime);
 
-		/* Run timers */
+void MinetestApp::updateCamera(VolatileRunFlags *flags, u32 busy_time,
+		f32 dtime, float time_from_last_punch)
+{
+	LocalPlayer *player = client->getEnv().getLocalPlayer();
 
-		if(nodig_delay_timer >= 0)
-			nodig_delay_timer -= dtime;
-		if(object_hit_delay_timer >= 0)
-			object_hit_delay_timer -= dtime;
-		time_from_last_punch += dtime;
+	/*
+		For interaction purposes, get info about the held item
+		- What item is it?
+		- Is it a usable item?
+		- Can it point to liquids?
+	*/
+	ItemStack playeritem;
+	{
+		InventoryList *mlist = local_inventory->getList("main");
 
-		g_profiler->add("Elapsed time", dtime);
-		g_profiler->avg("FPS", 1./dtime);
+		if (mlist && client->getPlayerItem() < mlist->getSize())
+			playeritem = mlist->getItem(client->getPlayerItem());
+	}
 
-		/*
-			Time average and jitter calculation
-		*/
+	ToolCapabilities playeritem_toolcap =
+		playeritem.getToolCapabilities(itemdef_manager);
 
-		static f32 dtime_avg1 = 0.0;
-		dtime_avg1 = dtime_avg1 * 0.96 + dtime * 0.04;
-		f32 dtime_jitter1 = dtime - dtime_avg1;
-
-		static f32 dtime_jitter1_max_sample = 0.0;
-		static f32 dtime_jitter1_max_fraction = 0.0;
-		{
-			static f32 jitter1_max = 0.0;
-			static f32 counter = 0.0;
-			if(dtime_jitter1 > jitter1_max)
-				jitter1_max = dtime_jitter1;
-			counter += dtime;
-			if(counter > 0.0)
-			{
-				counter -= 3.0;
-				dtime_jitter1_max_sample = jitter1_max;
-				dtime_jitter1_max_fraction
-						= dtime_jitter1_max_sample / (dtime_avg1+0.001);
-				jitter1_max = 0.0;
-			}
-		}
+	v3s16 old_camera_offset = camera->getOffset();
 
-		/*
-			Busytime average and jitter calculation
-		*/
+	if (input->wasKeyDown(getKeySetting("keymap_camera_mode"))) {
+		camera->toggleCameraMode();
+		GenericCAO *playercao = player->getCAO();
 
-		static f32 busytime_avg1 = 0.0;
-		busytime_avg1 = busytime_avg1 * 0.98 + busytime * 0.02;
-		f32 busytime_jitter1 = busytime - busytime_avg1;
-
-		static f32 busytime_jitter1_max_sample = 0.0;
-		static f32 busytime_jitter1_min_sample = 0.0;
-		{
-			static f32 jitter1_max = 0.0;
-			static f32 jitter1_min = 0.0;
-			static f32 counter = 0.0;
-			if(busytime_jitter1 > jitter1_max)
-				jitter1_max = busytime_jitter1;
-			if(busytime_jitter1 < jitter1_min)
-				jitter1_min = busytime_jitter1;
-			counter += dtime;
-			if(counter > 0.0){
-				counter -= 3.0;
-				busytime_jitter1_max_sample = jitter1_max;
-				busytime_jitter1_min_sample = jitter1_min;
-				jitter1_max = 0.0;
-				jitter1_min = 0.0;
-			}
-		}
+		assert(playercao != NULL);
 
-		/*
-			Handle miscellaneous stuff
-		*/
+		playercao->setVisible(camera->getCameraMode() > CAMERA_MODE_FIRST);
+	}
 
-		if(client.accessDenied())
-		{
-			error_message = L"Access denied. Reason: "
-					+client.accessDeniedReason();
-			errorstream<<wide_to_narrow(error_message)<<std::endl;
-			break;
-		}
+	float full_punch_interval = playeritem_toolcap.full_punch_interval;
+	float tool_reload_ratio = time_from_last_punch / full_punch_interval;
 
-		if(g_gamecallback->disconnect_requested)
-		{
-			g_gamecallback->disconnect_requested = false;
-			break;
-		}
+	tool_reload_ratio = MYMIN(tool_reload_ratio, 1.0);
+	camera->update(player, dtime, busy_time / 1000.0f, tool_reload_ratio,
+		      client->getEnv());
+	camera->step(dtime);
 
-		if(g_gamecallback->changepassword_requested)
-		{
-			(new GUIPasswordChange(guienv, guiroot, -1,
-				&g_menumgr, &client))->drop();
-			g_gamecallback->changepassword_requested = false;
-		}
+	v3f camera_position = camera->getPosition();
+	v3f camera_direction = camera->getDirection();
+	f32 camera_fov = camera->getFovMax();
+	v3s16 camera_offset = camera->getOffset();
 
-		if(g_gamecallback->changevolume_requested)
-		{
-			(new GUIVolumeChange(guienv, guiroot, -1,
-				&g_menumgr, &client))->drop();
-			g_gamecallback->changevolume_requested = false;
-		}
+	flags->camera_offset_changed = (camera_offset != old_camera_offset);
 
-		if(g_gamecallback->keyconfig_requested)
-		{
-			(new GUIKeyChangeMenu(guienv, guiroot, -1,
-				&g_menumgr))->drop();
-			g_gamecallback->keyconfig_requested = false;
-		}
+	if (!flags->disable_camera_update) {
+		client->getEnv().getClientMap().updateCamera(camera_position,
+				camera_direction, camera_fov, camera_offset);
 
+		if (flags->camera_offset_changed) {
+			client->updateCameraOffset(camera_offset);
+			client->getEnv().updateCameraOffset(camera_offset);
 
-		/* Process TextureSource's queue */
-		tsrc->processQueue();
+			if (clouds)
+				clouds->updateCameraOffset(camera_offset);
+		}
+	}
+}
 
-		/* Process ItemDefManager's queue */
-		itemdef->processQueue(gamedef);
 
-		/*
-			Process ShaderSource's queue
-		*/
-		shsrc->processQueue();
+void MinetestApp::updateSound(f32 dtime)
+{
+	// Update sound listener
+	v3s16 camera_offset = camera->getOffset();
+	sound->updateListener(camera->getCameraNode()->getPosition() + intToFloat(camera_offset, BS),
+			      v3f(0, 0, 0), // velocity
+			      camera->getDirection(),
+			      camera->getCameraNode()->getUpVector());
+	sound->setListenerGain(g_settings->getFloat("sound_volume"));
 
-		/*
-			Random calculations
-		*/
-		hud.resizeHotbar();
 
-		// Hilight boxes collected during the loop and displayed
-		std::vector<aabb3f> hilightboxes;
+	//	Update sound maker
+	soundmaker->step(dtime);
 
-		/* reset infotext */
-		infotext = L"";
-		/*
-			Profiler
-		*/
-		float profiler_print_interval =
-				g_settings->getFloat("profiler_print_interval");
-		bool print_to_log = true;
-		if(profiler_print_interval == 0){
-			print_to_log = false;
-			profiler_print_interval = 5;
-		}
-		if(m_profiler_interval.step(dtime, profiler_print_interval))
-		{
-			if(print_to_log){
-				infostream<<"Profiler:"<<std::endl;
-				g_profiler->print(infostream);
-			}
+	LocalPlayer *player = client->getEnv().getLocalPlayer();
 
-			update_profiler_gui(guitext_profiler, font, text_height,
-					show_profiler, show_profiler_max);
+	ClientMap &map = client->getEnv().getClientMap();
+	MapNode n = map.getNodeNoEx(player->getStandingNodePos());
+	soundmaker->m_player_step_sound = nodedef_manager->get(n).sound_footstep;
+}
 
-			g_profiler->clear();
-		}
 
-		/*
-			Direct handling of user input
-		*/
+void MinetestApp::processPlayerInteraction(std::vector<aabb3f> &highlight_boxes,
+		InteractParams *interactArgs, f32 dtime, bool show_hud, bool show_debug)
+{
+	LocalPlayer *player = client->getEnv().getLocalPlayer();
 
-		// Reset input if window not active or some menu is active
-		if(device->isWindowActive() == false
-				|| noMenuActive() == false
-				|| guienv->hasFocus(gui_chat_console))
-		{
-			input->clear();
-		}
-		if (!guienv->hasFocus(gui_chat_console) && gui_chat_console->isOpen())
-		{
-			gui_chat_console->closeConsoleAtOnce();
-		}
+	ItemStack playeritem;
+	{
+		InventoryList *mlist = local_inventory->getList("main");
 
-		// Input handler step() (used by the random input generator)
-		input->step(dtime);
-#ifdef HAVE_TOUCHSCREENGUI
-		if (g_touchscreengui) {
-			g_touchscreengui->step(dtime);
-		}
-#endif
-#ifdef __ANDROID__
-		if (current_formspec != 0)
-			current_formspec->getAndroidUIInput();
-#endif
+		if (mlist && client->getPlayerItem() < mlist->getSize())
+			playeritem = mlist->getItem(client->getPlayerItem());
+	}
 
-		// Increase timer for doubleclick of "jump"
-		if(g_settings->getBool("doubletap_jump") && jump_timer <= 0.2)
-			jump_timer += dtime;
+	const ItemDefinition &playeritem_def =
+			playeritem.getDefinition(itemdef_manager);
 
-		/*
-			Launch menus and trigger stuff according to keys
-		*/
-		if(input->wasKeyDown(getKeySetting("keymap_drop")))
-		{
-			// drop selected item
-			IDropAction *a = new IDropAction();
-			a->count = 0;
-			a->from_inv.setCurrentPlayer();
-			a->from_list = "main";
-			a->from_i = client.getPlayerItem();
-			client.inventoryAction(a);
-		}
-		else if(input->wasKeyDown(getKeySetting("keymap_inventory")))
-		{
-			infostream<<"the_game: "
-					<<"Launching inventory"<<std::endl;
+	v3f player_position  = player->getPosition();
+	v3f camera_position  = camera->getPosition();
+	v3f camera_direction = camera->getDirection();
+	v3s16 camera_offset  = camera->getOffset();
 
-			PlayerInventoryFormSource* fs_src = new PlayerInventoryFormSource(&client);
-			TextDest* txt_dst = new TextDestPlayerInventory(&client);
 
-			create_formspec_menu(&current_formspec, &client, gamedef, tsrc, device, fs_src, txt_dst, &client);
+	/*
+		Calculate what block is the crosshair pointing to
+	*/
 
-			InventoryLocation inventoryloc;
-			inventoryloc.setCurrentPlayer();
-			current_formspec->setFormSpec(fs_src->getForm(), inventoryloc);
-		}
-		else if(input->wasKeyDown(EscapeKey) || input->wasKeyDown(CancelKey))
-		{
-			show_pause_menu(&current_formspec, &client, gamedef, tsrc, device,
-					simple_singleplayer_mode);
-		}
-		else if(input->wasKeyDown(getKeySetting("keymap_chat")))
-		{
-			show_chat_menu(&current_formspec, &client, gamedef, tsrc, device,
-					&client,"");
-		}
-		else if(input->wasKeyDown(getKeySetting("keymap_cmd")))
-		{
-			show_chat_menu(&current_formspec, &client, gamedef, tsrc, device,
-					&client,"/");
-		}
-		else if(input->wasKeyDown(getKeySetting("keymap_console")))
-		{
-			if (!gui_chat_console->isOpenInhibited())
-			{
-				// Open up to over half of the screen
-				gui_chat_console->openConsole(0.6);
-				guienv->setFocus(gui_chat_console);
-			}
-		}
-		else if(input->wasKeyDown(getKeySetting("keymap_freemove")))
-		{
-			if(g_settings->getBool("free_move"))
-			{
-				g_settings->set("free_move","false");
-				statustext = L"free_move disabled";
-				statustext_time = 0;
-			}
-			else
-			{
-				g_settings->set("free_move","true");
-				statustext = L"free_move enabled";
-				statustext_time = 0;
-				if(!client.checkPrivilege("fly"))
-					statustext += L" (note: no 'fly' privilege)";
-			}
-		}
-		else if(input->wasKeyDown(getKeySetting("keymap_jump")))
-		{
-			if(g_settings->getBool("doubletap_jump") && jump_timer < 0.2)
-			{
-				if(g_settings->getBool("free_move"))
-				{
-					g_settings->set("free_move","false");
-					statustext = L"free_move disabled";
-					statustext_time = 0;
-				}
-				else
-				{
-					g_settings->set("free_move","true");
-					statustext = L"free_move enabled";
-					statustext_time = 0;
-					if(!client.checkPrivilege("fly"))
-						statustext += L" (note: no 'fly' privilege)";
-				}
-			}
-			reset_jump_timer = true;
-		}
-		else if(input->wasKeyDown(getKeySetting("keymap_fastmove")))
-		{
-			if(g_settings->getBool("fast_move"))
-			{
-				g_settings->set("fast_move","false");
-				statustext = L"fast_move disabled";
-				statustext_time = 0;
-			}
-			else
-			{
-				g_settings->set("fast_move","true");
-				statustext = L"fast_move enabled";
-				statustext_time = 0;
-				if(!client.checkPrivilege("fast"))
-					statustext += L" (note: no 'fast' privilege)";
-			}
-		}
-		else if(input->wasKeyDown(getKeySetting("keymap_noclip")))
-		{
-			if(g_settings->getBool("noclip"))
-			{
-				g_settings->set("noclip","false");
-				statustext = L"noclip disabled";
-				statustext_time = 0;
-			}
-			else
-			{
-				g_settings->set("noclip","true");
-				statustext = L"noclip enabled";
-				statustext_time = 0;
-				if(!client.checkPrivilege("noclip"))
-					statustext += L" (note: no 'noclip' privilege)";
-			}
-		}
-		else if(input->wasKeyDown(getKeySetting("keymap_screenshot")))
-		{
-			client.makeScreenshot(device);
-		}
-		else if(input->wasKeyDown(getKeySetting("keymap_toggle_hud")))
-		{
-			show_hud = !show_hud;
-			if(show_hud) {
-				statustext = L"HUD shown";
-				client.setHighlighted(client.getHighlighted(), true);
-			} else {
-				statustext = L"HUD hidden";
-				client.setHighlighted(client.getHighlighted(), false);
-			}
-			statustext_time = 0;
-		}
-		else if(input->wasKeyDown(getKeySetting("keymap_toggle_chat")))
-		{
-			show_chat = !show_chat;
-			if(show_chat)
-				statustext = L"Chat shown";
-			else
-				statustext = L"Chat hidden";
-			statustext_time = 0;
-		}
-		else if(input->wasKeyDown(getKeySetting("keymap_toggle_force_fog_off")))
-		{
-			force_fog_off = !force_fog_off;
-			if(force_fog_off)
-				statustext = L"Fog disabled";
-			else
-				statustext = L"Fog enabled";
-			statustext_time = 0;
-		}
-		else if(input->wasKeyDown(getKeySetting("keymap_toggle_update_camera")))
-		{
-			disable_camera_update = !disable_camera_update;
-			if(disable_camera_update)
-				statustext = L"Camera update disabled";
-			else
-				statustext = L"Camera update enabled";
-			statustext_time = 0;
-		}
-		else if(input->wasKeyDown(getKeySetting("keymap_toggle_debug")))
-		{
-			// Initial / 3x toggle: Chat only
-			// 1x toggle: Debug text with chat
-			// 2x toggle: Debug text with profiler graph
-			if(!show_debug)
-			{
-				show_debug = true;
-				show_profiler_graph = false;
-				statustext = L"Debug info shown";
-				statustext_time = 0;
-			}
-			else if(show_profiler_graph)
-			{
-				show_debug = false;
-				show_profiler_graph = false;
-				statustext = L"Debug info and profiler graph hidden";
-				statustext_time = 0;
-			}
-			else
-			{
-				show_profiler_graph = true;
-				statustext = L"Profiler graph shown";
-				statustext_time = 0;
-			}
-		}
-		else if(input->wasKeyDown(getKeySetting("keymap_toggle_profiler")))
-		{
-			show_profiler = (show_profiler + 1) % (show_profiler_max + 1);
-
-			// FIXME: This updates the profiler with incomplete values
-			update_profiler_gui(guitext_profiler, font, text_height,
-					show_profiler, show_profiler_max);
-
-			if(show_profiler != 0)
-			{
-				std::wstringstream sstr;
-				sstr<<"Profiler shown (page "<<show_profiler
-					<<" of "<<show_profiler_max<<")";
-				statustext = sstr.str();
-				statustext_time = 0;
-			}
-			else
-			{
-				statustext = L"Profiler hidden";
-				statustext_time = 0;
-			}
-		}
-		else if(input->wasKeyDown(getKeySetting("keymap_increase_viewing_range_min")))
-		{
-			s16 range = g_settings->getS16("viewing_range_nodes_min");
-			s16 range_new = range + 10;
-			g_settings->set("viewing_range_nodes_min", itos(range_new));
-			statustext = narrow_to_wide(
-					"Minimum viewing range changed to "
-					+ itos(range_new));
-			statustext_time = 0;
-		}
-		else if(input->wasKeyDown(getKeySetting("keymap_decrease_viewing_range_min")))
-		{
-			s16 range = g_settings->getS16("viewing_range_nodes_min");
-			s16 range_new = range - 10;
-			if(range_new < 0)
-				range_new = range;
-			g_settings->set("viewing_range_nodes_min",
-					itos(range_new));
-			statustext = narrow_to_wide(
-					"Minimum viewing range changed to "
-					+ itos(range_new));
-			statustext_time = 0;
-		}
+	f32 d = playeritem_def.range; // max. distance
+	f32 d_hand = itemdef_manager->get("").range;
 
-		// Reset jump_timer
-		if(!input->isKeyDown(getKeySetting("keymap_jump")) && reset_jump_timer)
-		{
-			reset_jump_timer = false;
-			jump_timer = 0.0;
-		}
+	if (d < 0 && d_hand >= 0)
+		d = d_hand;
+	else if (d < 0)
+		d = 4.0;
 
-		// Handle QuicktuneShortcutter
-		if(input->wasKeyDown(getKeySetting("keymap_quicktune_next")))
-			quicktune.next();
-		if(input->wasKeyDown(getKeySetting("keymap_quicktune_prev")))
-			quicktune.prev();
-		if(input->wasKeyDown(getKeySetting("keymap_quicktune_inc")))
-			quicktune.inc();
-		if(input->wasKeyDown(getKeySetting("keymap_quicktune_dec")))
-			quicktune.dec();
-		{
-			std::string msg = quicktune.getMessage();
-			if(msg != ""){
-				statustext = narrow_to_wide(msg);
-				statustext_time = 0;
-			}
-		}
+	core::line3d<f32> shootline;
 
-		// Item selection with mouse wheel
-		u16 new_playeritem = client.getPlayerItem();
-		{
-			s32 wheel = input->getMouseWheel();
-			u16 max_item = MYMIN(PLAYER_INVENTORY_SIZE-1,
-					player->hud_hotbar_itemcount-1);
-
-			if(wheel < 0)
-			{
-				if(new_playeritem < max_item)
-					new_playeritem++;
-				else
-					new_playeritem = 0;
-			}
-			else if(wheel > 0)
-			{
-				if(new_playeritem > 0)
-					new_playeritem--;
-				else
-					new_playeritem = max_item;
-			}
-		}
+	if (camera->getCameraMode() != CAMERA_MODE_THIRD_FRONT) {
 
-		// Item selection
-		for(u16 i=0; i<10; i++)
-		{
-			const KeyPress *kp = NumberKey + (i + 1) % 10;
-			if(input->wasKeyDown(*kp))
-			{
-				if(i < PLAYER_INVENTORY_SIZE && i < player->hud_hotbar_itemcount)
-				{
-					new_playeritem = i;
-
-					infostream<<"Selected item: "
-							<<new_playeritem<<std::endl;
-				}
-			}
-		}
+		shootline = core::line3d<f32>(camera_position,
+						camera_position + camera_direction * BS * (d + 1));
 
-		// Viewing range selection
-		if(input->wasKeyDown(getKeySetting("keymap_rangeselect")))
-		{
-			draw_control.range_all = !draw_control.range_all;
-			if(draw_control.range_all)
-			{
-				infostream<<"Enabled full viewing range"<<std::endl;
-				statustext = L"Enabled full viewing range";
-				statustext_time = 0;
-			}
-			else
-			{
-				infostream<<"Disabled full viewing range"<<std::endl;
-				statustext = L"Disabled full viewing range";
-				statustext_time = 0;
-			}
-		}
+	} else {
+	    // prevent player pointing anything in front-view
+		if (camera->getCameraMode() == CAMERA_MODE_THIRD_FRONT)
+			shootline = core::line3d<f32>(0, 0, 0, 0, 0, 0);
+	}
 
-		// Print debug stacks
-		if(input->wasKeyDown(getKeySetting("keymap_print_debug_stacks")))
-		{
-			dstream<<"-----------------------------------------"
-					<<std::endl;
-			dstream<<DTIME<<"Printing debug stacks:"<<std::endl;
-			dstream<<"-----------------------------------------"
-					<<std::endl;
-			debug_stacks_print();
-		}
+#ifdef HAVE_TOUCHSCREENGUI
 
-		/*
-			Mouse and camera control
-			NOTE: Do this before client.setPlayerControl() to not cause a camera lag of one frame
-		*/
+	if ((g_settings->getBool("touchtarget")) && (g_touchscreengui)) {
+		shootline = g_touchscreengui->getShootline();
+		shootline.start += intToFloat(camera_offset, BS);
+		shootline.end += intToFloat(camera_offset, BS);
+	}
 
-		float turn_amount = 0;
-		if((device->isWindowActive() && noMenuActive()) || random_input)
-		{
-#ifndef __ANDROID__
-			if(!random_input)
-			{
-				// Mac OSX gets upset if this is set every frame
-				if(device->getCursorControl()->isVisible())
-					device->getCursorControl()->setVisible(false);
-			}
 #endif
 
-			if(first_loop_after_window_activation){
-				//infostream<<"window active, first loop"<<std::endl;
-				first_loop_after_window_activation = false;
+	PointedThing pointed = getPointedThing(
+			// input
+			client, player_position, camera_direction,
+			camera_position, shootline, d,
+			playeritem_def.liquids_pointable,
+			!interactArgs->ldown_for_dig,
+			camera_offset,
+			// output
+			highlight_boxes,
+			interactArgs->selected_object);
+
+	if (pointed != interactArgs->pointed_old) {
+		infostream << "Pointing at " << pointed.dump() << std::endl;
+
+		if (g_settings->getBool("enable_node_highlighting")) {
+			if (pointed.type == POINTEDTHING_NODE) {
+				client->setHighlighted(pointed.node_undersurface, show_hud);
 			} else {
-#ifdef HAVE_TOUCHSCREENGUI
-				if (g_touchscreengui) {
-					camera_yaw   = g_touchscreengui->getYaw();
-					camera_pitch = g_touchscreengui->getPitch();
-				} else {
-#endif
-					s32 dx = input->getMousePos().X - (driver->getScreenSize().Width/2);
-					s32 dy = input->getMousePos().Y - (driver->getScreenSize().Height/2);
-				if ((invert_mouse)
-						|| (camera.getCameraMode() == CAMERA_MODE_THIRD_FRONT)) {
-					dy = -dy;
-				}
-				//infostream<<"window active, pos difference "<<dx<<","<<dy<<std::endl;
-
-				/*const float keyspeed = 500;
-				if(input->isKeyDown(irr::KEY_UP))
-					dy -= dtime * keyspeed;
-				if(input->isKeyDown(irr::KEY_DOWN))
-					dy += dtime * keyspeed;
-				if(input->isKeyDown(irr::KEY_LEFT))
-					dx -= dtime * keyspeed;
-				if(input->isKeyDown(irr::KEY_RIGHT))
-					dx += dtime * keyspeed;*/
-
-				float d = g_settings->getFloat("mouse_sensitivity");
-				d = rangelim(d, 0.01, 100.0);
-				camera_yaw -= dx*d;
-				camera_pitch += dy*d;
-				turn_amount = v2f(dx, dy).getLength() * d;
-
-#ifdef HAVE_TOUCHSCREENGUI
-				}
-#endif
-				if(camera_pitch < -89.5) camera_pitch = -89.5;
-				if(camera_pitch > 89.5) camera_pitch = 89.5;
+				client->setHighlighted(pointed.node_undersurface, false);
 			}
-			input->setMousePos((driver->getScreenSize().Width/2),
-					(driver->getScreenSize().Height/2));
-		}
-		else{
-#ifndef ANDROID
-			// Mac OSX gets upset if this is set every frame
-			if(device->getCursorControl()->isVisible() == false)
-				device->getCursorControl()->setVisible(true);
-#endif
-
-			//infostream<<"window inactive"<<std::endl;
-			first_loop_after_window_activation = true;
 		}
-		recent_turn_speed = recent_turn_speed * 0.9 + turn_amount * 0.1;
-		//std::cerr<<"recent_turn_speed = "<<recent_turn_speed<<std::endl;
+	}
 
-		/*
-			Player speed control
-		*/
-		{
-			/*bool a_up,
-			bool a_down,
-			bool a_left,
-			bool a_right,
-			bool a_jump,
-			bool a_superspeed,
-			bool a_sneak,
-			bool a_LMB,
-			bool a_RMB,
-			float a_pitch,
-			float a_yaw*/
-			PlayerControl control(
-				input->isKeyDown(getKeySetting("keymap_forward")),
-				input->isKeyDown(getKeySetting("keymap_backward")),
-				input->isKeyDown(getKeySetting("keymap_left")),
-				input->isKeyDown(getKeySetting("keymap_right")),
-				input->isKeyDown(getKeySetting("keymap_jump")),
-				input->isKeyDown(getKeySetting("keymap_special1")),
-				input->isKeyDown(getKeySetting("keymap_sneak")),
-				input->getLeftState(),
-				input->getRightState(),
-				camera_pitch,
-				camera_yaw
-			);
-			client.setPlayerControl(control);
-			LocalPlayer* player = client.getEnv().getLocalPlayer();
-			player->keyPressed=
-			(((int)input->isKeyDown(getKeySetting("keymap_forward"))  & 0x1) << 0) |
-			(((int)input->isKeyDown(getKeySetting("keymap_backward")) & 0x1) << 1) |
-			(((int)input->isKeyDown(getKeySetting("keymap_left"))     & 0x1) << 2) |
-			(((int)input->isKeyDown(getKeySetting("keymap_right"))    & 0x1) << 3) |
-			(((int)input->isKeyDown(getKeySetting("keymap_jump"))     & 0x1) << 4) |
-			(((int)input->isKeyDown(getKeySetting("keymap_special1")) & 0x1) << 5) |
-			(((int)input->isKeyDown(getKeySetting("keymap_sneak"))    & 0x1) << 6) |
-			(((int)input->getLeftState()  & 0x1) << 7) |
-			(((int)input->getRightState() & 0x1) << 8);
+	/*
+		Stop digging when
+		- releasing left mouse button
+		- pointing away from node
+	*/
+	if (interactArgs->digging) {
+		if (input->getLeftReleased()) {
+			infostream << "Left button released"
+			           << " (stopped digging)" << std::endl;
+			interactArgs->digging = false;
+		} else if (pointed != interactArgs->pointed_old) {
+			if (pointed.type == POINTEDTHING_NODE
+					&& interactArgs->pointed_old.type == POINTEDTHING_NODE
+					&& pointed.node_undersurface
+							== interactArgs->pointed_old.node_undersurface) {
+				// Still pointing to the same node, but a different face.
+				// Don't reset.
+			} else {
+				infostream << "Pointing away from node"
+				           << " (stopped digging)" << std::endl;
+				interactArgs->digging = false;
+			}
 		}
 
-		/*
-			Run server, client (and process environments)
-		*/
-		bool can_be_and_is_paused =
-				(simple_singleplayer_mode && g_menumgr.pausesGame());
-		if(can_be_and_is_paused)
-		{
-			// No time passes
-			dtime = 0;
-		}
-		else
-		{
-			if(server != NULL)
-			{
-				//TimeTaker timer("server->step(dtime)");
-				server->step(dtime);
-			}
-			{
-				//TimeTaker timer("client.step(dtime)");
-				client.step(dtime);
-			}
+		if (!interactArgs->digging) {
+			client->interact(1, interactArgs->pointed_old);
+			client->setCrack(-1, v3s16(0, 0, 0));
+			interactArgs->dig_time = 0.0;
 		}
+	}
 
-		{
-			// Read client events
-			for(;;) {
-				ClientEvent event = client.getClientEvent();
-				if(event.type == CE_NONE) {
-					break;
-				}
-				else if(event.type == CE_PLAYER_DAMAGE &&
-						client.getHP() != 0) {
-					//u16 damage = event.player_damage.amount;
-					//infostream<<"Player damage: "<<damage<<std::endl;
+	if (!interactArgs->digging && interactArgs->ldown_for_dig && !input->getLeftState()) {
+		interactArgs->ldown_for_dig = false;
+	}
 
-					damage_flash += 100.0;
-					damage_flash += 8.0 * event.player_damage.amount;
+	interactArgs->left_punch = false;
 
-					player->hurt_tilt_timer = 1.5;
-					player->hurt_tilt_strength = event.player_damage.amount/4;
-					player->hurt_tilt_strength = rangelim(player->hurt_tilt_strength, 1.0, 4.0);
+	soundmaker->m_player_leftpunch_sound.name = "";
 
-					MtEvent *e = new SimpleTriggerEvent("PlayerDamage");
-					gamedef->event()->put(e);
-				}
-				else if(event.type == CE_PLAYER_FORCE_MOVE) {
-					camera_yaw = event.player_force_move.yaw;
-					camera_pitch = event.player_force_move.pitch;
-				}
-				else if(event.type == CE_DEATHSCREEN) {
-					show_deathscreen(&current_formspec, &client, gamedef, tsrc,
-							device, &client);
+	if (input->getRightState())
+		interactArgs->repeat_rightclick_timer += dtime;
+	else
+		interactArgs->repeat_rightclick_timer = 0;
 
-					chat_backend.addMessage(L"", L"You died.");
+	if (playeritem_def.usable && input->getLeftState()) {
+		if (input->getLeftClicked())
+			client->interact(4, pointed);
+	} else if (pointed.type == POINTEDTHING_NODE) {
+		ToolCapabilities playeritem_toolcap =
+				playeritem.getToolCapabilities(itemdef_manager);
+		handlePointingAtNode(interactArgs, pointed, playeritem_def,
+				playeritem_toolcap, dtime);
+	} else if (pointed.type == POINTEDTHING_OBJECT) {
+		handlePointingAtObject(interactArgs, pointed, playeritem,
+				player_position, show_debug);
+	} else if (input->getLeftState()) {
+		// When button is held down in air, show continuous animation
+		interactArgs->left_punch = true;
+	}
 
-					/* Handle visualization */
-					damage_flash = 0;
+	interactArgs->pointed_old = pointed;
 
-					LocalPlayer* player = client.getEnv().getLocalPlayer();
-					player->hurt_tilt_timer = 0;
-					player->hurt_tilt_strength = 0;
+	if (interactArgs->left_punch || input->getLeftClicked())
+		camera->setDigging(0); // left click animation
 
-				}
-				else if (event.type == CE_SHOW_FORMSPEC) {
-					FormspecFormSource* fs_src =
-							new FormspecFormSource(*(event.show_formspec.formspec));
-					TextDestPlayerInventory* txt_dst =
-							new TextDestPlayerInventory(&client,*(event.show_formspec.formname));
+	input->resetLeftClicked();
+	input->resetRightClicked();
 
-					create_formspec_menu(&current_formspec, &client, gamedef,
-							tsrc, device, fs_src, txt_dst, &client);
+	input->resetLeftReleased();
+	input->resetRightReleased();
+}
 
-					delete(event.show_formspec.formspec);
-					delete(event.show_formspec.formname);
-				}
-				else if(event.type == CE_SPAWN_PARTICLE) {
-					LocalPlayer* player = client.getEnv().getLocalPlayer();
-					video::ITexture *texture =
-						gamedef->tsrc()->getTexture(*(event.spawn_particle.texture));
-
-					new Particle(gamedef, smgr, player, client.getEnv(),
-						*event.spawn_particle.pos,
-						*event.spawn_particle.vel,
-						*event.spawn_particle.acc,
-						 event.spawn_particle.expirationtime,
-						 event.spawn_particle.size,
-						 event.spawn_particle.collisiondetection,
-						 event.spawn_particle.vertical,
-						 texture,
-						 v2f(0.0, 0.0),
-						 v2f(1.0, 1.0));
-				}
-				else if(event.type == CE_ADD_PARTICLESPAWNER) {
-					LocalPlayer* player = client.getEnv().getLocalPlayer();
-					video::ITexture *texture =
-						gamedef->tsrc()->getTexture(*(event.add_particlespawner.texture));
-
-					new ParticleSpawner(gamedef, smgr, player,
-						 event.add_particlespawner.amount,
-						 event.add_particlespawner.spawntime,
-						*event.add_particlespawner.minpos,
-						*event.add_particlespawner.maxpos,
-						*event.add_particlespawner.minvel,
-						*event.add_particlespawner.maxvel,
-						*event.add_particlespawner.minacc,
-						*event.add_particlespawner.maxacc,
-						 event.add_particlespawner.minexptime,
-						 event.add_particlespawner.maxexptime,
-						 event.add_particlespawner.minsize,
-						 event.add_particlespawner.maxsize,
-						 event.add_particlespawner.collisiondetection,
-						 event.add_particlespawner.vertical,
-						 texture,
-						 event.add_particlespawner.id);
-				}
-				else if(event.type == CE_DELETE_PARTICLESPAWNER) {
-					delete_particlespawner (event.delete_particlespawner.id);
-				}
-				else if (event.type == CE_HUDADD) {
-					u32 id = event.hudadd.id;
-
-					HudElement *e = player->getHud(id);
-
-					if (e != NULL) {
-						delete event.hudadd.pos;
-						delete event.hudadd.name;
-						delete event.hudadd.scale;
-						delete event.hudadd.text;
-						delete event.hudadd.align;
-						delete event.hudadd.offset;
-						delete event.hudadd.world_pos;
-						delete event.hudadd.size;
-						continue;
-					}
 
-					e = new HudElement;
-					e->type   = (HudElementType)event.hudadd.type;
-					e->pos    = *event.hudadd.pos;
-					e->name   = *event.hudadd.name;
-					e->scale  = *event.hudadd.scale;
-					e->text   = *event.hudadd.text;
-					e->number = event.hudadd.number;
-					e->item   = event.hudadd.item;
-					e->dir    = event.hudadd.dir;
-					e->align  = *event.hudadd.align;
-					e->offset = *event.hudadd.offset;
-					e->world_pos = *event.hudadd.world_pos;
-					e->size = *event.hudadd.size;
-
-					u32 new_id = player->addHud(e);
-					//if this isn't true our huds aren't consistent
-					assert(new_id == id);
-
-					delete event.hudadd.pos;
-					delete event.hudadd.name;
-					delete event.hudadd.scale;
-					delete event.hudadd.text;
-					delete event.hudadd.align;
-					delete event.hudadd.offset;
-					delete event.hudadd.world_pos;
-					delete event.hudadd.size;
-				}
-				else if (event.type == CE_HUDRM) {
-					HudElement* e = player->removeHud(event.hudrm.id);
+void MinetestApp::handlePointingAtNode(InteractParams *interactArgs,
+		const PointedThing &pointed, const ItemDefinition &playeritem_def,
+		const ToolCapabilities &playeritem_toolcap, f32 dtime)
+{
+	v3s16 nodepos = pointed.node_undersurface;
+	v3s16 neighbourpos = pointed.node_abovesurface;
 
-					if (e != NULL)
-						delete (e);
-				}
-				else if (event.type == CE_HUDCHANGE) {
-					u32 id = event.hudchange.id;
-					HudElement* e = player->getHud(id);
-					if (e == NULL)
-					{
-						delete event.hudchange.v3fdata;
-						delete event.hudchange.v2fdata;
-						delete event.hudchange.sdata;
-						delete event.hudchange.v2s32data;
-						continue;
-					}
+	/*
+		Check information text of node
+	*/
 
-					switch (event.hudchange.stat) {
-						case HUD_STAT_POS:
-							e->pos = *event.hudchange.v2fdata;
-							break;
-						case HUD_STAT_NAME:
-							e->name = *event.hudchange.sdata;
-							break;
-						case HUD_STAT_SCALE:
-							e->scale = *event.hudchange.v2fdata;
-							break;
-						case HUD_STAT_TEXT:
-							e->text = *event.hudchange.sdata;
-							break;
-						case HUD_STAT_NUMBER:
-							e->number = event.hudchange.data;
-							break;
-						case HUD_STAT_ITEM:
-							e->item = event.hudchange.data;
-							break;
-						case HUD_STAT_DIR:
-							e->dir = event.hudchange.data;
-							break;
-						case HUD_STAT_ALIGN:
-							e->align = *event.hudchange.v2fdata;
-							break;
-						case HUD_STAT_OFFSET:
-							e->offset = *event.hudchange.v2fdata;
-							break;
-						case HUD_STAT_WORLD_POS:
-							e->world_pos = *event.hudchange.v3fdata;
-							break;
-						case HUD_STAT_SIZE:
-							e->size = *event.hudchange.v2s32data;
-							break;
-					}
+	ClientMap &map = client->getEnv().getClientMap();
+	NodeMetadata *meta = map.getNodeMetadata(nodepos);
 
-					delete event.hudchange.v3fdata;
-					delete event.hudchange.v2fdata;
-					delete event.hudchange.sdata;
-					delete event.hudchange.v2s32data;
-				}
-				else if (event.type == CE_SET_SKY) {
-					sky->setVisible(false);
-					if(skybox){
-						skybox->remove();
-						skybox = NULL;
-					}
-					// Handle according to type
-					if(*event.set_sky.type == "regular") {
-						sky->setVisible(true);
-					}
-					else if(*event.set_sky.type == "skybox" &&
-							event.set_sky.params->size() == 6) {
-						sky->setFallbackBgColor(*event.set_sky.bgcolor);
-						skybox = smgr->addSkyBoxSceneNode(
-								tsrc->getTexture((*event.set_sky.params)[0]),
-								tsrc->getTexture((*event.set_sky.params)[1]),
-								tsrc->getTexture((*event.set_sky.params)[2]),
-								tsrc->getTexture((*event.set_sky.params)[3]),
-								tsrc->getTexture((*event.set_sky.params)[4]),
-								tsrc->getTexture((*event.set_sky.params)[5]));
-					}
-					// Handle everything else as plain color
-					else {
-						if(*event.set_sky.type != "plain")
-							infostream<<"Unknown sky type: "
-									<<(*event.set_sky.type)<<std::endl;
-						sky->setFallbackBgColor(*event.set_sky.bgcolor);
-					}
+	if (meta) {
+		infotext = narrow_to_wide(meta->getString("infotext"));
+	} else {
+		MapNode n = map.getNode(nodepos);
 
-					delete event.set_sky.bgcolor;
-					delete event.set_sky.type;
-					delete event.set_sky.params;
-				}
-				else if (event.type == CE_OVERRIDE_DAY_NIGHT_RATIO) {
-					bool enable = event.override_day_night_ratio.do_override;
-					u32 value = event.override_day_night_ratio.ratio_f * 1000;
-					client.getEnv().setDayNightRatioOverride(enable, value);
-				}
-			}
+		if (nodedef_manager->get(n).tiledef[0].name == "unknown_node.png") {
+			infotext = L"Unknown node: ";
+			infotext += narrow_to_wide(nodedef_manager->get(n).name);
 		}
+	}
 
-		//TimeTaker //timer2("//timer2");
+	if (interactArgs->nodig_delay_timer <= 0.0 && input->getLeftState()
+			&& client->checkPrivilege("interact")) {
+		handleDigging(interactArgs, pointed, nodepos, playeritem_toolcap, dtime);
+	}
 
-		/*
-			For interaction purposes, get info about the held item
-			- What item is it?
-			- Is it a usable item?
-			- Can it point to liquids?
-		*/
-		ItemStack playeritem;
-		{
-			InventoryList *mlist = local_inventory.getList("main");
-			if((mlist != NULL) && (client.getPlayerItem() < mlist->getSize()))
-				playeritem = mlist->getItem(client.getPlayerItem());
-		}
-		const ItemDefinition &playeritem_def =
-				playeritem.getDefinition(itemdef);
-		ToolCapabilities playeritem_toolcap =
-				playeritem.getToolCapabilities(itemdef);
+	if ((input->getRightClicked() ||
+			interactArgs->repeat_rightclick_timer >=
+			g_settings->getFloat("repeat_rightclick_time")) &&
+			client->checkPrivilege("interact")) {
+		interactArgs->repeat_rightclick_timer = 0;
+		infostream << "Ground right-clicked" << std::endl;
 
-		/*
-			Update camera
-		*/
+		if (meta && meta->getString("formspec") != "" && !random_input
+				&& !input->isKeyDown(getKeySetting("keymap_sneak"))) {
+			infostream << "Launching custom inventory view" << std::endl;
 
-		v3s16 old_camera_offset = camera.getOffset();
+			InventoryLocation inventoryloc;
+			inventoryloc.setNodeMeta(nodepos);
 
-		LocalPlayer* player = client.getEnv().getLocalPlayer();
-		float full_punch_interval = playeritem_toolcap.full_punch_interval;
-		float tool_reload_ratio = time_from_last_punch / full_punch_interval;
+			NodeMetadataFormSource *fs_src = new NodeMetadataFormSource(
+				&client->getEnv().getClientMap(), nodepos);
+			TextDest *txt_dst = new TextDestNodeMetadata(nodepos, client);
 
-		if(input->wasKeyDown(getKeySetting("keymap_camera_mode"))) {
-			camera.toggleCameraMode();
-			GenericCAO* playercao = player->getCAO();
+			create_formspec_menu(&current_formspec, client, gamedef,
+					     texture_src, device, fs_src, txt_dst, client);
 
-			assert( playercao != NULL );
-			if (camera.getCameraMode() > CAMERA_MODE_FIRST) {
-				playercao->setVisible(true);
-			}
-			else {
-				playercao->setVisible(false);
+			current_formspec->setFormSpec(meta->getString("formspec"), inventoryloc);
+		} else {
+			// Report right click to server
+
+			camera->setDigging(1);  // right click animation (always shown for feedback)
+
+			// If the wielded item has node placement prediction,
+			// make that happen
+			bool placed = nodePlacementPrediction(*client,
+					playeritem_def,
+					nodepos, neighbourpos);
+
+			if (placed) {
+				// Report to server
+				client->interact(3, pointed);
+				// Read the sound
+				soundmaker->m_player_rightpunch_sound =
+						playeritem_def.sound_place;
+			} else {
+				soundmaker->m_player_rightpunch_sound =
+						SimpleSoundSpec();
 			}
+
+			if (playeritem_def.node_placement_prediction == "" ||
+					nodedef_manager->get(map.getNode(nodepos)).rightclickable)
+				client->interact(3, pointed); // Report to server
 		}
-		tool_reload_ratio = MYMIN(tool_reload_ratio, 1.0);
-		camera.update(player, dtime, busytime, tool_reload_ratio,
-				client.getEnv());
-		camera.step(dtime);
+	}
+}
 
-		v3f player_position = player->getPosition();
-		v3f camera_position = camera.getPosition();
-		v3f camera_direction = camera.getDirection();
-		f32 camera_fov = camera.getFovMax();
-		v3s16 camera_offset = camera.getOffset();
 
-		bool camera_offset_changed = (camera_offset != old_camera_offset);
+void MinetestApp::handlePointingAtObject(InteractParams *interactArgs,
+		const PointedThing &pointed,
+		const ItemStack &playeritem,
+		const v3f &player_position,
+		bool show_debug)
+{
+	infotext = narrow_to_wide(interactArgs->selected_object->infoText());
 
-		if(!disable_camera_update){
-			client.getEnv().getClientMap().updateCamera(camera_position,
-				camera_direction, camera_fov, camera_offset);
-			if (camera_offset_changed){
-				client.updateCameraOffset(camera_offset);
-				client.getEnv().updateCameraOffset(camera_offset);
-				if (clouds)
-					clouds->updateCameraOffset(camera_offset);
-			}
-		}
+	if (infotext == L"" && show_debug) {
+		infotext = narrow_to_wide(interactArgs->selected_object->debugInfoText());
+	}
 
-		// Update sound listener
-		sound->updateListener(camera.getCameraNode()->getPosition()+intToFloat(camera_offset, BS),
-				v3f(0,0,0), // velocity
-				camera.getDirection(),
-				camera.getCameraNode()->getUpVector());
-		sound->setListenerGain(g_settings->getFloat("sound_volume"));
+	if (input->getLeftState()) {
+		bool do_punch = false;
+		bool do_punch_damage = false;
 
-		/*
-			Update sound maker
-		*/
-		{
-			soundmaker.step(dtime);
+		if (interactArgs->object_hit_delay_timer <= 0.0) {
+			do_punch = true;
+			do_punch_damage = true;
+			interactArgs->object_hit_delay_timer = object_hit_delay;
+		}
+
+		if (input->getLeftClicked())
+			do_punch = true;
 
-			ClientMap &map = client.getEnv().getClientMap();
-			MapNode n = map.getNodeNoEx(player->getStandingNodePos());
-			soundmaker.m_player_step_sound = nodedef->get(n).sound_footstep;
+		if (do_punch) {
+			infostream << "Left-clicked object" << std::endl;
+			interactArgs->left_punch = true;
 		}
 
-		/*
-			Calculate what block is the crosshair pointing to
-		*/
+		if (do_punch_damage) {
+			// Report direct punch
+			v3f objpos = interactArgs->selected_object->getPosition();
+			v3f dir = (objpos - player_position).normalize();
 
-		//u32 t1 = device->getTimer()->getRealTime();
+			bool disable_send = interactArgs->selected_object->directReportPunch(
+					dir, &playeritem, interactArgs->time_from_last_punch);
+			interactArgs->time_from_last_punch = 0;
 
-		f32 d = playeritem_def.range; // max. distance
-		f32 d_hand = itemdef->get("").range;
-		if(d < 0 && d_hand >= 0)
-			d = d_hand;
-		else if(d < 0)
-			d = 4.0;
-		core::line3d<f32> shootline(camera_position,
-				camera_position + camera_direction * BS * (d+1));
+			if (!disable_send)
+				client->interact(0, pointed);
+		}
+	} else if (input->getRightClicked()) {
+		infostream << "Right-clicked object" << std::endl;
+		client->interact(3, pointed);  // place
+	}
+}
 
 
-		// prevent player pointing anything in front-view
-		if (camera.getCameraMode() == CAMERA_MODE_THIRD_FRONT)
-			shootline = core::line3d<f32>(0,0,0,0,0,0);
+void MinetestApp::handleDigging(InteractParams *interactArgs,
+		const PointedThing &pointed, const v3s16 &nodepos,
+		const ToolCapabilities &playeritem_toolcap, f32 dtime)
+{
+	if (!interactArgs->digging) {
+		infostream << "Started digging" << std::endl;
+		client->interact(0, pointed);
+		interactArgs->digging = true;
+		interactArgs->ldown_for_dig = true;
+	}
 
-#ifdef HAVE_TOUCHSCREENGUI
-		if ((g_settings->getBool("touchtarget")) && (g_touchscreengui)) {
-			shootline = g_touchscreengui->getShootline();
-			shootline.start += intToFloat(camera_offset,BS);
-			shootline.end += intToFloat(camera_offset,BS);
-		}
-#endif
+	LocalPlayer *player = client->getEnv().getLocalPlayer();
+	ClientMap &map = client->getEnv().getClientMap();
+	MapNode n = client->getEnv().getClientMap().getNode(nodepos);
 
-		ClientActiveObject *selected_object = NULL;
-
-		PointedThing pointed = getPointedThing(
-				// input
-				&client, player_position, camera_direction,
-				camera_position, shootline, d,
-				playeritem_def.liquids_pointable, !ldown_for_dig,
-				camera_offset,
-				// output
-				hilightboxes,
-				selected_object);
-
-		if(pointed != pointed_old)
-		{
-			infostream<<"Pointing at "<<pointed.dump()<<std::endl;
-			if (g_settings->getBool("enable_node_highlighting")) {
-				if (pointed.type == POINTEDTHING_NODE) {
-					client.setHighlighted(pointed.node_undersurface, show_hud);
-				} else {
-					client.setHighlighted(pointed.node_undersurface, false);
-				}
-			}
-		}
+	// NOTE: Similar piece of code exists on the server side for
+	// cheat detection.
+	// Get digging parameters
+	DigParams params = getDigParams(nodedef_manager->get(n).groups,
+			&playeritem_toolcap);
 
-		/*
-			Stop digging when
-			- releasing left mouse button
-			- pointing away from node
-		*/
-		if(digging)
-		{
-			if(input->getLeftReleased())
-			{
-				infostream<<"Left button released"
-					<<" (stopped digging)"<<std::endl;
-				digging = false;
-			}
-			else if(pointed != pointed_old)
-			{
-				if (pointed.type == POINTEDTHING_NODE
-					&& pointed_old.type == POINTEDTHING_NODE
-					&& pointed.node_undersurface == pointed_old.node_undersurface)
-				{
-					// Still pointing to the same node,
-					// but a different face. Don't reset.
-				}
-				else
-				{
-					infostream<<"Pointing away from node"
-						<<" (stopped digging)"<<std::endl;
-					digging = false;
-				}
-			}
-			if(!digging)
-			{
-				client.interact(1, pointed_old);
-				client.setCrack(-1, v3s16(0,0,0));
-				dig_time = 0.0;
-			}
-		}
-		if(!digging && ldown_for_dig && !input->getLeftState())
-		{
-			ldown_for_dig = false;
-		}
+	// If can't dig, try hand
+	if (!params.diggable) {
+		const ItemDefinition &hand = itemdef_manager->get("");
+		const ToolCapabilities *tp = hand.tool_capabilities;
 
-		bool left_punch = false;
-		soundmaker.m_player_leftpunch_sound.name = "";
+		if (tp)
+			params = getDigParams(nodedef_manager->get(n).groups, tp);
+	}
 
-		if(input->getRightState())
-			repeat_rightclick_timer += dtime;
-		else
-			repeat_rightclick_timer = 0;
+	if (params.diggable == false) {
+		// I guess nobody will wait for this long
+		interactArgs->dig_time_complete = 10000000.0;
+	} else {
+		interactArgs->dig_time_complete = params.time;
 
-		if(playeritem_def.usable && input->getLeftState())
-		{
-			if(input->getLeftClicked())
-				client.interact(4, pointed);
+		if (g_settings->getBool("enable_particles")) {
+			const ContentFeatures &features =
+					client->getNodeDefManager()->get(n);
+			addPunchingParticles(gamedef, smgr, player,
+					client->getEnv(), nodepos, features.tiles);
 		}
-		else if(pointed.type == POINTEDTHING_NODE)
-		{
-			v3s16 nodepos = pointed.node_undersurface;
-			v3s16 neighbourpos = pointed.node_abovesurface;
-
-			/*
-				Check information text of node
-			*/
-
-			ClientMap &map = client.getEnv().getClientMap();
-			NodeMetadata *meta = map.getNodeMetadata(nodepos);
-			if(meta){
-				infotext = narrow_to_wide(meta->getString("infotext"));
-			} else {
-				MapNode n = map.getNode(nodepos);
-				if(nodedef->get(n).tiledef[0].name == "unknown_node.png"){
-					infotext = L"Unknown node: ";
-					infotext += narrow_to_wide(nodedef->get(n).name);
-				}
-			}
+	}
 
-			/*
-				Handle digging
-			*/
-
-			if(nodig_delay_timer <= 0.0 && input->getLeftState()
-					&& client.checkPrivilege("interact"))
-			{
-				if(!digging)
-				{
-					infostream<<"Started digging"<<std::endl;
-					client.interact(0, pointed);
-					digging = true;
-					ldown_for_dig = true;
-				}
-				MapNode n = client.getEnv().getClientMap().getNode(nodepos);
-
-				// NOTE: Similar piece of code exists on the server side for
-				// cheat detection.
-				// Get digging parameters
-				DigParams params = getDigParams(nodedef->get(n).groups,
-						&playeritem_toolcap);
-				// If can't dig, try hand
-				if(!params.diggable){
-					const ItemDefinition &hand = itemdef->get("");
-					const ToolCapabilities *tp = hand.tool_capabilities;
-					if(tp)
-						params = getDigParams(nodedef->get(n).groups, tp);
-				}
+	if (interactArgs->dig_time_complete >= 0.001) {
+		interactArgs->dig_index = (float)crack_animation_length
+				* interactArgs->dig_time
+				/ interactArgs->dig_time_complete;
+	} else {
+		// This is for torches
+		interactArgs->dig_index = crack_animation_length;
+	}
 
-				float dig_time_complete = 0.0;
+	SimpleSoundSpec sound_dig = nodedef_manager->get(n).sound_dig;
 
-				if(params.diggable == false)
-				{
-					// I guess nobody will wait for this long
-					dig_time_complete = 10000000.0;
-				}
-				else
-				{
-					dig_time_complete = params.time;
-					if (g_settings->getBool("enable_particles"))
-					{
-						const ContentFeatures &features =
-							client.getNodeDefManager()->get(n);
-						addPunchingParticles
-							(gamedef, smgr, player, client.getEnv(),
-							 nodepos, features.tiles);
-					}
-				}
+	if (sound_dig.exists() && params.diggable) {
+		if (sound_dig.name == "__group") {
+			if (params.main_group != "") {
+				soundmaker->m_player_leftpunch_sound.gain = 0.5;
+				soundmaker->m_player_leftpunch_sound.name =
+						std::string("default_dig_") +
+						params.main_group;
+			}
+		} else {
+			soundmaker->m_player_leftpunch_sound = sound_dig;
+		}
+	}
 
-				if(dig_time_complete >= 0.001)
-				{
-					dig_index = (u16)((float)crack_animation_length
-							* dig_time/dig_time_complete);
-				}
-				// This is for torches
-				else
-				{
-					dig_index = crack_animation_length;
-				}
+	// Don't show cracks if not diggable
+	if (interactArgs->dig_time_complete >= 100000.0) {
+	} else if (interactArgs->dig_index < crack_animation_length) {
+		//TimeTaker timer("client.setTempMod");
+		//infostream<<"dig_index="<<dig_index<<std::endl;
+		client->setCrack(interactArgs->dig_index, nodepos);
+	} else {
+		infostream << "Digging completed" << std::endl;
+		client->interact(2, pointed);
+		client->setCrack(-1, v3s16(0, 0, 0));
+		MapNode wasnode = map.getNode(nodepos);
+		client->removeNode(nodepos);
 
-				SimpleSoundSpec sound_dig = nodedef->get(n).sound_dig;
-				if(sound_dig.exists() && params.diggable){
-					if(sound_dig.name == "__group"){
-						if(params.main_group != ""){
-							soundmaker.m_player_leftpunch_sound.gain = 0.5;
-							soundmaker.m_player_leftpunch_sound.name =
-									std::string("default_dig_") +
-											params.main_group;
-						}
-					} else{
-						soundmaker.m_player_leftpunch_sound = sound_dig;
-					}
-				}
+		if (g_settings->getBool("enable_particles")) {
+			const ContentFeatures &features =
+				client->getNodeDefManager()->get(wasnode);
+			addDiggingParticles
+			(gamedef, smgr, player, client->getEnv(),
+			 nodepos, features.tiles);
+		}
 
-				// Don't show cracks if not diggable
-				if(dig_time_complete >= 100000.0)
-				{
-				}
-				else if(dig_index < crack_animation_length)
-				{
-					//TimeTaker timer("client.setTempMod");
-					//infostream<<"dig_index="<<dig_index<<std::endl;
-					client.setCrack(dig_index, nodepos);
-				}
-				else
-				{
-					infostream<<"Digging completed"<<std::endl;
-					client.interact(2, pointed);
-					client.setCrack(-1, v3s16(0,0,0));
-					MapNode wasnode = map.getNode(nodepos);
-					client.removeNode(nodepos);
-
-					if (g_settings->getBool("enable_particles"))
-					{
-						const ContentFeatures &features =
-							client.getNodeDefManager()->get(wasnode);
-						addDiggingParticles
-							(gamedef, smgr, player, client.getEnv(),
-							 nodepos, features.tiles);
-					}
+		interactArgs->dig_time = 0;
+		interactArgs->digging = false;
 
-					dig_time = 0;
-					digging = false;
-
-					nodig_delay_timer = dig_time_complete
-							/ (float)crack_animation_length;
-
-					// We don't want a corresponding delay to
-					// very time consuming nodes
-					if(nodig_delay_timer > 0.3)
-						nodig_delay_timer = 0.3;
-					// We want a slight delay to very little
-					// time consuming nodes
-					float mindelay = 0.15;
-					if(nodig_delay_timer < mindelay)
-						nodig_delay_timer = mindelay;
-
-					// Send event to trigger sound
-					MtEvent *e = new NodeDugEvent(nodepos, wasnode);
-					gamedef->event()->put(e);
-				}
+		interactArgs->nodig_delay_timer =
+				interactArgs->dig_time_complete / (float)crack_animation_length;
 
-				if(dig_time_complete < 100000.0)
-					dig_time += dtime;
-				else {
-					dig_time = 0;
-					client.setCrack(-1, nodepos);
-				}
+		// We don't want a corresponding delay to
+		// very time consuming nodes
+		if (interactArgs->nodig_delay_timer > 0.3)
+			interactArgs->nodig_delay_timer = 0.3;
 
-				camera.setDigging(0);  // left click animation
-			}
+		// We want a slight delay to very little
+		// time consuming nodes
+		const float mindelay = 0.15;
 
-			if((input->getRightClicked() ||
-					repeat_rightclick_timer >=
-						g_settings->getFloat("repeat_rightclick_time")) &&
-					client.checkPrivilege("interact"))
-			{
-				repeat_rightclick_timer = 0;
-				infostream<<"Ground right-clicked"<<std::endl;
+		if (interactArgs->nodig_delay_timer < mindelay)
+			interactArgs->nodig_delay_timer = mindelay;
 
-				if(meta && meta->getString("formspec") != "" && !random_input
-						&& !input->isKeyDown(getKeySetting("keymap_sneak")))
-				{
-					infostream<<"Launching custom inventory view"<<std::endl;
+		// Send event to trigger sound
+		MtEvent *e = new NodeDugEvent(nodepos, wasnode);
+		gamedef->event()->put(e);
+	}
 
-					InventoryLocation inventoryloc;
-					inventoryloc.setNodeMeta(nodepos);
+	if (interactArgs->dig_time_complete < 100000.0) {
+		interactArgs->dig_time += dtime;
+	} else {
+		interactArgs->dig_time = 0;
+		client->setCrack(-1, nodepos);
+	}
 
-					NodeMetadataFormSource* fs_src = new NodeMetadataFormSource(
-							&client.getEnv().getClientMap(), nodepos);
-					TextDest* txt_dst = new TextDestNodeMetadata(nodepos, &client);
+	camera->setDigging(0);  // left click animation
+}
 
-					create_formspec_menu(&current_formspec, &client, gamedef,
-							tsrc, device, fs_src, txt_dst, &client);
 
-					current_formspec->setFormSpec(meta->getString("formspec"), inventoryloc);
-				}
-				// Otherwise report right click to server
-				else
-				{
-					camera.setDigging(1);  // right click animation (always shown for feedback)
-
-					// If the wielded item has node placement prediction,
-					// make that happen
-					bool placed = nodePlacementPrediction(client,
-						playeritem_def,
-						nodepos, neighbourpos);
-
-					if(placed) {
-						// Report to server
-						client.interact(3, pointed);
-						// Read the sound
-						soundmaker.m_player_rightpunch_sound =
-							playeritem_def.sound_place;
-					} else {
-						soundmaker.m_player_rightpunch_sound =
-							SimpleSoundSpec();
-					}
+void MinetestApp::updateFrame(std::vector<aabb3f> &highlight_boxes,
+		ProfilerGraph *graph, RunStats *stats, InteractParams *interactArgs,
+		f32 dtime, const VolatileRunFlags &flags, const CameraOrientation &cam)
+{
+	LocalPlayer *player = client->getEnv().getLocalPlayer();
 
-					if (playeritem_def.node_placement_prediction == "" ||
-						nodedef->get(map.getNode(nodepos)).rightclickable)
-						client.interact(3, pointed); // Report to server
-				}
-			}
-		}
-		else if(pointed.type == POINTEDTHING_OBJECT)
-		{
-			infotext = narrow_to_wide(selected_object->infoText());
+	/*
+		Fog range
+	*/
 
-			if(infotext == L"" && show_debug){
-				infotext = narrow_to_wide(selected_object->debugInfoText());
-			}
+	if (draw_control->range_all) {
+		interactArgs->fog_range = 100000 * BS;
+	} else {
+		interactArgs->fog_range = draw_control->wanted_range * BS
+				+ 0.0 * MAP_BLOCKSIZE * BS;
+		interactArgs->fog_range = MYMIN(
+				interactArgs->fog_range,
+				(draw_control->farthest_drawn + 20) * BS);
+		interactArgs->fog_range *= 0.9;
+	}
 
-			//if(input->getLeftClicked())
-			if(input->getLeftState())
-			{
-				bool do_punch = false;
-				bool do_punch_damage = false;
-				if(object_hit_delay_timer <= 0.0){
-					do_punch = true;
-					do_punch_damage = true;
-					object_hit_delay_timer = object_hit_delay;
-				}
-				if(input->getLeftClicked()){
-					do_punch = true;
-				}
-				if(do_punch){
-					infostream<<"Left-clicked object"<<std::endl;
-					left_punch = true;
-				}
-				if(do_punch_damage){
-					// Report direct punch
-					v3f objpos = selected_object->getPosition();
-					v3f dir = (objpos - player_position).normalize();
-
-					bool disable_send = selected_object->directReportPunch(
-							dir, &playeritem, time_from_last_punch);
-					time_from_last_punch = 0;
-					if(!disable_send)
-						client.interact(0, pointed);
-				}
-			}
-			else if(input->getRightClicked())
-			{
-				infostream<<"Right-clicked object"<<std::endl;
-				client.interact(3, pointed);  // place
-			}
-		}
-		else if(input->getLeftState())
-		{
-			// When button is held down in air, show continuous animation
-			left_punch = true;
-		}
+	/*
+		Calculate general brightness
+	*/
+	u32 daynight_ratio = client->getEnv().getDayNightRatio();
+	float time_brightness = decode_light_f((float)daynight_ratio / 1000.0);
+	float direct_brightness = 0;
+	bool sunlight_seen = false;
+
+	if (g_settings->getBool("free_move")) {
+		direct_brightness = time_brightness;
+		sunlight_seen = true;
+	} else {
+		ScopeProfiler sp(g_profiler, "Detecting background light", SPT_AVG);
+		float old_brightness = sky->getBrightness();
+		direct_brightness = client->getEnv().getClientMap()
+				.getBackgroundBrightness(MYMIN(interactArgs->fog_range * 1.2, 60 * BS),
+					daynight_ratio, (int)(old_brightness * 255.5), &sunlight_seen)
+				    / 255.0;
+	}
 
-		pointed_old = pointed;
+	float time_of_day = 0;
+	float time_of_day_smooth = 0;
 
-		if(left_punch || input->getLeftClicked())
-		{
-			camera.setDigging(0); // left click animation
-		}
+	time_of_day = client->getEnv().getTimeOfDayF();
 
-		input->resetLeftClicked();
-		input->resetRightClicked();
+	const float maxsm = 0.05;
 
-		input->resetLeftReleased();
-		input->resetRightReleased();
+	if (fabs(time_of_day - time_of_day_smooth) > maxsm &&
+			fabs(time_of_day - time_of_day_smooth + 1.0) > maxsm &&
+			fabs(time_of_day - time_of_day_smooth - 1.0) > maxsm)
+		time_of_day_smooth = time_of_day;
 
-		/*
-			Calculate stuff for drawing
-		*/
+	const float todsm = 0.05;
 
-		/*
-			Fog range
-		*/
+	if (time_of_day_smooth > 0.8 && time_of_day < 0.2)
+		time_of_day_smooth = time_of_day_smooth * (1.0 - todsm)
+				+ (time_of_day + 1.0) * todsm;
+	else
+		time_of_day_smooth = time_of_day_smooth * (1.0 - todsm)
+				+ time_of_day * todsm;
 
-		if(draw_control.range_all)
-			fog_range = 100000*BS;
-		else {
-			fog_range = draw_control.wanted_range*BS + 0.0*MAP_BLOCKSIZE*BS;
-			fog_range = MYMIN(fog_range, (draw_control.farthest_drawn+20)*BS);
-			fog_range *= 0.9;
-		}
+	sky->update(time_of_day_smooth, time_brightness, direct_brightness,
+			sunlight_seen, camera->getCameraMode(), player->getYaw(),
+			player->getPitch());
 
-		/*
-			Calculate general brightness
-		*/
-		u32 daynight_ratio = client.getEnv().getDayNightRatio();
-		float time_brightness = decode_light_f((float)daynight_ratio/1000.0);
-		float direct_brightness = 0;
-		bool sunlight_seen = false;
-		if(g_settings->getBool("free_move")){
-			direct_brightness = time_brightness;
-			sunlight_seen = true;
+	/*
+		Update clouds
+	*/
+	if (clouds) {
+		v3f player_position = player->getPosition();
+		if (sky->getCloudsVisible()) {
+			clouds->setVisible(true);
+			clouds->step(dtime);
+			clouds->update(v2f(player_position.X, player_position.Z),
+				       sky->getCloudColor());
 		} else {
-			ScopeProfiler sp(g_profiler, "Detecting background light", SPT_AVG);
-			float old_brightness = sky->getBrightness();
-			direct_brightness = (float)client.getEnv().getClientMap()
-					.getBackgroundBrightness(MYMIN(fog_range*1.2, 60*BS),
-					daynight_ratio, (int)(old_brightness*255.5), &sunlight_seen)
-					/ 255.0;
-		}
-
-		time_of_day = client.getEnv().getTimeOfDayF();
-		float maxsm = 0.05;
-		if(fabs(time_of_day - time_of_day_smooth) > maxsm &&
-				fabs(time_of_day - time_of_day_smooth + 1.0) > maxsm &&
-				fabs(time_of_day - time_of_day_smooth - 1.0) > maxsm)
-			time_of_day_smooth = time_of_day;
-		float todsm = 0.05;
-		if(time_of_day_smooth > 0.8 && time_of_day < 0.2)
-			time_of_day_smooth = time_of_day_smooth * (1.0-todsm)
-					+ (time_of_day+1.0) * todsm;
-		else
-			time_of_day_smooth = time_of_day_smooth * (1.0-todsm)
-					+ time_of_day * todsm;
-
-		sky->update(time_of_day_smooth, time_brightness, direct_brightness,
-				sunlight_seen,camera.getCameraMode(), player->getYaw(),
-				player->getPitch());
-
-		video::SColor bgcolor = sky->getBgColor();
-		video::SColor skycolor = sky->getSkyColor();
-
-		/*
-			Update clouds
-		*/
-		if(clouds){
-			if(sky->getCloudsVisible()){
-				clouds->setVisible(true);
-				clouds->step(dtime);
-				clouds->update(v2f(player_position.X, player_position.Z),
-						sky->getCloudColor());
-			} else{
-				clouds->setVisible(false);
-			}
+			clouds->setVisible(false);
 		}
+	}
 
-		/*
-			Update particles
-		*/
+	/*
+		Update particles
+	*/
 
-		allparticles_step(dtime);
-		allparticlespawners_step(dtime, client.getEnv());
+	allparticles_step(dtime);
+	allparticlespawners_step(dtime, client->getEnv());
 
-		/*
-			Fog
-		*/
+	/*
+		Fog
+	*/
 
-		if(g_settings->getBool("enable_fog") && !force_fog_off)
-		{
-			driver->setFog(
-				bgcolor,
+	if (g_settings->getBool("enable_fog") && !flags.force_fog_off) {
+		driver->setFog(
+				sky->getBgColor(),
 				video::EFT_FOG_LINEAR,
-				fog_range*0.4,
-				fog_range*1.0,
+				interactArgs->fog_range * 0.4,
+				interactArgs->fog_range * 1.0,
 				0.01,
 				false, // pixel fog
 				false // range fog
-			);
-		}
-		else
-		{
-			driver->setFog(
-				bgcolor,
+		);
+	} else {
+		driver->setFog(
+				sky->getBgColor(),
 				video::EFT_FOG_LINEAR,
-				100000*BS,
-				110000*BS,
+				100000 * BS,
+				110000 * BS,
 				0.01,
 				false, // pixel fog
 				false // range fog
-			);
-		}
-
-		/*
-			Update gui stuff (0ms)
-		*/
-
-		//TimeTaker guiupdatetimer("Gui updating");
-
-		if(show_debug)
-		{
-			static float drawtime_avg = 0;
-			drawtime_avg = drawtime_avg * 0.95 + (float)drawtime*0.05;
-			/*static float beginscenetime_avg = 0;
-			beginscenetime_avg = beginscenetime_avg * 0.95 + (float)beginscenetime*0.05;
-			static float scenetime_avg = 0;
-			scenetime_avg = scenetime_avg * 0.95 + (float)scenetime*0.05;
-			static float endscenetime_avg = 0;
-			endscenetime_avg = endscenetime_avg * 0.95 + (float)endscenetime*0.05;*/
-
-			u16 fps = (1.0/dtime_avg1);
-
-			std::ostringstream os(std::ios_base::binary);
-			os<<std::fixed
-				<<"Minetest "<<minetest_version_hash
-				<<" FPS = "<<fps
-				<<" (R: range_all="<<draw_control.range_all<<")"
-				<<std::setprecision(0)
-				<<" drawtime = "<<drawtime_avg
-				<<std::setprecision(1)
-				<<", dtime_jitter = "
-				<<(dtime_jitter1_max_fraction * 100.0)<<" %"
-				<<std::setprecision(1)
-				<<", v_range = "<<draw_control.wanted_range
-				<<std::setprecision(3)
-				<<", RTT = "<<client.getRTT();
-			guitext->setText(narrow_to_wide(os.str()).c_str());
-			guitext->setVisible(true);
-		}
-		else if(show_hud || show_chat)
-		{
-			std::ostringstream os(std::ios_base::binary);
-			os<<"Minetest "<<minetest_version_hash;
-			guitext->setText(narrow_to_wide(os.str()).c_str());
-			guitext->setVisible(true);
-		}
-		else
-		{
-			guitext->setVisible(false);
-		}
+		);
+	}
 
-		if (guitext->isVisible())
-		{
-			core::rect<s32> rect(
-				5,
-				5,
-				screensize.X,
-				5 + text_height
-			);
-			guitext->setRelativePosition(rect);
-		}
+	/*
+		Get chat messages from client
+	*/
 
-		if(show_debug)
-		{
-			std::ostringstream os(std::ios_base::binary);
-			os<<std::setprecision(1)<<std::fixed
-				<<"(" <<(player_position.X/BS)
-				<<", "<<(player_position.Y/BS)
-				<<", "<<(player_position.Z/BS)
-				<<") (yaw="<<(wrapDegrees_0_360(camera_yaw))
-				<<") (seed = "<<((u64)client.getMapSeed())
-				<<")";
-			guitext2->setText(narrow_to_wide(os.str()).c_str());
-			guitext2->setVisible(true);
-
-			core::rect<s32> rect(
-				5,
-				5 + text_height,
-				screensize.X,
-				5 + (text_height * 2)
-			);
-			guitext2->setRelativePosition(rect);
-		}
-		else
-		{
-			guitext2->setVisible(false);
-		}
+	v2u32 screensize = driver->getScreenSize();
 
-		{
-			guitext_info->setText(infotext.c_str());
-			guitext_info->setVisible(show_hud && g_menumgr.menuCount() == 0);
-		}
+	updateChat(*client, dtime, flags.show_debug, screensize,
+			flags.show_chat, interactArgs->profiler_current_page,
+			*chat_backend, guitext_chat, font);
 
-		{
-			float statustext_time_max = 1.5;
-			if(!statustext.empty())
-			{
-				statustext_time += dtime;
-				if(statustext_time >= statustext_time_max)
-				{
-					statustext = L"";
-					statustext_time = 0;
-				}
-			}
-			guitext_status->setText(statustext.c_str());
-			guitext_status->setVisible(!statustext.empty());
-
-			if(!statustext.empty())
-			{
-				s32 status_y = screensize.Y - 130;
-				core::rect<s32> rect(
-						10,
-						status_y - guitext_status->getTextHeight(),
-						10 + guitext_status->getTextWidth(),
-						status_y
-				);
-				guitext_status->setRelativePosition(rect);
-
-				// Fade out
-				video::SColor initial_color(255,0,0,0);
-				if(guienv->getSkin())
-					initial_color = guienv->getSkin()->getColor(gui::EGDC_BUTTON_TEXT);
-				video::SColor final_color = initial_color;
-				final_color.setAlpha(0);
-				video::SColor fade_color =
-					initial_color.getInterpolated_quadratic(
-						initial_color,
-						final_color,
-						pow(statustext_time / (float)statustext_time_max, 2.0f));
-				guitext_status->setOverrideColor(fade_color);
-				guitext_status->enableOverrideColor(true);
-			}
-		}
+	/*
+		Inventory
+	*/
 
-		/*
-			Get chat messages from client
-		*/
-		updateChat(client, dtime, show_debug, screensize, show_chat,
-				show_profiler, chat_backend, guitext_chat, font);
+	bool update_wielded_item_trigger = true;
 
-		/*
-			Inventory
-		*/
+	if (client->getPlayerItem() != interactArgs->new_playeritem) {
+		client->selectPlayerItem(interactArgs->new_playeritem);
+	}
 
-		if(client.getPlayerItem() != new_playeritem)
-		{
-			client.selectPlayerItem(new_playeritem);
-		}
-		if(client.getLocalInventoryUpdated())
-		{
-			//infostream<<"Updating local inventory"<<std::endl;
-			client.getLocalInventory(local_inventory);
+	if (client->getLocalInventoryUpdated()) {
+		//infostream<<"Updating local inventory"<<std::endl;
+		client->getLocalInventory(*local_inventory);
 
-			update_wielded_item_trigger = true;
-		}
-		if(update_wielded_item_trigger)
-		{
-			update_wielded_item_trigger = false;
-			// Update wielded tool
-			InventoryList *mlist = local_inventory.getList("main");
-			ItemStack item;
-			if((mlist != NULL) && (client.getPlayerItem() < mlist->getSize()))
-				item = mlist->getItem(client.getPlayerItem());
-			camera.wield(item, client.getPlayerItem());
-		}
+		update_wielded_item_trigger = true;
+	}
 
-		/*
-			Update block draw list every 200ms or when camera direction has
-			changed much
-		*/
-		update_draw_list_timer += dtime;
-		if(update_draw_list_timer >= 0.2 ||
-				update_draw_list_last_cam_dir.getDistanceFrom(camera_direction) > 0.2 ||
-				camera_offset_changed){
-			update_draw_list_timer = 0;
-			client.getEnv().getClientMap().updateDrawList(driver);
-			update_draw_list_last_cam_dir = camera_direction;
-		}
+	if (update_wielded_item_trigger) {
+		update_wielded_item_trigger = false;
+		// Update wielded tool
+		InventoryList *mlist = local_inventory->getList("main");
+		ItemStack item;
 
-		/*
-			1. Delete formspec menu reference if menu was removed
-			2. Else, make sure formspec menu is on top
-		*/
-		if (current_formspec) {
-			if (current_formspec->getReferenceCount() == 1) {
-				current_formspec->drop();
-				current_formspec = NULL;
-			} else if (!noMenuActive()) {
-				guiroot->bringToFront(current_formspec);
-			}
-		}
+		if (mlist  && (client->getPlayerItem() < mlist->getSize()))
+			item = mlist->getItem(client->getPlayerItem());
 
-		/*
-			Drawing begins
-		*/
-		TimeTaker tt_draw("mainloop: draw");
-		{
-			TimeTaker timer("beginScene");
-			driver->beginScene(true, true, skycolor);
-			beginscenetime = timer.stop(true);
-		}
+		camera->wield(item, client->getPlayerItem());
+	}
 
+	/*
+		Update block draw list every 200ms or when camera direction has
+		changed much
+	*/
+	interactArgs->update_draw_list_timer += dtime;
+
+	v3f camera_direction = camera->getDirection();
+	if (interactArgs->update_draw_list_timer >= 0.2
+			|| interactArgs->update_draw_list_last_cam_dir.getDistanceFrom(camera_direction) > 0.2
+			|| flags.camera_offset_changed) {
+		interactArgs->update_draw_list_timer = 0;
+		client->getEnv().getClientMap().updateDrawList(driver);
+		interactArgs->update_draw_list_last_cam_dir = camera_direction;
+	}
 
-		draw_scene(driver, smgr, camera, client, player, hud, guienv,
-				hilightboxes, screensize, skycolor, show_hud);
+	updateGui(&interactArgs->statustext_time, *stats, dtime, flags, cam);
 
-		/*
-			Profiler graph
-		*/
-		if(show_profiler_graph)
-		{
-			graph.draw(10, screensize.Y - 10, driver, font);
+	/*
+	   make sure menu is on top
+	   1. Delete formspec menu reference if menu was removed
+	   2. Else, make sure formspec menu is on top
+	*/
+	if (current_formspec) {
+		if (current_formspec->getReferenceCount() == 1) {
+			current_formspec->drop();
+			current_formspec = NULL;
+		} else if (!noMenuActive()) {
+			guiroot->bringToFront(current_formspec);
 		}
+	}
 
-		/*
-			Damage flash
-		*/
-		if(damage_flash > 0.0)
-		{
-			video::SColor color(std::min(damage_flash, 180.0f),180,0,0);
-			driver->draw2DRectangle(color,
-					core::rect<s32>(0,0,screensize.X,screensize.Y),
-					NULL);
+	/*
+		Drawing begins
+	*/
 
-			damage_flash -= 100.0*dtime;
-		}
+	video::SColor skycolor = sky->getSkyColor();
 
-		/*
-			Damage camera tilt
-		*/
-		if(player->hurt_tilt_timer > 0.0)
-		{
-			player->hurt_tilt_timer -= dtime*5;
-			if(player->hurt_tilt_timer < 0)
-				player->hurt_tilt_strength = 0;
-		}
+	TimeTaker tt_draw("mainloop: draw");
+	{
+		TimeTaker timer("beginScene");
+		driver->beginScene(true, true, skycolor);
+		stats->beginscenetime = timer.stop(true);
+	}
 
-		/*
-			End scene
-		*/
-		{
-			TimeTaker timer("endScene");
-			driver->endScene();
-			endscenetime = timer.stop(true);
-		}
+	draw_scene(driver, smgr, *camera, *client, player, *hud, guienv,
+			highlight_boxes, screensize, skycolor, flags.show_hud);
 
-		drawtime = tt_draw.stop(true);
-		g_profiler->graphAdd("mainloop_draw", (float)drawtime/1000.0f);
+	/*
+		Profiler graph
+	*/
+	if (flags.show_profiler_graph)
+		graph->draw(10, screensize.Y - 10, driver, font);
 
-		/*
-			End of drawing
-		*/
+	/*
+		Damage flash
+	*/
+	if (interactArgs->damage_flash > 0.0) {
+		video::SColor color(std::min(interactArgs->damage_flash, 180.0f),
+				180,
+				0,
+				0);
+		driver->draw2DRectangle(color,
+					core::rect<s32>(0, 0, screensize.X, screensize.Y),
+					NULL);
 
-		/*
-			Log times and stuff for visualization
-		*/
-		Profiler::GraphValues values;
-		g_profiler->graphGet(values);
-		graph.put(values);
+		interactArgs->damage_flash -= 100.0 * dtime;
 	}
 
 	/*
-		Drop stuff
+		Damage camera tilt
 	*/
-	if (clouds)
-		clouds->drop();
-	if (gui_chat_console)
-		gui_chat_console->drop();
-	if (sky)
-		sky->drop();
-	clear_particles();
+	if (player->hurt_tilt_timer > 0.0) {
+		player->hurt_tilt_timer -= dtime * 5;
 
-	/* cleanup menus */
-	while (g_menumgr.menuCount() > 0)
-	{
-		g_menumgr.m_stack.front()->setVisible(false);
-		g_menumgr.deletingMenu(g_menumgr.m_stack.front());
-	}
-	if (current_formspec) {
-		current_formspec->drop();
-		current_formspec = NULL;
+		if (player->hurt_tilt_timer < 0)
+			player->hurt_tilt_strength = 0;
 	}
 
 	/*
-		Draw a "shutting down" screen, which will be shown while the map
-		generator and other stuff quits
+		End scene
 	*/
 	{
-		wchar_t* text = wgettext("Shutting down stuff...");
-		draw_load_screen(text, device, guienv, font, 0, -1, false);
-		delete[] text;
+		TimeTaker timer("endScene");
+		driver->endScene();
+		stats->endscenetime = timer.stop(true);
 	}
 
-	chat_backend.addMessage(L"", L"# Disconnected.");
-	chat_backend.addMessage(L"", L"");
+	stats->drawtime = tt_draw.stop(true);
+	g_profiler->graphAdd("mainloop_draw", stats->drawtime / 1000.0f);
+}
+
+
+void MinetestApp::updateGui(float *statustext_time, const RunStats& stats,
+		f32 dtime, const VolatileRunFlags &flags, const CameraOrientation &cam)
+{
+	v2u32 screensize = driver->getScreenSize();
+	LocalPlayer *player = client->getEnv().getLocalPlayer();
+	v3f player_position = player->getPosition();
 
-	client.Stop();
+	if (flags.show_debug) {
+		static float drawtime_avg = 0;
+		drawtime_avg = drawtime_avg * 0.95 + stats.drawtime * 0.05;
 
-	//force answer all texture and shader jobs (TODO return empty values)
+		u16 fps = 1.0 / stats.dtime_jitter.avg;
 
-	while(!client.isShutdown()) {
-		tsrc->processQueue();
-		shsrc->processQueue();
-		sleep_ms(100);
+		std::ostringstream os(std::ios_base::binary);
+		os << std::fixed
+		   << "Minetest " << minetest_version_hash
+		   << " FPS = " << fps
+		   << " (R: range_all=" << draw_control->range_all << ")"
+		   << std::setprecision(0)
+		   << " drawtime = " << drawtime_avg
+		   << std::setprecision(1)
+		   << ", dtime_jitter = "
+		   << (stats.dtime_jitter.max_fraction * 100.0) << " %"
+		   << std::setprecision(1)
+		   << ", v_range = " << draw_control->wanted_range
+		   << std::setprecision(3)
+		   << ", RTT = " << client->getRTT();
+		guitext->setText(narrow_to_wide(os.str()).c_str());
+		guitext->setVisible(true);
+	} else if (flags.show_hud || flags.show_chat) {
+		std::ostringstream os(std::ios_base::binary);
+		os << "Minetest " << minetest_version_hash;
+		guitext->setText(narrow_to_wide(os.str()).c_str());
+		guitext->setVisible(true);
+	} else {
+		guitext->setVisible(false);
 	}
 
-	// Client scope (client is destructed before destructing *def and tsrc)
-	}while(0);
-	} // try-catch
-	catch(SerializationError &e)
-	{
-		error_message = L"A serialization error occurred:\n"
-				+ narrow_to_wide(e.what()) + L"\n\nThe server is probably "
-				L" running a different version of Minetest.";
-		errorstream<<wide_to_narrow(error_message)<<std::endl;
+	if (guitext->isVisible()) {
+		core::rect<s32> rect(
+				5,              5,
+				screensize.X,   5 + text_height
+		);
+		guitext->setRelativePosition(rect);
 	}
-	catch(ServerError &e) {
-		error_message = narrow_to_wide(e.what());
-		errorstream << "ServerError: " << e.what() << std::endl;
+
+	if (flags.show_debug) {
+		std::ostringstream os(std::ios_base::binary);
+		os << std::setprecision(1) << std::fixed
+		   << "(" << (player_position.X / BS)
+		   << ", " << (player_position.Y / BS)
+		   << ", " << (player_position.Z / BS)
+		   << ") (yaw=" << (wrapDegrees_0_360(cam.camera_yaw))
+		   << ") (seed = " << ((u64)client->getMapSeed())
+		   << ")";
+		guitext2->setText(narrow_to_wide(os.str()).c_str());
+		guitext2->setVisible(true);
+
+		core::rect<s32> rect(
+				5,             5 + text_height,
+				screensize.X,  5 + text_height * 2
+		);
+		guitext2->setRelativePosition(rect);
+	} else {
+		guitext2->setVisible(false);
 	}
-	catch(ModError &e) {
-		errorstream << "ModError: " << e.what() << std::endl;
-		error_message = narrow_to_wide(e.what()) + wgettext("\nCheck debug.txt for details.");
+
+	guitext_info->setText(infotext.c_str());
+	guitext_info->setVisible(flags.show_hud && g_menumgr.menuCount() == 0);
+
+	float statustext_time_max = 1.5;
+
+	if (!statustext.empty()) {
+		*statustext_time += dtime;
+
+		if (*statustext_time >= statustext_time_max) {
+			statustext = L"";
+			*statustext_time = 0;
+		}
+	}
+
+	guitext_status->setText(statustext.c_str());
+	guitext_status->setVisible(!statustext.empty());
+
+	if (!statustext.empty()) {
+		s32 status_y = screensize.Y - 130;
+		core::rect<s32> rect(
+				10, status_y - guitext_status->getTextHeight(),
+				10 + guitext_status->getTextWidth(), status_y
+		);
+		guitext_status->setRelativePosition(rect);
+
+		// Fade out
+		video::SColor initial_color(255, 0, 0, 0);
+
+		if (guienv->getSkin())
+			initial_color = guienv->getSkin()->getColor(gui::EGDC_BUTTON_TEXT);
+
+		video::SColor final_color = initial_color;
+		final_color.setAlpha(0);
+		video::SColor fade_color = initial_color.getInterpolated_quadratic(
+				initial_color, final_color,
+				pow(*statustext_time / statustext_time_max, 2.0f));
+		guitext_status->setOverrideColor(fade_color);
+		guitext_status->enableOverrideColor(true);
 	}
+}
+
+
+/* Log times and stuff for visualization */
+inline void MinetestApp::updateProfilerGraphs(ProfilerGraph *graph)
+{
+	Profiler::GraphValues values;
+	g_profiler->graphGet(values);
+	graph->put(values);
+}
 
 
 
-	if(!sound_is_dummy)
-		delete sound;
+/****************************************************************************
+ Misc
+ ****************************************************************************/
+
+/* On some computers framerate doesn't seem to be automatically limited
+ *
+ * *Must* be called after device->run() so that device->getTimer()->getTime();
+ * is correct
+ */
+inline void MinetestApp::limitFps(FpsControl *params, f32 *dtime)
+{
+	// not using getRealTime is necessary for wine
+	u32 time = device->getTimer()->getTime();
+
+	u32 last_time = params->last_time;
+
+	if (time > last_time)	// Make sure last_time hasn't overflowed
+		params->busy_time = time - last_time;
+	else
+		params->busy_time = 0;
+
+	u32 frametime_min = 1000 / (g_menumgr.pausesGame()
+			? g_settings->getFloat("pause_fps_max")
+			: g_settings->getFloat("fps_max"));
+
+	if (params->busy_time < frametime_min) {
+		params->sleep_time = frametime_min - params->busy_time;
+		device->sleep(params->sleep_time);
+	} else {
+		params->sleep_time = 0;
+	}
+
+	// Necessary for device->getTimer()->getTime()
+	device->run();
+	time = device->getTimer()->getTime();
+
+	if (time > last_time)	// Make sure last_time hasn't overflowed
+		*dtime = (time - last_time) / 1000.0;
+	else
+		*dtime = 0;
+
+	params->last_time = time;
+}
+
+
+void MinetestApp::showOverlayMessage(const char *msg, float dtime,
+		int percent, bool draw_clouds)
+{
+	wchar_t *text = wgettext(msg);
+	draw_load_screen(text, device, guienv, font, dtime, percent, draw_clouds);
+	delete[] text;
+}
+
+
+inline const char *MinetestApp::boolToCStr(bool v)
+{
+	static const char *str[] = { "false", "true" };
+	return str[v];
+}
+
 
-	//has to be deleted first to stop all server threads
-	delete server;
 
-	delete tsrc;
-	delete shsrc;
-	delete nodedef;
-	delete itemdef;
+/****************************************************************************
+ Shutdown / cleanup
+ ****************************************************************************/
 
-	//extended resource accounting
+void MinetestApp::extendedResourceCleanup()
+{
+	// Extended resource accounting
 	infostream << "Irrlicht resources after cleanup:" << std::endl;
 	infostream << "\tRemaining meshes   : "
-		<< device->getSceneManager()->getMeshCache()->getMeshCount() << std::endl;
+	           << device->getSceneManager()->getMeshCache()->getMeshCount() << std::endl;
 	infostream << "\tRemaining textures : "
-		<< driver->getTextureCount() << std::endl;
-	for (unsigned int i = 0; i < driver->getTextureCount(); i++ ) {
-		irr::video::ITexture* texture = driver->getTextureByIndex(i);
+	           << driver->getTextureCount() << std::endl;
+
+	for (unsigned int i = 0; i < driver->getTextureCount(); i++) {
+		irr::video::ITexture *texture = driver->getTextureByIndex(i);
 		infostream << "\t\t" << i << ":" << texture->getName().getPath().c_str()
-				<< std::endl;
+		           << std::endl;
 	}
+
 	clearTextureNameCache();
 	infostream << "\tRemaining materials: "
-		<< driver-> getMaterialRendererCount ()
-		<< " (note: irrlicht doesn't support removing renderers)"<< std::endl;
+               << driver-> getMaterialRendererCount()
+		       << " (note: irrlicht doesn't support removing renderers)" << std::endl;
+}
+
+
+
+/****************************************************************************
+ extern function for launching the game
+ ****************************************************************************/
+
+void the_game(bool *kill,
+		bool random_input,
+		InputHandler *input,
+		IrrlichtDevice *device,
+		gui::IGUIFont *font,
+
+		const std::string &map_dir,
+		const std::string &playername,
+		const std::string &password,
+		const std::string &address,         // If empty local server is created
+		u16 port,
+
+		std::wstring &error_message,
+		ChatBackend &chat_backend,
+		const SubgameSpec &gamespec,        // Used for local game
+		bool simple_singleplayer_mode)
+{
+	MinetestApp app;
+
+	/* Make a copy of the server address because if a local singleplayer server
+	 * is created then this is updated and we don't want to change the value
+	 * passed to us by the calling function
+	 */
+	std::string server_address = address;
+
+	try {
+
+		if (app.startup(kill, random_input, input, device, font, map_dir,
+					playername, password, &server_address, port,
+					&error_message, &chat_backend, gamespec,
+					simple_singleplayer_mode)) {
+
+			//std::cout << "App started" << std::endl;
+			app.run();
+			app.shutdown();
+		}
+
+	} catch (SerializationError &e) {
+		error_message = L"A serialization error occurred:\n"
+				+ narrow_to_wide(e.what()) + L"\n\nThe server is probably "
+				L" running a different version of Minetest.";
+		errorstream << wide_to_narrow(error_message) << std::endl;
+	} catch (ServerError &e) {
+		error_message = narrow_to_wide(e.what());
+		errorstream << "ServerError: " << e.what() << std::endl;
+	} catch (ModError &e) {
+		errorstream << "ModError: " << e.what() << std::endl;
+		error_message = narrow_to_wide(e.what()) + wgettext("\nCheck debug.txt for details.");
+	}
 }
diff --git a/src/game.h b/src/game.h
index 1c831c530067527c3bda16135109ec2728eae465..c3a7691d864eb5ae6fd1e36919cad6786ba5dc0d 100644
--- a/src/game.h
+++ b/src/game.h
@@ -35,11 +35,14 @@ class KeyList : protected std::list<KeyPress>
 	{
 		const_iterator f(begin());
 		const_iterator e(end());
-		while (f!=e) {
+
+		while (f != e) {
 			if (*f == key)
 				return f;
+
 			++f;
 		}
+
 		return e;
 	}
 
@@ -47,16 +50,22 @@ class KeyList : protected std::list<KeyPress>
 	{
 		iterator f(begin());
 		iterator e(end());
-		while (f!=e) {
+
+		while (f != e) {
 			if (*f == key)
 				return f;
+
 			++f;
 		}
+
 		return e;
 	}
 
 public:
-	void clear() { super::clear(); }
+	void clear()
+	{
+		super::clear();
+	}
 
 	void set(const KeyPress &key)
 	{
@@ -67,6 +76,7 @@ class KeyList : protected std::list<KeyPress>
 	void unset(const KeyPress &key)
 	{
 		iterator p(find(key));
+
 		if (p != end())
 			erase(p);
 	}
@@ -74,6 +84,7 @@ class KeyList : protected std::list<KeyPress>
 	void toggle(const KeyPress &key)
 	{
 		iterator p(this->find(key));
+
 		if (p != end())
 			erase(p);
 		else
@@ -98,7 +109,7 @@ class InputHandler
 
 	virtual bool isKeyDown(const KeyPress &keyCode) = 0;
 	virtual bool wasKeyDown(const KeyPress &keyCode) = 0;
-	
+
 	virtual v2s32 getMousePos() = 0;
 	virtual void setMousePos(s32 x, s32 y) = 0;
 
@@ -114,33 +125,31 @@ class InputHandler
 	virtual bool getRightReleased() = 0;
 	virtual void resetLeftReleased() = 0;
 	virtual void resetRightReleased() = 0;
-	
+
 	virtual s32 getMouseWheel() = 0;
 
-	virtual void step(float dtime) {};
+	virtual void step(float dtime) {}
 
-	virtual void clear() {};
+	virtual void clear() {}
 };
 
 class ChatBackend;  /* to avoid having to include chat.h */
 struct SubgameSpec;
 
-void the_game(
-	bool &kill,
-	bool random_input,
-	InputHandler *input,
-	IrrlichtDevice *device,
-	gui::IGUIFont* font,
-	std::string map_dir,
-	std::string playername,
-	std::string password,
-	std::string address, // If "", local server is used
-	u16 port,
-	std::wstring &error_message,
-	ChatBackend &chat_backend,
-	const SubgameSpec &gamespec, // Used for local game
-	bool simple_singleplayer_mode
-);
+void the_game(bool *kill,
+		bool random_input,
+		InputHandler *input,
+		IrrlichtDevice *device,
+		gui::IGUIFont *font,
+		const std::string &map_dir,
+		const std::string &playername,
+		const std::string &password,
+		const std::string &address, // If "", local server is used
+		u16 port,
+		std::wstring &error_message,
+		ChatBackend &chat_backend,
+		const SubgameSpec &gamespec, // Used for local game
+		bool simple_singleplayer_mode);
 
 #endif
 
diff --git a/src/main.cpp b/src/main.cpp
index 9d336825ec3a25b69b59b3d11b9a7be92cf43bf3..7a6b3e47cc3745f13504e130366a3cfb0a5fcbe2 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -1861,7 +1861,7 @@ int main(int argc, char *argv[])
 	g_touchscreengui = receiver->m_touchscreengui;
 #endif
 			the_game(
-				kill,
+				&kill,
 				random_input,
 				input,
 				device,