--=============================GFX Functions=======================================
--Visual Effects that are used in multiple instances are sometimes put here for convenience.
--If something only affects the game visually, then you could probably find it here.
--For the record, anyone may reuse and edit anything from SMSreborn without needing to ask.

--I'll appreciate if your edits don't break compatibility with SMS and credit is given.
--Thank you! ~Golden Shine
--=================================================================================

//Set up SMS' quake for the thinkframe. SMS' quake is slightly different from regular P_StartQuake, having an epicenter.
//Var1 = Intensity, Var2 = Duration, Var3 = Distance(optional), Var4 = Low Priority Quake
function A_SMSquake(s, var1, var2, var3, var4)
	if (var4 and s.smsqtime) --Already a quake active? Don't start another if var4 is there.
		return --
	elseif s.valid
		s.smsqtime = min(var2, 200) 
		s.smsqlvl = min(var1, 500<<16)
		s.smsqdist = (var3) and var3 or 850
	end
end

--Use a squash table for enriching certain animations. I prefer sublety and mostly use this for extreme animation shifts.
--NOTE: This function requires you to set spritexscale, spriteyscale and the table beforehand. It only desquashes! Example:
--s.spritexscale = FRACUNIT-28500
--s.spriteyscale = FRACUNIT+41000
--s.SMS_squashtable = {timer = 4, Xspeed = 5500, Yspeed = 4000}
--Only timer is mandatory, Xspeed and Yspeed MUST be positive values, it'll bug if you put -4000, for example.
rawset(_G, "SMS_SquashAndStretch", function(s)
	local yu = s.SMS_squashtable
	
	--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.SMS_squashtable,s.spriteyoffset,s.spritexoffset,s.spritexscale,s.spriteyscale = nil,0,0,FRACUNIT,FRACUNIT
		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.spriteyoffset > 0) and max($-2<<16, 0) or min($+2<<16, 0)
		end		
	end
	return s --Still stretching....!
end)

--Stomp effects! Often used for a lot of different things like transformations, actually!
rawset(_G, "SMS_StompGFX", function(s, p, FORCE, FORCEOLD)

	if not FORCE
		if not p.hypermysticsonic --Cancel conditions.
			if (s.eflags & MFE_UNDERWATER) 
			or (s.state == S_PLAY_STND)
			or P_IsObjectOnGround(s) and not (s.momz*P_MobjFlip(s) > 0) 
				return --
			end
		end
	end
	
	--Setup!
	if p.hypermysticsonic
		s.hmgodmticket = true
	end
	if p.shieldready --Mod support.
		p.shieldready = false
	end
	
	local GRAPHIX = FORCEOLD or SMS_Graphix(s)
	
	--Now do the effects!
	if FORCEOLD or GRAPHIX == 2 --Use the old effect!
		if p.smsrotatespikeball == nil or p.smsrotatespikeball2 == nil
			p.smsrotatespikeball = 0
			p.smsrotatespikeball2 = 0
		end
		p.smsrotatespikeball = $ + 1278743
		p.smsrotatespikeball2 = $ - 1278743 --19.5 FRACUNITs.
		
		local Zoffset = s.z + (s.momz/2) - ( SMSLocalRNG(2,5,s)*s.scale*P_MobjFlip(s) )
		local ANGLEONE = FixedAngle(p.smsrotatespikeball)
		local ANGLETWO = FixedAngle(p.smsrotatespikeball2)
		for i = -1,1,2
			P_SpawnXYZMissile(s, s, MT_SMSSTOMPLINE, 
			s.x+(s.momx/3)+cos(ANGLEONE*i)*42,
			s.y+(s.momy/3)+sin(ANGLEONE*i)*42,
			Zoffset)
			
			P_SpawnXYZMissile(s, s, MT_SMSSTOMPLINE, 
			s.x+(s.momx/3)+cos(ANGLETWO*i)*20,
			s.y+(s.momy/3)+sin(ANGLETWO*i)*20, 
			Zoffset)
		end
		
	else --New GFX!
	
		local DIST = SMSLocalRNG(16,28,s)*s.scale
		local ROTATESPEED = SMSLocalRNG(17,23,s)
		local FUSE = min(7, max(3, FixedInt((abs(s.momz)/7))) )
		local COLORTABLE = {SKINCOLOR_RED, SKINCOLOR_ORANGE, SKINCOLOR_RED, SKINCOLOR_YELLOW, SKINCOLOR_RED,SKINCOLOR_PERIDOT, SKINCOLOR_EMERALD, SKINCOLOR_RED, SKINCOLOR_SALMON}
		
		--Note to self on lua's % calculations: Say you have a value of 100. You decide to do: 100 % 8
		--It'll subtract 8 from your 100 right until it would go beyond the zero barrier.
		--After subtracting 8 twelve times, your 100 value is now 4. 
		--It would go past the zero barrier if it tried subtracting 8 again.
		--Therefore it stops and returns the value 4 that was left!
		for d = 0,18,18
		
			local DIST = (SMSLocalRNG(3,24,s)+d)*s.scale
			local ANGLE = s.angle + (d*ANG2) + (leveltime*ROTATESPEED*ANG1)
			local SCALED = SMSLocalRNG(-16543,33456,s)
			local ZOFFSET = (s.momz/6) + ( SMSLocalRNG(4,-3,s) + (d and 9 or 1) * P_MobjFlip(s) * s.scale )
			
			for i = -1,1,2
				local ghs = P_SpawnMobjFromMobj(s, FixedMul(DIST*i, cos(ANGLE)), FixedMul(DIST*i, sin(ANGLE)), ZOFFSET, MT_SMSAUTOFRAME)	
				ghs.sprite = SPR_AURA
				ghs.frame = (d != 18) and F|(FF_TRANS80-(FUSE<<16)) or F|FF_TRANS60
				ghs.blendmode = (d != 18) and AST_ADD or AST_TRANSLUCENT
				ghs.renderflags = (leveltime % 2) and RF_FULLBRIGHT|RF_PAPERSPRITE or RF_FULLBRIGHT
				ghs.color = COLORTABLE[ (leveltime%7)+2+i ]
				ghs.fuse = FUSE
				ghs.scalespeed = FRACUNIT/(FUSE+4)
				ghs.destscale = 1
				ghs.sttfirstframe = F --Loop!
				ghs.sttfinalframe = H
				ghs.sttspeed = 1
				ghs.sttfade = true
				ghs.spriteyscale = FRACUNIT+SCALED+SMSLocalRNG(1,26009,ghs)
				ghs.spritexscale = FRACUNIT+SCALED-((ghs.spriteyscale-SCALED-FRACUNIT)/3)
				ghs.spriteyoffset = $+((ghs.spriteyscale-FRACUNIT)*3)
				ghs.sttYspeed = (28+(FUSE*2))<<16 --Rise up quick!
				ghs.tracer = s
				ghs.ditchtracer = ((FUSE/2)-1) or 3
				ghs.GSoffsetX = DIST*i
				ghs.GSoffsetY = DIST*i
				ghs.GSoffsetZ = ZOFFSET
				ghs.GSoffsetang = ANGLE
				ghs.GSoffsetangFACE = ROTATESPEED*ANG1+(d<<16)
				s.GSshiftoffsetXY = 95
			end
		end
	end
end)

