--╔════════════════════════════╗══════════════════════════════════════════════════════════════════════════════════════════════#
--║      Dark Super Sonic      ║ Functions & passive thinkers for all 3 Dark Phases. Phase 3 is the full power "super" version!
--╚════════════════════════════╝══════════════════════════════════════════════════════════════════════════════════════════════#
local XSon = XSon
local function P_MobjFlip(s) if s and s.valid and ((s.eflags&MFE_VERTICALFLIP) or (s.flags2&MF2_OBJECTFLIP)) return -1 end return 1 end

local DARKAURACOLORS
XSon.DarkAura = function(OFFSET)
	if DARKAURACOLORS == nil --I don't recommend adding any colors that are directly black!
		DARKAURACOLORS = {
		SKINCOLOR_CRIMSON,
		SKINCOLOR_LAVENDER,
		SKINCOLOR_XSDARKNESS,
		SKINCOLOR_XSDARKNESS,
		SKINCOLOR_GALAXY,
		SKINCOLOR_MIDNIGHT,
		SKINCOLOR_FUCHSIA,	
		SKINCOLOR_CRIMSON,SKINCOLOR_LAVENDER,SKINCOLOR_XSDARKNESS,SKINCOLOR_GALAXY,SKINCOLOR_MIDNIGHT--Overflow colors for laziness
		}
	end
	local MAXCOLORS = #DARKAURACOLORS
	OFFSET = OFFSET or 0
	for i = MAXCOLORS,1,-1
		if leveltime%i==0
			return DARKAURACOLORS[ min(MAXCOLORS-1, (i+OFFSET)) ] or SKINCOLOR_XSDARKNESS  --
		end
	end
end

--Add damage to a bag.
XSon.AddDamage = function(t, ADD)
	if t and t.valid
		local BAGDAMAGE = t.BAGdamage or 0
		t.BAGdamage = max(0, BAGDAMAGE+ADD)
		t.HUDhit = 3
		if t.target and t.target.player
			t.BAGPdamage = t.BAGdamage
		end		
	--	local so = t.tracer
	--	if so and so.player and so.health
	--		
	--	end
	end
end

--t is the object getting damaged. Then we add darkness accordingly.
XSon.AddDarkness = function(s, ADD, SPECIAL)
	local p = s.player local xs = p and p.xsonictable
	if not xs or not xs.darkphase or not s.health
		s.XS_darkness = nil --You shouldn't have this.
	else
		local RB = s.XS_DarkTable
		local DARKNESS = s.XS_darkness or 0 --How much we had.
		local OG = DARKNESS --How much we started with.
		
		s.XS_darkness = max(0, DARKNESS+ADD)
		
		local GOTDARKNESS = RB and RB.GotDarkness or 0
		local DIFF = FixedInt(max(FRACUNIT, ADD))
		if RB
			if xs.darkphase and xs.darkphase != 3
				local CDARKNESS = FixedInt(max(1<<16, s.XS_darkness))
				if s.XS_darknessgoals == nil
					if xs.darkphase == 1 
						s.XS_darknessgoals = {41,56,67,75,81}
					elseif xs.darkphase == 2 
						s.XS_darknessgoals = {80,91,101,110,118,126}
					end
				end
				local TABLE = s.XS_darknessgoals
				if (TABLE[1] != ANGLE_90)
					local ENDNO = #TABLE
					for i = 1,ENDNO
						local GOAL = TABLE[i] if GOAL == nil continue end --
						if (CDARKNESS >= GOAL)
							S_StartSound(s, sfx_xssfe7) S_StartSound(nil, sfx_xssfe7, p)
							XSon.RageHUD(p,s,xs,RB, "double")
							RB.heartbeat = (GOAL==TABLE[ENDNO]) and 2 or 1
							if i == ENDNO
								TABLE[1] = ANGLE_90
							else
								TABLE[i] = ANGLE_135
							end
						end
					end	
				end
			else
				s.XS_darknessgoals = nil
			end
			if (DIFF >= 5) and RB.maxrings and (p.rings < RB.maxrings) --Regen up to your max ringcap. (However many rings you had when you first transformed)
				local ADDRINGS = max((DIFF-1)/4, 1)
				p.rings = min($+ADDRINGS , RB.maxrings) 
				S_StartSoundAtVolume(s, sfx_itemup, min(255, ADDRINGS*50))
			end
		end
		
		if SPECIAL != "noeffects" and not (GOTDARKNESS>5)
		
			if RB RB.GotDarkness = GOTDARKNESS+min(DIFF, 3) end
			XSon.DarkBody(s, nil, nil, SKINCOLOR_CRIMSON, 2)
			
			if (ADD >= 1>>16)
				local RVOLUME = min(255, 20+(DIFF*30))
				S_StartSoundAtVolume(s, sfx_xssfpg, RVOLUME)
				
				for i = 1,min(12, max(1, DIFF-1))
					XSon.DarkBody(s, nil, nil, SKINCOLOR_CRIMSON, 2)
				end
				if (DIFF >= 2)
					local ghs = XSon.ElectricBody(s, nil, SKINCOLOR_CRIMSON)
					ghs.dispoffset = 133
					ghs.frame = (leveltime%2==0) and ($+FF_TRANS20)|FF_HORIZONTALFLIP or $+FF_TRANS20
					ghs.spritexscale,ghs.spriteyscale = $+35000,$+35000
					ghs.spriteyoffset = $-5<<16
					
					if (DIFF >= 3)
						
						local ghs = XSon.Overglow(s, 2, SKINCOLOR_PUREBLACK)
						if ghs and ghs.valid
							ghs.frame = ($ & ~FF_TRANSMASK)|FF_TRANS10
							ghs.blendmode = AST_TRANSLUCENT
							ghs.fuse,ghs.tics = 10,99
							ghs.sttfade = true
							ghs.tracer = s
						end
						if (DIFF >= 5)
							local DARKAURA = XSon.DarkAura(1)
							XSon.SmokeMist(s, 4, 99*s.scale, nil, nil, nil, DARKAURA)
							if (DIFF >= 6)
								if not s.player.xssuperflash
									s.player.xssuperflash = {trans = FF_TRANS90, timer = 18, speed = 1, reversephase = 9, COLOR = SKINCOLOR_PUREBLACK}
								end
								XSon.SmokeCircle(s, (DIFF>8) and 4 or 3, 64*s.scale, s, 45000, 36, DARKAURA, 6<<16)
							elseif not s.player.xssuperflash
								s.player.xssuperflash = {trans = FF_TRANS90, timer = 14, speed = 1, reversephase = 7, COLOR = SKINCOLOR_PUREBLACK}
							end
						end
					end
				end
			end
		end
	end
