--╔════════════════════════════╗══════════════════════════════════════════════════════════════════════════════════════════════#
--║       NEW FUNCTIONS        ║ Newer functions using the XSon table, a style I began using for SA-Knuckles when realizing
--║      USING XSon TABLE      ║ I was using piling up hundreds of rawset functions and had to start organizing better.
--╚════════════════════════════╝══════════════════════════════════════════════════════════════════════════════════════════════#
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

--Shortcut to swap HUDs.
XSon.SwapHUD = function(p,s,xs, SET, SILENT)
	if SET == "correct" --Set HUD to what it should be.
		if xs.HUDpref==true and IsXSonic(s) and not s.noXSHUDthisstage
			xs.XHUD = true
			if p == consoleplayer
				hud.disable("lives") hud.disable("score")
				hud.disable("time") hud.disable("rings")
			end
		else
			xs.XHUD = false
			if p == consoleplayer
				hud.enable("lives") hud.enable("score")
				hud.enable("time") hud.enable("rings")	
			end
		end return --
	end
	if ((xs.HUDpref != true) or SET==true) and (SET != false)
		xs.HUDpref,xs.XHUD = true,true
		if not SILENT
			CONS_Printf(p, "\x82"+"Set X Sonic's Custom HUD to\x83 ON")
		end
		if not (s and s.noXSHUDthisstage)	
			if p == consoleplayer
				hud.disable("lives") hud.disable("score")
				hud.disable("time") hud.disable("rings")
			end
		end		
	elseif xs.HUDpref==true or xs.XHUD==true or SET==false
		xs.HUDpref,xs.XHUD = false,false
		if not SILENT
			CONS_Printf(p, "\x82"+"Set X Sonic's Custom HUD to \x84 OFF")
		end
		if p == consoleplayer
			hud.enable("lives") hud.enable("score")
			hud.enable("time") hud.enable("rings")	
		end
	end	
	if s and s.valid
		s.XShudpopin = nil
		if s.noXSHUDthisstage
			xs.XHUD = false
			if IsXSonic(s) and p == consoleplayer		
				hud.enable("lives") hud.enable("score")
				hud.enable("time") hud.enable("rings")
			end
		end
	end	
end

--Echoing voice!
XSon.Echoes = function(p,s,xs, STOP, ECHO)
	if STOP --Stop all echoes regardless of whether they're there.
		if s.XSechotable
			for i,m in pairs(s.XSechotable)
				if m and m.valid
					P_RemoveMobj(m)
				end
			end
		end s.XSecho = nil return --
	end
	ECHO.timer = ECHO.timer and max($-1,0) or 0
	if not ECHO.timer or ECHO.timer == 3
		if ECHO.sfx and ECHO.rvolume
			local SFX = P_SpawnMobjFromMobj(s,0,0,0, MT_THOK)
			SFX.flags2 = MF2_DONTDRAW SFX.tics = 222 SFX.fuse = 222
			S_StartSoundAtVolume(SFX, ECHO.sfx, (ECHO.timer == 3) and ECHO.rvolume or ECHO.rvolume/2, ECHO.meonly)
			
			if ECHO == s.XSecho
				if s.XSechotable == nil s.XSechotable = {} end --Save echoes to a table.
				table.insert(s.XSechotable, SFX)
			end
		end
		if not ECHO.timer
			if ECHO == s.XSecho
				s.XSecho = nil
			else
				s.XSecho2 = nil
			end
		end
	end
end

XSon.StopShout = function(s)
	for i = 0,2
		S_StopSoundByID(s, sfx_xsvopf+i)
	end
end

--Cannot input stuff for a while. "ExitState" "NoInput"
XSon.NoInput = function(s, p, xs)
	xs.jump = 99<<16
	xs.spin = 99<<16
	xs.custom3 = 99<<16
	p.cmd.buttons = $ & ~BT_CUSTOM3
	p.pflags = $|PF_JUMPDOWN|PF_USEDOWN
	p.cmd.buttons = $ & ~(BT_JUMP|BT_SPIN)
/*	if p.exiting 
		if not s.YUKexitcomment and s.health
		and not p.YUKattack and not p.YUKcrouch and not P_PlayerInPain(p)
			if (leveltime >= 10500) --After 10 minutes...
				YKNUX.voice(s, (leveltime >= 21000) and sfx_ykvocn or sfx_ykvocq, nil, p) --Sheesh > FINALLY
			elseif (leveltime < 1050)
				YKNUX.voice(s, sfx_ykvoco, nil, p) --Not bad!
			else
				YKNUX.voice(s, (leveltime % 2) and sfx_ykvoch or sfx_ykvocd, nil, p) --Very happy Knuckles.
			end
			s.YUKexitcomment = true
		end
	end */	
end

--Apply airdrag. Doesn't necessarily need to be in the air.
XSon.AirDrag = function(s, AMOUNT, TRESHOLD)
	local CONTINUE = true
	if TRESHOLD
		local SPEED = s.player and s.player.speed or abs(s.momx)+abs(s.momy)
		CONTINUE = (SPEED>TRESHOLD) --Don't continue if our speed's already lowered enough.
	end
	if CONTINUE == true
		s.momx = FixedDiv(FixedMul($, 100), 100+AMOUNT)
		if not ((s.flags2 & MF2_TWOD) or twodlevel)
			s.momy = FixedDiv(FixedMul($, 100), 100+AMOUNT)
		end
	end
end

--Spawn the edgestop object?
XSon.SpawnEdgeStop = function(p,s,xs)
	if not (xs.zipdashslide and p.panim != PA_EDGE or p.panim == PA_WALK or p.panim == PA_RUN)
	or (XS_Super(p) == 3)
	or (p.speed <= 3*s.scale) or (xs.direct >= 5)
	or (s.eflags & (MFE_GOOWATER|MFE_SPRUNG|MFE_TOUCHLAVA|MFE_APPLYPMOMZ))	
		return --
	end
	if (s.eflags & MFE_TOUCHWATER) and not (s.eflags & MFE_UNDERWATER) --You're running on water! Don't do it, Sonic!
		if (p.charflags & SF_RUNONWATER) and (p.speed > 20*s.scale)
			return --
		end
	end
	--Spawn the edgestop object to protect us from falling off ledges. ledgestop ledgefall.
	if not (xs.edgestop and xs.edgestop.valid)
		xs.edgestop = P_SpawnMobjFromMobj(s, 0, 0, 0, MT_XSEDGESTOP)
	end
	--Edgestop object is here. Begin operation!
	local ghs = xs.edgestop
	ghs.sprite = SPR_NULL
	ghs.frame = A
	ghs.flags2 = $|MF2_DONTDRAW
	ghs.target = s
	ghs.scale = s.scale --Always be Sonic's size.
	ghs.tics = 3 
	ghs.active = false
			
	if (s.eflags & MFE_VERTICALFLIP) --Flip, damn you!
		P_SetOrigin(ghs, s.x, s.y, s.z-(s.height*5/3))
		ghs.eflags = $|MFE_VERTICALFLIP
	else
		P_SetOrigin(ghs, s.x, s.y, s.z+(s.height*5/3))
		ghs.eflags = $ & ~MFE_VERTICALFLIP
	end
	
	--on the ground? Then check for ledge dropoff instead. Edge protect. Edge guard. Edge stop? Ledgestop? Ledge Stop? Ledge Protect!							
	if xs.ledgeprotectcd or xs.ledgereactivate				
		if not (s.standingslope and (abs(s.standingslope.zdelta) > 19999) ) --Not on steep slopes.	
			ghs.active = true
			ghs.flags = $|MF_MISSILE
			if ghs and ghs.valid
				local XYOFFSET = p.speed+((s.radius>24*s.scale) and s.radius or 24*s.scale)
				local ANG = R_PointToAngle2(s.x, s.y, s.x+s.momx, s.y+s.momy)
				ghs.sonicang = ANG
				P_SetOrigin(ghs, s.x+FixedMul(XYOFFSET, cos(ANG)),s.y+FixedMul(XYOFFSET, sin(ANG)), s.z)
				if ghs and ghs.valid
					XSon.EdgeStopThinker(ghs)
				end
			end
		end
	end	
