> 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

*<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 %}


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://unknown-development.gitbook.io/unknown-development/free-scripts/unknown_dialog.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
