From e9cdb938fe44282e09fb88628a6e86e5e7279c69 Mon Sep 17 00:00:00 2001
From: Perttu Ahola <celeron55@gmail.com>
Date: Sun, 4 Mar 2012 21:08:03 +0200
Subject: [PATCH] Entity damage system WIP; Remove C++ mobs

---
 data/mods/default/init.lua |   20 +-
 src/content_abm.cpp        |  115 ---
 src/content_cao.cpp        | 1611 ++------------------------------
 src/content_sao.cpp        | 1796 +++++++-----------------------------
 src/content_sao.h          |  182 +---
 src/itemgroup.h            |   39 +
 src/luaentity_common.cpp   |   10 +-
 src/luaentity_common.h     |    6 +
 src/scriptapi.cpp          |   53 +-
 src/scriptapi.h            |    4 +-
 src/server.cpp             |   15 +-
 src/serverobject.h         |   10 +-
 src/serverremoteplayer.cpp |   20 +-
 src/serverremoteplayer.h   |    5 +-
 14 files changed, 553 insertions(+), 3333 deletions(-)
 create mode 100644 src/itemgroup.h

diff --git a/data/mods/default/init.lua b/data/mods/default/init.lua
index 9750f610f..bcd633b34 100644
--- a/data/mods/default/init.lua
+++ b/data/mods/default/init.lua
@@ -202,7 +202,7 @@
 -- - getpos() -> {x=num, y=num, z=num}
 -- - setpos(pos); pos={x=num, y=num, z=num}
 -- - moveto(pos, continuous=false): interpolated move
--- - punch(puncher, time_from_last_punch)
+-- - punch(puncher, time_from_last_punch, tool_capabilities, direction)
 --   ^ puncher = an another ObjectRef,
 --   ^ time_from_last_punch = time since last punch action of the puncher
 -- - right_click(clicker); clicker = an another ObjectRef
@@ -1124,14 +1124,14 @@ minetest.register_node("default:tree", {
 	description = "Tree",
 	tile_images = {"default_tree_top.png", "default_tree_top.png", "default_tree.png"},
 	is_ground_content = true,
-	groups = {snappy=2},
+	groups = {snappy=2,choppy=2},
 })
 
 minetest.register_node("default:jungletree", {
 	description = "Jungle Tree",
 	tile_images = {"default_jungletree_top.png", "default_jungletree_top.png", "default_jungletree.png"},
 	is_ground_content = true,
-	groups = {snappy=2},
+	groups = {snappy=2,choppy=2},
 })
 
 minetest.register_node("default:junglegrass", {
@@ -1174,7 +1174,7 @@ minetest.register_node("default:cactus", {
 	description = "Cactus",
 	tile_images = {"default_cactus_top.png", "default_cactus_top.png", "default_cactus_side.png"},
 	is_ground_content = true,
-	groups = {snappy=2},
+	groups = {snappy=2,choppy=3},
 })
 
 minetest.register_node("default:papyrus", {
@@ -1193,7 +1193,7 @@ minetest.register_node("default:bookshelf", {
 	description = "Bookshelf",
 	tile_images = {"default_wood.png", "default_wood.png", "default_bookshelf.png"},
 	is_ground_content = true,
-	groups = {snappy=2},
+	groups = {snappy=2,choppy=3},
 })
 
 minetest.register_node("default:glass", {
@@ -1219,7 +1219,7 @@ minetest.register_node("default:fence_wood", {
 		type = "fixed",
 		fixed = {-1/7, -1/2, -1/7, 1/7, 1/2, 1/7},
 	},
-	groups = {snappy=2},
+	groups = {snappy=2,choppy=2},
 })
 
 minetest.register_node("default:rail", {
@@ -1255,7 +1255,7 @@ minetest.register_node("default:ladder", {
 		--wall_bottom = = <default>
 		--wall_side = = <default>
 	},
-	groups = {snappy=2},
+	groups = {snappy=2,choppy=2},
 	legacy_wallmounted = true,
 })
 
@@ -1263,7 +1263,7 @@ minetest.register_node("default:wood", {
 	description = "Wood",
 	tile_images = {"default_wood.png"},
 	is_ground_content = true,
-	groups = {snappy=2},
+	groups = {snappy=2,choppy=2},
 })
 
 minetest.register_node("default:mese", {
@@ -1421,7 +1421,7 @@ minetest.register_node("default:chest", {
 		"default_chest_side.png", "default_chest_side.png", "default_chest_front.png"},
 	paramtype2 = "facedir",
 	metadata_name = "chest",
-	groups = {snappy=2},
+	groups = {snappy=2,choppy=2},
 	legacy_facedir_simple = true,
 })
 
@@ -1431,7 +1431,7 @@ minetest.register_node("default:chest_locked", {
 		"default_chest_side.png", "default_chest_side.png", "default_chest_lock.png"},
 	paramtype2 = "facedir",
 	metadata_name = "locked_chest",
-	groups = {snappy=2},
+	groups = {snappy=2,choppy=2},
 	legacy_facedir_simple = true,
 })
 
diff --git a/src/content_abm.cpp b/src/content_abm.cpp
index 63867b78b..3a92796a4 100644
--- a/src/content_abm.cpp
+++ b/src/content_abm.cpp
@@ -88,119 +88,6 @@ class RemoveGrassABM : public ActiveBlockModifier
 	}
 };
 
-class SpawnRatsAroundTreesABM : public ActiveBlockModifier
-{
-private:
-public:
-	virtual std::set<std::string> getTriggerContents()
-	{
-		std::set<std::string> s;
-		s.insert("tree");
-		s.insert("jungletree");
-		return s;
-	}
-	virtual float getTriggerInterval()
-	{ return 10.0; }
-	virtual u32 getTriggerChance()
-	{ return 200; }
-	virtual void trigger(ServerEnvironment *env, v3s16 p, MapNode n,
-			u32 active_object_count, u32 active_object_count_wider)
-	{
-		if(active_object_count_wider != 0)
-			return;
-
-		INodeDefManager *ndef = env->getGameDef()->ndef();
-		ServerMap *map = &env->getServerMap();
-		
-		v3s16 p1 = p + v3s16(myrand_range(-2, 2),
-				0, myrand_range(-2, 2));
-		MapNode n1 = map->getNodeNoEx(p1);
-		MapNode n1b = map->getNodeNoEx(p1+v3s16(0,-1,0));
-		if(n1b.getContent() == ndef->getId("dirt_with_grass") &&
-				n1.getContent() == CONTENT_AIR)
-		{
-			v3f pos = intToFloat(p1, BS);
-			ServerActiveObject *obj = new RatSAO(env, pos);
-			env->addActiveObject(obj);
-		}
-	}
-};
-
-static void getMob_dungeon_master(Settings &properties)
-{
-	properties.set("looks", "dungeon_master");
-	properties.setFloat("yaw", 1.57);
-	properties.setFloat("hp", 30);
-	properties.setBool("bright_shooting", true);
-	properties.set("shoot_type", "fireball");
-	properties.set("shoot_y", "0.7");
-	properties.set("player_hit_damage", "1");
-	properties.set("player_hit_distance", "1.0");
-	properties.set("player_hit_interval", "0.5");
-	properties.setBool("mindless_rage", myrand_range(0,100)==0);
-}
-
-class SpawnInCavesABM : public ActiveBlockModifier
-{
-private:
-public:
-	virtual std::set<std::string> getTriggerContents()
-	{
-		std::set<std::string> s;
-		s.insert("stone");
-		s.insert("mossycobble");
-		return s;
-	}
-	virtual float getTriggerInterval()
-	{ return 2.0; }
-	virtual u32 getTriggerChance()
-	{ return 1000; }
-	virtual void trigger(ServerEnvironment *env, v3s16 p, MapNode n,
-			u32 active_object_count, u32 active_object_count_wider)
-	{
-		if(active_object_count_wider != 0)
-			return;
-
-		INodeDefManager *ndef = env->getGameDef()->ndef();
-		ServerMap *map = &env->getServerMap();
-
-		v3s16 p1 = p + v3s16(0,1,0);
-		MapNode n1a = map->getNodeNoEx(p1+v3s16(0,0,0));
-		if(n1a.getLightBlend(env->getDayNightRatio(), ndef) <= 3){
-			MapNode n1b = map->getNodeNoEx(p1+v3s16(0,1,0));
-			if(n1a.getContent() == CONTENT_AIR &&
-					n1b.getContent() == CONTENT_AIR)
-			{
-				v3f pos = intToFloat(p1, BS);
-				int i = myrand()%5;
-				if(i == 0 || i == 1){
-					actionstream<<"A dungeon master spawns at "
-							<<PP(p1)<<std::endl;
-					Settings properties;
-					getMob_dungeon_master(properties);
-					ServerActiveObject *obj = new MobV2SAO(
-							env, pos, &properties);
-					env->addActiveObject(obj);
-				} else if(i == 2 || i == 3){
-					actionstream<<"Rats spawn at "
-							<<PP(p1)<<std::endl;
-					for(int j=0; j<3; j++){
-						ServerActiveObject *obj = new RatSAO(
-								env, pos);
-						env->addActiveObject(obj);
-					}
-				} else {
-					actionstream<<"An oerkki spawns at "
-							<<PP(p1)<<std::endl;
-					ServerActiveObject *obj = new Oerkki1SAO(
-							env, pos);
-					env->addActiveObject(obj);
-				}
-			}
-		}
-	}
-};
-
 class MakeTreesFromSaplingsABM : public ActiveBlockModifier
 {
 private:
@@ -261,8 +148,6 @@ void add_legacy_abms(ServerEnvironment *env, INodeDefManager *nodedef)
 {
 	env->addActiveBlockModifier(new GrowGrassABM());
 	env->addActiveBlockModifier(new RemoveGrassABM());
-	env->addActiveBlockModifier(new SpawnRatsAroundTreesABM());
-	env->addActiveBlockModifier(new SpawnInCavesABM());
 	env->addActiveBlockModifier(new MakeTreesFromSaplingsABM());
 }
 
diff --git a/src/content_cao.cpp b/src/content_cao.cpp
index 3c30a0819..d6289a1b9 100644
--- a/src/content_cao.cpp
+++ b/src/content_cao.cpp
@@ -116,1564 +116,99 @@ struct SmoothTranslator
 	}
 };
 
-
-/*
-	TestCAO
-*/
-
-class TestCAO : public ClientActiveObject
-{
-public:
-	TestCAO(IGameDef *gamedef, ClientEnvironment *env);
-	virtual ~TestCAO();
-	
-	u8 getType() const
-	{
-		return ACTIVEOBJECT_TYPE_TEST;
-	}
-	
-	static ClientActiveObject* create(IGameDef *gamedef, ClientEnvironment *env);
-
-	void addToScene(scene::ISceneManager *smgr, ITextureSource *tsrc,
-			IrrlichtDevice *irr);
-	void removeFromScene();
-	void updateLight(u8 light_at_pos);
-	v3s16 getLightPosition();
-	void updateNodePos();
-
-	void step(float dtime, ClientEnvironment *env);
-
-	void processMessage(const std::string &data);
-
-private:
-	scene::IMeshSceneNode *m_node;
-	v3f m_position;
-};
-
-/*
-	ItemCAO
-*/
-
-class ItemCAO : public ClientActiveObject
-{
-public:
-	ItemCAO(IGameDef *gamedef, ClientEnvironment *env);
-	virtual ~ItemCAO();
-	
-	u8 getType() const
-	{
-		return ACTIVEOBJECT_TYPE_ITEM;
-	}
-	
-	static ClientActiveObject* create(IGameDef *gamedef, ClientEnvironment *env);
-
-	void addToScene(scene::ISceneManager *smgr, ITextureSource *tsrc,
-			IrrlichtDevice *irr);
-	void removeFromScene();
-	void updateLight(u8 light_at_pos);
-	v3s16 getLightPosition();
-	void updateNodePos();
-	void updateInfoText();
-	void updateTexture();
-
-	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;}
-	
-	std::string infoText()
-		{return m_infotext;}
-
-private:
-	core::aabbox3d<f32> m_selection_box;
-	scene::IMeshSceneNode *m_node;
-	v3f m_position;
-	std::string m_itemstring;
-	std::string m_infotext;
-};
-
-/*
-	RatCAO
-*/
-
-class RatCAO : public ClientActiveObject
-{
-public:
-	RatCAO(IGameDef *gamedef, ClientEnvironment *env);
-	virtual ~RatCAO();
-	
-	u8 getType() const
-	{
-		return ACTIVEOBJECT_TYPE_RAT;
-	}
-	
-	static ClientActiveObject* create(IGameDef *gamedef, ClientEnvironment *env);
-
-	void addToScene(scene::ISceneManager *smgr, ITextureSource *tsrc,
-			IrrlichtDevice *irr);
-	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 pos_translator.vect_show;}
-		//{return m_position;}
-
-private:
-	core::aabbox3d<f32> m_selection_box;
-	scene::IMeshSceneNode *m_node;
-	v3f m_position;
-	float m_yaw;
-	SmoothTranslator pos_translator;
-};
-
-/*
-	Oerkki1CAO
-*/
-
-class Oerkki1CAO : public ClientActiveObject
-{
-public:
-	Oerkki1CAO(IGameDef *gamedef, ClientEnvironment *env);
-	virtual ~Oerkki1CAO();
-	
-	u8 getType() const
-	{
-		return ACTIVEOBJECT_TYPE_OERKKI1;
-	}
-	
-	static ClientActiveObject* create(IGameDef *gamedef, ClientEnvironment *env);
-
-	void addToScene(scene::ISceneManager *smgr, ITextureSource *tsrc,
-			IrrlichtDevice *irr);
-	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 pos_translator.vect_show;}
-		//{return m_position;}
-
-	// If returns true, punch will not be sent to the server
-	bool directReportPunch(const std::string &toolname, v3f dir);
-
-private:
-	IntervalLimiter m_attack_interval;
-	core::aabbox3d<f32> m_selection_box;
-	scene::IMeshSceneNode *m_node;
-	v3f m_position;
-	float m_yaw;
-	SmoothTranslator pos_translator;
-	float m_damage_visual_timer;
-	bool m_damage_texture_enabled;
-};
-
-/*
-	FireflyCAO
-*/
-
-class FireflyCAO : public ClientActiveObject
-{
-public:
-	FireflyCAO(IGameDef *gamedef, ClientEnvironment *env);
-	virtual ~FireflyCAO();
-	
-	u8 getType() const
-	{
-		return ACTIVEOBJECT_TYPE_FIREFLY;
-	}
-	
-	static ClientActiveObject* create(IGameDef *gamedef, ClientEnvironment *env);
-
-	void addToScene(scene::ISceneManager *smgr, ITextureSource *tsrc,
-			IrrlichtDevice *irr);
-	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;
-};
-
-static void setBillboardTextureMatrix(scene::IBillboardSceneNode *bill,
-		float txs, float tys, int col, int row)
-{
-	video::SMaterial& material = bill->getMaterial(0);
-	core::matrix4& matrix = material.getTextureMatrix(0);
-	matrix.setTextureTranslate(txs*col, tys*row);
-	matrix.setTextureScale(txs, tys);
-}
-
-/*
-	MobV2CAO
-*/
-
-class MobV2CAO : public ClientActiveObject
-{
-public:
-	MobV2CAO(IGameDef *gamedef, ClientEnvironment *env);
-	virtual ~MobV2CAO();
-	
-	u8 getType() const
-	{
-		return ACTIVEOBJECT_TYPE_MOBV2;
-	}
-	
-	static ClientActiveObject* create(IGameDef *gamedef, ClientEnvironment *env);
-
-	void addToScene(scene::ISceneManager *smgr, ITextureSource *tsrc,
-			IrrlichtDevice *irr);
-	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 pos_translator.vect_show;}
-		//{return m_position;}
-	bool doShowSelectionBox(){return false;}
-
-	// If returns true, punch will not be sent to the server
-	bool directReportPunch(const std::string &toolname, v3f dir);
-
-private:
-	void setLooks(const std::string &looks);
-	
-	IntervalLimiter m_attack_interval;
-	core::aabbox3d<f32> m_selection_box;
-	scene::IBillboardSceneNode *m_node;
-	v3f m_position;
-	std::string m_texture_name;
-	float m_yaw;
-	SmoothTranslator pos_translator;
-	bool m_walking;
-	float m_walking_unset_timer;
-	float m_walk_timer;
-	int m_walk_frame;
-	float m_damage_visual_timer;
-	u8 m_last_light;
-	bool m_shooting;
-	float m_shooting_unset_timer;
-	v2f m_sprite_size;
-	float m_sprite_y;
-	bool m_bright_shooting;
-	std::string m_sprite_type;
-	int m_simple_anim_frames;
-	float m_simple_anim_frametime;
-	bool m_lock_full_brightness;
-	int m_player_hit_damage;
-	float m_player_hit_distance;
-	float m_player_hit_interval;
-	float m_player_hit_timer;
-
-	Settings *m_properties;
-};
-
-/*
-	TestCAO
-*/
-
-// Prototype
-TestCAO proto_TestCAO(NULL, NULL);
-
-TestCAO::TestCAO(IGameDef *gamedef, ClientEnvironment *env):
-	ClientActiveObject(0, gamedef, env),
-	m_node(NULL),
-	m_position(v3f(0,10*BS,0))
-{
-	ClientActiveObject::registerType(getType(), create);
-}
-
-TestCAO::~TestCAO()
-{
-}
-
-ClientActiveObject* TestCAO::create(IGameDef *gamedef, ClientEnvironment *env)
-{
-	return new TestCAO(gamedef, env);
-}
-
-void TestCAO::addToScene(scene::ISceneManager *smgr, ITextureSource *tsrc,
-			IrrlichtDevice *irr)
-{
-	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, tsrc->getTextureRaw("rat.png"));
-	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, ClientEnvironment *env)
-{
-	if(m_node)
-	{
-		v3f rot = m_node->getRotation();
-		//infostream<<"dtime="<<dtime<<", rot.Y="<<rot.Y<<std::endl;
-		rot.Y += dtime * 180;
-		m_node->setRotation(rot);
-	}
-}
-
-void TestCAO::processMessage(const std::string &data)
-{
-	infostream<<"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();
-	}
-}
-
-/*
-	ItemCAO
-*/
-
-#include "inventory.h"
-
-// Prototype
-ItemCAO proto_ItemCAO(NULL, NULL);
-
-ItemCAO::ItemCAO(IGameDef *gamedef, ClientEnvironment *env):
-	ClientActiveObject(0, gamedef, env),
-	m_selection_box(-BS/3.,0.0,-BS/3., BS/3.,BS*2./3.,BS/3.),
-	m_node(NULL),
-	m_position(v3f(0,10*BS,0))
-{
-	if(!gamedef && !env)
-	{
-		ClientActiveObject::registerType(getType(), create);
-	}
-}
-
-ItemCAO::~ItemCAO()
-{
-}
-
-ClientActiveObject* ItemCAO::create(IGameDef *gamedef, ClientEnvironment *env)
-{
-	return new ItemCAO(gamedef, env);
-}
-
-void ItemCAO::addToScene(scene::ISceneManager *smgr, ITextureSource *tsrc,
-			IrrlichtDevice *irr)
-{
-	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),*/
-		video::S3DVertex(BS/3.,0,0, 0,0,0, c, 0,1),
-		video::S3DVertex(-BS/3.,0,0, 0,0,0, c, 1,1),
-		video::S3DVertex(-BS/3.,0+BS*2./3.,0, 0,0,0, c, 1,0),
-		video::S3DVertex(BS/3.,0+BS*2./3.,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);
-	// Initialize with a generated placeholder texture
-	buf->getMaterial().setTexture(0, tsrc->getTextureRaw(""));
-	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();
-
-	/*
-		Update image of node
-	*/
-
-	updateTexture();
-}
-
-void ItemCAO::removeFromScene()
-{
-	if(m_node == NULL)
-		return;
-
-	m_node->remove();
-	m_node = NULL;
-}
-
-void ItemCAO::updateLight(u8 light_at_pos)
-{
-	if(m_node == NULL)
-		return;
-
-	u8 li = decode_light(light_at_pos);
-	video::SColor color(255,li,li,li);
-	setMeshColor(m_node->getMesh(), color);
-}
-
-v3s16 ItemCAO::getLightPosition()
-{
-	return floatToInt(m_position, BS);
-}
-
-void ItemCAO::updateNodePos()
-{
-	if(m_node == NULL)
-		return;
-
-	m_node->setPosition(m_position);
-}
-
-void ItemCAO::updateInfoText()
-{
-	try{
-		IItemDefManager *idef = m_gamedef->idef();
-		ItemStack item;
-		item.deSerialize(m_itemstring, idef);
-		if(item.isKnown(idef))
-			m_infotext = item.getDefinition(idef).description;
-		else
-			m_infotext = "Unknown item: '" + m_itemstring + "'";
-		if(item.count >= 2)
-			m_infotext += " (" + itos(item.count) + ")";
-	}
-	catch(SerializationError &e)
-	{
-		m_infotext = "Unknown item: '" + m_itemstring + "'";
-	}
-}
-
-void ItemCAO::updateTexture()
-{
-	if(m_node == NULL)
-		return;
-
-	// Create an inventory item to see what is its image
-	std::istringstream is(m_itemstring, std::ios_base::binary);
-	video::ITexture *texture = NULL;
-	try{
-		IItemDefManager *idef = m_gamedef->idef();
-		ItemStack item;
-		item.deSerialize(is, idef);
-		texture = item.getDefinition(idef).inventory_texture;
-	}
-	catch(SerializationError &e)
-	{
-		infostream<<"WARNING: "<<__FUNCTION_NAME
-				<<": error deSerializing itemstring \""
-				<<m_itemstring<<std::endl;
-	}
-	
-	// Set meshbuffer texture
-	m_node->getMaterial(0).setTexture(0, texture);
-}
-
-
-void ItemCAO::step(float dtime, ClientEnvironment *env)
-{
-	if(m_node)
-	{
-		/*v3f rot = m_node->getRotation();
-		rot.Y += dtime * 120;
-		m_node->setRotation(rot);*/
-		LocalPlayer *player = env->getLocalPlayer();
-		assert(player);
-		v3f rot = m_node->getRotation();
-		rot.Y = 180.0 - (player->getYaw());
-		m_node->setRotation(rot);
-	}
-}
-
-void ItemCAO::processMessage(const std::string &data)
-{
-	//infostream<<"ItemCAO: Got message"<<std::endl;
-	std::istringstream is(data, std::ios::binary);
-	// command
-	u8 cmd = readU8(is);
-	if(cmd == 0)
-	{
-		// pos
-		m_position = readV3F1000(is);
-		updateNodePos();
-	}
-	if(cmd == 1)
-	{
-		// itemstring
-		m_itemstring = deSerializeString(is);
-		updateInfoText();
-		updateTexture();
-	}
-}
-
-void ItemCAO::initialize(const std::string &data)
-{
-	infostream<<"ItemCAO: 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);
-		// itemstring
-		m_itemstring = deSerializeString(is);
-	}
-	
-	updateNodePos();
-	updateInfoText();
-}
-
-/*
-	RatCAO
-*/
-
-#include "inventory.h"
-
-// Prototype
-RatCAO proto_RatCAO(NULL, NULL);
-
-RatCAO::RatCAO(IGameDef *gamedef, ClientEnvironment *env):
-	ClientActiveObject(0, gamedef, env),
-	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)),
-	m_yaw(0)
-{
-	ClientActiveObject::registerType(getType(), create);
-}
-
-RatCAO::~RatCAO()
-{
-}
-
-ClientActiveObject* RatCAO::create(IGameDef *gamedef, ClientEnvironment *env)
-{
-	return new RatCAO(gamedef, env);
-}
-
-void RatCAO::addToScene(scene::ISceneManager *smgr, ITextureSource *tsrc,
-			IrrlichtDevice *irr)
-{
-	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, tsrc->getTextureRaw("rat.png"));
-	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);
-	setMeshColor(m_node->getMesh(), color);
-}
-
-v3s16 RatCAO::getLightPosition()
-{
-	return floatToInt(m_position+v3f(0,BS*0.5,0), 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)
-{
-	//infostream<<"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)
-{
-	//infostream<<"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();
-}
-
-/*
-	Oerkki1CAO
-*/
-
-#include "inventory.h"
-
-// Prototype
-Oerkki1CAO proto_Oerkki1CAO(NULL, NULL);
-
-Oerkki1CAO::Oerkki1CAO(IGameDef *gamedef, ClientEnvironment *env):
-	ClientActiveObject(0, gamedef, env),
-	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)),
-	m_yaw(0),
-	m_damage_visual_timer(0),
-	m_damage_texture_enabled(false)
-{
-	ClientActiveObject::registerType(getType(), create);
-}
-
-Oerkki1CAO::~Oerkki1CAO()
-{
-}
-
-ClientActiveObject* Oerkki1CAO::create(IGameDef *gamedef, ClientEnvironment *env)
-{
-	return new Oerkki1CAO(gamedef, env);
-}
-
-void Oerkki1CAO::addToScene(scene::ISceneManager *smgr, ITextureSource *tsrc,
-			IrrlichtDevice *irr)
-{
-	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,0,0, 0,0,0, c, 0,1),
-		video::S3DVertex(BS/2+BS,0,0, 0,0,0, c, 1,1),
-		video::S3DVertex(BS/2+BS,BS*2,0, 0,0,0, c, 1,0),
-		video::S3DVertex(-BS/2-BS,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, tsrc->getTextureRaw("oerkki1.png"));
-	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 Oerkki1CAO::removeFromScene()
-{
-	if(m_node == NULL)
-		return;
-
-	m_node->remove();
-	m_node = NULL;
-}
-
-void Oerkki1CAO::updateLight(u8 light_at_pos)
-{
-	if(m_node == NULL)
-		return;
-	
-	if(light_at_pos <= 2)
-	{
-		m_node->setVisible(false);
-		return;
-	}
-
-	m_node->setVisible(true);
-
-	u8 li = decode_light(light_at_pos);
-	video::SColor color(255,li,li,li);
-	setMeshColor(m_node->getMesh(), color);
-}
-
-v3s16 Oerkki1CAO::getLightPosition()
-{
-	return floatToInt(m_position+v3f(0,BS*1.5,0), BS);
-}
-
-void Oerkki1CAO::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 + 90.0;
-	m_node->setRotation(rot);
-}
-
-void Oerkki1CAO::step(float dtime, ClientEnvironment *env)
-{
-	ITextureSource *tsrc = m_gamedef->tsrc();
-
-	pos_translator.translate(dtime);
-	updateNodePos();
-
-	LocalPlayer *player = env->getLocalPlayer();
-	assert(player);
-	
-	v3f playerpos = player->getPosition();
-	v2f playerpos_2d(playerpos.X,playerpos.Z);
-	v2f objectpos_2d(m_position.X,m_position.Z);
-
-	if(fabs(m_position.Y - playerpos.Y) < 1.5*BS &&
-			objectpos_2d.getDistanceFrom(playerpos_2d) < 1.5*BS)
-	{
-		if(m_attack_interval.step(dtime, 0.5))
-		{
-			env->damageLocalPlayer(2);
-		}
-	}
-
-	if(m_damage_visual_timer > 0)
-	{
-		if(!m_damage_texture_enabled)
-		{
-			// Enable damage texture
-			if(m_node)
-			{
-				/*video::IVideoDriver* driver =
-					m_node->getSceneManager()->getVideoDriver();*/
-				
-				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);
-					buf->getMaterial().setTexture(0,
-							tsrc->getTextureRaw("oerkki1_damaged.png"));
-				}
-			}
-			m_damage_texture_enabled = true;
-		}
-		m_damage_visual_timer -= dtime;
-	}
-	else
-	{
-		if(m_damage_texture_enabled)
-		{
-			// Disable damage texture
-			if(m_node)
-			{
-				/*video::IVideoDriver* driver =
-					m_node->getSceneManager()->getVideoDriver();*/
-				
-				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);
-					buf->getMaterial().setTexture(0,
-							tsrc->getTextureRaw("oerkki1.png"));
-				}
-			}
-			m_damage_texture_enabled = false;
-		}
-	}
-}
-
-void Oerkki1CAO::processMessage(const std::string &data)
-{
-	//infostream<<"Oerkki1CAO: 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();
-	}
-	else if(cmd == 1)
-	{
-		//u16 damage = readU8(is);
-		m_damage_visual_timer = 1.0;
-	}
-}
-
-void Oerkki1CAO::initialize(const std::string &data)
-{
-	//infostream<<"Oerkki1CAO: 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();
-}
-
-bool Oerkki1CAO::directReportPunch(const std::string &toolname, v3f dir)
-{
-	m_damage_visual_timer = 1.0;
-
-	m_position += dir * BS;
-	pos_translator.sharpen();
-	pos_translator.update(m_position);
-	updateNodePos();
-	
-	return false;
-}
-
 /*
-	FireflyCAO
+	Other stuff
 */
 
