--#==============================================================================
--Our HUD hook for menus, flashes, and little trinkets
--#==============================================================================
local XSon = XSon

--Seperates a number or string into a table of individual characters.
local function GSSeperateChars(NUM)
	local TABLE,OK = {},tostring(NUM)
	local CHARS = string.len(OK) if not CHARS return TABLE end --
	for i = 1,CHARS
		TABLE[i] = string.sub(OK, i, i)
	end return TABLE --
end

--Show results of the last Beam Clash!
local function BeamClashResults(v, MYPOWER, FOEPOWER, TIMER, WON, p)
	if (p!=displayplayer) return end
	local YOFF,TRANS = 0,0
	if (TIMER < 16)
		if (TIMER%2) return end
		TRANS = (8-(TIMER/2))<<16
	elseif (TIMER > 38)
		TRANS = abs(38-TIMER)<<16
		TRANS = min(FF_TRANS90, max($, FF_TRANS10))
		YOFF = 38-TIMER
	end
	MYPOWER,FOEPOWER = max(1, MYPOWER/5),max(1, FOEPOWER/5)
	if (MYPOWER==FOEPOWER) --Results show simplified power scales, so adjust "equal" scores to avoid confusion.
		if WON==true
			MYPOWER= $+1
		else
			FOEPOWER = $+1
		end
	end
	local X,Y = 26,99+YOFF
	local SCALE = (WON==true) and 33000 or 24000
	local CMB,CM = v.getColormap(nil, SKINCOLOR_PUREBLACK)
	local TABLE,PATCH = GSSeperateChars(MYPOWER)
	if TABLE != nil
		CM = v.getColormap(nil, (WON==true) and SKINCOLOR_SUPASONIKKU or SKINCOLOR_GREY)
		for TEXT = 1, #TABLE
			if type(TABLE[TEXT])=="string"
				PATCH = v.cachePatch(("XSDSNU"+TABLE[TEXT]))
				v.drawScaled((X+2)<<16,(Y+2)<<16, SCALE, PATCH, TRANS, CMB) --The number's shadow.
				v.drawScaled(X<<16,Y<<16, SCALE, PATCH, TRANS, CM)
				X = $+27
			end
		end
	end
	
	TABLE = GSSeperateChars(FOEPOWER)
	if TABLE != nil
		SCALE = (WON!=true) and 33000 or 24000
		X,Y = 186,40+YOFF	
		CM = v.getColormap(nil, (WON==false) and SKINCOLOR_SUPASONIKKU or SKINCOLOR_GREY)
		for TEXT = 1, #TABLE 
			if type(TABLE[TEXT])=="string"
				PATCH = v.cachePatch(("XSDSNU"+TABLE[TEXT]))
				v.drawScaled((X+2)<<16,(Y+2)<<16, SCALE, PATCH, TRANS, CMB)
				v.drawScaled(X<<16,Y<<16, SCALE, PATCH, TRANS, CM)
				X = $+27
			end
		end
	end
end

--Always fun to build up a big counter clash number.
local function CounterClashDisplay(v, p, RB) if (p!=displayplayer) return end
	local TIMER,NUM = RB.timer or 0, min(9999, max(1, RB.num or 0))
	if (NUM <= 1) return end --Don't display the first counter.
	
	local XSHAKE,YSHAKE = 0,0
	if (NUM > 4) or (TIMER > 36)
		XSHAKE = (leveltime%2) and 1 or -1
		YSHAKE = (leveltime%4==0) and 2 or (leveltime%3==0) and 1 or (leveltime%2==0) and -1 or 0
	end
	local SCALE,TRANS = 24000+min(12000, NUM*620), 0
	local X,Y = 60+XSHAKE,35+YSHAKE
	
	if (TIMER > 37)
		if (TIMER%2) return end
		local DIFF = abs(37-TIMER)
		X = $+DIFF
		Y = $-(DIFF*2)
		TRANS = min(FF_TRANS90, DIFF<<16)
		SCALE = $+(DIFF*999)
	end
	
	local TABLE,PATCH = GSSeperateChars(NUM)
	if TABLE != nil
		local CMB,CM = v.getColormap(nil, SKINCOLOR_PUREBLACK)
		if (NUM > 5) 
			if (NUM >= 10) 
				local TEXT = (NUM>999) and "Someone press C3 already!" or (NUM>499) and "wtf..." or (NUM>99) and "What."
				or (NUM>=50) and "AMAAAAAZING!!!!!"
				or (NUM>=40) and "OUTSTANDING!!!!"
				or (NUM>=30) and "AWESOME!!!"
				or (NUM>=20) and "GREAT!!" or "GOOD!"
				v.drawString(X+48, Y-12, ((leveltime%2) and "\x82" or "\x85")+TEXT, V_ALLOWLOWERCASE, "thin-center")	
			end
			CM = v.getColormap(nil, (leveltime%2) and SKINCOLOR_SUPASONIKKU or SKINCOLOR_CRIMSON)
		else
			CM = v.getColormap(nil, 
			(NUM>4) and SKINCOLOR_FLAME or (NUM>3) and SKINCOLOR_SUPERORANGE5 
			or (NUM>2) and SKINCOLOR_SUPERSKY2 or SKINCOLOR_WHITE )
		end
		for TEXT = 1, #TABLE
			if type(TABLE[TEXT])=="string"
				PATCH = v.cachePatch(("XSDSNU"+TABLE[TEXT]))
				v.drawScaled((X+2)<<16,(Y+2)<<16, SCALE, PATCH, TRANS, CMB) --The number's shadow.
				v.drawScaled(X<<16,Y<<16, SCALE, PATCH, TRANS, CM)
				X = $+28
			end
		end
	end
end

