Help with gun script server lag

My gun script causes frequent server freezes that last up to 10 seconds, and in many cases eventually even crash.

With about 50 players in the server, I’m pretty sure about 1500 bullets get fired each minute, yet the server constantly freezes. The script activity doesn’t seem to rise about 2%, and I’m not really sure how to go about the “debugging”.

FireClient and FireServer is only used once per fire, so with a shotgun it will just be one fire for all the bullets in the shot.

REMOTES.Effect:FireAllClients("Shoot", player,Tool, position, newdirections,config.Blood,exa1)

I am completely lost on why the server freezes so much, and im asking whats the best way to go about fixing this? I am willing to send over full code once I have tried everything, however this is all of the main part.

function Hit(player,tool,config,shootPos,t,bulletType)
	local hitChar = t[1]
	local hit = t[2]
	local pos = t[3]
	local normal = t[4]
	local hcf = t[5]
	local bcf = t[6]
	local re = t[7]
	
	--print(hitChar,hit)
	
	if t 
	and hitChar 
	and hitChar:IsA("Model") 
	and hitChar ~= player.Character 
	and hitChar.Parent == workspace.Characters 
	and hit and hit:IsA("BasePart") 
	and hit.Parent == hitChar 
	and pos and typeof(pos) == "Vector3"
	and normal and typeof(normal) == "Vector3"
	and hcf and typeof(hcf) == "CFrame"
	and bcf and typeof(bcf) == "CFrame"
	and re and typeof(re) == "table"
	then
		local hHumanoid = hitChar:FindFirstChildOfClass("Humanoid")
		if hHumanoid.Health > 0 and DAMAGE:PlayerCanDamage(player, hHumanoid) then
			local distance	= (hit.Position - shootPos).Magnitude
			local distance2	= (hit.Position - hcf.p).Magnitude
			
			if distance > config.Range * 1.05 then return end
			if distance2 > 15 then return end
			
			--print("ye")
			
			local damage,perc	= DAMAGE:Calculate(tool, hit, pos)
			if bulletType and config.BulletTypeChange and config.BulletTypeChange.Enabled and bulletType == "RB" then
				damage = damage / config.BulletTypeChange[bulletType .. "_Reduction"]
				if hHumanoid.Health - damage < 1 then
					damage = hHumanoid.Health - 1
				end
			end
			
			local wRagdolled = false
			if _G.RagdollCheck and _G.RagdollCheck(hitChar) then
				wRagdolled = true
			end
			delay(0.05,function()
				if hitChar and hitChar.Parent and _G.RagdollCheck and _G.RagdollCheck(hitChar) then
					local x1 = shootPos
					local x2 = pos
					
					x1 = Vector3.new(x1.X,0,x1.Z)
					x2 = Vector3.new(x2.X,0,x2.Z)
					
					local force = (CFrame.new(x1,x2).LookVector + Vector3.new(0,1.5,0)) * (config.Knockback and config.Knockback) or 1
					
					if not wRagdolled then
						force = force * 2.5
					end
					
					_G.SMains.ForceChar(hitChar,hit,force * perc,0.15,hHumanoid.Health - damage <= 0)										
				end
			end)
			
			return hitChar,hit,hHumanoid,damage

--			if hPlayer then
--				--REMOTES.HitIndicator:FireClient(otherPlayer, direction, damage)
--			end
--
--			if hHumanoid.Health <= 0 then
--				for _, part in pairs(hHumanoid.Parent:GetChildren()) do
--					if part:IsA("BasePart") then
--						part.Velocity	= direction * config.Damage
--					end
--				end
--				--local killDist	= math.floor((position - humanoid.Parent.HumanoidRootPart.Position).Magnitude + 0.5)
--				--STAT_SCRIPT.FurthestKill:Fire(player, killDist)
--				--REMOTES.Killfeed:FireAllClients(player.Name, humanoid.Parent.Name, shot.Tool.Name, killDist)
--			end
		end
	end
end

