AI fails to use movement and attack functions at the same time

I have a Fencer AI script I wish to implement into my game, however the NPC seems to stand still instead doing both movement and attacking when the player is within certain X and Z axis and does not do any form of movement it has been given to do despite no errors in the console, for example evasive movement.

Any explanation and fix suggestions are much appreciated.

local Players = game:GetService("Players")

local AI = script.Parent
local Handle = AI:FindFirstChild("Sword"):FindFirstChild("Handle")
local ZoneXMin = -10
local ZoneXMax = 10 
local ZoneZMin = -10
local ZoneZMax = 10
local StartingPosition = Vector3.new(0, 0, 0)

local DamageValues = {
	BaseDamage = 5,
	SlashDamage = 10,
	LungeDamage = 30
}

Damage = DamageValues.BaseDamage

local Sounds = {
	Slash = Handle:WaitForChild("SwordSlash"),
	Lunge = Handle:WaitForChild("SwordLunge"),
	Unsheath = Handle:WaitForChild("Unsheath")
}

local Grips = {
	Up = CFrame.new(0, 0, -1.70000005, 0, 0, 1, 1, 0, 0, 0, 1, 0),
	Out = CFrame.new(0, 0, -1.70000005, 0, 1, 0, 1, -0, 0, 0, 0, -1)
}

local function CalculateDistance(playerPosition, aiPosition)
	return (playerPosition - aiPosition).Magnitude
end

local function FaceTarget(targetPosition, aiPosition)
	local direction = (targetPosition - aiPosition).unit
	AI:SetPrimaryPartCFrame(CFrame.new(aiPosition, aiPosition + direction))
end

local function PlayAttackAnimation(attackType)
	local Anim = Instance.new("StringValue")
	Anim.Name = "toolanim"

	if attackType == "slash" then
		Anim.Value = "Slash"
	elseif attackType == "lunge" then
		Anim.Value = "Lunge"
	end

	Anim.Parent = AI:FindFirstChild("Sword")
end

local function MoveTo(position)
	local humanoid = AI:FindFirstChild("Humanoid")
	if humanoid then
		humanoid:MoveTo(position)
	end
end

local function Jump()
	local humanoid = AI:FindFirstChild("Humanoid")
	if humanoid then
		humanoid:Move(Vector3.new(0, 10, 0))
	end
end

local function EvasiveMove(targetPosition)
	local evadeDistance = 5
	local randomAngle = math.random() * 360

	local evasivePosition = targetPosition + Vector3.new(math.cos(math.rad(randomAngle)) * evadeDistance, 0, math.sin(math.rad(randomAngle)) * evadeDistance)

	local shouldJump = math.random() < 0.3
	if shouldJump then
		Jump()
	end

	MoveTo(evasivePosition)
end

local function Lunge(targetPosition)
	Damage = DamageValues.LungeDamage
	Sounds.Lunge:Play()
	wait(0.2)
	AI:FindFirstChild("Sword").Grip = Grips.Out
	wait(0.6)
	AI:FindFirstChild("Sword").Grip = Grips.Up

	Damage = DamageValues.SlashDamage
end

local function Attack(targetPosition)
	Damage = DamageValues.SlashDamage
	Sounds.Slash:Play()

	PlayAttackAnimation("slash")

	local targetDistance = CalculateDistance(targetPosition, AI.HumanoidRootPart.Position)
	if targetDistance < 5 then
		PlayAttackAnimation("lunge")
		wait(0.2)

		local movementTask = coroutine.wrap(function()
			while targetDistance < 5 do
				MoveTo(targetPosition)
				wait(0.1)
				targetDistance = CalculateDistance(targetPosition, AI.HumanoidRootPart.Position)
			end
		end)
		movementTask()

		Lunge(targetPosition)

		wait(movementTask)
	else
		local shouldEvade = math.random() < 0.2
		if shouldEvade then
			EvasiveMove(targetPosition)
		else
			MoveTo(targetPosition)
		end
	end
end

