--=============================MAIN THINKER 3============================
--This main thinker houses some effects, NiGHTS, and SMS bots.
--Most importantly, it contains the 3 thinkframe variants.
--These are useful for deciding code priority and ordering.
--Lastly, it contains alot of special player-related hooks.
--=======================================================================

--Made this a PlayerThink so there's two seperate chances for me to setup.
--PlayerThink is basically identical to MobjThinker, except it works without a mobj too. Rarely will you need that, though.
addHook("PlayerThink", function(p)
if p.sweepangle == nil SetUpSMSreborn(p) end --Apparently PlayerThink goes slightly before a MobjThinker. good to know.
if not (p.mo and p.mo.health) return end --Need a living mobj from here on out.

	local s = p.mo
	
	//Super Mystic Sonic is merely another transformation for Sonic. Although most people
	//would probably prefer to choose SMS as a skin, I still believe a manual transformation would be cool.	
	
	--From the og LUA_SONICTRANSFORM.	
	if p.sms_transformstate and not p.sms_sontransform
	
		if (s.skin != "sms") or SMS_NerfedMode(p) --Can't be transformed like this!
		
			p.sms_transformstate = 0
			
		elseif s.skin == "sms" --We're currently a "transformed" SMS. Sparkle always and allow untransforming.
		
			A_BossScream(s, 0, MT_SMSTSPARK)
			
			if (p.cmd.buttons & BT_CUSTOM3) --Manually untransform in these circumstances.
			and (p.cmd.buttons & BT_SPIN) //Untransform, detransform, unbecome
			and not (p.speed > s.scale)
			and P_IsObjectOnGround(s)
			and (p.cmd.forwardmove < -5)
			and not (p.spinhold > 1)
			and not SMS_Locked(s, p)
			and not (p.pflags & PF_JUMPED)
			
				A_SMSTOSONIC(s, true)
				
			elseif leveltime <= 1 --Auto untransform in these circumstances.
			or p.exiting
			or not s.health
			
				if p.exiting
					A_SMSTOSONIC(s, nil, true, nil)
				elseif ((p.panim == PA_ROLL) or (p.panim == PA_RUN) or (p.panim == PA_WALK) or (s.state == S_PLAY_DEAD) or (s.state == S_PLAY_PAIN))
					A_SMSTOSONIC(s, nil, nil, true)		
				else
					A_SMSTOSONIC(s, nil, true, true)
				end
			end
			
		end
	end		
	
	if (s.skin == "sms") --Be SMS from now on.
		if p.isxmomentum
			p.noxmomchar = 2 --None of that!
			
			--Do some ridiculous momentum shenanigans for Xmom.
			
			if ((p.sms_boostlevel > 65) or p.hypermysticsonic)
			and ((p.cmd.buttons & BT_CUSTOM1) or p.sms_autoboosthold)
				if (p.smsrtable and p.smsrtable.direct > 15)
				and not (p.cmd.forwardmove < -40)
				
					if (p.hypermysticsonic or P_IsObjectOnGround(s) and not p.gotflag) --Raise speed...FOREVER!
					and not (p.pflags & (PF_STARTDASH|PF_SLIDING))
					and not p.powers[pw_carry]
					
						if (p.speed > p.normalspeed+FRACUNIT)
							local DIFF = p.speed-p.normalspeed
							
							local MAXGAIN = 
							   (p.normalspeed < 220<<16) and 12<<16
							or (p.normalspeed < 400<<16) and 9<<16
							or (p.normalspeed < 650<<16) and 7<<16
							or (p.normalspeed < 900<<16) and 4<<16
							or 2<<16	
							
							p.normalspeed = $+min(MAXGAIN , abs(DIFF))
						else
							local MAXGAIN = 
							   (p.normalspeed < 220<<16) and FRACUNIT*2
							or (p.normalspeed < 400<<16) and FRACUNIT+45000
							or (p.normalspeed < 700<<16) and FRACUNIT+22500
							or FRACUNIT	
							p.normalspeed = max((p.normalspeed+MAXGAIN), 157<<16)
						end
						
						--Cause dust and quakes!
						if not (p.powers[pw_sneakers] or p.hypermysticsonic)
						and P_IsObjectOnGround(s) 
						and (p.speed >= 222*s.scale)
						and (p.normalspeed >= 222<<16) 
						and not (s.eflags & MFE_TOUCHWATER)
						and not s.hmsrunquake
							A_SMSquake(s, 12<<16, 1, 2500)
							
							if (leveltime % 2 == 0)
							s.hmsrunquake = 1
								if SMS_Graphix(s) != 2
									for i = -1,1
										local ghs = SMS_CraterSpew(s, s.angle+(ANG1*120*i), 13)
										if ghs and ghs.valid
											ghs.momx = ($*9)+FixedDiv(FixedMul(s.momx, 2),5)
											ghs.momy = ($*9)+FixedDiv(FixedMul(s.momy, 2),5)
											ghs.smsrgtimer = 20
										end
									end
								end
							else
								local ghs = P_SpawnMobjFromMobj(s, FixedMul(24*s.scale, cos(s.angle+ANGLE_90)),FixedMul(24*s.scale, sin(s.angle+ANGLE_90)), 0, MT_SPINDUST)
								ghs.frame = SMSLocalRNG(4,2,s)-2|FF_TRANS10
								ghs = P_SpawnMobjFromMobj(s, FixedMul(24*s.scale, cos(s.angle-ANGLE_90)), FixedMul(24*s.scale, sin(s.angle-ANGLE_90)), 0, MT_SPINDUST)
								ghs.frame = SMSLocalRNG(0,2,s)|FF_TRANS10								
							end
						end							
					end
				end
			end
		end
		
		//Revert into a custom base Sonic for NiGHTs when you have SMS selected as a skin

		if (p.powers[pw_carry] == CR_NIGHTSMODE) 
		
			SMSNiGHTS(s, p)
			
			//Fix some bugs transforming Sonic's sprites into Super Mystic ones in NiGHTs	
			if (s.state >= S_PLAY_NIGHTS_STUN) and (s.state <= S_PLAY_NIGHTS_ATTACK)
				s.state = S_PLAY_NIGHTS_PULL
				if s.nrollangle != nil
					s.rollangle = s.nrollangle
					s.nrollanga = true
				end
			end
			if s.fboostgfx
				s.renderflags = $|RF_FULLBRIGHT
			else
				s.renderflags = $ & ~RF_FULLBRIGHT
			end
		
		//Yes, those wind lines around SMS' body when he runs are actually speed tickets. Guy's drowning in debt.
		
		elseif s.smsspeedticket	
		
			SMSWindLines(s, p)
			s.smsspeedticket = false

		end
		
		//The Super Mystic boost aura
		
		//Start of the boost aura code, passes it to thinkframe for spawn.

		if s.boostpass
			SMS_BoostAura(s, p)
		elseif (((p.speed > 79*s.scale) and not p.hypermysticsonic) or (((p.speed > 135*s.scale) or p.hmsdash) and p.hypermysticsonic)) and (((s.state == S_SMS_AIRBOOST) and s.tics > 20)
		or p.hypermysticsonic or (p.sms_boostlevel > 64))
			if s.health
				s.boostpass = 1
				SMS_BoostAura(s, p)
			end				
		end

		//You have a thokaura active.
		
		if s.thokaurapass
			local ZHEIGHT = (s.eflags & MFE_VERTICALFLIP) and 18*s.scale or 0
			local ghs = P_SpawnMobjFromMobj(s, 0,0,ZHEIGHT, MT_SMSAURATRAIL)
			ghs.aftertrail = 40
			if s.lasttrailra
				ghs.rollangle = s.lasttrailra
			end
			ghs.target = s
			ghs.renderflags = RF_FULLBRIGHT|RF_NOCOLORMAPS
			ghs.sprite = SPR_AURA
			ghs.tics = 8
			
			if s.thokaurapass >= 7
				ghs.color = SKINCOLOR_FLAME
				ghs.frame = B|TR_TRANS50
			elseif s.thokaurapass == 6
				ghs.color = SKINCOLOR_ORANGE
				ghs.frame = B|TR_TRANS60				
			elseif s.thokaurapass == 5
				if SMSisyellow(s) or s.color == SKINCOLOR_BLACK or s.color == SKINCOLOR_CRIMSON
					ghs.color = SKINCOLOR_GOLD
				else
					ghs.color = s.color
				end
				ghs.frame = B|TR_TRANS70
			elseif s.thokaurapass >= 2
				ghs.color = s.color
				ghs.frame = B|TR_TRANS80
			elseif s.thokaurapass == 1
				ghs.color = SKINCOLOR_EMERALD
				ghs.frame = B|TR_TRANS90
			elseif not ghs.color
				ghs.color = SKINCOLOR_FLAME
			end
			
			if (ghs.color == SKINCOLOR_FLAME) and (s.tics > 6)
				ghs.sprite = SPR_SMBO
				ghs.scale = s.scale*4/5
			else
				ghs.scale = s.scale*4/3
			end
			ghs.scalespeed = $*2/3
			ghs.destscale = 9
			s.thokaurapass = $-1
		end
		
		//Special Sonic X-like Ring Burst ghostmobjs for ringburstflight
		if s.ringburstticket
			local ghs
			if (leveltime%2 == 0)
				if SMSLocalRNG(0,2,s) != 0
					ghs = P_SpawnMobjFromMobj(s, 0,0,0, MT_THOK)
					ghs.angle = p.drawangle
					ghs.color = s.color
					ghs.sprite = SPR_SMSO
					ghs.frame = (Z+1)|TR_TRANS60
					ghs.momx,ghs.momy,ghs.momz = -s.momx/4,-s.momy/4,-s.momz/4								
				else
					ghs = P_SpawnGhostMobj(s) 
					ghs.sprite2 = SPR2_ROLL
					ghs.frame = D|TR_TRANS50
					ghs.scale = $*3/2	
					ghs.momx,ghs.momy,ghs.momz = s.momx/2,s.momy/2,-s.momz/2	
				end
				ghs.target,ghs.tracer = s,s
				ghs.renderflags = RF_FULLBRIGHT|RF_NOCOLORMAPS
				ghs.blendmode = AST_ADD
				if (leveltime%4==0)
					ghs.frame = $|FF_HORIZONTALFLIP
				else
					ghs.rollangle = SMSLocalRNG(0,359,s)*ANG1
				end
				ghs.tics = 12
				ghs.scalespeed,ghs.destscale = $*2,s.scale*4					
				A_CapeChase(ghs, 1, 0)
			end	
			
			ghs = P_SpawnGhostMobj(s) 
			ghs.blendmode = AST_ADD
			ghs.target,ghs.tracer = s,s
			ghs.tics = 9
			ghs.sprite = SPR_PLAY
			ghs.sprite2 = SPR2_ROLL
			ghs.renderflags = RF_FULLBRIGHT
			ghs.frame = D|TR_TRANS50
			ghs.scalespeed = $*2
			ghs.destscale = s.scale*4
			ghs.momx,ghs.momy = s.momx,s.momy
			ghs.momz = s.momz+32320
			A_CapeChase(ghs, 1, 0)
			s.ringburstticket = false
		end
		//A golden shine when initiating a ringburst!
		if s.ringburstticket2 
			if (s.ringburstticket2 >= 17)
				local ghs = P_SpawnMobjFromMobj(s, s.momx/2,s.momy/2,(s.height/3), MT_THOK)
				ghs.sprite = SPR_AURA
				ghs.renderflags = RF_FULLBRIGHT|RF_NOCOLORMAPS
				ghs.frame = S|FF_TRANS60
			 	ghs.color = SKINCOLOR_MYSTICAL3
				ghs.spriteyoffset = $-12<<16
				ghs.flags = $|MF_NOCLIPHEIGHT|MF_NOBLOCKMAP|MF_NOCLIPTHING|MF_NOCLIP
				if (leveltime%3==0)
					--
				elseif (leveltime%2==0)
					ghs.frame = $|FF_HORIZONTALFLIP
				else
					ghs.rollangle = FixedAngle((leveltime*20<<16))
				end
				ghs.scale = (s.scale*4)/SMSLocalRNG(1,5,s)
				ghs.destscale = s.scale*6
			  	ghs.tics,ghs.fuse = 2,2
			  	ghs.momx,ghs.momy,ghs.momz = s.momx,s.momy,s.momz
			elseif (s.ringburstticket2%2==0)
				local ghs = P_SpawnMobjFromMobj(s,0,0,0, MT_GOLDENSHINE)
				ghs.target = s
				ghs.renderflags = RF_FULLBRIGHT|RF_NOCOLORMAPS
				ghs.spriteyoffset = $-14<<16
				ghs.color = SKINCOLOR_SMS3R
				ghs.frame = U|FF_TRANS90
				if (leveltime%3==0)
					--
				elseif (leveltime%2==0)
					ghs.frame = $|FF_HORIZONTALFLIP
				else
					ghs.rollangle = SMSLocalRNG(0,160,s)*ANG10
				end
			end
			
			s.ringburstticket2 = $-1
			
			if not (p.sms_ringburstfly < 3) or SMS_Graphix(s) == 2 --Must be ringbursting!
				s.ringburstticket2 = 0
			end
		end
	end

	//Glowing ticket here! Glow ticket makes the shine appear.
	local SEC = s.subsector and s.subsector.sector
	if ( ( ((s.color >= SKINCOLOR_SUPERSILVER1) or p.shinyglosscoat) and (s.skin == "sms")) or s.smsglowticket)
	and SEC and SEC.valid and (p.powers[pw_carry] != CR_NIGHTSMODE)
		local FOFLIGHT = nil
		local LIGHT = 255 
		if s.cooldownlight --Light is currently shrinking/expanding.
			s.cooldownlight = $+1
			if (s.cooldownlight > 23)
				s.cooldownlight = 0
			end
		end
		if SEC.ffloors
			for fof in SEC.ffloors() --Use a FOF's shadow as reference instead?
				if fof and (fof.flags & FF_EXISTS) 
					if not (fof.flags & (FF_NOSHADE|FF_DOUBLESHADOW))
					and (s.z < fof.bottomheight)
						FOFLIGHT = fof.toplightlevel
					elseif (fof.flags & FF_CUTSPRITES)
						if ((s.z >= fof.bottomheight) and (s.z+(s.height/2) < fof.topheight))
							FOFLIGHT = fof.toplightlevel+20
						end
					end
				end
			end
		end
		if FOFLIGHT != nil 
			LIGHT = FOFLIGHT
		else
			LIGHT = SEC.lightlevel or 0
		end	
		if (LIGHT > 179) and (s.eflags & MFE_UNDERWATER)
			LIGHT = 155
			s.cooldownlight = 0
			s.smsgoldenshine = true
		elseif (LIGHT > 179) and s.smsgoldenshine
			if not s.cooldownlight
				s.cooldownlight = 1
			end
			s.smsgoldenshine = false
		end
			
		if (LIGHT < 180)
			s.cooldownlight = 0
			s.smsgoldenshine = true
		end
			
		if p.hypermysticsonic --HMS has a fancy rotating light.
			if (LIGHT < 210) and (p.smsgraphix != 2)
				HMS_DarkLight(s, p, s.color) --Much simpler.
			end	
		elseif ((LIGHT < 180) or s.cooldownlight)
		and not p.smsgraphix == 2
		  
			local ghs = P_SpawnMobjFromMobj(s, 0, 0, s.height/2, MT_THOK)
			ghs.sprite = SPR_OILF
			ghs.renderflags = RF_FULLBRIGHT|RF_NOCOLORMAPS
			if s.cooldownlight
				ghs.scale = max(99, s.scale-(s.cooldownlight*1000) )
			else
				if p.hypermysticsonic
			   		ghs.scale = s.scale*4/3
					if P_MobjFlip(s) == 1
						ghs.z = $+11*s.scale
					end
				else
					ghs.scale = s.scale
				end
			end
			ghs.frame = A|TR_TRANS90
			ghs.tics = (LIGHT < 100) and 4 or (LIGHT < 100) and 3 or 2
			ghs.momx,ghs.momy,ghs.momz = s.momx,s.momy,s.momz
			ghs.scalespeed = $+SMSLocalRNG(-500, 1000, s) --Flickery light!  
			ghs.destscale = 1
			ghs.flags = MF_NOCLIP|MF_NOCLIPHEIGHT|MF_NOBLOCKMAP|MF_SCENERY --No MF_NOGRAVITY for extra jitteryness?
			ghs.radius = 0
			ghs.height = 0
			if not s.cooldownlight
				s.smsgoldenshine = true
			end
		end
		  
		if s.smsglowticket
			if s.skin != "sms"
			or p.shinyglosscoat 
			or p.hypermysticsonic
			or not (s.color >= SKINCOLOR_SUPERSILVER1)
				s.smsglowticket = $-1
				if not s.smsglowticket
					s.frame = $ & ~FF_FULLBRIGHT
				end
			end
		end
	end
	
	//Execute SMS ssxa aura		
	
	if s.ssxaticket
		s.ssxaticket = false
		if SMS_Graphix(s) == 0
			local ghs
			local AngRNG = s.angle+(SMSLocalRNG(0,359, s)*ANG1)
			if p.hypermysticsonic
				if (leveltime % 2 == 0)
					ghs = P_SpawnMobjFromMobj(s,
					FixedMul(SMSLocalRNG(6,20,s)*s.scale, cos(AngRNG)),
					FixedMul(SMSLocalRNG(6,20,s)*s.scale, sin(AngRNG)),
					SMSLocalRNG(0,79, s)*s.scale,
					MT_SSXA)
				else
					ghs = P_SpawnMobjFromMobj(s,
					-FixedMul(SMSLocalRNG(6,20,s)*s.scale, cos(AngRNG)),
					FixedMul(SMSLocalRNG(6,20,s)*s.scale, sin(AngRNG)),
					SMSLocalRNG(0,79, s)*s.scale,
					MT_SSXA)
				end
			else
				if (leveltime % 2 == 0)
					ghs = P_SpawnMobjFromMobj(s, 
					FixedMul(SMSLocalRNG(6,20,s)*s.scale, cos(AngRNG)),
					FixedMul(SMSLocalRNG(6,20,s)*s.scale, sin(AngRNG)),
					SMSLocalRNG(0, 54, s)*s.scale,
					MT_SSXA)
				else
					ghs = P_SpawnMobjFromMobj(s,
					-FixedMul(SMSLocalRNG(6,20,s)*s.scale, cos(AngRNG)),
					FixedMul(SMSLocalRNG(6,20,s)*s.scale, sin(AngRNG)),
					SMSLocalRNG(0, 54,s)*s.scale,
					MT_SSXA)
				end
			end
			ghs.sprite = SPR_SMST
			ghs.blendmode = AST_ADD
			if (leveltime % 8 == 0) or (leveltime % 3 == 0)
				ghs.mcd = SMSLocalRNG(2,4,s)
				ghs.frame = SMSLocalRNG(3,6,s)|FF_FULLBRIGHT|TR_TRANS30
			else
				ghs.mcd = SMSLocalRNG(2, 4, s)
				ghs.frame = SMSLocalRNG(3,6, s)|FF_FULLBRIGHT|TR_TRANS40
			end
			if p.hypermysticsonic
				ghs.scalespeed = $*SMSLocalRNG(1,3,s)
			else
				ghs.scalespeed = $/SMSLocalRNG(1,5,s)
			end
			ghs.destscale = $*7
			P_SetObjectMomZ(ghs, 32555+SMSLocalRNG(-20000, 40000, s))
			
		//	ghs = P_SpawnMobj(s.x+(s.momx/2)-FixedMul((P_RandomRange(9, 22)*s.scale), cos(s.angle+ANGLE_90)), s.y+(s.momy/2)-FixedMul((P_RandomRange(9, 22)*s.scale), sin(s.angle+ANGLE_90)), s.z+s.momz+(P_RandomRange(1, 54)*P_MobjFlip(s))*s.scale, MT_SSXA)
			if (leveltime % 2 == 0)
				ghs = P_SpawnMobjFromMobj(s,
				SMSLocalRNG(-6, -20, s)*s.scale,
				SMSLocalRNG(-6, -20, s)*s.scale,
				SMSLocalRNG(0, 54, s)*s.scale,
				MT_SSXA)
			else
				ghs = P_SpawnMobjFromMobj(s,
				SMSLocalRNG(-6, -20, s)*s.scale,
				-SMSLocalRNG(-6, -20, s)*s.scale,
				SMSLocalRNG(0, 54, s)*s.scale,
				MT_SSXA)
			end
			ghs.sprite = SPR_SMST
			ghs.blendmode = AST_ADD
			if leveltime % 2 == 0
				ghs.frame = (SMSLocalRNG(3, 6, s))|FF_FULLBRIGHT|TR_TRANS30
			else
				ghs.frame = (SMSLocalRNG(3, 6, s))|FF_FULLBRIGHT|TR_TRANS10
			end
			if p.hypermysticsonic
				ghs.scalespeed = $*SMSLocalRNG(1, 3, s)
			else
				ghs.scalespeed = $/SMSLocalRNG(1, 5, s)
			end
			ghs.destscale = $*7
			P_SetObjectMomZ(ghs, 32555+SMSLocalRNG(-20000, 40000, s))
		end
	end	
end, MT_PLAYER)

