Stop spam click from breaking weapon

So I’ve been working on an fps framework for my game, and I’m in the process of handling the firing of the weapon. However, when the user spam clicks the mouse, eventually the gun just breaks and stops shooting. How can I prevent this?

Scripts:
Handling firing

function module:fire(tofire)
	if tofire == self.firing then return end

	local check = replicatedStorage.events.isEquipped:InvokeServer()
	if not check then return end
	self.firing = tofire

	while self.firing and not self.cooling do
		local barrelPos
		
		if not self.attachments[self.curwep] or not self.attachments[self.curwep].barrel then
			barrelPos = self.viewmodel:FindFirstChild(self.curwep).nodes.barrel.muzzle.WorldCFrame
		else
			barrelPos = self.viewmodel:FindFirstChild(self.curwep):FindFirstChild(self.attachments[self.curwep].barrel.name).PrimaryPart.muzzle.WorldCFrame
		end
		
		local projectile = bullet.new(barrelPos, 10)
		projectile:fire()
			
		self.cooling = true
		task.wait(self.settings.firerate)
		self.cooling = false
	end
end

bullet is a module (I’m mainly worried about the while loop)

Input handling

local function fire(name, state)
	if state == Enum.UserInputState.Begin and not cooling then
		user:fire(true)
	elseif state == Enum.UserInputState.End then
		user:fire(false)
	end
end
CAS:BindAction("fire", fire, false, Enum.UserInputType.MouseButton1)

Thanks for the help in advance!

Do some debugging
Print:
self.cooling
self.firing
check
input state
and anything that can return the function or stop it

Isn’t that it?

self.cooling = true
task.wait(self.settings.firerate)
self.cooling = false

Yeah, thats the code. However, when you spam click, I guess it’s experiencing input delay, and you cant fire anymore because it gets stopped at this argument:

if tofire == self.firing then return end

I’ve done all of that, and I’ve even tried adding a debounce to the input so that you can only click once every 0.05 seconds.

So then I’m guessing the problem is here.

Either the begin state suddenly stopped registering which I doubt, or the cooling somehow starts after ending once and it never stops.

Try this:

	task.spawn(function()
		if not self.cooling then
			self.cooling = true
			task.wait(self.settings.firerate)
			self.cooling = false
		end
	end)

Should I opt for task.spawn or coroutines in this scenario?

Alright so i tried it with both spawn and coroutines, and it does seem to stop the weapon from breaking. However, you cant fire the weapon full auto anymore

Alr yeah I see why another thread would do that. Try the same thing but without the coroutine or spawn:

		if not self.cooling then
			self.cooling = true
			task.wait(self.settings.firerate)
			self.cooling = false
		end

That causes it to still break fkdjnfdsf

Alright then try:

	task.spawn(function()
		if not self.cooling then
			self.cooling = true
			task.wait(self.settings.firerate)
			self.cooling = false
		end
	end)
	task.wait(self.settings.firerate) -- or just task.wait()

I’m guessing I messed up since now there is no wait in the thread of the actual while loop, which causes the studio to crash or give u some runtime error.

Yeah, don’t think that would work

Yeah realized after I posted it. Saw that the while loop stops if its cooling. As a desperate measure try removing the check

	while self.firing do
		local barrelPos
		
		if not self.attachments[self.curwep] or not self.attachments[self.curwep].barrel then
			barrelPos = self.viewmodel:FindFirstChild(self.curwep).nodes.barrel.muzzle.WorldCFrame
		else
			barrelPos = self.viewmodel:FindFirstChild(self.curwep):FindFirstChild(self.attachments[self.curwep].barrel.name).PrimaryPart.muzzle.WorldCFrame
		end
		
		local projectile = bullet.new(barrelPos, 10)
		projectile:fire()
			
		self.cooling = true
		task.wait(self.settings.firerate)
		self.cooling = false
	end

The issue with this is that they can now spam click and get infinite firerate, thats the reason i added the check in the first place. Spam clicking in this scenario would cause a runtime error from all the while loops

It shouldn’t be possible though since theres already a check here:

Nah, that cooling check was a desperate measure, but in theory i could try implementing the check in the input script. But now thats a local script, meaning any hacker can access it and just remove the check…

I added another check

if self.cooling and tofire then return end

this kinda helped, but after a long series of clicks, it broke eventually. Im gonna sleep now, but I’ll be back tomorrow.

Alr imma brainstorm

Idea 1

Local script:

local firing = false
local function fire(name, state)
	if state == Enum.UserInputState.Begin then
		firing = true
		while firing do
			user:fire()
			task.wait(firerate)
		end
	elseif state == Enum.UserInputState.End then
		firing = false
	end
end

Module script:

function module:fire()

	local check = replicatedStorage.events.isEquipped:InvokeServer()
	if not check then return end

	if self.firing and not self.cooling then
		local barrelPos
		
		if not self.attachments[self.curwep] or not self.attachments[self.curwep].barrel then
			barrelPos = self.viewmodel:FindFirstChild(self.curwep).nodes.barrel.muzzle.WorldCFrame
		else
			barrelPos = self.viewmodel:FindFirstChild(self.curwep):FindFirstChild(self.attachments[self.curwep].barrel.name).PrimaryPart.muzzle.WorldCFrame
		end
		
		local projectile = bullet.new(barrelPos, 10)
		projectile:fire()

		task.spawn(function()
			self.cooling = true
			task.wait(self.settings.firerate)
			self.cooling = false
		end)
	end
end

Though this might decrease performance since you’re firing the event every shot instead of toggling shooting (I think this is how it’s done in the roblox fps themplate but they need the player mouse position so its understandable, and you seem to be just shooting 10 studs forward or something like that)

Idea 2

Maybe we can check beforehand:

function module:fire(tofire)
	if tofire == self.firing or self.cooling then return end

	local check = replicatedStorage.events.isEquipped:InvokeServer()
	if not check then return end
	self.firing = tofire

	while self.firing do
		local barrelPos
		
		if not self.attachments[self.curwep] or not self.attachments[self.curwep].barrel then
			barrelPos = self.viewmodel:FindFirstChild(self.curwep).nodes.barrel.muzzle.WorldCFrame
		else
			barrelPos = self.viewmodel:FindFirstChild(self.curwep):FindFirstChild(self.attachments[self.curwep].barrel.name).PrimaryPart.muzzle.WorldCFrame
		end
		
		local projectile = bullet.new(barrelPos, 10)
		projectile:fire()
			
		self.cooling = true
		task.wait(self.settings.firerate)
		self.cooling = false
	end
end

Hopefully one of these work when you wake up

1 Like

god! That would still Break Try looking up a tutorial, Anything else would Love to help!

This would work, but I want to avoid implementing the loop in the input handler if I can. If there are no more solutions, I guess this is the best one.

This would still cause it to break