diff --git a/SRB2.cbp b/SRB2.cbp
index 74ec96c6eeb8b0ee22b6260e883bc2d421bc57ad..3b9befc7b7e110439e94c32906e34246838c8e4e 100644
--- a/SRB2.cbp
+++ b/SRB2.cbp
@@ -1516,6 +1516,21 @@ HW3SOUND for 3D hardware sound  support
 		<Unit filename="src/lua_baselib.c">
 			<Option compilerVar="CC" />
 		</Unit>
+		<Unit filename="src/lua_blockmaplib.c">
+			<Option compilerVar="CC" />
+			<Option target="Debug Native/SDL" />
+			<Option target="Release Native/SDL" />
+			<Option target="Debug Mingw/SDL" />
+			<Option target="Release Mingw/SDL" />
+			<Option target="Debug Mingw/DirectX" />
+			<Option target="Release Mingw/DirectX" />
+			<Option target="Debug Linux/SDL" />
+			<Option target="Release Linux/SDL" />
+			<Option target="Debug Mingw64/SDL" />
+			<Option target="Release Mingw64/SDL" />
+			<Option target="Debug Mingw64/DirectX" />
+			<Option target="Release Mingw64/DirectX" />
+		</Unit>
 		<Unit filename="src/lua_consolelib.c">
 			<Option compilerVar="CC" />
 		</Unit>
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index ba354c2899870ccb7d4d850863b6a20ebd618110..cc728a61363ae095ee6746cc36811c1d7f981731 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -231,6 +231,7 @@ if(${SRB2_CONFIG_HAVE_BLUA})
 	add_definitions(-DHAVE_BLUA)
 	set(SRB2_LUA_SOURCES
 		lua_baselib.c
+		lua_blockmaplib.c
 		lua_consolelib.c
 		lua_hooklib.c
 		lua_hudlib.c
diff --git a/src/blua/Makefile.cfg b/src/blua/Makefile.cfg
index e3fb3df4664e4943aff54361dd32be4500675bdd..8d2e7371428db75ced4042be0f33167a594d65f3 100644
--- a/src/blua/Makefile.cfg
+++ b/src/blua/Makefile.cfg
@@ -48,4 +48,5 @@ OBJS:=$(OBJS) \
 	$(OBJDIR)/lua_skinlib.o \
 	$(OBJDIR)/lua_thinkerlib.o \
 	$(OBJDIR)/lua_maplib.o \
+	$(OBJDIR)/lua_blockmaplib.o \
 	$(OBJDIR)/lua_hudlib.o
diff --git a/src/dehacked.c b/src/dehacked.c
index 6d52f95cbd5e5b05dfcef5e4ece890d3edef320b..56a211be86fb8a63bd9d563c737abd49c48412ff 100644
--- a/src/dehacked.c
+++ b/src/dehacked.c
@@ -7578,6 +7578,11 @@ struct {
 	{"FF_COLORMAPONLY",FF_COLORMAPONLY},       ///< Only copy the colormap, not the lightlevel
 	{"FF_GOOWATER",FF_GOOWATER},               ///< Used with ::FF_SWIMMABLE. Makes thick bouncey goop.
 
+#ifdef HAVE_LUA_SEGS
+	// Node flags
+	{"NF_SUBSECTOR",NF_SUBSECTOR}, // Indicate a leaf.
+#endif
+
 	// Angles
 	{"ANG1",ANG1},
 	{"ANG2",ANG2},
diff --git a/src/lua_baselib.c b/src/lua_baselib.c
index 0d59ff25687b9974402b146e17ed62a3f9d93405..def0ad1b37dc42460e9fb3675638e281a93b6236 100644
--- a/src/lua_baselib.c
+++ b/src/lua_baselib.c
@@ -27,7 +27,6 @@
 
 #define NOHUD if (hud_running) return luaL_error(L, "HUD rendering code should not call this function!");
 
-
 boolean luaL_checkboolean(lua_State *L, int narg) {
 	luaL_checktype(L, narg, LUA_TBOOLEAN);
 	return lua_toboolean(L, narg);
@@ -205,6 +204,41 @@ static int lib_pClosestPointOnLine(lua_State *L)
 	return 2;
 }
 
+static int lib_pPointOnLineSide(lua_State *L)
+{
+	int n = lua_gettop(L);
+	fixed_t x = luaL_checkfixed(L, 1);
+	fixed_t y = luaL_checkfixed(L, 2);
+	//HUDSAFE
+	if (lua_isuserdata(L, 3)) // use a real linedef to get our points
+	{
+		line_t *line = *((line_t **)luaL_checkudata(L, 3, META_LINE));
+		if (!line)
+			return LUA_ErrInvalid(L, "line_t");
+		lua_pushinteger(L, P_PointOnLineSide(x, y, line));
+	}
+	else // use custom coordinates of our own!
+	{
+		vertex_t v1, v2; // fake vertexes
+		line_t junk; // fake linedef
+
+		if (n < 6)
+			return luaL_error(L, "arguments 3 to 6 not all given (expected 4 fixed-point integers)");
+
+		v1.x = luaL_checkfixed(L, 3);
+		v1.y = luaL_checkfixed(L, 4);
+		v2.x = luaL_checkfixed(L, 5);
+		v2.y = luaL_checkfixed(L, 6);
+
+		junk.v1 = &v1;
+		junk.v2 = &v2;
+		junk.dx = v2.x - v1.x;
+		junk.dy = v2.y - v1.y;
+		lua_pushinteger(L, P_PointOnLineSide(x, y, &junk));
+	}
+	return 1;
+}
+
 // P_ENEMY
 /////////////
 
@@ -2047,6 +2081,7 @@ static luaL_Reg lib[] = {
 	// p_maputil
 	{"P_AproxDistance",lib_pAproxDistance},
 	{"P_ClosestPointOnLine",lib_pClosestPointOnLine},
+	{"P_PointOnLineSide",lib_pPointOnLineSide},
 
 	// p_enemy
 	{"P_CheckMeleeRange", lib_pCheckMeleeRange},
diff --git a/src/lua_blockmaplib.c b/src/lua_blockmaplib.c
new file mode 100644
index 0000000000000000000000000000000000000000..33f350d6912863df71db33a36f070af221c38f34
--- /dev/null
+++ b/src/lua_blockmaplib.c
@@ -0,0 +1,266 @@
+// SONIC ROBO BLAST 2
+//-----------------------------------------------------------------------------
+// Copyright (C) 2016 by Iestyn "Monster Iestyn" Jealous.
+// Copyright (C) 2016 by Sonic Team Junior.
+//
+// This program is free software distributed under the
+// terms of the GNU General Public License, version 2.
+// See the 'LICENSE' file for more details.
+//-----------------------------------------------------------------------------
+/// \file  lua_blockmaplib.c
+/// \brief blockmap library for Lua scripting
+
+#include "doomdef.h"
+#ifdef HAVE_BLUA
+#include "p_local.h"
+#include "r_main.h" // validcount
+#include "lua_script.h"
+#include "lua_libs.h"
+//#include "lua_hud.h" // hud_running errors
+
+static const char *const search_opt[] = {
+	"objects",
+	"lines",
+	NULL};
+
+// a quickly-made function pointer typedef used by lib_searchBlockmap...
+// return values:
+// 0 - normal, no interruptions
+// 1 - stop search through current block
+// 2 - stop search completely
+typedef UINT8 (*blockmap_func)(lua_State *, INT32, INT32, mobj_t *);
+
+static boolean blockfuncerror = false; // errors should only print once per search blockmap call
+
+// Helper function for "objects" search
+static UINT8 lib_searchBlockmap_Objects(lua_State *L, INT32 x, INT32 y, mobj_t *thing)
+{
+	mobj_t *mobj, *bnext = NULL;
+
+	if (x < 0 || y < 0 || x >= bmapwidth || y >= bmapheight)
+		return 0;
+
+	// Check interaction with the objects in the blockmap.
+	for (mobj = blocklinks[y*bmapwidth + x]; mobj; mobj = bnext)
+	{
+		P_SetTarget(&bnext, mobj->bnext); // We want to note our reference to bnext here incase it is MF_NOTHINK and gets removed!
+		if (mobj == thing)
+			continue; // our thing just found itself, so move on
+		lua_pushvalue(L, 1); // push function
+		LUA_PushUserdata(L, thing, META_MOBJ);
+		LUA_PushUserdata(L, mobj, META_MOBJ);
+		if (lua_pcall(gL, 2, 1, 0)) {
+			if (!blockfuncerror || cv_debug & DBG_LUA)
+				CONS_Alert(CONS_WARNING,"%s\n",lua_tostring(gL, -1));
+			lua_pop(gL, 1);
+			blockfuncerror = true;
+			return 0; // *shrugs*
+		}
+		if (!lua_isnil(gL, -1))
+		{ // if nil, continue
+			if (lua_toboolean(gL, -1))
+				return 2; // stop whole search
+			else
+				return 1; // stop block search
+		}
+		lua_pop(gL, 1);
+		if (P_MobjWasRemoved(thing) // func just popped our thing, cannot continue.
+		|| (bnext && P_MobjWasRemoved(bnext))) // func just broke blockmap chain, cannot continue.
+		{
+			P_SetTarget(&bnext, NULL);
+			return (P_MobjWasRemoved(thing)) ? 2 : 1;
+		}
+	}
+	return 0;
+}
+
+// Helper function for "lines" search
+static UINT8 lib_searchBlockmap_Lines(lua_State *L, INT32 x, INT32 y, mobj_t *thing)
+{
+	INT32 offset;
+	const INT32 *list; // Big blockmap
+#ifdef POLYOBJECTS
+	polymaplink_t *plink; // haleyjd 02/22/06
+#endif
+	line_t *ld;
+
+	if (x < 0 || y < 0 || x >= bmapwidth || y >= bmapheight)
+		return 0;
+
+	offset = y*bmapwidth + x;
+
+#ifdef POLYOBJECTS
+	// haleyjd 02/22/06: consider polyobject lines
+	plink = polyblocklinks[offset];
+
+	while (plink)
+	{
+		polyobj_t *po = plink->po;
+
+		if (po->validcount != validcount) // if polyobj hasn't been checked
+		{
+			size_t i;
+			po->validcount = validcount;
+
+			for (i = 0; i < po->numLines; ++i)
+			{
+				if (po->lines[i]->validcount == validcount) // line has been checked
+					continue;
+				po->lines[i]->validcount = validcount;
+
+				lua_pushvalue(L, 1);
+				LUA_PushUserdata(L, thing, META_MOBJ);
+				LUA_PushUserdata(L, po->lines[i], META_LINE);
+				if (lua_pcall(gL, 2, 1, 0)) {
+					if (!blockfuncerror || cv_debug & DBG_LUA)
+						CONS_Alert(CONS_WARNING,"%s\n",lua_tostring(gL, -1));
+					lua_pop(gL, 1);
+					blockfuncerror = true;
+					return 0; // *shrugs*
+				}
+				if (!lua_isnil(gL, -1))
+				{ // if nil, continue
+					if (lua_toboolean(gL, -1))
+						return 2; // stop whole search
+					else
+						return 1; // stop block search
+				}
+				lua_pop(gL, 1);
+				if (P_MobjWasRemoved(thing))
+					return 2;
+			}
+		}
+		plink = (polymaplink_t *)(plink->link.next);
+	}
+#endif
+
+	offset = *(blockmap + offset); // offset = blockmap[y*bmapwidth+x];
+
+	// First index is really empty, so +1 it.
+	for (list = blockmaplump + offset + 1; *list != -1; list++)
+	{
+		ld = &lines[*list];
+
+		if (ld->validcount == validcount)
+			continue; // Line has already been checked.
+
+		ld->validcount = validcount;
+
+		lua_pushvalue(L, 1);
+		LUA_PushUserdata(L, thing, META_MOBJ);
+		LUA_PushUserdata(L, ld, META_LINE);
+		if (lua_pcall(gL, 2, 1, 0)) {
+			if (!blockfuncerror || cv_debug & DBG_LUA)
+				CONS_Alert(CONS_WARNING,"%s\n",lua_tostring(gL, -1));
+			lua_pop(gL, 1);
+			blockfuncerror = true;
+			return 0; // *shrugs*
+		}
+		if (!lua_isnil(gL, -1))
+		{ // if nil, continue
+			if (lua_toboolean(gL, -1))
+				return 2; // stop whole search
+			else
+				return 1; // stop block search
+		}
+		lua_pop(gL, 1);
+		if (P_MobjWasRemoved(thing))
+			return 2;
+	}
+	return 0; // Everything was checked.
+}
+
+// The searchBlockmap function
+// arguments: searchBlockmap(searchtype, function, mobj, [x1, x2, y1, y2])
+// return value:
+//   true = search completely uninteruppted,
+//   false = searching of at least one block stopped mid-way (including if the whole search was stopped)
+static int lib_searchBlockmap(lua_State *L)
+{
+	int searchtype = luaL_checkoption(L, 1, "objects", search_opt);
+	int n;
+	mobj_t *mobj;
+	INT32 xl, xh, yl, yh, bx, by;
+	fixed_t x1, x2, y1, y2;
+	boolean retval = true;
+	UINT8 funcret = 0;
+	blockmap_func searchFunc;
+
+	lua_remove(L, 1); // remove searchtype, stack is now function, mobj, [x1, x2, y1, y2]
+	luaL_checktype(L, 1, LUA_TFUNCTION);
+
+	switch (searchtype)
+	{
+		case 0: // "objects"
+		default:
+			searchFunc = lib_searchBlockmap_Objects;
+			break;
+		case 1: // "lines"
+			searchFunc = lib_searchBlockmap_Lines;
+			break;
+	}
+
+	// the mobj we are searching around, the "calling" mobj we could say
+	mobj = *((mobj_t **)luaL_checkudata(L, 2, META_MOBJ));
+	if (!mobj)
+		return LUA_ErrInvalid(L, "mobj_t");
+
+	n = lua_gettop(L);
+
+	if (n > 2) // specific x/y ranges have been supplied
+	{
+		if (n < 6)
+			return luaL_error(L, "arguments 4 to 6 not all given (expected 4 fixed-point integers)");
+
+		x1 = luaL_checkfixed(L, 3);
+		x2 = luaL_checkfixed(L, 4);
+		y1 = luaL_checkfixed(L, 5);
+		y2 = luaL_checkfixed(L, 6);
+	}
+	else // mobj and function only - search around mobj's radius by default
+	{
+		fixed_t radius = mobj->radius + MAXRADIUS;
+		x1 = mobj->x - radius;
+		x2 = mobj->x + radius;
+		y1 = mobj->y - radius;
+		y2 = mobj->y + radius;
+	}
+	lua_settop(L, 2); // pop everything except function, mobj
+
+	xl = (unsigned)(x1 - bmaporgx)>>MAPBLOCKSHIFT;
+	xh = (unsigned)(x2 - bmaporgx)>>MAPBLOCKSHIFT;
+	yl = (unsigned)(y1 - bmaporgy)>>MAPBLOCKSHIFT;
+	yh = (unsigned)(y2 - bmaporgy)>>MAPBLOCKSHIFT;
+
+	BMBOUNDFIX(xl, xh, yl, yh);
+
+	blockfuncerror = false; // reset
+	validcount++;
+	for (bx = xl; bx <= xh; bx++)
+		for (by = yl; by <= yh; by++)
+		{
+			funcret = searchFunc(L, bx, by, mobj);
+			// return value of searchFunc determines searchFunc's return value and/or when to stop
+			if (funcret == 2){ // stop whole search
+				lua_pushboolean(L, false); // return false
+				return 1;
+			}
+			else if (funcret == 1) // search was interrupted for this block
+				retval = false; // this changes the return value, but doesn't stop the whole search
+			// else don't do anything, continue as normal
+			if (P_MobjWasRemoved(mobj)){ // ...unless the original object was removed
+				lua_pushboolean(L, false); // in which case we have to stop now regardless
+				return 1;
+			}	
+		}
+	lua_pushboolean(L, retval);
+	return 1;
+}
+
+int LUA_BlockmapLib(lua_State *L)
+{
+	lua_register(L, "searchBlockmap", lib_searchBlockmap);
+	return 0;
+}
+
+#endif
\ No newline at end of file
diff --git a/src/lua_hudlib.c b/src/lua_hudlib.c
index 60cbbe5018f28fb657cee6ec608d275386b53411..5b3cd46ce8f86cf3f987dc716d2b4f0e0c86d7f3 100644
--- a/src/lua_hudlib.c
+++ b/src/lua_hudlib.c
@@ -226,7 +226,12 @@ static int hudinfo_num(lua_State *L)
 
 static int colormap_get(lua_State *L)
 {
-	return luaL_error(L, "colormap is not a struct.");
+	const UINT8 *colormap = *((UINT8 **)luaL_checkudata(L, 1, META_COLORMAP));
+	UINT32 i = luaL_checkinteger(L, 2);
+	if (i >= 256)
+		return luaL_error(L, "colormap index %d out of range (0 - %d)", i, 255);
+	lua_pushinteger(L, colormap[i]);
+	return 1;
 }
 
 static int patch_get(lua_State *L)
diff --git a/src/lua_libs.h b/src/lua_libs.h
index 931cf62d0bc11901d303969d821dd30a30266e6c..fd4937b6354207a63a73f1a4c9318d7e802083c7 100644
--- a/src/lua_libs.h
+++ b/src/lua_libs.h
@@ -38,12 +38,22 @@ extern lua_State *gL;
 #define META_SUBSECTOR "SUBSECTOR_T*"
 #define META_SECTOR "SECTOR_T*"
 #define META_FFLOOR "FFLOOR_T*"
+#ifdef HAVE_LUA_SEGS
+#define META_SEG "SEG_T*"
+#define META_NODE "NODE_T*"
+#endif
 #define META_MAPHEADER "MAPHEADER_T*"
 
 #define META_CVAR "CONSVAR_T*"
 
 #define META_SECTORLINES "SECTOR_T*LINES"
 #define META_SIDENUM "LINE_T*SIDENUM"
+#ifdef HAVE_LUA_SEGS
+#define META_NODEBBOX "NODE_T*BBOX"
+#define META_NODECHILDREN "NODE_T*CHILDREN"
+#endif
+
+#define META_BBOX "BOUNDING_BOX"
 
 #define META_HUDINFO "HUDINFO_T*"
 #define META_PATCH "PATCH_T*"
@@ -64,6 +74,7 @@ int LUA_PlayerLib(lua_State *L);
 int LUA_SkinLib(lua_State *L);
 int LUA_ThinkerLib(lua_State *L);
 int LUA_MapLib(lua_State *L);
+int LUA_BlockmapLib(lua_State *L);
 int LUA_HudLib(lua_State *L);
 
 #endif
diff --git a/src/lua_maplib.c b/src/lua_maplib.c
index 208aebe37951b484b5212637f774ea837f701729..9f6d3e7fa34a87d16518d72288f3700343b55da9 100644
--- a/src/lua_maplib.c
+++ b/src/lua_maplib.c
@@ -185,6 +185,82 @@ static const char *const ffloor_opt[] = {
 	"alpha",
 	NULL};
 
+#ifdef HAVE_LUA_SEGS
+enum seg_e {
+	seg_valid = 0,
+	seg_v1,
+	seg_v2,
+	seg_side,
+	seg_offset,
+	seg_angle,
+	seg_sidedef,
+	seg_linedef,
+	seg_frontsector,
+	seg_backsector,
+};
+
+static const char *const seg_opt[] = {
+	"valid",
+	"v1",
+	"v2",
+	"side",
+	"offset",
+	"angle",
+	"sidedef",
+	"linedef",
+	"frontsector",
+	"backsector",
+	NULL};
+
+enum node_e {
+	node_valid = 0,
+	node_x,
+	node_y,
+	node_dx,
+	node_dy,
+	node_bbox,
+	node_children,
+};
+
+static const char *const node_opt[] = {
+	"valid",
+	"x",
+	"y",
+	"dx",
+	"dy",
+	"bbox",
+	"children",
+	NULL};
+
+enum nodechild_e {
+	nodechild_valid = 0,
+	nodechild_right,
+	nodechild_left,
+};
+
+static const char *const nodechild_opt[] = {
+	"valid",
+	"right",
+	"left",
+	NULL};
+#endif
+
+enum bbox_e {
+	bbox_valid = 0,
+	bbox_top,
+	bbox_bottom,
+	bbox_left,
+	bbox_right,
+};
+
+static const char *const bbox_opt[] = {
+	"valid",
+	"top",
+	"bottom",
+	"left",
+	"right",
+	NULL};
+
 static const char *const array_opt[] ={"iterate",NULL};
 static const char *const valid_opt[] ={"valid",NULL};
 
@@ -768,6 +844,262 @@ static int vertex_num(lua_State *L)
 	return 1;
 }
 
+#ifdef HAVE_LUA_SEGS
+static int seg_get(lua_State *L)
+{
+	seg_t *seg = *((seg_t **)luaL_checkudata(L, 1, META_SEG));
+	enum seg_e field = luaL_checkoption(L, 2, seg_opt[0], seg_opt);
+
+	if (!seg)
+	{
+		if (field == seg_valid) {
+			lua_pushboolean(L, 0);
+			return 1;
+		}
+		return luaL_error(L, "accessed seg_t doesn't exist anymore.");
+	}
+
+	switch(field)
+	{
+	case seg_valid: // valid
+		lua_pushboolean(L, 1);
+		return 1;
+	case seg_v1:
+		LUA_PushUserdata(L, seg->v1, META_VERTEX);
+		return 1;
+	case seg_v2:
+		LUA_PushUserdata(L, seg->v2, META_VERTEX);
+		return 1;
+	case seg_side:
+		lua_pushinteger(L, seg->side);
+		return 1;
+	case seg_offset:
+		lua_pushfixed(L, seg->offset);
+		return 1;
+	case seg_angle:
+		lua_pushangle(L, seg->angle);
+		return 1;
+	case seg_sidedef:
+		LUA_PushUserdata(L, seg->sidedef, META_SIDE);
+		return 1;
+	case seg_linedef:
+		LUA_PushUserdata(L, seg->linedef, META_LINE);
+		return 1;
+	case seg_frontsector:
+		LUA_PushUserdata(L, seg->frontsector, META_SECTOR);
+		return 1;
+	case seg_backsector:
+		LUA_PushUserdata(L, seg->backsector, META_SECTOR);
+		return 1;
+	}
+	return 0;
+}
+
+static int seg_num(lua_State *L)
+{
+	seg_t *seg = *((seg_t **)luaL_checkudata(L, 1, META_SEG));
+	lua_pushinteger(L, seg-segs);
+	return 1;
+}
+
+static int node_get(lua_State *L)
+{
+	node_t *node = *((node_t **)luaL_checkudata(L, 1, META_NODE));
+	enum node_e field = luaL_checkoption(L, 2, node_opt[0], node_opt);
+
+	if (!node)
+	{
+		if (field == node_valid) {
+			lua_pushboolean(L, 0);
+			return 1;
+		}
+		return luaL_error(L, "accessed node_t doesn't exist anymore.");
+	}
+
+	switch(field)
+	{
+	case node_valid: // valid
+		lua_pushboolean(L, 1);
+		return 1;
+	case node_x:
+		lua_pushfixed(L, node->x);
+		return 1;
+	case node_y:
+		lua_pushfixed(L, node->y);
+		return 1;
+	case node_dx:
+		lua_pushfixed(L, node->x);
+		return 1;
+	case node_dy:
+		lua_pushfixed(L, node->x);
+		return 1;
+	case node_bbox:
+		LUA_PushUserdata(L, node->bbox, META_NODEBBOX);
+		return 1;
+	case node_children:
+		LUA_PushUserdata(L, node->children, META_NODECHILDREN);
+		return 1;
+	}
+	return 0;
+}
+
+static int node_num(lua_State *L)
+{
+	node_t *node = *((node_t **)luaL_checkudata(L, 1, META_NODE));
+	lua_pushinteger(L, node-nodes);
+	return 1;
+}
+/*
+// node.bbox[i][j]: i = 0 or 1, j = 0 1 2 or 3
+// NOTE: 2D arrays are NOT double pointers,
+//       the second bbox will be directly after the first in memory (hence the way the bbox is pushed here)
+// this function handles the [i] part, bbox_get handles the [j] part
+static int nodebbox_get(lua_State *L)
+{
+	fixed_t *bbox = *((fixed_t **)luaL_checkudata(L, 1, META_NODEBBOX));
+	int i;
+	lua_settop(L, 2);
+	if (!lua_isnumber(L, 2))
+	{
+		int field = luaL_checkoption(L, 2, NULL, valid_opt);
+		if (!bbox)
+		{
+			if (field == 0) {
+				lua_pushboolean(L, 0);
+				return 1;
+			}
+			return luaL_error(L, "accessed node_t doesn't exist anymore.");
+		} else if (field == 0) {
+			lua_pushboolean(L, 1);
+			return 1;
+		}
+	}
+
+	i = lua_tointeger(L, 2);
+	if (i < 0 || i > 1)
+		return 0;
+	LUA_PushUserdata(L, bbox + i*4*sizeof(fixed_t), META_BBOX);
+	return 1;
+}
+*/
+static int nodebbox_call(lua_State *L)
+{
+	fixed_t *bbox = *((fixed_t **)luaL_checkudata(L, 1, META_NODEBBOX));
+	int i, j;
+	int n = lua_gettop(L);
+
+	if (!bbox)
+		return luaL_error(L, "accessed node bbox doesn't exist anymore.");
+	if (n < 3)
+		return luaL_error(L, "arguments 2 and/or 3 not given (expected node.bbox(child, coord))");
+	// get child
+	if (!lua_isnumber(L, 2)) {
+		enum nodechild_e field = luaL_checkoption(L, 2, nodechild_opt[0], nodechild_opt);
+		switch (field) {
+			case nodechild_right: i = 0; break;
+			case nodechild_left:  i = 1; break;
+			default:
+				return luaL_error(L, "invalid node child \"%s\".", lua_tostring(L, 2));
+		}
+	}
+	else {
+		i = lua_tointeger(L, 2);
+		if (i < 0 || i > 1)
+			return 0;
+	}
+	// get bbox coord
+	if (!lua_isnumber(L, 3)) {
+		enum bbox_e field = luaL_checkoption(L, 3, bbox_opt[0], bbox_opt);
+		switch (field) {
+			case bbox_top:    j = BOXTOP;    break;
+			case bbox_bottom: j = BOXBOTTOM; break;
+			case bbox_left:   j = BOXLEFT;   break;
+			case bbox_right:  j = BOXRIGHT;  break;
+			default:
+				return luaL_error(L, "invalid bbox coordinate \"%s\".", lua_tostring(L, 3));
+		}
+	}
+	else {
+		j = lua_tointeger(L, 3);
+		if (j < 0 || j > 3)
+			return 0;
+	}
+	lua_pushinteger(L, bbox[i*4 + j]);
+	return 1;
+}
+
+// node.children[i]: i = 0 or 1
+static int nodechildren_get(lua_State *L)
+{
+	UINT16 *children = *((UINT16 **)luaL_checkudata(L, 1, META_NODECHILDREN));
+	int i;
+	lua_settop(L, 2);
+	if (!lua_isnumber(L, 2))
+	{
+		enum nodechild_e field = luaL_checkoption(L, 2, nodechild_opt[0], nodechild_opt);
+		if (!children)
+		{
+			if (field == nodechild_valid) {
+				lua_pushboolean(L, 0);
+				return 1;
+			}
+			return luaL_error(L, "accessed node_t doesn't exist anymore.");
+		} else if (field == nodechild_valid) {
+			lua_pushboolean(L, 1);
+			return 1;
+		} else switch (field) {
+			case nodechild_right: i = 0; break;
+			case nodechild_left:  i = 1; break;
+			default:              return 0;
+		}
+	}
+	else {
+		i = lua_tointeger(L, 2);
+		if (i < 0 || i > 1)
+			return 0;
+	}
+	lua_pushinteger(L, children[i]);
+	return 1;
+}
+#endif
+
+// bounding box (aka fixed_t array with four elements)
+// NOTE: may be useful for polyobjects or other things later
+static int bbox_get(lua_State *L)
+{
+	fixed_t *bbox = *((fixed_t **)luaL_checkudata(L, 1, META_BBOX));
+	int i;
+	lua_settop(L, 2);
+	if (!lua_isnumber(L, 2))
+	{
+		enum bbox_e field = luaL_checkoption(L, 2, bbox_opt[0], bbox_opt);
+		if (!bbox)
+		{
+			if (field == bbox_valid) {
+				lua_pushboolean(L, 0);
+				return 1;
+			}
+			return luaL_error(L, "accessed bbox doesn't exist anymore.");
+		} else if (field == bbox_valid) {
+			lua_pushboolean(L, 1);
+			return 1;
+		} else switch (field) {
+			case bbox_top:    i = BOXTOP;    break;
+			case bbox_bottom: i = BOXBOTTOM; break;
+			case bbox_left:   i = BOXLEFT;   break;
+			case bbox_right:  i = BOXRIGHT;  break;
+			default:          return 0;
+		}
+	}
+	else {
+		i = lua_tointeger(L, 2);
+		if (i < 0 || i > 3)
+			return 0;
+	}
+	lua_pushinteger(L, bbox[i]);
+	return 1;
+}
+
 static int lib_iterateSectors(lua_State *L)
 {
 	size_t i = 0;
@@ -998,6 +1330,100 @@ static int lib_numvertexes(lua_State *L)
 	return 1;
 }
 
+#ifdef HAVE_LUA_SEGS
+static int lib_iterateSegs(lua_State *L)
+{
+	size_t i = 0;
+	if (lua_gettop(L) < 2)
+		return luaL_error(L, "Don't call segs.iterate() directly, use it as 'for seg in segs.iterate do <block> end'.");
+	lua_settop(L, 2);
+	lua_remove(L, 1); // state is unused.
+	if (!lua_isnil(L, 1))
+		i = (size_t)(*((seg_t **)luaL_checkudata(L, 1, META_SEG)) - segs)+1;
+	if (i < numsegs)
+	{
+		LUA_PushUserdata(L, &segs[i], META_SEG);
+		return 1;
+	}
+	return 0;
+}
+
+static int lib_getSeg(lua_State *L)
+{
+	int field;
+	lua_settop(L, 2);
+	lua_remove(L, 1); // dummy userdata table is unused.
+	if (lua_isnumber(L, 1))
+	{
+		size_t i = lua_tointeger(L, 1);
+		if (i >= numsegs)
+			return 0;
+		LUA_PushUserdata(L, &segs[i], META_SEG);
+		return 1;
+	}
+	field = luaL_checkoption(L, 1, NULL, array_opt);
+	switch(field)
+	{
+	case 0: // iterate
+		lua_pushcfunction(L, lib_iterateSegs);
+		return 1;
+	}
+	return 0;
+}
+
+static int lib_numsegs(lua_State *L)
+{
+	lua_pushinteger(L, numsegs);
+	return 1;
+}
+
+static int lib_iterateNodes(lua_State *L)
+{
+	size_t i = 0;
+	if (lua_gettop(L) < 2)
+		return luaL_error(L, "Don't call nodes.iterate() directly, use it as 'for node in nodes.iterate do <block> end'.");
+	lua_settop(L, 2);
+	lua_remove(L, 1); // state is unused.
+	if (!lua_isnil(L, 1))
+		i = (size_t)(*((node_t **)luaL_checkudata(L, 1, META_NODE)) - nodes)+1;
+	if (i < numsegs)
+	{
+		LUA_PushUserdata(L, &nodes[i], META_NODE);
+		return 1;
+	}
+	return 0;
+}
+
+static int lib_getNode(lua_State *L)
+{
+	int field;
+	lua_settop(L, 2);
+	lua_remove(L, 1); // dummy userdata table is unused.
+	if (lua_isnumber(L, 1))
+	{
+		size_t i = lua_tointeger(L, 1);
+		if (i >= numnodes)
+			return 0;
+		LUA_PushUserdata(L, &nodes[i], META_NODE);
+		return 1;
+	}
+	field = luaL_checkoption(L, 1, NULL, array_opt);
+	switch(field)
+	{
+	case 0: // iterate
+		lua_pushcfunction(L, lib_iterateNodes);
+		return 1;
+	}
+	return 0;
+}
+
+static int lib_numnodes(lua_State *L)
+{
+	lua_pushinteger(L, numnodes);
+	return 1;
+}
+#endif
+
 static int ffloor_get(lua_State *L)
 {
 	ffloor_t *ffloor = *((ffloor_t **)luaL_checkudata(L, 1, META_FFLOOR));
@@ -1317,6 +1743,41 @@ int LUA_MapLib(lua_State *L)
 		lua_setfield(L, -2, "__newindex");
 	lua_pop(L, 1);
 
+#ifdef HAVE_LUA_SEGS
+	luaL_newmetatable(L, META_SEG);
+		lua_pushcfunction(L, seg_get);
+		lua_setfield(L, -2, "__index");
+
+		lua_pushcfunction(L, seg_num);
+		lua_setfield(L, -2, "__len");
+	lua_pop(L, 1);
+
+	luaL_newmetatable(L, META_NODE);
+		lua_pushcfunction(L, node_get);
+		lua_setfield(L, -2, "__index");
+
+		lua_pushcfunction(L, node_num);
+		lua_setfield(L, -2, "__len");
+	lua_pop(L, 1);
+
+	luaL_newmetatable(L, META_NODEBBOX);
+		//lua_pushcfunction(L, nodebbox_get);
+		//lua_setfield(L, -2, "__index");
+		lua_pushcfunction(L, nodebbox_call);
+		lua_setfield(L, -2, "__call");
+	lua_pop(L, 1);
+
+	luaL_newmetatable(L, META_NODECHILDREN);
+		lua_pushcfunction(L, nodechildren_get);
+		lua_setfield(L, -2, "__index");
+	lua_pop(L, 1);
+#endif
+
+	luaL_newmetatable(L, META_BBOX);
+		lua_pushcfunction(L, bbox_get);
+		lua_setfield(L, -2, "__index");
+	lua_pop(L, 1);
+
 	luaL_newmetatable(L, META_MAPHEADER);
 		lua_pushcfunction(L, mapheaderinfo_get);
 		lua_setfield(L, -2, "__index");
@@ -1375,6 +1836,28 @@ int LUA_MapLib(lua_State *L)
 		lua_setmetatable(L, -2);
 	lua_setglobal(L, "vertexes");
 
+#ifdef HAVE_LUA_SEGS
+	lua_newuserdata(L, 0);
+		lua_createtable(L, 0, 2);
+			lua_pushcfunction(L, lib_getSeg);
+			lua_setfield(L, -2, "__index");
+
+			lua_pushcfunction(L, lib_numsegs);
+			lua_setfield(L, -2, "__len");
+		lua_setmetatable(L, -2);
+	lua_setglobal(L, "segs");
+
+	lua_newuserdata(L, 0);
+		lua_createtable(L, 0, 2);
+			lua_pushcfunction(L, lib_getNode);
+			lua_setfield(L, -2, "__index");
+
+			lua_pushcfunction(L, lib_numnodes);
+			lua_setfield(L, -2, "__len");
+		lua_setmetatable(L, -2);
+	lua_setglobal(L, "nodes");
+#endif
+
 	lua_newuserdata(L, 0);
 		lua_createtable(L, 0, 2);
 			lua_pushcfunction(L, lib_getMapheaderinfo);
diff --git a/src/lua_script.c b/src/lua_script.c
index acb306827ecf0687c8689fa1600ddd95dc52914b..d30790be1bc70359b7e13b566d4791b2271c0780 100644
--- a/src/lua_script.c
+++ b/src/lua_script.c
@@ -48,6 +48,7 @@ static lua_CFunction liblist[] = {
 	LUA_SkinLib, // skin_t, skins[]
 	LUA_ThinkerLib, // thinker_t
 	LUA_MapLib, // line_t, side_t, sector_t, subsector_t
+	LUA_BlockmapLib, // blockmap stuff
 	LUA_HudLib, // HUD stuff
 	NULL
 };
@@ -395,6 +396,7 @@ void LUA_InvalidateLevel(void)
 {
 	thinker_t *th;
 	size_t i;
+	ffloor_t *rover = NULL;
 	if (!gL)
 		return;
 
@@ -406,7 +408,15 @@ void LUA_InvalidateLevel(void)
 	for (i = 0; i < numsubsectors; i++)
 		LUA_InvalidateUserdata(&subsectors[i]);
 	for (i = 0; i < numsectors; i++)
+	{
 		LUA_InvalidateUserdata(&sectors[i]);
+		LUA_InvalidateUserdata(sectors[i].lines);
+		if (sectors[i].ffloors)
+		{
+			for (rover = sectors[i].ffloors; rover; rover = rover->next)
+				LUA_InvalidateUserdata(rover);
+		}
+	}
 	for (i = 0; i < numlines; i++)
 	{
 		LUA_InvalidateUserdata(&lines[i]);
@@ -416,6 +426,16 @@ void LUA_InvalidateLevel(void)
 		LUA_InvalidateUserdata(&sides[i]);
 	for (i = 0; i < numvertexes; i++)
 		LUA_InvalidateUserdata(&vertexes[i]);
+#ifdef HAVE_LUA_SEGS
+	for (i = 0; i < numsegs; i++)
+		LUA_InvalidateUserdata(&segs[i]);
+	for (i = 0; i < numnodes; i++)
+	{
+		LUA_InvalidateUserdata(&nodes[i]);
+		LUA_InvalidateUserdata(nodes[i].bbox);
+		LUA_InvalidateUserdata(nodes[i].children);
+	}
+#endif
 }
 
 void LUA_InvalidateMapthings(void)
@@ -455,6 +475,11 @@ enum
 	ARCH_SIDE,
 	ARCH_SUBSECTOR,
 	ARCH_SECTOR,
+#ifdef HAVE_LUA_SEGS
+	ARCH_SEG,
+	ARCH_NODE,
+#endif
+	ARCH_FFLOOR,
 	ARCH_MAPHEADER,
 
 	ARCH_TEND=0xFF,
@@ -474,6 +499,11 @@ static const struct {
 	{META_SIDE,     ARCH_SIDE},
 	{META_SUBSECTOR,ARCH_SUBSECTOR},
 	{META_SECTOR,   ARCH_SECTOR},
+#ifdef HAVE_LUA_SEGS
+	{META_SEG,      ARCH_SEG},
+	{META_NODE,     ARCH_NODE},
+#endif
+	{META_FFLOOR,	ARCH_FFLOOR},
 	{META_MAPHEADER,   ARCH_MAPHEADER},
 	{NULL,          ARCH_NULL}
 };
@@ -664,6 +694,56 @@ static UINT8 ArchiveValue(int TABLESINDEX, int myindex)
 			}
 			break;
 		}
+#ifdef HAVE_LUA_SEGS
+		case ARCH_SEG:
+		{
+			seg_t *seg = *((seg_t **)lua_touserdata(gL, myindex));
+			if (!seg)
+				WRITEUINT8(save_p, ARCH_NULL);
+			else {
+				WRITEUINT8(save_p, ARCH_SEG);
+				WRITEUINT16(save_p, seg - segs);
+			}
+			break;
+		}
+		case ARCH_NODE:
+		{
+			node_t *node = *((node_t **)lua_touserdata(gL, myindex));
+			if (!node)
+				WRITEUINT8(save_p, ARCH_NULL);
+			else {
+				WRITEUINT8(save_p, ARCH_NODE);
+				WRITEUINT16(save_p, node - nodes);
+			}
+			break;
+		}
+#endif
+		case ARCH_FFLOOR:
+		{
+			ffloor_t *rover = *((ffloor_t **)lua_touserdata(gL, myindex));
+			if (!rover)
+				WRITEUINT8(save_p, ARCH_NULL);
+			else {
+				ffloor_t *r2;
+				UINT16 i = 0;
+				// search for id
+				for (r2 = rover->target->ffloors; r2; r2 = r2->next)
+				{
+					if (r2 == rover)
+						break;
+					i++;
+				}
+				if (!r2)
+					WRITEUINT8(save_p, ARCH_NULL);
+				else
+				{
+					WRITEUINT8(save_p, ARCH_FFLOOR);
+					WRITEUINT16(save_p, rover->target - sectors);
+					WRITEUINT16(save_p, i);
+				}
+			}
+			break;
+		}
 		case ARCH_MAPHEADER:
 		{
 			mapheader_t *header = *((mapheader_t **)lua_touserdata(gL, myindex));
@@ -842,6 +922,23 @@ static UINT8 UnArchiveValue(int TABLESINDEX)
 	case ARCH_SECTOR:
 		LUA_PushUserdata(gL, &sectors[READUINT16(save_p)], META_SECTOR);
 		break;
+#ifdef HAVE_LUA_SEGS
+	case ARCH_SEG:
+		LUA_PushUserdata(gL, &segs[READUINT16(save_p)], META_SEG);
+		break;
+	case ARCH_NODE:
+		LUA_PushUserdata(gL, &nodes[READUINT16(save_p)], META_NODE);
+		break;
+#endif
+	case ARCH_FFLOOR:
+	{
+		sector_t *sector = &sectors[READUINT16(save_p)];
+		UINT16 id = READUINT16(save_p);
+		ffloor_t *rover = P_GetFFloorByID(sector, id);
+		if (rover)
+			LUA_PushUserdata(gL, rover, META_FFLOOR);
+		break;
+	}
 	case ARCH_MAPHEADER:
 		LUA_PushUserdata(gL, &sectors[READUINT16(save_p)], META_MAPHEADER);
 		break;
diff --git a/src/lua_script.h b/src/lua_script.h
index 3b159234a5f75fc2561ad6ad65a94a9a367b8f1e..d143ed879a25944778f6105c6b123b0190184ab0 100644
--- a/src/lua_script.h
+++ b/src/lua_script.h
@@ -92,4 +92,7 @@ void COM_Lua_f(void);
 	}\
 }
 
+// uncomment if you want seg_t/node_t in Lua
+// #define HAVE_LUA_SEGS
+
 #endif
diff --git a/src/p_spec.c b/src/p_spec.c
index 43a750d17f3a990b652a9f6e49cabb12dac125df..38185745e67ddd4e144a15320cb1d8fcd2439d90 100644
--- a/src/p_spec.c
+++ b/src/p_spec.c
@@ -4752,6 +4752,13 @@ void P_UpdateSpecials(void)
 	}
 }
 
