--#==============================================================================#
--Dedicated functions for spawning effects. Most use the MT_XSAUTOFRAME object to spare having to needing billions of freeslots.
--#==============================================================================#
local XSon = XSon
local function P_MobjFlip(s) if s and s.valid and ((s.eflags&MFE_VERTICALFLIP) or (s.flags2&MF2_OBJECTFLIP)) return -1 end return 1 end
local LASTCHECK,LASTBLOCK = false,false

local RCVAR = CV_FindVar("renderer")
if RCVAR == nil RCVAR = {string="2",value=2} end
local FCVAR = CV_FindVar("chasecam")
if FCVAR == nil FCVAR = {string= "1",value=1} end


--Adjust scalespeed somewhat with your size, but not TOO precisely
local function ADJUSTSP(s, NUM) 
	if (s.scale < 49250)
		return FixedDiv(FixedMul(NUM, s.scale), 49250)
	elseif (s.scale < 77700)
		return NUM
	else
		return FixedDiv(FixedMul(NUM, min(205000, s.scale)), 77700)
	end
end

--Fixes flickering and performance issue for an ancient renderer.
local function Fix_Software(ghs,s)
	if (leveltime==LASTBLOCK) 
	or (isdedicatedserver) and s.player and (s.player==server)
		return --
	end
	if (leveltime==LASTCHECK) ghs.flags2 = $|MF2_DONTDRAW return end
	if s.player and displayplayer==s.player
		if RCVAR.value==1
			local xs = s.player.xsonictable
			if xs
				local xs = s.player.xsonictable
				if xs.alreadytrailed
				or xs.zipdash and (xs.zipdash>6) and not xs.zipdashslide
					ghs.flags2 = $|MF2_DONTDRAW
					LASTCHECK = leveltime
				end
			end
		else
			LASTBLOCK = leveltime --Save performance for OpenGL too, it never deserved this.
		end
	end
end

--A generic expanding wave.		XS_CircleWave(s, STYLE, COLOR, FUSE, CSCALE, ANGLE)
rawset(_G, "XS_CircleWave", function(s, STYLE, COLOR, FUSE, CSCALE, ANGLE)
	local ghs = P_SpawnMobjFromMobj(s, s.momx, s.momy, 0, MT_XSAUTOFRAME)	
	ghs.sprite = SPR_XSG1
	ghs.frame = C|FF_TRANS40
	ghs.blendmode = AST_ADD
	ghs.fuse = (FUSE) or 24
	ghs.sttfade = (FUSE) and FUSE/2 or 14
	if COLOR
		ghs.color = COLOR
		ghs.colorized = true
	end
	ghs.destscale = 99<<16
	ghs.scalespeed = ADJUSTSP(s, FRACUNIT/5)
	if STYLE
		if (STYLE > 1) --flat wave
			ghs.renderflags = RF_FLOORSPRITE|RF_NOSPLATROLLANGLE|RF_NOSPLATBILLBOARD
			ghs.flags2 = $|MF2_SPLAT
			ghs.frame = C|FF_TRANS30
		elseif STYLE == 1 --paper wave
			ghs.tracer = s
			ghs.ditchtracer = 2
			ghs.renderflags = $|RF_PAPERSPRITE|RF_SEMIBRIGHT
			ghs.angle = R_PointToAngle2(s.x, s.y, s.x+s.momx, s.y+s.momy)+ANGLE_90
			ghs.spriteyoffset = $+8<<16
		end
	end
	if CSCALE
		ghs.spriteyscale = CSCALE
		ghs.spritexscale = CSCALE
	end
	if ANGLE --Override whatever's given before.
		ghs.angle = ANGLE
	end
	return ghs --
end)

--Pulse Electricity from your body
XSon.ElectricBody = function(s, FUSE, COLOR)				
	local ghs = P_SpawnMobjFromMobj(s, 0, 0, 0, MT_XSAUTOFRAME)	
	ghs.sprite = SPR_XSG4
	ghs.renderflags = RF_FULLBRIGHT|RF_NOCOLORMAPS
	ghs.frame = H|FF_TRANS10
	ghs.blendmode = AST_ADD
	ghs.fuse = (FUSE) or 6
	ghs.dispoffset = 3
	if (COLOR)
		ghs.color = COLOR
		ghs.colorized = true
	end
	ghs.sttspeed = 1
	ghs.sttfirstframe = H
	ghs.sttfinalframe = L
	ghs.tracer = s
	return ghs
end

--SMSreborn's stretch function. Would've been useful I made this BEFORE I did all the other stretch code.
XSon.Stretch = function(s)
	local yu = s.XSstretch local xs = yu
	--Cancel Conditions	
	if not yu.timer
	
	or (s.spritexscale == FRACUNIT and s.spriteyscale == FRACUNIT) --We're already done!
	
	or yu.air and not (s.momz*P_MobjFlip(s) > 0) --Cancels instantly when we hit a floor.	
	and ( (s.eflags & MFE_JUSTHITFLOOR) or P_IsObjectOnGround(s) )
	
	or yu.ground and not (P_IsObjectOnGround(s) and s.momz*P_MobjFlip(s) <= 0) --Cancels if we leave the floor instead.	
		s.spritexscale,s.spriteyscale = FRACUNIT,FRACUNIT
		s.spritexoffset,s.spriteyoffset = 0,0
		s.XSstretch = nil
		return false --Done!
	end
	
	if yu.Xspeed == nil yu.Yspeed = 5000 yu.Xspeed = 5000 end  --Default values not set...??

	yu.timer = $-1
	
	if s.spritexscale != FRACUNIT
		s.spritexscale = (s.spritexscale > FRACUNIT) and max($-yu.Xspeed, FRACUNIT) or min($+yu.Xspeed, FRACUNIT)
	end
	if s.spriteyscale != FRACUNIT
		s.spriteyscale = (s.spriteyscale > FRACUNIT) and max($-yu.Yspeed, FRACUNIT) or min($+yu.Yspeed, FRACUNIT)
	end	
	--Bonus yoffset movement?
	if s.spriteyoffset != 0 
		s.spriteyoffset = (s.spriteyoffset > 0) and max($-FRACUNIT, 0) or min($+FRACUNIT, 0)
		if s.spritexoffset != 0 
			s.spritexoffset = (s.spritexoffset > 0) and max($-2<<16, 0) or min($+2<<16, 0)
		end
	end
	return s --Still stretching....!
end

