diff --git a/src/lua_script.c b/src/lua_script.c
index 5f2aab08878ec8b31eade659137fe1a1204064e4..158b1bc4b07456e90fabc7f6aaab5709508492cf 100644
--- a/src/lua_script.c
+++ b/src/lua_script.c
@@ -474,6 +474,7 @@ enum
 	ARCH_SECTOR,
 	ARCH_SEG,
 	ARCH_NODE,
+	ARCH_FFLOOR,
 	ARCH_MAPHEADER,
 
 	ARCH_TEND=0xFF,
@@ -495,6 +496,7 @@ static const struct {
 	{META_SECTOR,   ARCH_SECTOR},
 	{META_SEG,      ARCH_SEG},
 	{META_NODE,     ARCH_NODE},
+	{META_FFLOOR,	ARCH_FFLOOR},
 	{META_MAPHEADER,   ARCH_MAPHEADER},
 	{NULL,          ARCH_NULL}
 };
@@ -707,6 +709,32 @@ static UINT8 ArchiveValue(int TABLESINDEX, int myindex)
 			}
 			break;
 		}
+		case ARCH_FFLOOR:
+		{
+			ffloor_t *rover = *((ffloor_t **)lua_touserdata(gL, myindex));
+			if (!rover)
+				WRITEUINT8(save_p, ARCH_NULL);
+			else {
+				ffloor_t *r2 = NULL;
+				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));
@@ -891,6 +919,15 @@ static UINT8 UnArchiveValue(int TABLESINDEX)
 	case ARCH_NODE:
 		LUA_PushUserdata(gL, &nodes[READUINT16(save_p)], META_NODE);
 		break;
+	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/p_spec.c b/src/p_spec.c
index 3ca697295a3a4a984dc5d071962125f59d01b57a..51c2d03d8f1ec74de9aefe5a5adabf3d49ee8b63 100644
--- a/src/p_spec.c
+++ b/src/p_spec.c
@@ -4708,6 +4708,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;
@@ -4720,6 +4727,28 @@ 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. Can be a number from 0 to sec->numattached-1.
+  * \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;
+	if (id >= sec->numattached)
+		return NULL; // ID out of range
+	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
 //