From 67e438128435aca992e2d45aa1f35603c5501984 Mon Sep 17 00:00:00 2001
From: mazmazz <mar.marcoz@outlook.com>
Date: Mon, 26 Mar 2018 00:33:17 -0400
Subject: [PATCH 01/15] Fix NiGHTS drone loop detection by using pl->flyangle

---
 src/p_map.c | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/src/p_map.c b/src/p_map.c
index 6d1760596..2704478e1 100644
--- a/src/p_map.c
+++ b/src/p_map.c
@@ -917,14 +917,14 @@ static boolean PIT_CheckThing(mobj_t *thing)
 		// not (your direction) xor (stored direction)
 		// In other words, you can't u-turn and respawn rings near the drone.
 		if (pl->bonustime && (pl->powers[pw_carry] == CR_NIGHTSMODE) && (INT32)leveltime > droneobj->extravalue2 && (
-		   !(pl->anotherflyangle >= 90 &&   pl->anotherflyangle <= 270)
+		   !(pl->flyangle >= 90 &&   pl->flyangle <= 270)
 		^ (droneobj->extravalue1 >= 90 && droneobj->extravalue1 <= 270)
 		))
 		{
 			// Reload all the fancy ring stuff!
 			P_ReloadRings();
 		}
-		droneobj->extravalue1 = pl->anotherflyangle;
+		droneobj->extravalue1 = pl->flyangle;
 		droneobj->extravalue2 = (INT32)leveltime + TICRATE;
 	}
 

From ce215195f89bf52e5c2e2ddd7d5444685edf835d Mon Sep 17 00:00:00 2001
From: mazmazz <mar.marcoz@outlook.com>
Date: Mon, 26 Mar 2018 01:04:02 -0400
Subject: [PATCH 02/15] NiGHTS drone loop: Change flyangle comparison to fix
 detection from vertical angles

Use > 90 && < 270 instead of >= 90 && <= 270. Fixes a bug where if you fly directly up (flyangle 90) or directly down (flyangle 270), that registers as a backwards direction, so you trigger the loop detection by flying BACKWARDS, not FORWARDS. This edge case (only possible via JUMPTOAXIS) should default to FORWARDS looping.
---
 src/p_map.c | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/src/p_map.c b/src/p_map.c
index 2704478e1..630dd87ae 100644
--- a/src/p_map.c
+++ b/src/p_map.c
@@ -917,8 +917,8 @@ static boolean PIT_CheckThing(mobj_t *thing)
 		// not (your direction) xor (stored direction)
 		// In other words, you can't u-turn and respawn rings near the drone.
 		if (pl->bonustime && (pl->powers[pw_carry] == CR_NIGHTSMODE) && (INT32)leveltime > droneobj->extravalue2 && (
-		   !(pl->flyangle >= 90 &&   pl->flyangle <= 270)
-		^ (droneobj->extravalue1 >= 90 && droneobj->extravalue1 <= 270)
+		   !(pl->flyangle > 90 &&   pl->flyangle < 270)
+		^ (droneobj->extravalue1 > 90 && droneobj->extravalue1 < 270)
 		))
 		{
 			// Reload all the fancy ring stuff!

From 8d622ff6f8d428fd848c473566240402b748a487 Mon Sep 17 00:00:00 2001
From: Monster Iestyn <iestynjealous@ntlworld.com>
Date: Sun, 5 Aug 2018 20:17:30 +0100
Subject: [PATCH 03/15] Quick fix for LJ's password fix: don't check if
 password is set until we've confirmed that the receiving player is the
 server!

---
 src/d_netcmd.c | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/src/d_netcmd.c b/src/d_netcmd.c
index 727d5eff4..bf26ca61a 100644
--- a/src/d_netcmd.c
+++ b/src/d_netcmd.c
@@ -2730,15 +2730,15 @@ static void Got_Login(UINT8 **cp, INT32 playernum)
 
 	READMEM(*cp, sentmd5, 16);
 
+	if (client)
+		return;
+
 	if (!adminpasswordset)
 	{
 		CONS_Printf(M_GetText("Password from %s failed (no password set).\n"), player_names[playernum]);
 		return;
 	}
 
-	if (client)
-		return;
-
 	// Do the final pass to compare with the sent md5
 	D_MD5PasswordPass(adminpassmd5, 16, va("PNUM%02d", playernum), &finalmd5);
 

From 2738f3a537fc57aba5e40d529bc11222b0cf9e72 Mon Sep 17 00:00:00 2001
From: Monster Iestyn <iestynjealous@ntlworld.com>
Date: Sun, 5 Aug 2018 22:02:20 +0100
Subject: [PATCH 04/15] Rewrite archiving/unarchiving of Lua strings for
 netgames.

This now means:
* Lua strings longer than 1024 chars can now be read properly without awful crashes
* Lua strings with embedded zeros can be written/read without truncating anything (hopefully)
---
 src/lua_script.c | 32 ++++++++++++++++++++++++++++----
 1 file changed, 28 insertions(+), 4 deletions(-)

diff --git a/src/lua_script.c b/src/lua_script.c
index 67ce77c53..5561094ad 100644
--- a/src/lua_script.c
+++ b/src/lua_script.c
@@ -526,9 +526,23 @@ static UINT8 ArchiveValue(int TABLESINDEX, int myindex)
 		break;
 	}
 	case LUA_TSTRING:
