Add a reliable method for swapping out R15 packages without killing the Humanoid

As a Roblox developer, it is currently incredibly painful to switch out R15 limbs, due to the peskiness of our friend the Humanoid.

Whenever the neck joint is removed, the Humanoid’s dead state gets immediately triggered. While in the dead state, all joints are forcefully broken during every single physics step. So if you try to create new joints, they usually disappear before you can revive the Humanoid.

Now you might think that we can work around this by using SetStateEnabled, but heres where things get weirder. Humanoid:SetStateEnabled("Dead",false) does not replicate across the server-client boundary, so you have to make sure it is set on the client as well via a RemoteFunction. On top of that, we also have to verify that the joints replicated after we apply the new limbs so that we don’t accidentally kill the player while we re-activate the dead state.

So to sum it up, this is the current protocol required to switch out Humanoid limbs on the server for a player:

  1. Make sure the Dead enabled state is set to false on both the server and client through a RemoteFunction
  2. Swap out the old limbs for the new limbs.
  3. Call Humanoid:BuildRigFromAttachments()
  4. Ping the client through a RemoteFunction up to 10 times until it confirms that all RigAttachments have corresponding Motor6D instances generated.
  5. Make sure the Dead enabled state is set to true on both the server and client through a RemoteFunction.

As you can see, this is kinda ridiculous. I think there should be a more straight-forward way of going about with this, because it would be really difficult for an inexperienced developer to figure out how to do this correctly.

The ultimate underlying problem here is that the Humanoid is incredibly picky while in its Dead state.

61 Likes

I agree with all of what you’re saying in your post, but I am feeling pedantic. Wouldn’t it be better to yield until the client tells you that all the RigAttachments have worked? Seems more reliable and less “guess worky” to me.

I want this. Swapping out R15 packages is so much more complicated right now than it was for R6.

Well I don’t want it to yield indefinitely due to something random and unforeseen.

I was swapping out R15 package parts on the humanoid on server/client without any problems until a client update sometime last week. Now, there’s a race condition where about 50% of the time, the engine detects that the connection between UpperTorso and Head broke, and it kills the humanoid.

4 Likes

Edit: Solution didn’t work in online mode.

Edit 2: Found a working solution! Put this in a LocalScript in StarterPlayerScripts. Since death state isn’t needed locally unless you are resetting, this script auto-disables the death state for your character, and re-enables it if you try to reset.

local RunService = game:GetService("RunService")
if RunService:IsServer() and RunService:IsClient() then
	-- play solo
	return
end

local player = game:GetService("Players").LocalPlayer

local resetBindable = Instance.new("BindableEvent")
resetBindable.Event:connect(function()
    if player.Character and player.Character:FindFirstChild("Humanoid") then
		-- re-enable death state
		player.Character.Humanoid:SetStateEnabled(Enum.HumanoidStateType.Dead, true)
		player.Character:BreakJoints()
		-- force death
	end
end)

game:GetService("StarterGui"):SetCore("ResetButtonCallback", resetBindable)

local function onCharacterAdded(character)
	if character == nil then
		return
	end
	if character.Parent == nil then
		character.AncestryChanged:wait()
	end
	local humanoid = character:WaitForChild("Humanoid")
	humanoid:SetStateEnabled(Enum.HumanoidStateType.Dead, false)
end

player.CharacterAdded:connect(onCharacterAdded)
onCharacterAdded(player.Character)
12 Likes

When trying out the code-snippet, there’s some loose ends:

  • onCharacterAdded(player) should check on the character
  • SetCore never set with ResetButtonCallback
  • Resetting in Solo Mode does not detect death when using R15, but does for R6
  • Falling into the void in Solo Mode does not detect death
  • Resetting in a local server makes the death go undetected, and you start healing with 0 health after resetting, but your joints doesn’t seem broken

To me, it seems more appropriate to handle the case of R15 characters changing, as CloneTrooper outlined, rather than changing the death logic for everyone, even if they aren’t included in this issue.

  1. Fixed
  2. Fixed
  3. Fixed
  4. Fixed
  5. What is local server? Using start server with 1 player?
2 Likes

Yeah - to test how the script would behave when server & client were separated

With the fixed script, this case should work, since ResetButtonCallback ends up calling :BreakJoints() on the character.

4 Likes