--6 dust particles shooting off behind you!
rawset(_G, "SMS_DustLayers", function(s, groundcheck, FORCEANGLE)
	if groundcheck
		if (s.eflags & MFE_VERTICALFLIP)
			if (s.z+s.momz < s.ceilingz-185*s.scale)
				return --
			end		
		elseif (s.z+s.momz > s.floorz+165*s.scale)
			return -- We're too high, cancel!
		end
	end
	local ghs
	local ANGLE = FORCEANGLE or (s.momx or s.momy) and R_PointToAngle2(s.x, s.y, s.x+s.momx, s.y+s.momy) or s.angle
	--Dust layers.
	for i = -3,3,1
		if i == 0 continue end --Skip 0. (Also! Thank you, Monster Iestyn, for teaching me about "for" loops long ago!)
		
		ghs = P_SpawnMobjFromMobj(s,s.momx,s.momy,s.momz, MT_SMSAUTOFRAME)
		
		ghs.sprite = SPR_SMMK
		ghs.renderflags = RF_PAPERSPRITE|RF_SEMIBRIGHT
		ghs.fuse = 16 ghs.tics = 99
		ghs.frame = I|(FF_TRANS40-(abs(i)<<16))
		ghs.sttspeed = 1
		ghs.sttfade = true
		ghs.sttfinalframe = W
		ghs.angle = ANGLE+(i*ANG1*20)
		P_InstaThrust(ghs, ANGLE+(i*ANG1*20), -16*s.scale)
		if (leveltime%2)
			ghs.momx = $+s.momx/3
			ghs.momy = $+s.momy/3
		end
	end	
end)

--Two papersprite circles expanding, followed by several layers of papersprite dust and a puff of smoke.
rawset(_G, "SMS_WallJumpGFX", function(s, circleonly, momentum)
	local ghs
	local ANGLE = s.angle
	if momentum
		ANGLE = R_PointToAngle2(s.x, s.y, s.x+s.momx, s.y+s.momy)
	end
	for i = 4,1,-3
		if circleonly and i == 4 continue end
		ghs = P_SpawnMobjFromMobj(s,0,0,s.height/2, MT_SMSAUTOFRAME)
		ghs.sprite = SPR_SMX2
		ghs.renderflags = $|RF_PAPERSPRITE
		ghs.angle = ANGLE+ANGLE_90
		ghs.colorized = true
		ghs.fuse = max(8, 16-i)
		ghs.tics = 99
		ghs.frame = U|min(FF_TRANS30, (FRACUNIT*i))
		ghs.scale = s.scale/3
		ghs.scalespeed = max(($*2)-(i*100), 99)
		ghs.destscale = $*99
		ghs.sttfinalframe = N
		ghs.sttfade = true
		if circleonly != "simple"
			ghs.color = SKINCOLOR_WHITE
			ghs.spriteyscale = FRACUNIT/((i == 4) and 3 or 2)
			ghs.spritexscale = FRACUNIT/((i == 4) and 3 or 2)
		end
	end

	if circleonly return ghs end --Be done early and return second (bigger) circle.
	
	SMS_DustLayers(s)
	
	--Burst light, the old effect, still mixed in.
	P_SpawnMobjFromMobj(s, 0, 0, 0, MT_BURSTLIGHT)
	
	--Smoke Puff, mostly for making the jumpwave feel more complete.
	ghs = P_SpawnMobjFromMobj(s,0,0,s.height/4, MT_SMSAUTOFRAME)
	ghs.sprite = SPR_SMMK
	ghs.destscale = $*99
	ghs.scalespeed = $*3
	ghs.color = SKINCOLOR_BONE
	ghs.fuse = 12 ghs.tics = 99
	ghs.frame = B|FF_TRANS20
	ghs.sttspeed = 4
	ghs.sttfinalframe = C
	ghs.sttfade = true
	
	--Jumpwave
	ghs = P_SpawnMobjFromMobj(s,0,0,0, MT_SMSAUTOFRAME)
	ghs.sprite = SPR_SMMK
	ghs.fuse = 6
	ghs.spritexscale = FRACUNIT-12000
	ghs.frame = D|FF_TRANS10
	ghs.sttfinalframe = H
	
	return ghs --Return jumpwave
end)

--The glowing effect I really wanted way back in 2006. The silly looking white outline of pixels finally paid off! 
--ROverGlow", "Dream Aura", "DreamAura"
rawset(_G, "SMSRglow", function(s, POSTTHINK)
	if (s.flags2 & MF2_DONTDRAW) or s.NoSMSGlow --Skip.
		s.rglowticket = false return --
	elseif not POSTTHINK and s.player --Wait until thinkframe...
		s.rglowticket = true return --		
	end
	local p = s.player
	local ghs = P_SpawnMobjFromMobj(s, 0, 0, 0, MT_SMSROVERGLOW)  --Stationary frame GFX
	ghs.target,ghs.tracer = s,s
	ghs.mirrored = s.mirrored
	ghs.renderflags = RF_FULLBRIGHT|RF_NOCOLORMAPS //s.renderflags
	ghs.blendmode = AST_ADD
	ghs.sprite = s.sprite
	if s.skin
		ghs.skin = s.skin
		ghs.sprite2 = s.sprite2
	end
	ghs.frame = (s.frame & ~FF_TRANSMASK)|((p and (p.speed>34*s.scale)) and FF_TRANS70 or FF_TRANS80)
	ghs.fuse = POSTTHINK and 2 or 3
	ghs.tics = (s.tics<ghs.fuse) and s.tics or ghs.fuse
	ghs.anim_duration = s.anim_duration
	ghs.color = SKINCOLOR_SMSROVERGLOW
	ghs.colorized = s.colorized
	ghs.angle = p and p.drawangle or s.angle
	if (leveltime%2==0)
		ghs.spritexscale,ghs.spriteyscale = FRACUNIT+7000,FRACUNIT+900
		ghs.rollangle = s.rollangle
	elseif (leveltime%3==0)
		ghs.spritexscale,ghs.spriteyscale = FRACUNIT+5200,FRACUNIT+450
	elseif (leveltime%5==0)
		ghs.spritexscale,ghs.spriteyscale = FRACUNIT+4000,FRACUNIT+700
	end
	ghs.spritexoffset,ghs.spriteyoffset = s.spritexoffset,s.spriteyoffset	
	
	--Player specifics....
	if p
		if s.skin != "sms"
			s.smsglowticket = s.smsglowticket and max($,3) or 3
		elseif (s.sms_boostloss and s.sms_boostloss != 12)
			ghs.color = SKINCOLOR_SMSROVERGLOWRED --Effect when boost is lost.
			ghs.dispoffset = 3
		end
		if SMS_Graphix(s) != 0 --Only render your own aura if graphics are lowered!
			if displayplayer and (displayplayer != p)
				ghs.flags2 = $|MF2_DONTDRAW
			end
		end
	end
	ghs.rollangle = s.rollangle
	
	s.rglowticket = false --Remove this and we're done!
	return ghs --
end)

--SMS version of ghost spawning stuff.
rawset(_G, "SMSRSpawnGhost", function(s, FUSE, NOFLICKER)
	if s.player and s.player.sms_comethaymaker return end

	local ghs = P_SpawnGhostMobj(s)
	ghs.blendmode = AST_ADD
	ghs.scalespeed = $/2
	ghs.destscale = ghs.scalespeed+1
	ghs.rollangle = s.rollangle
	ghs.spritexscale,ghs.spriteyscale = s.spritexscale,s.spriteyscale
	ghs.spritexoffset,ghs.spriteyoffset = s.spritexoffset,s.spriteyoffset
	ghs.mirrored = s.mirrored
	
	if (s.frame & FF_VERTICALFLIP) --All the accuracy!
		ghs.frame = $|FF_VERTICALFLIP
	end
	if (s.frame & FF_HORIZONTALFLIP)
		ghs.frame = $|FF_HORIZONTALFLIP
	end
	
	if FUSE	
		ghs.fuse,ghs.tics = FUSE,FUSE+8
	end
	if not NOFLICKER
		local DIV = (leveltime%2) and 2 or (leveltime%3==0) and 5 or nil
		if DIV != nil
			ghs.momx,ghs.momy,ghs.momz = s.momx/DIV,s.momy/DIV,s.momz/DIV	
		end
	end
	return ghs
end)