--Adventure-style spin trail. Except it's for the zip dash! Since SA-Sonic's version, 
--I made this one compatible with every mobj, not just players.
--There's also extra settings to tamper with, but you don't need to fill in any of them.
rawset(_G, "XSZipTrail", function(s, BLEND, TAPER, FRAME, FUSE, COLOR, CSCALE, RFLAGS, SPRITE)
	local p,xs = s.player, (s.player) and s.player.xsonictable
	local SUPER = 0 --Are you a Super Saiyan?
	local OGMOMX,OGMOMY,OGMOMZ = s.momx,s.momy,s.momz
	if p and xs --Are we a player?
		if (p.powers[pw_carry] != CR_NIGHTSMODE) --Always go during NiGHTS.			
			if xs.ziptrailticket xs.ziptrailticket = max($-1,0) end --count down
			
			if (p.speed < 12*s.scale) and (abs(s.momz) < 3*s.scale) --too slow. Don't spawn!
			or (p.powers[pw_carry] == CR_ZOOMTUBE) --this often blinds our player.
			or p.realmo != s	
				return --
			end
		end
		if xs.alreadytrailed return end --Already did it this frame!
		OGMOMX,OGMOMY,OGMOMZ = s.x-xs.lastx, s.y-xs.lasty,s.z-xs.lastz  --What's our REAL Z momentum?
		SUPER = XS_Super(p)
		if xs.darkphase
			COLOR,BLEND = SKINCOLOR_XSSUPERAURA,AST_REVERSESUBTRACT
		elseif not COLOR
			COLOR = XSon.TrailColor(s, xs) or SKINCOLOR_SILVER
		end
		xs.alreadytrailed = true  --Only once per frame! This'll get reeeeaaaal laggy otherwise.
	else --Spawned from a non-player, apparently!
		if not s.health and not (s.momx or s.momy or s.momz) return end --Never if you've no momentum!
		COLOR = (COLOR) or SKINCOLOR_FLAME
	end
	
	--Setup everything! This lets the function be called without having to manually input everything.
	if (COLOR >= SKINCOLOR_CARBON) and (COLOR <= SKINCOLOR_BLACK)
		BLEND = AST_TRANSLUCENT --AST_ADD would make these colors white.
	else
		BLEND = (BLEND) or AST_ADD
	end
	SPRITE = (SPRITE) or SPR_XTK7
	FRAME = (FRAME) or A|FF_TRANS90
	FUSE = (FUSE) or 16
	RFLAGS = (RFLAGS) or (SUPER==3) and RF_FULLBRIGHT|RF_NOCOLORMAPS or RF_SEMIBRIGHT --Custom renderflags	
	local CSCALEX,CSCALEY = 0,0 --Custom spritey scale?
	if type(CSCALE) == "table" --If it's a table, you can have seperate spritex and spritey values.
		CSCALEX,CSCALEY = CSCALE.EX,CSCALE.EY
	elseif CSCALE
		CSCALEX,CSCALEY = CSCALE,CSCALE
	end
	
	--Adjustments for RENDERER. Assume we're using software unless we're the displayplayer and find out otherwise.
	local RENDERER = 1	
	local DENSITY = 22*s.scale --The distance before drawing another sprite
	local OLDTRANS = (FRAME & FF_TRANSMASK)
	local YOFFSET = 0
	local FIRSTPERSON = false
	if p == displayplayer --This part doesn't need to sync!
		if not (p.awayviewtics) and FCVAR.value
			FIRSTPERSON = true
		end	
		if xs
			if xs.zipdash
				if (abs(OGMOMZ)<16*s.scale) and (p.speed>77*s.scale) and (FUSE > 7)  and not xs.zipdashleap
					FUSE = max(7, $-10) --Performance saver!
				end
				if xs.FreeFlight
					YOFFSET = (p.cmd.buttons & BT_SPIN) and 7*s.scale or 0 --So it doesn't obscure your view
				end
			elseif xs.wallrun 
				DENSITY = $*2/3	
			end
		end
		RENDERER = RCVAR.value
		if RENDERER == 2 --Software can't handle too many translucent sprites. 2:OPENGL 1:SOFTWARE 0: NONE
			DENSITY = P_IsObjectOnGround(s) and 11*s.scale or 8*s.scale
		elseif (FRAME & FF_FRAMEMASK)==A --More opaque in software, pls?
			OLDTRANS = max(FF_TRANS10, OLDTRANS-FF_TRANS30) 
			FRAME = A|OLDTRANS
		end
		local CAMANG = XS_AngleDifference(p.cmd.angleturn<<16, R_PointToAngle2(s.x, s.y, s.x+s.momx, s.y+s.momy) , 2)
		if (CAMANG > ANG10)
			DENSITY = (CAMANG>ANG30) and DENSITY/2 or (CAMANG>ANG20) and DENSITY*2/3 or DENSITY*3/4 
		end
	else
		if xs and xs.FreeFlight and xs.zipdash
			YOFFSET = $+7*s.scale
		end
		if (FRAME & FF_FRAMEMASK)==A and RCVAR.value != 2
			OLDTRANS = max(FF_TRANS10, OLDTRANS-FF_TRANS30) 
			FRAME = A|OLDTRANS
		end
	end

	--Now to calculate how many sprites to draw for the ziptrail.
	local XYDIST = max(1, FixedHypot(OGMOMX, OGMOMY))
	local DIST = max(1, FixedHypot(XYDIST, OGMOMZ))
	local MAXS = min(75, max(1, DIST/DENSITY)) --Draw at least 1 sprite, but never more than 75 for sanity reasons.
	
	local XDIST = FixedDiv(FixedDiv((OGMOMX) or 1, DIST), s.scale) --Can't use normal division because of stupid overflows.
	local YDIST = FixedDiv(FixedDiv((OGMOMY) or 1, DIST), s.scale)
	local ZDIST = FixedDiv(FixedDiv((OGMOMZ) or 1, DIST), s.scale)
	local XADD,YADD,ZADD = -OGMOMX/2,-OGMOMY/2,(-OGMOMZ/2)+1 --Offset values that exist outside of calculations.
	
	--We have the info, time to spawn the trails! MAXS can be any value from 1 to 160. We'll keep counting up until we reach it.
	--The further we go, the further away trails are spawned.
	for i = 1,MAXS,1
		local X,Y,Z = -s.momx/2,-s.momy/2,-s.momz/2
		if i%2==0
			if i%4==0 --Some intentional stuttering to save on performance while still making the trail look "full"
				X,Y,Z = $-s.momx/3,$-s.momy/3,$-s.momz/3
			else
				X,Y,Z = $-s.momx/5,$-s.momy/5,$-s.momz/5
			end
		end
		
		local zip = P_SpawnMobjFromMobj(s, 
		X + XADD + FixedMul(XDIST, DIST),
		Y + YADD + FixedMul(YDIST, DIST),
		Z + ZADD + ((FixedMul(ZDIST, DIST)+YOFFSET)),
		MT_XSZIPDASHTRAIL)
		
		zip.tics,zip.fuse = FUSE+1,FUSE
		zip.scale = (TAPER) and s.scale or s.scale*13/14
		
		if s.player and s.player == displayplayer
			if (i<5) and (FIRSTPERSON==true)
			or (i>20 and (i%2) and abs(p.cmd.sidemove)<8) 
				zip.flags2 = $|MF2_DONTDRAW continue  --Don't blind players in 1st person! Next!
			else
				zip.flags2 = $ & ~MF2_DONTDRAW 
			end
		end
		
		zip.sprite,zip.frame = SPRITE,FRAME
		zip.color = COLOR		
		zip.renderflags,zip.blendmode = RFLAGS,BLEND
		if CSCALE --Custom scale? FRACUNIT is normal scale.
			zip.spritexscale,zip.spriteyscale = CSCALEX,CSCALEY
		end
		if TAPER --Can also expand if you want to for whatever reason. Just set it to 5<<16 or something.
			zip.destscale = TAPER
			zip.scalespeed = ADJUSTSP(s, (FRACUNIT+34500))/FUSE		
			if SUPER == 3 and ((i%2==0) or i==1) and SPRITE==SPR_XTK7 --Super Sonic gets slightly unique taper trails.
				if not (i%6==0)
					zip.sprite = SPR_XSG4
					zip.frame = T|OLDTRANS
					zip.color = COLOR
				else
					zip.sprite = SPR_XSG1
					zip.frame = E|OLDTRANS
					zip.color,zip.colorized = SKINCOLOR_XSSUPERAURA, true
					zip.fuse = FUSE+4
					zip.spritexscale,zip.spriteyscale = $+29750,$+29750
				end
			end
		else
			if (i>20) and (i%2) --Fade out trail! --and (i > MAXS-120) and (MAXS>119) 
				zip.flags2 = $|MF2_DONTDRAW
			end
		end
		DIST = $-DENSITY
	end
end)

