diff --git a/src/d_player.h b/src/d_player.h
index 7ad5b9f811962cb56ffcf55030f2ff1d2aea331d..ca700d4e1f593c26423d3f0947a9dae1348fe903 100644
--- a/src/d_player.h
+++ b/src/d_player.h
@@ -607,6 +607,7 @@ typedef struct player_s
 
 	tic_t jointime; // Timer when player joins game to change skin/color
 	tic_t quittime; // Time elapsed since user disconnected, zero if connected
+	tic_t lastinputtime; // the last tic the player has made any input
 #ifdef HWRENDER
 	fixed_t fovadd; // adjust FOV for hw rendering
 #endif
diff --git a/src/lua_baselib.c b/src/lua_baselib.c
index 2642d950433c3916e67bb3a9264202f572d8a837..16f238abeb19575cb5ce0e626be4660e723727ca 100644
--- a/src/lua_baselib.c
+++ b/src/lua_baselib.c
@@ -3713,6 +3713,7 @@ static int lib_gAddPlayer(lua_State *L)
 
 	newplayer->jointime = 0;
 	newplayer->quittime = 0;
+	newplayer->lastinputtime = 0;
 
 	// Read the skin argument (defaults to Sonic)
 	if (!lua_isnoneornil(L, 1))
diff --git a/src/lua_playerlib.c b/src/lua_playerlib.c
index 66d9c009b110044f8654e3da4d24f53dfde74485..d12aa1c3fae8f5e1e0e9171118c21b03d1310350 100644
--- a/src/lua_playerlib.c
+++ b/src/lua_playerlib.c
@@ -223,6 +223,7 @@ enum player_e
 	player_blocked,
 	player_jointime,
 	player_quittime,
+	player_lastinputtime,
 	player_ping,
 #ifdef HWRENDER
 	player_fovadd,
@@ -371,6 +372,7 @@ static const char *const player_opt[] = {
 	"blocked",
 	"jointime",
 	"quittime",
+	"lastinputtime",
 	"ping",
 #ifdef HWRENDER
 	"fovadd",
@@ -826,6 +828,9 @@ static int player_get(lua_State *L)
 	case player_quittime:
 		lua_pushinteger(L, plr->quittime);
 		break;
+	case player_lastinputtime:
+		lua_pushinteger(L, plr->lastinputtime);
+		break;
 	case player_ping:
 		lua_pushinteger(L, playerpingtable[plr - players]);
 		break;
@@ -1349,6 +1354,9 @@ static int player_set(lua_State *L)
 	case player_quittime:
 		plr->quittime = (tic_t)luaL_checkinteger(L, 3);
 		break;
+	case player_lastinputtime:
+		plr->lastinputtime = (tic_t)luaL_checkinteger(L, 3);
+		break;
 #ifdef HWRENDER
 	case player_fovadd:
 		plr->fovadd = luaL_checkfixed(L, 3);
diff --git a/src/netcode/d_clisrv.c b/src/netcode/d_clisrv.c
index f06192f2c8500bc1a35600b43ed502e69a222400..7804b068f7f8d262bd7fab535539af82675bd639 100644
--- a/src/netcode/d_clisrv.c
+++ b/src/netcode/d_clisrv.c
@@ -113,6 +113,7 @@ consvar_t cv_blamecfail = CVAR_INIT ("blamecfail", "Off", CV_SAVE|CV_NETVAR, CV_
 static CV_PossibleValue_t playbackspeed_cons_t[] = {{1, "MIN"}, {10, "MAX"}, {0, NULL}};
 consvar_t cv_playbackspeed = CVAR_INIT ("playbackspeed", "1", 0, playbackspeed_cons_t, NULL);
 
+consvar_t cv_idletime = CVAR_INIT ("idletime", "0", CV_SAVE, CV_Unsigned, NULL);
 consvar_t cv_dedicatedidletime = CVAR_INIT ("dedicatedidletime", "10", CV_SAVE, CV_Unsigned, NULL);
 
 void ResetNode(INT32 node)
@@ -226,6 +227,7 @@ static void Got_AddPlayer(UINT8 **p, INT32 playernum)
 
 	newplayer->jointime = 0;
 	newplayer->quittime = 0;
+	newplayer->lastinputtime = 0;
 
 	READSTRINGN(*p, player_names[newplayernum], MAXPLAYERNAME);
 
@@ -492,6 +494,10 @@ static void Got_KickCmd(UINT8 **p, INT32 playernum)
 			HU_AddChatText(va("\x82*%s has been banned (%s)", player_names[pnum], reason), false);
 			kickreason = KR_BAN;
 			break;
+		case KICK_MSG_IDLE:
+			HU_AddChatText(va("\x82*%s has left the game (Inactive for too long)", player_names[pnum]), false);
+			kickreason = KR_TIMEOUT;
+			break;
 	}
 
 	if (pnum == consoleplayer)
@@ -507,6 +513,8 @@ static void Got_KickCmd(UINT8 **p, INT32 playernum)
 			M_StartMessage(M_GetText("Server closed connection\n(synch failure)\nPress ESC\n"), NULL, MM_NOTHING);
 		else if (msg == KICK_MSG_PING_HIGH)
 			M_StartMessage(M_GetText("Server closed connection\n(Broke ping limit)\nPress ESC\n"), NULL, MM_NOTHING);
+		else if (msg == KICK_MSG_IDLE)
+			M_StartMessage(M_GetText("Server closed connection\n(Inactive for too long)\nPress ESC\n"), NULL, MM_NOTHING);
 		else if (msg == KICK_MSG_BANNED)
 			M_StartMessage(M_GetText("You have been banned by the server\n\nPress ESC\n"), NULL, MM_NOTHING);
 		else if (msg == KICK_MSG_CUSTOM_KICK)
@@ -1267,6 +1275,32 @@ static void UpdatePingTable(void)
 	}
 }
 
