Stamina drains (and restores) much faster in-game

So, I’m making a forsaken inspired game. obviously one of the key components is sprinting, so I have set up a stamina system. Now I have a problem where in-studio, it drains normally, but in-game it drains and restores WAY faster than intended. If someone can let me know what is causing this I would be happy to attempt to fix it myself.

Here’s the problem:

Studio:

In-game:

Here’s my code:

local repStorage = game:GetService("ReplicatedStorage")
local players = game:GetService("Players")
local debris = game:GetService("Debris")

local events = repStorage:WaitForChild("Events")
local functions = repStorage:WaitForChild("Functions")
local info = workspace:WaitForChild("Info")

local skins = repStorage:WaitForChild("Skins")
local killers = repStorage:WaitForChild("Killers")
local survivors = repStorage:WaitForChild("Survivors")
local maps = repStorage:WaitForChild("Maps")

local assets = repStorage:WaitForChild("Assets")

local state = "Intermission"
local currentMap = nil

local currentKiller = nil

function playerAdded(plr:Player)
	local selectedKiller = Instance.new("ObjectValue", plr)
	selectedKiller.Name = "Killer"
	selectedKiller.Value = killers.Corrupted
	
	local killerSkin = Instance.new("ObjectValue", plr)
	killerSkin.Name = "KillerSkin"
	killerSkin.Value = nil --skins.Killer.Corrupted.Nab
	
	local selectedSurvivor = Instance.new("ObjectValue", plr)
	selectedSurvivor.Name = "Survivor"
	selectedSurvivor.Value = survivors.Jack
	
	local leaderstats = Instance.new("Folder", plr)
	leaderstats.Name = "leaderstats"
	
	local money = Instance.new("NumberValue", leaderstats)
	money.Name = "Money"
	
	local corruption = Instance.new("NumberValue", leaderstats)
	corruption.Name = "Corruption"
	
	local inRound = Instance.new("BoolValue", plr)
	inRound.Name = "InRound"
	
	plr.CharacterAppearanceLoaded:Connect(function(char)
		plr:WaitForChild("InRound").Value = false
		plr.PlayerGui:WaitForChild("GameGui").Enabled = false
	end)
	
	plr.CharacterAdded:Connect(function(char)
		char:SetAttribute("Speed", 16)
		char:SetAttribute("Running", false)
		--if inGame.Value == true then
		char:SetAttribute("MaxStamina", char:GetAttribute("Stamina"))
		--end
		
		local statusEffects = Instance.new("Folder", char)
		statusEffects.Name = "StatusEffects"
		
		local speedMult = Instance.new("NumberValue", char)
		speedMult.Name = "SpeedMultiplier"
		speedMult.Value = 1
		
		statusEffects.ChildAdded:Connect(function(effect:NumberValue)
			local applied = true
			
			coroutine.wrap(function()
				repeat
					task.wait()
				until not applied
			end)()
			
			if effect.Name == "Speed 1" then
				speedMult.Value += 0.25
			end
			
			if effect.Name == "Speed 2" then
				speedMult.Value += 0.5
			end
			
			if effect.Name == "Speed 3" then
				speedMult.Value += 1
			end
			
			if effect.Name == "Blindness" then
				events.Remote.Blur:FireClient(plr, true)
			end
			
			for i=effect.Value, 1, -1 do
				task.wait(1)
			end
			
			if effect.Name == "Speed 1" then
				speedMult.Value -= 0.25
			end
			
			if effect.Name == "Speed 2" then
				speedMult.Value -= 0.5
			end

			if effect.Name == "Speed 3" then
				speedMult.Value -= 1
			end
			
			if effect.Name == "Blindness" then
				events.Remote.Blur:FireClient(plr, false)
			end
			
			applied = false
			
			effect:Destroy()
		end)
		
		while task.wait() do
			char.Humanoid.WalkSpeed = char:GetAttribute("Speed")
		end
	end)
	
	for i, player in game.Players:GetPlayers() do
		if player ~= plr then
			player:WaitForChild("leaderstats"):WaitForChild("Corruption").Value += 1
		end
	end
end

function setInfo(timer)
	for i, plr in players:GetPlayers() do
		local gui = plr.PlayerGui
		
		gui:WaitForChild("InfoGui").Info.Text = state .. ": " .. timer
	end
end

function updateStats()
	for i, plr in players:GetPlayers() do
		local gui = plr.PlayerGui
		
		if plr:WaitForChild("InRound").Value == true and plr.Character:GetAttribute("Stamina") ~= nil then
			gui.GameGui.Stats.Stamina.Text = math.floor(plr.Character:GetAttribute("Stamina")) .. "/" .. plr.Character:GetAttribute("MaxStamina")
			gui.GameGui.Stats.Health.Text = math.floor(plr.Character.Humanoid.Health) .. "/" .. plr.Character.Humanoid.MaxHealth
		end
	end