REMOTES.Gun_Shoot.OnServerEvent:connect(function(player, Tool, mode,position, directions,exa1,exa2)
	spawn(function()
		if not guns[Tool] then return end
		
		AA_Module = _G.AntiExploitModule
		if not AA_Module:CheckTing(player.Character) then return end
		
		if shots[player] then
			shots[player]	= nil
		end
		
		local character	= player.Character
		local rootPart	= character.HumanoidRootPart
		local humanoid	= character.Humanoid
				
		if Tool.Parent == character 
		then
			local Handle =	Tool.Handle
			local Values =	Tool.Values
			local ammo		= Values.Ammo
			local config	= CONFIG:GetConfig(Tool)
			if mode == "Do" then
				if _G.CheckIfCan and not _G.CheckIfCan(character) then return end
				
				if (rootPart.Position - position).Magnitude < 15 then
					if ammo.Value > 0 then
						if not (config.GrenadeLauncherEnabled or config.RocketLauncherEnabled) and #directions == config.BulletsPerShot then
							if #directions > config.BulletsPerShot then return end
							
							ammo.Value	= ammo.Value - 1
							
							if config.MinigunEnabled and not Handle.Muzzle.FireSound.Playing then
								Handle.Muzzle.FireSound:Play()
							end
							
							_G.ZaWarudoWait()
							
							local shot	= {
								Tool		= Tool;
								Config		= config;
								Position	= position;
								Directions	= directions;
							}
							
							shots[player]	= shot
							cancels[Tool]	= true
							
							local dmges = {}
							local newdirections = {}
							for i, direction in pairs(directions) do
								if direction[2] then
									local hChar,hit,hHumanoid,damage = Hit(player,Tool,config,position,direction[2],exa1)
									if hChar then
										local l = dmges[direction[2][2].Name]
										if not l then dmges[direction[2][2].Name]  = {hChar,hit,hHumanoid,0};l = dmges[direction[2][2].Name] end
	
										l[4] = l[4] + damage
										
										if config.CanExecute and _G.DownedCheck(hChar) then
											table.insert(direction[2],true)
										end
									end
								end
								table.insert(newdirections,direction)
							end
							
							for _,v in pairs(dmges) do
								local hChar = v[1]
								local hit = v[2]
								local hHumanoid = v[3]
								local damage = v[4]
								
								if _G.DownedCheck ~= nil and _G.DownedCheck(hChar) then
									if config.CanExecute then
										--damage = damage / 4
										DAMAGE:Damage(hHumanoid, damage / config.DownedDiv, player)
									end
								else
									DAMAGE:Damage(hHumanoid, damage, player)
									if _G.FlinchChar then
										local x = not (config.RocketLauncherEnabled or config.GrenadeLauncherEnabled)
										_G.FlinchChar(hChar,hHumanoid,config.FlinchTime or 0,false,x)
									end		
									if _G.TagChar then
										_G.TagChar(hChar,player,15,0)	
									end
								end
							end
							
							--REMOTES.Effect:FireAllClients("Shoot", player,Tool, position, newdirections,config.Blood,exa1)
							FireClientFunc("Shoot", player,Tool, position, newdirections,config.Blood,exa1)
																		
							local c = math.random()
							sCodes[Tool] = c
							
							wait(1)
							
							if sCodes[Tool] == c and config.MinigunEnabled and Handle.Muzzle.FireSound.Playing then
								Handle.TriggerA_Server.WindUp:Stop()
								Handle.TriggerA_Server.WindDown:Play()
								Handle.Muzzle.FireSound:Stop()
								Handle.Muzzle.FireStop:Play()
							end
1 Like

I have read / studied the code you posted, and see some major and minor issues.

Though I wonder, if the “server freezes” you observe, are caused by the garbage collector doing much work, as it looks like, from your code, you create lots of short-living variables/objects, which the garbage collector need to ‘clean up’.

Some major issues:

1) Why do you spawn an anonymous function for your REMOTES.Gun_Shoot event on the server?

An exploiter/malicious client/player could easily fire this event many times, that build up a massive amount of “spawned functions needed to be executed”.

Shouldn’t a “shoot” event be fast / not-too-much-to-execute, if you state that “1500 bullets get fired each minute” (but how may OnServerEvents occur per minute?).

You should check / verify before thinking of spawning a function, if it actually needs to be spawned and do any work.

For instance, you have this “if not guns[Tool] then return end” as the very first statement in the spawned function. It would be much faster (also for the Roblox-engine’s garbage collector and process scheduler) to have this check and similar checks before doing a spawn of a function.

2) There are two issues I see with this part in your code:

