diff --git a/appveyor.yml b/appveyor.yml
index d0d94b9828b56bc70f2e60bb18064bf9c933a099..130b0deccbf43e52ebfa40f4984cea82a24c5135 100644
--- a/appveyor.yml
+++ b/appveyor.yml
@@ -1,4 +1,4 @@
-version: 2.2.14.{branch}-{build}
+version: 2.2.16.{branch}-{build}
 os: MinGW
 
 environment:
diff --git a/src/console.c b/src/console.c
index 50ecfec9c28fa11a9395907478dbb93d9d416a24..2eb5b26c3afeae0459a9d4b06aa79274185628f8 100644
--- a/src/console.c
+++ b/src/console.c
@@ -954,6 +954,9 @@ boolean CON_Responder(event_t *ev)
 
 		if ((key == gamecontrol[GC_CONSOLE][0] || key == gamecontrol[GC_CONSOLE][1]) && !shiftdown)
 		{
+			if (con_destlines == 0 && I_GetTextInputMode())
+				return false; // some other component is holding keyboard input, don't hijack it!
+
 			I_SetTextInputMode(con_destlines == 0); // inverse, since this is changed next tic.
 			consoletoggle = true;
 			return true;
diff --git a/src/g_game.c b/src/g_game.c
index 1c186ae03149780b254c72f5243ed3d551350e52..4e5c3e668b2f83ad5a73f84737428a5f2a8f99a3 100644
--- a/src/g_game.c
+++ b/src/g_game.c
@@ -2270,6 +2270,11 @@ boolean G_LuaResponder(event_t *ev)
 		cancelled = LUA_HookKey(ev, HOOK(KeyUp));
 		LUA_InvalidateUserdata(ev);
 	}
+	else if (ev->type == ev_text)
+	{
+		cancelled = LUA_HookText(ev, HOOK(TextInput));
+		LUA_InvalidateUserdata(ev);
+	}
 
 	return cancelled;
 }
diff --git a/src/lua_baselib.c b/src/lua_baselib.c
index 1ffa3968be16bf8ae67b04461f12b4a6d8d1f77b..c253fa3b19a97f0513b90cdbddd56489515945db 100644
--- a/src/lua_baselib.c
+++ b/src/lua_baselib.c
@@ -238,6 +238,7 @@ static const struct {
 	{META_LUABANKS,     "luabanks[]"},
 
 	{META_KEYEVENT,     "keyevent_t"},
+	{META_TEXTEVENT,    "textevent_t"},
 	{META_MOUSE,        "mouse_t"},
 	{NULL,              NULL}
 };
diff --git a/src/lua_hook.h b/src/lua_hook.h
index ce79cd1cb70a2e81f9d531ed15094e7cf42b1e75..326df376176c892dd3b21f3fc9e1fdf1bbadd99b 100644
--- a/src/lua_hook.h
+++ b/src/lua_hook.h
@@ -75,6 +75,7 @@ automatically.
 	X (AddonLoaded),\
 	X (KeyDown),\
 	X (KeyUp),\
+	X (TextInput),\
 
 #define STRING_HOOK_LIST(X) \
 	X (BotAI),/* B_BuildTailsTiccmd by skin name */\
@@ -135,6 +136,7 @@ void LUA_HookBool(boolean value, int hook);
 int  LUA_HookPlayer(player_t *, int hook);
 int  LUA_HookTiccmd(player_t *, ticcmd_t *, int hook);
 int  LUA_HookKey(event_t *event, int hook); // Hooks for key events
+int  LUA_HookText(event_t *event, int hook); // Hooks for text events
 
 void LUA_HookPreThinkFrame(void);
 void LUA_HookThinkFrame(void);
diff --git a/src/lua_hooklib.c b/src/lua_hooklib.c
index 1bf3caf65fa502f70b447374b6b21600d6261c87..a39745438db6c8b9a690b0a24c3240f74f1975cc 100644
--- a/src/lua_hooklib.c
+++ b/src/lua_hooklib.c
@@ -690,6 +690,17 @@ int LUA_HookKey(event_t *event, int hook_type)
 	return hook.status;
 }
 