local function CheckIfInZone(playerPosition)
	return playerPosition.X >= ZoneXMin and playerPosition.X <= ZoneXMax and
		playerPosition.Z >= ZoneZMin and playerPosition.Z <= ZoneZMax
end

local function GetClosestPlayer()
	local closestPlayer = nil
	local closestDistance = math.huge

	for _, player in ipairs(Players:GetPlayers()) do
		local character = player.Character
		if character then
			local humanoid = character:FindFirstChild("Humanoid")
			if humanoid and humanoid.Health > 0 and CheckIfInZone(character.PrimaryPart.Position) then
				local distance = CalculateDistance(character.PrimaryPart.Position, AI.HumanoidRootPart.Position)
				if distance < closestDistance then
					closestDistance = distance
					closestPlayer = player
				end
			end
		end
	end

	return closestPlayer
end

local function ReturnToStartingPosition()
	MoveTo(StartingPosition)
end

local function MainLoop()
	while true do
		local targetPlayer = GetClosestPlayer()
		if targetPlayer then
			local targetPosition = targetPlayer.Character.PrimaryPart.Position
			Attack(targetPosition)
			FaceTarget(targetPosition, AI.HumanoidRootPart.Position)
		else
			MoveTo(StartingPosition)
		end
		wait(1)
	end
end

MainLoop()

NPC Model with sword: FencerAI.rbxm (19.1 KB)

It has something to do with how often you call each function. I changed the wait time in the MainLoop to 10 seconds and the movement worked.

Unfortunately this also makes the attack duration once every 10 seconds, which is unwanted.

I know, I’m just telling you what the problem is. You most likely have to yield for other events, I’m just telling you that timing is the problem. I’ve also noticed the walk only works once, because your CheckIfInZone function only fires if the player is near the center.

Hello, I did quite a bit of changes, here are the changes I did:

I noticed that the function FaceTarget was setting the CFrame insteading of rotating to face it, this caused other issues that are more prevelant later on. Here is the changes.

local function FaceTarget(targetPosition, aiPosition)
	local direction = (targetPosition - aiPosition).unit
	AI:PivotTo(CFrame.new(aiPosition, ((aiPosition + direction) * Vector3.new(1, 0, 1)) + Vector3.new(0, aiPosition.Y, 0)))
end

Another issue I found was in the Attack function, there were alot of changes I had to do there, but one I can point out is that the movement task while loop was useful, but it had alot of limitations for the way the ai works. Here is the changes.

local function Attack(targetPosition)
	Damage = DamageValues.SlashDamage

	local targetDistance = CalculateDistance(targetPosition, AI.HumanoidRootPart.Position)
	
	if targetDistance < 5 then
		task.spawn(function()
			PlayAttackAnimation("lunge")
			FaceTarget(targetPosition, AI.HumanoidRootPart.Position)
			
			Sounds.Slash:Play()
			
			wait(0.2)

			Lunge()
		end)
	else
		local shouldEvade = math.random() < 0.2
		
		if shouldEvade then
			EvasiveMove(targetPosition)
		else
			MoveTo(targetPosition)
		end
	end
end

Another thing I saw was in the function CheckIfInZone fired incorrectly and would only fire at 0,0,0. This caused issues with detection. Here is the changes.

local function CheckIfInZone(playerPosition)
	return CalculateDistance(playerPosition, AI.HumanoidRootPart.Position) <= 80
end

Final Change I did was move FaceTarget into the Attack function, this would just help with how ai functions and does not break anything. Here is the changes.

local function MainLoop()
	while true do
		local targetPlayer = GetClosestPlayer()
		if targetPlayer then
			local targetPosition = targetPlayer.Character.HumanoidRootPart.Position
			Attack(targetPosition)
		else
			ReturnToStartingPosition()
		end
		wait(1)
	end
end

MainLoop()

Here is the rbxl file if you need it.
fenced.rbxl (63.6 KB)

If you have any other issues, let me know!

This topic was automatically closed 14 days after the last reply. New replies are no longer allowed.