Custom Camera's not allowing the player to climb down a ladder

Hello developers! I’ve worked on a custom “Over-The-Shoulder” camera for my game. However, I have an issue. I can’t seem to climb down a ladder or truss when I’m in this scriptable camera mode. How do I fix this?

I’ve tried switching back to the default Roblox-powered camera, and it works perfectly. However, I want to achieve the player climbing down a ladder with my custom camera.

VIDEO:

External Media

2025-08-09 18-56-02

CODE:

function CameraHandler.OverTheShoulderCamera()
    -- Basic player assets.
    local Player = Players.LocalPlayer
    local Character = Player.Character or Player.CharacterAdded:Wait()
    local Humanoid = Character:FindFirstChildOfClass("Humanoid")
    local CurrentCamera = game.Workspace.CurrentCamera
    
    -- Reset look angles
    local y, x, _ : CFrame = workspace.CurrentCamera.CFrame:ToOrientation()
    CameraHandler.CameraSettings.cameraAngles.AngleX = math.deg(x)
    CameraHandler.CameraSettings.cameraAngles.AngleY = math.deg(y)

    UserInputService.MouseBehavior = Enum.MouseBehavior.LockCenter
    CurrentCamera.CameraType = Enum.CameraType.Scriptable

    Humanoid.AutoRotate = false
    
    local cameraConnection = nil
    cameraConnection = RunService.Stepped:Connect(function(t: number, dT: number)
        if Humanoid.Health == 0 then 
            CameraHandler.CameraSettings.CameraStates.OverTheShoulder = false
            CameraHandler.CameraSettings.CameraStates.Default = true
            
            CameraHandler.Reset(true)
            cameraConnection:Disconnect() 
        end
        
        -- OTS camera system.
        local startCFrame : CFrame = CFrame.new(Character.HumanoidRootPart.Position) * CFrame.Angles(0, math.rad(CameraHandler.CameraSettings.cameraAngles.AngleX), 0) * CFrame.Angles(math.rad(CameraHandler.CameraSettings.cameraAngles.AngleY), 0, 0)

        -- If the camera state is default, then we'll use the default camera offset.
        if CameraHandler.CameraSettings.CameraStates["Default"] then
            CameraHandler.CameraSettings.cameraOffset = CameraHandler.CameraSettings.defaultCameraPosition
            CameraHandler.CameraSettings.cameraTransitionNumber += dT
            
            if CameraHandler.CameraSettings.cameraTransitionNumber > 0.2 then
                CameraHandler.Reset(true)
                cameraConnection:Disconnect()
            end
        end

        -- OTS camera CFrame values.
        local cameraCFrame : Vector3 = startCFrame:PointToWorldSpace(CameraHandler.CameraSettings.cameraOffset)
        local cameraFocus : Vector3 = startCFrame:PointToWorldSpace(Vector3.new(CameraHandler.CameraSettings.cameraOffset.X, CameraHandler.CameraSettings.cameraOffset.Y, -10000))
        local resultingCameraCFrame : CFrame = CFrame.new(cameraCFrame, cameraFocus)
        
        -- Side-To-Side camera system.
        if CameraHandler.CameraSettings.isFirearmCameraRight then
            CameraHandler.CameraSettings.cameraOffset = CameraHandler.CameraSettings.cameraOffsetRight
            if Movement.GetState()["Crouching"] then
                CameraHandler.CameraSettings.cameraOffset = CameraHandler.CameraSettings.cameraOffsetRightCrouching
            end
        else
            CameraHandler.CameraSettings.cameraOffset = CameraHandler.CameraSettings.cameraOffsetLeft
            if Movement.GetState()["Crouching"] then
                CameraHandler.CameraSettings.cameraOffset = CameraHandler.CameraSettings.cameraOffsetLeftCrouching
            end
        end

        -- Create raycast parameters for the camera.
        local cameraRaycastParameters = RaycastParams.new()
        cameraRaycastParameters.FilterType = Enum.RaycastFilterType.Exclude
        cameraRaycastParameters.IgnoreWater = true
        cameraRaycastParameters.FilterDescendantsInstances = {Character}

        -- Cast a ray behind the player's camera to avoid seeing through parts.
        local cameraRaycastResult = game.Workspace:Raycast(Character:GetPivot().Position, -resultingCameraCFrame.LookVector * 6, cameraRaycastParameters)

        -- Cast a ray above the player's camera to avoid seeing through parts.
        local cameraUpRaycastResult = game.Workspace:Raycast(Character:GetPivot().Position, resultingCameraCFrame.UpVector * 10, cameraRaycastParameters)

        -- Cast a ray going left to right the player's camera to avoid seeing through parts.
        local sideViewCameraCFrame = resultingCameraCFrame.RightVector * 6
        if not CameraHandler.CameraSettings.isFirearmCameraRight then sideViewCameraCFrame = -resultingCameraCFrame.RightVector * 6 end
        local cameraSideRaycastResult = game.Workspace:Raycast(Character:GetPivot().Position, sideViewCameraCFrame, cameraRaycastParameters)

        -- Determine if the ray touches any parts.
        if not CameraHandler.CameraSettings.CameraStates.Default then
            if cameraRaycastResult then
                local backDistance = (Character:GetPivot().Position - cameraRaycastResult.Position).Magnitude
                
                if backDistance < 5.5 then
                    CameraHandler.CameraSettings.cameraOffset = Vector3.new(0, CameraHandler.CameraSettings.cameraOffset.Y, 0)
                end
                
                cameraCFrame = startCFrame:PointToWorldSpace(Vector3.new(CameraHandler.CameraSettings.cameraOffset.X, CameraHandler.CameraSettings.cameraOffset.Y, backDistance))
                resultingCameraCFrame = CFrame.new(cameraCFrame, cameraFocus)
            elseif cameraSideRaycastResult then
                local sideDistance = (Character:GetPivot().Position - cameraSideRaycastResult.Position).Magnitude
                if sideDistance < 2.5 then
                    CameraHandler.CameraSettings.cameraOffset = Vector3.new(0, CameraHandler.CameraSettings.cameraOffset.Y, 0)
                end
                cameraCFrame = startCFrame:PointToWorldSpace(Vector3.new(CameraHandler.CameraSettings.cameraOffset.X, CameraHandler.CameraSettings.cameraOffset.Y, sideDistance))
                resultingCameraCFrame = CFrame.new(cameraCFrame, cameraFocus)
            elseif cameraUpRaycastResult then
                local upDistance = (Character:GetPivot().Position - cameraUpRaycastResult.Position).Magnitude
                cameraCFrame = startCFrame:PointToWorldSpace(Vector3.new(CameraHandler.CameraSettings.cameraOffset.X, CameraHandler.CameraSettings.cameraOffset.Y, upDistance))
                resultingCameraCFrame = CFrame.new(cameraCFrame, cameraFocus)
            end
        end

        -- Root Part movement.
        local RootPart : BasePart = Character.HumanoidRootPart
        local lookingCFrame : Vector3 = CurrentCamera.CFrame:PointToWorldSpace(Vector3.new(0, 0, -10000))
        local rootLookDirection : Vector3 = (lookingCFrame - RootPart.CFrame.Position)
        local rootYaw : number = math.atan2(-rootLookDirection.X, -rootLookDirection.Z)
        
        if CameraHandler.CameraSettings.CameraStates["OverTheShoulder"] then
            RootPart.CFrame = CFrame.new(RootPart.CFrame.Position) * CFrame.Angles(0, rootYaw, 0)
        end

        -- Setup camera movement.
        local cameraAlpha = Interpolate:Exponential(dT, 2)
        CurrentCamera.CFrame = CurrentCamera.CFrame:Lerp(resultingCameraCFrame, cameraAlpha)
        
    end)
end

I’ve tried messing with the camera modes and some code, but nothing seemed to work.