--╔════════════════════════════╗══════════════════════════════════════════════════════════════════════════════════════════════#
--║   Wallrunning Functions    ║ Most of the wallrunning functions are here. MobjMoveBlocked is in XS-Collide, however!
--╚════════════════════════════╝══════════════════════════════════════════════════════════════════════════════════════════════#
local XSon = XSon
local function print(_) end
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

--START the wallrun! MANUAL forces a wallrun at a set speed.
rawset(_G, "XS_StartWallRun", function(p,s,xs, line, MANUAL)
	if not line return end --Need a line!
	
	--You're holding NOTHING? ...don't attach, then.
	if not (p.cmd.buttons & (BT_SPIN|BT_JUMP|BT_CUSTOM1)) and not (xs.direct > 20) return end --
	
	if (s.eflags & (MFE_GOOWATER|MFE_SPRUNG)) return end --
	
	if P_PlayerInPain(p) or p.powers[pw_carry] or (p.pflags & PF_SLIDING) return end --
	
	--Pressing the right controls in 2D?
	if ((s.flags2 & MF2_TWOD) or twodlevel)
		if (s.momx > 0) and (p.cmd.sidemove < -20) 
		or (s.momx < 0) and (p.cmd.sidemove > 20)
			return --
		end
	end	
	
	--Not a skybox wall?
	if XS_SkyBoxWall(s, line, 0) return end --
					
	--Don't wallrun on the tiniest hills.
	if (XS_AboveOrBelowSector(s, line, 120*s.scale)) != true return end

	local CANASCEND = XSon.WallRunCanAscend(s, s.subsector and s.subsector.sector, line, xs)
--	print("\x88 CANASCEND:\x80"+CANASCEND)
	if CANASCEND == false return end --Is there anywhere TO ascend to?
	local OVERWALLFLOOR = XS_OverWallFloor(s, line)
	
	if not (OVERWALLFLOOR == 0)
	--	print("\x85 Fail 5: \x8e"+OVERWALLFLOOR>>16)
		return --Wall not over floor.
	end 
	
	local SUPER = XS_Super(p)
	
	if (s.eflags & MFE_UNDERWATER) and (SUPER != 3) return end --Only Super Sonic can scale underwater walls!
	local SLIP = ((line.flags & ML_NOCLIMB) and (SUPER < 2)) and 26 or 0
	
	if (SLIP!=0) and xs.walljump return end --You can't walljump between slippery walls!
	
	--Now, determine how strong our wallrunning power will be!
	local LEAPPOWER = 5*s.scale
	if MANUAL --Manually force a set power? Required if you want to wallrun without the zipdash leap!
		LEAPPOWER = MANUAL
	else
		--Need to have JUST leaped!	
		if not (xs.justleaped < 22)	or not xs.zipdashleappower return end --
		
		--Determine the running power we'll get.
		local MOMBONUS = (xs.justleaped < 6) and abs(s.momz) or (xs.justleaped < 13) and abs(s.momz/2) or abs(s.momz/4)
		if (xs.zipdashleappower < 10) --Geez, what a lousy jump...
			MOMBONUS = 0 --No MOMZ from this nonsense.
		end
		
		--Get rewarded for timing your jump close to the wall!
		local PERFECTBONUS = min(10, max(0, 14-(xs.justleaped)) )
		LEAPPOWER = ( (((xs.zipdashleap+xs.zipdashleappower)*2)/3)+PERFECTBONUS )*s.scale + MOMBONUS
		
		--If you did a poor jump with great timing, grant a minimum power treshold!
		if (LEAPPOWER < 16*s.scale) and (xs.justleaped < 8)
			LEAPPOWER = (xs.justleaped < 4) and 16*s.scale or max($, 12*s.scale)
		elseif (LEAPPOWER > 36*s.scale)
			LEAPPOWER = max(36*s.scale, (xs.justleaped < 4) and $*5/6 or $*4/5) --On the other hand ,make it a bit harder to cross these seperate tresholds.
		elseif (LEAPPOWER > 26*s.scale)
			LEAPPOWER = max(26*s.scale, $*5/6)
		elseif (LEAPPOWER > 21*s.scale)
			LEAPPOWER = max(21*s.scale, $*8/9)
		end
	end
	
	LEAPPOWER = min($, 43*s.scale) --Absolute cap!
	
	--Powerup buffs?
	if SUPER == 3
		LEAPPOWER = max((MANUAL) or 1, min(50*s.scale, max(35*s.scale, $+10*s.scale)) )
		p.pflags = $|PF_CANCARRY
		if xs.airzipWR 
			xs.airzipWR = 0
			xs.jump,xs.custom1 = 99<<16,99<<16
		end
	elseif p.powers[pw_sneakers]
		LEAPPOWER = max(31*s.scale, $+8*s.scale) --Minor boost for runshoes too!
	end	
	
	--Calculate line angles...
--	local x,y = P_ClosestPointOnLine(s.x, s.y, line)
	local TOWALLANG =  XSon.WallAngle(s, line) --R_PointToAngle2(s.x, s.y, x, y)
	local LINEANGLE = R_PointToAngle2(line.v1.x, line.v1.y, line.v2.x, line.v2.y)
	local PREVFA = (p.xswallrun and p.xswallrun.faceangle != nil) and p.xswallrun.faceangle
	if not p.xswallrun
		local SFX = P_SpawnMobjFromMobj(s,0,0,0,MT_THOK)
		SFX.flags2 = MF2_DONTDRAW SFX.tics = 33 SFX.fuse = 33
		S_StartSound(SFX, sfx_s236) S_StartSound(s, sfx_s236)
		S_StopSoundByID(s, sfx_xssfxo)
	end
	
	--Good, now set up the wallrun table!
	p.xswallrun = {
		inittime = 1, --Camera-focused init tracker.
		INITANG = p.cmd.angleturn<<16,
		INITANG2 = R_PointToAngle2(s.x, s.y, s.x+s.momx, s.y+s.momy),
		startpower = LEAPPOWER, --Power we started with. Used for calculations.
		power = LEAPPOWER, --How long you can run for.
		start = 1, --How long you've been wallrunning for. For momentum calculations.
		countdown = 4, --How long before you need a wallcheck refresher to continue
		direction = (s.eflags & MFE_VERTICALFLIP) and -1 or 1, --Starting direction.
		jump = 99<<16,
		spin = 99<<16,
		custom1 = (not (xs.custom1 and xs.custom1 < 6 and not xs.FreeFlight)) and FRACUNIT*99 or 0, --Maybe allow a buffer?
		faceangle = TOWALLANG,
		line = line,
		lineangle = LINEANGLE, --The line's angle itself. Used in MobjMoveBlocked.
		swappedline = 3,
		Zmult = s.XS_lastZpower or 0,
		slip = SLIP,
		walljumped = (xs.walljump or xs.walljumpP) and true or false,
		angupdate = 999
	}
	local WR = p.xswallrun
	
	--Try to remember if we're running on a FOF!
	if s.subsector and s.subsector.sector
		local secT = s.subsector.sector
		local lineside = ((secT != line.backsector) and line.backsector) and line.backside or line.frontside --lineside facing player.
		local secL = lineside.sector
		if XSon.FOFheightcheck(secL, s, line) --XS_FOFcheck(s, secL)
			WR.fofline = line --If we're running on a FOF or polyobject, remember that!
			WR.faceangle = XSon.WallAngle(s, WR.fofline) print("\x83 Use FOFline angle")
		end
	end	
		
	XSZipDashCancel(p, s, xs) --Call this AFTER setting up the table.
	XSSnapDiveReset(p, s, xs, true)
	S_StopSoundByID(s, sfx_xssfxo)
	XS_RingBurstSparkles(s)
	
	if PREVFA == nil
	--	XSon.CamToDest(s, s.angle, TOWALLANG, ANG10)
		p.drawangle = TOWALLANG
	end
	s.state = S_PLAY_XWALLRUN
	xs.leapaftermath = 0
	xs.justleaped = 0
	xs.zipdashleappower = 8
	xs.bonkeddash = 0
	xs.rollcd = 2
	xs.bonkimmune = 2
	xs.custom2,xs.custom3 = 999<<16,999<<16
	s.XS_lastZpower = 29 --Make sure to put this AFTER the calculations were set...
	
	local ZFLIP = P_MobjFlip(s)
	if P_IsObjectOnGround(s) s.z = $+(99*ZFLIP) xs.custom1,xs.jump = $+9,$+9 end --Don't be on the floor!
	s.eflags = $ & ~(MFE_JUSTHITFLOOR)
	p.pflags = $|PF_USEDOWN|PF_JUMPDOWN & ~(PF_SPINNING|PF_JUMPED|PF_THOKKED|PF_NOJUMPDAMAGE|PF_SLIDING)
	s.momz = (ZFLIP==-1) and max($, -19*s.scale) or min($, 19*s.scale) 
	s.XS_crushimmune = 2
	
	if (LEAPPOWER/3 > abs(s.momz))
		s.momz = abs(LEAPPOWER/3)*ZFLIP
	end	
	if SLIP and (s.momz*ZFLIP > 5*s.scale)
		s.momz = 5*s.scale*ZFLIP
	end
	--Snap any remaining distance! To a certain extend, anyway.
	local STEP = 15*s.scale --22
	for i = 1,2 --10
		if not P_TryMove(s, s.x+FixedMul(STEP, cos(TOWALLANG)), s.y+FixedMul(STEP, sin(TOWALLANG)), true) 
			STEP = $*2/3
		end
	end 
	local ghs = XS_CircleWave(s, 1, SKINCOLOR_ORANGE, 17, FRACUNIT-16000)
	ghs.frame = ($ & ~FF_TRANSMASK)|FF_TRANS80		
	if (LEAPPOWER > 16*s.scale) and not xs.walljumpP
		s.spritexscale = FRACUNIT-11000
		s.spriteyscale = FRACUNIT+40000
		s.XSstretch = {timer = 7, Xspeed = 2300, Yspeed = 6000, air = true}	
			
		if (LEAPPOWER > 21*s.scale) --Initial trail?
			XSGhostTrail(p, s, xs)
			XSZipTrail(s, nil, 1, FF_TRANS40, 18, nil, FRACUNIT+35000)
		end
	end
	xs.painrecovery = 0
	xs.walljump,xs.walljumpP,xs.forceangle = nil
	P_InstaThrust(s, TOWALLANG, 16*s.scale)
	
	if p.xboomtable --Fade Sonic Booms quicker.
		for _,ghs in pairs(p.xboomtable)
			if ghs and ghs.valid ghs.fuse = min($,11) end
		end
	end
	p.lastlinehit,p.lastsidehit = -1,-1
	XSon.WallRunning(p,s,xs, p.xswallrun) return (LEAPPOWER) or 1 --Success!
end)

