How do I call method for a single NPC only?

I have a module script that handles NPCs in my game, and a server script that constructs npcs, and a local script that handles player interaction with the npc.
Everything works fine, but when I call the Purchase method from the server script, it calls that method for every single NPC in the workspace. Can anyone help me with this issue? It would be greatly appreciated

-- Server script that constructs a new npc every 5 seconds.
local NPC = require(script.Parent.NPCSHandler)
local PS = game:GetService("Players")

while task.wait(5) do
	for _,v in PS:GetPlayers() do
		local npc = NPC.New(v)
		
		game.ReplicatedStorage.Events.NPCPurchases.OnServerEvent:Connect(function(player,action)
			npc:Purchase(player,action)
		end)
		
	end
end
-- local script
RepS.Events.NPCPurchases.OnClientEvent:Connect(function(NPC, Obj)
	if Player ~= NPC.CustomerOf then return end
	local dialog = Instance.new("Dialog")
	local dialogChoice = Instance.new("DialogChoice")
	dialogChoice.UserDialog = "Sure"
	dialogChoice.Parent = dialog
	dialog.GoodbyeDialog = "Nah"
	dialog.InitialPrompt = `I want to buy {Obj.Name} for {ItemConfigs[Obj.Name].Price}$`
	dialog.Parent = NPC.Model.Head
	
	dialog.DialogChoiceSelected:Connect(function(player: Instance, Choice: Instance)
		if Choice.UserDialog == dialogChoice.UserDialog then
			Events.NPCPurchases:FireServer(true)
		else
			Events.NPCPurchases:FireServer(false)
		end
	end)
end)
-- module script that handles npc actions
local module = {}
local paths = {}
local returnpaths = {}

module.__index = module
local NPCConfigs = require(game.ReplicatedStorage.NPCs.Configs)
local PlotFunctions = require(game.ServerScriptService.PlotFunctions)
local PathS = game:GetService("PathfindingService")


local function RandomizeChance()
	local TotalWeight = 0
	for i,v in NPCConfigs do
		TotalWeight += v.AppearChance
	end
	
	local RandomNumber = math.random() * TotalWeight
	local CurrentWeight = 0
	
	for i,v in NPCConfigs do
		CurrentWeight += v.AppearChance
		if RandomNumber <= CurrentWeight then 
			return i
		end
	end
end

local function CalculatePaths()
	for _,v in game.Workspace.Shops:GetChildren() do
		local path = PathS:CreatePath()
		local success, error = pcall(function()
			path:ComputeAsync(game.Workspace.SpawnLocation.Position,v.NPCDestination.Position)
		end)
		
		if success and path.Status == Enum.PathStatus.Success then
			paths[v] = path:GetWaypoints()
		end
		
		local back = PathS:CreatePath()
		local success, error = pcall(function()
			back:ComputeAsync(v.NPCDestination.Position,game.Workspace.SpawnLocation.Position)
		end)

		if success and path.Status == Enum.PathStatus.Success then
			returnpaths[v] = back:GetWaypoints()
		end
	end
end

local function FindItemWithLargestPriceInRange(range, plot)
	local ItemConfigs = require(game.ReplicatedStorage.Placables.Configs)
	local plotObjects = plot.Objects
	local object 
	local largestnum = 0
	
	for i, v in pairs(plotObjects:GetChildren()) do
		if ItemConfigs[v.Name].Price > largestnum and ItemConfigs[v.Name].Price <= range then
			largestnum = ItemConfigs[v.Name].Price
			object = v
		end 
	end
	
	return object
end

local function FindItemWithSmallestPriceInRange(range, plot)
	local minval = range / 10
	local ItemConfigs = require(game.ReplicatedStorage.Placables.Configs)
	local plotObjects = plot.Objects
	local object 
	
	if minval <= 20 then
		minval = 20
	end
	
	local smallestnum = math.huge
	
	for i,v in pairs(plotObjects:GetChildren()) do
		if ItemConfigs[v.Name].Price < smallestnum and ItemConfigs[v.Name].Price >= minval then
			smallestnum = ItemConfigs[v.Name].Price
			object = v
		end
	end
	
	return object
	
end

CalculatePaths()

