How do I make it in server side without lags?

With what I am not satisfied?

Hello! So I am making rpg game which is about a bullet hell game which means there will be a lot of projectiles different types. I have been figuring out how to make good projectiles for a year. And I think im pretty close enough to it! However the reason why I want you to review my code is because projectiles made on the client side. I have been trying it do on server side but it would be too laggy if there would be a lot of knives. I want to know how I can try to make it on server side.

What my code does?

My code uses a module to create projectile on client side. Client side have a caster argument which contains 1 which is max. It depends on the player, if player is the creator projectile then caster will be equal to 1, however if not then it gonna contain ANYTHING expect 1 because there no really any sense at it. It detects hit on both casters, but damage does only on caster which equals to 1. one of the reasons why I want make it on server side - damages goes through remote event.

Code

COMBAT MODULE

local module = {}

function module:CreateHitbox(CFrame: CFrame, Size: Vector3, Params: OverlapParams, Visual: BoolValue)
	
end

function module:CreateProjectile(Character: Model, Projectile: StringValue, ProjectileCFrame: CFrame, Speed: NumberValue, Debris :NumberValue)
	local overparams = OverlapParams.new()
	overparams.FilterDescendantsInstances = {workspace.EffectsFolder}
	overparams.FilterType = Enum.RaycastFilterType.Blacklist
	
	game.ReplicatedStorage.Events.Projectile_Events[Projectile]:FireClient(game.Players[Character.Name],Character,ProjectileCFrame,Speed,Debris,1)
	
	local ProjectileCreateHitbox = game.Workspace:GetPartBoundsInRadius(ProjectileCFrame.Position,100,overparams)

	for index, hit in pairs(ProjectileCreateHitbox) do
		if hit.Name == "HumanoidRootPart" then
			if game.Players:GetPlayerFromCharacter(hit.Parent) and hit.Parent.Name ~= Character.Name then
				game.ReplicatedStorage.Events.Projectile_Events[Projectile]:FireClient(game.Players:GetPlayerFromCharacter(hit.Parent),Character,ProjectileCFrame,Speed,Debris)
			end
		end
	end
end

function module:CreateProjectileClientSide(character,projectile,cframe,speed,debris,dmg,caster)
	local timeStopped = false
	local DebrisTime = 0
	
	while game:GetService("RunService").Heartbeat:Wait() do
		if game.ReplicatedStorage.TimeStop.Value == false then
			local oldPos = projectile.CFrame
			local newPos = projectile.CFrame * CFrame.new(0,0,-speed)		

			local posDifference = (oldPos.Position-newPos.Position).Magnitude
			local params = RaycastParams.new()
			params.FilterDescendantsInstances = {character,workspace.EffectsFolder}

			local ray = workspace:Raycast(oldPos.Position,oldPos.lookVector*posDifference,params)

			DebrisTime += .1

			if DebrisTime >= debris then
				projectile:Destroy()
				print("death")
				break
			end

			if ray then
				local lookVector = projectile.CFrame.LookVector

				print(projectile.Name.."has hitted "..ray.Instance.Name.."!")
				projectile.CFrame = CFrame.new(projectile.CFrame.Position,ray.Position)*CFrame.new(0,0,-ray.Distance/2)

				if caster == 1 then
					if ray.Instance.Parent:FindFirstChild("Humanoid") then
						warn("Target has a humanoid, damaging.")
						game.ReplicatedStorage.Events.DamageEvent:FireServer(ray.Instance.Parent,dmg)
					end
				end

				game.Debris:AddItem(projectile,2)
				break
			else
				projectile.CFrame = projectile.CFrame * CFrame.new(0,0,-speed)
			end
		else
			warn(projectile.Name.." has been frozen. Reason: Time Stop.")
			timeStopped = true
			break
		end

	end

	if timeStopped == true then
		local changedConnection
		changedConnection = game.ReplicatedStorage.TimeStop.Changed:Connect(function()
			changedConnection:Disconnect()
			local projectileCFrame = projectile.CFrame

			module:CreateProjectileClientSide(character,projectile,projectileCFrame,speed,debris,dmg,caster)
		end)
	end
