Skip to main content
The Targeting system (also called “Third Eye” or “Interaction Menu”) allows players to interact with entities in the world. Point at a vehicle, NPC, or object to see available actions based on job permissions, items held, and distance.

Overview

The targeting system uses raycasting to detect entities and displays context-sensitive interaction menus.

Entity Detection

Ray-based entity detection

Context Menus

Dynamic action menus

Permission System

Job & item-based access

Visual Feedback

On-screen targeting reticle
Client-Side: Targeting is handled client-side but actions are validated server-side for security.

How It Works

Activation

  • Hold Left Alt (default) to activate targeting mode
  • Aim at entities to see available interactions
  • Visual indicator shows what you’re targeting
  • Release to deactivate

Entity Detection

The system detects:
  • Vehicles - Cars, boats, aircraft
  • Peds - NPCs and other players
  • Objects - Props, doors, items
  • Zones - Invisible interaction areas

Permission Checks

Before showing an action, the system validates:
  • Job requirements
  • On-duty status
  • Item possession
  • Distance from entity
  • Entity model/type
  • Character states
  • Reputation levels

Registering Interactions

Interactions are registered in the targeting configuration files:
  • client/targets/ped.lua - NPC/player interactions
  • client/targets/objects.lua - Object interactions
  • client/targets/zones.lua - Zone-based interactions

Basic Interaction

-- client/targets/ped.lua example
{
    text = 'Talk to NPC',
    icon = 'comments',
    event = 'npc:client:Talk',
    data = { npcId = 'shopkeeper_1' },
    minDist = 3.0,
    isEnabled = function(data, entityData)
        return true -- Always show
    end
}

Parameters

FieldTypeRequiredDescription
textstring/functionYesDisplay text or function returning text
iconstringYesFont Awesome icon name
eventstringYesEvent to trigger
datatableNoData to pass to event
minDistnumberNoMaximum interaction distance
jobPermstableNoJob permission requirements
tempjobstringNoTemporary job requirement
statestringNoRequired character state
itemstringNoRequired item in inventory
itemstableNoRequired multiple items (all)
anyItemstableNoRequired any of these items
modelnumberNoSpecific vehicle model hash
reptableNoReputation requirement {id, level}
isEnabledfunctionNoCustom visibility check
textFuncfunctionNoDynamic text generation

Job Permissions

Basic Job Check

{
    text = 'Impound Vehicle',
    icon = 'warehouse',
    event = 'police:client:ImpoundVehicle',
    jobPerms = {
        {
            job = 'police',
            reqDuty = true,
        }
    }
}

Multiple Job Options

{
    text = 'Revive Player',
    icon = 'hand-holding-medical',
    event = 'ems:client:Revive',
    jobPerms = {
        { job = 'ems', reqDuty = true },
        { job = 'police', reqDuty = true, gradeLevel = 10 },
        { permissionKey = 'PD_HIGH_COMMAND' }
    }
}

Job Permission Fields

FieldTypeDescription
jobstringJob ID required
workplacestringSpecific workplace
gradestringSpecific grade ID
gradeLevelnumberMinimum grade level
reqDutybooleanMust be on duty
reqOffDutybooleanMust be off duty
permissionKeystringSpecific permission required

Item Requirements

Single Item

{
    text = 'Lockpick Vehicle',
    icon = 'key',
    event = 'vehicles:client:Lockpick',
    item = 'lockpick',
    itemCount = 1
}

Multiple Items (All Required)

{
    text = 'Repair Engine',
    icon = 'wrench',
    event = 'vehicles:client:Repair',
    items = {
        { name = 'repairkit', count = 1 },
        { name = 'wrench', count = 1 }
    }
}

Any Item (At Least One)

{
    text = 'Cut Door',
    icon = 'scissors',
    event = 'vehicles:client:CutDoor',
    anyItems = {
        { name = 'blowtorch', count = 1 },
        { name = 'jaws_of_life', count = 1 }
    }
}

Dynamic Text

Text Function

{
    text = 'Search Vehicle',
    textFunc = function(data, entityData)
        local vState = Entity(entityData.entity).state
        if vState.isLocked then
            return 'Vehicle Locked'
        else
            return 'Search Vehicle'
        end
    end,
    icon = 'magnifying-glass',
    event = 'police:client:SearchVehicle'
}