--SA2 style Homing Trick Trail. s.player is optional!
rawset(_G, "SMS_SA2JumpTrail", function(s)
	if s.SMS_DLtrail
		return --
	end
	local ghs = P_SpawnMobjFromMobj(s, 0,0,(-6*s.scale)*P_MobjFlip(s), MT_THOK)	
	ghs.state = S_SMS_THOK
	ghs.scale = s.scale*25/16
	ghs.scalespeed = $*3
	ghs.renderflags = RF_SEMIBRIGHT|RF_NOCOLORMAPS
	ghs.target = s
	ghs.blendmode = AST_ADD
	ghs.fuse = 5 ghs.tics = 99
	ghs.frame = Z|((s.player and (s.player.speed > 36*s.scale)) and FF_TRANS80 or FF_TRANS90)
	ghs.destscale = ghs.scalespeed+1
	ghs.momx = s.momx/3
	ghs.momy = s.momy/3
	if (s.sms_boostloss and s.sms_boostloss != 12)
		ghs.color = SKINCOLOR_SMSROVERGLOWRED
	elseif (s.player and s.player.smscoatc5 == SKINCOLOR_GREY) --Adjust color depending on what SMS is!
	or (s.color >= SKINCOLOR_CARBON and s.color <= SKINCOLOR_BLACK)
	or (s.color >= SKINCOLOR_RUBY and s.color <= SKINCOLOR_KETCHUP)
		ghs.color = SKINCOLOR_SUPERGOLD4
	elseif s.color and not SMSisyellow(s)
		ghs.color = s.color
	else
		ghs.color = SKINCOLOR_BONE
	end
	return ghs --
end)

--Grounded circle wind blast.
rawset(_G, "SMS_GroundWindBlast", function(s)
	local ghs = P_SpawnMobjFromMobj(s, 0, 0, 0, MT_SMSAUTOFRAME)				
	ghs.sprite = SPR_SMX2
	ghs.frame = A|FF_TRANS10
	ghs.fuse = 16
	ghs.scalespeed = $*3
	ghs.sttspeed = 3
	ghs.destscale = $*99
	ghs.sttfinalframe = D 
	ghs.sttfade = true
	return ghs
end)


--Wind Blast, great for accompanying numerous flinging objects and explosions!
rawset(_G, "SMS_WindBlast", function(s)
	local ths,ghs
	--Wind B last!
	for i = 1,2		
		ghs = P_SpawnMobjFromMobj(s, 
		FixedMul((15*i)*s.scale, cos(s.angle)),
		FixedMul((15*i)*s.scale, sin(s.angle)), 0, MT_SMSAUTOFRAME)
	
		ghs.sprite = SPR_SMX1
		ghs.frame = A|(i<<16)
		ghs.fuse = 11+(6*i)
		if i == 2
			ghs.rollangle = ANGLE_45
		else
			ghs.scalespeed = $*3
		end
		ghs.sttspeed = 3+i
		ghs.destscale = $*99
		ghs.sttfinalframe = D 
		ghs.sttfade = true
		if i != 2
			ths = ghs
		end
	end
	return ths, ghs --Return both! 
end)


--Golden Shock Waves! Useful for all kinds of stuff!
rawset(_G, "SMS_GoldWaves", function(s,p, SINGULAR)
	local NO = (not SINGULAR) and -1 or 0
	
	for i = NO,1 --Shockwaves as visual reference for the Haymaker's AoE range.
		local ANG = s.angle+((ANG1*34)*i)
		
		local ghs = P_SpawnMobjFromMobj(s, 0,0, s.height*2, MT_THOK)

		ghs.sprite = SPR_SMX2
		ghs.renderflags = RF_SEMIBRIGHT|RF_PAPERSPRITE
		ghs.angle = (ANG+(ANG1*16*i)) + ANGLE_90
		ghs.blendmode = AST_ADD
		ghs.fuse = 10 ghs.tics = 99
		ghs.scale = s.scale/3
		if (i == 0)
			ghs.frame = U|FF_TRANS10
			ghs.fuse = 15
			ghs.scalespeed = $*3
			P_InstaThrust(ghs, ANG, 40<<16)
		else
			ghs.frame = U|FF_TRANS40
			ghs.scalespeed = $*2
			ghs.colorized = true
			ghs.color = (p and p.hypermysticsonic and P_RandomRange(SKINCOLOR_EMERALD, SKINCOLOR_SUPERGOLD5)) or SKINCOLOR_FLAME
			P_InstaThrust(ghs, ANG, -10<<16)
		end
		ghs.destscale = $*99	
		
		if SINGULAR return ghs end --		
	end
end)

--Electric Recoil!
rawset(_G, "SMS_ElectricRecoil", function(s)
		local ghs = P_SpawnMobjFromMobj(s,0,0,-s.height/2, MT_SMSAUTOFRAME)

		ghs.sprite = SPR_SMX2
		ghs.renderflags = RF_FULLBRIGHT|RF_PAPERSPRITE
		ghs.angle = s.angle+ANGLE_90
		ghs.blendmode = AST_ADD
		ghs.fuse = 11 ghs.tics = 99
		ghs.scale = s.scale/3
		ghs.frame = E|FF_TRANS30
		ghs.rollangle = leveltime*ANG30
		ghs.scalespeed = $*2
		P_InstaThrust(ghs, s.angle, -5<<16)
		ghs.destscale = $*99
		ghs.sttfinalframe = N
		return ghs
end)


--Rising Skid Mark flames.
rawset(_G, "SMS_RisingFlames", function(s)
	--Flames coming from the skid mark.
	for i = -1,1
		local ANG = s.angle+((ANG1*48)*i)
	
		local ghs = P_SpawnMobjFromMobj(s,
		FixedMul(50*s.scale, cos(ANG)),
		FixedMul(50*s.scale, sin(ANG)),
		s.height/2, MT_SMSAUTOFRAME)
		
		ghs.sprite = SPR_SMX1
		ghs.renderflags = RF_FULLBRIGHT
		ghs.blendmode = AST_ADD
		if i == 0
			ghs.frame = L|FF_TRANS20
			ghs.sttspeed = 2
			ghs.fuse = 16
			ghs.scale = s.scale/2
			ghs.z = $+((s.height)*P_MobjFlip(s))
		else
			P_SetObjectMomZ(ghs, FRACUNIT, true)
			P_InstaThrust(ghs, ANG, 5<<16)
			ghs.frame = N|FF_TRANS30
			ghs.sttspeed = 4
			ghs.fuse = 28
			ghs.scale = s.scale/4
			ghs.rollangle = (ANG20*i)
		end
		if s.player and s.player.hypermysticsonic
			ghs.fuse = $+8
			ghs.sttspeed = $+1
			ghs.colorized = true
			ghs.color = P_RandomRange(SKINCOLOR_EMERALD, SKINCOLOR_SUPERGOLD5)
		end							
		ghs.destscale = $*99
		ghs.sttfinalframe = R
		ghs.sttfade = true						
	end
end)


--Wall Crater! Be careful to spawn this when near an actual wall for accuracy. 
--"t" can be the same as "s". "line" must be valid.
rawset(_G, "SMS_WallCrater", function(s,t,line)
	local x,y = P_ClosestPointOnLine(t.x, t.y, line)
	local ANG = R_PointToAngle2(t.x, t.y, x, y)+ANGLE_90
	for i = 1,2	
		local ghs = P_SpawnMobj(
		x+FixedMul(-3<<16, cos(ANG-ANGLE_90)),
		y+FixedMul(-3<<16, sin(ANG-ANGLE_90)),
		t.z+t.height/2,
		MT_THOK)	
		ghs.sprite = SPR_SMX1
		ghs.renderflags = RF_PAPERSPRITE
		ghs.angle = R_PointToAngle2(s.x, s.y, x, y)+ANGLE_90
		ghs.tics = 299
		ghs.scale = min(FRACUNIT*3, max(35356, (t.height/24)))
		if i == 2 --Flaming crater.
			ghs.frame = S
			ghs.fuse = 40
			ghs.colorized = true
			ghs.color = SKINCOLOR_FLAME
			ghs.blendmode = AST_ADD
		else
			ghs.frame = T|FF_TRANS10
			ghs.fuse = 100
		end
		ghs.height = 0
		ghs.radius = 0					
	end
end)


