From e1ff5b13619666e5b987ecf4faaf294400ffd979 Mon Sep 17 00:00:00 2001 From: Jeija Date: Wed, 23 Jan 2013 18:32:02 +0100 Subject: [PATCH] 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 --- doc/lua_api.txt | 31 ++++ src/CMakeLists.txt | 1 + src/client.cpp | 83 ++++++++++ src/client.h | 34 +++- src/clientserver.h | 44 +++++ src/game.cpp | 49 +++++- src/particles.cpp | 322 ++++++++++++++++++++++++++++++------ src/particles.h | 72 +++++++- src/scriptapi.cpp | 4 + src/scriptapi_particles.cpp | 143 ++++++++++++++++ src/scriptapi_particles.h | 32 ++++ src/server.cpp | 232 ++++++++++++++++++++++++++ src/server.h | 69 ++++++++ 13 files changed, 1060 insertions(+), 56 deletions(-) create mode 100644 src/scriptapi_particles.cpp create mode 100644 src/scriptapi_particles.h diff --git a/doc/lua_api.txt b/doc/lua_api.txt index 3d47785ba..4edca5adb 100644 --- a/doc/lua_api.txt +++ b/doc/lua_api.txt @@ -1028,6 +1028,37 @@ minetest.get_ban_description(ip_or_name) -> ban description (string) minetest.ban_player(name) -> ban a player 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: minetest.get_connected_players() -> list of ObjectRefs minetest.hash_node_position({x=,y=,z=}) -> 48-bit integer diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 8c2890f76..8f0cc1ac5 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -219,6 +219,7 @@ set(common_SRCS scriptapi_object.cpp scriptapi_nodemeta.cpp scriptapi_inventory.cpp + scriptapi_particles.cpp scriptapi.cpp script.cpp log.cpp diff --git a/src/client.cpp b/src/client.cpp index 2ccace842..f27f95d98 100644 --- a/src/client.cpp +++ b/src/client.cpp @@ -1936,6 +1936,89 @@ void Client::ProcessData(u8 *data, u32 datasize, u16 sender_peer_id) event.show_formspec.formname = new std::string(formname); 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 { infostream<<"Client: Ignoring unknown command " diff --git a/src/client.h b/src/client.h index 570a6b3b0..d476a1d51 100644 --- a/src/client.h +++ b/src/client.h @@ -157,7 +157,10 @@ enum ClientEventType CE_PLAYER_FORCE_MOVE, CE_DEATHSCREEN, CE_TEXTURES_UPDATED, - CE_SHOW_FORMSPEC + CE_SHOW_FORMSPEC, + CE_SPAWN_PARTICLE, + CE_ADD_PARTICLESPAWNER, + CE_DELETE_PARTICLESPAWNER }; struct ClientEvent @@ -185,6 +188,35 @@ struct ClientEvent } show_formspec; struct{ } 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; }; }; diff --git a/src/clientserver.h b/src/clientserver.h index 6d830b92b..535fc04d8 100644 --- a/src/clientserver.h +++ b/src/clientserver.h @@ -82,6 +82,9 @@ SharedBuffer makePacket_TOCLIENT_TIME_OF_DAY(u16 time, float time_speed); PROTOCOL_VERSION 17: Serialization format change: include backface_culling flag in TileDef Added rightclickable field in nodedef + TOCLIENT_SPAWN_PARTICLE + TOCLIENT_ADD_PARTICLESPAWNER + TOCLIENT_DELETE_PARTICLESPAWNER */ #define LATEST_PROTOCOL_VERSION 17 @@ -359,6 +362,7 @@ enum ToClientCommand u8[len] name [2] serialized inventory */ + TOCLIENT_SHOW_FORMSPEC = 0x44, /* [0] u16 command @@ -384,6 +388,46 @@ enum ToClientCommand f1000 movement_liquid_sink 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 diff --git a/src/game.cpp b/src/game.cpp index 1ae29b13b..58ca654a0 100644 --- a/src/game.cpp +++ b/src/game.cpp @@ -2186,6 +2186,47 @@ void the_game( { 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 = client.getNodeDefManager()->get(n); 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 = client.getNodeDefManager()->get(wasnode); addDiggingParticles - (gamedef, smgr, player, nodepos, features.tiles); + (gamedef, smgr, player, client.getEnv(), + nodepos, features.tiles); } dig_time = 0; @@ -2734,6 +2777,7 @@ void the_game( */ allparticles_step(dtime, client.getEnv()); + allparticlespawners_step(dtime, client.getEnv()); /* Fog @@ -3220,6 +3264,7 @@ void the_game( clouds->drop(); if(gui_chat_console) gui_chat_console->drop(); + clear_particles (); /* Draw a "shutting down" screen, which will be shown while the map diff --git a/src/particles.cpp b/src/particles.cpp index fef21d91b..0445d7726 100644 --- a/src/particles.cpp +++ b/src/particles.cpp @@ -32,19 +32,34 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "clientmap.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 all_particles; +std::map all_particlespawners; + Particle::Particle( IGameDef *gamedef, scene::ISceneManager* smgr, LocalPlayer *player, - s32 id, + ClientEnvironment &env, v3f pos, v3f velocity, v3f acceleration, float expirationtime, float size, + bool collisiondetection, AtlasPointer ap ): - scene::ISceneNode(smgr->getRootSceneNode(), smgr, id) + scene::ISceneNode(smgr->getRootSceneNode(), smgr) { // Misc m_gamedef = gamedef; @@ -57,7 +72,6 @@ Particle::Particle( m_material.MaterialType = video::EMT_TRANSPARENT_ALPHA_CHANNEL; m_material.setTexture(0, ap.atlas); m_ap = ap; - m_light = 0; // Particle related @@ -68,10 +82,20 @@ Particle::Particle( m_time = 0; m_player = player; m_size = size; + m_collisiondetection = collisiondetection; - // Irrlicht stuff (TODO) - m_collisionbox = core::aabbox3d(-size/2,-size/2,-size/2,size/2,size/2,size/2); + // Irrlicht stuff + m_collisionbox = core::aabbox3d + (-size/2,-size/2,-size/2,size/2,size/2,size/2); this->setAutomaticCulling(scene::EAC_OFF); + + // Init lighting + updateLight(env); + + // Init model + updateVertices(); + + all_particles.push_back(this); } Particle::~Particle() @@ -82,8 +106,10 @@ void Particle::OnRegisterSceneNode() { if (IsVisible) { - SceneManager->registerNodeForRendering(this, scene::ESNRP_TRANSPARENT); - SceneManager->registerNodeForRendering(this, scene::ESNRP_SOLID); + SceneManager->registerNodeForRendering + (this, scene::ESNRP_TRANSPARENT); + SceneManager->registerNodeForRendering + (this, scene::ESNRP_SOLID); } ISceneNode::OnRegisterSceneNode(); @@ -96,45 +122,45 @@ void Particle::render() video::IVideoDriver* driver = SceneManager->getVideoDriver(); driver->setMaterial(m_material); 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}; - driver->drawVertexPrimitiveList(vertices, 4, indices, 2, - video::EVT_STANDARD, scene::EPT_TRIANGLES, video::EIT_16BIT); + driver->drawVertexPrimitiveList(m_vertices, 4, + indices, 2, video::EVT_STANDARD, + scene::EPT_TRIANGLES, video::EIT_16BIT); } void Particle::step(float dtime, ClientEnvironment &env) { - core::aabbox3d box = m_collisionbox; - v3f p_pos = m_pos*BS; - v3f p_velocity = m_velocity*BS; - v3f p_acceleration = m_acceleration*BS; - collisionMoveSimple(&env.getClientMap(), m_gamedef, - BS*0.5, box, - 0, dtime, - p_pos, p_velocity, p_acceleration); - m_pos = p_pos/BS; - m_velocity = p_velocity/BS; - m_acceleration = p_acceleration/BS; m_time += dtime; + if (m_collisiondetection) + { + core::aabbox3d box = m_collisionbox; + v3f p_pos = m_pos*BS; + v3f p_velocity = m_velocity*BS; + v3f p_acceleration = m_acceleration*BS; + collisionMoveSimple(&env.getClientMap(), m_gamedef, + BS*0.5, box, + 0, dtime, + p_pos, p_velocity, p_acceleration); + m_pos = p_pos/BS; + m_velocity = p_velocity/BS; + m_acceleration = p_acceleration/BS; + } + else + { + m_velocity += m_acceleration * dtime; + m_pos += m_velocity * dtime; + } // Update lighting + updateLight(env); + + // Update model + updateVertices(); +} + +void Particle::updateLight(ClientEnvironment &env) +{ u8 light = 0; try{ v3s16 p = v3s16( @@ -151,11 +177,37 @@ void Particle::step(float dtime, ClientEnvironment &env) m_light = decode_light(light); } -std::vector 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) { - for(std::vector::iterator i = all_particles.begin(); i != all_particles.end();) + for(std::vector::iterator i = all_particles.begin(); + i != all_particles.end();) { 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 { - 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 // 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 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); // 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 particlepos = v3f( (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 ); - Particle *particle = new Particle( + new Particle( gamedef, smgr, player, - 0, + env, particlepos, velocity, acceleration, rand()%100/100., // expiration time visual_size, + true, 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(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::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::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::iterator i = + all_particlespawners.begin(); + i != all_particlespawners.end();) + { + delete i->second; + all_particlespawners.erase(i++); + } + + for(std::vector::iterator i = + all_particles.begin(); + i != all_particles.end();) + { + (*i)->remove(); + delete *i; + all_particles.erase(i); + } } diff --git a/src/particles.h b/src/particles.h index b317549e7..308da551f 100644 --- a/src/particles.h +++ b/src/particles.h @@ -35,12 +35,13 @@ class Particle : public scene::ISceneNode IGameDef* gamedef, scene::ISceneManager* mgr, LocalPlayer *player, - s32 id, + ClientEnvironment &env, v3f pos, v3f velocity, v3f acceleration, float expirationtime, float size, + bool collisiondetection, AtlasPointer texture ); ~Particle(); @@ -69,6 +70,10 @@ class Particle : public scene::ISceneNode { return m_expiration < m_time; } private: + void updateLight(ClientEnvironment &env); + void updateVertices(); + + video::S3DVertex m_vertices[4]; float m_time; float m_expiration; @@ -87,12 +92,71 @@ private: float m_size; AtlasPointer m_ap; 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 m_spawntimes; + bool m_collisiondetection; }; 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 addPunchingParticles(IGameDef* gamedef, scene::ISceneManager* smgr, LocalPlayer *player, v3s16 pos, const TileSpec tiles[]); -void addNodeParticle(IGameDef* gamedef, scene::ISceneManager* smgr, LocalPlayer *player, v3s16 pos, const TileSpec tiles[]); +void delete_particlespawner (u32 id); +void clear_particles (); + +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 diff --git a/src/scriptapi.cpp b/src/scriptapi.cpp index 13696eabf..29494ff69 100644 --- a/src/scriptapi.cpp +++ b/src/scriptapi.cpp @@ -44,6 +44,7 @@ extern "C" { #include "scriptapi_item.h" #include "scriptapi_content.h" #include "scriptapi_craft.h" +#include "scriptapi_particles.h" /*****************************************************************************/ /* Mod related */ @@ -1089,6 +1090,9 @@ static const struct luaL_Reg minetest_f [] = { {"get_all_craft_recipes", l_get_all_craft_recipes}, {"rollback_get_last_node_actor", l_rollback_get_last_node_actor}, {"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} }; diff --git a/src/scriptapi_particles.cpp b/src/scriptapi_particles.cpp new file mode 100644 index 000000000..dc9b3776e --- /dev/null +++ b/src/scriptapi_particles.cpp @@ -0,0 +1,143 @@ +/* +Minetest +Copyright (C) 2013 celeron55, Perttu Ahola + +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; +} diff --git a/src/scriptapi_particles.h b/src/scriptapi_particles.h new file mode 100644 index 000000000..4b37d7ce1 --- /dev/null +++ b/src/scriptapi_particles.h @@ -0,0 +1,32 @@ +/* +Minetest-c55 +Copyright (C) 2013 celeron55, Perttu Ahola + +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 +#include +} + +int l_add_particle(lua_State *L); +int l_add_particlespawner(lua_State *L); +int l_delete_particlespawner(lua_State *L); + +#endif diff --git a/src/server.cpp b/src/server.cpp index f77ac6ad9..644f89349 100644 --- a/src/server.cpp +++ b/src/server.cpp @@ -20,6 +20,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "server.h" #include #include +#include #include "clientserver.h" #include "map.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); } +// 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< 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::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< 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::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 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::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) { for(std::map::iterator @@ -4432,6 +4559,111 @@ void Server::notifyPlayers(const std::wstring 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) { m_emerge->enqueueBlockEmerge(PEER_ID_INEXISTENT, blockpos, allow_generate); diff --git a/src/server.h b/src/server.h index 63717eaec..813dc9828 100644 --- a/src/server.h +++ b/src/server.h @@ -456,6 +456,35 @@ public: // Envlock and conlock should be locked when calling this void notifyPlayer(const char *name, 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); @@ -574,6 +603,41 @@ private: void sendDetachedInventoryToAll(const std::string &name); 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 */ @@ -790,6 +854,11 @@ private: */ // key = name std::map m_detached_inventories; + + /* + Particles + */ + std::vector m_particlespawner_ids; }; /*