// --------------------------------------
// Lua_MetalKnuckles
// Only took like a month to start
// writing this oops.
// Jojo's Bizzare Adventure was a mistake
// やれやれだぜ
// --------------------------------------
freeslot("S_METK_ROCKETCHARGE", "S_METK_CLIMB", "S_METK_LANDING",
"MT_METK_RESIDUE", "S_METK_RESIDUE", "SPR_KRES")

local DASHMODE_THRESHOLD = (3*TICRATE)
local DASHMODE_MAX = (DASHMODE_THRESHOLD + 3)

local metalbounceback = false	// Bounce back at higher charges

local function ResetMK(player)
	if(player.mtkvar == nil)
		player.mtkvar = {}
	end
	
	// Charge glide
	player.mtkvar.glidecharge = 0
	player.mtkvar.glidetime = 0
	player.mtkvar.restorecolor = false
	player.mtkvar.glideland = false			// When hitting the floor, do the pose!
	player.mtkvar.slidetime = 0				// Eat my dust!
	
	// Wall spindash
	player.mtkvar.walldashtime = 0
	player.mtkvar.spindashstart = false
	player.mtkvar.spinheldtime = 0
	
	// Keep dash mode!
	player.mtkvar.storeddash = false
	player.mtkvar.storenormalspeed = player.normalspeed
	player.mtkvar.storedashmode = player.dashmode
	
	player.mtkvar.prevbutt = player.cmd.buttons
	player.mtkvar.prevground = P_IsObjectOnGround(player.mo)
	player.mtkvar.prevcarry = player.powers[pw_carry]
end

local chargedecel = FRACUNIT*97/100
local chargetime = TICRATE/3
local maxcharge = 4

local maxglidetime = TICRATE

// Charges
local releasesounds = {
	[0] = sfx_s3ka6,
	[1] = sfx_kc68,
	[2] = sfx_kc3b,
	[3] = sfx_bkpoof,
}
local releasespeeds = {
	[0] = FRACUNIT*36,
	[1] = FRACUNIT*46,
	[2] = FRACUNIT*56,
	[3] = FRACUNIT*76,
}
local releaseminmomz = {
	[0] = -8*FRACUNIT,
	[1] = -6*FRACUNIT,
	[2] = -4*FRACUNIT,
	[3] = 0,
}

// Wall spindash
local walldashverti = 27*FRACUNIT
local maxwalldashtime = TICRATE

// Charge effect
local chargecolor = SKINCOLOR_BLUEBELL
local startspeed = FRACUNIT*5
local endspeed = FRACUNIT*10
local function SpawnChargeEffect(player)
	local res = P_SpawnMobj(player.mo.x, player.mo.y, player.mo.z+(player.mo.height/2), MT_METK_RESIDUE)
	// Pick a random speed!
	local speed = (P_RandomByte()*(endspeed-startspeed)/256)+startspeed
	// Pick a random direction
	res.resmomx = (P_RandomByte()-128)*FRACUNIT/128
	res.resmomy = (P_RandomByte()-128)*FRACUNIT/128
	res.resmomz = (P_RandomByte()-128)*FRACUNIT/128
	local mag = FixedHypot(FixedHypot(res.resmomx, res.resmomy), res.resmomz)
	// Use speed!
	res.resmomx = FixedMul(FixedDiv($1, mag), speed)
	res.resmomy = FixedMul(FixedDiv($1, mag), speed)
	res.resmomz = FixedMul(FixedDiv($1, mag), speed)
	// Set color!
	res.color = chargecolor
	res.target = player.mo
end

// Wall collision stuff
local function SlopePos(mobj, height, slope)
	if(slope)
		// Get the corner of the object that should be the highest on the slope
		local testx
		local testy
		if(slope.d.x < 0)
			testx = mobj.radius
		else
			testx = -mobj.radius
		end
		
		if(slope.d.y < 0)
			testy = mobj.radius
		else
			testy = -mobj.radius
		end
		
		if(slope.zdelta > 0)
			testx = -testx
			testy = -testy
		end
		
		testx = $1 + mobj.x
		testy = $1 + mobj.y
		return P_GetZAt(slope, testx, testy)
	else
		return height
	end
end

local function CheckCollidedWall(mobj, line)
	// Check if we were facing it
	local crossx = line.v2.x-line.v1.x	// Not actually a cross since I'm not sure if my math was right... This works too anyway
	local crossy = line.v2.y-line.v1.y
	local mag = FixedHypot(crossx, crossy)
	crossx = FixedDiv($1, mag)
	crossy = FixedDiv($1, mag)
	
	local px = cos(mobj.angle)
	local py = sin(mobj.angle)
	
	// Dot!
	local facing = FixedMul(crossx, px)+FixedMul(crossy, py)
	
	if(abs(facing) < cos(ANGLE_22h))
		return true
	end
	return false
