I didn’t script this entirely, my friend helped me.
Ai Script:
local Players = game:GetService("Players")
local PathfindingService = 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 AI_Config = require(ReplicatedStorage:WaitForChild("AI_Config"))
local UpdateSpeed_Event = ReplicatedStorage:WaitForChild("UpdateSpeed")
-- Disable client network ownership for all parts of the AI model
for _, part in ipairs(Ai:GetDescendants()) do
if part:IsA("BasePart") then
part:SetNetworkOwner(nil)
end
end
humanoid.WalkSpeed = AI_Config.minSpeed
local lastPosition = primPart.Position
local stuckCounter = 0
local stuck = false
local chasing = false
local obstructed = true
local attacked = true
local attackCount = 0
local attackCooldown = 1
-- Get closest visible target
local function GetClosestTarget(maxDistance)
local closest, shortestDistance = nil, maxDistance
local forwardVector = primPart.CFrame.LookVector
local fovAngle = math.rad(AI_Config.range)
local blindSpotRange = AI_Config.nearRange
for _, player in ipairs(Players:GetPlayers()) do
local character = player.Character
local root = character and character:FindFirstChild("HumanoidRootPart")
local humanoidChar = character and character:FindFirstChildOfClass("Humanoid")
if root and humanoidChar and humanoidChar.Health > 0 then
local direction = root.Position - primPart.Position
local dist = direction.Magnitude
if dist <= maxDistance then
local inBlindSpot = dist <= blindSpotRange
local angle = math.acos(forwardVector:Dot(direction.Unit))
if inBlindSpot or angle <= fovAngle then
if dist < shortestDistance then
shortestDistance = dist
closest = character
end
end
end
end
end
return closest, shortestDistance
end
-- Check line-of-sight obstruction
local function CheckObstruction(target)
if not target then return true end
local targetRoot = target:FindFirstChild("HumanoidRootPart") or target.PrimaryPart
if not targetRoot then return true end
local rayParams = RaycastParams.new()
rayParams.FilterDescendantsInstances = {Ai, target}
rayParams.FilterType = Enum.RaycastFilterType.Exclude
local direction = (targetRoot.Position - primPart.Position)
local result = workspace:Raycast(primPart.Position, direction.Unit * direction.Magnitude, rayParams)
-- If raycast hit something, obstruction exists
return result ~= nil
end
-- Path follow logic
local function Follow(target)
local targetRoot = target.PrimaryPart or target:FindFirstChild("HumanoidRootPart")
if not targetRoot then return end
local targetHumanoid = target:FindFirstChildWhichIsA("Humanoid")
if not targetHumanoid or targetHumanoid.Health <= 0 then return end
local destination = targetRoot.Position
local path = PathfindingService:CreatePath({
AgentRadius = 2,
AgentHeight = 5,
AgentCanJump = true,
AgentJumpHeight = 7,
AgentMaxSlope = 45,
Costs = {Climb = 0}
})
local success, err = pcall(function()
path:ComputeAsync(primPart.Position, destination)
end)
if success and path.Status == Enum.PathStatus.Success then
local waypoints = path:GetWaypoints()
if workspace:FindFirstChild("Waypoints") then
for _, w in ipairs(workspace.Waypoints:GetChildren()) do
w:Destroy()
end
for _, waypoint in ipairs(waypoints) do
local part = Instance.new("Part")
part.Name = "Waypoint"
part.Shape = Enum.PartType.Ball
part.Material = Enum.Material.Neon
part.Size = Vector3.new(0.6, 0.6, 0.6)
part.Position = waypoint.Position + Vector3.new(0, 2, 0)
part.Anchored = true
part.CanCollide = false
part.Parent = workspace.Waypoints
end
end
for _, waypoint in ipairs(waypoints) do
if waypoint.Action == Enum.PathWaypointAction.Jump then
humanoid.Jump = true
end
local dist = (waypoint.Position - primPart.Position).Magnitude
local timeout = math.clamp(dist / humanoid.WalkSpeed, 1, 4)
humanoid:MoveTo(waypoint.Position)
local reached = humanoid.MoveToFinished:Wait(timeout)
if not reached or stuck then
print("[AI] Stopping path following due to stuck or failure")
break
end
end
else
warn("[AI] Pathfinding failed for " .. Ai.Name .. " to " .. target.Name .. ": " .. (err or tostring(path.Status)))
end
if (primPart.Position - lastPosition).Magnitude < 1 then
stuckCounter += 1
else
stuckCounter = 0
end
stuck = stuckCounter > 10
lastPosition = primPart.Position
if stuck then
warn("[AI] " .. Ai.Name .. " might be stuck at " .. tostring(primPart.Position) .. " (stuckCounter = " .. stuckCounter .. ")")
end
end
local function AttemptAttack(target)
if not attacked then return end
attacked = false
local targetHumanoid = target:FindFirstChildWhichIsA("Humanoid")
if not targetHumanoid or target:FindFirstChildWhichIsA("ForceField") then
attacked = true
return
end
targetHumanoid:TakeDamage(AI_Config.damage)
attackCount = math.clamp(attackCount + 1, 0, 3)
humanoid.WalkSpeed = math.max(AI_Config.minSpeed, AI_Config.maxSpeed - attackCount)
local player = Players:GetPlayerFromCharacter(target)
if player then
UpdateSpeed_Event:FireClient(player, false, true)
end
print(string.format("[AI] Attacked %s! Attack count: %d, AI WalkSpeed: %.2f", target.Name, attackCount, humanoid.WalkSpeed))
if targetHumanoid.Health > 0 then
local boost = math.floor((targetHumanoid.MaxHealth - targetHumanoid.Health) / 10 + 10)
targetHumanoid.WalkSpeed += boost
local ff = Instance.new("ForceField")
ff.Visible = false
ff.Parent = target
ff.Destroying:Once(function()
targetHumanoid.WalkSpeed -= boost
if player then
UpdateSpeed_Event:FireClient(player, true, false)
end
end)
Debris:AddItem(ff, 5)
end
task.delay(attackCooldown, function()
attacked = true
print("[AI] Attack cooldown ended, can attack again.")
end)
end
RunService.Stepped:Connect(function()
local target = GetClosestTarget(AI_Config.range)
if target then
obstructed = CheckObstruction(target)
else
obstructed = true
end
print(string.format("[AI] Obstructed: %s, Chasing: %s, Stuck: %s", tostring(obstructed), tostring(chasing), tostring(stuck)))
end)
task.spawn(function()
while true do
task.wait(0.05)
if stuck then
local posStr = string.format("(%.2f, %.2f, %.2f)", primPart.Position.X, primPart.Position.Y, primPart.Position.Z)
warn("[AI] " .. Ai.Name .. " might be stuck at " .. posStr)
local target = GetClosestTarget(AI_Config.range)
if target then
Follow(target)
end
end
end
end)
task.spawn(function()
while task.wait() do
if chasing then
humanoid.WalkSpeed = math.min(humanoid.WalkSpeed + AI_Config.speedIncreaseRate, AI_Config.maxSpeed)
else
humanoid.WalkSpeed = math.max(humanoid.WalkSpeed - AI_Config.speedIncreaseRate, AI_Config.minSpeed)
end
end
end)
task.delay(2, function()
while task.wait() do
local target, distance = GetClosestTarget(AI_Config.range)
if target then
local humanoidChar = target:FindFirstChildWhichIsA("Humanoid")
if humanoidChar and humanoidChar.Health > 0 then
chasing = distance <= AI_Config.chaseRange
print(string.format("[AI] Target: %s, Distance: %.2f, Chasing: %s", target.Name, distance, tostring(chasing)))
Follow(target)
if distance <= AI_Config.attackRange then
AttemptAttack(target)
end
else
chasing = false
end
else
chasing = false
print("[AI] No target found, idle.")
humanoid:MoveTo(primPart.Position)
end
end
end)
I do apologies if the script is unorganised.