Help with miner npcs targetting the same ore

That would take a long time to do, I would have to replace all of my variables like humrp and stuff with, for example, walkToOre(humrp,hum,etc.), would it be worth it?

you wouldn’t have to do that. This line for example has a getNearestOre().Main.Position call that should just be nearest.Main.Position. You need only a careful find-and-replace.

path:ComputeAsync(humrp.Position, nearest.Main.Position + ((humrp.Position-getNearestOre().Main.Position).Unit * 6))
1 Like

this is what i think it should look like

ocal ReplicatedStorage = game:GetService('ReplicatedStorage')
local Players = game:GetService('Players')
local CollectionService = game:GetService('CollectionService')
local PathfindingService = game:GetService('PathfindingService')
local RaycastHitbox = require(ReplicatedStorage.RaycastHitboxV4)

local MinerNumber = 0

local function getTags(model)
			local tags = 0
			for _,tag in pairs(model:GetChildren()) do
				if string.find(tag.Name,'Miner') then
					tags += 1
				end
			end
			return tags
		end

local function getNearestOre()
			local Ores = workspace.Ores:GetChildren()
			local closestOre = nil
			local closestDistance = nil
			for _, Ore in pairs(Ores) do
				local my_distance = (Ore.Main.Position - humrp.Position).Magnitude
				if CollectionService:HasTag(Ore, "ToBeMined") then
					continue
				elseif closestOre == nil then -- nothing to compare add default
					closestOre = Ore
					closestDistance = my_distance
				elseif Ore.Main and closestDistance > my_distance then -- default was set, time to compare
					closestOre = Ore
					closestDistance = my_distance
				end
			end
			print(closestOre)
			return closestOre
		end

local function breakOre()
			if Mining == false then
				Mining = true
				Miner:SetPrimaryPartCFrame(CFrame.new(humrp.Position,getNearestOre().Main.Position))
				MiningAnim:Play()
				newHitbox:HitStart()

				-- I apologize to my future self on my terrible readability on this code

				newHitbox.OnHit:Connect(function(hit)
					if hit:FindFirstAncestor('Ores') then
						local OreHealth = hit.Parent.Health.Value
						if OreHealth >= 2 then
							hit.Parent.Health.Value -= 1
							Miner.Handle.Hit:Play()
							local Particles = script.Smoke:Clone()
							delay(0.3,function()
								local newPart = Instance.new('Part')
								newPart.Transparency = 1
								newPart.Anchored = true
								newPart.Position = Miner.Handle.Attachment.WorldPosition
								newPart.Parent = Miner
								Particles.Parent = newPart
								delay(0.5,function()
									Particles.Enabled = false
									game:GetService('Debris'):AddItem(newPart,1)
								end)
							end)
						else
							Miner.Handle.Break:Play()
							for _,ore in pairs(hit.Parent:GetChildren()) do
								if ore.Name == 'Ore' then
									ore.Anchored = false
									ore.Parent = workspace
									ore.Touched:Connect(function(Hit)
										if Players:GetPlayerFromCharacter(Hit.Parent) then
											require(ReplicatedStorage.FactoryResources).Resources[ore.Name] += 1
										end
									end)
								end
							end
							if hit.Parent:IsA('Model') and hit.Parent.Name ~= 'Workspace' then
								hit.Parent:Destroy()
							end
						end
					end
				end)

local function walkToOre()
			local path = PathfindingService:CreatePath()

			local nearest = getNearestOre()
			if nearest == nil then
				return
			end
			CollectionService:AddTag(nearest, "ToBeMined")

			local success, errorMessage = pcall(function()
				--path:ComputeAsync(humrp.Position, workspace.FrontOfFactory.Position + Vector3.new(math.random(1,10),math.random(1,10),math.random(1,10)))
				path:ComputeAsync(humrp.Position, nearest.Main.Position + ((humrp.Position-getNearestOre().Main.Position).Unit * 6))
			end)
			WalkAnim:Play()
			if success and path.Status == Enum.PathStatus.Success then
				local waypoints = path:GetWaypoints()
				for _,waypoint in pairs(waypoints) do

					if waypoint.Action == Enum.PathWaypointAction.Jump then
						hum:ChangeState(Enum.HumanoidStateType.Jumping)
					end

					if nearest and (humrp.Position - nearest .Main.Position).Magnitude <= 10 then
						breakOre(nearest)
					end

					hum:MoveTo(waypoint.Position)
					hum.MoveToFinished:Wait()
				end
			end

			WalkAnim:Stop()
			while task.wait(0.5) do
				walkToOre()
			end
		end
		walkToOre()
	end)

