Role randomizer not working

One random player has to be “Guard” and that works, but other roles don’t. Each role can be in use by only one player.

local Players = game:GetService("Players")
local ReplicatedStorage = game:GetService("ReplicatedStorage")

local roles = {
	"One",
	"Two",
	"Three",
	"Four"
}

local function onPlayerJoin(player)
	local role = Instance.new("StringValue")
	role.Name = "Role"
	role.Parent = player
end

local function onGameEnd(gameState)
	if gameState == "Waiting for players" then
		for i, v in pairs(Players:GetPlayers()) do
			v.Role.Value = "None"
		end
	end
end

local function onIntermission(gameState)
	if gameState == "Intermission" then
		local availableRoles = {}
		for i, v in pairs(roles) do
			availableRoles[i] = v
		end
		local availablePlayers = {}
		for i, v in pairs(Players:GetPlayers()) do
			availablePlayers[i] = v
		end
		local selectedPlayerGuard = math.random(1, #availablePlayers)
		Players:GetPlayers()[selectedPlayerGuard].Role.Value = "Guard"
		table.remove(availablePlayers, selectedPlayerGuard)
		repeat
			wait()
			local selectedRole = math.random(1, #availableRoles)
			local selectedPlayer = math.random(1, #availablePlayers)
			print(availableRoles[selectedRole], Players:GetPlayers()[selectedPlayer])
			Players:GetPlayers()[selectedPlayer].Role.Value = availableRoles[selectedRole]
			table.remove(availableRoles, selectedRole)
			table.remove(availablePlayers, selectedPlayer)
		until #availablePlayers == 0
	end
end

Players.PlayerAdded:Connect(onPlayerJoin)
ReplicatedStorage.GameState.Changed:Connect(onGameEnd)
ReplicatedStorage.GameState.Changed:Connect(onIntermission)

You forgot to remove “Guard” on availableRoles table/array.

availableRoles comes from roles which doesnt contain guard

Oh, i thought the Guard role was in it sorry for a bit misunderstanding.

One question, do you tested this script with someone else so it can loop? if yes i don’t really know to where the error cames up…

I recommend testing this out with a few friends as if your testing it yourself you will always get the guard role since the guard role is picked first.
I would recommend changing this line,

Players:GetPlayers()[selectedPlayerGuard].Role.Value = "Guard"

To

selectedPlayerGuard.Role.Value = "Guard"

Reasoning is you already have the player as a variable.

local client tests. there are no errors, just sometimes there are more than one of the same role in game, and sometimes someone doesnt have any role.

local selectedPlayerGuard = math.random(1, #availablePlayers) is a number value

image
code acts like it didnt remove roles from the table, even tho i told it to do so here
(code for print print(availableRoles[selectedRole], Players:GetPlayers()[selectedPlayer]).)

table.remove(availablePlayers, selectedPlayerGuard)

table.remove(availableRoles, selectedRole)
table.remove(availablePlayers, selectedPlayer)

~~ Ah yeah, Wasnt paying too much attention, what seems to be happening with players not getting a role is lets say there’s 5 roles, we run local selectedRole = math.random(1, #availableRoles) to pick the role according to the amount of available roles, lets say the number 4 was picked so this line table.remove(availableRoles, selectedRole) removes it, so now it runs the selectedRole again with the number being anywhere from 1, 4. Well it picks 4 again but 4 isnt an available role so when it comes to this line Players:GetPlayers()[selectedPlayer].Role.Value = availableRoles[selectedRole] it assigns duplicate roles. ~~ Realized it would just pick what was the previous 5th role.

I’m not 100% on this logic but I think this is what might be happening, correct me if im wrong.

This confirms my guess, even though the role is removed it can still be assigned.

but it is supposed to choose roles from the availableRoles table. if it gets removed then its not in the table so it shouldnt be able to get chosen

Hmmm true, I’m no longer sure as to why its not working. Only suggestion I have now is trying a index in value loop on the role picking part. It shouldn’t make a difference but lua is weird and small things like that can matter. Either way the issue is out of my realm of experience, Hopefully someone can come along and find the issue for you.

i will try and let u know if that helped. thanks for help

nope that doesnt work as well. three people couldnt fix my script already. i guess i will have to write it over again

I am taking a look at this but I already can say that this is very… strange. It appears you wrote the function as if you will have a single gamestate in a single function yet you bind separate functions and even then again check the “gamestate” in an if statement.

I’ll reply once again once I am done my testing but don’t wait for me in the meantime.

When testing solo I did note encounter the issue that you had, however I did find this.

It appears that you force a random person to be the guard, remove them from the pool, then try to set a role for them. You wouldn’t really find this if you were testing with multiple people and one of the last people got picked as the Guard, but as I was testing solo it happened immediately.

I have a feeling that this is also similar to the issue you have.

I was Wrong-ish, I did more testing while typing this out and figured out more of the issue

An issue, which I presume is one of the issues, is that you select the player to change out of the pool of people.

For example, a 2 player array would be

  • Player1
  • Player2

You randomly pick 1 out of the 2 entry long array. That will now choose Player1 and get the position 1 from GetPlayers() array, which because the pool of players was made from GetPlayers(), lines up correctly. You then remove integer 1 from the available players, which is Player1. This is where the problem is made.

The new list after removing Player1 is

  • Player2

When you pick again, you are randomly picking from 1 - the length of the pool, which is also 1. You pick integer 1, which is Player2. When you get the Player object from GetPlayers(), you get the player at position 1. You end up picking Player1 again, as it was the first player in the GetPlayers() list. You then set it to the role again, however you remove Player2 from the available player pool as it is position 1 in that array.

To summarize, your “selected” player integer is not the same number in the GetPlayers() array. This and the beforementioned Guest no check means rewrites of multiple players and more issues.

The fix

Firstly, if you are going to pass a “gamestate” variable I would remove the OnGameEnd thing in it’s entirety so you do not need to link multiple functions to the same connect (this is just my preference, however you may keep it the same if it works for you).

Next, don’t make multiple arrays if your data is just going to be strung together all the time. You want what you need to specifically reference, not a list of what you might need to reference and then a specific number for it. What I would do is get the PlayerObject and make a list of what roles are NOT available, and then you can simplify your code.

This has been a long explanation which may not necessarily help as much as seeing the code itself, so below I provide beautiful and working code. :smirk: Please let me know if you encounter more errors from this fix, and code on!

-- >> init vars
local Players = game:GetService("Players")
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local studio = game:GetService("RunService"):IsStudio()

-- >> our cool tables <<
local takenRoles = {}

local roles = {
	"dog",
	"cat",
	"foo",
	"oof"
}

-- >> FUNCTIONS HECK YEAHHHH <<
local function print2(stuff_to_print) -- for printing only in studio, you can delete this and control + f delete all the print2s if you want
	if studio then
		print(stuff_to_print)
	end	
end


local function onPlayerJoin(player) 
	print2("[ROLE] GOT PLAYER CONNECT AS")
	print2(player)
	
	local role = Instance.new("StringValue")
	role.Name = "Role"
	role.Parent = player
end


local function onGameStateChange(gameState) -- changed function name
	if gameState == "Waiting for players" then -- I recommend doing something like "waiting" to shorten your vars but whatever floats your boat
		
		table.clear(takenRoles) -- cleanup our taken roles
		
		for i, v in ipairs(Players:GetPlayers()) do -- do ipairs so you get an integer pair back, pairs is meant for dictionaries
			v.Role.Value = "None"
		end
	end
	
	if gameState == "inter" then -- shortened for time purposes
		
		local players_right_now = Players:GetPlayers()
		print2(players_right_now)
		
		for index, playerObject in ipairs(players_right_now) do -- do ipairs so you get an integer pair back, pairs is meant for dictionaries
			print2("Began search")
			local ChosenRole
			repeat
				local crINT = math.random(1,#roles)  --chosenroleInteger to pick from our array
				ChosenRole = roles[crINT]-- Make sure all the roles are strings
				print2("Picked "..ChosenRole) 
			until table.find(takenRoles, ChosenRole) == nil --find if the provided string is found in the array takenRoles, will return nil if cannot be found
			table.insert(takenRoles, ChosenRole)
			
			print2("Set "..playerObject.Name.."'s role")
			playerObject.Role.Value = ChosenRole
		end
		
	end
end

-- >> fancy connections <<
Players.PlayerAdded:Connect(onPlayerJoin)
ReplicatedStorage.GameState.Changed:Connect(onGameStateChange) -- bound to new function

print("Game role script has began - Made by evilmen8")
2 Likes

everything seems to be working and works great, however you forgot that one of the players MUST be a guard (one player should be a guard even if player count is less than role count). ive never worked with ipairs() so i dont really know how to code this.

i also dont understand how the script knows which player has a role assigned and which doesnt [edit1: okay i understand now that scripts takes random role until it doesnt find this role in takenRoles{}]. i have to exclude on of the players from players table and assign them with guard role. so i did this:

if gameState == "Intermission" then -- shortened for time purposes

		local availablePlayers = Players:GetPlayers()
		print2(availablePlayers)
		
		local cgINT = math.random(1, #availablePlayers) -- chosen guard integer
		local cg = availablePlayers[cgINT] -- chosen guard player
		cg.Role.Value = "Guard"
		table.remove(availablePlayers, cgINT)
		
		print2("Set "..cg.Name.."'s role to Guard")
		
		for index, playerObject in ipairs(availablePlayers) do -- do ipairs so you get an integer pair back, pairs is meant for dictionaries
			print2("Began search")
			local ChosenRole
			repeat
				local crINT = math.random(1, #roles)  --chosenroleInteger to pick from our array
				ChosenRole = roles[crINT] -- Make sure all the roles are strings
				print2("Picked "..ChosenRole) 
			until table.find(takenRoles, ChosenRole) == nil -- find if the provided string is found in the array takenRoles, will return nil if cannot be found
			table.insert(takenRoles, ChosenRole)

			print2("Set "..playerObject.Name.."'s role")
			playerObject.Role.Value = ChosenRole
		end

	end

it works nicely, but when there are more roles than players in the server, repeat loop is gonna roll for role that isnt being used, often causing this:
image
it is not a problem for now, with 1 excessive role, but what if there will be 6 players and 10 available roles (i will add more roles for sure)? it will roll sometimes 10 times, sometimes once, sometimes 1000 times if we get unlucky. isnt it going to cause delays? if yes then how to avoid them?

You could just make Guard be one of the roles from the active pool and if it no one gets it picked then you can force one person to get it at the end.

If you want the loop to end before all roles are chosen in case servers are not max size, do

until table.find(takenRoles, ChosenRole) == nil or #takenRoles >= #availablePlayers

Just thinking off the top of my head here, if that doesn’t work here is my explanation: We will repeat until all roles are done OR if the takenRoles’ length is greater or exactly the length of availablePlayers

I also want to note that this code does not update new players to the list once the game has been initiated, just so you know. If someone joins 0.1 second after the GetPlayers() is called they will be left out. This is an common practice to be done and only a few games out there let you join mid-match so I didn’t implement that at all, but you can if you want.

1 Like