Module script that calls a module script is running async on client?

Im calling a module script named Match on server script:


local COUNTDOWN_TIME = 30 -- Countdown time in seconds
local MINIMUM_PLAYER_COUNT_PERCENTAGE = 0 -- The minimum players percentage for starting the match

local Players = game:GetService("Players")
local Settings = require(game.ReplicatedStorage.LobbySettings)
local KeyConstants = require(game.ReplicatedStorage.KeyConstants)
local PlayerCount = 9 -- The current player count in the server
local Countdown = false -- If countdown is on

local ReplicatedStorage = game:GetService("ReplicatedStorage")
local CountdownEvent = ReplicatedStorage:WaitForChild("CountdownEvent") -- args: {time,visible}
local WaitForPlayersEvent = ReplicatedStorage:WaitForChild("WaitForPlayersEvent") -- args: {playerCount, visible}
local AppliedRoleEvent = ReplicatedStorage:WaitForChild("AppliedRoleEvent")

local Match = require(game.ReplicatedStorage.Match)

Players.PlayerAdded:Connect(function(player)
	player.CharacterAppearanceLoaded:Connect(function(character)
	PlayerCount += 1
	OnPlayerCountChange()
		
	end)
end)

Players.PlayerRemoving:Connect(function(player)
	PlayerCount -= 1
	OnPlayerCountChange()
end)


function OnPlayerCountChange()
	if PlayerCount/Settings.MaxPlayers > MINIMUM_PLAYER_COUNT_PERCENTAGE then
		-- start countdown if not already counting
		if not Countdown then
			Countdown = true
			WaitForPlayersEvent:FireAllClients(PlayerCount,false)
			InitiateCountdown()
		end
	else
		Countdown = false
		CountdownEvent:FireAllClients(time,false)
		WaitForPlayersEvent:FireAllClients(PlayerCount,true)
	end
	
end


function InitiateCountdown()
	
	local time = COUNTDOWN_TIME
	while(Countdown) do
		time -= 1
		if PlayerCount >= Settings.MaxPlayers and time > 10 then
			time = 10
		end
		CountdownEvent:FireAllClients(time,true)
		print("Countdown: ",time)
		print("players count: ",PlayerCount)
		if(time == 0) then
			Countdown = false
			CountdownEvent:FireAllClients(time,false)
			StartGame()
		end
		task.wait(1)
	end
	
	-- countdown is false, meaning stopped or canceled
	
end


function StartGame()
	local match = Match.new()
	local roles = Match:GenerateRoles()
	for _,innocent in ipairs(roles.Innocent) do
		AppliedRoleEvent:FireClient(innocent.Player,KeyConstants.InnocentKey)
	end
	
	for _,murderer in ipairs(roles.Murderer) do
		AppliedRoleEvent:FireClient(murderer.Player,KeyConstants.MurdererKey)
	end
	
	for _,sheriff in ipairs(roles.Sheriff) do
		AppliedRoleEvent:FireClient(sheriff.Player,KeyConstants.ShriffKey)
	end
end

Match module script:

local Match = {}
Match.__index = Match

local Players = game:GetService("Players")
local PlayerProperties = require(game.ReplicatedStorage.PlayerProperties)


function Match.new()
	local match = {}
	setmetatable(match,Match)
	match.AllPlayers = PlayerProperties:GetAllPlayersAsProperties()
        task.wait()
	print("players:",match.AllPlayers)
	return match
end


function Match:GenerateRoles()
	local roles ={
		Innocent = {},
		Murderer = {},
		Sheriff = {}
	}
	
	local MurdererCount = 1
	local SheriffCount = 1
	-- TODO multiple
	local totalWeight = self:CalculateTotalWeight() 
	
	local random = math.random(1,100) -- a number between 1 to 100
	local sum = 0
	-- currently sheriff and murderer is same weight
	local i = 1
	while sum/totalWeight < random / 100 and i ~= #self.AllPlayers do
		sum+= self.AllPlayers[i].Weight
		i += 1
	end
	
	i -= 1
	roles.Murderer = {self.AllPlayers[i]}
	
	-- for sheriff
	local withoutMurder = table.clone(self.AllPlayers)
	withoutMurder = withoutMurder.remove(withoutMurder,i)
	for _, p in ipairs(roles.Murderer) do
		totalWeight -= p.Weight
	end
	
	i = 1
	random = math.random(1,100)
	sum = 0
	while sum/totalWeight < random / 100 and i ~= #withoutMurder do
		sum+= withoutMurder[i].Weight
		i += 1
	end
	
	i-= 1
	
	roles.Sheriff = {withoutMurder[i]}
	
	roles.Innocent = table.remove(withoutMurder,i)
	return roles
end

function Match:CalculateTotalWeight()
	local total = 0
	for _, properties in ipairs(self.AllPlayers) do
		total += properties.Weight
	end
	
	return total
end

return Match

PlayerProperties module script:

local PlayerProperties = {}

local Players = game:GetService("Players")