end

--The Snap Dive is replaced with a new ability with a focus on speed and aggressive combat chaining
--rather than bouncing and minor repositioning.
XSon.DarkRush = function(p,s,xs, ZIPSPEED)
	if s.XS_DarkTable == nil s.XS_DarkTable = {} end
	
	local RB = s.XS_DarkTable
	local PHASE = (xs.darkphase) or 0
	local DIRECTION = (not xs.direct and (p.cmd.buttons & BT_SPIN)) and 3 or 0 --Forwards
	local DARKAURA = XSon.DarkAura(0)
	
	--Setup...
	p.xsringburst,p.xswallrun,p.xstauntstance = nil
	s.XSstretch,xs.zipstretch,xs.zipstretch2,xs.spinstretch,xs.jumpstretch,xs.edgestretch = nil
	s.spriteyoffset = 0	
	s.rollangle = 0
	xs.didjumpboost = true
	xs.ringburstcharge = 0
	RB.rushcount = RB.rushcount and $+1 or 1
	local RUSHCOUNT = (PHASE==3 and not (p.cmd.buttons & BT_SPIN)) and min(5,RB.rushcount) or RB.rushcount
	if ((s.flags2 & MF2_TWOD) or twodlevel)
		p.cmd.forwardmove = 50
		s.momy = 0
		DIRECTION = 0
	elseif xs.direct
		if (p.cmd.forwardmove <= -28)
			DIRECTION = 3 --Backwards.
		elseif (abs(p.cmd.sidemove) > 28)
			DIRECTION = (p.cmd.sidemove < 0) and 1 or 2 --Left 1. Right 2
		end
	end
	
	--Dark Rush can redirect insanely fast multiple times.
	local MIN = (s.eflags & MFE_UNDERWATER) and 5*s.scale or 1
	local ZIPLEAP = (ZIPSPEED and DIRECTION!=3)
	if DIRECTION == 3
		MIN = $+26*s.scale
		if PHASE != 3
			xs.ringburstcd = 18
		end
	elseif PHASE == 1 or PHASE == 2
		MIN = (PHASE==1 and not p.powers[pw_sneakers]) and 26*s.scale or 18*s.scale
		if PHASE == 2
			RB.forcetrail = 1
		end
		xs.ringburstcd = 18
	else --if PHASE == 3
		RB.forcetrail = 2
	end
	
	
	--Horizontal calculations.
	local SUBT = min(4, max(1, 4-RB.rushcount))
	
	local MAXSPEED = (p.speed<92*s.scale) and p.speed
	or (p.speed<150*s.scale) and max(92*s.scale, p.speed-SUBT*s.scale)
	or max(150*s.scale, p.speed-(SUBT+6)*s.scale)
	
	local MINSPEED = (PHASE==3) and 81*s.scale or (PHASE==2) and 78*s.scale or 73*s.scale
	MINSPEED = $+ min(50+PHASE*54, 3*max(2, PHASE)*RB.rushcount)*s.scale --Keep increasing minimum speed!
	MINSPEED = $-(ZIPLEAP == true and -50*s.scale or MIN)
	
	
	local RUSHSPEED = min(210*s.scale, max(MINSPEED, MAXSPEED) )
	RUSHSPEED = (s.eflags&MFE_UNDERWATER) and $*8/(4+xs.darkphase) or $
	if ((s.flags2 & MF2_TWOD) or twodlevel)
		DIRECTION = 0
		local TWODSPEED = max(p.speed*2/3, abs(RUSHSPEED))	
		RB.forcemove = 2 --Because fuck you, 2D mode
		RB.forcespeed = max(TWODSPEED, 36+(18*PHASE*s.scale) )
		P_InstaThrust(s, (p.cmd.sidemove<0) and ANGLE_270 or (p.cmd.sidemove==0) and s.angle or 0, RB.forcespeed) 
		xs.stopstick = 3
	else
		P_InstaThrust(s, (xs.direct) and xs.turnang or s.angle, RUSHSPEED)
	end
	
	--Vertical calculations
	if not (s.eflags & MFE_GOOWATER)
		local ADDMOMZ = (p.cmd.buttons & BT_SPIN) and -9<<16 or 3*PHASE<<16
		if (ADDMOMZ < 0) and not ((xs.direct or 0) < 3)
			ADDMOMZ = -25*s.scale
		end
		local MINMOMZ = (RUSHCOUNT>1) and RUSHCOUNT*4<<16 or 0
		if MINMOMZ == 0
			if (s.momz*P_MobjFlip(s) > 4*s.scale) 
				ADDMOMZ = (ADDMOMZ>0) and max(0, $-abs(s.momz)) or 0
			end
		else
			local DOWNVALUE = (ADDMOMZ < 0) and ADDMOMZ*2 or 3*s.scale 
			if (s.momz*P_MobjFlip(s) > DOWNVALUE)
				s.momz = DOWNVALUE*P_MobjFlip(s)
			end
			if (RUSHCOUNT > 2)
				ADDMOMZ = $/(RUSHCOUNT-2)
			end
		end
		P_SetObjectMomZ(s, ADDMOMZ-MINMOMZ, true)
	end
	
	--Animation stuff.
	XSon.SwapStretch(p, s, xs)
	s.spritexscale,s.spriteyscale = FRACUNIT-12000,FRACUNIT+7500
	s.XSstretch = {timer = 4, Xspeed = 3000, Yspeed = 1800}	
	s.state = S_PLAY_XZIPLEAP
	s.tics = 18
	if DIRECTION == 0 or DIRECTION  == 3 --Do the forward/backwards animation?
		if DIRECTION == 3 --Backflip!
			xs.diveslowdown = 12
			s.spriteyoffset = 6<<16
		else	
			xs.frontflip = 13
			xs.poutydrop = 8
			s.spriteyoffset = 3<<16
		end
	end	
	s.XSAssaultT = nil --Reset!
	if not xs.ManualRushAssault
	or (p.cmd.buttons & BT_SPIN) and ((xs.spin or 0) < 6)
		RB.AssaultHitBox = 2
		XSon.AssaultHitBox(p,s,xs) --Spawn Hitbox
	end
	
	xs.snapdive,xs.snapdivetrick = 0,0
	RB.flashing = max(2, min(FixedInt(p.speed/10), 9))
	RB.speedlines = RB.flashing+1
	RB.darkrush = min(18, max(RB.flashing, s.tics)) --Properties.
	s.XSresist = 2
	if PHASE != 1
		RB.windlines = RB.flashing/(PHASE==2 and 3 or 2)
	end
	p.pflags = $|PF_JUMPED|PF_JUMPDOWN & ~(PF_THOKKED|PF_NOJUMPDAMAGE|PF_SPINNING)

	XS_RingBurstDust(p, s, xs, 350*s.scale)
	XS_RingBurstSparkles(s, nil, DARKAURA)
	
	local ghs = XS_CircleWave(s, 1, DARKAURA, 10, FRACUNIT+5111)
	ghs.frame = E|FF_TRANS60
	ghs.spriteyoffset = $-20<<16
	XSon.sparkles(p,s,xs, 38, 5, 5, 2<<16, DARKAURA, AST_ADD)	
	
	S_StopSoundByID(s, sfx_xssfpo)
	
	--What sound to play?
	if (s.eflags & MFE_UNDERWATER)
		S_StartSound(s, sfx_s3k7d)
		for d = 1,(P_RandomRange(1,4))
			local bubble = P_SpawnMobj(s.x-P_RandomRange(-35,35)*s.scale, s.y-P_RandomRange(-35,35)*s.scale, s.z+P_RandomRange(-2,20)*s.scale, MT_SMALLBUBBLE)
			if bubble and bubble.valid
				bubble.scale = s.scale bubble.fuse = 99
			end	
		end
		XSon.Sound(s, sfx_xssfpo, (PHASE==1) and 79 or (PHASE==2) and 130 or 255)
	else
		if PHASE == 1
			XSon.Quake(s, 36<<16, 2, 200<<16)
		end
		XSon.Sound(s, sfx_xssfpo)
	end
	if (PHASE >= 2) and ((PHASE == 3) or not (s.eflags & MFE_UNDERWATER))
	
		XSon.Quake(s, PHASE*25<<16, 2+PHASE, 300*PHASE<<16)
		
		RB.SFX = P_SpawnMobjFromMobj(s,s.momx,s.momy,s.momz, MT_THOK)
		RB.SFX.flags2 = MF2_DONTDRAW RB.SFX.tics,RB.SFX.fuse = 99,99
		XSon.Sound(RB.SFX, sfx_xssfpn, (PHASE==3) and 205 or 102)	
	end
	
	if s.skin != "xsonicserious"
		XSon.DeTransform(p,s,xs)
	end	
