Nodebox: Allow nodeboxes to "connect"
authorAuke Kok <sofar@foo-projects.org>
Thu, 25 Feb 2016 08:16:31 +0000 (00:16 -0800)
committerShadowNinja <shadowninja@minetest.net>
Sat, 12 Mar 2016 17:08:17 +0000 (12:08 -0500)
We introduce a new nodebox type "connected", and allow these nodes to
have optional nodeboxes that connect it to other connecting nodeboxes.

This is all done at scenedraw time in the client. The client will
inspect the surrounding nodes and if they are to be connected to,
it will draw the appropriate connecting nodeboxes to make those
connections.

In the node_box definition, we have to specify separate nodeboxes for
each valid connection. This allows us to make nodes that connect only
horizontally (the common case) by providing optional nodeboxes for +x,
-x, +z, -z directions. Or this allows us to make wires that can connect
up and down, by providing nodeboxes that connect it up and down (+y,
-y) as well.

The optional nodeboxes can be arrays. They are named "connect_top,
"connect_bottom", "connect_front", "connect_left", "connect_back" and
"connect_right". Here, "front" means the south facing side of the node
that has facedir = 0.

Additionally, a "fixed" nodebox list present will always be drawn,
so one can make a central post, for instance. This "fixed" nodebox
can be omitted, or it can be an array of nodeboxes.

Collision boxes are also updated in exactly the same fashion, which
allows you to walk over the upper extremities of the individual
node boxes, or stand really close to them. You can also walk up
node noxes that are small in height, all as expected, and unlike the
NDT_FENCELIKE nodes.

I've posted a screenshot demonstrating the flexibility at
    http://i.imgur.com/zaJq8jo.png
In the screenshot, all connecting nodes are of this new subtype.

Transparent textures render incorrectly, Which I don't think is
related to this text, as other nodeboxes also have issues with this.

A protocol bump is performed in order to be able to send older clients
a nodeblock that is usable for them. In order to avoid abuse of users
we send older clients a "full-size" node, so that it's impossible for
them to try and walk through a fence or wall that's created in this
fashion. This was tested with a pre-bump client connected against a
server running the new protocol.

These nodes connect to other nodes, and you can select which ones
those are by specifying node names (or group names) in the
connects_to string array:
      connects_to = { "group:fence", "default:wood" }
By default, nodes do not connect to anything, allowing you to create
nodes that always have to be paired in order to connect. lua_api.txt
is updated to reflect the extension to the node_box API.

Example lua code needed to generate these nodes can be found here:
    https://gist.github.com/sofar/b381c8c192c8e53e6062

13 files changed:
doc/lua_api.txt
src/collision.cpp
src/content_mapblock.cpp
src/game.cpp
src/localplayer.cpp
src/mapnode.cpp
src/mapnode.h
src/network/networkprotocol.h
src/nodedef.cpp
src/nodedef.h
src/script/common/c_content.cpp
src/script/cpp_api/s_node.cpp
src/server.cpp

index e2ab65ddcb4af77a4d310b9fb20b220ff3b82f59..f51c950c3d9c5a341ae7b1cc0fb9c5c4ad9fde81 100644 (file)
@@ -617,6 +617,18 @@ A nodebox is defined as any of:
         wall_bottom = box,
         wall_side = box
     }
+    {
+        -- A node that has optional boxes depending on neighbouring nodes'
+        -- presence and type. See also `connects_to`.
+        type = "connected",
+        fixed = box OR {box1, box2, ...}
+        connect_top = box OR {box1, box2, ...}
+        connect_bottom = box OR {box1, box2, ...}
+        connect_front = box OR {box1, box2, ...}
+        connect_left = box OR {box1, box2, ...}
+        connect_back = box OR {box1, box2, ...}
+        connect_right = box OR {box1, box2, ...}
+    }
 
 A `box` is defined as:
 
@@ -3461,6 +3473,10 @@ Definition tables
         light_source = 0, -- Amount of light emitted by node
         damage_per_second = 0, -- If player is inside node, this damage is caused
         node_box = {type="regular"}, -- See "Node boxes"