end

addHook("AbilitySpecial", function(player)
	if player.mo and player.mo.valid and player.mo.skin == "metalknuckles"
	and player.charability == CA_GLIDEANDCLIMB
		return true
	end
end)

addHook("PlayerCanDamage", function(player, mobj)
	if player.mo and player.mo.valid and player.mo.skin == "metalknuckles"
	and player.mtkvar and (player.mtkvar.glidetime or player.mtkvar.slidetime)
		local mo = player.mo
		local angle = player.drawangle - R_PointToAngle2(mo.x-mo.momx,
		mo.y-mo.momy, mobj.x, mobj.y)
		if angle > abs(ANGLE_180)
			angle = $+ANGLE_180*2
		elseif angle < ANGLE_180
			angle = $-ANGLE_180*2
		end
		if abs(angle) < ANGLE_90
			return true
		end
	end
end)

addHook("ThinkFrame", function()
	for player in players.iterate
		if(player.mo and player.mo.valid and player.mo.skin == "metalknuckles")
			and(player.mo.health)
			if(player.mtkvar == nil)
				ResetMK(player)
			end
			
			if player.mo.state == S_PLAY_WAIT
				player.mo.anim_duration = min($,3)
			end
			
			if not player.climbing
				player.mtkvar.walldashtime = 0
				player.mtkvar.spindashstart = false
				player.mtkvar.spinheldtime = 0
			end
			
			if(player.mtkvar.restorecolor)
				player.mtkvar.restorecolor = false
				player.mo.color = player.skincolor
				if((player.powers[pw_shield] & SH_STACK) == SH_FIREFLOWER)
					player.mo.color = SKINCOLOR_WHITE
				end
				player.mo.colorized = false
			end
			
			//No jump boost from dashmode
			if player.dashmode >= DASHMODE_THRESHOLD
				player.jumpfactor = skins[player.skin].jumpfactor
			end
			
			// No landing!
			if(P_PlayerInPain(player))
				or(player.powers[pw_carry] or player.mtkvar.prevcarry)
				or(player.pflags & PF_SLIDING)
				player.mtkvar.glideland = false
			end
			
			// Landing
			if(player.mtkvar.glideland)
				and(P_IsObjectOnGround(player.mo))
				S_StartSoundAtVolume(player.mo, sfx_s3k96, 128)
				player.mo.state = S_METK_LANDING
				//player.powers[pw_nocontrol] = max($1, player.mo.tics)
				if player.powers[pw_super]
					P_Earthquake(player.mo,player.mo,256*FRACUNIT)
					S_StartSound(player.mo, sfx_s3k49)
				else
					S_StartSound(player.mo, sfx_s3k4c)
				end
				player.mtkvar.glideland = false
			end
			
			//Sliding
			if(player.mtkvar.glidetime)
				and(P_IsObjectOnGround(player.mo))
				player.mtkvar.slidetime = 7
				player.mo.state = S_PLAY_GLIDE
				player.panim = PA_DASH
			elseif(player.mtkvar.slidetime)
				player.mtkvar.slidetime = $-1
				if (player.mtkvar.slidetime)
					and (P_IsObjectOnGround(player.mo))
					and (player.panim <= PA_DASH)
					if (player.mo.state != S_PLAY_GLIDE and player.mo.state != S_PLAY_SWIM)
						player.mo.state = S_PLAY_GLIDE
						player.panim = PA_DASH
					end
					P_SpawnSkidDust(player, player.mo.radius, true)
				else
					player.mtkvar.slidetime = 0
					if (player.mo.state == S_PLAY_GLIDE or player.mo.state == S_PLAY_SWIM)
						player.mo.state = S_PLAY_WALK
					end
				end
			end
			
			// Water
			local wdiv = 1
			if(player.mo.eflags & MFE_UNDERWATER)
				wdiv = 2
			end
			
			if(player.cmd.buttons & BT_JUMP)
				and not(player.mtkvar.prevbutt & BT_JUMP)
				and(player.pflags & PF_JUMPED)
				and not(P_IsObjectOnGround(player.mo) or player.mtkvar.prevground)
				and (player.powers[pw_super] != 1)
				and not(player.powers[pw_carry] or player.mtkvar.prevcarry)
				and not(player.pflags & PF_THOKKED)
				and not(player.exiting)
				and not(player.pflags & PF_STASIS)
				and not(player.powers[pw_nocontrol])
				and not(P_PlayerInPain(player))
				and not(player.pflags & PF_SLIDING)
				and not(player.climbing)
				and not(player.mtkvar.walldashtime)
				and not(player.gotflag)
				and not(player.gotcrystal)
				player.mtkvar.glidecharge = 1
				player.secondjump = 2 //If these two ever change past the
				player.glidetime = 1 //intial charge, we cancel the glide
				player.pflags = ($1|PF_THOKKED) & !(PF_JUMPED|PF_SPINNING)
			end
			
			// Force cancel charge
			if(P_IsObjectOnGround(player.mo))
				or(player.powers[pw_super] == 1)
				or(player.powers[pw_carry])
				or(P_PlayerInPain(player))
				or(player.pflags & PF_SLIDING)
				or(player.mo.eflags & MFE_SPRUNG)
				or(player.powers[pw_nocontrol])
				or(player.panim == PA_PAIN)
				or(player.exiting)
				or(player.gotflag)
				or(player.gotcrystal)
				or(player.secondjump != 2)
				or not(player.glidetime)
				player.mtkvar.glidecharge = 0
				player.mtkvar.glidetime = 0
				if(player.mo.state == S_METK_ROCKETCHARGE)
				and not(player.actionstate)
					if(P_IsObjectOnGround(player.mo))
						player.mo.state = S_PLAY_WALK
					else
						player.mo.state = S_PLAY_FALL
					end
				end
			end
			
			// Mid charge
			if(player.mtkvar.glidecharge)
				// Store dash!
				if not(player.mtkvar.storeddash)
					player.mtkvar.storeddash = true
					player.mtkvar.storenormalspeed = player.normalspeed
					player.mtkvar.storedashmode = player.dashmode
				end
		//		player.secondjump = 1
				
				if not(player.mtkvar.glidetime)
					if not(player.mo.state == S_METK_ROCKETCHARGE)
						player.mo.state = S_METK_ROCKETCHARGE
					end
				//	player.dashmode = 0
					if player.exiting
						//Nope
					elseif(player.mtkvar.storedashmode >= DASHMODE_THRESHOLD)
						player.dashmode = TICRATE*12
					else
						player.dashmode = max(player.mtkvar.storedashmode-1, 0)
					end
					
					// Charging
					player.mtkvar.glidecharge = $1 + 1
				//	if(player.mtkvar.glidecharge > chargetime*maxcharge)
				//		player.mtkvar.glidecharge = chargetime*maxcharge
				//	end
					local charge = (player.mtkvar.glidecharge-1)/chargetime
					local realcharge = charge
					if(charge > maxcharge-1)
						charge = maxcharge-1
					end
					
					for i, v in ipairs(releasespeeds) do
						if v < player.normalspeed
							player.mtkvar.glidecharge = $+chargetime
						end
					end
					
					// Sound
					if(charge*chargetime == (player.mtkvar.glidecharge-1))
						or(charge >= maxcharge-1 and (player.mtkvar.glidecharge%4 == 0))
						S_StartSound(player.mo, sfx_s3k5c)
						local rnum = 0
						while(rnum < 25)
							SpawnChargeEffect(player)
							rnum = $1 + 1
						end
						// Colorize!
						player.mtkvar.restorecolor = true
						player.mo.color = chargecolor
						player.mo.colorized = true
					end
					
					player.normalspeed = 0	// Can't go any faster
					player.mo.momx = FixedMul($1 - player.cmomx, chargedecel)+player.cmomx
					player.mo.momy = FixedMul($1 - player.cmomy, chargedecel)+player.cmomy
					player.mo.momz = 0
					
					// Release
					if not(player.cmd.buttons & BT_JUMP)
						or(realcharge >= maxcharge)	// Held too long
						player.mtkvar.glidetime = 1
						S_StartSound(player.mo, releasesounds[charge])
						P_InstaThrust(player.mo, player.mo.angle,
						FixedMul(player.mo.scale, max(releasespeeds[charge], player.normalspeed)/wdiv))
						player.mo.momx = $1 + player.cmomx
						player.mo.momy = $1 + player.cmomy
						if player.mtkvar.storedashmode <= DASHMODE_THRESHOLD
							player.mtkvar.storedashmode = min($+charge*TICRATE*2/3, DASHMODE_THRESHOLD-1)
						end
						//if not(charge)
						//	player.mtkvar.storenormalspeed = skins[player.mo.skin].normalspeed
						//	player.mtkvar.storedashmode = 0
						//	player.dashmode = 0
						//end
					end
				end
				// Gliding!
				if (player.mtkvar.glidetime)
					player.mtkvar.glidetime = $1 + 1
					if (player.cmd.buttons & BT_JUMP) and not(player.mtkvar.prevbutt & BT_JUMP)
						player.mtkvar.glidetime = 0
						player.mtkvar.glidecharge = 0
						player.mtkvar.glideland = true
						player.mo.momx = $/2
						player.mo.momy = $/2
						player.mo.state = S_PLAY_FALL
						player.pflags = $1|PF_JUMPED|PF_NOJUMPDAMAGE
					elseif (player.cmd.buttons & BT_SPIN) and not(player.mtkvar.prevbutt & BT_SPIN)
						player.mtkvar.glidetime = 0
						player.mtkvar.glidecharge = 0
						player.mtkvar.glideland = false
						player.mo.state = S_PLAY_JUMP
						player.pflags = $1|PF_JUMPED&~PF_NOJUMPDAMAGE
					else
						player.pflags = $1|PF_JUMPED|PF_NOJUMPDAMAGE
						if (player.mo.state != S_PLAY_GLIDE and player.mo.state != S_PLAY_SWIM)
							player.mo.state = S_PLAY_GLIDE
							player.panim = PA_DASH
						end
						
						// Gravity
						player.mo.momz = $1 - (P_GetMobjGravity(player.mo)/2)
						
						if (player.mtkvar.glidetime <= maxglidetime)
							//player.dashmode = 0
							if player.exiting
								//Nope
							elseif(player.mtkvar.storedashmode >= DASHMODE_THRESHOLD)
								player.dashmode = TICRATE*12
							else
								player.dashmode = max(player.mtkvar.storedashmode-1, 0)
							end
						
							local charge = (player.mtkvar.glidecharge-1)/chargetime
							if(charge > maxcharge-1)
								charge = maxcharge-1
							end
							// Zoom!
							P_InstaThrust(player.mo,
							R_PointToAngle2(player.cmomx, player.cmomy, player.mo.momx, player.mo.momy),
							FixedMul(player.mo.scale, max(releasespeeds[charge], player.normalspeed)/wdiv))
							player.mo.momx = $1 + player.cmomx
							player.mo.momy = $1 + player.cmomy
							
							// Glide
							local minmomz = FixedMul(player.mo.scale, releaseminmomz[charge])
							if(player.mo.momz*P_MobjFlip(player.mo) < minmomz)
								player.mo.momz = minmomz*P_MobjFlip(player.mo)
							end
							
							player.powers[pw_justsprung] = max($1, 1)
						elseif player.speed < FixedMul(player.mo.scale, skins[player.mo.skin].normalspeed/wdiv)
							// Zoom! ...but less exciting
							P_InstaThrust(player.mo,
							R_PointToAngle2(player.cmomx, player.cmomy, player.mo.momx, player.mo.momy),
							FixedMul(player.mo.scale, skins[player.mo.skin].normalspeed/wdiv))
							player.mo.momx = $1 + player.cmomx
							player.mo.momy = $1 + player.cmomy
						end
						
						player.drawangle = R_PointToAngle2(player.cmomx, player.cmomy, player.mo.momx, player.mo.momy)
					end
				end
			elseif player.climbing
				if player.exiting
						//Nope
				elseif(player.mtkvar.storedashmode >= DASHMODE_THRESHOLD)
					player.dashmode = TICRATE*12
				else
					player.dashmode = max(player.mtkvar.storedashmode-1, 0)
				end
				player.normalspeed = player.mtkvar.storenormalspeed
				
				if(player.mtkvar.spindashstart or player.mtkvar.walldashtime < 5)
					player.mtkvar.walldashtime = min($+1, maxwalldashtime)
				end
					
				if(player.mtkvar.spindashstart)
					local maxtics = max(4-((player.mtkvar.walldashtime*4)/maxwalldashtime), 1)
					if(player.mo.tics > maxtics)
						player.mo.tics = maxtics
					end
					
					for i=0, max(P_RandomKey(3),1)
						local dashspeed = min((player.mindash*3/2)+(player.mtkvar.walldashtime*FRACUNIT),player.maxdash)
						local dust = P_SpawnMobjFromMobj(player.mo,
						FRACUNIT*P_SignedRandom()/12+P_ReturnThrustX(nil,player.mo.angle,skins[player.mo.skin].radius),
						FRACUNIT*P_SignedRandom()/12+P_ReturnThrustY(nil,player.mo.angle,skins[player.mo.skin].radius),
						FRACUNIT*P_RandomByte()/16,MT_SPINDUST)
						dust.angle = player.drawangle
						P_Thrust(dust, dust.angle, FRACUNIT*P_RandomByte()/-20)
						dust.angle = $+ANGLE_90
						P_Thrust(dust, dust.angle, FRACUNIT*P_SignedRandom()/-16)
						dust.momz = dashspeed/-12
						if not (player.mtkvar.walldashtime%6)
							S_StartSound(player.mo, sfx_spndsh)
						end
					end
				end
				
				// Launch!
				if(player.mtkvar.spindashstart and not player.mtkvar.spinheldtime)
				//or(player.mtkvar.walldashtime > maxwalldashtime)
					player.mo.momz = FixedMul(walldashverti/wdiv, player.mo.scale)*P_MobjFlip(player.mo)
					player.mo.momz = FixedMul($1, (FRACUNIT*player.mtkvar.walldashtime)/maxwalldashtime)	// E
					player.pflags = ($1|PF_SPINNING|PF_JUMPED) & !(PF_STARTJUMP|PF_THOKKED)
					player.mo.state = S_PLAY_ROLL
					S_StartSound(player.mo, sfx_zoom)
					player.mtkvar.walldashtime = 0
					player.climbing = 0
				end
				
				// Start launch
				if player.mtkvar.spinheldtime > 5
					and not(player.mtkvar.spindashstart)
					player.mtkvar.spindashstart = true
					player.mo.tics = 1
				end
			elseif player.mtkvar.storeddash
			and not P_IsObjectOnGround(player.mo)
			and not (player.mo.eflags&MFE_JUSTHITFLOOR)
			and not player.mtkvar.glidecharge and not player.mtkvar.glidetime
			and not player.powers[pw_carry]
				if player.exiting
						//Nope
				elseif(player.mtkvar.storedashmode >= DASHMODE_THRESHOLD)
					player.dashmode = TICRATE*12
				else
					player.dashmode = max(player.mtkvar.storedashmode-1, 0)
				end
				player.normalspeed = player.mtkvar.storenormalspeed
			elseif(player.mtkvar.storeddash)
				// Release dash!
				player.mtkvar.storeddash = false
				player.normalspeed = player.mtkvar.storenormalspeed
				player.dashmode = player.mtkvar.storedashmode
		//		player.secondjump = 0
			end
			
			player.mtkvar.prevbutt = player.cmd.buttons
			if player.climbing
				player.mtkvar.prevbutt = $|BT_JUMP
			end
			player.mtkvar.prevground = P_IsObjectOnGround(player.mo)
			player.mtkvar.prevcarry = player.powers[pw_carry]
		elseif(player.mtkvar)
			if(player.mtkvar.restorecolor)
				player.mtkvar.restorecolor = false
				player.mo.color = player.skincolor
				if((player.powers[pw_shield] & SH_STACK) == SH_FIREFLOWER)
					player.mo.color = SKINCOLOR_WHITE
				end
				player.mo.colorized = false
			end
			player.normalspeed = skins[player.realmo.skin].normalspeed
			player.mtkvar = nil
		end
	end
end)