delay(0.05,function()
  if hitChar and hitChar.Parent and _G.RagdollCheck and _G.RagdollCheck(hitChar) then
    -- ...
  end
end)

There would be no need to spawn this anonymous function, if it can be determined before that hitChar or _G.RagdollCheck will be false or nil.

And you already know that hitChar cannot be false or nil, as it is a local variable you assigned and checked earlier. (Though hitChar.Parent could become nil after those 0.05 seconds.)

(The usage of _G also looks like an “anti-pattern design” - but I guess it might be due to, not wanting / having a modulized scripts approach, or issues with designing / executing it.)

Also, why even delay this effect by 0.05 seconds? I am guessing that the code in _G.SMains.ForceChar() causes some visual “knockback”-effect on the hitChar character. - Why delay that by 0.05 seconds? - Why not just “do it now”?

Some minor issues

3) A “too late”-check

function Hit(player,tool,config,shootPos,t,bulletType)
	local hitChar = t[1]
	local hit = t[2]
[..]
	if t 
	and hitChar 
	and hitChar:IsA("Model") 
[..]	

Why checking that t is not nil (or false), when it has already been accessed before the check? - If t indeed is nil, then the t[1] will have failed with error, as t does not contain an array/table.

4) Try avoid adding code to “the current event being executed” that has nothing to do with the “current event”.

I see this in your REMOTES.Gun_Shoot.OnServerEvent code:

local c = math.random()
sCodes[Tool] = c

wait(1)

if sCodes[Tool] == c and config.MinigunEnabled and Handle.Muzzle.FireSound.Playing then
	Handle.TriggerA_Server.WindUp:Stop()
	Handle.TriggerA_Server.WindDown:Play()
	Handle.Muzzle.FireSound:Stop()
	Handle.Muzzle.FireStop:Play()
end

If I interpret this correctly, due to its location in the code-body, you want the ‘Minigun’-tool to “stop” if the client/player hasn’t fired a new shot (i.e. called Gun_Shoot event) within the next second.

Without knowing what you have designed / coded for the client, I would guess that you have made your client-script repeatedly call Gun_Shoot:FireServer() when player holds down “fire button” when using the ‘Minigun’-tool. - And then uses the above code to make the ‘Minigun’ “stop”, when player releases the “fire button”.

May I suggest you rethink your design, to differentiate the “input interface”-events among the different types of (weapon-)tools your game have, to reduce the amount of FireServer()-calls needed.

For a ´Minigun`-tool, you could make it, so there are only two events being fired to the server;

  • when player press-and-holds-down the “fire button” - (fire-event)
  • when player releases the “fire button” - (ceasefire-event)

There are no need to have any more events that those two. - The server-code should be made in such a way, that, for a ‘Minigun’-tool, when the fire-event is received, it then “spins up the minigun” and keeps the shooting-interval/-loop purely server-side. - When the ceasefire-event is received, the server-code then changes the ‘Minigun’-tool’s state into a “winding down effect”, though still with an expectation that a new fire-event can be received, before the winding down effect is finished, which has to bring the ‘Minigun’-tool’s state back into the “is firing”-state.

2 Likes

… thinking about it, there may be a need for an additional and repeated client-to-server event, in between the fire-event and ceasefire-event;

  • when player changes/moves where the tool points/aims towards - (direction-event)

This direction-event should only contain one single item; “the direction vector”, as the server-code will already know that the ‘Minigun’-tool is “active and firing”, so the server only needs to get updates of what the client/player is aiming at.

1 Like

Testing again after taking in your advise:

With normal guns (assault rifle): 2000 OnServerEvents and 65000 clients fired.
With the minigun: about 7000 OnServerEvents and 300,000 clients fired.

I think the main problem were:
-I didn’t clear out tables, so after doing hits[plr] etc etc it would just stack up with lists becoming huge.
-the delay which I forgot to remove, and the spawn at the start.

I rarely saw any lag spikes with the assault rifles, however it could be my internet too. But with the minigun occasionally as expected. I will keep testing, thank you.

1 Like