end
return module

SERVER SIDE SCRIPT

local cmod = require(game.ReplicatedStorage.Modules.CombatModule)

game.ReplicatedStorage.Events.SpellCardEvents.Sakuya.KillingDollEvent.OnServerEvent:Connect(function(player,status,mousePos,angle)
	if status == "Message" then
		game.ReplicatedStorage.Events.VFX_Events.SignEvent:FireAllClients(player.Character.HumanoidRootPart,"Sakuya",2)
		
		local s = script.UseSound:Clone()
		s.Parent = player.Character.HumanoidRootPart
		s:Play()
		game.Debris:AddItem(s,2)
		
		local params = OverlapParams.new()
		params.FilterDescendantsInstances = {workspace.EffectsFolder}
		params.FilterType = Enum.RaycastFilterType.Blacklist
		
		local hit = game.Workspace:GetPartBoundsInRadius(player.Character.HumanoidRootPart.CFrame.Position,50,params)
		
		for i,v in pairs(hit) do
			if v.Name == "HumanoidRootPart" then
				if game.Players:GetPlayerFromCharacter(v.Parent) then
					game.ReplicatedStorage.Events.VFX_Events.SpellcardMessage:FireClient(game.Players:GetPlayerFromCharacter(v.Parent),'Illusion Sign "Killing Doll"',Color3.new(1,0,0))
				end
			end
		end
	else
		local char = player.Character
		local hum = char.Humanoid
		local hrp = char.HumanoidRootPart
		local ProjectileCFrame = hrp.CFrame

		wait()

		ProjectileCFrame = hrp.CFrame*CFrame.fromEulerAnglesXYZ(0,math.rad(angle),0)*CFrame.new(0,0,-5)
		ProjectileCFrame = CFrame.new(ProjectileCFrame.Position,mousePos)
		angle += 30

		local ray = Ray.new(ProjectileCFrame.Position,mousePos)
		local hit,pos = workspace:FindPartOnRay(ray,hrp)

		wait(.75)

		cmod:CreateProjectile(char,"SakuyaBlueKnife",ProjectileCFrame,2,10)
	end
end)

game.ReplicatedStorage.Events.SpellCardEvents.Sakuya.TimestopEvent.OnServerEvent:Connect(function(player)
	if game.ReplicatedStorage.TimeStop.Value == false then
		game.ReplicatedStorage.Events.VFX_Events.SignEvent:FireAllClients(player.Character.HumanoidRootPart,"Sakuya",2)
		
		local s = script.UseSound:Clone()
		s.Parent = player.Character.HumanoidRootPart
		s:Play()
		game.Debris:AddItem(s,2)
		
		local params = OverlapParams.new()
		params.FilterDescendantsInstances = {workspace.EffectsFolder}
		params.FilterType = Enum.RaycastFilterType.Blacklist

		local hit = game.Workspace:GetPartBoundsInRadius(player.Character.HumanoidRootPart.CFrame.Position,50,params)

		for i,v in pairs(hit) do
			if v.Name == "HumanoidRootPart" then
				if game.Players:GetPlayerFromCharacter(v.Parent) then
					game.ReplicatedStorage.Events.VFX_Events.SpellcardMessage:FireClient(game.Players:GetPlayerFromCharacter(v.Parent),'Illusion World "'..player.Name.."'s World"..'"',Color3.new(1,0,0))
				end
			end
		end
		wait(1)
	end
	game.ReplicatedStorage.TimeStop.Value = not game.ReplicatedStorage.TimeStop.Value
	game.Lighting.ColorCorrection.Enabled = game.ReplicatedStorage.TimeStop.Value
end)

CLIENT SIDE SCRIPT

local ProjectileEvents = game.ReplicatedStorage.Events.Projectile_Events
local cmod = require(game.ReplicatedStorage.Modules.CombatModule)
local player = game.Players.LocalPlayer
local dmg = 2


