From c9e7a27eeb628be78a835abadf8afe1177eb90c5 Mon Sep 17 00:00:00 2001
From: raymoo <uguu@installgentoo.com>
Date: Thu, 4 Aug 2016 13:09:21 -0700
Subject: [PATCH] Attached particle spawners

---
 doc/lua_api.txt                     |  2 +
 src/client.h                        |  1 +
 src/content_sao.cpp                 | 10 +++-
 src/environment.cpp                 | 24 +++++++++
 src/environment.h                   |  4 +-
 src/network/clientpackethandler.cpp |  3 ++
 src/particles.cpp                   | 81 ++++++++++++++++++-----------
 src/particles.h                     |  3 +-
 src/script/lua_api/l_particles.cpp  | 11 ++++
 src/server.cpp                      | 17 ++++--
 src/server.h                        |  2 +
 src/serverobject.cpp                |  1 -
 src/serverobject.h                  | 10 ++++
 13 files changed, 132 insertions(+), 37 deletions(-)

diff --git a/doc/lua_api.txt b/doc/lua_api.txt
index 6bdcd4fe4..a1d598e7d 100644
--- a/doc/lua_api.txt
+++ b/doc/lua_api.txt
@@ -4120,6 +4120,8 @@ The Biome API is still in an experimental phase and subject to change.
         collision_removal = false,
     --  ^ collision_removal: if true then particle is removed when it collides,
     --  ^ requires collisiondetection = true to have any effect
+        attached = ObjectRef,
+    --  ^ attached: if defined, makes particle positions relative to this object.
         vertical = false,
     --  ^ vertical: if true faces player using y axis only
         texture = "image.png",
diff --git a/src/client.h b/src/client.h
index 6d24c0b1d..9f5bda059 100644
--- a/src/client.h
+++ b/src/client.h
@@ -201,6 +201,7 @@ struct ClientEvent
 			f32 maxsize;
 			bool collisiondetection;
 			bool collision_removal;
+			u16 attached_id;
 			bool vertical;
 			std::string *texture;
 			u32 id;
diff --git a/src/content_sao.cpp b/src/content_sao.cpp
index 5d3ed38bc..375a43c90 100644
--- a/src/content_sao.cpp
+++ b/src/content_sao.cpp
@@ -156,6 +156,11 @@ LuaEntitySAO::~LuaEntitySAO()
 	if(m_registered){
 		m_env->getScriptIface()->luaentity_Remove(m_id);
 	}
+
+	for (UNORDERED_SET<u32>::iterator it = m_attached_particle_spawners.begin();
+		it != m_attached_particle_spawners.end(); ++it) {
+		m_env->deleteParticleSpawner(*it, false);
+	}
 }
 
 void LuaEntitySAO::addedToEnvironment(u32 dtime_s)
@@ -817,7 +822,6 @@ PlayerSAO::~PlayerSAO()
 {
 	if(m_inventory != &m_player->inventory)
 		delete m_inventory;
-
 }
 
 std::string PlayerSAO::getDescription()
@@ -844,6 +848,10 @@ void PlayerSAO::removingFromEnvironment()
 		m_player->peer_id = 0;
 		m_env->savePlayer(m_player);
 		m_env->removePlayer(m_player);
+		for (UNORDERED_SET<u32>::iterator it = m_attached_particle_spawners.begin();
+			it != m_attached_particle_spawners.end(); ++it) {
+			m_env->deleteParticleSpawner(*it, false);
+		}
 	}
 }
 
diff --git a/src/environment.cpp b/src/environment.cpp
index ceaa01d7a..ceaf40d89 100644
--- a/src/environment.cpp
+++ b/src/environment.cpp
@@ -1518,6 +1518,30 @@ u32 ServerEnvironment::addParticleSpawner(float exptime)
 	return id;
 }
 