--Draw a ghost from point 1 to point 2. Basically a clone of the zip trail, but spoopy and less lenient.
rawset(_G, "XSGhostTrail", function(p, s, xs, COLOR, FUSE, TRANS, CSCALE, XYZ, NOSOFTWARE)
	if xs.didghosttrail return end	--Only once per frame!
	
	xs.didghosttrail = true	
	TRANS = (TRANS) or FF_TRANS80
	FUSE = (FUSE) or 7
	COLOR = (COLOR) or s.color
	
	local FIRSTPERSON = false
	local RENDERER = RCVAR.value
	if p==displayplayer
		FIRSTPERSON = (FCVAR.value) and true or false
	end
	local OGMOMZ = (s.z-xs.lastz) or (s.momz) or 1 --What's our REAL Z momentum?
	local MOMX,MOMY = s.momx,s.momy
	local ANGLE = nil
	if XYZ and type(XYZ)=="table" --Set custom values?
		MOMX,MOMY,OGMOMZ = XYZ.x, XYZ.y, XYZ.z
		if XYZ.angle!=nil
			ANGLE = XYZ.angle
		end
	end
	if ANGLE==nil
		ANGLE = R_PointToDist2(0, 0, R_PointToDist2(0, 0, MOMX, MOMY), OGMOMZ)
	end
	--Calculate all this stuff here before the loop, so the code won't need to calculate stuff for every individual object!
	local DENSITY = (RENDERER==1) and 24*s.scale or 16*s.scale
	local DIST = max(1, min(ANGLE/DENSITY, 180)) 
	local XDIST,YDIST,ZDIST = MOMX/DIST,MOMY/DIST, OGMOMZ/DIST
	
	local ROLLANGLE = 0--s.rollangle
	if (XS_CRAPPYPRIORITIES==1)
		ROLLANGLE = 0
	elseif p.xsringburst
		ROLLANGLE = p.xsringburst.rollangle
	end

	
	local FLIP = ((abs(ROLLANGLE)>ANGLE_90) or (s.frame & FF_VERTICALFLIP)) and FF_VERTICALFLIP or 0	
	local FRAME = (s.frame & ~FF_TRANSMASK)|TRANS|FLIP
	local RFLAGS = xs.supersonic and RF_FULLBRIGHT|RF_NOCOLORMAPS or RF_SEMIBRIGHT
	local OJANGLE = p and p.drawangle or s.angle
	local SCALESPEED = ADJUSTSP(s, (FRACUNIT+35000))/FUSE
	local ITEMTYPE,ghs = (TRANS>FF_TRANS70) and MT_XSZIPDASHTRAIL or MT_THOK  --Use MT_XSZIPDASHTRAIL if we don't need fading.
	
	for i = DIST,1,-1		
		ghs = P_SpawnMobjFromMobj(s, XDIST*i - MOMX/2, YDIST*i - MOMX/2, ZDIST*i - OGMOMZ/2, ITEMTYPE)
		ghs.tics,ghs.fuse = FUSE+15, FUSE
		
		if (i<3) and (FIRSTPERSON==true) or RENDERER==1 and (NOSOFTWARE==true) ghs.flags2 = $|MF2_DONTDRAW continue end --NEXT!
		
		ghs.sprite = s.sprite
		if s.skin
			ghs.skin = s.skin
			ghs.sprite2 = s.sprite2
		end
		ghs.frame = FRAME
		ghs.angle = OJANGLE --Face this way!
	--	ghs.rollangle = s.rollangle
	--	ghs.spritexoffset = s.spritexoffset
		ghs.spriteyoffset = s.spriteyoffset
		ghs.color,ghs.colorized = COLOR, true
		ghs.renderflags,ghs.blendmode = RFLAGS, AST_ADD
		ghs.scalespeed = SCALESPEED
		ghs.destscale = 1
		if CSCALE
			ghs.spritexscale,ghs.spriteyscale = CSCALE,CSCALE
		end
	end
end)

--Spawn SA2-esque wind lines behind Sonic! Function edited from SA-Sonic, who edited it from SMSreborn's windlines"
rawset(_G, "XSWindSpawn", function(s)
	local tmomz,ANG,ghs = s.momz, s.angle+ANGLE_90
	if IsXSonic(s) and P_IsObjectOnGround(s) and s.player and s.player.xsonictable
		tmomz = s.z - s.player.xsonictable.lastz --SRB2's momz values won't be accurate on slopes.
	end
	local ZFLIP = 1 if (s.eflags & MFE_VERTICALFLIP) ZFLIP = -1 end
	local X,Y,Z = XSLocalRNG(9, 35,s)*s.scale, P_RandomRange(9, 35)*s.scale, tmomz+(XSLocalRNG(1,58,s)*ZFLIP)*s.scale
	local MOMANG = R_PointToAngle2(s.x, s.y, s.x+s.momx, s.y+s.momy)
	local THRUST = ((s.momz*ZFLIP<-4*s.scale) and 1 or 3)*s.scale
	local FUSE = (s.eflags & MFE_UNDERWATER) and 7 or 1
	local YOFFSET = (s.eflags & MFE_VERTICALFLIP) and -46<<16 or 0
	for d = -1,1,2	
		ghs = P_SpawnMobjFromMobj(s,(s.momx/2)+FixedMul(X, cos(ANG)),(s.momy/2)+FixedMul(Y, sin(ANG)), Z, MT_XSWINDLINE)
		ghs.angle = MOMANG-(ANG2*3*d)
		ghs.momx,ghs.momy,ghs.momz = s.momx/4,s.momy/4,tmomz/2
		P_Thrust(ghs, ANG, THRUST)
		ghs.flags = $|MF_SCENERY
		if (FUSE!=1)
			ghs.fuse = FUSE
			ghs.state = (leveltime%2) and S_MEDIUMBUBBLE or S_SMALLBUBBLE
		end
		ghs.spriteyoffset = $+YOFFSET
		ANG = $+ANGLE_180
	 end
end)

--Speeding wind lines for falling and rising! Mostly mean to make Blur's skydiving sprites look cooler.
XSon.zwind = function(s, NUM)
	NUM = (NUM) or 2
	local tmomz = s.momz
	local ghs,INVX,INVY,INVZ,SCALE,SCALESPEED
	
	--Spawn lines. Every second one is an opposite-sided replica of the first.
	for d = 1,NUM	
		if not INVX
			INVX = XSLocalRNG(9, 35,s)*s.scale
			INVY = P_RandomRange(9, 35)*s.scale
			INVZ = XSLocalRNG(1, 58,s)*P_MobjFlip(s)*s.scale
			
			ghs = P_SpawnMobjFromMobj(s, INVX, INVY, INVZ, MT_THOK)
			ghs.scale = max( s.scale/3, ghs.scale-P_RandomRange(20000,0) )
			ghs.destscale = 1
			ghs.scalespeed = FRACUNIT/P_RandomRange(33,4)
			
			SCALE = ghs.scale
			SCALESPEED = ghs.scalespeed
		else
			ghs = P_SpawnMobjFromMobj(s, INVX*-1, INVY*-1, INVZ, MT_THOK)
			ghs.destscale = 1
			ghs.scale = SCALE
			ghs.scalespeed = SCALESPEED				
			INVX = nil
		end		

		ghs.sprite = SPR_XSWL
		ghs.frame = E|FF_TRANS70
		ghs.renderflags = RF_SEMIBRIGHT
		ghs.fuse = 9 ghs.tics = 99
		
		ghs.momx = s.momx/3
		ghs.momy = s.momy/3
		ghs.momz = s.momz/-2
		ghs.spriteyoffset = (s.momz < 0) and 40<<16 or -10<<16
		if (s.eflags & MFE_UNDERWATER)
			ghs.fuse = 7
			ghs.state = (leveltime % 2) and S_MEDIUMBUBBLE or S_SMALLBUBBLE
		end
	 end
end


