CCCPaste
local RecoilModule = {}
RecoilModule.__index = RecoilModule

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

local function lerp(a, b, t)
	return a + (b - a) * t
end

local Spring = {}
Spring.__index = Spring

function Spring.new(stiffness, damping, mass)
	local self = setmetatable({}, Spring)
	self.stiffness = stiffness or 100
	self.damping = damping or 10
	self.mass = mass or 1
	self.position = vector.zero
	self.velocity = vector.zero
	self.target = vector.zero
	return self
end

function Spring:Update(dt)
	local force = (self.target - self.position) * self.stiffness
	force = force - self.velocity * self.damping
	local acceleration = force / self.mass
	self.velocity = self.velocity + acceleration * dt
	self.position = self.position + self.velocity * dt
	return self.position
end

function Spring:Impulse(force)
	self.velocity = self.velocity + force / self.mass
end

function Spring:Reset()
	self.position = vector.zero
	self.velocity = vector.zero
	self.target = vector.zero
end

function RecoilModule.new(camera)
	local self = setmetatable({}, RecoilModule)

	self.camera = camera or workspace.CurrentCamera
	self.player = Players.LocalPlayer

	self.spring = Spring.new(150, 15, 1)
	self.visualSpring = Spring.new(200, 20, 1)
	self.shakeSpring = Spring.new(250, 25, 0.8)

	self.currentRecoil = vector.zero
	self.targetRecoil = vector.zero
	self.visualRecoil = vector.zero
	self.shakeRecoil = vector.zero

	self.patterns = {}
	self.currentPatternIndex = 1
	self.shotCount = 0

	self.recoverySpeed = 8
	self.maxRecoilX = 15
	self.maxRecoilY = 25

	self.connection = nil
	self.isActive = false

	self.config = {
		multiplier = 1;
		recoveryDelay = 0.1;
		patternReset = .5;
		smoothing = .85;
		visualIntensity = 1.2;
		adsMultiplier = .6;
		shakeIntensity = 1.5;
		shakeFrequency = 35;
		shakeDecay = .88;
		horizontalShake = 1.8;
		verticalShake = 1.4;
		rotationalShake = .8;
	}

	self.lastShotTime = 0
	self.isAiming = false
	self.shakeTime = 0
	self.currentShakeIntensity = 0

	return self
end

function RecoilModule:SetConfig(config)
	for key, value in config do
		if self.config[key] ~= nil then
			self.config[key] = value
		end
	end

	if config.spring then
		self.spring.stiffness = config.spring.stiffness or self.spring.stiffness
		self.spring.damping = config.spring.damping or self.spring.damping
		self.spring.mass = config.spring.mass or self.spring.mass
	end

	if config.visualSpring then
		self.visualSpring.stiffness = config.visualSpring.stiffness or self.visualSpring.stiffness
		self.visualSpring.damping = config.visualSpring.damping or self.visualSpring.damping
		self.visualSpring.mass = config.visualSpring.mass or self.visualSpring.mass
	end

	if config.shakeSpring then
		self.shakeSpring.stiffness = config.shakeSpring.stiffness or self.shakeSpring.stiffness
		self.shakeSpring.damping = config.shakeSpring.damping or self.shakeSpring.damping
		self.shakeSpring.mass = config.shakeSpring.mass or self.shakeSpring.mass
	end

	self.recoverySpeed = config.recoverySpeed or self.recoverySpeed
	self.maxRecoilX = config.maxRecoilX or self.maxRecoilX
	self.maxRecoilY = config.maxRecoilY or self.maxRecoilY
end

function RecoilModule:LoadPreset(preset)
	if preset.pattern then
		self.patterns = preset.pattern
	end

	if preset.config then
		self:SetConfig(preset.config)
	end
end

function RecoilModule:SetAiming(isAiming)
	self.isAiming = isAiming
end