//Movement while climbing
//...you wouldn't think that we'd need this, but we do
//Also do the spin held junk
addHook("PreThinkFrame", do for player in players.iterate
	if not(player.mo and player.mo.valid)
		or not(player.mo.skin == "metalknuckles")
		or not player.climbing
		continue
	end
	
	local cmd = player.cmd
	if player.mtkvar.spindashstart
		cmd.forwardmove = -1
		cmd.sidemove = 0
		if cmd.buttons&BT_SPIN
			player.mtkvar.spinheldtime = 1
		else
			player.mtkvar.spinheldtime = 0
		end
		cmd.buttons = $&~BT_SPIN
		player.pflags = $|PF_USEDOWN
		continue
	elseif (player.mo.eflags & MFE_UNDERWATER)
		P_SetObjectMomZ(player.mo, FixedDiv(cmd.forwardmove*FRACUNIT, player.powers[pw_super] and 20*FRACUNIT/3 or 10*FRACUNIT), false) // 2/3 while super
		P_InstaThrust(player.mo, player.mo.angle-ANGLE_90, FixedDiv(cmd.sidemove*player.mo.scale, player.powers[pw_super] and 20*FRACUNIT/3 or 10*FRACUNIT)) // 2/3 while super
	else
		P_SetObjectMomZ(player.mo, FixedDiv(cmd.forwardmove*FRACUNIT, player.powers[pw_super] and 5*FRACUNIT or 15*FRACUNIT>>1), false); // 2/3 while super
		P_InstaThrust(player.mo, player.mo.angle-ANGLE_90, FixedDiv(cmd.sidemove*player.mo.scale, player.powers[pw_super] and 5*FRACUNIT or 15*FRACUNIT>>1)) // 2/3 while super
	end
	
	if cmd.buttons&BT_SPIN
		player.mtkvar.spinheldtime = $+1
		cmd.buttons = $&~BT_SPIN
		player.pflags = $|PF_USEDOWN
	elseif player.mtkvar.spinheldtime and player.mtkvar.spinheldtime <= 5
		cmd.buttons = $|BT_SPIN
		player.pflags = $&~PF_USEDOWN
	end
end end)

