NPC NetworkOwnership Failing for no apparent reason?

I’m facing a difficult problem with my NPC system.

My NPC system works by spawning the NPC on server and then setting the Network Ownership of the NPC to the Player that I want to control the NPC.

The NPCs are moved around by LinearVelocity manipulation on Client and AlignOrientation to make the NPC face towards my selected direction. (I’m not sure if this affects the NPC at network ownership)

This all works fine until I change the Scale of my NPC from 1 to 2 (The model). The NPC flashes once and then NetworkOwnership is lost.

Here is the main NPC handler:

-- THIS IS THE FUNCTION THAT SPAWNS THE NPC

function NPCHandler.LoadAI(NPC:Model, AIName:string, NetworkOwner:Player)
	if RunService:IsClient() then
		local Handler = ItemsModulesTbl[AIName]
		if Handler~=nil then
			Handler.LoadAI(NPC, NetworkOwner)
		end
	end
	if RunService:IsServer() then
		if typeof(NetworkOwner)=="Instance" then
			if NetworkOwner:IsA("Player") then
				local Handler = ItemsModulesTbl[AIName]
				if Handler~=nil then
					local Halted = false
					local Loaded = false
					Handler.Setup(NPC, NetworkOwner, function()
						if Halted==true then return end
						-- Done loading
						Loaded = true
						NPC:SetAttribute("AI", AIName)
						ClientRemote:FireAllClients({ST = "LoadAI", N = NPC, A = AIName, O = NetworkOwner})
					end)
					task.delay(3, function() -- No this is not the problem (I already commented it out and nothing fixed)
						if Loaded==false then
							Halted = true
							Handler.HaltSetup(NPC)
							NPCHandler.LoadAI(NPC, AIName, NetworkOwner)
						end
					end)
				end
			end
		end
	end
end

Now this next module is included for EVERY NPC

-- THIS IS THE MODULE THAT EACH NPC HAS WHICH IS SLIGHTLY DIFFERENT PER NPC SETUP.
function CurrentHandler.HaltSetup(NPC:Model) -- Yes I made sure this is NEVER called
	if NPC~=nil then
		 if NPC.Parent~=nil then
			if LoadingThreads[NPC]~=nil then
				task.cancel(LoadingThreads[NPC])
				LoadingThreads[NPC] = nil
				NPC:SetAttribute("NetworkOwner", "")
				local HRP = NPC:FindFirstChild("HumanoidRootPart") :: BasePart
				if HRP~=nil then
					HRP:SetNetworkOwner(nil)
				end
			end
		 end
	end
end -- Halt setup never prints so it isn't this affecting my NPCs

function CurrentHandler.Setup(NPC:Model, NetworkOwner:Player, Loaded)
	if NPC~=nil then
		if NPC.Parent~=nil then
			if RunService:IsServer() then
				LoadingThreads[NPC] = task.spawn(function()
					local HRP = NPC:WaitForChild("HumanoidRootPart") :: BasePart
					local Humanoid = NPC:WaitForChild("Humanoid") :: Humanoid

					if NetworkOwner~=nil then
						NPC:SetAttribute("NetworkOwner", NetworkOwner.Name)
						HRP:SetNetworkOwner(NetworkOwner)
						Loaded()
					end
				end)
			end
		end
	end
end


function CurrentHandler.LoadAI(NPC:Model, NetworkOwner:Player)
	if RunService:IsServer() then warn("AI is made to be used on Clients!") return end
	
	local FoundActor = NPC:FindFirstChildOfClass("Actor")
	if FoundActor~=nil then
		warn("Prevented AI Duplication [CLIENT]")
		FoundActor:Destroy()
	end
	
	local AI_LocalScript = script:WaitForChild("Actor"):Clone() -- This is where I put my AI inside the NPC.
	AI_LocalScript.Parent = NPC
	AI_LocalScript:FindFirstChildOfClass('Script').Enabled = true
end

Lastly, my AI script (the script which handles the pathfinding), does not have any issues with it so I won’t list it, It’s also client so I refuse to believe that its the one causing the problem

