I made a chair. I can sit on it. I can die on it. But why I die on it, is mystery I with my small tester team can't solve for 2 months

So, I have made chair system - I use WeldConstraint to weld character primary part to part (Not SeatPart, just Part).
Sitting on chair is absolutely ok, but when I try to stand up, depending on time I spent sitting on it, current moon phase, star alignment, math.random() and many other factors I can die. Reason of such death is character being parented to nil. But why?

  1. Server code for handling chairs:
local RS = game:GetService("ReplicatedStorage")
local SSS = game:GetService("ServerScriptService")
local IsLobby = --check for PlaceId
local Remotes = RS:WaitForChild("Remotes")
local CollectionService = game:GetService("CollectionService")

local Libraries = {
	Materials = require(RS:WaitForChild("Libraries"):WaitForChild("Materials")),
	ItemCreator = require(RS:WaitForChild("Libraries"):WaitForChild("ItemCreator")),
	Types = require(RS:WaitForChild("Libraries"):WaitForChild("TypesLibrary")),
	CurrentItems = require(RS:WaitForChild("Libraries"):WaitForChild("CurrentItemsData")),
	
	AntiCheat = require(SSS.PlayerActions.AntiCheat.AntiCheatController),
}

local ModuleMeta = {}
ModuleMeta.__index = ModuleMeta

local ClientAllowedActions = {"Sit", "Free"}

if IsLobby then
	--So far no issue on lobby side was noticed.
else
	--Initialization of chair
	function ModuleMeta.Initialize(Object:Instance, StartInfo)
		local self = setmetatable({}, ModuleMeta)
		self.Owner = StartInfo.Owner
		self.Object = Object
		self.Occupants = StartInfo.Occupants or {}
		self.ClientAllowed = ClientAllowedActions

		self.Connections = {}

		local Seats = {}
		for _, Object in pairs(self.Object:GetDescendants()) do
			if CollectionService:HasTag(Object, "Seat") then
				table.insert(Seats, Object)
			end
		end

		return self
	end

	--handles sitting on chairs.
	function ModuleMeta:Sit(Occupant, Seat)
		if not Seat:IsDescendantOf(self.Object) then return false, "This seat is not from this chair" end
		if self.Occupants[Seat] then return false, "Already occupied" end
		for Seat, Person in self.Occupants do
			if Person == Occupant then
				self:Free(Occupant, Seat)
			end
		end
		if Occupant.Character.Humanoid:GetState() == Enum.HumanoidStateType.Seated then return false, "Already sitting" end
		self.Occupants[Seat] = Occupant
		local Root = Occupant.Character.HumanoidRootPart
		Occupant.Character:PivotTo(Seat.CFrame * CFrame.new(0, 1, 0))
		local Weld = Instance.new("WeldConstraint")
		Weld.Name = "SeatWeld"
		Weld.Part0 = Seat
		Weld.Part1 = Root
		Weld.Parent = Seat
		Remotes.CharacterEvent:FireClient(Occupant, "Sit", {State = true, Object = self.Object, Seat = Seat})
		local CurTime = DateTime.now().UnixTimestampMillis/1000
		return true
	end

	--handles standing up from chairs.
	function ModuleMeta:Free(Player, Seat)
		--print(Player, Seat, self.Object)
		if not (self.Occupants[Seat] or Seat:IsDescendantOf(self.Object)) then return false, "This seat is not from this chair" end
		if not self.Occupants[Seat] then return false, "Already free" end
		if self.Occupants[Seat] ~= Player then return false, "You are not sitting here" end
		self.Occupants[Seat] = nil
		if Seat:FindFirstChild("SeatWeld") then
			Seat.SeatWeld:Destroy()
		end
		Remotes.CharacterEvent:FireClient(Player, "Sit", {State = false})
		local CurTime = DateTime.now().UnixTimestampMillis/1000
		--print("Freed!")
		return true
	end

	--this function is called when chair gets destroyed.
	function ModuleMeta:Exit()
		local function DisconnectAll(Connections)
			for _, Connection in pairs(Connections) do
				if typeof(Connection) == "table" then
					DisconnectAll(Connection)
				else
					Connection:Disconnect()
				end
			end
		end
		for Seat, Occupant in self.Occupants do
			print("Freeing ", Occupant, "on seat:", Seat)
			self:Free(Occupant, Seat)
		end
		DisconnectAll(self.Connections)
		self = nil
	end
