--╔════════════════════════════╗══════════════════════════════════════════════════════════════════════════════════════════════#
--║     MT_PLAYER Collission   ║ MT_PLAYER object & wall collision hooks.
--╚════════════════════════════╝══════════════════════════════════════════════════════════════════════════════════════════════#
local XSon = XSon
local function P_MobjFlip(s) if s and s.valid and ((s.eflags&MFE_VERTICALFLIP) or (s.flags2&MF2_OBJECTFLIP)) return -1 end return 1 end
local CT = XS_CRASHTABLE

--Yo Blur! Add objects here for X Sonic to zipdash up. He'll only do it on tall enough, solid objects. But UMDF can scale objects!
--Here's a list of SRB2 objects: https://wiki.srb2.org/wiki/List_of_Object_types
--Don't forget the comma on all but the last thing.
local XCLIMBOBJECTS = {
MT_PINETREE,
MT_EGGSTATUE,
MT_DSZSTALAGMITE,
MT_DSZ2STALAGMITE,
MT_FHZTREE,
MT_FHZPINKTREE,
MT_BUSHTREE,
MT_POLYGONTREE
}

--Returns true if you hit the shield first.
local function ShieldorNot(p,s,xs, v)
	if v.target and v.target.valid and v.target.health and (v.target.type == MT_EGGGUARD)
	and xs and xs.lastx!=nil and xs.lasty!=nil
		local t = v.target
		local TANG = t.angle
		local ANG = R_PointToAngle2(s.x, s.y, s.x+s.momx, s.y+s.momy)
		local ANGLE,PLUS = XS_AngleDifference(ANG, TANG, 2)
		if (ANGLE < ANG1*58)
			t.flags = $|MF_SHOOTABLE|MF_ENEMY
			P_DamageMobj(t, s, s)
			P_KillMobj(v) return false --
		end
	end return true --
end

--Bounce off something
local function X_Rebound(p,s,xs,v)
	if not (v) or (v.type==MT_EGGSHIELD) and (ShieldorNot(p,s,xs, v)!=true)
	or ((v.type==MT_EGGGUARD) and (v.flags & MF_SHOOTABLE))
		return false --
	end
	local ANG = R_PointToAngle2(s.x, s.y, s.x+s.momx, s.y+s.momy)
	if xs.zipdash ANG = xs.zipdashangle XSZipDashCancel(p, s, xs) end
	
	xs.rollcd,xs.ringburstcd = 6,6
	s.z = $+(8*s.scale*P_MobjFlip(s))
	
	if not (P_TryMove(s, s.x+FixedMul(-99*s.scale, cos(ANG)), s.y+FixedMul(-99*s.scale, sin(ANG)), true))
		P_TryMove(s, s.x+FixedMul(-66*s.scale, cos(ANG)), s.y+FixedMul(-66*s.scale, sin(ANG)), true)
	end
	XSon.SwapStretch(p, s, xs)
	s.spritexscale,s.spriteyscale = FRACUNIT+27000, FRACUNIT-17000
	s.spriteyoffset = -5<<16	
	s.XSstretch = {timer = 4, Xspeed = 5000, Yspeed = 3900}
	
	P_SetObjectMomZ(s, (s.eflags & MFE_UNDERWATER) and 4<<16 or 8<<16, true)
	if ( v.type == MT_EGGSHIELD)
		local ghs = XS_CircleWave(s, 1, SKINCOLOR_SKY, 15, FRACUNIT)
		ghs.frame = E|FF_TRANS40
		ghs.dispoffset = 144
		ghs.spriteyoffset = $-24<<16		
		XSon.Quake(so, 45<<16, 3)
		S_StartSound(s, sfx_s3k7b) S_StartSound(v, sfx_s3k7b)
		s.XS_reboundtime = 11
	else
		local ghs = XS_CircleWave(s, 1, SKINCOLOR_RUBY)
		ghs.blendmode = AST_ADD
		ghs.sprite = SPR_XSG2
		ghs.frame = 31|FF_TRANS80
		P_Thrust(ghs, ghs.angle-ANGLE_90, s.scale*3)	
		ghs.spritexscale,ghs.spritexscale = $*2/3,$*2/3
		
		XSon.Sound(s, sfx_xssfpl, 99) S_StartSound(s, sfx_dmpain)
		XSon.Quake(so, 77<<16, 7)
		s.XS_reboundtime = 5
	end
	P_InstaThrust(s, ANG, -33*s.scale)
	xs.spin = 999<<16
	s.state = S_PLAY_XDIVEFLIP1
	p.pflags = $ & ~(PF_SPINNING|PF_JUMPED|PF_STARTDASH)
	xs.stopstick = 4
	if p.xboomtable --Fade Sonic Booms quicker.
		for _,ghs in pairs(p.xboomtable)
			if ghs and ghs.valid ghs.fuse = min($,3) end
		end
	end	
	if not p.powers[pw_flashing]
		p.powers[pw_flashing] = 8
		s.flags2 = $|MF2_DONTDRAW
	end return true --
end

