From 6e07631cc9851ca8a4d9ac0e6aa143fe6fdd75b0 Mon Sep 17 00:00:00 2001
From: toaster <rollerorbital@gmail.com>
Date: Sun, 3 Jun 2018 22:41:54 +0100
Subject: [PATCH] MONSTER COMMIT.

OLD SPECIAL STAGES:
* Spheres in old special stages instead of rings!
* Individual timers in old special stages instead of a global one!
* Old special stages use a variant of the NiGHTS HUD now!
* Special stage damage in old special stages loses 5 seconds of time instead of 10 rings/spheres!
* All damage gained through old special stages is converted to special stage damage! As a consequence, the special spikeball has no need to be special anymore.
* Made emerald gaining function be based on special stage number rather than gained emeralds!
* Consistency with...

NiGHTS SPECIAL STAGES:
* Spheres now flash in bonus time. https://cdn.discordapp.com/attachments/400761370800422922/452590553100713984/srb20032.gif
* Sphere and ring mapthingnums are now less fucked up in 'em. (Rings are 300, same as usual, while Spheres are now 1706 replacing NiGHTS Wings.)

SPECIAL STAGES IN GENERAL:
* useNightsSS is now dead. Each individual special stage is now assessed for NiGHTS-mode behaviour based on maptol & TOL_NIGHTS.
* CRAWLA HONCHO\n CAN NOW BE\n SUPER CRAWLA HONCHO end tally modification now also includes a mini-tutorial on turning super. https://cdn.discordapp.com/attachments/400761370800422922/452844894113759233/srb20036.gif
* SONIC GOT A CHAOS EMERALD? https://cdn.discordapp.com/attachments/400761370800422922/452623869497573386/srb20034.gif

NiGHTS NON-SPECIAL STAGES:
* Colour Chips and Star Chips! Replaces Spheres and Rings of NiGHTS Special Stages.
* Colour Chips turn yellow in bonus time.
* Ideya!
* Its own "drowning" music!
* All of the object types for Dream Hill.
* GIF: https://cdn.discordapp.com/attachments/400761370800422922/452844894113759233/srb20036.gif

