I am making a game where you move troops to kill enemy troops to win the game. I need to have a simple prototype now and made a game that just works. However, when I play the game and start the round and then end the round it freezes everything. There is probably a uncontrolled recursion because I can’t figure out how to disconnect events properly. This is my code:
local gameStarted = false
RemoteEvents:WaitForChild("TroopSystem"):WaitForChild("startGame").OnServerEvent:Connect(function(player, msg)
if gameStarted or msg == "Stop" then return end
local eventConnection2 = RemoteEvents:WaitForChild("TroopSystem"):WaitForChild("startGame").OnServerEvent:Connect(function(player, msg)
if msg == "Stop" and gameStarted then
print("StopGame")
gameStarted = false
warn("game ended")
wait(5)
for index, troop in game.Workspace.Troops:GetChildren() do
troop:Destroy()
end
player.Character:Destroy()
player:LoadCharacter()
end
end)
gameStarted = true
-- start game
--RemoteEvents.TroopSystem.changeCameraPerspective:FireClient(player, Vector3.new())
for index, troop in game.Workspace.Troops:GetChildren() do
troop:Destroy()
end
local char = player.Character
char.HumanoidRootPart.CFrame = workspace.TPPart.CFrame
local i = 0
repeat
spawnTroop("Troop", player, workspace.TroopSpawner.CFrame.Position + Vector3.new(0,0,i*2))
i += 1
until i == 10
i = 0
repeat
i += 1
spawnTroop("EnemyTroop", player, workspace["EnemySpawner"..tostring(i)].CFrame.Position)
until i == 10
-- spawn enemies
local function checkIfGameEnded()
if #game.Workspace.Troops:GetChildren() >= 1 then
for index, troop in game.Workspace.Troops:GetChildren() do
if troop.Name == "EnemyTroop" then
for index, troop in game.Workspace.Troops:GetChildren() do
if troop.Name == "Troop" then
return
end
end
end
end
end
-- end game
gameStarted = false
warn("game ended")
wait(5)
for index, troop in game.Workspace.Troops:GetChildren() do
troop:Destroy()
end
player.Character:Destroy()
player:LoadCharacter()
end
checkIfGameEnded()
local eventConnection = game.Workspace.Troops.ChildRemoved:Connect(function()
checkIfGameEnded()
end)
if not gameStarted then
eventConnection:Disconnect()
eventConnection2:Disconnect()
print("disconnected Event")
end
end)
Why are you connecting to the same event twice?
Firing the event with identical parameters will constantly cause new connections to be added. gameStarted will keep being reset to false.
I would suggest putting your game logic in a separate function, then run some sanity checks before initiating the round. As it presently stands, an exploiter could abuse the remotes to start and end the round(s). It would also make it easier to maintain and handle.
I know, I just wanted to make a temporary solution for this and work another time on it as I need to have the simple game ready now (as I described above). But maybe I should make a sort of permanently solution.
Kind of a script feed but not really. Mostly just cleaning up your existing script and restructuring it a bit. This obviously isn’t a perfect script by no means you still may need to tweak some stuff but hopefully it’ll point you in the right direction.
One module you will need to get is Maid and it’s honestly such a great module for cleaning up events you wont need long term. Maid - AeroGameFramework
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local RemoteEvents = Path.To.YourEvents
local TroopSystem = RemoteEvents.TroopSystem.startGame --// Server does not need to wait for children to load.
local Troops = workspace:FindFirstChild("Troops")
local Maid = Path.To.Maid
local GameEndConnections = Maid.new()
local HasGameStarted = false
function StopGame()
HasGameStarted = false
GameEndConnections:DoCleaning()
end
function HasGameEnded(Player)
--// Try to avoid deep nesting
-- Assuming you only need to clear EnemyTroop?
for _, Troop in Troops:GetChildren() do
if Troop.Name == "EnemyTroop" then return end -- If their is an enemy troop game probably hasn't ended.. right? unsure, its your game so you tell me.. anyways if thats right we can just exit the loop right now and return the function. why wait?
end
-- assuming we got to this point we "should" only have friendly troops left. lets just go ahead and end the game here.
HasGameStarted = false
task.delay(5, function()
for _, Troop in Troops:GetChildren() do
Troop:Destroy()
end
Player.Character:Destroy()
Player:LoadCharacter()
end)
return true
end
TroopSystem.OnServerEvent:Connect(function(Player: Player, Msg: string)
--// Manage round ending (Be careful with this cause depending on how your game is setup an exploiter can just fire the "Stop" message and stop the current game.)
if HasGameStarted and Msg == "Stop" then
print("Stop Game")
HasGameStarted = false
task.delay(5, function()
for _, Troop in Troop:GetChildren() do
Troop:Destroy()
end
Player.Character:Destroy()
Player:LoadCharacter()
end)
return
end
HasGameStarted = true
for _, Troop in Troop:GetChildren() do
Troop:Destroy()
end
local Character = Player.Character
Character.HumanoidRootPart.CFrame = workspace.TPPart.CFrame
for i = 0, 10 do -- Friendly?
local Position = workspace.TroopSpawner.Position + Vector3.new(0, 0, i*2)
spawnTroop("Troop", Player, Position)
end
for i = 0, 10 do -- Enemy?
local Position = workspace["EnemySpawner"..tostring(i)].Position + Vector3.new(0, 0, i*2)
spawnTroop("EnemyTroop", Player, Position)
end
GameEndConnections:GiveTask(Troops.ChildRemoved:Connect(function()
HasGameEnded(Player)
end))
end)
ps; in your script I’m like 90% sure it kept freezing cause you didn’t have any type of wait inside of your repeats.
It didn’t work because when it stops the game the troops are first removed and then the connections were cleaned. So the onChildRemoved Connections was fired when the Map was cleaned. This resulted in the game thinking the player killed the troops so the game ended the round multiple times when it should end the round only one time. I hope this was understandable!
Final Fix:
local Troops = workspace:WaitForChild("Troops")
local Maid = require(script:WaitForChild("Maid"))
local GameEndConnections = Maid.new()
local HasGameStarted = false
function StopGame(Player)
task.delay(5, function()
GameEndConnections:DoCleaning()
for _, Troop in Troops:GetChildren() do
Troop:Destroy()
end
Player.Character:Destroy()
Player:LoadCharacter()
HasGameStarted = false
end)
end
function HasGameEnded(Player)
-- check if one side has no troops
for _, Troop in Troops:GetChildren() do
if Troop.Name == "EnemyTroop" then
-- There are Enemy Troops, check if there are Friendly Troops, if not stop game
for _, Troop in Troops:GetChildren() do
if Troop.Name == "Troop" then
-- There are Enemy Troops and Friendly Troops. Game should continue
return
end
end
end
end
-- End Game
print("End Game because one side has no troops left")
StopGame(Player)
return true
end
RemoteEvents:WaitForChild("TroopSystem"):WaitForChild("startGame").OnServerEvent:Connect(function(Player: Player, Msg: string)
if HasGameStarted and Msg == "Stop" then
print("End Game because player forces to stop the game")
HasGameStarted = false
StopGame(Player)
return
end
if HasGameStarted then return end -- game is already running
HasGameStarted = true
for _, Troop in Troops:GetChildren() do -- clear map
Troop:Destroy()
end
-- teleport player
local Character = Player.Character
Character.HumanoidRootPart.CFrame = workspace.TPPart.CFrame
for i = 0, 10 do -- Spawn Friendly Troops
local Position = workspace.TroopSpawner.Position + Vector3.new(0, 0, i*2)
spawnTroop("Troop", Player, Position)
end
for i = 1, 10 do -- Spawn Enemy Troops
local Position = workspace["EnemySpawner"..tostring(i)].Position + Vector3.new(0, 0, i*2)
spawnTroop("EnemyTroop", Player, Position)
end
GameEndConnections:GiveTask(Troops.ChildRemoved:Connect(function() -- check if game ends when troops are killed
if not HasGameStarted then return end
HasGameEnded(Player)
end))
end)