I’ve been working on a round system for my game where the game doesn’t start if there aren’t enough players. If the game or the intermission timer has already started and a player leaves, the game should reset.
The problem is that when a player joins or leaves during the intermission or round timer, the game may have enough players to start, and it adds another timer for that player. So, now there are multiple timers running at the same time, which is causing issues.
I need help finding a way to make sure that only one timer runs at a time and the game resets correctly when players leave during the intermission or round timer.
(If you need code lmk in comments)
edit: I use player added/removing to check how many people are in the game and if it is at least 2 it will start the game, but if another person joins then the bug happens
Hm. This one might be difficult because I haven’t made a round system before, but I’ll try my best.
In my opinion, you should be running the timer on the Server, and the clients should take the timer’s data and put it on their end (i.e. updating a Gui to show the timer duration)
This can easily be solved by checking how many players exist at the end of the timer’s duration by calling :GetPlayers() on the Players Service and checking the number of players that exist from that function.
local Players = game:GetService("Players")
print(#Players:GetPlayers()) -- Prints the number of players in the game
I could probably help you better if you provided the erroring code.
I’m confused as to why multiple timers is possible in the first place, assuming this isn’t a per-party queue system but instead a per-server round system.
It would probably be helpful if you provide the code.
local Players = game:GetService("Players")
local function Countdown(duration: number)
repeat
-- process duration such as display it to text label
task.wait(1)
duration -= 1
until duration == 0
StartGame()
end
local intermissionThread = nil
local function StartIntermission()
if intermissionThread == nil then
intermissionThread = task.spawn(Countdown, 30)
end
end
local function StopIntermission()
if intermissionThread ~= nil then
task.cancel(intermissionThread)
intermissionThread = nil
end
end
Players.PlayerAdded:Connect(function(player))
if #Players:GetPlayers() >= 2 then
StartIntermission()
end
end)
Players.PlayerRemoving:Connect(function(player))
if #Players:GetPlayers() < 2 then
StopIntermission()
end
end)
You should use an integer (local timer = 0) to keep track of the timer, and when the round ends, the timer changes or the intermission end you should check the playercount by using #game.Players:GetChildren(). That will give you the amount of players currently in-game. So just check if that is higher than 1 or whatever.
The timer uses a for loop on the server and updates the ui local script. If I put the player count check inside the for loop would that work? and then I could say— if player count < 2 then just set the timer all the way to 0 again and return?
Why would you do that on the client in the first place?
There are so many issues with that, for example people could exploit so so easy with mechanics by just extending their round time, or people that lag a lot would just have longer rounds. Also it wouldn’t start for everyone at the same time.
You can just make a global value in ReplicatedStorage and update that, and the client only displays what it is, like this:
local v = game.ReplicatedStorage:WaitForChild("RoundTimer")
local label = script.Parent
v.Changed:Connect(function()
label.Text = "Remaining time: "..v.Value.." s"
end)
Well I use a for loop that counts down and fires an event to the client to edit the numbers on the text label as it counts down. Is a hacker able to change that…? (I do have a global value btw)
Try to detect when the value of the timer is changed, then update the text. I’m pretty sure this means there is less data going over the network, but it is also less to keep up with (one less remote event). Just make sure that all you do on the client with that information is update labels or stuff like that.
The timers for each player on the Client is a bad idea because exploiters can change the script, or simply just get rid of the script entirely (dump). It gives them full control of the round, depending on how you have implemented it. Exploiters can change anything on there clients, so you should avoid using data from the client or at least validate it in some way. Data validation can be hard to do properly, since you might falsely flag a player as cheating or something.
An example of validating the data from a player (kinda) is to measure the magnitude of a player’s character to check their speed. This is because players can change their WalkSpeed on the Client, and the Server won’t know. You could check on their speed from local script, but like I said, exploiters can change or dump scripts. So we have to check if their magnitude goes over a certain speed. If they do then the player is probably cheating. There are also exceptions where they might accidently get flung because of a glitch, and get falsely flagged. You have to be careful about how you do it.
That was somewhat irrelevant, but just know that players can change anything on their clients.
Yes well the server has the timer not the client-- the client just adds text for the player to visually see the countdown. If they remove the client script will it affect anything…? I can try removing it in game or making tweaks while I am playing to see if it does anything. But the server has the real timer.
Client script-
local Players = game:GetService("Players")
local PlayerGui = Players.LocalPlayer:WaitForChild("PlayerGui")
local RoundSystem = PlayerGui:WaitForChild("RoundSystem")
local Status = RoundSystem:WaitForChild("Container"):WaitForChild("Status")
local TweenService = game:GetService("TweenService")
local Container = RoundSystem:WaitForChild("Container")
local Timer = RoundSystem:WaitForChild("Timer")
--local Starting = false
--local RS = game:GetService("ReplicatedStorage")
--local Map = RS:WaitForChild("ClassicMap")
--local Spawns = Map:FindFirstChild("SpawnParts"):GetChildren()
--local AvailableSpawns = {}
--local NewMap = Map:Clone()
local updateTimer = game:GetService("ReplicatedStorage"):WaitForChild("UpdateTimer")
local PlayerReq = game:GetService("ReplicatedStorage"):WaitForChild("PlayerReq")
local TimerText = RoundSystem:WaitForChild("Timer"):WaitForChild("RoundTimer"):WaitForChild("Clock")
updateTimer.OnClientEvent:Connect(function(i)
TweenService:Create(Container, TweenInfo.new(1, Enum.EasingStyle.Exponential), {Position = UDim2.fromScale(0.5, -0.3)}):Play()
task.wait(1)
TweenService:Create(Timer, TweenInfo.new(1.3, Enum.EasingStyle.Exponential), {Position = UDim2.fromScale(0.5, 0.010)}):Play()
TimerText.Text = i
if TimerText.Text == 0 then
print("here")
task.wait(1)
TweenService:Create(Timer, TweenInfo.new(1.3, Enum.EasingStyle.Exponential), {Position = UDim2.fromScale(0.5, -0.3)}):Play()
end
end)
PlayerReq.OnClientEvent:Connect(function()
task.wait(1)
TimerText.Text = "N/A"
TweenService:Create(Timer, TweenInfo.new(1.3, Enum.EasingStyle.Exponential), {Position = UDim2.fromScale(0.5, -0.3)}):Play()
print("player req")
wait(1)
Container.Visible = true
Status.Visible = true
Status.Text = "Need 2 players to start."
TweenService:Create(Container, TweenInfo.new(1, Enum.EasingStyle.Exponential), {Position = UDim2.fromScale(0.5, 0)}):Play()
print("Tweened player req")
end)
EDIT: I removed almost the entire client script and the only change was the gui that did not apear, the round worked just as normal
As long as you have a global value on the server that cannot be changed with remoteevents you should be good.
Of course they could still change the number for them, but as long as all stat reward are handled by the server that doesnt matter
Changed events rely on loops, thats how Roblox works.
So RemoteEvents theoretically are much more effective than these.
But I mean its a change so small that no player would notice it