--Adjust player's camera angle to the destination angle by a certain max amount. Everything but "s" is optional.
XSon.CamToDest = function(s, ANG, DEST, MAX, SKIP)
	if (ANG==nil) ANG=s.angle end if (DEST==nil) DEST=p.drawangle end if (MAX==nil) MAX=ANG2 end
	local ANGLE,PLUS = XS_AngleDifference(ANG, DEST, 2, ANGLE_90)
	if (ANGLE >= MAX)
		if SKIP != "skip"
			s.angle = $-(MAX*PLUS)
			if s.player
				s.player.cmd.angleturn = $-((MAX*PLUS)>>16)
			end
		end
		return ANGLE,PLUS --Return the amount we've adjusted
	else --Snap directly.
		if SKIP != "skip" 
			s.angle = DEST
			if s.player
				s.player.cmd.angleturn = DEST>>16
			end
		end
		return 0,1
	end
end

--Returns Angle differences between connected 2 lines. LV is the vertex connecting them both.
XSon.LineDiffAng = function(s, line, line2, LV)
	local L, LINEANG,LINEANG2 = line,0,0
	for i = 1,2 if i == 2 L = line2 end
		local FLIP = P_PointOnLineSide(s.x, s.y, L)
		local LINEANG = R_PointToAngle2(L.v1.x, L.v1.y, L.v2.x, L.v2.y)+((FLIP==1) and -ANGLE_90 or ANGLE_90)		
	end
	local ANGDIFF,PLUS = XS_AngleDifference(LINEANG, LINEANG2, 2)
	ANGDIFF,LINEANG,LINEANG2 = $/ANG1,$/ANG1,$/ANG1
	return ANGDIFF --
end

--Determine the real angle towards the wall.
XSon.WallAngle = function(s, line)
	local LINEANG = R_PointToAngle2(line.v1.x, line.v1.y, line.v2.x, line.v2.y) 
	local FLIP = P_PointOnLineSide(s.x, s.y, line)
	return LINEANG + ((FLIP==1) and -ANGLE_90 or ANGLE_90)
end

--Is there anything in front of us? Input coordinates as a table if needed.
XSon.CheckFront = function(p,s,xs, WR, DIST, ANG, OGXY)
	if not (WR and WR.countdown) return end --
	local OGZ = s.z
	if OGXY == nil OGXY = {x = s.x, y = s.y} end
	if ANG == nil ANG = WR.faceangle end
	if DIST == nil DIST = 50*s.scale end
	local MOVED = false
	local X,Y = s.x,s.y 
	DIST = $*4
	for i = 4,4
		X = $+ FixedMul(DIST, cos(ANG))
		Y = $+ FixedMul(DIST, sin(ANG))
		
		if not (P_TryMove(s, X, Y, true)) break end --We got blocked, so we're good.
		MOVED = true
		if i == 1
			WR.countdown = min($, 3)
		else
			WR.countdown = max($-2,0) if WR.countdown == 0 break end --
		end
	end
--	if MOVED == true
		P_SetOrigin(s, OGXY.x, OGXY.y, OGZ) --Put Sonic back to where he was.
--	end
end

--Do wall running animation + effects?
rawset(_G, "XS_WallRunAnim", function(p,s,xs,WR)

	xs.rollcd = 6
	xs.painrecovery = 0
	if not (s.eflags & MFE_SPRUNG) and not P_PlayerInPain(p) and not p.powers[pw_carry]
		if (XS_Super(p)) == 3 --Wallrun animation!
			s.state = S_PLAY_SPRING
			s.frame = ($ & ~FF_FRAMEMASK)|((leveltime%2) and B or A)
		else
			if (s.state != S_PLAY_XWALLRUN) s.state = S_PLAY_XWALLRUN end
			if s.tics != 1 s.tics = 1 end
		end
		p.panim = PA_ABILITY
	end
	p.XSscalecam = 4 --Adjust camera.
	p.camerascale = min((2<<16)+26000, (FRACUNIT+30000)+WR.start*1800)
	
	--Adjust angle.
/*	if s.angle != WR.faceangle
		if (p.cmd.forwardmove <= 0) and (abs(p.cmd.sidemove) < 3)
			XSon.CamToDest(s, s.angle, WR.faceangle, ANG10/8) --Auto-adjust cam gradually when not holding anything.
			CONS_Printf(p, "ANGLE:"+ANGLE/ANG1+" and PLUS"+PLUS)
			p.cmd.buttons = $ & ~(BT_CAMLEFT|BT_CAMRIGHT)
		elseif (p.cmd.forwardmove > 24) and (abs(p.cmd.sidemove) < p.cmd.forwardmove)
			local ANGLE,PLUS = XSon.CamToDest(s, s.angle, WR.faceangle, (p.cmd.forwardmove > 43) and ANG1/12 or ANG1/25)
			CONS_Printf(p, "\x83"+"ANGLE:"+ANGLE/ANG1+" PLUS"+PLUS) 
			p.cmd.buttons = $ & ~(BT_CAMLEFT|BT_CAMRIGHT) 
		end
	end  */
	
	p.drawangle = WR.faceangle

	--Flashy blue trail and effects.
	if (s.momz*P_MobjFlip(s) > 7*s.scale) and not WR.slip
		XSon.zwind(s)
		if (abs(s.momz) > 23*s.scale)
			XSSpawnDust(s, nil, nil, true)
			if (WR.start < 9)
				XSGhostTrail(p, s, xs, nil, 10, FF_TRANS30, FRACUNIT+15000)
			else
				XSZipTrail(s, nil, 1, (xs.supersonic) and FF_TRANS90 or FF_TRANS60, 18, nil, FRACUNIT+15000)
			end
		end	
		if (leveltime % 2 == 0) and (WR.countdown >= 3)
		
			XS_MediumDust(s, SKINCOLOR_BEIGE, nil, nil, XSLocalRNG(1, 16, s)*s.scale)
			if (leveltime % 4 == 0)  and (WR.start < 20)
				local ghs = XS_CircleWave(s, 2, nil, XSLocalRNG(9,17,s), FRACUNIT-XSLocalRNG(0,35000,s))
				ghs.sprite2 = SPR_XSG2
				ghs.renderflags = $|RF_ABSOLUTEOFFSETS
				ghs.spritexoffset = 38<<16
				ghs.spriteyoffset = 41<<16
				ghs.frame = P|FF_TRANS40
			end
		end
		
	else --Sweating a bit...
	
		if (WR.countdown >= 3)			
			if (leveltime % 2 == 0)
				local ANG = WR.faceangle if ANG == nil ANG = s.angle end
				local SIDE = p.cmd.sidemove or 0
				local XYRNG = XSLocalRNG(15,18,s)*s.scale
				for i = -1,1,2
					local ghs = P_SpawnMobjFromMobj(s,
					FixedMul(SIDE*(s.scale/2), cos(ANG+ANGLE_90)) + FixedMul(XYRNG*i, cos(ANG+ANG60)),
					FixedMul(SIDE*(s.scale/2), sin(ANG+ANGLE_90)) + FixedMul(XYRNG*i, sin(ANG+ANG60)), 
					0, MT_SPINDUST)
					ghs.frame = ((i==-1) and (XSLocalRNG(4,2,ghs)-2) or XSLocalRNG(0,2,ghs))|FF_TRANS20
					P_SetObjectMomZ(ghs, -3<<16, true)
					if (abs(SIDE) > 5)
						local DIST = (SIDE*s.scale)/4
						P_InstaThrust(ghs, ANG+ANGLE_90, -DIST) --Move the dust too.
					end
				end	
				S_StartSound(s, (leveltime%4==0) and sfx_s3kbcs or sfx_s3k7e)
			end
		else
			S_StopSoundByID(s, sfx_s3k7e)
			S_StopSoundByID(s, sfx_s3kbcs)
		end
		local NUM = 1
		if WR.slip
			NUM = 2
			if (WR.slip % 5 == 0)
				S_StopSoundByID(s, sfx_xssfe8)
				S_StartSound(s, sfx_xssfe8)
			end
		end
		local rng = P_RandomRange(-30,30) or 1
		for i = 1,NUM --Sweatdrops gfx.
			if i==2 rng = -$ end
			local ghs = P_SpawnMobjFromMobj(s, s.momx+(rng*s.scale), s.momy+(rng*s.scale), s.momz+s.height, MT_XSAUTOFRAME)
			ghs.sprite = SPR_DRIP
			ghs.frame = D|FF_TRANS20
			ghs.rollangle = s.rollangle+(rng*ANG2) 
			ghs.scale = ($*15)/(30+abs(rng))
			ghs.destscale = 99<<16
			ghs.colorized = true
			ghs.color = SKINCOLOR_SKY
			ghs.tics,ghs.fuse = 16,16
			ghs.sttfade = true
			P_InstaThrust(ghs, XSLocalRNG(1,360, (i==2) and s or ghs)*ANG1, 16*s.scale)
			P_SetObjectMomZ(ghs, abs(s.momz/2)+6<<16, true)
		end
	end return true --
end)

