--╔════════════════════════════╗══════════════════════════════════════════════════════════════════════════════════════════════#
--║    Ring Burst Functions    ║ The most iconic technique from Sonic X! Ring Burst related GFX are not placed here, though!
--╚════════════════════════════╝══════════════════════════════════════════════════════════════════════════════════════════════#
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

--Charge the Ring Burst up?
XSon.RingBurstCharge = function(p, s, xs)	
	if xs.ringburstcd
	or (not p.rings and not xs.PowerRing) 
	or not s.health
	or (p.pflags & (PF_FULLSTASIS|PF_SLIDING))
	or s.noXSHUDthisstage and (G_IsSpecialStage(gamemap)) and not (maptol & TOL_NIGHTS) and not xs.supersonic
	or (s.XS_tutorX) and not xs.PowerRing
		xs.ringburstcharge = 0 return false --
	end
	
	--Holding spin?
	if xs.spin and (p.cmd.buttons & BT_SPIN) and not (xs.spin >= 99<<16) and not (p.cmd.buttons & BT_CUSTOM1)
	
		xs.ringburstcharge = max(0, min($+1, 35)) --Charging...!
		
		if (xs.ringburstcharge > 3) and not xs.poutysonic --Display aura?
			if not P_IsObjectOnGround(s)
			and ((p.pflags & PF_JUMPED) or xs.snapdive or xs.snapdivetrick or p.panim == PA_FALL or p.panim == PA_SPRING)
			and not ((p.panim == PA_IDLE) and (p.panim <= PA_DASH))	
			
				local SUPER = XS_Super(p)
			
				if not ((xs.ringburstcharge >= 34) and (p.cmd.buttons & BT_CUSTOM1))
						local ghs = XSon.SmokeAura(p, s, xs, SKINCOLOR_SKY) --Show the aura.
						if ghs and ghs.valid
							ghs.scale = FixedDiv(FixedMul($, max(10, xs.ringburstcharge)), 18)	
							ghs.frame = ($ & ~FF_TRANSMASK) | min(FF_TRANS20, ((FF_TRANS80)-((xs.ringburstcharge/4)<<16)) )
						end
						if (xs.ringburstcharge > 5) --Animate into Ring Burst smoothly!
							if (p.panim == PA_ROLL) or (p.panim == PA_JUMP) 
							or (p.panim == PA_SPRING) or (p.panim == PA_FALL)				
								if (xs.ringburstcharge > 21) and (xs.ringburstcharge < 25) --Curl up animation
									s.sprite2 = SPR2_MLEE|((SUPER == 3) and FF_SPR2SUPER or 0)
									s.frame = ($ & ~FF_FRAMEMASK)|min(D, (xs.ringburstcharge-21))
									s.tics,s.anim_duration = 2,2
								end
							end	
							if (xs.ringburstcharge > 8)
								if (xs.ringburstcharge % 8 == 0) --Imply the glowing ring in your hand with this!
									XS_RingBurstShine(p, s, xs)
								end
								--Sonic himself starts glowing!
								local ghs = XSon.Overglow(s, 2, SKINCOLOR_SUPERSKY1)
								if ghs and ghs.valid
									ghs.frame = ($ & ~FF_TRANSMASK)|(FF_TRANS90-((xs.ringburstcharge/6)<<16))
								end
							end							
						end		
				
						--Defy gravity and slow down!
						if (s.momz*P_MobjFlip(s) < 5*s.scale)
							if (s.momz*P_MobjFlip(s) < s.scale)
								if not SUPER
									s.momx = FixedDiv(FixedMul($, 14), 15)
									s.momy = FixedDiv(FixedMul($, 14), 15)
								end
								
								if not xs.snapdive
								and not (xs.snapdivetrick and xs.snapdivetrick < 9)
									if SUPER != 3
										local GRAV = P_GetMobjGravity(s) or 32678
										if (s.momz*P_MobjFlip(s) < -2*s.scale)
											s.momz = FixedDiv(FixedMul($, 5), 6)
										else
											s.momz = FixedDiv(FixedMul($, 10), 11)
										end
										P_SetObjectMomZ(s, GRAV*3/2, true)						
										if (xs.ringburstcharge > 18) --Almost there? Then don't touch floors.
											if (s.eflags & MFE_VERTICALFLIP)
												if (s.z+(s.momz*5) > s.ceilingz)
													P_SetObjectMomZ(s, FRACUNIT)
												end
											elseif (s.z+(s.momz*2) < s.floorz)
												P_SetObjectMomZ(s, FRACUNIT)
											end
										end
									end
									--Spawn cloud waves over nearby surfaces.
									if (xs.ringburstcharge % 6 == 0)
										XS_RingBurstDust(p, s, xs, 160*s.scale)	
									end
								end
							elseif not (SUPER > 1)
								s.momx = FixedDiv(FixedMul($, 28), 29)
								s.momy = FixedDiv(FixedMul($, 28), 29)	
							end
						elseif not (SUPER > 1)
							s.momx = FixedDiv(FixedMul($, 65), 66)
							s.momy = FixedDiv(FixedMul($, 65), 66)				
						end
				end
				if (xs.ringburstcharge >= 25) --Ready to do it!
					if not (xs.snapdivetrick and xs.snapdivetrick < 12)
					and not (p.cmd.buttons & BT_CUSTOM1)					
						return true --
					end
				end
			else
				xs.ringburstcharge = min($,7) --Keep charge to some extend.
			end
		end
	elseif xs.ringburstcharge --Count down by each time.
		xs.ringburstcharge = max(0, $-3)
	end
end

--The main Ring Burst ability code!
--TYPE 0 - Failed to ringburst. Cancel if it's active.
--TYPE 1 - Not ringbursting, but will now start!
--TYPE 2 - Currently in the middle of ringburst and not stopping!
XSon.RingBurst = function(p, s, xs)
	if p.xsringburst --Already ringbursting Then, check if we're capable of going on! 	
		if p.exiting or xs.ExitState --Double check for rings.
			XSRingBurstReset(p, s, xs) return 0 --
		end
		local maxNI = (p.xsringburst) and p.xsringburst.maxNI if maxNI==nil MAXNI = 22 end
		
		if (abs(p.cmd.forwardmove)+abs(p.cmd.sidemove) > 22) --Can just keep holding the stick or other buttons at this point.
		or (p.xsringburst and (p.xsringburst.noinput < maxNI)) --Allow a few tics of no input.
		or (p.cmd.buttons & (BT_JUMP|BT_SPIN))
		or p.powers[pw_carry] or xs.ExitState
			if not (xs.custom3 == 1 or (p.cmd.buttons & BT_CUSTOM1)) --Cancel buttons.
				if (p.rings or xs.PowerRing) and not P_PlayerInPain(p) and not p.powers[pw_carry]
					XS_RingBursting(p, s, xs) return 2 --
				end
			end
		end
		XSRingBurstReset(p, s, xs) return 0 --Reset if we got here.
	else--Haven't started yet? Then....
	
		if not p.rings and not xs.PowerRing --Double check for rings.
			XSRingBurstReset(p, s, xs) return  0 --
		end 
		if s.xsoverglow 
			if s.xsoverglow.valid
				s.xsoverglow.state = S_NULL
			end s.xsoverglow = nil
		end
			
		--=================================
		--Set up the table!
		--=================================
		
		p.xsringburst = {
			init = 1, --Real init.
			start = 1, --How long we've ringbursted.
			speed = max(40*s.scale, p.speed/2), --Speed Build, for carrying over to bounces and ground.
			lastmomz = s.momz/2, --Lastmomz, for carrying over to floor/ceiling bounces.
			drainstart = (XS_Super(p)>=2) and 3 or 12,
			jump = (p.cmd.buttons & BT_JUMP) and 99<<16 or 0,
			spin = 99<<16,
			angle = s.angle, --Angle to uphold?
			lockroll = 2,
			rollmult = 5,
			startroll = (p.cmd.sidemove < -4) and -36 or (p.cmd.sidemove > 4) and 36 or 1,
			rollangle = 0,
			rollspeed = 0,
			straightshot = 0,
			wallstress = 0,
			noinput = 0,
			maxNI = 38,
			wallbounce = 0, --Cooldown on wallbounce?
			stripes = 12, --Spawn stripes on startup.
			maxturn = ANG10,
			turnbuff = 0,
			sharpturn = 0,
		--	ghosttrail = 0, 
			sfx = 0, --Sound timer for Ring Burst main loop sound. Resets to 76. (2.1 seconds)
		--	sfx2 = 27,				
			trail = 0, --Should we create a bluetrail?
			xymult = 100, --Modifier to horizontal thrust values.
			zmult = 100, --Modifier to z thrust values.
			STARTSPIN = (p.cmd.buttons & BT_SPIN) and 1 or 0,
			afterdrain = (not (xs.powerring or xs.supersonic or xs.darkphase)) and 44 or 0,
			immunetobounce = 17,
			recordtap = (xs.direct < 6) and 3 or 0,
			tapped = 0,
			TUTORX = (s.XS_tutorX) and true or nil --Enables a true infinite Power Ring.
		}
		
		local RB = p.xsringburst
	
		XSZipDashCancel(p, s, xs)
		XSSnapDiveReset(p, s, xs, true)	
		XSon.DeleteAura(p, s, xs, true, true, true)
		xs.justleaped = 0
		s.rollangle = 0
		if (abs(p.cmd.sidemove) > 5)
			RB.rollangle = (ANG1/2)*p.cmd.sidemove
		end
		xs.leapaftermath = 0
		xs.walljump,xs.walljumpP,p.xswallrun = nil
		xs.quickdrain = (RB.drainstart==12) and 10 or RB.drainstart --NOT on the Ring Burst table, since it carries over after you cancel it.
					
		--Initial boost!
		if (p.speed < 290*s.scale)
			P_Thrust(s, RB.angle, ((p.rings>2) or xs.PowerRing) and 18*s.scale or 28*s.scale) --Starting boost!
		end
		if (s.momz*P_MobjFlip(s) < 30*s.scale) and ((p.rings > 3) or xs.PowerRing)
			if (s.momz*P_MobjFlip(s) < -8*s.scale)
				s.momz = $/2 --Always work in player's favor.
			end
			P_SetObjectMomZ(s, 3<<16, true)
		end
		XSon.RingBurstStartGFX(p,s,xs)
		
		if p.XSRingBurstTutorial == nil and ((p.rings>20) or xs.PowerRing)
		and not ((gametype != GT_COOP) or (modeattacking))
			p.XSRingBurstTutorial = 235
		end
		XS_RingBursting(p, s, xs) return 1--
	end