end

--Spawn or upkeep an assault hitbox!
XSon.AssaultHitBox = function(p,s,xs, DIST, SCALE)
	local SUPER = XS_Super(p)
	local ZDIST = DIST
	if DIST == nil
		if s.XS_DarkTable and not s.XS_DarkTable.darkrush and xs.darkphase
			DIST = 490*s.scale
			ZDIST = (270+(SUPER*30))*s.scale
		else
			DIST = (490+(SUPER*180))*s.scale --Custom distance?
			ZDIST = ((p.cmd.buttons&BT_SPIN) and not (p.cmd.buttons&BT_JUMP)) and DIST or DIST*3/4
		end
	end
	if SCALE != nil --Adjust for a custom scale?
		DIST = FixedDiv(FixedMul($, SCALE), 100) 
		ZDIST = FixedDiv(FixedMul($, SCALE), 100)
	end
	local ANGLE = p.drawangle --How should I angle the hitbox?
	if (p.speed > 16*s.scale) or p.speed and (xs.direct<=9)
		ANGLE = R_PointToAngle2(s.x, s.y, s.x+s.momx, s.y+s.momy)
	elseif (xs.direct>9)
		ANGLE = xs.turnang
	end
	
	local X,Y,Z = s.x+s.momx+FixedMul(DIST-s.scale*3, cos(ANGLE)), s.y+s.momy+FixedMul(DIST-s.scale*3, sin(ANGLE)), s.z+s.momz-ZDIST --Coordinates.
	local ghs = s.XSassaultHB 
	
	if not (ghs and ghs.valid)
		s.XSassaultHB = P_SpawnMobj(X,Y,Z, MT_XSASSAULTHB)
		ghs = s.XSassaultHB
		ghs.tracer,ghs.target = s,s
	else
		P_MoveOrigin(ghs, X,Y,Z)
	end
	ghs.fuse,ghs.tics = 3,3 --Keep it alive!
	ghs.radius,ghs.height = DIST,DIST*3/4
	ghs.momx = 1
