Allow spawning particles from the server, from lua

Spawn single particles or make use of ParticleSpawner for many randomly spawned particles.
Accessible in Lua using minetest.spawn_particle and minetest.add_particlespawner.
Increase Protocol Version to 17.

Conflicts:
	src/clientserver.h
This commit is contained in:
Jeija 2013-01-23 18:32:02 +01:00 committed by PilzAdam
parent ab57fdac44
commit e1ff5b1361
13 changed files with 1060 additions and 56 deletions

View File

@ -1028,6 +1028,37 @@ minetest.get_ban_description(ip_or_name) -> ban description (string)
minetest.ban_player(name) -> ban a player minetest.ban_player(name) -> ban a player
minetest.unban_player_or_ip(name) -> unban player or IP address minetest.unban_player_or_ip(name) -> unban player or IP address
Particles:
minetest.add_particle(pos, velocity, acceleration, expirationtime,
size, collisiondetection, texture, playername)
^ Spawn particle at pos with velocity and acceleration
^ Disappears after expirationtime seconds
^ collisiondetection: if true collides with physical objects
^ Uses texture (string)
^ Playername is optional, if specified spawns particle only on the player's client
minetest.add_particlespawner(amount, time,
minpos, maxpos,
minvel, maxvel,
minacc, maxacc,
minexptime, maxexptime,
minsize, maxsize,
collisiondetection, texture, playername)
^ Add a particlespawner, an object that spawns an amount of particles over time seconds
^ The particle's properties are random values in between the boundings:
^ minpos/maxpos, minvel/maxvel (velocity), minacc/maxacc (acceleration),
^ minsize/maxsize, minexptime/maxexptime (expirationtime)
^ collisiondetection: if true uses collisiondetection
^ Uses texture (string)
^ Playername is optional, if specified spawns particle only on the player's client
^ If time is 0 has infinite lifespan and spawns the amount on a per-second base
^ Returns and id
minetest.delete_particlespawner(id, player)
^ Delete ParticleSpawner with id (return value from add_particlespawner)
^ If playername is specified, only deletes on the player's client,
^ otherwise on all clients
Random: Random:
minetest.get_connected_players() -> list of ObjectRefs minetest.get_connected_players() -> list of ObjectRefs
minetest.hash_node_position({x=,y=,z=}) -> 48-bit integer minetest.hash_node_position({x=,y=,z=}) -> 48-bit integer

View File

@ -219,6 +219,7 @@ set(common_SRCS
scriptapi_object.cpp scriptapi_object.cpp
scriptapi_nodemeta.cpp scriptapi_nodemeta.cpp
scriptapi_inventory.cpp scriptapi_inventory.cpp
scriptapi_particles.cpp
scriptapi.cpp scriptapi.cpp
script.cpp script.cpp
log.cpp log.cpp

View File

@ -1936,6 +1936,89 @@ void Client::ProcessData(u8 *data, u32 datasize, u16 sender_peer_id)
event.show_formspec.formname = new std::string(formname); event.show_formspec.formname = new std::string(formname);
m_client_event_queue.push_back(event); m_client_event_queue.push_back(event);
} }
else if(command == TOCLIENT_SPAWN_PARTICLE)
{
std::string datastring((char*)&data[2], datasize-2);
std::istringstream is(datastring, std::ios_base::binary);
v3f pos = readV3F1000(is);
v3f vel = readV3F1000(is);
v3f acc = readV3F1000(is);
float expirationtime = readF1000(is);
float size = readF1000(is);
bool collisiondetection = readU8(is);
std::string texture = deSerializeLongString(is);
ClientEvent event;
event.type = CE_SPAWN_PARTICLE;
event.spawn_particle.pos = new v3f (pos);
event.spawn_particle.vel = new v3f (vel);
event.spawn_particle.acc = new v3f (acc);
event.spawn_particle.expirationtime = expirationtime;
event.spawn_particle.size = size;
event.add_particlespawner.collisiondetection =
collisiondetection;
event.spawn_particle.texture = new std::string(texture);
m_client_event_queue.push_back(event);
}
else if(command == TOCLIENT_ADD_PARTICLESPAWNER)
{
std::string datastring((char*)&data[2], datasize-2);
std::istringstream is(datastring, std::ios_base::binary);
u16 amount = readU16(is);
float spawntime = readF1000(is);
v3f minpos = readV3F1000(is);
v3f maxpos = readV3F1000(is);
v3f minvel = readV3F1000(is);
v3f maxvel = readV3F1000(is);
v3f minacc = readV3F1000(is);
v3f maxacc = readV3F1000(is);
float minexptime = readF1000(is);
float maxexptime = readF1000(is);
float minsize = readF1000(is);
float maxsize = readF1000(is);
bool collisiondetection = readU8(is);
std::string texture = deSerializeLongString(is);
u32 id = readU32(is);
ClientEvent event;
event.type = CE_ADD_PARTICLESPAWNER;
event.add_particlespawner.amount = amount;
event.add_particlespawner.spawntime = spawntime;
event.add_particlespawner.minpos = new v3f (minpos);
event.add_particlespawner.maxpos = new v3f (maxpos);
event.add_particlespawner.minvel = new v3f (minvel);
event.add_particlespawner.maxvel = new v3f (maxvel);
event.add_particlespawner.minacc = new v3f (minacc);
event.add_particlespawner.maxacc = new v3f (maxacc);
event.add_particlespawner.minexptime = minexptime;
event.add_particlespawner.maxexptime = maxexptime;
event.add_particlespawner.minsize = minsize;
event.add_particlespawner.maxsize = maxsize;
event.add_particlespawner.collisiondetection = collisiondetection;
event.add_particlespawner.texture = new std::string(texture);
event.add_particlespawner.id = id;
m_client_event_queue.push_back(event);
}
else if(command == TOCLIENT_DELETE_PARTICLESPAWNER)
{
std::string datastring((char*)&data[2], datasize-2);
std::istringstream is(datastring, std::ios_base::binary);
u32 id = readU16(is);
ClientEvent event;
event.type = CE_DELETE_PARTICLESPAWNER;
event.delete_particlespawner.id = id;
m_client_event_queue.push_back(event);
}
else else
{ {
infostream<<"Client: Ignoring unknown command " infostream<<"Client: Ignoring unknown command "

View File

@ -157,7 +157,10 @@ enum ClientEventType
CE_PLAYER_FORCE_MOVE, CE_PLAYER_FORCE_MOVE,
CE_DEATHSCREEN, CE_DEATHSCREEN,
CE_TEXTURES_UPDATED, CE_TEXTURES_UPDATED,
CE_SHOW_FORMSPEC CE_SHOW_FORMSPEC,
CE_SPAWN_PARTICLE,
CE_ADD_PARTICLESPAWNER,
CE_DELETE_PARTICLESPAWNER
}; };
struct ClientEvent struct ClientEvent
@ -185,6 +188,35 @@ struct ClientEvent
} show_formspec; } show_formspec;
struct{ struct{
} textures_updated; } textures_updated;
struct{
v3f *pos;
v3f *vel;
v3f *acc;
f32 expirationtime;
f32 size;
bool collisiondetection;
std::string *texture;
} spawn_particle;
struct{
u16 amount;
f32 spawntime;
v3f *minpos;
v3f *maxpos;
v3f *minvel;
v3f *maxvel;
v3f *minacc;
v3f *maxacc;
f32 minexptime;
f32 maxexptime;
f32 minsize;
f32 maxsize;
bool collisiondetection;
std::string *texture;
u32 id;
} add_particlespawner;
struct{
u32 id;
} delete_particlespawner;
}; };
}; };

