Browse Source

Tool specific pointing and blocking pointable type (#13992)

cx384 3 months ago
parent
commit
5958714309
39 changed files with 676 additions and 67 deletions
  1. 2 0
      builtin/game/features.lua
  2. 33 2
      doc/lua_api.md
  3. 1 0
      games/devtest/mods/testentities/init.lua
  4. 23 0
      games/devtest/mods/testentities/pointable.lua
  5. 20 0
      games/devtest/mods/testnodes/properties.lua
  6. BIN
      games/devtest/mods/testnodes/textures/testnodes_blocking_pointable.png
  7. BIN
      games/devtest/mods/testnodes/textures/testnodes_not_pointable.png
  8. BIN
      games/devtest/mods/testnodes/textures/testnodes_pointable.png
  9. 66 0
      games/devtest/mods/testtools/init.lua
  10. BIN
      games/devtest/mods/testtools/textures/testtools_blocked_pointing_staff.png
  11. BIN
      games/devtest/mods/testtools/textures/testtools_ultimate_pointing_staff.png
  12. 19 4
      src/client/clientenvironment.cpp
  13. 2 1
      src/client/clientenvironment.h
  14. 1 2
      src/client/content_cao.cpp
  15. 7 0
      src/client/content_cao.h
  16. 5 1
      src/client/game.cpp
  17. 32 13
      src/environment.cpp
  18. 4 1
      src/environment.h
  19. 17 0
      src/itemdef.cpp
  20. 5 1
      src/itemdef.h
  21. 5 5
      src/nodedef.cpp
  22. 3 2
      src/nodedef.h
  23. 3 3
      src/object_properties.cpp
  24. 2 1
      src/object_properties.h
  25. 4 2
      src/raycast.cpp
  26. 2 1
      src/raycast.h
  27. 145 5
      src/script/common/c_content.cpp
  28. 6 0
      src/script/common/c_content.h
  29. 1 1
      src/script/lua_api/l_env.cpp
  30. 3 2
      src/script/lua_api/l_env.h
  31. 2 2
      src/server/luaentity_sao.cpp
  32. 2 2
      src/server/player_sao.cpp
  33. 22 5
      src/serverenvironment.cpp
  34. 2 1
      src/serverenvironment.h
  35. 1 0
      src/util/CMakeLists.txt
  36. 147 0
      src/util/pointabilities.cpp
  37. 70 0
      src/util/pointabilities.h
  38. 10 7
      src/util/pointedthing.cpp
  39. 9 3
      src/util/pointedthing.h

+ 2 - 0
builtin/game/features.lua

@@ -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)

+ 33 - 2
doc/lua_api.md

@@ -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 - 0
games/devtest/mods/testentities/init.lua

@@ -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")

+ 23 - 0
games/devtest/mods/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")

+ 20 - 0
games/devtest/mods/testnodes/properties.lua

@@ -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")

BIN
games/devtest/mods/testnodes/textures/testnodes_blocking_pointable.png


BIN
games/devtest/mods/testnodes/textures/testnodes_not_pointable.png


BIN
games/devtest/mods/testnodes/textures/testnodes_pointable.png


+ 66 - 0
games/devtest/mods/testtools/init.lua

@@ -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
+		}
+	}
+})

BIN
games/devtest/mods/testtools/textures/testtools_blocked_pointing_staff.png


BIN
games/devtest/mods/testtools/textures/testtools_ultimate_pointing_staff.png


+ 19 - 4
src/client/clientenvironment.cpp

@@ -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);
+			}
 		}
 	}
 }

+ 2 - 1
src/client/clientenvironment.h

@@ -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; }

+ 1 - 2
src/client/content_cao.cpp

@@ -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;

+ 7 - 0
src/client/content_cao.h

@@ -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;

+ 5 - 1
src/client/game.cpp

@@ -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) {

+ 32 - 13
src/environment.cpp

@@ -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;
+		}
 	}
 }
 

+ 4 - 1
src/environment.h

@@ -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.

+ 17 - 0
src/itemdef.cpp

@@ -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) {};
 }
 

+ 5 - 1
src/itemdef.h

@@ -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;

+ 5 - 5
src/nodedef.cpp

@@ -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;

+ 3 - 2
src/nodedef.h

@@ -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

+ 3 - 3
src/object_properties.cpp

@@ -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();

+ 2 - 1
src/object_properties.h

@@ -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);

+ 4 - 2
src/raycast.cpp

@@ -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)
 {
 }
 

+ 2 - 1
src/raycast.h

@@ -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 };

+ 145 - 5
src/script/common/c_content.cpp

@@ -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)
 {

+ 6 - 0
src/script/common/c_content.h

@@ -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);

+ 1 - 1
src/script/lua_api/l_env.cpp

@@ -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);

+ 3 - 2
src/script/lua_api/l_env.h

@@ -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.

+ 2 - 2
src/server/luaentity_sao.cpp

@@ -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;
 	}
 

+ 2 - 2
src/server/player_sao.cpp

@@ -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;
 	}
 

+ 22 - 5
src/serverenvironment.cpp

@@ -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);
+			}
 		}
 	}
 }

+ 2 - 1
src/serverenvironment.h

@@ -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
 	);
 
 	/*

+ 1 - 0
src/util/CMakeLists.txt

@@ -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 - 0
src/util/pointabilities.cpp

@@ -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 - 0
src/util/pointabilities.h

@@ -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);
+};

+ 10 - 7
src/util/pointedthing.cpp

@@ -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;

+ 9 - 3
src/util/pointedthing.h

@@ -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);