-// Prototype
-FireflyCAO proto_FireflyCAO(NULL, NULL);
-
-FireflyCAO::FireflyCAO(IGameDef *gamedef, ClientEnvironment *env):
-	ClientActiveObject(0, gamedef, env),
-	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)),
-	m_yaw(0)
-{
-	ClientActiveObject::registerType(getType(), create);
-}
-
-FireflyCAO::~FireflyCAO()
-{
-}
-
-ClientActiveObject* FireflyCAO::create(IGameDef *gamedef, ClientEnvironment *env)
-{
-	return new FireflyCAO(gamedef, env);
-}
-
-void FireflyCAO::addToScene(scene::ISceneManager *smgr, ITextureSource *tsrc,
-			IrrlichtDevice *irr)
-{
-	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(0,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(0,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, tsrc->getTextureRaw("firefly.png"));
-	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 FireflyCAO::removeFromScene()
-{
-	if(m_node == NULL)
-		return;
-
-	m_node->remove();
-	m_node = NULL;
-}
-
-void FireflyCAO::updateLight(u8 light_at_pos)
-{
-	if(m_node == NULL)
-		return;
-
-	u8 li = 255;
-	video::SColor color(255,li,li,li);
-	setMeshColor(m_node->getMesh(), color);
-}
-
-v3s16 FireflyCAO::getLightPosition()
-{
-	return floatToInt(m_position+v3f(0,BS*0.5,0), BS);
-}
-
-void FireflyCAO::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 FireflyCAO::step(float dtime, ClientEnvironment *env)
-{
-	pos_translator.translate(dtime);
-	updateNodePos();
-}
-
-void FireflyCAO::processMessage(const std::string &data)
-{
-	//infostream<<"FireflyCAO: 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 FireflyCAO::initialize(const std::string &data)
+static void setBillboardTextureMatrix(scene::IBillboardSceneNode *bill,
+		float txs, float tys, int col, int row)
 {
-	//infostream<<"FireflyCAO: 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();
+	video::SMaterial& material = bill->getMaterial(0);
+	core::matrix4& matrix = material.getTextureMatrix(0);
+	matrix.setTextureTranslate(txs*col, tys*row);
+	matrix.setTextureScale(txs, tys);
 }
 
 /*
-	MobV2CAO
+	TestCAO
 */
 
-// Prototype
-MobV2CAO proto_MobV2CAO(NULL, NULL);
-
-MobV2CAO::MobV2CAO(IGameDef *gamedef, ClientEnvironment *env):
-	ClientActiveObject(0, gamedef, env),
-	m_selection_box(-0.4*BS,-0.4*BS,-0.4*BS, 0.4*BS,0.8*BS,0.4*BS),
-	m_node(NULL),
-	m_position(v3f(0,10*BS,0)),
-	m_yaw(0),
-	m_walking(false),
-	m_walking_unset_timer(0),
-	m_walk_timer(0),
-	m_walk_frame(0),
-	m_damage_visual_timer(0),
-	m_last_light(0),
-	m_shooting(0),
-	m_shooting_unset_timer(0),
-	m_sprite_size(BS,BS),
-	m_sprite_y(0),
-	m_bright_shooting(false),
-	m_lock_full_brightness(false),
-	m_player_hit_timer(0)
-{
-	ClientActiveObject::registerType(getType(), create);
-
-	m_properties = new Settings;
-}
-
-MobV2CAO::~MobV2CAO()
-{
-	delete m_properties;
-}
-
-ClientActiveObject* MobV2CAO::create(IGameDef *gamedef, ClientEnvironment *env)
-{
-	return new MobV2CAO(gamedef, env);
-}
-
-void MobV2CAO::addToScene(scene::ISceneManager *smgr, ITextureSource *tsrc,
-			IrrlichtDevice *irr)
+class TestCAO : public ClientActiveObject
 {
-	if(m_node != NULL)
-		return;
-	
-	/*infostream<<"MobV2CAO::addToScene using texture_name="<<
-			m_texture_name<<std::endl;*/
-	std::string texture_string = m_texture_name +
-			"^[makealpha:128,0,0^[makealpha:128,128,0";
+public:
+	TestCAO(IGameDef *gamedef, ClientEnvironment *env);
+	virtual ~TestCAO();
 	
-	scene::IBillboardSceneNode *bill = smgr->addBillboardSceneNode(
-			NULL, v2f(1, 1), v3f(0,0,0), -1);
-	bill->setMaterialTexture(0, tsrc->getTextureRaw(texture_string));
-	bill->setMaterialFlag(video::EMF_LIGHTING, false);
-	bill->setMaterialFlag(video::EMF_BILINEAR_FILTER, false);
-	bill->setMaterialType(video::EMT_TRANSPARENT_ALPHA_CHANNEL_REF);
-	bill->setMaterialFlag(video::EMF_FOG_ENABLE, true);
-	bill->setColor(video::SColor(255,0,0,0));
-	bill->setVisible(false); /* Set visible when brightness is known */
-	bill->setSize(m_sprite_size);
-	if(m_sprite_type == "humanoid_1"){
-		const float txp = 1./192;
-		const float txs = txp*32;
-		const float typ = 1./240;
-		const float tys = typ*48;
-		setBillboardTextureMatrix(bill, txs, tys, 0, 0);
-	} else if(m_sprite_type == "simple"){
-		const float txs = 1.0;
-		const float tys = 1.0 / m_simple_anim_frames;
-		setBillboardTextureMatrix(bill, txs, tys, 0, 0);
-	} else {
-		infostream<<"MobV2CAO: Unknown sprite type \""<<m_sprite_type<<"\""
-				<<std::endl;
+	u8 getType() const
+	{
+		return ACTIVEOBJECT_TYPE_TEST;
 	}
-
-	m_node = bill;
-
-	updateNodePos();
-}
-
-void MobV2CAO::removeFromScene()
-{
-	if(m_node == NULL)
-		return;
-
-	m_node->remove();
-	m_node = NULL;
-}
-
-void MobV2CAO::updateLight(u8 light_at_pos)
-{
-	if(m_lock_full_brightness)
-		light_at_pos = 15;
-	
-	m_last_light = light_at_pos;
-
-	if(m_node == NULL)
-		return;
 	
-	if(m_damage_visual_timer > 0)
-		return;
-	
-	if(m_shooting && m_bright_shooting)
-		return;
-	
-	/*if(light_at_pos <= 2){
-		m_node->setVisible(false);
-		return;
-	}*/
+	static ClientActiveObject* create(IGameDef *gamedef, ClientEnvironment *env);
 
-	m_node->setVisible(true);
+	void addToScene(scene::ISceneManager *smgr, ITextureSource *tsrc,
+			IrrlichtDevice *irr);
+	void removeFromScene();
+	void updateLight(u8 light_at_pos);
+	v3s16 getLightPosition();
+	void updateNodePos();
 
-	u8 li = decode_light(light_at_pos);
-	video::SColor color(255,li,li,li);
-	m_node->setColor(color);
-}
+	void step(float dtime, ClientEnvironment *env);
 
-v3s16 MobV2CAO::getLightPosition()
-{
-	return floatToInt(m_position+v3f(0,0,0), BS);
-}
+	void processMessage(const std::string &data);
 
-void MobV2CAO::updateNodePos()
-{
-	if(m_node == NULL)
-		return;
+private:
+	scene::IMeshSceneNode *m_node;
+	v3f m_position;
+};
 
-	m_node->setPosition(pos_translator.vect_show + v3f(0,m_sprite_y,0));
-}
+/*
+	ItemCAO
+*/
 
-void MobV2CAO::step(float dtime, ClientEnvironment *env)
+class ItemCAO : public ClientActiveObject
 {
-	scene::IBillboardSceneNode *bill = m_node;
-	if(!bill)
-		return;
-
-	pos_translator.translate(dtime);
+public:
+	ItemCAO(IGameDef *gamedef, ClientEnvironment *env);
+	virtual ~ItemCAO();
 	
-	if(m_sprite_type == "humanoid_1"){
-		scene::ICameraSceneNode* camera = m_node->getSceneManager()->getActiveCamera();
-		if(!camera)
-			return;
-		v3f cam_to_mob = m_node->getAbsolutePosition() - camera->getAbsolutePosition();
-		cam_to_mob.normalize();
-		int col = 0;
-		if(cam_to_mob.Y > 0.75)
-			col = 5;
-		else if(cam_to_mob.Y < -0.75)
-			col = 4;
-		else{
-			float mob_dir = atan2(cam_to_mob.Z, cam_to_mob.X) / PI * 180.;
-			float dir = mob_dir - m_yaw;
-			dir = wrapDegrees_180(dir);
-			//infostream<<"id="<<m_id<<" dir="<<dir<<std::endl;
-			if(fabs(wrapDegrees_180(dir - 0)) <= 45.1)
-				col = 2;
-			else if(fabs(wrapDegrees_180(dir - 90)) <= 45.1)
-				col = 3;
-			else if(fabs(wrapDegrees_180(dir - 180)) <= 45.1)
-				col = 0;
-			else if(fabs(wrapDegrees_180(dir + 90)) <= 45.1)
-				col = 1;
-			else
-				col = 4;
-		}
-
-		int row = 0;
-		if(m_shooting){
-			row = 3;
-		} else if(m_walking){
-			m_walk_timer += dtime;
-			if(m_walk_timer >= 0.5){
-				m_walk_frame = (m_walk_frame + 1) % 2;
-				m_walk_timer = 0;
-			}
-			if(m_walk_frame == 0)
-				row = 1;
-			else
-				row = 2;
-		}
-
-		const float txp = 1./192;
-		const float txs = txp*32;
-		const float typ = 1./240;
-		const float tys = typ*48;
-		setBillboardTextureMatrix(bill, txs, tys, col, row);
-	} else if(m_sprite_type == "simple"){
-		m_walk_timer += dtime;
-		if(m_walk_timer >= m_simple_anim_frametime){
-			m_walk_frame = (m_walk_frame + 1) % m_simple_anim_frames;
-			m_walk_timer = 0;
-		}
-		int col = 0;
-		int row = m_walk_frame;
-		const float txs = 1.0;
-		const float tys = 1.0 / m_simple_anim_frames;
-		setBillboardTextureMatrix(bill, txs, tys, col, row);
-	} else {
-		infostream<<"MobV2CAO::step(): Unknown sprite type \""
-				<<m_sprite_type<<"\""<<std::endl;
-	}
-
-	updateNodePos();
-
-	/* Damage local player */
-	if(m_player_hit_damage && m_player_hit_timer <= 0.0){
-		LocalPlayer *player = env->getLocalPlayer();
-		assert(player);
-		
-		v3f playerpos = player->getPosition();
-		v2f playerpos_2d(playerpos.X,playerpos.Z);
-		v2f objectpos_2d(m_position.X,m_position.Z);
-
-		if(fabs(m_position.Y - playerpos.Y) < m_player_hit_distance*BS &&
-		objectpos_2d.getDistanceFrom(playerpos_2d) < m_player_hit_distance*BS)
-		{
-			env->damageLocalPlayer(m_player_hit_damage);
-			m_player_hit_timer = m_player_hit_interval;
-		}
-	}
-
-	/* Run timers */
-
-	m_player_hit_timer -= dtime;
-
-	if(m_damage_visual_timer >= 0){
-		m_damage_visual_timer -= dtime;
-		if(m_damage_visual_timer <= 0){
-			infostream<<"id="<<m_id<<" damage visual ended"<<std::endl;
-		}
-	}
-
-	m_walking_unset_timer += dtime;
-	if(m_walking_unset_timer >= 1.0){
-		m_walking = false;
-	}
-
-	m_shooting_unset_timer -= dtime;
-	if(m_shooting_unset_timer <= 0.0){
-		if(m_bright_shooting){
-			u8 li = decode_light(m_last_light);
-			video::SColor color(255,li,li,li);
-			bill->setColor(color);
-			m_bright_shooting = false;
-		}
-		m_shooting = false;
-	}
-
-}
-
-void MobV2CAO::processMessage(const std::string &data)
-{
-	//infostream<<"MobV2CAO: Got message"<<std::endl;
-	std::istringstream is(data, std::ios::binary);
-	// command
-	u8 cmd = readU8(is);
-
-	// Move
-	if(cmd == 0)
-	{
-		// pos
-		m_position = readV3F1000(is);
-		pos_translator.update(m_position);
-		// yaw
-		m_yaw = readF1000(is);
-
-		m_walking = true;
-		m_walking_unset_timer = 0;
-
-		updateNodePos();
-	}
-	// Damage
-	else if(cmd == 1)
-	{
-		//u16 damage = readU16(is);
-
-		/*u8 li = decode_light(m_last_light);
-		if(li >= 100)
-			li = 30;
-		else
-			li = 255;*/
-
-		/*video::SColor color(255,255,0,0);
-		m_node->setColor(color);
-
-		m_damage_visual_timer = 0.2;*/
-	}
-	// Trigger shooting
-	else if(cmd == 2)
+	u8 getType() const
 	{
-		// length
-		m_shooting_unset_timer = readF1000(is);
-		// bright?
-		m_bright_shooting = readU8(is);
-		if(m_bright_shooting){
-			u8 li = 255;
-			video::SColor color(255,li,li,li);
-			m_node->setColor(color);
-		}
-
-		m_shooting = true;
+		return ACTIVEOBJECT_TYPE_ITEM;
 	}
-}
-
-void MobV2CAO::initialize(const std::string &data)
-{
-	//infostream<<"MobV2CAO: Got init data"<<std::endl;
 	
-	{
-		std::istringstream is(data, std::ios::binary);
-		// version
-		u8 version = readU8(is);
-		// check version
-		if(version != 0){
-			infostream<<__FUNCTION_NAME<<": Invalid version"<<std::endl;
-			return;
-		}
-		
-		std::ostringstream tmp_os(std::ios::binary);
-		decompressZlib(is, tmp_os);
-		std::istringstream tmp_is(tmp_os.str(), std::ios::binary);
-		m_properties->parseConfigLines(tmp_is, "MobArgsEnd");
-
-		infostream<<"MobV2CAO::initialize(): got properties:"<<std::endl;
-		m_properties->writeLines(infostream);
-		
-		m_properties->setDefault("looks", "dummy_default");
-		m_properties->setDefault("yaw", "0");
-		m_properties->setDefault("pos", "(0,0,0)");
-		m_properties->setDefault("player_hit_damage", "0");
-		m_properties->setDefault("player_hit_distance", "1.5");
-		m_properties->setDefault("player_hit_interval", "1.5");
-		
-		setLooks(m_properties->get("looks"));
-		m_yaw = m_properties->getFloat("yaw");
-		m_position = m_properties->getV3F("pos");
-		m_player_hit_damage = m_properties->getS32("player_hit_damage");
-		m_player_hit_distance = m_properties->getFloat("player_hit_distance");
-		m_player_hit_interval = m_properties->getFloat("player_hit_interval");
+	static ClientActiveObject* create(IGameDef *gamedef, ClientEnvironment *env);
 
-		pos_translator.init(m_position);
-	}
-	
-	updateNodePos();
-}
+	void addToScene(scene::ISceneManager *smgr, ITextureSource *tsrc,
+			IrrlichtDevice *irr);
+	void removeFromScene();
+	void updateLight(u8 light_at_pos);
+	v3s16 getLightPosition();
+	void updateNodePos();
+	void updateInfoText();
+	void updateTexture();
 
-bool MobV2CAO::directReportPunch(const std::string &toolname, v3f dir)
-{
-	video::SColor color(255,255,0,0);
-	m_node->setColor(color);
+	void step(float dtime, ClientEnvironment *env);
 
-	m_damage_visual_timer = 0.05;
+	void processMessage(const std::string &data);
 
-	m_position += dir * BS;
-	pos_translator.sharpen();
-	pos_translator.update(m_position);
-	updateNodePos();
+	void initialize(const std::string &data);
 	
-	return false;
-}
-
-void MobV2CAO::setLooks(const std::string &looks)
-{
-	v2f selection_size = v2f(0.4, 0.4) * BS;
-	float selection_y = 0 * BS;
-
-	if(looks == "dungeon_master"){
-		m_texture_name = "dungeon_master.png";
-		m_sprite_type = "humanoid_1";
-		m_sprite_size = v2f(2, 3) * BS;
-		m_sprite_y = 0.85 * BS;
-		selection_size = v2f(0.4, 2.6) * BS;
-		selection_y = -0.4 * BS;
-	}
-	else if(looks == "fireball"){
-		m_texture_name = "fireball.png";
-		m_sprite_type = "simple";
-		m_sprite_size = v2f(1, 1) * BS;
-		m_simple_anim_frames = 3;
-		m_simple_anim_frametime = 0.1;
-		m_lock_full_brightness = true;
-	}
-	else{
-		m_texture_name = "stone.png";
-		m_sprite_type = "simple";
-		m_sprite_size = v2f(1, 1) * BS;
-		m_simple_anim_frames = 3;
-		m_simple_anim_frametime = 0.333;
-		selection_size = v2f(0.4, 0.4) * BS;
-		selection_y = 0 * BS;
-	}
+	core::aabbox3d<f32>* getSelectionBox()
+		{return &m_selection_box;}
+	v3f getPosition()
+		{return m_position;}
+	
+	std::string infoText()
+		{return m_infotext;}
 
-	m_selection_box = core::aabbox3d<f32>(
-			-selection_size.X, selection_y, -selection_size.X,
-			selection_size.X, selection_y+selection_size.Y,
-			selection_size.X);
-}
+private:
+	core::aabbox3d<f32> m_selection_box;
+	scene::IMeshSceneNode *m_node;
+	v3f m_position;
+	std::string m_itemstring;
+	std::string m_infotext;
+};
 
 /*
 	LuaEntityCAO
@@ -1691,6 +226,7 @@ class LuaEntityCAO : public ClientActiveObject
 	v3f m_velocity;
 	v3f m_acceleration;
 	float m_yaw;
+	s16 m_hp;
 	struct LuaEntityProperties *m_prop;
 	SmoothTranslator pos_translator;
 	// Spritesheet/animation stuff
@@ -1712,6 +248,7 @@ class LuaEntityCAO : public ClientActiveObject
 		m_velocity(v3f(0,0,0)),
 		m_acceleration(v3f(0,0,0)),
 		m_yaw(0),
+		m_hp(1),
 		m_prop(new LuaEntityProperties),
 		m_tx_size(1,1),
 		m_tx_basepos(0,0),
@@ -1733,12 +270,14 @@ class LuaEntityCAO : public ClientActiveObject
 		// version
 		u8 version = readU8(is);
 		// check version
-		if(version != 0)
+		if(version != 1)
 			return;
 		// pos
 		m_position = readV3F1000(is);
 		// yaw
 		m_yaw = readF1000(is);
+		// hp
+		m_hp = readS16(is);
 		// properties
 		std::istringstream prop_is(deSerializeLongString(is), std::ios::binary);
 		m_prop->deSerialize(prop_is);
@@ -2000,7 +539,7 @@ class LuaEntityCAO : public ClientActiveObject
 		std::istringstream is(data, std::ios::binary);
 		// command
 		u8 cmd = readU8(is);
-		if(cmd == 0) // update position
+		if(cmd == LUAENTITY_CMD_UPDATE_POSITION) // update position
 		{
 			// do_interpolate
 			bool do_interpolate = readU8(is);
@@ -2025,12 +564,12 @@ class LuaEntityCAO : public ClientActiveObject
 			}
 			updateNodePos();
 		}
-		else if(cmd == 1) // set texture modification
+		else if(cmd == LUAENTITY_CMD_SET_TEXTURE_MOD) // set texture modification
 		{
 			std::string mod = deSerializeString(is);
 			updateTextures(mod);
 		}
-		else if(cmd == 2) // set sprite
+		else if(cmd == LUAENTITY_CMD_SET_SPRITE) // set sprite
 		{
 			v2s16 p = readV2S16(is);
 			int num_frames = readU16(is);
@@ -2044,6 +583,14 @@ class LuaEntityCAO : public ClientActiveObject
 
 			updateTexturePos();
 		}
+		else if(cmd == LUAENTITY_CMD_PUNCHED)
+		{
+			s16 damage = readS16(is);
+			s16 result_hp = readS16(is);
+			
+			m_hp = result_hp;
+			// TODO: Execute defined fast response
+		}
 	}
 };
 
diff --git a/src/content_sao.cpp b/src/content_sao.cpp
index b7a6db906..5c03c9053 100644
--- a/src/content_sao.cpp
+++ b/src/content_sao.cpp
@@ -29,1508 +29,306 @@ with this program; if not, write to the Free Software Foundation, Inc.,
 
 core::map<u16, ServerActiveObject::Factory> ServerActiveObject::m_types;
 
-/* Some helper functions */
-
-// Y is copied, X and Z change is limited
-void accelerate_xz(v3f &speed, v3f target_speed, f32 max_increase)
-{
-	v3f d_wanted = target_speed - speed;
-	d_wanted.Y = 0;
-	f32 dl_wanted = d_wanted.getLength();
-	f32 dl = dl_wanted;
-	if(dl > max_increase)
-		dl = max_increase;
-	
-	v3f d = d_wanted.normalize() * dl;
-
-	speed.X += d.X;
-	speed.Z += d.Z;
-	speed.Y = target_speed.Y;
-}
-
 /*
-	TestSAO
-*/
-
-// Prototype
-TestSAO proto_TestSAO(NULL, v3f(0,0,0));
-
-TestSAO::TestSAO(ServerEnvironment *env, v3f pos):
-	ServerActiveObject(env, pos),
-	m_timer1(0),
-	m_age(0)
-{
-	ServerActiveObject::registerType(getType(), create);
-}
-
-ServerActiveObject* TestSAO::create(ServerEnvironment *env, v3f pos,
-		const std::string &data)
-{
-	return new TestSAO(env, pos);
-}
-
-void TestSAO::step(float dtime, bool send_recommended)
-{
-	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;
-
-	if(send_recommended == false)
-		return;
-
-	m_timer1 -= dtime;
-	if(m_timer1 < 0.0)
-	{
-		m_timer1 += 0.125;
-
-		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(), false, data);
-		m_messages_out.push_back(aom);
-	}
-}
-
-
-/*
-	ItemSAO
-*/
-
-// Prototype
-ItemSAO proto_ItemSAO(NULL, v3f(0,0,0), "");
-
-ItemSAO::ItemSAO(ServerEnvironment *env, v3f pos,
-		const std::string itemstring):
-	ServerActiveObject(env, pos),
-	m_itemstring(itemstring),
-	m_itemstring_changed(false),
-	m_speed_f(0,0,0),
-	m_last_sent_position(0,0,0)
-{
-	ServerActiveObject::registerType(getType(), create);
-}
-
-ServerActiveObject* ItemSAO::create(ServerEnvironment *env, 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;
-	std::string itemstring = deSerializeString(is);
-	infostream<<"ItemSAO::create(): Creating item \""
-			<<itemstring<<"\""<<std::endl;
-	return new ItemSAO(env, pos, itemstring);
-}
-
-void ItemSAO::step(float dtime, bool send_recommended)
-{
-	ScopeProfiler sp2(g_profiler, "ItemSAO::step avg", SPT_AVG);
-
-	assert(m_env);
-
-	const float interval = 0.2;
-	if(m_move_interval.step(dtime, interval)==false)
-		return;
-	dtime = interval;
-	
-	core::aabbox3d<f32> box(-BS/3.,0.0,-BS/3., BS/3.,BS*2./3.,BS/3.);
-	collisionMoveResult moveresult;
-	// Apply gravity
-	m_speed_f += v3f(0, -dtime*9.81*BS, 0);
-	// 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;
-	IGameDef *gamedef = m_env->getGameDef();
-	moveresult = collisionMoveSimple(&m_env->getMap(), gamedef,
-			pos_max_d, box, dtime, pos_f, m_speed_f);
-	
-	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);
-		// command (0 = update position)
-		writeU8(os, 0);
-		// pos
-		writeV3F1000(os, m_base_position);
-		// create message and add to list
-		ActiveObjectMessage aom(getId(), false, os.str());
-		m_messages_out.push_back(aom);
-	}
-	if(m_itemstring_changed)
-	{
-		m_itemstring_changed = false;
-
-		std::ostringstream os(std::ios::binary);
-		// command (1 = update itemstring)
-		writeU8(os, 1);
-		// itemstring
-		os<<serializeString(m_itemstring);
-		// create message and add to list
-		ActiveObjectMessage aom(getId(), false, os.str());
-		m_messages_out.push_back(aom);
-	}
-}
-
-std::string ItemSAO::getClientInitializationData()
-{
-	std::ostringstream os(std::ios::binary);
-	// version
-	writeU8(os, 0);
-	// pos
-	writeV3F1000(os, m_base_position);
-	// itemstring
-	os<<serializeString(m_itemstring);
-	return os.str();
-}
-
-std::string ItemSAO::getStaticData()
-{
-	infostream<<__FUNCTION_NAME<<std::endl;
-	std::ostringstream os(std::ios::binary);
-	// version
-	writeU8(os, 0);
-	// itemstring
-	os<<serializeString(m_itemstring);
-	return os.str();
-}
-
-ItemStack ItemSAO::createItemStack()
-{
-	try{
-		IItemDefManager *idef = m_env->getGameDef()->idef();
-		ItemStack item;
-		item.deSerialize(m_itemstring, idef);
-		infostream<<__FUNCTION_NAME<<": m_itemstring=\""<<m_itemstring
-				<<"\" -> item=\""<<item.getItemString()<<"\""
-				<<std::endl;
-		return item;
-	}
-	catch(SerializationError &e)
-	{
-		infostream<<__FUNCTION_NAME<<": serialization error: "
-				<<"m_itemstring=\""<<m_itemstring<<"\""<<std::endl;
-		return ItemStack();
-	}
-}
-
-void ItemSAO::punch(ServerActiveObject *puncher, float time_from_last_punch)
-{
-	// Allow removing items in creative mode
-	if(g_settings->getBool("creative_mode") == true)
-	{
-		m_removed = true;
-		return;
-	}
-
-	ItemStack item = createItemStack();
-	Inventory *inv = puncher->getInventory();
-	if(inv != NULL)
-	{
-		std::string wieldlist = puncher->getWieldList();
-		ItemStack leftover = inv->addItem(wieldlist, item);
-		puncher->setInventoryModified();
-		if(leftover.empty())
-		{
-			m_removed = true;
-		}
-		else
-		{
-			m_itemstring = leftover.getItemString();
-			m_itemstring_changed = true;
-		}
-	}
-}
-
-/*
-	RatSAO
-*/
-
-// Prototype
-RatSAO proto_RatSAO(NULL, v3f(0,0,0));
-
-RatSAO::RatSAO(ServerEnvironment *env, v3f pos):
-	ServerActiveObject(env, pos),
-	m_is_active(false),
-	m_speed_f(0,0,0)
-{
-	ServerActiveObject::registerType(getType(), create);
-
-	m_oldpos = v3f(0,0,0);
-	m_last_sent_position = v3f(0,0,0);
-	m_yaw = myrand_range(0,PI*2);
-	m_counter1 = 0;
-	m_counter2 = 0;
-	m_age = 0;
-	m_touching_ground = false;
-}
-
-ServerActiveObject* RatSAO::create(ServerEnvironment *env, 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, pos);
-}
-
-void RatSAO::step(float dtime, bool send_recommended)
-{
-	ScopeProfiler sp2(g_profiler, "RatSAO::step avg", SPT_AVG);
-
-	assert(m_env);
-
-	if(m_is_active == false)
-	{
-		if(m_inactive_interval.step(dtime, 0.5)==false)
-			return;
-	}
-
-	/*
-		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;
-		}
-	}
-
-	m_is_active = player_is_close;
-	
-	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;
-	IGameDef *gamedef = m_env->getGameDef();
-	moveresult = collisionMoveSimple(&m_env->getMap(), gamedef,
-			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());
-		m_messages_out.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()
-{
-	//infostream<<__FUNCTION_NAME<<std::endl;
-	std::ostringstream os(std::ios::binary);
-	// version
-	writeU8(os, 0);
-	return os.str();
-}
-
-void RatSAO::punch(ServerActiveObject *puncher, float time_from_last_punch)
-{
-	// Allow removing rats in creative mode
-	if(g_settings->getBool("creative_mode") == true)
-	{
-		m_removed = true;
-		return;
-	}
-
-	IItemDefManager *idef = m_env->getGameDef()->idef();
-	ItemStack item("rat", 1, 0, "", idef);
-	Inventory *inv = puncher->getInventory();
-	if(inv != NULL)
-	{
-		std::string wieldlist = puncher->getWieldList();
-		ItemStack leftover = inv->addItem(wieldlist, item);
-		puncher->setInventoryModified();
-		if(leftover.empty())
-			m_removed = true;
-	}
-}
-
-/*
-	Oerkki1SAO
-*/
-
-// Prototype
-Oerkki1SAO proto_Oerkki1SAO(NULL, v3f(0,0,0));
-
-Oerkki1SAO::Oerkki1SAO(ServerEnvironment *env, v3f pos):
-	ServerActiveObject(env, pos),
-	m_is_active(false),
-	m_speed_f(0,0,0)
-{
-	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;
-	m_hp = 20;
-	m_after_jump_timer = 0;
-}
-
-ServerActiveObject* Oerkki1SAO::create(ServerEnvironment *env, v3f pos,
-		const std::string &data)
-{
-	std::istringstream is(data, std::ios::binary);
-	// read version
-	u8 version = readU8(is);
-	// read hp
-	u8 hp = readU8(is);
-	// check if version is supported
-	if(version != 0)
-		return NULL;
-	Oerkki1SAO *o = new Oerkki1SAO(env, pos);
-	o->m_hp = hp;
-	return o;
-}
-
-void Oerkki1SAO::step(float dtime, bool send_recommended)
-{
-	ScopeProfiler sp2(g_profiler, "Oerkki1SAO::step avg", SPT_AVG);
-
-	assert(m_env);
-
-	if(m_is_active == false)
-	{
-		if(m_inactive_interval.step(dtime, 0.5)==false)
-			return;
-	}
-
-	/*
-		The AI
-	*/
-
-	m_age += dtime;
-	if(m_age > 120)
-	{
-		// Die
-		m_removed = true;
-		return;
-	}
-
-	m_after_jump_timer -= dtime;
-
-	v3f old_speed = m_speed_f;
-
-	// Apply gravity
-	m_speed_f.Y -= dtime*9.81*BS;
-
-	/*
-		Move around if some player is close
-	*/
-	bool player_is_close = false;
-	bool player_is_too_close = false;
-	v3f near_player_pos;
-	// 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();
-		f32 dist = m_base_position.getDistanceFrom(playerpos);
-		if(dist < BS*0.6)
-		{
-			m_removed = true;
-			return;
-			player_is_too_close = true;
-			near_player_pos = playerpos;
-		}
-		else if(dist < BS*15.0 && !player_is_too_close)
-		{
-			player_is_close = true;
-			near_player_pos = playerpos;
-		}
-	}
-
-	m_is_active = player_is_close;
-
-	v3f target_speed = m_speed_f;
-
-	if(!player_is_close)
-	{
-		target_speed = v3f(0,0,0);
-	}
-	else
-	{
-		// Move around
-
-		v3f ndir = near_player_pos - m_base_position;
-		ndir.Y = 0;
-		ndir.normalize();
-
-		f32 nyaw = 180./PI*atan2(ndir.Z,ndir.X);
-		if(nyaw < m_yaw - 180)
-			nyaw += 360;
-		else if(nyaw > m_yaw + 180)
-			nyaw -= 360;
-		m_yaw = 0.95*m_yaw + 0.05*nyaw;
-		m_yaw = wrapDegrees(m_yaw);
-		
-		f32 speed = 2*BS;
-
-		if((m_touching_ground || m_after_jump_timer > 0.0)
-				&& !player_is_too_close)
-		{
-			v3f dir(cos(m_yaw/180*PI),0,sin(m_yaw/180*PI));
-			target_speed.X = speed * dir.X;
-			target_speed.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 += 0.2;
-				// Jump
-				target_speed.Y = 5.0*BS;
-				m_after_jump_timer = 1.0;
-			}
-		}
-
-		{
-			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 += ((float)(myrand()%200)-100)/100*90;
-				m_yaw = wrapDegrees(m_yaw);
-			}
-		}
-	}
-	
-	if((m_speed_f - target_speed).getLength() > BS*4 || player_is_too_close)
-		accelerate_xz(m_speed_f, target_speed, dtime*BS*8);
-	else
-		accelerate_xz(m_speed_f, target_speed, dtime*BS*4);
-	
-	m_oldpos = m_base_position;
-
-	/*
-		Move it, with collision detection
-	*/
-
-	core::aabbox3d<f32> box(-BS/3.,0.0,-BS/3., BS/3.,BS*5./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;
-	IGameDef *gamedef = m_env->getGameDef();
-	moveresult = collisionMovePrecise(&m_env->getMap(), gamedef,
-			pos_max_d, box, dtime, pos_f, m_speed_f);
-	m_touching_ground = moveresult.touching_ground;
-	
-	// Do collision damage
-	float tolerance = BS*30;
-	float factor = BS*0.5;
-	v3f speed_diff = old_speed - m_speed_f;
-	// Increase effect in X and Z
-	speed_diff.X *= 2;
-	speed_diff.Z *= 2;
-	float vel = speed_diff.getLength();
-	if(vel > tolerance)
-	{
-		f32 damage_f = (vel - tolerance)/BS*factor;
-		u16 damage = (u16)(damage_f+0.5);
-		doDamage(damage);
-	}
-
-	setBasePosition(pos_f);
-
-	if(send_recommended == false && m_speed_f.getLength() < 3.0*BS)
-		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());
-		m_messages_out.push_back(aom);
-	}
-}
-
-std::string Oerkki1SAO::getClientInitializationData()
-{
-	std::ostringstream os(std::ios::binary);
-	// version
-	writeU8(os, 0);
-	// pos
-	writeV3F1000(os, m_base_position);
-	return os.str();
-}
-
-std::string Oerkki1SAO::getStaticData()
-{
-	//infostream<<__FUNCTION_NAME<<std::endl;
-	std::ostringstream os(std::ios::binary);
-	// version
-	writeU8(os, 0);
-	// hp
-	writeU8(os, m_hp);
-	return os.str();
-}
-
-void Oerkki1SAO::punch(ServerActiveObject *puncher, float time_from_last_punch)
-{
-	if(!puncher)
-		return;
-	
-	v3f dir = (getBasePosition() - puncher->getBasePosition()).normalize();
-	m_speed_f += dir*12*BS;
-
-	// "Material" groups of the object
-	ItemGroupList groups;
-	groups["snappy"] = 1;
-	groups["choppy"] = 1;
-	groups["fleshy"] = 3;
-
-	IItemDefManager *idef = m_env->getGameDef()->idef();
-	ItemStack punchitem = puncher->getWieldedItem();
-	ToolCapabilities tp = punchitem.getToolCapabilities(idef);
-
-	HitParams hit_params = getHitParams(groups, &tp,
-			time_from_last_punch);
-
-	doDamage(hit_params.hp);
-	if(g_settings->getBool("creative_mode") == false)
-	{
-		punchitem.addWear(hit_params.wear, idef);
-		puncher->setWieldedItem(punchitem);
-	}
-}
-
-void Oerkki1SAO::doDamage(u16 d)
-{
-	infostream<<"oerkki damage: "<<d<<std::endl;
-	
-	if(d < m_hp)
-	{
-		m_hp -= d;
-	}
-	else
-	{
-		// Die
-		m_hp = 0;
-		m_removed = true;
-	}
-
-	{
-		std::ostringstream os(std::ios::binary);
-		// command (1 = damage)
-		writeU8(os, 1);
-		// amount
-		writeU8(os, d);
-		// create message and add to list
-		ActiveObjectMessage aom(getId(), false, os.str());
-		m_messages_out.push_back(aom);
-	}
-}
-
-/*
-	FireflySAO
-*/
-
-// Prototype
-FireflySAO proto_FireflySAO(NULL, v3f(0,0,0));
-
-FireflySAO::FireflySAO(ServerEnvironment *env, v3f pos):
-	ServerActiveObject(env, pos),
-	m_is_active(false),
-	m_speed_f(0,0,0)
-{
-	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* FireflySAO::create(ServerEnvironment *env, 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 FireflySAO(env, pos);
-}
-
-void FireflySAO::step(float dtime, bool send_recommended)
-{
-	ScopeProfiler sp2(g_profiler, "FireflySAO::step avg", SPT_AVG);
-
-	assert(m_env);
-
-	if(m_is_active == false)
-	{
-		if(m_inactive_interval.step(dtime, 0.5)==false)
-			return;
-	}
-
-	/*
-		The AI
-	*/
-
-	// Apply (less) gravity
-	m_speed_f.Y -= dtime*3*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;
-		}
-	}
-
-	m_is_active = player_is_close;
-	
-	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 = BS/2;
-		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;
-			}
-		}
+	DummyLoadSAO
+*/
 