end
--Called from PostThinkFrame for animation purposes.
XSon.SkyDiving = function(p,s,xs)
	if (s.momz*P_MobjFlip(s) < ((xs.zipdash) and -22*s.scale or -34*s.scale))
		--Start sky diving animation?
		if ( (s.state == S_PLAY_FALL)
		or ((p.pflags & PF_JUMPED) and ((p.panim == PA_ROLL) or (p.panim == PA_JUMP))) )
		and not xs.snapdive
		and not p.powers[pw_carry]
		and not P_IsObjectOnGround(s)
			local SUPER = ((XS_Super(p)) == 3) and FF_SPR2SUPER or 0
			if (xs.skydive > 3)
				s.sprite2 = SPR2_DIVE|SUPER
				s.frame = ($ & ~FF_FRAMEMASK)  |((leveltime % 2) and B or A)
				XSon.zwind(s, NUM)
			else
				s.sprite2 = SPR2_MLEE|SUPER
				s.frame = ($ & ~FF_FRAMEMASK)|min(D,(xs.skydive))
				if xs.skydive == 4 and not s.XSstretch
					XSon.SwapStretch(p, s, xs)
					s.spritexscale = FRACUNIT+14000
					s.spriteyscale = FRACUNIT+14000
					s.spriteyoffset = 6<<16
					s.XSstretch = {timer = 10, Xspeed = 1200, Yspeed = 1200, air = true}			
				end
			end
			xs.skydive = $+1 return 2 --
		end
	elseif xs.skydive --Land gracefully or not so much?
	
		if (s.eflags & MFE_JUSTHITFLOOR)
		and ((p.panim == PA_WALK) or (p.panim == PA_IDLE) or (p.panim == PA_RUN) or p.skidtime) --These animations!
		
			if not (p.pflags & (PF_JUMPED|PF_THOKKED|PF_SPINNING|PF_STARTDASH|PF_SLIDING))
			and (xs.skydive > 5)
			and not p.xstauntstance
			and not xs.zipdash
			and not xs.snapdivetrick 	
			and not (p.cmd.buttons & (BT_SPIN|BT_CUSTOM1))
			and P_IsObjectOnGround(s) --Sanity check.
				local SUPER = XS_Super(p)
				if (p.speed < 77*s.scale) and not (xs.direct > 6) and SUPER == 0
					XSon.StartTaunts(p, s, xs, (p.exiting) and 77 or true)
				else
					s.tics,s.anim_duration = 7,7
					s.sprite2 = (SUPER==3) and SPR2_SKID|FF_SPR2SUPER or SPR2_MLEL --Landing frames.
					s.frame = $ & ~FF_FRAMEMASK
					XSon.SwapStretch(p, s, xs)
					s.spriteyscale = FRACUNIT-999
					s.spritexscale = FRACUNIT+2900
					xs.zipstretch = 4
					for d = 1,8
						local ghs = P_SpawnMobjFromMobj(s, 0,0,-4<<16, MT_SPINDUST)
						ghs.state = S_SPINDUST2 ghs.tics = $/2 ghs.anim_duration = $/2
						ghs.destscale = 99<<16 ghs.fuse = 25 ghs.flags2 = $|MF2_SHADOW
						P_InstaThrust(ghs, ANGLE_45*d, 3<<16)
					end
					xs.dustcd = 3
					XSon.Sound(s, sfx_xssfx7)					
				end
			end
		end
		xs.skydive = 0 return 1 --
	end
end

--Called from PostThinkFrame for animation reasons.
XSon.FrontFlip = function(p,s,xs)
	if (s.state >= S_PLAY_XDIVEFLIP1) and (s.state <= S_PLAY_XDIVEFLIP4)
	--	if (s.sprite2 & ~FF_SPR2SUPER) != SPR2_MLEE
			s.sprite2 = SPR2_MLEE|(xs.supersonic and FF_SPR2SUPER or 0)
	--	end
		s.frame = ($ & ~FF_FRAMEMASK)|
		((s.state == S_PLAY_XDIVEFLIP1) and D 
		or (s.state==S_PLAY_XDIVEFLIP2) and C
		or (s.state==S_PLAY_XDIVEFLIP3) and B
		or A)
	else
		xs.frontflip = 0 return 0 --
	end
	xs.frontflip = max($-1,0) return xs.frontflip --
end

--Speaks for itself. Called in XS-MAIN.
XSon.SneakerBehaviour = function(p,s,xs)	
	if (p.normalspeed > FRACUNIT) and not xs.uwdebuff 
		p.normalspeed = 70<<16
		p.acceleration = max(min(69-((p.speed/FRACUNIT)/4), 63), 38) //Last 2 values. 63 & 38 are max & min.
	else
		if (p.normalspeed == 70<<16)
			p.normalspeed = 36<<16
		end
		p.acceleration = 36
	end	
	if (p.speed > p.runspeed) and (p.powers[pw_sneakers] > 2)
		if not (XSon.SneakerSpeed(p,s,xs)) == true
		and (p.panim == PA_RUN or s.state == S_PLAY_RUN) and P_IsObjectOnGround(s)
			s.state = S_PLAY_DASH
		end	
	else
		if s.state == S_PLAY_DASH or p.panim == PA_DASH
			s.state = (P_IsObjectOnGround(s) and p.speed) and S_PLAY_RUN or S_PLAY_FALL
		elseif s.state == S_PLAY_XZIPLEAP and (p.pflags & PF_JUMPED) and not xs.zipdash and not xs.snapdive
			s.state = S_PLAY_ROLL
		end
	end		
	--Speed Shoes running sound.
	if (p.speed > 36*s.scale) and not xs.zipdashslide and not xs.uwdebuff
		if not (xs.speednoise3 and xs.speednoise3.valid)
			xs.speednoise3 = P_SpawnMobjFromMobj(s, 0,0,0, MT_THOK)
			xs.speednoise3.flags2 = MF2_DONTDRAW
			XSon.Sound(xs.speednoise3, sfx_xssfxf)
		else
			P_SetOrigin(xs.speednoise3, s.x+s.momx, s.y+s.momy, s.z)
			xs.speednoise3.tics = 7 --Refresh timer infinitely.
		end		
		if p.speed > 99*s.scale 
		and (p.panim >= PA_RUN and p.panim <= PA_FALL or (s.state == S_PLAY_XZIPLEAP and not xs.zipdash))
			if not xs.shoesgfx
				xs.shoesgfx = 4
				XSon.Quake(s, 36<<16, 4)
				XSon.SonicBoom(p, s, xs, 5)
			else
				xs.shoesgfx = $+1
				if (xs.shoesgfx < 10) and s.health
					XSon.SonicBoom(p, s, xs, xs.shoesgfx+5)
				end
			end		
			xs.ziptrailticket = 1
		end
	elseif xs.shoesgfx
		xs.shoesgfx = min(3, $-1)
	end
end
--Special animation when running fast. Called from XS-MAIN.
XSon.SneakerSpeed = function(p,s,xs)
	--Do a special sneakers leap capable of retaining all momentum!
	if (p.speed > 99*s.scale)
		if (p.pflags & PF_JUMPED) and not xs.zipdash and not xs.snapdive and not xs.snapdivetrick
		and (p.panim >= PA_RUN and p.panim <= PA_FALL or (s.state == S_PLAY_XZIPLEAP and not xs.zipdash))
		and not xs.uwdebuff and s.health 
		and not p.powers[pw_carry] and not P_IsObjectOnGround(s) 
			if (p.panim == PA_ROLL or p.panim == PA_JUMP or p.panim == PA_SPRING or p.panim == PA_FALL)
				if s.state != S_PLAY_XZIPLEAP and (abs(s.momz) < 8*s.scale)
					s.state = S_PLAY_XZIPLEAP
					if not xs.spinstretch
						XSon.SwapStretch(p, s, xs)
						xs.spinstretch = 12
						s.spriteyscale = FRACUNIT+10000
						s.spritexscale = FRACUNIT-15000
						xs.alteredoffset = true
						s.spriteyoffset = -12<<16
					end
					return true --
				end
			elseif (s.state == S_PLAY_XZIPLEAP) and (abs(s.momz) >= 8*s.scale) 
				s.state = S_PLAY_ROLL return true --
			end
		end			
		xs.ziptrailticket = 1 
	end