+static void IdleUpdate(void)
+{
+	INT32 i;
+	if (!server || !netgame)
+		return;
+
+	for (i = 1; i < MAXPLAYERS; i++)
+	{
+		if (cv_idletime.value && playeringame[i] && playernode[i] != UINT8_MAX && !players[i].quittime && !players[i].spectator && !players[i].bot && !IsPlayerAdmin(i) && i != serverplayer)
+		{
+			if (players[i].cmd.forwardmove || players[i].cmd.sidemove || players[i].cmd.buttons)
+				players[i].lastinputtime = 0;
+			else
+				players[i].lastinputtime++;
+
+			if (players[i].lastinputtime > (tic_t)cv_idletime.value * TICRATE * 60)
+			{
+				players[i].lastinputtime = 0;
+				SendKick(i, KICK_MSG_IDLE | KICK_MSG_KEEP_BODY);
+			}
+		}
+		else
+			players[i].lastinputtime = 0;
+	}
+}
+
 // Handle timeouts to prevent definitive freezes from happenning
 static void HandleNodeTimeouts(void)
 {
@@ -1299,6 +1333,8 @@ void NetKeepAlive(void)
 
 	GetPackets();
 
+	IdleUpdate();
+
 #ifdef MASTERSERVER
 	MasterClient_Ticker();
 #endif
@@ -1419,6 +1455,8 @@ void NetUpdate(void)
 
 	GetPackets(); // get packet from client or from server
 
+	IdleUpdate();
+
 	// The client sends the command after receiving from the server
 	// The server sends it before because this is better in single player
 
diff --git a/src/netcode/d_clisrv.h b/src/netcode/d_clisrv.h
index d87ead9ecbf5e71f280f13f3598ada1a1592a524..db9a780cd915bf38065391d3b0030c09c60bda4e 100644
--- a/src/netcode/d_clisrv.h
+++ b/src/netcode/d_clisrv.h
@@ -48,7 +48,7 @@ typedef enum
 	KR_TIMEOUT       = 4, //Connection Timeout
 	KR_BAN           = 5, //Banned by server
 	KR_LEAVE         = 6, //Quit the game
-
+	KR_IDLE          = 7, //Remained still for too long
 } kickreason_t;
 
 /* the max number of name changes in some time period */
@@ -73,7 +73,7 @@ extern UINT32 realpingtable[MAXPLAYERS];
 extern UINT32 playerpingtable[MAXPLAYERS];
 extern tic_t servermaxping;
 
-extern consvar_t cv_netticbuffer, cv_resynchattempts, cv_blamecfail, cv_playbackspeed, cv_dedicatedidletime;
+extern consvar_t cv_netticbuffer, cv_resynchattempts, cv_blamecfail, cv_playbackspeed, cv_idletime, cv_dedicatedidletime;
 
 // Used in d_net, the only dependence
 void D_ClientServerInit(void);
diff --git a/src/netcode/d_netcmd.c b/src/netcode/d_netcmd.c
index 52df53b5a06cedd0f5be26cbe0b1a2ef2d0f4351..9b87e8cfe80ca9f02fadffa48199ab17cd83cd6b 100644
--- a/src/netcode/d_netcmd.c
+++ b/src/netcode/d_netcmd.c
@@ -601,6 +601,7 @@ void D_RegisterServerCommands(void)
 	CV_RegisterVar(&cv_showjoinaddress);
 	CV_RegisterVar(&cv_blamecfail);
 	CV_RegisterVar(&cv_dedicatedidletime);
+	CV_RegisterVar(&cv_idletime);
 
 	COM_AddCommand("ping", Command_Ping_f, COM_LUA);
 	CV_RegisterVar(&cv_nettimeout);
diff --git a/src/netcode/protocol.h b/src/netcode/protocol.h
index a992e3b69db1ac1eb981c15b24d41e4ffac156eb..c084d920cfa4dc14d3e0cf475b3c62ea7226020d 100644
--- a/src/netcode/protocol.h
+++ b/src/netcode/protocol.h
@@ -331,6 +331,7 @@ typedef struct
 #define KICK_MSG_PING_HIGH   6
 #define KICK_MSG_CUSTOM_KICK 7
 #define KICK_MSG_CUSTOM_BAN  8
+#define KICK_MSG_IDLE        9
 #define KICK_MSG_KEEP_BODY   0x80
 
 #endif