+/** Gets a 3Dfloor by control sector.
+  *
+  * \param sec  Target sector.
+  * \param sec2 Control sector.
+  * \return Pointer to found 3Dfloor, or NULL.
+  * \sa P_GetFFloorByID
+  */
 static inline ffloor_t *P_GetFFloorBySec(sector_t *sec, sector_t *sec2)
 {
 	ffloor_t *rover;
@@ -4764,6 +4771,26 @@ static inline ffloor_t *P_GetFFloorBySec(sector_t *sec, sector_t *sec2)
 	return NULL;
 }
 
+/** Gets a 3Dfloor by ID number.
+  *
+  * \param sec Target sector.
+  * \param id  ID of 3Dfloor in target sector. Note that the first FOF's ID is 0.
+  * \return Pointer to found 3Dfloor, or NULL.
+  * \sa P_GetFFloorBySec
+  */
+ffloor_t *P_GetFFloorByID(sector_t *sec, UINT16 id)
+{
+	ffloor_t *rover;
+	UINT16 i = 0;
+
+	if (!sec->ffloors)
+		return NULL;
+	for (rover = sec->ffloors; rover; rover = rover->next)
+		if (i++ == id)
+			return rover;
+	return NULL;
+}
+
 /** Adds a newly formed 3Dfloor structure to a sector's ffloors list.
   *
   * \param sec    Target sector.
diff --git a/src/p_spec.h b/src/p_spec.h
index a8f9ac492988ba927ff4ad4cc5d4c7bb996fe552..23e4cfdf96f5cb3527db7a9d8435442d1486cd86 100644
--- a/src/p_spec.h
+++ b/src/p_spec.h
@@ -64,6 +64,8 @@ boolean P_RunTriggerLinedef(line_t *triggerline, mobj_t *actor, sector_t *caller
 void P_LinedefExecute(INT16 tag, mobj_t *actor, sector_t *caller);
 void P_ChangeSectorTag(UINT32 sector, INT16 newtag);
 
+ffloor_t *P_GetFFloorByID(sector_t *sec, UINT16 id);
+
 //
 // P_LIGHTS
 //
diff --git a/src/sdl/Srb2SDL-vc10.vcxproj b/src/sdl/Srb2SDL-vc10.vcxproj
index 820192649d981adeaa477f0791d200f46d3ddfe9..abf6d76d2935d7546f445cf9b53880963d0b1433 100644
--- a/src/sdl/Srb2SDL-vc10.vcxproj
+++ b/src/sdl/Srb2SDL-vc10.vcxproj
@@ -295,6 +295,7 @@
     </ClCompile>
     <ClCompile Include="..\i_tcp.c" />
     <ClCompile Include="..\lua_baselib.c" />
+    <ClCompile Include="..\lua_blockmaplib.c" />
     <ClCompile Include="..\lua_consolelib.c" />
     <ClCompile Include="..\lua_hooklib.c" />
     <ClCompile Include="..\lua_hudlib.c" />
diff --git a/src/sdl/Srb2SDL-vc10.vcxproj.filters b/src/sdl/Srb2SDL-vc10.vcxproj.filters
index d04007dd77d026e48e8480bf93a54b86d279b7c5..bdb029cf9437e8c960a84a111bbcbfaf5dc9b82b 100644
--- a/src/sdl/Srb2SDL-vc10.vcxproj.filters
+++ b/src/sdl/Srb2SDL-vc10.vcxproj.filters
@@ -633,6 +633,9 @@
     <ClCompile Include="..\lua_baselib.c">
       <Filter>LUA</Filter>
     </ClCompile>
+    <ClCompile Include="..\lua_blockmaplib.c">
+      <Filter>LUA</Filter>
+    </ClCompile>
     <ClCompile Include="..\lua_consolelib.c">
       <Filter>LUA</Filter>
     </ClCompile>
diff --git a/src/win32/Srb2win-vc10.vcxproj b/src/win32/Srb2win-vc10.vcxproj
index 064f75d7d06cdee1aa62d70dda03e9e28d53e6f2..95f9c85f94aeb1e6b62b2ac9447402a01c811840 100644
--- a/src/win32/Srb2win-vc10.vcxproj
+++ b/src/win32/Srb2win-vc10.vcxproj
@@ -131,6 +131,7 @@
     </ClCompile>
     <ClCompile Include="..\i_tcp.c" />
     <ClCompile Include="..\lua_baselib.c" />
+    <ClCompile Include="..\lua_blockmaplib.c" />
     <ClCompile Include="..\lua_consolelib.c" />
     <ClCompile Include="..\lua_hooklib.c" />
     <ClCompile Include="..\lua_hudlib.c" />
diff --git a/src/win32/Srb2win-vc10.vcxproj.filters b/src/win32/Srb2win-vc10.vcxproj.filters
index b2647ea1c21518389b7061276f6464d05998721b..cfd46f1f81d260f7f9645759f7601626c6a64dad 100644
--- a/src/win32/Srb2win-vc10.vcxproj.filters
+++ b/src/win32/Srb2win-vc10.vcxproj.filters
@@ -225,6 +225,9 @@
     <ClCompile Include="..\lua_baselib.c">
       <Filter>LUA</Filter>
     </ClCompile>
+    <ClCompile Include="..\lua_blockmaplib.c">
+      <Filter>LUA</Filter>
+    </ClCompile>
     <ClCompile Include="..\lua_consolelib.c">
       <Filter>LUA</Filter>
     </ClCompile>