Skip to content

Commit

Permalink
Allow managing object observers
Browse files Browse the repository at this point in the history
  • Loading branch information
appgurueu authored and LoneWolfHT committed Dec 25, 2023
1 parent 8547b7e commit 8af7937
Show file tree
Hide file tree
Showing 11 changed files with 177 additions and 16 deletions.
14 changes: 14 additions & 0 deletions doc/lua_api.md
Original file line number Diff line number Diff line change
Expand Up @@ -7583,6 +7583,20 @@ child will follow movement and rotation of that bone.
* `get_bone_overrides()`: returns all bone overrides as table `{[bonename] = override, ...}`
* `set_properties(object property table)`
* `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 (in proximity). This is the default.
* `observers` is a "set" of player names: `{[player name] = true, [other player name] = true, ...}`
* A player is an observer if and only if the player's name is a key in the `observers` table.
* The values are irrelevant. To prevent the mistake of expecting `[player name] = false`
to "uninstall" a player as an observer, they must be truthy.
* If players are managed, they always need to have themselves as observers.
* Attachments:
* If an object's observers are managed, the observers of all children need to be managed too.
* The observers of children need to be a subset of the observers of parents.
* `get_observers()`:
* returns `nil` if the observers are unmanaged
* returns a table with all observer names as keys and `true` values (a "set") otherwise
* `is_player()`: returns true for players, false otherwise
* `get_nametag_attributes()`
* returns a table with the attributes of the nametag of an object
Expand Down
1 change: 1 addition & 0 deletions games/devtest/mods/testentities/init.lua
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
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").."/armor.lua")
37 changes: 37 additions & 0 deletions games/devtest/mods/testentities/observers.lua
Original file line number Diff line number Diff line change
@@ -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
})
81 changes: 81 additions & 0 deletions src/script/lua_api/l_object.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -816,6 +816,85 @@ int ObjectRef::l_get_properties(lua_State *L)
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)) {
ServerActiveObject *parent = sao->getParent();
if (parent != nullptr && !parent->m_observer_names.has_value())
throw LuaError("Child observers need to be managed if parent observers are managed");
sao->m_observer_names.reset();
return 0;
}

std::unordered_set<std::string> observer_names;
lua_pushnil(L);
while (lua_next(L, 2) != 0) {
observer_names.insert(readParam<std::string>(L, -2));
if (!lua_toboolean(L, -1)) // falsy value?
throw LuaError("Values in the `observers` table need to be truthy");
lua_pop(L, 1); // pop value, keep key
}

RemotePlayer *player = getplayer(ref);
if (player != nullptr) {
if (observer_names.find(player->getName()) == observer_names.end())
throw LuaError("Players need to observe themselves");
}

// Check attachments
ServerActiveObject *parent = sao->getParent();
if (parent != nullptr) {
for (auto name : observer_names) {
if (!parent->isObservedBy(name))
throw LuaError("Child observers not a subset of parent observers");
}
}
const std::unordered_set<int> child_ids = sao->getAttachmentChildIds();
for (auto child_id : child_ids) {
ServerActiveObject *child = env->getActiveObject(child_id);
if (!child->m_observer_names.has_value())
throw LuaError("Child observers need to be managed if parent observers are managed");
for (auto name : child->m_observer_names.value()) {
if (!sao->isObservedBy(name))
throw LuaError("Child observers not a subset of parent observers");
}
}
sao->m_observer_names = observer_names;
return 0;
}

// get_observers(self)
int ObjectRef::l_get_observers(lua_State *L)
{
NO_MAP_LOCK_REQUIRED;
ObjectRef *ref = checkObject<ObjectRef>(L, 1);
ServerActiveObject *sao = getobject(ref);
if (sao == nullptr)
throw LuaError("invalid ObjectRef");

const auto &observer_names = sao->m_observer_names;
if (!observer_names.has_value()) {
lua_pushnil(L);
return 1;
}

// Push set of observers {[name] = true}
lua_createtable(L, 0, observer_names.value().size());
for (auto name : observer_names.value()) {
lua_pushboolean(L, true);
lua_setfield(L, -2, name.c_str());
}
return 1;
}

// is_player(self)
int ObjectRef::l_is_player(lua_State *L)
{
Expand Down Expand Up @@ -2625,6 +2704,8 @@ luaL_Reg ObjectRef::methods[] = {
luamethod(ObjectRef, get_properties),
luamethod(ObjectRef, set_nametag_attributes),
luamethod(ObjectRef, get_nametag_attributes),
luamethod(ObjectRef, set_observers),
luamethod(ObjectRef, get_observers),

luamethod_aliased(ObjectRef, set_velocity, setvelocity),
luamethod_aliased(ObjectRef, add_velocity, add_player_velocity),
Expand Down
6 changes: 6 additions & 0 deletions src/script/lua_api/l_object.h
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,12 @@ class ObjectRef : public ModApiBase {
// get_properties(self)
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);

// is_player(self)
static int l_is_player(lua_State *L);

Expand Down
9 changes: 7 additions & 2 deletions src/server/activeobjectmgr.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -156,8 +156,10 @@ void ActiveObjectMgr::getObjectsInArea(const aabb3f &box,
}
}

void ActiveObjectMgr::getAddedActiveObjectsAroundPos(const v3f &player_pos, f32 radius,
f32 player_radius, std::set<u16> &current_objects,
void ActiveObjectMgr::getAddedActiveObjectsAroundPos(
const v3f &player_pos, const std::string &player_name,
f32 radius, f32 player_radius,
std::set<u16> &current_objects,
std::queue<u16> &added_objects)
{
/*
Expand Down Expand Up @@ -186,6 +188,9 @@ void ActiveObjectMgr::getAddedActiveObjectsAroundPos(const v3f &player_pos, f32
} else if (distance_f > radius)
continue;

if (!object->isObservedBy(player_name))
continue;

// Discard if already on current_objects
auto n = current_objects.find(id);
if (n != current_objects.end())
Expand Down
7 changes: 4 additions & 3 deletions src/server/activeobjectmgr.h
Original file line number Diff line number Diff line change
Expand Up @@ -44,9 +44,10 @@ class ActiveObjectMgr final : public ::ActiveObjectMgr<ServerActiveObject>
void getObjectsInArea(const aabb3f &box,
std::vector<ServerActiveObject *> &result,
std::function<bool(ServerActiveObject *obj)> include_obj_cb);

void getAddedActiveObjectsAroundPos(const v3f &player_pos, f32 radius,
f32 player_radius, std::set<u16> &current_objects,
void getAddedActiveObjectsAroundPos(
const v3f &player_pos, const std::string &player_name,
f32 radius, f32 player_radius,
std::set<u16> &current_objects,
std::queue<u16> &added_objects);
};
} // namespace server
5 changes: 5 additions & 0 deletions src/server/serveractiveobject.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -95,3 +95,8 @@ InventoryLocation ServerActiveObject::getInventoryLocation() const
{
return InventoryLocation();
}

bool ServerActiveObject::isObservedBy(const std::string &player_name) const
{
return !m_observer_names.has_value() || m_observer_names.value().count(player_name) > 0;
}
8 changes: 8 additions & 0 deletions src/server/serveractiveobject.h
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,

#include <cassert>
#include <unordered_set>
#include <optional>
#include "irrlichttypes_bloated.h"
#include "activeobject.h"
#include "itemgroup.h"
Expand Down Expand Up @@ -235,6 +236,13 @@ class ServerActiveObject : public ActiveObject
*/
v3s16 m_static_block = v3s16(1337,1337,1337);

bool isObservedBy(const std::string &player_name) const;

/*
Names of players to whom the object is to be sent
*/
std::optional<std::unordered_set<std::string>> m_observer_names;

protected:
virtual void onMarkedForDeactivation() {}
virtual void onMarkedForRemoval() {}
Expand Down
21 changes: 12 additions & 9 deletions src/serverenvironment.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1701,8 +1701,10 @@ void ServerEnvironment::getAddedActiveObjects(PlayerSAO *playersao, s16 radius,
if (player_radius_f < 0.0f)
player_radius_f = 0.0f;

m_ao_manager.getAddedActiveObjectsAroundPos(playersao->getBasePosition(), radius_f,
player_radius_f, current_objects, added_objects);
m_ao_manager.getAddedActiveObjectsAroundPos(
playersao->getBasePosition(), playersao->getPlayer()->getName(),
radius_f, player_radius_f,
current_objects, added_objects);
}

/*
Expand All @@ -1719,6 +1721,9 @@ void ServerEnvironment::getRemovedActiveObjects(PlayerSAO *playersao, s16 radius

if (player_radius_f < 0)
player_radius_f = 0;

const std::string player_name = playersao->getPlayer()->getName();

/*
Go through current_objects; object is removed if:
- object is not found in m_active_objects (this is actually an
Expand All @@ -1743,14 +1748,12 @@ void ServerEnvironment::getRemovedActiveObjects(PlayerSAO *playersao, s16 radius
}

f32 distance_f = object->getBasePosition().getDistanceFrom(playersao->getBasePosition());
if (object->getType() == ACTIVEOBJECT_TYPE_PLAYER) {
if (distance_f <= player_radius_f || player_radius_f == 0)
continue;
} else if (distance_f <= radius_f)
continue;
bool inRange = object->getType() == ACTIVEOBJECT_TYPE_PLAYER
? distance_f <= player_radius_f || player_radius_f == 0
: distance_f <= radius_f;

// Object is no longer visible
removed_objects.push(id);
if (!inRange || !object->isObservedBy(player_name))
removed_objects.push(id); // out of range or not observed anymore
}
}

Expand Down
4 changes: 2 additions & 2 deletions src/unittest/test_serveractiveobjectmgr.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -175,12 +175,12 @@ void TestServerActiveObjectMgr::testGetAddedActiveObjectsAroundPos()

std::queue<u16> result;
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);

result = std::queue<u16>();
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);

saomgr.clear();
Expand Down

0 comments on commit 8af7937

Please sign in to comment.