I am making a system which gives out roles but it has a few problems

I have been trying to make a role system which for each role, they have a different max limit to give to people. If that doesn’t make sense here is an example: The werewolf card’s max limit is 2, so when 2 players gain the werewolf role, no other player can get the werewolf card.

The problem is that when a role reaches its max limit, it doesn’t stop. It keeps giving that same role to other people.

Scripts affiliated with role system:

-- Module script
local CardModule = {}




CardModule.MaximumRoles = {

	["Werewolf"] = {
		["MaximumRole"] = 2;
	};

	["Villager"] = {
		["MaximumRole"] = 1;
	};

	["Hunter"] = {
		["MaximumRole"] = 1;
	};

	["Tanner"] = {
		["MaximumRole"] = 1;
	};

	["Seer"] = {
		["MaximumRole"] = 1;
	};

	["Minion"] = {
		["MaximumRole"] = 1;
	};

	["Drunk"] = {
		["MaximumRole"] = 1;
	};

	["Mason"] = {
		["MaximumRole"] = 2;
	};
}




CardModule.ChooseRandomCard = function()

			for i,player in pairs(game.Players:GetPlayers()) do
		if player:FindFirstChildWhichIsA("IntValue").Value < 1 then
			for i,intvalue in pairs(player:GetChildren()) do
				if intvalue.ClassName == "IntValue" then
					if intvalue.Value < 1 then
			for i,v in pairs(game.ReplicatedStorage.RoleValues:GetChildren()) do
				if v.Value < CardModule.MaximumRoles[v.Name].MaximumRole then
					print(v.Name)
								return v.Name
							else
								CardModule.ChooseRandomCard()
				end
						end
					else
						local folder = player 

						local greatestVal = 0
						local greatestValObject
						for I,v in pairs(folder:GetChildren()) do
							if v.Value == greatestVal then
								greatestVal = v.Value
								greatestValObject = v
								print(greatestValObject.Name)
							end
							
						end
						end
			end
					end
		end
		end
		end
	


return CardModule



-- Local script
game.ReplicatedStorage.RoleNarraration.OnClientEvent:Connect(function()
	local cardModule = require(game.ReplicatedStorage.ModuleScript)
	local card = cardModule.ChooseRandomCard()
	wait(1)
	if card == nil then
		for i,v in pairs(game.Players:GetPlayers()) do
			if v:FindFirstChildWhichIsA("IntValue").Value < 1 then
				for i,v in pairs(game.ReplicatedStorage.RoleValues:GetChildren()) do
					if v.Value < cardModule.MaximumRoles[v.Name].MaximumRole then

						print(v.Name)
						game.ReplicatedStorage.Role:FireServer()
						game.ReplicatedStorage.Role3:FireServer(card)

						game.ReplicatedStorage.Role.OnClientEvent:Connect(function(value)





							game.ReplicatedStorage.RoleValues:FindFirstChild(value).Value += 1
							script.Parent.Parent.Exit.Visible = true

							game.ReplicatedStorage.BillboardGuie.OnClientEvent:Connect(function(text)
								local clone = game.ReplicatedStorage.BillboardGui:Clone()
								clone.Parent = game.Players.LocalPlayer.Character:WaitForChild("Head")
								game.Players.LocalPlayer.Character.Head:WaitForChild("BillboardGui").TextLabel.Text = text
							end)
						end)
						end
				end
			end
			end
	else

		game.ReplicatedStorage.Role:FireServer()
		game.ReplicatedStorage.Role3:FireServer(card)

		game.ReplicatedStorage.Role.OnClientEvent:Connect(function(value)





			game.ReplicatedStorage.RoleValues:FindFirstChild(value).Value += 1
			script.Parent.Parent.Exit.Visible = true

			game.ReplicatedStorage.BillboardGuie.OnClientEvent:Connect(function(text)
				local clone = game.ReplicatedStorage.BillboardGui:Clone()
				clone.Parent = game.Players.LocalPlayer.Character:WaitForChild("Head")
				game.Players.LocalPlayer.Character.Head:WaitForChild("BillboardGui").TextLabel.Text = text
			end)
		end)
	end
end)