--Let's use SA-Sonic's method instead, my old one is just clumsy.

addHook("MobjMoveBlocked", function(s, m, line)
	if s.skin != "sms" or not line or not s.player or s.currentaxis
		return --
	end	local p = s.player

	if s.hypermysticdash --You gotta turn to slide off walls!
	
		if not (p.cmd.forwardmove < -28)
			s.hypermysticdash = 0
		end
		
	elseif (p.sms_ringburstfly or s.SMS_CrashRoll or (p.smsdivetimer and (p.smsdivetimer != 1)) )
	 --Reflect off walls like a pingpong ball!

		if not s.SMS_WallBounce
		and not P_IsObjectOnGround(s) 
		and not (p.pflags & (PF_JUMPED|PF_SLIDING))
		
			local x,y = P_ClosestPointOnLine(s.x, s.y, line)
			local BOUNCEANGLE = R_PointToAngle2(s.x, s.y, x, y)
			local ANGSHIFT,PLUS = SMS_AngleDifference(BOUNCEANGLE,SMS_PlayerAngle(s,p), 2, ANG30, 2)
			BOUNCEANGLE = $+(ANGSHIFT*PLUS)	
			
			if s.SMS_CrashRoll --In ragdoll state!
				s.hmsrotateright = (s.hmsrotateright != true) and true or false	
				if not (p.sms_ringburstfly or p.smsdivetimer)
					S_StartSoundAtVolume(s, sfx_corkh, 134)
					S_StartSoundAtVolume(s, sfx_doord2,66)
					S_StartSoundAtVolume(s, sfx_mswing,99)
					if not s.SMS_squashtable
						s.spritexscale = FRACUNIT-6100
						s.spriteyscale = FRACUNIT+9900
						s.SMS_squashtable = {timer = 2, Xspeed = 3200, Yspeed = 5500, air = true}
					end

					local PRESPEED = max(p.speed*3/5, 3*s.scale)
					s.momx = $/4 s.momy = $/4
					P_SetObjectMomZ(s, -s.scale, true)
					P_Thrust(s, BOUNCEANGLE+ANGLE_180, PRESPEED)
							
					local RNG = SMSLocalRNG(2,6,s)
					local ghs = SMS_SmokeCircle(s, 0, 1, s, (FRACUNIT/2)*RNG)
					if ghs and ghs.valid
						if (RNG > 4)
							ghs.ditchtracer = 1
						end
						ghs.fuse = $-6
						ghs.spriteyoffset = -27<<16
					end
					if s.SMS_CrashRoll.rollangle != nil
						local DIRECT = (s.hmsrotateright == true) and 1 or -1
						s.SMS_CrashRoll.rollangle = $+(s.SMS_CrashRoll.rollspeed*3*DIRECT)
						s.SMS_CrashRoll.faceangle = $+(s.SMS_CrashRoll.spinspeed*3*DIRECT)
					end
					s.SMS_WallBounce = 2
					return true --
				end
			end
			
			--Cancel conditions...
			if p.smsdivedelay
			or (p.smsdivetimer and (p.smsdivetimer < 4) and SMS_NerfedMode(p))
			or (p.panim == PA_SPRING)
				return --
			end
			
			if not p.hypermysticsonic --SMS stuff.
				if  p.sms_ringburstfly and ( p.sms_ringburstfly != 1)
					s.SMS_WallBounce = 2 --Horizontal RingBurst Crash.
					s.SMS_lastwallNUM = line
					return --
				else
					p.smsatomdivestun = nil
					if p.sms_ringburstfly
					or p.smsdivetimer
						if (p.pflags & (PF_ANALOGMODE|PF_DIRECTIONCHAR))
							p.drawangle = BOUNCEANGLE+ANGLE_180
						end
						s.angle = BOUNCEANGLE+ANGLE_180
					end
					if p.sms_ringburstfly == 1
						P_SetObjectMomZ(s, 41350, true) --Slight vertical boost.

						local SfX = P_SpawnMobjFromMobj(s, 0,0,0, MT_THOK) 
						SfX.flags2 = $|MF2_DONTDRAW
						SfX.tics = 133
						S_StartSoundAtVolume(SfX, sfx_smsfx6, 120)
						S_StartSound(SfX, sfx_smsfx2)		
						S_StartSoundAtVolume(SfX, sfx_doord2, 65)
						
						local ghs = SMS_DashGFX(s, SKINCOLOR_MYSTICAL2)
						ghs.rollangle = $+ANGLE_270
						ghs.renderflags = $ & ~RF_PAPERSPRITE	
						if SMS_Graphix(s) != 2
							local ghs = SMS_LightFlash(s, SKINCOLOR_ORANGE, FRACUNIT-11500, 5, ((Z+3)|TR_TRANS30))
							ghs.tracer = s
							if s.SMS_DLtrail == nil
								s.SMS_DLtrail = {timer = 8, colorized = true,
								COLORT_trail = {SKINCOLOR_MYSTICAL5, SKINCOLOR_SMS5R, SKINCOLOR_ORANGE, SKINCOLOR_RED, SKINCOLOR_RED, SKINCOLOR_EMERALD, SKINCOLOR_GREEN, SKINCOLOR_MYSTICAL2},
								COLORT_trailmax = 8} 
							end
						end
					else
						if s.hmsrotateright
							s.hmsrotateright = false
						else
							s.hmsrotateright = true
						end			
						if not (p.sms_ringburstfly or p.smsdivetimer)
							S_StartSoundAtVolume(s, sfx_corkh, 134)
							S_StartSoundAtVolume(s, sfx_doord2,66)
							S_StartSoundAtVolume(s, sfx_mswing,99)
							if not s.SMS_squashtable
								s.spritexscale = FRACUNIT-6100
								s.spriteyscale = FRACUNIT+9900
								s.SMS_squashtable = {timer = 2, Xspeed = 3200, Yspeed = 5500, air = true}
							end

							local PRESPEED = max(p.speed*3/5, 3*s.scale)
							s.momx = $/4 s.momy = $/4
							P_SetObjectMomZ(s, -s.scale, true)
							P_Thrust(s, BOUNCEANGLE+ANGLE_180, PRESPEED)
							
							local RNG = SMSLocalRNG(2,6,s)
							local ghs = SMS_SmokeCircle(s, 0, 1, s, (FRACUNIT/2)*RNG)
							if ghs and ghs.valid
								if (RNG > 4)
									ghs.ditchtracer = 1
								end
								ghs.fuse = $-6
								ghs.spriteyoffset = -33<<16
							end
							if s.SMS_CrashRoll.rollangle != nil
								local DIRECT = (s.hmsrotateright == true) and 1 or -1
								s.SMS_CrashRoll.rollangle = $+(s.SMS_CrashRoll.rollspeed*3*DIRECT)
								s.SMS_CrashRoll.faceangle = $+(s.SMS_CrashRoll.spinspeed*3*DIRECT)
							end
							s.SMS_WallBounce = 2
							return true --
						else
							S_StartSound(s, sfx_doord2)
							for i = 0,4
								local ghs = SMS_DashGFX(s, SKINCOLOR_SMS5R-i)
								ghs.rollangle = $+ANGLE_90
								ghs.renderflags = $ & ~RF_PAPERSPRITE
								ghs.frame = (i == 4) and $|FF_HORIZONTALFLIP or ($ & ~FF_HORIZONTALFLIP)
							end
						end
					end
				end
						
			else --HMS' version is explosive because of course.
					
				p.smsatomdivestun = nil
				if (p.pflags & (PF_ANALOGMODE|PF_DIRECTIONCHAR))
					p.drawangle = BOUNCEANGLE+ANGLE_180 //$ + ANGLE_180
				end
				s.angle = BOUNCEANGLE+ANGLE_180 //$ + ANGLE_180
				A_SMSSTOMPQUAKE(s, 95, 10, 30<<16, 0, (ANG20*2), 0, 22, SKINCOLOR_SUPERRED5)
				
				for i = 1,4
					P_SpawnMobjFromMobj(s, 
					FixedMul(70*s.scale, cos(s.angle+(ANGLE_90*i))), 
					FixedMul(70*s.scale, sin(s.angle+(ANGLE_90*i))),
					0, MT_SMSDIVEBOMB)

					P_SpawnMobjFromMobj(s, 
					FixedMul(250*s.scale, cos(s.angle+(ANGLE_90*i))), 
					FixedMul(250*s.scale, sin(s.angle+(ANGLE_90*i))),
					0, MT_SMSDIVEBOMB)	
				
					if i != 4
						P_SpawnMobjFromMobj(s, 
						FixedMul(45*s.scale, cos(s.angle+(ANG1*114))), 
						FixedMul(45*s.scale, sin(s.angle+(ANG1*114))),
						0, MT_SMSDIVEBOMB)						
					end
				end

				local ghs = P_SpawnMobjFromMobj(s, 0,0,0, MT_SMSDIVEBOMB)
				ghs.target = s
				
				P_NukeEnemies(s, s, 400*s.scale)
				S_StartSound(s, sfx_brakrx)
				S_StartSound(s, sfx_s3kcfs)
			end
			s.SMS_WallBounce = 1
		end
		return --
		
	elseif s.HMS_airboostspeed and p.hypermysticsonic
	and (p.sms_c1 == 1 or (s.HMS_airboostspeed > 250*s.scale+60<<16) and p.sms_c1 < 8)
	and not p.smsatomdivestun
	and s.SMSLASTANGLE != nil
	and s.HMS_airboostspeedtimer and p.hmsdash
	
		if (FixedAngle(SMS_AngleDifference((p.cmd.angleturn<<16),s.SMSLASTANGLE, 2, ANG1*120, 2)) < 20<<16)
		
			local x,y = P_ClosestPointOnLine(s.x, s.y, line)
			local BOUNCEANGLE = R_PointToAngle2(s.x, s.y, x, y)
			local ANGSHIFT,PLUS = SMS_AngleDifference(BOUNCEANGLE,SMS_PlayerAngle(s,p), 2, ANGLE_90, 2)
			BOUNCEANGLE = $+(ANGSHIFT*PLUS)
			p.smsatomdivestun = 2
			if (p.pflags & (PF_ANALOGMODE|PF_DIRECTIONCHAR))
				p.drawangle = BOUNCEANGLE+ANGLE_180 
			end
			s.angle = BOUNCEANGLE+ANGLE_180	
			if p == displayplayer
				P_StartQuake(450<<16, 6)
				if CV_FindVar("renderer").value == 2 --Not on software...
					P_FlashPal(displayplayer, PAL_NUKE, 6)
				end
			end
			if line.frontside and not SMS_SkyBoxWall(s, line, 0) --Wall crater!
				SMS_WallCrater(s,s,line)
				for i = 1,3
					SMS_CraterSpew(s, s.angle+ANGLE_180, 11)
				end
				S_StartSound(s, sfx_smsfx6)
			end
		end
	end
	if (p.cmd.buttons & BT_SPIN) and not P_IsObjectOnGround(s) 
	and not p.hypermysticsonic --Player's prepping a walljump. They'd hate to wallslide.	
		if (leveltime % 3 == 0)
		and not p.smsblastboost
		and not SMS_Locked(s, p, nil, true)
		and not SMS_SkyBoxWall(s, line, 0) --IF we didn't hit a skybox wall, continue!
			S_StartSound(s, sfx_s3k7e)
			local ghs = P_SpawnMobjFromMobj(s, FixedMul(13*s.scale, cos(s.angle+ANGLE_90)),FixedMul(13*s.scale, sin(s.angle+ANGLE_90)), s.height+s.momz, MT_SPINDUST)
			ghs.frame = SMSLocalRNG(4,2,s)-2|FF_TRANS10
			ghs = P_SpawnMobjFromMobj(s, FixedMul(13*s.scale, cos(s.angle-ANGLE_90)), FixedMul(13*s.scale, sin(s.angle-ANGLE_90)), s.height+s.momz, MT_SPINDUST)
			ghs.frame = SMSLocalRNG(0,2,s)|FF_TRANS10
			if not s.SMS_squashtable
				s.spritexscale = FRACUNIT-5100
				s.spriteyscale = FRACUNIT+8100
				s.spriteyoffset = SMSLocalRNG(-8,8,s)*(FRACUNIT/5)
				s.spritexoffset = SMSLocalRNG(-20,20,s)*(FRACUNIT/7)
				s.SMS_squashtable = {timer = 2, Xspeed = 4200, Yspeed = 7500, air = true}
			end
		end
		return --
	end
	if not (p.speed > 60*s.scale) --Hit a wall real fast with speed? Slide along rather than losing all speed!
	or ((p.cmd.buttons & BT_SPIN) and (p.pflags & PF_JUMPED)) --Player's prepping a walljump. They'd hate to wallslide.
	or s.SMS_WallJumpTable
		return --
	end
	local SLIDE = false

	if (p.sms_boostlevel >= 64)
	and ( (p.pflags & (PF_JUMPED|PF_THOKKED|PF_SPINNING)) or p.panim == PA_RUN or ((s.state >= S_SMS_BOOST01) and (s.state <= S_SMS_BOOST04)))
		if not (p.cmd.forwardmove < -29)
			SLIDE = true
		end
	end
	if (p.speed > 100*s.scale) or (s.state == S_SMS_AIRBOOST)
		if (p.cmd.buttons & BT_CUSTOM1) or p.sms_autoboosthold
			if ((p.cmd.forwardmove) > 3) and not ((s.state == S_SMS_AIRBOOST) and not (abs(p.cmd.sidemove) > 10))
				SLIDE = true
			end	
		end
	end
	if (p.pflags & PF_SPINNING) 
		if P_IsObjectOnGround(s) and not (p.pflags & (PF_STARTDASH|PF_SLIDING))
			SLIDE = true
		end
	end
	if SLIDE == true
	and not SMS_Locked(s, p, true)
	and not (s.flags2 & MF2_TWOD) and not (twodlevel) --Not in 2D mode	
		if p.hypermysticsonic or not s.SMSwallslideCD or (s.SMSwallslideCD < 50)
	
			local x,y,BOUNCEANGLE = P_ClosestPointOnLine(s.x, s.y, line)
			local BOUNCEANGLE = R_PointToAngle2(s.x,s.y, x, y)
			local MOMANGLE = R_PointToAngle2(s.x, s.y, s.x+s.momx, s.y+s.momy) //SMS_PlayerAngle(s,p)	
			local ANGSHIFT,PLUS = SMS_AngleDifference(BOUNCEANGLE,MOMANGLE, 2, ANG20*2, 2)			
			if (ANGSHIFT < ANG20) return end --You ran RIGHT into the wall. Wow. Good job.				
			BOUNCEANGLE = $+(ANGSHIFT*PLUS)	
			
			local PRESPEED = p.speed/4
			s.momx = FixedDiv(FixedMul($, 25), 32)
			s.momy = FixedDiv(FixedMul($, 25), 32)
			P_Thrust(s, BOUNCEANGLE+ANGLE_180, PRESPEED)
			
			--Change angle a bit.
			if not P_IsObjectOnGround(s)
				BOUNCEANGLE = ((ANGSHIFT*PLUS) or 1)/16
				s.SMSwallslideCD = s.SMSwallslideCD and $+16 or 16
			else
				BOUNCEANGLE = ((ANGSHIFT*PLUS) or 1)/8
				s.SMSwallslideCD = s.SMSwallslideCD and $+8 or 8
			end
			s.angle = $-BOUNCEANGLE
			if s.SMSwallslideCD >= 50 s.SMSwallslideCD = 75 end --BREAK! Can't wallslide a while.
			return true --
		else
			s.SMSwallslideCD = s.SMSwallslideCD and min($+2,75) or 2
		end
	end
end)

