diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index a1f752ed7d540d72ac280aeff28dd4f9c5c0210e..124a89b090987d5bfed337387ee32f16f884952f 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -169,6 +169,7 @@ set(SRB2_CORE_GAME_SOURCES
 	p_telept.c
 	p_tick.c
 	p_user.c
+	taglist.c
 
 	p_local.h
 	p_maputl.h
@@ -180,6 +181,7 @@ set(SRB2_CORE_GAME_SOURCES
 	p_slopes.h
 	p_spec.h
 	p_tick.h
+	taglist.h
 )
 
 if(NOT (CMAKE_CXX_COMPILER_ID MATCHES "Clang"))
diff --git a/src/Makefile b/src/Makefile
index da918e2057146f2031fce4eec742e85d46e79c3f..2cd6d5f6b309b76ace6073ffb1cd5580851e71d1 100644
--- a/src/Makefile
+++ b/src/Makefile
@@ -522,6 +522,7 @@ OBJS:=$(i_main_o) \
 		$(OBJDIR)/r_picformats.o \
 		$(OBJDIR)/r_portal.o \
 		$(OBJDIR)/screen.o   \
+		$(OBJDIR)/taglist.o  \
 		$(OBJDIR)/v_video.o  \
 		$(OBJDIR)/s_sound.o  \
 		$(OBJDIR)/sounds.o   \
diff --git a/src/doomdata.h b/src/doomdata.h
index f3890c846536dc438573104f98398bace13f7415..b3f7f5c4dbcc4ea5ce9ee8ff1cdf34a1845cdafa 100644
--- a/src/doomdata.h
+++ b/src/doomdata.h
@@ -23,6 +23,7 @@
 // Some global defines, that configure the game.
 #include "doomdef.h"
 
+#include "taglist.h"
 #include "m_fixed.h" // See the mapthing_t scale.
 
 //
@@ -208,11 +209,10 @@ typedef struct
 	UINT16 options;
 	INT16 z;
 	UINT8 extrainfo;
+	taglist_t tags;
 	fixed_t scale;
-	INT16 tag;
 	INT32 args[NUMMAPTHINGARGS];
 	char *stringargs[NUMMAPTHINGSTRINGARGS];
-
 	struct mobj_s *mobj;
 } mapthing_t;
 
diff --git a/src/hardware/hw_main.c b/src/hardware/hw_main.c
index bd977ed15a07d1199f0f01d3f779ab2b62696c93..a0fd3e9f757ddba3870b56dadd9d2a5667cb3e81 100644
--- a/src/hardware/hw_main.c
+++ b/src/hardware/hw_main.c
@@ -1608,7 +1608,7 @@ static void HWR_ProcessSeg(void) // Sort of like GLWall::Process in GZDoom
 
 
 	//Hurdler: 3d-floors test
