Help with NPC System

Hello, I am currently making an NPC fighting and movement system using Forbidden AI, I am having a slight issue where sometimes my NPC behaves very jittery, it sometimes also flies around occasionally after gripping a player as well. Below is a video of the problem I’m experiencing and my actual code for NPC functionality. Thank you for anyone’s help.

-- Services
local RunService = game:GetService("RunService")
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local SoundService = game:GetService("SoundService")
local Server = game:GetService("ServerScriptService"):WaitForChild("Server")

-- Libraries
local ModuleLibrary = ReplicatedStorage:WaitForChild("Library")
local MiscLibrary = ModuleLibrary.Misc
local States = ModuleLibrary:WaitForChild("States")
local BridgeFolder = ModuleLibrary.Bridge
local BridgeNet2 = require(BridgeFolder.BridgeNet2)
local StateModule = require(States:WaitForChild("StateManager"))
local ActionCheck = require(States:WaitForChild("StateChecker"))
local ForbiddenAI = require(MiscLibrary.Forbidden.AI)
local Janitor = require(MiscLibrary:WaitForChild("Janitor"))

-- Combat modules
local LightAttack = require(Server.CharacterUnit.LightAttack)
local Grip = require(Server.CharacterUnit.Grip)
local Parry = require(Server.CharacterUnit.Parry)
local Util = require(MiscLibrary.Utility)
local BlockStart = require(Server.CharacterUnit.BlockStart)
local BlockStop = require(Server.CharacterUnit.BlockStop)
local Feint = require(Server.CharacterUnit.Feint)
local RunningM1 = require(Server.CharacterUnit.RunningM1)
local Aerial = require(Server.CharacterUnit.AerialAttack)

-- Weapon data
local Models = ReplicatedStorage.Assets.Models
local Welds = ReplicatedStorage.Assets.Welds
local Weapons = Models.Weapons
local WeaponWelds = Welds.Weapons

-- NPC setup
local NPC = script.Parent
local janitor = Janitor.new()
local root = NPC:WaitForChild("HumanoidRootPart")
local hum = NPC:WaitForChild("Humanoid")

-- Animation setup
local Style = NPC:GetAttribute("Style")
local Weapon = NPC:GetAttribute("Weapon")
local AnimationFolder = ReplicatedStorage.Assets.Animations
local StyleAnimations = AnimationFolder.Styles[Style][Weapon]
local MovementAnimations = StyleAnimations.Dashes.Equipped

local Crit = require(Server.CharacterUnit.Critical[Weapon])

local RunAnim = hum:LoadAnimation(StyleAnimations.Main:WaitForChild("RunEquipped"))
local WalkAnim = hum:LoadAnimation(StyleAnimations.Main:WaitForChild("Walk"))
local IdleAnim = nil

janitor:Add(RunAnim, "Stop")

-- DISABLE JUMPING
NPC.Humanoid:SetStateEnabled(Enum.HumanoidStateType.Jumping, false)

-- Enhanced Bandit Config
local DETECTION_RADIUS = 50
local UPDATE_INTERVAL = 0.1
local TARGET_POSITION_THRESHOLD = 2
local ATTACK_DISTANCE = 5
local FOLLOW_DISTANCE = 15
local STOP_DISTANCE = 4
local WALK_DISTANCE = 14
local WALK_STOP_DISTANCE = 10
local SPRINT_DISTANCE = 30 
local MOVEMENT_STATE_DELAY = 0.3 
local VISION_ANGLE = 140
local VERTICAL_VISION_ANGLE = 80
local VISION_DISTANCE = 85
local SHOW_RAYS = false
local RAY_FADE = 0.08

-- Enhanced tracking variables
local CHASE_DURATION = 10
local DIRECT_LOS_DISTANCE = 200
local MEMORY_UPDATE_DISTANCE = 35
local MAX_TRACKING_DISTANCE = 1000
local SIGHT_LOSS_BUFFER = 1.5
local TRACKING_GRACE_PERIOD = 3

-- Tracking state
local currentTarget = nil
local lastKnownPosition = nil
local lastSightTime = 0
local trackingStartTime = 0
local isActivelyTracking = false
local sightLossTime = 0
local hasEquippedWeapon = false

-- AI variables
local lastTarget, lastTargetPos = nil, nil
local lastUpdate = 0
local isPathfinding = false

-- Combat variables
local lastAttackTime = 0
local ATTACK_COOLDOWN = 0.6
local lastFeintTime = 0
local FEINT_COOLDOWN = 5.5
local lastRunningM1Time = 0
local RUNNINGM1_COOLDOWN = 3
local lastAerialTime = 0
local AERIAL_COOLDOWN = 5
local comboCount = 0
local lastComboTime = 0
local COMBO_RESET_TIME = 2
local lastMovementStateChange = 0
local DODGE_INTERVAL = 5 
local lastDodgeTime = 0
local currentMovementState = "Idle"

-- HP tracking
local maxHealth = hum.MaxHealth
local currentHPPercent = 1.0

-- Combat args
local CombatArguements = { NPC = true }

-- Forbidden AI setup
local aiConfig = ForbiddenAI.GetConfig(NPC)
aiConfig.Tracking.Enabled = true
aiConfig.Visualization.Enabled = false
aiConfig.StopType = "Instant"
if aiConfig.Movement then
	aiConfig.Movement.Jump = false
	aiConfig.Movement.MaxJumpHeight = 0
end