function PlayerProperties:GetPlayerWeight(player : Player)
	
	return 1
end

function PlayerProperties:SetWeightToPlayer(player : Player,weight)
	
end

function PlayerProperties:GetAllPlayersAsProperties()
	local players = Players:GetPlayers()
	local allPlayers = {}

	for _, p in ipairs(players) do
		
		local properties = {
			Player = p,
			Weight = self:GetPlayerWeight(p)
		}

		table.insert(allPlayers,properties)
	end
	return allPlayers
end


return PlayerProperties

The server console prints “in calc” and errors on the ipairs of the CalculateTotalWeight, saying self.AllPlayers is nil. But does not print the players on the new() function.
in other hand, the client console prints the players from the new() function but does not error…
and from some reason I had to place task.wait() after the initializeation of the AllPlayers so it wouldnt be nil.
I really have no clue whats going on, it seems that calling a module script from a module script is async, and that it calls it from a client side.
May anyone explain to me why this happens?

I made progress with the issue, I had a typo on the server script
Instead of:

local roles = Match:GenerateRoles()

was supposed to be:

local roles = match:GenerateRoles()

(oops)
and i fixed some errors of nil checks (apperantly a table with 0 items is nil)

but things still dont work, i need to put many task.wait() just for values to update and even then it seems to not work.
this is the updated version of Match:

local Match = {}
Match.__index = Match

local Players = game:GetService("Players")
local PlayerProperties = require(game.ReplicatedStorage.PlayerProperties)


function Match.new()
	local match = {}
	setmetatable(match,Match)
	match.AllPlayers = PlayerProperties:GetAllPlayersAsProperties()
	task.wait()
	return match
end


function Match:GenerateRoles()
	local roles ={
		Innocent = {},
		Murderer = {},
		Sheriff = {}
	}
	
	local MurdererCount = 1
	local SheriffCount = 1
	-- TODO multiple
	local totalWeight = self:CalculateTotalWeight() 
	
	local random = math.random(1,100) -- a number between 1 to 100
	local sum = 0
	-- currently sheriff and murderer is same weight
	local i = 1
	while sum/totalWeight < random / 100 and i ~= #self.AllPlayers do
		sum+= self.AllPlayers[i].Weight
		i += 1
	end
	
	i -= 1
	roles.Murderer = {self.AllPlayers[i]}
	
	-- for sheriff
	print("all players:")
	print(self.AllPlayers)
	
	local withoutMurder = table.clone(self.AllPlayers)
	task.wait()
	print("without murder: b4 ")
	print(withoutMurder)
	withoutMurder = table.remove(withoutMurder,i)
	task.wait()	
	print("without murder: after ",withoutMurder)

	for _, p in ipairs(roles.Murderer) do
		totalWeight -= p.Weight
	end
	
	if(withoutMurder ~= nil) then
		
		i = 1
		random = math.random(1,100)
		sum = 0
		while sum/totalWeight < random / 100 and i ~= #withoutMurder do
			sum+= withoutMurder[i].Weight
			i += 1
		end
	
		i-= 1
		

		roles.Sheriff = {withoutMurder[i]}
		
		roles.Innocent = table.remove(withoutMurder,i)
	end
	print("murderers:")
	print(roles.Murderer)
	return roles
end

