From 5a4d8ffad3b172eae67844deda6b65273b7c9757 Mon Sep 17 00:00:00 2001
From: Perttu Ahola <celeron55@gmail.com>
Date: Sun, 10 Apr 2011 15:16:27 +0300
Subject: [PATCH] implemented rats in new system to verify that it works

---
 src/clientobject.cpp | 189 ++++++++++++++++++++++++++++++++++++-----
 src/clientobject.h   |  99 ++++++++++++++++++++++
 src/collision.cpp    | 184 ++++++++++++++++++++++++++++++++++++++++
 src/collision.h      |  43 ++++++++++
 src/environment.cpp  |  37 +++++++--
 src/environment.h    |   1 +
 src/inventory.cpp    |  57 +++++++++++++
 src/inventory.h      |  39 ++++-----
 src/mapblock.cpp     |   6 +-
 src/mapblock.h       |   3 +-
 src/server.cpp       |  47 ++++++-----
 src/serverobject.cpp | 194 ++++++++++++++++++++++++++++++++++++++++++-
 src/serverobject.h   |  79 +++++++++++++++++-
 src/utility.h        |  81 +++++++++++++++++-
 14 files changed, 974 insertions(+), 85 deletions(-)
 create mode 100644 src/collision.cpp
 create mode 100644 src/collision.h

diff --git a/src/clientobject.cpp b/src/clientobject.cpp
index 901b3d072..1d9dd215b 100644
--- a/src/clientobject.cpp
+++ b/src/clientobject.cpp
@@ -24,6 +24,10 @@ with this program; if not, write to the Free Software Foundation, Inc.,
 #include "utility.h"
 #include "environment.h"
 
+/*
+	ClientActiveObject
+*/
+
 core::map<u16, ClientActiveObject::Factory> ClientActiveObject::m_types;
 
 ClientActiveObject::ClientActiveObject(u16 id):
@@ -231,8 +235,9 @@ void ItemCAO::addToScene(scene::ISceneManager *smgr)
 	buf->getMaterial().setFlag(video::EMF_LIGHTING, false);
 	buf->getMaterial().setFlag(video::EMF_BACK_FACE_CULLING, false);
 	//buf->getMaterial().setTexture(0, NULL);
+	// Initialize with the stick texture
 	buf->getMaterial().setTexture
-			(0, driver->getTexture(porting::getDataPath("rat.png").c_str()));
+			(0, driver->getTexture(porting::getDataPath("stick.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;
@@ -313,19 +318,12 @@ void ItemCAO::processMessage(const std::string &data)
 {
 	dstream<<"ItemCAO: Got message"<<std::endl;
 	std::istringstream is(data, std::ios::binary);
-	char buf[4];
 	// command
-	is.read(buf, 1);
-	u8 cmd = buf[0];
+	u8 cmd = readU8(is);
 	if(cmd == 0)
 	{
 		// pos
-		is.read(buf, 4);
-		m_position.X = (float)readS32((u8*)buf)/1000.0;
-		is.read(buf, 4);
-		m_position.Y = (float)readS32((u8*)buf)/1000.0;
-		is.read(buf, 4);
-		m_position.Z = (float)readS32((u8*)buf)/1000.0;
+		m_position = readV3F1000(is);
 		updateNodePos();
 	}
 }
@@ -336,20 +334,13 @@ void ItemCAO::initialize(const std::string &data)
 	
 	{
 		std::istringstream is(data, std::ios::binary);
-		char buf[4];
 		// version
-		is.read(buf, 1);
-		u8 version = buf[0];
+		u8 version = readU8(is);
 		// check version
 		if(version != 0)
 			return;
 		// pos
-		is.read(buf, 4);
-		m_position.X = (float)readS32((u8*)buf)/1000.0;
-		is.read(buf, 4);
-		m_position.Y = (float)readS32((u8*)buf)/1000.0;
-		is.read(buf, 4);
-		m_position.Z = (float)readS32((u8*)buf)/1000.0;
+		m_position = readV3F1000(is);
 		// inventorystring
 		m_inventorystring = deSerializeString(is);
 	}
@@ -400,4 +391,164 @@ void ItemCAO::initialize(const std::string &data)
 	
 }
 
+/*
+	RatCAO
+*/
+
+#include "inventory.h"
+
+// Prototype
+RatCAO proto_RatCAO;
+
+RatCAO::RatCAO():
+	ClientActiveObject(0),
+	m_selection_box(-BS/3.,0.0,-BS/3., BS/3.,BS/2.,BS/3.),
+	m_node(NULL),
+	m_position(v3f(0,10*BS,0))
+{
+	ClientActiveObject::registerType(getType(), create);
+}
+
+RatCAO::~RatCAO()
+{
+}
+
+ClientActiveObject* RatCAO::create()
+{
+	return new RatCAO();
+}
+
+void RatCAO::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,0,0, 0,0,0, c, 0,1),
+		video::S3DVertex(BS/2,0,0, 0,0,0, c, 1,1),
+		video::S3DVertex(BS/2,BS/2,0, 0,0,0, c, 1,0),
+		video::S3DVertex(-BS/2,BS/2,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, NULL);
+	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();
+	// Set it to use the materials of the meshbuffers directly.
+	// This is needed for changing the texture in the future
+	m_node->setReadOnlyMaterials(true);
+	updateNodePos();
+}
+
+void RatCAO::removeFromScene()
+{
+	if(m_node == NULL)
+		return;
+
+	m_node->remove();
+	m_node = NULL;
+}
+
+void RatCAO::updateLight(u8 light_at_pos)
+{
+	if(m_node == NULL)
+		return;
+
+	u8 li = decode_light(light_at_pos);
+	video::SColor color(255,li,li,li);
+
+	scene::IMesh *mesh = m_node->getMesh();
+	if(mesh == NULL)
+		return;
+	
+	u16 mc = mesh->getMeshBufferCount();
+	for(u16 j=0; j<mc; j++)
+	{
+		scene::IMeshBuffer *buf = mesh->getMeshBuffer(j);
+		video::S3DVertex *vertices = (video::S3DVertex*)buf->getVertices();
+		u16 vc = buf->getVertexCount();
+		for(u16 i=0; i<vc; i++)
+		{
+			vertices[i].Color = color;
+		}
+	}
+}
+
+v3s16 RatCAO::getLightPosition()
+{
+	return floatToInt(m_position, BS);
+}
+
+void RatCAO::updateNodePos()
+{
+	if(m_node == NULL)
+		return;
+
+	//m_node->setPosition(m_position);
+	m_node->setPosition(pos_translator.vect_show);
+
+	v3f rot = m_node->getRotation();
+	rot.Y = 180.0 - m_yaw;
+	m_node->setRotation(rot);
+}
+
+void RatCAO::step(float dtime, ClientEnvironment *env)
+{
+	pos_translator.translate(dtime);
+	updateNodePos();
+}
+
+void RatCAO::processMessage(const std::string &data)
+{
+	//dstream<<"RatCAO: Got message"<<std::endl;
+	std::istringstream is(data, std::ios::binary);
+	// command
+	u8 cmd = readU8(is);
+	if(cmd == 0)
+	{
+		// pos
+		m_position = readV3F1000(is);
+		pos_translator.update(m_position);
+		// yaw
+		m_yaw = readF1000(is);
+		updateNodePos();
+	}
+}
+
+void RatCAO::initialize(const std::string &data)
+{
+	//dstream<<"RatCAO: Got init data"<<std::endl;
+	
+	{
+		std::istringstream is(data, std::ios::binary);
+		// version
+		u8 version = readU8(is);
+		// check version
+		if(version != 0)
+			return;
+		// pos
+		m_position = readV3F1000(is);
+		pos_translator.init(m_position);
+	}
+	
+	updateNodePos();
+}
+
 