-- Helpers
local function IsAir() return hum:GetState() == Enum.HumanoidStateType.Freefall end
local function isCharacterDisabled(char)
	return StateModule.findAttribute(char, "BeingGripped")
		or StateModule.findAttribute(char, "BeingCarried")
		or StateModule.findAttribute(char, "Carrying")
		or StateModule.findAttribute(char, "IsDodging")
		or StateModule.findAttribute(char, "Dashing")
		or StateModule.findAttribute(char, "Gripping") 
		or StateModule.findAttribute(char, "CantMove") 
end

local function updateHPPercent()
	currentHPPercent = math.clamp(hum.Health / maxHealth, 0, 1)
end

local function hasFeature(threshold)
	return currentHPPercent <= threshold
end

local function getParryChance()
	if currentHPPercent <= 0.25 then
		return 80
	elseif currentHPPercent <= 0.5 then
		return 70
	else
		return 60
	end
end

-- Enhanced 3D vision cone function
local function isInVisionCone(npcPos, npcDir, targetPos)
	local toTarget = (targetPos - npcPos)
	local distance = toTarget.Magnitude

	if distance == 0 then return true end

	local toTargetUnit = toTarget.Unit

	-- Calculate horizontal angle (yaw)
	local npcDirHorizontal = Vector3.new(npcDir.X, 0, npcDir.Z).Unit
	local toTargetHorizontal = Vector3.new(toTarget.X, 0, toTarget.Z).Unit

	local horizontalAngle = math.deg(math.acos(math.clamp(npcDirHorizontal:Dot(toTargetHorizontal), -1, 1)))

	-- Calculate vertical angle (pitch)
	local verticalAngle = math.deg(math.asin(math.clamp(toTargetUnit.Y, -1, 1)))
	local npcPitch = math.deg(math.asin(math.clamp(npcDir.Y, -1, 1)))
	local relativePitch = math.abs(verticalAngle - npcPitch)

	-- Check if target is within both horizontal and vertical FOV
	local withinHorizontalFOV = horizontalAngle <= (VISION_ANGLE / 2)
	local withinVerticalFOV = relativePitch <= (VERTICAL_VISION_ANGLE / 2)

	return withinHorizontalFOV and withinVerticalFOV
end

-- Enhanced debug ray
local function drawDebugRay(fromPos, toPos, blocked)
	if not SHOW_RAYS then return end
	local part = Instance.new("Part")
	part.Anchored = true
	part.CanCollide = false
	part.Material = Enum.Material.Neon
	part.Transparency = 0.9
	part.Color = blocked and Color3.fromRGB(255,0,0) or Color3.fromRGB(0,255,0)
	local length = (toPos - fromPos).Magnitude
	part.Size = Vector3.new(0.1,0.1,length)
	part.CFrame = CFrame.lookAt(fromPos, toPos) * CFrame.new(0,0,-length/2)
	part.Name = "DebugVisionRay"
	part.Parent = workspace
	game.Debris:AddItem(part, RAY_FADE)
end

local blacklistParams = RaycastParams.new()
blacklistParams.FilterType = Enum.RaycastFilterType.Exclude
blacklistParams.IgnoreWater = true

local function getLOSOrigin(character)
	local head = character:FindFirstChild("Head")
	if head then return head.Position end
	local hrp = character:FindFirstChild("HumanoidRootPart")
	return hrp and (hrp.Position + Vector3.new(0,1.5,0)) or root.Position
end

local function hasLineOfSight(fromModel, toModel)
	local fromPos = getLOSOrigin(fromModel)
	local toPos = getLOSOrigin(toModel)
	blacklistParams.FilterDescendantsInstances = {fromModel, toModel}
	local dir = (toPos - fromPos)
	local result = workspace:Raycast(fromPos, dir, blacklistParams)
	drawDebugRay(fromPos, result and result.Position or toPos, result ~= nil)
	return not result
end

