Загрузка данных


--[[
    REALISTIC FPS + SPRINT + STAMINA EFFECTS
    Место: StarterPlayer > StarterPlayerScripts
    Тип: LocalScript
    F       - переключить первое / третье лицо
    CTRL    - удерживать для бега
    Возможности:
    - FOV 90 / 120
    - плавное ускорение и замедление
    - покачивание камеры при ходьбе и беге
    - инерция камеры при поворотах
    - видимое тело в первом лице
    - усталость после 90 секунд бега
    - усиление эффектов к 120 секундам
    - лёгкое затемнение при сильной усталости
    - дыхательный эффект через визуальную пульсацию
]]
--==================================================
-- SERVICES
--==================================================
local Players = game:GetService("Players")
local UserInputService = game:GetService("UserInputService")
local RunService = game:GetService("RunService")
local TweenService = game:GetService("TweenService")
local player = Players.LocalPlayer
local playerGui = player:WaitForChild("PlayerGui")
--==================================================
-- SETTINGS
--==================================================
local SETTINGS = {
    -- Управление
    ToggleCameraKey = Enum.KeyCode.F,
    SprintKey = Enum.KeyCode.LeftControl,
    -- Скорость
    WalkSpeed = 16,
    SprintSpeed = 24,
    -- Камера
    WalkFOV = 90,
    SprintFOV = 120,
    FOVSmoothness = 7,
    ThirdPersonMinZoom = 8,
    ThirdPersonMaxZoom = 12,
    -- Покачивание при ходьбе
    WalkBobFrequency = 8,
    WalkBobX = 0.055,
    WalkBobY = 0.075,
    WalkBobRotation = 0.45,
    -- Покачивание при беге
    SprintBobFrequency = 12,
    SprintBobX = 0.085,
    SprintBobY = 0.13,
    SprintBobRotation = 0.8,
    -- Инерция
    TurnSwayAmount = 0.8,
    TurnSwaySmoothness = 8,
    MovementTilt = 1.1,
    -- Усталость
    FatigueStart = 90,
    FatigueMaximum = 120,
    FatigueBobMultiplier = 1.8,
    FatigueFOVReduction = 5,
    -- Затемнение
    DarknessStart = 110,
    MaximumDarkness = 0.18,
    -- Скорость переходов
    CameraSmoothness = 8,
}
--==================================================
-- CHARACTER
--==================================================
local character
local humanoid
local rootPart
local head
local sprintHeld = false
local isFirstPerson = true
local isExhausted = false
local sprintDuration = 0
local bobTime = 0
local currentFOV = SETTINGS.WalkFOV
local currentTurnSway = 0
local currentMoveTilt = 0
local lastCameraYaw = nil
local currentCamera = workspace.CurrentCamera
local characterConnections = {}
--==================================================
-- GUI: FATIGUE DARKENING
--==================================================
local oldGui = playerGui:FindFirstChild("RealisticCameraEffects")
if oldGui then
    oldGui:Destroy()
end
local effectGui = Instance.new("ScreenGui")
effectGui.Name = "RealisticCameraEffects"
effectGui.IgnoreGuiInset = true
effectGui.ResetOnSpawn = false
effectGui.DisplayOrder = 100
effectGui.Parent = playerGui
local darkness = Instance.new("Frame")
darkness.Name = "FatigueDarkness"
darkness.Size = UDim2.fromScale(1, 1)
darkness.Position = UDim2.fromScale(0, 0)
darkness.BorderSizePixel = 0
darkness.BackgroundColor3 = Color3.fromRGB(0, 0, 0)
darkness.BackgroundTransparency = 1
darkness.Parent = effectGui
--==================================================
-- CAMERA SETTINGS
--==================================================
local function updateCameraMode()
    currentCamera = workspace.CurrentCamera
    if not currentCamera then
        return
    end
    if isFirstPerson then
        player.CameraMode = Enum.CameraMode.LockFirstPerson
        player.CameraMinZoomDistance = 0.5
        player.CameraMaxZoomDistance = 0.5
    else
        player.CameraMode = Enum.CameraMode.Classic
        player.CameraMinZoomDistance = SETTINGS.ThirdPersonMinZoom
        player.CameraMaxZoomDistance = SETTINGS.ThirdPersonMaxZoom
    end
    if humanoid then
        currentCamera.CameraType = Enum.CameraType.Custom
        currentCamera.CameraSubject = humanoid
    end