-- Script
game.Players.PlayerAdded:Connect(function(player)	
	local villager = Instance.new("IntValue")
	villager.Name = "Villager"
	villager.Parent = player
	local werewolf = Instance.new("IntValue")
	werewolf.Parent = player
	werewolf.Name = "Werewolf"
	local tanner = Instance.new("IntValue")
	tanner.Parent = player
	tanner.Name = "Tanner"
	local mason = Instance.new("IntValue")
	mason.Parent = player
	mason.Name = "Mason"
	local seer = Instance.new("IntValue")
	seer.Parent = player
	seer.Name = "Seer"
	local hunter = Instance.new("IntValue")
	hunter.Parent = player
	hunter.Name =  "Hunter"
	local minion = Instance.new("IntValue")
	minion.Parent = player
	minion.Name = "Minion"
	local drunk = Instance.new("IntValue")
	drunk.Parent = player
	drunk.Name = "Drunk"		
	end)

game.ReplicatedStorage.GameStart.OnServerEvent:Connect(function()
	wait(2)
	game.ReplicatedStorage.RoleNarraration:FireAllClients()
	game.ReplicatedStorage.Role.OnServerEvent:Connect(function()
		wait(1)
		for i,player in pairs(game.Players:GetPlayers()) do
			
			for i,intvalue in pairs(player:GetChildren()) do
				if intvalue.ClassName == "IntValue" then
					if intvalue.Value > 0 then
	
			for i,v in pairs(player:GetChildren()) do
				
				if v.ClassName == "IntValue" then
					
								if v.Value < 1 then
									local int = intvalue.Name
									game.ReplicatedStorage.Role:FireAllClients(intvalue.Name)
									player:WaitForChild("PlayerGui").MainGui.Narraration.Text = "Your role is "..int.." (Click the information button for more info.)"
									game.ReplicatedStorage.BillboardGuie:FireAllClients(" "..int.." (Only you can see this)")
						
						v:Destroy()
							end
						end
						end
				end
			end
			end
		end
		end)
	end)
--Local script
if not game:IsLoaded() then
	game.Loaded:Wait()
screenGui.Frame.Visible = true
else
	ReplicatedFirst:RemoveDefaultLoadingScreen()
	screenGui:Destroy()
	game.ReplicatedStorage.GameStart:FireServer()
end

Having lots of deeply-nested loops with non-descriptive variable names and no comments is a pretty sure-fire way to make hard to fix bugs. It also makes it really hard to help you when you get stuck because your code is hard to understand.

Here’s how I would do it:

-- CardModule
CardModule.RoleLimits = {
	Werewolf = 2;
    Villager = 1;
    Hunter = 1;
    Tanner = 1;
    Seer = 1;
    Minion = 1;
    Drunk = 1;
    Mason = 2;
}

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

function CardModule.CreateRoleDeck()
    local deck = {}
    for role, numCopies in pairs(CardModule.RoleLimits) do
        for _ = 1, numCopies do
            table.insert(deck, role)
        end
    end
    
    shuffle(deck)

    return deck
end

function CardModule.TakeCardFromDeck(deck)
    local k = #deck
    local card = deck[k]
    deck[k] = nil
    return k
