Issues With Managing Multiple NPC's Behavior With One Serverscript

This is a continuation of a previous post since the helper wasn’t responding anymore.

I have gotten NPCs to be able to go to random waypoints. However, they stutter and the script is constantly trying to move each of them incrementally but also changes their waypoints, too. But I want the NPCs to individually move simultaneously, and only change waypoint once they’ve reached it.

I also want to implement pathfinding, but I’m also a bit confused on that topic.

Adding MoveToFinished:Wait() to the wander function in the NPCManager module that tells the NPCs to go to the designated location makes the NPCs move individually to the locations which is not what I want.

There is also a problem where the ChangeState() function in that same module doesn’t actually work. Changing it from "Wandering" to "Idle" does nothing.

I’m not sure whether or not it’s that I just don’t understand the module script or something is wrong with the script. But I am lost on how I can fix this.

Serverscript:

local function GetNPC(identifier: number)
	return activeNPCs[identifier] or error(`{identifier} is not in activeNPCs`)
end

NPCManager.NPCs = {
	["Base Pedestrian"] = function(player, npc, actingNPC)
		local sound
		local outcome
		local AnimOutcome
		local Animating
		local humanoid = npc:WaitForChild("Humanoid")
		print("...")
		local waitingTime = math.random(3, 5)
		TurnToPlayer(player, npc, waitingTime)
		actingNPC:StopMoving()
		local decisionMade = math.random(1,2)
		if decisionMade == 1 then
			print("Sure thing!")
			outcome = 1
			sound = Var1SureSounds
			AnimOutcome = YesAnims
		else
			print("No way")
			outcome = 2
			sound = Var1refuseSounds
			AnimOutcome = NoAnims
		end
		local playing = sound[math.random(1,#sound)]
		local animatingT = AnimOutcome
		Animating = humanoid.Animator:LoadAnimation(animatingT)
		Animating.Priority= Enum.AnimationPriority.Action
		playing:Play()
		playing.Ended:Wait()
		task.wait(0.5)
		ReceivedEvent:FireClient(player, outcome)
		CurrentlySigning(actingNPC)
	end,
	["Cop"] = function(player, npc, gunActive, actingNPC)
		print("...")
		TurnToPlayer(player, npc, 4)
		print("Go away")
		local playing = Var1refuseSounds[math.random(1, #Var1refuseSounds)]
		playing:Play()
		playing.Ended:Wait()
		ReceivedEvent:FireClient(player, 3)
		CurrentlySigning(actingNPC)
	end,
	["Friendly Pedestrian"] = function(player, npc, actingNPC)
		print("...")
		TurnToPlayer(player, npc, 4)
		print("For sure!")
		local playing = Var1SureSounds[math.random(1, #Var1SureSounds)]
		playing:Play()
		playing.Ended:Wait()
		ReceivedEvent:FireClient(player, 1)
		CurrentlySigning(actingNPC)
	end,

}

local function onRemoteTriggered(player, npcID)
	print(player.Name .. "'s signal was received, now triggering script for " .. npcID.Name)
	local npc = game.Workspace:FindFirstChild(npcID.Name)
	if npc and NPCManager.NPCs[npcID.Name] then
		print("Script running!")
		print(player)
		print(npc)
		local actingNPC = GetNPC(npcID)
		actingNPC:StopMoving()
		actingNPC:ChangeState("Idle")
		NPCManager.NPCs[npcID.Name](player, npcID, actingNPC)
	else
		print(npcID.Name.. " was not a valid NPC")
		ReceivedEvent:FireClient(player, 4)
	end
end

AskedEvent.OnServerEvent:Connect(onRemoteTriggered)

for _, AddedNPC in CollectionService:GetTagged(InteractableNPC) do
	NPCAdded(AddedNPC)
end

CollectionService:GetInstanceRemovedSignal(InteractableNPC):Connect(NPCRemoved)
CollectionService:GetInstanceAddedSignal(InteractableNPC):Connect(NPCAdded)

while task.wait(0.1) do
	for _, npc in activeNPCs do
		npc:StopMoving()

		if npc:GetState() == "Wandering" then
			-- This NPC is meant to wander! Have it walk to a random point nearby.
			local random_point = Waypoints[math.random(1, #Waypoints)]
			npc:WalkTo(random_point.Position)
		end
	end
end

ModuleScript:

export type NPC_State = "Wandering" | "Idle" -- Fill this in with your desired states. It wont affect functionality, only autocompletion. Will make your life easier.

local randomSeed = Random.new()

local NPCManager = {}

-- Create a new class. This creates the NPC and all you will need to work with it.
local NPC = {}
NPC.__index = NPC

-- Now we can create our functions.
-- This is just for our use case scenario. Would likely be ineffecient in any other case.

-- Return the current Vector3 of the HumanoidRootPart
function NPC:GetPosition(): Vector3
	return self._Humanoid.RootPart.Position
end
-- Change the internal state of the NPC between "Wandering" and "Idle". Alternatively, Attributes can be used.
function NPC:ChangeState(new_state: NPC_State)
	self._state = new_state
end
-- Return the current self._state
function NPC:GetState(): NPC_State
	return self._state
end
-- Iterate through and disconnect all connections.
function NPC:CleanupAsync() -- You can use a Janitor module to help with cleanup.
	for index: number = 1, #self._connections do
		local connection = self._connections[index]

		connection:Disconnect()
	end
end
-- Reset the WalkToPoint so the Humanoid stops moving.
function NPC:StopMoving()
	self._Humanoid.WalkToPoint = Vector3.zero
end
-- Call :MoveTo on the Humanoid, printing off the result when `point` is reached.
function NPC:WalkTo(point: Vector3)
	self._Humanoid:MoveTo(point)
	self._Humanoid.MoveToFinished:Wait()
end

function NPCManager.new<NPC>(NPCModel: Model)
	local newNPC = {}
	setmetatable(newNPC, NPC) -- In layman's terms, we are adding the `NPC` table into this `newNPC` table, making the methods we constructed available to it.

	local function walkToFinished()
		print(`{newNPC._Model.Name} finished walking!`)
	end

	local Humanoid = NPCModel:FindFirstChildOfClass("Humanoid")

	newNPC._Humanoid = Humanoid -- I like to use an underscore for "do not touch" properties
	newNPC._Model = NPCModel
	newNPC._state = "Wandering"

	-- Connections
	local moveToConnection = Humanoid.MoveToFinished:Connect(walkToFinished)
	local destroyedConnection = NPCModel.Destroying:Once(function()
		newNPC:CleanupAsync()
	end)

	newNPC._connections = {
		moveToConnection,
		destroyedConnection
	}

	return newNPC
end

return NPCManager
1 Like

So, what is your problem with this code. I can only see that it will change active NPC’s direction every 0.1 sec without waiting them to finish its path.

Does that mean you want them to move as group?

2 Likes

I would recommend adding more print statements around. You should be able to tell us exactly what line of code isn’t working.

1 Like

It doesn’t move the NPCs individually, and even if it does, the NPCs change waypoints way before they reach their destination. Additionally, the state changes don’t wokr.

1 Like

It’s the change state one I believe.

But something about not being able to control multiple NPCs simultaneously

1 Like

Then add some print statements and see if that method runs and correctly updates the value. Then go back to where it was called and print the value after the method is called. Debugging is an incredibly important skill, and without us directly accessing the studio file this is the best method of solving your issue.