Concerns within creating multiple server-side connections handling players

I’m concerned about any future problems regarding creating multiple Heartbeat connections server-side, that handle players’ functions and attributes. For example, when one specific player presses the shift button locally, it fires a remote event that handles the player sprint. However, I’m using RunService to decrease the player’s stamina, meaning any player that starts sprinting will also create a connection, and so on. Another example being implemented in my game is the ‘AverageSpeed’ calculator function I’m using in my game. This function basically determines how fast the player is moving (studs/time): I’m also creating RunService connections for each character that joins/reloads in the game.

The main problem is, all those multiple connections would be really weighty to the game, as I’m creating connections for each player, which is bad. It may be possible to create a connection running locally, and then firing a remote event that updates the player stats. However, there are two visible problems for me:

  • The player will send a lot of information to the server in a small quantity of times, multiple times, as I’m creating a loop running infinitely that fires the remote event.
  • There’s a chance players’ exploit and ruins everything.

If you are wondering what the scripts are, take a look at them:

Sprint Script:

--// Useful part

local function animate(char, hum, run)
	connection = RS.Heartbeat:Connect(function(dt)
		acc += dt

		if acc >= frametick then
			acc = 0

			local stam = char:GetAttribute("Stamina")

			local moveDirection = hum.MoveDirection
			local isMoving = moveDirection.Magnitude > 0

			if run then
				if not isMoving then
					handleSprint(char, hum)
					return
				end

				loseStam(char, hum, stam)
			else
				regenStam(char, stam, isMoving)
			end
		end
	end)
end

local function changeStamina(char : Model, hum, run)
	if connection and connection.Connected then
		connection:Disconnect()
	end

	animate(char, hum, run)
end

handleSprint = function(char : Model, hum : Humanoid, run : boolean?, finishedStamina : boolean?)
	local targetSpeed = run and 16 or 10 do
		targetSpeed = finishedStamina and 6 or targetSpeed
	end

	changeStamina(char, hum, run)
	sprint.animateSpeed(char, hum, targetSpeed)
end

sprintRemote.OnServerEvent:Connect(function(plr, run)
	if not canSprint then return end

	local char, hum = getComponentes(plr)

	if not hum then return end

	local moveDirection = hum.MoveDirection
	local isMoving = moveDirection.Magnitude > 0

	if not isMoving then return end

	handleSprint(plr, hum, run)
end)

--// Rest of Script

Average Speed:

--// Module
local RS = game:GetService("RunService")

return function(char : Model)
	local hum = char:WaitForChild("Humanoid")
	local HRP = char:WaitForChild("HumanoidRootPart") :: BasePart

	local previousPosition = HRP.Position

	RS.Heartbeat:Connect(function(DT)
		local currentPosition = HRP.Position

		local delta = currentPosition - previousPosition

		delta = Vector3.new(delta.X, 0, delta.Z)

		local distance = delta.Magnitude

		char:SetAttribute("AverageSpeed", distance/DT)

		previousPosition = currentPosition
	end)
end

--// ServerScript

--// Useful part

local function initializeCharacter(char : Model)
	local rawWalkSpeed do
		rawWalkSpeed = Instance.new("IntValue")

		rawWalkSpeed.Name = "RawWalkSpeed"
		rawWalkSpeed.Value = 10
		rawWalkSpeed.Parent = char
	end

	char:SetAttribute("Stamina", 100)
	char:SetAttribute("AverageSpeed", 0)
	
	averageSpeed(char) -- AverageSpeed is the function inside of a Module I just sent 
end

plrs.PlayerAdded:Connect(function()
     plr.CharacterAdded:Connect(initializeCharacter)
end)

As you can see, both scripts create a lot of connections for each player. So, my question is, how can I do that without damaging the game’s efficiency?

It is widely recommended to handle all movement on the client, including stamina. If you’re worried about cheating, the best solution is creating some sort of anti-cheat

2 Likes

I would handle the sprinting mechanic in client and firing a remote event to change stamina and walkspeed?

No, just fully handle the stamina on the client. If someone really wanted to cheat, they would just set their walk walkspeed to 100, and they could walk-run everywhere.

So I would set the stamina on the client? But wouldn’t it be dangerous since I could set my stamina 1000000 and I would be able to run infinitely?

Everybody handles stamina and other movement-related stuff on the client. If you’re really against people speed-hacking, then add an anti-cheat.

As for the people editing the stamina duration, just don’t use attributes or other instance-type values; make it built-in to the script so it doesn’t get tampered with after execution, and people would still just remove your stamina and sprint scripts and add their own.

What about walkspeed, would I fire a remote event to change it? Or would it be client side too

No, typically you should handle movement and other stuff that are visual on the client and have an anti-cheat in the game to detect speed hacks and so on.

