-- This function is only called on clients
function BulletWeapon:simulateFire(firingPlayer, fireInfo)
	BaseWeapon.simulateFire(self, fireInfo)

	-- Play "Fired" sound
	if self.lastFireSound then
		self.lastFireSound:Stop()
	end
	self.lastFireSound = self:tryPlaySound("Fired", self:getConfigValue("FiredPlaybackSpeedRange", 0.1))

	-- Simulate each projectile/bullet fired from current weapon
	local numProjectiles = self:getConfigValue("NumProjectiles", 1)
	local randomGenerator = Random.new(self:getRandomSeedForId(fireInfo.id))
	for i = 1, numProjectiles do
		self:simulateProjectile(firingPlayer, fireInfo, i, randomGenerator)
	end

	-- Animate the bolt if the current gun has one
	local actionOpenTime = self:getConfigValue("ActionOpenTime", 0.025)
	if self.boltMotor then
		coroutine.wrap(function()
			self:animateBoltAction(true)
			wait(actionOpenTime)
			self:animateBoltAction(false)
		end)()
	end

	-- Eject bullet casings and play "CasingHitSound" (child of casing) sound if applicable for current weapon
	if not NO_BULLET_CASINGS and self.casingTemplate and self.casingEjectPoint then
		local casing = self.casingTemplate:Clone()
		casing.Anchored = false
		casing.Archivable = false
		casing.CFrame = self.casingEjectPoint.WorldCFrame
		casing.Velocity = self.casingEjectPoint.Parent.Velocity + (self.casingEjectPoint.WorldAxis * localRandom:NextNumber(self:getConfigValue("CasingEjectSpeedMin", 15), self:getConfigValue("CasingEjectSpeedMax", 18)))
		casing.Parent = workspace.CurrentCamera
		CollectionService:AddTag(casing, "WeaponsSystemIgnore")

		local casingHitSound = casing:FindFirstChild("CasingHitSound")
		if casingHitSound then
			local touchedConn = nil
			touchedConn = casing.Touched:Connect(function(hitPart)
				if not hitPart:IsDescendantOf(self.instance) then
					casingHitSound:Play()
					touchedConn:Disconnect()
					touchedConn = nil
				end
			end)
		end

		Debris:AddItem(casing, 2)
	end

	if self.player == Players.LocalPlayer then
		coroutine.wrap(function()
			-- Wait for "RecoilDelayTime" before adding recoil
			local startTime = tick()
			local recoilDelayTime = self:getConfigValue("RecoilDelayTime", 0.07)
			while tick() < startTime + recoilDelayTime do
				RunService.RenderStepped:Wait()
			end
			RunService.RenderStepped:Wait()

			-- Add recoil to camera
			local recoilMin, recoilMax = self:getConfigValue("RecoilMin", 0.05), self:getConfigValue("RecoilMax", 0.5)
			local intensityToAdd = randomGenerator:NextNumber(recoilMin, recoilMax)
			local xIntensity = math.sin(tick() * 2) * intensityToAdd * math.rad(0.05)
			local yIntensity = intensityToAdd * 0.025
			self.weaponsSystem.camera:addRecoil(Vector2.new(xIntensity, yIntensity))

			if not (self.weaponsSystem.camera:isZoomed() and self:getConfigValue("HasScope", false)) then
				self.recoilIntensity = math.clamp(self.recoilIntensity * 1 + (intensityToAdd / 10), 0.005, 1)
			end

			-- Make crosshair reflect recoil/spread amount
			local weaponsGui = self.weaponsSystem.gui
			if weaponsGui then
				weaponsGui:setCrosshairScale(1 + intensityToAdd)
			end
		end)()
	end
end

function BulletWeapon:getIgnoreList(includeLocalPlayer)
	local now = tick()
	local ignoreList = self.ignoreList
	if not ignoreList or now - self.ignoreListRefreshTime > IGNORE_LIST_LIFETIME then
		ignoreList = {
			self.instanceIsTool and self.instance.Parent or self.instance,
			workspace.CurrentCamera
		}
		if not RunService:IsServer() then
			if includeLocalPlayer and Players.LocalPlayer and Players.LocalPlayer.Character then
				table.insert(ignoreList, Players.LocalPlayer.Character)
			end
		end
		self.ignoreList = ignoreList
	end
	return ignoreList
end