end
local function toggleCamera()
    isFirstPerson = not isFirstPerson
    updateCameraMode()
end
--==================================================
-- BODY VISIBILITY IN FIRST PERSON
--==================================================
local function updateBodyVisibility()
    if not character then
        return
    end
    for _, object in ipairs(character:GetDescendants()) do
        if object:IsA("BasePart") then
            if isFirstPerson then
                -- Голова скрыта, чтобы не перекрывать обзор.
                -- Остальные части тела остаются видимыми.
                if object == head then
                    object.LocalTransparencyModifier = 1
                elseif object.Name == "HumanoidRootPart" then
                    object.LocalTransparencyModifier = 1
                elseif object.Parent and object.Parent:IsA("Accessory") then
                    local handle = object.Parent:FindFirstChild("Handle")
                    if handle == object then
                        local accessoryWeld =
                            object:FindFirstChildWhichIsA("Weld")
                            or object:FindFirstChildWhichIsA("WeldConstraint")
                        local attachedToHead = false
                        if accessoryWeld then
                            local part0 = accessoryWeld.Part0
                            local part1 = accessoryWeld.Part1
                            attachedToHead =
                                part0 == head or part1 == head
                        end
                        object.LocalTransparencyModifier =
                            attachedToHead and 1 or 0
                    else
                        object.LocalTransparencyModifier = 0
                    end
                else
                    object.LocalTransparencyModifier = 0
                end
            else
                -- В третьем лице возвращаем обычную видимость.
                object.LocalTransparencyModifier = 0
            end
        end
    end
end
--==================================================
-- CHARACTER INITIALIZATION
--==================================================
local function disconnectCharacterConnections()
    for _, connection in ipairs(characterConnections) do
        connection:Disconnect()
    end
    table.clear(characterConnections)
end
local function setupCharacter(newCharacter)
    disconnectCharacterConnections()
    character = newCharacter
    humanoid = character:WaitForChild("Humanoid")
    rootPart = character:WaitForChild("HumanoidRootPart")
    head = character:WaitForChild("Head")
    sprintHeld = false
    sprintDuration = 0
    bobTime = 0
    isExhausted = false
    currentTurnSway = 0
    currentMoveTilt = 0
    lastCameraYaw = nil
    humanoid.WalkSpeed = SETTINGS.WalkSpeed
    updateCameraMode()
    updateBodyVisibility()
    table.insert(
        characterConnections,
        character.DescendantAdded:Connect(function(object)
            if object:IsA("BasePart") then
                task.defer(updateBodyVisibility)
            end
        end)
    )
    table.insert(
        characterConnections,
        humanoid.Died:Connect(function()
            sprintHeld = false
            sprintDuration = 0
            darkness.BackgroundTransparency = 1
        end)
    )
end
player.CharacterAdded:Connect(setupCharacter)
if player.Character then
    task.spawn(setupCharacter, player.Character)
end
--==================================================
-- INPUT
--==================================================
UserInputService.InputBegan:Connect(function(input, gameProcessed)
    if gameProcessed then
        return
    end
    if input.KeyCode == SETTINGS.ToggleCameraKey then
        toggleCamera()
        updateBodyVisibility()
    elseif input.KeyCode == SETTINGS.SprintKey then
        sprintHeld = true
    end
end)
UserInputService.InputEnded:Connect(function(input)
    if input.KeyCode == SETTINGS.SprintKey then
        sprintHeld = false
    end
end)
-- Если окно потеряло фокус, не оставляем бег включённым.
UserInputService.WindowFocusReleased:Connect(function()
    sprintHeld = false
end)
--==================================================
-- CAMERA HELPERS
--==================================================
local function smooth(current, target, speed, deltaTime)
    local alpha = 1 - math.exp(-speed * deltaTime)
    return current + (target - current) * alpha