//Old Super Tails' 2.1 Whirlwind ability!
addHook("JumpSpinSpecial", function(p)
	if p.mysticsuper and (p.charability == CA_FLY) and not p.powers[pw_tailsfly] and p.mo
		if not (p.spinhold and (p.spinhold > 1)) and not (p.pflags & PF_THOKKED)
			p.pflags = $ & ~PF_SHIELDABILITY
			P_DoJumpShield(p)
			p.mo.momz = $*2
			p.pflags = $|PF_SHIELDABILITY
		end
	end
end)

//SMS uses a custom thok merging the vanilla homing attack with extended capabilities+effects and a vanilla speed thok.
addHook("AbilitySpecial", function(p)
	if not (p.mo and p.mo.skin == "sms") return end
	local s = p.mo	
	//Always ignore thok ability in this case.
	if p.hypermysticsonic or p.secondjump == 167 or s.stompnegatedinput
		return true --
	end	
	--Turn stomp into thok-stomp.
	if (p.sms_stompanim or p.sms_afterstompthok)
		if not (p.sms_oldhoming and not (p.cmd.buttons & BT_USE) and (p.pflags & PF_THOKKED))
			p.sms_stompanim = 0
			p.hometrickanim = false
			p.sms_afterstompthok = true
		end
	end
	
	--Defaults
	p.charflags = $ & ~SF_MULTIABILITY
	p.charability = CA_HOMINGTHOK
	p.thokitem = 0
	
	--Do a walljump from a thok?
	if s.SMS_WallJumpTable and (s.SMS_WallJumpTable.timer > 12)
	and (s.SMS_WallJumpTable.straightjump or s.SMS_WallJumpTable.wallbounce)
		SMS_WallJumpDelete(s, true) 
		s.SMS_WJauto = 0 --Not if we're currently doing those walljumps.
	else
		SMS_WallJumpDelete(s, true)
		s.SMS_WJauto = (not p.homing) and 3 or 0 --Yes!
		
		--Hyper specific legacy walljump fix.
		if not s.SMS_walltable s.SMS_walltable = {} end
		s.SMS_walltable[0] = P_SpawnMobjFromMobj(s, FixedMul(-10*s.scale, cos(s.angle)),FixedMul(-10*s.scale, sin(s.angle)), 0,MT_SMSWALLJUMP) 
		local ghs = s.SMS_walltable[0]
		ghs.target = s ghs.tracer = s
		ghs.fuse = 6
		ghs.SMS_number = 0
		ghs.flags = MF_NOBLOCKMAP|MF_NOGRAVITY|MF_MISSILE|MF_NOSECTOR
		ghs.angle = s.angle
		P_InstaThrust(ghs, s.angle, 55*s.scale)		
	end
	if s.SMS_WJauto
		s.SMS_preWJautopenalty = s.SMS_WJpenalty
	else
		s.SMS_preWJautopenalty = nil
	end
	
	--Now, determine what thok to use.
	if p.homing
		p.actionspd = skins["sms"].actionspd
	else
		if p.sms_oldhoming and not (p.cmd.buttons & BT_USE)
			p.charability = CA_THOK
			p.actionspd = skins["sms"].actionspd
			p.charflags = $ & ~SF_MULTIABILITY		
			if (p.pflags & PF_THOKKED) or (s.state == S_SMS_AIRBOOST) or not (p.pflags & PF_JUMPED)
				return true --Cancel entirely.	
			end
		else
			if (p.cmd.buttons & BT_USE) or p.smsblastboost
				if not p.sms_wallstat
					p.charflags = $|SF_MULTIABILITY
				end
				p.thokitem = MT_SMSAURATRAIL
				if p.smsblastboost
					p.actionspd = 142<<16 --GO!
				else
				p.actionspd = skins["sms"].actionspd		
				end
			else
				p.actionspd = 142<<16
			end	
		end
		--Animation set.
		if p.sms_oldhoming and not (p.cmd.buttons & BT_USE) 
		or SMS_TryForcedHomingAttack(s, p)
		or (p.actionspd == 142<<16 and not (p.pflags & PF_THOKKED))
			if not (s.SMS_DLtrail and (s.SMS_DLtrail.timer > 5)) and not ((p.pflags & PF_THOKKED) and not p.homing)
				s.SMS_DLtrail = {timer = 5, colorized = true,
				spriteyscale = FRACUNIT-19000,
				spritexscale = FRACUNIT-16000,
				spriteyoffset = 4<<16,
				scalespeed = FRACUNIT/30,
				fuse = 12,
				COLORT_trail = {SKINCOLOR_MYSTICAL4, SKINCOLOR_MYSTICAL4, SKINCOLOR_ORANGE, SKINCOLOR_RED, SKINCOLOR_EMERALD, SKINCOLOR_LIME},
				COLORT_trailmax = 5} 
			end
			if not (s.SMS_squashtable and s.SMS_squashtable.timer > 5) --Thok squash.	
				s.spritexscale = FRACUNIT-11500
				s.spriteyscale = FRACUNIT+14000
				s.SMS_squashtable = {timer = 5, Xspeed = 4500, Yspeed = 5000, air = true}
			end
		end			
	end
	
	if (p.charability == CA_HOMINGTHOK) --A juiced up homing attack!

		if not (p.pflags & PF_THOKKED)	
			p.sms_readyairboost = 0
			s.atomdiveusebuffer = 0
			p.sms_jumphold = 10			
	 		if p.hometrickanim 
			or (p.panim == PA_FALL)
			or (s.state >= S_SMS_STOMP1) and (s.state <= S_SMS_ADVFALL2)
				p.hometrickanim = false
				if not P_IsObjectOnGround(s)
					s.state = S_SMSTHOKANIM
					p.panim = PA_ROLL				
				end
	  		end		
		elseif not p.homing
			s.smsthokanim = true
		end
		
	elseif (p.charability == CA_THOK) and not (p.pflags & PF_THOKKED) --Rarely used nowadays.
		p.sms_readyairboost = 0
		p.sms_jumphold = 10
		s.atomdiveusebuffer = 0
		s.smsthokanim = true
		
		if p.hometrickanim 
		or (p.panim == PA_FALL)
		or (s.state >= S_SMS_STOMP1) and (s.state <= S_SMS_ADVFALL2)
			p.hometrickanim = false
			if not P_IsObjectOnGround(s)
				s.state = S_SMSTHOKANIM
				p.panim = PA_ROLL					
			end
		end
	end
	
	--Always force actionspeed in these cases!
	if p.homing or (p.cmd.buttons & BT_SPIN) 
		p.actionspd = skins["sms"].actionspd
	end
end)

