How would I go about making a secure, decent-looking raycast turret system?

For a new game I am working on, there is a turret on the back of a vehicle. Here is a video of it in action.

This looks ok, but functionally, it is far from ideal. The system works by detecting when a player is holding down his or her mouse (or whatever, e.g. tapping / the RightTrigger on Console), sends the mouse data through a RemoteEvent, and then a server script runs a loop that creates a brick as the “bullet” or “bullet trail.” At the end of the bullet trail, an invisible explosion happens, and that is what does the damage on hit players. When the player releases the mouse, the loop stops.

Here is the code:

---server 
local system = script.Parent
local gunSeat = system.gunSeat
local core = system.important
local firin = false
local fireRate = system:GetAttribute("firerate") or 850
local damage = system:GetAttribute("damage") or 25

core.firing.OnServerEvent:Connect(function(plr:Player,firing:BoolValue,mouseHit,mouseTarget:Instance)
	--print("hi")
	firin = firing
	while firin and system:GetAttribute("ammo") > 0 do
		task.wait()
		local barrels = core.barrels:GetChildren()
		local choosenBarrel = barrels[math.random(1,#barrels)]
		if mouseTarget == nil or system:FindFirstChild("bullet") then return end
		if mouseTarget:IsAncestorOf(system.Parent) == false then
			core.effect.Fire:Play()
			core.effect.Heat.Enabled = true
			core.barrelMount.HingeConstraint.AngularVelocity = 15
			local theTar = mouseHit.p + Vector3.new(math.random(-5, 5), math.random(-5, 5), math.random(-5, 5))
			local bull = Instance.new("Part")
			bull.Name = "bullet"
			bull.Parent = system
			bull.CFrame = CFrame.new((theTar + choosenBarrel.firePoint.WorldCFrame.Position)/2,choosenBarrel.firePoint.WorldCFrame.Position) --- script.Parent.Parent is the vehicle seat
			bull.CanCollide = false
			bull.BrickColor = BrickColor.new(24)
			bull.Anchored = true
			bull.Size = Vector3.new(1,1.2,1)
			game.Debris:AddItem(bull,0.1)
			local mesher = Instance.new("BlockMesh")
			mesher.Parent = bull
			mesher.Scale = Vector3.new(0.2, 0.2, (mouseHit.p - choosenBarrel.firePoint.WorldCFrame.Position).magnitude)
			local ex = Instance.new("Explosion")
			ex.Visible = false
			ex.BlastRadius = 5
			ex.BlastPressure = 0
			ex.Position = theTar
			ex.Parent = game.Workspace
			ex.Hit:Connect(function(hit)
				if hit and hit.Parent then
					local hum = cf.findHumanoid(hit)
					if hum and not hum:FindFirstChild("explosion") then
						local tag1 = Instance.new("Folder",hum)
						tag1.Name = "explosion"
						game:GetService("Debris"):AddItem(tag1,.1)
						local tag2 = Instance.new("ObjectValue",hum)
						tag2.Value = plr
						tag2.Name = "creator"
						game:GetService("Debris"):AddItem(tag2,3)
						hum:TakeDamage(damage)
					end
				end
			end)
			local oldAmmo = system:GetAttribute("ammo")
			system:SetAttribute("ammo",oldAmmo-1)
			task.wait(60/fireRate)
		end
	end
	core.barrelMount.HingeConstraint.AngularVelocity = 0
	core.effect.Heat.Enabled = false
end)

---client
mouse.Button1Down:Connect(function()
		if firingGun == false then
			firingGun = true
			firing:FireServer(true,mouse.Hit,mouse.Target)
		end
	end)

	mouse.Button1Up:Connect(function()
		firingGun = false
		firing:FireServer(false,mouse.Hit,mouse.Target)
	end)
	
	uis.InputEnded:Connect(function(input) ---backup, in case Button1Up fails - actually fixed that
		if input.UserInputType == Enum.UserInputType.MouseButton1 then
			firingGun = false
			firing:FireServer(false,mouse.Hit,mouse.Target)
		end
	end)

It works, but it is not something I want to keep using, given that you can see in the video, a few things happen:

  1. When you move your mouse, the beam does not follow. You have to release your mouse, and then hold down again, for the location to be updated.

  2. Is it extremely inaccurate, the beam/bullet part flies around pretty wildly.

What I would like to do, is convert this system over to a raycast based on, like seen here. This would allow for the bullet’s targeting to be updated even when the mouse moves, would allow for better accuracy, and would allow for better hit-detecting that isn’t via an invisible explosion…

From my brief research on raycasting weapons, they need a RemoteEvent to detect hits, which I know is super vulnerable. I want to make something safe and secure from exploiters.

How would I do this?

It’s not remote events in general that are vulnerable, it’s what the server code allows the remote events to do.

For example, in this case, if you have the server damage players based on a remote event that sends a player to damage and doesn’t do any sanity checks, it would be trivial to kill all/any player with exploits.

On the other side, if you have a chess game and you send move selections from the client with a remote event and sanity check them on the server, there is no potential for exploits, since the server can completely check if the client’s input is valid by the rules of chess.

For guns there are generally trade offs between making the guns work correctly and without any unexpected delays versus making them secure.

Based on how your gun looks in the video, I would take a hit scan approach and then sanity check it on the server. If properly sanity checked on the server, the only exploit that would happen would be aim-botting and a slightly faster fire rate, though triple-A games can’t fix that either. Triple-A games implement advance checking methods like backtracking and stuff, though a lot of those things are way beyond your scope and sometimes beyond the Roblox engines’s abilities.

Here’s an outline:

On the client:

  • When the player clicks, raycast to the mouse hit position from the barrel.
  • Create a visual for the hit
  • Send the information about the shot to the server
    • For your case stuff like the direction, if/what it hit, potentially the hit offset on the hit part so it looks nice on the other clients, and anything you need to make the visual or do sanity checks

On the server:

  • When a shot is got from the client
  • Sanity check it
    • Check if the ammo is valid (prevent inf fire)
    • Check if the timing between shots is approximately correct (prevent spam, though note potential latency issues can create weird shots, so just ignore shots from a player for a bit if the shots are too close together)
    • If there is a hit object, raycast from the barrel to the hit object and make sure the shot is clear (prevent shooting through walls and across the map)
    • In general sanity check with the assumption that whatever is coming through the remote event could be absolutely anything and completely malicious
  • If all the sanity checks pass
    • Do the hit server effect (deal dmg, etc)
    • Send the bullet info to all the other clients so they can create visuals for it

On the other clients:

  • When getting the signal a bullet was fired, use the data to create the visual for the bullet (ex: create a beam from the barrel of the gun in the direction or if it hit something (ideally) in the direction of the hit object times the CFrame offset, maybe if the hit object is the player use some send data to make a hit effect (e.g. blood, shot visual, attack direction indicator, etc))

Trying to prevent exploiting on Roblox is mostly damage control with sanity checking and moderation efforts. Any real time combat on Roblox that doesn’t have like 0.2 seconds of lag for every shot is vulnerable (including your current code too even), so it’s important not to sweat the small stuff. Take Easy.gg’s Bed Wars for example: 100k players or something and 1 in 20 servers has an exploiter–and not by any means for a lack of trying or skill

2 Likes