Hello, I have been trying to make a boss AI for a game I’m making. But I ran into some issues as of lately regarding my code.
I need him to be able to actually walk and have awareness of what the players doing. It’s written there but it doesnt work for some reason the AI just keeps bugging out and pauses or does something incredibly stupid.
Here is an example of what I’m trying to explain:
I’ve been going at this for a week trying to tackle it with machine learning, trying to get support from many sources but to no avail they just keep failing in diffrent ways.
If there are any sources or tutorials I could follow spesifically for this it would be great. I’ve left the script below if you want to check it out (I doubt it it’s like 300 lines) but feel free to use it for yourself if it works for you but it’s really broken and I couldn’t fix it thats why I’m asking for support.
Also I don’t mind starting from scratch so I’m open to any method.
local boss = script.Parent
local humanoid = boss:WaitForChild("Humanoid", 5)
local rootPart = boss:WaitForChild("HumanoidRootPart", 5)
local Players = game:GetService("Players")
local RunService = game:GetService("RunService")
local Debris = game:GetService("Debris")
if not humanoid or not rootPart then
warn("Boss missing Humanoid or HumanoidRootPart")
return
end
-- Reference the Linked Sword
local sword = boss:WaitForChild("LinkedSword", 5)
if not sword then
warn("LinkedSword not found in boss model")
return
end
-- Skills module
local success, Skills = pcall(function()
return require(game.ServerStorage.SkillsModule)
end)
if not success then
warn("Failed to load SkillsModule: " .. tostring(Skills))
return
end
-- Initialize variables
local originalWalkSpeed = 20 -- Faster than player (16 studs/s)
humanoid.WalkSpeed = originalWalkSpeed
local turnSpeed = 0.8
local predictionTime = 0.6
local currentDirection = rootPart.CFrame.LookVector
local lastSwingTime = 0
local swingCooldown = 0.4
local lastDashTime = 0
local dashCooldown = 1
local isDashing = false
local minDistance = 6
local optimalDistance = 8
local lastActionTime = 0
local actionCooldown = 0.5
local playerDamageTracker = {}
local lastDamageTime = 0
local lastAttack = 0
local toolEquipped = false
-- Sword gear variables
local DamageValues = {
BaseDamage = 5,
SlashDamage = 10,
LungeDamage = 30,
}
local Damage = DamageValues.BaseDamage
-- Sword gear functions
local function SwordUp()
sword.Grip = CFrame.new(0, 0, -1.5, 0, 0, 1, 1, 0, 0, 0, 1, 0)
end
local function SwordOut()
sword.Grip = CFrame.new(0, 0, -1.5, 0, -1, 0, -1, 0, 0, 0, 0, -1)
end
local function TagHumanoid(humanoid, player)
local Creator_Tag = Instance.new("ObjectValue")
Creator_Tag.Name = "creator"
Creator_Tag.Value = player
Debris:AddItem(Creator_Tag, 2)
Creator_Tag.Parent = humanoid
end
local function UntagHumanoid(humanoid)
for _, v in pairs(humanoid:GetChildren()) do
if v:IsA("ObjectValue") and v.Name == "creator" then
v:Destroy()
end
end
end
local function Attack()
Damage = DamageValues.SlashDamage
local slashSound = sword:WaitForChild("Handle"):FindFirstChild("Slash")
if slashSound then slashSound:Play() end
local anim = Instance.new("StringValue")
anim.Name = "toolanim"
anim.Value = "Slash"
anim.Parent = sword
print("Boss used Slash (10 damage)")
end
local function Lunge()
Damage = DamageValues.LungeDamage
local lungeSound = sword:WaitForChild("Handle"):FindFirstChild("Lunge")
if lungeSound then lungeSound:Play() end
local anim = Instance.new("StringValue")
anim.Name = "toolanim"
anim.Value = "Lunge"
anim.Parent = sword
local force = Instance.new("BodyVelocity")
force.Velocity = (target.Character.HumanoidRootPart.Position - rootPart.Position).Unit * 40 + Vector3.new(0, 10, 0)
force.MaxForce = Vector3.new(1e5, 1e5, 1e5)
force.P = 1e4
force.Parent = rootPart
Debris:AddItem(force, 0.3)
createWhiteSilhouettes(boss)
task.spawn(function()
task.wait(0.25)
SwordOut()
task.wait(0.75)
SwordUp()
end)
print("Boss used Lunge (30 damage)")
end
local function Blow(Hit)
if not Hit or not Hit.Parent or not humanoid or humanoid.Health <= 0 then
return
end
local character = Hit.Parent
local victimHumanoid = character:FindFirstChildOfClass("Humanoid")
if not victimHumanoid then
return
end
local player = Players:GetPlayerFromCharacter(character)
if player then
UntagHumanoid(victimHumanoid)
TagHumanoid(victimHumanoid, nil)
victimHumanoid:TakeDamage(Damage)
end
end
local function Activated()
if not toolEquipped or humanoid.Health <= 0 or tick() - lastSwingTime < swingCooldown then
return
end
lastSwingTime = tick()
lastActionTime = tick()
local tickTime = tick()
if (tickTime - lastAttack) < 0.4 then
Lunge()
else
Attack()
end
Damage = DamageValues.BaseDamage
lastAttack = tickTime
end
local function Equipped()
if humanoid.Health <= 0 then
return
end
toolEquipped = true
local unsheathSound = sword:WaitForChild("Handle"):FindFirstChild("Unsheath")
if unsheathSound then unsheathSound:Play() end
end
-- White silhouettes for lunge and dash
local function createWhiteSilhouettes(character)
for _, part in pairs(character:GetChildren()) do
if part:IsA("BasePart") and part.Name ~= "HumanoidRootPart" then
local silhouette = part:Clone()
silhouette.Color = Color3.fromRGB(255, 255, 255)
silhouette.Material = Enum.Material.SmoothPlastic
silhouette.CanCollide = false
silhouette.Transparency = 0.9
silhouette.Anchored = true
silhouette.Parent = workspace
Debris:AddItem(silhouette, 0.125)
end
end
end
-- Dash function
local function dash(direction)
if isDashing or tick() - lastDashTime < dashCooldown then return end
isDashing = true
lastDashTime = tick()
local bv = Instance.new("BodyVelocity")
bv.Velocity = direction * 40
bv.MaxForce = Vector3.new(1e5, 0, 1e5)
bv.P = 1e4
bv.Parent = rootPart
Debris:AddItem(bv, 0.2)
createWhiteSilhouettes(boss)
task.wait(0.2)
isDashing = false
print("Boss dashed")
end
-- Initialize sword
SwordUp()
sword:WaitForChild("Handle").Touched:Connect(Blow)
Equipped()
-- Track player aggression
local function updatePlayerAggression(player, damage)
if not playerDamageTracker[player] then
playerDamageTracker[player] = { damage = 0, lastHit = tick() }
end
playerDamageTracker[player].damage = playerDamageTracker[player].damage + damage
playerDamageTracker[player].lastHit = tick()
end
-- Find nearest player
local function getNearestPlayer()
local closestPlayer = nil
local minDistance = math.huge
local maxAggression = 0
local lowestHealth = math.huge
for _, player in ipairs(Players:GetPlayers()) do
if player.Character and player.Character:FindFirstChild("HumanoidRootPart") and player.Character:FindFirstChildOfClass("Humanoid") and player.Character.Humanoid.Health > 0 then
local distance = (rootPart.Position - player.Character.HumanoidRootPart.Position).Magnitude
local aggression = playerDamageTracker[player] and (playerDamageTracker[player].damage / (tick() - playerDamageTracker[player].lastHit + 1)) or 0
local health = player.Character.Humanoid.Health
if health < lowestHealth or (health == lowestHealth and aggression > maxAggression) or (health == lowestHealth and aggression == maxAggression and distance < minDistance) then
lowestHealth = health
maxAggression = aggression
minDistance = distance
closestPlayer = player
end
end
end
return closestPlayer
end
-- Get player distance
local function getPlayerDistance(target)
local origin = rootPart.Position
local targetPos = target.Character.HumanoidRootPart.Position
local direction = (targetPos - origin)
local ray = Ray.new(origin, direction.Unit * 50)
local hit, position = workspace:FindPartOnRayWithIgnoreList(ray, {boss, target.Character})
if hit and hit:IsDescendantOf(target.Character) then
return (position - origin).Magnitude
end
return direction.Magnitude
end
-- Smooth movement
local function smoothSeek(targetPosition)
local direction = (targetPosition - rootPart.Position)
local distance = direction.Magnitude
if distance < minDistance then
direction = -direction.Unit * (minDistance - distance)
end
local desiredDirection = direction.Unit
currentDirection = currentDirection:Lerp(desiredDirection, turnSpeed)
humanoid:Move(currentDirection)
end
-- Choose best skill
local function getBestSkill(distance, healthPercent, aggression)
local isAggressive = aggression > 1
local isDefensive = distance > 15 or (tick() - lastDamageTime > 5)
if healthPercent < 0.3 then
if distance < 5 and not Skills.isOnCooldown(boss, "IceBurst") then
print("Chose IceBurst (low health, close)")
return "IceBurst"
end
return nil
end
if isAggressive and distance < 5 and not Skills.isOnCooldown(boss, "IceBurst") then
print("Chose IceBurst (aggressive player, close)")
return "IceBurst"
elseif isDefensive and distance > 10 and not Skills.isOnCooldown(boss, "IlluminaLunge") then
print("Chose IlluminaLunge (defensive player, far)")
return "IlluminaLunge"
elseif distance < 5 and not Skills.isOnCooldown(boss, "FirebrandSlash") then
print("Chose FirebrandSlash (close range)")
return "FirebrandSlash"
elseif distance < 15 and not Skills.isOnCooldown(boss, "WitherSlash") then
print("Chose WitherSlash (mid range)")
return "WitherSlash"
elseif not Skills.isOnCooldown(boss, "IlluminaLunge") then
print("Chose IlluminaLunge (default)")
return "IlluminaLunge"
elseif not Skills.isOnCooldown(boss, "IceBurst") then
print("Chose IceBurst (default)")
return "IceBurst"
elseif not Skills.isOnCooldown(boss, "FirebrandSlash") then
print("Chose FirebrandSlash (default)")
return "FirebrandSlash"
elseif not Skills.isOnCooldown(boss, "WitherSlash") then
print("Chose WitherSlash (default)")
return "WitherSlash"
end
return nil
end
-- Track damage
humanoid.HealthChanged:Connect(function(health)
if health < humanoid.Health then
local damage = humanoid.Health - health
local closestPlayer = getNearestPlayer()
if closestPlayer then
updatePlayerAggression(closestPlayer, damage)
lastDamageTime = tick()
end
end
end)
-- React to player sword swings
game.ReplicatedStorage.SwordSwingEvent.OnServerEvent:Connect(function(player)
print("Player swung sword:", player.Name)
if player == target then
local directionAway = (rootPart.Position - target.Character.HumanoidRootPart.Position).Unit
dash(directionAway)
end
end)
-- Main loop
local target = nil
while true do
if not humanoid or humanoid.Health <= 0 then
print("Boss is dead or missing humanoid")
break
end
target = getNearestPlayer()
if not target or not target.Character or not target.Character:FindFirstChild("HumanoidRootPart") then
humanoid.WalkSpeed = originalWalkSpeed
print("No valid target found")
RunService.Heartbeat:Wait()
continue
end
local distance = getPlayerDistance(target)
local healthPercent = humanoid.Health / humanoid.MaxHealth
local aggression = playerDamageTracker[target] and (playerDamageTracker[target].damage / (tick() - playerDamageTracker[target].lastHit + 1)) or 0
print("Target:", target.Name, "Distance:", distance, "Aggression:", aggression, "ActionTime:", tick() - lastActionTime, "MoveActive:", boss:GetAttribute("MoveActive"), "Stunned:", boss:GetAttribute("Stunned"), "IsDashing:", isDashing)
local toPlayer = (target.Character.HumanoidRootPart.Position - rootPart.Position)
local circleDirection = Vector3.new(-toPlayer.Z, 0, toPlayer.X).Unit * (math.random() < 0.5 and 1 or -1)
local targetPosition = rootPart.Position + circleDirection * optimalDistance
local idealDistance = (distance < 5 and minDistance) or (distance < 15 and optimalDistance) or 10
if distance < 5 then
targetPosition = rootPart.Position - toPlayer.Unit * (minDistance - distance)
if math.random() < 0.3 then
dash(-toPlayer.Unit)
end
elseif distance > idealDistance then
targetPosition = target.Character.HumanoidRootPart.Position + target.Character.HumanoidRootPart.Velocity * predictionTime
elseif distance < 15 then
local playerLookVector = target.Character.HumanoidRootPart.CFrame.LookVector
targetPosition = target.Character.HumanoidRootPart.Position - playerLookVector * 5
end
if not boss:GetAttribute("MoveActive") and not boss:GetAttribute("Stunned") and not isDashing then
humanoid.WalkSpeed = originalWalkSpeed * (healthPercent < 0.3 and 1.2 or 1)
smoothSeek(targetPosition)
if math.random() < 0.1 then
dash(circleDirection)
end
else
humanoid.WalkSpeed = 0
end
if tick() - lastActionTime >= actionCooldown and not boss:GetAttribute("MoveActive") and not boss:GetAttribute("Stunned") and not isDashing then
if distance < 6 and math.random() < 0.6 then
Activated()
elseif distance > 10 and distance < 15 and math.random() < 0.2 then
Lunge()
else
local bestSkill = getBestSkill(distance, healthPercent, aggression)
if bestSkill then
Skills[bestSkill](boss)
lastActionTime = tick()
end
end
end
RunService.Heartbeat:Wait()
end
I also made this script with the help of AI since scripting is not my forte.
Thanks for checking out.