--Spring Collision detection stuff.
local function XSonicCollide(s, v)
	if not IsXSonic(s) or not (v and v.valid) or not (s.player and s.player.xsonictable) return end
	local p = s.player local xs = p.xsonictable local yu = xs local RB = xs.FreeFlight
	if v == xs.snaptarget and (xs.snapdive or (xs.snapdivetrick and xs.snapdivetrick < 10))
		return false --Never collide with the thing you're tricking off!
	end

	--Player touch?
	if v.player
		if (s.eflags & MFE_UNDERWATER) and v.player.powers[pw_shield] 
		and (v.player.powers[pw_shield] & SH_PROTECTWATER)
		and not s.XS_watergiftcd and (v != s) --Duh?
		
		local shield = (p.powers[pw_shield] & SH_NOSTACK) --Disregarding fireflower data, what's our shield?
		local extra = (p.powers[pw_shield] & SH_STACK) --Re-add fireflower data if it's there.
		
			if not shield or shield == SH_PITY or shield == SH_PINK --If it's these or none, upgrade your shield!
				p.powers[pw_shield] = v.player.powers[pw_shield]|extra
				S_StartSound(s, sfx_s3k3f)
				P_FlashPal(p, PAL_RECYCLE, 2)
				P_NukeEnemies(s, s, 0)
				P_SpawnShieldOrb(p)
				CONS_Printf(p, "\x83"+"You've received a charm to protect you from water!")
				s.XS_watergiftcd = 300
			end
		end	
		return --
	end
	
	--Spring behaviour.
	if (v.flags & MF_SPRING)
		if xs.snapdive and ((s.eflags & MFE_SPRUNG) or xs.hitspring or (s.momz*P_MobjFlip(s) >= -s.scale))
		or xs.snapdivetrick and (xs.snapdivetrick < 10)
			return false --
		end
		if not (s.z > v.z+v.height) and not (v.z > s.z+s.height) --Height check.
			if (s.state == S_PLAY_XWALLRUN) s.state = S_PLAY_SPRING end
			if p.xswallrun or xs.walljump
				p.aiming = 0
			end
			XSSnapDiveReset(p, s, xs, true)
			xs.hitspring = 2
			p.xswallrun,xs.walljump,xs.walljumpP = nil
			RestoreZipDashCam(p, s, xs)
			p.camerascale = skins[s.skin].camerascale
			p.XSscalecam = 1
			xs.yteleport,xs.zipclimbedwall = nil
		--	xs.squat,xs.jumpboost,xs.squatcharge = 0,0,0
		--	xs.didjumpboost = true
			if (p.cmd.buttons & BT_JUMP) or xs.jump
				xs.jump = 99
				p.pflags = $|PF_JUMPDOWN
			end

 			--A spring's "mass" value means it's vertical spring power! Confusing? Sure.
			--An excuse to rename it and nuke all backwards compatibility? No. Bad Sonic Team Jr. Do something productive.	
			if not v.info.mass and v.info.damage
				xs.nospindrag = 60
				if (v.info.damage > 45<<16)
					s.friction = FRACUNIT --Uphold some speed from the spring! Like...vanilla honestly should've been doing.
					xs.runfriction = FRACUNIT
					xs.frictionlock = (((v.info.damage/FRACUNIT)-45)/2)
					if (xs.frictionlock > 23)
						xs.frictionlock = 23
					elseif (xs.frictionlock < 5)
						xs.frictionlock = 5
					end
				end
				if not RB and not xs.squat and not (s.momz*P_MobjFlip(s)>0)
					--Snap to the floor for smoother gameplay!
					if ((v.z < v.floorz+30*v.scale) and not (s.eflags & MFE_VERTICALFLIP))
					or ((v.z > v.ceilingz-60*v.scale) and (s.eflags & MFE_VERTICALFLIP))	
						if (s.eflags & MFE_VERTICALFLIP) and (s.z > s.ceilingz-110<<16)
							if not (p.pflags & PF_SPINNING)
							s.state = S_PLAY_FALL
								if not P_IsObjectOnGround(s)
									p.pflags = $|PF_JUMPED & ~PF_THOKKED								
								end
							end
						elseif (s.z < s.floorz+110<<16)
							if not (p.pflags & PF_SPINNING) --Allow continued spinning/charging.
							s.state = S_PLAY_FALL
								if not P_IsObjectOnGround(s)
									p.pflags = $|PF_JUMPED & ~PF_THOKKED
								end
							end
						end
					end
				end
				--Redirect Sonic's zipdash from a spring.
				if xs.zipdash
					xs.zipdashangle = v.angle
					xs.zipcd = 0
					xs.bonkeddash = 0
				end
				if (s.state == S_PLAY_XZIPSLIDE) or (s.state == S_PLAY_XZIPSLIDE2) or ((s.sprite2 & ~FF_SPR2SUPER) == SPR2_SKID)
				or xs.ChaosSnap
					if P_IsObjectOnGround(s)
						s.state = (p.speed) and S_PLAY_RUN or S_PLAY_STND
					else
						s.state = (s.momz*P_MobjFlip(s) > 8*s.scale) and S_PLAY_SPRING or S_PLAY_FALL
						P_InstaThrust(s, v.angle, v.info.damage+(v.info.damage/5))
						P_SetObjectMomZ(s, 7<<16, true)
					end
					s.tics = 2
				end		
			elseif not v.info.damage --Vertical-only springs!
				if (xs.snapdive or xs.snapdivetrick)
				and not ((v.eflags & MFE_VERTICALFLIP) and not (s.eflags & MFE_VERTICALFLIP))
					if (v.sprite == SPR_BLON) or (v.sprite == SPR_BUMP)
						S_StopSoundByID(s, sfx_sprong)
						xs.strongspring = 2
					else
						xs.strongspring = 1
					end
				elseif s.XSlunging or s.XS_DarkTable and s.XS_DarkTable.darkrush
					s.momx,s.momy = $/3,$/3
				end
			end
		end	
		return --	
	elseif (v.type == MT_SALOONDOOR) or (v.type == MT_SALOONDOORCENTER)
		if gametype == GT_COOP
			if v.health and (p.powers[pw_super] or (p.pflags & PF_SPINNING) or xs.zipdash or p.xsringburst or s.XSassault or xs.darkphase and (p.pflags & PF_THOKKED))
			and not (v.flags & MF_NOCLIP)
				S_StartSound(s, sfx_wbreak)
				for d = 0,12
					local debris = P_SpawnMobj(v.x+(P_RandomRange(-50,50)*v.scale), v.y+(P_RandomRange(-50,50)*v.scale), v.z+(P_RandomRange(5,80)*v.scale), MT_WOODDEBRIS)
					if debris and debris.valid
						debris.flags = $|MF_BOUNCE|MF_GRENADEBOUNCE|MF_NOCLIPTHING
						P_InstaThrust(debris, s.angle+(P_RandomRange(-20,20)*ANG1), p.speed+8*s.scale)
						P_SetObjectMomZ(debris, P_RandomRange(-4,8)*s.scale, false)
					end
				end
				if (v.type == MT_SALOONDOOR)
					v.flags = $|MF_NOCLIP
					P_KillMobj(v, s, s)
				elseif (v.type == MT_SALOONDOORCENTER)
					v.flags = $|MF_NOCLIP
				end
				return false --
			end
		end
		return --
	end	
	
	--Dodge these objects completely with the snap dive.		
	if xs.snapdivetrick or xs.snapdive
	or (s.state >= S_PLAY_XKICK2) and (s.state <= S_PLAY_XKICK4)
		if ((v.type >= MT_EGGMOBILE_FIRE) and (v.type <= MT_SHOCKWAVE))
			return false --
		elseif (v.flags & (MF_ENEMY|MF_BOSS|MF_PAIN|MF_PUSHABLE)) and not (xs.snapdivetrick > 13)
			return false --
		elseif (v.flags & MF_MISSILE) and not (xs.snapdivetrick > 13) --Dodge missiles!
			local t = v.target local tp = t and t.player
			if not (tp and (tp.powers[pw_super] or tp.hypermysticsonic or tp.xsonictable and tp.xsonictable.supersonic))
			and not (v.height > 150<<16)
				return false --
			end
		end
	elseif (v.flags & (MF_ENEMY|MF_BOSS))
		if xs.darkphase and s.XS_DarkTable and s.XS_DarkTable.darkrush
			if v.health and (v.flags & MF_SHOOTABLE) and not (v.flags2 & MF2_FRET)
				if not (s.z > v.z+v.height+2*s.scale)
				and not (v.z > s.z+s.height+2*s.scale) --Height check.
				and s.XSAssaultT != v
					s.XSAssaultT = v
					XSon.DSSlunge(p,s,xs,s.XS_DarkTable, 2222<<16) 
					return false --
				end
			end
	/*	elseif xs.zipdash and (xs.zipdash>5) or s.XS_reboundtime --Rebound of these guys.
			if (v.type == MT_FACESTABBER)
			or (v.type == MT_EGGMOBILE)
			or (v.type == MT_EGGMOBILE3)
			or (v.type == MT_CYBRAKDEMON)
				if (s.z > v.z+v.height+(s.scale*5))
				or (v.z > s.z+s.height+(s.scale*5))
					print("1")
					if s.XS_reboundtime
						print("2")
						return false --
					elseif not (p.powers[pw_super] or p.powers[pw_invulnerability] or xs.darkphase or p.xsringburst)
						print("3")
						if (X_Rebound(p, s, xs, v)==true) 
							print("4")
							return false --
						end 
					end	
				end
			end	*/	
		end
	else
		if (s.z > v.z+v.height+(s.scale*20))
		or (v.z > s.z+s.height+(s.scale*20))	
			return --
		end	
		--Destroy special objects?
		local SUPER = XS_Super(p) or 0
		if xs.zipdash and (xs.zipdash > 5)
		or SUPER == 3
		or s.XS_DarkTable and s.XS_DarkTable.darkrush
		or p.xsringburst
			if v.health
				if (v.type == MT_SPIKE) or (v.type == MT_WALLSPIKE) or (v.type == MT_TURRET)
					if xs.zipdash or xs.supersonic or p.xsringburst
						P_KillMobj(v, s, s) return false --
					end
				elseif (v.type == MT_EGGROBO1)
					if SUPER == 3 return false end
				elseif (v.type == MT_EGGSHIELD)
					if s.XS_reboundtime
						return false --
					elseif xs.zipdash and (xs.zipdash > 5) and not (p.powers[pw_super] or p.powers[pw_invulnerability])
						if (X_Rebound(p, s, xs, v)==true) return false end 
					end	
					local t = v.target
					if t and t.valid and (t.type == MT_EGGGUARD) and t.health
						if (xs.zipdash and (xs.zipdash > 4)) and SUPER==0
							X_Rebound(p,s,xs,v) return false --				
						else
							t.flags = $|MF_ENEMY|MF_SHOOTABLE
							if SUPER
								S_StartSoundAtVolume(s, sfx_xssfxp, 180)
								S_StartSoundAtVolume(s, (leveltime%2) and sfx_xssfxr or sfx_xssfxt, 180)
								XSon.gibs(t, s, nil, 70<<16, (SUPER == 3) and true or (SUPER == 2) and 3 or nil)
								P_DamageMobj(t, s, s, 2, DMG_NUKE)
							end
						end
					end
					P_KillMobj(v, s, s) return false --
				elseif (v.type == MT_CYBRAKDEMON_ELECTRIC_BARRIER)
					if SUPER==3 and v.target and v.target.valid and (v.target.type == MT_CYBRAKDEMON) and (v.target.health < 13)
						v.target.health = $+1 --Don't do damage to Brak if he has a shield.
					end
					P_KillMobj(v, s, s) return false --
				end
			end
		end
		if p.xsringburst and (p.speed+abs(s.momz) > 55<<16)
			SUPER = 3
		end
		if ( (CT[v.type]) )
		and ( (SUPER and xs.zipdash) or (SUPER>=2) )
			if not v.WaitingXTree --not (v.flags & (MF_NOCLIPTHING|MF_GRENADEBOUNCE)) and not (v.flags2 & MF2_DONTDRAW)
			and not ((s.XS_BreakThroughItAll or 0) > 60)
				if p.xsringburst
				or xs.zipdash and (xs.zipdash>4) and not xs.zipdashslide and (p.speed > 16<<16)
				or s.XSAssault and ( (p.speed > 50*s.scale) or ((s.XSAssault.timer or 0) < 5) )
				or (SUPER == 3) and (s.XSlunging or xs.snapdivetrick or s.XS_DarkTable and s.XS_DarkTable.darkrush)
					if (XSon.ScenicCrash(p,s,xs, v, SUPER))==true
						return false --
					end
				end
			end
		end
	end