diff --git a/src/clientobject.h b/src/clientobject.h
index 50fae67c2..569e9eca6 100644
--- a/src/clientobject.h
+++ b/src/clientobject.h
@@ -35,6 +35,63 @@ Some planning
 
 */
 
+/*
+	SmoothTranslator
+*/
+
+struct SmoothTranslator
+{
+	v3f vect_old;
+	f32 anim_counter;
+	f32 anim_time;
+	f32 anim_time_counter;
+	v3f vect_show;
+	v3f vect_aim;
+
+	SmoothTranslator():
+		vect_old(0,0,0),
+		anim_counter(0),
+		anim_time(0),
+		anim_time_counter(0),
+		vect_show(0,0,0),
+		vect_aim(0,0,0)
+	{}
+
+	void init(v3f vect)
+	{
+		vect_old = vect;
+		vect_show = vect;
+		vect_aim = vect;
+	}
+
+	void update(v3f vect_new)
+	{
+		vect_old = vect_show;
+		vect_aim = vect_new;
+		if(anim_time < 0.001 || anim_time > 1.0)
+			anim_time = anim_time_counter;
+		else
+			anim_time = anim_time * 0.9 + anim_time_counter * 0.1;
+		anim_time_counter = 0;
+		anim_counter = 0;
+	}
+
+	void translate(f32 dtime)
+	{
+		anim_time_counter = anim_time_counter + dtime;
+		anim_counter = anim_counter + dtime;
+		v3f vect_move = vect_aim - vect_old;
+		f32 moveratio = 1.0;
+		if(anim_time > 0.001)
+			moveratio = anim_time_counter / anim_time;
+		// Move a bit less than should, to avoid oscillation
+		moveratio = moveratio * 0.8;
+		if(moveratio > 1.5)
+			moveratio = 1.5;
+		vect_show = vect_old + vect_move * moveratio;
+	}
+};
+
 class ClientEnvironment;
 
 class ClientActiveObject : public ActiveObject
@@ -168,5 +225,47 @@ class ItemCAO : public ClientActiveObject
 	std::string m_inventorystring;
 };
 
+/*
+	RatCAO
+*/
+
+class RatCAO : public ClientActiveObject
+{
+public:
+	RatCAO();
+	virtual ~RatCAO();
+	
+	u8 getType() const
+	{
+		return ACTIVEOBJECT_TYPE_RAT;
+	}
+	
+	static ClientActiveObject* create();
+
+	void addToScene(scene::ISceneManager *smgr);
+	void removeFromScene();
+	void updateLight(u8 light_at_pos);
+	v3s16 getLightPosition();
+	void updateNodePos();
+
+	void step(float dtime, ClientEnvironment *env);
+
+	void processMessage(const std::string &data);
+
+	void initialize(const std::string &data);
+	
+	core::aabbox3d<f32>* getSelectionBox()
+		{return &m_selection_box;}
+	v3f getPosition()
+		{return m_position;}
+
+private:
+	core::aabbox3d<f32> m_selection_box;
+	scene::IMeshSceneNode *m_node;
+	v3f m_position;
+	float m_yaw;
+	SmoothTranslator pos_translator;
+};
+
 #endif
 