-		{
-			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);
-			}
-		}
+class DummyLoadSAO : public ServerActiveObject
+{
+public:
+	DummyLoadSAO(ServerEnvironment *env, v3f pos, u8 type):
+		ServerActiveObject(env, pos)
+	{
+		ServerActiveObject::registerType(type, create);
 	}
+	// Pretend to be the test object (to fool the client)
+	u8 getType() const
+	{ return ACTIVEOBJECT_TYPE_TEST; }
+	// And never save to disk
+	bool isStaticAllowed() const
+	{ return false; }
 	
-	m_oldpos = m_base_position;
-
-	/*
-		Move it, with collision detection
-	*/
-
-	core::aabbox3d<f32> box(-BS/3.,-BS*2/3.0,-BS/3., BS/3.,BS*4./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;
-	IGameDef *gamedef = m_env->getGameDef();
-	moveresult = collisionMoveSimple(&m_env->getMap(), gamedef,
-			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)
+	static ServerActiveObject* create(ServerEnvironment *env, v3f pos,
+			const std::string &data)
 	{
-		m_last_sent_position = pos_f;
+		return new DummyLoadSAO(env, pos, 0);
+	}
 
-		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());
-		m_messages_out.push_back(aom);
+	void step(float dtime, bool send_recommended)
+	{
+		m_removed = true;
+		infostream<<"DummyLoadSAO step"<<std::endl;
 	}
-}
 
