diff --git a/builtin/game/features.lua b/builtin/game/features.lua index 0bbb1d3666..a75319e70a 100644 --- a/builtin/game/features.lua +++ b/builtin/game/features.lua @@ -49,6 +49,7 @@ core.features = { httpfetch_additional_methods = true, object_guids = true, on_timer_four_args = true, + particlespawner_exclude_player = true, } function core.has_feature(arg) diff --git a/doc/lua_api.md b/doc/lua_api.md index ca0ca20359..d4a991a1cb 100644 --- a/doc/lua_api.md +++ b/doc/lua_api.md @@ -5843,6 +5843,8 @@ Utilities object_guids = true, -- The NodeTimer `on_timer` callback is passed additional `node` and `timeout` args (5.14.0) on_timer_four_args = true, + -- `ParticleSpawner` definition supports `exclude_player` field (5.14.0) + particlespawner_exclude_player = true, } ``` @@ -7471,6 +7473,7 @@ Particles --------- * `core.add_particle(particle definition)` + * Spawn a single particle * Deprecated: `core.add_particle(pos, velocity, acceleration, expirationtime, size, collisiondetection, texture, playername)` @@ -11487,6 +11490,9 @@ Used by `core.add_particle`. playername = "singleplayer", -- Optional, if specified spawns particle only on the player's client + -- Note that `exclude_player` is not supported here. You can use a single-use + -- particlespawner if needed. + animation = {Tile Animation definition}, -- Optional, specifies how to animate the particle texture @@ -11550,6 +11556,9 @@ will be ignored. -- If time is 0 spawner has infinite lifespan and spawns the `amount` on -- a per-second basis. + size = 1, + -- Size of the particle. + collisiondetection = false, -- If true collide with `walkable` nodes and, depending on the -- `object_collision` field, objects too. @@ -11576,7 +11585,12 @@ will be ignored. -- following section. playername = "singleplayer", - -- Optional, if specified spawns particles only on the player's client + -- Optional, if specified spawns particles only for this player + -- Can't be used together with `exclude_player`. + + exclude_player = "singleplayer", + -- Optional, if specified spawns particles not for this player + -- Added in v5.14.0. Can't be used together with `playername`. animation = {Tile Animation definition}, -- Optional, specifies how to animate the particles' texture diff --git a/src/script/lua_api/l_particles.cpp b/src/script/lua_api/l_particles.cpp index efcf3725e4..ac785215de 100644 --- a/src/script/lua_api/l_particles.cpp +++ b/src/script/lua_api/l_particles.cpp @@ -157,7 +157,7 @@ int ModApiParticles::l_add_particlespawner(lua_State *L) // Get parameters ParticleSpawnerParameters p; ServerActiveObject *attached = NULL; - std::string playername; + std::string playername, not_playername; using namespace ParticleParamTypes; if (lua_gettop(L) > 1) //deprecated @@ -257,7 +257,6 @@ int ModApiParticles::l_add_particlespawner(lua_State *L) lua_pop(L, 1); p.vertical = getboolfield_default(L, 1, "vertical", p.vertical); - playername = getstringfield_default(L, 1, "playername", ""); p.glow = getintfield_default(L, 1, "glow", p.glow); lua_getfield(L, 1, "texpool"); @@ -279,12 +278,16 @@ int ModApiParticles::l_add_particlespawner(lua_State *L) lua_pop(L, 1); p.node_tile = getintfield_default(L, 1, "node_tile", p.node_tile); + + // meta parameters + playername = getstringfield_default(L, 1, "playername", ""); + not_playername = getstringfield_default(L, 1, "exclude_player", ""); } if (p.time < 0) throw LuaError("particle spawner 'time' must be >= 0"); - u32 id = getServer(L)->addParticleSpawner(p, attached, playername); + u32 id = getServer(L)->addParticleSpawner(p, attached, playername, not_playername); lua_pushnumber(L, id); return 1; diff --git a/src/server.cpp b/src/server.cpp index 1c53bede3f..05a4fcc3d5 100644 --- a/src/server.cpp +++ b/src/server.cpp @@ -1646,45 +1646,61 @@ void Server::SendSpawnParticle(session_t peer_id, u16 protocol_version, Send(&pkt); } -// Adds a ParticleSpawner on peer with peer_id -void Server::SendAddParticleSpawner(session_t peer_id, u16 protocol_version, +void Server::SendAddParticleSpawner(const std::string &to_player, + const std::string &exclude_player, const ParticleSpawnerParameters &p, u16 attached_id, u32 id) { static thread_local const float radius = g_settings->getS16("max_block_send_distance") * MAP_BLOCKSIZE * BS; + const float radius_sq = radius * radius; - if (peer_id == PEER_ID_INEXISTENT) { - std::vector clients = m_clients.getClientIDs(); - const v3f pos = ( - p.pos.start.min.val + - p.pos.start.max.val + - p.pos.end.min.val + - p.pos.end.max.val - ) / 4.0f * BS; - const float radius_sq = radius * radius; - /* Don't send short-lived spawners to distant players. - * This could be replaced with proper tracking at some point. - * A lifetime of 0 means that the spawner exists forever.*/ - const bool distance_check = !attached_id && p.time <= 1.0f && p.time != 0.0f; + // Average position where particles would spawn (approximate) + const v3f pos = ( + p.pos.start.min.val + + p.pos.start.max.val + + p.pos.end.min.val + + p.pos.end.max.val + ) / 4.0f * BS; + /* Don't send short-lived spawners to distant players. + * This could be replaced with proper tracking at some point. + * A lifetime of 0 means that the spawner exists forever. */ + const bool distance_check = !attached_id && p.time <= 1.0f && p.time != 0.0f; - for (const session_t client_id : clients) { - RemotePlayer *player = m_env->getPlayer(client_id); - if (!player) - continue; - - if (distance_check) { - PlayerSAO *sao = player->getPlayerSAO(); - if (!sao) - continue; - if (sao->getBasePosition().getDistanceFromSQ(pos) > radius_sq) - continue; - } - - SendAddParticleSpawner(client_id, player->protocol_version, - p, attached_id, id); + const auto &consider_player = [&] (RemotePlayer *player) { + if (distance_check) { + PlayerSAO *sao = player->getPlayerSAO(); + if (!sao) + return; + if (sao->getBasePosition().getDistanceFromSQ(pos) > radius_sq) + return; } + + SendAddParticleSpawner(player->getPeerId(), player->protocol_version, + p, attached_id, id); + }; + + // Send to one -or- all (except one) + if (!to_player.empty()) { + RemotePlayer *player = m_env->getPlayer(to_player); + if (player) + consider_player(player); return; } + std::vector clients = m_clients.getClientIDs(); + for (const session_t client_id : clients) { + RemotePlayer *player = m_env->getPlayer(client_id); + if (!player) + continue; + if (!exclude_player.empty() && exclude_player == player->getName()) + continue; + consider_player(player); + } +} + +void Server::SendAddParticleSpawner(session_t peer_id, u16 protocol_version, + const ParticleSpawnerParameters &p, u16 attached_id, u32 id) +{ + assert(peer_id != PEER_ID_INEXISTENT); assert(protocol_version != 0); NetworkPacket pkt(TOCLIENT_ADD_PARTICLESPAWNER, 100, peer_id); @@ -3619,22 +3635,12 @@ void Server::spawnParticle(const std::string &playername, } u32 Server::addParticleSpawner(const ParticleSpawnerParameters &p, - ServerActiveObject *attached, const std::string &playername) + ServerActiveObject *attached, const std::string &to_player, + const std::string &exclude_player) { - // m_env will be NULL if the server is initializing if (!m_env) return -1; - session_t peer_id = PEER_ID_INEXISTENT; - u16 proto_ver = 0; - if (!playername.empty()) { - RemotePlayer *player = m_env->getPlayer(playername.c_str()); - if (!player) - return -1; - peer_id = player->getPeerId(); - proto_ver = player->protocol_version; - } - u16 attached_id = attached ? attached->getId() : 0; u32 id; @@ -3643,13 +3649,12 @@ u32 Server::addParticleSpawner(const ParticleSpawnerParameters &p, else id = m_env->addParticleSpawner(p.time, attached_id); - SendAddParticleSpawner(peer_id, proto_ver, p, attached_id, id); + SendAddParticleSpawner(to_player, exclude_player, p, attached_id, id); return id; } void Server::deleteParticleSpawner(const std::string &playername, u32 id) { - // m_env will be NULL if the server is initializing if (!m_env) throw ServerError("Can't delete particle spawners during initialisation!"); @@ -3661,7 +3666,11 @@ void Server::deleteParticleSpawner(const std::string &playername, u32 id) peer_id = player->getPeerId(); } + // FIXME: we don't track which client still knows about this spawner, so + // just deleting it entirely is problematic! + // We also don't check if the ID is even in use. FAIL! m_env->deleteParticleSpawner(id); + SendDeleteParticleSpawner(peer_id, id); } diff --git a/src/server.h b/src/server.h index 9f22fe678b..edb9416924 100644 --- a/src/server.h +++ b/src/server.h @@ -291,7 +291,8 @@ public: const ParticleParameters &p); u32 addParticleSpawner(const ParticleSpawnerParameters &p, - ServerActiveObject *attached, const std::string &playername); + ServerActiveObject *attached, const std::string &to_player, + const std::string &exclude_player); void deleteParticleSpawner(const std::string &playername, u32 id); @@ -593,7 +594,11 @@ private: const std::unordered_set &tosend); void stepPendingDynMediaCallbacks(float dtime); - // Adds a ParticleSpawner on peer with peer_id (PEER_ID_INEXISTENT == all) + /// @brief send particle spawner to a selection of clients + void SendAddParticleSpawner(const std::string &to_player, + const std::string &exclude_player, + const ParticleSpawnerParameters &p, u16 attached_id, u32 id); + /// @brief send particle spawner to one client (internal) void SendAddParticleSpawner(session_t peer_id, u16 protocol_version, const ParticleSpawnerParameters &p, u16 attached_id, u32 id);