diff --git a/src/lua_baselib.c b/src/lua_baselib.c
index 8c4739b02bb1e528b9a3e6b49eb92f95c52ada71..f5930cdbe1a3fe9c3e025c0d256240097496a6ea 100644
--- a/src/lua_baselib.c
+++ b/src/lua_baselib.c
@@ -176,6 +176,7 @@ static const struct {
 	{META_SECTORLINES,  "sector_t.lines"},
 	{META_SIDENUM,      "line_t.sidenum"},
 	{META_LINEARGS,     "line_t.args"},
+	{META_LINESTRINGARGS, "line_t.stringargs"},
 #ifdef HAVE_LUA_SEGS
 	{META_NODEBBOX,     "node_t.bbox"},
 	{META_NODECHILDREN, "node_t.children"},
diff --git a/src/lua_libs.h b/src/lua_libs.h
index 155c34eaa068fabf6dc012d777a190f50a5555db..1110e93c2576431efcfaa893f935cc165ae6eb38 100644
--- a/src/lua_libs.h
+++ b/src/lua_libs.h
@@ -57,6 +57,7 @@ extern lua_State *gL;
 #define META_SECTORLINES "SECTOR_T*LINES"
 #define META_SIDENUM "LINE_T*SIDENUM"
 #define META_LINEARGS "LINE_T*ARGS"
+#define META_LINESTRINGARGS "LINE_T*STRINGARGS"
 #ifdef HAVE_LUA_SEGS
 #define META_NODEBBOX "NODE_T*BBOX"
 #define META_NODECHILDREN "NODE_T*CHILDREN"
diff --git a/src/lua_maplib.c b/src/lua_maplib.c
index 03da0a2e09579eb53655d9e9f217245842506d22..4cb316313dd7a46080f2f85312cc6c484c219b6b 100644
--- a/src/lua_maplib.c
+++ b/src/lua_maplib.c
@@ -95,6 +95,7 @@ enum line_e {
 	line_special,
 	line_tag,
 	line_args,
+	line_stringargs,
 	line_sidenum,
 	line_frontside,
 	line_backside,
@@ -117,6 +118,7 @@ static const char *const line_opt[] = {
 	"special",
 	"tag",
 	"args",
+	"stringargs",
 	"sidenum",
 	"frontside",
 	"backside",
@@ -721,6 +723,24 @@ static int lineargs_len(lua_State* L)
 	return 1;
 }
 
+// stringargs, i -> stringargs[i]
+static int linestringargs_get(lua_State *L)
+{
+	char **stringargs = *((char***)luaL_checkudata(L, 1, META_LINESTRINGARGS));
+	int i = luaL_checkinteger(L, 2);
+	if (i < 0 || i >= NUMLINESTRINGARGS)
+		return luaL_error(L, LUA_QL("line_t.stringargs") " index cannot be %d", i);
+	lua_pushstring(L, stringargs[i]);
+	return 1;
+}
+
+// #stringargs -> NUMLINESTRINGARGS
+static int linestringargs_len(lua_State *L)
+{
+	lua_pushinteger(L, NUMLINESTRINGARGS);
+	return 1;
+}
+
 static int line_get(lua_State *L)
 {
 	line_t *line = *((line_t **)luaL_checkudata(L, 1, META_LINE));
@@ -764,6 +784,9 @@ static int line_get(lua_State *L)
 	case line_args:
 		LUA_PushUserdata(L, line->args, META_LINEARGS);
 		return 1;
+	case line_stringargs:
+		LUA_PushUserdata(L, line->stringargs, META_LINESTRINGARGS);
+		return 1;
 	case line_sidenum:
 		LUA_PushUserdata(L, line->sidenum, META_SIDENUM);
 		return 1;
@@ -2168,6 +2191,14 @@ int LUA_MapLib(lua_State *L)
 		lua_setfield(L, -2, "__len");
 	lua_pop(L, 1);
 
+	luaL_newmetatable(L, META_LINESTRINGARGS);
+		lua_pushcfunction(L, linestringargs_get);
+		lua_setfield(L, -2, "__index");
+
+		lua_pushcfunction(L, linestringargs_len);
+		lua_setfield(L, -2, "__len");
+	lua_pop(L, 1);
+
 	luaL_newmetatable(L, META_SIDENUM);
 		lua_pushcfunction(L, sidenum_get);
 		lua_setfield(L, -2, "__index");
diff --git a/src/p_saveg.c b/src/p_saveg.c
index 1e645116408527a7845eb2288969c8e7f3887a85..0cf5ed2e83cab098b3e64994e3fe6774089e63a4 100644
--- a/src/p_saveg.c
+++ b/src/p_saveg.c
@@ -764,17 +764,28 @@ static void P_NetUnArchiveColormaps(void)
 #define LD_DIFF2    0x80
 
 // diff2 flags
-#define LD_S2TEXOFF 0x01
-#define LD_S2TOPTEX 0x02
-#define LD_S2BOTTEX 0x04
-#define LD_S2MIDTEX 0x08
-#define LD_ARGS     0x10
-
-static boolean P_AreArgsEqual(const INT32 args[NUMLINEARGS], const INT32 spawnargs[NUMLINEARGS])
+#define LD_S2TEXOFF   0x01
+#define LD_S2TOPTEX   0x02
+#define LD_S2BOTTEX   0x04
+#define LD_S2MIDTEX   0x08
+#define LD_ARGS       0x10
+#define LD_STRINGARGS 0x20
+
+static boolean P_AreArgsEqual(const line_t *li, const line_t *spawnli)
 {
 	UINT8 i;
 	for (i = 0; i < NUMLINEARGS; i++)
-		if (args[i] != spawnargs[i])
+		if (li->args[i] != spawnli->args[i])
+			return false;
+
+	return true;
+}
+
+static boolean P_AreStringArgsEqual(const line_t *li, const line_t *spawnli)
+{
+	UINT8 i;
+	for (i = 0; i < NUMLINESTRINGARGS; i++)
+		if (strcmp(li->stringargs[i], spawnli->stringargs[i]))
 			return false;
 
 	return true;
@@ -955,9 +966,12 @@ static void P_NetArchiveWorld(void)
 		if (spawnli->special == 321 || spawnli->special == 322) // only reason li->callcount would be non-zero is if either of these are involved
 			diff |= LD_CLLCOUNT;
 
-		if (!P_AreArgsEqual(li->args, spawnli->args))
+		if (!P_AreArgsEqual(li, spawnli))
 			diff2 |= LD_ARGS;
 
+		if (!P_AreStringArgsEqual(li, spawnli))
+			diff2 |= LD_STRINGARGS;
+
 		if (li->sidenum[0] != 0xffff)
 		{
 			si = &sides[li->sidenum[0]];
@@ -1028,6 +1042,25 @@ static void P_NetArchiveWorld(void)
 				for (j = 0; j < NUMLINEARGS; j++)
 					WRITEINT32(put, li->args[j]);
 			}
+			if (diff2 & LD_STRINGARGS)
+			{
+				UINT8 j;
+				for (j = 0; j < NUMLINESTRINGARGS; j++)
+				{
+					size_t len, k;
+
+					if (!li->stringargs[j])
+					{
+						WRITEINT32(put, 0);
+						continue;
+					}
+
+					len = strlen(li->stringargs[j]);
+					WRITEINT32(put, len);
+					for (k = 0; k < len; k++)
+						WRITECHAR(put, li->stringargs[j][k]);
+				}
+			}
 		}
 	}
 	WRITEUINT16(put, 0xffff);
@@ -1217,7 +1250,27 @@ static void P_NetUnArchiveWorld(void)
 			for (j = 0; j < NUMLINEARGS; j++)
 				li->args[j] = READINT32(get);
 		}
+		if (diff2 & LD_STRINGARGS)
+		{
+			UINT8 j;
+			for (j = 0; j < NUMLINESTRINGARGS; j++)
+			{
+				size_t len = READINT32(get);
+				size_t k;
 
+				if (!len)
+				{
+					Z_Free(li->stringargs[j]);
+					li->stringargs[j] = NULL;
+					continue;
+				}
+
+				li->stringargs[j] = Z_Realloc(li->stringargs[j], len + 1, PU_LEVEL, NULL);
+				for (k = 0; k < len; k++)
+					li->stringargs[j][k] = READCHAR(get);
+				li->stringargs[j][len] = '\0';
+			}
+		}
 	}
 
 	save_p = get;