-std::string FireflySAO::getClientInitializationData()
-{
-	std::ostringstream os(std::ios::binary);
-	// version
-	writeU8(os, 0);
-	// pos
-	writeV3F1000(os, m_base_position);
-	return os.str();
-}
+private:
+};
 
-std::string FireflySAO::getStaticData()
-{
-	//infostream<<__FUNCTION_NAME<<std::endl;
-	std::ostringstream os(std::ios::binary);
-	// version
-	writeU8(os, 0);
-	return os.str();
-}
+// Prototype (registers item for deserialization)
+DummyLoadSAO proto1_DummyLoadSAO(NULL, v3f(0,0,0), ACTIVEOBJECT_TYPE_RAT);
+DummyLoadSAO proto2_DummyLoadSAO(NULL, v3f(0,0,0), ACTIVEOBJECT_TYPE_OERKKI1);
+DummyLoadSAO proto3_DummyLoadSAO(NULL, v3f(0,0,0), ACTIVEOBJECT_TYPE_FIREFLY);
+DummyLoadSAO proto4_DummyLoadSAO(NULL, v3f(0,0,0), ACTIVEOBJECT_TYPE_MOBV2);
 
 /*
-	MobV2SAO
+	TestSAO
 */
 
-// Prototype
-MobV2SAO proto_MobV2SAO(NULL, v3f(0,0,0), NULL);
-
-MobV2SAO::MobV2SAO(ServerEnvironment *env, v3f pos,
-		Settings *init_properties):
-	ServerActiveObject(env, pos),
-	m_move_type("ground_nodes"),
-	m_speed(0,0,0),
-	m_last_sent_position(0,0,0),
-	m_oldpos(0,0,0),
-	m_yaw(0),
-	m_counter1(0),
-	m_counter2(0),
-	m_age(0),
-	m_touching_ground(false),
-	m_hp(10),
-	m_walk_around(false),
-	m_walk_around_timer(0),
-	m_next_pos_exists(false),
-	m_shoot_reload_timer(0),
-	m_shooting(false),
-	m_shooting_timer(0),
-	m_falling(false),
-	m_disturb_timer(100000),
-	m_random_disturb_timer(0),
-	m_shoot_y(0)
+class TestSAO : public ServerActiveObject
 {
-	ServerActiveObject::registerType(getType(), create);
-	
-	m_properties = new Settings();
-	if(init_properties)
-		m_properties->update(*init_properties);
-	
-	m_properties->setV3F("pos", pos);
-	
-	setPropertyDefaults();
-	readProperties();
-}
+public:
+	TestSAO(ServerEnvironment *env, v3f pos):
+		ServerActiveObject(env, pos),
+		m_timer1(0),
+		m_age(0)
+	{
+		ServerActiveObject::registerType(getType(), create);
+	}
+	u8 getType() const
+	{ return ACTIVEOBJECT_TYPE_TEST; }
 	
