mirror of
https://github.com/sky-systems/sky_phone.git
synced 2026-08-29 02:01:40 +00:00
ENH - add standalone framework and inventory bridges
This commit is contained in:
@@ -0,0 +1,32 @@
|
||||
local registered_callbacks = {}
|
||||
|
||||
function Bridge.Callbacks.Register(name, callback)
|
||||
assert(type(name) == "string", "Callback name must be a string")
|
||||
assert(type(callback) == "function", "Callback handler must be a function")
|
||||
assert(not registered_callbacks[name], ("Callback '%s' is already registered"):format(name))
|
||||
registered_callbacks[name] = callback
|
||||
end
|
||||
|
||||
RegisterNetEvent("sky_phone:bridge:callback:request", function(name, request_id, data)
|
||||
local player_source = source
|
||||
if type(name) ~= "string" or type(request_id) ~= "number" or type(data) ~= "table" then
|
||||
Bridge.Debug("warn", "[sky_phone] Rejected malformed callback request from source %s.", tostring(player_source))
|
||||
return
|
||||
end
|
||||
|
||||
local callback = registered_callbacks[name]
|
||||
if not callback then
|
||||
Bridge.Debug("error", "[sky_phone] Server callback '%s' is not registered.", name)
|
||||
TriggerClientEvent("sky_phone:bridge:callback:response", player_source, request_id, nil)
|
||||
return
|
||||
end
|
||||
|
||||
local success, result = pcall(callback, player_source, data)
|
||||
if not success then
|
||||
Bridge.Debug("error", "[sky_phone] Server callback '%s' failed: %s", name, tostring(result))
|
||||
TriggerClientEvent("sky_phone:bridge:callback:response", player_source, request_id, nil)
|
||||
return
|
||||
end
|
||||
|
||||
TriggerClientEvent("sky_phone:bridge:callback:response", player_source, request_id, result)
|
||||
end)
|
||||
@@ -0,0 +1,16 @@
|
||||
function Bridge.Database.Query(query, parameters)
|
||||
return MySQL.query.await(query, parameters or {})
|
||||
end
|
||||
|
||||
function Bridge.Database.Transaction(statements)
|
||||
local queries = {}
|
||||
for index = 1, #statements do
|
||||
local statement = statements[index]
|
||||
queries[index] = {
|
||||
query = statement.query,
|
||||
values = statement.params or statement.values or {},
|
||||
}
|
||||
end
|
||||
|
||||
return MySQL.transaction.await(queries)
|
||||
end
|
||||
@@ -0,0 +1,21 @@
|
||||
local configured_framework = Config.Bridge.Framework
|
||||
|
||||
if configured_framework == "auto" then
|
||||
if GetResourceState("es_extended") == "started" then
|
||||
configured_framework = "esx"
|
||||
elseif GetResourceState("qbx_core") == "started" then
|
||||
configured_framework = "qbox"
|
||||
elseif GetResourceState("qb-core") == "started" then
|
||||
configured_framework = "qb"
|
||||
end
|
||||
end
|
||||
|
||||
if configured_framework ~= "esx" and configured_framework ~= "qbox" and configured_framework ~= "qb" then
|
||||
error(("[sky_phone] Unsupported or unavailable framework '%s'."):format(tostring(configured_framework)))
|
||||
end
|
||||
|
||||
Bridge.Framework.Name = configured_framework
|
||||
|
||||
function Bridge.Framework.GetName()
|
||||
return Bridge.Framework.Name
|
||||
end
|
||||
@@ -0,0 +1,42 @@
|
||||
if Bridge.Framework.Name ~= "esx" then
|
||||
return
|
||||
end
|
||||
|
||||
local ESX = exports["es_extended"]:getSharedObject()
|
||||
|
||||
local function get_player(source)
|
||||
return ESX.GetPlayerFromId(source)
|
||||
end
|
||||
|
||||
function Bridge.Framework.GetPlayers()
|
||||
local players = {}
|
||||
for _, player in pairs(ESX.GetExtendedPlayers()) do
|
||||
players[#players + 1] = player.source
|
||||
end
|
||||
return players
|
||||
end
|
||||
|
||||
function Bridge.Framework.GetIdentifier(source)
|
||||
local player = get_player(source)
|
||||
return player and player.identifier or nil
|
||||
end
|
||||
|
||||
function Bridge.Framework.GetFirstname(source)
|
||||
local player = get_player(source)
|
||||
return player and player.get("firstName") or nil
|
||||
end
|
||||
|
||||
function Bridge.Framework.GetLastname(source)
|
||||
local player = get_player(source)
|
||||
return player and player.get("lastName") or nil
|
||||
end
|
||||
|
||||
function Bridge.Framework.GetBirthdate(source)
|
||||
local player = get_player(source)
|
||||
return player and (player.get("dateofbirth") or player.get("dob")) or nil
|
||||
end
|
||||
|
||||
function Bridge.Framework.RegisterUsableItem(item_name, callback)
|
||||
ESX.RegisterUsableItem(item_name, callback)
|
||||
return true
|
||||
end
|
||||
@@ -0,0 +1,47 @@
|
||||
if Bridge.Framework.Name ~= "qb" then
|
||||
return
|
||||
end
|
||||
|
||||
local QBCore = exports["qb-core"]:GetCoreObject()
|
||||
|
||||
local function get_player(source)
|
||||
return QBCore.Functions.GetPlayer(tonumber(source))
|
||||
end
|
||||
|
||||
function Bridge.Framework.GetPlayers()
|
||||
local players = {}
|
||||
for player_source in pairs(QBCore.Functions.GetQBPlayers()) do
|
||||
players[#players + 1] = tonumber(player_source)
|
||||
end
|
||||
return players
|
||||
end
|
||||
|
||||
function Bridge.Framework.GetIdentifier(source)
|
||||
local player = get_player(source)
|
||||
return player and player.PlayerData and tostring(player.PlayerData.citizenid) or nil
|
||||
end
|
||||
|
||||
local function get_character_info(source)
|
||||
local player = get_player(source)
|
||||
return player and player.PlayerData and player.PlayerData.charinfo or nil
|
||||
end
|
||||
|
||||
function Bridge.Framework.GetFirstname(source)
|
||||
local character = get_character_info(source)
|
||||
return character and character.firstname or nil
|
||||
end
|
||||
|
||||
function Bridge.Framework.GetLastname(source)
|
||||
local character = get_character_info(source)
|
||||
return character and character.lastname or nil
|
||||
end
|
||||
|
||||
function Bridge.Framework.GetBirthdate(source)
|
||||
local character = get_character_info(source)
|
||||
return character and character.birthdate or nil
|
||||
end
|
||||
|
||||
function Bridge.Framework.RegisterUsableItem(item_name, callback)
|
||||
QBCore.Functions.CreateUseableItem(item_name, callback)
|
||||
return true
|
||||
end
|
||||
@@ -0,0 +1,45 @@
|
||||
if Bridge.Framework.Name ~= "qbox" then
|
||||
return
|
||||
end
|
||||
|
||||
local function get_player(source)
|
||||
return exports.qbx_core:GetPlayer(tonumber(source))
|
||||
end
|
||||
|
||||
function Bridge.Framework.GetPlayers()
|
||||
local players = {}
|
||||
for _, player_source in ipairs(GetPlayers()) do
|
||||
players[#players + 1] = tonumber(player_source)
|
||||
end
|
||||
return players
|
||||
end
|
||||
|
||||
function Bridge.Framework.GetIdentifier(source)
|
||||
local player = get_player(source)
|
||||
return player and player.PlayerData and tostring(player.PlayerData.citizenid) or nil
|
||||
end
|
||||
|
||||
local function get_character_info(source)
|
||||
local player = get_player(source)
|
||||
return player and player.PlayerData and player.PlayerData.charinfo or nil
|
||||
end
|
||||
|
||||
function Bridge.Framework.GetFirstname(source)
|
||||
local character = get_character_info(source)
|
||||
return character and character.firstname or nil
|
||||
end
|
||||
|
||||
function Bridge.Framework.GetLastname(source)
|
||||
local character = get_character_info(source)
|
||||
return character and character.lastname or nil
|
||||
end
|
||||
|
||||
function Bridge.Framework.GetBirthdate(source)
|
||||
local character = get_character_info(source)
|
||||
return character and character.birthdate or nil
|
||||
end
|
||||
|
||||
function Bridge.Framework.RegisterUsableItem(item_name, callback)
|
||||
exports.qbx_core:CreateUseableItem(item_name, callback)
|
||||
return true
|
||||
end
|
||||
@@ -0,0 +1,67 @@
|
||||
local configured_inventory = Config.Bridge.Inventory
|
||||
|
||||
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"
|
||||
end
|
||||
end
|
||||
|
||||
local supported_inventories = {
|
||||
ox = true,
|
||||
qb = true,
|
||||
lj = true,
|
||||
qs = true,
|
||||
codem = true,
|
||||
core = true,
|
||||
mf = 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)))
|
||||
end
|
||||
|
||||
Bridge.Inventory.Name = configured_inventory
|
||||
|
||||
function Bridge.Inventory.NormalizeItem(item, metadata_field)
|
||||
if not item then
|
||||
return nil
|
||||
end
|
||||
|
||||
local metadata = item[metadata_field or "metadata"]
|
||||
if type(metadata) ~= "table" then
|
||||
metadata = item.metadata or item.info
|
||||
end
|
||||
|
||||
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,
|
||||
metadata = type(metadata) == "table" and metadata or {},
|
||||
}
|
||||
end
|
||||
|
||||
function Bridge.Inventory.MetadataMatches(actual, expected)
|
||||
if type(expected) ~= "table" then
|
||||
return true
|
||||
end
|
||||
actual = type(actual) == "table" and actual or {}
|
||||
for key, value in pairs(expected) do
|
||||
if actual[key] ~= value then
|
||||
return false
|
||||
end
|
||||
end
|
||||
return true
|
||||
end
|
||||
@@ -0,0 +1,72 @@
|
||||
if Bridge.Inventory.Name ~= "codem" then
|
||||
return
|
||||
end
|
||||
|
||||
local inventory = exports["codem-inventory"]
|
||||
|
||||
local function normalize(item)
|
||||
return Bridge.Inventory.NormalizeItem(item, "info")
|
||||
end
|
||||
|
||||
local function get_inventory(source)
|
||||
return inventory:GetInventory(false, source) or {}
|
||||
end
|
||||
|
||||
function Bridge.Inventory.GetResourceName()
|
||||
return "codem-inventory"
|
||||
end
|
||||
|
||||
function Bridge.Inventory.GetSlot(source, slot_id)
|
||||
for _, item in pairs(get_inventory(source)) do
|
||||
if tostring(item.slot) == tostring(slot_id) then
|
||||
return normalize(item)
|
||||
end
|
||||
end
|
||||
return nil
|
||||
end
|
||||
|
||||
function Bridge.Inventory.GetSlotsWithItem(source, item_name, metadata)
|
||||
local matches = {}
|
||||
for _, item in pairs(get_inventory(source)) do
|
||||
local normalized = normalize(item)
|
||||
if 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
|
||||
inventory:SetItemMetadata(source, slot.slot, metadata or {})
|
||||
local updated = Bridge.Inventory.GetSlot(source, slot.slot)
|
||||
return updated and Bridge.Inventory.MetadataMatches(updated.metadata, metadata or {}) or 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, item)
|
||||
callback(source, normalize(item))
|
||||
end)
|
||||
end
|
||||
@@ -0,0 +1,80 @@
|
||||
if Bridge.Inventory.Name ~= "core" then
|
||||
return
|
||||
end
|
||||
|
||||
local inventory = exports.core_inventory
|
||||
|
||||
local function normalize(item)
|
||||
return Bridge.Inventory.NormalizeItem(item, "metadata")
|
||||
end
|
||||
|
||||
local function get_inventory(source)
|
||||
return inventory:getInventory(source) or {}
|
||||
end
|
||||
|
||||
function Bridge.Inventory.GetResourceName()
|
||||
return "core_inventory"
|
||||
end
|
||||
|
||||
function Bridge.Inventory.GetSlot(source, slot_id)
|
||||
for _, item in pairs(get_inventory(source)) do
|
||||
local item_slot = item.slot or item.id
|
||||
if tostring(item_slot) == tostring(slot_id) then
|
||||
return normalize(item)
|
||||
end
|
||||
end
|
||||
return nil
|
||||
end
|
||||
|
||||
function Bridge.Inventory.GetSlotsWithItem(source, item_name, metadata)
|
||||
local matches = {}
|
||||
for _, item in pairs(get_inventory(source)) do
|
||||
local normalized = normalize(item)
|
||||
if 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
|
||||
inventory:updateMetadata(source, slot.slot, metadata or {})
|
||||
local updated = Bridge.Inventory.GetSlot(source, slot.slot)
|
||||
return updated and Bridge.Inventory.MetadataMatches(updated.metadata, metadata or {}) or false
|
||||
end
|
||||
|
||||
function Bridge.Inventory.CanCarryItem()
|
||||
return true
|
||||
end
|
||||
|
||||
function Bridge.Inventory.AddItem(source, item_name, count, _, metadata)
|
||||
return inventory:addItem(source, item_name, count, 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 success
|
||||
if slot then
|
||||
success = inventory:removeItemExact(source, slot, count)
|
||||
else
|
||||
success = inventory:removeItem(source, item_name, count)
|
||||
end
|
||||
return success == true and count or 0
|
||||
end
|
||||
|
||||
function Bridge.Inventory.RegisterUsableItem(item_name, callback)
|
||||
return Bridge.Framework.RegisterUsableItem(item_name, function(source, item_name_or_item, item)
|
||||
local used_item = Bridge.Framework.GetName() == "esx" and item or item_name_or_item
|
||||
callback(source, normalize(used_item))
|
||||
end)
|
||||
end
|
||||
@@ -0,0 +1,94 @@
|
||||
if Bridge.Inventory.Name ~= "mf" then
|
||||
return
|
||||
end
|
||||
|
||||
if Bridge.Framework.GetName() ~= "esx" then
|
||||
error("[sky_phone] mf-inventory is only supported with ESX.")
|
||||
end
|
||||
|
||||
local ESX = exports["es_extended"]:getSharedObject()
|
||||
local inventory = exports["mf-inventory"]
|
||||
|
||||
local function get_identifier(source)
|
||||
local player = ESX.GetPlayerFromId(source)
|
||||
return player and player.identifier or nil
|
||||
end
|
||||
|
||||
local function get_inventory(source)
|
||||
local identifier = get_identifier(source)
|
||||
return identifier and inventory:getInventoryItems(identifier) or {}
|
||||
end
|
||||
|
||||
local function normalize(item)
|
||||
return Bridge.Inventory.NormalizeItem(item, "metadata")
|
||||
end
|
||||
|
||||
function Bridge.Inventory.GetResourceName()
|
||||
return "mf-inventory"
|
||||
end
|
||||
|
||||
function Bridge.Inventory.GetSlot(source, slot_id)
|
||||
for _, item in pairs(get_inventory(source)) do
|
||||
if tostring(item.slot) == tostring(slot_id) then
|
||||
return normalize(item)
|
||||
end
|
||||
end
|
||||
return nil
|
||||
end
|
||||
|
||||
function Bridge.Inventory.GetSlotsWithItem(source, item_name, metadata)
|
||||
local matches = {}
|
||||
for _, item in pairs(get_inventory(source)) do
|
||||
local normalized = normalize(item)
|
||||
if 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 = get_identifier(source)
|
||||
local slot = identifier and Bridge.Inventory.GetSlot(source, slot_id) or nil
|
||||
if not slot then
|
||||
return false
|
||||
end
|
||||
inventory:setItemProperty(identifier, slot.slot, "metadata", metadata or {})
|
||||
local updated = Bridge.Inventory.GetSlot(source, slot.slot)
|
||||
return updated and Bridge.Inventory.MetadataMatches(updated.metadata, metadata or {}) or false
|
||||
end
|
||||
|
||||
function Bridge.Inventory.CanCarryItem(source, item_name, count)
|
||||
local identifier = get_identifier(source)
|
||||
return identifier and inventory:canCarry(identifier, item_name, count) == true or false
|
||||
end
|
||||
|
||||
function Bridge.Inventory.AddItem(source, item_name, count, _, metadata)
|
||||
local identifier = get_identifier(source)
|
||||
if not identifier or not Bridge.Inventory.CanCarryItem(source, item_name, count, metadata) then
|
||||
return false
|
||||
end
|
||||
return inventory:addInventoryItem(identifier, item_name, count, source, 100, 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 identifier = get_identifier(source)
|
||||
if not identifier then
|
||||
return 0
|
||||
end
|
||||
return inventory:removeInventoryItem(identifier, item_name, count, source, slot) == true and count or 0
|
||||
end
|
||||
|
||||
function Bridge.Inventory.RegisterUsableItem(item_name, callback)
|
||||
ESX.RegisterUsableItem(item_name, function(source, _, _, item)
|
||||
callback(source, normalize(item))
|
||||
end)
|
||||
return true
|
||||
end
|
||||
@@ -0,0 +1,85 @@
|
||||
if Bridge.Inventory.Name ~= "ox" then
|
||||
return
|
||||
end
|
||||
|
||||
local inventory = exports.ox_inventory
|
||||
local usable_items = {}
|
||||
|
||||
AddEventHandler("ox_inventory:usedItem", function(source, item_name, slot_id)
|
||||
local callback = usable_items[item_name]
|
||||
if not callback then
|
||||
return
|
||||
end
|
||||
|
||||
local slot = Bridge.Inventory.GetSlot(source, slot_id)
|
||||
if not slot or slot.name ~= item_name then
|
||||
Bridge.Debug(
|
||||
"warn",
|
||||
"[sky_phone] ox_inventory reported an invalid used slot %s for item '%s' and source %s.",
|
||||
tostring(slot_id),
|
||||
tostring(item_name),
|
||||
tostring(source)
|
||||
)
|
||||
return
|
||||
end
|
||||
|
||||
callback(source, slot)
|
||||
end)
|
||||
|
||||
function Bridge.Inventory.GetResourceName()
|
||||
return "ox_inventory"
|
||||
end
|
||||
|
||||
function Bridge.Inventory.GetSlot(source, slot_id)
|
||||
return inventory:GetSlot(source, tonumber(slot_id))
|
||||
end
|
||||
|
||||
function Bridge.Inventory.GetSlotsWithItem(source, item_name, metadata)
|
||||
return inventory:GetSlotsWithItem(source, item_name, metadata, false) or {}
|
||||
end
|
||||
|
||||
function Bridge.Inventory.SetSlotMetadata(source, slot_id, metadata)
|
||||
local slot = Bridge.Inventory.GetSlot(source, slot_id)
|
||||
if not slot then
|
||||
return false
|
||||
end
|
||||
|
||||
inventory:SetMetadata(source, tonumber(slot_id), metadata or {})
|
||||
return true
|
||||
end
|
||||
|
||||
function Bridge.Inventory.CanCarryItem(source, item_name, count, metadata)
|
||||
return inventory:CanCarryItem(source, item_name, count, metadata)
|
||||
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, slot)
|
||||
return success == true
|
||||
end
|
||||
|
||||
function Bridge.Inventory.RemoveItem(source, item_name, count, slot, metadata)
|
||||
if slot then
|
||||
local success = inventory:RemoveItem(source, item_name, count, nil, slot)
|
||||
return success and count or 0
|
||||
end
|
||||
|
||||
if metadata 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, metadata, slot, false, false)
|
||||
return success and count or 0
|
||||
end
|
||||
|
||||
function Bridge.Inventory.RegisterUsableItem(item_name, callback)
|
||||
usable_items[item_name] = callback
|
||||
return true
|
||||
end
|
||||
@@ -0,0 +1,124 @@
|
||||
if Bridge.Inventory.Name ~= "qb" and Bridge.Inventory.Name ~= "lj" then
|
||||
return
|
||||
end
|
||||
|
||||
local resource_name = Bridge.Inventory.Name == "lj" and "lj-inventory" or "qb-inventory"
|
||||
local inventory = exports[resource_name]
|
||||
local QBCore = Bridge.Inventory.Name == "lj" and exports["qb-core"]:GetCoreObject() or nil
|
||||
|
||||
local function get_lj_player(source)
|
||||
return QBCore and QBCore.Functions.GetPlayer(tonumber(source)) or nil
|
||||
end
|
||||
|
||||
local function normalize(item)
|
||||
if not item then
|
||||
return nil
|
||||
end
|
||||
|
||||
return {
|
||||
name = item.name,
|
||||
slot = tonumber(item.slot),
|
||||
count = tonumber(item.amount) or 0,
|
||||
amount = tonumber(item.amount) or 0,
|
||||
metadata = type(item.info) == "table" and item.info or {},
|
||||
}
|
||||
end
|
||||
|
||||
local function metadata_matches(actual, expected)
|
||||
if type(expected) ~= "table" then
|
||||
return true
|
||||
end
|
||||
actual = type(actual) == "table" and actual or {}
|
||||
for key, value in pairs(expected) do
|
||||
if actual[key] ~= value then
|
||||
return false
|
||||
end
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
function Bridge.Inventory.GetResourceName()
|
||||
return resource_name
|
||||
end
|
||||
|
||||
function Bridge.Inventory.GetSlot(source, slot_id)
|
||||
if Bridge.Inventory.Name == "lj" then
|
||||
local player = get_lj_player(source)
|
||||
return normalize(player and player.PlayerData.items[tonumber(slot_id)] or nil)
|
||||
end
|
||||
return normalize(inventory:GetItemBySlot(source, tonumber(slot_id)))
|
||||
end
|
||||
|
||||
function Bridge.Inventory.GetSlotsWithItem(source, item_name, metadata)
|
||||
local matches = {}
|
||||
local items
|
||||
if Bridge.Inventory.Name == "lj" then
|
||||
local player = get_lj_player(source)
|
||||
items = player and player.PlayerData.items or {}
|
||||
else
|
||||
items = inventory:GetItemsByName(source, item_name) or {}
|
||||
end
|
||||
for _, item in pairs(items) do
|
||||
local normalized = normalize(item)
|
||||
if normalized.name == item_name and metadata_matches(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
|
||||
if Bridge.Inventory.Name == "lj" then
|
||||
local player = get_lj_player(source)
|
||||
if not player then
|
||||
return false
|
||||
end
|
||||
player.PlayerData.items[slot.slot].info = metadata or {}
|
||||
player.Functions.SetInventory(player.PlayerData.items, true)
|
||||
return true
|
||||
end
|
||||
return inventory:SetItemData(source, slot.name, "info", metadata or {}, slot.slot) == true
|
||||
end
|
||||
|
||||
function Bridge.Inventory.CanCarryItem(source, item_name, count)
|
||||
if Bridge.Inventory.Name == "lj" then
|
||||
return get_lj_player(source) ~= nil
|
||||
end
|
||||
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
|
||||
if Bridge.Inventory.Name == "lj" then
|
||||
local player = get_lj_player(source)
|
||||
return player and player.Functions.AddItem(item_name, count, slot, metadata or {}) == true or false
|
||||
end
|
||||
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
|
||||
if Bridge.Inventory.Name == "lj" then
|
||||
local player = get_lj_player(source)
|
||||
return player and player.Functions.RemoveItem(item_name, count, slot) == true and count or 0
|
||||
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, item)
|
||||
callback(source, normalize(item))
|
||||
end)
|
||||
end
|
||||
@@ -0,0 +1,81 @@
|
||||
if Bridge.Inventory.Name ~= "qs" then
|
||||
return
|
||||
end
|
||||
|
||||
local inventory = exports["qs-inventory"]
|
||||
|
||||
local function normalize(item)
|
||||
return Bridge.Inventory.NormalizeItem(item, "info")
|
||||
end
|
||||
|
||||
local function get_inventory(source)
|
||||
return inventory:GetInventory(source) or {}
|
||||
end
|
||||
|
||||
function Bridge.Inventory.GetResourceName()
|
||||
return "qs-inventory"
|
||||
end
|
||||
|
||||
function Bridge.Inventory.GetSlot(source, slot_id)
|
||||
for _, item in pairs(get_inventory(source)) do
|
||||
if tostring(item.slot) == tostring(slot_id) then
|
||||
return normalize(item)
|
||||
end
|
||||
end
|
||||
return nil
|
||||
end
|
||||
|
||||
function Bridge.Inventory.GetSlotsWithItem(source, item_name, metadata)
|
||||
local matches = {}
|
||||
for _, item in pairs(get_inventory(source)) do
|
||||
local normalized = normalize(item)
|
||||
if 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
|
||||
inventory:SetItemMetadata(source, slot.slot, metadata or {})
|
||||
local updated = Bridge.Inventory.GetSlot(source, slot.slot)
|
||||
return updated and Bridge.Inventory.MetadataMatches(updated.metadata, metadata or {}) 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)
|
||||
if Bridge.Framework.GetName() == "esx" then
|
||||
inventory:CreateUsableItem(item_name, function(source, item)
|
||||
callback(source, normalize(item))
|
||||
end)
|
||||
return true
|
||||
end
|
||||
return Bridge.Framework.RegisterUsableItem(item_name, function(source, item)
|
||||
callback(source, normalize(item))
|
||||
end)
|
||||
end
|
||||
@@ -0,0 +1,192 @@
|
||||
local completed_migrations = {}
|
||||
local migration_callbacks = {}
|
||||
|
||||
local function build_column_definition(column)
|
||||
local data_type, attributes = column.type:match("^(%S+)%s*(.*)$")
|
||||
local definition = data_type
|
||||
|
||||
if column.characterSet then
|
||||
definition = definition .. " CHARACTER SET " .. column.characterSet
|
||||
end
|
||||
if column.collation then
|
||||
definition = definition .. " COLLATE " .. column.collation
|
||||
end
|
||||
if attributes ~= "" then
|
||||
definition = definition .. " " .. attributes
|
||||
end
|
||||
|
||||
return definition
|
||||
end
|
||||
|
||||
local function build_create_query(table_definition)
|
||||
local definitions = {}
|
||||
for index = 1, #table_definition.columns do
|
||||
local column = table_definition.columns[index]
|
||||
definitions[#definitions + 1] = ("`%s` %s"):format(column.name, build_column_definition(column))
|
||||
end
|
||||
|
||||
if table_definition.primaryKey then
|
||||
definitions[#definitions + 1] = ("PRIMARY KEY (`%s`)"):format(table_definition.primaryKey)
|
||||
end
|
||||
for _, unique_key in ipairs(table_definition.uniqueKeys or {}) do
|
||||
definitions[#definitions + 1] = ("UNIQUE KEY `%s` %s"):format(unique_key.name, unique_key.columns)
|
||||
end
|
||||
for _, index in ipairs(table_definition.indexes or {}) do
|
||||
definitions[#definitions + 1] = ("INDEX `%s` %s"):format(index.name, index.columns)
|
||||
end
|
||||
for _, foreign_key in ipairs(table_definition.foreignKeys or {}) do
|
||||
definitions[#definitions + 1] = ("FOREIGN KEY (`%s`) REFERENCES %s"):format(
|
||||
foreign_key.column,
|
||||
foreign_key.references
|
||||
)
|
||||
end
|
||||
|
||||
return ("CREATE TABLE IF NOT EXISTS `%s` (\n%s\n) %s"):format(
|
||||
table_definition.name,
|
||||
table.concat(definitions, ",\n"),
|
||||
table_definition.tableOptions or ""
|
||||
)
|
||||
end
|
||||
|
||||
local function query_or_error(query, parameters, context)
|
||||
local success, result = pcall(Bridge.Database.Query, query, parameters)
|
||||
if not success then
|
||||
error(("[sky_phone] Database migration failed while %s: %s"):format(context, tostring(result)))
|
||||
end
|
||||
return result
|
||||
end
|
||||
|
||||
function Bridge.Database.EnsureIndex(table_name, index_name, columns, options)
|
||||
local table_count = Bridge.Database.Query([[
|
||||
SELECT COUNT(*) AS `count`
|
||||
FROM INFORMATION_SCHEMA.TABLES
|
||||
WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = ?
|
||||
]], { table_name })
|
||||
if not table_count[1] or tonumber(table_count[1].count) == 0 then
|
||||
return false
|
||||
end
|
||||
|
||||
local indexes = Bridge.Database.Query(("SHOW INDEX FROM `%s`"):format(table_name), {})
|
||||
for _, index in ipairs(indexes) do
|
||||
if index.Key_name == index_name then
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
local index_type = options and options.unique and "UNIQUE KEY" or "INDEX"
|
||||
query_or_error(
|
||||
("ALTER TABLE `%s` ADD %s `%s` %s"):format(table_name, index_type, index_name, columns),
|
||||
{},
|
||||
("adding index '%s'"):format(index_name)
|
||||
)
|
||||
Bridge.Debug("info", "[sky_phone] Added database index '%s' to '%s'.", index_name, table_name)
|
||||
return true
|
||||
end
|
||||
|
||||
function Bridge.Database.Migrate(migration_name, schema)
|
||||
local table_names = {}
|
||||
local placeholders = {}
|
||||
for index = 1, #schema do
|
||||
table_names[index] = schema[index].name
|
||||
placeholders[index] = "?"
|
||||
end
|
||||
|
||||
local existing_tables = {}
|
||||
local existing_columns = {}
|
||||
if #table_names > 0 then
|
||||
local placeholder_list = table.concat(placeholders, ", ")
|
||||
local tables = Bridge.Database.Query(([[
|
||||
SELECT TABLE_NAME
|
||||
FROM INFORMATION_SCHEMA.TABLES
|
||||
WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME IN (%s)
|
||||
]]):format(placeholder_list), table_names)
|
||||
for _, row in ipairs(tables) do
|
||||
existing_tables[(row.TABLE_NAME or row.table_name):lower()] = true
|
||||
end
|
||||
|
||||
local columns = Bridge.Database.Query(([[
|
||||
SELECT TABLE_NAME, COLUMN_NAME, CHARACTER_SET_NAME, COLLATION_NAME
|
||||
FROM INFORMATION_SCHEMA.COLUMNS
|
||||
WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME IN (%s)
|
||||
]]):format(placeholder_list), table_names)
|
||||
for _, row in ipairs(columns) do
|
||||
local table_name = (row.TABLE_NAME or row.table_name):lower()
|
||||
local column_name = (row.COLUMN_NAME or row.column_name):lower()
|
||||
existing_columns[table_name] = existing_columns[table_name] or {}
|
||||
existing_columns[table_name][column_name] = {
|
||||
character_set = row.CHARACTER_SET_NAME or row.character_set_name,
|
||||
collation = row.COLLATION_NAME or row.collation_name,
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
for _, table_definition in ipairs(schema) do
|
||||
local table_name = table_definition.name:lower()
|
||||
if not existing_tables[table_name] then
|
||||
query_or_error(build_create_query(table_definition), {}, ("creating table '%s'"):format(table_definition.name))
|
||||
Bridge.Debug("info", "[sky_phone] Created database table '%s'.", table_definition.name)
|
||||
else
|
||||
local columns = existing_columns[table_name] or {}
|
||||
for _, column in ipairs(table_definition.columns) do
|
||||
local current = columns[column.name:lower()]
|
||||
local definition = build_column_definition(column)
|
||||
if not current then
|
||||
query_or_error(
|
||||
("ALTER TABLE `%s` ADD COLUMN `%s` %s"):format(table_definition.name, column.name, definition),
|
||||
{},
|
||||
("adding column '%s.%s'"):format(table_definition.name, column.name)
|
||||
)
|
||||
elseif (column.characterSet and current.character_set ~= column.characterSet)
|
||||
or (column.collation and current.collation ~= column.collation) then
|
||||
query_or_error(
|
||||
("ALTER TABLE `%s` MODIFY COLUMN `%s` %s"):format(table_definition.name, column.name, definition),
|
||||
{},
|
||||
("updating column '%s.%s'"):format(table_definition.name, column.name)
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
for _, index in ipairs(table_definition.indexes or {}) do
|
||||
Bridge.Database.EnsureIndex(table_definition.name, index.name, index.columns)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
function Bridge.Database.CompleteMigration(migration_name)
|
||||
if completed_migrations[migration_name] then
|
||||
error(("[sky_phone] Database migration '%s' was completed more than once."):format(tostring(migration_name)))
|
||||
end
|
||||
|
||||
completed_migrations[migration_name] = true
|
||||
local callbacks = migration_callbacks[migration_name] or {}
|
||||
migration_callbacks[migration_name] = nil
|
||||
|
||||
Bridge.Debug("info", "[sky_phone] Database migration '%s' completed.", migration_name)
|
||||
for index = 1, #callbacks do
|
||||
callbacks[index]()
|
||||
end
|
||||
end
|
||||
|
||||
function Bridge.Database.AfterMigration(migration_name, callback)
|
||||
if type(callback) ~= "function" then
|
||||
error("[sky_phone] Database migration callback must be a function.")
|
||||
end
|
||||
if completed_migrations[migration_name] then
|
||||
callback()
|
||||
return
|
||||
end
|
||||
|
||||
migration_callbacks[migration_name] = migration_callbacks[migration_name] or {}
|
||||
migration_callbacks[migration_name][#migration_callbacks[migration_name] + 1] = callback
|
||||
end
|
||||
|
||||
function Bridge.Database.AwaitMigration(migration_name)
|
||||
if completed_migrations[migration_name] then
|
||||
return true
|
||||
end
|
||||
|
||||
Bridge.Debug("error", "[sky_phone] Database migration '%s' has not completed.", tostring(migration_name))
|
||||
return false
|
||||
end
|
||||
Reference in New Issue
Block a user