Are there better methods to make an automatic gun (compared to mine)?

as some people may know, i am extremely horrible at making my code exploit-proof

my current issue is automatic guns, not semi, because those are really simple to code and exploit-proof

but the issue with automatic guns is that, the code they’ve got needs to run consistently while bypassing the sanity checks that are put on the server

now, i do have a current method for this, but something tells me it’s not that good, and since i am reorganizing my gun code, i figured i’d just ask on here


my current method is:

  • whenever client does LMB down, there’s an attribute called “IsHolding” that gets set to true on the server, and to false when the client lets go of LMB
  • remote event with mouse.hit.position as a parameter
  • if the gun is an automatic, fire a remote event called “UpdateHitPosEvent” with an updated hit position
  • while the IsHolding attribute is set to true, there’s a codeblock that just repeats

i came here to ask if there’s better methods for this


my current code looks like this:

server script

shootEvent.OnServerEvent:Connect(function(player, initialHitPos)
	local tool = player.Character:FindFirstChildOfClass("Tool")	

	local bullets = tool.Bullets	
	local spread = tool.Spread

	local firerate = gunInfo[tool.Name]["firerate"]
	
	if tool:GetAttribute("Shooting") == true then player:Kick("You need more power.") return end
	if tool:GetAttribute("Reloading") == true then player:Kick("Why?") return end
	if bullets.Value < 1 then player:Kick("Your manipulation techniques are atrocious.") return end
	if bullets.Value > gunInfo[tool.Name]["maxBullets"] then player:Kick("The underworld calls to you.") return end

	local hitPos = initialHitPos
	
	tool:SetAttribute("Shooting", true)

	coroutine.wrap(function()
		tool.FlashPart.FlashImage.Enabled = true
		tool.FlashPart.Light.Enabled = true
		task.wait(0.1)
		tool.FlashPart.FlashImage.Enabled = false
		tool.FlashPart.Light.Enabled = false
	end)()
	

	shootingModule.Shoot(tool.FlashPart, player, hitPos, tool)
	
	task.wait(firerate)
	
	if tool.GunType.Value ~= "AUTOMATIC" then
		tool:SetAttribute("Shooting", false)
	end
end)

module (automatic codeblock only)

AUTOMATIC = function(origin: Part, player: Player, hitPos: Mouse, weapon: Tool)
		local firerate = gunInfo[weapon.Name]["firerate"]

		weapon:SetAttribute("IsHolding", true)
		local updateConnection

		updateConnection = updateHitPosEvent.OnServerEvent:Connect(function(updatePlayer, newHitPos)
			if updatePlayer == player then
				hitPos = newHitPos
			end
		end)

		while weapon:GetAttribute("IsHolding") == true do
			if weapon.Bullets.Value < 1 then
				weapon:SetAttribute("Shooting", false)
				weapon:SetAttribute("IsHolding", false)
				updateConnection:Disconnect()
			end

			Raycasting(origin, player, hitPos, weapon)
			weapon.Bullets.Value -= 1
			weapon.FlashPart.ShootSound:Play()

			if weapon.Bullets.Value < 0 then
				weapon.Bullets.Value = 0
			end

			task.wait(firerate)
		end

		weapon:SetAttribute("IsHolding", false)
		weapon:SetAttribute("Shooting", false)
		updateConnection:Disconnect()
	end,
2 Likes

Don’t concern yourself with verifying the legitimacy of the client’s input. Instead, verify the legitimacy of the gun’s intended behaviour against what the client is asking it to do. For example, an automatic weapon should not be able to fire at a rate greater than what its stats set. It should not able to continue firing past its ammo capacity either. Reported “hit positions” should not be possible if anything is blocking the path between that point and the exit point of the barrel. There are a number of things to consider, so I’ll share my notes for a projectile system I developed a few years back.

This was theoretical at the time, and when implementation time came, I realized a queue is not viable. A later-fired projectile could be fired at a target with a much closer distance than older projectiles. This invalidates the claim that the oldest projectile can be associated with the latest collision report. To resolve this, each fired projectile would be given an identifier that is shared with the server, and when the client detected a collision with its projectile, it would notify the server to evaluate that projectile

2 Likes

does this mean i should do this in my case aswell?

client raycasts → sends to server
server verifies that raycast → uses that raycast

otherwise, there’s a bit of a “delay” in the raycast, since the server makes the raycast, not the client, i think you get what i mean

1 Like

These are where acceptable margins of error are introduced. My system used client-sided rendering and simulation. If the client owns the projectile, collision detection starts with them. A detected collision is reported to the server and sanity checks are applied. This gives the best user experience in terms of latency with weapon operation and the replication of VFX/SFX of other clients

2 Likes

I presume these weapons are hitscan based off the code provided. Certainly, as the other replies said, you should probably move the raycast to client then sanity check for the sake of latency. I would like to note why I think hitscan systems are actually less secure than projectile systems due to their nature of “instantaneous collisions.” While it will be easy to prevent an exploit that tries to shoot through walls or damage players without properly “aiming” at them, you will be quick to realize that having instant hits will allow aimbots to become extremely powerful. Simply reaching vantage point (e.g. fly exploits very high in the sky) will allow a cheater to easily kill almost everyone repeatedly. While you may want a hitscan system for game design purposes, do consider the drawbacks compared to a projectile system , where the exploiter cannot know where to shoot to perfectly hit moving players.

1 Like

no worries! the game isn’t pvp

2 Likes