Guide 2026-04-20

Advanced NPC AI & Behavior Systems for FiveM

TDYSKY

TDYSKY

Founder & Lead Developer at Agency Scripts

Why Custom NPC AI Matters

The default ambient population in GTA V is designed for a single-player experience, not for immersive roleplay. NPCs walk aimlessly, react unpredictably to player actions, and have no concept of the roleplay context around them. A shop clerk stands motionless, a bartender does nothing behind the counter, and pedestrians ignore active crime scenes. Custom NPC AI transforms these cardboard cutouts into believable characters that enhance immersion. A well-scripted shopkeeper actually stands behind the register and greets customers. Security guards patrol designated routes and react to gunfire. Gang members defend their territory and flee when outgunned. In this guide, we will cover the core GTA V native functions for controlling NPC behavior, build a task sequence system, implement patrol routes, configure relationship groups for realistic combat dynamics, and create scenario-based ambient population that brings your server's world to life.

Task Sequences: The Foundation of NPC Behavior

Task sequences are ordered lists of actions that a ped executes in sequence. Unlike individual task calls that interrupt each other, a task sequence guarantees each action completes before the next begins. This is essential for complex behaviors like a security guard who walks to a door, opens it, enters a room, and then takes up a standing guard position. Without task sequences, you would need manual state tracking with timers, which is fragile and hard to maintain. The native OpenSequenceTask and CloseSequenceTask functions create a sequence handle that can be assigned to any ped via TaskPerformSequence.

-- Create a task sequence for a shopkeeper NPC
local function CreateShopkeeperRoutine(ped, counterCoords, counterHeading)
    local seq = 0
    -- Open a new sequence (pass 0 as reference, it returns a handle)
    OpenSequenceTask(seq)

    -- Walk to the counter position
    TaskGoStraightToCoord(0, counterCoords.x, counterCoords.y, counterCoords.z, 1.0, -1, counterHeading, 0.5)

    -- Turn to face customers
    TaskAchieveHeading(0, counterHeading, 2000)

    -- Play idle animation at counter
    TaskStartScenarioInPlace(0, 'PROP_HUMAN_SHOP_INTERACT', 0, true)

    CloseSequenceTask(seq)

    -- Assign sequence to the ped
    TaskPerformSequence(ped, seq)
    ClearSequenceTask(seq)
end

-- Usage
local shopkeeper = CreateNPC('s_m_m_shopkeep_01', vector3(25.7, -1347.3, 29.5))
CreateShopkeeperRoutine(shopkeeper, vector3(25.7, -1347.3, 29.5), 270.0)

The Scenario System

Scenarios are predefined animation loops that make NPCs look like they are performing real activities. GTA V includes hundreds of built-in scenarios ranging from smoking and drinking to using an ATM, working at a desk, or doing push-ups in a park. Using TaskStartScenarioInPlace for stationary activities or TaskStartScenarioAtPosition for position-specific ones, you can make NPCs perform contextually appropriate actions. The key to believable ambient AI is matching scenarios to locations. A bartender should use WORLD_HUMAN_BARTENDER, a mechanic in a garage should use WORLD_HUMAN_WELDING, and NPCs outside a nightclub should use WORLD_HUMAN_SMOKING or WORLD_HUMAN_STAND_MOBILE.

-- Scenario-based ambient NPCs with location awareness
local AmbientNPCs = {
    {
        model = 'a_m_y_barman_01',
        coords = vector4(-561.2, 286.3, 82.2, 270.0),
        scenario = 'WORLD_HUMAN_BARTENDER',
        invincible = true,
    },
    {
        model = 's_m_y_doorman_01',
        coords = vector4(-560.8, 293.5, 82.2, 180.0),
        scenario = 'WORLD_HUMAN_GUARD_STAND',
        weapon = 'WEAPON_PISTOL',
    },
    {
        model = 'a_f_y_business_01',
        coords = vector4(-555.0, 290.1, 82.2, 90.0),
        scenario = 'WORLD_HUMAN_STAND_MOBILE',
    },
    {
        model = 's_m_y_ammucity_01',
        coords = vector4(22.0, -1105.0, 29.8, 160.0),
        scenario = 'PROP_HUMAN_SHOP_INTERACT',
        invincible = true,
    },
}

local spawnedPeds = {}

local function SpawnAmbientNPCs()
    for _, data in ipairs(AmbientNPCs) do
        local hash = GetHashKey(data.model)
        RequestModel(hash)
        while not HasModelLoaded(hash) do Wait(10) end

        local ped = CreatePed(4, hash, data.coords.x, data.coords.y, data.coords.z, data.coords.w, false, true)
        SetEntityInvincible(ped, data.invincible or false)
        SetBlockingOfNonTemporaryEvents(ped, true)
        FreezeEntityPosition(ped, false)

        if data.weapon then
            GiveWeaponToPed(ped, GetHashKey(data.weapon), 250, false, true)
        end

        TaskStartScenarioInPlace(ped, data.scenario, 0, true)
        SetModelAsNoLongerNeeded(hash)
        table.insert(spawnedPeds, ped)
    end