function Match:CalculateTotalWeight()
	local total = 0
	print("in calc",#self.AllPlayers)
	print()
	for _, properties in ipairs(self.AllPlayers) do
		total += properties.Weight
	end
	
	return total
end

return Match

The updated version of the server script:


local COUNTDOWN_TIME = 30 -- Countdown time in seconds
local MINIMUM_PLAYER_COUNT_PERCENTAGE = 0 -- The minimum players percentage for starting the match

local Players = game:GetService("Players")
local Settings = require(game.ReplicatedStorage.LobbySettings)
local KeyConstants = require(game.ReplicatedStorage.KeyConstants)
local PlayerCount = 9 -- The current player count in the server
local Countdown = false -- If countdown is on

local ReplicatedStorage = game:GetService("ReplicatedStorage")
local CountdownEvent = ReplicatedStorage:WaitForChild("CountdownEvent") -- args: {time,visible}
local WaitForPlayersEvent = ReplicatedStorage:WaitForChild("WaitForPlayersEvent") -- args: {playerCount, visible}
local AppliedRoleEvent = ReplicatedStorage:WaitForChild("AppliedRoleEvent")

local Match = require(game.ReplicatedStorage.Match)

Players.PlayerAdded:Connect(function(player)
	player.CharacterAppearanceLoaded:Connect(function(character)
	PlayerCount += 1
	OnPlayerCountChange()
		
	end)
end)

Players.PlayerRemoving:Connect(function(player)
	PlayerCount -= 1
	OnPlayerCountChange()
end)


function OnPlayerCountChange()
	if PlayerCount/Settings.MaxPlayers > MINIMUM_PLAYER_COUNT_PERCENTAGE then
		-- start countdown if not already counting
		if not Countdown then
			Countdown = true
			WaitForPlayersEvent:FireAllClients(PlayerCount,false)
			InitiateCountdown()
		end
	else
		Countdown = false
		CountdownEvent:FireAllClients(time,false)
		WaitForPlayersEvent:FireAllClients(PlayerCount,true)
	end
	
end


function InitiateCountdown()
	
	local time = COUNTDOWN_TIME
	while(Countdown) do
		time -= 1
		if PlayerCount >= Settings.MaxPlayers and time > 10 then
			time = 10
		end
		CountdownEvent:FireAllClients(time,true)

		if(time == 0) then
			Countdown = false
			CountdownEvent:FireAllClients(time,false)
			StartGame()
		end
		task.wait(1)
	end
	
	-- countdown is false, meaning stopped or canceled
	
end


function StartGame()
	local match = Match.new()
	local roles = match:GenerateRoles()
	task.wait(1)

	for _,innocent in ipairs(roles.Innocent) do
		task.wait()

		print("innocent: ",innocent)
		AppliedRoleEvent:FireClient(innocent.Player,KeyConstants.InnocentKey)
	end
	
	for _,murderer in ipairs(roles.Murderer) do
		task.wait()

		print("murderer: ",murderer)

		AppliedRoleEvent:FireClient(murderer.Player,KeyConstants.MurdererKey)
	end
	
	for _,sheriff in ipairs(roles.Sheriff) do
		task.wait()
		print("sherriff: ",sheriff)
		AppliedRoleEvent:FireClient(sheriff.Player,KeyConstants.ShriffKey)
	end
end

this is what it prints on console:

Client:


image

Server:
image

Why is this acting so weird???

Haven’t made any progress, someone can help me here?

I noticed there is no metatable set for PlayerProperties, so I think it is better to index functions like:

PlayerProperties.GetPlayerWeight = function(player:Player)
    --Rest of code
end

and then call with . instead of :

Phew this is a lot to take in with the info given and amount of scripts, I’m not sure exactly what problem you’re having except for not calling the object, but you fixed that. This also wouldn’t be running on the client unless you’re calling it on the client either, which I can’t see anything about. Nothing indicates to me that it should be running asynchronously, so put some breakpoints and some watches and check if it actually really does that.

I’d also recommend you steer away from the OOP approach. It makeslittle sense for this situation and you’re not using it for anything (When you fire roles they don’t even have a keyconstant or the other info attached to them so that you don’t need to check and conditionally choose what to send). Usually you want to use OOP when you could benefit from a lot of inheritance, similar objects, polymorphism, etc.

your right, there is no reason of automatically passing self to PlayerProperties’s functions.
Altho it does not directly solves the problem, thanks for the note!

Ye sorry for the huge amount of code… I just didn’t know what could be relevant and whats not, I didnt want people to shout at me saying I haven’t showed all the code.
About OOP, im not exactly understanding. Im new to lua and to roblox API, but I have knowledge in several coding languages like c#, java, JS (React-Typescript). and they all use OOP. Web uses less but it still does in a way that made sense.
I know that lua has a special way of doing it, but the main use of OOP is simply as the name says - to create objects. The objects allow to reuse structure and code, I thought this is the case since a Match, is a perfect type of object example.

One thing that I have thought about doing is to pass a list of players to match instead of using game again to retreive them within the module script, dont know if it would help or not but ill try.
Thank you for your response!

You are testing this with one player, so the index for roles.Murderer = self.AllPlayers here is [0], but Lua indexing starts at [1] (i.e. nothing there).
You can index tables with [0] but this normally needs to be done explicitly.

local withoutMurder = table.clone(self.AllPlayers)

This would also create a shallow copy of the table, returning only variables and not the content of child tables.

(I puzzled over this for a while, as it was quite complex, and everything looks like it should work, and probably does, but basically you are printing nil, because you are indexing nil values.)

So I managed to find the solution using the Output window, which prints everything correctly and easily to look for the errors with.
I dont know why the chat console does so much mess, but at least I know now how to look for errors next time.
The problem was simple, in this line it wont get in the first loop if theres one player:

while sum/totalWeight < random / 100 and i ~= #self.AllPlayers do

Since i is 1 and the length is 1 (im new to this index system, my bad)
So instead, did this:

while sum/totalWeight < random / 100 and i <= #self.AllPlayers do

Afterwards I had some minor fixes to do and then everything worked just fine, without any waits or prints.
As a conclusion, the solution was simply to DO NOT USE THE CHAT CONSOLE (that you open by writing /console command). It is missleading and does not print everything sync (I beleive that it doesnt, since thats the only reason I could think of that made me use waits).

Anyway, thank you guys for helping!

Thank you for the answer! it was the solution.
Weird tho I didnt see this reply when I was writing my last reply…

Is there a built in method to use for deep clone or I have to make one my own (or search the internet if that so)

I think we were both writing at the same time. It happens sometimes!

There is an example of deep cloning in the official docs
Tables | Documentation - Roblox Creator Hub

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