+        connects_to = nodenames, --[[
+        * Used for nodebox nodes with the type == "connected"
+        * Specifies to what neighboring nodes connections will be drawn
+        * e.g. `{"group:fence", "default:wood"}` or `"default:stone"` ]]
         mesh = "model",
         selection_box = {type="regular"}, -- See "Node boxes" --[[
         ^ If drawtype "nodebox" is used and selection_box is nil, then node_box is used. ]]
index d847f6297c26bc5cf89528711188194083112ee1..a3979f1dc03289a70f1e8dd755fc128ca12769bb 100644 (file)
@@ -185,6 +185,13 @@ bool wouldCollideWithCeiling(
        return false;
 }
 
+static inline void getNeighborConnectingFace(v3s16 p, INodeDefManager *nodedef,
+               Map *map, MapNode n, int v, int *neighbors)
+{
+       MapNode n2 = map->getNodeNoEx(p);
+       if (nodedef->nodeboxConnects(n, n2))
+               *neighbors |= v;
+}
 
 collisionMoveResult collisionMoveSimple(Environment *env, IGameDef *gamedef,
                f32 pos_max_d, const aabb3f &box_0,
@@ -261,12 +268,41 @@ collisionMoveResult collisionMoveSimple(Environment *env, IGameDef *gamedef,
                        // Object collides into walkable nodes
 
                        any_position_valid = true;
-                       const ContentFeatures &f = gamedef->getNodeDefManager()->get(n);
+                       INodeDefManager *nodedef = gamedef->getNodeDefManager();
+                       const ContentFeatures &f = nodedef->get(n);
                        if(f.walkable == false)
                                continue;
                        int n_bouncy_value = itemgroup_get(f.groups, "bouncy");
 
-                       std::vector<aabb3f> nodeboxes = n.getCollisionBoxes(gamedef->ndef());
+                       int neighbors = 0;
+                       if (f.drawtype == NDT_NODEBOX && f.node_box.type == NODEBOX_CONNECTED) {
+                               v3s16 p2 = p;
+
+                               p2.Y++;
+                               getNeighborConnectingFace(p2, nodedef, map, n, 1, &neighbors);
+
+                               p2 = p;
+                               p2.Y--;
+                               getNeighborConnectingFace(p2, nodedef, map, n, 2, &neighbors);
+
+                               p2 = p;
+                               p2.Z--;
+                               getNeighborConnectingFace(p2, nodedef, map, n, 4, &neighbors);
+
+                               p2 = p;
+                               p2.X--;
+                               getNeighborConnectingFace(p2, nodedef, map, n, 8, &neighbors);
+
+                               p2 = p;
+                               p2.Z++;
+                               getNeighborConnectingFace(p2, nodedef, map, n, 16, &neighbors);
+
+                               p2 = p;
+                               p2.X++;
+                               getNeighborConnectingFace(p2, nodedef, map, n, 32, &neighbors);
+                       }
+                       std::vector<aabb3f> nodeboxes;
+                       n.getCollisionBoxes(gamedef->ndef(), &nodeboxes, neighbors);
                        for(std::vector<aabb3f>::iterator
                                        i = nodeboxes.begin();
                                        i != nodeboxes.end(); ++i)
index c15b9c4240c9f7c170b86ac770c140d8174cafd0..c2934f26a0b7985d00044887450d184c7ae34f53 100644 (file)
@@ -163,6 +163,14 @@ void makeCuboid(MeshCollector *collector, const aabb3f &box,
        }
 }
 
+static inline void getNeighborConnectingFace(v3s16 p, INodeDefManager *nodedef,
+               MeshMakeData *data, MapNode n, int v, int *neighbors)
+{
+       MapNode n2 = data->m_vmanip.getNodeNoEx(p);
+       if (nodedef->nodeboxConnects(n, n2))
+               *neighbors |= v;
+}
+
 /*
        TODO: Fix alpha blending for special nodes
        Currently only the last element rendered is blended correct
@@ -1501,7 +1509,38 @@ void mapblock_mesh_generate_special(MeshMakeData *data,
 
                        v3f pos = intToFloat(p, BS);
 
-                       std::vector<aabb3f> boxes = n.getNodeBoxes(nodedef);
+                       int neighbors = 0;
+
+                       // locate possible neighboring nodes to connect to
+                       if (f.node_box.type == NODEBOX_CONNECTED) {
+                               v3s16 p2 = p;
+
+                               p2.Y++;
+                               getNeighborConnectingFace(blockpos_nodes + p2, nodedef, data, n, 1, &neighbors);
+
+                               p2 = p;
+                               p2.Y--;
+                               getNeighborConnectingFace(blockpos_nodes + p2, nodedef, data, n, 2, &neighbors);
+
+                               p2 = p;
+                               p2.Z--;
+                               getNeighborConnectingFace(blockpos_nodes + p2, nodedef, data, n, 4, &neighbors);
+
+                               p2 = p;
+                               p2.X--;
+                               getNeighborConnectingFace(blockpos_nodes + p2, nodedef, data, n, 8, &neighbors);
+
+                               p2 = p;
+                               p2.Z++;
+                               getNeighborConnectingFace(blockpos_nodes + p2, nodedef, data, n, 16, &neighbors);
+
+                               p2 = p;
+                               p2.X++;
+                               getNeighborConnectingFace(blockpos_nodes + p2, nodedef, data, n, 32, &neighbors);
+                       }
+
+                       std::vector<aabb3f> boxes;
+                       n.getNodeBoxes(nodedef, &boxes, neighbors);
                        for(std::vector<aabb3f>::iterator
                                        i = boxes.begin();
                                        i != boxes.end(); ++i)
index c6125dd437d09065ca38de0fd990354f88c004e9..be4c46bc353970e2cbcfaaefe9ab59d6bd7d0cfb 100644 (file)
@@ -358,7 +358,9 @@ PointedThing getPointedThing(Client *client, Hud *hud, const v3f &player_positio
                                if (!isPointableNode(n, client, liquids_pointable)) {
                                        continue;
                                }
-                               std::vector<aabb3f> boxes = n.getSelectionBoxes(nodedef);
+
+                               std::vector<aabb3f> boxes;
+                               n.getSelectionBoxes(nodedef, &boxes);
 
                                v3s16 np(x, y, z);
                                v3f npf = intToFloat(np, BS);
@@ -389,7 +391,8 @@ PointedThing getPointedThing(Client *client, Hud *hud, const v3f &player_positio
                f32 d = 0.001 * BS;
                MapNode n = map.getNodeNoEx(pointed_pos);
                v3f npf = intToFloat(pointed_pos, BS);
-               std::vector<aabb3f> boxes = n.getSelectionBoxes(nodedef);
+               std::vector<aabb3f> boxes;
+               n.getSelectionBoxes(nodedef, &boxes);
                f32 face_min_distance = 1000 * BS;
                for (std::vector<aabb3f>::const_iterator
                                i = boxes.begin();
index 524c6272ec51d00569abdca3c50deac351d519bc..0c94582aaa2be85186d8b88e5c7993427460aa39 100644 (file)
@@ -310,7 +310,8 @@ void LocalPlayer::move(f32 dtime, Environment *env, f32 pos_max_d,
                if (sneak_node_found) {
                        f32 cb_max = 0;
                        MapNode n = map->getNodeNoEx(m_sneak_node);
-                       std::vector<aabb3f> nodeboxes = n.getCollisionBoxes(nodemgr);
+                       std::vector<aabb3f> nodeboxes;
+                       n.getCollisionBoxes(nodemgr, &nodeboxes);
                        for (std::vector<aabb3f>::iterator it = nodeboxes.begin();
                                        it != nodeboxes.end(); ++it) {
                                aabb3f box = *it;
index 671e949f1d22b73f3e1c5494f7cb36cda6bc7ecc..a5465887339b4daec44dff574a8386d0de6004ab 100644 (file)
@@ -214,12 +214,12 @@ void MapNode::rotateAlongYAxis(INodeDefManager *nodemgr, Rotation rot)
        }
 }
 
-static std::vector<aabb3f> transformNodeBox(const MapNode &n,
-               const NodeBox &nodebox, INodeDefManager *nodemgr)
+void transformNodeBox(const MapNode &n, const NodeBox &nodebox,
+               INodeDefManager *nodemgr, std::vector<aabb3f> *p_boxes, u8 neighbors = 0)
 {
-       std::vector<aabb3f> boxes;
-       if(nodebox.type == NODEBOX_FIXED || nodebox.type == NODEBOX_LEVELED)
-       {
+       std::vector<aabb3f> &boxes = *p_boxes;
+
+       if (nodebox.type == NODEBOX_FIXED || nodebox.type == NODEBOX_LEVELED) {
                const std::vector<aabb3f> &fixed = nodebox.fixed;
                int facedir = n.getFaceDir(nodemgr);
                u8 axisdir = facedir>>2;
@@ -395,32 +395,71 @@ static std::vector<aabb3f> transformNodeBox(const MapNode &n,
                        boxes.push_back(box);
                }
        }
+       else if (nodebox.type == NODEBOX_CONNECTED)
+       {
+               size_t boxes_size = boxes.size();
+               boxes_size += nodebox.fixed.size();
+               if (neighbors & 1)
+                       boxes_size += nodebox.connect_top.size();
+               if (neighbors & 2)
+                       boxes_size += nodebox.connect_bottom.size();
+               if (neighbors & 4)
+                       boxes_size += nodebox.connect_front.size();
+               if (neighbors & 8)
+                       boxes_size += nodebox.connect_left.size();
+               if (neighbors & 16)
+                       boxes_size += nodebox.connect_back.size();
+               if (neighbors & 32)
+                       boxes_size += nodebox.connect_right.size();
+               boxes.reserve(boxes_size);
+
+#define BOXESPUSHBACK(c) do { \
+               for (std::vector<aabb3f>::const_iterator \
+                               it = (c).begin(); \
+                               it != (c).end(); ++it) \
+                       (boxes).push_back(*it); \
+               } while (0)
+
+               BOXESPUSHBACK(nodebox.fixed);
+
+               if (neighbors & 1)
+                       BOXESPUSHBACK(nodebox.connect_top);
+               if (neighbors & 2)
+                       BOXESPUSHBACK(nodebox.connect_bottom);
+               if (neighbors & 4)
+                       BOXESPUSHBACK(nodebox.connect_front);
+               if (neighbors & 8)
+                       BOXESPUSHBACK(nodebox.connect_left);
+               if (neighbors & 16)
+                       BOXESPUSHBACK(nodebox.connect_back);
+               if (neighbors & 32)
+                       BOXESPUSHBACK(nodebox.connect_right);
+       }
        else // NODEBOX_REGULAR
        {
                boxes.push_back(aabb3f(-BS/2,-BS/2,-BS/2,BS/2,BS/2,BS/2));
        }
-       return boxes;
 }
 
-std::vector<aabb3f> MapNode::getNodeBoxes(INodeDefManager *nodemgr) const
+void MapNode::getNodeBoxes(INodeDefManager *nodemgr, std::vector<aabb3f> *boxes, u8 neighbors)
 {
        const ContentFeatures &f = nodemgr->get(*this);
-       return transformNodeBox(*this, f.node_box, nodemgr);
+       transformNodeBox(*this, f.node_box, nodemgr, boxes, neighbors);
 }
 
-std::vector<aabb3f> MapNode::getCollisionBoxes(INodeDefManager *nodemgr) const
+void MapNode::getCollisionBoxes(INodeDefManager *nodemgr, std::vector<aabb3f> *boxes, u8 neighbors)
 {
        const ContentFeatures &f = nodemgr->get(*this);
        if (f.collision_box.fixed.empty())
-               return transformNodeBox(*this, f.node_box, nodemgr);
+               transformNodeBox(*this, f.node_box, nodemgr, boxes, neighbors);
        else
-               return transformNodeBox(*this, f.collision_box, nodemgr);
+               transformNodeBox(*this, f.collision_box, nodemgr, boxes, neighbors);
 }
 
-std::vector<aabb3f> MapNode::getSelectionBoxes(INodeDefManager *nodemgr) const
+void MapNode::getSelectionBoxes(INodeDefManager *nodemgr, std::vector<aabb3f> *boxes)
 {
        const ContentFeatures &f = nodemgr->get(*this);
-       return transformNodeBox(*this, f.selection_box, nodemgr);
+       transformNodeBox(*this, f.selection_box, nodemgr, boxes);
 }
 
 u8 MapNode::getMaxLevel(INodeDefManager *nodemgr) const
index 7cc25c60cfabde4b70036e1b80667ad67f69ebad..4db8886160b619b5c7d8b4de2f4e757777c90d6a 100644 (file)
@@ -240,17 +240,17 @@ struct MapNode
        /*
                Gets list of node boxes (used for rendering (NDT_NODEBOX))
        */
-       std::vector<aabb3f> getNodeBoxes(INodeDefManager *nodemgr) const;
+       void getNodeBoxes(INodeDefManager *nodemgr, std::vector<aabb3f> *boxes, u8 neighbors = 0);
 
        /*
                Gets list of selection boxes
        */
-       std::vector<aabb3f> getSelectionBoxes(INodeDefManager *nodemgr) const;
+       void getSelectionBoxes(INodeDefManager *nodemg, std::vector<aabb3f> *boxes);
 
        /*
                Gets list of collision boxes
        */
-       std::vector<aabb3f> getCollisionBoxes(INodeDefManager *nodemgr) const;
+       void getCollisionBoxes(INodeDefManager *nodemgr, std::vector<aabb3f> *boxes, u8 neighbors = 0);
 
        /*
                Liquid helpers
index dc15326d9624cc5e541f6fa066752b7768853bd6..7cde6d764b29989c45a06d4861f6436a5bce4bc7 100644 (file)
@@ -135,6 +135,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
        PROTOCOL_VERSION 27:
                backface_culling: backwards compatibility for playing with
                newer client on pre-27 servers.
+               Add nodedef v3 - connected nodeboxes
 */
 
 #define LATEST_PROTOCOL_VERSION 27
index ba9b4abf22046f354a13f1e41d731ca267b1eca5..85cd848aea738852e32143c9ca72dee18cee6ed0 100644 (file)
@@ -33,6 +33,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
 #include "exceptions.h"
 #include "debug.h"
 #include "gamedef.h"
+#include "mapnode.h"
 #include <fstream> // Used in applyTextureOverrides()
 
 /*
@@ -48,44 +49,91 @@ void NodeBox::reset()
        wall_top = aabb3f(-BS/2, BS/2-BS/16., -BS/2, BS/2, BS/2, BS/2);
        wall_bottom = aabb3f(-BS/2, -BS/2, -BS/2, BS/2, -BS/2+BS/16., BS/2);
        wall_side = aabb3f(-BS/2, -BS/2, -BS/2, -BS/2+BS/16., BS/2, BS/2);
+       // no default for other parts
+       connect_top.clear();
+       connect_bottom.clear();
+       connect_front.clear();
+       connect_left.clear();
+       connect_back.clear();
+       connect_right.clear();
 }
 
 void NodeBox::serialize(std::ostream &os, u16 protocol_version) const
 {
-       int version = protocol_version >= 21 ? 2 : 1;
+       int version = 1;
+       if (protocol_version >= 27)
+               version = 3;
+       else if (protocol_version >= 21)
+               version = 2;
        writeU8(os, version);
 
-       if (version == 1 && type == NODEBOX_LEVELED)
-               writeU8(os, NODEBOX_FIXED);
-       else
-               writeU8(os, type);
+       switch (type) {
+       case NODEBOX_LEVELED:
+       case NODEBOX_FIXED:
+               if (version == 1)
+                       writeU8(os, NODEBOX_FIXED);
+               else
+                       writeU8(os, type);
 
-       if(type == NODEBOX_FIXED || type == NODEBOX_LEVELED)
-       {
                writeU16(os, fixed.size());
-               for(std::vector<aabb3f>::const_iterator
+               for (std::vector<aabb3f>::const_iterator
                                i = fixed.begin();
                                i != fixed.end(); ++i)
                {
                        writeV3F1000(os, i->MinEdge);
                        writeV3F1000(os, i->MaxEdge);
                }
-       }
-       else if(type == NODEBOX_WALLMOUNTED)
-       {
+               break;
+       case NODEBOX_WALLMOUNTED:
+               writeU8(os, type);
+
                writeV3F1000(os, wall_top.MinEdge);
                writeV3F1000(os, wall_top.MaxEdge);
                writeV3F1000(os, wall_bottom.MinEdge);
                writeV3F1000(os, wall_bottom.MaxEdge);
                writeV3F1000(os, wall_side.MinEdge);
                writeV3F1000(os, wall_side.MaxEdge);
+               break;
+       case NODEBOX_CONNECTED:
+               if (version <= 2) {
+                       // send old clients nodes that can't be walked through
+                       // to prevent abuse
+                       writeU8(os, NODEBOX_FIXED);
+
+                       writeU16(os, 1);
+                       writeV3F1000(os, v3f(-BS/2, -BS/2, -BS/2));
+                       writeV3F1000(os, v3f(BS/2, BS/2, BS/2));
+               } else {
+                       writeU8(os, type);
+
+#define WRITEBOX(box) do { \
+               writeU16(os, (box).size()); \
+               for (std::vector<aabb3f>::const_iterator \
+                               i = (box).begin(); \
+                               i != (box).end(); ++i) { \
+                       writeV3F1000(os, i->MinEdge); \
+                       writeV3F1000(os, i->MaxEdge); \
+               }; } while (0)
+
+                       WRITEBOX(fixed);
+                       WRITEBOX(connect_top);
+                       WRITEBOX(connect_bottom);
+                       WRITEBOX(connect_front);
+                       WRITEBOX(connect_left);
+                       WRITEBOX(connect_back);
+                       WRITEBOX(connect_right);
+               }
+               break;
+       default:
+               writeU8(os, type);
+               break;
        }
 }
 
 void NodeBox::deSerialize(std::istream &is)
 {
        int version = readU8(is);
-       if(version < 1 || version > 2)
+       if (version < 1 || version > 3)
                throw SerializationError("unsupported NodeBox version");
 
        reset();
@@ -112,6 +160,26 @@ void NodeBox::deSerialize(std::istream &is)
                wall_side.MinEdge = readV3F1000(is);
                wall_side.MaxEdge = readV3F1000(is);
        }
+       else if (type == NODEBOX_CONNECTED)
+       {
+#define READBOXES(box) do { \
+               count = readU16(is); \
+               (box).reserve(count); \
+               while (count--) { \
+                       v3f min = readV3F1000(is); \
+                       v3f max = readV3F1000(is); \
+                       (box).push_back(aabb3f(min, max)); }; } while (0)
+
+               u16 count;
+
+               READBOXES(fixed);
+               READBOXES(connect_top);
+               READBOXES(connect_bottom);
+               READBOXES(connect_front);
+               READBOXES(connect_left);
+               READBOXES(connect_back);
+               READBOXES(connect_right);
+       }
 }
 
 /*
@@ -261,6 +329,8 @@ void ContentFeatures::reset()
        sound_footstep = SimpleSoundSpec();
        sound_dig = SimpleSoundSpec("__group");
        sound_dug = SimpleSoundSpec();
+       connects_to.clear();
+       connects_to_ids.clear();
 }
 
 void ContentFeatures::serialize(std::ostream &os, u16 protocol_version) const
@@ -328,6 +398,10 @@ void ContentFeatures::serialize(std::ostream &os, u16 protocol_version) const
        os<<serializeString(mesh);
        collision_box.serialize(os, protocol_version);
        writeU8(os, floodable);
+       writeU16(os, connects_to_ids.size());
+       for (std::set<content_t>::const_iterator i = connects_to_ids.begin();
+                       i != connects_to_ids.end(); ++i)
+               writeU16(os, *i);
 }
 
 void ContentFeatures::deSerialize(std::istream &is)
@@ -402,6 +476,9 @@ void ContentFeatures::deSerialize(std::istream &is)
        mesh = deSerializeString(is);
        collision_box.deSerialize(is);
        floodable = readU8(is);
+       u16 connects_to_size = readU16(is);
+       for (u16 i = 0; i < connects_to_size; i++)
+               connects_to_ids.insert(readU16(is));
        }catch(SerializationError &e) {};
 }
 
@@ -439,6 +516,8 @@ public:
        virtual bool cancelNodeResolveCallback(NodeResolver *nr);
        virtual void runNodeResolveCallbacks();
        virtual void resetNodeResolveState();
+       virtual void mapNodeboxConnections();
+       virtual bool nodeboxConnects(MapNode from, MapNode to);
 
 private:
        void addNameIdMapping(content_t i, std::string name);
@@ -1438,6 +1517,39 @@ void CNodeDefManager::resetNodeResolveState()
        m_pending_resolve_callbacks.clear();
 }
 
+void CNodeDefManager::mapNodeboxConnections()
+{
+       for (u32 i = 0; i < m_content_features.size(); i++) {
+               ContentFeatures *f = &m_content_features[i];
+               if ((f->drawtype != NDT_NODEBOX) || (f->node_box.type != NODEBOX_CONNECTED))
+                       continue;
+               for (std::vector<std::string>::iterator it = f->connects_to.begin();
+                               it != f->connects_to.end(); ++it) {
+                       getIds(*it, f->connects_to_ids);
+               }
+       }
+}
+
+bool CNodeDefManager::nodeboxConnects(MapNode from, MapNode to)
+{
+       const ContentFeatures &f1 = get(from);
+
+       if ((f1.drawtype != NDT_NODEBOX) || (f1.node_box.type != NODEBOX_CONNECTED))
+               return false;
+
+       // lookup target in connected set
+       if (f1.connects_to_ids.find(to.param0) == f1.connects_to_ids.end())
+               return false;
+
+       const ContentFeatures &f2 = get(to);
+
+       if ((f2.drawtype == NDT_NODEBOX) && (f1.node_box.type == NODEBOX_CONNECTED))
+               // ignores actually looking if back connection exists
+               return (f2.connects_to_ids.find(from.param0) != f2.connects_to_ids.end());
+
+       // the target is just a regular node, so connect no matter back connection
+       return true;
+}
 
 ////
 //// NodeResolver
index bf2c7bc727baa725b97387800fa668fadae55680..f92a3a941eca735a5d7b503b6dde8054d52059ba 100644 (file)
@@ -80,6 +80,7 @@ enum NodeBoxType
        NODEBOX_FIXED, // Static separately defined box(es)
        NODEBOX_WALLMOUNTED, // Box for wall mounted nodes; (top, bottom, side)
        NODEBOX_LEVELED, // Same as fixed, but with dynamic height from param2. for snow, ...
+       NODEBOX_CONNECTED, // optionally draws nodeboxes if a neighbor node attaches
 };
 
 struct NodeBox
@@ -92,6 +93,13 @@ struct NodeBox
        aabb3f wall_top;
        aabb3f wall_bottom;
        aabb3f wall_side; // being at the -X side
+       // NODEBOX_CONNECTED
+       std::vector<aabb3f> connect_top;
+       std::vector<aabb3f> connect_bottom;
+       std::vector<aabb3f> connect_front;
+       std::vector<aabb3f> connect_left;
+       std::vector<aabb3f> connect_back;
+       std::vector<aabb3f> connect_right;
 
        NodeBox()
        { reset(); }
@@ -269,6 +277,9 @@ struct ContentFeatures
        SimpleSoundSpec sound_dig;
        SimpleSoundSpec sound_dug;
 
+       std::vector<std::string> connects_to;
+       std::set<content_t> connects_to_ids;
+
        /*
                Methods
        */
@@ -314,6 +325,7 @@ public:
 
        virtual void pendNodeResolve(NodeResolver *nr)=0;
        virtual bool cancelNodeResolveCallback(NodeResolver *nr)=0;
+       virtual bool nodeboxConnects(const MapNode from, const MapNode to)=0;
 };
 
 class IWritableNodeDefManager : public INodeDefManager {
@@ -368,6 +380,7 @@ public:
        virtual bool cancelNodeResolveCallback(NodeResolver *nr)=0;
        virtual void runNodeResolveCallbacks()=0;
        virtual void resetNodeResolveState()=0;
+       virtual void mapNodeboxConnections()=0;
 };
 
 IWritableNodeDefManager *createNodeDefManager();