end

--We're getting carried by something. pw_carried doesn't necessarily imply "carried", but it's a state that should override 
--all else. So, cancel ALOT of properties if we have it!
XSon.Carried = function(p,s,xs, CARRY)

	xs.leappenalty,xs.yteleport,xs.damageboost = nil
	xs.framejump = 0
	xs.superflight = 0
	xs.bonkeddash = 0
	xs.bonked = 0
	if s.wallspintrans
		s.wallspintrans = 0
	end
	xs.painrecovery = 0
	xs.diveslowdown = 0
	xs.airborne = 0
	xs.ringburstcd = 0
	xs.ringburstcharge = 0
	if xs.rolltimer
		s.rollangle = 0
		xs.rolltimer = 0
	end
	if xs.poutydrop
		s.spriteyoffset = 0
		xs.poutydrop = 0
	end
	if p.xsringburst 
		XSRingBurstReset(p, s, xs)
		p.xsringburst = nil
	end
	
	if not CARRY
		return --
	else
		if CARRY == CR_NIGHTSMODE
			if (s.color >= SKINCOLOR_SUPERGOLD1) and (s.color <= SKINCOLOR_SUPERGOLD5)
				s.color = SKINCOLOR_SUPASONIKKU --Bad SRB2, no.
			end
			XSon.ShinySplat(s, SKINCOLOR_YELLOW, 3, 95, false) --Glow
			local SPRITE2 = s.sprite2 & ~FF_SPR2SUPER
			local COLOR = (leveltime%12==0) and SKINCOLOR_SILVER or XSon.RNG(SKINCOLOR_SUPERGOLD1, SKINCOLOR_SUPERGOLD4, s)	
			
			if (SPRITE2==SPR2_TAL5 or SPRITE2==SPR2_TRNS or s.XS_NiGHTStransform) --Transform Animation.
				s.XS_NiGHTStransform = s.XS_NiGHTStransform and $+1 or 1
			else
				if not s.XSauraSFX or ((not netgame) and not S_SoundPlaying(s, sfx_xssfxy))
					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(33, min(60, 104-SPEED)) or 60 --Get less noticable the faster you move.
					s.XSauraSFX = 76
					S_StopSoundByID(s, sfx_xssfxy)
					XSon.Sound(s, sfx_xssfxy, RVolume)
				elseif netgame and not S_SoundPlaying(s, sfx_xssfxy)
					XSon.Sound(s, sfx_xssfxy, 50)
				end
				s.XSauraSFX = max($-1,0)	
				
				if SPRITE2==SPR2_NDRL and (leveltime%2==0)  --drilling effects
					local RNG1 = min(7, P_RandomRange(1,9)) 				
					XSon.sparkles(p,s,xs, RNG1+11, 6, 1, -1, COLOR, AST_ADD, XSon.RNG(4,RNG1,s)<<16, XSon.RNG(14,22,s), 9800)
					
					XS_RingBurstSparkles(s, 16, COLOR, 41)
				end
			end
			return "nights" --We're done here. Abort code and do NiGHTS behaviour instead.
		else
			xs.jump = 5	
			if CARRY == CR_PLAYER
				if s.state == S_PLAY_RIDE
					s.XS_CheckHeight = 6
					if (p.pflags & PF_JUMPED)
						p.pflags = $ & ~(PF_JUMPED|PF_THOKKED|PF_SPINNING|PF_STARTDASH)
					end
				end
			elseif CARRY != CR_MINECART
				xs.spin = 5
				if CARRY == CR_MACESPIN or CARRY == CR_ZOOMTUBE
					if (s.state != S_PLAY_ROLL)
						s.state = S_PLAY_ROLL
					end
					s.eflags = $ & ~MFE_JUSTHITFLOOR
				elseif CARRY == CR_ROLLOUT
					if (s.state == S_PLAY_ROLL) or (p.pflags & (PF_JUMPED|PF_SPINNING)) or p.panim == PA_ROLL --Cancel a rare bug
						p.pflags = $ & ~(PF_JUMPED|PF_THOKKED|PF_SPINNING|PF_STARTDASH)
						s.state = S_PLAY_WALK
					end					
				elseif CARRY == CR_DUSTDEVIL				
					xs.hitspring = 2 --Have spring properties after this!
					if (s.eflags & MFE_VERTICALFLIP)
						if (s.z > s.ceilingz-60*s.scale)
							s.z = $-25*s.scale
						end			
					else --How are you getting stuck in the ground?! Get outta there!
						if (s.z < s.floorz+80*s.scale)
							s.z = $+30*s.scale
						end
					end			
				end
			end
		end
	end
end

--Old stretch functions before I made a dedicated table since I was using this stuff so often.
XSon.OldStretch = function(p,s,xs)
	if xs.zipstretch --Squash down, then go!
		xs.zipstretch = $-1
		xs.alteredscale = true
		s.spritexscale = min($+5500, FRACUNIT)
		s.spriteyscale = max($-6500, FRACUNIT/2) 
	end	
	if xs.zipstretch2 --Extreme version of the zipdash stretch.
		xs.zipstretch2 = $-1
		xs.alteredscale = true
		s.spritexscale = min($+14000, FRACUNIT)
		s.spriteyscale = max($-2000, FRACUNIT)
	end
	if xs.spinstretch
		xs.spinstretch = $-1
		if xs.spinstretch > 1
			local ghs = P_SpawnGhostMobj(s)
			ghs.scale = s.scale
			ghs.blendmode = AST_ADD
			ghs.color = s.color
			ghs.colorized = true
			ghs.fuse,ghs.tics = 9,10
			ghs.scalespeed = 8000
			ghs.frame = ($ & ~FF_TRANSMASK)|FF_TRANS70
			ghs.destscale = 1
			ghs.spritexscale = s.spritexscale
			ghs.spriteyscale = s.spriteyscale
			ghs.momx = -s.momx
			ghs.momy = -s.momy
		end
		xs.alteredscale = true
		s.spritexscale = min($+5500, FRACUNIT)
		s.spriteyscale = max($-6500, FRACUNIT) 
		if s.spriteyoffset > 0 --Very often used together.
			s.spriteyoffset = max($-FRACUNIT, 0)
		elseif s.spriteyoffset < 0
			s.spriteyoffset = min($+FRACUNIT, 0)
		end
	end
	if xs.divestretch --Very particular one.
		if xs.divestretch == 4
			s.spriteyscale = FRACUNIT+20000
			s.spritexscale = FRACUNIT-13000
		elseif xs.divestretch == 3
			s.spriteyscale = FRACUNIT+15000
			s.spritexscale = FRACUNIT-10000		
		else
			s.spriteyscale = max($-10500, FRACUNIT) 
			s.spritexscale = min($+6000, FRACUNIT)		
		end			
		xs.divestretch = $-1
	end
	if xs.edgestretch
		xs.alteredscale = true
		xs.edgestretch = $-1
		s.spritexscale = min($+1800, FRACUNIT)
		s.spriteyscale = max($-6200, FRACUNIT) 			
	end
end