--Smoldering Crater Fire.
rawset(_G, "SMS_RisingCraterFlames", function(s)
				--Rising crater flames.
				for i = 1,8
					local Dist = 80*s.scale
					if i % 2 != 0
						Dist = 42*s.scale
					end
					local ghs = P_SpawnMobjFromMobj(s, 
					FixedMul(Dist, cos(s.angle+(ANGLE_45*i))),
					FixedMul(Dist, sin(s.angle+(ANGLE_45*i))),
					0, MT_SMSAUTOFRAME)				
					ghs.sprite = SPR_SMX1
					ghs.frame = U|FF_TRANS10
					P_SetObjectMomZ(ghs, Dist/12)
					ghs.fuse = 25
					ghs.color = SKINCOLOR_FLAME
					ghs.blendmode = AST_ADD
					ghs.sttspeed = 4
					if i % 2 == 0
						ghs.color = SKINCOLOR_RED
						ghs.scale = $/2
						ghs.fuse = $+8
						ghs.sttspeed = 4
					end
					ghs.destscale = $*99
					ghs.sttfinalframe = Y
					ghs.sttfade = true
				end
end)


--Cooler than usual explosion
rawset(_G, "SMS_SA2Explosion", function(s)
	local ghs = P_SpawnMobjFromMobj(s, 0, 0, 0, MT_SMSAUTOFRAME)					
	ghs.sprite = SPR_GPLZ
	ghs.frame = A|FF_TRANS30
	ghs.blendmode = AST_ADD
	ghs.fuse = 31
	ghs.sttspeed = 2
	ghs.destscale = $*99
	ghs.sttfinalframe = P
	ghs.sttfade = true
end)


--Explosion turning into lingering embers. t can be same as s, but needs to be filled in.
rawset(_G, "SMS_Embers", function(s, t, startframe)		
				local dist = 40*t.scale
				for d = -1,1
					for i = -4,4,1
						local ghs = P_SpawnMobjFromMobj(t, 
						FixedMul(dist*abs(i), cos(s.angle+(ANGLE_45*i+(ANG10*d)))),
						FixedMul(dist*abs(i), sin(s.angle+(ANGLE_45*i+(ANG10*d)))),
						(50*t.scale*d)-(t.height*2),
						MT_SMSAUTOFRAME) 
						
						ghs.momz = FRACUNIT/2 
						local ANGgo = R_PointToAngle2(ghs.x, ghs.y, t.x, t.y)
						P_InstaThrust(ghs, ANGgo, -(25<<16+(d-5<<16)) )
						ghs.angle = ANGgo+ANGLE_90
						
						ghs.renderflags = $|RF_PAPERSPRITE|RF_FULLBRIGHT
						ghs.sprite = SPR_SMX1
						ghs.frame = (U+(startframe or 0))|FF_TRANS80
						
						if i % 2 == 0
							ghs.color = SKINCOLOR_APRICOT
						else
							ghs.color = SKINCOLOR_RED
						end
						ghs.rollangle = ANGLE_180
						ghs.scale = $*max(2, abs(i))
						ghs.destscale = ghs.scalespeed+1
						ghs.blendmode = AST_ADD
						ghs.sttspeed = 1
						ghs.fuse = 160
						ghs.sttfinalframe = Y
						ghs.sttfade = true
					end
				end
end)


--Clone of Mobj that inflates really fast before "popping". Looks stupid without accompanying explosion effects.
rawset(_G, "SMS_ExplodingMobj", function(s)		
		local ghs = P_SpawnGhostMobj(s)
		ghs.tics = 4
		ghs.fuse = 99
		ghs.rollangle = s.rollangle
		ghs.colorized = s.colorized
		ghs.mirrored = s.mirrored
		ghs.renderflags = s.renderflags
		ghs.frame = s.frame
		ghs.scale = s.scale*2
		ghs.scalespeed = $*11
		ghs.destscale = $*99
end)


--Haymaker Smear effect.
rawset(_G, "SMS_PunchSmear", function(s,p)
		local ghs = P_SpawnMobjFromMobj(s, 
		FixedMul(24*s.scale, cos(s.angle)),
		FixedMul(24*s.scale, sin(s.angle)),
		0, MT_SMSAUTOFRAME)
		
		P_InstaThrust(ghs, s.angle, 8<<16)	
		P_SetObjectMomZ(ghs, -4<<16)
		
		ghs.sprite = SPR_SMX2
		ghs.renderflags = RF_SEMIBRIGHT|RF_PAPERSPRITE
		ghs.angle = s.angle+ANGLE_90
		if not p.hypermysticsonic
			ghs.frame = X|FF_TRANS50
			ghs.color = SKINCOLOR_RED //SKINCOLOR_FLAME
		else
			ghs.frame = X|FF_TRANS10
			ghs.blendmode = AST_ADD
			ghs.color = P_RandomRange(SKINCOLOR_EMERALD, SKINCOLOR_SUPERGOLD5) 
			if (p.smsrotatespikeball3 == 2)
				ghs.mirrored = true
			end
		end
		ghs.fuse = 15
		ghs.scale = s.scale*2
		ghs.scalespeed = $*2
		ghs.destscale = $*99
		ghs.sttspeed = 3 --animation speed.
		ghs.sttfinalframe = Z+1 --Don't allow it to go past this frame.
		ghs.sttfade = true --Auto fades if true.
	--	ghs.tracer = s --Stay relative to player position!
	--	ghs.GSoffsetX = 60*s.scale
	--	ghs.GSoffsetY = 60*s.scale
	--	ghs.GSoffsetZ = 0
		return ghs --
end)

--==================FLOORSPRITED SPLATS!============================

--Simple splat that can float and do whatever
rawset(_G, "SMS_Splat", function(s, fuse, ATTACH)
	local ghs = P_SpawnMobjFromMobj(s, 0, 0, 53000, MT_SMSSPLAT)
	ghs.renderflags = $|RF_FLOORSPRITE|RF_NOSPLATBILLBOARD
	ghs.flags2 = $|MF2_SPLAT
	ghs.fuse = fuse or 16
	if ATTACH --This is also used as maxdist number.
		SMS_AttachSplatToFloor(s, ghs, ATTACH)
	end
	return ghs --
end)

--Attaches a splat to a slope. Everything must be valid.
rawset(_G, "SMS_AttachSplatToFloor", function(s, ghs, maxdist)
	if ((s.z-s.momz)+(maxdist) < s.floorz) --Attach to floor?
		ghs.z = s.floorz+(FRACUNIT)
		if s.subsector and s.subsector.sector --We got slopes?
			local sec = s.subsector.sector
			local fslope = ((sec.f_slope) and (sec.f_slope.zdelta) and (sec.floorpic != "F_SKY1")) and sec.f_slope or 1
			if fslope != 1 --Become a sloped splat!
				P_CreateFloorSpriteSlope(ghs)
				ghs.renderflags = $|RF_FLOORSPRITE|RF_SLOPESPLAT
				ghs.floorspriteslope.o = {x = s.x, y = s.y, z = s.floorz}
				ghs.floorspriteslope.xydirection = fslope.xydirection
				ghs.floorspriteslope.zangle = fslope.zangle
			end
		end
	end
end)