//Modern Sonic code used with permission
local GetSlidable = function(mo,line)
	if (line.frontsector) then
		if (line.frontsector.ceilingpic == "F_SKY1") then
			if (mo.z > line.frontsector.ceilingheight) then
				return false
			end
		end
	end
	if (line.backsector) then
		if (line.backsector.ceilingpic == "F_SKY1") then
			if (mo.z > line.backsector.ceilingheight) then
				return false
			end
		end
	end
	return true
end

//Allow laching onto walls
addHook("MobjMoveBlocked", function(mobj)
	if not(mobj.skin == "metalknuckles")
		or not(mobj.player and mobj.player.valid)
		or mobj.player.climbing
		or not mobj.player.mtkvar
		or not (mobj.player.mtkvar.glidetime or mobj.player.mtkvar.glidecharge)
		return
	end
	
	mobj.player.lastlinehit = -1
	mobj.player.lastsidehit = -1
	
	local oldmomx = mobj.momx
	local oldmomy = mobj.momy
	
	P_SlideMove(mobj)
	mobj.player.powers[pw_pushing] = 3
	
	if lines[mobj.player.lastlinehit] and lines[mobj.player.lastlinehit].valid
		local line = lines[mobj.player.lastlinehit]
		local pointx, pointy = P_ClosestPointOnLine(mobj.x, mobj.y, line)
		if R_PointToDist2(mobj.x, mobj.y, pointx, pointy) <= mobj.radius*2
		and(CheckCollidedWall(mobj, line)) // We hit it straight on!
		and GetSlidable(mobj, line)
		and line.special != 41
			local num = P_PointOnLineSide(mobj.x, mobj.y, line)
			if num == 0
				num = 1
			else
				num = 0
			end
			local side = sides[line.sidenum[num]]
			if side and side.valid and side.sector and side.sector.valid
			and side.sector and side.sector.valid
			and mobj.z+mobj.height < side.sector.ceilingheight
			and mobj.z > side.sector.floorheight
				local fofwall = false
				for rover in side.sector.ffloors()
					if not(rover.flags & FF_EXISTS)
						or not(rover.flags & FF_BLOCKPLAYER)
						continue
					end
					
					local staircaset = 16*mobj.scale
					local staircaseb = 0
					if(mobj.eflags & MFE_VERTICALFLIP)
						staircaset = 0
						staircaseb = 16*mobj.scale
					end
					
					local top = SlopePos(mobj, rover.topheight, rover.t_slope)
					local bottom = SlopePos(mobj, rover.bottomheight, rover.b_slope)
					
					// Wall!
					if((mobj.z+mobj.height)-staircaseb > bottom)
						and(mobj.z+staircaset < top)
						fofwall = true
						break
					end
				end
				if not fofwall
					//Bounce off lines you can't climb
					P_InstaThrust(mobj,
					R_PointToAngle2(mobj.x, mobj.y, pointx, pointy), -mobj.player.speed)
					S_StartSound(mobj, sfx_s3kaa)
					mobj.player.mtkvar.glidecharge = max($-chargetime/2, 1)
					mobj.player.mtkvar.glidetime = 1
					return true
				end
			end
			
			local charge = (mobj.player.mtkvar.glidecharge-1)/chargetime
			
			if line.flags & ML_NOCLIMB
				//Bounce off lines you can't climb
				P_InstaThrust(mobj,
				R_PointToAngle2(mobj.x, mobj.y, pointx, pointy), -mobj.player.speed)
				S_StartSound(mobj, sfx_s3kaa)
				mobj.player.mtkvar.glidecharge = max($-chargetime/2, 1)
				mobj.player.mtkvar.glidetime = 1
			else
				// Climb
				mobj.player.mtkvar.glidecharge = 0
				mobj.player.mtkvar.glidetime = 0

				mobj.angle = R_PointToAngle2(mobj.x, mobj.y, pointx, pointy)
				P_Thrust(mobj, mobj.angle, 5*mobj.scale)

				mobj.state = S_METK_CLIMB
				mobj.player.pflags = $1|PF_JUMPED&~PF_THOKKED
				
				// Store dash!
				if not(mobj.player.mtkvar.storeddash)
					mobj.player.mtkvar.storeddash = true
					mobj.player.mtkvar.storenormalspeed = mobj.player.normalspeed
					mobj.player.mtkvar.storedashmode = mobj.player.dashmode
					if mobj.player.exiting
						//Nope
					elseif(mobj.player.mtkvar.storedashmode >= DASHMODE_THRESHOLD)
						mobj.player.dashmode = TICRATE*12
					end
				end
				
				mobj.player.climbing = 5
				mobj.player.mtkvar.walldashtime = 1
				mobj.player.mtkvar.spinheldtime = 0
				
				S_StartSoundAtVolume(mobj, sfx_s3k96, 128)
				if mobj.player.powers[pw_super]
					P_Earthquake(mobj,mobj,256*FRACUNIT)
					S_StartSound(mobj, sfx_s3k49)
				else
					S_StartSound(mobj, sfx_s3k4a)
				end
			end
		end
	end
	return true
end, MT_PLAYER)