-MobV2SAO::~MobV2SAO()
-{
-	delete m_properties;
-}
-
-ServerActiveObject* MobV2SAO::create(ServerEnvironment *env, v3f pos,
-		const std::string &data)
-{
-	std::istringstream is(data, std::ios::binary);
-	Settings properties;
-	properties.parseConfigLines(is, "MobArgsEnd");
-	MobV2SAO *o = new MobV2SAO(env, pos, &properties);
-	return o;
-}
-
-std::string MobV2SAO::getStaticData()
-{
-	updateProperties();
-
-	std::ostringstream os(std::ios::binary);
-	m_properties->writeLines(os);
-	return os.str();
-}
-
-std::string MobV2SAO::getClientInitializationData()
-{
-	//infostream<<__FUNCTION_NAME<<std::endl;
+	static ServerActiveObject* create(ServerEnvironment *env, v3f pos,
+			const std::string &data)
+	{
+		return new TestSAO(env, pos);
+	}
 
-	updateProperties();
+	void step(float dtime, bool send_recommended)
+	{
+		m_age += dtime;
+		if(m_age > 10)
+		{
+			m_removed = true;
+			return;
+		}
 
-	std::ostringstream os(std::ios::binary);
+		m_base_position.Y += dtime * BS * 2;
+		if(m_base_position.Y > 8*BS)
+			m_base_position.Y = 2*BS;
 
-	// version
-	writeU8(os, 0);
-	
-	Settings client_properties;
-	
-	/*client_properties.set("version", "0");
-	client_properties.updateValue(*m_properties, "pos");
-	client_properties.updateValue(*m_properties, "yaw");
-	client_properties.updateValue(*m_properties, "hp");*/
+		if(send_recommended == false)
+			return;
 
-	// Just send everything for simplicity
-	client_properties.update(*m_properties);
+		m_timer1 -= dtime;
+		if(m_timer1 < 0.0)
+		{
+			m_timer1 += 0.125;
 
-	std::ostringstream os2(std::ios::binary);
-	client_properties.writeLines(os2);
-	compressZlib(os2.str(), os);
+			std::string data;
 
-	return os.str();
-}
+			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);
 
-bool checkFreePosition(Map *map, v3s16 p0, v3s16 size)
-{
-	for(int dx=0; dx<size.X; dx++)
-	for(int dy=0; dy<size.Y; dy++)
-	for(int dz=0; dz<size.Z; dz++){
-		v3s16 dp(dx, dy, dz);
-		v3s16 p = p0 + dp;
-		MapNode n = map->getNodeNoEx(p);
-		if(n.getContent() != CONTENT_AIR)
-			return false;
+			ActiveObjectMessage aom(getId(), false, data);
+			m_messages_out.push_back(aom);
+		}
 	}
-	return true;
-}
-
-bool checkWalkablePosition(Map *map, v3s16 p0)
-{
-	v3s16 p = p0 + v3s16(0,-1,0);
-	MapNode n = map->getNodeNoEx(p);
-	if(n.getContent() != CONTENT_AIR)
-		return true;
-	return false;
-}
 
-bool checkFreeAndWalkablePosition(Map *map, v3s16 p0, v3s16 size)
-{
-	if(!checkFreePosition(map, p0, size))
-		return false;
-	if(!checkWalkablePosition(map, p0))
-		return false;
-	return true;
-}
+private:
+	float m_timer1;
+	float m_age;
+};
 
-static void get_random_u32_array(u32 a[], u32 len)
-{
-	u32 i, n;
-	for(i=0; i<len; i++)
-		a[i] = i;
-	n = len;
-	while(n > 1){
-		u32 k = myrand() % n;
-		n--;
-		u32 temp = a[n];
-		a[n] = a[k];
-		a[k] = temp;
-	}
-}
+// Prototype (registers item for deserialization)
+TestSAO proto_TestSAO(NULL, v3f(0,0,0));
 
-#define PP(x) "("<<(x).X<<","<<(x).Y<<","<<(x).Z<<")"
+/*
+	ItemSAO
+*/
 
-static void explodeSquare(Map *map, v3s16 p0, v3s16 size)
+class ItemSAO : public ServerActiveObject
 {
-	core::map<v3s16, MapBlock*> modified_blocks;
-
-	for(int dx=0; dx<size.X; dx++)
-	for(int dy=0; dy<size.Y; dy++)
-	for(int dz=0; dz<size.Z; dz++){
-		v3s16 dp(dx - size.X/2, dy - size.Y/2, dz - size.Z/2);
-		v3s16 p = p0 + dp;
-		MapNode n = map->getNodeNoEx(p);
-		if(n.getContent() == CONTENT_IGNORE)
-			continue;
-		//map->removeNodeWithEvent(p);
-		map->removeNodeAndUpdate(p, modified_blocks);
-	}
-
-	// Send a MEET_OTHER event
-	MapEditEvent event;
-	event.type = MEET_OTHER;
-	for(core::map<v3s16, MapBlock*>::Iterator
-		  i = modified_blocks.getIterator();
-		  i.atEnd() == false; i++)
+public:
+	u8 getType() const
+	{ return ACTIVEOBJECT_TYPE_ITEM; }
+	
+	float getMinimumSavedMovement()
+	{ return 0.1*BS; }
+
+	static ServerActiveObject* create(ServerEnvironment *env, 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;
+		std::string itemstring = deSerializeString(is);
+		infostream<<"create(): Creating item \""
+				<<itemstring<<"\""<<std::endl;
+		return new ItemSAO(env, pos, itemstring);
+	}
+
+	ItemSAO(ServerEnvironment *env, v3f pos,
+			const std::string itemstring):
+		ServerActiveObject(env, pos),
+		m_itemstring(itemstring),
+		m_itemstring_changed(false),
+		m_speed_f(0,0,0),
+		m_last_sent_position(0,0,0)
 	{
-		v3s16 p = i.getNode()->getKey();
-		event.modified_blocks.insert(p, true);
+		ServerActiveObject::registerType(getType(), create);
 	}
-	map->dispatchEvent(&event);
-}
 
-void MobV2SAO::step(float dtime, bool send_recommended)
-{
-	ScopeProfiler sp2(g_profiler, "MobV2SAO::step avg", SPT_AVG);
+	void step(float dtime, bool send_recommended)
+	{
+		ScopeProfiler sp2(g_profiler, "step avg", SPT_AVG);
 
-	assert(m_env);
-	Map *map = &m_env->getMap();
+		assert(m_env);
 
-	m_age += dtime;
+		const float interval = 0.2;
+		if(m_move_interval.step(dtime, interval)==false)
+			return;
+		dtime = interval;
+		
+		core::aabbox3d<f32> box(-BS/3.,0.0,-BS/3., BS/3.,BS*2./3.,BS/3.);
+		collisionMoveResult moveresult;
+		// Apply gravity
+		m_speed_f += v3f(0, -dtime*9.81*BS, 0);
+		// 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;
+		IGameDef *gamedef = m_env->getGameDef();
+		moveresult = collisionMoveSimple(&m_env->getMap(), gamedef,
+				pos_max_d, box, dtime, pos_f, m_speed_f);
+		
+		if(send_recommended == false)
+			return;
 
-	if(m_die_age >= 0.0 && m_age >= m_die_age){
-		m_removed = true;
-		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);
+			// command (0 = update position)
+			writeU8(os, 0);
+			// pos
+			writeV3F1000(os, m_base_position);
+			// create message and add to list
+			ActiveObjectMessage aom(getId(), false, os.str());
+			m_messages_out.push_back(aom);
+		}
+		if(m_itemstring_changed)
+		{
+			m_itemstring_changed = false;
+
+			std::ostringstream os(std::ios::binary);
+			// command (1 = update itemstring)
+			writeU8(os, 1);
+			// itemstring
+			os<<serializeString(m_itemstring);
+			// create message and add to list
+			ActiveObjectMessage aom(getId(), false, os.str());
+			m_messages_out.push_back(aom);
+		}
 	}
 
