Why do I have to fire RemoteEvent 2x for it to cast the value to client?

My code is now working fine, but Im woried. First thing is that I have a recursive function (a function that ends up calling itself) and i’ve read that this can lead to stack overflow (is it true in my case?). Second because of, for a weird motive that I have no idea what, my remote event only fires if I write the fire line 2 following times.

Actually, when the player enters it casts 2 times, but when it dies and reloads, all the following times are called once, even though there are 2 lines calling it. Odd! The function uses :LoadCharacter(), CharacterAutoLoads is off, maybe the cause is something related to that but i dont know.

This is the function, it is a function called by PlayerAdded:Connect on server. I dont know also why is that if I call this function inside PlayerAdded, a following CharacterAdded event does not fire at all, and obviously not a following Humanoid.Died event too. I wanted to make it to possibly prevent the stack overflow. Here is the full code:

local Charlist = {}
game.Players.CharacterAutoLoads = false

local function SpawnCharacter(Player)
	local Character
	local Humanoid
	
	local EggsCount = 0
	local testing = false
	for i, v in pairs(workspace.GrownEggs:GetChildren()) do
		EggsCount += 1
		if v.Name == "Test" then
			testing = true
		end
	end

	if EggsCount > 0 and not testing then -- egg spawn
		-- set starterChar Name
		for i, v in pairs(game.StarterPlayer:GetChildren()) do
			if v.Name == "StarterCharacter" then
				v.Name = v.Role.Value
			end
		end
		if not testing then
			game.StarterPlayer.Baby.Name = "StarterCharacter"
		else
			game.StarterPlayer.Worker.Name = "StarterCharacter"
		end
		
		-- loads Char
		Player:LoadCharacter()
		Character = Player.Character
		Humanoid = Character.Humanoid

		-- destroy closer egg
		local closer = {Part = {Position = Vector3.new(900,900,900)}}
		for i, v in pairs(workspace.GrownEggs:GetChildren()) do
			if (v.Part.Position - Character.HumanoidRootPart.Position).Magnitude < (closer.Part.Position - Character.HumanoidRootPart.Position).Magnitude then
				closer = v
			end
		end
		if closer.Part.Position ~= Vector3.new(900,900,900) then
			closer:Destroy()
		end

	else -- no eggs spawn

		-- set starterChar Name
		for i, v in pairs(game.StarterPlayer:GetChildren()) do
			if v.Name == "StarterCharacter" then
				v.Name = v.Role.Value
			end
		end
		if not testing then
			game.StarterPlayer.Farmer.Name = "StarterCharacter"
		else
			game.StarterPlayer.Worker.Name = "StarterCharacter"
		end

		-- loads char in position
		Player:LoadCharacter()
		if not testing then
			Player.Character:MoveTo(Vector3.new(math.random(-100,100), 10, math.random(-100,100)))
		end
		Character = Player.Character
		Humanoid = Character.Humanoid
		
		
		--=============================================================================
		--Charadded
		
		-- Variables
		local Root = Character:WaitForChild("HumanoidRootPart")

		-- LoadAppearence
		repeat wait() until Player:HasAppearanceLoaded() and Player.Character.Parent == workspace
		Charlist[Character] = Character

		-- Hunger
		local MaxHunger = 200
		local Hunger = Instance.new("IntValue")
		Hunger.Name = "HungerValue"
		Hunger.Parent = Character
		Hunger.Value = MaxHunger

		spawn(function()
			local connection = true
			while connection do
				wait(1)
				if Hunger.Value > 0 then
					Hunger.Value -= 1
				end
				if Hunger.Value < 1 then
					Humanoid.Health -= 10
				end
				if Humanoid.Health == 0 or Humanoid:GetState() == Enum.HumanoidStateType.Dead then
					connection = nil
				end
			end
		end)

		Hunger.Changed:connect(function()
			if Hunger.Value < 0 then
				Hunger = 0
			end
			if Hunger.Value > MaxHunger then
				Hunger.Value = MaxHunger
			end
		end)

		-- Grip Tool
		local GripTool = Instance.new("Tool")
		GripTool.Parent = Character
		GripTool.Name = "GripTool"
		GripTool.CanBeDropped = false

		-- Turn Down Player Sound
		RemoteEvent:FireAllClients("TurnDownPlayerSound", Character)
		RemoteEvent:FireAllClients("TurnDownPlayerSound", Character)
		-- Attention: Needs to be called 2 times, otherwise only will fire in first char life.
		-- First life calls 2x, next lifes calls 1.

		-- Sounds ----
		for i, v in pairs(SoundService.ToRoot:GetChildren()) do
			v:Clone().Parent = Root
		end

                -- adding things to char
		local antflesh = game.ServerStorage.FleshEffect:Clone()
		antflesh.Parent = Character.RightAnt2
 		local antflesh = game.ServerStorage.FleshEffect:Clone()
		antflesh.Parent = Character.LeftAnt2
		local GreenFeromon = game.ServerStorage.GreenFeromon:Clone()
		GreenFeromon.Parent = Character.HumanoidRootPart
		local RedFeromon = game.ServerStorage.RedFeromon:Clone()
		RedFeromon.Parent = Character.HumanoidRootPart
		local RLightParticle = game.ServerStorage.LightParticle:Clone()
		RLightParticle.Parent = Character.RightAnt3
		local LLightParticle = game.ServerStorage.LightParticle:Clone()
		LLightParticle.Parent = Character.LeftAnt3


		Humanoid.WalkSpeed = 13

		--Leaderstats
		local leaderstats = Instance.new("Folder")
		leaderstats.Name = "leaderstats"
		leaderstats.Parent = Player

		local coins = Instance.new("IntValue")
		coins.Name = "Coins"
		coins.Parent = leaderstats

		-- Store plrs description
		Appearences.Player = Character.Humanoid:GetAppliedDescription()

		-- Health Destroy
		if Character:FindFirstChild("Health") then
			Character.Health:Destroy()
		end

		-- BreakJointsOnDeath
		Humanoid.BreakJointsOnDeath = false
		
		Humanoid.Died:Connect(function()
			wait(5)
			SpawnCharacter(Player)
		end)
		
	end