--Oh my.
XSon.Bonking = function(p,s,xs)
	xs.bonkeddash = 0
	xs.snapdivewait = 3
	if (xs.bonked > 1)
		xs.rollcd = max($,2)
		xs.spin = 0
		if xs.leappenalty
			P_SetObjectMomZ(s, (-3+xs.bonked)<<16)
		else
			P_SetObjectMomZ(s, 2<<16)
		end
		s.angle = p.drawangle
		P_InstaThrust(s, p.drawangle, min((xs.bonked*-4), -18)<<16)	
		p.powers[pw_nocontrol] = max($, xs.bonked)
	end
	if P_IsObjectOnGround(s) and (xs.bonked < 8)
		xs.bonked = 0
		xs.leappenalty = nil	
		s.spritexscale = FRACUNIT+3500
		s.spriteyscale = FRACUNIT-1500
		s.spriteyoffset = 3<<16
		s.XSstretch = {timer = 3, Xspeed = 1150, Yspeed = 500}	
	else
		xs.bonked = max($-1, 0)
		xs.ringburstcd = max($,2)
		xs.damageboost = nil
		if p.panim != PA_PAIN --Stay in this pose for now.
			s.state = S_PLAY_PAIN
		end
		if p.powers[pw_flashing] 
			if p.powers[pw_flashing] > 99 and p.powers[pw_flashing] < 180
				p.powers[pw_flashing] = 99
			end
		end
	end	
	if not xs.bonked and not P_IsObjectOnGround(s)
		p.pflags = $|PF_JUMPED & ~PF_THOKKED
		s.state = S_PLAY_XDIVEFLIP1
		s.spriteyoffset = 0
		xs.frontflip = 0
		s.spritexscale = FRACUNIT-1000
		s.spriteyscale = FRACUNIT+4000
		s.spriteyoffset = 4<<16
		s.XSstretch = {timer = 3, Xspeed = 334, Yspeed = 1250}			
		P_SetObjectMomZ(s, -3<<16, true)
		xs.leappenalty = nil
	end
	p.xswallrun = nil
end

--Clumping the alteredscale and rollangle adjusting timer stuff in this one little function.
XSon.AlterScaleorRoll = function(p,s,xs)
	--Change everything back when we're done!
	if xs.alteredscale --Altered spritex/y scale. Should we change back?
		if not (xs.jumpstretch or xs.painstretch or xs.spinstretch or xs.zipstretch or xs.zipstretch2
		or xs.edgestretch or xs.snapdivetrick or xs.divestretch or s.XSstretch)
			s.spritexscale = FRACUNIT
			s.spriteyscale = FRACUNIT
			xs.alteredscale = false
		end
	end
	if xs.alteredoffset --Altered offset, change it back when we're done.
		if not xs.jumpstretch and not xs.spinstretch and not xs.poutydrop and not xs.divestretch  and not xs.zipstretch2
			s.spritexoffset = 0
			s.spriteyoffset = 0
			xs.alteredoffset = false
		end
	end
	if xs.alteredrollangle --Altered rollangle. Takes priority over rolltimers
		xs.rolltimer = 0
		if not xs.painstretch and not xs.snapdive and p.xswallrun == nil
			s.rollangle = 0
			xs.snapdiveroll = 0
			xs.alteredrollangle = false
		end
	elseif xs.rolltimer	--Gradually adjust character rollangle!
		if P_IsObjectOnGround(s)
		or xs.snapdive and (xs.snapdive <= 2)
		or (p.pflags & PF_JUMPED) and not xs.snapdivetrick
			if xs.rollangle
				s.rollangle = 0
			end
			xs.rolltimer = 0 --Time to stop!
		else	
			xs.rolltimer = $-1
	--		CONS_Printf(p,"Recurring rollangle = "+(xs.rollangle or 1)/ANG1+" Timer = "+xs.rolltimer)	
			local diff = xs.rollangle
			local diff2 = xs.rollspeed
			
			if (diff > ANGLE_180)
				diff = InvAngle(diff)
			end
			if (diff2 > ANGLE_180)
				diff2 = InvAngle(diff2)
			end			
			if abs(abs(diff)-abs(diff2)) < abs(xs.rollspeed) --We've caught up, let's stop here.
				xs.rolltimer = 0
			else
				xs.rollangle = $+(xs.rollspeed*xs.rolldirection)
				s.rollangle = xs.rollangle
			end
		end
		if not xs.rolltimer
			xs.alteredrollangle = true
		end
	end
end

--For the half pipe mod. Stolen from SA-Sonic like many things in this mod.
XSon.HalfPipeWallRun = function(p,s,xs)		
	if xs.spin == 1
	or (p.pflags & PF_JUMPED) or s.XStransform 
	or ((p.pflags & PF_THOKKED) and not (p.pflags & PF_SPINNING))
	or s.wallspintrans == 1
		s.wallspintrans = 0 
	elseif not p.wallrun
		if (p.pflags & PF_SPINNING)
			if p.ceilingrun
				s.wallspintrans = max($,32)
				s.wallspintrans2 = true
				s.walljumpheightnerf = nil
				p.pflags = $ & ~PF_STARTDASH
			else
				if not P_IsObjectOnGround(s)
				and s.wallspintrans2 --We came from the ceiling and are heading back down, stay in ball a little longer!
					s.wallspintrans = max($-1, 0)
				else
					s.wallspintrans = max($-4, 0)
				end
			end
		end
		if P_IsObjectOnGround(s)
		and (p.panim <= PA_DASH) and s.wallspintrans
			p.pflags = $|PF_SPINNING & ~PF_STARTDASH & ~PF_JUMPED
			s.state = S_PLAY_ROLL
		end
	end
	if s.walljumpheightnerf --The height gained is ridiculous, nerf it by 1/3
	and not (p.wallrun or p.ceilingrun)
		s.walljumpheightnerf = nil
		if (s.momz*P_MobjFlip(s) > 1) and not P_IsObjectOnGround(s)
			s.momz = $*2/3
		end
	end	
	if not s.wallspintrans --It's over, remove wallrunning values.
		if not p.wallrun
			s.wasnotwallspin = true
			s.walljumpheightnerf = nil
			s.wallspintrans2 = nil
			xs.forcecontrolside = nil
		end
	end
end

--Apply the friction modifiers!
XSon.FrictionModifiers = function(p,s,xs)	
	if (xs.runfriction < xs.groundfriction) --ANY of these happen at all, your superfriction is gone, mister.
	or (p.speed < 50*s.scale) and (xs.frictionspeedtreshold > 4)
	or (p.cmd.buttons & BT_JUMP) and (xs.jump <= 2) and (p.pflags & PF_JUMPED) and (p.pflags & PF_STARTJUMP)
	or not xs.direct and not xs.zipdashslide
	or (p.pflags & (PF_SPINNING|PF_SLIDING|PF_STARTDASH|PF_FULLSTASIS|PF_STASIS))
	or p.panim == PA_PAIN
	or (p.speed > 220*s.scale) --Put a limit on it
	or xs.ExitState or p.gotflag
	
		xs.runfriction = xs.groundfriction
		xs.frictionreset = 180
		xs.frictionlock = 0
		
	elseif (xs.runfriction >= xs.groundfriction)
		if xs.frictionlock
			if (XS_Super(p) == 3 or (p.isxmomentum and not p.xmomtoggledoff))
			and xs.direct > 10
				if (leveltime % 8 == 0) --Lose friction slower. 'cause you're technically floating, idiot.
					xs.frictionlock = $-1
				end
			else
				xs.frictionlock = $-1
			end
		else
			if (p.isxmomentum and not p.xmomtoggledoff)
				xs.runfriction = $-1 --Xmom's weird.
			elseif XS_Super(p) == 3
				xs.runfriction = $-29
			else
				xs.runfriction = $-45
			end
		end
	end
	
	s.friction = xs.runfriction
	
	if xs.frictionreset
	xs.frictionreset = $-1
		if not xs.frictionreset and not (xs.spin and p.speed > 50*s.scale) --Failsafe.
			xs.runfriction = xs.groundfriction
			xs.frictionreset = 180
			xs.frictionlock = 0
		end
	end	
end	

