mirror of
https://github.com/luanti-org/luanti.git
synced 2025-06-27 16:36:03 +00:00
Merge 77f05b5f73
into 2d36d32da8
This commit is contained in:
commit
e8b71274e6
19 changed files with 244 additions and 14 deletions
|
@ -47,6 +47,7 @@ core.features = {
|
|||
particle_blend_clip = true,
|
||||
remove_item_match_meta = true,
|
||||
httpfetch_additional_methods = true,
|
||||
object_guids = true,
|
||||
}
|
||||
|
||||
function core.has_feature(arg)
|
||||
|
|
|
@ -5810,6 +5810,8 @@ Utilities
|
|||
remove_item_match_meta = true,
|
||||
-- The HTTP API supports the HEAD and PATCH methods (5.12.0)
|
||||
httpfetch_additional_methods = true,
|
||||
-- objects have get_guid method (5.13.0)
|
||||
object_guids = true,
|
||||
}
|
||||
```
|
||||
|
||||
|
@ -7857,8 +7859,12 @@ Global tables
|
|||
* Values in this table may be modified directly.
|
||||
Note: changes to initial properties will only affect entities spawned afterwards,
|
||||
as they are only read when spawning.
|
||||
* `core.objects_by_guid`
|
||||
* Map of active object references, indexed by object GUID
|
||||
* `core.object_refs`
|
||||
* Map of object references, indexed by active object id
|
||||
* **Obsolete:** Use `core.objects_by_guid` instead.
|
||||
GUIDs are strictly more useful than active object IDs.
|
||||
* Map of active object references, indexed by active object id
|
||||
* `core.luaentities`
|
||||
* Map of Lua entities, indexed by active object id
|
||||
* `core.registered_abms`
|
||||
|
@ -8556,6 +8562,14 @@ child will follow movement and rotation of that bone.
|
|||
-- Default: false
|
||||
}
|
||||
```
|
||||
* `get_guid()`: returns a global unique identifier (a string)
|
||||
* For players, this is a player name.
|
||||
* For Lua entities, this is a uniquely generated string, guaranteed not to collide with player names.
|
||||
* example: `@bGh3p2AbRE29Mb4biqX6OA`
|
||||
* GUIDs only use printable ASCII characters.
|
||||
* GUIDs persist internally between object reloads; their format is guaranteed not to change.
|
||||
Thus you can use the GUID to identify an object in a particular world online nad offline.
|
||||
|
||||
|
||||
#### Lua entity only (no-op for other objects)
|
||||
|
||||
|
|
|
@ -596,9 +596,11 @@ Object types:
|
|||
* `s32` yaw * 1000
|
||||
|
||||
Since protocol version 37:
|
||||
* `u8` `version2` (=1)
|
||||
* `u8` `version2` (=1 or 2)
|
||||
* `s32` pitch * 1000
|
||||
* `s32` roll * 1000
|
||||
* if version2 >= 2:
|
||||
* `u8[16]` guid
|
||||
|
||||
# Itemstring Format
|
||||
|
||||
|
|
|
@ -255,3 +255,17 @@ local function test_item_drop(_, pos)
|
|||
assert(itemstack_ret:equals(itemstack_src))
|
||||
end
|
||||
unittests.register("test_item_drop", test_item_drop, {map=true})
|
||||
|
||||
local function test_entity_guid(_, pos)
|
||||
local obj0 = core.add_entity(pos, "unittests:dummy")
|
||||
local obj1 = core.add_entity(pos, "unittests:dummy")
|
||||
|
||||
assert(obj0 ~= obj1)
|
||||
assert(obj0:get_guid() ~= obj1:get_guid())
|
||||
assert(core.objects_by_guid[obj0:get_guid()] == obj0)
|
||||
assert(core.objects_by_guid[obj1:get_guid()] == obj1)
|
||||
|
||||
obj0:remove()
|
||||
obj1:remove()
|
||||
end
|
||||
unittests.register("test_entity_guid", test_entity_guid, {map=true})
|
||||
|
|
|
@ -204,3 +204,11 @@ local function run_player_hotbar_clamp_tests(player)
|
|||
player:hud_set_hotbar_itemcount(old_bar_size)
|
||||
end
|
||||
unittests.register("test_player_hotbar_clamp", run_player_hotbar_clamp_tests, {player=true})
|
||||
|
||||
--
|
||||
-- Player get GUID
|
||||
--
|
||||
local function test_player_guid_tests(player)
|
||||
assert(player:get_guid() == player:get_player_name())
|
||||
end
|
||||
unittests.register("test_player_guid", test_player_guid_tests, {player=true})
|
||||
|
|
|
@ -406,17 +406,17 @@ void ScriptApiBase::setOriginFromTableRaw(int index, const char *fxn)
|
|||
/*
|
||||
* How ObjectRefs are handled in Lua:
|
||||
* When an active object is created, an ObjectRef is created on the Lua side
|
||||
* and stored in core.object_refs[id].
|
||||
* and stored in core.object_refs[id] and in core.objects_by_guids[GUID].
|
||||
* Methods that require an ObjectRef to a certain object retrieve it from that
|
||||
* table instead of creating their own.(*)
|
||||
* When an active object is removed, the existing ObjectRef is invalidated
|
||||
* using ::set_null() and removed from the core.object_refs table.
|
||||
* using ::set_null() and removed from the core.object_refs and
|
||||
* core.object_by_guids tables.
|
||||
* (*) An exception to this are NULL ObjectRefs and anonymous ObjectRefs
|
||||
* for objects without ID.
|
||||
* It's unclear what the latter are needed for and their use is problematic
|
||||
* since we lose control over the ref and the contained pointer.
|
||||
*/
|
||||
|
||||
void ScriptApiBase::addObjectReference(ServerActiveObject *cobj)
|
||||
{
|
||||
SCRIPTAPI_PRECHECKHEADER
|
||||
|
@ -434,7 +434,20 @@ void ScriptApiBase::addObjectReference(ServerActiveObject *cobj)
|
|||
|
||||
// object_refs[id] = object
|
||||
lua_pushinteger(L, cobj->getId()); // Push id
|
||||
lua_pushvalue(L, object); // Copy object to top of stack
|
||||
lua_pushvalue(L, object);
|
||||
lua_settable(L, objectstable);
|
||||
|
||||
// Get core.objects_by_guid table
|
||||
lua_getglobal(L, "core");
|
||||
lua_getfield(L, -1, "objects_by_guid");
|
||||
luaL_checktype(L, -1, LUA_TTABLE);
|
||||
objectstable = lua_gettop(L);
|
||||
|
||||
// objects_by_guid[guid] = object
|
||||
auto guid = cobj->getGUID();
|
||||
assert(!guid.empty());
|
||||
lua_pushstring(L, guid.c_str());
|
||||
lua_pushvalue(L, object);
|
||||
lua_settable(L, objectstable);
|
||||
}
|
||||
|
||||
|
@ -445,6 +458,7 @@ void ScriptApiBase::removeObjectReference(ServerActiveObject *cobj)
|
|||
|
||||
// Get core.object_refs table
|
||||
lua_getglobal(L, "core");
|
||||
int core = lua_gettop(L);
|
||||
lua_getfield(L, -1, "object_refs");
|
||||
luaL_checktype(L, -1, LUA_TTABLE);
|
||||
int objectstable = lua_gettop(L);
|
||||
|
@ -460,6 +474,16 @@ void ScriptApiBase::removeObjectReference(ServerActiveObject *cobj)
|
|||
lua_pushinteger(L, cobj->getId()); // Push id
|
||||
lua_pushnil(L);
|
||||
lua_settable(L, objectstable);
|
||||
|
||||
// Get core.objects_by_guid
|
||||
lua_getfield(L, core, "objects_by_guid");
|
||||
luaL_checktype(L, -1, LUA_TTABLE);
|
||||
objectstable = lua_gettop(L);
|
||||
|
||||
// Set objects_by_guid[guid] = nil
|
||||
lua_pushstring(L, cobj->getGUID().c_str());
|
||||
lua_pushnil(L);
|
||||
lua_settable(L, objectstable);
|
||||
}
|
||||
|
||||
void ScriptApiBase::objectrefGetOrCreate(lua_State *L, ServerActiveObject *cobj)
|
||||
|
|
|
@ -119,6 +119,20 @@ int ObjectRef::l_is_valid(lua_State *L)
|
|||
return 1;
|
||||
}
|
||||
|
||||
// get_guid()
|
||||
int ObjectRef::l_get_guid(lua_State *L)
|
||||
{
|
||||
NO_MAP_LOCK_REQUIRED;
|
||||
ObjectRef *ref = checkObject<ObjectRef>(L, 1);
|
||||
ServerActiveObject *sao = getobject(ref);
|
||||
if (sao == nullptr)
|
||||
return 0;
|
||||
|
||||
std::string guid = sao->getGUID();
|
||||
lua_pushlstring(L, guid.c_str(), guid.size());
|
||||
return 1;
|
||||
}
|
||||
|
||||
// get_pos(self)
|
||||
int ObjectRef::l_get_pos(lua_State *L)
|
||||
{
|
||||
|
@ -2838,6 +2852,7 @@ luaL_Reg ObjectRef::methods[] = {
|
|||
// ServerActiveObject
|
||||
luamethod(ObjectRef, remove),
|
||||
luamethod(ObjectRef, is_valid),
|
||||
luamethod(ObjectRef, get_guid),
|
||||
luamethod_aliased(ObjectRef, get_pos, getpos),
|
||||
luamethod_aliased(ObjectRef, set_pos, setpos),
|
||||
luamethod(ObjectRef, add_pos),
|
||||
|
|
|
@ -61,6 +61,9 @@ private:
|
|||
// is_valid(self)
|
||||
static int l_is_valid(lua_State *L);
|
||||
|
||||
// get_guid()
|
||||
static int l_get_guid(lua_State *L);
|
||||
|
||||
// get_pos(self)
|
||||
static int l_get_pos(lua_State *L);
|
||||
|
||||
|
|
|
@ -62,6 +62,9 @@ ServerScripting::ServerScripting(Server* server):
|
|||
lua_newtable(L);
|
||||
lua_setfield(L, -2, "object_refs");
|
||||
|
||||
lua_newtable(L);
|
||||
lua_setfield(L, -2, "objects_by_guid");
|
||||
|
||||
lua_newtable(L);
|
||||
lua_setfield(L, -2, "luaentities");
|
||||
|
||||
|
|
|
@ -50,7 +50,14 @@ LuaEntitySAO::LuaEntitySAO(ServerEnvironment *env, v3f pos, const std::string &d
|
|||
rotation.X = readF1000(is);
|
||||
rotation.Z = readF1000(is);
|
||||
|
||||
// if (version2 < 2)
|
||||
if (version2 < 2) {
|
||||
m_guid = env->getGUIDGenerator().next();
|
||||
break;
|
||||
}
|
||||
|
||||
m_guid.deSerialize(is);
|
||||
|
||||
// if (version2 < 3)
|
||||
// break;
|
||||
// <read new values>
|
||||
break;
|
||||
|
@ -70,6 +77,14 @@ LuaEntitySAO::LuaEntitySAO(ServerEnvironment *env, v3f pos, const std::string &d
|
|||
m_rotation = rotation;
|
||||
}
|
||||
|
||||
LuaEntitySAO::LuaEntitySAO(ServerEnvironment *env, v3f pos, const std::string &name,
|
||||
const std::string &state) :
|
||||
UnitSAO(env, pos),
|
||||
m_init_name(name), m_init_state(state),
|
||||
m_guid(env->getGUIDGenerator().next())
|
||||
{
|
||||
}
|
||||
|
||||
LuaEntitySAO::~LuaEntitySAO()
|
||||
{
|
||||
if(m_registered){
|
||||
|
@ -294,11 +309,13 @@ void LuaEntitySAO::getStaticData(std::string *result) const
|
|||
writeF1000(os, m_rotation.Y);
|
||||
|
||||
// version2. Increase this variable for new values
|
||||
writeU8(os, 1); // PROTOCOL_VERSION >= 37
|
||||
writeU8(os, 2);
|
||||
|
||||
writeF1000(os, m_rotation.X);
|
||||
writeF1000(os, m_rotation.Z);
|
||||
|
||||
m_guid.serialize(os);
|
||||
|
||||
// <write new values>
|
||||
|
||||
*result = os.str();
|
||||
|
@ -414,6 +431,13 @@ u16 LuaEntitySAO::getHP() const
|
|||
return m_hp;
|
||||
}
|
||||
|
||||
std::string LuaEntitySAO::getGUID()
|
||||
{
|
||||
// The "@" ensures that entity GUIDs are easily recognizable
|
||||
// and makes it obvious that they can't collide with player names.
|
||||
return "@" + m_guid.base64();
|
||||
}
|
||||
|
||||
void LuaEntitySAO::setVelocity(v3f velocity)
|
||||
{
|
||||
m_velocity = velocity;
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
#pragma once
|
||||
|
||||
#include "unit_sao.h"
|
||||
#include "util/guid.h"
|
||||
|
||||
class LuaEntitySAO : public UnitSAO
|
||||
{
|
||||
|
@ -15,11 +16,7 @@ public:
|
|||
LuaEntitySAO(ServerEnvironment *env, v3f pos, const std::string &data);
|
||||
// Used by the Lua API
|
||||
LuaEntitySAO(ServerEnvironment *env, v3f pos, const std::string &name,
|
||||
const std::string &state) :
|
||||
UnitSAO(env, pos),
|
||||
m_init_name(name), m_init_state(state)
|
||||
{
|
||||
}
|
||||
const std::string &state);
|
||||
~LuaEntitySAO();
|
||||
|
||||
ActiveObjectType getType() const { return ACTIVEOBJECT_TYPE_LUAENTITY; }
|
||||
|
@ -47,6 +44,7 @@ public:
|
|||
|
||||
void setHP(s32 hp, const PlayerHPChangeReason &reason);
|
||||
u16 getHP() const;
|
||||
std::string getGUID() override;
|
||||
|
||||
/* LuaEntitySAO-specific */
|
||||
void setVelocity(v3f velocity);
|
||||
|
@ -86,6 +84,8 @@ private:
|
|||
std::string m_init_state;
|
||||
bool m_registered = false;
|
||||
|
||||
MyGUID m_guid;
|
||||
|
||||
v3f m_velocity;
|
||||
v3f m_acceleration;
|
||||
|
||||
|
|
|
@ -14,6 +14,7 @@ PlayerSAO::PlayerSAO(ServerEnvironment *env_, RemotePlayer *player_, session_t p
|
|||
bool is_singleplayer):
|
||||
UnitSAO(env_, v3f(0,0,0)),
|
||||
m_player(player_),
|
||||
m_player_name(player_->getName()),
|
||||
m_peer_id_initial(peer_id_),
|
||||
m_is_singleplayer(is_singleplayer)
|
||||
{
|
||||
|
|
|
@ -78,6 +78,7 @@ public:
|
|||
void addPos(const v3f &added_pos) override;
|
||||
void moveTo(v3f pos, bool continuous) override;
|
||||
void setPlayerYaw(const float yaw);
|
||||
std::string getGUID() override { return m_player_name; }
|
||||
// Data should not be sent at player initialization
|
||||
void setPlayerYawAndSend(const float yaw);
|
||||
void setLookPitch(const float pitch);
|
||||
|
@ -182,6 +183,8 @@ private:
|
|||
std::string generateUpdatePhysicsOverrideCommand() const;
|
||||
|
||||
RemotePlayer *m_player = nullptr;
|
||||
// Replace with m_player->getName method from m_player cause SIGSEG
|
||||
std::string m_player_name; ///< used as GUID
|
||||
session_t m_peer_id_initial = 0; ///< only used to initialize RemotePlayer
|
||||
|
||||
// Cheat prevention
|
||||
|
|
|
@ -141,6 +141,10 @@ public:
|
|||
virtual u16 getHP() const
|
||||
{ return 0; }
|
||||
|
||||
/// @brief Returns an unique ID for this object (persistent across unload, server restarts).
|
||||
/// @note Because these strings are very short, copying them is not expensive.
|
||||
virtual std::string getGUID() = 0;
|
||||
|
||||
virtual void setArmorGroups(const ItemGroupList &armor_groups)
|
||||
{}
|
||||
virtual const ItemGroupList &getArmorGroups() const
|
||||
|
|
|
@ -10,6 +10,8 @@
|
|||
#include "activeobject.h"
|
||||
#include "environment.h"
|
||||
#include "servermap.h"
|
||||
#include "util/guid.h"
|
||||
#include "map.h"
|
||||
#include "settings.h"
|
||||
#include "server/activeobjectmgr.h"
|
||||
#include "server/blockmodifier.h"
|
||||
|
@ -123,6 +125,9 @@ public:
|
|||
float getSendRecommendedInterval()
|
||||
{ return m_recommended_send_interval; }
|
||||
|
||||
GUIDGenerator & getGUIDGenerator()
|
||||
{ return m_guid_generator; }
|
||||
|
||||
// Save players
|
||||
void saveLoadedPlayers(bool force = false);
|
||||
void savePlayer(RemotePlayer *player);
|
||||
|
@ -357,6 +362,7 @@ private:
|
|||
server::ActiveObjectMgr m_ao_manager;
|
||||
// on_mapblocks_changed map event receiver
|
||||
OnMapblocksChangedReceiver m_on_mapblocks_changed_receiver;
|
||||
GUIDGenerator m_guid_generator;
|
||||
// Outgoing network message buffer for active objects
|
||||
std::queue<ActiveObjectMessage> m_active_object_messages;
|
||||
// Some timers
|
||||
|
|
|
@ -2,16 +2,31 @@
|
|||
// SPDX-License-Identifier: LGPL-2.1-or-later
|
||||
// Copyright (C) 2022 Minetest core developers & community
|
||||
|
||||
#include "util/guid.h"
|
||||
#include "serverenvironment.h"
|
||||
#include <server/serveractiveobject.h>
|
||||
#include <string>
|
||||
|
||||
class MockServerActiveObject : public ServerActiveObject
|
||||
{
|
||||
public:
|
||||
MockServerActiveObject(ServerEnvironment *env = nullptr, v3f p = v3f()) :
|
||||
ServerActiveObject(env, p) {}
|
||||
ServerActiveObject(env, p)
|
||||
{
|
||||
if (env)
|
||||
m_guid = "mock:" + env->getGUIDGenerator().next().base64();
|
||||
}
|
||||
|
||||
virtual ActiveObjectType getType() const { return ACTIVEOBJECT_TYPE_TEST; }
|
||||
virtual bool getCollisionBox(aabb3f *toset) const { return false; }
|
||||
virtual bool getSelectionBox(aabb3f *toset) const { return false; }
|
||||
virtual bool collideWithObjects() const { return false; }
|
||||
virtual std::string getGUID()
|
||||
{
|
||||
assert(!m_guid.empty());
|
||||
return m_guid;
|
||||
}
|
||||
|
||||
private:
|
||||
std::string m_guid;
|
||||
};
|
||||
|
|
|
@ -5,6 +5,7 @@ set(util_SRCS
|
|||
${CMAKE_CURRENT_SOURCE_DIR}/colorize.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/directiontables.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/enriched_string.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/guid.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/hashing.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/ieee_float.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/metricsbackend.cpp
|
||||
|
|
45
src/util/guid.cpp
Normal file
45
src/util/guid.cpp
Normal file
|
@ -0,0 +1,45 @@
|
|||
// Luanti
|
||||
// SPDX-License-Identifier: LGPL-2.1-or-later
|
||||
// Copyright (C) 2024 SFENCE
|
||||
|
||||
#include "guid.h"
|
||||
#include <cstring>
|
||||
#include <sstream>
|
||||
#include <string_view>
|
||||
|
||||
#include "exceptions.h"
|
||||
#include "util/base64.h"
|
||||
#include "log.h"
|
||||
|
||||
std::string MyGUID::base64() const
|
||||
{
|
||||
return base64_encode(std::string_view(&bytes[0], bytes.size()));
|
||||
}
|
||||
|
||||
void MyGUID::serialize(std::ostream &os) const
|
||||
{
|
||||
os.write(&bytes[0], bytes.size());
|
||||
}
|
||||
|
||||
void MyGUID::deSerialize(std::istream &is)
|
||||
{
|
||||
is.read(&bytes[0], bytes.size());
|
||||
}
|
||||
|
||||
GUIDGenerator::GUIDGenerator() :
|
||||
m_uniform(0, UINT64_MAX)
|
||||
{
|
||||
std::random_device rd;
|
||||
m_rand.seed(rd());
|
||||
}
|
||||
|
||||
MyGUID GUIDGenerator::next()
|
||||
{
|
||||
u64 rand1 = m_uniform(m_rand);
|
||||
u64 rand2 = m_uniform(m_rand);
|
||||
|
||||
std::array<char, 16> bytes;
|
||||
std::memcpy(&bytes[0], reinterpret_cast<char*>(&rand1), 8);
|
||||
std::memcpy(&bytes[8], reinterpret_cast<char*>(&rand2), 8);
|
||||
return MyGUID{bytes};
|
||||
}
|
47
src/util/guid.h
Normal file
47
src/util/guid.h
Normal file
|
@ -0,0 +1,47 @@
|
|||
// Luanti
|
||||
// SPDX-License-Identifier: LGPL-2.1-or-later
|
||||
// Copyright (C) 2024 SFENCE
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "irrlichttypes.h"
|
||||
#include "util/basic_macros.h"
|
||||
#include <random>
|
||||
#include <string>
|
||||
#include <array>
|
||||
|
||||
class ServerEnvironment;
|
||||
|
||||
/**
|
||||
* A global unique identifier.
|
||||
* It is global because it stays valid forever.
|
||||
* It is unique because there are no collisions.
|
||||
*/
|
||||
struct MyGUID
|
||||
{
|
||||
std::array<char, 16> bytes;
|
||||
|
||||
std::string base64() const;
|
||||
void serialize(std::ostream &os) const;
|
||||
void deSerialize(std::istream &is);
|
||||
};
|
||||
|
||||
class GUIDGenerator
|
||||
{
|
||||
DISABLE_CLASS_COPY(GUIDGenerator)
|
||||
|
||||
public:
|
||||
|
||||
GUIDGenerator();
|
||||
|
||||
/**
|
||||
* Generates the next GUID, which it will never return again.
|
||||
* @return the new GUID
|
||||
*/
|
||||
MyGUID next();
|
||||
|
||||
private:
|
||||
|
||||
std::mt19937_64 m_rand;
|
||||
std::uniform_int_distribution<u64> m_uniform;
|
||||
};
|
Loading…
Add table
Add a link
Reference in a new issue