View File

@ -82,6 +82,9 @@ SharedBuffer<u8> makePacket_TOCLIENT_TIME_OF_DAY(u16 time, float time_speed);
PROTOCOL_VERSION 17: PROTOCOL_VERSION 17:
Serialization format change: include backface_culling flag in TileDef Serialization format change: include backface_culling flag in TileDef
Added rightclickable field in nodedef Added rightclickable field in nodedef
TOCLIENT_SPAWN_PARTICLE
TOCLIENT_ADD_PARTICLESPAWNER
TOCLIENT_DELETE_PARTICLESPAWNER
*/ */
#define LATEST_PROTOCOL_VERSION 17 #define LATEST_PROTOCOL_VERSION 17
@ -359,6 +362,7 @@ enum ToClientCommand
u8[len] name u8[len] name
[2] serialized inventory [2] serialized inventory
*/ */
TOCLIENT_SHOW_FORMSPEC = 0x44, TOCLIENT_SHOW_FORMSPEC = 0x44,
/* /*
[0] u16 command [0] u16 command
@ -384,6 +388,46 @@ enum ToClientCommand
f1000 movement_liquid_sink f1000 movement_liquid_sink
f1000 movement_gravity f1000 movement_gravity
*/ */
TOCLIENT_SPAWN_PARTICLE = 0x46,
/*
u16 command
v3f1000 pos
v3f1000 velocity
v3f1000 acceleration
f1000 expirationtime
f1000 size
u8 bool collisiondetection
u32 len
u8[len] texture
*/
TOCLIENT_ADD_PARTICLESPAWNER = 0x47,
/*
u16 command
u16 amount
f1000 spawntime
v3f1000 minpos
v3f1000 maxpos
v3f1000 minvel
v3f1000 maxvel
v3f1000 minacc
v3f1000 maxacc
f1000 minexptime
f1000 maxexptime
f1000 minsize
f1000 maxsize
u8 bool collisiondetection
u32 len
u8[len] texture
u32 id
*/
TOCLIENT_DELETE_PARTICLESPAWNER = 0x48,
/*
u16 command
u32 id
*/
}; };
enum ToServerCommand enum ToServerCommand

View File