diff --git a/src/collision.cpp b/src/collision.cpp
new file mode 100644
index 000000000..83cefe4d1
--- /dev/null
+++ b/src/collision.cpp
@@ -0,0 +1,184 @@
+/*
+Minetest-c55
+Copyright (C) 2010 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 "collision.h"
+#include "mapblock.h"
+#include "map.h"
+
+collisionMoveResult collisionMoveSimple(Map *map, f32 pos_max_d,
+		const core::aabbox3d<f32> &box_0,
+		f32 dtime, v3f &pos_f, v3f &speed_f)
+{
+	collisionMoveResult result;
+
+	v3f oldpos_f = pos_f;
+	v3s16 oldpos_i = floatToInt(oldpos_f, BS);
+
+	/*
+		Calculate new position
+	*/
+	pos_f += speed_f * dtime;
+
+	/*
+		Collision detection
+	*/
+	
+	// position in nodes
+	v3s16 pos_i = floatToInt(pos_f, BS);
+	
+	/*
+		Collision uncertainty radius
+		Make it a bit larger than the maximum distance of movement
+	*/
+	f32 d = pos_max_d * 1.1;
+	// A fairly large value in here makes moving smoother
+	//f32 d = 0.15*BS;
+
+	// This should always apply, otherwise there are glitches
+	assert(d > pos_max_d);
+	
+	/*
+		Calculate collision box
+	*/
+	core::aabbox3d<f32> box = box_0;
+	box.MaxEdge += pos_f;
+	box.MinEdge += pos_f;
+	core::aabbox3d<f32> oldbox = box_0;
+	oldbox.MaxEdge += oldpos_f;
+	oldbox.MinEdge += oldpos_f;
+
+	/*
+		If the object lies on a walkable node, this is set to true.
+	*/
+	result.touching_ground = false;
+	
+	/*
+		Go through every node around the object
+	*/
+	for(s16 y = oldpos_i.Y - 1; y <= oldpos_i.Y + 2; y++)
+	for(s16 z = oldpos_i.Z - 1; z <= oldpos_i.Z + 1; z++)
+	for(s16 x = oldpos_i.X - 1; x <= oldpos_i.X + 1; x++)
+	{
+		try{
+			// Object collides into walkable nodes
+			if(content_walkable(map->getNode(v3s16(x,y,z)).d) == false)
+				continue;
+		}
+		catch(InvalidPositionException &e)
+		{
+			// Doing nothing here will block the object from
+			// walking over map borders
+		}
+
+		core::aabbox3d<f32> nodebox = getNodeBox(v3s16(x,y,z), BS);
+		
+		/*
+			See if the object is touching ground.
+
+			Object touches ground if object's minimum Y is near node's
+			maximum Y and object's X-Z-area overlaps with the node's
+			X-Z-area.
+
+			Use 0.15*BS so that it is easier to get on a node.
+		*/
+		if(
+				//fabs(nodebox.MaxEdge.Y-box.MinEdge.Y) < d
+				fabs(nodebox.MaxEdge.Y-box.MinEdge.Y) < 0.15*BS
+				&& nodebox.MaxEdge.X-d > box.MinEdge.X
+				&& nodebox.MinEdge.X+d < box.MaxEdge.X
+				&& nodebox.MaxEdge.Z-d > box.MinEdge.Z
+				&& nodebox.MinEdge.Z+d < box.MaxEdge.Z
+		){
+			result.touching_ground = true;
+		}
+		
+		// If object doesn't intersect with node, ignore node.
+		if(box.intersectsWithBox(nodebox) == false)
+			continue;
+		
+		/*
+			Go through every axis
+		*/
+		v3f dirs[3] = {
+			v3f(0,0,1), // back-front
+			v3f(0,1,0), // top-bottom
+			v3f(1,0,0), // right-left
+		};
+		for(u16 i=0; i<3; i++)
+		{
+			/*
+				Calculate values along the axis
+			*/
+			f32 nodemax = nodebox.MaxEdge.dotProduct(dirs[i]);
+			f32 nodemin = nodebox.MinEdge.dotProduct(dirs[i]);
+			f32 objectmax = box.MaxEdge.dotProduct(dirs[i]);
+			f32 objectmin = box.MinEdge.dotProduct(dirs[i]);
+			f32 objectmax_old = oldbox.MaxEdge.dotProduct(dirs[i]);
+			f32 objectmin_old = oldbox.MinEdge.dotProduct(dirs[i]);
+			
+			/*
+				Check collision for the axis.
+				Collision happens when object is going through a surface.
+			*/
+			bool negative_axis_collides =
+				(nodemax > objectmin && nodemax <= objectmin_old + d
+					&& speed_f.dotProduct(dirs[i]) < 0);
+			bool positive_axis_collides =
+				(nodemin < objectmax && nodemin >= objectmax_old - d
+					&& speed_f.dotProduct(dirs[i]) > 0);
+			bool main_axis_collides =
+					negative_axis_collides || positive_axis_collides;
+			
+			/*
+				Check overlap of object and node in other axes
+			*/
+			bool other_axes_overlap = true;
+			for(u16 j=0; j<3; j++)
+			{
+				if(j == i)
+					continue;
+				f32 nodemax = nodebox.MaxEdge.dotProduct(dirs[j]);
+				f32 nodemin = nodebox.MinEdge.dotProduct(dirs[j]);
+				f32 objectmax = box.MaxEdge.dotProduct(dirs[j]);
+				f32 objectmin = box.MinEdge.dotProduct(dirs[j]);
+				if(!(nodemax - d > objectmin && nodemin + d < objectmax))
+				{
+					other_axes_overlap = false;
+					break;
+				}
+			}
+			
+			/*
+				If this is a collision, revert the pos_f in the main
+				direction.
+			*/
+			if(other_axes_overlap && main_axis_collides)
+			{
+				speed_f -= speed_f.dotProduct(dirs[i]) * dirs[i];
+				pos_f -= pos_f.dotProduct(dirs[i]) * dirs[i];
+				pos_f += oldpos_f.dotProduct(dirs[i]) * dirs[i];
+			}
+		
+		}
+	} // xyz
+	
+	return result;
+}
+
+
diff --git a/src/collision.h b/src/collision.h
new file mode 100644
index 000000000..172431401
--- /dev/null
+++ b/src/collision.h
@@ -0,0 +1,43 @@
+/*
+Minetest-c55
+Copyright (C) 2010 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 COLLISION_HEADER
+#define COLLISION_HEADER
+
+#include "common_irrlicht.h"
+
+class Map;
+
+struct collisionMoveResult
+{
+	bool touching_ground;
+
+	collisionMoveResult():
+		touching_ground(false)
+	{}
+};
+
+collisionMoveResult collisionMoveSimple(Map *map, f32 pos_max_d,
+		const core::aabbox3d<f32> &box_0,
+		f32 dtime, v3f &pos_f, v3f &speed_f);
+//{return collisionMoveResult();}
+
+
+#endif
+
diff --git a/src/environment.cpp b/src/environment.cpp
index 7e1d268df..edccad1ce 100644
--- a/src/environment.cpp
+++ b/src/environment.cpp
@@ -189,7 +189,8 @@ u32 Environment::getDayNightRatio()
 ServerEnvironment::ServerEnvironment(ServerMap *map, Server *server):
 	m_map(map),
 	m_server(server),
-	m_random_spawn_timer(3)
+	m_random_spawn_timer(3),
+	m_send_recommended_timer(0)
 {
 }
 
@@ -422,19 +423,25 @@ void ServerEnvironment::step(float dtime)
 		}
 	}
 	
-	//if(g_settings.getBool("enable_experimental"))
-	{
-
 	/*
 		Step active objects
 	*/
