Making sure that walkspeed is dynamic?

hi so i like got my stamina system done and going, but heres the thing - i dont know how to really make sure the walkspeed stays consistent, so like, if its 16, and the player sprints, it increases by a bit, to for example, 20, and then if theres a status effect that boosts the speed, it stacks with it.

kinda like so:
16
20 - sprint
25 - sprint and status effect
21 - status effect

problem is i dont really know how to do that, aside from maybe saving the player’s walkspeed to a number value?

-- // SERVICES
local RunService = game:GetService("RunService")
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local Players = game:GetService("Players")

-- // DEPENDENCY
local RemoteEvents = ReplicatedStorage.RemoteEvents
local PlayerStaminaEvent = RemoteEvents:WaitForChild("StaminaAction", 7)

-- // MODULE
local StaminaModule = {}

StaminaModule.ActivePlayers = {}
StaminaModule.UpdateLoop = nil

-- // CONFIGURATION <<<
local LOOP_RATE = 1/4
local REGEN_DELAY = 2 -- seconds
local REGEN_RATE = .25 -- per loop
local DRAIN_RATE = .15 -- per loop

local BASE_SPRINT_SPEED = 20

------------------------->>>


local function getSprintSpeed(humanoid)
	local stat = humanoid:FindFirstChild("Stats")
	if stat then
		local sprintSpeed = stat:GetAttribute("SprintSpeed")
		if sprintSpeed then
			return sprintSpeed
		end
	end
	return BASE_SPRINT_SPEED
end

function StaminaModule:SetRunning(player, state)
	local data = self.ActivePlayers[player]
	if data then
		data.IsRunning = state
	end
end

function StaminaModule:GetStamina(player)
	local character = player.Character
	local humanoid = character:FindFirstChildOfClass("Humanoid") :: Humanoid
	return humanoid:GetAttribute("Stamina") or 0
end

function StaminaModule:SetMaxStamina(player, amount)
	local character = player.Character
	local humanoid = character:FindFirstChildOfClass("Humanoid") :: Humanoid
	humanoid:SetAttribute("MaxStamina", amount)
end

function StaminaModule:SetStamina(player, amount)
	local character = player.Character
	local humanoid = character:FindFirstChildOfClass("Humanoid") :: Humanoid
	humanoid:SetAttribute("Stamina", amount)
end

function StaminaModule:AdjustMaxStamina(player, amount)
	local character = player.Character
	local humanoid = character:FindFirstChildOfClass("Humanoid") :: Humanoid
	local maxStamina = humanoid:GetAttribute("MaxStamina")
	if maxStamina then
		humanoid:SetAttribute("MaxStamina", maxStamina + amount)
	end
end

function StaminaModule:AdjustStamina(player, amount)
	local character = player.Character
	local humanoid = character:FindFirstChildOfClass("Humanoid") :: Humanoid
	local max= humanoid:GetAttribute("MaxStamina") or 100
	humanoid:SetAttribute("Stamina",math.clamp((humanoid:GetAttribute("Stamina") or max)+amount,0,max))
end

function StaminaModule:Initialize()
	if self.UpdateLoop then self.UpdateLoop:Disconnect() end
	
	StaminaModule.UpdateLoop = RunService.Heartbeat:Connect(function()
		for player, data in next, self.ActivePlayers do
			local character = player.Character :: Model
			local humanoid = character and character:FindFirstChildOfClass("Humanoid")
			if not humanoid or humanoid.Health <= 0 then continue end
			if player:GetAttribute("Menu") == true then continue end
			
			
			local currentTime = tick()
			local baseSpeed = getSprintSpeed(humanoid)
			if data.IsRunning then
				self:AdjustStamina(player, -DRAIN_RATE)
				data.LastWalkSpeed = humanoid.WalkSpeed
				humanoid.WalkSpeed = humanoid.WalkSpeed + baseSpeed
				data.LastDrainTime = currentTime
			else
				if currentTime - (data.LastDrainTime or 0) >= REGEN_DELAY then
					self:AdjustStamina(player, REGEN_RATE)
					humanoid.WalkSpeed = humanoid.WalkSpeed - baseSpeed
				end
			end
			print(player.Name, self:GetStamina(player))
		end
		task.wait(LOOP_RATE)
	end)