end

game.Players.PlayerAdded:Connect(SpawnCharacter)
1 Like

Oh goodness, why are you using StarterCharacter like that.

What I’m more curious to know is which script is listening to OnClientEvent for the remote, not the script, because that’s where the source of the problem is. Where is that LocalScript and what is the content of it relevant to the remote connection?

the local script listening contains just 3 lines turning down players roots sounds volumes. When i use “print(request)” there it prints “TurnDownPlayerSound” 2x in first life, 1x in next lifes.

I’ve found usefull to use StarterCharacter like that because I had problems running CharacterAdded when calling “LoadCharacter()” in a custom way. I would had to set lots of things manually like network ownership, spawnLocation, etc. And would have to put everything from startercharacterscripts inside the char manually, even the Animate and Health script. AND i didnt get to solve but Animations stopped working when i used “LoadCharacter” without this trick.

This isn’t enough information for me to work off of. I need to know where it is and what code is running in it. Having your use case on hand is appreciated and does add more context, but it’s not sufficient enough for me to actually know any specifics about how this LocalScript is running.

As for the StarterCharacter use, it just seems weird and hacky. Developers have often likened it to be a way to change out characters at run time even though that’s not its purpose.

ok then…

RemoteEvent.OnClientEvent:Connect(function(request, dado, dado2)

if request == "TurnDownPlayerSound" then
	print("TurnDownPlayerSound event called") -- dont erease
	dado.HumanoidRootPart.Running.Volume = 0
	dado.HumanoidRootPart.Climbing.Volume = 0
	dado.HumanoidRootPart.Died.SoundId = "rbxassetid://1413010947"
end

end

I’ve tried adding a print(request) before “if” too, to see if there was another request ovewriting, but it wasnt the case.

Also need to know where this LocalScript is. That still hasn’t been provided.

Ultimately though, now that I know exactly what your code is doing, I can tell you that you aren’t going to need this LocalScript or the remote to begin with. You can just fork RbxCharacterSounds and mute the sounds from that script instead, since it’s responsible for handling character sounds. Shouldn’t need the interference or reliance of a remote for doing that.

Oh sorry. The LocalScript was in StarterCharacterScripts

well if the sound was the only problem… Im just afraid that this problem is pointing a bigger one sinse it is so strange that I need to call remote 2x.

That could explain the behaviour you were seeing. What might’ve been happening is because of the odd method of switching out characters, the event might’ve been firing for an old character that was in the process of being removed and switched with a new one.

The unpredictability of the current character loading events causes weird problems like this, where CharacterAdded fires before the character is actually even set up and everything. An update is coming to address this, but has been postponed for upwards a year because of some technical run-ins and priorities on other features.

Additionally, your script probably just shouldn’t be there at all.

Forking RbxCharacterSounds would allow you to make the changes you want without having to use a remote to facilitate this. You can edit the sound id as well as the volume or just make those sounds not appear at all when the character loads.

So what I mean to say is:

  1. Fork RbxCharacterSounds, make your changes there.
  2. Remove the RemoteEvent and the LocalScript you have in StarterCharacterScripts that connects to the remote.
  3. Remove the lines of code from your server script that fire the remote twice (or at all). Completely get rid of the remote, you don’t need it.

When you fork RbxCharacterSounds, you can make a few adjustments so that all newly spawned characters will have their sounds set up the way you want: Running muted, Climbing muted and the Died sound changed to a different audio.

At the top of the script, you will see a dictionary under the variable SOUND_DATA. Remove the fields Climbing and Running. Since these sounds are looped, they also have additional handlers that you need to find and remove. Look for “stateTransitions” and then “Enum.HumanoidStateType.Climbing”, then remove that whole chunk. Same goes with Running. Do the same for loopedSoundUpdaters. You will also need to find “stateRemap” and remove the RunningNoPhysics entry from it.

Ultimately if this is all too hard for you to understand, then the only thing you need to do is add a Volume key in the Running and Climbing tables and make it 0 to mute them. Simple.

With the Running and Climbing bits removed, this is what your code should look like (a LocalScript in StarterPlayerScripts called RbxCharacterSounds):

With Running and Climbing parts removed
-- Roblox character sound script

local Players = game:GetService("Players")
local RunService = game:GetService("RunService")

local SOUND_DATA = {
	Died = {
		SoundId = "rbxassetid://1413010947",
	},
	FreeFalling = {
		SoundId = "rbxasset://sounds/action_falling.mp3",
		Looped = true,
	},
	GettingUp = {
		SoundId = "rbxasset://sounds/action_get_up.mp3",
	},
	Jumping = {
		SoundId = "rbxasset://sounds/action_jump.mp3",
	},
	Landing = {
		SoundId = "rbxasset://sounds/action_jump_land.mp3",
	},
	Splash = {
		SoundId = "rbxasset://sounds/impact_water.mp3",
	},
	Swimming = {
		SoundId = "rbxasset://sounds/action_swim.mp3",
		Looped = true,
		Pitch = 1.6,
	},
}

 -- wait for the first of the passed signals to fire
local function waitForFirst(...)
	local shunt = Instance.new("BindableEvent")
	local slots = {...}

	local function fire(...)
		for i = 1, #slots do
			slots[i]:Disconnect()
		end

		return shunt:Fire(...)
	end

	for i = 1, #slots do
		slots[i] = slots[i]:Connect(fire)
	end

	return shunt.Event:Wait()
end

-- map a value from one range to another
local function map(x, inMin, inMax, outMin, outMax)
	return (x - inMin)*(outMax - outMin)/(inMax - inMin) + outMin
end

local function playSound(sound)
	sound.TimePosition = 0
	sound.Playing = true
end

local function shallowCopy(t)
	local out = {}
	for k, v in pairs(t) do
		out[k] = v
	end
	return out
end

local function initializeSoundSystem(player, humanoid, rootPart)
	local sounds = {}

	-- initialize sounds
	for name, props in pairs(SOUND_DATA) do
		local sound = Instance.new("Sound")
		sound.Name = name

		-- set default values
		sound.Archivable = false
		sound.EmitterSize = 5
		sound.MaxDistance = 150
		sound.Volume = 0.65

		for propName, propValue in pairs(props) do
			sound[propName] = propValue
		end

		sound.Parent = rootPart
		sounds[name] = sound
	end

	local playingLoopedSounds = {}

	local function stopPlayingLoopedSounds(except)
		for sound in pairs(shallowCopy(playingLoopedSounds)) do
			if sound ~= except then
				sound.Playing = false
				playingLoopedSounds[sound] = nil
			end
		end
	end

	-- state transition callbacks
	local stateTransitions = {
		[Enum.HumanoidStateType.FallingDown] = function()
			stopPlayingLoopedSounds()
		end,

		[Enum.HumanoidStateType.GettingUp] = function()
			stopPlayingLoopedSounds()
			playSound(sounds.GettingUp)
		end,

		[Enum.HumanoidStateType.Jumping] = function()
			stopPlayingLoopedSounds()
			playSound(sounds.Jumping)
		end,

		[Enum.HumanoidStateType.Swimming] = function()
			local verticalSpeed = math.abs(rootPart.Velocity.Y)
			if verticalSpeed > 0.1 then
				sounds.Splash.Volume = math.clamp(map(verticalSpeed, 100, 350, 0.28, 1), 0, 1)
				playSound(sounds.Splash)
			end
			stopPlayingLoopedSounds(sounds.Swimming)
			sounds.Swimming.Playing = true
			playingLoopedSounds[sounds.Swimming] = true
		end,

		[Enum.HumanoidStateType.Freefall] = function()
			sounds.FreeFalling.Volume = 0
			stopPlayingLoopedSounds(sounds.FreeFalling)
			playingLoopedSounds[sounds.FreeFalling] = true
		end,

		[Enum.HumanoidStateType.Landed] = function()
			stopPlayingLoopedSounds()
			local verticalSpeed = math.abs(rootPart.Velocity.Y)
			if verticalSpeed > 75 then
				sounds.Landing.Volume = math.clamp(map(verticalSpeed, 50, 100, 0, 1), 0, 1)
				playSound(sounds.Landing)
			end
		end,

		[Enum.HumanoidStateType.Seated] = function()
			stopPlayingLoopedSounds()
		end,

		[Enum.HumanoidStateType.Dead] = function()
			stopPlayingLoopedSounds()
			playSound(sounds.Died)
		end,
	}

	-- updaters for looped sounds
	local loopedSoundUpdaters = {
		[sounds.FreeFalling] = function(dt, sound, vel)
			if vel.Magnitude > 75 then
				sound.Volume = math.clamp(sound.Volume + 0.9*dt, 0, 1)
			else
				sound.Volume = 0
			end
		end,
	}

	-- state substitutions to avoid duplicating entries in the state table
	local stateRemap = {}

	local activeState = stateRemap[humanoid:GetState()] or humanoid:GetState()

	local stateChangedConn = humanoid.StateChanged:Connect(function(_, state)
		state = stateRemap[state] or state

		if state ~= activeState then
			local transitionFunc = stateTransitions[state]

			if transitionFunc then
				transitionFunc()
			end

			activeState = state
		end
	end)

	local steppedConn = RunService.Stepped:Connect(function(_, worldDt)
		-- update looped sounds on stepped
		for sound in pairs(playingLoopedSounds) do
			local updater = loopedSoundUpdaters[sound]

			if updater then
				updater(worldDt, sound, rootPart.Velocity)
			end
		end
	end)

	local humanoidAncestryChangedConn
	local rootPartAncestryChangedConn
	local characterAddedConn

	local function terminate()
		stateChangedConn:Disconnect()
		steppedConn:Disconnect()
		humanoidAncestryChangedConn:Disconnect()
		rootPartAncestryChangedConn:Disconnect()
		characterAddedConn:Disconnect()
	end

	humanoidAncestryChangedConn = humanoid.AncestryChanged:Connect(function(_, parent)
		if not parent then
			terminate()
		end
	end)

	rootPartAncestryChangedConn = rootPart.AncestryChanged:Connect(function(_, parent)
		if not parent then
			terminate()
		end
	end)

	characterAddedConn = player.CharacterAdded:Connect(terminate)
end

local function playerAdded(player)
	local function characterAdded(character)
		-- Avoiding memory leaks in the face of Character/Humanoid/RootPart lifetime has a few complications:
		-- * character deparenting is a Remove instead of a Destroy, so signals are not cleaned up automatically.
		-- ** must use a waitForFirst on everything and listen for hierarchy changes.
		-- * the character might not be in the dm by the time CharacterAdded fires
		-- ** constantly check consistency with player.Character and abort if CharacterAdded is fired again
		-- * Humanoid may not exist immediately, and by the time it's inserted the character might be deparented.
		-- * RootPart probably won't exist immediately.
		-- ** by the time RootPart is inserted and Humanoid.RootPart is set, the character or the humanoid might be deparented.

		if not character.Parent then
			waitForFirst(character.AncestryChanged, player.CharacterAdded)
		end

		if player.Character ~= character or not character.Parent then
			return
		end

		local humanoid = character:FindFirstChildOfClass("Humanoid")
		while character:IsDescendantOf(game) and not humanoid do
			waitForFirst(character.ChildAdded, character.AncestryChanged, player.CharacterAdded)
			humanoid = character:FindFirstChildOfClass("Humanoid")
		end

		if player.Character ~= character or not character:IsDescendantOf(game) then
			return
		end

		-- must rely on HumanoidRootPart naming because Humanoid.RootPart does not fire changed signals
		local rootPart = character:FindFirstChild("HumanoidRootPart")
		while character:IsDescendantOf(game) and not rootPart do
			waitForFirst(character.ChildAdded, character.AncestryChanged, humanoid.AncestryChanged, player.CharacterAdded)
			rootPart = character:FindFirstChild("HumanoidRootPart")
		end

		if rootPart and humanoid:IsDescendantOf(game) and character:IsDescendantOf(game) and player.Character == character then
			initializeSoundSystem(player, humanoid, rootPart)
		end
	end

	if player.Character then
		characterAdded(player.Character)
	end
	player.CharacterAdded:Connect(characterAdded)
end

Players.PlayerAdded:Connect(playerAdded)
for _, player in ipairs(Players:GetPlayers()) do
	playerAdded(player)
end

And then the simple version, which is just to use 0 volume (but there’s still the remaining overhead of actually being connected to the events), you only need to change the SOUND_DATA table.

SOUND_DATA table with Running and Climbing muted
local SOUND_DATA = {
	Climbing = {
		SoundId = "rbxasset://sounds/action_footsteps_plastic.mp3",
		Looped = true,
		Volume = 0,
	},
	Died = {
		SoundId = "rbxasset://sounds/uuhhh.mp3",
	},
	FreeFalling = {
		SoundId = "rbxasset://sounds/action_falling.mp3",
		Looped = true,
	},
	GettingUp = {
		SoundId = "rbxasset://sounds/action_get_up.mp3",
	},
	Jumping = {
		SoundId = "rbxasset://sounds/action_jump.mp3",
	},
	Landing = {
		SoundId = "rbxasset://sounds/action_jump_land.mp3",
	},
	Running = {
		SoundId = "rbxasset://sounds/action_footsteps_plastic.mp3",
		Looped = true,
		Pitch = 1.85,
		Volume = 0,
	},
	Splash = {
		SoundId = "rbxasset://sounds/impact_water.mp3",
	},
	Swimming = {
		SoundId = "rbxasset://sounds/action_swim.mp3",
		Looped = true,
		Pitch = 1.6,
	},
}

(Meant to do a direct reply, oops. cc @Necro_las)

1 Like

Just crazy stuff happening with char loading then xD

Thank you, im happy to know about RBXCharacterSounds