--Treat Mystic Super like regular Super, ignoring shields.
addHook("ShieldSpecial", function(p)
	if p.mysticsuper and leveltime //Simple and clean.
		return true --
	end
end)


--PreThinkFrame is good for manipulating player controls.
addHook("PreThinkFrame", do
	for p in players.iterate if p.sweepangle == nil SetUpSMSreborn(p) end -- ♫ You will know our variables ♫ 
		if p.mo
			local s = p.mo
			if s.SMS_ControlAlter
				SMS_AlterControls(s,p, s.SMS_ControlAlter)
			end
			if s.skin == "sms" 
				SMS_WallBustPower(s,p)	
				--Perform the walljump from the prethinkframe so we can alter controls.
				if s.SMS_wallready
					if (not (p.cmd.buttons & BT_SPIN) or s.SMS_WJauto)
						if not s.SMS_wallwait
							if (p.pflags & PF_JUMPED)
							or p.sms_stompanim or p.hometrickanim
							or (s.state == S_SMS_AIRBOOST)
							or (p.panim == PA_FALL)
							or (p.panim == PA_ROLL) and not (p.pflags & (PF_SPINNING|PF_STARTDASH))
							or ((s.eflags & MFE_JUSTHITFLOOR) and s.standingslope and abs(s.standingslope.zdelta) > 53999) --Allow the 1 frame on slopes!
								if not (s.eflags & MFE_SPRUNG)
								and not p.hypermysticsonic
									SMSDoWallJump(s,p)
								end
							end
						end
					end
				end
				if s.SMS_preWJautopenalty
					if not s.SMS_WJauto
						s.SMS_preWJautopenalty = nil
					end
				end
				--Minor improvement over vanilla controls, ALWAYS let SMS land into a spin if the player holds spin.
				if (s.eflags & MFE_JUSTHITFLOOR) and (p.cmd.buttons & BT_SPIN)
					if (p.panim == PA_RUN)
					or (p.panim == PA_WALK) 
					or (s.state >= S_SMS_BOOST01) and (s.state <= S_SMS_BOOST04)
						if not (p.pflags & (PF_SPINNING|PF_SLIDING))
							p.pflags = $ & ~PF_USEDOWN
						end
					end
				else					
					if p.sms_autoboosthold and (p.sms_autoboosthold > 2) and (abs(p.cmd.forwardmove) > 4)
						if (p.panim == PA_RUN) --Usually can only gain boostlvl on the ground.								
						or P_IsObjectOnGround(s) and (p.speed > 22*s.scale)
							p.cmd.buttons = $|BT_CUSTOM1
							p.sms_autoboosthold = $-1
						else
							p.sms_autoboosthold = 2
						end
					end
				end
				if p.hypermysticsonic 
					if (p.rings > 1)
						s.HMSlr = p.rings
					end
					if s.EvilGlare and s.EvilGlare.valid
						p.cmd.buttons = $ & ~(BT_SPIN|BT_CUSTOM2|BT_TOSSFLAG)
					end
				end
			end
			if p.sms_sontransform --Currently transforming into SMS?
				SMS_DoTransform(s, p)
			elseif s.skin != "sms"
				s.tnospina = nil
				SMS_TransformInput(s, p) --Allow input for transformation into SMS.
			end
		end
	end
end)