--Jump Squash and Stretch visual effects, along with some trailing.
XSon.JumpStretch = function(p,s,xs)
	if not (p.pflags & PF_JUMPED)
	or not (p.panim == PA_JUMP or p.panim == PA_ROLL or xs.snapdivetrick or ((s.sprite2 & ~FF_SPR2SUPER) == SPR2_SPNG))
	or (s.momz*P_MobjFlip(s) <= 0)
		xs.jumpstretch = 0 return --
	end
	xs.jumpstretch = $-1
	
	if (xs.jumpstretch > 2)
		local SUPER = (s.sprite2 & FF_SPR2SUPER) and FF_SPR2SUPER or 0
		if (xs.jumpstretch > 3)
			s.sprite2 = SPR2_SPNG|SUPER
			s.frame = $ & ~FF_FRAMEMASK
			xs.alteredoffset = true
			s.spriteyscale = FRACUNIT+22500
			s.spritexscale = FRACUNIT-26500
			s.spriteyoffset = -7<<16 --Make it look like we're sticking to the ground for 1 frame longer!
		else
			s.sprite2 = SPR2_ROLL|SUPER
		end
		local ghs = P_SpawnGhostMobj(s) --Little jumptrail.
		ghs.scale = s.scale
		ghs.blendmode = AST_ADD
		ghs.color = s.color
		ghs.colorized = true
		ghs.fuse,ghs.tics = 7,9
		ghs.scalespeed = 8000
		ghs.frame = ($ & ~FF_TRANSMASK)|FF_TRANS80
		ghs.destscale = 1
		ghs.spritexscale = s.spritexscale
		ghs.spriteyscale = s.spriteyscale
		ghs.momz = s.momz*-1		
	end
	xs.alteredscale = true
	s.spritexscale = min($+8500, FRACUNIT)
	s.spriteyscale = max($-9500, FRACUNIT) 
end

--What's the mobj's true floorheight? Accounts for FOFs.
XSon.GetFloorZ = function(s, SEC, FLIP)
	SEC = (SEC) or R_PointInSubsector(s.x, s.y).sector
	if not (SEC and SEC.valid and SEC.floorheight != nil) return end
	local FLOORZ
	local FLOORZ = SEC.floorheight
	for fof in SEC.ffloors() --Is a FOF higher?
		if (fof.flags & FF_EXISTS) and (fof.flags & FF_SOLID)
		and (fof.topheight > FLOORZ) and (fof.topheight <= s.z)
			FLOORZ = fof.topheight
		end
	end return FLOORZ --
end

--Shortcut for returning momentum angle.
XSon.MOMANGLE = function(s) if not (s.momx or s.momy) return 0 end
	return R_PointToAngle2(s.x, s.y, s.x+s.momx, (not ((s.flags2 & MF2_TWOD) or twodlevel) ) and s.y+s.momy or s.y)
end

--Momentum angle compared to a line.
XSon.WallMOMANG = function(s,line) 
	local x,y = P_ClosestPointOnLine(s.x, s.y, line) 
	local TOWALLANG = R_PointToAngle2(s.x, s.y, x, y)
	local ANGDIFF,PLUS = XS_AngleDifference(XSon.MOMANGLE(s), TOWALLANG, 2)
	return ANGDIFF,PLUS,TOWALLANG,x,y --
end

--Have a valid t or MANUAL!
XSon.GradualTurn = function(s, t, MAXTURN, MANUAL, STARTANGLE)
	MAXTURN = (MAXTURN) or ANG10
	STARTANGLE = (STARTANGLE) or s.angle
	local destANGLE = (MANUAL) or R_PointToAngle2(s.x, s.y, t.x, t.y) or s.angle
	local ANGLE,PLUS = XS_AngleDifference(STARTANGLE, destANGLE, 2, MAXTURN)	
	if (ANGLE <= MAXTURN) --Face the angle exactly!
		s.angle = destANGLE
	else --Adjust angle bit by bit.
		s.angle = $-(ANGLE*PLUS)
	end	return ANGLE,PLUS --	
end

--Do a cactus crash
local function CactiCrash(p,s,xs,v)
	local REALV = (v and v.valid) and v or s
	XS_RingBurstSparkles(REALV, nil, SKINCOLOR_RUBY)
	XSon.PuffDust(REALV, SKINCOLOR_FLAME, FF_TRANS10, 24, 5<<16)
	XSon.LightningStrike(REALV, SKINCOLOR_FLAME)
	XS_MediumDust(REALV, SKINCOLOR_FLAME, AST_ADD, nil, nil, s.angle)
	for i = 1,5
		local FIREBALL = XSon.FireBall(REALV, nil, 65356+(12500*i), FRACUNIT/(8-i), FF_TRANS10)
		if FIREBALL and FIREBALL.valid
			FIREBALL.fuse,FIREBALL.tics = $+12,$+12 P_SetObjectMomZ(FIREBALL, (9-i*2)<<16, true)
		end	
	end
	
	local XYDIST,ZDIST = 50<<16, 810<<16
	searchBlockmap("objects",function(v,m)
		if m.valid and (m.type >= MT_CACTI1) and (m.type <= MT_CACTISMALLSEG)
		and not (m.z > v.z+ZDIST) and not (v.z > m.z+ZDIST) and (m.WaitingXTree!=true)
			if (XSon.ScenicCrash(p,s,xs, m, 3, "cacti", nil, "extracacti"))!=true
				P_RemoveMobj(m)
			elseif m and m.valid
				m.WaitingXTree = true
			end
		end
	end, v, v.x-XYDIST,v.x+XYDIST, v.y-XYDIST, v.y+XYDIST)	
end

--Enter a sector and then check if we can go for it.
XSon.CanParkour = function(SEC, p, s, xs)
	if not SEC return false,false end --
	local YEP,CHECKFAIL = true,false
	local ZMAX = 106<<16
	if not (s.eflags & MFE_VERTICALFLIP)
		for fof in SEC.ffloors()
			if fof.valid and (fof.flags & (FF_SHATTER|FF_QUICKSAND|FF_SWIMMABLE))	
			and (s.z-ZMAX <= fof.topheight)
				YEP = false
			end
		end
	else
		for fof in SEC.ffloors()
			if fof.valid and (fof.flags & (FF_SHATTER|FF_QUICKSAND|FF_SWIMMABLE))	
				if (s.z+ZMAX >= fof.topheight) or (s.z-ZMAX <= fof.bottomheight)
					YEP = false
				end
			end
		end
	end
	local type = GetSecSpecial(SEC.special, 1)
	if type --Pain/Death sector.	
		if (type <= 5) 
			if not (xs.supersonic or p.powers[pw_invulnerability] or p.powers[pw_flashing])
				if type == 4 --Electric
					CHECKFAIL = (not (p.powers[pw_shield] & SH_PROTECTELECTRIC))				
				elseif type == 2 -- Water
					CHECKFAIL = (not (p.powers[pw_shield] & SH_PROTECTWATER))
				elseif type == 3 --Fire
					CHECKFAIL = (not (p.powers[pw_shield] & SH_PROTECTFIRE))
				else
					CHECKFAIL = true --generic/spike damage.
				end							
			end
		else --if (type > 5) --Death sector! Never run into these, duh.
			CHECKFAIL = true
		end
	end
	if SEC.damagetype --UDMF pain sectors
		local type = SEC.damagetype
		if (type >= SD_DEATHPITTILT) 
			CHECKFAIL = true
		elseif not (p.powers[pw_invulnerability] or p.powers[pw_flashing] or xs.supersonic)		
			if type == SF_ELECTRIC 
				CHECKFAIL = (not (p.powers[pw_shield] & SH_PROTECTELECTRIC))				
			elseif type == SF_WATER
				CHECKFAIL = (not (p.powers[pw_shield] & SH_PROTECTWATER))
			elseif type == SF_FIRE or type == SF_LAVA
				CHECKFAIL = (not (p.powers[pw_shield] & SH_PROTECTFIRE))
			else
				CHECKFAIL = true --generic/spike damage.
			end
		end
	end
	return YEP,CHECKFAIL
end