--The main wallrunning code!
--Starts and is upheld by the MobjMoveBlocked hook, then continues here.
XSon.WallRunning = function(p,s,xs, WR)

	local ZFLIP = P_MobjFlip(s)
	
	xs.falltransition = 0
	xs.framejump = 0
	xs.ringburstcd = 12
	xs.ringburstcharge = 0
	xs.spin,xs.jump = 999<<16,999<<16
	xs.justleaped = 0
	xs.ledgeprotectcd = 8
	
	if xs.FreeFlight 
		s.flags = $ & ~MF_NOGRAVITY --Will re-apply on its own.
		xs.FreeFlight.forceanim = nil 
		if (p.cmd.buttons & BT_CUSTOM1)
			xs.FreeFlight.WallTransfer = 3
		end
	end
	if not (WR) --Big failsafe.
		if not xs.FreeFlight
			s.flags = $ & ~MF_NOGRAVITY
		end
		if (s.state == S_PLAY_XWALLRUN)
			s.state = (P_IsObjectOnGround(s)) and S_PLAY_SPRING or S_PLAY_XDIVEFLIP1
		end
		s.eflags = $ & ~(MFE_JUSTHITFLOOR|MFE_APPLYPMOMZ)
		p.xswallrun = nil return --
	else
		local NILSEC = R_PointInSubsectorOrNil(s.x, s.y) --Did we go through a teleporter?
		if ((XSon.Teleport(p,s,xs, WR.faceangle)) > 460*s.scale)
			xs.lastx,xs.lasty,xs.lastz,xs.lastmomz = s.x, s.y, s.z, 0
			WR.INITANG,WR.endangle,WR.faceang = s.angle,s.angle,s.angle
			WR.start,WR.countdown,WR.inittime,WR.camadd = 150,0,150,1
			WR.teleported = true
			xs.rollcd = 30
			if p.powers[pw_nocontrol]
			or s.momx == 0 and s.momy == 0 and s.momz == 0
			or s.state == S_PLAY_STND and (s.tics > 102)
				if not (s.eflags & MFE_SPRUNG)
					local FLOORZ = (ZFLIP==-1) and s.ceilingz or s.floorz
					if (abs((s.z+s.momz)-FLOORZ) < 32*s.scale)
						s.z = FLOORZ
					end
					s.momz = -ZFLIP	
				end
			end
		end
		WR.ogX, WR.ogY = s.x,s.y
	end

	--Cancel conditions.
	if not (p.cmd.buttons & BT_JUMP) and not (p.cmd.buttons & BT_CUSTOM1) and not (p.cmd.buttons & BT_SPIN) and not xs.direct 
	or (WR.countdown <= 0) --Couldn't find a valid wall for long enough!
	or (WR.start > 100) --Ran for too long
	or P_IsObjectOnGround(s)
	or WR.line == nil and not (WR.tree and WR.tree.valid)
	or (s.eflags & (MFE_GOOWATER|MFE_SPRUNG))
	or ((s.eflags & MFE_VERTICALFLIP) and (s.z+s.momz-(49*s.scale) <= s.floorz))
	or (not (s.eflags & MFE_VERTICALFLIP) and (s.z+s.height+s.momz >= s.ceilingz))
	or not s.health
	or p.powers[pw_carry]
	or P_PlayerInPain(p) --Ow.	
	--	if p.WRdebug and not (p.WRdebug == p.WRdebugprev)
	--		p.WRdebugprev = p.WRdebug
	--	end	
	--	print("\x85 cancel")
	
		p.viewrollangle,p.aiming,s.rollangle = 0,0,0
		RestoreZipDashCam(p, s, xs)
		if p.XSscalecam p.XSscalecam = 1 end
		
		if (abs(s.z-xs.lastz) > 260*s.scale) and (WR.teleported != true)
			s.z = xs.lastz --Wha?! Way too far!
		end
		local TMOMZ = s.momz*ZFLIP
		if P_IsObjectOnGround(s) and (TMOMZ <= 0) or WR.teleported == true
			s.state = (p.speed) and S_PLAY_RUN or S_PLAY_STND
			xs.rollcd = 30
			xs.lastmomz = 0
			s.eflags = $ & ~MFE_JUSTHITFLOOR
			s.momx,s.momy = 0,0
			p.powers[pw_nocontrol] = max($,3)			
		else
			if (TMOMZ > 2)
				local FCAP = (xs.supersonic) and 53 or 41
				if (TMOMZ > FCAP*s.scale) --Maximum fling cap.
					s.momz = FCAP*s.scale*ZFLIP
				elseif (TMOMZ < 3*s.scale) --Only lower it to a minimum!
					s.momz = 3*s.scale*ZFLIP			
				end
				local HALF = (WR.leapboost) and 6 or 11
				local WINIT = WR and WR.inittime or 0
				if (WINIT > 12) and (TMOMZ < 13*s.scale) and (HALF>6)
					HALF = (TMOMZ > 8*s.scale) and $-1 or $-2
				elseif (WINIT < 4) 
					HALF = $*4/3 
				end
				s.momz = FixedDiv(FixedMul($, 5), HALF) --Halved.
			end
			local ANG = WR and WR.faceang if ANG == nil ANG = s.angle end
			for i = 1,2
				P_TryMove(s, s.x+FixedMul(-s.scale*8, cos(ANG)), s.y+FixedMul(-s.scale*8, sin(ANG)), true)
			end
			local DISLODGESPEED = min(10*s.scale, max(s.scale, abs(s.momz/3)) )
			if xs.FreeFlight
				if not (p.cmd.buttons & BT_CUSTOM1) or not xs.direct			
					xs.FreeFlight.WallTransfer = nil
				elseif WR
					local OGX,OGY,OGZ = WR.ogX,WR.ogY,s.z
					if not P_TryMove(s, s.x+FixedMul(300*s.scale, cos(xs.turnang)), s.y+FixedMul(300*s.scale, sin(xs.turnang)), true)
						xs.FreeFlight.WallTransfer = nil
						P_SetObjectMomZ(s, -7<<16)
						xs.FreeFlight.noXYcontrol = 12
						xs.FreeFlight.noZcontrol = 9
					else
						xs.FreeFlight.WallTransfer = 3
					end
					P_SetOrigin(s, OGX,OGY,OGZ)
				end
				if not xs.FreeFlight.WallTransfer
					XSon.Sound(s, sfx_xssfxk, 210)
					s.state = S_PLAY_XDIVEFLIP1
				end
			end
			P_InstaThrust(s, (WR==nil or WR.faceangle==nil) and s.angle or WR.faceangle, -DISLODGESPEED)
			if not (WR and WR.countdown)
				s.state = (s.momz*ZFLIP > s.scale) and S_PLAY_SPRING or S_PLAY_FALL
			elseif (s.state == S_PLAY_XWALLRUN)
				s.state = S_PLAY_XDIVEFLIP1
				S_StartSound(s, sfx_xssfxk)
			end
			if WR and (WR.teleported != true)
				if ((WR.camadd or 0) > 4) or WR.walljumped or WR.stepunlocked==true
					if WR and WR.endangle != nil
						p.drawangle = WR.endangle
						XSon.CamToDest(s, s.angle, WR.endangle, ANG20)
					end	
				elseif ((WR.inittime or 0) < 12)
					local SETANG = WR.INITANG
					if SETANG != nil and not ((s.flags2&MF2_TWOD) or twodlevel)
						local ANGDIFF = XS_AngleDifference(SETANG, p.drawangle, 2)
						if (ANGDIFF > ANG60) --failsafe against camera jank.
							p.drawangle = SETANG
							XSon.CamToDest(s, s.angle, SETANG, ANGLE_45)
						end
					end	
				end
			end
		end
		
		if (WR.teleported != true)
			XSSpawnJumpDust(s)
			XS_QuickDust(s)
			XSon.SwapStretch(p, s, xs)
			s.spriteyoffset = -6<<16
			
			if (p.powers[pw_sneakers] or xs.darkphase)
				if not xs.supersonic and (p.panim == PA_SPRING)
				and (s.momz*ZFLIP > 10*s.scale) 
				and (WR==nil or (WR.startpower > 20*s.scale)) 
					XSon.GibSound(s, sfx_xssfe4, 180)
					XSon.Sound(s, sfx_s1b8, 150)
					xs.ziptrailticket = 12
					xs.Ztrail = 12
					xs.snapdivetrick = 11
					
					xs.WallTrick = 24
					s.spriteyoffset = -4<<16
					s.spritexscale,s.spriteyscale = FRACUNIT-20000,FRACUNIT+25000
					s.XSstretch = {timer = 9, Xspeed = 2000, Yspeed = 2500}		
				end
			else
				
				if (abs(s.momz) > 9*s.scale)
					s.spriteyoffset = -6<<16
					s.spritexscale,s.spriteyscale = FRACUNIT-15000,FRACUNIT+21000
				else
					s.spriteyoffset = 14<<16
					s.spritexscale,s.spriteyscale = FRACUNIT+11000,FRACUNIT-15600
				end
				s.XSstretch = {timer = 5, Xspeed = 2000, Yspeed = 3200}	
			end
			p.cmd.sidemove,p.cmd.forwardmove = 0,0
			p.cmd.buttons = $ & ~(BT_CAMLEFT|BT_CAMRIGHT)
			p.pflags = $|PF_JUMPDOWN|PF_USEDOWN
			xs.jump = 99<<16
			s.eflags = $ & ~(MFE_JUSTHITFLOOR|MFE_APPLYPMOMZ)
			p.powers[pw_justlaunched] = 2
			if p.cmd.forwardmove or p.cmd.sidemove
				p.cmd.sidemove,p.cmd.forwardmove = 0,0
				if not xs.stopstick 
					xs.stopstick = 1
				end
			end			
		end	
		p.xswallrun = nil return false --
	end
	
	local SUPER = XS_Super(p)
	
	--Start wallrunning behaviour.	
	p.pflags = $|PF_JUMPDOWN|PF_USEDOWN & ~(PF_JUMPED|PF_SPINNING|PF_THOKKED|PF_STARTDASH|PF_NOJUMPDAMAGE)	
	WR.jump = (p.cmd.buttons & BT_JUMP) and $+1 or 0
	WR.spin = (p.cmd.buttons & BT_SPIN) and $+1 or 0 --Controls...
	WR.custom1 = (p.cmd.buttons & BT_CUSTOM1) and $+1 or 0
	s.XSLastWall = WR.line
	p.normalspeed = 0
	s.eflags = $ & ~(MFE_JUSTHITFLOOR|MFE_APPLYPMOMZ)
	p.powers[pw_pushing] = 3
	p.powers[pw_justlaunched] = 0
	p.powers[pw_ignorelatch] = 32770
	if not (WR.tree)
		p.lastlinehit,p.lastsidehit = -1,-1
		if p.blocked == true
			p.blocked = 0
		elseif p.blocked == 1
			p.blocked = 0
		end
	end
	
	--Jump off?
	if not xs.FreeFlight and (WR.jump == 1 or WR.spin == 1 or WR.custom1 == 1)
	or xs.FreeFlight and (xs.FreeFlight.truespin == 1 or WR.jump == 1)	
	
		if (s.momz*ZFLIP > 48*s.scale) --Maximum fling cap.
			s.momz = 48*s.scale*ZFLIP
		end		
		p.aiming = 0
		s.rollangle = 0
		local MOMZ = s.momz*ZFLIP
		P_DoJump(p, true)
		s.momz = FixedDiv(FixedMul(MOMZ, 10), 23)
		p.pflags = $|PF_JUMPED
		S_StartSound(s, sfx_xssfx7)	
		p.cmd.buttons = $|BT_JUMP
		p.pflags = $|PF_JUMPED|PF_JUMPDOWN & ~PF_STARTJUMP
		
		--Walljump / Perfect Walljump
		if WR.jump == 1 or ((p.cmd.buttons & BT_CUSTOM1) and WR.spin != 1) and not xs.FreeFlight
		
			s.angle = WR.faceangle+ANGLE_180 --walljump 180 turnaround
			p.drawangle = s.angle
			xs.ziptrailticket = 1
			s.state = S_PLAY_XZIPLEAP
			s.tics = 35 s.anim_duration = 35
			xs.ringburstcd = 9
			xs.walljump = 16
			
			if p.xboomtable
				for _,ghs in pairs(p.xboomtable)
					if ghs and ghs.valid ghs.fuse = min($,3) end
				end
			end
			if (xs.speednoise and xs.speednoise.valid) xs.speednoise.state = S_NULL end			
			if (xs.speednoise2 and xs.speednoise2.valid) xs.speednoise2.state = S_NULL end				
			
			if (WR.start < 7) and (WR.startpower > 11*s.scale) and not WR.slip --Quickly jump off for a SUPER walljump!
			or (xs.FreeFlight and (p.cmd.buttons & BT_CUSTOM1) and not xs.FreeFlight.truespin) --Super Sonic can do it automatically
			--	XSon.Sound(s, sfx_xssfxd, 126)
				local JUMPANG,TMOMZ = WR.faceangle, s.momz*ZFLIP
				if not (xs.FreeFlight and (WR.inittime > 9)) and WR.INITANG2 != nil and WR.INITANG2 != JUMPANG
					local MAX,PLUS = XSon.CamToDest(s, JUMPANG, WR.INITANG2, 2, ANGLE_90, "skip")
					if (MAX > ANG20)  --Diminishing returns!
						MAX = max(ANG20, MAX/3)
					elseif (MAX > ANG1*7)
						MAX = max(ANG1*7, MAX/2)
					end
					JUMPANG = $+(MAX*PLUS)
				end
			
				xs.walljump = 32-(SUPER*2)
				xs.walljumpP = (WR.power*ZFLIP >= TMOMZ) and WR.power*ZFLIP or TMOMZ
				if (TMOMZ > 30*s.scale)
					s.momz = 30*s.scale*ZFLIP
				elseif (TMOMZ < (2*s.scale)+45000)
					P_SetObjectMomZ(s, (2<<16)+45000) --Pity jump height
				end
				xs.jump = 999<<16
				XS_RingBurstStripes(s, nil, AST_ADD, 11<<16, s)
				P_InstaThrust(s, JUMPANG, (abs(s.momz) > 9*s.scale) and -87*s.scale or -70*s.scale)
				XSon.Sound(s, sfx_xssfpo)
				S_StartSound(s, sfx_s251)
				if SUPER --Super Saiyan walljump multipliers
					s.momx = FixedDiv(FixedMul($, 4+SUPER), 4)
					s.momy = FixedDiv(FixedMul($, 4+SUPER), 4)
					s.momz = FixedDiv(FixedMul($, 7+SUPER), 7)
				end
				
			else --Regular Walljump
				
				xs.leapaftermath = 23
				xs.walljumpP = (WR.power >= s.momz*ZFLIP) and WR.power or s.momz*ZFLIP
				S_StartSound(s, sfx_jump)
				S_StartSoundAtVolume(s, sfx_s251, 130)
				P_InstaThrust(s, WR.faceangle, (abs(s.momz) > 9*s.scale) and -60*s.scale or -44*s.scale)					
				if SUPER --Super Saiyan walljump multiplier.
					s.momx = FixedDiv(FixedMul($, 6+SUPER), 6)
					s.momy = FixedDiv(FixedMul($, 6+SUPER), 6)
					s.momz = FixedDiv(FixedMul($, 8+SUPER), 8)
				else
					XSon.StopShout(s)
					local GRUNT = sfx_xsvock+P_RandomRange(0, 3)
					if GRUNT == s.XSlastkickgrunt
						GRUNT = ($==(sfx_xsvock+3)) and sfx_xsvock or $+1
					end
					XSVoice(p, s, xs, GRUNT, true, nil, 180)
					s.XSlastkickgrunt = GRUNT	
					if WR.slip
						s.momx,s.momy = $*2/3,$*2/3
					end
				end	
				if (s.momz*ZFLIP < (5*s.scale)+45000) --Pity jump height
					P_SetObjectMomZ(s, 5<<16+45000)
				end					
			end				
			--Cap and diminish!
			if xs.walljumpP != nil
				if (xs.walljumpP*ZFLIP < s.scale)
					xs.walljumpP = nil --Oh well, no more power then...			
				else
					xs.walljumpP = abs(xs.walljumpP)
					if SUPER != 3 and (xs.walljumpP > 6*s.scale)
						if (xs.walljump < 20) --Lose some speed from regular jumps.
							xs.walljumpP = FixedDiv(FixedMul($, 7),9)
						end
					end
				end
			end
			if not xs.spinstretch
				XSon.SwapStretch(p, s, xs)
				xs.spinstretch = 12
				s.spriteyscale = FRACUNIT+28000
				s.spritexscale = FRACUNIT-28000
				xs.alteredoffset = true
				s.spriteyoffset = -14<<16
			end
			if xs.FreeFlight --Super Saiyan flying walljump!
				local yu = xs.FreeFlight
				if (p.cmd.buttons & BT_CUSTOM1)
					xs.walljumpP,xs.walljump = nil
					yu.noXYcontrol = 4 yu.noZcontrol = 5
					yu.WallTransfer = 3
					yu.custom1 = 1
					XSon.TryAirZipDash(p,s,xs)
				else
					yu.noZcontrol = 20
					yu.noXYcontrol = 3
				end
			end		
			if not xs.forceangle
				xs.forceangle = {angle = XSon.MOMANGLE(s), timer = 5, nosangle = true}
			end
			
		else --Regular dislodge!
		
			local DISLODGESPEED = min(25*s.scale, max(abs(s.momz*2), 8*s.scale))					
			if xs.FreeFlight
				local yu = xs.FreeFlight
				if xs.zipdash XSZipDashCancel(p, s, xs) xs.zipdash = 0 xs.zipdashslide = 0 xs.zipcontinue = nil end
				yu.custom1 = 99<<16
				yu.WallTransfer,yu.forceanim = nil
				yu.noXYcontrol = 9
				yu.noZcontrol = 12
				DISLODGESPEED = $*2
			elseif not xs.uwdebuff
				local TMOMZ = s.momz*ZFLIP
				if XS_SHIELD(p, SH_WHIRLWIND) and SUPER != 3
					local GRAV = max(s.scale, abs(P_GetMobjGravity(s)) )
					if (TMOMZ < 17*GRAV)
						P_SetObjectMomZ(s, 20*GRAV)
					else
						P_SetObjectMomZ(s, 3*GRAV,(TMOMZ>0) and true or false)
					end
					local ghs = XSon.TransformGFX(s)
					ghs.frame = ($&~FF_TRANSMASK)|FF_TRANS70
					ghs.fuse = $-6
					S_StartSound(s, sfx_wdjump)
				elseif (TMOMZ > -3*s.scale)
					P_SetObjectMomZ(s, abs(P_GetMobjGravity(s))*((TMOMZ>20*s.scale) and 1 or (TMOMZ>14*s.scale) and 2 or 4), true)				
				end			
			end
			P_InstaThrust(s, WR.faceangle, -DISLODGESPEED)
			S_StartSound(s, sfx_xssfxk)
			s.state = S_PLAY_XDIVEFLIP1
			XSon.SwapStretch(p, s, xs)
			s.spritexscale,s.spriteyscale = FRACUNIT+15000,FRACUNIT-13000
			s.spriteyoffset = 8<<16
			s.XSstretch = {timer = 6, Xspeed = 3000, Yspeed = 2900, air = true}	
		end

		RestoreZipDashCam(p, s, xs)
		xs.spin = 999<<16
		
		local ghs = XS_CircleWave(s, 1, SKINCOLOR_ORANGE, 17, FRACUNIT-16000)
		ghs.frame = ($ & ~FF_TRANSMASK)|FF_TRANS80
		
		XSSpawnJumpDust(s)
		XS_QuickDust(s)	
		XSon.sparkles(p, s, xs, 65, 10, 11)
		XSGhostTrail(p, s, xs, nil, 11, FF_TRANS40, FRACUNIT+40000)
		
		p.xswallrun = nil
		return false --We're done then!
	end
	if (s.eflags & MFE_UNDERWATER) and SUPER != 3 and (abs(WR.power) > s.scale*4)
		WR.power = FixedDiv(FixedMul($, 12),13)
	end
	
	--Gimmick reference running up the pinetree?
	if WR.tree
		local m = WR.tree
		WR.countdown = $-1
		s.momx,s.momy = 0,0
		if m.valid and (s.z+s.momz > m.z+m.height) --Snap to the treetop!
			P_SpawnGhostMobj(s)
			P_SetOrigin(s, m.x+FixedMul(5*m.scale, cos(WR.faceangle)), m.y+FixedMul(5*m.scale, sin(WR.faceangle)), m.z+m.height)
			s.momz = 0
			p.XSscalecam = 28
			p.camerascale = FRACUNIT*2+35000
			XSon.SwapStretch(p, s, xs)
			s.state = S_PLAY_EDGE
			xs.stopstick = 50
			s.spritexscale = FRACUNIT-22500
			s.spriteyscale = FRACUNIT+45000
			xs.edgestretch = 4
			XS_MediumDust(s, nil, nil, Q|FF_TRANS60, nil, s.angle)
			if xs.darkphase
				XSon.EarthQuake(s, 8, 330*s.scale, 1, 70000, FRACUNIT/5, -ANG30, 32|FF_TRANS50, 10, SKINCOLOR_NOBLE, AST_ADD)
				S_StartSound(s, sfx_xssfps)
			else
				if xs.supersonic
					XS_RingBurstSparkles(s)	
					XSon.sparkles(p,s,xs, 70, 5, 11, 4*s.scale, SKINCOLOR_SUPERGOLD3, AST_ADD, FF_HORIZONTALFLIP, nil, FRACUNIT/4)
					XSon.EarthQuake(s, 8, 330*s.scale, 1, 66000, FRACUNIT/5, -ANG30, 32|FF_TRANS50, 10, SKINCOLOR_SUPERGOLD4, AST_ADD)
					S_StartSound(s, sfx_xssfps)
				else
					XSVoice(p, s, xs, sfx_xsvoc2, false)
				end
			end
			xs.edgeanimcd = 90
			p.xswallrun = nil			
		elseif not m.valid 
		or (FixedHypot(s.x-m.x, s.y-m.y) < 150*s.scale)
		or (WR.start > 70)
			s.momz = max(20*m.scale, (40-(WR.start/2))*s.scale)
			WR.countdown = 3
			WR.faceangle = R_PointToAngle2(s.x, s.y, m.x, m.y)
			XS_WallRunAnim(p,s,xs,WR)		
		end return --
	end
	
	
	--Force a direction for indecisive players.
	if (p.cmd.buttons & (BT_SPIN|BT_CUSTOM1|BT_JUMP)) and not p.cmd.forwardmove and not p.cmd.sidemove
		p.cmd.forwardmove = 2
	end
	if WR.slip
		WR.slip = $-1
		if (WR.power > 5*s.scale)
			WR.power = 5*s.scale
		end
	end
	--Speed upwards! (or run downwards, naturally!)	
	if (WR.power > 2*s.scale)
		if WR.direction == 1 and (s.eflags & MFE_VERTICALFLIP)
		or WR.direction == -1 and not (s.eflags & MFE_VERTICALFLIP)
			WR.power = 0 --We swapped direction, quit propelling!
		else
			s.momz = WR.power*ZFLIP --Only propel if the wallthrust is still going up, otherwise use gravity.
		end
	end
	if WR.inittime
		WR.inittime = $+1
	end
	
	--Wallrun power slowly diminishes and goes into the negatives.
	if SUPER != 3
		local MINUS = s.scale
		if (abs(WR.power) >= 5*s.scale) and (s.momz*ZFLIP >= 2*s.scale)
			local ADD = FixedInt(abs(WR.power))
			ADD = max(1, min($, 15))
			MINUS = $+((s.scale/20)*ADD)
		end
		local QUICKDROP = (WR.Zmult and (WR.Zmult < 18)) and (SUPER*3+(6+WR.Zmult)) or 32
		if (WR.start < QUICKDROP) 
			MINUS = $/(QUICKDROP-WR.start)
		end
		if not ( (WR.power <= (3*s.scale*ZFLIP)) and ( (WR.leapboost or 0) > 3) )
			WR.power = $+( MINUS*((WR.startpower < 0) and 1 or -1) )
		end
	elseif not xs.darkphase
		p.pflags = $|PF_CANCARRY
	end
	WR.start = $+1 --Our timer, how long we've been at it.
	
	local TOWALLANG = XSon.WallAngle(s, (WR.fofline) and WR.fofline or WR.line) --Keep refreshing the angle.
	if WR.faceangle == nil
		WR.faceangle = TOWALLANG print("It was nil?!\x83 Set")
	elseif not (WR.faceangle == TOWALLANG)
		local MAX,PLUS = XSon.CamToDest(s, WR.faceangle, TOWALLANG, 2, ANG1, "skip")
	--	CONS_Printf(p, "Adjust by:\x82 "+MAX/(ANG1/3)+"\x80 PLUS:\x82 "+PLUS)
		if (MAX >= ANG1/4)
			WR.faceangle = $-(MAX*PLUS)
		end
	end

