Uncontrolled recursion, Remote Event inside Remote Event for simple round handler

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.

1 Like

Even when commenting that part out, playing one round and starting a new one, the game will completely freak out and roblox studio wont respond.

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.

1 Like

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.

1 Like

Just tried it, doesn’t fix the freeze when using the remote event to stop the game early, also freezes when playing the round to the end.

Edit:
The stopgame function is never called
Also I figured out HasGameEnded function is called like 20 times when the player forces to end the game.

Edit:
Fixed it, I will give the code and the cause of the error in the next message

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)
2 Likes

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