ADD - support extended inventory metadata bridges (#23)

* ADD - support extended inventory metadata bridges

* FIX - align inventory contract tests
This commit is contained in:
Leon.Schmidt
2026-08-21 22:04:15 +02:00
committed by GitHub
parent 5f8c835685
commit 7aa4677b81
15 changed files with 1174 additions and 77 deletions
+87 -13
View File
@@ -86,7 +86,7 @@ Sky Phone is built to be the **free FiveM phone you can choose without accepting
| Layer | Supported options |
| --- | --- |
| **Frameworks** | ESX Legacy, QBCore, Qbox |
| **Inventories** | ox_inventory, qb-inventory, lj-inventory, qs-inventory, codem-inventory, core_inventory, mf-inventory, smx-inventory, hex_4_inventory, and native ESX inventory |
| **Inventories** | ak47_inventory, codem-inventory, core_inventory, jaksam_inventory, jpr-inventory, lj-inventory, mf-inventory, one_inventory, origen_inventory, ox_inventory, ps-inventory, qb-inventory, qs-inventory, smx-inventory, tgiann-inventory, hex_4_inventory, and native ESX inventory |
| **Calls** | YACA, PMA Voice, SaltyChat |
| **Radio** | YACA, PMA Voice, SaltyChat |
| **Housing** | RTX Housing, Quasar Housing, VMS Housing, RX Housing, NoLag Properties, SN Properties, ESX Property, qbx_properties |
@@ -121,21 +121,34 @@ Sky Phone is built to be the **free FiveM phone you can choose without accepting
| **Framework** | ESX Legacy (`es_extended`), QBCore (`qb-core`), or Qbox (`qbx_core`) |
| **Inventory** | Choose one supported adapter from the table below |
| Inventory | Metadata support | Unique Phones | Notes |
| Inventory (configuration value) | Metadata support | Unique Phones | Notes |
| --- | --- | --- | --- |
| `ox_inventory` | Yes | Yes | Full per-item phone and physical SIM metadata |
| `qb-inventory` | Yes | Yes | Uses item `info` metadata |
| `lj-inventory` | Yes | Yes | QBCore inventory with item `info` metadata |
| `qs-inventory` | Yes | Yes | Full per-slot metadata |
| `codem-inventory` | Yes | Yes | Full per-slot metadata |
| `core_inventory` | Yes | Yes | Full per-slot metadata |
| `mf-inventory` | Yes | Yes | Supported with ESX |
| `smx-inventory` | Yes | Yes | Supported with ESX through the player metadata bridge |
| `hex_4_inventory` | **No metadata support** | **No, Unique Phones are not possible** | ESX only; set `Config.Phone.Unique = false` and `Config.Sim.Enabled = false` |
| Native ESX inventory | **No metadata support** | **No, Unique Phones are not possible** | Count-based items; set `Config.Phone.Unique = false` and `Config.Sim.Enabled = false` |
| `jaksam_inventory` (`jaksam`) | Yes | Yes | Direct per-slot metadata; usable items are registered through jaksam_inventory |
| `qs-inventory` (`qs`) | Yes | Yes | Full per-slot metadata |
| `ps-inventory` (`ps`) | Yes | Yes | QBCore only; uses item `info` metadata |
| `codem-inventory` (`codem`) | Yes | Yes | Full per-slot metadata |
| `tgiann-inventory` (`tgiann`) | Yes | Yes | Per-slot metadata; item definitions must enable `hasMetadata` |
| `core_inventory` (`core`) | Yes | Yes | Full per-slot metadata |
| `jpr-inventory` (`jpr`) | Yes | Yes | QBCore only; uses item `info` metadata |
| `origen_inventory` (`origen`) | Yes | Yes | Full per-slot metadata |
| `ak47_inventory` (`ak47`) | Yes | Yes | Uses per-slot item `info` metadata |
| `one_inventory` (`one`) | Yes | Yes | Full per-slot metadata |
| `ox_inventory` (`ox`) | Yes | Yes | Full per-item phone and physical SIM metadata |
| `mf-inventory` (`mf`) | Yes | Yes | ESX only |
| `smx-inventory` (`smx`) | Yes | Yes | ESX only; one metadata record per configured item name through the player metadata bridge |
| `lj-inventory` (`lj`) | Yes | Yes | QBCore inventory with item `info` metadata |
| `qb-inventory` (`qb`) | Yes | Yes | Uses item `info` metadata |
| `hex_4_inventory` (`hex`) | **No metadata support** | **No, Unique Phones are not possible** | ESX only; set `Config.Phone.Unique = false` and `Config.Sim.Enabled = false` |
| Native ESX inventory (`esx`) | **No metadata support** | **No, Unique Phones are not possible** | Count-based items; set `Config.Phone.Unique = false` and `Config.Sim.Enabled = false` |
`hex_4_inventory` and native ESX inventory cannot persist per-item metadata. Unique Phones and physical SIM cards are therefore unavailable with these adapters.
`Config.Bridge.Inventory = "auto"` detects framework-compatible adapters in the table order. This deliberately matches the Sky inventory priority so a dedicated inventory is selected before a compatibility resource it may run beside. You may configure either the short value shown in parentheses or the exact resource name.
The adapters shared with `sky_base` are implemented locally inside Sky Phone. Installing or starting `sky_base` is not required; Sky Phone remains a standalone resource.
For configuration parity with `sky_base`, `qb-inv` is accepted as an alias for `qb`, while `qbox` selects the Qbox-native `ox_inventory` adapter.
### Voice
Phone calls support:
@@ -322,7 +335,7 @@ Default entries for unique phones with physical SIM cards:
Do not configure an LB Phone client event or client export. Sky Phone registers the usable items through its server-side inventory adapter.
The server registers `Config.Phone.Item` as usable for every supported inventory adapter: `ox`, `qb`, `lj`, `qs`, `codem`, `core`, `mf`, `smx`, `hex`, and `esx`. Resource startup fails visibly if the selected adapter cannot complete that registration.
The server registers `Config.Phone.Item` as usable for every supported inventory adapter: `ak47`, `codem`, `core`, `jaksam`, `jpr`, `lj`, `mf`, `one`, `origen`, `ox`, `ps`, `qb`, `qs`, `smx`, `tgiann`, `hex`, and `esx`. Resource startup fails visibly if the selected adapter or its resource is unavailable.
The `hex` and `esx` adapters use ESX's count-based item API. They require both `Config.Phone.Unique = false` and `Config.Sim.Enabled = false` because this API cannot persist per-item phone or physical SIM metadata. `auto` selects `hex` when `hex_4_inventory` is started and otherwise falls back to `esx` on an ESX server when no metadata-capable inventory is detected.
@@ -333,6 +346,67 @@ The `hex` and `esx` adapters use ESX's count-based item API. They require both `
- Physical SIM items must always be unique.
- SIM items are not required when `Config.Sim.Enabled = false`.
Example for `qb-inventory`, `lj-inventory`, `ps-inventory`, and `jpr-inventory`:
```lua
phone = {
name = "phone",
label = "iFruit Phone",
weight = 200,
type = "item",
image = "phone.png",
unique = true,
useable = true,
shouldClose = true,
description = "A personal mobile phone",
},
sky_phone_sim_registered = {
name = "sky_phone_sim_registered",
label = "Registered SIM",
weight = 5,
type = "item",
image = "sky_phone_sim_registered.png",
unique = true,
useable = true,
shouldClose = true,
},
sky_phone_sim_anonymous = {
name = "sky_phone_sim_anonymous",
label = "Anonymous SIM",
weight = 5,
type = "item",
image = "sky_phone_sim_anonymous.png",
unique = true,
useable = true,
shouldClose = true,
},
```
For `tgiann-inventory`, set `hasMetadata = true`, `useable = true`, and `shouldClose = true` on all three item definitions. Follow the inventory's own item schema for the remaining adapters; the required behavior is always the same: a unique phone or physical SIM must occupy its own slot and its metadata table must survive moving, dropping, storing, and trading the item.
### Unique Phones and metadata
Sky Phone owns the metadata values and writes them server-side. Do not pre-generate IMEIs or phone numbers in item definitions:
| Item | Metadata written by Sky Phone |
| --- | --- |
| Phone | `imei`; when a SIM is inserted, also `sim_id`, `phone_number`, and `formatted_number` |
| Physical SIM | `sim_metadata_version`, `sim_id`, `phone_number`, `formatted_number`, `sim_type`, and registration details where applicable |
When a metadata-capable phone item is used for the first time, Sky Phone reserves an IMEI and writes it back to that exact slot. Existing metadata is preserved. The adapter then reads the slot again and rejects the operation if the inventory did not persist the requested values.
For reliable Unique Phones:
- Set `Config.Phone.Unique = true`.
- Make the phone item non-stackable/unique. Every phone slot must contain exactly one item.
- If `Config.Sim.Enabled = true`, make both physical SIM items non-stackable/unique and metadata-capable too.
- Do not use inventory conversion, admin, crafting, or shop scripts that strip item metadata. Copying an item with its metadata also copies its IMEI; duplicated IMEIs are reported in the server console.
- When changing inventory systems, migrate the complete item metadata table. Without the old `imei`, the next use creates a new device identity and does not automatically attach the old handset data.
With `Config.Phone.Unique = false`, the handset identity is stored once per framework character instead of on each phone item. The phone item may stack. Physical SIMs still require per-item metadata, so `Config.Sim.Enabled` must be `false` on `hex` and native `esx`.
## Phone and SIM modes
The two mode switches are independent:
+33 -9
View File
@@ -3,17 +3,27 @@ import { readFileSync } from 'node:fs'
import { describe, expect, it } from 'vitest'
const readResourceFile = (path: string) =>
readFileSync(new URL(`../../sky_phone/${path}`, import.meta.url), 'utf8').replace(/\r\n/g, '\n')
readFileSync(
new URL(`../../sky_phone/${path}`, import.meta.url),
'utf8',
).replace(/\r\n/g, '\n')
const readFrontendFile = (path: string) =>
readFileSync(new URL(path, import.meta.url), 'utf8')
const inventoryAdapters = [
['jaksam', 'source/bridge/server/inventory/jaksam.lua'],
['ox', 'source/bridge/server/inventory/ox.lua'],
['qb', 'source/bridge/server/inventory/qb.lua'],
['lj', 'source/bridge/server/inventory/qb.lua'],
['qs', 'source/bridge/server/inventory/qs.lua'],
['ps', 'source/bridge/server/inventory/ps.lua'],
['codem', 'source/bridge/server/inventory/codem.lua'],
['tgiann', 'source/bridge/server/inventory/tgiann.lua'],
['core', 'source/bridge/server/inventory/core.lua'],
['jpr', 'source/bridge/server/inventory/jpr.lua'],
['origen', 'source/bridge/server/inventory/origen.lua'],
['ak47', 'source/bridge/server/inventory/ak47.lua'],
['one', 'source/bridge/server/inventory/one.lua'],
['mf', 'source/bridge/server/inventory/mf.lua'],
['smx', 'source/bridge/server/inventory/smx.lua'],
['hex', 'source/bridge/server/inventory/esx.lua'],
@@ -39,15 +49,19 @@ describe('phone inventory contracts', () => {
expect(phoneServer).toContain('if not usable_registered then')
})
it('auto-detects HEX and limits count-based ESX inventories to metadata-free modes', () => {
it('auto-detects registered inventories and limits metadata-free adapters', () => {
const inventoryBridge = readResourceFile(
'source/bridge/server/inventory.lua',
)
expect(inventoryBridge).toContain(
'GetResourceState("hex_4_inventory") == "started"',
'{ name = "hex", resource = "hex_4_inventory", framework = "esx", metadata = false },',
)
expect(inventoryBridge).toContain('configured_inventory = "hex"')
expect(inventoryBridge).toContain(
'GetResourceState(adapter.resource) == "started"',
)
expect(inventoryBridge).toContain('configured_inventory = adapter.name')
expect(inventoryBridge).toContain('selected_adapter.metadata == false')
expect(inventoryBridge).toContain('Config.Phone.Unique ~= false')
expect(inventoryBridge).toContain('Config.Sim.Enabled ~= false')
})
@@ -100,9 +114,15 @@ describe('phone inventory contracts', () => {
expect(equippedNumberExport).toBeDefined()
expect(equippedNumberResolver).toBeDefined()
expect(equippedNumberExport).toContain('type(player) == "number"')
expect(equippedNumberExport).toContain('online_source_for_identifier(player)')
expect(phoneServer).toContain('Bridge.Inventory.GetSlotsWithItem(source, Config.Phone.Item)')
expect(phoneServer).toContain('return resolve_equipped_phone_number(player)')
expect(equippedNumberExport).toContain(
'online_source_for_identifier(player)',
)
expect(phoneServer).toContain(
'Bridge.Inventory.GetSlotsWithItem(source, Config.Phone.Item)',
)
expect(phoneServer).toContain(
'return resolve_equipped_phone_number(player)',
)
expect(equippedNumberExport).not.toContain('tonumber(player)')
expect(equippedNumberExport).not.toContain('equipped_phone_numbers[player]')
expect(equippedNumberResolver).not.toContain('return cached_number')
@@ -157,7 +177,9 @@ describe('phone inventory contracts', () => {
expect(phoneClient).toContain(
'TriggerEvent("sky_phone:client:phoneToggled", false)',
)
expect(phoneBridge).toContain('TriggerEvent("lb-phone:numberChanged", phone_number)')
expect(phoneBridge).toContain(
'TriggerEvent("lb-phone:numberChanged", phone_number)',
)
expect(phoneBridge).toContain('TriggerEvent("lb-phone:phoneToggled", open)')
})
@@ -210,7 +232,9 @@ describe('phone inventory contracts', () => {
expect(mediaServer).toContain(
'TriggerEvent("sky_phone:server:galleryMediaDeleted", src, phone_number, deleted_link)',
)
expect(phoneBridge).toContain('TriggerEvent("lb-phone:phoneNumberGenerated"')
expect(phoneBridge).toContain(
'TriggerEvent("lb-phone:phoneNumberGenerated"',
)
expect(phoneBridge).toContain('TriggerEvent("lb-phone:factoryReset"')
expect(phoneBridge).toContain('TriggerEvent("lb-phone:deletedFromGallery"')
})
+3 -1
View File
@@ -16,7 +16,9 @@
Config.Bridge = {
Framework = "auto", -- auto, esx, qbox, qb
Inventory = "auto", -- auto, ox, qb, lj, qs, codem, core, mf, smx, hex, esx
-- auto, ak47, codem, core, jaksam, jpr, lj, mf, one, origen, ox, ps, qb, qs, smx, tgiann, hex, esx
-- Compatibility aliases: qb-inv -> qb, qbox -> ox
Inventory = "auto",
Locale = "en",
CallbackTimeout = 15000,
Debug = false, -- true: show debug/info output; warnings and errors are always shown
+73 -44
View File
@@ -1,60 +1,75 @@
local inventory_adapters = {
{ name = "jaksam", resource = "jaksam_inventory" },
{ name = "qs", resource = "qs-inventory" },
{ name = "ps", resource = "ps-inventory", framework = "qb" },
{ name = "codem", resource = "codem-inventory" },
{ name = "tgiann", resource = "tgiann-inventory" },
{ name = "core", resource = "core_inventory" },
{ name = "jpr", resource = "jpr-inventory", framework = "qb" },
{ name = "origen", resource = "origen_inventory" },
{ name = "ak47", resource = "ak47_inventory" },
{ name = "one", resource = "one_inventory" },
{ name = "ox", resource = "ox_inventory" },
{ name = "mf", resource = "mf-inventory", framework = "esx" },
{ name = "smx", resource = "smx-inventory", framework = "esx" },
{ name = "lj", resource = "lj-inventory" },
{ name = "qb", resource = "qb-inventory" },
{ name = "hex", resource = "hex_4_inventory", framework = "esx", metadata = false },
{ name = "esx", resource = "es_extended", framework = "esx", metadata = false },
}
local inventory_aliases = {
["qb-inv"] = "qb",
qbox = "ox",
}
local supported_inventories = {}
for _, adapter in ipairs(inventory_adapters) do
inventory_aliases[adapter.resource] = adapter.name
supported_inventories[adapter.name] = adapter
end
local configured_inventory = Config.Bridge.Inventory
configured_inventory = inventory_aliases[configured_inventory] or configured_inventory
local framework_name = Bridge.Framework.GetName()
if configured_inventory == "auto" then
if GetResourceState("ox_inventory") == "started" then
configured_inventory = "ox"
elseif GetResourceState("qb-inventory") == "started" then
configured_inventory = "qb"
elseif GetResourceState("lj-inventory") == "started" then
configured_inventory = "lj"
elseif GetResourceState("qs-inventory") == "started" then
configured_inventory = "qs"
elseif GetResourceState("codem-inventory") == "started" then
configured_inventory = "codem"
elseif GetResourceState("core_inventory") == "started" then
configured_inventory = "core"
elseif GetResourceState("mf-inventory") == "started" then
configured_inventory = "mf"
elseif GetResourceState("smx-inventory") == "started" then
configured_inventory = "smx"
elseif GetResourceState("hex_4_inventory") == "started" then
configured_inventory = "hex"
elseif Bridge.Framework.GetName() == "esx" then
for _, adapter in ipairs(inventory_adapters) do
local compatible_framework = not adapter.framework or adapter.framework == framework_name
if adapter.name ~= "esx" and compatible_framework and GetResourceState(adapter.resource) == "started" then
configured_inventory = adapter.name
break
end
end
if configured_inventory == "auto" and framework_name == "esx" then
configured_inventory = "esx"
end
end
local supported_inventories = {
ox = true,
qb = true,
lj = true,
qs = true,
codem = true,
core = true,
mf = true,
smx = true,
hex = true,
esx = true,
}
if not supported_inventories[configured_inventory] then
local selected_adapter = supported_inventories[configured_inventory]
if not selected_adapter then
error(("[sky_phone] Unsupported or unavailable inventory '%s'. Configure a supported inventory adapter."):format(tostring(configured_inventory)))
end
if GetResourceState(selected_adapter.resource) ~= "started" then
error(("[sky_phone] Inventory '%s' is configured, but resource '%s' is not started.")
:format(tostring(configured_inventory), selected_adapter.resource))
end
if selected_adapter.framework and selected_adapter.framework ~= framework_name then
error(("[sky_phone] Inventory '%s' is only supported with framework '%s'.")
:format(tostring(configured_inventory), selected_adapter.framework))
end
Bridge.Inventory.Name = configured_inventory
if configured_inventory == "hex" or configured_inventory == "esx" then
if Bridge.Framework.GetName() ~= "esx" then
Bridge.Inventory.ConfigurationError =
("[sky_phone] Inventory '%s' is only supported with ESX."):format(configured_inventory)
elseif Config.Phone.Unique ~= false or Config.Sim.Enabled ~= false then
if selected_adapter.metadata == false then
if Config.Phone.Unique ~= false or Config.Sim.Enabled ~= false then
Bridge.Inventory.ConfigurationError = ("[sky_phone] Inventory '%s' cannot store unique phone or physical SIM metadata. Set Config.Phone.Unique = false and Config.Sim.Enabled = false, or configure a metadata-capable inventory.")
:format(configured_inventory)
end
end
function Bridge.Inventory.NormalizeItem(item, metadata_field)
if not item then
function Bridge.Inventory.NormalizeItem(item, metadata_field, fallback_slot)
if type(item) ~= "table" then
return nil
end
@@ -63,11 +78,15 @@ function Bridge.Inventory.NormalizeItem(item, metadata_field)
metadata = item.metadata or item.info
end
local slot = item.slot or item.id or fallback_slot
local numeric_slot = tonumber(slot)
local count = tonumber(item.count or item.amount or item.quantity) or 0
return {
name = item.name,
slot = item.slot or item.id,
count = tonumber(item.count or item.amount) or 0,
amount = tonumber(item.amount or item.count) or 0,
name = item.name or item.item,
slot = numeric_slot or slot,
count = count,
amount = count,
metadata = type(metadata) == "table" and metadata or {},
}
end
@@ -84,3 +103,13 @@ function Bridge.Inventory.MetadataMatches(actual, expected)
end
return true
end
function Bridge.Inventory.ResolveUsableItem(...)
for index = select("#", ...), 1, -1 do
local candidate = select(index, ...)
if type(candidate) == "table" and (candidate.name or candidate.item) then
return candidate
end
end
return nil
end
@@ -0,0 +1,87 @@
if Bridge.Inventory.Name ~= "ak47" then
return
end
local inventory = exports["ak47_inventory"]
local function normalize(item, fallback_slot)
local normalized = Bridge.Inventory.NormalizeItem(item, "info", fallback_slot)
if not normalized or not normalized.name or normalized.amount < 1 then
return nil
end
return normalized
end
function Bridge.Inventory.GetResourceName()
return "ak47_inventory"
end
function Bridge.Inventory.GetSlot(source, slot_id)
local identifier = Bridge.Framework.GetIdentifier(source)
local numeric_slot = tonumber(slot_id)
if not identifier or not numeric_slot then
return nil
end
return normalize(inventory:GetSlot(identifier, numeric_slot), numeric_slot)
end
function Bridge.Inventory.GetSlotsWithItem(source, item_name, metadata)
local identifier = Bridge.Framework.GetIdentifier(source)
if not identifier then
return {}
end
local matches = {}
local filter_metadata = type(metadata) == "table" and metadata or nil
for index, item in pairs(inventory:GetSlotsWithItem(identifier, item_name, filter_metadata, filter_metadata ~= nil) or {}) do
local normalized = normalize(item, tonumber(index) or index)
if normalized and normalized.name == item_name
and Bridge.Inventory.MetadataMatches(normalized.metadata, metadata) then
matches[#matches + 1] = normalized
end
end
return matches
end
function Bridge.Inventory.SetSlotMetadata(source, slot_id, metadata)
local identifier = Bridge.Framework.GetIdentifier(source)
local slot = identifier and Bridge.Inventory.GetSlot(source, slot_id) or nil
if not slot then
return false
end
local requested_metadata = type(metadata) == "table" and metadata or {}
inventory:SetItemInfo(identifier, slot.slot, requested_metadata)
local updated = Bridge.Inventory.GetSlot(source, slot.slot)
return updated and Bridge.Inventory.MetadataMatches(updated.metadata, requested_metadata) or false
end
function Bridge.Inventory.CanCarryItem(source, item_name, count)
return inventory:CanAddItem(source, item_name, count) == true
end
function Bridge.Inventory.AddItem(source, item_name, count, slot, metadata)
if not Bridge.Inventory.CanCarryItem(source, item_name, count, metadata) then
return false
end
return inventory:AddItem(source, item_name, count, slot, metadata or {}) == true
end
function Bridge.Inventory.RemoveItem(source, item_name, count, slot, metadata)
if metadata and not slot then
local slots = Bridge.Inventory.GetSlotsWithItem(source, item_name, metadata)
slot = slots[1] and slots[1].slot or nil
if not slot then
return 0
end
end
local removed = inventory:RemoveItem(source, item_name, count, slot)
return removed ~= false and count or 0
end
function Bridge.Inventory.RegisterUsableItem(item_name, callback)
return Bridge.Framework.RegisterUsableItem(item_name, function(source, ...)
callback(source, normalize(Bridge.Inventory.ResolveUsableItem(...)))
end)
end
@@ -9,7 +9,8 @@ local function normalize(item)
end
local function get_inventory(source)
return inventory:GetInventory(false, source) or {}
local identifier = Bridge.Framework.GetIdentifier(source)
return identifier and inventory:GetInventory(identifier, source) or {}
end
function Bridge.Inventory.GetResourceName()
@@ -27,9 +28,14 @@ end
function Bridge.Inventory.GetSlotsWithItem(source, item_name, metadata)
local matches = {}
for _, item in pairs(get_inventory(source)) do
local items = inventory:GetItemsByName(source, item_name) or {}
if items.name then
items = { items }
end
for _, item in pairs(items) do
local normalized = normalize(item)
if normalized.name == item_name and Bridge.Inventory.MetadataMatches(normalized.metadata, metadata) then
if normalized and normalized.name == item_name
and Bridge.Inventory.MetadataMatches(normalized.metadata, metadata) then
matches[#matches + 1] = normalized
end
end
@@ -66,7 +72,7 @@ function Bridge.Inventory.RemoveItem(source, item_name, count, slot, metadata)
end
function Bridge.Inventory.RegisterUsableItem(item_name, callback)
return Bridge.Framework.RegisterUsableItem(item_name, function(source, item)
callback(source, normalize(item))
return Bridge.Framework.RegisterUsableItem(item_name, function(source, ...)
callback(source, normalize(Bridge.Inventory.ResolveUsableItem(...)))
end)
end
@@ -39,11 +39,15 @@ end
function Bridge.Inventory.SetSlotMetadata(source, slot_id, metadata)
local numeric_slot = tonumber(slot_id)
if not numeric_slot then
local slot = numeric_slot and Bridge.Inventory.GetSlot(source, numeric_slot) or nil
if not slot then
return false
end
inventory:setMetadata(source, numeric_slot, metadata or {})
return true
local requested_metadata = type(metadata) == "table" and metadata or {}
inventory:setMetadata(source, numeric_slot, requested_metadata)
local updated = Bridge.Inventory.GetSlot(source, numeric_slot)
return updated and Bridge.Inventory.MetadataMatches(updated.metadata, requested_metadata) or false
end
function Bridge.Inventory.CanCarryItem()
@@ -0,0 +1,94 @@
if Bridge.Inventory.Name ~= "jaksam" then
return
end
local inventory = exports["jaksam_inventory"]
local function normalize(item)
local normalized = Bridge.Inventory.NormalizeItem(item)
if not normalized or not normalized.name or normalized.amount < 1 then
return nil
end
return normalized
end
local function get_inventory(source)
local player_inventory = inventory:getInventory(source)
return type(player_inventory) == "table" and type(player_inventory.items) == "table"
and player_inventory.items or {}
end
function Bridge.Inventory.GetResourceName()
return "jaksam_inventory"
end
function Bridge.Inventory.GetSlot(source, slot_id)
for index, item in pairs(get_inventory(source)) do
local normalized = normalize(item)
local item_slot = normalized and (normalized.slot or tonumber(index)) or nil
if tostring(item_slot) == tostring(slot_id) then
normalized.slot = item_slot
return normalized
end
end
return nil
end
function Bridge.Inventory.GetSlotsWithItem(source, item_name, metadata)
local matches = {}
for index, item in pairs(get_inventory(source)) do
local normalized = normalize(item)
if normalized and normalized.name == item_name
and Bridge.Inventory.MetadataMatches(normalized.metadata, metadata) then
normalized.slot = normalized.slot or tonumber(index) or index
matches[#matches + 1] = normalized
end
end
return matches
end
function Bridge.Inventory.SetSlotMetadata(source, slot_id, metadata)
local slot = Bridge.Inventory.GetSlot(source, slot_id)
if not slot then
return false
end
local requested_metadata = type(metadata) == "table" and metadata or {}
local success = inventory:setItemMetadataInSlot(source, slot.slot, requested_metadata)
if success ~= true then
return false
end
local updated = Bridge.Inventory.GetSlot(source, slot.slot)
return updated and Bridge.Inventory.MetadataMatches(updated.metadata, requested_metadata) or false
end
function Bridge.Inventory.CanCarryItem(source, item_name, count)
return inventory:canCarryItem(source, item_name, count) == true
end
function Bridge.Inventory.AddItem(source, item_name, count, slot, metadata)
if not Bridge.Inventory.CanCarryItem(source, item_name, count, metadata) then
return false
end
local success = inventory:addItem(source, item_name, count, metadata or {}, slot)
return success == true
end
function Bridge.Inventory.RemoveItem(source, item_name, count, slot, metadata)
if metadata and not slot then
local slots = Bridge.Inventory.GetSlotsWithItem(source, item_name, metadata)
slot = slots[1] and slots[1].slot or nil
if not slot then
return 0
end
end
local success = inventory:removeItem(source, item_name, count, nil, slot)
return success == true and count or 0
end
function Bridge.Inventory.RegisterUsableItem(item_name, callback)
inventory:registerUsableItem(item_name, function(source, ...)
callback(source, normalize(Bridge.Inventory.ResolveUsableItem(...)))
end)
return true
end
@@ -0,0 +1,109 @@
if Bridge.Inventory.Name ~= "jpr" then
return
end
if Bridge.Framework.GetName() ~= "qb" then
error("[sky_phone] jpr-inventory is only supported with QBCore.")
end
local inventory = exports["jpr-inventory"]
local QBCore = exports["qb-core"]:GetCoreObject()
local function normalize(item, fallback_slot)
local normalized = Bridge.Inventory.NormalizeItem(item, "info", fallback_slot)
if not normalized or not normalized.name or normalized.amount < 1 then
return nil
end
return normalized
end
local function get_inventory(source)
local player = QBCore.Functions.GetPlayer(tonumber(source))
return player and player.PlayerData.items or {}
end
local function restore_slot(source, slot, metadata)
local restored = inventory:AddItem(source, slot.name, slot.amount, slot.slot, metadata, "sky_phone:metadata-rollback")
if not restored then
Bridge.Debug(
"error",
"[sky_phone] jpr-inventory could not restore item '%s' in slot %s after a metadata update failed.",
tostring(slot.name),
tostring(slot.slot)
)
end
return restored == true
end
function Bridge.Inventory.GetResourceName()
return "jpr-inventory"
end
function Bridge.Inventory.GetSlot(source, slot_id)
local numeric_slot = tonumber(slot_id)
return numeric_slot and normalize(inventory:GetItemBySlot(source, numeric_slot), numeric_slot) or nil
end
function Bridge.Inventory.GetSlotsWithItem(source, item_name, metadata)
local matches = {}
for index, item in pairs(get_inventory(source)) do
local normalized = normalize(item, tonumber(index) or index)
if normalized and normalized.name == item_name
and Bridge.Inventory.MetadataMatches(normalized.metadata, metadata) then
normalized.slot = normalized.slot or tonumber(index) or index
matches[#matches + 1] = normalized
end
end
return matches
end
function Bridge.Inventory.SetSlotMetadata(source, slot_id, metadata)
local slot = Bridge.Inventory.GetSlot(source, slot_id)
if not slot or slot.amount < 1 then
return false
end
local previous_metadata = slot.metadata
local requested_metadata = type(metadata) == "table" and metadata or {}
if inventory:RemoveItem(source, slot.name, slot.amount, slot.slot, "sky_phone:metadata-update") ~= true then
return false
end
if inventory:AddItem(source, slot.name, slot.amount, slot.slot, requested_metadata, "sky_phone:metadata-update") ~= true then
restore_slot(source, slot, previous_metadata)
return false
end
local updated = Bridge.Inventory.GetSlot(source, slot.slot)
if updated and Bridge.Inventory.MetadataMatches(updated.metadata, requested_metadata) then
return true
end
inventory:RemoveItem(source, slot.name, slot.amount, slot.slot, "sky_phone:metadata-rollback")
restore_slot(source, slot, previous_metadata)
return false
end
function Bridge.Inventory.CanCarryItem()
return true
end
function Bridge.Inventory.AddItem(source, item_name, count, slot, metadata)
return inventory:AddItem(source, item_name, count, slot, metadata or {}, "sky_phone") == true
end
function Bridge.Inventory.RemoveItem(source, item_name, count, slot, metadata)
if metadata and not slot then
local slots = Bridge.Inventory.GetSlotsWithItem(source, item_name, metadata)
slot = slots[1] and slots[1].slot or nil
if not slot then
return 0
end
end
return inventory:RemoveItem(source, item_name, count, slot, "sky_phone") == true and count or 0
end
function Bridge.Inventory.RegisterUsableItem(item_name, callback)
return Bridge.Framework.RegisterUsableItem(item_name, function(source, ...)
callback(source, normalize(Bridge.Inventory.ResolveUsableItem(...)))
end)
end
@@ -0,0 +1,76 @@
if Bridge.Inventory.Name ~= "one" then
return
end
local inventory = exports.one_inventory
local function normalize(item, fallback_slot)
local normalized = Bridge.Inventory.NormalizeItem(item, nil, fallback_slot)
if not normalized or not normalized.name or normalized.amount < 1 then
return nil
end
return normalized
end
function Bridge.Inventory.GetResourceName()
return "one_inventory"
end
function Bridge.Inventory.GetSlot(source, slot_id)
local numeric_slot = tonumber(slot_id)
return numeric_slot and normalize(inventory:GetSlot(source, numeric_slot), numeric_slot) or nil
end
function Bridge.Inventory.GetSlotsWithItem(source, item_name, metadata)
local matches = {}
for index, item in pairs(inventory:SearchInventory(source, item_name, metadata) or {}) do
local normalized = normalize(item, tonumber(index) or index)
if normalized and normalized.name == item_name
and Bridge.Inventory.MetadataMatches(normalized.metadata, metadata) then
matches[#matches + 1] = normalized
end
end
return matches
end
function Bridge.Inventory.SetSlotMetadata(source, slot_id, metadata)
local slot = Bridge.Inventory.GetSlot(source, slot_id)
if not slot then
return false
end
local requested_metadata = type(metadata) == "table" and metadata or {}
if inventory:SetItemMetadata(source, slot.slot, requested_metadata) ~= true then
return false
end
local updated = Bridge.Inventory.GetSlot(source, slot.slot)
return updated and Bridge.Inventory.MetadataMatches(updated.metadata, requested_metadata) or false
end
function Bridge.Inventory.CanCarryItem(source, item_name, count)
return inventory:CanCarryItem(source, item_name, count) == true
end
function Bridge.Inventory.AddItem(source, item_name, count, slot, metadata)
if not Bridge.Inventory.CanCarryItem(source, item_name, count, metadata) then
return false
end
return inventory:AddItem(source, item_name, count, metadata or {}, slot) == true
end
function Bridge.Inventory.RemoveItem(source, item_name, count, slot, metadata)
if metadata and not slot then
local slots = Bridge.Inventory.GetSlotsWithItem(source, item_name, metadata)
slot = slots[1] and slots[1].slot or nil
if not slot then
return 0
end
end
return inventory:RemoveItem(source, item_name, count, metadata, slot) == true and count or 0
end
function Bridge.Inventory.RegisterUsableItem(item_name, callback)
return Bridge.Framework.RegisterUsableItem(item_name, function(source, ...)
callback(source, normalize(Bridge.Inventory.ResolveUsableItem(...)))
end)
end
@@ -0,0 +1,88 @@
if Bridge.Inventory.Name ~= "origen" then
return
end
local inventory = exports.origen_inventory
local function normalize(item)
local normalized = Bridge.Inventory.NormalizeItem(item)
if not normalized or not normalized.name or normalized.amount < 1 then
return nil
end
return normalized
end
local function get_inventory(source)
local player_inventory = inventory:GetInventory(source)
return type(player_inventory) == "table" and type(player_inventory.inventory) == "table"
and player_inventory.inventory or {}
end
function Bridge.Inventory.GetResourceName()
return "origen_inventory"
end
function Bridge.Inventory.GetSlot(source, slot_id)
for index, item in pairs(get_inventory(source)) do
local normalized = normalize(item)
local item_slot = normalized and (normalized.slot or tonumber(index)) or nil
if tostring(item_slot) == tostring(slot_id) then
normalized.slot = item_slot
return normalized
end
end
return nil
end
function Bridge.Inventory.GetSlotsWithItem(source, item_name, metadata)
local matches = {}
for index, item in pairs(get_inventory(source)) do
local normalized = normalize(item)
if normalized and normalized.name == item_name
and Bridge.Inventory.MetadataMatches(normalized.metadata, metadata) then
normalized.slot = normalized.slot or tonumber(index) or index
matches[#matches + 1] = normalized
end
end
return matches
end
function Bridge.Inventory.SetSlotMetadata(source, slot_id, metadata)
local slot = Bridge.Inventory.GetSlot(source, slot_id)
if not slot then
return false
end
local requested_metadata = type(metadata) == "table" and metadata or {}
inventory:setMetadata(source, slot.slot, requested_metadata)
local updated = Bridge.Inventory.GetSlot(source, slot.slot)
return updated and Bridge.Inventory.MetadataMatches(updated.metadata, requested_metadata) or false
end
function Bridge.Inventory.CanCarryItem(source, item_name, count)
return inventory:canCarryItem(source, item_name, count) == true
end
function Bridge.Inventory.AddItem(source, item_name, count, slot, metadata)
if not Bridge.Inventory.CanCarryItem(source, item_name, count, metadata) then
return false
end
return inventory:addItem(source, item_name, count, metadata or {}, slot, false) == true
end
function Bridge.Inventory.RemoveItem(source, item_name, count, slot, metadata)
if metadata and not slot then
local slots = Bridge.Inventory.GetSlotsWithItem(source, item_name, metadata)
slot = slots[1] and slots[1].slot or nil
if not slot then
return 0
end
end
return inventory:removeItem(source, item_name, count, nil, slot, false) == true and count or 0
end
function Bridge.Inventory.RegisterUsableItem(item_name, callback)
return Bridge.Framework.RegisterUsableItem(item_name, function(source, ...)
callback(source, normalize(Bridge.Inventory.ResolveUsableItem(...)))
end)
end
@@ -44,8 +44,10 @@ function Bridge.Inventory.SetSlotMetadata(source, slot_id, metadata)
return false
end
inventory:SetMetadata(source, tonumber(slot_id), metadata or {})
return true
local requested_metadata = type(metadata) == "table" and metadata or {}
inventory:SetMetadata(source, tonumber(slot_id), requested_metadata)
local updated = Bridge.Inventory.GetSlot(source, slot_id)
return updated and Bridge.Inventory.MetadataMatches(updated.metadata, requested_metadata) or false
end
function Bridge.Inventory.CanCarryItem(source, item_name, count, metadata)
@@ -0,0 +1,109 @@
if Bridge.Inventory.Name ~= "ps" then
return
end
if Bridge.Framework.GetName() ~= "qb" then
error("[sky_phone] ps-inventory is only supported with QBCore.")
end
local inventory = exports["ps-inventory"]
local QBCore = exports["qb-core"]:GetCoreObject()
local function normalize(item, fallback_slot)
local normalized = Bridge.Inventory.NormalizeItem(item, "info", fallback_slot)
if not normalized or not normalized.name or normalized.amount < 1 then
return nil
end
return normalized
end
local function get_inventory(source)
local player = QBCore.Functions.GetPlayer(tonumber(source))
return player and player.PlayerData.items or {}
end
local function restore_slot(source, slot, metadata)
local restored = inventory:AddItem(source, slot.name, slot.amount, slot.slot, metadata)
if not restored then
Bridge.Debug(
"error",
"[sky_phone] ps-inventory could not restore item '%s' in slot %s after a metadata update failed.",
tostring(slot.name),
tostring(slot.slot)
)
end
return restored == true
end
function Bridge.Inventory.GetResourceName()
return "ps-inventory"
end
function Bridge.Inventory.GetSlot(source, slot_id)
local numeric_slot = tonumber(slot_id)
return numeric_slot and normalize(inventory:GetItemBySlot(source, numeric_slot), numeric_slot) or nil
end
function Bridge.Inventory.GetSlotsWithItem(source, item_name, metadata)
local matches = {}
for index, item in pairs(get_inventory(source)) do
local normalized = normalize(item, tonumber(index) or index)
if normalized and normalized.name == item_name
and Bridge.Inventory.MetadataMatches(normalized.metadata, metadata) then
normalized.slot = normalized.slot or tonumber(index) or index
matches[#matches + 1] = normalized
end
end
return matches
end
function Bridge.Inventory.SetSlotMetadata(source, slot_id, metadata)
local slot = Bridge.Inventory.GetSlot(source, slot_id)
if not slot or slot.amount < 1 then
return false
end
local previous_metadata = slot.metadata
local requested_metadata = type(metadata) == "table" and metadata or {}
if inventory:RemoveItem(source, slot.name, slot.amount, slot.slot) ~= true then
return false
end
if inventory:AddItem(source, slot.name, slot.amount, slot.slot, requested_metadata) ~= true then
restore_slot(source, slot, previous_metadata)
return false
end
local updated = Bridge.Inventory.GetSlot(source, slot.slot)
if updated and Bridge.Inventory.MetadataMatches(updated.metadata, requested_metadata) then
return true
end
inventory:RemoveItem(source, slot.name, slot.amount, slot.slot)
restore_slot(source, slot, previous_metadata)
return false
end
function Bridge.Inventory.CanCarryItem()
return true
end
function Bridge.Inventory.AddItem(source, item_name, count, slot, metadata)
return inventory:AddItem(source, item_name, count, slot, metadata or {}) == true
end
function Bridge.Inventory.RemoveItem(source, item_name, count, slot, metadata)
if metadata and not slot then
local slots = Bridge.Inventory.GetSlotsWithItem(source, item_name, metadata)
slot = slots[1] and slots[1].slot or nil
if not slot then
return 0
end
end
return inventory:RemoveItem(source, item_name, count, slot) == true and count or 0
end
function Bridge.Inventory.RegisterUsableItem(item_name, callback)
return Bridge.Framework.RegisterUsableItem(item_name, function(source, ...)
callback(source, normalize(Bridge.Inventory.ResolveUsableItem(...)))
end)
end
@@ -0,0 +1,75 @@
if Bridge.Inventory.Name ~= "tgiann" then
return
end
local inventory = exports["tgiann-inventory"]
local function normalize(item, fallback_slot)
local normalized = Bridge.Inventory.NormalizeItem(item, "info", fallback_slot)
if not normalized or not normalized.name or normalized.amount < 1 then
return nil
end
return normalized
end
function Bridge.Inventory.GetResourceName()
return "tgiann-inventory"
end
function Bridge.Inventory.GetSlot(source, slot_id)
local numeric_slot = tonumber(slot_id)
return numeric_slot and normalize(inventory:GetItemBySlot(source, numeric_slot), numeric_slot) or nil
end
function Bridge.Inventory.GetSlotsWithItem(source, item_name, metadata)
local matches = {}
for index, item in pairs(inventory:GetPlayerItems(source) or {}) do
local normalized = normalize(item, tonumber(index) or index)
if normalized and normalized.name == item_name
and Bridge.Inventory.MetadataMatches(normalized.metadata, metadata) then
normalized.slot = normalized.slot or tonumber(index) or index
matches[#matches + 1] = normalized
end
end
return matches
end
function Bridge.Inventory.SetSlotMetadata(source, slot_id, metadata)
local slot = Bridge.Inventory.GetSlot(source, slot_id)
if not slot then
return false
end
local requested_metadata = type(metadata) == "table" and metadata or {}
inventory:UpdateItemMetadata(source, slot.name, slot.slot, requested_metadata)
local updated = Bridge.Inventory.GetSlot(source, slot.slot)
return updated and Bridge.Inventory.MetadataMatches(updated.metadata, requested_metadata) or false
end
function Bridge.Inventory.CanCarryItem(source, item_name, count)
return inventory:CanCarryItem(source, item_name, count) == true
end
function Bridge.Inventory.AddItem(source, item_name, count, slot, metadata)
if not Bridge.Inventory.CanCarryItem(source, item_name, count, metadata) then
return false
end
return inventory:AddItem(source, item_name, count, slot, metadata or {}) == true
end
function Bridge.Inventory.RemoveItem(source, item_name, count, slot, metadata)
if metadata and not slot then
local slots = Bridge.Inventory.GetSlotsWithItem(source, item_name, metadata)
slot = slots[1] and slots[1].slot or nil
if not slot then
return 0
end
end
return inventory:RemoveItem(source, item_name, count, slot, metadata) == true and count or 0
end
function Bridge.Inventory.RegisterUsableItem(item_name, callback)
return Bridge.Framework.RegisterUsableItem(item_name, function(source, ...)
callback(source, normalize(Bridge.Inventory.ResolveUsableItem(...)))
end)
end
+318
View File
@@ -0,0 +1,318 @@
local function reset_bridge(inventory_name, resource_name, inventory_export, framework_name, extra_exports)
Config = {
Bridge = { Inventory = inventory_name },
Phone = { Unique = true },
Sim = { Enabled = true },
}
local usable_items = {}
Bridge = {
Debug = function()
end,
Framework = {
GetName = function()
return framework_name or "qb"
end,
GetIdentifier = function(source)
return ("player:%s"):format(source)
end,
RegisterUsableItem = function(item_name, callback)
usable_items[item_name] = callback
return true
end,
},
Inventory = {},
}
exports = extra_exports or {}
exports[resource_name] = inventory_export
GetResourceState = function(candidate)
return candidate == resource_name and "started" or "missing"
end
return usable_items
end
local function load_adapter(adapter_path)
dofile("sky_phone/source/bridge/server/inventory.lua")
dofile(adapter_path)
dofile("sky_phone/source/bridge/server/inventory_contract.lua")
end
local function assert_metadata(metadata, imei)
assert(type(metadata) == "table" and metadata.imei == imei)
end
Config = {
Bridge = { Inventory = "auto" },
Phone = { Unique = true },
Sim = { Enabled = true },
}
Bridge = {
Framework = {
GetName = function()
return "esx"
end,
},
Inventory = {},
}
GetResourceState = function(resource_name)
if resource_name == "ps-inventory" or resource_name == "tgiann-inventory" or resource_name == "ox_inventory" then
return "started"
end
return "missing"
end
dofile("sky_phone/source/bridge/server/inventory.lua")
assert(Bridge.Inventory.Name == "tgiann", "auto detection must follow the shared Sky inventory priority")
local ak47_items = {
[3] = { name = "phone", slot = 3, amount = 1, info = { owner = "kept" } },
}
local ak47 = {}
function ak47:GetSlot(identifier, slot)
assert(identifier == "player:21")
return ak47_items[slot]
end
function ak47:GetSlotsWithItem(identifier, item_name)
assert(identifier == "player:21" and item_name == "phone")
return ak47_items
end
function ak47:SetItemInfo(identifier, slot, metadata)
assert(identifier == "player:21")
ak47_items[slot].info = metadata
end
function ak47:CanAddItem()
return true
end
function ak47:AddItem()
return true
end
function ak47:RemoveItem()
return true
end
local ak47_usable = reset_bridge("ak47_inventory", "ak47_inventory", ak47)
load_adapter("sky_phone/source/bridge/server/inventory/ak47.lua")
assert(Bridge.Inventory.Name == "ak47", "resource-name aliases must resolve to adapter names")
assert(Bridge.Inventory.SetSlotMetadata(21, 3, { owner = "kept", imei = "111111111111111" }))
assert_metadata(Bridge.Inventory.GetSlot(21, 3).metadata, "111111111111111")
assert(Bridge.Inventory.RegisterUsableItem("phone", function(source, item)
assert(source == 21 and item.slot == 3)
end))
ak47_usable.phone(21, "phone", ak47_items[3])
local jaksam_items = {
[5] = { name = "phone", slot = 5, amount = 1, metadata = {} },
}
local jaksam_usable = {}
local jaksam = {}
function jaksam:getInventory()
return { items = jaksam_items }
end
function jaksam:setItemMetadataInSlot(_, slot, metadata)
jaksam_items[slot].metadata = metadata
return true
end
function jaksam:canCarryItem()
return true
end
function jaksam:addItem()
return true
end
function jaksam:removeItem()
return true
end
function jaksam:registerUsableItem(item_name, callback)
jaksam_usable[item_name] = callback
end
reset_bridge("jaksam", "jaksam_inventory", jaksam)
load_adapter("sky_phone/source/bridge/server/inventory/jaksam.lua")
assert(Bridge.Inventory.SetSlotMetadata(22, 5, { imei = "222222222222222" }))
assert_metadata(Bridge.Inventory.GetSlot(22, 5).metadata, "222222222222222")
assert(Bridge.Inventory.RegisterUsableItem("phone", function(source, item)
assert(source == 22 and item.slot == 5)
end))
jaksam_usable.phone(22, jaksam_items[5])
local one_items = {
[7] = { name = "phone", slot = 7, count = 1, metadata = {} },
}
local one = {}
function one:GetSlot(_, slot)
return one_items[slot]
end
function one:SearchInventory()
return one_items
end
function one:SetItemMetadata(_, slot, metadata)
one_items[slot].metadata = metadata
return true
end
function one:CanCarryItem()
return true
end
function one:AddItem()
return true
end
function one:RemoveItem()
return true
end
reset_bridge("one", "one_inventory", one)
load_adapter("sky_phone/source/bridge/server/inventory/one.lua")
assert(Bridge.Inventory.SetSlotMetadata(23, 7, { imei = "333333333333333" }))
assert_metadata(Bridge.Inventory.GetSlotsWithItem(23, "phone")[1].metadata, "333333333333333")
local origen_items = {
[9] = { name = "phone", slot = 9, amount = 1, metadata = {} },
}
local origen = {}
function origen:GetInventory()
return { inventory = origen_items }
end
function origen:setMetadata(_, slot, metadata)
origen_items[slot].metadata = metadata
end
function origen:canCarryItem()
return true
end
function origen:addItem()
return true
end
function origen:removeItem()
return true
end
reset_bridge("origen", "origen_inventory", origen)
load_adapter("sky_phone/source/bridge/server/inventory/origen.lua")
assert(Bridge.Inventory.SetSlotMetadata(24, 9, { imei = "444444444444444" }))
assert_metadata(Bridge.Inventory.GetSlot(24, 9).metadata, "444444444444444")
local tgiann_items = {
[11] = { name = "phone", slot = 11, amount = 1, info = {} },
}
local tgiann = {}
function tgiann:GetPlayerItems()
return tgiann_items
end
function tgiann:GetItemBySlot(_, slot)
return tgiann_items[slot]
end
function tgiann:UpdateItemMetadata(_, _, slot, metadata)
tgiann_items[slot].info = metadata
end
function tgiann:CanCarryItem()
return true
end
function tgiann:AddItem()
return true
end
function tgiann:RemoveItem()
return true
end
reset_bridge("tgiann", "tgiann-inventory", tgiann)
load_adapter("sky_phone/source/bridge/server/inventory/tgiann.lua")
assert(Bridge.Inventory.SetSlotMetadata(25, 11, { imei = "555555555555555" }))
assert_metadata(Bridge.Inventory.GetSlot(25, 11).metadata, "555555555555555")
local qb_items = {
[13] = { name = "phone", slot = 13, amount = 1, info = { owner = "kept" } },
}
local qb_player = { PlayerData = { items = qb_items } }
local qb_core = {
Functions = {
GetPlayer = function()
return qb_player
end,
},
}
local qb_export = {
GetCoreObject = function()
return qb_core
end,
}
local jpr_drop_next_metadata = true
local jpr = {}
function jpr:GetItemBySlot(_, slot)
return qb_items[slot]
end
function jpr:RemoveItem(_, _, _, slot)
qb_items[slot] = nil
return true
end
function jpr:AddItem(_, item_name, amount, slot, metadata)
qb_items[slot] = {
name = item_name,
slot = slot,
amount = amount,
info = jpr_drop_next_metadata and {} or metadata,
}
jpr_drop_next_metadata = false
return true
end
reset_bridge("jpr", "jpr-inventory", jpr, "qb", { ["qb-core"] = qb_export })
load_adapter("sky_phone/source/bridge/server/inventory/jpr.lua")
assert(not Bridge.Inventory.SetSlotMetadata(26, 13, { owner = "kept", imei = "666666666666666" }))
assert(qb_items[13] and qb_items[13].info.owner == "kept", "jpr metadata failure must restore the original item")
assert(Bridge.Inventory.SetSlotMetadata(26, 13, { owner = "kept", imei = "666666666666666" }))
assert_metadata(Bridge.Inventory.GetSlot(26, 13).metadata, "666666666666666")
qb_items[15] = { name = "phone", slot = 15, amount = 1, info = {} }
local ps = {}
function ps:GetItemBySlot(_, slot)
return qb_items[slot]
end
function ps:RemoveItem(_, _, _, slot)
qb_items[slot] = nil
return true
end
function ps:AddItem(_, item_name, amount, slot, metadata)
qb_items[slot] = { name = item_name, slot = slot, amount = amount, info = metadata }
return true
end
reset_bridge("ps", "ps-inventory", ps, "qb", { ["qb-core"] = qb_export })
load_adapter("sky_phone/source/bridge/server/inventory/ps.lua")
assert(Bridge.Inventory.SetSlotMetadata(27, 15, { imei = "777777777777777" }))
assert_metadata(Bridge.Inventory.GetSlot(27, 15).metadata, "777777777777777")
print("extended inventory bridge regression checks passed")