While true do statement randomly pausing, plus other bugs

So I’ve made this script that gives all the players their necessary things and basically makes the whole game function and makes sure that the teachers and students have their necessary settings adjusted (Guis, seats, etc). However, there are multiple bugs.

For one, the while true do statement will randomly pause, as seen by the timer section that will stop counting after a random time.
Also, I need to account for players that join after the main loop that calls establishSettings() for each player.
I also need help with making sure that multiple players aren’t placed into the same seat. I have tried to fix this by removing a player’s seat from the available seats when it is assigned, but it still persists.
And I’ve received reports that the the TimerLabel’s text will show the message that displays when there are less than 2 players, even when the server has 2 or more.

Here is the script:

while true do

	local allPlayers = Players:GetPlayers()

	if #allPlayers < 2 then
		for _, player:Player in allPlayers do
			local playerGui = player:WaitForChild("PlayerGui")
			local timer = playerGui:WaitForChild("GlobalGui").TimerLabel

			timer.Text = "2 players required to start game..."
		end
		task.wait(0.01)
		continue
	end

	local teacherIndex = math.random(1, #allPlayers)
	local teacher = allPlayers[teacherIndex]

	-- Assign teacher
	teacher.Team = Teams.Teachers
	local teacherCharacter:Model = teacher.Character or teacher.CharacterAdded:Wait() --wait for teacher character
	
	local teacherHumanoid:Humanoid = teacherCharacter.Humanoid
	teacherHumanoid.JumpPower = 50 --allow the teacher to jump
	teacherHumanoid:ChangeState(Enum.HumanoidStateType.Jumping)
	
	teacherCharacter:MoveTo(teacherSpawnpoint.Position) --move teacher character to teacherspawnpoint

	local ruler:Tool = teacher.Backpack:FindFirstChild("Ruler",false)
	--check if user has ruler tool

	if not ruler then --if not, then give them the tool
		local newTool = game.ReplicatedStorage.Ruler:Clone()
		newTool.Parent = teacher.Backpack
	end



	local function establishSettings(player:Player)

		if player ~= teacher then --if player is not the teacher (means they are a student)

			player.Team = Teams.Students --make player a student
			print("Function called for:" .. player.Name)

			local character = player.Character or player.CharacterAdded:Wait()

			local studentHumanoid:Humanoid = character:WaitForChild("Humanoid")
			--get student's humanoid

			local StudentSeatValue:ObjectValue = player.StudentSeatValue--get student seat value

			local seat  = studentSeats[#allPlayers]

			print(seat)

			StudentSeatValue.Value = seat
			print(StudentSeatValue.Value)

			studentHumanoid.JumpPower = 0
			seat:Sit(studentHumanoid)    --lock player into seat

			for i, item in studentSeats do --REMOVE SEAT FROM STUDENTSEATS
				if item == seat then		--SO TWO PLAYERS DONT GET ASSIGNED]

					table.remove(studentSeats,i) --THE SAME SEAT
				end
			end

			local ruler = player.Backpack:FindFirstChild("Ruler") --check if user has ruler

			if ruler then
				ruler:Destroy() --destroy if user has ruler
			end
		else

			local teacherSeat = player:FindFirstChild("StudentSeatValue")
			addToTable(studentSeats,teacherSeat) --add teachers studentseatvalue to studentseats table
			--so it can be reused
		end

	end

	Players.PlayerRemoving:Connect(function(player)
		local seatValue = player:FindFirstChild("StudentSeatValue")
		addToTable(studentSeats,seatValue)
	end)


	for _, player:Player in allPlayers do
		establishSettings(player)
	end

	local minutes = 3
	local tenSeconds = 0
	local seconds = 0

	repeat
		for _, player:Player in allPlayers do
			local playerGui = player:WaitForChild("PlayerGui")
			local timer = playerGui:WaitForChild("GlobalGui").TimerLabel

			timer.Text = minutes .. ":" .. tenSeconds .. seconds		
		end	

		seconds -= 1

		if seconds < 0 then
			tenSeconds -= 1
			seconds = 9
		end

		if tenSeconds < 0 then
			minutes -= 1
			tenSeconds = 5
		end

		task.wait(1)

	until minutes < 0
end

Any help is appreciated.

so for your last issue, it looks like if you have less than 2 players, it will make the GUI show up for all players, but there’s never any logic that hides it again if there are 2 or more players.

for your first issue, have you checked the errors? Probably it just can’t find one of the player’s GUI or something, it errors, and it breaks the whole script.

the seat stuff is kinda confusing, but you could prob just do this:

local seat  = studentSeats[#allPlayers]
studentSeats[#allPlayers] = nil

but, its somewhat confusing what you are trying to do, since im pretty sure its just going to try and call the same seat over and over again, because #allPlayers number isnt going to change. Maybe this would work better:

local seat  = studentSeats[#studentSeats]
studentSeats[#studentSeats] = nil
1 Like

that should be all the issues you listed, tell me which solutions don’t work or if you have any questions or need help with anything else.

I checked the error report and found a warning:

Infinite yield possible on Salmonhed1234:WaitForChild(“PlayerGui”)

1 Like

It is supposed to be overridden during the timer part of the script.
It is at the bottom of the script

Id recommend using remote events, or some value everyone can see, really anything to make it so the client handles its own GUI and not the server, but ignoring that, just make it skip over players where their playerGUI isnt found using :FindFirstChild so it doesn’t yield

1 Like

that would be caused most likely when the script errors, and it can’t update their GUI.

Your whole code is super messy and subject to heavy peformance issues and memory leak.

The establishSettings function and the PlayerRemoving signal should be outside of the loop to start with. The players timer GUI should be changed in client side. The repeat until loop actually stop the while loop until 3 minutes has elapsed.

Any WaitForChild() call can yield, and so will the teacher.CharacterAdded:Wait().
I’d suggest using FindFirstChild() and and skip the code that involves the child if the child is nil.

Another thing you could do is wrap things like this to create a new thread.

task.spawn(function()
	-- code
end)

This will run the inside code on a new thread that gets ran independently, and allows the outside to continue without waiting for the inside code.

1 Like

This is intentional: Each round is three minutes long, and once the three minutes has finished, a new teacher is chosen (the while loop cycles).

Alright, but there are better and more optimized methods that don’t require to put a loop inside another. One single loop should be enough for a round based gameplay.

Let me some minutes to show an exemple using your code. (maybe a little more time, there’s a studio update ^^)

That code looks fine to me and is a clear way to program the gameloop. The counting is oldschool but the inner loop is fine. It’s unrelated to the problem here so we probably shouldn’t focus on it.

The current code is susceptible to memory leaks caused by duplicate functions and connections on each loop restart. Additionally, errors may occur due to the absence of condition checks that verify the existence of certain instances. There could also be significant performance issues resulting from unoptimized iterations.

But that’s okay, I’m not here to criticize, I just want to help… it’s just that it would be easier for me to edits the code and show an exemple instead of writing everything as it would be too long to explain :slightly_smiling_face:

In short, in my opinion, a round-based code should consist of a single infinite loop that primarily serves to pass time and call functions.

What should I do if FindFirstChild() returns nil?

I’d suggest using an “if” statement and ignoring the code that would use the child.

You can think about it more deeply, and if the code is a necessity to run, you might need to do something more complicated. In the case that it’s updating UI to reflect the time, then I’d ignore it.

1 Like

I pretty much tried to clean up the code as best as I could. Feel free to tell me if it work, edits it as you wish or ask questions.

Server Script

-- Roblox Services --
local PlayerService = game:GetService("Players")
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local TeamService = game:GetService("Teams")

-- Primary Instances --
local RulerTool = ReplicatedStorage:WaitForChild("Ruler")

local RoundTimer = Instance.new("IntValue")
local Status = Instance.new("StringValue")

--------------------------------------------------

-- Local Functions --
local function SetupStudents(teacher)
	local playerList = PlayerService:GetPlayers()
	
	table.remove(playerList, table.find(playerList, teacher))
	for _, player in playerList do
		local studentcharacter: Model = player.Character or player.CharacterAdded:Wait(10)
		local studentHumanoid: Humanoid = studentcharacter:WaitForChild("Humanoid", 10)
		local studentBackpack: Backpack = player and player:FindFirstChildWhichIsA("Backpack")
		local studentSeatValue = player and player:FindFirstChild("StudentSeatValue")

		local ruler: Tool = studentBackpack and studentBackpack:FindFirstChild("Ruler")
		local seat  = studentSeats[#PlayerService:GetChildren()]

		player.Team = TeamService.Students
		if studentcharacter and studentHumanoid and studentBackpack and studentSeatValue then
			studentSeatValue.Value = seat
			studentHumanoid.JumpPower = 0
			seat:Sit(studentHumanoid) 

			for i, item in studentSeats do
				if item == seat then
					table.remove(studentSeats, i)
				end
			end

			if ruler then
				ruler:Destroy()
			end
		end
	end
end

local function SetupTeacher()
	local playerList = PlayerService:GetPlayers()
	local teacher = playerList[math.random(1, #playerList)]

	local teacherCharacter: Model = teacher.Character or teacher.CharacterAdded:Wait(10)
	local teacherHumanoid: Humanoid = teacherCharacter:WaitForChild("Humanoid", 10)
	local teacherBackpack: Backpack = teacher and teacher:FindFirstChildWhichIsA("Backpack")		
	local teacherSeat = teacher and teacher:FindFirstChild("StudentSeatValue")

	local ruler: Tool = teacherBackpack and teacherBackpack:FindFirstChild("Ruler") or RulerTool:Clone()

	if teacher and teacherCharacter and teacherHumanoid and teacherBackpack and teacherSeat and ruler then
		ruler.Parent = teacherBackpack
		teacher.Team = TeamService.Teachers
		teacherHumanoid.JumpPower = 50
		teacherHumanoid:ChangeState(Enum.HumanoidStateType.Jumping)
		teacherHumanoid:MoveTo(teacherSpawnpoint.Position)
		addToTable(studentSeats, teacherSeat)

		return teacher
	else
		return nil
	end
end

--------------------------------------------------

-- Signal Connections --
PlayerService.PlayerRemoving:Connect(function(OldPlayer)
	local seatValue = OldPlayer:FindFirstChild("StudentSeatValue")
	
	if seatValue then
		addToTable(studentSeats, seatValue)
	end
end)

-- Setup Values --
-- Create new values in the workspace as a reference for clients
RoundTimer.Parent = workspace
RoundTimer.Name = "RoundTimer"
RoundTimer.Value = 300
	
Status.Parent = workspace
Status.Name = "Status"
Status.Value = "WaitForPlayers"

-- Main Loop --	
while task.wait(1) do
	if Status.Value == "WaitForPlayers" and #PlayerService:GetPlayers() >= 2 then -- Start a new game
		local teacher = SetupTeacher()
		
		if teacher then
			SetupStudents(teacher)
			Status.Value = "IsPlaying"
			RoundTimer.Value = 300
		end
	elseif Status.Value == "IsPlaying" and RoundTimer.Value > 0 then -- It is playing so just change timer
		RoundTimer.Value -= 1
	elseif Status.Value == "IsPlaying" and RoundTimer.Value <= 0 then -- Stop the game
		Status.Value = "WaitForPlayers" --or implement a "intermission"
	end
end

local script for text

local RoundTimer = workspace:WaitForChild("RoundTimer")
local Status = workspace:WaitForChild("Status")
local TimerLabel = script.Parent

local function TimerFormat(Seconds: number)
	local Minutes = (Seconds - Seconds%60) / 60
	
	Seconds = Seconds - Minutes * 60
	return string.format("%02i", Minutes)..":"..string.format("%02i", Seconds)
end

RoundTimer:GetPropertyChangedSignal("Value"):Connect(function()
	if Status.Value == "IsPLaying" then
		TimerLabel.Text = TimerFormat(RoundTimer.Value)
	end
end)

Status:GetPropertyChangedSignal("Value"):Connect(function()
	if Status.Value == "IsPLaying" then
		TimerLabel.Text = TimerFormat(RoundTimer.Value)
	elseif Status.Value == "WaitForPlayers" then
		TimerLabel.Text = "2 players required to start game..."
	end
end)

Does this account for players that join after the main game loop?

Nop, I did not added it because I didn’t knew if there were a lobby or if joining players would dirrectly join the current round. If needed, you can add a new connection for it:

PlayerService.PlayerAdded:Connect(function(NewPlayer)
	if Status.Value == "IsPlaying" then
		-- Do everything needed to join the game
	elseif Status.Value == "WaitForPlayers" then
		-- Join lobby?
	end
end)

I also suggest to manually add a Configuration into the workspace, in which you can add both the Status and RoundTimer values.

This way, you can replace the top variables to this:

local Configuration = workspace:WaitForChild("Configuration")
local RoundTimer = Configuration:WaitForChild("RoundTimer")
local Status = Configuration:WaitForChild("Status")

and completely remove the following lines, which save up some space and make the bottom script feel more proper and readable.

-- Setup Values --
-- Create new values in the workspace as a reference for clients
RoundTimer.Parent = workspace
RoundTimer.Name = "RoundTimer"
RoundTimer.Value = 300
	
Status.Parent = workspace
Status.Name = "Status"
Status.Value = "WaitForPlayers"

Don’t forget to also update the client script