Skip to content

Commit

Permalink
feat: attempt to make a qb-target compatibility layer
Browse files Browse the repository at this point in the history
bridge: framework related functions
providers: where we replace exports
  • Loading branch information
swkeep committed Aug 29, 2024
1 parent 2bc1738 commit 1ef5bc0
Show file tree
Hide file tree
Showing 7 changed files with 861 additions and 131 deletions.
12 changes: 8 additions & 4 deletions interactionMenu/fxmanifest.lua
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,10 @@ client_script {
'@PolyZone/BoxZone.lua',
'@PolyZone/CircleZone.lua',
'@PolyZone/ComboZone.lua',

-- bridges
'lua/bridge/main.lua',

-- core
'lua/client/util.lua',
'lua/client/3dDuiMaker.lua',
Expand All @@ -39,9 +43,8 @@ client_script {
'lua/client/garbageCollector.lua',

-- providers
-- 'lua/providers/qb-target.lua',
-- 'lua/providers/qb-target_test.lua',
-- 'lua/providers/qb-target_debug.lua',
'lua/providers/qb-target.lua',
'lua/providers/qb-target_test.lua',

-- examples / tests
'lua/examples/*.lua',
Expand All @@ -53,8 +56,9 @@ server_script {

files {
'lua/client/icons/*.*',
'lua/bridge/qb.lua',
}

-- provide 'qb-target'
provide 'qb-target'

lua54 'yes'
52 changes: 52 additions & 0 deletions interactionMenu/lua/bridge/main.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
local resource_name = GetCurrentResourceName()

local LoadResourceFile = LoadResourceFile
local context = IsDuplicityVersion() and 'server' or 'client'

string.split = function(str, pattern)
pattern = pattern or "[^%s]+"
if pattern:len() == 0 then
pattern = "[^%s]+"
end
local parts = { __index = table.insert }
setmetatable(parts, parts)
str:gsub(pattern, parts)
setmetatable(parts, nil)
parts.__index = nil
return parts
end

local function loadModule(module_name, dir)
local chunk = LoadResourceFile(resource_name, ('%s.lua'):format(dir))

if chunk then
local fn, err = load(chunk)
if not fn or err then
return error(('\n^1Error (%s): %s^0'):format(dir, err), 3)
end

local result = fn()

return result[context]()
end
end

---comment
---@param module string
local function link(module)
local sub = module:split('[^.]+')
local dir = ('lua/bridge/%s'):format(sub[1])
return loadModule(sub[1], dir)
end

Bridge = {
active = false
}

if GetResourceState('qb-core') == 'started' then
local bridge_link = link('qb')
Bridge.hasItem = bridge_link.hasItem
Bridge.getJob = bridge_link.getJob
Bridge.getGang = bridge_link.getGang
Bridge.active = true
end
90 changes: 90 additions & 0 deletions interactionMenu/lua/bridge/qb.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
local function updatePlayerItems(playerData, qb_items)
for _, itemData in pairs(playerData.items or {}) do
if qb_items[itemData.name] then
qb_items[itemData.name] = qb_items[itemData.name] + itemData.amount
else
qb_items[itemData.name] = itemData.amount
end
end
end

local function reset(t) table.wipe(t) end

return {
client = function()
local QBCore = exports['qb-core']:GetCoreObject()
local playerData = QBCore.Functions.GetPlayerData() or {}
local qb_items = {}

AddEventHandler('QBCore:Client:OnPlayerLoaded', function()
playerData = QBCore.Functions.GetPlayerData()
reset(qb_items)
updatePlayerItems(playerData, qb_items)
end)

RegisterNetEvent('QBCore:Client:OnPlayerUnload', function()
playerData = {}
reset(qb_items)
end)

RegisterNetEvent('QBCore:Player:SetPlayerData', function(val)
playerData = QBCore.Functions.GetPlayerData()
reset(qb_items)
updatePlayerItems(playerData, qb_items)
end)

RegisterNetEvent('QBCore:Client:OnJobUpdate', function(JobInfo)
playerData = QBCore.Functions.GetPlayerData()
reset(qb_items)
updatePlayerItems(playerData, qb_items)
end)

RegisterNetEvent('QBCore:Client:OnGangUpdate', function(GangInfo)
playerData = QBCore.Functions.GetPlayerData()
reset(qb_items)
updatePlayerItems(playerData, qb_items)
end)

local function init()
playerData = QBCore.Functions.GetPlayerData()
reset(qb_items)
updatePlayerItems(playerData, qb_items)
end

init()

return {
['getJob'] = function()
local job = playerData['job']
return job.name, job.grade.level
end,
['getGang'] = function()
local gang = playerData['gang']
return gang.name, gang.grade.level
end,
['hasItem'] = function(itemName, requiredAmount)
if not requiredAmount then requiredAmount = 1 end
local hasItem = qb_items[itemName] ~= nil
local hasEnough = hasItem and qb_items[itemName] >= requiredAmount or false
return hasItem, hasEnough
end,
['hasItems'] = function(itemNames, requiredAmount)
if not requiredAmount then requiredAmount = 1 end

for _, itemName in pairs(itemNames) do
local hasItem = qb_items[itemName] ~= nil
local hasEnough = hasItem and qb_items[itemName] >= requiredAmount or false

if not hasEnough then
return false, itemName
end
end

return true
end
}
end,
server = function()

end
}
93 changes: 75 additions & 18 deletions interactionMenu/lua/client/menuContainer.lua
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,26 @@ end
---@field event? string "Event name to trigger when the option is selected"
---@field hide boolean "Indicates whether the option should be hidden"

local function transformRestrictions(t)
if not Bridge.active then return end
if not t then return end
if type(t) == 'string' then t = { t } end
local transformed = {}

for index, name in pairs(t) do
if type(name) == 'string' then
transformed[name] = {}
elseif type(name) == 'table' then
transformed[index] = {}
for key, grade in pairs(name) do
transformed[index][grade] = true
end
end
end

return transformed
end

local function buildOption(data, instance)
for i, option in ipairs(data.options or {}) do
local formatted = {
Expand All @@ -177,6 +197,9 @@ local function buildOption(data, instance)
style = option.style,
progress = option.progress,
icon = option.icon,
item = option.item,
job = option.job and transformRestrictions(option.job),
gang = option.gang and transformRestrictions(option.gang),

flags = {
dynamic = option.dynamic,
Expand Down Expand Up @@ -234,19 +257,6 @@ local function classifyMenuInstance(instance)
end
end

local function transformJobData(data)
if not (data.extra and data.extra.job) then return end

for job_name, raw_grades in pairs(data.extra.job) do
local job_grades = {}

for _, grade in pairs(raw_grades) do
job_grades[grade] = true
end
data.extra.job[job_name] = job_grades
end
end

function Container.create(t)
local invokingResource = GetInvokingResource() or 'interactionMenu'
local id = t.id or Util.createUniqueId(Container.data)
Expand Down Expand Up @@ -377,8 +387,6 @@ function Container.create(t)
end

buildOption(t, instance)
transformJobData(instance)

buildInteraction(t, instance.interactions, "onTrigger")
buildInteraction(t, instance.interactions, "onSeen")
buildInteraction(t, instance.interactions, "onExit")
Expand Down Expand Up @@ -424,8 +432,6 @@ function Container.createGlobal(t)
}

buildOption(t, instance)
transformJobData(instance)

buildInteraction(t, instance.interactions, "onTrigger")
buildInteraction(t, instance.interactions, "onSeen")
buildInteraction(t, instance.interactions, "onExit")
Expand Down Expand Up @@ -916,6 +922,11 @@ local function processData(params)
zone = menuData.zone,
distance = menuData.distance
}
if interaction.payload then
for key, value in pairs(interaction.payload) do
data[key] = value
end
end
try_unpack = false
end
else
Expand Down Expand Up @@ -1076,6 +1087,49 @@ local function updateOptionVisibility(updatedElements, menuId, option, optionInd
return false
end

local function check_restrictions(restrictions)
if not Bridge.active then return true end

local job, job_level = Bridge.getJob()
local gang, gang_level = Bridge.getGang()

if restrictions.job then
local allowed_job_levels = restrictions.job[job]
if allowed_job_levels and (next(allowed_job_levels) == nil or allowed_job_levels[job_level]) then
return true, 'job'
end
end

if restrictions.gang then
local allowed_gang_levels = restrictions.gang[gang]
if allowed_gang_levels and (next(allowed_gang_levels) == nil or allowed_gang_levels[gang_level]) then
return true, 'gang'
end
end

return false
end

local function frameworkOptionVisibilityRestrictions(updatedElements, menuId, option, optionIndex, menuOriginalData, pt)
if not Bridge.active then return false end

local shouldHide = false
if option.item then
local res = Bridge.hasItem(option.item)
shouldHide = type(res) == "boolean" and not res
elseif option.job then
local res = check_restrictions({
job = option.job,
gang = option.gang
})
shouldHide = type(res) == "boolean" and not res
end

option.flags.hide = shouldHide

return false
end

--- calculate canInteract and update values and refresh UI
---@param scaleform table
---@param menuData table
Expand Down Expand Up @@ -1104,13 +1158,16 @@ function Container.syncData(scaleform, menuData, refreshUI)

for optionIndex, option in ipairs(menu.options) do
local already_inserted = false

-- #TODO: I think we should pass already_inserted to next step and check so we don't override it!
already_inserted = evaluateDynamicValue(updatedElements, menuId, option, optionIndex, menuOriginalData,
passThrough)
already_inserted = evaluateBindValue(updatedElements, menuId, option, optionIndex, menuOriginalData,
passThrough)
already_inserted = updateOptionVisibility(updatedElements, menuId, option, optionIndex, menuOriginalData,
passThrough)
already_inserted = frameworkOptionVisibilityRestrictions(updatedElements, menuId, option, optionIndex,
menuOriginalData,
passThrough)

-- to hide option if its canInteract value has been changed
if not already_inserted and option.flags.hide ~= nil and option.flags.hide ~= option.flags.previous_hide then
Expand Down
Loading

0 comments on commit 1ef5bc0

Please sign in to comment.