end

function getNextKiller()
	local killer = nil
	local low = -1
	for i, plr in players:GetPlayers() do
		if plr:WaitForChild("leaderstats"):WaitForChild("Corruption").Value > low then
			low = plr.leaderstats.Corruption.Value
			killer = plr
		end
	end
	return killer
end

function setupRound()
	local selectedMap = maps:GetChildren()[math.random(1, #maps:GetChildren())]:Clone()
	
	selectedMap.Parent = workspace.CurrentMap
	currentMap = selectedMap
	
	local killer = getNextKiller()
	killer:WaitForChild("leaderstats"):WaitForChild("Corruption").Value = 0
	
	for i, plr in players:GetPlayers() do
		plr:WaitForChild("InRound").Value = true
		if plr ~= killer then
			local char = plr.Character
			local spawns = selectedMap:FindFirstChild("Spawns")

			local selectedSpawn:BasePart = spawns:GetChildren()[math.random(1, #spawns:GetChildren())]

			local newChar = plr.Survivor.Value:Clone()
			plr.Character = newChar
			newChar.Parent = workspace
			
			plr:WaitForChild("leaderstats"):WaitForChild('Corruption').Value += 1

			newChar:PivotTo(selectedSpawn.CFrame)
			newChar.Humanoid.WalkSpeed = newChar:GetAttribute("WalkSpeed") * char.SpeedMultiplier.Value
		else
			local char = plr.Character
			local spawns = selectedMap:FindFirstChild("Spawns")

			local selectedSpawn:BasePart = spawns:GetChildren()[math.random(1, #spawns:GetChildren())]

			if plr.KillerSkin.Value ~= nil then
				local newChar = plr.KillerSkin.Value:Clone()
				plr.Character = newChar
				newChar.Parent = workspace

				currentKiller = plr
				info.CurrentKiller.Value = plr

				newChar:PivotTo(selectedSpawn.CFrame)
				newChar.Humanoid.WalkSpeed = newChar:GetAttribute("WalkSpeed") * char.SpeedMultiplier.Value
			else
				local newChar = plr.Killer.Value:Clone()
				plr.Character = newChar
				newChar.Parent = workspace

				currentKiller = plr
				info.CurrentKiller.Value = plr

				newChar:PivotTo(selectedSpawn.CFrame)
				newChar.Humanoid.WalkSpeed = newChar:GetAttribute("WalkSpeed") * char.SpeedMultiplier.Value
			end
		end
		
		plr.PlayerGui:WaitForChild("GameGui").Enabled = true
	end
end

function endRound()
	currentMap:Destroy()
	
	for i, plr in players:GetPlayers() do
		plr:LoadCharacter()
	end
	
	currentKiller = nil
	info.CurrentKiller.Value = nil
end

function gameLoop()
	while task.wait() do
		state = "Waiting for players"
		repeat setInfo(#players:GetPlayers()) task.wait() until #players:GetPlayers() > 0
		state = "Intermission"
		for i=0, 0, -1 do
			setInfo(i)
			task.wait(1)
		end
		setupRound()
		state = "Round"
		for i=2000, 0, -1 do
			setInfo(i)
			task.wait(1)
		end
		endRound()
	end
end

events.Remote.Run.OnServerEvent:Connect(function(plr, held)
	local char:Model = plr.Character
	local anims = char:FindFirstChild("Anims")
	--print(char:GetAttribute("Stamina"))
	
	if plr.InRound.Value ~= true then
		return
	end
	
	if held == true and char:GetAttribute("Stamina") > 0 and plr.Character.HumanoidRootPart.AssemblyLinearVelocity.Magnitude > 0 then
		--char:SetAttribute("Speed", char:GetAttribute("RunSpeed") * char.SpeedMultiplier.Value)
		char:SetAttribute("Stamina", char:GetAttribute("Stamina") - 0.2)
		
		if char:GetAttribute("Running") == false and char:GetAttribute("Stamina") > 10 then
			char:SetAttribute("Running", true)
			char:SetAttribute("Speed", char:GetAttribute("RunSpeed") * char.SpeedMultiplier.Value)
			
			char.Humanoid:LoadAnimation(char.Anims.Run):Play(0.2, 1, char.SpeedMultiplier.Value)
		end
		
		for i, track:AnimationTrack in char.Humanoid:GetPlayingAnimationTracks() do
			if track.Name == "Run" then
				track:AdjustSpeed(char.SpeedMultiplier.Value)
			end
		end
		
		repeat task.wait() until not held
	end
	if held == false or char:GetAttribute("Stamina") <= 0 or plr.Character.HumanoidRootPart.AssemblyLinearVelocity.Magnitude == 0 then
		char:SetAttribute("Speed", char:GetAttribute("WalkSpeed") * char.SpeedMultiplier.Value)
		char:SetAttribute("Running", false)
		if char:GetAttribute("Stamina") <= char:GetAttribute("MaxStamina") and held == false then
			char:SetAttribute("Stamina", char:GetAttribute("Stamina") + 0.25)
			if char:GetAttribute("Stamina") > char:GetAttribute("MaxStamina") then
				char:SetAttribute("Stamina", char:GetAttribute("MaxStamina"))
			end
		end
		
		for i, track:AnimationTrack in char.Humanoid:GetPlayingAnimationTracks() do
			if track.Name == "Run" then
				track:Stop(0.4)
			end
		end
	end
end)

events.Remote.Attack.OnServerEvent:Connect(function(plr, input)
	if plr == currentKiller then
		local killer = plr.Killer.Value.Name
		
		local char = plr.Character
		
		local anims = char:WaitForChild("Anims")
		
		local alreadyHit = false
		local hitRigs = {}
		
		local function createHitbox(pos, size, multi, sound:Sound, particle:ParticleEmitter, emission)
			coroutine.wrap(function()
				local newHitbox = Instance.new("Part", workspace)
				newHitbox.CanCollide = false
				newHitbox.Size = size
				newHitbox.Anchored = true
				newHitbox.Material = Enum.Material.ForceField
				newHitbox.Color = Color3.new(1, 0, 0)
				newHitbox.Transparency = 0.5
				
				if sound then
					local newSound = sound:Clone()
					newSound.Parent = newHitbox
					newSound:Play()
				end
				
				if particle then
					task.wait()
					local newParticle = particle:Clone()
					newParticle.Parent = newHitbox
					newParticle:Emit(emission)
				end

				newHitbox.CFrame = pos
				--newHitbox.CFrame += char.HumanoidRootPart.CFrame.LookVector * char:GetAttribute("HitboxSize").Z / 2

				local function hitbox()
					--for i=1, char:GetAttribute("HitboxAmount") do
						--if newHitbox then
							local touchingParts = workspace:GetPartsInPart(newHitbox)

							for i, obj in touchingParts do
								if multi == false then
									if obj.Parent:FindFirstChild("Humanoid") and obj.Parent ~= char and not alreadyHit and obj.Parent:FindFirstChild("Humanoid").Health > 0 then
										alreadyHit = true
										local target:Model = obj.Parent

										if input == "M1" then
											char.HumanoidRootPart.Hit:Play()
											target.Humanoid:TakeDamage(char:GetAttribute("Damage"))
											
									if target.Humanoid.Health <= 0 then
										if killer == "Corrupted" then
											char.HumanoidRootPart.Anchored = true
											target.Humanoid.MaxHealth = math.huge
											target.Humanoid.Health = math.huge

											target.HumanoidRootPart.Anchored = true

											local finisher:AnimationTrack = char.Humanoid:LoadAnimation(anims.Finish)
											finisher:Play()
											local finishee:AnimationTrack = target.Humanoid:LoadAnimation(anims.Finishee)
											finishee:Play()

											--target:PivotTo(char.HumanoidRootPart.CFrame)
											target.HumanoidRootPart.CFrame = char.HumanoidRootPart.CFrame + char.HumanoidRootPart.CFrame.LookVector * 4
											target:PivotTo(target:GetPivot() * CFrame.Angles(0, math.rad(180), 0))

											task.wait(0.5)

											target.Humanoid.Health = 0
											char.HumanoidRootPart.Ban:Play()

											target.HumanoidRootPart.Anchored = false

											finisher.Ended:Wait()
										elseif killer == "Copy" then
											char.HumanoidRootPart.Anchored = true
											target.Humanoid.MaxHealth = math.huge
											target.Humanoid.Health = math.huge

											target.HumanoidRootPart.Anchored = true
											
											target.HumanoidRootPart.CFrame = char.HumanoidRootPart.CFrame + char.HumanoidRootPart.CFrame.LookVector * 4
											target:PivotTo(target:GetPivot() * CFrame.Angles(0, math.rad(180), 0))

											local finisher:AnimationTrack = char.Humanoid:LoadAnimation(anims.Finish)
											finisher:Play()
											local finishee:AnimationTrack = target.Humanoid:LoadAnimation(anims.Finishee)
											finishee:Play()

											--target:PivotTo(char.HumanoidRootPart.CFrame)
											
											
											char.HumanoidRootPart.Finish:Play()

											task.wait(0.33)

											target.Humanoid.Health = 0

											target.HumanoidRootPart.Anchored = false

											finisher.Ended:Wait()
										end
										
										char.HumanoidRootPart.Anchored = false
									end
										else
											target.Humanoid:TakeDamage(30)
										end
									end
								else
							if obj.Parent:FindFirstChild("Humanoid") and obj.Parent ~= char and not table.find(hitRigs, obj.Parent) and obj.Parent:FindFirstChild("Humanoid").Health > 0 then
										table.insert(hitRigs, obj.Parent)
										local target = obj.Parent

										if input == "M1" then
											char.HumanoidRootPart.Hit:Play()
											
											target.Humanoid:TakeDamage(char:GetAttribute("Damage"))
										else
											target.Humanoid:TakeDamage(30)
										end
									end
								end
							end
						--end
						--task.wait()
					--end
				end

				coroutine.wrap(hitbox)()

				debris:AddItem(newHitbox, 1)
				
				return newHitbox
			end)()
		end
		
		if killer == "Corrupted" then
			if input == "M1" and not char:HasTag("M1Cooldown") and not char:HasTag("Cooldown") then
				char:AddTag("M1Cooldown")
				char:AddTag("Cooldown")
				coroutine.wrap(function()
					local track = char.Humanoid:LoadAnimation(anims.M1)
					track:Play(0)
					char.HumanoidRootPart.Swing:Play()
					task.wait(char:GetAttribute("Windup"))
					for i=1, char:GetAttribute("HitboxAmount") do
						createHitbox(functions.Remote.GetPlayerPos:InvokeClient(plr), char:GetAttribute("HitboxSize"), false)
						--task.wait()
					end
					
					track.Ended:Wait()
					
					char:RemoveTag("Cooldown")
				end)()
				task.wait(char:GetAttribute("Cooldown"))
				char:RemoveTag("M1Cooldown")
			end
			
			if input == "E" and not char:HasTag("SlamCooldown") and not char:HasTag("Cooldown") then
				char:AddTag("SlamCooldown")
				char:AddTag("Cooldown")
				coroutine.wrap(function()
					char.HumanoidRootPart.Anchored = true
					local slamTrack = char.Humanoid:LoadAnimation(anims.Slam)
					slamTrack:Play(0)
					--char.HumanoidRootPart.Swing:Play()
					task.wait(0.3)
					char.HumanoidRootPart.Slam:Play()
					char.HumanoidRootPart.Attachment.ParticleEmitter:Emit(100)
					--task.wait(char:GetAttribute("Windup"))
					--[[for i=1, char:GetAttribute("HitboxAmount") do
						createHitbox()
						task.wait()
					end]]
					
					coroutine.wrap(function()
						local pos = functions.Remote.GetPlayerPos:InvokeClient(plr)
						
						for i=1, 10 do
							createHitbox(pos + pos.LookVector * (i * 10), Vector3.new(5 + i * 2,5,10), true, char.HumanoidRootPart.Smash, assets.Particles.RockParticle, 3 * i)
							task.wait(0.1)
						end
					end)()
					
					--slamTrack:Play(0.1)
					slamTrack.Ended:Wait()
					char:RemoveTag("Cooldown")
					char.HumanoidRootPart.Anchored = false
				end)()
				task.wait(char:GetAttribute("SlamCooldown"))
				char:RemoveTag("SlamCooldown")
			end
			
			if input == "R" and not char:HasTag("DevPanelCooldown") and not char:HasTag("Cooldown") then
				char:AddTag("DevPanelCooldown")
				local panelTrack:AnimationTrack = char.Humanoid:LoadAnimation(anims.DevPanel)
				panelTrack:Play()
				
				char.HumanoidRootPart.Anchored = true
				
				panelTrack:GetMarkerReachedSignal("Open"):Connect(function()
					char.Panel.Transparency = 0
					char.Panel.SurfaceGui.Enabled = true
					char.HumanoidRootPart.PanelOpen:Play()
				end)
				panelTrack:GetMarkerReachedSignal("Close"):Connect(function()
					char.Panel.Transparency = 1
					char.Panel.SurfaceGui.Enabled = false
					char.HumanoidRootPart.Reveal:Play()
					events.Remote.Reveal:FireClient(plr, 3)
				end)
				
				panelTrack.Ended:Wait()
				
				char:RemoveTag("Cooldown")
				
				char.HumanoidRootPart.Anchored = false
				
				task.wait(char:GetAttribute("DevPanelCooldown"))
				char:RemoveTag("DevPanelCooldown")
			end
			
			if input == "Q" and not char:HasTag("OverloadCooldown") and not char:HasTag("Cooldown") then
				char:AddTag("Cooldown")
				char:AddTag("OverloadCooldown")
				
				char.HumanoidRootPart.Anchored = true
				char.HumanoidRootPart.OverloadCharge:Play()
				
				local overloadTrack:AnimationTrack = char.Humanoid:LoadAnimation(anims.ErrorOverload)
				overloadTrack:Play(0)
				
				char.HumanoidRootPart.ParticleEmitter.Enabled = true
				
				task.wait(0.5)
				
				char.HumanoidRootPart.Overload:Play()
				
				local newEffect = Instance.new("NumberValue", char.StatusEffects)
				newEffect.Name = "Speed 1"
				newEffect.Value = 5
				
				local newEffect = Instance.new("NumberValue", char.StatusEffects)
				newEffect.Name = "Blindness"
				newEffect.Value = 7
				
				coroutine.wrap(function()
					task.wait(7)
					char.HumanoidRootPart.ParticleEmitter.Enabled = false
				end)()
				
				task.wait(0.1)
				char.Hammer.Attachment.ParticleEmitter:Emit()
				
				overloadTrack.Ended:Wait()
				
				char:RemoveTag("Cooldown")
				
				char.HumanoidRootPart.Anchored = false
				
				task.wait(char:GetAttribute("OverloadCooldown"))
				
				char:RemoveTag("OverloadCooldown")
			end
		end
		
		if killer == "Copy" then
			if input == "M1" and not char:HasTag("M1Cooldown") and not char:HasTag("Cooldown") then
				char:AddTag("M1Cooldown")
				char:AddTag("Cooldown")
				coroutine.wrap(function()
					local track = char.Humanoid:LoadAnimation(anims.M1)
					track:Play(0)
					char.HumanoidRootPart.Swing:Play()
					task.wait(char:GetAttribute("Windup"))
					for i=1, char:GetAttribute("HitboxAmount") do
						createHitbox(functions.Remote.GetPlayerPos:InvokeClient(plr), char:GetAttribute("HitboxSize"), false)
						--task.wait()
					end

					track.Ended:Wait()

					char:RemoveTag("Cooldown")
				end)()
				task.wait(char:GetAttribute("Cooldown"))
				char:RemoveTag("M1Cooldown")
			end
		end
	else
		
	end
end)

players.PlayerAdded:Connect(playerAdded)

coroutine.wrap(gameLoop)()

while task.wait() do
	updateStats()
end

I’ve already searched the forum, but I haven’t found what’s going on.

2 Likes

Is there a wait() or task.wait() command anywhere that would pause between each stamina decrease? I don’t see one anywhere inside the Run event that handles the stamina. My best guess is that the line speed is slower in Studio so it looks slower than in-game.

I tried adding it along with a few other related things, doesn’t work.

Sounds like an odd one, try setting your FPS in game to 60 / 120 / 240 see if it effects how fast your stamina changes.

If that is the case it’ll most likely be your task.wait running at the same speed as your fps.

To fix this you could add a cap to it.

edit:
If your FPS is the case, which i believe it is, set your task.wait to 1/60:
task.wait(1/60) (assuming your studio is set to 60 FPS)

1 Like

I think in roblox studio with you firing events constantly im assuming at runtime, roblox studio throttles them so that they don’t spam too much or overload the server, but in game if I remember correctly they run alot faster and more frequent, so you could try using tick() to limit this or have a run state for each player whether they’re running or not and use a task.spawn and a while loop, which yes is innefficient but could be there to see the problem
local RunStates = {} -- [player] = true/false (as an example)

I believe this is based on your PC stats

I see, i’ll try and replicate the issue and get back to you if I find a solution ! ^^

1 Like

This is the problem, I am going to attempt to fix this.

I’ve tried a few fixes, none of them have worked (mostly involving waits, and what @EmberSquared suggested.)

Alright, so rather than using while task.wait(1/60) do try using a capped heartbeat

try messing with the 1/60 aswell?
Maybe 1/30 1/24 etc…

local elapsed = 0
Runservice.HeartBeat:Connect(function(deltaTime)
   elapsed += deltaTime
   if elapsed >= 1/60 then
    elapsed = 0;
     -- Your code here
   end
end)
1 Like

Worked like a charm! Thanks for the help.

1 Like