--Did we have "displayplayer" back in 2.1? This was the superhacky solution for player-dependent quakes at the time.
--local smsp
--hud.add(function(v, p)
--	smsp = p
--end)

--This is the ThinkFrame, necessary to make some things work in the correct order.
addHook("ThinkFrame", do for p in players.iterate
	if p.hfear and p.spectator //or ((p.realmo and p.realmo.valid) and not (p.mo and p.mo.valid))
		for sector in sectors.iterate
			sector.lightlevel = ($+31)
		end
		P_SetupLevelSky(p.hfear)
		for z in players.iterate
			z.hmshascome = nil
			P_FlashPal(z, PAL_MIXUP, 3)
		end
		S_StartSound(nil, sfx_s3k66)
		p.hfear = nil
		p.powers[pw_invulnerability] = 2
		p.powers[pw_shield] = SH_NONE	
	end
	local s = p.mo
	if p.ffdelaycs and p.sms_originalcolor
		p.skincolor = p.sms_originalcolor
		if s and s.valid
			s.color = p.sms_originalcolor
		end
		p.ffdelaycs = $-1
	end	
	
	if not (s and s.valid) continue end --
	
	--All skins apply, not just SMS!
	if s.smsqtime and s.smsqlvl and (s.smsqdist or splitscreen)
		if displayplayer and displayplayer.valid and displayplayer.mo and not displayplayer.nosmsquakespls
		local m = displayplayer.mo
			if splitscreen or (FixedHypot(FixedHypot(m.x-s.x, m.y-s.y), m.z-s.z) < s.smsqdist<<16)
				P_StartQuake(s.smsqlvl, 1)
				s.smsqtime = max($-1, 0)
			end
		end
	end
	if p.dohmscarry
		if s.hmspickuptarget and s.hmspickuptarget.valid
			local t = s.hmspickuptarget
			s.momx,s.momy,s.momz = t.momx,t.momy,t.momz
			if t.player and t.player.drawangle != nil
				p.drawangle = t.player.drawangle
			end
			P_MoveOrigin(s, t.x, t.y, t.z-(43*t.scale))
		end
		p.dohmscarry = false
	end		
	if s.SMS_tempRF
		s.SMS_tempRF = $-1
		if not s.SMS_tempRF --undo renderflag changes after 2 tics.
			s.SMS_tempRF = nil
			s.renderflags = $ & ~RF_FULLBRIGHT
		end	
	end
	--Just SMS applies from here on out!	
	if s.skin == "sms" 		
		if p.hypermysticsonic --HMS animations!
			HMS_Animation(s, p, 1)
		end
		if p.smsdivefloat 
		or p.smsdivetimer
		or p.atomdiveaim and p.hypermysticsonic
			p.atomdiveaim = true
			A_AtomDiveAim(p)
			if p.mo and (P_MobjFlip(p.mo) == 1)
				p.aiming = -4850<<16
			else
				p.aiming = 4850<<16
			end
		end
		if p.smsblastboost --Are you using Legend/Legacy Mode?
 	 		p.charability = CA_HOMINGTHOK
 	 		if not p.homing
		 		p.actionspd = 142<<16 //Emulate SMS' 1.09.4 multithok.
 	 		else
 	 			p.actionspd = skins[s.skin].actionspd
 	 		end
 	 		if (p.cmd.buttons & BT_CUSTOM1)
 	 		and not (p.pflags & (PF_STARTDASH|PF_JUMPED))
 	 		and not (p.cmd.forwardmove < 0)
 	 		and not ((s.eflags & (MFE_UNDERWATER|MFE_SPRUNG)) and not p.hypermysticsonic)
 	 		and not (SMS_NerfedMode(p) and not p.rings)
 	 		and not p.powers[pw_carry]
 	 		and P_IsObjectOnGround(s)
 	 		and s.health		
 	 		 	SMSblast_boost(s, p)
 	 		else
 	 		 	s.smsblastboost1 = false
 	 		end
 	 		if (p.pflags & PF_THOKKED) and (p.pflags & PF_JUMPED) and not p.sms_wallstat
 	 		 	if p.smsblastboost --SMSunleashed walljump
 	 		 		P_SpawnSpinMobj(p, MT_SMSWALLJUMP)
	 		 	end
 	 		 	p.pflags = $ & ~PF_THOKKED --Infinite multi-speed-thok.
 	 		end
		elseif s.changedmyrsd
			p.runspeed = skins[s.skin].runspeed
			p.actionspd = skins[s.skin].actionspd
		end
		if (p.charflags & SF_SUPER)
			p.charflags = $ & ~SF_SUPER
			if (p.pflags & PF_JUMPED) and not (p.powers[pw_invulnerability] or p.powers[pw_shield])
				p.pflags = $|PF_USEDOWN //Fighting these pesky "all super" addons that dare conceive Super Super Mystic Sonic
			end
		end
		if p.powers[pw_super]
			p.powers[pw_super] = 0 //You're already super enough. It's in your darn name.
			p.powers[pw_emeralds] = 0
			p.charflags = $ & ~SF_SUPER
			if (s.state >= S_PLAY_SUPER_TRANS1) and (s.state <= S_PLAY_SUPER_TRANS6)
				P_FlashPal(p, PAL_NUKE, 4)
				S_StartSound(s, sfx_lose)
				s.state = S_PLAY_FALL
				CONS_Printf(p, "\x85"+"You're already super! What are you trying to pull?")
			end
		end
	end
	
	--SMS skin check is gone again
	
	if s.resetsongtodayok --Cut that shit out!
		s.resetsongtodayok = nil
		if p.mysticsuper
			COM_BufInsertText(p, "tunes -default")
		end
	end
	
	if s.nuuuuuuuuu --Probably what the victim is screaming right about now! (Player's getting HMS' godbreakered)
		s.frame = $ & ~FF_FRAMEMASK 
		s.nuuuuuuuuu = nil	
	elseif s.health	
		--SMS pick up system! Keep object in front of you. 
		--Purposely leaving a bug that lets other characters keep SMS' carry, btw.
		if p.smspickup
			if not p.smspickup.valid --The hell? Disappear.
				p.smspickup = nil
			else --That's a picked up object!
				local t = p.smspickup
				t.z = s.z+((s.height/2)*P_MobjFlip(s))
				if not t.player
					t.angle = p.drawangle
				else
					t.player.drawangle = p.drawangle //Let players rotate their camera
				end
				t.iampickedupbysmshelp = 2
				t.eflags = s.eflags
				t.pmomz = s.pmomz
				t.momx,t.momy,t.momz = s.momx,s.momy,0
				t.rollangle = s.rollangle or 0

				if t.fuse --The thing we're holding has a fuse? Infinitely extend it.
					t.fuse = $+1 
				end
				--Use TryMove to make things seem like they make sense
				if not ( FixedHypot(t.x-s.x, t.y-s.y) > 130*s.scale)
					P_TryMove(t,
					s.x+FixedMul(t.info.radius*20/9, cos(p.drawangle)),
					s.y+FixedMul(t.info.radius*20/9, sin(p.drawangle)),
					true)
				else --But if they somehow get too far away, forcibly teleport!
					P_MoveOrigin(t,
					s.x+FixedMul(t.info.radius*20/9, cos(p.drawangle)),
					s.y+FixedMul(t.info.radius*20/9, sin(p.drawangle)),
					s.z+FixedMul(27<<16,s.scale)*(P_MobjFlip(s)))
				end
			end
		end
		
		--Become brighter than other characters!
		if s.skin == "sms"	
		or s.smsglowticket --glowticket applies on objects and Mystic Supers too, hence no sms skin check.			
			if (p.powers[pw_carry] != CR_NIGHTSMODE) --NiGHTS mode has it's own behaviour.
				s.SMS_tempRF = 2
				s.renderflags = $|RF_FULLBRIGHT
			end
		end
	end
end end)