+u32 ServerEnvironment::addParticleSpawner(float exptime, u16 attached_id)
+{
+	u32 id = addParticleSpawner(exptime);
+	m_particle_spawner_attachments[id] = attached_id;
+	if (ServerActiveObject *obj = getActiveObject(attached_id)) {
+		obj->attachParticleSpawner(id);
+	}
+	return id;
+}
+
+void ServerEnvironment::deleteParticleSpawner(u32 id, bool remove_from_object)
+{
+	m_particle_spawners.erase(id);
+	UNORDERED_MAP<u32, u16>::iterator it = m_particle_spawner_attachments.find(id);
+	if (it != m_particle_spawner_attachments.end()) {
+		u16 obj_id = (*it).second;
+		ServerActiveObject *sao = getActiveObject(obj_id);
+		if (sao != NULL && remove_from_object) {
+			sao->detachParticleSpawner(id);
+		}
+		m_particle_spawner_attachments.erase(id);
+	}
+}
+
 ServerActiveObject* ServerEnvironment::getActiveObject(u16 id)
 {
 	ActiveObjectMap::iterator n = m_active_objects.find(id);
diff --git a/src/environment.h b/src/environment.h
index 83ad69562..3f3c1cf2c 100644
--- a/src/environment.h
+++ b/src/environment.h
@@ -329,7 +329,8 @@ class ServerEnvironment : public Environment
 	void loadDefaultMeta();
 
 	u32 addParticleSpawner(float exptime);
-	void deleteParticleSpawner(u32 id) { m_particle_spawners.erase(id); }
+	u32 addParticleSpawner(float exptime, u16 attached_id);
+	void deleteParticleSpawner(u32 id, bool remove_from_object = true);
 
 	/*
 		External ActiveObject interface
@@ -519,6 +520,7 @@ class ServerEnvironment : public Environment
 	// Particles
 	IntervalLimiter m_particle_management_interval;
 	UNORDERED_MAP<u32, float> m_particle_spawners;
+	UNORDERED_MAP<u32, u16> m_particle_spawner_attachments;
 };
 
 #ifndef SERVER
diff --git a/src/network/clientpackethandler.cpp b/src/network/clientpackethandler.cpp
index b39356e92..090741f9f 100644
--- a/src/network/clientpackethandler.cpp
+++ b/src/network/clientpackethandler.cpp
@@ -944,9 +944,11 @@ void Client::handleCommand_AddParticleSpawner(NetworkPacket* pkt)
 
 	bool vertical = false;
 	bool collision_removal = false;
+	u16 attached_id = 0;
 	try {
 		*pkt >> vertical;
 		*pkt >> collision_removal;
+		*pkt >> attached_id;
 
 	} catch (...) {}
 
@@ -966,6 +968,7 @@ void Client::handleCommand_AddParticleSpawner(NetworkPacket* pkt)
 	event.add_particlespawner.maxsize            = maxsize;
 	event.add_particlespawner.collisiondetection = collisiondetection;
 	event.add_particlespawner.collision_removal  = collision_removal;
+	event.add_particlespawner.attached_id        = attached_id;
 	event.add_particlespawner.vertical           = vertical;
 	event.add_particlespawner.texture            = new std::string(texture);
 	event.add_particlespawner.id                 = id;
diff --git a/src/particles.cpp b/src/particles.cpp
index ccca691d1..2efee6ada 100644
--- a/src/particles.cpp
+++ b/src/particles.cpp
@@ -213,7 +213,7 @@ ParticleSpawner::ParticleSpawner(IGameDef* gamedef, scene::ISceneManager *smgr,
 	u16 amount, float time,
 	v3f minpos, v3f maxpos, v3f minvel, v3f maxvel, v3f minacc, v3f maxacc,
 	float minexptime, float maxexptime, float minsize, float maxsize,
-	bool collisiondetection, bool collision_removal, bool vertical,
+	bool collisiondetection, bool collision_removal, u16 attached_id, bool vertical,
 	video::ITexture *texture, u32 id, ParticleManager *p_manager) :
 	m_particlemanager(p_manager)
 {
@@ -234,6 +234,7 @@ ParticleSpawner::ParticleSpawner(IGameDef* gamedef, scene::ISceneManager *smgr,
 	m_maxsize = maxsize;
 	m_collisiondetection = collisiondetection;
 	m_collision_removal = collision_removal;
+	m_attached_id = attached_id;
 	m_vertical = vertical;
 	m_texture = texture;
 	m_time = 0;
@@ -251,6 +252,15 @@ void ParticleSpawner::step(float dtime, ClientEnvironment* env)
 {
 	m_time += dtime;
 
+	bool unloaded = false;
+	v3f attached_offset = v3f(0,0,0);
+	if (m_attached_id != 0) {
+		if (ClientActiveObject *attached = env->getActiveObject(m_attached_id))
+			attached_offset = attached->getPosition() / BS;
+		else
+			unloaded = true;
+	}
+
 	if (m_spawntime != 0) // Spawner exists for a predefined timespan
 	{
 		for(std::vector<float>::iterator i = m_spawntimes.begin();
@@ -260,33 +270,41 @@ void ParticleSpawner::step(float dtime, ClientEnvironment* env)
 			{
 				m_amount--;
 
-				v3f pos = random_v3f(m_minpos, m_maxpos);
-				v3f vel = random_v3f(m_minvel, m_maxvel);
-				v3f acc = random_v3f(m_minacc, m_maxacc);
-				float exptime = rand()/(float)RAND_MAX
-						*(m_maxexptime-m_minexptime)
-						+m_minexptime;
-				float size = rand()/(float)RAND_MAX
-						*(m_maxsize-m_minsize)
-						+m_minsize;
-
-				Particle* toadd = new Particle(
-					m_gamedef,
-					m_smgr,
-					m_player,
-					env,
-					pos,
-					vel,
-					acc,
-					exptime,
-					size,
-					m_collisiondetection,
-					m_collision_removal,
-					m_vertical,
-					m_texture,
-					v2f(0.0, 0.0),
-					v2f(1.0, 1.0));
-				m_particlemanager->addParticle(toadd);
+				// Pretend to, but don't actually spawn a
+				// particle if it is attached to an unloaded
+				// object.
+				if (!unloaded) {
+					v3f pos = random_v3f(m_minpos, m_maxpos)
+							+ attached_offset;
+					v3f vel = random_v3f(m_minvel, m_maxvel);
+					v3f acc = random_v3f(m_minacc, m_maxacc);
+					// Make relative to offest
+					pos += attached_offset;
+					float exptime = rand()/(float)RAND_MAX
+							*(m_maxexptime-m_minexptime)
+							+m_minexptime;
+					float size = rand()/(float)RAND_MAX
+							*(m_maxsize-m_minsize)
+							+m_minsize;
+
+					Particle* toadd = new Particle(
+						m_gamedef,
+						m_smgr,
+						m_player,
+						env,
+						pos,
+						vel,
+						acc,
+						exptime,
+						size,
+						m_collisiondetection,
+						m_collision_removal,
+						m_vertical,
+						m_texture,
+						v2f(0.0, 0.0),
+						v2f(1.0, 1.0));
+					m_particlemanager->addParticle(toadd);
+				}
 				i = m_spawntimes.erase(i);
 			}
 			else
@@ -297,11 +315,15 @@ void ParticleSpawner::step(float dtime, ClientEnvironment* env)
 	}
 	else // Spawner exists for an infinity timespan, spawn on a per-second base
 	{
+		// Skip this step if attached to an unloaded object
+		if (unloaded)
+			return;
 		for (int i = 0; i <= m_amount; i++)
 		{
 			if (rand()/(float)RAND_MAX < dtime)
 			{
-				v3f pos = random_v3f(m_minpos, m_maxpos);
+				v3f pos = random_v3f(m_minpos, m_maxpos)
+						+ attached_offset;
 				v3f vel = random_v3f(m_minvel, m_maxvel);
 				v3f acc = random_v3f(m_minacc, m_maxacc);
 				float exptime = rand()/(float)RAND_MAX
@@ -453,6 +475,7 @@ void ParticleManager::handleParticleEvent(ClientEvent *event, IGameDef *gamedef,
 					event->add_particlespawner.maxsize,
 					event->add_particlespawner.collisiondetection,
 					event->add_particlespawner.collision_removal,
+					event->add_particlespawner.attached_id,
 					event->add_particlespawner.vertical,
 					texture,
 					event->add_particlespawner.id,
diff --git a/src/particles.h b/src/particles.h
index bc3ca53b7..eb8c6665d 100644
--- a/src/particles.h
+++ b/src/particles.h
@@ -119,6 +119,7 @@ class ParticleSpawner
 		float minsize, float maxsize,
 		bool collisiondetection,
 		bool collision_removal,
+		u16 attached_id,
 		bool vertical,
 		video::ITexture *texture,
 		u32 id,
@@ -154,7 +155,7 @@ class ParticleSpawner
 	bool m_collisiondetection;
 	bool m_collision_removal;
 	bool m_vertical;
-
+	u16 m_attached_id;
 };
 
 /**
diff --git a/src/script/lua_api/l_particles.cpp b/src/script/lua_api/l_particles.cpp
index 263e35407..667ac7272 100644
--- a/src/script/lua_api/l_particles.cpp
+++ b/src/script/lua_api/l_particles.cpp
@@ -18,6 +18,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
 */
 
 #include "lua_api/l_particles.h"
+#include "lua_api/l_object.h"
 #include "lua_api/l_internal.h"
 #include "common/c_converter.h"
 #include "server.h"
@@ -138,6 +139,7 @@ int ModApiParticles::l_add_particlespawner(lua_State *L)
 	      time= minexptime= maxexptime= minsize= maxsize= 1;
 	bool collisiondetection, vertical, collision_removal;
 	     collisiondetection = vertical = collision_removal = false;
+	ServerActiveObject *attached = NULL;
 	std::string texture = "";
 	std::string playername = "";
 
@@ -198,6 +200,14 @@ int ModApiParticles::l_add_particlespawner(lua_State *L)
 			"collisiondetection", collisiondetection);
 		collision_removal = getboolfield_default(L, 1,
 			"collision_removal", collision_removal);