then

CollectionService:AddTag(ReplicatedStorage.AI.Miner,'Miner')

CollectionService:GetInstanceAddedSignal('Miner'):Connect(function(Miner)
spawn(function()
		Miner.Name = Miner.Name..tostring(MinerNumber)
		MinerNumber += 1
		local humrp = Miner.HumanoidRootPart
		local hum = Miner.Humanoid
		local WalkAnim = hum:LoadAnimation(script.Parent.Anims.walk.WalkAnim)
		local MiningAnim = hum:LoadAnimation(script.Parent.Anims.Swing)
		local Mining = false
		local Params = RaycastParams.new()
		Params.FilterDescendantsInstances = {Miner,workspace.OreSpawningArea} --- remember to define our character!
		Params.FilterType = Enum.RaycastFilterType.Blacklist
		local newHitbox = RaycastHitbox.new(Miner.Handle)
		newHitbox:SetPoints(Miner.Handle,{Vector3.new(0,0,2)})
		newHitbox.RaycastParams = Params
		newHitbox.DetectionMode = RaycastHitbox.DetectionMode.PartMode



call functions in same order

end)




1 Like

The script does seem to work a lot better now!

local ReplicatedStorage = game:GetService('ReplicatedStorage')
local Players = game:GetService('Players')
local CollectionService = game:GetService('CollectionService')
local PathfindingService = game:GetService('PathfindingService')
local RaycastHitbox = require(ReplicatedStorage.RaycastHitboxV4)

local MinerNumber = 0

CollectionService:AddTag(ReplicatedStorage.AI.Miner,'Miner')

CollectionService:GetInstanceAddedSignal('Miner'):Connect(function(Miner)
	spawn(function()
		Miner.Name = Miner.Name..tostring(MinerNumber)
		MinerNumber += 1
		local humrp = Miner.HumanoidRootPart
		local hum = Miner.Humanoid
		local WalkAnim = hum:LoadAnimation(script.Parent.Anims.walk.WalkAnim)
		local MiningAnim = hum:LoadAnimation(script.Parent.Anims.Swing)
		local Mining = false
		local Params = RaycastParams.new()
		Params.FilterDescendantsInstances = {Miner,workspace.OreSpawningArea} --- remember to define our character!
		Params.FilterType = Enum.RaycastFilterType.Blacklist
		local newHitbox = RaycastHitbox.new(Miner.Handle)
		newHitbox:SetPoints(Miner.Handle,{Vector3.new(0,0,2)})
		newHitbox.RaycastParams = Params
		newHitbox.DetectionMode = RaycastHitbox.DetectionMode.PartMode

		local function getNearestOre()
			local Ores = workspace.Ores:GetChildren()
			local closestOre = nil
			local closestDistance = nil
			for _, Ore in pairs(Ores) do
				local my_distance = (Ore.Main.Position - humrp.Position).Magnitude
				if CollectionService:HasTag(Ore, "ToBeMined") then
					continue
				elseif closestOre == nil then -- nothing to compare add default
					closestOre = Ore
					closestDistance = my_distance
				elseif Ore.Main and closestDistance > my_distance then -- default was set, time to compare
					closestOre = Ore
					closestDistance = my_distance
				end
			end
			return closestOre
		end

		local function breakOre(nearest)
			if Mining == false then
				Mining = true
				Miner:SetPrimaryPartCFrame(CFrame.new(humrp.Position,nearest.Main.Position))
				MiningAnim:Play()
				newHitbox:HitStart()

				-- I apologize to my future self on my terrible readability on this code

				newHitbox.OnHit:Connect(function(hit)
					if hit:FindFirstAncestor('Ores') then
						local OreHealth = hit.Parent.Health.Value
						if OreHealth >= 2 then
							hit.Parent.Health.Value -= 1
							Miner.Handle.Hit:Play()
							local Particles = script.Smoke:Clone()
							delay(0.3,function()
								local newPart = Instance.new('Part')
								newPart.Transparency = 1
								newPart.Anchored = true
								newPart.Position = Miner.Handle.Attachment.WorldPosition
								newPart.Parent = Miner
								Particles.Parent = newPart
								delay(0.5,function()
									Particles.Enabled = false
									game:GetService('Debris'):AddItem(newPart,1)
								end)
							end)
						else
							Miner.Handle.Break:Play()
							for _,ore in pairs(hit.Parent:GetChildren()) do
								if ore.Name == 'Ore' then
									ore.Anchored = false
									ore.Parent = workspace
									ore.Touched:Connect(function(Hit)
										if Players:GetPlayerFromCharacter(Hit.Parent) then
											require(ReplicatedStorage.FactoryResources).Resources[ore.Name] += 1
										end
									end)
								end
							end
							if hit.Parent:IsA('Model') and hit.Parent.Name ~= 'Workspace' then
								hit.Parent:Destroy()
							end
						end
					end
				end)
				MiningAnim.Stopped:Wait()
				task.wait(0.5)
				newHitbox:HitStop()
				Mining = false
			end
		end

		local function walkToOre()
			local path = PathfindingService:CreatePath()

			local nearest = getNearestOre()
			if nearest == nil then
				return
			end
			CollectionService:AddTag(nearest, "ToBeMined")

			local success, errorMessage = pcall(function()
				--path:ComputeAsync(humrp.Position, workspace.FrontOfFactory.Position + Vector3.new(math.random(1,10),math.random(1,10),math.random(1,10)))
				path:ComputeAsync(humrp.Position, nearest.Main.Position + ((humrp.Position-nearest.Main.Position).Unit * 6))
			end)
			WalkAnim:Play()
			if success and path.Status == Enum.PathStatus.Success then
				local waypoints = path:GetWaypoints()
				for _,waypoint in pairs(waypoints) do

					if waypoint.Action == Enum.PathWaypointAction.Jump then
						hum:ChangeState(Enum.HumanoidStateType.Jumping)
					end

					if nearest and (humrp.Position - nearest .Main.Position).Magnitude <= 10 then
						breakOre(nearest)
					end

					hum:MoveTo(waypoint.Position)
					hum.MoveToFinished:Wait()
				end
			end

			WalkAnim:Stop()
			while task.wait(0.5) do
				walkToOre()
			end
		end
		walkToOre()
	end)
end)

local clone = ReplicatedStorage.AI.Miner:Clone()
clone.Parent = workspace
task.wait(0.1)
local clone = ReplicatedStorage.AI.Miner:Clone()
clone.Parent = workspace
clone:SetPrimaryPartCFrame(CFrame.new(Vector3.new(-150.777, 4, 50.515)))
task.wait(0.1)
local clone = ReplicatedStorage.AI.Miner:Clone()
clone.Parent = workspace
clone:SetPrimaryPartCFrame(CFrame.new(Vector3.new(-150.777, 4, 50.515)))
task.wait(0.1)
local clone = ReplicatedStorage.AI.Miner:Clone()
clone.Parent = workspace
clone:SetPrimaryPartCFrame(CFrame.new(Vector3.new(-150.777, 4, 50.515)))

However it still runs into the issue of this:


Do I pcall it or check if Main is a child of Ore?

I’m afraid that there are just too many issues with this


I would need to manually define all of the variables like local function breakOre(Mining,Miner,MiningAnim,newHitbox) for every single function, it would just be too much work and I don’t think that the performance change would be significant enough.


I still run into the issue of half of the miners just being idle and the other half wandering around aimlessly.

May be good to add CollectionService:RemoveTag(nearest, "ToBeMined") when they are hit inside breakOre. There is certainly some part here that needs the tag removed, maybe if walking fails, think of ToBeMined as a debounce to be toggled when selecting and when broken/errored.

1 Like

I was about to say stuff about me deleting the model later on in the script but I added that in and it actually did work, yet still have this issue sadly:


would it be wise to pcall it as I said in a previous reply? or is this avoidable somehow.

I feel like it is something to do with this line of code
image
and definitely this line of code, as the error message calls this as being the issue

Sorry! Yeah I am not sure why that would be happening, is Main a part of all Stone? which line is that?

1 Like

Oh I should’ve maybe mentioned this at the start, but here is how a single ore model looks like, all of them are identical apart from their values and the actual name of the model.
image
Edit: Main is the, well, main part of the ore, the stone/slate mesh thing in the centre.

Well I certainly do not know why that is happening then, if nearest == nil or nearest.Main == nil then should capture that problem. I say try GetPivot instead, it works with both models and parts, maybe the space between nearest and .Main i really don’t know lol.

(humrp.Position - nearest:GetPivot().Position).Magnitude <= 10

Maybe nearest is destroyed at that point after walking takes some time, since :Destroy only sets and locks the parent to nil we should check that too

if nearest and nearest.Parent and (humrp.Position - nearest:GetPivot().Position).Magnitude <= 10 then
1 Like

Wow that actually somehow fixed all of the problems I had before, thanks, I will do a stress test on this and see if it breaks.

1 Like

Man this post is going on for far too long, i’ve found a few issues but no error messages for them:


Sometimes, the miners just stop completely as if the script has been removed from them and through some print statements, i’ve figured out that this code stops them:

			if nearest == nil or nearest.Main == nil then
				return
			end

I tried to make this call the walkToOre statement again when it returns but that just ended in a stack overflow and studio nearly crashing.

Edit: I just changed the return to nearest = getNearestOre(), thanks for all the help man!

Edit2: the moment I typed the first edit in I checked studio and saw this:
https://gyazo.com/6069f96223d72c026202b6ef7dc4eca4
i’m also definitely not playing Unit: Classified [TECH DEMO] in the background.

@gertkeno I now put a print in
image
this and
image
pain.

You should probably do, The wait is important like it is in the while loop at the end of walkToOre, speaking of which you should remove that while loop. The function is already recursive and will degrade performance over time if you loop and recurse at the same time.

if nearest == nil or nearest.Main == nil then
	task.wait(0.5)
	walkToOre()
	return -- still important! return prevents multiple runs while recursing
end

-- later on ...
WalkAnim:Stop()
-- while task.wait(0.5) do -- delete this
task.wait(0.5)
walkToOre()
1 Like

image
still just inactivating after a while, if I put a print in between the task.wait(0.5) walkToOre() return then it prints nothing throughout the entire time, would you like a place download?

sure I’ll take a place download. It should be printing nothing as if nearest == nil then should be the one catching this, it’s the case where it cannot find a closest ore that isn’t tagged.

– place download link removed to not let anyone else reading this to copy my game
edit: miner script is located in ServerScriptService → AI.

I think this addition will fix them idling around, I added remove tag after the pathfinding, since it will only try to break ore when they are close enough. Another fix may be to increase the minimum distance but then the teleport is quite visible. I also removed the MiningAnim.Stopped:Wait() as that never seemed to trigger for me.

			if success and path.Status == Enum.PathStatus.Success then
				local waypoints = path:GetWaypoints()
				for _,waypoint in pairs(waypoints) do

					if waypoint.Action == Enum.PathWaypointAction.Jump then
						hum:ChangeState(Enum.HumanoidStateType.Jumping)
					end

					if nearest and nearest.Parent and (humrp.Position - nearest:GetPivot().Position).Magnitude <= 10 then
						breakOre(nearest)
					end

					hum:MoveTo(waypoint.Position)
					hum.MoveToFinished:Wait()
				end
			end
			
			-- removes tag if failed to mine
			if nearest and nearest.Parent then
				CollectionService:RemoveTag(nearest, "ToBeMined")
			end

			WalkAnim:Stop()
			task.wait(0.5)
			walkToOre()
		end
1 Like

Yep, thank you so much for the help, here are the results after stress testing for half an hour


The one slight bug feature that i’ve spotted is that they occasionally stop for like 20 seconds or flip over but those are manageable and look kinda goofy, i’ll work them out myself or even keep them.

1 Like