end

--Timers specially for Dark Sonic. All info must be valid.
XSon.DSSTimers = function(p,s,xs, RB)
	if xs.darkphase!=3 and (p.rings>10)
		RB.lastrings = p.rings
	end
/*	if RB.loserings --This exists purely to avoid desyncs.
		RB.loserings = max($-1,0)
		if xs.darkphase!=3
			if RB.loserings==2
				P_DoPlayerPain(p)
				P_PlayRinglossSound(s, p)
			elseif not (RB.loserings)
				if xs.darkphase==2
					if (p.rings > 10)
						P_PlayerRingBurst(p, 8)
					end
					p.rings = max($-10, 0)
				else
					if (p.rings > 20)
						P_PlayerRingBurst(p, 15)
					end
					p.rings = max($-20, 0)
				end
				if G_PlatformGametype()
					P_NukeEnemies(s,s, 150<<16)	
				else--if (gametype != GT_COOP)
					P_PlayerFlagBurst(p, false)
					P_PlayerEmeraldBurst(p, false)
					P_PlayerWeaponPanelOrAmmoBurst(p)
				end
				if p.panim==PA_PAIN
					p.drawangle = R_PointToAngle2(s.x, s.y, s.x+s.momx, s.y+s.momy)+ANGLE_180
				end
				RB.loserings = nil
			end
		end
	end */
	if RB.darkrush
		RB.darkrush = min(17, max($-1,0))
		if P_IsObjectOnGround(s) or not RB.darkrush or (p.pflags & (PF_STARTJUMP|PF_SPINNING))
			if RB.flashing or RB.darkrush
				s.flags2 = $ & ~MF2_DONTDRAW
			end
			RB.rushcount,RB.darkrush,RB.flashing = 0,0,0
			if (s.state == S_PLAY_RUN) and (p.pflags & PF_JUMPED)
				s.state = (s.momz*P_MobjFlip(s) > 8*s.scale) and S_PLAY_SPRING or S_PLAY_FALL
			end
		else
			if not xs.ManualRushAssault
				RB.AssaultHitBox = 2
				XSon.AssaultHitBox(p,s,xs) --Spawn Hitbox
			end
			if RB.forcemove --Fix 2D bullshit.
				if not (s.flags2 & MF2_TWOD) and not (twodlevel)
					RB.forcemove,RB.forcespeed = nil
				else
					RB.forcemove = $-1
					P_InstaThrust(s, (p.cmd.sidemove<0) and ANGLE_270 or (p.cmd.sidemove==0) and s.angle or 0, RB.forcespeed) 
				end
			end			
			if s.state == S_PLAY_XZIPLEAP
				local SPRITE2 = SPR2_TAL0
				if (RB.darkrush < 5)
					s.sprite2 = SPR2_MLEE|(xs.supersonic and FF_SPR2SUPER or 0)
					s.frame = ($ & ~FF_FRAMEMASK)| (max(A, min(D, 4-RB.darkrush)))
					if RB.darkrush == 4 and not s.XSstretch
						XSon.SwapStretch(p, s, xs)
						s.spritexscale,s.spriteyscale = FRACUNIT+4000,FRACUNIT+4000
						s.spriteyoffset = 3<<16
						s.XSstretch = {timer = 4, Xspeed = 1000, Yspeed = 1500, air = true}			
					end
					SPRITE2 = s.sprite2
				else
					if (p.speed > 32*s.scale)
						p.drawangle = R_PointToAngle2(s.x, s.y, s.x+s.momx, s.y+s.momy)
					end
					if xs.darkphase == 3
						if (RB.darkrush > 12)
							if not s.XSstretch
								XSon.SwapStretch(p, s, xs)
								s.spritexscale,s.spriteyscale = FRACUNIT+34000,FRACUNIT+3000
								s.spriteyoffset = -8<<16
								s.XSstretch = {timer = 7, Xspeed = 9000, Yspeed = 400, air = true}
							end
						elseif (RB.darkrush < 8) and (xs.jump > 6) and (p.speed > 31*s.scale) --Minor hover power.
							if not (p.cmd.forwardmove < -44) and (p.pflags & PF_JUMPED)
							and not (p.cmd.buttons & (BT_SPIN|BT_CUSTOM1|BT_CUSTOM3))
							and not ((RB.darkrushhover or 0) > 41) and not s.XSlunging
								RB.darkrush = 8
								if (s.tics < 10) and (s.tics != 1)
									s.tics = $+1
								end
								if (leveltime%2)
									XS_RingBurstSparkles(s, nil, DARKAURA)
								end							
								if (s.momz*P_MobjFlip(s) < 1*s.scale)
									RB.darkrushhover = RB.darkrushhover and $+1 or 1
									s.momz = $*8/9
									P_SetObjectMomZ(s, abs(P_GetMobjGravity(s)), true)
								end
							end
						end
						if (s.momz*P_MobjFlip(s) > -16*s.scale)
							s.sprite2 = SPR2_TAL0|FF_SPR2SUPER	
						else
							s.sprite2 = SPR2_DIVE|FF_SPR2SUPER
						end
						s.frame = ($ & ~FF_FRAMEMASK)|((leveltime%2) and B or A )
					else
						if (s.momz*P_MobjFlip(s) < 9*s.scale)
							s.sprite2 = SPR2_TAL0				
							s.frame = ($ & ~FF_FRAMEMASK)|((leveltime%2) and B or A)
						else
							s.sprite2 = SPR2_GLID				
							s.frame = ($ & ~FF_FRAMEMASK)					
						end
						SPRITE2 = s.sprite2
					end
					if (RB.darkrush > (xs.darkphase==3 and 8 or 10)) and p.speed
						local RNG = P_RandomRange(6250,19250)
						XSon.SpikedAura(s, 100250+RNG)
					end
				end
				if (RB.darkrush > 13-xs.darkphase) or (RB.darkrush < 3)
					local ghs = P_SpawnGhostMobj(s)
					ghs.color = SKINCOLOR_XSSUPERAURA
					ghs.colorized = true
					ghs.sprite2 = SPRITE2|(xs.supersonic and FF_SPR2SUPER or 0)
					ghs.frame = (SPRITE2==SPR2_RUN) and H|FF_TRANS50 or ((s.frame & ~FF_TRANSMASK)|FF_TRANS50)
					ghs.blendmode = AST_REVERSESUBTRACT
				end
			end
			if s.XSAssaultT
				XSon.DSSlunge(p,s,xs, RB)
			end
		end
	else
		RB.darkrushhover = 0
		if not s.XSlunging
			RB.jumpDR = 0
		end
	end
	if RB.jumpDR
		RB.jumpDR = max($-1,0)
	end
	if RB.GotDarkness
		RB.GotDarkness = max($-1,0)
	end
	if p.powers[pw_carry] or P_PlayerInPain(p) or p.xsringburst or p.xswallrun
		if RB.flashing or RB.darkrush
			s.flags2 = $ & ~MF2_DONTDRAW
		end
		RB.rushcount,RB.speedlines,RB.windlines,RB.darkrush,RB.flashing = 0,0,0,0,0
	elseif RB.flashing
		RB.flashing = max($-1,0)
		if (RB.flashing%2) or (RB.flashing < 2)
			s.flags2 = $ & ~MF2_DONTDRAW
		else
			s.flags2 = $|MF2_DONTDRAW
		end
	end
	if RB.forcetrail
		RB.forcetrail = $-1
		XSZipTrail(s, nil, 1)
	end
	if RB.speedlines
		RB.speedlines = (p.speed>8*s.scale) and $-1 or 0
		XS_RingBurstStripes(s, SKINCOLOR_XSSUPERAURA, AST_REVERSESUBTRACT, 34<<16)
	end
	if RB.AssaultHitBox
		if p.xsringburst or p.xswallrun or xs.zipdashleap or s.XSassault
		or p.powers[pw_carry] or (xs.spin > 999<<16) or (s.state == S_PLAY_GASP)
			RB.AssaultHitBox = 0
		else
			RB.AssaultHitBox = $-1
			XSon.AssaultHitBox(p,s,xs)
		end
	end
	if RB.windlines
		RB.windlines = $-1
		local MINXY = FixedDiv(FixedMul(p.speed, 4), s.scale)
		if not RB.stripes
			XSon.FlightWindLines(s, 165*s.scale, {
			XY = XSLocalRNG(MINXY/2, MINXY*2,s)*s.scale ,
			EZ = s.height/2},
			FF_TRANS20|FF_FULLBRIGHT, FRACUNIT+18000, 12, XSon.DarkAura(3))
		end
		XSon.FlightWindLines(s, 320*s.scale, {
		XY = XSLocalRNG(MINXY, MINXY*3,s)*s.scale,
		EZ = (s.momz<6*s.scale) and XSLocalRNG(-99+s.momz/s.scale,140+s.momz/s.scale,s)*s.scale or XSLocalRNG(-180,124,s)*s.scale },
		FF_TRANS30|FF_FULLBRIGHT, FRACUNIT+XSLocalRNG(22555,54000,s), 14, DARKAURA)
	end	
	if RB.maxrings == nil
		RB.maxrings = max(p.rings, 50)
	end
	local VIOLENCE = RB.noviolence or 0
	RB.noviolence = VIOLENCE+1
	if (VIOLENCE > 200) and (p.rings > 1) --Increasing ring drain overtime when not in combat for a long time.
		local DRAINTIME = max(350-(VIOLENCE/7), 51)
		if (DRAINTIME < 130) and (VIOLENCE > 401) 
			DRAINTIME = 130
		end
		if (leveltime % DRAINTIME==0)
			p.rings = max($-1,0)
			S_StartSoundAtVolume(nil, sfx_antiri, 80, p)
			local ghs = XSon.Overglow(s, 2, SKINCOLOR_RED)
			if ghs and ghs.valid
				XSon.ElectricBody(s, nil, SKINCOLOR_NOBLE)
				ghs.frame = ($ & ~FF_TRANSMASK)|FF_TRANS10
				ghs.blendmode = AST_TRANSLUCENT
				ghs.fuse,ghs.tics = 9,99
				ghs.sttfade = true
				ghs.tracer = s
			end			
		end
	end
	
	if RB.heartbeat
		if RB.heartbeat == 1
			XSon.RageHUD(p,s,xs,RB, "veins")
		else
			XSon.RageHUD(p,s,xs,RB, "bloodlust")
			if not S_SoundPlaying(s, sfx_xssfe7)
				S_StartSound(s, sfx_xssfe7)
			end
		end
	elseif RB.RAGEHUD != nil
		XSon.RageHUD(p,s,xs,RB, nil, true)
	end
