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


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

local player = Players.LocalPlayer

-- CAMERA
local NORMAL_FOV = 70
local SPRINT_FOV = 78
local SPRINT_SPEED = 20

-- WALK BOB
local WALK_BOB_AMOUNT = 0.035
local RUN_BOB_AMOUNT = 0.065
local WALK_BOB_SPEED = 8
local RUN_BOB_SPEED = 12

-- BREATHING
local BREATH_AMOUNT = 0.008
local BREATH_SPEED = 1.5

-- LANDING
local NORMAL_LANDING_SHAKE = 0.12
local HEAVY_LANDING_SHAKE = 0.30
local MIN_HEAVY_FALL = 5
local MAX_HEAVY_FALL = 25

-- DAMAGE
local DAMAGE_SHAKE = 0.18

-- LOW HEALTH
local LOW_HEALTH_THRESHOLD = 0.35
local LOW_HEALTH_SHAKE = 0.025
local LOW_HEALTH_SPEED = 5

-- FATIGUE
local MAX_RUN_TIME = 12
local MAX_FATIGUE = 1

-- SMOOTHNESS
local EFFECT_SMOOTH = 10
local FOV_SMOOTH = 7

-- HEAD TILT
local HEAD_TILT_AMOUNT = 1.2

local character
local humanoid
local rootPart

local bobTime = 0
local lowHealthTime = 0
local impactTime = 0

local shake = 0
local fatigue = 0
local runTime = 0

local lastHealth = 100
local wasAirborne = false
local fallStartY = nil

local currentOffset = Vector3.zero
local currentRotation = Vector3.zero

local function setupCharacter(char)
	character = char
	humanoid = char:WaitForChild("Humanoid")
	rootPart = char:WaitForChild("HumanoidRootPart")

	lastHealth = humanoid.Health
	wasAirborne = false
	fallStartY = nil

	bobTime = 0
	lowHealthTime = 0
	impactTime = 0

	shake = 0
	fatigue = 0
	runTime = 0

	currentOffset = Vector3.zero
	currentRotation = Vector3.zero

	humanoid.HealthChanged:Connect(function(health)
		if health < lastHealth then
			shake = math.max(shake, DAMAGE_SHAKE)
			impactTime = 0
		end

		lastHealth = health
	end)
end

if player.Character then
	setupCharacter(player.Character)
end

player.CharacterAdded:Connect(setupCharacter)

