diff --git a/data/grass.png b/data/grass.png
index 8fe9078caffee8e3a78b9ccd6b70b1782ad8955d..b5c1559f6b9fedf23e48b48eef03524a38bda130 100644
Binary files a/data/grass.png and b/data/grass.png differ
diff --git a/data/stone.png b/data/stone.png
index c7a453e3f7be95a2dd091542441108ad05b1bca5..d085cb8dd267ade7ae0d8a1e9ac78e0f757acb62 100644
Binary files a/data/stone.png and b/data/stone.png differ
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index c0301cc80cf7c463f151cc891e200be47e35aae8..b26294113ddf2b4cb2dfe09e3afce59c55c5a615 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -50,6 +50,7 @@ configure_file(
 )
 
 set(common_SRCS
+	serverobject.cpp
 	noise.cpp
 	mineral.cpp
 	porting.cpp
@@ -75,8 +76,10 @@ set(common_SRCS
 	test.cpp
 )
 
+# Client sources
 set(minetest_SRCS
 	${common_SRCS}
+	clientobject.cpp
 	guiMainMenu.cpp
 	guiMessageMenu.cpp
 	guiTextInputMenu.cpp
@@ -88,6 +91,7 @@ set(minetest_SRCS
 	main.cpp
 )
 
+# Server sources
 set(minetestserver_SRCS
 	${common_SRCS}
 	servermain.cpp
diff --git a/src/activeobject.h b/src/activeobject.h
new file mode 100644
index 0000000000000000000000000000000000000000..103d90d12949b8cc190ed4ed7a399176f33bb41d
--- /dev/null
+++ b/src/activeobject.h
@@ -0,0 +1,71 @@
+/*
+Minetest-c55
+Copyright (C) 2010-2011 celeron55, Perttu Ahola <celeron55@gmail.com>
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 2 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License along
+with this program; if not, write to the Free Software Foundation, Inc.,
+51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+*/
+
+#ifndef ACTIVEOBJECT_HEADER
+#define ACTIVEOBJECT_HEADER
+
+#include "common_irrlicht.h"
+#include <string>
+
+struct ActiveObjectMessage
+{
+	ActiveObjectMessage(u16 id_, bool reliable_=true, std::string data_=""):
+		id(id_),
+		reliable(reliable_),
+		datastring(data_)
+	{}
+
+	u16 id;
+	bool reliable;
+	std::string datastring;
+};
+
+#define ACTIVEOBJECT_TYPE_INVALID 0
+#define ACTIVEOBJECT_TYPE_TEST 1
+#define ACTIVEOBJECT_TYPE_LUA 2
+
+/*
+	Parent class for ServerActiveObject and ClientActiveObject
+*/
+class ActiveObject
+{
+public:
+	ActiveObject(u16 id):
+		m_id(id)
+	{
+	}
+	
+	u16 getId()
+	{
+		return m_id;
+	}
+
+	void setId(u16 id)
+	{
+		m_id = id;
+	}
+
+	virtual u8 getType() const = 0;
+
+protected:
+	u16 m_id; // 0 is invalid, "no id"
+};
+
+#endif
+
diff --git a/src/client.cpp b/src/client.cpp
index 21c911ec6fe3eb99ece4e8eed052e4f2df973c11..4ad1f12269548719b055598eedb5f039a18d2537 100644
--- a/src/client.cpp
+++ b/src/client.cpp
@@ -56,10 +56,12 @@ Client::Client(
 		const char *playername,
 		MapDrawControl &control):
 	m_thread(this),
-	m_env(new ClientMap(this, control,
+	m_env(
+		new ClientMap(this, control,
 			device->getSceneManager()->getRootSceneNode(),
 			device->getSceneManager(), 666),
-			dout_client),
+		device->getSceneManager()
+	),
 	m_con(PROTOCOL_ID, 512, CONNECTION_TIMEOUT, this),
 	m_device(device),
 	camera_position(0,0,0),
@@ -82,20 +84,25 @@ Client::Client(
 	m_step_dtime_mutex.Init();
 
 	m_thread.Start();
-	
+
+	/*
+		Add local player
+	*/
 	{
 		JMutexAutoLock envlock(m_env_mutex);
-		//m_env.getMap().StartUpdater();
 
 		Player *player = new LocalPlayer();
 
 		player->updateName(playername);
 
-		/*f32 y = BS*2 + BS*20;
-		player->setPosition(v3f(0, y, 0));*/
-		//player->setPosition(v3f(0, y, 30900*BS)); // DEBUG
 		m_env.addPlayer(player);
 	}
+
+	// Add some active objects for testing
+	/*{
+		ClientActiveObject *obj = new TestCAO(0, v3f(0, 10*BS, 0));
+		m_env.addActiveObject(obj);
+	}*/
 }
 
 Client::~Client()
@@ -493,7 +500,7 @@ void Client::ProcessData(u8 *data, u32 datasize, u16 sender_peer_id)
 		v3s16 playerpos_s16(0, BS*2+BS*20, 0);
 		if(datasize >= 2+1+6)
 			playerpos_s16 = readV3S16(&data[2+1]);
-		v3f playerpos_f = intToFloat(playerpos_s16) - v3f(0, BS/2, 0);
+		v3f playerpos_f = intToFloat(playerpos_s16, BS) - v3f(0, BS/2, 0);
 
 		{ //envlock
 			JMutexAutoLock envlock(m_env_mutex);
@@ -1037,6 +1044,99 @@ void Client::ProcessData(u8 *data, u32 datasize, u16 sender_peer_id)
 		
 		m_chat_queue.push_back(message);
 	}
+	else if(command == TOCLIENT_ACTIVE_OBJECT_REMOVE_ADD)
+	{
+		/*
+			u16 command
+			u16 count of removed objects
+			for all removed objects {
+				u16 id
+			}
+			u16 count of added objects
+			for all added objects {
+				u16 id
+				u8 type
+			}
+		*/
+
+		char buf[6];
+		// Get all data except the command number
+		std::string datastring((char*)&data[2], datasize-2);
+		// Throw them in an istringstream
+		std::istringstream is(datastring, std::ios_base::binary);
+
+		// Read stuff
+		
+		// Read removed objects
+		is.read(buf, 2);
+		u16 removed_count = readU16((u8*)buf);
+		for(u16 i=0; i<removed_count; i++)
+		{
+			is.read(buf, 2);
+			u16 id = readU16((u8*)buf);
+			// Remove it
+			{
+				JMutexAutoLock envlock(m_env_mutex);
+				m_env.removeActiveObject(id);
+			}
+		}
+		
+		// Read added objects
+		is.read(buf, 2);
+		u16 added_count = readU16((u8*)buf);
+		for(u16 i=0; i<added_count; i++)
+		{
+			is.read(buf, 2);
+			u16 id = readU16((u8*)buf);
+			is.read(buf, 1);
+			u8 type = readU8((u8*)buf);
+			// Add it
+			{
+				JMutexAutoLock envlock(m_env_mutex);
+				m_env.addActiveObject(id, type);
+			}
+		}
+	}
+	else if(command == TOCLIENT_ACTIVE_OBJECT_MESSAGES)
+	{
+		/*
+			u16 command
+			for all objects
+			{
+				u16 id
+				u16 message length
+				string message
+			}
+		*/
+		char buf[6];
+		// Get all data except the command number
+		std::string datastring((char*)&data[2], datasize-2);
+		// Throw them in an istringstream
+		std::istringstream is(datastring, std::ios_base::binary);
+		
+		while(is.eof() == false)
+		{
+			// Read stuff
+			is.read(buf, 2);
+			u16 id = readU16((u8*)buf);
+			if(is.eof())
+				break;
+			is.read(buf, 2);
+			u16 message_size = readU16((u8*)buf);
+			std::string message;
+			message.reserve(message_size);
+			for(u16 i=0; i<message_size; i++)
+			{
+				is.read(buf, 1);
+				message.append(buf, 1);
+			}
+			// Pass on to the environment
+			{
+				JMutexAutoLock envlock(m_env_mutex);
+				m_env.processActiveObjectMessage(id, message);
+			}
+		}
+	}
 	// Default to queueing it (for slow commands)
 	else
 	{
@@ -1197,7 +1297,7 @@ bool Client::AsyncProcessPacket()
 			main thread, from which is will want to retrieve textures.
 		*/
 
-		m_env.getMap().updateMeshes(block->getPos(), getDayNightRatio());
+		m_env.getClientMap().updateMeshes(block->getPos(), getDayNightRatio());
 	}
 	else
 	{
@@ -1464,7 +1564,7 @@ void Client::removeNode(v3s16 p)
 			i.atEnd() == false; i++)
 	{
 		v3s16 p = i.getNode()->getKey();
-		m_env.getMap().updateMeshes(p, m_env.getDayNightRatio());
+		m_env.getClientMap().updateMeshes(p, m_env.getDayNightRatio());
 	}
 }
 
@@ -1486,7 +1586,7 @@ void Client::addNode(v3s16 p, MapNode n)
 			i.atEnd() == false; i++)
 	{
 		v3s16 p = i.getNode()->getKey();
-		m_env.getMap().updateMeshes(p, m_env.getDayNightRatio());
+		m_env.getClientMap().updateMeshes(p, m_env.getDayNightRatio());
 	}
 }
 	
@@ -1503,36 +1603,6 @@ MapNode Client::getNode(v3s16 p)
 	return m_env.getMap().getNode(p);
 }
 