--Press jump to avoid cheap deathpit knockbacks, or spin to get back into the fray asap.
XSon.QuickRecover = function(p,s,xs)
	local RB,SPIN = xs.FreeFlight
	if RB
		if not RB.painstun
		or (RB.painstun>=FRACUNIT)
		or RB.MeteorSmashed
		or xs.ChaosSnap or xs.ChaosLambent or xs.snapdivetrick
			return 0 --Can't cancel!
		end
		if RB.refreshC1==1 or RB.painstun==9
			xs.spin = 0
		else
			xs.spin = min(RB.spin, $)
		end
	end
	local SPIN = (xs.spin < 2) and (p.cmd.buttons & BT_SPIN)
	if not (SPIN) and not RB and (xs.spin < 2)
		SPIN = (p.cmd.buttons & (BT_SPIN|BT_CUSTOM1))
	end
	
	local STOP = 0
	if (SPIN) --Quick-recovery
	and (RB or (s.sprite2!=SPR2_CLNG) and not (p.pflags&(PF_FULLSTASIS|PF_SLIDING|PF_STASIS)) )
		STOP = -2
		s.state = S_PLAY_XDIVEFLIP1
		if RB
			local ANG = xs.turnang
			local SPEED = max( (p.speed*3)/2, 80*s.scale)
			if not (xs.direct)
				ANG = R_PointToAngle2(s.x, s.y,s.x+s.momx, s.y+s.momy)
			elseif (abs(p.cmd.sidemove) > 32)
				SPEED = $*3/2
			elseif (p.cmd.forwardmove > 25)
				SPEED = $*2/3	
			end
			P_InstaThrust(s, ANG, SPEED)
			local UD = -16<<16
			if (p.cmd.buttons & BT_JUMP)
				UD = 30<<16
				s.momx,s.momy = $/3,$/3
			else
				local FLOORZ = (s.eflags & MFE_VERTICALFLIP) and s.ceilingz or s.floorz
				if (abs(s.z-FLOORZ) < 350*s.scale)
					UD = 28<<16
				end
			end
			P_SetObjectMomZ(s, UD, true)
			s.anim_duration,s.tics = $+2,$+2
		else
			P_InstaThrust(s, R_PointToAngle2(s.x, s.y,s.x+s.momx, s.y+s.momy), max(p.speed*2/3, 31*s.scale) )
			XSon.Sound(s, sfx_xssfpt, 128)
			p.pflags = $|PF_THOKKED|PF_JUMPDOWN|PF_USEDOWN|PF_SHIELDABILITY
			XS_QuickDust(s)
			XSSpawnJumpDust(s)
			XSGhostTrail(p, s, xs, SKINCOLOR_SKY, 11, FF_TRANS40, FRACUNIT+40000)
		end
		XSon.sparkles(p, s, xs, 65, 10, 11)
		S_StartSound(s, sfx_xssfxk)

		local ghs = XS_CircleWave(s, 1, SKINCOLOR_MINT, 13, FRACUNIT-27000)
		ghs.frame = ($ & ~FF_TRANSMASK)|FF_TRANS80

		XSVoice(p, s, xs, (leveltime%2) and sfx_xsvop7 or sfx_xsvop5, (RB) and "ForceEcho" or nil)
		
	elseif (xs.painrecovery > 29) and (p.cmd.buttons & BT_JUMP) and not RB --Hold the button for all I care, it's fine!
		STOP = 1
		s.state = S_PLAY_ROLL
		P_DoJump(p, true)
	end
	if (STOP != 0)
		XSon.SwapStretch(p, s, xs)
		s.spritexscale,s.spriteyscale = FRACUNIT+15000, FRACUNIT-13000
		s.spriteyoffset = (-2*STOP)<<16						
		for i = 0,4
			S_StopSoundByID(s, sfx_xsvop0+i)
		end
		S_StopSoundByID(s, sfx_xsvoc4) S_StopSoundByID(s, sfx_xssfxj)	
		xs.painrecovery = 0
		xs.alteredrollangle = true
		xs.jump,xs.spin = 99<<16,99<<16
		s.rollangle,p.viewrollangle = 0,0
		xs.rollcd = abs(STOP)*3
		xs.damageboost = nil	
		p.pflags = $|PF_JUMPED
		P_SetObjectMomZ(s, 138500*STOP, true) --Slightly higher than a regular jump	
		
		if RB
			RB.PvPENERGY = max(0, min(10000, $+35))
			RB.refreshC1 = 16
			RB.forceanim,RB.quickroll,RB.painstun,RB.rollD,RB.C2stun,RB.C1stun = nil		
			RB.CantHitLambence = max(RB.CantHitLambence or 0, 11) --I'll be a LITTLE nice here...
			RB.DodgeFrames = max(DodgeFrames or 0, (p.cmd.forwardmove>16) and 9 or (abs(p.cmd.sidemove)>30) and 15 or 11) 
			RB.cantsnap = max(RB.cantsnap or 0, (p.cmd.forwardmove>9) and 2 or 5)
			RB.custom1,xs.custom1 = 99<<16,99<<16
			RB.custom2,xs.custom2 = 0,0
			RB.stripes = 5
			RB.snapCD = max(RB.snapCD or 0, (p.cmd.forwardmove>9) and 5 or 2)
			if RB.SnapOverHeat
				RB.SnapOverHeat = max($-7,0)
			end
			xs.stopstick = 2
			XSon.Sound(s, sfx_xssfe4, 218)
			xs.Ztrail = 12
			xs.windtrail = 12
			xs.hudboost = 5
			xs.ZoomLines = max(xs.zoomlines or 0, 3)
			XSon.DeTransformGFX(p,s,xs, "ghosts")
			XSon.RadiantLight(s, SKINCOLOR_SKY, 12, nil, S|FF_TRANS40)	
			XSGhostTrail(p, s, xs, SKINCOLOR_SUPERSKY5, 15, FF_TRANS30, 2<<16) 
			XSZipTrail(s, nil, 1)
			XS_RingBurstWave(s, 14)
			XS_QuickDust(s, SKINCOLOR_SUPERSKY5, AST_TRANSLUCENT, nil, nil, N|FF_TRANS10)	
			XS_RingBurstDust(p, s, xs, 450*s.scale)	
			RB.painstun = nil
		end		
	end return STOP
end

--Were we teleported? This asks how far.
XSon.Teleport = function(p,s,xs, SPECIALANG)
	local NILSEC = R_PointInSubsectorOrNil(s.x, s.y) --Did we go through a teleporter?
	if NILSEC and xs.lastsector != NILSEC
		if SPECIALANG == nil SPECIALANG = xs.lastangle end
		
		if (s.angle != xs.lastangle) and (s.angle != SPECIALANG)
		and xs.lastx != nil and xs.lastsector!=nil
			return FixedHypot(FixedHypot(s.x-xs.lastx, s.y-xs.lasty), s.z-xs.lastz)
		end
	end return 0 --
end

--Add items to the Scenery Crash Table. (Array, whatever.)
--You can add your own custom items the same way, or exclude them from being destroyed.
local CT = XS_CRASHTABLE  if CT!=nil

CT[MT_FACESTABBERSTATUE] = 	S_BRICKDEBRIS
CT[MT_CRAWLASTATUE] = 		S_BRICKDEBRIS
CT[MT_EGGSTATUE] = 			S_BRICKDEBRIS
CT[MT_TURRET] = 			S_BRICKDEBRIS

CT[MT_DSZSTALAGMITE] = S_TNTDUST_1 --The default state for most of them. Dust is nice and ambiguous.
CT[MT_DSZ2STALAGMITE] = S_TNTDUST_1
CT[MT_BIGGARGOYLE] = 	S_TNTDUST_1
CT[MT_GARGOYLE] = 		S_TNTDUST_1
CT[MT_LAMPPOST1] = 		S_TNTDUST_1
CT[MT_LAMPPOST2] = 		S_TNTDUST_1
CT[MT_STARPOST] = 		S_TNTDUST_1
CT[MT_ROSY] = 			S_TNTDUST_1
CT[MT_CANDLEPRICKET] = 	S_TNTDUST_1

for ITYPE = MT_STALAGMITE0, MT_STALAGMITE9
	CT[ITYPE] = 		S_TNTDUST_1
end

for ITYPE = MT_CACTI1, MT_CACTISMALLSEG
	CT[ITYPE] = 		S_TNTDUST_1
end

for ITYPE = MT_GFZTREE,MT_THZTREEBRANCH
	CT[ITYPE] = 		S_WOODDEBRIS --Turn to wood.
end
for ITYPE = MT_ARIDSIGN_CAUTION,MT_ARIDSIGN_SHARPTURN
	CT[ITYPE] = 		(7<<16)+7 --Special for papersprite wood.
end


