Humanoid moving connection

I’m having a problem with moving a character by the humanoid. Lets use this example.

Player A: Buys character

Player B: Buys same character

Character goes to player B’s base. BUT, it does not stay there. It tries to go to Player A’s base.

It should not do that and instead should stay at player B’s base.
I think that it might be because of how I am handling moving characters to player bases but I am not exactly sure.

Code handling moving characters

function SpawnService.MoveToPlayerBase(characterModel: Model, targetPosition: Vector3)
	if activeNPCs[characterModel] then
		task.cancel(activeNPCs[characterModel])
		activeNPCs[characterModel] = nil
	end

	local humanoid = characterModel:WaitForChild("Humanoid")
	humanoid:MoveTo(characterModel.HumanoidRootPart.Position)
	local targetCFrame = CFrame.new(targetPosition)
	MoveNPC(targetCFrame, characterModel)
	SpawnService.UnAnimate(characterModel.Humanoid.Animator)
end

function MoveNPC(targetCFrame: CFrame, Character: Model)
	local HRP = Character.HumanoidRootPart
	repeat
		task.wait()
		if not Character.Humanoid then return end
		Character.Humanoid:MoveTo(targetCFrame.Position)
	until not Character.Parent or (HRP.Position - Vector3.new(targetCFrame.Position.X, HRP.Position.Y, targetCFrame.Position.Z) ).magnitude <= 1
end

code handling buying character

function BuyService:BuyCharacter()
	self.Prompt.Triggered:Connect(function(plr)
		pendingPlayerPurchases[plr.UserId] = (pendingPlayerPurchases[plr.UserId] or 0) + 1
		local function cleanup()
			if pendingPlayerPurchases[plr.UserId] then
				pendingPlayerPurchases[plr.UserId] = pendingPlayerPurchases[plr.UserId] - 1
			end
		end

		local playerBase = Bases:FindFirstChild(plr.Name)
		if not playerBase then
			warn("Could not find player base for " .. plr.Name)
			cleanup()
			return
		end

		local charactersFolder = playerBase:FindFirstChild("Characters")
		local bedsFolder = playerBase and playerBase:FindFirstChild("Beds")

		if not charactersFolder or not bedsFolder then
			warn("Base for " .. plr.Name .. " is missing a Characters or Beds folder.")
			cleanup()
			return
		end
		
		local currentCharacterCount = #charactersFolder:GetChildren()
		local maxCharacterSlots = #bedsFolder:GetChildren()
		local pendingCount = pendingPlayerPurchases[plr.UserId]
		if (currentCharacterCount + pendingCount) > maxCharacterSlots then
			warn("CANT BUY: SLOTS ALL FULL (including pending purchases) for " .. plr.Name)
			cleanup() 
			return
		end

		local charData = CharacterData[self.Model.Name]
		local plrProfile = DataMethods.GetProfile(plr)

		if plrProfile.Data.Tix >= charData.Cost then
			DataMethods.RemoveTix(plr, charData.Cost)

			HidePromptEvent:FireAllClients(plr, self.Prompt)
			local PadToMove = playerBase:FindFirstChild("CollectPad", true)
			SpawnService.MoveToPlayerBase(self.Model, PadToMove.Position)

			local characterPad = BaseService.AddCharacterToBase(self.Model, playerBase)
			if characterPad then
				local newGenerator = MoneyService.new(plr, self.Model, characterPad.Collect, plrProfile, 0)
				if not DataMethods.playerMoneyServices[plr] then
					DataMethods.playerMoneyServices[plr] = {}
				end
				table.insert(DataMethods.playerMoneyServices[plr], newGenerator)
				print("MoneyService created for new character: " .. self.Model.Name)
			else
				warn("Could not find characterPad for " .. self.Model.Name .. ", MoneyService not started.")
			end
			self.Model.Parent = playerBase.Characters

			self.Prompt:Destroy()
		else
			warn(plr.Name .. " does not have enough Tix for " .. self.Model.Name)
		end
		cleanup()
	end)
