From 8abeae7805eb1350a5a12ced1074276d971162d6 Mon Sep 17 00:00:00 2001
From: wolfy852 <wolfy852@hotmail.com>
Date: Fri, 15 Jun 2018 05:20:01 -0500
Subject: [PATCH 01/38] Chat Manager

---
 src/d_main.c           |   1 +
 src/d_netcmd.c         |   7 +
 src/doomdef.h          |   1 +
 src/g_game.c           |  26 ++
 src/g_game.h           |   1 +
 src/hardware/hw_draw.c | 105 +++++
 src/hardware/hw_main.h |   1 +
 src/hu_stuff.c         | 990 +++++++++++++++++++++++++++++++++++++----
 src/hu_stuff.h         |   6 +
 src/lua_baselib.c      |  47 ++
 src/lua_hook.h         |   2 +-
 src/lua_hooklib.c      |  19 +-
 src/v_video.c          | 151 ++++++-
 src/v_video.h          |   3 +
 14 files changed, 1262 insertions(+), 98 deletions(-)

diff --git a/src/d_main.c b/src/d_main.c
index b12dcdbf..367a0e08 100644
--- a/src/d_main.c
+++ b/src/d_main.c
@@ -184,6 +184,7 @@ void D_PostEvent_end(void) {};
 UINT8 shiftdown = 0; // 0x1 left, 0x2 right
 UINT8 ctrldown = 0; // 0x1 left, 0x2 right
 UINT8 altdown = 0; // 0x1 left, 0x2 right
+boolean capslock = 0; // jeez i wonder what this does.
 //
 // D_ModifierKeyResponder
 // Sets global shift/ctrl/alt variables, never actually eats events
diff --git a/src/d_netcmd.c b/src/d_netcmd.c
index 2f2657d7..5a4227c3 100644
--- a/src/d_netcmd.c
+++ b/src/d_netcmd.c
@@ -776,6 +776,13 @@ void D_RegisterClientCommands(void)
 	CV_RegisterVar(&cv_usegamma);
 
 	// m_menu.c
+	CV_RegisterVar(&cv_compactscoreboard);
+	CV_RegisterVar(&cv_chatheight);
+	CV_RegisterVar(&cv_chatwidth);
+	CV_RegisterVar(&cv_chattime);
+	CV_RegisterVar(&cv_chatspamprotection);
+	CV_RegisterVar(&cv_consolechat);
+	CV_RegisterVar(&cv_chatnotifications);
 	CV_RegisterVar(&cv_crosshair);
 	CV_RegisterVar(&cv_crosshair2);
 	CV_RegisterVar(&cv_crosshair3);
diff --git a/src/doomdef.h b/src/doomdef.h
index 175838c0..278dc002 100644
--- a/src/doomdef.h
+++ b/src/doomdef.h
@@ -434,6 +434,7 @@ extern INT32 cv_debug;
 
 // Modifier key variables, accessible anywhere
 extern UINT8 shiftdown, ctrldown, altdown;
+extern boolean capslock;
 
 // if we ever make our alloc stuff...
 #define ZZ_Alloc(x) Z_Malloc(x, PU_STATIC, NULL)
diff --git a/src/g_game.c b/src/g_game.c
index bbce2d3e..9224ad84 100644
--- a/src/g_game.c
+++ b/src/g_game.c
@@ -397,6 +397,32 @@ static CV_PossibleValue_t joyaxis_cons_t[] = {{0, "None"},
 #endif
 #endif
 
+// don't mind me putting these here, I was lazy to figure out where else I could put those without blowing up the compiler.
+
+// it automatically becomes compact with 20+ players, but if you like it, I guess you can turn that on!
+consvar_t cv_compactscoreboard= {"compactscoreboard", "Off", CV_SAVE, CV_OnOff, NULL, 0, NULL, NULL, 0, 0, NULL};
+
+// chat timer thingy
+static CV_PossibleValue_t chattime_cons_t[] = {{5, "MIN"}, {999, "MAX"}, {0, NULL}};
+consvar_t cv_chattime = {"chattime", "8", CV_SAVE, chattime_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL};
+
+// chatwidth
+static CV_PossibleValue_t chatwidth_cons_t[] = {{64, "MIN"}, {150, "MAX"}, {0, NULL}};
+consvar_t cv_chatwidth = {"chatwidth", "150", CV_SAVE, chatwidth_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL};
+
+// chatheight
+static CV_PossibleValue_t chatheight_cons_t[] = {{6, "MIN"}, {22, "MAX"}, {0, NULL}};
+consvar_t cv_chatheight= {"chatheight", "8", CV_SAVE, chatheight_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL};
+
+// chat notifications (do you want to hear beeps? I'd understand if you didn't.)
+consvar_t cv_chatnotifications= {"chatnotifications", "On", CV_SAVE, CV_OnOff, NULL, 0, NULL, NULL, 0, 0, NULL};
+
+// chat spam protection (why would you want to disable that???)
+consvar_t cv_chatspamprotection= {"chatspamprotection", "On", CV_SAVE, CV_OnOff, NULL, 0, NULL, NULL, 0, 0, NULL};
+
+// old shit console chat. (mostly exists for stuff like terminal, not because I cared if anyone liked the old chat.)
+consvar_t cv_consolechat= {"consolechat", "Off", CV_SAVE, CV_OnOff, NULL, 0, NULL, NULL, 0, 0, NULL};
+
 consvar_t cv_crosshair = {"crosshair", "Cross", CV_SAVE, crosshair_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL};
 consvar_t cv_crosshair2 = {"crosshair2", "Cross", CV_SAVE, crosshair_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL};
 consvar_t cv_crosshair3 = {"crosshair3", "Cross", CV_SAVE, crosshair_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL};
diff --git a/src/g_game.h b/src/g_game.h
index f59641fb..fa188824 100644
--- a/src/g_game.h
+++ b/src/g_game.h
@@ -54,6 +54,7 @@ extern tic_t timeinmap; // Ticker for time spent in level (used for levelcard di
 extern INT16 rw_maximums[NUM_WEAPONS];
 
 // used in game menu
+extern consvar_t cv_chatwidth, cv_chatnotifications, cv_chatheight, cv_chattime, cv_consolechat, cv_chatspamprotection, cv_compactscoreboard;
 extern consvar_t cv_crosshair, cv_crosshair2, cv_crosshair3, cv_crosshair4;
 extern consvar_t cv_invertmouse, cv_alwaysfreelook, cv_mousemove;
 extern consvar_t cv_turnaxis,cv_moveaxis,cv_brakeaxis,cv_aimaxis,cv_lookaxis,cv_fireaxis,cv_driftaxis;
diff --git a/src/hardware/hw_draw.c b/src/hardware/hw_draw.c
index c142e74c..99cc3613 100644
--- a/src/hardware/hw_draw.c
+++ b/src/hardware/hw_draw.c
@@ -900,6 +900,111 @@ void HWR_DrawFill(INT32 x, INT32 y, INT32 w, INT32 h, INT32 color)
 		PF_Modulated|PF_NoTexture|PF_NoDepthTest);
 }
 
+// -------------------+
+// HWR_DrawConsoleFill     : draw flat coloured transparent rectangle because that's cool, and hw sucks less than sw for that.
+// -------------------+
+void HWR_DrawConsoleFill(INT32 x, INT32 y, INT32 w, INT32 h, UINT32 color, INT32 options)
+{
+	FOutVector v[4];
+	FSurfaceInfo Surf;
+	float fx, fy, fw, fh;
+
+	if (w < 0 || h < 0)
+		return; // consistency w/ software
+
+//  3--2
+//  | /|
+//  |/ |
+//  0--1
+
+	fx = (float)x;
+	fy = (float)y;
+	fw = (float)w;
+	fh = (float)h;
+
+	if (!(options & V_NOSCALESTART))
+	{
+		float dupx = (float)vid.dupx, dupy = (float)vid.dupy;
+
+		if (x == 0 && y == 0 && w == BASEVIDWIDTH && h == BASEVIDHEIGHT)
+		{
+			RGBA_t rgbaColour = V_GetColor(color);
+			FRGBAFloat clearColour;
+			clearColour.red = (float)rgbaColour.s.red / 255;
+			clearColour.green = (float)rgbaColour.s.green / 255;
+			clearColour.blue = (float)rgbaColour.s.blue / 255;
+			clearColour.alpha = 1;
+			HWD.pfnClearBuffer(true, false, &clearColour);
+			return;
+		}
+
+		fx *= dupx;
+		fy *= dupy;
+		fw *= dupx;
+		fh *= dupy;
+
+		if (vid.width != BASEVIDWIDTH * vid.dupx)
+		{
+			if (options & V_SNAPTORIGHT)
+				fx += ((float)vid.width - ((float)BASEVIDWIDTH * dupx));
+			else if (!(options & V_SNAPTOLEFT))
+				fx += ((float)vid.width - ((float)BASEVIDWIDTH * dupx)) / 2;
+		}
+		if (vid.height != BASEVIDHEIGHT * dupy)
+		{
+			// same thing here
+			if (options & V_SNAPTOBOTTOM)
+				fy += ((float)vid.height - ((float)BASEVIDHEIGHT * dupy));
+			else if (!(options & V_SNAPTOTOP))
+				fy += ((float)vid.height - ((float)BASEVIDHEIGHT * dupy)) / 2;
+		}
+	}
+
+	if (fx >= vid.width || fy >= vid.height)
+		return;
+	if (fx < 0)
+	{
+		fw += fx;
+		fx = 0;
+	}
+	if (fy < 0)
+	{
+		fh += fy;
+		fy = 0;
+	}
+
+	if (fw <= 0 || fh <= 0)
+		return;
+	if (fx + fw > vid.width)
+		fw = (float)vid.width - fx;
+	if (fy + fh > vid.height)
+		fh = (float)vid.height - fy;
+
+	fx = -1 + fx / (vid.width / 2);
+	fy = 1 - fy / (vid.height / 2);
+	fw = fw / (vid.width / 2);
+	fh = fh / (vid.height / 2);
+
+	v[0].x = v[3].x = fx;
+	v[2].x = v[1].x = fx + fw;
+	v[0].y = v[1].y = fy;
+	v[2].y = v[3].y = fy - fh;
+
+	//Hurdler: do we still use this argb color? if not, we should remove it
+	v[0].argb = v[1].argb = v[2].argb = v[3].argb = 0xff00ff00; //;
+	v[0].z = v[1].z = v[2].z = v[3].z = 1.0f;
+
+	v[0].sow = v[3].sow = 0.0f;
+	v[2].sow = v[1].sow = 1.0f;
+	v[0].tow = v[1].tow = 0.0f;
+	v[2].tow = v[3].tow = 1.0f;
+	
+	Surf.FlatColor.rgba = UINT2RGBA(color);
+	Surf.FlatColor.s.alpha = 0x80;
+
+	HWD.pfnDrawPolygon(&Surf, v, 4, PF_NoTexture|PF_Modulated|PF_Translucent|PF_NoDepthTest);
+}
+
 #ifdef HAVE_PNG
 
 #ifndef _MSC_VER
diff --git a/src/hardware/hw_main.h b/src/hardware/hw_main.h
index fce17062..f0efc1c5 100644
--- a/src/hardware/hw_main.h
+++ b/src/hardware/hw_main.h
@@ -52,6 +52,7 @@ void HWR_CreatePlanePolygons(INT32 bspnum);
 void HWR_CreateStaticLightmaps(INT32 bspnum);
 void HWR_PrepLevelCache(size_t pnumtextures);
 void HWR_DrawFill(INT32 x, INT32 y, INT32 w, INT32 h, INT32 color);
+void HWR_DrawConsoleFill(INT32 x, INT32 y, INT32 w, INT32 h, UINT32 color, INT32 options);	// Lat: separate flags from color since color needs to be an uint to work right.
 void HWR_DrawPic(INT32 x,INT32 y,lumpnum_t lumpnum);
 
 void HWR_AddCommands(void);
diff --git a/src/hu_stuff.c b/src/hu_stuff.c
index c3c04bed..135cedfc 100644
--- a/src/hu_stuff.c
+++ b/src/hu_stuff.c
@@ -76,6 +76,7 @@ patch_t *cred_font[CRED_FONTSIZE];
 static player_t *plr;
 boolean chat_on; // entering a chat message?
 static char w_chat[HU_MAXMSGLEN];
+static INT32 c_input = 0;	// let's try to make the chat input less shitty.
 static boolean headsupactive = false;
 boolean hu_showscores; // draw rankings
 static char hu_tick;
@@ -336,6 +337,74 @@ void HU_Start(void)
 //======================================================================
 
 #ifndef NONET
+
+// EVERY CHANGE IN THIS SCRIPT IS LOL XD! BY VINCYTM
+
+static UINT32 chat_nummsg_log = 0;
+static UINT32 chat_nummsg_min = 0;
+static UINT32 chat_scroll = 0;		
+static tic_t chat_scrolltime = 0;
+
+static INT32 chat_maxscroll = 0;	// how far can we scroll? 
+
+//static chatmsg_t chat_mini[CHAT_BUFSIZE];	// Display the last few messages sent.
+//static chatmsg_t chat_log[CHAT_BUFSIZE];	// Keep every message sent to us in memory so we can scroll n shit, it's cool.
+
+static char chat_log[CHAT_BUFSIZE][255];	// hold the last 48 or so messages in that log.
+static char chat_mini[8][255];			// display up to 8 messages that will fade away / get overwritten
+static tic_t chat_timers[8];
+
+static boolean chat_scrollmedown = false;	// force instant scroll down on the chat log. Happens when you open it / send a message.
+
+// remove text from minichat table
+
+static INT16 addy = 0;	// use this to make the messages scroll smoothly when one fades away 
+
+static void HU_removeChatText_Mini(void)
+{
+    // MPC: Don't create new arrays, just iterate through an existing one
+	int i;
+    for(i=0;i<chat_nummsg_min-1;i++) {
+        strcpy(chat_mini[i], chat_mini[i+1]);
+        chat_timers[i] = chat_timers[i+1];
+    }
+	chat_nummsg_min--;	// lost 1 msg.
+
+	// use addy and make shit slide smoothly af.
+	addy += (vid.width < 640) ? 8 : 6;
+
+}
+
+// same but w the log. TODO: optimize this and maybe merge in a single func? im bad at C.
+static void HU_removeChatText_Log(void)
+{
+	// MPC: Don't create new arrays, just iterate through an existing one
+	int i;
+    for(i=0;i<chat_nummsg_log-1;i++) {
+        strcpy(chat_log[i], chat_log[i+1]);
+    }
+    chat_nummsg_log--;	// lost 1 msg.
+}
+	
+void HU_AddChatText(const char *text)
+{
+	
+	// TODO: check if we're oversaturating the log (we can only log CHAT_BUFSIZE messages.)
+	
+	if (chat_nummsg_log >= CHAT_BUFSIZE)
+		HU_removeChatText_Log();
+	
+	strcpy(chat_log[chat_nummsg_log], text);
+	chat_nummsg_log++;
+	
+	if (chat_nummsg_min >= 8)
+		HU_removeChatText_Mini();
+	
+	strcpy(chat_mini[chat_nummsg_min], text);
+	chat_timers[chat_nummsg_min] = TICRATE*cv_chattime.value;
+	chat_nummsg_min++;
+}
+
 /** Runs a say command, sending an ::XD_SAY message.
   * A say command consists of a signed 8-bit integer for the target, an
   * unsigned 8-bit flag variable, and then the message itself.
@@ -364,14 +433,14 @@ static void DoSayCommand(SINT8 target, size_t usedargs, UINT8 flags)
 	numwords = COM_Argc() - usedargs;
 	I_Assert(numwords > 0);
 
-	if (cv_mute.value && !(server || IsPlayerAdmin(consoleplayer)))
+	if (cv_mute.value && !(server || IsPlayerAdmin(consoleplayer)))	// TODO: Per Player mute.
 	{
-		CONS_Alert(CONS_NOTICE, M_GetText("The chat is muted. You can't say anything at the moment.\n"));
+		HU_AddChatText(va("%s>ERROR: The chat is muted. You can't say anything.", "\x85"));
 		return;
 	}
 
 	// Only servers/admins can CSAY.
-	if(!server && IsPlayerAdmin(consoleplayer))
+	if(!server && !(IsPlayerAdmin(consoleplayer)))
 		flags &= ~HU_CSAY;
 
 	// We handle HU_SERVER_SAY, not the caller.
@@ -389,6 +458,52 @@ static void DoSayCommand(SINT8 target, size_t usedargs, UINT8 flags)
 			strlcat(msg, " ", msgspace);
 		strlcat(msg, COM_Argv(ix + usedargs), msgspace);
 	}
+	
+	if (strlen(msg) > 4 && strnicmp(msg, "/pm", 3) == 0)	// used /pm
+	{
+		// what we're gonna do now is check if the node exists
+		// with that logic, characters 4 and 5 are our numbers:
+		int spc = 1;	// used if nodenum[1] is a space.
+		char *nodenum = (char*) malloc(3);
+		strncpy(nodenum, msg+3, 5);
+		// check for undesirable characters in our "number"
+		if 	(((nodenum[0] < '0') || (nodenum[0] > '9')) || ((nodenum[1] < '0') || (nodenum[1] > '9')))
+		{	
+			// check if nodenum[1] is a space
+			if (nodenum[1] == ' ')
+				spc = 0;
+				// let it slide
+			else
+			{	
+				HU_AddChatText("\x82NOTICE: \x80Invalid command format. Correct format is \'/pm<node> \'.");
+				return;
+			}	
+		}
+		// I'm very bad at C, I swear I am, additional checks eww!
+			if (spc != 0)
+			{	
+				if (msg[5] != ' ')
+				{
+					HU_AddChatText("\x82NOTICE: \x80Invalid command format. Correct format is \'/pm<node> \'.");
+					return;
+				}
+			}
+		
+		target = atoi((const char*) nodenum);	// turn that into a number
+		//CONS_Printf("%d\n", target);
+		
+		// check for target player, if it doesn't exist then we can't send the message!
+		if (playeringame[target])	// player exists
+			target++;				// even though playernums are from 0 to 31, target is 1 to 32, so up that by 1 to have it work!
+		else
+		{
+			HU_AddChatText(va("\x82NOTICE: \x80Player %d does not exist.", target));	// same
+			return;
+		}
+		buf[0] = target;
+		const char *newmsg = msg+5+spc;
+		memcpy(msg, newmsg, 255);
+	}
 
 	SendNetXCmd(XD_SAY, buf, strlen(msg) + 1 + msg-buf);
 }
@@ -473,6 +588,7 @@ static void Command_CSay_f(void)
 
 	DoSayCommand(0, 1, HU_CSAY);
 }
+static tic_t stop_spamming_you_cunt[MAXPLAYERS];
 
 /** Receives a message, processing an ::XD_SAY command.
   * \sa DoSayCommand
@@ -486,7 +602,7 @@ static void Got_Saycmd(UINT8 **p, INT32 playernum)
 	char *msg;
 	boolean action = false;
 	char *ptr;
-
+	
 	CONS_Debug(DBG_NETPLAY,"Received SAY cmd from Player %d (%s)\n", playernum+1, player_names[playernum]);
 
 	target = READSINT8(*p);
@@ -494,7 +610,7 @@ static void Got_Saycmd(UINT8 **p, INT32 playernum)
 	msg = (char *)*p;
 	SKIPSTRING(*p);
 
-	if ((cv_mute.value || flags & (HU_CSAY|HU_SERVER_SAY)) && playernum != serverplayer && !IsPlayerAdmin(playernum))
+	if ((cv_mute.value || flags & (HU_CSAY|HU_SERVER_SAY)) && playernum != serverplayer && !(IsPlayerAdmin(playernum)))
 	{
 		CONS_Alert(CONS_WARNING, cv_mute.value ?
 			M_GetText("Illegal say command received from %s while muted\n") : M_GetText("Illegal csay command received from non-admin %s\n"),
@@ -531,12 +647,31 @@ static void Got_Saycmd(UINT8 **p, INT32 playernum)
 			}
 		}
 	}
-
+	
+	int spam_eatmsg = 0;
+	
+	// before we do anything, let's verify the guy isn't spamming, get this easier on us.
+	
+	//if (stop_spamming_you_cunt[playernum] != 0 && cv_chatspamprotection.value && !(flags & HU_CSAY))
+	if (stop_spamming_you_cunt[playernum] != 0 && consoleplayer != playernum && cv_chatspamprotection.value && !(flags & HU_CSAY))
+	{	
+		CONS_Debug(DBG_NETPLAY,"Received SAY cmd too quickly from Player %d (%s), assuming as spam and blocking message.\n", playernum+1, player_names[playernum]);
+		stop_spamming_you_cunt[playernum] = 4;
+		spam_eatmsg = 1;
+	}
+	else
+		stop_spamming_you_cunt[playernum] = 4;	// you can hold off for 4 tics, can you?
+	
+	// run the lua hook even if we were supposed to eat the msg, netgame consistency goes first.
+	
 #ifdef HAVE_BLUA
-	if (LUAh_PlayerMsg(playernum, target, flags, msg))
+	if (LUAh_PlayerMsg(playernum, target, flags, msg, spam_eatmsg))
 		return;
 #endif
-
+	
+	if (spam_eatmsg)
+		return;	// don't proceed if we were supposed to eat the message.
+	
 	// If it's a CSAY, just CECHO and be done with it.
 	if (flags & HU_CSAY)
 	{
@@ -576,18 +711,23 @@ static void Got_Saycmd(UINT8 **p, INT32 playernum)
 	|| target == 0 // To everyone
 	|| consoleplayer == target-1) // To you
 	{
-		const char *cstart = "", *cend = "", *adminchar = "~", *remotechar = "@", *fmt;
+		const char *prefix = "", *cstart = "", *cend = "", *adminchar = "\x82~\x83", *remotechar = "\x82@\x83", *fmt, *fmt2;
 		char *tempchar = NULL;
-
+		
 		// In CTF and team match, color the player's name.
 		if (G_GametypeHasTeams())
 		{
-			cend = "\x80";
-			if (players[playernum].ctfteam == 1) // red
-				cstart = "\x85";
+			cend = "";
+			if (players[playernum].ctfteam == 1) // red	
+				cstart = "\x85";		
 			else if (players[playernum].ctfteam == 2) // blue
 				cstart = "\x84";
+			
 		}
+		
+		// player is a spectator?
+		if (players[playernum].spectator)
+				cstart = "\x86";	// grey name
 
 		// Give admins and remote admins their symbols.
 		if (playernum == serverplayer)
@@ -596,11 +736,11 @@ static void Got_Saycmd(UINT8 **p, INT32 playernum)
 			tempchar = (char *)Z_Calloc(strlen(cstart) + strlen(remotechar) + 1, PU_STATIC, NULL);
 		if (tempchar)
 		{
-			strcat(tempchar, cstart);
 			if (playernum == serverplayer)
 				strcat(tempchar, adminchar);
 			else
 				strcat(tempchar, remotechar);
+			strcat(tempchar, cstart);
 			cstart = tempchar;
 		}
 
@@ -609,21 +749,61 @@ static void Got_Saycmd(UINT8 **p, INT32 playernum)
 		// name, color end, and the message itself.
 		// '\4' makes the message yellow and beeps; '\3' just beeps.
 		if (action)
-			fmt = "\4* %s%s%s \x82%s\n";
+		{	
+			fmt = "\3* %s%s%s%s \x82%s\n";	// don't make /me yellow, yellow will be for mentions and PMs!
+			fmt2 = "* %s%s%s%s \x82%s";
+		}	
 		else if (target == 0) // To everyone
-			fmt = "\3<%s%s%s> %s\n";
+		{
+			fmt = "\3%s\x83<%s%s%s\x83>\x80 %s\n";
+			fmt2 = "%s\x83<%s%s%s\x83>\x80 %s";
+		}	
 		else if (target-1 == consoleplayer) // To you
-			fmt = "\3*%s%s%s* %s\n";
+		{
+			prefix = "\x82[PM]";
+			cstart = "\x82";
+			fmt = "\4%s<%s%s>%s\x80 %s\n";	// make this yellow, however.
+			fmt2 = "%s<%s%s>%s\x80 %s";
+		}
 		else if (target > 0) // By you, to another player
 		{
 			// Use target's name.
 			dispname = player_names[target-1];
-			fmt = "\3->*%s%s%s* %s\n";
+			/*fmt = "\3\x82[TO]\x80%s%s%s* %s\n";
+			fmt2 = "\x82[TO]\x80%s%s%s* %s";*/
+			prefix = "\x82[TO]";
+			cstart = "\x82";
+			fmt = "\4%s<%s%s>%s\x80 %s\n";	// make this yellow, however.
+			fmt2 = "%s<%s%s>%s\x80 %s";
+			
 		}
 		else // To your team
-			fmt = "\3>>%s%s%s<< (team) %s\n";
-
-		CONS_Printf(fmt, cstart, dispname, cend, msg);
+		{
+			if (players[playernum].ctfteam == 1) // red	
+				prefix = "\x85[TEAM]";		
+			else if (players[playernum].ctfteam == 2) // blue
+				prefix = "\x84[TEAM]";
+			else
+				prefix = "\x83";	// makes sure this doesn't implode if you sayteam on non-team gamemodes
+				
+			fmt = "\3%s<%s%s>\x80%s %s\n";
+			fmt2 = "%s<%s%s>\x80%s %s";
+		
+		}		
+		
+		if (cv_consolechat.value)
+		{	
+			CONS_Printf(fmt, prefix, cstart, dispname, cend, msg);	
+			HU_AddChatText(va(fmt2, prefix, cstart, dispname, cend, msg));	// add it reguardless, in case we decide to change our mind about our chat type.
+		}	
+		else
+		{	
+			HU_AddChatText(va(fmt2, prefix, cstart, dispname, cend, msg));
+			CON_LogMessage(va(fmt, prefix, cstart, dispname, cend, msg));	// save to log.txt
+			if (cv_chatnotifications.value)
+				S_StartSound(NULL, sfx_radio);
+		}	
+		
 		if (tempchar)
 			Z_Free(tempchar);
 	}
@@ -650,19 +830,48 @@ static inline boolean HU_keyInChatString(char *s, char ch)
 		l = strlen(s);
 		if (l < HU_MAXMSGLEN - 1)
 		{
-			s[l++] = ch;
-			s[l]=0;
+			if (c_input >= strlen(s))	// don't do anything complicated
+			{
+				s[l++] = ch;
+				s[l]=0;
+			}	
+			else
+			{	
+				
+				// move everything past c_input for new characters:
+				INT32 m = HU_MAXMSGLEN-1;
+				for (;(m>=c_input);m--)
+				{
+					if (s[m])
+						s[m+1] = (s[m]);
+				}
+				s[c_input] = ch;		// and replace this.
+			}
+			c_input++;
 			return true;
 		}
 		return false;
 	}
 	else if (ch == KEY_BACKSPACE)
 	{
-		l = strlen(s);
-		if (l)
-			s[--l] = 0;
-		else
+		if (c_input <= 0)
 			return false;
+		size_t i = c_input;
+		if (!s[i-1])
+			return false;
+		
+		if (i >= strlen(s)-1)
+		{	
+			s[strlen(s)-1] = 0;
+			c_input--;
+			return false;
+		}	
+		
+		for (; (i < HU_MAXMSGLEN); i++)
+		{	
+			s[i-1] = s[i];
+		}
+		c_input--;
 	}
 	else if (ch != KEY_ENTER)
 		return false; // did not eat key
@@ -686,29 +895,9 @@ void HU_Ticker(void)
 		hu_showscores = false;
 }
 
-#define QUEUESIZE 256
 
 static boolean teamtalk = false;
-static char chatchars[QUEUESIZE];
-static INT32 head = 0, tail = 0;
-
-//
-// HU_dequeueChatChar
-//
-char HU_dequeueChatChar(void)
-{
-	char c;
-
-	if (head != tail)
-	{
-		c = chatchars[tail];
-		tail = (tail + 1) & (QUEUESIZE-1);
-	}
-	else
-		c = 0;
-
-	return c;
-}
+// WHY DO YOU OVERCOMPLICATE EVERYTHING?????????
 
 //
 //
@@ -719,79 +908,130 @@ static void HU_queueChatChar(char c)
 	{
 		char buf[2+256];
 		size_t ci = 2;
-
+		char *msg = &buf[2];
 		do {
-			c = HU_dequeueChatChar();
+			c = w_chat[-2+ci++];
 			if (!c || (c >= ' ' && !(c & 0x80))) // copy printable characters and terminating '\0' only.
-				buf[ci++]=c;
+				buf[ci-1]=c;
 		} while (c);
-
+		size_t i = 0;
+		for (;(i<HU_MAXMSGLEN);i++)
+			w_chat[i] = 0;	// reset this.
+		
+		c_input = 0;
+		
 		// last minute mute check
 		if (cv_mute.value && !(server || IsPlayerAdmin(consoleplayer)))
 		{
-			CONS_Alert(CONS_NOTICE, M_GetText("The chat is muted. You can't say anything at the moment.\n"));
+			HU_AddChatText(va("%s>ERROR: The chat is muted. You can't say anything.", "\x85"));
 			return;
 		}
-
+		
+		INT32 target = 0;
+		
+		if (strlen(msg) > 4 && strnicmp(msg, "/pm", 3) == 0)	// used /pm
+		{
+			// what we're gonna do now is check if the node exists
+			// with that logic, characters 4 and 5 are our numbers:
+			
+			// teamtalk can't send PMs, just don't send it, else everyone would be able to see it, and no one wants to see your sex RP sicko.
+			if (teamtalk)
+			{
+				HU_AddChatText(va("%sCannot send sayto in Say-Team.", "\x85"));
+				return;
+			}	
+			
+			int spc = 1;	// used if nodenum[1] is a space.
+			char *nodenum = (char*) malloc(3);
+			strncpy(nodenum, msg+3, 5);
+			// check for undesirable characters in our "number"
+			if 	(((nodenum[0] < '0') || (nodenum[0] > '9')) || ((nodenum[1] < '0') || (nodenum[1] > '9')))
+			{	
+				// check if nodenum[1] is a space
+				if (nodenum[1] == ' ')
+					spc = 0;
+					// let it slide
+				else
+				{	
+					HU_AddChatText("\x82NOTICE: \x80Invalid command format. Correct format is \'/pm<node> \'.");
+					return;
+				}	
+			}
+			// I'm very bad at C, I swear I am, additional checks eww!
+			if (spc != 0)
+			{	
+				if (msg[5] != ' ')
+				{
+					HU_AddChatText("\x82NOTICE: \x80Invalid command format. Correct format is \'/pm<node> \'.");
+					return;
+				}
+			}
+			
+			target = atoi((const char*) nodenum);	// turn that into a number
+			//CONS_Printf("%d\n", target);
+		
+			// check for target player, if it doesn't exist then we can't send the message!
+			if (playeringame[target])	// player exists
+				target++;				// even though playernums are from 0 to 31, target is 1 to 32, so up that by 1 to have it work!
+			else
+			{
+				HU_AddChatText(va("\x82NOTICE: \x80Player %d does not exist.", target));	// same
+				return;
+			}
+			// we need to get rid of the /pm<node>
+			const char *newmsg = msg+5+spc;
+			memcpy(msg, newmsg, 255);
+		}	
 		if (ci > 3) // don't send target+flags+empty message.
 		{
 			if (teamtalk)
 				buf[0] = -1; // target
 			else
-				buf[0] = 0; // target
+				buf[0] = target;
+			
 			buf[1] = 0; // flags
 			SendNetXCmd(XD_SAY, buf, 2 + strlen(&buf[2]) + 1);
 		}
 		return;
 	}
-
-	if (((head + 1) & (QUEUESIZE-1)) == tail)
-		CONS_Printf(M_GetText("[Message unsent]\n")); // message not sent
-	else
-	{
-		if (c == KEY_BACKSPACE)
-		{
-			if (tail != head)
-				head = (head - 1) & (QUEUESIZE-1);
-		}
-		else
-		{
-			chatchars[head] = c;
-			head = (head + 1) & (QUEUESIZE-1);
-		}
-	}
 }
 
 void HU_clearChatChars(void)
 {
-	while (tail != head)
-		HU_queueChatChar(KEY_BACKSPACE);
+	size_t i = 0;
+	for (;i<HU_MAXMSGLEN;i++)
+		w_chat[i] = 0;	// reset this.
 	chat_on = false;
+	c_input = 0;
 }
 
+static boolean justscrolleddown;
+static boolean justscrolledup;
+
 //
 // Returns true if key eaten
 //
 boolean HU_Responder(event_t *ev)
 {
-	UINT8 c;
-
+	UINT8 c=0;
+		
 	if (ev->type != ev_keydown)
 		return false;
 
 	// only KeyDown events now...
-
+	
 	if (!chat_on)
 	{
 		// enter chat mode
 		if ((ev->data1 == gamecontrol[gc_talkkey][0] || ev->data1 == gamecontrol[gc_talkkey][1])
-			&& netgame && (!cv_mute.value || server || IsPlayerAdmin(consoleplayer)))
+			&& netgame && (!cv_mute.value || server || (IsPlayerAdmin(consoleplayer))))
 		{
 			if (cv_mute.value && !(server || IsPlayerAdmin(consoleplayer)))
 				return false;
 			chat_on = true;
 			w_chat[0] = 0;
 			teamtalk = false;
+			chat_scrollmedown = true;
 			return true;
 		}
 		if ((ev->data1 == gamecontrol[gc_teamkey][0] || ev->data1 == gamecontrol[gc_teamkey][1])
@@ -802,11 +1042,13 @@ boolean HU_Responder(event_t *ev)
 			chat_on = true;
 			w_chat[0] = 0;
 			teamtalk = true;
+			chat_scrollmedown = true;
 			return true;
 		}
 	}
 	else // if chat_on
 	{
+		
 		// Ignore modifier keys
 		// Note that we do this here so users can still set
 		// their chat keys to one of these, if they so desire.
@@ -816,18 +1058,95 @@ boolean HU_Responder(event_t *ev)
 			return true;
 
 		c = (UINT8)ev->data1;
-
-		// use console translations
-		if (shiftdown)
+		
+		// capslock
+		if (c && c == KEY_CAPSLOCK)	// it's a toggle.
+		{	
+			if (capslock)
+				capslock = false;
+			else	
+				capslock = true;
+			return true;
+		}	
+		
+		// use console translations		
+		if (shiftdown ^ capslock)
 			c = shiftxform[c];
-
+		
+		// TODO: make chat behave like the console, so that we can go back and edit stuff when we fuck up.
+		
+		// pasting. pasting is cool. chat is a bit limited, though :(
+		if ((c == 'v' || c == 'V') && ctrldown)
+		{
+			const char *paste = I_ClipboardPaste();
+			
+			// create a dummy string real quickly
+			
+			if (paste == NULL)
+				return true;
+			
+			size_t chatlen = strlen(w_chat);
+			size_t pastelen = strlen(paste);
+			if (chatlen+pastelen > HU_MAXMSGLEN)
+				return true; // we can't paste this!!
+			
+			if (c_input >= strlen(w_chat))	// add it at the end of the string.
+			{
+				memcpy(&w_chat[chatlen], paste, pastelen);	// copy all of that.
+				c_input += pastelen;
+				/*size_t i = 0;
+				for (;i<pastelen;i++)
+				{
+					HU_queueChatChar(paste[i]);				// queue it so that it's actually sent. (this chat write thing is REALLY messy.)
+				}*/
+				return true;
+			}
+			else	// otherwise, we need to shift everything and make space, etc etc
+			{	
+				size_t i = HU_MAXMSGLEN-1;
+				for (; i>=c_input;i--)
+				{
+					if (w_chat[i])
+						w_chat[i+pastelen] = w_chat[i];
+					
+				}
+				memcpy(&w_chat[c_input], paste, pastelen);	// copy all of that.
+				c_input += pastelen;
+				return true;
+			}
+		}
+		
 		if (HU_keyInChatString(w_chat,c))
+		{	
 			HU_queueChatChar(c);
+		}	
 		if (c == KEY_ENTER)
+		{	
 			chat_on = false;
+			c_input = 0;			// reset input cursor
+			chat_scrollmedown = true; // you hit enter, so you might wanna autoscroll to see what you just sent. :)
+		}	
 		else if (c == KEY_ESCAPE)
+		{	
 			chat_on = false;
-
+			c_input = 0;			// reset input cursor
+		}	
+		else if ((c == KEY_UPARROW || c == KEY_MOUSEWHEELUP) && chat_scroll > 0)	// CHAT SCROLLING YAYS!
+		{
+			chat_scroll--;
+			justscrolledup = true;
+			chat_scrolltime = 4;
+		}	
+		else if ((c == KEY_DOWNARROW || c == KEY_MOUSEWHEELDOWN) && chat_scroll < chat_maxscroll && chat_maxscroll > 0)
+		{	
+			chat_scroll++;
+			justscrolleddown = true;
+			chat_scrolltime = 4;
+		}
+		else if (c == KEY_LEFTARROW && c_input != 0)	// i said go back
+			c_input--;
+		else if (c == KEY_RIGHTARROW && c_input < strlen(w_chat))
+			c_input++;	
 		return true;
 	}
 	return false;
@@ -837,12 +1156,412 @@ boolean HU_Responder(event_t *ev)
 //                         HEADS UP DRAWING
 //======================================================================
 
+// Gets string colormap, used for 0x80 color codes
+//
+static UINT8 *CHAT_GetStringColormap(INT32 colorflags)	// pasted from video.c, sorry for the mess.
+{
+	switch ((colorflags & V_CHARCOLORMASK) >> V_CHARCOLORSHIFT)
+	{
+	case 1: // 0x81, purple
+		return purplemap;
+	case 2: // 0x82, yellow
+		return yellowmap;
+	case 3: // 0x83, lgreen
+		return lgreenmap;
+	case 4: // 0x84, blue
+		return bluemap;
+	case 5: // 0x85, red
+		return redmap;
+	case 6: // 0x86, gray
+		return graymap;
+	case 7: // 0x87, orange
+		return orangemap;
+	default: // reset
+		return NULL;
+	}
+}
+
+// Precompile a wordwrapped string to any given width.
+// This is a muuuch better method than V_WORDWRAP.
+// again stolen and modified a bit from video.c, don't mind me, will need to rearrange this one day.
+// this one is simplified for the chat drawer.
+char *CHAT_WordWrap(INT32 x, INT32 w, INT32 option, const char *string)
+{
+	int c;
+	size_t chw, i, lastusablespace = 0;
+	size_t slen;
+	char *newstring = Z_StrDup(string);
+	INT32 spacewidth = (vid.width < 640) ? 8 : 4, charwidth = (vid.width < 640) ? 8 : 4;
+
+	slen = strlen(string);
+	x = 0;
+
+	for (i = 0; i < slen; ++i)
+	{
+		c = newstring[i];
+		if ((UINT8)c >= 0x80 && (UINT8)c <= 0x89) //color parsing! -Inuyasha 2.16.09
+			continue;
+
+		if (c == '\n')
+		{
+			x = 0;
+			lastusablespace = 0;
+			continue;
+		}
+
+		if (!(option & V_ALLOWLOWERCASE))
+			c = toupper(c);
+		c -= HU_FONTSTART;
+
+		if (c < 0 || c >= HU_FONTSIZE || !hu_font[c])
+		{
+			chw = spacewidth;
+			lastusablespace = i;
+		}
+		else
+			chw = charwidth;
+
+		x += chw;
+
+		if (lastusablespace != 0 && x > w)
+		{
+			//CONS_Printf("Wrap at index %d\n", i);
+			newstring[lastusablespace] = '\n';
+			i = lastusablespace+1;	
+			lastusablespace = 0;
+			x = 0;
+		}
+	}
+	return newstring;
+}
+
+INT16 chatx = 160, chaty = 16;	// let's use this as our coordinates, shh
+
+// chat stuff by VincyTM LOL XD!
+
+// HU_DrawMiniChat
+
+static void HU_drawMiniChat(void)
+{
+	INT32 charwidth = (vid.width < 640) ? 8 : 4, charheight = (vid.width < 640) ? 8 : 6;
+	INT32 x = chatx+2, y = chaty+2, dx = 0, dy = 0;
+	size_t i = 0;
+	
+	for (i=0; i<chat_nummsg_min; i++)	// iterate through our hot messages
+	{
+		
+		INT32 clrflag = 0;
+		
+		INT32 timer = ((cv_chattime.value*TICRATE)-chat_timers[i]) - cv_chattime.value*TICRATE+9;	// see below...		
+		INT32 transflag = (timer >= 0 && timer <= 9) ? (timer*V_10TRANS) : 0;	// you can make bad jokes out of this one.
+		size_t j = 0;
+		const char *msg = CHAT_WordWrap(x, cv_chatwidth.value-charwidth, V_SNAPTOTOP|V_SNAPTOLEFT|V_ALLOWLOWERCASE, chat_mini[i]);	// get the current message, and word wrap it.
+		while(msg[j])	// iterate through msg
+		{	
+			if (msg[j] < HU_FONTSTART)	// don't draw
+			{			
+				if (msg[j] == '\n')	// get back down.
+				{
+					++j;
+					dy += charheight;
+					dx = 0;
+					continue;
+				}
+				else if (msg[j] & 0x80) // stolen from video.c, nice.
+				{
+					clrflag = ((msg[j] & 0x7f) << V_CHARCOLORSHIFT) & V_CHARCOLORMASK;
+					++j;
+					continue;
+				}
+				
+				++j;	
+			}
+			else
+			{
+				UINT8 *colormap = CHAT_GetStringColormap(clrflag);
+				V_DrawChatCharacter(x + dx + 2, y+dy+addy, msg[j++] |V_SNAPTOTOP|V_SNAPTORIGHT|transflag, !cv_allcaps.value, colormap);
+			}
+			
+			dx += charwidth;
+			if (dx >= cv_chatwidth.value)
+			{
+				dx = 0;
+				dy += charheight;
+			}
+		}
+		dy += charheight;
+		dx = 0;
+	}
+	
+	// decrement addy and make that shit smooth:
+	addy /= 2;
+	
+}
+
+// HU_DrawUpArrow
+// You see, we don't have arrow graphics in 2.1 and I'm too lazy to include a 2 bytes file for it.
+
+static void HU_DrawUpArrow(INT32 x, INT32 y, INT32 options)
+{
+	// Ok I'm super lazy so let's make this as the worst draw function:
+	V_DrawFill(x+2, y, 1, 1, 103|options);
+	V_DrawFill(x+1, y+1, 3, 1, 103|options);
+	V_DrawFill(x, y+2, 5, 1, 103|options);	// that's the yellow part, I swear
+	
+	V_DrawFill(x+3, y, 1, 1, 26|options);
+	V_DrawFill(x+4, y+1, 1, 1, 26|options);
+	V_DrawFill(x+5, y+2, 1, 1, 26|options);
+	V_DrawFill(x, y+3, 6, 1, 26|options);	// that's the black part. no racism intended. i swear.
+}
+
+// HU_DrawDownArrow
+// Should we talk about anime waifus to pass the time? This feels retarded.
+
+static void HU_DrawDownArrow(INT32 x, INT32 y, INT32 options)
+{
+	// Ok I'm super lazy so let's make this as the worst draw function:
+	V_DrawFill(x, y, 6, 1, 26|options);
+	V_DrawFill(x, y+1, 5, 1, 26|options);
+	V_DrawFill(x+1, y+2, 3, 1, 26|options);
+	V_DrawFill(x+2, y+3, 1, 1, 26|options);	// that's the black part. no racism intended. i swear.
+	
+	V_DrawFill(x, y, 5, 1, 103|options);
+	V_DrawFill(x+1, y+1, 3, 1, 103|options);
+	V_DrawFill(x+2, y+2, 1, 1, 103|options);	// that's the yellow part, I swear
+}	
+
+// HU_DrawChatLog
+// TODO: fix dumb word wrapping issues
+
+static void HU_drawChatLog(void)
+{
+	
+	// before we do anything, make sure that our scroll position isn't "illegal";
+	if (chat_scroll > chat_maxscroll)
+		chat_scroll = chat_maxscroll;
+	
+	INT32 charwidth = (vid.width < 640) ? 8 : 4, charheight = (vid.width < 640) ? 8 : 6;
+	INT32 x = chatx+2, y = chaty+2-(chat_scroll*charheight), dx = 0, dy = 0;
+	size_t i = 0;
+	boolean atbottom = false;
+	
+	V_DrawFillConsoleMap(chatx, chaty, cv_chatwidth.value, cv_chatheight.value*charheight +2, 239|V_SNAPTOTOP|V_SNAPTORIGHT);	// INUT
+		
+	for (i=0; i<chat_nummsg_log; i++)	// iterate through our chatlog
+	{
+		INT32 clrflag = 0;
+		size_t j = 0;
+		const char *msg = CHAT_WordWrap(x, cv_chatwidth.value-charwidth, V_SNAPTOTOP|V_SNAPTOLEFT|V_ALLOWLOWERCASE, chat_log[i]);	// get the current message, and word wrap it.
+		while(msg[j])	// iterate through msg
+		{	
+			if (msg[j] < HU_FONTSTART)	// don't draw
+			{			
+				if (msg[j] == '\n')	// get back down.
+				{
+					++j;
+					dy += charheight;
+					dx = 0;
+					continue;
+				}
+				else if (msg[j] & 0x80) // stolen from video.c, nice.
+				{
+					clrflag = ((msg[j] & 0x7f) << V_CHARCOLORSHIFT) & V_CHARCOLORMASK;
+					++j;
+					continue;
+				}
+				
+				++j;	
+			}
+			else
+			{
+				if ((y+dy > chaty) && (y+dy < (chaty+cv_chatheight.value*charheight)))
+				{	
+					UINT8 *colormap = CHAT_GetStringColormap(clrflag);
+					V_DrawChatCharacter(x + dx + 2, y+dy, msg[j++] |V_SNAPTOTOP|V_SNAPTORIGHT, !cv_allcaps.value, colormap);
+				}
+				else
+					j++;	// don't forget to increment this or we'll get stuck in the limbo.
+			}
+			
+			dx += charwidth;
+			if (dx >= cv_chatwidth.value-charwidth-2 && i<chat_nummsg_log && msg[j] >= HU_FONTSTART) // end of message shouldn't count, nor should invisible characters!!!!
+			{
+				dx = 0;
+				dy += charheight;
+			}
+		}
+		dy += charheight;
+		dx = 0;
+	}
+	
+	if (((chat_scroll >= chat_maxscroll) || (chat_scrollmedown)) && !(justscrolleddown || justscrolledup || chat_scrolltime))	// was already at the bottom of the page before new maxscroll calculation and was NOT scrolling.
+	{
+		atbottom = true;	// we should scroll
+	}
+	chat_scrollmedown = false;
+	
+	// getmaxscroll through a lazy hack. We do all these loops, so let's not do more loops that are gonna lag the game more. :P
+	chat_maxscroll = (dy/charheight)-cv_chatheight.value;	// welcome to C, we don't know what min() and max() are.
+	if (chat_maxscroll < 0)
+		chat_maxscroll = 0;
+	
+	// if we're not bound by the time, autoscroll for next frame:
+	if (atbottom)
+		chat_scroll = chat_maxscroll;
+	
+	// draw arrows to indicate that we can (or not) scroll.
+	
+	if (chat_scroll > 0)
+		HU_DrawUpArrow(chatx-8, ((justscrolledup) ? (chaty-1) : (chaty)), V_SNAPTOTOP | V_SNAPTORIGHT);
+	if (chat_scroll < chat_maxscroll)
+		HU_DrawDownArrow(chatx-8, chaty+(cv_chatheight.value*charheight)-((justscrolleddown) ? 3 : 4), V_SNAPTOTOP | V_SNAPTORIGHT);
+	
+	justscrolleddown = false;
+	justscrolledup = false;
+}	
+
 //
 // HU_DrawChat
 //
 // Draw chat input
 //
+
+static INT16 typelines = 1;	// number of drawfill lines we need. it's some weird hack and might be one frame off but I'm lazy to make another loop.
 static void HU_DrawChat(void)
+{	
+	INT32 charwidth = (vid.width < 640) ? 8 : 4, charheight = (vid.width < 640) ? 8 : 6;
+	INT32 t = 0, c = 0, y = chaty + 4 + cv_chatheight.value*charheight;
+	size_t i = 0;
+	const char *ntalk = "Say: ", *ttalk = "Team: ";
+	const char *talk = ntalk;
+	
+	if (teamtalk)
+	{
+		talk = ttalk;
+#if 0
+		if (players[consoleplayer].ctfteam == 1)
+			t = 0x500;  // Red
+		else if (players[consoleplayer].ctfteam == 2)
+			t = 0x400; // Blue
+#endif
+	}
+			
+	HU_drawChatLog();
+	V_DrawFillConsoleMap(chatx, y-1, cv_chatwidth.value, (vid.width < 640 ) ? (typelines*charheight+2) : (typelines*charheight), 239 | V_SNAPTOTOP | V_SNAPTORIGHT);
+	
+	while (talk[i])
+	{
+		if (talk[i] < HU_FONTSTART)
+			++i;
+		else
+			V_DrawChatCharacter(chatx + c + 2, y, talk[i++] |V_SNAPTOTOP|V_SNAPTORIGHT, !cv_allcaps.value, NULL);
+
+		c += charwidth;
+	}
+	
+	i = 0;
+	typelines = 1;
+	
+	if ((strlen(w_chat) == 0 || c_input == 0) && hu_tick < 4)
+		V_DrawChatCharacter(chatx + 2 + c, y+1, '_' |V_SNAPTOTOP|V_SNAPTORIGHT|t, !cv_allcaps.value, NULL);
+	
+	while (w_chat[i])
+	{
+		
+		if (c_input == (i+1) && hu_tick < 4)
+		{
+			int cursorx = (c+charwidth < cv_chatwidth.value-charwidth) ? (chatx + 2 + c+charwidth) : (chatx);	// we may have to go down.
+			int cursory = (cursorx != chatx) ? (y) : (y+charheight);
+			V_DrawChatCharacter(cursorx, cursory+1, '_' |V_SNAPTOTOP|V_SNAPTORIGHT|t, !cv_allcaps.value, NULL);	
+		}	
+		
+		//Hurdler: isn't it better like that?
+		if (w_chat[i] < HU_FONTSTART)
+			++i;
+		else
+			V_DrawChatCharacter(chatx + c + 2, y, w_chat[i++] | V_SNAPTOTOP|V_SNAPTORIGHT | t, !cv_allcaps.value, NULL);
+
+		c += charwidth;
+		if (c > cv_chatwidth.value-charwidth)
+		{
+			c = 0;
+			y += charheight;
+			typelines += 1;
+		}
+	}
+
+	// handle /pm list.
+	if (strnicmp(w_chat, "/pm", 3) == 0 && vid.width >= 400 && !teamtalk)	// 320x200 unsupported kthxbai
+	{	
+		i = 0;
+		int count = 0;
+		INT32 p_dispy = chaty+2;
+		V_DrawFillConsoleMap(chatx-50, p_dispy-2, 48, 2, 239 | V_SNAPTOTOP | V_SNAPTORIGHT);	// top (don't mind me)
+		for(i=0; (i<MAXPLAYERS); i++)
+		{
+			
+			// filter: (code needs optimization pls help I'm bad with C)
+			if (w_chat[3])
+			{
+				
+				// right, that's half important: (w_chat[4] may be a space since /pm0 msg is perfectly acceptable!)
+				if ( ( ((w_chat[3] != 0) && ((w_chat[3] < '0') || (w_chat[3] > '9'))) || ((w_chat[4] != 0) && (((w_chat[4] < '0') || (w_chat[4] > '9'))))) && (w_chat[4] != ' '))
+					break;
+					
+				
+				char *nodenum = (char*) malloc(3);
+				strncpy(nodenum, w_chat+3, 4);
+				INT32 n = atoi((const char*) nodenum);	// turn that into a number
+				// special cases:
+				
+				if ((n == 0) && !(w_chat[4] == '0'))
+				{	
+					if (!(i<10))
+						continue;		
+				}
+				else if ((n == 1) && !(w_chat[3] == '0'))
+				{	
+					if (!((i == 1) || ((i >= 10) && (i <= 19))))
+						continue;
+				}
+				else if ((n == 2) && !(w_chat[3] == '0'))
+				{	
+					if (!((i == 2) || ((i >= 20) && (i <= 29))))
+						continue;
+				}
+				else if ((n == 3) && !(w_chat[3] == '0'))
+				{	
+					if (!((i == 3) || ((i >= 30) && (i <= 31))))
+						continue;
+				}
+				else	// general case.
+				{	
+					if (i != n)
+						continue;
+				}
+			}
+			
+			if ((playeringame[i]))
+			{	
+				char name[MAXPLAYERNAME+1];
+				strlcpy(name, player_names[i], 7);	// shorten name to 7 characters.
+				V_DrawFillConsoleMap(chatx-50, p_dispy+ (6*count), 48, 6, 239 | V_SNAPTOTOP | V_SNAPTORIGHT);	// fill it like the chat so the text doesn't become hard to read because of the hud.
+				V_DrawSmallString(chatx-48, p_dispy+ (6*count), V_SNAPTOTOP|V_SNAPTORIGHT|V_ALLOWLOWERCASE, va("\x82%d\x80 - %s", i, name));
+				count++;
+			}
+		}
+		if (count == 0)	// no results.
+		{
+			V_DrawFillConsoleMap(chatx-50, p_dispy+ (6*count), 48, 6, 239 | V_SNAPTOTOP | V_SNAPTORIGHT);	// fill it like the chat so the text doesn't become hard to read because of the hud.
+			V_DrawSmallString(chatx-48, p_dispy+ (6*count), V_SNAPTOTOP|V_SNAPTORIGHT|V_ALLOWLOWERCASE, "NO RESULT.");
+		}	
+	}
+	
+}
+
+// why the fuck would you use this...
+
+static void HU_DrawChat_Old(void)
 {
 	INT32 t = 0, c = 0, y = HU_INPUTY;
 	size_t i = 0;
@@ -850,7 +1569,6 @@ static void HU_DrawChat(void)
 	const char *talk = ntalk;
 	INT32 charwidth = 8 * con_scalefactor; //SHORT(hu_font['A'-HU_FONTSTART]->width) * con_scalefactor;
 	INT32 charheight = 8 * con_scalefactor; //SHORT(hu_font['A'-HU_FONTSTART]->height) * con_scalefactor;
-
 	if (teamtalk)
 	{
 		talk = ttalk;
@@ -876,10 +1594,21 @@ static void HU_DrawChat(void)
 		}
 		c += charwidth;
 	}
-
+	
+	if ((strlen(w_chat) == 0 || c_input == 0) && hu_tick < 4)
+		V_DrawCharacter(HU_INPUTX+c, y+2*con_scalefactor, '_' |cv_constextsize.value | V_NOSCALESTART|t, !cv_allcaps.value);
+	
 	i = 0;
 	while (w_chat[i])
 	{
+		
+		if (c_input == (i+1) && hu_tick < 4)
+		{
+			int cursorx = (HU_INPUTX+c+charwidth < vid.width) ? (HU_INPUTX + c + charwidth) : (HU_INPUTX);	// we may have to go down.
+			int cursory = (cursorx != HU_INPUTX) ? (y) : (y+charheight);
+			V_DrawCharacter(cursorx, cursory+2*con_scalefactor, '_' |cv_constextsize.value | V_NOSCALESTART|t, !cv_allcaps.value);	
+		}	
+		
 		//Hurdler: isn't it better like that?
 		if (w_chat[i] < HU_FONTSTART)
 		{
@@ -899,12 +1628,8 @@ static void HU_DrawChat(void)
 			y += charheight;
 		}
 	}
-
-	if (hu_tick < 4)
-		V_DrawCharacter(HU_INPUTX + c, y, '_' | cv_constextsize.value |V_NOSCALESTART|t, !cv_allcaps.value);
 }
 
-
 // draw the Crosshair, at the exact center of the view.
 //
 // Crosshairs are pre-cached at HU_Init
@@ -1175,7 +1900,45 @@ void HU_Drawer(void)
 {
 	// draw chat string plus cursor
 	if (chat_on)
-		HU_DrawChat();
+	{	
+		// count down the scroll timer. 
+		if (chat_scrolltime > 0)
+			chat_scrolltime--;
+		if (!cv_consolechat.value)
+			HU_DrawChat();
+		else
+			HU_DrawChat_Old();	// why the fuck.........................
+	}
+	else
+	{
+		if (!cv_consolechat.value)
+		{	
+			HU_drawMiniChat();		// draw messages in a cool fashion.
+			chat_scrolltime = 0;	// do scroll anyway.
+			typelines = 0;			// make sure that the chat doesn't have a weird blinking huge ass square if we typed a lot last time.
+		}	
+	}
+	
+	if (netgame)	// would handle that in hu_drawminichat, but it's actually kinda awkward when you're typing a lot of messages. (only handle that in netgames duh)
+	{
+		size_t i = 0;
+		
+		// handle spam while we're at it:
+		for(; (i<MAXPLAYERS); i++)
+		{	
+			if (stop_spamming_you_cunt[i] > 0)
+				stop_spamming_you_cunt[i]--;
+		}	
+		
+		// handle chat timers
+		for (i=0; (i<chat_nummsg_min); i++)
+		{	
+			if (chat_timers[i] > 0)
+				chat_timers[i]--;
+			else
+				HU_removeChatText_Mini();
+		}
+	}
 
 	if (cechotimer)
 		HU_DrawCEcho();
@@ -1319,6 +2082,40 @@ void HU_Erase(void)
 //                   IN-LEVEL MULTIPLAYER RANKINGS
 //======================================================================
 
+//
+// HU_drawPing
+//
+void HU_drawPing(INT32 x, INT32 y, INT32 ping, boolean notext)
+{
+	UINT8 numbars = 1;		// how many ping bars do we draw?
+	UINT8 barcolor = 128;	// color we use for the bars (green, yellow or red)
+	SINT8 i = 0;
+	SINT8 yoffset = 6;
+	if (ping < 128)
+	{	
+		numbars = 3;
+		barcolor = 184;
+	}	
+	else if (ping < 256)
+	{	
+		numbars = 2;	// Apparently ternaries w/ multiple statements don't look good in C so I decided against it.
+		barcolor = 103;
+	}	
+	
+	INT32 dx = x+1 - (V_SmallStringWidth(va("%dms", ping), V_ALLOWLOWERCASE)/2);
+	if (!notext || vid.width >= 640)	// how sad, we're using a shit resolution.
+		V_DrawSmallString(dx, y+4, V_ALLOWLOWERCASE, va("%dms", ping));
+	
+	for (i=0; (i<3); i++)		// Draw the ping bar
+	{	
+		V_DrawFill(x+2 *(i-1), y+yoffset-4, 2, 8-yoffset, 31);
+		if (i < numbars)
+			V_DrawFill(x+2 *(i-1), y+yoffset-3, 1, 8-yoffset-1, barcolor);
+		
+		yoffset -= 2;
+	}
+}	
+
 //
 // HU_DrawTabRankings
 //
@@ -1336,7 +2133,13 @@ void HU_DrawTabRankings(INT32 x, INT32 y, playersort_t *tab, INT32 scorelines, I
 	{
 		if (players[tab[i].num].spectator)
 			continue; //ignore them.
-
+		
+		if (!splitscreen)	// don't draw it on splitscreen,
+		{
+			if (!(tab[i].num == serverplayer))
+				HU_drawPing(x+ 253, y+2, playerpingtable[tab[i].num], false);
+		}	
+		
 		V_DrawString(x + 20, y,
 		             ((tab[i].num == whiteplayer) ? V_YELLOWMAP : 0)
 		             | ((players[tab[i].num].health > 0) ? 0 : V_60TRANS)
@@ -1493,6 +2296,11 @@ void HU_DrawTeamTabRankings(playersort_t *tab, INT32 whiteplayer)
 				V_DrawSmallMappedPatch (x, y-4, 0, faceprefix[players[tab[i].num].skin], colormap);
 		}
 		V_DrawRightAlignedThinString(x+120, y-1, ((players[tab[i].num].health > 0) ? 0 : V_TRANSLUCENT), va("%u", tab[i].count));
+		if (!splitscreen)
+		{	
+			if (!(tab[i].num == serverplayer))
+				HU_drawPing(x+ 113, y+2, playerpingtable[tab[i].num], false);
+		}	
 	}
 }
 
@@ -1515,6 +2323,8 @@ void HU_DrawDualTabRankings(INT32 x, INT32 y, playersort_t *tab, INT32 scoreline
 			continue; //ignore them.
 
 		strlcpy(name, tab[i].name, 9);
+		if (!(tab[i].num == serverplayer))
+			HU_drawPing(x+ 113, y+2, playerpingtable[tab[i].num], false);
 		V_DrawString(x + 20, y,
 		             ((tab[i].num == whiteplayer) ? V_YELLOWMAP : 0)
 		             | ((players[tab[i].num].health > 0) ? 0 : V_TRANSLUCENT)
diff --git a/src/hu_stuff.h b/src/hu_stuff.h
index 451cc8d8..2a99be4b 100644
--- a/src/hu_stuff.h
+++ b/src/hu_stuff.h
@@ -80,6 +80,11 @@ extern patch_t *tagico;
 extern patch_t *tallminus;
 extern patch_t *iconprefix[MAXSKINS];
 
+#define CHAT_BUFSIZE 64		// that's enough messages, right? We'll delete the older ones when that gets out of hand.
+
+// some functions
+void HU_AddChatText(const char *text);
+
 // set true when entering a chat message
 extern boolean chat_on;
 
@@ -105,6 +110,7 @@ char HU_dequeueChatChar(void);
 void HU_Erase(void);
 void HU_clearChatChars(void);
 void HU_DrawTabRankings(INT32 x, INT32 y, playersort_t *tab, INT32 scorelines, INT32 whiteplayer);
+void HU_drawPing(INT32 x, INT32 y, INT32 ping, boolean notext);	// Lat': Ping drawer for scoreboard.
 void HU_DrawTeamTabRankings(playersort_t *tab, INT32 whiteplayer);
 void HU_DrawDualTabRankings(INT32 x, INT32 y, playersort_t *tab, INT32 scorelines, INT32 whiteplayer);
 void HU_DrawEmeralds(INT32 x, INT32 y, INT32 pemeralds);
diff --git a/src/lua_baselib.c b/src/lua_baselib.c
index b49cdd72..5cdd31f0 100644
--- a/src/lua_baselib.c
+++ b/src/lua_baselib.c
@@ -86,6 +86,51 @@ static int lib_print(lua_State *L)
 	return 0;
 }
 
+// Print stuff in the chat, or in the console if we can't.
+static int lib_chatprint(lua_State *L)
+{
+	const char *str = luaL_checkstring(L, 1);	// retrieve string
+	if (str == NULL)	// error if we don't have a string!
+		return luaL_error(L, LUA_QL("tostring") " must return a string to " LUA_QL("chatprint"));
+	int len = strlen(str);
+	if (len > 255)	// string is too long!!!
+		return luaL_error(L, "String exceeds the 255 characters limit of the chat buffer.");
+	
+	if (cv_consolechat.value || !netgame)
+		CONS_Printf("%s\n", str);
+	else
+		HU_AddChatText(str);
+	return 0;
+}
+
+// Same as above, but do it for only one player.
+static int lib_chatprintf(lua_State *L)
+{
+	int n = lua_gettop(L);  /* number of arguments */
+	player_t *plr;
+	if (n < 2)
+		return luaL_error(L, "chatprintf requires at least two arguments: player and text.");
+	
+	plr = *((player_t **)luaL_checkudata(L, 1, META_PLAYER));	// retrieve player
+	if (!plr)
+		return LUA_ErrInvalid(L, "player_t");
+	if (plr != &players[consoleplayer])
+		return 0;
+	
+	const char *str = luaL_checkstring(L, 2);	// retrieve string
+	if (str == NULL)	// error if we don't have a string!
+		return luaL_error(L, LUA_QL("tostring") " must return a string to " LUA_QL("chatprintf"));
+	int len = strlen(str);
+	if (len > 255)	// string is too long!!!
+		return luaL_error(L, "String exceeds the 255 characters limit of the chat buffer.");
+	
+	if (cv_consolechat.value || !netgame)
+		CONS_Printf("%s\n", str);
+	else
+		HU_AddChatText(str);
+	return 0;
+}
+
 static int lib_evalMath(lua_State *L)
 {
 	const char *word = luaL_checkstring(L, 1);
@@ -2165,6 +2210,8 @@ static int lib_kGetKartFlashing(lua_State *L)
 
 static luaL_Reg lib[] = {
 	{"print", lib_print},
+	{"chatprint", lib_chatprint},
+	{"chatprintf", lib_chatprintf},
 	{"EvalMath", lib_evalMath},
 
 	// m_random
diff --git a/src/lua_hook.h b/src/lua_hook.h
index 53e0a7d8..7acc0d0f 100644
--- a/src/lua_hook.h
+++ b/src/lua_hook.h
@@ -74,7 +74,7 @@ boolean LUAh_MobjDeath(mobj_t *target, mobj_t *inflictor, mobj_t *source); // Ho
 boolean LUAh_BotTiccmd(player_t *bot, ticcmd_t *cmd); // Hook for B_BuildTiccmd
 boolean LUAh_BotAI(mobj_t *sonic, mobj_t *tails, ticcmd_t *cmd); // Hook for B_BuildTailsTiccmd by skin name
 boolean LUAh_LinedefExecute(line_t *line, mobj_t *mo, sector_t *sector); // Hook for linedef executors
-boolean LUAh_PlayerMsg(int source, int target, int flags, char *msg); // Hook for chat messages
+boolean LUAh_PlayerMsg(int source, int target, int flags, char *msg, int mute); // Hook for chat messages
 boolean LUAh_HurtMsg(player_t *player, mobj_t *inflictor, mobj_t *source); // Hook for hurt messages
 #define LUAh_PlayerSpawn(player) LUAh_PlayerHook(player, hook_PlayerSpawn) // Hook for G_SpawnPlayer
 
diff --git a/src/lua_hooklib.c b/src/lua_hooklib.c
index eadd0153..5b05b77c 100644
--- a/src/lua_hooklib.c
+++ b/src/lua_hooklib.c
@@ -952,7 +952,9 @@ boolean LUAh_LinedefExecute(line_t *line, mobj_t *mo, sector_t *sector)
 }
 
 // Hook for player chat
-boolean LUAh_PlayerMsg(int source, int target, int flags, char *msg)
+// Added the "mute" field. It's set to true if the message was supposed to be eaten by spam protection. 
+// But for netgame consistency purposes, this hook is ran first reguardless, so this boolean allows for modders to adapt if they so desire.
+boolean LUAh_PlayerMsg(int source, int target, int flags, char *msg, int mute)
 {
 	hook_p hookp;
 	boolean hooked = false;
@@ -981,14 +983,19 @@ boolean LUAh_PlayerMsg(int source, int target, int flags, char *msg)
 					LUA_PushUserdata(gL, &players[target-1], META_PLAYER); // target
 				}
 				lua_pushstring(gL, msg); // msg
+				if (mute)
+					lua_pushboolean(gL, true); // the message was supposed to be eaten by spamprotecc.
+				else
+					lua_pushboolean(gL, false);	
 			}
 			lua_pushfstring(gL, FMT_HOOKID, hookp->id);
 			lua_gettable(gL, LUA_REGISTRYINDEX);
-			lua_pushvalue(gL, -5);
-			lua_pushvalue(gL, -5);
-			lua_pushvalue(gL, -5);
-			lua_pushvalue(gL, -5);
-			if (lua_pcall(gL, 4, 1, 0)) {
+			lua_pushvalue(gL, -6);
+			lua_pushvalue(gL, -6);
+			lua_pushvalue(gL, -6);
+			lua_pushvalue(gL, -6);
+			lua_pushvalue(gL, -6);
+			if (lua_pcall(gL, 5, 1, 0)) {
 				if (!hookp->error || cv_debug & DBG_LUA)
 					CONS_Alert(CONS_WARNING,"%s\n",lua_tostring(gL, -1));
 				lua_pop(gL, 1);
diff --git a/src/v_video.c b/src/v_video.c
index ac0eed17..44319dd7 100644
--- a/src/v_video.c
+++ b/src/v_video.c
@@ -303,7 +303,7 @@ void VID_BlitLinearScreen(const UINT8 *srcptr, UINT8 *destptr, INT32 width, INT3
 #endif
 }
 
-//static UINT8 hudplusalpha[11]  = { 10,  8,  6,  4,  2,  0,  0,  0,  0,  0,  0};
+static UINT8 hudplusalpha[11]  = { 10,  8,  6,  4,  2,  0,  0,  0,  0,  0,  0};
 static UINT8 hudminusalpha[11] = { 10,  9,  9,  8,  8,  7,  7,  6,  6,  5,  5};
 
 static const UINT8 *v_colormap = NULL;
@@ -840,6 +840,129 @@ void V_DrawFill(INT32 x, INT32 y, INT32 w, INT32 h, INT32 c)
 		memset(dest, (UINT8)(c&255), w * vid.bpp);
 }
 
+// THANK YOU MPC!!!
+
+void V_DrawFillConsoleMap(INT32 x, INT32 y, INT32 w, INT32 h, INT32 c)
+{
+	UINT8 *dest;
+    INT32 u, v;
+	UINT32 alphalevel = 0;
+
+	if (rendermode == render_none)
+		return;
+
+#ifdef HWRENDER
+	if (rendermode != render_soft && rendermode != render_none)
+	{
+		UINT32 hwcolor;
+		switch (cons_backcolor.value)
+		{
+			case 0:		hwcolor = 0xffffff00;	break; // White
+			case 1:		hwcolor = 0x80808000;	break; // Gray
+			case 2:		hwcolor = 0x40201000;	break; // Brown
+			case 3:		hwcolor = 0xff000000;	break; // Red
+			case 4:		hwcolor = 0xff800000;	break; // Orange
+			case 5:		hwcolor = 0x80800000;	break; // Yellow
+			case 6:		hwcolor = 0x00800000;	break; // Green
+			case 7:		hwcolor = 0x0000ff00;	break; // Blue
+			case 8:		hwcolor = 0x4080ff00;	break; // Cyan
+			// Default green
+			default:	hwcolor = 0x00800000;	break;
+		}
+		HWR_DrawConsoleFill(x, y, w, h, hwcolor, c);	// we still use the regular color stuff but only for flags. actual draw color is "hwcolor" for this.
+		return;
+	}
+#endif
+
+	if (!(c & V_NOSCALESTART))
+	{
+		INT32 dupx = vid.dupx, dupy = vid.dupy;
+
+		if (x == 0 && y == 0 && w == BASEVIDWIDTH && h == BASEVIDHEIGHT)
+		{ // Clear the entire screen, from dest to deststop. Yes, this really works.
+			memset(screens[0], (UINT8)(c&255), vid.width * vid.height * vid.bpp);
+			return;
+		}
+
+		x *= dupx;
+		y *= dupy;
+		w *= dupx;
+		h *= dupy;
+
+		// Center it if necessary
+		if (vid.width != BASEVIDWIDTH * dupx)
+		{
+			// dupx adjustments pretend that screen width is BASEVIDWIDTH * dupx,
+			// so center this imaginary screen
+			if (c & V_SNAPTORIGHT)
+				x += (vid.width - (BASEVIDWIDTH * dupx));
+			else if (!(c & V_SNAPTOLEFT))
+				x += (vid.width - (BASEVIDWIDTH * dupx)) / 2;
+		}
+		if (vid.height != BASEVIDHEIGHT * dupy)
+		{
+			// same thing here
+			if (c & V_SNAPTOBOTTOM)
+				y += (vid.height - (BASEVIDHEIGHT * dupy));
+			else if (!(c & V_SNAPTOTOP))
+				y += (vid.height - (BASEVIDHEIGHT * dupy)) / 2;
+		}
+	}
+
+	if (x >= vid.width || y >= vid.height)
+		return; // off the screen
+	if (x < 0) {
+		w += x;
+		x = 0;
+	}
+	if (y < 0) {
+		h += y;
+		y = 0;
+	}
+
+	if (w <= 0 || h <= 0)
+		return; // zero width/height wouldn't draw anything
+	if (x + w > vid.width)
+		w = vid.width-x;
+	if (y + h > vid.height)
+		h = vid.height-y;
+
+	dest = screens[0] + y*vid.width + x;
+
+	if ((alphalevel = ((c & V_ALPHAMASK) >> V_ALPHASHIFT)))
+	{
+		if (alphalevel == 13)
+			alphalevel = hudminusalpha[cv_translucenthud.value];
+		else if (alphalevel == 14)
+			alphalevel = 10 - cv_translucenthud.value;
+		else if (alphalevel == 15)
+			alphalevel = hudplusalpha[cv_translucenthud.value];
+
+		if (alphalevel >= 10)
+			return; // invis
+	}
+
+	c &= 255;
+
+	if (!alphalevel) {
+        for (v = 0; v < h; v++, dest += vid.width) {
+            for (u = 0; u < w; u++) {
+                dest[u] = consolebgmap[dest[u]];
+            }
+        }
+	} else {        // mpc 12-04-2018
+        const UINT8 *fadetable = ((UINT8 *)transtables + ((alphalevel-1)<<FF_TRANSSHIFT) + (c*256));
+        #define clip(x,y) (x>y) ? y : x
+        w = clip(w,vid.width);
+        h = clip(h,vid.height);
+        for (v = 0; v < h; v++, dest += vid.width) {
+            for (u = 0; u < w; u++) {
+                dest[u] = fadetable[consolebgmap[dest[u]]];
+            }
+        }
+	}
+}
+
 //
 // Fills a box of pixels using a flat texture as a pattern, scaled to screen size.
 //
@@ -1060,6 +1183,32 @@ void V_DrawCharacter(INT32 x, INT32 y, INT32 c, boolean lowercaseallowed)
 		V_DrawScaledPatch(x, y, flags, hu_font[c]);
 }
 
+// Writes a single character for the chat. (draw WHITE if bit 7 set)
+// Essentially the same as the above but it's small or big depending on what resolution you've chosen to huge..
+//
+void V_DrawChatCharacter(INT32 x, INT32 y, INT32 c, boolean lowercaseallowed, UINT8 *colormap)
+{
+	INT32 w, flags;
+	//const UINT8 *colormap = V_GetStringColormap(c);
+
+	flags = c & ~(V_CHARCOLORMASK | V_PARAMMASK);
+	c &= 0x7f;
+	if (lowercaseallowed)
+		c -= HU_FONTSTART;
+	else
+		c = toupper(c) - HU_FONTSTART;
+	if (c < 0 || c >= HU_FONTSIZE || !hu_font[c])
+		return;
+
+	w = (vid.width < 640 ) ? (SHORT(hu_font[c]->width)/2) : (SHORT(hu_font[c]->width));	// use normal sized characters if we're using a terribly low resolution.
+	if (x + w > vid.width)
+		return;
+	
+	V_DrawFixedPatch(x*FRACUNIT, y*FRACUNIT, (vid.width < 640) ? (FRACUNIT) : (FRACUNIT/2), flags, hu_font[c], colormap);
+
+	
+}
+
 // Precompile a wordwrapped string to any given width.
 // This is a muuuch better method than V_WORDWRAP.
 char *V_WordWrap(INT32 x, INT32 w, INT32 option, const char *string)
diff --git a/src/v_video.h b/src/v_video.h
index 990f9b13..ffa1038c 100644
--- a/src/v_video.h
+++ b/src/v_video.h
@@ -139,6 +139,7 @@ void V_DrawScaledPic (INT32 px1, INT32 py1, INT32 scrn, INT32 lumpnum);
 
 // fill a box with a single color
 void V_DrawFill(INT32 x, INT32 y, INT32 w, INT32 h, INT32 c);
+void V_DrawFillConsoleMap(INT32 x, INT32 y, INT32 w, INT32 h, INT32 c);
 // fill a box with a flat as a pattern
 void V_DrawFlatFill(INT32 x, INT32 y, INT32 w, INT32 h, lumpnum_t flatnum);
 
@@ -149,6 +150,8 @@ void V_DrawFadeConsBack(INT32 plines);
 
 // draw a single character
 void V_DrawCharacter(INT32 x, INT32 y, INT32 c, boolean lowercaseallowed);
+// draw a single character, but for the chat
+void V_DrawChatCharacter(INT32 x, INT32 y, INT32 c, boolean lowercaseallowed, UINT8 *colormap);
 
 void V_DrawLevelTitle(INT32 x, INT32 y, INT32 option, const char *string);
 

From 823497ea0667d8e5148e1b821db46cb6bde88d95 Mon Sep 17 00:00:00 2001
From: toaster <rollerorbital@gmail.com>
Date: Tue, 24 Jul 2018 01:03:59 +0100
Subject: [PATCH 02/38] Fix accidentially tripped PARANOIA error for
 teamchanges.

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

diff --git a/src/d_netcmd.c b/src/d_netcmd.c
index a83f23b2..767f5729 100644
--- a/src/d_netcmd.c
+++ b/src/d_netcmd.c
@@ -2531,8 +2531,8 @@ static void Command_Teamchange_f(void)
 		return;
 	}
 
-	if (players[consoleplayer].spectator && !(players[consoleplayer].pflags & PF_WANTSTOJOIN) && !NetPacket.packet.newteam)
-		error = true;
+	if (players[consoleplayer].spectator && !NetPacket.packet.newteam)
+		error = !(players[consoleplayer].pflags & PF_WANTSTOJOIN);
 	else if (G_GametypeHasTeams() && NetPacket.packet.newteam == (unsigned)players[consoleplayer].ctfteam)
 		error = true;
 	else if (G_GametypeHasSpectators() && !players[consoleplayer].spectator && NetPacket.packet.newteam == 3)
@@ -2622,8 +2622,8 @@ static void Command_Teamchange2_f(void)
 		return;
 	}
 
-	if (players[secondarydisplayplayer].spectator && !(players[secondarydisplayplayer].pflags & PF_WANTSTOJOIN) && !NetPacket.packet.newteam)
-		error = true;
+	if (players[secondarydisplayplayer].spectator && !NetPacket.packet.newteam)
+		error = !(players[secondarydisplayplayer].pflags & PF_WANTSTOJOIN);
 	else if (G_GametypeHasTeams() && NetPacket.packet.newteam == (unsigned)players[secondarydisplayplayer].ctfteam)
 		error = true;
 	else if (G_GametypeHasSpectators() && !players[secondarydisplayplayer].spectator && NetPacket.packet.newteam == 3)
@@ -2713,8 +2713,8 @@ static void Command_Teamchange3_f(void)
 		return;
 	}
 
-	if (players[thirddisplayplayer].spectator && !(players[thirddisplayplayer].pflags & PF_WANTSTOJOIN) && !NetPacket.packet.newteam)
-		error = true;
+	if (players[thirddisplayplayer].spectator && !NetPacket.packet.newteam)
+		error = !(players[thirddisplayplayer].pflags & PF_WANTSTOJOIN);
 	else if (G_GametypeHasTeams() && NetPacket.packet.newteam == (unsigned)players[thirddisplayplayer].ctfteam)
 		error = true;
 	else if (G_GametypeHasSpectators() && !players[thirddisplayplayer].spectator && NetPacket.packet.newteam == 3)
@@ -2804,8 +2804,8 @@ static void Command_Teamchange4_f(void)
 		return;
 	}
 
-	if (players[fourthdisplayplayer].spectator && !(players[fourthdisplayplayer].pflags & PF_WANTSTOJOIN) && !NetPacket.packet.newteam)
-		error = true;
+	if (players[fourthdisplayplayer].spectator && !NetPacket.packet.newteam)
+		error = !(players[fourthdisplayplayer].pflags & PF_WANTSTOJOIN);
 	else if (G_GametypeHasTeams() && NetPacket.packet.newteam == (unsigned)players[fourthdisplayplayer].ctfteam)
 		error = true;
 	else if (G_GametypeHasSpectators() && !players[fourthdisplayplayer].spectator && NetPacket.packet.newteam == 3)

From 4f23b84f9260f9aa41a8303f8d10066b069e7500 Mon Sep 17 00:00:00 2001
From: toaster <rollerorbital@gmail.com>
Date: Tue, 24 Jul 2018 01:12:40 +0100
Subject: [PATCH 03/38] ...fix it properly instead of rushing into it at 1am

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

diff --git a/src/d_netcmd.c b/src/d_netcmd.c
index 767f5729..1b6bf4b7 100644
--- a/src/d_netcmd.c
+++ b/src/d_netcmd.c
@@ -2531,12 +2531,12 @@ static void Command_Teamchange_f(void)
 		return;
 	}
 
-	if (players[consoleplayer].spectator && !NetPacket.packet.newteam)
-		error = !(players[consoleplayer].pflags & PF_WANTSTOJOIN);
-	else if (G_GametypeHasTeams() && NetPacket.packet.newteam == (unsigned)players[consoleplayer].ctfteam)
-		error = true;
-	else if (G_GametypeHasSpectators() && !players[consoleplayer].spectator && NetPacket.packet.newteam == 3)
-		error = true;
+	if (players[consoleplayer].spectator)
+		error = !(NetPacket.packet.newteam || (players[consoleplayer].pflags & PF_WANTSTOJOIN));
+	else if (G_GametypeHasTeams())
+		error = (NetPacket.packet.newteam == (unsigned)players[consoleplayer].ctfteam);
+	else if (G_GametypeHasSpectators() && !players[consoleplayer].spectator)
+		error = (NetPacket.packet.newteam == 3);
 #ifdef PARANOIA
 	else
 		I_Error("Invalid gametype after initial checks!");
@@ -2622,12 +2622,12 @@ static void Command_Teamchange2_f(void)
 		return;
 	}
 
-	if (players[secondarydisplayplayer].spectator && !NetPacket.packet.newteam)
-		error = !(players[secondarydisplayplayer].pflags & PF_WANTSTOJOIN);
-	else if (G_GametypeHasTeams() && NetPacket.packet.newteam == (unsigned)players[secondarydisplayplayer].ctfteam)
-		error = true;
-	else if (G_GametypeHasSpectators() && !players[secondarydisplayplayer].spectator && NetPacket.packet.newteam == 3)
-		error = true;
+	if (players[secondarydisplayplayer].spectator)
+		error = !(NetPacket.packet.newteam || (players[secondarydisplayplayer].pflags & PF_WANTSTOJOIN));
+	else if (G_GametypeHasTeams())
+		error = (NetPacket.packet.newteam == (unsigned)players[secondarydisplayplayer].ctfteam);
+	else if (G_GametypeHasSpectators() && !players[secondarydisplayplayer].spectator)
+		error = (NetPacket.packet.newteam == 3);
 #ifdef PARANOIA
 	else
 		I_Error("Invalid gametype after initial checks!");
@@ -2713,12 +2713,12 @@ static void Command_Teamchange3_f(void)
 		return;
 	}
 
-	if (players[thirddisplayplayer].spectator && !NetPacket.packet.newteam)
-		error = !(players[thirddisplayplayer].pflags & PF_WANTSTOJOIN);
-	else if (G_GametypeHasTeams() && NetPacket.packet.newteam == (unsigned)players[thirddisplayplayer].ctfteam)
-		error = true;
-	else if (G_GametypeHasSpectators() && !players[thirddisplayplayer].spectator && NetPacket.packet.newteam == 3)
-		error = true;
+	if (players[thirddisplayplayer].spectator)
+		error = !(NetPacket.packet.newteam || (players[thirddisplayplayer].pflags & PF_WANTSTOJOIN));
+	else if (G_GametypeHasTeams())
+		error = (NetPacket.packet.newteam == (unsigned)players[thirddisplayplayer].ctfteam);
+	else if (G_GametypeHasSpectators() && !players[thirddisplayplayer].spectator)
+		error = (NetPacket.packet.newteam == 3);
 #ifdef PARANOIA
 	else
 		I_Error("Invalid gametype after initial checks!");
@@ -2804,12 +2804,12 @@ static void Command_Teamchange4_f(void)
 		return;
 	}
 
-	if (players[fourthdisplayplayer].spectator && !NetPacket.packet.newteam)
-		error = !(players[fourthdisplayplayer].pflags & PF_WANTSTOJOIN);
-	else if (G_GametypeHasTeams() && NetPacket.packet.newteam == (unsigned)players[fourthdisplayplayer].ctfteam)
-		error = true;
-	else if (G_GametypeHasSpectators() && !players[fourthdisplayplayer].spectator && NetPacket.packet.newteam == 3)
-		error = true;
+	if (players[fourthdisplayplayer].spectator)
+		error = !(NetPacket.packet.newteam || (players[fourthdisplayplayer].pflags & PF_WANTSTOJOIN));
+	else if (G_GametypeHasTeams())
+		error = (NetPacket.packet.newteam == (unsigned)players[fourthdisplayplayer].ctfteam);
+	else if (G_GametypeHasSpectators() && !players[fourthdisplayplayer].spectator)
+		error = (NetPacket.packet.newteam == 3);
 #ifdef PARANOIA
 	else
 		I_Error("Invalid gametype after initial checks!");

From f98bc6f8513340de6492c107dc80451871b854b9 Mon Sep 17 00:00:00 2001
From: wolfy852 <wolfy852@hotmail.com>
Date: Mon, 23 Jul 2018 19:50:28 -0500
Subject: [PATCH 04/38] Fix a minor conditional error in d_netcmd.c

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

diff --git a/src/d_netcmd.c b/src/d_netcmd.c
index 1b6bf4b7..bd436485 100644
--- a/src/d_netcmd.c
+++ b/src/d_netcmd.c
@@ -2532,7 +2532,7 @@ static void Command_Teamchange_f(void)
 	}
 
 	if (players[consoleplayer].spectator)
-		error = !(NetPacket.packet.newteam || (players[consoleplayer].pflags & PF_WANTSTOJOIN));
+		error = !(NetPacket.packet.newteam && (players[consoleplayer].pflags & PF_WANTSTOJOIN));
 	else if (G_GametypeHasTeams())
 		error = (NetPacket.packet.newteam == (unsigned)players[consoleplayer].ctfteam);
 	else if (G_GametypeHasSpectators() && !players[consoleplayer].spectator)
@@ -2623,7 +2623,7 @@ static void Command_Teamchange2_f(void)
 	}
 
 	if (players[secondarydisplayplayer].spectator)
-		error = !(NetPacket.packet.newteam || (players[secondarydisplayplayer].pflags & PF_WANTSTOJOIN));
+		error = !(NetPacket.packet.newteam && (players[secondarydisplayplayer].pflags & PF_WANTSTOJOIN));
 	else if (G_GametypeHasTeams())
 		error = (NetPacket.packet.newteam == (unsigned)players[secondarydisplayplayer].ctfteam);
 	else if (G_GametypeHasSpectators() && !players[secondarydisplayplayer].spectator)
@@ -2714,7 +2714,7 @@ static void Command_Teamchange3_f(void)
 	}
 
 	if (players[thirddisplayplayer].spectator)
-		error = !(NetPacket.packet.newteam || (players[thirddisplayplayer].pflags & PF_WANTSTOJOIN));
+		error = !(NetPacket.packet.newteam && (players[thirddisplayplayer].pflags & PF_WANTSTOJOIN));
 	else if (G_GametypeHasTeams())
 		error = (NetPacket.packet.newteam == (unsigned)players[thirddisplayplayer].ctfteam);
 	else if (G_GametypeHasSpectators() && !players[thirddisplayplayer].spectator)
@@ -2805,7 +2805,7 @@ static void Command_Teamchange4_f(void)
 	}
 
 	if (players[fourthdisplayplayer].spectator)
-		error = !(NetPacket.packet.newteam || (players[fourthdisplayplayer].pflags & PF_WANTSTOJOIN));
+		error = !(NetPacket.packet.newteam && (players[fourthdisplayplayer].pflags & PF_WANTSTOJOIN));
 	else if (G_GametypeHasTeams())
 		error = (NetPacket.packet.newteam == (unsigned)players[fourthdisplayplayer].ctfteam);
 	else if (G_GametypeHasSpectators() && !players[fourthdisplayplayer].spectator)

From 4f1ddaaa7d5384c932f70829c350f18e634fefd3 Mon Sep 17 00:00:00 2001
From: toaster <rollerorbital@gmail.com>
Date: Tue, 24 Jul 2018 22:04:27 +0100
Subject: [PATCH 05/38] Fix buffer overrun and iteration clobbering in
 D_SetupVote by: * Decreasing number of writes - now the unchanging gametype
 is only written once, and both gametypes are written as UINT8s instead of
 UINT16s. * Increasing size of buffer to match new threshold.

---
 src/d_netcmd.c | 34 ++++++++++++++++++----------------
 1 file changed, 18 insertions(+), 16 deletions(-)

diff --git a/src/d_netcmd.c b/src/d_netcmd.c
index 1b6bf4b7..6fe39b6f 100644
--- a/src/d_netcmd.c
+++ b/src/d_netcmd.c
@@ -1966,26 +1966,22 @@ void D_MapChange(INT32 mapnum, INT32 newgametype, boolean pultmode, boolean rese
 
 void D_SetupVote(void)
 {
-	char buf[8];
-	char *p = buf;
+	UINT8 buf[6*2]; // five UINT16 maps (at twice the width of a UINT8), and two gametypes
+	UINT8 *p = buf;
 	INT32 i;
+	UINT8 secondgt = G_SometimesGetDifferentGametype();
+
+	WRITEUINT8(p, gametype);
+	WRITEUINT8(p, secondgt);
 
 	for (i = 0; i < 5; i++)
 	{
 		if (i == 2) // sometimes a different gametype
-		{
-			INT16 gt = G_SometimesGetDifferentGametype();
-			WRITEUINT16(p, G_RandMap(G_TOLFlag(gt), prevmap, false, false, 0, true));
-			WRITEUINT16(p, gt);
-		}
+			WRITEUINT16(p, G_RandMap(G_TOLFlag(secondgt), prevmap, false, false, 0, true));
+		else if (i >= 3) // unknown-random and force-unknown MAP HELL
+			WRITEUINT16(p, G_RandMap(G_TOLFlag(gametype), prevmap, true, false, (i-2), (i < 4)));
 		else
-		{
-			if (i >= 3) // unknown-random and force-unknown MAP HELL
-				WRITEUINT16(p, G_RandMap(G_TOLFlag(gametype), prevmap, true, false, (i-2), (i < 4)));
-			else
-				WRITEUINT16(p, G_RandMap(G_TOLFlag(gametype), prevmap, false, false, 0, true));
-			WRITEUINT16(p, gametype);
-		}
+			WRITEUINT16(p, G_RandMap(G_TOLFlag(gametype), prevmap, false, false, 0, true));
 	}
 
 	SendNetXCmd(XD_SETUPVOTE, buf, p - buf);
@@ -4582,6 +4578,7 @@ static void Got_ExitLevelcmd(UINT8 **cp, INT32 playernum)
 static void Got_SetupVotecmd(UINT8 **cp, INT32 playernum)
 {
 	INT32 i;
+	UINT8 gt, secondgt;
 
 	if (playernum != serverplayer && !IsPlayerAdmin(playernum))
 	{
@@ -4597,14 +4594,19 @@ static void Got_SetupVotecmd(UINT8 **cp, INT32 playernum)
 		return;
 	}
 
+	gt = (UINT8)READUINT8(*cp);
+	secondgt = (UINT8)READUINT8(*cp);
+
 	for (i = 0; i < 5; i++)
 	{
-		votelevels[i][0] = (INT16)READUINT16(*cp);
-		votelevels[i][1] = (INT16)READUINT16(*cp);
+		votelevels[i][0] = (UINT16)READUINT16(*cp);
+		votelevels[i][1] = gt;
 		if (!mapheaderinfo[votelevels[i][0]])
 			P_AllocMapHeader(votelevels[i][0]);
 	}
 
+	votelevels[2][1] = secondgt;
+
 	G_SetGamestate(GS_VOTING);
 	Y_StartVote();
 }

From b42d083b992361e99c8b2fd03e36716ebcf1506a Mon Sep 17 00:00:00 2001
From: TehRealSalt <tehrealsalt@gmail.com>
Date: Tue, 24 Jul 2018 22:47:09 -0400
Subject: [PATCH 06/38] Finishing touches on Ballhog

- Ballhog has explosion + sound on death
- Ballhog shrinks to nothing if it didn't hit anything
- Replaced a Mario sound on Orbinaut
- Fixed Jawz's sound effect
- Fixed Mines not being removed on death pits
---
 src/d_netcmd.c |  2 +-
 src/dehacked.c | 18 ++++++++++++++
 src/info.c     | 67 +++++++++++++++++++++++++++++++++++++++++---------
 src/info.h     | 18 ++++++++++++++
 src/k_kart.c   |  4 +++
 src/p_enemy.c  | 18 +++++++++++++-
 src/p_map.c    |  2 +-
 src/p_mobj.c   | 23 +++++++++++------
 src/sounds.c   |  7 +++---
 src/sounds.h   |  7 +++---
 10 files changed, 138 insertions(+), 28 deletions(-)

diff --git a/src/d_netcmd.c b/src/d_netcmd.c
index fd729d3e..e959f406 100644
--- a/src/d_netcmd.c
+++ b/src/d_netcmd.c
@@ -5259,4 +5259,4 @@ static void KartComeback_OnChange(void)
 			comeback = (boolean)cv_kartcomeback.value;
 		}
 	}
-}
\ No newline at end of file
+}
diff --git a/src/dehacked.c b/src/dehacked.c
index 9cc06dfd..921cc2db 100644
--- a/src/dehacked.c
+++ b/src/dehacked.c
@@ -1827,6 +1827,7 @@ static actionpointer_t actionpointers[] =
 	{{A_JawzChase},            "A_JAWZCHASE"}, // SRB2kart
 	{{A_JawzExplode},          "A_JAWZEXPLODE"}, // SRB2kart
 	{{A_MineExplode},          "A_MINEEXPLODE"}, // SRB2kart
+	{{A_BallhogExplode},       "A_BALLHOGEXPLODE"}, // SRB2kart
 	{{A_OrbitNights},          "A_ORBITNIGHTS"},
 	{{A_GhostMe},              "A_GHOSTME"},
 	{{A_SetObjectState},       "A_SETOBJECTSTATE"},
@@ -6415,6 +6416,22 @@ static const char *const STATE_LIST[] = { // array length left dynamic for sanit
 	"S_BALLHOG7",
 	"S_BALLHOG8",
 	"S_BALLHOG_DEAD",
+	"S_BALLHOGBOOM1",
+	"S_BALLHOGBOOM2",
+	"S_BALLHOGBOOM3",
+	"S_BALLHOGBOOM4",
+	"S_BALLHOGBOOM5",
+	"S_BALLHOGBOOM6",
+	"S_BALLHOGBOOM7",
+	"S_BALLHOGBOOM8",
+	"S_BALLHOGBOOM9",
+	"S_BALLHOGBOOM10",
+	"S_BALLHOGBOOM11",
+	"S_BALLHOGBOOM12",
+	"S_BALLHOGBOOM13",
+	"S_BALLHOGBOOM14",
+	"S_BALLHOGBOOM15",
+	"S_BALLHOGBOOM16",
 
 	// Self-Propelled Bomb - just an explosion for now...
 	"S_BLUELIGHTNING1",
@@ -7173,6 +7190,7 @@ static const char *const MOBJTYPE_LIST[] = {  // array length left dynamic for s
 	"MT_BOOMPARTICLE",
 
 	"MT_BALLHOG", // Ballhog
+	"MT_BALLHOGBOOM",
 
 	"MT_BLUELIGHTNING", // Grow/shrink stuff
 	"MT_BLUEEXPLOSION",
diff --git a/src/info.c b/src/info.c
index 939efd44..8cfea02c 100644
--- a/src/info.c
+++ b/src/info.c
@@ -2725,15 +2725,31 @@ state_t states[NUMSTATES] =
 	{SPR_KRBM, FF_FULLBRIGHT|8, 5, {NULL}, 0, 0, S_SLOWBOOM10},		// S_SLOWBOOM9
 	{SPR_KRBM, FF_FULLBRIGHT|9, 5, {NULL}, 0, 0, S_NULL},				// S_SLOWBOOM10
 
-	{SPR_BHOG,               0, 3, {A_PlaySound}, sfx_s1bd, 1, S_BALLHOG2}, // S_BALLHOG1
-	{SPR_BHOG, FF_FULLBRIGHT|1, 1, {NULL}, 0, 0, S_BALLHOG3}, // S_BALLHOG2
-	{SPR_BHOG, FF_FULLBRIGHT|2, 2, {NULL}, 0, 0, S_BALLHOG4}, // S_BALLHOG3
-	{SPR_BHOG, FF_FULLBRIGHT|3, 3, {NULL}, 0, 0, S_BALLHOG5}, // S_BALLHOG4
-	{SPR_BHOG, FF_FULLBRIGHT|4, 3, {NULL}, 0, 0, S_BALLHOG6}, // S_BALLHOG5
-	{SPR_BHOG,               5, 2, {NULL}, 0, 0, S_BALLHOG7}, // S_BALLHOG6
-	{SPR_BHOG,               6, 1, {NULL}, 0, 0, S_BALLHOG8}, // S_BALLHOG7
-	{SPR_BHOG,               7, 1, {NULL}, 0, 0, S_BALLHOG1}, // S_BALLHOG8
-	{SPR_NULL, 0, 1, {NULL}, 0, 0, S_NULL},	// S_BALLHOG_DEAD
+	{SPR_BHOG,               0, 3, {A_PlaySound}, sfx_s1bd, 1, S_BALLHOG2},	// S_BALLHOG1
+	{SPR_BHOG, FF_FULLBRIGHT|1, 1, {NULL}, 0, 0, S_BALLHOG3},					// S_BALLHOG2
+	{SPR_BHOG, FF_FULLBRIGHT|2, 2, {NULL}, 0, 0, S_BALLHOG4},					// S_BALLHOG3
+	{SPR_BHOG, FF_FULLBRIGHT|3, 3, {NULL}, 0, 0, S_BALLHOG5}, 				// S_BALLHOG4
+	{SPR_BHOG, FF_FULLBRIGHT|4, 3, {NULL}, 0, 0, S_BALLHOG6}, 				// S_BALLHOG5
+	{SPR_BHOG,               5, 2, {NULL}, 0, 0, S_BALLHOG7}, 				// S_BALLHOG6
+	{SPR_BHOG,               6, 1, {NULL}, 0, 0, S_BALLHOG8}, 				// S_BALLHOG7
+	{SPR_BHOG,               7, 1, {NULL}, 0, 0, S_BALLHOG1}, 				// S_BALLHOG8
+	{SPR_NULL,               0, 1, {A_BallhogExplode}, 0, 0, S_NULL},			// S_BALLHOG_DEAD
+	{SPR_BHOG, FF_FULLBRIGHT|8, 1, {NULL}, 0, 0, S_BALLHOGBOOM2},			// S_BALLHOGBOOM1
+	{SPR_BHOG, FF_FULLBRIGHT|9, 1, {NULL}, 0, 0, S_BALLHOGBOOM3},			// S_BALLHOGBOOM2
+	{SPR_BHOG, FF_FULLBRIGHT|10, 1, {NULL}, 0, 0, S_BALLHOGBOOM4},			// S_BALLHOGBOOM3
+	{SPR_BHOG, FF_FULLBRIGHT|11, 1, {NULL}, 0, 0, S_BALLHOGBOOM5},			// S_BALLHOGBOOM4
+	{SPR_BHOG, FF_FULLBRIGHT|12, 1, {NULL}, 0, 0, S_BALLHOGBOOM6},			// S_BALLHOGBOOM5
+	{SPR_BHOG, FF_FULLBRIGHT|13, 1, {NULL}, 0, 0, S_BALLHOGBOOM7},			// S_BALLHOGBOOM6
+	{SPR_BHOG, FF_FULLBRIGHT|14, 1, {NULL}, 0, 0, S_BALLHOGBOOM8},			// S_BALLHOGBOOM7
+	{SPR_BHOG, FF_FULLBRIGHT|15, 1, {NULL}, 0, 0, S_BALLHOGBOOM9},			// S_BALLHOGBOOM8
+	{SPR_BHOG, FF_FULLBRIGHT|16, 1, {NULL}, 0, 0, S_BALLHOGBOOM10},			// S_BALLHOGBOOM9
+	{SPR_BHOG, FF_FULLBRIGHT|17, 1, {NULL}, 0, 0, S_BALLHOGBOOM11},			// S_BALLHOGBOOM10
+	{SPR_BHOG, FF_FULLBRIGHT|18, 1, {NULL}, 0, 0, S_BALLHOGBOOM12},			// S_BALLHOGBOOM11
+	{SPR_BHOG, FF_FULLBRIGHT|19, 1, {NULL}, 0, 0, S_BALLHOGBOOM13},			// S_BALLHOGBOOM12
+	{SPR_BHOG, FF_FULLBRIGHT|20, 1, {NULL}, 0, 0, S_BALLHOGBOOM14},			// S_BALLHOGBOOM13
+	{SPR_BHOG, FF_FULLBRIGHT|21, 1, {NULL}, 0, 0, S_BALLHOGBOOM15},			// S_BALLHOGBOOM14
+	{SPR_BHOG, FF_FULLBRIGHT|22, 1, {NULL}, 0, 0, S_BALLHOGBOOM16},			// S_BALLHOGBOOM15
+	{SPR_BHOG, FF_FULLBRIGHT|23, 1, {NULL}, 0, 0, S_NULL},					// S_BALLHOGBOOM16
 
 	{SPR_BLIG, 0,  2, {NULL}, 0, 0, S_BLUELIGHTNING2},             // S_BLUELIGHTNING1
 	{SPR_BLIG, 1,  2, {NULL}, 0, 0, S_BLUELIGHTNING3},             // S_BLUELIGHTNING2
@@ -14603,13 +14619,13 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		S_NULL,         // seestate
 		sfx_tossed,     // seesound
 		8,              // reactiontime
-		sfx_mario1,     // attacksound
+		sfx_s3k49,      // attacksound
 		S_NULL,         // painstate
 		0,              // painchance
 		sfx_None,       // painsound
 		S_NULL,         // meleestate
 		S_NULL,         // missilestate
-		S_ORBINAUT_DEAD, // deathstate
+		S_ORBINAUT_DEAD,// deathstate
 		S_NULL,         // xdeathstate
 		sfx_shbrk,      // deathsound
 		64*FRACUNIT,    // speed
@@ -14935,7 +14951,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		S_NULL,         // missilestate
 		S_BALLHOG_DEAD, // deathstate
 		S_NULL,         // xdeathstate
-		sfx_mario1,     // deathsound
+		sfx_hogbom,     // deathsound
 		0,              // speed
 		16*FRACUNIT,    // radius
 		32*FRACUNIT,    // height
@@ -14947,6 +14963,33 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		S_NULL          // raisestate
 	},
 
+	{           // MT_BALLHOGBOOM
+		-1,             // doomednum
+		S_BALLHOGBOOM1, // 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
+		1,              // damage
+		sfx_None,       // activesound
+		MF_NOGRAVITY|MF_NOCLIP|MF_NOCLIPHEIGHT|MF_NOCLIPTHING, // flags
+		S_NULL          // raisestate
+	},
+
 	{           // MT_BLUELIGHTNING
 		-1,             // doomednum
 		S_BLUELIGHTNING1, // spawnstate
diff --git a/src/info.h b/src/info.h
index 53552f4c..d24ba3a3 100644
--- a/src/info.h
+++ b/src/info.h
@@ -167,6 +167,7 @@ void A_ItemPop(); // SRB2kart
 void A_JawzChase(); // SRB2kart
 void A_JawzExplode(); // SRB2kart
 void A_MineExplode(); // SRB2kart
+void A_BallhogExplode(); // SRB2kart
 void A_OrbitNights();
 void A_GhostMe();
 void A_SetObjectState();
@@ -3254,6 +3255,22 @@ typedef enum state
 	S_BALLHOG7,
 	S_BALLHOG8,
 	S_BALLHOG_DEAD,
+	S_BALLHOGBOOM1,
+	S_BALLHOGBOOM2,
+	S_BALLHOGBOOM3,
+	S_BALLHOGBOOM4,
+	S_BALLHOGBOOM5,
+	S_BALLHOGBOOM6,
+	S_BALLHOGBOOM7,
+	S_BALLHOGBOOM8,
+	S_BALLHOGBOOM9,
+	S_BALLHOGBOOM10,
+	S_BALLHOGBOOM11,
+	S_BALLHOGBOOM12,
+	S_BALLHOGBOOM13,
+	S_BALLHOGBOOM14,
+	S_BALLHOGBOOM15,
+	S_BALLHOGBOOM16,
 
 	// Self-Propelled Bomb - just an explosion for now...
 	S_BLUELIGHTNING1,
@@ -4029,6 +4046,7 @@ typedef enum mobj_type
 	MT_BOOMPARTICLE,
 
 	MT_BALLHOG, // Ballhog
+	MT_BALLHOGBOOM,
 
 	MT_BLUELIGHTNING, // Grow/shrink stuff
 	MT_BLUEEXPLOSION,
diff --git a/src/k_kart.c b/src/k_kart.c
index 8d64af57..464c4869 100644
--- a/src/k_kart.c
+++ b/src/k_kart.c
@@ -2046,6 +2046,10 @@ static mobj_t *K_SpawnKartMissile(mobj_t *source, mobjtype_t type, angle_t angle
 		else
 			th->color = SKINCOLOR_CLOUDY;
 	}
+	else if (type == MT_JAWZ || type == MT_JAWZ_DUD)
+	{
+		S_StartSound(th, th->info->activesound);
+	}
 
 	x = x + P_ReturnThrustX(source, an, source->radius + th->radius);
 	y = y + P_ReturnThrustY(source, an, source->radius + th->radius);
diff --git a/src/p_enemy.c b/src/p_enemy.c
index 0c5f9d17..a9333777 100644
--- a/src/p_enemy.c
+++ b/src/p_enemy.c
@@ -191,6 +191,7 @@ void A_ItemPop(mobj_t *actor); // SRB2kart
 void A_JawzChase(mobj_t *actor); // SRB2kart
 void A_JawzExplode(mobj_t *actor); // SRB2kart
 void A_MineExplode(mobj_t *actor); // SRB2kart
+void A_BallhogExplode(mobj_t *actor); // SRB2kart
 void A_OrbitNights(mobj_t *actor);
 void A_GhostMe(mobj_t *actor);
 void A_SetObjectState(mobj_t *actor);
@@ -8092,7 +8093,7 @@ void A_ToggleFlameJet(mobj_t* actor)
 	}
 }
 
-//{ SRB2kart - A_ItemPop, A_JawzChase, A_JawzExplode, and A_MineExplode
+//{ SRB2kart - A_ItemPop, A_JawzChase, A_JawzExplode, A_MineExplode, and A_BallhogExplode
 void A_ItemPop(mobj_t *actor)
 {
 	mobj_t *remains;
@@ -8361,6 +8362,21 @@ void A_MineExplode(mobj_t *actor)
 
 	return;
 }
+
+void A_BallhogExplode(mobj_t *actor)
+{
+	mobj_t *mo2;
+#ifdef HAVE_BLUA
+	if (LUA_CallAction("A_BallhogExplode", actor))
+		return;
+#endif
+
+	mo2 = P_SpawnMobj(actor->x, actor->y, actor->z, MT_BALLHOGBOOM);
+	P_SetScale(mo2, actor->scale*2);
+	mo2->destscale = mo2->scale;
+	S_StartSound(mo2, actor->info->deathsound);
+	return;
+}
 //}
 
 // Function: A_OrbitNights
diff --git a/src/p_map.c b/src/p_map.c
index d4ee7c90..e50fbb2c 100644
--- a/src/p_map.c
+++ b/src/p_map.c
@@ -881,7 +881,7 @@ static boolean PIT_CheckThing(mobj_t *thing)
 			return true;
 
 		if (tmthing->type == MT_BALLHOG && thing->type == MT_BALLHOG)
-			return true; // Fireballs don't collide with eachother
+			return true; // Ballhogs don't collide with eachother
 
 		if (thing->player && thing->player->powers[pw_flashing])
 			return true;
diff --git a/src/p_mobj.c b/src/p_mobj.c
index 9e13db6d..d7ecbcae 100644
--- a/src/p_mobj.c
+++ b/src/p_mobj.c
@@ -1723,14 +1723,22 @@ void P_XYMovement(mobj_t *mo)
 	if (CheckForBustableBlocks && mo->flags & MF_PUSHABLE)
 		P_PushableCheckBustables(mo);
 
-	//{ SRB2kart - Fireball
+	//{ SRB2kart - Ballhogs
 	if (mo->type == MT_BALLHOG)
 	{
-		mo->health--;
-		if (mo->health == 0)
+		if (mo->health)
 		{
-			S_StartSound(mo, mo->info->deathsound);
-			P_SetMobjState(mo, mo->info->deathstate);
+			mo->health--;
+			if (mo->health == 0)
+				mo->destscale = 1;
+		}
+		else
+		{
+			if (mo->scale < mapheaderinfo[gamemap-1]->mobj_scale/16)
+			{
+				P_RemoveMobj(mo);
+				return;
+			}
 		}
 	}
 	//}
@@ -2336,6 +2344,7 @@ static boolean P_ZMovement(mobj_t *mo)
 		case MT_JAWZ:
 		case MT_JAWZ_DUD:
 		case MT_BALLHOG:
+		case MT_SSMINE:
 			// Remove stuff from death pits.
 			if (P_CheckDeathPitCollide(mo))
 			{
@@ -8005,7 +8014,7 @@ void P_MobjThinker(mobj_t *mobj)
 
 			if (mobj->threshold > 0)
 				mobj->threshold--;
-			if (leveltime % 7 == 0)
+			if (leveltime % TICRATE == 0)
 				S_StartSound(mobj, mobj->info->activesound);
 
 			if (gamespeed == 0)
@@ -8079,7 +8088,7 @@ void P_MobjThinker(mobj_t *mobj)
 			if (mobj->threshold > 0)
 				mobj->threshold--;
 
-			if (leveltime % 7 == 0)
+			if (leveltime % TICRATE == 0)
 				S_StartSound(mobj, mobj->info->activesound);
 
 			break;
diff --git a/src/sounds.c b/src/sounds.c
index 69fc8433..ffe0e479 100644
--- a/src/sounds.c
+++ b/src/sounds.c
@@ -147,9 +147,6 @@ sfxinfo_t S_sfx[NUMSFX] =
   {"drown",  false, 192,  0, -1, NULL, 0,        -1,  -1, LUMPERROR},
   {"fizzle", false, 127,  8, -1, NULL, 0,        -1,  -1, LUMPERROR},
   {"gbeep",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR}, // Grenade beep
-  {"yeeeah", false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR},
-  {"noooo1", false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR},
-  {"noooo2", false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR},
   {"ghit" ,  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR},
   {"gloop",  false,  60,  0, -1, NULL, 0,        -1,  -1, LUMPERROR},
   {"gspray", false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR},
@@ -809,6 +806,10 @@ sfxinfo_t S_sfx[NUMSFX] =
   {"boing",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR},
   {"smkinv", false, 140,  8, -1, NULL, 0,        -1,  -1, LUMPERROR},
   {"screec", false,  52,  0, -1, NULL, 0,        -1,  -1, LUMPERROR},
+  {"yeeeah", false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR},
+  {"noooo1", false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR},
+  {"noooo2", false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR},
+  {"hogbom", false, 110,  8, -1, NULL, 0,        -1,  -1, LUMPERROR},
 
   // SRB2kart - Skin sounds
   {"kwin",   false,  64,  0, -1, NULL, 0,    SKSWIN,  -1, LUMPERROR},
diff --git a/src/sounds.h b/src/sounds.h
index 106e77df..0f8d5a3b 100644
--- a/src/sounds.h
+++ b/src/sounds.h
@@ -219,9 +219,6 @@ typedef enum
 	sfx_drown,
 	sfx_fizzle,
 	sfx_gbeep,
-	sfx_yeeeah,
-	sfx_noooo1,
-	sfx_noooo2,
 	sfx_ghit,
 	sfx_gloop,
 	sfx_gspray,
@@ -881,6 +878,10 @@ typedef enum
 	sfx_boing,
 	sfx_smkinv,
 	sfx_screec,
+	sfx_yeeeah,
+	sfx_noooo1,
+	sfx_noooo2,
+	sfx_hogbom,
 
 	sfx_kwin,
 	sfx_klose,

From 0dc2c43a672b6199ac2904c2afee9e9793d40ba3 Mon Sep 17 00:00:00 2001
From: TehRealSalt <tehrealsalt@gmail.com>
Date: Wed, 25 Jul 2018 12:49:30 -0400
Subject: [PATCH 07/38] Banana drag touches

- Banana drag timer resets when placing one, for x3 & x10 bananas
- Banana dragging spawns transulcent dust
- Dragging bananas make them twitch up & down
---
 src/k_kart.c      | 31 +++++++++++++++++++++++++------
 src/k_kart.h      |  2 +-
 src/lua_baselib.c |  3 ++-
 src/p_user.c      |  2 +-
 4 files changed, 29 insertions(+), 9 deletions(-)

diff --git a/src/k_kart.c b/src/k_kart.c
index 464c4869..d2ded697 100644
--- a/src/k_kart.c
+++ b/src/k_kart.c
@@ -2154,7 +2154,7 @@ void K_SpawnSparkleTrail(mobj_t *mo)
 	}
 }
 
-void K_SpawnWipeoutTrail(mobj_t *mo)
+void K_SpawnWipeoutTrail(mobj_t *mo, boolean translucent)
 {
 	mobj_t *dust;
 
@@ -2168,6 +2168,9 @@ void K_SpawnWipeoutTrail(mobj_t *mo)
 	dust->destscale = mo->scale;
 	P_SetScale(dust, mo->scale);
 	dust->eflags = (dust->eflags & ~MFE_VERTICALFLIP)|(mo->eflags & MFE_VERTICALFLIP);
+
+	if (translucent)
+		dust->flags2 |= MF2_SHADOW;
 }
 
 //	K_DriftDustHandling
@@ -2280,8 +2283,7 @@ static mobj_t *K_ThrowKartItem(player_t *player, boolean missile, mobjtype_t map
 	mobj_t *mo;
 	INT32 dir, PROJSPEED;
 	angle_t newangle;
-	fixed_t newx;
-	fixed_t newy;
+	fixed_t newx, newy, newz;
 	mobj_t *throwmo;
 
 	if (!player)
@@ -2393,10 +2395,13 @@ static mobj_t *K_ThrowKartItem(player_t *player, boolean missile, mobjtype_t map
 		{
 			mobj_t *lasttrail = K_FindLastTrailMobj(player);
 
+			player->kartstuff[k_bananadrag] = 0; // RESET timer, for multiple bananas
+
 			if (lasttrail)
 			{
 				newx = lasttrail->x;
 				newy = lasttrail->y;
+				newz = lasttrail->z;
 			}
 			else
 			{
@@ -2407,9 +2412,10 @@ static mobj_t *K_ThrowKartItem(player_t *player, boolean missile, mobjtype_t map
 
 				newx = player->mo->x + P_ReturnThrustX(player->mo, newangle + ANGLE_180, dropradius);
 				newy = player->mo->y + P_ReturnThrustY(player->mo, newangle + ANGLE_180, dropradius);
+				newz = player->mo->z;
 			}
 
-			mo = P_SpawnMobj(newx, newy, player->mo->z, mapthing);
+			mo = P_SpawnMobj(newx, newy, newz, mapthing);
 
 			if (P_MobjFlip(player->mo) < 0)
 				mo->z = player->mo->z + player->mo->height - mo->height;
@@ -2785,8 +2791,12 @@ static void K_MoveHeldObjects(player_t *player)
 				if (P_IsObjectOnGround(player->mo) && player->speed > 0)
 				{
 					player->kartstuff[k_bananadrag]++;
-					if (player->kartstuff[k_bananadrag] > TICRATE && leveltime % 7 == 0)
-						S_StartSound(player->mo, sfx_cdfm70);
+					if (player->kartstuff[k_bananadrag] > TICRATE)
+					{
+						K_SpawnWipeoutTrail(player->mo, true);
+						if (leveltime % 6 == 0)
+							S_StartSound(player->mo, sfx_cdfm70);
+					}
 				}
 
 				while (cur && !P_MobjWasRemoved(cur))
@@ -2824,6 +2834,15 @@ static void K_MoveHeldObjects(player_t *player)
 
 					cur->angle = R_PointToAngle2(cur->x, cur->y, targx, targy);
 
+					if (P_IsObjectOnGround(player->mo) && player->speed > 0 && player->kartstuff[k_bananadrag] > TICRATE
+						&& P_RandomChance(min(FRACUNIT/2, FixedDiv(player->speed, K_GetKartSpeed(player, false))/2)))
+					{
+						if (leveltime & 1)
+							targz += 8*(2*FRACUNIT)/7;
+						else
+							targz -= 8*(2*FRACUNIT)/7;
+					}
+
 					if (speed > dist)
 						P_InstaThrust(cur, cur->angle, speed-dist);
 
diff --git a/src/k_kart.h b/src/k_kart.h
index 4a6a7e1a..9b3bdc1a 100644
--- a/src/k_kart.h
+++ b/src/k_kart.h
@@ -33,7 +33,7 @@ void K_SpawnKartExplosion(fixed_t x, fixed_t y, fixed_t z, fixed_t radius, INT32
 void K_SpawnMineExplosion(mobj_t *source, UINT8 color);
 void K_SpawnBoostTrail(player_t *player);
 void K_SpawnSparkleTrail(mobj_t *mo);
-void K_SpawnWipeoutTrail(mobj_t *mo);
+void K_SpawnWipeoutTrail(mobj_t *mo, boolean translucent);
 void K_DriftDustHandling(mobj_t *spawner);
 void K_DoSneaker(player_t *player, boolean doPFlag);
 void K_DoPogoSpring(mobj_t *mo, fixed_t vertispeed, boolean mute);
diff --git a/src/lua_baselib.c b/src/lua_baselib.c
index d86315c0..30285e0e 100644
--- a/src/lua_baselib.c
+++ b/src/lua_baselib.c
@@ -2135,10 +2135,11 @@ static int lib_kSpawnSparkleTrail(lua_State *L)
 static int lib_kSpawnWipeoutTrail(lua_State *L)
 {
 	mobj_t *mo = *((mobj_t **)luaL_checkudata(L, 1, META_MOBJ));
+	boolean translucent = luaL_checkboolean(L, 2);
 	NOHUD
 	if (!mo)
 		return LUA_ErrInvalid(L, "mobj_t");
-	K_SpawnWipeoutTrail(mo);
+	K_SpawnWipeoutTrail(mo, translucent);
 	return 0;
 }
 
diff --git a/src/p_user.c b/src/p_user.c
index f612e81d..106b3645 100644
--- a/src/p_user.c
+++ b/src/p_user.c
@@ -6938,7 +6938,7 @@ static void P_MovePlayer(player_t *player)
 		K_SpawnSparkleTrail(player->mo);
 
 	if (player->kartstuff[k_wipeoutslow] > 1 && (leveltime & 1))
-		K_SpawnWipeoutTrail(player->mo);
+		K_SpawnWipeoutTrail(player->mo, false);
 
 	K_DriftDustHandling(player->mo);
 

From bd12658355098b78cc270a5e15d6f7d4ced98f88 Mon Sep 17 00:00:00 2001
From: toaster <rollerorbital@gmail.com>
Date: Wed, 25 Jul 2018 21:33:03 +0100
Subject: [PATCH 08/38] Added the ability to toggle the f-zero style
 elimination of last place specifically.

Type `karteliminatelast off` to turn it off, and `karteliminatelast on` to... you know the rest.
---
 src/d_netcmd.c | 10 ++++++++++
 src/d_netcmd.h |  2 ++
 src/k_kart.c   |  1 +
 src/p_inter.c  | 37 ++++++++++++++++++++-----------------
 4 files changed, 33 insertions(+), 17 deletions(-)

diff --git a/src/d_netcmd.c b/src/d_netcmd.c
index e959f406..e0a31408 100644
--- a/src/d_netcmd.c
+++ b/src/d_netcmd.c
@@ -114,6 +114,7 @@ static void KartFrantic_OnChange(void);
 static void KartSpeed_OnChange(void);
 static void KartMirror_OnChange(void);
 static void KartComeback_OnChange(void);
+static void KartEliminateLast_OnChange(void);
 
 #ifdef NETGAME_DEVMODE
 static void Fishcake_OnChange(void);
@@ -366,6 +367,9 @@ consvar_t cv_kartmirror = {"kartmirror", "Off", CV_NETVAR|CV_CHEAT|CV_CALL|CV_NO
 static CV_PossibleValue_t kartspeedometer_cons_t[] = {{0, "Off"}, {1, "Kilometers"}, {2, "Miles"}, {3, "Fracunits"}, {0, NULL}};
 consvar_t cv_kartspeedometer = {"kartdisplayspeed", "Off", CV_SAVE, kartspeedometer_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL}; // use tics in display
 
+// this might be a debug or it might be an undocumented regular feature
+consvar_t cv_karteliminatelast = {"karteliminatelast", "Yes", CV_NETVAR|CV_CHEAT|CV_CALL, CV_OnOff, KartEliminateLast_OnChange, 0, NULL, NULL, 0, 0, NULL};
+
 static CV_PossibleValue_t kartdebugitem_cons_t[] = {{-1, "MIN"}, {NUMKARTITEMS-1, "MAX"}, {0, NULL}};
 consvar_t cv_kartdebugitem = {"kartdebugitem", "0", CV_NETVAR|CV_CHEAT, kartdebugitem_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL};
 static CV_PossibleValue_t kartdebugamount_cons_t[] = {{1, "MIN"}, {255, "MAX"}, {0, NULL}};
@@ -5260,3 +5264,9 @@ static void KartComeback_OnChange(void)
 		}
 	}
 }
+
+static void KartEliminateLast_OnChange(void)
+{
+	if (G_RaceGametype() && cv_karteliminatelast.value)
+		P_CheckRacers();
+}
diff --git a/src/d_netcmd.h b/src/d_netcmd.h
index 0805a41f..009563af 100644
--- a/src/d_netcmd.h
+++ b/src/d_netcmd.h
@@ -127,6 +127,8 @@ extern consvar_t cv_kartcomeback;
 extern consvar_t cv_kartmirror;
 extern consvar_t cv_kartspeedometer;
 
+extern consvar_t cv_karteliminatelast;
+
 extern consvar_t cv_votetime;
 
 extern consvar_t cv_kartdebugitem, cv_kartdebugamount, cv_kartdebugcheckpoint, cv_kartdebugshrink;
diff --git a/src/k_kart.c b/src/k_kart.c
index d2ded697..3b982756 100644
--- a/src/k_kart.c
+++ b/src/k_kart.c
@@ -403,6 +403,7 @@ void K_RegisterKartStuff(void)
 	CV_RegisterVar(&cv_kartcomeback);
 	CV_RegisterVar(&cv_kartmirror);
 	CV_RegisterVar(&cv_kartspeedometer);
+	CV_RegisterVar(&cv_karteliminatelast);
 	CV_RegisterVar(&cv_votetime);
 
 	CV_RegisterVar(&cv_kartdebugitem);
diff --git a/src/p_inter.c b/src/p_inter.c
index a0cecbb3..765c73f9 100644
--- a/src/p_inter.c
+++ b/src/p_inter.c
@@ -2068,29 +2068,32 @@ boolean P_CheckRacers(void)
 		return true;
 	}
 
-	for (j = 0; j < MAXPLAYERS; j++)
+	if (cv_karteliminatelast.value)
 	{
-		if (!playeringame[j] || players[j].spectator)
-			continue;
-		numplayersingame++;
-	}
-
-	if (numplayersingame > 1) // if there's more than one player in-game, this is safe to do
-	{
-		// check if we just got unlucky and there was only one guy who was a problem
-		for (j = i+1; j < MAXPLAYERS; j++)
+		for (j = 0; j < MAXPLAYERS; j++)
 		{
-			if (!playeringame[j] || players[j].spectator || players[j].exiting || !players[j].lives)
+			if (!playeringame[j] || players[j].spectator)
 				continue;
-
-			break;
+			numplayersingame++;
 		}
 
-		if (j == MAXPLAYERS) // finish anyways, force a time over
+		if (numplayersingame > 1) // if there's more than one player in-game, this is safe to do
 		{
-			P_DoTimeOver(&players[i]);
-			countdown = countdown2 = 0;
-			return true;
+			// check if we just got unlucky and there was only one guy who was a problem
+			for (j = i+1; j < MAXPLAYERS; j++)
+			{
+				if (!playeringame[j] || players[j].spectator || players[j].exiting || !players[j].lives)
+					continue;
+
+				break;
+			}
+
+			if (j == MAXPLAYERS) // finish anyways, force a time over
+			{
+				P_DoTimeOver(&players[i]);
+				countdown = countdown2 = 0;
+				return true;
+			}
 		}
 	}
 

From f9e5a11060c64611c4596d60a16874db3e41f522 Mon Sep 17 00:00:00 2001
From: TehRealSalt <tehrealsalt@gmail.com>
Date: Thu, 26 Jul 2018 20:12:42 -0400
Subject: [PATCH 09/38] - Instashield on failed damge

- Ballhog explosion separated onto another sprite
- Disabled banana jitter
---
 src/d_player.h    |   1 +
 src/dehacked.c    |  19 ++++++++
 src/info.c        | 110 +++++++++++++++++++++++++++++++++++++---------
 src/info.h        |  21 +++++++++
 src/k_kart.c      |  47 +++++++++++++++++---
 src/k_kart.h      |   1 +
 src/lua_baselib.c |  11 +++++
 src/p_inter.c     |   9 ++--
 src/p_map.c       |  15 ++-----
 src/p_mobj.c      |  14 ++++++
 src/sounds.c      |   2 +-
 11 files changed, 208 insertions(+), 42 deletions(-)

diff --git a/src/d_player.h b/src/d_player.h
index f184d4cf..7b5aeafc 100644
--- a/src/d_player.h
+++ b/src/d_player.h
@@ -281,6 +281,7 @@ typedef enum
 	k_cardanimation,	// Used to determine the position of some full-screen Battle Mode graphics
 	k_voices,			// Used to stop the player saying more voices than it should
 	k_tauntvoices,		// Used to specifically stop taunt voice spam
+	k_instashield,		// Instashield no-damage animation timer
 
 	k_floorboost,		// Prevents Sneaker sounds for a breif duration when triggered by a floor panel
 	k_spinouttype,		// Determines whether to thrust forward or not while spinning out; 0 = move forwards, 1 = stay still
diff --git a/src/dehacked.c b/src/dehacked.c
index 921cc2db..03884474 100644
--- a/src/dehacked.c
+++ b/src/dehacked.c
@@ -6625,6 +6625,21 @@ static const char *const STATE_LIST[] = { // array length left dynamic for sanit
 	"S_FIREDITEM3",
 	"S_FIREDITEM4",
 
+	"S_INSTASHIELDA1", // No damage instashield effect
+	"S_INSTASHIELDA2",
+	"S_INSTASHIELDA3",
+	"S_INSTASHIELDA4",
+	"S_INSTASHIELDA5",
+	"S_INSTASHIELDA6",
+	"S_INSTASHIELDA7",
+	"S_INSTASHIELDB1",
+	"S_INSTASHIELDB2",
+	"S_INSTASHIELDB3",
+	"S_INSTASHIELDB4",
+	"S_INSTASHIELDB5",
+	"S_INSTASHIELDB6",
+	"S_INSTASHIELDB7",
+
 	"S_PLAYERARROW", // Above player arrow
 	"S_PLAYERARROW_BOX",
 	"S_PLAYERARROW_ITEM",
@@ -7272,6 +7287,9 @@ static const char *const MOBJTYPE_LIST[] = {  // array length left dynamic for s
 
 	"MT_FIREDITEM",
 
+	"MT_INSTASHIELDA",
+	"MT_INSTASHIELDB",
+
 	"MT_PLAYERARROW",
 	"MT_PLAYERWANTED",
 
@@ -7609,6 +7627,7 @@ static const char *const KARTSTUFF_LIST[] = {
 	"CARDANIMATION",
 	"VOICES",
 	"TAUNTVOICES",
+	"INSTASHIELD",
 
 	"FLOORBOOST",
 	"SPINOUTTYPE",
diff --git a/src/info.c b/src/info.c
index 8cfea02c..41e89ae6 100644
--- a/src/info.c
+++ b/src/info.c
@@ -56,10 +56,10 @@ char sprnames[NUMSPRITES + 1][5] =
 	"SRBJ","SRBK","SRBL","SRBM","SRBN","SRBO",
 	//SRB2kart Sprites
 	"SPRG","BSPR","RNDM","RPOP","KFRE","KINV","KINF","WIPD","DRIF","DUST",
-	"FITM","BANA","ORBN","JAWZ","SSMN","KRBM","BHOG","BLIG","LIGH","SINK",
-	"SITR","KBLN","DEZL","POKE","AUDI","DECO","DOOD","SNES","GBAS","SPRS",
-	"BUZB","CHOM","SACO","CRAB","SHAD","BRNG","BUMP","FLEN","CLAS","PSHW",
-	"ARRO","ITEM","ITMI","ITMN","WANT","PBOM","VIEW"
+	"FITM","BANA","ORBN","JAWZ","SSMN","KRBM","BHOG","BHBM","BLIG","LIGH",
+	"SINK","SITR","KBLN","DEZL","POKE","AUDI","DECO","DOOD","SNES","GBAS",
+	"SPRS","BUZB","CHOM","SACO","CRAB","SHAD","BRNG","BUMP","FLEN","CLAS",
+	"PSHW","ISTA","ISTB","ARRO","ITEM","ITMI","ITMN","WANT","PBOM","VIEW"
 };
 
 // Doesn't work with g++, needs actionf_p1 (don't modify this comment)
@@ -2734,22 +2734,22 @@ state_t states[NUMSTATES] =
 	{SPR_BHOG,               6, 1, {NULL}, 0, 0, S_BALLHOG8}, 				// S_BALLHOG7
 	{SPR_BHOG,               7, 1, {NULL}, 0, 0, S_BALLHOG1}, 				// S_BALLHOG8
 	{SPR_NULL,               0, 1, {A_BallhogExplode}, 0, 0, S_NULL},			// S_BALLHOG_DEAD
-	{SPR_BHOG, FF_FULLBRIGHT|8, 1, {NULL}, 0, 0, S_BALLHOGBOOM2},			// S_BALLHOGBOOM1
-	{SPR_BHOG, FF_FULLBRIGHT|9, 1, {NULL}, 0, 0, S_BALLHOGBOOM3},			// S_BALLHOGBOOM2
-	{SPR_BHOG, FF_FULLBRIGHT|10, 1, {NULL}, 0, 0, S_BALLHOGBOOM4},			// S_BALLHOGBOOM3
-	{SPR_BHOG, FF_FULLBRIGHT|11, 1, {NULL}, 0, 0, S_BALLHOGBOOM5},			// S_BALLHOGBOOM4
-	{SPR_BHOG, FF_FULLBRIGHT|12, 1, {NULL}, 0, 0, S_BALLHOGBOOM6},			// S_BALLHOGBOOM5
-	{SPR_BHOG, FF_FULLBRIGHT|13, 1, {NULL}, 0, 0, S_BALLHOGBOOM7},			// S_BALLHOGBOOM6
-	{SPR_BHOG, FF_FULLBRIGHT|14, 1, {NULL}, 0, 0, S_BALLHOGBOOM8},			// S_BALLHOGBOOM7
-	{SPR_BHOG, FF_FULLBRIGHT|15, 1, {NULL}, 0, 0, S_BALLHOGBOOM9},			// S_BALLHOGBOOM8
-	{SPR_BHOG, FF_FULLBRIGHT|16, 1, {NULL}, 0, 0, S_BALLHOGBOOM10},			// S_BALLHOGBOOM9
-	{SPR_BHOG, FF_FULLBRIGHT|17, 1, {NULL}, 0, 0, S_BALLHOGBOOM11},			// S_BALLHOGBOOM10
-	{SPR_BHOG, FF_FULLBRIGHT|18, 1, {NULL}, 0, 0, S_BALLHOGBOOM12},			// S_BALLHOGBOOM11
-	{SPR_BHOG, FF_FULLBRIGHT|19, 1, {NULL}, 0, 0, S_BALLHOGBOOM13},			// S_BALLHOGBOOM12
-	{SPR_BHOG, FF_FULLBRIGHT|20, 1, {NULL}, 0, 0, S_BALLHOGBOOM14},			// S_BALLHOGBOOM13
-	{SPR_BHOG, FF_FULLBRIGHT|21, 1, {NULL}, 0, 0, S_BALLHOGBOOM15},			// S_BALLHOGBOOM14
-	{SPR_BHOG, FF_FULLBRIGHT|22, 1, {NULL}, 0, 0, S_BALLHOGBOOM16},			// S_BALLHOGBOOM15
-	{SPR_BHOG, FF_FULLBRIGHT|23, 1, {NULL}, 0, 0, S_NULL},					// S_BALLHOGBOOM16
+	{SPR_BHBM, FF_FULLBRIGHT, 1, {NULL}, 0, 0, S_BALLHOGBOOM2},				// S_BALLHOGBOOM1
+	{SPR_BHBM, FF_FULLBRIGHT|1, 1, {NULL}, 0, 0, S_BALLHOGBOOM3},				// S_BALLHOGBOOM2
+	{SPR_BHBM, FF_FULLBRIGHT|2, 1, {NULL}, 0, 0, S_BALLHOGBOOM4},				// S_BALLHOGBOOM3
+	{SPR_BHBM, FF_FULLBRIGHT|3, 1, {NULL}, 0, 0, S_BALLHOGBOOM5},				// S_BALLHOGBOOM4
+	{SPR_BHBM, FF_FULLBRIGHT|4, 1, {NULL}, 0, 0, S_BALLHOGBOOM6},				// S_BALLHOGBOOM5
+	{SPR_BHBM, FF_FULLBRIGHT|5, 1, {NULL}, 0, 0, S_BALLHOGBOOM7},				// S_BALLHOGBOOM6
+	{SPR_BHBM, FF_FULLBRIGHT|6, 1, {NULL}, 0, 0, S_BALLHOGBOOM8},				// S_BALLHOGBOOM7
+	{SPR_BHBM, FF_FULLBRIGHT|7, 1, {NULL}, 0, 0, S_BALLHOGBOOM9},				// S_BALLHOGBOOM8
+	{SPR_BHBM, FF_FULLBRIGHT|8, 1, {NULL}, 0, 0, S_BALLHOGBOOM10},			// S_BALLHOGBOOM9
+	{SPR_BHBM, FF_FULLBRIGHT|9, 1, {NULL}, 0, 0, S_BALLHOGBOOM11},			// S_BALLHOGBOOM10
+	{SPR_BHBM, FF_FULLBRIGHT|10, 1, {NULL}, 0, 0, S_BALLHOGBOOM12},			// S_BALLHOGBOOM11
+	{SPR_BHBM, FF_FULLBRIGHT|11, 1, {NULL}, 0, 0, S_BALLHOGBOOM13},			// S_BALLHOGBOOM12
+	{SPR_BHBM, FF_FULLBRIGHT|12, 1, {NULL}, 0, 0, S_BALLHOGBOOM14},			// S_BALLHOGBOOM13
+	{SPR_BHBM, FF_FULLBRIGHT|13, 1, {NULL}, 0, 0, S_BALLHOGBOOM15},			// S_BALLHOGBOOM14
+	{SPR_BHBM, FF_FULLBRIGHT|14, 1, {NULL}, 0, 0, S_BALLHOGBOOM16},			// S_BALLHOGBOOM15
+	{SPR_BHBM, FF_FULLBRIGHT|15, 1, {NULL}, 0, 0, S_NULL},					// S_BALLHOGBOOM16
 
 	{SPR_BLIG, 0,  2, {NULL}, 0, 0, S_BLUELIGHTNING2},             // S_BLUELIGHTNING1
 	{SPR_BLIG, 1,  2, {NULL}, 0, 0, S_BLUELIGHTNING3},             // S_BLUELIGHTNING2
@@ -2931,6 +2931,22 @@ state_t states[NUMSTATES] =
 	{SPR_PSHW, FF_FULLBRIGHT|2, 3, {NULL}, 0, 0, S_FIREDITEM4}, // S_FIREDITEM3
 	{SPR_PSHW, FF_FULLBRIGHT|3, 3, {NULL}, 0, 0, S_NULL}, // S_FIREDITEM4
 
+	{SPR_ISTA, FF_FULLBRIGHT|FF_TRANS30, 2, {NULL}, 0, 0, S_INSTASHIELDA2},	// S_INSTASHIELDA1
+	{SPR_ISTA, FF_FULLBRIGHT|FF_TRANS30|1, 2, {NULL}, 0, 0, S_INSTASHIELDA3},	// S_INSTASHIELDA2
+	{SPR_ISTA, FF_FULLBRIGHT|FF_TRANS30|2, 2, {NULL}, 0, 0, S_INSTASHIELDA4},	// S_INSTASHIELDA3
+	{SPR_ISTA, FF_FULLBRIGHT|FF_TRANS30|3, 2, {NULL}, 0, 0, S_INSTASHIELDA5},	// S_INSTASHIELDA4
+	{SPR_ISTA, FF_FULLBRIGHT|FF_TRANS30|4, 2, {NULL}, 0, 0, S_INSTASHIELDA6},	// S_INSTASHIELDA5
+	{SPR_ISTA, FF_FULLBRIGHT|FF_TRANS30|5, 2, {NULL}, 0, 0, S_INSTASHIELDA7},	// S_INSTASHIELDA6
+	{SPR_ISTA, FF_FULLBRIGHT|FF_TRANS30|6, 2, {NULL}, 0, 0, S_NULL},			// S_INSTASHIELDA7
+
+	{SPR_ISTB, FF_FULLBRIGHT, 2, {NULL}, 0, 0, S_INSTASHIELDB2},		// S_INSTASHIELDB1
+	{SPR_ISTB, FF_FULLBRIGHT|1, 2, {NULL}, 0, 0, S_INSTASHIELDB3},	// S_INSTASHIELDB2
+	{SPR_ISTB, FF_FULLBRIGHT|2, 2, {NULL}, 0, 0, S_INSTASHIELDB4},	// S_INSTASHIELDB3
+	{SPR_ISTB, FF_FULLBRIGHT|3, 2, {NULL}, 0, 0, S_INSTASHIELDB5},	// S_INSTASHIELDB4
+	{SPR_ISTB, FF_FULLBRIGHT|4, 2, {NULL}, 0, 0, S_INSTASHIELDB6},	// S_INSTASHIELDB5
+	{SPR_ISTB, FF_FULLBRIGHT|5, 2, {NULL}, 0, 0, S_INSTASHIELDB7},	// S_INSTASHIELDB6
+	{SPR_ISTB, FF_FULLBRIGHT|6, 2, {NULL}, 0, 0, S_NULL},				// S_INSTASHIELDB7
+
 	// Above player arrow
 	{SPR_ARRO, FF_FULLBRIGHT, -1, {NULL}, 0, 0, S_NULL}, // S_PLAYERARROW
 	{SPR_ARRO, FF_FULLBRIGHT|1, -1, {NULL}, 0, 0, S_NULL}, // S_PLAYERARROW_BOX
@@ -16637,6 +16653,60 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		S_NULL          // raisestate
 	},
 
+	{           // MT_INSTASHIELDA
+		-1,						// doomednum
+		S_INSTASHIELDA1,	    // 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
+		8,						// speed
+		8*FRACUNIT,				// radius
+		8*FRACUNIT,				// height
+		1,						// display offset
+		100,					// mass
+		0,						// damage
+		sfx_None,				// activesound
+		MF_NOBLOCKMAP|MF_NOGRAVITY|MF_NOCLIP|MF_NOCLIPHEIGHT, // flags
+		S_NULL					// raisestate
+	},
+
+	{           // MT_INSTASHIELDB
+		-1,						// doomednum
+		S_INSTASHIELDB1,	    // 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
+		8,						// speed
+		8*FRACUNIT,				// radius
+		8*FRACUNIT,				// height
+		2,						// display offset
+		100,					// mass
+		0,						// damage
+		sfx_None,				// activesound
+		MF_NOBLOCKMAP|MF_NOGRAVITY|MF_NOCLIP|MF_NOCLIPHEIGHT, // flags
+		S_NULL					// raisestate
+	},
+
 	{           // MT_PLAYERARROW
 		-1,             // doomednum
 		S_PLAYERARROW,  // spawnstate
diff --git a/src/info.h b/src/info.h
index d24ba3a3..3186fc88 100644
--- a/src/info.h
+++ b/src/info.h
@@ -597,6 +597,7 @@ typedef enum sprite
 	SPR_SSMN, // SS Mine
 	SPR_KRBM, // SS Mine BOOM
 	SPR_BHOG, // Ballhog
+	SPR_BHBM, // Ballhog BOOM
 	SPR_BLIG, // Self-Propelled Bomb
 	SPR_LIGH, // Grow/shrink beams (Metallic Maddness)
 	SPR_SINK, // Kitchen Sink
@@ -624,6 +625,8 @@ typedef enum sprite
 	SPR_FLEN, // Shell hit graphics stuff
 	SPR_CLAS, // items clash
 	SPR_PSHW, // thrown indicator
+	SPR_ISTA, // instashield layer A
+	SPR_ISTB, // instashield layer B
 
 	SPR_ARRO, // player arrows
 	SPR_ITEM,
@@ -3464,6 +3467,21 @@ typedef enum state
 	S_FIREDITEM3,
 	S_FIREDITEM4,
 
+	S_INSTASHIELDA1, // No damage instashield effect
+	S_INSTASHIELDA2,
+	S_INSTASHIELDA3,
+	S_INSTASHIELDA4,
+	S_INSTASHIELDA5,
+	S_INSTASHIELDA6,
+	S_INSTASHIELDA7,
+	S_INSTASHIELDB1,
+	S_INSTASHIELDB2,
+	S_INSTASHIELDB3,
+	S_INSTASHIELDB4,
+	S_INSTASHIELDB5,
+	S_INSTASHIELDB6,
+	S_INSTASHIELDB7,
+
 	S_PLAYERARROW, // Above player arrow
 	S_PLAYERARROW_BOX,
 	S_PLAYERARROW_ITEM,
@@ -4128,6 +4146,9 @@ typedef enum mobj_type
 
 	MT_FIREDITEM,
 
+	MT_INSTASHIELDA,
+	MT_INSTASHIELDB,
+
 	MT_PLAYERARROW,
 	MT_PLAYERWANTED,
 
diff --git a/src/k_kart.c b/src/k_kart.c
index 3b982756..41af9754 100644
--- a/src/k_kart.c
+++ b/src/k_kart.c
@@ -1557,6 +1557,24 @@ fixed_t K_3dKartMovement(player_t *player, boolean onground, fixed_t forwardmove
 	return finalspeed;
 }
 
+void K_DoInstashield(player_t *player)
+{
+	mobj_t *layera;
+	mobj_t *layerb;
+
+	if (player->kartstuff[k_instashield] > 0)
+		return;
+
+	player->kartstuff[k_instashield] = 14; // length of instashield animation
+	S_StartSound(player->mo, sfx_cdpcm9);
+
+	layera = P_SpawnMobj(player->mo->x, player->mo->y, player->mo->z, MT_INSTASHIELDA);
+	P_SetTarget(&layera->target, player->mo);
+
+	layerb = P_SpawnMobj(player->mo->x, player->mo->y, player->mo->z, MT_INSTASHIELDB);
+	P_SetTarget(&layerb->target, player->mo);
+}
+
 void K_SpinPlayer(player_t *player, mobj_t *source, INT32 type, boolean trapitem)
 {
 	const UINT8 scoremultiply = ((K_IsPlayerWanted(player) && !trapitem) ? 2 : 1);
@@ -1567,7 +1585,10 @@ void K_SpinPlayer(player_t *player, mobj_t *source, INT32 type, boolean trapitem
 	if (player->powers[pw_flashing] > 0 || player->kartstuff[k_squishedtimer] > 0 || player->kartstuff[k_spinouttimer] > 0
 		|| player->kartstuff[k_invincibilitytimer] > 0 || player->kartstuff[k_growshrinktimer] > 0 || player->kartstuff[k_hyudorotimer] > 0
 		|| (G_BattleGametype() && ((player->kartstuff[k_bumper] <= 0 && player->kartstuff[k_comebacktimer]) || player->kartstuff[k_comebackmode] == 1)))
+	{
+		K_DoInstashield(player);
 		return;
+	}
 
 	if (source && source != player->mo && source->player)
 		K_PlayHitEmSound(source);
@@ -1640,7 +1661,10 @@ void K_SquishPlayer(player_t *player, mobj_t *source)
 	if (player->powers[pw_flashing] > 0 || player->kartstuff[k_squishedtimer] > 0 || player->kartstuff[k_invincibilitytimer] > 0
 		|| player->kartstuff[k_growshrinktimer] > 0 || player->kartstuff[k_hyudorotimer] > 0
 		|| (G_BattleGametype() && ((player->kartstuff[k_bumper] <= 0 && player->kartstuff[k_comebacktimer]) || player->kartstuff[k_comebackmode] == 1)))
+	{
+		K_DoInstashield(player);
 		return;
+	}
 
 	player->kartstuff[k_sneakertimer] = 0;
 	player->kartstuff[k_driftboost] = 0;
@@ -1698,7 +1722,10 @@ void K_ExplodePlayer(player_t *player, mobj_t *source) // A bit of a hack, we ju
 	if (player->powers[pw_flashing] > 0 || player->kartstuff[k_squishedtimer] > 0 || player->kartstuff[k_spinouttimer] > 0
 		|| player->kartstuff[k_invincibilitytimer] > 0 || player->kartstuff[k_growshrinktimer] > 0 || player->kartstuff[k_hyudorotimer] > 0
 		|| (G_BattleGametype() && ((player->kartstuff[k_bumper] <= 0 && player->kartstuff[k_comebacktimer]) || player->kartstuff[k_comebackmode] == 1)))
+	{
+		K_DoInstashield(player);
 		return;
+	}
 
 	player->mo->momz = 18*(mapheaderinfo[gamemap-1]->mobj_scale);
 	player->mo->momx = player->mo->momy = 0;
@@ -1772,12 +1799,15 @@ void K_StealBumper(player_t *player, player_t *victim, boolean force)
 		if (victim->kartstuff[k_bumper] <= 0) // || player->kartstuff[k_bumper] >= cv_kartbumpers.value+2
 			return;
 
-		if ((player->powers[pw_flashing] > 0 || player->kartstuff[k_squishedtimer] > 0 || player->kartstuff[k_spinouttimer] > 0
-			|| player->kartstuff[k_invincibilitytimer] > 0 || player->kartstuff[k_growshrinktimer] > 0 || player->kartstuff[k_hyudorotimer] > 0
-			|| (player->kartstuff[k_bumper] <= 0 && player->kartstuff[k_comebacktimer]))
-			|| (victim->powers[pw_flashing] > 0 || victim->kartstuff[k_squishedtimer] > 0 || victim->kartstuff[k_spinouttimer] > 0
-			|| victim->kartstuff[k_invincibilitytimer] > 0 || victim->kartstuff[k_growshrinktimer] > 0 || victim->kartstuff[k_hyudorotimer] > 0))
+		if (player->kartstuff[k_squishedtimer] > 0 || player->kartstuff[k_spinouttimer] > 0)
 			return;
+
+		if (victim->powers[pw_flashing] > 0 || victim->kartstuff[k_squishedtimer] > 0 || victim->kartstuff[k_spinouttimer] > 0
+			|| victim->kartstuff[k_invincibilitytimer] > 0 || victim->kartstuff[k_growshrinktimer] > 0 || victim->kartstuff[k_hyudorotimer] > 0)
+		{
+			K_DoInstashield(victim);
+			return;
+		}
 	}
 
 	if (netgame)
@@ -2835,14 +2865,14 @@ static void K_MoveHeldObjects(player_t *player)
 
 					cur->angle = R_PointToAngle2(cur->x, cur->y, targx, targy);
 
-					if (P_IsObjectOnGround(player->mo) && player->speed > 0 && player->kartstuff[k_bananadrag] > TICRATE
+					/*if (P_IsObjectOnGround(player->mo) && player->speed > 0 && player->kartstuff[k_bananadrag] > TICRATE
 						&& P_RandomChance(min(FRACUNIT/2, FixedDiv(player->speed, K_GetKartSpeed(player, false))/2)))
 					{
 						if (leveltime & 1)
 							targz += 8*(2*FRACUNIT)/7;
 						else
 							targz -= 8*(2*FRACUNIT)/7;
-					}
+					}*/
 
 					if (speed > dist)
 						P_InstaThrust(cur, cur->angle, speed-dist);
@@ -3036,6 +3066,9 @@ void K_KartPlayerThink(player_t *player, ticcmd_t *cmd)
 	if (P_IsObjectOnGround(player->mo))
 		player->kartstuff[k_waterskip] = 0;
 
+	if (player->kartstuff[k_instashield])
+		player->kartstuff[k_instashield]--;
+
 	// ???
 	/*
 	if (player->kartstuff[k_jmp] > 1 && onground)
diff --git a/src/k_kart.h b/src/k_kart.h
index 9b3bdc1a..c0810a86 100644
--- a/src/k_kart.h
+++ b/src/k_kart.h
@@ -25,6 +25,7 @@ void K_RespawnChecker(player_t *player);
 void K_KartMoveAnimation(player_t *player);
 void K_KartPlayerThink(player_t *player, ticcmd_t *cmd);
 void K_KartPlayerAfterThink(player_t *player);
+void K_DoInstashield(player_t *player);
 void K_SpinPlayer(player_t *player, mobj_t *source, INT32 type, boolean trapitem);
 void K_SquishPlayer(player_t *player, mobj_t *source);
 void K_ExplodePlayer(player_t *player, mobj_t *source);
diff --git a/src/lua_baselib.c b/src/lua_baselib.c
index 30285e0e..9c5cfc4c 100644
--- a/src/lua_baselib.c
+++ b/src/lua_baselib.c
@@ -2038,6 +2038,16 @@ static int lib_kKartBouncing(lua_State *L)
 	return 0;
 }
 
+static int lib_kDoInstashield(lua_State *L)
+{
+	player_t *player = *((player_t **)luaL_checkudata(L, 1, META_PLAYER));
+	NOHUD
+	if (!player)
+		return LUA_ErrInvalid(L, "player_t");
+	K_DoInstashield(player);
+	return 0;
+}
+
 static int lib_kSpinPlayer(lua_State *L)
 {
 	player_t *player = *((player_t **)luaL_checkudata(L, 1, META_PLAYER));
@@ -2422,6 +2432,7 @@ static luaL_Reg lib[] = {
 	{"K_IsPlayerLosing",lib_kIsPlayerLosing},
 	{"K_IsPlayerWanted",lib_kIsPlayerWanted},
 	{"K_KartBouncing",lib_kKartBouncing},
+	{"K_DoInstashield",lib_kDoInstashield},
 	{"K_SpinPlayer",lib_kSpinPlayer},
 	{"K_SquishPlayer",lib_kSquishPlayer},
 	{"K_ExplodePlayer",lib_kExplodePlayer},
diff --git a/src/p_inter.c b/src/p_inter.c
index 765c73f9..474cf148 100644
--- a/src/p_inter.c
+++ b/src/p_inter.c
@@ -3079,7 +3079,6 @@ boolean P_DamageMobj(mobj_t *target, mobj_t *inflictor, mobj_t *source, INT32 da
 #else
 	static const boolean force = false;
 #endif
-	mobj_t *blueexplode;
 
 	if (objectplacing)
 		return false;
@@ -3283,12 +3282,13 @@ boolean P_DamageMobj(mobj_t *target, mobj_t *inflictor, mobj_t *source, INT32 da
 		// Self-Propelled Bomb
 		if (damage == 65)
 		{
+			mobj_t *spbexplode;
 			if (player == source->player)
 				return false;
 			// Just need to do this now! Being thrown upwards is done by the explosion.
 			//P_SpawnMobj(player->mo->x, player->mo->y, player->mo->z, MT_BLUELIGHTNING);
-			blueexplode = P_SpawnMobj(player->mo->x, player->mo->y, player->mo->z, MT_BLUEEXPLOSION);
-			P_SetTarget(&blueexplode->target, source);
+			spbexplode = P_SpawnMobj(player->mo->x, player->mo->y, player->mo->z, MT_BLUEEXPLOSION);
+			P_SetTarget(&spbexplode->target, source);
 			return true;
 		}
 		//}
@@ -3315,7 +3315,10 @@ boolean P_DamageMobj(mobj_t *target, mobj_t *inflictor, mobj_t *source, INT32 da
 		if (damage == 10000)
 			P_KillPlayer(player, source, damage);
 		else if (player->kartstuff[k_invincibilitytimer] > 0 || player->kartstuff[k_growshrinktimer] > 0 || player->powers[pw_flashing])
+		{
+			K_DoInstashield(player);
 			return false;
+		}
 		else
 		{
 			if (inflictor && (inflictor->type == MT_ORBINAUT || inflictor->type == MT_ORBINAUT_SHIELD
diff --git a/src/p_map.c b/src/p_map.c
index e50fbb2c..52ab6154 100644
--- a/src/p_map.c
+++ b/src/p_map.c
@@ -1633,13 +1633,6 @@ static boolean PIT_CheckThing(mobj_t *thing)
 		}
 		else if (thing->player) // bounce when players collide
 		{
-			const boolean tvulnerable = (!(thing->player->powers[pw_flashing]
-				|| thing->player->kartstuff[k_invincibilitytimer]
-				|| thing->player->kartstuff[k_spinouttimer]));
-			const boolean tmtvulnerable = (!(tmthing->player->powers[pw_flashing]
-				|| tmthing->player->kartstuff[k_invincibilitytimer]
-				|| tmthing->player->kartstuff[k_spinouttimer]));
-
 			// see if it went over / under
 			if (tmthing->z > thing->z + thing->height)
 				return true; // overhead
@@ -1659,7 +1652,7 @@ static boolean PIT_CheckThing(mobj_t *thing)
 			if (P_IsObjectOnGround(thing) && tmthing->momz < 0)
 			{
 				K_KartBouncing(tmthing, thing, true, false);
-				if (G_BattleGametype() && tmthing->player->kartstuff[k_pogospring] && tvulnerable)
+				if (G_BattleGametype() && tmthing->player->kartstuff[k_pogospring])
 				{
 					K_StealBumper(tmthing->player, thing->player, false);
 					K_SpinPlayer(thing->player, tmthing, 0, false);
@@ -1668,7 +1661,7 @@ static boolean PIT_CheckThing(mobj_t *thing)
 			else if (P_IsObjectOnGround(tmthing) && thing->momz < 0)
 			{
 				K_KartBouncing(thing, tmthing, true, false);
-				if (G_BattleGametype() && thing->player->kartstuff[k_pogospring] && tmtvulnerable)
+				if (G_BattleGametype() && thing->player->kartstuff[k_pogospring])
 				{
 					K_StealBumper(thing->player, tmthing->player, false);
 					K_SpinPlayer(tmthing->player, thing, 0, false);
@@ -1679,12 +1672,12 @@ static boolean PIT_CheckThing(mobj_t *thing)
 
 			if (G_BattleGametype())
 			{
-				if (thing->player->kartstuff[k_sneakertimer] && !(tmthing->player->kartstuff[k_sneakertimer]) && tmtvulnerable)
+				if (thing->player->kartstuff[k_sneakertimer] && !(tmthing->player->kartstuff[k_sneakertimer]))
 				{
 					K_StealBumper(thing->player, tmthing->player, false);
 					K_SpinPlayer(tmthing->player, thing, 0, false);
 				}
-				else if (tmthing->player->kartstuff[k_sneakertimer] && !(thing->player->kartstuff[k_sneakertimer]) && tvulnerable)
+				else if (tmthing->player->kartstuff[k_sneakertimer] && !(thing->player->kartstuff[k_sneakertimer]))
 				{
 					K_StealBumper(tmthing->player, thing->player, false);
 					K_SpinPlayer(thing->player, tmthing, 0, false);
diff --git a/src/p_mobj.c b/src/p_mobj.c
index d7ecbcae..4ef5cff0 100644
--- a/src/p_mobj.c
+++ b/src/p_mobj.c
@@ -8200,6 +8200,20 @@ void P_MobjThinker(mobj_t *mobj)
 			}
 			P_TeleportMove(mobj, mobj->target->x, mobj->target->y, mobj->target->z);
 			break;
+		case MT_INSTASHIELDB:
+			if (leveltime & 1)
+				mobj->flags2 |= MF2_DONTDRAW;
+			else
+				mobj->flags2 &= ~MF2_DONTDRAW;
+			/* FALLTHRU */
+		case MT_INSTASHIELDA:
+			if (!mobj->target || !mobj->target->health || (mobj->target->player && !mobj->target->player->kartstuff[k_instashield]))
+			{
+				P_RemoveMobj(mobj);
+				return;
+			}
+			P_TeleportMove(mobj, mobj->target->x, mobj->target->y, mobj->target->z);
+			break;
 		case MT_KARMAHITBOX:
 			if (!mobj->target || !mobj->target->health || !mobj->target->player || mobj->target->player->spectator
 				|| (G_RaceGametype() || mobj->target->player->kartstuff[k_bumper]))
diff --git a/src/sounds.c b/src/sounds.c
index ffe0e479..60cbb676 100644
--- a/src/sounds.c
+++ b/src/sounds.c
@@ -695,7 +695,7 @@ sfxinfo_t S_sfx[NUMSFX] =
   {"cdpcm6", false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR},
   {"cdpcm7", false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR},
   {"cdpcm8", false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR},
-  {"cdpcm9", false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR},
+  {"cdpcm9", false,  64,  8, -1, NULL, 0,        -1,  -1, LUMPERROR}, // No damage taken
 
   // Knuckles Chaotix sounds
   {"kc2a",   false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR},

From 22a87e5926bb13c2bbe89fff3ccd72552de96c38 Mon Sep 17 00:00:00 2001
From: TehRealSalt <tehrealsalt@gmail.com>
Date: Fri, 27 Jul 2018 17:08:45 -0400
Subject: [PATCH 10/38] Experimental air speed cap

Made very easy to revert if needed. tbh I can hardly notice it unless if I pay attention, and it most definitely never happens without Grow
---
 src/k_kart.c | 3 +++
 src/p_user.c | 2 +-
 2 files changed, 4 insertions(+), 1 deletion(-)

diff --git a/src/k_kart.c b/src/k_kart.c
index 41af9754..90743918 100644
--- a/src/k_kart.c
+++ b/src/k_kart.c
@@ -1460,6 +1460,9 @@ fixed_t K_GetKartSpeed(player_t *player, boolean doboostpower)
 	UINT8 kartspeed = player->kartspeed;
 	fixed_t finalspeed;
 
+	if (doboostpower && !player->kartstuff[k_pogospring] && !P_IsObjectOnGround(player->mo))
+		return (75*mapheaderinfo[gamemap-1]->mobj_scale); // air speed cap
+
 	switch (gamespeed)
 	{
 		case 0:
diff --git a/src/p_user.c b/src/p_user.c
index 106b3645..9d196268 100644
--- a/src/p_user.c
+++ b/src/p_user.c
@@ -4901,7 +4901,7 @@ static void P_3dMovement(player_t *player)
 	if (newMagnitude > K_GetKartSpeed(player, true)) //topspeed)
 	{
 		fixed_t tempmomx, tempmomy;
-		if (oldMagnitude > K_GetKartSpeed(player, true)) //topspeed)
+		if (oldMagnitude > K_GetKartSpeed(player, true) && onground) // SRB2Kart: onground check for air speed cap
 		{
 			if (newMagnitude > oldMagnitude)
 			{

From ca115c1d71ad11ab50752d9ff39a78454f55933c Mon Sep 17 00:00:00 2001
From: TehRealSalt <tehrealsalt@gmail.com>
Date: Fri, 27 Jul 2018 17:22:51 -0400
Subject: [PATCH 11/38] 15 tic delay on first instashield

---
 src/k_kart.c | 6 +++++-
 1 file changed, 5 insertions(+), 1 deletion(-)

diff --git a/src/k_kart.c b/src/k_kart.c
index 90743918..2628db2c 100644
--- a/src/k_kart.c
+++ b/src/k_kart.c
@@ -1568,7 +1568,7 @@ void K_DoInstashield(player_t *player)
 	if (player->kartstuff[k_instashield] > 0)
 		return;
 
-	player->kartstuff[k_instashield] = 14; // length of instashield animation
+	player->kartstuff[k_instashield] = 15; // length of instashield animation
 	S_StartSound(player->mo, sfx_cdpcm9);
 
 	layera = P_SpawnMobj(player->mo->x, player->mo->y, player->mo->z, MT_INSTASHIELDA);
@@ -1651,6 +1651,7 @@ void K_SpinPlayer(player_t *player, mobj_t *source, INT32 type, boolean trapitem
 	if (player->mo->state != &states[S_KART_SPIN])
 		P_SetPlayerMobjState(player->mo, S_KART_SPIN);
 
+	player->kartstuff[k_instashield] = 15;
 	return;
 }
 
@@ -1712,6 +1713,7 @@ void K_SquishPlayer(player_t *player, mobj_t *source)
 
 	P_PlayRinglossSound(player->mo);
 
+	player->kartstuff[k_instashield] = 15;
 	return;
 }
 
@@ -1781,6 +1783,7 @@ void K_ExplodePlayer(player_t *player, mobj_t *source) // A bit of a hack, we ju
 		quake.time = 5;
 	}
 
+	player->kartstuff[k_instashield] = 15;
 	return;
 }
 
@@ -1855,6 +1858,7 @@ void K_StealBumper(player_t *player, player_t *victim, boolean force)
 	/*victim->powers[pw_flashing] = K_GetKartFlashing();
 	victim->kartstuff[k_comebacktimer] = comebacktime;*/
 
+	victim->kartstuff[k_instashield] = 15;
 	return;
 }
 

From 68aa98eb426f1ac7bec081f195ba663af959387e Mon Sep 17 00:00:00 2001
From: TehRealSalt <tehrealsalt@gmail.com>
Date: Fri, 27 Jul 2018 17:56:10 -0400
Subject: [PATCH 12/38] Better Grow odds

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

diff --git a/src/k_kart.c b/src/k_kart.c
index 2628db2c..f7288537 100644
--- a/src/k_kart.c
+++ b/src/k_kart.c
@@ -464,8 +464,8 @@ static INT32 K_KartItemOddsRace[NUMKARTRESULTS][9] =
 {
 				//P-Odds	 0  1  2  3  4  5  6  7  8
 			   /*Sneaker*/ {20, 0, 0, 3, 6, 5, 0, 0, 0 }, // Sneaker
-		/*Rocket Sneaker*/ { 0, 0, 0, 0, 0, 3, 6, 5, 0 }, // Rocket Sneaker
-		 /*Invincibility*/ { 0, 0, 0, 0, 0, 1, 6, 8,16 }, // Invincibility
+		/*Rocket Sneaker*/ { 0, 0, 0, 0, 0, 2, 5, 4, 0 }, // Rocket Sneaker
+		 /*Invincibility*/ { 0, 0, 0, 0, 0, 1, 5, 6,16 }, // Invincibility
 				/*Banana*/ { 0, 9, 4, 2, 1, 0, 0, 0, 0 }, // Banana
 		/*Eggman Monitor*/ { 0, 4, 3, 2, 0, 0, 0, 0, 0 }, // Eggman Monitor
 			  /*Orbinaut*/ { 0, 6, 5, 4, 2, 0, 0, 0, 0 }, // Orbinaut
@@ -473,13 +473,13 @@ static INT32 K_KartItemOddsRace[NUMKARTRESULTS][9] =
 				  /*Mine*/ { 0, 0, 1, 2, 1, 0, 0, 0, 0 }, // Mine
 			   /*Ballhog*/ { 0, 0, 1, 2, 1, 0, 0, 0, 0 }, // Ballhog
    /*Self-Propelled Bomb*/ { 0, 0, 1, 1, 1, 2, 2, 3, 2 }, // Self-Propelled Bomb
-				  /*Grow*/ { 0, 0, 0, 0, 0, 0, 1, 2, 4 }, // Grow
+				  /*Grow*/ { 0, 0, 0, 0, 0, 2, 4, 6, 4 }, // Grow
 				/*Shrink*/ { 0, 0, 0, 0, 0, 0, 0, 1, 0 }, // Shrink
 	  /*Lightning Shield*/ { 0, 1, 2, 0, 0, 0, 0, 0, 0 }, // Lightning Shield
 			   /*Hyudoro*/ { 0, 0, 0, 0, 1, 2, 1, 0, 0 }, // Hyudoro
 		   /*Pogo Spring*/ { 0, 0, 0, 0, 0, 0, 0, 0, 0 }, // Pogo Spring
 		  /*Kitchen Sink*/ { 0, 0, 0, 0, 0, 0, 0, 0, 0 }, // Kitchen Sink
-			/*Sneaker x3*/ { 0, 0, 0, 0, 3, 7, 6, 4, 0 }, // Sneaker x3
+			/*Sneaker x3*/ { 0, 0, 0, 0, 3, 6, 5, 3, 0 }, // Sneaker x3
 			 /*Banana x3*/ { 0, 0, 1, 1, 0, 0, 0, 0, 0 }, // Banana x3
 			/*Banana x10*/ { 0, 0, 0, 0, 1, 0, 0, 0, 0 }, // Banana x10
 		   /*Orbinaut x3*/ { 0, 0, 0, 1, 1, 1, 0, 0, 0 }, // Orbinaut x3

From 8c8d7127f6827ceecde1c5a514982a0385c988be Mon Sep 17 00:00:00 2001
From: TehRealSalt <tehrealsalt@gmail.com>
Date: Fri, 27 Jul 2018 18:04:27 -0400
Subject: [PATCH 13/38] Actually, add another point here, too~

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

diff --git a/src/k_kart.c b/src/k_kart.c
index f7288537..c190deef 100644
--- a/src/k_kart.c
+++ b/src/k_kart.c
@@ -463,7 +463,7 @@ boolean K_IsPlayerWanted(player_t *player)
 static INT32 K_KartItemOddsRace[NUMKARTRESULTS][9] =
 {
 				//P-Odds	 0  1  2  3  4  5  6  7  8
-			   /*Sneaker*/ {20, 0, 0, 3, 6, 5, 0, 0, 0 }, // Sneaker
+			   /*Sneaker*/ {20, 0, 0, 3, 5, 5, 0, 0, 0 }, // Sneaker
 		/*Rocket Sneaker*/ { 0, 0, 0, 0, 0, 2, 5, 4, 0 }, // Rocket Sneaker
 		 /*Invincibility*/ { 0, 0, 0, 0, 0, 1, 5, 6,16 }, // Invincibility
 				/*Banana*/ { 0, 9, 4, 2, 1, 0, 0, 0, 0 }, // Banana
@@ -473,7 +473,7 @@ static INT32 K_KartItemOddsRace[NUMKARTRESULTS][9] =
 				  /*Mine*/ { 0, 0, 1, 2, 1, 0, 0, 0, 0 }, // Mine
 			   /*Ballhog*/ { 0, 0, 1, 2, 1, 0, 0, 0, 0 }, // Ballhog
    /*Self-Propelled Bomb*/ { 0, 0, 1, 1, 1, 2, 2, 3, 2 }, // Self-Propelled Bomb
-				  /*Grow*/ { 0, 0, 0, 0, 0, 2, 4, 6, 4 }, // Grow
+				  /*Grow*/ { 0, 0, 0, 0, 1, 2, 4, 6, 4 }, // Grow
 				/*Shrink*/ { 0, 0, 0, 0, 0, 0, 0, 1, 0 }, // Shrink
 	  /*Lightning Shield*/ { 0, 1, 2, 0, 0, 0, 0, 0, 0 }, // Lightning Shield
 			   /*Hyudoro*/ { 0, 0, 0, 0, 1, 2, 1, 0, 0 }, // Hyudoro

From 25c344a6e895d0a39ca34cdcf5b73c237af2eb28 Mon Sep 17 00:00:00 2001
From: TehRealSalt <tehrealsalt@gmail.com>
Date: Fri, 27 Jul 2018 21:59:00 -0400
Subject: [PATCH 14/38] A lot more safety checks on the voting screen for
 spectators

- A new variable, voteclient.loaded, for keeping track of whenever or not voting data has been set up or not. Gets set to true in Y_StartVote, false in Y_UnloadVoteData. This is used to prevent drawing the screen in cases where it would crash, and preventing duplicate Y_EndVote calls.
- The game checks for all spectator when transitioning to vote, to decide whenever or not it should skip it entirely or not.
- Unrelated: made the roulette cheating much more common. Hopefully it's as cheaty as Mario Party now :p
---
 src/g_game.c  | 15 +++++++++++++++
 src/y_inter.c | 42 ++++++++++++++++++++++++++++++------------
 2 files changed, 45 insertions(+), 12 deletions(-)

diff --git a/src/g_game.c b/src/g_game.c
index a652e83f..fd2e92d4 100644
--- a/src/g_game.c
+++ b/src/g_game.c
@@ -3434,8 +3434,23 @@ void G_AfterIntermission(void)
 //
 void G_NextLevel(void)
 {
+	boolean dovote = false;
+
 	if ((cv_advancemap.value == 3 && gamestate != GS_VOTING)
 		&& !modeattacking && !skipstats && (multiplayer || netgame))
+	{
+		UINT8 i;
+		for (i = 0; i < MAXPLAYERS; i++)
+		{
+			if (playeringame[i] && !players[i].spectator)
+			{
+				dovote = true;
+				break;
+			}
+		}
+	}
+	
+	if (dovote)
 		gameaction = ga_startvote;
 	else
 	{
diff --git a/src/y_inter.c b/src/y_inter.c
index 4c5d9e26..65419789 100644
--- a/src/y_inter.c
+++ b/src/y_inter.c
@@ -142,6 +142,7 @@ typedef struct
 	UINT8 roffset;
 	UINT8 rsynctime;
 	UINT8 rendoff;
+	boolean loaded;
 } y_voteclient;
 
 static y_votelvlinfo levelinfo[5];
@@ -954,6 +955,9 @@ void Y_VoteDrawer(void)
 
 	V_DrawFill(0, 0, BASEVIDWIDTH, BASEVIDHEIGHT, 31);
 
+	if (!voteclient.loaded)
+		return;
+
 	if (widebgpatch && rendermode == render_soft && vid.width / vid.dupx > 320)
 		V_DrawScaledPatch(((vid.width/2) / vid.dupx) - (SHORT(widebgpatch->width)/2),
 							(vid.height / vid.dupy) - SHORT(widebgpatch->height),
@@ -1200,8 +1204,11 @@ void Y_VoteTicker(void)
 
 	if (votetic == voteendtic)
 	{
-		Y_UnloadVoteData(); // Y_EndVote resets voteendtic too early apparently, causing the game to try to render patches that we just unloaded...
-		Y_FollowIntermission();
+		if (voteclient.loaded)
+		{
+			Y_EndVote();
+			Y_FollowIntermission();
+		}
 		return;
 	}
 
@@ -1242,14 +1249,17 @@ void Y_VoteTicker(void)
 
 			if (numvotes < 1) // Whoops! Get outta here.
 			{
-				Y_UnloadVoteData();
-				Y_FollowIntermission();
+				if (voteclient.loaded)
+				{
+					Y_EndVote();
+					Y_FollowIntermission();
+				}
 				return;
 			}
 
 			voteclient.rtics--;
 
-			if (voteclient.rtics <= 0)
+			if (voteclient.rtics <= 0 && voteclient.loaded)
 			{
 				voteclient.roffset++;
 				voteclient.rtics = min(20, (3*voteclient.roffset/4)+5);
@@ -1270,7 +1280,7 @@ void Y_VoteTicker(void)
 							if (tempvotes[((pickedvote + voteclient.roffset + i) % numvotes)] == pickedvote)
 							{
 								voteclient.rendoff = voteclient.roffset+i;
-								if (M_RandomChance(FRACUNIT/1024)) // Let it cheat occasionally~
+								if (M_RandomChance(FRACUNIT/32)) // Let it cheat occasionally~
 									voteclient.rendoff++;
 								S_ChangeMusicInternal("voteeb", false);
 								break;
@@ -1474,6 +1484,8 @@ void Y_StartVote(void)
 		else
 			levelinfo[i].pic = W_CachePatchName("BLANKLVL", PU_STATIC);
 	}
+
+	voteclient.loaded = true;
 }
 
 //
@@ -1493,6 +1505,8 @@ static void Y_UnloadVoteData(void)
 	if (rendermode != render_soft)
 		return;
 
+	voteclient.loaded = false;
+
 	UNLOAD(widebgpatch);
 	UNLOAD(bgpatch);
 	UNLOAD(cursor);
@@ -1516,9 +1530,11 @@ void Y_SetupVoteFinish(SINT8 pick, SINT8 level)
 {
 	if (pick == -1) // No other votes? We gotta get out of here, then!
 	{
-		timer = 0;
-		Y_UnloadVoteData();
-		Y_FollowIntermission();
+		if (voteclient.loaded)
+		{
+			Y_EndVote();
+			Y_FollowIntermission();
+		}
 		return;
 	}
 
@@ -1564,9 +1580,11 @@ void Y_SetupVoteFinish(SINT8 pick, SINT8 level)
 		}
 		else if (endtype == 0) // Might as well put this here, too.
 		{
-			timer = 0;
-			Y_UnloadVoteData();
-			Y_FollowIntermission();
+			if (voteclient.loaded)
+			{
+				Y_EndVote();
+				Y_FollowIntermission();
+			}
 			return;
 		}
 		else

From 7073c9ede2a09e7dfc0208d902c3757bd6d2ff35 Mon Sep 17 00:00:00 2001
From: TehRealSalt <tehrealsalt@gmail.com>
Date: Fri, 27 Jul 2018 22:16:56 -0400
Subject: [PATCH 15/38] Use joysticks 1-4 by default, restore KEY_BACKSPACE
 functionality

These are both huge boons for gamepad functionality. If we can add a way to add a menu key, then all of the controller requirements for v1 will be covered.
---
 src/d_netcmd.c | 17 +++--------------
 src/m_menu.c   |  7 ++++---
 2 files changed, 7 insertions(+), 17 deletions(-)

diff --git a/src/d_netcmd.c b/src/d_netcmd.c
index e0a31408..6d4aa65a 100644
--- a/src/d_netcmd.c
+++ b/src/d_netcmd.c
@@ -273,26 +273,15 @@ INT32 cv_debug;
 consvar_t cv_usemouse = {"use_mouse", "Off", CV_SAVE|CV_CALL,usemouse_cons_t, I_StartupMouse, 0, NULL, NULL, 0, 0, NULL};
 consvar_t cv_usemouse2 = {"use_mouse2", "Off", CV_SAVE|CV_CALL,usemouse_cons_t, I_StartupMouse2, 0, NULL, NULL, 0, 0, NULL};
 
-#if defined (DC) || defined (_XBOX) || defined (WMINPUT) || defined (_WII) //joystick 1 and 2
 consvar_t cv_usejoystick = {"use_joystick", "1", CV_SAVE|CV_CALL, usejoystick_cons_t,
 	I_InitJoystick, 0, NULL, NULL, 0, 0, NULL};
 consvar_t cv_usejoystick2 = {"use_joystick2", "2", CV_SAVE|CV_CALL, usejoystick_cons_t,
 	I_InitJoystick2, 0, NULL, NULL, 0, 0, NULL};
-#elif defined (PSP) || defined (GP2X) || defined (_NDS) //only one joystick
-consvar_t cv_usejoystick = {"use_joystick", "1", CV_SAVE|CV_CALL, usejoystick_cons_t,
-	I_InitJoystick, 0, NULL, NULL, 0, 0, NULL};
-consvar_t cv_usejoystick2 = {"use_joystick2", "0", CV_SAVE|CV_CALL, usejoystick_cons_t,
-	I_InitJoystick2, 0, NULL, NULL, 0, 0, NULL};
-#else //all esle, no joystick
-consvar_t cv_usejoystick = {"use_joystick", "0", CV_SAVE|CV_CALL, usejoystick_cons_t,
-	I_InitJoystick, 0, NULL, NULL, 0, 0, NULL};
-consvar_t cv_usejoystick2 = {"use_joystick2", "0", CV_SAVE|CV_CALL, usejoystick_cons_t,
-	I_InitJoystick2, 0, NULL, NULL, 0, 0, NULL};
-consvar_t cv_usejoystick3 = {"use_joystick3", "0", CV_SAVE|CV_CALL, usejoystick_cons_t,
+consvar_t cv_usejoystick3 = {"use_joystick3", "3", CV_SAVE|CV_CALL, usejoystick_cons_t,
 	I_InitJoystick3, 0, NULL, NULL, 0, 0, NULL};
-consvar_t cv_usejoystick4 = {"use_joystick4", "0", CV_SAVE|CV_CALL, usejoystick_cons_t,
+consvar_t cv_usejoystick4 = {"use_joystick4", "4", CV_SAVE|CV_CALL, usejoystick_cons_t,
 	I_InitJoystick4, 0, NULL, NULL, 0, 0, NULL};
-#endif
+
 #if (defined (LJOYSTICK) || defined (HAVE_SDL))
 #ifdef LJOYSTICK
 consvar_t cv_joyport = {"joyport", "/dev/js0", CV_SAVE, joyport_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL};
diff --git a/src/m_menu.c b/src/m_menu.c
index 48857e20..1ee24f47 100644
--- a/src/m_menu.c
+++ b/src/m_menu.c
@@ -2687,9 +2687,10 @@ boolean M_Responder(event_t *ev)
 			}
 
 			// Why _does_ backspace go back anyway?
-			//currentMenu->lastOn = itemOn;
-			//if (currentMenu->prevMenu)
-			//	M_SetupNextMenu(currentMenu->prevMenu);
+			// Sal: Because it supports gamepads better. And still makes sense for keyboard.
+			currentMenu->lastOn = itemOn;
+			if (currentMenu->prevMenu)
+				M_SetupNextMenu(currentMenu->prevMenu);
 			return false;
 
 		default:

From a80c6ccf3977efe9d56685e2be5217e51b68f1a1 Mon Sep 17 00:00:00 2001
From: TehRealSalt <tehrealsalt@gmail.com>
Date: Fri, 27 Jul 2018 22:21:13 -0400
Subject: [PATCH 16/38] Fix basenumlaps prints

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

diff --git a/src/d_netcmd.c b/src/d_netcmd.c
index 6d4aa65a..167457d1 100644
--- a/src/d_netcmd.c
+++ b/src/d_netcmd.c
@@ -5194,9 +5194,9 @@ static void BaseNumLaps_OnChange(void)
 	if (gamestate == GS_LEVEL)
 	{
 		if (cv_basenumlaps.value)
-			CONS_Printf(M_GetText("Number of laps will be changed to map defaults next round.\n"));
-		else
 			CONS_Printf(M_GetText("Number of laps will be changed to %d next round.\n"), cv_basenumlaps.value);
+		else
+			CONS_Printf(M_GetText("Number of laps will be changed to map defaults next round.\n"));
 	}
 }
 

From f0f9e204aa8594c751b65b9fc0bf972c11167c76 Mon Sep 17 00:00:00 2001
From: TehRealSalt <tehrealsalt@gmail.com>
Date: Sat, 28 Jul 2018 02:37:00 -0400
Subject: [PATCH 17/38] Reverse gravity fixes pt 1: basic driving

- Turning dust is spawned in the correct position
- Turning dust sprites are flipped
- Shadows display on the ceiling for flipped objects
- Drift sparks display in the correct position for flipped players

Will continue with item fixes tomorrow
---
 src/k_kart.c |  9 +++++++++
 src/p_mobj.c | 22 ++++++++++++++--------
 2 files changed, 23 insertions(+), 8 deletions(-)

diff --git a/src/k_kart.c b/src/k_kart.c
index c190deef..081a9492 100644
--- a/src/k_kart.c
+++ b/src/k_kart.c
@@ -2264,6 +2264,10 @@ void K_DriftDustHandling(mobj_t *spawner)
 		fixed_t spawny = P_RandomRange(-spawnrange, spawnrange)<<FRACBITS;
 		INT32 speedrange = 2;
 		mobj_t *dust = P_SpawnMobj(spawner->x + spawnx, spawner->y + spawny, spawner->z, MT_DRIFTDUST);
+		if (spawner->eflags & MFE_VERTICALFLIP)
+		{
+			dust->z += spawner->height - dust->height;
+		}
 		dust->momx = FixedMul(spawner->momx + (P_RandomRange(-speedrange, speedrange)<<FRACBITS), 3*FRACUNIT/4);
 		dust->momy = FixedMul(spawner->momy + (P_RandomRange(-speedrange, speedrange)<<FRACBITS), 3*FRACUNIT/4);
 		dust->momz = P_MobjFlip(spawner) * P_RandomRange(1, 4)<<FRACBITS;
@@ -2279,6 +2283,11 @@ void K_DriftDustHandling(mobj_t *spawner)
 		else
 			dust->flags2 &= ~MF2_DONTDRAW;
 
+		if (spawner->eflags & MFE_VERTICALFLIP)
+			dust->eflags |= MFE_VERTICALFLIP;
+		else
+			dust->eflags &= ~MFE_VERTICALFLIP;
+
 		if (spawner->eflags & MFE_DRAWONLYFORP1)
 			dust->eflags |= MFE_DRAWONLYFORP1;
 		else
diff --git a/src/p_mobj.c b/src/p_mobj.c
index 4ef5cff0..9cf78ebf 100644
--- a/src/p_mobj.c
+++ b/src/p_mobj.c
@@ -6291,6 +6291,11 @@ void P_RunShadows(void)
 		else
 			mobj->flags2 &= ~MF2_DONTDRAW;
 
+		if (mobj->target->eflags & MFE_VERTICALFLIP)
+			mobj->eflags |= MFE_VERTICALFLIP;
+		else
+			mobj->eflags &= ~MFE_VERTICALFLIP;
+
 		if (mobj->target->eflags & MFE_DRAWONLYFORP1) // groooooaann...
 			mobj->eflags |= MFE_DRAWONLYFORP1;
 		else
@@ -6316,12 +6321,13 @@ void P_RunShadows(void)
 
 		P_TeleportMove(mobj, mobj->target->x, mobj->target->y, mobj->target->z);
 
-		if (mobj->floorz < mobj->z)
+		if (((mobj->eflags & MFE_VERTICALFLIP) && (mobj->ceilingz > mobj->z+mobj->height))
+			|| (!(mobj->eflags & MFE_VERTICALFLIP) && (mobj->floorz < mobj->z)))
 		{
 			INT32 i;
 			fixed_t prevz;
 
-			mobj->z = mobj->floorz;
+			mobj->z = (mobj->eflags & MFE_VERTICALFLIP ? mobj->ceilingz : mobj->floorz);
 
 			for (i = 0; i < MAXFFLOORS; i++)
 			{
@@ -6333,7 +6339,7 @@ void P_RunShadows(void)
 				// Check new position to see if you should still be on that ledge
 				P_TeleportMove(mobj, mobj->target->x, mobj->target->y, mobj->z);
 
-				mobj->z = mobj->floorz;
+				mobj->z = (mobj->eflags & MFE_VERTICALFLIP ? mobj->ceilingz : mobj->floorz);
 
 				if (mobj->z == prevz)
 					break;
@@ -6609,7 +6615,7 @@ void P_MobjThinker(mobj_t *mobj)
 			{
 				if (mobj->target && mobj->target->player && mobj->target->player->mo && mobj->target->player->health > 0 && !mobj->target->player->spectator)
 				{
-					INT32 HEIGHT;
+					fixed_t HEIGHT;
 					fixed_t radius;
 
 					fixed_t dsone = K_GetKartDriftSparkValue(mobj->target->player);
@@ -6679,15 +6685,15 @@ void P_MobjThinker(mobj_t *mobj)
 						mobj->angle = ANGLE_180 + mobj->target->player->frameangle;
 
 					// If the player is on the ceiling, then flip
-					if (mobj->target->player && mobj->target->eflags & MFE_VERTICALFLIP)
+					if (mobj->target->eflags & MFE_VERTICALFLIP)
 					{
 						mobj->eflags |= MFE_VERTICALFLIP;
-						HEIGHT = mobj->target->height;
+						HEIGHT = (16<<FRACBITS);
 					}
 					else
 					{
 						mobj->eflags &= ~MFE_VERTICALFLIP;
-						HEIGHT = mobj->target->height-mobj->target->height;
+						HEIGHT = 0;
 					}
 
 					// Shrink if the player shrunk too.
@@ -6698,7 +6704,7 @@ void P_MobjThinker(mobj_t *mobj)
 						const angle_t fa = mobj->angle>>ANGLETOFINESHIFT;
 						mobj->x = mobj->target->x + FixedMul(finecosine[fa],radius);
 						mobj->y = mobj->target->y + FixedMul(finesine[fa],radius);
-						mobj->z = mobj->target->z + HEIGHT;
+						mobj->z = mobj->target->z - HEIGHT;
 						P_SetThingPosition(mobj);
 					}
 				}

From 62c6bc0a13f3984e1da06efde55bc0ef311da475 Mon Sep 17 00:00:00 2001
From: toaster <rollerorbital@gmail.com>
Date: Sun, 29 Jul 2018 01:04:43 +0100
Subject: [PATCH 18/38] Change brightness of buffer fade in intermission
 (Oni-approved).

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

diff --git a/src/y_inter.c b/src/y_inter.c
index 65419789..a77c12a0 100644
--- a/src/y_inter.c
+++ b/src/y_inter.c
@@ -296,7 +296,7 @@ void Y_IntermissionDrawer(void)
 		V_DrawPatchFill(bgtile);
 
 	if (usebuffer) // Fade everything out
-		V_DrawFadeScreen(0xFF00, 16);
+		V_DrawFadeScreen(0xFF00, 20);
 
 	if (!splitscreen)
 		whiteplayer = demoplayback ? displayplayer : consoleplayer;

From f1255730485d42f1a5b62f3a26605c65faf22d8b Mon Sep 17 00:00:00 2001
From: toaster <rollerorbital@gmail.com>
Date: Sun, 29 Jul 2018 18:35:56 +0100
Subject: [PATCH 19/38] Some minor things to help draw sonicitems to a close. *
 "kartvoices" cvar. Possible values "Never", "Tasteful" (default), and "Meme".
 * Added a way to move quickly through the credits, rather than skip them
 entirely (hold spacebar or down arrow). * Fix a few mistakes in M_ChangeCvar,
 some of which I introduced and some of which were weird in the first place. *
 Tweak the offset of the arrows that let you know you can modify a cvar by
 pressing left or right (some via a patch.kart change, but others via tweaking
 the drawing location).

---
 src/d_netcmd.c |  2 ++
 src/d_netcmd.h |  1 +
 src/f_finale.c | 17 ++++++++++++-----
 src/k_kart.c   | 23 ++++++++++++++++-------
 src/m_menu.c   | 33 ++++++++++++++++++---------------
 src/p_user.c   | 11 +++++++----
 6 files changed, 56 insertions(+), 31 deletions(-)

diff --git a/src/d_netcmd.c b/src/d_netcmd.c
index 167457d1..f7954747 100644
--- a/src/d_netcmd.c
+++ b/src/d_netcmd.c
@@ -355,6 +355,8 @@ consvar_t cv_kartcomeback = {"kartcomeback", "On", CV_NETVAR|CV_CHEAT|CV_CALL|CV
 consvar_t cv_kartmirror = {"kartmirror", "Off", CV_NETVAR|CV_CHEAT|CV_CALL|CV_NOINIT, CV_OnOff, KartMirror_OnChange, 0, NULL, NULL, 0, 0, NULL};
 static CV_PossibleValue_t kartspeedometer_cons_t[] = {{0, "Off"}, {1, "Kilometers"}, {2, "Miles"}, {3, "Fracunits"}, {0, NULL}};
 consvar_t cv_kartspeedometer = {"kartdisplayspeed", "Off", CV_SAVE, kartspeedometer_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL}; // use tics in display
+static CV_PossibleValue_t kartvoices_cons_t[] = {{0, "Never"}, {1, "Tasteful"}, {2, "Meme"}, {0, NULL}};
+consvar_t cv_kartvoices = {"kartvoices", "Tasteful", CV_SAVE, kartvoices_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL};
 
 // this might be a debug or it might be an undocumented regular feature
 consvar_t cv_karteliminatelast = {"karteliminatelast", "Yes", CV_NETVAR|CV_CHEAT|CV_CALL, CV_OnOff, KartEliminateLast_OnChange, 0, NULL, NULL, 0, 0, NULL};
diff --git a/src/d_netcmd.h b/src/d_netcmd.h
index 009563af..371df720 100644
--- a/src/d_netcmd.h
+++ b/src/d_netcmd.h
@@ -126,6 +126,7 @@ extern consvar_t cv_kartfrantic;
 extern consvar_t cv_kartcomeback;
 extern consvar_t cv_kartmirror;
 extern consvar_t cv_kartspeedometer;
+extern consvar_t cv_kartvoices;
 
 extern consvar_t cv_karteliminatelast;
 
diff --git a/src/f_finale.c b/src/f_finale.c
index e10ba1a6..ab79fa78 100644
--- a/src/f_finale.c
+++ b/src/f_finale.c
@@ -625,7 +625,7 @@ void F_CreditDrawer(void)
 			y += 12<<FRACBITS;
 			break;
 		}
-		if (FixedMul(y,vid.dupy) > vid.height)
+		if (((y>>FRACBITS) * vid.dupy) > vid.height)
 			break;
 	}
 
@@ -686,13 +686,20 @@ boolean F_CreditResponder(event_t *event)
 			break;
 	}
 
-	/*if (!(timesBeaten) && !(netgame || multiplayer))
-		return false;*/
-
 	if (event->type != ev_keydown)
 		return false;
 
-	if (key != KEY_ESCAPE && key != KEY_ENTER && key != KEY_SPACE && key != KEY_BACKSPACE)
+	if (key == KEY_DOWNARROW || key == KEY_SPACE)
+	{
+		if (!timetonext && !finalecount)
+			animtimer += 7;
+		return false;
+	}
+
+	/*if (!(timesBeaten) && !(netgame || multiplayer))
+		return false;*/
+
+	if (key != KEY_ESCAPE && key != KEY_ENTER && key != KEY_BACKSPACE)
 		return false;
 
 	if (keypressed)
diff --git a/src/k_kart.c b/src/k_kart.c
index 081a9492..dc1f52ed 100644
--- a/src/k_kart.c
+++ b/src/k_kart.c
@@ -403,6 +403,7 @@ void K_RegisterKartStuff(void)
 	CV_RegisterVar(&cv_kartcomeback);
 	CV_RegisterVar(&cv_kartmirror);
 	CV_RegisterVar(&cv_kartspeedometer);
+	CV_RegisterVar(&cv_kartvoices);
 	CV_RegisterVar(&cv_karteliminatelast);
 	CV_RegisterVar(&cv_votetime);
 
@@ -1338,18 +1339,21 @@ static void K_RegularVoiceTimers(player_t *player)
 
 static void K_PlayTauntSound(mobj_t *source)
 {
-	if (source->player && source->player->kartstuff[k_tauntvoices]) // Prevents taunt sounds from playing every time the button is pressed
-		return;
+	sfxenum_t pick = P_RandomKey(4); // Gotta roll the RNG every time this is called for sync reasons
+	boolean tasteful = (!source->player || !source->player->kartstuff[k_tauntvoices]);
 
-	S_StartSound(source, sfx_taunt1+P_RandomKey(4));
+	if (cv_kartvoices.value && (tasteful || cv_kartvoices.value == 2))
+		S_StartSound(source, sfx_taunt1+pick);
+
+	if (!tasteful)
+		return;
 
 	K_TauntVoiceTimers(source->player);
 }
 
 static void K_PlayOvertakeSound(mobj_t *source)
 {
-	if (source->player && source->player->kartstuff[k_voices]) // Prevents taunt sounds from playing every time the button is pressed
-		return;
+	boolean tasteful = (!source->player || !source->player->kartstuff[k_voices]);
 
 	if (!G_RaceGametype()) // Only in race
 		return;
@@ -1358,14 +1362,19 @@ static void K_PlayOvertakeSound(mobj_t *source)
 	if (leveltime < starttime+(10*TICRATE))
 		return;
 
-	S_StartSound(source, sfx_slow);
+	if (cv_kartvoices.value && (tasteful || cv_kartvoices.value == 2))
+		S_StartSound(source, sfx_slow);
+
+	if (!tasteful)
+		return;
 
 	K_RegularVoiceTimers(source->player);
 }
 
 static void K_PlayHitEmSound(mobj_t *source)
 {
-	S_StartSound(source, sfx_hitem);
+	if (cv_kartvoices.value)
+		S_StartSound(source, sfx_hitem);
 
 	K_RegularVoiceTimers(source->player);
 }
diff --git a/src/m_menu.c b/src/m_menu.c
index 1ee24f47..ce4e7486 100644
--- a/src/m_menu.c
+++ b/src/m_menu.c
@@ -1337,9 +1337,10 @@ static menuitem_t OP_SoundOptionsMenu[] =
 	{IT_STRING|IT_CVAR,			NULL, "Reverse L/R Channels",	&stereoreverse,			 70},
 	{IT_STRING|IT_CVAR,			NULL, "Surround Sound",			&surround,				 80},
 
-	{IT_STRING|IT_CVAR,			NULL, "Powerup Warning",		&cv_kartinvinsfx,		100},
+	{IT_STRING|IT_CVAR,			NULL, "Character voices",		&cv_kartvoices,			100},
+	{IT_STRING|IT_CVAR,			NULL, "Powerup Warning",		&cv_kartinvinsfx,		110},
 
-	{IT_KEYHANDLER|IT_STRING,	NULL, "Sound Test",				M_HandleSoundTest,		120},
+	{IT_KEYHANDLER|IT_STRING,	NULL, "Sound Test",				M_HandleSoundTest,		130},
 };
 
 /*static menuitem_t OP_DataOptionsMenu[] =
@@ -2203,29 +2204,30 @@ static void M_ChangeCvar(INT32 choice)
 		if (cv == &cv_playercolor)
 		{
 			SINT8 skinno = R_SkinAvailable(cv_chooseskin.string);
-			if (skinno == -1)
-				return;
-			CV_SetValue(cv,skins[skinno].prefcolor);
+			if (skinno != -1)
+				CV_SetValue(cv,skins[skinno].prefcolor);
+			return;
 		}
-		return;
 		CV_Set(cv,cv->defaultvalue);
 		return;
 	}
 
+	choice = (choice<<1) - 1;
+
 	if (((currentMenu->menuitems[itemOn].status & IT_CVARTYPE) == IT_CV_SLIDER)
 	    ||((currentMenu->menuitems[itemOn].status & IT_CVARTYPE) == IT_CV_INVISSLIDER)
 	    ||((currentMenu->menuitems[itemOn].status & IT_CVARTYPE) == IT_CV_NOMOD))
 	{
-		CV_SetValue(cv,cv->value+(choice*2-1));
+		CV_SetValue(cv,cv->value+choice);
 	}
 	else if (cv->flags & CV_FLOAT)
 	{
 		char s[20];
-		sprintf(s,"%f",FIXED_TO_FLOAT(cv->value)+(choice*2-1)*(1.0f/16.0f));
+		sprintf(s,"%f",FIXED_TO_FLOAT(cv->value)+(choice)*(1.0f/16.0f));
 		CV_Set(cv,s);
 	}
 	else
-		CV_AddValue(cv,choice*2-1);
+		CV_AddValue(cv,choice);
 }
 
 static boolean M_ChangeStringCvar(INT32 choice)
@@ -2583,7 +2585,7 @@ boolean M_Responder(event_t *ev)
 			if (routine && ((currentMenu->menuitems[itemOn].status & IT_TYPE) == IT_ARROWS
 				|| (currentMenu->menuitems[itemOn].status & IT_TYPE) == IT_CVAR))
 			{
-				if (currentMenu != &OP_SoundOptionsDef)
+				if (currentMenu != &OP_SoundOptionsDef || itemOn > 3)
 					S_StartSound(NULL, sfx_menu1);
 				routine(0);
 			}
@@ -2593,7 +2595,7 @@ boolean M_Responder(event_t *ev)
 			if (routine && ((currentMenu->menuitems[itemOn].status & IT_TYPE) == IT_ARROWS
 				|| (currentMenu->menuitems[itemOn].status & IT_TYPE) == IT_CVAR))
 			{
-				if (currentMenu != &OP_SoundOptionsDef)
+				if (currentMenu != &OP_SoundOptionsDef || itemOn > 3)
 					S_StartSound(NULL, sfx_menu1);
 				routine(1);
 			}
@@ -2680,7 +2682,7 @@ boolean M_Responder(event_t *ev)
 					|| cv == &cv_newgametype)
 					return true;
 
-				if (currentMenu != &OP_SoundOptionsDef)
+				if (currentMenu != &OP_SoundOptionsDef || itemOn > 3)
 					S_StartSound(NULL, sfx_menu1);
 				routine(-1);
 				return true;
@@ -3127,7 +3129,7 @@ static void M_DrawSlider(INT32 x, INT32 y, const consvar_t *cv, boolean ontop)
 
 	if (ontop)
 	{
-		V_DrawCharacter(x - 15 - (skullAnimCounter/5), y,
+		V_DrawCharacter(x - 16 - (skullAnimCounter/5), y,
 			'\x1C' | highlightflags, false); // left arrow
 		V_DrawCharacter(x+(SLIDER_RANGE*8) + 8 + (skullAnimCounter/5), y,
 			'\x1D' | highlightflags, false); // right arrow
@@ -4566,6 +4568,7 @@ static void M_DrawSkyRoom(void)
 			break;
 		}
 	}
+
 	if (y)
 	{
 		y += currentMenu->y;
@@ -4581,9 +4584,9 @@ static void M_DrawSkyRoom(void)
 	if (lengthstring)
 	{
 		V_DrawCharacter(BASEVIDWIDTH - currentMenu->x - 10 - lengthstring - (skullAnimCounter/5), currentMenu->y+currentMenu->menuitems[itemOn].alphaKey,
-			'\x1C' | highlightflags, false);
+			'\x1C' | highlightflags, false); // left arrow
 		V_DrawCharacter(BASEVIDWIDTH - currentMenu->x + 2 + (skullAnimCounter/5), currentMenu->y+currentMenu->menuitems[itemOn].alphaKey,
-			'\x1D' | highlightflags, false);
+			'\x1D' | highlightflags, false); // right arrow
 	}
 }
 
diff --git a/src/p_user.c b/src/p_user.c
index 9d196268..f33aeffd 100644
--- a/src/p_user.c
+++ b/src/p_user.c
@@ -1708,10 +1708,13 @@ void P_DoPlayerExit(player_t *player)
 		else if (!countdown)
 			countdown = cv_countdowntime.value*TICRATE + 1; // Use cv_countdowntime
 
-		if (K_IsPlayerLosing(player))
-			S_StartSound(player->mo, sfx_klose);
-		else
-			S_StartSound(player->mo, sfx_kwin);
+		if (cv_kartvoices.value)
+		{
+			if (K_IsPlayerLosing(player))
+				S_StartSound(player->mo, sfx_klose);
+			else
+				S_StartSound(player->mo, sfx_kwin);
+		}
 
 		player->exiting = 3*TICRATE;
 

From 74e182237a49b5e2b08649fa87f2e32ed61be652 Mon Sep 17 00:00:00 2001
From: TehRealSalt <tehrealsalt@gmail.com>
Date: Sun, 29 Jul 2018 15:09:20 -0400
Subject: [PATCH 20/38] Revert bad RNG

---
 src/k_kart.c | 25 +++++++++++++++++++++++++
 1 file changed, 25 insertions(+)

diff --git a/src/k_kart.c b/src/k_kart.c
index dc1f52ed..69d73e83 100644
--- a/src/k_kart.c
+++ b/src/k_kart.c
@@ -1339,6 +1339,7 @@ static void K_RegularVoiceTimers(player_t *player)
 
 static void K_PlayTauntSound(mobj_t *source)
 {
+#if 0
 	sfxenum_t pick = P_RandomKey(4); // Gotta roll the RNG every time this is called for sync reasons
 	boolean tasteful = (!source->player || !source->player->kartstuff[k_tauntvoices]);
 
@@ -1349,10 +1350,19 @@ static void K_PlayTauntSound(mobj_t *source)
 		return;
 
 	K_TauntVoiceTimers(source->player);
+#else
+	if (source->player && source->player->kartstuff[k_tauntvoices]) // Prevents taunt sounds from playing every time the button is pressed
+		return;
+
+	S_StartSound(source, sfx_taunt1+P_RandomKey(4));
+
+	K_TauntVoiceTimers(source->player);
+#endif
 }
 
 static void K_PlayOvertakeSound(mobj_t *source)
 {
+#if 0
 	boolean tasteful = (!source->player || !source->player->kartstuff[k_voices]);
 
 	if (!G_RaceGametype()) // Only in race
@@ -1369,6 +1379,21 @@ static void K_PlayOvertakeSound(mobj_t *source)
 		return;
 
 	K_RegularVoiceTimers(source->player);
+#else
+	if (source->player && source->player->kartstuff[k_voices]) // Prevents taunt sounds from playing every time the button is pressed
+		return;
+
+	if (!G_RaceGametype()) // Only in race
+		return;
+
+	// 4 seconds from before race begins, 10 seconds afterwards
+	if (leveltime < starttime+(10*TICRATE))
+		return;
+
+	S_StartSound(source, sfx_slow);
+
+	K_RegularVoiceTimers(source->player);
+#endif
 }
 
 static void K_PlayHitEmSound(mobj_t *source)

From 8d4fd5a1eacec477154d0c1ec11b77255d566265 Mon Sep 17 00:00:00 2001
From: TehRealSalt <tehrealsalt@gmail.com>
Date: Sun, 29 Jul 2018 15:59:35 -0400
Subject: [PATCH 21/38] sink

---
 src/k_kart.c | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/src/k_kart.c b/src/k_kart.c
index 69d73e83..50d03c1e 100644
--- a/src/k_kart.c
+++ b/src/k_kart.c
@@ -2550,6 +2550,7 @@ static void K_DoHyudoroSteal(player_t *player)
 	INT32 playerswappable[MAXPLAYERS];
 	INT32 stealplayer = -1; // The player that's getting stolen from
 	INT32 prandom = 0;
+	fixed_t sink = P_RandomChance(FRACUNIT/64);
 
 	for (i = 0; i < MAXPLAYERS; i++)
 	{
@@ -2573,7 +2574,7 @@ static void K_DoHyudoroSteal(player_t *player)
 	prandom = P_RandomFixed();
 	S_StartSound(player->mo, sfx_s3k92);
 
-	if (P_RandomChance(FRACUNIT/64)) // BEHOLD THE KITCHEN SINK
+	if (sink) // BEHOLD THE KITCHEN SINK
 	{
 		player->kartstuff[k_hyudorotimer] = hyudorotime;
 		player->kartstuff[k_stealingtimer] = stealtime;

From 0dddf7623a1afcfb8b9390a02fdbe5e9574c5a27 Mon Sep 17 00:00:00 2001
From: toaster <rollerorbital@gmail.com>
Date: Sun, 29 Jul 2018 23:07:10 +0100
Subject: [PATCH 22/38] Buggy netgame! Sorry yalls. * Fix free play on
 intermission. * Fix crash in killing/damaging mobj with null death/pain
 state.

---
 src/p_inter.c | 27 +++++++++++++++------------
 src/y_inter.c | 21 ++-------------------
 2 files changed, 17 insertions(+), 31 deletions(-)

diff --git a/src/p_inter.c b/src/p_inter.c
index 474cf148..6ca5e6c2 100644
--- a/src/p_inter.c
+++ b/src/p_inter.c
@@ -3492,22 +3492,25 @@ boolean P_DamageMobj(mobj_t *target, mobj_t *inflictor, mobj_t *source, INT32 da
 			break;
 		}
 
-	target->reactiontime = 0; // we're awake now...
-
-	if (source && source != target)
+	if (!P_MobjWasRemoved(target))
 	{
-		// if not intent on another player,
-		// chase after this one
-		P_SetTarget(&target->target, source);
-		if (target->state == &states[target->info->spawnstate] && target->info->seestate != S_NULL)
+		target->reactiontime = 0; // we're awake now...
+
+		if (source && source != target)
 		{
-			if (player)
+			// if not intent on another player,
+			// chase after this one
+			P_SetTarget(&target->target, source);
+			if (target->state == &states[target->info->spawnstate] && target->info->seestate != S_NULL)
 			{
-				if (!(player->powers[pw_super] && ALL7EMERALDS(player->powers[pw_emeralds])))
-					P_SetPlayerMobjState(target, target->info->seestate);
+				if (player)
+				{
+					if (!(player->powers[pw_super] && ALL7EMERALDS(player->powers[pw_emeralds])))
+						P_SetPlayerMobjState(target, target->info->seestate);
+				}
+				else
+					P_SetMobjState(target, target->info->seestate);
 			}
-			else
-				P_SetMobjState(target, target->info->seestate);
 		}
 	}
 
diff --git a/src/y_inter.c b/src/y_inter.c
index a77c12a0..cb171ed9 100644
--- a/src/y_inter.c
+++ b/src/y_inter.c
@@ -493,25 +493,8 @@ void Y_IntermissionDrawer(void)
 
 dotimer:
 
-	if (netgame) //  FREE PLAY?
-	{
-		i = MAXPLAYERS;
-
-		if (!forcefreeplay)
-		{
-			// check to see if there's anyone else at all
-			for (i = 0; i < MAXPLAYERS; i++)
-			{
-				if (i == consoleplayer)
-					continue;
-				if (playeringame[i] && !stplyr->spectator)
-					break;
-			}
-		}
-
-		if (i == MAXPLAYERS)
-			K_drawKartFreePlay(intertic);
-	}
+	if (netgame && forcefreeplay) // FREE PLAY?
+		K_drawKartFreePlay(intertic);
 
 	if (timer)
 	{

From 65a956259b597fd15818d2b0b6b6c352df725b80 Mon Sep 17 00:00:00 2001
From: toaster <rollerorbital@gmail.com>
Date: Sun, 29 Jul 2018 23:51:08 +0100
Subject: [PATCH 23/38] * Re-enable kartvoices controls for K_PlayTauntSound
 and K_PlayOvertakeSound. * Make the three P_Play<?>Sounds from p_local.h
 actual functions that acknowledge the kartvoices cvar.

---
 src/k_kart.c  |  4 ++--
 src/p_local.h |  6 +++---
 src/p_user.c  | 25 +++++++++++++++++++++++++
 3 files changed, 30 insertions(+), 5 deletions(-)

diff --git a/src/k_kart.c b/src/k_kart.c
index 50d03c1e..d546a06f 100644
--- a/src/k_kart.c
+++ b/src/k_kart.c
@@ -1339,7 +1339,7 @@ static void K_RegularVoiceTimers(player_t *player)
 
 static void K_PlayTauntSound(mobj_t *source)
 {
-#if 0
+#if 1
 	sfxenum_t pick = P_RandomKey(4); // Gotta roll the RNG every time this is called for sync reasons
 	boolean tasteful = (!source->player || !source->player->kartstuff[k_tauntvoices]);
 
@@ -1362,7 +1362,7 @@ static void K_PlayTauntSound(mobj_t *source)
 
 static void K_PlayOvertakeSound(mobj_t *source)
 {
-#if 0
+#if 1
 	boolean tasteful = (!source->player || !source->player->kartstuff[k_voices]);
 
 	if (!G_RaceGametype()) // Only in race
diff --git a/src/p_local.h b/src/p_local.h
index 42b70543..3bf98bee 100644
--- a/src/p_local.h
+++ b/src/p_local.h
@@ -194,9 +194,9 @@ void P_SpawnSpinMobj(player_t *player, mobjtype_t type);
 void P_Telekinesis(player_t *player, fixed_t thrust, fixed_t range);
 
 void P_PlayLivesJingle(player_t *player);
-#define P_PlayRinglossSound(s)	S_StartSound(s, (mariomode) ? sfx_mario8 : sfx_altow1 + P_RandomKey(4));
-#define P_PlayDeathSound(s)		S_StartSound(s, sfx_altdi1 + P_RandomKey(4));
-#define P_PlayVictorySound(s)	S_StartSound(s, sfx_victr1 + P_RandomKey(4));
+void P_PlayRinglossSound(mobj_t *source);
+void P_PlayDeathSound(mobj_t *source);
+void P_PlayVictorySound(mobj_t *source);
 
 
 //
diff --git a/src/p_user.c b/src/p_user.c
index f33aeffd..9ba02714 100644
--- a/src/p_user.c
+++ b/src/p_user.c
@@ -1118,6 +1118,31 @@ void P_PlayLivesJingle(player_t *player)
 	}
 }
 
+void P_PlayRinglossSound(mobj_t *source)
+{
+	sfxenum_t key = P_RandomKey(4);
+	if (cv_kartvoices.value)
+		S_StartSound(source, (mariomode) ? sfx_mario8 : sfx_altow1 + key);
+	else
+		S_StartSound(source, sfx_slip);
+}
+
+void P_PlayDeathSound(mobj_t *source)
+{
+	sfxenum_t key = P_RandomKey(4);
+	if (cv_kartvoices.value)
+		S_StartSound(source, sfx_altdi1 + key);
+	else
+		S_StartSound(source, sfx_s3k35);
+}
+
+void P_PlayVictorySound(mobj_t *source)
+{
+	sfxenum_t key = P_RandomKey(4);
+	if (cv_kartvoices.value)
+		S_StartSound(source, sfx_victr1 + key);
+}
+
 //
 // P_EndingMusic
 //

From 7f509c7c2737084dae76b39298af8d165bbe804a Mon Sep 17 00:00:00 2001
From: toaster <rollerorbital@gmail.com>
Date: Mon, 30 Jul 2018 21:53:54 +0100
Subject: [PATCH 24/38] Some UI stuff. * Place FREE PLAY in a less contentous
 location. * Fix comeback timer being incorrectly offset from the center of
 the screen. * Prevent vote drawer from nuking itself (visual-wise) at the
 start of the level fade following it.

---
 src/k_kart.c  | 25 ++++++++++++-------------
 src/y_inter.c | 10 +++++-----
 2 files changed, 17 insertions(+), 18 deletions(-)

diff --git a/src/k_kart.c b/src/k_kart.c
index d546a06f..6792895a 100644
--- a/src/k_kart.c
+++ b/src/k_kart.c
@@ -5730,19 +5730,16 @@ static void K_drawBattleFullscreen(void)
 	}
 	else if (stplyr->kartstuff[k_bumper] <= 0 && stplyr->kartstuff[k_comebacktimer] && comeback)
 	{
-		INT32 t = stplyr->kartstuff[k_comebacktimer]/TICRATE;
-		INT32 txoff = 0;
+		UINT16 t = stplyr->kartstuff[k_comebacktimer]/(10*TICRATE);
+		INT32 txoff, adjust = (splitscreen > 1) ? 4 : 6; // normal string is 8, kart string is 12, half of that for ease
 		INT32 ty = (BASEVIDHEIGHT/2)+66;
 
-		if (t == 0)
-			txoff = 8;
-		else
+		txoff = adjust;
+
+		while (t)
 		{
-			while (t)
-			{
-				txoff += 8;
-				t /= 10;
-			}
+			txoff += adjust;
+			t /= 10;
 		}
 
 		if (splitscreen)
@@ -5763,7 +5760,7 @@ static void K_drawBattleFullscreen(void)
 			V_DrawFixedPatch(x<<FRACBITS, y<<FRACBITS, scale, splitflags, kp_battlewait, NULL);
 
 		if (splitscreen > 1)
-			V_DrawString(x-(txoff/2), ty, 0, va("%d", stplyr->kartstuff[k_comebacktimer]/TICRATE));
+			V_DrawString(x-txoff, ty, 0, va("%d", stplyr->kartstuff[k_comebacktimer]/TICRATE));
 		else
 		{
 			V_DrawFixedPatch(x<<FRACBITS, ty<<FRACBITS, scale, 0, kp_timeoutsticker, NULL);
@@ -6054,11 +6051,13 @@ static void K_drawCheckpointDebugger(void)
 
 void K_drawKartFreePlay(UINT32 flashtime)
 {
+	// no splitscreen support because it's not FREE PLAY if you have more than one player in-game
+
 	if ((flashtime % TICRATE) < TICRATE/2)
 		return;
 
-	V_DrawKartString(BASEVIDWIDTH/2 - (6*9), // horizontally centered, nice
-		LAPS_Y+3, V_SNAPTOBOTTOM, "FREE PLAY");
+	V_DrawKartString((BASEVIDWIDTH - (LAPS_X+1)) - (12*9), // mirror the laps thingy
+		LAPS_Y+3, V_SNAPTOBOTTOM|V_SNAPTORIGHT, "FREE PLAY");
 }
 
 void K_drawKartHUD(void)
diff --git a/src/y_inter.c b/src/y_inter.c
index cb171ed9..f07e8e2f 100644
--- a/src/y_inter.c
+++ b/src/y_inter.c
@@ -499,13 +499,13 @@ dotimer:
 	if (timer)
 	{
 		INT32 tickdown = (timer+1)/TICRATE;
-		V_DrawCenteredString(BASEVIDWIDTH/2, 188, hilicol|V_SNAPTOBOTTOM,
+		V_DrawCenteredString(BASEVIDWIDTH/2, 188, hilicol,
 			va("start in %d second%s", tickdown, (tickdown == 1 ? "" : "s")));
 	}
 
 	// Make it obvious that scrambling is happening next round.
 	if (cv_scrambleonchange.value && cv_teamscramble.value && (intertic/TICRATE % 2 == 0))
-		V_DrawCenteredString(BASEVIDWIDTH/2, BASEVIDHEIGHT/2, hilicol|V_SNAPTOBOTTOM, M_GetText("Teams will be scrambled next round!"));
+		V_DrawCenteredString(BASEVIDWIDTH/2, BASEVIDHEIGHT/2, hilicol, M_GetText("Teams will be scrambled next round!"));
 }
 
 //
@@ -936,11 +936,11 @@ void Y_VoteDrawer(void)
 	if (votetic >= voteendtic && voteendtic != -1)
 		return;
 
-	V_DrawFill(0, 0, BASEVIDWIDTH, BASEVIDHEIGHT, 31);
-
 	if (!voteclient.loaded)
 		return;
 
+	V_DrawFill(0, 0, BASEVIDWIDTH, BASEVIDHEIGHT, 31);
+
 	if (widebgpatch && rendermode == render_soft && vid.width / vid.dupx > 320)
 		V_DrawScaledPatch(((vid.width/2) / vid.dupx) - (SHORT(widebgpatch->width)/2),
 							(vid.height / vid.dupy) - SHORT(widebgpatch->height),
@@ -1138,7 +1138,7 @@ void Y_VoteDrawer(void)
 			hilicol = V_SKYMAP;
 		else //if (gametype == GT_MATCH)
 			hilicol = V_REDMAP;
-		V_DrawCenteredString(BASEVIDWIDTH/2, 188, hilicol|V_SNAPTOBOTTOM,
+		V_DrawCenteredString(BASEVIDWIDTH/2, 188, hilicol,
 			va("Vote ends in %d second%s", tickdown, (tickdown == 1 ? "" : "s")));
 	}
 }

From 057bc4120305a112ce57f4380c03ea8145feabb6 Mon Sep 17 00:00:00 2001
From: toaster <rollerorbital@gmail.com>
Date: Mon, 30 Jul 2018 22:07:28 +0100
Subject: [PATCH 25/38] Fix dropped else in tab rankings view, leading to the
 exiting text being drawn on top of other text for no good reason.

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

diff --git a/src/hu_stuff.c b/src/hu_stuff.c
index fd71470b..dd08d26e 100644
--- a/src/hu_stuff.c
+++ b/src/hu_stuff.c
@@ -1363,7 +1363,7 @@ void HU_DrawTabRankings(INT32 x, INT32 y, playersort_t *tab, INT32 scorelines, I
 				V_DrawRightAlignedString(x, y-4, hilicol, "FIN");
 				V_DrawRightAlignedString(x+rightoffset, y, hilicol, timestring(players[tab[i].num].realtime));
 			}
-			if (players[tab[i].num].pflags & PF_TIMEOVER)
+			else if (players[tab[i].num].pflags & PF_TIMEOVER)
 				V_DrawRightAlignedThinString(x+rightoffset, y-1, 0, "TIME OVER...");
 			else if (circuitmap)
 			{

From 731b05e26389bb847a9b72bd9e9971f096abc9c0 Mon Sep 17 00:00:00 2001
From: toaster <rollerorbital@gmail.com>
Date: Mon, 30 Jul 2018 23:17:01 +0100
Subject: [PATCH 26/38] More UI crap! * Fix minor additional error in tab
 rankings. * Remove FREE PLAY from intermission drawer, per Sal's request. *
 Remove "second%s" from the end of the "Start in %d"/"Vote ends in %d" string.
 * Rename cv_advancemap's "Off" value to "Same", to make the behaviour
 clearer. * Make the "Start in %d" string now begin with cv_advancemap's
 string (ie, "Vote in %d", "Same in %d", "Random in %d, Next in %d"...

---
 src/d_netcmd.c |  2 +-
 src/hu_stuff.c |  2 +-
 src/m_menu.c   |  2 +-
 src/y_inter.c  | 13 ++-----------
 4 files changed, 5 insertions(+), 14 deletions(-)

diff --git a/src/d_netcmd.c b/src/d_netcmd.c
index f7954747..78531be3 100644
--- a/src/d_netcmd.c
+++ b/src/d_netcmd.c
@@ -432,7 +432,7 @@ consvar_t cv_maxping = {"maxping", "0", CV_SAVE, CV_Unsigned, NULL, 0, NULL, NUL
 static CV_PossibleValue_t inttime_cons_t[] = {{0, "MIN"}, {3600, "MAX"}, {0, NULL}};
 consvar_t cv_inttime = {"inttime", "20", CV_NETVAR, inttime_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL};
 
-static CV_PossibleValue_t advancemap_cons_t[] = {{0, "Off"}, {1, "Next"}, {2, "Random"}, {3, "Vote"}, {0, NULL}};
+static CV_PossibleValue_t advancemap_cons_t[] = {{0, "Same"}, {1, "Next"}, {2, "Random"}, {3, "Vote"}, {0, NULL}};
 consvar_t cv_advancemap = {"advancemap", "Vote", CV_NETVAR, advancemap_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL};
 static CV_PossibleValue_t playersforexit_cons_t[] = {{0, "One"}, {1, "All"}, {0, NULL}};
 consvar_t cv_playersforexit = {"playersforexit", "One", CV_NETVAR, playersforexit_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL};
diff --git a/src/hu_stuff.c b/src/hu_stuff.c
index dd08d26e..4122b398 100644
--- a/src/hu_stuff.c
+++ b/src/hu_stuff.c
@@ -1376,7 +1376,7 @@ void HU_DrawTabRankings(INT32 x, INT32 y, playersort_t *tab, INT32 scorelines, I
 #undef timestring
 		}
 		else
-			V_DrawRightAlignedString(x+rightoffset, y, ((players[tab[i].num].health > 0) ? 0 : V_60TRANS), va("%u", tab[i].count));
+			V_DrawRightAlignedString(x+rightoffset, y, 0, va("%u", tab[i].count));
 
 		y += 16;
 		if (i == 9)
diff --git a/src/m_menu.c b/src/m_menu.c
index ce4e7486..63eae220 100644
--- a/src/m_menu.c
+++ b/src/m_menu.c
@@ -1436,7 +1436,7 @@ static menuitem_t OP_ServerOptionsMenu[] =
 
 	{IT_STRING | IT_CVAR,    NULL, "Intermission Timer",			&cv_inttime,			 40},
 	{IT_STRING | IT_CVAR,    NULL, "Voting Timer",					&cv_votetime,			 50},
-	{IT_STRING | IT_CVAR,    NULL, "Advance to Next Level",		&cv_advancemap,			 60},
+	{IT_STRING | IT_CVAR,    NULL, "Advance to Next Level",			&cv_advancemap,			 60},
 
 #ifndef NONET
 	{IT_STRING | IT_CVAR,    NULL, "Max Player Count",				&cv_maxplayers,			 80},
diff --git a/src/y_inter.c b/src/y_inter.c
index f07e8e2f..29c81df0 100644
--- a/src/y_inter.c
+++ b/src/y_inter.c
@@ -263,7 +263,6 @@ static void Y_CalculateMatchData(boolean rankingsmode, void (*comparison)(INT32)
 //
 void Y_IntermissionDrawer(void)
 {
-	boolean forcefreeplay = false;
 	INT32 i, whiteplayer = MAXPLAYERS, x = 4, hilicol = V_YELLOWMAP; // fallback
 
 	if (intertype == int_none || rendermode == render_none)
@@ -370,11 +369,7 @@ void Y_IntermissionDrawer(void)
 		if (data.match.rankingsmode)
 			timeheader = "RANK";
 		else
-		{
 			timeheader = (intertype == int_race ? "TIME" : "SCORE");
-			if (data.match.numplayers <= 1)
-				forcefreeplay = true;
-		}
 
 		// draw the level name
 		V_DrawCenteredString(-4 + x + BASEVIDWIDTH/2, 20, 0, data.match.levelstring);
@@ -492,15 +487,11 @@ void Y_IntermissionDrawer(void)
 	}
 
 dotimer:
-
-	if (netgame && forcefreeplay) // FREE PLAY?
-		K_drawKartFreePlay(intertic);
-
 	if (timer)
 	{
 		INT32 tickdown = (timer+1)/TICRATE;
 		V_DrawCenteredString(BASEVIDWIDTH/2, 188, hilicol,
-			va("start in %d second%s", tickdown, (tickdown == 1 ? "" : "s")));
+			va("%s in %d", cv_advancemap.string, tickdown));
 	}
 
 	// Make it obvious that scrambling is happening next round.
@@ -1139,7 +1130,7 @@ void Y_VoteDrawer(void)
 		else //if (gametype == GT_MATCH)
 			hilicol = V_REDMAP;
 		V_DrawCenteredString(BASEVIDWIDTH/2, 188, hilicol,
-			va("Vote ends in %d second%s", tickdown, (tickdown == 1 ? "" : "s")));
+			va("Vote ends in %d", tickdown));
 	}
 }
 

From f2070ba6d1b9bb5aab42649c7cdbdc2cb2652e2f Mon Sep 17 00:00:00 2001
From: toaster <rollerorbital@gmail.com>
Date: Mon, 30 Jul 2018 23:20:04 +0100
Subject: [PATCH 27/38] Some tidbits for countdowns. * Fix an inconsistency in
 timing re a time over that involves a single person (the number of tics
 exiting usually takes), versus a time over that involves multiple people
 (8*TICRATE). * Barebones preparatory work for F-Zero explosion, since I was
 futzing around in this already.

---
 src/p_user.c | 10 ++++++++--
 1 file changed, 8 insertions(+), 2 deletions(-)

diff --git a/src/p_user.c b/src/p_user.c
index 9ba02714..86e404ac 100644
--- a/src/p_user.c
+++ b/src/p_user.c
@@ -1747,8 +1747,8 @@ void P_DoPlayerExit(player_t *player)
 			P_EndingMusic(player);
 
 		// SRB2kart 120217
-		if (!countdown2)
-			countdown2 = countdown + 8*TICRATE;
+		//if (!countdown2)
+			//countdown2 = countdown + 8*TICRATE;
 
 		if (P_CheckRacers())
 			player->exiting = (14*TICRATE)/5 + 1;
@@ -9088,6 +9088,12 @@ void P_DoTimeOver(player_t *player)
 	player->lives = 0;
 
 	P_EndingMusic(player);
+
+#if 0
+	// sal, when you do the f-zero explosion, this is how you make sure the map doesn't end before it's done ^u^ ~toast
+	if (!countdown2)
+		countdown2 = 5*TICRATE;
+#endif
 }
 
 //

From 9109bb1ee77252410a9fc85ed1bdbaacd0f168c1 Mon Sep 17 00:00:00 2001
From: Latapostrophe <hyperclassic3@gmail.com>
Date: Tue, 31 Jul 2018 23:35:16 +0200
Subject: [PATCH 28/38] Fix console leftover + positionning

---
 src/console.c  |  16 ++++-
 src/hu_stuff.c | 154 +++++++++++++++++++++++++++++++++++--------------
 2 files changed, 125 insertions(+), 45 deletions(-)

diff --git a/src/console.c b/src/console.c
index 43d5df2d..e6ec039e 100644
--- a/src/console.c
+++ b/src/console.c
@@ -1037,7 +1037,17 @@ boolean CON_Responder(event_t *ev)
 	else if (key == KEY_KPADSLASH)
 		key = '/';
 
-	if (shiftdown)
+	// capslock
+	if (key == KEY_CAPSLOCK)	// it's a toggle.
+	{	
+		if (capslock)
+			capslock = false;
+		else	
+			capslock = true;
+		return true;
+	}	
+	
+	if (capslock ^ shiftdown)	// gets capslock to work because capslock is cool
 		key = shiftxform[key];
 
 	// enter a char into the command prompt
@@ -1045,7 +1055,7 @@ boolean CON_Responder(event_t *ev)
 		return true; // even if key can't be printed, eat it anyway
 
 	// add key to cmd line here
-	if (key >= 'A' && key <= 'Z' && !shiftdown) //this is only really necessary for dedicated servers
+	if (key >= 'A' && key <= 'Z' && !(shiftdown ^ capslock)) //this is only really necessary for dedicated servers
 		key = key + 'a' - 'A';
 
 	if (input_sel != input_cur)
@@ -1432,7 +1442,7 @@ static void CON_DrawHudlines(void)
 	if (con_hudlines <= 0)
 		return;
 
-	if (chat_on)
+	if (chat_on && (cv_consolechat.value || vid.width < 640))
 		y = charheight; // leave place for chat input in the first row of text
 	else
 		y = 0;
diff --git a/src/hu_stuff.c b/src/hu_stuff.c
index 6114748b..29f9c356 100644
--- a/src/hu_stuff.c
+++ b/src/hu_stuff.c
@@ -1167,7 +1167,7 @@ static UINT8 *CHAT_GetStringColormap(INT32 colorflags)	// pasted from video.c, s
 	case 2: // 0x82, yellow
 		return yellowmap;
 	case 3: // 0x83, lgreen
-		return lgreenmap;
+		return greenmap;
 	case 4: // 0x84, blue
 		return bluemap;
 	case 5: // 0x85, red
@@ -1176,6 +1176,8 @@ static UINT8 *CHAT_GetStringColormap(INT32 colorflags)	// pasted from video.c, s
 		return graymap;
 	case 7: // 0x87, orange
 		return orangemap;
+	case 8: // 0x88, sky
+		return skymap;
 	default: // reset
 		return NULL;
 	}
@@ -1235,7 +1237,9 @@ char *CHAT_WordWrap(INT32 x, INT32 w, INT32 option, const char *string)
 	return newstring;
 }
 
-INT16 chatx = 160, chaty = 16;	// let's use this as our coordinates, shh
+// 30/7/18: chaty is now the distance at which the lowest point of the chat will be drawn if that makes any sense.
+
+INT16 chatx = 13, chaty = 169;	// let's use this as our coordinates, shh
 
 // chat stuff by VincyTM LOL XD!
 
@@ -1243,19 +1247,75 @@ INT16 chatx = 160, chaty = 16;	// let's use this as our coordinates, shh
 
 static void HU_drawMiniChat(void)
 {
-	INT32 charwidth = (vid.width < 640) ? 8 : 4, charheight = (vid.width < 640) ? 8 : 6;
-	INT32 x = chatx+2, y = chaty+2, dx = 0, dy = 0;
-	size_t i = 0;
 	
-	for (i=0; i<chat_nummsg_min; i++)	// iterate through our hot messages
+	if (!chat_nummsg_min)
+		return;	// needless to say it's useless to do anything if we don't have anything to draw.
+	
+	
+	INT32 x = chatx+2; 
+	INT32 charwidth = 4, charheight = 6;	
+	INT32 dx = 0, dy = 0;
+	size_t i = chat_nummsg_min;
+	
+	INT32 msglines = 0;
+	// process all messages once without rendering anything or doing anything fancy so that we know how many lines each message has...
+	
+	for (; i>0; i--)
+	{	
+		const char *msg = CHAT_WordWrap(x, cv_chatwidth.value-charwidth, V_SNAPTOBOTTOM|V_SNAPTOLEFT|V_ALLOWLOWERCASE, chat_mini[i-1]);
+		size_t j = 0;
+		INT32 linescount = 0;
+		
+		while(msg[j])	// iterate through msg
+		{	
+			if (msg[j] < HU_FONTSTART)	// don't draw
+			{			
+				if (msg[j] == '\n')	// get back down.
+				{
+					++j;
+					linescount += 1;
+					dx = 0;
+					continue;
+				}
+				else if (msg[j] & 0x80) // stolen from video.c, nice.
+				{
+					++j;
+					continue;
+				}
+				
+				++j;	
+			}
+			else
+			{
+				j++;
+			}
+			
+			dx += charwidth;
+			if (dx >= cv_chatwidth.value)
+			{
+				dx = 0;
+				linescount += 1;
+			}
+		}
+		dy = 0;
+		dx = 0;
+		msglines += linescount+1;
+	}
+	
+	INT32 y = chaty - charheight*(msglines+1) - (cv_kartspeedometer.value ? 16 : 0);
+	dx = 0; 
+	dy = 0;
+	i = 0;
+	
+	for (; i<=(chat_nummsg_min-1); i++)	// iterate through our hot messages
 	{
 		
 		INT32 clrflag = 0;
-		
 		INT32 timer = ((cv_chattime.value*TICRATE)-chat_timers[i]) - cv_chattime.value*TICRATE+9;	// see below...		
 		INT32 transflag = (timer >= 0 && timer <= 9) ? (timer*V_10TRANS) : 0;	// you can make bad jokes out of this one.
 		size_t j = 0;
-		const char *msg = CHAT_WordWrap(x, cv_chatwidth.value-charwidth, V_SNAPTOTOP|V_SNAPTOLEFT|V_ALLOWLOWERCASE, chat_mini[i]);	// get the current message, and word wrap it.
+		const char *msg = CHAT_WordWrap(x, cv_chatwidth.value-charwidth, V_SNAPTOBOTTOM|V_SNAPTOLEFT|V_ALLOWLOWERCASE, chat_mini[i]);	// get the current message, and word wrap it.
+		
 		while(msg[j])	// iterate through msg
 		{	
 			if (msg[j] < HU_FONTSTART)	// don't draw
@@ -1279,7 +1339,7 @@ static void HU_drawMiniChat(void)
 			else
 			{
 				UINT8 *colormap = CHAT_GetStringColormap(clrflag);
-				V_DrawChatCharacter(x + dx + 2, y+dy+addy, msg[j++] |V_SNAPTOTOP|V_SNAPTORIGHT|transflag, !cv_allcaps.value, colormap);
+				V_DrawChatCharacter(x + dx + 2, y+dy, msg[j++] |V_SNAPTOBOTTOM|V_SNAPTOLEFT|transflag, !cv_allcaps.value, colormap);
 			}
 			
 			dx += charwidth;
@@ -1292,7 +1352,7 @@ static void HU_drawMiniChat(void)
 		dy += charheight;
 		dx = 0;
 	}
-	
+		
 	// decrement addy and make that shit smooth:
 	addy /= 2;
 	
@@ -1333,25 +1393,27 @@ static void HU_DrawDownArrow(INT32 x, INT32 y, INT32 options)
 // HU_DrawChatLog
 // TODO: fix dumb word wrapping issues
 
-static void HU_drawChatLog(void)
+static void HU_drawChatLog(INT32 offset)
 {
 	
 	// before we do anything, make sure that our scroll position isn't "illegal";
 	if (chat_scroll > chat_maxscroll)
 		chat_scroll = chat_maxscroll;
 	
-	INT32 charwidth = (vid.width < 640) ? 8 : 4, charheight = (vid.width < 640) ? 8 : 6;
-	INT32 x = chatx+2, y = chaty+2-(chat_scroll*charheight), dx = 0, dy = 0;
+	INT32 charwidth = 4, charheight = 6;
+	INT32 x = chatx+2, y = chaty - offset*charheight - (chat_scroll*charheight) - cv_chatheight.value*charheight - 12 - (cv_kartspeedometer.value ? 16 : 0), dx = 0, dy = 0;
 	size_t i = 0;
+	INT32 chat_topy = y + chat_scroll*charheight;
+	INT32 chat_bottomy = chat_topy + cv_chatheight.value*charheight;
 	boolean atbottom = false;
 	
-	V_DrawFillConsoleMap(chatx, chaty, cv_chatwidth.value, cv_chatheight.value*charheight +2, 239|V_SNAPTOTOP|V_SNAPTORIGHT);	// INUT
+	V_DrawFillConsoleMap(chatx, chat_topy, cv_chatwidth.value, cv_chatheight.value*charheight +2, 239|V_SNAPTOBOTTOM|V_SNAPTOLEFT);	// log box
 		
 	for (i=0; i<chat_nummsg_log; i++)	// iterate through our chatlog
 	{
 		INT32 clrflag = 0;
 		size_t j = 0;
-		const char *msg = CHAT_WordWrap(x, cv_chatwidth.value-charwidth, V_SNAPTOTOP|V_SNAPTOLEFT|V_ALLOWLOWERCASE, chat_log[i]);	// get the current message, and word wrap it.
+		const char *msg = CHAT_WordWrap(x, cv_chatwidth.value-charwidth, V_SNAPTOBOTTOM|V_SNAPTOLEFT|V_ALLOWLOWERCASE, chat_log[i]);	// get the current message, and word wrap it.
 		while(msg[j])	// iterate through msg
 		{	
 			if (msg[j] < HU_FONTSTART)	// don't draw
@@ -1374,10 +1436,10 @@ static void HU_drawChatLog(void)
 			}
 			else
 			{
-				if ((y+dy > chaty) && (y+dy < (chaty+cv_chatheight.value*charheight)))
+				if ((y+dy+2 >= chat_topy) && (y+dy < (chat_bottomy)))
 				{	
 					UINT8 *colormap = CHAT_GetStringColormap(clrflag);
-					V_DrawChatCharacter(x + dx + 2, y+dy, msg[j++] |V_SNAPTOTOP|V_SNAPTORIGHT, !cv_allcaps.value, colormap);
+					V_DrawChatCharacter(x + dx + 2, y+dy+2, msg[j++] |V_SNAPTOBOTTOM|V_SNAPTOLEFT, !cv_allcaps.value, colormap);
 				}
 				else
 					j++;	// don't forget to increment this or we'll get stuck in the limbo.
@@ -1412,9 +1474,9 @@ static void HU_drawChatLog(void)
 	// draw arrows to indicate that we can (or not) scroll.
 	
 	if (chat_scroll > 0)
-		HU_DrawUpArrow(chatx-8, ((justscrolledup) ? (chaty-1) : (chaty)), V_SNAPTOTOP | V_SNAPTORIGHT);
+		HU_DrawUpArrow(chatx-8, ((justscrolledup) ? (chat_topy-1) : (chat_topy)), V_SNAPTOBOTTOM | V_SNAPTOLEFT);
 	if (chat_scroll < chat_maxscroll)
-		HU_DrawDownArrow(chatx-8, chaty+(cv_chatheight.value*charheight)-((justscrolleddown) ? 3 : 4), V_SNAPTOTOP | V_SNAPTORIGHT);
+		HU_DrawDownArrow(chatx-8, chat_bottomy-((justscrolleddown) ? 3 : 4), V_SNAPTOBOTTOM | V_SNAPTOLEFT);
 	
 	justscrolleddown = false;
 	justscrolledup = false;
@@ -1429,8 +1491,8 @@ static void HU_drawChatLog(void)
 static INT16 typelines = 1;	// number of drawfill lines we need. it's some weird hack and might be one frame off but I'm lazy to make another loop.
 static void HU_DrawChat(void)
 {	
-	INT32 charwidth = (vid.width < 640) ? 8 : 4, charheight = (vid.width < 640) ? 8 : 6;
-	INT32 t = 0, c = 0, y = chaty + 4 + cv_chatheight.value*charheight;
+	INT32 charwidth = 4, charheight = 6;
+	INT32 t = 0, c = 0, y = chaty - (typelines*charheight)  - (cv_kartspeedometer.value ? 16 : 0);
 	size_t i = 0;
 	const char *ntalk = "Say: ", *ttalk = "Team: ";
 	const char *talk = ntalk;
@@ -1446,15 +1508,14 @@ static void HU_DrawChat(void)
 #endif
 	}
 			
-	HU_drawChatLog();
-	V_DrawFillConsoleMap(chatx, y-1, cv_chatwidth.value, (vid.width < 640 ) ? (typelines*charheight+2) : (typelines*charheight), 239 | V_SNAPTOTOP | V_SNAPTORIGHT);
+	V_DrawFillConsoleMap(chatx, y-1, cv_chatwidth.value, (typelines*charheight), 239 | V_SNAPTOBOTTOM | V_SNAPTOLEFT);
 	
 	while (talk[i])
 	{
 		if (talk[i] < HU_FONTSTART)
 			++i;
 		else
-			V_DrawChatCharacter(chatx + c + 2, y, talk[i++] |V_SNAPTOTOP|V_SNAPTORIGHT, !cv_allcaps.value, NULL);
+			V_DrawChatCharacter(chatx + c + 2, y, talk[i++] |V_SNAPTOBOTTOM|V_SNAPTOLEFT, !cv_allcaps.value, NULL);
 
 		c += charwidth;
 	}
@@ -1463,26 +1524,34 @@ static void HU_DrawChat(void)
 	typelines = 1;
 	
 	if ((strlen(w_chat) == 0 || c_input == 0) && hu_tick < 4)
-		V_DrawChatCharacter(chatx + 2 + c, y+1, '_' |V_SNAPTOTOP|V_SNAPTORIGHT|t, !cv_allcaps.value, NULL);
-	
+		V_DrawChatCharacter(chatx + 2 + c, y+1, '_' |V_SNAPTOBOTTOM|V_SNAPTOLEFT|t, !cv_allcaps.value, NULL);
+		
 	while (w_chat[i])
 	{
-		
-		if (c_input == (i+1) && hu_tick < 4)
+		boolean skippedline = false;
+		if (c_input == (i+1))
 		{
-			int cursorx = (c+charwidth < cv_chatwidth.value-charwidth) ? (chatx + 2 + c+charwidth) : (chatx);	// we may have to go down.
-			int cursory = (cursorx != chatx) ? (y) : (y+charheight);
-			V_DrawChatCharacter(cursorx, cursory+1, '_' |V_SNAPTOTOP|V_SNAPTORIGHT|t, !cv_allcaps.value, NULL);	
+			int cursorx = (c+charwidth < cv_chatwidth.value-charwidth) ? (chatx + 2 + c+charwidth) : (chatx+1);	// we may have to go down.
+			int cursory = (cursorx != chatx+1) ? (y) : (y+charheight);
+			if (hu_tick < 4)
+				V_DrawChatCharacter(cursorx, cursory+1, '_' |V_SNAPTOBOTTOM|V_SNAPTOLEFT|t, !cv_allcaps.value, NULL);	
+			
+			if (cursorx == chatx+1)	// a weirdo hack
+			{
+				typelines += 1;
+				skippedline = true;
+			}	
+			
 		}	
 		
 		//Hurdler: isn't it better like that?
 		if (w_chat[i] < HU_FONTSTART)
 			++i;
 		else
-			V_DrawChatCharacter(chatx + c + 2, y, w_chat[i++] | V_SNAPTOTOP|V_SNAPTORIGHT | t, !cv_allcaps.value, NULL);
+			V_DrawChatCharacter(chatx + c + 2, y, w_chat[i++] | V_SNAPTOBOTTOM|V_SNAPTOLEFT | t, !cv_allcaps.value, NULL);
 
 		c += charwidth;
-		if (c > cv_chatwidth.value-charwidth)
+		if (c > cv_chatwidth.value-(charwidth*2) && !skippedline)
 		{
 			c = 0;
 			y += charheight;
@@ -1495,8 +1564,7 @@ static void HU_DrawChat(void)
 	{	
 		i = 0;
 		int count = 0;
-		INT32 p_dispy = chaty+2;
-		V_DrawFillConsoleMap(chatx-50, p_dispy-2, 48, 2, 239 | V_SNAPTOTOP | V_SNAPTORIGHT);	// top (don't mind me)
+		INT32 p_dispy = chaty - charheight -1;
 		for(i=0; (i<MAXPLAYERS); i++)
 		{
 			
@@ -1541,22 +1609,24 @@ static void HU_DrawChat(void)
 				}
 			}
 			
-			if ((playeringame[i]))
+			if (playeringame[i])
 			{	
 				char name[MAXPLAYERNAME+1];
 				strlcpy(name, player_names[i], 7);	// shorten name to 7 characters.
-				V_DrawFillConsoleMap(chatx-50, p_dispy+ (6*count), 48, 6, 239 | V_SNAPTOTOP | V_SNAPTORIGHT);	// fill it like the chat so the text doesn't become hard to read because of the hud.
-				V_DrawSmallString(chatx-48, p_dispy+ (6*count), V_SNAPTOTOP|V_SNAPTORIGHT|V_ALLOWLOWERCASE, va("\x82%d\x80 - %s", i, name));
+				V_DrawFillConsoleMap(chatx+ cv_chatwidth.value + 2, p_dispy- (6*count), 48, 6, 239 | V_SNAPTOBOTTOM | V_SNAPTOLEFT);	// fill it like the chat so the text doesn't become hard to read because of the hud.
+				V_DrawSmallString(chatx+ cv_chatwidth.value + 4, p_dispy- (6*count), V_SNAPTOBOTTOM|V_SNAPTOLEFT|V_ALLOWLOWERCASE, va("\x82%d\x80 - %s", i, name));
 				count++;
 			}
 		}
 		if (count == 0)	// no results.
 		{
-			V_DrawFillConsoleMap(chatx-50, p_dispy+ (6*count), 48, 6, 239 | V_SNAPTOTOP | V_SNAPTORIGHT);	// fill it like the chat so the text doesn't become hard to read because of the hud.
-			V_DrawSmallString(chatx-48, p_dispy+ (6*count), V_SNAPTOTOP|V_SNAPTORIGHT|V_ALLOWLOWERCASE, "NO RESULT.");
+			V_DrawFillConsoleMap(chatx-50, p_dispy- (6*count), 48, 6, 239 | V_SNAPTOBOTTOM | V_SNAPTOLEFT);	// fill it like the chat so the text doesn't become hard to read because of the hud.
+			V_DrawSmallString(chatx-48, p_dispy- (6*count), V_SNAPTOBOTTOM|V_SNAPTOLEFT|V_ALLOWLOWERCASE, "NO RESULT.");
 		}	
 	}
 	
+	HU_drawChatLog(typelines-1);	// typelines is the # of lines we're typing. If there's more than 1 then the log should scroll up to give us more space.
+	
 }
 
 // why the fuck would you use this...
@@ -1887,7 +1957,7 @@ void HU_Drawer(void)
 		// count down the scroll timer. 
 		if (chat_scrolltime > 0)
 			chat_scrolltime--;
-		if (!cv_consolechat.value)
+		if (!cv_consolechat.value && vid.width > 320)	// don't even try using newchat sub 400p, I'm too fucking lazy
 			HU_DrawChat();
 		else
 			HU_DrawChat_Old();	// why the fuck.........................
@@ -1898,7 +1968,7 @@ void HU_Drawer(void)
 		{	
 			HU_drawMiniChat();		// draw messages in a cool fashion.
 			chat_scrolltime = 0;	// do scroll anyway.
-			typelines = 0;			// make sure that the chat doesn't have a weird blinking huge ass square if we typed a lot last time.
+			typelines = 1;			// make sure that the chat doesn't have a weird blinking huge ass square if we typed a lot last time.
 		}	
 	}
 	

From ad119af07b7770ef15557ea28026deb9ddda0cef Mon Sep 17 00:00:00 2001
From: toaster <rollerorbital@gmail.com>
Date: Thu, 2 Aug 2018 23:52:07 +0100
Subject: [PATCH 29/38] * Get chat to compile with errormode on. * Add chat
 options to the HUD and Sound Option menus.

---
 src/console.c     |   8 +-
 src/g_game.c      |   9 +-
 src/hu_stuff.c    | 441 +++++++++++++++++++++++-----------------------
 src/lua_baselib.c |   1 +
 src/m_menu.c      |  39 ++--
 5 files changed, 254 insertions(+), 244 deletions(-)

diff --git a/src/console.c b/src/console.c
index e6ec039e..eaa98550 100644
--- a/src/console.c
+++ b/src/console.c
@@ -1039,14 +1039,14 @@ boolean CON_Responder(event_t *ev)
 
 	// capslock
 	if (key == KEY_CAPSLOCK)	// it's a toggle.
-	{	
+	{
 		if (capslock)
 			capslock = false;
-		else	
+		else
 			capslock = true;
 		return true;
-	}	
-	
+	}
+
 	if (capslock ^ shiftdown)	// gets capslock to work because capslock is cool
 		key = shiftxform[key];
 
diff --git a/src/g_game.c b/src/g_game.c
index 223e203d..762cac85 100644
--- a/src/g_game.c
+++ b/src/g_game.c
@@ -413,16 +413,17 @@ consvar_t cv_chatwidth = {"chatwidth", "150", CV_SAVE, chatwidth_cons_t, NULL, 0
 
 // chatheight
 static CV_PossibleValue_t chatheight_cons_t[] = {{6, "MIN"}, {22, "MAX"}, {0, NULL}};
-consvar_t cv_chatheight= {"chatheight", "8", CV_SAVE, chatheight_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL};
+consvar_t cv_chatheight = {"chatheight", "8", CV_SAVE, chatheight_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL};
 
 // chat notifications (do you want to hear beeps? I'd understand if you didn't.)
-consvar_t cv_chatnotifications= {"chatnotifications", "On", CV_SAVE, CV_OnOff, NULL, 0, NULL, NULL, 0, 0, NULL};
+consvar_t cv_chatnotifications = {"chatnotifications", "On", CV_SAVE, CV_OnOff, NULL, 0, NULL, NULL, 0, 0, NULL};
 
 // chat spam protection (why would you want to disable that???)
-consvar_t cv_chatspamprotection= {"chatspamprotection", "On", CV_SAVE, CV_OnOff, NULL, 0, NULL, NULL, 0, 0, NULL};
+consvar_t cv_chatspamprotection = {"chatspamprotection", "On", CV_SAVE, CV_OnOff, NULL, 0, NULL, NULL, 0, 0, NULL};
 
 // old shit console chat. (mostly exists for stuff like terminal, not because I cared if anyone liked the old chat.)
-consvar_t cv_consolechat= {"consolechat", "Off", CV_SAVE, CV_OnOff, NULL, 0, NULL, NULL, 0, 0, NULL};
+//static CV_PossibleValue_t consolechat_cons_t[] = {{0, "Box"}, {1, "Console"}, {0, NULL}}; -- for menu, but menu disabled...
+consvar_t cv_consolechat = {"consolechat", "Off", CV_SAVE, CV_OnOff, NULL, 0, NULL, NULL, 0, 0, NULL};
 
 consvar_t cv_crosshair = {"crosshair", "Cross", CV_SAVE, crosshair_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL};
 consvar_t cv_crosshair2 = {"crosshair2", "Cross", CV_SAVE, crosshair_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL};
diff --git a/src/hu_stuff.c b/src/hu_stuff.c
index 29f9c356..dd0d61fe 100644
--- a/src/hu_stuff.c
+++ b/src/hu_stuff.c
@@ -76,7 +76,7 @@ patch_t *cred_font[CRED_FONTSIZE];
 static player_t *plr;
 boolean chat_on; // entering a chat message?
 static char w_chat[HU_MAXMSGLEN];
-static INT32 c_input = 0;	// let's try to make the chat input less shitty.
+static UINT32 c_input = 0;	// let's try to make the chat input less shitty.
 static boolean headsupactive = false;
 boolean hu_showscores; // draw rankings
 static char hu_tick;
@@ -342,10 +342,10 @@ void HU_Start(void)
 
 static UINT32 chat_nummsg_log = 0;
 static UINT32 chat_nummsg_min = 0;
-static UINT32 chat_scroll = 0;		
+static UINT32 chat_scroll = 0;
 static tic_t chat_scrolltime = 0;
 
-static INT32 chat_maxscroll = 0;	// how far can we scroll? 
+static UINT32 chat_maxscroll = 0;	// how far can we scroll?
 
 //static chatmsg_t chat_mini[CHAT_BUFSIZE];	// Display the last few messages sent.
 //static chatmsg_t chat_log[CHAT_BUFSIZE];	// Keep every message sent to us in memory so we can scroll n shit, it's cool.
@@ -358,12 +358,12 @@ static boolean chat_scrollmedown = false;	// force instant scroll down on the ch
 
 // remove text from minichat table
 
-static INT16 addy = 0;	// use this to make the messages scroll smoothly when one fades away 
+static INT16 addy = 0;	// use this to make the messages scroll smoothly when one fades away
 
 static void HU_removeChatText_Mini(void)
 {
     // MPC: Don't create new arrays, just iterate through an existing one
-	int i;
+	UINT32 i;
     for(i=0;i<chat_nummsg_min-1;i++) {
         strcpy(chat_mini[i], chat_mini[i+1]);
         chat_timers[i] = chat_timers[i+1];
@@ -379,27 +379,27 @@ static void HU_removeChatText_Mini(void)
 static void HU_removeChatText_Log(void)
 {
 	// MPC: Don't create new arrays, just iterate through an existing one
-	int i;
+	UINT32 i;
     for(i=0;i<chat_nummsg_log-1;i++) {
         strcpy(chat_log[i], chat_log[i+1]);
     }
     chat_nummsg_log--;	// lost 1 msg.
 }
-	
+
 void HU_AddChatText(const char *text)
 {
-	
+
 	// TODO: check if we're oversaturating the log (we can only log CHAT_BUFSIZE messages.)
-	
+
 	if (chat_nummsg_log >= CHAT_BUFSIZE)
 		HU_removeChatText_Log();
-	
+
 	strcpy(chat_log[chat_nummsg_log], text);
 	chat_nummsg_log++;
-	
+
 	if (chat_nummsg_min >= 8)
 		HU_removeChatText_Mini();
-	
+
 	strcpy(chat_mini[chat_nummsg_min], text);
 	chat_timers[chat_nummsg_min] = TICRATE*cv_chattime.value;
 	chat_nummsg_min++;
@@ -458,7 +458,7 @@ static void DoSayCommand(SINT8 target, size_t usedargs, UINT8 flags)
 			strlcat(msg, " ", msgspace);
 		strlcat(msg, COM_Argv(ix + usedargs), msgspace);
 	}
-	
+
 	if (strlen(msg) > 4 && strnicmp(msg, "/pm", 3) == 0)	// used /pm
 	{
 		// what we're gonna do now is check if the node exists
@@ -468,30 +468,30 @@ static void DoSayCommand(SINT8 target, size_t usedargs, UINT8 flags)
 		strncpy(nodenum, msg+3, 5);
 		// check for undesirable characters in our "number"
 		if 	(((nodenum[0] < '0') || (nodenum[0] > '9')) || ((nodenum[1] < '0') || (nodenum[1] > '9')))
-		{	
+		{
 			// check if nodenum[1] is a space
 			if (nodenum[1] == ' ')
 				spc = 0;
 				// let it slide
 			else
-			{	
+			{
 				HU_AddChatText("\x82NOTICE: \x80Invalid command format. Correct format is \'/pm<node> \'.");
 				return;
-			}	
+			}
 		}
 		// I'm very bad at C, I swear I am, additional checks eww!
 			if (spc != 0)
-			{	
+			{
 				if (msg[5] != ' ')
 				{
 					HU_AddChatText("\x82NOTICE: \x80Invalid command format. Correct format is \'/pm<node> \'.");
 					return;
 				}
 			}
-		
+
 		target = atoi((const char*) nodenum);	// turn that into a number
 		//CONS_Printf("%d\n", target);
-		
+
 		// check for target player, if it doesn't exist then we can't send the message!
 		if (playeringame[target])	// player exists
 			target++;				// even though playernums are from 0 to 31, target is 1 to 32, so up that by 1 to have it work!
@@ -502,7 +502,7 @@ static void DoSayCommand(SINT8 target, size_t usedargs, UINT8 flags)
 		}
 		buf[0] = target;
 		const char *newmsg = msg+5+spc;
-		memcpy(msg, newmsg, 255);
+		memcpy(msg, newmsg, 252);
 	}
 
 	SendNetXCmd(XD_SAY, buf, strlen(msg) + 1 + msg-buf);
@@ -602,7 +602,7 @@ static void Got_Saycmd(UINT8 **p, INT32 playernum)
 	char *msg;
 	boolean action = false;
 	char *ptr;
-	
+
 	CONS_Debug(DBG_NETPLAY,"Received SAY cmd from Player %d (%s)\n", playernum+1, player_names[playernum]);
 
 	target = READSINT8(*p);
@@ -647,31 +647,31 @@ static void Got_Saycmd(UINT8 **p, INT32 playernum)
 			}
 		}
 	}
-	
+
 	int spam_eatmsg = 0;
-	
+
 	// before we do anything, let's verify the guy isn't spamming, get this easier on us.
-	
+
 	//if (stop_spamming_you_cunt[playernum] != 0 && cv_chatspamprotection.value && !(flags & HU_CSAY))
 	if (stop_spamming_you_cunt[playernum] != 0 && consoleplayer != playernum && cv_chatspamprotection.value && !(flags & HU_CSAY))
-	{	
+	{
 		CONS_Debug(DBG_NETPLAY,"Received SAY cmd too quickly from Player %d (%s), assuming as spam and blocking message.\n", playernum+1, player_names[playernum]);
 		stop_spamming_you_cunt[playernum] = 4;
 		spam_eatmsg = 1;
 	}
 	else
 		stop_spamming_you_cunt[playernum] = 4;	// you can hold off for 4 tics, can you?
-	
+
 	// run the lua hook even if we were supposed to eat the msg, netgame consistency goes first.
-	
+
 #ifdef HAVE_BLUA
 	if (LUAh_PlayerMsg(playernum, target, flags, msg, spam_eatmsg))
 		return;
 #endif
-	
+
 	if (spam_eatmsg)
 		return;	// don't proceed if we were supposed to eat the message.
-	
+
 	// If it's a CSAY, just CECHO and be done with it.
 	if (flags & HU_CSAY)
 	{
@@ -713,18 +713,18 @@ static void Got_Saycmd(UINT8 **p, INT32 playernum)
 	{
 		const char *prefix = "", *cstart = "", *cend = "", *adminchar = "\x82~\x83", *remotechar = "\x82@\x83", *fmt, *fmt2;
 		char *tempchar = NULL;
-		
+
 		// In CTF and team match, color the player's name.
 		if (G_GametypeHasTeams())
 		{
 			cend = "";
-			if (players[playernum].ctfteam == 1) // red	
-				cstart = "\x85";		
+			if (players[playernum].ctfteam == 1) // red
+				cstart = "\x85";
 			else if (players[playernum].ctfteam == 2) // blue
 				cstart = "\x84";
-			
+
 		}
-		
+
 		// player is a spectator?
 		if (players[playernum].spectator)
 				cstart = "\x86";	// grey name
@@ -749,15 +749,15 @@ static void Got_Saycmd(UINT8 **p, INT32 playernum)
 		// name, color end, and the message itself.
 		// '\4' makes the message yellow and beeps; '\3' just beeps.
 		if (action)
-		{	
+		{
 			fmt = "\3* %s%s%s%s \x82%s\n";	// don't make /me yellow, yellow will be for mentions and PMs!
 			fmt2 = "* %s%s%s%s \x82%s";
-		}	
+		}
 		else if (target == 0) // To everyone
 		{
 			fmt = "\3%s\x83<%s%s%s\x83>\x80 %s\n";
 			fmt2 = "%s\x83<%s%s%s\x83>\x80 %s";
-		}	
+		}
 		else if (target-1 == consoleplayer) // To you
 		{
 			prefix = "\x82[PM]";
@@ -775,35 +775,35 @@ static void Got_Saycmd(UINT8 **p, INT32 playernum)
 			cstart = "\x82";
 			fmt = "\4%s<%s%s>%s\x80 %s\n";	// make this yellow, however.
 			fmt2 = "%s<%s%s>%s\x80 %s";
-			
+
 		}
 		else // To your team
 		{
-			if (players[playernum].ctfteam == 1) // red	
-				prefix = "\x85[TEAM]";		
+			if (players[playernum].ctfteam == 1) // red
+				prefix = "\x85[TEAM]";
 			else if (players[playernum].ctfteam == 2) // blue
 				prefix = "\x84[TEAM]";
 			else
 				prefix = "\x83";	// makes sure this doesn't implode if you sayteam on non-team gamemodes
-				
+
 			fmt = "\3%s<%s%s>\x80%s %s\n";
 			fmt2 = "%s<%s%s>\x80%s %s";
-		
-		}		
-		
+
+		}
+
 		if (cv_consolechat.value)
-		{	
-			CONS_Printf(fmt, prefix, cstart, dispname, cend, msg);	
+		{
+			CONS_Printf(fmt, prefix, cstart, dispname, cend, msg);
 			HU_AddChatText(va(fmt2, prefix, cstart, dispname, cend, msg));	// add it reguardless, in case we decide to change our mind about our chat type.
-		}	
+		}
 		else
-		{	
+		{
 			HU_AddChatText(va(fmt2, prefix, cstart, dispname, cend, msg));
 			CON_LogMessage(va(fmt, prefix, cstart, dispname, cend, msg));	// save to log.txt
 			if (cv_chatnotifications.value)
 				S_StartSound(NULL, sfx_radio);
-		}	
-		
+		}
+
 		if (tempchar)
 			Z_Free(tempchar);
 	}
@@ -834,12 +834,12 @@ static inline boolean HU_keyInChatString(char *s, char ch)
 			{
 				s[l++] = ch;
 				s[l]=0;
-			}	
+			}
 			else
-			{	
-				
+			{
+
 				// move everything past c_input for new characters:
-				INT32 m = HU_MAXMSGLEN-1;
+				UINT32 m = HU_MAXMSGLEN-1;
 				for (;(m>=c_input);m--)
 				{
 					if (s[m])
@@ -859,16 +859,16 @@ static inline boolean HU_keyInChatString(char *s, char ch)
 		size_t i = c_input;
 		if (!s[i-1])
 			return false;
-		
+
 		if (i >= strlen(s)-1)
-		{	
+		{
 			s[strlen(s)-1] = 0;
 			c_input--;
 			return false;
-		}	
-		
+		}
+
 		for (; (i < HU_MAXMSGLEN); i++)
-		{	
+		{
 			s[i-1] = s[i];
 		}
 		c_input--;
@@ -901,7 +901,7 @@ static boolean teamtalk = false;
 
 //
 //
-static void HU_queueChatChar(char c)
+static void HU_queueChatChar(INT32 c)
 {
 	// send automaticly the message (no more chat char)
 	if (c == KEY_ENTER)
@@ -917,59 +917,59 @@ static void HU_queueChatChar(char c)
 		size_t i = 0;
 		for (;(i<HU_MAXMSGLEN);i++)
 			w_chat[i] = 0;	// reset this.
-		
+
 		c_input = 0;
-		
+
 		// last minute mute check
 		if (cv_mute.value && !(server || IsPlayerAdmin(consoleplayer)))
 		{
 			HU_AddChatText(va("%s>ERROR: The chat is muted. You can't say anything.", "\x85"));
 			return;
 		}
-		
+
 		INT32 target = 0;
-		
+
 		if (strlen(msg) > 4 && strnicmp(msg, "/pm", 3) == 0)	// used /pm
 		{
 			// what we're gonna do now is check if the node exists
 			// with that logic, characters 4 and 5 are our numbers:
-			
+
 			// teamtalk can't send PMs, just don't send it, else everyone would be able to see it, and no one wants to see your sex RP sicko.
 			if (teamtalk)
 			{
 				HU_AddChatText(va("%sCannot send sayto in Say-Team.", "\x85"));
 				return;
-			}	
-			
+			}
+
 			int spc = 1;	// used if nodenum[1] is a space.
 			char *nodenum = (char*) malloc(3);
 			strncpy(nodenum, msg+3, 5);
 			// check for undesirable characters in our "number"
 			if 	(((nodenum[0] < '0') || (nodenum[0] > '9')) || ((nodenum[1] < '0') || (nodenum[1] > '9')))
-			{	
+			{
 				// check if nodenum[1] is a space
 				if (nodenum[1] == ' ')
 					spc = 0;
 					// let it slide
 				else
-				{	
+				{
 					HU_AddChatText("\x82NOTICE: \x80Invalid command format. Correct format is \'/pm<node> \'.");
 					return;
-				}	
+				}
 			}
 			// I'm very bad at C, I swear I am, additional checks eww!
 			if (spc != 0)
-			{	
+			{
 				if (msg[5] != ' ')
 				{
 					HU_AddChatText("\x82NOTICE: \x80Invalid command format. Correct format is \'/pm<node> \'.");
 					return;
 				}
 			}
-			
+
 			target = atoi((const char*) nodenum);	// turn that into a number
 			//CONS_Printf("%d\n", target);
-		
+
 			// check for target player, if it doesn't exist then we can't send the message!
 			if (playeringame[target])	// player exists
 				target++;				// even though playernums are from 0 to 31, target is 1 to 32, so up that by 1 to have it work!
@@ -981,14 +981,14 @@ static void HU_queueChatChar(char c)
 			// we need to get rid of the /pm<node>
 			const char *newmsg = msg+5+spc;
 			memcpy(msg, newmsg, 255);
-		}	
+		}
 		if (ci > 3) // don't send target+flags+empty message.
 		{
 			if (teamtalk)
 				buf[0] = -1; // target
 			else
 				buf[0] = target;
-			
+
 			buf[1] = 0; // flags
 			SendNetXCmd(XD_SAY, buf, 2 + strlen(&buf[2]) + 1);
 		}
@@ -1013,13 +1013,13 @@ static boolean justscrolledup;
 //
 boolean HU_Responder(event_t *ev)
 {
-	UINT8 c=0;
-		
+	INT32 c=0;
+
 	if (ev->type != ev_keydown)
 		return false;
 
 	// only KeyDown events now...
-	
+
 	if (!chat_on)
 	{
 		// enter chat mode
@@ -1048,7 +1048,7 @@ boolean HU_Responder(event_t *ev)
 	}
 	else // if chat_on
 	{
-		
+
 		// Ignore modifier keys
 		// Note that we do this here so users can still set
 		// their chat keys to one of these, if they so desire.
@@ -1057,39 +1057,39 @@ boolean HU_Responder(event_t *ev)
 		 || ev->data1 == KEY_LALT || ev->data1 == KEY_RALT)
 			return true;
 
-		c = (UINT8)ev->data1;
-		
+		c = (INT32)ev->data1;
+
 		// capslock
 		if (c && c == KEY_CAPSLOCK)	// it's a toggle.
-		{	
+		{
 			if (capslock)
 				capslock = false;
-			else	
+			else
 				capslock = true;
 			return true;
-		}	
-		
-		// use console translations		
+		}
+
+		// use console translations
 		if (shiftdown ^ capslock)
 			c = shiftxform[c];
-		
+
 		// TODO: make chat behave like the console, so that we can go back and edit stuff when we fuck up.
-		
+
 		// pasting. pasting is cool. chat is a bit limited, though :(
 		if ((c == 'v' || c == 'V') && ctrldown)
 		{
 			const char *paste = I_ClipboardPaste();
-			
+
 			// create a dummy string real quickly
-			
+
 			if (paste == NULL)
 				return true;
-			
+
 			size_t chatlen = strlen(w_chat);
 			size_t pastelen = strlen(paste);
 			if (chatlen+pastelen > HU_MAXMSGLEN)
 				return true; // we can't paste this!!
-			
+
 			if (c_input >= strlen(w_chat))	// add it at the end of the string.
 			{
 				memcpy(&w_chat[chatlen], paste, pastelen);	// copy all of that.
@@ -1102,43 +1102,43 @@ boolean HU_Responder(event_t *ev)
 				return true;
 			}
 			else	// otherwise, we need to shift everything and make space, etc etc
-			{	
+			{
 				size_t i = HU_MAXMSGLEN-1;
 				for (; i>=c_input;i--)
 				{
 					if (w_chat[i])
 						w_chat[i+pastelen] = w_chat[i];
-					
+
 				}
 				memcpy(&w_chat[c_input], paste, pastelen);	// copy all of that.
 				c_input += pastelen;
 				return true;
 			}
 		}
-		
+
 		if (HU_keyInChatString(w_chat,c))
-		{	
+		{
 			HU_queueChatChar(c);
-		}	
+		}
 		if (c == KEY_ENTER)
-		{	
+		{
 			chat_on = false;
 			c_input = 0;			// reset input cursor
 			chat_scrollmedown = true; // you hit enter, so you might wanna autoscroll to see what you just sent. :)
-		}	
+		}
 		else if (c == KEY_ESCAPE)
-		{	
+		{
 			chat_on = false;
 			c_input = 0;			// reset input cursor
-		}	
+		}
 		else if ((c == KEY_UPARROW || c == KEY_MOUSEWHEELUP) && chat_scroll > 0)	// CHAT SCROLLING YAYS!
 		{
 			chat_scroll--;
 			justscrolledup = true;
 			chat_scrolltime = 4;
-		}	
+		}
 		else if ((c == KEY_DOWNARROW || c == KEY_MOUSEWHEELDOWN) && chat_scroll < chat_maxscroll && chat_maxscroll > 0)
-		{	
+		{
 			chat_scroll++;
 			justscrolleddown = true;
 			chat_scrolltime = 4;
@@ -1146,7 +1146,7 @@ boolean HU_Responder(event_t *ev)
 		else if (c == KEY_LEFTARROW && c_input != 0)	// i said go back
 			c_input--;
 		else if (c == KEY_RIGHTARROW && c_input < strlen(w_chat))
-			c_input++;	
+			c_input++;
 		return true;
 	}
 	return false;
@@ -1187,7 +1187,7 @@ static UINT8 *CHAT_GetStringColormap(INT32 colorflags)	// pasted from video.c, s
 // This is a muuuch better method than V_WORDWRAP.
 // again stolen and modified a bit from video.c, don't mind me, will need to rearrange this one day.
 // this one is simplified for the chat drawer.
-char *CHAT_WordWrap(INT32 x, INT32 w, INT32 option, const char *string)
+static char *CHAT_WordWrap(INT32 x, INT32 w, INT32 option, const char *string)
 {
 	int c;
 	size_t chw, i, lastusablespace = 0;
@@ -1229,7 +1229,7 @@ char *CHAT_WordWrap(INT32 x, INT32 w, INT32 option, const char *string)
 		{
 			//CONS_Printf("Wrap at index %d\n", i);
 			newstring[lastusablespace] = '\n';
-			i = lastusablespace+1;	
+			i = lastusablespace+1;
 			lastusablespace = 0;
 			x = 0;
 		}
@@ -1247,29 +1247,27 @@ INT16 chatx = 13, chaty = 169;	// let's use this as our coordinates, shh
 
 static void HU_drawMiniChat(void)
 {
-	
 	if (!chat_nummsg_min)
 		return;	// needless to say it's useless to do anything if we don't have anything to draw.
-	
-	
-	INT32 x = chatx+2; 
-	INT32 charwidth = 4, charheight = 6;	
+
+	INT32 x = chatx+2;
+	INT32 charwidth = 4, charheight = 6;
 	INT32 dx = 0, dy = 0;
 	size_t i = chat_nummsg_min;
-	
+
 	INT32 msglines = 0;
 	// process all messages once without rendering anything or doing anything fancy so that we know how many lines each message has...
-	
+
 	for (; i>0; i--)
-	{	
+	{
 		const char *msg = CHAT_WordWrap(x, cv_chatwidth.value-charwidth, V_SNAPTOBOTTOM|V_SNAPTOLEFT|V_ALLOWLOWERCASE, chat_mini[i-1]);
 		size_t j = 0;
 		INT32 linescount = 0;
-		
+
 		while(msg[j])	// iterate through msg
-		{	
+		{
 			if (msg[j] < HU_FONTSTART)	// don't draw
-			{			
+			{
 				if (msg[j] == '\n')	// get back down.
 				{
 					++j;
@@ -1282,14 +1280,14 @@ static void HU_drawMiniChat(void)
 					++j;
 					continue;
 				}
-				
-				++j;	
+
+				++j;
 			}
 			else
 			{
 				j++;
 			}
-			
+
 			dx += charwidth;
 			if (dx >= cv_chatwidth.value)
 			{
@@ -1301,25 +1299,24 @@ static void HU_drawMiniChat(void)
 		dx = 0;
 		msglines += linescount+1;
 	}
-	
+
 	INT32 y = chaty - charheight*(msglines+1) - (cv_kartspeedometer.value ? 16 : 0);
-	dx = 0; 
+	dx = 0;
 	dy = 0;
 	i = 0;
-	
+
 	for (; i<=(chat_nummsg_min-1); i++)	// iterate through our hot messages
 	{
-		
 		INT32 clrflag = 0;
-		INT32 timer = ((cv_chattime.value*TICRATE)-chat_timers[i]) - cv_chattime.value*TICRATE+9;	// see below...		
+		INT32 timer = ((cv_chattime.value*TICRATE)-chat_timers[i]) - cv_chattime.value*TICRATE+9;	// see below...
 		INT32 transflag = (timer >= 0 && timer <= 9) ? (timer*V_10TRANS) : 0;	// you can make bad jokes out of this one.
 		size_t j = 0;
 		const char *msg = CHAT_WordWrap(x, cv_chatwidth.value-charwidth, V_SNAPTOBOTTOM|V_SNAPTOLEFT|V_ALLOWLOWERCASE, chat_mini[i]);	// get the current message, and word wrap it.
-		
+
 		while(msg[j])	// iterate through msg
-		{	
+		{
 			if (msg[j] < HU_FONTSTART)	// don't draw
-			{			
+			{
 				if (msg[j] == '\n')	// get back down.
 				{
 					++j;
@@ -1333,15 +1330,15 @@ static void HU_drawMiniChat(void)
 					++j;
 					continue;
 				}
-				
-				++j;	
+
+				++j;
 			}
 			else
 			{
 				UINT8 *colormap = CHAT_GetStringColormap(clrflag);
 				V_DrawChatCharacter(x + dx + 2, y+dy, msg[j++] |V_SNAPTOBOTTOM|V_SNAPTOLEFT|transflag, !cv_allcaps.value, colormap);
 			}
-			
+
 			dx += charwidth;
 			if (dx >= cv_chatwidth.value)
 			{
@@ -1352,10 +1349,10 @@ static void HU_drawMiniChat(void)
 		dy += charheight;
 		dx = 0;
 	}
-		
+
 	// decrement addy and make that shit smooth:
 	addy /= 2;
-	
+
 }
 
 // HU_DrawUpArrow
@@ -1367,7 +1364,7 @@ static void HU_DrawUpArrow(INT32 x, INT32 y, INT32 options)
 	V_DrawFill(x+2, y, 1, 1, 103|options);
 	V_DrawFill(x+1, y+1, 3, 1, 103|options);
 	V_DrawFill(x, y+2, 5, 1, 103|options);	// that's the yellow part, I swear
-	
+
 	V_DrawFill(x+3, y, 1, 1, 26|options);
 	V_DrawFill(x+4, y+1, 1, 1, 26|options);
 	V_DrawFill(x+5, y+2, 1, 1, 26|options);
@@ -1384,40 +1381,40 @@ static void HU_DrawDownArrow(INT32 x, INT32 y, INT32 options)
 	V_DrawFill(x, y+1, 5, 1, 26|options);
 	V_DrawFill(x+1, y+2, 3, 1, 26|options);
 	V_DrawFill(x+2, y+3, 1, 1, 26|options);	// that's the black part. no racism intended. i swear.
-	
+
 	V_DrawFill(x, y, 5, 1, 103|options);
 	V_DrawFill(x+1, y+1, 3, 1, 103|options);
 	V_DrawFill(x+2, y+2, 1, 1, 103|options);	// that's the yellow part, I swear
-}	
+}
 
 // HU_DrawChatLog
 // TODO: fix dumb word wrapping issues
 
 static void HU_drawChatLog(INT32 offset)
 {
-	
+
 	// before we do anything, make sure that our scroll position isn't "illegal";
 	if (chat_scroll > chat_maxscroll)
 		chat_scroll = chat_maxscroll;
-	
+
 	INT32 charwidth = 4, charheight = 6;
 	INT32 x = chatx+2, y = chaty - offset*charheight - (chat_scroll*charheight) - cv_chatheight.value*charheight - 12 - (cv_kartspeedometer.value ? 16 : 0), dx = 0, dy = 0;
-	size_t i = 0;
+	UINT32 i = 0;
 	INT32 chat_topy = y + chat_scroll*charheight;
 	INT32 chat_bottomy = chat_topy + cv_chatheight.value*charheight;
 	boolean atbottom = false;
-	
+
 	V_DrawFillConsoleMap(chatx, chat_topy, cv_chatwidth.value, cv_chatheight.value*charheight +2, 239|V_SNAPTOBOTTOM|V_SNAPTOLEFT);	// log box
-		
+
 	for (i=0; i<chat_nummsg_log; i++)	// iterate through our chatlog
 	{
 		INT32 clrflag = 0;
-		size_t j = 0;
+		INT32 j = 0;
 		const char *msg = CHAT_WordWrap(x, cv_chatwidth.value-charwidth, V_SNAPTOBOTTOM|V_SNAPTOLEFT|V_ALLOWLOWERCASE, chat_log[i]);	// get the current message, and word wrap it.
 		while(msg[j])	// iterate through msg
-		{	
+		{
 			if (msg[j] < HU_FONTSTART)	// don't draw
-			{			
+			{
 				if (msg[j] == '\n')	// get back down.
 				{
 					++j;
@@ -1431,20 +1428,20 @@ static void HU_drawChatLog(INT32 offset)
 					++j;
 					continue;
 				}
-				
-				++j;	
+
+				++j;
 			}
 			else
 			{
 				if ((y+dy+2 >= chat_topy) && (y+dy < (chat_bottomy)))
-				{	
+				{
 					UINT8 *colormap = CHAT_GetStringColormap(clrflag);
 					V_DrawChatCharacter(x + dx + 2, y+dy+2, msg[j++] |V_SNAPTOBOTTOM|V_SNAPTOLEFT, !cv_allcaps.value, colormap);
 				}
 				else
 					j++;	// don't forget to increment this or we'll get stuck in the limbo.
 			}
-			
+
 			dx += charwidth;
 			if (dx >= cv_chatwidth.value-charwidth-2 && i<chat_nummsg_log && msg[j] >= HU_FONTSTART) // end of message shouldn't count, nor should invisible characters!!!!
 			{
@@ -1455,32 +1452,34 @@ static void HU_drawChatLog(INT32 offset)
 		dy += charheight;
 		dx = 0;
 	}
-	
+
 	if (((chat_scroll >= chat_maxscroll) || (chat_scrollmedown)) && !(justscrolleddown || justscrolledup || chat_scrolltime))	// was already at the bottom of the page before new maxscroll calculation and was NOT scrolling.
 	{
 		atbottom = true;	// we should scroll
 	}
 	chat_scrollmedown = false;
-	
+
 	// getmaxscroll through a lazy hack. We do all these loops, so let's not do more loops that are gonna lag the game more. :P
-	chat_maxscroll = (dy/charheight)-cv_chatheight.value;	// welcome to C, we don't know what min() and max() are.
-	if (chat_maxscroll < 0)
+	chat_maxscroll = (dy/charheight);	// welcome to C, we don't know what min() and max() are.
+	if (chat_maxscroll <= (UINT32)cv_chatheight.value)
 		chat_maxscroll = 0;
-	
+	else
+		chat_maxscroll -= cv_chatheight.value;
+
 	// if we're not bound by the time, autoscroll for next frame:
 	if (atbottom)
 		chat_scroll = chat_maxscroll;
-	
+
 	// draw arrows to indicate that we can (or not) scroll.
-	
+
 	if (chat_scroll > 0)
 		HU_DrawUpArrow(chatx-8, ((justscrolledup) ? (chat_topy-1) : (chat_topy)), V_SNAPTOBOTTOM | V_SNAPTOLEFT);
 	if (chat_scroll < chat_maxscroll)
 		HU_DrawDownArrow(chatx-8, chat_bottomy-((justscrolleddown) ? 3 : 4), V_SNAPTOBOTTOM | V_SNAPTOLEFT);
-	
+
 	justscrolleddown = false;
 	justscrolledup = false;
-}	
+}
 
 //
 // HU_DrawChat
@@ -1490,13 +1489,13 @@ static void HU_drawChatLog(INT32 offset)
 
 static INT16 typelines = 1;	// number of drawfill lines we need. it's some weird hack and might be one frame off but I'm lazy to make another loop.
 static void HU_DrawChat(void)
-{	
+{
 	INT32 charwidth = 4, charheight = 6;
 	INT32 t = 0, c = 0, y = chaty - (typelines*charheight)  - (cv_kartspeedometer.value ? 16 : 0);
-	size_t i = 0;
+	UINT32 i = 0;
 	const char *ntalk = "Say: ", *ttalk = "Team: ";
 	const char *talk = ntalk;
-	
+
 	if (teamtalk)
 	{
 		talk = ttalk;
@@ -1507,9 +1506,9 @@ static void HU_DrawChat(void)
 			t = 0x400; // Blue
 #endif
 	}
-			
+
 	V_DrawFillConsoleMap(chatx, y-1, cv_chatwidth.value, (typelines*charheight), 239 | V_SNAPTOBOTTOM | V_SNAPTOLEFT);
-	
+
 	while (talk[i])
 	{
 		if (talk[i] < HU_FONTSTART)
@@ -1519,13 +1518,13 @@ static void HU_DrawChat(void)
 
 		c += charwidth;
 	}
-	
+
 	i = 0;
 	typelines = 1;
-	
+
 	if ((strlen(w_chat) == 0 || c_input == 0) && hu_tick < 4)
 		V_DrawChatCharacter(chatx + 2 + c, y+1, '_' |V_SNAPTOBOTTOM|V_SNAPTOLEFT|t, !cv_allcaps.value, NULL);
-		
+
 	while (w_chat[i])
 	{
 		boolean skippedline = false;
@@ -1534,16 +1533,16 @@ static void HU_DrawChat(void)
 			int cursorx = (c+charwidth < cv_chatwidth.value-charwidth) ? (chatx + 2 + c+charwidth) : (chatx+1);	// we may have to go down.
 			int cursory = (cursorx != chatx+1) ? (y) : (y+charheight);
 			if (hu_tick < 4)
-				V_DrawChatCharacter(cursorx, cursory+1, '_' |V_SNAPTOBOTTOM|V_SNAPTOLEFT|t, !cv_allcaps.value, NULL);	
-			
+				V_DrawChatCharacter(cursorx, cursory+1, '_' |V_SNAPTOBOTTOM|V_SNAPTOLEFT|t, !cv_allcaps.value, NULL);
+
 			if (cursorx == chatx+1)	// a weirdo hack
 			{
 				typelines += 1;
 				skippedline = true;
-			}	
-			
-		}	
-		
+			}
+
+		}
+
 		//Hurdler: isn't it better like that?
 		if (w_chat[i] < HU_FONTSTART)
 			++i;
@@ -1561,56 +1560,56 @@ static void HU_DrawChat(void)
 
 	// handle /pm list.
 	if (strnicmp(w_chat, "/pm", 3) == 0 && vid.width >= 400 && !teamtalk)	// 320x200 unsupported kthxbai
-	{	
+	{
 		i = 0;
-		int count = 0;
+		INT32 count = 0;
 		INT32 p_dispy = chaty - charheight -1;
 		for(i=0; (i<MAXPLAYERS); i++)
 		{
-			
+
 			// filter: (code needs optimization pls help I'm bad with C)
 			if (w_chat[3])
 			{
-				
+
 				// right, that's half important: (w_chat[4] may be a space since /pm0 msg is perfectly acceptable!)
 				if ( ( ((w_chat[3] != 0) && ((w_chat[3] < '0') || (w_chat[3] > '9'))) || ((w_chat[4] != 0) && (((w_chat[4] < '0') || (w_chat[4] > '9'))))) && (w_chat[4] != ' '))
 					break;
-					
-				
+
+
 				char *nodenum = (char*) malloc(3);
 				strncpy(nodenum, w_chat+3, 4);
-				INT32 n = atoi((const char*) nodenum);	// turn that into a number
+				UINT32 n = atoi((const char*) nodenum);	// turn that into a number
 				// special cases:
-				
+
 				if ((n == 0) && !(w_chat[4] == '0'))
-				{	
+				{
 					if (!(i<10))
-						continue;		
+						continue;
 				}
 				else if ((n == 1) && !(w_chat[3] == '0'))
-				{	
+				{
 					if (!((i == 1) || ((i >= 10) && (i <= 19))))
 						continue;
 				}
 				else if ((n == 2) && !(w_chat[3] == '0'))
-				{	
+				{
 					if (!((i == 2) || ((i >= 20) && (i <= 29))))
 						continue;
 				}
 				else if ((n == 3) && !(w_chat[3] == '0'))
-				{	
+				{
 					if (!((i == 3) || ((i >= 30) && (i <= 31))))
 						continue;
 				}
 				else	// general case.
-				{	
+				{
 					if (i != n)
 						continue;
 				}
 			}
-			
+
 			if (playeringame[i])
-			{	
+			{
 				char name[MAXPLAYERNAME+1];
 				strlcpy(name, player_names[i], 7);	// shorten name to 7 characters.
 				V_DrawFillConsoleMap(chatx+ cv_chatwidth.value + 2, p_dispy- (6*count), 48, 6, 239 | V_SNAPTOBOTTOM | V_SNAPTOLEFT);	// fill it like the chat so the text doesn't become hard to read because of the hud.
@@ -1622,11 +1621,11 @@ static void HU_DrawChat(void)
 		{
 			V_DrawFillConsoleMap(chatx-50, p_dispy- (6*count), 48, 6, 239 | V_SNAPTOBOTTOM | V_SNAPTOLEFT);	// fill it like the chat so the text doesn't become hard to read because of the hud.
 			V_DrawSmallString(chatx-48, p_dispy- (6*count), V_SNAPTOBOTTOM|V_SNAPTOLEFT|V_ALLOWLOWERCASE, "NO RESULT.");
-		}	
+		}
 	}
-	
+
 	HU_drawChatLog(typelines-1);	// typelines is the # of lines we're typing. If there's more than 1 then the log should scroll up to give us more space.
-	
+
 }
 
 // why the fuck would you use this...
@@ -1664,21 +1663,21 @@ static void HU_DrawChat_Old(void)
 		}
 		c += charwidth;
 	}
-	
+
 	if ((strlen(w_chat) == 0 || c_input == 0) && hu_tick < 4)
 		V_DrawCharacter(HU_INPUTX+c, y+2*con_scalefactor, '_' |cv_constextsize.value | V_NOSCALESTART|t, !cv_allcaps.value);
-	
+
 	i = 0;
 	while (w_chat[i])
 	{
-		
+
 		if (c_input == (i+1) && hu_tick < 4)
 		{
 			int cursorx = (HU_INPUTX+c+charwidth < vid.width) ? (HU_INPUTX + c + charwidth) : (HU_INPUTX);	// we may have to go down.
 			int cursory = (cursorx != HU_INPUTX) ? (y) : (y+charheight);
-			V_DrawCharacter(cursorx, cursory+2*con_scalefactor, '_' |cv_constextsize.value | V_NOSCALESTART|t, !cv_allcaps.value);	
-		}	
-		
+			V_DrawCharacter(cursorx, cursory+2*con_scalefactor, '_' |cv_constextsize.value | V_NOSCALESTART|t, !cv_allcaps.value);
+		}
+
 		//Hurdler: isn't it better like that?
 		if (w_chat[i] < HU_FONTSTART)
 		{
@@ -1953,8 +1952,8 @@ void HU_Drawer(void)
 {
 	// draw chat string plus cursor
 	if (chat_on)
-	{	
-		// count down the scroll timer. 
+	{
+		// count down the scroll timer.
 		if (chat_scrolltime > 0)
 			chat_scrolltime--;
 		if (!cv_consolechat.value && vid.width > 320)	// don't even try using newchat sub 400p, I'm too fucking lazy
@@ -1965,27 +1964,27 @@ void HU_Drawer(void)
 	else
 	{
 		if (!cv_consolechat.value)
-		{	
+		{
 			HU_drawMiniChat();		// draw messages in a cool fashion.
 			chat_scrolltime = 0;	// do scroll anyway.
 			typelines = 1;			// make sure that the chat doesn't have a weird blinking huge ass square if we typed a lot last time.
-		}	
+		}
 	}
-	
+
 	if (netgame)	// would handle that in hu_drawminichat, but it's actually kinda awkward when you're typing a lot of messages. (only handle that in netgames duh)
 	{
 		size_t i = 0;
-		
+
 		// handle spam while we're at it:
 		for(; (i<MAXPLAYERS); i++)
-		{	
+		{
 			if (stop_spamming_you_cunt[i] > 0)
 				stop_spamming_you_cunt[i]--;
-		}	
-		
+		}
+
 		// handle chat timers
 		for (i=0; (i<chat_nummsg_min); i++)
-		{	
+		{
 			if (chat_timers[i] > 0)
 				chat_timers[i]--;
 			else
@@ -2036,7 +2035,7 @@ void HU_Drawer(void)
 
 		if (cv_crosshair2.value && !camera2.chase && !players[secondarydisplayplayer].spectator)
 			HU_DrawCrosshair2();
-	
+
 		if (cv_crosshair3.value && !camera3.chase && !players[thirddisplayplayer].spectator)
 			HU_DrawCrosshair3();
 
@@ -2145,29 +2144,29 @@ void HU_drawPing(INT32 x, INT32 y, INT32 ping, boolean notext)
 	SINT8 i = 0;
 	SINT8 yoffset = 6;
 	if (ping < 128)
-	{	
+	{
 		numbars = 3;
 		barcolor = 184;
-	}	
+	}
 	else if (ping < 256)
-	{	
+	{
 		numbars = 2;	// Apparently ternaries w/ multiple statements don't look good in C so I decided against it.
 		barcolor = 103;
-	}	
-	
+	}
+
 	INT32 dx = x+1 - (V_SmallStringWidth(va("%dms", ping), V_ALLOWLOWERCASE)/2);
 	if (!notext || vid.width >= 640)	// how sad, we're using a shit resolution.
 		V_DrawSmallString(dx, y+4, V_ALLOWLOWERCASE, va("%dms", ping));
-	
+
 	for (i=0; (i<3); i++)		// Draw the ping bar
-	{	
+	{
 		V_DrawFill(x+2 *(i-1), y+yoffset-4, 2, 8-yoffset, 31);
 		if (i < numbars)
 			V_DrawFill(x+2 *(i-1), y+yoffset-3, 1, 8-yoffset-1, barcolor);
-		
+
 		yoffset -= 2;
 	}
-}	
+}
 
 //
 // HU_DrawTabRankings
@@ -2192,13 +2191,13 @@ void HU_DrawTabRankings(INT32 x, INT32 y, playersort_t *tab, INT32 scorelines, I
 	{
 		if (players[tab[i].num].spectator || !players[tab[i].num].mo)
 			continue; //ignore them.
-		
+
 		if (!splitscreen)	// don't draw it on splitscreen,
 		{
 			if (!(tab[i].num == serverplayer))
 				HU_drawPing(x+ 253, y+2, playerpingtable[tab[i].num], false);
-		}	
-		
+		}
+
 		V_DrawString(x + 20, y,
 			((tab[i].num == whiteplayer)
 				? hilicol|V_ALLOWLOWERCASE
@@ -2332,10 +2331,10 @@ void HU_DrawTabRankings(INT32 x, INT32 y, playersort_t *tab, INT32 scorelines, I
 		}
 		V_DrawRightAlignedThinString(x+120, y-1, ((players[tab[i].num].health > 0) ? 0 : V_TRANSLUCENT), va("%u", tab[i].count));
 		if (!splitscreen)
-		{	
+		{
 			if (!(tab[i].num == serverplayer))
 				HU_drawPing(x+ 113, y+2, playerpingtable[tab[i].num], false);
-		}	
+		}
 	}
 }
 
diff --git a/src/lua_baselib.c b/src/lua_baselib.c
index 587f9514..068ae204 100644
--- a/src/lua_baselib.c
+++ b/src/lua_baselib.c
@@ -20,6 +20,7 @@
 #include "m_random.h"
 #include "s_sound.h"
 #include "g_game.h"
+#include "hu_stuff.h"
 #include "k_kart.h"
 
 #include "lua_script.h"
diff --git a/src/m_menu.c b/src/m_menu.c
index 63eae220..4428ab3a 100644
--- a/src/m_menu.c
+++ b/src/m_menu.c
@@ -1047,7 +1047,7 @@ static menuitem_t OP_MainMenu[] =
 	{IT_SUBMENU|IT_STRING,		NULL, "Video Options...",		&OP_VideoOptionsDef,		 30},
 	{IT_SUBMENU|IT_STRING,		NULL, "Sound Options...",		&OP_SoundOptionsDef,		 40},
 
-	{IT_SUBMENU|IT_STRING,		NULL, "HUD Options...",		&OP_HUDOptionsDef,			 60},
+	{IT_SUBMENU|IT_STRING,		NULL, "HUD Options...",			&OP_HUDOptionsDef,			 60},
 	{IT_STRING|IT_CALL,			NULL, "Screenshot Options...",	M_ScreenshotOptions,		 70},
 
 	{IT_SUBMENU|IT_STRING,		NULL, "Gameplay Options...",	&OP_GameOptionsDef,			 90},
@@ -1334,13 +1334,14 @@ static menuitem_t OP_SoundOptionsMenu[] =
 
 	{IT_STRING|IT_CALL,			NULL, "Restart Audio System",	M_RestartAudio,			 50},
 
-	{IT_STRING|IT_CVAR,			NULL, "Reverse L/R Channels",	&stereoreverse,			 70},
-	{IT_STRING|IT_CVAR,			NULL, "Surround Sound",			&surround,				 80},
+	{IT_STRING|IT_CVAR,			NULL, "Reverse L/R Channels",	&stereoreverse,			 65},
+	{IT_STRING|IT_CVAR,			NULL, "Surround Sound",			&surround,				 75},
 
+	{IT_STRING|IT_CVAR,			NULL, "Chat sounds",			&cv_chatnotifications,	 90},
 	{IT_STRING|IT_CVAR,			NULL, "Character voices",		&cv_kartvoices,			100},
 	{IT_STRING|IT_CVAR,			NULL, "Powerup Warning",		&cv_kartinvinsfx,		110},
 
-	{IT_KEYHANDLER|IT_STRING,	NULL, "Sound Test",				M_HandleSoundTest,		130},
+	{IT_KEYHANDLER|IT_STRING,	NULL, "Sound Test",				M_HandleSoundTest,		125},
 };
 
 /*static menuitem_t OP_DataOptionsMenu[] =
@@ -1393,19 +1394,27 @@ static menuitem_t OP_EraseDataMenu[] =
 
 static menuitem_t OP_HUDOptionsMenu[] =
 {
-	{IT_STRING | IT_CVAR, NULL, "Show HUD (F3)",					&cv_showhud,			 10},
+	{IT_STRING | IT_CVAR, NULL, "Show HUD (F3)",			&cv_showhud,			 10},
 	{IT_STRING | IT_CVAR | IT_CV_SLIDER,
 	                      NULL, "HUD Visibility",			&cv_translucenthud,		 20},
 
 	{IT_STRING | IT_CVAR | IT_CV_SLIDER,
-						  NULL, "Minimap Visibility",		&cv_kartminimap,		 40},
-	{IT_STRING | IT_CVAR, NULL, "Speedometer Display",		&cv_kartspeedometer,	 50},
-	{IT_STRING | IT_CVAR, NULL, "Show \"CHECK\"",			&cv_kartcheck,			 60},
+						  NULL, "Minimap Visibility",		&cv_kartminimap,		 35},
+	{IT_STRING | IT_CVAR, NULL, "Speedometer Display",		&cv_kartspeedometer,	 45},
+	{IT_STRING | IT_CVAR, NULL, "Show \"CHECK\"",			&cv_kartcheck,			 55},
 
-	{IT_STRING | IT_CVAR, NULL, "Console Color",			&cons_backcolor,		 80},
-	{IT_STRING | IT_CVAR, NULL, "Console Text Size",		&cv_constextsize,		 90},
+	{IT_STRING | IT_CVAR, NULL, "Menu Highlights",			&cons_menuhighlight,     70},
+	// highlight info - (GOOD HIGHLIGHT, WARNING HIGHLIGHT) - 80 (see M_DrawHUDOptions)
 
-	{IT_STRING | IT_CVAR, NULL, "Menu Highlights",			&cons_menuhighlight,    110},
+	//{IT_STRING | IT_CVAR, NULL, "Chat mode",				&cv_consolechat,		 95}, -- will ANYONE who doesn't know how to use the console want to touch this
+	{IT_STRING | IT_CVAR | IT_CV_SLIDER,
+	                      NULL, "Chat box width",			&cv_chatwidth,		 	 95},
+	{IT_STRING | IT_CVAR | IT_CV_SLIDER,
+	                      NULL, "Chat box height",			&cv_chatheight,		 	105},
+	{IT_STRING | IT_CVAR, NULL, "Chat fadeout time",		&cv_chattime,			115},
+
+	{IT_STRING | IT_CVAR, NULL, "Background Color",			&cons_backcolor,		130},
+	{IT_STRING | IT_CVAR, NULL, "Console Text Size",		&cv_constextsize,		140},
 };
 
 static menuitem_t OP_GameOptionsMenu[] =
@@ -1420,7 +1429,7 @@ static menuitem_t OP_GameOptionsMenu[] =
 	{IT_STRING | IT_CVAR, NULL, "Exit Countdown Timer",			&cv_countdowntime,		 80},
 
 	//{IT_STRING | IT_CVAR, NULL, "Time Limit",					&cv_timelimit,			100},
-	{IT_STRING | IT_CVAR, NULL, "Starting Bumpers",			&cv_kartbumpers,		100},
+	{IT_STRING | IT_CVAR, NULL, "Starting Bumpers",				&cv_kartbumpers,		100},
 	{IT_STRING | IT_CVAR, NULL, "Karma Comeback",				&cv_kartcomeback,		110},
 
 	{IT_STRING | IT_CVAR, NULL, "Force Character #",			&cv_forceskin,          130},
@@ -1440,10 +1449,10 @@ static menuitem_t OP_ServerOptionsMenu[] =
 
 #ifndef NONET
 	{IT_STRING | IT_CVAR,    NULL, "Max Player Count",				&cv_maxplayers,			 80},
-	{IT_STRING | IT_CVAR,    NULL, "Allow Players to Join",		&cv_allownewplayer,		 90},
+	{IT_STRING | IT_CVAR,    NULL, "Allow Players to Join",			&cv_allownewplayer,		 90},
 	//{IT_STRING | IT_CVAR,    NULL, "Join on Map Change",			&cv_joinnextround,		100},
 
-	{IT_STRING | IT_CVAR,    NULL, "Allow WAD Downloading",		&cv_downloading,		100},
+	{IT_STRING | IT_CVAR,    NULL, "Allow WAD Downloading",			&cv_downloading,		100},
 	{IT_STRING | IT_CVAR,    NULL, "Attempts to Resynch",			&cv_resynchattempts,	110},
 #endif
 };
@@ -8364,7 +8373,7 @@ static void M_DrawHUDOptions(void)
 	const char *str1 = " Warning highlight";
 	const char *str2 = ",";
 	const char *str3 = "Good highlight";
-	INT32 x = BASEVIDWIDTH - currentMenu->x + 2, y = currentMenu->y + 120;
+	INT32 x = BASEVIDWIDTH - currentMenu->x + 2, y = currentMenu->y + 80;
 	INT32 w0 = V_StringWidth(str0, 0), w1 = V_StringWidth(str1, 0), w2 = V_StringWidth(str2, 0), w3 = V_StringWidth(str3, 0);
 	M_DrawGenericMenu();
 	x -= w0;

From b319c2d9b848bfcf5fa83d4ac6ce2dd221a4d58f Mon Sep 17 00:00:00 2001
From: toaster <rollerorbital@gmail.com>
Date: Fri, 3 Aug 2018 23:01:09 +0100
Subject: [PATCH 30/38] * Homogenise old chat mode detection, which includes
 `cv_consolechat.value`, `dedicated`, and `vid.width < 640`. * Make the chat
 notification sounds work in old chat mode.

---
 src/console.c     |  2 +-
 src/hu_stuff.c    | 26 ++++++++++----------------
 src/hu_stuff.h    |  2 ++
 src/lua_baselib.c | 19 +++++++++++++------
 4 files changed, 26 insertions(+), 23 deletions(-)

diff --git a/src/console.c b/src/console.c
index eaa98550..212e6c8d 100644
--- a/src/console.c
+++ b/src/console.c
@@ -1442,7 +1442,7 @@ static void CON_DrawHudlines(void)
 	if (con_hudlines <= 0)
 		return;
 
-	if (chat_on && (cv_consolechat.value || vid.width < 640))
+	if (chat_on && OLDCHAT)
 		y = charheight; // leave place for chat input in the first row of text
 	else
 		y = 0;
diff --git a/src/hu_stuff.c b/src/hu_stuff.c
index dd0d61fe..338fd26c 100644
--- a/src/hu_stuff.c
+++ b/src/hu_stuff.c
@@ -388,6 +388,8 @@ static void HU_removeChatText_Log(void)
 
 void HU_AddChatText(const char *text)
 {
+	if (cv_chatnotifications.value)
+		S_StartSound(NULL, sfx_radio);
 
 	// TODO: check if we're oversaturating the log (we can only log CHAT_BUFSIZE messages.)
 
@@ -791,18 +793,12 @@ static void Got_Saycmd(UINT8 **p, INT32 playernum)
 
 		}
 
-		if (cv_consolechat.value)
-		{
+		HU_AddChatText(va(fmt2, prefix, cstart, dispname, cend, msg)); // add it reguardless, in case we decide to change our mind about our chat type.
+
+		if OLDCHAT
 			CONS_Printf(fmt, prefix, cstart, dispname, cend, msg);
-			HU_AddChatText(va(fmt2, prefix, cstart, dispname, cend, msg));	// add it reguardless, in case we decide to change our mind about our chat type.
-		}
 		else
-		{
-			HU_AddChatText(va(fmt2, prefix, cstart, dispname, cend, msg));
-			CON_LogMessage(va(fmt, prefix, cstart, dispname, cend, msg));	// save to log.txt
-			if (cv_chatnotifications.value)
-				S_StartSound(NULL, sfx_radio);
-		}
+			CON_LogMessage(va(fmt, prefix, cstart, dispname, cend, msg)); // save to log.txt
 
 		if (tempchar)
 			Z_Free(tempchar);
@@ -1956,19 +1952,17 @@ void HU_Drawer(void)
 		// count down the scroll timer.
 		if (chat_scrolltime > 0)
 			chat_scrolltime--;
-		if (!cv_consolechat.value && vid.width > 320)	// don't even try using newchat sub 400p, I'm too fucking lazy
+		if (!OLDCHAT)
 			HU_DrawChat();
 		else
 			HU_DrawChat_Old();	// why the fuck.........................
 	}
 	else
 	{
-		if (!cv_consolechat.value)
-		{
+		chat_scrolltime = 0;	// do scroll anyway.
+		typelines = 1;			// make sure that the chat doesn't have a weird blinking huge ass square if we typed a lot last time.
+		if (!OLDCHAT)
 			HU_drawMiniChat();		// draw messages in a cool fashion.
-			chat_scrolltime = 0;	// do scroll anyway.
-			typelines = 1;			// make sure that the chat doesn't have a weird blinking huge ass square if we typed a lot last time.
-		}
 	}
 
 	if (netgame)	// would handle that in hu_drawminichat, but it's actually kinda awkward when you're typing a lot of messages. (only handle that in netgames duh)
diff --git a/src/hu_stuff.h b/src/hu_stuff.h
index b4a78338..90ffeb42 100644
--- a/src/hu_stuff.h
+++ b/src/hu_stuff.h
@@ -80,6 +80,8 @@ extern patch_t *iconprefix[MAXSKINS];
 
 #define CHAT_BUFSIZE 64		// that's enough messages, right? We'll delete the older ones when that gets out of hand.
 
+#define OLDCHAT (cv_consolechat.value || dedicated || vid.width < 640)
+
 // some functions
 void HU_AddChatText(const char *text);
 
diff --git a/src/lua_baselib.c b/src/lua_baselib.c
index 068ae204..e13c1b0d 100644
--- a/src/lua_baselib.c
+++ b/src/lua_baselib.c
@@ -21,6 +21,7 @@
 #include "s_sound.h"
 #include "g_game.h"
 #include "hu_stuff.h"
+#include "console.h"
 #include "k_kart.h"
 
 #include "lua_script.h"
@@ -96,11 +97,14 @@ static int lib_chatprint(lua_State *L)
 	int len = strlen(str);
 	if (len > 255)	// string is too long!!!
 		return luaL_error(L, "String exceeds the 255 characters limit of the chat buffer.");
-	
-	if (cv_consolechat.value || !netgame)
+
+	HU_AddChatText(str);
+
+	if OLDCHAT
 		CONS_Printf("%s\n", str);
 	else
-		HU_AddChatText(str);
+		CON_LogMessage(str); // save to log.txt
+
 	return 0;
 }
 
@@ -124,11 +128,14 @@ static int lib_chatprintf(lua_State *L)
 	int len = strlen(str);
 	if (len > 255)	// string is too long!!!
 		return luaL_error(L, "String exceeds the 255 characters limit of the chat buffer.");
-	
-	if (cv_consolechat.value || !netgame)
+
+	HU_AddChatText(str);
+
+	if OLDCHAT
 		CONS_Printf("%s\n", str);
 	else
-		HU_AddChatText(str);
+		CON_LogMessage(str); // save to log.txt
+
 	return 0;
 }
 

From 97ec3852348a7f7ffdcfaf21e48b0452799664bb Mon Sep 17 00:00:00 2001
From: TehRealSalt <tehrealsalt@gmail.com>
Date: Sat, 4 Aug 2018 15:48:31 -0400
Subject: [PATCH 31/38] Thunder shield overlay

---
 src/d_netcmd.c |  2 +-
 src/d_netcmd.h |  2 +-
 src/d_player.h |  5 ++--
 src/dehacked.c | 37 +++++++++++++++++++++++---
 src/info.c     | 59 ++++++++++++++++++++++++++++++++++++++---
 src/info.h     | 31 +++++++++++++++++++++-
 src/k_kart.c   | 71 +++++++++++++++++++++++++++++---------------------
 src/m_menu.c   |  2 +-
 src/p_mobj.c   |  8 ++++++
 src/sounds.c   |  2 +-
 10 files changed, 175 insertions(+), 44 deletions(-)

diff --git a/src/d_netcmd.c b/src/d_netcmd.c
index f7954747..fb5234c9 100644
--- a/src/d_netcmd.c
+++ b/src/d_netcmd.c
@@ -332,7 +332,7 @@ consvar_t cv_ballhog = 				{"ballhog", 			"On", CV_NETVAR|CV_CHEAT, CV_OnOff, NU
 consvar_t cv_selfpropelledbomb =	{"selfpropelledbomb", 	"On", CV_NETVAR|CV_CHEAT, CV_OnOff, NULL, 0, NULL, NULL, 0, 0, NULL};
 consvar_t cv_grow = 				{"grow", 				"On", CV_NETVAR|CV_CHEAT, CV_OnOff, NULL, 0, NULL, NULL, 0, 0, NULL};
 consvar_t cv_shrink = 				{"shrink", 				"On", CV_NETVAR|CV_CHEAT, CV_OnOff, NULL, 0, NULL, NULL, 0, 0, NULL};
-consvar_t cv_lightningshield = 		{"lightningshield", 	"On", CV_NETVAR|CV_CHEAT, CV_OnOff, NULL, 0, NULL, NULL, 0, 0, NULL};
+consvar_t cv_thundershield = 		{"thundershield", 		"On", CV_NETVAR|CV_CHEAT, CV_OnOff, NULL, 0, NULL, NULL, 0, 0, NULL};
 consvar_t cv_hyudoro = 				{"hyudoro", 			"On", CV_NETVAR|CV_CHEAT, CV_OnOff, NULL, 0, NULL, NULL, 0, 0, NULL};
 consvar_t cv_pogospring = 			{"pogospring", 			"On", CV_NETVAR|CV_CHEAT, CV_OnOff, NULL, 0, NULL, NULL, 0, 0, NULL};
 
diff --git a/src/d_netcmd.h b/src/d_netcmd.h
index 371df720..b2a57bc0 100644
--- a/src/d_netcmd.h
+++ b/src/d_netcmd.h
@@ -112,7 +112,7 @@ extern consvar_t cv_recycler;*/
 extern consvar_t cv_sneaker, cv_rocketsneaker, cv_invincibility, cv_banana;
 extern consvar_t cv_eggmanmonitor, cv_orbinaut, cv_jawz, cv_mine;
 extern consvar_t cv_ballhog, cv_selfpropelledbomb, cv_grow, cv_shrink;
-extern consvar_t cv_lightningshield, cv_hyudoro, cv_pogospring;
+extern consvar_t cv_thundershield, cv_hyudoro, cv_pogospring;
 
 extern consvar_t cv_triplesneaker, cv_triplebanana, cv_tripleorbinaut, cv_dualjawz;
 
diff --git a/src/d_player.h b/src/d_player.h
index 7b5aeafc..baa03f3e 100644
--- a/src/d_player.h
+++ b/src/d_player.h
@@ -246,7 +246,7 @@ typedef enum
 	KITEM_SPB,
 	KITEM_GROW,
 	KITEM_SHRINK,
-	KITEM_LIGHTNINGSHIELD,
+	KITEM_THUNDERSHIELD,
 	KITEM_HYUDORO,
 	KITEM_POGOSPRING,
 	KITEM_KITCHENSINK,
@@ -306,7 +306,8 @@ typedef enum
 	k_itemheld,		// Are you holding an item?
 
 	// Some items use timers for their duration or effects
-	k_attractiontimer,		// Duration of Lightning Shield's item-break and item box pull
+	//k_thunderanim,			// Duration of Thunder Shield's use animation
+	k_curshield,			// 0 = no shield, 1 = thunder shield
 	k_hyudorotimer,			// Duration of the Hyudoro offroad effect itself
 	k_stealingtimer,		// You are stealing an item, this is your timer
 	k_stolentimer,			// You are being stolen from, this is your timer
diff --git a/src/dehacked.c b/src/dehacked.c
index 03884474..70cf730f 100644
--- a/src/dehacked.c
+++ b/src/dehacked.c
@@ -6247,7 +6247,7 @@ static const char *const STATE_LIST[] = { // array length left dynamic for sanit
 	"S_DRIFTDUST3",
 	"S_DRIFTDUST4",
 
-	// Lightning Shield Burst
+	// Thunder Shield Burst
 
 	// Sneaker Fire Trail
 	"S_KARTFIRE1",
@@ -6446,6 +6446,32 @@ static const char *const STATE_LIST[] = { // array length left dynamic for sanit
 	"S_LIGHTNING3",
 	"S_LIGHTNING4",
 
+	// Thunder Shield
+	"S_THUNDERSHIELD1",
+	"S_THUNDERSHIELD2",
+	"S_THUNDERSHIELD3",
+	"S_THUNDERSHIELD4",
+	"S_THUNDERSHIELD5",
+	"S_THUNDERSHIELD6",
+	"S_THUNDERSHIELD7",
+	"S_THUNDERSHIELD8",
+	"S_THUNDERSHIELD9",
+	"S_THUNDERSHIELD10",
+	"S_THUNDERSHIELD11",
+	"S_THUNDERSHIELD12",
+	"S_THUNDERSHIELD13",
+	"S_THUNDERSHIELD14",
+	"S_THUNDERSHIELD15",
+	"S_THUNDERSHIELD16",
+	"S_THUNDERSHIELD17",
+	"S_THUNDERSHIELD18",
+	"S_THUNDERSHIELD19",
+	"S_THUNDERSHIELD20",
+	"S_THUNDERSHIELD21",
+	"S_THUNDERSHIELD22",
+	"S_THUNDERSHIELD23",
+	"S_THUNDERSHIELD24",
+
 	// The legend
 	"S_SINK",
 	"S_SINKTRAIL1",
@@ -7185,8 +7211,8 @@ static const char *const MOBJTYPE_LIST[] = {  // array length left dynamic for s
 	"MT_FAKESHIELD",
 	"MT_FAKEITEM",
 
-	"MT_BANANA",
-	"MT_BANANA_SHIELD", // Banana Stuff
+	"MT_BANANA", // Banana Stuff
+	"MT_BANANA_SHIELD",
 
 	"MT_ORBINAUT", // Orbinaut stuff
 	"MT_ORBINAUT_SHIELD",
@@ -7211,6 +7237,8 @@ static const char *const MOBJTYPE_LIST[] = {  // array length left dynamic for s
 	"MT_BLUEEXPLOSION",
 	"MT_LIGHTNING",
 
+	"MT_THUNDERSHIELD", // Thunder Shield stuff
+
 	"MT_SINK", // Kitchen Sink Stuff
 	"MT_SINKTRAIL",
 
@@ -7652,7 +7680,8 @@ static const char *const KARTSTUFF_LIST[] = {
 	"ITEMHELD",
 
 	// Some items use timers for their duration or effects
-	"ATTRACTIONTIMER",
+	//"THUNDERANIM",
+	"CURSHIELD",
 	"HYUDOROTIMER",
 	"STEALINGTIMER",
 	"STOLENTIMER",
diff --git a/src/info.c b/src/info.c
index 41e89ae6..0a3d0668 100644
--- a/src/info.c
+++ b/src/info.c
@@ -57,9 +57,10 @@ char sprnames[NUMSPRITES + 1][5] =
 	//SRB2kart Sprites
 	"SPRG","BSPR","RNDM","RPOP","KFRE","KINV","KINF","WIPD","DRIF","DUST",
 	"FITM","BANA","ORBN","JAWZ","SSMN","KRBM","BHOG","BHBM","BLIG","LIGH",
-	"SINK","SITR","KBLN","DEZL","POKE","AUDI","DECO","DOOD","SNES","GBAS",
-	"SPRS","BUZB","CHOM","SACO","CRAB","SHAD","BRNG","BUMP","FLEN","CLAS",
-	"PSHW","ISTA","ISTB","ARRO","ITEM","ITMI","ITMN","WANT","PBOM","VIEW"
+	"THNS","SINK","SITR","KBLN","DEZL","POKE","AUDI","DECO","DOOD","SNES",
+	"GBAS","SPRS","BUZB","CHOM","SACO","CRAB","SHAD","BRNG","BUMP","FLEN",
+	"CLAS","PSHW","ISTA","ISTB","ARRO","ITEM","ITMI","ITMN","WANT","PBOM",
+	"VIEW"
 };
 
 // Doesn't work with g++, needs actionf_p1 (don't modify this comment)
@@ -2762,6 +2763,31 @@ state_t states[NUMSTATES] =
 	{SPR_LIGH, 2,  2, {NULL}, 0, 0, S_LIGHTNING4}, // S_LIGHTNING3
 	{SPR_LIGH, 3,  2, {NULL}, 0, 0, S_NULL},       // S_LIGHTNING4
 
+	{SPR_THNS,  0, 2, {NULL}, 0, 0, S_THUNDERSHIELD2},		// S_THUNDERSHIELD1
+	{SPR_THNS,  1, 2, {NULL}, 0, 0, S_THUNDERSHIELD3},		// S_THUNDERSHIELD2
+	{SPR_THNS,  2, 2, {NULL}, 0, 0, S_THUNDERSHIELD4},		// S_THUNDERSHIELD3
+	{SPR_THNS,  3, 2, {NULL}, 0, 0, S_THUNDERSHIELD5},		// S_THUNDERSHIELD4
+	{SPR_THNS,  4, 2, {NULL}, 0, 0, S_THUNDERSHIELD6},		// S_THUNDERSHIELD5
+	{SPR_THNS,  5, 2, {NULL}, 0, 0, S_THUNDERSHIELD7},		// S_THUNDERSHIELD6
+	{SPR_THNS,  6, 2, {NULL}, 0, 0, S_THUNDERSHIELD8},		// S_THUNDERSHIELD7
+	{SPR_THNS,  7, 2, {NULL}, 0, 0, S_THUNDERSHIELD9},		// S_THUNDERSHIELD8
+	{SPR_THNS,  8, 2, {NULL}, 0, 0, S_THUNDERSHIELD10},		// S_THUNDERSHIELD9
+	{SPR_THNS,  9, 2, {NULL}, 0, 0, S_THUNDERSHIELD11},		// S_THUNDERSHIELD10
+	{SPR_THNS, 10, 2, {NULL}, 0, 0, S_THUNDERSHIELD12},		// S_THUNDERSHIELD11
+	{SPR_THNS, 11, 2, {NULL}, 0, 0, S_THUNDERSHIELD13},		// S_THUNDERSHIELD12
+	{SPR_THNS,  8, 2, {NULL}, 0, 0, S_THUNDERSHIELD14},		// S_THUNDERSHIELD13
+	{SPR_THNS,  7, 2, {NULL}, 0, 0, S_THUNDERSHIELD15},		// S_THUNDERSHIELD14
+	{SPR_THNS,  6, 2, {NULL}, 0, 0, S_THUNDERSHIELD16},		// S_THUNDERSHIELD15
+	{SPR_THNS,  5, 2, {NULL}, 0, 0, S_THUNDERSHIELD17},		// S_THUNDERSHIELD16
+	{SPR_THNS,  4, 2, {NULL}, 0, 0, S_THUNDERSHIELD18},		// S_THUNDERSHIELD17
+	{SPR_THNS,  3, 2, {NULL}, 0, 0, S_THUNDERSHIELD19},		// S_THUNDERSHIELD18
+	{SPR_THNS,  2, 2, {NULL}, 0, 0, S_THUNDERSHIELD20},		// S_THUNDERSHIELD19
+	{SPR_THNS,  1, 2, {NULL}, 0, 0, S_THUNDERSHIELD21},		// S_THUNDERSHIELD20
+	{SPR_THNS,  0, 2, {NULL}, 0, 0, S_THUNDERSHIELD22},		// S_THUNDERSHIELD21
+	{SPR_THNS,  9, 2, {NULL}, 0, 0, S_THUNDERSHIELD23},		// S_THUNDERSHIELD22
+	{SPR_THNS, 10, 2, {NULL}, 0, 0, S_THUNDERSHIELD24},		// S_THUNDERSHIELD23
+	{SPR_THNS, 11, 2, {NULL}, 0, 0, S_THUNDERSHIELD1},		// S_THUNDERSHIELD24
+
 	{SPR_SINK, 0,  4, {A_SmokeTrailer}, MT_SINKTRAIL, 0, S_SINK}, // S_SINK
 	{SPR_SITR, 0,  1, {NULL}, 0, 0, S_SINKTRAIL2},                       // S_SINKTRAIL1
 	{SPR_SITR, 1,  5, {NULL}, 0, 0, S_SINKTRAIL3},                       // S_SINKTRAIL2
@@ -15087,6 +15113,33 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		S_NULL          // raisestate
 	},
 
+	{           // MT_THUNDERSHIELD
+		-1,             // doomednum
+		S_THUNDERSHIELD1, // 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
+		8,              // speed
+		16*FRACUNIT,    // radius
+		56*FRACUNIT,    // height
+		1,              // display offset
+		16,             // mass
+		0,              // damage
+		sfx_None,       // activesound
+		MF_NOBLOCKMAP|MF_NOCLIP|MF_NOGRAVITY, // flags
+		S_NULL          // raisestate
+	},
+
 	{           // MT_SINK
 		-1,             // doomednum
 		S_SINK,         // spawnstate
diff --git a/src/info.h b/src/info.h
index 3186fc88..ea07e2f3 100644
--- a/src/info.h
+++ b/src/info.h
@@ -600,6 +600,7 @@ typedef enum sprite
 	SPR_BHBM, // Ballhog BOOM
 	SPR_BLIG, // Self-Propelled Bomb
 	SPR_LIGH, // Grow/shrink beams (Metallic Maddness)
+	SPR_THNS, // Thunder Shield
 	SPR_SINK, // Kitchen Sink
 	SPR_SITR, // Kitchen Sink Trail
 	SPR_KBLN, // Battle Mode Bumper
@@ -3282,12 +3283,38 @@ typedef enum state
 	S_BLUELIGHTNING4,
 	S_BLUEEXPLODE,
 
-	// Size-Down
+	// Grow/Shrink
 	S_LIGHTNING1,
 	S_LIGHTNING2,
 	S_LIGHTNING3,
 	S_LIGHTNING4,
 
+	// Thunder Shield
+	S_THUNDERSHIELD1,
+	S_THUNDERSHIELD2,
+	S_THUNDERSHIELD3,
+	S_THUNDERSHIELD4,
+	S_THUNDERSHIELD5,
+	S_THUNDERSHIELD6,
+	S_THUNDERSHIELD7,
+	S_THUNDERSHIELD8,
+	S_THUNDERSHIELD9,
+	S_THUNDERSHIELD10,
+	S_THUNDERSHIELD11,
+	S_THUNDERSHIELD12,
+	S_THUNDERSHIELD13,
+	S_THUNDERSHIELD14,
+	S_THUNDERSHIELD15,
+	S_THUNDERSHIELD16,
+	S_THUNDERSHIELD17,
+	S_THUNDERSHIELD18,
+	S_THUNDERSHIELD19,
+	S_THUNDERSHIELD20,
+	S_THUNDERSHIELD21,
+	S_THUNDERSHIELD22,
+	S_THUNDERSHIELD23,
+	S_THUNDERSHIELD24,
+
 	// The legend
 	S_SINK,
 	S_SINKTRAIL1,
@@ -4070,6 +4097,8 @@ typedef enum mobj_type
 	MT_BLUEEXPLOSION,
 	MT_LIGHTNING,
 
+	MT_THUNDERSHIELD, // Thunder Shield stuff
+
 	MT_SINK, // Kitchen Sink Stuff
 	MT_SINKTRAIL,
 
diff --git a/src/k_kart.c b/src/k_kart.c
index 50d03c1e..a1733f1b 100644
--- a/src/k_kart.c
+++ b/src/k_kart.c
@@ -385,7 +385,7 @@ void K_RegisterKartStuff(void)
 	CV_RegisterVar(&cv_selfpropelledbomb);
 	CV_RegisterVar(&cv_grow);
 	CV_RegisterVar(&cv_shrink);
-	CV_RegisterVar(&cv_lightningshield);
+	CV_RegisterVar(&cv_thundershield);
 	CV_RegisterVar(&cv_hyudoro);
 	CV_RegisterVar(&cv_pogospring);
 
@@ -476,7 +476,7 @@ static INT32 K_KartItemOddsRace[NUMKARTRESULTS][9] =
    /*Self-Propelled Bomb*/ { 0, 0, 1, 1, 1, 2, 2, 3, 2 }, // Self-Propelled Bomb
 				  /*Grow*/ { 0, 0, 0, 0, 1, 2, 4, 6, 4 }, // Grow
 				/*Shrink*/ { 0, 0, 0, 0, 0, 0, 0, 1, 0 }, // Shrink
-	  /*Lightning Shield*/ { 0, 1, 2, 0, 0, 0, 0, 0, 0 }, // Lightning Shield
+		/*Thunder Shield*/ { 0, 1, 2, 0, 0, 0, 0, 0, 0 }, // Thunder Shield
 			   /*Hyudoro*/ { 0, 0, 0, 0, 1, 2, 1, 0, 0 }, // Hyudoro
 		   /*Pogo Spring*/ { 0, 0, 0, 0, 0, 0, 0, 0, 0 }, // Pogo Spring
 		  /*Kitchen Sink*/ { 0, 0, 0, 0, 0, 0, 0, 0, 0 }, // Kitchen Sink
@@ -502,7 +502,7 @@ static INT32 K_KartItemOddsBattle[NUMKARTRESULTS][6] =
    /*Self-Propelled Bomb*/ { 0, 0, 0, 0 }, // Self-Propelled Bomb
 				  /*Grow*/ { 4, 2, 0, 2 }, // Grow
 				/*Shrink*/ { 0, 0, 0, 0 }, // Shrink
-	  /*Lightning Shield*/ { 0, 0, 0, 0 }, // Lightning Shield
+		/*Thunder Shield*/ { 0, 0, 0, 0 }, // Thunder Shield
 			   /*Hyudoro*/ { 0, 0, 1, 0 }, // Hyudoro
 		   /*Pogo Spring*/ { 0, 0, 1, 0 }, // Pogo Spring
 		  /*Kitchen Sink*/ { 0, 0, 0, 0 }, // Kitchen Sink
@@ -680,9 +680,9 @@ static INT32 K_KartGetItemOdds(UINT8 pos, SINT8 item, fixed_t mashed)
 				|| (indirectitemcooldown > 0)
 				|| (pingame-1 <= pexiting)) newodds = 0;
 			break;
-		case KITEM_LIGHTNINGSHIELD:
+		case KITEM_THUNDERSHIELD:
 			POWERITEMODDS(newodds);
-			if (!cv_lightningshield.value) newodds = 0;
+			if (!cv_thundershield.value) newodds = 0;
 			break;
 		case KITEM_HYUDORO:
 			if (!cv_hyudoro.value) newodds = 0;
@@ -899,7 +899,7 @@ static void K_KartItemRoulette(player_t *player, ticcmd_t *cmd)
 	SETITEMRESULT(useodds, KITEM_SPB);				// Self-Propelled Bomb
 	SETITEMRESULT(useodds, KITEM_GROW);				// Grow
 	SETITEMRESULT(useodds, KITEM_SHRINK);			// Shrink
-	SETITEMRESULT(useodds, KITEM_LIGHTNINGSHIELD);	// Lightning Shield
+	SETITEMRESULT(useodds, KITEM_THUNDERSHIELD);	// Thunder Shield
 	SETITEMRESULT(useodds, KITEM_HYUDORO);			// Hyudoro
 	SETITEMRESULT(useodds, KITEM_POGOSPRING);		// Pogo Spring
 	//SETITEMRESULT(useodds, KITEM_KITCHENSINK);	// Kitchen Sink
@@ -2537,10 +2537,10 @@ static mobj_t *K_ThrowKartItem(player_t *player, boolean missile, mobjtype_t map
 	return mo;
 }
 
-static void K_DoLightningShield(player_t *player)
+static void K_DoThunderShield(player_t *player)
 {
 	S_StartSound(player->mo, sfx_s3k45);
-	player->kartstuff[k_attractiontimer] = 35;
+	//player->kartstuff[k_thunderanim] = 35;
 	P_NukeEnemies(player->mo, player->mo, RING_DIST/4);
 }
 
@@ -2550,7 +2550,7 @@ static void K_DoHyudoroSteal(player_t *player)
 	INT32 playerswappable[MAXPLAYERS];
 	INT32 stealplayer = -1; // The player that's getting stolen from
 	INT32 prandom = 0;
-	fixed_t sink = P_RandomChance(FRACUNIT/64);
+	boolean sink = P_RandomChance(FRACUNIT/64);
 
 	for (i = 0; i < MAXPLAYERS; i++)
 	{
@@ -2574,7 +2574,7 @@ static void K_DoHyudoroSteal(player_t *player)
 	prandom = P_RandomFixed();
 	S_StartSound(player->mo, sfx_s3k92);
 
-	if (sink) // BEHOLD THE KITCHEN SINK
+	if (sink && numplayers > 0) // BEHOLD THE KITCHEN SINK
 	{
 		player->kartstuff[k_hyudorotimer] = hyudorotime;
 		player->kartstuff[k_stealingtimer] = stealtime;
@@ -3010,8 +3010,8 @@ void K_KartPlayerThink(player_t *player, ticcmd_t *cmd)
 	if (player->kartstuff[k_spinouttimer] == 0 && player->powers[pw_flashing] == K_GetKartFlashing())
 		player->powers[pw_flashing]--;
 
-	if (player->kartstuff[k_attractiontimer])
-		player->kartstuff[k_attractiontimer]--;
+	/*if (player->kartstuff[k_thunderanim])
+		player->kartstuff[k_thunderanim]--;*/
 
 	if (player->kartstuff[k_sneakertimer])
 		player->kartstuff[k_sneakertimer]--;
@@ -3538,7 +3538,9 @@ void K_StripItems(player_t *player)
 	player->kartstuff[k_stealingtimer] = 0;
 	player->kartstuff[k_stolentimer] = 0;
 
-	player->kartstuff[k_attractiontimer] = 0;
+	player->kartstuff[k_curshield] = 0;
+	//player->kartstuff[k_thunderanim] = 0;
+	player->kartstuff[k_bananadrag] = 0;
 
 	player->kartstuff[k_sadtimer] = 0;
 }
@@ -3871,10 +3873,16 @@ void K_MoveKartPlayer(player_t *player, boolean onground)
 						player->kartstuff[k_itemamount]--;
 					}
 					break;
-				case KITEM_LIGHTNINGSHIELD:
+				case KITEM_THUNDERSHIELD:
+					if (player->kartstuff[k_curshield] <= 0)
+					{
+						mobj_t *shield = P_SpawnMobj(player->mo->x, player->mo->y, player->mo->z, MT_THUNDERSHIELD);
+						P_SetTarget(&shield->target, player->mo);
+						player->kartstuff[k_curshield] = 1;
+					}
 					if (ATTACK_IS_DOWN && !HOLDING_ITEM && NO_HYUDORO)
 					{
-						K_DoLightningShield(player);
+						K_DoThunderShield(player);
 						player->pflags |= PF_ATTACKDOWN;
 						player->kartstuff[k_itemamount]--;
 					}
@@ -3924,6 +3932,9 @@ void K_MoveKartPlayer(player_t *player, boolean onground)
 		if (!player->kartstuff[k_itemamount] && !player->kartstuff[k_itemheld])
 			player->kartstuff[k_itemtype] = KITEM_NONE;
 
+		if (player->kartstuff[k_itemtype] != KITEM_THUNDERSHIELD)
+			player->kartstuff[k_curshield] = 0;
+
 		if (player->kartstuff[k_itemtype] == KITEM_SPB
 			|| player->kartstuff[k_itemtype] == KITEM_SHRINK
 			|| player->kartstuff[k_growshrinktimer] < 0
@@ -4384,7 +4395,7 @@ static patch_t *kp_ballhog[2];
 static patch_t *kp_selfpropelledbomb[2];
 static patch_t *kp_grow[2];
 static patch_t *kp_shrink[2];
-static patch_t *kp_lightningshield[2];
+static patch_t *kp_thundershield[2];
 static patch_t *kp_hyudoro[2];
 static patch_t *kp_pogospring[2];
 static patch_t *kp_kitchensink[2];
@@ -4494,7 +4505,7 @@ void K_LoadKartHUDGraphics(void)
 	kp_selfpropelledbomb[0] =	W_CachePatchName("K_ITSPB", PU_HUDGFX);
 	kp_grow[0] =				W_CachePatchName("K_ITGROW", PU_HUDGFX);
 	kp_shrink[0] =				W_CachePatchName("K_ITSHRK", PU_HUDGFX);
-	kp_lightningshield[0] =		W_CachePatchName("K_ITLITS", PU_HUDGFX);
+	kp_thundershield[0] =		W_CachePatchName("K_ITTHNS", PU_HUDGFX);
 	kp_hyudoro[0] = 			W_CachePatchName("K_ITHYUD", PU_HUDGFX);
 	kp_pogospring[0] = 			W_CachePatchName("K_ITPOGO", PU_HUDGFX);
 	kp_kitchensink[0] = 		W_CachePatchName("K_ITSINK", PU_HUDGFX);
@@ -4522,7 +4533,7 @@ void K_LoadKartHUDGraphics(void)
 	kp_selfpropelledbomb[1] =	W_CachePatchName("K_ISSPB", PU_HUDGFX);
 	kp_grow[1] =				W_CachePatchName("K_ISGROW", PU_HUDGFX);
 	kp_shrink[1] =				W_CachePatchName("K_ISSHRK", PU_HUDGFX);
-	kp_lightningshield[1] =		W_CachePatchName("K_ISLITS", PU_HUDGFX);
+	kp_thundershield[1] =		W_CachePatchName("K_ISTHNS", PU_HUDGFX);
 	kp_hyudoro[1] = 			W_CachePatchName("K_ISHYUD", PU_HUDGFX);
 	kp_pogospring[1] = 			W_CachePatchName("K_ISPOGO", PU_HUDGFX);
 	kp_kitchensink[1] = 		W_CachePatchName("K_ISSINK", PU_HUDGFX);
@@ -4756,22 +4767,22 @@ static void K_drawKartItem(void)
 		switch((stplyr->kartstuff[k_itemroulette] % (13*3)) / 3)
 		{
 			// Each case is handled in threes, to give three frames of in-game time to see the item on the roulette
-			case  0: localpatch = kp_sneaker[offset]; break;			// Sneaker
+			case  0: localpatch = kp_sneaker[offset]; break;				// Sneaker
 			case  1: localpatch = kp_banana[offset]; break;				// Banana
-			case  2: localpatch = kp_orbinaut[offset]; break;			// Orbinaut
-			case  3: localpatch = kp_mine[offset]; break;				// Mine
-			case  4: localpatch = kp_grow[offset]; break;				// Grow
-			case  5: localpatch = kp_hyudoro[offset]; break;			// Hyudoro
+			case  2: localpatch = kp_orbinaut[offset]; break;				// Orbinaut
+			case  3: localpatch = kp_mine[offset]; break;					// Mine
+			case  4: localpatch = kp_grow[offset]; break;					// Grow
+			case  5: localpatch = kp_hyudoro[offset]; break;				// Hyudoro
 			case  6: localpatch = kp_rocketsneaker[offset]; break;		// Rocket Sneaker
-			case  7: localpatch = kp_jawz[offset]; break;				// Jawz
+			case  7: localpatch = kp_jawz[offset]; break;					// Jawz
 			case  8: localpatch = kp_selfpropelledbomb[offset]; break;	// Self-Propelled Bomb
 			case  9: localpatch = kp_shrink[offset]; break;				// Shrink
-			case 10: localpatch = localinv; break;						// Invincibility
+			case 10: localpatch = localinv; break;							// Invincibility
 			case 11: localpatch = kp_eggman[offset]; break;				// Eggman Monitor
-			case 12: localpatch = kp_ballhog[offset]; break;			// Ballhog
-			case 13: localpatch = kp_lightningshield[offset]; break;	// Lightning Shield
-			//case 14: localpatch = kp_pogospring[offset]; break;		// Pogo Spring
-			//case 15: localpatch = kp_kitchensink[offset]; break;		// Kitchen Sink
+			case 12: localpatch = kp_ballhog[offset]; break;				// Ballhog
+			case 13: localpatch = kp_thundershield[offset]; break;		// Thunder Shield
+			//case 14: localpatch = kp_pogospring[offset]; break;			// Pogo Spring
+			//case 15: localpatch = kp_kitchensink[offset]; break;			// Kitchen Sink
 			default: break;
 		}
 	}
@@ -4832,7 +4843,7 @@ static void K_drawKartItem(void)
 				case KITEM_SPB:					localpatch = kp_selfpropelledbomb[offset]; break;
 				case KITEM_GROW:				localpatch = kp_grow[offset]; break;
 				case KITEM_SHRINK:				localpatch = kp_shrink[offset]; break;
-				case KITEM_LIGHTNINGSHIELD:		localpatch = kp_lightningshield[offset]; break;
+				case KITEM_THUNDERSHIELD:		localpatch = kp_thundershield[offset]; break;
 				case KITEM_HYUDORO:				localpatch = kp_hyudoro[offset]; break;
 				case KITEM_POGOSPRING:			localpatch = kp_pogospring[offset]; break;
 				case KITEM_KITCHENSINK:			localpatch = kp_kitchensink[offset]; break;
diff --git a/src/m_menu.c b/src/m_menu.c
index ce4e7486..4364a0f5 100644
--- a/src/m_menu.c
+++ b/src/m_menu.c
@@ -1500,7 +1500,7 @@ static menuitem_t OP_MonitorToggleMenu[] =
 	{IT_STRING | IT_CVAR, NULL, "Self-Propelled Bombs",&cv_selfpropelledbomb,114},
 	{IT_STRING | IT_CVAR, NULL, "Grow",				&cv_grow,             122},
 	{IT_STRING | IT_CVAR, NULL, "Shrink",				&cv_shrink,           130},
-	{IT_STRING | IT_CVAR, NULL, "Lightning Shields",	&cv_lightningshield,  138},
+	{IT_STRING | IT_CVAR, NULL, "Thunder Shields",	    &cv_thundershield,    138},
 	{IT_STRING | IT_CVAR, NULL, "Hyudoros",			&cv_hyudoro,          146},
 	{IT_STRING | IT_CVAR, NULL, "Pogo Springs",		&cv_pogospring,       154},
 };
diff --git a/src/p_mobj.c b/src/p_mobj.c
index 9cf78ebf..c2c08f33 100644
--- a/src/p_mobj.c
+++ b/src/p_mobj.c
@@ -8220,6 +8220,14 @@ void P_MobjThinker(mobj_t *mobj)
 			}
 			P_TeleportMove(mobj, mobj->target->x, mobj->target->y, mobj->target->z);
 			break;
+		case MT_THUNDERSHIELD:
+			if (!mobj->target || !mobj->target->health || (mobj->target->player && mobj->target->player->kartstuff[k_curshield] != 1))
+			{
+				P_RemoveMobj(mobj);
+				return;
+			}
+			P_TeleportMove(mobj, mobj->target->x, mobj->target->y, mobj->target->z);
+			break;
 		case MT_KARMAHITBOX:
 			if (!mobj->target || !mobj->target->health || !mobj->target->player || mobj->target->player->spectator
 				|| (G_RaceGametype() || mobj->target->player->kartstuff[k_bumper]))
diff --git a/src/sounds.c b/src/sounds.c
index 60cbb676..3b41dfa1 100644
--- a/src/sounds.c
+++ b/src/sounds.c
@@ -415,7 +415,7 @@ sfxinfo_t S_sfx[NUMSFX] =
   {"s3k42",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR},
   {"s3k43",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR},
   {"s3k44",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR},
-  {"s3k45",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR}, // Kart Lightning Shield use
+  {"s3k45",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR}, // Kart Thunder Shield use
   {"s3k46",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR},
   {"s3k47",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR},
   {"s3k48",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR},

From ad15ad4b89c7731a1fcc02683eb5ba30708749ab Mon Sep 17 00:00:00 2001
From: TehRealSalt <tehrealsalt@gmail.com>
Date: Sat, 4 Aug 2018 16:54:48 -0400
Subject: [PATCH 32/38] Orbinaut shield

---
 src/dehacked.c |  7 ++++++-
 src/info.c     | 39 ++++++++++++++++++++++-----------------
 src/info.h     |  7 ++++++-
 3 files changed, 34 insertions(+), 19 deletions(-)

diff --git a/src/dehacked.c b/src/dehacked.c
index 70cf730f..8fcf597d 100644
--- a/src/dehacked.c
+++ b/src/dehacked.c
@@ -6324,8 +6324,13 @@ static const char *const STATE_LIST[] = { // array length left dynamic for sanit
 	"S_ORBINAUT4",
 	"S_ORBINAUT5",
 	"S_ORBINAUT6",
-	"S_ORBINAUT_SHIELD",
 	"S_ORBINAUT_DEAD",
+	"S_ORBINAUT_SHIELD1",
+	"S_ORBINAUT_SHIELD2",
+	"S_ORBINAUT_SHIELD3",
+	"S_ORBINAUT_SHIELD4",
+	"S_ORBINAUT_SHIELD5",
+	"S_ORBINAUT_SHIELD6",
 	"S_ORBINAUT_SHIELDDEAD",
 	//}
 	//{ Jawz
diff --git a/src/info.c b/src/info.c
index 0a3d0668..8c6560fc 100644
--- a/src/info.c
+++ b/src/info.c
@@ -2610,7 +2610,7 @@ state_t states[NUMSTATES] =
 	{SPR_WIPD, 3, 3, {NULL}, 0, 0, S_WIPEOUTTRAIL5}, // S_WIPEOUTTRAIL4
 	{SPR_WIPD, 4, 3, {NULL}, 0, 0, S_NULL}, // S_WIPEOUTTRAIL5
 
-	{SPR_FITM,  0|FF_FULLBRIGHT,   3, {NULL}, 0, 0, S_FAKEITEM2},  // S_FAKEITEM1
+	{SPR_FITM,    FF_FULLBRIGHT,   3, {NULL}, 0, 0, S_FAKEITEM2},  // S_FAKEITEM1
 	{SPR_FITM,  1|FF_FULLBRIGHT,   3, {NULL}, 0, 0, S_FAKEITEM3},  // S_FAKEITEM2
 	{SPR_FITM,  2|FF_FULLBRIGHT,   3, {NULL}, 0, 0, S_FAKEITEM4},  // S_FAKEITEM3
 	{SPR_FITM,  3|FF_FULLBRIGHT,   3, {NULL}, 0, 0, S_FAKEITEM5},  // S_FAKEITEM4
@@ -2634,20 +2634,25 @@ state_t states[NUMSTATES] =
 	{SPR_FITM, 21|FF_FULLBRIGHT,   3, {NULL}, 0, 0, S_FAKEITEM23}, // S_FAKEITEM22  // *****
 	{SPR_FITM, 22|FF_FULLBRIGHT,   3, {NULL}, 0, 0, S_FAKEITEM24}, // S_FAKEITEM23 // *****
 	{SPR_FITM, 23|FF_FULLBRIGHT,   3, {NULL}, 0, 0, S_FAKEITEM1},  // S_FAKEITEM24 // *****
-	{SPR_FITM, 24|FF_FULLBRIGHT, 175, {NULL}, 0, 0, S_FAKEITEM1},  // S_DEADFAKEITEM
+	{SPR_FITM,    FF_FULLBRIGHT, 175, {NULL}, 0, 0, S_FAKEITEM1},  // S_DEADFAKEITEM
 
 	{SPR_BANA, 0,  -1, {NULL}, 0, 0, S_NULL}, // S_BANANA
-	{SPR_BANA, 1, 175, {NULL}, 0, 0, S_NULL}, // S_BANANA_DEAD
+	{SPR_BANA, 0, 175, {NULL}, 0, 0, S_NULL}, // S_BANANA_DEAD
 
-	{SPR_ORBN, 0, 1, {NULL}, 0, 0, S_ORBINAUT2},		// S_ORBINAUT1
-	{SPR_ORBN, 1, 1, {NULL}, 0, 0, S_ORBINAUT3},		// S_ORBINAUT2
-	{SPR_ORBN, 2, 1, {NULL}, 0, 0, S_ORBINAUT4},		// S_ORBINAUT3
-	{SPR_ORBN, 3, 1, {NULL}, 0, 0, S_ORBINAUT5},		// S_ORBINAUT4
-	{SPR_ORBN, 4, 1, {NULL}, 0, 0, S_ORBINAUT6},		// S_ORBINAUT5
-	{SPR_ORBN, 5, 1, {NULL}, 0, 0, S_ORBINAUT1},		// S_ORBINAUT6
-	{SPR_ORBN, 7, 1, {NULL}, 0, 0, S_ORBINAUT_SHIELD},	// S_ORBINAUT_SHIELD
-	{SPR_ORBN, 6, 175, {NULL}, 0, 0, S_NULL},			// S_ORBINAUT_DEAD
-	{SPR_ORBN, 8, 175, {NULL}, 0, 0, S_NULL},			// S_ORBINAUT_SHIELDDEAD
+	{SPR_ORBN,  0, 1, {NULL}, 0, 0, S_ORBINAUT2},			// S_ORBINAUT1
+	{SPR_ORBN,  1, 1, {NULL}, 0, 0, S_ORBINAUT3},			// S_ORBINAUT2
+	{SPR_ORBN,  2, 1, {NULL}, 0, 0, S_ORBINAUT4},			// S_ORBINAUT3
+	{SPR_ORBN,  3, 1, {NULL}, 0, 0, S_ORBINAUT5},			// S_ORBINAUT4
+	{SPR_ORBN,  4, 1, {NULL}, 0, 0, S_ORBINAUT6},			// S_ORBINAUT5
+	{SPR_ORBN,  5, 1, {NULL}, 0, 0, S_ORBINAUT1},			// S_ORBINAUT6
+	{SPR_ORBN,  0, 175, {NULL}, 0, 0, S_NULL},			// S_ORBINAUT_DEAD
+	{SPR_ORBN,  6, 1, {NULL}, 0, 0, S_ORBINAUT_SHIELD2},	// S_ORBINAUT_SHIELD1
+	{SPR_ORBN,  7, 1, {NULL}, 0, 0, S_ORBINAUT_SHIELD3},	// S_ORBINAUT_SHIELD2
+	{SPR_ORBN,  8, 1, {NULL}, 0, 0, S_ORBINAUT_SHIELD4},	// S_ORBINAUT_SHIELD3
+	{SPR_ORBN,  9, 1, {NULL}, 0, 0, S_ORBINAUT_SHIELD5},	// S_ORBINAUT_SHIELD4
+	{SPR_ORBN, 10, 1, {NULL}, 0, 0, S_ORBINAUT_SHIELD6},	// S_ORBINAUT_SHIELD5
+	{SPR_ORBN, 11, 1, {NULL}, 0, 0, S_ORBINAUT_SHIELD1},	// S_ORBINAUT_SHIELD6
+	{SPR_ORBN,  6, 175, {NULL}, 0, 0, S_NULL},			// S_ORBINAUT_SHIELDDEAD
 
 	{SPR_JAWZ, 0, 1, {A_JawzChase}, 0, 0, S_JAWZ2},	// S_JAWZ1
 	{SPR_JAWZ, 4, 1, {A_JawzChase}, 0, 0, S_JAWZ3},	// S_JAWZ2
@@ -14669,7 +14674,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		S_NULL,         // missilestate
 		S_ORBINAUT_DEAD,// deathstate
 		S_NULL,         // xdeathstate
-		sfx_shbrk,      // deathsound
+		sfx_s3k5d,      // deathsound
 		64*FRACUNIT,    // speed
 		16*FRACUNIT,    // radius
 		32*FRACUNIT,    // height
@@ -14683,7 +14688,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 
 	{           // MT_ORBINAUT_SHIELD
 		-1,             // doomednum
-		S_ORBINAUT_SHIELD, // spawnstate
+		S_ORBINAUT_SHIELD1, // spawnstate
 		1000,           // spawnhealth
 		S_NULL,         // seestate
 		sfx_None,       // seesound
@@ -14723,7 +14728,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		S_NULL,         // missilestate
 		S_JAWZ_DEAD1,   // deathstate
 		S_JAWZ_DEAD2,   // xdeathstate
-		sfx_shbrk,      // deathsound
+		sfx_s3k5d,      // deathsound
 		7*FRACUNIT,     // speed
 		16*FRACUNIT,    // radius
 		32*FRACUNIT,    // height
@@ -14750,7 +14755,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		S_NULL,         // missilestate
 		S_JAWZ_DEAD1,   // deathstate
 		S_JAWZ_DEAD2,   // xdeathstate
-		sfx_shbrk,      // deathsound
+		sfx_s3k5d,      // deathsound
 		56*FRACUNIT,    // speed
 		16*FRACUNIT,    // radius
 		32*FRACUNIT,    // height
@@ -15155,7 +15160,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		S_NULL,         // missilestate
 		S_NULL,         // deathstate
 		S_NULL,         // xdeathstate
-		sfx_shbrk,      // deathsound
+		sfx_s3k5d,      // deathsound
 		0,              // speed
 		16*FRACUNIT,    // radius
 		24*FRACUNIT,    // height
diff --git a/src/info.h b/src/info.h
index ea07e2f3..4e2be267 100644
--- a/src/info.h
+++ b/src/info.h
@@ -3167,8 +3167,13 @@ typedef enum state
 	S_ORBINAUT4,
 	S_ORBINAUT5,
 	S_ORBINAUT6,
-	S_ORBINAUT_SHIELD,
 	S_ORBINAUT_DEAD,
+	S_ORBINAUT_SHIELD1,
+	S_ORBINAUT_SHIELD2,
+	S_ORBINAUT_SHIELD3,
+	S_ORBINAUT_SHIELD4,
+	S_ORBINAUT_SHIELD5,
+	S_ORBINAUT_SHIELD6,
 	S_ORBINAUT_SHIELDDEAD,
 	//}
 	//{ Jawz

From ee5ebd978a89611e02f04b0b3b031dd97298a733 Mon Sep 17 00:00:00 2001
From: Latapostrophe <hyperclassic3@gmail.com>
Date: Sun, 5 Aug 2018 13:08:27 +0200
Subject: [PATCH 33/38] Chat wordwrap fix + Minichat bg highlight

---
 objs/Mingw/SDL/Release/.gitignore |  2 --
 src/d_netcmd.c                    |  1 +
 src/g_game.c                      |  3 +++
 src/g_game.h                      |  2 +-
 src/hu_stuff.c                    | 34 ++++++++++++++++++++++---------
 src/m_menu.c                      |  5 +++--
 6 files changed, 32 insertions(+), 15 deletions(-)
 delete mode 100644 objs/Mingw/SDL/Release/.gitignore

diff --git a/objs/Mingw/SDL/Release/.gitignore b/objs/Mingw/SDL/Release/.gitignore
deleted file mode 100644
index 42c6dc2c..00000000
--- a/objs/Mingw/SDL/Release/.gitignore
+++ /dev/null
@@ -1,2 +0,0 @@
-# DON'T REMOVE
-# This keeps the folder from disappearing
diff --git a/src/d_netcmd.c b/src/d_netcmd.c
index f87ebcfa..c7647e02 100644
--- a/src/d_netcmd.c
+++ b/src/d_netcmd.c
@@ -787,6 +787,7 @@ void D_RegisterClientCommands(void)
 	CV_RegisterVar(&cv_chatspamprotection);
 	CV_RegisterVar(&cv_consolechat);
 	CV_RegisterVar(&cv_chatnotifications);
+	CV_RegisterVar(&cv_chatbackteint);
 	CV_RegisterVar(&cv_crosshair);
 	CV_RegisterVar(&cv_crosshair2);
 	CV_RegisterVar(&cv_crosshair3);
diff --git a/src/g_game.c b/src/g_game.c
index 762cac85..7af11dc6 100644
--- a/src/g_game.c
+++ b/src/g_game.c
@@ -421,6 +421,9 @@ consvar_t cv_chatnotifications = {"chatnotifications", "On", CV_SAVE, CV_OnOff,
 // chat spam protection (why would you want to disable that???)
 consvar_t cv_chatspamprotection = {"chatspamprotection", "On", CV_SAVE, CV_OnOff, NULL, 0, NULL, NULL, 0, 0, NULL};
 
+// minichat text background
+consvar_t cv_chatbackteint = {"chatbackteint", "Off", CV_SAVE, CV_OnOff, NULL, 0, NULL, NULL, 0, 0, NULL};
+
 // old shit console chat. (mostly exists for stuff like terminal, not because I cared if anyone liked the old chat.)
 //static CV_PossibleValue_t consolechat_cons_t[] = {{0, "Box"}, {1, "Console"}, {0, NULL}}; -- for menu, but menu disabled...
 consvar_t cv_consolechat = {"consolechat", "Off", CV_SAVE, CV_OnOff, NULL, 0, NULL, NULL, 0, 0, NULL};
diff --git a/src/g_game.h b/src/g_game.h
index 47c51d93..08ac73f2 100644
--- a/src/g_game.h
+++ b/src/g_game.h
@@ -54,7 +54,7 @@ extern tic_t timeinmap; // Ticker for time spent in level (used for levelcard di
 extern INT16 rw_maximums[NUM_WEAPONS];
 
 // used in game menu
-extern consvar_t cv_chatwidth, cv_chatnotifications, cv_chatheight, cv_chattime, cv_consolechat, cv_chatspamprotection, cv_compactscoreboard;
+extern consvar_t cv_chatwidth, cv_chatnotifications, cv_chatheight, cv_chattime, cv_consolechat, cv_chatspamprotection, cv_compactscoreboard, cv_chatbackteint;
 extern consvar_t cv_crosshair, cv_crosshair2, cv_crosshair3, cv_crosshair4;
 extern consvar_t cv_invertmouse, cv_alwaysfreelook, cv_mousemove;
 extern consvar_t cv_turnaxis,cv_moveaxis,cv_brakeaxis,cv_aimaxis,cv_lookaxis,cv_fireaxis,cv_driftaxis;
diff --git a/src/hu_stuff.c b/src/hu_stuff.c
index 338fd26c..22dc7fc5 100644
--- a/src/hu_stuff.c
+++ b/src/hu_stuff.c
@@ -1189,7 +1189,7 @@ static char *CHAT_WordWrap(INT32 x, INT32 w, INT32 option, const char *string)
 	size_t chw, i, lastusablespace = 0;
 	size_t slen;
 	char *newstring = Z_StrDup(string);
-	INT32 spacewidth = (vid.width < 640) ? 8 : 4, charwidth = (vid.width < 640) ? 8 : 4;
+	INT32 charwidth = 4;
 
 	slen = strlen(string);
 	x = 0;
@@ -1213,7 +1213,7 @@ static char *CHAT_WordWrap(INT32 x, INT32 w, INT32 option, const char *string)
 
 		if (c < 0 || c >= HU_FONTSIZE || !hu_font[c])
 		{
-			chw = spacewidth;
+			chw = charwidth;
 			lastusablespace = i;
 		}
 		else
@@ -1250,13 +1250,14 @@ static void HU_drawMiniChat(void)
 	INT32 charwidth = 4, charheight = 6;
 	INT32 dx = 0, dy = 0;
 	size_t i = chat_nummsg_min;
+	boolean prev_linereturn = false;	// a hack to prevent double \n while I have no idea why they happen in the first place.
 
 	INT32 msglines = 0;
 	// process all messages once without rendering anything or doing anything fancy so that we know how many lines each message has...
 
 	for (; i>0; i--)
 	{
-		const char *msg = CHAT_WordWrap(x, cv_chatwidth.value-charwidth, V_SNAPTOBOTTOM|V_SNAPTOLEFT|V_ALLOWLOWERCASE, chat_mini[i-1]);
+		const char *msg = CHAT_WordWrap(x+2, cv_chatwidth.value-(charwidth*2), V_SNAPTOBOTTOM|V_SNAPTOLEFT|V_ALLOWLOWERCASE, chat_mini[i-1]);
 		size_t j = 0;
 		INT32 linescount = 0;
 
@@ -1267,8 +1268,12 @@ static void HU_drawMiniChat(void)
 				if (msg[j] == '\n')	// get back down.
 				{
 					++j;
-					linescount += 1;
-					dx = 0;
+					if (!prev_linereturn)
+					{	
+						linescount += 1;
+						dx = 0;
+					}	
+					prev_linereturn = true;
 					continue;
 				}
 				else if (msg[j] & 0x80) // stolen from video.c, nice.
@@ -1283,7 +1288,7 @@ static void HU_drawMiniChat(void)
 			{
 				j++;
 			}
-
+			prev_linereturn = false;
 			dx += charwidth;
 			if (dx >= cv_chatwidth.value)
 			{
@@ -1300,6 +1305,7 @@ static void HU_drawMiniChat(void)
 	dx = 0;
 	dy = 0;
 	i = 0;
+	prev_linereturn = false;
 
 	for (; i<=(chat_nummsg_min-1); i++)	// iterate through our hot messages
 	{
@@ -1307,7 +1313,7 @@ static void HU_drawMiniChat(void)
 		INT32 timer = ((cv_chattime.value*TICRATE)-chat_timers[i]) - cv_chattime.value*TICRATE+9;	// see below...
 		INT32 transflag = (timer >= 0 && timer <= 9) ? (timer*V_10TRANS) : 0;	// you can make bad jokes out of this one.
 		size_t j = 0;
-		const char *msg = CHAT_WordWrap(x, cv_chatwidth.value-charwidth, V_SNAPTOBOTTOM|V_SNAPTOLEFT|V_ALLOWLOWERCASE, chat_mini[i]);	// get the current message, and word wrap it.
+		const char *msg = CHAT_WordWrap(x+2, cv_chatwidth.value-(charwidth*2), V_SNAPTOBOTTOM|V_SNAPTOLEFT|V_ALLOWLOWERCASE, chat_mini[i]);	// get the current message, and word wrap it.
 
 		while(msg[j])	// iterate through msg
 		{
@@ -1316,8 +1322,12 @@ static void HU_drawMiniChat(void)
 				if (msg[j] == '\n')	// get back down.
 				{
 					++j;
-					dy += charheight;
-					dx = 0;
+					if (!prev_linereturn)
+					{	
+						dy += charheight;
+						dx = 0;
+					}	
+					prev_linereturn = true;
 					continue;
 				}
 				else if (msg[j] & 0x80) // stolen from video.c, nice.
@@ -1331,11 +1341,15 @@ static void HU_drawMiniChat(void)
 			}
 			else
 			{
+				if (cv_chatbackteint.value)	// on request of wolfy
+					V_DrawFillConsoleMap(x + dx + 2, y+dy, charwidth, charheight, 239|V_SNAPTOBOTTOM|V_SNAPTOLEFT);
+				
 				UINT8 *colormap = CHAT_GetStringColormap(clrflag);
 				V_DrawChatCharacter(x + dx + 2, y+dy, msg[j++] |V_SNAPTOBOTTOM|V_SNAPTOLEFT|transflag, !cv_allcaps.value, colormap);
 			}
 
 			dx += charwidth;
+			prev_linereturn = false;
 			if (dx >= cv_chatwidth.value)
 			{
 				dx = 0;
@@ -1406,7 +1420,7 @@ static void HU_drawChatLog(INT32 offset)
 	{
 		INT32 clrflag = 0;
 		INT32 j = 0;
-		const char *msg = CHAT_WordWrap(x, cv_chatwidth.value-charwidth, V_SNAPTOBOTTOM|V_SNAPTOLEFT|V_ALLOWLOWERCASE, chat_log[i]);	// get the current message, and word wrap it.
+		const char *msg = CHAT_WordWrap(x+2, cv_chatwidth.value-(charwidth*2), V_SNAPTOBOTTOM|V_SNAPTOLEFT|V_ALLOWLOWERCASE, chat_log[i]);	// get the current message, and word wrap it.
 		while(msg[j])	// iterate through msg
 		{
 			if (msg[j] < HU_FONTSTART)	// don't draw
diff --git a/src/m_menu.c b/src/m_menu.c
index 16d04af1..eb5ffb6c 100644
--- a/src/m_menu.c
+++ b/src/m_menu.c
@@ -1412,9 +1412,10 @@ static menuitem_t OP_HUDOptionsMenu[] =
 	{IT_STRING | IT_CVAR | IT_CV_SLIDER,
 	                      NULL, "Chat box height",			&cv_chatheight,		 	105},
 	{IT_STRING | IT_CVAR, NULL, "Chat fadeout time",		&cv_chattime,			115},
+	{IT_STRING | IT_CVAR, NULL, "Chat background teint",	&cv_chatbackteint,		125},
 
-	{IT_STRING | IT_CVAR, NULL, "Background Color",			&cons_backcolor,		130},
-	{IT_STRING | IT_CVAR, NULL, "Console Text Size",		&cv_constextsize,		140},
+	{IT_STRING | IT_CVAR, NULL, "Background Color",			&cons_backcolor,		140},
+	{IT_STRING | IT_CVAR, NULL, "Console Text Size",		&cv_constextsize,		150},
 };
 
 static menuitem_t OP_GameOptionsMenu[] =

From c1749b01b105725f3ca6763a6fc2e97ebba1de15 Mon Sep 17 00:00:00 2001
From: toaster <rollerorbital@gmail.com>
Date: Sun, 5 Aug 2018 16:34:51 +0100
Subject: [PATCH 34/38] * Tint, not Teint. * Update/rearrange menus a tad, to
 be more appealing and correct some categorisation in general.

---
 src/d_netcmd.c |   2 +-
 src/g_game.c   |   2 +-
 src/g_game.h   |   2 +-
 src/hu_stuff.c |   7 ++--
 src/m_menu.c   | 111 ++++++++++++++++++++++++++++---------------------
 5 files changed, 71 insertions(+), 53 deletions(-)

diff --git a/src/d_netcmd.c b/src/d_netcmd.c
index c7647e02..521f3c42 100644
--- a/src/d_netcmd.c
+++ b/src/d_netcmd.c
@@ -787,7 +787,7 @@ void D_RegisterClientCommands(void)
 	CV_RegisterVar(&cv_chatspamprotection);
 	CV_RegisterVar(&cv_consolechat);
 	CV_RegisterVar(&cv_chatnotifications);
-	CV_RegisterVar(&cv_chatbackteint);
+	CV_RegisterVar(&cv_chatbacktint);
 	CV_RegisterVar(&cv_crosshair);
 	CV_RegisterVar(&cv_crosshair2);
 	CV_RegisterVar(&cv_crosshair3);
diff --git a/src/g_game.c b/src/g_game.c
index 7af11dc6..4a1c84d3 100644
--- a/src/g_game.c
+++ b/src/g_game.c
@@ -422,7 +422,7 @@ consvar_t cv_chatnotifications = {"chatnotifications", "On", CV_SAVE, CV_OnOff,
 consvar_t cv_chatspamprotection = {"chatspamprotection", "On", CV_SAVE, CV_OnOff, NULL, 0, NULL, NULL, 0, 0, NULL};
 
 // minichat text background
-consvar_t cv_chatbackteint = {"chatbackteint", "Off", CV_SAVE, CV_OnOff, NULL, 0, NULL, NULL, 0, 0, NULL};
+consvar_t cv_chatbacktint = {"chatbacktint", "Off", CV_SAVE, CV_OnOff, NULL, 0, NULL, NULL, 0, 0, NULL};
 
 // old shit console chat. (mostly exists for stuff like terminal, not because I cared if anyone liked the old chat.)
 //static CV_PossibleValue_t consolechat_cons_t[] = {{0, "Box"}, {1, "Console"}, {0, NULL}}; -- for menu, but menu disabled...
diff --git a/src/g_game.h b/src/g_game.h
index 08ac73f2..53c970d2 100644
--- a/src/g_game.h
+++ b/src/g_game.h
@@ -54,7 +54,7 @@ extern tic_t timeinmap; // Ticker for time spent in level (used for levelcard di
 extern INT16 rw_maximums[NUM_WEAPONS];
 
 // used in game menu
-extern consvar_t cv_chatwidth, cv_chatnotifications, cv_chatheight, cv_chattime, cv_consolechat, cv_chatspamprotection, cv_compactscoreboard, cv_chatbackteint;
+extern consvar_t cv_chatwidth, cv_chatnotifications, cv_chatheight, cv_chattime, cv_consolechat, cv_chatspamprotection, cv_compactscoreboard, cv_chatbacktint;
 extern consvar_t cv_crosshair, cv_crosshair2, cv_crosshair3, cv_crosshair4;
 extern consvar_t cv_invertmouse, cv_alwaysfreelook, cv_mousemove;
 extern consvar_t cv_turnaxis,cv_moveaxis,cv_brakeaxis,cv_aimaxis,cv_lookaxis,cv_fireaxis,cv_driftaxis;
diff --git a/src/hu_stuff.c b/src/hu_stuff.c
index 22dc7fc5..d8b6093c 100644
--- a/src/hu_stuff.c
+++ b/src/hu_stuff.c
@@ -1341,10 +1341,11 @@ static void HU_drawMiniChat(void)
 			}
 			else
 			{
-				if (cv_chatbackteint.value)	// on request of wolfy
-					V_DrawFillConsoleMap(x + dx + 2, y+dy, charwidth, charheight, 239|V_SNAPTOBOTTOM|V_SNAPTOLEFT);
-				
 				UINT8 *colormap = CHAT_GetStringColormap(clrflag);
+
+				if (cv_chatbacktint.value)	// on request of wolfy
+					V_DrawFillConsoleMap(x + dx + 2, y+dy, charwidth, charheight, 239|V_SNAPTOBOTTOM|V_SNAPTOLEFT);
+
 				V_DrawChatCharacter(x + dx + 2, y+dy, msg[j++] |V_SNAPTOBOTTOM|V_SNAPTOLEFT|transflag, !cv_allcaps.value, colormap);
 			}
 
diff --git a/src/m_menu.c b/src/m_menu.c
index eb5ffb6c..7da112b0 100644
--- a/src/m_menu.c
+++ b/src/m_menu.c
@@ -339,7 +339,6 @@ static void M_DrawSetupChoosePlayerMenu(void);
 static void M_DrawControl(void);
 static void M_DrawVideoMenu(void);
 static void M_DrawVideoMode(void);
-static void M_DrawHUDOptions(void);
 //static void M_DrawMonitorToggles(void);
 #ifdef HWRENDER
 static void M_OGL_DrawFogMenu(void);
@@ -1246,23 +1245,49 @@ static menuitem_t OP_Mouse2OptionsMenu[] =
 static menuitem_t OP_VideoOptionsMenu[] =
 {
 	{IT_STRING | IT_CALL,	NULL,	"Set Resolution...",	M_VideoModeMenu,		 10},
-#ifdef HWRENDER
-	{IT_SUBMENU|IT_STRING,	NULL,	"OpenGL Options...",	&OP_OpenGLOptionsDef,	 20},
-#endif
 #if (defined (__unix__) && !defined (MSDOS)) || defined (UNIXCOMMON) || defined (HAVE_SDL)
-	{IT_STRING|IT_CVAR,		NULL,	"Fullscreen",			&cv_fullscreen,			 30},
+	{IT_STRING|IT_CVAR,		NULL,	"Fullscreen",			&cv_fullscreen,			 20},
 #endif
 	{IT_STRING | IT_CVAR | IT_CV_SLIDER,
-							NULL,	"Gamma",				&cv_usegamma,			 50},
+							NULL,	"Gamma",				&cv_usegamma,			 30},
+
+	{IT_STRING | IT_CVAR,	NULL,	"Menu Highlights",		&cons_menuhighlight,     45},
+	// highlight info - (GOOD HIGHLIGHT, WARNING HIGHLIGHT) - 55 (see M_DrawVideoMenu)
 
 	{IT_STRING | IT_CVAR,	NULL,	"Draw Distance",		&cv_drawdist,			 70},
 	//{IT_STRING | IT_CVAR,	NULL,	"NiGHTS Draw Dist",		&cv_drawdist_nights,	 80},
-	{IT_STRING | IT_CVAR,	NULL,	"Weather Draw Dist.",	&cv_drawdist_precip,	 80},
+	{IT_STRING | IT_CVAR,	NULL,	"Weather Draw Distance",&cv_drawdist_precip,	 80},
 	{IT_STRING | IT_CVAR,	NULL,	"Weather Density",		&cv_precipdensity,		 90},
 	{IT_STRING | IT_CVAR,	NULL,	"Skyboxes",				&cv_skybox,				100},
 
-	{IT_STRING | IT_CVAR,	NULL,	"Show FPS",				&cv_ticrate,			120},
-	{IT_STRING | IT_CVAR,	NULL,	"Vertical Sync",		&cv_vidwait,			130},
+	{IT_STRING | IT_CVAR,	NULL,	"Show FPS",				&cv_ticrate,			115},
+	{IT_STRING | IT_CVAR,	NULL,	"Vertical Sync",		&cv_vidwait,			125},
+
+	{IT_STRING | IT_CVAR,	NULL,	"Console Text Size",	&cv_constextsize,		140},
+
+#ifdef HWRENDER
+	{IT_SUBMENU|IT_STRING,	NULL,	"OpenGL Options...",	&OP_OpenGLOptionsDef,	155},
+#endif
+};
+
+enum
+{
+	op_video_res = 0,
+#if (defined (__unix__) && !defined (MSDOS)) || defined (UNIXCOMMON) || defined (HAVE_SDL)
+	op_video_fullscreen,
+#endif
+	op_video_gamma,
+	op_video_hili,
+	op_video_dd,
+	op_video_wdd,
+	op_video_wd,
+	op_video_skybox,
+	op_video_fps,
+	op_video_vsync,
+	op_video_consoletext,
+#ifdef HWRENDER
+	op_video_ogl,
+#endif
 };
 
 static menuitem_t OP_VideoModeMenu[] =
@@ -1403,19 +1428,14 @@ static menuitem_t OP_HUDOptionsMenu[] =
 	{IT_STRING | IT_CVAR, NULL, "Speedometer Display",		&cv_kartspeedometer,	 45},
 	{IT_STRING | IT_CVAR, NULL, "Show \"CHECK\"",			&cv_kartcheck,			 55},
 
-	{IT_STRING | IT_CVAR, NULL, "Menu Highlights",			&cons_menuhighlight,     70},
-	// highlight info - (GOOD HIGHLIGHT, WARNING HIGHLIGHT) - 80 (see M_DrawHUDOptions)
-
-	//{IT_STRING | IT_CVAR, NULL, "Chat mode",				&cv_consolechat,		 95}, -- will ANYONE who doesn't know how to use the console want to touch this
+	//{IT_STRING | IT_CVAR, NULL, "Chat mode",				&cv_consolechat,		 70}, -- will ANYONE who doesn't know how to use the console want to touch this
 	{IT_STRING | IT_CVAR | IT_CV_SLIDER,
-	                      NULL, "Chat box width",			&cv_chatwidth,		 	 95},
+	                      NULL, "Chat box width",			&cv_chatwidth,		 	 70},
 	{IT_STRING | IT_CVAR | IT_CV_SLIDER,
-	                      NULL, "Chat box height",			&cv_chatheight,		 	105},
-	{IT_STRING | IT_CVAR, NULL, "Chat fadeout time",		&cv_chattime,			115},
-	{IT_STRING | IT_CVAR, NULL, "Chat background teint",	&cv_chatbackteint,		125},
-
-	{IT_STRING | IT_CVAR, NULL, "Background Color",			&cons_backcolor,		140},
-	{IT_STRING | IT_CVAR, NULL, "Console Text Size",		&cv_constextsize,		150},
+	                      NULL, "Chat box height",			&cv_chatheight,		 	 80},
+	{IT_STRING | IT_CVAR, NULL, "Chat fadeout time",		&cv_chattime,			 90},
+	{IT_STRING | IT_CVAR, NULL, "Show tint behind messages",&cv_chatbacktint,		100},
+	{IT_STRING | IT_CVAR, NULL, "Background Color",			&cons_backcolor,		110},
 };
 
 static menuitem_t OP_GameOptionsMenu[] =
@@ -1445,8 +1465,8 @@ static menuitem_t OP_ServerOptionsMenu[] =
 #endif
 
 	{IT_STRING | IT_CVAR,    NULL, "Intermission Timer",			&cv_inttime,			 40},
-	{IT_STRING | IT_CVAR,    NULL, "Voting Timer",					&cv_votetime,			 50},
-	{IT_STRING | IT_CVAR,    NULL, "Advance to Next Level",			&cv_advancemap,			 60},
+	{IT_STRING | IT_CVAR,    NULL, "Map Progression",				&cv_advancemap,			 50},
+	{IT_STRING | IT_CVAR,    NULL, "Voting Timer",					&cv_votetime,			 60},
 
 #ifndef NONET
 	{IT_STRING | IT_CVAR,    NULL, "Max Player Count",				&cv_maxplayers,			 80},
@@ -1454,7 +1474,7 @@ static menuitem_t OP_ServerOptionsMenu[] =
 	//{IT_STRING | IT_CVAR,    NULL, "Join on Map Change",			&cv_joinnextround,		100},
 
 	{IT_STRING | IT_CVAR,    NULL, "Allow WAD Downloading",			&cv_downloading,		100},
-	{IT_STRING | IT_CVAR,    NULL, "Attempts to Resynch",			&cv_resynchattempts,	110},
+	{IT_STRING | IT_CVAR,    NULL, "Attempts to resynchronise",		&cv_resynchattempts,	110},
 #endif
 };
 
@@ -1886,7 +1906,7 @@ menu_t OP_VideoOptionsDef =
 	&OP_MainDef,
 	OP_VideoOptionsMenu,
 	M_DrawVideoMenu,
-	60, 30,
+	30, 30,
 	0,
 	NULL
 };
@@ -1910,7 +1930,7 @@ menu_t OP_SoundOptionsDef =
 	&OP_MainDef,
 	OP_SoundOptionsMenu,
 	M_DrawSkyRoom,
-	60, 30,
+	30, 30,
 	0,
 	NULL
 };
@@ -1921,7 +1941,7 @@ menu_t OP_HUDOptionsDef =
 	sizeof (OP_HUDOptionsMenu)/sizeof (menuitem_t),
 	&OP_MainDef,
 	OP_HUDOptionsMenu,
-	M_DrawHUDOptions,
+	M_DrawGenericMenu, //M_DrawHUDOptions,
 	30, 30,
 	0,
 	NULL
@@ -3060,7 +3080,7 @@ void M_Init(void)
 #ifdef HWRENDER
 	// Permanently hide some options based on render mode
 	if (rendermode == render_soft)
-		OP_VideoOptionsMenu[1].status = IT_DISABLED;
+		OP_VideoOptionsMenu[op_video_ogl].status = IT_DISABLED;
 #endif
 
 #ifndef NONET
@@ -8216,8 +8236,25 @@ static void M_VideoModeMenu(INT32 choice)
 
 static void M_DrawVideoMenu(void)
 {
+	const char *str0 = ")";
+	const char *str1 = " Warning highlight";
+	const char *str2 = ",";
+	const char *str3 = "Good highlight";
+	INT32 x = BASEVIDWIDTH - currentMenu->x + 2, y = currentMenu->y + 55;
+	INT32 w0 = V_StringWidth(str0, 0), w1 = V_StringWidth(str1, 0), w2 = V_StringWidth(str2, 0), w3 = V_StringWidth(str3, 0);
 
 	M_DrawGenericMenu();
+
+	x -= w0;
+	V_DrawString(x, y, highlightflags, str0);
+	x -= w1;
+	V_DrawString(x, y, warningflags, str1);
+	x -= w2;
+	V_DrawString(x, y, highlightflags, str2);
+	x -= w3;
+	V_DrawString(x, y, recommendedflags, str3);
+	V_DrawRightAlignedString(x, y, highlightflags, "(");
+
 	V_DrawRightAlignedString(BASEVIDWIDTH - currentMenu->x, currentMenu->y + OP_VideoOptionsMenu[0].alphaKey,
 		(SCR_IsAspectCorrect(vid.width, vid.height) ? recommendedflags : highlightflags),
 			va("%dx%d", vid.width, vid.height));
@@ -8368,26 +8405,6 @@ static void M_HandleVideoMode(INT32 ch)
 	}
 }
 
-static void M_DrawHUDOptions(void)
-{
-	const char *str0 = ")";
-	const char *str1 = " Warning highlight";
-	const char *str2 = ",";
-	const char *str3 = "Good highlight";
-	INT32 x = BASEVIDWIDTH - currentMenu->x + 2, y = currentMenu->y + 80;
-	INT32 w0 = V_StringWidth(str0, 0), w1 = V_StringWidth(str1, 0), w2 = V_StringWidth(str2, 0), w3 = V_StringWidth(str3, 0);
-	M_DrawGenericMenu();
-	x -= w0;
-	V_DrawString(x, y, highlightflags, str0);
-	x -= w1;
-	V_DrawString(x, y, warningflags, str1);
-	x -= w2;
-	V_DrawString(x, y, highlightflags, str2);
-	x -= w3;
-	V_DrawString(x, y, recommendedflags, str3);
-	V_DrawRightAlignedString(x, y, highlightflags, "(");
-}
-
 // ===============
 // Monitor Toggles
 // ===============

From fb5ddf44d6d0d133ccf08e4faaa7d2eea8bb4c6e Mon Sep 17 00:00:00 2001
From: toaster <rollerorbital@gmail.com>
Date: Sun, 5 Aug 2018 16:35:33 +0100
Subject: [PATCH 35/38] Improve y_inter.c levelname setting.

---
 src/y_inter.c | 69 +++++++++++++++++++++++++++------------------------
 1 file changed, 36 insertions(+), 33 deletions(-)

diff --git a/src/y_inter.c b/src/y_inter.c
index 29c81df0..93459811 100644
--- a/src/y_inter.c
+++ b/src/y_inter.c
@@ -87,7 +87,7 @@ typedef union
 		INT32 num[MAXPLAYERS]; // Winner's player #
 		char *name[MAXPLAYERS]; // Winner's name
 		INT32 numplayers; // Number of players being displayed
-		char levelstring[62]; // holds levelnames up to 32 characters
+		char levelstring[64]; // holds levelnames up to 64 characters
 		// SRB2kart
 		UINT8 increase[MAXPLAYERS]; //how much did the score increase by?
 		UINT32 val[MAXPLAYERS]; //Gametype-specific value
@@ -204,6 +204,39 @@ static void Y_CalculateMatchData(boolean rankingsmode, void (*comparison)(INT32)
 	// Initialize variables
 	if ((data.match.rankingsmode = rankingsmode))
 		sprintf(data.match.levelstring, "* Total Rankings *");
+	else
+	{
+		// set up the levelstring
+		if (mapheaderinfo[prevmap]->levelflags & LF_NOZONE)
+		{
+			if (mapheaderinfo[prevmap]->actnum[0])
+				snprintf(data.match.levelstring,
+					sizeof data.match.levelstring,
+					"* %s %s *",
+					mapheaderinfo[prevmap]->lvlttl, mapheaderinfo[prevmap]->actnum);
+			else
+				snprintf(data.match.levelstring,
+					sizeof data.match.levelstring,
+					"* %s *",
+					mapheaderinfo[prevmap]->lvlttl);
+		}
+		else
+		{
+			const char *zonttl = (mapheaderinfo[prevmap]->zonttl[0] ? mapheaderinfo[prevmap]->zonttl : "ZONE");
+			if (mapheaderinfo[prevmap]->actnum[0])
+				snprintf(data.match.levelstring,
+					sizeof data.match.levelstring,
+					"* %s %s %s *",
+					mapheaderinfo[prevmap]->lvlttl, zonttl, mapheaderinfo[prevmap]->actnum);
+			else
+				snprintf(data.match.levelstring,
+					sizeof data.match.levelstring,
+					"* %s %s *",
+					mapheaderinfo[prevmap]->lvlttl, zonttl);
+		}
+
+		data.match.levelstring[sizeof data.match.levelstring - 1] = '\0';
+	}
 
 	for (i = 0; i < MAXPLAYERS; i++)
 	{
@@ -491,7 +524,7 @@ dotimer:
 	{
 		INT32 tickdown = (timer+1)/TICRATE;
 		V_DrawCenteredString(BASEVIDWIDTH/2, 188, hilicol,
-			va("%s in %d", cv_advancemap.string, tickdown));
+			va("%s starts in %d", cv_advancemap.string, tickdown));
 	}
 
 	// Make it obvious that scrambling is happening next round.
@@ -750,38 +783,8 @@ void Y_StartIntermission(void)
 			break;
 	}
 
-	if (intertype == int_race || intertype == int_match)
+	//if (intertype == int_race || intertype == int_match)
 	{
-		// set up the levelstring
-		if (strlen(mapheaderinfo[prevmap]->zonttl) > 0)
-		{
-			if (strlen(mapheaderinfo[prevmap]->actnum) > 0)
-				snprintf(data.match.levelstring,
-					sizeof data.match.levelstring,
-					"* %.32s %.32s %s *",
-					mapheaderinfo[prevmap]->lvlttl, mapheaderinfo[prevmap]->zonttl, mapheaderinfo[prevmap]->actnum);
-			else
-				snprintf(data.match.levelstring,
-					sizeof data.match.levelstring,
-					"* %.32s %.32s *",
-					mapheaderinfo[prevmap]->lvlttl, mapheaderinfo[prevmap]->zonttl);
-		}
-		else
-		{
-			if (strlen(mapheaderinfo[prevmap]->actnum) > 0)
-				snprintf(data.match.levelstring,
-					sizeof data.match.levelstring,
-					"* %.32s %s *",
-					mapheaderinfo[prevmap]->lvlttl, mapheaderinfo[prevmap]->actnum);
-			else
-				snprintf(data.match.levelstring,
-					sizeof data.match.levelstring,
-					"* %.32s *",
-					mapheaderinfo[prevmap]->lvlttl);
-		}
-
-		data.match.levelstring[sizeof data.match.levelstring - 1] = '\0';
-
 		//bgtile = W_CachePatchName("SRB2BACK", PU_STATIC);
 		usetile = useinterpic = false;
 		usebuffer = true;

From 59c64e1ec700b983c55e3f92f10b86d5be9031ea Mon Sep 17 00:00:00 2001
From: toaster <rollerorbital@gmail.com>
Date: Sun, 5 Aug 2018 16:38:46 +0100
Subject: [PATCH 36/38] * Change/add a handful of sounds. * Make Orbinaut
 Shields animate at 3 tics per frame, not 1. * Make dead items flash in and
 out of visibility. * Related to above, fix a few circumstances where
 returning was not properly handled after removing a mobj in mobjthinker.

---
 src/info.c   | 14 +++++++-------
 src/k_kart.c | 13 +++++++++++--
 src/p_mobj.c | 16 +++++++++++++++-
 3 files changed, 33 insertions(+), 10 deletions(-)

diff --git a/src/info.c b/src/info.c
index 8c6560fc..0fa1e1dd 100644
--- a/src/info.c
+++ b/src/info.c
@@ -2646,12 +2646,12 @@ state_t states[NUMSTATES] =
 	{SPR_ORBN,  4, 1, {NULL}, 0, 0, S_ORBINAUT6},			// S_ORBINAUT5
 	{SPR_ORBN,  5, 1, {NULL}, 0, 0, S_ORBINAUT1},			// S_ORBINAUT6
 	{SPR_ORBN,  0, 175, {NULL}, 0, 0, S_NULL},			// S_ORBINAUT_DEAD
-	{SPR_ORBN,  6, 1, {NULL}, 0, 0, S_ORBINAUT_SHIELD2},	// S_ORBINAUT_SHIELD1
-	{SPR_ORBN,  7, 1, {NULL}, 0, 0, S_ORBINAUT_SHIELD3},	// S_ORBINAUT_SHIELD2
-	{SPR_ORBN,  8, 1, {NULL}, 0, 0, S_ORBINAUT_SHIELD4},	// S_ORBINAUT_SHIELD3
-	{SPR_ORBN,  9, 1, {NULL}, 0, 0, S_ORBINAUT_SHIELD5},	// S_ORBINAUT_SHIELD4
-	{SPR_ORBN, 10, 1, {NULL}, 0, 0, S_ORBINAUT_SHIELD6},	// S_ORBINAUT_SHIELD5
-	{SPR_ORBN, 11, 1, {NULL}, 0, 0, S_ORBINAUT_SHIELD1},	// S_ORBINAUT_SHIELD6
+	{SPR_ORBN,  6, 3, {NULL}, 0, 0, S_ORBINAUT_SHIELD2},	// S_ORBINAUT_SHIELD1
+	{SPR_ORBN,  7, 3, {NULL}, 0, 0, S_ORBINAUT_SHIELD3},	// S_ORBINAUT_SHIELD2
+	{SPR_ORBN,  8, 3, {NULL}, 0, 0, S_ORBINAUT_SHIELD4},	// S_ORBINAUT_SHIELD3
+	{SPR_ORBN,  9, 3, {NULL}, 0, 0, S_ORBINAUT_SHIELD5},	// S_ORBINAUT_SHIELD4
+	{SPR_ORBN, 10, 3, {NULL}, 0, 0, S_ORBINAUT_SHIELD6},	// S_ORBINAUT_SHIELD5
+	{SPR_ORBN, 11, 3, {NULL}, 0, 0, S_ORBINAUT_SHIELD1},	// S_ORBINAUT_SHIELD6
 	{SPR_ORBN,  6, 175, {NULL}, 0, 0, S_NULL},			// S_ORBINAUT_SHIELDDEAD
 
 	{SPR_JAWZ, 0, 1, {A_JawzChase}, 0, 0, S_JAWZ2},	// S_JAWZ1
@@ -14816,7 +14816,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		0,						// display offset
 		100,					// mass
 		1,						// damage
-		sfx_bomb,				// activesound
+		sfx_s3k5c,				// activesound
 		MF_BOUNCE|MF_SHOOTABLE,	// flags
 		S_NULL					// raisestate
 	},
diff --git a/src/k_kart.c b/src/k_kart.c
index 080e7a11..7442d85f 100644
--- a/src/k_kart.c
+++ b/src/k_kart.c
@@ -3647,6 +3647,7 @@ void K_MoveKartPlayer(player_t *player, boolean onground)
 						//K_PlayTauntSound(player->mo);
 						player->kartstuff[k_itemheld] = 1;
 						player->pflags |= PF_ATTACKDOWN;
+						S_StartSound(player->mo, sfx_s254);
 
 						for (moloop = 0; moloop < player->kartstuff[k_itemamount]; moloop++)
 						{
@@ -3677,6 +3678,7 @@ void K_MoveKartPlayer(player_t *player, boolean onground)
 						player->kartstuff[k_itemamount]--;
 						player->kartstuff[k_eggmanheld] = 1;
 						player->pflags |= PF_ATTACKDOWN;
+						S_StartSound(player->mo, sfx_s254);
 						mo = P_SpawnMobj(player->mo->x, player->mo->y, player->mo->z, MT_FAKESHIELD);
 						mo->threshold = 10;
 						mo->movecount = 1;
@@ -3782,6 +3784,7 @@ void K_MoveKartPlayer(player_t *player, boolean onground)
 						mobj_t *mo;
 						player->kartstuff[k_itemheld] = 1;
 						player->pflags |= PF_ATTACKDOWN;
+						S_StartSound(player->mo, sfx_s254);
 						mo = P_SpawnMobj(player->mo->x, player->mo->y, player->mo->z, MT_SSMINE_SHIELD);
 						mo->threshold = 10;
 						mo->movecount = 1;
@@ -3892,7 +3895,7 @@ void K_MoveKartPlayer(player_t *player, boolean onground)
 					{
 						player->kartstuff[k_itemamount]--;
 						player->pflags |= PF_ATTACKDOWN;
-						K_DoHyudoroSteal(player);
+						K_DoHyudoroSteal(player); // yes. yes they do.
 					}
 					break;
 				case KITEM_POGOSPRING:
@@ -4089,8 +4092,14 @@ void K_MoveKartPlayer(player_t *player, boolean onground)
 	// Determine the outcome of your charge.
 	if (leveltime > starttime && player->kartstuff[k_boostcharge])
 	{
+		// Not even trying?
+		if (player->kartstuff[k_boostcharge] < 35)
+		{
+			if (player->kartstuff[k_boostcharge] > 17)
+				S_StartSound(player->mo, sfx_cdfm00); // chosen instead of a conventional skid because it's more engine-like
+		}
 		// Get an instant boost!
-		if (player->kartstuff[k_boostcharge] >= 35 && player->kartstuff[k_boostcharge] <= 50)
+		else if (player->kartstuff[k_boostcharge] <= 50)
 		{
 			player->kartstuff[k_sneakertimer] = -((21*(player->kartstuff[k_boostcharge]*player->kartstuff[k_boostcharge]))/425)+131; // max time is 70, min time is 7; yay parabooolas
 			if (!player->kartstuff[k_floorboost] || player->kartstuff[k_floorboost] == 3)
diff --git a/src/p_mobj.c b/src/p_mobj.c
index c2c08f33..016734bd 100644
--- a/src/p_mobj.c
+++ b/src/p_mobj.c
@@ -7462,12 +7462,23 @@ void P_MobjThinker(mobj_t *mobj)
 		case MT_BANANA:
 		case MT_FAKEITEM:
 			if (mobj->z <= mobj->floorz)
+			{
 				P_RemoveMobj(mobj);
+				return;
+			}
+			// fallthru
+		case MT_ORBINAUT_SHIELD:
+		case MT_BANANA_SHIELD:
+		case MT_FAKESHIELD:
+			mobj->flags2 ^= MF2_DONTDRAW;
 			break;
 		case MT_JAWZ:
 		case MT_JAWZ_DUD:
 			if (mobj->z <= mobj->floorz)
 				P_SetMobjState(mobj, mobj->info->xdeathstate);
+			// fallthru
+		case MT_JAWZ_SHIELD:
+			mobj->flags2 ^= MF2_DONTDRAW;
 			break;
 		case MT_SSMINE:
 		case MT_BLUEEXPLOSION:
@@ -7477,11 +7488,14 @@ void P_MobjThinker(mobj_t *mobj)
 				mobj->health = -100;
 			}
 			else
+			{
 				P_RemoveMobj(mobj);
+				return;
+			}
 			break;
 		case MT_MINEEXPLOSIONSOUND:
 			P_RemoveMobj(mobj);
-			break;
+			return;
 		//}
 		default:
 			break;

From 2abf256531cf37f47c2779e681557b090afcb1bd Mon Sep 17 00:00:00 2001
From: toaster <rollerorbital@gmail.com>
Date: Mon, 6 Aug 2018 16:19:07 +0100
Subject: [PATCH 37/38] * Fix various instances where bananadrag might not be
 reset. * Improve K_KillBananaChain to be tail recursive.

---
 src/k_kart.c  | 23 +++++++++++++++++------
 src/p_inter.c |  3 +++
 2 files changed, 20 insertions(+), 6 deletions(-)

diff --git a/src/k_kart.c b/src/k_kart.c
index 7442d85f..a395ad1d 100644
--- a/src/k_kart.c
+++ b/src/k_kart.c
@@ -2440,6 +2440,8 @@ static mobj_t *K_ThrowKartItem(player_t *player, boolean missile, mobjtype_t map
 	}
 	else
 	{
+		player->kartstuff[k_bananadrag] = 0; // RESET timer, for multiple bananas
+
 		if (dir == 1 || dir == 2)
 		{
 			// Shoot forward
@@ -2476,8 +2478,6 @@ static mobj_t *K_ThrowKartItem(player_t *player, boolean missile, mobjtype_t map
 		{
 			mobj_t *lasttrail = K_FindLastTrailMobj(player);
 
-			player->kartstuff[k_bananadrag] = 0; // RESET timer, for multiple bananas
-
 			if (lasttrail)
 			{
 				newx = lasttrail->x;
@@ -2724,10 +2724,10 @@ void K_DoPogoSpring(mobj_t *mo, fixed_t vertispeed, boolean mute)
 
 void K_KillBananaChain(mobj_t *banana, mobj_t *inflictor, mobj_t *source)
 {
-    if (banana->hnext)
-	{
-        K_KillBananaChain(banana->hnext, inflictor, source);
-	}
+	mobj_t *cachenext;
+
+killnext:
+	cachenext = banana->hnext;
 
 	if (banana->health)
 	{
@@ -2743,6 +2743,9 @@ void K_KillBananaChain(mobj_t *banana, mobj_t *inflictor, mobj_t *source)
 		if (inflictor)
 			P_InstaThrust(banana, R_PointToAngle2(inflictor->x, inflictor->y, banana->x, banana->y)+ANGLE_90, 16*FRACUNIT);
 	}
+
+	if ((banana = cachenext))
+		goto killnext;
 }
 
 void K_RepairOrbitChain(mobj_t *orbit)
@@ -2796,6 +2799,14 @@ static void K_MoveHeldObjects(player_t *player)
 		return;
 	}
 
+	if (P_MobjWasRemoved(player->mo->hnext))
+	{
+		// we need this here too because this is done in afterthink - pointers are cleaned up at the START of each tic...
+		P_SetTarget(&player->mo->hnext, NULL);
+		player->kartstuff[k_bananadrag] = 0;
+		return;
+	}
+
 	switch (player->mo->hnext->type)
 	{
 		case MT_ORBINAUT_SHIELD: // Kart orbit items
diff --git a/src/p_inter.c b/src/p_inter.c
index 6ca5e6c2..913d1722 100644
--- a/src/p_inter.c
+++ b/src/p_inter.c
@@ -2183,6 +2183,9 @@ void P_KillMobj(mobj_t *target, mobj_t *inflictor, mobj_t *source)
 
 			if (!target->target->player->kartstuff[k_itemamount])
 				target->target->player->kartstuff[k_itemheld] = 0;
+
+			if (target->target->hnext == target)
+				P_SetTarget(&target->target->hnext, NULL);
 		}
 	}
 	//

From c256f2ae299ced5b41ea5356bb430223bc83f0ff Mon Sep 17 00:00:00 2001
From: toaster <rollerorbital@gmail.com>
Date: Mon, 6 Aug 2018 18:37:03 +0100
Subject: [PATCH 38/38] * Tweak battle odds in a few ways, as authorised by
 Sal. 	* Steal a point from Ballhog and give to Orbinaut. 	* Make it
 based on the maximum number of bumpers someone has, not the average for
 everyone. 	* Improve the odds a tad if you're Wanted. 	* Tweak the
 score multiplication to give three points for Wanted and two for removing the
 last bumper (both disabled if you get the hit with a fake item). * Allow
 Karma players to drop their Random box. 	* These boxes flicker out of
 existence after 15 seconds. 	* Causes a WAIT penalty, too. * Remove some
 redundant PF_ATTACKDOWN stuff, given under the conditions the flag would be
 applied, it has definitely *already* been applied.

---
 src/k_kart.c  | 74 ++++++++++++++++++++++++++++++++-------------------
 src/p_enemy.c |  4 +--
 src/p_mobj.c  | 25 ++++++++++-------
 3 files changed, 65 insertions(+), 38 deletions(-)

diff --git a/src/k_kart.c b/src/k_kart.c
index a395ad1d..31cbbaea 100644
--- a/src/k_kart.c
+++ b/src/k_kart.c
@@ -495,10 +495,10 @@ static INT32 K_KartItemOddsBattle[NUMKARTRESULTS][6] =
 		 /*Invincibility*/ { 4, 2, 1, 2 }, // Invincibility
 				/*Banana*/ { 0, 0, 2, 0 }, // Banana
 		/*Eggman Monitor*/ { 0, 0, 1, 0 }, // Eggman Monitor
-			  /*Orbinaut*/ { 0, 1, 4, 0 }, // Orbinaut
+			  /*Orbinaut*/ { 0, 1, 5, 0 }, // Orbinaut
 				  /*Jawz*/ { 1, 3, 2, 2 }, // Jawz
 				  /*Mine*/ { 1, 3, 2, 2 }, // Mine
-			   /*Ballhog*/ { 1, 2, 2, 2 }, // Ballhog
+			   /*Ballhog*/ { 1, 2, 1, 2 }, // Ballhog
    /*Self-Propelled Bomb*/ { 0, 0, 0, 0 }, // Self-Propelled Bomb
 				  /*Grow*/ { 4, 2, 0, 2 }, // Grow
 				/*Shrink*/ { 0, 0, 0, 0 }, // Shrink
@@ -732,7 +732,7 @@ static void K_KartItemRoulette(player_t *player, ticcmd_t *cmd)
 	INT32 pdis = 0, useodds = 0;
 	INT32 spawnchance[NUMKARTRESULTS * NUMKARTODDS];
 	INT32 chance = 0, numchoices = 0;
-	INT32 avgbumper = 0;
+	INT32 bestbumper = 0;
 	boolean oddsvalid[9];
 	UINT8 disttable[14];
 	UINT8 distlen = 0;
@@ -750,13 +750,10 @@ static void K_KartItemRoulette(player_t *player, ticcmd_t *cmd)
 		if (!playeringame[i] || players[i].spectator)
 			continue;
 		pingame++;
-		if (players[i].kartstuff[k_bumper] > 0)
-			avgbumper += players[i].kartstuff[k_bumper];
+		if (players[i].kartstuff[k_bumper] > bestbumper)
+			bestbumper = players[i].kartstuff[k_bumper];
 	}
 
-	if (pingame)
-		avgbumper /= pingame;
-
 	// This makes the roulette produce the random noises.
 	if ((player->kartstuff[k_itemroulette] % 3) == 1 && P_IsLocalPlayer(player))
 		S_StartSound(NULL, sfx_mkitm1 + ((player->kartstuff[k_itemroulette] / 3) % 8));
@@ -836,7 +833,9 @@ static void K_KartItemRoulette(player_t *player, ticcmd_t *cmd)
 			useodds = 3;
 		else
 		{
-			SINT8 wantedpos = (player->kartstuff[k_bumper]-avgbumper)+2; // 0 is two bumpers below average, 2 is average
+			SINT8 wantedpos = (player->kartstuff[k_bumper]-bestbumper)+2; // 0 is two bumpers below best player's bumper count, 2 is best player's bumper count
+			if (K_IsPlayerWanted(player))
+				wantedpos--;
 			if (wantedpos > 2)
 				wantedpos = 2;
 			if (wantedpos < 0)
@@ -1614,7 +1613,14 @@ void K_DoInstashield(player_t *player)
 
 void K_SpinPlayer(player_t *player, mobj_t *source, INT32 type, boolean trapitem)
 {
-	const UINT8 scoremultiply = ((K_IsPlayerWanted(player) && !trapitem) ? 2 : 1);
+	UINT8 scoremultiply = 1;
+	if (!trapitem && G_BattleGametype())
+	{
+		if (K_IsPlayerWanted(player))
+			scoremultiply = 3;
+		else if (player->kartstuff[k_bumper] == 1)
+			scoremultiply = 2;
+	}
 
 	if (player->health <= 0)
 		return;
@@ -1691,7 +1697,14 @@ void K_SpinPlayer(player_t *player, mobj_t *source, INT32 type, boolean trapitem
 
 void K_SquishPlayer(player_t *player, mobj_t *source)
 {
-	const UINT8 scoremultiply = (K_IsPlayerWanted(player) ? 2 : 1);
+	UINT8 scoremultiply = 1;
+	if (G_BattleGametype())
+	{
+		if (K_IsPlayerWanted(player))
+			scoremultiply = 3;
+		else if (player->kartstuff[k_bumper] == 1)
+			scoremultiply = 2;
+	}
 
 	if (player->health <= 0)
 		return;
@@ -1753,7 +1766,14 @@ void K_SquishPlayer(player_t *player, mobj_t *source)
 
 void K_ExplodePlayer(player_t *player, mobj_t *source) // A bit of a hack, we just throw the player up higher here and extend their spinout timer
 {
-	const UINT8 scoremultiply = (K_IsPlayerWanted(player) ? 2 : 1);
+	UINT8 scoremultiply = 1;
+	if (G_BattleGametype())
+	{
+		if (K_IsPlayerWanted(player))
+			scoremultiply = 3;
+		else if (player->kartstuff[k_bumper] == 1)
+			scoremultiply = 2;
+	}
 
 	if (player->health <= 0)
 		return;
@@ -3593,8 +3613,22 @@ void K_MoveKartPlayer(player_t *player, boolean onground)
 	if (player && player->mo && player->mo->health > 0 && !player->spectator && !(player->exiting || mapreset) && player->kartstuff[k_spinouttimer] == 0)
 	{
 		// First, the really specific, finicky items that function without the item being directly in your item slot.
+		// Karma item dropping
+		if (ATTACK_IS_DOWN && player->kartstuff[k_comebackmode] && !player->kartstuff[k_comebacktimer])
+		{
+			mobj_t *newitem;
+
+			player->kartstuff[k_comebackmode] = 0;
+			player->kartstuff[k_comebacktimer] = comebacktime;
+			S_StartSound(player->mo, sfx_s254);
+
+			newitem = P_SpawnMobj(player->mo->x, player->mo->y, player->mo->z, MT_RANDOMITEM);
+			newitem->flags2 = (player->mo->flags2 & MF2_OBJECTFLIP);
+			newitem->fuse = 15*TICRATE; // selected randomly.
+			newitem->threshold = 69; // selected "randomly".
+		}
 		// Eggman Monitor throwing
-		if (ATTACK_IS_DOWN && player->kartstuff[k_eggmanheld])
+		else if (ATTACK_IS_DOWN && player->kartstuff[k_eggmanheld])
 		{
 			K_ThrowKartItem(player, false, MT_FAKEITEM, -1, false);
 			K_PlayTauntSound(player->mo);
@@ -3645,7 +3679,6 @@ void K_MoveKartPlayer(player_t *player, boolean onground)
 							S_StartSound(player->mo, sfx_kinvnc);
 						K_PlayTauntSound(player->mo);
 						player->kartstuff[k_itemamount]--;
-						player->pflags |= PF_ATTACKDOWN;
 					}
 					break;
 				case KITEM_BANANA:
@@ -3657,7 +3690,6 @@ void K_MoveKartPlayer(player_t *player, boolean onground)
 
 						//K_PlayTauntSound(player->mo);
 						player->kartstuff[k_itemheld] = 1;
-						player->pflags |= PF_ATTACKDOWN;
 						S_StartSound(player->mo, sfx_s254);
 
 						for (moloop = 0; moloop < player->kartstuff[k_itemamount]; moloop++)
@@ -3676,7 +3708,6 @@ void K_MoveKartPlayer(player_t *player, boolean onground)
 					{
 						K_ThrowKartItem(player, false, MT_BANANA, -1, false);
 						K_PlayTauntSound(player->mo);
-						player->pflags |= PF_ATTACKDOWN;
 						player->kartstuff[k_itemamount]--;
 						if (!player->kartstuff[k_itemamount])
 							player->kartstuff[k_itemheld] = 0;
@@ -3688,7 +3719,6 @@ void K_MoveKartPlayer(player_t *player, boolean onground)
 						mobj_t *mo;
 						player->kartstuff[k_itemamount]--;
 						player->kartstuff[k_eggmanheld] = 1;
-						player->pflags |= PF_ATTACKDOWN;
 						S_StartSound(player->mo, sfx_s254);
 						mo = P_SpawnMobj(player->mo->x, player->mo->y, player->mo->z, MT_FAKESHIELD);
 						mo->threshold = 10;
@@ -3713,7 +3743,6 @@ void K_MoveKartPlayer(player_t *player, boolean onground)
 
 						//K_PlayTauntSound(player->mo);
 						player->kartstuff[k_itemheld] = 1;
-						player->pflags |= PF_ATTACKDOWN;
 						S_StartSound(player->mo, sfx_s3k3a);
 
 						for (moloop = 0; moloop < player->kartstuff[k_itemamount]; moloop++)
@@ -3737,7 +3766,6 @@ void K_MoveKartPlayer(player_t *player, boolean onground)
 					{
 						K_ThrowKartItem(player, true, MT_ORBINAUT, 1, false);
 						K_PlayTauntSound(player->mo);
-						player->pflags |= PF_ATTACKDOWN;
 
 						player->kartstuff[k_itemamount]--;
 						if (!player->kartstuff[k_itemamount])
@@ -3756,7 +3784,6 @@ void K_MoveKartPlayer(player_t *player, boolean onground)
 
 						//K_PlayTauntSound(player->mo);
 						player->kartstuff[k_itemheld] = 1;
-						player->pflags |= PF_ATTACKDOWN;
 						S_StartSound(player->mo, sfx_s3k3a);
 
 						for (moloop = 0; moloop < player->kartstuff[k_itemamount]; moloop++)
@@ -3782,7 +3809,6 @@ void K_MoveKartPlayer(player_t *player, boolean onground)
 						else if (player->kartstuff[k_throwdir] == -1) // Throwing backward gives you a dud that doesn't home in
 							K_ThrowKartItem(player, true, MT_JAWZ_DUD, -1, false);
 						K_PlayTauntSound(player->mo);
-						player->pflags |= PF_ATTACKDOWN;
 
 						player->kartstuff[k_itemamount]--;
 						if (!player->kartstuff[k_itemamount])
@@ -3794,7 +3820,6 @@ void K_MoveKartPlayer(player_t *player, boolean onground)
 					{
 						mobj_t *mo;
 						player->kartstuff[k_itemheld] = 1;
-						player->pflags |= PF_ATTACKDOWN;
 						S_StartSound(player->mo, sfx_s254);
 						mo = P_SpawnMobj(player->mo->x, player->mo->y, player->mo->z, MT_SSMINE_SHIELD);
 						mo->threshold = 10;
@@ -3856,7 +3881,6 @@ void K_MoveKartPlayer(player_t *player, boolean onground)
 
 						spbplayer = player-players;
 
-						player->pflags |= PF_ATTACKDOWN;
 						player->kartstuff[k_itemamount]--;
 
 						K_PlayTauntSound(player->mo);
@@ -3876,7 +3900,6 @@ void K_MoveKartPlayer(player_t *player, boolean onground)
 						if (!cv_kartinvinsfx.value && !P_IsLocalPlayer(player))
 							S_StartSound(player->mo, sfx_kgrow);
 						S_StartSound(player->mo, sfx_kc5a);
-						player->pflags |= PF_ATTACKDOWN;
 						player->kartstuff[k_itemamount]--;
 					}
 					break;
@@ -3897,7 +3920,6 @@ void K_MoveKartPlayer(player_t *player, boolean onground)
 					if (ATTACK_IS_DOWN && !HOLDING_ITEM && NO_HYUDORO)
 					{
 						K_DoThunderShield(player);
-						player->pflags |= PF_ATTACKDOWN;
 						player->kartstuff[k_itemamount]--;
 					}
 					break;
@@ -3905,7 +3927,6 @@ void K_MoveKartPlayer(player_t *player, boolean onground)
 					if (ATTACK_IS_DOWN && !HOLDING_ITEM && NO_HYUDORO)
 					{
 						player->kartstuff[k_itemamount]--;
-						player->pflags |= PF_ATTACKDOWN;
 						K_DoHyudoroSteal(player); // yes. yes they do.
 					}
 					break;
@@ -3915,7 +3936,6 @@ void K_MoveKartPlayer(player_t *player, boolean onground)
 					{
 						K_PlayTauntSound(player->mo);
 						K_DoPogoSpring(player->mo, 32<<FRACBITS, false);
-						player->pflags |= PF_ATTACKDOWN;
 						player->kartstuff[k_pogospring] = 1;
 						player->kartstuff[k_itemamount]--;
 					}
diff --git a/src/p_enemy.c b/src/p_enemy.c
index a9333777..1e66fb82 100644
--- a/src/p_enemy.c
+++ b/src/p_enemy.c
@@ -8142,7 +8142,7 @@ void A_ItemPop(mobj_t *actor)
 	remains->flags = actor->flags; // Transfer flags
 	remains->flags2 = actor->flags2; // Transfer flags2
 	remains->fuse = actor->fuse; // Transfer respawn timer
-	remains->threshold = 68;
+	remains->threshold = (actor->threshold = 69 ? 69 : 68);
 	remains->skin = NULL;
 	remains->spawnpoint = actor->spawnpoint;
 
@@ -8156,7 +8156,7 @@ void A_ItemPop(mobj_t *actor)
 
 	remains->flags2 &= ~MF2_AMBUSH;
 
-	if (G_BattleGametype())
+	if (G_BattleGametype() && actor->threshold != 69)
 		numgotboxes++;
 
 	P_RemoveMobj(actor);
diff --git a/src/p_mobj.c b/src/p_mobj.c
index 016734bd..5af79ae8 100644
--- a/src/p_mobj.c
+++ b/src/p_mobj.c
@@ -8572,19 +8572,24 @@ for (i = ((mobj->flags2 & MF2_STRONGBOX) ? strongboxamt : weakboxamt); i; --i) s
 					return;
 				case MT_RANDOMITEM:
 					if (G_BattleGametype())
-						break;
-
-					// Respawn from mapthing if you have one!
-					if (mobj->spawnpoint)
 					{
-						P_SpawnMapThing(mobj->spawnpoint);
-						newmobj = mobj->spawnpoint->mobj; // this is set to the new mobj in P_SpawnMapThing
+						if (mobj->threshold != 69)
+							break;
 					}
 					else
-						newmobj = P_SpawnMobj(mobj->x, mobj->y, mobj->z, mobj->type);
+					{
+						// Respawn from mapthing if you have one!
+						if (mobj->spawnpoint)
+						{
+							P_SpawnMapThing(mobj->spawnpoint);
+							newmobj = mobj->spawnpoint->mobj; // this is set to the new mobj in P_SpawnMapThing
+						}
+						else
+							newmobj = P_SpawnMobj(mobj->x, mobj->y, mobj->z, mobj->type);
 
-					// Transfer flags2 (strongbox, objectflip)
-					newmobj->flags2 = mobj->flags2;
+						// Transfer flags2 (strongbox, objectflip)
+						newmobj->flags2 = mobj->flags2;
+					}
 					P_RemoveMobj(mobj); // make sure they disappear
 					return;
 				case MT_METALSONIC_BATTLE:
@@ -8607,6 +8612,8 @@ for (i = ((mobj->flags2 & MF2_STRONGBOX) ? strongboxamt : weakboxamt); i; --i) s
 			if (P_MobjWasRemoved(mobj))
 				return;
 		}
+		else if (mobj->type == MT_RANDOMITEM && mobj->threshold == 69 && mobj->fuse <= TICRATE)
+			mobj->flags2 ^= MF2_DONTDRAW;
 	}
 
 	I_Assert(mobj != NULL);