--Function to spawn splats easily that adjust to slopes and try to stick to floors properly.
--Used for creating smoking craters mostly. Check if the return value isn't nil!
rawset(_G, "SMS_SmartSplat", function(s, fuse, t, offset)
	local Floor = s.floorz
	local so = s
	if (t and t.valid and t.floorz != nil) --Floorz's not always a given.
		Floor = t.floorz
		if (t.z > Floor+150<<16)
			return
		end
		so = t
	elseif (s.z > s.floorz+150<<16)
		return
	end
	local sec = so.subsector.sector	
	if sec == nil 
		return --
	end
	
	--Determine potential slopes!
	local fslope
	if sec.f_slope and sec.f_slope.zdelta and sec.floorpic != "F_SKY1"
		fslope = sec.f_slope
	end
	
	local offsetX = 0
	local offsetY = 0
	if offset --Forwards and backwards only.
		local ANGL = t and t.valid and R_PointToAngle2(s.x, s.y, t.x, t.y) or s.angle
		offsetX = FixedMul(offset, cos(ANGL))
		offsetX = FixedMul(offset, sin(ANGL))	
	end
	
	local ghs = P_SpawnMobj(
	so.x+offsetX,
	so.y+offsetY,
	Floor+53000,
	MT_SMSSPLAT) --Spawn splat object!
	
	ghs.renderflags = RF_FLOORSPRITE|RF_SHADOWDRAW|RF_NOSPLATBILLBOARD
	ghs.fuse = fuse or 16
	ghs.angle = so.angle
	ghs.dispoffset = -1
	
	--Need any further adjustments?
	if fslope --On a slope!
		P_CreateFloorSpriteSlope(ghs)
		ghs.flags2 = $|MF2_SPLAT
		ghs.renderflags = $|RF_FLOORSPRITE|RF_SLOPESPLAT
		ghs.floorspriteslope.o = {x = so.x, y = so.y, z = Floor}
		ghs.floorspriteslope.xydirection = fslope.xydirection
		ghs.floorspriteslope.zangle = fslope.zangle
	elseif sec.floorheight == Floor and sec.floorpic == "F_SKY1" --On a sky. Don't render.
		ghs.flags2 = MF2_DONTDRAW
		return
	end	
	ghs.scale = so.scale --Set scale and pass it back!
	return ghs --
end)

--When doing a high jump, have a whirlwind efect around you
rawset(_G, "SMS_WindJumpGFX", function(s)				
	local ghs = P_SpawnMobjFromMobj(s, 0, 0, s.height/2, MT_SMSAUTOFRAME)
		
	ghs.sprite = SPR_SMX1
	ghs.frame = E
	ghs.blendmode = AST_ADD
	ghs.fuse = 9
	ghs.scale = s.scale
	ghs.destscale = $*99
	ghs.sttspeed = 1 
	ghs.sttfinalframe = K 
	ghs.sttfade = true 
	ghs.tracer = s
	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, "SMS_CraterSpew", function(s, ANGLE, range) //speed, vertical, extramom, extramomang)
	if SMS_Gore(s) > 230 return end --Don't spawn with too much gore around.
	local RNG1 = P_RandomRange(5,20)
	local RNG2 = P_RandomRange(0,359)
	local height = s.height/2
	if P_IsObjectOnGround(s) height = 0 end
	
	local ghs = P_SpawnMobjFromMobj(s, 0, 0, height, MT_SMSRGORE)
	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)
		ghs.shadowscale = FRACUNIT*8
		ghs.ROCK = true
		ghs.srgorelvl = SMS_Gore(s, 1)
	end
	return ghs --
end)


//Different from A_SMSSTOMPSMOKE! This works with FF_PAPERSPRITE sprites.
--Var1= base radius | Var2 = scale | Var3 = TravelSpeed | Var4 = MomZ | Var5 = Additional Angle value
--Var6 = Additional Z offset | Var7 = Optional Fuse | Var8 = Color | Var9 = ObjType
function A_SMSSTOMPQUAKE(s, var1, var2, var3, var4, var5, var6, var7, var8, var9)
if SMS_Graphix(s) == 2
or (SMS_Gore(s) > 230) and (leveltime % 2)
	return --
end
var1 = var1 or 45
var2 = var2 or 15
var3 = var3 or 0 
var4 = var4 or 0
var5 = var5 or 0
var6 = var6 or 0
var7 = var7 or 10
var8 = var8 or s.color 
var9 = var9 or MT_SMSSWAVE --Perhaps I've added too many variables?

	local RETURNTABLE = {}
	
	for d = 0,5
	local cal = (ANG60*d)
	local ghs = P_SpawnMobjFromMobj(s,
	FixedMul(var1*cos(s.angle+var5+cal), FRACUNIT),
	FixedMul(var1*sin(s.angle+var5+cal), FRACUNIT),
	var6,
	var9) --var9 = type
	
		ghs.target = s
		ghs.fuse = var7
		ghs.angle = (s.angle+(var5+(ANG60*d)))+ANGLE_90
		ghs.scale = (s.scale*8)/var2
		ghs.renderflags = $|RF_SEMIBRIGHT
		
		if (var2 < 7)
			ghs.scalespeed = min(8000, $*3)
		elseif (s.scale > FRACUNIT)
			ghs.scalespeed = $*2 //min(5000, FixedDiv(FixedMul($, s.scale), FRACUNIT))
			ghs.spritexscale = $*2
		elseif not (s.player and s.player.hypermysticsonic)
			ghs.scalespeed = min(8000, $*8/13)
		end

		ghs.destscale = ghs.scalespeed+1
		ghs.color = var8
		ghs.flags2 = s.flags2 & ~MF2_DONTDRAW & ~MF2_SHADOW

		if (s.type == MT_THOK) or s.player and s.player.hypermysticsonic
			ghs.renderflags = $|RF_FULLBRIGHT
		end
		if var4
			ghs.momz = var4
		end
		if var3
			P_Thrust(ghs, R_PointToAngle2(s.x, s.y, ghs.x, ghs.y), var3)
		end
		if var9 and var9 == MT_HMSWAVEAURA
			ghs.momx = $+(s.momx)
			ghs.momy = $+(s.momy)
			ghs.sprite = SPR_SMMK
			ghs.dustwave2 = 1
			ghs.frame = 8
			ghs.destscale = 10<<16
		end
		RETURNTABLE[d] = ghs
	end
	return RETURNTABLE
end


//var1 = spawn dist | var2 = item type | var3 = Optional Z Spawn 
--Var4 = Optional Additional Z momentum | Var5 = Scale | Var6 = Color
function A_SMSSTOMPSMOKE(s, var1, var2, var3, var4, var5, var6)
	for i = 0,8 // Spawn 9 of them!
		local smmk = P_SpawnMobjFromMobj(s, 0, 0, s.scale, var2)
		smmk.target = s
		
		if smmk.type == MT_HMSLIGHTNING2 
			if s.type == MT_HMSPOWERBOMB
				if not var4
					smmk.growx2 = true
				end
				smmk.color = SKINCOLOR_SUPERGOLD5
				smmk.flags = $|MF_NOCLIP & ~MF_SOLID
				smmk.silentl = true
			end			
		elseif (smmk.type == MT_BOSSEXPLODE)
		
			if s.hmsspinpass
				smmk.state = $+P_RandomRange(0,4)
			end
			smmk.frame = ($ & FF_FRAMEMASK)|FF_TRANS40
			
		elseif (smmk.type == MT_ENERGYBALL)
		
			smmk.sprite = SPR_SMBO
			smmk.state = S_SMSBOOSTAURA1
			smmk.tics = 12
			A_FaceTarget(smmk)
			smmk.angle = $+ANGLE_180
			smmk.frame = 3|FF_TRANS70
			smmk.flags = $ & ~MF_MISSILE & ~MF_PAIN & ~MF_SOLID
			smmk.color = P_RandomRange(SKINCOLOR_SUPERSILVER1, SKINCOLOR_SUPERTAN3)
			smmk.fuse = 12
			smmk.destscale = $/10
			smmk.momz = var4 or (s.momz or 1/2)
			
		elseif (smmk.type == MT_NIGHTSPARKLE)
		
			smmk.state = S_SMSTSPARK1
			smmk.coverride = true
			smmk.scale = $*4
			smmk.destscale = $*6
			if not (s.type == MT_HMSNOVABLAST)
				if P_RandomRange(0,1)
					smmk.momz = s.momz/3
				end
			end		
		end
		
		smmk.scale = var5 or s.scale
		smmk.flags = $|MF_NOCLIPHEIGHT
		smmk.flags2 = s.flags2
		smmk.smssmmk = true
		smmk.momx = ( (var1*(s.scale/FRACUNIT))*cos((i*ANGLE_45)) + (s.momx/2) )
		smmk.momy = ( (var1*(s.scale/FRACUNIT))*sin((i*ANGLE_45)) + (s.momy/2) )
		
		if not smmk return end
		
		if var3
			P_MoveOrigin(smmk, smmk.x, smmk.y, smmk.z+FixedMul(var3, (P_MobjFlip(s)*s.scale)))
		end
		if var4 //Var5 is on top.
			smmk.momz = var4
		end
		if var6
			smmk.color = var6
		end	
		if (s.eflags & MFE_UNDERWATER)
			P_SetObjectMomZ(smmk, 2<<16, true)
		end
	end
