Round system acting weird

You can write your topic however you want, but you need to answer these questions:

  1. What do you want to achieve? Keep it simple and clear!
    I just want my round system to assign teams with no teams being empty with different priorities ( 3 teams total planned).
    Inmates with the most people, security second, then aberrations last
    I plan for always a guaranteed aberration no matter lobby size, even two people.

  2. What is the issue? Include screenshots / videos if possible!
    Don’t know how to describe it the best but basically it would do one of the following.

  • just stop working
  • All players are assigned to the Inmate team and the round instantly ends.
  • A endless respawn loop.
  1. What solutions have you tried so far? Did you look for solutions on the Developer Hub?
    It’s genuinely hard to find the root of the problem since there are many sections. I tried tweaking some small parts which may have solved future problems but I still end up having one issue or the other. The script just bugs out when there are more than two people.

my prints don’t give me any clue to what my problem is. It’s very likely to be with the team assignment for the inmates problem, the while loop for the respawn loop problem, and it not starting again.

What is very much clear is that when there are more than three players it straight up breaks.

I’m going to head to bed and will check upon this tomorrow.


-- SERVICES
local Teams   = game:GetService("Teams")
local Players = game:GetService("Players")
local Workspace = game:GetService("Workspace")

-- CONFIG
local IntermissionTime          = 30    -- seconds between rounds
local DecontaminationCountdown  = 120   -- optional lockdown timer
local MinPlayersToStart        = 2

-- TEAM REFERENCES
local TeamInmate     = Teams:FindFirstChild("Inmate")
local TeamSecurity   = Teams:FindFirstChild("Security Personnel")
local TeamSpectator  = Teams:FindFirstChild("Spectator")

-- SPAWN POINTS
local spawnFolder     = Workspace:WaitForChild("SpawnPoints")
local InmateSpawns    = spawnFolder:WaitForChild("Inmates"):GetChildren()
local SecuritySpawns  = spawnFolder:WaitForChild("Security"):GetChildren()

-- STATE
local RoundActive = false

-- UTILITIES
local function shuffle(tbl)
	for i = #tbl, 2, -1 do
		local j = math.random(1, i)
		tbl[i], tbl[j] = tbl[j], tbl[i]
	end
end

-- FORCE MID-ROUND JOINERS TO SPECTATE
Players.PlayerAdded:Connect(function(player)
	player.Team = TeamSpectator
	player.CharacterAdded:Connect(function(char)
		-- if a round is live, keep them as spectator
		if RoundActive and player.Team ~= TeamSpectator then
			player.Team = TeamSpectator
			player:LoadCharacter()
		end
	end)
end)

-- MAIN FUNCTIONS