/*	for i = -9,9 --DEBUG DEBUG TEST TEST
		if i == 0 continue end
		local ghs = P_SpawnMobjFromMobj(s, FixedMul(24*s.scale*i, cos(TOWALLANG)),FixedMul(24*s.scale*i, sin(TOWALLANG)), s.height, MT_THOK)
		ghs.renderflags = RF_FULLBRIGHT|RF_NOCOLORMAPS
		ghs.spritexscale = FRACUNIT / ( max(3,abs(i)) )
		ghs.spriteyscale = ghs.spritexscale
		ghs.color = (i<0) and SKINCOLOR_SUPERSILVER1 or SKINCOLOR_EMERALD
		ghs.tics = 2
		ghs.frame = ($ & ~FF_TRANSMASK)|(max(5, abs(i))<<16)
	end
	local ANGLE,PLUS = XS_AngleDifference(WR.faceangle, TOWALLANG, 2)	
	if ANGLE and (ANGLE >= (ANG1/4))
		ANGLE = $/(ANG1/2)
--		print("AngleDIFF in 0.5 angles:\x82 "+ANGLE*PLUS)
		if (ANGLE < 20) and (ANGLE > 340) --Adjust angle if it makes sense.
--			print("\x84"+"Adjusted ANG\x80 from\x88"+WR.faceangle/ANG1+"\x80 to\x8b"+TOWALLANG/ANG1)
			WR.faceangle = TOWALLANG
		end
	end	*/
