diff --git a/CMakeLists.txt b/CMakeLists.txt
index 23b768a70d744aa1ba4a6ba75b226fff985834e2..f9364fdd2c1b20ea8c3fa2afa744cbe8360a62de 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -1,6 +1,6 @@
 cmake_minimum_required(VERSION 3.0)
 project(SRB2
-	VERSION 2.1.18
+	VERSION 2.1.19
 	LANGUAGES C)
 
 if(${PROJECT_SOURCE_DIR} MATCHES ${PROJECT_BINARY_DIR})
diff --git a/appveyor.yml b/appveyor.yml
index c1f6894ef74075a2189441c9dc2e9dda4476b769..cc073ff01157446cc896f63ae70cfc92f80ef3e8 100644
--- a/appveyor.yml
+++ b/appveyor.yml
@@ -1,4 +1,4 @@
-version: 2.1.18.{branch}-{build}
+version: 2.1.19.{branch}-{build}
 os: MinGW
 
 environment:
diff --git a/src/Makefile b/src/Makefile
index 76f013c5285aaf684c2517cb0cf05a0069f08d42..426dc22898716a92ce7e6bc4330616939ad479a6 100644
--- a/src/Makefile
+++ b/src/Makefile
@@ -511,13 +511,11 @@ OBJS:=$(i_main_o) \
 # For reference, this is the command I use to build a srb2.pot file from the source code.
 # (The listed source files are the ones containing translated strings).
 # FILES=""; for file in `find ./ | grep "\.c" | grep -v svn`; do [ "`grep "M_GetText(" $file`" ] && FILES="$FILES $file"; done; xgettext -d srb2 -o locale/srb2.pot -kM_GetText -F --no-wrap $FILES
-ifndef NOGETTEXT
 ifdef GETTEXT
 POS:=$(BIN)/en.mo
 
 OPTS+=-DGETTEXT
 endif
-endif
 
 ifdef DJGPPDOS
 all:	 pre-build $(BIN)/$(EXENAME)
diff --git a/src/Makefile.cfg b/src/Makefile.cfg
index 80d018c4b5993ad01e1ddbfd34cff1f2564a3398..5bf7f247dfc6c0f0dea25405eaa4eb8e36f8e75b 100644
--- a/src/Makefile.cfg
+++ b/src/Makefile.cfg
@@ -283,9 +283,6 @@ else
 ifdef LINUX
 	NASMFORMAT=elf -DLINUX
 	SDL=1
-ifndef NOGETTEXT
-	GETTEXT=1
-endif
 ifdef LINUX64
 	OBJDIR:=$(OBJDIR)/Linux64
 	BIN:=$(BIN)/Linux64
@@ -321,9 +318,6 @@ else
 ifdef MINGW64
 	INTERFACE=win32
 	#NASMFORMAT=win64
-ifndef NOGETTEXT
-	#GETTEXT=1
-endif
 	OBJDIR:=$(OBJDIR)/Mingw64
 	BIN:=$(BIN)/Mingw64
 else
@@ -354,9 +348,6 @@ else
 ifdef MINGW
 	INTERFACE=win32
 	NASMFORMAT=win32
-ifndef NOGETTEXT
-	GETTEXT=1
-endif
 	OBJDIR:=$(OBJDIR)/Mingw
 	BIN:=$(BIN)/Mingw
 else