+int LUA_HookText(event_t *event, int hook_type)
+{
+	Hook_State hook;
+	if (prepare_hook(&hook, false, hook_type))
+	{
+		LUA_PushUserdata(gL, event, META_TEXTEVENT);
+		call_hooks(&hook, 1, res_true);
+	}
+	return hook.status;
+}
+
 void LUA_HookHUD(int hook_type, huddrawlist_h list)
 {
 	Hook_State hook;
diff --git a/src/lua_inputlib.c b/src/lua_inputlib.c
index ef3a9011f1c47a27dc8c75b3e43dc2cd47b9a2d8..eb9fe7dc55a5947b6d5594f01fcda962ccfa0178 100644
--- a/src/lua_inputlib.c
+++ b/src/lua_inputlib.c
@@ -129,6 +129,18 @@ static int lib_getCursorPosition(lua_State *L)
 	return 2;
 }
 
+static int lib_setTextInputMode(lua_State *L)
+{
+	I_SetTextInputMode(luaL_checkboolean(L, 1));
+	return 0;
+}
+
+static int lib_getTextInputMode(lua_State *L)
+{
+	lua_pushinteger(L, I_GetTextInputMode());
+	return 1;
+}
+
 static luaL_Reg lib[] = {
 	{"gameControlDown", lib_gameControlDown},
 	{"gameControl2Down", lib_gameControl2Down},
@@ -143,6 +155,8 @@ static luaL_Reg lib[] = {
 	{"getMouseGrab", lib_getMouseGrab},
 	{"setMouseGrab", lib_setMouseGrab},
 	{"getCursorPosition", lib_getCursorPosition},
+	{"setTextInputMode", lib_setTextInputMode},
+	{"getTextInputMode", lib_getTextInputMode},
 	{NULL, NULL}
 };
 
@@ -220,6 +234,28 @@ static int lib_lenGameKeyDown(lua_State *L)
 	return 1;
 }
 
+////////////////
+// TEXT EVENT //
+////////////////
+
+static int textevent_get(lua_State *L)
+{
+	event_t *event = *((event_t **)luaL_checkudata(L, 1, META_TEXTEVENT));
+	const char *field = luaL_checkstring(L, 2);
+
+	I_Assert(event != NULL);
+
+	if (fastcmp(field,"text"))
+	{
+		char s[2] = { event->key, 0 };
+		lua_pushstring(L, s);
+	}
+	else
+		return luaL_error(L, "textevent_t has no field named %s", field);
+
+	return 1;
+}
+
 ///////////////
 // KEY EVENT //
 ///////////////