end

--The main Ring Burst behaviour code! We're past the checks to see if can, so this is all the flying around and spinning!
rawset(_G, "XS_RingBursting", function(p, s, xs)			
	local RB = p.xsringburst --I'll call the Ring Burst table "RB" here.
	local RBSFX = (xs.FourKidsRB) and sfx_xssfpz or sfx_xssfxx
	local SUPER = (XS_Super(p)) or 0
	local TWOD = ((s.flags2 & MF2_TWOD) or twodlevel) and true or false
	if not (p.cmd.buttons & (BT_SPIN|BT_JUMP)) and (abs(p.cmd.forwardmove) < 3) and (abs(p.cmd.sidemove) < 3)
		RB.noinput = $+3
	elseif RB.noinput
		RB.noinput = $-1 
	end
	if TWOD==true
		s.momy = 0 --Brute force ahoy.
		p.cmd.forwardmove = 50		
		RB.nosteering = 10
		RB.rollangle = 0
	end	
	if p.XSRingBurstTutorial
		if xs.tutorials == "all" and not (s.XS_tutor and s.XS_tutor.ID == "ringburst")
			s.XS_tutor = {
			ID = "ringburst",
			b1 = "3", ex1 = "none", 
			b2 = "1", ex2 = "2", 
			text1="/"+"NO INPUT: \x86 Cancel",
			text2=" 2x TAP: \x84 Vertical Burst",
			timelimit = p.XSRingBurstTutorial+70
			}
		else
			p.XSRingBurstTutorial = max($-1,0)
		end
	end
	RB.init = RB.init and $+1 or 1
	if RB.STARTSPIN --After a while, make player go downwards if they never let go off spin.
		RB.STARTSPIN = (p.cmd.buttons & BT_SPIN) and $+1 or 0
		if (RB.STARTSPIN > 35) or (p.cmd.buttons & BT_JUMP)
			RB.spin,xs.spin = 25,25
			RB.STARTSPIN = 0
		end
	end	
	if RB.wallstress --How many times you can bounce before you bonk out of a Ring Burst.
		RB.wallstress = $-1
	end
	if RB.wallbounce --Cooldown for bouncing off walls.
		RB.wallbounce = $-1
	end
	if RB.straightshot --Going straight for how long?
		RB.straightshot = $-1
	end
	if RB.lockroll --Bypass rolling system for a bit.
		RB.lockroll = $-1
	elseif RB.rollmult --Extra rolling speed?
		RB.rollmult = $-1
	end
--	if RB.instaturn
--		RB.instaturn = $-1
--	end
	if RB.turnCD
		RB.turnCD = $-1
		if (RB.turnCD > 20)
			RB.noinput = 0
		end
	end
	if RB.immunetobounce
		RB.immunetobounce = $-1
	end
	if RB.Jimmunebounce
		RB.Jimmunebounce = $-1
	end
	if (RB.maxNI > 27) --MaxNoInput before canceling Ring Burst.
		if RB.jump == 1 or (RB.rocket) or RB.spin==1 and (RB.init > 69)
			RB.maxNI = 22
		end
	end
	RB.sfx = max($-1,0)
	if not RB.sfx --Spam RingBurst SFX.
		RB.sfx = (xs.FourKidsRB) and 108 or 76
		S_StopSoundByID(s, RBSFX) --4kids is sfpz, JP is sfxx.
		local VOLUME if (xs.FourKidsRB) VOLUME = (RB.start>50) and 152 or 167 end
		XSon.Sound(s, RBSFX, VOLUME)
	elseif not S_SoundPlaying(s, RBSFX)
		S_StartSound(s, RBSFX)
	end
	--Speed multipliers are...?
	if SUPER == 3 and not xs.darkphase
	or xs.PowerRing and not (s.eflags & MFE_UNDERWATER) and (xs.darkphase!=3)
		RB.xymult = (s.eflags & MFE_UNDERWATER) and 125 or 104
		RB.zmult = (s.eflags & MFE_UNDERWATER) and 90 or 115
		RB.Radius =  (SUPER==3) and 380*s.scale or 300*s.scale
	else
		RB.Radius = (xs.darkphase==3) and 380*s.scale or 250*s.scale	
		if (s.eflags & MFE_UNDERWATER) and not (xs.darkphase==3)
			RB.xymult,RB.zmult = 109,69
		elseif SUPER != 0
			RB.xymult =  (SUPER>=2) and 103 or 101
			RB.zmult = (SUPER>=2) and 109 or 104
		else
			RB.xymult,RB.zmult = 100,100
		end
	end	
	if RB.rocketjump RB.rocketjump = $-1 end 
	if RB.rocketspin RB.rocketspin = $-1 end
	
	local ZFLIP = P_MobjFlip(s)
	
	--Rocket mode?
	if RB.rocket
		if not (p.cmd.buttons & (BT_JUMP|BT_SPIN))
		or P_IsObjectOnGround(s)
			RB.rocket = 0 RB.rocketjump = 0 RB.rocketspin = 0
		else
			RB.zmult = (s.eflags & MFE_UNDERWATER) and 134 or 200
			if (leveltime % 2 == 0)
				s.sprite2 = SPR2_BNCE|(SUPER==3 and FF_SPR2SUPER or 0)
				XSGhostTrail(p, s, xs, (xs.darkphase) and XSon.DarkAura(0) or SKINCOLOR_SUPERSKY5, 11, FF_TRANS90, FRACUNIT-5500)
				if (abs(s.momz) > 20*s.scale)
					XSon.Quake(s, min(99<<16, abs(s.momz)*3), 3)	
					
					local ghs = XS_CircleWave(s, 2, (xs.darkphase) and XSon.DarkAura(0) or nil, 12, FRACUNIT+15000)
					ghs.sprite = SPR_XSG1
					ghs.renderflags = RF_FLOORSPRITE|RF_NOSPLATROLLANGLE|RF_NOSPLATBILLBOARD|RF_ABSOLUTEOFFSETS|RF_NOCOLORMAPS|RF_FULLBRIGHT
					ghs.flags2 = $|MF2_SPLAT
					ghs.momx,ghs.momy = s.momx/4,s.momy/4
					ghs.tracer = nil
					ghs.spritexoffset = 59<<16
					ghs.spriteyoffset = 56<<16
					ghs.frame = R|FF_TRANS50
				--	ghs.sttroll = ANG30		
					ghs.z = $+(s.momz)
					if RB.rocket == 1
						ghs.z = $+(64*s.scale*ZFLIP)
					end
					P_CreateFloorSpriteSlope(ghs)
					ghs.floorspriteslope.o = {x = s.x, y = s.y, z = s.z}
					ghs.floorspriteslope.xydirection = s.angle
					ghs.floorspriteslope.zangle = -ANG20*2
				end
			end
		end
	else --Activate Rocket Mode?
		local DOROCKET = false
		if RB.jump == 1
			if RB.rocketjump
				DOROCKET = 1
			else
				RB.rocketjump = 8				
			end
			RB.rocketspin = 0
		elseif RB.spin == 1
			if RB.rocketspin
				DOROCKET = -1
			else
				RB.rocketspin = 8
			end
			if not RB.jump
				RB.rocketjump = 0
			end
		end
		if DOROCKET != false
			XSon.Quake(s, 90<<16, 7)
			RB.rocketjump,RB.rocketspin = 0,0
			RB.tapped,RB.recordtap = 0,0
			RB.rocket = DOROCKET
			RB.xymult = 8
			if not (xs.FourKidsRB)
				RB.sfx = 0
				S_StopSoundByID(s, sfx_xssfxx) --Don't stop the 4kids ring burst one.
			end
			S_StartSoundAtVolume(s, sfx_kc5b, 80)
			S_StartSoundAtVolume(s, sfx_kc4d, 70)
		--	S_StartSoundAtVolume(s, sfx_kc47, 69)
			DOROCKET = $*ZFLIP				
			if DOROCKET == -1
				local ZMOMMULT1 = FixedDiv(FixedMul(80*s.scale, RB.zmult), 100)
				local ZMOMMULT2 = FixedDiv(FixedMul(29*s.scale, RB.zmult), 100)
				s.momz = min(0, max(-ZMOMMULT1, $-ZMOMMULT2))
			else
				local ZMOMMULT1 = FixedDiv(FixedMul(80*s.scale, RB.zmult), 100)
				local ZMOMMULT2 = FixedDiv(FixedMul(25*s.scale, RB.zmult), 100)				
				s.momz = min(ZMOMMULT1, $+ZMOMMULT2)
				s.momx = $/2 s.momy = $/2
			end
			local ADDDRAIN = (DOROCKET == -1) and 4 or 12
			if (xs.quickdrain < ADDDRAIN)
				xs.quickdrain = ADDDRAIN
			end
			if ((abs(p.cmd.sidemove) > 20) or (p.cmd.forwardmove < 0)) and (xs.direct > 20)  --Initial burst of speed!
				local DIRECT = min(50, (abs(p.cmd.sidemove)+p.cmd.forwardmove*-1) )
				P_Thrust(s, xs.turnang, ((p.speed < 77*s.scale) and (RB.start > 16)) and DIRECT*s.scale/2 or DIRECT*(s.scale/3))
			end
			local ghs = XSon.TransformGFX(s)
			ghs.rollangle = (DOROCKET==1) and ANGLE_180 or 0
			ghs.frame = $+FF_TRANS40
			ghs.fuse = $-6
			
			XSSpawnDust(s, nil, nil, true)
			local ghs = XS_CircleWave(s, 2, (xs.darkphase) and XSon.DarkAura(0) or SKINCOLOR_SUPERGOLD5, 12, FRACUNIT+15000)
			ghs.frame = $+FF_TRANS40
			
			XS_RingBurstShine(p, s, xs)	
			
		/*	local ghs = XS_RingBurstWave(s)
			if ghs and ghs.valid
				ghs.renderflags = RF_FLOORSPRITE|RF_NOSPLATROLLANGLE|RF_NOSPLATBILLBOARD|RF_ABSOLUTEOFFSETS
				ghs.flags2 = $|MF2_SPLAT
				ghs.tracer = nil
				ghs.spritexoffset = 59<<16
				ghs.spriteyoffset = 56<<16
				ghs.z = $+(s.momz)
				if DOROCKET == 1
					ghs.z = $+(120*s.scale)
				end
			end */
		end
	end
	
	--Lock up standard player controls.
	xs.jump,xs.spin = 99<<16, 99<<16
	xs.ringburstcharge = 0
	p.pflags = $|PF_THOKKED|PF_JUMPDOWN|PF_USEDOWN & ~(PF_JUMPED|PF_NOJUMPDAMAGE|PF_SPINNING|PF_GLIDING|PF_SLIDING)
	p.panim = PA_ROLL --Treat this like rolling frames, even if we're not.
	xs.changedcharflags = 2
	s.flags2 = $|MF2_DONTDRAW --Don't draw the player, only the ball!
	if (s.state != S_PLAY_ROLL) and not P_IsObjectOnGround(s)
		s.state = S_PLAY_ROLL
	end
	if s.XSSuperRadius
		P_RadiusAttack(s, s, s.XSSuperRadius, DMG_NUKE, true)
		s.XSSuperRadius = nil
	end
	--Now the main part!
	if RB.TapBurst
		RB.TapBurst = $-1
	else
		XS_RingBurstSparkles(s, 16, nil, 240)
		if RB.trail --Spawn ziptrail?
			RB.trail = $-1
			XSZipTrail(s, nil, 1)
		end
	end
	if (RB.start%2)==0 --Regular Ring Burst trail!	
		
		XSon.RingBurstAfterImages(p,s,xs, RB)
		
		if (RB.start%16)==0 --Spawn a burst wave every so often.
		and not P_IsObjectOnGround(s) and (p.speed > 36*s.scale)
			if RB.lastwave == nil RB.lastwave = {} end
			local ths = XS_RingBurstWave(s)
			if (p == displayplayer) and (RB.start > 33) and (abs(s.momz) < 50*s.scale)
			and (p.speed > 88*s.scale) and not RB.rocket
				ths.flags2 = $|MF2_SHADOW --Oof, quit flashbanging me
			end
			table.insert(RB.lastwave, ths)
		end
	end
	
	--On the ground? Create quakes and spawn rocks!
	if P_IsObjectOnGround(s)	
	
		RB.groundtime = RB.groundtime and $+1 or 1
		
		--Speed we should be going at.
		if not (p.cmd.forwardmove > 34)
			RB.speed = (RB.speed < 39*s.scale) and 39*s.scale or (RB.speed < 64*s.scale) and $+s.scale/2
			or min(150*s.scale, $+(s.scale/3))
		else
			RB.speed = (RB.speed < 54*s.scale) and 54*s.scale or (RB.speed < 84*s.scale) and $+s.scale/2
			or min(150*s.scale, $+(s.scale/3))			
		end
		if (p.speed > 16*s.scale) or (xs.direct > 4)
			XS_RingBurstStripes(s, nil, AST_ADD)
		end
		
		--Attach to the ground like a chainsaw!
		if (leveltime % 2 == 0) and RB.groundtime != 1
			if (p.speed > 34*s.scale) or (leveltime%6==0)
				for i = -1,1 --Fling rocks around!
					local ghs = XS_CraterSpew(s, s.angle+(ANG60*i), 9)
					if ghs and ghs.valid
						ghs.momx,ghs.momy = $*5,$*5
						ghs.tics,ghs.fuse = 45,44
					end
				end
			end
			if (leveltime % 4 == 0) --Impact dust in front of you.
				local ths,ghs = XS_QuickDust(s, SKINCOLOR_SUPERORANGE2, AST_ADD, nil, s)
				ths.frame = ($ & ~FF_TRANSMASK)|FF_TRANS30
				ghs.frame = ($ & ~FF_TRANSMASK)|FF_TRANS30
			end								
		else --Thrust only every 2nd frame for a bit of realistic chunkiness.
			local NEWSPEED = RB.speed
			if not (xs.direct < 5)
				if TWOD==true or (p.cmd.forwardmove > 34) --Nyooooom!
					P_InstaThrust(s, s.angle, NEWSPEED)		
				elseif (p.speed < NEWSPEED)
					P_Thrust(s, xs.turnang, (NEWSPEED-p.speed)/3)
				end
				local trueturn = (XSon.MOMANGLE(s)-xs.turnang) 
				if trueturn != 0 trueturn = abs($/ANG1) end	
				if (trueturn > 2)
					RB.immunetobounce = 3
					XSon.FreeFlightVector(s, xs.turnang, max(1, (trueturn/20)), (trueturn > 64) and 3 or 4)
				end
			elseif (p.speed < NEWSPEED/24)
				P_InstaThrust(s, s.angle, NEWSPEED/24)	
			end
			XSon.Quake(s, max(60<<16, min(10<<16, p.speed/2)), 3)
			P_RadiusAttack(s, s, min(RB.Radius*2, (RB.Radius>330*s.scale) and RB.speed*4 or RB.speed*2), DMG_NUKE, true)
			local ghs = XS_CraterSpew(s, s.angle+ANGLE_180, 12)	
			if ghs and ghs.valid
				ghs.tics,ghs.fuse = 33,30
			end
			if (p.cmd.buttons & BT_SPIN)
				RB.lastmomz = FixedDiv(FixedMul($,11), 12)
				if (abs(RB.lastmomz) < 2*s.scale)
					RB.lastmomz = 2*s.scale
				end
				RB.forcejump,RB.forcespin = 0,0
			end
		end
		if (s.eflags & MFE_JUSTHITFLOOR) --Dig into the floor on impact!	
			if (leveltime % 2 != 0)
				P_RadiusAttack(s, s, RB.Radius+(50*s.scale), DMG_NUKE, true)
			end
			XS_QuickDust(s, SKINCOLOR_WHITE)
			if not s.XSstretch
				s.spritexscale = FRACUNIT+20100
				s.spriteyscale = FRACUNIT-25100
				s.spriteyoffset = -4<<16
				s.XSstretch = {timer = 5, Xspeed = 4200, Yspeed = 5900}
			end
		end
		if TWOD!=true
			local ANGLE,PLUS = XS_AngleDifference(RB.angle, xs.turnang, 2, ANG10*5)
			RB.rollangle = (ANGLE*PLUS*-3)-(p.cmd.sidemove*(ANG1/2)) --Directly tilt in whatever angle you're holding.
		else
			RB.rollangle = -(p.cmd.sidemove*(ANG1*2/3))
		end
		RB.rollspeed = 0
		RB.straightshot = 0
		RB.stripes = 0
		RB.tapped,RB.recordtap = 0,0
		
		XSon.Bounce(p, s, xs)
		RB.angle = s.angle --On the ground, you get incredibly tight control, but less max speed
		
	else --if not P_IsObjectOnGround(s)
	
		if TWOD != true and not (RB.bounced and RB.bounced > 6)
			local FTAP = (p.cmd.forwardmove > 8) and 1 or (p.cmd.forwardmove < -8) and -1 or 0	
			if RB.tapped --Record a tap input? If it's 9, then we tapped 2x!
				RB.tapped = $-1
			end
			if (xs.direct < 10)
				RB.recordtap = 5
			end
			if RB.recordtap
				RB.recordtap = $-1
				if (xs.direct >= 34)
					if RB.tapped and (FTAP == RB.FTAP or FTAP == 0)
						RB.tapped = 10
						xs.direct = 50
					else
						RB.tapped = 8
					end
					RB.FTAP = FTAP
					RB.recordtap = 0
				end
			end	
		end
		
		RB.groundtime = 0	
		
		if RB.nosteering
			RB.nosteering = $-1
			s.angle = RB.angle
			p.drawangle = RB.angle			
		else
			if RB.turndebuff
				RB.turndebuff = $-1		
			elseif RB.turnbuff
				RB.turnbuff = $-1
			end
			RB.maxturn = (RB.turndebuff) and 99 or (RB.turnbuff > 11) and ANG10*6 or (RB.turnbuff) and ANG10*5 or ANG30
			if not RB.turndebuff and not RB.bounced and ((abs(RB.sharpturn) > 20) and (abs(p.cmd.sidemove) > 20) or (RB.spin == 1 or RB.jump == 1) )
				RB.maxturn = $+ANG10
			end
			--In the air, plane-style steering is on! Gradually turn Sonic overtime.
			local ANGLE,PLUS = XS_AngleDifference(RB.angle, (p.cmd.angleturn<<16), 2, RB.maxturn)
			if RB.rollmult and not RB.lockroll
				ANGLE = $*2
				PLUS = $*-1
			end
			RB.angle = $-(ANGLE*PLUS)	
			s.angle = RB.angle
			if (p.pflags & (PF_DIRECTIONCHAR|PF_ANALOGMODE))
				p.drawangle = RB.angle
			end
		
			if (ANGLE < ANG1*3) and (abs(p.cmd.sidemove) < 8)
				RB.straightshot = $+3
			elseif ANGLE and (p.pflags & PF_ANALOGMODE)
				p.cmd.angleturn = $+((ANGLE*PLUS*2)>>15)
			end
		
			--Cool flips and rolling stuff.
		
			if not RB.lockroll
				RB.rollspeed = $-(((ANGLE*PLUS) or 1)/2)-(p.cmd.sidemove*(ANG1/30))
			end
			if (RB.rollspeed > -ANG1*2) and (RB.rollspeed < ANG1*2)
				RB.rollspeed = 0
			end
			if RB.rollspeed
		
				local MAXANG = (RB.rollmult) and ANG10*5 or ANG30
				
				if (RB.rollspeed < 0)
					RB.rollspeed = max($, -MAXANG)
				else
					RB.rollspeed = min($, MAXANG)		
				end
				
				if not RB.lockroll					
					--Lock Sonic to tilting only or let him roll entirely in 360 degrees?
					--Always finish an-going twirl, though!
					local LOCKANG = ((ANGLE < ANG1*5) and not (abs(s.momz) > 18*s.scale)) and 1 or 2
					if (LOCKANG == 1) 
					and not (RB.rollangle < -ANG10*5) 
					and not (RB.rollangle > ANG10*5)
						if (RB.rollangle < 0)
							RB.rollangle = max($+(RB.rollspeed), -ANG10*4)
						else
							RB.rollangle = min($+(RB.rollspeed), ANG10*4)
						end
						RB.rollspeed = FixedDiv(FixedMul($,4), 5)
					else
						RB.rollangle = $+(RB.rollspeed)
						RB.rollspeed = FixedDiv(FixedMul($,6), 7)
					end
					
				end
			else
				if (RB.rollangle > -ANG1*15) and (RB.rollangle < ANG1*15)
					RB.rollangle = 0
				else
					RB.rollangle = FixedDiv(FixedMul($,10), 11) --Even out over time.
				end
			end
		end
		
		--Special twirling behaviour when the ring burst just started.
		if RB.startroll
			if (RB.startroll < 0)
				RB.startroll = $+1
				RB.rollangle = $+(ANG1*15)
			else
				RB.startroll = $-1
				RB.rollangle = $-(ANG1*15)
			end
			if (RB.straightshot > 6) and (abs(RB.startroll) < 22)
				RB.startroll = 0
			end
		end
	--	s.rollangle = RB.rollangle
		
		RB.lastmomz = s.momz
		RB.speed = max($, 40*s.scale) 
		if (p.speed > RB.speed)
			RB.speed = p.speed-(10*s.scale)
		end
		P_RadiusAttack(s, s, min((RB.Radius/2)+30*s.scale, RB.speed), DMG_NUKE, true)
		
		if (s.eflags & MFE_VERTICALFLIP)
			if (abs((s.z+s.momz)-s.floorz) < 55*s.scale) and s.momz < -s.scale
				XSon.Bounce(p, s, xs, nil, true)
			end
		elseif (abs((s.z+s.momz)-s.ceilingz) < 55*s.scale) and s.momz > s.scale
			XSon.Bounce(p, s, xs, nil, true)
		end
		if RB.stripes
			XS_RingBurstStripes(s, nil, AST_ADD, (RB.start < 10) and 80<<16 or 40<<16)
			RB.stripes = $-1
			if (RB.start < 16) and (RB.start % 3 == 0)
				XS_RingBurstDust(p, s, xs, 180*s.scale)	
			end
		end
	end
	--Windlines.
	if (RB.start%2) and (p.speed > 60*s.scale) and (RB.start > 4) 
		local MINXY = FixedDiv(FixedMul(p.speed, 3), 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, SUPER and SKINCOLOR_FLAME or SKINCOLOR_SUPERSKY3)
		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, SUPER and s.color or SKINCOLOR_SKY)
	end
	
	--Vertical control.
	if not (p.cmd.buttons & (BT_JUMP|BT_SPIN)) --No buttons, then float stable!
	or (RB.spin >= 99<<16) and (p.cmd.buttons & BT_SPIN)
	or (RB.jump >= 99<<16) and (p.cmd.buttons & BT_JUMP)		
	or (abs(s.momz) > 80*s.scale) --Or we've reached the max z speed...
		if not P_IsObjectOnGround(s)
			if (abs(s.momz) > 24*s.scale) or (abs(s.momz) < 3*s.scale)
				s.momz = FixedDiv(FixedMul($, 25), 26) --Divide momz
			else
				s.momz = FixedDiv(FixedMul($, 35), 36) --Divide momz
			end
			if (s.momz*ZFLIP < s.scale)
				P_SetObjectMomZ(s, FRACUNIT, true) --But float a bit.
			end
		else
			s.momz = -1*ZFLIP
		end
		
	elseif (abs(s.momz) < 80*s.scale) --Rise or fall!
	
		local SHIFT = (RB.zmult != 100) and FixedDiv(FixedMul(FRACUNIT,RB.zmult), 100) or FRACUNIT
		
		if (p.cmd.buttons & BT_JUMP) --Going up!
			if (s.momz*ZFLIP < 0)
				if (abs(s.momz) > 25*s.scale) or (abs(s.momz) < 3*s.scale)
					s.momz = FixedDiv(FixedMul($, 100), 120)
				else
					s.momz = FixedDiv(FixedMul($, 100), 111) --Divide momz
				end				
			end
			if (RB.jump == 1)
				if (s.momz*ZFLIP < 12*s.scale)
					P_SetObjectMomZ(s, 7*SHIFT, true)
				end
				S_StartSound(s, sfx_xssfxv)
				local SFX = P_SpawnMobjFromMobj(s,s.momx,s.momy,s.momz,MT_THOK)
				SFX.flags2 = MF2_DONTDRAW SFX.fuse = 44 SFX.tics = 44 
				S_StartSound(SFX, sfx_xssfxv)
				RB.spin = 0
			end
			P_SetObjectMomZ(s, SHIFT, true)
			
		elseif (p.cmd.buttons & BT_SPIN) and not P_IsObjectOnGround(s)
			if (s.momz*ZFLIP > 0)
				if (abs(s.momz) > 25*s.scale) or (abs(s.momz) < 3*s.scale)
					s.momz = FixedDiv(FixedMul($, 100), 120)
				else
					s.momz = FixedDiv(FixedMul($, 100), 111) --Divide momz
				end
			end
			if (RB.spin == 1)
				if (s.momz*ZFLIP > -12*s.scale)
					P_SetObjectMomZ(s, -10*SHIFT, true)	
				end
				RB.jump = 0
				XSon.Sound(s, sfx_xssfpb, 136)
			end
			P_SetObjectMomZ(s, -SHIFT, true)
		end
	end
	
	if (xs.direct > 18)
		if (RB.tapped == 9 or RB.tapped == 10) and not (RB.TapBurst and RB.TapBurst > 13) and TWOD!=true
			local trueturn = (XSon.MOMANGLE(s)-xs.turnang) 
			if trueturn != 0 trueturn = abs($/ANG1) end	
			if (trueturn > 18) --Can't be in the same direction you're already going!		
				local THRUST = s.scale*20
				RB.tapped,RB.recordtap = 0,0
				if not (xs.FourKidsRB)
					RB.sfx = 0
					S_StopSoundByID(s, sfx_xssfxx) --Don't stop the 4kids ring burst one.
				end
				S_StartSoundAtVolume(s, sfx_kc5b, 80)
				S_StartSoundAtVolume(s, sfx_kc4d, 70)
				XSon.Sound(s, sfx_thok, 20)
				
				XSon.Quake(s, 74<<16, 4)
				
				local ghs = XS_CircleWave(s, 1, SKINCOLOR_GREY, 10, 70365)
				ghs.frame = E|FF_TRANS70
				ghs.spriteyoffset = $-20<<16
				
				local RNG1 = XSon.RNG(3,10,s)
				XSon.sparkles(p,s,xs, RNG1*3, RNG1, (XSon.RNG(-14,14,s)) or 8, (s.momz*2/3)+7<<16, (xs.darkphase) and SKINCOLOR_LAVENDER or SKINCOLOR_SILVER, (not xs.darkphase) and AST_ADD or AST_TRANSLUCENT, FF_TRANS20, 32-RNG1)
				
				for i = 32,46,13
					local ghs = XS_RingBurstWave(s, 11, (i==32) and SKINCOLOR_GREY or SKINCOLOR_ICY)
					ghs.ditchtracer = nil
					ghs.sttfade = true
					ghs.GSoffsetX,ghs.GSoffsetY,ghs.GSoffsetZ = i*s.scale,i*s.scale,(i/2)*s.scale
					ghs.spritexscale = (i == 32) and $*2/3 or $/2
					ghs.spriteyscale = ghs.spritexscale
				end
				if (p.cmd.forwardmove < -36)
					RB.turnCD = 25
					RB.turndebuff = 9		
					P_InstaThrust(s, xs.turnang, 92*s.scale)
					s.angle = xs.turnang
					p.drawangle = s.angle
					p.cmd.angleturn = s.angle>>16
					RB.angle = s.angle
					RB.rollangle = 0
				elseif (p.speed+(s.scale*20) <= 64*s.scale)
					P_InstaThrust(s, xs.turnang, 64*s.scale)
				elseif (p.speed < 320*s.scale)
					P_Thrust(s, xs.turnang, s.scale*20)
				end
				RB.maxNI = max($, 32)
				RB.immunetobounce = 3
				RB.noinput = 0
				RB.TapBurst = 20
				XS_RingBurstDust(p, s, xs, 200*s.scale)
				XSon.sparkles(p,s,xs, 50, 3, 3, 4<<16, SKINCOLOR_GREY, AST_ADD)
				XS_RingBurstStripes(s, s.color, AST_ADD, -16<<16, nil, p.drawangle)
				XS_MediumDust(s, SKINCOLOR_SILVER, AST_ADD, Q|FF_TRANS60)
				
			end
					
		elseif (xs.direct > 25)
		and not RB.bounced and not RB.turndebuff and not RB.nosteering and (RB.start > 20)
		and not P_IsObjectOnGround(s)
			if RB.turnCD == 21
				s.angle = XSon.MOMANGLE(s)
				p.drawangle = XSon.MOMANGLE(s)
				RB.angle = s.angle
			end
			--Allow quick turnarounds when you're going slowly enough. "turnaround"
			if ( (abs(p.cmd.sidemove) > 30) and (p.cmd.forwardmove <= 8) and (p.speed < 67*s.scale)
			or (p.cmd.forwardmove < -20) and (p.speed < 123*s.scale) )
				if (p.cmd.forwardmove >= 0)
					local MOMANG = R_PointToAngle2(s.x, s.y, s.x+s.momx, s.y+s.momy)
					P_Thrust(s, MOMANG+((p.cmd.sidemove<0) and ANG10*11 or -ANG10*11), (p.speed<62*s.scale) and 6*s.scale or 4*s.scale)
				else
					P_Thrust(s, xs.turnang, (p.speed<62*s.scale) and 6*s.scale or 4*s.scale)
				end
				--Turn 180 degrees instantly.
			/*	if (p.speed < 16*s.scale)
				and RB.instaturn
				and ((p.cmd.forwardmove < -40) or (p.cmd.forwardmove < -8) and (abs(p.cmd.sidemove) < 4))
				and not RB.turnCD and not RB.rocket
				and not (RB.jump and RB.jump < 96) and not (RB.spin and RB.spin < 96)
					RB.turnCD = 24 RB.turndebuff = 9 -- RB.nosteering = 9
					RB.noinput = 0
					RB.rollangle = (p.cmd.sidemove<0) and -ANG60 or ANG60
					RB.rollangle = $/2
				--	s.rollangle = RB.rollangle/2
					XSon.Sound(s, sfx_xssfpb, 136)
					RB.angle = s.angle+ANGLE_180
					p.cmd.angleturn = RB.angle>>16
					s.angle = RB.angle
					P_InstaThrust(s, RB.angle, 48*s.scale)
					RB.instaturn = nil */
				else				
					RB.angle = s.angle --On the ground, you get incredibly tight control, but less max speed
			--	end
				p.drawangle = RB.angle
			end
		end	
	end
	--Horizontal control.
	if (p.cmd.forwardmove < -39) --Player's trying to brake, quit thrusting forwards!
		if not (RB.turnCD and RB.turnCD > 21)
			s.momx = FixedDiv(FixedMul($, 15), 16)
			s.momy = FixedDiv(FixedMul($, 15), 16)
		end	
	elseif (p.speed < 333*s.scale) and not P_IsObjectOnGround(s) --Thrust forwards with a cap of...uhh, 333?
	
		local THRUST = s.scale
		THRUST = (p.speed < 62*$) and $*3+($/2)
		or (p.speed < 76*$) and $*3
		or (p.speed < 91*$) and $*2+($/2)
		or (p.speed < 105*$) and $*2-($/9)
		or (p.speed < 128*$) and $+(($/3)*2) --166%
		or (p.speed < 158*$) and $+($/2)
		or (p.speed < 220*$) and $+($/3)
		or $-($/3)
		
		local SIDE = abs(p.cmd.sidemove)
		local FORWARD = abs(p.cmd.forwardmove)
		local THRUSTANG = RB.angle
		if TWOD == true
			FORWARD,SIDE = 0,0
		end
		local THRUSTMULT = RB.xymult+(SIDE and SIDE/8 or 0)
		if ((p.cmd.buttons & BT_JUMP) or RB.jump or RB.floorbounced) and (s.momz*ZFLIP > -s.scale)
			if (p.speed > 30*s.scale) and not RB.rocket
				THRUSTMULT = ((p.speed>56*s.scale) and $/3 or $/2)+(SIDE/2) --Much slower accel when rising, unless holding sideways.
			end
		end
		if TWOD != true and not RB.turndebuff and not RB.nosteering
			if (p.cmd.forwardmove >= 38) --Full speed ahead? Yeeesss!
				if (p.speed < 250*s.scale) and (SIDE < 35) and not RB.floorbounced and not RB.bounced
					THRUSTMULT = $+ (FORWARD/2) * ((p.powers[pw_super]) and 3 or 1)
				end
			elseif (SIDE > 5) and (RB.start > 16) and not RB.bounced
				if (p.speed > 99*s.scale) and (SIDE > 15)
					P_Thrust(s, R_PointToAngle2(s.x, s.y, s.x+s.momx, s.y+s.momy), -(s.scale/50)*SIDE) 
				end
				local EMULT = SIDE
				local ADDA = ANG1*4/3
				if (p.cmd.forwardmove < -2)
					ADDA = FixedDiv(FixedMul($, 50+FORWARD), 50)
					EMULT = $+FORWARD
				end
				THRUSTANG = $-(ADDA*p.cmd.sidemove)
				THRUSTMULT = $+EMULT --Accelerate sideways faster at the cost of forwards speed.
			end			
		end
		if not xs.direct and (p.speed > 60*s.scale)
			THRUST = ($*2)/3
		end
		if THRUSTMULT != 100
			THRUST = FixedDiv(FixedMul($, THRUSTMULT), 100)
		end
		
		--Now change our momentum. Thrust in half of your input direction, half of your old momentum.
		P_Thrust(s, THRUSTANG, THRUST)
	--	if (p.cmd.forwardmove < 16) and (p.cmd.forwardmove > -40) and (SIDE > 3)
	--		P_Thrust(s, RB.angle+((p.cmd.sidemove < 0) and ANG10*10 or -ANG10*10), SIDE*(s.scale/10) )
	--	end
		if RB.TapBurst and (xs.direct > 10) and (p.speed < 220*s.scale) --Tap Burst mechanics.
			local trueturn = (XSon.MOMANGLE(s)-xs.turnang) 
			if trueturn != 0 trueturn = abs($/ANG1) end	
			if (trueturn > 30)
				P_Thrust(s, xs.turnang, (p.speed < 69*s.scale) and s.scale*4/3 or (p.speed < 104*s.scale) and s.scale*2/3 or s.scale/2)
				if (p.speed > 16*s.scale) --Nowa also redirect!
					XSon.FreeFlightVector(s, xs.turnang, max(1, (trueturn/20)), (trueturn > 64) and 3 or 4)
				end	
			end
		end	
	end
	--When you've picked up some speed, allow sharp turning when flicking the stick!
	if (abs(p.cmd.sidemove) > 20) and (p.speed > 76*s.scale)
	and (RB.start > 9) and not P_IsObjectOnGround(s) and not RB.bounced
		if (p.cmd.sidemove < 0)
			RB.sharpturn = min($-1,-6)
		else
			RB.sharpturn = max($+1,6)
		end
	elseif RB.sharpturn
		RB.sharpturn = max(-6, min(6,$))
		if (RB.sharpturn < 0)
			RB.sharpturn = $+1
		elseif RB.sharpturn
			RB.sharpturn = $-1
		end
	end	
	
	RB.start = $+1 --Timer for how long we've been going.
	xs.ringburstanim = 2
	
	--Drain the Power Ring first instead of your ring supply!
	
	if SUPER != 3 and not (RB.DidKill and xs.darkphase)
		if xs.PowerRing
			if not P_IsObjectOnGround(s) and not (RB.floorbounced and RB.floorbounced > 8) and not RB.DidKill
			or (leveltime%2)
				if RB.TUTORX != true
					xs.PowerRing = max($-1,0)	 --Ring Drain speed after the initial burst?
					if xs.PowerRing == 0 --Uh-oh!
						S_StartSound(s, sfx_antiri) S_StartSound(nil, sfx_antiri, p)
						s.XS_pwringget = -6
						xs.quickdrain = 10
						RB.drainstart = 12
					end
				end
			end
		elseif not xs.quickdrain --This stat takes prevalance at first.
		and not p.exiting
			local DRAIN = 6
			local RBSPEED = p.speed+abs(s.momz)
			if (RB.start < (76+RB.drainstart)) and SUPER==0 
				DRAIN = $-1 --Drain the slowest after you've been ringbursting for a while.
			end
			if RB.DidKill
				DRAIN = $+2
			end
			if not P_IsObjectOnGround(s) and not (RB.floorbounced and (RB.floorbounced > 7))
				if ((xs.TotalRings or 0) > 250)
					DRAIN = $-1
					if (xs.TotalRings > 420) --So many rings...!
						DRAIN = $-1
					end
				end
				if not RB.bounced --Drain more when rocketing or moving at extreme speeds.
				and ((RBSPEED > 175*s.scale) or RB.rocket or RB.TapBurst)
					DRAIN = (RBSPEED > 240*s.scale) and $-2 or $-1
				elseif s.momz and (s.momz*ZFLIP > s.scale*((p.cmd.buttons & BT_JUMP) and -5 or 7) )
					DRAIN = $-1 --Rising or TapBursting also drains rings.
				end
			else
				DRAIN = (P_IsObjectOnGround(s)) and $+2 or $+1 --Drain less on the ground.
			end
			
			if (RB.start % (max(2, DRAIN)) == 0)
				p.rings = max($-1,0)
			end
		end
	end
	
	if (p.speed < 50*s.scale) or TWOD==true
		RB.lockroll = 6
	end
	--Register buttons BEFORE we remove inputs, naturally.	
	RB.spin = ((p.cmd.buttons & BT_SPIN) or RB.forcespin) and $+1 or 0
	RB.jump = ((p.cmd.buttons & BT_JUMP) or RB.forcejump) and $+1 or 0
	if RB.forcejump
		RB.forcejump = $-1
		RB.spin = 0
	end
	if RB.forcespin
		RB.forcespin = $-1
		RB.jump = 0
	end
	if RB.bounced
		RB.bounced = $-1
	end
	if RB.floorbounced
		RB.floorbounced = $-1
	end
	if RB.DidKill
		RB.DidKill = $-1
	end
	xs.rollcd = 2
	
	--Stats.
	p.charflags = $|SF_NOSKID & ~SF_RUNONWATER
	p.powers[pw_strong] = (s.momz*ZFLIP < 0) and STR_BUST|STR_ATTACK|STR_SPIKE|STR_SPRING|STR_HEAVY or STR_BUST|STR_ATTACK|STR_SPIKE
	return 2 --We did it!