@ -2186,6 +2186,47 @@ void the_game(
{ {
update_wielded_item_trigger = true; update_wielded_item_trigger = true;
} }
else if(event.type == CE_SPAWN_PARTICLE)
{
LocalPlayer* player = client.getEnv().getLocalPlayer();
AtlasPointer ap =
gamedef->tsrc()->getTexture(*(event.spawn_particle.texture));
new Particle(gamedef, smgr, player, client.getEnv(),
*event.spawn_particle.pos,
*event.spawn_particle.vel,
*event.spawn_particle.acc,
event.spawn_particle.expirationtime,
event.spawn_particle.size,
event.spawn_particle.collisiondetection, ap);
}
else if(event.type == CE_ADD_PARTICLESPAWNER)
{
LocalPlayer* player = client.getEnv().getLocalPlayer();
AtlasPointer ap =
gamedef->tsrc()->getTexture(*(event.add_particlespawner.texture));
new ParticleSpawner(gamedef, smgr, player,
event.add_particlespawner.amount,
event.add_particlespawner.spawntime,
*event.add_particlespawner.minpos,
*event.add_particlespawner.maxpos,
*event.add_particlespawner.minvel,
*event.add_particlespawner.maxvel,
*event.add_particlespawner.minacc,
*event.add_particlespawner.maxacc,
event.add_particlespawner.minexptime,
event.add_particlespawner.maxexptime,
event.add_particlespawner.minsize,
event.add_particlespawner.maxsize,
event.add_particlespawner.collisiondetection,
ap,
event.add_particlespawner.id);
}
else if(event.type == CE_DELETE_PARTICLESPAWNER)
{
delete_particlespawner (event.delete_particlespawner.id);
}
} }
} }
@ -2415,7 +2456,8 @@ void the_game(
const ContentFeatures &features = const ContentFeatures &features =
client.getNodeDefManager()->get(n); client.getNodeDefManager()->get(n);
addPunchingParticles addPunchingParticles
(gamedef, smgr, player, nodepos, features.tiles); (gamedef, smgr, player, client.getEnv(),
nodepos, features.tiles);
} }
} }
@ -2453,7 +2495,8 @@ void the_game(
const ContentFeatures &features = const ContentFeatures &features =
client.getNodeDefManager()->get(wasnode); client.getNodeDefManager()->get(wasnode);
addDiggingParticles addDiggingParticles
(gamedef, smgr, player, nodepos, features.tiles); (gamedef, smgr, player, client.getEnv(),
nodepos, features.tiles);
} }
dig_time = 0; dig_time = 0;
@ -2734,6 +2777,7 @@ void the_game(
*/ */
allparticles_step(dtime, client.getEnv()); allparticles_step(dtime, client.getEnv());
allparticlespawners_step(dtime, client.getEnv());
/* /*
Fog Fog
@ -3220,6 +3264,7 @@ void the_game(
clouds->drop(); clouds->drop();
if(gui_chat_console) if(gui_chat_console)
gui_chat_console->drop(); gui_chat_console->drop();
clear_particles ();
/* /*
Draw a "shutting down" screen, which will be shown while the map Draw a "shutting down" screen, which will be shown while the map

View File

@ -32,19 +32,34 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include "clientmap.h" #include "clientmap.h"
#include "mapnode.h" #include "mapnode.h"
/*
Utility
*/
v3f random_v3f(v3f min, v3f max)
{
return v3f( rand()/(float)RAND_MAX*(max.X-min.X)+min.X,
rand()/(float)RAND_MAX*(max.Y-min.Y)+min.Y,
rand()/(float)RAND_MAX*(max.Z-min.Z)+min.Z);
}
std::vector<Particle*> all_particles;
std::map<u32, ParticleSpawner*> all_particlespawners;
Particle::Particle( Particle::Particle(
IGameDef *gamedef, IGameDef *gamedef,
scene::ISceneManager* smgr, scene::ISceneManager* smgr,
LocalPlayer *player, LocalPlayer *player,
s32 id, ClientEnvironment &env,
v3f pos, v3f pos,
v3f velocity, v3f velocity,
v3f acceleration, v3f acceleration,
float expirationtime, float expirationtime,
float size, float size,
bool collisiondetection,
AtlasPointer ap AtlasPointer ap
): ):
scene::ISceneNode(smgr->getRootSceneNode(), smgr, id) scene::ISceneNode(smgr->getRootSceneNode(), smgr)
{ {
// Misc // Misc
m_gamedef = gamedef; m_gamedef = gamedef;
@ -57,7 +72,6 @@ Particle::Particle(
m_material.MaterialType = video::EMT_TRANSPARENT_ALPHA_CHANNEL; m_material.MaterialType = video::EMT_TRANSPARENT_ALPHA_CHANNEL;
m_material.setTexture(0, ap.atlas); m_material.setTexture(0, ap.atlas);
m_ap = ap; m_ap = ap;
m_light = 0;
// Particle related // Particle related
@ -68,10 +82,20 @@ Particle::Particle(
m_time = 0; m_time = 0;
m_player = player; m_player = player;
m_size = size; m_size = size;
m_collisiondetection = collisiondetection;
// Irrlicht stuff (TODO) // Irrlicht stuff
m_collisionbox = core::aabbox3d<f32>(-size/2,-size/2,-size/2,size/2,size/2,size/2); m_collisionbox = core::aabbox3d<f32>
(-size/2,-size/2,-size/2,size/2,size/2,size/2);
this->setAutomaticCulling(scene::EAC_OFF); this->setAutomaticCulling(scene::EAC_OFF);
// Init lighting
updateLight(env);
// Init model
updateVertices();
all_particles.push_back(this);
} }
Particle::~Particle() Particle::~Particle()
@ -82,8 +106,10 @@ void Particle::OnRegisterSceneNode()
{ {
if (IsVisible) if (IsVisible)
{ {
SceneManager->registerNodeForRendering(this, scene::ESNRP_TRANSPARENT); SceneManager->registerNodeForRendering
SceneManager->registerNodeForRendering(this, scene::ESNRP_SOLID); (this, scene::ESNRP_TRANSPARENT);
SceneManager->registerNodeForRendering
(this, scene::ESNRP_SOLID);
} }
ISceneNode::OnRegisterSceneNode(); ISceneNode::OnRegisterSceneNode();
@ -96,31 +122,18 @@ void Particle::render()
video::IVideoDriver* driver = SceneManager->getVideoDriver(); video::IVideoDriver* driver = SceneManager->getVideoDriver();
driver->setMaterial(m_material); driver->setMaterial(m_material);
driver->setTransform(video::ETS_WORLD, AbsoluteTransformation); driver->setTransform(video::ETS_WORLD, AbsoluteTransformation);
video::SColor c(255, m_light, m_light, m_light);
video::S3DVertex vertices[4] =
{
video::S3DVertex(-m_size/2,-m_size/2,0, 0,0,0, c, m_ap.x0(), m_ap.y1()),
video::S3DVertex(m_size/2,-m_size/2,0, 0,0,0, c, m_ap.x1(), m_ap.y1()),
video::S3DVertex(m_size/2,m_size/2,0, 0,0,0, c, m_ap.x1(), m_ap.y0()),
video::S3DVertex(-m_size/2,m_size/2,0, 0,0,0, c ,m_ap.x0(), m_ap.y0()),
};
for(u16 i=0; i<4; i++)
{
vertices[i].Pos.rotateYZBy(m_player->getPitch());
vertices[i].Pos.rotateXZBy(m_player->getYaw());
m_box.addInternalPoint(vertices[i].Pos);
vertices[i].Pos += m_pos*BS;
}
u16 indices[] = {0,1,2, 2,3,0}; u16 indices[] = {0,1,2, 2,3,0};
driver->drawVertexPrimitiveList(vertices, 4, indices, 2, driver->drawVertexPrimitiveList(m_vertices, 4,
video::EVT_STANDARD, scene::EPT_TRIANGLES, video::EIT_16BIT); indices, 2, video::EVT_STANDARD,
scene::EPT_TRIANGLES, video::EIT_16BIT);
} }
void Particle::step(float dtime, ClientEnvironment &env) void Particle::step(float dtime, ClientEnvironment &env)
{ {
m_time += dtime;
if (m_collisiondetection)
{
core::aabbox3d<f32> box = m_collisionbox; core::aabbox3d<f32> box = m_collisionbox;
v3f p_pos = m_pos*BS; v3f p_pos = m_pos*BS;
v3f p_velocity = m_velocity*BS; v3f p_velocity = m_velocity*BS;
@ -132,9 +145,22 @@ void Particle::step(float dtime, ClientEnvironment &env)
m_pos = p_pos/BS; m_pos = p_pos/BS;
m_velocity = p_velocity/BS; m_velocity = p_velocity/BS;
m_acceleration = p_acceleration/BS; m_acceleration = p_acceleration/BS;
m_time += dtime; }
else
{
m_velocity += m_acceleration * dtime;
m_pos += m_velocity * dtime;
}
// Update lighting // Update lighting
updateLight(env);
// Update model
updateVertices();
}
void Particle::updateLight(ClientEnvironment &env)
{
u8 light = 0; u8 light = 0;
try{ try{
v3s16 p = v3s16( v3s16 p = v3s16(
@ -151,11 +177,37 @@ void Particle::step(float dtime, ClientEnvironment &env)
m_light = decode_light(light); m_light = decode_light(light);
} }
std::vector<Particle*> all_particles; void Particle::updateVertices()
{
video::SColor c(255, m_light, m_light, m_light);
m_vertices[0] = video::S3DVertex(-m_size/2,-m_size/2,0, 0,0,0,
c, m_ap.x0(), m_ap.y1());
m_vertices[1] = video::S3DVertex(m_size/2,-m_size/2,0, 0,0,0,
c, m_ap.x1(), m_ap.y1());
m_vertices[2] = video::S3DVertex(m_size/2,m_size/2,0, 0,0,0,
c, m_ap.x1(), m_ap.y0());
m_vertices[3] = video::S3DVertex(-m_size/2,m_size/2,0, 0,0,0,
c ,m_ap.x0(), m_ap.y0());
for(u16 i=0; i<4; i++)
{
m_vertices[i].Pos.rotateYZBy(m_player->getPitch());
m_vertices[i].Pos.rotateXZBy(m_player->getYaw());
m_box.addInternalPoint(m_vertices[i].Pos);
m_vertices[i].Pos += m_pos*BS;
}
}
/*
Helpers
*/
void allparticles_step (float dtime, ClientEnvironment &env) void allparticles_step (float dtime, ClientEnvironment &env)
{ {
for(std::vector<Particle*>::iterator i = all_particles.begin(); i != all_particles.end();) for(std::vector<Particle*>::iterator i = all_particles.begin();
i != all_particles.end();)
{ {
if ((*i)->get_expired()) if ((*i)->get_expired())
{ {
@ -171,22 +223,28 @@ void allparticles_step (float dtime, ClientEnvironment &env)
} }
} }
void addDiggingParticles(IGameDef* gamedef, scene::ISceneManager* smgr, LocalPlayer *player, v3s16 pos, const TileSpec tiles[]) void addDiggingParticles(IGameDef* gamedef, scene::ISceneManager* smgr,
LocalPlayer *player, ClientEnvironment &env, v3s16 pos,
const TileSpec tiles[])
{ {
for (u16 j = 0; j < 32; j++) // set the amount of particles here for (u16 j = 0; j < 32; j++) // set the amount of particles here
{ {
addNodeParticle(gamedef, smgr, player, pos, tiles); addNodeParticle(gamedef, smgr, player, env, pos, tiles);
} }
} }
void addPunchingParticles(IGameDef* gamedef, scene::ISceneManager* smgr, LocalPlayer *player, v3s16 pos, const TileSpec tiles[]) void addPunchingParticles(IGameDef* gamedef, scene::ISceneManager* smgr,
LocalPlayer *player, ClientEnvironment &env,
v3s16 pos, const TileSpec tiles[])
{ {
addNodeParticle(gamedef, smgr, player, pos, tiles); addNodeParticle(gamedef, smgr, player, env, pos, tiles);
} }
// add a particle of a node // add a particle of a node
// used by digging and punching particles // used by digging and punching particles
void addNodeParticle(IGameDef* gamedef, scene::ISceneManager* smgr, LocalPlayer *player, v3s16 pos, const TileSpec tiles[]) void addNodeParticle(IGameDef* gamedef, scene::ISceneManager* smgr,
LocalPlayer *player, ClientEnvironment &env, v3s16 pos,
const TileSpec tiles[])
{ {
// Texture // Texture
u8 texid = myrand_range(0,5); u8 texid = myrand_range(0,5);
@ -205,7 +263,10 @@ void addNodeParticle(IGameDef* gamedef, scene::ISceneManager* smgr, LocalPlayer
ap.pos.Y = ap.y0() + (y1 - ap.y0()) * ((rand()%64)/64.-texsize); ap.pos.Y = ap.y0() + (y1 - ap.y0()) * ((rand()%64)/64.-texsize);
// Physics // Physics
v3f velocity((rand()%100/50.-1)/1.5, rand()%100/35., (rand()%100/50.-1)/1.5); v3f velocity( (rand()%100/50.-1)/1.5,
rand()%100/35.,
(rand()%100/50.-1)/1.5);
v3f acceleration(0,-9,0); v3f acceleration(0,-9,0);
v3f particlepos = v3f( v3f particlepos = v3f(
(f32)pos.X+rand()%100/200.-0.25, (f32)pos.X+rand()%100/200.-0.25,
@ -213,17 +274,180 @@ void addNodeParticle(IGameDef* gamedef, scene::ISceneManager* smgr, LocalPlayer
(f32)pos.Z+rand()%100/200.-0.25 (f32)pos.Z+rand()%100/200.-0.25
); );
Particle *particle = new Particle( new Particle(
gamedef, gamedef,
smgr, smgr,
player, player,
0, env,
particlepos, particlepos,
velocity, velocity,
acceleration, acceleration,
rand()%100/100., // expiration time rand()%100/100., // expiration time
visual_size, visual_size,
true,
ap); ap);
}
all_particles.push_back(particle);
/*
ParticleSpawner
*/
ParticleSpawner::ParticleSpawner(IGameDef* gamedef, scene::ISceneManager *smgr, LocalPlayer *player,
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, AtlasPointer ap, u32 id)
{
m_gamedef = gamedef;
m_smgr = smgr;
m_player = player;
m_amount = amount;
m_spawntime = time;
m_minpos = minpos;
m_maxpos = maxpos;
m_minvel = minvel;
m_maxvel = maxvel;
m_minacc = minacc;
m_maxacc = maxacc;
m_minexptime = minexptime;
m_maxexptime = maxexptime;
m_minsize = minsize;
m_maxsize = maxsize;
m_collisiondetection = collisiondetection;
m_ap = ap;
m_time = 0;
for (u16 i = 0; i<=m_amount; i++)
{
float spawntime = (float)rand()/(float)RAND_MAX*m_spawntime;
m_spawntimes.push_back(spawntime);
}
all_particlespawners.insert(std::pair<u32, ParticleSpawner*>(id, this));
}
ParticleSpawner::~ParticleSpawner() {}
void ParticleSpawner::step(float dtime, ClientEnvironment &env)
{
m_time += dtime;
if (m_spawntime != 0) // Spawner exists for a predefined timespan
{
for(std::vector<float>::iterator i = m_spawntimes.begin();
i != m_spawntimes.end();)
{
if ((*i) <= m_time && m_amount > 0)
{
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;
new Particle(
m_gamedef,
m_smgr,
m_player,
env,
pos,
vel,
acc,
exptime,
size,
m_collisiondetection,
m_ap);
m_spawntimes.erase(i);
}
else
{
i++;
}
}
}
else // Spawner exists for an infinity timespan, spawn on a per-second base
{
for (int i = 0; i <= m_amount; i++)
{
if (rand()/(float)RAND_MAX < dtime)
{
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;
new Particle(
m_gamedef,
m_smgr,
m_player,
env,
pos,
vel,
acc,
exptime,
size,
m_collisiondetection,
m_ap);
}
}
}
}
void allparticlespawners_step (float dtime, ClientEnvironment &env)
{
for(std::map<u32, ParticleSpawner*>::iterator i =
all_particlespawners.begin();
i != all_particlespawners.end();)
{
if (i->second->get_expired())
{
delete i->second;
all_particlespawners.erase(i++);
}
else
{
i->second->step(dtime, env);
i++;
}
}
}
void delete_particlespawner (u32 id)
{
if (all_particlespawners.find(id) != all_particlespawners.end())
{
delete all_particlespawners.find(id)->second;
all_particlespawners.erase(id);
}
}
void clear_particles ()
{
for(std::map<u32, ParticleSpawner*>::iterator i =
all_particlespawners.begin();
i != all_particlespawners.end();)
{
delete i->second;
all_particlespawners.erase(i++);
}
for(std::vector<Particle*>::iterator i =
all_particles.begin();
i != all_particles.end();)
{
(*i)->remove();
delete *i;
all_particles.erase(i);
}
} }

View File

@ -35,12 +35,13 @@ class Particle : public scene::ISceneNode
IGameDef* gamedef, IGameDef* gamedef,
scene::ISceneManager* mgr, scene::ISceneManager* mgr,
LocalPlayer *player, LocalPlayer *player,
s32 id, ClientEnvironment &env,
v3f pos, v3f pos,
v3f velocity, v3f velocity,
v3f acceleration, v3f acceleration,
float expirationtime, float expirationtime,
float size, float size,
bool collisiondetection,
AtlasPointer texture AtlasPointer texture
); );
~Particle(); ~Particle();
@ -69,6 +70,10 @@ class Particle : public scene::ISceneNode
{ return m_expiration < m_time; } { return m_expiration < m_time; }
private: private:
void updateLight(ClientEnvironment &env);
void updateVertices();
video::S3DVertex m_vertices[4];
float m_time; float m_time;
float m_expiration; float m_expiration;
@ -87,12 +92,71 @@ private:
float m_size; float m_size;
AtlasPointer m_ap; AtlasPointer m_ap;
u8 m_light; u8 m_light;
bool m_collisiondetection;
};
class ParticleSpawner
{
public:
ParticleSpawner(IGameDef* gamedef,
scene::ISceneManager *smgr,
LocalPlayer *player,
u16 amount,
float time,
v3f minp, v3f maxp,
v3f minvel, v3f maxvel,
v3f minacc, v3f maxacc,
float minexptime, float maxexptime,
float minsize, float maxsize,
bool collisiondetection,
AtlasPointer ap,
u32 id);
~ParticleSpawner();
void step(float dtime, ClientEnvironment &env);
bool get_expired ()
{ return (m_amount <= 0) && m_spawntime != 0; }
private:
float m_time;
IGameDef *m_gamedef;
scene::ISceneManager *m_smgr;
LocalPlayer *m_player;
u16 m_amount;
float m_spawntime;
v3f m_minpos;
v3f m_maxpos;
v3f m_minvel;
v3f m_maxvel;
v3f m_minacc;
v3f m_maxacc;
float m_minexptime;
float m_maxexptime;
float m_minsize;
float m_maxsize;
AtlasPointer m_ap;
std::vector<float> m_spawntimes;
bool m_collisiondetection;
}; };
void allparticles_step (float dtime, ClientEnvironment &env); void allparticles_step (float dtime, ClientEnvironment &env);
void allparticlespawners_step (float dtime, ClientEnvironment &env);
void addDiggingParticles(IGameDef* gamedef, scene::ISceneManager* smgr, LocalPlayer *player, v3s16 pos, const TileSpec tiles[]); void delete_particlespawner (u32 id);
void addPunchingParticles(IGameDef* gamedef, scene::ISceneManager* smgr, LocalPlayer *player, v3s16 pos, const TileSpec tiles[]); void clear_particles ();
void addNodeParticle(IGameDef* gamedef, scene::ISceneManager* smgr, LocalPlayer *player, v3s16 pos, const TileSpec tiles[]);
void addDiggingParticles(IGameDef* gamedef, scene::ISceneManager* smgr,
LocalPlayer *player, ClientEnvironment &env, v3s16 pos,
const TileSpec tiles[]);
void addPunchingParticles(IGameDef* gamedef, scene::ISceneManager* smgr,
LocalPlayer *player, ClientEnvironment &env, v3s16 pos,
const TileSpec tiles[]);
void addNodeParticle(IGameDef* gamedef, scene::ISceneManager* smgr,
LocalPlayer *player, ClientEnvironment &env, v3s16 pos,
const TileSpec tiles[]);
#endif #endif

View File

@ -44,6 +44,7 @@ extern "C" {
#include "scriptapi_item.h" #include "scriptapi_item.h"
#include "scriptapi_content.h" #include "scriptapi_content.h"
#include "scriptapi_craft.h" #include "scriptapi_craft.h"
#include "scriptapi_particles.h"
/*****************************************************************************/ /*****************************************************************************/
/* Mod related */ /* Mod related */
@ -1089,6 +1090,9 @@ static const struct luaL_Reg minetest_f [] = {
{"get_all_craft_recipes", l_get_all_craft_recipes}, {"get_all_craft_recipes", l_get_all_craft_recipes},
{"rollback_get_last_node_actor", l_rollback_get_last_node_actor}, {"rollback_get_last_node_actor", l_rollback_get_last_node_actor},
{"rollback_revert_actions_by", l_rollback_revert_actions_by}, {"rollback_revert_actions_by", l_rollback_revert_actions_by},
{"add_particle", l_add_particle},
{"add_particlespawner", l_add_particlespawner},
{"delete_particlespawner", l_delete_particlespawner},
{NULL, NULL} {NULL, NULL}
}; };

143
src/scriptapi_particles.cpp Normal file
View File

@ -0,0 +1,143 @@
/*
Minetest
Copyright (C) 2013 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 Lesser General Public License as published by
the Free Software Foundation; either version 2.1 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 Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include "scriptapi.h"
#include "scriptapi_particles.h"
#include "server.h"
#include "script.h"
#include "scriptapi_types.h"
#include "scriptapi_common.h"
// add_particle(pos, velocity, acceleration, expirationtime,
// size, collisiondetection, texture, player)
// pos/velocity/acceleration = {x=num, y=num, z=num}
// expirationtime = num (seconds)
// size = num
// texture = e.g."default_wood.png"
int l_add_particle(lua_State *L)
{
// Get server from registry
Server *server = get_server(L);
// Get parameters
v3f pos = check_v3f(L, 1);
v3f vel = check_v3f(L, 2);
v3f acc = check_v3f(L, 3);
float expirationtime = luaL_checknumber(L, 4);
float size = luaL_checknumber(L, 5);
bool collisiondetection = lua_toboolean(L, 6);
std::string texture = luaL_checkstring(L, 7);
if (lua_gettop(L) == 8) // only spawn for a single player
{
const char *playername = luaL_checkstring(L, 8);
server->spawnParticle(playername,
pos, vel, acc, expirationtime,
size, collisiondetection, texture);
}
else // spawn for all players
{
server->spawnParticleAll(pos, vel, acc,
expirationtime, size, collisiondetection, texture);
}
return 1;
}
// add_particlespawner(amount, time,
// minpos, maxpos,
// minvel, maxvel,
// minacc, maxacc,
// minexptime, maxexptime,
// minsize, maxsize,
// collisiondetection,
// texture,
// player)
// minpos/maxpos/minvel/maxvel/minacc/maxacc = {x=num, y=num, z=num}
// minexptime/maxexptime = num (seconds)
// minsize/maxsize = num
// collisiondetection = bool
// texture = e.g."default_wood.png"
int l_add_particlespawner(lua_State *L)
{
// Get server from registry
Server *server = get_server(L);
// Get parameters
u16 amount = luaL_checknumber(L, 1);
float time = luaL_checknumber(L, 2);
v3f minpos = check_v3f(L, 3);
v3f maxpos = check_v3f(L, 4);
v3f minvel = check_v3f(L, 5);
v3f maxvel = check_v3f(L, 6);
v3f minacc = check_v3f(L, 7);
v3f maxacc = check_v3f(L, 8);
float minexptime = luaL_checknumber(L, 9);
float maxexptime = luaL_checknumber(L, 10);
float minsize = luaL_checknumber(L, 11);
float maxsize = luaL_checknumber(L, 12);
bool collisiondetection = lua_toboolean(L, 13);
std::string texture = luaL_checkstring(L, 14);
if (lua_gettop(L) == 15) // only spawn for a single player
{
const char *playername = luaL_checkstring(L, 15);
u32 id = server->addParticleSpawner(playername,
amount, time,
minpos, maxpos,
minvel, maxvel,
minacc, maxacc,
minexptime, maxexptime,
minsize, maxsize,
collisiondetection,
texture);
lua_pushnumber(L, id);
}
else // spawn for all players
{
u32 id = server->addParticleSpawnerAll( amount, time,
minpos, maxpos,
minvel, maxvel,
minacc, maxacc,
minexptime, maxexptime,
minsize, maxsize,
collisiondetection,
texture);
lua_pushnumber(L, id);
}
return 1;
}
// delete_particlespawner(id, player)
// player (string) is optional
int l_delete_particlespawner(lua_State *L)
{
// Get server from registry
Server *server = get_server(L);
// Get parameters
u32 id = luaL_checknumber(L, 1);
if (lua_gettop(L) == 2) // only delete for one player
{
const char *playername = luaL_checkstring(L, 2);
server->deleteParticleSpawner(playername, id);
}
else // delete for all players
{
server->deleteParticleSpawnerAll(id);
}
return 1;
}

32
src/scriptapi_particles.h Normal file
View File

@ -0,0 +1,32 @@
/*
Minetest-c55
Copyright (C) 2013 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 Lesser General Public License as published by
the Free Software Foundation; either version 2.1 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 Lesser General Public License for more details.
You should have received a copy of the GNU Lesser 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 LUA_PARTICLES_H_
#define LUA_PARTICLES_H_
extern "C" {
#include <lua.h>
#include <lauxlib.h>
}
int l_add_particle(lua_State *L);
int l_add_particlespawner(lua_State *L);
int l_delete_particlespawner(lua_State *L);
#endif

View File

@ -20,6 +20,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include "server.h" #include "server.h"
#include <iostream> #include <iostream>
#include <queue> #include <queue>
#include <algorithm>
#include "clientserver.h" #include "clientserver.h"
#include "map.h" #include "map.h"
#include "jmutexautolock.h" #include "jmutexautolock.h"
@ -3474,6 +3475,132 @@ void Server::SendShowFormspecMessage(u16 peer_id, const std::string formspec, co
m_con.Send(peer_id, 0, data, true); m_con.Send(peer_id, 0, data, true);
} }
// Spawns a particle on peer with peer_id
void Server::SendSpawnParticle(u16 peer_id, v3f pos, v3f velocity, v3f acceleration, float expirationtime, float size, bool collisiondetection, std::string texture)
{
DSTACK(__FUNCTION_NAME);
std::ostringstream os(std::ios_base::binary);
writeU16(os, TOCLIENT_SPAWN_PARTICLE);
writeV3F1000(os, pos);
writeV3F1000(os, velocity);
writeV3F1000(os, acceleration);
writeF1000(os, expirationtime);
writeF1000(os, size);
writeU8(os, collisiondetection);
os<<serializeLongString(texture);
// Make data buffer
std::string s = os.str();
SharedBuffer<u8> data((u8*)s.c_str(), s.size());
// Send as reliable
m_con.Send(peer_id, 0, data, true);
}
// Spawns a particle on all peers
void Server::SendSpawnParticleAll(v3f pos, v3f velocity, v3f acceleration, float expirationtime, float size, bool collisiondetection, std::string texture)
{
for(std::map<u16, RemoteClient*>::iterator
i = m_clients.begin();
i != m_clients.end(); i++)
{
// Get client and check that it is valid
RemoteClient *client = i->second;
assert(client->peer_id == i->first);
if(client->serialization_version == SER_FMT_VER_INVALID)
continue;
SendSpawnParticle(client->peer_id, pos, velocity, acceleration,
expirationtime, size, collisiondetection, texture);
}
}
// Adds a ParticleSpawner on peer with peer_id
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, std::string texture, u32 id)
{
DSTACK(__FUNCTION_NAME);
std::ostringstream os(std::ios_base::binary);
writeU16(os, TOCLIENT_ADD_PARTICLESPAWNER);
writeU16(os, amount);
writeF1000(os, spawntime);
writeV3F1000(os, minpos);
writeV3F1000(os, maxpos);
writeV3F1000(os, minvel);
writeV3F1000(os, maxvel);
writeV3F1000(os, minacc);
writeV3F1000(os, maxacc);
writeF1000(os, minexptime);
writeF1000(os, maxexptime);
writeF1000(os, minsize);
writeF1000(os, maxsize);
writeU8(os, collisiondetection);
os<<serializeLongString(texture);
writeU32(os, id);
// Make data buffer
std::string s = os.str();
SharedBuffer<u8> data((u8*)s.c_str(), s.size());
// Send as reliable
m_con.Send(peer_id, 0, data, true);
}
// Adds a ParticleSpawner on all peers
void Server::SendAddParticleSpawnerAll(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, std::string texture, u32 id)
{
for(std::map<u16, RemoteClient*>::iterator
i = m_clients.begin();
i != m_clients.end(); i++)
{
// Get client and check that it is valid
RemoteClient *client = i->second;
assert(client->peer_id == i->first);
if(client->serialization_version == SER_FMT_VER_INVALID)
continue;
SendAddParticleSpawner(client->peer_id, amount, spawntime,
minpos, maxpos, minvel, maxvel, minacc, maxacc,
minexptime, maxexptime, minsize, maxsize, collisiondetection, texture, id);
}
}
void Server::SendDeleteParticleSpawner(u16 peer_id, u32 id)
{
DSTACK(__FUNCTION_NAME);
std::ostringstream os(std::ios_base::binary);
writeU16(os, TOCLIENT_DELETE_PARTICLESPAWNER);
writeU16(os, id);
// Make data buffer
std::string s = os.str();
SharedBuffer<u8> data((u8*)s.c_str(), s.size());
// Send as reliable
m_con.Send(peer_id, 0, data, true);
}
void Server::SendDeleteParticleSpawnerAll(u32 id)
{
for(std::map<u16, RemoteClient*>::iterator
i = m_clients.begin();
i != m_clients.end(); i++)
{
// Get client and check that it is valid
RemoteClient *client = i->second;
assert(client->peer_id == i->first);
if(client->serialization_version == SER_FMT_VER_INVALID)
continue;
SendDeleteParticleSpawner(client->peer_id, id);
}
}
void Server::BroadcastChatMessage(const std::wstring &message) void Server::BroadcastChatMessage(const std::wstring &message)
{ {
for(std::map<u16, RemoteClient*>::iterator for(std::map<u16, RemoteClient*>::iterator
@ -4432,6 +4559,111 @@ void Server::notifyPlayers(const std::wstring msg)
BroadcastChatMessage(msg); BroadcastChatMessage(msg);
} }
void Server::spawnParticle(const char *playername, v3f pos,
v3f velocity, v3f acceleration,
float expirationtime, float size, bool
collisiondetection, std::string texture)
{
Player *player = m_env->getPlayer(playername);
if(!player)
return;
SendSpawnParticle(player->peer_id, pos, velocity, acceleration,
expirationtime, size, collisiondetection, texture);
}
void Server::spawnParticleAll(v3f pos, v3f velocity, v3f acceleration,
float expirationtime, float size,
bool collisiondetection, std::string texture)
{
SendSpawnParticleAll(pos, velocity, acceleration,
expirationtime, size, collisiondetection, texture);
}
u32 Server::addParticleSpawner(const char *playername,
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, std::string texture)
{
Player *player = m_env->getPlayer(playername);
if(!player)
return -1;
u32 id = 0;
for(;;) // look for unused particlespawner id
{
id++;
if (std::find(m_particlespawner_ids.begin(),
m_particlespawner_ids.end(), id)
== m_particlespawner_ids.end())
{
m_particlespawner_ids.push_back(id);
break;
}
}
SendAddParticleSpawner(player->peer_id, amount, spawntime,
minpos, maxpos, minvel, maxvel, minacc, maxacc,
minexptime, maxexptime, minsize, maxsize,
collisiondetection, texture, id);
return id;
}
u32 Server::addParticleSpawnerAll(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, std::string texture)
{
u32 id = 0;
for(;;) // look for unused particlespawner id
{
id++;
if (std::find(m_particlespawner_ids.begin(),
m_particlespawner_ids.end(), id)
== m_particlespawner_ids.end())
{
m_particlespawner_ids.push_back(id);
break;
}
}
SendAddParticleSpawnerAll(amount, spawntime,
minpos, maxpos, minvel, maxvel, minacc, maxacc,
minexptime, maxexptime, minsize, maxsize,
collisiondetection, texture, id);
return id;
}
void Server::deleteParticleSpawner(const char *playername, u32 id)
{
Player *player = m_env->getPlayer(playername);
if(!player)
return;
m_particlespawner_ids.erase(
std::remove(m_particlespawner_ids.begin(),
m_particlespawner_ids.end(), id),
m_particlespawner_ids.end());
SendDeleteParticleSpawner(player->peer_id, id);
}
void Server::deleteParticleSpawnerAll(u32 id)
{
m_particlespawner_ids.erase(
std::remove(m_particlespawner_ids.begin(),
m_particlespawner_ids.end(), id),
m_particlespawner_ids.end());
SendDeleteParticleSpawnerAll(id);
}
void Server::queueBlockEmerge(v3s16 blockpos, bool allow_generate) void Server::queueBlockEmerge(v3s16 blockpos, bool allow_generate)
{ {
m_emerge->enqueueBlockEmerge(PEER_ID_INEXISTENT, blockpos, allow_generate); m_emerge->enqueueBlockEmerge(PEER_ID_INEXISTENT, blockpos, allow_generate);

View File

@ -456,6 +456,35 @@ public:
// Envlock and conlock should be locked when calling this // Envlock and conlock should be locked when calling this
void notifyPlayer(const char *name, const std::wstring msg); void notifyPlayer(const char *name, const std::wstring msg);
void notifyPlayers(const std::wstring msg); void notifyPlayers(const std::wstring msg);
void spawnParticle(const char *playername,
v3f pos, v3f velocity, v3f acceleration,
float expirationtime, float size,
bool collisiondetection, std::string texture);
void spawnParticleAll(v3f pos, v3f velocity, v3f acceleration,
float expirationtime, float size,
bool collisiondetection, std::string texture);
u32 addParticleSpawner(const char *playername,
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, std::string texture);
u32 addParticleSpawnerAll(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, std::string texture);
void deleteParticleSpawner(const char *playername, u32 id);
void deleteParticleSpawnerAll(u32 id);
void queueBlockEmerge(v3s16 blockpos, bool allow_generate); void queueBlockEmerge(v3s16 blockpos, bool allow_generate);
@ -574,6 +603,41 @@ private:
void sendDetachedInventoryToAll(const std::string &name); void sendDetachedInventoryToAll(const std::string &name);
void sendDetachedInventories(u16 peer_id); void sendDetachedInventories(u16 peer_id);
// Adds a ParticleSpawner on peer with peer_id
void 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, std::string texture, u32 id);
// Adds a ParticleSpawner on all peers
void SendAddParticleSpawnerAll(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, std::string texture, u32 id);
// Deletes ParticleSpawner on a single client
void SendDeleteParticleSpawner(u16 peer_id, u32 id);
// Deletes ParticleSpawner on all clients
void SendDeleteParticleSpawnerAll(u32 id);
// Spawns particle on single client
void SendSpawnParticle(u16 peer_id,
v3f pos, v3f velocity, v3f acceleration,
float expirationtime, float size,
bool collisiondetection, std::string texture);
// Spawns particle on all clients
void SendSpawnParticleAll(v3f pos, v3f velocity, v3f acceleration,
float expirationtime, float size,
bool collisiondetection, std::string texture);
/* /*
Something random Something random
*/ */
@ -790,6 +854,11 @@ private:
*/ */
// key = name // key = name
std::map<std::string, Inventory*> m_detached_inventories; std::map<std::string, Inventory*> m_detached_inventories;
/*
Particles
*/
std::vector<u32> m_particlespawner_ids;
}; };
/* /*