diff --git a/src/d_clisrv.c b/src/d_clisrv.c
index 939d53decb65a5ace59ffe670891bdf5478a0690..312a308a101b76acc66a3459b540e7db48922af5 100644
--- a/src/d_clisrv.c
+++ b/src/d_clisrv.c
@@ -1733,9 +1733,7 @@ static boolean CL_ServerConnectionSearchTicker(boolean viams, tic_t *asksent)
 {
 #ifndef NONET
 	INT32 i;
-#endif
 
-#ifndef NONET
 	// serverlist is updated by GetPacket function
 	if (serverlistcount > 0)
 	{
@@ -1769,7 +1767,20 @@ static boolean CL_ServerConnectionSearchTicker(boolean viams, tic_t *asksent)
 				serverlist[i].info.fileneeded);
 			CONS_Printf(M_GetText("Checking files...\n"));
 			i = CL_CheckFiles();
-			if (i == 2) // cannot join for some reason
+			if (i == 3) // too many files
+			{
+				D_QuitNetGame();
+				CL_Reset();
+				D_StartTitle();
+				M_StartMessage(M_GetText(
+					"You have too many WAD files loaded\n"
+					"to add ones the server is using.\n"
+					"Please restart SRB2 before connecting.\n\n"
+					"Press ESC\n"
+				), NULL, MM_NOTHING);
+				return false;
+			}
+			else if (i == 2) // cannot join for some reason
 			{
 				D_QuitNetGame();
 				CL_Reset();
@@ -3402,17 +3413,42 @@ static void HandlePacketFromAwayNode(SINT8 node)
 	if (node != servernode)
 		DEBFILE(va("Received packet from unknown host %d\n", node));
 
+// macro for packets that should only be sent by the server
+// if it is NOT from the server, bail out and close the connection!
+#define SERVERONLY \
+			if (node != servernode) \
+			{ \
+				Net_CloseConnection(node); \
+				break; \
+			}
 	switch (netbuffer->packettype)
 	{
 		case PT_ASKINFOVIAMS:
+#if 0
 			if (server && serverrunning)
 			{
-				INT32 clientnode = I_NetMakeNode(netbuffer->u.msaskinfo.clientaddr);
-				SV_SendServerInfo(clientnode, (tic_t)LONG(netbuffer->u.msaskinfo.time));
-				SV_SendPlayerInfo(clientnode); // Send extra info
-				Net_CloseConnection(clientnode);
-				// Don't close connection to MS.
+				INT32 clientnode;
+				if (ms_RoomId < 0) // ignore if we're not actually on the MS right now
+				{
+					Net_CloseConnection(node); // and yes, close connection
+					return;
+				}
+				clientnode = I_NetMakeNode(netbuffer->u.msaskinfo.clientaddr);
+				if (clientnode != -1)
+				{
+					SV_SendServerInfo(clientnode, (tic_t)LONG(netbuffer->u.msaskinfo.time));
+					SV_SendPlayerInfo(clientnode); // Send extra info
+					Net_CloseConnection(clientnode);
+					// Don't close connection to MS...
+				}
+				else
+					Net_CloseConnection(node); // ...unless the IP address is not valid
 			}
+			else
+				Net_CloseConnection(node); // you're not supposed to get it, so ignore it
+#else
+			Net_CloseConnection(node);
+#endif
 			break;
 
 		case PT_ASKINFO:
@@ -3420,8 +3456,8 @@ static void HandlePacketFromAwayNode(SINT8 node)
 			{
 				SV_SendServerInfo(node, (tic_t)LONG(netbuffer->u.askinfo.time));
 				SV_SendPlayerInfo(node); // Send extra info
-				Net_CloseConnection(node);
 			}
+			Net_CloseConnection(node);
 			break;
 
 		case PT_SERVERREFUSE: // Negative response of client join request
@@ -3430,6 +3466,7 @@ static void HandlePacketFromAwayNode(SINT8 node)
 				Net_CloseConnection(node);
 				break;
 			}
+			SERVERONLY
 			if (cl_mode == CL_WAITJOINRESPONSE)
 			{
 				// Save the reason so it can be displayed after quitting the netgame
@@ -3461,6 +3498,7 @@ static void HandlePacketFromAwayNode(SINT8 node)
 				Net_CloseConnection(node);
 				break;
 			}
+			SERVERONLY
 			/// \note how would this happen? and is it doing the right thing if it does?
 			if (cl_mode != CL_WAITJOINRESPONSE)
 				break;
@@ -3524,13 +3562,18 @@ static void HandlePacketFromAwayNode(SINT8 node)
 				Net_CloseConnection(node);
 				break;
 			}
-			else
-				Got_Filetxpak();
+			SERVERONLY
+			Got_Filetxpak();
 			break;
 
 		case PT_REQUESTFILE:
 			if (server)
-				Got_RequestFilePak(node);
+			{
+				if (!cv_downloading.value || !Got_RequestFilePak(node))
+					Net_CloseConnection(node); // close connection if one of the requested files could not be sent, or you disabled downloading anyway
+			}
+			else
+				Net_CloseConnection(node); // nope
 			break;
 
 		case PT_NODETIMEOUT:
@@ -3553,6 +3596,7 @@ static void HandlePacketFromAwayNode(SINT8 node)
 			break; // Ignore it
 
 	}