end)

--Return a player-preferred angle! Assumes player and player.xsonictable are both valid!
--0/nil = Context dependent: Use momentum angle if available, otherwise use input angle, otherwise use mo.angle. 
--1 = Prioritize player input angle exclusively. With no input, uses mo.angle
--2 = Force player momentum angle.
--3 = Return a mix of momentum angle and input angle.
local function XS_PlayerAngle(s, STYLE)
	local p = s.player local xs = p and p.xsonictable
	if not STYLE --
		if s.momx or s.momy
			return R_PointToAngle2(s.x, s.y, s.x+s.momx, s.y+s.momy) --
		elseif (abs(p.cmd.sidemove) > 3) or (abs(p.cmd.forwardmove) > 3)
			return (p.cmd.angleturn<<16)+(R_PointToAngle2(0, 0, p.cmd.forwardmove<<16, -(p.cmd.sidemove<<16)))
		end
	elseif STYLE == 1 --Tries to force input angle. Without input, uses mo.angle.
		if (abs(p.cmd.sidemove) > 3) or (abs(p.cmd.forwardmove) > 3)
			return (p.cmd.angleturn<<16)+(R_PointToAngle2(0, 0, p.cmd.forwardmove<<16, -(p.cmd.sidemove<<16)))
		end
	elseif STYLE == 2 --Forces momentum angle.
		return R_PointToAngle2(s.x, s.y, s.x+s.momx, s.y+s.momy) --
	elseif STYLE == 3 --Mixes momentum angle and input angle. Falls back to mo.angle if neither are present.
		local ANGLE,ANGLE2
		if (s.momx or s.momy)
			ANGLE = R_PointToAngle2(s.x, s.y, s.x+s.momx, s.y+s.momy)
		end
		if ( (abs(p.cmd.sidemove) > 3) or (abs(p.cmd.forwardmove) > 3) )
			ANGLE2 = (p.cmd.angleturn<<16)+(R_PointToAngle2(0, 0, p.cmd.forwardmove<<16, -(p.cmd.sidemove<<16)))
		end
		if ANGLE != nil
			if ANGLE2 != nil
				local ANGLEFIN,PLUS = XS_AngleDifference(ANGLE, ANGLE2, 2)
				return ANGLE + (ANGLEFIN*PLUS) --Mixture!
			else
				return ANGLE --
			end
		elseif ANGLE2 != nil
			return ANGLE2 --
		end
	end
	return s.angle --
