// --------------------------------------
// 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_GLIDE", "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.climbwall = nil
	player.mtkvar.walldashtime = 0
	player.mtkvar.wallangle = 0
	player.mtkvar.spindashstart = false
	
	// Ow.
	player.mtkvar.followsector = nil		// Could be an ffloor instead
	player.mtkvar.followmode = 0			// 0: None, 1: Sector floor, 2: Sector ceiling, 2: FoF top, 3: FoF bottom
	player.mtkvar.prevfollowvalue = nil
	player.mtkvar.prevfollowsector = nil
	
	// 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/2
local maxcharge = 4

local maxglidetime = TICRATE

// Charges
local releasesounds = {
	[0] = sfx_cybdth,
	[1] = sfx_bkpoof,
	[2] = sfx_bkpoof,
	[3] = sfx_bkpoof,
}
local releasespeeds = {
	[0] = FRACUNIT*25,
	[1] = FRACUNIT*40,
	[2] = FRACUNIT*55,
	[3] = FRACUNIT*75,
}
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_45))
		return true
	end
	return false
end

local function CheckHitSolidOnLine(mobj, line, bust)
	if(line.flags & ML_NOCLIMB)
		return false	// Can't climb a noclimb wall
	end
	
	local staircaset = 16*mobj.scale
	local staircaseb = 0
	if(mobj.eflags & MFE_VERTICALFLIP)
		staircaset = 0
		staircaseb = 16*mobj.scale
	end

	local snum = 0
	local numside = 2
	if not(line.flags & ML_TWOSIDED)
		numside = 1
	end
	local hitmode = 0
	local hitsector = nil
	local hitwall = false
	while(snum < numside)
		local side = sides[line.sidenum[snum]]
		
		if not (side and side.valid and side.sector and side.sector.valid)
		snum = $1 + 1 continue end
		
		// Collide with wall
		if(numside == 1) or not (sides[line.sidenum[(snum+1)&1]]
		and sides[line.sidenum[(snum+1)&1]].valid)
			// One sided?
			hitwall = true	// We 100% hit this.
			hitmode = 1
			hitsector = side.sector
		else
			// Check for the other side!
			local otherside = sides[line.sidenum[(snum+1)&1]]
			// Ceiling wall
			local ourcel = SlopePos(mobj, side.sector.ceilingheight, side.sector.c_slope)
			local othcel = SlopePos(mobj, otherside.sector.ceilingheight, otherside.sector.c_slope)
			if(ourcel > othcel)
				and((mobj.z+mobj.height)-staircaseb > othcel)
				hitwall = true
				hitmode = 2
				hitsector = otherside.sector
			else
				// Floor wall
				local ourflr = SlopePos(mobj, side.sector.floorheight, side.sector.f_slope)
				local othflr = SlopePos(mobj, otherside.sector.floorheight, otherside.sector.f_slope)
				if(ourflr < othflr)
					and(mobj.z+staircaset < othflr)
					hitwall = true
					hitmode = 1
					hitsector = side.sector
				end
			end
		end
		
		for rover in side.sector.ffloors()
			if not(rover.flags & FF_EXISTS)
				or not(rover.flags & FF_BLOCKPLAYER)
				continue
			end
			
			local top = SlopePos(mobj, rover.topheight, rover.t_slope)
			local bottom = SlopePos(mobj, rover.bottomheight, rover.b_slope)
			
			// Bust!
			if(bust)
				and(rover.flags & FF_BUSTUP)
				and(mobj.z+mobj.height > bottom)
				and(mobj.z < top)
				// In bustable; break it!
				EV_CrumbleChain(nil, rover)		// Bust it!
				// Run a linedef executor??
				if(rover.master.flags & ML_EFFECT5)
					P_LinedefExecute(P_AproxDistance(rover.master.dx, rover.master.dy)>>FRACBITS, mobj, sector)
				end
				continue
			end
			
			// Wall!
			if((mobj.z+mobj.height)-staircaseb > bottom)
				and(mobj.z+staircaset < top)
				hitwall = true
				hitmode = 3
				if(mobj.eflags & MFE_VERTICALFLIP)
					hitmode = 4
				end
				hitsector = rover
			end
		end
		snum = $1 + 1
	end
	
	if(mobj.player and mobj.player.valid and mobj.player.mtkvar)
		mobj.player.mtkvar.followsector = hitsector
		mobj.player.mtkvar.followmode = hitmode
	end
	return hitwall