local OverLayMan = nil 
addHook("MobjThinker", function(s)
	if s.valid and s.target and s.target.player and s.target.player.mysticsuper and s.target.skin == "sonic"
		OverLayMan = s
		if (s.target.player.pflags & PF_DIRECTIONCHAR) 
			OverLayMan.directchar = true
		end
		if CBW_Battle //Battlemoooooodddd....
			s.goddamnitwhybattlemod	= true
		end
	end
end, MT_OVERLAY)

--Sometimes you need a postthinkframe to assure nothing else can take priority rendering-wise.
addHook("PostThinkFrame", do
	if OverLayMan and OverLayMan.valid and OverLayMan.target and OverLayMan.target.valid and OverLayMan.target.player
		if mariomode
			OverLayMan.color = OverLayMan.target.color
		end
		if OverLayMan.directchar
			OverLayMan.angle = OverLayMan.target.player.drawangle
			OverLayMan.directchar = nil
		end
		if OverLayMan.goddamnitwhybattlemod
			OverLayMan.goddamnitwhybattlemod = nil
			OverLayMan.flags2 = $ & ~MF2_DONTDRAW
		end
	end	
	for p in players.iterate	
		local s = p.mo
		if s and s.valid		
			--Be SMS from here on out.
			if s.skin == "sms"
				if p.hypermysticsonic --HMS animations. Doing it again in postthink for better accuracy.
					HMS_Animation(s, p)		
				else --if not p.hypermysticsonic		
					if s.HayMakerPunch --Allow player to float in the air for a bit.
						s.HayMakerPunch = $-1
						if s.SMS_HomingBolt
							s.sprite = SPR_SMSO
							s.frame = ($ & ~FF_FRAMEMASK)|Q
						elseif (s.z > s.floorz+s.height)
						and not (p.pflags & (PF_JUMPED|PF_SPINNING)) 
						and not p.sms_didairboost
						and not (p.speed > 38*s.scale)
						and not p.hypermysticsonic
							s.sprite = SPR_SMSO
							s.frame = ($ & ~FF_FRAMEMASK)|S
							if (s.momz*P_MobjFlip(s) < 4*s.scale)
							and not P_IsObjectOnGround(s)
								P_SetObjectMomZ(s, FRACUNIT/3, true)
							end
						else
							s.HayMakerPunch = nil
						end
						
					elseif p.sms_comethaymaker --Chasing? Stay in chasing frames, then.
					
						if (s.target and s.target.valid)
							s.sprite = SPR_SMSO
							s.frame = ($ & ~FF_FRAMEMASK)|R
						end
					
					elseif ((p.footsweep03 > 10) or ((p.footsweep02 > 5) and (p.footsweep02 < 18)))
					and (s.state >= S_SMS_FOOTSWEEP1) and (s.state <= S_SMS_FOOTSWEEP8)
						s.sprite = SPR_SMSO --Speed animation up for later footsweeps.
						s.frame = ($ & ~FF_FRAMEMASK)|SMS_TimeFrame(7, E)
						
					elseif s.sms_juststomped --Minor crouch animation to make stomps feel more impactful.
					
						s.sms_juststomped = $-1
						if s.sms_juststomped
						
						and (
						((p.panim == PA_RUN) and s.sms_juststomped > 1)
						or (p.panim == PA_IDLE)
						or (p.panim == PA_WALK)
						or (s.state == S_SMS_ATOMLAND1) or (s.state == S_SMS_ATOMLAND2) or (s.state == S_SMSTOSSREADY)
						)
						
						and (P_IsObjectOnGround(s) and not (p.pflags & PF_SPINNING) or p.powers[pw_carry] == CR_MINECART)
							if s.SMS_squashtable
								s.SMS_squashtable,s.spriteyoffset,s.spritexoffset,s.spritexscale,s.spriteyscale = nil,0,0,FRACUNIT,FRACUNIT
							end
							s.sprite = SPR_SMSO
							s.frame = ($ & ~FF_FRAMEMASK)|Q
							
							local UW = 12000
							if (s.eflags & MFE_UNDERWATER)
								s.sms_juststomped = min($,4)
								UW = 18000
							end
							s.spritexscale = max(FRACUNIT/2, FRACUNIT-UW+(s.sms_juststomped*9001))
							s.spriteyscale = min(FRACUNIT+45356, FRACUNIT+UW-(s.sms_juststomped*7000))
							s.sms_squashy = true
							if (p.speed > s.scale)
								for i = -1,1,2
									local ghs = P_SpawnMobjFromMobj(s,
									FixedMul(35*s.scale*i, cos(p.drawangle)), 
									FixedMul(35*s.scale*i, sin(p.drawangle)), 
									0, MT_THOK)
									ghs.state = S_SPINDUST2
									ghs.fuse = 10
								end
							end
						else
							if s.sms_squashy
								s.spriteyscale = FRACUNIT
								s.spritexscale = FRACUNIT
								if ((p.panim == PA_IDLE) or p.skidtime) and s.sprite == SPR_SMSO
								and (s.frame & FF_FRAMEMASK) == Q and P_IsObjectOnGround(s)
									if p.skidtime
										s.state = S_PLAY_WALK
									else
										s.state = S_PLAY_STND
									end
								end
								s.sms_squashy = nil	
							end			
						end
					elseif s.sms_stompspin != nil
						if not p.sms_stompanim
						or p.sms_afterstompthok
						or (s.stompanimburst <= 6)
						or p.powers[pw_carry]
						or p.powers[pw_nocontrol]				
							s.sms_stompspin = nil
							if s.hmsspinpass != nil
								p.drawangle = s.hmsspinpass
								s.hmsspinpass = nil
							end	
						else						
							s.sms_stompspin = $+ANGLE_45
							p.drawangle = s.sms_stompspin
						end
					elseif (p.powers[pw_carry] == CR_PTERABYTE) and s.state != S_SMS_ADVFALL1
						s.state = S_SMS_ADVFALL1	
					end
				end
				if p.hmsenoughdeath and p.hmsrushactive --SMSchallenges related.
					if (p.hmsenoughdeath < 370) and (gametype == GT_COOP)
						s.sprite = SPR_HMSO
						s.frame = ($ & ~FF_FRAMEMASK)|B
						s.tics = 500
					end
				end
				if s.SMS_stompmom
					if not p.homing --Freeze when homing!
						if P_IsObjectOnGround(s) and (s.SMS_stompmom*P_MobjFlip(s) < -3*s.scale)
							if p.hypermysticsonic
								SMS_stompimpact(s,p,3)
							elseif (s.SMS_stompmom*P_MobjFlip(s) < -61*s.scale)
								SMS_stompimpact(s,p,2)
							else
								SMS_stompimpact(s,p)
							end
						elseif p.powers[pw_carry] or (s.momz*P_MobjFlip(s) > -20*s.scale)
							s.SMS_stompmom = 0
						end
					end
				end
				s.SMS_lastz = s.z --For posterity
			end
			
			--End of SMS skin check.
			if s.rglowticket --ALWAYS go after all player animations have played out!
				SMSRglow(s, true)
			end
			s.NoSMSGlow = nil
			s.SMS_locked = nil --Unlock SMS actions after everything.
		end
	end
end)