-- Completely rewritten target detection with robust tracking
local function getNearestCharacter(position)
	local nearest, nearestDist = nil, math.huge
	local npcLook = root.CFrame.LookVector
	local currentTime = os.clock()

	local function evaluateTarget(char)
		if not char or char == NPC then return end
		local hrp = char:FindFirstChild("HumanoidRootPart")
		if not hrp then return end

		local targetPos = hrp.Position
		local distance = (position - targetPos).Magnitude

		if distance > MAX_TRACKING_DISTANCE then 
			return 
		end

		local hasLOS = hasLineOfSight(NPC, char)
		local inVisionCone = isInVisionCone(position, npcLook, targetPos)
		local isCurrentTarget = (currentTarget == char)
		local timeSinceLastSight = isCurrentTarget and (currentTime - lastSightTime) or math.huge
		local trackingDuration = isCurrentTarget and (currentTime - trackingStartTime) or 0

		local canDetect = false
		local detectionReason = ""

		-- Tier 1: Perfect visibility
		if hasLOS and inVisionCone and distance <= VISION_DISTANCE then
			canDetect = true
			detectionReason = "direct_sight"
		end

		-- Tier 2: Close range detection
		if not canDetect and hasLOS and distance <= 30 then
			canDetect = true
			detectionReason = "close_range"
		end

		-- Tier 3: Active tracking continuation
		if not canDetect and isCurrentTarget and isActivelyTracking then
			if timeSinceLastSight <= CHASE_DURATION then
				if hasLOS then
					canDetect = true
					detectionReason = "tracking_los"
				elseif distance <= MEMORY_UPDATE_DISTANCE then
					canDetect = true
					detectionReason = "tracking_close"
				elseif timeSinceLastSight <= SIGHT_LOSS_BUFFER then
					canDetect = true
					detectionReason = "sight_buffer"
				end
			end
		end

		-- Tier 4: Sound detection
		if not canDetect and distance <= 12 then
			canDetect = true
			detectionReason = "sound_detection"
		end

		if canDetect and distance < nearestDist then
			nearest = char
			nearestDist = distance

			if detectionReason == "direct_sight" or detectionReason == "close_range" then
				if currentTarget ~= char then
					currentTarget = char
					trackingStartTime = currentTime
					isActivelyTracking = true
				end
				lastKnownPosition = targetPos
				lastSightTime = currentTime
				sightLossTime = 0
			elseif detectionReason == "tracking_los" or detectionReason == "tracking_close" then
				lastKnownPosition = targetPos
				if hasLOS then
					lastSightTime = currentTime
					sightLossTime = 0
				elseif sightLossTime == 0 then
					sightLossTime = currentTime
				end
			elseif detectionReason == "sight_buffer" then
				if sightLossTime == 0 then
					sightLossTime = currentTime
				end
			else
				if currentTarget ~= char then
					currentTarget = char
					trackingStartTime = currentTime
					isActivelyTracking = true
					lastKnownPosition = targetPos
					lastSightTime = currentTime
					sightLossTime = 0
				end
			end
		end
	end

	for _, p in pairs(game.Players:GetPlayers()) do
		if p.Character then evaluateTarget(p.Character) end
	end
	for _, char in pairs(workspace.Alive:GetChildren()) do
		if char:IsA("Model") and char ~= NPC and char:FindFirstChild("HumanoidRootPart") and not char:FindFirstChild("NPCValues") then
			evaluateTarget(char)
		end
	end

	if not nearest and currentTarget and isActivelyTracking then
		local targetHRP = currentTarget:FindFirstChild("HumanoidRootPart")
		if targetHRP then
			local distanceToCurrentTarget = (position - targetHRP.Position).Magnitude
			local timeSinceLastSight = currentTime - lastSightTime
			local timeSinceSightLoss = sightLossTime > 0 and (currentTime - sightLossTime) or 0

			local shouldContinueTracking = false

			if distanceToCurrentTarget <= MAX_TRACKING_DISTANCE then
				if timeSinceLastSight <= CHASE_DURATION then
					shouldContinueTracking = true
				elseif lastKnownPosition then
					local distanceToLastKnown = (position - lastKnownPosition).Magnitude
					if distanceToLastKnown <= FOLLOW_DISTANCE * 2 and timeSinceLastSight <= CHASE_DURATION + TRACKING_GRACE_PERIOD then
						shouldContinueTracking = true
					end
				end
			end

			if shouldContinueTracking then
				nearest = currentTarget
				nearestDist = distanceToCurrentTarget
			else
				currentTarget = nil
				lastKnownPosition = nil
				isActivelyTracking = false
				trackingStartTime = 0
				lastSightTime = 0
				sightLossTime = 0
			end
		else
			currentTarget = nil
			lastKnownPosition = nil
			isActivelyTracking = false
			trackingStartTime = 0
			lastSightTime = 0
			sightLossTime = 0
		end
	elseif not nearest then
		currentTarget = nil
		lastKnownPosition = nil
		isActivelyTracking = false
		trackingStartTime = 0
		lastSightTime = 0
		sightLossTime = 0
	end

	return nearest, nearestDist
end

-- Enhanced target position calculation
local function getTargetPosition(target)
	if not target then return nil end

	local hrp = target:FindFirstChild("HumanoidRootPart")
	local currentTime = os.clock()

	if hrp then
		local distance = (root.Position - hrp.Position).Magnitude
		local hasRecentSight = (currentTime - lastSightTime) <= SIGHT_LOSS_BUFFER

		if hasLineOfSight(NPC, target) or distance <= MEMORY_UPDATE_DISTANCE or hasRecentSight then
			return hrp.Position
		end
	end

	if lastKnownPosition and currentTarget == target then
		local timeSinceUpdate = currentTime - lastSightTime
		local distanceToLastKnown = (root.Position - lastKnownPosition).Magnitude

		if timeSinceUpdate <= CHASE_DURATION and distanceToLastKnown <= FOLLOW_DISTANCE * 3 then
			return lastKnownPosition
		end
	end

	if hrp then
		return hrp.Position
	end

	return nil
end

local function isAggroedByOtherNPCs(character)
	for _, npc in pairs(workspace.Alive:GetChildren()) do
		if npc ~= NPC and npc:FindFirstChild("NPCValues") then
			local aggro = npc.NPCValues:FindFirstChild("Aggro")
			if aggro and aggro.Value == character.Name then return true end
		end
	end
	return false
end

-- Adjust weapon visuals
local function adjustWeapon(isEquipped)
	local WeaponFolder = NPC:FindFirstChild("WeaponFolder")
	if not WeaponFolder then
		WeaponFolder = Instance.new("Folder")
		WeaponFolder.Name = "WeaponFolder"
		WeaponFolder.Parent = NPC
	else
		WeaponFolder:ClearAllChildren()
	end

	local weaponName = NPC:GetAttribute("Weapon") or "Fists"
	local styleName = NPC:GetAttribute("Style") or "None"
	local WeaponToFind = Weapons:FindFirstChild(weaponName)

	if WeaponToFind and WeaponToFind:IsA("Folder") then
		for _, weaponModel in pairs(WeaponToFind:GetChildren()) do
			if weaponModel:IsA("Model") and weaponModel.PrimaryPart then
				local NewModel = weaponModel:Clone()
				NewModel.Parent = WeaponFolder

				local WeldPart = NewModel.PrimaryPart
				local WeldTargetAttr = isEquipped and "WeldTarget_Equipped" or "WeldTarget_Unequipped"
				local WeldTargetName = WeldPart:GetAttribute(WeldTargetAttr)
				local BasePart = NPC:FindFirstChild(WeldTargetName)

				if BasePart and WeldPart then
					local weaponWeldFolder = WeaponWelds[weaponName]
					local modelWeldData = weaponWeldFolder and weaponWeldFolder[weaponModel.Name]

					if modelWeldData then
						local Motor6D = (isEquipped and modelWeldData.EquippedWeld or modelWeldData.UnequippedWeld):Clone()
						Motor6D.Name = "Handle"
						Motor6D.Part0 = BasePart
						Motor6D.Part1 = WeldPart
						Motor6D.Parent = WeldPart
					end
				end

				for _, child in pairs(NewModel:GetDescendants()) do
					local style = child:GetAttribute("IsStyle")
					if style and style ~= styleName then
						child:Destroy()
					end
				end
			end
		end
	end