+#undef SERVERONLY
 }
 
 /** Handles a packet received from a node that is in game
@@ -3585,6 +3629,8 @@ FILESTAMP
 	{
 // -------------------------------------------- SERVER RECEIVE ----------
 		case PT_RESYNCHGET:
+			if (client)
+				break;
 			SV_AcknowledgeResynchAck(netconsole, netbuffer->u.resynchgot);
 			break;
 		case PT_CLIENTCMD:
@@ -3651,7 +3697,8 @@ FILESTAMP
 			}
 
 			// Splitscreen cmd
-			if (netbuffer->packettype == PT_CLIENT2CMD && nodetoplayer2[node] >= 0)
+			if ((netbuffer->packettype == PT_CLIENT2CMD || netbuffer->packettype == PT_CLIENT2MIS)
+				&& nodetoplayer2[node] >= 0)
 				G_MoveTiccmd(&netcmds[maketic%BACKUPTICS][(UINT8)nodetoplayer2[node]],
 					&netbuffer->u.client2pak.cmd2, 1);
 
@@ -3710,6 +3757,27 @@ FILESTAMP
 				tic_t tic = maketic;
 				UINT8 *textcmd;
 
+				// ignore if the textcmd has a reported size of zero
+				// this shouldn't be sent at all
+				if (!netbuffer->u.textcmd[0])
+				{
+					DEBFILE(va("GetPacket: Textcmd with size 0 detected! (node %u, player %d)\n",
+						node, netconsole));
+					Net_UnAcknowledgePacket(node);
+					break;
+				}
+
+				// ignore if the textcmd size var is actually larger than it should be
+				// BASEPACKETSIZE + 1 (for size) + textcmd[0] should == datalength
+				if (netbuffer->u.textcmd[0] > (size_t)doomcom->datalength-BASEPACKETSIZE-1)
+				{
+					DEBFILE(va("GetPacket: Bad Textcmd packet size! (expected %d, actual %s, node %u, player %d)\n",
+					netbuffer->u.textcmd[0], sizeu1((size_t)doomcom->datalength-BASEPACKETSIZE-1),
+						node, netconsole));
+					Net_UnAcknowledgePacket(node);
+					break;
+				}
+
 				// check if tic that we are making isn't too large else we cannot send it :(
 				// doomcom->numslots+1 "+1" since doomcom->numslots can change within this time and sent time
 				j = software_MAXPACKETLENGTH
@@ -3913,6 +3981,21 @@ FILESTAMP
 		case PT_SERVERCFG:
 			break;
 		case PT_FILEFRAGMENT:
+			// Only accept PT_FILEFRAGMENT from the server.
+			if (node != servernode)
+			{
+				CONS_Alert(CONS_WARNING, M_GetText("%s received from non-host %d\n"), "PT_FILEFRAGMENT", node);
+
+				if (server)
+				{
+					XBOXSTATIC UINT8 buf[2];
+					buf[0] = (UINT8)node;
+					buf[1] = KICK_MSG_CON_FAIL;
+					SendNetXCmd(XD_KICK, &buf, 2);
+				}
+
+				break;
+			}
 			if (client)
 				Got_Filetxpak();
 			break;
diff --git a/src/d_net.c b/src/d_net.c
index 70cdc5f1459109bf27c1c1ec507d218eaaef9768..50b6c8cf692f35561e8ad4a5695775895c603345 100644
--- a/src/d_net.c
+++ b/src/d_net.c
@@ -711,6 +711,13 @@ void Net_CloseConnection(INT32 node)
 #else
 	INT32 i;
 	boolean forceclose = (node & FORCECLOSE) != 0;
+
+	if (node == -1)
+	{
+		DEBFILE(M_GetText("Net_CloseConnection: node -1 detected!\n"));
+		return; // nope, just ignore it
+	}
+
 	node &= ~FORCECLOSE;
 
 	if (!node)
@@ -718,7 +725,7 @@ void Net_CloseConnection(INT32 node)
 
 	if (node < 0 || node >= MAXNETNODES) // prevent invalid nodes from crashing the game
 	{
-		CONS_Alert(CONS_WARNING, M_GetText("Net_CloseConnection: invalid node %d detected!\n"), node);
+		DEBFILE(va(M_GetText("Net_CloseConnection: invalid node %d detected!\n"), node));
 		return;
 	}
 
diff --git a/src/d_netcmd.c b/src/d_netcmd.c
index 478772ed9ae3f85f86080dd1474404ae0f0736c5..b5563f4e89f78be595cf7398ee30c242c8474584 100644
--- a/src/d_netcmd.c
+++ b/src/d_netcmd.c
@@ -1569,8 +1569,13 @@ void D_MapChange(INT32 mapnum, INT32 newgametype, boolean pultmode, boolean rese
 		mapchangepending = 0;
 		// spawn the server if needed
 		// reset players if there is a new one
-		if (!(adminplayer == consoleplayer) && SV_SpawnServer())
-			buf[0] &= ~(1<<1);
+		if (!(adminplayer == consoleplayer))
+		{
+			if (SV_SpawnServer())
+				buf[0] &= ~(1<<1);
+			if (!Playing()) // you failed to start a server somehow, so cancel the map change
+				return;
+		}
 
 		// Kick bot from special stages
 		if (botskin)
@@ -3078,7 +3083,13 @@ static void Got_RequestAddfilecmd(UINT8 **cp, INT32 playernum)
 	filestatus_t ncs = FS_NOTFOUND;
 	UINT8 md5sum[16];
 	boolean kick = false;
+	boolean toomany = false;
 	INT32 i;
+	size_t packetsize = 0;
+	serverinfo_pak *dummycheck = NULL;
+
+	// Shut the compiler up.
+	(void)dummycheck;
 
 	READSTRINGN(*cp, filename, 240);
 	READMEM(*cp, md5sum, 16);
@@ -3104,13 +3115,25 @@ static void Got_RequestAddfilecmd(UINT8 **cp, INT32 playernum)
 		return;
 	}
 
-	ncs = findfile(filename,md5sum,true);
+	// See W_LoadWadFile in w_wad.c
+	for (i = 0; i < numwadfiles; i++)
+		packetsize += nameonlylength(wadfiles[i]->filename) + 22;
+
+	packetsize += nameonlylength(filename) + 22;
 
-	if (ncs != FS_FOUND)
+	if ((numwadfiles >= MAX_WADFILES)
+	|| (packetsize > sizeof(dummycheck->fileneeded)))
+		toomany = true;
+	else
+		ncs = findfile(filename,md5sum,true);
+
+	if (ncs != FS_FOUND || toomany)
 	{
 		char message[256];
 
-		if (ncs == FS_NOTFOUND)
+		if (toomany)
+			sprintf(message, M_GetText("Too many files loaded to add %s\n"), filename);
+		else if (ncs == FS_NOTFOUND)
 			sprintf(message, M_GetText("The server doesn't have %s\n"), filename);
 		else if (ncs == FS_MD5SUMBAD)
 			sprintf(message, M_GetText("Checksum mismatch on %s\n"), filename);
@@ -3180,10 +3203,15 @@ static void Got_Addfilecmd(UINT8 **cp, INT32 playernum)
 
 	ncs = findfile(filename,md5sum,true);
 
-	if (ncs != FS_FOUND)
+	if (ncs != FS_FOUND || !P_AddWadFile(filename, NULL))
 	{
 		Command_ExitGame_f();
-		if (ncs == FS_NOTFOUND)
+		if (ncs == FS_FOUND)
+		{
+			CONS_Printf(M_GetText("The server tried to add %s,\nbut you have too many files added.\nRestart the game to clear loaded files\nand play on this server."), filename);
+			M_StartMessage(va("The server added a file \n(%s)\nbut you have too many files added.\nRestart the game to clear loaded files.\n\nPress ESC\n",filename), NULL, MM_NOTHING);
+		}
+		else if (ncs == FS_NOTFOUND)
 		{
 			CONS_Printf(M_GetText("The server tried to add %s,\nbut you don't have this file.\nYou need to find it in order\nto play on this server."), filename);
 			M_StartMessage(va("The server added a file \n(%s)\nthat you do not have.\n\nPress ESC\n",filename), NULL, MM_NOTHING);
@@ -3201,7 +3229,6 @@ static void Got_Addfilecmd(UINT8 **cp, INT32 playernum)
 		return;
 	}
 
-	P_AddWadFile(filename, NULL);
 	G_SetGameModified(true);
 }
 
diff --git a/src/d_netfil.c b/src/d_netfil.c
index bf4e5987825b2ca40c9fb91f4b28f42aef096e12..172624ad297a753c43abf0e348c3a6df07d8c1f6 100644
--- a/src/d_netfil.c
+++ b/src/d_netfil.c
@@ -62,7 +62,8 @@
 
 #include <errno.h>
 
-static void SV_SendFile(INT32 node, const char *filename, UINT8 fileid);
+// Prototypes
+static boolean SV_SendFile(INT32 node, const char *filename, UINT8 fileid);
 
 // Sender structure
 typedef struct filetx_s
@@ -303,7 +304,8 @@ boolean CL_SendRequestFile(void)
 }
 
 // get request filepak and put it on the send queue
-void Got_RequestFilePak(INT32 node)
+// returns false if a requested file was not found or cannot be sent
+boolean Got_RequestFilePak(INT32 node)
 {
 	char wad[MAX_WADPATH+1];
 	UINT8 *p = netbuffer->u.textcmd;
@@ -314,8 +316,13 @@ void Got_RequestFilePak(INT32 node)
 		if (id == 0xFF)
 			break;
 		READSTRINGN(p, wad, MAX_WADPATH);
-		SV_SendFile(node, wad, id);
+		if (!SV_SendFile(node, wad, id))
+		{
+			SV_AbortSendFiles(node);
+			return false; // don't read the rest of the files
+		}
 	}
+	return true; // no problems with any files
 }
 
 /** Checks if the files needed aren't already loaded or on the disk
@@ -330,6 +337,12 @@ INT32 CL_CheckFiles(void)
 	INT32 i, j;
 	char wadfilename[MAX_WADPATH];
 	INT32 ret = 1;
+	size_t packetsize = 0;
+	size_t filestoget = 0;
+	serverinfo_pak *dummycheck = NULL;
+
+	// Shut the compiler up.
+	(void)dummycheck;
 
 //	if (M_CheckParm("-nofiles"))
 //		return 1;
@@ -378,6 +391,10 @@ INT32 CL_CheckFiles(void)
 		return 1;
 	}
 
+	// See W_LoadWadFile in w_wad.c
+	for (i = 0; i < numwadfiles; i++)
+		packetsize += nameonlylength(wadfiles[i]->filename) + 22;
+
 	for (i = 1; i < fileneedednum; i++)
 	{
 		CONS_Debug(DBG_NETPLAY, "searching for '%s' ", fileneeded[i].filename);
@@ -397,6 +414,14 @@ INT32 CL_CheckFiles(void)
 		if (fileneeded[i].status != FS_NOTFOUND || !fileneeded[i].important)
 			continue;
 
+		packetsize += nameonlylength(fileneeded[i].filename) + 22;
+
+		if ((numwadfiles+filestoget >= MAX_WADFILES)
+		|| (packetsize > sizeof(dummycheck->fileneeded)))
+			return 3;
+
+		filestoget++;
+
 		fileneeded[i].status = findfile(fileneeded[i].filename, fileneeded[i].md5sum, true);
 		CONS_Debug(DBG_NETPLAY, "found %d\n", fileneeded[i].status);
 		if (fileneeded[i].status != FS_FOUND)
@@ -480,7 +505,7 @@ static INT32 filestosend = 0;
   * \sa SV_SendRam
   *
   */
