diff --git a/src/doomstat.h b/src/doomstat.h
index 95cb9f6a855da3b56263b824cbc5d3099a643729..f0bf5d6a9d903834018f30b89e6d117d7bbd39fa 100644
--- a/src/doomstat.h
+++ b/src/doomstat.h
@@ -634,14 +634,33 @@ extern tic_t hidetime;
 //  WAD, partly set at startup time.
 
 extern tic_t gametic;
+
 #define localgametic leveltime
 
+enum
+{
+	PLAYER_START_TYPE_COOP,
+	PLAYER_START_TYPE_MATCH,
+	PLAYER_START_TYPE_TEAM
+};
+
+typedef struct
+{
+	size_t count;
+	size_t capacity;
+	mapthing_t **list;
+} playerstarts_t;
+
+#define MAX_DM_STARTS 64
+
 // Player spawn spots.
-extern mapthing_t *playerstarts[MAXPLAYERS]; // Cooperative
-extern mapthing_t *teamstarts[MAXTEAMS][MAXPLAYERS]; // CTF
+extern playerstarts_t playerstarts;
+extern playerstarts_t deathmatchstarts;
+extern playerstarts_t teamstarts[MAXTEAMS];
 
 #define WAYPOINTSEQUENCESIZE 256
 #define NUMWAYPOINTSEQUENCES 256
+
 extern mobj_t *waypoints[NUMWAYPOINTSEQUENCES][WAYPOINTSEQUENCESIZE];
 extern UINT16 numwaypoints[NUMWAYPOINTSEQUENCES];
 
diff --git a/src/f_finale.c b/src/f_finale.c
index d03795dc437c69af5f12177755f3e48af643fcad..2d93bc5bd9955cba9e26ed5b98f7d0acefbc9192 100644
--- a/src/f_finale.c
+++ b/src/f_finale.c
@@ -2441,13 +2441,8 @@ void F_StartTitleScreen(void)
 
 		players[displayplayer].playerstate = PST_DEAD; // Don't spawn the player in dummy (I'm still a filthy cheater)
 
