Fallen Character/Parts Recall Height [v1]

Fallen Character/Parts Recall Height

In response to the following request from the last AMA:

I’ve just created two different scripts for a non-built-in custom solution. Place either script into “ServerScriptService”. You’ll only need one of the scripts.

With either solution, I’d recommend setting your Workspace’s “FallenPartsDestroyHeight” property to a large negative number or running this in your command line:

workspace.FallenPartsDestroyHeight = -math.huge

You can also set a custom height for the recall by creating a number attribute with the correct name and value. Instructions are at the top of either script.


Player Characters Only:

The first script only works with player characters:

Model: Fallen Character Recall Height

Raw Script
--[[

For the best experience, do one of the following:

1. Change the Workspace's "FallenPartsDestroyHeight" to a large negative number,
   then add a number attribute to the Workspace. Name it "FallenCharacterRecallHeight"
   and set its value to "-500" or your desired height.

OR

2. Run the following code in your command line:

   workspace.FallenPartsDestroyHeight=-math.huge workspace:SetAttribute('FallenCharacterRecallHeight',-500)

]]

local fallenCharacterRecallHeight = workspace:GetAttribute('FallenCharacterRecallHeight') or workspace.FallenPartsDestroyHeight + 10

if not workspace:GetAttribute('FallenCharacterRecallHeight') and workspace.FallenPartsDestroyHeight <= -50000 then
	fallenCharacterRecallHeight = -500
end

local runService = game:GetService('RunService')

function Raycast(humanoidRootPart)
	local raycastParams = RaycastParams.new()
	raycastParams.FilterDescendantsInstances = {humanoidRootPart.Parent}
	local raycastCenter = workspace:Raycast(humanoidRootPart.Position,Vector3.new(0,-10,0),raycastParams)
	local raycastFront = workspace:Raycast(humanoidRootPart.Position+Vector3.new(0,0,2),Vector3.new(0,-10,0),raycastParams)
	local raycastBack = workspace:Raycast(humanoidRootPart.Position+Vector3.new(0,0,-2),Vector3.new(0,-10,0),raycastParams)
	local raycastLeft = workspace:Raycast(humanoidRootPart.Position+Vector3.new(2,0,0),Vector3.new(0,-10,0),raycastParams)
	local raycastRight = workspace:Raycast(humanoidRootPart.Position+Vector3.new(-2,0,0),Vector3.new(0,-10,0),raycastParams)
	local result
	if raycastCenter and raycastFront and raycastBack and raycastLeft and raycastRight then
		if raycastCenter.Position and raycastFront.Position and raycastBack.Position and raycastLeft.Position and raycastRight.Position then
			result = humanoidRootPart.CFrame
		end
	end
	raycastParams,raycastCenter,raycastFront,raycastBack,raycastLeft,raycastRight = nil
	return result
end

function ResetAssembly(basePart,networkOwner)
	basePart.AssemblyLinearVelocity = Vector3.new()
	basePart.AssemblyAngularVelocity = Vector3.new()
	pcall(function()
		if networkOwner then
			basePart:SetNetworkOwner(networkOwner)
		elseif basePart:GetNetworkOwnershipAuto() then
			basePart:SetNetworkOwnershipAuto()
		end
	end)
end

game:GetService('Players').PlayerAdded:Connect(function(player)
	player.CharacterAdded:Connect(function(character)
		local humanoidRootPart = character:WaitForChild('HumanoidRootPart')
		local cframe = humanoidRootPart.CFrame
		while character do
			if humanoidRootPart.Position.Y <= fallenCharacterRecallHeight then
				humanoidRootPart.CFrame = cframe
				for _,basePart in character:GetDescendants() do
					if basePart:IsA('BasePart') then
						ResetAssembly(basePart,player)
					end
				end
			elseif humanoidRootPart then
				local result = Raycast(humanoidRootPart)
				if result and cframe then
					cframe = result
				end
				result = nil
			end
			task.wait()
		end
		local removed
		removed = character:GetPropertyChangedSignal('Parent'):Connect(function()
			if not character.Parent then
				removed:Disconnect()
				character,humanoidRootPart,cframe,removed = nil
			end
		end)
	end)
end)

workspace:GetAttributeChangedSignal('FallenCharacterRecallHeight'):Connect(function()
	fallenCharacterRecallHeight = workspace:GetAttribute('FallenCharacterRecallHeight')
end)

workspace:SetAttribute('FallenCharacterRecallHeight',fallenCharacterRecallHeight)

All Physics Parts:

This second script will work with all physics parts, including models that have an unanchored PrimaryPart:

Model: Fallen Parts Recall Height

Raw Script
--[[

For the best experience, do one of the following:

1. Change the Workspace's "FallenPartsDestroyHeight" to a large negative number,
   then add a number attribute to the Workspace. Name it "FallenPartsRecallHeight"
   and set its value to "-500" or your desired height.

OR

2. Run the following code in your command line:

   workspace.FallenPartsDestroyHeight=-math.huge workspace:SetAttribute('FallenPartsRecallHeight',-500)

]]

local fallenPartsRecallHeight = workspace:GetAttribute('FallenPartsRecallHeight') or workspace.FallenPartsDestroyHeight + 10

if not workspace:GetAttribute('FallenPartsRecallHeight') and workspace.FallenPartsDestroyHeight <= -50000 then
	fallenPartsRecallHeight = -500