end

--Anger building
XSon.RageHUD = function(p,s,xs,RB, STYLE, FADE)
	if RB.RAGEHUD == nil 
		if FADE return end --
		RB.RAGEHUD = {direct = 1, trans = 5, countdown = 8, type = "veins", color = SKINCOLOR_CRIMSON}
	end local RAGE = RB.RAGEHUD
	
	if FADE
		if (leveltime%4==0)
			RAGE.trans = min($+1,9)
			if RAGE.trans>=9 RB.RAGEHUD = nil end
		end return --
	end
	
	if STYLE == "double"
		RAGE.double = 8
		RAGE.type = "double"
		RAGE.direct = -1
		RAGE.trans = 6
	else
		if RAGE.double
			RAGE.double = $-1
			RAGE.type = "double"
		elseif STYLE
			RAGE.type = STYLE
		elseif RAGE.type == "double"
			RAGE.type = "bloodlust"
		end
	end
	
	if (RAGE.trans >= 6) or (RAGE.trans <= 4)
		RAGE.countdown = max($-1,0)
		if RAGE.countdown == 0
			RAGE.direct = (RAGE.trans>=6) and -1 or 1
			RAGE.trans = $+RAGE.direct
		end
	else
		RAGE.countdown = 8
		if STYLE!="veins" or (leveltime % 2 == 0)
			RAGE.trans = $+RAGE.direct
		end
	end