-		// Set Default Position
-		if (playerstarts[0])
-			startpos = playerstarts[0];
-		else if (deathmatchstarts[0])
-			startpos = deathmatchstarts[0];
-		else
-			startpos = NULL;
+		// Set initial camera position
+		startpos = G_GetInitialSpawnPoint();
 
 		if (startpos)
 		{
diff --git a/src/g_demo.c b/src/g_demo.c
index 4b9ff56e80f5711faabd18b53a9474f2c0a60c5e..a6597051d5181bf087fab107fc2fa6c23d6c7cad 100644
--- a/src/g_demo.c
+++ b/src/g_demo.c
@@ -2564,7 +2564,7 @@ void G_AddGhost(char *defdemoname)
 	ghosts = gh;
 
 	gh->version = ghostversion;
-	mthing = playerstarts[0];
+	mthing = G_GetPlayerStart(0);
 	I_Assert(mthing);
 	{ // A bit more complex than P_SpawnPlayer because ghosts aren't solid and won't just push themselves out of the ceiling.
 		fixed_t z,f,c;
diff --git a/src/g_game.c b/src/g_game.c
index 0764f566904f96f1dc191d45632cf4225de27375..f950a6b198979fce8934a5af50853a6b98b52aa3 100644
--- a/src/g_game.c
+++ b/src/g_game.c
@@ -153,10 +153,13 @@ INT16 nextmapoverride;
 UINT8 skipstats;
 INT16 nextgametype = -1;
 
-// Pointers to each CTF flag
-mobj_t *flagmobjs[MAXTEAMS];
-// Pointers to CTF spawn location
-mapthing_t *flagpoints[MAXTEAMS];
+// Maintain single and multi player starting spots.
+playerstarts_t playerstarts;
+playerstarts_t deathmatchstarts;
+playerstarts_t teamstarts[MAXTEAMS];
+
+mobj_t *flagmobjs[MAXTEAMS]; // Pointers to each CTF flag
+mapthing_t *flagpoints[MAXTEAMS]; // Pointers to CTF flag spawn locations
 
 struct quake quake;
 
@@ -2849,29 +2852,134 @@ void G_MovePlayerToSpawnOrStarpost(INT32 playernum)
 		P_ResetCamera(&players[playernum], &camera2);
 }
 
-enum
-{
-	PLAYER_START_TYPE_COOP,
-	PLAYER_START_TYPE_MATCH,
-	PLAYER_START_TYPE_TEAM
-};
-
 static boolean G_AreCoopStartsAvailable(void)
 {
-	return numcoopstarts != 0;
+	return playerstarts.count != 0;
 }
 
 static boolean G_AreMatchStartsAvailable(void)
 {
-	return numdmstarts != 0;
+	return deathmatchstarts.count != 0;
 }
 
 static boolean G_AreTeamStartsAvailable(UINT8 team)
 {
-	if (team >= numteams)
+	if (team == TEAM_NONE || team >= numteams)
 		return false;
 
-	return numteamstarts[team] != 0;
+	return teamstarts[team].count != 0;
+}
+
+mapthing_t *G_GetPlayerStart(INT32 i)
+{
+	if (i < 0 || i >= (signed)playerstarts.count || playerstarts.list == NULL)
+		return NULL;
+
+	return playerstarts.list[i];
+}
+
+mapthing_t *G_GetMatchPlayerStart(INT32 i)
+{
+	if (i < 0 || i >= (signed)deathmatchstarts.count || deathmatchstarts.list == NULL)
+		return NULL;
+
+	return deathmatchstarts.list[i];
+}
+
+mapthing_t *G_GetTeamPlayerStart(UINT8 team, INT32 i)
+{
+	if (team == TEAM_NONE || team >= numteams)
+		return NULL;
+
+	if (i < 0 || i >= (signed)teamstarts[team].count || teamstarts[team].list == NULL)
+		return NULL;
+
+	return teamstarts[team].list[i];
+}
+
+mapthing_t *G_GetInitialSpawnPoint(void)
+{
+	if (gametyperules & GTR_DEATHMATCHSTARTS)
+		return G_GetMatchPlayerStart(0);
+	else
+		return G_GetPlayerStart(0);
+}
+
+void G_InitSpawnPointList(playerstarts_t *starts, size_t capacity)
+{
+	Z_Free(starts->list);
+	starts->list = NULL;
+	starts->capacity = capacity;
+	starts->count = 0;
+}
+
+void G_AddSpawnPointToList(playerstarts_t *starts, mapthing_t *mthing)
+{
+	if (starts->list == NULL || starts->count == starts->capacity)
+	{
+		if (starts->capacity == 0 || starts->list != NULL)
+			starts->capacity += 16;
+
+		if (starts->list != NULL)
+			starts->list = Z_Realloc(starts->list, sizeof(mapthing_t *) * starts->capacity, PU_STATIC, NULL);
+		else
+			starts->list = Z_Calloc(sizeof(mapthing_t *) * starts->capacity, PU_STATIC, NULL);
+	}
+
+	starts->list[starts->count++] = mthing;
+}
+
+void G_InitSpawnPoints(void)
+{
+	G_InitSpawnPointList(&playerstarts, MAXPLAYERS);
+	G_InitSpawnPointList(&deathmatchstarts, MAX_DM_STARTS);
+
+	for (UINT8 i = 1; i < MAXTEAMS; i++)
+		G_InitSpawnPointList(&teamstarts[i], MAXPLAYERS);
+}
+
+void G_AddPlayerStart(int index, mapthing_t *mthing)
+{
+	if (playerstarts.list == NULL)
+	{
+		if (playerstarts.capacity == 0)
+			playerstarts.capacity = MAXPLAYERS;
+
+		playerstarts.list = Z_Calloc(sizeof(mapthing_t *) * playerstarts.capacity, PU_STATIC, NULL);
+	}
+
+	if (index >= 0 && index < (signed)playerstarts.capacity)
+		playerstarts.list[index] = mthing;
+}
+
+void G_AddMatchPlayerStart(mapthing_t *mthing)
+{
+	G_AddSpawnPointToList(&deathmatchstarts, mthing);
+}
+
+void G_AddTeamPlayerStart(UINT8 team, mapthing_t *mthing)
+{
+	if (team == TEAM_NONE || team >= numteams)
+		return;
+
+	G_AddSpawnPointToList(&teamstarts[team], mthing);
+}
+
+void G_CountPlayerStarts(void)
+{
+	playerstarts.count = 0;
+
+	if (playerstarts.list == NULL)
+		return;
+
+	for (; playerstarts.count < playerstarts.capacity; playerstarts.count++)
+		if (!playerstarts.list[playerstarts.count])
+			break;
+}
+
+boolean G_IsSpawnPointThingType(UINT16 mthingtype)
+{
+	return mthingtype >= 1 && mthingtype <= 35;
 }
 
 static boolean G_AreTeamStartsAvailableForPlayer(INT32 playernum)
@@ -2885,13 +2993,14 @@ static boolean G_AreTeamStartsAvailableForPlayer(INT32 playernum)
 mapthing_t *G_FindTeamStart(INT32 playernum)
 {
 	UINT8 team = players[playernum].ctfteam;
+
 	if (team != TEAM_NONE && G_AreTeamStartsAvailable(team))
 	{
 		for (INT32 j = 0; j < MAXPLAYERS; j++)
 		{
-			INT32 i = P_RandomKey(numteamstarts[team]);
-			if (G_CheckSpot(playernum, teamstarts[team][i]))
-				return teamstarts[team][i];
+			INT32 i = P_RandomKey(teamstarts[team].count);
+			if (G_CheckSpot(playernum, teamstarts[team].list[i]))
+				return teamstarts[team].list[i];
 		}
 	}
 
@@ -2900,15 +3009,13 @@ mapthing_t *G_FindTeamStart(INT32 playernum)
 
 mapthing_t *G_FindMatchStart(INT32 playernum)
 {
-	INT32 i, j;
-
 	if (G_AreMatchStartsAvailable())
 	{
-		for (j = 0; j < 64; j++)
+		for (INT32 j = 0; j < 64; j++)
 		{
-			i = P_RandomKey(numdmstarts);
-			if (G_CheckSpot(playernum, deathmatchstarts[i]))
-				return deathmatchstarts[i];
+			INT32 i = P_RandomKey(deathmatchstarts.count);
+			if (G_CheckSpot(playernum, deathmatchstarts.list[i]))
+				return deathmatchstarts.list[i];
 		}
 	}
 
@@ -2919,13 +3026,14 @@ mapthing_t *G_FindCoopStart(INT32 playernum)
 {
 	if (G_AreCoopStartsAvailable())
 	{
-		//if there's 6 players in a map with 3 player starts, this spawns them 1/2/3/1/2/3.
-		if (G_CheckSpot(playernum, playerstarts[playernum % numcoopstarts]))
-			return playerstarts[playernum % numcoopstarts];
+		// if there's 6 players in a map with 3 player starts, this spawns them 1/2/3/1/2/3.
+		mapthing_t *spawnpoint = G_GetPlayerStart(playernum % playerstarts.count);
+		if (G_CheckSpot(playernum, spawnpoint))
+			return spawnpoint;
 
-		//Don't bother checking to see if the player 1 start is open.
-		//Just spawn there.
-		return playerstarts[0];
+		// Don't bother checking to see if the player 1 start is open.
+		// Just spawn there.
+		return G_GetPlayerStart(0);
 	}
 
 	return NULL;
diff --git a/src/g_game.h b/src/g_game.h
index 3cba4901c84a36c7049a848e5bfe8945cc737286..bb427d341d31ebebf797f4a49418708bdd9eb8b9 100644
--- a/src/g_game.h
+++ b/src/g_game.h
@@ -164,6 +164,21 @@ void G_FreeMapSearch(mapsearchfreq_t *freq, INT32 freqc);
 /* Match map name by search + 2 digit map code or map number. */
 INT32 G_FindMapByNameOrCode(const char *query, char **foundmapnamep);
 
+void G_InitSpawnPointList(playerstarts_t *starts, size_t capacity);
+void G_AddSpawnPointToList(playerstarts_t *starts, mapthing_t *mthing);
+boolean G_IsSpawnPointThingType(UINT16 mthingtype);
+mapthing_t *G_GetInitialSpawnPoint(void);
+
+mapthing_t *G_GetPlayerStart(INT32 i);
+mapthing_t *G_GetMatchPlayerStart(INT32 i);
+mapthing_t *G_GetTeamPlayerStart(UINT8 team, INT32 i);
+
+void G_InitSpawnPoints(void);
+void G_AddPlayerStart(int index, mapthing_t *mthing);
+void G_AddMatchPlayerStart(mapthing_t *mthing);
+void G_AddTeamPlayerStart(UINT8 team, mapthing_t *mthing);
+void G_CountPlayerStarts(void);
+
 mapthing_t *G_FindMapStart(INT32 playernum);
 mapthing_t *G_FindBestPlayerStart(INT32 playernum);
 mapthing_t *G_FindCoopStart(INT32 playernum);
diff --git a/src/lua_baselib.c b/src/lua_baselib.c
index ef440beeabcecf67ff8dbae06d718c335b2da338..18429a2b44ca35019c1f7dbe51cf11aa69872d33 100644
--- a/src/lua_baselib.c
+++ b/src/lua_baselib.c
@@ -158,6 +158,8 @@ static const struct {
 	{META_GAMETYPE,     "gametype_t"},
 	{META_TEAM,         "team_t"},
 	{META_TEAMLIST,     "teamlist_t"},
+	{META_TEAMSCORES,   "teamscores"},
+	{META_PLAYERSTARTS, "playerstarts"},
 	{META_SPRITEINFO,   "spriteinfo_t"},
 	{META_PIVOTLIST,    "spriteframepivot_t[]"},
 	{META_FRAMEPIVOT,   "spriteframepivot_t"},
diff --git a/src/lua_infolib.c b/src/lua_infolib.c
index 22f9a2218c9298740b6f8b8f8bcc806c4a5a0065..a1408168899f1a7968eccbb0572be83a90a147b8 100644
--- a/src/lua_infolib.c
+++ b/src/lua_infolib.c
@@ -1910,7 +1910,7 @@ static int lib_getGametypes(lua_State *L)
 
 	i = luaL_checkinteger(L, 1);
 	if (i < 0 || i >= gametypecount)
-		return luaL_error(L, "gametypes[] index %d out of range (0 - %d)", i, gametypecount-1);
+		return luaL_error(L, "gametypes[] index %d out of range (0 - %d)", i, gametypecount - 1);
 	LUA_PushUserdata(L, &gametypes[i], META_GAMETYPE);
 	return 1;
 }
@@ -2138,7 +2138,7 @@ static int lib_getTeams(lua_State *L)
 
 	i = luaL_checkinteger(L, 1);
 	if (i < 0 || i >= numteams)
-		return luaL_error(L, "teams[] index %d out of range (0 - %d)", i, numteams-1);
+		return luaL_error(L, "teams[] index %d out of range (0 - %d)", i, max(0, (int)numteams - 1));
 	LUA_PushUserdata(L, &teams[i], META_GAMETYPE);
 	return 1;
 }
@@ -2255,7 +2255,7 @@ static int lib_setTeams(lua_State *L)
 	{
 		teamnum = luaL_checkinteger(L, 1);
 		if (teamnum >= numteams)
-			return luaL_error(L, "teams[] index %d out of range (0 - %d)", teamnum, numteams-1);
+			return luaL_error(L, "teams[] index %d out of range (0 - %d)", teamnum, max(0, (int)numteams - 1));
 		team = &teams[teamnum];
 	}
 	luaL_checktype(L, 2, LUA_TTABLE);
@@ -2433,7 +2433,7 @@ static int teamscores_get(lua_State *L)
 	UINT32 *scoreslist = *((UINT32 **)luaL_checkudata(L, 1, META_TEAMSCORES));
 	int i = luaL_checkinteger(L, 2);
 	if (i < 0 || i >= numteams)
-		return luaL_error(L, "array index %d out of range (0 - %d)", i, numteams - 1);
+		return luaL_error(L, "array index %d out of range (0 - %d)", i, max(0, (int)numteams - 1));
 	lua_pushinteger(L, scoreslist[i]);
 	return 1;
 }
@@ -2444,7 +2444,7 @@ static int teamscores_set(lua_State *L)
 	int i = luaL_checkinteger(L, 2);
 	UINT32 score = (UINT32)luaL_checkinteger(L, 3);
 	if (i < 0 || i >= numteams)
-		return luaL_error(L, "array index %d out of range (0 - %d)", i, numteams - 1);
+		return luaL_error(L, "array index %d out of range (0 - %d)", i, max(0, (int)numteams - 1));
 	scoreslist[i] = score;
 	return 0;
 }
@@ -2455,6 +2455,61 @@ static int teamscores_len(lua_State *L)
 	return 1;
 }
 