+	{
+		UINT16 len = (UINT16)lua_objlen(gL, myindex); // get length of string, including embedded zeros
+		const char *s = lua_tostring(gL, myindex);
+		UINT16 i = 0;
 		WRITEUINT8(save_p, ARCH_STRING);
-		WRITESTRING(save_p, lua_tostring(gL, myindex));
+		// if you're wondering why we're writing a string to save_p this way,
+		// it turns out that Lua can have embedded zeros ('\0') in the strings,
+		// so we can't use WRITESTRING as that cuts off when it finds a '\0'.
+		// Saving the size of the string also allows us to get the size of the string on the other end,
+		// fixing the awful crashes previously encountered for reading strings longer than 1024
+		// (yes I know that's kind of a stupid thing to care about, but it'd be evil to trim or ignore them?)
+		// -- Monster Iestyn 05/08/18
+		WRITEUINT16(save_p, len); // save size of string
+		while (i < len)
+			WRITECHAR(save_p, s[i++]); // write chars individually, including the embedded zeros
 		break;
+	}
 	case LUA_TTABLE:
 	{
 		boolean found = false;
@@ -809,9 +823,19 @@ static UINT8 UnArchiveValue(int TABLESINDEX)
 		break;
 	case ARCH_STRING:
 	{
-		char value[1024];
-		READSTRING(save_p, value);
-		lua_pushstring(gL, value);
+		UINT16 len = READUINT16(save_p); // length of string, including embedded zeros
+		char *value;
+		UINT16 i = 0;
+		// See my comments in the ArchiveValue function;
+		// it's much the same for reading strings as writing them!
+		// (i.e. we can't use READSTRING either)
+		// -- Monster Iestyn 05/08/18
+		value = malloc(len); // make temp buffer of size len
+		// now read the actual string
+		while (i < len)
+			value[i++] = READCHAR(save_p); // read chars individually, including the embedded zeros
+		lua_pushlstring(gL, value, len); // push the string (note: this function supports embedded zeros)
+		free(value); // free the buffer
 		break;
 	}
 	case ARCH_TABLE:

From ecc9ebe8c1f88da4b67a546fc2f4eab8089d6573 Mon Sep 17 00:00:00 2001
From: toaster <rollerorbital@gmail.com>
Date: Tue, 7 Aug 2018 19:12:10 +0100
Subject: [PATCH 05/15] Change the order of operations when applying
 transparency and colormap such that colormap isn't applied to the screen
 pixel twice (or, in the case of R_DrawTranslatedTranslucentColumn_8, thrice).

Please note I haven't touched the ASM equivalent, given as it's not actually used.
---
 src/r_draw8.c | 37 +++++++++++++++++--------------------
 1 file changed, 17 insertions(+), 20 deletions(-)

diff --git a/src/r_draw8.c b/src/r_draw8.c
index 39585f587..9240cc3f1 100644
--- a/src/r_draw8.c
+++ b/src/r_draw8.c
@@ -297,7 +297,7 @@ void R_DrawTranslucentColumn_8(void)
 				// Re-map color indices from wall texture column
 				// using a lighting/special effects LUT.
 				// heightmask is the Tutti-Frutti fix
-				*dest = colormap[*(transmap + (source[frac>>FRACBITS]<<8) + (*dest))];
+				*dest = *(transmap + (colormap[source[frac>>FRACBITS]]<<8) + (*dest));
 				dest += vid.width;
 				if ((frac += fracstep) >= heightmask)
 					frac -= heightmask;
@@ -308,15 +308,15 @@ void R_DrawTranslucentColumn_8(void)
 		{
 			while ((count -= 2) >= 0) // texture height is a power of 2
 			{
-				*dest = colormap[*(transmap + ((source[(frac>>FRACBITS)&heightmask]<<8)) + (*dest))];
+				*dest = *(transmap + (colormap[source[(frac>>FRACBITS)&heightmask]]<<8) + (*dest));
 				dest += vid.width;
 				frac += fracstep;
-				*dest = colormap[*(transmap + ((source[(frac>>FRACBITS)&heightmask]<<8)) + (*dest))];
+				*dest = *(transmap + (colormap[source[(frac>>FRACBITS)&heightmask]]<<8) + (*dest));
 				dest += vid.width;
 				frac += fracstep;
 			}
 			if (count & 1)
-				*dest = colormap[*(transmap + ((source[(frac>>FRACBITS)&heightmask]<<8)) + (*dest))];
+				*dest = *(transmap + (colormap[source[(frac>>FRACBITS)&heightmask]]<<8) + (*dest));
 		}
 	}
 }
@@ -367,8 +367,7 @@ void R_DrawTranslatedTranslucentColumn_8(void)
 				//  using a lighting/special effects LUT.
 				// heightmask is the Tutti-Frutti fix
 
-				*dest = dc_colormap[*(dc_transmap
-					+ (dc_colormap[dc_translation[dc_source[frac>>FRACBITS]]]<<8) + (*dest))];
+				*dest = *(dc_transmap + (dc_colormap[dc_translation[dc_source[frac>>FRACBITS]]]<<8) + (*dest));
 
 				dest += vid.width;
 				if ((frac += fracstep) >= heightmask)
@@ -380,17 +379,15 @@ void R_DrawTranslatedTranslucentColumn_8(void)
 		{
 			while ((count -= 2) >= 0) // texture height is a power of 2
 			{
-				*dest = dc_colormap[*(dc_transmap
-					+ (dc_colormap[dc_translation[dc_source[frac>>FRACBITS]]]<<8) + (*dest))];
+				*dest = *(dc_transmap + (dc_colormap[dc_translation[dc_source[(frac>>FRACBITS)&heightmask]]]<<8) + (*dest));
 				dest += vid.width;
 				frac += fracstep;
-				*dest = dc_colormap[*(dc_transmap
-					+ (dc_colormap[dc_translation[dc_source[frac>>FRACBITS]]]<<8) + (*dest))];
+				*dest = *(dc_transmap + (dc_colormap[dc_translation[dc_source[(frac>>FRACBITS)&heightmask]]]<<8) + (*dest));
 				dest += vid.width;
 				frac += fracstep;
 			}
 			if (count & 1)
-				*dest = dc_colormap[*(dc_transmap + (dc_colormap[dc_translation[dc_source[frac>>FRACBITS]]] <<8) + (*dest))];
+				*dest = *(dc_transmap + (dc_colormap[dc_translation[dc_source[(frac>>FRACBITS)&heightmask]]]<<8) + (*dest));
 		}
 	}
 }
@@ -1220,35 +1217,35 @@ void R_DrawTranslucentSpan_8 (void)
 		// SoM: Why didn't I see this earlier? the spot variable is a waste now because we don't
 		// have the uber complicated math to calculate it now, so that was a memory write we didn't
 		// need!
-		dest[0] = colormap[*(ds_transmap + (source[((yposition >> nflatyshift) & nflatmask) | (xposition >> nflatxshift)] << 8) + dest[0])];
+		dest[0] = *(ds_transmap + (colormap[source[((yposition >> nflatyshift) & nflatmask) | (xposition >> nflatxshift)]] << 8) + dest[0]);
 		xposition += xstep;
 		yposition += ystep;
 
-		dest[1] = colormap[*(ds_transmap + (source[((yposition >> nflatyshift) & nflatmask) | (xposition >> nflatxshift)] << 8) + dest[1])];
+		dest[1] = *(ds_transmap + (colormap[source[((yposition >> nflatyshift) & nflatmask) | (xposition >> nflatxshift)]] << 8) + dest[1]);
 		xposition += xstep;
 		yposition += ystep;
 
-		dest[2] = colormap[*(ds_transmap + (source[((yposition >> nflatyshift) & nflatmask) | (xposition >> nflatxshift)] << 8) + dest[2])];
+		dest[2] = *(ds_transmap + (colormap[source[((yposition >> nflatyshift) & nflatmask) | (xposition >> nflatxshift)]] << 8) + dest[2]);
 		xposition += xstep;
 		yposition += ystep;
 
-		dest[3] = colormap[*(ds_transmap + (source[((yposition >> nflatyshift) & nflatmask) | (xposition >> nflatxshift)] << 8) + dest[3])];
+		dest[3] = *(ds_transmap + (colormap[source[((yposition >> nflatyshift) & nflatmask) | (xposition >> nflatxshift)]] << 8) + dest[3]);
 		xposition += xstep;
 		yposition += ystep;
 
-		dest[4] = colormap[*(ds_transmap + (source[((yposition >> nflatyshift) & nflatmask) | (xposition >> nflatxshift)] << 8) + dest[4])];
+		dest[4] = *(ds_transmap + (colormap[source[((yposition >> nflatyshift) & nflatmask) | (xposition >> nflatxshift)]] << 8) + dest[4]);
 		xposition += xstep;
 		yposition += ystep;
 