+
+		lua_getfield(L, 1, "attached");
+		if (!lua_isnil(L, -1)) {
+			ObjectRef *ref = ObjectRef::checkobject(L, -1);
+			lua_pop(L, 1);
+			attached = ObjectRef::getobject(ref);
+		}
+
 		vertical = getboolfield_default(L, 1, "vertical", vertical);
 		texture = getstringfield_default(L, 1, "texture", "");
 		playername = getstringfield_default(L, 1, "playername", "");
@@ -211,6 +221,7 @@ int ModApiParticles::l_add_particlespawner(lua_State *L)
 			minsize, maxsize,
 			collisiondetection,
 			collision_removal,
+			attached,
 			vertical,
 			texture, playername);
 	lua_pushnumber(L, id);
diff --git a/src/server.cpp b/src/server.cpp
index a93c143c7..e67f37d56 100644
--- a/src/server.cpp
+++ b/src/server.cpp
@@ -1678,7 +1678,7 @@ void Server::SendSpawnParticle(u16 peer_id, v3f pos, v3f velocity, v3f accelerat
 void Server::SendAddParticleSpawner(u16 peer_id, u16 amount, float spawntime, v3f minpos, v3f maxpos,
 	v3f minvel, v3f maxvel, v3f minacc, v3f maxacc, float minexptime, float maxexptime,
 	float minsize, float maxsize, bool collisiondetection, bool collision_removal,
-	bool vertical, const std::string &texture, u32 id)
+	u16 attached_id, bool vertical, const std::string &texture, u32 id)
 {
 	DSTACK(FUNCTION_NAME);
 
@@ -1692,6 +1692,7 @@ void Server::SendAddParticleSpawner(u16 peer_id, u16 amount, float spawntime, v3
 
 	pkt << id << vertical;
 	pkt << collision_removal;
+	pkt << attached_id;
 
 	if (peer_id != PEER_ID_INEXISTENT) {
 		Send(&pkt);
@@ -3156,7 +3157,7 @@ u32 Server::addParticleSpawner(u16 amount, float spawntime,
 	v3f minpos, v3f maxpos, v3f minvel, v3f maxvel, v3f minacc, v3f maxacc,
 	float minexptime, float maxexptime, float minsize, float maxsize,
 	bool collisiondetection, bool collision_removal,
-	bool vertical, const std::string &texture,
+	ServerActiveObject *attached, bool vertical, const std::string &texture,
 	const std::string &playername)
 {
 	// m_env will be NULL if the server is initializing
@@ -3171,11 +3172,19 @@ u32 Server::addParticleSpawner(u16 amount, float spawntime,
 		peer_id = player->peer_id;
 	}
 
-	u32 id = m_env->addParticleSpawner(spawntime);
+	u16 attached_id = attached ? attached->getId() : 0;
+
+	u32 id;
+	if (attached_id == 0)
+		id = m_env->addParticleSpawner(spawntime);
+	else
+		id = m_env->addParticleSpawner(spawntime, attached_id);
+
 	SendAddParticleSpawner(peer_id, amount, spawntime,
 		minpos, maxpos, minvel, maxvel, minacc, maxacc,
 		minexptime, maxexptime, minsize, maxsize,
-		collisiondetection, collision_removal, vertical, texture, id);
+		collisiondetection, collision_removal, attached_id, vertical,
+		texture, id);
 
 	return id;
 }
diff --git a/src/server.h b/src/server.h
index 6ee61a0eb..5fc2a9133 100644
--- a/src/server.h
+++ b/src/server.h
@@ -258,6 +258,7 @@ class Server : public con::PeerHandler, public MapEventReceiver,
 		float minexptime, float maxexptime,
 		float minsize, float maxsize,
 		bool collisiondetection, bool collision_removal,
+		ServerActiveObject *attached,
 		bool vertical, const std::string &texture,
 		const std::string &playername);
 
@@ -434,6 +435,7 @@ class Server : public con::PeerHandler, public MapEventReceiver,
 		float minexptime, float maxexptime,
 		float minsize, float maxsize,
 		bool collisiondetection, bool collision_removal,
+		u16 attached_id,
 		bool vertical, const std::string &texture, u32 id);
 
 	void SendDeleteParticleSpawner(u16 peer_id, u32 id);