function module.New(player)
	
	if not PlotFunctions.GetPlot(player) then return end
	
	local SelectedNPC = RandomizeChance()
	local minval = NPCConfigs[SelectedNPC].PurchasingPower / 10
	
	if minval <= 20 then
		minval = 20
	end
	
	local self = setmetatable({
		Model = NPCConfigs[SelectedNPC].Model:Clone(),
		PurchasingPower = math.random(minval,NPCConfigs[SelectedNPC].PurchasingPower),
		PurchasePriority = NPCConfigs[SelectedNPC].PurchasePriority,
		Buys = NPCConfigs[SelectedNPC].Tendency,
		ObjectToBuy = nil,
		CustomerOf = player,
	},module)
	
	self:WalkIn()
	
	return self
end

function module:SelectObjectToBuy()
	local plot = PlotFunctions.GetPlot(self.CustomerOf)
	local plotObjects = plot.Objects
	local ItemConfigs = require(game.ReplicatedStorage.Placables.Configs)
	local tbl = {}

	for _,v in plotObjects:GetChildren() do
		local weight = 0
		if ItemConfigs[v.Name].Rarity == self.PurchasePriority then weight += 2 end
		if ItemConfigs[v.Name].Price <= self.PurchasingPower then weight += 10 end 

		if self.Buys == "Expensive" then
			if v == FindItemWithLargestPriceInRange(self.PurchasingPower, plot) then weight += 2 end
		elseif self.Buys == "Cheap" then
			if v == FindItemWithSmallestPriceInRange(self.PurchasingPower, plot) then weight += 2 end
		end
		
		tbl[v] = weight
	end
	
	local highestnum = 0
	local object

	for i,v in pairs(tbl) do
		if v > highestnum then highestnum = v object = i end
	end
	
	if ItemConfigs[object.Name].Price > self.PurchasingPower then object = nil end

	return object

end

function module:WalkIn()
	local plot = PlotFunctions.GetPlot(self.CustomerOf)
	local Humanoid: Humanoid = self.Model.Humanoid
	self.Model:PivotTo(CFrame.new(workspace.SpawnLocation.CFrame.X, 5, workspace.SpawnLocation.CFrame.Z)) 
	self.Model.Parent = plot.NPCs
	
	for _,waypoint in paths[plot] do
		Humanoid:MoveTo(waypoint.Position)
		Humanoid.MoveToFinished:Wait()
	end
	
	self:Inquire()
end

function module:Inquire()
	local dialog = Instance.new("Dialog")
	dialog.Parent = self.Model.Head
	dialog.GoodbyeDialog = "Nah"
	local plot = PlotFunctions.GetPlot(self.CustomerOf)
	local plotObjects = plot.Objects:GetChildren()
	local ItemConfigs = require(game.ReplicatedStorage.Placables.Configs)
	
	if plot and #plotObjects > 0 then
		self.ObjectToBuy = self:SelectObjectToBuy()
		
		if self.ObjectToBuy then
			game.ReplicatedStorage.Events.NPCPurchases:FireClient(self.CustomerOf, self, self.ObjectToBuy)
		else
			self:WalkAway()
		end
		
	else
		self:WalkAway()
	end
	
end

function module:WaitInLine()
	
end

function module:WalkAway()
	local plot = PlotFunctions.GetPlot(self.CustomerOf)
	local humanoid = self.Model.Humanoid

	for _,waypoint in returnpaths[plot] do
		humanoid:MoveTo(waypoint.Position)
		humanoid.MoveToFinished:Wait()
	end

	self:Destroy()
end

function module:Purchase(player, action)
	local ItemConfigs = require(game.ReplicatedStorage.Placables.Configs)
	
	if action == true and self.ObjectToBuy then
		PlotFunctions.Delete(player, self.ObjectToBuy)
		player.leaderstats.Money.Value += ItemConfigs[self.ObjectToBuy.Name].Price
		self:WalkAway()
	else 
		self:WalkAway()
	end
	
end

function module:Destroy()
	self.Model:Destroy()
	self = nil
end


return module

2 Likes

The issue you are running into is because every NPC is connecting to the same event, and you are giving no additional information to clarify which NPC it should happen for.

To fix this, I would make a RemoteEvent to stick inside of each NPC when you create them. Then you can change all code which uses the event in ReplicatedStorage to instead use the event you just created inside of the NPC.

This setup makes it so that each NPC ‘owns’ a Remote

I will show you if you are confused about this setup

1 Like

Thanks a lot for the solution, ill try it rn, but i have to ask if this could affect performance if there are a lot of npcs?

No actually, doing it the way you are currently is more costly. This is because you have many connections to the same event, and they all have to fire whenever you use it. It is more performant to split up the work

1 Like