mirror of
https://github.com/sky-systems/sky_phone.git
synced 2026-08-28 17:01:18 +00:00
FIX - support HEX inventory compatibility
Auto-detect hex_4_inventory and add a count-based ESX adapter for non-unique, virtual-SIM phone configurations. Expose the LB Phone IsOpen alias required by HEX and document/test the supported configuration boundaries.
This commit is contained in:
@@ -214,7 +214,9 @@ 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`, and `smx`. 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: `ox`, `qb`, `lj`, `qs`, `codem`, `core`, `mf`, `smx`, `hex`, and `esx`. Resource startup fails visibly if the selected adapter cannot complete that registration.
|
||||
|
||||
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.
|
||||
|
||||
### QBCore-style item tables
|
||||
|
||||
|
||||
@@ -14,6 +14,8 @@ const inventoryAdapters = [
|
||||
['core', 'source/bridge/server/inventory/core.lua'],
|
||||
['mf', 'source/bridge/server/inventory/mf.lua'],
|
||||
['smx', 'source/bridge/server/inventory/smx.lua'],
|
||||
['hex', 'source/bridge/server/inventory/esx.lua'],
|
||||
['esx', 'source/bridge/server/inventory/esx.lua'],
|
||||
] as const
|
||||
|
||||
describe('phone inventory contracts', () => {
|
||||
@@ -35,6 +37,28 @@ 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', () => {
|
||||
const inventoryBridge = readResourceFile(
|
||||
'source/bridge/server/inventory.lua',
|
||||
)
|
||||
|
||||
expect(inventoryBridge).toContain(
|
||||
'GetResourceState("hex_4_inventory") == "started"',
|
||||
)
|
||||
expect(inventoryBridge).toContain('configured_inventory = "hex"')
|
||||
expect(inventoryBridge).toContain('Config.Phone.Unique ~= false')
|
||||
expect(inventoryBridge).toContain('Config.Sim.Enabled ~= false')
|
||||
})
|
||||
|
||||
it('provides the LB IsOpen export alias from the authoritative client state', () => {
|
||||
const phoneClient = readResourceFile('source/client/main.lua')
|
||||
|
||||
expect(phoneClient).toContain(
|
||||
'SkyPhoneCompatibility.RegisterExportAlias("lb-phone", "IsOpen"',
|
||||
)
|
||||
expect(phoneClient).toContain('return is_open')
|
||||
})
|
||||
|
||||
it('opens from a configurable F1 mapping without client-provided device identity', () => {
|
||||
const config = readResourceFile('config/config.lua')
|
||||
const phoneClient = readResourceFile('source/client/main.lua')
|
||||
@@ -56,7 +80,9 @@ describe('phone inventory contracts', () => {
|
||||
const phoneServer = readResourceFile('source/server/phone.lua')
|
||||
|
||||
expect(phoneServer).toContain('local preferred_device_imeis = {}')
|
||||
expect(phoneServer).toContain('local preferred_imei = preferred_device_imeis[source]')
|
||||
expect(phoneServer).toContain(
|
||||
'local preferred_imei = preferred_device_imeis[source]',
|
||||
)
|
||||
expect(phoneServer).toContain('preferred_device_imeis[source] = imei')
|
||||
})
|
||||
})
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
|
||||
Config.Bridge = {
|
||||
Framework = "auto", -- auto, esx, qbox, qb
|
||||
Inventory = "auto", -- auto, ox, qb, lj, qs, codem, core, mf, smx
|
||||
Inventory = "auto", -- auto, ox, qb, lj, qs, codem, core, mf, smx, hex, esx
|
||||
Locale = "en",
|
||||
CallbackTimeout = 15000,
|
||||
Debug = false, -- true: show debug/info output; warnings and errors are always shown
|
||||
@@ -26,7 +26,7 @@ Config.Command = "phone"
|
||||
|
||||
Config.Phone = {
|
||||
Item = "phone",
|
||||
Unique = true, -- true: data follows each phone item; false: one persistent phone per character
|
||||
Unique = true, -- true: data follows each phone item; false: one persistent phone per character; hex/esx require false
|
||||
Keybind = "F1", -- false disables the configurable phone key mapping
|
||||
AllowMovement = true, -- true: game input stays active while the mobile phone is open
|
||||
DevelopmentCommand = true,
|
||||
@@ -64,7 +64,7 @@ Config.Security = {
|
||||
}
|
||||
|
||||
Config.Sim = {
|
||||
Enabled = true, -- false: devices without a SIM receive a persistent random number automatically
|
||||
Enabled = true, -- false: devices receive a persistent random number automatically; hex/esx require false
|
||||
RegisteredItem = "sky_phone_sim_registered",
|
||||
AnonymousItem = "sky_phone_sim_anonymous",
|
||||
NumberLength = 10,
|
||||
|
||||
@@ -17,6 +17,10 @@ if configured_inventory == "auto" 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
|
||||
configured_inventory = "esx"
|
||||
end
|
||||
end
|
||||
|
||||
@@ -29,10 +33,24 @@ local supported_inventories = {
|
||||
core = true,
|
||||
mf = true,
|
||||
smx = true,
|
||||
hex = true,
|
||||
esx = true,
|
||||
}
|
||||
|
||||
if not supported_inventories[configured_inventory] then
|
||||
error(("[sky_phone] Unsupported or unavailable inventory '%s'. A metadata-capable inventory adapter is required."):format(tostring(configured_inventory)))
|
||||
error(("[sky_phone] Unsupported or unavailable inventory '%s'. Configure a supported inventory adapter."):format(tostring(configured_inventory)))
|
||||
end
|
||||
|
||||
if configured_inventory == "hex" or configured_inventory == "esx" then
|
||||
if Bridge.Framework.GetName() ~= "esx" then
|
||||
error(("[sky_phone] Inventory '%s' is only supported with ESX."):format(configured_inventory))
|
||||
end
|
||||
if Config.Phone.Unique ~= false then
|
||||
error(("[sky_phone] Inventory '%s' cannot store unique phone metadata. Set Config.Phone.Unique = false or configure a metadata-capable inventory."):format(configured_inventory))
|
||||
end
|
||||
if Config.Sim.Enabled ~= false then
|
||||
error(("[sky_phone] Inventory '%s' cannot store physical SIM metadata. Set Config.Sim.Enabled = false or configure a metadata-capable inventory."):format(configured_inventory))
|
||||
end
|
||||
end
|
||||
|
||||
Bridge.Inventory.Name = configured_inventory
|
||||
|
||||
@@ -0,0 +1,112 @@
|
||||
if Bridge.Inventory.Name ~= "esx" and Bridge.Inventory.Name ~= "hex" then
|
||||
return
|
||||
end
|
||||
|
||||
if GetResourceState("es_extended") ~= "started" then
|
||||
error("[sky_phone] The ESX inventory adapter requires es_extended to be started.")
|
||||
end
|
||||
if Bridge.Inventory.Name == "hex" and GetResourceState("hex_4_inventory") ~= "started" then
|
||||
error("[sky_phone] hex_4_inventory is configured, but the resource is not started.")
|
||||
end
|
||||
|
||||
local ESX = exports["es_extended"]:getSharedObject()
|
||||
|
||||
local function get_player(source)
|
||||
return ESX.GetPlayerFromId(source)
|
||||
end
|
||||
|
||||
local function get_item(source, item_name)
|
||||
local player = get_player(source)
|
||||
local item = player and player.getInventoryItem(item_name) or nil
|
||||
local count = item and tonumber(item.count) or 0
|
||||
if count < 1 then
|
||||
return nil
|
||||
end
|
||||
|
||||
return {
|
||||
name = item_name,
|
||||
slot = item_name,
|
||||
count = count,
|
||||
amount = count,
|
||||
metadata = {},
|
||||
}
|
||||
end
|
||||
|
||||
local function has_metadata(metadata)
|
||||
return type(metadata) == "table" and next(metadata) ~= nil
|
||||
end
|
||||
|
||||
function Bridge.Inventory.GetResourceName()
|
||||
return Bridge.Inventory.Name == "hex" and "hex_4_inventory" or "es_extended"
|
||||
end
|
||||
|
||||
function Bridge.Inventory.GetSlot(source, slot_id)
|
||||
return get_item(source, tostring(slot_id))
|
||||
end
|
||||
|
||||
function Bridge.Inventory.GetSlotsWithItem(source, item_name, metadata)
|
||||
local item = get_item(source, item_name)
|
||||
if not item or has_metadata(metadata) then
|
||||
return {}
|
||||
end
|
||||
return { item }
|
||||
end
|
||||
|
||||
function Bridge.Inventory.SetSlotMetadata()
|
||||
return false
|
||||
end
|
||||
|
||||
function Bridge.Inventory.CanCarryItem(source, item_name, count, metadata)
|
||||
if has_metadata(metadata) then
|
||||
return false
|
||||
end
|
||||
local player = get_player(source)
|
||||
return player and player.canCarryItem(item_name, count) or false
|
||||
end
|
||||
|
||||
function Bridge.Inventory.AddItem(source, item_name, count, _, metadata)
|
||||
if not Bridge.Inventory.CanCarryItem(source, item_name, count, metadata) then
|
||||
return false
|
||||
end
|
||||
|
||||
local player = get_player(source)
|
||||
local before = player.getInventoryItem(item_name)
|
||||
local before_count = before and tonumber(before.count) or 0
|
||||
player.addInventoryItem(item_name, count)
|
||||
local after = player.getInventoryItem(item_name)
|
||||
return (after and tonumber(after.count) or 0) - before_count == count
|
||||
end
|
||||
|
||||
function Bridge.Inventory.RemoveItem(source, item_name, count, _, metadata)
|
||||
if has_metadata(metadata) then
|
||||
return 0
|
||||
end
|
||||
|
||||
local player = get_player(source)
|
||||
local before = player and player.getInventoryItem(item_name) or nil
|
||||
local before_count = before and tonumber(before.count) or 0
|
||||
if not player or before_count < count then
|
||||
return 0
|
||||
end
|
||||
|
||||
player.removeInventoryItem(item_name, count)
|
||||
local after = player.getInventoryItem(item_name)
|
||||
return before_count - (after and tonumber(after.count) or 0)
|
||||
end
|
||||
|
||||
function Bridge.Inventory.RegisterUsableItem(item_name, callback)
|
||||
ESX.RegisterUsableItem(item_name, function(source)
|
||||
local item = get_item(source, item_name)
|
||||
if not item then
|
||||
Bridge.Debug(
|
||||
"warn",
|
||||
"[sky_phone] ESX usable callback could not resolve item '%s' for source %s.",
|
||||
item_name,
|
||||
tostring(source)
|
||||
)
|
||||
return
|
||||
end
|
||||
callback(source, item)
|
||||
end)
|
||||
return true
|
||||
end
|
||||
@@ -17,6 +17,10 @@ local phone_block_look = false
|
||||
local phone_game_input = false
|
||||
local phone_cursor_disabled = false
|
||||
|
||||
SkyPhoneCompatibility.RegisterExportAlias("lb-phone", "IsOpen", function()
|
||||
return is_open
|
||||
end)
|
||||
|
||||
Bridge.Debug("debug", "[sky_phone] Client script initialized.", { always = true })
|
||||
|
||||
local server_callbacks = {
|
||||
|
||||
Reference in New Issue
Block a user