end

--Spawn cartoony wind lines around you when speeding!
--p doesn't need to exist if nonplayer is true.
rawset(_G, "SMSWindLines", function (s, p, nonplayer)
	if not s.health or SMS_Graphix(s) == 2 return end		
		local momZ = s.momz --Real MomZ because SRB2 doesn't account for going/down slopes.		
		--Don't appear in these cases.
		if nonplayer 
			--For other objects.
		else --It's a player. SMS, probably.
			if p.smsdivetimer
			or (p.sms_ringburstfly == 1)
			or p.sms_afterstompthok 
			or p.sms_stompanim
			or p.powers[pw_carry]
			or (s.hms_jumprisen and s.hmsspinpass and (abs(s.momz) > 10*s.scale) )
				return
			else
				if s.SMS_lastz != nil
					momZ = s.z-s.SMS_lastz
				end
				--Harsh wind takes priority.
				if not (s.smssoundobj3 and s.smssoundobj3.valid and s.smssoundobj3.health)
				and not (s.smssoundobj2 and s.smssoundobj2.valid and s.smssoundobj2.health) 
					if not (s.eflags & MFE_UNDERWATER)
						s.smssoundobj3 = P_SpawnMobjFromMobj(s, 0, 0, 0, MT_SMSSOUNDOBJ)
						s.smssoundobj3.master = s
						s.smssoundobj3.health = 1
						S_StartSound(s.smssoundobj3, sfx_machw) --Create wind sound.
						s.smssoundobj3.smsmwtim = 74 //51 for harsh
					end
				end				
			end
		end

			local MomAngle = R_PointToAngle2(s.x, s.y, s.x+s.momx, s.y+s.momy)
			local ghs
			local rng = SMSLocalRNG(9,35,s)
			
			for i = 1,-1,-2
			
			   	ghs = P_SpawnMobjFromMobj(s,
				(s.momx/2)+FixedMul((rng*s.scale), cos(s.angle+(ANGLE_90*i))),
				(s.momy/2)+FixedMul((rng*s.scale), sin(s.angle+(ANGLE_90*i))),
				momZ+(P_RandomRange(1, 58)*P_MobjFlip(s)*s.scale),
				MT_SMSSPRINTLINE)
				
				ghs.angle = MomAngle+(ANG2*-3*i)
				ghs.momx = s.momx/4
				ghs.momy = s.momy/4
				ghs.momz = momZ-(momZ or 1/2)
				ghs.spriteyoffset = s.spriteyoffset			
				
				if ((momZ*P_MobjFlip(s)) < -4*s.scale)
					P_Thrust(ghs, s.angle+(ANGLE_90*i), 1<<16)
				else
					P_Thrust(ghs, s.angle+(ANGLE_90*i), 3<<16)
				end
				
				if nonplayer
				or ((p.sms_boostlevel < 33) and not p.hypermysticsonic)
					ghs.state = S_SMSSPRINTLINE1S --Small windlines!
					ghs.flags = $|MF_SCENERY
				else --Fiery windlines!
				 	ghs.blendmode = AST_ADD
				 	ghs.target = s
					ghs.color = SKINCOLOR_KETCHUP
					if (ghs.eflags & MFE_UNDERWATER)
						ghs.fuse = 7
						if leveltime % 3 == 0
							ghs.state = S_MEDIUMBUBBLE
						else
							ghs.state = S_SMALLBUBBLE
						end
					else
				 		if (rng < 15)
							if i == -1
								ghs.state = S_SMSSPRINTLINE2
							else
								ghs.state = S_SMSSPRINTLINE2M
							end
						else
							if i == -1
								ghs.state = S_SMSSPRINTLINE1
							else
								ghs.state = S_SMSSPRINTLINE1M
							end
						end
					end
				end
				if not nonplayer 
				and (displayplayer and displayplayer == p)
				and not CV_FindVar("chasecam").value
					ghs.flags2 = $|MF2_DONTDRAW
				end
			end
end)