-	m_random_disturb_timer += dtime;
-	if(m_random_disturb_timer >= 5.0)
+	std::string getClientInitializationData()
 	{
-		m_random_disturb_timer = 0;
-		// 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();
-			f32 dist = m_base_position.getDistanceFrom(playerpos);
-			if(dist < BS*16)
-			{
-				if(myrand_range(0,3) == 0){
-					actionstream<<"Mob id="<<m_id<<" at "
-							<<PP(m_base_position/BS)
-							<<" got randomly disturbed by "
-							<<player->getName()<<std::endl;
-					m_disturbing_player = player->getName();
-					m_disturb_timer = 0;
-					break;
-				}
-			}
-		}
+		std::ostringstream os(std::ios::binary);
+		// version
+		writeU8(os, 0);
+		// pos
+		writeV3F1000(os, m_base_position);
+		// itemstring
+		os<<serializeString(m_itemstring);
+		return os.str();
 	}
 
-	Player *disturbing_player =
-			m_env->getPlayer(m_disturbing_player.c_str());
-	v3f disturbing_player_off = v3f(0,1,0);
-	v3f disturbing_player_norm = v3f(0,1,0);
-	float disturbing_player_distance = 1000000;
-	float disturbing_player_dir = 0;
-	if(disturbing_player){
-		disturbing_player_off =
-				disturbing_player->getPosition() - m_base_position;
-		disturbing_player_distance = disturbing_player_off.getLength();
-		disturbing_player_norm = disturbing_player_off;
-		disturbing_player_norm.normalize();
-		disturbing_player_dir = 180./PI*atan2(disturbing_player_norm.Z,
-				disturbing_player_norm.X);
+	std::string getStaticData()
+	{
+		infostream<<__FUNCTION_NAME<<std::endl;
+		std::ostringstream os(std::ios::binary);
+		// version
+		writeU8(os, 0);
+		// itemstring
+		os<<serializeString(m_itemstring);
+		return os.str();
 	}
 
-	m_disturb_timer += dtime;
-	
-	if(!m_falling)
+	ItemStack createItemStack()
 	{
-		m_shooting_timer -= dtime;
-		if(m_shooting_timer <= 0.0 && m_shooting){
-			m_shooting = false;
-			
-			std::string shoot_type = m_properties->get("shoot_type");
-			v3f shoot_pos(0,0,0);
-			shoot_pos.Y += m_properties->getFloat("shoot_y") * BS;
-			if(shoot_type == "fireball"){
-				v3f dir(cos(m_yaw/180*PI),0,sin(m_yaw/180*PI));
-				dir.Y = m_shoot_y;
-				dir.normalize();
-				v3f speed = dir * BS * 10.0;
-				v3f pos = m_base_position + shoot_pos;
-				infostream<<__FUNCTION_NAME<<": Mob id="<<m_id
-						<<" shooting fireball from "<<PP(pos)
-						<<" at speed "<<PP(speed)<<std::endl;
-				Settings properties;
-				properties.set("looks", "fireball");
-				properties.setV3F("speed", speed);
-				properties.setFloat("die_age", 5.0);
-				properties.set("move_type", "constant_speed");
-				properties.setFloat("hp", 1000);
-				properties.set("lock_full_brightness", "true");
-				properties.set("player_hit_damage", "9");
-				properties.set("player_hit_distance", "2");
-				properties.set("player_hit_interval", "1");
-				ServerActiveObject *obj = new MobV2SAO(m_env,
-						pos, &properties);
-				//m_env->addActiveObjectAsStatic(obj);
-				m_env->addActiveObject(obj);
-			} else {
-				infostream<<__FUNCTION_NAME<<": Mob id="<<m_id
-						<<": Unknown shoot_type="<<shoot_type
-						<<std::endl;
-			}
+		try{
+			IItemDefManager *idef = m_env->getGameDef()->idef();
+			ItemStack item;
+			item.deSerialize(m_itemstring, idef);
+			infostream<<__FUNCTION_NAME<<": m_itemstring=\""<<m_itemstring
+					<<"\" -> item=\""<<item.getItemString()<<"\""
+					<<std::endl;
+			return item;
 		}
-
-		m_shoot_reload_timer += dtime;
-
-		float reload_time = 15.0;
-		if(m_disturb_timer <= 15.0)
-			reload_time = 3.0;
-
-		bool shoot_without_player = false;
-		if(m_properties->getBool("mindless_rage"))
-			shoot_without_player = true;
-
-		if(!m_shooting && m_shoot_reload_timer >= reload_time &&
-				!m_next_pos_exists &&
-				(m_disturb_timer <= 60.0 || shoot_without_player))
+		catch(SerializationError &e)
 		{
-			m_shoot_y = 0;
-			if(m_disturb_timer < 60.0 && disturbing_player &&
-					disturbing_player_distance < 16*BS &&
-					fabs(disturbing_player_norm.Y) < 0.8){
-				m_yaw = disturbing_player_dir;
-				sendPosition();
-				m_shoot_y += disturbing_player_norm.Y;
-			} else {
-				m_shoot_y = 0.01 * myrand_range(-30,10);
-			}
-			m_shoot_reload_timer = 0.0;
-			m_shooting = true;
-			m_shooting_timer = 1.5;
-			{
-				std::ostringstream os(std::ios::binary);
-				// command (2 = shooting)
-				writeU8(os, 2);
-				// time
-				writeF1000(os, m_shooting_timer + 0.1);
-				// bright?
-				writeU8(os, true);
-				// create message and add to list
-				ActiveObjectMessage aom(getId(), false, os.str());
-				m_messages_out.push_back(aom);
-			}
+			infostream<<__FUNCTION_NAME<<": serialization error: "
+					<<"m_itemstring=\""<<m_itemstring<<"\""<<std::endl;
+			return ItemStack();
 		}
 	}
-	
-	if(m_move_type == "ground_nodes")
-	{
-		if(!m_shooting){
-			m_walk_around_timer -= dtime;
-			if(m_walk_around_timer <= 0.0){
-				m_walk_around = !m_walk_around;
-				if(m_walk_around)
-					m_walk_around_timer = 0.1*myrand_range(10,50);
-				else
-					m_walk_around_timer = 0.1*myrand_range(30,70);
-			}
-		}
-
-		/* Move */
-		if(m_next_pos_exists){
-			v3f pos_f = m_base_position;
-			v3f next_pos_f = intToFloat(m_next_pos_i, BS);
-
-			v3f v = next_pos_f - pos_f;
-			m_yaw = atan2(v.Z, v.X) / PI * 180;
-			
-			v3f diff = next_pos_f - pos_f;
-			v3f dir = diff;
-			dir.normalize();
-			float speed = BS * 0.5;
-			if(m_falling)
-				speed = BS * 3.0;
-			dir *= dtime * speed;
-			bool arrived = false;
-			if(dir.getLength() > diff.getLength()){
-				dir = diff;
-				arrived = true;
-			}
-			pos_f += dir;
-			m_base_position = pos_f;
 
-			if((pos_f - next_pos_f).getLength() < 0.1 || arrived){
-				m_next_pos_exists = false;
-			}
-		}
-
-		v3s16 pos_i = floatToInt(m_base_position, BS);
-		v3s16 size_blocks = v3s16(m_size.X+0.5,m_size.Y+0.5,m_size.X+0.5);
-		v3s16 pos_size_off(0,0,0);
-		if(m_size.X >= 2.5){
-			pos_size_off.X = -1;
-			pos_size_off.Y = -1;
+	int punch(v3f dir,
+			const ToolCapabilities *toolcap,
+			ServerActiveObject *puncher,
+			float time_from_last_punch)
+	{
+		// Directly delete item in creative mode
+		if(g_settings->getBool("creative_mode") == true)
+		{
+			m_removed = true;
+			return 0;
 		}
 		
-		if(!m_next_pos_exists){
-			/* Check whether to drop down */
-			if(checkFreePosition(map,
-					pos_i + pos_size_off + v3s16(0,-1,0), size_blocks)){
-				m_next_pos_i = pos_i + v3s16(0,-1,0);
-				m_next_pos_exists = true;
-				m_falling = true;
-			} else {
-				m_falling = false;
-			}
-		}
-
-		if(m_walk_around)
+		// Take item into inventory
+		ItemStack item = createItemStack();
+		Inventory *inv = puncher->getInventory();
+		if(inv != NULL)
 		{
-			if(!m_next_pos_exists){
-				/* Find some position where to go next */
-				v3s16 dps[3*3*3];
-				int num_dps = 0;
-				for(int dx=-1; dx<=1; dx++)
-				for(int dy=-1; dy<=1; dy++)
-				for(int dz=-1; dz<=1; dz++){
-					if(dx == 0 && dy == 0)
-						continue;
-					if(dx != 0 && dz != 0 && dy != 0)
-						continue;
-					dps[num_dps++] = v3s16(dx,dy,dz);
-				}
-				u32 order[3*3*3];
-				get_random_u32_array(order, num_dps);
-				for(int i=0; i<num_dps; i++){
-					v3s16 p = dps[order[i]] + pos_i;
-					bool is_free = checkFreeAndWalkablePosition(map,
-							p + pos_size_off, size_blocks);
-					if(!is_free)
-						continue;
-					m_next_pos_i = p;
-					m_next_pos_exists = true;
-					break;
-				}
+			std::string wieldlist = puncher->getWieldList();
+			ItemStack leftover = inv->addItem(wieldlist, item);
+			puncher->setInventoryModified();
+			if(leftover.empty())
+			{
+				m_removed = true;
+			}
+			else
+			{
+				m_itemstring = leftover.getItemString();
+				m_itemstring_changed = true;
 			}
 		}
-	}
-	else if(m_move_type == "constant_speed")
-	{
-		m_base_position += m_speed * dtime;
 		
-		v3s16 pos_i = floatToInt(m_base_position, BS);
-		v3s16 size_blocks = v3s16(m_size.X+0.5,m_size.Y+0.5,m_size.X+0.5);
-		v3s16 pos_size_off(0,0,0);
-		if(m_size.X >= 2.5){
-			pos_size_off.X = -1;
-			pos_size_off.Y = -1;
-		}
-		bool free = checkFreePosition(map, pos_i + pos_size_off, size_blocks);
-		if(!free){
-			explodeSquare(map, pos_i, v3s16(3,3,3));
-			m_removed = true;
-			return;
-		}
-	}
-	else
-	{
-		errorstream<<"MobV2SAO::step(): id="<<m_id<<" unknown move_type=\""
-				<<m_move_type<<"\""<<std::endl;
+		return 0;
 	}
 
-	if(send_recommended == false)
-		return;
-
-	if(m_base_position.getDistanceFrom(m_last_sent_position) > 0.05*BS)
-	{
-		sendPosition();
-	}
-}
-
-void MobV2SAO::punch(ServerActiveObject *puncher, float time_from_last_punch)
-{
-	if(!puncher)
-		return;
-	
-	v3f dir = (getBasePosition() - puncher->getBasePosition()).normalize();
-
-	// A quick hack; SAO description is player name for player
-	std::string playername = puncher->getDescription();
-
-	Map *map = &m_env->getMap();
-	
-	actionstream<<playername<<" punches mob id="<<m_id
-			<<" at "<<PP(m_base_position/BS)<<std::endl;
-
-	m_disturb_timer = 0;
-	m_disturbing_player = playername;
-	m_next_pos_exists = false; // Cancel moving immediately
-	
-	m_yaw = wrapDegrees_180(180./PI*atan2(dir.Z, dir.X) + 180.);
-	v3f new_base_position = m_base_position + dir * BS;
-	{
-		v3s16 pos_i = floatToInt(new_base_position, BS);
-		v3s16 size_blocks = v3s16(m_size.X+0.5,m_size.Y+0.5,m_size.X+0.5);
-		v3s16 pos_size_off(0,0,0);
-		if(m_size.X >= 2.5){
-			pos_size_off.X = -1;
-			pos_size_off.Y = -1;
-		}
-		bool free = checkFreePosition(map, pos_i + pos_size_off, size_blocks);
-		if(free)
-			m_base_position = new_base_position;
-	}
-	sendPosition();
-	
-
-	// "Material" groups of the object
-	ItemGroupList groups;
-	groups["snappy"] = 1;
-	groups["choppy"] = 1;
-	groups["fleshy"] = 3;
-
-	IItemDefManager *idef = m_env->getGameDef()->idef();
-	ItemStack punchitem = puncher->getWieldedItem();
-	ToolCapabilities tp = punchitem.getToolCapabilities(idef);
-
-	HitParams hit_params = getHitParams(groups, &tp,
-			time_from_last_punch);
-
-	doDamage(hit_params.hp);
-	if(g_settings->getBool("creative_mode") == false)
-	{
-		punchitem.addWear(hit_params.wear, idef);
-		puncher->setWieldedItem(punchitem);
-	}
-}
-
-bool MobV2SAO::isPeaceful()
-{
-	return m_properties->getBool("is_peaceful");
-}
-
-void MobV2SAO::sendPosition()
-{
-	m_last_sent_position = m_base_position;
 
-	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());
-	m_messages_out.push_back(aom);
-}
+private:
+	std::string m_itemstring;
+	bool m_itemstring_changed;
+	v3f m_speed_f;
+	v3f m_last_sent_position;
+	IntervalLimiter m_move_interval;
+};
 
-void MobV2SAO::setPropertyDefaults()
-{
-	m_properties->setDefault("is_peaceful", "false");
-	m_properties->setDefault("move_type", "ground_nodes");
-	m_properties->setDefault("speed", "(0,0,0)");
-	m_properties->setDefault("age", "0");
-	m_properties->setDefault("yaw", "0");
-	m_properties->setDefault("pos", "(0,0,0)");
-	m_properties->setDefault("hp", "0");
-	m_properties->setDefault("die_age", "-1");
-	m_properties->setDefault("size", "(1,2)");
-	m_properties->setDefault("shoot_type", "fireball");
-	m_properties->setDefault("shoot_y", "0");
-	m_properties->setDefault("mindless_rage", "false");
-}
-void MobV2SAO::readProperties()
-{
-	m_move_type = m_properties->get("move_type");
-	m_speed = m_properties->getV3F("speed");
-	m_age = m_properties->getFloat("age");
-	m_yaw = m_properties->getFloat("yaw");
-	m_base_position = m_properties->getV3F("pos");
-	m_hp = m_properties->getS32("hp");
-	m_die_age = m_properties->getFloat("die_age");
-	m_size = m_properties->getV2F("size");
-}
-void MobV2SAO::updateProperties()
-{
-	m_properties->set("move_type", m_move_type);
-	m_properties->setV3F("speed", m_speed);
-	m_properties->setFloat("age", m_age);
-	m_properties->setFloat("yaw", m_yaw);
-	m_properties->setV3F("pos", m_base_position);
-	m_properties->setS32("hp", m_hp);
-	m_properties->setFloat("die_age", m_die_age);
-	m_properties->setV2F("size", m_size);
-
-	m_properties->setS32("version", 0);
-}
+// Prototype (registers item for deserialization)
+ItemSAO proto_ItemSAO(NULL, v3f(0,0,0), "");
 
-void MobV2SAO::doDamage(u16 d)
+ServerActiveObject* createItemSAO(ServerEnvironment *env, v3f pos,
+		const std::string itemstring)
 {
-	if(d > 0)
-		actionstream<<"MobV2 ("<<m_hp<<"hp) takes "<<d<<"hp of damage"<<std::endl;
-	
-	if(d < m_hp)
-	{
-		m_hp -= d;
-	}
-	else
-	{
-		actionstream<<"A "<<(isPeaceful()?"peaceful":"non-peaceful")
-				<<" mob id="<<m_id<<" dies at "<<PP(m_base_position)<<std::endl;
-		// Die
-		m_hp = 0;
-		m_removed = true;
-	}
-
-	{
-		std::ostringstream os(std::ios::binary);
-		// command (1 = damage)
-		writeU8(os, 1);
-		// amount
-		writeU16(os, d);
-		// create message and add to list
-		ActiveObjectMessage aom(getId(), false, os.str());
-		m_messages_out.push_back(aom);
-	}
+	return new ItemSAO(env, pos, itemstring);
 }
 
-
 /*
 	LuaEntitySAO
 */
@@ -1538,7 +336,7 @@ void MobV2SAO::doDamage(u16 d)
 #include "scriptapi.h"
 #include "luaentity_common.h"
 
-// Prototype
+// Prototype (registers item for deserialization)
 LuaEntitySAO proto_LuaEntitySAO(NULL, v3f(0,0,0), "_prototype", "");
 
 LuaEntitySAO::LuaEntitySAO(ServerEnvironment *env, v3f pos,
@@ -1548,6 +346,7 @@ LuaEntitySAO::LuaEntitySAO(ServerEnvironment *env, v3f pos,
 	m_init_state(state),
 	m_registered(false),
 	m_prop(new LuaEntityProperties),
+	m_hp(-1),
 	m_velocity(0,0,0),
 	m_acceleration(0,0,0),
 	m_yaw(0),
@@ -1562,6 +361,10 @@ LuaEntitySAO::LuaEntitySAO(ServerEnvironment *env, v3f pos,
 		ServerActiveObject::registerType(getType(), create);
 		return;
 	}
+	
+	// Initialize something to armor groups
+	m_armor_groups["fleshy"] = 3;
+	m_armor_groups["snappy"] = 2;
 }
 
 LuaEntitySAO::~LuaEntitySAO()
@@ -1594,17 +397,34 @@ ServerActiveObject* LuaEntitySAO::create(ServerEnvironment *env, v3f pos,
 	std::istringstream is(data, std::ios::binary);
 	// read version
 	u8 version = readU8(is);
+	std::string name;
+	std::string state;
+	s16 hp = 1;
+	v3f velocity;
+	float yaw = 0;
 	// check if version is supported
-	if(version != 0)
+	if(version == 0){
+		name = deSerializeString(is);
+		state = deSerializeLongString(is);
+	}
+	else if(version == 1){
+		name = deSerializeString(is);
+		state = deSerializeLongString(is);
+		hp = readS16(is);
+		velocity = readV3F1000(is);
+		yaw = readF1000(is);
+	}
+	else{
 		return NULL;
-	// read name
-	std::string name = deSerializeString(is);
-	// read state
-	std::string state = deSerializeLongString(is);
+	}
 	// create object
 	infostream<<"LuaEntitySAO::create(name=\""<<name<<"\" state=\""
 			<<state<<"\")"<<std::endl;
-	return new LuaEntitySAO(env, pos, name, state);
+	LuaEntitySAO *sao = new LuaEntitySAO(env, pos, name, state);
+	sao->m_hp = hp;
+	sao->m_velocity = velocity;
+	sao->m_yaw = yaw;
+	return sao;
 }
 
 void LuaEntitySAO::step(float dtime, bool send_recommended)
@@ -1661,11 +481,13 @@ std::string LuaEntitySAO::getClientInitializationData()
 {
 	std::ostringstream os(std::ios::binary);
 	// version
-	writeU8(os, 0);
+	writeU8(os, 1);
 	// pos
 	writeV3F1000(os, m_base_position);
 	// yaw
 	writeF1000(os, m_yaw);
+	// hp
+	writeS16(os, m_hp);
 	// properties
 	std::ostringstream prop_os(std::ios::binary);
 	m_prop->serialize(prop_os);
@@ -1679,7 +501,7 @@ std::string LuaEntitySAO::getStaticData()
 	infostream<<__FUNCTION_NAME<<std::endl;
 	std::ostringstream os(std::ios::binary);
 	// version
-	writeU8(os, 0);
+	writeU8(os, 1);
 	// name
 	os<<serializeString(m_init_name);
 	// state
@@ -1690,18 +512,52 @@ std::string LuaEntitySAO::getStaticData()
 	} else {
 		os<<serializeLongString(m_init_state);
 	}
+	// hp
+	writeS16(os, m_hp);
+	// velocity
+	writeV3F1000(os, m_velocity);
+	// yaw
+	writeF1000(os, m_yaw);
 	return os.str();
 }
 
-void LuaEntitySAO::punch(ServerActiveObject *puncher, float time_from_last_punch)
+int LuaEntitySAO::punch(v3f dir,
+		const ToolCapabilities *toolcap,
+		ServerActiveObject *puncher,
+		float time_from_last_punch)
 {
 	if(!m_registered){
 		// Delete unknown LuaEntities when punched
 		m_removed = true;
-		return;
+		return 0;
 	}
 	lua_State *L = m_env->getLua();
-	scriptapi_luaentity_punch(L, m_id, puncher, time_from_last_punch);
+	scriptapi_luaentity_punch(L, m_id, puncher,
+			time_from_last_punch, toolcap, dir);
+
+	HitParams hitparams = getHitParams(m_armor_groups, toolcap,
+			time_from_last_punch);
+	
+	actionstream<<getDescription()<<" punched by "
+			<<puncher->getDescription()<<", damage "<<hitparams.hp
+			<<" HP"<<std::endl;
+	
+	setHP(getHP() - hitparams.hp);
+	
+	{
+		std::ostringstream os(std::ios::binary);
+		// command 
+		writeU8(os, LUAENTITY_CMD_PUNCHED);
+		// damage
+		writeS16(os, hitparams.hp);
+		// result_hp
+		writeS16(os, getHP());
+		// create message and add to list
+		ActiveObjectMessage aom(getId(), true, os.str());
+		m_messages_out.push_back(aom);
+	}
+
+	return hitparams.wear;
 }
 
 void LuaEntitySAO::rightClick(ServerActiveObject *clicker)
@@ -1712,6 +568,17 @@ void LuaEntitySAO::rightClick(ServerActiveObject *clicker)
 	scriptapi_luaentity_rightclick(L, m_id, clicker);
 }
 
+void LuaEntitySAO::setHP(s16 hp)
+{
+	if(hp < 0) hp = 0;
+	m_hp = hp;
+}
+
+s16 LuaEntitySAO::getHP()
+{
+	return m_hp;
+}
+
 void LuaEntitySAO::setPos(v3f pos)
 {
 	m_base_position = pos;
@@ -1730,6 +597,17 @@ float LuaEntitySAO::getMinimumSavedMovement()
 	return 0.1 * BS;
 }
 
+std::string LuaEntitySAO::getDescription()
+{
+	std::ostringstream os(std::ios::binary);
+	os<<"LuaEntitySAO at (";
+	os<<(m_base_position.X/BS)<<",";
+	os<<(m_base_position.Y/BS)<<",";
+	os<<(m_base_position.Z/BS);
+	os<<")";
+	return std::string("LuaEntitySAO");
+}
+
 void LuaEntitySAO::setVelocity(v3f velocity)
 {
 	m_velocity = velocity;
@@ -1763,8 +641,8 @@ float LuaEntitySAO::getYaw()
 void LuaEntitySAO::setTextureMod(const std::string &mod)
 {
 	std::ostringstream os(std::ios::binary);
-	// command (1 = set texture modification)
-	writeU8(os, 1);
+	// command 
+	writeU8(os, LUAENTITY_CMD_SET_TEXTURE_MOD);
 	// parameters
 	os<<serializeString(mod);
 	// create message and add to list
@@ -1776,8 +654,8 @@ void LuaEntitySAO::setSprite(v2s16 p, int num_frames, float framelength,
 		bool select_horiz_by_yawpitch)
 {
 	std::ostringstream os(std::ios::binary);
-	// command (2 = set sprite)
-	writeU8(os, 2);
+	// command
+	writeU8(os, LUAENTITY_CMD_SET_SPRITE);
 	// parameters
 	writeV2S16(os, p);
 	writeU16(os, num_frames);
@@ -1806,8 +684,8 @@ void LuaEntitySAO::sendPosition(bool do_interpolate, bool is_movement_end)
 	float update_interval = m_env->getSendRecommendedInterval();
 
 	std::ostringstream os(std::ios::binary);
-	// command (0 = update position)
-	writeU8(os, 0);
+	// command
+	writeU8(os, LUAENTITY_CMD_UPDATE_POSITION);
 
 	// do_interpolate
 	writeU8(os, do_interpolate);
diff --git a/src/content_sao.h b/src/content_sao.h
index f0c9cea90..507631d2a 100644
--- a/src/content_sao.h
+++ b/src/content_sao.h
@@ -22,173 +22,16 @@ with this program; if not, write to the Free Software Foundation, Inc.,
 
 #include "serverobject.h"
 #include "content_object.h"
+#include "itemgroup.h"
 
-class TestSAO : public ServerActiveObject
-{
-public:
-	TestSAO(ServerEnvironment *env, v3f pos);
-	u8 getType() const
-		{return ACTIVEOBJECT_TYPE_TEST;}
-	static ServerActiveObject* create(ServerEnvironment *env, v3f pos,
-			const std::string &data);
-	void step(float dtime, bool send_recommended);
-private:
-	float m_timer1;
-	float m_age;
-};
-
-class ItemSAO : public ServerActiveObject
-{
-public:
-	ItemSAO(ServerEnvironment *env, v3f pos, const std::string itemstring);
-	u8 getType() const
-		{return ACTIVEOBJECT_TYPE_ITEM;}
-	static ServerActiveObject* create(ServerEnvironment *env, v3f pos,
-			const std::string &data);
-	void step(float dtime, bool send_recommended);
-	std::string getClientInitializationData();
-	std::string getStaticData();
-	ItemStack createItemStack();
-	void punch(ServerActiveObject *puncher, float time_from_last_punch);
-	float getMinimumSavedMovement(){ return 0.1*BS; }
-private:
-	std::string m_itemstring;
-	bool m_itemstring_changed;
-	v3f m_speed_f;
-	v3f m_last_sent_position;
-	IntervalLimiter m_move_interval;
-};
+ServerActiveObject* createItemSAO(ServerEnvironment *env, v3f pos,
+		const std::string itemstring);
 
-class RatSAO : public ServerActiveObject
-{
-public:
-	RatSAO(ServerEnvironment *env, v3f pos);
-	u8 getType() const
-		{return ACTIVEOBJECT_TYPE_RAT;}
-	static ServerActiveObject* create(ServerEnvironment *env, v3f pos,
-			const std::string &data);
-	void step(float dtime, bool send_recommended);
-	std::string getClientInitializationData();
-	std::string getStaticData();
-	void punch(ServerActiveObject *puncher, float time_from_last_punch);
-private:
-	bool m_is_active;
-	IntervalLimiter m_inactive_interval;
-	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;
-};
-
-class Oerkki1SAO : public ServerActiveObject
-{
-public:
-	Oerkki1SAO(ServerEnvironment *env, v3f pos);
-	u8 getType() const
-		{return ACTIVEOBJECT_TYPE_OERKKI1;}
-	static ServerActiveObject* create(ServerEnvironment *env, v3f pos,
-			const std::string &data);
-	void step(float dtime, bool send_recommended);
-	std::string getClientInitializationData();
-	std::string getStaticData();
-	void punch(ServerActiveObject *puncher, float time_from_last_punch);
-	bool isPeaceful(){return false;}
-private:
-	void doDamage(u16 d);
-
-	bool m_is_active;
-	IntervalLimiter m_inactive_interval;
-	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;
-	u8 m_hp;
-	float m_after_jump_timer;
-};
-
-class FireflySAO : public ServerActiveObject
-{
-public:
-	FireflySAO(ServerEnvironment *env, v3f pos);
-	u8 getType() const
-		{return ACTIVEOBJECT_TYPE_FIREFLY;}
-	static ServerActiveObject* create(ServerEnvironment *env, v3f pos,
-			const std::string &data);
-	void step(float dtime, bool send_recommended);
-	std::string getClientInitializationData();
-	std::string getStaticData();
-private:
-	bool m_is_active;
-	IntervalLimiter m_inactive_interval;
-	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;
-};
-
-class Settings;
-
-class MobV2SAO : public ServerActiveObject
-{
-public:
-	MobV2SAO(ServerEnvironment *env, v3f pos,
-			Settings *init_properties);
-	virtual ~MobV2SAO();
-	u8 getType() const
-		{return ACTIVEOBJECT_TYPE_MOBV2;}
-	static ServerActiveObject* create(ServerEnvironment *env, v3f pos,
-			const std::string &data);
-	std::string getStaticData();
-	std::string getClientInitializationData();
-	void step(float dtime, bool send_recommended);
-	void punch(ServerActiveObject *puncher, float time_from_last_punch);
-	bool isPeaceful();
-private:
-	void sendPosition();
-	void setPropertyDefaults();
-	void readProperties();
-	void updateProperties();
-	void doDamage(u16 d);
-	
-	std::string m_move_type;
-	v3f m_speed;
-	v3f m_last_sent_position;
-	v3f m_oldpos;
-	float m_yaw;
-	float m_counter1;
-	float m_counter2;
-	float m_age;
-	bool m_touching_ground;
-	int m_hp;
-	bool m_walk_around;
-	float m_walk_around_timer;
-	bool m_next_pos_exists;
-	v3s16 m_next_pos_i;
-	float m_shoot_reload_timer;
-	bool m_shooting;
-	float m_shooting_timer;
-	float m_die_age;
-	v2f m_size;
-	bool m_falling;
-	float m_disturb_timer;
-	std::string m_disturbing_player;
-	float m_random_disturb_timer;
-	float m_shoot_y;
+/*
+	LuaEntitySAO
 	
-	Settings *m_properties;
-};
+	This is the only SAO that needs to have a bunch of it's internals exposed.
+*/
 
 struct LuaEntityProperties;
 
@@ -206,11 +49,17 @@ class LuaEntitySAO : public ServerActiveObject
 	void step(float dtime, bool send_recommended);
 	std::string getClientInitializationData();
 	std::string getStaticData();
-	void punch(ServerActiveObject *puncher, float time_from_last_punch);
+	int punch(v3f dir,
+			const ToolCapabilities *toolcap=NULL,
+			ServerActiveObject *puncher=NULL,
+			float time_from_last_punch=1000000);
 	void rightClick(ServerActiveObject *clicker);
 	void setPos(v3f pos);
 	void moveTo(v3f pos, bool continuous);
 	float getMinimumSavedMovement();
+	std::string getDescription();
+	void setHP(s16 hp);
+	s16 getHP();
 	/* LuaEntitySAO-specific */
 	void setVelocity(v3f velocity);
 	v3f getVelocity();
@@ -230,9 +79,12 @@ class LuaEntitySAO : public ServerActiveObject
 	bool m_registered;
 	struct LuaEntityProperties *m_prop;
 	
+	s16 m_hp;
 	v3f m_velocity;
 	v3f m_acceleration;
 	float m_yaw;
+	ItemGroupList m_armor_groups;
+
 	float m_last_sent_yaw;
 	v3f m_last_sent_position;
 	v3f m_last_sent_velocity;
diff --git a/src/itemgroup.h b/src/itemgroup.h
new file mode 100644
index 000000000..927811dd0
--- /dev/null
+++ b/src/itemgroup.h
@@ -0,0 +1,39 @@
+/*
+Minetest-c55
+Copyright (C) 2012 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 ITEMGROUP_HEADER
+#define ITEMGROUP_HEADER
+
+#include "common_irrlicht.h"
+#include <string>
+#include <map>
+
+typedef std::map<std::string, int> ItemGroupList;
+
+static inline int itemgroup_get(const ItemGroupList &groups,
+		const std::string &name)
+{
+	std::map<std::string, int>::const_iterator i = groups.find(name);
+	if(i == groups.end())
+		return 0;
+	return i->second;
+}
+
+#endif
+
diff --git a/src/luaentity_common.cpp b/src/luaentity_common.cpp
index 40dc4de35..63c3f2d6b 100644
--- a/src/luaentity_common.cpp
+++ b/src/luaentity_common.cpp
@@ -25,6 +25,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
 #define PP2(x) "("<<(x).X<<","<<(x).Y<<")"
 
 LuaEntityProperties::LuaEntityProperties():
+	hp_max(1),
 	physical(false),
 	weight(5),
 	collisionbox(-0.5,-0.5,-0.5, 0.5,0.5,0.5),
@@ -39,7 +40,8 @@ LuaEntityProperties::LuaEntityProperties():
 std::string LuaEntityProperties::dump()
 {
 	std::ostringstream os(std::ios::binary);
-	os<<"physical="<<physical;
+	os<<"hp_max="<<hp_max;
+	os<<", physical="<<physical;
 	os<<", weight="<<weight;
 	os<<", collisionbox="<<PP(collisionbox.MinEdge)<<","<<PP(collisionbox.MaxEdge);
 	os<<", visual="<<visual;
@@ -56,7 +58,8 @@ std::string LuaEntityProperties::dump()
 
 void LuaEntityProperties::serialize(std::ostream &os)
 {
-	writeU8(os, 0); // version
+	writeU8(os, 1); // version
+	writeS16(os, hp_max);
 	writeU8(os, physical);
 	writeF1000(os, weight);
 	writeV3F1000(os, collisionbox.MinEdge);
@@ -74,8 +77,9 @@ void LuaEntityProperties::serialize(std::ostream &os)
 void LuaEntityProperties::deSerialize(std::istream &is)
 {
 	int version = readU8(is);
-	if(version != 0) throw SerializationError(
+	if(version != 1) throw SerializationError(
 			"unsupported LuaEntityProperties version");
+	hp_max = readS16(is);
 	physical = readU8(is);
 	weight = readF1000(is);
 	collisionbox.MinEdge = readV3F1000(is);
diff --git a/src/luaentity_common.h b/src/luaentity_common.h
index bc2871a94..b63663828 100644
--- a/src/luaentity_common.h
+++ b/src/luaentity_common.h
@@ -27,6 +27,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
 struct LuaEntityProperties
 {
 	// Values are BS=1
+	s16 hp_max;
 	bool physical;
 	float weight;
 	core::aabbox3d<f32> collisionbox;
@@ -42,5 +43,10 @@ struct LuaEntityProperties
 	void deSerialize(std::istream &is);
 };
 
+#define LUAENTITY_CMD_UPDATE_POSITION 0
+#define LUAENTITY_CMD_SET_TEXTURE_MOD 1
+#define LUAENTITY_CMD_SET_SPRITE 2
+#define LUAENTITY_CMD_PUNCHED 3
+
 #endif
 
diff --git a/src/scriptapi.cpp b/src/scriptapi.cpp
index 2ee727b3f..c3059ec55 100644
--- a/src/scriptapi.cpp
+++ b/src/scriptapi.cpp
@@ -2297,17 +2297,22 @@ class ObjectRef
 		return 0;
 	}
 
-	// punch(self, puncher); puncher = an another ObjectRef
+	// punch(self, puncher, tool_capabilities, direction, time_from_last_punch)
 	static int l_punch(lua_State *L)
 	{
 		ObjectRef *ref = checkobject(L, 1);
-		ObjectRef *ref2 = checkobject(L, 2);
+		ObjectRef *puncher_ref = checkobject(L, 2);
 		ServerActiveObject *co = getobject(ref);
-		ServerActiveObject *co2 = getobject(ref2);
+		ServerActiveObject *puncher = getobject(puncher_ref);
 		if(co == NULL) return 0;
-		if(co2 == NULL) return 0;
+		if(puncher == NULL) return 0;
+		ToolCapabilities toolcap = read_tool_capabilities(L, 3);
+		v3f dir = read_v3f(L, 4);
+		float time_from_last_punch = 1000000;
+		if(lua_isnumber(L, 5))
+			time_from_last_punch = lua_tonumber(L, 5);
 		// Do it
-		co->punch(co2);
+		puncher->punch(dir, &toolcap, puncher, time_from_last_punch);
 		return 0;
 	}
 
@@ -2878,7 +2883,7 @@ class EnvRef
 		if(item.empty() || !item.isKnown(get_server(L)->idef()))
 			return 0;
 		// Do it
-		ServerActiveObject *obj = new ItemSAO(env, pos, item.getItemString());
+		ServerActiveObject *obj = createItemSAO(env, pos, item.getItemString());
 		int objectid = env->addActiveObject(obj);
 		// If failed to add, return nothing (reads as nil)
 		if(objectid == 0)
@@ -2892,15 +2897,8 @@ class EnvRef
 	// pos = {x=num, y=num, z=num}
 	static int l_add_rat(lua_State *L)
 	{
-		infostream<<"EnvRef::l_add_rat()"<<std::endl;
-		EnvRef *o = checkobject(L, 1);
-		ServerEnvironment *env = o->m_env;
-		if(env == NULL) return 0;
-		// pos
-		v3f pos = checkFloatPos(L, 2);
-		// Do it
-		ServerActiveObject *obj = new RatSAO(env, pos);
-		env->addActiveObject(obj);
+		infostream<<"EnvRef::l_add_rat(): C++ mobs have been removed."
+				<<" Doing nothing."<<std::endl;
 		return 0;
 	}
 
@@ -2908,15 +2906,8 @@ class EnvRef
 	// pos = {x=num, y=num, z=num}
 	static int l_add_firefly(lua_State *L)
 	{
-		infostream<<"EnvRef::l_add_firefly()"<<std::endl;
-		EnvRef *o = checkobject(L, 1);
-		ServerEnvironment *env = o->m_env;
-		if(env == NULL) return 0;
-		// pos
-		v3f pos = checkFloatPos(L, 2);
-		// Do it
-		ServerActiveObject *obj = new FireflySAO(env, pos);
-		env->addActiveObject(obj);
+		infostream<<"EnvRef::l_add_firefly(): C++ mobs have been removed."
+				<<" Doing nothing."<<std::endl;
 		return 0;
 	}
 
@@ -4348,6 +4339,8 @@ void scriptapi_luaentity_get_properties(lua_State *L, u16 id,
 
 	/* Read stuff */
 	
+	prop->hp_max = getintfield_default(L, -1, "hp_max", 10);
+
 	getboolfield(L, -1, "physical", prop->physical);
 
 	getfloatfield(L, -1, "weight", prop->weight);
@@ -4415,9 +4408,11 @@ void scriptapi_luaentity_step(lua_State *L, u16 id, float dtime)
 		script_error(L, "error running function 'on_step': %s\n", lua_tostring(L, -1));
 }
 
-// Calls entity:on_punch(ObjectRef puncher, time_from_last_punch)
+// Calls entity:on_punch(ObjectRef puncher, time_from_last_punch,
+//                       tool_capabilities, direction)
 void scriptapi_luaentity_punch(lua_State *L, u16 id,
-		ServerActiveObject *puncher, float time_from_last_punch)
+		ServerActiveObject *puncher, float time_from_last_punch,
+		const ToolCapabilities *toolcap, v3f dir)
 {
 	realitycheck(L);
 	assert(lua_checkstack(L, 20));
@@ -4436,8 +4431,10 @@ void scriptapi_luaentity_punch(lua_State *L, u16 id,
 	lua_pushvalue(L, object); // self
 	objectref_get_or_create(L, puncher); // Clicker reference
 	lua_pushnumber(L, time_from_last_punch);
-	// Call with 2 arguments, 0 results
-	if(lua_pcall(L, 3, 0, 0))
+	push_tool_capabilities(L, *toolcap);
+	push_v3f(L, dir);
+	// Call with 5 arguments, 0 results
+	if(lua_pcall(L, 5, 0, 0))
 		script_error(L, "error running function 'on_punch': %s\n", lua_tostring(L, -1));
 }
 
diff --git a/src/scriptapi.h b/src/scriptapi.h
index df8ae344e..aa2d9d4c7 100644
--- a/src/scriptapi.h
+++ b/src/scriptapi.h
@@ -33,6 +33,7 @@ struct LuaEntityProperties;
 struct ItemStack;
 struct PointedThing;
 //class IGameDef;
+struct ToolCapabilities;
 
 void scriptapi_export(lua_State *L, Server *server);
 bool scriptapi_loadmod(lua_State *L, const std::string &scriptpath,
@@ -82,7 +83,8 @@ void scriptapi_luaentity_get_properties(lua_State *L, u16 id,
 		LuaEntityProperties *prop);
 void scriptapi_luaentity_step(lua_State *L, u16 id, float dtime);
 void scriptapi_luaentity_punch(lua_State *L, u16 id,
-		ServerActiveObject *puncher, float time_from_last_punch);
+		ServerActiveObject *puncher, float time_from_last_punch,
+		const ToolCapabilities *toolcap, v3f dir);
 void scriptapi_luaentity_rightclick(lua_State *L, u16 id,
 		ServerActiveObject *clicker);
 
diff --git a/src/server.cpp b/src/server.cpp
index 443a2a39b..899624633 100644
--- a/src/server.cpp
+++ b/src/server.cpp
@@ -47,6 +47,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
 #include "mods.h"
 #include "sha1.h"
 #include "base64.h"
+#include "tool.h"
 
 #define PP(x) "("<<(x).X<<","<<(x).Y<<","<<(x).Z<<")"
 
@@ -1244,7 +1245,7 @@ void Server::AsyncRunStep()
 				explosion.
 			*/
 			player->m_last_good_position_age += dtime;
-			if(player->m_last_good_position_age >= 2.0){
+			if(player->m_last_good_position_age >= 1.0){
 				float age = player->m_last_good_position_age;
 				v3f diff = (player->getPosition() - player->m_last_good_position);
 				float d_vert = diff.Y;
@@ -2870,7 +2871,7 @@ void Server::ProcessData(u8 *data, u32 datasize, u16 peer_id)
 		if(action == 0 || action == 2 || action == 3)
 		{
 			float d = player_pos.getDistanceFrom(pointed_pos_under);
-			float max_d = BS * 10; // Just some large enough value
+			float max_d = BS * 14; // Just some large enough value
 			if(d > max_d){
 				actionstream<<"Player "<<player->getName()
 						<<" tried to access "<<pointed.dump()
@@ -2933,8 +2934,14 @@ void Server::ProcessData(u8 *data, u32 datasize, u16 peer_id)
 				actionstream<<player->getName()<<" punches object "
 					<<pointed.object_id<<std::endl;
 
-				// Do stuff
-				pointed_object->punch(srp, srp->m_time_from_last_punch);
+				ItemStack punchitem = srp->getWieldedItem();
+				ToolCapabilities toolcap =
+						punchitem.getToolCapabilities(m_itemdef);
+				v3f dir = (pointed_object->getBasePosition() -
+						(srp->getPosition() + srp->getEyeOffset())
+							).normalize();
+				pointed_object->punch(dir, &toolcap, srp,
+						srp->m_time_from_last_punch);
 				srp->m_time_from_last_punch = 0;
 			}
 
diff --git a/src/serverobject.h b/src/serverobject.h
index 380bf7302..15bbe52f7 100644
--- a/src/serverobject.h
+++ b/src/serverobject.h
@@ -44,7 +44,7 @@ Some planning
 class ServerEnvironment;
 struct ItemStack;
 class Player;
-struct ToolDiggingProperties;
+struct ToolCapabilities;
 
 class ServerActiveObject : public ActiveObject
 {
@@ -133,10 +133,12 @@ class ServerActiveObject : public ActiveObject
 	virtual bool isStaticAllowed() const
 	{return true;}
 	
-	// time_from_last_punch is used for lessening damage if punching fast
-	virtual void punch(ServerActiveObject *puncher,
+	// Returns tool wear
+	virtual int punch(v3f dir,
+			const ToolCapabilities *toolcap=NULL,
+			ServerActiveObject *puncher=NULL,
 			float time_from_last_punch=1000000)
-	{}
+	{ return 0; }
 	virtual void rightClick(ServerActiveObject *clicker)
 	{}
 	virtual void setHP(s16 hp)
diff --git a/src/serverremoteplayer.cpp b/src/serverremoteplayer.cpp
index c57794ea2..667ece7f2 100644
--- a/src/serverremoteplayer.cpp
+++ b/src/serverremoteplayer.cpp
@@ -169,16 +169,18 @@ std::string ServerRemotePlayer::getStaticData()
 	return "";
 }
 
-void ServerRemotePlayer::punch(ServerActiveObject *puncher,
+int ServerRemotePlayer::punch(v3f dir,
+		const ToolCapabilities *toolcap,
+		ServerActiveObject *puncher,
 		float time_from_last_punch)
 {
-	if(!puncher)
-		return;
+	if(!toolcap)
+		return 0;
 	
 	// No effect if PvP disabled
 	if(g_settings->getBool("enable_pvp") == false){
 		if(puncher->getType() == ACTIVEOBJECT_TYPE_PLAYER)
-			return;
+			return 0;
 	}
 	
 	// "Material" groups of the player
@@ -186,19 +188,13 @@ void ServerRemotePlayer::punch(ServerActiveObject *puncher,
 	groups["choppy"] = 2;
 	groups["fleshy"] = 3;
 
-	IItemDefManager *idef = m_env->getGameDef()->idef();
-	ItemStack punchitem = puncher->getWieldedItem();
-	ToolCapabilities tp = punchitem.getToolCapabilities(idef);
-
-	HitParams hitparams = getHitParams(groups, &tp, time_from_last_punch);
+	HitParams hitparams = getHitParams(groups, toolcap, time_from_last_punch);
 	
 	actionstream<<"Player "<<getName()<<" punched by "
 			<<puncher->getDescription()<<", damage "<<hitparams.hp
 			<<" HP"<<std::endl;
 	
 	setHP(getHP() - hitparams.hp);
-	punchitem.addWear(hitparams.wear, idef);
-	puncher->setWieldedItem(punchitem);
 	
 	if(hitparams.hp != 0)
 	{
@@ -211,6 +207,8 @@ void ServerRemotePlayer::punch(ServerActiveObject *puncher,
 		ActiveObjectMessage aom(getId(), false, os.str());
 		m_messages_out.push_back(aom);
 	}
+
+	return hitparams.wear;
 }
 
 void ServerRemotePlayer::rightClick(ServerActiveObject *clicker)
diff --git a/src/serverremoteplayer.h b/src/serverremoteplayer.h
index 94926c824..2ff1b0013 100644
--- a/src/serverremoteplayer.h
+++ b/src/serverremoteplayer.h
@@ -67,7 +67,10 @@ class ServerRemotePlayer : public Player, public ServerActiveObject
 	void step(float dtime, bool send_recommended);
 	std::string getClientInitializationData();
 	std::string getStaticData();
-	void punch(ServerActiveObject *puncher, float time_from_last_punch);
+	int punch(v3f dir,
+			const ToolCapabilities *toolcap,
+			ServerActiveObject *puncher,
+			float time_from_last_punch);
 	void rightClick(ServerActiveObject *clicker);
 	void setPos(v3f pos);
 	void moveTo(v3f pos, bool continuous);
-- 
GitLab