--	WR.faceangle = XSon.WallAngle(s, WR.line) --DEBUG DEBUG TEST TEST
	
	
	--Can only swap lines every couple tics!
	local DIST = 32*s.scale
	if WR.swappedline
		DIST = $+(WR.swappedline*16*s.scale)
		
		WR.swappedline = $-1		
		p.drawangle = WR.faceangle
	--	s.angle = WR.faceangle
	--	p.cmd.angleturn = WR.faceangle>>16
	
	end	
	
	--What should our rollangle be?
	s.rollangle = 0
	if (WR.start >= 40)
		p.aiming = 0
	else
		p.aiming = (ANG1/2)*(40-WR.start)*(ZFLIP*-1)
	end
	
	if (WR.inittime > 2)
		local CAMADD = 0
		if not WR.stepunlocked
			if (p.cmd.forwardmove > 22)
				WR.stepunlocked = true
				CAMADD = 2
			elseif (abs(p.cmd.sidemove) > 16) and (WR.inittime > 5)
			--	s.angle = WR.faceangle
			--	p.cmd.angleturn = WR.faceangle>>16
				p.drawangle = WR.faceangle
				CAMADD = 1
			end
		end
		if CAMADD!=0 and (WR.inittime > 5)
			WR.camadd = WR.camadd and $+CAMADD or CAMADD
		end
	end
	
	local DIDSWAP = nil
	--Move slightly left or right?