--Footsweep effects! Kick up smoke and create blurs!
rawset(_G, "SMS_FootSweepEffects", function(s, p)
				
		--Rotating object effect, much like the 1.09.4 eggman spikeballs which were used for reference.
					
		if (p.footsweep01 > 8)
				p.smsrotatespikeball3 = s.angle-ANGLE_90 + (s.frame & FF_FRAMEMASK)*ANG1*-50 --The visual direction of SMS' kick.
			--	p.smsrotatespikeball3 = $ - 26<<16	
				
				local ghs
				for i = -1,1,2
					ghs = P_SpawnXYZMissile(s, s, MT_THOK,
					s.x+cos(FixedAngle(p.smsrotatespikeball3))*(30*i), 
					s.y+sin(FixedAngle(p.smsrotatespikeball3))*(30*i), 
					s.z-7*s.scale)
					ghs.momx = $+s.momx
					ghs.momy = $+s.momy
					ghs.scale = s.scale
					ghs.state = S_SPINDUST2
					ghs.fuse = 16
					P_Thrust(ghs, R_PointToAngle2(ghs.x, ghs.y, s.x, s.y), 11*s.scale)
				end
				return true --
				
		elseif (p.footsweep02 > 8)
		
				p.smsrotatespikeball3 = s.angle-ANGLE_90 + (s.frame & FF_FRAMEMASK)*ANG1*-50 --The visual direction of SMS' kick.
				
				local ghs
				local BLENDMODE = AST_ADD
				if (p.footsweep02 < 11) BLENDMODE = AST_TRANSLUCENT end
				for i = -1,1,2
					ghs = P_SpawnXYZMissile(s, s, MT_SUPERSPARK, 
					s.x+cos(FixedAngle(p.smsrotatespikeball3))*(42*i), 
					s.y+sin(FixedAngle(p.smsrotatespikeball3))*(42*i),
					s.z-3*s.scale)
					ghs.momx = $+s.momx
					ghs.momy = $+s.momy
					ghs.blendmode = BLENDMODE
					ghs.fuse = 22
					P_Thrust(ghs, R_PointToAngle2(ghs.x, ghs.y, s.x, s.y), 11*s.scale)
				end
				
				for i = -1,1,2
					ghs = P_SpawnXYZMissile(s, s, MT_SMMK, 
					s.x+cos(FixedAngle(p.smsrotatespikeball3))*(62*i), 
					s.y+sin(FixedAngle(p.smsrotatespikeball3))*(62*i),
					s.z-3*s.scale)
					ghs.momx = s.momx
					ghs.momy = s.momy
					ghs.blendmode = BLENDMODE
					ghs.fuse = 16
					P_Thrust(ghs, R_PointToAngle2(ghs.x, ghs.y, s.x, s.y), 11*s.scale)
				end			
				return true --
				
		elseif p.footsweep03 or not p.footsweepanim
			
				local ghs
				if (p.lddiscount == 1 or p.lddiscount == 3)
				and not (p.footsweep03 and (p.footsweep03 > 9) and (p.footsweep03 < 17))
				
					if p.footsweep03
						p.smsrotatespikeball3 = $ + s.angle-ANGLE_90 + (s.frame & FF_FRAMEMASK)*ANG1*-50
						p.smsrotatespikeball4 = p.smsrotatespikeball3+ANGLE_180		
					else
						p.smsrotatespikeball3 = $ + 1978743
						p.smsrotatespikeball4 = $ - 1978743 --Spin both ways!		
					end
					
					for i = -1,1,2
						ghs = P_SpawnXYZMissile(s, s, MT_SMMK, 
						s.x+cos(FixedAngle(p.smsrotatespikeball3))*(65*i),
						s.y+sin(FixedAngle(p.smsrotatespikeball3))*(65*i),
						s.z-3*s.scale)
						if ghs and ghs.valid
						ghs.target = s
						end
					end
					
					for i = -1,1,2
						ghs = P_SpawnXYZMissile(s, s, MT_SMMK, 
						s.x+cos(FixedAngle(p.smsrotatespikeball4))*(20*i),
						s.y+sin(FixedAngle(p.smsrotatespikeball4))*(20*i),
						s.z-3*s.scale)
						if ghs and ghs.valid
						ghs.target = s
						end		
					end
				end
				
				
				if SMS_Graphix(s) == 2 --Too intensive, we're done here...
				or (p.footsweep03 and (p.footsweep03 < 10) and (p.footsweep03 % 2))
					return true
				end
				
				--Footsweep blurring effects with ghosts!
				local dist = 16
				local color = s.color
				local amount = 8
				local angle = R_PointToAngle2(s.x, s.y, s.x+s.momy, s.y+s.momy)-((p.footsweep03 or leveltime)*(ANG1*5))
				local trans = FF_TRANS90
				
				if leveltime % 5 == 0
					dist = 7
					color = SKINCOLOR_EMERALD
					amount = 4
				elseif leveltime % 2 == 0
					dist = 24
					color = SKINCOLOR_FLAME
				end
				
				for i = 1,amount
					ghs = P_SpawnMobjFromMobj(s, 
					FixedMul( (26+(i*dist))*s.scale, cos(angle+(ANGLE_45*i)) ),
					FixedMul( (26+(i*dist))*s.scale, sin(angle+(ANGLE_45*i)) ),
					s.height/4, MT_THOK)
					
					ghs.renderflags = RF_PAPERSPRITE|RF_SEMIBRIGHT				
					ghs.fuse = min(16, max(5, i+20-dist))
					ghs.tics = 55
					
					if color != SKINCOLOR_FLAME
						ghs.blendmode = AST_ADD
					end
					
					ghs.skin = s.skin
					ghs.sprite = s.sprite
					ghs.sprite2 = s.sprite2
					ghs.frame = (s.frame & FF_FRAMEMASK)|trans
					ghs.colorized = true
					ghs.color = color
					
					ghs.angle = R_PointToAngle2(s.x, s.y, ghs.x, ghs.y)+ANGLE_90
					ghs.momx = s.momx/8
					ghs.momy = s.momy/8
					ghs.momz = s.momz*3/4
					ghs.destscale = $*99
					ghs.spriteyscale = $*3/4
					ghs.spritexscale = ($*dist)/10
					if (p == displayplayer) and CV_FindVar("gr_models").value --This many models will probably lag like shit.
						ghs.flags2 = $|MF2_DONTDRAW
					end
				end
				
				return true --
		end
end)

--Fancy new footsweep slash! Edited version of HMS' novablast flame impacts, surprisingly enough!
--All values must be valid beforehand, function MUST be used from a player.
rawset(_G, "SMS_FootSweepSlash", function(s, TRANS, ANGLE)
	if SMS_Graphix(s) == 2 return end --Kind of intense...

	local p = s.player
	local FOOTSWEEP = p.footsweep03 or p.footsweep02 or p.footsweep01 or SMSLocalRNG(1,21,s)
	
	local ZANGLE = (p.footsweep01 == 20) and -20 --sloped sprite angles. Anything under 5 won't create a slope at all for +performance
	or max(26-(FOOTSWEEP*5/4), 1)
	or max(26-FOOTSWEEP, 1) + (p.footsweep03 and SMSLocalRNG(1,9,s) or 1)
	
	local SIZE = FRACUNIT+(p.footsweep03 and 46969 or p.footsweep02 and 16969 or -6969)
	
	local COLOR = (p.footsweep03 or not p.footsweepanim) and "prism" or SKINCOLOR_MINT
	local SETFUSE = p.footsweep03 and 17-SMSLocalRNG(2,7,s) or 7

	local ghs = SMS_FlamingImpact(s, COLOR, 0, SIZE, (Z+5)|TRANS|FF_REVERSESUBTRACT, SETFUSE, ZANGLE)

	ghs.sttspeed = nil
	ghs.renderflags = ($ & ~(RF_NOCOLORMAPS|RF_FULLBRIGHT))|RF_SEMIBRIGHT
	ghs.sttfade = 9
	ghs.angle = ANGLE
	
	local ZADJUST = max(s.scale/2, min(abs(ZANGLE)*(s.scale/3), 12*s.scale) )
	
	P_SetOrigin(ghs,
	ghs.x+FixedMul(s.scale*3, cos(ANGLE)), 
	ghs.y+FixedMul(s.scale*3, sin(ANGLE)),
	ghs.z+(ZADJUST*P_MobjFlip(s))
	)
	ghs.momx, ghs.momy, ghs.momz = s.momx*3/4, s.momy*3/4, s.momz*2/3
	ghs.sttroll = -ANG1*6
	
	if p.footsweep03
		P_Thrust(ghs, ANGLE, FixedDiv(FixedMul( (9+(ZANGLE/4)) *ghs.spritexscale, s.scale), FRACUNIT))
		if (p.footsweep03 < 11)
			ghs.frame = ($ & ~FF_TRANSMASK)|((p.footsweep03 < 5) and FF_TRANS70 or FF_TRANS50)
		end
	elseif p.footsweep01 == 20 or p.footsweep02 == 20
		ghs.color = SKINCOLOR_MYSTICAL2
		ghs.spriteyscale = $*3/4
		ghs.spritexscale = $*3/4
	else		
		if p.footsweep02
			if (FOOTSWEEP % 3 == 0)
				ghs.color = SKINCOLOR_RED
				P_Thrust(ghs, ANGLE, FixedDiv(FixedMul(13*ghs.spritexscale, s.scale), FRACUNIT) )
			else
				ghs.spriteyoffset = $+6<<16
			end
			ghs.fuse = $+2
		else
			ghs.destscale = ghs.scale
		end
		ghs.spriteyoffset = $+11<<16+(ZANGLE<<16*3)
	end
	
	if (ghs.renderflags & RF_SLOPESPLAT)
		ghs.floorspriteslope.o = {x = s.x, y = s.y, z = s.z+ZADJUST+(p.footsweep03 and s.height or (s.height/2)) }
		ghs.sttYspeed = ghs.momz --Use this as a fuck you to slopesprites refusing to vertically move.
	elseif not (ghs.eflags & MFE_VERTICALFLIP)
		ghs.z = s.z+(s.height/6)
	end
	return ghs --
end)