+
+	bool send_recommended = false;
+	m_send_recommended_timer += dtime;
+	if(m_send_recommended_timer > 0.2)
+	{
+		m_send_recommended_timer = 0;
+		send_recommended = true;
+	}
+
 	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);
+		obj->step(dtime, m_active_object_messages, send_recommended);
 	}
 
 	/*
@@ -460,6 +467,19 @@ void ServerEnvironment::step(float dtime)
 			// If not m_removed, don't remove.
 			if(obj->m_removed == false)
 				continue;
+			// Delete static data from block
+			if(obj->m_static_exists)
+			{
+				MapBlock *block = m_map->getBlockNoCreateNoEx(obj->m_static_block);
+				if(block)
+				{
+					block->m_static_objects.remove(id);
+					block->setChangedFlag();
+				}
+			}
+			// If m_known_by_count > 0, don't actually remove.
+			if(obj->m_known_by_count > 0)
+				continue;
 			// Delete
 			delete obj;
 			// Id to be removed from m_active_objects
@@ -633,6 +653,9 @@ void ServerEnvironment::step(float dtime)
 		}
 	}
 
+	if(g_settings.getBool("enable_experimental"))
+	{
+
 	/*
 		TEST CODE
 	*/
@@ -668,7 +691,8 @@ void ServerEnvironment::step(float dtime)
 
 		//TestSAO *obj = new TestSAO(this, 0, pos);
 		//ServerActiveObject *obj = new ItemSAO(this, 0, pos, "CraftItem Stick 1");
-		//addActiveObject(obj);
+		ServerActiveObject *obj = new RatSAO(this, 0, pos);
+		addActiveObject(obj);
 	}
 #endif
 
@@ -1082,6 +1106,7 @@ void ClientEnvironment::step(float dtime)
 	/*
 		Step active objects
 	*/
+	
 	for(core::map<u16, ClientActiveObject*>::Iterator
 			i = m_active_objects.getIterator();
 			i.atEnd()==false; i++)
diff --git a/src/environment.h b/src/environment.h
index 9532271bb..85d2f668f 100644
--- a/src/environment.h
+++ b/src/environment.h
@@ -153,6 +153,7 @@ class ServerEnvironment : public Environment
 	core::map<u16, ServerActiveObject*> m_active_objects;
 	Queue<ActiveObjectMessage> m_active_object_messages;
 	float m_random_spawn_timer;
+	float m_send_recommended_timer;
 };
 
 #ifndef SERVER
diff --git a/src/inventory.cpp b/src/inventory.cpp
index f9b9107a0..b14828ae2 100644
--- a/src/inventory.cpp
+++ b/src/inventory.cpp
@@ -27,6 +27,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
 #include "debug.h"
 #include <sstream>
 #include "main.h"
+#include "serverobject.h"
 
 /*
 	InventoryItem
@@ -90,6 +91,19 @@ InventoryItem* InventoryItem::deSerialize(std::istream &is)
 	}
 }
 
+ServerActiveObject* InventoryItem::createSAO(ServerEnvironment *env, u16 id, v3f pos)
+{
+	/*
+		Create an ItemSAO
+	*/
+	// Get item string
+	std::ostringstream os(std::ios_base::binary);
+	serialize(os);
+	// Create object
+	ServerActiveObject *obj = new ItemSAO(env, 0, pos, os.str());
+	return obj;
+}
+
 /*
 	MaterialItem
 */
@@ -124,6 +138,48 @@ InventoryItem *MaterialItem::createCookResult()
 	CraftItem
 */
 
