mirror of
https://github.com/minetest/minetest.git
synced 2024-11-29 19:13:44 +01:00
Allow managing object observers
----- Co-authored-by: sfan5 <sfan5@live.de> Co-authored-by: SmallJoker <SmallJoker@users.noreply.github.com>
This commit is contained in:
parent
cc8e7a569e
commit
6874c358ea
@ -7986,6 +7986,29 @@ child will follow movement and rotation of that bone.
|
|||||||
* `get_bone_overrides()`: returns all bone overrides as table `{[bonename] = override, ...}`
|
* `get_bone_overrides()`: returns all bone overrides as table `{[bonename] = override, ...}`
|
||||||
* `set_properties(object property table)`
|
* `set_properties(object property table)`
|
||||||
* `get_properties()`: returns a table of all object properties
|
* `get_properties()`: returns a table of all object properties
|
||||||
|
* `set_observers(observers)`: sets observers (players this object is sent to)
|
||||||
|
* If `observers` is `nil`, the object's observers are "unmanaged":
|
||||||
|
The object is sent to all players as governed by server settings. This is the default.
|
||||||
|
* `observers` is a "set" of player names: `{[player name] = true, [other player name] = true, ...}`
|
||||||
|
* A set is a table where the keys are the elements of the set (in this case, player names)
|
||||||
|
and the values are all `true`.
|
||||||
|
* Attachments: The *effective observers* of an object are made up of
|
||||||
|
all players who can observe the object *and* are also effective observers
|
||||||
|
of its parent object (if there is one).
|
||||||
|
* Players are automatically added to their own observer sets.
|
||||||
|
Players **must** effectively observe themselves.
|
||||||
|
* Object activation and deactivation are unaffected by observability.
|
||||||
|
* Attached sounds do not work correctly and thus should not be used
|
||||||
|
on objects with managed observers yet.
|
||||||
|
* `get_observers()`:
|
||||||
|
* throws an error if the object is invalid
|
||||||
|
* returns `nil` if the observers are unmanaged
|
||||||
|
* returns a table with all observer names as keys and `true` values (a "set") otherwise
|
||||||
|
* `get_effective_observers()`:
|
||||||
|
* Like `get_observers()`, but returns the "effective" observers, taking into account attachments
|
||||||
|
* Time complexity: O(nm)
|
||||||
|
* n: number of observers of the involved entities
|
||||||
|
* m: number of ancestors along the attachment chain
|
||||||
* `is_player()`: returns true for players, false otherwise
|
* `is_player()`: returns true for players, false otherwise
|
||||||
* `get_nametag_attributes()`
|
* `get_nametag_attributes()`
|
||||||
* returns a table with the attributes of the nametag of an object
|
* returns a table with the attributes of the nametag of an object
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
dofile(minetest.get_modpath("testentities").."/visuals.lua")
|
dofile(minetest.get_modpath("testentities").."/visuals.lua")
|
||||||
|
dofile(minetest.get_modpath("testentities").."/observers.lua")
|
||||||
dofile(minetest.get_modpath("testentities").."/selectionbox.lua")
|
dofile(minetest.get_modpath("testentities").."/selectionbox.lua")
|
||||||
dofile(minetest.get_modpath("testentities").."/armor.lua")
|
dofile(minetest.get_modpath("testentities").."/armor.lua")
|
||||||
dofile(minetest.get_modpath("testentities").."/pointable.lua")
|
dofile(minetest.get_modpath("testentities").."/pointable.lua")
|
||||||
|
37
games/devtest/mods/testentities/observers.lua
Normal file
37
games/devtest/mods/testentities/observers.lua
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
local function player_names_excluding(exclude_player_name)
|
||||||
|
local player_names = {}
|
||||||
|
for _, player in ipairs(minetest.get_connected_players()) do
|
||||||
|
player_names[player:get_player_name()] = true
|
||||||
|
end
|
||||||
|
player_names[exclude_player_name] = nil
|
||||||
|
return player_names
|
||||||
|
end
|
||||||
|
|
||||||
|
minetest.register_entity("testentities:observable", {
|
||||||
|
initial_properties = {
|
||||||
|
visual = "sprite",
|
||||||
|
textures = { "testentities_sprite.png" },
|
||||||
|
static_save = false,
|
||||||
|
infotext = "Punch to set observers to anyone but you"
|
||||||
|
},
|
||||||
|
on_activate = function(self)
|
||||||
|
self.object:set_armor_groups({punch_operable = 1})
|
||||||
|
assert(self.object:get_observers() == nil)
|
||||||
|
-- Using a value of `false` in the table should error.
|
||||||
|
assert(not pcall(self.object, self.object.set_observers, self.object, {test = false}))
|
||||||
|
end,
|
||||||
|
on_punch = function(self, puncher)
|
||||||
|
local puncher_name = puncher:get_player_name()
|
||||||
|
local observers = player_names_excluding(puncher_name)
|
||||||
|
self.object:set_observers(observers)
|
||||||
|
local got_observers = self.object:get_observers()
|
||||||
|
for name in pairs(observers) do
|
||||||
|
assert(got_observers[name])
|
||||||
|
end
|
||||||
|
for name in pairs(got_observers) do
|
||||||
|
assert(observers[name])
|
||||||
|
end
|
||||||
|
self.object:set_properties({infotext = "Excluding " .. puncher_name})
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
})
|
@ -19,7 +19,6 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
|||||||
|
|
||||||
#include "localplayer.h"
|
#include "localplayer.h"
|
||||||
#include <cmath>
|
#include <cmath>
|
||||||
#include <string>
|
|
||||||
#include "mtevent.h"
|
#include "mtevent.h"
|
||||||
#include "collision.h"
|
#include "collision.h"
|
||||||
#include "nodedef.h"
|
#include "nodedef.h"
|
||||||
|
@ -30,7 +30,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
|||||||
#include "porting.h" // strlcpy
|
#include "porting.h" // strlcpy
|
||||||
|
|
||||||
|
|
||||||
Player::Player(const std::string name, IItemDefManager *idef):
|
Player::Player(const std::string &name, IItemDefManager *idef):
|
||||||
inventory(idef)
|
inventory(idef)
|
||||||
{
|
{
|
||||||
m_name = name;
|
m_name = name;
|
||||||
|
@ -27,6 +27,8 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
|||||||
#include "common/c_converter.h"
|
#include "common/c_converter.h"
|
||||||
#include "common/c_content.h"
|
#include "common/c_content.h"
|
||||||
#include "log.h"
|
#include "log.h"
|
||||||
|
#include "player.h"
|
||||||
|
#include "server/serveractiveobject.h"
|
||||||
#include "tool.h"
|
#include "tool.h"
|
||||||
#include "remoteplayer.h"
|
#include "remoteplayer.h"
|
||||||
#include "server.h"
|
#include "server.h"
|
||||||
@ -36,6 +38,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
|||||||
#include "server/player_sao.h"
|
#include "server/player_sao.h"
|
||||||
#include "server/serverinventorymgr.h"
|
#include "server/serverinventorymgr.h"
|
||||||
#include "server/unit_sao.h"
|
#include "server/unit_sao.h"
|
||||||
|
#include "util/string.h"
|
||||||
|
|
||||||
using object_t = ServerActiveObject::object_t;
|
using object_t = ServerActiveObject::object_t;
|
||||||
|
|
||||||
@ -837,6 +840,85 @@ int ObjectRef::l_get_properties(lua_State *L)
|
|||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// set_observers(self, observers)
|
||||||
|
int ObjectRef::l_set_observers(lua_State *L)
|
||||||
|
{
|
||||||
|
GET_ENV_PTR;
|
||||||
|
ObjectRef *ref = checkObject<ObjectRef>(L, 1);
|
||||||
|
ServerActiveObject *sao = getobject(ref);
|
||||||
|
if (sao == nullptr)
|
||||||
|
throw LuaError("Invalid ObjectRef");
|
||||||
|
|
||||||
|
// Reset object to "unmanaged" (sent to everyone)?
|
||||||
|
if (lua_isnoneornil(L, 2)) {
|
||||||
|
sao->m_observers.reset();
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::unordered_set<std::string> observer_names;
|
||||||
|
lua_pushnil(L);
|
||||||
|
while (lua_next(L, 2) != 0) {
|
||||||
|
std::string name = readParam<std::string>(L, -2);
|
||||||
|
if (name.empty())
|
||||||
|
throw LuaError("Observer name is empty");
|
||||||
|
if (name.size() > PLAYERNAME_SIZE)
|
||||||
|
throw LuaError("Observer name is too long");
|
||||||
|
if (!string_allowed(name, PLAYERNAME_ALLOWED_CHARS))
|
||||||
|
throw LuaError("Observer name contains invalid characters");
|
||||||
|
if (!lua_toboolean(L, -1)) // falsy value?
|
||||||
|
throw LuaError("Values in the `observers` table need to be true");
|
||||||
|
observer_names.insert(std::move(name));
|
||||||
|
lua_pop(L, 1); // pop value, keep key
|
||||||
|
}
|
||||||
|
|
||||||
|
RemotePlayer *player = getplayer(ref);
|
||||||
|
if (player != nullptr) {
|
||||||
|
observer_names.insert(player->getName());
|
||||||
|
}
|
||||||
|
|
||||||
|
sao->m_observers = std::move(observer_names);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename F>
|
||||||
|
static int get_observers(lua_State *L, F observer_getter)
|
||||||
|
{
|
||||||
|
ObjectRef *ref = ObjectRef::checkObject<ObjectRef>(L, 1);
|
||||||
|
ServerActiveObject *sao = ObjectRef::getobject(ref);
|
||||||
|
if (sao == nullptr)
|
||||||
|
throw LuaError("invalid ObjectRef");
|
||||||
|
|
||||||
|
const auto observers = observer_getter(sao);
|
||||||
|
if (!observers) {
|
||||||
|
lua_pushnil(L);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
// Push set of observers {[name] = true}
|
||||||
|
lua_createtable(L, 0, observers->size());
|
||||||
|
for (auto &name : *observers) {
|
||||||
|
lua_pushboolean(L, true);
|
||||||
|
lua_setfield(L, -2, name.c_str());
|
||||||
|
}
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// get_observers(self)
|
||||||
|
int ObjectRef::l_get_observers(lua_State *L)
|
||||||
|
{
|
||||||
|
NO_MAP_LOCK_REQUIRED;
|
||||||
|
return get_observers(L, [](auto sao) { return sao->m_observers; });
|
||||||
|
}
|
||||||
|
|
||||||
|
// get_effective_observers(self)
|
||||||
|
int ObjectRef::l_get_effective_observers(lua_State *L)
|
||||||
|
{
|
||||||
|
NO_MAP_LOCK_REQUIRED;
|
||||||
|
return get_observers(L, [](auto sao) {
|
||||||
|
// The cache may be outdated, so we always have to recalculate.
|
||||||
|
return sao->recalculateEffectiveObservers();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// is_player(self)
|
// is_player(self)
|
||||||
int ObjectRef::l_is_player(lua_State *L)
|
int ObjectRef::l_is_player(lua_State *L)
|
||||||
{
|
{
|
||||||
@ -2676,6 +2758,9 @@ luaL_Reg ObjectRef::methods[] = {
|
|||||||
luamethod(ObjectRef, get_properties),
|
luamethod(ObjectRef, get_properties),
|
||||||
luamethod(ObjectRef, set_nametag_attributes),
|
luamethod(ObjectRef, set_nametag_attributes),
|
||||||
luamethod(ObjectRef, get_nametag_attributes),
|
luamethod(ObjectRef, get_nametag_attributes),
|
||||||
|
luamethod(ObjectRef, set_observers),
|
||||||
|
luamethod(ObjectRef, get_observers),
|
||||||
|
luamethod(ObjectRef, get_effective_observers),
|
||||||
|
|
||||||
luamethod_aliased(ObjectRef, set_velocity, setvelocity),
|
luamethod_aliased(ObjectRef, set_velocity, setvelocity),
|
||||||
luamethod_aliased(ObjectRef, add_velocity, add_player_velocity),
|
luamethod_aliased(ObjectRef, add_velocity, add_player_velocity),
|
||||||
|
@ -163,6 +163,15 @@ private:
|
|||||||
// get_properties(self)
|
// get_properties(self)
|
||||||
static int l_get_properties(lua_State *L);
|
static int l_get_properties(lua_State *L);
|
||||||
|
|
||||||
|
// set_observers(self, observers)
|
||||||
|
static int l_set_observers(lua_State *L);
|
||||||
|
|
||||||
|
// get_observers(self)
|
||||||
|
static int l_get_observers(lua_State *L);
|
||||||
|
|
||||||
|
// get_effective_observers(self)
|
||||||
|
static int l_get_effective_observers(lua_State *L);
|
||||||
|
|
||||||
// is_player(self)
|
// is_player(self)
|
||||||
static int l_is_player(lua_State *L);
|
static int l_is_player(lua_State *L);
|
||||||
|
|
||||||
|
@ -116,6 +116,8 @@ void *ServerThread::run()
|
|||||||
m_server->setAsyncFatalError(e.what());
|
m_server->setAsyncFatalError(e.what());
|
||||||
} catch (LuaError &e) {
|
} catch (LuaError &e) {
|
||||||
m_server->setAsyncFatalError(e);
|
m_server->setAsyncFatalError(e);
|
||||||
|
} catch (ModError &e) {
|
||||||
|
m_server->setAsyncFatalError(e.what());
|
||||||
}
|
}
|
||||||
|
|
||||||
float dtime = 0.0f;
|
float dtime = 0.0f;
|
||||||
@ -142,6 +144,8 @@ void *ServerThread::run()
|
|||||||
m_server->setAsyncFatalError(e.what());
|
m_server->setAsyncFatalError(e.what());
|
||||||
} catch (LuaError &e) {
|
} catch (LuaError &e) {
|
||||||
m_server->setAsyncFatalError(e);
|
m_server->setAsyncFatalError(e);
|
||||||
|
} catch (ModError &e) {
|
||||||
|
m_server->setAsyncFatalError(e.what());
|
||||||
}
|
}
|
||||||
|
|
||||||
dtime = 1e-6f * (porting::getTimeUs() - t0);
|
dtime = 1e-6f * (porting::getTimeUs() - t0);
|
||||||
@ -781,6 +785,12 @@ void Server::AsyncRunStep(float dtime, bool initial_step)
|
|||||||
//infostream<<"Server: Checking added and deleted active objects"<<std::endl;
|
//infostream<<"Server: Checking added and deleted active objects"<<std::endl;
|
||||||
MutexAutoLock envlock(m_env_mutex);
|
MutexAutoLock envlock(m_env_mutex);
|
||||||
|
|
||||||
|
// This guarantees that each object recomputes its cache only once per server step,
|
||||||
|
// unless get_effective_observers is called.
|
||||||
|
// If we were to update observer sets eagerly in set_observers instead,
|
||||||
|
// the total costs of calls to set_observers could theoretically be higher.
|
||||||
|
m_env->invalidateActiveObjectObserverCaches();
|
||||||
|
|
||||||
{
|
{
|
||||||
ClientInterface::AutoLock clientlock(m_clients);
|
ClientInterface::AutoLock clientlock(m_clients);
|
||||||
const RemoteClientMap &clients = m_clients.getClientList();
|
const RemoteClientMap &clients = m_clients.getClientList();
|
||||||
|
@ -118,6 +118,16 @@ void ActiveObjectMgr::removeObject(u16 id)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ActiveObjectMgr::invalidateActiveObjectObserverCaches()
|
||||||
|
{
|
||||||
|
for (auto &active_object : m_active_objects.iter()) {
|
||||||
|
ServerActiveObject *obj = active_object.second.get();
|
||||||
|
if (!obj)
|
||||||
|
continue;
|
||||||
|
obj->invalidateEffectiveObservers();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void ActiveObjectMgr::getObjectsInsideRadius(const v3f &pos, float radius,
|
void ActiveObjectMgr::getObjectsInsideRadius(const v3f &pos, float radius,
|
||||||
std::vector<ServerActiveObject *> &result,
|
std::vector<ServerActiveObject *> &result,
|
||||||
std::function<bool(ServerActiveObject *obj)> include_obj_cb)
|
std::function<bool(ServerActiveObject *obj)> include_obj_cb)
|
||||||
@ -153,15 +163,18 @@ void ActiveObjectMgr::getObjectsInArea(const aabb3f &box,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void ActiveObjectMgr::getAddedActiveObjectsAroundPos(v3f player_pos, f32 radius,
|
void ActiveObjectMgr::getAddedActiveObjectsAroundPos(
|
||||||
f32 player_radius, const std::set<u16> ¤t_objects,
|
const v3f &player_pos, const std::string &player_name,
|
||||||
|
f32 radius, f32 player_radius,
|
||||||
|
const std::set<u16> ¤t_objects,
|
||||||
std::vector<u16> &added_objects)
|
std::vector<u16> &added_objects)
|
||||||
{
|
{
|
||||||
/*
|
/*
|
||||||
Go through the object list,
|
Go through the object list,
|
||||||
- discard removed/deactivated objects,
|
- discard removed/deactivated objects,
|
||||||
- discard objects that are too far away,
|
- discard objects that are too far away,
|
||||||
- discard objects that are found in current_objects.
|
- discard objects that are found in current_objects,
|
||||||
|
- discard objects that are not observed by the player.
|
||||||
- add remaining objects to added_objects
|
- add remaining objects to added_objects
|
||||||
*/
|
*/
|
||||||
for (auto &ao_it : m_active_objects.iter()) {
|
for (auto &ao_it : m_active_objects.iter()) {
|
||||||
@ -183,6 +196,9 @@ void ActiveObjectMgr::getAddedActiveObjectsAroundPos(v3f player_pos, f32 radius,
|
|||||||
} else if (distance_f > radius)
|
} else if (distance_f > radius)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
|
if (!object->isEffectivelyObservedBy(player_name))
|
||||||
|
continue;
|
||||||
|
|
||||||
// Discard if already on current_objects
|
// Discard if already on current_objects
|
||||||
auto n = current_objects.find(id);
|
auto n = current_objects.find(id);
|
||||||
if (n != current_objects.end())
|
if (n != current_objects.end())
|
||||||
|
@ -38,15 +38,18 @@ public:
|
|||||||
bool registerObject(std::unique_ptr<ServerActiveObject> obj) override;
|
bool registerObject(std::unique_ptr<ServerActiveObject> obj) override;
|
||||||
void removeObject(u16 id) override;
|
void removeObject(u16 id) override;
|
||||||
|
|
||||||
|
void invalidateActiveObjectObserverCaches();
|
||||||
|
|
||||||
void getObjectsInsideRadius(const v3f &pos, float radius,
|
void getObjectsInsideRadius(const v3f &pos, float radius,
|
||||||
std::vector<ServerActiveObject *> &result,
|
std::vector<ServerActiveObject *> &result,
|
||||||
std::function<bool(ServerActiveObject *obj)> include_obj_cb);
|
std::function<bool(ServerActiveObject *obj)> include_obj_cb);
|
||||||
void getObjectsInArea(const aabb3f &box,
|
void getObjectsInArea(const aabb3f &box,
|
||||||
std::vector<ServerActiveObject *> &result,
|
std::vector<ServerActiveObject *> &result,
|
||||||
std::function<bool(ServerActiveObject *obj)> include_obj_cb);
|
std::function<bool(ServerActiveObject *obj)> include_obj_cb);
|
||||||
|
void getAddedActiveObjectsAroundPos(
|
||||||
void getAddedActiveObjectsAroundPos(v3f player_pos, f32 radius,
|
const v3f &player_pos, const std::string &player_name,
|
||||||
f32 player_radius, const std::set<u16> ¤t_objects,
|
f32 radius, f32 player_radius,
|
||||||
|
const std::set<u16> ¤t_objects,
|
||||||
std::vector<u16> &added_objects);
|
std::vector<u16> &added_objects);
|
||||||
};
|
};
|
||||||
} // namespace server
|
} // namespace server
|
||||||
|
@ -18,11 +18,9 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
#include "serveractiveobject.h"
|
#include "serveractiveobject.h"
|
||||||
#include <fstream>
|
|
||||||
#include "inventory.h"
|
#include "inventory.h"
|
||||||
#include "inventorymanager.h"
|
#include "inventorymanager.h"
|
||||||
#include "constants.h" // BS
|
#include "constants.h" // BS
|
||||||
#include "log.h"
|
|
||||||
|
|
||||||
ServerActiveObject::ServerActiveObject(ServerEnvironment *env, v3f pos):
|
ServerActiveObject::ServerActiveObject(ServerEnvironment *env, v3f pos):
|
||||||
ActiveObject(0),
|
ActiveObject(0),
|
||||||
@ -95,3 +93,48 @@ InventoryLocation ServerActiveObject::getInventoryLocation() const
|
|||||||
{
|
{
|
||||||
return InventoryLocation();
|
return InventoryLocation();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ServerActiveObject::invalidateEffectiveObservers()
|
||||||
|
{
|
||||||
|
m_effective_observers.reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
using Observers = ServerActiveObject::Observers;
|
||||||
|
|
||||||
|
const Observers &ServerActiveObject::getEffectiveObservers()
|
||||||
|
{
|
||||||
|
if (m_effective_observers) // cached
|
||||||
|
return *m_effective_observers;
|
||||||
|
|
||||||
|
auto parent = getParent();
|
||||||
|
if (parent == nullptr)
|
||||||
|
return *(m_effective_observers = m_observers);
|
||||||
|
auto parent_observers = parent->getEffectiveObservers();
|
||||||
|
if (!parent_observers) // parent is unmanaged
|
||||||
|
return *(m_effective_observers = m_observers);
|
||||||
|
if (!m_observers) // we are unmanaged
|
||||||
|
return *(m_effective_observers = parent_observers);
|
||||||
|
// Set intersection between parent_observers and m_observers
|
||||||
|
// Avoid .clear() to free the allocated memory.
|
||||||
|
m_effective_observers = std::unordered_set<std::string>();
|
||||||
|
for (const auto &observer_name : *m_observers) {
|
||||||
|
if (parent_observers->count(observer_name) > 0)
|
||||||
|
(*m_effective_observers)->insert(observer_name);
|
||||||
|
}
|
||||||
|
return *m_effective_observers;
|
||||||
|
}
|
||||||
|
|
||||||
|
const Observers& ServerActiveObject::recalculateEffectiveObservers()
|
||||||
|
{
|
||||||
|
// Invalidate final observers for this object and all of its parents.
|
||||||
|
for (auto obj = this; obj != nullptr; obj = obj->getParent())
|
||||||
|
obj->invalidateEffectiveObservers();
|
||||||
|
// getEffectiveObservers will now be forced to recalculate.
|
||||||
|
return getEffectiveObservers();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ServerActiveObject::isEffectivelyObservedBy(const std::string &player_name)
|
||||||
|
{
|
||||||
|
auto effective_observers = getEffectiveObservers();
|
||||||
|
return !effective_observers || effective_observers->count(player_name) > 0;
|
||||||
|
}
|
||||||
|
@ -21,6 +21,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
|||||||
|
|
||||||
#include <cassert>
|
#include <cassert>
|
||||||
#include <unordered_set>
|
#include <unordered_set>
|
||||||
|
#include <optional>
|
||||||
#include "irrlichttypes_bloated.h"
|
#include "irrlichttypes_bloated.h"
|
||||||
#include "activeobject.h"
|
#include "activeobject.h"
|
||||||
#include "itemgroup.h"
|
#include "itemgroup.h"
|
||||||
@ -236,7 +237,25 @@ public:
|
|||||||
*/
|
*/
|
||||||
v3s16 m_static_block = v3s16(1337,1337,1337);
|
v3s16 m_static_block = v3s16(1337,1337,1337);
|
||||||
|
|
||||||
|
// Names of players to whom the object is to be sent, not considering parents.
|
||||||
|
using Observers = std::optional<std::unordered_set<std::string>>;
|
||||||
|
Observers m_observers;
|
||||||
|
|
||||||
|
/// Invalidate final observer cache. This needs to be done whenever
|
||||||
|
/// the observers of this object or any of its ancestors may have changed.
|
||||||
|
void invalidateEffectiveObservers();
|
||||||
|
/// Cache `m_effective_observers` with the names of all observers,
|
||||||
|
/// also indirect observers (object attachment chain).
|
||||||
|
const Observers &getEffectiveObservers();
|
||||||
|
/// Force a recalculation of final observers (including all parents).
|
||||||
|
const Observers &recalculateEffectiveObservers();
|
||||||
|
/// Whether the object is sent to `player_name`
|
||||||
|
bool isEffectivelyObservedBy(const std::string &player_name);
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
|
// Cached intersection of m_observers of this object and all its parents.
|
||||||
|
std::optional<Observers> m_effective_observers;
|
||||||
|
|
||||||
virtual void onMarkedForDeactivation() {}
|
virtual void onMarkedForDeactivation() {}
|
||||||
virtual void onMarkedForRemoval() {}
|
virtual void onMarkedForRemoval() {}
|
||||||
|
|
||||||
|
@ -1711,6 +1711,11 @@ u16 ServerEnvironment::addActiveObject(std::unique_ptr<ServerActiveObject> objec
|
|||||||
return id;
|
return id;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ServerEnvironment::invalidateActiveObjectObserverCaches()
|
||||||
|
{
|
||||||
|
m_ao_manager.invalidateActiveObjectObserverCaches();
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Finds out what new objects have been added to
|
Finds out what new objects have been added to
|
||||||
inside a radius around a position
|
inside a radius around a position
|
||||||
@ -1726,8 +1731,13 @@ void ServerEnvironment::getAddedActiveObjects(PlayerSAO *playersao, s16 radius,
|
|||||||
if (player_radius_f < 0.0f)
|
if (player_radius_f < 0.0f)
|
||||||
player_radius_f = 0.0f;
|
player_radius_f = 0.0f;
|
||||||
|
|
||||||
m_ao_manager.getAddedActiveObjectsAroundPos(playersao->getBasePosition(), radius_f,
|
if (!playersao->isEffectivelyObservedBy(playersao->getPlayer()->getName()))
|
||||||
player_radius_f, current_objects, added_objects);
|
throw ModError("Player does not observe itself");
|
||||||
|
|
||||||
|
m_ao_manager.getAddedActiveObjectsAroundPos(
|
||||||
|
playersao->getBasePosition(), playersao->getPlayer()->getName(),
|
||||||
|
radius_f, player_radius_f,
|
||||||
|
current_objects, added_objects);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -1744,13 +1754,20 @@ void ServerEnvironment::getRemovedActiveObjects(PlayerSAO *playersao, s16 radius
|
|||||||
|
|
||||||
if (player_radius_f < 0)
|
if (player_radius_f < 0)
|
||||||
player_radius_f = 0;
|
player_radius_f = 0;
|
||||||
|
|
||||||
|
const std::string &player_name = playersao->getPlayer()->getName();
|
||||||
|
|
||||||
|
if (!playersao->isEffectivelyObservedBy(player_name))
|
||||||
|
throw ModError("Player does not observe itself");
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Go through current_objects; object is removed if:
|
Go through current_objects; object is removed if:
|
||||||
- object is not found in m_active_objects (this is actually an
|
- object is not found in m_active_objects (this is actually an
|
||||||
error condition; objects should be removed only after all clients
|
error condition; objects should be removed only after all clients
|
||||||
have been informed about removal), or
|
have been informed about removal), or
|
||||||
- object is to be removed or deactivated, or
|
- object is to be removed or deactivated, or
|
||||||
- object is too far away
|
- object is too far away, or
|
||||||
|
- object is marked as not observable by the client
|
||||||
*/
|
*/
|
||||||
for (u16 id : current_objects) {
|
for (u16 id : current_objects) {
|
||||||
ServerActiveObject *object = getActiveObject(id);
|
ServerActiveObject *object = getActiveObject(id);
|
||||||
@ -1768,14 +1785,12 @@ void ServerEnvironment::getRemovedActiveObjects(PlayerSAO *playersao, s16 radius
|
|||||||
}
|
}
|
||||||
|
|
||||||
f32 distance_f = object->getBasePosition().getDistanceFrom(playersao->getBasePosition());
|
f32 distance_f = object->getBasePosition().getDistanceFrom(playersao->getBasePosition());
|
||||||
if (object->getType() == ACTIVEOBJECT_TYPE_PLAYER) {
|
bool in_range = object->getType() == ACTIVEOBJECT_TYPE_PLAYER
|
||||||
if (distance_f <= player_radius_f || player_radius_f == 0)
|
? distance_f <= player_radius_f || player_radius_f == 0
|
||||||
continue;
|
: distance_f <= radius_f;
|
||||||
} else if (distance_f <= radius_f)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
// Object is no longer visible
|
if (!in_range || !object->isEffectivelyObservedBy(player_name))
|
||||||
removed_objects.emplace_back(false, id);
|
removed_objects.emplace_back(false, id); // out of range or not observed anymore
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -277,6 +277,8 @@ public:
|
|||||||
*/
|
*/
|
||||||
u16 addActiveObject(std::unique_ptr<ServerActiveObject> object);
|
u16 addActiveObject(std::unique_ptr<ServerActiveObject> object);
|
||||||
|
|
||||||
|
void invalidateActiveObjectObserverCaches();
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Find out what new objects have been added to
|
Find out what new objects have been added to
|
||||||
inside a radius around a position
|
inside a radius around a position
|
||||||
|
@ -175,12 +175,12 @@ void TestServerActiveObjectMgr::testGetAddedActiveObjectsAroundPos()
|
|||||||
|
|
||||||
std::vector<u16> result;
|
std::vector<u16> result;
|
||||||
std::set<u16> cur_objects;
|
std::set<u16> cur_objects;
|
||||||
saomgr.getAddedActiveObjectsAroundPos(v3f(), 100, 50, cur_objects, result);
|
saomgr.getAddedActiveObjectsAroundPos(v3f(), "singleplayer", 100, 50, cur_objects, result);
|
||||||
UASSERTCMP(int, ==, result.size(), 1);
|
UASSERTCMP(int, ==, result.size(), 1);
|
||||||
|
|
||||||
result.clear();
|
result.clear();
|
||||||
cur_objects.clear();
|
cur_objects.clear();
|
||||||
saomgr.getAddedActiveObjectsAroundPos(v3f(), 740, 50, cur_objects, result);
|
saomgr.getAddedActiveObjectsAroundPos(v3f(), "singleplayer", 740, 50, cur_objects, result);
|
||||||
UASSERTCMP(int, ==, result.size(), 2);
|
UASSERTCMP(int, ==, result.size(), 2);
|
||||||
|
|
||||||
saomgr.clear();
|
saomgr.clear();
|
||||||
|
Loading…
Reference in New Issue
Block a user