FIX - harden inventory contracts and resolve phone interaction issues (#20)

* FIX - repair inventory metadata and ESX contracts

Use the stable core_inventory metadata setter and validate the complete inventory adapter contract after provider bridges load. Preserve the real ESX configuration error instead of cascading into a missing RegisterUsableItem failure.

* FIX - clarify phone opening without SIM

* FIX - harden inventory and device contracts

Validate SIM number configuration before item handling, preserve QB metadata mutation contracts, and propagate phone-open failures to callers. Extend regression coverage for inventory bridges and device bootstrap errors.

* FIX - resolve reported phone interaction issues

Stop game-input passthrough while NUI text fields are focused and add an independent hold-to-look option. Propagate SIM number formatting to the frontend, migrate configured currency symbols to utf8mb4, and clarify SkyRide, VaultX, and DarkChat behavior with focused contract coverage.

* FIX - detect missing packaged phone UI

Validate the generated NUI entrypoint, its referenced assets, and required static media when the resource starts. Print an actionable server-console warning for incomplete source archives, package phone sounds through the manifest, and cover the detection contract with Lua regressions.
This commit is contained in:
DerEchteAlec
2026-08-21 17:19:27 +02:00
committed by GitHub
parent 698420584d
commit 97db163cd0
34 changed files with 912 additions and 79 deletions
+104 -1
View File
@@ -1,5 +1,6 @@
local registered_callbacks = {}
local migration_callbacks = {}
local event_handlers = {}
Bridge = {
Callbacks = {
@@ -75,8 +76,10 @@ json = {
end,
}
function AddEventHandler(_, callback)
function AddEventHandler(name, callback)
assert(type(callback) == "function")
event_handlers[name] = event_handlers[name] or {}
event_handlers[name][#event_handlers[name] + 1] = callback
end
function TriggerClientEvent()
@@ -177,6 +180,106 @@ for _, callback_name in ipairs({
assert(response.success == false and response.error == "device_not_open", "callback not bound to core: " .. callback_name)
end
local phone_item = {
name = "phone",
slot = 4,
amount = 1,
metadata = { imei = "123456789012345" },
}
local opened_event
local device_error
local hide_phone_during_prepare = false
Bridge.Framework.GetIdentifier = function(source)
assert(source == 1)
return "license:test-player"
end
Bridge.Framework.GetFirstname = function()
return "Test"
end
Bridge.Framework.GetLastname = function()
return "Player"
end
Bridge.Inventory.GetSlot = function(source, slot)
assert(source == 1 and slot == phone_item.slot)
return phone_item
end
Bridge.Inventory.GetSlotsWithItem = function(source, item_name)
assert(source == 1 and item_name == Config.Phone.Item)
if hide_phone_during_prepare == true then
return {}
end
return { phone_item }
end
Bridge.Inventory.SetSlotMetadata = function()
error("existing phone metadata must not be rewritten")
end
Bridge.Database.Query = function(query)
if query:find("FROM `sky_phone_devices` d", 1, true) then
return {
{
imei = phone_item.metadata.imei,
device_name = Config.Phone.DeviceName,
account_id = nil,
sim_id = nil,
},
}
end
return {}
end
SkyPhoneImei.IsValid = function(imei)
return imei == phone_item.metadata.imei
end
SkyPhoneSim = {
PrepareDevice = function(source, slot, imei)
assert(source == 1 and slot == phone_item and imei == phone_item.metadata.imei)
if hide_phone_during_prepare == "next" then
hide_phone_during_prepare = true
end
return true
end,
}
SkyPhoneNotes = {
List = function()
return {}
end,
}
SkyPhoneMemos = {
List = function()
return {}
end,
}
SkyPhoneCompanies = {
ClearCallAvailability = function()
end,
}
TriggerClientEvent = function(event_name, source, payload)
if event_name == "sky_phone:device:open" then
opened_event = { source = source, payload = payload }
elseif event_name == "sky_phone:device:error" then
device_error = { source = source, error = payload }
end
end
for _, callback in ipairs(event_handlers.onServerResourceStart or {}) do
callback("sky_phone")
end
local no_sim_open = registered_callbacks["sky_phone:device:open-request"](1, {})
assert(no_sim_open.success == true, "a phone item without a SIM must still open")
assert(opened_event and opened_event.source == 1, "no-SIM open must reach the client")
assert(opened_event.payload.device.imei == phone_item.metadata.imei)
assert(opened_event.payload.device.sim == nil, "no-SIM bootstrap must keep device.sim nullable")
opened_event = nil
device_error = nil
hide_phone_during_prepare = "next"
local lost_phone_open = registered_callbacks["sky_phone:device:open-request"](1, {})
assert(lost_phone_open.success == false, "bootstrap ownership loss must fail the open request")
assert(lost_phone_open.error == "device_not_owned", "bootstrap ownership loss must return its error code")
assert(opened_event == nil, "bootstrap ownership loss must not open the NUI")
assert(device_error and device_error.error == "device_not_owned", "bootstrap ownership loss must notify the client")
local manifest_file = assert(io.open("sky_phone/fxmanifest.lua", "rb"))
local manifest = manifest_file:read("*a")
manifest_file:close()