end

--Bounce off a wall, floor, or ceiling. If line isn't present, look for ground or ceiling instead.
XSon.Bounce = function(p, s, xs, line, ceiling)
	local DOBOUNCE = false	
	local RB = p.xsringburst
	
	if RB.rocket
		p.cmd.buttons = $ & ~ BT_SPIN
	end
	if line --Collissions on walls.
	
		if RB.wallbounce return end --	
		
		local x,y = P_ClosestPointOnLine(s.x, s.y, line)
		local BOUNCEANGLE = R_PointToAngle2(s.x, s.y, x, y)
		local MAXANG = (xs.direct > 30) and ANG10*10 or ANG60
		local ANGSHIFT,PLUS = XS_AngleDifference(BOUNCEANGLE, XS_PlayerAngle(s), 2, MAXANG)
		if not (p.cmd.forwardmove < -40)
			BOUNCEANGLE = $+(ANGSHIFT*PLUS)	
		end
		if (ANGSHIFT >= MAXANG)
			RB.turndebuff = 3
		end
		DOBOUNCE = true
		if (p.pflags & (PF_ANALOGMODE|PF_DIRECTIONCHAR))
			p.drawangle = BOUNCEANGLE+ANGLE_180
		end
		s.angle = BOUNCEANGLE+ANGLE_180	
		if s.momz
			s.momz = (p.cmd.buttons & (BT_JUMP|BT_SPIN)) and FixedDiv(FixedMul($,8),9) or FixedDiv(FixedMul($,6),7)
		end
		P_InstaThrust(s, BOUNCEANGLE+ANGLE_180, max(p.speed*2/3, 11*s.scale))
		if (p.speed > 40*s.scale)
			s.momx,s.momy = FixedDiv(FixedMul($,11),12),FixedDiv(FixedMul($,11),12) 
		end
		XSWindSpawn(s)
		RB.angle = s.angle
		RB.bounced = 21
		RB.rollmult = 6
		RB.startroll = 0
		RB.stripes = 5
		RB.turnbuff = 4
		RB.maxturn = ANG2*8
		RB.sharpturn = 0
		if (p.cmd.forwardmove > 41)
			RB.nosteering = 5
		end

		XS_CircleWave(s, 1) --Primary impact circle.

		local ghs = XS_CircleWave(s, 1) --Secondary wave that follows you.
		ghs.ditchtracer = 8
		ghs.frame = E|FF_TRANS20	
		ghs.dispoffset = -7
		ghs.fuse = 10
		ghs.spriteyoffset = -24<<16
		
	else --Collissions on the wall or ceiling.
		if (not (p.cmd.buttons & BT_SPIN) and (abs(s.momz) < s.scale))
		or ceiling
			DOBOUNCE = true
			local MOMZ = abs(RB.lastmomz) or 1
			local SPEED = min(46*s.scale, abs(MOMZ))
			XS_QuickDust(s)
			RB.rollmult = 8	
			RB.stripes = 0
			RB.bounced = 8
			if (p.cmd.sidemove < - 4)
				RB.startroll = -30
			elseif (p.cmd.sidemove > 4)
				RB.startroll = 30
			end
			
			if ceiling
				RB.forcespin = 4
				if not (p.cmd.buttons & BT_JUMP) and not P_IsObjectOnGround(s)
					SPEED = $/4 --Looks like you're already trying to go down yourself, lessen impact for control!
				elseif (s.eflags & MFE_UNDERWATER)
					SPEED = $/3						
				end
				P_SetObjectMomZ(s, -SPEED-(9*s.scale))
				local ghs = XS_CircleWave(s, 2) --Expanding floor circle, but pushed a bit higher.
				ghs.z = $+(24*s.scale*P_MobjFlip(s))
				RB.turnbuff = 0
			else
				if not (p.cmd.buttons & BT_SPIN)
					RB.forcejump = 3
				end
				RB.floorbounced = 16
				RB.turnbuff = 16	
				local MINSPEED = 21*s.scale
				if (s.eflags & MFE_UNDERWATER)
					MINSPEED = $/2
					MOMZ = $/3
				end				
				SPEED = max(MINSPEED, abs(MOMZ/2))
				XSSpawnDust(s, p.speed+1, 8)	
				s.z = $+((s.scale/5)*P_MobjFlip(s))
				P_SetObjectMomZ(s, SPEED)	
				
				XS_CircleWave(s, 2) --Expanding floor circle.
			end
		end
	end
	
	if DOBOUNCE --Effects that apply to all collission types.	
	--	XSon.Sound(s, sfx_xssfxo, 200)
	
		RB.tapped,RB.recordtap = 0,0
		RB.angle = s.angle
		RB.rollmult = 5
		RB.straightshot = 0
		
		if line or ceiling
		or (abs(s.momz) > s.scale) 
		or (not (p.cmd.buttons & BT_SPIN) and P_IsObjectOnGround(s))
		
			if line
				RB.wallstress = min($+10, 100)
				if (RB.wallstress > 80) --Too much! Bonk out of the Ring Burst!
					XSRingBurstReset(p, s, xs)
					local x,y = P_ClosestPointOnLine(s.x, s.y, line)
					xs.zipdashangle = R_PointToAngle2(s.x, s.y, x, y)
					P_InstaThrust(s, xs.zipdashangle, 50*s.scale)
					XSon.Bonk(p, s, xs) return false --
				end
			else
				RB.wallstress = min($+5, 100)
				--Kick up dust.
				local ghs = XS_RingBurstDust(p, s, xs, 180*s.scale)	
				if ghs and ghs.valid
					ghs.momx = s.momx/2
					ghs.momy = s.momy/2
				end
			end
			RB.rocket = 0
			RB.rocketjump = 0
			RB.rocketspin = 0			
			RB.wallbounce = 4
			
			if not (P_IsObjectOnGround(s) and (p.cmd.buttons & BT_SPIN))
				RB.trail = 9

				XSon.SwapStretch(p, s, xs)
				xs.zipstretch2 = 5
				s.spriteyscale = FRACUNIT+25000
				s.spritexscale = FRACUNIT-15000
				xs.alteredoffset = true
				s.spriteyoffset = 12<<16
			end
		
			--Sound object.		
			local SfX = P_SpawnMobjFromMobj(s, 0,0,0, MT_THOK)
			SfX.flags2 = $|MF2_DONTDRAW
			SfX.tics = 133
		
			local RNG = XSLocalRNG(-1,2,s)
			S_StartSound(SfX, sfx_rocks2+RNG)
			S_StartSoundAtVolume(s, sfx_s1b4, 80)
			S_StartSoundAtVolume(SfX, sfx_corkh, 85)
			S_StartSoundAtVolume(s, sfx_doord2, 45)	
			
		end
	end		
	return DOBOUNCE --