end
local function getHorizontalVelocity()
    if not rootPart then
        return 0
    end
    local velocity = rootPart.AssemblyLinearVelocity
    return Vector3.new(velocity.X, 0, velocity.Z).Magnitude
end
local function getCameraYaw(camera)
    local look = camera.CFrame.LookVector
    return math.atan2(-look.X, -look.Z)
end
local function wrapAngle(angle)
    return math.atan2(math.sin(angle), math.cos(angle))
end
--==================================================
-- MAIN UPDATE
--==================================================
RunService:BindToRenderStep(
    "RealisticCameraEffects",
    Enum.RenderPriority.Camera.Value + 1,
    function(deltaTime)
        if not character or not humanoid or not rootPart then
            return
        end
        if humanoid.Health <= 0 then
            return
        end
        currentCamera = workspace.CurrentCamera
        if not currentCamera then
            return
        end
        -- Не позволяем удерживаемому Ctrl оставаться активным
        -- после смерти персонажа или отпускания клавиши.
        local moving =
            humanoid.MoveDirection.Magnitude > 0.05
            and getHorizontalVelocity() > 0.5
        local actuallySprinting =
            sprintHeld and moving and humanoid.Health > 0
        ------------------------------------------------
        -- SPEED
        ------------------------------------------------
        if actuallySprinting then
            humanoid.WalkSpeed = SETTINGS.SprintSpeed
            sprintDuration += deltaTime
        else
            humanoid.WalkSpeed = SETTINGS.WalkSpeed
            sprintDuration = 0
        end
        ------------------------------------------------
        -- FATIGUE
        ------------------------------------------------
        local fatigue = 0
        if sprintDuration >= SETTINGS.FatigueStart then
            fatigue = math.clamp(
                (sprintDuration - SETTINGS.FatigueStart)
                / (SETTINGS.FatigueMaximum - SETTINGS.FatigueStart),
                0,
                1
            )
        end
        isExhausted = sprintDuration >= SETTINGS.FatigueMaximum
        ------------------------------------------------
        -- FOV
        ------------------------------------------------
        local targetFOV = SETTINGS.WalkFOV
        if actuallySprinting then
            targetFOV = SETTINGS.SprintFOV
        end
        -- При сильной усталости угол обзора немного уменьшается.
        targetFOV -= fatigue * SETTINGS.FatigueFOVReduction
        currentFOV = smooth(
            currentFOV,
            targetFOV,
            SETTINGS.FOVSmoothness,
            deltaTime
        )
        currentCamera.FieldOfView = currentFOV
        ------------------------------------------------
        -- DARKENING
        ------------------------------------------------
        local darknessAmount = 0
        if sprintDuration >= SETTINGS.DarknessStart then
            darknessAmount = math.clamp(
                (sprintDuration - SETTINGS.DarknessStart)
                / (SETTINGS.FatigueMaximum - SETTINGS.DarknessStart),
                0,
                1
            ) * SETTINGS.MaximumDarkness
        end
        -- Небольшая пульсация при сильной усталости.
        local breathingPulse = 0
        if fatigue > 0 then
            breathingPulse =
                math.sin(os.clock() * (2.2 + fatigue * 1.5))
                * 0.012
                * fatigue
        end
        darkness.BackgroundTransparency = math.clamp(
            1 - darknessAmount + breathingPulse,
            1 - SETTINGS.MaximumDarkness,
            1
        )
        ------------------------------------------------
        -- BODY VISIBILITY
        ------------------------------------------------
        updateBodyVisibility()
        ------------------------------------------------
        -- REALISTIC CAMERA MOTION
        ------------------------------------------------
        local speed = getHorizontalVelocity()
        local movementAmount = math.clamp(
            speed / math.max(SETTINGS.SprintSpeed, 1),
            0,
            1
        )
        local bobX = 0
        local bobY = 0
        local bobRoll = 0
        if moving then
            local frequency
            local amountX
            local amountY
            local amountRotation
            if actuallySprinting then
                frequency = SETTINGS.SprintBobFrequency
                amountX = SETTINGS.SprintBobX
                amountY = SETTINGS.SprintBobY
                amountRotation = SETTINGS.SprintBobRotation
            else
                frequency = SETTINGS.WalkBobFrequency
                amountX = SETTINGS.WalkBobX
                amountY = SETTINGS.WalkBobY
                amountRotation = SETTINGS.WalkBobRotation
            end
            -- Усталость постепенно усиливает движения камеры.
            local fatigueMultiplier =
                1 + fatigue * (SETTINGS.FatigueBobMultiplier - 1)
            bobTime += deltaTime * frequency
            bobX =
                math.cos(bobTime * 0.5)
                * amountX
                * movementAmount
                * fatigueMultiplier
            bobY =
                math.abs(math.sin(bobTime))
                * amountY
                * movementAmount
                * fatigueMultiplier
            bobRoll =
                math.sin(bobTime * 0.5)
                * math.rad(amountRotation)
                * movementAmount
                * fatigueMultiplier
        else
            -- Не сбрасываем движение камеры мгновенно.
            bobX = 0
            bobY = 0
            bobRoll = 0
        end
        ------------------------------------------------
        -- TURN INERTIA
        ------------------------------------------------
        local yaw = getCameraYaw(currentCamera)
        local yawDelta = 0
        if lastCameraYaw then
            yawDelta = wrapAngle(yaw - lastCameraYaw)
        end
        lastCameraYaw = yaw
        local targetTurnSway = math.clamp(
            -yawDelta * SETTINGS.TurnSwayAmount * 60,
            -0.035,
            0.035
        )
        currentTurnSway = smooth(
            currentTurnSway,
            targetTurnSway,
            SETTINGS.TurnSwaySmoothness,
            deltaTime
        )
        -- Небольшой наклон при движении в стороны.
        local localMove =
            currentCamera.CFrame:VectorToObjectSpace(
                humanoid.MoveDirection
            )
        local targetMoveTilt =
            -localMove.X
            * math.rad(SETTINGS.MovementTilt)
        currentMoveTilt = smooth(
            currentMoveTilt,
            targetMoveTilt,
            SETTINGS.CameraSmoothness,
            deltaTime
        )
        ------------------------------------------------
        -- APPLY CAMERA OFFSET
        ------------------------------------------------
        -- Смещение применяется после стандартной камеры Roblox.
        -- Мы не меняем CameraSubject и не блокируем управление мышью.
        local fatigueBreathY = 0
        if fatigue > 0 then
            fatigueBreathY =
                math.sin(os.clock() * 2.5)
                * 0.015
                * fatigue
        end
        local cameraOffset = CFrame.new(
            bobX + currentTurnSway,
            -bobY + fatigueBreathY,
            0
        ) * CFrame.Angles(
            0,
            0,
            bobRoll + currentMoveTilt
        )
        currentCamera.CFrame = currentCamera.CFrame * cameraOffset
    end
)
--==================================================
-- KEEP CAMERA MODE VALID
--==================================================
workspace:GetPropertyChangedSignal("CurrentCamera"):Connect(function()
    currentCamera = workspace.CurrentCamera
    if currentCamera and humanoid then
        currentCamera.CameraType = Enum.CameraType.Custom
        currentCamera.CameraSubject = humanoid
    end
end)
--==================================================
-- CLEANUP
--==================================================
script.Destroying:Connect(function()
    RunService:UnbindFromRenderStep("RealisticCameraEffects")
    disconnectCharacterConnections()
    if humanoid and humanoid.Parent then
        humanoid.WalkSpeed = SETTINGS.WalkSpeed
    end
    player.CameraMode = Enum.CameraMode.Classic
    player.CameraMinZoomDistance = 0.5
    player.CameraMaxZoomDistance = 12
end)