Match Module Not Starting

I created a match module for my game that manages loops for the game, with an intermission, starting countdown, and the ongoing timer. Everything works, but when the module tries to set the state to Ongoing, it doesn’t start.

I’ve tried rewriting this module multiple times and I’ve gotten no luck. I have no idea why this is happening.

https://gyazo.com/3e4e11a3ff1f4231cef5cb2c82077a83

The props spawning should indicate that the game has started

function module.StartMatch()
	local spawnPoints = spawnsFolder:GetChildren()

	for _, player in ipairs(Players:GetPlayers()) do
		local char = player.Character or player.CharacterAdded:Wait()
		if char and char:FindFirstChild("HumanoidRootPart") then
			local spawn = spawnPoints[math.random(1, #spawnPoints)]
			char:PivotTo(spawn.CFrame)
			inGamePlayers[player] = true

			local humanoid = char:FindFirstChildOfClass("Humanoid")
			if humanoid then
				humanoid.Died:Connect(function()
					inGamePlayers[player] = nil
				end)
			end
		end

		ensureHUD(player)
	end

	for p, _ in pairs(inGamePlayers) do
		print("→", p.Name)
	end

	timer = COUNTDOWN_TIME
	while timer > 0 do
		updateUIForAll("STARTING", timer)
		task.wait(1)
		timer -= 1
	end

	gameActive = true
	timer = MATCH_TIME
	PropSpawnHandler.StartSpawning()
	
	print("Match started with", #Players:GetPlayers(), "players")

	while timer > 0 do
		updateUIForAll("ONGOING", timer)

		if not next(inGamePlayers) then
			break
		end

		task.wait(1)
		timer -= 1
	end

	module.EndMatch()
end
local MatchHandler = require(script.Parent.Handlers.MatchHandler)
MatchHandler.startIntermission()

only thing i can think off on the spot is that PropSpawnHandler.StartSpawning() might be yielding code, does it print Match started with x players or no?

No, everything else prints though, like the "→", p.Name.

Can you show us the code of the PropSpawnHandler module? That’s where the code breaks, that’s likely where the issue is.

if it doesnt print match started than its getting stuck somewhere between the → name and match started print, i’d guess its either in PropSpawnHandler.StartSpawning() or updateUIForAll, do you have any loops or anything else that could yield code in either of those?

local Players = game:GetService("Players")
local ServerStorage = game:GetService("ServerStorage")
local Ragdoll = require(game.ServerScriptService.Providers.RagdollProvider)
local KillFeedHandler = require(game.ServerScriptService.Providers.FeedProvider)
local LimbHealth = require(game.ServerScriptService.Providers.LimbProvider)
local ConcussionEffect = require(game.ReplicatedStorage.Modules.ConcussionEffect)

local propsFolder = ServerStorage.Assets:WaitForChild("Props")
local soundsFolder = ServerStorage.Assets:WaitForChild("Sounds"):WaitForChild("Props")
local spawnArea = workspace.Map:WaitForChild("PropSpawnArea")
local despawnArea = workspace.Map:WaitForChild("PropDespawnArea")

local module = {}
local activeProps = {}
local damagedByProp = {}
local spawning = false

local function getCharacterFromPart(part)
	while part and part.Parent do
		local humanoid = part.Parent:FindFirstChildOfClass("Humanoid")
		if humanoid then return part.Parent end
		part = part.Parent
	end
	return nil
end

local function breakAllWelds(model)
	for _, desc in ipairs(model:GetDescendants()) do
		if desc:IsA("Weld") or desc:IsA("WeldConstraint") or desc:IsA("ManualWeld") then
			desc:Destroy()
		end
	end
end

local function playRandomSoundForProp(propClone)
	local soundFolder = soundsFolder:FindFirstChild(propClone.Name)
	if not soundFolder then return end
	local list = soundFolder:GetChildren()
	if #list == 0 then return end

	local sound = list[math.random(1, #list)]:Clone()

	local parent
	if propClone:IsA("Model") then
		parent = propClone.PrimaryPart or propClone:FindFirstChildWhichIsA("BasePart")
	elseif propClone:IsA("BasePart") then
		parent = propClone
	end

	if not parent then return end

	sound.Parent = parent
	sound:Play()
	sound.Ended:Connect(function() sound:Destroy() end)
end

local function handleTouch(hit, propClone)
	local character = getCharacterFromPart(hit)
	if not character then return end

	local humanoid = character:FindFirstChildOfClass("Humanoid")
	if not humanoid or humanoid.Health <= 0 then return end

	local config = propClone:FindFirstChild("Configuration")
	if not config then return end

	local maxDamage = config:GetAttribute("MaxDamage")
	local ragdollTime = config:GetAttribute("RagdollTime")
	local explodes = config:GetAttribute("ExplodesOnImpact")

	if damagedByProp[propClone][character] then return end

	local rootPart
	if propClone:IsA("Model") then
		rootPart = propClone.PrimaryPart or propClone:FindFirstChildWhichIsA("BasePart")
	elseif propClone:IsA("BasePart") then
		rootPart = propClone
	end

	local velocity = rootPart and rootPart.AssemblyLinearVelocity.Magnitude or 0
	if velocity < maxDamage then return end

	local player = Players:GetPlayerFromCharacter(character)
	if not player then return end

	damagedByProp[propClone][character] = true
	playRandomSoundForProp(propClone)

	if explodes then breakAllWelds(propClone) end

	local con
	con = humanoid.Died:Connect(function()
		if con then con:Disconnect() end
		KillFeedHandler.SendKillFeed(player, propClone.Name)
	end)

	local damage = maxDamage
	local ragdollDuration = ragdollTime
	local hitName = hit.Name

	if hitName == "Head" then
		damage *= 2
		ragdollDuration *= 1.3
		ConcussionEffect.Trigger(player)
	end

	humanoid:TakeDamage(damage)

	local limbMultiplier = (hitName == "Head") and 1 or 0.5
	LimbHealth.DamageLimb(player, hitName, damage * limbMultiplier)

	local motors = Ragdoll.CreateJoints(character)
	Ragdoll.Ragdoll(character)
	Ragdoll.SetMotorsEnabled(motors, false)

	task.delay(ragdollDuration, function()
		if humanoid.Health > 0 then
			Ragdoll.SetMotorsEnabled(motors, true)
			Ragdoll.UnRagdoll(character)
			Ragdoll.DestroyJoints(character)
		end
	end)
end

function module.SpawnSingleProp()
	local props = propsFolder:GetChildren()
	if #props == 0 then return end

	local template = props[math.random(1, #props)]
	local propClone = template:Clone()
	propClone.Parent = workspace
	table.insert(activeProps, propClone)
	damagedByProp[propClone] = {}

	local size = spawnArea.Size
	local pos = spawnArea.Position
	local randomX = math.random(-size.X / 2, size.X / 2)
	local randomZ = math.random(-size.Z / 2, size.Z / 2)
	local spawnPos = pos + Vector3.new(randomX, size.Y / 2, randomZ)

	if propClone:IsA("Model") then
		if not propClone.PrimaryPart then
			for _, part in ipairs(propClone:GetDescendants()) do
				if part:IsA("BasePart") then
					pcall(function()
						propClone.PrimaryPart = part
					end)
					break
				end
			end
		end

		if propClone.PrimaryPart then
			propClone:SetPrimaryPartCFrame(CFrame.new(spawnPos))
		else
			warn("No PrimaryPart could be set on model:", propClone.Name)
		end

	elseif propClone:IsA("BasePart") then
		propClone.CFrame = CFrame.new(spawnPos)
	else
		warn("Unsupported object type in props:", propClone.ClassName, propClone.Name)
	end

	if propClone:IsA("Model") then
		for _, part in ipairs(propClone:GetDescendants()) do
			if part:IsA("BasePart") then
				part.Touched:Connect(function(hit)
					handleTouch(hit, propClone)
				end)
			end
		end
	elseif propClone:IsA("BasePart") then
		propClone.Touched:Connect(function(hit)
			handleTouch(hit, propClone)
		end)
	end
end

function module.ClearProps()
	for _, prop in ipairs(activeProps) do
		if prop and prop.Parent then prop:Destroy() end
	end
	table.clear(activeProps)
	table.clear(damagedByProp)
end

function module.StartSpawning(interval)
	if spawning then return end
	spawning = true

	while spawning do
		module.SpawnSingleProp()
		task.wait(interval or 2)
	end
end

function module.StopSpawning()
	spawning = false
end

despawnArea.Touched:Connect(function(hit)
	local model = hit:FindFirstAncestorWhichIsA("Model")
	if model and table.find(activeProps, model) then
		model:Destroy()
		table.remove(activeProps, table.find(activeProps, model))
	end
end)

return module

StartSpawning doesn’t seem to automatically turn itself off when done. The variable spawning isn’t set back to false, so the loop will keep running and cause an infinite yield?

Because the MatchModule calls the StopSpawning function when the game is over, so there isn’t any need to turn it off automatically within the prop module.

That should explain your problem, the function yields indefinitely because it enters a while loop that doesn’t end and halts the thread (which is the same thread that is doing the intermission logic)

You should isolate the loop using task.spawn

1 Like

Thank you so much! This problem has been horrible for me.

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