/*	if WR.cantsidestep
		WR.cantsidestep = $-1
		s.momx,s.momy = 0,0
	elseif WR.stepunlocked == true and (WR.start > 3) and WR.line
		if (abs(p.cmd.sidemove) > 4)
			if not (WR.swappedline and WR.swappedline>=5)
			and not ((s.flags2 & MF2_TWOD) or twodlevel)
				DIDSWAP = XSon.SideStep(p,s,xs,WR)
			end
		else
			WR.lastblocked = 0
		end
	end */
	--Go with wall-scrolling textures and conveyor belts.
	local POINT = P_PointOnLineSide(s.x, s.y, WR.line)
	
	if (WR.inittime > 4)
		s.momx,s.momy = $/2,$/2
	end
	--Stay on target with the wall, keeping trying to collide into it!
	if not (WR.swappedline and WR.swappedline >= 4)
		P_InstaThrust(s, WR.faceangle, (p.blocked) and s.scale/2 or (WR.inittime<8) and 18*s.scale or 2*s.scale) --Adjust momentum.
		local side = (POINT==1) and WR.line.backside or WR.line.frontside
		if WR.Xoffset != nil and DIDSWAP != "swapped" and not WR.swappedline
			if side.textureoffset != WR.Xoffset
				local SHIFT = (side.textureoffset-WR.Xoffset)*POINT*2
				local ANG = WR.faceangle+(ANGLE_90+5)
				P_TryMove(s, s.x+FixedMul(SHIFT, cos(ANG)), s.y+FixedMul(SHIFT, sin(ANG)), true)
			end
			if side.rowoffset != WR.Yoffset	
				local NEWZ = s.z+ (((side.rowoffset-WR.Yoffset)*5)/3)
				if (NEWZ >= s.floorz) and (NEWZ <= s.ceilingz)
					if not (s.eflags & MFE_VERTICALFLIP) and (NEWZ > s.z)
					or (s.eflags & MFE_VERTICALFLIP) and (NEWZ < s.z)
						WR.leapboost = 7
					end
					s.z = NEWZ print("Jump from "+s.z>>16+" to "+NEWZ>>16)
				end
			end
		end
		WR.Xoffset, WR.Yoffset = side.textureoffset,side.rowoffset	
	else
		P_InstaThrust(s, WR.faceangle, (p.blocked) and s.scale/2 or 2*s.scale) --Adjust momentum.
	end
--	if (s.momz*ZFLIP <= 2*s.scale)
--		P_TryMove(s, s.x+FixedMul(s.scale, cos(TOWALLANG)), s.y+FixedMul(s.scale, sin(TOWALLANG)), true) 
--	end
	
	if WR.leapboost
		WR.leapboost = $-1
	end
	
	--Can we continue running? Continuously check for the wall!	
	if (WR.countdown > 3)
		if WR.swappedline
			WR.countdown = max($-1,0)			
		else
			WR.countdown = 3 --Something else checked for us, so skip the checks this frame!
		end
	else
		--If the countdown hits 0, we stop wallrunning!	
		local COUNTDOWN,OVERWALL,SKYBOX = 0,0
		local OVERWALL = 0
		local SKYBOX = XS_SkyBoxWall(s, WR.line)
		if WR.fofline --Do FOF calculations instead?
			local secT = (s.subsector) and s.subsector.sector
			local lineside = ((WR.fofline.backsector) and WR.fofline.backsector != secT) and WR.fofline.backside or WR.fofline.frontside
			local secL = lineside.sector
			if not (XSon.FOFheightcheck(secL, s)) --XS_FOFcheck(s, secL)
				COUNTDOWN = 1
			else
				WR.countdown = 3
			end
		end
		if COUNTDOWN==0 or (SKYBOX==true)
			OVERWALL = XS_OverWallFloor(s, WR.line)
			if (OVERWALL <= 16*s.scale) --We haven't already climbed over the wall?
				if (SKYBOX==true) and not xs.FreeFlight and (WR.inittime > 3)
					COUNTDOWN = 5 --Way over the wall!
				end
				if xs.FreeFlight or (SKYBOX!=true) --We haven't started climbing some skybox?		
					local x,y = P_ClosestPointOnLine(s.x, s.y, WR.line) --Are we still close to the line?
					
					local MAXDIST = max(48*s.scale, s.radius)+p.speed+DIST
					if WR.swappedline or (WR.inittime < 5) MAXDIST = $*2 end
					
					if not (FixedHypot(s.x-x, s.y-y) < MAXDIST) 
						COUNTDOWN = ($==5) and 5 or 2 --Too far away from wall!
					else
						WR.countdown = 3	
					end
					
				elseif (COUNTDOWN != 5)
					COUNTDOWN = 3 --Skybox/invalid wall!
				end
			elseif not WR.swappedline
				if (WR.inittime%2==0)
				or (SKYBOX==true) and not xs.FreeFlight
					COUNTDOWN = ((SKYBOX==true) and not xs.FreeFlight) and 5 or ($==1) and 1 or 4 --Over the floor.
				end
			end
		end
		if COUNTDOWN != 0 and (DIDSWAP != "swapped")
			WR.countdown = max($-((COUNTDOWN==5) and 3 or 1), 0)
	--		CONS_Printf(p, "\x86"+"Counting down timer..."+WR.countdown+" CD type: "+COUNTDOWN+"\x87 Overwall:\x82"+OVERWALL>>16)
		end
	end
	if WR.removewalls
		WR.removewalls = $-1
		if not WR.removewalls
			WR.lastwall = nil
		end
	end
	if WR.countdown and (DIDSWAP != "swapped") and not WR.swappedline  and not (WR.inittime < 5)
		local XYDIST = ((WR.inittime < 3) or (WR.swappedline)) and 80*s.scale or 63*s.scale
		XSon.CheckFront(p,s,xs, WR, XYDIST, WR.faceangle, {x=WR.ogX, y=WR.ogY})
	end
	if s.rollangle != 0
		xs.alteredrollangle = true	
	end
	if WR.line and (WR.line.flags & ML_IMPASSIBLE) and WR.line.backsector and WR.countdown and not xs.FreeFlight
		local secT = (s.subsector) and s.subsector.sector --Object sector.
		if secT
			local secL = (secT!=WR.line.backsector) and WR.line.backsector or WR.line.frontsector
			if not (s.eflags & MFE_VERTICALFLIP) and (s.z > secL.floorheight+8*s.scale)
			or (s.eflags & MFE_VERTICALFLIP) and (s.z < secL.ceilingheight-8*s.scale)
				WR.countdown = 0
			end
		end
	end
	if not WR.countdown
	--	print("out of countdown, cancel\x85 now")
		XSon.WallRunning(p,s,xs,WR) return false --Loop into yourself to end this quicker.
	else
		WR.angupdate = $+1
		if (WR.angupdate > 4)
			WR.endangle = WR.faceangle
			WR.angupdate = 0
		end
		XS_WallRunAnim(p,s,xs, WR) --Play the wallrun animation.
	end
	return true --
