Wave module not moving enemies properly

local module = {}

local enemyNum = 0
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local waveCount = 1

local enemyGroupWeight = {
	SmallGruntGroup = {Type = "Goblin Grunt", Count = 6, Delay = 2, Cost = 1},
	SmallRunningGroup = {Type = "Goblin Runner", Count = 4, Delay = 3, Cost = 3},
	CondenseGruntGroup = {Type = "Goblin Grunt", Count = 10, Delay = 1, Cost = 4},
	SmallSpearGroup = {Type = "Spear Goblin", Count = 2, Delay = 5, Cost = 6},
	SmallHybridGroup = {
		{Type = "Spear Goblin", Count = 1, Delay = 0, Cost = 3},
		{Type = "Goblin Grunt", Count = 6, Delay = 2, Cost = 4},
	},
}

local function getPackCost(pack)
    if type(pack) ~= "table" then return math.huge end
    if pack[1] ~= nil then -- composite pack (array of subpacks)
        local sum = 0
        for _, sub in ipairs(pack) do
            sum = sum + (sub.Cost or 0)
        end
        return sum
    else -- single pack
        return pack.Cost or math.huge
    end
end

local function filterAvailable(list, maxCost)
    local out = {}
    for _, p in ipairs(list) do
        local c = getPackCost(p)
        if c <= maxCost and c > 0 then
            table.insert(out, p)
        end
    end
    return out
end