end

--Disable the Ring Burst! Function does nothing if Ring Burst isn't active. Ringburstexit" RingburstReset"
rawset(_G, "XSRingBurstReset", function(p, s, xs)
	if p.xsringburst --Don't bother if it's not active in the first place.
		local RB = p.xsringburst
		local SUPER = (XS_Super(p)) or 0
		if RB.lastwave and type(RB.lastwave) == "table" --Remove visual waves on exit, pls.
			local WAVES = RB.lastwave 
			if (#WAVES)
				for i = 1,#WAVES
					local wave = WAVES[i]
					if wave and wave.valid
						P_RemoveMobj(wave)
					end
				end
			end
		end
		if RB.afterdrain and p.rings and not xs.supersonic
			xs.afterdrain = RB.afterdrain
		end
		if s.XS_tutor and s.XS_tutor.ID == "ringburst" and s.XS_tutor.timelimit
			s.XS_tutor.timelimit = min($, 40)
		end
		s.rollangle = 0
		s.spritexscale,s.spriteyscale,s.spriteyoffset = FRACUNIT,FRACUNIT,0
		xs.ringburstcharge = 0
	
		if (p.pflags & PF_SPINNING)
		or not P_IsObjectOnGround(s) and (p.speed or s.momz) and not p.powers[pw_carry]
			local RVOLUME = (RB.start*5)
			XSon.Sound(s, sfx_xssfpy, max(10, min(100, RVOLUME)))
		end

		if SUPER != 3
			if (RB.start < 25)
				if not p.powers[pw_sneakers] --Extra momentum penalty for cutting the ringburst off early.
					s.momx = FixedDiv(FixedMul($, 2), 3)
					s.momy = FixedDiv(FixedMul($, 2), 3)
				end
				if (s.momz*P_MobjFlip(s) > 0)
					s.momz = $/3
				end
			end
			if not p.rings or xs.PowerRing
				if RB.TUTORX != true
					if xs.PowerRing
						xs.PowerRing = 0
						s.XS_pwringget = -6
					end
					S_StartSound(s, sfx_antiri, p)
				end
			end
		end
		if not p.powers[pw_carry]
		and not (p.panim == PA_RIDE)
		and not ((p.panim == PA_PAIN) or P_PlayerInPain(p))
		and not (p.pflags & (PF_STARTDASH|PF_SLIDING))
		
			if P_IsObjectOnGround(s) --On the ground?
				s.eflags = $|MFE_JUSTHITFLOOR
				if (p.cmd.buttons & BT_SPIN) and (p.speed > 8*s.scale) --Transition into a roll.
					s.state = S_PLAY_ROLL
					p.pflags = $|PF_SPINNING & ~(PF_JUMPED|PF_THOKKED|PF_STARTJUMP|PF_STARTDASH|PF_NOJUMPDAMAGE)
				else
					s.state = S_PLAY_FALL
				end
				XSSpawnDust(s, p.speed+8<<16, 8)
				p.cmd.forwardmove = 5
				s.momx = FixedDiv(FixedMul($, 5), 6)
				s.momy = FixedDiv(FixedMul($, 5), 6)
				if not s.momz
					P_SetObjectMomZ(s, -2)
				end
				RB.lockroll = 14
				
			else --Exit out of the ringburst with a cool flip!
				if SUPER == 3 and not (p.cmd.buttons & BT_SPIN) and (xs.FreeFlight or XSon.TryFlight(p,s,xs, nil, 90, 90, (not (xs.custom3)) and "noSFX" or nil))
					--Always transition into flight when canceling a Ring Burst in midair!
				else
					local SPEED = FixedInt(p.speed)
					local MOMZ = FixedInt(abs(s.momz))
					if (SPEED > 30)
						local SPEED = FixedInt(p.speed)
						s.momx = FixedDiv(FixedMul($, 10), max(11, min(19, SPEED/3)) )
						s.momy = FixedDiv(FixedMul($, 10), max(11, min(19, SPEED/3)) )
					end
					if (MOMZ > 8)
						s.momz = FixedDiv(FixedMul($, 5), max(6, min(11, MOMZ/2)) )
					end
					p.pflags = $|PF_JUMPED & ~(PF_SPINNING|PF_THOKKED|PF_STARTJUMP|PF_NOJUMPDAMAGE)
				end
				s.state = S_PLAY_XDIVEFLIP1
				XSon.SwapStretch(p, s, xs)
				xs.snapdiveangle = p.drawangle
				xs.zipstretch2 = 3
				xs.frontflip = 13					
				xs.alteredscale = true
				s.spritexscale = FRACUNIT-4000
				s.spriteyscale = FRACUNIT+2000		
				xs.poutydrop = 8
				xs.alteredoffset = true
				s.spriteyoffset = 8<<16
				s.anim_duration = $+2
				s.tics = $+2				
				S_StartSoundAtVolume(s, sfx_xssfxl, 100)
				if xs.darkphase
					XS_RingBurstSparkles(s, 44)
				else
					XS_RingBurstSparkles(s, 50, ((SUPER==3) and s.color or SKINCOLOR_SUPERSKY5) )
				end
				RB.lockroll = 3
				
				local ghs = XS_CircleWave(s, nil, nil, 10)
				ghs.sttspeed = nil
				ghs.sttfade = true
				ghs.spriteyoffset = $+4<<16
				ghs.scalespeed = $*3/2
				ghs.frame = G|FF_TRANS10
				ghs.momx = s.momx/3
				ghs.momy = s.momy/3		
			end
			
			--Exit effects.
			if (p.speed > 16*s.scale)
				XS_QuickDust(s, SKINCOLOR_SUPERSILVER1, nil, nil, s)
				XSWindSpawn(s)
				XSZipTrail(s, nil, nil, FF_TRANS50, 20, nil, FRACUNIT+45000, RF_FULLBRIGHT|RF_NOCOLORMAPS)
			end
			XSon.ShinySplat(s, s.color, 3, 95, false)
		end
		S_StopSoundByID(s, sfx_xssfxw) S_StopSoundByID(s, sfx_xssfxx) S_StopSoundByID(s, sfx_xssfpz)			
		if not xs.FreeFlight
			p.powers[pw_strong] = 0
			p.charflags = $|SF_RUNONWATER & ~SF_NOSKID
			xs.ringburstcd = 33-(6*SUPER)
			S_StartSoundAtVolume(s, sfx_s1c0, 50)
		end
	end		
	p.xsringburst = nil --Always set to nil.
end)

--The Ring Burst Ball! A multi-layered part of 2 shields, a CGI ball render, and a feet blurring overlay!
--Legs means it's positioned underneath sonic for his "spinball" legs. This is smaller and has 1 less shield.

rawset(_G, "XSRingBurstBall", function(p, s, xs, dashanim)
	if not s.health return false end --Abort!
	
	local ball = xs.ringburstball
	local SPAWNED = false
	local ROLLANGLE = 0
	if (XS_CRAPPYPRIORITIES==1)
		ROLLANGLE = 0
	elseif p.xsringburst
		ROLLANGLE = p.xsringburst.rollangle or 0
	else
		ROLLANGLE = s.rollangle
	end
	
	
	--Spawn the Ring Burst Ball object!
	if not (ball and ball.valid)
		xs.ringburstball = P_SpawnMobjFromMobj(s, 0,0,0, MT_XSRINGBURSTBALL)
		ball = xs.ringburstball
		if not dashanim
			XSon.sparkles(p, s, xs, 48, 6, 5)
		end
		SPAWNED = true
	end
	local shield,shield2 = ball.ringburstshield,ball.ringburstshield2
	
	--Now spawn the white shield effect around it? Only 1 if "dashanim"	
	if not (shield and shield.valid)
		ball.ringburstshield = P_SpawnMobjFromMobj(ball, 0,0,0, MT_XSRINGBURSTBALL) --MT_XSRINGBURSTSHIELD
		shield = ball.ringburstshield
		shield.dispoffset = 25
		SPAWNED = true
	end
	if not (shield2 and shield2.valid)
		ball.ringburstshield2 = P_SpawnMobjFromMobj(ball, 0,0,-32*ball.scale, MT_XSRINGBURSTBALL) --MT_XSRINGBURSTSHIELD2
		shield2 = ball.ringburstshield2
		shield2.dispoffset = -78
		SPAWNED = true
	end	
	local COLORIZED = nil
	if dashanim --Position below Sonic and be smaller to give the illusion of spinning feet!
		ball.scale = (s.scale*2)/9
		COLORIZED = XSon.TrailColor(s, xs)
		if COLORIZED == SKINCOLOR_XSBLUETRAIL
			COLORIZED = SKINCOLOR_SKY
		end
		shield.color,shield2.color = COLORIZED,COLORIZED
		shield.colorized,shield2.colorized = true,true
	else
		if p.xsringburst
			XSon.ShinySplat(s, SKINCOLOR_BONE, 3, 100, false)
		end
		ball.scale = s.scale*4/11
		s.flags2 = $|MF2_DONTDRAW --Make actual player sprite invisible during Ring Burst.
	end
	
	--We can copy all of these values, so let's do it like so.
	local ghs = ball
	local SKIN = (s.skin=="xsonicserious") and "xsonicserious" or "xsonic" 
	local RFLAGS = s.renderflags
	local DELAY = 1
	if (p!=displayplayer)
		DELAY = 2
		if p.mo and p.mo.valid
			local DIST = FixedInt(FixedHypot(s.x-p.mo.x, s.y-p.mo.y))
			if DIST
				DELAY = max(1, min(8, DIST/1000))
			end
		end
	end
	if not (s.renderflags & (RF_FULLBRIGHT|RF_FULLDARK))
		RFLAGS = $|RF_SEMIBRIGHT
	end	
	
	for i = 1,3,1
		if i != 1
			if i==2
				ghs = shield
			else--if i==3
				ghs = shield2
			end
		else
			ghs.target,ghs.tracer = s,s
		end
		if (leveltime%(i+DELAY)==0) or SPAWNED==true
			ghs.rollangle = ROLLANGLE --Space rollangle shifts out to prevent crashes...thanks, SRB2.
		end
		
		ghs.skin = SKIN
		ghs.mirrored = s.mirrored
		ghs.angle = p.drawangle
		ghs.renderflags = RFLAGS
		if xs.darkphase and not dashanim
			if i==3 and xs.darkphase==3
				ghs.color = SKINCOLOR_XSSUPERBLUR
				ghs.blendmode = AST_REVERSESUBTRACT
				ghs.SKIP = true
			else
				local DARKAURA = XSon.DarkAura((i*2)-2)
				ghs.color = DARKAURA
			end
			ghs.colorized = true
		elseif dashanim and not xs.darkphase and (i!=1)
			ghs.color,ghs.colorized = COLORIZED, true
		else
			ghs.color,ghs.colorized = s.color, false
		end
		ghs.fuse,ghs.tics = 3,9
		ghs.sprite = SPR_PLAY
		ghs.spriteyoffset = s.spriteyoffset
		if dashanim and (s.eflags & MFE_VERTICALFLIP)
			P_MoveOrigin(ghs, s.x, s.y, s.z+47*s.scale)
		else
			P_MoveOrigin(ghs, s.x, s.y, s.z-((i==3) and 21*ball.scale*P_MobjFlip(s) or 0))
		end
	end


	--Now give them their respective subsprites...
	ball.sprite2 = SPR2_BNCE|(((XS_Super(p)) == 3) and FF_SPR2SUPER or 0)
	shield.sprite2 = SPR2_LAND
	shield2.sprite2 = SPR2_LAND
	
	--And their correct frames...
	local ff = XSon.TimeFrame(H)
	
	ball.frame = ff
	
	shield.blendmode = AST_ADD
	shield.frame = ff|FF_TRANS60
	shield.scale = ball.scale*15/16
	shield.eflags = s.eflags
	
	if shield2.SKIP
		shield2.SKIP = nil
		shield2.frame = (ff & ~FF_TRANSMASK)|FF_TRANS50
	else
		shield2.frame = ff
		shield2.blendmode = AST_ADD
	end
	shield2.scale = ball.scale*6/5
	shield2.eflags = s.eflags
	
	if dashanim --Extra
		shield.color,shield.colorized = COLORIZED, true
		shield2.color,shield2.colorized = COLORIZED, true
		
		local ghs = P_SpawnGhostMobj(shield2)
		ghs.scale = shield2.scale
		ghs.color,ghs.colorized = COLORIZED,true
		shield2.fuse = 4
		shield2.tics = 10
		shield2.momy = $*4/5
		shield2.momx = $*4/5
		shield2.blendmode = AST_ADD

		ghs = P_SpawnGhostMobj(shield)
		ghs.scale = shield.scale
		ghs.color,ghs.colorized = COLORIZED,true
		shield.fuse = 4
		shield.tics = 10
		shield.momy = $*3/4
		shield.momx = $*3/4
		shield.blendmode = AST_ADD
	end
	s.XS_DidBlur = true

--	shield2.frame = ff|FF_TRANS40
	
	--Finally misc stuff unique to each layer.
--	if leveltime % 2 == 0
--		shield.scale = $*6/5
--		shield2.scale = $*6/5
--	end
	
	--Set them to the correct states if necessary.
/*	if ball.state != S_PLAY_XRINGBURST ball.state = S_PLAY_XRINGBURST end
	if shield.state != S_PLAY_XRINGBURSTSHIELD shield.state = S_PLAY_XRINGBURSTSHIELD end
	if shield2.state != S_PLAY_XRINGBURSTshield2 shield2.state = S_PLAY_XRINGBURSTshield2 end */
	
	return true
end)