+#ifndef SERVER
+video::ITexture * CraftItem::getImage()
+{
+	if(g_texturesource == NULL)
+		return NULL;
+	
+	std::string name;
+
+	if(m_subname == "Stick")
+		name = "stick.png";
+	else if(m_subname == "lump_of_coal")
+		name = "lump_of_coal.png";
+	else if(m_subname == "lump_of_iron")
+		name = "lump_of_iron.png";
+	else if(m_subname == "steel_ingot")
+		name = "steel_ingot.png";
+	else if(m_subname == "rat")
+		name = "rat.png";
+	else
+		name = "cloud.png";
+	
+	// Get such a texture
+	//return g_irrlicht->getTexture(name);
+	return g_texturesource->getTextureRaw(name);
+}
+#endif
+
+ServerActiveObject* CraftItem::createSAO(ServerEnvironment *env, u16 id, v3f pos)
+{
+	// Special cases
+	if(m_subname == "rat")
+	{
+		ServerActiveObject *obj = new RatSAO(env, id, pos);
+		return obj;
+	}
+	// Default
+	else
+	{
+		return InventoryItem::createSAO(env, id, pos);
+	}
+}
+
 bool CraftItem::isCookable()
 {
 	if(m_subname == "lump_of_iron")
@@ -144,6 +200,7 @@ InventoryItem *CraftItem::createCookResult()
 
 /*
 	MapBlockObjectItem
+	TODO: Remove
 */
 #ifndef SERVER
 video::ITexture * MapBlockObjectItem::getImage()
diff --git a/src/inventory.h b/src/inventory.h
index 761e664a9..3ba655880 100644
--- a/src/inventory.h
+++ b/src/inventory.h
@@ -35,6 +35,9 @@ with this program; if not, write to the Free Software Foundation, Inc.,
 
 #define QUANTITY_ITEM_MAX_COUNT 99
 
+class ServerActiveObject;
+class ServerEnvironment;
+
 class InventoryItem
 {
 public:
@@ -54,6 +57,12 @@ class InventoryItem
 #endif
 	// Shall return a text to show in the GUI
 	virtual std::string getText() { return ""; }
+	// Creates an object from the item, to be placed in the world.
+	virtual ServerActiveObject* createSAO(ServerEnvironment *env, u16 id, v3f pos);
+
+	/*
+		Quantity methods
+	*/
 
 	// Shall return true if the item can be add()ed to the other
 	virtual bool addableTo(InventoryItem *other)
@@ -61,9 +70,6 @@ class InventoryItem
 		return false;
 	}
 	
-	/*
-		Quantity methods
-	*/
 	u16 getCount()
 	{
 		return m_count;
@@ -175,6 +181,7 @@ class MaterialItem : public InventoryItem
 	u8 m_content;
 };
 
+//TODO: Remove
 class MapBlockObjectItem : public InventoryItem
 {
 public:
@@ -262,28 +269,7 @@ class CraftItem : public InventoryItem
 		return new CraftItem(m_subname, m_count);
 	}
 #ifndef SERVER
-	video::ITexture * getImage()
-	{
-		if(g_texturesource == NULL)
-			return NULL;
-		
-		std::string name;
-
-		if(m_subname == "Stick")
-			name = "stick.png";
-		else if(m_subname == "lump_of_coal")
-			name = "lump_of_coal.png";
-		else if(m_subname == "lump_of_iron")
-			name = "lump_of_iron.png";
-		else if(m_subname == "steel_ingot")
-			name = "steel_ingot.png";
-		else
-			name = "cloud.png";
-		
-		// Get such a texture
-		//return g_irrlicht->getTexture(name);
-		return g_texturesource->getTextureRaw(name);
-	}
+	video::ITexture * getImage();
 #endif
 	std::string getText()
 	{
@@ -291,6 +277,9 @@ class CraftItem : public InventoryItem
 		os<<m_count;
 		return os.str();
 	}
+
+	ServerActiveObject* createSAO(ServerEnvironment *env, u16 id, v3f pos);
+
 	virtual bool addableTo(InventoryItem *other)
 	{
 		if(std::string(other->getName()) != "CraftItem")
diff --git a/src/mapblock.cpp b/src/mapblock.cpp
index a4e13320e..377e28435 100644
--- a/src/mapblock.cpp
+++ b/src/mapblock.cpp
@@ -1295,7 +1295,7 @@ MapBlock::MapBlock(NodeContainer *parent, v3s16 pos, bool dummy):
 	if(dummy == false)
 		reallocate();
 	
-	m_spawn_timer = -10000;
+	//m_spawn_timer = -10000;
 
 #ifndef SERVER
 	m_mesh_expired = false;
@@ -1687,7 +1687,8 @@ void MapBlock::stepObjects(float dtime, bool server, u32 daynight_ratio)
 		Step objects
 	*/
 	m_objects.step(dtime, server, daynight_ratio);
-	
+
+#if 0
 	/*
 		Spawn some objects at random.
 
@@ -1724,6 +1725,7 @@ void MapBlock::stepObjects(float dtime, bool server, u32 daynight_ratio)
 			}
 		}
 	}
+#endif
 
 	setChangedFlag();
 }
diff --git a/src/mapblock.h b/src/mapblock.h
index ce5682568..30e9e38f8 100644
--- a/src/mapblock.h
+++ b/src/mapblock.h
@@ -729,10 +729,11 @@ class MapBlock : public NodeContainer
 	// Whether day and night lighting differs
 	bool m_day_night_differs;
 	
+	// TODO: Remove this
 	MapBlockObjectList m_objects;
 
 	// Object spawning stuff
-	float m_spawn_timer;
+	//float m_spawn_timer;
 
 #ifndef SERVER // Only on client
 	/*
diff --git a/src/server.cpp b/src/server.cpp
index 4334d77c7..04d204a2a 100644
--- a/src/server.cpp
+++ b/src/server.cpp
@@ -2045,28 +2045,26 @@ void Server::ProcessData(u8 *data, u32 datasize, u16 peer_id)
 					dout_server<<"Player inventory has no free space"<<std::endl;
 					return;
 				}
+
+				// Skip if object has been removed
+				if(obj->m_removed)
+					return;
 				
 				/*
 					Create the inventory item
 				*/
-				InventoryItem *item = NULL;
-				// If it is an item-object, take the item from it
-				if(obj->getType() == ACTIVEOBJECT_TYPE_ITEM
-						&& obj->m_removed == false)
-				{
-					item = ((ItemSAO*)obj)->createInventoryItem();
-				}
+				InventoryItem *item = obj->createPickedUpItem();
 				
 				if(item)
 				{
 					// Add to inventory and send inventory
 					ilist->addItem(item);
 					SendInventory(player->peer_id);
+
+					// Remove object from environment
+					obj->m_removed = true;
 				}
 			}
-
-			// Remove object from environment
-			obj->m_removed = true;
 		}
 	}
 	else if(command == TOSERVER_GROUND_ACTION)