diff --git a/src/serverobject.cpp b/src/serverobject.cpp
index 236d7e8dc..191247829 100644
--- a/src/serverobject.cpp
+++ b/src/serverobject.cpp
@@ -98,4 +98,3 @@ bool ServerActiveObject::setWieldedItem(const ItemStack &item)
 	}
 	return false;
 }
-
diff --git a/src/serverobject.h b/src/serverobject.h
index cfe2b6bcc..63650e3be 100644
--- a/src/serverobject.h
+++ b/src/serverobject.h
@@ -188,6 +188,15 @@ class ServerActiveObject : public ActiveObject
 	{ return 0; }
 	virtual ItemStack getWieldedItem() const;
 	virtual bool setWieldedItem(const ItemStack &item);
+	inline void attachParticleSpawner(u32 id)
+	{
+		m_attached_particle_spawners.insert(id);
+	}
+	inline void detachParticleSpawner(u32 id)
+	{
+		m_attached_particle_spawners.erase(id);
+	}
+
 
 	/*
 		Number of players which know about this object. Object won't be
@@ -242,6 +251,7 @@ class ServerActiveObject : public ActiveObject
 
 	ServerEnvironment *m_env;
 	v3f m_base_position;
+	UNORDERED_SET<u32> m_attached_particle_spawners;
 
 private:
 	// Used for creating objects based on type
-- 
GitLab