-/*void Client::getNode(v3s16 p, MapNode n)
-{
-	JMutexAutoLock envlock(m_env_mutex);
-	m_env.getMap().setNode(p, n);
-}*/
-
-/*f32 Client::getGroundHeight(v2s16 p)
-{
-	JMutexAutoLock envlock(m_env_mutex);
-	return m_env.getMap().getGroundHeight(p);
-}*/
-
-/*bool Client::isNodeUnderground(v3s16 p)
-{
-	JMutexAutoLock envlock(m_env_mutex);
-	return m_env.getMap().isNodeUnderground(p);
-}*/
-
-/*Player * Client::getLocalPlayer()
-{
-	JMutexAutoLock envlock(m_env_mutex);
-	return m_env.getLocalPlayer();
-}*/
-
-/*core::list<Player*> Client::getPlayers()
-{
-	JMutexAutoLock envlock(m_env_mutex);
-	return m_env.getPlayers();
-}*/
-
 v3f Client::getPlayerPosition()
 {
 	JMutexAutoLock envlock(m_env_mutex);
@@ -1597,7 +1667,7 @@ MapBlockObject * Client::getSelectedObject(
 
 		// Calculate from_pos relative to block
 		v3s16 block_pos_i_on_map = block->getPosRelative();
-		v3f block_pos_f_on_map = intToFloat(block_pos_i_on_map);
+		v3f block_pos_f_on_map = intToFloat(block_pos_i_on_map, BS);
 		v3f from_pos_f_on_block = from_pos_f_on_map - block_pos_f_on_map;
 
 		block->getObjects(from_pos_f_on_block, max_d, objects);
@@ -1617,7 +1687,7 @@ MapBlockObject * Client::getSelectedObject(
 
 		// Calculate shootline relative to block
 		v3s16 block_pos_i_on_map = block->getPosRelative();
-		v3f block_pos_f_on_map = intToFloat(block_pos_i_on_map);
+		v3f block_pos_f_on_map = intToFloat(block_pos_i_on_map, BS);
 		core::line3d<f32> shootline_on_block(
 				shootline_on_map.start - block_pos_f_on_map,
 				shootline_on_map.end - block_pos_f_on_map
@@ -1672,7 +1742,7 @@ u32 Client::getDayNightRatio()
 	v3f playerpos = player->getPosition();
 	v3f playerspeed = player->getSpeed();
 
-	v3s16 center_nodepos = floatToInt(playerpos);
+	v3s16 center_nodepos = floatToInt(playerpos, BS);
 	v3s16 center = getNodeBlockPos(center_nodepos);
 
 	u32 counter = 0;
diff --git a/src/client.h b/src/client.h
index 00fd3a5edf72476735083fd7111e3a0bd8b841de..fb1e70722e1a936cf8c96d88c8cda2099f01b8e3 100644
--- a/src/client.h
+++ b/src/client.h
@@ -27,6 +27,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
 #include "common_irrlicht.h"
 #include "jmutex.h"
 #include <ostream>
+#include "clientobject.h"
 
 class ClientNotReadyException : public BaseException
 {
@@ -165,11 +166,6 @@ class Client : public con::PeerHandler
 	
 	// Returns InvalidPositionException if not found
 	MapNode getNode(v3s16 p);
-	// Returns InvalidPositionException if not found
-	//void setNode(v3s16 p, MapNode n);
-
-	// Returns InvalidPositionException if not found
-	//f32 getGroundHeight(v2s16 p);
 
 	v3f getPlayerPosition();
 
@@ -192,7 +188,6 @@ class Client : public con::PeerHandler
 	// Prints a line or two of info
 	void printDebugInfo(std::ostream &os);
 
-	//s32 getDayNightIndex();
 	u32 getDayNightRatio();
 
 	//void updateSomeExpiredMeshes();
@@ -230,27 +225,6 @@ class Client : public con::PeerHandler
 		}
 	}
 
-#if 0
-	void setTempMod(v3s16 p, NodeMod mod)
-	{
-		JMutexAutoLock envlock(m_env_mutex);
-		assert(m_env.getMap().mapType() == MAPTYPE_CLIENT);
-		bool changed = false;
-		v3s16 blockpos = ((ClientMap&)m_env.getMap()).setTempMod(p, mod, &changed);
-		if(changed)
-			m_env.getMap().updateMeshes(blockpos, m_env.getDayNightRatio());
-	}
-	void clearTempMod(v3s16 p)
-	{
-		JMutexAutoLock envlock(m_env_mutex);
-		assert(m_env.getMap().mapType() == MAPTYPE_CLIENT);
-		bool changed = false;
-		v3s16 blockpos = ((ClientMap&)m_env.getMap()).clearTempMod(p, &changed);
-		if(changed)
-			m_env.getMap().updateMeshes(blockpos, m_env.getDayNightRatio());
-	}
-#endif
-
 	float getAvgRtt()
 	{
 		JMutexAutoLock lock(m_con_mutex);
@@ -302,15 +276,12 @@ class Client : public con::PeerHandler
 	// NOTE: If connection and environment are both to be locked,
 	// environment shall be locked first.
 
-	Environment m_env;
+	ClientEnvironment m_env;
 	JMutex m_env_mutex;
 	
 	con::Connection m_con;
 	JMutex m_con_mutex;
 
-	/*core::map<v3s16, float> m_fetchblock_history;
-	JMutex m_fetchblock_mutex;*/
-
 	core::list<IncomingPacket> m_incoming_queue;
 	JMutex m_incoming_queue_mutex;
 
diff --git a/src/clientobject.cpp b/src/clientobject.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..46db389cdc53e696200e18ca66a304adddac971a
--- /dev/null
+++ b/src/clientobject.cpp
@@ -0,0 +1,169 @@
+/*
+Minetest-c55
+Copyright (C) 2010-2011 celeron55, Perttu Ahola <celeron55@gmail.com>
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 2 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License along
+with this program; if not, write to the Free Software Foundation, Inc.,
+51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+*/
+
+#include "clientobject.h"
+#include "debug.h"
+#include "porting.h"
+#include "constants.h"
+#include "utility.h"
+
+ClientActiveObject::ClientActiveObject(u16 id):
+	ActiveObject(id)
+{
+}
+
+ClientActiveObject::~ClientActiveObject()
+{
+	removeFromScene();
+}
+
+ClientActiveObject* ClientActiveObject::create(u8 type)
+{
+	if(type == ACTIVEOBJECT_TYPE_INVALID)
+	{
+		dstream<<"ClientActiveObject::create(): passed "
+				<<"ACTIVEOBJECT_TYPE_INVALID"<<std::endl;
+		return NULL;
+	}
+	else if(type == ACTIVEOBJECT_TYPE_TEST)
+	{
+		dstream<<"ClientActiveObject::create(): passed "
+				<<"ACTIVEOBJECT_TYPE_TEST"<<std::endl;
+		return new TestCAO(0);
+	}
+	else if(type == ACTIVEOBJECT_TYPE_LUA)
+	{
+		dstream<<"ClientActiveObject::create(): passed "
+				<<"ACTIVEOBJECT_TYPE_LUA"<<std::endl;
+		return NULL;
+	}
+	else
+	{
+		dstream<<"ClientActiveObject::create(): passed "
+				<<"unknown type="<<type<<std::endl;
+		return NULL;
+	}
+}
+
+/*
+	TestCAO
+*/
+
+TestCAO::TestCAO(u16 id):
+	ClientActiveObject(id),
+	m_node(NULL),
+	m_position(v3f(0,10*BS,0))
+{
+}
+
+TestCAO::~TestCAO()
+{
+}
+
+void TestCAO::addToScene(scene::ISceneManager *smgr)
+{
+	if(m_node != NULL)
+		return;
+	
+	video::IVideoDriver* driver = smgr->getVideoDriver();
+	
+	scene::SMesh *mesh = new scene::SMesh();
+	scene::IMeshBuffer *buf = new scene::SMeshBuffer();
+	video::SColor c(255,255,255,255);
+	video::S3DVertex vertices[4] =
+	{
+		video::S3DVertex(-BS/2,-BS/4,0, 0,0,0, c, 0,1),
+		video::S3DVertex(BS/2,-BS/4,0, 0,0,0, c, 1,1),
+		video::S3DVertex(BS/2,BS/4,0, 0,0,0, c, 1,0),
+		video::S3DVertex(-BS/2,BS/4,0, 0,0,0, c, 0,0),
+	};
+	u16 indices[] = {0,1,2,2,3,0};
+	buf->append(vertices, 4, indices, 6);
+	// Set material
+	buf->getMaterial().setFlag(video::EMF_LIGHTING, false);
+	buf->getMaterial().setFlag(video::EMF_BACK_FACE_CULLING, false);
+	buf->getMaterial().setTexture
+			(0, driver->getTexture(porting::getDataPath("rat.png").c_str()));
+	buf->getMaterial().setFlag(video::EMF_BILINEAR_FILTER, false);
+	buf->getMaterial().setFlag(video::EMF_FOG_ENABLE, true);
+	buf->getMaterial().MaterialType = video::EMT_TRANSPARENT_ALPHA_CHANNEL;
+	// Add to mesh
+	mesh->addMeshBuffer(buf);
+	buf->drop();
+	m_node = smgr->addMeshSceneNode(mesh, NULL);
+	mesh->drop();
+	updateNodePos();
+}
+
+void TestCAO::removeFromScene()
+{
+	if(m_node == NULL)
+		return;
+
+	m_node->remove();
+	m_node = NULL;
+}
+
+void TestCAO::updateLight(u8 light_at_pos)
+{
+}
+
+v3s16 TestCAO::getLightPosition()
+{
+	return floatToInt(m_position, BS);
+}
+
+void TestCAO::updateNodePos()
+{
+	if(m_node == NULL)
+		return;
+
+	m_node->setPosition(m_position);
+	//m_node->setRotation(v3f(0, 45, 0));
+}
+
+void TestCAO::step(float dtime)
+{
+	if(m_node)
+	{
+		v3f rot = m_node->getRotation();
+		//dstream<<"dtime="<<dtime<<", rot.Y="<<rot.Y<<std::endl;
+		rot.Y += dtime * 180;
+		m_node->setRotation(rot);
+	}
+}
+
+void TestCAO::processMessage(const std::string &data)
+{
+	//dstream<<"TestCAO: Got data: "<<data<<std::endl;
+	std::istringstream is(data, std::ios::binary);
+	u16 cmd;
+	is>>cmd;
+	if(cmd == 0)
+	{
+		v3f newpos;
+		is>>newpos.X;
+		is>>newpos.Y;
+		is>>newpos.Z;
+		m_position = newpos;
+		updateNodePos();
+	}
+}
+
+
diff --git a/src/clientobject.h b/src/clientobject.h
new file mode 100644
index 0000000000000000000000000000000000000000..454531aec92603abfc51d00cdaa25d38d630b88d
--- /dev/null
+++ b/src/clientobject.h
@@ -0,0 +1,88 @@
+/*
+Minetest-c55
+Copyright (C) 2010-2011 celeron55, Perttu Ahola <celeron55@gmail.com>
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 2 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License along
+with this program; if not, write to the Free Software Foundation, Inc.,
+51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+*/
+
+#ifndef CLIENTOBJECT_HEADER
+#define CLIENTOBJECT_HEADER
+
+#include "common_irrlicht.h"
+#include "activeobject.h"
+
+/*
+
+Some planning
+-------------
+
+* Client receives a network packet with information of added objects
+  in it
+* Client supplies the information to its ClientEnvironment
+* The environment adds the specified objects to itself
+
+*/
+
+class ClientActiveObject : public ActiveObject
+{
+public:
+	ClientActiveObject(u16 id);
+	virtual ~ClientActiveObject();
+
+	virtual void addToScene(scene::ISceneManager *smgr){}
+	virtual void removeFromScene(){}
+	// 0 <= light_at_pos <= LIGHT_SUN
+	virtual void updateLight(u8 light_at_pos){}
+	virtual v3s16 getLightPosition(){return v3s16(0,0,0);}
+	
+	// Step object in time
+	virtual void step(float dtime){}
+	
+	// Process a message sent by the server side object
+	virtual void processMessage(const std::string &data){}
+
+	static ClientActiveObject* create(u8 type);
+
+protected:
+};
+
+class TestCAO : public ClientActiveObject
+{
+public:
+	TestCAO(u16 id);
+	virtual ~TestCAO();
+	
+	u8 getType() const
+	{
+		return ACTIVEOBJECT_TYPE_TEST;
+	}
+	
+	void addToScene(scene::ISceneManager *smgr);
+	void removeFromScene();
+	void updateLight(u8 light_at_pos);
+	v3s16 getLightPosition();
+	void updateNodePos();
+
+	void step(float dtime);
+
+	void processMessage(const std::string &data);
+
+private:
+	scene::IMeshSceneNode *m_node;
+	v3f m_position;
+};
+
+#endif
+
diff --git a/src/clientserver.h b/src/clientserver.h
index 07b1cf60f317daa3fc0e90174b08afec6c920995..66ee58473e5a83e2c069d09fd75600f426dc4368 100644
--- a/src/clientserver.h
+++ b/src/clientserver.h
@@ -106,6 +106,31 @@ enum ToClientCommand
 		wstring message
 	*/
 
+	TOCLIENT_ACTIVE_OBJECT_REMOVE_ADD = 0x31,
+	/*
+		u16 command
+		u16 count of removed objects
+		for all removed objects {
+			u16 id
+		}
+		u16 count of added objects
+		for all added objects {
+			u16 id
+			u8 type
+		}
+	*/
+	
+	TOCLIENT_ACTIVE_OBJECT_MESSAGES = 0x32,
+	/*
+		u16 command
+		for all objects
+		{
+			u16 id
+			u16 message length
+			string message
+		}
+	*/
+
 };
 
 enum ToServerCommand
diff --git a/src/environment.cpp b/src/environment.cpp
index 07437ec400057dc175d60622eea739812c7c4699..5e16602e0712956ec1fb0bcaac829206452941c8 100644
--- a/src/environment.cpp
+++ b/src/environment.cpp
@@ -20,11 +20,9 @@ with this program; if not, write to the Free Software Foundation, Inc.,
 #include "environment.h"
 #include "filesys.h"
 
-Environment::Environment(Map *map, std::ostream &dout):
-		m_dout(dout)
+Environment::Environment()
 {
-	m_map = map;
-	m_daynight_ratio = 0.2;
+	m_daynight_ratio = 0.5;
 }
 
 Environment::~Environment()
@@ -35,183 +33,16 @@ Environment::~Environment()
 	{
 		delete (*i);
 	}
-	
-	// The map is removed by the SceneManager
-	m_map->drop();
-	//delete m_map;
-}
-
-void Environment::step(float dtime)
-{
-	DSTACK(__FUNCTION_NAME);
-	/*
-		Run Map's timers
-	*/
-	//TimeTaker maptimerupdatetimer("m_map->timerUpdate()", g_device);
-	// 0ms
-	m_map->timerUpdate(dtime);
-	//maptimerupdatetimer.stop();
-
-	/*
-		Get the highest speed some player is going
-	*/
-	//TimeTaker playerspeed("playerspeed", g_device);
-	// 0ms
-	f32 maximum_player_speed = 0.001; // just some small value
-	for(core::list<Player*>::Iterator i = m_players.begin();
-			i != m_players.end(); i++)
-	{
-		f32 speed = (*i)->getSpeed().getLength();
-		if(speed > maximum_player_speed)
-			maximum_player_speed = speed;
-	}
-	//playerspeed.stop();
-	
-	/*
-		Maximum position increment
-	*/
-	//f32 position_max_increment = 0.05*BS;
-	f32 position_max_increment = 0.1*BS;
-
-	// Maximum time increment (for collision detection etc)
-	// time = distance / speed
-	f32 dtime_max_increment = position_max_increment / maximum_player_speed;
-	// Maximum time increment is 10ms or lower
-	if(dtime_max_increment > 0.01)
-		dtime_max_increment = 0.01;
-	
-	//TimeTaker playerupdate("playerupdate", g_device);
-	
-	/*
-		Stuff that has a maximum time increment
-	*/
-	// Don't allow overly huge dtime
-	if(dtime > 0.5)
-		dtime = 0.5;
-
-	u32 loopcount = 0;
-	do
-	{
-		loopcount++;
-
-		f32 dtime_part;
-		if(dtime > dtime_max_increment)
-			dtime_part = dtime_max_increment;
-		else
-			dtime_part = dtime;
-		dtime -= dtime_part;
-		
-		/*
-			Handle players
-		*/
-		for(core::list<Player*>::Iterator i = m_players.begin();
-				i != m_players.end(); i++)
-		{
-			Player *player = *i;
-
-			v3f playerpos = player->getPosition();
-			
-			// Apply physics to local player
-			bool free_move = g_settings.getBool("free_move");
-			if(player->isLocal() && free_move == false)
-			{
-				// Apply gravity to local player
-				v3f speed = player->getSpeed();
-				if(player->swimming_up == false)
-					speed.Y -= 9.81 * BS * dtime_part * 2;
-
-				/*
-					Apply water resistance
-				*/
-				if(player->in_water_stable || player->in_water)
-				{
-					f32 max_down = 2.0*BS;
-					if(speed.Y < -max_down) speed.Y = -max_down;
-
-					f32 max = 2.5*BS;
-					if(speed.getLength() > max)
-					{
-						speed = speed / speed.getLength() * max;
-					}
-				}
-
-				player->setSpeed(speed);
-			}
-
-			/*
-				Move the player.
-				For local player, this also calculates collision detection.
-			*/
-			player->move(dtime_part, *m_map, position_max_increment);
-			
-			/*
-				Update lighting on remote players on client
-			*/
-			u8 light = LIGHT_MAX;
-			try{
-				// Get node at feet
-				v3s16 p = floatToInt(playerpos + v3f(0,BS/4,0));
-				MapNode n = m_map->getNode(p);
-				light = n.getLightBlend(m_daynight_ratio);
-			}
-			catch(InvalidPositionException &e) {}
-			player->updateLight(light);
-
-			/*
-				Add footsteps to grass
-			*/
-			if(g_settings.getBool("footprints"))
-			{
-				// Get node that is at BS/4 under player
-				v3s16 bottompos = floatToInt(playerpos + v3f(0,-BS/4,0));
-				try{
-					MapNode n = m_map->getNode(bottompos);
-					if(n.d == CONTENT_GRASS)
-					{
-						n.d = CONTENT_GRASS_FOOTSTEPS;
-						m_map->setNode(bottompos, n);
-#ifndef SERVER
-						// Update mesh on client
-						if(m_map->mapType() == MAPTYPE_CLIENT)
-						{
-							v3s16 p_blocks = getNodeBlockPos(bottompos);
-							MapBlock *b = m_map->getBlockNoCreate(p_blocks);
-							b->updateMesh(m_daynight_ratio);
-						}
-#endif
-					}
-				}
-				catch(InvalidPositionException &e)
-				{
-				}
-			}
-		}
-	}
-	while(dtime > 0.001);
-	
-	//std::cout<<"Looped "<<loopcount<<" times."<<std::endl;
-}
-
-Map & Environment::getMap()
-{
-	return *m_map;
 }
 
 void Environment::addPlayer(Player *player)
 {
 	DSTACK(__FUNCTION_NAME);
 	/*
-		Check that only one local player exists and peer_ids are unique.
+		Check that peer_ids are unique.
 		Also check that names are unique.
 		Exception: there can be multiple players with peer_id=0
 	*/
-#ifndef SERVER
-	/*
-		It is a failure if player is local and there already is a local
-		player
-	*/
-	assert(!(player->isLocal() == true && getLocalPlayer() != NULL));
-#endif
 	// If peer id is non-zero, it has to be unique.
 	if(player->peer_id != 0)
 		assert(getPlayer(player->peer_id) == NULL);
@@ -240,20 +71,6 @@ void Environment::removePlayer(u16 peer_id)
 	}
 }
 
-#ifndef SERVER
-LocalPlayer * Environment::getLocalPlayer()
-{
-	for(core::list<Player*>::Iterator i = m_players.begin();
-			i != m_players.end(); i++)
-	{
-		Player *player = *i;
-		if(player->isLocal())
-			return (LocalPlayer*)player;
-	}
-	return NULL;
-}
-#endif
-
 Player * Environment::getPlayer(u16 peer_id)
 {
 	for(core::list<Player*>::Iterator i = m_players.begin();
@@ -315,7 +132,38 @@ void Environment::printPlayers(std::ostream &o)
 	}
 }
 
-void Environment::serializePlayers(const std::string &savedir)
+void Environment::setDayNightRatio(u32 r)
+{
+	m_daynight_ratio = r;
+}
+
+u32 Environment::getDayNightRatio()
+{
+	return m_daynight_ratio;
+}
+
+/*
+	ServerEnvironment
+*/
+
+ServerEnvironment::ServerEnvironment(ServerMap *map):
+	m_map(map),
+	m_random_spawn_timer(0)
+{
+	/*
+		TEST CODE
+	*/
+	TestSAO *obj = new TestSAO(0, v3f(0, BS*5, 0));
+	addActiveObject(obj);
+}
+
+ServerEnvironment::~ServerEnvironment()
+{
+	// Drop/delete map
+	m_map->drop();
+}
+
+void ServerEnvironment::serializePlayers(const std::string &savedir)
 {
 	std::string players_path = savedir + "/players";
 	fs::CreateDir(players_path);
@@ -430,7 +278,7 @@ void Environment::serializePlayers(const std::string &savedir)
 	//dstream<<"Saved "<<saved_players.size()<<" players."<<std::endl;
 }
 
-void Environment::deSerializePlayers(const std::string &savedir)
+void ServerEnvironment::deSerializePlayers(const std::string &savedir)
 {
 	std::string players_path = savedir + "/players";
 
@@ -492,25 +340,630 @@ void Environment::deSerializePlayers(const std::string &savedir)
 	}
 }
 
