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


local Players = game:GetService("Players")
local RunService = game:GetService("RunService")
local UserInputService = game:GetService("UserInputService")
local Lighting = game:GetService("Lighting")

-- ================= НАСТРОЙКИ =================
local Settings = {
	-- Позиция камеры
	CameraHeight = -0.25,  -- высота: больше = выше, меньше = ниже
	CameraBack = -1.5,     -- вперёд/назад: минус = вперёд, плюс = назад
	CameraSide = 0,        -- влево/вправо: минус = влево, плюс = вправо

	-- FOV (максимум в Roblox = 120)
	IdleFOV = 90,          -- когда стоишь
	WalkFOV = 100,         -- при ходьбе
	SprintFOV = 120,       -- при беге
	FOVSmoothing = 6,      -- плавность смены FOV (больше = быстрее)

	-- Движение
	WalkSpeed = 16,
	SprintSpeed = 26,
	SprintKey = Enum.KeyCode.LeftShift,         -- бег
	ToggleCameraKey = Enum.KeyCode.F,           -- выход из / вход в режим камеры

	-- Покачивание при ходьбе и беге
	BobEnabled = true,
	BobVertical = 0.28,
	BobHorizontal = 0.2,
	BobSpeed = 0.45,
	BobRoll = 0.035,
	BobPitch = 0.025,
	SprintBobMultiplier = 1.8,
	BobSmoothing = 10,

	-- Покачивание в покое (дыхание)
	IdleEnabled = true,
	IdleVertical = 0.03,
	IdleHorizontal = 0.015,
	IdleRotation = 0.004,
	IdleSpeed = 1.1,

	-- Усталость от долгого бега
	FatigueEnabled = true,
	BlurAfter = 30,        -- через сколько секунд бега появляется размытие
	BlurDuration = 10,     -- сколько оно длится
	BlurMaxSize = 8,
	DimAfter = 60,         -- через сколько секунд бега появляется затемнение
	DimDuration = 30,      -- сколько оно длится
	DimBrightness = -0.2,
	DimSaturation = -0.35,

	-- Третье лицо (после нажатия F)
	ThirdPersonMinZoom = 10,
	ThirdPersonMaxZoom = 400,

	-- Видимость тела
	ViewAccessories = false,
	ViewModels = false,
	HeadLocalTransparency = 1,
	HiddenParts = { "UpperTorso", "LeftUpperArm", "RightUpperArm" },
}
-- =============================================

local player = Players.LocalPlayer
local camera = workspace.CurrentCamera

local character, humanoid, root
local firstPerson = true
local sprinting = false
local bobTime = 0
local idleTime = 0
local bobOffset = Vector3.zero
local moveAlpha = 0
local locked = {}

-- Эффекты усталости
for _, name in ipairs({ "FatigueBlur", "FatigueDim" }) do
	local old = Lighting:FindFirstChild(name)
	if old then old:Destroy() end
end

local blur = Instance.new("BlurEffect")
blur.Name = "FatigueBlur"
blur.Size = 0
blur.Parent = Lighting

local dim = Instance.new("ColorCorrectionEffect")
dim.Name = "FatigueDim"
dim.Parent = Lighting

local sprintTime = 0
local blurT = nil
local dimT = nil
local blurFired = false

local function envelope(t, duration, fadeIn, fadeOut)
	return math.clamp(t / fadeIn, 0, 1) * math.clamp((duration - t) / fadeOut, 0, 1)
end

local function resetFatigue()
	sprintTime = 0
	blurT = nil
	dimT = nil
	blurFired = false
	blur.Size = 0
	dim.Brightness = 0
	dim.Saturation = 0
end

local function classify(part)
	if not part:IsA("BasePart") or not character then return nil end
	if part.Name == "Head" and part.Parent == character then
		return Settings.HeadLocalTransparency
	end
	if part:FindFirstAncestorOfClass("Accessory") then
		return Settings.ViewAccessories and 0 or nil
	end
	if part.Parent == character then
		if table.find(Settings.HiddenParts, part.Name) then
			return 1
		end
		return 0
	end
	if part:FindFirstAncestorOfClass("Model") ~= character and Settings.ViewModels then
		return 0
	end
	return nil
end

local function register(part)
	local value = classify(part)
	if value ~= nil then
		locked[part] = value
	end
end

local function applyMode()
	if not humanoid then return end
	if firstPerson 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
		humanoid.CameraOffset = Vector3.zero
		camera.FieldOfView = 70
		for part in pairs(locked) do
			part.LocalTransparencyModifier = 0
		end
	end
end

local function setupCharacter(char)
	character = char
	humanoid = char:WaitForChild("Humanoid")
	root = char:WaitForChild("HumanoidRootPart")
	locked = {}
	sprinting = false
	bobTime = 0
	bobOffset = Vector3.zero
	moveAlpha = 0
	resetFatigue()

	humanoid.WalkSpeed = Settings.WalkSpeed

	for _, d in ipairs(char:GetDescendants()) do
		register(d)
	end
	char.DescendantAdded:Connect(register)
	char.DescendantRemoving:Connect(function(d)
		locked[d] = nil
	end)

	applyMode()
end