//Now you can play as Toilet Paper and SMS on the run from hoarders! Okay, okay, TP stands for True Player in this case.
//SMS is actually quite a useful partner. Good at fighting, giving you a boost, powersharing, and can open inaccessible areas for you

addHook("BotTiccmd", function(p, cmd) if gamestate != GS_LEVEL return end
	if not (players[0] and players[0].valid and players[0].mo) return end --

	local tp = players[0]
	if tp.powers[pw_carry] == CR_PLAYER and (tp.cmd.buttons & BT_CUSTOM3)
	and tp.mo.skin == "sms"
	and p.powers[pw_tailsfly]
	and p.bot == 1
		cmd.forwardmove = tp.cmd.forwardmove
		if not (tp.transformdown and tp.transformdown > 2)
			cmd.buttons = $|BT_CUSTOM3
		end
		return --
	end local s = p.mo

	if not (s and s.skin == "sms" and p.bot == 1) return end --
	local tp = players[0]
	if s
		if p.powers[pw_shield]
			if (tp.powers[pw_shield] == SH_PINK or tp.powers[pw_shield] == SH_PITY or tp.powers[pw_shield] == 0) 
			and not (tp.powers[pw_shield] == SH_PINK and p.powers[pw_shield] == SH_PINK)
			tp.powers[pw_shield] = p.powers[pw_shield]
			P_SpawnShieldOrb(tp)
			P_FlashPal(tp, PAL_MIXUP, 4)
			CONS_Printf(tp, "\x83"+"SMS gave you his shield!")
			p.powers[pw_shield] = 0
			end
		end
		if not tp.powers[pw_invulnerability] and p.powers[pw_invulnerability] and (p.powers[pw_invulnerability] > 95)
		tp.powers[pw_invulnerability] = p.powers[pw_invulnerability]
		P_FlashPal(tp, PAL_MIXUP, 4)
		CONS_Printf(tp, "\x83"+"SMS is sharing his invulnerability powers!")
		end
		if not tp.powers[pw_sneakers] and p.powers[pw_sneakers] and (p.powers[pw_sneakers] > 95)
		tp.powers[pw_sneakers] = p.powers[pw_sneakers]
		P_FlashPal(tp, PAL_MIXUP, 4)
		CONS_Printf(tp, "\x83"+"SMS is sharing his sneaker powerup")
		end
		if s.smsbothomingspam
			if p.hometrickanim and (s.smsbothomingspam < 5)
				s.smsbothomingspam = 19
			else
				s.smsbothomingspam = $-1
			end
			cmd.buttons = $|BT_SPIN|BT_CUSTOM1
			cmd.forwardmove = 22
		  if not p.homing
			if (s.smsbothomingspam > 29)
				cmd.buttons = $|BT_JUMP
				if P_IsObjectOnGround(s)
					s.smsbothomingspam = 29
				end
			elseif (s.smsbothomingspam % 6 == 0)
				cmd.buttons = $|BT_JUMP
			else
				cmd.buttons = $ & ~BT_JUMP
			end

			searchBlockmap("objects",function (s,mo)
				if not (mo and mo.valid and mo.health)
				return true
				elseif (mo.flags & (MF_BOSS|MF_ENEMY))
				and P_CheckSight(s,mo) and not (mo.z > s.z+s.height)
					s.angle = R_PointToAngle2(s.x, s.y, mo.x, mo.y)
					return true
				end
		  	end,s, s.x-600*s.scale,s.x+600*s.scale, s.y-600*s.scale,s.y+600*s.scale)			
			
		  end
		return true
		end
		
		if p.smspickup and (tp.cmd.buttons & BT_TOSSFLAG)
			cmd.buttons = $|BT_TOSSFLAG
			s.smsbottf = 0
		return true
		end
		if (tp.cmd.buttons & BT_TOSSFLAG)
		or (tp.pflags & PF_STARTDASH) and (tp.cmd.buttons & BT_JUMP)
		or (tp.cmd.buttons & BT_SPIN) and (tp.pflags & PF_JUMPED) and (tp.spinhold != nil) and (tp.spinhold < 2)
		s.smsbottf = 0
			if (P_IsObjectOnGround(s) or (p.pflags & PF_JUMPED) or p.hometrickanim or (s.state == S_PLAY_SPRING) or (s.state == S_SMS_ADVFALL1) or (s.state == S_SMS_ADVFALL2))
			and not p.footsweepanim //Attack in this direction
				cmd.buttons = $|BT_CUSTOM1|BT_JUMP|BT_SPIN
				cmd.forwardmove = 25
				cmd.sidemove = 0
				if (p.pflags & PF_JUMPED) or p.hometrickanim
				s.smsbothomingspam = 29
				else
				s.smsbothomingspam = 40
				end
				return true
			end		
		end
		if (tp.cmd.buttons & BT_CUSTOM1)
			cmd.buttons = $|BT_CUSTOM1
			s.smsbottf = 0
		end
		if (tp.cmd.buttons & BT_CUSTOM2)
			cmd.buttons = $|BT_CUSTOM2
			s.smsbottf = 0
			if P_IsObjectOnGround(s) or p.footsweepanim
				if not (p.sms_boostlevel > 64)
					cmd.forwardmove = 0
					cmd.sidemove = 0
				end
				
				searchBlockmap("objects",function (s,mo)
					if mo.valid and (mo.flags & (MF_BOSS|MF_ENEMY)) and mo.health
					and not (mo.z > s.z+(200*s.scale*P_MobjFlip(s)))
						if P_CheckSight(s,mo) 
							p.botsmst = mo
							p.sweepangle = R_PointToAngle2(s.x, s.y, mo.x, mo.y)
							s.angle = p.sweepangle
							return true --
						end
					end
		  		end,s, s.x-900*s.scale,s.x+900*s.scale, s.y-900*s.scale,s.y+900*s.scale)	
		  		if not p.botsmst
					s.angle = tp.mo.angle
					p.sweepangle = tp.mo.angle			
				end
				p.botsmst = nil	
				p.sweepspeed = 65*s.scale
			end
		end
		if (tp.cmd.buttons & BT_WEAPONPREV)			
		cmd.buttons = $|tp.cmd.buttons & ~BT_CUSTOM3 & ~BT_TOSSFLAG
		s.smsbottf = 0
		end	
		if (FixedHypot(s.x-tp.mo.x, s.y-tp.mo.y) < 200*s.scale) and not p.smspickup
			if ((tp.cmd.buttons & BT_CUSTOM3) or tp.readyforpowershare) and P_IsObjectOnGround(s)
			cmd.buttons = $|BT_CUSTOM3
			cmd.forwardmove = 0
			cmd.sidemove = 0
			s.smsbottf = 0
			elseif s.smsbottf and (s.smsbottf > 45)
			s.angle = R_PointToAngle2(s.x, s.y, tp.mo.x, tp.mo.y)
			p.drawangle = R_PointToAngle2(s.x, s.y, tp.mo.x, tp.mo.y)
			cmd.buttons = $|BT_TOSSFLAG
			cmd.forwardmove = 0
			cmd.sidemove = 0
				if (tp.cmd.buttons & BT_JUMP) and (tp.pflags & PF_JUMPED)
				cmd.buttons = $|BT_JUMP & ~BT_TOSSFLAG	
				s.smsbottf = 0
				end
			end
			return true 
		end
		if p.footsweepanim
			cmd.forwardmove = 0 cmd.sidemove = 0 return true --
		end
	end
end)