end

-- Equip weapon
local function equipWeapon()
	if hasEquippedWeapon then return end
	if StateModule.findAttribute(NPC, "Equipped") then return end

	local weaponName = NPC:GetAttribute("Weapon") or "Fists"
	local equipSound = SoundService.Weapons:FindFirstChild(weaponName)
	if equipSound and equipSound:FindFirstChild("Equip") then
		local sound = equipSound.Equip:Clone()
		sound.Parent = root
		sound:Play()
		game.Debris:AddItem(sound, sound.TimeLength + 0.1)
	end

	adjustWeapon(true)

	if not IdleAnim then
		IdleAnim = hum:LoadAnimation(StyleAnimations.Main.Idle)
		IdleAnim.Priority = Enum.AnimationPriority.Idle
	end

	if IdleAnim and not IdleAnim.IsPlaying then
		IdleAnim:Play()
	end

	StateModule.createAttribute(NPC, "Equipped", true)
	hasEquippedWeapon = true
end

-- Unequip weapon
local function unequipWeapon()
	if not hasEquippedWeapon then return end
	if not StateModule.findAttribute(NPC, "Equipped") then return end

	local weaponName = NPC:GetAttribute("Weapon") or "Fists"
	local equipSound = SoundService.Weapons:FindFirstChild(weaponName)
	if equipSound and equipSound:FindFirstChild("Equip") then
		local sound = equipSound.Equip:Clone()
		sound.Parent = root
		sound:Play()
		game.Debris:AddItem(sound, sound.TimeLength + 0.1)
	end

	if IdleAnim and IdleAnim.IsPlaying then
		IdleAnim:Stop()
	end

	adjustWeapon(false)

	StateModule.removeAttribute(NPC, "Equipped")
	hasEquippedWeapon = false
end

-- Animations
local function playWalk() 
	if not WalkAnim.IsPlaying and not isCharacterDisabled(NPC) then 
		WalkAnim:Play() 
	end 
end

local function stopWalk() 
	if WalkAnim.IsPlaying then 
		WalkAnim:Stop() 
	end 
end

local function playRun() 
	if not IsAir() and not isCharacterDisabled(NPC) then
		if not RunAnim.IsPlaying then 
			RunAnim:Play()
			RunAnim:AdjustSpeed(1.2)
			RunAnim.Priority = Enum.AnimationPriority.Action2 
		end
	end 
end

local function stopRun()
	if RunAnim.IsPlaying then
		RunAnim:Stop()
	end
end

-- Feinting system
local function attemptFeint()
	if not hasFeature(0.75) then return false end

	local currentTime = os.clock()
	if (currentTime - lastFeintTime) < FEINT_COOLDOWN then return false end

	if not NPC:GetAttribute("CanFeint") then return false end

	local comboValue = NPC:FindFirstChild("CombatStats") and NPC.CombatStats:FindFirstChild("ComboValue")
	if not comboValue or comboValue.Value <= 0 then return false end

	if math.random(1, 100) <= 100 then
		lastFeintTime = currentTime

		task.delay(.2, function()
			Feint.fire(NPC, CombatArguements)
		end)

		return true
	end

	return false
end

-- Dodge cancel system
local function attemptDodgeCancel()
	if not hasFeature(0.5) then return false end

	if not StateModule.findAttribute(NPC, "Dashing") then return false end

	if math.random(1, 100) <= 35 then
		StateModule.removeAttribute(NPC, "Dashing")

		local cancelAnimName = math.random(1, 2) == 1 and "CancelLeft" or "CancelRight"
		local cancelAnim = MovementAnimations:FindFirstChild(cancelAnimName)

		if cancelAnim then
			local CancelAnimation = hum:LoadAnimation(cancelAnim)
			CancelAnimation:Play()
			CancelAnimation.Priority = Enum.AnimationPriority.Action3
		end

		local highlight = Instance.new("Highlight")
		highlight.FillColor = Color3.fromRGB(255, 255, 255)
		highlight.OutlineColor = Color3.fromRGB(255, 255, 255)
		highlight.FillTransparency = 0
		highlight.OutlineTransparency = 0
		highlight.Parent = NPC

		task.spawn(function()
			local fadeTime = 1
			local steps = 20
			local stepTime = fadeTime / steps
			for i = 1, steps do
				if highlight and highlight.Parent then
					highlight.FillTransparency = i / steps
					highlight.OutlineTransparency = i / steps
				end
				task.wait(stepTime)
			end
			if highlight and highlight.Parent then
				highlight:Destroy()
			end
		end)

		return true
	end

	return false
end

local function attemptRunningM1()
	local currentTime = os.clock()
	if (currentTime - lastRunningM1Time) < RUNNINGM1_COOLDOWN then return false end
	if isCharacterDisabled(NPC) then  return false end
	if StateModule.findAttribute(NPC, "Aerial") then return false end

	RunningM1.fire(NPC)
	lastRunningM1Time = currentTime

	return true
