mirror of
https://github.com/sky-systems/sky_phone.git
synced 2026-08-28 23:01:37 +00:00
ADD - support extended inventory metadata bridges (#23)
* ADD - support extended inventory metadata bridges * FIX - align inventory contract tests
This commit is contained in:
@@ -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:
|
||||
|
||||
@@ -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"')
|
||||
})
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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")
|
||||
Reference in New Issue
Block a user