// Residue thinker
local residuetime = TICRATE
local residuefadetime = TICRATE/2
local resfric = FRACUNIT*90/100
addHook("MobjThinker", function(mobj)
	// Timer
	if(mobj.timer == nil)
		mobj.timer = 0
		mobj.resoffx = 0
		mobj.resoffy = 0
		mobj.resoffz = 0
	end
	if(mobj.resmomx == nil)
		mobj.resmomx = 0
		mobj.resmomy = 0
		mobj.resmomz = 0
	end
	mobj.timer = $1 + 1
	
	if(mobj.timer > residuetime)
		P_RemoveMobj(mobj)
		return
	end
	
	// Fade out
	if(mobj.timer > residuefadetime)
		local residuefade = ((mobj.timer-residuefadetime)*10)/(residuetime-residuefadetime)
		residuefade = min(max($1, 0), 9)	// Keep between 0 and 9
		mobj.frame = ($1 & !FF_TRANSMASK)|(FF_TRANS10*residuefade)
	end
	
	// Placement
	mobj.resoffx = $1 + mobj.resmomx
	mobj.resoffy = $1 + mobj.resmomy
	mobj.resoffz = $1 + mobj.resmomz
	P_TeleportMove(mobj, mobj.target.x+mobj.resoffx, mobj.target.y+mobj.resoffy, mobj.target.z+(mobj.target.height/2)+mobj.resoffz)
	
	// Momentum
	mobj.resmomx = FixedMul($1, resfric)
	mobj.resmomy = FixedMul($1, resfric)
	mobj.resmomz = FixedMul($1, resfric)
	
	// Color!
	if(P_RandomByte() & 1)
		mobj.color = SKINCOLOR_AETHER
	else
		mobj.color = chargecolor
	end
end, MT_METK_RESIDUE)