end

local function attemptAerial()
	local currentTime = os.clock()
	if (currentTime - lastAerialTime) < AERIAL_COOLDOWN then return false end
	if isCharacterDisabled(NPC) then return false end
	if StateModule.findAttribute(NPC, "Aerial") then return false end

	-- Mark as performing aerial to prevent other attacks
	StateModule.createAttribute(NPC, "Aerial", true)

	-- Temporarily enable jumping
	hum:SetStateEnabled(Enum.HumanoidStateType.Jumping, true)
	hum.JumpHeight = 7.2
	hum.Jump = true

	-- Disable after jump and fire aerial
	task.delay(0.2, function()
		hum:SetStateEnabled(Enum.HumanoidStateType.Jumping, false)
		hum.JumpHeight = 0
		Aerial.fire(NPC)

		-- Remove aerial flag after attack completes
		task.delay(1, function()
			StateModule.removeAttribute(NPC, "Aerial")
		end)
	end)

	lastAerialTime = currentTime
	return true
end

-- Enhanced dash system
local function dash(rootPart, humanoid, char)
	if isCharacterDisabled(char) or StateModule.findAttribute(char, "DashCooldown") then 
		return 
	end

	local dirs, strings
	if currentTarget then
		local targetHRP = currentTarget:FindFirstChild("HumanoidRootPart")
		if targetHRP then
			local toTarget = (targetHRP.Position - rootPart.Position).Unit
			local dotForward = rootPart.CFrame.LookVector:Dot(toTarget)
			local dotRight = rootPart.CFrame.RightVector:Dot(toTarget)

			if math.abs(dotForward) > 0.7 then
				dirs = {-rootPart.CFrame.RightVector, rootPart.CFrame.RightVector}
				strings = {"Left", "Right"}
			else
				dirs = {rootPart.CFrame.LookVector, -rootPart.CFrame.LookVector, -rootPart.CFrame.RightVector, rootPart.CFrame.RightVector}
				strings = {"Forward","Back","Left","Right"}
			end
		else
			dirs = {rootPart.CFrame.LookVector, -rootPart.CFrame.LookVector, -rootPart.CFrame.RightVector, rootPart.CFrame.RightVector}
			strings = {"Forward","Back","Left","Right"}
		end
	else
		dirs = {rootPart.CFrame.LookVector, -rootPart.CFrame.LookVector, -rootPart.CFrame.RightVector, rootPart.CFrame.RightVector}
		strings = {"Forward","Back","Left","Right"}
	end

	local idx = math.random(1,#dirs)
	local dir, str = dirs[idx], strings[idx]

	if ActionCheck.CanDash(char) then
		StateModule.CreateAttributeTemp(char, "IsDodging", true, 0.3)
		StateModule.CreateAttributeTemp(char, "DashCooldown", true, 1.8)
		StateModule.CreateAttributeTemp(char, "Dashing", true, 0.35)

		local anim = humanoid:LoadAnimation(MovementAnimations:FindFirstChild(str))
		anim:Play()
		anim.Priority = Enum.AnimationPriority.Action3
		anim:AdjustSpeed(1.25)

		local bv = Instance.new("BodyVelocity")
		bv.MaxForce = Vector3.new(math.huge, 0, math.huge)
		bv.Velocity = Vector3.new(dir.X * 65, 0, dir.Z * 65)
		bv.Parent = rootPart
		game.Debris:AddItem(bv, 0.25)

		task.defer(function()
			while bv.Parent do
				local flatVelocity = Vector3.new(0, 0, 0)
				if str == "Forward" then 
					flatVelocity = Vector3.new(rootPart.CFrame.LookVector.X * 70, 0, rootPart.CFrame.LookVector.Z * 70)
				elseif str == "Back" then 
					flatVelocity = Vector3.new(-rootPart.CFrame.LookVector.X * 70, 0, -rootPart.CFrame.LookVector.Z * 70)
				elseif str == "Left" then 
					flatVelocity = Vector3.new(-rootPart.CFrame.RightVector.X * 70, 0, -rootPart.CFrame.RightVector.Z * 70)
				elseif str == "Right" then 
					flatVelocity = Vector3.new(rootPart.CFrame.RightVector.X * 70, 0, rootPart.CFrame.RightVector.Z * 70)
				end
				bv.Velocity = flatVelocity
				task.wait(0.01)
			end
		end)
	end
end

-- Enhanced pathfinding
local function startPathfinding(target)
	if not isPathfinding then 
		isPathfinding = true
		ForbiddenAI.SmartPathfind(NPC, target, false, "DefaultStart")
	end
end

local function stopPathfinding() 
	if isPathfinding then 
		isPathfinding = false
		ForbiddenAI.Stop(NPC, false, "DefaultStop")
	end 
end

local function updateCombo()
	local currentTime = os.clock()
	if (currentTime - lastComboTime) > COMBO_RESET_TIME then
		comboCount = 0
	end
	comboCount = comboCount + 1
	lastComboTime = currentTime
end

local function dodgeOrBlock()
	if hasFeature(0.25) then
		if math.random(1, 100) <= 30 then
			return true
		end
	elseif hasFeature(0.5) then
		if math.random(1, 100) <= 20 then
			return true
		end
	else
		if math.random(1, 100) <= 10 then
			return true
		end
	end
	return false
end

-- SEPARATE DEFENSIVE REACTION SYSTEM (handles all ranges)
local function handleDefense(target, distance, canSeeTarget)
	local currentTime = os.clock()
	updateHPPercent()

	-- Check target state
	local targetIsAttacking = StateModule.findAttribute(target, "LightAttack") 
		or StateModule.findAttribute(target, "HeavyAttack")
		or StateModule.findAttribute(target, "UsingMove")
	local targetIsBlocking = StateModule.findAttribute(target, "IsBlocking")

	-- Stop blocking if already blocking and target isn't attacking
	if StateModule.findAttribute(NPC, "IsBlocking") and not targetIsAttacking then
		BlockStop.fire(NPC, CombatArguements)
	end

	-- Attempt dodge cancel during dash
	if attemptDodgeCancel() then return true end

	-- React to target attacking at ANY range (including far-range moves)
	if targetIsAttacking then
		-- Try to feint if we're in the middle of an attack
		attemptFeint()

		-- Defensive responses based on distance and HP
		if ActionCheck.CanBlock(NPC) and not StateModule.findAttribute(NPC, "ParryCooldown") then
			local parryChance = getParryChance()

			-- Close range - prefer parry
			if distance <= ATTACK_DISTANCE then
				if math.random(1, 100) <= parryChance then
					local anim = hum:LoadAnimation(AnimationFolder.Styles[Style][Weapon].Blocking.Blocking)
					anim:Play()
					Parry.fire(NPC, CombatArguements)
					StateModule.CreateAttributeTemp(NPC, "IsBlocking", true, 0.35)
					task.wait(0.35)
					anim:Stop()
					return true
				end
			end

			-- Any range - dodge or block based on HP and RNG
			if not StateModule.findAttribute(NPC, "IsBlocking") and dodgeOrBlock() then
				if ActionCheck.CanDash(NPC) and distance >= 5 then
					-- Prefer dodge at medium-far range
					dash(root, hum, NPC)
					return true
				else
					-- Block if can't dash or too close
					BlockStart.fire(NPC, {Target = target})
					local blockDuration = currentHPPercent <= 0.5 and 0.8 or 0.5
					StateModule.CreateAttributeTemp(NPC, "BlockCooldown", true, blockDuration)

					task.delay(math.random(1, 2), function()
						if StateModule.findAttribute(NPC, "IsBlocking") then
							BlockStop.fire(NPC, CombatArguements)
						end
					end)
					return true
				end
				-- Fallback dodge if no other option
			elseif ActionCheck.CanDash(NPC) and distance >= 5 and math.random(1, 100) <= 40 then
				dash(root, hum, NPC)
				return true
			end
		end
	end

	return false
end

-- SEPARATE OFFENSIVE SYSTEM (handles attacks)
local function handleOffense(target, distance, canSeeTarget)
	local currentTime = os.clock()

	-- Handle knocked targets
	if target:GetAttribute("Knocked") then
		if distance <= ATTACK_DISTANCE * 1.5 then
			if not StateModule.findAttribute(NPC, "Gripping") and not StateModule.findAttribute(NPC, "BeingGripped") then
				Grip.fire(NPC, {Target = target, NPC = true})
				return true
			end
		end
		return false
	end

	-- Check if we're in attack cooldown
	if (currentTime - lastAttackTime) < ATTACK_COOLDOWN then
		return false
	end

	local targetIsBlocking = StateModule.findAttribute(target, "IsBlocking")

	-- Offensive actions - close range attacks
	if distance <= ATTACK_DISTANCE and canSeeTarget then
		if not StateModule.findAttribute(NPC, "LightAttack") 
			and not StateModule.findAttribute(NPC, "HeavyAttack")
			and not StateModule.findAttribute(NPC, "IsBlocking")
			and not StateModule.findAttribute(NPC, "IsDodging") then

			lastAttackTime = currentTime
			updateCombo()

			-- Intelligent attack choice based on situation
			local attackChoice = math.random(1, 100)

			if targetIsBlocking then
				if attackChoice <= 30 then
					Crit.fire(NPC, CombatArguements)
				else
					LightAttack.fire(NPC, CombatArguements)
				end
			else
				LightAttack.fire(NPC, CombatArguements)
			end

			if NPC:GetAttribute("CanFeint") then
				if attemptFeint() then
					return true
				end
			end
			return true
		end
	end

	return false 
end

-- Main AI initialization
local function InitiateBandit()
	local cleanup = Janitor.new()
	ForbiddenAI.InsertAntiLag(NPC)

	-- HP monitoring
	local hpConn = hum.HealthChanged:Connect(function(health)
		updateHPPercent()
	end)
	cleanup:Add(hpConn)

	-- Enhanced AI loop with better state management
	local targetFPS = 60
	local targetDelta = 1 / targetFPS
	local accumulator = 0

	local aiConn = RunService.Heartbeat:Connect(function(dt)
		accumulator += dt
		while accumulator >= targetDelta do
			accumulator -= targetDelta

			-- Dynamic movement speed based on state and distance
			local WalkSpeed = 0
			local now = os.clock()

			if currentTarget and isActivelyTracking and not isCharacterDisabled(NPC) then
				local targetHRP = currentTarget:FindFirstChild("HumanoidRootPart")
				if targetHRP then
					local distance = (root.Position - targetHRP.Position).Magnitude
					local desiredState = currentMovementState
					
					-- Periodic defensive dodge
					if now - lastDodgeTime >= DODGE_INTERVAL then
						lastDodgeTime = now
						if not isCharacterDisabled(NPC) and ActionCheck.CanDash(NPC) then
							dash(root, hum, NPC)
						end
					end

					-- Determine desired movement state with hysteresis
					if distance <= STOP_DISTANCE then
						desiredState = "Stop"
					elseif distance <= WALK_STOP_DISTANCE then
						desiredState = "Walk"
					elseif distance <= WALK_DISTANCE then
						desiredState = "Walk"
					elseif distance <= SPRINT_DISTANCE then
						desiredState = "Run"
					else
						desiredState = "Sprint"
					end

					-- Only change state if enough time has passed OR if it's a major change
					local canChangeState = (now - lastMovementStateChange) >= MOVEMENT_STATE_DELAY
					local isMajorChange = (currentMovementState == "Stop" and desiredState ~= "Stop") or
						(currentMovementState ~= "Stop" and desiredState == "Stop")

					if desiredState ~= currentMovementState and (canChangeState or isMajorChange) then
						currentMovementState = desiredState
						lastMovementStateChange = now
					end

					-- Apply movement state
					if currentMovementState == "Stop" then
						WalkSpeed = 0
						stopRun()
						stopWalk()
						StateModule.removeAttribute(NPC, "Sprint")
					elseif currentMovementState == "Walk" then
						WalkSpeed = 16
						stopRun()
						playWalk()
						StateModule.removeAttribute(NPC, "Sprint")
					elseif currentMovementState == "Run" then
						StateModule.createAttribute(NPC, "Sprint", true)
						WalkSpeed = 28
						playRun()
						stopWalk()
					elseif currentMovementState == "Sprint" then
						StateModule.createAttribute(NPC, "Sprint", true)
						WalkSpeed = 32
						playRun()
						stopWalk()
					end

					-- Apply status effects
					if StateModule.findAttribute(NPC, "TrueSlow") then
						WalkSpeed = 4
					elseif StateModule.findAttribute(NPC, "Slowed") then
						WalkSpeed = 10
					end
				else
					-- No target HRP - sprint to last known
					if currentMovementState ~= "Sprint" then
						currentMovementState = "Sprint"
						lastMovementStateChange = now
					end
					StateModule.createAttribute(NPC, "Sprint", true)
					WalkSpeed = 32
					playRun()
					stopWalk()
				end
			else
				-- Idle state
				if currentMovementState ~= "Idle" then
					currentMovementState = "Idle"
					lastMovementStateChange = now
				end
				if StateModule.findAttribute(NPC, "Sprint") then
					StateModule.removeAttribute(NPC, "Sprint")
				end
				stopRun()
				stopWalk()
			end

			-- Apply movement settings
			hum.WalkSpeed = WalkSpeed
			hum.JumpHeight = 7.2
		end

		-- Main AI logic - runs at UPDATE_INTERVAL
		local now = os.clock()
		if now - lastUpdate < UPDATE_INTERVAL then return end
		lastUpdate = now

		-- Handle disabled states
		if NPC:GetAttribute("Ragdoll") or isCharacterDisabled(NPC) then
			stopPathfinding()
			NPC.NPCValues.State.Value = "Idle"
			NPC.NPCValues.Aggro.Value = ""
			lastTarget, lastTargetPos = nil, nil
			currentTarget = nil
			lastKnownPosition = nil
			isActivelyTracking = false
			return
		end

		-- Enhanced target detection
		local nearest, distance = getNearestCharacter(root.Position)

		if nearest and distance <= MAX_TRACKING_DISTANCE and not StateModule.findAttribute(nearest, "Dead") then
			-- Equip weapon when target is detected
			if not hasEquippedWeapon then
				equipWeapon()
			end

			-- Check for NPC conflicts with improved logic
			if isAggroedByOtherNPCs(nearest) and NPC.NPCValues.Aggro.Value ~= nearest.Name then
				local trackingDuration = isActivelyTracking and (now - trackingStartTime) or 0
				if trackingDuration < 3 then
					NPC.NPCValues.State.Value = "Idle"
					NPC.NPCValues.Aggro.Value = ""
					stopPathfinding()
					return
				end
			end

			-- Update aggro
			NPC.NPCValues.Aggro.Value = nearest.Name
			local targetPos = getTargetPosition(nearest)

			if targetPos then
				local canSeeTarget = hasLineOfSight(NPC, nearest)

				-- ALWAYS check for defensive reactions first (works at ANY range)
				handleDefense(nearest, distance, canSeeTarget)

				-- Handle combat/movement based on distance
				if distance <= ATTACK_DISTANCE and canSeeTarget then
					-- Close combat range
					stopPathfinding()
					hum:Move(Vector3.zero)
					NPC.NPCValues.State.Value = "Attacking"
					handleOffense(nearest, distance, canSeeTarget)

				elseif distance <= STOP_DISTANCE and canSeeTarget then
					-- Approach for attack
					stopPathfinding()
					NPC.NPCValues.State.Value = "Approaching"
					if distance > ATTACK_DISTANCE then
						hum:MoveTo(targetPos)
					else
						handleOffense(nearest, distance, canSeeTarget)
					end

				elseif distance <= 20 and distance >= WALK_DISTANCE and canSeeTarget then
					-- Medium range - attempt running attack
					if not StateModule.findAttribute(NPC, "LightAttack") then
						if math.random() < 0.5 then
							attemptRunningM1()
						else
							attemptAerial()
						end
					end
					NPC.NPCValues.State.Value = "Running"

				else
					-- Long range - chase/pathfind
					if canSeeTarget then
						NPC.NPCValues.State.Value = "Running"
					else
						NPC.NPCValues.State.Value = "Chasing"
					end

					-- Enhanced pathfinding logic
					local needsNewPath = false
					if not lastTarget or lastTarget ~= nearest then
						needsNewPath = true
					elseif lastTargetPos then
						local positionDelta = (lastTargetPos - targetPos).Magnitude
						if positionDelta > TARGET_POSITION_THRESHOLD then
							needsNewPath = true
						end
					else
						needsNewPath = true
					end

					if needsNewPath then
						lastTarget, lastTargetPos = nearest, targetPos
						if isPathfinding then 
							stopPathfinding()
							task.wait(0.05)
						end
						startPathfinding(nearest)
					end
				end
			else
				-- No valid target position - enter search mode briefly
				if isActivelyTracking and (now - lastSightTime) <= TRACKING_GRACE_PERIOD then
					NPC.NPCValues.State.Value = "Searching"
				else
					-- Give up
					NPC.NPCValues.Aggro.Value = ""
					NPC.NPCValues.State.Value = "Idle"
					lastTarget, lastTargetPos = nil, nil
					stopPathfinding()
				end
			end
		else
			-- No target found - handle persistence and cleanup
			if isActivelyTracking and currentTarget then
				local timeSinceLastSight = now - lastSightTime
				if timeSinceLastSight <= CHASE_DURATION then
					-- Continue search briefly (keep weapon equipped)
					NPC.NPCValues.State.Value = "Searching"
					NPC.NPCValues.Aggro.Value = currentTarget.Name
				else
					-- Give up tracking
					NPC.NPCValues.Aggro.Value = ""
					NPC.NPCValues.State.Value = "Idle"
					lastTarget, lastTargetPos = nil, nil
					stopPathfinding()
					if StateModule.findAttribute(NPC, "Sprint") then
						StateModule.removeAttribute(NPC, "Sprint")
					end
					-- Unequip weapon after losing target
					task.delay(3, function()
						if not currentTarget or not isActivelyTracking then
							unequipWeapon()
						end
					end)
				end
			else
				-- Idle state
				NPC.NPCValues.Aggro.Value = ""
				NPC.NPCValues.State.Value = "Idle"
				lastTarget, lastTargetPos = nil, nil
				stopPathfinding()
				if StateModule.findAttribute(NPC, "Sprint") then
					StateModule.removeAttribute(NPC, "Sprint")
				end
				-- Unequip weapon when idle
				if hasEquippedWeapon then
					task.delay(2, function()
						if not currentTarget or not isActivelyTracking then
							unequipWeapon()
						end
					end)
				end
			end
		end
	end)

	cleanup:Add(aiConn)

	-- Enhanced movement and rotation system
	local moveConn = RunService.Heartbeat:Connect(function()
		if not currentTarget or not isActivelyTracking then
			hum:Move(Vector3.zero)
			return
		end

		-- Check if movement is allowed
		local isDisabled = StateModule.findAttribute(NPC, "IsDodging")
			or StateModule.findAttribute(NPC, "Dashing")
			or StateModule.findAttribute(NPC, "Aerial")

		if not isDisabled then
			local targetHRP = currentTarget:FindFirstChild("HumanoidRootPart")
			local movePos = nil

			-- Determine movement target
			if targetHRP and hasLineOfSight(NPC, currentTarget) then
				movePos = targetHRP.Position
			elseif lastKnownPosition then
				movePos = lastKnownPosition
			elseif targetHRP then
				movePos = targetHRP.Position
			end

			if movePos then
				local direction = (movePos - root.Position)
				direction = Vector3.new(direction.X, 0, direction.Z)

				-- Smooth rotation towards target
				if targetHRP then
					local lookAtCF = CFrame.lookAt(root.Position, Vector3.new(targetHRP.Position.X, root.Position.Y, targetHRP.Position.Z))
					root.CFrame = lookAtCF
				end

				if direction.Magnitude > 0 then
					hum.AutoRotate = true
					local distance = direction.Magnitude

					if distance > ATTACK_DISTANCE then
						-- Full speed movement when far
						hum:Move(direction.Unit)
					else
						-- Controlled movement when close
						hum:Move(Vector3.zero)
					end
				end
			end
		else
			hum:Move(Vector3.zero)
		end
	end)

	cleanup:Add(moveConn)

	-- Cleanup connections
	NPC:SetAttribute('Knocked', false)
	NPC:SetAttribute('Gripped', false)
	cleanup:Add(NPC:GetAttributeChangedSignal("Gripped"):Connect(function()
		stopPathfinding()
		cleanup:Destroy()
		game.Debris:AddItem(NPC, 2)
	end))

	-- Handle death cleanup
	cleanup:Add(NPC:GetAttributeChangedSignal("Ragdoll"):Connect(function()
		if NPC:GetAttribute("Ragdoll") then
			stopPathfinding()
			currentTarget = nil
			lastKnownPosition = nil
			isActivelyTracking = false
			NPC.NPCValues.State.Value = "Idle"
			NPC.NPCValues.Aggro.Value = ""
		end
	end))

	-- Monitor humanoid health for additional cleanup
	cleanup:Add(hum.Died:Connect(function()
		stopPathfinding()
		currentTarget = nil
		lastKnownPosition = nil
		isActivelyTracking = false
		cleanup:Destroy()
	end))
end

-- Initialize the bandit AI
if NPC.PrimaryPart then
	task.spawn(InitiateBandit)
else
	NPC:GetPropertyChangedSignal("PrimaryPart"):Wait()
	task.spawn(InitiateBandit)
end
1 Like

Hello and sorry for the late response,

I am the developer of the Forbidden API.

Firstly, I would like to say it is hard to debug due to the code dump you sent. I would also advise against sending source code in this fashion.

If you have made any progress or would like assistance, please join the community discord as directed in any of the comment sections on my channel @CritDEV.

Even if you have solved the problem, your situation helps improve the API and my knowledge for future use.

Thank you.