> For the complete documentation index, see [llms.txt](https://unknown-development.gitbook.io/unknown-development/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://unknown-development.gitbook.io/unknown-development/free-scripts/unknown_dialog.md).

# NPC Dialog

This script supports QBCore, Qbox and ESX

*<mark style="color:$info;">Are you looking for a universal and flexible NPC dialog system for your FiveM server? You’ve found it! Our Universal NPC Dialog System provides a clean and immersive way to interact with NPCs using simple confirmation buttons or full shop-style dialogs.</mark>*

<table data-view="cards"><thead><tr><th></th><th data-hidden data-card-target data-type="content-ref"></th></tr></thead><tbody><tr><td>Take it for FREE!</td><td><a href="https://unknown-development.tebex.io/package/dialog">https://unknown-development.tebex.io/package/dialog</a></td></tr><tr><td>Checkout all our scripts</td><td><a href="https://unknown-development.tebex.io/category/resources">https://unknown-development.tebex.io/category/resources</a></td></tr></tbody></table>

## Showcase

{% embed url="<https://youtu.be/bGa5-d7Tcs4>" %}

## Config

{% tabs %}
{% tab title="config.lua" %}

```lua
Config = {}

-- Framework: 'qbox', 'qb', 'esx'
Config.Framework = 'qbox'

-- Inventory system: 'ox_inventory', 'qb-inventory'
Config.Inventory = 'ox_inventory'

-- Target system: 'ox_target', 'qb-target'
Config.Target = 'ox_target'

Config.Locale = 'en'  -- 'lt' or 'en'

-- NPC spawn locations with dialog configs
Config.NPCs = {
    -- ============================================
    -- EXAMPLE 1: Shop NPC (with purchasing)
    -- ============================================
    {
        model = 'a_m_m_business_01',
        coords = vector4(1079.8682, -1858.9929, 35.7475, 293.0599),
        scenario = 'WORLD_HUMAN_STAND_IMPATIENT',
        
        npcInfo = {
            name = "Mike",
            specifier = "General Store Clerk"
        },
        
        targetLabel = "Talk",
        targetIcon = "fas fa-comments",
        targetDistance = 2.5,
        
        -- Initial dialog
        dialog = {
            question = "Welcome to Mike's General Store! How can I help you?",
            answers = {
                { id = "pos_buy", label = "I want to buy something", icon = "basket-shopping" },
                { id = "neg_leave", label = "Just looking around", icon = "eye" }
            }
        },
        
        -- Shop items
        shopItems = {
            { name = "beer", label = "Beer", price = 5, imageUrl = "nui://ox_inventory/web/images/beer.png" },
            { name = "water", label = "Water", price = 3, imageUrl = "nui://ox_inventory/web/images/water.png" },
            { name = "burger", label = "Burger", price = 10, imageUrl = "nui://ox_inventory/web/images/burger.png" },
            { name = "sandwich", label = "Sandwich", price = 8, imageUrl = "nui://ox_inventory/web/images/sandwich.png" }
        },
        
        paymentMethods = {
            { id = "cash", label = "Cash", icon = "money-bill-wave" },
            { id = "bank", label = "Bank", icon = "credit-card" }
        }
    },

    -- ============================================
    -- EXAMPLE 2: Information NPC (with actions)
    -- ============================================
    {
        model = 'a_f_y_business_01',
        coords = vector4(1084.3364, -1857.7158, 36.3256, 297.6758),
        scenario = 'WORLD_HUMAN_CLIPBOARD',
        
        npcInfo = {
            name = "Sarah",
            specifier = "Information Desk"
        },
        
        targetLabel = "Talk",
        targetIcon = "fas fa-info-circle",
        targetDistance = 2.5,
        
        dialog = {
            question = "Hello! I'm Sarah. How can I help you?",
            answers = {
                { 
                    id = "pos_minigame", 
                    label = "Play minigame", 
                    icon = "gamepad",

                    action = {
                        type = "export",           -- Type: event, serverEvent, export, command
                        resource = "unknown_skillBar", -- Resource name
                        name = "skillBar", -- Export function name
                        args = {70, 3} -- Arguments: difficulty = 70, duration = 3
                    }
                },
                { 
                    id = "pos_vip", 
                    label = "Open VIP menu", 
                    icon = "crown",
                    action = {
                        type = "command",
                        name = "vip",
                    }
                },
                { 
                    -- Submenu
                    id = "pos_more", 
                    label = "More information", 
                    icon = "circle-info",
                    nextDialog = {
                        question = "What information are you looking for?",
                        answers = {
                            { 
                                id = "rules", 
                                label = "Server rules", 
                                icon = "scale-balanced",
                                action = {
                                    type = "event",
                                    name = "chat:addMessage",
                                    args = { color = {255, 200, 0}, args = {"System", "Rules can be found at: discord.gg/example"} }
                                }
                            },
                            { 
                                id = "help", 
                                label = "Help", 
                                icon = "circle-question",
                                action = {
                                    type = "command",
                                    name = "help"
                                }
                            },
                            { id = "neg_back", label = "Go back", icon = "arrow-left" }
                        }
                    }
                },
                { id = "neg_leave", label = "Goodbye", icon = "hand-peace" }
            }
        }
    },

    -- ============================================
    -- EXAMPLE 3: Confirmation dialog
    -- ============================================
    {
        model = 'a_m_y_business_02',
        coords = vector4(1081.9873, -1856.8446, 36.0590, 283.1140),
        scenario = 'WORLD_HUMAN_STAND_MOBILE',
        
        npcInfo = {
            name = "Tom",
            specifier = "City Hall"
        },
        
        targetLabel = "Contact",
        targetIcon = "fas fa-landmark",
        targetDistance = 2.5,
        
        dialog = {
            question = "Hello, this is the city hall. How can I help you?",
            answers = {
                { 
                    id = "pos_license", 
                    label = "I want to get a license", 
                    icon = "id-card",
                    nextDialog = {
                        question = "The license costs $500. Are you sure you want to buy it?",
                        answers = {
                            { 
                                id = "pos_confirm", 
                                label = "Yes, buy", 
                                icon = "check",
                                action = {
                                    type = "serverEvent",
                                    name = "cityhall:buyLicense",
                                    args = { licenseType = "driver", price = 500 }
                                }
                            },
                            { id = "neg_cancel", label = "No, I changed my mind", icon = "xmark" }
                        }
                    }
                },
                { id = "neg_leave", label = "Nothing needed", icon = "door-open" }
            }
        }
    }
}

Config.Debug = true
```

{% endtab %}

{% tab title="shared/inventory.lua" %}

```lua
function AddItem(source, itemName, amount, metadata)
    if Config.Inventory == 'ox_inventory' then
        return exports.ox_inventory:AddItem(source, itemName, amount, metadata)
    elseif Config.Inventory == 'qb-inventory' then
        local Player = exports['qb-core']:GetCoreObject().Functions.GetPlayer(source)
        if Player then
            return Player.Functions.AddItem(itemName, amount, nil, metadata)
        end
    end
    return false
end

function RemoveItem(source, itemName, amount, metadata, slot)
    if Config.Inventory == 'ox_inventory' then
        return exports.ox_inventory:RemoveItem(source, itemName, amount, metadata, slot)
    elseif Config.Inventory == 'qb-inventory' then
        local Player = exports['qb-core']:GetCoreObject().Functions.GetPlayer(source)
        if Player then
            return Player.Functions.RemoveItem(itemName, amount, slot)
        end
    end
    return false
end

function GetItemCount(source, itemName, metadata)
    if Config.Inventory == 'ox_inventory' then
        return exports.ox_inventory:GetItemCount(source, itemName, metadata) or 0
    elseif Config.Inventory == 'qb-inventory' then
        local Player = exports['qb-core']:GetCoreObject().Functions.GetPlayer(source)
        if Player then
            local item = Player.Functions.GetItemByName(itemName)
            return item and item.amount or 0
        end
    end
    return 0
end

function CanCarryItem(source, itemName, amount)
    if Config.Inventory == 'ox_inventory' then
        return exports.ox_inventory:CanCarryItem(source, itemName, amount)
    elseif Config.Inventory == 'qb-inventory' then
        -- QB doesn't have a direct check, assume true
        return true
    end
    return true
end

function HasItem(source, itemName, amount)
    amount = amount or 1
    return GetItemCount(source, itemName) >= amount
end

```

{% endtab %}

{% tab title="shared/framework.lua" %}

```lua
local Framework = {}

function Framework:GetCurrent()
    if Config.Framework == 'esx' and GetResourceState('es_extended') == 'started' then
        return 'esx'
    elseif Config.Framework == 'qb' and GetResourceState('qb-core') == 'started' then
        return 'qb'
    elseif Config.Framework == 'qbox' and GetResourceState('qbx_core') == 'started' then
        return 'qbox'
    end
    return nil
end

function Framework:AddItem(source, itemName, amount, metadata)
    return AddItem(source, itemName, amount, metadata)
end

function Framework:CanCarryItem(source, itemName, amount)
    return CanCarryItem(source, itemName, amount)
end

function Framework:AddMoney(source, account, amount, reason)
    local framework = self:GetCurrent()
    
    if framework == 'qb' then
        local QBCore = exports['qb-core']:GetCoreObject()
        local Player = QBCore.Functions.GetPlayer(source)
        if Player then
            Player.Functions.AddMoney(account, amount, reason)
            return true
        end
    elseif framework == 'qbox' then
        local QBCore = exports['qb-core']:GetCoreObject()
        local Player = QBCore.Functions.GetPlayer(source)
        if Player then
            Player.Functions.AddMoney(account, amount, reason)
            return true
        end
    elseif framework == 'esx' then
        local ESX = exports['es_extended']:getSharedObject()
        local xPlayer = ESX.GetPlayerFromId(source)
        if xPlayer then
            if account == 'cash' or account == 'money' then
                xPlayer.addMoney(amount)
            elseif account == 'bank' then
                xPlayer.addAccountMoney('bank', amount)
            end
            return true
        end
    end
    return false
end

function Framework:RemoveMoney(source, account, amount, reason)
    local framework = self:GetCurrent()
    
    if framework == 'qb' then
        local QBCore = exports['qb-core']:GetCoreObject()
        local Player = QBCore.Functions.GetPlayer(source)
        if Player then
            return Player.Functions.RemoveMoney(account, amount, reason)
        end
    elseif framework == 'qbox' then
        local QBCore = exports['qb-core']:GetCoreObject()
        local Player = QBCore.Functions.GetPlayer(source)
        if Player then
            return Player.Functions.RemoveMoney(account, amount, reason)
        end
    elseif framework == 'esx' then
        local ESX = exports['es_extended']:getSharedObject()
        local xPlayer = ESX.GetPlayerFromId(source)
        if xPlayer then
            if account == 'cash' or account == 'money' then
                return xPlayer.removeMoney(amount)
            elseif account == 'bank' then
                return xPlayer.removeAccountMoney('bank', amount)
            end
        end
    end
    return false
end

function Framework:HasMoney(source, account, amount)
    local money = self:GetMoney(source, account)
    return money >= amount
end

function Framework:GetMoney(source, account)
    local framework = self:GetCurrent()

    if Config.Inventory == 'ox_inventory' then
        if account == 'cash' or account == 'money' then
            return GetItemCount(source, 'money')
        elseif account == 'black' or account == 'black_money' then
            return GetItemCount(source, 'black_money')
        end
    end
    
    if framework == 'qb' then
        local QBCore = exports['qb-core']:GetCoreObject()
        local Player = QBCore.Functions.GetPlayer(source)
        if Player then
            return Player.Functions.GetMoney(account)
        end
    elseif framework == 'qbox' then
        local QBCore = exports['qb-core']:GetCoreObject()
        local Player = QBCore.Functions.GetPlayer(source)
        if Player then
            return Player.PlayerData.money[account]
        end
    elseif framework == 'esx' then
        local ESX = exports['es_extended']:getSharedObject()
        local xPlayer = ESX.GetPlayerFromId(source)
        if xPlayer then
            if account == 'cash' or account == 'money' then
                return xPlayer.getMoney()
            elseif account == 'bank' then
                return xPlayer.getAccount('bank').money
            end
        end
    end
    return 0
end

return Framework
```

{% endtab %}

{% tab title="shared/target.lua" %}

```lua
function AddEntity(entity, options)
    if Config.Target == 'ox_target' and GetResourceState('ox_target') == 'started' then
        exports.ox_target:addLocalEntity(entity, options)
    elseif Config.Target == 'qb-target' and GetResourceState('qb-target') == 'started' then
        local qbOptions = {}
        local distance = 2.0
        
        for _, opt in ipairs(options) do
            if opt.distance then distance = opt.distance end
            table.insert(qbOptions, {
                type = "client",
                action = opt.onSelect,
                icon = opt.icon,
                label = opt.label,
            })
        end
        
        exports['qb-target']:AddTargetEntity(entity, {
            options = qbOptions,
            distance = distance
        })
    end
end
```

{% endtab %}
{% endtabs %}

## Locales

{% tabs %}
{% tab title="locales/locales.lua" %}

```lua
Locales = {
    ['en'] = {
        -- Buttons
        buy_button = "Buy",
        confirm_button = "Confirm",
        
        -- Messages
        enter_amount = "Enter amount",
        select_payment = "Select payment method",
        purchase_complete = "Purchase complete!",
        not_enough_money = "Not enough money!",
        inventory_full = "Inventory is full!",
        no_items_selected = "No items selected!",
        npc_not_found = "Error: NPC not found!",
        error_adding_items = "Error adding items!",
        error = "Error",
        
        -- Target
        talk = "Talk",
        
        -- Payment methods
        cash = "Cash",
        bank = "Bank"
    },
    ['lt'] = {
        -- Buttons
        buy_button = "Pirkti",
        confirm_button = "Patvirtinti",
        
        -- Messages
        enter_amount = "Įveskite kiekį",
        select_payment = "Pasirinkite mokėjimo būdą",
        purchase_complete = "Pirkimas sėkmingas!",
        not_enough_money = "Neužtenka pinigų!",
        inventory_full = "Inventorius pilnas!",
        no_items_selected = "Nepasirinkti jokie produktai!",
        npc_not_found = "Klaida: NPC nerastas!",
        error_adding_items = "Klaida pridedant prekes!",
        error = "Klaida",
        
        -- Target
        talk = "Kalbėti",
        
        -- Payment methods
        cash = "Grynieji",
        bank = "Bankas"
    }
}

function Locale(key)
    local lang = Config.Locale or 'lt'
    if Locales[lang] and Locales[lang][key] then
        return Locales[lang][key]
    end

    if Locales['lt'] and Locales['lt'][key] then
        return Locales['lt'][key]
    end
    return key
end
```

{% endtab %}
{% endtabs %}