@@ -2448,15 +2446,9 @@ void Server::ProcessData(u8 *data, u32 datasize, u16 peer_id)
 				pos.Z += BS*0.2*(float)myrand_range(-1000,1000)/1000.0;
 
 				/*
-					Create an ItemSAO
+					Create the object
 				*/
-				// Get item string
-				std::ostringstream os(std::ios_base::binary);
-				item->serialize(os);
-				dout_server<<"Item string is \""<<os.str()<<"\""<<std::endl;
-				// Create object
-				ServerActiveObject *obj = new ItemSAO
-						(&m_env, 0, pos, os.str());
+				ServerActiveObject *obj = item->createSAO(&m_env, 0, pos);
 
 				if(obj == NULL)
 				{
@@ -2471,11 +2463,22 @@ void Server::ProcessData(u8 *data, u32 datasize, u16 peer_id)
 					
 					dout_server<<"Placed object"<<std::endl;
 
-					InventoryList *ilist = player->inventory.getList("main");
-					if(g_settings.getBool("creative_mode") == false && ilist)
+					// If item has count<=1, delete it
+					if(item->getCount() <= 1)
 					{
-						// Remove from inventory and send inventory
-						ilist->deleteItem(item_i);
+						InventoryList *ilist = player->inventory.getList("main");
+						if(g_settings.getBool("creative_mode") == false && ilist)
+						{
+							// Remove from inventory and send inventory
+							ilist->deleteItem(item_i);
+							// Send inventory
+							SendInventory(peer_id);
+						}
+					}
+					// Else decrement it
+					else
+					{
+						item->remove(1);
 						// Send inventory
 						SendInventory(peer_id);
 					}
diff --git a/src/serverobject.cpp b/src/serverobject.cpp
index 92a0b83f4..ce7259d67 100644
--- a/src/serverobject.cpp
+++ b/src/serverobject.cpp
@@ -91,7 +91,8 @@ ServerActiveObject* TestSAO::create(ServerEnvironment *env, u16 id, v3f pos,
 	return new TestSAO(env, id, pos);
 }
 
-void TestSAO::step(float dtime, Queue<ActiveObjectMessage> &messages)
+void TestSAO::step(float dtime, Queue<ActiveObjectMessage> &messages,
+		bool send_recommended)
 {
 	m_age += dtime;
 	if(m_age > 10)
@@ -104,6 +105,9 @@ void TestSAO::step(float dtime, Queue<ActiveObjectMessage> &messages)
 	if(m_base_position.Y > 8*BS)
 		m_base_position.Y = 2*BS;
 
+	if(send_recommended == false)
+		return;
+
 	m_timer1 -= dtime;
 	if(m_timer1 < 0.0)
 	{
@@ -137,7 +141,8 @@ ItemSAO::ItemSAO(ServerEnvironment *env, u16 id, v3f pos,
 		const std::string inventorystring):
 	ServerActiveObject(env, id, pos),
 	m_inventorystring(inventorystring),
-	m_speed_f(0,0,0)
+	m_speed_f(0,0,0),
+	m_last_sent_position(0,0,0)
 {
 	dstream<<"Server: ItemSAO created with inventorystring=\""
 			<<m_inventorystring<<"\""<<std::endl;
@@ -161,7 +166,8 @@ ServerActiveObject* ItemSAO::create(ServerEnvironment *env, u16 id, v3f pos,
 	return new ItemSAO(env, id, pos, inventorystring);
 }
 
-void ItemSAO::step(float dtime, Queue<ActiveObjectMessage> &messages)
+void ItemSAO::step(float dtime, Queue<ActiveObjectMessage> &messages,
+		bool send_recommended)
 {
 	core::aabbox3d<f32> box(-BS/3.,0.0,-BS/3., BS/3.,BS*2./3.,BS/3.);
 	collisionMoveResult moveresult;
@@ -177,9 +183,13 @@ void ItemSAO::step(float dtime, Queue<ActiveObjectMessage> &messages)
 	moveresult = collisionMoveSimple(&m_env->getMap(), pos_max_d,
 			box, dtime, pos_f, m_speed_f);
 	
-	if(pos_f.getDistanceFrom(pos_f_old) > 0.01*BS)
+	if(send_recommended == false)
+		return;
+
+	if(pos_f.getDistanceFrom(m_last_sent_position) > 0.05*BS)
 	{
 		setBasePosition(pos_f);
+		m_last_sent_position = pos_f;
 
 		std::ostringstream os(std::ios::binary);
 		char buf[6];
@@ -250,4 +260,180 @@ InventoryItem * ItemSAO::createInventoryItem()
 }
 
 
+/*
+	RatSAO
+*/
+
+// Prototype
+RatSAO proto_RatSAO(NULL, 0, v3f(0,0,0));
+
+RatSAO::RatSAO(ServerEnvironment *env, u16 id, v3f pos):
+	ServerActiveObject(env, id, pos),
+	m_speed_f(0,0,0)
+{
+	dstream<<"Server: RatSAO created"<<std::endl;
+	ServerActiveObject::registerType(getType(), create);
+
+	m_oldpos = v3f(0,0,0);
+	m_last_sent_position = v3f(0,0,0);
+	m_yaw = 0;
+	m_counter1 = 0;
+	m_counter2 = 0;
+	m_age = 0;
+	m_touching_ground = false;
+}
+
+ServerActiveObject* RatSAO::create(ServerEnvironment *env, u16 id, v3f pos,
+		const std::string &data)
+{
+	std::istringstream is(data, std::ios::binary);
+	char buf[1];
+	// read version
+	is.read(buf, 1);
+	u8 version = buf[0];
+	// check if version is supported
+	if(version != 0)
+		return NULL;
+	return new RatSAO(env, id, pos);
+}
+
+void RatSAO::step(float dtime, Queue<ActiveObjectMessage> &messages,
+		bool send_recommended)
+{
+	/*
+		The AI
+	*/
+
+	m_age += dtime;
+	if(m_age > 60)
+	{
+		// Die
+		m_removed = true;
+		return;
+	}
+
+	// Apply gravity
+	m_speed_f.Y -= dtime*9.81*BS;
+
+	/*
+		Move around if some player is close
+	*/
+	bool player_is_close = false;
+	// Check connected players
+	core::list<Player*> players = m_env->getPlayers(true);
+	core::list<Player*>::Iterator i;
+	for(i = players.begin();
+			i != players.end(); i++)
+	{
+		Player *player = *i;
+		v3f playerpos = player->getPosition();
+		if(m_base_position.getDistanceFrom(playerpos) < BS*10.0)
+		{
+			player_is_close = true;
+			break;
+		}
+	}
+	
+	if(player_is_close == false)
+	{
+		m_speed_f.X = 0;
+		m_speed_f.Z = 0;
+	}
+	else
+	{
+		// Move around
+		v3f dir(cos(m_yaw/180*PI),0,sin(m_yaw/180*PI));
+		f32 speed = 2*BS;
+		m_speed_f.X = speed * dir.X;
+		m_speed_f.Z = speed * dir.Z;
+
+		if(m_touching_ground && (m_oldpos - m_base_position).getLength()
+				< dtime*speed/2)
+		{
+			m_counter1 -= dtime;
+			if(m_counter1 < 0.0)
+			{
+				m_counter1 += 1.0;
+				m_speed_f.Y = 5.0*BS;
+			}
+		}
+
+		{
+			m_counter2 -= dtime;
+			if(m_counter2 < 0.0)
+			{
+				m_counter2 += (float)(myrand()%100)/100*3.0;
+				m_yaw += ((float)(myrand()%200)-100)/100*180;
+				m_yaw = wrapDegrees(m_yaw);
+			}
+		}
+	}
+	
+	m_oldpos = m_base_position;
+
+	/*
+		Move it, with collision detection
+	*/
+
+	core::aabbox3d<f32> box(-BS/3.,0.0,-BS/3., BS/3.,BS*2./3.,BS/3.);
+	collisionMoveResult moveresult;
+	// Maximum movement without glitches
+	f32 pos_max_d = BS*0.25;
+	// Limit speed
+	if(m_speed_f.getLength()*dtime > pos_max_d)
+		m_speed_f *= pos_max_d / (m_speed_f.getLength()*dtime);
+	v3f pos_f = getBasePosition();
+	v3f pos_f_old = pos_f;
+	moveresult = collisionMoveSimple(&m_env->getMap(), pos_max_d,
+			box, dtime, pos_f, m_speed_f);
+	m_touching_ground = moveresult.touching_ground;
+	
+	setBasePosition(pos_f);
+
+	if(send_recommended == false)
+		return;
+
+	if(pos_f.getDistanceFrom(m_last_sent_position) > 0.05*BS)
+	{
+		m_last_sent_position = pos_f;
+
+		std::ostringstream os(std::ios::binary);
+		// command (0 = update position)
+		writeU8(os, 0);
+		// pos
+		writeV3F1000(os, m_base_position);
+		// yaw
+		writeF1000(os, m_yaw);
+		// create message and add to list
+		ActiveObjectMessage aom(getId(), false, os.str());
+		messages.push_back(aom);
+	}
+}
+
+std::string RatSAO::getClientInitializationData()
+{
+	std::ostringstream os(std::ios::binary);
+	// version
+	writeU8(os, 0);
+	// pos
+	writeV3F1000(os, m_base_position);
+	return os.str();
+}
+
+std::string RatSAO::getStaticData()
+{
+	dstream<<__FUNCTION_NAME<<std::endl;
+	std::ostringstream os(std::ios::binary);
+	// version
+	writeU8(os, 0);
+	return os.str();
+}
+
+InventoryItem* RatSAO::createPickedUpItem()
+{
+	std::istringstream is("CraftItem rat 1", std::ios_base::binary);
+	InventoryItem *item = InventoryItem::deSerialize(is);
+	return item;
+}
+
 
diff --git a/src/serverobject.h b/src/serverobject.h
index 5e3afb57b..d3dabdd4d 100644
--- a/src/serverobject.h
+++ b/src/serverobject.h
@@ -40,6 +40,38 @@ Some planning
 
 */
 
+#if 0
+class IntervalLimiter
+{
+public:
+	IntervalLimiter():
+		m_accumulator(0)
+	{
+	}
+	/*
+		dtime: time from last call to this method
+		wanted_interval: interval wanted
+		return value:
+			true: action should be skipped
+			false: action should be done and dtime has been set
+	*/
+	bool step(float &dtime, float wanted_interval)
+	{
+		accumulator += dtime;
+		if(accumulator < wanted_interval)
+		{
+			dtime = 0;
+			return true;
+		}
+		accumulator -= wanted-interval;
+		dtime = wanted_interval;
+		return false;
+	}
+protected:
+	float m_accumulator;
+};
+#endif
+
 class ServerEnvironment;
 class InventoryItem;
 
@@ -67,8 +99,15 @@ class ServerActiveObject : public ActiveObject
 	/*
 		Step object in time.
 		Messages added to messages are sent to client over network.
+
+		send_recommended:
+			True at around 5 times a second, same for all objects.
+			This is used to let objects send most of the data at the
+			same time so that the data can be combined in a single
+			packet.
 	*/
-	virtual void step(float dtime, Queue<ActiveObjectMessage> &messages){}
+	virtual void step(float dtime, Queue<ActiveObjectMessage> &messages,
+			bool send_recommended){}
 	
 	/*
 		The return value of this is passed to the client-side object
@@ -83,6 +122,12 @@ class ServerActiveObject : public ActiveObject
 	*/
 	virtual std::string getStaticData(){return "";}
 	
+	/*
+		Item that the player gets when this object is picked up.
+		If NULL, object cannot be picked up.
+	*/
+	virtual InventoryItem* createPickedUpItem(){return NULL;}
+	
 	// Number of players which know about this object
 	u16 m_known_by_count;
 	/*
@@ -129,7 +174,8 @@ class TestSAO : public ServerActiveObject
 		{return ACTIVEOBJECT_TYPE_TEST;}
 	static ServerActiveObject* create(ServerEnvironment *env, u16 id, v3f pos,
 			const std::string &data);
-	void step(float dtime, Queue<ActiveObjectMessage> &messages);
+	void step(float dtime, Queue<ActiveObjectMessage> &messages,
+			bool send_recommended);
 private:
 	float m_timer1;
 	float m_age;
@@ -144,13 +190,40 @@ class ItemSAO : public ServerActiveObject
 		{return ACTIVEOBJECT_TYPE_ITEM;}
 	static ServerActiveObject* create(ServerEnvironment *env, u16 id, v3f pos,
 			const std::string &data);
-	void step(float dtime, Queue<ActiveObjectMessage> &messages);
+	void step(float dtime, Queue<ActiveObjectMessage> &messages,
+			bool send_recommended);
 	std::string getClientInitializationData();
 	std::string getStaticData();
 	InventoryItem* createInventoryItem();
+	InventoryItem* createPickedUpItem(){return createInventoryItem();}
 private:
 	std::string m_inventorystring;
 	v3f m_speed_f;
+	v3f m_last_sent_position;
+};
+
+class RatSAO : public ServerActiveObject
+{
+public:
+	RatSAO(ServerEnvironment *env, u16 id, v3f pos);
+	u8 getType() const
+		{return ACTIVEOBJECT_TYPE_RAT;}
+	static ServerActiveObject* create(ServerEnvironment *env, u16 id, v3f pos,
+			const std::string &data);
+	void step(float dtime, Queue<ActiveObjectMessage> &messages,
+			bool send_recommended);
+	std::string getClientInitializationData();
+	std::string getStaticData();
+	InventoryItem* createPickedUpItem();
+private:
+	v3f m_speed_f;
+	v3f m_oldpos;
+	v3f m_last_sent_position;
+	float m_yaw;
+	float m_counter1;
+	float m_counter2;
+	float m_age;
+	bool m_touching_ground;
 };
 
 #endif
diff --git a/src/utility.h b/src/utility.h
index 2b143f0ba..f2f3018df 100644
--- a/src/utility.h
+++ b/src/utility.h
@@ -95,8 +95,6 @@ inline u8 readU8(u8 *data)
 	return (data[0]<<0);
 }
 
-// Signed variants of the above
-
 inline void writeS32(u8 *data, s32 i){
 	writeU32(data, (u32)i);
 }
@@ -104,6 +102,13 @@ inline s32 readS32(u8 *data){
 	return (s32)readU32(data);
 }
 
+inline void writeF1000(u8 *data, f32 i){
+	writeS32(data, i*1000);
+}
+inline f32 readF1000(u8 *data){
+	return (f32)readS32(data)/1000.;
+}
+
 inline void writeS16(u8 *data, s16 i){
 	writeU16(data, (u16)i);
 }
@@ -117,7 +122,6 @@ inline void writeV3S32(u8 *data, v3s32 p)
 	writeS32(&data[4], p.Y);
 	writeS32(&data[8], p.Z);
 }
-
 inline v3s32 readV3S32(u8 *data)
 {
 	v3s32 p;
@@ -127,6 +131,21 @@ inline v3s32 readV3S32(u8 *data)
 	return p;
 }
 
+inline void writeV3F1000(u8 *data, v3f p)
+{
+	writeF1000(&data[0], p.X);
+	writeF1000(&data[4], p.Y);
+	writeF1000(&data[8], p.Z);
+}
+inline v3f readV3F1000(u8 *data)
+{
+	v3f p;
+	p.X = (float)readF1000(&data[0]);
+	p.Y = (float)readF1000(&data[4]);
+	p.Z = (float)readF1000(&data[8]);
+	return p;
+}
+
 inline void writeV2S16(u8 *data, v2s16 p)
 {
 	writeS16(&data[0], p.X);
@@ -171,6 +190,62 @@ inline v3s16 readV3S16(u8 *data)
 	return p;
 }
 
+/*
+	The above stuff directly interfaced to iostream
+*/
+
+inline void writeU8(std::ostream &os, u8 p)
+{
+	char buf[1];
+	writeU8((u8*)buf, p);
+	os.write(buf, 1);
+}
+inline u8 readU8(std::istream &is)
+{
+	char buf[1];
+	is.read(buf, 1);
+	return readU8((u8*)buf);
+}
+
+inline void writeU16(std::ostream &os, u16 p)
+{
+	char buf[2];
+	writeU16((u8*)buf, p);
+	os.write(buf, 2);
+}
+inline u16 readU16(std::istream &is)
+{
+	char buf[12];
+	is.read(buf, 12);
+	return readU16((u8*)buf);
+}
+
+inline void writeF1000(std::ostream &os, f32 p)
+{
+	char buf[2];
+	writeF1000((u8*)buf, p);
+	os.write(buf, 2);
+}
+inline f32 readF1000(std::istream &is)
+{
+	char buf[12];
+	is.read(buf, 12);
+	return readF1000((u8*)buf);
+}
+
+inline void writeV3F1000(std::ostream &os, v3f p)
+{
+	char buf[12];
+	writeV3F1000((u8*)buf, p);
+	os.write(buf, 12);
+}
+inline v3f readV3F1000(std::istream &is)
+{
+	char buf[12];
+	is.read(buf, 12);
+	return readV3F1000((u8*)buf);
+}
+
 /*
 	None of these are used at the moment
 */
-- 
GitLab