+void ServerEnvironment::step(float dtime)
+{
+	DSTACK(__FUNCTION_NAME);
+
+	// Get some settings
+	//bool free_move = g_settings.getBool("free_move");
+	bool footprints = g_settings.getBool("footprints");
+
+	{
+		//TimeTaker timer("Server m_map->timerUpdate()", g_device);
+		m_map->timerUpdate(dtime);
+	}
+
+	/*
+		Handle players
+	*/
+	for(core::list<Player*>::Iterator i = m_players.begin();
+			i != m_players.end(); i++)
+	{
+		Player *player = *i;
+		v3f playerpos = player->getPosition();
+		
+		// Move
+		player->move(dtime, *m_map, 100*BS);
+		
+		/*
+			Add footsteps to grass
+		*/
+		if(footprints)
+		{
+			// Get node that is at BS/4 under player
+			v3s16 bottompos = floatToInt(playerpos + v3f(0,-BS/4,0), BS);
+			try{
+				MapNode n = m_map->getNode(bottompos);
+				if(n.d == CONTENT_GRASS)
+				{
+					n.d = CONTENT_GRASS_FOOTSTEPS;
+					m_map->setNode(bottompos, n);
+				}
+			}
+			catch(InvalidPositionException &e)
+			{
+			}
+		}
+	}
+	
+	/*
+		Step active objects
+	*/
+	for(core::map<u16, ServerActiveObject*>::Iterator
+			i = m_active_objects.getIterator();
+			i.atEnd()==false; i++)
+	{
+		ServerActiveObject* obj = i.getNode()->getValue();
+		// Step object, putting messages directly to the queue
+		obj->step(dtime, m_active_object_messages);
+	}
+
+	/*
+		Remove (m_removed && m_known_by_count==0) objects
+	*/
+	{
+		core::list<u16> objects_to_remove;
+		for(core::map<u16, ServerActiveObject*>::Iterator
+				i = m_active_objects.getIterator();
+				i.atEnd()==false; i++)
+		{
+			u16 id = i.getNode()->getKey();
+			ServerActiveObject* obj = i.getNode()->getValue();
+			// This shouldn't happen but check it
+			if(obj == NULL)
+			{
+				dstream<<"WARNING: NULL object found in ServerEnvironment"
+						<<" while finding removed objects. id="<<id<<std::endl;
+				// Id to be removed from m_active_objects
+				objects_to_remove.push_back(id);
+				continue;
+			}
+			else
+			{
+				// If not m_removed, don't remove.
+				if(obj->m_removed == false)
+					continue;
+				// Delete
+				delete obj;
+				// Id to be removed from m_active_objects
+				objects_to_remove.push_back(id);
+			}
+		}
+		// Remove references from m_active_objects
+		for(core::list<u16>::Iterator i = objects_to_remove.begin();
+				i != objects_to_remove.end(); i++)
+		{
+			m_active_objects.remove(*i);
+		}
+	}
+
+	/*
+		TEST CODE
+	*/
+	m_random_spawn_timer -= dtime;
+	if(m_random_spawn_timer < 0)
+	{
+		m_random_spawn_timer += 0.1;
+		TestSAO *obj = new TestSAO(0,
+				v3f(myrand_range(-2*BS,2*BS), BS*5, myrand_range(-2*BS,2*BS)));
+		addActiveObject(obj);
+	}
+}
+
+ServerActiveObject* ServerEnvironment::getActiveObject(u16 id)
+{
+	core::map<u16, ServerActiveObject*>::Node *n;
+	n = m_active_objects.find(id);
+	if(n == NULL)
+		return NULL;
+	return n->getValue();
+}
+
+bool isFreeServerActiveObjectId(u16 id,
+		core::map<u16, ServerActiveObject*> &objects)
+{
+	if(id == 0)
+		return false;
+	
+	for(core::map<u16, ServerActiveObject*>::Iterator
+			i = objects.getIterator();
+			i.atEnd()==false; i++)
+	{
+		if(i.getNode()->getKey() == id)
+			return false;
+	}
+	return true;
+}
+
+u16 getFreeServerActiveObjectId(
+		core::map<u16, ServerActiveObject*> &objects)
+{
+	u16 new_id = 1;
+	for(;;)
+	{
+		if(isFreeServerActiveObjectId(new_id, objects))
+			return new_id;
+		
+		if(new_id == 65535)
+			return 0;
+
+		new_id++;
+	}
+}
+
+u16 ServerEnvironment::addActiveObject(ServerActiveObject *object)
+{
+	assert(object);
+	if(object->getId() == 0)
+	{
+		u16 new_id = getFreeServerActiveObjectId(m_active_objects);
+		if(new_id == 0)
+		{
+			dstream<<"WARNING: ServerEnvironment::addActiveObject(): "
+					<<"no free ids available"<<std::endl;
+			delete object;
+			return 0;
+		}
+		object->setId(new_id);
+	}
+	if(isFreeServerActiveObjectId(object->getId(), m_active_objects) == false)
+	{
+		dstream<<"WARNING: ServerEnvironment::addActiveObject(): "
+				<<"id is not free ("<<object->getId()<<")"<<std::endl;
+		delete object;
+		return 0;
+	}
+	dstream<<"INGO: ServerEnvironment::addActiveObject(): "
+			<<"added (id="<<object->getId()<<")"<<std::endl;
+	m_active_objects.insert(object->getId(), object);
+	return object->getId();
+}
+
+/*
+	Finds out what new objects have been added to
+	inside a radius around a position
+*/
+void ServerEnvironment::getAddedActiveObjects(v3s16 pos, s16 radius,
+		core::map<u16, bool> &current_objects,
+		core::map<u16, bool> &added_objects)
+{
+	v3f pos_f = intToFloat(pos, BS);
+	f32 radius_f = radius * BS;
+	/*
+		Go through the object list,
+		- discard m_removed objects,
+		- discard objects that are too far away,
+		- discard objects that are found in current_objects.
+		- add remaining objects to added_objects
+	*/
+	for(core::map<u16, ServerActiveObject*>::Iterator
+			i = m_active_objects.getIterator();
+			i.atEnd()==false; i++)
+	{
+		u16 id = i.getNode()->getKey();
+		// Get object
+		ServerActiveObject *object = i.getNode()->getValue();
+		if(object == NULL)
+			continue;
+		// Discard if removed
+		if(object->m_removed)
+			continue;
+		// Discard if too far
+		f32 distance_f = object->getBasePosition().getDistanceFrom(pos_f);
+		if(distance_f > radius_f)
+			continue;
+		// Discard if already on current_objects
+		core::map<u16, bool>::Node *n;
+		n = current_objects.find(id);
+		if(n != NULL)
+			continue;
+		// Add to added_objects
+		added_objects.insert(id, false);
+	}
+}
+
+/*
+	Finds out what objects have been removed from
+	inside a radius around a position
+*/
+void ServerEnvironment::getRemovedActiveObjects(v3s16 pos, s16 radius,
+		core::map<u16, bool> &current_objects,
+		core::map<u16, bool> &removed_objects)
+{
+	v3f pos_f = intToFloat(pos, BS);
+	f32 radius_f = radius * BS;
+	/*
+		Go through current_objects; object is removed if:
+		- object is not found in m_active_objects (this is actually an
+		  error condition; objects should be set m_removed=true and removed
+		  only after all clients have been informed about removal), or
+		- object has m_removed=true, or
+		- object is too far away
+	*/
+	for(core::map<u16, bool>::Iterator
+			i = current_objects.getIterator();
+			i.atEnd()==false; i++)
+	{
+		u16 id = i.getNode()->getKey();
+		ServerActiveObject *object = getActiveObject(id);
+		if(object == NULL)
+		{
+			dstream<<"WARNING: ServerEnvironment::getRemovedActiveObjects():"
+					<<" object in current_objects is NULL"<<std::endl;
+		}
+		else if(object->m_removed == false)
+		{
+			f32 distance_f = object->getBasePosition().getDistanceFrom(pos_f);
+			/*dstream<<"removed == false"
+					<<"distance_f = "<<distance_f
+					<<", radius_f = "<<radius_f<<std::endl;*/
+			if(distance_f < radius_f)
+			{
+				// Not removed
+				continue;
+			}
+		}
+		removed_objects.insert(id, false);
+	}
+}
+
+ActiveObjectMessage ServerEnvironment::getActiveObjectMessage()
+{
+	if(m_active_object_messages.size() == 0)
+		return ActiveObjectMessage(0);
+	
+	return m_active_object_messages.pop_front();
+}
+
 #ifndef SERVER
-void Environment::updateMeshes(v3s16 blockpos)
+
+/*
+	ClientEnvironment
+*/
+
+ClientEnvironment::ClientEnvironment(ClientMap *map, scene::ISceneManager *smgr):
+	m_map(map),
+	m_smgr(smgr)
+{
+	assert(m_map);
+	assert(m_smgr);
+}
+
+ClientEnvironment::~ClientEnvironment()
+{
+	// delete active objects
+	for(core::map<u16, ClientActiveObject*>::Iterator
+			i = m_active_objects.getIterator();
+			i.atEnd()==false; i++)
+	{
+		delete i.getNode()->getValue();
+	}
+
+	// Drop/delete map
+	m_map->drop();
+}
+
+void ClientEnvironment::addPlayer(Player *player)
+{
+	DSTACK(__FUNCTION_NAME);
+	/*
+		It is a failure if player is local and there already is a local
+		player
+	*/
+	assert(!(player->isLocal() == true && getLocalPlayer() != NULL));
+
+	Environment::addPlayer(player);
+}
+
+LocalPlayer * ClientEnvironment::getLocalPlayer()
+{
+	for(core::list<Player*>::Iterator i = m_players.begin();
+			i != m_players.end(); i++)
+	{
+		Player *player = *i;
+		if(player->isLocal())
+			return (LocalPlayer*)player;
+	}
+	return NULL;
+}
+
+void ClientEnvironment::step(float dtime)
+{
+	DSTACK(__FUNCTION_NAME);
+
+	// Get some settings
+	bool free_move = g_settings.getBool("free_move");
+	bool footprints = g_settings.getBool("footprints");
+
+	{
+		//TimeTaker timer("Client m_map->timerUpdate()", g_device);
+		m_map->timerUpdate(dtime);
+	}
+
+	/*
+		Get the speed the player is going
+	*/
+	f32 player_speed = 0.001; // just some small value
+	LocalPlayer *lplayer = getLocalPlayer();
+	if(lplayer)
+		player_speed = lplayer->getSpeed().getLength();
+	
+	/*
+		Maximum position increment
+	*/
+	//f32 position_max_increment = 0.05*BS;
+	f32 position_max_increment = 0.1*BS;
+
+	// Maximum time increment (for collision detection etc)
+	// time = distance / speed
+	f32 dtime_max_increment = position_max_increment / player_speed;
+	
+	// Maximum time increment is 10ms or lower
+	if(dtime_max_increment > 0.01)
+		dtime_max_increment = 0.01;
+	
+	// Don't allow overly huge dtime
+	if(dtime > 0.5)
+		dtime = 0.5;
+	
+	f32 dtime_downcount = dtime;
+
+	/*
+		Stuff that has a maximum time increment
+	*/
+
+	u32 loopcount = 0;
+	do
+	{
+		loopcount++;
+
+		f32 dtime_part;
+		if(dtime_downcount > dtime_max_increment)
+			dtime_part = dtime_max_increment;
+		else
+			dtime_part = dtime;
+		dtime_downcount -= dtime_part;
+		
+		/*
+			Handle local player
+		*/
+		
+		{
+			Player *player = getLocalPlayer();
+
+			v3f playerpos = player->getPosition();
+			
+			// Apply physics
+			if(free_move == false)
+			{
+				// Gravity
+				v3f speed = player->getSpeed();
+				if(player->swimming_up == false)
+					speed.Y -= 9.81 * BS * dtime_part * 2;
+
+				// Water resistance
+				if(player->in_water_stable || player->in_water)
+				{
+					f32 max_down = 2.0*BS;
+					if(speed.Y < -max_down) speed.Y = -max_down;
+
+					f32 max = 2.5*BS;
+					if(speed.getLength() > max)
+					{
+						speed = speed / speed.getLength() * max;
+					}
+				}
+
+				player->setSpeed(speed);
+			}
+
+			/*
+				Move the player.
+				This also does collision detection.
+			*/
+			player->move(dtime_part, *m_map, position_max_increment);
+		}
+	}
+	while(dtime_downcount > 0.001);
+		
+	//std::cout<<"Looped "<<loopcount<<" times."<<std::endl;
+	
+	/*
+		Stuff that can be done in an arbitarily large dtime
+	*/
+	for(core::list<Player*>::Iterator i = m_players.begin();
+			i != m_players.end(); i++)
+	{
+		Player *player = *i;
+		v3f playerpos = player->getPosition();
+		
+		/*
+			Handle non-local players
+		*/
+		if(player->isLocal() == false)
+		{
+			// Move
+			player->move(dtime, *m_map, 100*BS);
+
+			// Update lighting on remote players on client
+			u8 light = LIGHT_MAX;
+			try{
+				// Get node at head
+				v3s16 p = floatToInt(playerpos + v3f(0,BS+BS/2,0), BS);
+				MapNode n = m_map->getNode(p);
+				light = n.getLightBlend(m_daynight_ratio);
+			}
+			catch(InvalidPositionException &e) {}
+			player->updateLight(light);
+		}
+		
+		/*
+			Add footsteps to grass
+		*/
+		if(footprints)
+		{
+			// Get node that is at BS/4 under player
+			v3s16 bottompos = floatToInt(playerpos + v3f(0,-BS/4,0), BS);
+			try{
+				MapNode n = m_map->getNode(bottompos);
+				if(n.d == CONTENT_GRASS)
+				{
+					n.d = CONTENT_GRASS_FOOTSTEPS;
+					m_map->setNode(bottompos, n);
+					// Update mesh on client
+					if(m_map->mapType() == MAPTYPE_CLIENT)
+					{
+						v3s16 p_blocks = getNodeBlockPos(bottompos);
+						MapBlock *b = m_map->getBlockNoCreate(p_blocks);
+						b->updateMesh(m_daynight_ratio);
+					}
+				}
+			}
+			catch(InvalidPositionException &e)
+			{
+			}
+		}
+	}
+	
+	/*
+		Step active objects
+	*/
+	for(core::map<u16, ClientActiveObject*>::Iterator
+			i = m_active_objects.getIterator();
+			i.atEnd()==false; i++)
+	{
+		ClientActiveObject* obj = i.getNode()->getValue();
+		// Step object
+		obj->step(dtime);
+	}
+}
+
+void ClientEnvironment::updateMeshes(v3s16 blockpos)
 {
 	m_map->updateMeshes(blockpos, m_daynight_ratio);
 }
 
-void Environment::expireMeshes(bool only_daynight_diffed)
+void ClientEnvironment::expireMeshes(bool only_daynight_diffed)
 {
 	m_map->expireMeshes(only_daynight_diffed);
 }
-#endif
 