Entity Data Access

textFunc = function(data, entityData)
    -- entityData contains:
    -- entityData.entity - Entity handle
    -- entityData.endCoords - Target coordinates
    -- entityData.type - Entity type ('vehicle', 'ped', 'object')

    if entityData.type == 'vehicle' then
        local plate = GetVehicleNumberPlateText(entityData.entity)
        return 'Check Plate: ' .. plate
    end
    return 'Interact'
end

Conditional Visibility

isEnabled Function

{
    text = 'Open Trunk',
    icon = 'box',
    event = 'vehicles:client:OpenTrunk',
    isEnabled = function(data, entityData)
        -- Only show if vehicle is unlocked
        local vState = Entity(entityData.entity).state
        return not vState.isLocked
    end
}

Multiple Conditions

{
    text = 'Tow Vehicle',
    icon = 'truck-tow',
    event = 'towing:client:TowVehicle',
    jobPerms = {{ job = 'tow', reqDuty = true }},
    isEnabled = function(data, entityData)
        -- Check if vehicle is abandoned
        local vState = Entity(entityData.entity).state
        if not vState.VIN then
            return false -- Not an owned vehicle
        end

        -- Check if player is in tow truck
        local ped = PlayerPedId()
        local vehicle = GetVehiclePedIsIn(ped, false)
        if vehicle == 0 then
            return false
        end

        local model = GetEntityModel(vehicle)
        return model == GetHashKey('flatbed')
    end
}

Distance Requirements

Maximum Distance

{
    text = 'Pickpocket',
    icon = 'hand',
    event = 'crime:client:Pickpocket',
    minDist = 1.5, -- Must be within 1.5 units
    item = 'lockpick'
}
Notes:
  • Default max distance is unlimited if not specified
  • Distance checked automatically before showing option
  • Distance calculated from entity center point

Vehicle-Specific Interactions

Model-Specific

{
    text = 'Refuel',
    icon = 'gas-pump',
    event = 'fuel:client:Refuel',
    model = GetHashKey('flatbed'), -- Only for flatbed trucks
    jobPerms = {{ job = 'mechanic' }}
}

Vehicle Type Check

{
    text = 'Anchor Boat',
    icon = 'anchor',
    event = 'boats:client:Anchor',
    isEnabled = function(data, entityData)
        if entityData.type ~= 'vehicle' then
            return false
        end

        local vehClass = GetVehicleClass(entityData.entity)
        return vehClass == 14 -- Boats only
    end
}

State Requirements

Character States

{
    text = 'Remove Cuffs',
    icon = 'handcuffs',
    event = 'police:client:Uncuff',
    state = 'ARRESTED', -- Player must have ARRESTED state
    jobPerms = {{ job = 'police' }}
}
Common States:
  • ARRESTED - Player is under arrest
  • WANTED - Player has active warrant
  • HOSPITAL_PATIENT - In medical care
  • FLEECA_HEIST - Actively in heist

Reputation Requirements

Minimum Level

{
    text = 'Access VIP Lounge',
    icon = 'crown',
    event = 'casino:client:VIPLounge',
    rep = {
        id = 'casino',
        level = 5
    }
}

Temporary Jobs

Temp Job Check

{
    text = 'Deliver Package',
    icon = 'box',
    event = 'delivery:client:Deliver',
    tempjob = 'postop', -- Must have postop temp job active
}

Complete Examples

Police Vehicle Interaction

{
    text = 'Impound Vehicle',
    icon = 'warehouse',
    event = 'police:client:ImpoundVehicle',
    jobPerms = {
        { job = 'police', reqDuty = true },
        { job = 'tow', reqDuty = true }
    },
    minDist = 5.0,
    isEnabled = function(data, entityData)
        -- Don't impound occupied vehicles
        local veh = entityData.entity
        for i = -1, 10 do
            if GetPedInVehicleSeat(veh, i) ~= 0 then
                return false
            end
        end
        return true
    end
}

Medical Revive

