diff --git a/src/dehacked.c b/src/dehacked.c
index cf42e7c1fe62d3d151944bc44e32cfab7e63a96e..1a2f2e239828209a02b94336c9a47a300e4f72de 100644
--- a/src/dehacked.c
+++ b/src/dehacked.c
@@ -1203,6 +1203,8 @@ static void readlevelheader(MYFILE *f, INT32 num)
 					deh_warning("Level header %d: invalid bonus type number %d", num, i);
 			}
 
+			else if (fastcmp(word, "MAXBONUSLIVES"))
+				mapheaderinfo[num-1]->maxbonuslives = (SINT8)i;
 			else if (fastcmp(word, "LEVELFLAGS"))
 				mapheaderinfo[num-1]->levelflags = (UINT8)i;
 			else if (fastcmp(word, "MENUFLAGS"))
diff --git a/src/doomstat.h b/src/doomstat.h
index 24b9e5753d644c6c0cb7d3cc4d5f95ce143f0848..651540ecc833afc3ac975231f4ee0c63a781072d 100644
--- a/src/doomstat.h
+++ b/src/doomstat.h
@@ -244,6 +244,7 @@ typedef struct
 	SINT8 unlockrequired; ///< Is an unlockable required to play this level? -1 if no.
 	UINT8 levelselect;    ///< Is this map available in the level select? If so, which map list is it available in?
 	SINT8 bonustype;      ///< What type of bonus does this level have? (-1 for null.)
+	SINT8 maxbonuslives;  ///< How many bonus lives to award at Intermission? (-1 for unlimited.)
 
 	UINT8 levelflags;     ///< LF_flags:  merged eight booleans into one UINT8 for space, see below
 	UINT8 menuflags;      ///< LF2_flags: options that affect record attack / nights mode menus
diff --git a/src/lua_maplib.c b/src/lua_maplib.c
index b929fc0bc643c6ac72c581baf79eff67b0428b27..28fe8c75f7c0f955d51f18e02a60e92109a83072 100644
--- a/src/lua_maplib.c
+++ b/src/lua_maplib.c
@@ -1797,6 +1797,8 @@ static int mapheaderinfo_get(lua_State *L)
 		lua_pushinteger(L, header->levelselect);
 	else if (fastcmp(field,"bonustype"))
 		lua_pushinteger(L, header->bonustype);
+	else if (fastcmp(field,"maxbonuslives"))
+		lua_pushinteger(L, header->maxbonuslives);
 	else if (fastcmp(field,"levelflags"))
 		lua_pushinteger(L, header->levelflags);
 	else if (fastcmp(field,"menuflags"))
diff --git a/src/p_setup.c b/src/p_setup.c
index c62f281b3ee461eed4ccce49121c6a23d988bbb8..17ca730e235f479b34470e343d9a8c1716c982bb 100644
--- a/src/p_setup.c
+++ b/src/p_setup.c
@@ -225,6 +225,7 @@ static void P_ClearSingleMapHeaderInfo(INT16 i)
 	mapheaderinfo[num]->unlockrequired = -1;
 	mapheaderinfo[num]->levelselect = 0;
 	mapheaderinfo[num]->bonustype = 0;
+	mapheaderinfo[num]->maxbonuslives = -1;
 	mapheaderinfo[num]->levelflags = 0;
 	mapheaderinfo[num]->menuflags = 0;
 #if 1 // equivalent to "FlickyList = DEMO"
diff --git a/src/y_inter.c b/src/y_inter.c
index 966d84477d3c65278b8c4638f685d65e432302fd..e1fd14a790f51ab33b40f7590ecf247afe276f8c 100644
--- a/src/y_inter.c
+++ b/src/y_inter.c
@@ -1874,7 +1874,9 @@ static void Y_AwardCoopBonuses(void)
 				players[i].score = MAXSCORE;
 		}
 
-		ptlives = (!ultimatemode && !modeattacking && players[i].lives != 0x7f) ? max((players[i].score/50000) - (oldscore/50000), 0) : 0;
+		ptlives = min(
+			((!ultimatemode && !modeattacking && players[i].lives != 0x7f) ? max((players[i].score/50000) - (oldscore/50000), 0) : 0),
+			(mapheaderinfo[prevmap]->maxbonuslives < 0 ? INT32_MAX : mapheaderinfo[prevmap]->maxbonuslives));
 		if (ptlives)
 			P_GivePlayerLives(&players[i], ptlives);
 
@@ -1918,7 +1920,9 @@ static void Y_AwardSpecialStageBonus(void)
 			players[i].score = MAXSCORE;
 
 		// grant extra lives right away since tally is faked
-		ptlives = (!ultimatemode && !modeattacking && players[i].lives != 0x7f) ? max((players[i].score/50000) - (oldscore/50000), 0) : 0;
+		ptlives = min(
+			((!ultimatemode && !modeattacking && players[i].lives != 0x7f) ? max((players[i].score/50000) - (oldscore/50000), 0) : 0),
+			(mapheaderinfo[prevmap]->maxbonuslives < 0 ? INT32_MAX : mapheaderinfo[prevmap]->maxbonuslives));
 		if (ptlives)
 			P_GivePlayerLives(&players[i], ptlives);