REMEMBER! – The NPC works COMPLETELY fine when the scale is 1.

VIDEOS SO YOU CAN WATCH THE PROBLEM:

https://youtu.be/wcGPmx-02eg – Working NPC [Scale 1]

https://youtu.be/VDkTFblQtE8 – Broken NPC [Scale 2]

(When the color turns white it means the NetworkOwnership was lost)

1 Like

Have you tried to track what happens here? Specifically if the HaltSetup function is being called. This seems to be the only place where you explicitly take away network ownership yourself. This way we can figure out if it’s actually the engine that takes away the network ownership.

Yep, I put prints there to make sure that wasn’t being called and it doesn’t ever print, the network ownership just kind of goes away on its own.

Scaling changes the mass of the parts, try making them massless (although this may break them altogether since I don’t know exactly how they work)

Mass doesn’t affect it at all :pensive_face:

I edited the mass to what it would normally be at Scale 1 and it doesn’t change anything.

If I make everything massless he just becomes too floppy. (not what i want, so i keep the HRP not massless)

Could be humanoid hip height is too low

Re-apply the network ownership after scaling

No, I scaled it before running the game, so the default size of the NPC would be 2 before I spawn it in.

I don’t manipulate the scale at all while running the game only through manual typing in studio.

The hipheight doubles itself with model sizing properly. So that isn’t the problem

The regular NPC hipheight is fine too.

Then check the pathfinding if it prints the points. Check if npc isnt stuck on ground

Maybe try adding waits of like 5 seconds??

Certain changes do cause the loss of network ownership (such as anchoring, welding to a different assembly, etc.). I would also check if your HaltSetup function is causing it, as it does set the network owner back to nil (server) and runs 3 seconds after Setup.

1 Like

Set the Scale on the Model to 2, then press that arrow button that pops up on hovering over the Scale property. That will make/map the size to Scale, like making 2 perceived as 1. The property will then update to show 1 with the same size you set.

If that fixes the issues, it’s a Roblox bug or intentional but odd behavior. If that doesn’t fix it, the code is the problem in some way I think.

If this is confusing, I can make a video around when I wake up.

I don’t see why you don’t use your setup function to adjust the NPC before spawning it for computing.

Network ownership itself is very unpredictable so you shouldn’t do anything on the server to the model once you’ve given ownership to the client.

Alternatively, if you want to make sure the network ownership stays consistent you would need to call getnetworkowner in a loop and resetting it everytime.

Side note, you shouldn’t copy-paste scripts under models. Local scripts especially, run them in starter player or replicated first just for security.

Great Idea!

I sadly already made sure it wasn’t being called anywhere though with prints :sob:

I also don’t weld, rig, anchor the NPC at all.

Never has and never does print the HaltSetup thing.

The issue isn’t the NPC getting stuck, its him losing NetworkOwnership

When the NPC loses NetworkOwnership of the client the NPC AI just breaks since its handled on client.

The server doesn’t manipulate the Rig after I give network ownership to the client.

I’m using Parallel Lua U and this is the most performant way to have the NPCs running as smoothly as possible (copy and pasting the script)

I don’t size the model inside any scripts, I sized it by hand in studio and then I just clone it with it already having Scale 2. I feel like the server takes away network ownership if a part gets too big, I’m not sure.

Edit: I don’t care about anti-exploit since exploiting in my game is pointless either (single-player)

I understand what you mean and I tried this before.

It reacts the same way even if the Scale says 1 (even though I set it to 2 and pressed the arrow)

1 Like

Then obviously it’s bound to be unstable if you edit the rig’s physical properties unconventionally with studio, especially after assigning network ownership.

Like I said, if you want to guarantee the network owner stays as the client in case of random Roblox engine interactions, you should just run getnetworkowner() in a loop and reassigning back to the client if it’s ever nil.

if hrp:GetNetworkOwner() ~= plr then
    hrp:SetNetworkOwner(plr)
end

The point of running them on client is to prevent putting any loops or repeating checks on server. So I need a solution that doesn’t involve spamming network ownership on server.

Changing the physical properties of something BEFORE running the game is most conventional way to do it too.