freeslot("S_PLAY_WALLJUMP", "SPR2_CLNG")

states[S_PLAY_WALLJUMP] = {
    sprite = SPR_PLAY,
    frame = FF_ANIMATE | SPR2_CLNG,
    tics = -1,
    var1 = 5,
    var2 = 2,
    nextstate = S_PLAY_WALLJUMP,
    isGrinding = true,
}

-- =========================
-- TUNING
-- =========================

-- Wall pin + down slide
local WALLJUMP_WALLPUSH  = 2*FRACUNIT
local WALLJUMP_SLIDEDOWN = 3*FRACUNIT

-- Momentum → lift behavior:
-- We convert HALF the incoming horizontal speed into up-velocity, then multiply it.
-- Example: if incoming speed ~16 FU/tic:
-- half = 8; mult=4 => 32 FU/tic initial lift (then decays)
local WALLJUMP_LIFT_MULT = 4*FRACUNIT   -- increase this to get more lift at normal speeds (try 5*FRACUNIT or 6*FRACUNIT)

-- How quickly the lift decays each tic (FU/tic^2 in fixed-point).
-- Lower = longer lift, Higher = shorter lift.
local WALLJUMP_UP_DECEL  = FRACUNIT/2   -- 0.5 FU/tic per tic; try FRACUNIT/3 for longer, or FRACUNIT for snappier

-- Safety bounds for the upward velocity
local WALLJUMP_UP_MINMOM = 10*FRACUNIT   -- if you have *any* speed, enforce at least this much lift (raise if still too weak)
local WALLJUMP_UP_MAXMOM = 15*FRACUNIT  -- cap so it never becomes a pop


local function WJ_Abs(v)
	if v < 0 then return -v end
	return v
end

-- Cheap distance approximation without sqrt/math
local function WJ_AproxDist(x, y)
	x = WJ_Abs(x)
	y = WJ_Abs(y)
	if x < y then
		local t = x
		x = y
		y = t
	end
	return x + FixedDiv(y, 2*FRACUNIT)
end


