script:

local UserInputService = game:GetService("UserInputService")
local ContextActionService = game:GetService("ContextActionService")
local RunService = game:GetService("RunService")
local TweenService = game:GetService("TweenService")
local Debris = game:GetService("Debris")
local GuiService = game:GetService("GuiService")

local player = game.Players.LocalPlayer
local character = player.Character or player.CharacterAdded:Wait()
local camera = workspace.CurrentCamera
local humanoid = character:WaitForChild("Humanoid")
local rootPart = character:WaitForChild("HumanoidRootPart")
local head = character:WaitForChild("Head")

local grabAnim = humanoid:LoadAnimation(game:GetService("ReplicatedStorage"):WaitForChild("Animations"):WaitForChild("Player"):WaitForChild("Grab"))

local raycastParams = RaycastParams.new()
raycastParams.FilterDescendantsInstances = {character}
raycastParams.FilterType = Enum.RaycastFilterType.Exclude

local vaultMoveNumber = 10
local canVault = true
local canMove = true
local vaultConnection = nil

local function partCheck(ledge)
	local vaultPartCheck = workspace:Raycast(ledge.Position + Vector3.new(0, -1, 0) + ledge.LookVector * 1, ledge.UpVector * 3, raycastParams)
	return vaultPartCheck == nil
end

local function vaultMove(direction)
	local headCFrame, headRightVector, headLookVector = head.CFrame, head.CFrame.RightVector, head.CFrame.LookVector
	local moveRay = workspace:Raycast(head.Position, headRightVector * direction + headLookVector * 8, raycastParams)

	if moveRay or workspace:Raycast(head.Position + Vector3.new(0, -1, 0) + headRightVector * direction, headRightVector * -direction + headLookVector * 2, raycastParams) then
	end
end

local function onMoveDirectionChanged()
	if not canMove then return end
	local moveDirection = humanoid.MoveDirection
	local cameraRightVector = camera.CFrame.RightVector
	if moveDirection:Dot(cameraRightVector) > .7 and not canVault then
		vaultMove(vaultMoveNumber)
	elseif moveDirection:Dot(-cameraRightVector) > .7 and not canVault then
		vaultMove(-vaultMoveNumber)
	end
end

local function createLedgePart(ledgeOffset)
	local ledgePart = Instance.new("Part")
	ledgePart.Parent = workspace
	ledgePart.Anchored = true
	ledgePart.Size = Vector3.one
	ledgePart.CFrame = ledgeOffset + Vector3.new(0, -2, 0) + ledgeOffset.LookVector * -1
	ledgePart.CanQuery = false
	ledgePart.CanCollide = false
	ledgePart.CanTouch = false
	ledgePart.Transparency = 1
	return ledgePart
end

local function performVault(ledgePart, ledgeOffset)
	canVault = false
	canMove = false
	
	grabAnim:Play()
	
	vaultConnection = RunService.RenderStepped:Connect(function(dt)
		rootPart.Anchored = true
		humanoid.AutoRotate = false
		rootPart.CFrame = rootPart.CFrame:Lerp(CFrame.lookAt(ledgePart.Position, (ledgePart.CFrame * CFrame.new(0, 0, -1)).Position), .25)
		humanoid:ChangeState(Enum.HumanoidStateType.Seated)
	end)
end

local function detectLedge()
	if (not canVault) or (humanoid:GetState() ~= Enum.HumanoidStateType.Freefall and humanoid:GetState() ~= Enum.HumanoidStateType.Jumping) then
		canVault = true
		canMove = true
		humanoid.AutoRotate = true
		rootPart.Anchored = false
		humanoid:ChangeState(Enum.HumanoidStateType.Jumping)

		if vaultConnection then
			vaultConnection:Disconnect()
		end
		if grabAnim.IsPlaying then
			grabAnim:Stop()
		end
		return
	end

	local vaultCheck = workspace:Raycast(rootPart.CFrame.Position, rootPart.CFrame.LookVector * 5, raycastParams)
	if not vaultCheck or not vaultCheck.Instance then return end

	local localPos = vaultCheck.Instance.CFrame:PointToObjectSpace(vaultCheck.Position)
	local localLedgePos = Vector3.new(localPos.X, vaultCheck.Instance.Size.Y / 2, localPos.Z)
	local ledgePos = vaultCheck.Instance.CFrame:PointToWorldSpace(localLedgePos)
	local ledgeOffset = CFrame.lookAt(ledgePos, ledgePos - vaultCheck.Normal)

	local magnitude = (ledgePos - head.Position).Magnitude
	if magnitude >= 6 then return end
	if not partCheck(ledgeOffset) then return end

	local ledgePart = createLedgePart(ledgeOffset)
	performVault(ledgePart, ledgeOffset)
end

local function setupInputHandlers()
	UserInputService.InputBegan:Connect(function(input, gp)
		if input.KeyCode == Enum.KeyCode.ButtonA or input.KeyCode == Enum.KeyCode.Space then
			detectLedge()
		end
	end)

	if UserInputService.TouchEnabled and not UserInputService.KeyboardEnabled and not UserInputService.MouseEnabled and not UserInputService.GamepadEnabled and not GuiService:IsTenFootInterface() then
		local jumpButton = player.PlayerGui:WaitForChild("TouchGui"):WaitForChild("TouchControlFrame"):WaitForChild("JumpButton")
		jumpButton.Activated:Connect(detectLedge)
	end
end

humanoid:GetPropertyChangedSignal("MoveDirection"):Connect(onMoveDirectionChanged)
setupInputHandlers()


module:

local StaminaManager = {}
local RunService = game:GetService("RunService")

StaminaManager.stamina = 100
StaminaManager.maxStamina = 100
local minStamina = 0
local lastStaminaUseTime = 0
local staminaRegenDelay = 0.5
local staminaRegenRate = 30


function StaminaManager.GetStamina()
	return StaminaManager.stamina
end


function StaminaManager.AddStamina(amount)
	local currentTime = tick()
	if currentTime - lastStaminaUseTime >= staminaRegenDelay then
		StaminaManager.stamina = math.min(StaminaManager.maxStamina, StaminaManager.stamina + amount)
	end
end


function StaminaManager.ReduceStamina(amount)
	if StaminaManager.stamina >= amount then
		StaminaManager.stamina = math.max(minStamina, StaminaManager.stamina - amount)
		lastStaminaUseTime = tick()
		return true
	end
	return false
end


RunService.Heartbeat:Connect(function(deltaTime)
	StaminaManager.AddStamina(staminaRegenRate * deltaTime)
end)

return StaminaManager


while the player is holding on an ledge, his stamina should slowly drain. if his stamina reaches 10 while grabbing on a ledge, he shouldn't be able to jump anymore. if his stamina reaches 0 while grabbing on a ledge, he should get off.