end



--Are we currently over the wall's floor? Probably shouldn't be running on it then, right...?
--A 0 would always be a success, where a high number will be more likely to be a failure!
rawset(_G, "XS_OverWallFloor", function(s, line, FOFline)
	local secT = (s.subsector) and s.subsector.sector --Object sector.
	if not secT return 0 end --We're not in a sector...?? Whatever, count it as a success.

	if FOFline --Request using only the FOF calculations instead?

		local lineside = ((FOFline.backsector) and FOFline.backsector != secT) and FOFline.backside or FOFline.frontside --lineside facing player.
		local secL = lineside.sector
		if (XSon.FOFheightcheck(secL, s, FOFline)) return 0 end --
	--	if XS_FOFcheck(s, secL) return 0 end --

	elseif line

		if (line.flags & ML_EFFECT4) and (line.flags & ML_EFFECT5)
			if line.frontside.midtexture or line.backside and line.backside.midtexture
				return 0 --Repeating solid midtexture!
			end
		end

		if (line.flags & ML_IMPASSIBLE) --Impassible walls will work so long as they have a texture, I guess.
			if line.backside
				if line.backside.bottomtexture or line.backside.midtexture return 0 end
			end
			if line.frontside.bottomtexture or line.frontside.midtexture return 0 end --
		end

		local lineside = ((secT != line.backsector) and line.backsector) and line.backside or line.frontside --lineside facing player.
		local secL = lineside.sector
		
		if not line.backsector return 0 end --
		if XSon.FOFheightcheck(secL, s, line) return 0 end --
--		if XS_FOFcheck(s, secL) return 0 end --
		
		if not (s.eflags & MFE_VERTICALFLIP) or (s.player and s.player.xswallrun and (s.momz >= 0))
			if (s.z >= (secL.floorheight+s.momz)) --Did we rise above the floor? Then stop already!
				if (secL.ceilingheight <= (s.z-(s.height/3)) ) and lineside.toptexture
				or (secL.floorheight >= (s.z+s.momz) ) and lineside.bottomtexture
				or (s.player and s.player.blocked)
				or (XSon.FOFheightcheck(secT, s.z, s.z))
					print("\x82"+"Special success") return 0 --
				end
				print("s.z:"+s.z>>16+"\x84 Floorheight:\x80"+secL.floorheight>>16+"\x82 Ceilingheight:\x80"+secL.ceilingheight>>16+"\x8d DIFF:"+abs(s.z-(secL.floorheight+s.momz))>>16)			
				return abs(s.z-(secL.floorheight+s.momz)) --return height difference.
			end
		elseif (s.z < (secL.ceilingheight+s.momz)) --Inverted version.
			return abs(s.z-(secL.floorheight+s.momz)) 
		end
	end
	return 0 --0 is always a success.
end)

--Check for FOFs. Sector must be valid first.
rawset(_G, "XS_FOFcheck", function(s, sector, POLY)
	for fof in sector.ffloors()		
		if (fof.flags & FF_BLOCKPLAYER) and (fof.flags & FF_EXISTS)
			if (s.z >= fof.bottomheight-abs(s.momz))
			and (s.z < fof.topheight)
				if (fof.flags & FF_RENDERSIDES) and fof.master
				and not (fof.flags & (FF_SWIMMABLE|FF_BUSTUP))
					if fof.frontside and fof.frontside.midtexture
					or fof.backside and fof.master.backside.midtexture --Must have a texture.			
						return fof --
					end
				end
			end
		end
	end
	if POLY and POLY.polyobj --Enter the line if you want to check polyobjects too.
	local polyobj = POLY.polyobj 
		if polyobj.flags and (polyobj.flags & POF_SOLID)
		and polyobject.sector and polyobj:mobjTouching(s)
		local fof = polyobject.sector
			if (s.z >= fof.bottomheight-abs(s.momz))
			and (s.z < fof.topheight)
				return fof --
			end
		end
	end
	return nil --
end)

--Return max difference between sector floorheight and an object's?
--Different from SMSReborn's. Returns true if goal is met, otherwise it won't! Can also consider FOFs!
XSon.MobjSecHeightDiff = function(s, line, GOAL, skipFOF)
	local secT = (s.subsector) and s.subsector.sector --Object sector.
	if not line.backsector or not secT  --There IS no other sector.
		return true --Continue, I guess.
	end
	
	local DIFF = 0
	local GOAL = (GOAL) or 40*s.scale
	local secL = (secT != line.backsector) and line.backsector or line.frontsector
	
	if (s.eflags & MFE_VERTICALFLIP)
		if (s.z+s.height > secL.ceilingheight)
			DIFF = abs((s.z+s.height)-secL.ceilingheight)
			if (DIFF >= GOAL) return true end 
		end
		if not skipFOF return (YKNUX.FOFheightcheck(secL, s.z+s.height-DIFF, s.z+s.height)) end	
	else
		if (s.z < secL.floorheight)
			DIFF = abs(s.z-secL.floorheight)
			if (DIFF >= GOAL) return true end --Success?
		end
		if not (skipFOF) return (YKNUX.FOFheightcheck(secL, s.z, s.z-DIFF)) end
	end
--	print("\x85 failure. No FOFs or nothing.")
end

--Is there a solid FOF between TOP and HEIGHT? It'll auto-calculate if you give it userdata.
XSon.FOFheightcheck = function(secT, TOP, BOTTOM, POLY)
	if not (secT and secT.valid) return end --Sector doesn't exist, return!
	if type(TOP) == "userdata" --If you've entered a mobj, use that.
		local s = TOP
		local ADD = (tonumber(BOTTOM)) or TOP.momz or FRACUNIT
		TOP = max(s.z-s.momz, s.z+s.height+ADD)	-- print("\x81"+"userdata input! TOP: "+FixedInt(TOP)) --
		BOTTOM = s.z-abs(s.momz)
	end
	for fof in secT.ffloors()
		if (fof.flags & (FF_BLOCKPLAYER|FF_SOLID))
		and (fof.flags & (FF_RENDERSIDES|FF_RENDERALL))
		and (fof.flags & FF_EXISTS)
			if (TOP <= fof.topheight)
			and (BOTTOM >= fof.bottomheight)
				return true --
			end
		end
	end
	--Enter the line_t data if you want to check polyobjects too.
	if POLY and POLY.valid
	local polyobj = POLY.polyobj 
		if polyobj.valid and polyobj.flags
		and (polyobj.flags & POF_SOLID)
		and polyobject.sector and polyobj:mobjTouching(s)
		local fof = polyobject.sector
			if (s.z >= fof.bottomheight-abs(s.momz))
			and (s.z < fof.topheight)
				return fof --
			end
		end
	end	
end

