> For the complete documentation index, see [llms.txt](https://devix.gitbook.io/devix/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://devix.gitbook.io/devix/devix-inventory/errors/lb-phone.md).

# LB-PHONE

## What you change by case

| You use                   | Edit these                                                                                   |
| ------------------------- | -------------------------------------------------------------------------------------------- |
| **Any framework + devix** | `HasItem` in **your** framework files (§2).                                                  |
| **Non-unique phone**      | `config.lua` + `server/custom/functions/devix_inventory.lua`.                                |
| **Unique phone**          | `config.lua` + §2 + both `uniquePhones/devix_inventory.lua` files (§3 — full sources below). |
| **devix item**            | `devix-inventory/config_items.lua` → `phone` → `useable = true`.                             |

{% stepper %}
{% step %}

### `lb-phone/config/config.lua`

**Non-unique:** `Config.Item.Inventory` can stay `auto`.

**Unique phones:**

```lua
Config.Item.Require = true
Config.Item.Unique = false
Config.Item.Inventory = "devix-inventory"
```

Match `Config.Item.Name` / `Config.Item.Names` to devix items. [LB unique phones](https://docs.lbscripts.com/phone/configuration/#unique-phones).
{% endstep %}

{% step %}

### `HasItem` — **`devix-inventory` before `ox_inventory`**

When **both** resources are `started`, **`devix-inventory` must be checked first** (first matching `if` wins).

#### Server — `function HasItem(source, itemName)`

**Files:** `server/custom/frameworks/esx/esx.lua` · `qb/qb.lua` · `qbox/qbox.lua`

Replace the **top of** `HasItem` with (keep your existing **default** tail — ESX `xPlayer` / QB `qPlayer` block):

```lua
function HasItem(source, itemName)
    if GetResourceState("devix-inventory") == "started" then
        return (exports["devix-inventory"]:GetItemCount(source, itemName) or 0) > 0
    elseif GetResourceState("ox_inventory") == "started" then
        return (exports.ox_inventory:Search(source, "count", itemName) or 0) > 0
    elseif GetResourceState("qs-inventory") == "started" then
        return (exports["qs-inventory"]:GetItemTotalAmount(source, itemName) or 0) > 0
    end

    -- … rest unchanged (ESX inventory or QB GetItemByName)
end
```

*(QB/QBox files that have no `qs-inventory` branch: omit that `elseif` only.)*

#### Client — `function HasItem(itemName)`

**Files:** `client/custom/frameworks/esx/item.lua` · `qb/item.lua` · `qbox/item.lua`

**ESX** (devix → ox → qs → then your existing ESX logic):

```lua
function HasItem(itemName)
    if GetResourceState("devix-inventory") == "started" then
        return (exports["devix-inventory"]:GetItemCount(itemName) or 0) > 0
    elseif GetResourceState("ox_inventory") == "started" then
        return (exports.ox_inventory:Search("count", itemName) or 0) > 0
    elseif GetResourceState("qs-inventory") == "started" then
        return (exports["qs-inventory"]:Search(itemName) or 0) > 0
    end

    -- … rest unchanged
end
```

**QB / QBox** (no qs branch in stock file):

```lua
function HasItem(itemName)
    if GetResourceState("devix-inventory") == "started" then
        return (exports["devix-inventory"]:GetItemCount(itemName) or 0) > 0
    elseif GetResourceState("ox_inventory") == "started" then
        return (exports.ox_inventory:Search("count", itemName) or 0) > 0
    end

    return QB.Functions.HasItem(itemName)
end
```

{% endstep %}

{% step %}

### Unique phones — full file sources

Paths under **lb-phone** (ship with this integration). If your tree is missing them, create these two files with the contents below.

#### `server/custom/uniquePhones/devix_inventory.lua`

{% code expandable="true" %}

```lua
if Config.Item.Inventory ~= "devix-inventory" or not Config.Item.Unique or not Config.Item.Require then
    return
end

---@type { [number]: number }
local usedPhoneSlots = {}

local function mergeInfo(old, patch)
    local m = {}
    if type(old) == "table" then
        for k, v in pairs(old) do
            m[k] = v
        end
    end
    if type(patch) == "table" then
        for k, v in pairs(patch) do
            m[k] = v
        end
    end
    return m
end

---@param source number
---@return table
local function GetPhonesInInventory(source)
    local inv = exports["devix-inventory"]:GetInventory(source)
    local phones = {}
    if not inv then
        return phones
    end
    for slot = 1, 200 do
        local it = inv[slot]
        if not it or not it.name then
            break
        end
        if IsItemAPhone(it.name) then
            phones[#phones + 1] = {
                slot = slot,
                name = it.name,
                metadata = it.info or {},
            }
        end
    end
    return phones
end

---@param source number
---@param phoneNumber string
---@return boolean
function HasPhoneNumber(source, phoneNumber)
    debugprint("checking if " .. source .. " has a phone item with number", phoneNumber)

    local phones = GetPhonesInInventory(source)
    for i = 1, #phones do
        local meta = phones[i].metadata
        if meta and meta.lbPhoneNumber == phoneNumber then
            debugprint("they do")
            return true
        end
    end
    debugprint("they do not")
    return false
end

---@param source number
---@param phoneNumber string
---@return boolean
function SetPhoneNumber(source, phoneNumber)
    debugprint("setting phone number to", phoneNumber, "for", source)

    local slot = usedPhoneSlots[source]
    if slot then
        debugprint("Used slot:", slot)
        local inv = exports["devix-inventory"]:GetInventory(source)
        local it = inv and inv[slot]
        if it and IsItemAPhone(it.name) then
            local info = it.info or {}
            if info.lbPhoneNumber == nil then
                local newInfo = mergeInfo(info, {
                    lbPhoneNumber = phoneNumber,
                    lbFormattedNumber = FormatNumber(phoneNumber),
                })
                if exports["devix-inventory"]:UpdateSlotMetadata(source, slot, newInfo) then
                    debugprint("set phone number to", phoneNumber, "for", source, "using slot", slot)
                    return true
                end
            end
        end
    end

    local phones = GetPhonesInInventory(source)
    for i = 1, #phones do
        local p = phones[i]
        local meta = p.metadata or {}
        if meta.lbPhoneNumber == nil then
            local newInfo = mergeInfo(meta, {
                lbPhoneNumber = phoneNumber,
                lbFormattedNumber = FormatNumber(phoneNumber),
            })
            if exports["devix-inventory"]:UpdateSlotMetadata(source, p.slot, newInfo) then
                debugprint("set phone number to", phoneNumber, "for", source)
                return true
            end
        end
    end
    return false
end

---@param source number
---@param phoneNumber string
---@param name string
function SetItemName(source, phoneNumber, name)
    local phones = GetPhonesInInventory(source)
    for i = 1, #phones do
        local p = phones[i]
        local meta = p.metadata or {}
        if tostring(meta.lbPhoneNumber) == tostring(phoneNumber) then
            local newInfo = mergeInfo(meta, {
                lbPhoneName = name,
                lbFormattedNumber = FormatNumber(phoneNumber),
            })
            return exports["devix-inventory"]:UpdateSlotMetadata(source, p.slot, newInfo)
        end
    end
    return false
end

local function registerUsables()
    if GetResourceState("devix-inventory") ~= "started" or GetResourceState("devix-core") ~= "started" then
        return
    end
    local ok, DEVIX = pcall(function()
        return exports["devix-core"]:getObjects()
    end)
    if not ok or not DEVIX or not DEVIX.UsableItem then
        return
    end

    local function onPhoneUse(source, itemData)
        local slot = tonumber(itemData and itemData.slot)
        if slot then
            usedPhoneSlots[source] = slot
            SetTimeout(10000, function()
                if usedPhoneSlots[source] == slot then
                    usedPhoneSlots[source] = nil
                end
            end)
        end
        TriggerClientEvent("lb-phone:usePhoneItem", source, {
            name = itemData and (itemData.name or itemData.item),
            slot = slot,
            info = (itemData and itemData.info and type(itemData.info) == "table") and itemData.info or {},
        })
    end

    if Config.Item.Name then
        DEVIX.UsableItem(Config.Item.Name, onPhoneUse)
    elseif Config.Item.Names then
        for i = 1, #Config.Item.Names do
            DEVIX.UsableItem(Config.Item.Names[i].name, onPhoneUse)
        end
    end
end

AddEventHandler("onResourceStart", function(res)
    if res == "devix-inventory" or res == "devix-core" or res == GetCurrentResourceName() then
        CreateThread(function()
            Wait(250)
            registerUsables()
        end)
    end
end)

CreateThread(function()
    for _ = 1, 90 do
        if GetResourceState("devix-inventory") == "started" then
            break
        end
        Wait(1000)
    end
    registerUsables()
end)
```

{% endcode %}

#### `client/custom/uniquePhones/devix_inventory.lua`

{% code expandable="true" %}

```lua
if Config.Item.Inventory ~= "devix-inventory" or not Config.Item.Unique or not Config.Item.Require then
    return
end

local Inv = exports["devix-inventory"]

-- Search("slots", name) can return 0 when empty; in Lua 0 is truthy so `x or {}` keeps a number and `#` crashes.
---@param raw any
---@return table
local function normalizeSlotSearch(raw)
    if type(raw) ~= "table" then
        return {}
    end
    local out = {}
    for _, entry in pairs(raw) do
        if type(entry) == "table" then
            out[#out + 1] = entry
        end
    end
    return out
end

---@return table
local function GetPhonesInInventory()
    if Config.Item.Name then
        return normalizeSlotSearch(Inv.Search("slots", Config.Item.Name))
    end

    local phones = {}
    for i = 1, #Config.Item.Names do
        local items = normalizeSlotSearch(Inv.Search("slots", Config.Item.Names[i].name))
        for _, phone in pairs(items) do
            phones[#phones + 1] = phone
        end
    end
    return phones
end

---@return string?
function GetFirstNumber()
    local phones = GetPhonesInInventory()
    for i = 1, #phones do
        local phone = phones[i]
        if phone?.metadata?.lbPhoneNumber then
            return phone.metadata.lbPhoneNumber
        end
    end
end

---@param number string
function HasPhoneNumber(number)
    local phones = GetPhonesInInventory()
    for i = 1, #phones do
        local phone = phones[i]
        if phone?.metadata?.lbPhoneNumber == number then
            return true, GetPhoneItemVariationIndex(phone.name)
        end
    end
    return false
end

RegisterNetEvent("lb-phone:usePhoneItem", function(item)
    local number = item.info?.lbPhoneNumber
    if number ~= currentPhone or number == nil then
        SetPhone(number, true)
        if Config.Item.Names then
            local variation = GetPhoneItemVariationIndex(item.name)
            if variation then
                SetPhoneVariation(variation)
            end
        end
    end
    ToggleOpen(not phoneOpen)
end)

RegisterNetEvent("devix-inventory:client:itemCountChanged", function(itemName, newTotal)
    if not IsItemAPhone(itemName) then
        return
    end
    Wait(500)
    if currentPhone then
        if not HasPhoneItem(currentPhone) then
            SetPhone()
        end
    elseif newTotal and newTotal > 0 then
        local firstNumber = GetFirstNumber()
        SetPhone(firstNumber, true)
    end
end)

RegisterNetEvent("lb-phone:itemRemoved", function()
    Wait(500)
    if currentPhone and not HasPhoneItem(currentPhone) and Config.Item.Unique then
        SetPhone()
    end
end)

local waitingAdded = false
RegisterNetEvent("lb-phone:itemAdded", function()
    Wait(500)
    if currentPhone or waitingAdded then
        return
    end
    waitingAdded = true
    local firstNumber = GetFirstNumber()
    SetPhone(firstNumber, true)
    waitingAdded = false
end)
```

{% endcode %}
{% endstep %}
{% endstepper %}

## 4) Non-unique helper

**File:** `server/custom/functions/devix_inventory.lua` — registers `DEVIX.UsableItem` → `phone:toggleOpen`. Skipped when unique + `Inventory == "devix-inventory"` (unique server file owns `UsableItem`).

## 5) Troubleshooting

| Symptom                 | Check                                                               |
| ----------------------- | ------------------------------------------------------------------- |
| Use does nothing        | `devix-core` + `devix-inventory` started; item name matches config. |
| Wrong inventory counted | `HasItem`: **devix** `if` must be **above** **ox** (§2).            |
| Unique: `#` on number   | Client file must keep **`normalizeSlotSearch`**.                    |

## References

* [LB Phone](https://docs.lbscripts.com/phone/)
* [Unique phones](https://docs.lbscripts.com/phone/configuration/#unique-phones)


---

# 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, and the optional `goal` query parameter:

```
GET https://devix.gitbook.io/devix/devix-inventory/errors/lb-phone.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

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.