At least that’s what I do on my games

But if I store the stamina as a variable, I wouldn’t be able to locate it in another script (for example, stamina bar system)

If I change player’s walkspeed locally, would the server replicate it automatically?

You can actually use module scripts for this; make the stamina module have functions to return the variables you need, and to access the modules that are executed without requiring another one is by using a module loader.

If you haven’t got this set up, I really recommend it, but if you don’t have the time for it (it will take a bit to set up), then just store the variables in instance-type values.

I could link you to a module loader tutorial that’s pretty easy to follow.

2 Likes

The server creates and owns the character model, and the client can simulate input and physics locally (like movement).

TL;DR: Yeah, it will replicate.

(removed the other part to make it less confusing)

3 Likes

What is a module loader? I’ve never heard this name before

Here’s a tutorial on how to create your own module loader.

And here’s a video on how to set up your module system.

Basically, what module loaders are:

  • It’s a script/system that manages how your modules load.
  • It can automatically require modules, cache them, and/or handle dependencies between modules.

(This is what I meant by using the module loaders to require certain variables from a script during runtime.)

1 Like

@Omnipotent_Corrupted

Have I solved your problem? I’m going to log off in a couple of minutes, and I won’t be replying anymore. And if I didn’t, I really hope that someone else can come and help you.

Take care.

1 Like

Thank you so much!! I’ll take a look and study what you’ve said to me. This helped a lot.

Really glad I’ve managed to help you!

Take it easy, and good luck with your game!

1 Like

Hey there! I’ve already found the solution, but can you elaborate on how I would make a stamina variable shared across my scripts so I can make the bar size smaller/bigger depending on the stamina value, for example?

A module is basically a table after requiring it. your structure would look somewhat like this in the module:

local data = {} -- this can have any name
data.Stamina = 100
data.MaxStamina = 100
return data

You can also structure it like:

local data = {
    Stamina = 100,
    MaxStamina = 100
}
return data

To get the values u should do something like this:

local staminaStuff =  require(PATH_TO_MODULE)
print(staminaStuff.Stamina) -- prints 100
2 Likes

What @Dankinations said is also correct, but if you want to specifically get variables from a module that’s already been required and cached by your loader, meaning it won’t re-run and will just return the same reference, you should do something like this, assuming you already set up your module loader and module structure:

For example, let’s say this is your sprint module:

local Sprint = {}

--// You can’t directly access local variables from another script.
--// To make them accessible, you'd have to store them in the Sprint table (global), not locally.
--// I'll show both styles here.

--// Local (not accessible from outside)
local stamina_max = 100
local stamina_min = 0
local stamina_cur = stamina_max

--// Global (accessible directly via Sprint.stamina_max (annotations))
-- Sprint.stamina_max = 100
-- Sprint.stamina_min = 0
-- Sprint.stamina_cur = stamina_max

--// If you use global variables, you can access them directly.
--// If you use local variables, you'll need to provide getter functions.
--// In this example, we’ll stick with local + getter functions.

-- example sprint functions
function Sprint.abc()
    -- Sprint logic here
end

-- Getter functions for accessing locals
function Sprint.getCurrentStamina()
    return stamina_cur
end

function Sprint.getMaxStamina()
    return stamina_max
end

--etc
return Sprint

Now, with those getters being set up. What you need to do is simply use your module loader to use the already executed modules for those functions instead of requiring a new version of the already running Sprint module.


Here’s another example of using your module loader to get modules without requiring them after they’ve been executed by your module loader:

Module Loader (from the video I sent)

local ModuleLoader = {}
local CachedModules = {}

function ModuleLoader.Get(name : string)
	return CachedModules[name]
end

function ModuleLoader._Init(scripts : {ModuleScript})
	for _, modscript in scripts do
		if not modscript:IsA("ModuleScript") then continue end
		local mod = require(modscript)
		CachedModules[modscript.Name] = mod
	end
end

function ModuleLoader._Start()
	for _, mod in CachedModules do
		if mod.Start then
			task.spawn(mod.Start)
		end
	end
end

-- Global accessor to modules
shared.Get = ModuleLoader.Get

return ModuleLoader

Getting the Sprint module (after it’s been initialized):

-- Let's say this is your other script that needs to access your stamina variables.

-- Get the cached Sprint module using the global accessor
local Sprint = shared.Get("Sprint")

--// Example Usage
local function useStaminaVariableExample()
	local stamina_cur = Sprint.getCurrentStamina()

	-- You *could* do this if the variable was global:
	-- local stamina_max = Sprint.stamina_max

	-- But since we’re using locals, we access it through the getter
	local stamina_max = Sprint.getMaxStamina()
end

This is just a simple example to show you how you could use module loaders to access variables from modules without re-requiring them. All using your own modules and module loader.

1 Like