Making a automatic weapon, need opinions on how to code it

  1. What do you want to achieve?

I’m making a automatic weapon (submachine gun), but I’m not sure how to handle rapid-firing. So far, I have it so it fires once every time the user clicks their mouse, but I need to do it in a loop

  1. What is the issue?
  • I’m not sure how I should do it.

Doing a loop on the client controlled by mouse state means constantly firing a remote function, which could create latency problems and be potentially exploitable

Doing a loop on the server seems impossible since I can’t grab mouse position from the client, and setting a Vector3Value wouldn’t work since those changes won’t replicate to the server

I’m not even sure if I’m overthinking this and should just go with a solution and figure out the problems later

  1. What solutions have you tried so far?

I found a single devforum post that somewhat matched my problem, but it didn’t gain much attention

client-side localscript

local Players = game:GetService("Players")

local LocalPlayer = Players.LocalPlayer
local CurrentCamera = workspace.CurrentCamera
local Mouse = LocalPlayer:GetMouse()
local Tool = script.Parent

local Equipped = false
Tool.Equipped:Connect(function()
	Equipped = true
end)

Tool.Unequipped:Connect(function()
	Equipped = false
end)

Mouse.Button1Down:Connect(function()
	local Hit = Mouse.Hit.Position
	local _, IsVisible = CurrentCamera:WorldToScreenPoint(Hit)
	if Equipped and IsVisible then
		local DidHit = Tool.Fire:InvokeServer(Hit)
		print(DidHit)
	end
end)

task.wait()
script.Parent = LocalPlayer.PlayerScripts

Server-side OOP code

local SMG = {}

SMG.__index = SMG

local ReplicatedStorage = game:GetService("ReplicatedStorage")
local Players = game:GetService("Players")
local ServerStorage = game:GetService("ServerStorage")
local ServerScriptService = game:GetService("ServerScriptService")
local t = require(ReplicatedStorage.t)
local GunUtil = require(script.Parent.Parent.GunUtil)

local FireValidator = t.tuple(t.instanceIsA("Player"), t.Vector3)

local Damage = 5
local FireRate = 0.1

function SMG.new(plr:Player)
	local self = setmetatable({}, SMG)
	self.Tool = script.SMG:Clone()
	self.Player = plr
	self.Tool.Parent = plr.Backpack
	self.Params = RaycastParams.new()
	self.Params.IgnoreWater = true
	self.Tool.Fire.OnServerInvoke = function(Player, Hit)
		if Player == plr and FireValidator(Player, Hit) then
			return self:Fire(Hit)
		end
	end
	local CharacterAddedConnection = plr.CharacterAdded:Connect(function(Char)
		self:SetActive(ServerScriptService.TimeSystem.CurrentGameState.Value == "Fighting")
		local Humanoid = Char:FindFirstChildWhichIsA("Humanoid")
		Humanoid.Died:Once(function()
			self:SetActive(false)
		end)
	end)
	local Humanoid = plr.Character:FindFirstChildWhichIsA("Humanoid")
	Humanoid.Died:Once(function()
		self:SetActive(false)
	end)
	local PlayerRemovingConnection = Players.PlayerRemoving:Connect(function(x)
		if x == self.Player then
			self:Destroy()
		end
	end)
	self.Connections = {
		CharacterAddedConnection,
		PlayerRemovingConnection
	}
	--self:SetActive(ServerScriptService.TimeSystem.CurrentGameState.Value == "Fighting")
end

function SMG:Destroy()
	for k, v in pairs(self.Connections) do
		v:Disconnect()
	end
end

function SMG:SetActive(Active)
	if not Active then
		self.Tool.Parent = ServerStorage.CombinedInventory
	else
		self.Tool.Parent = self.Player.Backpack
	end
end

function SMG:Fire(Hit:Vector3)
	local Origin = self.Tool.SMG.SMG.TipAttachment.WorldPosition
	self.Params.FilterDescendantsInstances = {self.Player.Character}
	local Result = GunUtil:Raycast(Origin, Hit, self.Params)
	if not Result then
		return false
	end
	GunUtil:CreateNonProjectileBeam(Origin, Result.Position)
	self.Tool.SMG.SMG.Fired:Play()
	local Humanoid = Result.Instance.Parent:FindFirstChild("Humanoid") or Result.Instance.Parent.Parent:FindFirstChild("Humanoid")
	local DoesExist = Humanoid ~= nil
	local IsDead
	if DoesExist then
		IsDead = Humanoid.Health == 0
		if IsDead then
			return false
		else
			Humanoid:TakeDamage(Damage)
		end
	end
	return false
end

return SMG

Also feel free to critque any unrelated code I included, I do like trying to improve and learn new things and ways to accomplish tasks

1 Like

You’re going to have to put the firing loop on the client so you can access the mouse. Any client-to-server remote is exploitable, but you can of course do what you can to implement sanity checks and protections. Latency will be an issue in one form or another no matter what you do.

This code isn’t perfect, but here’s what I’ve done for my automatic weapon.

local Mouse = game.Players.LocalPlayer:GetMouse()

local Equipped = false
local MouseDown = false
local OnFireWait = false

script.Parent.Equipped:Connect(function()
	Equipped = true
end)

Mouse.Button1Up:Connect(function()
	MouseDown = false
end)

Mouse.Button1Down:Connect(function()
	if Equipped == false or OnFireWait == true then return end
	MouseDown = true
	while MouseDown do
		OnFireWait = true
		script.Parent.Click:FireServer(Mouse.Hit.Position)
		wait(GunSettings.FireRate)
		OnFireWait = false
	end	
end)

script.Parent.Unequipped:Connect(function()
	Equipped = false
end)
1 Like

Since you’re doing server-side hit detection, use UnreliableRemoteEvents to send the client’s mouse position or look angle every set time interval (1/20th second, maybe?) with some compression to minimize network traffic.

To compress a look angle, for example, you would only need to send two numbers: a pitch and yaw. The pitch value (vertical rotation) would be between -90 and 90 degrees, and yaw value (horizontal) -180 and 180 degrees. You could compress them into two 16-bit integers for precision up to the hundredths place (e.g. a pitch of -88.655790 becomes -8866 and a yaw of 156.433321 becomes 15643) and write them to a buffer which you can then send through the unreliable event.

1 Like

I usually use a repeat until loop for both firing modes; break the loop if the firing mode is semi-automatic, continue if automatic. Conditions go after until.

In your case, here’s an example:

Mouse.Button1Down:Connect(function()
	repeat
        local Hit = Mouse.Hit.Position
	    local _, IsVisible = CurrentCamera:WorldToScreenPoint(Hit)
	    if Equipped and IsVisible then
		    local DidHit = Tool.Fire:InvokeServer(Hit)
		    print(DidHit)
	    end

        task.wait(rpmCooldown) -- Time between each shot. RPM formula is (60 / rpm)

        if firingMode == "Semi" then
            break
        elseif firingMode == "Auto" then
            continue
        end
    until notHoldingMouse or ammo == 0 -- Conditions that the gun is not able to fire under. I put some examples
end)

This topic was automatically closed 14 days after the last reply. New replies are no longer allowed.