--When two Falcon Punches collide.
local function BeamClashHUD(v, p,s,xs,CL, tp,t,xss,CLL)
	if (splitscreen==true and p==players[1]) or (p!=displayplayer) return end --

	local X,Y = 25,99	
	local FLAGS = V_SNAPTOLEFT
	local XSHAKE = (leveltime%2) and 1 or -1
	local YSHAKE = (leveltime%4==0) and 2 or (leveltime%3==0) and 1 or (leveltime%2==0) and -1 or 0	
	local XOFF,YOFF = XSHAKE,YSHAKE
	
	--Draw the power text.
	if CL.powerpressed
		XOFF = $*((CL.powerpressed>5) and 4 or (CL.powerpressed>3) and 3 or 2)
		YOFF = $*((CL.powerpressed>5) and 4 or (CL.powerpressed>3) and 3 or 2) + CL.powerpressed
	end	
	local MYPOWER,FOEPOWER,CM = 1,1
	if CL.power
		MYPOWER = max(1, CL.power/5)
	end
	if CLL.power
		FOEPOWER = max(1, CLL.power/5)
	end	
	local SCALE = 33000
	local CMB = v.getColormap(nil, SKINCOLOR_PUREBLACK)
	if (leveltime%2==0) and (p.cmd.buttons & BT_SPIN) --Trying your best!
		CMB = v.getColormap(nil, (leveltime%4==0) and SKINCOLOR_MINT or SKINCOLOR_EMERALD)
		SCALE = $+1250
	end
	if (MYPOWER > FOEPOWER-8) --About equal or winning!
		CM = v.getColormap(nil, (leveltime%2) and SKINCOLOR_ORANGE or SKINCOLOR_SUPASONIKKU)
	else --Losing!!
		CM = v.getColormap(nil, (leveltime%2) and SKINCOLOR_SUPERGOLD5 or SKINCOLOR_CRIMSON)
		SCALE = $-min(9000, abs(FOEPOWER-MYPOWER)*320)
	end
	
	local TABLE = GSSeperateChars(MYPOWER)
	if TABLE != nil
		if (#TABLE > 1)
			X = $-((#TABLE)*7)
		end	
		for TEXT = 1, #TABLE --Use X Sonic Font for the numbers.
			if type(TABLE[TEXT])=="string"
				local PATCH = v.cachePatch(("XSDSNU"+TABLE[TEXT]))
				v.drawScaled((X+XOFF+2)<<16,(Y+YOFF+2)<<16, SCALE, PATCH, 0, CMB)
				v.drawScaled((X+XOFF)<<16,(Y+YOFF)<<16, SCALE, PATCH, 0, CM)
				X = $+28
			end
		end
	end
	
	--Now for the enemy!
	X,Y = 186,40
	SCALE = 33000
	XOFF,YOFF = XSHAKE,YSHAKE
	if CLL.powerpressed
		XOFF = $*((CLL.powerpressed>5) and 4 or (CLL.powerpressed>3) and 3 or 2)
		YOFF = $*((CLL.powerpressed>5) and 4 or (CLL.powerpressed>3) and 3 or 2) + CLL.powerpressed
	end
	CMB = v.getColormap(nil, SKINCOLOR_PUREBLACK)
	if (leveltime%2==0) and (tp.cmd.buttons & BT_SPIN) --Trying your best!
		CMB = v.getColormap(nil, (leveltime%4==0) and SKINCOLOR_MINT or SKINCOLOR_EMERALD)
		SCALE = $+1250
	end
	if (FOEPOWER > MYPOWER-8)
		CM = v.getColormap(nil, (leveltime%2) and SKINCOLOR_ORANGE or SKINCOLOR_SUPASONIKKU)
	else
		CM = v.getColormap(nil, (leveltime%2) and SKINCOLOR_RUBY or SKINCOLOR_CRIMSON)
		SCALE = $-min(9000, abs(MYPOWER-FOEPOWER)*320)
	end
	TABLE = GSSeperateChars(FOEPOWER)
	if TABLE != nil
		if (#TABLE > 1)
			X = $-((#TABLE)*7)
		end	
		for TEXT = 1, #TABLE 
			if type(TABLE[TEXT])=="string"
				local PATCH = v.cachePatch(("XSDSNU"+TABLE[TEXT]))
				v.drawScaled((X+XOFF+2)<<16,(Y+YOFF+2)<<16, SCALE, PATCH, 0, CMB)
				v.drawScaled((X+XOFF)<<16,(Y+YOFF)<<16, SCALE, PATCH, 0, CM)
				X = $+28
			end
		end
	end

	--Now draw the buttons. A mashable JUMP button, and a hold-SPIN button for energy consumption.
	X,Y = 144+XSHAKE,141+YSHAKE
	SCALE = 34000
	if CL.powerpressed == 8 and (p.cmd.buttons & BT_JUMP)
		SCALE = 45000
		X,Y = $-3,$-2
		CM = v.getColormap(nil, SKINCOLOR_SUPERGOLD5)
	elseif (leveltime%3==0)
		SCALE = 29000
		X,Y = $+3,$+3
		CM = v.getColormap(nil, SKINCOLOR_FLAME)
	else
		CM = v.getColormap(nil, SKINCOLOR_CRIMSON)
	end
	v.drawScaled(X<<16, Y<<16, SCALE, v.cachePatch("XSBUTT1"), FLAGS, CM)
	
	X,Y = 190,146
	if (p.cmd.buttons & BT_SPIN)
		CM = v.getColormap(nil, (leveltime%2) and SKINCOLOR_MINT or SKINCOLOR_EMERALD)
		v.drawScaled(X<<16, Y<<16, 25500, v.cachePatch("XSBUTT2"), FLAGS, CM)
		if not (leveltime%3==0)
			CM = (leveltime%2) and "\x82" or "\x85"
			v.drawString(X+5, Y+7, CM+"Draining health", V_ALLOWLOWERCASE, "small")	
			v.drawString(X+5, Y+15, CM+" for power!!", V_ALLOWLOWERCASE, "small")
		end
	else
		v.drawScaled(X<<16, Y<<16, 27500, v.cachePatch("XSBUTT2"), FLAGS|FF_TRANS30, v.getColormap(nil, SKINCOLOR_GREY))
	end
/*	v.drawString(225, 191, 
	"\x88"+"CL.struggle: \x82"+tostring(CL.struggle)+"\x88 CL.init: \x82"+tostring(CL.init),
	V_ALLOWLOWERCASE, "small")
	v.drawString(225, 200, 
	"\x89"+"CLL.struggle: \x82"+tostring(CLL.struggle)+"\x89 CLL.init: \x82"+tostring(CLL.init),
	V_ALLOWLOWERCASE, "small") */
end

--Button tutor behaviour
--s.XS_tutor = {b1 = "2", ex1 = "3", text1="Zip Dash", timelimit=120}
XSon.ButtonTutor = function(p,s,xs, TUT)
	if TUT.init == nil TUT.init = 0 end if TUT.timelimit == nil TUT.timelimit = 61 end
	TUT.init = $+1

	if (TUT.init > TUT.timelimit)
		s.XS_tutor,s.XS_TutTimer = nil return --
	elseif not TUT.constant
		s.XS_TutTimer = s.XS_TutTimer and $+1 or 1
		if TUT.dontshow
			TUT.dontshow = $-1
		end	
		local MAXTIME = max(3, TUT.timelimit-70)
		if (TUT.init > MAXTIME)
			if (leveltime%2)
				TUT.dontshow = 1
			end
			if (TUT.init > (TUT.timelimit-70))
				TUT.forcetrans = TUT.forcetrans and $+1 or 1 
			end
		end
	end
end

--Display buttons neatly for the Ring Burst.
local function XS_ShowButtons(v, s, TUT)
	if TUT.dontshow return end --
	local TIME = s.XS_TutTimer or 0
	local BUTTONS,TEXT,EX = {},{},{}
	local X,Y,XOFF,YOFF,TRANS = 100,128,0,0,0
	
	BUTTONS[1] = TUT.b1 if TUT.b2 BUTTONS[2] = TUT.b2 end if TUT.b3 BUTTONS[3] = TUT.b3 end
	TEXT[1],TEXT[2],TEXT[3] = TUT.text1,TUT.text2,TUT.text3
	EX[1],EX[2],EX[3] = TUT.ex1,TUT.ex2,TUT.ex3	
	
	local BSCALE = 20500
	if TUT.forcetrans
		TRANS = max(0, min(TUT.forcetrans/6,9))<<16
		if (TUT.forcetrans>36)
			YOFF = $+(36+TUT.forcetrans)
		end
	else
		if TIME != "constant" and (TIME < 14)
			YOFF = $+(28-(TIME*2))
			if (TIME < 7)
				TRANS = max(0, 7-TIME)<<16
				BSCALE = 13500-(1000*TIME)
			end
		end
		if TUT.shake
			XOFF = $+((leveltime%2) and 1 or -1)
			YOFF = $+((leveltime%4==0) and 2 or (leveltime%3==0) and 1 or (leveltime%2==0) and -1 or 0)		
		end		
	end

	local COLOR = ((s.color<6) or s.color == SKINCOLOR_SUPERSILVER1) and SKINCOLOR_SONIKKU or s.color
	local CM = v.getColormap(nil, COLOR)
	local OGCM = CM
	local FLAGS = V_SNAPTOBOTTOM|TRANS|V_PERPLAYER
	--1:JUMP  2:SPIN  3:C1  4:C2  5:C3  6:TOSSFLAG
	for i = 1,#BUTTONS
		local KEY = tostring(BUTTONS[i]) if KEY == nil continue end --
		local EXTRA = EX[i]
		if type(EXTRA) == "string" and EXTRA !="none" --Extra/alt button?
			if EXTRA == "1"
				CM = v.getColormap(nil, SKINCOLOR_SALMON)
			elseif EXTRA == "2"
				CM = v.getColormap(nil, SKINCOLOR_EMERALD)
			elseif EXTRA == "3"
				CM = v.getColormap(nil, SKINCOLOR_GOLDENROD)			
			else
				CM = OGCM
			end			
			v.drawScaled((X+XOFF-18)<<16, (Y+YOFF-1)<<16, BSCALE-2000, v.cachePatch("XSBUTT"+EXTRA), FLAGS, CM)			
		end
		if KEY == "1"
			CM = v.getColormap(nil, SKINCOLOR_SALMON)
		elseif KEY == "2"
			CM = v.getColormap(nil, SKINCOLOR_EMERALD)
		elseif KEY == "3"
			CM = v.getColormap(nil, SKINCOLOR_GOLDENROD)			
		else
			CM = OGCM
		end		
		v.drawScaled((X+XOFF)<<16, (Y+YOFF)<<16, BSCALE, v.cachePatch(("XSBUTT"+KEY)), FLAGS, CM)
		if type(TEXT[i]) == "string" and TEXT[i]!="none"
			v.drawString((X+XOFF)+24, (Y+YOFF)+7, TEXT[i], FLAGS|V_ALLOWLOWERCASE, "thin")	
		end
		YOFF = $+20
	end
end

--This Planet Egg...this is the one from THEIR world!
--BTW, scoreboard HUD is in XS-EGG. "scores"
local function XS_AtPlanetEgg(v,p,s,xs, EGG, t)
	local INIT = EGG.init or 0
	local TIMER = EGG.timer or 0
	local TRANS,FLAGS = 0, V_SNAPTOTOP|V_SNAPTOLEFT|V_PERPLAYER
	if (TIMER < 9)
		TRANS = max(9-TIMER, $)
	elseif (INIT < 30)
		TRANS = max((30-INIT)/3, $)
	end

	local X,Y, XO,YO = 127,142, 0,0
	if (TRANS < 10)
		local TCOLOR = (t.CanOffer) and "\x80" or "\x85" --Display the text.
		YO = $+(TRANS*2)
		if (INIT > 45) and TCOLOR == "\x80"
			if (INIT%46==0)
				TCOLOR = "\x81"
				XO,YO = $-3,$-2	
			elseif (INIT%47==0)
				TCOLOR = "\x8f"
				XO,YO = $+1,$+2
			end
		end
		local PTRANS = (not t.CanOffer) and FF_TRANS90 or TRANS<<16
		v.drawScaled((X+XO)<<16, (Y+YO)<<16, 41000, v.cachePatch("XSBUTT5"), PTRANS, v.getColormap(nil, XSon.DarkAura(0)))
		
		if t.sacrifice
			v.drawString(160+XO, (Y+YO)-20,  "\x8f"+"Sacrifice Required:", max(3<<16, TRANS<<16)|V_ALLOWLOWERCASE, "thin-center")
			v.drawString(160+XO, (Y+YO)-9,  TCOLOR+t.sacrifice, max(2<<16, TRANS<<16)|V_ALLOWLOWERCASE, "thin-center")
		end
	end
	
	--Screen Fuzzing?
/*	local PATCH,COLOR = "XSSCFUZZ", SKINCOLOR_GREY
	local vHEIGHT,SCALE = v.height() or 0, FRACUNIT
	X,Y = 0,0
	
	if vHEIGHT == 1080 --1080p.
		Y = -40
		SCALE = 82033	
	elseif (vHEIGHT == 1200) or (vHEIGHT == 800) or (vHEIGHT == 400) or (vHEIGHT == 200) --Green resolutions.
		SCALE = FRACUNIT
		Y = 0
	end	
	if (leveltime%2==0)
		X = 320
		FLAGS = ($ & ~V_SNAPTOLEFT)|V_SNAPTORIGHT|V_FLIP
	end
	if (INIT > 20) 
		local FTRANS  = max(4, 14-(INIT/5))
		if (FTRANS < 10)
			FTRANS  = FTRANS <<16
			v.drawScaled((X)<<16, (13+Yo)<<16, SCALE, v.cachePatch(PATCH), FLAGS|FTRANS, v.getColormap(nil, COLOR))	
		end
	end */
end

--You've obtained a Power Ring! Do an animation, or just display it normally?
local function XS_PowerRing(v,p,s,xs)
	local Xo,Yo,SCALE,TRANS = 0,0,FRACUNIT/8,FF_TRANS10
	if p.xsringburst
		if xs.PowerRing and (xs.PowerRing < 60) and (leveltime%2)
			return --Flicker!
		end
		local ADD = (leveltime%4==0) and 4 or (leveltime%3==0) and 3 or (leveltime%2==0) and 2 or 1
		Xo = $-(ADD) --Compensate for the scaling
		Yo = $-(ADD) 
		SCALE = $+(ADD*900)
	elseif xs.FirstPWring and not ((xs.FirstPWring < 60) and (xs.FirstPWring%2==0))
		local ZO = (xs.FirstPWring>268) and xs.FirstPWring-267 or 0
		local ZTRANS = (xs.FirstPWring>274) and FF_TRANS60 or (xs.FirstPWring>270) and FF_TRANS30 or 0
		v.drawString(160, 163+ZO, "You got a\x82 Power Ring!", V_SNAPTOBOTTOM|ZTRANS|V_PERPLAYER, "small-center")
		v.drawString(160, 171+ZO, "It gives one Ring Burst\x82 infinite\x80 energy!", V_SNAPTOBOTTOM|ZTRANS|V_PERPLAYER, "small-center")		
	end
	
	if s.XS_pwringget
		if (s.XS_pwringget < 0) --Fade from the screen gracefully.
			TRANS = (10-abs(s.XS_pwringget))<<16
		else
			Xo = $+(s.XS_pwringget*-11) --Come in from the left.
			Yo = $+(s.XS_pwringget*7) --Come in from below.
			SCALE = $+(s.XS_pwringget*1500)
			TRANS = min(FF_TRANS60, s.XS_pwringget<<16)
		end
	end
	local FLAGS = V_SNAPTOTOP|V_SNAPTORIGHT|V_PERPLAYER
	if xs.HUDpref!=true
		Xo = $-182
		Yo = $+163
		FLAGS = V_SNAPTOBOTTOM|V_SNAPTOLEFT|V_PERPLAYER
	elseif s.XShudpopin and not s.noXSHUDthisstage
		Yo = $-s.XShudpopin
		TRANS = min(FF_TRANS90, max($, s.XShudpopin<<16) )
	end
	v.drawScaled((258+Xo)<<16, (13+Yo)<<16, SCALE, v.cachePatch("XSPOWERR"), FLAGS|TRANS|V_ADD|V_PERPLAYER)	
end

--Seeing red.
local function DSS_RageHUD(v, RAGE)
	if splitscreen and players[1] return end --
	local STYLE = (RAGE.type=="veins") and "veins" or "bloodlust"
	local XSHAKE = (leveltime%2) and 1 or -1
	local YSHAKE = (leveltime%4==0) and 2 or (leveltime%3==0) and 1 or (leveltime%2==0) and -1 or 0	
	local SCALE = FRACUNIT
	local TRANS = min(FF_TRANS90, RAGE.trans<<16)
	local COLOR = v.getColormap(nil, RAGE.color)
	
	if STYLE == "bloodlust"
		local PATCH = v.cachePatch("XSDSSRGO")
		if RAGE.double
			TRANS = min(FF_TRANS60, $)
		end
		v.drawScaled((-1+XSHAKE)<<16, (-2+YSHAKE)<<16, SCALE, PATCH, V_SNAPTOTOP|V_SNAPTOLEFT|TRANS, COLOR)
		v.drawScaled((321+XSHAKE)<<16, (-2+YSHAKE)<<16, SCALE, PATCH, V_SNAPTOTOP|V_SNAPTORIGHT|TRANS|V_FLIP, COLOR)
		if RAGE.double
			COLOR = v.getColormap(nil, SKINCOLOR_MIDNIGHT)
			v.drawScaled((-10+XSHAKE)<<16, (-10+YSHAKE*5)<<16, SCALE*3/2, PATCH, V_SNAPTOLEFT|FF_TRANS80, COLOR)
			v.drawScaled((331+XSHAKE)<<16, (-10+YSHAKE*5)<<16, SCALE*3/2, PATCH, V_FLIP|V_SNAPTORIGHT|FF_TRANS80, COLOR)			
		end
	else
		v.drawScaled(XSHAKE<<16, (-2+YSHAKE)<<16, SCALE, v.cachePatch("XSDSSRGT"), V_SNAPTOTOP|TRANS, COLOR)
		v.drawScaled((-1+XSHAKE)<<16, (-2+YSHAKE)<<16, SCALE, v.cachePatch("XSDSSRGL"), V_SNAPTOLEFT|TRANS, COLOR)
		v.drawScaled((273+XSHAKE)<<16, (-2+YSHAKE)<<16, SCALE, v.cachePatch("XSDSSRGR"), V_SNAPTORIGHT|TRANS, COLOR)
		v.drawScaled((-1+XSHAKE)<<16, (166+YSHAKE)<<16, SCALE, v.cachePatch("XSDSSRGB"), V_SNAPTOBOTTOM|TRANS, COLOR)
	end
end

--Button inputs for FreeFlight
local function FreeFlightPrompts(v, p,s,xs, RB)
	if RB.painstun and (RB.painstun>8) --In pain, can't display buttons rn!
	or xs.ChaosLambent and (xs.ChaosLambent.ChaosControl==true or xs.ChaosLambent.struggle)
		return --
	end
	local XOFF,YOFF,SCALE,TRANS = 0,0,FRACUNIT/5,0
	local FLAGS = V_PERPLAYER|V_SNAPTOBOTTOM|V_SNAPTOLEFT
	local INIT = RB.init or 0
	if (INIT < 18)
		XOFF = -18+INIT
		TRANS = max($, min(FF_TRANS90, (9-INIT)<<16))
	end
	local X,Y = 1+XOFF,151+YOFF --Starting coordinates. 110
	if (xs.HUDpref!=true) or s.noXSHUDthisstage
		local vHEIGHT = v.height() or 0
		Y = ((vHEIGHT==1080) and 41 or 59)+YOFF --Adjust for vanilla HUD!
		X = $+10+(XOFF) --More XOFF!
	elseif RB.LockOn and RB.LockOn.valid
		Y = $-((v.height()==1080) and 46 or 64) --Move up a bit to not get in the way of the Life Icons during Tenkaichi.
	end
	local OGY = Y
	local XADD,YADD,textY = 20, 12, 5
	local FIRING = (xs.ChaosLambent and xs.ChaosLambent.firing)
	
	if FIRING != true
		local OTRANS = TRANS
		if xs.ChaosLambent and ((xs.ChaosLambent.init or 0) < 24)
			--Aim Up button
			v.drawScaled(X<<16, Y<<16, SCALE, v.cachePatch("XSBUTT1"), FLAGS|TRANS, v.getColormap(nil, SKINCOLOR_ROSY))
			v.drawString(X+XADD, Y+textY, "\x82"+"Aim Up", FLAGS|TRANS, "small")	
			
			Y = OGY+YADD
			
			--Aim Down button
			v.drawScaled(X<<16, Y<<16, SCALE, v.cachePatch("XSBUTT2"), FLAGS|TRANS, v.getColormap(nil, SKINCOLOR_MINT))
			v.drawString(X+XADD, Y+textY, "\x82"+"Aim Down", FLAGS|TRANS, "small")	
			
			Y = OGY+YADD*2
			
			--Chaos Snap can still be used at this stage!
			v.drawScaled(X<<16, Y<<16, SCALE, v.cachePatch("XSBUTT4"), FLAGS|TRANS, v.getColormap(nil, SKINCOLOR_PURPLE))
			v.drawString(X+(XADD-4), Y+textY-1, "/", FLAGS|TRANS, "thin")
			v.drawScaled((X+XADD)<<16, Y<<16, SCALE, v.cachePatch("XSBUTT6"), FLAGS|TRANS, v.getColormap(nil, SKINCOLOR_PURPLE))
			local TTEXT = (RB.LockOn and RB.LockOn.valid) and "Dodge/Kick" or "Chaos Snap"
			v.drawString(X+(XADD+18), Y+textY, "\x88"+TTEXT, FLAGS|TRANS, "small")	
			
		else
			if xs.snapdivetrick and (xs.snapdivetrick < 12) --Sonic Boom Kick button!
				if not (RB.LockOn and RB.LockOn.valid)
					Y = OGY+YADD
					v.drawScaled(X<<16, Y<<16, SCALE, v.cachePatch("XSBUTT4"), FLAGS|TRANS, v.getColormap(nil, SKINCOLOR_AETHER))
					v.drawString(X+(XADD-4), Y+textY-1, "/", FLAGS|TRANS, "thin")
					v.drawScaled((X+XADD)<<16, Y<<16, SCALE, v.cachePatch("XSBUTT2"), FLAGS|TRANS, v.getColormap(nil, SKINCOLOR_AETHER))
					v.drawString(X+(XADD+18), Y+textY, ((leveltime%2) and "\x80" or "\x88")+"Sonic Boom Kick!", FLAGS|TRANS, "small")
				end
				return --
			end
			local TCM = SKINCOLOR_GREY
			if RB.LockOn and RB.LockOn.valid
				if xs.ChaosLambent and (xs.ChaosLambent.firing or xs.ChaosLambent.struggle)
				or RB.RingBurstChase and (RB.RingBurstChase > 9)
				or RB.explosion and (RB.explosion > 9)
				or RB.crossedarms and (RB.crossedarms>6) and (p.cmd.buttons & BT_CUSTOM3) and not RB.painstun
				or RB.SRAlunge and (RB.SRAlunge>9)
					TCM = nil
				end
			end
			
			if xs.ChaosSnap and (not xs.ChaosSnap.timer or (xs.ChaosSnap.timer > 6))
				TRANS = max($, FF_TRANS70)			
			elseif xs.zipdash and not xs.zipdashslide
				TRANS = max(OTRANS, FF_TRANS60)
			end
			
			if TCM != nil
				--Zip Dash button
				v.drawScaled(X<<16, Y<<16, SCALE, v.cachePatch("XSBUTT3"), FLAGS|TRANS, v.getColormap(nil, SKINCOLOR_GOLDENROD))
				if RB.LockOn and RB.LockOn.valid
					v.drawString(X+XADD, Y+textY, "\x82"+"Zipdash Tackle", FLAGS|TRANS, "small")	
				else
					v.drawString(X+XADD, Y+textY, "\x82"+"Zip Dash", FLAGS|TRANS, "small")	
				end
			end
			--Chaos Snap button!
			TRANS = OTRANS
			Y = OGY+YADD
			if xs.ChaosSnap and (xs.ChaosSnap.timer == nil or (xs.ChaosSnap.timer > 6))
				TRANS = max($, FF_TRANS70)		
				OTRANS = TRANS
			end
			v.drawScaled(X<<16, Y<<16, SCALE, v.cachePatch("XSBUTT4"), FLAGS|TRANS, v.getColormap(nil, SKINCOLOR_PURPLE))
			v.drawString(X+(XADD-4), Y+textY-1, "/", FLAGS|TRANS, "thin")
			v.drawScaled((X+XADD)<<16, Y<<16, SCALE, v.cachePatch("XSBUTT6"), FLAGS|TRANS, v.getColormap(nil, SKINCOLOR_PURPLE))
			if (RB.LockOn and RB.LockOn.valid)
				v.drawString(X+(XADD+18), Y+textY, "\x88"+"Dodge/Kick", FLAGS|TRANS, "small")
			else
				v.drawString(X+(XADD+18), Y+textY, "\x88"+"Chaos Snap", FLAGS|TRANS, "small")
			end
			
			if xs.zipdash and not xs.zipdashslide
				TRANS = max($, FF_TRANS60)	
			end
			
			Y = OGY+(YADD*2)
			
			if TCM != nil --Chaos Lambent button!
				v.drawScaled(X<<16, Y<<16, SCALE, v.cachePatch("XSBUTT1"), FLAGS|TRANS, v.getColormap(nil, SKINCOLOR_RED))
				v.drawString(X+(XADD-4), Y+textY-2, "+", FLAGS|TRANS, "thin")	
				v.drawScaled((X+XADD)<<16, Y<<16, SCALE, v.cachePatch("XSBUTT2"), FLAGS|TRANS, v.getColormap(nil, SKINCOLOR_FOREST))
				
				v.drawString(X+(XADD+18), Y+textY, "\x87"+"Chaos Lambent", FLAGS|TRANS, "small")
				if not (RB.LockOn and RB.LockOn.valid)
					local RTRANS = (max(2<<16, TRANS))|V_ADD --Ring cost.
					v.drawScaled((X-9+XADD*5)<<16, (Y+textY+4)<<16, 9000, v.cachePatch("RINGA0"), FLAGS|RTRANS )
					v.drawString(X-6+(XADD*5), Y+textY, "x5", FLAGS|RTRANS|V_ALLOWLOWERCASE, "small")
				end
			end
			
			--Cancel Flight!
			TRANS = OTRANS
			Y = OGY+(YADD*3)
			
			if TCM != nil --Chaos Lambent button!
				if RB.LockOn and RB.LockOn.valid
					if (RB.PvPENERGY>4850)
						if (RB.PvPENERGY>9850) and xs.ChaosLambent and ((xs.ChaosLambent.init or 0) < 17)
							local TC = (leveltime%3==0) and "\x84" or (leveltime%2==0) and "\x80" or "\x88"
							v.drawString(X+XADD, Y+textY, TC+"Chaos Control!", FLAGS|TRANS, "small")
							TCM = SKINCOLOR_BLUE
						else
							v.drawString(X+XADD, Y+textY, "\x80"+"Chaos Blast", FLAGS|TRANS, "small")
							TCM = SKINCOLOR_SILVER
						end
					elseif p.XS_chargehaxx 
					or RB.cancharge
					or RB.charging
						local CMT = "\x8b"
						if (leveltime%2)
							if RB.charging or RB.cancharge and not p.XS_chargehaxx
								CMT = "\x8a"
							end
						end
						v.drawString(X+XADD, Y+textY, CMT+"Power up", FLAGS|TRANS, "small")
					else
						v.drawString(X+XADD, Y+textY, "\x86"+"Taunt", FLAGS|TRANS, "small")
					end
				else
					v.drawString(X+XADD, Y+textY, "\x86"+"Toggle FreeFlight", FLAGS|TRANS, "small")
				end
				--Now draw the button
				v.drawScaled(X<<16, Y<<16, SCALE, v.cachePatch("XSBUTT5"), FLAGS|TRANS, v.getColormap(nil, TCM))
			end
		end
	else
		if xs.ChaosLambent and xs.ChaosLambent.init
			TRANS = max($, min(FF_TRANS90, ((xs.ChaosLambent.init-10)/2)<<16))
		end
		v.drawScaled(X<<16, Y<<16, SCALE, v.cachePatch("XSBUTT1"), FLAGS|TRANS, v.getColormap(nil, SKINCOLOR_ROSY))
		v.drawString(X+XADD, Y+textY, "\x82"+"Aim Up", FLAGS|TRANS, "small")	
			
		Y = OGY+YADD
		
		v.drawScaled(X<<16, Y<<16, SCALE, v.cachePatch("XSBUTT2"), FLAGS|TRANS, v.getColormap(nil, SKINCOLOR_MINT))
		v.drawString(X+XADD, Y+textY, "\x82"+"Aim Down", FLAGS|TRANS, "small")	
	end
end


--Overkill/Damage text and button prompts.
local function AssaultHUD(v, p,s,xs, ASSAULT, BAG)	
	local OVERKILL = ASSAULT.overkill or ASSAULT.damage if OVERKILL == nil return end --
	local SST = (xs.FreeFlight) and true or false
	local TEXTPATCH = v.cachePatch(((ASSAULT.overkill) and not (SST)) and "XSDSSOK" or "XSDSSDM")
	local X,Y = 146,160
	local XSHAKE = (leveltime%2) and 1 or -1
	local YSHAKE = (leveltime%4==0) and 2 or (leveltime%3==0) and 1 or (leveltime%2==0) and -1 or 0
	local SHOUT = 0
	
	local OKSCALE = FRACUNIT/7
	local CM,CM2,CM3 = SKINCOLOR_SUPERSILVER1, SKINCOLOR_PUREBLACK, (ASSAULT.overkill) and SKINCOLOR_GOLDENROD or SKINCOLOR_SUPERSILVER1
	if ASSAULT.overkill and (leveltime%2==0)
		if (leveltime%4==0)
			CM3 = SKINCOLOR_ICY
		end
		if (SST)
			CM = (leveltime%4==0) and SKINCOLOR_FLAME or SKINCOLOR_RED
		else
			CM = (OVERKILL>5) and SKINCOLOR_MIDNIGHT or (OVERKILL>4) and SKINCOLOR_RUBY or (OVERKILL>1) and SKINCOLOR_PURPLE or SKINCOLOR_SUPERGOLD2
		end
		if (OVERKILL>5)
			CM2 = SKINCOLOR_NOBLE
		end
	end
	CM,CM2,CM3 = v.getColormap(nil, CM), v.getColormap(nil, CM2), v.getColormap(nil, CM3)
	
	local MULT = max(1, min(3, OVERKILL-1))
	XSHAKE = ($==0) and -1 or $*MULT
	YSHAKE = ($==2) and 2+MULT or $*MULT
	X,Y = $+XSHAKE, $+YSHAKE --Add shake to the coordinates.
	SHOUT = max(0, OVERKILL-5)
	local HS = (BAG and BAG.HUDhit) and BAG.HUDhit*2500 or 0
	
	--First, draw the enemy sprite!
	if not (leveltime%3==0) and not xs.FreeFlight --Flicker a little!
		local SPRITE = sprnames[BAG.sprite]
		if SPRITE --Render enemy sprite...? I dunno why I'm  doing that, tbh.
			local PATCH = (v.patchExists(SPRITE+"A1")) and SPRITE+"A1" or (v.patchExists(SPRITE+"A0")) and SPRITE+"A0" or nil
			if PATCH
				local TRANS = (OVERKILL) and (OVERKILL*2/3)*FF_TRANS10 or 0
				local COLOR = (BAG.target and BAG.target.valid and BAG.target.color) and v.getColormap(nil, BAG.target.color) or nil
				v.drawScaled((115+XSHAKE)<<16, (168+YSHAKE)<<16-(HS*11), (FRACUNIT/6)+HS, v.cachePatch(PATCH), V_PERPLAYER|TRANS, COLOR)
			end
		end
	end	
	
	v.drawScaled((X-20)<<16, (Y-2)<<16, OKSCALE, TEXTPATCH, V_PERPLAYER, CM2) --The overkill/damage text.
	v.drawScaled((X-21)<<16, (Y-3)<<16, OKSCALE, TEXTPATCH, V_PERPLAYER, CM)
	if (SST) and OVERKILL
		OVERKILL = $*16
	end
	local TABLE,PATCH = GSSeperateChars(OVERKILL) --,STAGE
	if TABLE != nil
		local EX,PATCH = (HS) and X+25 or X+33, nil
		OKSCALE = $*6/5
		local OGY = Y
		Y = (HS) and $-4 or $
		for TEXT = 1,#TABLE --Use X Sonic Font for the numbers.
			if TABLE[TEXT]==nil continue end
			PATCH = "XSDSNU"+TABLE[TEXT]
			for d = 0,1
				v.drawScaled((EX-d)<<16-(HS*3),(Y-9-d)<<16-(HS*16), OKSCALE+HS,
				v.cachePatch(PATCH), V_PERPLAYER, (d!=0 and leveltime%3==0) and CM3 or (d==0) and CM2 or CM)		
			end
			EX = $+10 --Increase X coordinates for the next letter, adding "EXADD" to it.
		end
		if ASSAULT.overkill
			EX = (HS) and $+6 or $
			PATCH = v.cachePatch("XSDSSEX")
			for i = 1, min((SST) and 1 or 25, OVERKILL) --Spam exclamation marks
				for d = 0,1
					v.drawScaled( (EX+4-d)<<16, (Y-4-d)<<16-(1<<15)-(HS*11), OKSCALE+HS, PATCH, V_PERPLAYER, (d==0) and CM2 or CM)	
				end
				EX = $+((HS) and 6 or 4)
			end
		end
		Y = OGY
	end
	if xs.tutorials == "all" and ASSAULT
		local ADD = (BAG and BAG.HUDhit) and min(9, BAG.HUDhit*2)<<16 or 0
		local SCALE,TRANS = 21000,ADD
		local FLAGS = V_PERPLAYER|V_SNAPTOBOTTOM
		local CHARGE = 0
		if not (BAG and BAG.JustGotHit)
			XSHAKE,YSHAKE = 0,0 --Test, see if it makes things more readable while keeping the style.
		end		
		XSHAKE = $-((HS) and 3 or 0)
		Y = 164+YSHAKE-((HS) and 3 or 0)
		
		CM3 = (leveltime%3==0) and CM or (leveltime%2==0) and CM2 or v.getColormap(nil, SKINCOLOR_CRIMSON)
		
		if ASSAULT.type != "groundslam"
			local CM4 = v.getColormap(nil, SKINCOLOR_FOREST)
			if ASSAULT.charged and (ASSAULT.charged > 47) and xs.darkphase != 1 and not ASSAULT.cantcharge
				CHARGE = (ASSAULT.charged < 70) and 2 or 1
				CM,CM2,CM3 = v.getColormap(nil, SKINCOLOR_ICY),v.getColormap(nil, SKINCOLOR_ICY),v.getColormap(nil, SKINCOLOR_ICY)
				CM4 = CM3
			else
				if BAG and BAG.JustGotHit
					CM4 = CM3
				end
			end
			if not ASSAULT.combolock and BAG and not BAG.BAGcutcombo	
			--	local OGTRANS = TRANS
			--	local OGCM4 = CM4
				for i = 2,1,-1 --Show upper and straight buttons!
					if i != 2
						if ASSAULT.type == "flurry" continue end --Upper!
						TRANS = min(FF_TRANS90, $+((ASSAULT.jumped) and FF_TRANS80 or 0))	
						if CM4!=CM3 CM4 = v.getColormap(nil, SKINCOLOR_RUBY) end
						X = 204+((CHARGE!=2) and XSHAKE or 0)
						v.drawScaled(X<<16-HS*9, (Y<<16)-HS*11, SCALE+HS, v.cachePatch("XSBUTT1"), FLAGS|TRANS, CM4)
					else --Straight!		
						X = 100+((CHARGE!=2) and XSHAKE or 0)
						v.drawScaled(X<<16-HS*9, (Y<<16)-HS*11, SCALE+HS, v.cachePatch("XSBUTT2"), FLAGS|TRANS, CM4)
					end
				end
			
				X = 130+XSHAKE
				if CHARGE==2
					v.drawString(X, Y+7, "\x88"+"Hyper"+"\n"+"Smash!!", FLAGS, "small")
					v.drawString(X+41, Y+7, "\x88"+"Hyper"+"\n"+"Smash!!", FLAGS, "small") --Hyper Meteor!
				elseif CHARGE==1
					v.drawString(X, Y+8, "\x8a"+"Smash!", FLAGS, "small")
					v.drawString(X+41, Y+8, "\x8a"+"Smash!", FLAGS, "small") --Meteor!				
				else
					v.drawString(X, Y+9, ((ADD) and "\x80" or "\x8b")+"Straight", FLAGS|ADD, "small")	
					if ASSAULT.type == "attack"
						v.drawString(X+41, Y+9, ((ADD) and "\x80" or "\x87")+"Upper", FLAGS|TRANS, "small")
					end
				end
			end
		end
		
		FLAGS = $|V_SNAPTOLEFT
		X,Y = 1, 147-((modeattacking) and 45 or 1)
		for i = 3,4
			if i == 3
				v.drawScaled(X<<16, (Y-8)<<16, SCALE, v.cachePatch("XSBUTT3"), FLAGS, v.getColormap(nil, SKINCOLOR_GREY))			
				v.drawString(X+25, Y, "\x86"+"Cancel", FLAGS|min(ADD, 2<<16), "small")
			else
				if ASSAULT.type == "groundslam"
					if s.player and s.player.xsonictable and s.player.xsonictable.darkphase==3
						v.drawScaled((X+XSHAKE)<<16, (Y-8)<<16, SCALE+4000, v.cachePatch("XSBUTT4"), FLAGS, v.getColormap(nil, SKINCOLOR_CRIMSON))	
						v.drawString(X+30+XSHAKE, Y+3+YSHAKE, "\x8f"+"EarthShaker", FLAGS, "small")
					else
						v.drawScaled(X<<16, (Y-8)<<16, SCALE, v.cachePatch("XSBUTT4"), FLAGS, v.getColormap(nil, SKINCOLOR_RUST))	
						v.drawString(X+25, Y, "\x87"+"Punt Kick", FLAGS, "small")					
					end
				else
					v.drawScaled(X<<16, (Y-8)<<16, SCALE, v.cachePatch("XSBUTT4"), FLAGS, v.getColormap(nil, SKINCOLOR_NOBLE))	
					v.drawString(X+25, Y, "\x89"+"GroundSlam", FLAGS, "small")
				end
			end
			X = $+1
			Y = $+17
		end
	end	
end

--Custom 3 context HUD.
local function C3_Context(v, p,s,xs, C3)
	local X,Y,YOFF = 115,120,0
	local TRANS = max(10-C3.TIMER, 1)
	local ALIVE = C3.ALIVE or 0
	if (ALIVE < 8)
		TRANS = max($, 10-C3.ALIVE)
	end
	local FLAGS, CM = V_PERPLAYER|V_SNAPTOBOTTOM|(min(8, TRANS)<<16)
	local CM = v.getColormap(nil, SKINCOLOR_GOLDENROD)
	
	local BUTTON = C3.JUMP
	local BFLAGS,TRANSMASK = FLAGS, (FLAGS & FF_TRANSMASK) 
	for i = 1,3 --Buttons, progress bar, and text.
		if i == 1
			if p.powers[pw_super] or xs.darkphase
				YOFF = $+24 continue --
			end
		elseif i == 2 
			if p.powers[pw_super] or xs.darkphase --Revert!
				CM = v.getColormap(nil, SKINCOLOR_CORNFLOWER)
			end
			BUTTON = C3.SPIN 
		elseif i == 3 
			if (marathonmode) continue end --Not in Time-Attack.
			BUTTON = max(C3.CUSTOM1, C3.CUSTOM2)
		end
		local BSCALE = 27500
		if BUTTON and not (ALIVE < 8)
			if not (i==1 and (emeralds!=127 or (p.rings<100)))
			and not (i==2 and (XS_FAKEEMERALDS!=127 or (p.rings<50)) and not xs.darkphase and not p.powers[pw_super])
				BFLAGS = (FLAGS & ~FF_TRANSMASK)|V_PERPLAYER
				
				local BTO = (i==3) and BUTTON*4 or BUTTON*8
				if i==2 and xs.supersonic --debug test
					BTO,BUTTON = $/2,$/2
				end
				v.drawFill(X, Y+YOFF, 2+BTO, 15, 100|FLAGS) --Underside.
				v.drawFill(X, Y+YOFF+6, 2+BTO, 9, 101|FLAGS)
				v.drawFill(X, Y+YOFF+10, 2+BTO, 5, 102|FLAGS)
				
				v.drawFill(X, Y+YOFF, 2+BTO, 2, 96|FLAGS) --Above?
				v.drawFill(X, Y+YOFF+13, 2+BTO, 2, 107|FLAGS)
				v.drawFill(X, Y+YOFF, 2, 13, 96|FLAGS)
				if (BUTTON < 12)
					v.drawScaled(((X-2)+BTO)<<16, (Y+YOFF+29-C3.RNG)<<16, 40055, v.cachePatch("SSPKA0"), (FLAGS+FF_TRANS10)|V_ADD|V_PERPLAYER)
					v.drawScaled(((X+2)+BTO)<<16, (Y+YOFF+20+C3.RNG)<<16, 55355, v.cachePatch("SSPKA0"), (FLAGS+FF_TRANS10)|V_ADD|V_PERPLAYER)
				end
			end
		else
			BSCALE = 24000
			BFLAGS = (FLAGS & ~FF_TRANSMASK)|(max(TRANSMASK, FF_TRANS10))|V_PERPLAYER
		end

		--Bar setup done, now buttons & text!
		if i == 1 --Super
			local TEMPTRANS = (BFLAGS & FF_TRANSMASK)
			local SUPTRANS = max(TEMPTRANS, 1<<16)
			if (p.rings<100) or emeralds != 127
				v.drawScaled( (X-22)<<16, (Y-6+YOFF)<<16, BSCALE, v.cachePatch("XSBUTT1"), 
				(BFLAGS&~FF_TRANSMASK) | (max(TEMPTRANS, FF_TRANS50)) , v.getColormap(nil, SKINCOLOR_FLAME))
				
				v.drawScaled((X+12)<<16,(Y-4+YOFF)<<16, BSCALE/3, v.cachePatch("XSCMSUP2"), 
				(BFLAGS & ~FF_TRANSMASK) | max(TEMPTRANS, FF_TRANS60), v.getColormap(nil, SKINCOLOR_ORANGE))				
				SUPTRANS = max($, 8<<16)
			else
				v.drawScaled((X-22)<<16,(Y-6+YOFF)<<16,BSCALE, v.cachePatch("XSBUTT1"), BFLAGS, v.getColormap(nil, SKINCOLOR_FLAME)) --Button!
				v.drawScaled((X+12)<<16,(Y-2+YOFF)<<16,BSCALE/3,v.cachePatch("XSCMSUP2"),(BFLAGS&~FF_TRANSMASK)|max(TEMPTRANS, FF_TRANS10),v.getColormap(nil, SKINCOLOR_VOLCANIC))--Shadow!
				if s
					local PATCH = (leveltime%2) and "NSPKA0" or "NSPKB0"
					local RNGX = XSLocalRNG(8, 84, s) --Imperfect RNG works in our favor this time
					local RNGY = XSLocalRNG(9, 20, s)
					v.drawScaled((X+RNGX)<<16, (Y+RNGY+YOFF)<<16, 1<<16, v.cachePatch(PATCH), SUPFLAGS)
				end
			end
			local SUPFLAGS = (BFLAGS & ~FF_TRANSMASK) | SUPTRANS | V_ADD | V_PERPLAYER
			v.drawScaled((X+11)<<16, (Y-2+YOFF)<<16, (BSCALE/3)*2, v.cachePatch("XSCMSUPE"), SUPFLAGS)
			
			--Ring Requirement.
			if emeralds == 127
				v.drawScaled((X+101)<<16, (Y+12+YOFF)<<16, 20000, v.cachePatch("RINGA0"), SUPFLAGS)
				v.drawString(X+109, Y+YOFF+4, "x 100", SUPFLAGS, "thin")	
			end
				
		elseif i == 2 --DSS or REVERT.	
			local TEMPTRANS = (BFLAGS & FF_TRANSMASK)
			local DX = (X-5)<<16
			local DY = (Y-9+YOFF)<<16	
			if xs.darkphase or p.powers[pw_super] --Simple revert option!
				v.drawScaled((X-22)<<16, (Y-8+YOFF)<<16, BSCALE, v.cachePatch("XSBUTT2"), BFLAGS, v.getColormap(nil, SKINCOLOR_EMERALD))
				for f = 1,0,-1 --Revert!
					local DCM = v.getColormap(nil, (f==1) and SKINCOLOR_PUREBLACK or SKINCOLOR_SUPERSKY2)
					v.drawScaled(DX+((f+13)<<16), DY+((f-2)<<16), BSCALE/3, v.cachePatch("XSCMBASE"), BFLAGS, DCM)
				end
			else
				local XSHAKE,YSHAKE = 0,0
				local DSTR = v.cachePatch("XSCMDSS2")
				local DCM = v.getColormap(nil, SKINCOLOR_PUREBLACK)
				local SHADFLAGS = (BFLAGS & ~FF_TRANSMASK) | (max(TEMPTRANS, 3<<16))
				
				if XS_FAKEEMERALDS==127 and (p.rings>=50) --You sure about this?
					v.drawScaled((X-22)<<16, (Y-6+YOFF)<<16, BSCALE, v.cachePatch("XSBUTT2"), BFLAGS, v.getColormap(nil, SKINCOLOR_FOREST))
					if (leveltime%16==0)
						DCM = v.getColormap(nil, SKINCOLOR_CRIMSON)
						XSHAKE = (leveltime%32==0) and 1 or -1
						YSHAKE = (leveltime%64==0) and 2 or (leveltime%48==0) and 1 or (leveltime%32==0) and -1 or -2
						XSHAKE,YSHAKE = $<<16, $<<16				
						if v.renderer() != "software" 
							local SCALEX = (BSCALE/3)+abs(XSHAKE*48500)
							local SCALEY = max(4000, (BSCALE/3)+YSHAKE*6900) 						
							v.drawStretched(DX-(XSHAKE*5), DY-(YSHAKE*2),SCALEX, SCALEY, v.cachePatch("XSCMDSS2"), (SHADFLAGS&~FF_TRANSMASK)|FF_TRANS30, DCM)
						else --Software once again sucks.
							v.drawScaled(DX-(XSHAKE*5)-(6<<16), DY-(5<<16)-(YSHAKE*2), (BSCALE*2)/5, DSTR, (SHADFLAGS&~FF_TRANSMASK)|FF_TRANS30, DCM)
						end
					end					
					local CFLAGS = (BFLAGS & ~FF_TRANSMASK) | (max(TEMPTRANS, 6<<16)) //| ((not XSHAKE) and V_ADD or 0)
					v.drawScaled(DX+(XSHAKE*2), DY+YSHAKE, BSCALE/3, DSTR, SHADFLAGS, DCM) --Distortion text, now underneath
					v.drawScaled(DX, DY, BSCALE/3, v.cachePatch("XSCMDSS1"), BFLAGS, DCM) --Text
					v.drawScaled(DX, DY, (BSCALE/3)*2, v.cachePatch("XSCMDSS3"), CFLAGS) --Mist overlay

				else --You aren't ready for this
				
					v.drawScaled((X-22)<<16, (Y-6+YOFF)<<16, BSCALE, v.cachePatch("XSBUTT2"), (BFLAGS&~FF_TRANSMASK)|(max(TEMPTRANS, FF_TRANS50)), v.getColormap(nil, SKINCOLOR_FOREST)) 
					local DFLAGS = BFLAGS
					if XS_FAKEEMERALDS != 127 --Completely obscured.
						DFLAGS = (BFLAGS & ~FF_TRANSMASK)| (max(FF_TRANS80, TEMPTRANS))
						SHADFLAGS = ($ & ~FF_TRANSMASK)| (max(FF_TRANS60, TEMPTRANS))
					elseif (p.rings < 50) --Slightly obscured.
						DFLAGS = (BFLAGS & ~FF_TRANSMASK)| (max(FF_TRANS50, TEMPTRANS))
						SHADFLAGS = ($ & ~FF_TRANSMASK)| (max(FF_TRANS40, TEMPTRANS))
					end
					local CFLAGS = (BFLAGS & ~FF_TRANSMASK) | (max(TEMPTRANS, 4<<16))| V_ADD
					v.drawScaled(DX-XSHAKE, DY-YSHAKE, BSCALE/3, v.cachePatch("XSCMDSS1"), DFLAGS, DCM) --Text
					if (leveltime%32==0)
						DCM = v.getColormap(nil, SKINCOLOR_MIDNIGHT)
						XSHAKE = (leveltime%64==0) and 1 or -1
						YSHAKE = (leveltime%128==0) and 2 or (leveltime%96==0) and 1 or (leveltime%64==0) and -1 or -2
						XSHAKE,YSHAKE = $<<16, $<<16
						if v.renderer() != "software" 
							local SCALEX = (BSCALE/3)+abs(XSHAKE*11500)
							local SCALEY = max(5000, (BSCALE/3)+YSHAKE*4000) 					
							v.drawStretched(DX-(XSHAKE*4), DY-YSHAKE, SCALEX, SCALEY, DSTR, (SHADFLAGS&~FF_TRANSMASK)|FF_TRANS30, DCM)	
						else --Software once again sucks, part 2.
							v.drawScaled(DX-(XSHAKE*4)-(4<<16), DY-YSHAKE-(2<<16), (BSCALE*4)/11, DSTR, (SHADFLAGS&~FF_TRANSMASK)|FF_TRANS40, DCM)
						end																	
					end										
					v.drawScaled(DX+XSHAKE, DY+YSHAKE, BSCALE/3, v.cachePatch("XSCMDSS2"), SHADFLAGS, DCM) --Distortion text
					v.drawScaled(DX, DY, (BSCALE/3)*2, v.cachePatch("XSCMDSS3"), CFLAGS) --Mist overlay
				end
				if XS_FAKEEMERALDS == 127 --Ring Requirements.
					local RFLAGS = (BFLAGS&~FF_TRANSMASK)
					if (p.rings < 50)
						RFLAGS = $|(max(TEMPTRANS, FF_TRANS80))
					else
						RFLAGS = $|V_ADD|(max(TEMPTRANS, FF_TRANS10))
					end				
					v.drawScaled((X+101)<<16, (Y+12+YOFF)<<16, 20000, v.cachePatch("RINGA0"), RFLAGS)
					v.drawString(X+109, Y+YOFF+4, "x"+" 50", RFLAGS, "thin")		
				end
			end
		elseif i == 3 --Menu
			v.drawScaled((X+12)<<16, (Y+4+YOFF)<<16, BSCALE*3/4, v.cachePatch("XSNAMTAG"), BFLAGS)
			for f = 1,0,-1
				v.drawScaled((X+51+f)<<16, (Y+2+f+YOFF)<<16, BSCALE/3, v.cachePatch("XSCMMEN"), BFLAGS, 
				(f==1) and v.getColormap(nil, SKINCOLOR_PUREBLACK) or v.getColormap(nil, SKINCOLOR_SUPERSKY5))
			end
			CM = v.getColormap(nil, SKINCOLOR_GOLDENROD)
			v.drawScaled((X-32)<<16, (Y-9+YOFF)<<16, BSCALE-2000, v.cachePatch("XSBUTT4"), BFLAGS, CM) --Extra button for menu.(C2)
			v.drawScaled((X-22)<<16, (Y-6+YOFF)<<16, BSCALE, v.cachePatch("XSBUTT3"), BFLAGS, CM)
		end			
		YOFF = $+24
	end
end

--Render portraits for Super Sonic PvP!
local function PvPPortrait(v, p,s,xs, RB, OPPONENT)
	local X,Y = 1,154
	local X2,Y2 = 0,0 --Shake coordinates. Only apply to portrait, not background or spike.
	local VFLAGS = V_SNAPTOBOTTOM|V_SNAPTOLEFT
	local COLOR = SKINCOLOR_ICY --Background color
	local SPIKECOLOR = SKINCOLOR_PUREBLACK 
	local SCALE = FRACUNIT/8
	local HEALTH = (RB.PvPHEALTH!=nil) and RB.PvPHEALTH or 10000 --10000 is full PvP health.
	local TRANS,FLIP,SPIKEADD,LAMBENTFLASH,PAINFLASH = 0,0,0,0,0
	
	if OPPONENT != nil --This is the opponent, so change some flags!
		X,Y = 319,5
		FLIP = V_FLIP
		VFLAGS = V_SNAPTORIGHT|V_SNAPTOTOP
	end
	if (HEALTH < 9500) --Change background color depending on player health.
		local CTABLE = {SKINCOLOR_VOLCANIC, SKINCOLOR_CRIMSON, SKINCOLOR_PEPPER, SKINCOLOR_ORANGE, SKINCOLOR_APRICOT,
		SKINCOLOR_GOLDENROD, SKINCOLOR_LEMON, SKINCOLOR_AQUA, SKINCOLOR_SKY, SKINCOLOR_ICY}
		COLOR = CTABLE[min(#CTABLE, (HEALTH/1000)+1)] --Change background color depending on player health.
	end
	
	local FACE = "XSPVPF1"
	if xs.ChaosLambent
		local INIT = xs.ChaosLambent.init or 0
		FACE = "XSPVPF3"
		if xs.ChaosLambent.firing
			X2 = (leveltime%3==0) and -1 or (leveltime%2==0) and 1 or 0
			Y2 = (leveltime%2) and 1 or -1
			COLOR = (leveltime%2) and SKINCOLOR_SUPERSILVER1 or SKINCOLOR_GOLD
			SPIKECOLOR = (leveltime%2) and SKINCOLOR_GOLDENROD or SKINCOLOR_FLAME
			SPIKEADD = V_ADD|FF_TRANS10
		elseif (INIT < 14)
		
			local CTABLE = {SKINCOLOR_BLACK, SKINCOLOR_VOLCANIC, SKINCOLOR_CRIMSON, SKINCOLOR_RED, SKINCOLOR_COPPER,SKINCOLOR_ORANGE, 
			SKINCOLOR_APRICOT, SKINCOLOR_TOPAZ, SKINCOLOR_GOLDENROD, SKINCOLOR_SUPERGOLD3, SKINCOLOR_SUPERGOLD2, SKINCOLOR_SUPERGOLD1, SKINCOLOR_SUPERSILVER1}
			
			SPIKECOLOR = CTABLE[INIT]
			LAMBENTFLASH = max(1, min(9,INIT-3))<<16
			if (INIT > 4)
				COLOR = SPIKECOLOR
				SPIKEADD = V_ADD|FF_TRANS10
			end
		end
		if xs.ChaosLambent.struggle and xs.FreeFlight
			local t = xs.FreeFlight.LockOn
			local tp = t and t.player
			local xss = tp and tp.xsonictable
			local CLL = xss and xss.ChaosLambent
			if CLL
				BeamClashHUD(v, p,s,xs,xs.ChaosLambent, tp,t,xss,CLL)
			end
		end
	else
		if p.powers[pw_flashing] and (s.flags2 & MF2_DONTDRAW)
			return --
		end
		if RB.painstun
			FACE = "XSPVPF2"
			if RB.painflash and (RB.painstun > 2) and RB.painstun != FRACUNIT
				PAINFLASH = max(1, (19-RB.painflash)/2)<<16
				SPIKECOLOR = (RB.painflash%2) and SKINCOLOR_VOLCANIC or SKINCOLOR_MIDNIGHT
				SPIKEADD = 0
				if (RB.painflash > 9)
					X2 = (leveltime%3==0) and -1 or (leveltime%2==0) and 1 or 0 
					Y2 = (RB.painflash%2) and -1 or 1
				/*	if (leveltime%2)
						COLOR = (HEALTH<2500) and SKINCOLOR_BLACK or (HEALTH<5000) and SKINCOLOR_MIDNIGHT or SKINCOLOR_RED
					else
						COLOR = (HEALTH<2500) and SKINCOLOR_RED or (HEALTH<5000) and SKINCOLOR_FLAME or SKINCOLOR_BLACK
					end */ 				
				end
			end
		else
			if RB.DodgeFrames and ((RB.DodgeFrames > 2) or xs.ChaosSnap) --Show when the opponent is intangible.
				if (RB.DodgeFrames > 9)
					TRANS = (RB.DodgeFrames>13) and FF_TRANS40 or (RB.DodgeFrames>11) and FF_TRANS60 or FF_TRANS80
				else
					TRANS = RB.DodgeFrames<<16 --Make portrait transparent when the player has i-frames.
				end
				if (RB.DodgeFrames > 2)
					COLOR = SKINCOLOR_AETHER
				end
				SPIKECOLOR = SKINCOLOR_SLATE
			end
		end	
	end
	
	--Colored background
	v.drawScaled(X<<16, Y<<16, SCALE, v.cachePatch("XSPVPBG1"), VFLAGS|FLIP|TRANS, v.getColormap(nil, COLOR))
	
	--Super Sonic Portrait
	if (SPIKECOLOR == SKINCOLOR_PUREBLACK) or (FACE != "XSPVPF1")
		v.drawScaled((X+X2)<<16, (Y+Y2)<<16, SCALE, v.cachePatch(FACE), VFLAGS|FLIP|TRANS)
	else
		v.drawScaled(X<<16, Y<<16, SCALE, v.cachePatch(FACE), VFLAGS|FLIP|FF_TRANS80) --Disappearing act.
		v.drawScaled(X<<16, Y<<16, SCALE, v.cachePatch("XSPVPF1O"), VFLAGS|FLIP|min(FF_TRANS90, TRANS+FF_TRANS20), v.getColormap(nil, COLOR))
	end
	if LAMBENTFLASH
		v.drawScaled((X+X2)<<16, (Y+Y2)<<16, SCALE, v.cachePatch("XSPVPF3O"), VFLAGS|FLIP|LAMBENTFLASH, v.getColormap(nil, SKINCOLOR_SUPERGOLD3))
	elseif PAINFLASH
		v.drawScaled((X+X2)<<16, (Y+Y2)<<16, SCALE, v.cachePatch("XSPVPF2O"), VFLAGS|FLIP|PAINFLASH, v.getColormap(nil, SKINCOLOR_RED))
	end
	
	--Portrait aura
	if RB.auratrans != nil	
		local MIN = (FACE=="XSPVPF1") and 3<<16 or 0
		local AURATRANS = max(MIN, ((RB.auratrans/3)+1)<<16)+TRANS
		v.drawScaled((X+X2)<<16, (Y+Y2)<<16, SCALE, v.cachePatch(FACE+"B"), VFLAGS|FLIP|V_ADD|(min(FF_TRANS90, AURATRANS)) )
	end
	
	--Spiky Sonic X-style thing
	v.drawScaled(X<<16, Y<<16, SCALE, v.cachePatch("XSPVPBG2"), VFLAGS|FLIP|TRANS|SPIKEADD, v.getColormap(nil, SPIKECOLOR))	
	if p.XS_chargehaxx or RB.cancharge
		local PATCH = v.cachePatch(
		( "XSG1" + R_Frame2Char( O+XSon.TimeFrame(2) ) + "0" ) --Goes from I to Q. We only use O to Q
		)
		local AX = X+X2 + ((OPPONENT) and -49 or 56) 
		local AY = Y+Y2 + ((OPPONENT) and 7 or 26)
		for i = 2,4,2
			v.drawScaled(AX<<16, AY<<16, SCALE/i, PATCH, V_ADD|VFLAGS|FLIP|min(FF_TRANS90, TRANS+2<<16) )
		end
	end
end

--Autocorrects X offsets since I develop HUD primarily for 1080p
local function hudX(v, FRAC)
	local WIDTH,HEIGHT = v.width(),v.height()
	if HEIGHT == 1080 or WIDTH == 1152 return 0 end --Standard 1080p or that random render that conveniently works.
	if FRAC == nil FRAC = 1 end

	if WIDTH == 1024
		return 18*FRAC --440x900	
	elseif WIDTH == 1440
		return 9*FRAC --1440x900
	elseif WIDTH == 1680
		return 23*FRAC --1680x1050		
	else
		if WIDTH == 800
			return -6*FRAC --800x600
		elseif WIDTH == 1600 and HEIGHT == 900 
			return -12*FRAC --1600x900	
		elseif WIDTH == 1366
			return -34*FRAC --1366x768
		elseif HEIGHT == 720
			return -20*FRAC --1280x720
		end
		return 29*FRAC --All other resolutions
	end
end

--The menu card and background, all for decoration!
--XSHUDM0 is the base card. XSHUDMB is the back of the card.
--XSHUDM1-4 are the card images for the front, XSHUDF is a colorable frame overlay for glowing effects.
--All card graphics are 501 pixels wide, 703 pixels tall.
--Scaled down to 20%, they'll render out as 100x wide and 140x tall.
local function ShowCard(v, p,s,xs, MENU)

	--Card coordinates and scale.
	local X,Y = 60<<16, 32<<16  --Frame X and Y coordinates.
	local FRAMESCALE = 13071 --It's FRACUNIT/5, exactly 20%.
	
	local IX,IY = X,Y --Image X and Y coordinates.
	local XSCALE,YSCALE = FRAMESCALE,FRAMESCALE --For the image itself!
	
	--Decide which side of the card, and which image to show!
	local CARD,TRANSC,TRANSI = "front",0,0 --Translucency values
	local BOOT,EXIT = MENU.BOOT,MENU.EXIT
	
	if BOOT or EXIT
		local XADD, YADD = (-MENU.EXIT*12)<<16, MENU.EXIT*2<<16
		if (BOOT > 7)
			YADD = $+((BOOT-7)*10<<16)
		end
		X,Y = $+XADD, $+YADD
		
		if BOOT --The menu open animation is playing! The value tics down from 25 to 0.	
			IX,IY = X+(18<<16), Y+(8<<16) --Might as well update them here.
			TRANSC = min(FF_TRANS90, (BOOT*2/3)<<16) --Fade in just a little as well.	
		end
		if EXIT
			TRANSC = min(FF_TRANS90, $+EXIT*FF_TRANS20)	
		end
	end
		
	--Display the card's frame!
	v.drawScaled(X,Y, FRAMESCALE, v.cachePatch("XSHUDM0"), V_SNAPTOLEFT|TRANSC)
	
	if MENU.IMAGEFADE --What's our inner image's translucency?
		TRANSI = (MENU.IMAGEFADE*3/2)<<16
	end
	
	--The outter frame flashes when you change an option.
	if MENU.FLASH
		local GTRANS = (MENU.FLASH>5) and 13-MENU.FLASH or 10-MENU.FLASH
		GTRANS = max(0, min(9, $))<<16
		v.drawScaled(X,Y, FRAMESCALE, v.cachePatch("XSHUDMF"), V_ADD|V_SNAPTOLEFT|GTRANS, v.getColormap(nil, SKINCOLOR_SUPERSILVER1))
	end	
	
	--Display the inner image? Not if translucency is over 90%, ofc.
	if (TRANSI <= FF_TRANS90) --and (BOOT<9)
		if EXIT
			TRANSI = max(FF_TRANS50,$)
			IX,IY = X,Y
			XSCALE,YSCALE = FRAMESCALE,FRAMESCALE	
		elseif MENU.SPINNING != nil --The image inside the card is spinning! Loops from 8 to -7 until it stops at 5.
			local SPIN = abs(MENU.SPINNING)
			
			XSCALE = (SPIN==5) and FRAMESCALE
			or (SPIN==4 or SPIN==6) and FRAMESCALE*2/3
			or (SPIN==3 or SPIN==7) and FRAMESCALE/2
			or (SPIN==2 or MENU.SPINNING==8) and FRAMESCALE/3
			or FRAMESCALE/7	
			
			IX = (SPIN==4 or SPIN==6) and 16<<16
			or (SPIN==3 or SPIN==7) and 25<<16
			or (SPIN==2 or SPIN==8) and 33<<16
			or 44<<16
			if abs(MENU.SPINNING) == 5
				IX = 0 --For some reason I had to specificy this HERE.
			end
			
			if BOOT --Scale into the picture on boot!
				XSCALE = max(1, $-BOOT*975)
				YSCALE = max(1, $-BOOT*1135)
				IX = $+(4*BOOT<<16)
				IY = $+(2*BOOT<<16)
			end
			IX = $+X 
			
			CARD = (MENU.SPINNING<0) and "back" or "front" --Show front or back of the card?
		end	
		local IFLAGS = ((TRANSI>FF_TRANS10) and (CARD != "back")) and V_SNAPTOLEFT|V_ADD or V_SNAPTOLEFT
		local IMAGE = v.cachePatch( ((CARD=="back") and "XSHUDMB" or MENU.IMAGE) ) --Show the back or front?
		v.drawStretched(IX, IY, XSCALE, YSCALE, IMAGE, IFLAGS|TRANSI)
	end	
	
	--Lastly, a glowing overlay for the outter frame!
/*	if not MENU.BOOT
		local GTRANS = (max(6, MENU.OVERLAY)<<16)+TRANSI 
		if (GTRANS <= FF_TRANS90) --MENU.OVERLAY goes from 14 to 3, then 3 to 14 again in a loop.
			for i = 1,3
				v.drawScaled(X,Y, FRAMESCALE, v.cachePatch("XSHUDMF"), V_ADD|V_SNAPTOLEFT|(GTRANS<<16), v.getColormap(nil, SKINCOLOR_SUPERSILVER1))
				SCALE = $+2200
				X,Y = $-65000, $+65000
				GTRANS = min(FF_TRANS90, $+FF_TRANS20)
			end
		end
	end */	
	
	--Sonic image popin when you select colors!
	local POPIN = MENU.POPIN
	if POPIN != nil 
	
	--	v.drawString(222, 14, "\x84 POPIN: "+(-POPIN)+" xs.SPcolor:"+xs.SPcolor, FF_TRANS20, "thin-center")
	
		local FLIP = 24<<16
		local STRETCH = MENU.POPINSTRETCH or 0
		local PXSCALE = 25000
		local PYSCALE = PXSCALE-(STRETCH*1600)
		local PFLAGS = 0
		local VFLIP = 0
		
		local type = "XSHUDP4"
		if xs.darkphase or p.powers[pw_super]
			if xs.darkphase
				type = "XSHUDP2" --The only one using only a single graphic
			else
				if (POPIN < 0)
					type = "XSHUDP3"
					PFLAGS = $|V_FLIP
					VFLIP = V_FLIP
					X = $+66<<16
				else
					type = "XSHUDP1"
				end
				FLIP = -$
			end
		elseif POPIN != 0 or MENU.SELECT != 2
			type = "XSHUDP3" --Running pose. Uses 4 when he comes to a stop normally.
		end

		--Adjust coordinates!
		X = $+ hudX(v,1<<16) - 24<<16
		Y = $+ (40+(STRETCH*5))<<16
		if (type=="XSHUDP1") --slide pose
			PXSCALE,PYSCALE = $-4500,$-4500
			X = $- (9+(POPIN*FLIP*6))<<16
			Y = $- 10<<16
		elseif (type=="XSHUDP2") --Jump Pose
			X = $- 6<<16
			Y = $- 11<<16
		end
		if (POPIN != 0) --Change stretch and mobile coordinates!
			if (POPIN*FLIP < 0) --Going to the right!
				X = $-(POPIN*FLIP*2)
				PFLAGS = $+min(FF_TRANS70, (abs(POPIN/2)<<16))
			else --Coming from the left!
				X = $-POPIN*FLIP		
			end
			if (type=="XSHUDP2") --darkphase jump pose
				PXSCALE = $-(MENU.POPIN*1250)
				PYSCALE = $-(MENU.POPIN*1250)
				Y = $+(POPIN*11)<<16
			else
				PXSCALE = $+abs(MENU.POPIN*900)
				PYSCALE = $-abs(MENU.POPIN*600)			
			end
		end
		local CM = v.getColormap(nil, xs.SPcolor or 4)
		local PATCH = v.cachePatch(type)
		v.drawStretched(X, Y, PXSCALE, PYSCALE, PATCH, PFLAGS, CM) --Now draw Sonic!
		
		--Add blur after effects?
		if not MENU.EXIT and POPIN!=0
			if type=="XSHUDP1"
				v.drawStretched(X, Y, PXSCALE+2500, PYSCALE,  v.cachePatch("XSHUDP0"), VFLIP|V_ADD|FF_TRANS20, CM)
			elseif (POPIN < 0)
				if VFLIP != 0
					for i = -1,-4,-1
						v.drawStretched(X+((POPIN*-9*i)+64)<<16, Y, PXSCALE-(1200*i), PYSCALE+(750*i), PATCH, VFLIP|V_ADD|((10+i*2)<<16), CM)
					end
				else
					for i = 1,2
						v.drawStretched(X+(POPIN*7*i)<<16, Y, PXSCALE+(1500*i), PYSCALE-(500*i), PATCH, V_ADD|((5+i*2)<<16), CM)
					end					
				end
			end
		end
	end
end

--Display the X Sonic Menu!
local function XSonicMenu(v, p,s,xs, MENU)

	ShowCard(v, p,s,xs, MENU) --Display the menu card and other such fluff.
	
	local BOOT = MENU.BOOT
	local FADE = MENU.FADE and min(FF_TRANS90, (MENU.FADE/3)<<16) or 0 --If the menu's currently fading in, add translucency.
	
	local SELECT = MENU.SELECT or 0 --Which options did we select?
	local rX = hudX(v)*2
	local X,Y = 210+rX,40-(BOOT*2)+(MENU.EXIT*5) --Where to place the text.
	
	local YSPACING = 15 --How far to space out vertical lines.
	
	--Text for the menu selections.
	local TEXT = {
	[1] = "Custom HUD",
	[2] = "SP Color",
	[3] = "Sonic Voice", --"Disable BlueBlur"
	[4] = "Tutorials",
	[5] = "Ringburst SFX",
	[6] = "Coop PvP",
	[7] = "Accessibility"
	}
	if MENU.TIMEATTACK==true
		TEXT[(#TEXT+1)] = "Super TimeAttack"
	end
	if MENU.ADMIN==true
		TEXT[(#TEXT+1)] = "Stupid Memoryleak Fix"
		if ((#TEXT)>8)
			Y = $-5
		end
	end	
	if TEXT[SELECT] --Highlight the selected text as yellow.
		TEXT[SELECT] = "\x82"+$
	end
	
	--What each option is currently set to.
	local OPTIONS = {
	[1] = (xs.HUDpref==true) and "\x83"+"On" or "\x85"+"Off",
	[2] = (tonumber(xs.SPcolor)) and "\x80"+R_GetNameByColor(xs.SPcolor)+" ("+xs.SPcolor+")" or "\x86"+"Auto",
	[3] = (xs.voice) and "\x83"+"On" or "\x85"+"Off",
	[4] = (xs.tutorials=="all") and "\x83"+"all" or (xs.tutorials=="mild") and "\x8b"+"aimline-only" or "\x85"+"none",
	[5] = (xs.FourKidsRB) and "\x87"+"4Kids" or "\x8c"+"Original",
	[6] = (xs.coopPvP) and "\x83"+"On" or "\x85"+"Off",
	[7] = (xs.ACCE) and "\x81"+xs.ACCE or "\x83"+"Normal"
	}
	if MENU.TIMEATTACK==true
		OPTIONS[(#OPTIONS+1)] = 
		(xs.SuperTimeAttack=="AllEmeralds") and "\x88"+"Emeralds"
		or (xs.SuperTimeAttack=="Super") and "\x82"+"Super"
		or not (xs.SuperTimeAttack) and "\x85"+"Off"
		or "\x89"+xs.SuperTimeAttack
	end
	if MENU.ADMIN==true
		OPTIONS[(#OPTIONS+1)] = (XS_CRAPPYPRIORITIES==1) and "\x81"+"On" or "\x83"+"Off"
	end	
	
	--Now do all the rendering nonsense.
	for i = 1, (MENU.MAXNUM) do
		if type(TEXT[i]) != "string"
			Y = $+YSPACING continue --Don't show this option!
		end
		local OPTION = OPTIONS[i] or "???"
		local LENGTH = string.len(OPTION)+string.len(TEXT[i])
		if i==SELECT
			v.drawString(X+LENGTH-MENU.SHIFT, Y, TEXT[i]+"\x80 : "+OPTION, FADE|V_SNAPTORIGHT, "thin-right")
		else
			v.drawString(X+LENGTH, Y, TEXT[i]+"\x80 : "+OPTION, min(FF_TRANS90, FADE+FF_TRANS20)|V_SNAPTORIGHT, "thin-right")
		end
		Y = $+YSPACING
	end
	
	--Also, add a description for the option you're currently hovering on.	
	local DESCRIPTION = {
	[1] = "Use X Sonic's custom HUD or the vanilla SRB2 behaviour?",
	[2] = "Set up X Sonic's custom color for Single Player!",
	[3] = "You won't hear X Sonic voices from anyone if this is disabled.",
	[4] = "Show button prompts, just the zipdash aimline, or nothing?",
	[5] = "Prefer any RingBurst SFX?",
	[6] = "Allow fellow coop players to engage in PvP combat with you?",
	[7] = "Halt bothersome visuals."
	}
	if MENU.TIMEATTACK==true
		DESCRIPTION[(#DESCRIPTION+1)] = "Start transformed or with emeralds in Time Attack/Marathon."	
	end
	if MENU.ADMIN==true
		DESCRIPTION[(#DESCRIPTION+1)] = "Disable most rollangling to stop memory leaks eventually crashing SRB2"
	end
	
	--Formatting descriptions is a bit of a bitch.
	local TEXT1,TEXT2 = DESCRIPTION[SELECT], nil
	if type(TEXT1) == "string"
		local CHARS1,CHARS2 = string.len(TEXT1), 0
		if (CHARS1 > 25) --Automatically cut our description into 2 lines without using \n if it is long enough.
			local CUT = (string.find(TEXT1, " ", 24)) or 26 --Cut text only at the first space we find
			TEXT2 = string.sub(TEXT1, CUT)
			TEXT1 = string.sub(TEXT1, 0, CUT)
			CHARS2 = string.len(TEXT2)
			CHARS1 = string.len(TEXT1)
			--Now for some extra minor notes.
			if DESCRIPTION[SELECT] == DESCRIPTION[2]
				if (netgame==true) or (splitscreen==true)
					local DY = (MENU.ADMIN==true) and 159 or 183
					local EFLAGS = V_ALLOWLOWERCASE|V_SNAPTORIGHT|V_SNAPTOBOTTOM|FADE
					v.drawString(116+rX, DY+BOOT, "\x82"+"NOTE:\x80 Color swaps from other methods", EFLAGS, "small")
					v.drawString(116+rX, DY+6+BOOT, "in multiplayer override this option!", EFLAGS, "small")
				end		
			elseif DESCRIPTION[SELECT] == DESCRIPTION[8] and (MENU.TIMEATTACK==true)
				local DY = (MENU.ADMIN==true) and 159 or 183
				local EFLAGS = V_ALLOWLOWERCASE|V_SNAPTORIGHT|V_SNAPTOBOTTOM|FADE
				v.drawString(116+rX, DY+BOOT, "\x82"+"NOTE:\x85 Ghosts may cause errors emulating", EFLAGS, "small")
				v.drawString(116+rX, DY+6+BOOT, "\x85"+"'transformed' runs! Set them to\x86 "+"'hide'", EFLAGS, "small")
			elseif DESCRIPTION[SELECT] == DESCRIPTION[((MENU.TIMEATTACK==true) and 9 or 8)]
				local DY = (MENU.ADMIN==true) and 161 or 185
				local EFLAGS = V_ALLOWLOWERCASE|V_SNAPTORIGHT|V_SNAPTOBOTTOM|FADE
				v.drawString(116+rX, DY+BOOT, "\x82"+"NOTE:\x80 Option is temporary and won't save!", EFLAGS, "small")
			end	
		end
		local XOFFSET,YOFFSET = min(24, CHARS1*3/2), (BOOT*3)-MENU.SHIFT
		local CM = (MENU.SHIFT) and "\x8a" or "\x84"
		v.drawString(214+XOFFSET+rX, Y+YOFFSET+10, CM+TEXT1, V_ALLOWLOWERCASE|FADE|V_SNAPTORIGHT, "thin-right")
		if TEXT2
			v.drawString(214+CHARS2+rX, Y+YOFFSET+20, CM+TEXT2, V_ALLOWLOWERCASE|FADE|V_SNAPTORIGHT, "thin-right")
		end
	end
	
	--Yes, you may exit the menu.
	v.drawString(7, 191+BOOT, "\x85"+"SPIN:\x80 Save & Exit", V_ALLOWLOWERCASE|V_SNAPTOBOTTOM|V_SNAPTOLEFT|FADE, "small")
end

--Very important
local function NBlueBlur(NAME)
NAME = tostring(NAME) if NAME and (string.lower(NAME) == "blue blur" or string.lower(NAME) == "blueblur") return true end return false --
end

--Menu Thinker called from a PreThinkFrame.
XSon.MenuThinker = function(p,s,xs, MENU)
	if not (MENU) or (splitscreen and p == players[1]) --First, cancel conditions!
		p.XSonicMenu = nil return false --
	elseif P_PlayerInPain(p) or (p.pflags & (PF_SLIDING|PF_SPINNING)) or (s.eflags & MFE_SPRUNG)
	or not s.health or (s.state == S_PLAY_S_XTRANSFORM) or p.powers[pw_carry]
		XSon.SaveSettings(p,s,xs, MENU, true) return false --
	elseif (MENU.EXIT > 4)--Manual cancel!
		XSon.SaveSettings(p,s,xs, MENU) return false --	
	end
	
	MENU.JUMP = (p.cmd.buttons & BT_JUMP) and $+1 or 0 --Button inputs.
	MENU.SPIN = (p.cmd.buttons & BT_SPIN) and $+1 or 0	
	MENU.CUSTOM1 = (p.cmd.buttons & BT_CUSTOM1) and $+1 or 0
		
--	local ADMIN = (IsPlayerAdmin(p) or p==server) and true or false
--	for i = 1,MENU.MAXNUM --Which options are locked?
--		MENU.LOCKED[i] = false
--	end
	
	--Timers.
	MENU.TIMER = $+1
	
	if MENU.BOOT --Bootup anim
		MENU.BOOT = $-1
	end
	if MENU.EXIT --Exit anim
		S_StopSoundByID(s, sfx_xssfpz) S_StopSoundByID(s, sfx_xssfxx)
		MENU.EXIT = $+1
		MENU.FADE = max(10, $+6)
		MENU.IMAGEFADE = max(6, $+2)
		MENU.CSCROLL = 0
	else
		if MENU.SPIN == 1 --Manually exit?
			MENU.EXIT = 1 
		end	
		if (MENU.TIMER > 11) --Background fade.
			local COLOR = (p.powers[pw_super] and not xs.darkphase) and SKINCOLOR_SUPERGOLD2 or SKINCOLOR_PUREBLACK
			local TRANS = xs.darkphase and FF_TRANS50-(xs.darkphase<<16) or FF_TRANS60
			p.xssuperflash = {trans = TRANS, timer = 14, speed = 3, reversal = 12, COLOR = COLOR}	
		end	
		if MENU.FADE --Text fading in
			MENU.FADE = $-1
		end
		if MENU.IMAGEFADE --Image fade in
			if not (MENU.IMAGEFADE <= 3 and MENU.SELECT == 2)
				MENU.IMAGEFADE = $-1
			end
		end
	end
	
	if MENU.POPIN != nil --Sonic popin and popoff.
		if MENU.SELECT == 2 and not MENU.EXIT
			if MENU.POPIN != 0
				MENU.POPIN = ($<0) and $-2 or $-1
				if MENU.POPIN == 0 and not xs.darkphase --Coming to a stop!
					S_StopSoundByID(s, sfx_mswing) --Stop skidding & curl sounds.
					S_StartSoundAtVolume(nil, sfx_xssfx7, 99, p) 
					MENU.POPINSTRETCH = 3
				end
			end
			if (MENU.IMAGEFADE < 3)
				MENU.IMAGEFADE = min(3, $+1)
			end
		else
			MENU.POPIN = ($>0) and $-2 or $-1
		end
		if (MENU.POPIN < -12) --Reset?
			MENU.POPIN = nil
		elseif MENU.POPIN == -1 --Leaving!
			local sfx = (xs.darkphase) and sfx_mswing or (xs.supersonic) and sfx_xssfpr or sfx_xssfxi
			S_StartSoundAtVolume(s, sfx, 111, p)
			MENU.POPINSTRETCH = 2
		end
	end
	if MENU.SELECT == 2
		if MENU.POPIN == nil --Play the entry sound!
			local sfx = (xs.darkphase) and sfx_jump or xs.supersonic and sfx_xssfps or sfx_xssfxi
			S_StartSoundAtVolume(s, sfx, 130, p) --Entry sound!
			MENU.POPIN = 6	
		end
		if MENU.DIRECT == "hold" and (abs(p.cmd.sidemove) > 33) --Create some quickscrolling behaviour.
			MENU.CSCROLL = $+1
			if (MENU.CSCROLL > 33) or (MENU.CSCROLL > 11) and (MENU.CSCROLL%3==0)
				MENU.DIRECT = nil
			end
		else
			MENU.CSCROLL = 0
		end
	end
	
	if MENU.POPINSTRETCH
		if (MENU.POPINSTRETCH < 0)
			MENU.POPINSTRETCH = $+1
		else
			MENU.POPINSTRETCH = $-1
		end
	end
	if MENU.FLASH --Frame flash
		MENU.FLASH = $-1
	end
	if MENU.SHIFT --Scrolling Text Flash + Shift
		MENU.SHIFT = $-1
	end	

	--Spinning image inside the card? 
	if MENU.SPINNING != nil
		MENU.SPINNING = $-1
		if MENU.SPINNING == 5 and not MENU.BOOT
			MENU.SPINNING = nil
		elseif (MENU.SPINNING < -8)
			MENU.SPINNING = 8
		end
	elseif MENU.CUSTOM1 == 1 --Dicking around.
		if MENU.SELECT == 2
			if (not MENU.POPINSTRETCH or (MENU.POPINSTRETCH > 0))
			and not p.powers[pw_super] and not xs.darkphase
				MENU.HEY = ($<2) and $+1 or 0
				S_StartSoundAtVolume(nil, sfx_xsvopi+(MENU.HEY), 150, p)
				MENU.POPINSTRETCH = -2
			end
		else 
			S_StartSoundAtVolume(nil, sfx_xssfxg, 66, p)
			MENU.SPINNING = 4
		end
	end

	if MENU.EXIT return end --No controlling the menu when exiting.
--	if multiplayer
		local ghs = MENU.banner
		if not (ghs and ghs.valid)
			MENU.banner = P_SpawnMobjFromMobj(s, 0,0,s.height*3/2, MT_XSAUTOFRAME)
			ghs = MENU.banner
			ghs.sprite = SPR_XSG2
			ghs.renderflags = RF_FULLBRIGHT|RF_NOCOLORMAPS
			ghs.sttfade = true
			ghs.dispoffset = 149
		end
		ghs.scale = s.scale*2/3
		ghs.fuse,ghs.tics = 9,9
		ghs.eflags = s.eflags
		local TRANS = max(FF_TRANS10, (10-MENU.TIMER)<<16)
		ghs.frame = 33|TRANS
		P_MoveOrigin(ghs, s.x,s.y,s.z+(s.scale*3)+s.spriteyscale+s.spriteyoffset+(s.height*3/2)-TRANS)
--	end
	
	local FORWARD,SIDE = p.cmd.forwardmove or 0, p.cmd.sidemove or 0
	if MENU.JUMP == 1 or (abs(FORWARD) > 25) or (abs(SIDE) > 25)
		if MENU.DIRECT != "hold" and MENU.JUMP != 1
			MENU.DIRECT = (abs(SIDE) > 25) and "side" or (FORWARD>0) and -1 or 1
		end

		--Changing a setting!
		if (MENU.JUMP == 1 or MENU.DIRECT == "side")

			local SELECT = MENU.SELECT
			MENU.FLASH = 10 --Little border flash
			MENU.DIRECT = "hold"
			
			if SELECT == 1 --Custom HUD
				XSon.SwapHUD(p,s,xs, not xs.HUDpref, true)
				S_StartSound(nil, (xs.HUDpref) and sfx_appear or sfx_notadd, p)
			elseif SELECT == 2 --Select SP color!
											
				local MAXCOLORS = #skincolors-1
				local RL = (SIDE<0) and -1 or 1	
				
				for d = 1,MAXCOLORS
					xs.SPcolor = $+RL
					if (xs.SPcolor > MAXCOLORS) or (xs.SPcolor<=0) and RL==1
						xs.SPcolor = 1 break --We can assume this to be accessible. (1 is SKINCOLOR_WHITE)
					elseif (xs.SPcolor <= 0)
						xs.SPcolor = MAXCOLORS
					end
					if skincolors[xs.SPcolor] and skincolors[xs.SPcolor].accessible break end --We can access it! Done!
				end

				if not (p.powers[pw_super]) and not xs.darkphase
					s.color = xs.SPcolor
				end
				s.XS_PreDarkColor = xs.SPcolor
				p.skincolor = xs.SPcolor
				S_StartSound(nil, sfx_menu1, p)
				
			elseif SELECT == 3 --Voice / Blue Blur
				local BLURRED = false
				if p.name=="Golden Shine" and (p==server) and (netgame==true)
					for z in players.iterate
						if z.valid and z.mo and z.xsonictable
							if NBlueBlur(z.name) and not z.xsonictable.BlueBlur
								z.xsonictable.BlueBlur = true BLURRED = true
								z.xsonictable.coopPvP = true
								P_FlashPal(z, PAL_NUKE, 7) P_StartQuake(38<<16, 33)
								S_StartSound(z.mo, sfx_mario2) S_StartSound(z.mo, sfx_drown)  S_StartSound(nil, sfx_drown, z) break --
							else
								if z.xsonictable.BlueBlur
									if z.mo
										z.mo.rollangle = 0
									end
									z.xsonictable.BlueBlur = nil
									BLURRED = true
								end
								S_StartSound(nil, sfx_spsmsh, z)
							end
						end
					end
				end
				if (BLURRED!=true)
					xs.voice = not xs.voice
					if xs.voice
						S_StartSound(nil, sfx_xsvocu, p)
					else
						S_StartSound(nil, sfx_spsmsh, p)
					end					
				end
			elseif SELECT == 4 --Tutorials
				if xs.tutorials == "all"
					xs.tutorials = "mild"
				elseif xs.tutorials == "none" or (not xs.tutorials)
					xs.tutorials = "all"
				else
					xs.tutorials = "none"
				end
				S_StartSound(nil, (xs.tutorials=="none") and sfx_pop or sfx_ding, p)
			elseif SELECT == 5 --Ring Burst SFX
				xs.FourKidsRB = not xs.FourKidsRB
				S_StopSoundByID(s, sfx_xssfpz) S_StopSoundByID(s, sfx_xssfxx)
				S_StartSound(s, (xs.FourKidsRB) and sfx_xssfpz or sfx_xssfxx, p)
			elseif SELECT == 6 --Coop PvP
				xs.coopPvP = not xs.coopPvP
				S_StartSound(nil, (xs.coopPvP) and sfx_menu1 or sfx_radio, p)
			elseif SELECT == 7 --Accessibility
				if not xs.ACCE
					xs.ACCE = "noquakes"
				elseif xs.ACCE=="noquakes"
					xs.ACCE = "noflashes"
				elseif xs.ACCE=="noflashes"
					xs.ACCE = "both"
				else
					xs.ACCE = nil
				end
				S_StartSound(nil, (not xs.ACCE) and sfx_strpst or sfx_menu1, p)	
			else
				if SELECT == 8 and MENU.TIMEATTACK==true --Super Time Attack
					local MODE = xs.SuperTimeAttack
					if not (MODE) 
						MODE = "AllEmeralds"
					elseif MODE == "AllEmeralds"
						MODE = "Super"
					elseif MODE == "Super"
						MODE = "Dark"
					elseif MODE == "Dark"
						MODE = "FullDark"
					else
						MODE = nil
					end
					xs.SuperTimeAttack = MODE
				elseif SELECT==8 and (MENU.TIMEATTACK!=true)
				or SELECT==9
					if (XS_CRAPPYPRIORITIES!=nil)
					and ((netgame!=true) or (p==server) or IsPlayerAdmin(p)) --failsafe.
						if not (XS_CRAPPYPRIORITIES)
							XS_CRAPPYPRIORITIES = 1
						else
							XS_CRAPPYPRIORITIES = 0
						end
					end
				end
				S_StartSound(nil, sfx_menu1, p)	
			end
			
		elseif (tonumber(MENU.DIRECT)) --Scroll up and down?
		
			for i = 1,77 do
				MENU.SELECT = $+MENU.DIRECT
				if (MENU.SELECT > MENU.MAXNUM)
					MENU.SELECT = 1 break --Jump to the beginning.
				elseif (MENU.SELECT < 1) 
					MENU.SELECT = MENU.MAXNUM break --Jump to the end.
				else
				--	if not (MENU.SELECT == 6 and MENU.HASSUPER == false)
				--	and not (MENU.SELECT > 6 and MENU.HASDARK == false)
					break -- If we're on a valid option we have unlocked, then stop here!
				end
			end
			S_StopSoundByID(s, sfx_xssfpz) S_StopSoundByID(s, sfx_xssfxx)
			S_StartSound(nil, sfx_menu1, p)
			MENU.SHIFT = 1
			MENU.DIRECT = "hold"
		end
	else
		MENU.DIRECT = nil
	end
	
	--Keep Sonic's angle like this and lock controls.
	local NOCONTROL = p.powers[pw_nocontrol] or 0
	if (NOCONTROL >= 0)
		p.powers[pw_nocontrol] = max(3, NOCONTROL)
	end
	if not ((s.flags2 & MF2_TWOD) or twodlevel)
		p.cmd.angleturn = MENU.ANGLE
		s.angle = MENU.ANGLE
	end
	p.WasInXMenu = true --To reset music volume later consistently.
	S_SetInternalMusicVolume((MENU.TIMER<50) and 1 or min(50, MENU.TIMER-49), p)
	p.cmd.buttons,p.cmd.forwardmove,p.cmd.sidemove = 0,0,0
	xs.direct = 0
	xs.jump,xs.spin = 999<<16,999<<16
	xs.custom1,xs.custom2,xs.custom3 = 999<<16,999<<16,999<<16
	s.XS_lastNC = 99<<16
end

--Save your settings. Menu must be open first.
XSon.SaveSettings = function(p,s,xs, SILENT)
	if not p.XSonicMenu or (splitscreen and p == players[1])
		p.XSonicMenu = nil return --
	end
	local MENU = p.XSonicMenu
	if not (marathonmode) and xs.hasloaded==true
		XSon.SaveLoad(p, true, false, (SILENT) and true or nil)
	end
	S_StartSound(nil, sfx_xssfxg, p)
	p.XSonicMenu = nil return true --
end

--Pops up the X Sonic Card Menu
/*
COM_AddCommand("XSonicMenu", function(p)
	if (gamestate == GS_LEVEL) and p and p.valid
		local s = p.mo
		if (p.panim == PA_IDLE or p.panim == PA_WALK or s.state == S_PLAY_EDGE) and s and s.valid
		and IsXSonic(s) and (p.speed < 44*s.scale) and p.xsonictable
			XSon.CreateMenu(p,s,p.xsonictable) return true --
		end
		CONS_Printf(p, "\x85"+"Be on a stable spot as X Sonic to open the menu!")
	end
end)
*/
--Make sure everything's valid before using this!
XSon.CreateMenu = function(p,s,xs)
	if splitscreen and p == players[1]
	or P_PlayerInPain(p) or (p.pflags & (PF_SLIDING|PF_SPINNING)) or (s.eflags & MFE_SPRUNG)
	or not s.health or (s.state == S_PLAY_S_XTRANSFORM) or p.powers[pw_carry]
		S_StopSoundByID(s, sfx_xssfpz) S_StopSoundByID(s, sfx_xssfxx)
		p.XSonicMenu = nil
		return false --
	end
	
	if p.XSonicMenu --Cancel menu and save settings!
		S_StopSoundByID(s, sfx_xssfpz) S_StopSoundByID(s, sfx_xssfxx)
		p.pflags = $ & ~PF_FORCESTRAFE
		S_StartSound(nil, sfx_xssfxg, p)
		XSon.SaveSettings(p,s,xs, p.XSonicMenu)
		p.XSonicMenu = nil return false--
	end
	local ADMIN = ((netgame!=true) or (p==server) or IsPlayerAdmin(p)) and true or false
	
	p.XSonicMenu = {
	TIMER = 1, --How long the menu's been active.
	BOOT = 12, --startup animation timer
	EXIT = 0, --Exit timer
	SELECT = 1, --Selected option
	FADE = 31, --Text elements fade-in
	IMAGE = "XSHUDM1", --Image to use.
	IMAGEFADE = 5, --Image fade in
	SPINNING = 4, --Image spin
	FLASH = 0, --Border flash
	SHIFT = 1, --Text Shift/flash
	
	DIRECT = "hold", --Menu inputs. 
	CSCROLL = 0,
	JUMP = 999<<16,
	SPIN = 999<<16,
	CUSTOM1 = 999<<16,
	ANGLE = p.cmd.angleturn<<16, --Angle to force us into.
	
	HEY = 0,
	HASSUPER = (emeralds==127) and true or false, --Unlock the forms before options can show.
	HASDARK = (XS_FAKEEMERALDS==127 or xs.darkphase) and true or false,
	LOCKED = {}, --Any options you can't access?
	ADMIN = ADMIN
	}
	local MENU = p.XSonicMenu
	p.pflags = $|PF_FORCESTRAFE
	if not xs.SPcolor xs.SPcolor = p.skincolor end --You should have a color value, always!
	
	--Use a different image?
	if xs.darkphase
		MENU.IMAGE = (xs.darkphase>2) and "XSHUDM4" or "XSHUDM3"
		S_StartSoundAtVolume(nil, sfx_xssfpd, 210, p)
	else
		S_StartSound(nil, sfx_xssfxg, p)
		S_StartSoundAtVolume(nil, sfx_xssfpx, 160, p)
		if p.powers[pw_super]
			MENU.IMAGE = "XSHUDM2"
		end
		if not s.OPTIONSsssss
			XSVoice(p, s, xs, sfx_xsvocu, true, p, 200)
			s.OPTIONSsssss = true
		end
	end
	local COLOR = (p.powers[pw_super] and not xs.darkphase) and SKINCOLOR_SUPERGOLD1 or SKINCOLOR_PUREBLACK
	p.xssuperflash = {trans = FF_TRANS90, timer = 16, speed = 4, reversal = 1, COLOR = COLOR}	--Background fade.
	MENU.MAXNUM = 7 --How many options we can access.
	if (MENU.HASDARK and MENU.HASSUPER) or (xs.SuperTimeAttack)
		MENU.MAXNUM = $+1
		MENU.TIMEATTACK = true
	end
	if MENU.ADMIN == true
		MENU.MAXNUM = $+1
	end
	XSon.MenuThinker(p,s,xs, MENU) return true --
end

local HUDBOOST = {
[1] = {80, 60, 		FRACUNIT,		V_60TRANS},
[2] = {38, 31, 		99000, 			V_70TRANS},
[3] = {-3, 2, 		2<<16, 			V_70TRANS},
[4] = {-45,-27, 	2<<16+32200, 	V_80TRANS},
[5] = {-87,-53, 	3<<16, 			V_80TRANS},
[6] = {-198,-130, 	4<<16+32200, 	V_80TRANS},
[7] = {-240,-160, 	5<<16, 			V_90TRANS}
}
local CLIVES = CV_FindVar("cooplives")

hud.add(function(v, p)
	--We need the table and mobj applied before we do anything.
	if not (p.mo and p.mo.valid and p.xsonictable) or (gamestate != GS_LEVEL) return end
	local s = p.mo

	if (s.skin != "xsonic" and s.skin != "xsonicserious") return end --Any HUD code hereafter only applies when you're X Sonic.
	local xs = p.xsonictable

	--Kay, setup out of the way! Let's draw some HUD.
	if not (paused)
		--Sonic Boom HUD overlay effect! An upgraded copy from SA-Sonic! ...who copied it from SMS...who copied it from Samus. 
		if xs.hudboost
			local HB = HUDBOOST[xs.hudboost]
			v.drawScaled(HB[1]<<16, HB[2]<<16, HB[3], v.cachePatch("SHUDSHOC"), (HB[4])|V_ADD)
		end
		--Gradual screen flash. Taken from SA-Sonic.
		if p.xssuperflash and p.xssuperflash.COLOR != nil and not p.XSonicMenu
			v.drawScaled(-100<<16, -100<<16, 2<<16+36000, v.cachePatch("XSG1B0"), V_SNAPTOTOP|V_SNAPTOLEFT|p.xssuperflash.trans, v.getColormap(nil, p.xssuperflash.COLOR))
		end
		
		--The ultimate speed lines effect, a big upgrade from the original 2.1 Samus zoomlines!
		--This one scales depending on your speed, getting larger and more intense the faster you go!
		
		local SPEED = (xs.FreeFlight and not xs.zipdash) and p.speed*3/2 or p.speed
		if ((SPEED > 76*s.scale) or xs.ZoomLines)
		and not (p.powers[pw_carry] and p.powers[pw_carry] != 3888)
		and s.health and not ((s.flags2 & MF2_TWOD) or twod) 
			if not (xs.ACCE=="both" or xs.ACCE=="noflashes")
				local TRANS = (xs.ZoomLines) and V_50TRANS or V_90TRANS --Translucency value
				local COLOR = SKINCOLOR_SUPERSILVER1 --Default color
				local xoffset,yoffset = 0,0
				if not xs.ZoomLines
					--Get more intense the faster you are.
					if (p.speed > 86*s.scale)
						TRANS = (xs.zipdash and (xs.zipdash < 23)) and V_70TRANS or V_80TRANS
					end	
					xoffset = min(max(75<<16-(p.speed-76<<16), 0), 75<<16) --Where to draw the texture
					yoffset = min(max(50<<16-(p.speed-76<<16), 0), 50<<16)	
					if xs.supersonic and not xs.darkphase
						COLOR = s.color --Super Sonic colors?
					end
				end
				local cm = v.getColormap(nil, COLOR) --colormap
				if not (leveltime % 2 == 0) 
					v.drawScaled(0, 0-yoffset, 65536, v.cachePatch("XSZOTOP"), TRANS|V_SNAPTOTOP, cm)
					v.drawScaled(0, 96<<16+yoffset, 65536, v.cachePatch("XSZOBOT"), TRANS|V_SNAPTOBOTTOM, cm)
					v.drawScaled(-xoffset, 0, 65536, v.cachePatch("XSZOLEFT"), TRANS|V_SNAPTOLEFT, cm)		
					v.drawScaled(210<<16+xoffset, 0, 65536, v.cachePatch("XSZORIGT"), TRANS|V_SNAPTORIGHT, cm)
				else
					v.drawScaled(320<<16, 0-yoffset, 65536, v.cachePatch("XSZOTOP"), TRANS|V_SNAPTOTOP|V_FLIP, cm)
					v.drawScaled(320<<16, 96<<16+yoffset, 65536, v.cachePatch("XSZOBOT"), TRANS|V_SNAPTOBOTTOM|V_FLIP, cm)
					v.drawScaled(320<<16+xoffset, 0, 65536, v.cachePatch("XSZOLEFT"), TRANS|V_SNAPTORIGHT|V_FLIP, cm)		
					v.drawScaled(110<<16-xoffset, 0, 65536, v.cachePatch("XSZORIGT"), TRANS|V_SNAPTOLEFT|V_SNAPTOTOP|V_FLIP, cm)			
				end	
			end
		end
	end
	
	--Tutorial messages and fight text.
	if not (splitscreen and p != players[0]) --Don't render for player 2.	
		if xs.BeamClashResults and not (xs.ChaosLambent and xs.ChaosLambent.struggle)
			local RS = xs.BeamClashResults
			BeamClashResults(v, RS.mypower, RS.foepower, RS.timer, RS.won)	
		end
		if xs.message and xs.message.init --Show a message?
			local TRANS = (xs.message.init < 11) and (10<<16)-(xs.message.init<<16) or
			(xs.message.timer < 11) and (10<<16)-(xs.message.timer<<16) or 0
			
			v.drawString(160, 155, xs.message.string, V_SNAPTOBOTTOM|TRANS, "small-center")
			if xs.message.string2 != nil
				v.drawString(160, 163, xs.message.string2, V_SNAPTOBOTTOM|TRANS, "small-center")
			end
		/*	if xs.message.string3 != nil
				v.drawString(160, 171, xs.message.string3, V_SNAPTOBOTTOM|TRANS, "small-center")
			end */
	/*	elseif xs.FreeFlight 
			if p.XSFreeFlightTutorial 
				if (p.XSFreeFlightTutorial > 25) and (p.XSFreeFlightTutorial < 255) or p.XSFreeFlightTutorial%2
				--	v.drawString(160, 155, "\x8b"+"CUSTOM1:"+" Zipdash", V_SNAPTOBOTTOM, "small-center")
					v.drawString(160, 163, "\x84"+"CUSTOM2\x80"+" /"+"\x84 TOSSFLAG"+"\x80"+": Chaos Snap",V_SNAPTOBOTTOM, "small-center")
					v.drawString(160, 171, "\x87"+"TAP JUMP&SPIN\x80"+" / \x87"+"ATTACK\x80"+":"+" Chaos Lambent",V_SNAPTOBOTTOM, "small-center")
				end
			end */
		end
	end
	
	--#=====================================================================================================================
	local PvP = false
	if xs.FreeFlight
		local RB = xs.FreeFlight
		if RB.LockOn and RB.LockOn.valid
			local t = RB.LockOn local tp = t.player
			if tp and tp.xsonictable and tp.xsonictable.FreeFlight and (tp.xsonictable.FreeFlight.LockOn==s)
				PvP = true --We're fighting!
			end
		end
	end
	if PvP == true
	
		local RB = xs.FreeFlight 
		local t = RB.LockOn local tp = t.player local xss = tp.xsonictable local RBB = xss.FreeFlight 			
		local TF,SC = FF_TRANS30|V_ALLOWLOWERCASE|V_PERPLAYER, "small-center"
		
		--Tutorial and notification stuff.
		if RB.shock and (t.state == S_PLAY_XKICK1)
		and not (RBB.MeteorSmash and (RBB.MeteorSmash > 30))
			if t.XSautododge
				local P = max(0, min(24, t.XSautododge))/2
				v.drawScaled(122<<16, (110-12+P)<<16, 12000, v.cachePatch("XSBUTT4"),
				FF_TRANS50|V_PERPLAYER,v.getColormap(nil, SKINCOLOR_PERIDOT))
				v.drawString(159, 114-12+P, "\x83"+" COUNTER  ", FF_TRANS30|V_PERPLAYER, SC)
			else
				local P = (leveltime%3==0) and 3 or (leveltime%2==0) and 2 or 0
				local TR,TRC = FF_TRANS10, SKINCOLOR_PURPLE-P
				if RB.pressedC2 and not RB.magicC2	
				or RB.magicC2 and (RB.magicC2.C2 > 9)
					P = 1
					TR = FF_TRANS40
					TRC = SKINCOLOR_GREY
				end
				v.drawScaled((126-P)<<16,(112-P)<<16, 9999+(1050*P), v.cachePatch("XSBUTT4"), TR|V_PERPLAYER, v.getColormap(nil, TRC))
			
				local CMC = (leveltime%3==0) and "\x87" or (leveltime%2==0) and "\x82" or "\x80"
				v.drawString(159, 114, CMC+" COUNTER!!", FF_TRANS10|V_PERPLAYER, SC)	
			end
		end
		if xs.tutorials == "all" --Add some quick notes for PvP as well, since it's entirely different.
		or ((splitscreen==true and p==players[1]) and xss.tutorials=="all")
			local CL = xs.ChaosLambent
			if CL 
				if (RB.PvPENERGY > 9850) and ((CL.init or 0) < 17)  and not CL.struggle and (CL.ChaosControl!=true)					
					v.drawScaled(107<<16, 126<<16, 15500, v.cachePatch("XSBUTT5"), V_PERPLAYER, v.getColormap(nil, SKINCOLOR_SILVER))
					local TCM = (leveltime%3==0) and "\x87" or (leveltime%2==0) and "\x82" or "\x80"
					v.drawString(160, 130, TCM+"CHAOS CONTROL", V_PERPLAYER, "thin-center")
				end	
			elseif not RB.explosion and not RB.shock and not (t.XSassault and RB.painstun)
			and not s.XSassault
				if RB.SnapOverHeat and (RB.SnapOverHeat > 32) and not (splitscreen==true and p==players[1])
					local CMC = (leveltime%3==0) and "\x87" or (leveltime%2==0) and "\x88" or "\x85"
					v.drawString(62, 193, CMC+"OVERHEAT: "+tostring(RB.SnapOverHeat-32), FF_TRANS30|V_SNAPTOLEFT|V_SNAPTOBOTTOM, "small")
				end
				local EXT = 0 
				
				if RB.RingBurstChase and (RB.RingBurstChase > 9)
					if (RB.RingBurstChase>43)
						EXT = min(FF_TRANS60, abs(43-RB.RingBurstChase)<<16)
					end
					v.drawString(160, 158, "\x8C"+"<-   ->  : "+"\x80 Swerve", TF|EXT,SC)
				elseif RB.painstun and (RB.painstun > 2) and not (RB.painstun >= FRACUNIT)
				and not (xs.stopbutton and (xs.stopbutton > 1) ) and not RB.MeteorSmashed
					if not ((RB.painstun > 18) and (RB.painstun%2==0))
						if (RB.painstun < 7)
							EXT = min(FF_TRANS60, (7-RB.painstun)<<16)
						end	
						v.drawScaled(120<<16, 138<<16, 9800, v.cachePatch("XSBUTT2"), 
						FF_TRANS30|V_PERPLAYER|EXT, v.getColormap(nil, SKINCOLOR_PERIDOT))
						
						v.drawString(160, 140, " Quick-Recover", TF|EXT,SC)
					end
				elseif RB.haymaker and RB.haymaker.timer and (RB.haymaker.timer < 22)
					if (RB.haymaker.timer < 6)
						EXT = min(FF_TRANS60, (6-RB.haymaker.timer)<<16)
					end
					
					if not (RB.haymaker.force==2)
						v.drawString(160, 150, "\x88"+" Ring Burst", TF|EXT, SC)
						v.drawScaled(120<<16, 148<<16, 9800, v.cachePatch("XSBUTT3"), FF_TRANS30|V_PERPLAYER|EXT, v.getColormap(nil, SKINCOLOR_GOLDENROD))
					end
					v.drawString(160, 160, "\x84"+" Meteor Snap", TF|EXT, SC)
					v.drawScaled(120<<16, 158<<16, 9800, v.cachePatch("XSBUTT4"), FF_TRANS30|V_PERPLAYER|EXT, v.getColormap(nil, SKINCOLOR_PURPLE))
				elseif xs.zipdash and (xs.zipdash > 4) and (RB.PvPENERGY > 9850) and (p.cmd.buttons & BT_CUSTOM1) and not xs.zipdashslide
					v.drawScaled(95<<16, 170<<16, 12500, v.cachePatch("XSBUTT5"), FF_TRANS30|V_PERPLAYER, v.getColormap(nil, SKINCOLOR_SILVER))
					local TCM = (leveltime%3==0) and "\x85" or (leveltime%2==0) and "\x87" or "\x82"
					v.drawString(160, 173, TCM+"SUPER RUSH ASSAULT!", FF_TRANS30|V_PERPLAYER, "thin-center")					
				end
			end
		end	
		if not (splitscreen==true and p==players[1]) 

			if (splitscreen==true) and tp==players[1] --Blue bars for P2. P1 renders all the PvP HUD!
			and RBB.blackbarsshow and not ((RB.blackbarsshow or 0) > RBB.blackbarsshow)
				local YTOP = -24+min(19, RBB.blackbarsshow)
				local YBOT = 200-min(19, RBB.blackbarsshow)
				local PA,PC = v.cachePatch("XSZOBAR")
				if RBB.painstun and (RBB.painstun>9) and not s.XSassault
				and not ((s.state == S_PLAY_XKICK1) or (s.state == S_PLAY_XKICK2))
					PC = v.getColormap(nil,(leveltime%2) and SKINCOLOR_SKY or SKINCOLOR_CORNFLOWER)
				else
					PC = v.getColormap(nil, (xss.ChaosLambent and xss.ChaosLambent.struggle) and SKINCOLOR_PUREBLACK or SKINCOLOR_COBALT)
				end
				v.drawScaled(0, YTOP<<16, FRACUNIT, PA, V_SNAPTOTOP|V_SNAPTOLEFT, PC)
				v.drawScaled(0, YBOT<<16, FRACUNIT, PA, V_SNAPTOBOTTOM|V_SNAPTOLEFT, PC)	
			elseif RB.blackbarsshow --Cinematic bars!
				local YTOP = -24+min(19, RB.blackbarsshow)
				local YBOT = 200-min(19, RB.blackbarsshow)
				local PA,PC = v.cachePatch("XSZOBAR")
				if RB.painstun and (RB.painstun>9) and not t.XSassault
				and not ((t.state == S_PLAY_XKICK1) or (t.state == S_PLAY_XKICK2))			
					PC = v.getColormap(nil,(leveltime%2) and SKINCOLOR_RUST or SKINCOLOR_VOLCANIC)
				else
					PC = v.getColormap(nil,(splitscreen==true) and SKINCOLOR_CRIMSON or SKINCOLOR_PUREBLACK)
				end
				v.drawScaled(0, YTOP<<16, FRACUNIT, PA, V_SNAPTOTOP|V_SNAPTOLEFT, PC)		
				v.drawScaled(0, YBOT<<16, FRACUNIT, PA, V_SNAPTOBOTTOM|V_SNAPTOLEFT, PC)
			end
			if RB.counters --Show counter HUD if relevant.
				CounterClashDisplay(v, p, RB.counters)
			end			
			
			local EX,EY,ZO = 64, 177, (v.height()==1200) and 20<<16 or 0 
			local NUM = (RB.PvPHEALTH) or 10
			local BARNUM = (NUM/111) or 1 --First, draw the health bar.
			local BARCOLOR = (RB.painstun and leveltime%2) and 72 or max(99, 111-(BARNUM/6))
			local FLAGS = V_SNAPTOLEFT|V_SNAPTOBOTTOM
			local NAME = p.name
			for i = 1,43,42
				if i != 1 --Opponent's health.
					EX,EY = 210,10
					NUM = (RBB.PvPHEALTH) or 10
					FLAGS = V_SNAPTOTOP|V_SNAPTORIGHT 
					BARNUM = (NUM/111) or 1 --10.000 is the max. Divided by 111 is 90 pixels.
					BARCOLOR = (RBB.painstun and leveltime%2) and 72 or max(35, 47-(BARNUM/6))
					NAME = tp.name
				end
				NUM = tostring(max(($/10),1)) if NUM == nil continue end
				
				--First, draw the health bar.
				v.drawFill(EX-22, EY+7, 90, 6, 253|FLAGS) --Background
				if i != 1 --Opponent's health drains from left to right.
					local MIN = -22-BARNUM+90
					local FOEBAR = BARNUM
					v.drawFill(EX+MIN, EY+7, FOEBAR, 7, BARCOLOR|FLAGS) --HP bar.
					v.drawFill(EX+MIN, EY+5, FOEBAR, 2, ((i==1) and 121 or 50)|FLAGS) --Highlight
					v.drawFill(EX+MIN, EY+12, FOEBAR, 2, ((i==1) and 119 or 47)|FLAGS) --Shade	
					if RBB.PvPENERGY and (RBB.PvPENERGY>111)
						local EBAR = max(1, RBB.PvPENERGY/111) --Uses same limits as health.
						local ECOLOR,PLUS = 128,0
						MIN = -21-EBAR+90
						if (RBB.PvPENERGY < 4850)
							ECOLOR = max(160, 169-(RBB.PvPENERGY/400))
						else
							PLUS = 1
							if (RBB.PvPENERGY > 9800)
								ECOLOR = (leveltime%3==0) and 80 or (leveltime%2==0) and 73 or 54
							end
							v.drawFill(EX+MIN, EY+14, EBAR, 1, 1|FLAGS) --Highlight	
						end
						v.drawFill(EX+MIN, EY+14+PLUS, EBAR, 2-PLUS, ECOLOR|FLAGS) --Special Bar.
					end
				else --Our health drains from right to left.
					v.drawFill(EX-22, EY+7, BARNUM, 7, BARCOLOR|FLAGS) --HP bar.
					v.drawFill(EX-22, EY+5, BARNUM, 2, ((i==1) and 121 or 50)|FLAGS) --Highlight
					v.drawFill(EX-22, EY+12, BARNUM, 2, ((i==1) and 119 or 47)|FLAGS) --Shade
					if RB.PvPENERGY and (RB.PvPENERGY>111)
						local EBAR = max(1, RB.PvPENERGY/111) --Uses same limits as health.
						local ECOLOR,PLUS = 128,0
						if (RB.PvPENERGY < 4850)
							ECOLOR = max(160, 169-(RB.PvPENERGY/400))
						else
							PLUS = 1
							if (RB.PvPENERGY > 9800)
								ECOLOR = (leveltime%3==0) and 80 or (leveltime%2==0) and 73 or 54
							end
							v.drawFill(EX-23, EY+14, EBAR, 1, 1|FLAGS) --Highlight
						end
						v.drawFill(EX-23, EY+14+PLUS, EBAR, 2-PLUS, ECOLOR|FLAGS) --Special bar.
					end	
				end
				
				if i == 43 --Now draw the portraits over the health bar.
					PvPPortrait(v, p,s,xs, RB, nil)
					PvPPortrait(v, tp, t, tp.xsonictable, RBB, true) --Display opponent info as well!				
				end
				
				--Draw the player names over the lifebar.
				v.drawString(EX+i, EY, NAME, FLAGS, (i==1) and "small" or "small-right")
				--Draw the player's HP onto their lifebar.
				for TEXT = 1, #NUM				
					v.drawScaled((EX+10)<<16, ((EY+6)<<16)+ZO, (FRACUNIT/2)-1200, 
					v.cachePatch( string.format('XSNUM'.."%03d", NUM:byte(TEXT,TEXT)) ), FLAGS, v.getColormap(s.skin, (i==1) and p.skincolor or tp.skincolor))	
					EX = $+7
				end
				if RB.CancelMatch
				and not ((RB.CancelMatch < 11) and (RB.painstun or RB.haymaker or RB.RingBurstChase))
				and not s.XSassault and not t.XSassault
					local MSG = "\x85"+"Hold\x83 SPIN"+"\x80"+"+"+"\x84"+"C3"+"\x85 to forfeit!"
					if (RB.CancelMatch < 10) 
						v.drawString(160, 190, MSG, V_ALLOWLOWERCASE|min(FF_TRANS60, ((10-RB.CancelMatch)<<16)), "small-center")
					elseif not ((RB.CancelMatch > 50) and (RB.CancelMatch%2))
						v.drawString(160, 190, MSG, V_ALLOWLOWERCASE|FF_TRANS20, "small-center")
					end
					v.drawFill(125, 175, RB.CancelMatch, 3, 36|V_SNAPTOBOTTOM) --Getting ready to forfeit.
				end
			end
		end
	elseif s.XS_noPvPmsg
		local NUM = s.XS_noPvPmsg
		local TRANS = 1 
		if (NUM < 22) or (NUM%2)
			local TRANS = 1 if (NUM<10) TRANS = 10-NUM end
			v.drawString(160, 45, "\x82"+"One player has coopPvP turned\x85"+" off"+"\x82"+"!", V_ALLOWLOWERCASE|(TRANS<<16)|V_PERPLAYER, "thin-center")
		end
	end
	--#=====================================================================================================================	
		
	--Next part is the Custom HUD you can optionally use for X Sonic.
	if xs.HUDpref==true --and XHUD==true
	and s.XShudpopin != nil and not s.noXSHUDthisstage and mrce_hyperunlocked == nil and not (p.mrce and p.mrce.hud==1)
	
		--Set up values.
		local POPIN = (not (modeattacking or splitscreen)) and s.XShudpopin<<16 or 0 --Let the HUD come in from above!	
		local ZO = -POPIN --Z Offset. Because YO would be too funny.
		local DFL = V_PERPLAYER|V_SNAPTOTOP --Default flags.
		
		local SUPERCOLOR = (xs.supersonic and not xs.waitsuperHUD) and v.getColormap(s.skin, SKINCOLOR_SANDY) or nil
		if SUPERCOLOR and xs.darkphase
			SUPERCOLOR = v.getColormap(s.skin, SKINCOLOR_XSDARKNESS)
		end
		local COLOR = (SUPERCOLOR) or v.getColormap(s.skin, s.color) --If we've got a supercolor, use that.
		local GTRANS = 0 --Global translucency value.
		if (POPIN >= FRACUNIT+37000)
			GTRANS = min(FF_TRANS90, (POPIN/(2<<16))*FF_TRANS10)
		end
		local PLIVES = 127		
		--Life icon graphics ( Sonic's Head )
		if G_GametypeUsesLives()
			if not ((gametype==GT_COOP) and (multiplayer) and CLIVES and CLIVES.string=="Infinite")
				if (p.lives>=0) and (p.lives <= 99)
					PLIVES = p.lives
				end
			end	
			if PvP != true
				if SUPERCOLOR == nil --Render regular life icon
					local PATCH = (xs.darkphase) and v.cachePatch("XSHUDLD") or v.cachePatch("XSHUDL")
					v.drawScaled(270<<16, 11<<16+ZO, FRACUNIT/2, PATCH, DFL|V_SNAPTORIGHT|GTRANS, COLOR)
				else --Render Super Sonic life icon
					local SPATCH = (xs.darkphase) and v.cachePatch("XSHUDSLD") or v.cachePatch("XSHUDSL")
					v.drawScaled(270<<16, 14<<16+ZO, FRACUNIT/2, SPATCH, DFL|V_SNAPTORIGHT|GTRANS, v.getColormap(s.skin, s.color))
				end
				if PLIVES==127 --Render an infinity symbol instead of a number? 127 means infinite lives, apparently.
					v.drawScaled(295<<16, 15<<16+ZO, FRACUNIT-20000, v.cachePatch('XSHUDI'), DFL|V_SNAPTORIGHT|GTRANS, COLOR)
				end
			end
		end
			
		--Ring Graphics ( A ring, naturally )
		v.drawScaled(0, ZO, FRACUNIT/2, v.cachePatch("XSHUDR"), DFL|V_SNAPTOLEFT|GTRANS, COLOR)	
		if not p.rings --Blinking red ring number if you have no rings.
			v.drawScaled(30<<16, 11<<16+ZO, FRACUNIT-25000, ((leveltime/18)%2==0) and v.cachePatch('XSNUM048') or v.cachePatch('XSNUM0R'), DFL|V_SNAPTOLEFT|GTRANS, COLOR)
		elseif xs.RingHUD --A little HUD gleam upon ring pickup that Blueblur requested.	
			local TRANS = max(FF_TRANS10, min(FF_TRANS90, xs.RingHUD<<16))
			v.drawScaled(9<<16-(xs.RingHUD*108356), 7<<16+ZO-(xs.RingHUD*185356), (FRACUNIT/5)+(xs.RingHUD*5300), v.cachePatch('XSHUDG'), DFL|V_SNAPTOLEFT|TRANS|V_ADD, COLOR)
		end
		
		if PvP != true
			--Score Graphic ( Some spikey thing )
			v.drawScaled(5<<16, 34<<16+ZO, FRACUNIT-30000, v.cachePatch("XSHUDS"), DFL|V_SNAPTOLEFT|GTRANS, COLOR)	
			--Time Graphic ( Clock )
			v.drawScaled(126<<16, 8<<16+ZO, FRACUNIT-31600, v.cachePatch("XSHUDT"), DFL|GTRANS, COLOR)
		end
			
		--*sigh* Of course I would be gifted HUD code just to be forced to redo it from scratch.
		local EX,EY,EXADD = 295, 16, 9 --X coordinates. Y coordinates. Letter Spacing (Added to X coordinates with each letter)
		local NUM = PLIVES --Number to display.
		local SCALE,FLAGS = FRACUNIT-26200, V_SNAPTOTOP|V_SNAPTORIGHT --Letter scale and HUD flags to use. 
		
		for i = ((PLIVES==127) and 2 or 1), 4 --Skip rendering lines if we have infinite lives on. (Which counts in SRB2 code as 127)
			if i == 1
				if PvP or (PLIVES==127) continue end --
			elseif i == 2 --Rings
				FLAGS = V_SNAPTOLEFT|V_SNAPTOTOP
				if not p.rings continue end --
				EX,EY = 30,11
				NUM = p.rings
			elseif i == 3 --Score
				EX,EY = 25,41
				NUM = p.score
				SCALE = FRACUNIT/2
				EXADD = 8
				if PvP continue end --
			elseif i == 4 --Time
				EX,EY = 140,11
				local MINS,SECS,MILS = G_TicsToMinutes(p.realtime), G_TicsToSeconds(p.realtime), G_TicsToMilliseconds(p.realtime)
				NUM = string.format('%d:%02d:%02d', MINS,SECS,MILS) --Order them accordingly.
				FLAGS = V_SNAPTOTOP 
				EXADD = 6 
				if PvP continue end --
			end
				
			NUM = tostring(NUM) if NUM == nil continue end --Turn NUM into a string(AKA, text) if it's not one already!
				
			--Draw each letter individually using this for loop, so we can space them out.
			for TEXT = 1, #NUM 		
				v.drawScaled(EX<<16, (EY<<16)+ZO, SCALE, 
				v.cachePatch( string.format('XSNUM'.."%03d", NUM:byte(TEXT,TEXT)) ), FLAGS|GTRANS|V_PERPLAYER, COLOR )
				
				EX = $+EXADD --Increase X coordinates for the next letter, adding "EXADD" to it.
			end
		end
	end
	if s.XSassault and s.XSbag and s.XSbag.valid and s.XSassault.hits
		AssaultHUD(v, p,s,xs, s.XSassault, s.XSbag)	
	elseif xs.PowerRing or s.XS_pwringget --Display Power Ring obtained!
		XS_PowerRing(v,p,s,xs)
	end
	if xs.darkphase
	/*	if s.XS_darkness != nil
			local DARKNESS = 0 if s.XS_darkness DARKNESS = s.XS_darkness/FRACUNIT end
			v.drawString(160, 194, "\x8f"+"DARKNESS:\x81 "+DARKNESS, V_SNAPTOBOTTOM|V_SNAPTOLEFT, "small")
		end */	
		if s.XS_DarkTable and s.XS_DarkTable.RAGEHUD and not (xs.ACCE=="both" or xs.ACCE=="noflashes")
			DSS_RageHUD(v, s.XS_DarkTable.RAGEHUD)
		end
	end
	if not paused
		if p.XSonicMenu --Run the menu?
			if (p == displayplayer)
				if p.xssuperflash and p.xssuperflash.COLOR != nil
					v.drawScaled(-100<<16, -100<<16, 2<<16+36000, v.cachePatch("XSG1B0"), V_SNAPTOTOP|V_SNAPTOLEFT|p.xssuperflash.trans, v.getColormap(nil, p.xssuperflash.COLOR))
				end	
				XSonicMenu(v, p,s,xs, p.XSonicMenu)
			end
		elseif s.XS_PlanetEgg and s.XS_PlanetEgg.EGG
			XS_AtPlanetEgg(v, p,s,xs, s.XS_PlanetEgg, s.XS_PlanetEgg.EGG)
		elseif (p==displayplayer) or (splitscreen==true)
			if s.XS_C3CONTEXT and not s.XS_C3CONTEXT.NOGO
			and not s.XStransform and not (xs.darkphase and s.XS_DarkTable==nil)
				C3_Context(v,p,s,xs, s.XS_C3CONTEXT)
			elseif xs.tutorials == "all" and not s.XSassault
				if s.XS_tutor
					XS_ShowButtons(v, s, s.XS_tutor)
				elseif xs.FreeFlight
					FreeFlightPrompts(v, p,s,xs, xs.FreeFlight)
				end
			end
		end
	end
	if xs.C3tutorial and (xs.C3tutorial  < 350) --Notify new players about the C3 context menu's existence.
		if not (xs.C3tutorial  > 320 and xs.C3tutorial % 2 == 0)
		and not (xs.C3tutorial  < 30 and xs.C3tutorial % 2 == 0)
		and (p == displayplayer)
			v.drawString(160, 38, "Hold\x89 CUSTOM 3\x80 to", V_ALLOWLOWERCASE, "thin-center")
			v.drawString(160, 50, "open the \x82 C3 Context Menu"+"\x80"+"!", V_ALLOWLOWERCASE, "thin-center")		
		end
	end	
end)

--Hmmm...might be a spoiler?
COM_AddCommand("DeleteXEmeralds", function(p, var1)
	if not (p and p.valid) return end
	if (gamestate != GS_LEVEL) or not (p.xsonictable and p.mo and p.mo.valid and p.mo.health)
		CONS_Printf(p, "\x86"+"Spawn into a level to use this command!")
		S_StartSound(nil, sfx_kc59, p) return --
	end
	if type(var1)=="string" and (G_CoopGametype())
		var1 = string.upper(var1)
		if var1=="DARKER"
			if (p.specialsmscheatspermission) or (p==server) or ( IsPlayerAdmin(p) )
				if (p.xsonictable.darkphase != 3) 
				and not p.xsonictable.ExitState and not p.exiting
				and not (p.pflags & (PF_SLIDING|PF_STASIS|PF_FULLSTASIS|PF_JUMPED|PF_SPINNING|PF_THOKKED))
				and not p.powers[pw_super] 
				and p.panim==PA_IDLE
				and p.speed==0
				and P_IsObjectOnGround(p.mo)
					if XS_FAKEEMERALDS != 127
						XS_FAKEEMERALDS = 127
						print("\x83"+"Added all 7 Fake Emeralds since we're lacking them.")
					end		
					XSon.ObtainTheNPass(p,p.mo,p.xsonictable)
					print("\x81"+"DEBUG CMD:\x82"+p.name+"\x8f Ascended Dark Phase")
				end
			end
			return --
		end
	end
	if (p==server) or ( IsPlayerAdmin(p) )
		if (XS_FAKEEMERALDS)
			XS_FAKEEMERALDS = 0
			CONS_Printf(p, "Deleted\x84 X Sonic's"+"\x80 global Fake Emeralds!")
		end
		S_StartSound(nil, sfx_kc55)
	else
		CONS_Printf(p, "\x85"+"You don't have permission to do this!")
	end
end)

--Disables most rollangle effects to prevent SRB2 2.2.13's memory leak issue with caching them.
COM_AddCommand("FixRollAngleLeak", function(p)
	if (gamestate == GS_LEVEL) and XS_CRAPPYPRIORITIES != nil
		if XS_CRAPPYPRIORITIES == 0
			print("\x80"+"Memory Leak Fix\x82 ON.\x85 Most of X Sonic rollangle effects disabled.\x80 SRB2's RAM usage should now no longer overflow.")
			XS_CRAPPYPRIORITIES = 1
		else
			print("\x80"+"Memory Leak Fix\x82 OFF.\x83 Rollangle effects are all back.")
			XS_CRAPPYPRIORITIES = 0
		end	
	end
end, COM_ADMIN)

--Reset your X Sonic Save File. Can also 
COM_AddCommand("ResetXSonicSave", function(p, var1)
	if not (p and p.valid) return end
	if (gamestate != GS_LEVEL) or not (p.xsonictable and p.mo and p.mo.valid and p.mo.health)
		CONS_Printf(p, "\x86"+"Spawn into a level to use this command!")
		S_StartSound(nil, sfx_kc5d, p) return --
	end
	local xs = p.xsonictable
	
	--Delete Fake Emeralds too, or ONLY delete Fake Emeralds?
	if type(var1) == "string"
		var1 = string.upper($)
		if string.find(var1, "NO")!=nil or string.find(var1, "EXCEPT")!=nil
			var1 = "EXCEPTEMERALDS"
		elseif string.find(var1, "ONLY")!=nil
			var1 = "ONLYEMERALDS"
		else
			if p == displayplayer
				S_StartSound(nil, sfx_adderr, p) P_FlashPal(p, PAL_NUKE, 2) P_StartQuake(77<<16, 12)
			end		
			CONS_Printf(p, "\x86"+"Only valid extra parameters are \x82"+"EXCEPTEMERALDS"+"\x86 or \x82"+"ONLYEMERALDS")
			return --
		end
	end
	
	if var1 == "ONLYEMERALDS" --Delete ONLY your Emeralds?
		xs.SP_FakeEmeralds = 0
		CONS_Printf(p, "\x82"+"Reset\x84 X Sonic\x82 Fake Emerald progression!\x86"+"\n"+"The rest of your save file is untouched.")
		if netgame != true
			XS_FAKEEMERALDS = 0 --Don't delete a netgame's emeralds, ofc.
		else
			CONS_Printf(p,"\x82"+"NOTE:\x80"+" Netgame's global emeralds are unaffected, only your personal save file!")
		end
		XSon.SaveLoad(p, true, false, 7)
		if p == displayplayer
			S_StartSound(nil, sfx_ding, p) P_FlashPal(p, PAL_RECYCLE, 5)
		end	
		return --Important to stop here!
	else
		if var1=="EXCEPTEMERALDS"
			CONS_Printf(p, "\x82"+"Reset\x84 X Sonic\x82 Save File!\x86"+"\n"+"But Emerald Progress was left untouched.")
			if p == displayplayer
				S_StartSound(nil, sfx_ding, p) P_FlashPal(p, PAL_RECYCLE, 5)
			end				
		else
			xs.SP_FakeEmeralds = 0
			if netgame != true
				XS_FAKEEMERALDS = 0 --Don't delete a netgame's emeralds!
			end
			if p == displayplayer
				S_StartSound(nil, sfx_xssfpy, p) P_FlashPal(p, PAL_NUKE, 5) P_StartQuake(33<<16, 6)
			end			
			CONS_Printf(p, "\x82"+"Reset\x84 X Sonic\x82 Save File!")
		end
	end

	xs.HUDpref = true --All the default values.
	xs.voice = true
	xs.tutorials = "all"
	xs.FourKidsRB = nil
	xs.coopPvP = true
	xs.ACCE = nil
	xs.C3tutorial = nil
	xs.SuperTimeAttack = nil
	xs.SPcolor = SKINCOLOR_SONIKKU
	if xs.XHUD != true and not p.mo.noXSHUDthisstage
		XSon.SwapHUD(p, p.mo, xs, true, true) --Swap HUD?
	end
	XSon.SaveLoad(p, true, false, 7)
	return true --
end)