// I hate this
local FUME_SKINCOLORS =
{
	SKINCOLOR_ICY,
	SKINCOLOR_SKY,
	SKINCOLOR_CYAN,
	SKINCOLOR_WAVE,
	SKINCOLOR_TEAL,
	SKINCOLOR_AQUA,
	SKINCOLOR_SEAFOAM,
	SKINCOLOR_MINT,
	SKINCOLOR_PERIDOT,
	SKINCOLOR_LIME,
	SKINCOLOR_YELLOW,
	SKINCOLOR_SANDY,
	SKINCOLOR_GOLD,
	SKINCOLOR_APRICOT,
	SKINCOLOR_SUNSET,
	SKINCOLOR_SUPERGOLD1
}
local numfumecolors = 15
addHook("FollowMobj", function(player, fume)
	if not(player.mtkvar and player.mtkvar.glidecharge)	// Custom non custom behavior
		return
	end
	
	local mo = player.mo
	local angle = player.drawangle
	local dist
	local panim = player.panim
	local dashmode = player.dashmode
	local underwater = mo.eflags & MFE_UNDERWATER
	
	if(fume.state == mobjinfo[fume.type].spawnstate) // If currently inivisble, activate!
		fume.state = mobjinfo[fume.type].seestate
		P_SetScale(fume, mo.scale)
	end
	
	if(dashmode > DASHMODE_THRESHOLD and fume.state != mobjinfo[fume.type].seestate) // If in dashmode, grow really big and flash
		fume.destscale = mo.scale;
		fume.flags2 = $1 ^^ MF2_DONTDRAW	// ???
		fume.flags2 = $1|(mo.flags2 & MF2_DONTDRAW)
	else // Otherwise, pick a size and color depending on speed and proximity to dashmode
		if(dashmode == DASHMODE_THRESHOLD and dashmode > fume.movecount) // If just about to enter dashmode, play the startup animation again
			fume.state = mobjinfo[fume.type].seestate
			P_SetScale(fume, mo.scale << 1)
		end
		fume.flags2 = ($1 & !MF2_DONTDRAW) | (mo.flags2 & MF2_DONTDRAW)
		local scale = 3
		if(underwater)
			scale = 3
		end
		fume.destscale = (mo.scale + FixedDiv(player.speed, max(player.normalspeed, skins[player.mo.skin].normalspeed))) / scale
		local dashmodeuse = ((dashmode * numfumecolors) / (DASHMODE_MAX + 1))+1
		if(dashmodeuse > numfumecolors)
			dashmodeuse = numfumecolors	// Dash mode is set to 9999, well above this!
		end
		fume.color = FUME_SKINCOLORS[dashmodeuse]

		if(underwater)
			fume.frame = ($1 & FF_FRAMEMASK) | FF_ANIMATE | (P_RandomRange(0, 9) * FF_TRANS10)
			fume.threshold = 1
		elseif(fume.threshold)
			fume.frame = ($1 & FF_FRAMEMASK) | states[fume.state].frame
			fume.threshold = 0
		end
	end
	
	fume.movecount = dashmode // keeps track of previous dashmode value so we know whether Metal is entering or leaving it
	fume.eflags = ($1 & !MF2_OBJECTFLIP) | (mo.flags2 & MF2_OBJECTFLIP) // Make sure to flip in reverse gravity!
	fume.eflags = ($1 & !MFE_VERTICALFLIP) | (mo.eflags & MFE_VERTICALFLIP) // Make sure to flip in reverse gravity!
	
	// Finally, set its position
	dist = -mo.radius - FixedMul(mobjinfo[fume.type].radius, fume.destscale - mo.scale/3);
	local dumboff = 0
	if(player.mo.state == S_METK_ROCKETCHARGE)
		dumboff = -5*player.mo.scale*P_MobjFlip(player.mo)
	end
	P_TeleportMove(fume, mo.x+P_ReturnThrustX(fume, angle, dist), mo.y+P_ReturnThrustY(fume, angle, dist), mo.z+((mo.height - fume.height) >> 1)+dumboff)
	
	// Ghost.
	if player.mtkvar.glidetime and player.mtkvar.glidetime <= maxglidetime
		P_SpawnGhostMobj(fume)
	end
	
	return true
end, MT_METALJETFUME)