end

Patrol Routes and Waypoint Navigation

Patrol routes make NPCs move between predefined waypoints in a loop, which is essential for security guards, police officers, and gang sentries. The simplest approach uses a repeating task sequence that walks the ped to each waypoint, waits briefly, then moves to the next. A more advanced approach uses a state machine that tracks the current waypoint index and handles interruptions gracefully. When a patrolling guard detects a threat, they should break out of the patrol, handle the threat, and then resume patrolling from their last known waypoint rather than restarting from the beginning. The patrol system should also support different movement speeds, optional scenario actions at each waypoint, and configurable wait times.

-- Patrol route system with state management
local PatrolRoutes = {
    police_station_exterior = {
        speed = 1.0, -- walking speed
        waypoints = {
            { coords = vector3(441.0, -982.0, 30.7), heading = 90.0,  wait = 5000, scenario = 'WORLD_HUMAN_GUARD_STAND' },
            { coords = vector3(451.0, -982.0, 30.7), heading = 0.0,   wait = 3000 },
            { coords = vector3(451.0, -993.0, 30.7), heading = 270.0, wait = 5000, scenario = 'WORLD_HUMAN_COP_IDLES' },
            { coords = vector3(441.0, -993.0, 30.7), heading = 180.0, wait = 3000 },
        }
    },
}

local function StartPatrol(ped, routeName)
    local route = PatrolRoutes[routeName]
    if not route then return end

    CreateThread(function()
        local waypointIndex = 1
        while DoesEntityExist(ped) and not IsEntityDead(ped) do
            local wp = route.waypoints[waypointIndex]

            -- Walk to waypoint
            TaskGoStraightToCoord(ped, wp.coords.x, wp.coords.y, wp.coords.z, route.speed, -1, wp.heading, 0.5)

            -- Wait until ped reaches destination
            while DoesEntityExist(ped) and not IsEntityDead(ped) do
                local dist = #(GetEntityCoords(ped) - wp.coords)
                if dist < 1.5 then break end
                Wait(500)
            end

            -- Face the correct direction
            TaskAchieveHeading(ped, wp.heading, 1500)
            Wait(1500)

            -- Play scenario at waypoint if defined
            if wp.scenario then
                TaskStartScenarioInPlace(ped, wp.scenario, 0, false)
                Wait(wp.wait or 5000)
                ClearPedTasks(ped)
            else
                Wait(wp.wait or 3000)
            end

            -- Move to next waypoint (loop back to 1)
            waypointIndex = waypointIndex % #route.waypoints + 1
        end
    end)
end

Relationship Groups and Combat Behavior

Relationship groups determine how NPCs react to each other and to players. By default, all NPCs are in generic relationship groups with neutral standing, which is why gang members do not fight each other and cops do not automatically pursue criminals. Custom relationship groups let you create faction dynamics where police are hostile to armed criminals, rival gangs attack each other on sight, and civilians flee from combat. The SetRelationshipBetweenGroups native accepts relationship levels from 0 (companion) through 3 (neutral) to 5 (hate), controlling whether peds will fight, flee, or ignore each other. This system is the backbone of creating realistic faction warfare, territorial disputes, and law enforcement responses.

-- Relationship group setup for faction-based AI
local RelGroups = {}

local function InitRelationshipGroups()
    -- Create custom groups
    AddRelationshipGroup('GANG_BALLAS', RelGroups)
    AddRelationshipGroup('GANG_FAMILIES', RelGroups)
    AddRelationshipGroup('GANG_VAGOS', RelGroups)
    AddRelationshipGroup('POLICE_CUSTOM', RelGroups)
    AddRelationshipGroup('CIVILIAN', RelGroups)

    -- Gangs hate rival gangs
    SetRelationshipBetweenGroups(5, GetHashKey('GANG_BALLAS'), GetHashKey('GANG_FAMILIES'))
    SetRelationshipBetweenGroups(5, GetHashKey('GANG_FAMILIES'), GetHashKey('GANG_BALLAS'))
    SetRelationshipBetweenGroups(5, GetHashKey('GANG_BALLAS'), GetHashKey('GANG_VAGOS'))
    SetRelationshipBetweenGroups(5, GetHashKey('GANG_VAGOS'), GetHashKey('GANG_BALLAS'))

    -- Police dislike all gangs
    SetRelationshipBetweenGroups(4, GetHashKey('POLICE_CUSTOM'), GetHashKey('GANG_BALLAS'))
    SetRelationshipBetweenGroups(4, GetHashKey('POLICE_CUSTOM'), GetHashKey('GANG_FAMILIES'))
    SetRelationshipBetweenGroups(4, GetHashKey('POLICE_CUSTOM'), GetHashKey('GANG_VAGOS'))

    -- Civilians flee from gangs
    SetRelationshipBetweenGroups(1, GetHashKey('CIVILIAN'), GetHashKey('GANG_BALLAS'))
    SetRelationshipBetweenGroups(1, GetHashKey('CIVILIAN'), GetHashKey('GANG_FAMILIES'))