ProjectileEvents.SakuyaBlueKnife.OnClientEvent:Connect(function(character,cframe,speed,debris,caster)
	local timeStopped = false
	local DebrisTime = 0
	local knife = game.ReplicatedStorage.Assets.Sakuya.SakuyaBlueKnife:Clone()
	knife.MainTrail.Enabled = true
	knife.CFrame = cframe
	knife.Parent = workspace.EffectsFolder

	cmod:CreateProjectileClientSide(character,knife,cframe,speed,debris,dmg,caster)
end)

CODE DEMONSTRATION
Normal projectiles

Projectiles created while time stop

*Projectiles that has been time stopped

LAG TEST
That’s a client side. Imagine if this would be on server side.

1 Like

Looks good!

And yes, you should make it fully server-sided as exploiters can easily modify local scripts or modules that are not in the server.

Try limiting the amount of knives that can be thrown at once, as alot of unanchored objects can break the game.

1 Like

Thanks! I will be nerfing this in future. The problem is what if there will be in example 20 players that will use this at once?

Use

game.Players.PlayerAdded:Connect(function(plr)
     plr.CharacterAdded:Connect(function(char)
          --Now you can get the character and the player through the server
end)
end)

I told that I have been trying make these projectiles on server side. Result: Too laggy.

Oh you want them on the client side?

No, I mean I want try make it on server side somehow. I just don’t have idea how to make it not too laggy (like on client side).

Off the top of my head here are a couple things you could do to optimize this a bit:

  • Create a caching system for the projectile models, that way you can reuse old projectile models without having to clone or create new ones.

  • Throttle the update rate of the projectile model depending on the amount or proximity of active projectiles.
    – This would require you compensate for the time lost between updates

  • Treat large groups of closely oriented projectiles as a single projectile and hitbox

1 Like

I think you should handle the security on the server, with everything else on the client. (The :Clone()ing of knives, the projectile tweening, etc)

Make the local client calculate if the knife is able to be thrown and then have the server double check if the client says it’s okay. This way the server won’t check unless you need it to and you also won’t let exploiters have control. It’s a mix of dont trust the client and trusting the client, but in the end you, the server, will have the last say.

@jbjgang2’s suggestions are really good too.
For caching:
Instead of calling :Destroy() on a knife when it’s finished, use :SetPrimayPartCFrame on it and send it to Vector3.new(0,0,9e9). Objects out that far don’t render on the camera (9e9 might be too far, multi-instanced models might break down and distort in terms of size and orientation, if you have streamingenabled, you can set it much closer).

Then instead of calling :Clone() to create a new knife, just use :SetPrimaryPartCFrame on a cached knife and reuse it.

6 Likes

Why do you want to put this on the server? Most games with projectiles put them on the client

Reason is simple. Exploiters. They can easly modify code + damage remote event basically let’s them make a command to kill everyone with a snap. However I already transformed it into server side and I love the current result.

Doing it on the server has its own downside, one of them is delay. Not only there will be a delay when attacking, the target the player is aiming at is also delayed. It’s just not ideal. I wonder how you deal with it? I feel like the lost of player experience doesn’t worth the security gained.

In games outside of Roblox, lag compensation is used, where the server steps back in time by the player’s ping to make it more fair, and where the client shoots it’s own decoy projectile to make it feel more responsive.

Honestly, you shouldn’t do anything like this on the server. My idea:

When the player attacks in your local script, fire a remote, then on the server, when that remote is fired, fire a remote in all players, and replicate it that way. Therefore, the lag is only clientsided. Ofcourse, you should still predict damage and stuff on the server.

So basically:

Player attacks, the remote gets passed over to the server with where the player was attacking

Server recieves, passes the info on to all the clients to make da projectiles

1 Like

I have no idea how it works but I used cache like people above have said. If you wonder what I used I used PartCache. It reduced lag so it became much smoother.

nice (( very cool runservice) roblox [char limit])

1 Like

I would suggest using less projectiles per combat move because you have a huge amount of projectiles in one location with in the workspace.

1 Like