RunService:BindToRenderStep(
	"AdvancedCameraEffects",
	Enum.RenderPriority.Camera.Value + 5,
	function(deltaTime)

		local camera = workspace.CurrentCamera

		if not character or not humanoid or not rootPart then
			return
		end

		if humanoid.Health <= 0 then
			return
		end

		--------------------------------------------------
		-- MOVEMENT
		--------------------------------------------------

		local velocity = rootPart.AssemblyLinearVelocity

		local horizontalVelocity = Vector3.new(
			velocity.X,
			0,
			velocity.Z
		)

		local speed = horizontalVelocity.Magnitude

		local moving = humanoid.MoveDirection.Magnitude > 0.05

		local isSprinting =
			moving
			and speed >= SPRINT_SPEED

		--------------------------------------------------
		-- HUMANOID STATE
		--------------------------------------------------

		local state = humanoid:GetState()

		local airborne =
			state == Enum.HumanoidStateType.Jumping
			or state == Enum.HumanoidStateType.Freefall

		--------------------------------------------------
		-- FALL DETECTION
		--------------------------------------------------

		if airborne and not wasAirborne then
			fallStartY = rootPart.Position.Y
		end

		if not airborne and wasAirborne then

			if fallStartY then

				local fallDistance =
					math.max(
						0,
						fallStartY - rootPart.Position.Y
					)

				if fallDistance >= MIN_HEAVY_FALL then

					local strength =
						math.clamp(
							(fallDistance - MIN_HEAVY_FALL)
								/ (MAX_HEAVY_FALL - MIN_HEAVY_FALL),
							0,
							1
						)

					local landingStrength =
						NORMAL_LANDING_SHAKE
						+ strength
						* (HEAVY_LANDING_SHAKE - NORMAL_LANDING_SHAKE)

					shake = math.max(
						shake,
						landingStrength
					)

					impactTime = 0

				else

					shake = math.max(
						shake,
						NORMAL_LANDING_SHAKE
					)

				end

			else

				shake = math.max(
					shake,
					NORMAL_LANDING_SHAKE
				)

			end

			fallStartY = nil
		end

		wasAirborne = airborne

		--------------------------------------------------
		-- FATIGUE
		--------------------------------------------------

		if isSprinting and not airborne then

			runTime = math.min(
				runTime + deltaTime,
				MAX_RUN_TIME
			)

			fatigue =
				math.clamp(
					runTime / MAX_RUN_TIME,
					0,
					MAX_FATIGUE
				)

		else

			runTime = math.max(
				runTime - deltaTime * 2,
				0
			)

			fatigue =
				math.clamp(
					runTime / MAX_RUN_TIME,
					0,
					MAX_FATIGUE
				)

		end

		--------------------------------------------------
		-- TARGET CAMERA EFFECT
		--------------------------------------------------

		local targetOffset = Vector3.zero
		local targetRotation = Vector3.zero

		if moving and not airborne then

			local bobSpeed
			local bobAmount

			if isSprinting then

				bobSpeed = RUN_BOB_SPEED

				bobAmount =
					RUN_BOB_AMOUNT
					* (1 + fatigue * 0.35)

			else

				bobSpeed = WALK_BOB_SPEED
				bobAmount = WALK_BOB_AMOUNT

			end

			bobTime += deltaTime * bobSpeed

			local vertical =
				math.sin(bobTime * 2)
				* bobAmount

			local horizontal =
				math.cos(bobTime)
				* bobAmount
				* 0.35

			targetOffset = Vector3.new(
				horizontal,
				vertical,
				0
			)

			local headTilt =
				math.sin(bobTime)
				* HEAD_TILT_AMOUNT

			targetRotation = Vector3.new(
				math.sin(bobTime * 2) * 0.25,
				0,
				headTilt
			)

		else

			bobTime += deltaTime * BREATH_SPEED

			local breathing =
				math.sin(bobTime)
				* BREATH_AMOUNT

			local fatigueBreathing =
				1 + fatigue * 0.5

			targetOffset = Vector3.new(
				0,
				breathing * fatigueBreathing,
				0
			)

			targetRotation = Vector3.zero

		end

		--------------------------------------------------
		-- SMOOTH EFFECT
		--------------------------------------------------

		local smooth =
			math.clamp(
				deltaTime * EFFECT_SMOOTH,
				0,
				1
			)

		currentOffset =
			currentOffset:Lerp(
				targetOffset,
				smooth
			)

		currentRotation =
			currentRotation:Lerp(
				targetRotation,
				smooth
			)

		--------------------------------------------------
		-- DAMAGE / LANDING
		--------------------------------------------------

		if shake > 0 then

			shake =
				math.max(
					shake - deltaTime * 1.8,
					0
				)

			impactTime += deltaTime * 35

			local shakeX =
				math.sin(impactTime * 1.7)
				* shake

			local shakeY =
				math.cos(impactTime * 2.3)
				* shake

			local shakeZ =
				math.sin(impactTime * 2.8)
				* shake

			currentOffset += Vector3.new(
				shakeX,
				shakeY,
				shakeZ
			)

			currentRotation += Vector3.new(
				math.rad(shakeY * 30),
				math.rad(shakeX * 20),
				math.rad(shakeZ * 30)
			)

		end

		--------------------------------------------------
		-- LOW HEALTH
		--------------------------------------------------

		local healthRatio =
			humanoid.Health
			/ math.max(humanoid.MaxHealth, 1)

		if healthRatio <= LOW_HEALTH_THRESHOLD then

			local danger =
				1
				- math.clamp(
					healthRatio / LOW_HEALTH_THRESHOLD,
					0,
					1
				)

			lowHealthTime +=
				deltaTime
				* LOW_HEALTH_SPEED

			local pulse =
				math.sin(lowHealthTime)
				* LOW_HEALTH_SHAKE
				* danger

			currentOffset += Vector3.new(
				pulse * 0.5,
				pulse,
				0
			)

			currentRotation += Vector3.new(
				0,
				0,
				math.rad(pulse * 15)
			)

		else

			lowHealthTime = 0

		end

		--------------------------------------------------
		-- FOV
		--------------------------------------------------

		local targetFOV = NORMAL_FOV

		if isSprinting and not airborne then
			targetFOV = SPRINT_FOV
		end

		targetFOV -= fatigue * 2

		camera.FieldOfView +=
			(targetFOV - camera.FieldOfView)
			* math.clamp(
				deltaTime * FOV_SMOOTH,
				0,
				1
			)

		--------------------------------------------------
		-- APPLY EFFECT WITHOUT MOVING CAMERA DIRECTION
		--------------------------------------------------

		camera.CFrame =
			camera.CFrame
			* CFrame.new(currentOffset)
			* CFrame.Angles(
				math.rad(currentRotation.X),
				0,
				math.rad(currentRotation.Z)
			)
	end
)