I’m making a game where the player gets morphed into a survivor/killer, and ui is supposed to appear on their screen depending on their role. For some reason, the client isn’t always recieving the message to make the ui visible. Here is some of the code:
Server Script Code
if player:FindFirstChild("IsKiller").Value == true then
print("Morphing Killer")
Morph.KillerMorph(player, "Test")
else
print("Morphing Survivor")
Morph.SurvivorMorph(player, "MinWage")
end
Module Script Code: (run on the server)
morphModule.KillerMorph = function(plr, killer)
local charClone = Killers:FindFirstChild(killer):Clone()
charClone.Name = plr.Name
plr.Character:Destroy()
plr.Character = charClone
charClone.Parent = workspace
charClone:FindFirstChild("HumanoidRootPart").Anchored = false
charClone:MoveTo(plr.Character.HumanoidRootPart.Position)
ShowMovesetGui:FireClient(plr, charClone)
print("Firing to Client (Killer), "..plr.Name)
end
Client Code:
Events.ShowMovesetGui.OnClientEvent:Connect(function(character)
print("Client recieved")
local guiToShow = nil
local coreName = character:FindFirstChild("CoreName")
if coreName then
for _, Gui in pairs(StarterGui.MovesetGui:GetDescendants()) do
if Gui.Name == coreName.Value and Gui:IsA("Frame") then
guiToShow = Gui
--print("Name is equal")
else
--print("Name is not equal")
end
end
end
if guiToShow then
guiToShow.Visible = true
else
return
end
end)
hey man, i think the better option here would be to avoid using remoteevents entirely and just networking states, then listening for those states on the client. for example, each player has their own states folder with something like a boolvalue named “Killer” inside, then use getpropertychangedsignal to listen for when the player is the killer, you could also add a childadded wait loop to check if a certain part was added that can confirm the player was morphed correctly inside of the players character.
also, please use generalized iteration & querydescendants.
generalized iteration makes it so that you don’t have to specify an iterator function, like pairs, ipairs, or next, and automatically picks the best for u. example: for key, value in table do ... end
querydescendants is a better more optimized version of getdescendants that allows for quicker & easier filtering between instances
i’m not too sure how multiple selectors work with it, but you could try somethiing like:
local CoreName = coreName.Value
local UI = StarterGui.MovesetGui:QueryDescendants(`#{CoreName} Frame`)[1]
the first selector here (#{CoreName} just gets every instance with that name, then the second thing is Frame which just filters it so that only Frame instances appear in the results. since querydescendants returns a table you index the result of htat by 1 and you have the UI without any for loops or anything.
There’s no such thing: If server sends event, the client gets it (unless there are network issues, but that would appear in different and more noticeable areas also, like experience not loading and so on).
What can be an issue with the events: the receiver has not set listener yet.
For example, if you have this setup:
event is “static” in, let’s say, ReplicatedStorage/Events/Event
client does ReplicatedStorage.Events.Event.OnClientEvent(function() end)
server does ReplicatedStorage.Events.Event:FireClient(player)
Then when you test this experience in studio, it’s impossible to be sure what’s being executed first 2. or 3. because of race conditions.
That’s a nice topic.
And since you, i am sure, like puzzles, leaving finding solution for you.
Thank you!
If client isnt almost receiving remote event, i would look into the server code. Specifically findfirstchild is killer. Because if it wont find it, it wont fire. You are certainly hitting a runtime condition here like @af_2048 said, however i would look into the server code if you havent yet.
Alternatively, we can evade remote events which is good in my opinion and follow @ruinedcenturies way by having a boolean and get a listener in.
ok figured out the missed remote thing, two things going on. one, KillerMorph reads HumanoidRootPart.Position AFTER destroying the old character which can just error and kill the rest of the function before it even gets to FireClient. two, if the morph happens before the client script connects (like right on spawn) the event just gets dropped, FireClient doesnt queue for late listeners
module script fix:
morphModule.KillerMorph = function(plr, killer)
local charClone = Killers:FindFirstChild(killer):Clone()
if not charClone then
warn("No killer model found for:", killer)
return
end
charClone.Name = plr.Name
local oldChar = plr.Character
local spawnPos = oldChar and oldChar:FindFirstChild("HumanoidRootPart") and oldChar.HumanoidRootPart.Position
charClone.Parent = workspace
plr.Character = charClone
local hrp = charClone:FindFirstChild("HumanoidRootPart")
if hrp then
hrp.Anchored = false
if spawnPos then
charClone:MoveTo(spawnPos)
end
end
if oldChar then
oldChar:Destroy()
end
plr:SetAttribute("PendingMovesetGui", charClone.Name)
ShowMovesetGui:FireClient(plr, charClone)
print("Firing to Client (Killer), " .. plr.Name)
end
same thing for SurvivorMorph just swap the model lookup
client side just gotta catch the case where it already happened before we connected:
local function applyMoveset(character)
local guiToShow = nil
local coreName = character:FindFirstChild("CoreName")
if coreName then
for _, gui in pairs(StarterGui.MovesetGui:GetDescendants()) do
if gui.Name == coreName.Value and gui:IsA("Frame") then
guiToShow = gui
break
end
end
end
if guiToShow then
guiToShow.Visible = true
end
end
Events.ShowMovesetGui.OnClientEvent:Connect(applyMoveset)
if player.Character then
applyMoveset(player.Character)
end
player.CharacterAdded:Connect(applyMoveset)
also also double check this localscript is in StarterPlayerScripts and not inside the character model, if its under the character it dies every morph/respawn and takes the connection with it which honestly couldve been causing half the missed cases on its own