end

return ModuleMeta

Client side of chair handling:

local function ActionReact(Action, Data)
		if Action == "Ragdoll" then
			--handling of ragdolling.
		elseif Action == "Sit" then
			if self.CurrentCharacter and self.CurrentHumanoid:GetState() ~= Enum.HumanoidStateType.Ragdoll then
				--locks all HumanoidStateTypes, and sets it to Seated.
				self:ChangeHumanoidState(not Data.State, Enum.HumanoidStateType.Seated)
				self.CurrentHumanoid.Sit = Data.State
				if Data.State then
					self.Seat = {Object = Data.Object, Seat = Data.Seat}
				elseif self.Seat then
					local Success, Info = Libraries.ObjectController.Action("Chair/Free", self.Seat.Object, Player, self.Seat.Seat)
					if Success then
						self.Seat = nil
					end
				end
			end
		end
	end
	
	Remotes.CharacterEvent.OnClientEvent:Connect(ActionReact)

--... a lot of line codes later, unrelated to chairs ...

		--self.ActiveModule.IsJumping is true when spacebar is being held, or jump button on mobile.
		if self.Seat and self.ActiveModule.IsJumping then
			if not IsLobby and self.Seat.Object:GetAttribute("DataId") then
				--this line initializes chair on client side, to access it's functions. Used further to invoke server functions only.
				Libraries.ObjectController.Action("Chair/Initialize", self.Seat.Object, Libraries.CurrentItems.GetItem(self.Seat.Object:GetAttribute("DataId")))
			end
			--this line of code invokes server via my module and runs "Free" function of chair, shown above.
			local Success, Info = Libraries.ObjectController.Action("Chair/Free", self.Seat.Object, Player, self.Seat.Seat)
			if Success then
				self:ChangeHumanoidState(true, Enum.HumanoidStateType.Running)
				self.Seat = nil
			end
		end
		self.CurrentHumanoid.Jump = self.ActiveModule.IsJumping

I genuinely can’t understand why everyone who tried to sit on chair for longer than 3 seconds has chance to be sent to nil-zone. Maybe, anyone here has clues on what’s going on here?

J- Just use an instance named “Seat”… You dont need all this code…

2 Likes

I need all that code for chairs because of how complex entire system is.

Well, why cant you just use a Seat? It works fine, you can use custom animations, do things like activating it with proximity prompts, have things happen when a player sits in it, and even more…

1 Like
  1. My game is overally harder than 99.99% of all roblox games.
  2. I use custom part system for seats due to how permissions to sit on chair work.
  3. Chairs can break easilly.
  4. Seat has some replication issues, especially when I begin to mess with character, wearing metal balls on their legs, explode them with bombs, and dropping anvils on that all.
  5. Seat has fixed place of where player can be located. In all chair cases I wanted to set specific point of where user needs to be located.

  1. All NPCs use custom Humanoid system, which is incompatible with regular seats.
1 Like

Are you very sure that this is what actually happens, that their character’s parent gets set to nil, or is it just being killed?
Frankly the only reason I could suspect something breaking in the shown code, is that :Destroy()ing the weld somehow takes the Root with itself.
Otherwise it has to be other code somehow mistakingly reacting to the chair code incorrectly (what’s in ObjectController, and what does “Chair/Initialize” do?)

To track this behavior, I have added death messages. For nil-parenting, for :Destroy(), for health going to 0 and such. In all cases, reparenting to nil was printed.

ObjectController is main way I work with objects. Chair - name of object script, Initialize is action. In case of client, it returns simplest object possible which allows to fire remotes. In the case of server, it returns first shown script.

The root cause here is almost certainly that welding the character’s HumanoidRootPart to a non-Seat part is extremely fragile. When the weld is destroyed or if the character moves slightly before un-welding, the physics engine can try to move the HumanoidRootPart into an invalid position, and the character can get parented to nil (essentially Roblox deletes the model because the physics engine fails to resolve it).

2 Likes

So, thankfully to another bug report, I have accidently found reason of why this were occurring:

  1. Anchored objects have infinite AssemblyMass
  2. I have 2 events which create VectorForces. Their power depends on user character’s mass.
  3. If player were sitting on the chair, and he got this vector force, it will result in either inf or -inf forced constraint. As result, player gets teleported to nullzone.
  4. Due to how roblox handled this, Character reported that it was still in the same place, which made this debugging longer.
3 Likes

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