Help with Random Player Selection

I’m trying to choose a random player out of all the players that are on the “Playing” team:

local ReplicatedStorage = game:GetService("ReplicatedStorage")
local IntermissionUIRemote = ReplicatedStorage:WaitForChild("IntermissionUIRemote")
local ToMapRemote = ReplicatedStorage:WaitForChild("ToMapRemote")
local Intermission = game.Workspace:WaitForChild("Intermission")
local OnGoingRound = game.Workspace:WaitForChild("OnGoingRound")
local CanClickStartButton = game.Workspace:WaitForChild("CanClickStartButton")
local CanStartIntermission = game.Workspace:WaitForChild("CanStartIntermission")
local CompleteSound = game.Workspace:WaitForChild("CompleteSound")

-- Teleport all players to a random spawn and select a random tagger
local function teleportPlayers(plr)
	
	plr.PlayerGui.PickingIT.Frame.Visible = true
	plr.PlayerGui.PickingIT.Frame.Text.Visible = true
	
	CanClickStartButton.Value = false
	CanStartIntermission.Value = false
	Intermission.Value = false
	OnGoingRound.Value = true

	plr.Team = game.Teams.Playing

	local MapSpawns = game.Workspace:WaitForChild("TagMap"):GetChildren()
	local validSpawns = {}

	-- Collect all valid spawn locations
	for _, v in pairs(MapSpawns) do
		if v.Name == "MapSpawn" then
			table.insert(validSpawns, v)
		end
	end

	if #validSpawns > 0 then
		-- Teleport all players to a random spawn point
		local randomSpawn = validSpawns[math.random(1, #validSpawns)]
		if plr.Character then
			plr.Character:SetPrimaryPartCFrame(randomSpawn.CFrame)
		end
	end

	-- Set a random walk speed
	plr.Character:WaitForChild("Humanoid").WalkSpeed = 145

	wait(0.5)

	local allPlayers = game:GetService("Players")

	local playersTable = {}
	for _, v in pairs(allPlayers:GetPlayers()) do
		if v.Team == game.Teams.Playing then
			table.insert(playersTable, v)
		end
	end

	wait(5)

	-- Select a random player from the playersTable
	local ChosenTagger = playersTable[math.random(1,#playersTable)]  -- Choose ONE random player

	if ChosenTagger and ChosenTagger.Character then
		local TaggerBool = ChosenTagger.Character:FindFirstChild("Tagger")
		if TaggerBool then
			TaggerBool.Value = true
			ChosenTagger.Character:WaitForChild("Humanoid").WalkSpeed = 150

			if ChosenTagger.PlayerGui:FindFirstChild("TagCooldownUI") then
				ChosenTagger.PlayerGui.TagCooldownUI:WaitForChild("Frame").Visible = true
			end
			
			plr.PlayerGui.PickingIT.Frame.Text.Visible = false
			plr.PlayerGui.PickingIT.Frame.Text2.Visible = true
			CompleteSound:Play()
			
			wait(1)
			
			for i = 0, 1, 0.05 do
				plr.PlayerGui.PickingIT.Frame.Text2.TextTransparency = i
				plr.PlayerGui.PickingIT.Frame.BackgroundTransparency = i
				task.wait(0.05)  -- Controls the speed of the fade (smaller value means slower fade)
			end
			
			plr.PlayerGui.PickingIT.Frame.Visible = false
			plr.PlayerGui.PickingIT.Frame.Text.Visible = false
			plr.PlayerGui.PickingIT.Frame.Text2.Visible = false
			
		end
	end

	wait()
	game.Workspace:WaitForChild("OnGoingRound").Value = true
	game.Workspace:WaitForChild("Intermission").Value = false
	game.Workspace:WaitForChild("CanStartIntermission").Value = false
end

ToMapRemote.OnServerEvent:Connect(function(plr)
	-- Teleport players to the map and select the Tagger
	teleportPlayers(plr)
end)

My issue: Sometimes it’ll choose just one player, which is good, but then it would sometimes choose all or more than one.

If you need more information, let me know!

Thanks!

Could ToMapRemote be triggered multiple times? or teleportPlayers

1 Like

It’s fired from the client so do you think that means it’s being triggered the amount of times that there is for the amount of players in the game?

I’d need to see the script that triggers it

1 Like
local IntermissionUIRemote = game:GetService("ReplicatedStorage"):WaitForChild("IntermissionUIRemote")
local STOPIntermissionUIRemote = game:GetService("ReplicatedStorage"):WaitForChild("STOPIntermissionUIRemote")
local ResetIntermissionUIRemote = game:GetService("ReplicatedStorage"):WaitForChild("ResetIntermissionUIRemote")
local ToMapRemote = game:GetService("ReplicatedStorage"):WaitForChild("ToMapRemote")

local Frame = script.Parent
local Countdown = script.Parent:WaitForChild("Countdown")
local CountdownTick = game.Workspace:WaitForChild("CountdownTick")

local isCountingDown = false
local countdownCoroutine = nil -- To store the countdown coroutine so we can stop it

local function startCountdown()
	-- Show the countdown UI
	Frame.Visible = true
	Frame.Parent.Parent:WaitForChild("WaitingForPlr"):WaitForChild("Label").Visible = false

	-- Countdown from 30 to 0
	for i = 30, 0, -1 do
		if not isCountingDown then
			return -- Stop the countdown if isCountingDown is false (e.g., reset or stop happened)
		end

		Countdown.Text = tostring(i)
		CountdownTick:Play()
		wait(1)
	end

	-- After the countdown, hide the UI and trigger teleport
	Frame.Visible = false
	ToMapRemote:FireServer()
end

IntermissionUIRemote.OnClientEvent:Connect(function()
	-- Ensure we are not already counting down
	if isCountingDown then return end

	-- Start the countdown
	isCountingDown = true
	countdownCoroutine = coroutine.create(startCountdown)
	coroutine.resume(countdownCoroutine)
end)

ResetIntermissionUIRemote.OnClientEvent:Connect(function()
	-- Reset the countdown if it's running
	isCountingDown = false

	-- Stop the countdown coroutine if it's running
	if countdownCoroutine then
		-- We can't stop the coroutine directly, but we can have it exit early.
		-- Just set isCountingDown to false to break the loop in the countdown.
		countdownCoroutine = nil
	end

	-- Reset UI (countdown value and visibility)
	Countdown.Text = "30"  -- Reset the countdown to 30
	Frame.Visible = false   -- Hide the countdown UI

	-- Start the countdown again from 30
	isCountingDown = true
	countdownCoroutine = coroutine.create(startCountdown)
	coroutine.resume(countdownCoroutine)
end)

STOPIntermissionUIRemote.OnClientEvent:Connect(function()
	-- Stop the countdown when STOP event is triggered
	isCountingDown = false

	-- Hide the countdown UI
	Frame.Visible = false

	-- Optionally reset the countdown text to 30
	Countdown.Text = "30"
end)

This is the LocalScript that triggers it^^^. That LocalScript is triggered from A serverScript which is:

local Button = script.Parent
local players = game:GetService("Players")
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local Workspace = game:GetService("Workspace")

-- BoolValues
local Intermission = Workspace:WaitForChild("Intermission")
local CanStartIntermission = Workspace:WaitForChild("CanStartIntermission")
local OnGoingRound = Workspace:WaitForChild("OnGoingRound")
local CanClickStartButton = Workspace:WaitForChild("CanClickStartButton")

-- Remotes
local IntermissionUIRemote = ReplicatedStorage:WaitForChild("IntermissionUIRemote")
local GameResetRemote = ReplicatedStorage:WaitForChild("GameResetRemote")
local STOPIntermissionUIRemote = ReplicatedStorage:WaitForChild("STOPIntermissionUIRemote")
local ResetIntermissionUIRemote = ReplicatedStorage:WaitForChild("ResetIntermissionUIRemote")

local StartButton = Workspace:WaitForChild("StartButton"):WaitForChild("Glow")
local StartButtonSound = Button.Parent:WaitForChild("StartButtonSound")
local BlockedStartButtonSound = Button.Parent:WaitForChild("BlockedStartButtonSound")

local ButtonClicked = false
local ClickCooldown = false

local EnoughPlr = false
local NotEnoughPlr = false

-- Table to track players that are fully loaded
local playersFullyLoaded = {}

local function startCountdown()
	IntermissionUIRemote:FireAllClients()
end

local function resetCountdown()
	ResetIntermissionUIRemote:FireAllClients()
end

local function stopCountdown()
	STOPIntermissionUIRemote:FireAllClients()
end

local function allPlayersReady()
	for _, player in pairs(players:GetPlayers()) do
		if not playersFullyLoaded[player.UserId] then
			return false
		end
	end
	return true
end

local function updatePlayerStatus(plr)
	if ClickCooldown then
		-- If cooldown is active, return early
		print("Click cooldown active! Player cannot click yet.")
		return
	end

	ClickCooldown = true -- Set cooldown to true

	local playerCount = #players:GetPlayers()
	if playerCount > 1 then
		EnoughPlr = true
		NotEnoughPlr = false
		CanStartIntermission.Value = true
		print("There is more than one player in the server.")
	elseif playerCount == 1 then
		EnoughPlr = false
		NotEnoughPlr = true
		CanStartIntermission.Value = false
		Intermission.Value = false
		OnGoingRound.Value = false
		print("There is one or no players in the server.")
	end

	if EnoughPlr then
		-- Ensure the UI is visible for the player
		local waitingForPlrLabel = plr.PlayerGui:WaitForChild("WaitingForPlr"):WaitForChild("Label")
		waitingForPlrLabel.Visible = false

		StartButton.Color = Color3.fromRGB(0, 255, 0)

		if CanClickStartButton.Value == true then
			CanStartIntermission.Value = true
			OnGoingRound.Value = false
			CanClickStartButton.Value = false

			if ButtonClicked == true then
				resetCountdown()
				Intermission.Value = true
				
				ButtonClicked = false

				-- Ensure the sound is playing
				if StartButtonSound then
					StartButtonSound:Play()
				else
					print("StartButtonSound not found!")
				end

				StartButton.Color = Color3.fromRGB(255, 0, 0)
				wait(1)  -- You can consider using a different timing mechanism if needed
				ClickCooldown = false
			end
		end

	elseif NotEnoughPlr then
		local allPlayers = players:GetPlayers()
		if OnGoingRound.Value == true then
			for _, v in pairs(allPlayers) do
				v.Team = game.Teams.Lobby
				if v.Character then
					v.Character:BreakJoints()  -- Break joints if character exists
				end
			end

			stopCountdown()
		end

		stopCountdown()

		-- Ensure the UI is visible for the player
		local waitingForPlrLabel = plr.PlayerGui:WaitForChild("WaitingForPlr"):WaitForChild("Label")
		waitingForPlrLabel.Visible = true

		CanStartIntermission.Value = false
		OnGoingRound.Value = false
		Intermission.Value = false
		CanClickStartButton.Value = true

		-- Play the blocked sound if button was clicked
		if ButtonClicked == true then
			
			ButtonClicked = false
			
			if BlockedStartButtonSound then
				BlockedStartButtonSound:Play()
			else
				print("BlockedStartButtonSound not found!")
			end
		end
		StartButton.Color = Color3.fromRGB(255, 0, 0)
	end

	wait(1) -- This is important so the cooldown isn't set to false too soon
	ClickCooldown = false -- Reset cooldown after 1 second
end

-- Player added event
players.PlayerAdded:Connect(function(plr)
	playersFullyLoaded[plr.UserId] = false

	plr.CharacterAdded:Connect(function(char)
		local humanoid = char:WaitForChild("Humanoid")
		humanoid.Health = humanoid.MaxHealth
		playersFullyLoaded[plr.UserId] = true
		print(plr.Name .. " has fully loaded.")

		updatePlayerStatus(plr)
	end)

	plr.CharacterRemoving:Connect(function()
		playersFullyLoaded[plr.UserId] = false
		updatePlayerStatus(plr)
	end)

	updatePlayerStatus(plr)
end)

-- Player removing event
players.PlayerRemoving:Connect(function(plr)
	playersFullyLoaded[plr.UserId] = false
	updatePlayerStatus(plr)
end)

-- Button click handling
Button.MouseClick:Connect(function(plr)
	ButtonClicked = true
	if CanClickStartButton.Value == true then
		updatePlayerStatus(plr)
	end
end)

It’s a mess and I’m so sorry for that. I’ve been at this for days now and it’s all just falling apart.

The serverScript that triggers the localScript, which triggers the ToMap stuff is a script that constantly checks for more than one player in the game when players join, die, leave… and if there’s more than one player, then it does what it needs to (which doesn’t work properly).***

The LocalScript controls the 30 second countdown intermission UI – Stop, Reset, Start countdown.

1 Like

Try putting something like “print(‘countdown’)” in the startCountdown script so you can debug how many times the ToMapRemote function is being called

1 Like

The local script, correct? 111111111111111111111111111111111111111

local function startCountdown()
	-- Show the countdown UI
	Frame.Visible = true
	Frame.Parent.Parent:WaitForChild("WaitingForPlr"):WaitForChild("Label").Visible = false

	-- Countdown from 30 to 0
	for i = 30, 0, -1 do
		if not isCountingDown then
			return -- Stop the countdown if isCountingDown is false (e.g., reset or stop happened)
		end

		Countdown.Text = tostring(i)
		CountdownTick:Play()
		wait(1)
	end

	-- After the countdown, hide the UI and trigger teleport
	Frame.Visible = false
	ToMapRemote:FireServer()
end

Here

1 Like


Only did it once, but that’s on the Client side of the DevLog (idk if that matters)

I know what’s wrong. I’m testing w/ my alt and i turn my alt’s game volume all the way down, and I can hear the sounds (for the UI, etc…) being played twice. it’s being triggered the amount of times that there is #of players in the game: (2 players in the game = triggered 2 times). But I don’t know how to go about fixing this. Let me know when you think of something please. Thanks for helping too!

I suggest you rewrite most of the code to make it work on a server script while using local scripts for the bare minimum, use module scripts if you know how to. It’ll make debugging easier while making the game less susceptible to exploits. You already have an idea of what you want to do so rewriting the code to run on the server would be easy.

1 Like

This is actually not that hard to code. Here’s a script that selects a random player on the server-side to prevent exploits:

local players = game.Players:GetChildren()
local plrCount = #players

-- make sure there's actually players in the game
if plrCount > 0 then
    local target = players[math.random(plrCount)]
    if target:IsA("Player") then
         print(target.Name)
    end
else
    print("no players found") 
end

This uses the math.random() function and the Players class to select a random player by returning a list of children in the parent. Hope this helped! If you needed something else, feel free to tell me.

1 Like

I don’t know how to work with Module scripts, but I’m lost when you say localScripts should be used for the bare minimum when I need local scripts to control the Intermission Countdown UI. unless there’s a way to do that on the server (which would be so much easier). Thanks for helping btw!

I Tried this and now it always picks all players. I’m very positive it’s doing this because the FireAllClients() that are called from the ServerScript – handling the playerjoin/leaves and button press. Thank you so much for helping!!

I tried to do it on the server side as much as possible and it works, but it almost feels like it’s doing the functions for one player at a time; button is clicked, started AND shows countdown ui for one player (not the other one YET) and teleports them, THEN starts AND shows countdown ui for the other player and then teleports them. Here’s the server script that it’s all in (this script is located in ServerScriptService):

local Button = game.Workspace:WaitForChild("StartButton"):WaitForChild("Button"):WaitForChild("ClickDetector")
local players = game:GetService("Players")
local ReplicatedStorage = game:GetService("ReplicatedStorage")

-- BoolValues
local Intermission = game.Workspace:WaitForChild("Intermission")
local CanStartIntermission = game.Workspace:WaitForChild("CanStartIntermission")
local OnGoingRound = game.Workspace:WaitForChild("OnGoingRound")
local CanClickStartButton = game.Workspace:WaitForChild("CanClickStartButton")

-- Remotes
local IntermissionUIRemote = ReplicatedStorage:WaitForChild("IntermissionUIRemote")
local GameResetRemote = ReplicatedStorage:WaitForChild("GameResetRemote")
local STOPIntermissionUIRemote = ReplicatedStorage:WaitForChild("STOPIntermissionUIRemote")
local ResetIntermissionUIRemote = ReplicatedStorage:WaitForChild("ResetIntermissionUIRemote")

local StartButton = game.Workspace:WaitForChild("StartButton"):WaitForChild("Glow")
local StartButtonSound = Button.Parent:WaitForChild("StartButtonSound")
local BlockedStartButtonSound = Button.Parent:WaitForChild("BlockedStartButtonSound")

local ButtonClicked = false
local ClickCooldown = false

local EnoughPlr = false
local NotEnoughPlr = false

local isCountingDown = false
local countdownCoroutine = nil
local CountdownTick = game.Workspace:WaitForChild("CountdownTick")

-- Table to track players that are fully loaded
local playersFullyLoaded = {}

local function allPlayersReady()
	for _, player in pairs(players:GetPlayers()) do
		if not playersFullyLoaded[player.UserId] then
			return false
		end
	end
	return true
end

-- Teleport all players to a random spawn and select a random tagger
local function teleportPlayers()
	for _, v in pairs(players:GetPlayers()) do
		local player = v
		player.PlayerGui.PickingIT.Frame.Visible = true
		player.PlayerGui.PickingIT.Frame.Text.Visible = true

		CanClickStartButton.Value = false
		CanStartIntermission.Value = false
		Intermission.Value = false
		OnGoingRound.Value = true

		player.Team = game.Teams.Playing

		local MapSpawns = game.Workspace:WaitForChild("TagMap"):GetChildren()
		local validSpawns = {}

		-- Collect all valid spawn locations
		for _, v in pairs(MapSpawns) do
			if v.Name == "MapSpawn" then
				table.insert(validSpawns, v)
			end
		end

		if #validSpawns > 0 then
			-- Teleport all players to a random spawn point
			local randomSpawn = validSpawns[math.random(1, #validSpawns)]
			if player.Character then
				player.Character:SetPrimaryPartCFrame(randomSpawn.CFrame)
			end
		end

		-- Set a random walk speed
		player.Character:WaitForChild("Humanoid").WalkSpeed = 145

		local allPlayers = game:GetService("Players")
		local playersTable = {}

		-- Get players who are in the "Playing" team
		for _, v in pairs(allPlayers:GetPlayers()) do
			if v.Team == game.Teams.Playing then
				table.insert(playersTable, v)
			end
		end

		wait(5)

		-- Ensure only one player is chosen as the tagger
		local ChosenTagger = playersTable[math.random(1, #playersTable)]  -- Choose ONE random player
		if ChosenTagger and ChosenTagger.Character then
			local TaggerBool = ChosenTagger.Character:FindFirstChild("Tagger")
			local CompleteSound = game.Workspace:WaitForChild("CompleteSound")
			if TaggerBool then
				TaggerBool.Value = true
				ChosenTagger.Character:WaitForChild("Humanoid").WalkSpeed = 150

				if ChosenTagger.PlayerGui:FindFirstChild("TagCooldownUI") then
					ChosenTagger.PlayerGui.TagCooldownUI:WaitForChild("Frame").Visible = true
				end

				-- Hide the prompt for the original player
				player.PlayerGui.PickingIT.Frame.Text.Visible = false
				player.PlayerGui.PickingIT.Frame.Text2.Visible = true
				CompleteSound:Play()

				wait(1)

				-- Fade out the "Picking IT" UI
				for i = 0, 1, 0.05 do
					player.PlayerGui.PickingIT.Frame.Text2.TextTransparency = i
					player.PlayerGui.PickingIT.Frame.BackgroundTransparency = i
					task.wait(0.05)
				end

				-- Hide the "Picking IT" UI after the fade
				player.PlayerGui.PickingIT.Frame.Visible = false
				player.PlayerGui.PickingIT.Frame.Text.Visible = false
				player.PlayerGui.PickingIT.Frame.Text2.Visible = false
			end
		end
	end

end


local function startCountdown()
	
	for _, v in pairs(players:GetPlayers()) do
		local player = v
		local Frame = player.PlayerGui:WaitForChild("IntermissionUI"):WaitForChild("Frame")
		local Countdown = Frame:WaitForChild("Countdown")

		-- Show the countdown UI
		Frame.Visible = true
		Frame.Parent.Parent:WaitForChild("WaitingForPlr"):WaitForChild("Label").Visible = false

		-- Countdown from 30 to 0
		for i = 10, 0, -1 do
			if not isCountingDown then
				return -- Stop the countdown if isCountingDown is false (e.g., reset or stop happened)
			end

			Countdown.Text = tostring(i)
			CountdownTick:Play()
			wait(1)
		end

		-- After the countdown, hide the UI and trigger teleport
		Frame.Visible = false
		isCountingDown = false
		teleportPlayers()
	end
end

local function updatePlayerStatus2()

	ClickCooldown = true -- Set cooldown to true

	if EnoughPlr then

		StartButton.Color = Color3.fromRGB(0, 255, 0)

		if CanClickStartButton.Value == true then
			CanStartIntermission.Value = true
			OnGoingRound.Value = false

			if ButtonClicked == true then
				isCountingDown = true
				startCountdown()
				Intermission.Value = true
				CanClickStartButton.Value = false
				ButtonClicked = false

				-- Ensure the sound is playing
				if StartButtonSound then
					StartButtonSound:Play()
				else
					print("StartButtonSound not found!")
				end

				StartButton.Color = Color3.fromRGB(255, 0, 0)
				wait(1)  -- You can consider using a different timing mechanism if needed
				ClickCooldown = false
			end
		end

	elseif NotEnoughPlr then
		local allPlayers = players:GetPlayers()
		if OnGoingRound.Value == true then
			for _, v in pairs(allPlayers) do
				v.Team = game.Teams.Lobby
				if v.Character then
					v.Character:BreakJoints()  -- Break joints if character exists
				end
			end

			--stopCountdown()
		end

		--stopCountdown()

		CanStartIntermission.Value = false
		OnGoingRound.Value = false
		Intermission.Value = false
		CanClickStartButton.Value = true

		-- Play the blocked sound if button was clicked
		if ButtonClicked == true then

			ButtonClicked = false

			if BlockedStartButtonSound then
				BlockedStartButtonSound:Play()
			else
				print("BlockedStartButtonSound not found!")
			end
		end
		StartButton.Color = Color3.fromRGB(255, 0, 0)
	end

	wait(1) -- This is important so the cooldown isn't set to false too soon
	ClickCooldown = false -- Reset cooldown after 1 second
end

local function updatePlayerStatus1()

	local playerCount = #players:GetPlayers()

	-- Check if there are enough players (at least 2)
	if playerCount > 1 then
		EnoughPlr = true
		NotEnoughPlr = false
		CanStartIntermission.Value = true
		StartButton.Color = Color3.fromRGB(0, 255, 0)
		print("There is more than one player in the server.")
	elseif playerCount == 1 then
		EnoughPlr = false
		NotEnoughPlr = true
		CanStartIntermission.Value = false
		Intermission.Value = false
		OnGoingRound.Value = false
		StartButton.Color = Color3.fromRGB(255, 0, 0)
		print("There is one or no players in the server.")
	end
end

-- Player joining event
players.PlayerAdded:Connect(function(plr)
	playersFullyLoaded[plr.UserId] = false

	plr.CharacterAdded:Connect(function(char)
		local humanoid = char:WaitForChild("Humanoid")
		humanoid.Health = humanoid.MaxHealth
		playersFullyLoaded[plr.UserId] = true
		print(plr.Name .. " has fully loaded.")
	end)

	plr.CharacterRemoving:Connect(function()
		playersFullyLoaded[plr.UserId] = false
	end)

	updatePlayerStatus1()
end)

-- Player leaving event
players.PlayerRemoving:Connect(function(plr)
	playersFullyLoaded[plr.UserId] = false

	updatePlayerStatus1()
end)

-- Button click handling
Button.MouseClick:Connect(function()
	ButtonClicked = true
	if CanClickStartButton.Value == true then

		updatePlayerStatus2()
	end
end)

you could put a

coroutine.resume(coroutine.create(function()

at the beginning of the for loop above the player variable
then a

end))

at the end