Server Event Firing Multiple Times Depending On How Many Players There Are

Hello! I Have made like 50 thousand posts about my games bossfight / survival section area cuz there are too many bugs in it.

I just fixed the attacks in the bossfight and it works perfectly fine in singleplayer, but in multiplayer its complete chaos.

[Btw the bossfight happens right after a cutscene]

I Have no idea what is happening, but if anyone can help i will be really happy, cuz i really want to finish the game today cuz its like 90% done.

heres the script that fires the cutscene [Script inside a part]:

script.Parent.Touched:Connect(function(hit)
	if hit.Parent:FindFirstChild("Humanoid") then
		game.ReplicatedStorage.FixedHospitalCutscene:FireAllClients()
		script.Enabled = false
	end
end)

and heres the thing it is triggering in the massive cutscene handler [Local Script in StarterGui]:

game.ReplicatedStorage.FixedHospitalCutscene.OnClientEvent:Connect(function()
	Player.PlayerGui.Main.MobileButtons.SprintButton.Visible = false
	Player.PlayerGui.Main.Stamina.Background.Bar.Sprint.Enabled = false
	Humanoid.WalkSpeed = 0
	HumanoidRootPart.CFrame = workspace:WaitForChild("CutsceneSpawn").CFrame
	Camera.CameraType = Enum.CameraType.Scriptable
	TweenService:Create(Camera, TweenInfo.new(1, Enum.EasingStyle.Linear,Enum.EasingDirection.InOut, 0), {CFrame = workspace.Cam1.CFrame}):Play()
	HandWindow:Stop()
	HandDown:Play()
	Player.PlayerGui.Main.Stamina.Background.Visible = false
	Player.PlayerGui.Main.Dialogue.DialogueFrame.Visible = true
	script.TenebrisVoicelines2.L1C:Play()
	Player.PlayerGui.Main.Dialogue.DialogueFrame.DialogueText.Text = "this place was swarming with many children. Every child loved their friend Goaty, but he especialy loved one child..."
	script.TenebrisVoicelines2.L1C.Ended:Wait()
	script.TenebrisVoicelines2.GWS:Play()
	Player.PlayerGui.Main.Dialogue.DialogueFrame.DialogueText.Text = "Alex was the one he loved. They both really liked to draw and they were best friends. Sadly Alex was sick with cancer and when the doctor told him that Goaty was sad..."
	script.TenebrisVoicelines2.GWS.Ended:Wait()
	script.TenebrisVoicelines2.GLD:Play()
	Player.PlayerGui.Main.Dialogue.DialogueFrame.DialogueText.Text = "other kids noticed Goaty started to look different after the death of Alex. They said that sometimes it felt like they were being watched..."
	script.TenebrisVoicelines2.GLD.Ended:Wait()
	script.TenebrisVoicelines2.TGAAG:Play()
	Player.PlayerGui.Main.Dialogue.DialogueFrame.DialogueText.Text = "not too long after many weird stuff happened, the hospital was closed and abandoned. Although no one was supposed to go there, some people still went there and some captured camera footage of the Goat and a ghost..."
	script.TenebrisVoicelines2.TGAAG.Ended:Wait()
	script.TenebrisVoicelines2.S1983:Play()
	Player.PlayerGui.Main.Dialogue.DialogueFrame.DialogueText.Text = "since 1983 the hospital was alright and after the death of Alex, everything changed to this day"
	script.TenebrisVoicelines2.S1983.Ended:Wait()
	Player.PlayerGui.Main.Dialogue.DialogueFrame.Visible = false 
	wait(3)
	Player.PlayerGui.Main.Dialogue.DialogueFrame.Visible = true
	script.TenebrisVoicelines2.YNSTBH:Play()
	Player.PlayerGui.Main.Dialogue.DialogueFrame.DialogueText.Text = "Your not supposed to be here..."
	script.TenebrisVoicelines2.YNSTBH.Ended:Wait()
	Player.PlayerGui.Main.Dialogue.DialogueFrame.DialogueText.Text = "What?"
	wait(1.5)
	script.TenebrisVoicelines2.YSTDIE:Play()
	Player.PlayerGui.Main.Dialogue.DialogueFrame.DialogueText.Text = "Your supposed to die.."
	script.TenebrisVoicelines2.YSTDIE.Ended:Wait()
	Player.PlayerGui.Main.Dialogue.DialogueFrame.DialogueText.Text = "WHAT?!"
	wait(1.5)
	script.TenebrisVoicelines2.ISYST:Play()
	Player.PlayerGui.Main.Dialogue.DialogueFrame.DialogueText.Text = "I SAID YOUR SUPPOSED TO"
	script.TenebrisVoicelines2.ISYST.Ended:Wait()
	script.TenebrisVoicelines2.DIE:Play()
	Player.PlayerGui.Main.Dialogue.DialogueFrame.DialogueText.Text = "DIE!"
	HandDown:Stop()
	DieAnim:Play()
	wait(1)
	workspace.Ambience2:Stop()
	workspace.BeyondAmbience1:Stop()
	workspace.BeyondAmbience2:Stop()
	Player.PlayerGui.Main.Dialogue.DialogueFrame.Visible = false
	Player.PlayerGui.Main.Fade.BackgroundTransparency = 0
	wait(2)
	game.ReplicatedStorage.TriggerBossFightVisibility:FireServer()
	HumanoidRootPart.CFrame = workspace.HeartRespawnPoint.CFrame
	Camera.CameraType = Enum.CameraType.Custom
game.Lighting.Ambient = Color3.fromRGB(100,100,100)
game.Lighting.Brightness = 3
game.Lighting.ClockTime = 12
game.Lighting.FogEnd = 250
FadeTween2:Play()
	wait(2.5)
	Player.PlayerGui.Main.Fade.BackgroundTransparency = 1
	Player.PlayerGui.Main.Dialogue.DialogueFrame.Visible = true
	Player.PlayerGui.Main.Dialogue.DialogueFrame.DialogueText.Text = "Huh!? How did i get here?!"
	wait(3)
	workspace.BeyondAmbience1:Play()
	workspace.BeyondAmbience2:Play()
	script.TenebrisVoicelines2.TTDH:Play()
	Player.PlayerGui.Main.Dialogue.DialogueFrame.DialogueText.Text = "TIME TO DIE HUMAN :)"
	script.TenebrisVoicelines2.TTDH.Ended:Wait()
	game.ReplicatedStorage.StartBossFight:FireServer()
	Player.PlayerGui.Main.Dialogue.DialogueFrame.Visible = false
	if Uis.TouchEnabled then
		Player.PlayerGui.Main.MobileButtons.SprintButton.Visible = true
	end
	Player.PlayerGui.Main.Stamina.Background.Bar.Size = UDim2.new(1,0,1,0)
	Player.PlayerGui.Main.Stamina.Background.Bar.Position = UDim2.new(-0.001, 0,-0, 0)
	Player.PlayerGui.Main.Stamina.Background.Visible = true	
	Player.PlayerGui.Main.Dialogue.DialogueFrame.Visible = false
	Humanoid.WalkSpeed = 8
	Humanoid.JumpPower = 50
	Player.PlayerGui.Main.Stamina.Background.Bar.Sprint.Enabled = true
	workspace.Objective:Play()
	Character:WaitForChild("CanClick").Value = false
	Player.PlayerGui.Main.Lives.Heart1.Visible = true
	Player.PlayerGui.Main.Lives.Heart2.Visible = true
	Player.PlayerGui.Main.Lives.Heart3.Visible = true
	Player.PlayerGui.Main.Lives.Heart4.Visible = true
	Player.PlayerGui.Main.Lives.Heart5.Visible = true
	ObjectiveTween1:Play()
	ObjectiveTweenText1:Play()
	ObjectiveTweenText2:Play()
	Player.PlayerGui.Main.Objective.ObjectiveFrame.ObjectiveText.Text = "SURVIVE [YOU HAVE 6 LIVES AND YOU CAN JUMP]"
	wait(3)
	ObjectiveTween2:Play()
	ObjectiveTweenText3:Play()
	ObjectiveTweenText4:Play()
	ObjectiveTween2.Completed:Wait()
	Character:WaitForChild("CanClick").Value = true
end)