+///////////////////
+// PLAYER STARTS //
+///////////////////
+
+static int playerstarts_get(lua_State *L)
+{
+	playerstarts_t *starts = *((playerstarts_t **)luaL_checkudata(L, 1, META_PLAYERSTARTS));
+	int i = luaL_checkinteger(L, 2);
+	if (i < 0 || i >= (signed)starts->count)
+		return luaL_error(L, "player start index %d out of range (0 - %d)", i, max(0, (int)starts->count - 1));
+	LUA_PushUserdata(L, starts->list[i], META_MAPTHING);
+	return 1;
+}
+
+static int playerstarts_set(lua_State *L)
+{
+	playerstarts_t *starts = *((playerstarts_t **)luaL_checkudata(L, 1, META_PLAYERSTARTS));
+	int i = luaL_checkinteger(L, 2);
+	mapthing_t *mthing = *((mapthing_t **)luaL_checkudata(L, 3, META_MAPTHING));
+	if (i < 0 || i >= (signed)starts->count)
+		return luaL_error(L, "player start index %d out of range (0 - %d)", i, max(0, (int)starts->count - 1));
+	if (!mthing)
+		return LUA_ErrInvalid(L, "mapthing_t");
+	starts->list[i] = mthing;
+	return 0;
+}
+
+static int playerstarts_len(lua_State *L)
+{
+	playerstarts_t *starts = *((playerstarts_t **)luaL_checkudata(L, 1, META_PLAYERSTARTS));
+	lua_pushinteger(L, starts->count);
+	return 1;
+}
+
+static int lib_getTeamstarts(lua_State *L)
+{
+	int i;
+	lua_remove(L, 1);
+
+	i = luaL_checkinteger(L, 1);
+	if (i <= 0 || i >= numteams)
+		return luaL_error(L, "team index %d out of range (1 - %d)", i, numteams - 1);
+
+	LUA_PushUserdata(L, &teamstarts[i], META_PLAYERSTARTS);
+
+	return 1;
+}
+
+// #teamstarts -> MAXTEAMS
+static int lib_teamstartslen(lua_State *L)
+{
+	lua_pushinteger(L, MAXTEAMS);
+	return 1;
+}
+
 //////////////////////////////
 //
 // Now push all these functions into the Lua state!