-static void SV_SendFile(INT32 node, const char *filename, UINT8 fileid)
+static boolean SV_SendFile(INT32 node, const char *filename, UINT8 fileid)
 {
 	filetx_t **q; // A pointer to the "next" field of the last file in the list
 	filetx_t *p; // The new file request
@@ -488,7 +513,7 @@ static void SV_SendFile(INT32 node, const char *filename, UINT8 fileid)
 	char wadfilename[MAX_WADPATH];
 
 	if (cv_noticedownload.value)
-		CONS_Printf("Sending file \"%s\" to node %d\n", filename, node);
+		CONS_Printf("Sending file \"%s\" to node %d (%s)\n", filename, node, I_GetNodeAddress(node));
 
 	// Find the last file in the list and set a pointer to its "next" field
 	q = &transfer[node].txlist;
@@ -537,7 +562,7 @@ static void SV_SendFile(INT32 node, const char *filename, UINT8 fileid)
 		free(p->id.filename);
 		free(p);
 		*q = NULL;
-		return;
+		return false; // cancel the rest of the requests
 	}
 
 	// Handle huge file requests (i.e. bigger than cv_maxsend.value KB)
@@ -549,7 +574,7 @@ static void SV_SendFile(INT32 node, const char *filename, UINT8 fileid)
 		free(p->id.filename);
 		free(p);
 		*q = NULL;