CT[MT_XSASSAULTHB] = 	0 --Set to 0 to make an object impossible to fling
CT[MT_XSSNAPDIVE] = 	0
CT[MT_XSEDGESTOP] = 	0
CT[MT_XSSANDBAG] = 		0
CT[MT_XSFLUNG] = 		0
CT[MT_XSAUTOFRAME] = 	0
CT[MT_XSDUMMYTRACKER]= 	0
CT[MT_XSTRACKER] = 		0
CT[MT_XSTRACKER2] = 	0
CT[MT_XTREETRACKER] = 	0
CT[MT_XSPLANETEGG] = 	0
CT[MT_XSPOWERRING] = 	0
CT[MT_XSLAMBENTHB] = 	0
CT[MT_XSOVERGLOW] = 	0
CT[MT_XSUNDERGLOW] = 	0
CT[MT_XSRINGBURSTBALL]=	0


CT[MT_SIGN] = 			0
CT[MT_CEZPOLE1] = 		0
CT[MT_CEZPOLE2] = 		0
CT[MT_CEZBANNER1] = 	0
CT[MT_CEZBANNER2] = 	0
CT[MT_TNTBARREL] = 		0
CT[MT_PROXIMITYTNT] = 	0
CT[MT_SPIKE] = 			0
CT[MT_WALLSPIKE] = 		0
CT[MT_WALLSPIKEBASE] =	0
CT[MT_FIRETORCH] = 		0
CT[MT_FLAMEHOLDER] = 	0
CT[MT_METALSONIC_RACE]= 0
CT[MT_MSSHIELD_FRONT] = 0
CT[MT_MSGATHER] = 		0
CT[MT_ENERGYBALL] = 	0
CT[MT_PINETREE] = 		0

for ITYPE = MT_TELEPORTMAN,MT_SKYBOX
	CT[ITYPE] = 		0
end
for ITYPE = MT_NULL, MT_METALJETFUME
	CT[ITYPE] = 		0
end

end