-		dest[5] = colormap[*(ds_transmap + (source[((yposition >> nflatyshift) & nflatmask) | (xposition >> nflatxshift)] << 8) + dest[5])];
+		dest[5] = *(ds_transmap + (colormap[source[((yposition >> nflatyshift) & nflatmask) | (xposition >> nflatxshift)]] << 8) + dest[5]);
 		xposition += xstep;
 		yposition += ystep;
 
-		dest[6] = colormap[*(ds_transmap + (source[((yposition >> nflatyshift) & nflatmask) | (xposition >> nflatxshift)] << 8) + dest[6])];
+		dest[6] = *(ds_transmap + (colormap[source[((yposition >> nflatyshift) & nflatmask) | (xposition >> nflatxshift)]] << 8) + dest[6]);
 		xposition += xstep;
 		yposition += ystep;
 
-		dest[7] = colormap[*(ds_transmap + (source[((yposition >> nflatyshift) & nflatmask) | (xposition >> nflatxshift)] << 8) + dest[7])];
+		dest[7] = *(ds_transmap + (colormap[source[((yposition >> nflatyshift) & nflatmask) | (xposition >> nflatxshift)]] << 8) + dest[7]);
 		xposition += xstep;
 		yposition += ystep;
 
@@ -1257,7 +1254,7 @@ void R_DrawTranslucentSpan_8 (void)
 	}
 	while (count--)
 	{
-		*dest = colormap[*(ds_transmap + (source[((yposition >> nflatyshift) & nflatmask) | (xposition >> nflatxshift)] << 8) + *dest)];
+		*dest = *(ds_transmap + (colormap[source[((yposition >> nflatyshift) & nflatmask) | (xposition >> nflatxshift)]] << 8) + *dest);
 		dest++;
 		xposition += xstep;
 		yposition += ystep;

From 5daeaf529fc26fac71e36fb821bbd9eea3c99bd7 Mon Sep 17 00:00:00 2001
From: toaster <rollerorbital@gmail.com>
Date: Thu, 9 Aug 2018 16:56:43 +0100
Subject: [PATCH 06/15] Apply the double-colormap ordering fix to
 R_DrawTiltedTranslucentSpan_8 as well.

---
 src/r_draw8.c | 9 ++++-----
 1 file changed, 4 insertions(+), 5 deletions(-)

diff --git a/src/r_draw8.c b/src/r_draw8.c
index 9240cc3f1..d4aaf5cf9 100644
--- a/src/r_draw8.c
+++ b/src/r_draw8.c
@@ -735,8 +735,7 @@ void R_DrawTiltedTranslucentSpan_8(void)
 		v = (INT64)(vz*z) + viewy;
 
 		colormap = planezlight[tiltlighting[ds_x1++]] + (ds_colormap - colormaps);
-
-		*dest = colormap[*(ds_transmap + (source[((v >> nflatyshift) & nflatmask) | (u >> nflatxshift)] << 8) + dest[0])];
+		*dest = *(ds_transmap + (colormap[source[((v >> nflatyshift) & nflatmask) | (u >> nflatxshift)]] << 8) + *dest);
 		dest++;
 		iz += ds_sz.x;
 		uz += ds_su.x;
@@ -773,7 +772,7 @@ void R_DrawTiltedTranslucentSpan_8(void)
 		for (i = SPANSIZE-1; i >= 0; i--)
 		{
 			colormap = planezlight[tiltlighting[ds_x1++]] + (ds_colormap - colormaps);
-			*dest = colormap[*(ds_transmap + (source[((v >> nflatyshift) & nflatmask) | (u >> nflatxshift)] << 8) + dest[0])];
+			*dest = *(ds_transmap + (colormap[source[((v >> nflatyshift) & nflatmask) | (u >> nflatxshift)]] << 8) + *dest);
 			dest++;
 			u += stepu;
 			v += stepv;
@@ -789,7 +788,7 @@ void R_DrawTiltedTranslucentSpan_8(void)
 			u = (INT64)(startu);
 			v = (INT64)(startv);
 			colormap = planezlight[tiltlighting[ds_x1++]] + (ds_colormap - colormaps);
-			*dest = colormap[*(ds_transmap + (source[((v >> nflatyshift) & nflatmask) | (u >> nflatxshift)] << 8) + dest[0])];
+			*dest = *(ds_transmap + (colormap[source[((v >> nflatyshift) & nflatmask) | (u >> nflatxshift)]] << 8) + *dest);
 		}
 		else
 		{
@@ -810,7 +809,7 @@ void R_DrawTiltedTranslucentSpan_8(void)
 			for (; width != 0; width--)
 			{
 				colormap = planezlight[tiltlighting[ds_x1++]] + (ds_colormap - colormaps);
-				*dest = colormap[*(ds_transmap + (source[((v >> nflatyshift) & nflatmask) | (u >> nflatxshift)] << 8) + dest[0])];
+				*dest = *(ds_transmap + (colormap[source[((v >> nflatyshift) & nflatmask) | (u >> nflatxshift)]] << 8) + *dest);
 				dest++;
 				u += stepu;
 				v += stepv;

From 145c050e14e55f1053d8a33b92e1b7792398944d Mon Sep 17 00:00:00 2001
From: toaster <rollerorbital@gmail.com>
Date: Thu, 9 Aug 2018 17:08:20 +0100
Subject: [PATCH 07/15] ...and R_DrawTranslucentSplat_8, even though it isn't
 used!

---
 src/r_draw8.c | 18 +++++++++---------
 1 file changed, 9 insertions(+), 9 deletions(-)

diff --git a/src/r_draw8.c b/src/r_draw8.c
index d4aaf5cf9..800f28b6b 100644
--- a/src/r_draw8.c
+++ b/src/r_draw8.c
@@ -1120,49 +1120,49 @@ void R_DrawTranslucentSplat_8 (void)
 		// need!
 		val = source[((yposition >> nflatyshift) & nflatmask) | (xposition >> nflatxshift)];
 		if (val != TRANSPARENTPIXEL)
-			dest[0] = colormap[*(ds_transmap + (val << 8) + dest[0])];
+			dest[0] = *(ds_transmap + (colormap[val] << 8) + dest[0]);
 		xposition += xstep;
 		yposition += ystep;
 
 		val = source[((yposition >> nflatyshift) & nflatmask) | (xposition >> nflatxshift)];
 		if (val != TRANSPARENTPIXEL)
-			dest[1] = colormap[*(ds_transmap + (val << 8) + dest[1])];
+			dest[1] = *(ds_transmap + (colormap[val] << 8) + dest[1]);
 		xposition += xstep;
 		yposition += ystep;
 
 		val = source[((yposition >> nflatyshift) & nflatmask) | (xposition >> nflatxshift)];
 		if (val != TRANSPARENTPIXEL)
-			dest[2] = colormap[*(ds_transmap + (val << 8) + dest[2])];
+			dest[2] = *(ds_transmap + (colormap[val] << 8) + dest[2]);
 		xposition += xstep;
 		yposition += ystep;
 
 		val = source[((yposition >> nflatyshift) & nflatmask) | (xposition >> nflatxshift)];
 		if (val != TRANSPARENTPIXEL)
-			dest[3] = colormap[*(ds_transmap + (val << 8) + dest[3])];
+			dest[3] = *(ds_transmap + (colormap[val] << 8) + dest[3]);
 		xposition += xstep;
 		yposition += ystep;
 
 		val = source[((yposition >> nflatyshift) & nflatmask) | (xposition >> nflatxshift)];
 		if (val != TRANSPARENTPIXEL)
-			dest[4] = colormap[*(ds_transmap + (val << 8) + dest[4])];
+			dest[4] = *(ds_transmap + (colormap[val] << 8) + dest[4]);
 		xposition += xstep;
 		yposition += ystep;
 
 		val = source[((yposition >> nflatyshift) & nflatmask) | (xposition >> nflatxshift)];
 		if (val != TRANSPARENTPIXEL)
-			dest[5] = colormap[*(ds_transmap + (val << 8) + dest[5])];
+			dest[5] = *(ds_transmap + (colormap[val] << 8) + dest[5]);
 		xposition += xstep;
 		yposition += ystep;
 
 		val = source[((yposition >> nflatyshift) & nflatmask) | (xposition >> nflatxshift)];
 		if (val != TRANSPARENTPIXEL)
-			dest[6] = colormap[*(ds_transmap + (val << 8) + dest[6])];
+			dest[6] = *(ds_transmap + (colormap[val] << 8) + dest[6]);
 		xposition += xstep;
 		yposition += ystep;
 
 		val = source[((yposition >> nflatyshift) & nflatmask) | (xposition >> nflatxshift)];
 		if (val != TRANSPARENTPIXEL)
-			dest[7] = colormap[*(ds_transmap + (val << 8) + dest[7])];
+			dest[7] = *(ds_transmap + (colormap[val] << 8) + dest[7]);
 		xposition += xstep;
 		yposition += ystep;
 
@@ -1173,7 +1173,7 @@ void R_DrawTranslucentSplat_8 (void)
 	{
 		val = source[((yposition >> nflatyshift) & nflatmask) | (xposition >> nflatxshift)];
 		if (val != TRANSPARENTPIXEL)
-			*dest = colormap[*(ds_transmap + (val << 8) + *dest)];
+			*dest = *(ds_transmap + (colormap[val] << 8) + *dest);
 
 		dest++;
 		xposition += xstep;

From fe616784375f41dea4b420f95cd788c293f5b2d3 Mon Sep 17 00:00:00 2001
From: mazmazz <mar.marcoz@outlook.com>
Date: Fri, 10 Aug 2018 00:19:56 -0400
Subject: [PATCH 08/15] MT_FLINGBLUESPHERE and MT_FLINGNIGHTSCHIP entries

---
 src/dehacked.c |  2 ++
 src/info.c     | 58 ++++++++++++++++++++++++++++++++++++++++++++++++--
 src/info.h     |  2 ++
 3 files changed, 60 insertions(+), 2 deletions(-)

diff --git a/src/dehacked.c b/src/dehacked.c
index c4d0bc104..fb0f958c3 100644
--- a/src/dehacked.c
+++ b/src/dehacked.c
@@ -6369,6 +6369,7 @@ static const char *const MOBJTYPE_LIST[] = {  // array length left dynamic for s
 	"MT_RING",
 	"MT_FLINGRING", // Lost ring
 	"MT_BLUESPHERE",  // Blue sphere for special stages
+	"MT_FLINGBLUESPHERE", // Lost blue sphere
 	"MT_BOMBSPHERE",
 	"MT_REDTEAMRING",  //Rings collectable by red team.
 	"MT_BLUETEAMRING", //Rings collectable by blue team.
@@ -6833,6 +6834,7 @@ static const char *const MOBJTYPE_LIST[] = {  // array length left dynamic for s
 	"MT_HOOPCENTER", // Center of a hoop
 	"MT_NIGHTSCORE",
 	"MT_NIGHTSCHIP", // NiGHTS Chip
+	"MT_FLINGNIGHTSCHIP", // Lost NiGHTS Chip
 	"MT_NIGHTSSTAR", // NiGHTS Star
 	"MT_NIGHTSSUPERLOOP",
 	"MT_NIGHTSDRILLREFILL",
diff --git a/src/info.c b/src/info.c
index 1489917b7..782ab6381 100644
--- a/src/info.c
+++ b/src/info.c
@@ -5783,7 +5783,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		1000,           // spawnhealth
 		S_NULL,         // seestate
 		sfx_None,       // seesound
-		MT_NULL,        // reactiontime
+		MT_FLINGBLUESPHERE,        // reactiontime
 		sfx_None,       // attacksound
 		S_NULL,         // painstate
 		0,              // painchance
@@ -5804,6 +5804,33 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		S_BLUESPHEREBONUS // raisestate
 	},
 
+	{           // MT_FLINGBLUESPHERE
+		-1,             // doomednum
+		S_BLUESPHERE,         // spawnstate
+		1000,           // spawnhealth
+		S_NULL,         // seestate
+		sfx_None,       // seesound
+		MT_FLINGBLUESPHERE,   // reactiontime
+		sfx_None,       // attacksound
+		S_NULL,         // painstate
+		MT_BLUESPHERE,        // painchance
+		sfx_None,       // painsound
+		S_NULL,         // meleestate
+		S_NULL,         // missilestate
+		S_BLUESPHERESPARK, // deathstate
+		S_NULL,         // xdeathstate
+		sfx_s3k65,     // deathsound
+		38*FRACUNIT,    // speed
+		16*FRACUNIT,    // radius
+		24*FRACUNIT,    // height
+		0,              // display offset
+		100,            // mass
+		0,              // damage
+		sfx_None,       // activesound
+		MF_SLIDEME|MF_SPECIAL, // flags
+		S_BLUESPHEREBONUS // raisestate
+	},
+
 	{           // MT_BOMBSPHERE
 		520,            // doomednum
 		S_BOMBSPHERE1,  // spawnstate
@@ -16320,7 +16347,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		1000,           // spawnhealth
 		S_NULL,         // seestate
 		sfx_None,       // seesound
-		8,              // reactiontime
+		MT_FLINGNIGHTSCHIP, // reactiontime
 		sfx_None,       // attacksound
 		S_NULL,         // painstate
 		0,              // painchance
@@ -16341,6 +16368,33 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		S_NIGHTSCHIPBONUS // raisestate
 	},
 
+	{           // MT_FLINGNIGHTSCHIP
+		-1,             // doomednum
+		S_NIGHTSCHIP,         // spawnstate
+		1000,           // spawnhealth
+		S_NULL,         // seestate
+		sfx_None,       // seesound
+		MT_FLINGNIGHTSCHIP,   // reactiontime
+		sfx_None,       // attacksound
+		S_NULL,         // painstate
+		MT_NIGHTSCHIP,        // painchance
+		sfx_None,       // painsound
+		S_NULL,         // meleestate
+		S_NULL,         // missilestate
+		S_SPRK1,        // deathstate
+		S_NULL,         // xdeathstate
+		sfx_ncchip,     // deathsound
+		38*FRACUNIT,    // speed
+		16*FRACUNIT,    // radius
+		24*FRACUNIT,    // height
+		0,              // display offset
+		100,            // mass
+		0,              // damage
+		sfx_None,       // activesound
+		MF_SLIDEME|MF_SPECIAL, // flags
+		S_NIGHTSCHIPBONUS // raisestate
+	},
+
 	{           // MT_NIGHTSSTAR
 		-1,             // doomednum
 		S_NIGHTSSTAR,   // spawnstate
diff --git a/src/info.h b/src/info.h
index c9f7299d8..dfd30bc5b 100644
--- a/src/info.h
+++ b/src/info.h
@@ -3742,6 +3742,7 @@ typedef enum mobj_type
 	MT_RING,
 	MT_FLINGRING, // Lost ring
 	MT_BLUESPHERE,  // Blue sphere for special stages
+	MT_FLINGBLUESPHERE, // Lost blue sphere
 	MT_BOMBSPHERE,
 	MT_REDTEAMRING,  //Rings collectable by red team.
 	MT_BLUETEAMRING, //Rings collectable by blue team.
@@ -4206,6 +4207,7 @@ typedef enum mobj_type
 	MT_HOOPCENTER, // Center of a hoop
 	MT_NIGHTSCORE,
 	MT_NIGHTSCHIP, // NiGHTS Chip
+	MT_FLINGNIGHTSCHIP, // Lost NiGHTS Chip
 	MT_NIGHTSSTAR, // NiGHTS Star
 	MT_NIGHTSSUPERLOOP,
 	MT_NIGHTSDRILLREFILL,

From e0f6dee8bef1e67f17c315676530ef785c892a22 Mon Sep 17 00:00:00 2001
From: mazmazz <mar.marcoz@outlook.com>
Date: Fri, 10 Aug 2018 00:20:30 -0400
Subject: [PATCH 09/15] MT_FLINGBLUESPHERE and MT_FLINGNIGHTSCHIP
 implementation

---
 src/p_inter.c |  7 +++++++
 src/p_mobj.c  | 10 ++++++++++
 src/p_setup.c |  3 ++-
 3 files changed, 19 insertions(+), 1 deletion(-)

diff --git a/src/p_inter.c b/src/p_inter.c
index 5737e2c2a..f908601bb 100644
--- a/src/p_inter.c
+++ b/src/p_inter.c
@@ -490,7 +490,9 @@ void P_TouchSpecialThing(mobj_t *special, mobj_t *toucher, boolean heightcheck)
 				P_DoNightsScore(player);
 			break;
 		case MT_BLUESPHERE:
+		case MT_FLINGBLUESPHERE:
 		case MT_NIGHTSCHIP:
+		case MT_FLINGNIGHTSCHIP:
 			if (!(P_CanPickupItem(player, false)) && !(special->flags2 & MF2_NIGHTSPULL))
 				return;
 
@@ -3373,6 +3375,7 @@ void P_PlayerRingBurst(player_t *player, INT32 num_rings)
 	angle_t fa;
 	fixed_t ns;
 	fixed_t z;
+	boolean nightsreplace = ((maptol & TOL_NIGHTS) && !G_IsSpecialStage(gamemap));
 
 	// Better safe than sorry.
 	if (!player)
@@ -3396,6 +3399,8 @@ void P_PlayerRingBurst(player_t *player, INT32 num_rings)
 		INT32 objType = mobjinfo[MT_RING].reactiontime;
 		if (mariomode)
 			objType = mobjinfo[MT_COIN].reactiontime;
+		else if (player->powers[pw_carry] == CR_NIGHTSFALL)
+			objType = mobjinfo[(nightsreplace ? MT_NIGHTSCHIP : MT_BLUESPHERE)].reactiontime;
 
 		z = player->mo->z;
 		if (player->mo->eflags & MFE_VERTICALFLIP)
@@ -3424,6 +3429,8 @@ void P_PlayerRingBurst(player_t *player, INT32 num_rings)
 
 			P_SetObjectMomZ(mo, 8*FRACUNIT, false);
 			mo->fuse = 20*TICRATE; // Adjust fuse for NiGHTS
+
+			P_SetMobjState(mo, (player->bonustime ? mo->info->raisestate : mo->info->spawnstate));
 		}
 		else
 		{
diff --git a/src/p_mobj.c b/src/p_mobj.c
index be373fbf4..4353e67c3 100644
--- a/src/p_mobj.c
+++ b/src/p_mobj.c
@@ -1539,6 +1539,8 @@ fixed_t P_GetMobjGravity(mobj_t *mo)
 			{
 				case MT_FLINGRING:
 				case MT_FLINGCOIN:
+				case MT_FLINGBLUESPHERE:
+				case MT_FLINGNIGHTSCHIP:
 				case MT_FLINGEMERALD:
 				case MT_BOUNCERING:
 				case MT_RAILRING:
@@ -2523,6 +2525,8 @@ static boolean P_ZMovement(mobj_t *mo)
 		case MT_BLUETEAMRING:
 		case MT_FLINGRING:
 		case MT_FLINGCOIN:
+		case MT_FLINGBLUESPHERE:
+		case MT_FLINGNIGHTSCHIP:
 		case MT_FLINGEMERALD:
 			// Remove flinged stuff from death pits.
 			if (P_CheckDeathPitCollide(mo))
@@ -2709,6 +2713,8 @@ static boolean P_ZMovement(mobj_t *mo)
 			// Flingrings bounce
 			if (mo->type == MT_FLINGRING
 				|| mo->type == MT_FLINGCOIN
+				|| mo->type == MT_FLINGBLUESPHERE
+				|| mo->type == MT_FLINGNIGHTSCHIP
 				|| P_WeaponOrPanel(mo->type)
 				|| mo->type == MT_FLINGEMERALD
 				|| mo->type == MT_BIGTUMBLEWEED
@@ -7941,6 +7947,8 @@ void P_MobjThinker(mobj_t *mobj)
 			// Flung items
 			case MT_FLINGRING:
 			case MT_FLINGCOIN:
+			case MT_FLINGBLUESPHERE:
+			case MT_FLINGNIGHTSCHIP:
 				if (mobj->flags2 & MF2_NIGHTSPULL)
 					P_NightsItemChase(mobj);
 				else
@@ -8278,6 +8286,8 @@ for (i = ((mobj->flags2 & MF2_STRONGBOX) ? strongboxamt : weakboxamt); i; --i) s
 #ifdef ESLOPE // Sliding physics for slidey mobjs!
 	if (mobj->type == MT_FLINGRING
 		|| mobj->type == MT_FLINGCOIN
+		|| mobj->type == MT_FLINGBLUESPHERE
+		|| mobj->type == MT_FLINGNIGHTSCHIP
 		|| P_WeaponOrPanel(mobj->type)
 		|| mobj->type == MT_FLINGEMERALD
 		|| mobj->type == MT_BIGTUMBLEWEED
diff --git a/src/p_setup.c b/src/p_setup.c
index 1a0736cc2..7597b7d87 100644
--- a/src/p_setup.c
+++ b/src/p_setup.c
@@ -885,7 +885,8 @@ void P_SwitchSpheresBonusMode(boolean bonustime)
 
 		mo = (mobj_t *)th;
 
-		if (mo->type != MT_BLUESPHERE && mo->type != MT_NIGHTSCHIP)
+		if (mo->type != MT_BLUESPHERE && mo->type != MT_NIGHTSCHIP
+			&& mo->type != MT_FLINGBLUESPHERE && mo->type != MT_FLINGNIGHTSCHIP)
 			continue;
 
 		if (!mo->health)

From 62e288a664038a36a1a8fdb5b28ad27533584ad7 Mon Sep 17 00:00:00 2001
From: mazmazz <mar.marcoz@outlook.com>
Date: Fri, 10 Aug 2018 00:51:20 -0400
Subject: [PATCH 10/15] Correct player->spheres deduction upon losing spheres

player->rings appears to be used for ring/star pickups. Player never loses stars, so it seems player->rings should not be deducted in-level.

Therefore, just deduct player->spheres.
---
 src/p_inter.c | 48 ++++++++++++++++++++++++++++++++++++++----------
 src/p_user.c  |  2 +-
 2 files changed, 39 insertions(+), 11 deletions(-)

diff --git a/src/p_inter.c b/src/p_inter.c
index f908601bb..87d780f7a 100644
--- a/src/p_inter.c
+++ b/src/p_inter.c
@@ -2772,18 +2772,26 @@ static inline boolean P_TagDamage(mobj_t *target, mobj_t *inflictor, mobj_t *sou
 		return true;
 	}
 
-	if (player->rings > 0) // Ring loss
+	if (player->powers[pw_carry] == CR_NIGHTSFALL)
+	{
+		if (player->spheres > 0)
+		{
+			P_PlayRinglossSound(target);
+			P_PlayerRingBurst(player, player->spheres);
+			player->spheres = 0;
+		}
+	}
+	else if (player->rings > 0) // Ring loss
 	{
 		P_PlayRinglossSound(target);
-		P_PlayerRingBurst(player, player->rings);
+		P_PlayerRingBurst(player, max(player->spheres, player->rings));
+		player->rings = 0;
 	}
 	else // Death
 	{
 		P_PlayDeathSound(target);
 		P_PlayVictorySound(source); // Killer laughs at you! LAUGHS! BWAHAHAHHAHAA!!
 	}
-
-	player->rings = 0;
 	return true;
 }
 
@@ -2998,7 +3006,7 @@ static void P_ShieldDamage(player_t *player, mobj_t *inflictor, mobj_t *source,
 	}
 }
 
-static void P_RingDamage(player_t *player, mobj_t *inflictor, mobj_t *source, INT32 damage, UINT8 damagetype)
+static void P_RingDamage(player_t *player, mobj_t *inflictor, mobj_t *source, INT32 damage, UINT8 damagetype, boolean dospheres)
 {
 	P_DoPlayerPain(player, source, inflictor);
 
@@ -3028,9 +3036,19 @@ static void P_RingDamage(player_t *player, mobj_t *inflictor, mobj_t *source, IN
 	// Ring loss sound plays despite hitting spikes
 	P_PlayRinglossSound(player->mo); // Ringledingle!
 	P_PlayerRingBurst(player, damage);
-	player->rings -= damage;
-	if (player->rings < 0)
-		player->rings = 0;
+
+	if (dospheres)
+	{
+		player->spheres -= damage;
+		if (player->spheres < 0)
+			player->spheres = 0;
+	}
+	else
+	{
+		player->rings -= damage;
+		if (player->rings < 0)
+			player->rings = 0;
+	}
 }
 
 //
@@ -3247,7 +3265,7 @@ boolean P_DamageMobj(mobj_t *target, mobj_t *inflictor, mobj_t *source, INT32 da
 		if (damagetype & DMG_DEATHMASK)
 		{
 			P_KillPlayer(player, source, damage);
-			player->rings = 0;
+			player->rings = player->spheres = 0;
 		}
 		else if (metalrecording)
 		{
@@ -3291,10 +3309,19 @@ boolean P_DamageMobj(mobj_t *target, mobj_t *inflictor, mobj_t *source, INT32 da
 			P_ShieldDamage(player, inflictor, source, damage, damagetype);
 			damage = 0;
 		}
+		else if (player->powers[pw_carry] == CR_NIGHTSFALL)
+		{
+			if (player->spheres > 0)
+			{
+				damage = player->spheres;
+				P_RingDamage(player, inflictor, source, damage, damagetype, true);
+				damage = 0;
+			}
+		}
 		else if (player->rings > 0) // No shield but have rings.
 		{
 			damage = player->rings;
-			P_RingDamage(player, inflictor, source, damage, damagetype);
+			P_RingDamage(player, inflictor, source, damage, damagetype, false);
 			damage = 0;
 		}
 		// To reduce griefing potential, don't allow players to be killed
@@ -3430,6 +3457,7 @@ void P_PlayerRingBurst(player_t *player, INT32 num_rings)
 			P_SetObjectMomZ(mo, 8*FRACUNIT, false);
 			mo->fuse = 20*TICRATE; // Adjust fuse for NiGHTS
 
+			// Toggle bonus time colors
 			P_SetMobjState(mo, (player->bonustime ? mo->info->raisestate : mo->info->spawnstate));
 		}
 		else
diff --git a/src/p_user.c b/src/p_user.c
index 357933f14..449f95db2 100644
--- a/src/p_user.c
+++ b/src/p_user.c
@@ -7023,7 +7023,7 @@ static void P_MovePlayer(player_t *player)
 					if (playeringame[i])
 						players[i].exiting = (14*TICRATE)/5 + 1;
 			}
-			else if (player->rings > 0)
+			else if (player->spheres > 0)
 				P_DamageMobj(player->mo, NULL, NULL, 1, 0);
 			player->powers[pw_carry] = CR_NONE;
 		}

From 14c17cf012e70ae50bc5b011310f92d02ddbd7a2 Mon Sep 17 00:00:00 2001
From: mazmazz <mar.marcoz@outlook.com>
Date: Fri, 10 Aug 2018 02:34:21 -0400
Subject: [PATCH 11/15] Tag damage fix error

---
 src/p_inter.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/p_inter.c b/src/p_inter.c
index 87d780f7a..e740b62d1 100644
--- a/src/p_inter.c
+++ b/src/p_inter.c
@@ -2784,7 +2784,7 @@ static inline boolean P_TagDamage(mobj_t *target, mobj_t *inflictor, mobj_t *sou
 	else if (player->rings > 0) // Ring loss
 	{
 		P_PlayRinglossSound(target);
-		P_PlayerRingBurst(player, max(player->spheres, player->rings));
+		P_PlayerRingBurst(player, player->rings);
 		player->rings = 0;
 	}
 	else // Death

From b05960431d21e14d0ac9c233d2572330cd5a9689 Mon Sep 17 00:00:00 2001
From: mazmazz <mar.marcoz@outlook.com>
Date: Fri, 10 Aug 2018 03:05:10 -0400
Subject: [PATCH 12/15] Reset player->rings on denightserize and on new mare

---
 src/p_user.c | 7 ++++---
 1 file changed, 4 insertions(+), 3 deletions(-)

diff --git a/src/p_user.c b/src/p_user.c
index 449f95db2..52b4cd88a 100644
--- a/src/p_user.c
+++ b/src/p_user.c
@@ -590,8 +590,9 @@ static void P_DeNightserizePlayer(player_t *player)
 	else if (player == &players[secondarydisplayplayer])
 		localaiming2 = 0;
 
-	// If you screwed up, kiss your score goodbye.
+	// If you screwed up, kiss your score and ring bonus goodbye.
 	player->marescore = 0;
+	player->rings = 0;
 
 	P_SetPlayerMobjState(player->mo, S_PLAY_FALL);
 
@@ -721,7 +722,7 @@ void P_NightserizePlayer(player_t *player, INT32 nighttime)
 			players[i].lastmarescore = players[i].marescore;
 			players[i].marescore = 0;
 
-			players[i].spheres = 0;
+			players[i].spheres = players[i].rings = 0;
 			P_DoPlayerExit(&players[i]);
 		}
 	}
@@ -745,7 +746,7 @@ void P_NightserizePlayer(player_t *player, INT32 nighttime)
 		player->marescore = 0;
 		player->marebegunat = leveltime;
 
-		player->spheres = 0;
+		player->spheres = player->rings = 0;
 	}
 	else
 	{

From bd8316f49b5b2c2f61bb519a469085e28b6ac6d1 Mon Sep 17 00:00:00 2001
From: mazmazz <mar.marcoz@outlook.com>
Date: Fri, 10 Aug 2018 04:05:20 -0400
Subject: [PATCH 13/15] Track player's previous mare rings with
 player->finishedrings.

There may not be a point to this, other than to be consistent with how spheres are tracked. If non-special stage NiGHTS should tally a ring bonus, this may be useful.
---
 src/d_player.h      |  1 +
 src/lua_playerlib.c |  4 ++++
 src/p_saveg.c       |  2 ++
 src/p_setup.c       | 13 +++++++------
 src/p_user.c        |  6 ++++++
 5 files changed, 20 insertions(+), 6 deletions(-)

diff --git a/src/d_player.h b/src/d_player.h
index 24c4f9252..7bee5f337 100644
--- a/src/d_player.h
+++ b/src/d_player.h
@@ -462,6 +462,7 @@ typedef struct player_s
 	tic_t startedtime; // Time which you started this mare with.
 	tic_t finishedtime; // Time it took you to finish the mare (used for display)
 	INT16 finishedspheres; // The spheres you had left upon finishing the mare
+	INT16 finishedrings; // The rings/stars you had left upon finishing the mare
 	UINT32 marescore; // score for this nights stage
 	UINT32 lastmarescore; // score for the last mare
 	UINT8 lastmare; // previous mare
diff --git a/src/lua_playerlib.c b/src/lua_playerlib.c
index 8a1079c36..ff62f2459 100644
--- a/src/lua_playerlib.c
+++ b/src/lua_playerlib.c
@@ -298,6 +298,8 @@ static int player_get(lua_State *L)
 		lua_pushinteger(L, plr->finishedtime);
 	else if (fastcmp(field,"finishedspheres"))
 		lua_pushinteger(L, plr->finishedspheres);
+	else if (fastcmp(field,"finishedrings"))
+		lua_pushinteger(L, plr->finishedrings);
 	else if (fastcmp(field,"marescore"))
 		lua_pushinteger(L, plr->marescore);
 	else if (fastcmp(field,"lastmarescore"))
@@ -576,6 +578,8 @@ static int player_set(lua_State *L)
 		plr->finishedtime = (tic_t)luaL_checkinteger(L, 3);
 	else if (fastcmp(field,"finishedspheres"))
 		plr->finishedspheres = (INT16)luaL_checkinteger(L, 3);
+	else if (fastcmp(field,"finishedrings"))
+		plr->finishedrings = (INT16)luaL_checkinteger(L, 3);
 	else if (fastcmp(field,"marescore"))
 		plr->marescore = (UINT32)luaL_checkinteger(L, 3);
 	else if (fastcmp(field,"lastmarescore"))
diff --git a/src/p_saveg.c b/src/p_saveg.c
index 2e47f2077..7cf437384 100644
--- a/src/p_saveg.c
+++ b/src/p_saveg.c
@@ -203,6 +203,7 @@ static void P_NetArchivePlayers(void)
 		WRITEUINT32(save_p, players[i].startedtime);
 		WRITEUINT32(save_p, players[i].finishedtime);
 		WRITEINT16(save_p, players[i].finishedspheres);
+		WRITEINT16(save_p, players[i].finishedrings);
 		WRITEUINT32(save_p, players[i].marescore);
 		WRITEUINT32(save_p, players[i].lastmarescore);
 		WRITEUINT8(save_p, players[i].lastmare);
@@ -391,6 +392,7 @@ static void P_NetUnArchivePlayers(void)
 		players[i].startedtime = READUINT32(save_p);
 		players[i].finishedtime = READUINT32(save_p);
 		players[i].finishedspheres = READINT16(save_p);
+		players[i].finishedrings = READINT16(save_p);
 		players[i].marescore = READUINT32(save_p);
 		players[i].lastmarescore = READUINT32(save_p);
 		players[i].lastmare = READUINT8(save_p);
diff --git a/src/p_setup.c b/src/p_setup.c
index 7597b7d87..b8db575b5 100644
--- a/src/p_setup.c
+++ b/src/p_setup.c
@@ -2380,12 +2380,13 @@ static void P_LevelInitStuff(void)
 		 players[i].marescore = players[i].lastmarescore =\
 		 players[i].maxlink = players[i].startedtime =\
 		 players[i].finishedtime = players[i].finishedspheres =\
-		 players[i].lastmare = players[i].marebegunat =\
-		 players[i].textvar = players[i].texttimer =\
-		 players[i].linkcount = players[i].linktimer =\
-		 players[i].flyangle = players[i].anotherflyangle =\
-		 players[i].nightstime = players[i].mare =\
-		 players[i].realtime = players[i].exiting = 0;
+		 players[i].finishedrings = players[i].lastmare =\
+		 players[i].marebegunat = players[i].textvar =\
+		 players[i].texttimer = players[i].linkcount =\
+		 players[i].linktimer = players[i].flyangle =\
+		 players[i].anotherflyangle = players[i].nightstime =\
+		 players[i].mare = players[i].realtime =\
+		 players[i].exiting = 0;
 
 		// i guess this could be part of the above but i feel mildly uncomfortable implicitly casting
 		players[i].gotcontinue = false;
diff --git a/src/p_user.c b/src/p_user.c
index 52b4cd88a..fd09b0847 100644
--- a/src/p_user.c
+++ b/src/p_user.c
@@ -685,6 +685,7 @@ void P_NightserizePlayer(player_t *player, INT32 nighttime)
 	{
 		INT32 i;
 		INT32 total_spheres = 0;
+		INT32 total_rings = 0;
 
 		P_SetTarget(&player->mo->target, NULL);
 
@@ -692,7 +693,10 @@ void P_NightserizePlayer(player_t *player, INT32 nighttime)
 		{
 			for (i = 0; i < MAXPLAYERS; i++)
 				if (playeringame[i]/* && players[i].powers[pw_carry] == CR_NIGHTSMODE*/)
+				{
 					total_spheres += players[i].spheres;
+					total_rings += players[i].rings;
+				}
 		}
 
 		for (i = 0; i < MAXPLAYERS; i++)
@@ -706,11 +710,13 @@ void P_NightserizePlayer(player_t *player, INT32 nighttime)
 			if (G_IsSpecialStage(gamemap))
 			{
 				players[i].finishedspheres = (INT16)total_spheres;
+				players[i].finishedrings = (INT16)total_rings;
 				P_AddPlayerScore(player, total_spheres * 50);
 			}
 			else
 			{
 				players[i].finishedspheres = (INT16)(players[i].spheres);
+				players[i].finishedrings = (INT16)(players[i].rings);
 				P_AddPlayerScore(&players[i], (players[i].spheres) * 50);
 			}
 

From 4cb7036f513ea9a0c1c743a1de69c40294597c27 Mon Sep 17 00:00:00 2001
From: mazmazz <mar.marcoz@outlook.com>
Date: Fri, 10 Aug 2018 12:47:44 -0400
Subject: [PATCH 14/15] SETSPHERES console command for debugging/cheating

Fixed sphere spill bug where no spheres spill if player->rings is 0
---
 src/d_netcmd.c |  1 +
 src/m_cheat.c  | 17 +++++++++++++++++
 src/m_cheat.h  |  1 +
 src/p_inter.c  |  2 +-
 4 files changed, 20 insertions(+), 1 deletion(-)

diff --git a/src/d_netcmd.c b/src/d_netcmd.c
index f3fb1f6ae..dbb0b0ff5 100644
--- a/src/d_netcmd.c
+++ b/src/d_netcmd.c
@@ -822,6 +822,7 @@ void D_RegisterClientCommands(void)
 	COM_AddCommand("getallemeralds", Command_Getallemeralds_f);
 	COM_AddCommand("resetemeralds", Command_Resetemeralds_f);
 	COM_AddCommand("setrings", Command_Setrings_f);
+	COM_AddCommand("setspheres", Command_Setspheres_f);
 	COM_AddCommand("setlives", Command_Setlives_f);
 	COM_AddCommand("setcontinues", Command_Setcontinues_f);
 	COM_AddCommand("devmode", Command_Devmode_f);
diff --git a/src/m_cheat.c b/src/m_cheat.c
index 5ac742270..b572b84eb 100644
--- a/src/m_cheat.c
+++ b/src/m_cheat.c
@@ -890,6 +890,23 @@ void Command_Setrings_f(void)
 	}
 }
 
+void Command_Setspheres_f(void)
+{
+	REQUIRE_INLEVEL;
+	REQUIRE_SINGLEPLAYER;
+	REQUIRE_NOULTIMATE;
+	REQUIRE_PANDORA;
+
+	if (COM_Argc() > 1)
+	{
+		// P_GivePlayerRings does value clamping
+		players[consoleplayer].spheres = 0;
+		P_GivePlayerSpheres(&players[consoleplayer], atoi(COM_Argv(1)));
+
+		G_SetGameModified(multiplayer);
+	}
+}
+
 void Command_Setlives_f(void)
 {
 	REQUIRE_INLEVEL;
diff --git a/src/m_cheat.h b/src/m_cheat.h
index 951c7a16a..31f650b3f 100644
--- a/src/m_cheat.h
+++ b/src/m_cheat.h
@@ -51,6 +51,7 @@ void Command_Savecheckpoint_f(void);
 void Command_Getallemeralds_f(void);
 void Command_Resetemeralds_f(void);
 void Command_Setrings_f(void);
+void Command_Setspheres_f(void);
 void Command_Setlives_f(void);
 void Command_Setcontinues_f(void);
 void Command_Devmode_f(void);
diff --git a/src/p_inter.c b/src/p_inter.c
index e740b62d1..ce8bba6b6 100644
--- a/src/p_inter.c
+++ b/src/p_inter.c
@@ -3409,7 +3409,7 @@ void P_PlayerRingBurst(player_t *player, INT32 num_rings)
 		return;
 
 	// If no health, don't spawn ring!
-	if (player->rings <= 0)
+	if (((maptol & TOL_NIGHTS) && player->spheres <= 0) || (!(maptol & TOL_NIGHTS) && player->rings <= 0))
 		num_rings = 0;
 
 	if (num_rings > 32 && player->powers[pw_carry] != CR_NIGHTSFALL)

From 672e196aa492cbd9013decc2d41d2e646e6f22f8 Mon Sep 17 00:00:00 2001
From: mazmazz <mar.marcoz@outlook.com>
Date: Fri, 10 Aug 2018 13:15:54 -0400
Subject: [PATCH 15/15] Pandora's Box support for player->spheres

Opting to handle this transparently via the Rings menu option. Doesn't seem worth making a separate entry for Spheres.
---
 src/m_menu.c | 12 ++++++++++--
 1 file changed, 10 insertions(+), 2 deletions(-)

diff --git a/src/m_menu.c b/src/m_menu.c
index b11873adb..f99f5d860 100644
--- a/src/m_menu.c
+++ b/src/m_menu.c
@@ -5273,7 +5273,10 @@ static void M_HandleAddons(INT32 choice)
 static void M_PandorasBox(INT32 choice)
 {
 	(void)choice;
-	CV_StealthSetValue(&cv_dummyrings, max(players[consoleplayer].rings, 0));
+	if (maptol & TOL_NIGHTS)
+		CV_StealthSetValue(&cv_dummyrings, max(players[consoleplayer].spheres, 0));
+	else
+		CV_StealthSetValue(&cv_dummyrings, max(players[consoleplayer].rings, 0));
 	if (players[consoleplayer].lives == 0x7f)
 		CV_StealthSetValue(&cv_dummylives, -1);
 	else
@@ -5291,7 +5294,12 @@ static void M_PandorasBox(INT32 choice)
 static boolean M_ExitPandorasBox(void)
 {
 	if (cv_dummyrings.value != max(players[consoleplayer].rings, 0))
-		COM_ImmedExecute(va("setrings %d", cv_dummyrings.value));
+	{
+		if (maptol & TOL_NIGHTS)
+			COM_ImmedExecute(va("setspheres %d", cv_dummyrings.value));
+		else
+			COM_ImmedExecute(va("setrings %d", cv_dummyrings.value));
+	}
 	if (cv_dummylives.value != players[consoleplayer].lives)
 		COM_ImmedExecute(va("setlives %d", cv_dummylives.value));
 	if (cv_dummycontinues.value != players[consoleplayer].continues)