@@ -2542,6 +2597,17 @@ int LUA_InfoLib(lua_State *L)
 		lua_setfield(L, -2, "__len");
 	lua_pop(L, 1);
 
+	luaL_newmetatable(L, META_PLAYERSTARTS);
+		lua_pushcfunction(L, playerstarts_get);
+		lua_setfield(L, -2, "__index");
+
+		lua_pushcfunction(L, playerstarts_set);
+		lua_setfield(L, -2, "__newindex");
+
+		lua_pushcfunction(L, playerstarts_len);
+		lua_setfield(L, -2, "__len");
+	lua_pop(L, 1);
+
 	luaL_newmetatable(L, META_SKINCOLOR);
 		lua_pushcfunction(L, skincolor_get);
 		lua_setfield(L, -2, "__index");
@@ -2731,6 +2797,16 @@ int LUA_InfoLib(lua_State *L)
 		lua_setmetatable(L, -2);
 	lua_setglobal(L, "teams");
 
+	lua_newuserdata(L, 0);
+		lua_createtable(L, 0, 2);
+			lua_pushcfunction(L, lib_getTeamstarts);
+			lua_setfield(L, -2, "__index");
+
+			lua_pushcfunction(L, lib_teamstartslen);
+			lua_setfield(L, -2, "__len");
+		lua_setmetatable(L, -2);
+	lua_setglobal(L, "teamstarts");
+
 	luaL_newmetatable(L, META_LUABANKS);
 		lua_pushcfunction(L, lib_getluabanks);
 		lua_setfield(L, -2, "__index");