-void Environment::setDayNightRatio(u32 r)
+ClientActiveObject* ClientEnvironment::getActiveObject(u16 id)
 {
-	m_daynight_ratio = r;
+	core::map<u16, ClientActiveObject*>::Node *n;
+	n = m_active_objects.find(id);
+	if(n == NULL)
+		return NULL;
+	return n->getValue();
 }
 
-u32 Environment::getDayNightRatio()
+bool isFreeClientActiveObjectId(u16 id,
+		core::map<u16, ClientActiveObject*> &objects)
 {
-	return m_daynight_ratio;
+	if(id == 0)
+		return false;
+	
+	for(core::map<u16, ClientActiveObject*>::Iterator
+			i = objects.getIterator();
+			i.atEnd()==false; i++)
+	{
+		if(i.getNode()->getKey() == id)
+			return false;
+	}
+	return true;
+}
+
+u16 getFreeClientActiveObjectId(
+		core::map<u16, ClientActiveObject*> &objects)
+{
+	u16 new_id = 1;
+	for(;;)
+	{
+		if(isFreeClientActiveObjectId(new_id, objects))
+			return new_id;
+		
+		if(new_id == 65535)
+			return 0;
+
+		new_id++;
+	}
 }
 
+u16 ClientEnvironment::addActiveObject(ClientActiveObject *object)
+{
+	assert(object);
+	if(object->getId() == 0)
+	{
+		u16 new_id = getFreeClientActiveObjectId(m_active_objects);
+		if(new_id == 0)
+		{
+			dstream<<"WARNING: ClientEnvironment::addActiveObject(): "
+					<<"no free ids available"<<std::endl;
+			delete object;
+			return 0;
+		}
+		object->setId(new_id);
+	}
+	if(isFreeClientActiveObjectId(object->getId(), m_active_objects) == false)
+	{
+		dstream<<"WARNING: ClientEnvironment::addActiveObject(): "
+				<<"id is not free ("<<object->getId()<<")"<<std::endl;
+		delete object;
+		return 0;
+	}
+	dstream<<"INGO: ClientEnvironment::addActiveObject(): "
+			<<"added (id="<<object->getId()<<")"<<std::endl;
+	m_active_objects.insert(object->getId(), object);
+	object->addToScene(m_smgr);
+	return object->getId();
+}
+
+void ClientEnvironment::addActiveObject(u16 id, u8 type)
+{
+	ClientActiveObject* obj = ClientActiveObject::create(type);
+	if(obj == NULL)
+	{
+		dstream<<"WARNING: ClientEnvironment::addActiveObject(): "
+				<<"id="<<id<<" type="<<type<<": Couldn't create object"
+				<<std::endl;
+		return;
+	}
+	
+	obj->setId(id);
+
+	addActiveObject(obj);
+}
+
+void ClientEnvironment::removeActiveObject(u16 id)
+{
+	dstream<<"ClientEnvironment::removeActiveObject(): "
+			<<"id="<<id<<std::endl;
+	ClientActiveObject* obj = getActiveObject(id);
+	if(obj == NULL)
+	{
+		dstream<<"WARNING: ClientEnvironment::removeActiveObject(): "
+				<<"id="<<id<<" not found"<<std::endl;
+		return;
+	}
+	obj->removeFromScene();
+	delete obj;
+	m_active_objects.remove(id);
+}
+
+void ClientEnvironment::processActiveObjectMessage(u16 id,
+		const std::string &data)
+{
+	ClientActiveObject* obj = getActiveObject(id);
+	if(obj == NULL)
+	{
+		dstream<<"WARNING: ClientEnvironment::processActiveObjectMessage():"
+				<<" got message for id="<<id<<", which doesn't exist."
+				<<std::endl;
+		return;
+	}
+	obj->processMessage(data);
+}
+
+#endif // #ifndef SERVER
+
+
diff --git a/src/environment.h b/src/environment.h
index dfc60673b66698c1b1bc82ff47301079b97cdd66..1a786af23e7a5538c2a33320c1fde85a6f6e0e9f 100644
--- a/src/environment.h
+++ b/src/environment.h
@@ -26,7 +26,8 @@ with this program; if not, write to the Free Software Foundation, Inc.,
 	- The map
 	- Players
 	- Other objects
-	- The current time in the game, etc.
+	- The current time in the game (actually it only contains the brightness)
+	- etc.
 */
 
 #include <list>
@@ -39,51 +40,175 @@ class Environment
 {
 public:
 	// Environment will delete the map passed to the constructor
-	Environment(Map *map, std::ostream &dout);
-	~Environment();
+	Environment();
+	virtual ~Environment();
+
 	/*
-		This can do anything to the environment, such as removing
-		timed-out players.
-		Also updates Map's timers.
+		Step everything in environment.
+		- Move players
+		- Step mobs
+		- Run timers of map
 	*/
-	void step(f32 dtime);
+	virtual void step(f32 dtime) = 0;
 
-	Map & getMap();
+	virtual Map & getMap() = 0;
 
-	/*
-		Environment deallocates players after use.
-	*/
-	void addPlayer(Player *player);
+	virtual void addPlayer(Player *player);
 	void removePlayer(u16 peer_id);
-#ifndef SERVER
-	LocalPlayer * getLocalPlayer();
-#endif
 	Player * getPlayer(u16 peer_id);
 	Player * getPlayer(const char *name);
 	core::list<Player*> getPlayers();
 	core::list<Player*> getPlayers(bool ignore_disconnected);
 	void printPlayers(std::ostream &o);
 	
+	void setDayNightRatio(u32 r);
+	u32 getDayNightRatio();
+
+protected:
+	// peer_ids in here should be unique, except that there may be many 0s
+	core::list<Player*> m_players;
+	// Brightness
+	u32 m_daynight_ratio;
+};
+
+/*
+	The server-side environment.
+
+	This is not thread-safe. Server uses an environment mutex.
+*/
+
+#include "serverobject.h"
+
+class ServerEnvironment : public Environment
+{
+public:
+	ServerEnvironment(ServerMap *map);
+	~ServerEnvironment();
+
+	Map & getMap()
+	{
+		return *m_map;
+	}
+
+	ServerMap & getServerMap()
+	{
+		return *m_map;
+	}
+
+	void step(f32 dtime);
+
 	void serializePlayers(const std::string &savedir);
-	// This loads players as ServerRemotePlayers
 	void deSerializePlayers(const std::string &savedir);
 
+	/*
+		ActiveObjects
+	*/
+
+	ServerActiveObject* getActiveObject(u16 id);
+
+	/*
+		Adds an active object to the environment.
+		Environment handles deletion of object.
+		Object may be deleted by environment immediately.
+		If id of object is 0, assigns a free id to it.
+		Returns the id of the object.
+		Returns 0 if not added and thus deleted.
+	*/
+	u16 addActiveObject(ServerActiveObject *object);
+	
+	/*
+		Finds out what new objects have been added to
+		inside a radius around a position
+	*/
+	void getAddedActiveObjects(v3s16 pos, s16 radius,
+			core::map<u16, bool> &current_objects,
+			core::map<u16, bool> &added_objects);
+
+	/*
+		Finds out what new objects have been removed from
+		inside a radius around a position
+	*/
+	void getRemovedActiveObjects(v3s16 pos, s16 radius,
+			core::map<u16, bool> &current_objects,
+			core::map<u16, bool> &removed_objects);
+	
+	/*
+		Gets the next message emitted by some active object.
+		Returns a message with id=0 if no messages are available.
+	*/
+	ActiveObjectMessage getActiveObjectMessage();
+	
+private:
+	ServerMap *m_map;
+	core::map<u16, ServerActiveObject*> m_active_objects;
+	Queue<ActiveObjectMessage> m_active_object_messages;
+	float m_random_spawn_timer;
+};
+
 #ifndef SERVER
+
+#include "clientobject.h"
+
+/*
+	The client-side environment.
+
+	This is not thread-safe.
+	Must be called from main (irrlicht) thread (uses the SceneManager)
+	Client uses an environment mutex.
+*/
+
+class ClientEnvironment : public Environment
+{
+public:
+	ClientEnvironment(ClientMap *map, scene::ISceneManager *smgr);
+	~ClientEnvironment();
+
+	Map & getMap()
+	{
+		return *m_map;
+	}
+
+	ClientMap & getClientMap()
+	{
+		return *m_map;
+	}
+
+	void step(f32 dtime);
+
+	virtual void addPlayer(Player *player);
+	LocalPlayer * getLocalPlayer();
+
 	void updateMeshes(v3s16 blockpos);
 	void expireMeshes(bool only_daynight_diffed);
-#endif
-	void setDayNightRatio(u32 r);
-	u32 getDayNightRatio();
+
+	/*
+		ActiveObjects
+	*/
+	
+	ClientActiveObject* getActiveObject(u16 id);
+
+	/*
+		Adds an active object to the environment.
+		Environment handles deletion of object.
+		Object may be deleted by environment immediately.
+		If id of object is 0, assigns a free id to it.
+		Returns the id of the object.
+		Returns 0 if not added and thus deleted.
+	*/
+	u16 addActiveObject(ClientActiveObject *object);
+
+	void addActiveObject(u16 id, u8 type);
+	void removeActiveObject(u16 id);
+
+	void processActiveObjectMessage(u16 id, const std::string &data);
 
 private:
-	Map *m_map;
-	// peer_ids in here should be unique, except that there may be
-	// many 0s
-	core::list<Player*> m_players;
-	// Debug output goes here
-	std::ostream &m_dout;
-	u32 m_daynight_ratio;
+	ClientMap *m_map;
+	scene::ISceneManager *m_smgr;
+	core::map<u16, ClientActiveObject*> m_active_objects;
 };
 
 #endif
 
+#endif
+
diff --git a/src/jthread/jmutex.h b/src/jthread/jmutex.h
index 39bd95f02281318aaad2b557d1feabb67da74fd6..9ce013096f8a3817417d079003f2ed8319b15d5a 100644
--- a/src/jthread/jmutex.h
+++ b/src/jthread/jmutex.h
@@ -35,7 +35,7 @@
 	#endif // _WIN32_WCE
 	#include <winsock2.h>
 	#include <windows.h>
-	
+	// CriticalSection is way faster than the alternative
 	#define JMUTEX_CRITICALSECTION
 #else // using pthread
 	#include <pthread.h>
diff --git a/src/main.cpp b/src/main.cpp
index 2754c324b930cf7fe36644d036e45f3684bc30fc..f0bc6d7a249578abb2038ea40f029b73dbcc8b3a 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -160,32 +160,23 @@ TODO: Make fetching sector's blocks more efficient when rendering
 
 TODO: Flowing water animation
 
-NOTE(FIXED): A lock condition is possible:
-	1) MapBlock::updateMesh() is called from client asynchronously:
-	   - AsyncProcessData() -> Map::updateMeshes()
-	2) Asynchronous locks m_temp_mods_mutex
-	3) MapBlock::updateMesh() is called from client synchronously:
-	   - Client::step() -> Environment::step()
-	4) Synchronous starts waiting for m_temp_mods_mutex
-	5) Asynchronous calls getTexture, which starts waiting for main thread
-
 Configuration:
 --------------
 
-TODO: Make the video backend selectable
-
 Client:
 -------
 
 TODO: Untie client network operations from framerate
       - Needs some input queues or something
 
-TODO: Make morning and evening transition more smooth and maybe shorter
+SUGG: Make morning and evening transition more smooth and maybe shorter
 
-TODO: Don't update all meshes always on single node changes, but
+SUGG: Don't update all meshes always on single node changes, but
       check which ones should be updated
 	  - implement Map::updateNodeMeshes()
 
+TODO: Remove IrrlichtWrapper
+
 Server:
 -------
 
@@ -194,16 +185,13 @@ TODO: When player dies, throw items on map
 TODO: Make an option to the server to disable building and digging near
       the starting position
 
-TODO: Save players with inventories to disk
-TODO: Players to be saved as text in map/players/<name>
-
 TODO: Copy the text of the last picked sign to inventory in creative
       mode
 
 TODO: Check what goes wrong with caching map to disk (Kray)
       - Nothing?
 
-TODO: When server sees that client is removing an inexistent block to
+TODO: When server sees that client is removing an inexistent block in
       an existent position, resend the MapBlock.
 
 FIXME: Server went into some infinite PeerNotFoundException loop
@@ -237,6 +225,11 @@ Block object server side:
 	    - TODO: For incoming blocks, time difference is calculated and
 	      objects are stepped according to it.
 
+- When an active object goes far from a player, either delete
+  it or store it statically.
+- When a statically stored active object comes near a player,
+  recreate the active object
+
 Map:
 ----
 
@@ -345,18 +338,18 @@ Doing now (most important at the top):
 #include <fstream>
 #include <jmutexautolock.h>
 #include <locale.h>
+#include "main.h"
 #include "common_irrlicht.h"
 #include "debug.h"
 #include "map.h"
 #include "player.h"
-#include "main.h"
 #include "test.h"
-#include "environment.h"
+//#include "environment.h"
 #include "server.h"
 #include "client.h"
-#include "serialization.h"
+//#include "serialization.h"
 #include "constants.h"
-#include "strfnd.h"
+//#include "strfnd.h"
 #include "porting.h"
 #include "irrlichtwrapper.h"
 #include "gettime.h"
@@ -905,8 +898,12 @@ class RandomInputHandler : public InputHandler
 public:
 	RandomInputHandler()
 	{
+		leftdown = false;
+		rightdown = false;
 		leftclicked = false;
 		rightclicked = false;
+		leftreleased = false;
+		rightreleased = false;
 		for(u32 i=0; i<KEY_KEY_CODES_COUNT; ++i)
 			keydown[i] = false;
 	}
@@ -925,11 +922,11 @@ class RandomInputHandler : public InputHandler
 
 	virtual bool getLeftState()
 	{
-		return false;
+		return leftdown;
 	}
 	virtual bool getRightState()
 	{
-		return false;
+		return rightdown;
 	}
 
 	virtual bool getLeftClicked()
@@ -951,37 +948,23 @@ class RandomInputHandler : public InputHandler
 
 	virtual bool getLeftReleased()
 	{
-		return false;
+		return leftreleased;
 	}
 	virtual bool getRightReleased()
 	{
-		return false;
+		return rightreleased;
 	}
 	virtual void resetLeftReleased()
 	{
+		leftreleased = false;
 	}
 	virtual void resetRightReleased()
 	{
+		rightreleased = false;
 	}
 
 	virtual void step(float dtime)
 	{
-		{
-			static float counter1 = 0;
-			counter1 -= dtime;
-			if(counter1 < 0.0)
-			{
-				counter1 = 0.1*Rand(1,10);
-				/*if(g_selected_material < USEFUL_CONTENT_COUNT-1)
-					g_selected_material++;
-				else
-					g_selected_material = 0;*/
-				if(g_selected_item < PLAYER_INVENTORY_SIZE-1)
-					g_selected_item++;
-				else
-					g_selected_item = 0;
-			}
-		}
 		{
 			static float counter1 = 0;
 			counter1 -= dtime;
@@ -1033,7 +1016,11 @@ class RandomInputHandler : public InputHandler
 			if(counter1 < 0.0)
 			{
 				counter1 = 0.1*Rand(1, 30);
-				leftclicked = true;
+				leftdown = !leftdown;
+				if(leftdown)
+					leftclicked = true;
+				if(!leftdown)
+					leftreleased = true;
 			}
 		}
 		{
@@ -1041,8 +1028,12 @@ class RandomInputHandler : public InputHandler
 			counter1 -= dtime;
 			if(counter1 < 0.0)
 			{
-				counter1 = 0.1*Rand(1, 20);
-				rightclicked = true;
+				counter1 = 0.1*Rand(1, 15);
+				rightdown = !rightdown;
+				if(rightdown)
+					rightclicked = true;
+				if(!rightdown)
+					rightreleased = true;
 			}
 		}
 		mousepos += mousespeed;
@@ -1056,8 +1047,12 @@ class RandomInputHandler : public InputHandler
 	bool keydown[KEY_KEY_CODES_COUNT];
 	v2s32 mousepos;
 	v2s32 mousespeed;
+	bool leftdown;
+	bool rightdown;
 	bool leftclicked;
 	bool rightclicked;
+	bool leftreleased;
+	bool rightreleased;
 };
 
 void updateViewingRange(f32 frametime_in, Client *client)