end
-- GameController, a server script
function startGame()
    local players = game.Players:GetPlayers()
    local roleDeck = CardModule.CreateRoleDeck()
    assert(#players <= #roleDeck, "Not enough roles for this many players!")

    local playerRoles = {}
    for _, player in ipairs(players) do
        playerRoles[player] = CardModule.TakeCardFromDeck(roleDeck)
        game.ReplicatedStorage.RoleAssigned:FireClient(player, playerRoles[player])
        --Seems like you're trying to modify the player's GUI from the server. That is not a thing that can be done, you'll have to listen to the RoleAssigned RemoteEvent in a LocalScript and have that change the GUI.
    end
end

Not sure what you want all those IntValues for, but if they’re important they could totally be incorporated into this solution somehow.

1 Like

Could you please explain your solution? Sorry if I am asking for a bit much here.

2 Likes

Sure! What it does is to create a new “deck of roles” every round. In the deck there’s a number of cards for each role determined by RoleLimits. So 2 werewolves, 1 villager, 2 masons, etc. The deck gets shuffled, and to assign roles it “draws cards from the deck” one at a time. This works like with a normal deck, where the top card is taken and gets removed from the deck when it’s drawn. One card is drawn per player because each player needs one role. A dictionary where keys are players and values are roles is created, and each client is notified of what role they’ve been assigned.

Also, why did you put playerRoles[player] in this line:

game.ReplicatedStorage.RoleAssigned:FireClient(player, playerRoles[player])

playerRoles[player] Prints as the number of roles left.

How would I find the role taken? I know it probably has something to with TakeCardFromDeck() but how?

1 Like

On the line before, I assigned CardModule.TakeCardFromDeck(roleDeck) to playerRoles[player].

So game.ReplicatedStorage.RoleAssigned:FireClient(player, playerRoles[player]) sends whatever is stored in playerRoles[player] to the player.

The thing that is stored there is the return value from CardModule.TakeCardFromDeck(roleDeck).

Which is a card taken from the top of the shuffled deck of roles.

Here’s a version that breaks it down a bit more:

-- GameController, a server script
function startGame()
    local players = game.Players:GetPlayers()
    local roleDeck = CardModule.CreateRoleDeck()
    assert(#players <= #roleDeck, "Not enough roles for this many players!")

    local playerRoles = {}
    for _, player in ipairs(players) do
        local playerRole = CardModule.TakeCardFromDeck(roleDeck) --pick a role for this player by taking  the top card from the deck
        playerRoles[player] = playerRole --store it for later, so we can figure out what role a player has
        game.ReplicatedStorage.RoleAssigned:FireClient(player, playerRole)
        --Seems like you're trying to modify the player's GUI from the server. That is not a thing that can be done, you'll have to listen to the RoleAssigned RemoteEvent in a LocalScript and have that change the GUI.
    end
end

The playerRole is equal to the amount of roles left. How will that help me find the role that is taken?

This is what it prints when you put print(playerRole)
Screen Shot 2022-01-06 at 3.59.58 PM

Are you still here? I am still having the problem. @ThanksRoBama

Don’t do TakeCardFromDeck(CardModule.RoleLimits). Do TakeCardFromDeck(roleDeck). The role deck is just an array of roles (strings), while RoleLimits is a role to role limit dictionary (string to int dictionary)

I did do TakeCardFromDeck(roleDeck). Did you test the script?

Oh yeah, try this:

function CardModule.TakeCardFromDeck(deck)
	local k = #deck
	local card = deck[k]
	deck[k] = nil
	return card
end

Wait a minute. Every time a player joins, a player who had a role gets another one.

Local script:

game.ReplicatedStorage.RoleAssigned.OnClientEvent:Connect(function(player, playerRole)
	script.Parent.Text = "Your role is "..player.." (Click Information for info on your role.)"
	script.Parent.Parent.Exit.Visible = true
	game.ReplicatedStorage.BillboardGui.TextLabel.Text = player.." (Only you can see this)"
	game.ReplicatedStorage.BillboardGui.Parent = game.Players.LocalPlayer.Character:WaitForChild("Head")

end)

@ThanksRoBama

Is there any way to fix this?

1 Like

Are you calling startGame every time a player joins? That’d explain it, it’s only supposed to be called once per game, to start the game. If you want to support players joining mid game, you’ll have to have the roleDeck variable outside of startGame, so that other parts of your program can access it. That way you can call a separate function when a player joins, and take another card from the deck if there are any left.

1 Like

How would I use roleDeck when a player joins mid-game?

Also when I set it to not doing the function startGame() every time a player joins it still gives players multiple roles.

@ThanksRoBama

How would I view the roles that have not been picked? I am trying to make it so that there are always 3 cards that have not been picked.

You’d have to return cards to the deck when a player leaves, and give a player a card from the deck when they join mid game. So you’d have to connect some functions up to PlayerAdded and PlayerRemoving. Since these functions have to access the role deck, you should probably make the roleDeck a global variable in the script

1 Like

This is not what I am asking, however, if this is the correct answer to my question, please explain a little further.

@ThanksRoBama

Btw, what I actually meant was that I don’t care about the players that leave. I want to make it so that every round, there are 3 random cards that no player has obtained. I want to view these 3 random cards that nobody has obtained.

@ThanksRoBama

Are you still here? I need help.