diff --git a/src/p_setup.c b/src/p_setup.c
index 892323382cd9cf7ed7eeee93017f0c382a1bad9c..67cd1174043bb68cda0c30244b1e33e43ccf6688 100644
--- a/src/p_setup.c
+++ b/src/p_setup.c
@@ -1053,6 +1053,7 @@ static void P_LoadLinedefs(UINT8 *data)
 		ld->special = SHORT(mld->special);
 		ld->tag = SHORT(mld->tag);
 		memset(ld->args, 0, NUMLINEARGS*sizeof(*ld->args));
+		memset(ld->stringargs, (int)NULL, NUMLINESTRINGARGS*sizeof(*ld->stringargs));
 		P_SetLinedefV1(i, SHORT(mld->v1));
 		P_SetLinedefV2(i, SHORT(mld->v2));
 
@@ -1430,10 +1431,21 @@ static void ParseTextmapLinedefParameter(UINT32 i, char *param, char *val)
 		P_SetLinedefV2(i, atol(val));
 	else if (fastncmp(param, "arg", 3) && strlen(param) > 3)
 	{
-		size_t argnum = atol(param + 3);
-		if (argnum >= NUMLINEARGS)
-			return;
-		lines[i].args[argnum] = atol(val);
+		if (fastcmp(param + 4, "str"))
+		{
+			size_t argnum = param[3] - '0';
+			if (argnum >= NUMLINESTRINGARGS)
+				return;
+			lines[i].stringargs[argnum] = Z_Malloc(strlen(val) + 1, PU_LEVEL, NULL);
+			M_Memcpy(lines[i].stringargs[argnum], val, strlen(val) + 1);
+		}
+		else
+		{
+			size_t argnum = atol(param + 3);
+			if (argnum >= NUMLINEARGS)
+				return;
+			lines[i].args[argnum] = atol(val);
+		}
 	}
 	else if (fastcmp(param, "sidefront"))
 		lines[i].sidenum[0] = atol(val);