@@ -285,6 +321,7 @@ static int mouse_num(lua_State *L)
 
 int LUA_InputLib(lua_State *L)
 {
+	LUA_RegisterUserdataMetatable(L, META_TEXTEVENT, textevent_get, NULL, NULL);
 	LUA_RegisterUserdataMetatable(L, META_KEYEVENT, keyevent_get, NULL, NULL);
 	LUA_RegisterUserdataMetatable(L, META_MOUSE, mouse_get, NULL, mouse_num);
 
diff --git a/src/lua_libs.h b/src/lua_libs.h
index e1585f488a8a6205b1ee967dcfeffac125680fee..592f46df560435b085b9fe025a263d6b1317e042 100644
--- a/src/lua_libs.h
+++ b/src/lua_libs.h
@@ -93,6 +93,7 @@ extern boolean ignoregameinputs;
 
 #define META_LUABANKS "LUABANKS[]*"
 
+#define META_TEXTEVENT "TEXTEVENT_T*"
 #define META_KEYEVENT "KEYEVENT_T*"
 #define META_MOUSE "MOUSE_T*"
 
diff --git a/src/netcode/client_connection.c b/src/netcode/client_connection.c
index d55238f46643bc1183d1504e670b0c583056c172..652abe42d1196a4c3e81ff9ed8ac1cb1e6a99e71 100644
--- a/src/netcode/client_connection.c
+++ b/src/netcode/client_connection.c
@@ -1239,7 +1239,7 @@ static boolean CL_ServerConnectionTicker(const char *tmpsave, tic_t *oldtic, tic
 
 void CL_ConnectToServer(void)
 {
-	INT32 pnumnodes, nodewaited = doomcom->numnodes, i;
+	INT32 pnumnodes, nodewaited = numnetnodes, i;
 	tic_t oldtic;
 	tic_t asksent;
 	char tmpsave[256];
@@ -1265,7 +1265,7 @@ void CL_ConnectToServer(void)
 	if (gamestate == GS_INTERMISSION)
 		Y_EndIntermission(); // clean up intermission graphics etc
 
-	DEBFILE(va("waiting %d nodes\n", doomcom->numnodes));
+	DEBFILE(va("waiting %d nodes\n", numnetnodes));
 	G_SetGamestate(GS_WAITINGPLAYERS);
 	wipegamestate = GS_WAITINGPLAYERS;
 
@@ -1426,7 +1426,7 @@ void PT_ServerCFG(SINT8 node)
 
 	netnodes[(UINT8)servernode].ingame = true;
 	serverplayer = netbuffer->u.servercfg.serverplayer;
-	doomcom->numslots = SHORT(netbuffer->u.servercfg.totalslotnum);
+	numslots = SHORT(netbuffer->u.servercfg.totalslotnum);
 	mynode = netbuffer->u.servercfg.clientnode;
 	if (serverplayer >= 0)
 		playernode[(UINT8)serverplayer] = servernode;
diff --git a/src/netcode/d_clisrv.c b/src/netcode/d_clisrv.c
index 7a5fb06a6c37f4ff95cbef8abf25077a6c16d377..1cd26cd4f32ca9e239fd3f26a728e54629cc8ed2 100644
--- a/src/netcode/d_clisrv.c
+++ b/src/netcode/d_clisrv.c
@@ -149,8 +149,8 @@ void CL_Reset(void)
 	multiplayer = false;
 	servernode = 0;
 	server = true;
-	doomcom->numnodes = 1;
-	doomcom->numslots = 1;
+	numnetnodes = 1;
+	numslots = 1;
 	SV_StopServer();
 	SV_ResetServer();
 
@@ -215,8 +215,8 @@ static void Got_AddPlayer(UINT8 **p, INT32 playernum)
 			CL_ClearPlayer(newplayernum);
 		playeringame[newplayernum] = true;
 		G_AddPlayer(newplayernum);
-		if (newplayernum+1 > doomcom->numslots)
-			doomcom->numslots = (INT16)(newplayernum+1);
+		if (newplayernum+1 > numslots)
+			numslots = (INT16)(newplayernum+1);
 
 		if (server && I_GetNodeAddress)
 		{
@@ -612,8 +612,8 @@ void CL_RemovePlayer(INT32 playernum, kickreason_t reason)
 
 	// remove avatar of player
 	playeringame[playernum] = false;
-	while (!playeringame[doomcom->numslots-1] && doomcom->numslots > 1)
-		doomcom->numslots--;
+	while (!playeringame[numslots-1] && numslots > 1)
+		numslots--;
 
 	// Reset the name
 	sprintf(player_names[playernum], "Player %d", playernum+1);
@@ -754,7 +754,7 @@ void SV_ResetServer(void)
 	if (server)
 		servernode = 0;
 
-	doomcom->numslots = 0;
+	numslots = 0;
 
 	// clear server_context
 	memset(server_context, '-', 8);
@@ -806,7 +806,7 @@ void SV_SpawnServer(void)
 		// non dedicated server just connect to itself
 		if (!dedicated)
 			CL_ConnectToServer();
-		else doomcom->numslots = 1;
+		else numslots = 1;
 	}
 }
 
diff --git a/src/netcode/d_net.c b/src/netcode/d_net.c
index a3a47b9504d226ffa2bf01136c3e756624579300..e08a4c7d3be49112081af119b7aa9b63fda41233 100644
--- a/src/netcode/d_net.c
+++ b/src/netcode/d_net.c
@@ -48,6 +48,10 @@
 #define FORCECLOSE 0x8000
 tic_t connectiontimeout = (10*TICRATE);
 
+INT16 numnetnodes;
+INT16 numslots;
+INT16 extratics;
+
 /// \brief network packet
 doomcom_t *doomcom = NULL;
 /// \brief network packet data, points inside doomcom
@@ -999,8 +1003,8 @@ void D_SetDoomcom(void)
 {
 	if (doomcom) return;
 	doomcom = Z_Calloc(sizeof (doomcom_t), PU_STATIC, NULL);
-	doomcom->numslots = doomcom->numnodes = 1;
-	doomcom->extratics = 0;
+	numslots = numnetnodes = 1;
+	extratics = 0;
 }
 
 //
@@ -1046,10 +1050,10 @@ boolean D_CheckNetGame(void)
 	if (M_CheckParm("-extratic"))
 	{
 		if (M_IsNextParm())
-			doomcom->extratics = (INT16)atoi(M_GetNextParm());
+			extratics = (INT16)atoi(M_GetNextParm());
 		else
-			doomcom->extratics = 1;
-		CONS_Printf(M_GetText("Set extratics to %d\n"), doomcom->extratics);
+			extratics = 1;
+		CONS_Printf(M_GetText("Set extratics to %d\n"), extratics);
 	}
 
 	software_MAXPACKETLENGTH = hardware_MAXPACKETLENGTH;
@@ -1071,8 +1075,8 @@ boolean D_CheckNetGame(void)
 	if (netgame)
 		multiplayer = true;
 
-	if (doomcom->numnodes > MAXNETNODES)
-		I_Error("Too many nodes (%d), max:%d", doomcom->numnodes, MAXNETNODES);
+	if (numnetnodes > MAXNETNODES)
+		I_Error("Too many nodes (%d), max:%d", numnetnodes, MAXNETNODES);
 
 	netbuffer = (doomdata_t *)(void *)&doomcom->data;
 
diff --git a/src/netcode/d_netfil.c b/src/netcode/d_netfil.c
index 25290990789988be750ae5cdd41986c628f59ebf..b1d46c96fe56abab161e9ba2e7530d1822ec8067 100644
--- a/src/netcode/d_netfil.c
+++ b/src/netcode/d_netfil.c
@@ -129,7 +129,7 @@ boolean waitingforluafilecommand = false;
 char luafiledir[256 + 16] = "luafiles";
 
 // max file size to send to a player (in kilobytes)
-static CV_PossibleValue_t maxsend_cons_t[] = {{0, "MIN"}, {204800, "MAX"}, {0, NULL}};
+static CV_PossibleValue_t maxsend_cons_t[] = {{-1, "MIN"}, {999999999, "MAX"}, {0, NULL}};
 consvar_t cv_maxsend = CVAR_INIT ("maxsend", "4096", CV_SAVE|CV_NETVAR, maxsend_cons_t, NULL);
 
 consvar_t cv_noticedownload = CVAR_INIT ("noticedownload", "Off", CV_SAVE|CV_NETVAR, CV_OnOff, NULL);
@@ -206,7 +206,7 @@ UINT8 *PutFileNeeded(UINT16 firstfile)
 			// Store in the upper four bits
 			if (!cv_downloading.value)
 				filestatus += (WILLSEND_NO << 4); // Won't send
-			else if (wadfiles[i]->filesize <= (UINT32)cv_maxsend.value * 1024)
+			else if (cv_maxsend.value == -1 || wadfiles[i]->filesize <= (UINT32)cv_maxsend.value * 1024)
 				filestatus += (WILLSEND_YES << 4); // Will send if requested
 			else
 				filestatus += (WILLSEND_TOOLARGE << 4); // Won't send, too big
@@ -849,7 +849,7 @@ static boolean AddFileToSendQueue(INT32 node, UINT8 fileid)
 	strlcpy(p->id.filename, wadfiles[wadnum]->filename, MAX_WADPATH);
 
 	// Handle huge file requests (i.e. bigger than cv_maxsend.value KB)
-	if (wadfiles[wadnum]->filesize > (UINT32)cv_maxsend.value * 1024)
+	if (cv_maxsend.value != -1 && wadfiles[wadnum]->filesize > (UINT32)cv_maxsend.value * 1024)
 	{
 		// Too big
 		// Don't inform client (client sucks, man)
diff --git a/src/netcode/i_net.h b/src/netcode/i_net.h
index a2039bd1902197d1c812f7490fad3e1493f60588..11da77ce52b0b88035d2058372f2b1fe4ea638ee 100644
--- a/src/netcode/i_net.h
+++ b/src/netcode/i_net.h
@@ -44,15 +44,6 @@ typedef struct
 	/// Number of bytes in doomdata to be sent
 	INT16 datalength;
 
-	/// Info common to all nodes.
-	/// Console is always node 0.
-	INT16 numnodes;
-	/// Flag: 1 = send a backup tic in every packet.
-	INT16 extratics;
-
-	/// Number of "slots": the highest player number in use plus one.
-	INT16 numslots;
-
 	/// The packet data to be sent.
 	char data[MAXPACKETLENGTH];
 } ATTRPACK doomcom_t;
@@ -61,6 +52,18 @@ typedef struct
 #pragma pack()
 #endif
 
+/** \brief Number of connected nodes.
+*/
+extern INT16 numnetnodes;
+
+/** \brief Number of "slots": the highest player number in use plus one.
+*/
+extern INT16 numslots;
+
+/** \brief Flag: 1 = send a backup tic in every packet.
+*/
+extern INT16 extratics;
+
 extern doomcom_t *doomcom;
 
 /**	\brief return packet in doomcom struct
diff --git a/src/netcode/i_tcp.c b/src/netcode/i_tcp.c
index 38d4bbbaa2f739bcd55d236e350997ecddd1d7d4..eb4957e0466ccb0c8f8030a680895309ece8fb9d 100644
--- a/src/netcode/i_tcp.c
+++ b/src/netcode/i_tcp.c
@@ -1105,7 +1105,7 @@ static boolean UDP_Socket(void)
 
 	broadcastaddresses = s;
 
-	doomcom->extratics = 1; // internet is very high ping
+	extratics = 1; // internet is very high ping
 
 	return true;
 }
@@ -1393,18 +1393,18 @@ boolean I_InitTcpNetwork(void)
 		// in-game.
 		// Since Boris has implemented join in-game, there is no actual need for specifying a
 		// particular number here.
-		// FIXME: for dedicated server, numnodes needs to be set to 0 upon start
+		// FIXME: for dedicated server, numnetnodes needs to be set to 0 upon start
 		if (dedicated)
-			doomcom->numnodes = 0;
+			numnetnodes = 0;
 /*		else if (M_IsNextParm())
-			doomcom->numnodes = (INT16)atoi(M_GetNextParm());*/
+			numnetnodes = (INT16)atoi(M_GetNextParm());*/
 		else
-			doomcom->numnodes = 1;
+			numnetnodes = 1;
 
-		if (doomcom->numnodes < 0)
-			doomcom->numnodes = 0;
-		if (doomcom->numnodes > MAXNETNODES)
-			doomcom->numnodes = MAXNETNODES;
+		if (numnetnodes < 0)
+			numnetnodes = 0;
+		if (numnetnodes > MAXNETNODES)
+			numnetnodes = MAXNETNODES;
 
 		// server
 		servernode = 0;
diff --git a/src/netcode/net_command.c b/src/netcode/net_command.c
index 2b3abfd0238a3246fedc086afc007a85c993b425..d319e336f1bb52647cd242d3f4d1dc131e8bf902 100644
--- a/src/netcode/net_command.c
+++ b/src/netcode/net_command.c
@@ -269,10 +269,10 @@ void PT_TextCmd(SINT8 node, INT32 netconsole)
 		}
 
 		// 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
+		// numslots+1 "+1" since numslots can change within this time and sent time
 		j = software_MAXPACKETLENGTH
 			- (netbuffer->u.textcmd[0]+2+BASESERVERTICSSIZE
-			+ (doomcom->numslots+1)*sizeof(ticcmd_t));
+			+ (numslots+1)*sizeof(ticcmd_t));
 
 		// search a tic that have enougth space in the ticcmd
 		while ((textcmd = D_GetExistingTextcmd(tic, netconsole)),
diff --git a/src/netcode/server_connection.c b/src/netcode/server_connection.c
index f4601e4acac08a187b982f8e286e9e53d46b2396..4dfcf400da2a9dbafb7fa8a9164de72986fe4cf3 100644
--- a/src/netcode/server_connection.c
+++ b/src/netcode/server_connection.c
@@ -245,7 +245,7 @@ static boolean SV_SendServerConfig(INT32 node)
 	netbuffer->packettype = PT_SERVERCFG;
 
 	netbuffer->u.servercfg.serverplayer = (UINT8)serverplayer;
-	netbuffer->u.servercfg.totalslotnum = (UINT8)(doomcom->numslots);
+	netbuffer->u.servercfg.totalslotnum = (UINT8)numslots;
 	netbuffer->u.servercfg.gametic = (tic_t)LONG(gametic);
 	netbuffer->u.servercfg.clientnode = (UINT8)node;
 	netbuffer->u.servercfg.gamestate = (UINT8)gamestate;
diff --git a/src/netcode/tic_command.c b/src/netcode/tic_command.c
index 7721bc3f1b1149790ce4de35ba1f1a7aa69f00c5..66f6bb4252bb5a040700380ec73cb1c03786660b 100644
--- a/src/netcode/tic_command.c
+++ b/src/netcode/tic_command.c
@@ -244,7 +244,7 @@ void PT_ServerTics(SINT8 node, INT32 netconsole)
 	if (realstart <= neededtic && realend > neededtic)
 	{
 		UINT8 *pak = (UINT8 *)&packet->cmds;
-		UINT8 *txtpak = (UINT8 *)&packet->cmds[packet->numslots * packet->numtics];
+		UINT8 *txtpak = (UINT8 *)&packet->cmds[numslots * packet->numtics];
 
 		for (tic_t i = realstart; i < realend; i++)
 		{
@@ -253,7 +253,7 @@ void PT_ServerTics(SINT8 node, INT32 netconsole)
 
 			// copy the tics
 			pak = G_ScpyTiccmd(netcmds[i%BACKUPTICS], pak,
-				packet->numslots*sizeof (ticcmd_t));
+				numslots*sizeof (ticcmd_t));
 
 			CL_CopyNetCommandsFromServerPacket(i, &txtpak);
 		}
@@ -320,7 +320,7 @@ static tic_t SV_CalculateNumTicsForPacket(SINT8 nodenum, tic_t firsttic, tic_t l
 
 	for (tic_t tic = firsttic; tic < lasttic; tic++)
 	{
-		size += sizeof (ticcmd_t) * doomcom->numslots;
+		size += sizeof (ticcmd_t) * numslots;
 		size += TotalTextCmdPerTic(tic);
 
 		if (size > software_MAXPACKETLENGTH)
@@ -337,7 +337,7 @@ static tic_t SV_CalculateNumTicsForPacket(SINT8 nodenum, tic_t firsttic, tic_t l
 				if (size > MAXPACKETLENGTH)
 					I_Error("Too many players: can't send %s data for %d players to node %d\n"
 							"Well sorry nobody is perfect....\n",
-							sizeu1(size), doomcom->numslots, nodenum);
+							sizeu1(size), numslots, nodenum);
 				else
 				{
 					lasttic++; // send it anyway!
@@ -394,20 +394,20 @@ void SV_SendTics(void)
 			netbuffer->packettype = PT_SERVERTICS;
 			netbuffer->u.serverpak.starttic = realfirsttic;
 			netbuffer->u.serverpak.numtics = (UINT8)(lasttictosend - realfirsttic);
-			netbuffer->u.serverpak.numslots = (UINT8)SHORT(doomcom->numslots);
+			netbuffer->u.serverpak.numslots = (UINT8)SHORT(numslots);
 
 			// Fill and send the packet
 			UINT8 *bufpos = (UINT8 *)&netbuffer->u.serverpak.cmds;
 			for (tic_t i = realfirsttic; i < lasttictosend; i++)
-				bufpos = G_DcpyTiccmd(bufpos, netcmds[i%BACKUPTICS], doomcom->numslots * sizeof (ticcmd_t));
+				bufpos = G_DcpyTiccmd(bufpos, netcmds[i%BACKUPTICS], numslots * sizeof (ticcmd_t));
 			for (tic_t i = realfirsttic; i < lasttictosend; i++)
 				SV_WriteNetCommandsForTic(i, &bufpos);
 			size_t packsize = bufpos - (UINT8 *)&(netbuffer->u);
 			HSendPacket(n, false, 0, packsize);
 
 			// When tics are too large, only one tic is sent so don't go backwards!
-			if (lasttictosend-doomcom->extratics > realfirsttic)
-				node->supposedtic = lasttictosend-doomcom->extratics;
+			if (lasttictosend-extratics > realfirsttic)
+				node->supposedtic = lasttictosend-extratics;
 			else
 				node->supposedtic = lasttictosend;
 			node->supposedtic = max(node->supposedtic, node->tic);
diff --git a/src/p_mobj.c b/src/p_mobj.c
index 259bf19d492ac3a33f428b9767dac100b745c4b3..e758563f4180f06782d71569e66da540073a997f 100644
--- a/src/p_mobj.c
+++ b/src/p_mobj.c
@@ -7013,14 +7013,6 @@ static void P_MobjScaleThink(mobj_t *mobj)
 		mobj->z -= (mobj->height - oldheight)/2;
 	else if (correctionType == 2)
 		mobj->z -= mobj->height - oldheight;
-
-	if (mobj->scale == mobj->destscale)
-		/// \todo Lua hook for "reached destscale"?
-		switch (mobj->type)
-		{
-		default:
-			break;
-		}
 }
 
 static void P_MaceSceneryThink(mobj_t *mobj)
@@ -10139,6 +10131,7 @@ static boolean P_FuseThink(mobj_t *mobj)
 //
 void P_MobjThinker(mobj_t *mobj)
 {
+	boolean ispushable;
 	I_Assert(mobj != NULL);
 	I_Assert(!P_MobjWasRemoved(mobj));
 
@@ -10164,8 +10157,10 @@ void P_MobjThinker(mobj_t *mobj)
 
 	tmfloorthing = tmhitthing = NULL;
 
+	ispushable = mobj->flags & MF_PUSHABLE || (mobj->info->flags & MF_PUSHABLE && mobj->fuse);
+
 	// Sector flag MSF_TRIGGERLINE_MOBJ allows ANY mobj to trigger a linedef exec
-	P_CheckMobjTrigger(mobj, false);
+	P_CheckMobjTrigger(mobj, ispushable);
 
 	if (mobj->scale != mobj->destscale)
 		P_MobjScaleThink(mobj); // Slowly scale up/down to reach your destscale.
@@ -10209,7 +10204,7 @@ void P_MobjThinker(mobj_t *mobj)
 
 	// if it's pushable, or if it would be pushable other than temporary disablement, use the
 	// separate thinker
-	if (mobj->flags & MF_PUSHABLE || (mobj->info->flags & MF_PUSHABLE && mobj->fuse))
+	if (ispushable)
 	{
 		if (!P_MobjPushableThink(mobj))
 			return;
@@ -10385,8 +10380,6 @@ void P_PushableThinker(mobj_t *mobj)
 	I_Assert(mobj != NULL);
 	I_Assert(!P_MobjWasRemoved(mobj));
 
-	P_CheckMobjTrigger(mobj, true);
-
 	// it has to be pushable RIGHT NOW for this part to happen
 	if (mobj->flags & MF_PUSHABLE && !(mobj->momx || mobj->momy))
 		P_TryMove(mobj, mobj->x, mobj->y, true);
diff --git a/src/p_saveg.c b/src/p_saveg.c
index 650622f59f8130a896257748eb56a4116e963f7e..0a49c680ac73cc77805c6a405c9e1df7f9b1d1e3 100644
--- a/src/p_saveg.c
+++ b/src/p_saveg.c
@@ -302,6 +302,18 @@ typedef enum
 	DRONE      = 0x80,
 } player_saveflags;
 
+static inline UINT32 SavePlayer(const player_t *player)
+{
+	if (player) return (UINT32)(player - players);
+	return 0xFFFFFFFF;
+}
+
+static inline player_t *LoadPlayer(UINT32 player)
+{
+	if (player >= MAXPLAYERS) return NULL;
+	return &players[player];
+}
+
 static inline void P_ArchivePlayer(save_t *save_p)
 {
 	const player_t *player = &players[consoleplayer];
@@ -441,6 +453,7 @@ static void P_NetArchivePlayers(save_t *save_p)
 		// Bots //
 		//////////
 		P_WriteUINT8(save_p, players[i].bot);
+		P_WriteUINT32(save_p, SavePlayer(players[i].botleader));
 		P_WriteUINT8(save_p, players[i].botmem.lastForward);
 		P_WriteUINT8(save_p, players[i].botmem.lastBlocked);
 		P_WriteUINT8(save_p, players[i].botmem.catchup_tics);
@@ -672,6 +685,7 @@ static void P_NetUnArchivePlayers(save_t *save_p)
 		// Bots //
 		//////////
 		players[i].bot = P_ReadUINT8(save_p);
+		players[i].botleader = LoadPlayer(P_ReadUINT32(save_p));
 
 		players[i].botmem.lastForward = P_ReadUINT8(save_p);
 		players[i].botmem.lastBlocked = P_ReadUINT8(save_p);
@@ -2106,12 +2120,6 @@ static UINT32 SaveLine(const line_t *line)
 	return 0xFFFFFFFF;
 }
 
-static inline UINT32 SavePlayer(const player_t *player)
-{
-	if (player) return (UINT32)(player - players);
-	return 0xFFFFFFFF;
-}
-
 static UINT32 SaveSlope(const pslope_t *slope)
 {
 	if (slope) return (UINT32)(slope->id);
@@ -3237,12 +3245,6 @@ static line_t *LoadLine(UINT32 line)
 	return &lines[line];
 }
 
-static inline player_t *LoadPlayer(UINT32 player)
-{
-	if (player >= MAXPLAYERS) return NULL;
-	return &players[player];
-}
-
 static inline pslope_t *LoadSlope(UINT32 slopeid)
 {
 	pslope_t *p = slopelist;
diff --git a/src/sdl/i_main.c b/src/sdl/i_main.c
index b2177e563120fc76cf726353e5cad841e45b1762..f5fe9fb041b9f02057955afe072815e709b19dc3 100644
--- a/src/sdl/i_main.c
+++ b/src/sdl/i_main.c
@@ -186,9 +186,6 @@ int main(int argc, char **argv)
 #endif
 #endif
 
-	// disable text input right off the bat, since we don't need it at the start.
-	I_SetTextInputMode(false);
-
 #ifdef LOGMESSAGES
 	if (!M_CheckParm("-nolog"))
 		InitLogging();
diff --git a/src/sdl/i_video.c b/src/sdl/i_video.c
index 82583ccb4651ff8cbfc42be4a0f40ee54bd31d7d..2d1029ed09e50dd3b1834e6f421868aef8d752c8 100644
--- a/src/sdl/i_video.c
+++ b/src/sdl/i_video.c
@@ -1857,6 +1857,9 @@ void I_StartupGraphics(void)
 	if (mousegrabok && !disable_mouse)
 		SDLdoGrabMouse();
 
+	// disable text input right off the bat, since we don't need it at the start.
+	I_SetTextInputMode(false);
+
 	graphics_started = true;
 }
 
diff --git a/src/version.h b/src/version.h
index 8e69194cd20b98debe50bc67afb264a0bb2aec9b..54cf92bf1ab3ea66f6d7d267df292ea03f532965 100644
--- a/src/version.h
+++ b/src/version.h
@@ -1,4 +1,4 @@
-#define SRB2VERSION "2.2.15"/* this must be the first line, for cmake !! */
+#define SRB2VERSION "2.2.16"/* this must be the first line, for cmake !! */
 
 // The Modification ID; must be obtained from a Master Server Admin ( https://mb.srb2.org/members/?key=ms_admin ).
 // DO NOT try to set this otherwise, or your modification will be unplayable through the Master Server.
@@ -9,7 +9,7 @@
 // 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.2.0 is not version "1".
-#define MODVERSION 56
+#define MODVERSION 57
 
 // Define this as a prerelease version suffix (pre#, RC#)
-//#define BETAVERSION "pre4"
+#define BETAVERSION "nightly"
diff --git a/src/win32/Srb2win.rc b/src/win32/Srb2win.rc
index 6fba9f9a8877e65fb99c91c2e24022926857beba..9fd8ea574b4866c9a060375926ec11a39372fd8f 100644
--- a/src/win32/Srb2win.rc
+++ b/src/win32/Srb2win.rc
@@ -77,8 +77,8 @@ END
 #include "../doomdef.h" // Needed for version string
 
 VS_VERSION_INFO VERSIONINFO
- FILEVERSION 2,2,14,0
- PRODUCTVERSION 2,2,14,0
+ FILEVERSION 2,2,16,0
+ PRODUCTVERSION 2,2,16,0
  FILEFLAGSMASK 0x3fL
 #ifdef _DEBUG
  FILEFLAGS 0x1L