index 96fb78d997c7c0e7eae526cb695dce82a61176dd..ababf0718ebab51f1f61a346019266e555c56c56 100644 (file)
@@ -535,6 +535,18 @@ ContentFeatures read_content_features(lua_State *L, int index)
                f.node_box = read_nodebox(L, -1);
        lua_pop(L, 1);
 
+       lua_getfield(L, index, "connects_to");
+       if (lua_istable(L, -1)) {
+               int table = lua_gettop(L);
+               lua_pushnil(L);
+               while (lua_next(L, table) != 0) {
+                       // Value at -1
+                       f.connects_to.push_back(lua_tostring(L, -1));
+                       lua_pop(L, 1);
+               }
+       }
+       lua_pop(L, 1);
+
        lua_getfield(L, index, "selection_box");
        if(lua_istable(L, -1))
                f.selection_box = read_nodebox(L, -1);
@@ -627,25 +639,31 @@ NodeBox read_nodebox(lua_State *L, int index)
                nodebox.type = (NodeBoxType)getenumfield(L, index, "type",
                                ScriptApiNode::es_NodeBoxType, NODEBOX_REGULAR);
 
-               lua_getfield(L, index, "fixed");
-               if(lua_istable(L, -1))
-                       nodebox.fixed = read_aabb3f_vector(L, -1, BS);
-               lua_pop(L, 1);
-
-               lua_getfield(L, index, "wall_top");
-               if(lua_istable(L, -1))
-                       nodebox.wall_top = read_aabb3f(L, -1, BS);
-               lua_pop(L, 1);
-
-               lua_getfield(L, index, "wall_bottom");
-               if(lua_istable(L, -1))
-                       nodebox.wall_bottom = read_aabb3f(L, -1, BS);
-               lua_pop(L, 1);
-
-               lua_getfield(L, index, "wall_side");
-               if(lua_istable(L, -1))
-                       nodebox.wall_side = read_aabb3f(L, -1, BS);
-               lua_pop(L, 1);
+#define NODEBOXREAD(n, s) \
+       do { \
+               lua_getfield(L, index, (s)); \
+               if (lua_istable(L, -1)) \
+                       (n) = read_aabb3f(L, -1, BS); \
+               lua_pop(L, 1); \
+       } while (0)
+
+#define NODEBOXREADVEC(n, s) \
+       do { \
+               lua_getfield(L, index, (s)); \
+               if (lua_istable(L, -1)) \
+                       (n) = read_aabb3f_vector(L, -1, BS); \
+               lua_pop(L, 1); \
+       } while (0)
+               NODEBOXREADVEC(nodebox.fixed, "fixed");
+               NODEBOXREAD(nodebox.wall_top, "wall_top");
+               NODEBOXREAD(nodebox.wall_bottom, "wall_bottom");
+               NODEBOXREAD(nodebox.wall_side, "wall_side");
+               NODEBOXREADVEC(nodebox.connect_top, "connect_top");
+               NODEBOXREADVEC(nodebox.connect_bottom, "connect_bottom");
+               NODEBOXREADVEC(nodebox.connect_front, "connect_front");
+               NODEBOXREADVEC(nodebox.connect_left, "connect_left");
+               NODEBOXREADVEC(nodebox.connect_back, "connect_back");
+               NODEBOXREADVEC(nodebox.connect_right, "connect_right");
        }
        return nodebox;
 }
index a905f843de4c0127493a2b6d1ecc9e4d9466d1ba..17f0f0dac3be395ba7b0cd049351534ad2dcaf17 100644 (file)
@@ -82,6 +82,7 @@ struct EnumString ScriptApiNode::es_NodeBoxType[] =
                {NODEBOX_FIXED, "fixed"},
                {NODEBOX_WALLMOUNTED, "wallmounted"},
                {NODEBOX_LEVELED, "leveled"},
+               {NODEBOX_CONNECTED, "connected"},
                {0, NULL},
        };
 
index 2fa8c4cf8512f9f95a86d42259042eed0e0dfaae..6c008a2a138c3a7c7491e99bc1798c7c405b499f 100644 (file)
@@ -320,6 +320,9 @@ Server::Server(
        // Perform pending node name resolutions
        m_nodedef->runNodeResolveCallbacks();
 
+       // unmap node names for connected nodeboxes
+       m_nodedef->mapNodeboxConnections();
+
        // init the recipe hashes to speed up crafting
        m_craftdef->initHashes(this);