-	if (gl_frontsector && gl_backsector && gl_frontsector->tag != gl_backsector->tag && (gl_backsector->ffloors || gl_frontsector->ffloors))
+	if (gl_frontsector && gl_backsector && !Tag_Compare(&gl_frontsector->tags, &gl_backsector->tags) && (gl_backsector->ffloors || gl_frontsector->ffloors))
 	{
 		ffloor_t * rover;
 		fixed_t    highcut = 0, lowcut = 0;
@@ -1625,6 +1625,18 @@ static void HWR_ProcessSeg(void) // Sort of like GLWall::Process in GZDoom
 		{
 			for (rover = gl_backsector->ffloors; rover; rover = rover->next)
 			{
+				boolean bothsides = false;
+				// Skip if it exists on both sectors.
+				ffloor_t * r2;
+				for (r2 = gl_frontsector->ffloors; r2; r2 = r2->next)
+					if (rover->master == r2->master)
+					{
+						bothsides = true;
+						break;
+					}
+
+				if (bothsides) continue;
+
 				if (!(rover->flags & FF_EXISTS) || !(rover->flags & FF_RENDERSIDES))
 					continue;
 				if (!(rover->flags & FF_ALLSIDES) && rover->flags & FF_INVERTSIDES)
@@ -1759,6 +1771,18 @@ static void HWR_ProcessSeg(void) // Sort of like GLWall::Process in GZDoom
 		{
 			for (rover = gl_frontsector->ffloors; rover; rover = rover->next)
 			{
+				boolean bothsides = false;
+				// Skip if it exists on both sectors.
+				ffloor_t * r2;
+				for (r2 = gl_backsector->ffloors; r2; r2 = r2->next)
+					if (rover->master == r2->master)
+					{
+						bothsides = true;
+						break;
+					}
+
+				if (bothsides) continue;
+
 				if (!(rover->flags & FF_EXISTS) || !(rover->flags & FF_RENDERSIDES))
 					continue;
 				if (!(rover->flags & FF_ALLSIDES || rover->flags & FF_INVERTSIDES))
@@ -2384,7 +2408,7 @@ static void HWR_AddLine(seg_t * line)
 			if (!line->polyseg &&
 				!line->sidedef->midtexture
 				&& ((!gl_frontsector->ffloors && !gl_backsector->ffloors)
-					|| (gl_frontsector->tag == gl_backsector->tag)))
+					|| Tag_Compare(&gl_frontsector->tags, &gl_backsector->tags)))
 				return; // line is empty, don't even bother
 			// treat like wide open window instead
 			HWR_ProcessSeg(); // Doesn't need arguments because they're defined globally :D
@@ -2423,7 +2447,7 @@ static void HWR_AddLine(seg_t * line)
 		if (!line->polyseg &&
 			!line->sidedef->midtexture
 			&& ((!gl_frontsector->ffloors && !gl_backsector->ffloors)
-				|| (gl_frontsector->tag == gl_backsector->tag)))
+				|| Tag_Compare(&gl_frontsector->tags, &gl_backsector->tags)))
 			return; // line is empty, don't even bother
 
 		goto clippass; // treat like wide open window instead
diff --git a/src/lua_baselib.c b/src/lua_baselib.c
index 739c8aeb78c5258ef1dab8b7312e2b624f333bb9..157d4fe3723090a08377b3859366cbc3d024fcca 100644
--- a/src/lua_baselib.c
+++ b/src/lua_baselib.c
@@ -32,6 +32,7 @@
 #include "lua_script.h"
 #include "lua_libs.h"
 #include "lua_hud.h" // hud_running errors
+#include "taglist.h" // P_FindSpecialLineFromTag
 #include "lua_hook.h" // hook_cmd_running errors
 
 #define NOHUD if (hud_running)\
diff --git a/src/lua_maplib.c b/src/lua_maplib.c
index 5f6dbc4d6480c0d92e942ded4d01f0524207ae70..95cc8c1019e88de52213a0fac5ebff9d42ffa8d0 100644
--- a/src/lua_maplib.c
+++ b/src/lua_maplib.c
@@ -99,8 +99,6 @@ enum line_e {
 	line_slopetype,
 	line_frontsector,
 	line_backsector,
-	line_firsttag,
-	line_nexttag,
 	line_polyobj,
 	line_text,
 	line_callcount
@@ -125,8 +123,6 @@ static const char *const line_opt[] = {
 	"slopetype",
 	"frontsector",
 	"backsector",
-	"firsttag",
-	"nexttag",
 	"polyobj",
 	"text",
 	"callcount",
@@ -583,7 +579,7 @@ static int sector_get(lua_State *L)
 		lua_pushinteger(L, sector->special);
 		return 1;
 	case sector_tag:
-		lua_pushinteger(L, sector->tag);
+		lua_pushinteger(L, Tag_FGet(&sector->tags));
 		return 1;
 	case sector_thinglist: // thinglist
 		lua_pushcfunction(L, lib_iterateSectorThinglist);
@@ -684,7 +680,7 @@ static int sector_set(lua_State *L)
 		sector->special = (INT16)luaL_checkinteger(L, 3);
 		break;
 	case sector_tag:
-		P_ChangeSectorTag((UINT32)(sector - sectors), (INT16)luaL_checkinteger(L, 3));
+		Tag_SectorFSet((UINT32)(sector - sectors), (INT16)luaL_checkinteger(L, 3));
 		break;
 	}
 	return 0;
@@ -823,7 +819,7 @@ static int line_get(lua_State *L)
 		lua_pushinteger(L, line->special);
 		return 1;
 	case line_tag:
-		lua_pushinteger(L, line->tag);
+		lua_pushinteger(L, Tag_FGet(&line->tags));
 		return 1;
 	case line_args:
 		LUA_PushUserdata(L, line->args, META_LINEARGS);
@@ -871,12 +867,6 @@ static int line_get(lua_State *L)
 	case line_backsector:
 		LUA_PushUserdata(L, line->backsector, META_SECTOR);
 		return 1;
-	case line_firsttag:
-		lua_pushinteger(L, line->firsttag);
-		return 1;
-	case line_nexttag:
-		lua_pushinteger(L, line->nexttag);
-		return 1;
 	case line_polyobj:
 		LUA_PushUserdata(L, line->polyobj, META_POLYOBJ);
 		return 1;
diff --git a/src/lua_mobjlib.c b/src/lua_mobjlib.c
index a1a3e96c93afd9d036ad6f8120e6a1c0bf82b9ac..824e9df6ba938b3a0dbe769eb040f172f15c59a0 100644
--- a/src/lua_mobjlib.c
+++ b/src/lua_mobjlib.c
@@ -848,7 +848,7 @@ static int mapthing_get(lua_State *L)
 	else if(fastcmp(field,"extrainfo"))
 		number = mt->extrainfo;
 	else if(fastcmp(field,"tag"))
-		number = mt->tag;
+		number = Tag_FGet(&mt->tags);
 	else if(fastcmp(field,"args"))
 	{
 		LUA_PushUserdata(L, mt->args, META_THINGARGS);
@@ -910,7 +910,7 @@ static int mapthing_set(lua_State *L)
 		mt->extrainfo = (UINT8)extrainfo;
 	}
 	else if (fastcmp(field,"tag"))
-		mt->tag = (INT16)luaL_checkinteger(L, 3);
+		Tag_FSet(&mt->tags, (INT16)luaL_checkinteger(L, 3));
 	else if(fastcmp(field,"mobj"))
 		mt->mobj = *((mobj_t **)luaL_checkudata(L, 3, META_MOBJ));
 	else
diff --git a/src/m_cheat.c b/src/m_cheat.c
index 365bcee8c6849b4e872e78bacf90f19f48726202..6e0fb8c5c0784f66be508981aa33d75f9a661d89 100644
--- a/src/m_cheat.c
+++ b/src/m_cheat.c
@@ -1108,7 +1108,6 @@ static mapthing_t *OP_CreateNewMapThing(player_t *player, UINT16 type, boolean c
 
 	mt->options = (mt->z << ZSHIFT) | (UINT16)cv_opflags.value;
 	mt->scale = player->mo->scale;
-	mt->tag = 0;
 	memset(mt->args, 0, NUMMAPTHINGARGS*sizeof(*mt->args));
 	memset(mt->stringargs, 0x00, NUMMAPTHINGSTRINGARGS*sizeof(*mt->stringargs));
 	mt->pitch = mt->roll = 0;
diff --git a/src/p_ceilng.c b/src/p_ceilng.c
index 3c3c507cd168e46a50ccdce3a503c8570c028797..f12499d5ce6315e1a8baf70b43f454443ae0bef8 100644
--- a/src/p_ceilng.c
+++ b/src/p_ceilng.c
@@ -394,8 +394,10 @@ INT32 EV_DoCeiling(line_t *line, ceiling_e type)
 	INT32 secnum = -1;
 	sector_t *sec;
 	ceiling_t *ceiling;
+	mtag_t tag = Tag_FGet(&line->tags);
+	TAG_ITER_DECLARECOUNTER(0);
 
-	while ((secnum = P_FindSectorFromTag(line->tag,secnum)) >= 0)
+	TAG_ITER_SECTORS(0, tag, secnum)
 	{
 		sec = &sectors[secnum];
 
@@ -593,7 +595,7 @@ INT32 EV_DoCeiling(line_t *line, ceiling_e type)
 
 		}
 
-		ceiling->tag = sec->tag;
+		ceiling->tag = tag;
 		ceiling->type = type;
 		firstone = 0;
 	}
@@ -614,8 +616,10 @@ INT32 EV_DoCrush(line_t *line, ceiling_e type)
 	INT32 secnum = -1;
 	sector_t *sec;
 	ceiling_t *ceiling;
+	mtag_t tag = Tag_FGet(&line->tags);
+	TAG_ITER_DECLARECOUNTER(0);
 
-	while ((secnum = P_FindSectorFromTag(line->tag,secnum)) >= 0)
+	TAG_ITER_SECTORS(0, tag, secnum)
 	{
 		sec = &sectors[secnum];
 
@@ -670,7 +674,7 @@ INT32 EV_DoCrush(line_t *line, ceiling_e type)
 				break;
 		}
 
-		ceiling->tag = sec->tag;
+		ceiling->tag = tag;
 		ceiling->type = type;
 	}
 	return rtn;
diff --git a/src/p_enemy.c b/src/p_enemy.c
index ddb01b63ba4fdb2a0fa7ea1cc524a4f531f1b5d8..f9baf1813c7d7d894f2707d14f6f19a75682c6f7 100644
--- a/src/p_enemy.c
+++ b/src/p_enemy.c
@@ -3925,11 +3925,12 @@ void A_BossDeath(mobj_t *mo)
 	else
 	{
 		// Bring the egg trap up to the surface
-		junk.tag = LE_CAPSULE0;
+		// Incredibly shitty code ahead
+		Tag_FSet(&junk.tags, LE_CAPSULE0);
 		EV_DoElevator(&junk, elevateHighest, false);
-		junk.tag = LE_CAPSULE1;
+		Tag_FSet(&junk.tags, LE_CAPSULE1);
 		EV_DoElevator(&junk, elevateUp, false);
-		junk.tag = LE_CAPSULE2;
+		Tag_FSet(&junk.tags, LE_CAPSULE2);
 		EV_DoElevator(&junk, elevateHighest, false);
 
 		if (mapheaderinfo[gamemap-1]->muspostbossname[0] &&
@@ -4052,7 +4053,7 @@ bossjustdie:
 		}
 		case MT_KOOPA:
 		{
-			junk.tag = LE_KOOPA;
+			Tag_FSet(&junk.tags, LE_KOOPA);
 			EV_DoCeiling(&junk, raiseToHighest);
 			return;
 		}
@@ -6157,7 +6158,7 @@ void A_RockSpawn(mobj_t *actor)
 {
 	mobj_t *mo;
 	mobjtype_t type;
-	INT32 i = P_FindSpecialLineFromTag(12, (INT16)actor->threshold, -1);
+	INT32 i = Tag_FindLineSpecial(12, (INT16)actor->threshold);
 	line_t *line;
 	fixed_t dist;
 	fixed_t randomoomph;
diff --git a/src/p_floor.c b/src/p_floor.c
index a0f7edd9ca0e68617b1eede0441c5a14a2dbe549..ed49b03a38ef8b4381ad968a4540c09952d1d018 100644
--- a/src/p_floor.c
+++ b/src/p_floor.c
@@ -632,8 +632,10 @@ void T_BounceCheese(bouncecheese_t *bouncer)
 	fixed_t waterheight;
 	fixed_t floorheight;
 	sector_t *actionsector;
-	INT32 i;
 	boolean remove;
+	INT32 i;
+	mtag_t tag = Tag_FGet(&bouncer->sourceline->tags);
+	TAG_ITER_DECLARECOUNTER(0);
 
 	if (bouncer->sector->crumblestate == CRUMBLE_RESTORE || bouncer->sector->crumblestate == CRUMBLE_WAIT
 		|| bouncer->sector->crumblestate == CRUMBLE_ACTIVATED) // Oops! Crumbler says to remove yourself!
@@ -648,7 +650,7 @@ void T_BounceCheese(bouncecheese_t *bouncer)
 	}
 
 	// You can use multiple target sectors, but at your own risk!!!
-	for (i = -1; (i = P_FindSectorFromTag(bouncer->sourceline->tag, i)) >= 0 ;)
+	TAG_ITER_SECTORS(0, tag, i)
 	{
 		actionsector = &sectors[i];
 		actionsector->moved = true;
@@ -772,6 +774,8 @@ void T_StartCrumble(crumble_t *crumble)
 	ffloor_t *rover;
 	sector_t *sector;
 	INT32 i;
+	mtag_t tag = Tag_FGet(&crumble->sourceline->tags);
+	TAG_ITER_DECLARECOUNTER(0);
 
 	// Once done, the no-return thinker just sits there,
 	// constantly 'returning'... kind of an oxymoron, isn't it?
@@ -800,7 +804,7 @@ void T_StartCrumble(crumble_t *crumble)
 		}
 		else if (++crumble->timer == 0) // Reposition back to original spot
 		{
-			for (i = -1; (i = P_FindSectorFromTag(crumble->sourceline->tag, i)) >= 0 ;)
+			TAG_ITER_SECTORS(0, tag, i)
 			{
 				sector = &sectors[i];
 
@@ -836,7 +840,7 @@ void T_StartCrumble(crumble_t *crumble)
 		// Flash to indicate that the platform is about to return.
 		if (crumble->timer > -224 && (leveltime % ((abs(crumble->timer)/8) + 1) == 0))
 		{
-			for (i = -1; (i = P_FindSectorFromTag(crumble->sourceline->tag, i)) >= 0 ;)
+			TAG_ITER_SECTORS(0, tag, i)
 			{
 				sector = &sectors[i];
 
@@ -928,7 +932,7 @@ void T_StartCrumble(crumble_t *crumble)
 		P_RemoveThinker(&crumble->thinker);
 	}
 
-	for (i = -1; (i = P_FindSectorFromTag(crumble->sourceline->tag, i)) >= 0 ;)
+	TAG_ITER_SECTORS(0, tag, i)
 	{
 		sector = &sectors[i];
 		sector->moved = true;
@@ -944,6 +948,7 @@ void T_StartCrumble(crumble_t *crumble)
 void T_MarioBlock(mariothink_t *block)
 {
 	INT32 i;
+	TAG_ITER_DECLARECOUNTER(0);
 
 	T_MovePlane
 	(
@@ -978,8 +983,7 @@ void T_MarioBlock(mariothink_t *block)
 		block->sector->ceilspeed = 0;
 		block->direction = 0;
 	}
-
-	for (i = -1; (i = P_FindSectorFromTag(block->tag, i)) >= 0 ;)
+	TAG_ITER_SECTORS(0, (INT16)block->tag, i)
 		P_RecalcPrecipInSector(&sectors[i]);
 }
 
@@ -992,8 +996,7 @@ void T_FloatSector(floatthink_t *floater)
 
 	// Just find the first sector with the tag.
 	// Doesn't work with multiple sectors that have different floor/ceiling heights.
-	secnum = P_FindSectorFromTag(floater->tag, -1);
-	if (secnum <= 0)
+	if ((secnum = Tag_Iterate_Sectors((INT16)floater->tag, 0)) < 0)
 		return;
 	actionsector = &sectors[secnum];
 
@@ -1131,10 +1134,8 @@ void T_ThwompSector(thwomp_t *thwomp)
 
 	// Just find the first sector with the tag.
 	// Doesn't work with multiple sectors that have different floor/ceiling heights.
-	secnum = P_FindSectorFromTag(thwomp->tag, -1);
-
-	if (secnum <= 0)
-		return; // Bad bad bad!
+	if ((secnum = Tag_Iterate_Sectors((INT16)thwomp->tag, 0)) < 0)
+		return;
 
 	actionsector = &sectors[secnum];
 
@@ -1293,8 +1294,10 @@ void T_NoEnemiesSector(noenemies_t *nobaddies)
 	sector_t *sec = NULL;
 	INT32 secnum = -1;
 	boolean FOFsector = false;
+	mtag_t tag = Tag_FGet(&nobaddies->sourceline->tags);
+	TAG_ITER_DECLARECOUNTER(0);
 
-	while ((secnum = P_FindSectorFromTag(nobaddies->sourceline->tag, secnum)) >= 0)
+	TAG_ITER_SECTORS(0, tag, secnum)
 	{
 		sec = &sectors[secnum];
 
@@ -1304,13 +1307,15 @@ void T_NoEnemiesSector(noenemies_t *nobaddies)
 		for (i = 0; i < sec->linecount; i++)
 		{
 			INT32 targetsecnum = -1;
+			mtag_t tag2 = Tag_FGet(&sec->lines[i]->tags);
+			TAG_ITER_DECLARECOUNTER(1);
 
 			if (sec->lines[i]->special < 100 || sec->lines[i]->special >= 300)
 				continue;
 
 			FOFsector = true;
 
-			while ((targetsecnum = P_FindSectorFromTag(sec->lines[i]->tag, targetsecnum)) >= 0)
+			TAG_ITER_SECTORS(1, tag2, targetsecnum)
 			{
 				if (T_SectorHasEnemies(&sectors[targetsecnum]))
 					return;
@@ -1321,7 +1326,7 @@ void T_NoEnemiesSector(noenemies_t *nobaddies)
 			return;
 	}
 
-	CONS_Debug(DBG_GAMELOGIC, "Running no-more-enemies exec with tag of %d\n", nobaddies->sourceline->tag);
+	CONS_Debug(DBG_GAMELOGIC, "Running no-more-enemies exec with tag of %d\n", tag);
 
 	// No enemies found, run the linedef exec and terminate this thinker
 	P_RunTriggerLinedef(nobaddies->sourceline, NULL, NULL);
@@ -1396,6 +1401,8 @@ void T_EachTimeThinker(eachtime_t *eachtime)
 	boolean floortouch = false;
 	fixed_t bottomheight, topheight;
 	ffloor_t *rover;
+	mtag_t tag = Tag_FGet(&eachtime->sourceline->tags);
+	TAG_ITER_DECLARECOUNTER(0);
 
 	for (i = 0; i < MAXPLAYERS; i++)
 	{
@@ -1405,7 +1412,7 @@ void T_EachTimeThinker(eachtime_t *eachtime)
 		eachtime->playersOnArea[i] = false;
 	}
 
-	while ((secnum = P_FindSectorFromTag(eachtime->sourceline->tag, secnum)) >= 0)
+	TAG_ITER_SECTORS(0, tag, secnum)
 	{
 		sec = &sectors[secnum];
 
@@ -1422,13 +1429,15 @@ void T_EachTimeThinker(eachtime_t *eachtime)
 		for (i = 0; i < sec->linecount; i++)
 		{
 			INT32 targetsecnum = -1;
+			mtag_t tag2 = Tag_FGet(&sec->lines[i]->tags);
+			TAG_ITER_DECLARECOUNTER(1);
 
 			if (sec->lines[i]->special < 100 || sec->lines[i]->special >= 300)
 				continue;
 
 			FOFsector = true;
 
-			while ((targetsecnum = P_FindSectorFromTag(sec->lines[i]->tag, targetsecnum)) >= 0)
+			TAG_ITER_SECTORS(1, tag2, targetsecnum)
 			{
 				targetsec = &sectors[targetsecnum];
 
@@ -1530,7 +1539,7 @@ void T_EachTimeThinker(eachtime_t *eachtime)
 		if (!playersArea[i] && (!eachtime->triggerOnExit || !P_IsPlayerValid(i)))
 			continue;
 
-		CONS_Debug(DBG_GAMELOGIC, "Trying to activate each time executor with tag %d\n", eachtime->sourceline->tag);
+		CONS_Debug(DBG_GAMELOGIC, "Trying to activate each time executor with tag %d\n", tag);
 
 		// 03/08/14 -Monster Iestyn
 		// No more stupid hacks involving changing eachtime->sourceline's tag or special or whatever!
@@ -1562,11 +1571,13 @@ void T_RaiseSector(raise_t *raise)
 	fixed_t distToNearestEndpoint;
 	INT32 direction;
 	result_e res = 0;
+	mtag_t tag = raise->tag;
+	TAG_ITER_DECLARECOUNTER(0);
 
 	if (raise->sector->crumblestate >= CRUMBLE_FALL || raise->sector->ceilingdata)
 		return;
 
-	for (i = -1; (i = P_FindSectorFromTag(raise->tag, i)) >= 0 ;)
+	TAG_ITER_SECTORS(0, tag, i)
 	{
 		sector = &sectors[i];
 
@@ -1693,7 +1704,7 @@ void T_RaiseSector(raise_t *raise)
 	raise->sector->ceilspeed = 42;
 	raise->sector->floorspeed = speed*direction;
 
-	for (i = -1; (i = P_FindSectorFromTag(raise->tag, i)) >= 0 ;)
+	TAG_ITER_SECTORS(0, tag, i)
 		P_RecalcPrecipInSector(&sectors[i]);
 }
 
@@ -1810,8 +1821,10 @@ void EV_DoFloor(line_t *line, floor_e floortype)
 	INT32 secnum = -1;
 	sector_t *sec;
 	floormove_t *dofloor;
+	mtag_t tag = Tag_FGet(&line->tags);
+	TAG_ITER_DECLARECOUNTER(0);
 
-	while ((secnum = P_FindSectorFromTag(line->tag, secnum)) >= 0)
+	TAG_ITER_SECTORS(0, tag, secnum)
 	{
 		sec = &sectors[secnum];
 
@@ -2025,9 +2038,11 @@ void EV_DoElevator(line_t *line, elevator_e elevtype, boolean customspeed)
 	INT32 secnum = -1;
 	sector_t *sec;
 	elevator_t *elevator;
+	mtag_t tag = Tag_FGet(&line->tags);
+	TAG_ITER_DECLARECOUNTER(0);
 
 	// act on all sectors with the same tag as the triggering linedef
-	while ((secnum = P_FindSectorFromTag(line->tag,secnum)) >= 0)
+	TAG_ITER_SECTORS(0, tag, secnum)
 	{
 		sec = &sectors[secnum];
 
@@ -2148,6 +2163,7 @@ void EV_CrumbleChain(sector_t *sec, ffloor_t *rover)
 	INT16 flags;
 
 	sector_t *controlsec = rover->master->frontsector;
+	mtag_t tag = Tag_FGet(&controlsec->tags);
 
 	if (sec == NULL)
 	{
@@ -2176,9 +2192,9 @@ void EV_CrumbleChain(sector_t *sec, ffloor_t *rover)
 	lifetime = 3*TICRATE;
 	flags = 0;
 
-	if (controlsec->tag != 0)
+	if (tag != 0)
 	{
-		INT32 tagline = P_FindSpecialLineFromTag(14, controlsec->tag, -1);
+		INT32 tagline = Tag_FindLineSpecial(14, tag);
 		if (tagline != -1)
 		{
 			if (sides[lines[tagline].sidenum[0]].toptexture)
@@ -2322,6 +2338,8 @@ INT32 EV_StartCrumble(sector_t *sec, ffloor_t *rover, boolean floating,
 	crumble_t *crumble;
 	sector_t *foundsec;
 	INT32 i;
+	mtag_t tag = Tag_FGet(&rover->master->tags);
+	TAG_ITER_DECLARECOUNTER(0);
 
 	// If floor is already activated, skip it
 	if (sec->floordata)
@@ -2364,7 +2382,7 @@ INT32 EV_StartCrumble(sector_t *sec, ffloor_t *rover, boolean floating,
 
 	crumble->sector->crumblestate = CRUMBLE_ACTIVATED;
 
-	for (i = -1; (i = P_FindSectorFromTag(crumble->sourceline->tag, i)) >= 0 ;)
+	TAG_ITER_SECTORS(0, tag, i)
 	{
 		foundsec = &sectors[i];
 
@@ -2413,7 +2431,7 @@ void EV_MarioBlock(ffloor_t *rover, sector_t *sector, mobj_t *puncher)
 		block->direction = 1;
 		block->floorstartheight = block->sector->floorheight;
 		block->ceilingstartheight = block->sector->ceilingheight;
-		block->tag = (INT16)sector->tag;
+		block->tag = (INT16)Tag_FGet(&sector->tags);
 
 		if (itsamonitor)
 		{
diff --git a/src/p_inter.c b/src/p_inter.c
index bd044f32a1672f81eacb28c1b9a284643d796591..415c679e4922b1d55920bc5f937c39fee1927e30 100644
--- a/src/p_inter.c
+++ b/src/p_inter.c
@@ -1388,7 +1388,7 @@ void P_TouchSpecialThing(mobj_t *special, mobj_t *toucher, boolean heightcheck)
 				if (player->bot)
 					return;
 
-				junk.tag = LE_AXE;
+				Tag_FSet(&junk.tags, LE_AXE);
 				EV_DoElevator(&junk, bridgeFall, false);
 
 				// scan the remaining thinkers to find koopa
diff --git a/src/p_lights.c b/src/p_lights.c
index 371077a302d39b903b88658baf492d38680dcbb7..d396e92d3dedff6ac56f40901335ec701d6625e9 100644
--- a/src/p_lights.c
+++ b/src/p_lights.c
@@ -374,8 +374,10 @@ void P_FadeLightBySector(sector_t *sector, INT32 destvalue, INT32 speed, boolean
 void P_FadeLight(INT16 tag, INT32 destvalue, INT32 speed, boolean ticbased, boolean force)
 {
 	INT32 i;
+	TAG_ITER_DECLARECOUNTER(0);
+
 	// search all sectors for ones with tag
-	for (i = -1; (i = P_FindSectorFromTag(tag, i)) >= 0 ;)
+	TAG_ITER_SECTORS(0, tag, i)
 	{
 		if (!force && ticbased // always let speed fader execute
 			&& sectors[i].lightingdata
diff --git a/src/p_mobj.c b/src/p_mobj.c
index 8b6b66849d56f0e2b380925885a5e296f399e99f..4fc561b20eda8c4bfd3952c8b3ea80035677d614 100644
--- a/src/p_mobj.c
+++ b/src/p_mobj.c
@@ -3536,16 +3536,19 @@ static boolean P_CameraCheckHeat(camera_t *thiscam)
 {
 	sector_t *sector;
 	fixed_t halfheight = thiscam->z + (thiscam->height >> 1);
+	size_t i;
 
 	// see if we are in water
 	sector = thiscam->subsector->sector;
 
-	if (P_FindSpecialLineFromTag(13, sector->tag, -1) != -1)
-		return true;
+	for (i = 0; i < sector->tags.count; i++)
+		if (Tag_FindLineSpecial(13, sector->tags.tags[i]) != -1)
+			return true;
 
 	if (sector->ffloors)
 	{
 		ffloor_t *rover;
+		size_t j;
 
 		for (rover = sector->ffloors; rover; rover = rover->next)
 		{
@@ -3557,7 +3560,8 @@ static boolean P_CameraCheckHeat(camera_t *thiscam)
 			if (halfheight <= P_GetFFloorBottomZAt(rover, thiscam->x, thiscam->y))
 				continue;
 
-			if (P_FindSpecialLineFromTag(13, rover->master->frontsector->tag, -1) != -1)
+			for (j = 0; j < rover->master->frontsector->tags.count; j++)
+			if (Tag_FindLineSpecial(13, rover->master->frontsector->tags.tags[j]) != -1)
 				return true;
 		}
 	}
@@ -4626,16 +4630,18 @@ static boolean P_Boss4MoveCage(mobj_t *mobj, fixed_t delta)
 	const UINT16 tag = 65534 + (mobj->spawnpoint ? mobj->spawnpoint->extrainfo*LE_PARAMWIDTH : 0);
 	INT32 snum;
 	sector_t *sector;
-	for (snum = sectors[tag%numsectors].firsttag; snum != -1; snum = sector->nexttag)
+	boolean gotcage = false;
+	TAG_ITER_DECLARECOUNTER(0);
+
+	TAG_ITER_SECTORS(0, tag, snum)
 	{
 		sector = &sectors[snum];
-		if (sector->tag != tag)
-			continue;
 		sector->floorheight += delta;
 		sector->ceilingheight += delta;
 		P_CheckSector(sector, true);
+		gotcage = true;
 	}
-	return sectors[tag%numsectors].firsttag != -1;
+	return gotcage;
 }
 
 // Move Boss4's arms to angle
@@ -4707,26 +4713,16 @@ static void P_Boss4PinchSpikeballs(mobj_t *mobj, angle_t angle, fixed_t dz)
 static void P_Boss4DestroyCage(mobj_t *mobj)
 {
 	const UINT16 tag = 65534 + (mobj->spawnpoint ? mobj->spawnpoint->extrainfo*LE_PARAMWIDTH : 0);
-	INT32 snum, next;
+	INT32 snum;
 	size_t a;
 	sector_t *sector, *rsec;
 	ffloor_t *rover;
+	TAG_ITER_DECLARECOUNTER(0);
 
-	// This will be the final iteration of sector tag.
-	// We'll destroy the tag list as we go.
-	next = sectors[tag%numsectors].firsttag;
-	sectors[tag%numsectors].firsttag = -1;
-
-	for (snum = next; snum != -1; snum = next)
+	TAG_ITER_SECTORS(0, tag, snum)
 	{
 		sector = &sectors[snum];
 
-		next = sector->nexttag;
-		sector->nexttag = -1;
-		if (sector->tag != tag)
-			continue;
-		sector->tag = 0;
-
 		// Destroy the FOFs.
 		for (a = 0; a < sector->numattached; a++)
 		{
@@ -10046,11 +10042,12 @@ void P_MobjThinker(mobj_t *mobj)
 	// Sector special (2,8) allows ANY mobj to trigger a linedef exec
 	if (mobj->subsector && GETSECSPECIAL(mobj->subsector->sector->special, 2) == 8)
 	{
-		sector_t *sec2;
-
-		sec2 = P_ThingOnSpecial3DFloor(mobj);
+		sector_t *sec2 = P_ThingOnSpecial3DFloor(mobj);
 		if (sec2 && GETSECSPECIAL(sec2->special, 2) == 1)
-			P_LinedefExecute(sec2->tag, mobj, sec2);
+		{
+			mtag_t tag = Tag_FGet(&sec2->tags);
+			P_LinedefExecute(tag, mobj, sec2);
+		}
 	}
 
 	if (mobj->scale != mobj->destscale)
@@ -10274,14 +10271,19 @@ void P_PushableThinker(mobj_t *mobj)
 	sec = mobj->subsector->sector;
 
 	if (GETSECSPECIAL(sec->special, 2) == 1 && mobj->z == sec->floorheight)
-		P_LinedefExecute(sec->tag, mobj, sec);
-//	else if (GETSECSPECIAL(sec->special, 2) == 8)
 	{
-		sector_t *sec2;
+		mtag_t tag = Tag_FGet(&sec->tags);
+		P_LinedefExecute(tag, mobj, sec);
+	}
 
-		sec2 = P_ThingOnSpecial3DFloor(mobj);
+//	else if (GETSECSPECIAL(sec->special, 2) == 8)
+	{
+		sector_t *sec2 = P_ThingOnSpecial3DFloor(mobj);
 		if (sec2 && GETSECSPECIAL(sec2->special, 2) == 1)
-			P_LinedefExecute(sec2->tag, mobj, sec2);
+		{
+			mtag_t tag = Tag_FGet(&sec2->tags);
+			P_LinedefExecute(tag, mobj, sec2);
+		}
 	}
 
 	// it has to be pushable RIGHT NOW for this part to happen
@@ -12030,8 +12032,7 @@ static boolean P_SetupMace(mapthing_t *mthing, mobj_t *mobj, boolean *doangle)
 	const size_t mthingi = (size_t)(mthing - mapthings);
 
 	// Find the corresponding linedef special, using angle as tag
-	// P_FindSpecialLineFromTag works here now =D
-	line = P_FindSpecialLineFromTag(9, mthing->angle, -1);
+	line = Tag_FindLineSpecial(9, mthing->angle);
 
 	if (line == -1)
 	{
@@ -12341,7 +12342,7 @@ static boolean P_SetupParticleGen(mapthing_t *mthing, mobj_t *mobj)
 	const size_t mthingi = (size_t)(mthing - mapthings);
 
 	// Find the corresponding linedef special, using angle as tag
-	line = P_FindSpecialLineFromTag(15, mthing->angle, -1);
+	line = Tag_FindLineSpecial(15, mthing->angle);
 
 	if (line == -1)
 	{
@@ -12580,17 +12581,20 @@ static boolean P_SetupSpawnedMapThing(mapthing_t *mthing, mobj_t *mobj, boolean
 		break;
 	}
 	case MT_SKYBOX:
-		if (mthing->tag < 0 || mthing->tag > 15)
+	{
+		mtag_t tag = Tag_FGet(&mthing->tags);
+		if (tag < 0 || tag > 15)
 		{
-			CONS_Debug(DBG_GAMELOGIC, "P_SetupSpawnedMapThing: Skybox ID %d of mapthing %s is not between 0 and 15!\n", mthing->tag, sizeu1((size_t)(mthing - mapthings)));
+			CONS_Debug(DBG_GAMELOGIC, "P_SetupSpawnedMapThing: Skybox ID %d of mapthing %s is not between 0 and 15!\n", tag, sizeu1((size_t)(mthing - mapthings)));
 			break;
 		}
 
 		if (mthing->options & MTF_OBJECTSPECIAL)
-			skyboxcenterpnts[mthing->tag] = mobj;
+			skyboxcenterpnts[tag] = mobj;
 		else
-			skyboxviewpnts[mthing->tag] = mobj;
+			skyboxviewpnts[tag] = mobj;
 		break;
+	}
 	case MT_EGGSTATUE:
 		if (mthing->options & MTF_EXTRA)
 		{
diff --git a/src/p_polyobj.c b/src/p_polyobj.c
index 63d062c224bf2dbcbaa733215cfb653f711fd60b..874edbd50cfb60d6ff0308748dd149c96f8480c3 100644
--- a/src/p_polyobj.c
+++ b/src/p_polyobj.c
@@ -556,10 +556,11 @@ static void Polyobj_moveToSpawnSpot(mapthing_t *anchor)
 	polyobj_t *po;
 	vertex_t  dist, sspot;
 	size_t i;
+	mtag_t tag = Tag_FGet(&anchor->tags);
 
-	if (!(po = Polyobj_GetForNum(anchor->tag)))
+	if (!(po = Polyobj_GetForNum(tag)))
 	{
-		CONS_Debug(DBG_POLYOBJ, "Bad polyobject %d for anchor point\n", anchor->tag);
+		CONS_Debug(DBG_POLYOBJ, "Bad polyobject %d for anchor point\n", tag);
 		return;
 	}
 
@@ -1342,7 +1343,7 @@ void Polyobj_InitLevel(void)
 		{
 			qitem = (mobjqitem_t *)M_QueueIterator(&spawnqueue);
 
-			Polyobj_spawnPolyObj(i, qitem->mo, qitem->mo->spawnpoint->tag);
+			Polyobj_spawnPolyObj(i, qitem->mo, Tag_FGet(&qitem->mo->spawnpoint->tags));
 		}
 
 		// move polyobjects to spawn points
@@ -2444,10 +2445,11 @@ boolean EV_DoPolyObjFlag(polyflagdata_t *pfdata)
 	polymove_t *th;
 	size_t i;
 	INT32 start;
+	mtag_t tag = pfdata->polyObjNum;
 
-	if (!(po = Polyobj_GetForNum(pfdata->polyObjNum)))
+	if (!(po = Polyobj_GetForNum(tag)))
 	{
-		CONS_Debug(DBG_POLYOBJ, "EV_DoPolyFlag: bad polyobj %d\n", pfdata->polyObjNum);
+		CONS_Debug(DBG_POLYOBJ, "EV_DoPolyFlag: bad polyobj %d\n", tag);
 		return false;
 	}
 
@@ -2470,7 +2472,7 @@ boolean EV_DoPolyObjFlag(polyflagdata_t *pfdata)
 	po->thinker = &th->thinker;
 
 	// set fields
-	th->polyObjNum = pfdata->polyObjNum;
+	th->polyObjNum = tag;
 	th->distance   = 0;
 	th->speed      = pfdata->speed;
 	th->angle      = pfdata->angle;
diff --git a/src/p_saveg.c b/src/p_saveg.c
index 6c9c7aff45f6f1b871e8f1323c108a8e5de81dc9..2abd65a9598cafe6e224d6f2d319e86aeb0cab5c 100644
--- a/src/p_saveg.c
+++ b/src/p_saveg.c
@@ -923,7 +923,7 @@ static void UnArchiveFFloors(const sector_t *ss)
 
 static void ArchiveSectors(void)
 {
-	size_t i;
+	size_t i, j;
 	const sector_t *ss = sectors;
 	const sector_t *spawnss = spawnsectors;
 	UINT8 diff, diff2, diff3;
@@ -961,10 +961,8 @@ static void ArchiveSectors(void)
 		if (ss->ceilingpic_angle != spawnss->ceilingpic_angle)
 			diff2 |= SD_CEILANG;
 
-		if (ss->tag != spawnss->tag)
+		if (!Tag_Compare(&ss->tags, &spawnss->tags))
 			diff2 |= SD_TAG;
-		if (ss->nexttag != spawnss->nexttag || ss->firsttag != spawnss->firsttag)
-			diff3 |= SD_TAGLIST;
 
 		if (ss->extra_colormap != spawnss->extra_colormap)
 			diff3 |= SD_COLORMAP;
@@ -1012,12 +1010,11 @@ static void ArchiveSectors(void)
 				WRITEANGLE(save_p, ss->floorpic_angle);
 			if (diff2 & SD_CEILANG)
 				WRITEANGLE(save_p, ss->ceilingpic_angle);
-			if (diff2 & SD_TAG) // save only the tag
-				WRITEINT16(save_p, ss->tag);
-			if (diff3 & SD_TAGLIST) // save both firsttag and nexttag
-			{ // either of these could be changed even if tag isn't
-				WRITEINT32(save_p, ss->firsttag);
-				WRITEINT32(save_p, ss->nexttag);
+			if (diff2 & SD_TAG)
+			{
+				WRITEUINT32(save_p, ss->tags.count);
+				for (j = 0; j < ss->tags.count; j++)
+					WRITEINT16(save_p, ss->tags.tags[j]);
 			}
 
 			if (diff3 & SD_COLORMAP)
@@ -1035,7 +1032,7 @@ static void ArchiveSectors(void)
 
 static void UnArchiveSectors(void)
 {
-	UINT16 i;
+	UINT16 i, j;
 	UINT8 diff, diff2, diff3;
 	for (;;)
 	{
@@ -1089,13 +1086,29 @@ static void UnArchiveSectors(void)
 		if (diff2 & SD_CEILANG)
 			sectors[i].ceilingpic_angle = READANGLE(save_p);
 		if (diff2 & SD_TAG)
-			sectors[i].tag = READINT16(save_p); // DON'T use P_ChangeSectorTag
-		if (diff3 & SD_TAGLIST)
 		{
-			sectors[i].firsttag = READINT32(save_p);
-			sectors[i].nexttag = READINT32(save_p);
+			size_t ncount = READUINT32(save_p);
+
+			// Remove entries from global lists.
+			for (j = 0; j < sectors[i].tags.count; j++)
+				Taggroup_Remove(tags_sectors, sectors[i].tags.tags[j], i);
+
+			// Reallocate if size differs.
+			if (ncount != sectors[i].tags.count)
+			{
+				sectors[i].tags.count = ncount;
+				sectors[i].tags.tags = Z_Realloc(sectors[i].tags.tags, ncount*sizeof(mtag_t), PU_LEVEL, NULL);
+			}
+
+			for (j = 0; j < ncount; j++)
+				sectors[i].tags.tags[j] = READINT16(save_p);
+
+			// Add new entries.
+			for (j = 0; j < sectors[i].tags.count; j++)
+				Taggroup_Remove(tags_sectors, sectors[i].tags.tags[j], i);
 		}
 
+
 		if (diff3 & SD_COLORMAP)
 			sectors[i].extra_colormap = GetNetColormapFromList(READUINT32(save_p));
 		if (diff3 & SD_CRUMBLESTATE)
diff --git a/src/p_setup.c b/src/p_setup.c
index 23a73b1f51b1b28bd49f0a2cffeb4e8544c19bb1..d3e91d71ab264f75d74bb202c972f3d4265bf752 100644
--- a/src/p_setup.c
+++ b/src/p_setup.c
@@ -82,6 +82,8 @@
 
 #include "fastcmp.h" // textmap parsing
 
+#include "taglist.h"
+
 //
 // Map MD5, calculated on level load.
 // Sent to clients in PT_SERVERINFO.
@@ -963,8 +965,6 @@ static void P_LoadVertices(UINT8 *data)
 
 static void P_InitializeSector(sector_t *ss)
 {
-	ss->nexttag = ss->firsttag = -1;
-
 	memset(&ss->soundorg, 0, sizeof(ss->soundorg));
 
 	ss->validcount = 0;
@@ -1035,7 +1035,7 @@ static void P_LoadSectors(UINT8 *data)
 
 		ss->lightlevel = SHORT(ms->lightlevel);
 		ss->special = SHORT(ms->special);
-		ss->tag = SHORT(ms->tag);
+		Tag_FSet(&ss->tags, SHORT(ms->tag));
 
 		ss->floor_xoffs = ss->floor_yoffs = 0;
 		ss->ceiling_xoffs = ss->ceiling_yoffs = 0;
@@ -1077,7 +1077,6 @@ static void P_InitializeLinedef(line_t *ld)
 #ifdef WALLSPLATS
 	ld->splats = NULL;
 #endif
-	ld->firsttag = ld->nexttag = -1;
 	ld->polyobj = NULL;
 
 	ld->text = NULL;
@@ -1149,7 +1148,7 @@ static void P_LoadLinedefs(UINT8 *data)
 	{
 		ld->flags = SHORT(mld->flags);
 		ld->special = SHORT(mld->special);
-		ld->tag = SHORT(mld->tag);
+		Tag_FSet(&ld->tags, SHORT(mld->tag));
 		memset(ld->args, 0, NUMLINEARGS*sizeof(*ld->args));
 		memset(ld->stringargs, 0x00, NUMLINESTRINGARGS*sizeof(*ld->stringargs));
 		ld->alpha = FRACUNIT;
@@ -1342,7 +1341,7 @@ static void P_LoadSidedefs(UINT8 *data)
 						|| (msd->toptexture[0] >= 'A' && msd->toptexture[0] <= 'F'))
 						sd->toptexture = axtoi(msd->toptexture);
 					else
-						I_Error("Custom FOF (tag %d) needs a value in the linedef's back side upper texture field.", sd->line->tag);
+						I_Error("Custom FOF (line id %s) needs a value in the linedef's back side upper texture field.", sizeu1(sd->line - lines));
 
 					sd->midtexture = R_TextureNumForName(msd->midtexture);
 					sd->bottomtexture = R_TextureNumForName(msd->bottomtexture);
@@ -1382,8 +1381,8 @@ static void P_LoadThings(UINT8 *data)
 		mt->type = READUINT16(data);
 		mt->options = READUINT16(data);
 		mt->extrainfo = (UINT8)(mt->type >> 12);
+		Tag_FSet(&mt->tags, 0);
 		mt->scale = FRACUNIT;
-		mt->tag = 0;
 		memset(mt->args, 0, NUMMAPTHINGARGS*sizeof(*mt->args));
 		memset(mt->stringargs, 0x00, NUMMAPTHINGSTRINGARGS*sizeof(*mt->stringargs));
 		mt->pitch = mt->roll = 0;
@@ -1519,7 +1518,17 @@ static void ParseTextmapSectorParameter(UINT32 i, char *param, char *val)
 	else if (fastcmp(param, "special"))
 		sectors[i].special = atol(val);
 	else if (fastcmp(param, "id"))
-		sectors[i].tag = atol(val);
+		Tag_FSet(&sectors[i].tags, atol(val));
+	else if (fastcmp(param, "moreids"))
+	{
+		char* id = val;
+		while (id)
+		{
+			Tag_Add(&sectors[i].tags, atol(id));
+			if ((id = strchr(id, ' ')))
+				id++;
+		}
+	}
 	else if (fastcmp(param, "xpanningfloor"))
 		sectors[i].floor_xoffs = FLOAT_TO_FIXED(atof(val));
 	else if (fastcmp(param, "ypanningfloor"))
@@ -1597,7 +1606,17 @@ static void ParseTextmapSidedefParameter(UINT32 i, char *param, char *val)
 static void ParseTextmapLinedefParameter(UINT32 i, char *param, char *val)
 {
 	if (fastcmp(param, "id"))
-		lines[i].tag = atol(val);
+		Tag_FSet(&lines[i].tags, atol(val));
+	else if (fastcmp(param, "moreids"))
+	{
+		char* id = val;
+		while (id)
+		{
+			Tag_Add(&lines[i].tags, atol(id));
+			if ((id = strchr(id, ' ')))
+				id++;
+		}
+	}
 	else if (fastcmp(param, "special"))
 		lines[i].special = atol(val);
 	else if (fastcmp(param, "v1"))
@@ -1666,8 +1685,18 @@ static void ParseTextmapLinedefParameter(UINT32 i, char *param, char *val)
 static void ParseTextmapThingParameter(UINT32 i, char *param, char *val)
 {
 	if (fastcmp(param, "id"))
-		mapthings[i].tag = atol(val);
-	if (fastcmp(param, "x"))
+		Tag_FSet(&mapthings[i].tags, atol(val));
+	else if (fastcmp(param, "moreids"))
+	{
+		char* id = val;
+		while (id)
+		{
+			Tag_Add(&mapthings[i].tags, atol(id));
+			if ((id = strchr(id, ' ')))
+				id++;
+		}
+	}
+	else if (fastcmp(param, "x"))
 		mapthings[i].x = atol(val);
 	else if (fastcmp(param, "y"))
 		mapthings[i].y = atol(val);
@@ -1824,7 +1853,7 @@ static void P_LoadTextmap(void)
 		sc->lightlevel = 255;
 
 		sc->special = 0;
-		sc->tag = 0;
+		Tag_FSet(&sc->tags, 0);
 
 		sc->floor_xoffs = sc->floor_yoffs = 0;
 		sc->ceiling_xoffs = sc->ceiling_yoffs = 0;
@@ -1842,6 +1871,7 @@ static void P_LoadTextmap(void)
 		textmap_colormap.fadeend = 31;
 		textmap_colormap.flags = 0;
 		TextmapParse(sectorsPos[i], i, ParseTextmapSectorParameter);
+
 		P_InitializeSector(sc);
 		if (textmap_colormap.used)
 		{
@@ -1858,7 +1888,8 @@ static void P_LoadTextmap(void)
 		ld->v1 = ld->v2 = NULL;
 		ld->flags = 0;
 		ld->special = 0;
-		ld->tag = 0;
+		Tag_FSet(&ld->tags, 0);
+
 		memset(ld->args, 0, NUMLINEARGS*sizeof(*ld->args));
 		memset(ld->stringargs, 0x00, NUMLINESTRINGARGS*sizeof(*ld->stringargs));
 		ld->alpha = FRACUNIT;
@@ -1906,8 +1937,8 @@ static void P_LoadTextmap(void)
 		mt->options = 0;
 		mt->z = 0;
 		mt->extrainfo = 0;
+		Tag_FSet(&mt->tags, 0);
 		mt->scale = FRACUNIT;
-		mt->tag = 0;
 		memset(mt->args, 0, NUMMAPTHINGARGS*sizeof(*mt->args));
 		memset(mt->stringargs, 0x00, NUMMAPTHINGSTRINGARGS*sizeof(*mt->stringargs));
 		mt->mobj = NULL;
@@ -2924,30 +2955,6 @@ static void P_LinkMapData(void)
 	}
 }
 
-/** Hashes the sector tags across the sectors and linedefs.
-  *
-  * \sa P_FindSectorFromTag, P_ChangeSectorTag
-  * \author Lee Killough
-  */
-static inline void P_InitTagLists(void)
-{
-	register size_t i;
-
-	for (i = numsectors - 1; i != (size_t)-1; i--)
-	{
-		size_t j = (unsigned)sectors[i].tag % numsectors;
-		sectors[i].nexttag = sectors[j].firsttag;
-		sectors[j].firsttag = (INT32)i;
-	}
-
-	for (i = numlines - 1; i != (size_t)-1; i--)
-	{
-		size_t j = (unsigned)lines[i].tag % numlines;
-		lines[i].nexttag = lines[j].firsttag;
-		lines[j].firsttag = (INT32)i;
-	}
-}
-
 //For maps in binary format, converts setup of specials to UDMF format.
 static void P_ConvertBinaryMap(void)
 {
@@ -2955,14 +2962,28 @@ static void P_ConvertBinaryMap(void)
 
 	for (i = 0; i < numlines; i++)
 	{
+		mtag_t tag = Tag_FGet(&lines[i].tags);
+
 		switch (lines[i].special)
 		{
 		case 20: //PolyObject first line
 		{
-			INT32 paramline = P_FindSpecialLineFromTag(22, lines[i].tag, -1);
+			INT32 check = -1;
+			INT32 paramline = -1;
+
+			TAG_ITER_DECLARECOUNTER(0);
+
+			TAG_ITER_LINES(0, tag, check)
+			{
+				if (lines[check].special == 22)
+				{
+					paramline = check;
+					break;
+				}
+			}
 
 			//PolyObject ID
-			lines[i].args[0] = lines[i].tag;
+			lines[i].args[0] = tag;
 
 			//Default: Invisible planes
 			lines[i].args[3] |= TMPF_INVISIBLEPLANES;
@@ -3008,7 +3029,7 @@ static void P_ConvertBinaryMap(void)
 				CONS_Alert(CONS_WARNING, "Linedef %s is missing the hook name of the Lua function to call! (This should be given in the front texture fields)\n", sizeu1(i));
 			break;
 		case 447: //Change colormap
-			lines[i].args[0] = lines[i].tag;
+			lines[i].args[0] = Tag_FGet(&lines[i].tags);
 			if (lines[i].flags & ML_EFFECT3)
 				lines[i].args[2] |= TMCF_RELATIVE;
 			if (lines[i].flags & ML_EFFECT1)
@@ -3024,7 +3045,7 @@ static void P_ConvertBinaryMap(void)
 				abs(sides[lines[i].sidenum[1]].rowoffset >> FRACBITS)
 				: abs(sides[lines[i].sidenum[0]].rowoffset >> FRACBITS));
 
-			lines[i].args[0] = lines[i].tag;
+			lines[i].args[0] = Tag_FGet(&lines[i].tags);
 			if (lines[i].flags & ML_EFFECT4)
 				lines[i].args[2] = speed;
 			else
@@ -3044,10 +3065,10 @@ static void P_ConvertBinaryMap(void)
 			break;
 		}
 		case 456: //Stop fading colormap
-			lines[i].args[0] = lines[i].tag;
+			lines[i].args[0] = Tag_FGet(&lines[i].tags);
 			break;
 		case 606: //Colormap
-			lines[i].args[0] = lines[i].tag;
+			lines[i].args[0] = Tag_FGet(&lines[i].tags);
 			break;
 		case 700: //Slope front sector floor
 		case 701: //Slope front sector ceiling
@@ -3088,7 +3109,7 @@ static void P_ConvertBinaryMap(void)
 			else if (lines[i].special == 715)
 				lines[i].args[0] = TMSP_BACKCEILING;
 
-			lines[i].args[1] = lines[i].tag;
+			lines[i].args[1] = tag;
 
 			if (lines[i].flags & ML_EFFECT6)
 			{
@@ -3120,9 +3141,9 @@ static void P_ConvertBinaryMap(void)
 		case 721: //Copy front side ceiling slope
 		case 722: //Copy front side floor and ceiling slope
 			if (lines[i].special != 721)
-				lines[i].args[0] = lines[i].tag;
+				lines[i].args[0] = tag;
 			if (lines[i].special != 720)
-				lines[i].args[1] = lines[i].tag;
+				lines[i].args[1] = tag;
 			lines[i].special = 720;
 			break;
 		case 900: //Translucent wall (10%)
@@ -3155,21 +3176,39 @@ static void P_ConvertBinaryMap(void)
 		switch (mapthings[i].type)
 		{
 		case 750:
+			Tag_Add(&mapthings[i].tags, mapthings[i].angle);
+			break;
 		case 760:
 		case 761:
-			mapthings[i].tag = mapthings[i].angle;
+			Tag_FSet(&mapthings[i].tags, mapthings[i].angle);
 			break;
 		case 762:
 		{
-			INT32 firstline = P_FindSpecialLineFromTag(20, mapthings[i].angle, -1);
+			INT32 check = -1;
+			INT32 firstline = -1;
+			mtag_t tag = mapthings[i].angle;
+
+			TAG_ITER_DECLARECOUNTER(0);
+
+			Tag_FSet(&mapthings[i].tags, tag);
+
+			TAG_ITER_LINES(0, tag, check)
+			{
+				if (lines[check].special == 20)
+				{
+					firstline = check;
+					break;
+				}
+			}
+
 			if (firstline != -1)
 				lines[firstline].args[3] |= TMPF_CRUSH;
-			mapthings[i].tag = mapthings[i].angle;
+
 			mapthings[i].type = 761;
 			break;
 		}
 		case 780:
-			mapthings[i].tag = mapthings[i].extrainfo;
+			Tag_FSet(&mapthings[i].tags, mapthings[i].extrainfo);
 			break;
 		default:
 			break;
@@ -3243,6 +3282,7 @@ static boolean P_LoadMapFromFile(void)
 {
 	virtres_t *virt = vres_GetMap(lastloadedmaplumpnum);
 	virtlump_t *textmap = vres_Find(virt, "TEXTMAP");
+	size_t i;
 	udmf = textmap != NULL;
 
 	if (!P_LoadMapData(virt))
@@ -3252,7 +3292,7 @@ static boolean P_LoadMapFromFile(void)
 
 	P_LinkMapData();
 
-	P_InitTagLists();   // Create xref tables for tags
+	Taglist_InitGlobalTables();
 
 	if (!udmf)
 		P_ConvertBinaryMap();
@@ -3266,6 +3306,10 @@ static boolean P_LoadMapFromFile(void)
 	memcpy(spawnlines, lines, numlines * sizeof(*lines));
 	memcpy(spawnsides, sides, numsides * sizeof(*sides));
 
+	for (i = 0; i < numsectors; i++)
+		if (sectors[i].tags.count)
+			spawnsectors[i].tags.tags = memcpy(Z_Malloc(sectors[i].tags.count*sizeof(mtag_t), PU_LEVEL, NULL), sectors[i].tags.tags, sectors[i].tags.count*sizeof(mtag_t));
+
 	P_MakeMapMD5(virt, &mapmd5);
 
 	vres_Free(virt);
diff --git a/src/p_slopes.c b/src/p_slopes.c
index e93b0f6c993650043a1afe588c21aa1b64bbac37..aa46a84024d459e2c3dab0164d4122b59f30b126 100644
--- a/src/p_slopes.c
+++ b/src/p_slopes.c
@@ -139,7 +139,7 @@ void T_DynamicSlopeVert (dynplanethink_t* th)
 	INT32 l;
 
 	for (i = 0; i < 3; i++) {
-		l = P_FindSpecialLineFromTag(799, th->tags[i], -1);
+		l = Tag_FindLineSpecial(799, th->tags[i]);
 		if (l != -1) {
 			th->vex[i].z = lines[l].frontsector->floorheight;
 		}
@@ -405,9 +405,6 @@ static void line_SpawnViaLine(const int linenum, const boolean spawnthinker)
 				P_AddDynSlopeThinker(cslope, DP_BACKCEIL, line, extent, NULL, NULL);
 		}
 	}
-
-	if(!line->tag)
-		return;
 }
 
 /// Creates a new slope from three mapthings with the specified IDs
@@ -426,11 +423,11 @@ static pslope_t *MakeViaMapthings(INT16 tag1, INT16 tag2, INT16 tag3, UINT8 flag
 		if (mt->type != 750) // Haha, I'm hijacking the old Chaos Spawn thingtype for something!
 			continue;
 
-		if (!vertices[0] && mt->tag == tag1)
+		if (!vertices[0] && Tag_Find(&mt->tags, tag1))
 			vertices[0] = mt;
-		else if (!vertices[1] && mt->tag == tag2)
+		else if (!vertices[1] && Tag_Find(&mt->tags, tag2))
 			vertices[1] = mt;
-		else if (!vertices[2] && mt->tag == tag3)
+		else if (!vertices[2] && Tag_Find(&mt->tags, tag3))
 			vertices[2] = mt;
 	}
 
@@ -549,11 +546,11 @@ static boolean P_SetSlopeFromTag(sector_t *sec, INT32 tag, boolean ceiling)
 {
 	INT32 i;
 	pslope_t **secslope = ceiling ? &sec->c_slope : &sec->f_slope;
+	TAG_ITER_DECLARECOUNTER(0);
 
 	if (!tag || *secslope)
 		return false;
-
-	for (i = -1; (i = P_FindSectorFromTag(tag, i)) >= 0;)
+	TAG_ITER_SECTORS(0, tag, i)
 	{
 		pslope_t *srcslope = ceiling ? sectors[i].c_slope : sectors[i].f_slope;
 		if (srcslope)
diff --git a/src/p_spec.c b/src/p_spec.c
index 7a79bd494f951e8a0befbf2812a23b2326200624..a1afdd00ddb12c4c4253e4dfe4547a8e3391734d 100644
--- a/src/p_spec.c
+++ b/src/p_spec.c
@@ -988,99 +988,12 @@ static sector_t *P_FindModelCeilingSector(fixed_t ceildestheight, INT32 secnum)
 }
 #endif
 
-/** Searches the tag lists for the next sector with a given tag.
-  *
-  * \param tag   Tag number to look for.
-  * \param start -1 to start anew, or the result of a previous call to keep
-  *              searching.
-  * \return Number of the next tagged sector found.
-  */
-INT32 P_FindSectorFromTag(INT16 tag, INT32 start)
-{
-	if (tag == -1)
-	{
-		start++;
-
-		if (start >= (INT32)numsectors)
-			return -1;
-
-		return start;
-	}
-	else
-	{
-		start = start >= 0 ? sectors[start].nexttag :
-			sectors[(unsigned)tag % numsectors].firsttag;
-		while (start >= 0 && sectors[start].tag != tag)
-			start = sectors[start].nexttag;
-		return start;
-	}
-}
-
-/** Searches the tag lists for the next line with a given tag and special.
-  *
-  * \param tag     Tag number.
-  * \param start   -1 to start anew, or the result of a previous call to keep
-  *                searching.
-  * \return Number of next suitable line found.
-  * \author Graue <graue@oceanbase.org>
-  */
-static INT32 P_FindLineFromTag(INT32 tag, INT32 start)
-{
-	if (tag == -1)
-	{
-		start++;
-
-		if (start >= (INT32)numlines)
-			return -1;
-
-		return start;
-	}
-	else
-	{
-		start = start >= 0 ? lines[start].nexttag :
-			lines[(unsigned)tag % numlines].firsttag;
-		while (start >= 0 && lines[start].tag != tag)
-			start = lines[start].nexttag;
-		return start;
-	}
-}
-
-INT32 P_FindSpecialLineFromTag(INT16 special, INT16 tag, INT32 start)
-{
-	if (tag == -1)
-	{
-		start++;
-
-		// This redundant check stops the compiler from complaining about function expansion
-		// elsewhere for some reason and everything is awful
-		if (start >= (INT32)numlines)
-			return -1;
-
-		while (start < (INT32)numlines && lines[start].special != special)
-			start++;
-
-		if (start >= (INT32)numlines)
-			return -1;
-
-		return start;
-	}
-	else
-	{
-		start = start >= 0 ? lines[start].nexttag :
-			lines[(unsigned)tag % numlines].firsttag;
-		while (start >= 0 && (lines[start].tag != tag || lines[start].special != special))
-			start = lines[start].nexttag;
-		return start;
-	}
-}
-
-
 // Parses arguments for parameterized polyobject door types
 static boolean PolyDoor(line_t *line)
 {
 	polydoordata_t pdd;
 
-	pdd.polyObjNum = line->tag; // polyobject id
+	pdd.polyObjNum = Tag_FGet(&line->tags); // polyobject id
 
 	switch(line->special)
 	{
@@ -1117,7 +1030,7 @@ static boolean PolyMove(line_t *line)
 {
 	polymovedata_t pmd;
 
-	pmd.polyObjNum = line->tag;
+	pmd.polyObjNum = Tag_FGet(&line->tags);
 	pmd.speed      = sides[line->sidenum[0]].textureoffset / 8;
 	pmd.angle      = R_PointToAngle2(line->v1->x, line->v1->y, line->v2->x, line->v2->y);
 	pmd.distance   = sides[line->sidenum[0]].rowoffset;
@@ -1131,7 +1044,7 @@ static boolean PolyMove(line_t *line)
 // If NOCLIMB is ticked, the polyobject will still be tangible, just not visible.
 static void PolyInvisible(line_t *line)
 {
-	INT32 polyObjNum = line->tag;
+	INT32 polyObjNum = Tag_FGet(&line->tags);
 	polyobj_t *po;
 
 	if (!(po = Polyobj_GetForNum(polyObjNum)))
@@ -1155,7 +1068,7 @@ static void PolyInvisible(line_t *line)
 // If NOCLIMB is ticked, the polyobject will not be tangible, just visible.
 static void PolyVisible(line_t *line)
 {
-	INT32 polyObjNum = line->tag;
+	INT32 polyObjNum = Tag_FGet(&line->tags);
 	polyobj_t *po;
 
 	if (!(po = Polyobj_GetForNum(polyObjNum)))
@@ -1180,7 +1093,7 @@ static void PolyVisible(line_t *line)
 // Frontsector floor / 100 = translevel
 static void PolyTranslucency(line_t *line)
 {
-	INT32 polyObjNum = line->tag;
+	INT32 polyObjNum = Tag_FGet(&line->tags);
 	polyobj_t *po;
 	INT32 value;
 
@@ -1212,7 +1125,7 @@ static void PolyTranslucency(line_t *line)
 // Makes a polyobject translucency fade and applies tangibility
 static boolean PolyFade(line_t *line)
 {
-	INT32 polyObjNum = line->tag;
+	INT32 polyObjNum = Tag_FGet(&line->tags);
 	polyobj_t *po;
 	polyfadedata_t pfd;
 	INT32 value;
@@ -1274,7 +1187,7 @@ static boolean PolyWaypoint(line_t *line)
 {
 	polywaypointdata_t pwd;
 
-	pwd.polyObjNum = line->tag;
+	pwd.polyObjNum = Tag_FGet(&line->tags);
 	pwd.speed      = sides[line->sidenum[0]].textureoffset / 8;
 	pwd.sequence   = sides[line->sidenum[0]].rowoffset >> FRACBITS; // Sequence #
 
@@ -1301,7 +1214,7 @@ static boolean PolyRotate(line_t *line)
 {
 	polyrotdata_t prd;
 
-	prd.polyObjNum = line->tag;
+	prd.polyObjNum = Tag_FGet(&line->tags);
 	prd.speed      = sides[line->sidenum[0]].textureoffset >> FRACBITS; // angular speed
 	prd.distance   = sides[line->sidenum[0]].rowoffset >> FRACBITS; // angular distance
 
@@ -1326,7 +1239,7 @@ static boolean PolyFlag(line_t *line)
 {
 	polyflagdata_t pfd;
 
-	pfd.polyObjNum = line->tag;
+	pfd.polyObjNum = Tag_FGet(&line->tags);
 	pfd.speed = P_AproxDistance(line->dx, line->dy) >> FRACBITS;
 	pfd.angle = R_PointToAngle2(line->v1->x, line->v1->y, line->v2->x, line->v2->y) >> ANGLETOFINESHIFT;
 	pfd.momx = sides[line->sidenum[0]].textureoffset >> FRACBITS;
@@ -1339,7 +1252,7 @@ static boolean PolyDisplace(line_t *line)
 {
 	polydisplacedata_t pdd;
 
-	pdd.polyObjNum = line->tag;
+	pdd.polyObjNum = Tag_FGet(&line->tags);
 
 	pdd.controlSector = line->frontsector;
 	pdd.dx = line->dx>>8;
@@ -1355,7 +1268,7 @@ static boolean PolyRotDisplace(line_t *line)
 	polyrotdisplacedata_t pdd;
 	fixed_t anginter, distinter;
 
-	pdd.polyObjNum = line->tag;
+	pdd.polyObjNum = Tag_FGet(&line->tags);
 	pdd.controlSector = line->frontsector;
 
 	// Rotate 'anginter' interval for each 'distinter' interval from the control sector.
@@ -1375,66 +1288,6 @@ static boolean PolyRotDisplace(line_t *line)
 	return EV_DoPolyObjRotDisplace(&pdd);
 }
 
-/** Changes a sector's tag.
-  * Used by the linedef executor tag changer and by crumblers.
-  *
-  * \param sector Sector whose tag will be changed.
-  * \param newtag New tag number for this sector.
-  * \sa P_InitTagLists, P_FindSectorFromTag
-  * \author Graue <graue@oceanbase.org>
-  */
-void P_ChangeSectorTag(UINT32 sector, INT16 newtag)
-{
-	INT16 oldtag;
-	INT32 i;
-
-	I_Assert(sector < numsectors);
-
-	if ((oldtag = sectors[sector].tag) == newtag)
-		return;
-
-	// first you have to remove it from the old tag's taglist
-	i = sectors[(unsigned)oldtag % numsectors].firsttag;
-
-	if (i == -1) // shouldn't happen
-		I_Error("Corrupt tag list for sector %u\n", sector);
-	else if ((UINT32)i == sector)
-		sectors[(unsigned)oldtag % numsectors].firsttag = sectors[sector].nexttag;
-	else
-	{
-		while (sectors[i].nexttag != -1 && (UINT32)sectors[i].nexttag < sector )
-			i = sectors[i].nexttag;
-
-		sectors[i].nexttag = sectors[sector].nexttag;
-	}
-
-	sectors[sector].tag = newtag;
-
-	// now add it to the new tag's taglist
-	if ((UINT32)sectors[(unsigned)newtag % numsectors].firsttag > sector)
-	{
-		sectors[sector].nexttag = sectors[(unsigned)newtag % numsectors].firsttag;
-		sectors[(unsigned)newtag % numsectors].firsttag = sector;
-	}
-	else
-	{
-		i = sectors[(unsigned)newtag % numsectors].firsttag;
-
-		if (i == -1)
-		{
-			sectors[(unsigned)newtag % numsectors].firsttag = sector;
-			sectors[sector].nexttag = -1;
-		}
-		else
-		{
-			while (sectors[i].nexttag != -1 && (UINT32)sectors[i].nexttag < sector )
-				i = sectors[i].nexttag;
-
-			sectors[sector].nexttag = sectors[i].nexttag;
-			sectors[i].nexttag = sector;
-		}
-	}
-}
 
 //
 // P_RunNightserizeExecutors
@@ -2103,7 +1956,7 @@ void P_LinedefExecute(INT16 tag, mobj_t *actor, sector_t *caller)
 
 	for (masterline = 0; masterline < numlines; masterline++)
 	{
-		if (lines[masterline].tag != tag)
+		if (Tag_FGet(&lines[masterline].tags) != tag)
 			continue;
 
 		// "No More Enemies" and "Level Load" take care of themselves.
@@ -2369,6 +2222,8 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec)
 {
 	INT32 secnum = -1;
 	mobj_t *bot = NULL;
+	mtag_t tag = Tag_FGet(&line->tags);
+	TAG_ITER_DECLARECOUNTER(0);
 
 	I_Assert(!mo || !P_MobjWasRemoved(mo)); // If mo is there, mo must be valid!
 
@@ -2396,7 +2251,7 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec)
 				newceilinglightsec = line->frontsector->ceilinglightsec;
 
 				// act on all sectors with the same tag as the triggering linedef
-				while ((secnum = P_FindSectorFromTag(line->tag, secnum)) >= 0)
+				TAG_ITER_SECTORS(0, tag, secnum)
 				{
 					if (sectors[secnum].lightingdata)
 					{
@@ -2451,17 +2306,17 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec)
 		case 409: // Change tagged sectors' tag
 		// (formerly "Change calling sectors' tag", but behavior was changed)
 		{
-			while ((secnum = P_FindSectorFromTag(line->tag, secnum)) >= 0)
-				P_ChangeSectorTag(secnum,(INT16)(sides[line->sidenum[0]].textureoffset>>FRACBITS));
+			TAG_ITER_SECTORS(0, tag, secnum)
+				Tag_SectorFSet(secnum,(INT16)(sides[line->sidenum[0]].textureoffset>>FRACBITS));
 			break;
 		}
 
 		case 410: // Change front sector's tag
-			P_ChangeSectorTag((UINT32)(line->frontsector - sectors), (INT16)(sides[line->sidenum[0]].textureoffset>>FRACBITS));
+			Tag_SectorFSet((UINT32)(line->frontsector - sectors), (INT16)(sides[line->sidenum[0]].textureoffset>>FRACBITS));
 			break;
 
 		case 411: // Stop floor/ceiling movement in tagged sector(s)
-			while ((secnum = P_FindSectorFromTag(line->tag, secnum)) >= 0)
+			TAG_ITER_SECTORS(0, tag, secnum)
 			{
 				if (sectors[secnum].floordata)
 				{
@@ -2531,7 +2386,7 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec)
 				}
 				else
 				{
-					if ((secnum = P_FindSectorFromTag(line->tag, -1)) < 0)
+					if ((secnum = Tag_Iterate_Sectors(tag, 0)) < 0)
 						return;
 
 					dest = P_GetObjectTypeInSectorNum(MT_TELEPORTMAN, secnum);
@@ -2638,7 +2493,7 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec)
 					return;
 				}
 
-				if (line->tag != 0) // Do special stuff only if a non-zero linedef tag is set
+				if (tag != 0) // Do special stuff only if a non-zero linedef tag is set
 				{
 					// Play sounds from tagged sectors' origins.
 					if (line->flags & ML_EFFECT5) // Repeat Midtexture
@@ -2646,7 +2501,7 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec)
 						// Additionally play the sound from tagged sectors' soundorgs
 						sector_t *sec;
 
-						while ((secnum = P_FindSectorFromTag(line->tag, secnum)) >= 0)
+						TAG_ITER_SECTORS(0, tag, secnum)
 						{
 							sec = &sectors[secnum];
 							S_StartSound(&sec->soundorg, sfxnum);
@@ -2666,7 +2521,7 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec)
 							if (!camobj)
 								continue;
 
-							if (foundit || (camobj->subsector->sector->tag == line->tag))
+							if (foundit || Tag_Find(&camobj->subsector->sector->tags, tag))
 							{
 								foundit = true;
 								break;
@@ -2675,7 +2530,7 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec)
 							// Only trigger if mobj is touching the tag
 							for(rover = camobj->subsector->sector->ffloors; rover; rover = rover->next)
 							{
-								if (rover->master->frontsector->tag != line->tag)
+								if (!Tag_Find(&rover->master->frontsector->tags, tag))
 									continue;
 
 								if (camobj->z > P_GetSpecialTopZ(camobj, sectors + rover->secnum, camobj->subsector->sector))
@@ -2761,7 +2616,7 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec)
 			break;
 
 		case 416: // Spawn adjustable fire flicker
-			while ((secnum = P_FindSectorFromTag(line->tag, secnum)) >= 0)
+			TAG_ITER_SECTORS(0, tag, secnum)
 			{
 				if (line->flags & ML_NOCLIMB && line->backsector)
 				{
@@ -2795,7 +2650,7 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec)
 			break;
 
 		case 417: // Spawn adjustable glowing light
-			while ((secnum = P_FindSectorFromTag(line->tag, secnum)) >= 0)
+			TAG_ITER_SECTORS(0, tag, secnum)
 			{
 				if (line->flags & ML_NOCLIMB && line->backsector)
 				{
@@ -2829,7 +2684,7 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec)
 			break;
 
 		case 418: // Spawn adjustable strobe flash (unsynchronized)
-			while ((secnum = P_FindSectorFromTag(line->tag, secnum)) >= 0)
+			TAG_ITER_SECTORS(0, tag, secnum)
 			{
 				if (line->flags & ML_NOCLIMB && line->backsector)
 				{
@@ -2863,7 +2718,7 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec)
 			break;
 
 		case 419: // Spawn adjustable strobe flash (synchronized)
-			while ((secnum = P_FindSectorFromTag(line->tag, secnum)) >= 0)
+			TAG_ITER_SECTORS(0, tag, secnum)
 			{
 				if (line->flags & ML_NOCLIMB && line->backsector)
 				{
@@ -2897,7 +2752,7 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec)
 			break;
 
 		case 420: // Fade light levels in tagged sectors to new value
-			P_FadeLight(line->tag,
+			P_FadeLight(tag,
 				(line->flags & ML_DONTPEGBOTTOM) ? max(sides[line->sidenum[0]].textureoffset>>FRACBITS, 0) : line->frontsector->lightlevel,
 				// failsafe: if user specifies Back Y Offset and NOT Front Y Offset, use the Back Offset
 				// to be consistent with other light and fade specials
@@ -2911,7 +2766,7 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec)
 			break;
 
 		case 421: // Stop lighting effect in tagged sectors
-			while ((secnum = P_FindSectorFromTag(line->tag, secnum)) >= 0)
+			TAG_ITER_SECTORS(0, tag, secnum)
 				if (sectors[secnum].lightingdata)
 				{
 					P_RemoveThinker(&((elevator_t *)sectors[secnum].lightingdata)->thinker);
@@ -2926,7 +2781,7 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec)
 				if ((!mo || !mo->player) && !titlemapinaction) // only players have views, and title screens
 					return;
 
-				if ((secnum = P_FindSectorFromTag(line->tag, -1)) < 0)
+				if ((secnum = Tag_Iterate_Sectors(tag, 0)) < 0)
 					return;
 
 				altview = P_GetObjectTypeInSectorNum(MT_ALTVIEWMAN, secnum);
@@ -3108,8 +2963,7 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec)
 						continue;
 
 					scroller = (scroll_t *)th;
-
-					if (sectors[scroller->affectee].tag != line->tag)
+					if (!Tag_Find(&sectors[scroller->affectee].tags, tag))
 						continue;
 
 					scroller->dx = FixedMul(line->dx>>SCROLL_SHIFT, CARRYFACTOR);
@@ -3126,7 +2980,7 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec)
 				ffloor_t *rover; // FOF that we are going to crumble
 				boolean foundrover = false; // for debug, "Can't find a FOF" message
 
-				for (secnum = -1; (secnum = P_FindSectorFromTag(sectag, secnum)) >= 0 ;)
+				TAG_ITER_SECTORS(0, sectag, secnum)
 				{
 					sec = sectors + secnum;
 
@@ -3138,7 +2992,7 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec)
 
 					for (rover = sec->ffloors; rover; rover = rover->next)
 					{
-						if (rover->master->frontsector->tag == foftag)
+						if (Tag_Find(&rover->master->frontsector->tags, foftag))
 						{
 							foundrover = true;
 
@@ -3185,12 +3039,13 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec)
 				size_t linenum;
 				side_t *set = &sides[line->sidenum[0]], *this;
 				boolean always = !(line->flags & ML_NOCLIMB); // If noclimb: Only change mid texture if mid texture already exists on tagged lines, etc.
+
 				for (linenum = 0; linenum < numlines; linenum++)
 				{
 					if (lines[linenum].special == 439)
 						continue; // Don't override other set texture lines!
 
-					if (lines[linenum].tag != line->tag)
+					if (!Tag_Find(&lines[linenum].tags, tag))
 						continue; // Find tagged lines
 
 					// Front side
@@ -3250,7 +3105,7 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec)
 			if (line->sidenum[1] != 0xffff)
 				state = (statenum_t)sides[line->sidenum[1]].toptexture;
 
-			while ((secnum = P_FindSectorFromTag(line->tag, secnum)) >= 0)
+			TAG_ITER_SECTORS(0, tag, secnum)
 			{
 				boolean tryagain;
 				sec = sectors + secnum;
@@ -3310,7 +3165,7 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec)
 				boolean foundrover = false; // for debug, "Can't find a FOF" message
 				ffloortype_e oldflags; // store FOF's old flags
 
-				for (secnum = -1; (secnum = P_FindSectorFromTag(sectag, secnum)) >= 0 ;)
+				TAG_ITER_SECTORS(0, sectag, secnum)
 				{
 					sec = sectors + secnum;
 
@@ -3322,7 +3177,7 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec)
 
 					for (rover = sec->ffloors; rover; rover = rover->next)
 					{
-						if (rover->master->frontsector->tag == foftag)
+						if (Tag_Find(&rover->master->frontsector->tags, foftag))
 						{
 							foundrover = true;
 
@@ -3368,7 +3223,7 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec)
 				if (line->flags & ML_NOCLIMB) // don't respawn!
 					respawn = false;
 
-				for (secnum = -1; (secnum = P_FindSectorFromTag(sectag, secnum)) >= 0 ;)
+				TAG_ITER_SECTORS(0, sectag, secnum)
 				{
 					sec = sectors + secnum;
 
@@ -3380,7 +3235,7 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec)
 
 					for (rover = sec->ffloors; rover; rover = rover->next)
 					{
-						if (rover->master->frontsector->tag == foftag)
+						if (Tag_Find(&rover->master->frontsector->tags, foftag))
 						{
 							foundrover = true;
 
@@ -3415,7 +3270,7 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec)
 					source = line->frontsector->extra_colormap;
 				else
 				{
-					INT32 sourcesec = P_FindSectorFromTag(line->args[1], -1);
+					INT32 sourcesec = Tag_Iterate_Sectors(line->args[1], 0);
 					if (sourcesec == -1)
 					{
 						CONS_Debug(DBG_GAMELOGIC, "Line type 447 Executor: Can't find sector with source colormap (tag %d)!\n", line->args[1]);
@@ -3424,8 +3279,7 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec)
 					source = sectors[sourcesec].extra_colormap;
 				}
 			}
-
-			for (secnum = -1; (secnum = P_FindSectorFromTag(line->args[0], secnum)) >= 0;)
+			TAG_ITER_SECTORS(0, line->args[0], secnum)
 			{
 				if (sectors[secnum].colormap_protected)
 					continue;
@@ -3477,7 +3331,7 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec)
 				{
 					CONS_Alert(CONS_WARNING,
 					M_GetText("Skybox switch linedef (tag %d) doesn't have anything to do.\nConsider changing the linedef's flag configuration or removing it entirely.\n"),
-					line->tag);
+					tag);
 				}
 				else
 				{
@@ -3515,7 +3369,7 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec)
 			{
 				CONS_Alert(CONS_WARNING,
 					M_GetText("Boss enable linedef (tag %d) has an invalid texture x offset.\nConsider changing it or removing it entirely.\n"),
-					line->tag);
+					tag);
 				break;
 			}
 			if (line->flags & ML_NOCLIMB)
@@ -3532,7 +3386,7 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec)
 		}
 
 		case 450: // Execute Linedef Executor - for recursion
-			P_LinedefExecute(line->tag, mo, NULL);
+			P_LinedefExecute(tag, mo, NULL);
 			break;
 
 		case 451: // Execute Random Linedef Executor
@@ -3560,7 +3414,7 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec)
 			ffloor_t *rover; // FOF that we are going to operate
 			boolean foundrover = false; // for debug, "Can't find a FOF" message
 
-			for (secnum = -1; (secnum = P_FindSectorFromTag(sectag, secnum)) >= 0 ;)
+			TAG_ITER_SECTORS(0, sectag, secnum)
 			{
 				sec = sectors + secnum;
 
@@ -3572,7 +3426,7 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec)
 
 				for (rover = sec->ffloors; rover; rover = rover->next)
 				{
-					if (rover->master->frontsector->tag == foftag)
+					if (Tag_Find(&rover->master->frontsector->tags, foftag))
 					{
 						foundrover = true;
 
@@ -3624,7 +3478,7 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec)
 			boolean foundrover = false; // for debug, "Can't find a FOF" message
 			size_t j = 0; // sec->ffloors is saved as ffloor #0, ss->ffloors->next is #1, etc
 
-			for (secnum = -1; (secnum = P_FindSectorFromTag(sectag, secnum)) >= 0 ;)
+			TAG_ITER_SECTORS(0, sectag, secnum)
 			{
 				sec = sectors + secnum;
 
@@ -3636,7 +3490,7 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec)
 
 				for (rover = sec->ffloors; rover; rover = rover->next)
 				{
-					if (rover->master->frontsector->tag == foftag)
+					if (Tag_Find(&rover->master->frontsector->tags, foftag))
 					{
 						foundrover = true;
 
@@ -3709,7 +3563,7 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec)
 			ffloor_t *rover; // FOF that we are going to operate
 			boolean foundrover = false; // for debug, "Can't find a FOF" message
 
-			for (secnum = -1; (secnum = P_FindSectorFromTag(sectag, secnum)) >= 0 ;)
+			TAG_ITER_SECTORS(0, sectag, secnum)
 			{
 				sec = sectors + secnum;
 
@@ -3721,7 +3575,7 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec)
 
 				for (rover = sec->ffloors; rover; rover = rover->next)
 				{
-					if (rover->master->frontsector->tag == foftag)
+					if (Tag_Find(&rover->master->frontsector->tags, foftag))
 					{
 						foundrover = true;
 
@@ -3750,7 +3604,7 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec)
 					dest = line->frontsector->extra_colormap;
 				else
 				{
-					INT32 destsec = P_FindSectorFromTag(line->args[1], -1);
+					INT32 destsec = Tag_Iterate_Sectors(line->args[1], 0);
 					if (destsec == -1)
 					{
 						CONS_Debug(DBG_GAMELOGIC, "Line type 455 Executor: Can't find sector with destination colormap (tag %d)!\n", line->args[1]);
@@ -3760,7 +3614,7 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec)
 				}
 			}
 
-			for (secnum = -1; (secnum = P_FindSectorFromTag(line->args[0], secnum)) >= 0;)
+			TAG_ITER_SECTORS(0, line->args[0], secnum)
 			{
 				extracolormap_t *source_exc, *dest_exc, *exc;
 
@@ -3840,7 +3694,7 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec)
 			break;
 		}
 		case 456: // Stop fade colormap
-			for (secnum = -1; (secnum = P_FindSectorFromTag(line->args[0], secnum)) >= 0 ;)
+			TAG_ITER_SECTORS(0, line->args[0], secnum)
 				P_ResetColormapFader(&sectors[secnum]);
 			break;
 
@@ -3854,7 +3708,7 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec)
 				boolean persist = (line->flags & ML_EFFECT2);
 				mobj_t *anchormo;
 
-				if ((secnum = P_FindSectorFromTag(line->tag, -1)) < 0)
+				if ((secnum = Tag_Iterate_Sectors(tag, 0)) < 0)
 					return;
 
 				anchormo = P_GetObjectTypeInSectorNum(MT_ANGLEMAN, secnum);
@@ -3885,7 +3739,7 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec)
 			{
 				INT32 promptnum = max(0, (sides[line->sidenum[0]].textureoffset>>FRACBITS)-1);
 				INT32 pagenum = max(0, (sides[line->sidenum[0]].rowoffset>>FRACBITS)-1);
-				INT32 postexectag = abs((line->sidenum[1] != 0xFFFF) ? sides[line->sidenum[1]].textureoffset>>FRACBITS : line->tag);
+				INT32 postexectag = abs((line->sidenum[1] != 0xFFFF) ? sides[line->sidenum[1]].textureoffset>>FRACBITS : tag);
 
 				boolean closetextprompt = (line->flags & ML_BLOCKMONSTERS);
 				//boolean allplayers = (line->flags & ML_NOCLIMB);
@@ -4009,7 +3863,7 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec)
 					if (!mo2->spawnpoint)
 						continue;
 
-					if (mo2->spawnpoint->angle != line->tag)
+					if (mo2->spawnpoint->angle != tag)
 						continue;
 
 					P_KillMobj(mo2, NULL, mo, 0);
@@ -4033,11 +3887,12 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec)
 		case 465: // Set linedef executor delay
 			{
 				INT32 linenum;
+				TAG_ITER_DECLARECOUNTER(1);
 
 				if (!udmf)
 					break;
 
-				for (linenum = -1; (linenum = P_FindLineFromTag(line->args[0], linenum)) >= 0 ;)
+				TAG_ITER_LINES(1, line->args[0], linenum)
 				{
 					if (line->args[2])
 						lines[linenum].executordelay += line->args[1];
@@ -4382,6 +4237,7 @@ void P_ProcessSpecialSector(player_t *player, sector_t *sector, sector_t *rovers
 	INT32 i = 0;
 	INT32 section1, section2, section3, section4;
 	INT32 special;
+	mtag_t sectag = Tag_FGet(&sector->tags);
 
 	section1 = GETSECSPECIAL(sector->special, 1);
 	section2 = GETSECSPECIAL(sector->special, 2);
@@ -4536,7 +4392,7 @@ void P_ProcessSpecialSector(player_t *player, sector_t *sector, sector_t *rovers
 		case 6: // Linedef executor (7 Emeralds)
 		case 7: // Linedef executor (NiGHTS Mare)
 			if (!player->bot)
-				P_LinedefExecute(sector->tag, player->mo, sector);
+				P_LinedefExecute(sectag, player->mo, sector);
 			break;
 		case 8: // Tells pushable things to check FOFs
 			break;
@@ -4565,14 +4421,14 @@ void P_ProcessSpecialSector(player_t *player, sector_t *sector, sector_t *rovers
 			sector->special = 0;
 
 			// Move the button down
-			junk.tag = 680;
+			Tag_FSet(&junk.tags, 680);
 			EV_DoElevator(&junk, elevateDown, false);
 
 			// Open the top FOF
-			junk.tag = 681;
+			Tag_FSet(&junk.tags, 681);
 			EV_DoFloor(&junk, raiseFloorToNearestFast);
 			// Open the bottom FOF
-			junk.tag = 682;
+			Tag_FSet(&junk.tags, 682);
 			EV_DoCeiling(&junk, lowerToLowestFast);
 
 			// Mark all players with the time to exit thingy!
@@ -4607,7 +4463,7 @@ DoneSection2:
 			if (player->powers[pw_flashing] != 0 && player->powers[pw_flashing] < TICRATE/2)
 				break;
 
-			i = P_FindSpecialLineFromTag(4, sector->tag, -1);
+			i = Tag_FindLineSpecial(4, sectag);
 
 			if (i != -1)
 			{
@@ -4620,7 +4476,7 @@ DoneSection2:
 
 				if (linespeed == 0)
 				{
-					CONS_Debug(DBG_GAMELOGIC, "ERROR: Speed pad (tag %d) at zero speed.\n", sector->tag);
+					CONS_Debug(DBG_GAMELOGIC, "ERROR: Speed pad (tag %d) at zero speed.\n", sectag);
 					break;
 				}
 
@@ -4714,7 +4570,7 @@ DoneSection2:
 				// important: use sector->tag on next line instead of player->mo->subsector->tag
 				// this part is different from in P_PlayerThink, this is what was causing
 				// FOF custom exits not to work.
-				lineindex = P_FindSpecialLineFromTag(2, sector->tag, -1);
+				lineindex = Tag_FindLineSpecial(2, sectag);
 
 				if (G_CoopGametype() && lineindex != -1) // Custom exit!
 				{
@@ -4840,7 +4696,7 @@ DoneSection2:
 					break;
 
 				// Find line #3 tagged to this sector
-				lineindex = P_FindSpecialLineFromTag(3, sector->tag, -1);
+				lineindex = Tag_FindLineSpecial(3, sectag);
 
 				if (lineindex == -1)
 				{
@@ -4905,7 +4761,7 @@ DoneSection2:
 					break;
 
 				// Find line #3 tagged to this sector
-				lineindex = P_FindSpecialLineFromTag(3, sector->tag, -1);
+				lineindex = Tag_FindLineSpecial(3, sectag);
 
 				if (lineindex == -1)
 				{
@@ -5034,7 +4890,7 @@ DoneSection2:
 				memset(&resulthigh, 0x00, sizeof(resulthigh));
 
 				// Find line #11 tagged to this sector
-				lineindex = P_FindSpecialLineFromTag(11, sector->tag, -1);
+				lineindex = Tag_FindLineSpecial(11, sectag);
 
 				if (lineindex == -1)
 				{
@@ -5654,7 +5510,7 @@ static ffloor_t *P_AddFakeFloor(sector_t *sec, sector_t *sec2, line_t *master, f
 	{
 		fixed_t tempceiling = sec2->ceilingheight;
 		//flip the sector around and print an error instead of crashing 12.1.08 -Inuyasha
-		CONS_Alert(CONS_ERROR, M_GetText("A FOF tagged %d has a top height below its bottom.\n"), master->tag);
+		CONS_Alert(CONS_ERROR, M_GetText("FOF (line %s) has a top height below its bottom.\n"), sizeu1(master - lines));
 		sec2->ceilingheight = sec2->floorheight;
 		sec2->floorheight = tempceiling;
 	}
@@ -5794,7 +5650,7 @@ static ffloor_t *P_AddFakeFloor(sector_t *sec, sector_t *sec2, line_t *master, f
 
 	if ((flags & FF_FLOATBOB))
 	{
-		P_AddFloatThinker(sec2, sec->tag, master);
+		P_AddFloatThinker(sec2, Tag_FGet(&master->tags), master);
 		CheckForFloatBob = true;
 	}
 
@@ -5950,7 +5806,7 @@ static void P_AddAirbob(sector_t *sec, INT16 tag, fixed_t dist, boolean raise, b
   * \sa P_SpawnSpecials, T_ThwompSector
   * \author SSNTails <http://www.ssntails.org>
   */
-static inline void P_AddThwompThinker(sector_t *sec, INT16 tag, line_t *sourceline, fixed_t crushspeed, fixed_t retractspeed, UINT16 sound)
+static inline void P_AddThwompThinker(sector_t *sec, line_t *sourceline, fixed_t crushspeed, fixed_t retractspeed, UINT16 sound)
 {
 	thwomp_t *thwomp;
 
@@ -5974,7 +5830,7 @@ static inline void P_AddThwompThinker(sector_t *sec, INT16 tag, line_t *sourceli
 	thwomp->floorstartheight = sec->floorheight;
 	thwomp->ceilingstartheight = sec->ceilingheight;
 	thwomp->delay = 1;
-	thwomp->tag = tag;
+	thwomp->tag = Tag_FGet(&sourceline->tags);
 	thwomp->sound = sound;
 
 	sec->floordata = thwomp;
@@ -6066,8 +5922,9 @@ void T_LaserFlash(laserthink_t *flash)
 	sector_t *sector;
 	sector_t *sourcesec = flash->sourceline->frontsector;
 	fixed_t top, bottom;
+	TAG_ITER_DECLARECOUNTER(0);
 
-	for (s = -1; (s = P_FindSectorFromTag(flash->tag, s)) >= 0 ;)
+	TAG_ITER_SECTORS(0, flash->tag, s)
 	{
 		sector = &sectors[s];
 		for (fflr = sector->ffloors; fflr; fflr = fflr->next)
@@ -6322,6 +6179,8 @@ void P_SpawnSpecials(boolean fromnetsave)
 	// Init line EFFECTs
 	for (i = 0; i < numlines; i++)
 	{
+		mtag_t tag = Tag_FGet(&lines[i].tags);
+
 		if (lines[i].special != 7) // This is a hack. I can at least hope nobody wants to prevent flat alignment in netgames...
 		{
 			// set line specials to 0 here too, same reason as above
@@ -6345,10 +6204,11 @@ void P_SpawnSpecials(boolean fromnetsave)
 			INT32 s;
 			size_t sec;
 			ffloortype_e ffloorflags;
+			TAG_ITER_DECLARECOUNTER(0);
 
 			case 1: // Definable gravity per sector
 				sec = sides[*lines[i].sidenum].sector - sectors;
-				for (s = -1; (s = P_FindSectorFromTag(lines[i].tag, s)) >= 0 ;)
+				TAG_ITER_SECTORS(0, tag, s)
 				{
 					sectors[s].gravity = &sectors[sec].floorheight; // This allows it to change in realtime!
 
@@ -6372,7 +6232,7 @@ void P_SpawnSpecials(boolean fromnetsave)
 
 			case 5: // Change camera info
 				sec = sides[*lines[i].sidenum].sector - sectors;
-				for (s = -1; (s = P_FindSectorFromTag(lines[i].tag, s)) >= 0 ;)
+				TAG_ITER_SECTORS(0, tag, s)
 					P_AddCameraScanner(&sectors[sec], &sectors[s], R_PointToAngle2(lines[i].v2->x, lines[i].v2->y, lines[i].v1->x, lines[i].v1->y));
 				break;
 
@@ -6395,22 +6255,22 @@ void P_SpawnSpecials(boolean fromnetsave)
 					}
 
 					//If no tag is given, apply to front sector
-					if (lines[i].tag == 0)
+					if (tag == 0)
 						P_ApplyFlatAlignment(lines + i, lines[i].frontsector, flatangle, xoffs, yoffs);
 					else
 					{
-						for (s = -1; (s = P_FindSectorFromTag(lines[i].tag, s)) >= 0;)
+						TAG_ITER_SECTORS(0, tag, s)
 							P_ApplyFlatAlignment(lines + i, sectors + s, flatangle, xoffs, yoffs);
 					}
 				}
 				else // Otherwise, print a helpful warning. Can I do no less?
 					CONS_Alert(CONS_WARNING,
 					M_GetText("Flat alignment linedef (tag %d) doesn't have anything to do.\nConsider changing the linedef's flag configuration or removing it entirely.\n"),
-					lines[i].tag);
+					tag);
 				break;
 
 			case 8: // Sector Parameters
-				for (s = -1; (s = P_FindSectorFromTag(lines[i].tag, s)) >= 0 ;)
+				TAG_ITER_SECTORS(0, tag, s)
 				{
 					if (lines[i].flags & ML_NOCLIMB)
 					{
@@ -6437,8 +6297,7 @@ void P_SpawnSpecials(boolean fromnetsave)
 				break;
 
 			case 10: // Vertical culling plane for sprites and FOFs
-				sec = sides[*lines[i].sidenum].sector - sectors;
-				for (s = -1; (s = P_FindSectorFromTag(lines[i].tag, s)) >= 0 ;)
+				TAG_ITER_SECTORS(0, tag, s)
 					sectors[s].cullheight = &lines[i]; // This allows it to change in realtime!
 				break;
 
@@ -6499,36 +6358,38 @@ void P_SpawnSpecials(boolean fromnetsave)
 
 			case 63: // support for drawn heights coming from different sector
 				sec = sides[*lines[i].sidenum].sector-sectors;
-				for (s = -1; (s = P_FindSectorFromTag(lines[i].tag, s)) >= 0 ;)
+				TAG_ITER_SECTORS(0, tag, s)
 					sectors[s].heightsec = (INT32)sec;
 				break;
 
 			case 64: // Appearing/Disappearing FOF option
 				if (lines[i].flags & ML_BLOCKMONSTERS) { // Find FOFs by control sector tag
-					for (s = -1; (s = P_FindSectorFromTag(lines[i].tag, s)) >= 0 ;)
+					TAG_ITER_SECTORS(0, tag, s)
 						for (j = 0; (unsigned)j < sectors[s].linecount; j++)
 							if (sectors[s].lines[j]->special >= 100 && sectors[s].lines[j]->special < 300)
 								Add_MasterDisappearer(abs(lines[i].dx>>FRACBITS), abs(lines[i].dy>>FRACBITS), abs(sides[lines[i].sidenum[0]].sector->floorheight>>FRACBITS), (INT32)(sectors[s].lines[j]-lines), (INT32)i);
 				} else // Find FOFs by effect sector tag
-					for (s = -1; (s = P_FindLineFromTag(lines[i].tag, s)) >= 0 ;)
+				{
+					TAG_ITER_LINES(0, tag, s)
 					{
 						if ((size_t)s == i)
 							continue;
-						if (sides[lines[s].sidenum[0]].sector->tag == sides[lines[i].sidenum[0]].sector->tag)
+						if (Tag_Find(&sides[lines[s].sidenum[0]].sector->tags, Tag_FGet(&sides[lines[i].sidenum[0]].sector->tags)))
 							Add_MasterDisappearer(abs(lines[i].dx>>FRACBITS), abs(lines[i].dy>>FRACBITS), abs(sides[lines[i].sidenum[0]].sector->floorheight>>FRACBITS), s, (INT32)i);
 					}
+				}
 				break;
 
 			case 66: // Displace floor by front sector
-				for (s = -1; (s = P_FindSectorFromTag(lines[i].tag, s)) >= 0 ;)
+				TAG_ITER_SECTORS(0, tag, s)
 					P_AddPlaneDisplaceThinker(pd_floor, P_AproxDistance(lines[i].dx, lines[i].dy)>>8, sides[lines[i].sidenum[0]].sector-sectors, s, !!(lines[i].flags & ML_NOCLIMB));
 				break;
 			case 67: // Displace ceiling by front sector
-				for (s = -1; (s = P_FindSectorFromTag(lines[i].tag, s)) >= 0 ;)
+				TAG_ITER_SECTORS(0, tag, s)
 					P_AddPlaneDisplaceThinker(pd_ceiling, P_AproxDistance(lines[i].dx, lines[i].dy)>>8, sides[lines[i].sidenum[0]].sector-sectors, s, !!(lines[i].flags & ML_NOCLIMB));
 				break;
 			case 68: // Displace both floor AND ceiling by front sector
-				for (s = -1; (s = P_FindSectorFromTag(lines[i].tag, s)) >= 0 ;)
+				TAG_ITER_SECTORS(0, tag, s)
 					P_AddPlaneDisplaceThinker(pd_both, P_AproxDistance(lines[i].dx, lines[i].dy)>>8, sides[lines[i].sidenum[0]].sector-sectors, s, !!(lines[i].flags & ML_NOCLIMB));
 				break;
 
@@ -6725,16 +6586,16 @@ void P_SpawnSpecials(boolean fromnetsave)
 			{
 				fixed_t dist = (lines[i].special == 150) ? 16*FRACUNIT : P_AproxDistance(lines[i].dx, lines[i].dy);
 				P_AddFakeFloorsByLine(i, FF_EXISTS|FF_SOLID|FF_RENDERALL|FF_CUTLEVEL, secthinkers);
-				P_AddAirbob(lines[i].frontsector, lines[i].tag, dist, false, !!(lines[i].flags & ML_NOCLIMB), false);
+				P_AddAirbob(lines[i].frontsector, tag, dist, false, !!(lines[i].flags & ML_NOCLIMB), false);
 				break;
 			}
 			case 152: // Adjustable air bobbing platform in reverse
 				P_AddFakeFloorsByLine(i, FF_EXISTS|FF_SOLID|FF_RENDERALL|FF_CUTLEVEL, secthinkers);
-				P_AddAirbob(lines[i].frontsector, lines[i].tag, P_AproxDistance(lines[i].dx, lines[i].dy), true, !!(lines[i].flags & ML_NOCLIMB), false);
+				P_AddAirbob(lines[i].frontsector, tag, P_AproxDistance(lines[i].dx, lines[i].dy), true, !!(lines[i].flags & ML_NOCLIMB), false);
 				break;
 			case 153: // Dynamic Sinking Platform
 				P_AddFakeFloorsByLine(i, FF_EXISTS|FF_SOLID|FF_RENDERALL|FF_CUTLEVEL, secthinkers);
-				P_AddAirbob(lines[i].frontsector, lines[i].tag, P_AproxDistance(lines[i].dx, lines[i].dy), false, !!(lines[i].flags & ML_NOCLIMB), true);
+				P_AddAirbob(lines[i].frontsector, tag, P_AproxDistance(lines[i].dx, lines[i].dy), false, !!(lines[i].flags & ML_NOCLIMB), true);
 				break;
 
 			case 160: // Float/bob platform
@@ -6784,13 +6645,13 @@ void P_SpawnSpecials(boolean fromnetsave)
 
 			case 176: // Air bobbing platform that will crumble and bob on the water when it falls and hits
 				P_AddFakeFloorsByLine(i, FF_EXISTS|FF_SOLID|FF_RENDERALL|FF_FLOATBOB|FF_CRUMBLE, secthinkers);
-				P_AddAirbob(lines[i].frontsector, lines[i].tag, 16*FRACUNIT, false, !!(lines[i].flags & ML_NOCLIMB), false);
+				P_AddAirbob(lines[i].frontsector, tag, 16*FRACUNIT, false, !!(lines[i].flags & ML_NOCLIMB), false);
 				break;
 
 			case 177: // Air bobbing platform that will crumble and bob on
 				// the water when it falls and hits, then never return
 				P_AddFakeFloorsByLine(i, FF_EXISTS|FF_SOLID|FF_RENDERALL|FF_CUTLEVEL|FF_FLOATBOB|FF_CRUMBLE|FF_NORETURN, secthinkers);
-				P_AddAirbob(lines[i].frontsector, lines[i].tag, 16*FRACUNIT, false, !!(lines[i].flags & ML_NOCLIMB), false);
+				P_AddAirbob(lines[i].frontsector, tag, 16*FRACUNIT, false, !!(lines[i].flags & ML_NOCLIMB), false);
 				break;
 
 			case 178: // Crumbling platform that will float when it hits water
@@ -6803,7 +6664,7 @@ void P_SpawnSpecials(boolean fromnetsave)
 
 			case 180: // Air bobbing platform that will crumble
 				P_AddFakeFloorsByLine(i, FF_EXISTS|FF_SOLID|FF_RENDERALL|FF_CUTLEVEL|FF_CRUMBLE, secthinkers);
-				P_AddAirbob(lines[i].frontsector, lines[i].tag, 16*FRACUNIT, false, !!(lines[i].flags & ML_NOCLIMB), false);
+				P_AddAirbob(lines[i].frontsector, tag, 16*FRACUNIT, false, !!(lines[i].flags & ML_NOCLIMB), false);
 				break;
 
 			case 190: // Rising Platform FOF (solid, opaque, shadows)
@@ -6830,7 +6691,7 @@ void P_SpawnSpecials(boolean fromnetsave)
 					ffloorflags |= FF_NOSHADE;
 				P_AddFakeFloorsByLine(i, ffloorflags, secthinkers);
 
-				P_AddRaiseThinker(lines[i].frontsector, lines[i].tag, speed, ceilingtop, ceilingbottom, !!(lines[i].flags & ML_BLOCKMONSTERS), !!(lines[i].flags & ML_NOCLIMB));
+				P_AddRaiseThinker(lines[i].frontsector, tag, speed, ceilingtop, ceilingbottom, !!(lines[i].flags & ML_BLOCKMONSTERS), !!(lines[i].flags & ML_NOCLIMB));
 				break;
 			}
 
@@ -6892,7 +6753,7 @@ void P_SpawnSpecials(boolean fromnetsave)
 				fixed_t crushspeed = (lines[i].flags & ML_EFFECT5) ? lines[i].dy >> 3 : 10*FRACUNIT;
 				fixed_t retractspeed = (lines[i].flags & ML_EFFECT5) ? lines[i].dx >> 3 : 2*FRACUNIT;
 				UINT16 sound = (lines[i].flags & ML_EFFECT4) ? sides[lines[i].sidenum[0]].textureoffset >> FRACBITS : sfx_thwomp;
-				P_AddThwompThinker(lines[i].frontsector, lines[i].tag, &lines[i], crushspeed, retractspeed, sound);
+				P_AddThwompThinker(lines[i].frontsector, &lines[i], crushspeed, retractspeed, sound);
 				P_AddFakeFloorsByLine(i, FF_EXISTS|FF_SOLID|FF_RENDERALL|FF_CUTLEVEL, secthinkers);
 				break;
 			}
@@ -6934,7 +6795,7 @@ void P_SpawnSpecials(boolean fromnetsave)
 				break;
 
 			case 258: // Laser block
-				P_AddLaserThinker(lines[i].tag, lines + i, !!(lines[i].flags & ML_EFFECT1));
+				P_AddLaserThinker(tag, lines + i, !!(lines[i].flags & ML_EFFECT1));
 				P_AddFakeFloorsByLine(i, FF_EXISTS|FF_RENDERALL|FF_NOSHADE|FF_EXTRA|FF_CUTEXTRA|FF_TRANSLUCENT, secthinkers);
 				break;
 
@@ -6945,7 +6806,7 @@ void P_SpawnSpecials(boolean fromnetsave)
 					P_AddFakeFloorsByLine(i, fofflags, secthinkers);
 				}
 				else
-					I_Error("Custom FOF (tag %d) found without a linedef back side!", lines[i].tag);
+					I_Error("Custom FOF (tag %d) found without a linedef back side!", tag);
 				break;
 
 			case 300: // Linedef executor (combines with sector special 974/975) and commands
@@ -7075,7 +6936,7 @@ void P_SpawnSpecials(boolean fromnetsave)
 				{
 					CONS_Alert(CONS_WARNING,
 						M_GetText("Boss enable linedef (tag %d) has an invalid texture x offset.\nConsider changing it or removing it entirely.\n"),
-						lines[i].tag);
+						tag);
 					break;
 				}
 				if (!(lines[i].flags & ML_NOCLIMB))
@@ -7124,46 +6985,46 @@ void P_SpawnSpecials(boolean fromnetsave)
 
 			case 600: // floor lighting independently (e.g. lava)
 				sec = sides[*lines[i].sidenum].sector-sectors;
-				for (s = -1; (s = P_FindSectorFromTag(lines[i].tag, s)) >= 0 ;)
+				TAG_ITER_SECTORS(0, tag, s)
 					sectors[s].floorlightsec = (INT32)sec;
 				break;
 
 			case 601: // ceiling lighting independently
 				sec = sides[*lines[i].sidenum].sector-sectors;
-				for (s = -1; (s = P_FindSectorFromTag(lines[i].tag, s)) >= 0 ;)
+				TAG_ITER_SECTORS(0, tag, s)
 					sectors[s].ceilinglightsec = (INT32)sec;
 				break;
 
 			case 602: // Adjustable pulsating light
 				sec = sides[*lines[i].sidenum].sector - sectors;
-				for (s = -1; (s = P_FindSectorFromTag(lines[i].tag, s)) >= 0 ;)
+				TAG_ITER_SECTORS(0, tag, s)
 					P_SpawnAdjustableGlowingLight(&sectors[sec], &sectors[s],
 						P_AproxDistance(lines[i].dx, lines[i].dy)>>FRACBITS);
 				break;
 
 			case 603: // Adjustable flickering light
 				sec = sides[*lines[i].sidenum].sector - sectors;
-				for (s = -1; (s = P_FindSectorFromTag(lines[i].tag, s)) >= 0 ;)
+				TAG_ITER_SECTORS(0, tag, s)
 					P_SpawnAdjustableFireFlicker(&sectors[sec], &sectors[s],
 						P_AproxDistance(lines[i].dx, lines[i].dy)>>FRACBITS);
 				break;
 
 			case 604: // Adjustable Blinking Light (unsynchronized)
 				sec = sides[*lines[i].sidenum].sector - sectors;
-				for (s = -1; (s = P_FindSectorFromTag(lines[i].tag, s)) >= 0 ;)
+				TAG_ITER_SECTORS(0, tag, s)
 					P_SpawnAdjustableStrobeFlash(&sectors[sec], &sectors[s],
 						abs(lines[i].dx)>>FRACBITS, abs(lines[i].dy)>>FRACBITS, false);
 				break;
 
 			case 605: // Adjustable Blinking Light (synchronized)
 				sec = sides[*lines[i].sidenum].sector - sectors;
-				for (s = -1; (s = P_FindSectorFromTag(lines[i].tag, s)) >= 0 ;)
+				TAG_ITER_SECTORS(0, tag, s)
 					P_SpawnAdjustableStrobeFlash(&sectors[sec], &sectors[s],
 						abs(lines[i].dx)>>FRACBITS, abs(lines[i].dy)>>FRACBITS, true);
 				break;
 
 			case 606: // HACK! Copy colormaps. Just plain colormaps.
-				for (s = -1; (s = P_FindSectorFromTag(lines[i].args[0], s)) >= 0;)
+				TAG_ITER_SECTORS(0, lines[i].args[0], s)
 				{
 					extracolormap_t *exc;
 
@@ -7178,7 +7039,7 @@ void P_SpawnSpecials(boolean fromnetsave)
 							exc = lines[i].frontsector->extra_colormap;
 						else
 						{
-							INT32 sourcesec = P_FindSectorFromTag(lines[i].args[1], -1);
+							INT32 sourcesec = Tag_Iterate_Sectors(lines[i].args[1], 0);
 							if (sourcesec == -1)
 							{
 								CONS_Debug(DBG_GAMELOGIC, "Line type 606: Can't find sector with source colormap (tag %d)!\n", lines[i].args[1]);
@@ -7237,11 +7098,14 @@ void P_SpawnSpecials(boolean fromnetsave)
   */
 static void P_AddFakeFloorsByLine(size_t line, ffloortype_e ffloorflags, thinkerlist_t *secthinkers)
 {
+	TAG_ITER_DECLARECOUNTER(0);
 	INT32 s;
+	mtag_t tag = Tag_FGet(&lines[line].tags);
 	size_t sec = sides[*lines[line].sidenum].sector-sectors;
 
-	for (s = -1; (s = P_FindSectorFromTag(lines[line].tag, s)) >= 0 ;)
-		P_AddFakeFloor(&sectors[s], &sectors[sec], lines+line, ffloorflags, secthinkers);
+	line_t* li = lines + line;
+	TAG_ITER_SECTORS(0, tag, s)
+		P_AddFakeFloor(&sectors[s], &sectors[sec], li, ffloorflags, secthinkers);
 }
 
 /*
@@ -7350,6 +7214,7 @@ void T_Scroll(scroll_t *s)
 		size_t i;
 		INT32 sect;
 		ffloor_t *rover;
+		TAG_ITER_DECLARECOUNTER(0);
 
 		case sc_side: // scroll wall texture
 			side = sides + s->affectee;
@@ -7386,7 +7251,7 @@ void T_Scroll(scroll_t *s)
 				if (!is3dblock)
 					continue;
 
-				for (sect = -1; (sect = P_FindSectorFromTag(line->tag, sect)) >= 0 ;)
+				TAG_ITER_SECTORS(0, Tag_FGet(&line->tags), sect)
 				{
 					sector_t *psec;
 					psec = sectors + sect;
@@ -7461,8 +7326,7 @@ void T_Scroll(scroll_t *s)
 
 				if (!is3dblock)
 					continue;
-
-				for (sect = -1; (sect = P_FindSectorFromTag(line->tag, sect)) >= 0 ;)
+				TAG_ITER_SECTORS(0, Tag_FGet(&line->tags), sect)
 				{
 					sector_t *psec;
 					psec = sectors + sect;
@@ -7560,6 +7424,7 @@ static void P_SpawnScrollers(void)
 {
 	size_t i;
 	line_t *l = lines;
+	mtag_t tag;
 
 	for (i = 0; i < numlines; i++, l++)
 	{
@@ -7568,6 +7433,8 @@ static void P_SpawnScrollers(void)
 		INT32 control = -1, accel = 0; // no control sector or acceleration
 		INT32 special = l->special;
 
+		tag = Tag_FGet(&l->tags);
+
 		// These types are same as the ones they get set to except that the
 		// first side's sector's heights cause scrolling when they change, and
 		// this linedef controls the direction and speed of the scrolling. The
@@ -7599,10 +7466,11 @@ static void P_SpawnScrollers(void)
 		switch (special)
 		{
 			register INT32 s;
+			TAG_ITER_DECLARECOUNTER(0);
 
 			case 513: // scroll effect ceiling
 			case 533: // scroll and carry objects on ceiling
-				for (s = -1; (s = P_FindSectorFromTag(l->tag, s)) >= 0 ;)
+				TAG_ITER_SECTORS(0, tag, s)
 					Add_Scroller(sc_ceiling, -dx, dy, control, s, accel, l->flags & ML_NOCLIMB);
 				if (special != 533)
 					break;
@@ -7611,13 +7479,13 @@ static void P_SpawnScrollers(void)
 			case 523:	// carry objects on ceiling
 				dx = FixedMul(dx, CARRYFACTOR);
 				dy = FixedMul(dy, CARRYFACTOR);
-				for (s = -1; (s = P_FindSectorFromTag(l->tag, s)) >= 0 ;)
+				TAG_ITER_SECTORS(0, tag, s)
 					Add_Scroller(sc_carry_ceiling, dx, dy, control, s, accel, l->flags & ML_NOCLIMB);
 				break;
 
 			case 510: // scroll effect floor
 			case 530: // scroll and carry objects on floor
-				for (s = -1; (s = P_FindSectorFromTag(l->tag, s)) >= 0 ;)
+				TAG_ITER_SECTORS(0, tag, s)
 					Add_Scroller(sc_floor, -dx, dy, control, s, accel, l->flags & ML_NOCLIMB);
 				if (special != 530)
 					break;
@@ -7626,14 +7494,15 @@ static void P_SpawnScrollers(void)
 			case 520:	// carry objects on floor
 				dx = FixedMul(dx, CARRYFACTOR);
 				dy = FixedMul(dy, CARRYFACTOR);
-				for (s = -1; (s = P_FindSectorFromTag(l->tag, s)) >= 0 ;)
+				TAG_ITER_SECTORS(0, tag, s)
 					Add_Scroller(sc_carry, dx, dy, control, s, accel, l->flags & ML_NOCLIMB);
 				break;
 
 			// scroll wall according to linedef
 			// (same direction and speed as scrolling floors)
 			case 502:
-				for (s = -1; (s = P_FindLineFromTag(l->tag, s)) >= 0 ;)
+			{
+				TAG_ITER_LINES(0, tag, s)
 					if (s != (INT32)i)
 					{
 						if (l->flags & ML_EFFECT2) // use texture offsets instead
@@ -7650,6 +7519,7 @@ static void P_SpawnScrollers(void)
 							Add_Scroller(sc_side, dx, dy, control, lines[s].sidenum[0], accel, 0);
 					}
 				break;
+			}
 
 			case 505:
 				s = lines[i].sidenum[0];
@@ -7733,8 +7603,10 @@ void T_Disappear(disappear_t *d)
 	{
 		ffloor_t *rover;
 		register INT32 s;
+		mtag_t afftag = Tag_FGet(&lines[d->affectee].tags);
+		TAG_ITER_DECLARECOUNTER(0);
 
-		for (s = -1; (s = P_FindSectorFromTag(lines[d->affectee].tag, s)) >= 0 ;)
+		TAG_ITER_SECTORS(0, afftag, s)
 		{
 			for (rover = sectors[s].ffloors; rover; rover = rover->next)
 			{
@@ -8460,14 +8332,17 @@ static void P_SpawnFriction(void)
 {
 	size_t i;
 	line_t *l = lines;
+	mtag_t tag;
 	register INT32 s;
 	fixed_t strength; // frontside texture offset controls magnitude
 	fixed_t friction; // friction value to be applied during movement
 	INT32 movefactor; // applied to each player move to simulate inertia
+	TAG_ITER_DECLARECOUNTER(0);
 
 	for (i = 0; i < numlines; i++, l++)
 		if (l->special == 540)
 		{
+			tag = Tag_FGet(&l->tags);
 			strength = sides[l->sidenum[0]].textureoffset>>FRACBITS;
 			if (strength > 0) // sludge
 				strength = strength*2; // otherwise, the maximum sludginess value is +967...
@@ -8488,7 +8363,7 @@ static void P_SpawnFriction(void)
 			else
 				movefactor = FRACUNIT;
 
-			for (s = -1; (s = P_FindSectorFromTag(l->tag, s)) >= 0 ;)
+			TAG_ITER_SECTORS(0, tag, s)
 				Add_Friction(friction, movefactor, s, -1);
 		}
 }
@@ -9004,22 +8879,26 @@ static void P_SpawnPushers(void)
 {
 	size_t i;
 	line_t *l = lines;
+	mtag_t tag;
 	register INT32 s;
 	mobj_t *thing;
+	TAG_ITER_DECLARECOUNTER(0);
 
 	for (i = 0; i < numlines; i++, l++)
+	{
+		tag = Tag_FGet(&l->tags);
 		switch (l->special)
 		{
 			case 541: // wind
-				for (s = -1; (s = P_FindSectorFromTag(l->tag, s)) >= 0 ;)
+				TAG_ITER_SECTORS(0, tag, s)
 					Add_Pusher(p_wind, l->dx, l->dy, NULL, s, -1, l->flags & ML_NOCLIMB, l->flags & ML_EFFECT4);
 				break;
 			case 544: // current
-				for (s = -1; (s = P_FindSectorFromTag(l->tag, s)) >= 0 ;)
+				TAG_ITER_SECTORS(0, tag, s)
 					Add_Pusher(p_current, l->dx, l->dy, NULL, s, -1, l->flags & ML_NOCLIMB, l->flags & ML_EFFECT4);
 				break;
 			case 547: // push/pull
-				for (s = -1; (s = P_FindSectorFromTag(l->tag, s)) >= 0 ;)
+				TAG_ITER_SECTORS(0, tag, s)
 				{
 					thing = P_GetPushThing(s);
 					if (thing) // No MT_P* means no effect
@@ -9027,20 +8906,21 @@ static void P_SpawnPushers(void)
 				}
 				break;
 			case 545: // current up
-				for (s = -1; (s = P_FindSectorFromTag(l->tag, s)) >= 0 ;)
+				TAG_ITER_SECTORS(0, tag, s)
 					Add_Pusher(p_upcurrent, l->dx, l->dy, NULL, s, -1, l->flags & ML_NOCLIMB, l->flags & ML_EFFECT4);
 				break;
 			case 546: // current down
-				for (s = -1; (s = P_FindSectorFromTag(l->tag, s)) >= 0 ;)
+				TAG_ITER_SECTORS(0, tag, s)
 					Add_Pusher(p_downcurrent, l->dx, l->dy, NULL, s, -1, l->flags & ML_NOCLIMB, l->flags & ML_EFFECT4);
 				break;
 			case 542: // wind up
-				for (s = -1; (s = P_FindSectorFromTag(l->tag, s)) >= 0 ;)
+				TAG_ITER_SECTORS(0, tag, s)
 					Add_Pusher(p_upwind, l->dx, l->dy, NULL, s, -1, l->flags & ML_NOCLIMB, l->flags & ML_EFFECT4);
 				break;
 			case 543: // wind down
-				for (s = -1; (s = P_FindSectorFromTag(l->tag, s)) >= 0 ;)
+				TAG_ITER_SECTORS(0, tag, s)
 					Add_Pusher(p_downwind, l->dx, l->dy, NULL, s, -1, l->flags & ML_NOCLIMB, l->flags & ML_EFFECT4);
 				break;
 		}
+	}
 }
diff --git a/src/p_spec.h b/src/p_spec.h
index 596d8171d2acfb8b8b337f8a4fc4c686f2d3ce9b..bba7c4a40a090084cce1f9738dc414c5a0c1c013 100644
--- a/src/p_spec.h
+++ b/src/p_spec.h
@@ -55,9 +55,6 @@ fixed_t P_FindNextLowestFloor(sector_t *sec, fixed_t currentheight);
 fixed_t P_FindLowestCeilingSurrounding(sector_t *sec);
 fixed_t P_FindHighestCeilingSurrounding(sector_t *sec);
 
-INT32 P_FindSectorFromTag(INT16 tag, INT32 start);
-INT32 P_FindSpecialLineFromTag(INT16 special, INT16 tag, INT32 start);
-
 INT32 P_FindMinSurroundingLight(sector_t *sector, INT32 max);
 
 void P_SetupSignExit(player_t *player);
@@ -67,7 +64,6 @@ void P_SwitchWeather(INT32 weathernum);
 
 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);
 void P_RunNightserizeExecutors(mobj_t *actor);
 void P_RunDeNightserizeExecutors(mobj_t *actor);
 void P_RunNightsLapExecutors(mobj_t *actor);
diff --git a/src/p_user.c b/src/p_user.c
index f9593158f3b269e2dd03c3bd64fec9ec4f8ebcc8..10b7e970e2e86ae571116b465ca62f310baf6215 100644
--- a/src/p_user.c
+++ b/src/p_user.c
@@ -10579,6 +10579,7 @@ static void P_CalcPostImg(player_t *player)
 	postimg_t *type;
 	INT32 *param;
 	fixed_t pviewheight;
+	size_t i;
 
 	if (player->mo->eflags & MFE_VERTICALFLIP)
 		pviewheight = player->mo->z + player->mo->height - player->viewheight;
@@ -10603,28 +10604,45 @@ static void P_CalcPostImg(player_t *player)
 	}
 
 	// see if we are in heat (no, not THAT kind of heat...)
-
-	if (P_FindSpecialLineFromTag(13, sector->tag, -1) != -1)
-		*type = postimg_heat;
-	else if (sector->ffloors)
+	for (i = 0; i < sector->tags.count; i++)
 	{
-		ffloor_t *rover;
-		fixed_t topheight;
-		fixed_t bottomheight;
-
-		for (rover = sector->ffloors; rover; rover = rover->next)
+		if (Tag_FindLineSpecial(13, sector->tags.tags[i]) != -1)
 		{
-			if (!(rover->flags & FF_EXISTS))
-				continue;
+			*type = postimg_heat;
+			break;
+		}
+		else if (sector->ffloors)
+		{
+			ffloor_t *rover;
+			fixed_t topheight;
+			fixed_t bottomheight;
+			boolean gotres = false;
 
-			topheight    = P_GetFFloorTopZAt   (rover, player->mo->x, player->mo->y);
-			bottomheight = P_GetFFloorBottomZAt(rover, player->mo->x, player->mo->y);
+			for (rover = sector->ffloors; rover; rover = rover->next)
+			{
+				size_t j;
 
-			if (pviewheight >= topheight || pviewheight <= bottomheight)
-				continue;
+				if (!(rover->flags & FF_EXISTS))
+					continue;
+
+				topheight    = P_GetFFloorTopZAt   (rover, player->mo->x, player->mo->y);
+				bottomheight = P_GetFFloorBottomZAt(rover, player->mo->x, player->mo->y);
+
+				if (pviewheight >= topheight || pviewheight <= bottomheight)
+					continue;
 
-			if (P_FindSpecialLineFromTag(13, rover->master->frontsector->tag, -1) != -1)
-				*type = postimg_heat;
+				for (j = 0; j < rover->master->frontsector->tags.count; j++)
+				{
+					if (Tag_FindLineSpecial(13, rover->master->frontsector->tags.tags[j]) != -1)
+					{
+						*type = postimg_heat;
+						gotres = true;
+						break;
+					}
+				}
+			}
+			if (gotres)
+				break;
 		}
 	}
 
@@ -10723,22 +10741,21 @@ static sector_t *P_GetMinecartSector(fixed_t x, fixed_t y, fixed_t z, fixed_t *n
 static INT32 P_GetMinecartSpecialLine(sector_t *sec)
 {
 	INT32 line = -1;
+	size_t i;
 
 	if (!sec)
 		return line;
 
-	if (sec->tag != 0)
-		line = P_FindSpecialLineFromTag(16, sec->tag, -1);
+	for (i = 0; i < sec->tags.count; i++)
+		if (sec->tags.tags[i] != 0)
+			line = Tag_FindLineSpecial(16, sec->tags.tags[i]);
 
 	// Also try for lines facing the sector itself, with tag 0.
+	for (i = 0; i < sec->linecount; i++)
 	{
-		UINT32 i;
-		for (i = 0; i < sec->linecount; i++)
-		{
-			line_t *li = sec->lines[i];
-			if (li->tag == 0 && li->special == 16 && li->frontsector == sec)
-				line = li - lines;
-		}
+		line_t *li = sec->lines[i];
+		if (Tag_Find(&li->tags, 0) && li->special == 16 && li->frontsector == sec)
+			line = li - lines;
 	}
 
 	return line;
@@ -12297,12 +12314,14 @@ void P_PlayerThink(player_t *player)
 		sector_t *controlsec;
 		for (j=0; j<numsectors; j++)
 		{
+			mtag_t sectag = Tag_FGet(&sectors[j].tags);
 			controlsec = NULL;
 			// Does this sector have a water linedef?
 			for (i=0; i<numlines;i++)
 			{
+				mtag_t linetag = Tag_FGet(&lines[i].tags);
 				if ((lines[i].special == 121 || lines[i].special == 123)
-				&& lines[i].tag == sectors[j].tag)
+				&& linetag == sectag)
 				{
 					controlsec = lines[i].frontsector;
 					break;
@@ -12311,15 +12330,16 @@ void P_PlayerThink(player_t *player)
 
 			if (i < numlines && controlsec)
 			{
+				controlsectag = Tag_FGet(&controlsec->tags);
 				// Does this sector have a colormap?
 				for (i=0; i<numlines;i++)
 				{
-					if (lines[i].special == 606 && lines[i].tag == controlsec->tag)
+					if (lines[i].special == 606 && linetag == controlsectag)
 						break;
 				}
 
 				if (i == numlines)
-					CONS_Debug(DBG_GAMELOGIC, "%d, %d\n", j, sectors[j].tag);
+					CONS_Debug(DBG_GAMELOGIC, "%d, %d\n", j, sectag);
 			}
 		}
 
diff --git a/src/r_bsp.c b/src/r_bsp.c
index 56d038b44183a49ee7a5571251fce4af8fe888c8..2a3c662aa3f6e1db8bc6aeab332d488b9cfb93db 100644
--- a/src/r_bsp.c
+++ b/src/r_bsp.c
@@ -21,6 +21,7 @@
 #include "p_local.h" // camera
 #include "p_slopes.h"
 #include "z_zone.h" // Check R_Prep3DFloors
+#include "taglist.h"
 
 seg_t *curline;
 side_t *sidedef;
@@ -374,7 +375,7 @@ boolean R_IsEmptyLine(seg_t *line, sector_t *front, sector_t *back)
 		// Consider colormaps
 		&& back->extra_colormap == front->extra_colormap
 		&& ((!front->ffloors && !back->ffloors)
-		|| front->tag == back->tag));
+		|| Tag_Compare(&front->tags, &back->tags)));
 }
 
 //
@@ -448,21 +449,25 @@ static void R_AddLine(seg_t *line)
 	// Portal line
 	if (line->linedef->special == 40 && line->side == 0)
 	{
+		// Render portal if recursiveness limit hasn't been reached.
+		// Otherwise, render the wall normally.
 		if (portalrender < cv_maxportals.value)
 		{
-			// Find the other side!
-			INT32 line2 = P_FindSpecialLineFromTag(40, line->linedef->tag, -1);
-			if (line->linedef == &lines[line2])
-				line2 = P_FindSpecialLineFromTag(40, line->linedef->tag, line2);
-			if (line2 >= 0) // found it!
+			size_t p;
+			mtag_t tag = Tag_FGet(&line->linedef->tags);
+			INT32 li1 = line->linedef-lines;
+			INT32 li2;
+
+			for (p = 0; (li2 = Tag_Iterate_Lines(tag, p)) >= 0; p++)
 			{
-				Portal_Add2Lines(line->linedef-lines, line2, x1, x2); // Remember the lines for later rendering
-				//return; // Don't fill in that space now!
+				// Skip invalid lines.
+				if ((tag != Tag_FGet(&lines[li2].tags)) || (lines[li1].special != lines[li2].special) || (li1 == li2))
+					continue;
+
+				Portal_Add2Lines(li1, li2, x1, x2);
 				goto clipsolid;
 			}
 		}
-		// Recursed TOO FAR (viewing a portal within a portal)
-		// So uhhh, render it as a normal wall instead or something ???
 	}
 
 	// Single sided line?
@@ -483,7 +488,7 @@ static void R_AddLine(seg_t *line)
 		if (!line->polyseg &&
 			!line->sidedef->midtexture
 			&& ((!frontsector->ffloors && !backsector->ffloors)
-				|| (frontsector->tag == backsector->tag)))
+				|| Tag_Compare(&frontsector->tags, &backsector->tags)))
 			return; // line is empty, don't even bother
 
 		goto clippass; // treat like wide open window instead
diff --git a/src/r_defs.h b/src/r_defs.h
index 132106bc88c0dd0b28d93fb0cf24e51947bf804d..026edeb0a5e65ef41ba7be74fa303cfb3591f042 100644
--- a/src/r_defs.h
+++ b/src/r_defs.h
@@ -28,6 +28,8 @@
 #include "m_aatree.h"
 #endif
 
+#include "taglist.h"
+
 //
 // ClipWallSegment
 // Clips the given range of columns
@@ -281,8 +283,7 @@ typedef struct sector_s
 	INT32 ceilingpic;
 	INT16 lightlevel;
 	INT16 special;
-	UINT16 tag;
-	INT32 nexttag, firsttag; // for fast tag searches
+	taglist_t tags;
 
 	// origin for any sounds played by the sector
 	// also considered the center for e.g. Mario blocks
@@ -389,7 +390,7 @@ typedef struct line_s
 	// Animation related.
 	INT16 flags;
 	INT16 special;
-	INT16 tag;
+	taglist_t tags;
 	INT32 args[NUMLINEARGS];
 	char *stringargs[NUMLINESTRINGARGS];
 
@@ -412,7 +413,6 @@ typedef struct line_s
 #if 1//#ifdef WALLSPLATS
 	void *splats; // wallsplat_t list
 #endif
-	INT32 firsttag, nexttag; // improves searches for tags.
 	polyobj_t *polyobj; // Belongs to a polyobject?
 
 	char *text; // a concatenation of all front and back texture names, for linedef specials that require a string.
diff --git a/src/r_segs.c b/src/r_segs.c
index 2cd7ebab2b5ea3018f29dbda41c6e6bc7d2c8ac2..aa04777c6c4f06bb63021e947380d5e8c7711a3d 100644
--- a/src/r_segs.c
+++ b/src/r_segs.c
@@ -25,6 +25,7 @@
 #include "p_local.h" // Camera...
 #include "p_slopes.h"
 #include "console.h" // con_clipviewtop
+#include "taglist.h"
 
 // OPTIMIZE: closed two sided lines as single sided
 
@@ -2067,7 +2068,7 @@ void R_StoreWallRange(INT32 start, INT32 stop)
 		    || backsector->floorlightsec != frontsector->floorlightsec
 		    //SoM: 4/3/2000: Check for colormaps
 		    || frontsector->extra_colormap != backsector->extra_colormap
-		    || (frontsector->ffloors != backsector->ffloors && frontsector->tag != backsector->tag))
+		    || (frontsector->ffloors != backsector->ffloors && !Tag_Compare(&frontsector->tags, &backsector->tags)))
 		{
 			markfloor = true;
 		}
@@ -2098,7 +2099,7 @@ void R_StoreWallRange(INT32 start, INT32 stop)
 		    || backsector->ceilinglightsec != frontsector->ceilinglightsec
 		    //SoM: 4/3/2000: Check for colormaps
 		    || frontsector->extra_colormap != backsector->extra_colormap
-		    || (frontsector->ffloors != backsector->ffloors && frontsector->tag != backsector->tag))
+		    || (frontsector->ffloors != backsector->ffloors && !Tag_Compare(&frontsector->tags, &backsector->tags)))
 		{
 				markceiling = true;
 		}
@@ -2188,7 +2189,7 @@ void R_StoreWallRange(INT32 start, INT32 stop)
 		rw_bottomtexturemid += sidedef->rowoffset;
 
 		// allocate space for masked texture tables
-		if (frontsector && backsector && frontsector->tag != backsector->tag && (backsector->ffloors || frontsector->ffloors))
+		if (frontsector && backsector && !Tag_Compare(&frontsector->tags, &backsector->tags) && (backsector->ffloors || frontsector->ffloors))
 		{
 			ffloor_t *rover;
 			ffloor_t *r2;
@@ -2230,6 +2231,9 @@ void R_StoreWallRange(INT32 start, INT32 stop)
 
 					for (r2 = frontsector->ffloors; r2; r2 = r2->next)
 					{
+						if (r2->master == rover->master) // Skip if same control line.
+							break;
+
 						if (!(r2->flags & FF_EXISTS) || !(r2->flags & FF_RENDERSIDES))
 							continue;
 
@@ -2285,6 +2289,9 @@ void R_StoreWallRange(INT32 start, INT32 stop)
 
 					for (r2 = backsector->ffloors; r2; r2 = r2->next)
 					{
+						if (r2->master == rover->master) // Skip if same control line.
+							break;
+
 						if (!(r2->flags & FF_EXISTS) || !(r2->flags & FF_RENDERSIDES))
 							continue;
 
diff --git a/src/taglist.c b/src/taglist.c
new file mode 100644
index 0000000000000000000000000000000000000000..b11216b6cf7b3e7c709b73750c2d46e8e9155c40
--- /dev/null
+++ b/src/taglist.c
@@ -0,0 +1,366 @@
+// SONIC ROBO BLAST 2
+//-----------------------------------------------------------------------------
+// Copyright (C) 1998-2000 by DooM Legacy Team.
+// Copyright (C) 1999-2020 by Sonic Team Junior.
+// Copyright (C)      2020 by Nev3r.
+//
+// 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  taglist.c
+/// \brief Ingame sector/line/mapthing tagging.
+
+#include "taglist.h"
+#include "z_zone.h"
+#include "r_data.h"
+
+// Taggroups are used to list elements of the same tag, for iteration.
+// Since elements can now have multiple tags, it means an element may appear
+// in several taggroups at the same time. These are built on level load.
+taggroup_t* tags_sectors[MAXTAGS + 1];
+taggroup_t* tags_lines[MAXTAGS + 1];
+taggroup_t* tags_mapthings[MAXTAGS + 1];
+
+/// Adds a tag to a given element's taglist.
+/// \warning This does not rebuild the global taggroups, which are used for iteration.
+void Tag_Add (taglist_t* list, const mtag_t tag)
+{
+	list->tags = Z_Realloc(list->tags, (list->count + 1) * sizeof(list->tags), PU_LEVEL, NULL);
+	list->tags[list->count++] = tag;
+}
+
+/// Sets the first tag entry in a taglist.
+/// Replicates the old way of accessing element->tag.
+void Tag_FSet (taglist_t* list, const mtag_t tag)
+{
+	if (!list->count)
+	{
+		Tag_Add(list, tag);
+		return;
+	}
+
+	list->tags[0] = tag;
+}
+
+/// Gets the first tag entry in a taglist.
+/// Replicates the old way of accessing element->tag.
+mtag_t Tag_FGet (const taglist_t* list)
+{
+	if (list->count)
+		return list->tags[0];
+
+	return 0;
+}
+
+/// Returns true if the given tag exist inside the list.
+boolean Tag_Find (const taglist_t* list, const mtag_t tag)
+{
+	size_t i;
+	for (i = 0; i < list->count; i++)
+		if (list->tags[i] == tag)
+			return true;
+
+	return false;
+}
+
+/// Returns true if at least one tag is shared between two given lists.
+boolean Tag_Share (const taglist_t* list1, const taglist_t* list2)
+{
+	size_t i;
+	for (i = 0; i < list1->count; i++)
+		if (Tag_Find(list2, list1->tags[i]))
+			return true;
+
+	return false;
+}
+
+/// Returns true if both lists are identical.
+boolean Tag_Compare (const taglist_t* list1, const taglist_t* list2)
+{
+	size_t i;
+
+	if (list1->count != list2->count)
+		return false;
+
+	for (i = 0; i < list1->count; i++)
+		if (list1->tags[i] != list2->tags[i])
+			return false;
+
+	return true;
+}
+
+/// Search for an element inside a global taggroup.
+size_t Taggroup_Find (const taggroup_t *group, const size_t id)
+{
+	size_t i;
+
+	if (!group)
+		return -1;
+
+	for (i = 0; i < group->count; i++)
+		if (group->elements[i] == id)
+			return i;
+
+	return -1;
+}
+
+/// Add an element to a global taggroup.
+void Taggroup_Add (taggroup_t *garray[], const mtag_t tag, size_t id)
+{
+	taggroup_t *group;
+	size_t i; // Insert position.
+
+	if (tag == MTAG_GLOBAL)
+		return;
+
+	group = garray[(UINT16)tag];
+
+	// Don't add duplicate entries.
+	if (Taggroup_Find(group, id) != (size_t)-1)
+		return;
+
+	// Create group if empty.
+	if (!group)
+	{
+		i = 0;
+		group = garray[(UINT16)tag] = Z_Calloc(sizeof(taggroup_t), PU_LEVEL, NULL);
+	}
+	else
+	{
+		// Keep the group element ids in an ascending order.
+		// Find the location to insert the element to.
+		for (i = 0; i < group->count; i++)
+			if (group->elements[i] > id)
+				break;
+
+		group->elements = Z_Realloc(group->elements, (group->count + 1) * sizeof(size_t), PU_LEVEL, NULL);
+
+		// Offset existing elements to make room for the new one.
+		if (i < group->count)
+			memmove(&group->elements[i + 1], &group->elements[i], group->count - i);
+	}
+
+	group->count++;
+	group->elements = Z_Realloc(group->elements, group->count * sizeof(size_t), PU_LEVEL, NULL);
+	group->elements[i] = id;
+}
+
+/// Remove an element from a global taggroup.
+void Taggroup_Remove (taggroup_t *garray[], const mtag_t tag, size_t id)
+{
+	taggroup_t *group;
+	size_t rempos;
+	size_t newcount;
+
+	if (tag == MTAG_GLOBAL)
+		return;
+
+	group = garray[(UINT16)tag];
+
+	if ((rempos = Taggroup_Find(group, id)) == (size_t)-1)
+		return;
+
+	// Strip away taggroup if no elements left.
+	if (!(newcount = --group->count))
+	{
+		Z_Free(group->elements);
+		Z_Free(group);
+		garray[(UINT16)tag] = NULL;
+	}
+	else
+	{
+		size_t *newelements = Z_Malloc(newcount * sizeof(size_t), PU_LEVEL, NULL);
+		size_t i;
+
+		// Copy the previous entries save for the one to remove.
+		for (i = 0; i < rempos; i++)
+			newelements[i] = group->elements[i];
+
+		for (i = rempos + 1; i < group->count; i++)
+			newelements[i - 1] = group->elements[i];
+
+		Z_Free(group->elements);
+		group->elements = newelements;
+		group->count = newcount;
+	}
+}
+
+// Initialization.
+
+static void Taglist_AddToSectors (const mtag_t tag, const size_t itemid)
+{
+	Taggroup_Add(tags_sectors, tag, itemid);
+}
+
+static void Taglist_AddToLines (const mtag_t tag, const size_t itemid)
+{
+	Taggroup_Add(tags_lines, tag, itemid);
+}
+
+static void Taglist_AddToMapthings (const mtag_t tag, const size_t itemid)
+{
+	Taggroup_Add(tags_mapthings, tag, itemid);
+}
+
+/// After all taglists have been built for each element (sectors, lines, things),
+/// the global taggroups, made for iteration, are built here.
+void Taglist_InitGlobalTables(void)
+{
+	size_t i, j;
+
+	for (i = 0; i < MAXTAGS; i++)
+	{
+		tags_sectors[i] = NULL;
+		tags_lines[i] = NULL;
+		tags_mapthings[i] = NULL;
+	}
+	for (i = 0; i < numsectors; i++)
+	{
+		for (j = 0; j < sectors[i].tags.count; j++)
+			Taglist_AddToSectors(sectors[i].tags.tags[j], i);
+	}
+	for (i = 0; i < numlines; i++)
+	{
+		for (j = 0; j < lines[i].tags.count; j++)
+			Taglist_AddToLines(lines[i].tags.tags[j], i);
+	}
+	for (i = 0; i < nummapthings; i++)
+	{
+		for (j = 0; j < mapthings[i].tags.count; j++)
+			Taglist_AddToMapthings(mapthings[i].tags.tags[j], i);
+	}
+}
+
+// Iteration, ingame search.
+
+INT32 Tag_Iterate_Sectors (const mtag_t tag, const size_t p)
+{
+	if (tag == MTAG_GLOBAL)
+	{
+		if (p < numsectors)
+			return p;
+		return -1;
+	}
+
+	if (tags_sectors[(UINT16)tag])
+	{
+		if (p < tags_sectors[(UINT16)tag]->count)
+			return tags_sectors[(UINT16)tag]->elements[p];
+		return -1;
+	}
+	return -1;
+}
+
+INT32 Tag_Iterate_Lines (const mtag_t tag, const size_t p)
+{
+	if (tag == MTAG_GLOBAL)
+	{
+		if (p < numlines)
+			return p;
+		return -1;
+	}
+
+	if (tags_lines[(UINT16)tag])
+	{
+		if (p < tags_lines[(UINT16)tag]->count)
+			return tags_lines[(UINT16)tag]->elements[p];
+		return -1;
+	}
+	return -1;
+}
+
+INT32 Tag_Iterate_Things (const mtag_t tag, const size_t p)
+{
+	if (tag == MTAG_GLOBAL)
+	{
+		if (p < nummapthings)
+			return p;
+		return -1;
+	}
+
+	if (tags_mapthings[(UINT16)tag])
+	{
+		if (p < tags_mapthings[(UINT16)tag]->count)
+			return tags_mapthings[(UINT16)tag]->elements[p];
+		return -1;
+	}
+	return -1;
+}
+
+INT32 Tag_FindLineSpecial(const INT16 special, const mtag_t tag)
+{
+	size_t i;
+
+	if (tag == MTAG_GLOBAL)
+	{
+		for (i = 0; i < numlines; i++)
+			if (lines[i].special == special)
+				return i;
+	}
+	else if (tags_lines[(UINT16)tag])
+	{
+		taggroup_t *tagged = tags_lines[(UINT16)tag];
+		for (i = 0; i < tagged->count; i++)
+			if (lines[tagged->elements[i]].special == special)
+				return tagged->elements[i];
+	}
+	return -1;
+}
+
+/// Backwards compatibility iteration function for Lua scripts.
+INT32 P_FindSpecialLineFromTag(INT16 special, INT16 tag, INT32 start)
+{
+	if (tag == -1)
+	{
+		start++;
+
+		if (start >= (INT32)numlines)
+			return -1;
+
+		while (start < (INT32)numlines && lines[start].special != special)
+			start++;
+
+		return start;
+	}
+	else
+	{
+		size_t p = 0;
+		INT32 id;
+
+		// For backwards compatibility's sake, simulate the old linked taglist behavior:
+		// Iterate through the taglist and find the "start" line's position in the list,
+		// And start checking with the next one (if it exists).
+		if (start != -1)
+		{
+			for (; (id = Tag_Iterate_Lines(tag, p)) >= 0; p++)
+				if (id == start)
+				{
+					p++;
+					break;
+				}
+		}
+
+		for (; (id = Tag_Iterate_Lines(tag, p)) >= 0; p++)
+			if (lines[id].special == special)
+				return id;
+
+		return -1;
+	}
+}
+
+
+// Ingame list manipulation.
+
+/// Changes the first tag for a given sector, and updates the global taggroups.
+void Tag_SectorFSet (const size_t id, const mtag_t tag)
+{
+	sector_t* sec = &sectors[id];
+	mtag_t curtag = Tag_FGet(&sec->tags);
+	if (curtag == tag)
+		return;
+
+	Taggroup_Remove(tags_sectors, curtag, id);
+	Taggroup_Add(tags_sectors, tag, id);
+	Tag_FSet(&sec->tags, tag);
+}
diff --git a/src/taglist.h b/src/taglist.h
new file mode 100644
index 0000000000000000000000000000000000000000..0e6d9f8422bbc150dbbabe3c6048d6e66c448f05
--- /dev/null
+++ b/src/taglist.h
@@ -0,0 +1,127 @@
+// SONIC ROBO BLAST 2
+//-----------------------------------------------------------------------------
+// Copyright (C) 1998-2000 by DooM Legacy Team.
+// Copyright (C) 1999-2020 by Sonic Team Junior.
+// Copyright (C)      2020 by Nev3r.
+//
+// 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  taglist.h
+/// \brief Tag iteration and reading functions and macros' declarations.
+
+#ifndef __R_TAGLIST__
+#define __R_TAGLIST__
+
+#include "doomtype.h"
+
+typedef INT16 mtag_t;
+#define MAXTAGS UINT16_MAX
+#define MTAG_GLOBAL -1
+
+/// Multitag list. Each taggable element will have its own taglist.
+typedef struct
+{
+	mtag_t* tags;
+	UINT16 count;
+} taglist_t;
+
+void Tag_Add (taglist_t* list, const mtag_t tag);
+void Tag_FSet (taglist_t* list, const mtag_t tag);
+mtag_t Tag_FGet (const taglist_t* list);
+boolean Tag_Find (const taglist_t* list, const mtag_t tag);
+boolean Tag_Share (const taglist_t* list1, const taglist_t* list2);
+boolean Tag_Compare (const taglist_t* list1, const taglist_t* list2);
+
+void Tag_SectorFSet (const size_t id, const mtag_t tag);
+
+/// Taggroup list. It is essentially just an element id list.
+typedef struct
+{
+	size_t *elements;
+	size_t count;
+} taggroup_t;
+
+extern taggroup_t* tags_sectors[];
+extern taggroup_t* tags_lines[];
+extern taggroup_t* tags_mapthings[];
+
+void Taggroup_Add (taggroup_t *garray[], const mtag_t tag, size_t id);
+void Taggroup_Remove (taggroup_t *garray[], const mtag_t tag, size_t id);
+size_t Taggroup_Find (const taggroup_t *group, const size_t id);
+
+void Taglist_InitGlobalTables(void);
+
+INT32 Tag_Iterate_Sectors (const mtag_t tag, const size_t p);
+INT32 Tag_Iterate_Lines (const mtag_t tag, const size_t p);
+INT32 Tag_Iterate_Things (const mtag_t tag, const size_t p);
+
+INT32 Tag_FindLineSpecial(const INT16 special, const mtag_t tag);
+INT32 P_FindSpecialLineFromTag(INT16 special, INT16 tag, INT32 start);
+
+// Use this macro to declare an iterator position variable.
+#define TAG_ITER_DECLARECOUNTER(level) size_t ICNT_##level
+
+#define TAG_ITER(level, fn, tag, return_varname) for(ICNT_##level = 0; (return_varname = fn(tag, ICNT_##level)) >= 0; ICNT_##level++)
+
+// Use these macros as wrappers for a taglist iteration.
+#define TAG_ITER_SECTORS(level, tag, return_varname) TAG_ITER(level, Tag_Iterate_Sectors, tag, return_varname)
+#define TAG_ITER_LINES(level, tag, return_varname)   TAG_ITER(level, Tag_Iterate_Lines, tag, return_varname)
+#define TAG_ITER_THINGS(level, tag, return_varname)  TAG_ITER(level, Tag_Iterate_Things, tag, return_varname)
+
+/* ITERATION MACROS
+TAG_ITER_DECLARECOUNTER must be used before using the iterators.
+
+'level':
+For each nested iteration, an additional TAG_ITER_DECLARECOUNTER
+must be used with a different level number to avoid conflict with
+the outer iterations.
+Most cases don't have nested iterations and thus the level is just 0.
+
+'tag':
+Pretty much the elements' tag to iterate through.
+
+'return_varname':
+Target variable's name to return the iteration results to.
+
+
+EXAMPLE:
+{
+	TAG_ITER_DECLARECOUNTER(0);
+	TAG_ITER_DECLARECOUNTER(1); // For the nested iteration.
+
+	size_t li;
+	size_t sec;
+
+	INT32 tag1 = 4;
+
+	...
+
+	TAG_ITER_LINES(0, tag1, li)
+	{
+		line_t *line = lines + li;
+
+		...
+
+		if (something)
+		{
+			mtag_t tag2 = 8;
+
+			// Nested iteration; just make sure the level is higher
+			// and that it has its own counter declared in scope.
+			TAG_ITER_SECTORS(1, tag2, sec)
+			{
+				sector_t *sector = sectors + sec;
+
+				...
+			}
+		}
+	}
+}
+
+Notes:
+If no elements are found for a given tag, the loop inside won't be executed.
+*/
+
+#endif //__R_TAGLIST__