--Function stolen from SA-Sonic to spawn splats on any closeby ceilings/floors. They're final demo coronas, basically
XSon.ShinySplat = function(s, COLOR, FUSE, SCALEMULT, ALWAYS)
	local lightlevel = 0
	local sec = s.subsector.sector
	local cslope,fslope
	local p = s.player local xs = p and p.xsonictable

	if sec != nil 
		--Determine what's a slope and what isn't.
		if not (s.eflags & MFE_VERTICALFLIP)
		and (s.ceilingrover and s.ceilingrover.b_slope and s.ceilingrover.b_slope.zdelta)
		and (s.ceilingrover.flags & FF_RENDERPLANES) and (s.ceilingrover.flags & FF_EXISTS)
		and not (s.ceilingrover.flags & (FF_CUTSPRITES|FF_FOG))		
			cslope = s.ceilingrover.b_slope
		elseif sec.c_slope and sec.c_slope.zdelta and sec.ceilingpic != "F_SKY1"
			cslope = sec.c_slope		
		end
		if sec.f_slope and sec.f_slope.zdelta and sec.floorpic != "F_SKY1"
			fslope = sec.f_slope		
		end
		if not ALWAYS --Ignore light if this is checked.
			lightlevel = sec.lightlevel or 0 --First, calculate our light!
			for fof in sec.ffloors()
				if fof and (fof.flags & FF_EXISTS) 
					if not (fof.flags & FF_NOSHADE) and not (fof.flags & FF_DOUBLESHADOW)
					and (s.z < fof.bottomheight)
						lightlevel = fof.toplightlevel

					elseif (fof.flags & FF_CUTSPRITES) and ((s.z >= fof.bottomheight) and (s.z+(s.height/2) < fof.topheight))
						lightlevel = fof.toplightlevel+20
					end
				end
			end
			if (lightlevel > 239) return true end --Too bright. Don't render.
		end
	end
	
	local hyper,lightswitch = 0,false
	local ths,thss
	if p and p.panim == PA_EDGE and P_IsObjectOnGround(s) --smol
		SCALEMULT = $+25
		hyper = $+FRACUNIT
	end
	if s.XSPLATLIGHT == nil s.XSPLATLIGHT = {} end

	for d = -1,1,2 --Now let's spawn some splats!
	
		if ((d == 1) and not (s.z > s.floorz+150<<16) )
		or ((d == -1) and not (s.z < s.ceilingz-150<<16) )
			local ghs --Start as nil everytime!		
			
			if d == -1
			
				if not (s.XSPLATLIGHT[d] and s.XSPLATLIGHT[d].valid)
					ghs = P_SpawnMobj(s.x,s.y,s.ceilingz-5600, MT_XSOVERGLOW)
				else
					ghs = s.XSPLATLIGHT[d]
					P_MoveOrigin(ghs,s.x,s.y,s.ceilingz-5600)
				end
				ghs.eflags = $|MFE_VERTICALFLIP ghs.flags2 = $|MF2_OBJECTFLIP
				ghs.floordist = s.z-s.ceilingz-(150<<16) --Calculate distance to floor/ceiling.
				ghs.renderflags = $|RF_FULLBRIGHT|RF_FLOORSPRITE|RF_NOCOLORMAPS
				ghs.z = s.ceilingz-5600
				ths = ghs
				
				if cslope 
					P_CreateFloorSpriteSlope(ghs)
					ghs.flags2 = $|MF2_SPLAT
					ghs.renderflags = $|RF_FLOORSPRITE|RF_SLOPESPLAT|RF_NOSPLATBILLBOARD
					ghs.floorspriteslope.o = {x = s.x, y = s.y, z = s.ceilingz}
					ghs.floorspriteslope.xydirection = cslope.xydirection
					ghs.floorspriteslope.zangle = cslope.zangle
				elseif sec.ceilingpic == "F_SKY1" and sec.ceilingheight == s.ceilingz
					ghs.flags2 = MF2_DONTDRAW continue --
				end		
			else
				if not (s.XSPLATLIGHT[d] and s.XSPLATLIGHT[d].valid)
					ghs = P_SpawnMobj(s.x, s.y, s.floorz, MT_XSOVERGLOW)
				else
					ghs = s.XSPLATLIGHT[d]
					P_MoveOrigin(ghs, s.x, s.y, s.floorz)
				end
				ghs.floordist = s.z-s.floorz-(150<<16)
				ghs.renderflags = RF_FULLBRIGHT|RF_FLOORSPRITE|RF_SHADOWDRAW|RF_NOCOLORMAPS
				thss = ghs
				if fslope
					P_CreateFloorSpriteSlope(ghs)
					ghs.flags2 = $|MF2_SPLAT
					ghs.renderflags = $|RF_FLOORSPRITE|RF_SLOPESPLAT|RF_NOSPLATBILLBOARD
					ghs.floorspriteslope.o = {x = s.x, y = s.y, z = s.floorz}
					ghs.floorspriteslope.xydirection = fslope.xydirection
					ghs.floorspriteslope.zangle = fslope.zangle
				elseif sec.floorheight == s.floorz and sec.floorpic == "F_SKY1"
					ghs.flags2 = MF2_DONTDRAW continue --
				end	
				ghs.flags = $ & ~MF_NOCLIPHEIGHT
			end
			
			ghs.flags2 = $|MF2_SPLAT
			ghs.scale = s.scale
			ghs.trframe = 0
			if ghs.floordist
				ghs.floordist = ($/FRACUNIT)+150	
				if ghs.floordist < 0
					ghs.floordist = $*-1
				end	
				if d == -1 and not (s.eflags & MFE_VERTICALFLIP)
					ghs.floordist = max($-50, 0)
				end
				ghs.trframe = (ghs.floordist/29)<<16
			end		
			if not ALWAYS and lightlevel and (lightlevel > 199)
				ghs.trframe = $+((lightlevel-199)/7)<<16
				if (lightlevel > 239)
					lightswitch = true
				end
			end
							
			if leveltime % 2 == 0 --Flickering effect.
				ghs.scale = $*10/9
			elseif leveltime % 3 == 0
				ghs.scale = $*20/19
			end				
			if SCALEMULT != 100 --Multiply the splat's base scale?
				ghs.scale = FixedDiv(FixedMul($, 100), SCALEMULT)
			end
			
			--Force color adjustments or nah?		
			if COLOR
				if COLOR != "nocolor"
					ghs.colorized = true
					ghs.color = COLOR
				end
			elseif p  --auto color
				ghs.colorized = true
				local xs = p.xsonictable
				if xs and xs.supersonic
					if s.XS_modScolor and p.powers[pw_super]
						ghs.color = s.XS_modScolor
					else
						ghs.color = s.color
					end
					hyper = d<<16 --Bigger if Super!
				end
				if G_GametypeHasTeams() and (p.ctfteam)
					ghs.color = (p.ctfteam==1) and SKINCOLOR_RED or SKINCOLOR_NOBLE
				end
			end
			ghs.trframe = min($, 8<<16) --Never go over 8 FRAC!
			ghs.sprite = SPR_XTK8
			ghs.frame = A|min(FF_TRANS70+ghs.trframe+hyper,FF_TRANS90)
			ghs.sttfade = 1
			
			--Adjust size depending on distance to floor/ceiling.
			if d == -1 and not (s.eflags & MFE_VERTICALFLIP)
				ghs.scale = max(
				FixedDiv(FixedMul(($-(200*ghs.floordist)), 100), SCALEMULT-7) ,
				888)
			else
				ghs.scale = max(
				FixedDiv(FixedMul(($-(440*-ghs.floordist)), 100), SCALEMULT) ,
				888)
			end
			ghs.blendmode = AST_ADD
			P_MoveOrigin(ghs, s.x+s.momx, s.y+s.momy, ghs.z)
			ghs.fuse = FUSE ghs.tics = FUSE+9
			if (s.flags2 & MF2_DONTDRAW) and p
				s.flags2 = $|MF2_DONTDRAW
			else
				s.flags2 = $ & ~MF2_DONTDRAW
			end
		end
	end
	return lightswitch,ths,thss --
end

--Requires the same inputs as ShinySplat's! Make sure they're filled in.
XSon.ExpandingGlow = function(s, COLOR, FUSE, SCALEMULT, ALWAYS, BLEND, SCALESPEED)
	local lol,ghs,ths = XSon.ShinySplat(s, COLOR, FUSE, SCALEMULT, ALWAYS)
	for i = -1,1,2
		if (i==1) ghs = ths end
		if ghs and ghs.valid
			ghs.tracer = s
			ghs.frame = ($ & ~FF_TRANSMASK)|FF_TRANS10
			if BLEND
				ghs.blendmode = BLEND
			end
			ghs.sttfade = (FUSE > 10) and FUSE or true
			ghs.scalespeed = (SCALESPEED) or FRACUNIT/4
			ghs.destscale = 99<<16
			ghs.dispoffset = i
		end
	end return ghs,ths
end