function RecoilModule:AddRecoil(recoilData)
	local currentTime = tick()

	if currentTime - self.lastShotTime > self.config.patternReset then
		self.shotCount = 0
		self.currentPatternIndex = 1
	end

	self.lastShotTime = currentTime
	self.shotCount += 1

	local recoilAmount = recoilData or {x = 0; y = 1; z = 0}

	if self.patterns and #self.patterns > 0 then
		local pattern = self.patterns[math.min(self.currentPatternIndex, #self.patterns)]
		recoilAmount = {
			x = pattern.x or recoilAmount.x;
			y = pattern.y or recoilAmount.y;
			z = pattern.z or recoilAmount.z;
		}
		self.currentPatternIndex = math.min(self.currentPatternIndex + 1, #self.patterns)
	end

	local multiplier = self.config.multiplier
	if self.isAiming then
		multiplier *= self.config.adsMultiplier
	end

	local kick = vector.create(
		recoilAmount.x * multiplier,
		recoilAmount.y * multiplier,
		recoilAmount.z * multiplier
	)

	self.spring:Impulse(kick)

	local visualKick = kick * self.config.visualIntensity
	self.visualSpring:Impulse(visualKick)

	local shakeKick = vector.create(
		(math.random() - .5) * self.config.horizontalShake * self.config.shakeIntensity,
		(math.random() - .5) * self.config.verticalShake * self.config.shakeIntensity,
		(math.random() - .5) * self.config.rotationalShake * self.config.shakeIntensity
	)
	self.shakeSpring:Impulse(shakeKick)

	self.currentShakeIntensity = self.config.shakeIntensity
	self.shakeTime = currentTime

	if not self.isActive then
		self:Start()
	end
end

function RecoilModule:AddCameraShake(intensity, duration, frequency)
	intensity = intensity or .5
	duration = duration or .2
	frequency = frequency or 20

	local startTime = tick()
	local shakeConnection

	shakeConnection = RunService.RenderStepped:Connect(function()
		local elapsed = tick() - startTime
		if elapsed > duration then
			shakeConnection:Disconnect()
			return
		end

		local progress = elapsed / duration
		local fadeOut = 1 - progress

		local shakeX = math.sin(elapsed * frequency) * intensity * fadeOut
		local shakeY = math.cos(elapsed * frequency * 1.3) * intensity * fadeOut
		local shakeZ = math.sin(elapsed * frequency * .7) * intensity * .5 * fadeOut

		self.visualSpring:Impulse(vector.create(shakeX, shakeY, shakeZ) * .1)
	end)
end

function RecoilModule:Update(dt)
	local recoilSpring = self.spring:Update(dt)
	local visualSpring = self.visualSpring:Update(dt)
	local shakeSpring = self.shakeSpring:Update(dt)

	self.currentRecoil = lerp(self.currentRecoil, recoilSpring, 1 - self.config.smoothing)
	self.visualRecoil = visualSpring

	local currentTime = tick()
	local shakeElapsed = currentTime - self.shakeTime

	if self.currentShakeIntensity > .01 then
		self.currentShakeIntensity = self.currentShakeIntensity * self.config.shakeDecay

		local freq = self.config.shakeFrequency
		local shakeNoise = vector.create(
			math.sin(shakeElapsed * freq) * self.currentShakeIntensity * .15,
			math.cos(shakeElapsed * freq * 1.3) * self.currentShakeIntensity * .12,
			math.sin(shakeElapsed * freq * .7) * self.currentShakeIntensity * .08
		)

		self.shakeRecoil = shakeSpring + shakeNoise
	else
		self.shakeRecoil = shakeSpring
	end

	if currentTime - self.lastShotTime > self.config.recoveryDelay then
		self.spring.target = vector.zero
		self.visualSpring.target = vector.zero
		self.shakeSpring.target = vector.zero

		if self.spring.position.Magnitude < .01 and self.spring.velocity.Magnitude < .01 then
			self.shotCount = 0
			self.currentPatternIndex = 1
		end
	end

	local totalRecoil = self.currentRecoil + self.visualRecoil + self.shakeRecoil

	totalRecoil = vector.create(
		math.clamp(totalRecoil.X, -self.maxRecoilX, self.maxRecoilX),
		math.clamp(totalRecoil.Y, -self.maxRecoilY, self.maxRecoilY),
		totalRecoil.Z
	)

	local currentCFrame = self.camera.CFrame
	local recoilCFrame = CFrame.Angles(
		math.rad(-totalRecoil.Y),
		math.rad(totalRecoil.X),
		math.rad(totalRecoil.Z)
	)

	self.camera.CFrame = currentCFrame * recoilCFrame
end

function RecoilModule:Start()
	if self.connection then
		return
	end

	self.isActive = true
	self.connection = RunService.RenderStepped:Connect(function(dt)
		self:Update(dt)
	end)
end

function RecoilModule:Stop()
	if self.connection then
		self.connection:Disconnect()
		self.connection = nil
		self.isActive = false
	end
end

function RecoilModule:Reset()
	self.spring:Reset()
	self.visualSpring:Reset()
	self.shakeSpring:Reset()
	self.currentRecoil = vector.zero
	self.targetRecoil = vector.zero
	self.visualRecoil = vector.zero
	self.shakeRecoil = vector.zero
	self.shotCount = 0
	self.currentPatternIndex = 1
	self.lastShotTime = 0
	self.currentShakeIntensity = 0
	self.shakeTime = 0
end

function RecoilModule:Destroy()
	self:Stop()
	self.spring = nil
	self.visualSpring = nil
	self.shakeSpring = nil
	self.camera = nil
end