Tool specific pointing and blocking pointable type (#13992)

This commit is contained in:
cx384 2024-01-22 18:27:08 +01:00 committed by GitHub
parent fb461d21a5
commit 5958714309
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
39 changed files with 676 additions and 67 deletions

@ -33,6 +33,8 @@ core.features = {
random_state_restore = true,
after_order_expiry_registration = true,
wallmounted_rotate = true,
item_specific_pointabilities = true,
blocking_pointability_type = true,
}
function core.has_feature(arg)

@ -5298,6 +5298,10 @@ Utilities
-- wallmounted nodes mounted at floor or ceiling may additionally
-- be rotated by 90° with special param2 values (5.9.0)
wallmounted_rotate = true,
-- Availability of the `pointabilities` property in the item definition (5.9.0)
item_specific_pointabilities = true,
-- Nodes `pointable` property can be `"blocking"` (5.9.0)
blocking_pointability_type = true,
}
```
@ -8382,7 +8386,9 @@ Player properties need to be saved manually.
pointable = true,
-- Whether the object can be pointed at
-- Can be `true` if it is pointable, `false` if it can be pointed through,
-- or `"blocking"` if it is pointable but not selectable.
-- Can be overridden by the `pointabilities` of the held item.
visual = "cube" / "sprite" / "upright_sprite" / "mesh" / "wielditem" / "item",
-- "cube" is a node-sized cube.
@ -8746,6 +8752,27 @@ Used by `minetest.register_node`, `minetest.register_craftitem`, and
-- If true, item can point to all liquid nodes (`liquidtype ~= "none"`),
-- even those for which `pointable = false`
pointabilities = {
nodes = {
["default:stone"] = "blocking",
["group:leaves"] = false,
},
objects = {
["modname:entityname"] = true,
["group:ghosty"] = true, -- (an armor group)
}
},
-- Contains lists to override the `pointable` property of pointed nodes and objects.
-- The index can be a node/entity name or a group with the prefix `"group:"`.
-- (For objects `armor_groups` are used and for players the entity name is irrelevant.)
-- If multiple fields fit, the following priority order is applied:
-- value of matching node/entity name
-- `true` for any group
-- `false` for any group
-- `"blocking"` for any group
-- `liquids_pointable` if it is a liquid node
-- `pointable` property of the node or object
light_source = 0,
-- When used for nodes: Defines amount of light emitted by node.
-- Otherwise: Defines texture glow when viewed as a dropped item
@ -8971,7 +8998,11 @@ Used by `minetest.register_node`.
walkable = true, -- If true, objects collide with node
pointable = true, -- If true, can be pointed at
pointable = true,
-- Can be `true` if it is pointable, `false` if it can be pointed through,
-- or `"blocking"` if it is pointable but not selectable.
-- Can be overridden by the `pointabilities` of the held item.
-- A client may be able to point non-pointable nodes, since it isn't checked server-side.
diggable = true, -- If false, can never be dug

@ -1,3 +1,4 @@
dofile(minetest.get_modpath("testentities").."/visuals.lua")
dofile(minetest.get_modpath("testentities").."/selectionbox.lua")
dofile(minetest.get_modpath("testentities").."/armor.lua")
dofile(minetest.get_modpath("testentities").."/pointable.lua")

@ -0,0 +1,23 @@
-- Pointability test Entities
-- Register wrapper for compactness
local function register_pointable_testentity(name, pointable)
local texture = "testnodes_"..name..".png"
minetest.register_entity("testentities:"..name, {
initial_properties = {
visual = "cube",
visual_size = {x = 0.6, y = 0.6, z = 0.6},
textures = {
texture, texture, texture, texture, texture, texture
},
pointable = pointable,
},
on_activate = function(self)
self.object:set_armor_groups({[name.."_test"] = 1})
end
})
end
register_pointable_testentity("pointable", true)
register_pointable_testentity("not_pointable", false)
register_pointable_testentity("blocking_pointable", "blocking")

@ -663,3 +663,23 @@ minetest.register_node("testnodes:post_effect_color_shaded_true", {
is_ground_content = false,
groups = {dig_immediate=3},
})
-- Pointability
-- Register wrapper for compactness
local function register_pointable_test_node(name, description, pointable)
local texture = "testnodes_"..name..".png"
minetest.register_node("testnodes:"..name, {
description = S(description),
tiles = {texture},
drawtype = "glasslike_framed",
paramtype = "light",
walkable = false,
pointable = pointable,
groups = {dig_immediate=3, [name.."_test"]=1},
})
end
register_pointable_test_node("pointable", "Pointable Node", true)
register_pointable_test_node("not_pointable", "Not Pointable Node", false)
register_pointable_test_node("blocking_pointable", "Blocking Pointable Node", "blocking")

Binary file not shown.

After

Width:  |  Height:  |  Size: 150 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 152 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 144 B

@ -7,6 +7,20 @@ dofile(minetest.get_modpath("testtools") .. "/light.lua")
dofile(minetest.get_modpath("testtools") .. "/privatizer.lua")
dofile(minetest.get_modpath("testtools") .. "/particles.lua")
local pointabilities_nodes = {
nodes = {
["group:blocking_pointable_test"] = true,
["group:not_pointable_test"] = true,
},
}
local pointabilities_objects = {
objects = {
["group:blocking_pointable_test"] = true,
["group:not_pointable_test"] = true,
},
}
minetest.register_tool("testtools:param2tool", {
description = S("Param2 Tool") .."\n"..
S("Modify param2 value of nodes") .."\n"..
@ -16,6 +30,7 @@ minetest.register_tool("testtools:param2tool", {
S("Sneak+Place: -8"),
inventory_image = "testtools_param2tool.png",
groups = { testtool = 1, disable_repair = 1 },
pointabilities = pointabilities_nodes,
on_use = function(itemstack, user, pointed_thing)
local pos = minetest.get_pointed_thing_position(pointed_thing)
if pointed_thing.type ~= "node" or (not pos) then
@ -58,6 +73,7 @@ minetest.register_tool("testtools:node_setter", {
S("Place in air: Manually select a node"),
inventory_image = "testtools_node_setter.png",
groups = { testtool = 1, disable_repair = 1 },
pointabilities = pointabilities_nodes,
on_use = function(itemstack, user, pointed_thing)
local pos = minetest.get_pointed_thing_position(pointed_thing)
if pointed_thing.type == "nothing" then
@ -118,6 +134,10 @@ minetest.register_tool("testtools:remover", {
S("Punch: Remove pointed node or object"),
inventory_image = "testtools_remover.png",
groups = { testtool = 1, disable_repair = 1 },
pointabilities = {
nodes = pointabilities_nodes.nodes,
objects = pointabilities_objects.objects,
},
on_use = function(itemstack, user, pointed_thing)
local pos = minetest.get_pointed_thing_position(pointed_thing)
if pointed_thing.type == "node" and pos ~= nil then
@ -139,6 +159,7 @@ minetest.register_tool("testtools:falling_node_tool", {
S("Place: Move pointed node 2 units upwards, then make it fall"),
inventory_image = "testtools_falling_node_tool.png",
groups = { testtool = 1, disable_repair = 1 },
pointabilities = pointabilities_nodes,
on_place = function(itemstack, user, pointed_thing)
-- Teleport node 1-2 units upwards (if possible) and make it fall
local pos = minetest.get_pointed_thing_position(pointed_thing)
@ -192,6 +213,7 @@ minetest.register_tool("testtools:rotator", {
S("Aux1+Punch: Roll"),
inventory_image = "testtools_entity_rotator.png",
groups = { testtool = 1, disable_repair = 1 },
pointabilities = pointabilities_objects,
on_use = function(itemstack, user, pointed_thing)
if pointed_thing.type ~= "object" then
return
@ -250,6 +272,7 @@ minetest.register_tool("testtools:object_mover", {
S("Sneak+Place: Decrease distance"),
inventory_image = "testtools_object_mover.png",
groups = { testtool = 1, disable_repair = 1 },
pointabilities = pointabilities_objects,
on_place = mover_config,
on_secondary_use = mover_config,
on_use = function(itemstack, user, pointed_thing)
@ -296,6 +319,7 @@ minetest.register_tool("testtools:entity_scaler", {
S("Sneak+Punch: Decrease scale"),
inventory_image = "testtools_entity_scaler.png",
groups = { testtool = 1, disable_repair = 1 },
pointabilities = pointabilities_objects,
on_use = function(itemstack, user, pointed_thing)
if pointed_thing.type ~= "object" then
return
@ -355,6 +379,7 @@ minetest.register_tool("testtools:branding_iron", {
S("Devices that accept the returned name also accept \"player:<playername>\" for players."),
inventory_image = "testtools_branding_iron.png",
groups = { testtool = 1, disable_repair = 1 },
pointabilities = pointabilities_objects,
on_use = function(_itemstack, user, pointed_thing)
local obj
local msg
@ -499,6 +524,7 @@ minetest.register_tool("testtools:object_editor", {
S("Punch air: Edit yourself"),
inventory_image = "testtools_object_editor.png",
groups = { testtool = 1, disable_repair = 1 },
pointabilities = pointabilities_objects,
on_use = function(itemstack, user, pointed_thing)
if user and user:is_player() then
local name = user:get_player_name()
@ -586,6 +612,7 @@ minetest.register_tool("testtools:object_attacher", {
S("Aux1+Sneak+Place: Decrease attachment rotation"),
inventory_image = "testtools_object_attacher.png",
groups = { testtool = 1, disable_repair = 1 },
pointabilities = pointabilities_objects,
on_place = attacher_config,
on_secondary_use = attacher_config,
on_use = function(itemstack, user, pointed_thing)
@ -679,6 +706,7 @@ minetest.register_tool("testtools:children_getter", {
S("Punch air to show your own 'children'"),
inventory_image = "testtools_children_getter.png",
groups = { testtool = 1, disable_repair = 1 },
pointabilities = pointabilities_objects,
on_use = function(itemstack, user, pointed_thing)
if user and user:is_player() then
local name = user:get_player_name()
@ -998,3 +1026,41 @@ minetest.register_on_leaveplayer(function(player)
meta_latest_keylist[name] = nil
node_meta_posses[name] = nil
end)
-- Pointing Staffs
minetest.register_tool("testtools:blocked_pointing_staff", {
description = S("Blocked Pointing Staff").."\n"..
S("Can point the Blocking Pointable Node/Object and "..
"the Pointable Node/Object is point blocking."),
inventory_image = "testtools_blocked_pointing_staff.png",
pointabilities = {
nodes = {
["testnodes:blocking_pointable"] = true,
["group:pointable_test"] = "blocking"
},
objects = {
["testentities:blocking_pointable"] = true,
["group:pointable_test"] = "blocking"
}
}
})
minetest.register_tool("testtools:ultimate_pointing_staff", {
description = S("Ultimate Pointing Staff").."\n"..
S("Can point all pointable test nodes, objects and liquids."),
inventory_image = "testtools_ultimate_pointing_staff.png",
liquids_pointable = true,
pointabilities = {
nodes = {
["group:blocking_pointable_test"] = true,
["group:pointable_test"] = true,
["testnodes:not_pointable"] = true
},
objects = {
["group:blocking_pointable_test"] = true,
["group:pointable_test"] = true,
["testentities:not_pointable"] = true
}
}
})

Binary file not shown.

After

Width:  |  Height:  |  Size: 136 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 136 B

@ -489,7 +489,8 @@ ClientEnvEvent ClientEnvironment::getClientEnvEvent()
void ClientEnvironment::getSelectedActiveObjects(
const core::line3d<f32> &shootline_on_map,
std::vector<PointedThing> &objects)
std::vector<PointedThing> &objects,
const std::optional<Pointabilities> &pointabilities)
{
auto allObjects = m_ao_manager.getActiveSelectableObjects(shootline_on_map);
const v3f line_vector = shootline_on_map.getVector();
@ -516,9 +517,23 @@ void ClientEnvironment::getSelectedActiveObjects(
current_raw_normal = current_normal;
}
if (collision) {
current_intersection += obj->getPosition();
objects.emplace_back(obj->getId(), current_intersection, current_normal, current_raw_normal,
(current_intersection - shootline_on_map.start).getLengthSQ());
PointabilityType pointable;
if (pointabilities) {
if (gcao->isPlayer()) {
pointable = pointabilities->matchPlayer(gcao->getGroups()).value_or(
gcao->getProperties().pointable);
} else {
pointable = pointabilities->matchObject(gcao->getName(),
gcao->getGroups()).value_or(gcao->getProperties().pointable);
}
} else {
pointable = gcao->getProperties().pointable;
}
if (pointable != PointabilityType::POINTABLE_NOT) {
current_intersection += obj->getPosition();
objects.emplace_back(obj->getId(), current_intersection, current_normal, current_raw_normal,
(current_intersection - shootline_on_map.start).getLengthSQ(), pointable);
}
}
}
}

@ -131,7 +131,8 @@ public:
virtual void getSelectedActiveObjects(
const core::line3d<f32> &shootline_on_map,
std::vector<PointedThing> &objects
std::vector<PointedThing> &objects,
const std::optional<Pointabilities> &pointabilities
);
const std::set<std::string> &getPlayerNames() { return m_player_names; }

@ -411,8 +411,7 @@ GenericCAO::~GenericCAO()
bool GenericCAO::getSelectionBox(aabb3f *toset) const
{
if (!m_prop.is_visible || !m_is_visible || m_is_local_player
|| !m_prop.pointable) {
if (!m_prop.is_visible || !m_is_visible || m_is_local_player) {
return false;
}
*toset = m_selection_box;

@ -174,6 +174,8 @@ public:
inline const ObjectProperties &getProperties() const { return m_prop; }
inline const std::string &getName() const { return m_name; }
scene::ISceneNode *getSceneNode() const override;
scene::IAnimatedMeshSceneNode *getAnimatedMeshSceneNode() const override;
@ -208,6 +210,11 @@ public:
return m_is_local_player;
}
inline bool isPlayer() const
{
return m_is_player;
}
inline bool isVisible() const
{
return m_is_visible;

@ -841,6 +841,7 @@ protected:
* the camera position. This also gives the maximal distance
* of the search.
* @param[in] liquids_pointable if false, liquids are ignored
* @param[in] pointabilities item specific pointable overriding
* @param[in] look_for_object if false, objects are ignored
* @param[in] camera_offset offset of the camera
* @param[out] selected_object the selected object or
@ -848,6 +849,7 @@ protected:
*/
PointedThing updatePointedThing(
const core::line3d<f32> &shootline, bool liquids_pointable,
const std::optional<Pointabilities> &pointabilities,
bool look_for_object, const v3s16 &camera_offset);
void handlePointingAtNothing(const ItemStack &playerItem);
void handlePointingAtNode(const PointedThing &pointed,
@ -3343,6 +3345,7 @@ void Game::processPlayerInteraction(f32 dtime, bool show_hud)
PointedThing pointed = updatePointedThing(shootline,
selected_def.liquids_pointable,
selected_def.pointabilities,
!runData.btn_down_for_dig,
camera_offset);
@ -3454,6 +3457,7 @@ void Game::processPlayerInteraction(f32 dtime, bool show_hud)
PointedThing Game::updatePointedThing(
const core::line3d<f32> &shootline,
bool liquids_pointable,
const std::optional<Pointabilities> &pointabilities,
bool look_for_object,
const v3s16 &camera_offset)
{
@ -3470,7 +3474,7 @@ PointedThing Game::updatePointedThing(
runData.selected_object = NULL;
hud->pointing_at_object = false;
RaycastState s(shootline, look_for_object, liquids_pointable);
RaycastState s(shootline, look_for_object, liquids_pointable, pointabilities);
PointedThing result;
env.continueRaycast(&s, &result);
if (result.type == POINTEDTHING_OBJECT) {

@ -102,24 +102,33 @@ bool Environment::line_of_sight(v3f pos1, v3f pos2, v3s16 *p)
}
/*
Check if a node is pointable
Check how a node can be pointed at
*/
inline static bool isPointableNode(const MapNode &n,
const NodeDefManager *nodedef , bool liquids_pointable)
inline static PointabilityType isPointableNode(const MapNode &n,
const NodeDefManager *nodedef, bool liquids_pointable,
const std::optional<Pointabilities> &pointabilities)
{
const ContentFeatures &features = nodedef->get(n);
return features.pointable ||
(liquids_pointable && features.isLiquid());
if (pointabilities) {
std::optional<PointabilityType> match =
pointabilities->matchNode(features.name, features.groups);
if (match)
return match.value();
}
if (features.isLiquid() && liquids_pointable)
return PointabilityType::POINTABLE;
return features.pointable;
}
void Environment::continueRaycast(RaycastState *state, PointedThing *result)
void Environment::continueRaycast(RaycastState *state, PointedThing *result_p)
{
const NodeDefManager *nodedef = getMap().getNodeDefManager();
if (state->m_initialization_needed) {
// Add objects
if (state->m_objects_pointable) {
std::vector<PointedThing> found;
getSelectedActiveObjects(state->m_shootline, found);
getSelectedActiveObjects(state->m_shootline, found, state->m_pointabilities);
for (const PointedThing &pointed : found) {
state->m_found.push(pointed);
}
@ -184,10 +193,15 @@ void Environment::continueRaycast(RaycastState *state, PointedThing *result)
bool is_valid_position;
n = map.getNode(np, &is_valid_position);
if (!(is_valid_position && isPointableNode(n, nodedef,
state->m_liquids_pointable))) {
if (!is_valid_position)
continue;
PointabilityType pointable = isPointableNode(n, nodedef,
state->m_liquids_pointable,
state->m_pointabilities);
// If it can be pointed through skip
if (pointable == PointabilityType::POINTABLE_NOT)
continue;
}
PointedThing result;
@ -234,6 +248,7 @@ void Environment::continueRaycast(RaycastState *state, PointedThing *result)
if (!is_colliding) {
continue;
}
result.pointability = pointable;
result.type = POINTEDTHING_NODE;
result.node_undersurface = np;
result.distanceSq = min_distance_sq;
@ -275,12 +290,16 @@ void Environment::continueRaycast(RaycastState *state, PointedThing *result)
state->m_previous_node = state->m_iterator.m_current_node_pos;
state->m_iterator.next();
}
// Return empty PointedThing if nothing left on the ray
// Return empty PointedThing if nothing left on the ray or it is blocking pointable
if (state->m_found.empty()) {
result->type = POINTEDTHING_NOTHING;
result_p->type = POINTEDTHING_NOTHING;
} else {
*result = state->m_found.top();
*result_p = state->m_found.top();
state->m_found.pop();
if (result_p->pointability == PointabilityType::POINTABLE_BLOCKING) {
result_p->type = POINTEDTHING_NOTHING;
}
}
}

@ -34,6 +34,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include <map>
#include <atomic>
#include <mutex>
#include <optional>
#include "irr_v3d.h"
#include "util/basic_macros.h"
#include "line3d.h"
@ -42,6 +43,7 @@ class IGameDef;
class Map;
struct PointedThing;
class RaycastState;
struct Pointabilities;
class Environment
{
@ -97,7 +99,8 @@ public:
* @param[out] objects found objects
*/
virtual void getSelectedActiveObjects(const core::line3d<f32> &shootline_on_map,
std::vector<PointedThing> &objects) = 0;
std::vector<PointedThing> &objects,
const std::optional<Pointabilities> &pointabilities) = 0;
/*!
* Returns the next node or object the shootline meets.

@ -122,6 +122,7 @@ ItemDefinition& ItemDefinition::operator=(const ItemDefinition &def)
stack_max = def.stack_max;
usable = def.usable;
liquids_pointable = def.liquids_pointable;
pointabilities = def.pointabilities;
if (def.tool_capabilities)
tool_capabilities = new ToolCapabilities(*def.tool_capabilities);
groups = def.groups;
@ -167,6 +168,7 @@ void ItemDefinition::reset()
stack_max = 99;
usable = false;
liquids_pointable = false;
pointabilities = std::nullopt;
delete tool_capabilities;
tool_capabilities = NULL;
groups.clear();
@ -241,6 +243,14 @@ void ItemDefinition::serialize(std::ostream &os, u16 protocol_version) const
writeU8(os, wallmounted_rotate_vertical);
touch_interaction.serialize(os);
std::string pointabilities_s;
if (pointabilities) {
std::ostringstream tmp_os(std::ios::binary);
pointabilities->serialize(tmp_os);
pointabilities_s = tmp_os.str();
}
os << serializeString16(pointabilities_s);
}
void ItemDefinition::deSerialize(std::istream &is, u16 protocol_version)
@ -316,6 +326,13 @@ void ItemDefinition::deSerialize(std::istream &is, u16 protocol_version)
wallmounted_rotate_vertical = readU8(is); // 0 if missing
touch_interaction.deSerialize(is);
std::string pointabilities_s = deSerializeString16(is);
if (!pointabilities_s.empty()) {
std::istringstream tmp_is(pointabilities_s, std::ios::binary);
pointabilities = std::make_optional<Pointabilities>();
pointabilities->deSerialize(tmp_is);
}
} catch(SerializationError &e) {};
}

@ -28,6 +28,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include "itemgroup.h"
#include "sound.h"
#include "texture_override.h" // TextureOverride
#include "util/pointabilities.h"
class IGameDef;
class Client;
struct ToolCapabilities;
@ -97,8 +98,11 @@ struct ItemDefinition
u16 stack_max;
bool usable;
bool liquids_pointable;
// May be NULL. If non-NULL, deleted by destructor
std::optional<Pointabilities> pointabilities;
// They may be NULL. If non-NULL, deleted by destructor
ToolCapabilities *tool_capabilities;
ItemGroupList groups;
SoundSpec sound_place;
SoundSpec sound_place_failed;

@ -386,7 +386,7 @@ void ContentFeatures::reset()
light_propagates = false;
sunlight_propagates = false;
walkable = true;
pointable = true;
pointable = PointabilityType::POINTABLE;
diggable = true;
climbable = false;
buildable_to = false;
@ -504,7 +504,7 @@ void ContentFeatures::serialize(std::ostream &os, u16 protocol_version) const
// interaction
writeU8(os, walkable);
writeU8(os, pointable);
Pointabilities::serializePointabilityType(os, pointable);
writeU8(os, diggable);
writeU8(os, climbable);
writeU8(os, buildable_to);
@ -617,7 +617,7 @@ void ContentFeatures::deSerialize(std::istream &is, u16 protocol_version)
// interaction
walkable = readU8(is);
pointable = readU8(is);
pointable = Pointabilities::deSerializePointabilityType(is);
diggable = readU8(is);
climbable = readU8(is);
buildable_to = readU8(is);
@ -1083,7 +1083,7 @@ void NodeDefManager::clear()
f.light_propagates = true;
f.sunlight_propagates = true;
f.walkable = false;
f.pointable = false;
f.pointable = PointabilityType::POINTABLE_NOT;
f.diggable = false;
f.buildable_to = true;
f.floodable = true;
@ -1104,7 +1104,7 @@ void NodeDefManager::clear()
f.light_propagates = false;
f.sunlight_propagates = false;
f.walkable = false;
f.pointable = false;
f.pointable = PointabilityType::POINTABLE_NOT;
f.diggable = false;
f.buildable_to = true; // A way to remove accidental CONTENT_IGNOREs
f.is_ground_content = true;

@ -35,6 +35,7 @@ class Client;
#include "constants.h" // BS
#include "texture_override.h" // TextureOverride
#include "tileanimation.h"
#include "util/pointabilities.h"
class IItemDefManager;
class ITextureSource;
@ -395,8 +396,8 @@ struct ContentFeatures
// This is used for collision detection.
// Also for general solidness queries.
bool walkable;
// Player can point to these
bool pointable;
// Player can point to these, point through or it is blocking
PointabilityType pointable;
// Player can dig these
bool diggable;
// Player can climb these

@ -73,7 +73,7 @@ std::string ObjectProperties::dump()
os << ", selectionbox=" << selectionbox.MinEdge << "," << selectionbox.MaxEdge;
os << ", rotate_selectionbox=" << rotate_selectionbox;
os << ", pointable=" << pointable;
os << ", pointable=" << Pointabilities::toStringPointabilityType(pointable);
os << ", static_save=" << static_save;
os << ", eye_height=" << eye_height;
os << ", zoom_fov=" << zoom_fov;
@ -127,7 +127,7 @@ void ObjectProperties::serialize(std::ostream &os) const
writeV3F32(os, collisionbox.MaxEdge);
writeV3F32(os, selectionbox.MinEdge);
writeV3F32(os, selectionbox.MaxEdge);
writeU8(os, pointable);
Pointabilities::serializePointabilityType(os, pointable);
os << serializeString16(visual);
writeV3F32(os, visual_size);
writeU16(os, textures.size());
@ -188,7 +188,7 @@ void ObjectProperties::deSerialize(std::istream &is)
collisionbox.MaxEdge = readV3F32(is);
selectionbox.MinEdge = readV3F32(is);
selectionbox.MaxEdge = readV3F32(is);
pointable = readU8(is);
pointable = Pointabilities::deSerializePointabilityType(is);
visual = deSerializeString16(is);
visual_size = readV3F32(is);
textures.clear();

@ -25,6 +25,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include <iostream>
#include <map>
#include <vector>
#include "util/pointabilities.h"
struct ObjectProperties
{
@ -36,7 +37,7 @@ struct ObjectProperties
aabb3f collisionbox = aabb3f(-0.5f, -0.5f, -0.5f, 0.5f, 0.5f, 0.5f);
aabb3f selectionbox = aabb3f(-0.5f, -0.5f, -0.5f, 0.5f, 0.5f, 0.5f);
bool rotate_selectionbox = false;
bool pointable = true;
PointabilityType pointable = PointabilityType::POINTABLE;
std::string visual = "sprite";
std::string mesh = "";
v3f visual_size = v3f(1, 1, 1);

@ -58,12 +58,14 @@ bool RaycastSort::operator() (const PointedThing &pt1,
RaycastState::RaycastState(const core::line3d<f32> &shootline,
bool objects_pointable, bool liquids_pointable) :
bool objects_pointable, bool liquids_pointable,
const std::optional<Pointabilities> &pointabilities) :
m_shootline(shootline),
m_iterator(shootline.start / BS, shootline.getVector() / BS),
m_previous_node(m_iterator.m_current_node_pos),
m_objects_pointable(objects_pointable),
m_liquids_pointable(liquids_pointable)
m_liquids_pointable(liquids_pointable),
m_pointabilities(pointabilities)
{
}

@ -38,7 +38,7 @@ public:
* @param liquids pointable if false, liquid nodes won't be found
*/
RaycastState(const core::line3d<f32> &shootline, bool objects_pointable,
bool liquids_pointable);
bool liquids_pointable, const std::optional<Pointabilities> &pointabilities);
//! Shootline of the raycast.
core::line3d<f32> m_shootline;
@ -55,6 +55,7 @@ public:
bool m_objects_pointable;
bool m_liquids_pointable;
const std::optional<Pointabilities> &m_pointabilities;
//! The code needs to search these nodes around the center node.
core::aabbox3d<s16> m_search_range { 0, 0, 0, 0, 0, 0 };

@ -83,6 +83,12 @@ void read_item_definition(lua_State* L, int index,
getboolfield(L, index, "liquids_pointable", def.liquids_pointable);
lua_getfield(L, index, "pointabilities");
if(lua_istable(L, -1)){
def.pointabilities = std::make_optional<Pointabilities>(
read_pointabilities(L, -1));
}
lua_getfield(L, index, "tool_capabilities");
if(lua_istable(L, -1)){
def.tool_capabilities = new ToolCapabilities(
@ -199,6 +205,10 @@ void push_item_definition_full(lua_State *L, const ItemDefinition &i)
lua_setfield(L, -2, "usable");
lua_pushboolean(L, i.liquids_pointable);
lua_setfield(L, -2, "liquids_pointable");
if (i.pointabilities) {
push_pointabilities(L, *i.pointabilities);
lua_setfield(L, -2, "pointabilities");
}
if (i.tool_capabilities) {
push_tool_capabilities(L, *i.tool_capabilities);
lua_setfield(L, -2, "tool_capabilities");
@ -311,7 +321,12 @@ void read_object_properties(lua_State *L, int index,
}
lua_pop(L, 1);
getboolfield(L, -1, "pointable", prop->pointable);
lua_getfield(L, -1, "pointable");
if(!lua_isnil(L, -1)){
prop->pointable = read_pointability_type(L, -1);
}
lua_pop(L, 1);
getstringfield(L, -1, "visual", prop->visual);
getstringfield(L, -1, "mesh", prop->mesh);
@ -452,7 +467,7 @@ void push_object_properties(lua_State *L, ObjectProperties *prop)
lua_pushboolean(L, prop->rotate_selectionbox);
lua_setfield(L, -2, "rotate");
lua_setfield(L, -2, "selectionbox");
lua_pushboolean(L, prop->pointable);
push_pointability_type(L, prop->pointable);
lua_setfield(L, -2, "pointable");
lua_pushlstring(L, prop->visual.c_str(), prop->visual.size());
lua_setfield(L, -2, "visual");
@ -781,8 +796,14 @@ void read_content_features(lua_State *L, ContentFeatures &f, int index)
// This is used for collision detection.
// Also for general solidness queries.
getboolfield(L, index, "walkable", f.walkable);
// Player can point to these
getboolfield(L, index, "pointable", f.pointable);
// Player can point to these, point through or it is blocking
lua_getfield(L, index, "pointable");
if(!lua_isnil(L, -1)){
f.pointable = read_pointability_type(L, -1);
}
lua_pop(L, 1);
// Player can dig these
getboolfield(L, index, "diggable", f.diggable);
// Player can climb these
@ -1005,7 +1026,7 @@ void push_content_features(lua_State *L, const ContentFeatures &c)
lua_setfield(L, -2, "is_ground_content");
lua_pushboolean(L, c.walkable);
lua_setfield(L, -2, "walkable");
lua_pushboolean(L, c.pointable);
push_pointability_type(L, c.pointable);
lua_setfield(L, -2, "pointable");
lua_pushboolean(L, c.diggable);
lua_setfield(L, -2, "diggable");
@ -1592,6 +1613,125 @@ ToolCapabilities read_tool_capabilities(
return toolcap;
}
/******************************************************************************/
PointabilityType read_pointability_type(lua_State *L, int index)
{
if (lua_isboolean(L, index)) {
if (lua_toboolean(L, index))
return PointabilityType::POINTABLE;
else
return PointabilityType::POINTABLE_NOT;
} else {
const char* s = luaL_checkstring(L, index);
if (s && !strcmp(s, "blocking")) {
return PointabilityType::POINTABLE_BLOCKING;
}
}
throw LuaError("Invalid pointable type.");
}
/******************************************************************************/
Pointabilities read_pointabilities(lua_State *L, int index)
{
Pointabilities pointabilities;
lua_getfield(L, index, "nodes");
if(lua_istable(L, -1)){
int ti = lua_gettop(L);
lua_pushnil(L);
while(lua_next(L, ti) != 0) {
// key at index -2 and value at index -1
std::string name = luaL_checkstring(L, -2);
// handle groups
if(std::string_view(name).substr(0,6)=="group:") {
pointabilities.node_groups[name.substr(6)] = read_pointability_type(L, -1);
} else {
pointabilities.nodes[name] = read_pointability_type(L, -1);
}
// removes value, keeps key for next iteration
lua_pop(L, 1);
}
}
lua_pop(L, 1);
lua_getfield(L, index, "objects");
if(lua_istable(L, -1)){
int ti = lua_gettop(L);
lua_pushnil(L);
while(lua_next(L, ti) != 0) {
// key at index -2 and value at index -1
std::string name = luaL_checkstring(L, -2);
// handle groups
if(std::string_view(name).substr(0,6)=="group:") {
pointabilities.object_groups[name.substr(6)] = read_pointability_type(L, -1);
} else {
pointabilities.objects[name] = read_pointability_type(L, -1);
}
// removes value, keeps key for next iteration
lua_pop(L, 1);
}
}
lua_pop(L, 1);
return pointabilities;
}
/******************************************************************************/
void push_pointability_type(lua_State *L, PointabilityType pointable)
{
switch(pointable)
{
case PointabilityType::POINTABLE:
lua_pushboolean(L, true);
break;
case PointabilityType::POINTABLE_NOT:
lua_pushboolean(L, false);
break;
case PointabilityType::POINTABLE_BLOCKING:
lua_pushliteral(L, "blocking");
break;
}
}
/******************************************************************************/
void push_pointabilities(lua_State *L, const Pointabilities &pointabilities)
{
// pointabilities table
lua_newtable(L);
if (!pointabilities.nodes.empty() || !pointabilities.node_groups.empty()) {
// Create and fill table
lua_newtable(L);
for (const auto &entry : pointabilities.nodes) {
push_pointability_type(L, entry.second);
lua_setfield(L, -2, entry.first.c_str());
}
for (const auto &entry : pointabilities.node_groups) {
push_pointability_type(L, entry.second);
lua_setfield(L, -2, ("group:" + entry.first).c_str());
}
lua_setfield(L, -2, "nodes");
}
if (!pointabilities.objects.empty() || !pointabilities.object_groups.empty()) {
// Create and fill table
lua_newtable(L);
for (const auto &entry : pointabilities.objects) {
push_pointability_type(L, entry.second);
lua_setfield(L, -2, entry.first.c_str());
}
for (const auto &entry : pointabilities.object_groups) {
push_pointability_type(L, entry.second);
lua_setfield(L, -2, ("group:" + entry.first).c_str());
}
lua_setfield(L, -2, "objects");
}
}
/******************************************************************************/
void push_dig_params(lua_State *L,const DigParams &params)
{

@ -39,6 +39,7 @@ extern "C" {
#include "util/string.h"
#include "itemgroup.h"
#include "itemdef.h"
#include "util/pointabilities.h"
#include "c_types.h"
// We do an explicit path include because by default c_content.h include src/client/hud.h
// prior to the src/hud.h, which is not good on server only build
@ -107,6 +108,11 @@ ItemStack read_item (lua_State *L, int index, IItemDefM
struct TileAnimationParams read_animation_definition(lua_State *L, int index);
PointabilityType read_pointability_type (lua_State *L, int index);
Pointabilities read_pointabilities (lua_State *L, int index);
void push_pointability_type (lua_State *L, PointabilityType pointable);
void push_pointabilities (lua_State *L, const Pointabilities &pointabilities);
ToolCapabilities read_tool_capabilities (lua_State *L, int table);
void push_tool_capabilities (lua_State *L,
const ToolCapabilities &prop);

@ -188,7 +188,7 @@ int LuaRaycast::create_object(lua_State *L)
}
LuaRaycast *o = new LuaRaycast(core::line3d<f32>(pos1, pos2),
objects, liquids);
objects, liquids, std::nullopt);
*(void **) (lua_newuserdata(L, sizeof(void *))) = o;
luaL_getmetatable(L, className);

@ -336,8 +336,9 @@ public:
LuaRaycast(
const core::line3d<f32> &shootline,
bool objects_pointable,
bool liquids_pointable) :
state(shootline, objects_pointable, liquids_pointable)
bool liquids_pointable,
const std::optional<Pointabilities> &pointabilities) :
state(shootline, objects_pointable, liquids_pointable, pointabilities)
{}
//! Creates a LuaRaycast and leaves it on top of the stack.

@ -245,7 +245,7 @@ std::string LuaEntitySAO::getClientInitializationData(u16 protocol_version)
// PROTOCOL_VERSION >= 37
writeU8(os, 1); // version
os << serializeString16(""); // name
os << serializeString16(m_init_name); // name
writeU8(os, 0); // is_player
writeU16(os, getId()); //id
writeV3F32(os, m_base_position);
@ -553,7 +553,7 @@ bool LuaEntitySAO::getCollisionBox(aabb3f *toset) const
bool LuaEntitySAO::getSelectionBox(aabb3f *toset) const
{
if (!m_prop.is_visible || !m_prop.pointable) {
if (!m_prop.is_visible) {
return false;
}

@ -39,7 +39,7 @@ PlayerSAO::PlayerSAO(ServerEnvironment *env_, RemotePlayer *player_, session_t p
m_prop.physical = false;
m_prop.collisionbox = aabb3f(-0.3f, 0.0f, -0.3f, 0.3f, 1.77f, 0.3f);
m_prop.selectionbox = aabb3f(-0.3f, 0.0f, -0.3f, 0.3f, 1.77f, 0.3f);
m_prop.pointable = true;
m_prop.pointable = PointabilityType::POINTABLE;
// Start of default appearance, this should be overwritten by Lua
m_prop.visual = "upright_sprite";
m_prop.visual_size = v3f(1, 2, 1);
@ -724,7 +724,7 @@ bool PlayerSAO::getCollisionBox(aabb3f *toset) const
bool PlayerSAO::getSelectionBox(aabb3f *toset) const
{
if (!m_prop.is_visible || !m_prop.pointable) {
if (!m_prop.is_visible) {
return false;
}

@ -1830,7 +1830,8 @@ bool ServerEnvironment::getActiveObjectMessage(ActiveObjectMessage *dest)
void ServerEnvironment::getSelectedActiveObjects(
const core::line3d<f32> &shootline_on_map,
std::vector<PointedThing> &objects)
std::vector<PointedThing> &objects,
const std::optional<Pointabilities> &pointabilities)
{
std::vector<ServerActiveObject *> objs;
getObjectsInsideRadius(objs, shootline_on_map.start,
@ -1863,10 +1864,26 @@ void ServerEnvironment::getSelectedActiveObjects(
current_raw_normal = current_normal;
}
if (collision) {
current_intersection += pos;
objects.emplace_back(
(s16) obj->getId(), current_intersection, current_normal, current_raw_normal,
(current_intersection - shootline_on_map.start).getLengthSQ());
PointabilityType pointable;
if (pointabilities) {
if (LuaEntitySAO* lsao = dynamic_cast<LuaEntitySAO*>(obj)) {
pointable = pointabilities->matchObject(lsao->getName(),
usao->getArmorGroups()).value_or(props->pointable);
} else if (PlayerSAO* psao = dynamic_cast<PlayerSAO*>(obj)) {
pointable = pointabilities->matchPlayer(psao->getArmorGroups()).value_or(
props->pointable);
} else {
pointable = props->pointable;
}
} else {
pointable = props->pointable;
}
if (pointable != PointabilityType::POINTABLE_NOT) {
current_intersection += pos;
objects.emplace_back(
(s16) obj->getId(), current_intersection, current_normal, current_raw_normal,
(current_intersection - shootline_on_map.start).getLengthSQ(), pointable);
}
}
}
}

@ -315,7 +315,8 @@ public:
virtual void getSelectedActiveObjects(
const core::line3d<f32> &shootline_on_map,
std::vector<PointedThing> &objects
std::vector<PointedThing> &objects,
const std::optional<Pointabilities> &pointabilities
);
/*

@ -8,6 +8,7 @@ set(UTIL_SRCS
${CMAKE_CURRENT_SOURCE_DIR}/metricsbackend.cpp
${CMAKE_CURRENT_SOURCE_DIR}/numeric.cpp
${CMAKE_CURRENT_SOURCE_DIR}/pointedthing.cpp
${CMAKE_CURRENT_SOURCE_DIR}/pointabilities.cpp
${CMAKE_CURRENT_SOURCE_DIR}/quicktune.cpp
${CMAKE_CURRENT_SOURCE_DIR}/serialize.cpp
${CMAKE_CURRENT_SOURCE_DIR}/sha1.cpp

147
src/util/pointabilities.cpp Normal file

@ -0,0 +1,147 @@
/*
Minetest
Copyright (C) 2023 cx384
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 "pointabilities.h"
#include "serialize.h"
#include "exceptions.h"
#include <sstream>
PointabilityType Pointabilities::deSerializePointabilityType(std::istream &is)
{
PointabilityType pointable_type = static_cast<PointabilityType>(readU8(is));
switch(pointable_type) {
case PointabilityType::POINTABLE:
case PointabilityType::POINTABLE_NOT:
case PointabilityType::POINTABLE_BLOCKING:
break;
default:
// Default to POINTABLE in case of unknown PointabilityType type.
pointable_type = PointabilityType::POINTABLE;
break;
}
return pointable_type;
}
void Pointabilities::serializePointabilityType(std::ostream &os, PointabilityType pointable_type)
{
writeU8(os, static_cast<u8>(pointable_type));
}
std::string Pointabilities::toStringPointabilityType(PointabilityType pointable_type)
{
switch(pointable_type) {
case PointabilityType::POINTABLE:
return "true";
case PointabilityType::POINTABLE_NOT:
return "false";
case PointabilityType::POINTABLE_BLOCKING:
return "\"blocking\"";
}
return "unknown";
}
std::optional<PointabilityType> Pointabilities::matchNode(const std::string &name,
const ItemGroupList &groups) const
{
auto i = nodes.find(name);
return i == nodes.end() ? matchGroups(groups, node_groups) : i->second;
}
std::optional<PointabilityType> Pointabilities::matchObject(const std::string &name,
const ItemGroupList &groups) const
{
auto i = objects.find(name);
return i == objects.end() ? matchGroups(groups, object_groups) : i->second;
}
std::optional<PointabilityType> Pointabilities::matchPlayer(const ItemGroupList &groups) const
{
return matchGroups(groups, object_groups);
}
std::optional<PointabilityType> Pointabilities::matchGroups(const ItemGroupList &groups,
const std::unordered_map<std::string, PointabilityType> &pointable_groups)
{
// prefers POINTABLE over POINTABLE_NOT over POINTABLE_BLOCKING
bool blocking = false;
bool not_pointable = false;
for (auto const &ability : pointable_groups) {
if (itemgroup_get(groups, ability.first) > 0) {
switch(ability.second) {
case PointabilityType::POINTABLE:
return PointabilityType::POINTABLE;
case PointabilityType::POINTABLE_NOT:
not_pointable = true;
break;
default:
blocking = true;
break;
}
}
}
if (not_pointable)
return PointabilityType::POINTABLE_NOT;
if (blocking)
return PointabilityType::POINTABLE_BLOCKING;
return std::nullopt;
}
void Pointabilities::serializeTypeMap(std::ostream &os,
const std::unordered_map<std::string, PointabilityType> &map)
{
writeU32(os, map.size());
for (const auto &entry : map) {
os << serializeString16(entry.first);
writeU8(os, (u8)entry.second);
}
}
void Pointabilities::deSerializeTypeMap(std::istream &is,
std::unordered_map<std::string, PointabilityType> &map)
{
map.clear();
u32 size = readU32(is);
for (u32 i = 0; i < size; i++) {
std::string name = deSerializeString16(is);
PointabilityType type = Pointabilities::deSerializePointabilityType(is);
map[name] = type;
}
}
void Pointabilities::serialize(std::ostream &os) const
{
writeU8(os, 0); // version
serializeTypeMap(os, nodes);
serializeTypeMap(os, node_groups);
serializeTypeMap(os, objects);
serializeTypeMap(os, object_groups);
}
void Pointabilities::deSerialize(std::istream &is)
{
int version = readU8(is);
if (version != 0)
throw SerializationError("unsupported Pointabilities version");
deSerializeTypeMap(is, nodes);
deSerializeTypeMap(is, node_groups);
deSerializeTypeMap(is, objects);
deSerializeTypeMap(is, object_groups);
}

70
src/util/pointabilities.h Normal file

@ -0,0 +1,70 @@
/*
Minetest
Copyright (C) 2023 cx384
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.
*/
#pragma once
#include <string>
#include <unordered_map>
#include "itemgroup.h"
#include <optional>
#include "irrlichttypes.h"
enum class PointabilityType : u8
{
POINTABLE,
POINTABLE_NOT, // Can be pointed through.
POINTABLE_BLOCKING,
};
// An object to store overridden pointable properties
struct Pointabilities
{
// Nodes
std::unordered_map<std::string, PointabilityType> nodes;
std::unordered_map<std::string, PointabilityType> node_groups;
// Objects
std::unordered_map<std::string, PointabilityType> objects;
std::unordered_map<std::string, PointabilityType> object_groups; // armor_groups
// Match functions return fitting pointability,
// otherwise the default pointability should be used.
std::optional<PointabilityType> matchNode(const std::string &name,
const ItemGroupList &groups) const;
std::optional<PointabilityType> matchObject(const std::string &name,
const ItemGroupList &groups) const;
// For players only armor groups will work
std::optional<PointabilityType> matchPlayer(const ItemGroupList &groups) const;
void serialize(std::ostream &os) const;
void deSerialize(std::istream &is);
// For a save enum conversion.
static PointabilityType deSerializePointabilityType(std::istream &is);
static void serializePointabilityType(std::ostream &os, PointabilityType pointable_type);
static std::string toStringPointabilityType(PointabilityType pointable_type);
private:
static std::optional<PointabilityType> matchGroups(const ItemGroupList &groups,
const std::unordered_map<std::string, PointabilityType> &pointable_groups);
static void serializeTypeMap(std::ostream &os,
const std::unordered_map<std::string, PointabilityType> &map);
static void deSerializeTypeMap(std::istream &is,
std::unordered_map<std::string, PointabilityType> &map);
};

@ -25,7 +25,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
PointedThing::PointedThing(const v3s16 &under, const v3s16 &above,
const v3s16 &real_under, const v3f &point, const v3f &normal,
u16 box_id, f32 distSq):
u16 box_id, f32 distSq, PointabilityType pointab):
type(POINTEDTHING_NODE),
node_undersurface(under),
node_abovesurface(above),
@ -33,17 +33,19 @@ PointedThing::PointedThing(const v3s16 &under, const v3s16 &above,
intersection_point(point),
intersection_normal(normal),
box_id(box_id),
distanceSq(distSq)
distanceSq(distSq),
pointability(pointab)
{}
PointedThing::PointedThing(u16 id, const v3f &point,
const v3f &normal, const v3f &raw_normal, f32 distSq) :
PointedThing::PointedThing(u16 id, const v3f &point, const v3f &normal,
const v3f &raw_normal, f32 distSq, PointabilityType pointab) :
type(POINTEDTHING_OBJECT),
object_id(id),
intersection_point(point),
intersection_normal(normal),
raw_intersection_normal(raw_normal),
distanceSq(distSq)
distanceSq(distSq),
pointability(pointab)
{}
std::string PointedThing::dump() const
@ -118,12 +120,13 @@ bool PointedThing::operator==(const PointedThing &pt2) const
{
if ((node_undersurface != pt2.node_undersurface)
|| (node_abovesurface != pt2.node_abovesurface)
|| (node_real_undersurface != pt2.node_real_undersurface))
|| (node_real_undersurface != pt2.node_real_undersurface)
|| (pointability != pt2.pointability))
return false;
}
else if (type == POINTEDTHING_OBJECT)
{
if (object_id != pt2.object_id)
if (object_id != pt2.object_id || pointability != pt2.pointability)
return false;
}
return true;

@ -23,8 +23,9 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include "irr_v3d.h"
#include <iostream>
#include <string>
#include "pointabilities.h"
enum PointedThingType : u8
enum PointedThingType :u8
{
POINTEDTHING_NOTHING,
POINTEDTHING_NODE,
@ -90,15 +91,20 @@ struct PointedThing
* ray's start point and the intersection point in irrlicht coordinates.
*/
f32 distanceSq = 0;
/*!
* How the object or node has been pointed at.
*/
PointabilityType pointability = PointabilityType::POINTABLE_NOT;
//! Constructor for POINTEDTHING_NOTHING
PointedThing() = default;
//! Constructor for POINTEDTHING_NODE
PointedThing(const v3s16 &under, const v3s16 &above,
const v3s16 &real_under, const v3f &point, const v3f &normal,
u16 box_id, f32 distSq);
u16 box_id, f32 distSq, PointabilityType pointability);
//! Constructor for POINTEDTHING_OBJECT
PointedThing(u16 id, const v3f &point, const v3f &normal, const v3f &raw_normal, f32 distSq);
PointedThing(u16 id, const v3f &point, const v3f &normal, const v3f &raw_normal, f32 distSq,
PointabilityType pointability);
std::string dump() const;
void serialize(std::ostream &os) const;
void deSerialize(std::istream &is);