// Spike collision
local function MetalKnucklesSpikeCollide(mobj, spike)
	if not(mobj.player and mobj.player.valid)
		or not(mobj.skin == "metalknuckles")
		or mobj.player.climbing
		return
	end
	
	if(spike and spike.valid)
		and(spike.type == MT_SPIKE or spike.type == MT_WALLSPIKE)
		and(mobj.z+mobj.height+mobj.momz >= spike.z-(spike.scale))
		and(mobj.z+mobj.momz <= spike.z+spike.height+(spike.scale))
		
		// Check for hurt
		if(spike.type == MT_SPIKE)
			if(spike.eflags & MFE_VERTICALFLIP)
				// Ceiling spike
				if(mobj.z+mobj.height < spike.z+(spike.scale*2))
					return
				end
			else
				// Floor spike
				if(mobj.z > spike.z+spike.height-(spike.scale*2))
					return
				end
			end
		else
			// Wall spike
			if(mobj.z < spike.z+spike.height)
				and(mobj.z+mobj.height > spike.z)
				local ang = R_PointToAngle2(spike.x, spike.y, mobj.x, mobj.y)
				local px = cos(ang)
				local py = sin(ang)
				local sx = cos(spike.angle)
				local sy = sin(spike.angle)
				if((FixedMul(px, sx)+FixedMul(py, sy)) > sin(ANGLE_45))
					return
				end
			end
		end
		
		P_KillMobj(spike)
	end
end
addHook("MobjCollide", MetalKnucklesSpikeCollide, MT_PLAYER)
addHook("MobjMoveCollide", MetalKnucklesSpikeCollide, MT_PLAYER)

addHook("MobjDamage", function(mobj, inflictor)
	if not(mobj and mobj.valid)
		or not(inflictor and inflictor.valid)
		return	// That wouldn't be good...
	end
	
	if(inflictor.player and inflictor.player.valid)
		and(inflictor.player.mtkvar)
		and(mobjinfo[mobj.type].spawnhealth > 1)	// Double hit enemy
		and(mobj.flags & (MF_ENEMY|MF_BOSS))
		local player = inflictor.player
		// Bounce off
		if(player.mtkvar.glidetime)
			player.mtkvar.glidetime = 0
			player.mtkvar.glidecharge = 0
			inflictor.state = S_PLAY_FALL
			player.pflags = $1 & !(PF_JUMPED|PF_STARTJUMP)
			player.mtkvar.glideland = true
		end
	end
end)