local function AssignTeams()
	-- gather all active players
	local pool = Players:GetPlayers()
	shuffle(pool)

	local total = #pool
	if total < MinPlayersToStart then
		warn("Not enough players to start a round.")
		return false
	end

	-- ensure at least 1 per team
	local securityCount = math.clamp(math.floor(total / 2), 1, total - 1)

	for idx, player in ipairs(pool) do
		-- load or wait for character
		player:LoadCharacter()
		local char = player.Character or player.CharacterAdded:Wait()
		local root = char:WaitForChild("HumanoidRootPart", 5)
		if not root then
			warn("No HumanoidRootPart for", player.Name)
			continue
		end

		if idx <= securityCount then
			-- SECURITY
			player.Team = TeamSecurity
			local spawn = SecuritySpawns[math.random(1, #SecuritySpawns)]
			root.CFrame = spawn.CFrame
		else
			-- INMATE
			player.Team = TeamInmate
			local spawn = InmateSpawns[math.random(1, #InmateSpawns)]
			root.CFrame = spawn.CFrame
		end
	end

	-- (Optional) start your decontamination timer here:
	-- delay(DecontaminationCountdown, function()
	--     -- fire an event or handle lockdown
	-- end)

	return true
end

local function WinCheck()
	local aliveIn, aliveSec = 0, 0
	for _, player in ipairs(Players:GetPlayers()) do
		if player.Team == TeamInmate or player.Team == TeamSecurity then
			local hum = player.Character and player.Character:FindFirstChildWhichIsA("Humanoid")
			if hum and hum.Health > 0 then
				if player.Team == TeamInmate then
					aliveIn += 1
				else
					aliveSec += 1
				end
			end
		end
	end

	if aliveIn == 0 and aliveSec == 0 then
		return nil  -- e.g. everybody down simultaneously
	elseif aliveSec == 0 then
		return "Inmates"
	elseif aliveIn == 0 then
		return "Security"
	end
	return nil  -- round continues
end

local function EndRound(winner)
	RoundActive = false
	print(("Round ended. Winner: %s"):format(winner))

	-- reset everyone to spectator
	for _, player in ipairs(Players:GetPlayers()) do
		player.Team = TeamSpectator
		player:LoadCharacter()
	end
end

-- MAIN LOOP
task.spawn(function()
	while true do
		-- wait for minimum players
		repeat
			if #Players:GetPlayers() < MinPlayersToStart then
				print("Waiting for players... (" .. MinPlayersToStart .. " required)")
				task.wait(5)
			end
		until #Players:GetPlayers() >= MinPlayersToStart

		-- reset all to Spectator
		for _, p in ipairs(Players:GetPlayers()) do
			p.Team = TeamSpectator
			p:LoadCharacter()
		end

		-- start a new round
		print("----- Starting Round -----")
		if not AssignTeams() then
			task.wait(IntermissionTime)
			continue
		end
		RoundActive = true

		-- monitor for win
		while RoundActive do
			local win = WinCheck()
			if win then
				EndRound(win)
				break
			end
			task.wait(3)
		end

		-- intermission
		print("Intermission for " .. IntermissionTime .. " seconds")
		task.wait(IntermissionTime)
	end
end)

Please do not ask people to write entire scripts or design entire systems for you. If you can’t answer the three questions above, you should probably pick a different category.

Here’s the revised version of your script, it should work (i think)

here are the changees:
Guaranteed at least one Aberration.
Dynamically distributes players between Aberration, Security, and Inmates based on lobby size.

Spawn Points:
Randomly assigns each player to the correct team-specific spawn point.

Win Check:
Evaluates all three teams (Inmates, Security, Aberration) to determine the winner.

Respawn Fixes:
Prevents endless respawn loops by correctly handling team assignments during active rounds.

Debugging:
Added print statements for tracking team assignments and debugging issues.

-- SERVICES
local Teams   = game:GetService("Teams")
local Players = game:GetService("Players")
local Workspace = game:GetService("Workspace")

-- CONFIG
local IntermissionTime          = 30    -- seconds between rounds
local DecontaminationCountdown  = 120   -- optional lockdown timer
local MinPlayersToStart         = 2

-- TEAM REFERENCES
local TeamInmate     = Teams:FindFirstChild("Inmate")
local TeamSecurity   = Teams:FindFirstChild("Security Personnel")
local TeamAberration = Teams:FindFirstChild("Aberration")
local TeamSpectator  = Teams:FindFirstChild("Spectator")

-- SPAWN POINTS
local spawnFolder     = Workspace:WaitForChild("SpawnPoints")
local InmateSpawns    = spawnFolder:WaitForChild("Inmates"):GetChildren()
local SecuritySpawns  = spawnFolder:WaitForChild("Security"):GetChildren()
local AberrationSpawns = spawnFolder:WaitForChild("Aberration"):GetChildren()

-- STATE
local RoundActive = false

-- UTILITIES
local function shuffle(tbl)
	for i = #tbl, 2, -1 do
		local j = math.random(1, i)
		tbl[i], tbl[j] = tbl[j], tbl[i]
	end
end

-- FORCE MID-ROUND JOINERS TO SPECTATE
Players.PlayerAdded:Connect(function(player)
	player.Team = TeamSpectator
	player.CharacterAdded:Connect(function(char)
		if RoundActive and player.Team == TeamSpectator then
			player:LoadCharacter() -- Reload if Spectator
		elseif RoundActive and player.Team ~= TeamSpectator then
			-- Prevent unnecessary respawn loops
			return
		end
	end)
end)

-- MAIN FUNCTIONS
local function AssignTeams()
	-- Gather all active players
	local pool = Players:GetPlayers()
	shuffle(pool)

	local total = #pool
	if total < MinPlayersToStart then
		warn("Not enough players to start a round.")
		return false
	end

	-- Ensure at least 1 Aberration and distribute teams based on priority
	local aberrationCount = 1 -- Always guarantee at least one Aberration
	local securityCount = math.clamp(math.floor((total - aberrationCount) / 3), 1, total - aberrationCount)
	local inmateCount = total - securityCount - aberrationCount

	local assignedAberration = 0
	local assignedSecurity = 0
	local assignedInmates = 0

	for idx, player in ipairs(pool) do
		-- Load or wait for character
		player:LoadCharacter()
		local char = player.Character or player.CharacterAdded:Wait()
		local root = char:WaitForChild("HumanoidRootPart", 5)
		if not root then
			warn("No HumanoidRootPart for", player.Name)
			continue
		end

		if assignedAberration < aberrationCount then
			player.Team = TeamAberration
			assignedAberration += 1
			local spawn = AberrationSpawns[math.random(1, #AberrationSpawns)]
			root.CFrame = spawn.CFrame
		elseif assignedSecurity < securityCount then
			player.Team = TeamSecurity
			assignedSecurity += 1
			local spawn = SecuritySpawns[math.random(1, #SecuritySpawns)]
			root.CFrame = spawn.CFrame
		else
			player.Team = TeamInmate
			assignedInmates += 1
			local spawn = InmateSpawns[math.random(1, #InmateSpawns)]
			root.CFrame = spawn.CFrame
		end
	end

	print("Assigned Teams:")
	print("Inmates:", assignedInmates)
	print("Security:", assignedSecurity)
	print("Aberrations:", assignedAberration)

	return true
end

local function WinCheck()
	local aliveIn, aliveSec, aliveAberr = 0, 0, 0
	for _, player in ipairs(Players:GetPlayers()) do
		if player.Team and player.Character then
			local hum = player.Character:FindFirstChildWhichIsA("Humanoid")
			if hum and hum.Health > 0 then
				if player.Team == TeamInmate then
					aliveIn += 1
				elseif player.Team == TeamSecurity then
					aliveSec += 1
				elseif player.Team == TeamAberration then
					aliveAberr += 1
				end
			end
		end
	end

	if aliveIn == 0 and aliveSec == 0 and aliveAberr == 0 then
		return nil  -- Nobody left alive
	elseif aliveSec == 0 and aliveAberr == 0 then
		return "Inmates"
	elseif aliveIn == 0 and aliveAberr == 0 then
		return "Security"
	elseif aliveIn == 0 and aliveSec == 0 then
		return "Aberration"
	end
	return nil  -- Round continues
end

local function EndRound(winner)
	RoundActive = false
	print(("Round ended. Winner: %s"):format(winner))

	-- Reset players to Spectator and clean up state
	for _, player in ipairs(Players:GetPlayers()) do
		player.Team = TeamSpectator
		player:LoadCharacter()
	end

	-- (Optional) Handle winner announcement or rewards here
	task.wait(IntermissionTime) -- Pause before next round
end

-- MAIN LOOP
task.spawn(function()
	while true do
		-- Wait for minimum players
		repeat
			if #Players:GetPlayers() < MinPlayersToStart then
				print("Waiting for players... (" .. MinPlayersToStart .. " required)")
				task.wait(5)
			end
		until #Players:GetPlayers() >= MinPlayersToStart

		-- Reset all to Spectator
		for _, p in ipairs(Players:GetPlayers()) do
			p.Team = TeamSpectator
			p:LoadCharacter()
		end

		-- Start a new round
		print("----- Starting Round -----")
		if not AssignTeams() then
			task.wait(IntermissionTime)
			continue
		end
		RoundActive = true

		-- Monitor for win
		while RoundActive do
			local win = WinCheck()
			if win then
				EndRound(win)
				break
			end
			task.wait(3)
		end

		-- Intermission
		print("Intermission for " .. IntermissionTime .. " seconds")
		task.wait(IntermissionTime)
	end
end)

it seems to be working better? Had to fix some problems, though now having a new issues where the players stay on the spectator team even though everything seems fine? I did comment some of the code that assigns players to the spectator team except for the one within round ending function. (players are on the spectator team by default anyways since it’s the default)

Haven’t gotten to trying more than 3 players since it gotten really tedious to use browser login manually for every instance so I’m unsure if it fixed now or not.

1 Like

I tweaked some of the code to some small problems but besides that it works and even works with more than 3 people too! TYSM for the help!

1 Like

no problem dude glad i helped!

This topic was automatically closed 14 days after the last reply. New replies are no longer allowed.