The Ai doesn’t track the player when the player spawns until at a random time.
When the Ai chases the player, the Ai sometimes stops moving.
local Players = game:GetService("Players")
local Pathfinding = game:GetService("PathfindingService")
local RunService = game:GetService("RunService")
local Debris = game:GetService("Debris")
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local Ai = script.Parent
local primPart = Ai.PrimaryPart
local humanoid = Ai:WaitForChild("Ai")
local Settings = require(Ai:WaitForChild("Settings"))
local UpdateSpeed_Event = ReplicatedStorage:WaitForChild("UpdateSpeed")
-- Disable client network ownership
for _, part in ipairs(Ai:GetDescendants()) do
if part:IsA("BasePart") then
part:SetNetworkOwner(nil)
end
end
-- Initialization
humanoid.WalkSpeed = Settings.minSpeed
local lastPosition = primPart.Position
local stuck = false
local obstructed = true
local chasing = false
local path = Pathfinding:CreatePath({
AgentCanClimb = false,
Costs = { Climb = 0 }
})
local function GetClosestTarget(maxDistance)
local closest = nil
local shortestDistance = maxDistance
local forwardVector = primPart.CFrame.LookVector
local fovAngle = math.rad(Settings.range)
local blindSpotRange = Settings.nearRange
for _, player in ipairs(Players:GetPlayers()) do
local character = player.Character
if character and character.PrimaryPart then
local root = character.PrimaryPart
local humanoid = character:FindFirstChildOfClass("Humanoid")
if humanoid and humanoid.Health > 0 then
local directionToPlayer = (root.Position - primPart.Position)
local dist = directionToPlayer.Magnitude
if dist <= maxDistance then
local inBlindSpotRange = dist <= blindSpotRange
local angle = math.acos(forwardVector:Dot(directionToPlayer.Unit))
if inBlindSpotRange or angle <= fovAngle then
if dist < shortestDistance then
shortestDistance = dist
closest = character
end
end
end
end
end
end
return closest, shortestDistance
end
-- Check if there's something between AI and target
local function CheckObstruction(target)
if not target or not target.PrimaryPart then return true end
local rayParams = RaycastParams.new()
rayParams.FilterDescendantsInstances = { Ai, target }
rayParams.FilterType = Enum.RaycastFilterType.Exclude
local direction = target.PrimaryPart.Position - primPart.Position
local result = workspace:Raycast(primPart.Position, direction, rayParams)
return result ~= nil
end
local function Follow(target)
if not target.PrimaryPart then return end
local destination = target.PrimaryPart.Position
local success, err = pcall(function()
path:ComputeAsync(primPart.Position, destination)
end)
if success and path.Status == Enum.PathStatus.Success then
local waypoints = path:GetWaypoints()
for i, waypoint in ipairs(waypoints) do
if waypoint.Action == Enum.PathWaypointAction.Jump then
humanoid.Jump = true
end
humanoid:MoveTo(waypoint.Position)
local reached = humanoid.MoveToFinished:Wait(2) -- 2 second timeout per waypoint
if not reached then
-- Possibly stuck, break to recompute path next loop
break
end
end
else
warn(`Pathfinding failed for {Ai.Name} to {target.Name}: {err or path.Status}`)
end
-- Update stuck detection
stuck = (primPart.Position - lastPosition).Magnitude < 1
lastPosition = primPart.Position
end
-- Damage and temporary speed boost
local attackCount = 0
local function AttemptAttack(target: Model)
local targetHumanoid = target:FindFirstChildWhichIsA("Humanoid")
if not targetHumanoid or target:FindFirstChildWhichIsA("ForceField") then return end
targetHumanoid:TakeDamage(Settings.damage)
-- Increase attack count and reduce WalkSpeed accordingly (max 3 hits / 30 reduction)
attackCount = math.clamp(attackCount + 1, 0, 3)
local reduction = attackCount * 1
humanoid.WalkSpeed = math.max(Settings.minSpeed, Settings.maxSpeed - reduction)
-- Tell client to pause its WalkSpeed control
local player = Players:GetPlayerFromCharacter(target)
if player then
UpdateSpeed_Event:FireClient(player, false, true) -- pause speed + trigger phase increase
end
if targetHumanoid.Health > 0 then
local boost = math.floor((targetHumanoid.MaxHealth - targetHumanoid.Health) / 8 + 8)
targetHumanoid.WalkSpeed += boost
local ff = Instance.new("ForceField")
ff.Visible = false
ff.Parent = target
ff.Destroying:Once(function()
targetHumanoid.WalkSpeed -= boost
-- Tell client to resume WalkSpeed control after forcefield ends
if player then
UpdateSpeed_Event:FireClient(player, true, false) -- resume speed, no phase increase
end
end)
Debris:AddItem(ff, 10)
end
end
-- Update obstruction constantly
RunService.Stepped:Connect(function()
local target = GetClosestTarget(Settings.range)
obstructed = CheckObstruction(target)
end)
-- Reroute periodically in case of stuck
task.spawn(function()
while true do
task.wait(0.05)
if stuck then
-- Trigger a re-follow by breaking current path and restarting
local target, _ = GetClosestTarget(Settings.range)
if target then
Follow(target)
end
end
end
end)
-- Speed control based on chase state
task.spawn(function()
while task.wait() do
if chasing then
if humanoid.WalkSpeed < Settings.maxSpeed then
humanoid.WalkSpeed = math.min(humanoid.WalkSpeed + Settings.speedIncreaseRate, Settings.maxSpeed)
end
else
if humanoid.WalkSpeed > Settings.minSpeed then
humanoid.WalkSpeed = math.max(humanoid.WalkSpeed - Settings.speedIncreaseRate, Settings.minSpeed)
end
end
end
end)
-- Main AI Loop
task.spawn(function()
while task.wait(0.2) do -- check 5 times per second for responsiveness
local target, distance = GetClosestTarget(Settings.range)
if target then
chasing = distance <= Settings.chaseRange
Follow(target)
if distance <= Settings.attackRange then
AttemptAttack(target)
end
else
chasing = false
humanoid:MoveTo(primPart.Position) -- stop moving when no target
end
end
end)
Update your GetClosestTarget() function to ensure the character has fully loaded before it’s considered a valid target.
Add a check like this:
if character and character.PrimaryPart and character:FindFirstChildOfClass("Humanoid") and character:FindFirstChild("HumanoidRootPart") then
And optionally, delay the AI's targeting logic at startup for a second or two to let new players load:
task.delay(2, function()
-- Begin AI loop or enable targeting here
end)
A. Increase the Wait Timeout Dynamically
Try making the timeout proportional to distance:
local distanceToWaypoint = (waypoint.Position - primPart.Position).Magnitude
local moveTimeout = math.clamp(distanceToWaypoint / humanoid.WalkSpeed, 1, 4)
local reached = humanoid.MoveToFinished:Wait(moveTimeout)
B. Recompute Path More Aggressively If Stuck
You already have a loop for when the AI is stuck, but it’s not reliable if the path is invalid or blocked mid-chase. You can adjust the main AI loop to check this too:
Update the Follow() function like this:
if not reached or stuck then
break -- Recompute next loop
end
And log when it’s stuck for debugging:
if stuck then
warn(Ai.Name .. " might be stuck at " .. tostring(primPart.Position))
end
local Players = game:GetService("Players")
local Pathfinding = game:GetService("PathfindingService")
local RunService = game:GetService("RunService")
local Debris = game:GetService("Debris")
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local Ai = script.Parent
local primPart = Ai.PrimaryPart
local humanoid = Ai:WaitForChild("Ai")
local Settings = require(Ai:WaitForChild("Settings"))
local UpdateSpeed_Event = ReplicatedStorage:WaitForChild("UpdateSpeed")
-- Disable client network ownership
for _, part in ipairs(Ai:GetDescendants()) do
if part:IsA("BasePart") then
part:SetNetworkOwner(nil)
end
end
-- Initialization
humanoid.WalkSpeed = Settings.minSpeed
local lastPosition = primPart.Position
local stuck = false
local obstructed = true
local chasing = false
local path = Pathfinding:CreatePath({
AgentCanClimb = false,
Costs = { Climb = 0 }
})
local function GetClosestTarget(maxDistance)
local closest = nil
local shortestDistance = maxDistance
local forwardVector = primPart.CFrame.LookVector
local fovAngle = math.rad(Settings.range)
local blindSpotRange = Settings.nearRange
for _, player in ipairs(Players:GetPlayers()) do
local character = player.Character
if character and character.PrimaryPart then
local root = character.PrimaryPart
local humanoid = character:FindFirstChildOfClass("Humanoid")
if character and character.PrimaryPart and character:FindFirstChildOfClass("Humanoid") and character:FindFirstChild("HumanoidRootPart") then
if humanoid and humanoid.Health > 0 then
local directionToPlayer = (root.Position - primPart.Position)
local dist = directionToPlayer.Magnitude
if dist <= maxDistance then
local inBlindSpotRange = dist <= blindSpotRange
local angle = math.acos(forwardVector:Dot(directionToPlayer.Unit))
if inBlindSpotRange or angle <= fovAngle then
if dist < shortestDistance then
shortestDistance = dist
closest = character
end
end
end
end
end
end
return closest, shortestDistance
end
end
-- Check if there's something between AI and target
local function CheckObstruction(target)
if not target or not target.PrimaryPart then return true end
local rayParams = RaycastParams.new()
rayParams.FilterDescendantsInstances = { Ai, target }
rayParams.FilterType = Enum.RaycastFilterType.Exclude
local direction = target.PrimaryPart.Position - primPart.Position
local result = workspace:Raycast(primPart.Position, direction, rayParams)
return result ~= nil
end
local function Follow(target)
if not target.PrimaryPart then return end
local destination = target.PrimaryPart.Position
local success, err = pcall(function()
path:ComputeAsync(primPart.Position, destination)
end)
if success and path.Status == Enum.PathStatus.Success then
local waypoints = path:GetWaypoints()
for i, waypoint in ipairs(waypoints) do
if waypoint.Action == Enum.PathWaypointAction.Jump then
humanoid.Jump = true
end
local distanceToWaypoint = (waypoint.Position - primPart.Position).Magnitude
local moveTimeout = math.clamp(distanceToWaypoint / humanoid.WalkSpeed, 1, 4)
humanoid:MoveTo(waypoint.Position)
local reached = humanoid.MoveToFinished:Wait(moveTimeout)
if not reached or stuck then
break -- Recompute next loop
end
end
else
warn(`Pathfinding failed for {Ai.Name} to {target.Name}: {err or path.Status}`)
end
-- Update stuck detection
stuck = (primPart.Position - lastPosition).Magnitude < 1
lastPosition = primPart.Position
end
-- Damage and temporary speed boost
local attackCount = 0
local function AttemptAttack(target: Model)
local targetHumanoid = target:FindFirstChildWhichIsA("Humanoid")
if not targetHumanoid or target:FindFirstChildWhichIsA("ForceField") then return end
targetHumanoid:TakeDamage(Settings.damage)
-- Increase attack count and reduce WalkSpeed accordingly (max 3 hits / 30 reduction)
attackCount = math.clamp(attackCount + 1, 0, 3)
local reduction = attackCount * 1
humanoid.WalkSpeed = math.max(Settings.minSpeed, Settings.maxSpeed - reduction)
-- Tell client to pause its WalkSpeed control
local player = Players:GetPlayerFromCharacter(target)
if player then
UpdateSpeed_Event:FireClient(player, false, true) -- pause speed + trigger phase increase
end
if targetHumanoid.Health > 0 then
local boost = math.floor((targetHumanoid.MaxHealth - targetHumanoid.Health) / 8 + 8)
targetHumanoid.WalkSpeed += boost
local ff = Instance.new("ForceField")
ff.Visible = false
ff.Parent = target
ff.Destroying:Once(function()
targetHumanoid.WalkSpeed -= boost
-- Tell client to resume WalkSpeed control after forcefield ends
if player then
UpdateSpeed_Event:FireClient(player, true, false) -- resume speed, no phase increase
end
end)
Debris:AddItem(ff, 10)
end
end
-- Update obstruction constantly
RunService.Stepped:Connect(function()
local target = GetClosestTarget(Settings.range)
obstructed = CheckObstruction(target)
end)
-- Reroute periodically in case of stuck
task.spawn(function()
while true do
task.wait(0.05)
if stuck then
warn(Ai.Name .. " might be stuck at " .. tostring(primPart.Position))
local target, _ = GetClosestTarget(Settings.range)
if target then
Follow(target)
end
end
end
end)
-- Speed control based on chase state
task.spawn(function()
while task.wait() do
if chasing then
if humanoid.WalkSpeed < Settings.maxSpeed then
humanoid.WalkSpeed = math.min(humanoid.WalkSpeed + Settings.speedIncreaseRate, Settings.maxSpeed)
end
else
if humanoid.WalkSpeed > Settings.minSpeed then
humanoid.WalkSpeed = math.max(humanoid.WalkSpeed - Settings.speedIncreaseRate, Settings.minSpeed)
end
end
end
end)
-- Main AI Loop
task.delay(2, function()
while task.wait(0.2) do -- check 5 times per second for responsiveness
local target, distance = GetClosestTarget(Settings.range)
if target then
chasing = distance <= Settings.chaseRange
Follow(target)
if distance <= Settings.attackRange then
AttemptAttack(target)
end
else
chasing = false
humanoid:MoveTo(primPart.Position) -- stop moving when no target
end
end
end)