end

--Darkness passive.
XSon.DSSPassive = function(p,s,xs) -- if not xs.darkphase return end 
	if s.XS_DarkTable == nil s.XS_DarkTable = {} end
	local PHASE = xs.darkphase
	local RB = s.XS_DarkTable
	local DARKAURA = XSon.DarkAura(0)
	local JUMPFACTOR = skins["xsonicserious"].jumpfactor
	
	xs.moody = nil
	if s.skin == "xsonic" or s.skin == "xsonicoverlay" --Wrong skin.
		s.momx,s.momy,p.speed = 0,0,0
		COM_BufInsertText(p, "skin xsonic; skin xsonicserious")
		s.skin = "xsonicserious"
		R_SetPlayerSkin(p, "xsonicserious")
	end	
	if xs.FreeFlight
		XSon.CancelFlight(p,s,xs, true)
	end
	XSon.DSSTimers(p,s,xs, RB)
	
	if PHASE==3 --FULL power!
	
		local light,ths,thss = XSon.ShinySplat(s, SKINCOLOR_FUCHSIA, 3, 95, false)
		if ths and ths.valid ths.frame = ($ & ~FF_TRANSMASK)|FF_TRANS90 end
		if thss and thss.valid thss.frame = ($ & ~FF_TRANSMASK)|FF_TRANS90 end
		
	--	if not xs.darkphasetransition
			if s.XSboomshot --Not usually active, but I'll put code anyway.
				XSon.BoomShotHitBox(s,800*s.scale,30,21,13,6,2)
			end
			if s.XSSuperRadius --Activate a destruction hitbox.
				if not s.XSlunging
					P_RadiusAttack(s, s, s.XSSuperRadius, DMG_NUKE, true)
				end
				s.XSSuperRadius = nil
			end
			if s.XSsuperroll --Aerial Zipdash roll.
				if P_IsObjectOnGround(s) or p.powers[pw_carry] or xs.snapdive or p.xsringburst
					s.XSsuperroll = 0
				else
					s.XSsuperroll = (s.XSsuperroll<0) and min($+1,0) or max($-1,0)
				end
				s.rollangle = s.XSsuperroll*ANGLE_45
			end
			if p.panim == PA_IDLE
				if (s.tics<0) or (s.tics > 9)
					s.tics = 9
				end
				if (s.anim_duration<0) or (s.anim_duration > 9)
					s.anim_duration = 9
				end
				if (leveltime%4==0) or RB.idle == nil
					RB.idle = RB.idle and $+1 or 1
					if (RB.idle > D)
						RB.idle = A
					end
				end
			elseif s.state == S_PLAY_STUN or s.state == S_PLAY_PAIN --Will not allow infinite Stun!
				if (s.tics > 70) or s.tics == -1
					s.tics = 70
					if (s.state == S_PLAY_STUN) and xs.stupidbugoverride != 2 and not (p.pflags & PF_SLIDING)
						xs.stupidbugoverride = 2
						XSon.Quake(so, 80<<16, 5, 1250<<16)
						XSon.ElecticBody(s, 14, (xs.darkphase) and SKINCOLOR_CRIMSON or s.color)
						XSon.FlickerLight(s)
						XSon.DeTransformGFX(p,s,xs)
						local ghs = XSon.SuperSonicWind(s, (not (xs.darkphase)) and SKINCOLOR_FLAME or nil, nil, nil, FRACUNIT-18000)
						ghs.dispoffset = 122
					end
				end
			end

			--Play aura SFX on loop.
			if not s.XSauraSFX or ((not netgame) and not S_SoundPlaying(s, sfx_xssfe1))
				local SPEED = (xs.snapdive or p.xsringburst) and 60 or ((p.speed+abs(s.momz*2))/s.scale) or 0
				local RVolume = (SPEED>30) and max(34, min(62, 106-SPEED)) or 62 --Get less noticable the faster you move.
				s.XSauraSFX = 76
				S_StopSoundByID(s, sfx_xssfe1)
				XSon.Sound(s, sfx_xssfe1, RVolume)
			elseif netgame and not S_SoundPlaying(s, sfx_xssfe1)
				XSon.Sound(s, sfx_xssfe1, 52) --OG is sfx_xssfxy
			end
			s.XSauraSFX = max($-1,0)
			
			if xs.custom1 == 1 and not P_IsObjectOnGround(s) and XSon.TryAirZipDash(p,s,xs)	
				--
			else
				if s.state == S_PLAY_EDGE
					if (s.tics > 6) s.tics = 6 end
				elseif (s.state >= S_PLAY_XZIPBRAKESUPER) and (s.state <= S_PLAY_XZIPSLIDE2)
					xs.stupidbugoverride = xs.stupidbugoverride and $+1 or 1
					if (xs.stupidbugoverride > 41)
						XSZipDashCancel(p, s, xs)
						xs.waszipping = false
						RestoreZipDashCam(p, s, xs)
						XSAirGroundState(p, s, xs)
						xs.zipcd = 0
						xs.stupidbugoverride = 0
					end
				elseif (s.sprite2 == SPR2_GLID|FF_SPR2SUPER) --Auto animate!
					s.frame = ($ & ~FF_FRAMEMASK)| ((leveltime%2) and B or A)	
				else
					if s.state == S_PLAY_RUN
						if (leveltime%2) and (p.speed > 40*s.scale) and not s.XSlunging and not s.XSassault
							P_RadiusAttack(s, s, min(176*s.scale, p.speed+10*s.scale), DMG_NUKE, true)
						end	
					elseif s.state == S_PLAY_WAIT s.state = S_PLAY_STND end --Huh? Don't use that animation.
					xs.stupidbugoverride = 0
				end 		
			end
	--	elseif (leveltime % 35 == 0)
	--		p.rings = max($+1,2) --Don't lose rings in your transforming animation!
	--		xs.LastRings = nil
	--	end
		
		--Phase 3 adjustments.	
		s.eflags = $|MFE_FORCESUPER
	--	p.powers[pw_super] = max($,9)
		xs.supersonic = true
		p.charflags = $|SF_NOSKID & ~SF_SUPER
		s.color = SKINCOLOR_DAKUSONIKKU
		
		if (p.powers[pw_spacetime] > 0) and (p.powers[pw_spacetime] < 750)
			p.powers[pw_spacetime] = 800 
		end
		if not xs.zipdashslide and (p.normalspeed < (91<<16)+221)
			p.normalspeed = (s.eflags & MFE_UNDERWATER) and (164<<16)+221 or (91<<16)+221
		end
		if (p.runspeed > (44<<16)+221)
			p.runspeed = (44<<16)+221
		end
		
		local SPEEDAJ = p.speed/(4<<16) --Acceleration adjustment. 
		p.acceleration = max(min(63-SPEEDAJ, 52), 36)
		p.jumpfactor = JUMPFACTOR+36000
		
		if s.state == S_PLAY_STND or s.state == S_PLAY_WAIT
			if( (s.anim_duration < 0) or (s.anim_duration > 5))
				s.anim_duration = 5
			end
		end
		if (leveltime%35==0)
		and not ((p.pflags & PF_FULLSTASIS) and p.awayviewtics)
		and not (p.powers[pw_carry]==CR_ZOOMTUBE)
			p.rings = max($-1,0)
		end
		
	else--if PHASE != 3 --Passive effects!
		xs.supersonic = nil
		if s.XS_darkness and not xs.TScooldown
			local DARKNESS,GOAL = s.XS_darkness>>16, s.XS_darknessgoals
			local NEXTPHASE = (GOAL) and GOAL[#GOAL]
			if NEXTPHASE and (DARKNESS > NEXTPHASE) 
				if not RB.READY
					XSon.LightningStrike(s, SKINCOLOR_NOBLE, s)	RB.READY = 0
				end
				RB.READY = $+1
				
				if (RB.READY%2==0)
					local ghs = XSon.ElecticBody(s, 4, DARKAURA, FF_TRANS30, AST_TRANSLUCENT)
					ghs.mirrored = (RB.READY%4==0) and true or false
					if (RB.READY < 87) and not p.xssuperflash
						p.xssuperflash = {trans = FF_TRANS90, timer = 12, speed = 2, reversal = 6, COLOR = SKINCOLOR_PUREBLACK}
					end	
					if (RB.READY > 250) and not s.XSassault
					and (p.panim == PA_SPRING or p.panim == PA_FALL or p.panim == PA_ROLL or p.panim >= PA_IDLE and p.panim <= PA_RUN)
						XSon.ObtainTheNPass(p,s,xs)
						s.XS_darkness,s.XS_C3CONTEXT = 0,nil
						RB.READY = nil
					end					
				end			
			end
		end
		if (leveltime%35==0)
		and not ((p.pflags & PF_FULLSTASIS) and p.awayviewtics)
		and not (p.powers[pw_carry]==CR_ZOOMTUBE)
			p.rings = max($-1,0)
			P_RemoveShield(p)
			
			RB.instate = RB.instate and $+1 or 0
			local ADD = 1<<15
			if (RB.instate > 690)
				ADD = (RB.instate > 1950) and $+112500 or (RB.instate > 1350) and $+64000 or $+27500
			end
			XSon.AddDarkness(s, ADD, "noeffects")
		end
		if PHASE == 2
			local SPEEDAJ = p.speed/(4<<16)
			if not xs.zipdashslide
				p.normalspeed = max($, 79<<16+666)
			end
			p.jumpfactor = JUMPFACTOR+18660
			p.acceleration = max(min(62-SPEEDAJ, 49), 36)
		else
			local SPEEDAJ = p.speed/(3<<16)
			if not xs.zipdashslide
				p.normalspeed = max($, 69<<16+666)
			end
			p.jumpfactor = JUMPFACTOR+6660
			p.acceleration = max(min(60-SPEEDAJ, 45), 36)
		end
		p.runspeed = 44<<16+222
		s.eflags = $ & ~MFE_FORCESUPER
		p.powers[pw_super] = 0
		p.charflags = $ & ~(SF_NOSKID|SF_SUPER)
		s.color = SKINCOLOR_DAKUSONIKKU
	end
	if (PHASE > 1)
		XSon.DarkBody(s, nil, nil, DARKAURA, 2)	
		if (leveltime%2==0) 
			if P_IsObjectOnGround(s) and (p.speed < 40*s.scale) 
				XSon.SmokeMist(s, 2, 149*s.scale, nil, nil, nil, DARKAURA)
			end
			if (leveltime%28==0) and P_RandomRange(0,1)
				local ghs = XSon.ElectricBody(s, nil, SKINCOLOR_CRIMSON)
				ghs.dispoffset = 133
				ghs.frame = (leveltime%56==0) and ($+FF_TRANS20)|FF_HORIZONTALFLIP or $+FF_TRANS20
			end
		end
	end
	if not xs.ManualRushAssault
	and (xs.spin == 1 or s.XSAssaultT!=nil)
	and not P_IsObjectOnGround(s)
		if RB.darkrush 
		or (p.pflags & (PF_JUMPED|PF_THOKKED))
		or (p.panim >= PA_IDLE) and (p.panim <= PA_FALL) and (p.panim != PA_PAIN)
			if not (p.xsringburst or p.xswallrun or (xs.zipdash and ((xs.zipdash < 6) or (p.cmd.buttons & BT_CUSTOM1))))
			and not (p.powers[pw_carry] or P_PlayerInPain(p))
			and not (p.pflags & (PF_STARTDASH|PF_SLIDING|PF_STASIS|PF_FULLSTASIS|PF_JUMPSTASIS))
			and not p.powers[pw_nocontrol] and (s.state != S_PLAY_S_XTRANSFORM) and (s.state != S_PLAY_GASP) and not s.XSassault
				if s.XSAssaultT and s.XSAssaultT.valid and s.XSAssaultT.health
					S_StopSoundByID(s, sfx_s238)
					if not RB.darkrush
						local TARGET = s.XSAssaultT
						XSon.DarkRush(p,s,xs, 101)
						s.XSAssaultT = TARGET
					end
					XSon.DSSlunge(p,s,xs, RB)	
				elseif (p.cmd.buttons & BT_SPIN) and xs.spin == 1
					XSon.AttackBurst(s, 31356+(3000*PHASE), DARKAURA, AST_ADD, FF_TRANS10)
					XSon.Sound(s, sfx_s238, 143) --s236?
					
					RB.AssaultHitBox = 2
					XSon.AssaultHitBox(p,s,xs) --Spawn Hitbox
				end
			end
		end
	end
	if not s.XSlunging
		s.XSAssaultT = nil
		if xs.tossflag==1 and ((s.state==S_PLAY_STND) or (s.state==S_PLAY_WAIT))
			if p.speed==0 and P_IsObjectOnGround(s) and not xs.squat
				s.state = S_PLAY_EDGE		
			end
		end
	end	
	if xs.SonicNoMurdering or p.exiting
		if p.exiting and xs.darkphase==3
			local XYDIST = 5000<<16
			searchBlockmap("objects",function(s,m) --Disintegrate overdue rings.
				if m and m.valid and (m.type==MT_SIGN)
					m.XS_darktagged = true
				end
			end, s, s.x-XYDIST,s.x+XYDIST, s.y-XYDIST, s.y+XYDIST)	
		end
		XSon.DeTransform(p,s,xs)
		xs.darkphase = 0
	elseif (p.rings <= 0)
		if xs.darkphase==3 
		or not ( (P_PlayerInPain(p)) and ((RB.lastrings or 0)>10) )
			XSon.DeTransform(p,s,xs)
			xs.darkphase = 0
		end
	end
end