{
    text = 'Revive Patient',
    textFunc = function(data, entityData)
        local target = NetworkGetPlayerIndexFromPed(entityData.entity)
        if target ~= -1 then
            local targetId = GetPlayerServerId(target)
            return 'Revive (' .. targetId .. ')'
        end
        return 'Revive'
    end,
    icon = 'hand-holding-medical',
    event = 'ems:client:Revive',
    jobPerms = {{ job = 'ems', reqDuty = true }},
    items = {
        { name = 'medkit', count = 1 }
    },
    minDist = 2.0,
    isEnabled = function(data, entityData)
        -- Only show on dead players
        local target = NetworkGetPlayerIndexFromPed(entityData.entity)
        if target == -1 then
            return false
        end

        local targetSource = GetPlayerServerId(target)
        local state = Player(targetSource).state
        return state.isDead or false
    end
}

Mechanic Repair

{
    text = 'Repair Vehicle',
    icon = 'wrench',
    event = 'mechanic:client:RepairVehicle',
    jobPerms = {
        { job = 'mechanic', reqDuty = true }
    },
    anyItems = {
        { name = 'repairkit', count = 1 },
        { name = 'advrepairkit', count = 1 }
    },
    minDist = 3.0,
    isEnabled = function(data, entityData)
        if entityData.type ~= 'vehicle' then
            return false
        end

        local vState = Entity(entityData.entity).state
        local damage = vState.Damage

        if not damage then
            return false
        end

        -- Only show if vehicle is damaged
        return damage.Body < 100 or damage.Engine < 100
    end
}

Events

Triggering Interactions

When a player selects an interaction, the specified event is triggered:
-- Client side - Handle interaction event
AddEventHandler('police:client:ImpoundVehicle', function(data, entityData)
    -- data = Custom data from interaction definition
    -- entityData = {entity, endCoords, type}

    local vehicle = entityData.entity
    local plate = GetVehicleNumberPlateText(vehicle)

    -- Trigger server event to impound
    TriggerServerEvent('police:server:ImpoundVehicle', {
        netId = NetworkGetNetworkIdFromEntity(vehicle),
        plate = plate
    })
end)

Targeting:Client:OpenMenu Event

Triggered when targeting menu opens. Parameters:
NameTypeDescription
menutableArray of interaction options
Example:
-- Client side
AddEventHandler('Targeting:Client:OpenMenu', function(menu)
    print('Targeting menu opened with', #menu, 'options')
end)

Targeting:Client:UpdateState Event

Triggered when targeting state changes. Parameters:
NameTypeDescription
activebooleanTargeting mode active
iconstringCurrent target icon
Example:
-- Client side
AddEventHandler('Targeting:Client:UpdateState', function(active, icon)
    if active then
        print('Targeting active, icon:', icon)
    else
        print('Targeting deactivated')
    end
end)

Best Practices

Performance

-- ❌ BAD - Heavy computation in isEnabled
isEnabled = function(data, entityData)
    -- Don't do expensive operations
    local nearbyPlayers = GetNearbyPlayers(50.0)
    -- Don't query database
    local data = MySQL.Sync.fetchAll(...)
    return true
end

-- ✅ GOOD - Lightweight checks
isEnabled = function(data, entityData)
    -- Quick state checks only
    return Entity(entityData.entity).state.isUnlocked
end

Security

-- Client side - Define interaction
{
    text = 'Withdraw Money',
    event = 'bank:client:Withdraw',
    jobPerms = {{ job = 'bank' }}
}

-- Client side - Handle event
AddEventHandler('bank:client:Withdraw', function(data, entityData)
    -- Don't trust client data - send to server for validation
    TriggerServerEvent('bank:server:Withdraw', {
        amount = 1000
    })
end)

-- Server side - Validate everything
RegisterServerEvent('bank:server:Withdraw', function(data)
    local src = source
    local player = Fetch:Source(src)

    -- Re-check job permissions server-side
    if not Jobs.Permissions:HasJob(src, 'bank') then
        return -- Not authorized
    end

    -- Process withdrawal with server-side validation
    -- ...
end)

Next Steps

Dynamic Menus: Use isEnabled and textFunc to create context-aware interactions. The menu adapts based on entity state, player inventory, and permissions.
Server Validation: Always validate permissions and actions server-side. Client checks are for UX only - they can be bypassed.