end

local function OnPlayerStaminaEvent(plr,data)
	if typeof(data)=="table" and data.Action~=nil then
		StaminaModule:SetRunning(plr,data.Action)
	end
end

local function OnPlayerJoin(plr)
	local char = plr.Character or plr.CharacterAdded:Wait()
	local hum = char:WaitForChild("Humanoid") :: Humanoid
	StaminaModule.ActivePlayers[plr]={
		IsRunning=false,
		LastDrainTime = 0,
		LastWalkSpeed = hum.WalkSpeed
	}
end

local function OnPlayerLeave(plr)
	StaminaModule.ActivePlayers[plr]=nil
end

Players.PlayerAdded:Connect(OnPlayerJoin)
Players.PlayerRemoving:Connect(OnPlayerLeave)
PlayerStaminaEvent.OnServerEvent:Connect(OnPlayerStaminaEvent)

return StaminaModule

as you can see i tried adding it and subtracting it like that but it dont workk for obvious reasons

please disregard the code other than Initialize and other necessary functions. thank you

Have a base value that doesnt change and a speed multiplier that does change. So to increase speed, you just take the Basespeed x Speed multiplier and set whatever it is to the Walkspeed. If you want a static speed buff, as in just adding to the speed regardless of the basespeed, you can just set up another variable such as speedAdd: Basespeed x Walkspeed+SpeedAdd

First

  • make a base speed constant variable(no sprint, no effect): local BASE_WALK_SPEED = 16

Second

  • make a sprint extra speed constant variable: local SPRINT_EXTRA_WALK_SPEED = 5

Third

  • define some status effects in a table for extra speed also:
local EXTRA_SPEED_STATUS_EFFECTS = {
	Tier1 = 3,
	Tier2 = 8,
}

Fourth

  • make a main function that calculates the speed based on the 2 factors:
    • player is sprinting?
    • player active status effects
local function GetWalkSpeed(player)
	local playerStaminaData = StaminaModule.ActivePlayers[player]
	
	local speed = BASE_WALK_SPEED
	if playerStaminaData.IsRunning then
		speed += SPRINT_EXTRA_WALK_SPEED 
	end

	if playerStaminaData.HasTier1Effect then
		speed += EXTRA_SPEED_STATUS_EFFECTS.Tier1
	end
	if playerStaminaData.HasTier2Effect then
		speed += EXTRA_SPEED_STATUS_EFFECTS.Tier2
	end

	return speed
end

Fifth

  • use the GetWalkSpeed function every time the IsRunning get changed, or effect changes.

Adapt to your needs, but this is the main idea behind the sprint system. Just make a function that recalculates the speed every time the changes occur.

I took all the AI’s way of doing things out of this. AI believes the task it’s working on for you is the only thing going and it’s fine to run the wheels off the cycles. This will not do. For all its effort, in the end it placed the Module where it could easily be hacked. Confidently wrong AI 101.
Everything is event-driven, no AI quick fixes, heartbeat bandaids, or continuous-cycle sucking loops.

Module
--ModuleScript within the ServerScript in ServerScriptService
local Players = game:GetService("Players")
local ReplicatedStorage = game:GetService("ReplicatedStorage")

local PlayerStaminaEvent = ReplicatedStorage:WaitForChild("RemoteEvents"):WaitForChild("StaminaAction")
local StaminaModule = {}
StaminaModule.ActivePlayers = {}

local BASE_WALK_SPEED = 16
local BASE_SPRINT_SPEED = 20
local DRAIN_RATE = 0.15
local REGEN_RATE = 0.1
local REGEN_DELAY = 2