@@ -2486,11 +2481,9 @@ int main(int argc, char *argv[])
 		camera->setTarget(camera_position + camera_direction * 100.0);
 
 		if(FIELD_OF_VIEW_TEST){
-			//client.m_env.getMap().updateCamera(v3f(0,0,0), v3f(0,0,1));
 			client.updateCamera(v3f(0,0,0), v3f(0,0,1));
 		}
 		else{
-			//client.m_env.getMap().updateCamera(camera_position, camera_direction);
 			//TimeTaker timer("client.updateCamera");
 			client.updateCamera(camera_position, camera_direction);
 		}
@@ -2585,7 +2578,7 @@ int main(int argc, char *argv[])
 		core::aabbox3d<f32> nodehilightbox;
 		f32 mindistance = BS * 1001;
 		
-		v3s16 pos_i = floatToInt(player_position);
+		v3s16 pos_i = floatToInt(player_position, BS);
 
 		/*std::cout<<"pos_i=("<<pos_i.X<<","<<pos_i.Y<<","<<pos_i.Z<<")"
 				<<std::endl;*/
@@ -2615,7 +2608,7 @@ int main(int argc, char *argv[])
 			}
 
 			v3s16 np(x,y,z);
-			v3f npf = intToFloat(np);
+			v3f npf = intToFloat(np, BS);
 			
 			f32 d = 0.01;
 			
@@ -2723,7 +2716,7 @@ int main(int argc, char *argv[])
 							const float d = 0.502;
 							core::aabbox3d<f32> nodebox
 									(-BS*d, -BS*d, -BS*d, BS*d, BS*d, BS*d);
-							v3f nodepos_f = intToFloat(nodepos);
+							v3f nodepos_f = intToFloat(nodepos, BS);
 							nodebox.MinEdge += nodepos_f;
 							nodebox.MaxEdge += nodepos_f;
 							nodehilightbox = nodebox;
diff --git a/src/map.cpp b/src/map.cpp
index 159682696b0b1a792b88c489f53d16460771afcf..c92039664dca79cf2fdfb2297f731ae9c5e5de02 100644
--- a/src/map.cpp
+++ b/src/map.cpp
@@ -1221,95 +1221,6 @@ void Map::removeNodeAndUpdate(v3s16 p,
 	}
 }
 
-#ifndef SERVER
-void Map::expireMeshes(bool only_daynight_diffed)
-{
-	TimeTaker timer("expireMeshes()");
-
-	core::map<v2s16, MapSector*>::Iterator si;
-	si = m_sectors.getIterator();
-	for(; si.atEnd() == false; si++)
-	{
-		MapSector *sector = si.getNode()->getValue();
-
-		core::list< MapBlock * > sectorblocks;
-		sector->getBlocks(sectorblocks);
-		
-		core::list< MapBlock * >::Iterator i;
-		for(i=sectorblocks.begin(); i!=sectorblocks.end(); i++)
-		{
-			MapBlock *block = *i;
-
-			if(only_daynight_diffed && dayNightDiffed(block->getPos()) == false)
-			{
-				continue;
-			}
-			
-			{
-				JMutexAutoLock lock(block->mesh_mutex);
-				if(block->mesh != NULL)
-				{
-					/*block->mesh->drop();
-					block->mesh = NULL;*/
-					block->setMeshExpired(true);
-				}
-			}
-		}
-	}
-}
-
-void Map::updateMeshes(v3s16 blockpos, u32 daynight_ratio)
-{
-	assert(mapType() == MAPTYPE_CLIENT);
-
-	try{
-		v3s16 p = blockpos + v3s16(0,0,0);
-		MapBlock *b = getBlockNoCreate(p);
-		b->updateMesh(daynight_ratio);
-	}
-	catch(InvalidPositionException &e){}
-	// Leading edge
-	try{
-		v3s16 p = blockpos + v3s16(-1,0,0);
-		MapBlock *b = getBlockNoCreate(p);
-		b->updateMesh(daynight_ratio);
-	}
-	catch(InvalidPositionException &e){}
-	try{
-		v3s16 p = blockpos + v3s16(0,-1,0);
-		MapBlock *b = getBlockNoCreate(p);
-		b->updateMesh(daynight_ratio);
-	}
-	catch(InvalidPositionException &e){}
-	try{
-		v3s16 p = blockpos + v3s16(0,0,-1);
-		MapBlock *b = getBlockNoCreate(p);
-		b->updateMesh(daynight_ratio);
-	}
-	catch(InvalidPositionException &e){}
-	/*// Trailing edge
-	try{
-		v3s16 p = blockpos + v3s16(1,0,0);
-		MapBlock *b = getBlockNoCreate(p);
-		b->updateMesh(daynight_ratio);
-	}
-	catch(InvalidPositionException &e){}
-	try{
-		v3s16 p = blockpos + v3s16(0,1,0);
-		MapBlock *b = getBlockNoCreate(p);
-		b->updateMesh(daynight_ratio);
-	}
-	catch(InvalidPositionException &e){}
-	try{
-		v3s16 p = blockpos + v3s16(0,0,1);
-		MapBlock *b = getBlockNoCreate(p);
-		b->updateMesh(daynight_ratio);
-	}
-	catch(InvalidPositionException &e){}*/
-}
-
-#endif
-
 bool Map::dayNightDiffed(v3s16 blockpos)
 {
 	try{
@@ -4371,7 +4282,7 @@ MapBlock * ServerMap::generateBlock(
 			//if(!is_ground_content(block->getNode(cp).d))
 			if(1)
 			{
-				RatObject *obj = new RatObject(NULL, -1, intToFloat(cp));
+				RatObject *obj = new RatObject(NULL, -1, intToFloat(cp, BS));
 				block->addObject(obj);
 			}
 		}
@@ -5667,6 +5578,92 @@ bool ClientMap::clearTempMod(v3s16 p,
 	return changed;
 }
 
+void ClientMap::expireMeshes(bool only_daynight_diffed)
+{
+	TimeTaker timer("expireMeshes()");
+
+	core::map<v2s16, MapSector*>::Iterator si;
+	si = m_sectors.getIterator();
+	for(; si.atEnd() == false; si++)
+	{
+		MapSector *sector = si.getNode()->getValue();
+
+		core::list< MapBlock * > sectorblocks;
+		sector->getBlocks(sectorblocks);
+		
+		core::list< MapBlock * >::Iterator i;
+		for(i=sectorblocks.begin(); i!=sectorblocks.end(); i++)
+		{
+			MapBlock *block = *i;
+
+			if(only_daynight_diffed && dayNightDiffed(block->getPos()) == false)
+			{
+				continue;
+			}
+			
+			{
+				JMutexAutoLock lock(block->mesh_mutex);
+				if(block->mesh != NULL)
+				{
+					/*block->mesh->drop();
+					block->mesh = NULL;*/
+					block->setMeshExpired(true);
+				}
+			}
+		}
+	}
+}
+
+void ClientMap::updateMeshes(v3s16 blockpos, u32 daynight_ratio)
+{
+	assert(mapType() == MAPTYPE_CLIENT);
+
+	try{
+		v3s16 p = blockpos + v3s16(0,0,0);
+		MapBlock *b = getBlockNoCreate(p);
+		b->updateMesh(daynight_ratio);
+	}
+	catch(InvalidPositionException &e){}
+	// Leading edge
+	try{
+		v3s16 p = blockpos + v3s16(-1,0,0);
+		MapBlock *b = getBlockNoCreate(p);
+		b->updateMesh(daynight_ratio);
+	}
+	catch(InvalidPositionException &e){}
+	try{
+		v3s16 p = blockpos + v3s16(0,-1,0);
+		MapBlock *b = getBlockNoCreate(p);
+		b->updateMesh(daynight_ratio);
+	}
+	catch(InvalidPositionException &e){}
+	try{
+		v3s16 p = blockpos + v3s16(0,0,-1);
+		MapBlock *b = getBlockNoCreate(p);
+		b->updateMesh(daynight_ratio);
+	}
+	catch(InvalidPositionException &e){}
+	/*// Trailing edge
+	try{
+		v3s16 p = blockpos + v3s16(1,0,0);
+		MapBlock *b = getBlockNoCreate(p);
+		b->updateMesh(daynight_ratio);
+	}
+	catch(InvalidPositionException &e){}
+	try{
+		v3s16 p = blockpos + v3s16(0,1,0);
+		MapBlock *b = getBlockNoCreate(p);
+		b->updateMesh(daynight_ratio);
+	}
+	catch(InvalidPositionException &e){}
+	try{
+		v3s16 p = blockpos + v3s16(0,0,1);
+		MapBlock *b = getBlockNoCreate(p);
+		b->updateMesh(daynight_ratio);
+	}
+	catch(InvalidPositionException &e){}*/
+}
+
 void ClientMap::PrintInfo(std::ostream &out)
 {
 	out<<"ClientMap: ";
diff --git a/src/map.h b/src/map.h
index 60cfd698b76c2f686368d4617f57e5798cf9fcc4..adff82db06e6fcb43ab23ea73bbd8a24d9d27947 100644
--- a/src/map.h
+++ b/src/map.h
@@ -33,7 +33,6 @@ with this program; if not, write to the Free Software Foundation, Inc.,
 #endif
 
 #include "common_irrlicht.h"
-//#include "heightmap.h"
 #include "mapnode.h"
 #include "mapblock.h"
 #include "mapsector.h"
@@ -61,7 +60,10 @@ class Map : public NodeContainer
 	{
 		return MAPTYPE_BASE;
 	}
-
+	
+	/*
+		Drop (client) or delete (server) the map.
+	*/
 	virtual void drop()
 	{
 		delete this;
@@ -211,19 +213,6 @@ class Map : public NodeContainer
 	void removeNodeAndUpdate(v3s16 p,
 			core::map<v3s16, MapBlock*> &modified_blocks);
 	
-#ifndef SERVER
-	void expireMeshes(bool only_daynight_diffed);
-	
-	/*
-		Update the faces of the given block and blocks on the
-		leading edge.
-	*/
-	void updateMeshes(v3s16 blockpos, u32 daynight_ratio);
-	
-	// Update meshes that touch the node
-	//void updateNodeMeshes(v3s16 nodepos, u32 daynight_ratio);
-#endif
-
 	/*
 		Takes the blocks at the edges into account
 	*/
@@ -628,6 +617,17 @@ class ClientMap : public Map, public scene::ISceneNode
 	// Efficient implementation needs a cache of TempMods
 	//void clearTempMods();
 
+	void expireMeshes(bool only_daynight_diffed);
+	
+	/*
+		Update the faces of the given block and blocks on the
+		leading edge.
+	*/
+	void updateMeshes(v3s16 blockpos, u32 daynight_ratio);
+	
+	// Update meshes that touch the node
+	//void updateNodeMeshes(v3s16 nodepos, u32 daynight_ratio);
+
 	// For debug printing
 	virtual void PrintInfo(std::ostream &out);
 	
diff --git a/src/mapblock.cpp b/src/mapblock.cpp
index a1e3c6694c5138b2d378677d1e11f0b7d2677472..d489ec8ac11d47137c7ddc60e93bec895d5e464f 100644
--- a/src/mapblock.cpp
+++ b/src/mapblock.cpp
@@ -822,7 +822,7 @@ void MapBlock::updateMesh(u32 daynight_ratio)
 				if(dir == v3s16(0,1,0))
 					vertices[i].Pos.rotateXZBy(-45);
 
-				vertices[i].Pos += intToFloat(p + getPosRelative());
+				vertices[i].Pos += intToFloat(p + getPosRelative(), BS);
 			}
 
 			// Set material
@@ -1066,7 +1066,7 @@ void MapBlock::updateMesh(u32 daynight_ratio)
 					if(dir == v3s16(1,0,-0))
 						vertices[j].Pos.rotateXZBy(-90);
 
-					vertices[j].Pos += intToFloat(p + getPosRelative());
+					vertices[j].Pos += intToFloat(p + getPosRelative(), BS);
 				}
 
 				u16 indices[] = {0,1,2,2,3,0};
@@ -1105,7 +1105,7 @@ void MapBlock::updateMesh(u32 daynight_ratio)
 					//vertices[i].Pos.Y += neighbor_levels[v3s16(0,0,0)];
 					s32 j = corner_resolve[i];
 					vertices[i].Pos.Y += corner_levels[j];
-					vertices[i].Pos += intToFloat(p + getPosRelative());
+					vertices[i].Pos += intToFloat(p + getPosRelative(), BS);
 				}
 
 				u16 indices[] = {0,1,2,2,3,0};
@@ -1155,7 +1155,7 @@ void MapBlock::updateMesh(u32 daynight_ratio)
 			for(s32 i=0; i<4; i++)
 			{
 				vertices[i].Pos.Y += (-0.5+node_water_level)*BS;
-				vertices[i].Pos += intToFloat(p + getPosRelative());
+				vertices[i].Pos += intToFloat(p + getPosRelative(), BS);
 			}
 
 			u16 indices[] = {0,1,2,2,3,0};
@@ -1222,7 +1222,7 @@ void MapBlock::updateMesh(u32 daynight_ratio)
 
 				for(u16 i=0; i<4; i++)
 				{
-					vertices[i].Pos += intToFloat(p + getPosRelative());
+					vertices[i].Pos += intToFloat(p + getPosRelative(), BS);
 				}
 
 				u16 indices[] = {0,1,2,2,3,0};
@@ -1596,7 +1596,7 @@ void MapBlock::stepObjects(float dtime, bool server, u32 daynight_ratio)
 				if(getNode(p).d == CONTENT_AIR
 						&& getNode(p).getLightBlend(daynight_ratio) <= 11)
 				{
-					RatObject *obj = new RatObject(NULL, -1, intToFloat(p));
+					RatObject *obj = new RatObject(NULL, -1, intToFloat(p, BS));
 					addObject(obj);
 				}
 			}
diff --git a/src/mapblockobject.cpp b/src/mapblockobject.cpp
index ab12afc8e12a75aad035c6524668ea843551167c..ff58fd04555a4e29c0a24ce65d26ea0fee12067e 100644
--- a/src/mapblockobject.cpp
+++ b/src/mapblockobject.cpp
@@ -35,7 +35,7 @@ v3f MapBlockObject::getAbsolutePos()
 		return m_pos;
 	
 	// getPosRelative gets nodepos relative to map origin
-	v3f blockpos = intToFloat(m_block->getPosRelative());
+	v3f blockpos = intToFloat(m_block->getPosRelative(), BS);
 	return blockpos + m_pos;
 }
 