--SMS' New Charging Sparkles when readying the Diffusion Flame!
rawset(_G, "SMS_DiffusionChargeSparkles", function(s,p, forcecolor)
	
	local rng = SMSLocalRNG(1,24,s) 
	local FLAMECOUNT = (p.sms_hugeflametoss and 223) or ((p.sms_flamebuttonhold) and p.sms_flamebuttonhold*8) or 223
	local DIST = (260-min(FLAMECOUNT, 223)) * s.scale
	local ANGLE = leveltime*ANG1*33
	local COLOR = SKINCOLOR_SMS4R
	if forcecolor
		COLOR = forcecolor
	else
		if (DIST < 39*s.scale) --Almost ready!
			COLOR = SMSLocalRNG(SKINCOLOR_SUPERSKY1, SKINCOLOR_SUPERSKY5,s)			
		elseif (leveltime % 2)
			COLOR = SMSLocalRNG(SKINCOLOR_SALMON, SKINCOLOR_KETCHUP,s)
		end
		if (leveltime % 8 == 0) --Sparkly charge sounds.
		or not S_SoundPlaying(s, sfx_s227)
			S_StartSoundAtVolume(s, sfx_s227, max(1, 270-(DIST/s.scale)))
		end				
	end
	
	--Setup over, let's spawn some sparkles!
	for i = 1,16,2
		local ghs = P_SpawnMobjFromMobj(s,
		FixedMul(DIST, cos(ANGLE+(ANGLE_22h*i))),
		FixedMul(DIST, sin(ANGLE+(ANGLE_22h*i))),
		(-s.height/2)+(i*5*s.scale)+(rng*s.scale),
		MT_THOK)
		
		ghs.blendmode = AST_ADD
		ghs.renderflags = RF_FULLBRIGHT|RF_NOCOLORMAPS
		ghs.colorized = true
		ghs.color = COLOR
		ghs.scale = min(s.scale*2,(s.scale/((rng/7) or 1)))
		ghs.destscale = ghs.scalespeed+1
		ghs.momx,ghs.momy,ghs.momz = s.momx/2,s.momy/2,s.momz/2
		ghs.color = COLOR
		if (leveltime % 2)
			ghs.state = S_BOXSPARKLE1
			ghs.frame = (ghs.frame & ~FF_TRANSMASK)|FF_TRANS20
		else
			ghs.state = S_BOXSPARKLE2
			ghs.frame = (ghs.frame & ~FF_TRANSMASK)|FF_TRANS50
		end	
		ghs.fuse = 18 ghs.tics = 99
	end
	return true --
end)

--The Super Mystic Boost Aura, when you're zooming at full speed!
rawset(_G, "SMS_BoostAura", function(s, p)
	local ANGLE = p.drawangle
	local FRAME = p.lddiscount or 1
	local TRANS = FF_TRANS70
	if (p.powers[pw_carry] == CR_MINECART) --In minecarts, use momentum angle instead.
		ANGLE = R_PointToAngle2(s.x, s.y, s.x+s.momy, s.y+s.momy)
	end	
	local momZ = s.momz --Calculate the real momZ, because slopes don't adjust for this.
	if s.SMS_lastz != nil 
		momZ = s.z-s.SMS_lastz
	end
	
	--We spawn TWO boost objects with slightly different properties.
	--Spawn them both slightly in front of SMS.
	
	for i = 1,3,2
		local ghs = P_SpawnMobjFromMobj(s,
		s.momx+FixedMul(3*s.scale, cos(ANGLE)),
		s.momy+FixedMul(3*s.scale, sin(ANGLE)), 
		momZ,
		MT_SMSBOOSTAURA)
	
		ghs.sprite = SPR_SMBO
		ghs.renderflags = $|RF_FULLBRIGHT|RF_NOCOLORMAPS
		ghs.blendmode = AST_ADD
		ghs.angle = ANGLE
		ghs.tics = i+1
		FRAME = max(min((FRAME+2)-i,D), 0)
		
		--Determine boost color
		if p.hypermysticsonic
			ghs.color = P_RandomRange(SKINCOLOR_SUPERSILVER1, SKINCOLOR_SUPERSILVER5)
		else
			if SMSisyellow(s) or (s.color == SKINCOLOR_BLACK) or ((s.color >= SKINCOLOR_RUBY) and (s.color <= SKINCOLOR_FLAME))
				ghs.color = SKINCOLOR_SUPERGOLD3
			else
				ghs.color = s.color
			end
		end
		
		--Set translucency and momentum.
		if i == 3 --Only applies to the second boost aura.
			if P_RandomRange(0,1)
				ghs.momx = s.momx/4*-1
				ghs.momy = s.momy/4*-1
				ghs.momz = momZ/4*-1
			else
				ghs.momx = s.momx/5*-1
				ghs.momy = s.momy/5*-1
				ghs.momz = momZ/5*-1
			end
			TRANS = FF_TRANS90
			ghs.rollangle = leveltime*ANG10
		else -- Only applies to the first!
			TRANS = FF_TRANS80+(SMSLocalRNG(0,1,s)<<16)
		end
		--Decided frame is...?
		if (leveltime % 2 == 0)
			ghs.frame = FRAME|TRANS
		else
			ghs.frame = A|TRANS
		end
		
		--Scale the sprite how exactly?
		
		if (p.hypermysticsonic and (p.speed > 146*s.scale))
		
		or (p.powers[pw_sneakers] and (p.sms_boostlevel > 64))
		
			local rng = SMSLocalRNG(-20000, 20000, s)
			ghs.spriteyscale = FRACUNIT*2+rng
			ghs.spritexscale = FRACUNIT*2+rng
			ghs.z = $-(20*s.scale*P_MobjFlip(s))
		else
			local rng = SMSLocalRNG(-11000, 17000, s)
			ghs.spriteyscale = FRACUNIT+rng
			ghs.spritexscale = FRACUNIT+rng
		end
		
		--Offset manually when vertical-flipped.
		if (s.eflags & MFE_VERTICALFLIP)
			ghs.z = $-29*s.scale
		end
		--Don't hinder vision in first person!
		if (p == displayplayer) and not CV_FindVar("chasecam").value 
			ghs.flags2 = $|MF2_DONTDRAW
		end			
	end
	
	--Next, spawn a colored afterimage of the boost aura.
		
	local ghs = P_SpawnMobjFromMobj(s, 0, 0, s.height/4, MT_SMSBOOSTAURA)
	ghs.renderflags = $|RF_FULLBRIGHT|RF_NOCOLORMAPS
	ghs.blendmode = AST_ADD
	ghs.momx = s.momx/3
	ghs.momy = s.momy/3
	ghs.momz = s.momz/2
	ghs.angle = ANGLE
	ghs.color = SKINCOLOR_FLAME
	ghs.scale = s.scale+(SMSLocalRNG(15,14000,s))
	ghs.scalespeed = $/2
	ghs.destscale = ghs.scalespeed+1
		
	--Whether the flame transitions to green or blue depends on your form.
	if not p.hypermysticsonic
		ghs.greeeeeeen = 1
		ghs.momz = s.momz/2
		if p.powers[pw_sneakers] and (p.sms_boostlevel > 64)
			ghs.scale = s.scale+SMSLocalRNG(0, 40111,s)
		else
			ghs.scale = $*6/7
		end
		ghs.scale = $*3/4
	else
		ghs.blue = 1
		if (p.speed > 146*s.scale)
			ghs.scale = s.scale+SMSLocalRNG(0, 40111,s)
		end
	end		
	ghs.momz = $+s.scale*2
	ghs.frame = E|FF_TRANS90 --It's the most subtle one.
	ghs.tics = 4
		
	if (s.eflags & MFE_VERTICALFLIP)
		ghs.z = $-36*ghs.scale
	end

	s.boostpass = false	--We only need to spawn the aura once per frame!
	
	return true -- done!
end)