function StaminaModule:Initialize(player, humanoid)
	if self.ActivePlayers[player] then return end
	self.ActivePlayers[player] = {
		IsRunning = false,
		StatusEffectSpeed = 0,
		Stamina = humanoid:GetAttribute("Stamina") or 100,
		MaxStamina = humanoid:GetAttribute("MaxStamina") or 100,
		LastDrainTime = 0,
		Humanoid = humanoid
	}
	humanoid.WalkSpeed = BASE_WALK_SPEED
end

function StaminaModule:SetRunning(player, state)
	local data = self.ActivePlayers[player]
	if not data then return end
	local humanoid = data.Humanoid
	if not humanoid then return end
	data.IsRunning = state

	if state and data.Stamina > 0 then
		data.Stamina = math.max(data.Stamina - DRAIN_RATE, 0)
		humanoid:SetAttribute("Stamina", data.Stamina)
		humanoid.WalkSpeed = BASE_SPRINT_SPEED + data.StatusEffectSpeed
		data.LastDrainTime = tick()
	else
		humanoid.WalkSpeed = BASE_WALK_SPEED + data.StatusEffectSpeed
		if tick() - (data.LastDrainTime or 0) >= REGEN_DELAY then
			data.Stamina = math.min(data.Stamina + REGEN_RATE, data.MaxStamina)
			humanoid:SetAttribute("Stamina", data.Stamina)
		end
	end
end

PlayerStaminaEvent.OnServerEvent:Connect(function(player, data)
	if typeof(data) == "table" and data.Action ~= nil then
		StaminaModule:SetRunning(player, data.Action)
	end
end)

Players.PlayerRemoving:Connect(function(player)
	StaminaModule.ActivePlayers[player] = nil
end)

return StaminaModule
ServerScript
--ServerScript in ServerScriptService with the ModuleScript within
local Players = game:GetService("Players")
local StaminaModule = require(script:WaitForChild("StaminaModule"))

for _, player in pairs(Players:GetPlayers()) do
	local char = player.Character
	if char then
		local hum = char:FindFirstChildOfClass("Humanoid") or char:WaitForChild("Humanoid", 5)
		if hum then
			StaminaModule:Initialize(player, hum)
		end
	end
end

Players.PlayerAdded:Connect(function(player)
	local char = player.Character or player.CharacterAdded:Wait()
	local hum = char:FindFirstChildOfClass("Humanoid") or char:WaitForChild("Humanoid", 5)
	if hum then
		StaminaModule:Initialize(player, hum)
	end
end)
LocalScript
--LocalScript in StarterPlayerScripts
local Players = game:GetService("Players")
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local PlayerStaminaEvent = ReplicatedStorage.RemoteEvents:WaitForChild("StaminaAction")
local UserInputService = game:GetService("UserInputService")

local player = Players.LocalPlayer

UserInputService.InputBegan:Connect(function(input, processed)
	if processed then return end
	if input.KeyCode == Enum.KeyCode.LeftShift then
		PlayerStaminaEvent:FireServer({Action = true})
	end
end)

UserInputService.InputEnded:Connect(function(input)
	if input.KeyCode == Enum.KeyCode.LeftShift then
		PlayerStaminaEvent:FireServer({Action = false})
	end
end)

You need a list of all effects.

A good analogy would be a folder that has effect number values and every time something is added or removed, recalculate the speed from the base with all the effects.

I do this with a module though. Since I have a lot of things that want to change things like walk speed I have a module take requests and priorities and calculate it based on the highest priority speed change request (newest on tie) and every time a speed gets added or removed I check if something is the new priority. It then looks at the requested modifiers and calculates the real speed off of those also uses them adding removing to know when to recalculate.

A bit more complex than directly working with speed but it handles conflicts better

Maybe you can add a folder with NumberValues and add those values into it.

local Speed = 16

– loop through folder, add Speed+=Value.Value

hum.speed = Speed

this is how I did it atleast.

Or you could actually test what took me an hour to put together for you and know for sure that it works perfectly. I don’t need a solution; I know how this goes all too well.