--A Vortex Effect, practically ripped from SMSreborn
rawset(_G, "XS_Vortex", function(s, FUSE, CSCALE, CROLL, ALTFRAME, COLOR)	
	local ghs = P_SpawnMobjFromMobj(s, 0,0,0, MT_XSAUTOFRAME)	
	ghs.sprite = SPR_XSG1
	ghs.frame = (ALTFRAME) or R|FF_TRANS10 --Different frame?
	ghs.fuse = (FUSE) or 16
	ghs.blendmode = AST_ADD
	ghs.renderflags = RF_FULLBRIGHT|RF_NOCOLORMAPS
	if ghs.color
		ghs.color = (COLOR)
		ghs.colorized = true
	end
	ghs.destscale = 99<<16
	ghs.sttfade = true
	if (CSCALE) --Scale sprite, not actual scale! Use FRACUNITs
		ghs.spriteyscale = CSCALE
		ghs.spritexscale = CSCALE
	end	
/*	if (CROLL) --ANG20?
		ghs.sttroll = CROLL
	else
		ghs.sttroll = (leveltime % 2) and ANG20*3 or -ANG20*3
	end */
	return ghs --
end)

--Rocks flying out of the ground or from the walls.
--Higher range number means higher spewing rocks with less horizontal distance.
rawset(_G, "XS_CraterSpew", function(s, ANGLE, range, PERFORMANCE) //speed, vertical, extramom, extramomang)
	if ((s.eflags & MFE_TOUCHWATER) and not (s.eflags & MFE_UNDERWATER)) return end 
	local RNG1 = P_RandomRange(5,20)
	local RNG2 = P_RandomRange(1,360)
	local height = (P_IsObjectOnGround(s)) and 0 or s.height/2
	
	local ghs = P_SpawnMobjFromMobj(s, 0, 0, height, MT_XSFLUNG)
	if ghs and ghs.valid
		ghs.angle = RNG2*ANG1
		if ANGLE
			P_InstaThrust(ghs, ghs.angle, ((50+RNG1*2)*(FRACUNIT/2))/range)
			P_Thrust(ghs, ANGLE, ((50+RNG1*2)*(FRACUNIT/2))/range)
		else
			P_InstaThrust(ghs, ghs.angle, ((50+RNG1*2)<<16)/range)
		end
		P_SetObjectMomZ(ghs, P_RandomRange(-4,range)<<16)
		ghs.sprite = SPR_ROIJ
		ghs.frame = min(E, RNG1/4)
		ghs.scale = $/4
		ghs.spriteyscale = $*(RNG1/5)
		ghs.spritexscale = $*(RNG1/5)
		if PERFORMANCE
			ghs.WAITROLL = 8
			ghs.flags = $|MF_NOCLIPHEIGHT & ~MF_BOUNCE|MF_GRENADEBOUNCE
		else
			ghs.WAITROLL = 3
			ghs.shadowscale = 8<<16
			ghs.ROCK = true
		end
		if (XS_CRAPPYPRIORITIES==1)
			ghs.WAITROLL = 99<<16
		end
	end
	return ghs --
end)


--A kick flash effect! Uses both a floor and regular sprite normally.
rawset(_G, "XS_KickGFX", function(s, COLOR, XYOFFSET, ZOFFSET, TRACER, CSCALE, NOFLOOR, ANGLE)
	XYOFFSET = (XYOFFSET) or 40*s.scale
	ZOFFSET = (ZOFFSET) or 0
	ANGLE = (ANGLE != nil and ANGLE) or s.angle
	local ths,ghs
	for i = -1,1,2
		ghs = P_SpawnMobjFromMobj(s, FixedMul(XYOFFSET, cos(ANGLE)), FixedMul(XYOFFSET, sin(ANGLE)), ZOFFSET, MT_XSAUTOFRAME)	
		ghs.sprite = SPR_XSG2
		ghs.blendmode = AST_ADD
		ghs.renderflags = RF_SEMIBRIGHT
		ghs.frame = G|FF_TRANS20
		ghs.sttfade = 11
		ghs.sttspeed = 2
		ghs.sttfinalframe = J
		ghs.color = (COLOR) or SKINCOLOR_FLAME
		ghs.fuse = 11
		ghs.scalespeed = FRACUNIT/6
		ghs.destscale = 99<<16
		ghs.spritexscale = (CSCALE) or FRACUNIT
		ghs.spriteyscale = (CSCALE) or FRACUNIT-15000
		
		--Create angled floorsprite too?
		if i == 1
			ghs.renderflags = $|RF_FLOORSPRITE|RF_NOSPLATBILLBOARD|RF_SLOPESPLAT
			ghs.flags2 = $|MF2_SPLAT
			ghs.angle = s.angle+(ANG10*8)
			P_CreateFloorSpriteSlope(ghs)
			ghs.floorspriteslope.o = {x = s.x, y = s.y, z = s.z}
			ghs.floorspriteslope.xydirection = ANGLE
			ghs.floorspriteslope.zangle = ANG1*6
			P_InstaThrust(ghs, ANGLE, 16*s.scale)
			ghs.sttfade = true
--			ghs.sttroll = -ANG2
			if not COLOR
				ghs.color = s.color
			end
	
		else --Non-floorsprite effect.
			ghs.sttroll = ANG1*3
			if TRACER	
				ghs.tracer = TRACER
				ghs.GSoffsetX = (XYOFFSET) or 36*s.scale
				ghs.GSoffsetY = (XYOFFSET) or 36*s.scale
				ghs.GSoffsetZ = (ZOFFSET) or 0
				ghs.GSoffsetang = ANGLE
			else
				P_InstaThrust(ghs, ANGLE, 11*s.scale)
			end
			if NOFLOOR
				return ghs --
			else
				ghs.angle = s.angle+ANGLE_90
				XSWindSpawn(ghs)
			end
		end
		if i == -1 ths = ghs end
	end
	return ths,ghs --
end)

--Leave behind a super-quick kickup of dust.
rawset(_G, "XS_QuickDust", function(s, COLOR, BLENDMODE, OFFSET, TRACER, FRAME)
	local ANGLE = (s.momx or s.momy) and R_PointToAngle2(s.x, s.y, s.x+s.momx, s.y+s.momy) or s.player and s.player.drawangle or s.angle
	FRAME = N|FF_TRANS10
	local ths,ghs
	for i = -1,1,2
		ghs = P_SpawnMobjFromMobj(s,
		s.momx+FixedMul(36*s.scale, cos(ANGLE+(ANGLE_90*i))),
		s.momy+FixedMul(36*s.scale, sin(ANGLE+(ANGLE_90*i))), 
		0, MT_XSAUTOFRAME)	
		ghs.sprite = SPR_XSG2
		ghs.renderflags = RF_PAPERSPRITE|RF_SEMIBRIGHT
		ghs.angle = ANGLE-(ANG1*39*i)
		ghs.frame = (i != 1) and FRAME or FRAME|FF_HORIZONTALFLIP	
		P_Thrust(ghs, ANGLE+(ANG1*39*i), (5*s.scale)) 
		ghs.scale = s.scale/3
		ghs.scalespeed = ADJUSTSP(s, $*4)
		ghs.destscale = 99<<16
		ghs.sttfade = true
		ghs.sttspeed = 3
		ghs.sttfinalframe = O
		ghs.spriteyoffset = $+((OFFSET) or 0)
		if COLOR
			ghs.color = COLOR
			ghs.colorized = true
		end
		ghs.blendmode = BLENDMODE or $
		ghs.tracer = TRACER or nil
		ghs.fuse = TRACER and 7 or 5
		Fix_Software(ghs,s)
		if i == -1 ths = ghs end
	end
	return ths,ghs --
end)