end
addHook("MobjCollide", XSonicCollide,  MT_PLAYER)
addHook("MobjMoveCollide", XSonicCollide,  MT_PLAYER)

--Player wall-touching behaviour, such as slide off walls a bit with the zipdash, bouncing off with ringburst, or running up one.
addHook("MobjMoveBlocked", function(s, m, line)
	if not (IsXSonic(s) and s.player and s.player.xsonictable) return end --
	local p = s.player local xs = p.xsonictable local yu = xs
	
	if xs.snapdivetrick and m and (xs.snapdivetrick < 12) --Can't get blocked by mobjs like this.
		if not ((s.state >= S_PLAY_XKICK1) and (s.state <= S_PLAY_XKICK4))
			return false --
		end
	end
	--We're currently ringbursting!	
	if p.xsringburst
		p.powers[pw_strong] = $|STR_WALL|STR_UPPER|STR_FLOOR
		if line 
			
			local RB = p.xsringburst --Turn RB into Ring Burst instead!
			if RB.wallbounce return end --
			local JUMPSPIN = false
			if RB.immunetobounce and (p.cmd.buttons & (BT_JUMP|BT_SPIN))
				if (abs(s.momz) > s.scale*11)
					JUMPSPIN = true --Force jumpspin!
				else
					if (xs.quickdrain==0 or xs.quickdrain==1) and (p.cmd.buttons & BT_JUMP)
						xs.quickdrain = 3
					end
					P_SetObjectMomZ(s, (p.cmd.buttons & BT_JUMP) and s.scale/4 or -s.scale/3, true) return --
				end
			end
			if JUMPSPIN == false
				JUMPSPIN = ((p.cmd.buttons & (BT_JUMP|BT_SPIN)) and (RB.rocket or (abs(s.momz) > 27*s.scale))) and true or false
			end
			if ((s.flags2 & MF2_TWOD) or twodlevel) and JUMPSPIN != true
				RB.wallstress = min($+7, 100)
				XSon.Bounce(p, s, xs, line)
			else
				if P_IsObjectOnGround(s) and RB.groundturn
					return false --
				end
				local ANGDIFF,PLUS,TOWALLANG = XSon.WallMOMANG(s,line) 
				local BOUNCELEN = ((P_IsObjectOnGround(s)) and ANG20 or ANG30) - ( abs(p.cmd.sidemove) * (ANG1/9) )
				if (ANGDIFF < BOUNCELEN) and JUMPSPIN!=true and not RB.TapBurst --You're basically pressing towards the wall. Bounce!
					if RB.immunetobounce return false end --
					RB.wallstress = min($+2, 100)
					XSon.Bounce(p, s, xs, line)
				else
					if not JUMPSPIN or (ANGDIFF >= ANG20*4)
						if RB.immunetobounce return false end
						RB.speed = RB.speed and FixedDiv(FixedMul($,16),17) or 9*s.scale
						RB.angle = $+(ANG1*13*PLUS)
						s.angle = RB.angle p.drawangle = RB.angle
						s.rollangle = ANG10*7*PLUS
						XS_RingBurstStripes(s, nil, AST_ADD)
						RB.stripes = 8
						P_InstaThrust(s, RB.angle, RB.speed) --	print("Bounce ANGDiff "+FixedInt(AngleFixed(ANGDIFF))*PLUS)
						RB.wallstress = min($+7, 100)
						if (RB.wallstress > 80) --Too much! Bonk out of the Ring Burst!
							XSRingBurstReset(p, s, xs)
							xs.zipdashangle = TOWALLANG
							XSon.Bonk(p, s, xs) return false --
						end	
					elseif JUMPSPIN --Race straight up or down a wall!
						RB.wallstress = min($+2, 100)
						s.rollangle = 0
						if (abs(s.momz) < 70*s.scale)
							P_SetObjectMomZ(s, (p.cmd.buttons & BT_JUMP) and 5*s.scale or -5*s.scale, true)
						end
						local ghs,ths = XS_RingBurstStripes(s, nil, AST_ADD)
						if ghs and ths
							ghs.rollangle = $+((s.momz<0) and ANGLE_270 or ANGLE_90)
							ths.rollangle = $+((s.momz<0) and ANGLE_270 or ANGLE_90)
						end							
					end
					RB.rollangle = s.rollangle 
					RB.wallbounce = 3
					RB.rollspeed,RB.straightshot = 0,0
					XSon.Sound(s, sfx_kc40, 99)
					for i = -1,1 --Fling rocks around!
						local ghs = XS_CraterSpew(s, RB.angle+(ANG60*i), 9)
						if ghs and ghs.valid
							ghs.momx = $*5 ghs.momy = $*5
							ghs.tics = 45 ghs.fuse = 44
						end
					end		
					XSon.Quake(s, max(60<<16, min(10<<16, p.speed/2)), 3)	
					return false --
				end
			end
		end return --Ring Burst is a priority state.
	end
	
	local RB = xs.FreeFlight
	
	--Currently wallrunning?
	if p.xswallrun
		local WR = p.xswallrun
		if line and WR.line == line and line.valid and WR.treetop == nil and not (WR.tree and WR.tree.valid)
			if not RB and XS_SkyBoxWall(s, line, 0) return end 
			if (XS_AboveOrBelowSector(s, line, (RB) and 8 or 120*s.scale)) != true return end --120 FRACUNIT is pretty lenient!

		--	if WR.line == line --Neat, it's the same line!
			
				WR.countdown = 4 return --	
				
		/*	elseif not WR.swappedline and (abs(p.cmd.sidemove) > 9) --New line?
			and not ((s.flags2 & MF2_TWOD) or twodlevel)
			and (s.flags==0)
		
				local x,y = P_ClosestPointOnLine(s.x, s.y, line) 
				
			--	if (FixedHypot(s.x-x, s.y-y) < 240*s.scale) --Sanity check!
				
					local TOWALLANG = R_PointToAngle2(s.x, s.y, x, y)
					local ANGLE,PLUS = XS_AngleDifference(TOWALLANG, WR.faceangle, 2)
				--	local LINEANGLE = R_PointToAngle2(line.v1.x, line.v1.y, line.v2.x, line.v2.y)
				--	local ANGLE,PLUS = XS_AngleDifference(LINEANGLE, WR.lineangle, 2)	
				
					--If the wall has a mild enough angle, attach Sonic to it!
					if (ANGLE < ANG10*5)	
				--	/	local DIST1,DIST2,POINTANG = FixedHypot(s.x-line.v1.x, s.y-line.v1.y),FixedHypot(s.x-line.v2.x, s.y-line.v2.y)
				--		if (DIST1 <= DIST2)
				--			P_TryMove(s, line.v1.x, line.v1.y, true)
				--			POINTANG = R_PointToAngle2(s.x, s.y, line.v1.x, line.v1.y)
				--		else
				--			P_TryMove(s, line.v2.x, line.v2.y, true)
				--			POINTANG = R_PointToAngle2(s.x, s.y, line.v1.x, line.v1.y)
				--		end 
						WR.faceangle = TOWALLANG
						local ANGLE,PLUS = XS_AngleDifference(s.angle, WR.faceangle, 2, ANG60)	
						if ANGLE and (ANGLE < ANG30) --Adjust angle?
							s.angle = WR.faceangle
						end
						p.drawangle = WR.faceangle
					--	if POINTANG != nil
					--		local SANG = (p.cmd.sidemove < 0) and POINTANG+ANG1*55 or POINTANG-(ANG1*55)
					--		P_InstaThrust(s, POINTANG+SANG, 30*s.scale)
					--		WR.swappedline = 7	
					--		print("\x82 SwapLine! Thrust to PointAng")
					--	else 
							local SANG = (p.cmd.sidemove < 0) and WR.faceangle+ANG20 or WR.faceangle-(ANG20)
							WR.swappedline = 5
							P_InstaThrust(s, SANG, 16*s.scale)
					--		print("\x83 SwapLine, face new angle")
					--	end
						WR.line = line
						WR.lineangle = R_PointToAngle2(line.v1.x, line.v1.y, line.v2.x, line.v2.y)
						WR.countdown = 5
						
						--Now check if we're running on a FOF again or not.
						local secT = (s.subsector) and s.subsector.sector
						local lineside = ((line.backsector) and line.backsector != secT) and line.backside or line.frontside
						local secL = lineside.sector
						if XS_FOFcheck(s, secL)
							WR.fofline = line
						else
							WR.fofline = nil
						end
					end	
				end
			end  */
		end
		return --Don't need to continue this hook if we're wallrunning.
	end
	
	if p.powers[pw_carry] or xs.zipdashslide return end --We're done early, it seems.
	
	--============================================================================================
	--Let's try to start wallrunning! Leap into the wall to swoosh on up!
	--============================================================================================
	
	--In Matches, slide/bounce off walls when going really fast.
	if RB 
		if RB.LockOn and (xs.zipdash and (xs.zipdash>3) or RB.RingBurstChase) and RB.LockOn.valid
			if RB.RingBurstChase
				if not (RB.RBside) or (RB.RingBurstChase > 42)
					P_BounceMove(s)
				else
					P_SlideMove(s)
				end
				p.blocked = (p.blocked==false) and true or 1
			else
				P_SlideMove(s)
			end
			return false --Slide off walls at this stage.
		end
	elseif (p.cmd.buttons & BT_CUSTOM1) and xs.zipdash and P_IsObjectOnGround(s) and (xs.zipdash < 12)
		if not ((p.cmd.buttons & BT_JUMP) and (xs.jump < 6))
		and not ((p.cmd.buttons & BT_SPIN) and (xs.spin < 7))
			xs.custom1 = max(xs.custom1 or 0, 22)
		end
	end	
	
	local SUPER = XS_Super(p)
	
	--Wanna run up some walls?
	if xs.zipdashleap
	or SUPER==3 and (s.airzipWR or (xs.zipdash and (p.cmd.buttons & BT_JUMP)) )
	and not P_IsObjectOnGround(s) and (xs.zipdash > 4) and not RB
		if line
			local GOAHEAD = true
			local TOWALLANG =  XSon.WallAngle(s, line) --R_PointToAngle2(s.x, s.y, x, y)
			local MOMANG = xs.zipdashangle
			if not (xs.zipdashleap or xs.zipdash or xs.zipdashangle==nil)
				MOMANG = R_PointToAngle2(s.x, s.y, s.x+s.momx, s.y+s.momy) 
			end
			
			local ANGDIFF = XS_AngleDifference(MOMANG, TOWALLANG, 2)
			if (ANGDIFF > ANGLE_45) --Momentum isn't lining up with wall at all!
				if (xs.direct < 2) or xs.turnang==nil
					GOAHEAD = false
				else --But I'll allow it anyway if they're pressing towards the wall.
					ANGDIFF = XS_AngleDifference(xs.turnang, TOWALLANG, 2)
					if (ANGDIFF >= ANG30) GOAHEAD = false end --They're not doing that either. No wallrun, then!
				end
			end
			if (GOAHEAD==false) --Slide across wall instead?
				if not (m and m.valid) s.momx,s.momy = FixedDiv(FixedMul($,8),9),FixedDiv(FixedMul($,8),9) return false end 
			elseif XS_StartWallRun(p,s,xs,line,nil)
				return true --We did a wallrun!
			end
		end
		if m and (m.height >= s.height*3) and (m.flags & MF_SOLID) and not RB --Just for shits and giggles, make the reference.
			if not (m.flags & MFE_VERTICALFLIP) --Don't care enough, just a cute reference.
			and not (s.eflags & MFE_VERTICALFLIP)
			and not P_IsObjectOnGround(s) 
			and (xs.direct and (xs.direct>8) or (p.cmd.buttons & BT_JUMP))
				for i = 1,#XCLIMBOBJECTS --Scan list of objects we can climb!
					if not (m.type == XCLIMBOBJECTS[i]) continue end --
					local TOTREE = R_PointToAngle2(s.x, s.y, m.x, m.y)
					p.xswallrun = {					
					start = 1,
					inittime = 1,
					walljumped = false,
					angupdate = 999,
					slip = 0, swappedline = 0,
					startpower = 12*s.scale, power = 12*s.scale,
					countdown = 3, 
					jump = 999<<16,spin = 999<<16, custom1 = 999<<16,
					faceangle = TOTREE,
					INITANG = TOTREE,
					INITANG2 = TOTREE,
					tree = m,
					direction = 1}
					
					local WR = p.xswallrun
					XSZipDashCancel(p, s, xs) xs.zipdash,xs.zipdashslide = 0,0 --Call this AFTER setting up the table.
					XSSnapDiveReset(p, s, xs, true)
					xs.justleaped = 0
					xs.zipdashleappower = 8	
					
					P_InstaThrust(s, TOTREE, 8*s.scale) return false --
				end
			end
		end
	elseif xs.walljump --xs.walljumpP should always be a positive value in theory.
		if line and not P_IsObjectOnGround(s) --Should always be a positive value in theory.
			if xs.walljumpP != nil and s.XSLastWall != nil and s.XSLastWall.v1 and line != s.XSLastWall
				local LASTWALLANG = R_PointToAngle2(s.XSLastWall.v1.x, s.XSLastWall.v1.y, s.XSLastWall.v2.x, s.XSLastWall.v2.y)
				local ANGDIFF = XS_AngleDifference(LASTWALLANG, R_PointToAngle2(line.v1.x, line.v1.y, line.v2.x, line.v2.y), 2)	
				if (ANGDIFF < ANG20*2) or (ANGDIFF > ANG20*7)  --If the wall is not terribly angled, here you go, walljump away!
				or (xs.walljump > 17) and ((ANGDIFF < ANG60) or (ANGDIFF > ANG20*6))
					local POWER = max(s.scale*2, xs.walljumpP+s.scale)
					if XS_StartWallRun(p,s,xs,line, POWER) return end --
				else
					xs.walljump = max($-2,0)
				end
			else
				xs.walljump = max($-2,0)					
			end
		end
	else
		if xs.initwallrun and line 
		and ((xs.zipdash>4 and xs.zipdash<9) or (p.pflags & PF_JUMPED))  
			if not ((p.cmd.forwardmove < -3) and (abs(p.cmd.sidemove) < 5))
			and not (p.cmd.forwardmove < -44)
			and not xs.zipcontinue --This whole system just to buffer a wallrun...
			and not (p.pflags & (PF_SPINNING|PF_SLIDING|PF_THOKKED|PF_NOJUMPDAMAGE))
				if XS_StartWallRun(p,s,xs,line, (21+(SUPER*8))*s.scale)
					S_StartSoundAtVolume(s, sfx_xssfx2, (SUPER==3) and 255 or 160)
					xs.initwallrun = nil 
					s.spritexscale = FRACUNIT-22000
					s.spriteyscale = FRACUNIT+38000
					s.spriteyoffset = $-12<<16
					s.XSstretch = {timer = 11, Xspeed = 2000, Yspeed = 3400}	
					if SUPER == 3
						P_RadiusAttack(s,s, 230<<16, DMG_NUKE, true)
						XSon.RadiantLight(s, SKINCOLOR_XSSUPERAURA, 15, 0, S|FF_TRANS30, FRACUNIT+24000)
					end
					XSZipTrail(s) return false --
				end
			end
		elseif s.XSlunging //or s.XS_DarkTable and s.XS_DarkTable.darkrush
			xs.bonkeddash = (xs.bonkeddash) and $+5 or 5
			if (xs.bonkeddash > 13)
				XSon.Bonk(p, s, xs, p.drawangle) return --Damn you, wall!
			end
		elseif RB	
			if RB.painstun --Bounce around when stunned!

				if not line return end
				if s.xsmaxcrush return false end --
	
				local x,y = P_ClosestPointOnLine(s.x, s.y, line) 
				local BOUNCEANGLE = R_PointToAngle2(s.x, s.y, x, y)+ANGLE_180
				local ANGSHIFT,PLUS = XS_AngleDifference(BOUNCEANGLE, XSon.MOMANGLE(s), 2, ANG20*2, 2)
				BOUNCEANGLE = $+(ANGSHIFT*PLUS)
				local BOUNCESPEED = max(55*s.scale, p.speed-(s.scale*2))
				if RB.painstack 
					if (RB.painstack > 7)
						return --Okay, that's just too much.
					end
					BOUNCESPEED = max(3*s.scale, $/RB.painstack)
				end
				P_InstaThrust(s, BOUNCEANGLE, BOUNCESPEED)
				XS_CircleWave(s, 1)
				XS_RingBurstStripes(s, SKINCOLOR_FLAME, AST_ADD)
				s.xsmaxcrush = 2
				xs.forceangle = {angle = BOUNCEANGLE, timer = max(RB.painstun, 6), nosangle = true, nossangle = true} 
				XSon.Quake(s, 118<<16, 8, 999<<16)
				XSon.Flash(s, PAL_RECYCLE, 2, 0)
				RB.painstack = RB.painstack and $+1 or 1	
				for i = 1,4
					local ghs = XS_CraterSpew(s, XSLocalRNG(0,356+(i*6),s), 14-i)
					if ghs and ghs.valid
						ghs.momx = $*i ghs.momy = $*i
					end
				end
				for EX = -1,1 --Explosion dust!
					for d = 1,4
						local ghs = P_SpawnMobjFromMobj(s, 0, 0,(20*EX<<16)+s.momz, MT_TNTDUST)
						ghs.scale = s.scale*(2-EX)
						P_SetObjectMomZ(ghs, 6*EX<<16)
						ghs.destscale = 99<<16
						P_InstaThrust(ghs, (ANGLE_90*d)+(EX*ANGLE_22h), 16*s.scale+(12*s.scale*EX))
						if EX != 1 ghs.flags2 = $|MF2_SHADOW
						elseif d == 1 and EX == 1 S_StartSoundAtVolume(ghs, sfx_xssfpl, 180) end
					end
				end
				if not RB.rollD
					RB.rollD = (p.cmd.sidemove<0) and -1 or 1
				else
					RB.rollD = (RB.rollD<0) and $-1 or $+1
				end
				if not s.XSsuperroll
					s.XSsuperroll = (p.cmd.sidemove < 0) and -8 or 8
					s.spritexscale,s.spriteyscale = FRACUNIT-25000,FRACUNIT+25000 s.XSstretch = {timer = 5}
					if not RB.NoSlamDamage
						RB.PvPHEALTH = max($-54, 0)	
					end
				end
				return false --
			end
		elseif p.panim == PA_WALK or p.panim == PA_RUN --Don't speed up walk anims when pressing against walls, it looks silly.
			xs.nowalkanim = 3
		end
	end
	if xs.bonked return end --	Ignore everything past to come if one of these is active.
	
	--Can we bonk?
	local CANBONK = false
	if (xs.zipdash > 4)
		if not (s.eflags & MFE_SPRUNG)
		and not p.powers[pw_justlaunched]
		and not (xs.zipdashleap and SUPER == 3) --Can't bonk when doing Super Sonic's leap.
		
		and not ((abs(s.z-xs.lastz) > (12*s.scale)) --Not when vertical speed is strong
		and not (abs(s.momz) > (12*s.scale)) and not xs.zipdashleap)
			CANBONK = true
		end
	elseif xs.painrecovery and (s.sprite2 == SPR2_CLNG) and p.powers[pw_flashing]
		CANBONK = true
	elseif RB and ((p.speed > 11*s.scale) or xs.zipdash) 
	and ((abs(p.cmd.forwardmove) > 9) or xs.direct > 30 or xs.zipdash)
		CANBONK = 7
	end
	
	if CANBONK != false --Don't just run right into walls, idiot		
		--Wanna do some parkour? Automatically rush up minor sector steps without losing your zipdash! 
		
		if (xs.zipdash or CANBONK == 7) and line
		
			if line.backsector
			and not (s.eflags & MFE_VERTICALFLIP) --Don't care enough to make this work for reverse gravity too.
			and not ((s.eflags & MFE_UNDERWATER) and SUPER != 3) --Don't parkour underwater
			and not (xs.parkourstress > 50) --Too much parkouring in a row!	
			and line.frontsector
				local ssector = s.subsector.sector
				local nextsector = line.frontsector
				local destZ = s.z
				local DIFF = (xs.zipdash) and 150*s.scale or 64*s.scale
				local CHECKFAIL = false
				local SLOPEDIFF = nil
				if nextsector == ssector nextsector = line.backsector end
			
				if nextsector.valid and ssector.valid and (nextsector != ssector) --Okay! Let's check it out!
				and not (RB and abs(s.z-s.floorz) < 100*s.scale) --Feels better.
				and not (s.flags & MF_NOCLIP)
					local STARTDIFF = DIFF	
					p.powers[pw_strong] = $|STR_WALL --Just to be sure.
					
					local EX,EY = P_ClosestPointOnLine(s.x, s.y, line)
					destZ = nextsector.floorheight
				
					if nextsector.f_slope
						destZ = P_GetZAt(nextsector.f_slope, EX, EY) --A slope? ...bitch. Get this height instead.
					end
					DIFF = abs((s.z+s.momz)-destZ) --The difference?
					
					if (DIFF < 100*s.scale)
						if (destZ > s.z) --and (destZ >= ssector.floorheight) --We're going UP, not down!
							if nextsector.special
								local type = GetSecSpecial(nextsector.special, 1)
								if type --Pain/Death sector.	
									if (type <= 5) 
										if not (xs.supersonic or p.powers[pw_invulnerability] or p.powers[pw_flashing])
											if type == 4 --Electric
												CHECKFAIL = (not (p.powers[pw_shield] & SH_PROTECTELECTRIC))				
											elseif type == 2 -- Water
												CHECKFAIL = (not (p.powers[pw_shield] & SH_PROTECTWATER))
											elseif type == 3 --Fire
												CHECKFAIL = (not (p.powers[pw_shield] & SH_PROTECTFIRE))
											else
												CHECKFAIL = true --generic/spike damage.
											end							
										end
									else --if (type > 5) --Death sector! Never run into these, duh.
										CHECKFAIL = true
									end
								end
							end
							if (CHECKFAIL!=true)
							and s.standingslope and (abs(s.standingslope.zdelta) > 40000) --Super steep slope. 
							or (s.momz*P_MobjFlip(s) >= 0) and (p.powers[pw_justlaunched] or (p.panim == PA_SPRING)) --Launched!
								CHECKFAIL = 1
								SLOPEDIFF = DIFF
							end
							if nextsector.damagetype --UDMF pain sectors
								local type = nextsector.damagetype
								if (type >= SD_DEATHPITTILT)
									CHECKFAIL = true
								elseif not (p.powers[pw_invulnerability] or p.powers[pw_flashing] or xs.supersonic)		
									if type == SF_ELECTRIC
										CHECKFAIL = (not (p.powers[pw_shield] & SH_PROTECTELECTRIC))				
									elseif type == SF_WATER
										CHECKFAIL = (not (p.powers[pw_shield] & SH_PROTECTWATER))
									elseif type == SF_FIRE or type == SF_LAVA
										CHECKFAIL = (not (p.powers[pw_shield] & SH_PROTECTFIRE))
									else
										CHECKFAIL = true --generic/spike damage.
									end
								end
							end
							
							if CHECKFAIL == false
								local OLDZ = s.z
								
								s.z = nextsector.floorheight+(9*s.scale) 
								
								if ( (s.flags2 & MF2_TWOD) or twodlevel)
								or (not P_TryMove(s, EX, EY, true)
								and not P_TryMove(s, EX+3*s.scale, EY, true)
								and not P_TryMove(s, EX, EY+3*s.scale, true)
								and not P_TryMove(s, EX, EY-3*s.scale, true)
								and not P_TryMove(s, EX-3*s.scale, EY, true))
									s.z = OLDZ
									--CONS_Printf(p, "Failed trymove, cancel operation")
								else
								
									s.XS_crushimmune = 2
									XSGhostTrail(p, s, xs)
									if nextsector.f_slope
										s.z = destZ
									end
									xs.zipdashx,xs.zipdashy = EX,EY							
									xs.rollcd = 5
									if not xs.zipdashpause
										xs.parkourstress = min($+18, 80)
									end
									if not RB
										if not xs.zipdashpause
											if not s.momz and not P_IsObjectOnGround(s)
												P_SetObjectMomZ(s, -5<<16)
											end
											xs.zipdashminus = min($+20, 35)
										end
										xs.zipdashpause = 6
										RestoreZipDashCam(p, s, xs)								
										if xs.speednoise2 and xs.speednoise2.valid --Only 1 at a time.	
											if (xs.speednoise2.tics > 82) --Too soon!
												P_SetOrigin(xs.speednoise2, s.x, s.y, s.z) return false --
											else
												xs.speednoise2.state = S_NULL
											end
										end					
										xs.speednoise2 = P_SpawnMobjFromMobj(s,0,0,0, MT_THOK)
										xs.speednoise2.flags2 = $|MF2_DONTDRAW
										xs.speednoise2.tics = 88
										S_StartSound(xs.speednoise2, sfx_xssfxd)
									else
										xs.zipdashminus,xs.zipdashpause = 0,0
										xs.zipdash = max($,9)
										s.eflags = $ & ~MFE_JUSTHITFLOOR
										s.z = $+(s.scale*P_MobjFlip(s))
										if (s.momz*P_MobjFlip(s) < s.scale)
											P_SetObjectMomZ(s, FRACUNIT)
										end
									end
									return false --
								end
							end
						end
					elseif (destZ < s.z)
						s.eflags = $ & ~(MFE_JUSTSTEPPEDDOWN|MFE_JUSTHITFLOOR)
					end
				end
				if not RB and (CHECKFAIL!=true)
					local METHOD = 0
					DIFF = (xs.zipdash) and 99*s.scale or 70*s.scale
					if (line.flags & ML_EFFECT4) and not (line.flags & ML_EFFECT5)
					and (line.frontside.midtexture or line.backside.midtexture)
						if not (line.frontsector and (line.frontsector.floorheight > s.z+DIFF) )
						and not (line.backsector and (line.backsector.floorheight > s.z+DIFF) )
						and not ( nextsector.floorheight < s.z-(220*s.scale) )					
							METHOD = 1
						end
					elseif (SLOPEDIFF!=nil) and (SLOPEDIFF < 120*s.scale)
						METHOD = 2
					end
					if (METHOD!=0)
						local OGX,OGY,OGZ = s.x,s.y,s.z
						local ANG = xs.zipdashangle or R_PointToAngle2(s.x, s.y, s.x+s.momx, s.y+s.momy)
						s.XS_crushimmune = 2
						local STEP = max(33*s.scale, p.speed+s.scale)
						for i = 1,2
							s.z = $+(52*s.scale*P_MobjFlip(s))
							if (P_TryMove(s,
								s.x+FixedMul(STEP, cos(ANG)), 
								s.y+FixedMul(STEP, sin(ANG)), true))
								
								P_SetOrigin(s, OGX, OGY, s.z)
								xs.zipdashminus,xs.zipdashpause = min($+20, 35),6
								if METHOD == 2
									if (p.speed < 82*s.scale) and not xs.uwdebuff
										P_InstaThrust(s, s.angle, 84*s.scale)
									end
									s.eflags = $ & ~(MFE_JUSTHITFLOOR|MFE_JUSTSTEPPEDDOWN)
									if not xs.zipdashpause
										xs.parkourstress = min($+18, 80)
									end
									xs.zipdashx,xs.zipdashy = s.x+FixedMul(16*s.scale, cos(ANG)),s.y+FixedMul(16*s.scale, sin(ANG))
									P_SetObjectMomZ(s, -1<<16, true)
								else
									xs.zipdashx,xs.zipdashy = s.x+FixedMul(4*s.scale, cos(ANG)),s.y+FixedMul(4*s.scale, sin(ANG))
									P_SetObjectMomZ(s, -5<<16)
								end
								xs.rollcd = 5
								RestoreZipDashCam(p, s, xs)
								if xs.speednoise2 and xs.speednoise2.valid --Only 1 at a time.	
									if (xs.speednoise2.tics > 82) --Too soon!
										P_SetOrigin(xs.speednoise2, s.x, s.y, s.z) return false --
									else
										xs.speednoise2.state = S_NULL
									end
								end		
								xs.speednoise2 = P_SpawnMobjFromMobj(s,0,0,0,MT_THOK)
								xs.speednoise2.flags2 = $|MF2_DONTDRAW
								xs.speednoise2.tics = 88
								S_StartSound(xs.speednoise2, sfx_xssfxd)
								return false --
							end
							P_SetOrigin(s, s.x, s.y, OGZ) --Incase it fails.
						end
					end
				end
			end
		end
		if line
			if RB
				if xs.zipdash and (xs.zipdash > 3) and xs.direct --Speed up walls when freeflying!
				and not (RB.LockOn and RB.LockOn.valid and (p.cmd.forwardmove < -30))
					if (p.cmd.buttons & BT_JUMP)
					or (s.momz*P_MobjFlip(s) > -20*s.scale) and not RB.truespin
						if (not (s.eflags & MFE_VERTICALFLIP) and (abs((s.z+s.momz)-s.ceilingz) > 96*s.scale))
						or ((s.eflags & MFE_VERTICALFLIP) and (abs((s.z+s.momz)-s.floorz) > 96*s.scale))
							local x,y = P_ClosestPointOnLine(s.x, s.y, line) 
							local TOWALLANG = R_PointToAngle2(s.x, s.y, x, y) --Hold TOWARDS the wall if you want this to work.
							local TRUETURN = abs((XSon.MOMANGLE(s)-xs.turnang)) 
							local ANGLE,PLUS = XS_AngleDifference(XSon.MOMANGLE(s), TOWALLANG, 2)
							if (ANGLE+TRUETURN < ANG10*5) and XS_StartWallRun(p,s,xs,line, 64*s.scale)
								return false --
							end
						end
					end
				end
			end
			if not ((s.flags2 & MF2_TWOD) or twodlevel)
			and not (RB and RB.LockOn and RB.LockOn.valid)
				local ANGDIFF,PLUS = XSon.WallMOMANG(s,line) 
				if (ANGDIFF > ANGLE_45) --Slide across walls a bit.
					local ADD = ANG1*PLUS
					if xs.zipdash and xs.zipdashangle != nil
						xs.zipdashangle = $+ADD
						s.angle = xs.zipdashangle 
					else
						P_InstaThrust(s, XSon.MOMANGLE(s)+(ADD*4), p.speed)
					end
				end
			end					
		end
		if xs.zipdash and xs.zipdashpause return false end
		if CANBONK == 7 return end --	
		
		local ZIPSPEED = ((s.eflags & MFE_UNDERWATER) and SUPER != 3) and 30*s.scale or 99*s.scale
		--The wall impact is HOW strong? Give Sonic some leniency if he's barely scraping it.
		if xs.painrecovery and not xs.zipdash
			if line --Only bonk on lines here, not enemies.
				xs.bonkeddash = 10
				if (s.momz*P_MobjFlip(s) < 7*s.scale)
					xs.leappenalty = true
				end
			end
		else
			--When "bonkimmune", run up the wall instead!
			if s.standingslope and line and xs.zipdash and (abs(s.standingslope.zdelta) > 26500) 
			and xs.zipdashangle != nil and xs.lastz != nil
				local RISEN = (s.eflags & MFE_VERTICALFLIP) and xs.lastz-s.z or s.z-xs.lastz
			--	print("RISEN: "+(RISEN>>16))
				if not (RISEN < 0) and line.v1 and line.v2
					local SLOPEANG = s.standingslope.xydirection
					local LINEANG = XSon.WallAngle(s, line)
				
					local ANGLE,PLUS = XS_AngleDifference(SLOPEANG, LINEANG, 2)
			--		print("Line-to-slope!\x88 SLOPEANG:"+SLOPEANG/ANG1+"\x87 LINEANG:"+LINEANG/ANG1+"\x83 Diff:\x80"+ANGLE/ANG1)
					if (ANGLE < ANGLE_45) --Only if it alligns with the slope right!
						ANGLE,PLUS = XS_AngleDifference(xs.zipdashangle, LINEANG, 2)
						local MAXANG = ANG1*38
						
						--If holding towards the wall, increase maxang.
						local DIRANG = (xs.direct>5) and xs.turnang or R_PointToAngle2(s.x, s.y, s.x+s.momx, s.y+s.momy)
						local ANGLE2,PLUS2 = XS_AngleDifference(xs.turnang, LINEANG, 2)
						
						local MAXANG = ANG1*38+((ANGLE2 < ANG30)  and ANGLE_22h or 1) --Holding towards the wall, player probably wants to attach.
						local TDIFF = XS_AngleDifference(ANGLE, ANGLE+MAXANG, 2)
			--			print("Connecting to slopeline!\x82 ANGLE:"+ANGLE/ANG1+"\x87 MAXANG:"+MAXANG/ANG1+"\x81 xs.bonkimmune: "+xs.bonkimmune)
						if (ANGLE < MAXANG) or xs.bonkimmune
							local POWER = (SUPER == 3) and 51*s.scale or min(51*s.scale, max(abs(s.standingslope.zdelta*50), 18*s.scale))
							s.XS_lastZpower = 29
							s.z = $+(5*s.scale*P_MobjFlip(s))
							if XS_StartWallRun(p,s,xs,line,POWER) return end --
						end
					end
				end
			end			
			if (p.speed < ZIPSPEED/2)
				xs.bonkeddash = $+8 --Head on wall collission. Can't give much leniency there!
				xs.bonkframe = true
			else
				xs.bonkeddash = (p.speed < ZIPSPEED) and $+3 or $+1
			end	
		end
		
		if (xs.bonkeddash > 9) --If bonked-dash exceeds 9, then bonk!
			if xs.zipdashleap
				xs.leappenalty = true
			end
			XSZipDashCancel(p, s, xs)
			if SUPER != 3 --Super hedgehogs have foreheads of steel
				XSon.Bonk(p, s, xs)
				if line and not (m or xs.darkphase) and P_RandomRange(0,30) == 0
					XSVoice(p, s, xs, sfx_xsvoc6) --randomly play a funny voice
				end
			else
				xs.bonkeddash = 0
			end
		end
	end
end, MT_PLAYER)