diff --git a/src/lua_libs.h b/src/lua_libs.h
index f06daf4b611267395e58cf818b0b774aff6bb257..39d7ebe57b128d68f8ec37b69af7b459d55d4390 100644
--- a/src/lua_libs.h
+++ b/src/lua_libs.h
@@ -32,6 +32,7 @@ extern boolean mousegrabbedbylua;
 #define META_TEAM "TEAM_T*"
 #define META_TEAMLIST "TEAMLIST_T*"
 #define META_TEAMSCORES "TEAMSCORES"
+#define META_PLAYERSTARTS "PLAYERSTARTS"
 #define META_PIVOTLIST "SPRITEFRAMEPIVOT_T[]"
 #define META_FRAMEPIVOT "SPRITEFRAMEPIVOT_T*"
 
diff --git a/src/lua_script.c b/src/lua_script.c
index 2badde0e648dbeed67088b4984843f907d0005fb..7dc69260050dd12f89aabb0f486c5844a668521b 100644
--- a/src/lua_script.c
+++ b/src/lua_script.c
@@ -213,6 +213,12 @@ int LUA_PushGlobals(lua_State *L, const char *word)
 	} else if (fastcmp(word,"paused")) {
 		lua_pushboolean(L, paused);
 		return 1;
+	} else if (fastcmp(word, "playerstarts")) {
+		LUA_PushUserdata(L, &playerstarts, META_PLAYERSTARTS);
+		return 1;
+	} else if (fastcmp(word, "matchstarts")) {
+		LUA_PushUserdata(L, &deathmatchstarts, META_PLAYERSTARTS);
+		return 1;
 	} else if (fastcmp(word,"bluescore")) {
 		lua_pushinteger(L, teamscores[G_GetTeam(2)]);
 		return 1;
diff --git a/src/m_cheat.c b/src/m_cheat.c
index 920e4df49218781c35a2b0bf9f945acd1f0f2bfd..fe51a95761098b5757c061070d963e81f9d05ea0 100644
--- a/src/m_cheat.c
+++ b/src/m_cheat.c
@@ -513,13 +513,14 @@ void Command_Teleport_f(void)
 			mapthing_t *mt;
 			fixed_t offset;
 
-			if (starpostpath >= numcoopstarts)
+			int numstarts = (signed)playerstarts.count;
+			if (starpostpath >= numstarts)
 			{
-				CONS_Alert(CONS_NOTICE, M_GetText("Player %d spawnpoint not found (%d max).\n"), starpostpath+1, numcoopstarts-1);
+				CONS_Alert(CONS_NOTICE, M_GetText("Player %d spawnpoint not found (%d max).\n"), starpostpath+1, numstarts-1);
 				return;
 			}
 
-			mt = playerstarts[starpostpath]; // Given above check, should never be NULL.
+			mt = G_GetPlayerStart(starpostpath); // Given above check, should never be NULL.
 			intx = mt->x<<FRACBITS;
 			inty = mt->y<<FRACBITS;
 			offset = mt->z<<FRACBITS;
diff --git a/src/p_mobj.c b/src/p_mobj.c
index 2db4406cab044cc0dbbd360f9629c1077c41bf17..57170fd202595ce3e57de07178f527d19237da81 100644
--- a/src/p_mobj.c
+++ b/src/p_mobj.c
@@ -44,6 +44,8 @@ actioncache_t actioncachehead;
 
 static mobj_t *overlaycap = NULL;
 
+static mobj_t *P_RespawnMapThing(mapthing_t *mthing);
+
 #define MAXHUNTEMERALDS 64
 mapthing_t *huntemeralds[MAXHUNTEMERALDS];
 INT32 numhuntemeralds;
@@ -11527,7 +11529,7 @@ void P_RespawnSpecials(void)
 #endif
 
 	if (mthing)
-		P_SpawnMapThing(mthing);
+		P_RespawnMapThing(mthing);
 
 	// pull it from the que
 	iquetail = (iquetail+1)&(ITEMQUESIZE-1);
@@ -11933,17 +11935,6 @@ fixed_t P_GetMapThingSpawnHeight(const mobjtype_t mobjtype, const mapthing_t* mt
 	return P_GetMobjSpawnHeight(mobjtype, x, y, dz, offset, flip, mthing->scale);
 }
 
-static void P_SetTeamStart(UINT8 team, mapthing_t *mthing)
-{
-	if (team != TEAM_NONE && team < numteams && numteamstarts[team] < MAXPLAYERS)
-	{
-		teamstarts[team][numteamstarts[team]] = mthing;
-		numteamstarts[team]++;
-		numteamstarts[0]++;
-		mthing->type = 0;
-	}
-}
-
 static boolean P_SpawnNonMobjMapThing(mapthing_t *mthing)
 {
 #if MAXPLAYERS > 32
@@ -11953,33 +11944,28 @@ static boolean P_SpawnNonMobjMapThing(mapthing_t *mthing)
 	{
 		// save spots for respawning in network games
 		if (!metalrecording)
-			playerstarts[mthing->type - 1] = mthing;
+			G_AddPlayerStart(mthing->type - 1, mthing);
 		return true;
 	}
 	else if (mthing->type == 33) // Match starts
 	{
-		if (numdmstarts < MAX_DM_STARTS)
-		{
-			deathmatchstarts[numdmstarts] = mthing;
-			mthing->type = 0;
-			numdmstarts++;
-		}
+		G_AddMatchPlayerStart(mthing);
 		return true;
 	}
 	else if (mthing->type == 34) // Red CTF starts
 	{
-		P_SetTeamStart(G_GetTeam(TEAM_RED), mthing);
+		G_AddTeamPlayerStart(G_GetTeam(TEAM_RED), mthing);
 		return true;
 	}
 	else if (mthing->type == 35) // Blue CTF starts
 	{
-		P_SetTeamStart(G_GetTeam(TEAM_BLUE), mthing);
+		G_AddTeamPlayerStart(G_GetTeam(TEAM_BLUE), mthing);
 		return true;
 	}
 	else if (metalrecording && mthing->type == mobjinfo[MT_METALSONIC_RACE].doomednum)
 	{
 		// If recording, you ARE Metal Sonic. Do not spawn it, do not save normal spawnpoints.
-		playerstarts[0] = mthing;
+		G_AddPlayerStart(0, mthing);
 		return true;
 	}
 	else if (mthing->type == 750 // Slope vertex point (formerly chaos spawn)
@@ -13402,11 +13388,6 @@ static mobj_t *P_SpawnMobjFromMapThing(mapthing_t *mthing, fixed_t x, fixed_t y,
 	return mobj;
 }
 
-//
-// P_SpawnMapThing
-// The fields of the mapthing should
-// already be in host byte order.
-//
 mobj_t *P_SpawnMapThing(mapthing_t *mthing)
 {
 	mobjtype_t i;
@@ -13443,6 +13424,14 @@ mobj_t *P_SpawnMapThing(mapthing_t *mthing)
 	return P_SpawnMobjFromMapThing(mthing, x, y, z, i);
 }
 
+static mobj_t *P_RespawnMapThing(mapthing_t *mthing)
+{
+	if (G_IsSpawnPointThingType(mthing->type))
+		return NULL;
+
+	return P_SpawnMapThing(mthing);
+}
+
 void P_SpawnHoop(mapthing_t *mthing)
 {
 	if (metalrecording)
diff --git a/src/p_saveg.c b/src/p_saveg.c
index 33e3aa4640c0edded1c6bcb1f2e80b5a7509f12e..568ab221c787a42e5329b52b26f7eaa9d68d6b14 100644
--- a/src/p_saveg.c
+++ b/src/p_saveg.c
@@ -4300,6 +4300,101 @@ static inline void P_UnArchiveSPGame(INT16 mapoverride)
 	playeringame[consoleplayer] = true;
 }
 
+static void ArchiveSpawnPoints(void)
+{
+	// Write player starts
+	WRITEUINT8(save_p, playerstarts.capacity);
+	for (size_t i = 0; i < playerstarts.capacity; i++)
+	{
+		if (playerstarts.list[i] == NULL)
+			WRITEUINT16(save_p, 0xFFFF);
+		else
+		{
+			size_t mapthingnum = playerstarts.list[i] - mapthings;
+			WRITEUINT16(save_p, mapthingnum);
+		}
+	}
+
+	// Write Match starts
+	WRITEUINT8(save_p, deathmatchstarts.count);
+	for (size_t i = 0; i < deathmatchstarts.count; i++)
+	{
+		size_t mapthingnum = deathmatchstarts.list[i] - mapthings;
+		WRITEUINT16(save_p, mapthingnum);
+	}
+
+	// Write team starts
+	for (UINT8 team = 1; team < numteams; team++)
+	{
+		WRITEUINT8(save_p, teamstarts[team].count);
+		for (size_t i = 0; i < teamstarts[team].count; i++)
+		{
+			size_t mapthingnum = teamstarts[team].list[i] - mapthings;
+			WRITEUINT16(save_p, mapthingnum);
+		}
+	}
+}
+
+static boolean ReadPlayerStarts(playerstarts_t *starts)
+{
+	size_t count = (size_t)READUINT8(save_p);
+
+	for (size_t i = 0; i < count; i++)
+	{
+		UINT16 mapthingnum = READUINT16(save_p);
+		if (mapthingnum < nummapthings)
+			G_AddSpawnPointToList(starts, &mapthings[mapthingnum]);
+		else
+		{
+			CONS_Alert(CONS_ERROR, "Player start %d has invalid map thing number %d\n", (int)i, mapthingnum);
+			return false;
+		}
+	}
+
+	return true;
+}
+
+static boolean RestoreSpawnPoints(void)
+{
+	// Read player starts
+	playerstarts.capacity = READUINT8(save_p);
+
+	for (size_t i = 0; i < playerstarts.capacity; i++)
+	{
+		UINT16 mapthingnum = READUINT16(save_p);
+		if (mapthingnum == 0xFFFF)
+			G_AddPlayerStart(i, NULL);
+		else if (mapthingnum < nummapthings)
+			G_AddPlayerStart(i, &mapthings[mapthingnum]);
+		else
+		{
+			CONS_Alert(CONS_ERROR, "Player start %d has invalid map thing number %d\n", (int)i, mapthingnum);
+			return false;
+		}
+	}
+
+	G_CountPlayerStarts();
+
+	// Read Match starts
+	if (!ReadPlayerStarts(&deathmatchstarts))
+	{
+		CONS_Alert(CONS_ERROR, "Failed to read Match player starts\n");
+		return false;
+	}
+
+	// Read team starts
+	for (UINT8 team = 1; team < numteams; team++)
+	{
+		if (!ReadPlayerStarts(&teamstarts[team]))
+		{
+			CONS_Alert(CONS_ERROR, "Failed to read team %d player starts\n", team);
+			return false;
+		}
+	}
+
+	return true;
+}
+
 static void P_NetArchiveMisc(boolean resending)
 {
 	INT32 i;
@@ -4349,6 +4444,8 @@ static void P_NetArchiveMisc(boolean resending)
 	for (i = 0; i < MAXTEAMS; i++)
 		WRITEUINT32(save_p, teamscores[i]);
 
+	ArchiveSpawnPoints();
+
 	WRITEINT32(save_p, modulothing);
 
 	WRITEINT16(save_p, autobalance);
@@ -4444,6 +4541,9 @@ static inline boolean P_NetUnArchiveMisc(boolean reloading)
 	for (i = 0; i < MAXTEAMS; i++)
 		teamscores[i] = READUINT32(save_p);
 
+	if (!RestoreSpawnPoints())
+		I_Error("Savegame corrupted");
+
 	modulothing = READINT32(save_p);
 
 	autobalance = READINT16(save_p);
diff --git a/src/p_setup.c b/src/p_setup.c
index 1b3f5b4efb01a03cdc910c63ed44ce73f46db7e2..8cf3fdd657b6f9b8cbdcc597b2939c0560b18655 100644
--- a/src/p_setup.c
+++ b/src/p_setup.c
@@ -143,13 +143,6 @@ mobj_t **blocklinks;
 //
 UINT8 *rejectmatrix;
 
-// Maintain single and multi player starting spots.
-INT32 numdmstarts, numcoopstarts, numteamstarts[MAXTEAMS];
-
-mapthing_t *deathmatchstarts[MAX_DM_STARTS];
-mapthing_t *playerstarts[MAXPLAYERS];
-mapthing_t *teamstarts[MAXTEAMS][MAXPLAYERS];
-
 // Maintain waypoints
 mobj_t *waypoints[NUMWAYPOINTSEQUENCES][WAYPOINTSEQUENCESIZE];
 UINT16 numwaypoints[NUMWAYPOINTSEQUENCES];
@@ -870,7 +863,7 @@ static void P_SpawnEmeraldHunt(void)
 	}
 }
 
-static void P_SpawnMapThings(boolean spawnemblems)
+static void P_SpawnMapThings(boolean respawning, boolean fromnetsave)
 {
 	size_t i;
 	mapthing_t *mt;
@@ -895,22 +888,31 @@ static void P_SpawnMapThings(boolean spawnemblems)
 
 	for (i = 0, mt = mapthings; i < nummapthings; i++, mt++)
 	{
-		if (mt->type == 1700 // MT_AXIS
-			|| mt->type == 1701 // MT_AXISTRANSFER
-			|| mt->type == 1702) // MT_AXISTRANSFERLINE
+		UINT16 type = mt->type;
+
+		if (type == 1700 // MT_AXIS
+			|| type == 1701 // MT_AXISTRANSFER
+			|| type == 1702) // MT_AXISTRANSFERLINE
 			continue; // These were already spawned
 
-		if (!spawnemblems && mt->type == mobjinfo[MT_EMBLEM].doomednum)
+		if (fromnetsave && type == mobjinfo[MT_EMBLEM].doomednum)
 			continue;
 
 		mt->mobj = NULL;
 
-		if (mt->type >= 600 && mt->type <= 611) // item patterns
+		if (type >= 600 && type <= 611) // item patterns
 			P_SpawnItemPattern(mt, false);
-		else if (mt->type == 1713) // hoops
+		else if (type == 1713) // hoops
 			P_SpawnHoop(mt);
 		else // Everything else
+		{
+			// Don't respawn player starts if the game is either respawning all things,
+			// or if the game is loading a savegame.
+			if (G_IsSpawnPointThingType(mt->type) && (respawning || fromnetsave))
+				continue;
+
 			P_SpawnMapThing(mt);
+		}
 	}
 
 	// random emeralds for hunt
@@ -7158,7 +7160,7 @@ void P_RespawnThings(void)
 	localaiming = 0;
 	localaiming2 = 0;
 
-	P_SpawnMapThings(true);
+	P_SpawnMapThings(true, false);
 
 	// restore skybox viewpoint/centerpoint if necessary, set them to defaults if we can't do that
 	skyboxmo[0] = skyboxviewpnts[(viewid >= 0) ? viewid : 0];
@@ -7231,24 +7233,9 @@ static void P_ForceCharacter(const char *forcecharskin)
 
 static void P_ResetSpawnpoints(void)
 {
-	UINT8 i, j;
-
-	numdmstarts = 0;
-
-	// reset the player starts
-	for (i = 0; i < MAXPLAYERS; i++)
-		playerstarts[i] = NULL;
-
-	for (i = 0; i < MAX_DM_STARTS; i++)
-		deathmatchstarts[i] = NULL;
-
-	for (i = 0; i < MAXTEAMS; i++)
-	{
-		numteamstarts[i] = 0;
+	UINT8 i;
 
-		for (j = 0; j < MAXPLAYERS; j++)
-			teamstarts[i][j] = NULL;
-	}
+	G_InitSpawnPoints();
 
 	for (i = 0; i < 2; i++)
 		skyboxmo[i] = NULL;
@@ -7435,12 +7422,7 @@ static void P_SetupCamera(void)
 	}
 	else
 	{
-		mapthing_t *thing;
-
-		if (gametyperules & GTR_DEATHMATCHSTARTS)
-			thing = deathmatchstarts[0];
-		else
-			thing = playerstarts[0];
+		mapthing_t *thing = G_GetInitialSpawnPoint();
 
 		if (thing)
 		{
@@ -7877,13 +7859,12 @@ boolean P_LoadLevel(boolean fromnetsave, boolean reloadinggamestate)
 
 	P_SpawnSlopes(fromnetsave);
 
-	P_SpawnMapThings(!fromnetsave);
+	P_SpawnMapThings(false, fromnetsave);
 	skyboxmo[0] = skyboxviewpnts[0];
 	skyboxmo[1] = skyboxcenterpnts[0];
 
-	for (numcoopstarts = 0; numcoopstarts < MAXPLAYERS; numcoopstarts++)
-		if (!playerstarts[numcoopstarts])
-			break;
+	if (!fromnetsave)
+		G_CountPlayerStarts();
 
 	// set up world state
 	P_SpawnSpecials(fromnetsave);
diff --git a/src/p_setup.h b/src/p_setup.h
index 6e00a5b5cba59385e3fe4713213292c694401e2b..63bd90117e90b66606f169d928175cac7dd524ad 100644
--- a/src/p_setup.h
+++ b/src/p_setup.h
@@ -21,11 +21,6 @@
 // map md5, sent to players via PT_SERVERINFO
 extern unsigned char mapmd5[16];
 
-// Player spawn spots for deathmatch.
-#define MAX_DM_STARTS 64
-extern mapthing_t *deathmatchstarts[MAX_DM_STARTS];
-extern INT32 numdmstarts, numcoopstarts, numteamstarts[MAXTEAMS];
-
 extern boolean levelloading;
 extern UINT8 levelfadecol;