--Leave behind a medium-sized kickup of dust. Literally just stolen from SMSreborn.
rawset(_G, "XS_MediumDust", function(s, COLOR, BLENDMODE, FRAME, WALLRUN, ANGLE)
	ANGLE = (ANGLE) or R_PointToAngle2(s.x, s.y, s.x+s.momx, s.y+s.momy)
	WALLRUN = (WALLRUN) or 36*s.scale
	FRAME = (FRAME) or Q|FF_TRANS10
	local ghs,ths
	local p = s.player local xs = p and p.xsonictable
	for i = -1,1,2
		ghs = P_SpawnMobjFromMobj(s,
		s.momx+FixedMul(WALLRUN*i, cos(ANGLE+ANGLE_90)),
		s.momy+FixedMul(WALLRUN*i, sin(ANGLE+ANGLE_90)),
		0, MT_XSAUTOFRAME)	
		ghs.sprite = SPR_XSG2
		ghs.renderflags = RF_PAPERSPRITE
		ghs.angle = ANGLE-(ANG1*39*i)
		P_Thrust(ghs, ANGLE+(ANG1*39*i), 5*s.scale) 
		ghs.frame = (i == 1) and FRAME or FRAME|FF_HORIZONTALFLIP	
		ghs.fuse = 17
		ghs.scalespeed = ADJUSTSP(s, $*3)
		ghs.destscale = 99<<16
		ghs.sttfade = true
		ghs.sttspeed = 2
		ghs.sttfinalframe = W
		ghs.color = COLOR or $
		ghs.colorized = (COLOR) and true or false
		ghs.blendmode = BLENDMODE or $
		--Wallrun version?
		if WALLRUN != 36*s.scale
			if p and p.xswallrun 
				if s.momz
					local TRANS = FF_TRANS90- (FixedInt(abs(s.momz/3))<<16)
					ghs.frame = ($ & ~FF_TRANSMASK)+FRACUNIT+ min(FF_TRANS80, max(TRANS, 0))
					--Adjust with player strafe a bit.
					if (abs(p.cmd.sidemove) > 9)
						local WR = p.xswallrun 
						local SIDEMOVE = abs(p.cmd.sidemove)*s.scale
						local SANG = (p.cmd.sidemove < 0) and WR.faceangle+ANG1*91 or WR.faceangle-(ANG1*91)
						P_TryMove(ghs, ghs.x+FixedMul(SIDEMOVE*-1, cos(SANG)), ghs.y+FixedMul(SIDEMOVE*-1, sin(SANG)), true)
						P_SetObjectMomZ(ghs, -3<<16, true)
						P_Thrust(ghs, WR.faceangle+ANGLE_90, (p.cmd.sidemove < 0) and ghs.scale*9 or -ghs.scale*9)
						if i != 1
							ghs.z = $+(SIDEMOVE/2)*P_MobjFlip(s)
						else
							ghs.z = $-(SIDEMOVE/2)*P_MobjFlip(s)
						end
					end	
				end
			--	ghs.rollangle = s.rollangle*3*i	
			elseif ANGLE == s.angle
				P_Thrust(ghs, ANGLE, -16*s.scale)
			end
		else
			Fix_Software(ghs,s)	
		end	
		if i != 1
			ths = ghs
		end
	end
	return ghs,ths --
end)

--Zipdash dust! ...mostly obscured by the trail, but sure, Blur wants to mimmick this alot!
rawset(_G, "XS_CartoonDust", function(s, COLOR, BLENDMODE, CSCALE)
	local ANGLE = R_PointToAngle2(s.x, s.y, s.x+s.momx, s.y+s.momy)
	local ghs,ths
	local RNG1 = XSLocalRNG(10,24,s)
	local RNG2 = XSLocalRNG(-5000, 25000,s)
	local MINX,MINY = min(50<<16, s.momx/3),min(50<<16, s.momy/3)
	for i = -1,1,2
		ghs = P_SpawnMobjFromMobj(s,
		MINX+FixedMul(RNG1*s.scale, cos(ANGLE+(ANGLE_90*i))),
		MINY+FixedMul(RNG1*s.scale, sin(ANGLE+(ANGLE_90*i))),
		0, MT_XSAUTOFRAME)	
		ghs.sprite = SPR_XSG2
		ghs.renderflags = RF_PAPERSPRITE
		ghs.frame = (i == 1) and X|FF_TRANS30 or K|FF_TRANS30 --FF_HORIZONTALFLIP doesn't work here...
		ghs.sttfinalframe = (i == 1) and Z or M
		ghs.angle = ANGLE-(ANG20*i)
		P_Thrust(ghs, ANGLE+(ANGLE_90*i), (6*s.scale)+RNG2)
		ghs.momx,ghs.momy = $+s.momx/2,$+s.momy/2
		ghs.fuse = 3
		ghs.scalespeed = ADJUSTSP(s, FRACUNIT/3)
		ghs.destscale = 99<<16
		ghs.sttspeed = 1
		if CSCALE
			ghs.spritexscale,ghs.spriteyscale = CSCALE,CSCALE
			ghs.frame = $+2<<16
		else
			ghs.spritexscale = FRACUNIT+RNG2
			ghs.spriteyscale = FRACUNIT+RNG2
			if (RNG1 < 17)
				ghs.fuse = $+1
				ghs.frame = $+(2<<16)
			end			
		end
		if COLOR
			ghs.color = COLOR
			ghs.colorized = true
		end
		ghs.blendmode = BLENDMODE or $
		Fix_Software(ghs,s)			
		if i != 1
			ths = ghs
		end
	end
	return ghs,ths --
end)

--Shortcut for directional dust. Not the one used for his jump.
rawset(_G, "XSSpawnDust", function(s, xspeed, xscaler, ziprun)
	if (s.eflags & (MFE_GOOWATER|MFE_TOUCHLAVA)) return end --
	local speed = xspeed or -10<<16
	local scaler = xscaler or 10
	local angle = R_PointToAngle2(s.x, s.y, s.x+s.momx, s.y+s.momy)
	local SANGLE = (leveltime%3==0) and ANG1*15 or (leveltime%2==0) and ANG30 or 0
	for d = -1,1 --Loops within loops for ultimate power!
		for i = 0,1
			local extra = 0
			if i == 1 extra = speed/3 end
			local offset = -ANGLE_22h*i
			local ghs
			if (s.eflags & MFE_UNDERWATER) --Bubbles when underwater
				ghs = P_SpawnMobjFromMobj(s, 0,0,0, MT_SMALLBUBBLE)
				ghs.fuse = 8
			else
				if ziprun --Spawn big ones from your sides instead.
					if d == 0 or i == 1 continue end
					local RNG1 = XSLocalRNG(30+d,53,s)
					ghs = P_SpawnMobjFromMobj(s,
					FixedMul((RNG1/3)*s.scale*d, cos(angle+ANGLE_90)), 
					FixedMul((RNG1/3)*s.scale*d, sin(angle+ANGLE_90)), 0, MT_XSAUTOFRAME)
					ghs.scalespeed = ADJUSTSP(s, $+ ( (32000+RNG1*3999) /5))
					ghs.scale = ($/3)+(RNG1*1000)
					ghs.sprite = SPR_DUST
					if leveltime % 4 == 0
						ghs.sttspeed = 4
						ghs.frame = B|FF_TRANS40
					else
						ghs.frame = A|FF_TRANS50
						ghs.sttspeed = 3
					end
					ghs.fuse = 25
					ghs.sttfade = 25
					ghs.sttfinalframe = D
					ghs.sttYspeed = -52000
				else
					ghs = P_SpawnMobjFromMobj(s, 0,0,0, MT_SPINDUST)
					ghs.renderflags = $|RF_PAPERSPRITE
					ghs.flags2 = $|MF2_SHADOW
					ghs.angle = (ANGLE_45*d)+ANGLE_90+offset+angle
					ghs.fuse = 30-i-scaler
				end
				if (s.eflags & MFE_TOUCHWATER)
					ghs.flags2 = $|MF2_SHADOW
					ghs.frame = ($ & ~FF_TRANSMASK)|FF_TRANS90
					ghs.color = SKINCOLOR_AETHER
				else
					ghs.color = SKINCOLOR_BEIGE
				end
				ghs.colorized = true
			end
			P_InstaThrust(ghs, SANGLE+(ANGLE_45*d)-offset+angle, speed+extra)
			ghs.scalespeed = ADJUSTSP(s, $*10/scaler)
			ghs.scale = $/3
			ghs.destscale = 99<<16
			if ziprun
				ghs.momx = -$ +(s.momx/3)
				ghs.momy = -$ +(s.momy/3)
			end
			Fix_Software(ghs,s)				
		end
	end
end)