--Return max difference between sector floorheight and an object's personal floor/ceiling height?
--Used for choosing to attach Sonic to a wall or not, so he doesn't stick on the tiniest spots.
--Not quite the same as SMSreborn's function, which checks current mobj position.
rawset(_G, "XS_AboveOrBelowSector", function(s, line, GOAL, skipFOF)

	local secT = (s.subsector) and s.subsector.sector --Object sector.
	if not line.backsector --There IS no other sector.
	or (line.backsector == line.frontsector)
	or not secT --What? WE're not in a sector???
		return true --
	end	
	local secL = (secT != line.backsector) and line.backsector or line.frontsector

	if (line.flags & ML_EFFECT4) and (line.flags & ML_EFFECT5) return true end --Repeating solid midtexture means runnable wall.
	
	if (s.z+s.momz > secL.ceilingheight) --print("3") 
	return true end --We're above the ceiling's wall? Great! Just do the skybox check too.
	
--	if XS_FOFcheck(s, secL)
	if XSon.FOFheightcheck(secL,s) --print("4") 
	return true end --Check for FOF instead?
		
	--So what exactly is our "floor"? There's alot of things we need to explore, let's check them ALL.
	local FLIP = 1
	local FLOORZ = s.floorz
	if (s.eflags & MFE_VERTICALFLIP)
		FLOORZ = s.ceilingz
		FLIP = -1
	end
	for i = 1,5
		if FLIP == -1
			if i==2 FLOORZ = secT.ceilingheight end
			if i==3 FLOORZ = line.frontsector.ceilingheight end
			if i==4 if not line.backsector FLOORZ = line.backsector.ceilingheight end end
			if i==5 FLOORZ = s.z end
			
			if FLOORZ != secL.ceilingheight break end
		else
			if i==2 FLOORZ = secT.floorheight end
			if i==3 FLOORZ = line.frontsector.floorheight end
			if i==4 if not line.backsector continue end FLOORZ = line.backsector.floorheight end
			if i==5 FLOORZ = s.z end
			
			if FLOORZ != secL.floorheight break end
		end
	end
--	print("FLOORZ: "+FLOORZ>>16+" SECL.Floorheight:"+secL.floorheight>>16)
	local DIFF = 0
	GOAL = (GOAL) or 120*s.scale
	
	--Finally, check!
	if FLIP == -1
		DIFF = abs(FLOORZ-secL.ceilingheight)
--		CONS_Printf(p,"No fof here. but yes,\x84 NOFLIP "+FixedInt(abs(FLOORZ-secL.floorheight)))
	else
--		CONS_Printf(p,"No fof here. And NOFLIP either. "+FixedInt(abs(FLOORZ-secL.floorheight)))
 		DIFF = abs(FLOORZ-secL.floorheight)
	end
	if (line.flags & ML_IMPASSIBLE)
		if line.backside --Impassible walls will work so long as they have a proper texture, I guess.
			local TFLOORZ = (s.eflags & MFE_VERTICALFLIP) and s.ceilingz or s.floorz
			local TFLOORZ2 = (s.eflags & MFE_VERTICALFLIP) and secL.ceilingheight or secL.floorheight
			if (abs(TFLOORZ-TFLOORZ2) < 8*s.scale)
				return --
			elseif (line.backside.bottomtexture and line.frontside.bottomtexture --Must have one on both sides, though.
			or line.backside.midtexture and line.frontside.midtexture)
				return true --
			end
		else--if not (line.backside)
			return --Stop! There's no way this'll work now...
		end
	end
	
	if (DIFF > GOAL) --print("5") 
	return true end
	if not skipFOF
		if XSon.FOFheightcheck(secL, max(s.z+s.momz+DIFF, s.z+s.height+s.momz), s.z-abs(s.momz)-DIFF) -- print("6") 
		return true end
	end
--	print("\x83"+"Got to\x84 END:\x82"+line+"\x84 GOAL:\x82"+GOAL>>16+"\x86 DIFF:\x82"+DIFF>>16+"\x88 FLIP:\x82"+FLIP)
end)

--Your wall is a skybox wall. Ignore! Leniency must be filled in. -Stolen from SMSreborn.
rawset(_G, "XS_SkyBoxWall", function(s, line, leniency)
	local secT = (s.subsector) and s.subsector.sector --Object sector.
	local EXTRA = (leniency) or 0 --No leniency if not filled in.

	if line and secT
		if line.special == 41 return end --Horizon wall effect. EZ.
		
		local lineside = ((secT != line.backsector) and line.backsector) and line.backside or line.frontside --lineside facing player.
		local secL = lineside.sector	
		
		if (secL.floorheight == secL.ceilingheight)
		and (not secL.ceilingpic or (secL.ceilingpic:upper():find("F_SKY")))
		and not line.frontside.toptexture 
		and not (line.backside and line.backside.toptexture)
		and secL.floorheight != nil --Rare glitch prevention.	
			if not ( s.z <= (secL.floorheight+((s.height)+s.momz+EXTRA)) ) --Only cancel if we're ABOVE the skywall.
				return true --It's a skybox wall.
			end
		end
	end return false --
end)

--SEC is the player's sector.
XSon.WallRunCanAscend = function(s, SEC, line, xs)
	if SEC == nil return end --
	local NEXT = (line.frontsector != SEC) and line.frontsector or line.backsector or nil
	local CEILINGZ = (s.eflags & MFE_VERTICALFLIP) and s.floorz or s.ceilingz
	if NEXT != nil and NEXT.floorheight != nil
		local FLOORZ = (s.eflags & MFE_VERTICALFLIP) and s.ceilingz or s.floorz
		local FLOORZ2 = (s.eflags & MFE_VERTICALFLIP) and NEXT.ceilingheight or NEXT.floorheight
		local FLOORDIFF = abs(FLOORZ-FLOORZ2)
--		print("FLOORZ:"+(FLOORZ>>16)+" NEXT.FLOORZ:"+(FLOORZ2>>16)+" \x86 FLOORDIFF:\x82"+(FLOORDIFF>>16))
		if (s.eflags & MFE_VERTICALFLIP) 
			if (s.floorz > NEXT.ceilingheight)
				local CEILINGDIST = abs(s.floorz-NEXT.ceilingheight)
				if (CEILINGDIST < 200*s.scale)
--					print("\x84"+"INVERTED:"+"\x80 Close to ceiling\x85 FALSE: "+CEILINGDIST>>16)
					print("\x8f"+"C4")  return false 
				end	
			end
		else
			if (s.ceilingz+(s.height/3) < NEXT.floorheight)
				local CEILINGDIST = abs(s.ceilingz-NEXT.floorheight)
				if (CEILINGDIST < 200*s.scale) 
--					print("Close to ceiling\x85 FALSE: "+CEILINGDIST>>16)
					print("\x8f"+"C5: Ceilingdist:"+CEILINGDIST>>16)  return false 
				end
			end
		end
	/*	local FMOMZ = (s.momz*ZFLIP>0) and abs(s.momz) or 1
		local MAXFLOORDIFF = (66*s.scale) + min(63*s.scale, FMOMZ*3)
--		print("MAXFLOORDIFF:"+MAXFLOORDIFF>>16)
		if (FLOORDIFF < MAXFLOORDIFF) 
		and not ((FLOORDIFF == 0) and (line.backsector==nil or line.backsector.floorheight == line.backsector.ceilingheight
		or line.frontsector.floorheight == line.frontsector.ceilingheight))
--			print("Floorheight diff too small: \x85 FALSE")
			print("\x8f"+"C6: Floordiff:"+FLOORDIFF>>16) return false --
		end */
	elseif s.ceilingz != nil and not (s.eflags & MFE_VERTICALFLIP) --There's no backsector!
		local FLOORDIFF = abs(s.z-CEILINGZ)
		if (FLOORDIFF < 200*s.scale) --Wallrunning won't do much good here!
--			print("Missing backsector FALSE")
			print("\x8f"+"C7 "+FLOORDIFF>>16)
			return false --
		end
	end return true --
end

--Wall Jump Timer behaviour
XSon.WallJumpTimer = function(p,s,xs)
	if P_IsObjectOnGround(s) or P_PlayerInPain(p)
	or p.powers[pw_carry] or (s.eflags & (MFE_GOOWATER|MFE_SPRUNG))
		xs.walljump,xs.walljumpP,p.aiming = nil,nil,0 return --
	end
	xs.walljump = max($-1,0)
	if xs.jump == 1 and (p.cmd.buttons & BT_JUMP) and (xs.walljump < 28) --Go into floaty jump.
		xs.leapaftermath = 23
		xs.walljump = nil
		XSon.sparkles(p, s, xs, 65, 10, 11)
	elseif (p.speed > 55*s.scale) and not p.powers[pw_sneakers]
		if (xs.walljump < 17)
			s.momx = FixedDiv(FixedMul($,85),86)
			s.momy = FixedDiv(FixedMul($,85),86)
			if xs.walljump == 5
				xs.leapaftermath = 23
			end
		elseif (xs.walljump > 24)
			XSGhostTrail(p, s, xs)
			XS_CartoonDust(s, SKINCOLOR_WHITE, nil, FRACUNIT+25000)
			XS_RingBurstStripes(s, nil, nil, 60<<16)
			p.aiming = ANG1*-6*P_MobjFlip(s) * (34-xs.walljump)
		else
			XSZipTrail(s, nil, nil, FF_TRANS90)
			p.aiming = 0
		end
	elseif (xs.walljump > 17) and (p.speed < 25*s.scale)
		xs.walljump = $-3
	end
	if not xs.walljump
		xs.walljump,xs.walljumpP,p.aiming = nil,nil,0
	end
end