RANDOM BS:
* Turn super with the spin button instead of the jump button!
* Followmobj now correctly set with P_SetTarget instead of pointer assignment.
* Emerald hunt uses new sprites!
* Made unlock noise different from emblem gain noise! (It's the CRAWLA HONCHO CAN NOW TURN yadda yadda sound from S3K now.)
---
 src/d_clisrv.c          |  22 ++--
 src/d_clisrv.h          |   3 +-
 src/d_netcmd.c          |   7 +-
 src/d_player.h          |   5 +-
 src/dehacked.c          |  70 +++++++----
 src/doomstat.h          |   3 +-
 src/f_finale.c          |   2 +-
 src/g_game.c            |   6 +-
 src/hardware/hw_light.c |  13 +-
 src/hu_stuff.c          |   5 +-
 src/hu_stuff.h          |   2 +-
 src/info.c              | 256 ++++++++++++++++++++++++++++++----------
 src/info.h              |  83 ++++++++-----
 src/lua_hud.h           |   2 +-
 src/lua_playerlib.c     |  14 ++-
 src/m_cheat.c           |  22 ++--
 src/p_enemy.c           |  19 ++-
 src/p_inter.c           | 216 +++++++++++++++++++--------------
 src/p_local.h           |   1 +
 src/p_mobj.c            | 216 ++++++++++++++++-----------------
 src/p_mobj.h            |   4 +-
 src/p_saveg.c           |  16 +--
 src/p_setup.c           |  71 +++++++----
 src/p_setup.h           |   1 +
 src/p_spec.c            |  14 +--
 src/p_tick.c            |  89 +++++++-------
 src/p_user.c            | 161 ++++++++++++++-----------
 src/r_things.c          |   2 +-
 src/screen.c            |   7 +-
 src/sounds.c            |  14 ++-
 src/sounds.h            |   4 +-
 src/st_stuff.c          | 207 +++++++++++++++++++-------------
 src/y_inter.c           | 104 ++++++++++------
 33 files changed, 998 insertions(+), 663 deletions(-)

diff --git a/src/d_clisrv.c b/src/d_clisrv.c
index 5832b2c27e..14677b8a11 100644
--- a/src/d_clisrv.c
+++ b/src/d_clisrv.c
@@ -513,7 +513,8 @@ static inline void resynch_write_player(resynch_pak *rsp, const size_t i)
 		rsp->powers[j] = (UINT16)SHORT(players[i].powers[j]);
 
 	// Score is resynched in the rspfirm resync packet
-	rsp->rings = LONG(players[i].rings);
+	rsp->rings = SHORT(players[i].rings);
+	rsp->spheres = SHORT(players[i].spheres);
 	rsp->lives = players[i].lives;
 	rsp->continues = players[i].continues;
 	rsp->scoreadd = players[i].scoreadd;
@@ -643,7 +644,8 @@ static void resynch_read_player(resynch_pak *rsp)
 		players[i].powers[j] = (UINT16)SHORT(rsp->powers[j]);
 
 	// Score is resynched in the rspfirm resync packet
-	players[i].rings = LONG(rsp->rings);
+	players[i].rings = SHORT(rsp->rings);
+	players[i].spheres = SHORT(rsp->spheres);
 	players[i].lives = rsp->lives;
 	players[i].continues = rsp->continues;
 	players[i].scoreadd = rsp->scoreadd;
@@ -2377,11 +2379,11 @@ static void CL_RemovePlayer(INT32 playernum)
 	if (gametype == GT_CTF)
 		P_PlayerFlagBurst(&players[playernum], false); // Don't take the flag with you!
 
-	// If in a special stage, redistribute the player's rings across
+	// If in a special stage, redistribute the player's spheres across
 	// the remaining players.
 	if (G_IsSpecialStage(gamemap))
 	{
-		INT32 i, count, increment, rings;
+		INT32 i, count, increment, spheres;
 
 		for (i = 0, count = 0; i < MAXPLAYERS; i++)
 		{
@@ -2390,19 +2392,19 @@ static void CL_RemovePlayer(INT32 playernum)
 		}
 
 		count--;
-		rings = players[playernum].rings;
-		increment = rings/count;
+		spheres = players[playernum].spheres;
+		increment = spheres/count;
 
 		for (i = 0; i < MAXPLAYERS; i++)
 		{
 			if (playeringame[i] && i != playernum)
 			{
-				if (rings < increment)
-					P_GivePlayerRings(&players[i], rings);
+				if (spheres < increment)
+					P_GivePlayerSpheres(&players[i], spheres);
 				else
-					P_GivePlayerRings(&players[i], increment);
+					P_GivePlayerSpheres(&players[i], increment);
 
-				rings -= increment;
+				spheres -= increment;
 			}
 		}
 	}
diff --git a/src/d_clisrv.h b/src/d_clisrv.h
index bdf3326658..bdb85a76c2 100644
--- a/src/d_clisrv.h
+++ b/src/d_clisrv.h
@@ -164,7 +164,8 @@ typedef struct
 	UINT16 powers[NUMPOWERS];
 
 	// Score is resynched in the confirm resync packet
-	INT32 rings;
+	INT16 rings;
+	INT16 spheres;
 	SINT8 lives;
 	SINT8 continues;
 	UINT8 scoreadd;
diff --git a/src/d_netcmd.c b/src/d_netcmd.c
index e011781557..bd1f93512c 100644
--- a/src/d_netcmd.c
+++ b/src/d_netcmd.c
@@ -309,8 +309,8 @@ consvar_t cv_overtime = {"overtime", "Yes", CV_NETVAR, CV_YesNo, NULL, 0, NULL,
 
 consvar_t cv_rollingdemos = {"rollingdemos", "On", CV_SAVE, CV_OnOff, NULL, 0, NULL, NULL, 0, 0, NULL};
 
-static CV_PossibleValue_t timetic_cons_t[] = {{0, "Normal"}, {1, "Centiseconds"}, {2, "Mania"}, {3, "Tics"}, {0, NULL}};
-consvar_t cv_timetic = {"timerres", "Normal", CV_SAVE, timetic_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL}; // use tics in display
+static CV_PossibleValue_t timetic_cons_t[] = {{0, "Classic"}, {1, "Centiseconds"}, {2, "Mania"}, {3, "Tics"}, {0, NULL}};
+consvar_t cv_timetic = {"timerres", "Classic", CV_SAVE, timetic_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL};
 
 static CV_PossibleValue_t powerupdisplay_cons_t[] = {{0, "Never"}, {1, "First-person only"}, {2, "Always"}, {0, NULL}};
 consvar_t cv_powerupdisplay = {"powerupdisplay", "First-person only", CV_SAVE, powerupdisplay_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL};
@@ -2682,8 +2682,7 @@ static void Got_Teamchange(UINT8 **cp, INT32 playernum)
 	// Clear player score and rings if a spectator.
 	if (players[playernum].spectator)
 	{
-		players[playernum].score = 0;
-		players[playernum].rings = 0;
+		players[playernum].score = players[playernum].rings = 0;
 		if (players[playernum].mo)
 			players[playernum].mo->health = 1;
 	}
diff --git a/src/d_player.h b/src/d_player.h
index e1350fe67d..24c4f92521 100644
--- a/src/d_player.h
+++ b/src/d_player.h
@@ -324,7 +324,8 @@ typedef struct player_s
 	angle_t drawangle;
 
 	// player's ring count
-	INT32 rings;
+	INT16 rings;
+	INT16 spheres;
 
 	SINT8 pity; // i pity the fool.
 	INT32 currentweapon; // current weapon selected.
@@ -460,7 +461,7 @@ typedef struct player_s
 	tic_t marebegunat; // Leveltime when mare begun
 	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 finishedrings; // The rings you had left upon finishing the mare
+	INT16 finishedspheres; // The spheres 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/dehacked.c b/src/dehacked.c
index c25e9e1fa8..ca17614ede 100644
--- a/src/dehacked.c
+++ b/src/dehacked.c
@@ -2673,10 +2673,6 @@ static void readmaincfg(MYFILE *f)
 				sstage_start = (INT16)value;
 				sstage_end = (INT16)(sstage_start+6); // 7 special stages total
 			}
-			else if (fastcmp(word, "USENIGHTSSS"))
-			{
-				useNightsSS = (UINT8)(value || word2[0] == 'T' || word2[0] == 'Y');
-			}
 			else if (fastcmp(word, "REDTEAM"))
 			{
 				skincolor_redteam = (UINT8)get_number(word2);
@@ -3799,6 +3795,7 @@ static const char *const STATE_LIST[] = { // array length left dynamic for sanit
 
 	// Egg Shield for Egg Guard
 	"S_EGGSHIELD",
+	"S_EGGSHIELDBREAK",
 
 	// Green Snapper
 	"S_GSNAPPER_STND",
@@ -4367,8 +4364,17 @@ static const char *const STATE_LIST[] = { // array length left dynamic for sanit
 	"S_RING",
 
 	// Blue Sphere for special stages
-	"S_BLUEBALL",
-	"S_BLUEBALLSPARK",
+	"S_BLUESPHERE",
+	"S_BLUESPHEREBONUS",
+	"S_BLUESPHERESPARK",
+
+	// NiGHTS Chip
+	"S_NIGHTSCHIP",
+	"S_NIGHTSCHIPBONUS",
+
+	// NiGHTS Star
+	"S_NIGHTSSTAR",
+	"S_NIGHTSSTARXMAS",
 
 	// Gravity Wells for special stages
 	"S_GRAVWELLGREEN",
@@ -4426,8 +4432,10 @@ static const char *const STATE_LIST[] = { // array length left dynamic for sanit
 	"S_CEMG6",
 	"S_CEMG7",
 
-	// Emeralds (for hunt)
-	"S_EMER1",
+	// Emerald hunt shards
+	"S_SHRD1",
+	"S_SHRD2",
+	"S_SHRD3",
 
 	// Bubble Source
 	"S_BUBBLES1",
@@ -4709,7 +4717,6 @@ static const char *const STATE_LIST[] = { // array length left dynamic for sanit
 
 	// Arrow
 	"S_ARROW",
-	"S_TEMPSHI",
 	"S_ARROWBONK",
 
 	// Trapgoyle Demon fire
@@ -4739,6 +4746,7 @@ static const char *const STATE_LIST[] = { // array length left dynamic for sanit
 	"S_POLYGONTREE",
 	"S_BUSHTREE",
 	"S_BUSHREDTREE",
+	"S_SPRINGTREE",
 
 	// THZ flowers
 	"S_THZFLOWERA", // THZ1 Steam flower
@@ -6007,9 +6015,6 @@ static const char *const STATE_LIST[] = { // array length left dynamic for sanit
 	"S_NIGHTSCORE90_2",
 	"S_NIGHTSCORE100_2",
 
-	"S_NIGHTSWING",
-	"S_NIGHTSWING_XMAS",
-
 	// NiGHTS Paraloop Powerups
 	"S_NIGHTSSUPERLOOP",
 	"S_NIGHTSDRILLREFILL",
@@ -6027,14 +6032,11 @@ static const char *const STATE_LIST[] = { // array length left dynamic for sanit
 	"S_ORBITEM6",
 	"S_ORBITEM7",
 	"S_ORBITEM8",
-	"S_ORBITEM9",
-	"S_ORBITEM10",
-	"S_ORBITEM11",
-	"S_ORBITEM12",
-	"S_ORBITEM13",
-	"S_ORBITEM14",
-	"S_ORBITEM15",
-	"S_ORBITEM16",
+	"S_ORBIDYA1",
+	"S_ORBIDYA2",
+	"S_ORBIDYA3",
+	"S_ORBIDYA4",
+	"S_ORBIDYA5",
 
 	// "Flicky" helper
 	"S_NIGHTOPIANHELPER1",
@@ -6047,6 +6049,26 @@ static const char *const STATE_LIST[] = { // array length left dynamic for sanit
 	"S_NIGHTOPIANHELPER8",
 	"S_NIGHTOPIANHELPER9",
 
+	// Nightopian
+	"S_PIAN0",
+	"S_PIAN1",
+	"S_PIAN2",
+	"S_PIAN3",
+	"S_PIAN4",
+	"S_PIAN5",
+	"S_PIAN6",
+	"S_PIANSING",
+
+	// Shleep
+	"S_SHLEEP1",
+	"S_SHLEEP2",
+	"S_SHLEEP3",
+	"S_SHLEEP4",
+	"S_SHLEEPBOUNCE1",
+	"S_SHLEEPBOUNCE2",
+	"S_SHLEEPBOUNCE3",
+
+
 	// Secret badniks and hazards, shhhh
 	"S_HIVEELEMENTAL_LOOK",
 	"S_HIVEELEMENTAL_PREPARE1",
@@ -6322,7 +6344,7 @@ static const char *const MOBJTYPE_LIST[] = {  // array length left dynamic for s
 	// Collectible Items
 	"MT_RING",
 	"MT_FLINGRING", // Lost ring
-	"MT_BLUEBALL",  // Blue sphere replacement for special stages
+	"MT_BLUESPHERE",  // Blue sphere replacement for special stages
 	"MT_REDTEAMRING",  //Rings collectable by red team.
 	"MT_BLUETEAMRING", //Rings collectable by blue team.
 	"MT_TOKEN", // Special Stage Token
@@ -6466,6 +6488,7 @@ static const char *const MOBJTYPE_LIST[] = {  // array length left dynamic for s
 	"MT_POLYGONTREE",
 	"MT_BUSHTREE",
 	"MT_BUSHREDTREE",
+	"MT_SPRINGTREE",
 
 	// Techno Hill Scenery
 	"MT_THZFLOWER1",
@@ -6778,7 +6801,8 @@ static const char *const MOBJTYPE_LIST[] = {  // array length left dynamic for s
 	"MT_HOOPCOLLIDE", // Collision detection for NiGHTS hoops
 	"MT_HOOPCENTER", // Center of a hoop
 	"MT_NIGHTSCORE",
-	"MT_NIGHTSWING",
+	"MT_NIGHTSCHIP", // NiGHTS Chip
+	"MT_NIGHTSSTAR", // NiGHTS Star
 	"MT_NIGHTSSUPERLOOP",
 	"MT_NIGHTSDRILLREFILL",
 	"MT_NIGHTSHELPER",
@@ -6786,6 +6810,8 @@ static const char *const MOBJTYPE_LIST[] = {  // array length left dynamic for s
 	"MT_NIGHTSLINKFREEZE",
 	"MT_EGGCAPSULE",
 	"MT_NIGHTOPIANHELPER", // the actual helper object that orbits you
+	"MT_PIAN", // decorative singing friend
+	"MT_SHLEEP", // almost-decorative sleeping enemy
 
 	// Secret badniks and hazards, shhhh
 	"MT_HIVEELEMENTAL",
diff --git a/src/doomstat.h b/src/doomstat.h
index d4735f6b21..3a2084e291 100644
--- a/src/doomstat.h
+++ b/src/doomstat.h
@@ -134,7 +134,6 @@ extern boolean hidetitlepics;
 extern INT16 bootmap; //bootmap for loading a map on startup
 
 extern boolean looptitle;
-extern boolean useNightsSS;
 
 // CTF colors.
 extern UINT8 skincolor_redteam, skincolor_blueteam, skincolor_redring, skincolor_bluering;
@@ -175,7 +174,7 @@ extern cutscene_t *cutscenes[128];
 extern INT16 nextmapoverride;
 extern boolean skipstats;
 
-extern UINT32 totalrings; //  Total # of rings in a level
+extern UINT32 ssspheres; //  Total # of spheres in a level
 
 // Fun extra stuff
 extern INT16 lastmap; // Last level you were at (returning from special stages).
diff --git a/src/f_finale.c b/src/f_finale.c
index db62ddf092..2191e8673c 100644
--- a/src/f_finale.c
+++ b/src/f_finale.c
@@ -1345,7 +1345,7 @@ void F_GameEvaluationDrawer(void)
 				++timesBeatenUltimate;
 
 			if (M_UpdateUnlockablesAndExtraEmblems())
-				S_StartSound(NULL, sfx_ncitem);
+				S_StartSound(NULL, sfx_s3k68);
 
 			G_SaveGameData();
 		}
diff --git a/src/g_game.c b/src/g_game.c
index 509ccf0ab2..79f62bcd18 100644
--- a/src/g_game.c
+++ b/src/g_game.c
@@ -116,7 +116,7 @@ INT32 secondarydisplayplayer; // for splitscreen
 
 tic_t gametic;
 tic_t levelstarttic; // gametic at level start
-UINT32 totalrings; // for intermission
+UINT32 ssspheres; // old special stage
 INT16 lastmap; // last level you were at (returning from special stages)
 tic_t timeinmap; // Ticker for time spent in level (used for levelcard display)
 
@@ -129,7 +129,6 @@ boolean hidetitlepics = false;
 INT16 bootmap; //bootmap for loading a map on startup
 
 boolean looptitle = false;
-boolean useNightsSS = false;
 
 UINT8 skincolor_redteam = SKINCOLOR_RED;
 UINT8 skincolor_blueteam = SKINCOLOR_BLUE;
@@ -2201,7 +2200,7 @@ void G_PlayerReborn(INT32 player)
 	p->pflags |= PF_JUMPDOWN;
 
 	p->playerstate = PST_LIVE;
-	p->rings = 0; // 0 rings
+	p->rings = p->spheres = 0; // 0 rings
 	p->panim = PA_IDLE; // standing animation
 
 	//if ((netgame || multiplayer) && !p->spectator) -- moved into P_SpawnPlayer to account for forced changes there
@@ -3209,7 +3208,6 @@ void G_LoadGameSettings(void)
 	spstage_start = 1;
 	sstage_start = 50;
 	sstage_end = 57; // 8 special stages in vanilla SRB2
-	useNightsSS = false; //true;
 
 	// initialize free sfx slots for skin sounds
 	S_InitRuntimeSounds();
diff --git a/src/hardware/hw_light.c b/src/hardware/hw_light.c
index fbec957f79..aee6f93968 100644
--- a/src/hardware/hw_light.c
+++ b/src/hardware/hw_light.c
@@ -229,10 +229,12 @@ light_t *t_lspr[NUMSPRITES] =
 	&lspr[NOLIGHT],     // SPR_TOKE
 	&lspr[REDBALL_L],   // SPR_RFLG
 	&lspr[BLUEBALL_L],  // SPR_BFLG
-	&lspr[NOLIGHT],     // SPR_NWNG
+	&lspr[NOLIGHT],     // SPR_SPHR
+	&lspr[NOLIGHT],     // SPR_NCHP
+	&lspr[NOLIGHT],     // SPR_NSTR
 	&lspr[NOLIGHT],     // SPR_EMBM
 	&lspr[NOLIGHT],     // SPR_CEMG
-	&lspr[NOLIGHT],     // SPR_EMER
+	&lspr[NOLIGHT],     // SPR_SHRD
 
 	// Interactive Objects
 	&lspr[NOLIGHT],     // SPR_BBLS
@@ -298,6 +300,7 @@ light_t *t_lspr[NUMSPRITES] =
 	&lspr[NOLIGHT],     // SPR_TRE3
 	&lspr[NOLIGHT],     // SPR_TRE4
 	&lspr[NOLIGHT],     // SPR_TRE5
+	&lspr[NOLIGHT],     // SPR_TRE6
 
 	// Techno Hill Scenery
 	&lspr[NOLIGHT],     // SPR_THZP
@@ -498,6 +501,9 @@ light_t *t_lspr[NUMSPRITES] =
 	&lspr[NOLIGHT],     // SPR_HSCR
 	&lspr[NOLIGHT],     // SPR_NPRU
 	&lspr[NOLIGHT],     // SPR_CAPS
+	&lspr[INVINCIBLE_L], // SPR_IDYA
+	&lspr[NOLIGHT],     // SPR_NTPN
+	&lspr[NOLIGHT],     // SPR_SHLP
 
 	// Secret badniks and hazards, shhhh
 	&lspr[NOLIGHT],     // SPR_HIVE
@@ -538,9 +544,6 @@ light_t *t_lspr[NUMSPRITES] =
 	&lspr[NOLIGHT],     // SPR_ROIO
 	&lspr[NOLIGHT],     // SPR_ROIP
 
-	// Blue Spheres
-	&lspr[NOLIGHT],     // SPR_BBAL
-
 	// Gravity Well Objects
 	&lspr[NOLIGHT],     // SPR_GWLG
 	&lspr[NOLIGHT],     // SPR_GWLR
diff --git a/src/hu_stuff.c b/src/hu_stuff.c
index a13801388c..456f97bacd 100644
--- a/src/hu_stuff.c
+++ b/src/hu_stuff.c
@@ -89,7 +89,7 @@ patch_t *tallinfin;
 //              coop hud
 //-------------------------------------------
 
-patch_t *emeraldpics[3][7]; // 0 = normal, 1 = tiny, 2 = coinbox
+patch_t *emeraldpics[3][8]; // 0 = normal, 1 = tiny, 2 = coinbox
 static patch_t *emblemicon;
 patch_t *tokenicon;
 static patch_t *exiticon;
@@ -256,6 +256,7 @@ void HU_LoadGraphics(void)
 	emeraldpics[0][4] = W_CachePatchName("CHAOS5", PU_HUDGFX);
 	emeraldpics[0][5] = W_CachePatchName("CHAOS6", PU_HUDGFX);
 	emeraldpics[0][6] = W_CachePatchName("CHAOS7", PU_HUDGFX);
+	emeraldpics[0][7] = W_CachePatchName("CHAOS8", PU_HUDGFX);
 
 	emeraldpics[1][0] = W_CachePatchName("TEMER1", PU_HUDGFX);
 	emeraldpics[1][1] = W_CachePatchName("TEMER2", PU_HUDGFX);
@@ -264,6 +265,7 @@ void HU_LoadGraphics(void)
 	emeraldpics[1][4] = W_CachePatchName("TEMER5", PU_HUDGFX);
 	emeraldpics[1][5] = W_CachePatchName("TEMER6", PU_HUDGFX);
 	emeraldpics[1][6] = W_CachePatchName("TEMER7", PU_HUDGFX);
+	//emeraldpics[1][7] = W_CachePatchName("TEMER8", PU_HUDGFX); -- unused
 
 	emeraldpics[2][0] = W_CachePatchName("EMBOX1", PU_HUDGFX);
 	emeraldpics[2][1] = W_CachePatchName("EMBOX2", PU_HUDGFX);
@@ -272,6 +274,7 @@ void HU_LoadGraphics(void)
 	emeraldpics[2][4] = W_CachePatchName("EMBOX5", PU_HUDGFX);
 	emeraldpics[2][5] = W_CachePatchName("EMBOX6", PU_HUDGFX);
 	emeraldpics[2][6] = W_CachePatchName("EMBOX7", PU_HUDGFX);
+	//emeraldpics[2][7] = W_CachePatchName("EMBOX8", PU_HUDGFX); -- unused
 }
 
 // Initialise Heads up
diff --git a/src/hu_stuff.h b/src/hu_stuff.h
index fb1fa18176..46c47b59e6 100644
--- a/src/hu_stuff.h
+++ b/src/hu_stuff.h
@@ -63,7 +63,7 @@ extern patch_t *tallnum[10];
 extern patch_t *nightsnum[10];
 extern patch_t *lt_font[LT_FONTSIZE];
 extern patch_t *cred_font[CRED_FONTSIZE];
-extern patch_t *emeraldpics[3][7];
+extern patch_t *emeraldpics[3][8];
 extern patch_t *rflagico;
 extern patch_t *bflagico;
 extern patch_t *rmatcico;
diff --git a/src/info.c b/src/info.c
index e791c7728e..f02fba8d2d 100644
--- a/src/info.c
+++ b/src/info.c
@@ -118,10 +118,12 @@ char sprnames[NUMSPRITES + 1][5] =
 	"TOKE", // Special Stage Token
 	"RFLG", // Red CTF Flag
 	"BFLG", // Blue CTF Flag
-	"NWNG", // NiGHTS Wing collectable item.
+	"SPHR", // Sphere
+	"NCHP", // NiGHTS chip
+	"NSTR", // NiGHTS star
 	"EMBM", // Emblem
 	"CEMG", // Chaos Emeralds
-	"EMER", // Emerald Hunt
+	"SHRD", // Emerald hunt shards
 
 	// Interactive Objects
 	"BBLS", // water bubble source
@@ -187,6 +189,7 @@ char sprnames[NUMSPRITES + 1][5] =
 	"TRE3", // Frozen Hillside
 	"TRE4", // Polygon
 	"TRE5", // Bush tree
+	"TRE6", // Spring tree
 
 	// Techno Hill Scenery
 	"THZP", // THZ1 Steam Flower
@@ -392,6 +395,9 @@ char sprnames[NUMSPRITES + 1][5] =
 	"NSCR", // NiGHTS score sprite
 	"NPRU", // Nights Powerups
 	"CAPS", // Capsule thingy for NiGHTS
+	"IDYA", // Ideya
+	"NTPN", // Nightopian
+	"SHLP", // Shleep
 
 	// Secret badniks and hazards, shhhh
 	"HIVE",
@@ -432,9 +438,6 @@ char sprnames[NUMSPRITES + 1][5] =
 	"ROIO",
 	"ROIP",
 
-	// Blue Spheres
-	"BBAL",
-
 	// Gravity Well Objects
 	"GWLG",
 	"GWLR",
@@ -715,7 +718,7 @@ state_t states[NUMSTATES] =
 	{SPR_PLAY, SPR2_TRNS|FF_SPR2SUPER|FF_FULLBRIGHT,  2, {NULL},          0, 0, S_PLAY_SUPER_TRANS4}, // S_PLAY_SUPER_TRANS3
 	{SPR_PLAY, SPR2_TRNS|FF_SPR2SUPER|FF_FULLBRIGHT,  2, {NULL},          0, 0, S_PLAY_SUPER_TRANS5}, // S_PLAY_SUPER_TRANS4
 	{SPR_PLAY, SPR2_TRNS|FF_SPR2SUPER|FF_FULLBRIGHT,  2, {NULL},          0, 0, S_PLAY_SUPER_TRANS6}, // S_PLAY_SUPER_TRANS5
-	{SPR_PLAY, SPR2_TRNS|FF_SPR2SUPER|FF_FULLBRIGHT, 20, {A_FadeOverlay}, 0, 0, S_PLAY_FALL},         // S_PLAY_SUPER_TRANS6
+	{SPR_PLAY, SPR2_TRNS|FF_SPR2SUPER|FF_FULLBRIGHT, 20, {A_FadeOverlay}, 0, 0, S_PLAY_FLOAT},        // S_PLAY_SUPER_TRANS6
 
 	{SPR_NULL, 0, -1, {NULL}, 0, 0, S_OBJPLACE_DUMMY}, //S_OBJPLACE_DUMMY
 
@@ -1021,7 +1024,8 @@ state_t states[NUMSTATES] =
 	{SPR_SPSH, 10,  1, {A_GuardChase}, 0, 0, S_EGGGUARD_RUN4},  // S_EGGGUARD_RUN3
 	{SPR_SPSH, 11,  1, {A_GuardChase}, 0, 0, S_EGGGUARD_RUN1},  // S_EGGGUARD_RUN4
 
-	{SPR_ESHI, 0, 8, {A_EggShield}, 0, 0, S_EGGSHIELD}, // S_EGGSHIELD
+	{SPR_ESHI, 0, 8, {A_EggShield}, 0, 0, S_EGGSHIELD},  // S_EGGSHIELD
+	{SPR_ESHI, 0, TICRATE/2, {NULL}, 0, 0, S_NULL}, // S_EGGSHIELDBREAK
 
 	// Green Snapper
 	{SPR_GSNP, 0, 1,  {A_Look}, 0, 0, S_GSNAPPER_STND}, // S_GSNAPPER_STND
@@ -1591,18 +1595,22 @@ state_t states[NUMSTATES] =
 	// Ring
 	{SPR_RING, FF_ANIMATE|FF_GLOBALANIM, -1, {NULL}, 23, 1, S_RING}, // S_RING
 
-	// Blue Sphere Replacement for special stages
-	{SPR_BBAL, 0, -1, {NULL}, 0, 0, S_NULL}, // S_BLUEBALL
-	{SPR_BBAL, 0, 20, {NULL}, 0, 0, S_NULL}, // S_BLUEBALLSPARK
+	// Blue Sphere for special stages
+	{SPR_SPHR, FF_FULLBRIGHT, -1, {NULL}, 0, 0, S_NULL}, // S_BLUESPHERE
+	{SPR_SPHR, FF_FULLBRIGHT|FF_RANDOMANIM|FF_ANIMATE, -1, {NULL}, 1, 4, S_NULL}, // S_BLUESPHEREBONUS
+	{SPR_SPHR, 0, 20, {NULL}, 0, 0, S_NULL}, // S_BLUESPHERESPARK
 
-	// Gravity Well sprites for Egg Rock's Special Stage
-	{SPR_GWLG, 0, 1, {NULL}, 0, 0, S_GRAVWELLGREEN2}, // S_GRAVWELLGREEN
-	{SPR_GWLG, 1, 1, {NULL}, 0, 0, S_GRAVWELLGREEN3}, // S_GRAVWELLGREEN2
-	{SPR_GWLG, 2, 1, {NULL}, 0, 0, S_GRAVWELLGREEN},  // S_GRAVWELLGREEN3
+	// NiGHTS Chip
+	{SPR_NCHP, FF_FULLBRIGHT|FF_ANIMATE,    -1, {NULL}, 15, 2, S_NULL}, // S_NIGHTSCHIP
+	{SPR_NCHP, FF_FULLBRIGHT|FF_ANIMATE|16, -1, {NULL}, 15, 2, S_NULL}, // S_NIGHTSCHIPBONUS
 
-	{SPR_GWLR, 0, 1, {NULL}, 0, 0, S_GRAVWELLRED2},   // S_GRAVWELLRED
-	{SPR_GWLR, 1, 1, {NULL}, 0, 0, S_GRAVWELLRED3},   // S_GRAVWELLRED2
-	{SPR_GWLR, 2, 1, {NULL}, 0, 0, S_GRAVWELLRED},    // S_GRAVWELLRED3
+	// NiGHTS Star
+	{SPR_NSTR, FF_ANIMATE, -1, {NULL}, 14, 2, S_NULL}, // S_NIGHTSSTAR
+	{SPR_NSTR, 15, -1, {NULL}, 0, 0, S_NULL}, // S_NIGHTSSTARXMAS
+
+	// Gravity Well sprites for Egg Rock's Special Stage
+	{SPR_GWLG, FF_ANIMATE, -1, {NULL}, 2, 1, S_NULL}, // S_GRAVWELLGREEN
+	{SPR_GWLR, FF_ANIMATE, -1, {NULL}, 2, 1, S_NULL}, // S_GRAVWELLRED
 
 	// Individual Team Rings (now with shield attracting action! =P)
 	{SPR_TRNG, FF_ANIMATE|FF_GLOBALANIM, -1, {NULL}, 23, 1, S_TEAMRING},  // S_TEAMRING
@@ -1651,8 +1659,10 @@ state_t states[NUMSTATES] =
 	{SPR_CEMG, FF_FULLBRIGHT|5, -1, {NULL}, 0, 0, S_NULL}, // S_CEMG6
 	{SPR_CEMG, FF_FULLBRIGHT|6, -1, {NULL}, 0, 0, S_NULL}, // S_CEMG7
 
-	// Emeralds (for hunt)
-	{SPR_EMER, 0, -1, {NULL}, 0, 0, S_NULL}, // S_EMER1
+	// Emerald hunt shards
+	{SPR_SHRD, 0, -1, {NULL}, 0, 0, S_NULL}, // S_SHRD1
+	{SPR_SHRD, 1, -1, {NULL}, 0, 0, S_NULL}, // S_SHRD2
+	{SPR_SHRD, 2, -1, {NULL}, 0, 0, S_NULL}, // S_SHRD3
 
 	// Bubble Source
 	{SPR_BBLS, 0, 8, {A_BubbleSpawn}, 2048, 0, S_BUBBLES2}, // S_BUBBLES1
@@ -1934,7 +1944,6 @@ state_t states[NUMSTATES] =
 	{SPR_CBLL, 0, -1, {NULL}, 0, 0, S_NULL}, // S_CANNONBALL1
 
 	{SPR_AROW, 0, -1, {NULL}, 0, 0, S_NULL}, // S_ARROW
-	{SPR_ESHI, 0, TICRATE/2, {NULL}, 0, 0, S_NULL}, // S_TEMPSHI
 	{SPR_AROW, FF_ANIMATE, TICRATE, {A_ArrowBonks}, 7, 2, S_NULL}, // S_ARROWBONK
 
 	{SPR_CFIR, FF_FULLBRIGHT,   2, {NULL}, 0, 0, S_DEMONFIRE2}, // S_DEMONFIRE1
@@ -1963,6 +1972,7 @@ state_t states[NUMSTATES] =
 	{SPR_TRE4, 0, -1, {NULL}, 0, 0, S_NULL}, // S_POLYGONTREE
 	{SPR_TRE5, 0, -1, {NULL}, 0, 0, S_NULL}, // S_BUSHTREE
 	{SPR_TRE5, 1, -1, {NULL}, 0, 0, S_NULL}, // S_BUSHREDTREE
+	{SPR_TRE6, 0, -1, {NULL}, 0, 0, S_NULL}, // S_SPRINGTREE
 
 	// THZ flowers
 	{SPR_THZP, FF_ANIMATE, -1, {NULL},  7, 4, S_NULL}, // S_THZFLOWERA
@@ -3301,9 +3311,6 @@ state_t states[NUMSTATES] =
 	{SPR_NSCR, FF_FULLBRIGHT|18, -1, {NULL}, 0, 0, S_NULL}, // S_NIGHTSCORE90_2
 	{SPR_NSCR, FF_FULLBRIGHT|19, -1, {NULL}, 0, 0, S_NULL}, // S_NIGHTSCORE100_2
 
-	{SPR_NWNG, 0, -1, {NULL}, 0, 0, S_NULL}, // S_NIGHTSWING
-	{SPR_NWNG, 1, -1, {NULL}, 0, 0, S_NULL}, // S_NIGHTSWING_XMAS
-
 	// NiGHTS Paraloop Powerups
 	{SPR_NPRU, 0, -1, {NULL}, 0, 0, S_NULL}, // S_NIGHTSSUPERLOOP
 	{SPR_NPRU, 1, -1, {NULL}, 0, 0, S_NULL}, // S_NIGHTSDRILLREFILL
@@ -3313,7 +3320,7 @@ state_t states[NUMSTATES] =
 
 	{SPR_CAPS, 0, -1, {NULL}, 0, 0, S_NULL}, // S_EGGCAPSULE
 
-	// Orbiting Chaos Emeralds for NiGHTS
+	// Orbiting Chaos Emeralds/Ideya for NiGHTS
 	{SPR_CEMG, FF_FULLBRIGHT,   1, {A_OrbitNights}, ANG2*2, 0, S_ORBITEM1}, // S_ORBITEM1
 	{SPR_CEMG, FF_FULLBRIGHT|1, 1, {A_OrbitNights}, ANG2*2, 0, S_ORBITEM2}, // S_ORBITEM2
 	{SPR_CEMG, FF_FULLBRIGHT|2, 1, {A_OrbitNights}, ANG2*2, 0, S_ORBITEM3}, // S_ORBITEM3
@@ -3322,14 +3329,11 @@ state_t states[NUMSTATES] =
 	{SPR_CEMG, FF_FULLBRIGHT|5, 1, {A_OrbitNights}, ANG2*2, 0, S_ORBITEM6}, // S_ORBITEM6
 	{SPR_CEMG, FF_FULLBRIGHT|6, 1, {A_OrbitNights}, ANG2*2, 0, S_ORBITEM7}, // S_ORBITEM7
 	{SPR_CEMG, FF_FULLBRIGHT|7, 1, {A_OrbitNights}, ANG2*2, 0, S_ORBITEM8}, // S_ORBITEM8
-	{SPR_CEMG, FF_FULLBRIGHT|8, 1, {A_OrbitNights}, ANG2*2, 0, S_ORBITEM8}, // S_ORBITEM9
-	{SPR_CEMG, FF_FULLBRIGHT|9, 1, {A_OrbitNights}, ANG2*2, 0, S_ORBITEM8}, // S_ORBITEM10
-	{SPR_CEMG, FF_FULLBRIGHT|10, 1, {A_OrbitNights}, ANG2*2, 0, S_ORBITEM8}, // S_ORBITEM11
-	{SPR_CEMG, FF_FULLBRIGHT|11, 1, {A_OrbitNights}, ANG2*2, 0, S_ORBITEM8}, // S_ORBITEM12
-	{SPR_CEMG, FF_FULLBRIGHT|12, 1, {A_OrbitNights}, ANG2*2, 0, S_ORBITEM8}, // S_ORBITEM13
-	{SPR_CEMG, FF_FULLBRIGHT|13, 1, {A_OrbitNights}, ANG2*2, 0, S_ORBITEM8}, // S_ORBITEM14
-	{SPR_CEMG, FF_FULLBRIGHT|14, 1, {A_OrbitNights}, ANG2*2, 0, S_ORBITEM8}, // S_ORBITEM15
-	{SPR_CEMG, FF_FULLBRIGHT|15, 1, {A_OrbitNights}, ANG2*2, 0, S_ORBITEM8}, // S_ORBITEM16
+	{SPR_IDYA, FF_TRANS20|FF_FULLBRIGHT,   1, {A_OrbitNights}, ANG2*2, 0, S_ORBIDYA1}, // S_ORBIDYA1
+	{SPR_IDYA, FF_TRANS20|FF_FULLBRIGHT|1, 1, {A_OrbitNights}, ANG2*2, 0, S_ORBIDYA2}, // S_ORBIDYA2
+	{SPR_IDYA, FF_TRANS20|FF_FULLBRIGHT|2, 1, {A_OrbitNights}, ANG2*2, 0, S_ORBIDYA3}, // S_ORBIDYA3
+	{SPR_IDYA, FF_TRANS20|FF_FULLBRIGHT|3, 1, {A_OrbitNights}, ANG2*2, 0, S_ORBIDYA4}, // S_ORBIDYA4
+	{SPR_IDYA, FF_TRANS20|FF_FULLBRIGHT|4, 1, {A_OrbitNights}, ANG2*2, 0, S_ORBIDYA5}, // S_ORBIDYA5
 
 	// Flicky helper for NiGHTS
 	{SPR_FL01, 1, 1, {A_OrbitNights}, ANG2*2, 180 | 0x10000, S_NIGHTOPIANHELPER2}, // S_NIGHTOPIANHELPER1
@@ -3342,6 +3346,25 @@ state_t states[NUMSTATES] =
 	{SPR_FL01, 3, 1, {A_OrbitNights}, ANG2*2, 180 | 0x10000, S_NIGHTOPIANHELPER9}, // S_NIGHTOPIANHELPER8
 	{SPR_FL01, 3, 1, {A_OrbitNights}, ANG2*2, 180 | 0x10000, S_NIGHTOPIANHELPER1}, // S_NIGHTOPIANHELPER9
 
+	// Nightopian
+	{SPR_NTPN, 0, 4, {A_Look}, 0, 0, S_PIAN0}, // S_PIAN0
+	{SPR_NTPN, 0, 4, {A_JetgThink}, 0, 0, S_PIAN2}, // S_PIAN1
+	{SPR_NTPN, 1, 4, {NULL}, 0, 0, S_PIAN3}, // S_PIAN2
+	{SPR_NTPN, 2, 4, {NULL}, 0, 0, S_PIAN4}, // S_PIAN3
+	{SPR_NTPN, 3, 4, {NULL}, 0, 0, S_PIAN5}, // S_PIAN4
+	{SPR_NTPN, 2, 4, {NULL}, 0, 0, S_PIAN6}, // S_PIAN5
+	{SPR_NTPN, 1, 4, {NULL}, 0, 0, S_PIAN1}, // S_PIAN6
+	{SPR_NTPN, 5|FF_ANIMATE, 4, {NULL}, 1, 4, S_PIAN1}, // S_PIANSING
+
+	// Shleep
+	{SPR_SHLP, 0, 15, {NULL}, 0, 0, S_SHLEEP2}, // S_SHLEEP1
+	{SPR_SHLP, 1, 15, {NULL}, 0, 0, S_SHLEEP3}, // S_SHLEEP2
+	{SPR_SHLP, 2, 15, {NULL}, 0, 0, S_SHLEEP4}, // S_SHLEEP3
+	{SPR_SHLP, 1, 15, {NULL}, 0, 0, S_SHLEEP1}, // S_SHLEEP4
+	{SPR_SHLP, 3, 1, {A_Scream},  0, 0, S_SHLEEPBOUNCE2}, // S_SHLEEPBOUNCE1
+	{SPR_SHLP, 3, 1, {A_ZThrust}, 9, 0, S_SHLEEPBOUNCE3}, // S_SHLEEPBOUNCE2
+	{SPR_SHLP, 3, 400, {A_SetObjectFlags}, MF_SLIDEME|MF_ENEMY|MF_BOUNCE|MF_NOCLIP|MF_NOCLIPHEIGHT, 0, S_NULL}, // S_SHLEEPBOUNCE3
+
 	// Secret badniks and hazards, shhhh
 	{SPR_HIVE, 0,  5, {A_Look}, 1, 1, S_HIVEELEMENTAL_LOOK}, // S_HIVEELEMENTAL_LOOK
 	{SPR_HIVE, 0, 14, {A_PlaySound}, sfx_s3k76, 1, S_HIVEELEMENTAL_PREPARE2}, // S_HIVEELEMENTAL_PREPARE1
@@ -4337,7 +4360,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		sfx_s3k7b,       // painsound
 		S_NULL,          // meleestate
 		S_NULL,          // missilestate
-		S_TEMPSHI,       // deathstate
+		S_EGGSHIELDBREAK,// deathstate
 		S_NULL,          // xdeathstate
 		sfx_wbreak,      // deathsound
 		3,               // speed
@@ -5729,9 +5752,9 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		S_NULL          // raisestate
 	},
 
-	{           // MT_BLUEBALL
-		-1,             // doomednum
-		S_BLUEBALL,    // spawnstate
+	{           // MT_BLUESPHERE
+		1706,           // doomednum
+		S_BLUESPHERE,   // spawnstate
 		1000,           // spawnhealth
 		S_NULL,         // seestate
 		sfx_None,       // seesound
@@ -5742,7 +5765,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		sfx_None,       // painsound
 		S_NULL,         // meleestate
 		S_NULL,         // missilestate
-		S_BLUEBALLSPARK, // deathstate
+		S_BLUESPHERESPARK, // deathstate
 		S_NULL,         // xdeathstate
 		sfx_s3k65,      // deathsound
 		38*FRACUNIT,    // speed
@@ -5753,7 +5776,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		0,              // damage
 		sfx_None,       // activesound
 		MF_SLIDEME|MF_SPECIAL|MF_NOGRAVITY|MF_NOCLIPHEIGHT, // flags
-		S_NULL          // raisestate
+		S_BLUESPHEREBONUS // raisestate
 	},
 
 	{           // MT_REDTEAMRING
@@ -5825,7 +5848,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		S_NULL,         // missilestate
 		S_SPRK1,        // deathstate
 		S_NULL,         // xdeathstate
-		sfx_token,      // deathsound
+		sfx_None,       // deathsound
 		0,              // speed
 		8*FRACUNIT,     // radius
 		16*FRACUNIT,    // height
@@ -6103,7 +6126,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 
 	{           // MT_EMERHUNT
 		320,            // doomednum
-		S_EMER1,        // spawnstate
+		S_SHRD1,        // spawnstate
 		1000,           // spawnhealth
 		S_NULL,         // seestate
 		sfx_None,       // seesound
@@ -6118,8 +6141,8 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		S_NULL,         // xdeathstate
 		sfx_cgot,       // deathsound
 		8,              // speed
-		16*FRACUNIT,    // radius
-		32*FRACUNIT,    // height
+		12*FRACUNIT,    // radius
+		42*FRACUNIT,    // height
 		0,              // display offset
 		4,              // mass
 		0,              // damage
@@ -6588,7 +6611,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 	},
 
 	{           // MT_SPIKEBALL
-		-1,            // doomednum
+		521,            // doomednum
 		S_SPIKEBALL1,   // spawnstate
 		1000,           // spawnhealth
 		S_NULL,         // seestate
@@ -6604,7 +6627,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		S_NULL,         // xdeathstate
 		sfx_None,       // deathsound
 		10*FRACUNIT,    // speed
-		4*FRACUNIT,     // radius
+		12*FRACUNIT,    // radius
 		8*FRACUNIT,     // height
 		0,              // display offset
 		DMG_SPIKE,      // mass
@@ -8803,7 +8826,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 
 	{           // MT_GFZTREE
 		806,            // doomednum
-		S_GFZTREE,         // spawnstate
+		S_GFZTREE,      // spawnstate
 		1000,           // spawnhealth
 		S_NULL,         // seestate
 		sfx_None,       // seesound
@@ -8830,7 +8853,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 
 	{           // MT_GFZBERRYTREE
 		807,            // doomednum
-		S_GFZBERRYTREE,         // spawnstate
+		S_GFZBERRYTREE, // spawnstate
 		1000,           // spawnhealth
 		S_NULL,         // seestate
 		sfx_None,       // seesound
@@ -8857,7 +8880,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 
 	{           // MT_GFZCHERRYTREE
 		808,            // doomednum
-		S_GFZCHERRYTREE,         // spawnstate
+		S_GFZCHERRYTREE, // spawnstate
 		1000,           // spawnhealth
 		S_NULL,         // seestate
 		sfx_None,       // seesound
@@ -8884,7 +8907,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 
 	{           // MT_CHECKERTREE
 		810,            // doomednum
-		S_CHECKERTREE,         // spawnstate
+		S_CHECKERTREE,  // spawnstate
 		1000,           // spawnhealth
 		S_NULL,         // seestate
 		sfx_None,       // seesound
@@ -8911,7 +8934,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 
 	{           // MT_CHECKERSUNSETTREE
 		811,            // doomednum
-		S_CHECKERSUNSETTREE,         // spawnstate
+		S_CHECKERSUNSETTREE, // spawnstate
 		1000,           // spawnhealth
 		S_NULL,         // seestate
 		sfx_None,       // seesound
@@ -8938,7 +8961,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 
 	{           // MT_FHZTREE
 		812,            // doomednum
-		S_FHZTREE,         // spawnstate
+		S_FHZTREE,      // spawnstate
 		1000,           // spawnhealth
 		S_NULL,         // seestate
 		sfx_None,       // seesound
@@ -8965,7 +8988,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 
 	{           // MT_FHZPINKTREE
 		813,            // doomednum
-		S_FHZPINKTREE,         // spawnstate
+		S_FHZPINKTREE,  // spawnstate
 		1000,           // spawnhealth
 		S_NULL,         // seestate
 		sfx_None,       // seesound
@@ -8992,7 +9015,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 
 	{           // MT_POLYGONTREE
 		814,            // doomednum
-		S_POLYGONTREE,         // spawnstate
+		S_POLYGONTREE,  // spawnstate
 		1000,           // spawnhealth
 		S_NULL,         // seestate
 		sfx_None,       // seesound
@@ -9019,7 +9042,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 
 	{           // MT_BUSHTREE
 		815,            // doomednum
-		S_BUSHTREE,         // spawnstate
+		S_BUSHTREE,     // spawnstate
 		1000,           // spawnhealth
 		S_NULL,         // seestate
 		sfx_None,       // seesound
@@ -9046,7 +9069,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 
 	{           // MT_BUSHREDTREE
 		816,            // doomednum
-		S_BUSHREDTREE,         // spawnstate
+		S_BUSHREDTREE,  // spawnstate
 		1000,           // spawnhealth
 		S_NULL,         // seestate
 		sfx_None,       // seesound
@@ -9071,6 +9094,33 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		S_NULL          // raisestate
 	},
 
+	{           // MT_SPRINGTREE
+		1600,           // doomednum
+		S_SPRINGTREE,   // spawnstate
+		1000,           // spawnhealth
+		S_NULL,         // seestate
+		sfx_None,       // seesound
+		8,              // reactiontime
+		sfx_None,       // attacksound
+		S_NULL,         // painstate
+		0,              // painchance
+		sfx_None,       // painsound
+		S_NULL,         // meleestate
+		S_NULL,         // missilestate
+		S_NULL,         // deathstate
+		S_NULL,         // xdeathstate
+		sfx_None,       // deathsound
+		0,              // speed
+		16*FRACUNIT,    // radius
+		32*FRACUNIT,    // height
+		0,              // display offset
+		100,            // mass
+		0,              // damage
+		sfx_None,       // activesound
+		MF_NOTHINK|MF_NOBLOCKMAP|MF_NOCLIP|MF_SCENERY, // flags
+		S_NULL          // raisestate
+	},
+
 	{           // MT_THZFLOWER1
 		900,            // doomednum
 		S_THZFLOWERA,    // spawnstate
@@ -14406,7 +14456,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		0,              // painchance
 		sfx_None,       // painsound
 		S_ORBITEM1,     // meleestate
-		S_NULL,         // missilestate
+		S_ORBIDYA1,     // missilestate
 		S_NULL,         // deathstate
 		S_NULL,         // xdeathstate
 		sfx_None,       // deathsound
@@ -16131,11 +16181,11 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		S_NULL          // raisestate
 	},
 
-	{           // MT_NIGHTSWING
-		1706,           // doomednum
-		S_NIGHTSWING,   // spawnstate
+	{           // MT_NIGHTSCHIP
+		-1,             // doomednum
+		S_NIGHTSCHIP,   // spawnstate
 		1000,           // spawnhealth
-		S_NIGHTSWING_XMAS, // seestate
+		S_NULL,         // seestate
 		sfx_None,       // seesound
 		8,              // reactiontime
 		sfx_None,       // attacksound
@@ -16146,14 +16196,41 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		S_NULL,         // missilestate
 		S_SPRK1,        // deathstate
 		S_NULL,         // xdeathstate
-		sfx_None,       // deathsound
+		sfx_ncchip,     // deathsound
 		1,              // speed
-		12*FRACUNIT,    // radius
+		16*FRACUNIT,    // radius
 		24*FRACUNIT,    // height
 		0,              // display offset
 		4,              // mass
 		0,              // damage
-		sfx_ncitem,     // activesound
+		sfx_None,       // activesound
+		MF_SLIDEME|MF_SPECIAL|MF_NOGRAVITY|MF_NOCLIPHEIGHT, // flags
+		S_NIGHTSCHIPBONUS // raisestate
+	},
+
+	{           // MT_NIGHTSSTAR
+		-1,             // doomednum
+		S_NIGHTSSTAR,   // spawnstate
+		1000,           // spawnhealth
+		S_NIGHTSSTARXMAS, // seestate
+		sfx_None,       // seesound
+		8,              // reactiontime
+		sfx_None,       // attacksound
+		S_NULL,         // painstate
+		0,              // painchance
+		sfx_s3k33,      // painsound
+		S_RING,         // meleestate
+		S_NULL,         // missilestate
+		S_SPRK1,        // deathstate
+		S_NULL,         // xdeathstate
+		sfx_ncitem,     // deathsound
+		1,              // speed
+		16*FRACUNIT,    // radius
+		24*FRACUNIT,    // height
+		0,              // display offset
+		4,              // mass
+		0,              // damage
+		sfx_None,       // activesound
 		MF_SLIDEME|MF_SPECIAL|MF_NOGRAVITY|MF_NOCLIPHEIGHT, // flags
 		S_NULL          // raisestate
 	},
@@ -16320,7 +16397,6 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		S_NULL          // raisestate
 	},
 
-	// the flicky that orbits the player when they have a Nightopian helper
 	{           // MT_NIGHTOPIANHELPER
 		-1,             // doomednum
 		S_NIGHTOPIANHELPER1, // spawnstate
@@ -16348,6 +16424,60 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		S_NULL          // raisestate
 	},
 
+	{           // MT_PIAN
+		1602,           // doomednum
+		S_PIAN0,        // spawnstate
+		1000,           // spawnhealth
+		S_PIAN1,        // seestate
+		sfx_None,       // seesound
+		0,              // reactiontime
+		sfx_None,       // attacksound
+		S_NULL,         // painstate
+		200,            // painchance
+		sfx_None,       // painsound
+		S_PIANSING,     // meleestate
+		S_NULL,         // missilestate
+		S_NULL,         // deathstate
+		S_NULL,         // xdeathstate
+		sfx_None,       // deathsound
+		FRACUNIT,       // speed
+		16*FRACUNIT,    // radius
+		32*FRACUNIT,    // height
+		0,              // display offset
+		16,             // mass
+		0,              // damage
+		sfx_None,       // activesound
+		MF_SLIDEME|MF_ENEMY|MF_SPECIAL|MF_SHOOTABLE|MF_NOGRAVITY, // flags
+		S_NULL          // raisestate
+	},
+
+	{           // MT_SHLEEP
+		1601,           // doomednum
+		S_SHLEEP1,      // spawnstate
+		1,           // spawnhealth
+		S_NULL,         // seestate
+		sfx_None,       // seesound
+		0,              // reactiontime
+		sfx_None,       // attacksound
+		S_NULL,         // painstate
+		200,            // painchance
+		sfx_None,       // painsound
+		S_NULL,         // meleestate
+		S_NULL,         // missilestate
+		S_SHLEEPBOUNCE1, // deathstate
+		S_NULL,         // xdeathstate
+		sfx_peww,       // deathsound
+		0,              // speed
+		24*FRACUNIT,    // radius
+		32*FRACUNIT,    // height
+		0,              // display offset
+		16,             // mass
+		0,              // damage
+		sfx_None,       // activesound
+		MF_SLIDEME|MF_ENEMY|MF_SPECIAL|MF_SHOOTABLE|MF_NOGRAVITY, // flags
+		S_NULL          // raisestate
+	},
+
 	{           // MT_HIVEELEMENTAL
 		3190,            // doomednum
 		S_HIVEELEMENTAL_LOOK, // spawnstate
diff --git a/src/info.h b/src/info.h
index 5f84b22d42..4e316efdbf 100644
--- a/src/info.h
+++ b/src/info.h
@@ -337,10 +337,12 @@ typedef enum sprite
 	SPR_TOKE, // Special Stage Token
 	SPR_RFLG, // Red CTF Flag
 	SPR_BFLG, // Blue CTF Flag
-	SPR_NWNG, // NiGHTS Wing collectable item.
+	SPR_SPHR, // Sphere
+	SPR_NCHP, // NiGHTS chip
+	SPR_NSTR, // NiGHTS star
 	SPR_EMBM, // Emblem
 	SPR_CEMG, // Chaos Emeralds
-	SPR_EMER, // Emerald Hunt
+	SPR_SHRD, // Emerald Hunt
 
 	// Interactive Objects
 	SPR_BBLS, // water bubble source
@@ -406,6 +408,7 @@ typedef enum sprite
 	SPR_TRE3, // Frozen Hillside
 	SPR_TRE4, // Polygon
 	SPR_TRE5, // Bush tree
+	SPR_TRE6, // Spring tree
 
 	// Techno Hill Scenery
 	SPR_THZP, // THZ1 Steam Flower
@@ -611,6 +614,9 @@ typedef enum sprite
 	SPR_NSCR, // NiGHTS score sprite
 	SPR_NPRU, // Nights Powerups
 	SPR_CAPS, // Capsule thingy for NiGHTS
+	SPR_IDYA, // Ideya
+	SPR_NTPN, // Nightopian
+	SPR_SHLP, // Shleep
 
 	// Secret badniks and hazards, shhhh
 	SPR_HIVE,
@@ -651,9 +657,6 @@ typedef enum sprite
 	SPR_ROIO,
 	SPR_ROIP,
 
-	// Blue Spheres
-	SPR_BBAL,
-
 	// Gravity Well Objects
 	SPR_GWLG,
 	SPR_GWLR,
@@ -1149,6 +1152,7 @@ typedef enum state
 
 	// Egg Shield for Egg Guard
 	S_EGGSHIELD,
+	S_EGGSHIELDBREAK,
 
 	// Green Snapper
 	S_GSNAPPER_STND,
@@ -1717,17 +1721,21 @@ typedef enum state
 	S_RING,
 
 	// Blue Sphere for special stages
-	S_BLUEBALL,
-	S_BLUEBALLSPARK,
+	S_BLUESPHERE,
+	S_BLUESPHEREBONUS,
+	S_BLUESPHERESPARK,
+
+	// NiGHTS Chip
+	S_NIGHTSCHIP,
+	S_NIGHTSCHIPBONUS,
+
+	// NiGHTS Star
+	S_NIGHTSSTAR,
+	S_NIGHTSSTARXMAS,
 
 	// Gravity Wells for special stages
 	S_GRAVWELLGREEN,
-	S_GRAVWELLGREEN2,
-	S_GRAVWELLGREEN3,
-
 	S_GRAVWELLRED,
-	S_GRAVWELLRED2,
-	S_GRAVWELLRED3,
 
 	// Individual Team Rings
 	S_TEAMRING,
@@ -1776,8 +1784,10 @@ typedef enum state
 	S_CEMG6,
 	S_CEMG7,
 
-	// Emeralds (for hunt)
-	S_EMER1,
+	// Emerald hunt shards
+	S_SHRD1,
+	S_SHRD2,
+	S_SHRD3,
 
 	// Bubble Source
 	S_BUBBLES1,
@@ -2061,7 +2071,6 @@ typedef enum state
 
 	// Arrow
 	S_ARROW,
-	S_TEMPSHI,
 	S_ARROWBONK,
 
 	// Trapgoyle Demon fire
@@ -2091,6 +2100,7 @@ typedef enum state
 	S_POLYGONTREE,
 	S_BUSHTREE,
 	S_BUSHREDTREE,
+	S_SPRINGTREE,
 
 	// THZ flowers
 	S_THZFLOWERA, // THZ1 Steam flower
@@ -3359,9 +3369,6 @@ typedef enum state
 	S_NIGHTSCORE90_2,
 	S_NIGHTSCORE100_2,
 
-	S_NIGHTSWING,
-	S_NIGHTSWING_XMAS,
-
 	// NiGHTS Paraloop Powerups
 	S_NIGHTSSUPERLOOP,
 	S_NIGHTSDRILLREFILL,
@@ -3379,14 +3386,11 @@ typedef enum state
 	S_ORBITEM6,
 	S_ORBITEM7,
 	S_ORBITEM8,
-	S_ORBITEM9,
-	S_ORBITEM10,
-	S_ORBITEM11,
-	S_ORBITEM12,
-	S_ORBITEM13,
-	S_ORBITEM14,
-	S_ORBITEM15,
-	S_ORBITEM16,
+	S_ORBIDYA1,
+	S_ORBIDYA2,
+	S_ORBIDYA3,
+	S_ORBIDYA4,
+	S_ORBIDYA5,
 
 	// "Flicky" helper
 	S_NIGHTOPIANHELPER1,
@@ -3399,6 +3403,25 @@ typedef enum state
 	S_NIGHTOPIANHELPER8,
 	S_NIGHTOPIANHELPER9,
 
+	// Nightopian
+	S_PIAN0,
+	S_PIAN1,
+	S_PIAN2,
+	S_PIAN3,
+	S_PIAN4,
+	S_PIAN5,
+	S_PIAN6,
+	S_PIANSING,
+
+	// Shleep
+	S_SHLEEP1,
+	S_SHLEEP2,
+	S_SHLEEP3,
+	S_SHLEEP4,
+	S_SHLEEPBOUNCE1,
+	S_SHLEEPBOUNCE2,
+	S_SHLEEPBOUNCE3,
+
 	// Secret badniks and hazards, shhhh
 	S_HIVEELEMENTAL_LOOK,
 	S_HIVEELEMENTAL_PREPARE1,
@@ -3694,7 +3717,7 @@ typedef enum mobj_type
 	// Collectible Items
 	MT_RING,
 	MT_FLINGRING, // Lost ring
-	MT_BLUEBALL,  // Blue sphere replacement for special stages
+	MT_BLUESPHERE,  // Blue sphere replacement for special stages
 	MT_REDTEAMRING,  //Rings collectable by red team.
 	MT_BLUETEAMRING, //Rings collectable by blue team.
 	MT_TOKEN, // Special Stage token for special stage
@@ -3838,6 +3861,7 @@ typedef enum mobj_type
 	MT_POLYGONTREE,
 	MT_BUSHTREE,
 	MT_BUSHREDTREE,
+	MT_SPRINGTREE,
 
 	// Techno Hill Scenery
 	MT_THZFLOWER1,
@@ -4150,7 +4174,8 @@ typedef enum mobj_type
 	MT_HOOPCOLLIDE, // Collision detection for NiGHTS hoops
 	MT_HOOPCENTER, // Center of a hoop
 	MT_NIGHTSCORE,
-	MT_NIGHTSWING,
+	MT_NIGHTSCHIP, // NiGHTS Chip
+	MT_NIGHTSSTAR, // NiGHTS Star
 	MT_NIGHTSSUPERLOOP,
 	MT_NIGHTSDRILLREFILL,
 	MT_NIGHTSHELPER,
@@ -4158,6 +4183,8 @@ typedef enum mobj_type
 	MT_NIGHTSLINKFREEZE,
 	MT_EGGCAPSULE,
 	MT_NIGHTOPIANHELPER, // the actual helper object that orbits you
+	MT_PIAN, // decorative singing friend
+	MT_SHLEEP, // almost-decorative sleeping enemy
 
 	// Secret badniks and hazards, shhhh
 	MT_HIVEELEMENTAL,
diff --git a/src/lua_hud.h b/src/lua_hud.h
index beaca7883e..c1479d5efe 100644
--- a/src/lua_hud.h
+++ b/src/lua_hud.h
@@ -24,7 +24,7 @@ enum hud {
 	// NiGHTS mode
 	hud_nightslink,
 	hud_nightsdrill,
-	hud_nightsrings,
+	hud_nightsspheres,
 	hud_nightsscore,
 	hud_nightstime,
 	hud_nightsrecords,
diff --git a/src/lua_playerlib.c b/src/lua_playerlib.c
index 12b2646d04..8a1079c364 100644
--- a/src/lua_playerlib.c
+++ b/src/lua_playerlib.c
@@ -130,6 +130,8 @@ static int player_get(lua_State *L)
 		lua_pushangle(L, plr->drawangle);
 	else if (fastcmp(field,"rings"))
 		lua_pushinteger(L, plr->rings);
+	else if (fastcmp(field,"spheres"))
+		lua_pushinteger(L, plr->spheres);
 	else if (fastcmp(field,"pity"))
 		lua_pushinteger(L, plr->pity);
 	else if (fastcmp(field,"currentweapon"))
@@ -294,8 +296,8 @@ static int player_get(lua_State *L)
 		lua_pushinteger(L, plr->startedtime);
 	else if (fastcmp(field,"finishedtime"))
 		lua_pushinteger(L, plr->finishedtime);
-	else if (fastcmp(field,"finishedrings"))
-		lua_pushinteger(L, plr->finishedrings);
+	else if (fastcmp(field,"finishedspheres"))
+		lua_pushinteger(L, plr->finishedspheres);
 	else if (fastcmp(field,"marescore"))
 		lua_pushinteger(L, plr->marescore);
 	else if (fastcmp(field,"lastmarescore"))
@@ -396,6 +398,8 @@ static int player_set(lua_State *L)
 		plr->drawangle = luaL_checkangle(L, 3);
 	else if (fastcmp(field,"rings"))
 		plr->rings = (INT32)luaL_checkinteger(L, 3);
+	else if (fastcmp(field,"spheres"))
+		plr->spheres = (INT32)luaL_checkinteger(L, 3);
 	else if (fastcmp(field,"pity"))
 		plr->pity = (SINT8)luaL_checkinteger(L, 3);
 	else if (fastcmp(field,"currentweapon"))
@@ -448,7 +452,7 @@ static int player_set(lua_State *L)
 	else if (fastcmp(field,"followitem"))
 		plr->followitem = luaL_checkinteger(L, 3);
 	else if (fastcmp(field,"followmobj"))
-		plr->followmobj = *((mobj_t **)luaL_checkudata(L, 3, META_MOBJ));
+		P_SetTarget(&plr->followmobj, *((mobj_t **)luaL_checkudata(L, 3, META_MOBJ)));
 	else if (fastcmp(field,"actionspd"))
 		plr->actionspd = (INT32)luaL_checkinteger(L, 3);
 	else if (fastcmp(field,"mindash"))
@@ -570,8 +574,8 @@ static int player_set(lua_State *L)
 		plr->startedtime = (tic_t)luaL_checkinteger(L, 3);
 	else if (fastcmp(field,"finishedtime"))
 		plr->finishedtime = (tic_t)luaL_checkinteger(L, 3);
-	else if (fastcmp(field,"finishedrings"))
-		plr->finishedrings = (INT16)luaL_checkinteger(L, 3);
+	else if (fastcmp(field,"finishedspheres"))
+		plr->finishedspheres = (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/m_cheat.c b/src/m_cheat.c
index 174e2780dd..81a8702fda 100644
--- a/src/m_cheat.c
+++ b/src/m_cheat.c
@@ -728,7 +728,7 @@ void Command_Setrings_f(void)
 		// P_GivePlayerRings does value clamping
 		players[consoleplayer].rings = 0;
 		P_GivePlayerRings(&players[consoleplayer], atoi(COM_Argv(1)));
-		if (!G_IsSpecialStage(gamemap) || !useNightsSS)
+		if (!G_IsSpecialStage(gamemap) || !(maptol & TOL_NIGHTS))
 			players[consoleplayer].totalring -= atoi(COM_Argv(1)); //undo totalring addition done in P_GivePlayerRings
 
 		G_SetGameModified(multiplayer);
@@ -1010,7 +1010,7 @@ void OP_NightsObjectplace(player_t *player)
 		mt->options = (UINT16)((player->mo->z - fheight)>>FRACBITS);
 		mt->angle = (INT16)(mt->angle+(INT16)((FixedInt(FixedDiv(temp*FRACUNIT, 360*(FRACUNIT/256))))<<8));
 
-		P_SpawnHoopsAndRings(mt);
+		P_SpawnHoopsAndRings(mt, false);
 	}
 
 	// This places a bumper!
@@ -1024,26 +1024,26 @@ void OP_NightsObjectplace(player_t *player)
 		P_SpawnMapThing(mt);
 	}
 
-	// This places a ring!
+	// This places a sphere!
 	if (cmd->buttons & BT_WEAPONNEXT)
 	{
 		player->pflags |= PF_ATTACKDOWN;
 		if (!OP_HeightOkay(player, false))
 			return;
 
-		mt = OP_CreateNewMapThing(player, (UINT16)mobjinfo[MT_RING].doomednum, false);
-		P_SpawnHoopsAndRings(mt);
+		mt = OP_CreateNewMapThing(player, (UINT16)mobjinfo[MT_BLUESPHERE].doomednum, false);
+		P_SpawnHoopsAndRings(mt, false);
 	}
 
-	// This places a wing item!
+	// This places a ring!
 	if (cmd->buttons & BT_WEAPONPREV)
 	{
 		player->pflags |= PF_ATTACKDOWN;
 		if (!OP_HeightOkay(player, false))
 			return;
 
-		mt = OP_CreateNewMapThing(player, (UINT16)mobjinfo[MT_NIGHTSWING].doomednum, false);
-		P_SpawnHoopsAndRings(mt);
+		mt = OP_CreateNewMapThing(player, (UINT16)mobjinfo[MT_RING].doomednum, false);
+		P_SpawnHoopsAndRings(mt, false);
 	}
 
 	// This places a custom object as defined in the console cv_mapthingnum.
@@ -1077,12 +1077,12 @@ void OP_NightsObjectplace(player_t *player)
 
 		if (mt->type == 300 // Ring
 		|| mt->type == 308 || mt->type == 309 // Team Rings
-		|| mt->type == 1706 // Nights Wing
+		|| mt->type == 1706 // Sphere
 		|| (mt->type >= 600 && mt->type <= 609) // Placement patterns
 		|| mt->type == 1705 || mt->type == 1713 // NiGHTS Hoops
 		|| mt->type == 1800) // Mario Coin
 		{
-			P_SpawnHoopsAndRings(mt);
+			P_SpawnHoopsAndRings(mt, false);
 		}
 		else
 			P_SpawnMapThing(mt);
@@ -1227,7 +1227,7 @@ void OP_ObjectplaceMovement(player_t *player)
 		|| mt->type == 1705 || mt->type == 1713 // NiGHTS Hoops
 		|| mt->type == 1800) // Mario Coin
 		{
-			P_SpawnHoopsAndRings(mt);
+			P_SpawnHoopsAndRings(mt, false);
 		}
 		else
 			P_SpawnMapThing(mt);
diff --git a/src/p_enemy.c b/src/p_enemy.c
index 2c55210b76..2f1fa842e3 100644
--- a/src/p_enemy.c
+++ b/src/p_enemy.c
@@ -8604,10 +8604,12 @@ void A_OrbitNights(mobj_t* actor)
 		return;
 #endif
 
-	if (!actor->target || !actor->target->player ||
-	    !(actor->target->player->powers[pw_carry] == CR_NIGHTSMODE) || !actor->target->player->nightstime
+	if (!actor->target
+	|| (actor->target->player &&
+		// if NiGHTS special stage and not NiGHTSmode.
+	    (((maptol & TOL_NIGHTS) && G_IsSpecialStage(gamemap) && !(actor->target->player->powers[pw_carry] == CR_NIGHTSMODE))
 	    // Also remove this object if they no longer have a NiGHTS helper
-		|| (ishelper && !actor->target->player->powers[pw_nights_helper]))
+		|| (ishelper && !actor->target->player->powers[pw_nights_helper]))))
 	{
 		P_RemoveMobj(actor);
 		return;
@@ -8633,7 +8635,7 @@ void A_OrbitNights(mobj_t* actor)
 		}
 		P_SetThingPosition(actor);
 
-		if (ishelper) // Flash a helper that's about to be removed.
+		if (ishelper && actor->target->player) // Flash a helper that's about to be removed.
 		{
 			if ((actor->target->player->powers[pw_nights_helper] < TICRATE)
 			&& (actor->target->player->powers[pw_nights_helper] & 1))
@@ -11185,7 +11187,7 @@ void A_FlameParticle(mobj_t *actor)
 //
 // Description: Makes a pretty overlay (primarily for super/NiGHTS transformation).
 //
-// var1 = bit 1 = don't halt momentum, bit 2 = don't make fast, bit 3 = don't set tracer
+// var1 = bit 1 = bit 1 = don't make fast, bit 2 = don't set tracer
 // var2 = unused
 //
 void A_FadeOverlay(mobj_t *actor)
@@ -11199,13 +11201,10 @@ void A_FadeOverlay(mobj_t *actor)
 		return;
 #endif
 
-	if (!(locvar1 & 1))
-		actor->momx = actor->momy = actor->momz = 0;
-
 	fade = P_SpawnGhostMobj(actor);
 	fade->frame = actor->frame;
 
-	if (!(locvar1 & 2))
+	if (!(locvar1 & 1))
 	{
 		fade->fuse = 15;
 		fade->flags2 |= MF2_BOSSNOTRAP;
@@ -11213,7 +11212,7 @@ void A_FadeOverlay(mobj_t *actor)
 	else
 		fade->fuse = 20;
 
-	if (!(locvar1 & 4))
+	if (!(locvar1 & 2))
 		P_SetTarget(&actor->tracer, fade);
 }
 
diff --git a/src/p_inter.c b/src/p_inter.c
index fed459d6a9..33a5abcd2c 100644
--- a/src/p_inter.c
+++ b/src/p_inter.c
@@ -23,6 +23,7 @@
 #include "hu_stuff.h"
 #include "lua_hook.h"
 #include "m_cond.h" // unlockables, emblems, etc
+#include "p_setup.h"
 #include "m_cheat.h" // objectplace
 #include "m_misc.h"
 #include "v_video.h" // video flags for CEchos
@@ -471,39 +472,48 @@ void P_TouchSpecialThing(mobj_t *special, mobj_t *toucher, boolean heightcheck)
 			/* FALLTHRU */
 		case MT_RING:
 		case MT_FLINGRING:
+		case MT_COIN:
+		case MT_FLINGCOIN:
 			if (!(P_CanPickupItem(player, false)))
 				return;
 
 			special->momx = special->momy = special->momz = 0;
 			P_GivePlayerRings(player, 1);
 
-			if ((maptol & TOL_NIGHTS) && special->type != MT_FLINGRING)
+			if ((maptol & TOL_NIGHTS) && special->type != MT_FLINGRING && special->type != MT_FLINGCOIN)
 				P_DoNightsScore(player);
 			break;
-
-		case MT_COIN:
-		case MT_FLINGCOIN:
+		case MT_BLUESPHERE:
 			if (!(P_CanPickupItem(player, false)))
 				return;
 
-			special->momx = special->momy = 0;
-			P_GivePlayerRings(player, 1);
+			special->momx = special->momy = special->momz = 0;
+			P_GivePlayerSpheres(player, 1);
 
-			if ((maptol & TOL_NIGHTS) && special->type != MT_FLINGCOIN)
+			special->destscale = ((player->powers[pw_carry] == CR_NIGHTSMODE) ? 4 : 2)*special->scale;
+			if (states[special->info->deathstate].tics > 0)
+				special->scalespeed = FixedDiv(FixedDiv(special->destscale, special->scale), states[special->info->deathstate].tics<<FRACBITS);
+			else
+				special->scalespeed = 4*FRACUNIT/5;
+
+			if (maptol & TOL_NIGHTS)
 				P_DoNightsScore(player);
 			break;
-		case MT_BLUEBALL:
+		case MT_NIGHTSCHIP:
 			if (!(P_CanPickupItem(player, false)))
 				return;
 
-			P_GivePlayerRings(player, 1);
+			special->momx = special->momy = special->momz = 0;
+			P_GivePlayerSpheres(player, 1);
+
+			if (maptol & TOL_NIGHTS)
+				P_DoNightsScore(player);
+			break;
+		case MT_NIGHTSSTAR:
+			if (!(P_CanPickupItem(player, false)))
+				return;
 
 			special->momx = special->momy = special->momz = 0;
-			special->destscale = FixedMul(8*FRACUNIT, special->scale);
-			if (states[special->info->deathstate].tics > 0)
-				special->scalespeed = FixedDiv(FixedDiv(special->destscale, special->scale), states[special->info->deathstate].tics<<FRACBITS);
-			else
-				special->scalespeed = 4*FRACUNIT/5;
 
 			if (maptol & TOL_NIGHTS)
 				P_DoNightsScore(player);
@@ -562,7 +572,10 @@ void P_TouchSpecialThing(mobj_t *special, mobj_t *toucher, boolean heightcheck)
 			P_AddPlayerScore(player, 1000);
 
 			if (gametype != GT_COOP || modeattacking) // score only?
+			{
+				S_StartSound(toucher, sfx_chchng);
 				break;
+			}
 
 			tokenlist += special->health;
 
@@ -574,10 +587,17 @@ void P_TouchSpecialThing(mobj_t *special, mobj_t *toucher, boolean heightcheck)
 					players->gotcontinue = true;
 					if (P_IsLocalPlayer(player))
 						S_StartSound(NULL, sfx_s3kac);
+					else
+						S_StartSound(toucher, sfx_chchng);
 				}
+				else
+					S_StartSound(toucher, sfx_chchng);
 			}
 			else
+			{
 				token++;
+				S_StartSound(toucher, sfx_token);
+			}
 
 			break;
 
@@ -602,7 +622,7 @@ void P_TouchSpecialThing(mobj_t *special, mobj_t *toucher, boolean heightcheck)
 
 					players[i].exiting = (14*TICRATE)/5 + 1;
 				}
-				S_StartSound(NULL, sfx_lvpass);
+				//S_StartSound(NULL, sfx_lvpass);
 			}
 			break;
 
@@ -733,45 +753,71 @@ void P_TouchSpecialThing(mobj_t *special, mobj_t *toucher, boolean heightcheck)
 // NiGHTS gameplay items and powerups //
 // ********************************** //
 		case MT_NIGHTSDRONE:
-			if (player->bot)
-				return;
-			if (player->exiting)
-				return;
-			if (player->bonustime)
 			{
-				if (G_IsSpecialStage(gamemap)) //After-mare bonus time/emerald reward in special stages.
+				boolean spec = G_IsSpecialStage(gamemap);
+				if (player->bot)
+					return;
+				if (player->exiting)
+					return;
+				if (player->bonustime)
 				{
-					// only allow the player with the emerald in-hand to leave.
-					if (toucher->tracer
-					&& toucher->tracer->type == MT_GOTEMERALD)
+					if (spec) //After-mare bonus time/emerald reward in special stages.
 					{
+						// only allow the player with the emerald in-hand to leave.
+						if (toucher->tracer
+						&& toucher->tracer->type == MT_GOTEMERALD)
+						{}
+						else // Make sure that SOMEONE has the emerald, at least!
+						{
+							for (i = 0; i < MAXPLAYERS; i++)
+								if (playeringame[i] && players[i].playerstate == PST_LIVE
+								&& players[i].mo->tracer
+								&& players[i].mo->tracer->type == MT_GOTEMERALD)
+									return;
+							// Well no one has an emerald, so exit anyway!
+						}
+						P_GiveEmerald(false);
+						// Don't play Ideya sound in special stage mode
 					}
-					else // Make sure that SOMEONE has the emerald, at least!
+					else
+						S_StartSound(toucher, special->info->activesound);
+				}
+				else //Initial transformation. Don't allow second chances in special stages!
+				{
+					if (player->powers[pw_carry] == CR_NIGHTSMODE)
+						return;
+
+					S_StartSound(toucher, sfx_supert);
+				}
+				P_SwitchSpheresBonusMode(false);
+				if (!(netgame || multiplayer) && !(player->powers[pw_carry] == CR_NIGHTSMODE))
+					P_SetTarget(&special->tracer, toucher);
+				P_NightserizePlayer(player, special->health); // Transform!
+				if (!spec)
+				{
+					if (toucher->tracer) // Move the ideya over to the drone!
 					{
-						for (i = 0; i < MAXPLAYERS; i++)
-							if (playeringame[i] && players[i].playerstate == PST_LIVE
-							&& players[i].mo->tracer
-							&& players[i].mo->tracer->type == MT_GOTEMERALD)
-								return;
-						// Well no one has an emerald, so exit anyway!
+						mobj_t *hnext = special->hnext;
+						P_SetTarget(&special->hnext, toucher->tracer);
+						P_SetTarget(&special->hnext->hnext, hnext); // Buffalo buffalo Buffalo buffalo buffalo buffalo Buffalo buffalo.
+						P_SetTarget(&special->hnext->target, special);
+						P_SetTarget(&toucher->tracer, NULL);
+						if (hnext)
+						{
+							special->hnext->extravalue1 = (angle_t)(hnext->extravalue1 - 72*ANG1);
+							if (special->hnext->extravalue1 > hnext->extravalue1)
+								special->hnext->extravalue1 -= (72*ANG1)/special->hnext->extravalue1;
+						}
+					}
+					if (player->exiting) // ...then move it back?
+					{
+						mobj_t *hnext = special;
+						while ((hnext = hnext->hnext))
+							P_SetTarget(&hnext->target, toucher);
 					}
-					P_GiveEmerald(false);
-					// Don't play Ideya sound in special stage mode
 				}
-				else
-					S_StartSound(toucher, special->info->activesound);
-			}
-			else //Initial transformation. Don't allow second chances in special stages!
-			{
-				if (player->powers[pw_carry] == CR_NIGHTSMODE)
-					return;
-
-				S_StartSound(toucher, sfx_supert);
+				return;
 			}
-			if (!(netgame || multiplayer) && !(player->powers[pw_carry] == CR_NIGHTSMODE))
-				P_SetTarget(&special->tracer, toucher);
-			P_NightserizePlayer(player, special->health); // Transform!
-			return;
 		case MT_NIGHTSLOOPHELPER:
 			// One second delay
 			if (special->fuse < toucher->fuse - TICRATE)
@@ -876,8 +922,8 @@ void P_TouchSpecialThing(mobj_t *special, mobj_t *toucher, boolean heightcheck)
 						}
 					}
 
-					if (!(mo2->type == MT_NIGHTSWING || mo2->type == MT_RING || mo2->type == MT_COIN
-					   || mo2->type == MT_BLUEBALL
+					if (!(mo2->type == MT_RING || mo2->type == MT_COIN || mo2->type == MT_BLUESPHERE
+						|| mo2->type == MT_NIGHTSCHIP || mo2->type == MT_NIGHTSSTAR
 					   || ((mo2->type == MT_EMBLEM) && (mo2->reactiontime & GE_NIGHTSPULL))))
 						continue;
 
@@ -903,16 +949,16 @@ void P_TouchSpecialThing(mobj_t *special, mobj_t *toucher, boolean heightcheck)
 				return;
 
 			if (G_IsSpecialStage(gamemap) && !player->exiting)
-			{ // In special stages, share rings. Everyone gives up theirs to the player who touched the capsule
+			{ // In special stages, share spheres. Everyone gives up theirs to the player who touched the capsule
 				for (i = 0; i < MAXPLAYERS; i++)
-					if (playeringame[i] && (&players[i] != player) && players[i].rings > 0)
+					if (playeringame[i] && (&players[i] != player) && players[i].spheres > 0)
 					{
-						player->rings += players[i].rings;
-						players[i].rings = 0;
+						player->spheres += players[i].spheres;
+						players[i].spheres = 0;
 					}
 			}
 
-			if (player->rings <= 0 || player->exiting)
+			if (player->spheres <= 0 || player->exiting)
 				return;
 
 			// Mark the player as 'pull into the capsule'
@@ -1120,17 +1166,6 @@ void P_TouchSpecialThing(mobj_t *special, mobj_t *toucher, boolean heightcheck)
 				HU_DoCEcho(M_GetText("\\\\\\\\\\\\\\\\Link Freeze"));
 			}
 			break;
-		case MT_NIGHTSWING:
-			if (G_IsSpecialStage(gamemap) && useNightsSS)
-			{ // Pseudo-ring.
-				S_StartSound(toucher, special->info->painsound);
-				player->totalring++;
-			}
-			else
-				S_StartSound(toucher, special->info->activesound);
-
-			P_DoNightsScore(player);
-			break;
 		case MT_HOOPCOLLIDE:
 			// This produces a kind of 'domino effect' with the hoop's pieces.
 			for (; special->hprev != NULL; special = special->hprev); // Move to the first sprite in the hoop
@@ -1496,12 +1531,6 @@ void P_TouchSpecialThing(mobj_t *special, mobj_t *toucher, boolean heightcheck)
 			player->pflags |= PF_JUMPSTASIS;
 
 			return;
-		case MT_SPECIALSPIKEBALL:
-			if (!useNightsSS && G_IsSpecialStage(gamemap)) // Only for old special stages
-				P_SpecialStageDamage(player, special, NULL);
-			else
-				P_DamageMobj(toucher, special, special, 1, 0);
-			return;
 		case MT_EGGMOBILE2_POGO:
 			// sanity checks
 			if (!special->target || !special->target->health)
@@ -2070,8 +2099,8 @@ void P_KillMobj(mobj_t *target, mobj_t *inflictor, mobj_t *source, UINT8 damaget
 	if (inflictor && (inflictor->type == MT_SHELL || inflictor->type == MT_FIREBALL))
 		P_SetTarget(&target->tracer, inflictor);
 
-	if (!useNightsSS && G_IsSpecialStage(gamemap) && target->player && sstimer > 6)
-		sstimer = 6; // Just let P_Ticker take care of the rest.
+	if (!(maptol & TOL_NIGHTS) && G_IsSpecialStage(gamemap) && target->player && target->player->nightstime > 6)
+		target->player->nightstime = 6; // Just let P_Ticker take care of the rest.
 
 	if (target->flags & (MF_ENEMY|MF_BOSS))
 		target->momx = target->momy = target->momz = 0;
@@ -2633,20 +2662,10 @@ static inline void P_NiGHTSDamage(mobj_t *target, mobj_t *source)
 			player->drillmeter -= 5*20;
 		else
 		{
-			if (source && source->player)
-			{
-				if (player->nightstime > 20*TICRATE)
-					player->nightstime -= 20*TICRATE;
-				else
-					player->nightstime = 1;
-			}
+			if (player->nightstime > 5*TICRATE)
+				player->nightstime -= 5*TICRATE;
 			else
-			{
-				if (player->nightstime > 5*TICRATE)
-					player->nightstime -= 5*TICRATE;
-				else
-					player->nightstime = 1;
-			}
+				player->nightstime = 1;
 		}
 
 		if (player->pflags & PF_TRANSFERTOCLOSEST)
@@ -2670,7 +2689,7 @@ static inline void P_NiGHTSDamage(mobj_t *target, mobj_t *source)
 			&& player->nightstime < 10*TICRATE)
 		{
 			//S_StartSound(NULL, sfx_timeup); // that creepy "out of time" music from NiGHTS. Dummied out, as some on the dev team thought it wasn't Sonic-y enough (Mystic, notably). Uncomment to restore. -SH
-			S_ChangeMusicInternal("_drown",false);
+			S_ChangeMusicInternal((((maptol & TOL_NIGHTS) && !G_IsSpecialStage(gamemap)) ? "_ntime" : "_drown"), false);
 		}
 	}
 }
@@ -3000,11 +3019,13 @@ static void P_RingDamage(player_t *player, mobj_t *inflictor, mobj_t *source, IN
 // P_SpecialStageDamage
 //
 // Do old special stage-style damaging
-// Removes 10 rings from the player, or knocks off their shield if they have one.
+// Removes 5 seconds from the player, or knocks off their shield if they have one.
 // If they don't have anything, just knock the player back anyway (this doesn't kill them).
 //
 void P_SpecialStageDamage(player_t *player, mobj_t *inflictor, mobj_t *source)
 {
+	tic_t oldnightstime = player->nightstime;
+
 	if (player->powers[pw_invulnerability] || player->powers[pw_flashing] || player->powers[pw_super])
 		return;
 
@@ -3015,17 +3036,24 @@ void P_SpecialStageDamage(player_t *player, mobj_t *inflictor, mobj_t *source)
 	}
 	else
 	{
-		P_PlayRinglossSound(player->mo);
-		if (player->rings >= 10)
-			player->rings -= 10;
+		S_StartSound(player->mo, sfx_nghurt);
+		if (player->nightstime > 5*TICRATE)
+			player->nightstime -= 5*TICRATE;
 		else
-			player->rings = 0;
+			player->nightstime = 0;
 	}
 
 	P_DoPlayerPain(player, inflictor, source);
 
 	if (gametype == GT_CTF && player->gotflag & (GF_REDFLAG|GF_BLUEFLAG))
 		P_PlayerFlagBurst(player, false);
+
+	if (oldnightstime > 10*TICRATE
+		&& player->nightstime < 10*TICRATE)
+	{
+		//S_StartSound(NULL, sfx_timeup); // that creepy "out of time" music from NiGHTS. Dummied out, as some on the dev team thought it wasn't Sonic-y enough (Mystic, notably). Uncomment to restore. -SH
+		S_ChangeMusicInternal((((maptol & TOL_NIGHTS) && !G_IsSpecialStage(gamemap)) ? "_ntime" : "_drown"), false);
+	}
 }
 
 /** Damages an object, which may or may not be a player.
@@ -3175,6 +3203,12 @@ boolean P_DamageMobj(mobj_t *target, mobj_t *inflictor, mobj_t *source, INT32 da
 			return true;
 		}
 
+		if (G_IsSpecialStage(gamemap))
+		{
+			P_SpecialStageDamage(player, inflictor, source);
+			return true;
+		}
+
 		if (!force && inflictor && inflictor->flags & MF_FIRE)
 		{
 			if (player->powers[pw_shield] & SH_PROTECTFIRE)
diff --git a/src/p_local.h b/src/p_local.h
index da4c70b0ff..682fb7b558 100644
--- a/src/p_local.h
+++ b/src/p_local.h
@@ -146,6 +146,7 @@ void P_SpawnShieldOrb(player_t *player);
 void P_SwitchShield(player_t *player, UINT16 shieldtype);
 mobj_t *P_SpawnGhostMobj(mobj_t *mobj);
 void P_GivePlayerRings(player_t *player, INT32 num_rings);
+void P_GivePlayerSpheres(player_t *player, INT32 num_spheres);
 void P_GivePlayerLives(player_t *player, INT32 numlives);
 void P_GiveCoopLives(player_t *player, INT32 numlives, boolean sound);
 UINT8 P_GetNextEmerald(void);
diff --git a/src/p_mobj.c b/src/p_mobj.c
index dbf5f5880a..587615448b 100644
--- a/src/p_mobj.c
+++ b/src/p_mobj.c
@@ -2513,7 +2513,9 @@ static boolean P_ZMovement(mobj_t *mo)
 
 		case MT_RING: // Ignore still rings
 		case MT_COIN:
-		case MT_BLUEBALL:
+		case MT_BLUESPHERE:
+		case MT_NIGHTSCHIP:
+		case MT_NIGHTSSTAR:
 		case MT_REDTEAMRING:
 		case MT_BLUETEAMRING:
 		case MT_FLINGRING:
@@ -2550,10 +2552,6 @@ static boolean P_ZMovement(mobj_t *mo)
 			if (!(mo->momx || mo->momy || mo->momz))
 				return true;
 			break;
-		case MT_NIGHTSWING:
-			if (!(mo->momx || mo->momy || mo->momz))
-				return true;
-			break;
 		case MT_FLAMEJET:
 		case MT_VERTICALFLAMEJET:
 			if (!(mo->flags & MF_BOUNCE))
@@ -7225,7 +7223,7 @@ void P_MobjThinker(mobj_t *mobj)
 	else if (mobj->health <= 0) // Dead things think differently than the living.
 		switch (mobj->type)
 		{
-		case MT_BLUEBALL:
+		case MT_BLUESPHERE:
 			if ((mobj->tics>>2)+1 > 0 && (mobj->tics>>2)+1 <= tr_trans60) // tr_trans50 through tr_trans90, shifting once every second frame
 				mobj->frame = (NUMTRANSMAPS-((mobj->tics>>2)+1))<<FF_TRANSSHIFT;
 			else // tr_trans60 otherwise
@@ -7590,7 +7588,7 @@ void P_MobjThinker(mobj_t *mobj)
 					P_SetTarget(&mobj->target, NULL);
 					for (i = 0; i < MAXPLAYERS; i++)
 						if (playeringame[i] && players[i].mo
-						&& players[i].mare == mobj->threshold && players[i].rings > 0)
+						&& players[i].mare == mobj->threshold && players[i].spheres > 0)
 						{
 							fixed_t dist = P_AproxDistance(players[i].mo->x - mobj->x, players[i].mo->y - mobj->y);
 							if (dist < shortest)
@@ -7775,8 +7773,8 @@ void P_MobjThinker(mobj_t *mobj)
 							}
 						if (!bonustime)
 						{
-							mobj->flags &= ~MF_NOGRAVITY;
-							P_SetMobjState(mobj, S_NIGHTSDRONE1);
+							/*mobj->flags &= ~MF_NOGRAVITY;
+							P_SetMobjState(mobj, S_NIGHTSDRONE1);*/
 							mobj->flags2 |= MF2_DONTDRAW;
 						}
 					}
@@ -7848,7 +7846,9 @@ void P_MobjThinker(mobj_t *mobj)
 							mobj->flags2 &= ~MF2_DONTDRAW;
 					}
 					mobj->angle += ANG10;
-					if (mobj->z <= mobj->floorz)
+					if (mobj->flags2 & MF2_DONTDRAW)
+						mobj->momz = 0;
+					else if (mobj->z <= mobj->floorz)
 						mobj->momz = 5*FRACUNIT;
 				}
 				break;
@@ -7881,7 +7881,9 @@ void P_MobjThinker(mobj_t *mobj)
 				break;
 			case MT_RING:
 			case MT_COIN:
-			case MT_BLUEBALL:
+			case MT_BLUESPHERE:
+			case MT_NIGHTSCHIP:
+			case MT_NIGHTSSTAR:
 			case MT_REDTEAMRING:
 			case MT_BLUETEAMRING:
 				// No need to check water. Who cares?
@@ -7899,10 +7901,6 @@ void P_MobjThinker(mobj_t *mobj)
 				else
 					A_AttractChase(mobj);
 				break;
-			case MT_NIGHTSWING:
-				if (mobj->flags2 & MF2_NIGHTSPULL)
-					P_NightsItemChase(mobj);
-				break;
 			case MT_EMBLEM:
 				if (mobj->flags2 & MF2_NIGHTSPULL)
 					P_NightsItemChase(mobj);
@@ -8025,7 +8023,7 @@ void P_MobjThinker(mobj_t *mobj)
 		{
 			mobj_t *missile;
 
-			if (mobj->target->player && mobj->target->player->nightstime)
+			if (mobj->target->player && mobj->target->player->powers[pw_carry] == CR_NIGHTSMODE)
 			{
 				fixed_t oldval = mobjinfo[mobj->extravalue1].speed;
 
@@ -8726,7 +8724,6 @@ mobj_t *P_SpawnMobj(fixed_t x, fixed_t y, fixed_t z, mobjtype_t type)
 			break;
 		case MT_RING:
 		case MT_COIN:
-		case MT_BLUEBALL:
 			nummaprings++;
 		default:
 			break;
@@ -8861,7 +8858,6 @@ void P_RemoveMobj(mobj_t *mobj)
 	if (mobj->spawnpoint &&
 		(mobj->type == MT_RING
 		|| mobj->type == MT_COIN
-		|| mobj->type == MT_BLUEBALL
 		|| mobj->type == MT_REDTEAMRING
 		|| mobj->type == MT_BLUETEAMRING
 		|| P_WeaponOrPanel(mobj->type))
@@ -8881,7 +8877,7 @@ void P_RemoveMobj(mobj_t *mobj)
 	if (mobj->player && mobj->player->followmobj)
 	{
 		P_RemoveMobj(mobj->player->followmobj);
-		mobj->player->followmobj = NULL;
+		P_SetTarget(&mobj->player->followmobj, NULL);
 	}
 
 	mobj->health = 0; // Just because
@@ -9314,7 +9310,7 @@ void P_SpawnPlayer(INT32 playernum)
 		p->spectator = p->outofcoop =
 		(((multiplayer || netgame) && gametype == GT_COOP) // only question status in coop
 		&& ((leveltime > 0
-		&& ((G_IsSpecialStage(gamemap) && useNightsSS) // late join special stage
+		&& ((G_IsSpecialStage(gamemap) && (maptol & TOL_NIGHTS)) // late join special stage
 		|| (cv_coopstarposts.value == 2 && (p->jointime < 1 || p->outofcoop)))) // late join or die in new coop
 		|| (((cv_cooplives.value == 1) || !P_GetLives(p)) && p->lives <= 0))); // game over and can't redistribute lives
 	}
@@ -9383,7 +9379,7 @@ void P_SpawnPlayer(INT32 playernum)
 	P_SetupStateAnimation(mobj, mobj->state);
 
 	mobj->health = 1;
-	p->rings = 0;
+	p->rings = p->spheres = 0;
 	p->playerstate = PST_LIVE;
 
 	p->bonustime = false;
@@ -9402,6 +9398,22 @@ void P_SpawnPlayer(INT32 playernum)
 	mobj->radius = FixedMul(skins[p->skin].radius, mobj->scale);
 	mobj->height = P_GetPlayerHeight(p);
 
+	if (!leveltime && ((maptol & TOL_NIGHTS) == TOL_NIGHTS) != (G_IsSpecialStage(gamemap))) // non-special NiGHTS stage or special non-NiGHTS stage
+	{
+		if (maptol & TOL_NIGHTS)
+		{
+			if (p == players) // this is totally the wrong place to do this aaargh.
+			{
+				mobj_t *idya = P_SpawnMobjFromMobj(mobj, 0, 0, mobj->height, MT_GOTEMERALD);
+				P_SetTarget(&idya->target, mobj);
+				P_SetMobjState(idya, mobjinfo[MT_GOTEMERALD].missilestate);
+				P_SetTarget(&mobj->tracer, idya);
+			}
+		}
+		else if (sstimer)
+			p->nightstime = sstimer;
+	}
+
 	// Spawn with a pity shield if necessary.
 	P_DoPityCheck(p);
 }
@@ -9715,10 +9727,6 @@ void P_SpawnMapThing(mapthing_t *mthing)
 			return;
 	}
 
-	if (!G_RingSlingerGametype() || !cv_specialrings.value)
-		if (P_WeaponOrPanel(i))
-			return; // Don't place weapons/panels in non-ringslinger modes
-
 	if (i == MT_EMERHUNT)
 	{
 		// Emerald Hunt is Coop only.
@@ -9752,6 +9760,10 @@ void P_SpawnMapThing(mapthing_t *mthing)
 		if ((mobjinfo[i].flags & MF_ENEMY) || (mobjinfo[i].flags & MF_BOSS))
 			return;
 
+	if (!G_RingSlingerGametype() || !cv_specialrings.value)
+		if (P_WeaponOrPanel(i))
+			return; // Don't place weapons/panels in non-ringslinger modes
+
 	// Altering monitor spawns via cvars
 	// If MF_GRENADEBOUNCE is set in the monitor's info,
 	// skip this step. (Used for gold monitors)
@@ -10754,8 +10766,9 @@ ML_EFFECT4 : Don't clip inside the ground
 	mthing->mobj = mobj;
 }
 
-void P_SpawnHoopsAndRings(mapthing_t *mthing)
+void P_SpawnHoopsAndRings(mapthing_t *mthing, boolean bonustime)
 {
+	mobjtype_t ringthing = MT_RING;
 	mobj_t *mobj = NULL;
 	INT32 r, i;
 	fixed_t x, y, z, finalx, finaly, finalz;
@@ -11034,59 +11047,26 @@ void P_SpawnHoopsAndRings(mapthing_t *mthing)
 
 		return;
 	}
-	// Wing logo item.
-	else if (mthing->type == mobjinfo[MT_NIGHTSWING].doomednum)
-	{
-		z =
-#ifdef ESLOPE
-			sec->f_slope ? P_GetZAt(sec->f_slope, x, y) :
-#endif
-			sec->floorheight;
-		if (mthing->options >> ZSHIFT)
-			z += ((mthing->options >> ZSHIFT) << FRACBITS);
-
-		mthing->z = (INT16)(z>>FRACBITS);
-
-		mobj = P_SpawnMobj(x, y, z, MT_NIGHTSWING);
-		mobj->spawnpoint = mthing;
-
-		if (G_IsSpecialStage(gamemap) && useNightsSS)
-			P_SetMobjState(mobj, mobj->info->meleestate);
-		else if (maptol & TOL_XMAS)
-			P_SetMobjState(mobj, mobj->info->seestate);
-
-		mobj->angle = FixedAngle(mthing->angle*FRACUNIT);
-		mobj->flags2 |= MF2_AMBUSH;
-		mthing->mobj = mobj;
-	}
 	// All manners of rings and coins
 	else if (mthing->type == mobjinfo[MT_RING].doomednum || mthing->type == mobjinfo[MT_COIN].doomednum ||
-	         mthing->type == mobjinfo[MT_REDTEAMRING].doomednum || mthing->type == mobjinfo[MT_BLUETEAMRING].doomednum)
+	         mthing->type == mobjinfo[MT_REDTEAMRING].doomednum || mthing->type == mobjinfo[MT_BLUETEAMRING].doomednum ||
+			 mthing->type == mobjinfo[MT_BLUESPHERE].doomednum)
 	{
-		mobjtype_t ringthing = MT_RING;
-
-		// No rings in Ultimate!
-		if (ultimatemode && !(G_IsSpecialStage(gamemap) || maptol & TOL_NIGHTS))
-			return;
 
 		// Which ringthing to use
-		switch (mthing->type)
-		{
-			case 1800:
-				ringthing = MT_COIN;
-				break;
-			case 308: // No team rings in non-CTF
-				ringthing = (gametype == GT_CTF) ? MT_REDTEAMRING : MT_RING;
-				break;
-			case 309: // No team rings in non-CTF
-				ringthing = (gametype == GT_CTF) ? MT_BLUETEAMRING : MT_RING;
-				break;
-			default:
-				// Spawn rings as blue spheres in special stages, ala S3+K.
-				if (G_IsSpecialStage(gamemap) && useNightsSS)
-					ringthing = MT_BLUEBALL;
-				break;
-		}
+		if (mthing->type == mobjinfo[MT_COIN].doomednum)
+			ringthing = MT_COIN;
+		else if (mthing->type == mobjinfo[MT_REDTEAMRING].doomednum) // No team rings in non-CTF
+			ringthing = (gametype == GT_CTF) ? MT_REDTEAMRING : MT_RING;
+		else if (mthing->type == mobjinfo[MT_BLUETEAMRING].doomednum) // Ditto
+			ringthing = (gametype == GT_CTF) ? MT_BLUETEAMRING : MT_RING;
+		else if (mthing->type == mobjinfo[MT_BLUESPHERE].doomednum)
+			ringthing = MT_BLUESPHERE;
+
+		if ((maptol & TOL_NIGHTS) && !G_IsSpecialStage(gamemap))
+			ringthing = ((ringthing == MT_BLUESPHERE) ? MT_NIGHTSCHIP : MT_NIGHTSSTAR);
+		else if (ringthing != MT_BLUESPHERE && ultimatemode)
+			return; // No rings in Ultimate!
 
 		// Set proper height
 		if (mthing->options & MTF_OBJECTFLIP)
@@ -11130,8 +11110,14 @@ void P_SpawnHoopsAndRings(mapthing_t *mthing)
 		}
 
 		mobj->angle = FixedAngle(mthing->angle*FRACUNIT);
-		mobj->flags2 |= MF2_AMBUSH;
 		mthing->mobj = mobj;
+		if (mthing->options & MTF_AMBUSH)
+			mobj->flags2 |= MF2_AMBUSH;
+
+		if (bonustime && (ringthing == MT_BLUESPHERE || ringthing == MT_NIGHTSCHIP))
+			P_SetMobjState(mobj, mobj->info->raisestate);
+		else if ((maptol & TOL_XMAS) && (ringthing == MT_NIGHTSSTAR))
+			P_SetMobjState(mobj, mobj->info->seestate);
 	}
 	// ***
 	// Special placement patterns
@@ -11141,17 +11127,13 @@ void P_SpawnHoopsAndRings(mapthing_t *mthing)
 	else if (mthing->type == 600 || mthing->type == 601)
 	{
 		INT32 dist = 64*FRACUNIT;
-		mobjtype_t ringthing = MT_RING;
 		if (mthing->type == 601)
 			dist = 128*FRACUNIT;
 
-		// No rings in Ultimate!
-		if (ultimatemode && !(G_IsSpecialStage(gamemap) || maptol & TOL_NIGHTS))
-			return;
-
-		// Spawn rings as blue spheres in special stages, ala S3+K.
-		if (G_IsSpecialStage(gamemap) && useNightsSS)
-			ringthing = MT_BLUEBALL;
+		if ((maptol & TOL_NIGHTS) && !G_IsSpecialStage(gamemap))
+			ringthing = MT_NIGHTSSTAR;
+		else if (ultimatemode)
+			return; // No rings in Ultimate!
 
 		for (r = 1; r <= 5; r++)
 		{
@@ -11187,31 +11169,30 @@ void P_SpawnHoopsAndRings(mapthing_t *mthing)
 			mobj->angle = FixedAngle(mthing->angle*FRACUNIT);
 			if (mthing->options & MTF_AMBUSH)
 				mobj->flags2 |= MF2_AMBUSH;
+
+			if ((maptol & TOL_XMAS) && (ringthing == MT_NIGHTSSTAR))
+				P_SetMobjState(mobj, mobj->info->seestate);
 		}
 	}
 	// Diagonal rings (handles both types)
 	else if (mthing->type == 602 || mthing->type == 603) // Diagonal rings (5)
 	{
-		angle_t angle = FixedAngle(mthing->angle*FRACUNIT);
-		mobjtype_t ringthing = MT_RING;
 		INT32 iterations = 5;
 		if (mthing->type == 603)
 			iterations = 10;
 
-		// No rings in Ultimate!
-		if (ultimatemode && !(G_IsSpecialStage(gamemap) || maptol & TOL_NIGHTS))
-			return;
-
-		// Spawn rings as blue spheres in special stages, ala S3+K.
-		if (G_IsSpecialStage(gamemap) && useNightsSS)
-			ringthing = MT_BLUEBALL;
+		if ((maptol & TOL_NIGHTS) && !G_IsSpecialStage(gamemap))
+			ringthing = MT_NIGHTSSTAR;
+		else if (ultimatemode)
+			return; // No rings in Ultimate!
 
-		angle >>= ANGLETOFINESHIFT;
+		closestangle = FixedAngle(mthing->angle*FRACUNIT);
+		fa = (closestangle >> ANGLETOFINESHIFT);
 
 		for (r = 1; r <= iterations; r++)
 		{
-			x += FixedMul(64*FRACUNIT, FINECOSINE(angle));
-			y += FixedMul(64*FRACUNIT, FINESINE(angle));
+			x += FixedMul(64*FRACUNIT, FINECOSINE(fa));
+			y += FixedMul(64*FRACUNIT, FINESINE(fa));
 
 			if (mthing->options & MTF_OBJECTFLIP)
 			{
@@ -11242,9 +11223,12 @@ void P_SpawnHoopsAndRings(mapthing_t *mthing)
 				mobj->flags2 |= MF2_OBJECTFLIP;
 			}
 
-			mobj->angle = FixedAngle(mthing->angle*FRACUNIT);
+			mobj->angle = closestangle;
 			if (mthing->options & MTF_AMBUSH)
 				mobj->flags2 |= MF2_AMBUSH;
+
+			if ((maptol & TOL_XMAS) && (ringthing == MT_NIGHTSSTAR))
+				P_SetMobjState(mobj, mobj->info->seestate);
 		}
 	}
 	// Rings of items (all six of them)
@@ -11252,7 +11236,6 @@ void P_SpawnHoopsAndRings(mapthing_t *mthing)
 	{
 		INT32 numitems = 8;
 		INT32 size = 96*FRACUNIT;
-		mobjtype_t itemToSpawn = MT_NIGHTSWING;
 
 		if (mthing->type & 1)
 		{
@@ -11277,30 +11260,24 @@ void P_SpawnHoopsAndRings(mapthing_t *mthing)
 			{
 				case 604:
 				case 605:
-					itemToSpawn = MT_RING;
+					ringthing = MT_BLUESPHERE;
 					break;
 				case 608:
 				case 609:
-					itemToSpawn = (i & 1) ? MT_NIGHTSWING : MT_RING;
+					ringthing = (i & 1) ? MT_RING : MT_BLUESPHERE;
 					break;
 				case 606:
 				case 607:
-					itemToSpawn = MT_NIGHTSWING;
+					ringthing = MT_RING;
 					break;
 				default:
 					break;
 			}
 
-			// No rings in Ultimate!
-			if (itemToSpawn == MT_RING)
-			{
-				if (ultimatemode && !(G_IsSpecialStage(gamemap) || (maptol & TOL_NIGHTS)))
-					continue;
-
-				// Spawn rings as blue spheres in special stages, ala S3+K.
-				if (G_IsSpecialStage(gamemap) && useNightsSS)
-					itemToSpawn = MT_BLUEBALL;
-			}
+			if ((maptol & TOL_NIGHTS) && !G_IsSpecialStage(gamemap))
+				ringthing = ((ringthing == MT_BLUESPHERE) ? MT_NIGHTSCHIP : MT_NIGHTSSTAR);
+			else if (ringthing == MT_RING && ultimatemode)
+				continue; // No rings in Ultimate!
 
 			fa = i*FINEANGLES/numitems;
 			v[0] = FixedMul(FINECOSINE(fa),size);
@@ -11315,16 +11292,23 @@ void P_SpawnHoopsAndRings(mapthing_t *mthing)
 			finaly = y + v[1];
 			finalz = z + v[2];
 
-			mobj = P_SpawnMobj(finalx, finaly, finalz, itemToSpawn);
+			mobj = P_SpawnMobj(finalx, finaly, finalz, ringthing);
 			mobj->z -= mobj->height/2;
 
-			if (itemToSpawn == MT_NIGHTSWING)
+			if (mthing->options & MTF_OBJECTFLIP)
 			{
-				if (G_IsSpecialStage(gamemap) && useNightsSS)
-					P_SetMobjState(mobj, mobj->info->meleestate);
-				else if ((maptol & TOL_XMAS))
-					P_SetMobjState(mobj, mobj->info->seestate);
+				mobj->eflags |= MFE_VERTICALFLIP;
+				mobj->flags2 |= MF2_OBJECTFLIP;
 			}
+
+			mobj->angle = closestangle;
+			if (mthing->options & MTF_AMBUSH)
+				mobj->flags2 |= MF2_AMBUSH;
+
+			if (bonustime && (ringthing == MT_BLUESPHERE || ringthing == MT_NIGHTSCHIP))
+				P_SetMobjState(mobj, mobj->info->raisestate);
+			else if ((maptol & TOL_XMAS) && (ringthing == MT_NIGHTSSTAR))
+				P_SetMobjState(mobj, mobj->info->seestate);
 		}
 		return;
 	}
diff --git a/src/p_mobj.h b/src/p_mobj.h
index 405dca77e1..f7b2d8f360 100644
--- a/src/p_mobj.h
+++ b/src/p_mobj.h
@@ -314,7 +314,7 @@ typedef struct mobj_s
 	mobjtype_t type;
 	const mobjinfo_t *info; // &mobjinfo[mobj->type]
 
-	INT32 health; // for player this is rings + 1
+	INT32 health; // for player this is rings + 1 -- no it isn't, not any more!!
 
 	// Movement direction, movement generation (zig-zagging).
 	angle_t movedir; // dirtype_t 0-7; also used by Deton for up/down angle
@@ -435,7 +435,7 @@ void P_MovePlayerToStarpost(INT32 playernum);
 void P_AfterPlayerSpawn(INT32 playernum);
 
 void P_SpawnMapThing(mapthing_t *mthing);
-void P_SpawnHoopsAndRings(mapthing_t *mthing);
+void P_SpawnHoopsAndRings(mapthing_t *mthing, boolean bonustime);
 void P_SpawnHoopOfSomething(fixed_t x, fixed_t y, fixed_t z, fixed_t radius, INT32 number, mobjtype_t type, angle_t rotangle);
 void P_SpawnPrecipitation(void);
 void P_SpawnParaloop(fixed_t x, fixed_t y, fixed_t z, fixed_t radius, INT32 number, mobjtype_t type, statenum_t nstate, angle_t rotangle, boolean spawncenter);
diff --git a/src/p_saveg.c b/src/p_saveg.c
index 029df08f4b..2e47f20777 100644
--- a/src/p_saveg.c
+++ b/src/p_saveg.c
@@ -116,7 +116,8 @@ static void P_NetArchivePlayers(void)
 		WRITEANGLE(save_p, players[i].drawangle);
 		WRITEANGLE(save_p, players[i].awayviewaiming);
 		WRITEINT32(save_p, players[i].awayviewtics);
-		WRITEINT32(save_p, players[i].rings);
+		WRITEINT16(save_p, players[i].rings);
+		WRITEINT16(save_p, players[i].spheres);
 
 		WRITESINT8(save_p, players[i].pity);
 		WRITEINT32(save_p, players[i].currentweapon);
@@ -201,7 +202,7 @@ static void P_NetArchivePlayers(void)
 		WRITEUINT32(save_p, players[i].marebegunat);
 		WRITEUINT32(save_p, players[i].startedtime);
 		WRITEUINT32(save_p, players[i].finishedtime);
-		WRITEINT16(save_p, players[i].finishedrings);
+		WRITEINT16(save_p, players[i].finishedspheres);
 		WRITEUINT32(save_p, players[i].marescore);
 		WRITEUINT32(save_p, players[i].lastmarescore);
 		WRITEUINT8(save_p, players[i].lastmare);
@@ -303,7 +304,8 @@ static void P_NetUnArchivePlayers(void)
 		players[i].drawangle = READANGLE(save_p);
 		players[i].awayviewaiming = READANGLE(save_p);
 		players[i].awayviewtics = READINT32(save_p);
-		players[i].rings = READINT32(save_p);
+		players[i].rings = READINT16(save_p);
+		players[i].spheres = READINT16(save_p);
 
 		players[i].pity = READSINT8(save_p);
 		players[i].currentweapon = READINT32(save_p);
@@ -388,7 +390,7 @@ static void P_NetUnArchivePlayers(void)
 		players[i].marebegunat = READUINT32(save_p);
 		players[i].startedtime = READUINT32(save_p);
 		players[i].finishedtime = READUINT32(save_p);
-		players[i].finishedrings = READINT16(save_p);
+		players[i].finishedspheres = READINT16(save_p);
 		players[i].marescore = READUINT32(save_p);
 		players[i].lastmarescore = READUINT32(save_p);
 		players[i].lastmare = READUINT8(save_p);
@@ -1986,7 +1988,7 @@ static void LoadMobjThinker(actionf_p1 thinker)
 
 		if (mapthings[spawnpointnum].type == 1705 || mapthings[spawnpointnum].type == 1713) // NiGHTS Hoop special case
 		{
-			P_SpawnHoopsAndRings(&mapthings[spawnpointnum]);
+			P_SpawnHoopsAndRings(&mapthings[spawnpointnum], false);
 			return;
 		}
 
@@ -3261,7 +3263,7 @@ static void P_NetArchiveMisc(void)
 	WRITEUINT32(save_p, tokenlist);
 
 	WRITEUINT32(save_p, leveltime);
-	WRITEUINT32(save_p, totalrings);
+	WRITEUINT32(save_p, ssspheres);
 	WRITEINT16(save_p, lastmap);
 
 	WRITEUINT16(save_p, emeralds);
@@ -3338,7 +3340,7 @@ static inline boolean P_NetUnArchiveMisc(void)
 
 	// get the time
 	leveltime = READUINT32(save_p);
-	totalrings = READUINT32(save_p);
+	ssspheres = READUINT32(save_p);
 	lastmap = READINT16(save_p);
 
 	emeralds = READUINT16(save_p);
diff --git a/src/p_setup.c b/src/p_setup.c
index a9fc57652f..076c8dba76 100644
--- a/src/p_setup.c
+++ b/src/p_setup.c
@@ -808,7 +808,7 @@ void P_ReloadRings(void)
 	mapthing_t *hoopsToRespawn[4096];
 	mapthing_t *mt = mapthings;
 
-	// scan the thinkers to find rings/wings/hoops to unset
+	// scan the thinkers to find rings/spheres/hoops to unset
 	for (th = thinkercap.next; th != &thinkercap; th = th->next)
 	{
 		if (th->function.acp1 != (actionf_p1)P_MobjThinker)
@@ -826,7 +826,8 @@ void P_ReloadRings(void)
 			}
 			continue;
 		}
-		if (!(mo->type == MT_RING || mo->type == MT_NIGHTSWING || mo->type == MT_COIN || mo->type == MT_BLUEBALL))
+		if (!(mo->type == MT_RING || mo->type == MT_COIN || mo->type == MT_BLUESPHERE
+			|| mo->type == MT_NIGHTSCHIP || mo->type == MT_NIGHTSSTAR))
 			continue;
 
 		// Don't auto-disintegrate things being pulled to us
@@ -840,9 +841,9 @@ void P_ReloadRings(void)
 	for (i = 0; i < nummapthings; i++, mt++)
 	{
 		// Notice an omission? We handle hoops differently.
-		if (mt->type == 300 || mt->type == 308 || mt->type == 309
-		 || mt->type == 1706 || (mt->type >= 600 && mt->type <= 609)
-		 || mt->type == 1800)
+		if (mt->type == mobjinfo[MT_RING].doomednum || mt->type == mobjinfo[MT_REDTEAMRING].doomednum || mt->type == mobjinfo[MT_BLUETEAMRING].doomednum
+		 || mt->type == mobjinfo[MT_BLUESPHERE].doomednum || mt->type == mobjinfo[MT_COIN].doomednum
+		 || (mt->type >= 600 && mt->type <= 609)) // circles
 		{
 			mt->mobj = NULL;
 
@@ -850,12 +851,35 @@ void P_ReloadRings(void)
 			mt->z = (INT16)(R_PointInSubsector(mt->x << FRACBITS, mt->y << FRACBITS)
 				->sector->floorheight>>FRACBITS);
 
-			P_SpawnHoopsAndRings (mt);
+			P_SpawnHoopsAndRings(mt, true);
 		}
 	}
 	for (i = 0; i < numHoops; i++)
 	{
-		P_SpawnHoopsAndRings(hoopsToRespawn[i]);
+		P_SpawnHoopsAndRings(hoopsToRespawn[i], false);
+	}
+}
+
+void P_SwitchSpheresBonusMode(boolean bonustime)
+{
+	mobj_t *mo;
+	thinker_t *th;
+
+	// scan the thinkers to find spheres to switch
+	for (th = thinkercap.next; th != &thinkercap; th = th->next)
+	{
+		if (th->function.acp1 != (actionf_p1)P_MobjThinker)
+			continue;
+
+		mo = (mobj_t *)th;
+
+		if (mo->type != MT_BLUESPHERE && mo->type != MT_NIGHTSCHIP)
+			continue;
+
+		if (!mo->health)
+			continue;
+
+		P_SetMobjState(mo, ((bonustime) ? mo->info->raisestate : mo->info->spawnstate));
 	}
 }
 
@@ -1025,20 +1049,22 @@ static void P_LoadThings(void)
 		}
 
 		//decrement spawn values to the actual number because zero is valid.
-		if (emer1)
-			P_SpawnMobj(huntemeralds[emer1 - 1]->x<<FRACBITS,
-				huntemeralds[emer1 - 1]->y<<FRACBITS,
-				huntemeralds[emer1 - 1]->z<<FRACBITS, MT_EMERHUNT);
+		if (emer1--)
+			P_SpawnMobj(huntemeralds[emer1]->x<<FRACBITS,
+				huntemeralds[emer1]->y<<FRACBITS,
+				huntemeralds[emer1]->z<<FRACBITS, MT_EMERHUNT);
 
-		if (emer2)
-			P_SpawnMobj(huntemeralds[emer2 - 1]->x<<FRACBITS,
-				huntemeralds[emer2 - 1]->y<<FRACBITS,
-				huntemeralds[emer2 - 1]->z<<FRACBITS, MT_EMERHUNT);
+		if (emer2--)
+			P_SetMobjStateNF(P_SpawnMobj(huntemeralds[emer2]->x<<FRACBITS,
+				huntemeralds[emer2]->y<<FRACBITS,
+				huntemeralds[emer2]->z<<FRACBITS, MT_EMERHUNT),
+			mobjinfo[MT_EMERHUNT].spawnstate+1);
 
-		if (emer3)
-			P_SpawnMobj(huntemeralds[emer3 - 1]->x<<FRACBITS,
-				huntemeralds[emer3 - 1]->y<<FRACBITS,
-				huntemeralds[emer3 - 1]->z<<FRACBITS, MT_EMERHUNT);
+		if (emer3--)
+			P_SetMobjStateNF(P_SpawnMobj(huntemeralds[emer3]->x<<FRACBITS,
+				huntemeralds[emer3]->y<<FRACBITS,
+				huntemeralds[emer3]->z<<FRACBITS, MT_EMERHUNT),
+			mobjinfo[MT_EMERHUNT].spawnstate+2);
 	}
 
 	if (metalrecording) // Metal Sonic gets no rings to distract him.
@@ -1058,7 +1084,7 @@ static void P_LoadThings(void)
 			mt->z = (INT16)(R_PointInSubsector(mt->x << FRACBITS, mt->y << FRACBITS)
 				->sector->floorheight>>FRACBITS);
 
-			P_SpawnHoopsAndRings (mt);
+			P_SpawnHoopsAndRings(mt, false);
 		}
 	}
 }
@@ -2298,7 +2324,7 @@ static void P_LevelInitStuff(void)
 	// circuit, race and competition stuff
 	circuitmap = false;
 	numstarposts = 0;
-	totalrings = timeinmap = 0;
+	ssspheres = timeinmap = 0;
 
 	// special stage
 	stagefailed = false;
@@ -2334,6 +2360,7 @@ static void P_LevelInitStuff(void)
 
 		players[i].xtralife = players[i].deadtimer = players[i].numboxes = players[i].totalring = players[i].laps = 0;
 		players[i].rings = 0;
+		players[i].spheres = 0;
 		players[i].aiming = 0;
 		players[i].pflags &= ~PF_GAMETYPEOVER;
 
@@ -2341,7 +2368,7 @@ static void P_LevelInitStuff(void)
 		players[i].timeshit = 0;
 
 		players[i].marescore = players[i].lastmarescore = players[i].maxlink = 0;
-		players[i].startedtime = players[i].finishedtime = players[i].finishedrings = 0;
+		players[i].startedtime = players[i].finishedtime = players[i].finishedspheres = 0;
 		players[i].lastmare = players[i].marebegunat = 0;
 
 		// Don't show anything
diff --git a/src/p_setup.h b/src/p_setup.h
index a42ac5b760..569501531e 100644
--- a/src/p_setup.h
+++ b/src/p_setup.h
@@ -72,6 +72,7 @@ void P_DeleteFlickies(INT16 i);
 
 // Needed for NiGHTS
 void P_ReloadRings(void);
+void P_SwitchSpheresBonusMode(boolean bonustime);
 void P_DeleteGrades(INT16 i);
 void P_AddGradesForMare(INT16 i, UINT8 mare, char *gtext);
 UINT8 P_GetGrade(UINT32 pscore, INT16 map, UINT8 mare);
diff --git a/src/p_spec.c b/src/p_spec.c
index e77a783e93..2999d94ac4 100644
--- a/src/p_spec.c
+++ b/src/p_spec.c
@@ -2868,7 +2868,7 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec)
 					// Unlocked something?
 					if (M_UpdateUnlockablesAndExtraEmblems())
 					{
-						S_StartSound(NULL, sfx_ncitem);
+						S_StartSound(NULL, sfx_s3k68);
 						G_SaveGameData(); // only save if unlocked something
 					}
 				}
@@ -3527,7 +3527,7 @@ void P_ProcessSpecialSector(player_t *player, sector_t *sector, sector_t *rovers
 			if (player->exiting || player->bot) // Don't do anything for bots or players who have just finished
 				break;
 
-			if (!(player->powers[pw_shield] || player->rings > 0)) // Don't do anything if no shield or rings anyway
+			if (!(player->powers[pw_shield] || player->spheres > 0)) // Don't do anything if no shield or spheres anyway
 				break;
 
 			P_SpecialStageDamage(player, NULL, NULL);
@@ -3775,8 +3775,8 @@ DoneSection2:
 		case 2: // Special stage GOAL sector / Exit Sector / CTF Flag Return
 			if (player->bot)
 				break;
-			if (!useNightsSS && G_IsSpecialStage(gamemap) && sstimer > 6)
-				sstimer = 6; // Just let P_Ticker take care of the rest.
+			if (!(maptol & TOL_NIGHTS) && G_IsSpecialStage(gamemap) && player->nightstime > 6)
+				player->nightstime = 6; // Just let P_Ticker take care of the rest.
 
 			// Exit (for FOF exits; others are handled in P_PlayerThink in p_user.c)
 			{
@@ -4643,7 +4643,7 @@ static void P_RunSpecialSectorCheck(player_t *player, sector_t *sector)
 	switch(GETSECSPECIAL(sector->special, 4))
 	{
 		case 2: // Level Exit / GOAL Sector / Flag Return
-			if (!useNightsSS && G_IsSpecialStage(gamemap))
+			if (!(maptol & TOL_NIGHTS) && G_IsSpecialStage(gamemap))
 			{
 				// Special stage GOAL sector
 				// requires touching floor.
@@ -5502,7 +5502,7 @@ void P_InitSpecials(void)
 
 	// Defaults in case levels don't have them set.
 	sstimer = 90*TICRATE + 6;
-	totalrings = 1;
+	ssspheres = 1;
 
 	CheckForBustableBlocks = CheckForBouncySector = CheckForQuicksand = CheckForMarioBlocks = CheckForFloatBob = CheckForReverseGravity = false;
 
@@ -5596,7 +5596,7 @@ void P_SpawnSpecials(INT32 fromnetsave)
 		{
 			case 10: // Time for special stage
 				sstimer = (sector->floorheight>>FRACBITS) * TICRATE + 6; // Time to finish
-				totalrings = sector->ceilingheight>>FRACBITS; // Ring count for special stage
+				ssspheres = sector->ceilingheight>>FRACBITS; // Ring count for special stage
 				break;
 
 			case 11: // Custom global gravity!
diff --git a/src/p_tick.c b/src/p_tick.c
index 658b1e4ea8..483b161a4f 100644
--- a/src/p_tick.c
+++ b/src/p_tick.c
@@ -424,7 +424,7 @@ void P_DoTeamscrambling(void)
 
 static inline void P_DoSpecialStageStuff(void)
 {
-	boolean inwater = false;
+	boolean stillalive = false;
 	INT32 i;
 
 	// Can't drown in a special stage
@@ -436,68 +436,59 @@ static inline void P_DoSpecialStageStuff(void)
 		players[i].powers[pw_underwater] = players[i].powers[pw_spacetime] = 0;
 	}
 
-	if (sstimer < 15*TICRATE+6 && sstimer > 7 && (mapheaderinfo[gamemap-1]->levelflags & LF_SPEEDMUSIC))
-		S_SpeedMusic(1.4f);
+	//if (sstimer < 15*TICRATE+6 && sstimer > 7 && (mapheaderinfo[gamemap-1]->levelflags & LF_SPEEDMUSIC))
+		//S_SpeedMusic(1.4f);
 
-	if (sstimer < 7 && sstimer > 0) // The special stage time is up!
+	if (sstimer && !objectplacing)
 	{
-		sstimer = 0;
-		for (i = 0; i < MAXPLAYERS; i++)
-		{
-			if (playeringame[i])
-			{
-				players[i].exiting = (14*TICRATE)/5 + 1;
-				players[i].pflags &= ~PF_GLIDING;
-			}
-
-			if (i == consoleplayer)
-				S_StartSound(NULL, sfx_lose);
-		}
-
-		if (mapheaderinfo[gamemap-1]->levelflags & LF_SPEEDMUSIC)
-			S_SpeedMusic(1.0f);
-
-		stagefailed = true;
-	}
-
-	if (sstimer > 1) // As long as time isn't up...
-	{
-		UINT32 ssrings = 0;
+		UINT16 countspheres = 0;
 		// Count up the rings of all the players and see if
 		// they've collected the required amount.
 		for (i = 0; i < MAXPLAYERS; i++)
 			if (playeringame[i])
 			{
-				ssrings += players[i].rings;
+				tic_t oldnightstime = players[i].nightstime;
+				countspheres += players[i].spheres;
 
 				// If in water, deplete timer 6x as fast.
-				if ((players[i].mo->eflags & MFE_TOUCHWATER)
-					|| (players[i].mo->eflags & MFE_UNDERWATER))
-					inwater = true;
-			}
-
-		if (ssrings >= totalrings && totalrings > 0)
-		{
-			// Halt all the players
-			for (i = 0; i < MAXPLAYERS; i++)
-				if (playeringame[i])
+				if (players[i].mo->eflags & (MFE_TOUCHWATER|MFE_UNDERWATER))
+					players[i].nightstime -= 5;
+				if (--players[i].nightstime > 6)
+				{
+					if (P_IsLocalPlayer(&players[i]) && oldnightstime > 10*TICRATE && players[i].nightstime <= 10*TICRATE)
+						S_ChangeMusicInternal("_drown", false);
+					stillalive = true;
+				}
+				else if (!players[i].exiting)
 				{
-					players[i].mo->momx = players[i].mo->momy = 0;
 					players[i].exiting = (14*TICRATE)/5 + 1;
+					players[i].pflags &= ~(PF_GLIDING|PF_BOUNCING);
+					players[i].nightstime = 0;
+					if (P_IsLocalPlayer(&players[i]))
+						S_StartSound(NULL, sfx_s3k66);
 				}
+			}
 
-			sstimer = 0;
-
-			P_GiveEmerald(true);
+		if (stillalive)
+		{
+			if (countspheres >= ssspheres)
+			{
+				// Halt all the players
+				for (i = 0; i < MAXPLAYERS; i++)
+					if (playeringame[i])
+					{
+						players[i].mo->momx = players[i].mo->momy = 0;
+						players[i].exiting = (14*TICRATE)/5 + 1;
+					}
+
+				sstimer = 0;
+				P_GiveEmerald(true);
+			}
 		}
-
-		// Decrement the timer
-		if (!objectplacing)
+		else
 		{
-			if (inwater)
-				sstimer -= 6;
-			else
-				sstimer--;
+			sstimer = 0;
+			stagefailed = true;
 		}
 	}
 }
@@ -608,7 +599,7 @@ void P_Ticker(boolean run)
 	// Keep track of how long they've been playing!
 	totalplaytime++;
 
-	if (!useNightsSS && G_IsSpecialStage(gamemap))
+	if (!(maptol & TOL_NIGHTS) && G_IsSpecialStage(gamemap))
 		P_DoSpecialStageStuff();
 
 	if (runemeraldmanager)
diff --git a/src/p_user.c b/src/p_user.c
index a28c8f445d..8d05d2b01a 100644
--- a/src/p_user.c
+++ b/src/p_user.c
@@ -283,22 +283,9 @@ boolean P_PlayerMoving(INT32 pnum)
 //
 UINT8 P_GetNextEmerald(void)
 {
-	if (!useNightsSS) // In order
-	{
-		if (!(emeralds & EMERALD1)) return 0;
-		if (!(emeralds & EMERALD2)) return 1;
-		if (!(emeralds & EMERALD3)) return 2;
-		if (!(emeralds & EMERALD4)) return 3;
-		if (!(emeralds & EMERALD5)) return 4;
-		if (!(emeralds & EMERALD6)) return 5;
-		return 6;
-	}
-	else // Depends on stage
-	{
-		if (gamemap < sstage_start || gamemap > sstage_end)
-			return 0;
-		return (UINT8)(gamemap - sstage_start);
-	}
+	if (gamemap < sstage_start || gamemap > sstage_end)
+		return 0;
+	return (UINT8)(gamemap - sstage_start);
 }
 
 //
@@ -309,20 +296,20 @@ UINT8 P_GetNextEmerald(void)
 //
 void P_GiveEmerald(boolean spawnObj)
 {
-	INT32 i;
-	UINT8 em;
+	UINT8 em = P_GetNextEmerald();
 
 	S_StartSound(NULL, sfx_cgot); // Got the emerald!
-	em = P_GetNextEmerald();
 	emeralds |= (1 << em);
 
-	if (spawnObj)
+	if (spawnObj && playeringame[consoleplayer])
 	{
-		for (i = 0; i < MAXPLAYERS; i++)
-			if (playeringame[i])
-				P_SetMobjState(P_SpawnMobj(players[i].mo->x, players[i].mo->y, players[i].mo->z + players[i].mo->info->height, MT_GOTEMERALD),
-				mobjinfo[MT_GOTEMERALD].spawnstate + em);
-
+		// The Chaos Emerald begins to orbit us!
+		UINT8 em = P_GetNextEmerald();
+		// Only give it to ONE person!
+		mobj_t *emmo = P_SpawnMobjFromMobj(players[consoleplayer].mo, 0, 0, players[consoleplayer].mo->height, MT_GOTEMERALD);
+		P_SetTarget(&emmo->target, players[consoleplayer].mo);
+		P_SetMobjState(emmo, mobjinfo[MT_GOTEMERALD].meleestate + em);
+		P_SetTarget(&players[consoleplayer].mo->tracer, emmo);
 	}
 }
 
@@ -692,10 +679,10 @@ void P_NightserizePlayer(player_t *player, INT32 nighttime)
 
 	oldmare = player->mare;
 
-	if (P_TransferToNextMare(player) == false)
+	if (!P_TransferToNextMare(player))
 	{
 		INT32 i;
-		INT32 total_rings = 0;
+		INT32 total_spheres = 0;
 
 		P_SetTarget(&player->mo->target, NULL);
 
@@ -703,7 +690,7 @@ 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_rings += players[i].rings;
+					total_spheres += players[i].spheres;
 		}
 
 		for (i = 0; i < MAXPLAYERS; i++)
@@ -716,13 +703,13 @@ void P_NightserizePlayer(player_t *player, INT32 nighttime)
 			players[i].lastmare = players[i].mare;
 			if (G_IsSpecialStage(gamemap))
 			{
-				players[i].finishedrings = (INT16)total_rings;
-				P_AddPlayerScore(player, total_rings * 50);
+				players[i].finishedspheres = (INT16)total_spheres;
+				P_AddPlayerScore(player, total_spheres * 50);
 			}
 			else
 			{
-				players[i].finishedrings = (INT16)(players[i].rings);
-				P_AddPlayerScore(&players[i], (players[i].rings) * 50);
+				players[i].finishedspheres = (INT16)(players[i].spheres);
+				P_AddPlayerScore(&players[i], (players[i].spheres) * 50);
 			}
 
 			// Add score to leaderboards now
@@ -733,20 +720,20 @@ void P_NightserizePlayer(player_t *player, INT32 nighttime)
 			players[i].lastmarescore = players[i].marescore;
 			players[i].marescore = 0;
 
-			players[i].rings = 0;
+			players[i].spheres = 0;
 			P_DoPlayerExit(&players[i]);
 		}
 	}
 	else if (oldmare != player->mare)
 	{
 		/// \todo Handle multi-mare special stages.
-		// Ring bonus
-		P_AddPlayerScore(player, (player->rings) * 50);
+		// Spheres bonus
+		P_AddPlayerScore(player, (player->spheres) * 50);
 
 		player->lastmare = (UINT8)oldmare;
 		player->texttimer = 4*TICRATE;
 		player->textvar = 4; // Score and grades
-		player->finishedrings = (INT16)(player->rings);
+		player->finishedspheres = (INT16)(player->spheres);
 
 		// Add score to temp leaderboards
 		if (!(netgame||multiplayer) && P_IsLocalPlayer(player))
@@ -757,7 +744,7 @@ void P_NightserizePlayer(player_t *player, INT32 nighttime)
 		player->marescore = 0;
 		player->marebegunat = leveltime;
 
-		player->rings = 0;
+		player->spheres = 0;
 	}
 	else
 	{
@@ -857,7 +844,7 @@ void P_DoPlayerPain(player_t *player, mobj_t *source, mobj_t *inflictor)
 		}
 		else
 		{
-			ang = R_PointToAngle2(player->mo->momx, player->mo->momy, 0, 0);
+			ang = ((player->mo->momx || player->mo->momy) ? R_PointToAngle2(player->mo->momx, player->mo->momy, 0, 0) : player->drawangle);
 			fallbackspeed = FixedMul(4*FRACUNIT, player->mo->scale);
 		}
 
@@ -923,8 +910,7 @@ void P_GivePlayerRings(player_t *player, INT32 num_rings)
 
 	player->rings += num_rings;
 
-	if (!G_IsSpecialStage(gamemap) || !useNightsSS)
-		player->totalring += num_rings;
+	player->totalring += num_rings;
 
 	// Can only get up to 9999 rings, sorry!
 	if (player->rings > 9999)
@@ -956,6 +942,26 @@ void P_GivePlayerRings(player_t *player, INT32 num_rings)
 	}
 }
 
+void P_GivePlayerSpheres(player_t *player, INT32 num_spheres)
+{
+	if (!player)
+		return;
+
+	if (player->bot)
+		player = &players[consoleplayer];
+
+	if (!player->mo)
+		return;
+
+	player->spheres += num_spheres;
+
+	// Can only get up to 9999 spheres, sorry!
+	if (player->spheres > 9999)
+		player->spheres = 9999;
+	else if (player->spheres < 0)
+		player->spheres = 0;
+}
+
 //
 // P_GivePlayerLives
 //
@@ -1035,10 +1041,11 @@ void P_DoSuperTransformation(player_t *player, boolean giverings)
 
 	S_StartSound(NULL, sfx_supert); //let all players hear it -mattw_cfi
 
+	player->mo->momx = player->mo->momy = player->mo->momz = player->cmomx = player->cmomy = player->rmomx = player->rmomy = 0;
+
 	// Transformation animation
 	P_SetPlayerMobjState(player->mo, S_PLAY_SUPER_TRANS1);
 
-	player->mo->momx = player->mo->momy = player->mo->momz = 0;
 	player->pflags |= PF_NOJUMPDAMAGE; // just to avoid recurling but still allow thok
 
 	if (giverings)
@@ -3793,12 +3800,15 @@ static void P_DoSuperStuff(player_t *player)
 //
 boolean P_SuperReady(player_t *player)
 {
-	if ((ALL7EMERALDS(emeralds) && player->rings >= 50) && !player->powers[pw_super] && !player->powers[pw_tailsfly]
-	&& !(player->powers[pw_shield] & SH_NOSTACK)
+	if (!player->powers[pw_super]
 	&& !player->powers[pw_invulnerability]
-	&& !(maptol & TOL_NIGHTS || (player->powers[pw_carry] == CR_NIGHTSMODE)) // don't turn 'regular super' in nights levels
-	&& player->pflags & PF_JUMPED
-	&& player->charflags & SF_SUPER)
+	&& !player->powers[pw_tailsfly]
+	&& (player->charflags & SF_SUPER)
+	&& (player->pflags & PF_JUMPED)
+	&& !(player->powers[pw_shield] & SH_NOSTACK)
+	&& !(maptol & TOL_NIGHTS)
+	&& ALL7EMERALDS(emeralds)
+	&& (player->rings >= 50))
 		return true;
 
 	return false;
@@ -4484,12 +4494,12 @@ static void P_DoJumpStuff(player_t *player, ticcmd_t *cmd)
 		}
 		else if (player->pflags & PF_SLIDING || (gametype == GT_CTF && player->gotflag))
 			;
-		else if (P_SuperReady(player))
+		/*else if (P_SuperReady(player))
 		{
 			// If you can turn super and aren't already,
 			// and you don't have a shield, do it!
 			P_DoSuperTransformation(player, false);
-		}
+		}*/
 		else if (player->pflags & PF_JUMPED)
 		{
 #ifdef HAVE_BLUA
@@ -5953,10 +5963,10 @@ static void P_DoNiGHTSCapsule(player_t *player)
 	if (G_IsSpecialStage(gamemap))
 	{ // In special stages, share rings. Everyone gives up theirs to the capsule player always, because we can't have any individualism here!
 		for (i = 0; i < MAXPLAYERS; i++)
-			if (playeringame[i] && (&players[i] != player) && players[i].rings > 0)
+			if (playeringame[i] && (&players[i] != player) && players[i].spheres > 0)
 			{
-				player->rings += players[i].rings;
-				players[i].rings = 0;
+				player->spheres += players[i].spheres;
+				players[i].spheres = 0;
 			}
 	}
 
@@ -5965,9 +5975,9 @@ static void P_DoNiGHTSCapsule(player_t *player)
 		&& player->mo->y == player->capsule->y
 		&& player->mo->z == player->capsule->z+(player->capsule->height/3))
 	{
-		if (player->rings > 0)
+		if (player->spheres > 0)
 		{
-			player->rings--;
+			player->spheres--;
 			player->capsule->health--;
 			player->capsule->extravalue1++;
 
@@ -5999,9 +6009,6 @@ static void P_DoNiGHTSCapsule(player_t *player)
 
 				if (G_IsSpecialStage(gamemap))
 				{
-					// The Chaos Emerald begins to orbit us!
-					mobj_t *emmo;
-					UINT8 em = P_GetNextEmerald();
 					tic_t lowest_time;
 
 					/*for (i = 0; i < MAXPLAYERS; i++)
@@ -6016,8 +6023,10 @@ static void P_DoNiGHTSCapsule(player_t *player)
 
 					if (player->powers[pw_carry] == CR_NIGHTSMODE)
 					{
+						// The Chaos Emerald begins to orbit us!
+						UINT8 em = P_GetNextEmerald();
 						// Only give it to ONE person, and THAT player has to get to the goal!
-						emmo = P_SpawnMobj(player->mo->x, player->mo->y, player->mo->z + player->mo->info->height, MT_GOTEMERALD);
+						mobj_t *emmo = P_SpawnMobjFromMobj(player->mo, 0, 0, player->mo->height, MT_GOTEMERALD);
 						P_SetTarget(&emmo->target, player->mo);
 						P_SetMobjState(emmo, mobjinfo[MT_GOTEMERALD].meleestate + em);
 						P_SetTarget(&player->mo->tracer, emmo);
@@ -6035,18 +6044,24 @@ static void P_DoNiGHTSCapsule(player_t *player)
 				}
 				else
 				{
-					for (i = 0; i < 16; i++)
+					/*for (i = 0; i < 16; i++)
 					{
 						mobj_t *flicky = P_InternalFlickySpawn(player->capsule, 0, ((i%4) + 1)*2*FRACUNIT, true);
 						flicky->z += player->capsule->height/2;
 						flicky->angle = (i*(ANGLE_MAX/16));
 						P_InstaThrust(flicky, flicky->angle, 8*FRACUNIT);
-					}
+					}*/
+					mobj_t *idya = P_SpawnMobjFromMobj(player->mo, 0, 0, player->mo->height, MT_GOTEMERALD);
+					idya->extravalue2 = player->mare/5;
+					P_SetTarget(&idya->target, player->mo);
+					P_SetMobjState(idya, mobjinfo[MT_GOTEMERALD].missilestate + ((player->mare + 1) % 5));
+					P_SetTarget(&player->mo->tracer, idya);
 				}
 				for (i = 0; i < MAXPLAYERS; i++)
 					if (playeringame[i] && players[i].mare == player->mare)
 						P_SetTarget(&players[i].capsule, NULL); // Remove capsule from everyone now that it is dead!
 				S_StartScreamSound(player->mo, sfx_ngdone);
+				P_SwitchSpheresBonusMode(true);
 			}
 		}
 		else
@@ -6139,7 +6154,7 @@ static void P_NiGHTSMovement(player_t *player)
 	}
 	else if (P_IsLocalPlayer(player) && player->nightstime == 10*TICRATE)
 //		S_StartSound(NULL, sfx_timeup); // that creepy "out of time" music from NiGHTS. Dummied out, as some on the dev team thought it wasn't Sonic-y enough (Mystic, notably). Uncomment to restore. -SH
-		S_ChangeMusicInternal("_drown",false);
+		S_ChangeMusicInternal((((maptol & TOL_NIGHTS) && !G_IsSpecialStage(gamemap)) ? "_ntime" : "_drown"), false);
 
 
 	if (player->mo->z < player->mo->floorz)
@@ -7477,7 +7492,7 @@ static void P_MovePlayer(player_t *player)
 #endif
 			{
 				if (!(player->pflags & (PF_USEDOWN|PF_GLIDING|PF_SLIDING|PF_SHIELDABILITY)) // If the player is not holding down BT_USE, or having used an ability previously
-					&& (!(player->pflags & PF_THOKKED) || ((player->powers[pw_shield] & SH_NOSTACK) == SH_BUBBLEWRAP && player->secondjump == UINT8_MAX))) // thokked is optional if you're bubblewrapped
+					&& (!(player->powers[pw_shield] & SH_NOSTACK) || !(player->pflags & PF_THOKKED) || ((player->powers[pw_shield] & SH_NOSTACK) == SH_BUBBLEWRAP && player->secondjump == UINT8_MAX))) // thokked is optional if you're bubblewrapped/turning super
 				{
 					// Force shield activation
 					if ((player->powers[pw_shield] & ~(SH_FORCEHP|SH_STACK)) == SH_FORCE)
@@ -7490,6 +7505,11 @@ static void P_MovePlayer(player_t *player)
 					{
 						switch (player->powers[pw_shield] & SH_NOSTACK)
 						{
+							// Super!
+							case SH_NONE:
+								if (P_SuperReady(player))
+									P_DoSuperTransformation(player, false);
+								break;
 							// Whirlwind/Thundercoin shield activation
 							case SH_WHIRLWIND:
 							case SH_THUNDERCOIN:
@@ -9563,11 +9583,8 @@ void P_PlayerThink(player_t *player)
 
 		// If 11 seconds are left on the timer,
 		// begin the drown music for countdown!
-		if (countdown == 11*TICRATE - 1)
-		{
-			if (P_IsLocalPlayer(player))
-				S_ChangeMusicInternal("_drown", false);
-		}
+		if (countdown == 11*TICRATE - 1 && P_IsLocalPlayer(player))
+			S_ChangeMusicInternal("_drown", false);
 
 		// If you've hit the countdown and you haven't made
 		//  it to the exit, you're a goner!
@@ -9667,7 +9684,7 @@ void P_PlayerThink(player_t *player)
 		if (gametype != GT_COOP)
 			player->score = 0;
 		player->mo->health = 1;
-		player->rings = 0;
+		player->rings = player->spheres = 0;
 	}
 	else if ((netgame || multiplayer) && player->lives <= 0 && gametype != GT_COOP)
 	{
@@ -9720,8 +9737,8 @@ void P_PlayerThink(player_t *player)
 
 			mo2 = (mobj_t *)th;
 
-			if (!(mo2->type == MT_NIGHTSWING || mo2->type == MT_RING || mo2->type == MT_COIN
-			   || mo2->type == MT_BLUEBALL))
+			if (!(mo2->type == MT_RING || mo2->type == MT_COIN || mo2->type == MT_BLUESPHERE
+				|| mo2->type == MT_NIGHTSCHIP || mo2->type == MT_NIGHTSSTAR))
 				continue;
 
 			if (P_AproxDistance(P_AproxDistance(mo2->x - x, mo2->y - y), mo2->z - z) > FixedMul(128*FRACUNIT, player->mo->scale))
@@ -10210,7 +10227,7 @@ void P_PlayerAfterThink(player_t *player)
 		if (player->followmobj)
 		{
 			P_RemoveMobj(player->followmobj);
-			player->followmobj = NULL;
+			P_SetTarget(&player->followmobj, NULL);
 		}
 		return;
 	}
@@ -10495,14 +10512,14 @@ void P_PlayerAfterThink(player_t *player)
 	if (player->followmobj && (player->spectator || player->mo->health <= 0 || player->followmobj->type != player->followitem))
 	{
 		P_RemoveMobj(player->followmobj);
-		player->followmobj = NULL;
+		P_SetTarget(&player->followmobj, NULL);
 	}
 
 	if (!player->spectator && player->mo->health && player->followitem)
 	{
 		if (!player->followmobj || P_MobjWasRemoved(player->followmobj))
 		{
-			player->followmobj = P_SpawnMobjFromMobj(player->mo, 0, 0, 0, player->followitem);
+			P_SetTarget(&player->followmobj, P_SpawnMobjFromMobj(player->mo, 0, 0, 0, player->followitem));
 			P_SetTarget(&player->followmobj->tracer, player->mo);
 			player->followmobj->flags2 |= MF2_LINKDRAW;
 		}
diff --git a/src/r_things.c b/src/r_things.c
index b0cb3ab8ef..9e858aba9b 100644
--- a/src/r_things.c
+++ b/src/r_things.c
@@ -2673,7 +2673,7 @@ void SetPlayerSkinByNum(INT32 playernum, INT32 skinnum)
 		if (player->followmobj)
 		{
 			P_RemoveMobj(player->followmobj);
-			player->followmobj = NULL;
+			P_SetTarget(&player->followmobj, NULL);
 		}
 
 		if (player->mo)
diff --git a/src/screen.c b/src/screen.c
index cd97b62fa7..9bbcb995c5 100644
--- a/src/screen.c
+++ b/src/screen.c
@@ -446,10 +446,11 @@ void SCR_ClosedCaptions(void)
 	{
 		if (splitscreen)
 			basey -= 8;
-		else if (((maptol & TOL_NIGHTS) && (modeattacking == ATTACKING_NIGHTS))
-		|| (cv_powerupdisplay.value == 2)
+		else if ((modeattacking == ATTACKING_NIGHTS)
+		|| (!(maptol & TOL_NIGHTS)
+		&& ((cv_powerupdisplay.value == 2)
 		|| (cv_powerupdisplay.value == 1 && ((stplyr == &players[displayplayer] && !camera.chase)
-		|| ((splitscreen && stplyr == &players[secondarydisplayplayer]) && !camera2.chase))))
+		|| ((splitscreen && stplyr == &players[secondarydisplayplayer]) && !camera2.chase))))))
 			basey -= 16;
 	}
 
diff --git a/src/sounds.c b/src/sounds.c
index d3a0ac3735..4070839abd 100644
--- a/src/sounds.c
+++ b/src/sounds.c
@@ -141,7 +141,7 @@ sfxinfo_t S_sfx[NUMSFX] =
   {"bnce1",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Bounce"}, // Boing!
   {"bnce2",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Scatter"}, // Boing!
   {"cannon", false,  64,  8, -1, NULL, 0,        -1,  -1, LUMPERROR, "Powerful shot"},
-  {"cgot" ,   true, 120,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Got Chaos Emerald"}, // Got Emerald! Tails 09-02-2001
+  {"cgot" ,   true, 120,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Got Emerald"}, // Got Emerald! Tails 09-02-2001
   {"cybdth", false,  32,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Explosion"},
   {"deton",   true,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Ominous beeping"},
   {"ding",   false, 127,  8, -1, NULL, 0,        -1,  -1, LUMPERROR, "Ding"},
@@ -214,11 +214,12 @@ sfxinfo_t S_sfx[NUMSFX] =
   {"xideya", false, 127,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Success"}, // Xmas
   {"nbmper", false,  96,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Bumper"},
   {"nxbump", false,  96,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Bumper"}, // Xmas
+  {"ncchip", false, 204,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Got chip"},
   {"ncitem", false, 204,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Got special"},
   {"nxitem", false, 204,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Got special"}, // Xmas
   {"ngdone",  true, 127,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Bonus time start"},
   {"nxdone",  true, 127,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Bonus time start"}, // Xmas
-  {"drill1", false,  48,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Drill start"},
+  {"drill1", false,  48,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Drill"},
   {"drill2", false,  48,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Drill"},
   {"ncspec", false, 204,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Power-up"}, // Tails 12-15-2003
   {"nghurt", false,  96,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Hurt"},
@@ -228,12 +229,13 @@ sfxinfo_t S_sfx[NUMSFX] =
   {"hoop3",  false, 192,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Hoop++"},
   {"hidden", false, 204,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Discovery"},
   {"prloop", false, 104,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Gust of wind"},
-  {"timeup",  true, 256,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Ominous Countdown"},
+  {"ngjump", false,  96,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Jump"},
+  {"peww",   false,  96,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Pew"},
 
   // Halloween
   {"lntsit", false,  60,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Cacolantern awake"},
   {"lntdie", false,  60,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Cacolantern death"},
-  {"pumpkn", false,  60,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Pumpkin smash"},
+  {"pumpkn", false,  60,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Pumpkin smash"}, // idspispopd
   {"ghosty", false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Laughter"},
 
   // Mario
@@ -334,10 +336,10 @@ sfxinfo_t S_sfx[NUMSFX] =
   {"s3k62",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Jump"},
   {"s3k63",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Starpost"},
   {"s3k64",  false,  64,  2, -1, NULL, 0,        -1,  -1, LUMPERROR, "Clatter"},
-  {"s3k65",  false, 255,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Got blue sphere"}, // Blue Spheres
+  {"s3k65",  false, 255,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Got sphere"}, // Blue Spheres
   {"s3k66",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Special stage end"},
   {"s3k67",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Firing missile"},
-  {"s3k68",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Unknown possibilities"},
+  {"s3k68",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Discovery"},
   {"s3k69",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Switch click"},
   {"s3k6a",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Special stage clear"},
   {"s3k6b",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Punch"},
diff --git a/src/sounds.h b/src/sounds.h
index 56189f55fa..5fffc7b2e4 100644
--- a/src/sounds.h
+++ b/src/sounds.h
@@ -280,6 +280,7 @@ typedef enum
 	sfx_xideya, // Xmas
 	sfx_nbmper,
 	sfx_nxbump, // Xmas
+	sfx_ncchip,
 	sfx_ncitem,
 	sfx_nxitem, // Xmas
 	sfx_ngdone,
@@ -294,7 +295,8 @@ typedef enum
 	sfx_hoop3,
 	sfx_hidden,
 	sfx_prloop,
-	sfx_timeup, // Was gonna be played when less than ten seconds are on the clock; uncomment uses of this to see it in-context
+	sfx_ngjump,
+	sfx_peww,
 
 	// Halloween
 	sfx_lntsit,
diff --git a/src/st_stuff.c b/src/st_stuff.c
index a513a028c6..031aa61db4 100644
--- a/src/st_stuff.c
+++ b/src/st_stuff.c
@@ -112,6 +112,8 @@ static patch_t *yelstat;
 static patch_t *nbracket;
 static patch_t *nhud[12];
 static patch_t *nsshud;
+static patch_t *nbon[12];
+static patch_t *nssbon;
 static patch_t *narrow[9];
 static patch_t *nredar[8]; // Red arrow
 static patch_t *drillbar;
@@ -309,8 +311,12 @@ void ST_LoadGraphics(void)
 	yelstat = W_CachePatchName("YELSTAT", PU_HUDGFX);
 	nbracket = W_CachePatchName("NBRACKET", PU_HUDGFX);
 	for (i = 0; i < 12; ++i)
+	{
 		nhud[i] = W_CachePatchName(va("NHUD%d", i+1), PU_HUDGFX);
+		nbon[i] = W_CachePatchName(va("NBON%d", i+1), PU_HUDGFX);
+	}
 	nsshud = W_CachePatchName("NSSHUD", PU_HUDGFX);
+	nssbon = W_CachePatchName("NSSBON", PU_HUDGFX);
 	minicaps = W_CachePatchName("MINICAPS", PU_HUDGFX);
 
 	for (i = 0; i < 8; ++i)
@@ -735,18 +741,7 @@ static inline void ST_drawRings(void)
 
 	ST_DrawPatchFromHud(HUD_RINGS, ((!stplyr->spectator && stplyr->rings <= 0 && leveltime/5 & 1) ? sboredrings : sborings), ((stplyr->spectator) ? V_HUDTRANSHALF : V_HUDTRANS));
 
-	if (objectplacing)
-		ringnum = op_currentdoomednum;
-	else if (!useNightsSS && G_IsSpecialStage(gamemap))
-	{
-		INT32 i;
-		ringnum = 0;
-		for (i = 0; i < MAXPLAYERS; i++)
-			if (playeringame[i] && players[i].mo && players[i].rings > 0)
-				ringnum += players[i].rings;
-	}
-	else
-		ringnum = max(stplyr->rings, 0);
+	ringnum = ((objectplacing) ? op_currentdoomednum : max(stplyr->rings, 0));
 
 	if (cv_timetic.value == 2) // Yes, even in modeattacking
 		ST_DrawNumFromHud(HUD_RINGSNUMTICS, ringnum, V_PERPLAYER|((stplyr->spectator) ? V_HUDTRANSHALF : V_HUDTRANS));
@@ -1394,17 +1389,17 @@ static void ST_drawNightsRecords(void)
 		V_DrawCenteredString(BASEVIDWIDTH/2, 60, aflag,
 		                     va(M_GetText("\x80GET\x82 %d\x80 %s%s%s!"), stplyr->capsule->health,
 		                        (stplyr->textvar == 3) ? M_GetText("MORE ") : "",
-		                        (G_IsSpecialStage(gamemap)) ? "SPHERE" : "RING",
+		                        (G_IsSpecialStage(gamemap)) ? "SPHERE" : "CHIP",
 		                        (stplyr->capsule->health > 1) ? "S" : ""));
 	}
 
 	// End Bonus
 	else if (stplyr->textvar == 4)
 	{
-		V_DrawString(BASEVIDWIDTH/2 - 56, 140, aflag, (G_IsSpecialStage(gamemap)) ? "SPHERES:" : "RINGS:");
+		V_DrawString(BASEVIDWIDTH/2 - 56, 140, aflag, (G_IsSpecialStage(gamemap)) ? "SPHERES:" : "CHIPS:");
 		V_DrawString(BASEVIDWIDTH/2 - 56, 148, aflag, "BONUS:");
-		V_DrawRightAlignedString(BASEVIDWIDTH/2 + 56, 140, V_ORANGEMAP|aflag, va("%d", stplyr->finishedrings));
-		V_DrawRightAlignedString(BASEVIDWIDTH/2 + 56, 140, V_ORANGEMAP|aflag, va("%d", stplyr->finishedrings * 50));
+		V_DrawRightAlignedString(BASEVIDWIDTH/2 + 56, 140, V_ORANGEMAP|aflag, va("%d", stplyr->finishedspheres));
+		V_DrawRightAlignedString(BASEVIDWIDTH/2 + 56, 148, V_ORANGEMAP|aflag, va("%d", stplyr->finishedspheres * 50));
 		ST_DrawNightsOverlayNum((BASEVIDWIDTH/2 + 56)<<FRACBITS, 160<<FRACBITS, FRACUNIT, aflag, stplyr->lastmarescore, nightsnum, SKINCOLOR_AZURE);
 
 		// If new record, say so!
@@ -1462,15 +1457,15 @@ static void ST_drawNiGHTSHUD(void)
 {
 	INT32 origamount;
 	INT32 minlink = 1;
-	INT32 total_ringcount;
-
-	// When debugging, show "0 Link".
-	if (cv_debug & DBG_NIGHTSBASIC)
-		minlink = 0;
+	INT32 total_spherecount;
+	const boolean oldspecialstage = (G_IsSpecialStage(gamemap) && !(maptol & TOL_NIGHTS));
 
 	// Cheap hack: don't display when the score is showing (it popping up for a split second when exiting a map is intentional)
-	if (stplyr->texttimer && stplyr->textvar == 4)
+	if (oldspecialstage || (stplyr->texttimer && stplyr->textvar == 4))
 		minlink = INT32_MAX;
+	// When debugging, show "0 Link".
+	else if (cv_debug & DBG_NIGHTSBASIC)
+		minlink = 0;
 
 	// Drill meter
 	if (
@@ -1576,25 +1571,35 @@ static void ST_drawNiGHTSHUD(void)
 
 	// Begin drawing brackets/chip display
 #ifdef HAVE_BLUA
-	if (LUA_HudEnabled(hud_nightsrings))
+	if (LUA_HudEnabled(hud_nightsspheres))
 	{
 #endif
 	ST_DrawTopLeftOverlayPatch(16, 8, nbracket);
 	if (G_IsSpecialStage(gamemap))
-		ST_DrawTopLeftOverlayPatch(24, 16, nsshud);
+		ST_DrawTopLeftOverlayPatch(24, 16, ((stplyr->bonustime && (leveltime & 4)) ? nssbon : nsshud));
+	else if (stplyr->bonustime)
+		ST_DrawTopLeftOverlayPatch(24, 16, nbon[(leveltime/2)%12]);
 	else
 		ST_DrawTopLeftOverlayPatch(24, 16, nhud[(leveltime/2)%12]);
 
 	if (G_IsSpecialStage(gamemap))
 	{
 		INT32 i;
-		total_ringcount = 0;
+		total_spherecount = 0;
 		for (i = 0; i < MAXPLAYERS; i++)
-			if (playeringame[i] /*&& players[i].powers[pw_carry] == CR_NIGHTSMODE*/ && players[i].rings)
-				total_ringcount += players[i].rings;
+			if (playeringame[i] /*&& players[i].powers[pw_carry] == CR_NIGHTSMODE*/ && players[i].spheres)
+				total_spherecount += players[i].spheres;
 	}
 	else
-		total_ringcount = stplyr->rings;
+		total_spherecount = stplyr->spheres;
+
+	/*if (oldspecialstage)
+	{
+		if (total_spherecount < ssspheres)
+			total_spherecount = ssspheres - total_spherecount;
+		else
+			total_spherecount = 0;
+	}*/
 
 	if (stplyr->capsule)
 	{
@@ -1650,28 +1655,48 @@ static void ST_drawNiGHTSHUD(void)
 			amount = (origamount - stplyr->capsule->health);
 			amount = (amount * length)/origamount;
 
-			for (cfill = 0; cfill < amount && cfill < 88; ++cfill)
+			for (cfill = 0; cfill < amount && cfill < length; ++cfill)
 				V_DrawScaledPatch(15 + cfill + 1, 8 + 35, V_PERPLAYER|V_SNAPTOLEFT|V_SNAPTOTOP|V_HUDTRANS, capsulefill);
 		}
 
-		if (total_ringcount >= stplyr->capsule->health)
-			ST_DrawTopLeftOverlayPatch(40, 8 + 5, nredar[leveltime%8]);
+		if (total_spherecount >= stplyr->capsule->health)
+			ST_DrawTopLeftOverlayPatch(40, 8 + 5, nredar[leveltime&7]);
 		else
-			ST_DrawTopLeftOverlayPatch(40, 8 + 5, narrow[(leveltime/2)%8]);
+			ST_DrawTopLeftOverlayPatch(40, 8 + 5, narrow[(leveltime/2)&7]);
+	}
+	else if (oldspecialstage && total_spherecount < ssspheres)
+	{
+		INT32 cfill, amount;
+		const INT32 length = 88;
+		UINT8 em = P_GetNextEmerald();
+		ST_DrawTopLeftOverlayPatch(72, 8, nbracket);
+
+		if (em <= 7)
+			ST_DrawTopLeftOverlayPatch(80, 8 + 8, emeraldpics[0][em]);
+
+		ST_DrawTopLeftOverlayPatch(40, 8 + 5, narrow[(leveltime/2)&7]);
+
+		// Lil' white box!
+		V_DrawScaledPatch(15, 8 + 34, V_PERPLAYER|V_SNAPTOLEFT|V_SNAPTOTOP|V_HUDTRANS, capsulebar);
+
+		amount = (total_spherecount * length)/ssspheres;
+
+		for (cfill = 0; cfill < amount && cfill < length; ++cfill)
+			V_DrawScaledPatch(15 + cfill + 1, 8 + 35, V_PERPLAYER|V_SNAPTOLEFT|V_SNAPTOTOP|V_HUDTRANS, capsulefill);
 	}
 	else
 		ST_DrawTopLeftOverlayPatch(40, 8 + 5, narrow[8]);
 
-	if (total_ringcount >= 100)
-		V_DrawTallNum((total_ringcount >= 1000) ? 76 : 72, 8 + 11, V_PERPLAYER|V_SNAPTOTOP|V_SNAPTOLEFT|V_HUDTRANS, total_ringcount);
+	if (total_spherecount >= 100)
+		V_DrawTallNum((total_spherecount >= 1000) ? 76 : 72, 8 + 11, V_PERPLAYER|V_SNAPTOTOP|V_SNAPTOLEFT|V_HUDTRANS, total_spherecount);
 	else
-		V_DrawTallNum(68, 8 + 11, V_PERPLAYER|V_SNAPTOTOP|V_SNAPTOLEFT|V_HUDTRANS, total_ringcount);
+		V_DrawTallNum(68, 8 + 11, V_PERPLAYER|V_SNAPTOTOP|V_SNAPTOLEFT|V_HUDTRANS, total_spherecount);
 #ifdef HAVE_BLUA
 	}
 #endif
 
 	// Score
-	if (!stplyr->exiting
+	if (!stplyr->exiting && !oldspecialstage
 #ifdef HAVE_BLUA
 	&& LUA_HudEnabled(hud_nightsscore)
 #endif
@@ -1714,6 +1739,7 @@ static void ST_drawNiGHTSHUD(void)
 	{
 		INT32 realnightstime = stplyr->nightstime/TICRATE;
 		INT32 numbersize;
+		UINT8 col = ((realnightstime < 10) ? SKINCOLOR_RED : SKINCOLOR_SUPERGOLD4);
 
 		if (G_IsSpecialStage(gamemap))
 		{
@@ -1744,46 +1770,66 @@ static void ST_drawNiGHTSHUD(void)
 		else
 			numbersize = 48/2;
 
-		ST_DrawNightsOverlayNum((160 + numbersize)<<FRACBITS, 14<<FRACBITS, FRACUNIT, V_PERPLAYER|V_SNAPTOTOP, realnightstime, nightsnum,
-			((realnightstime < 10) ? SKINCOLOR_RED : SKINCOLOR_SUPERGOLD4));
+		if ((oldspecialstage && leveltime & 2)
+			&& (stplyr->mo->eflags & (MFE_TOUCHWATER|MFE_UNDERWATER)))
+			col = SKINCOLOR_ORANGE;
+
+		ST_DrawNightsOverlayNum((160 + numbersize)<<FRACBITS, 14<<FRACBITS, FRACUNIT, V_PERPLAYER|V_SNAPTOTOP, realnightstime, nightsnum, col);
 
 		// Show exact time in debug
 		if (cv_debug & DBG_NIGHTSBASIC)
 			V_DrawString(160 + numbersize + 8, 24, V_SNAPTOTOP|((realnightstime < 10) ? V_REDMAP : V_YELLOWMAP), va("%02d", G_TicsToCentiseconds(stplyr->nightstime)));
 	}
 
-	// Show pickup durations
-	if (cv_debug & DBG_NIGHTSBASIC)
+	if (oldspecialstage)
 	{
-		UINT16 pwr;
-
-		if (stplyr->powers[pw_nights_superloop])
+		if (leveltime < 5*TICRATE)
 		{
-			pwr = stplyr->powers[pw_nights_superloop];
-			V_DrawSmallScaledPatch(110, 44, 0, W_CachePatchName("NPRUA0",PU_CACHE));
-			V_DrawThinString(106, 52, V_MONOSPACE, va("%2d.%02d", pwr/TICRATE, G_TicsToCentiseconds(pwr)));
+			INT32 aflag = V_PERPLAYER;
+			tic_t drawtime = (5*TICRATE) - leveltime;
+			if (drawtime < TICRATE/2)
+				aflag |= (9 - 9*drawtime/(TICRATE/2)) << V_ALPHASHIFT;
+			// This one, not quite as much so.
+			V_DrawCenteredString(BASEVIDWIDTH/2, 60, aflag,
+		                     va(M_GetText("\x80GET\x82 %d\x80 SPHERE%s!"), ssspheres,
+		                        (ssspheres > 1) ? "S" : ""));
 		}
-
-		if (stplyr->powers[pw_nights_helper])
+	}
+	else
+	{
+		// Show pickup durations
+		if (cv_debug & DBG_NIGHTSBASIC)
 		{
-			pwr = stplyr->powers[pw_nights_helper];
-			V_DrawSmallScaledPatch(150, 44, 0, W_CachePatchName("NPRUC0",PU_CACHE));
-			V_DrawThinString(146, 52, V_MONOSPACE, va("%2d.%02d", pwr/TICRATE, G_TicsToCentiseconds(pwr)));
-		}
+			UINT16 pwr;
 
-		if (stplyr->powers[pw_nights_linkfreeze])
-		{
-			pwr = stplyr->powers[pw_nights_linkfreeze];
-			V_DrawSmallScaledPatch(190, 44, 0, W_CachePatchName("NPRUE0",PU_CACHE));
-			V_DrawThinString(186, 52, V_MONOSPACE, va("%2d.%02d", pwr/TICRATE, G_TicsToCentiseconds(pwr)));
+			if (stplyr->powers[pw_nights_superloop])
+			{
+				pwr = stplyr->powers[pw_nights_superloop];
+				V_DrawSmallScaledPatch(110, 44, 0, W_CachePatchName("NPRUA0",PU_CACHE));
+				V_DrawThinString(106, 52, V_MONOSPACE, va("%2d.%02d", pwr/TICRATE, G_TicsToCentiseconds(pwr)));
+			}
+
+			if (stplyr->powers[pw_nights_helper])
+			{
+				pwr = stplyr->powers[pw_nights_helper];
+				V_DrawSmallScaledPatch(150, 44, 0, W_CachePatchName("NPRUC0",PU_CACHE));
+				V_DrawThinString(146, 52, V_MONOSPACE, va("%2d.%02d", pwr/TICRATE, G_TicsToCentiseconds(pwr)));
+			}
+
+			if (stplyr->powers[pw_nights_linkfreeze])
+			{
+				pwr = stplyr->powers[pw_nights_linkfreeze];
+				V_DrawSmallScaledPatch(190, 44, 0, W_CachePatchName("NPRUE0",PU_CACHE));
+				V_DrawThinString(186, 52, V_MONOSPACE, va("%2d.%02d", pwr/TICRATE, G_TicsToCentiseconds(pwr)));
+			}
 		}
-	}
 
-	// Records/extra text
+		// Records/extra text
 #ifdef HAVE_BLUA
-	if (LUA_HudEnabled(hud_nightsrecords))
+		if (LUA_HudEnabled(hud_nightsrecords))
 #endif
-	ST_drawNightsRecords();
+		ST_drawNightsRecords();
+	}
 }
 
 static inline void ST_drawWeaponSelect(INT32 xoffs, INT32 y)
@@ -1952,7 +1998,7 @@ static void ST_drawTextHUD(void)
 	}
 	else if (stplyr->spectator && (gametype != GT_COOP || stplyr->playerstate == PST_LIVE))
 	{
-		if (G_IsSpecialStage(gamemap) && useNightsSS)
+		if (G_IsSpecialStage(gamemap) && (maptol & TOL_NIGHTS))
 			textHUDdraw(M_GetText("\x82""Wait for the stage to end..."))
 		else if (gametype == GT_COOP)
 		{
@@ -2070,22 +2116,22 @@ num:
 #undef SEP
 }
 
-static void ST_drawSpecialStageHUD(void)
+/*static void ST_drawSpecialStageHUD(void)
 {
-	if (totalrings > 0)
+	if (ssspheres > 0)
 	{
 		if (hudinfo[HUD_SS_TOTALRINGS].x)
-			ST_DrawNumFromHud(HUD_SS_TOTALRINGS, totalrings, V_HUDTRANS);
+			ST_DrawNumFromHud(HUD_SS_TOTALRINGS, ssspheres, V_HUDTRANS);
 		else if (cv_timetic.value == 2)
-			V_DrawTallNum(hudinfo[HUD_RINGSNUMTICS].x, hudinfo[HUD_SS_TOTALRINGS].y, hudinfo[HUD_RINGSNUMTICS].f|V_PERPLAYER|V_HUDTRANS, totalrings);
+			V_DrawTallNum(hudinfo[HUD_RINGSNUMTICS].x, hudinfo[HUD_SS_TOTALRINGS].y, hudinfo[HUD_RINGSNUMTICS].f|V_PERPLAYER|V_HUDTRANS, ssspheres);
 		else
-			V_DrawTallNum(hudinfo[HUD_RINGSNUM].x, hudinfo[HUD_SS_TOTALRINGS].y, hudinfo[HUD_RINGSNUM].f|V_PERPLAYER|V_HUDTRANS, totalrings);
+			V_DrawTallNum(hudinfo[HUD_RINGSNUM].x, hudinfo[HUD_SS_TOTALRINGS].y, hudinfo[HUD_RINGSNUM].f|V_PERPLAYER|V_HUDTRANS, ssspheres);
 	}
 
-	if (leveltime < 5*TICRATE && totalrings > 0)
+	if (leveltime < 5*TICRATE && ssspheres > 0)
 	{
 		ST_DrawPatchFromHud(HUD_GETRINGS, getall, V_HUDTRANS);
-		ST_DrawNumFromHud(HUD_GETRINGSNUM, totalrings, V_HUDTRANS);
+		ST_DrawNumFromHud(HUD_GETRINGSNUM, ssspheres, V_HUDTRANS);
 	}
 
 	if (sstimer)
@@ -2095,7 +2141,7 @@ static void ST_drawSpecialStageHUD(void)
 	}
 	else
 		ST_DrawPatchFromHud(HUD_TIMEUP, timeup, V_HUDTRANS);
-}
+}*/
 
 static INT32 ST_drawEmeraldHuntIcon(mobj_t *hunt, patch_t **patches, INT32 offset)
 {
@@ -2231,7 +2277,7 @@ static void ST_overlayDrawer(void)
 	//hu_showscores = auto hide score/time/rings when tab rankings are shown
 	if (!(hu_showscores && (netgame || multiplayer)))
 	{
-		if (maptol & TOL_NIGHTS)
+		if (maptol & TOL_NIGHTS || G_IsSpecialStage(gamemap))
 			ST_drawNiGHTSHUD();
 		else
 		{
@@ -2340,10 +2386,6 @@ static void ST_overlayDrawer(void)
 		if (gametype == GT_RACE || gametype == GT_COMPETITION)
 			ST_drawRaceHUD();
 
-		// Special Stage HUD
-		if (!useNightsSS && G_IsSpecialStage(gamemap) && stplyr == &players[displayplayer])
-			ST_drawSpecialStageHUD();
-
 		// Emerald Hunt Indicators
 		if (cv_itemfinder.value && M_SecretUnlocked(SECRET_ITEMFINDER))
 			ST_doItemFinderIconsAndSound();
@@ -2362,15 +2404,18 @@ static void ST_overlayDrawer(void)
 		}
 
 		// This is where we draw all the fun cheese if you have the chasecam off!
-		if ((stplyr == &players[displayplayer] && !camera.chase)
-		|| ((splitscreen && stplyr == &players[secondarydisplayplayer]) && !camera2.chase))
+		if (!(maptol & TOL_NIGHTS))
 		{
-			ST_drawFirstPersonHUD();
-			if (cv_powerupdisplay.value)
+			if ((stplyr == &players[displayplayer] && !camera.chase)
+			|| ((splitscreen && stplyr == &players[secondarydisplayplayer]) && !camera2.chase))
+			{
+				ST_drawFirstPersonHUD();
+				if (cv_powerupdisplay.value)
+					ST_drawPowerupHUD();
+			}
+			else if (cv_powerupdisplay.value == 2)
 				ST_drawPowerupHUD();
 		}
-		else if (cv_powerupdisplay.value == 2)
-			ST_drawPowerupHUD();
 	}
 
 #ifdef HAVE_BLUA
diff --git a/src/y_inter.c b/src/y_inter.c
index ed9cc4185b..2194dc96fe 100644
--- a/src/y_inter.c
+++ b/src/y_inter.c
@@ -78,7 +78,7 @@ typedef union
 	struct
 	{
 		char passed1[29]; // KNUCKLES GOT    / CRAWLA HONCHO
-		char passed2[17];             // A CHAOS EMERALD / GOT THEM ALL!
+		char passed2[17];             // A CHAOS EMERALD? / GOT THEM ALL!
 		char passed3[15];             //                   CAN NOW BECOME
 		char passed4[SKINNAMESIZE+7]; //                   SUPER CRAWLA HONCHO
 		INT32 passedx1;
@@ -315,6 +315,8 @@ void Y_IntermissionDrawer(void)
 		INT32 xoffset1 = 0; // Line 1 x offset
 		INT32 xoffset2 = 0; // Line 2 x offset
 		INT32 xoffset3 = 0; // Line 3 x offset
+		INT32 xoffset4 = 0; // Bonus line x offset
+		INT32 xoffset5 = 0; // Score line x offset
 		UINT8 drawsection = 0;
 
 		if (gottoken) // first to be behind everything else
@@ -324,28 +326,40 @@ void Y_IntermissionDrawer(void)
 		if (intertic <= 2*TICRATE)
 			animatetic = 0;
 		else if (!animatetic && data.spec.bonus.points == 0 && data.spec.passed3[0] != '\0')
-			animatetic = intertic;
+			animatetic = intertic + TICRATE;
 
-		if (animatetic)
+		if (animatetic && intertic >= animatetic)
 		{
 			INT32 animatetimer = (intertic - animatetic);
-			if (animatetimer <= 8)
+			if (animatetimer <= 12)
 			{
 				xoffset1 = -(animatetimer     * 40);
 				xoffset2 = -((animatetimer-2) * 40);
+				xoffset4 = -((animatetimer-4) * 40);
+				xoffset5 = -((animatetimer-6) * 40);
 				if (xoffset2 > 0) xoffset2 = 0;
+				if (xoffset4 > 0) xoffset4 = 0;
+				if (xoffset5 > 0) xoffset5 = 0;
 			}
-			else if (animatetimer <= 19)
+			else if (animatetimer < 28)
 			{
 				drawsection = 1;
-				xoffset1 = (16-animatetimer) * 40;
-				xoffset2 = (18-animatetimer) * 40;
-				xoffset3 = (20-animatetimer) * 40;
+				xoffset1 = (20-animatetimer) * 40;
+				xoffset2 = (22-animatetimer) * 40;
+				xoffset3 = (24-animatetimer) * 40;
+				xoffset4 = (26-animatetimer) * 40;
+				xoffset5 = (28-animatetimer) * 40;
 				if (xoffset1 < 0) xoffset1 = 0;
 				if (xoffset2 < 0) xoffset2 = 0;
+				if (xoffset3 < 0) xoffset3 = 0;
+				if (xoffset4 < 0) xoffset4 = 0;
 			}
 			else
+			{
 				drawsection = 1;
+				if (animatetimer == 28)
+					S_StartSound(NULL, sfx_s3k68);
+			}
 		}
 
 		if (drawsection == 1)
@@ -356,39 +370,60 @@ void Y_IntermissionDrawer(void)
 			V_DrawLevelTitle(data.spec.passedx3 + xoffset2, ttheight, 0, data.spec.passed3);
 			ttheight += V_LevelNameHeight(data.spec.passed4) + 2;
 			V_DrawLevelTitle(data.spec.passedx4 + xoffset3, ttheight, 0, data.spec.passed4);
-		}
-		else if (data.spec.passed1[0] != '\0')
-		{
-			ttheight = 24;
-			V_DrawLevelTitle(data.spec.passedx1 + xoffset1, ttheight, 0, data.spec.passed1);
-			ttheight += V_LevelNameHeight(data.spec.passed2) + 2;
-			V_DrawLevelTitle(data.spec.passedx2 + xoffset2, ttheight, 0, data.spec.passed2);
+
+			V_DrawCenteredString(BASEVIDWIDTH/2 + xoffset4, 108 - 4, 0, "\x86""50 RINGS, NO SHIELD");
+			V_DrawCenteredString(BASEVIDWIDTH/2 + xoffset5, 124 - 4, 0, "\x86""PRESS ""\x82""JUMP""\x86"", THEN ""\x82""SPIN");
 		}
 		else
 		{
-			ttheight = 24 + (V_LevelNameHeight(data.spec.passed2)/2) + 2;
-			V_DrawLevelTitle(data.spec.passedx2 + xoffset1, ttheight, 0, data.spec.passed2);
+			if (data.spec.passed1[0] != '\0')
+			{
+				ttheight = 24;
+				V_DrawLevelTitle(data.spec.passedx1 + xoffset1, ttheight, 0, data.spec.passed1);
+				ttheight += V_LevelNameHeight(data.spec.passed2) + 2;
+				V_DrawLevelTitle(data.spec.passedx2 + xoffset2, ttheight, 0, data.spec.passed2);
+			}
+			else
+			{
+				ttheight = 24 + (V_LevelNameHeight(data.spec.passed2)/2) + 2;
+				V_DrawLevelTitle(data.spec.passedx2 + xoffset1, ttheight, 0, data.spec.passed2);
+			}
+
+			V_DrawScaledPatch(152 + xoffset4, 108, 0, data.spec.bonuspatch);
+			V_DrawTallNum(BASEVIDWIDTH + xoffset4 - 68, 109, 0, data.spec.bonus.points);
+			V_DrawScaledPatch(152 + xoffset5, 124, 0, data.spec.pscore);
+			V_DrawTallNum(BASEVIDWIDTH + xoffset5 - 68, 125, 0, data.spec.score);
 		}
 
 		// draw the emeralds
 		//if (intertic & 1)
 		{
+			boolean drawthistic = !(ALL7EMERALDS(emeralds) && (intertic & 1));
 			INT32 emeraldx = 152 - 3*28;
 			INT32 em = (gamemap - sstage_start);
 
-			for (i = 0; i < 7; ++i)
+			if (em == 7)
 			{
-				if ((i != em) && !(intertic & 1) && (emeralds & (1 << i)))
-					V_DrawScaledPatch(emeraldx, 74, 0, emeraldpics[0][i]);
-				emeraldx += 28;
+				if (!stagefailed)
+				{
+					fixed_t adjust = 2*(FINESINE(FixedAngle((intertic + 1)<<(FRACBITS-4)) & FINEMASK));
+					V_DrawFixedPatch(152<<FRACBITS, (74<<FRACBITS) - adjust, FRACUNIT, 0, emeraldpics[0][em], NULL);
+				}
 			}
-
-			if (em < 7)
+			else if (em < 7)
 			{
 				static UINT8 emeraldbounces = 0;
 				static INT32 emeraldmomy = 20;
 				static INT32 emeraldy = -40;
 
+				if (drawthistic)
+					for (i = 0; i < 7; ++i)
+					{
+						if ((i != em) && (emeralds & (1 << i)))
+							V_DrawScaledPatch(emeraldx, 74, 0, emeraldpics[0][i]);
+						emeraldx += 28;
+					}
+
 				emeraldx = 152 + (em-3)*28;
 
 				if (intertic <= 1)
@@ -399,7 +434,7 @@ void Y_IntermissionDrawer(void)
 				}
 				else
 				{
-					if (emeralds & (1 << em))
+					if (!stagefailed)
 					{
 						if (emeraldbounces < 3)
 						{
@@ -427,16 +462,12 @@ void Y_IntermissionDrawer(void)
 							emeraldy = 74;
 						}
 					}
-					V_DrawScaledPatch(emeraldx, emeraldy, 0, emeraldpics[0][em]);
+					if (drawthistic)
+						V_DrawScaledPatch(emeraldx, emeraldy, 0, emeraldpics[0][em]);
 				}
 			}
 		}
 
-		V_DrawScaledPatch(152, 108, 0, data.spec.bonuspatch);
-		V_DrawTallNum(BASEVIDWIDTH - 68, 109, 0, data.spec.bonus.points);
-		V_DrawScaledPatch(152, 124, 0, data.spec.pscore);
-		V_DrawTallNum(BASEVIDWIDTH - 68, 125, 0, data.spec.score);
-
 		// Draw continues!
 		if (!multiplayer /* && (data.spec.continues & 0x80) */) // Always draw outside of netplay
 		{
@@ -831,7 +862,7 @@ void Y_Ticker(void)
 			if ((!modifiedgame || savemoddata) && !(netgame || multiplayer) && !demoplayback)
 			{
 				if (M_UpdateUnlockablesAndExtraEmblems())
-					S_StartSound(NULL, sfx_ncitem);
+					S_StartSound(NULL, sfx_s3k68);
 
 				G_SaveGameData();
 			}
@@ -865,7 +896,7 @@ void Y_Ticker(void)
 			if (playeringame[i] && (players[i].cmd.buttons & BT_USE))
 				skip = true;
 
-		if ((data.spec.continues & 0x80) && tallydonetic != -1)
+		if (((data.spec.continues & 0x80) || ALL7EMERALDS(emeralds)) && tallydonetic != -1)
 		{
 			if ((intertic - tallydonetic) > (3*TICRATE)/2)
 			{
@@ -896,7 +927,7 @@ void Y_Ticker(void)
 			if ((!modifiedgame || savemoddata) && !(netgame || multiplayer) && !demoplayback)
 			{
 				if (M_UpdateUnlockablesAndExtraEmblems())
-					S_StartSound(NULL, sfx_ncitem);
+					S_StartSound(NULL, sfx_s3k68);
 
 				G_SaveGameData();
 			}
@@ -1307,6 +1338,11 @@ void Y_StartIntermission(void)
 				else
 					strcpy(data.spec.passed1, "YOU GOT");
 				strcpy(data.spec.passed2, "A CHAOS EMERALD");
+				if (gamemap > (sstage_start + 5))
+				{
+					data.spec.passed2[15] = '?';
+					data.spec.passed2[16] = '\0';
+				}
 			}
 			data.spec.passedx1 = (BASEVIDWIDTH - V_LevelNameWidth(data.spec.passed1))/2;
 			data.spec.passedx2 = (BASEVIDWIDTH - V_LevelNameWidth(data.spec.passed2))/2;
@@ -1857,7 +1893,7 @@ static void Y_AwardSpecialStageBonus(void)
 
 		if (!playeringame[i] || players[i].lives < 1) // not active or game over
 			Y_SetNullBonus(&players[i], &localbonus);
-		else if (useNightsSS) // Link instead of Score
+		else if (maptol & TOL_NIGHTS) // Link instead of Rings
 			Y_SetLinkBonus(&players[i], &localbonus);
 		else
 			Y_SetRingBonus(&players[i], &localbonus);
-- 
GitLab