@@ -1623,6 +1635,7 @@ static void P_LoadTextmap(void)
 		ld->special = 0;
 		ld->tag = 0;
 		memset(ld->args, 0, NUMLINEARGS*sizeof(*ld->args));
+		memset(ld->stringargs, (int)NULL, NUMLINESTRINGARGS*sizeof(*ld->stringargs));
 		ld->sidenum[0] = 0xffff;
 		ld->sidenum[1] = 0xffff;
 
diff --git a/src/r_defs.h b/src/r_defs.h
index 2fc862de896e8b350bc2cd7ea3734cda5fb074e2..88d045d0967165fd9bedb3b0fb60f53c8e2881d5 100644
--- a/src/r_defs.h
+++ b/src/r_defs.h
@@ -402,6 +402,7 @@ typedef enum
 #define HORIZONSPECIAL 41
 
 #define NUMLINEARGS 6
+#define NUMLINESTRINGARGS 2
 
 typedef struct line_s
 {
@@ -416,6 +417,7 @@ typedef struct line_s
 	INT16 special;
 	INT16 tag;
 	INT32 args[NUMLINEARGS];
+	char *stringargs[NUMLINESTRINGARGS];
 
 	// Visual appearance: sidedefs.
 	UINT16 sidenum[2]; // sidenum[1] will be 0xffff if one-sided