-		return;
+		return false; // cancel the rest of the requests
 	}
 
 	DEBFILE(va("Sending file %s (id=%d) to %d\n", filename, fileid, node));
@@ -557,6 +582,7 @@ static void SV_SendFile(INT32 node, const char *filename, UINT8 fileid)
 	p->fileid = fileid;
 	p->next = NULL; // End of list
 	filestosend++;
+	return true;
 }
 
 /** Adds a memory block to the file list for a node
diff --git a/src/d_netfil.h b/src/d_netfil.h
index c9085a5b0ecdc38ed3fb8a0ecb9278ef37369749..b9b7b2f2ea389c977b26cfb970502f3b5ee67333 100644
--- a/src/d_netfil.h
+++ b/src/d_netfil.h
@@ -69,7 +69,7 @@ boolean SV_SendingFile(INT32 node);
 
 boolean CL_CheckDownloadable(void);
 boolean CL_SendRequestFile(void);
-void Got_RequestFilePak(INT32 node);
+boolean Got_RequestFilePak(INT32 node);
 
 void SV_AbortSendFiles(INT32 node);
 void CloseNetFile(void);
diff --git a/src/doomdef.h b/src/doomdef.h
index 428972ccd634f60b730937ca2ef98fe699ff9ea8..cdb1a7dbf4b685f7c9b01d9a6a607d63d83d3e8c 100644
--- a/src/doomdef.h
+++ b/src/doomdef.h
@@ -150,9 +150,9 @@ extern FILE *logstream;
 // we use comprevision and compbranch instead.
 #else
 #define VERSION    201 // Game version
-#define SUBVERSION 18  // more precise version number
-#define VERSIONSTRING "v2.1.18"
-#define VERSIONSTRINGW L"v2.1.18"
+#define SUBVERSION 19  // more precise version number
+#define VERSIONSTRING "v2.1.19"
+#define VERSIONSTRINGW L"v2.1.19"
 // Hey! If you change this, add 1 to the MODVERSION below!
 // Otherwise we can't force updates!
 #endif
@@ -214,7 +214,7 @@ extern FILE *logstream;
 // it's only for detection of the version the player is using so the MS can alert them of an update.
 // Only set it higher, not lower, obviously.
 // Note that we use this to help keep internal testing in check; this is why v2.1.0 is not version "1".
-#define MODVERSION 23
+#define MODVERSION 24
 
 // =========================================================================
 
diff --git a/src/lua_thinkerlib.c b/src/lua_thinkerlib.c
index d5251425a9f29fd708784fbe36a3d38231d23051..aaa8435e955f20a6344bca6219124761a36429fa 100644
--- a/src/lua_thinkerlib.c
+++ b/src/lua_thinkerlib.c
@@ -16,84 +16,118 @@
 #include "lua_script.h"
 #include "lua_libs.h"
 
+#define META_ITERATIONSTATE "iteration state"
+
 static const char *const iter_opt[] = {
 	"all",
 	"mobj",
 	NULL};
 
-static int lib_iterateThinkers(lua_State *L)
-{
-	int state = luaL_checkoption(L, 1, "mobj", iter_opt);
+static const actionf_p1 iter_funcs[] = {
+	NULL,
+	(actionf_p1)P_MobjThinker
+};
 
-	thinker_t *th = NULL;
-	actionf_p1 searchFunc;
-	const char *searchMeta;
+struct iterationState {
+	actionf_p1 filter;
+	int next;
+};
 
-	lua_settop(L, 2);
-	lua_remove(L, 1); // remove state now.
-
-	switch(state)
+static int iterationState_gc(lua_State *L)
+{
+	struct iterationState *it = luaL_checkudata(L, -1, META_ITERATIONSTATE);
+	if (it->next != LUA_REFNIL)
 	{
-		case 0:
-			searchFunc = NULL;
-			searchMeta = NULL;
-			break;
-		case 1:
-		default:
-			searchFunc = (actionf_p1)P_MobjThinker;
-			searchMeta = META_MOBJ;
-			break;
+		luaL_unref(L, LUA_REGISTRYINDEX, it->next);
+		it->next = LUA_REFNIL;
 	}
+	return 0;
+}
 
-	if (!lua_isnil(L, 1)) {
-		if (lua_islightuserdata(L, 1))
-			th = (thinker_t *)lua_touserdata(L, 1);
-		else if (searchMeta)
-			th = *((thinker_t **)luaL_checkudata(L, 1, searchMeta));
-		else
-			th = *((thinker_t **)lua_touserdata(L, 1));
-	} else
-		th = &thinkercap;
+#define push_thinker(th) {\
+	if ((th)->function.acp1 == (actionf_p1)P_MobjThinker) \
+		LUA_PushUserdata(L, (th), META_MOBJ); \
+	else \
+		lua_pushlightuserdata(L, (th)); \
+}
 
-	if (!th) // something got our userdata invalidated!
-		return 0;
+static int lib_iterateThinkers(lua_State *L)
+{
+	thinker_t *th = NULL, *next = NULL;
+	struct iterationState *it = luaL_checkudata(L, 1, META_ITERATIONSTATE);
+	lua_settop(L, 2);
 
-	if (searchFunc == NULL)
+	if (lua_isnil(L, 2))
+		th = &thinkercap;
+	else if (lua_isuserdata(L, 2))
 	{
-		if ((th = th->next) != &thinkercap)
+		if (lua_islightuserdata(L, 2))
+			th = lua_touserdata(L, 2);
+		else
 		{
-			if (th->function.acp1 == (actionf_p1)P_MobjThinker)
-				LUA_PushUserdata(L, th, META_MOBJ);
-			else
-				lua_pushlightuserdata(L, th);
-			return 1;
+			th = *(thinker_t **)lua_touserdata(L, -1);
+			if (!th)
+			{
+				if (it->next == LUA_REFNIL)
+					return 0;
+
+				lua_rawgeti(L, LUA_REGISTRYINDEX, it->next);
+				if (lua_islightuserdata(L, -1))
+					next = lua_touserdata(L, -1);
+				else
+					next = *(thinker_t **)lua_touserdata(L, -1);
+			}
 		}
-		return 0;
 	}
 
-	for (th = th->next; th != &thinkercap; th = th->next)
-	{
-		if (th->function.acp1 != searchFunc)
-			continue;
+	luaL_unref(L, LUA_REGISTRYINDEX, it->next);
+	it->next = LUA_REFNIL;
 
-		LUA_PushUserdata(L, th, searchMeta);
-		return 1;
-	}
+	if (th && !next)
+		next = th->next;
+	if (!next)
+		return luaL_error(L, "next thinker invalidated during iteration");
+
+	for (; next != &thinkercap; next = next->next)
+		if (!it->filter || next->function.acp1 == it->filter)
+		{
+			push_thinker(next);
+			if (next->next != &thinkercap)
+			{
+				push_thinker(next->next);
+				it->next = luaL_ref(L, LUA_REGISTRYINDEX);
+			}
+			return 1;
+		}
 	return 0;
 }
 
 static int lib_startIterate(lua_State *L)
 {
-	luaL_checkoption(L, 1, iter_opt[0], iter_opt);
-	lua_pushcfunction(L, lib_iterateThinkers);
-	lua_pushvalue(L, 1);
+	struct iterationState *it;
+
+	lua_pushvalue(L, lua_upvalueindex(1));
+	it = lua_newuserdata(L, sizeof(struct iterationState));
+	luaL_getmetatable(L, META_ITERATIONSTATE);
+	lua_setmetatable(L, -2);
+
+	it->filter = iter_funcs[luaL_checkoption(L, 1, "mobj", iter_opt)];
+	it->next = LUA_REFNIL;
 	return 2;
 }
 
+#undef push_thinker
+
 int LUA_ThinkerLib(lua_State *L)
 {
+	luaL_newmetatable(L, META_ITERATIONSTATE);
+	lua_pushcfunction(L, iterationState_gc);
+	lua_setfield(L, -2, "__gc");
+	lua_pop(L, 1);
+
 	lua_createtable(L, 0, 1);
-		lua_pushcfunction(L, lib_startIterate);
+		lua_pushcfunction(L, lib_iterateThinkers);
+		lua_pushcclosure(L, lib_startIterate, 1);
 		lua_setfield(L, -2, "iterate");
 	lua_setglobal(L, "thinkers");
 	return 0;
diff --git a/src/p_mobj.c b/src/p_mobj.c
index fb8648013fbecd1527dbdc22d70cd806a7202984..9fdbc37fb848a33c401d02f518c0fe76d5888a0f 100644
--- a/src/p_mobj.c
+++ b/src/p_mobj.c
@@ -2363,7 +2363,7 @@ static boolean P_ZMovement(mobj_t *mo)
 			mo->z = mo->floorz;
 
 #ifdef ESLOPE
-		if (mo->standingslope) // You're still on the ground; why are we here?
+		if (!(mo->flags & MF_MISSILE) && mo->standingslope) // You're still on the ground; why are we here?
 		{
 			mo->momz = 0;
 			return true;
@@ -7807,6 +7807,7 @@ mobj_t *P_SpawnMobj(fixed_t x, fixed_t y, fixed_t z, mobjtype_t type)
 			break;
 		case MT_EGGCAPSULE:
 			mobj->extravalue1 = -1; // timer for how long a player has been at the capsule
+			break;
 		case MT_REDTEAMRING:
 			mobj->color = skincolor_redteam;
 			break;
diff --git a/src/sdl/macosx/Srb2mac.xcodeproj/project.pbxproj b/src/sdl/macosx/Srb2mac.xcodeproj/project.pbxproj
index fbf9bacb21265d6dd1e9114da038211703dab154..68391f99ca9168b9f5c68d62657df71fae5ba167 100644
--- a/src/sdl/macosx/Srb2mac.xcodeproj/project.pbxproj
+++ b/src/sdl/macosx/Srb2mac.xcodeproj/project.pbxproj
@@ -1214,7 +1214,7 @@
 		C01FCF4B08A954540054247B /* Debug */ = {
 			isa = XCBuildConfiguration;
 			buildSettings = {
-				CURRENT_PROJECT_VERSION = 2.1.18;
+				CURRENT_PROJECT_VERSION = 2.1.19;
 				GCC_PREPROCESSOR_DEFINITIONS = (
 					"$(inherited)",
 					NORMALSRB2,
@@ -1226,7 +1226,7 @@
 		C01FCF4C08A954540054247B /* Release */ = {
 			isa = XCBuildConfiguration;
 			buildSettings = {
-				CURRENT_PROJECT_VERSION = 2.1.18;
+				CURRENT_PROJECT_VERSION = 2.1.19;
 				GCC_ENABLE_FIX_AND_CONTINUE = NO;
 				GCC_GENERATE_DEBUGGING_SYMBOLS = NO;
 				GCC_PREPROCESSOR_DEFINITIONS = (
diff --git a/src/sdl12/macosx/Srb2mac.xcodeproj/project.pbxproj b/src/sdl12/macosx/Srb2mac.xcodeproj/project.pbxproj
index 98a760c7ba5dd88c1a74a160f188a23dad4ef2ef..fada7849c2a2305aed694cd3ebb52f10597f5217 100644
--- a/src/sdl12/macosx/Srb2mac.xcodeproj/project.pbxproj
+++ b/src/sdl12/macosx/Srb2mac.xcodeproj/project.pbxproj
@@ -1214,7 +1214,7 @@
 		C01FCF4B08A954540054247B /* Debug */ = {
 			isa = XCBuildConfiguration;
 			buildSettings = {
-				CURRENT_PROJECT_VERSION = 2.1.18;
+				CURRENT_PROJECT_VERSION = 2.1.19;
 				GCC_PREPROCESSOR_DEFINITIONS = (
 					"$(inherited)",
 					NORMALSRB2,
@@ -1226,7 +1226,7 @@
 		C01FCF4C08A954540054247B /* Release */ = {
 			isa = XCBuildConfiguration;
 			buildSettings = {
-				CURRENT_PROJECT_VERSION = 2.1.18;
+				CURRENT_PROJECT_VERSION = 2.1.19;
 				GCC_ENABLE_FIX_AND_CONTINUE = NO;
 				GCC_GENERATE_DEBUGGING_SYMBOLS = NO;
 				GCC_PREPROCESSOR_DEFINITIONS = (
diff --git a/src/win32/win_cd.c b/src/win32/win_cd.c
index d73b95523e69ac9599463f590e6db5adbae55a30..ae13d3e574895db68c1389f4151b1e4db1ce717a 100644
--- a/src/win32/win_cd.c
+++ b/src/win32/win_cd.c
@@ -180,9 +180,9 @@ static LPSTR hms(UINT seconds)
 	hours = minutes / 60;
 	minutes %= 60;
 	if (hours > 0)
-		sprintf (s, "%lu:%02lu:%02lu", hours, minutes, seconds);
+		sprintf (s, "%lu:%02lu:%02lu", (long unsigned int)hours, (long unsigned int)minutes, (long unsigned int)seconds);
 	else
-		sprintf (s, "%2lu:%02lu", minutes, seconds);
+		sprintf (s, "%2lu:%02lu", (long unsigned int)minutes, (long unsigned int)seconds);
 	return s;
 }
 
diff --git a/src/win32/win_main.c b/src/win32/win_main.c
index d84c862320520940151e0d39017c4a42b7f74147..4ac05f94f4cc02d88cebc2ec9dc645d0c0031476 100644
--- a/src/win32/win_main.c
+++ b/src/win32/win_main.c
@@ -470,7 +470,7 @@ static inline BOOL tlErrorMessage(const TCHAR *err)
 	//
 	// warn user if there is one
 	//
-	printf("Error %Ts..\n", err);
+	printf("Error %s..\n", err);
 	fflush(stdout);
 
 	MessageBox(hWndMain, err, TEXT("ERROR"), MB_OK);
diff --git a/src/y_inter.c b/src/y_inter.c
index b2e1cdf9fa7884a9867ba884921066690eb73210..2fd37ff33b9a3a64aa8ede438fd9ce4ed8874469 100644
--- a/src/y_inter.c
+++ b/src/y_inter.c
@@ -965,7 +965,8 @@ void Y_StartIntermission(void)
 	}
 
 	// We couldn't display the intermission even if we wanted to.
-	if (dedicated) return;
+	// But we still need to give the players their score bonuses, dummy.
+	//if (dedicated) return;
 
 	// This should always exist, but just in case...
 	if(!mapheaderinfo[prevmap])