--Shortcut for jump-boosting dust.
rawset(_G, "XSSpawnJumpDust", function(s, xspeed, xscaler, xs)
	local speed = xspeed or 10<<16
	local scaler = xscaler or 10
	local angle = R_PointToAngle2(s.x, s.y, s.x+s.momx, s.y+s.momy)
	local extraspeed = 0
	local offset = 0
	local SANGLE = (leveltime%2) and ANGLE_22h or 0
	
	for d = 1,8 --Loops within loops for ultimate power!
		for i = 0,1
			local ghs = P_SpawnMobjFromMobj(s,0,0,0, MT_SPINDUST)
			if i == 1
				offset = ANGLE_22h
				extraspeed = FixedDiv(FixedMul(speed, 40), 18)
			end
			P_InstaThrust(ghs, SANGLE+(ANGLE_45*d)-offset, speed+extraspeed)
			ghs.renderflags = RF_PAPERSPRITE
			ghs.angle = (ANGLE_45*d)+ANGLE_90-offset
			ghs.scalespeed = ADJUSTSP(s, $*10/scaler)
			ghs.destscale = 99<<16
			ghs.flags2 = $|MF2_SHADOW
			if (s.eflags & MFE_TOUCHWATER)
				ghs.color = SKINCOLOR_AETHER
			else
				if xs and xs.snapdivetrick
					ghs.color = SKINCOLOR_WHITE
					ghs.renderflags = $|RF_SEMIBRIGHT
				else
					ghs.color = SKINCOLOR_BEIGE
					local FLOORZ = (s.eflags & MFE_VERTICALFLIP) and s.ceilingz or s.floorz
					if (abs(s.z-FLOORZ) < 750*s.scale)
						ghs.z = FLOORZ
					end
				end
			end
			
			ghs.colorized = true
			ghs.fuse = 35-i
			Fix_Software(ghs,s)	
		end
	end
end)

--Water skipping sprites for running on water. waterdust" 
rawset(_G, "XS_WaterSkip", function(p,s,xs, FRAC)
	local ANGLE = R_PointToAngle2(s.x, s.y, s.x+s.momx, s.y+s.momy)		
	FRAC = FRAC or 20*s.scale
	for d = -1,1,2			
		local dust = P_SpawnMobjFromMobj(s, FixedMul(FRAC,cos(ANGLE+(ANGLE_90*d))), FixedMul(FRAC,sin(ANGLE+(ANGLE_90*d))), 0, MT_XSAUTOFRAME)	
		dust.sprite = SPR_XSG2
		dust.sttspeed = 1
		
		local RNG = XSLocalRNG(1,60,s)
		local RNG2 = XSLocalRNG(2,6,s)
		local horflip = FF_HORIZONTALFLIP
		if d == 1
			horflip = 0
		end	
		dust.tics = 5
		dust.fuse = 5
		dust.frame = A|FF_PAPERSPRITE|(horflip)|FF_TRANS40
		dust.sttlastframe = E
		if (leveltime % 2)
			P_Thrust(dust, ANGLE+(ANG20*4*d), 6*s.scale)
			dust.angle = (ANGLE-(ANG30*d))
		else
			dust.angle = (ANGLE-(ANG20*d))
		end
		P_MoveOrigin(dust, dust.x+(s.momx),dust.y+(s.momy),dust.z)
		dust.momx = $+s.momx/2 dust.momy = $+s.momy/2
		dust.scale = s.scale*9/8		
		dust.destscale = 1
		Fix_Software(dust,s)
	end
end)


--Spawn sparkles in a fixed radius!
XSon.sparkles = function(p,s,xs, RAD, NO, XYMOM, ZMOM, COLOR, BLEND, TRANS, FUSE, CSCALE)
	RAD = (RAD) or 48
	NO = (NO) or 6
	COLOR = (COLOR) or s.color
	BLEND = (BLEND) or AST_ADD
	TRANS = (TRANS) or FF_TRANS10
	FUSE = (FUSE) or 18
	local ADDANG = s.angle
	local RNG = P_RandomRange(-RAD+6, RAD-6) if (NO%2) RNG = $*-1 end
	local RNG2 = P_RandomRange(-RAD,RAD)	
	local HIDE = false 
	if (p!=displayplayer) and (netgame==true)
		if (leveltime%2) and (RCVAR.value!=2)
			HIDE = true
		end 
	elseif p==displayplayer
		if not (p.awayviewtics) and FCVAR.value
			HIDE = true
		end
	end
	
	for i = 1,NO,1 
		ADDANG = $+ANG1*56	
		if i != 1
			if (i%2==0) 
				RNG = $*-1 
				RNG2 = $*-1 
			else
				RNG = P_RandomRange(-RAD+6, RAD-6) if (NO%2) RNG = $*-1 end
				RNG2 = P_RandomRange(-RAD,RAD)
			end
		end
		
		local ghs = P_SpawnMobjFromMobj(s, 
		FixedMul(s.momx+(RNG*s.scale), cos(ADDANG)),
		FixedMul(s.momy+(RNG*s.scale), sin(ADDANG)),
		(s.height/2)+(RNG2*s.scale), MT_XSAUTOFRAME)
		
		ghs.renderflags = RF_FULLBRIGHT|RF_NOCOLORMAPS
		ghs.sprite = SPR_XSG9
		ghs.sttspeed = 1
		ghs.sttfinalframe = R
		ghs.blendmode = BLEND
		ghs.sttfade = min(22, FUSE)
		if not (i%4==0) and not (i%3==0)
		--	ghs.sttroll = (i%2) and -ANG2 or ANG2
			ghs.color = COLOR 
		else
			ghs.color = SKINCOLOR_SILVER
		end
		ghs.spritexscale = (CSCALE) or $/5		
		ghs.spriteyscale = (CSCALE) or $/5
		if xs.leapaftermath
			ghs.scale = (s.scale*xs.leapaftermath)/17
			if (leveltime % 3 == 0) and (COLOR == s.color)
				ghs.color = SKINCOLOR_GOLDENROD
			end
			ghs.fuse = max(4, FUSE-3)
			ghs.frame = $|(min(FF_TRANS90, TRANS+(FF_TRANS10*3)))
		else
			ghs.fuse = FUSE
			ghs.frame = $|TRANS
		end
		ghs.tics = ghs.fuse+1
		ghs.destscale = 99<<16
		if XYMOM
			if XYMOM != 1
				ghs.momx = $+s.momx
				ghs.momy = $+s.momy
				ghs.angle = R_PointToAngle2(ghs.x, ghs.y, s.x, s.y)+ANGLE_180 --Move away from the player.
				P_Thrust(ghs, ghs.angle, XYMOM*89500)	
				if not ZMOM
					P_SetObjectMomZ(ghs, RNG2*(XYMOM or 1)*(s.scale/50), true)
				end
			else
				ghs.z = $+s.momz
			end
		end
		if ZMOM
			P_SetObjectMomZ(ghs, ZMOM, true)
		end
		if HIDE==true
			ghs.flags2 = $|MF2_DONTDRAW
		else
			Fix_Software(ghs, s)
		end
	end
end

--Pop in X Sonic's grinding SoapShoes" from absolutely nowhere for fanservice!
XSon.SoapShoes = function(p, s, xs, EXTRA)
	local ths
	for i = 6,4,-1
		local ghs = P_SpawnMobjFromMobj(s, 0,0,0, MT_XSAUTOFRAME)	
		ghs.sprite = SPR_XSG2
		ghs.frame = (Z+1)|((i!=4) and FF_TRANS40 or FF_TRANS10)
		ghs.renderflags = RF_FULLBRIGHT|RF_NOCOLORMAPS
		ghs.blendmode = AST_ADD		
		ghs.angle = s.player and s.player.drawangle or s.angle
		ghs.scale = (s.scale*i/3)
		ghs.scalespeed = ADJUSTSP(s, FRACUNIT/20)
		ghs.destscale = s.scale
		ghs.tracer = s
		ghs.fuse = 22-i
		ghs.sttfade = ghs.fuse		
		ghs.tics = 99
		ths = ghs
	end
	if EXTRA
		local ghs = XSon.FlickerLight(s, nil, 6, 0, FF_TRANS30, FRACUNIT-18000)
		ghs.spriteyoffset = $-6<<16
		ghs.momx = s.momx/2 ghs.momy = s.momy/2 ghs.momz = s.momz/3
		ghs = XSon.ElectricBody(s, 18, SKINCOLOR_GOLDENROD)
		ghs.spriteyoffset = $-6<<16
		ghs.spritexscale = FRACUNIT-25000
		ghs.spriteyscale = FRACUNIT/2
		for i = -1,2
			if i == 0 continue end
			local ghs = XSon.LightningStrike(s, SKINCOLOR_SUPERGOLD2, nil, true)	
			ghs.rollangle = ((ANG60*2)*i)+ANGLE_180
			ghs.sttroll = (ANG1*16)*i
			ghs.rollangle = ghs.rollangle
			ghs.destscale = s.scale
			ghs.momx = s.momx/3 ghs.momy = ghs.momy/3 ghs.momz = s.momz/3
			ghs.z = $-(90*s.scale*P_MobjFlip(s))
		end
		S_StartSoundAtVolume(s, sfx_laser,59)
		S_StartSound(s, sfx_beelec)
		XSon.sparkles(p,s,xs, 31, 7, 7, -FRACUNIT, SKINCOLOR_SILVER, AST_ADD)
	end
	return ths --