local function generateWave(waveCount)
    local budget = 5 + (tonumber(waveCount) or 1) * 2
    local wave = {}

    local available = {}
    for name, pack in pairs(enemyGroupWeight) do
        local cost = getPackCost(pack)
        if cost <= budget and cost > 0 then
            table.insert(available, pack)
        end
    end

    if #available == 0 then
        return wave
    end

    local attempts = 0
    while budget > 0 and #available > 0 do
        attempts = attempts + 1
        if attempts > 200 then break end

        local index = math.random(1, #available)
        local pack = available[index]
        local totalCost = getPackCost(pack)

		if totalCost <= 0 then
			available[index] = available[#available]
			table.remove(available)
		else
			if totalCost <= budget then
				table.insert(wave, pack)
				budget -= totalCost
			end

			available = filterAvailable(available, budget)
		end
        if totalCost <= budget then
            table.insert(wave, pack)
            budget = budget - totalCost
        end

        available = filterAvailable(available, budget)
    end

    return wave
end

local Waves = {
	[1] = {
		{Type = "Goblin Grunt", Count = 5, Delay = 2}
	},
	[2] = {
		{Type = "Goblin Grunt", Count = 8, Delay = 1}
	},
	[3] = {
		{Type = "Goblin Grunt", Count = 8, Delay = 1},
		{Type = "Spear Goblin", Count = 2, Delay = 4},
		{Type = "Goblin Runner", Count = 4, Delay = 2}
	}
}

local function playWalkAnimation(enemy)
	local humanoid = enemy:FindFirstChild("Humanoid")
	if not humanoid then return end

	local animator = humanoid:FindFirstChild("Animator")
	if not animator then
		animator = Instance.new("Animator")
		animator.Parent = humanoid
	end

	local walkAnimation = humanoid:FindFirstChild("walk")
	if not walkAnimation then return end

	local walkAnimTrack = animator:LoadAnimation(walkAnimation)
	walkAnimTrack.Looped = true
	walkAnimTrack:Play()
	return walkAnimTrack
end

local function createEnemy(enemyType)
	task.spawn(function()
		local paths = workspace:WaitForChild("PathsWaypoints")
		local waypoints = paths:GetChildren()

		table.sort(waypoints, function(a, b)
			local numA = tonumber(a.Name) or 0
			local numB = tonumber(b.Name) or 0
			return numA < numB
		end)

		local enemy = ReplicatedStorage.Items.Enemies[enemyType]:Clone()

		for i, v in enemy:GetDescendants() do
			if v:IsA("BasePart") then
				v.CollisionGroup = "Characters"
			end
		end

		enemyNum += 1
		enemy.Name = enemyNum.. " ".. enemyType

		local status = enemy:WaitForChild("Status")
		local stat = require(enemy:WaitForChild("Stats"))
		local enHum = enemy:WaitForChild("Humanoid")

		status.NameLabel.Text = enemyType
		status.healthStatus.Text = stat.Health.. "/".. stat.Health

		enHum.WalkSpeed = stat.Speed
		enHum.MaxHealth = stat.Health
		enHum.Health = stat.Health

		enemy.Parent = workspace:WaitForChild("Enemies")
		enemy:PivotTo(paths.Start.CFrame)

		if not enemy.PrimaryPart then
			local root = enemy:FindFirstChild("HumanoidRootPart")
			if root then
				enemy.PrimaryPart = root
			else
				for i, v in enemy:GetDescendants() do
					if v:IsA("BasePart") then
						enemy.PrimaryPart = v
						break
					end
				end
			end
		end

		local walkAnimTrack = playWalkAnimation(enemy)

		for i = 1, #waypoints do
			local waypoint = waypoints[i]
			if not enemy or not enemy.Parent or not enemy.PrimaryPart then
				break
			end

			local reached = false
			local conn

			enHum:MoveTo(waypoint.Position)
			print(waypoint.Position)
			conn = enHum.MoveToFinished:Connect(function(success)
				if success then
					reached = true
				end
			end)

			while not reached do
				if enemy.PrimaryPart then
					local distance = (enemy.PrimaryPart.Position - waypoint.Position).Magnitude
					print(distance)
					if distance < 1 then
						reached = true
					end
				end
				task.wait(.1)
			end

			conn:Disconnect()
		end

		if walkAnimTrack then
			walkAnimTrack:Stop()
		end

		if enemy and enemy.Parent then
			enemy:Destroy()
		end
	end)
end

function module.startWave(waveNumber)
	local waveData = generateWave(waveNumber)
	if not waveData then 
		print("No wave data for wave", waveNumber or waveCount)
		return 
	end
	print("Starting wave", waveCount)
	local EnemiesFolder = workspace:WaitForChild("Enemies")

	for _, group in pairs(waveData) do
		task.spawn(function()
			for i = 1, group.Count do
				createEnemy(group.Type)
				task.wait(group.Delay)
			end
		end)
	end

	while true do
		task.wait(2)
		local aliveEnemies = 0
		for _, enemy in ipairs(EnemiesFolder:GetChildren()) do
			local humanoid = enemy:FindFirstChild("Humanoid")
			if humanoid and humanoid.Health > 0 then
				aliveEnemies += 1
			end
		end

		if aliveEnemies == 0 then
			break
		end

		print("Waiting for", aliveEnemies, "enemies to be defeated...")
	end

	print("Wave", waveCount, "completed!")
	waveCount += 1
end

return module

thats the module and its called in a server script after 5 seconds after the game is running for testing purposes

enemies begin to move along the path but then stop before they even get to the 2nd point and at this point i can’t figure out what im doing wrong here

This block of code constantly updates the reached value which seems kinda reduandant since you’re waiting for MoveToFinished already?

Okay, even if it’s to account for potential failures, the while loop never retries the movement.

Anyways, I highly doubt this is the issue if the nodes aren’t placed that far apart…

gimme a mo while I read everything again.

Okay. I couldn’t find anything.

How many times does this:

			print(waypoint.Position)

print out?

the nodes are the half transparent cubes, they aren’t very close together so i could probably get away from checking distance and MoveToFinished

it prints once, for every enemy spawned, but is never printed again because they dont reach the next node

Gotcha. Could you add a print statement after the while loop?

I’m guessing that the primary part for the enemies is too high for them to ever reach the minimum distance to be considered for their movement to be complete.

oh yeah that might be true i never thought abt that, i’ll do that rq and lyk

i added a print of the waypoint position cuz thats what i assumed you meant in the while loop and it always prints the same position

(idk if having a picture helps but thats about how far they get during their path, faster ones go farther than slower ones so idk if it has anything to do with that) in workspace enemies are pivoted to that “Start” Part and i removed that from the PathsWaypoints Folder just in case, but I still can’t get it to work, and i also moved the waypoints close enough to their primary part

Where did you add the print statement? Could you give me a screenshot?

I just want the print statement so we know whether the enemies actually finish travelling to the first waypoint.

i added two print statements
first print always says: Connection -38, 4.999982833862305, 38.001224517822266

and the second one never prints reached

How about the distance?

If that prints to be always greater than 2, then we’ve found our problem.

Screenshot_39

distance never falls below those numbers once they get stuck

What’s the minimum value?

You probably need to move the waypoints closer.

EDIT:

Closer as in: closer to the actual primaryparts

so it seems after a certain time if they cant reach a check point they just stop, so the faster ones can go through the track if i add a check in the middle but the slower ones cannot, should i just add more waypoints across the track so they can make it across? or can I find the cause of it now, because it seems a little redundant to add a ton of waypoints across the whole track instead of just a few

Uh, so is the original issue resolved?

Also, there is a built in timeout for :MoveTo.

You can check the distance in your

conn = enHum.MoveToFinished:Connect(function(success)
				if success then
					reached = true
				end
			end)

and call :MoveTo() again if needed.

ok so yeah im slow, i didnt know there was a timeout in :MoveTo I just assumed they would keep trying to walk until they reached it, thank you for helping me

2 Likes