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


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

-- ================= НАСТРОЙКИ =================
local Settings = {
	-- Камера
	CameraOffset = Vector3.new(0, -0.25, -1.5), -- позиция камеры относительно головы
	FieldOfView = 100,
	SprintFOV = 108,                            -- FOV при беге

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

	-- Покачивание
	BobEnabled = true,
	BobVertical = 0.12,                         -- вверх-вниз
	BobHorizontal = 0.08,                       -- влево-вправо
	BobSpeed = 0.35,                            -- частота шагов
	SprintBobMultiplier = 1.5,                  -- во сколько раз сильнее при беге
	BobSmoothing = 10,                          -- плавность

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

	-- Видимость тела
	ViewAccessories = false,
	ViewModels = false,
	HeadLocalTransparency = 0, -- 0 = голова остаётся (камера стоит перед ней, её не видно, тень есть); 1 = полностью скрыть
}
-- =============================================

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

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

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
		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

	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)

-- Каждый кадр после стандартной камеры Roblox
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 target = Vector3.zero
	local flatVelocity = root.AssemblyLinearVelocity * Vector3.new(1, 0, 1)
	local speed = flatVelocity.Magnitude
	local onGround = humanoid.FloorMaterial ~= Enum.Material.Air

	if Settings.BobEnabled and onGround and speed > 1 and humanoid.MoveDirection.Magnitude > 0 then
		local mult = sprinting and Settings.SprintBobMultiplier or 1
		bobTime += dt * speed * Settings.BobSpeed
		target = Vector3.new(
			math.sin(bobTime) * Settings.BobHorizontal * mult,
			math.sin(bobTime * 2) * Settings.BobVertical * mult,
			0
		)
	end

	bobOffset = bobOffset:Lerp(target, math.clamp(dt * Settings.BobSmoothing, 0, 1))
	humanoid.CameraOffset = Settings.CameraOffset + bobOffset

	-- FOV (при беге чуть шире)
	local targetFov = (sprinting and speed > 1) and Settings.SprintFOV or Settings.FieldOfView
	camera.FieldOfView += (targetFov - camera.FieldOfView) * math.clamp(dt * 8, 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