//Walljump lua first so we can check if the player is clinging or not first
//From SSnMighty Modifed by TGTLS
addHook("PlayerThink", function(player)
		if (player.mo and player.mo.skin == "sonic")
			
			//if not (player.atwall)
				player.atwall = 0
			//end
			if not (player.cling)
				player.cling = false
			end
			if not (player.clingprev)
				player.clingprev = false
			end

			if (player.walljump_upmom == nil)
				player.walljump_upmom = 0
			end

			if (player.mo) //Fixes spectating, and possibly other things
				//Finding walls (not FOFs)
				local wall = R_PointInSubsector(player.mo.x+FixedMul(26*FRACUNIT, cos(player.mo.angle)), player.mo.y+FixedMul(26*FRACUNIT, sin(player.mo.angle))).sector
				local fheight = wall.floorheight
				local cheight = wall.ceilingheight

				if wall.f_slope then
					fheight = P_GetZAt(wall.f_slope, player.mo.x + player.mo.momx, player.mo.y + player.mo.momy)
				end
				if wall.c_slope then
					cheight = P_GetZAt(wall.c_slope, player.mo.x + player.mo.momx, player.mo.y + player.mo.momy)
				end
				
				if (fheight > player.mo.z) or ((cheight <= player.mo.height+player.mo.z) and not (fheight == cheight)/* and (wall.ceilingpic == "F_SKY1")*/) //This last bit seems to be broken...
					player.atwall = 1
				end
				//FOFs can be walls too, so lets check for those
				for wall in wall.ffloors()
					if (player.mo.z <= wall.topheight) and (player.mo.height+player.mo.z > wall.bottomheight)
						and(wall.flags & FF_BLOCKPLAYER) //Don't want the player to cling to water. That would be stupid
						player.atwall = $1 + 1
					end		
				end

				//IMPORTANT: Once clinging, do NOT auto-drop just because atwall detection fails.
				//Only drop the cling when SPIN is released.
				if(player.atwall <= 0)
					if (player.cling) and not (player.cmd.buttons & BT_SPIN)
						player.cling = false
						player.walljump_upmom = 0
						player.walljump_lockang = nil
					end
				end	

				if (P_IsObjectOnGround(player.mo))
					player.cling = false
					player.walljump_upmom = 0
					player.walljump_lockang = nil
				end	

				//This allows sonic to thok away from a wall. You can remove this if you dont want your character to do this.
				//Lazy checks
				if (player.powers[pw_carry] == CR_NONE) 
				and not (player.charability2 == CA2_MULTIABILITY)
					//If you're clinging to a wall
					if (player.cling)
						and not (player.cmd.buttons & BT_SPIN)
						if (player.powers[pw_underwater])
							P_SetObjectMomZ(player.mo, 10*FRACUNIT)
						else
							P_SetObjectMomZ(player.mo, 15*FRACUNIT)
						end
						S_StartSound(player.mo, sfx_jump)
						player.cling = false
						player.walljump_upmom = 0
						player.walljump_lockang = nil
						player.pflags = $ & ~PF_THOKKED
						if (player.charability2 == CA2_SPINDASH)
							player.mo.state = S_PLAY_ROLL //L
						else
							player.mo.state = S_PLAY_SPRING //P
						end
						player.jumping = false
						P_InstaThrust(player.mo, player.mo.angle, -20*FRACUNIT) //Devided by 3 for sonic, but you can remove this for your character
						player.mo.angle = R_PointToAngle2(0, 0, player.mo.momx, player.mo.momy)
					end

					//Cling onto a wall
					if (player.atwall > 0)
						and not (player.cling)
						and (((player.mo.momz < -3*FRACUNIT+1) and not (player.mo.flags2 & MF2_OBJECTFLIP)) or ((player.mo.momz > 3*FRACUNIT-1) and (player.mo.flags2 & MF2_OBJECTFLIP)))
						and (player.pflags & PF_JUMPED)
						and (player.cmd.buttons & BT_SPIN)
						and not (player.exiting)
						P_SpawnSkidDust(player, 15*FRACUNIT, sfx_none)

						//Capture incoming momentum BEFORE we zero it
						local in_momx = player.mo.momx
						local in_momy = player.mo.momy
						local hspeed  = WJ_AproxDist(in_momx, in_momy)

						//Convert HALF of that horizontal speed into upward speed, then multiply it.
						//Time scales naturally because we decay walljump_upmom every tic until it hits 0.
						local upmom = 0
						if (hspeed > 0)
							local half = FixedDiv(hspeed, 2*FRACUNIT)
							upmom = FixedMul(half, WALLJUMP_LIFT_MULT)

							//Clamp + minimum so normal speeds still produce noticeable lift
							if (upmom < WALLJUMP_UP_MINMOM)
								upmom = WALLJUMP_UP_MINMOM
							end
							if (upmom > WALLJUMP_UP_MAXMOM)
								upmom = WALLJUMP_UP_MAXMOM
							end
						end

						player.walljump_upmom   = upmom
						player.walljump_lockang = player.mo.angle

						//Kill drift so the lift doesn't dislodge you
						player.mo.momx = 0
						player.mo.momy = 0
						P_SetObjectMomZ(player.mo, 0)

						player.drawangle = player.mo.angle - ANGLE_180
						player.mo.state = S_PLAY_WALLJUMP
						player.cling = true

						-- CANCEL DROPDASH IF STARTING A WALL CLING
						-- (Paste right after: player.cling = true)

						-- Hard stop any active/charging dropdash visuals & flags
						player.mo.spritexscale = FRACUNIT
						player.mo.spriteyscale = FRACUNIT

						-- Core dropdash flags used by DropDash.lua
						player.mo.tyssondrpdsh  = 0   -- disables "dropdash mode"
						player.mo.tyssondrpdsh2 = 0   -- clears charge level
						player.mo.spindowndrpdh = 0   -- input gate used by dropdash
						player.mo.spindowndrpdh2 = 0  -- secondary gate

						-- Make sure we aren't considered spinning anymore
						player.pflags = $ & ~PF_SPINNING

						-- If we were in a dropdash state, force back to the stun you already set
						if player.mo.state == S_PLAY_JUNIOWALLRUN1
						or player.mo.state == S_PLAY_TYSSONDROPDASH
						or player.mo.state == S_PLAY_TYSSONDROPDASH_END
						or player.mo.state == S_PLAY_TYSSONROLL
							player.mo.state = S_PLAY_STUN
						end

						if (player.clingprev == false)
							S_StartSound(player.mo,sfx_s3k4a)
						end
						if not (S_SoundPlaying(player.mo,sfx_s3k7e))
							S_StartSound(player.mo, sfx_s3k7e)
						end
					end
				end			

				//While clinging (and SPIN is held), HARD-LOCK to the wall and drive the up/down slide ourselves.
				if (player.cling) and (player.cmd.buttons & BT_SPIN)
					P_SpawnSkidDust(player, 15*FRACUNIT, sfx_none)
					local downsign = -1
					if (player.mo.flags2 & MF2_OBJECTFLIP)
						downsign = 1
					end

					if (player.walljump_lockang == nil)
						player.walljump_lockang = player.mo.angle
					end

					//Never allow other states/physics to kick us out while spin is held
					if (player.mo.state ~= S_PLAY_WALLJUMP)
						player.mo.state = S_PLAY_WALLJUMP
					end

					player.drawangle = player.walljump_lockang - ANGLE_180

					//Pin into the wall every tic
					player.mo.momx = 0
					player.mo.momy = 0
					P_InstaThrust(player.mo, player.walljump_lockang, WALLJUMP_WALLPUSH)

					//Up-slide uses remaining "up momentum", which decays each tic.
					//This makes BOTH height and duration scale with the speed you had on impact.
					if (player.walljump_upmom and player.walljump_upmom > 0)
						P_SetObjectMomZ(player.mo, -downsign*player.walljump_upmom)

						player.walljump_upmom = $1 - WALLJUMP_UP_DECEL
						if (player.walljump_upmom < 0)
							player.walljump_upmom = 0
						end
					else
						P_SetObjectMomZ(player.mo, downsign*WALLJUMP_SLIDEDOWN)
					end
				end

				player.clingprev = player.cling	
			end		
		end
end)
