Settings:
return
{
range = math.huge,
chaseRange = 500,
minSpeed = 5,
maxSpeed = 29,
speedIncreaseRate = 1,
damage = 100,
attackRange = 2,
attackCooldown = 1,
targetScanCooldown = 1,
knockback = 10,
wanderDistance = 15,
wanderCooldown = {3, 6},
tagged = {"AiTag"},
attackAnimation = "rbxassetid://3489169607",
attackSounds = {"18942724219"},
respawning = true,
ragdoll = true,
}
Ai
local Ai = script.Parent
local humanoid = Ai:WaitForChild("Ai") -- Fixed variable name to "Humanoid"
local root = Ai:WaitForChild("HumanoidRootPart")
local PathfindingService = game:GetService("PathfindingService")
local RunService = game:GetService("RunService")
local module = require(Ai:WaitForChild("Settings"))
-- Load Attack Animation
local attackAnim = Instance.new("Animation")
attackAnim.AnimationId = module.attackAnimation
local attackAnimation = humanoid:LoadAnimation(attackAnim)
-- Setup Attack Sounds
local attackSounds = {}
for i, soundId in ipairs(module.attackSounds) do
local sound = Instance.new("Sound")
sound.SoundId = "rbxassetid://" .. soundId
sound.Name = "Attack" .. i
sound.Volume = 0.75
sound.Parent = Ai:FindFirstChild("Head") or Ai
table.insert(attackSounds, sound)
end
-- Variables
local target = nil
local lastTargetPosition = nil
local currentSpeed = module.minSpeed > 0 and module.minSpeed or 8
local lastDamageTime = -math.huge
local lastScanTime = -math.huge
local lastPathTime = 0
local speedMultiplier = 20
local currentPath = nil
local pathIndex = 1
local reachedConnection = nil
humanoid.WalkSpeed = currentSpeed
-- Respawn Logic
if module.respawning then
local clone = Ai:Clone()
humanoid.Died:Connect(function()
task.delay(4, function()
clone:SetPrimaryPartCFrame(Ai:GetPrimaryPartCFrame())
clone.Parent = workspace
clone.Humanoid.Health = clone.Humanoid.MaxHealth
clone:MakeJoints()
Ai:Destroy()
end)
end)
end
-- Ragdoll Logic
if module.ragdoll then
humanoid.BreakJointsOnDeath = false
humanoid.Died:Connect(function()
root.Velocity = root.CFrame.LookVector * -15
root.CanCollide = false
for _, joint in pairs(Ai:GetDescendants()) do
if joint:IsA("Motor6D") then
local socket = Instance.new("BallSocketConstraint")
local a1 = Instance.new("Attachment", joint.Part0)
local a2 = Instance.new("Attachment", joint.Part1)
socket.Attachment0 = a1
socket.Attachment1 = a2
socket.LimitsEnabled = true
socket.TwistLimitsEnabled = true
socket.Parent = joint.Parent
a1.CFrame = joint.C0
a2.CFrame = joint.C1
joint:Destroy()
end
end
end)
end
-- Attack Function
local function attack(targetRoot, range)
if humanoid.Health <= 0 then return end
lastDamageTime = time()
if not attackAnimation.IsPlaying then
attackAnimation:Play()
end
-- Apply knockback
targetRoot.Velocity = (targetRoot.Position - root.Position).Unit * module.knockback + Vector3.new(0, module.knockback / 2, 0)
-- Damage and sound after half animation length
task.delay(attackAnimation.Length / 2, function()
if target and (targetRoot.Position - root.Position).Magnitude <= range then
target:TakeDamage(module.damage)
end
attackSounds[math.random(#attackSounds)]:Play()
end)
-- Stop animation after full length if cooldown passed
task.delay(attackAnimation.Length, function()
if time() - lastDamageTime >= 0.2 then
attackAnimation:Stop()
end
end)
end
-- Follow Path Function
local function followPath(path, targetRoot)
if reachedConnection then
reachedConnection:Disconnect()
reachedConnection = nil
end
local waypoints = path:GetWaypoints()
pathIndex = 1
local function moveToNext()
if humanoid.Health <= 0 or pathIndex > #waypoints then return end
local waypoint = waypoints[pathIndex]
local distToWaypoint = (root.Position - waypoint.Position).Magnitude
if distToWaypoint < 1 then
pathIndex += 1
moveToNext()
return
end
humanoid:MoveTo(waypoint.Position)
pathIndex += 1
end
moveToNext()
reachedConnection = humanoid.MoveToFinished:Connect(function(reached)
if not reached then return end
if targetRoot and (targetRoot.Position - root.Position).Magnitude <= module.attackRange then
if time() - lastDamageTime >= module.attackCooldown then
attack(targetRoot, module.attackRange)
end
else
-- Small wait to prevent jittering before moving to next waypoint
task.wait(0.05)
moveToNext()
end
end)
end
-- Compute Path to Target
local function computePathToTarget(targetRoot)
if not targetRoot then return end
local path = PathfindingService:CreatePath({
AgentRadius = 2,
AgentHeight = 5,
AgentCanJump = true,
AgentJumpHeight = humanoid.JumpHeight,
AgentWalkableFloorAngle = humanoid.MaxSlopeAngle,
WaypointSpacing = 2
})
path:ComputeAsync(root.Position, targetRoot.Position)
if path.Status == Enum.PathStatus.Success then
currentPath = path
followPath(path, targetRoot)
else
-- Pathfinding failed - optionally handle here
end
end
-- Find Closest Valid Target
local function findClosestTarget()
local closestTarget = nil
local closestDistance = math.huge
for _, humanoidCandidate in pairs(workspace:GetDescendants()) do
if humanoidCandidate:IsA("Humanoid") and humanoidCandidate ~= humanoid and humanoidCandidate.Health > 0 then
local isTagged = false
for _, tag in ipairs(module.tagged) do
if humanoidCandidate.Parent:FindFirstChild(tag) then
isTagged = true
break
end
end
if not isTagged then
local candidateRoot = humanoidCandidate.Parent:FindFirstChild("HumanoidRootPart")
if candidateRoot then
local dist = (candidateRoot.Position - root.Position).Magnitude
if dist <= module.range and dist < closestDistance then
closestDistance = dist
closestTarget = humanoidCandidate
end
end
end
end
end
return closestTarget
end
-- AI Main Loop
RunService.Heartbeat:Connect(function(dt)
if humanoid.Health <= 0 then return end
humanoid.Sit = false
-- Target scanning
if time() - lastScanTime >= module.targetScanCooldown then
lastScanTime = time()
local newTarget = findClosestTarget()
if newTarget ~= target then
target = newTarget
if target then
local targetRoot = target.Parent and target.Parent:FindFirstChild("HumanoidRootPart")
if targetRoot then
computePathToTarget(targetRoot)
end
end
end
end
if target and target.Health > 0 then
local targetRoot = target.Parent and target.Parent:FindFirstChild("HumanoidRootPart")
if targetRoot then
local dist = (targetRoot.Position - root.Position).Magnitude
-- Increase speed when chasing
if dist <= module.chaseRange then
currentSpeed = math.min(currentSpeed + module.speedIncreaseRate * dt * speedMultiplier, module.maxSpeed)
humanoid.WalkSpeed = currentSpeed
end
-- Conditions to recompute path
local shouldRepath = false
if not currentPath or pathIndex > #currentPath:GetWaypoints() then
shouldRepath = true
elseif lastTargetPosition and (lastTargetPosition - targetRoot.Position).Magnitude > 10 then
shouldRepath = true
elseif humanoid.MoveDirection.Magnitude == 0 and (os.clock() - lastPathTime > 1) then
shouldRepath = true
end
if shouldRepath and (os.clock() - lastPathTime > 1.5) then
lastTargetPosition = targetRoot.Position
computePathToTarget(targetRoot)
lastPathTime = os.clock()
end
else
target = nil -- target root missing, reset target
end
else
-- Reset speed when no target
if currentSpeed ~= module.minSpeed then
currentSpeed = module.minSpeed
humanoid.WalkSpeed = currentSpeed
end
end
end)
I know the code is messy (I haven’t finished the pathfinding method)