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


local Players = game:GetService("Players")
local RunService = game:GetService("RunService")
local UserInputService = game:GetService("UserInputService")
local Lighting = game:GetService("Lighting")
local GuiService = game:GetService("GuiService")
local GameSettings = UserSettings():GetService("UserGameSettings")

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

	-- Мышка
	MouseSensitivity = 0.003, -- чувствительность (больше = быстрее)
	MaxDeltaPerFrame = 120,   -- ограничение рывка мышки за кадр (убирает срывы при тряске)
	MaxPitchDeg = 80,         -- максимум вверх / вниз в градусах

	-- FOV (максимум в Roblox = 120)
	IdleFOV = 90,
	WalkFOV = 100,
	SprintFOV = 120,
	FOVSmoothing = 6,

	-- Движение
	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 = 0.3,
	IdleDelay = 300,
	IdleFade = 5,

	-- Усталость от долгого бега
	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 = { "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 standTime = 0
local bobOffset = Vector3.zero
local moveAlpha = 0
local locked = {}
local yaw, pitch = 0, 0
local diedConn

-- Эффекты усталости
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

-- Отдаём камеру стандартной системе Roblox (третье лицо, смерть)
local function releaseCamera()
	camera.CameraType = Enum.CameraType.Custom
	if humanoid then
		camera.CameraSubject = humanoid
		humanoid.AutoRotate = true
		humanoid.CameraOffset = Vector3.zero
	end
	UserInputService.MouseBehavior = Enum.MouseBehavior.Default
	player.CameraMode = Enum.CameraMode.Classic
	player.CameraMinZoomDistance = Settings.ThirdPersonMinZoom
	player.CameraMaxZoomDistance = Settings.ThirdPersonMaxZoom
	camera.FieldOfView = 70
	for part in pairs(locked) do
		part.LocalTransparencyModifier = 0
	end
end

-- Забираем камеру себе (первое лицо)
local function takeCamera()
	if not humanoid or not root then return end
	local look = root.CFrame.LookVector
	yaw = math.atan2(-look.X, -look.Z)
	pitch = 0
	humanoid.CameraOffset = Vector3.zero
	humanoid.AutoRotate = false
	camera.CameraType = Enum.CameraType.Scriptable
end

local function applyMode()
	if firstPerson then
		takeCamera()
	else
		releaseCamera()
	end
end

local function setupCharacter(char)
	character = char
	humanoid = char:WaitForChild("Humanoid")
	root = char:WaitForChild("HumanoidRootPart")
	locked = {}
	sprinting = false
	bobTime = 0
	standTime = 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)

	if diedConn then diedConn:Disconnect() end
	diedConn = humanoid.Died:Connect(function()
		releaseCamera()
	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 firstPerson or not humanoid or not root or humanoid.Health <= 0 then return end
	local head = character and character:FindFirstChild("Head")
	if not head then return end

	-- Тело (самым последним делом в кадре дублируется ниже)
	for part, value in pairs(locked) do
		part.LocalTransparencyModifier = value
	end

	-- Мышка
	if GuiService.MenuIsOpen then
		UserInputService.MouseBehavior = Enum.MouseBehavior.Default
	else
		UserInputService.MouseBehavior = Enum.MouseBehavior.LockCenter
		local delta = UserInputService:GetMouseDelta()
		local m = Settings.MaxDeltaPerFrame
		local dx = math.clamp(delta.X, -m, m)
		local dy = math.clamp(delta.Y, -m, m)
		local sens = Settings.MouseSensitivity * GameSettings.MouseSensitivity
		yaw -= dx * sens
		local lim = math.rad(Settings.MaxPitchDeg)
		pitch = math.clamp(pitch - dy * sens, -lim, lim)
	end

	-- Персонаж поворачивается туда же, куда смотрит камера
	root.CFrame = CFrame.new(root.Position) * CFrame.Angles(0, yaw, 0)

	-- Покачивание
	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, bobPitch = 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
		bobPitch = math.sin(bobTime * 2) * Settings.BobPitch * mult * moveAlpha
	end

	-- Дыхание в покое (после IdleDelay секунд стояния)
	local idleOffset = Vector3.zero
	local idleRoll, idlePitch = 0, 0
	if Settings.IdleEnabled then
		idleTime += dt * Settings.IdleSpeed
		if moving then standTime = 0 else standTime += dt end
		local idleAlpha = (1 - moveAlpha) * math.clamp((standTime - Settings.IdleDelay) / Settings.IdleFade, 0, 1)
		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

	bobOffset = bobOffset:Lerp(walkOffset + idleOffset, math.clamp(dt * Settings.BobSmoothing, 0, 1))

	-- Позиция: от головы, смещение считается по повороту влево-вправо (не по наклону)
	local downFactor = 1 - math.clamp(-math.sin(pitch), 0, 1) * Settings.DownPullIn
	local localOffset = Vector3.new(Settings.CameraSide, Settings.CameraHeight, Settings.CameraBack * downFactor) + bobOffset
	local yawCF = CFrame.Angles(0, yaw, 0)
	local pos = head.Position + yawCF:VectorToWorldSpace(localOffset)

	camera.CFrame = CFrame.new(pos)
		* CFrame.fromEulerAnglesYXZ(pitch, yaw, 0)
		* CFrame.Angles(bobPitch + 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)

-- Прозрачность тела в самый последний момент перед отрисовкой
RunService:BindToRenderStep("LockBodyTransparency", Enum.RenderPriority.Last.Value, function()
	if not firstPerson then return end
	for part, value in pairs(locked) do
		part.LocalTransparencyModifier = value
	end
end)

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