end

workspace:GetAttributeChangedSignal('FallenPartsRecallHeight'):Connect(function()
	fallenPartsRecallHeight = workspace:GetAttribute('FallenPartsRecallHeight')
end)

workspace:GetPropertyChangedSignal('FallenPartsDestroyHeight'):Connect(function()
	fallenPartsRecallHeight = workspace.FallenPartsDestroyHeight + 10
end)

workspace:SetAttribute('FallenPartsRecallHeight',fallenPartsRecallHeight)

local physicsObjects = {}

function GetModelFromPart(basePart)
	local model
	local parent = basePart
	while not model or parent == game do
		parent = parent.Parent
		if parent:IsA('Model') then
			model = parent
		end
	end
	parent,basePart = nil
	return model
end

function SetupInstance(instance)
	if instance:IsA('BasePart') and not instance.Anchored then
		local object = {}
		object.Instance = instance
		object.CFrame = instance.CFrame
		local model = GetModelFromPart(instance)
		if model then
			local primaryPart = model.PrimaryPart
			if primaryPart and not primaryPart.Anchored then
				object.Model = model
				object.CFrame = primaryPart.CFrame
			end
			model,primaryPart = nil
		end
		table.insert(physicsObjects,object)
	end
end

for _,instance in workspace:GetDescendants() do
	SetupInstance(instance)
end

workspace.DescendantAdded:Connect(function(instance)
	SetupInstance(instance)
end)

function ResetAssembly(basePart,networkOwner)
	basePart.AssemblyLinearVelocity = Vector3.new()
	basePart.AssemblyAngularVelocity = Vector3.new()
	pcall(function()
		if networkOwner then
			basePart:SetNetworkOwner(networkOwner)
		elseif basePart:GetNetworkOwnershipAuto() then
			basePart:SetNetworkOwnershipAuto()
		end
	end)
end

function Raycast(basePart,params)
	local raycastCenter = workspace:Raycast(basePart.Position,Vector3.new(0,-10,0),params)
	local raycastFront = workspace:Raycast(basePart.Position+Vector3.new(0,0,2),Vector3.new(0,-10,0),params)
	local raycastBack = workspace:Raycast(basePart.Position+Vector3.new(0,0,-2),Vector3.new(0,-10,0),params)
	local raycastLeft = workspace:Raycast(basePart.Position+Vector3.new(2,0,0),Vector3.new(0,-10,0),params)
	local raycastRight = workspace:Raycast(basePart.Position+Vector3.new(-2,0,0),Vector3.new(0,-10,0),params)
	local result
	if raycastCenter and raycastFront and raycastBack and raycastLeft and raycastRight then
		if raycastCenter.Position and raycastFront.Position and raycastBack.Position and raycastLeft.Position and raycastRight.Position then
			result = basePart.CFrame
		end
	end
	basePart,params,raycastCenter,raycastFront,raycastBack,raycastLeft,raycastRight = nil
	return result
end

workspace.DescendantRemoving:Connect(function(instance)
	local index = 0
	for _,object in physicsObjects do
		index+=1
		if object.Instance == instance then
			table.remove(physicsObjects,index)
			object.Instance = nil
			object.CFrame = nil
			object.Model = nil
			object = nil
		end
	end
	instance,index = nil
end)

while true do
	for _,object in physicsObjects do
		if not object.Instance.Anchored then
			if object.Instance.Position.Y < fallenPartsRecallHeight then
				local networkOwner
				pcall(function()
					if not object.Instance:GetNetworkOwnershipAuto() then
						networkOwner = object.Instance:GetNetworkOwner()
					end
				end)
				if object.Model then
					object.Model:SetPrimaryPartCFrame(object.CFrame)
					for _,basePart in object.Model:GetDescendants() do
						if basePart:IsA('BasePart') and not basePart.Anchored then
							ResetAssembly(basePart,networkOwner)
						end
					end
					modelCFrame,position = nil
				else
					object.Instance.CFrame = object.CFrame
					ResetAssembly(object.Instance,networkOwner)
				end
				networkOwner = nil
			else
				local params = RaycastParams.new()
				if object.Model then
					params.FilterDescendantsInstances = {object.Model}
				else
					params.FilterDescendantsInstances = {object.Instance}
				end
				local result
				local results
				pcall(function()
					if object.Model then
						result = Raycast(object.Model.PrimaryPart,params)
					else
						result = Raycast(object.Instance,params)
					end
				end)
				if result then
					object.CFrame = result
				end
				result = nil
			end
		end
	end
	task.wait()
end

This script also works with models that have an unanchored PrimaryPart (including player characters), otherwise it will treat the part as a normal part.

(This script will obviously be more costly than the other script due to it tracking all physics part rather than just the root parts of player characters. It worked fine for my tests of about 100 parts, but it could lead to issues if your experience has a lot more physics parts.)


Let me know if there are any bugs or other issues you come across.


Other Resources:

4 Likes

Raycast on every characters on a task.wait loop goes so hard

2 Likes

For the highest accuracy, however this could obviously be slowed down if desired. Raycasts aren’t actually very demanding at all. You could have thousands of raycasts a frame and not even notice. But if you want the maximum performance, it would be best to do it every certain number of frames, and shift it so that each character or part is spread out across all of the available frames per second.

Fun fact, you can also set it to 0/0!

1 Like