end

--Generic dust!
XSon.PuffDust = function(s, COLOR, TRANS, FUSE, CSCALE)
	FUSE = (FUSE) or 19
	TRANS = (TRANS) or FF_TRANS10

	local ghs = P_SpawnMobjFromMobj(s,s.momx,s.momy,0, MT_XSAUTOFRAME)
	ghs.sprite = SPR_BOM1
	ghs.frame = B|TRANS
	ghs.sttspeed = 3
	ghs.sttfinalframe = F
	ghs.color = (COLOR) or SKINCOLOR_BEIGE
	ghs.colorized = true
	ghs.fuse = FUSE 
	ghs.sttfade = (FUSE == 19) and true or ghs.fuse-2
	ghs.dispoffset = -1
	ghs.scalespeed = FRACUNIT/6
	ghs.destscale = s.scale*10
	ghs.spritexscale = (CSCALE) or $
	ghs.spriteyscale = (CSCALE) or $
	return ghs --
end

--Spawn a ball of fire.(visual)
XSon.FireBall = function(s, COLOR, CSCALE, SCALESPEED, TRANS)
	local ghs = P_SpawnMobjFromMobj(s,0,0,s.height/4, MT_XSAUTOFRAME)
	ghs.sprite = SPR_XSG3
	ghs.renderflags = RF_FULLBRIGHT|RF_NOCOLORMAPS
	ghs.blendmode = AST_ADD
	ghs.frame = N|((TRANS) or FF_TRANS10)
	ghs.sttspeed = 1
	ghs.sttfinalframe = Z+10
	if COLOR
		ghs.color = COLOR
		ghs.colorized = true
	end
	ghs.scalespeed = (SCALESPEED) or FRACUNIT/8
	ghs.destscale = 99<<16
	ghs.fuse = 25
	ghs.sttfade = true
	ghs.dispoffset = 77
	ghs.spritexscale,ghs.spriteyscale = (CSCALE) or $,(CSCALE) or $
	ghs.sttYspeed = -19999
	return ghs --
end

--A vertically squashed flash of Light
XSon.SquashFlash = function(s, COLOR, FUSE, TRANS, CSCALE)
	local ghs = P_SpawnMobjFromMobj(s,0,0,s.height/2, MT_XSAUTOFRAME)
	ghs.sprite = SPR_XSG3
	ghs.renderflags = RF_FULLBRIGHT|RF_NOCOLORMAPS
	ghs.blendmode = AST_ADD
	ghs.frame = M|((TRANS) or FF_TRANS10)
	ghs.scalespeed = FRACUNIT/4
	ghs.destscale = 99<<16
	ghs.fuse = 12
	ghs.sttfade = true
	ghs.dispoffset = 69
	if CSCALE
		ghs.spritexscale = CSCALE
		ghs.spriteyscale = (CSCALE/2)
	else
		ghs.spriteyscale = $/2
	end
	if COLOR
		ghs.color = COLOR
		ghs.colorized = true
	end	
	return ghs --
end

--Sparks flying in both directions from "s"
XSon.SideSparks = function(s, COLOR, FUSE, TRANS, CSCALE, SCALESPEED)
	local ths, ghs
	for i = -1,1,2
		local ghs = P_SpawnMobjFromMobj(s,s.momx+FixedMul(20*s.scale, cos(s.angle+(ANGLE_90*i))), s.momy+FixedMul(20*s.scale, sin(s.angle+(ANGLE_90*i))),s.height/2, MT_XSFLUNG)
		ghs.sprite = SPR_XSG3
		ghs.frame = N|((TRANS) or FF_TRANS10)
		ghs.renderflags = RF_FULLBRIGHT|RF_NOCOLORMAPS
		ghs.blendmode = AST_ADD
		ghs.flags = MF_BOUNCE|MF_GRENADEBOUNCE|MF_NOCLIPTHING|MF_SOLID|MF_NOBLOCKMAP
		ghs.angle = s.angle+(ANGLE_90*i)
		P_InstaThrust(ghs, ghs.angle, 24*s.scale)
		P_SetObjectMomZ(ghs, 4*s.scale, true)
		ghs.scalespeed = (SCALESPEED) or FRACUNIT/11
		ghs.destscale = 99<<16	
		ghs.flamespark = (FUSE) or 20
		ghs.fuse = ghs.flamespark
		ghs.mirrored = (i==1) and true or false
		ghs.momx = $+(s.momx/3)
		ghs.momy = $+(s.momy/3)
		ghs.momz = $+(s.momz/2)
		ghs.dispoffset = 88
		ghs.spritexscale = (CSCALE) or $
		ghs.spriteyscale = (CSCALE) or $		
		if COLOR
			ghs.color = COLOR
			ghs.colorized = true
		end
		Fix_Software(ghs,s)
		if i == -1 ths = ghs end
	end return ths,ghs --
end

--Power sparkles. Edited from SMSreborn's diffusion flame sparkles.
XSon.PowerRingSparkles = function(s, FUSE)
	FUSE = (FUSE) or (s.fuse) and s.fuse or 150
	local RNG = XSLocalRNG(1,24,s) 
	local DIST = (FUSE>385) and RNG*4*s.scale or (FUSE>220) and RNG*2*s.scale or RNG*s.scale
	local ANGLE = s.angle+(leveltime*ANG1*33)
	local COLOR = SKINCOLOR_GOLDENROD
	local TRANS = FF_TRANS10
	local SCALE,ghs = s.scale
	if (leveltime%3==0)
		COLOR = XSLocalRNG(SKINCOLOR_SUPERGOLD1, SKINCOLOR_SUPERGOLD5,s)	
		SCALE= $*2/3
	elseif (leveltime%2==0)
		COLOR = XSLocalRNG(SKINCOLOR_SALMON, SKINCOLOR_KETCHUP,s)
		TRANS = FF_TRANS40
	end
	if (FUSE>100)
		if (leveltime % 8 == 0) or not S_SoundPlaying(s, sfx_s227) --Sparkly charge sounds.
			S_StartSoundAtVolume(s, sfx_s227, min(200, FUSE/2))
		end
	end
	local STATE = (leveltime%2) and S_BOXSPARKLE1 or S_BOXSPARKLE2
	
	for i = 1,16,2 --Setup over, let's spawn some sparkles!
		ghs = P_SpawnMobjFromMobj(s, FixedMul(DIST, cos(ANGLE)), FixedMul(DIST, sin(ANGLE)),
		-s.height+(i*2*s.scale)+(RNG*s.scale*2), MT_XSAUTOFRAME)
		
		ghs.blendmode = AST_ADD
		ghs.renderflags = RF_FULLBRIGHT|RF_NOCOLORMAPS
		ghs.color,ghs.colorized = COLOR,true
		ghs.state = STATE
		ghs.frame = ($ & ~FF_TRANSMASK)|TRANS		
		ghs.scale = SCALE
		ghs.sttfade = 9
		ghs.destscale = 1
		ghs.dispoffset = 144
		ghs.fuse,ghs.tics = 14,99
		ANGLE = $+ANGLE_22h
	end	
end