-- Бег и переключение камеры
UserInputService.InputBegan:Connect(function(input, processed)
	if processed or not humanoid then return end

	if input.KeyCode == Settings.SprintKey then
		sprinting = true
		humanoid.WalkSpeed = Settings.SprintSpeed
	elseif input.KeyCode == Settings.ToggleCameraKey then
		firstPerson = not firstPerson
		applyMode()
	end
end)

UserInputService.InputEnded:Connect(function(input)
	if input.KeyCode == Settings.SprintKey and humanoid then
		sprinting = false
		humanoid.WalkSpeed = Settings.WalkSpeed
	end
end)

-- Усталость
RunService.Heartbeat:Connect(function(dt)
	if not Settings.FatigueEnabled or not root or not humanoid then return end

	local speed = (root.AssemblyLinearVelocity * Vector3.new(1, 0, 1)).Magnitude
	local running = sprinting and speed > 10 and humanoid.Health > 0

	if running then
		sprintTime += dt
		if sprintTime >= Settings.BlurAfter and not blurFired then
			blurFired = true
			blurT = 0
		end
		if sprintTime >= Settings.DimAfter then
			dimT = 0
			sprintTime = 0
			blurFired = false
		end
	else
		sprintTime = math.max(0, sprintTime - dt * 2)
		if sprintTime < Settings.BlurAfter then
			blurFired = false
		end
	end

	if blurT then
		blurT += dt
		if blurT >= Settings.BlurDuration then
			blurT = nil
			blur.Size = 0
		else
			blur.Size = Settings.BlurMaxSize * envelope(blurT, Settings.BlurDuration, 1.5, 3)
		end
	end

	if dimT then
		dimT += dt
		if dimT >= Settings.DimDuration then
			dimT = nil
			dim.Brightness = 0
			dim.Saturation = 0
		else
			local e = envelope(dimT, Settings.DimDuration, 3, 6)
			dim.Brightness = Settings.DimBrightness * e
			dim.Saturation = Settings.DimSaturation * e
		end
	end
end)

-- Камера каждый кадр
RunService:BindToRenderStep("FirstPersonBody", Enum.RenderPriority.Camera.Value + 1, function(dt)
	if not humanoid or not root or humanoid.Health <= 0 or not firstPerson then return end

	for part, value in pairs(locked) do
		part.LocalTransparencyModifier = value
	end

	local speed = (root.AssemblyLinearVelocity * Vector3.new(1, 0, 1)).Magnitude
	local onGround = humanoid.FloorMaterial ~= Enum.Material.Air
	local moving = onGround and speed > 1 and humanoid.MoveDirection.Magnitude > 0

	moveAlpha += ((moving and 1 or 0) - moveAlpha) * math.clamp(dt * 8, 0, 1)

	local mult = sprinting and Settings.SprintBobMultiplier or 1
	local walkOffset = Vector3.zero
	local roll, pitch = 0, 0

	if Settings.BobEnabled and moving then
		bobTime += dt * speed * Settings.BobSpeed
	end
	if Settings.BobEnabled then
		walkOffset = Vector3.new(
			math.sin(bobTime) * Settings.BobHorizontal * mult,
			math.abs(math.sin(bobTime)) * Settings.BobVertical * mult - Settings.BobVertical * mult * 0.5,
			0
		) * moveAlpha
		roll = math.sin(bobTime) * Settings.BobRoll * mult * moveAlpha
		pitch = math.sin(bobTime * 2) * Settings.BobPitch * mult * moveAlpha
	end

	local idleOffset = Vector3.zero
	local idleRoll, idlePitch = 0, 0
	if Settings.IdleEnabled then
		idleTime += dt * Settings.IdleSpeed
		local idleAlpha = 1 - moveAlpha
		idleOffset = Vector3.new(
			math.sin(idleTime * 0.6) * Settings.IdleHorizontal,
			math.sin(idleTime) * Settings.IdleVertical,
			0
		) * idleAlpha
		idleRoll = math.sin(idleTime * 0.7) * Settings.IdleRotation * idleAlpha
		idlePitch = math.sin(idleTime * 0.9 + 1) * Settings.IdleRotation * idleAlpha
	end

	local base = Vector3.new(Settings.CameraSide, Settings.CameraHeight, Settings.CameraBack)
	local target = walkOffset + idleOffset
	bobOffset = bobOffset:Lerp(target, math.clamp(dt * Settings.BobSmoothing, 0, 1))
	humanoid.CameraOffset = base + bobOffset

	camera.CFrame = camera.CFrame * CFrame.Angles(pitch + idlePitch, 0, roll + idleRoll)

	-- FOV: стоишь / идёшь / бежишь
	local targetFov = Settings.IdleFOV
	if moving then
		targetFov = sprinting and Settings.SprintFOV or Settings.WalkFOV
	end
	targetFov = math.clamp(targetFov, 1, 120)
	camera.FieldOfView += (targetFov - camera.FieldOfView) * math.clamp(dt * Settings.FOVSmoothing, 0, 1)
end)

-- Камера не липнет к сиденьям
camera:GetPropertyChangedSignal("CameraSubject"):Connect(function()
	if humanoid and camera.CameraSubject and camera.CameraSubject:IsA("VehicleSeat") then
		camera.CameraSubject = humanoid
	end
end)

player.CharacterAdded:Connect(setupCharacter)
if player.Character then
	setupCharacter(player.Character)
end