end

-- Assign a ped to a relationship group
local function SetPedFaction(ped, faction)
    local groupHash = GetHashKey(faction)
    SetPedRelationshipGroupHash(ped, groupHash)
end

Flee and Fight Behavior

Controlling how NPCs respond to threats is critical for immersive combat scenarios. The default GTA V behavior is chaotic: some NPCs run, some cower, and armed NPCs might or might not engage. Custom combat behavior uses combat attributes and configuration flags to create predictable, role-appropriate responses. A gang member should stand and fight with poor accuracy, a trained security guard should take cover and engage with moderate accuracy, a civilian should flee immediately, and a VIP character should crouch and call for help. The SetPedCombatAttributes native controls individual behaviors like whether the ped can use cover, perform blind fire, investigate sounds, or flee when outgunned.

-- Combat behavior presets
local CombatPresets = {
    gang_member = function(ped)
        SetPedCombatAttributes(ped, 46, true)   -- Can fight armed peds on foot
        SetPedCombatAttributes(ped, 5, true)    -- Can use cover
        SetPedCombatAttributes(ped, 2, true)    -- Can do drivebys
        SetPedCombatAbility(ped, 1)              -- Average combat ability
        SetPedCombatRange(ped, 1)                -- Medium range
        SetPedAccuracy(ped, 30)                  -- Poor accuracy
        SetPedFleeAttributes(ped, 0, false)      -- Don't flee
        SetPedCombatMovement(ped, 2)             -- Offensive movement
    end,

    security_guard = function(ped)
        SetPedCombatAttributes(ped, 46, true)
        SetPedCombatAttributes(ped, 5, true)    -- Use cover
        SetPedCombatAttributes(ped, 21, true)   -- Investigate dead peds
        SetPedCombatAbility(ped, 2)              -- Professional
        SetPedCombatRange(ped, 2)                -- Long range
        SetPedAccuracy(ped, 60)                  -- Good accuracy
        SetPedCombatMovement(ped, 1)             -- Defensive movement
    end,

    civilian = function(ped)
        SetPedFleeAttributes(ped, 2, true)       -- Flee immediately
        SetPedCombatAttributes(ped, 17, true)    -- Can be scared
        SetPedCombatAttributes(ped, 46, false)   -- Cannot fight
        SetPedCombatAbility(ped, 0)              -- Poor fighter
    end,
}

-- Apply a combat preset
local function ApplyCombatPreset(ped, presetName)
    local preset = CombatPresets[presetName]
    if preset then preset(ped) end
end

Managing NPC Lifecycle and Performance

Spawning NPCs has a direct impact on server and client performance. Every active ped consumes CPU cycles for AI processing, physics simulation, and network synchronization. A naive implementation that spawns all NPCs at server start and keeps them alive permanently will tank performance as the population grows. The correct approach uses proximity-based spawning where NPCs are only created when a player is within a configurable range and despawned when all players leave the area. Use a spatial partitioning system that divides the map into zones and only processes NPC logic for zones with nearby players. Cache ped handles and reuse them when possible rather than deleting and recreating NPCs every time a player enters and exits a zone. Set SetEntityAsMissionEntity to false and use network-aware creation flags so the game engine can naturally manage ped lifecycle within its own streaming system.

-- Proximity-based NPC spawner with zone management
local SPAWN_RANGE = 80.0
local DESPAWN_RANGE = 120.0
local activeZones = {}

CreateThread(function()
    while true do
        local playerCoords = GetEntityCoords(PlayerPedId())

        for zoneName, zone in pairs(NPCZones) do
            local dist = #(playerCoords - zone.center)

            if dist < SPAWN_RANGE and not activeZones[zoneName] then
                -- Spawn zone NPCs
                activeZones[zoneName] = SpawnZoneNPCs(zone)
            elseif dist > DESPAWN_RANGE and activeZones[zoneName] then
                -- Despawn zone NPCs
                for _, ped in ipairs(activeZones[zoneName]) do
                    if DoesEntityExist(ped) then
                        DeleteEntity(ped)
                    end
                end
                activeZones[zoneName] = nil
            end
        end

        Wait(2000) -- Check every 2 seconds
    end
end)

Bringing It All Together

A production NPC AI system combines all these elements into a cohesive framework driven by configuration data. Each NPC definition in your config specifies the model, spawn location, behavior type (stationary, patrolling, guarding), combat preset, relationship group, and any scenario or task sequence to execute. The framework loads these definitions, manages spawning and despawning based on player proximity, handles state transitions when NPCs are interrupted by combat or player interaction, and cleans up resources when NPCs are no longer needed. The most important design principle is separating data from logic: all NPC behavior should be configurable without code changes, letting server owners add new NPCs, modify patrol routes, and adjust combat parameters through config files alone. This data-driven approach means your AI system can scale from a handful of quest NPCs to hundreds of ambient characters populating an entire city without architectural changes to the underlying code.

Share this article

Ready to upgrade your server?

Check out our premium FiveM scripts in the Agency Scripts store or join our Discord community for support and updates.