--Crash through objects! Reiterated version of the SA-Sonic original using the new XSflung object.
XSon.ScenicCrash = function(p,s,xs,v, SUPER, NORESPAWN, FORCESPEED, EXTRA)
	if not (v and v.valid and s.valid) return end --
	local type = v.type
	local STATE,SFX,NUM,NOSLOPE = nil, sfx_wbreak, 6, true
	local INFOHEIGHT = v.info.height or 0

	if NORESPAWN
		if NORESPAWN == "cacti"
			STATE,NUM = S_SONIC3KBOSSEXPLOSION1,12
		else--if NORESPAWN == "kill"
			STATE,NUM = S_WOODDEBRIS,8		
		end
	else	
		local FOUND = CT[type]
		if FOUND==0 or (v.flags2 & MF2_DONTDRAW) or (v.WaitingXTree) or (v.fuse) or (v.XAura)
			return --Don't interact.
		elseif FOUND == S_WOODDEBRIS
			STATE = S_WOODDEBRIS
		elseif FOUND == (7<<16)+7 --Sloped signs.
			STATE,NOSLOPE = S_WOODDEBRIS, nil
		else
			NUM,SFX = 12,sfx_xssfe3

			if FOUND and (FOUND != S_TNTDUST_1)
				STATE = FOUND
			elseif FOUND == S_TNTDUST_1
			or (v.flags & MF_MONITOR)
			or v.XS_CanCrashThroughMe --Supersimple custom method

				STATE = S_TNTDUST_1
				
				if (type == MT_STARPOST) or (v.flags & MF_MONITOR) or (type == MT_ROSY)
					if xs and not xs.darkphase
						return --Can only destroy as Dark Sonic or flung objects.
					end	
				elseif (type >= MT_CACTI1) and (type <= MT_CACTISMALLSEG)
				and (NORESPAWN != "cacti") --Delete other cacti
					if not (SUPER==3 or FORCESPEED or p and p.powers[pw_invulnerability])
						return --Need special conditions first.
					end 
					v.WaitingXTree = true
					CactiCrash(p,s,xs,v)
					if not (v and v.valid)
						S_StartSound(s, sfx_xssfe3) S_StartSound(s, sfx_kc40) return true --
					end 
					NORESPAWN = "cacti"
				end 
			elseif (v.flags & (MF_SOLID|MF_SLIDEME))
			and ( (v.flags & (MF_SCENERY|MF_PUSHABLE)) or (v.info.flags & MF_PUSHABLE) )
			and not (v.flags & (MF_SPRING|MF_ENEMY|MF_SPECIAL|MF_MISSILE|MF_PAIN|MF_BOSS|MF_NOCLIPHEIGHT|
			MF_NOCLIPTHING|MF_BOXICON|MF_NOSECTOR|MF_NOBLOCKMAP|MF_SHOOTABLE|MF_FIRE|MF_NIGHTSITEM|MF_STICKY))
				if (INFOHEIGHT > 180<<16) --Too large, might cause issues rollangling.
				or v.target and v.target.player
				or v.tracer and v.tracer.player
					return --
				end
				STATE = S_GFZDEBRIS
			end 
		end
	end
	
	if STATE != nil
		local SPEED = p and p.speed or abs(s.momx)+abs(s.momy)
		SUPER = (SUPER) or 0
		v.WaitingXTree = true
		
		if NORESPAWN != "cacti"
			s.XS_BreakThroughItAll = (s.XS_BreakThroughItAll or 0)+3 --Prevents infinite collission loops.
		end
		
		local DEBRISLVL = 0 --Lower graphics where needed.
		if NUM != 12 and p and p.mo and p.mo==s
			DEBRISLVL = XSon.DebrisLVL(s, 5)
			if (DEBRISLVL > 140) --Spawn less of them!
				NUM = (DEBRISLVL > 215) and 12 or (DEBRISLVL > 180) and 10 or 8
			end
		end
		
		local TRUESFX = (SFX==sfx_xssfe4) and sfx_xssfx5 or SFX
		if NORESPAWN == "cacti"
			S_StartSound(s, sfx_xssfe3) S_StartSound(s, TRUESFX)		
		else
			local NRVOLUME = ((type == MT_STARPOST) and 80 or 255)
			if SUPER == 3 S_StartSoundAtVolume(ghs, sfx_xssfe3, NRVOLUME) end
			S_StartSoundAtVolume(ghs, TRUESFX, NRVOLUME)	
			
			local ghs = P_SpawnMobjFromMobj(v, 0,0,0, MT_XTREETRACKER)
			ghs.sprite = v.sprite
			ghs.frame = v.frame
			ghs.flags2 = MF2_DONTDRAW
			ghs.target = v
			ghs.scale = v.scale/20
			ghs.scalespeed = $/4
			S_StartSoundAtVolume(ghs, SFX, NRVOLUME)
					
			if NORESPAWN == nil or (type == MT_STARPOST) or (v.flags & MF_MONITOR)
				ghs.iamatree = 300
				if ghs.iamatreeEXTRA == nil ghs.iamatreeEXTRA = {} end
				ghs.iamatreetotal = 1
				
				local EXTRA = v.tracer
				for i = 2,3
					if i==3 EXTRA = v.target end
					if EXTRA and EXTRA.valid and not EXTRA.player and EXTRA.flags
						EXTRA.WaitingXTree = EXTRA.flags
						EXTRA.WaitingXTree = true
						ghs.iamatreeEXTRA[i] = EXTRA
						ghs.iamatreetotal = $+1
						if not (EXTRA.flags & MF_SPECIAL)
							EXTRA.flags = $|MF_NOCLIP|MF_NOCLIPTHING|MF_NOTHINK & ~(MF_PAIN|MF_FIRE)
						end
					end
				end
			end
		end		
		
		local MOMANG,SIDEANG,NUM,RNG = R_PointToAngle2(s.x, s.y, s.x+s.momx, s.y+s.momy), ANGLE_90, 0, nil
		if FORCESPEED
			NUM = (NUM<10) and 10 or $
			SPEED = (FORCESPEED) 
			MOMANG = R_PointToAngle2(s.x, s.y, v.x, v.y)
		elseif not s.momx and not s.momy
			MOMANG = s.angle	
		end
		local DARKAURA = s.color if xs and xs.darkphase DARKAURA = XSon.DarkAura(3) end
		XS_RingBurstSparkles(v, nil, DARKAURA)
		
		if not FORCESPEED
			local BLEND = (xs and xs.darkphase) and AST_REVERSESUBTRACT or AST_ADD
			for i = 1,(EXTRA == "extracacti") and 1 or 2
				if i == 2 DARKAURA = SKINCOLOR_RUBY BLEND = AST_ADD end
				local ghs = XS_CircleWave(s, 1, DARKAURA) --Impact Wave!
				ghs.blendmode = BLEND
				ghs.spritexscale,ghs.spriteyscale = $*3/4,$*3/4
				ghs.sprite = SPR_XSG2
				ghs.frame = 31|FF_TRANS50
				P_Thrust(ghs, ghs.angle-ANGLE_90, (SPEED*2/3)+(s.scale*14*i))
			end
			if EXTRA == "extracacti"
				local ghs = P_SpawnMobjFromMobj(v,0,0,0,MT_SONIC3KBOSSEXPLODE)	
				P_InstaThrust(ghs, MOMANG+(XSLocalRNG(-11,11,ghs)*ANG1), SPEED*4/3)
				ghs.renderflags = RF_FULLBRIGHT|RF_NOCOLORMAPS
				ghs.spritexscale,ghs.spriteyscale = $*5,$*5
				ghs.dispoffset = 131
				P_RemoveMobj(v) return true --
			else
			--	S_StopSoundByID(s, TRUESFX) S_StartSound(s, TRUESFX)
				S_StopSoundByID(s, sfx_kc40) S_StartSound(s, sfx_kc40)
				XSon.Quake(s, 99<<16, 12, (1350<<16)+SPEED)
			end
		end	
		
		for d = NUM,12
			if (d<NUM) d=NUM end --What the hell.
			if (d%2==0) and RNG != nil
				SIDEANG = $+ANGLE_180
			else
				RNG = P_RandomRange(5,50)
			end
			local debris = P_SpawnMobjFromMobj(v, 
			FixedMul(RNG*v.scale, cos(MOMANG+SIDEANG)),
			FixedMul(RNG*v.scale, sin(MOMANG+SIDEANG)), 0, MT_XSFLUNG)		
			
			if debris and debris.valid
				debris.state = STATE
				debris.tics,debris.fuse = 99,(FORCESPEED) and 38 or 44
				debris.renderflags = $|RF_SEMIBRIGHT
				debris.flags = MF_BOUNCE|MF_GRENADEBOUNCE|MF_NOCLIPTHING|MF_SOLID|MF_NOBLOCKMAP
				debris.z = $+(XSLocalRNG(5,80,debris)*v.scale)
				P_InstaThrust(debris, MOMANG+(XSLocalRNG(-20,20,debris)*ANG1), SPEED+8*s.scale)
				P_SetObjectMomZ(debris, (4+(d/2))*s.scale, false)
				debris.radius,debris.height = 8*v.scale, 8*v.scale
				debris.WAITROLL = 99<<16 --These sprites roll on their own.
				debris.spritexscale,debris.spriteyscale = v.spritexscale,v.spriteyscale
							
				if (d%2==0) 
					if STATE == S_WOODDEBRIS
						debris.WOODDEBRIS = true
					elseif STATE != S_TNTDUST_1
						debris.METALDEBRIS = true
					end
					if d == 12 --YOU go ahead and respawn the old tree.
						debris.DEBRISTARGET = v
						debris.sprite = v.sprite
						debris.frame = v.frame
						debris.radius,debris.height = v.radius,v.height
						debris.dispoffset = 88
						debris.fuse = (FORCESPEED or EXTRA) and 26 or 32				
						if NOSLOPE==true
							debris.NOSLOPE = true
						end
						debris.SPECIALDEBRIS =  (INFOHEIGHT < 60<<16) and true or nil --Should be okay to rollangle more often.
						debris.TEMPROLL = ((INFOHEIGHT > 40<<16) and 12 or 22) + ((FORCESPEED) and 8 or 0)
						debris.WAITROLL = (FORCESPEED) and 1 or 5	
						debris.BIGOBJECT = (INFOHEIGHT > 64<<16) and 3 or nil
						debris.mirrored = v.mirrored
						debris.rollangle = v.rollangle
						if (type == MT_STARPOST) or (v.flags & MF_MONITOR) or (type == MT_ROSY)
							debris.spritexscale,debris.spriteyscale = $*3/2,$*3/2
						else
							debris.spritexscale,debris.spriteyscale = $*6/5,$*6/5
						end
					end
				end
				if (DEBRISLVL < 220)
					debris.shadowscale = (d==12) and v.shadowscale or 76536
					if d == 12
					or SUPER==3 and (d%2)
					or SUPER==2 and (d%3==0)
						if not ((d==12) and NORESPAWN == "kill")
							debris.FLAMEDEBRIS = true
							local ths = XSon.FireBall(debris)
							if d==12
								local ghs2 = P_SpawnGhostMobj(v)
								ghs2.scale = v.scale
								ghs2.destscale = 99<<16
								ghs2.fuse,ghs2.tics = 11,11
								ghs2.dispoffset = 11
								ghs2.momx,ghs2.momy,ghs2.momz = debris.momx*2/3, debris.momy*2/3, debris.momz*2/3
								if ths and ths.valid
									ths.spritexscale,ths.spriteyscale = $*2,$*2
								end							
								debris.ELECTRICDEBRIS = true
								XS_RingBurstDust(nil, v, nil, "manual", v.z)
								XSon.PuffDust(v, SKINCOLOR_BEIGE, FF_TRANS10, 44, 6<<16)
							end
						end
						debris.SMOKINGDEBRIS = 1
					end
				end
				XSon.FlungThinker(debris)
			end
		end	
		
		local TRYMOVE = not P_IsObjectOnGround(s)
		if xs and (EXTRA != "extracacti")
			if xs.darkphase
				if s.XS_DarkTable s.XS_DarkTable.noviolence = 0 end
				DARKAURA = XSon.DarkAura(0)
			end
			if xs.darkphase and xs.darkphase != 3
				XSon.AddDarkness(s, 97555)
				if xs.zipdash
					for i = 1,2
						P_TryMove(s, s.x-s.momx, s.y-s.momy, TRYMOVE)
					end
					xs.zipdashpause = (xs.darkphase==1) and 10 or 8
					xs.zipdashx,xs.zipdashy = s.x,s.y										
					xs.rollcd = 11	
					xs.zipdashminus = min($+((xs.darkphase==1) and 25 or 19), 40)
					RestoreZipDashCam(p, s, xs)			
				end
			elseif xs.zipdash and not xs.FreeFlight
				for i = 1,2
					P_TryMove(s, s.x-s.momx, s.y-s.momy, TRYMOVE)
				end
				xs.zipdashminus = min($+14, 35)
			end
		end

		v.flags2 = $|MF2_DONTDRAW 
		
		if not ((type == MT_STARPOST) or (v.flags & MF_MONITOR) or (type == MT_ROSY))
			v.flags = $|MF_NOCLIPTHING
		elseif type == MT_TURRET
			P_KillMobj(v)
		elseif (v.flags & MF_MONITOR) and v.health and (v.flags & MF_SHOOTABLE) and p 
		and not (type == MT_EGGMAN_BOX) and not (type == MT_EGGMAN_GOLDBOX)
			P_DamageMobj(v, p.mo, p.mo)
		end
		
		if NORESPAWN == "kill"
			XSon.FlickerLight(s)
			local ghs = XSon.ChargeSpark(s, nil, 2<<16, nil, AST_ADD, 10, 34|FF_TRANS10)
			ghs.dispoffset = -148
			ghs.scalespeed = $*2
			ghs.momx,ghs.momy,ghs.momz = s.momx/6,s.momy/6,s.momz/6
			if not (type == MT_STARPOST)
				P_RemoveMobj(v)
			end return true --
		end	
		return true --Done!
	end
end