end

Hi there! I looked over your code and I think I understand what’s happening with your character moving back to Player A’s base instead of staying at Player B’s. Here’s a breakdown of the likely cause and how to address it.

The Likely Issue: Shared Character Instance

From what I can see in your code, it looks like both players are trying to buy and move the same Model instance (self.Model).

In BuyService:BuyCharacter(), you’re passing self.Model to SpawnService.MoveToPlayerBase() and then later reparenting it with self.Model.Parent = playerBase.Characters.

That means:

Player A buys the character — it starts moving to their base.
Player B buys the same characterself.Model is reused, moved again, and reparented to Player B’s base.
• But because MoveToPlayerBase() uses the same instance, and any movement task that was previously running is canceled using task.cancel(activeNPCs[characterModel]), you end up with conflicting movement behavior.

So the character always tries to follow the last MoveToPlayerBase() call, regardless of which player bought it.

Recommended Fix: Clone the Character Model

Instead of using the same instance of self.Model, you should clone it for each player who buys the character.

Here’s how to fix it in your BuyCharacter() function:

local clonedModel = self.Model:Clone()
clonedModel.Name = self.Model.Name -- Optional: keep the same name
clonedModel.Parent = workspace -- or wherever you want to temporarily place it

Then update this line:

SpawnService.MoveToPlayerBase(clonedModel, PadToMove.Position)

And later:

self.Model.Parent = playerBase.Characters

Should become:

clonedModel.Parent = playerBase.Characters

And finally, use clonedModel wherever you reference the character after that point (e.g. in MoneyService.new()).

Other Notes on Movement Code

Your MoveNPC function runs in a loop and calls Humanoid:MoveTo() on each iteration. That’s generally not needed — Humanoid:MoveTo() already handles the movement internally.

Instead of looping and repeatedly calling MoveTo(), consider using the Humanoid.MoveToFinished event:

function MoveNPC(targetCFrame: CFrame, Character: Model)
	local humanoid = Character:FindFirstChild("Humanoid")
	if humanoid then
		humanoid:MoveTo(targetCFrame.Position)
		humanoid.MoveToFinished:Wait()
	end
end

This avoids redundant MoveTo calls and unnecessary resource usage.

:white_check_mark: Summary

• Clone the character model before assigning it to a player.
• Avoid reusing the same character instance for multiple players.
• Clean up the movement code by relying on MoveToFinished instead of a custom loop.

This should fix the problem of the character “returning” to the previous owner’s base.

Let me know if you want help modifying your code to handle cloning or anything else!

The players should be able to buy the same model. If you play steal a brainrot (inspiration for this system), they spawn characters on a conveyor and you can buy said character. But, it is not exclusive to whoever buys it first. Until it reaches a players base, anyone can buy it.

I see, in this case try using something like:

  1. Track character ownership
    Add a tag, attribute, or property to store which player currently owns or is moving the character.

Example:

characterModel:SetAttribute("OwnedBy", plr.UserId)
  1. Check for ownership before allowing a new purchase

In BuyCharacter, before starting the move:

local ownedBy = self.Model:GetAttribute("OwnedBy")
if ownedBy and ownedBy ~= plr.UserId then
	warn("Character already owned by another player.")
	return
end
  1. Set ownership when a player buys the character

Right after validating the purchase, before calling MoveToPlayerBase:

self.Model:SetAttribute("OwnedBy", plr.UserId)

You might also want to lock the prompt or destroy it after a successful purchase, so it can’t be triggered again. That helps avoid race conditions.

Optional Improvement: Use a “State” System

You could also track character states like:

"Idle" (on conveyor, unowned)
"Claimed" (someone is buying it and it’s moving)
"Owned" (in a player base)

Then, in BuyCharacter, you only allow the purchase if state == "Idle".