end

local function CheckStillOnWall(mobj, generous)
	local useangle = mobj.angle
	if(mobj.player and mobj.player.valid)
		useangle = mobj.player.drawangle
	end
	local addx
	local addy
	if(generous)
		addx = FixedMul(cos(useangle), mobj.radius*3)
		addy = FixedMul(sin(useangle), mobj.radius*3)
	else
		addx = FixedMul(cos(useangle), mobj.radius/2)
		addy = FixedMul(sin(useangle), mobj.radius/2)
	end
	
	// Oh boy.
	local tester = P_SpawnMobj(mobj.x, mobj.y, mobj.z, MT_GARGOYLE)
	tester.flags = $1 & !(MF_PUSHABLE|MF_SOLID)
	tester.flags2 = $1|MF2_DONTDRAW	// We delete it right after anyway...
	tester.scale = mobj.scale
	tester.radius = mobj.radius
	tester.height = mobj.height
	local moved = P_TryMove(tester, tester.x+addx, tester.y+addy, true)
	P_RemoveMobj(tester)
	if(moved)
		return false
	end
	return true
end

// Copied from Kirby
local function moveWithSide(mobj, side, normalx, normaly)
	// Init side tracking
	if not(mobj.metkwall == side)
		or(mobj.metklastmoved and leveltime-mobj.metklastmoved > 1)
		or(mobj.metkwallx == nil)
		or(mobj.metkwally == nil)
		mobj.metkwallx = side.textureoffset
		mobj.metkwally = side.rowoffset
	end
	mobj.metkwall = side
	
	local offx = FixedMul(mobj.metkwallx-side.textureoffset, normalx)
	local offy = FixedMul(mobj.metkwallx-side.textureoffset, normaly)
	local offz = mobj.metkwally-side.rowoffset
	
	mobj.metkwallx = side.textureoffset
	mobj.metkwally = side.rowoffset
	
	mobj.metklastmoved = leveltime
	
	return {offx, offy, offz}
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("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
			
			player.thrustfactor = skins[player.mo.skin].thrustfactor
			
			if(player.mtkvar.followsector == nil)
				or not(player.mtkvar.followsector == player.mtkvar.prevfollowsector)
				player.mtkvar.prevfollowvalue = nil
			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 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_StartSound(player.mo, sfx_s3k4c)
				player.mo.state = S_METK_LANDING
				//player.powers[pw_nocontrol] = max($1, player.mo.tics)
				player.mtkvar.glideland = false
			end
			
			//Sliding
			if(player.mtkvar.glidetime)
				and(P_IsObjectOnGround(player.mo))
				player.mtkvar.slidetime = 7
			elseif(player.mtkvar.slidetime)
				player.mtkvar.slidetime = $-1
				if (player.mtkvar.slidetime)
					and (P_IsObjectOnGround(player.mo))
					and (player.panim <= PA_DASH)
					if not(player.mo.state == S_METK_GLIDE)
						player.mo.state = S_METK_GLIDE
					end
					P_SpawnSkidDust(player, player.mo.radius, true)
				else
					player.mtkvar.slidetime = 0
					if(player.mo.state == S_METK_GLIDE)
						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 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)
				player.mtkvar.glidecharge = 1
				player.pflags = ($1|PF_THOKKED) & !(PF_JUMPED|PF_SPINNING)
			end
			
			// Force cancel charge
			if(P_IsObjectOnGround(player.mo))
				or(player.powers[pw_carry])
				or(P_PlayerInPain(player))
				or(player.pflags & PF_SLIDING)
				or(player.mo.eflags & MFE_SPRUNG)
				player.mtkvar.glidecharge = 0
				player.mtkvar.glidetime = 0
				if(player.mo.state == S_METK_ROCKETCHARGE)
					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.mtkvar.storedashmode >= DASHMODE_THRESHOLD)
						player.dashmode = 9999
					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
					
					// 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, releasespeeds[charge]/wdiv))
						player.mo.momx = $1 + player.cmomx
						player.mo.momy = $1 + player.cmomy
						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.mtkvar.glidetime > maxglidetime)
						or((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.state = S_PLAY_JUMP
						player.pflags = $1|PF_JUMPED|PF_STARTJUMP
					else
						//player.dashmode = 0
						if(player.mtkvar.storedashmode >= DASHMODE_THRESHOLD)
							player.dashmode = 9999
						else
							player.dashmode = max(player.mtkvar.storedashmode-1, 0)
						end
						
						player.pflags = $1|PF_JUMPED	// Do damage
						if not(player.mo.state == S_METK_GLIDE)
							player.mo.state = S_METK_GLIDE
						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, releasespeeds[charge]/wdiv))
						player.mo.momx = $1 + player.cmomx
						player.mo.momy = $1 + player.cmomy
						
						// Gravity
						player.mo.momz = $1 - (P_GetMobjGravity(player.mo)/2)
						
						// 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.drawangle = R_PointToAngle2(player.cmomx, player.cmomy, player.mo.momx, player.mo.momy)
						player.powers[pw_justsprung] = max($1, 1)
					end
				end
			elseif(player.mtkvar.walldashtime)	// Wall spindash
				// 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
				
				player.drawangle = player.mtkvar.wallangle
				
				// Check on wall!
				local onwall = true
				if not(player.mtkvar.climbwall and player.mtkvar.climbwall.valid)
					onwall = false
				else
					local pointx, pointy = P_ClosestPointOnLine(player.mo.x, player.mo.y, player.mtkvar.climbwall)
					if(R_PointToDist2(player.mo.x, player.mo.y, pointx, pointy) > FixedHypot(player.mo.radius, player.mo.radius)*2)
						onwall = false
					elseif not(CheckHitSolidOnLine(player.mo, player.mtkvar.climbwall, false))	// Not hitting an FOF or proper wall, just a line?
						onwall = false
					end
				end
				
				// Force end
				local generous = false
				if(player.mtkvar.walldashtime < 5)
					generous = true
				end
				if not(onwall or player.mtkvar.walldashtime < 5)
					or(P_IsObjectOnGround(player.mo))
					or(player.powers[pw_carry])
					or(P_PlayerInPain(player))
					or(player.pflags & PF_SLIDING)
					or(player.mo.eflags & MFE_SPRUNG)
					or not(CheckStillOnWall(player.mo, generous))
					player.mtkvar.walldashtime = 0
					player.mtkvar.followsector = nil
					player.mtkvar.followmode = 0
					if(player.mo.state == S_METK_CLIMB)
						player.mo.state = S_PLAY_STND
					end
				else
					if(player.mtkvar.storedashmode >= DASHMODE_THRESHOLD)
						player.dashmode = 9999
					else
						player.dashmode = max(player.mtkvar.storedashmode-1, 0)
					end
					
					player.pflags = $1|PF_JUMPED|PF_NOJUMPDAMAGE
					
					if(player.mtkvar.spindashstart or player.mtkvar.walldashtime < 5)
						player.mtkvar.walldashtime = $1 + 1
					end
					
					// Animation
					if not(player.mo.state == S_METK_CLIMB)
						player.mo.state = S_METK_CLIMB
					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
					end
					
					// Wall clinging
					if(player.mtkvar.walldashtime < 5)	// Stick to wall!
						local pointx, pointy = P_ClosestPointOnLine(player.mo.x, player.mo.y, player.mtkvar.climbwall)
						P_InstaThrust(player.mo, player.drawangle, 25*player.mo.scale)	// Was 15
					else
						player.mo.momx = 0
						player.mo.momy = 0
					end
					
					// Move with the wall!
					local metkside
					local nx
					local ny
					local theside = P_PointOnLineSide(player.mo.x, player.mo.y, player.mtkvar.climbwall)
					if(theside == 0)
						or not(player.mtkvar.climbwall.flags & ML_TWOSIDED)	// One sided lines only have a front...
						metkside = sides[player.mtkvar.climbwall.sidenum[0]]
						nx = (player.mtkvar.climbwall.v1.x - player.mtkvar.climbwall.v2.x)
						ny = (player.mtkvar.climbwall.v1.y - player.mtkvar.climbwall.v2.y)
					else
						metkside = sides[player.mtkvar.climbwall.sidenum[1]]
						nx = (player.mtkvar.climbwall.v2.x - player.mtkvar.climbwall.v1.x)
						ny = (player.mtkvar.climbwall.v2.y - player.mtkvar.climbwall.v1.y)
					end
					local mag = R_PointToDist2(0, 0, nx, ny)
					nx = FixedDiv($1, mag)
					ny = FixedDiv($1, mag)
					local n = moveWithSide(player.mo, metkside, nx, ny)
					player.mo.momx = $1-n[1]
					player.mo.momy = $1-n[2]
					player.mo.momz = -n[3]
					
					// Move side to side
					if not(player.mtkvar.spindashstart)
						local movespd = (-5*player.cmd.sidemove*player.mo.scale)/50
						player.mo.momx = $1 + FixedMul(movespd, nx)
						player.mo.momy = $1 + FixedMul(movespd, ny)
						if(movespd)
							if(player.mo.tics < 0)
								or(player.mo.tics > 4)
								player.mo.tics = 4
							end
						else
							player.mo.tics = -1
						end
					end
					
					// Move with floor!
					local followval
					if(player.mtkvar.followmode == 1)	// Sector floor
						followval = player.mtkvar.followsector.floorheight
					elseif(player.mtkvar.followmode == 2)	// Sector ceiling
						followval = player.mtkvar.followsector.ceilingheight
					elseif(player.mtkvar.followmode == 3)	// FoF top
						followval = player.mtkvar.followsector.topheight
					elseif(player.mtkvar.followmode == 4)	// FoF bottom
						followval = player.mtkvar.followsector.bottomheight
					end
					if(followval)
						if(player.mtkvar.prevfollowvalue)
							P_TeleportMove(player.mo, player.mo.x, player.mo.y, player.mo.z+(followval-player.mtkvar.prevfollowvalue))
						end
						player.mtkvar.prevfollowvalue = followval
					end
					
					// Can't move!
				//	player.powers[pw_nocontrol] = max($1, 1)
					player.normalspeed = 0
					player.thrustfactor = 0
					
					// Start launch
					if(player.cmd.forwardmove > 20)
						and not(player.mtkvar.spindashstart)
						S_StartSound(player.mo, sfx_spin)
						player.mtkvar.spindashstart = true
						player.mo.tics = 1
					end
					
					// Jump
					if not(player.mtkvar.spindashstart)
						and(player.cmd.buttons & BT_JUMP)
						and not(player.mtkvar.prevbutt & BT_JUMP)
						P_DoJump(player, true)
						player.pflags = ($1) & !(PF_THOKKED)
						player.mo.momz = (7*player.mo.scale*P_MobjFlip(player.mo))/wdiv
						P_Thrust(player.mo, player.drawangle, (-2*player.mo.scale)/wdiv)
						player.mtkvar.walldashtime = 0
						player.mtkvar.climbwall = nil
						player.mtkvar.followsector = nil
						player.mtkvar.followmode = 0
					end
					
					// Launch!
					if(player.mtkvar.spindashstart and not (player.cmd.forwardmove > 0))
						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
				//		player.powers[pw_nocontrol] = 0
						S_StartSound(player.mo, sfx_zoom)
						player.mtkvar.walldashtime = 0
						player.mtkvar.climbwall = nil
						player.mtkvar.followsector = nil
						player.mtkvar.followmode = 0
					end
				end
			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
			player.mtkvar.prevground = P_IsObjectOnGround(player.mo)
			player.mtkvar.prevcarry = player.powers[pw_carry]
			player.mtkvar.prevfollowsector = player.mtkvar.followsector
		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.mo.skin].normalspeed
			player.mtkvar = nil
		end
	end