and now here is the bossfight handler [Script in serverscriptservice]:

local rotation = CFrame.Angles(0, math.rad(135), 0)
local rotation2 = CFrame.Angles(0, math.rad(-135), 0)
local RotTentacle = CFrame.Angles(0, math.rad(180), 0)
local CanAttack = true

game.Players.PlayerAdded:Connect(function(plr)
	local function selectPlayer()
		local players = game.Players:GetPlayers()
		local selected = players[math.random(1,#players)]
		return selected
	end

	local function TearDropAttack()
		local TearDropModel = game.ReplicatedStorage.BossFightAttacks.TearDropAttack:Clone()
		workspace.BossFightAudio.Alert:Play()
		CanAttack = false
		TearDropModel.Parent = workspace
		wait(1.8)
		TearDropModel:WaitForChild("AlertParts"):Destroy()
		workspace.BossFightAudio.WaterDrop:Play()
		game:GetService("TweenService"):Create(TearDropModel.PrimaryPartBase, TweenInfo.new(3), {CFrame = workspace.TearDropFallenCFrame.CFrame}):Play()
		wait(1)
		workspace.BossFightAudio.WaterDrop:Stop()
		TearDropModel:Destroy()
		CanAttack = true
	end

	local function BallAttack(PlayerSelected)
		local Ball = game.ReplicatedStorage.BossFightAttacks.SlimeBallAttack:Clone()
		CanAttack = false
		Ball.Parent = workspace
		workspace.BossFightAudio.SlimeBallAttack:Play()
		game:GetService("TweenService"):Create(Ball, TweenInfo.new(1.5), {CFrame = PlayerSelected.Character.HumanoidRootPart.CFrame * CFrame.new(15,0,0)}):Play()
		wait(0.5)
		CanAttack = true
		Ball:Destroy()
	end

	local function TentacleSmackAttack(PlayerSelected)
		print("Smack Started")
		local Tentacle = game.ReplicatedStorage.BossFightAttacks.TentacleSlam:Clone()
		game:GetService('RunService').Stepped:Wait()
		CanAttack = false
		Tentacle.Parent = workspace
		Tentacle.PrimaryPart.CFrame = CFrame.lookAt(Tentacle.PrimaryPart.Position, PlayerSelected.Character.PrimaryPart.Position) * CFrame.Angles(math.rad(-82.251), 0, math.rad(90)) 
		wait(0.5)
		local Anim = Tentacle.Humanoid.Animator:LoadAnimation(script.SlamAnim)
		wait(0.5)
		workspace.BossFightAudio.TentacleAppear:Play()
		Anim:Play()
		Anim.Ended:Wait()
		print("Attack Finished")
		wait(1)
		CanAttack = true
		Tentacle:Destroy()
	end

	local function TentacleSpinAttack()
		local Tentacle = game.ReplicatedStorage.BossFightAttacks.TentacleSpin:Clone()
		--local Tentacle2 = game.ReplicatedStorage.BossFightAttacks.TentacleSpin2:Clone()
		game:GetService('RunService').Stepped:Wait()
		CanAttack = false
		Tentacle.Parent = workspace
		--Tentacle2.Parent = workspace
		wait(0.5)
		local Anim = Tentacle.Humanoid.Animator:LoadAnimation(script.SpinAnim)
	--	local Anim2 = Tentacle2.Humanoid.Animator:LoadAnimation(script.SpinAnim)
		wait(0.5)	
		workspace.BossFightAudio.TentacleAppear:Play()
		Anim:Play()
		--Anim2:Play()
		Anim.Ended:Wait()
		print("Attack Finished")
		wait(1)
		CanAttack = true
		Tentacle:Destroy()
		--Tentacle2:Destroy()
	end


	game.ReplicatedStorage.StartBossFight.OnServerEvent:Connect(function()
		workspace.BossFightAudio.BossFightMusicV2:Play()
		local RandomPlayer = nil

		TearDropAttack()

		RandomPlayer = selectPlayer()
		workspace.BossfightTenebris.Cube.CFrame = CFrame.lookAt(workspace.BossfightTenebris.Cube.Position, RandomPlayer.Character.HumanoidRootPart.Position)
		BallAttack(RandomPlayer)

		RandomPlayer = selectPlayer()
		workspace.BossfightTenebris.Cube.CFrame = CFrame.lookAt(workspace.BossfightTenebris.Cube.Position, RandomPlayer.Character.HumanoidRootPart.Position)
		BallAttack(RandomPlayer)

		RandomPlayer = selectPlayer()
		workspace.BossfightTenebris.Cube.CFrame = CFrame.lookAt(workspace.BossfightTenebris.Cube.Position, RandomPlayer.Character.HumanoidRootPart.Position)
		TentacleSmackAttack(RandomPlayer)

		TearDropAttack()

		RandomPlayer = selectPlayer()
		workspace.BossfightTenebris.Cube.CFrame = CFrame.lookAt(workspace.BossfightTenebris.Cube.Position, RandomPlayer.Character.HumanoidRootPart.Position)
		BallAttack(RandomPlayer)

		RandomPlayer = selectPlayer()
		workspace.BossfightTenebris.Cube.CFrame = CFrame.lookAt(workspace.BossfightTenebris.Cube.Position, RandomPlayer.Character.HumanoidRootPart.Position)
		TentacleSmackAttack(RandomPlayer)

		RandomPlayer = selectPlayer()
		workspace.BossfightTenebris.Cube.CFrame = CFrame.lookAt(workspace.BossfightTenebris.Cube.Position, RandomPlayer.Character.HumanoidRootPart.Position)
		TentacleSpinAttack()

		RandomPlayer = selectPlayer()
		workspace.BossfightTenebris.Cube.CFrame = CFrame.lookAt(workspace.BossfightTenebris.Cube.Position, RandomPlayer.Character.HumanoidRootPart.Position)
		TentacleSmackAttack(RandomPlayer)

		TearDropAttack()

		RandomPlayer = selectPlayer()
		workspace.BossfightTenebris.Cube.CFrame = CFrame.lookAt(workspace.BossfightTenebris.Cube.Position, RandomPlayer.Character.HumanoidRootPart.Position)
		TentacleSpinAttack()

		RandomPlayer = selectPlayer()
		workspace.BossfightTenebris.Cube.CFrame = CFrame.lookAt(workspace.BossfightTenebris.Cube.Position, RandomPlayer.Character.HumanoidRootPart.Position)
		TentacleSmackAttack(RandomPlayer)

		RandomPlayer = selectPlayer()
		workspace.BossfightTenebris.Cube.CFrame = CFrame.lookAt(workspace.BossfightTenebris.Cube.Position, RandomPlayer.Character.HumanoidRootPart.Position)
		TentacleSmackAttack(RandomPlayer)

		RandomPlayer = selectPlayer()
		workspace.BossfightTenebris.Cube.CFrame = CFrame.lookAt(workspace.BossfightTenebris.Cube.Position, RandomPlayer.Character.HumanoidRootPart.Position)
		TentacleSmackAttack(RandomPlayer)

		TearDropAttack()

		RandomPlayer = selectPlayer()
		workspace.BossfightTenebris.Cube.CFrame = CFrame.lookAt(workspace.BossfightTenebris.Cube.Position, RandomPlayer.Character.HumanoidRootPart.Position)
		BallAttack(RandomPlayer)

		RandomPlayer = selectPlayer()
		workspace.BossfightTenebris.Cube.CFrame = CFrame.lookAt(workspace.BossfightTenebris.Cube.Position, RandomPlayer.Character.HumanoidRootPart.Position)
		TentacleSpinAttack()
		print("Ended")
		workspace.BossFightAudio.BossFightMusicV2:Stop()
	end)
end)


script.SlamAnim:GetMarkerReachedSignal("SmackSound"):Connect(function(value)
	workspace.BossFightAudio.TentacleSmack:Play()
end)

script.SpinAnim:GetMarkerReachedSignal("SmackSound"):Connect(function(value)
	workspace.BossFightAudio.TentacleSmack:Play()
end)

[note : some stuff in the bossfight handler are not really used]

i have no idea what to do since this is the first time this had happened to me. [probably cuz this is my first multiplayer game]

3 Likes

The issue is that you’re re-creating all of your functions every time a Player joins; you’ve nested everything inside of game.Players.PlayerAdded:Connect(function(plr) ... end)

If you remove lines 6 and 147 (and unindent everything to fix the formatting :P) everything should work normally.

okay! i just removed it. when the second dev comes back on i will test it out with them! thanks!

ok. i tested it and i have a few things to say.
1 : theres not too much chaos anymore
2 : the attacks double when i tested it.

maybe theres an issue with picking a random player? or maybe not since some attacks which dont require a player still gets duplicated

This line in your Cutscene script is the issue;

Every player is going to be firing that RemoteEvent, so the boss fight function is being run simultaneously, one copy for each player. Before, the chaos was because you had n*n copies running, where n is the number of players, since every player was triggering the event which had a listener created for every player that had joined up to that point.


There are a couple ways to fix this, but the easiest is to just add a debounce to the StartBossFight function:

local bossFightStarted = false
game.ReplicatedStorage.StartBossFight.OnServerEvent:Connect(function()
    if bossFightStarted then return end
    bossFightStarted = true

    -- snip

    bossFightStarted = false
end)

Because of the asynchronous nature of RemoteEvents, I’d add a print just after setting the debounce variable to true to check if this actually works. There are better (and more reliable / safe) ways to fix this, if this method isn’t stable enough!

is the snip the area where the actual bossfight is?

Yes, it’s all the existing code you had, I just cut it out for clarity!

okay… i added the 4 lines of code. ill test it out to see if it works. dont really know how this would make it work but yeah.

omg thank you so so much!! it actually works now! but now i just need to fix 1 attacks cooldown and yeah!
i would send a video but the file size is too big!

1 Like

To explain; the bossFightStarted variable is scoped outside of the function you’re connecting to the StartBossFight RemoteEvent. (Technically, the functions you’re connecting, as a new one is generated each time the Event is fired, but that doesn’t matter here.)

Each time the function runs, it first checks if another function is already running, and immediately exits in that case. Setting bossFightStarted to true in one instance of the handler running sets it to true in all of them!

Without using Actors (parallel Luau), the Server will share compute resources by running each “thread” of Lua[u] code for some amount of time before “context switching” to another. The only reliable way to trigger this is with wait, but it will happen automatically for particularly compute-intense functions.

What this means for this code is that, every Player that fires the StartBossFight RemoteEvent will spawn one instance each of the handler function as a Lua[u] “thread”, but only one of those instances will actually be executed at a time. The rest will be paused right before the first line of code – the if – until that first one yields or is forcibly yielded by the Server. By that time, the bossFightStarted variable will have been set to true, so every subsequent “thread” immediately exits the function, with a net result of the rest of the code only running once!

In 99.99% of cases, a debounce like this will work exactly as intended, but as I said there are more reliable methods in those few cases where it doesn’t.


One such method is to have each Player fire an event to signal to the Server that it is ready to start the boss fight (i.e. its cutscene has finished), and the Server waits until all Players are ready to actually start the fight. This has its own edge cases to worry about, and is far more complicated than adding a debounce.

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