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.
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?
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)