end)

// Wall interaction
addHook("MobjLineCollide", function(mobj, line)
	if not(mobj.skin == "metalknuckles")
		or not(mobj.player and mobj.player.valid)
		return
	end
	
	local pointx, pointy = P_ClosestPointOnLine(mobj.x, mobj.y, line)
	local hitwall = CheckHitSolidOnLine(mobj, line, true)
	
	// Hit the line...
	if(hitwall)
		and(CheckCollidedWall(mobj, line))
		// We hit it straight on!
		and(mobj.player.mtkvar and mobj.player.mtkvar.glidetime)
		and(CheckStillOnWall(mobj, true))	// Just... also make sure this isn't a poly object...
		
		local charge = (mobj.player.mtkvar.glidecharge-1)/chargetime
		if(metalbounceback and charge)
			// Bounce back
			mobj.player.mtkvar.glidecharge = 0
			mobj.player.mtkvar.glidetime = 0
			S_StartSound(mobj, sfx_s3k49)
			mobj.momz = 5*mobj.scale*P_MobjFlip(mobj)
			mobj.player.drawangle = R_PointToAngle2(mobj.x, mobj.y, pointx, pointy)
			P_InstaThrust(mobj, mobj.player.drawangle, -15*mobj.scale)
			mobj.state = S_PLAY_PAIN
			mobj.player.powers[pw_flashing] = TICRATE/4
			mobj.player.pflags = $1 & !(PF_JUMPED)
		else
			// Climb
			mobj.player.mtkvar.climbwall = line
			mobj.player.mtkvar.glidecharge = 0
			mobj.player.mtkvar.glidetime = 0
			mobj.player.mtkvar.walldashtime = 1
			mobj.player.mtkvar.spindashstart = false
			mobj.momx = 0
			mobj.momy = 0
			mobj.momz = 0
			mobj.player.drawangle = R_PointToAngle2(mobj.x, mobj.y, pointx, pointy)
			P_InstaThrust(mobj, mobj.player.drawangle, 15*mobj.scale)
			mobj.player.mtkvar.wallangle = mobj.player.drawangle
			mobj.state = S_METK_CLIMB
			S_StartSound(mobj, sfx_s3k4a)
			mobj.player.pflags = $1|PF_JUMPED|PF_NOJUMPDAMAGE
			
			// 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.mtkvar.storedashmode >= DASHMODE_THRESHOLD)
					mobj.player.dashmode = 9999
				end
			end
		end
	end
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
}
local numfumecolors = 15
addHook("FollowMobj", function(player, fume)
	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 not(player.mtkvar and player.mtkvar.glidecharge)	// Custom non custom behavior
		return
	end
	
	if(underwater) // No fume underwater; spawn bubbles instead!
	/*	fume->movedir	+= FixedAngle(FixedDiv(2 * player->speed, 3 * mo->scale));
		fume->movefactor += player->speed;

		if (fume->movefactor > FixedDiv(2 * player->normalspeed, 3 * mo->scale))
			INT16 i;
			fixed_t radiusV = 4*FRACUNIT;
			fixed_t radiusX = P_ReturnThrustX(mo, angle, -mo->radius >> (panim == PA_WALK ? 1 : 0));
			fixed_t radiusY = P_ReturnThrustY(mo, angle, -mo->radius >> (panim == PA_WALK ? 1 : 0));
			fixed_t factorX = P_ReturnThrustX(mo, angle + ANGLE_90, mo->scale);
			fixed_t factorY = P_ReturnThrustY(mo, angle + ANGLE_90, mo->scale);
			fixed_t offsetH, offsetV, x, y, z;

			for (i = -1; i < 2; i += 2)
				offsetH = i*P_ReturnThrustX(fume, fume->movedir, radiusV);
				offsetV = i*P_ReturnThrustY(fume, fume->movedir, radiusV);
				x = mo->x + radiusX + FixedMul(offsetH, factorX);
				y = mo->y + radiusY + FixedMul(offsetH, factorY);
				z = mo->z + (mo->height >> 1) + offsetV;
				P_SpawnMobj(x, y, z, MT_SMALLBUBBLE)->scale = mo->scale >> 1;
			end

			fume->movefactor = 0;
		end

		if (panim == PA_WALK)
			if (fume.state != mobjinfo[fume.type].spawnstate)
				fume->threshold = 0;
				P_SetMobjState(fume, mobjinfo[fume.type].spawnstate);
			end
			return;
		end*/
	end
	
	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)
		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")
		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)