@@ -55,7 +55,7 @@ v3f MovingObject::getAbsoluteShowPos()
 		return m_pos;
 	
 	// getPosRelative gets nodepos relative to map origin
-	v3f blockpos = intToFloat(m_block->getPosRelative());
+	v3f blockpos = intToFloat(m_block->getPosRelative(), BS);
 	return blockpos + m_showpos;
 }
 
@@ -71,7 +71,7 @@ void MovingObject::move(float dtime, v3f acceleration)
 			acceleration.X, acceleration.Y, acceleration.Z
 			);
 	
-	v3s16 oldpos_i = floatToInt(m_pos);
+	v3s16 oldpos_i = floatToInt(m_pos, BS);
 	
 	if(m_block->isValidPosition(oldpos_i) == false)
 	{
@@ -137,7 +137,7 @@ void MovingObject::move(float dtime, v3f acceleration)
 			Collision detection
 		*/
 		
-		v3s16 pos_i = floatToInt(position);
+		v3s16 pos_i = floatToInt(position, BS);
 		
 		// The loop length is limited to the object moving a distance
 		f32 d = (float)BS * 0.15;
@@ -614,7 +614,7 @@ void MapBlockObjectList::update(std::istream &is, u8 version,
 		{
 			u8 light = LIGHT_MAX;
 			try{
-				v3s16 relpos_i = floatToInt(obj->m_pos);
+				v3s16 relpos_i = floatToInt(obj->m_pos, BS);
 				MapNode n = m_block->getNodeParent(relpos_i);
 				light = n.getLightBlend(daynight_ratio);
 			}
@@ -772,7 +772,7 @@ void MapBlockObjectList::step(float dtime, bool server, u32 daynight_ratio)
 				// Update light
 				u8 light = LIGHT_MAX;
 				try{
-					v3s16 relpos_i = floatToInt(obj->m_pos);
+					v3s16 relpos_i = floatToInt(obj->m_pos, BS);
 					MapNode n = m_block->getNodeParent(relpos_i);
 					light = n.getLightBlend(daynight_ratio);
 				}
@@ -824,7 +824,7 @@ void MapBlockObjectList::step(float dtime, bool server, u32 daynight_ratio)
 		{
 			MapBlockObject *obj = i.getNode()->getValue();
 
-			v3s16 pos_i = floatToInt(obj->m_pos);
+			v3s16 pos_i = floatToInt(obj->m_pos, BS);
 
 			if(m_block->isValidPosition(pos_i))
 			{
@@ -871,7 +871,7 @@ bool MapBlockObjectList::wrapObject(MapBlockObject *object)
 	// Calculate blockpos on map
 	v3s16 oldblock_pos_i_on_map = m_block->getPosRelative();
 	v3f pos_f_on_oldblock = object->m_pos;
-	v3s16 pos_i_on_oldblock = floatToInt(pos_f_on_oldblock);
+	v3s16 pos_i_on_oldblock = floatToInt(pos_f_on_oldblock, BS);
 	v3s16 pos_i_on_map = pos_i_on_oldblock + oldblock_pos_i_on_map;
 	v3s16 pos_blocks_on_map = getNodeBlockPos(pos_i_on_map);
 
@@ -905,9 +905,9 @@ bool MapBlockObjectList::wrapObject(MapBlockObject *object)
 	}
 	
 	// Calculate position on new block
-	v3f oldblock_pos_f_on_map = intToFloat(oldblock_pos_i_on_map);
+	v3f oldblock_pos_f_on_map = intToFloat(oldblock_pos_i_on_map, BS);
 	v3s16 newblock_pos_i_on_map = newblock->getPosRelative();
-	v3f newblock_pos_f_on_map = intToFloat(newblock_pos_i_on_map);
+	v3f newblock_pos_f_on_map = intToFloat(newblock_pos_i_on_map, BS);
 	v3f pos_f_on_newblock = pos_f_on_oldblock
 			- newblock_pos_f_on_map + oldblock_pos_f_on_map;
 
diff --git a/src/mapnode.h b/src/mapnode.h
index d67b9629e56ed8b27400662a64e1539bd0334fd1..7819d701d03e26fee322f2543ac4afc111be6a5b 100644
--- a/src/mapnode.h
+++ b/src/mapnode.h
@@ -683,32 +683,6 @@ struct MapNode
 	}
 };
 
-/*
-	Returns integer position of the node in given
-	floating point position.
-*/
-inline v3s16 floatToInt(v3f p)
-{
-	v3s16 p2(
-		(p.X + (p.X>0 ? BS/2 : -BS/2))/BS,
-		(p.Y + (p.Y>0 ? BS/2 : -BS/2))/BS,
-		(p.Z + (p.Z>0 ? BS/2 : -BS/2))/BS);
-	return p2;
-}
-
-/*
-	The same thing backwards
-*/
-inline v3f intToFloat(v3s16 p)
-{
-	v3f p2(
-		p.X * BS,
-		p.Y * BS,
-		p.Z * BS
-	);
-	return p2;
-}
-
 
 
 #endif
diff --git a/src/player.cpp b/src/player.cpp
index 9f8a97e7f12a65cfdd234b6415e12fa7b60f402f..07879c21adafdcfb113cef2a865ce4b2d23fd076 100644
--- a/src/player.cpp
+++ b/src/player.cpp
@@ -273,7 +273,7 @@ void LocalPlayer::move(f32 dtime, Map &map, f32 pos_max_d)
 {
 	v3f position = getPosition();
 	v3f oldpos = position;
-	v3s16 oldpos_i = floatToInt(oldpos);
+	v3s16 oldpos_i = floatToInt(oldpos, BS);
 
 	/*std::cout<<"oldpos_i=("<<oldpos_i.X<<","<<oldpos_i.Y<<","
 			<<oldpos_i.Z<<")"<<std::endl;*/
@@ -296,7 +296,7 @@ void LocalPlayer::move(f32 dtime, Map &map, f32 pos_max_d)
 	*/
 	
 	// Player position in nodes
-	v3s16 pos_i = floatToInt(position);
+	v3s16 pos_i = floatToInt(position, BS);
 	
 	/*
 		Check if player is in water (the oscillating value)
@@ -305,13 +305,13 @@ void LocalPlayer::move(f32 dtime, Map &map, f32 pos_max_d)
 		// If in water, the threshold of coming out is at higher y
 		if(in_water)
 		{
-			v3s16 pp = floatToInt(position + v3f(0,BS*0.1,0));
+			v3s16 pp = floatToInt(position + v3f(0,BS*0.1,0), BS);
 			in_water = content_liquid(map.getNode(pp).d);
 		}
 		// If not in water, the threshold of going in is at lower y
 		else
 		{
-			v3s16 pp = floatToInt(position + v3f(0,BS*0.5,0));
+			v3s16 pp = floatToInt(position + v3f(0,BS*0.5,0), BS);
 			in_water = content_liquid(map.getNode(pp).d);
 		}
 	}
@@ -324,7 +324,7 @@ void LocalPlayer::move(f32 dtime, Map &map, f32 pos_max_d)
 		Check if player is in water (the stable value)
 	*/
 	try{
-		v3s16 pp = floatToInt(position + v3f(0,0,0));
+		v3s16 pp = floatToInt(position + v3f(0,0,0), BS);
 		in_water_stable = content_liquid(map.getNode(pp).d);
 	}
 	catch(InvalidPositionException &e)
@@ -363,7 +363,7 @@ void LocalPlayer::move(f32 dtime, Map &map, f32 pos_max_d)
 	if(control.sneak && m_sneak_node_exists)
 	{
 		f32 maxd = 0.5*BS + sneak_max;
-		v3f lwn_f = intToFloat(m_sneak_node);
+		v3f lwn_f = intToFloat(m_sneak_node, BS);
 		position.X = rangelim(position.X, lwn_f.X-maxd, lwn_f.X+maxd);
 		position.Z = rangelim(position.Z, lwn_f.Z-maxd, lwn_f.Z+maxd);
 		
@@ -537,13 +537,13 @@ void LocalPlayer::move(f32 dtime, Map &map, f32 pos_max_d)
 		player is sneaking from, if any.
 	*/
 	{
-		v3s16 pos_i_bottom = floatToInt(position - v3f(0,BS/2,0));
+		v3s16 pos_i_bottom = floatToInt(position - v3f(0,BS/2,0), BS);
 		v2f player_p2df(position.X, position.Z);
 		f32 min_distance_f = 100000.0*BS;
 		// If already seeking from some node, compare to it.
 		/*if(m_sneak_node_exists)
 		{
-			v3f sneaknode_pf = intToFloat(m_sneak_node);
+			v3f sneaknode_pf = intToFloat(m_sneak_node, BS);
 			v2f sneaknode_p2df(sneaknode_pf.X, sneaknode_pf.Z);
 			f32 d_horiz_f = player_p2df.getDistanceFrom(sneaknode_p2df);
 			f32 d_vert_f = fabs(sneaknode_pf.Y + BS*0.5 - position.Y);
@@ -556,7 +556,7 @@ void LocalPlayer::move(f32 dtime, Map &map, f32 pos_max_d)
 		for(s16 z=-1; z<=1; z++)
 		{
 			v3s16 p = pos_i_bottom + v3s16(x,0,z);
-			v3f pf = intToFloat(p);
+			v3f pf = intToFloat(p, BS);
 			v2f node_p2df(pf.X, pf.Z);
 			f32 distance_f = player_p2df.getDistanceFrom(node_p2df);
 			f32 max_axis_distance_f = MYMAX(
diff --git a/src/player.h b/src/player.h
index 16de6c924ef3fd567bbd56fe50e6315f4ae42723..710a9e0ed7c61bffc17385e4e1b869bcce3b45e7 100644
--- a/src/player.h
+++ b/src/player.h
@@ -132,6 +132,10 @@ class Player
 	v3f m_position;
 };
 
+/*
+	Player on the server
+*/
+
 class ServerRemotePlayer : public Player
 {
 public:
@@ -150,12 +154,16 @@ class ServerRemotePlayer : public Player
 	virtual void move(f32 dtime, Map &map, f32 pos_max_d)
 	{
 	}
-
+	
 private:
 };
 
 #ifndef SERVER
 
+/*
+	All the other players on the client are these
+*/
+
 class RemotePlayer : public Player, public scene::ISceneNode
 {
 public:
diff --git a/src/server.cpp b/src/server.cpp
index 26d0d5d75f98c4401ed5adcf926ec5d468cb1429..6f04ef33a6ec2055e6c322ba5099ea616b73c8b7 100644
--- a/src/server.cpp
+++ b/src/server.cpp
@@ -283,21 +283,14 @@ void RemoteClient::GetNextBlocks(Server *server, float dtime,
 	DSTACK(__FUNCTION_NAME);
 	
 	// Increment timers
-	{
-		JMutexAutoLock lock(m_blocks_sent_mutex);
-		m_nearest_unsent_reset_timer += dtime;
-	}
+	m_nearest_unsent_reset_timer += dtime;
 
 	// Won't send anything if already sending
+	if(m_blocks_sending.size() >= g_settings.getU16
+			("max_simultaneous_block_sends_per_client"))
 	{
-		JMutexAutoLock lock(m_blocks_sending_mutex);
-		
-		if(m_blocks_sending.size() >= g_settings.getU16
-				("max_simultaneous_block_sends_per_client"))
-		{
-			//dstream<<"Not sending any blocks, Queue full."<<std::endl;
-			return;
-		}
+		//dstream<<"Not sending any blocks, Queue full."<<std::endl;
+		return;
 	}
 
 	Player *player = server->m_env.getPlayer(peer_id);
@@ -307,7 +300,7 @@ void RemoteClient::GetNextBlocks(Server *server, float dtime,
 	v3f playerpos = player->getPosition();
 	v3f playerspeed = player->getSpeed();
 
-	v3s16 center_nodepos = floatToInt(playerpos);
+	v3s16 center_nodepos = floatToInt(playerpos, BS);
 
 	v3s16 center = getNodeBlockPos(center_nodepos);
 	
@@ -323,29 +316,26 @@ void RemoteClient::GetNextBlocks(Server *server, float dtime,
 	*/
 	s16 last_nearest_unsent_d;
 	s16 d_start;
-	{
-		JMutexAutoLock lock(m_blocks_sent_mutex);
 		
-		if(m_last_center != center)
-		{
-			m_nearest_unsent_d = 0;
-			m_last_center = center;
-		}
-
-		/*dstream<<"m_nearest_unsent_reset_timer="
-				<<m_nearest_unsent_reset_timer<<std::endl;*/
-		if(m_nearest_unsent_reset_timer > 5.0)
-		{
-			m_nearest_unsent_reset_timer = 0;
-			m_nearest_unsent_d = 0;
-			//dstream<<"Resetting m_nearest_unsent_d"<<std::endl;
-		}
+	if(m_last_center != center)
+	{
+		m_nearest_unsent_d = 0;
+		m_last_center = center;
+	}
 
-		last_nearest_unsent_d = m_nearest_unsent_d;
-		
-		d_start = m_nearest_unsent_d;
+	/*dstream<<"m_nearest_unsent_reset_timer="
+			<<m_nearest_unsent_reset_timer<<std::endl;*/
+	if(m_nearest_unsent_reset_timer > 5.0)
+	{
+		m_nearest_unsent_reset_timer = 0;
+		m_nearest_unsent_d = 0;
+		//dstream<<"Resetting m_nearest_unsent_d"<<std::endl;
 	}
 
+	last_nearest_unsent_d = m_nearest_unsent_d;
+	
+	d_start = m_nearest_unsent_d;
+
 	u16 maximum_simultaneous_block_sends_setting = g_settings.getU16
 			("max_simultaneous_block_sends_per_client");
 	u16 maximum_simultaneous_block_sends = 
@@ -356,24 +346,15 @@ void RemoteClient::GetNextBlocks(Server *server, float dtime,
 		
 		Decrease send rate if player is building stuff.
 	*/
+	m_time_from_building += dtime;
+	if(m_time_from_building < g_settings.getFloat(
+				"full_block_send_enable_min_time_from_building"))
 	{
-		SharedPtr<JMutexAutoLock> lock(m_time_from_building.getLock());
-		m_time_from_building.m_value += dtime;
-		/*if(m_time_from_building.m_value
-				< FULL_BLOCK_SEND_ENABLE_MIN_TIME_FROM_BUILDING)*/
-		if(m_time_from_building.m_value < g_settings.getFloat(
-					"full_block_send_enable_min_time_from_building"))
-		{
-			maximum_simultaneous_block_sends
-				= LIMITED_MAX_SIMULTANEOUS_BLOCK_SENDS;
-		}
+		maximum_simultaneous_block_sends
+			= LIMITED_MAX_SIMULTANEOUS_BLOCK_SENDS;
 	}
 	
-	u32 num_blocks_selected;
-	{
-		JMutexAutoLock lock(m_blocks_sending_mutex);
-		num_blocks_selected = m_blocks_sending.size();
-	}
+	u32 num_blocks_selected = m_blocks_sending.size();
 	
 	/*
 		next time d will be continued from the d from which the nearest
@@ -384,11 +365,6 @@ void RemoteClient::GetNextBlocks(Server *server, float dtime,
 	*/
 	s32 new_nearest_unsent_d = -1;
 
-	// Serialization version used
-	//u8 ser_version = serialization_version;
-
-	//bool has_incomplete_blocks = false;
-	
 	s16 d_max = g_settings.getS16("max_block_send_distance");
 	s16 d_max_gen = g_settings.getS16("max_block_generate_distance");
 	
@@ -398,20 +374,16 @@ void RemoteClient::GetNextBlocks(Server *server, float dtime,
 	{
 		//dstream<<"RemoteClient::SendBlocks(): d="<<d<<std::endl;
 		
-		//if(has_incomplete_blocks == false)
+		/*
+			If m_nearest_unsent_d was changed by the EmergeThread
+			(it can change it to 0 through SetBlockNotSent),
+			update our d to it.
+			Else update m_nearest_unsent_d
+		*/
+		if(m_nearest_unsent_d != last_nearest_unsent_d)
 		{
-			JMutexAutoLock lock(m_blocks_sent_mutex);
-			/*
-				If m_nearest_unsent_d was changed by the EmergeThread
-				(it can change it to 0 through SetBlockNotSent),
-				update our d to it.
-				Else update m_nearest_unsent_d
-			*/
-			if(m_nearest_unsent_d != last_nearest_unsent_d)
-			{
-				d = m_nearest_unsent_d;
-				last_nearest_unsent_d = m_nearest_unsent_d;
-			}
+			d = m_nearest_unsent_d;
+			last_nearest_unsent_d = m_nearest_unsent_d;
 		}
 
 		/*
@@ -443,23 +415,19 @@ void RemoteClient::GetNextBlocks(Server *server, float dtime,
 						maximum_simultaneous_block_sends_setting;
 			}
 
+			// Limit is dynamically lowered when building
+			if(num_blocks_selected
+					>= maximum_simultaneous_block_sends_now)
 			{
-				JMutexAutoLock lock(m_blocks_sending_mutex);
-				
-				// Limit is dynamically lowered when building
-				if(num_blocks_selected
-						>= maximum_simultaneous_block_sends_now)
-				{
-					/*dstream<<"Not sending more blocks. Queue full. "
-							<<m_blocks_sending.size()
-							<<std::endl;*/
-					goto queue_full;
-				}
-
-				if(m_blocks_sending.find(p) != NULL)
-					continue;
+				/*dstream<<"Not sending more blocks. Queue full. "
+						<<m_blocks_sending.size()
+						<<std::endl;*/
+				goto queue_full;
 			}
-			
+
+			if(m_blocks_sending.find(p) != NULL)
+				continue;
+		
 			/*
 				Do not go over-limit
 			*/
@@ -519,7 +487,7 @@ void RemoteClient::GetNextBlocks(Server *server, float dtime,
 #endif
 
 			/*
-				Don't draw if not in sight
+				Don't generate or send if not in sight
 			*/
 
 			if(isBlockInSight(p, camera_pos, camera_dir, 10000*BS) == false)
@@ -531,8 +499,6 @@ void RemoteClient::GetNextBlocks(Server *server, float dtime,
 				Don't send already sent blocks
 			*/
 			{
-				JMutexAutoLock lock(m_blocks_sent_mutex);
-				
 				if(m_blocks_sent.find(p) != NULL)
 					continue;
 			}
@@ -546,12 +512,6 @@ void RemoteClient::GetNextBlocks(Server *server, float dtime,
 			bool block_is_invalid = false;
 			if(block != NULL)
 			{
-				/*if(block->isIncomplete())
-				{
-					has_incomplete_blocks = true;
-					continue;
-				}*/
-
 				if(block->isDummy())
 				{
 					surely_not_found_on_disk = true;
@@ -567,11 +527,6 @@ void RemoteClient::GetNextBlocks(Server *server, float dtime,
 				v2s16 chunkpos = map->sector_to_chunk(p2d);
 				if(map->chunkNonVolatile(chunkpos) == false)
 					block_is_invalid = true;
-				/*MapChunk *chunk = map->getChunk(chunkpos);
-				if(chunk == NULL)
-					block_is_invalid = true;
-				else if(chunk->getIsVolatile() == true)
-					block_is_invalid = true;*/
 			}
 
 			/*
@@ -598,11 +553,6 @@ void RemoteClient::GetNextBlocks(Server *server, float dtime,
 			*/
 			if(block == NULL || surely_not_found_on_disk || block_is_invalid)
 			{
-				//dstream<<"asd"<<std::endl;
-				
-				/*SharedPtr<JMutexAutoLock> lock
-						(m_num_blocks_in_emerge_queue.getLock());*/
-				
 				//TODO: Get value from somewhere
 				// Allow only one block in emerge queue
 				if(server->m_emerge_queue.peerItemCount(peer_id) < 1)
@@ -624,7 +574,7 @@ void RemoteClient::GetNextBlocks(Server *server, float dtime,
 			}
 
 			/*
-				Add block to queue
+				Add block to send queue
 			*/
 
 			PrioritySortedBlockTransfer q((float)d, p, peer_id);
@@ -638,7 +588,6 @@ void RemoteClient::GetNextBlocks(Server *server, float dtime,
 
 	if(new_nearest_unsent_d != -1)
 	{
-		JMutexAutoLock lock(m_blocks_sent_mutex);
 		m_nearest_unsent_d = new_nearest_unsent_d;
 	}
 }
@@ -743,7 +692,7 @@ void RemoteClient::SendObjectData(
 	v3f playerpos = player->getPosition();
 	v3f playerspeed = player->getSpeed();
 
-	v3s16 center_nodepos = floatToInt(playerpos);
+	v3s16 center_nodepos = floatToInt(playerpos, BS);
 	v3s16 center = getNodeBlockPos(center_nodepos);
 
 	s16 d_max = g_settings.getS16("active_object_range");
@@ -767,7 +716,6 @@ void RemoteClient::SendObjectData(
 				Ignore blocks that haven't been sent to the client
 			*/
 			{
-				JMutexAutoLock sentlock(m_blocks_sent_mutex);
 				if(m_blocks_sent.find(p) == NULL)
 					continue;
 			}
@@ -861,8 +809,6 @@ void RemoteClient::SendObjectData(
 
 void RemoteClient::GotBlock(v3s16 p)
 {
-	JMutexAutoLock lock(m_blocks_sending_mutex);
-	JMutexAutoLock lock2(m_blocks_sent_mutex);
 	if(m_blocks_sending.find(p) != NULL)
 		m_blocks_sending.remove(p);
 	else
@@ -876,13 +822,6 @@ void RemoteClient::GotBlock(v3s16 p)
 
 void RemoteClient::SentBlock(v3s16 p)
 {
-	JMutexAutoLock lock(m_blocks_sending_mutex);
-	/*if(m_blocks_sending.size() > 15)
-	{
-		dstream<<"RemoteClient::SentBlock(): "
-				<<"m_blocks_sending.size()="
-				<<m_blocks_sending.size()<<std::endl;
-	}*/
 	if(m_blocks_sending.find(p) == NULL)
 		m_blocks_sending.insert(p, 0.0);
 	else
@@ -892,9 +831,6 @@ void RemoteClient::SentBlock(v3s16 p)
 
 void RemoteClient::SetBlockNotSent(v3s16 p)
 {
-	JMutexAutoLock sendinglock(m_blocks_sending_mutex);
-	JMutexAutoLock sentlock(m_blocks_sent_mutex);
-
 	m_nearest_unsent_d = 0;
 	
 	if(m_blocks_sending.find(p) != NULL)
@@ -905,9 +841,6 @@ void RemoteClient::SetBlockNotSent(v3s16 p)
 
 void RemoteClient::SetBlocksNotSent(core::map<v3s16, MapBlock*> &blocks)
 {
-	JMutexAutoLock sendinglock(m_blocks_sending_mutex);
-	JMutexAutoLock sentlock(m_blocks_sent_mutex);
-
 	m_nearest_unsent_d = 0;
 	
 	for(core::map<v3s16, MapBlock*>::Iterator
@@ -964,7 +897,7 @@ u32 PIChecksum(core::list<PlayerInfo> &l)
 Server::Server(
 		std::string mapsavedir
 	):
-	m_env(new ServerMap(mapsavedir), dout_server),
+	m_env(new ServerMap(mapsavedir)),
 	m_con(PROTOCOL_ID, 512, CONNECTION_TIMEOUT, this),
 	m_thread(this),
 	m_emergethread(this),
@@ -1257,11 +1190,8 @@ void Server::AsyncRunStep()
 	}
 
 	/*
-		Update digging
-
-		NOTE: Some of this could be moved to RemoteClient
+		Check added and deleted active objects
 	*/
-#if 0
 	{
 		JMutexAutoLock envlock(m_env_mutex);
 		JMutexAutoLock conlock(m_con_mutex);
@@ -1272,100 +1202,209 @@ void Server::AsyncRunStep()
 		{
 			RemoteClient *client = i.getNode()->getValue();
 			Player *player = m_env.getPlayer(client->peer_id);
-
-			JMutexAutoLock digmutex(client->m_dig_mutex);
-
-			if(client->m_dig_tool_item == -1)
+			v3s16 pos = floatToInt(player->getPosition(), BS);
+			s16 radius = 32;
+
+			core::map<u16, bool> removed_objects;
+			core::map<u16, bool> added_objects;
+			m_env.getRemovedActiveObjects(pos, radius,
+					client->m_known_objects, removed_objects);
+			m_env.getAddedActiveObjects(pos, radius,
+					client->m_known_objects, added_objects);
+			
+			// Ignore if nothing happened
+			if(removed_objects.size() == 0 && added_objects.size() == 0)
 				continue;
+			
+			std::string data_buffer;
+
+			char buf[4];
+			
+			// Handle removed objects
+			writeU16((u8*)buf, removed_objects.size());
+			data_buffer.append(buf, 2);
+			for(core::map<u16, bool>::Iterator
+					i = removed_objects.getIterator();
+					i.atEnd()==false; i++)
+			{
+				// Get object
+				u16 id = i.getNode()->getKey();
+				ServerActiveObject* obj = m_env.getActiveObject(id);
+
+				// Add to data buffer for sending
+				writeU16((u8*)buf, i.getNode()->getKey());
+				data_buffer.append(buf, 2);
+				
+				// Remove from known objects
+				client->m_known_objects.remove(i.getNode()->getKey());
 
-			client->m_dig_time_remaining -= dtime;
+				if(obj && obj->m_known_by_count > 0)
+					obj->m_known_by_count--;
+			}
 
-			if(client->m_dig_time_remaining > 0)
+			// Handle added objects
+			writeU16((u8*)buf, added_objects.size());
+			data_buffer.append(buf, 2);
+			for(core::map<u16, bool>::Iterator
+					i = added_objects.getIterator();
+					i.atEnd()==false; i++)
 			{
-				client->m_time_from_building.set(0.0);
-				continue;
+				// Get object
+				u16 id = i.getNode()->getKey();
+				ServerActiveObject* obj = m_env.getActiveObject(id);
+				
+				// Get object type
+				u8 type = ACTIVEOBJECT_TYPE_INVALID;
+				if(obj == NULL)
+					dstream<<"WARNING: "<<__FUNCTION_NAME
+							<<": NULL object"<<std::endl;
+				else
+					type = obj->getType();
+
+				// Add to data buffer for sending
+				writeU16((u8*)buf, id);
+				data_buffer.append(buf, 2);
+				writeU8((u8*)buf, type);
+				data_buffer.append(buf, 1);
+
+				// Add to known objects
+				client->m_known_objects.insert(i.getNode()->getKey(), false);
+
+				if(obj)
+					obj->m_known_by_count++;
 			}
 
-			v3s16 p_under = client->m_dig_position;
-			
-			// Mandatory parameter; actually used for nothing
-			core::map<v3s16, MapBlock*> modified_blocks;
+			// Send packet
+			SharedBuffer<u8> reply(2 + data_buffer.size());
+			writeU16(&reply[0], TOCLIENT_ACTIVE_OBJECT_REMOVE_ADD);
+			memcpy((char*)&reply[2], data_buffer.c_str(),
+					data_buffer.size());
+			// Send as reliable
+			m_con.Send(client->peer_id, 0, reply, true);
 
-			u8 material;
+			dstream<<"INFO: Server: Sent object remove/add: "
+					<<removed_objects.size()<<" removed, "
+					<<added_objects.size()<<" added, "
+					<<"packet size is "<<reply.getSize()<<std::endl;
+		}
+	}
 
-			try
+	/*
+		Send object messages
+	*/
+	{
+		JMutexAutoLock envlock(m_env_mutex);
+		JMutexAutoLock conlock(m_con_mutex);
+
+		// Key = object id
+		// Value = data sent by object
+		core::map<u16, core::list<ActiveObjectMessage>* > buffered_messages;
+
+		// Get active object messages from environment
+		for(;;)
+		{
+			ActiveObjectMessage aom = m_env.getActiveObjectMessage();
+			if(aom.id == 0)
+				break;
+			
+			core::list<ActiveObjectMessage>* message_list = NULL;
+			core::map<u16, core::list<ActiveObjectMessage>* >::Node *n;
+			n = buffered_messages.find(aom.id);
+			if(n == NULL)
 			{
-				// Get material at position
-				material = m_env.getMap().getNode(p_under).d;
-				// If it's not diggable, do nothing
-				if(content_diggable(material) == false)
-				{
-					derr_server<<"Server: Not finishing digging: Node not diggable"
-							<<std::endl;
-					client->m_dig_tool_item = -1;
-					break;
-				}
+				message_list = new core::list<ActiveObjectMessage>;
+				buffered_messages.insert(aom.id, message_list);
 			}
-			catch(InvalidPositionException &e)
+			else
 			{
-				derr_server<<"Server: Not finishing digging: Node not found"
-						<<std::endl;
-				client->m_dig_tool_item = -1;
-				break;
+				message_list = n->getValue();
 			}
-			
-			// Create packet
-			u32 replysize = 8;
-			SharedBuffer<u8> reply(replysize);
-			writeU16(&reply[0], TOCLIENT_REMOVENODE);
-			writeS16(&reply[2], p_under.X);
-			writeS16(&reply[4], p_under.Y);
-			writeS16(&reply[6], p_under.Z);
-			// Send as reliable
-			m_con.SendToAll(0, reply, true);
-			
-			if(g_settings.getBool("creative_mode") == false)
-			{
-				// Add to inventory and send inventory
-				InventoryItem *item = new MaterialItem(material, 1);
-				player->inventory.addItem("main", item);
-				SendInventory(player->peer_id);
+			message_list->push_back(aom);
+		}
+		
+		// Route data to every client
+		for(core::map<u16, RemoteClient*>::Iterator
+			i = m_clients.getIterator();
+			i.atEnd()==false; i++)
+		{
+			RemoteClient *client = i.getNode()->getValue();
+			std::string reliable_data;
+			std::string unreliable_data;
+			// Go through all objects in message buffer
+			for(core::map<u16, core::list<ActiveObjectMessage>* >::Iterator
+					j = buffered_messages.getIterator();
+					j.atEnd()==false; j++)
+			{
+				// If object is not known by client, skip it
+				u16 id = j.getNode()->getKey();
+				if(client->m_known_objects.find(id) == NULL)
+					continue;
+				// Get message list of object
+				core::list<ActiveObjectMessage>* list = j.getNode()->getValue();
+				// Go through every message
+				for(core::list<ActiveObjectMessage>::Iterator
+						k = list->begin(); k != list->end(); k++)
+				{
+					// Compose the full new data with header
+					ActiveObjectMessage aom = *k;
+					std::string new_data;
+					// Add header (object id + length)
+					char header[4];
+					writeU16((u8*)&header[0], aom.id);
+					writeU16((u8*)&header[2], aom.datastring.size());
+					new_data.append(header, 4);
+					// Add data
+					new_data += aom.datastring;
+					// Add data to buffer
+					if(aom.reliable)
+						reliable_data += new_data;
+					else
+						unreliable_data += new_data;
+				}
 			}
-
 			/*
-				Remove the node
-				(this takes some time so it is done after the quick stuff)
+				reliable_data and unreliable_data are now ready.
+				Send them.
 			*/
-			m_env.getMap().removeNodeAndUpdate(p_under, modified_blocks);
-			
-			/*
-				Update water
-			*/
-			
-			// Update water pressure around modification
-			// This also adds it to m_flow_active_nodes if appropriate
-
-			MapVoxelManipulator v(&m_env.getMap());
-			v.m_disable_water_climb =
-					g_settings.getBool("disable_water_climb");
-			
-			VoxelArea area(p_under-v3s16(1,1,1), p_under+v3s16(1,1,1));
-
-			try
+			if(reliable_data.size() > 0)
 			{
-				v.updateAreaWaterPressure(area, m_flow_active_nodes);
+				SharedBuffer<u8> reply(2 + reliable_data.size());
+				writeU16(&reply[0], TOCLIENT_ACTIVE_OBJECT_MESSAGES);
+				memcpy((char*)&reply[2], reliable_data.c_str(),
+						reliable_data.size());
+				// Send as reliable
+				m_con.Send(client->peer_id, 0, reply, true);
 			}
-			catch(ProcessingLimitException &e)
+			if(unreliable_data.size() > 0)
 			{
-				dstream<<"Processing limit reached (1)"<<std::endl;
+				SharedBuffer<u8> reply(2 + unreliable_data.size());
+				writeU16(&reply[0], TOCLIENT_ACTIVE_OBJECT_MESSAGES);
+				memcpy((char*)&reply[2], unreliable_data.c_str(),
+						unreliable_data.size());
+				// Send as unreliable
+				m_con.Send(client->peer_id, 0, reply, false);
 			}
-			
-			v.blitBack(modified_blocks);
+			if(reliable_data.size() > 0 || unreliable_data.size() > 0)
+			{
+				dstream<<"INFO: Server: Size of object message data: "
+						<<"reliable: "<<reliable_data.size()
+						<<", unreliable: "<<unreliable_data.size()
+						<<std::endl;
+			}
+		}
+
+		// Clear buffered_messages
+		for(core::map<u16, core::list<ActiveObjectMessage>* >::Iterator
+				i = buffered_messages.getIterator();
+				i.atEnd()==false; i++)
+		{
+			delete i.getNode()->getValue();
 		}
 	}
-#endif
 
-	// Send object positions
+	/*
+		Send object positions
+	*/
 	{
 		float &counter = m_objectdata_timer;
 		counter += dtime;
@@ -1485,7 +1524,6 @@ void Server::ProcessData(u8 *data, u32 datasize, u16 peer_id)
 		return;
 	}
 	
-	//u8 peer_ser_ver = peer->serialization_version;
 	u8 peer_ser_ver = getClient(peer->id)->serialization_version;
 
 	try
@@ -1595,7 +1633,7 @@ void Server::ProcessData(u8 *data, u32 datasize, u16 peer_id)
 		SharedBuffer<u8> reply(2+1+6);
 		writeU16(&reply[0], TOCLIENT_INIT);
 		writeU8(&reply[2], deployed);
-		writeV3S16(&reply[3], floatToInt(player->getPosition()+v3f(0,BS/2,0)));
+		writeV3S16(&reply[3], floatToInt(player->getPosition()+v3f(0,BS/2,0), BS));
 		// Send as reliable
 		m_con.Send(peer_id, 0, reply, true);
 
@@ -1892,6 +1930,17 @@ void Server::ProcessData(u8 *data, u32 datasize, u16 peer_id)
 				{
 					derr_server<<"Server: Not finishing digging: Node not diggable"
 							<<std::endl;
+
+					// Client probably has wrong data.
+					// Set block not sent, so that client will get
+					// a valid one.
+					dstream<<"Client "<<peer_id<<" tried to dig "
+							<<"node from invalid position; setting"
+							<<" MapBlock not sent."<<std::endl;
+					RemoteClient *client = getClient(peer_id);
+					v3s16 blockpos = getNodeBlockPos(p_under);
+					client->SetBlockNotSent(blockpos);
+						
 					return;
 				}
 				// Get mineral
@@ -2088,7 +2137,7 @@ void Server::ProcessData(u8 *data, u32 datasize, u16 peer_id)
 				}
 
 				// Reset build time counter
-				getClient(peer->id)->m_time_from_building.set(0.0);
+				getClient(peer->id)->m_time_from_building = 0.0;
 				
 				// Create node data
 				MaterialItem *mitem = (MaterialItem*)item;
@@ -2166,9 +2215,9 @@ void Server::ProcessData(u8 *data, u32 datasize, u16 peer_id)
 				}
 
 				v3s16 block_pos_i_on_map = block->getPosRelative();
-				v3f block_pos_f_on_map = intToFloat(block_pos_i_on_map);
+				v3f block_pos_f_on_map = intToFloat(block_pos_i_on_map, BS);
 
-				v3f pos = intToFloat(p_over);
+				v3f pos = intToFloat(p_over, BS);
 				pos -= block_pos_f_on_map;
 				
 				/*dout_server<<"pos="
@@ -3060,6 +3109,7 @@ void Server::SendBlocks(float dtime)
 	DSTACK(__FUNCTION_NAME);
 
 	JMutexAutoLock envlock(m_env_mutex);
+	JMutexAutoLock conlock(m_con_mutex);
 
 	//TimeTaker timer("Server::SendBlocks");
 
@@ -3087,8 +3137,6 @@ void Server::SendBlocks(float dtime)
 	// Lowest is most important.
 	queue.sort();
 
-	JMutexAutoLock conlock(m_con_mutex);
-
 	for(u32 i=0; i<queue.size(); i++)
 	{
 		//TODO: Calculate limit dynamically
@@ -3268,7 +3316,7 @@ Player *Server::emergePlayer(const char *name, const char *password,
 				0,
 				45, //64,
 				0
-		)));
+		), BS));
 #endif
 #if 0
 		f32 groundheight = 0;
diff --git a/src/server.h b/src/server.h
index 9582c99dd0ffd96e93523359827e23b001af5b20..292e2d5cd84affc902812010e3fb272b079c903e 100644
--- a/src/server.h
+++ b/src/server.h
@@ -239,13 +239,6 @@ class RemoteClient
 		pending_serialization_version = SER_FMT_VER_INVALID;
 		m_nearest_unsent_d = 0;
 		m_nearest_unsent_reset_timer = 0.0;
-
-		m_blocks_sent_mutex.Init();
-		m_blocks_sending_mutex.Init();
-		
-		/*m_dig_mutex.Init();
-		m_dig_time_remaining = 0;
-		m_dig_tool_item = -1;*/
 	}
 	~RemoteClient()
 	{
@@ -279,7 +272,6 @@ class RemoteClient
 
 	s32 SendingCount()
 	{
-		JMutexAutoLock lock(m_blocks_sending_mutex);
 		return m_blocks_sending.size();
 	}
 	
@@ -290,8 +282,6 @@ class RemoteClient
 
 	void PrintInfo(std::ostream &o)
 	{
-		JMutexAutoLock l2(m_blocks_sent_mutex);
-		JMutexAutoLock l3(m_blocks_sending_mutex);
 		o<<"RemoteClient "<<peer_id<<": "
 				<<", m_blocks_sent.size()="<<m_blocks_sent.size()
 				<<", m_blocks_sending.size()="<<m_blocks_sending.size()
@@ -302,30 +292,21 @@ class RemoteClient
 	}
 
 	// Time from last placing or removing blocks
-	MutexedVariable<float> m_time_from_building;
+	float m_time_from_building;
 	
 	/*JMutex m_dig_mutex;
 	float m_dig_time_remaining;
 	// -1 = not digging
 	s16 m_dig_tool_item;
 	v3s16 m_dig_position;*/
-
-private:
+	
 	/*
-		All members that are accessed by many threads should
-		obviously be behind a mutex. The threads include:
-		- main thread (calls step())
-		- server thread (calls AsyncRunStep() and Receive())
-		- emerge thread 
+		List of active objects that the client knows of.
+		Value is dummy.
 	*/
-	
-	//TODO: core::map<v3s16, MapBlock*> m_active_blocks
-	//NOTE: Not here, it should be server-wide!
-
-	// Number of blocks in the emerge queue that have this client as
-	// a receiver. Used for throttling network usage.
-	//MutexedVariable<s16> m_num_blocks_in_emerge_queue;
+	core::map<u16, bool> m_known_objects;
 
+private:
 	/*
 		Blocks that have been sent to client.
 		- These don't have to be sent again.
@@ -339,7 +320,7 @@ class RemoteClient
 	s16 m_nearest_unsent_d;
 	v3s16 m_last_center;
 	float m_nearest_unsent_reset_timer;
-	JMutex m_blocks_sent_mutex;
+	
 	/*
 		Blocks that are currently on the line.
 		This is used for throttling the sending of blocks.
@@ -349,7 +330,6 @@ class RemoteClient
 		Value is time from sending. (not used at the moment)
 	*/
 	core::map<v3s16, float> m_blocks_sending;
-	JMutex m_blocks_sending_mutex;
 
 	/*
 		Count of excess GotBlocks().
@@ -361,15 +341,6 @@ class RemoteClient
 	u32 m_excess_gotblocks;
 };
 
-/*struct ServerSettings
-{
-	ServerSettings()
-	{
-		creative_mode = false;
-	}
-	bool creative_mode;
-};*/
-
 class Server : public con::PeerHandler
 {
 public:
@@ -470,7 +441,7 @@ class Server : public con::PeerHandler
 	// NOTE: If connection and environment are both to be locked,
 	// environment shall be locked first.
 	JMutex m_env_mutex;
-	Environment m_env;
+	ServerEnvironment m_env;
 
 	JMutex m_con_mutex;
 	con::Connection m_con;
diff --git a/src/serverobject.cpp b/src/serverobject.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..ffb6059dc6902fa1c25736c925d95d40d976f731
--- /dev/null
+++ b/src/serverobject.cpp
@@ -0,0 +1,75 @@
+/*
+Minetest-c55
+Copyright (C) 2010-2011 celeron55, Perttu Ahola <celeron55@gmail.com>
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 2 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License along
+with this program; if not, write to the Free Software Foundation, Inc.,
+51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+*/
+
+#include "serverobject.h"
+
+ServerActiveObject::ServerActiveObject(u16 id, v3f pos):
+	ActiveObject(id),
+	m_known_by_count(0),
+	m_removed(false),
+	m_base_position(pos)
+{
+}
+
+ServerActiveObject::~ServerActiveObject()
+{
+}
+
+TestSAO::TestSAO(u16 id, v3f pos):
+	ServerActiveObject(id, pos),
+	m_timer1(0),
+	m_age(0)
+{
+}
+
+void TestSAO::step(float dtime, Queue<ActiveObjectMessage> &messages)
+{
+	m_age += dtime;
+	if(m_age > 10)
+	{
+		m_removed = true;
+		return;
+	}
+
+	m_base_position.Y += dtime * BS * 2;
+	if(m_base_position.Y > 8*BS)
+		m_base_position.Y = 2*BS;
+
+	m_timer1 -= dtime;
+	if(m_timer1 < 0.0)
+	{
+		m_timer1 += 0.125;
+		//dstream<<"TestSAO: id="<<getId()<<" sending data"<<std::endl;
+
+		std::string data;
+
+		data += itos(0); // 0 = position
+		data += " ";
+		data += itos(m_base_position.X);
+		data += " ";
+		data += itos(m_base_position.Y);
+		data += " ";
+		data += itos(m_base_position.Z);
+
+		//ActiveObjectMessage aom(getId(), true, data);
+		ActiveObjectMessage aom(getId(), false, data);
+		messages.push_back(aom);
+	}
+}
+
diff --git a/src/serverobject.h b/src/serverobject.h
new file mode 100644
index 0000000000000000000000000000000000000000..d0866b4c33826d1ac935c9e8bc27107edf49274a
--- /dev/null
+++ b/src/serverobject.h
@@ -0,0 +1,87 @@
+/*
+Minetest-c55
+Copyright (C) 2010-2011 celeron55, Perttu Ahola <celeron55@gmail.com>
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 2 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License along
+with this program; if not, write to the Free Software Foundation, Inc.,
+51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+*/
+
+#ifndef SERVEROBJECT_HEADER
+#define SERVEROBJECT_HEADER
+
+#include "common_irrlicht.h"
+#include "activeobject.h"
+#include "utility.h"
+
+/*
+
+Some planning
+-------------
+
+* Server environment adds an active object, which gets the id 1
+* The active object list is scanned for each client once in a while,
+  and it finds out what objects have been added that are not known
+  by the client yet. This scan is initiated by the server and the
+  result ends up directly to the server.
+* A network packet is created with the info and sent to the client.
+
+*/
+
+class ServerActiveObject : public ActiveObject
+{
+public:
+	ServerActiveObject(u16 id, v3f pos=v3f(0,0,0));
+	virtual ~ServerActiveObject();
+
+	v3f getBasePosition()
+	{
+		return m_base_position;
+	}
+	
+	/*
+		Step object in time.
+		Messages added to messages are sent to client over network.
+	*/
+	virtual void step(float dtime, Queue<ActiveObjectMessage> &messages){}
+	
+	// Number of players which know about this one
+	u16 m_known_by_count;
+	/*
+		Whether this object is to be removed when nobody knows about
+		it anymore.
+		Removal is delayed to preserve the id for the time during which
+		it could be confused to some other object by some client.
+	*/
+	bool m_removed;
+	
+protected:
+	v3f m_base_position;
+};
+
+class TestSAO : public ServerActiveObject
+{
+public:
+	TestSAO(u16 id, v3f pos);
+	u8 getType() const
+	{
+		return ACTIVEOBJECT_TYPE_TEST;
+	}
+	void step(float dtime, Queue<ActiveObjectMessage> &messages);
+private:
+	float m_timer1;
+	float m_age;
+};
+
+#endif
+
diff --git a/src/utility.h b/src/utility.h
index 8c81aba9170c8c8f1f3a5b7e72646dc5760dc50d..8db2c276065b50848d32081eb9aaf7a214ad685b 100644
--- a/src/utility.h
+++ b/src/utility.h
@@ -1738,5 +1738,30 @@ inline std::string wrap_rows(const std::string &from, u32 rowlen)
 #define MYMIN(a,b) ((a)<(b)?(a):(b))
 #define MYMAX(a,b) ((a)>(b)?(a):(b))
 
+/*
+	Returns integer position of node in given floating point position
+*/
+inline v3s16 floatToInt(v3f p, f32 d)
+{
+	v3s16 p2(
+		(p.X + (p.X>0 ? BS/2 : -BS/2))/d,
+		(p.Y + (p.Y>0 ? BS/2 : -BS/2))/d,
+		(p.Z + (p.Z>0 ? BS/2 : -BS/2))/d);
+	return p2;
+}
+
+/*
+	Returns floating point position of node in given integer position
+*/
+inline v3f intToFloat(v3s16 p, f32 d)
+{
+	v3f p2(
+		(f32)p.X * d,
+		(f32)p.Y * d,
+		(f32)p.Z * d
+	);
+	return p2;
+}
+
 #endif