I needed a way to fetch all parts that are network-owned by the client, but Roblox doesn’t provide a built-in feature for this. To get around this, I created a script that sorts through the workspace to find and cache any owned parts dynamically. I’m sharing it here in case it helps others facing the same predicament.
Happy coding
--!strict
type TypeConnections = { [BasePart]: RBXScriptConnection? }
type TypeParts = { [BasePart]: boolean? }
local connections = {} :: TypeConnections
local parts = {} :: TypeParts
local UPDATE_RATE = 0.2
-- Determine which parts are owned by the client using ReceiveAge
-- https://create.roblox.com/docs/reference/engine/classes/BasePart#ReceiveAge
local function onUpdateLoop()
for part in pairs(parts) do
local value = part.ReceiveAge
if value ~= 0 then parts[part] = nil continue end
print("Owned by client", part.Name)
end
end
-- Cache basepart and listen for physics changes
local function onDescendantAdded(descendant: Instance)
local function onAnchoredChange(part: BasePart)
if part.Anchored or part:IsGrounded() then
parts[part] = nil
else
local add = part.AssemblyRootPart or part
parts[add] = true
end
end
if not descendant:IsA("BasePart") then return end
connections[descendant] = descendant:GetPropertyChangedSignal("Anchored"):Connect(function()
onAnchoredChange(descendant)
end)
onAnchoredChange(descendant)
end
-- Cleanup function that disconnects connections if any
local function onDescendantRemoving(descendant: Instance)
if not descendant:IsA("BasePart") then return end
parts[descendant] = nil
local connection = connections[descendant]
if connection then
connection:Disconnect()
connections[descendant] = nil
end
end
-- Connections
workspace.DescendantAdded:Connect(onDescendantAdded)
workspace.DescendantRemoving:Connect(onDescendantRemoving)
-- Initialise
for _, descendant in ipairs(workspace:GetDescendants()) do
onDescendantAdded(descendant)
end
task.spawn(function()
while true do
task.wait(UPDATE_RATE)
onUpdateLoop()
end
end)