--Hurt enemies on touch during certain animations.
addHook("PlayerCanDamage", function(p)
	if p.mo and p.mo.skin == "sms"
	local s = p.mo	
		if p.footsweepanim
		or (s.state == S_SMS_STOMP1)
		or (s.state == S_SMS_STOMP2)
		or p.smsdivetimer // and (p.smsdivetimer > 6)
		or p.hometrickanim
		or p.homing
		or (p.speed > 120*s.scale) and p.powers[pw_sneakers]
			return true
		end
	end
end)

--No special abilities when in the menu for anyone! Also not when SMS does certain dodge stuff.
addHook("SpinSpecial", function(p)
	if p.sms_intoship
	or p.sms_shipready
	or (p.sms_dodging and p.sms_dodging > 3)
	or p.mo and (p.mo.dodgeoh or p.mo.tnospina)
		return true
	end
end)

--Enter small holes when...! Hook apparently resets height to default every other frame.
addHook("PlayerHeight", function(p)
	if p.mo and p.mo.skin == "sms"
	local s = p.mo
		if p.footsweepanim
		or (s.state == S_SMSTOSSREADY)
			return 31<<16
		end	
	end
end)
addHook("PlayerCanEnterSpinGaps", function(p)
	if p.mo and p.mo.skin == "sms"
	local s = p.mo
		if p.footsweepanim
		or (s.state == S_SMSTOSSREADY)
			return true
		end	
	end
end)