2026-04-14 17:41:39 +02:00

475 lines
17 KiB
Lua
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

-- ==========================================================
-- ESX Bridge (QBCore -> ESX) for codem-phone
-- Mirrors your Core API and behaviors as close as possible.
-- ==========================================================
if Config.Framework ~= "esx" then
return
end
local function CheckESXStatus()
local state = GetResourceState('es_extended')
return state == "started" or state == "starting"
end
local nameCache = {}
Core = {
CoreReady = false,
JobbyJobs = {},
JobbyJobsReady = false,
}
Core.__index = Core
Core.Functions = {}
local databaseReady = false
AddEventHandler('codem-phone:database:ready', function()
databaseReady = true
end)
local ESX = nil
if CheckESXStatus() then
local ok, res = pcall(function()
return exports['es_extended']:getSharedObject()
end)
if ok and res then
ESX = res
Core.CoreReady = true
print('^2[codem-phone] ^7ESX object retrieved successfully')
Citizen.CreateThread(function()
local timeout = 0
while not databaseReady and timeout < 60000 do
Wait(100)
timeout = timeout + 100
end
if not databaseReady then
print('^1[CODEM-PHONE] Framework ERROR: Database initialization timeout!^7')
return
end
local JobbyConfig = LoadFile and LoadFile('config/AppConfig/JobbyConfig.lua') or nil
local players = MySQL.query.await([[
SELECT identifier, job, job_grade, firstname, lastname
FROM users
]])
local jobsRaw = MySQL.query.await("SELECT name, label FROM jobs")
local jobGradesRaw = MySQL.query.await("SELECT job_name, grade, label, salary, name FROM job_grades")
local JobsIndex = {}
for _, j in ipairs(jobsRaw or {}) do
JobsIndex[j.name] = { label = j.label, grades = {} }
end
for _, g in ipairs(jobGradesRaw or {}) do
local J = JobsIndex[g.job_name]
if J then
J.grades[tostring(g.grade)] = {
name = g.label or g.name or ('Grade ' .. g.grade),
payment = g.salary or 0,
isboss = false,
}
end
end
for jobName, _ in pairs(JobbyConfig and JobbyConfig.AllowJobs or {}) do
Core.JobbyJobs[jobName] = {
label = (JobsIndex[jobName] and JobsIndex[jobName].label) or 'Unknown Job',
players = {},
name = jobName,
grades = (JobsIndex[jobName] and JobsIndex[jobName].grades) or {},
money = 0,
announcements = {},
logs = {},
}
local hasAnn = MySQL.scalar.await(
"SELECT 1 FROM information_schema.tables WHERE table_name = 'codem_mphone_jobby_announcements' LIMIT 1")
if hasAnn then
Core.JobbyJobs[jobName].announcements = MySQL.query.await(
[[SELECT id, title, content, created_at
FROM codem_mphone_jobby_announcements
WHERE jobname = ?
ORDER BY created_at DESC
LIMIT 15]], { jobName }) or {}
end
local hasLogs = MySQL.scalar.await(
"SELECT 1 FROM information_schema.tables WHERE table_name = 'codem_mphone_jobby_log' LIMIT 1")
if hasLogs then
Core.JobbyJobs[jobName].logs = MySQL.query.await(
[[SELECT id, action, amount, playername, phone_number, date
FROM codem_mphone_jobby_log
WHERE jobname = ?
ORDER BY date DESC
LIMIT 20]], { jobName }) or {}
end
end
for _, u in ipairs(players or {}) do
local jname = u.job
if Core.JobbyJobs[jname] then
local gradeLevel = tonumber(u.job_grade) or 0
local gradeName = (JobsIndex[jname] and JobsIndex[jname].grades and JobsIndex[jname].grades[tostring(gradeLevel)]
and JobsIndex[jname].grades[tostring(gradeLevel)].name) or 'Unknown Grade'
local playerName = ((u.firstname or '') .. ' ' .. (u.lastname or ''))
:gsub("^%l", string.upper)
Core.JobbyJobs[jname].players[#Core.JobbyJobs[jname].players + 1] = {
name = playerName ~= ' ' and playerName or 'Unknown Player',
grade_level = gradeLevel,
grade_name = gradeName,
src = false,
identifier = u.identifier,
boss = false,
}
end
end
for jobName, data in pairs(Core.JobbyJobs) do
Core.JobbyJobs[jobName].money = GetJobMoney and GetJobMoney(jobName) or 0
end
Core.JobbyJobsReady = true
end)
else
print('^1[codem-phone] ^7Error: ESX object could not be retrieved')
return
end
else
print('^1[codem-phone] ^7Error: es_extended not found or not started')
return
end
local function getXPlayer(playerId)
if not playerId then return nil end
return ESX.GetPlayerFromId(playerId)
end
function GetHighestGrade(job)
local AllFrameworkJobs = Core.JobbyJobs or {}
if AllFrameworkJobs[job] then
local AllGrades = AllFrameworkJobs[job].grades or {}
local highest = -1
for key, _ in pairs(AllGrades) do
local n = tonumber(key)
if n and n > highest then highest = n end
end
return highest
end
return false
end
function IsBoss(job, gradeLevel)
local highestGrade = GetHighestGrade(job)
return highestGrade and gradeLevel >= highestGrade
end
Core.Functions.GetPlayer = function(playerId)
if not playerId then
-- DebugPrint and DebugPrint('Error: playerId is required')
return nil
end
local xPlayer = getXPlayer(playerId)
if xPlayer then return xPlayer end
-- DebugPrint and DebugPrint('Error: Player not found - ID: ' .. tostring(playerId))
return nil
end
Core.Functions.GetIdentifier = function(playerId)
if not playerId then
-- DebugPrint and DebugPrint('Error: playerId is required')
return nil
end
local xPlayer = getXPlayer(playerId)
if xPlayer then
return xPlayer.identifier
end
--DebugPrint and DebugPrint('Error: Player not found - ID: ' .. tostring(playerId))
return nil
end
Core.Functions.GetSourceFromIdentifier = function(identifier)
if not identifier then
--- DebugPrint and DebugPrint('Error: identifier is required')
return nil
end
if ESX.GetPlayerFromIdentifier then
local xPlayer = ESX.GetPlayerFromIdentifier(identifier)
return xPlayer and xPlayer.source or nil
end
for _, src in ipairs(GetPlayers()) do
local xP = ESX.GetPlayerFromId(tonumber(src))
if xP and xP.identifier == identifier then
return xP.source
end
end
-- DebugPrint and DebugPrint('Error: Player not online for identifier - ' .. tostring(identifier))
return nil
end
Core.Functions.GetPlayerJob = function(playerId)
if not playerId then
-- DebugPrint and DebugPrint('Error: playerId is required')
return nil
end
local xPlayer = getXPlayer(playerId)
if not xPlayer then
-- DebugPrint and DebugPrint('Error: Player not found - ID: ' .. tostring(playerId))
return nil
end
local job = xPlayer.getJob()
if not job then
return {
name = 'unemployed',
label = 'Unemployed',
onduty = true,
grade_name = 'unemployed',
grade_level = 0,
isboss = false
}
end
return {
name = job.name or 'unemployed',
label = job.label or 'Unemployed',
onduty = true,
grade_name = job.grade_label or (job.grade and tostring(job.grade)) or 'unemployed',
grade_level = job.grade or 0,
isboss = (job.grade_name == 'boss') or (job.grade_label == 'boss') or false,
}
end
Core.Functions.IsPlayerAdmin = function(playerId)
if not playerId then
--DebugPrint and DebugPrint('Error: playerId is required')
return false
end
local xPlayer = getXPlayer(playerId)
if not xPlayer then return false end
local grp = (xPlayer.getGroup and xPlayer.getGroup()) or xPlayer.group or 'user'
local allowed = Config.AdminPermissions or { 'admin', 'superadmin' }
for _, g in ipairs(allowed) do
if grp == g then return true end
end
if IsPlayerAceAllowed(playerId, 'command') then return true end
return false
end
AddEventHandler('esx:setJob', function(src, job)
if type(src) ~= 'number' then
src = source
end
if not src or not job then return end
TriggerClientEvent('codem-phone:client:OnJobUpdate', src)
OnJobUpdate(src, {
jobname = job.name or 'unemployed',
onduty = true,
})
OnJobbyJobUpdate(src)
end)
Core.Functions.GetName = function(playerIdOrIdentifier, nameType)
if not playerIdOrIdentifier then
-- DebugPrint and DebugPrint('Error: playerId is required')
return 'Unknown'
end
-- online?
local asNumber = tonumber(playerIdOrIdentifier)
if asNumber then
local xPlayer = getXPlayer(asNumber)
if xPlayer then
local first = xPlayer.get('firstName') or xPlayer.get('firstname') or ""
local last = xPlayer.get('lastName') or xPlayer.get('lastname') or ""
if (first == "" and last == "") and xPlayer.getName then
local n = xPlayer.getName()
if nameType and nameType:lower() == 'first' then
return (n:match("^(%S+)") or n)
elseif nameType and nameType:lower() == 'full' then
return n
end
return n
end
if nameType and nameType:lower() == 'first' then
return (first ~= "" and first or 'Unknown'):gsub("^%l", string.upper)
elseif nameType and nameType:lower() == 'full' then
local full = (first .. " " .. last):gsub("^%l", string.upper)
return (full ~= " " and full or 'Unknown')
end
local full = (first .. " " .. last):gsub("^%l", string.upper)
return (full ~= " " and full or 'Unknown')
end
-- fall through to offline using identifier cache (we dont have it here)
return 'Unknown'
else
-- treat param as identifier (offline support)
local identifier = tostring(playerIdOrIdentifier)
if nameCache[identifier] then
local cached = nameCache[identifier]
if nameType and nameType:lower() == 'first' then
return (cached.firstname):gsub("^%l", string.upper)
elseif nameType and nameType:lower() == 'full' then
return (cached.firstname .. " " .. cached.lastname):gsub("^%l", string.upper)
end
return (cached.firstname .. " " .. cached.lastname):gsub("^%l", string.upper)
end
-- DB lookup (ESX default schema)
local row = MySQL.single.await("SELECT firstname, lastname FROM users WHERE identifier = ? LIMIT 1",
{ identifier })
if row then
nameCache[identifier] = { firstname = row.firstname or "Unknown", lastname = row.lastname or "" }
if nameType and nameType:lower() == 'first' then
return (row.firstname or "Unknown"):gsub("^%l", string.upper)
elseif nameType and nameType:lower() == 'full' then
return ((row.firstname or "") .. " " .. (row.lastname or "")):gsub("^%l", string.upper)
end
return ((row.firstname or "") .. " " .. (row.lastname or "")):gsub("^%l", string.upper)
end
return 'Unknown'
end
end
-- Money getters in ESX: use accounts (bank/money/black_money)
Core.Functions.GetMoney = function(playerId, moneyType)
if not playerId then
--DebugPrint and DebugPrint('Error: playerId is required')
return 0
end
moneyType = moneyType or 'bank'
local xPlayer = getXPlayer(playerId)
if not xPlayer then
return { success = false, message = 'Error: Player not found - ID: ' .. tostring(playerId) }
end
local acc = xPlayer.getAccount(moneyType)
local amount = acc and acc.money or 0
return { success = true, money = amount }
end
Core.Functions.RemoveMoney = function(playerId, amount, moneyType)
if not playerId then
-- DebugPrint and DebugPrint('Error: playerId is required')
return false
end
amount = tonumber(amount)
if not amount or amount <= 0 then
-- DebugPrint and DebugPrint('Error: amount must be positive')
return false
end
moneyType = moneyType or 'bank'
local xPlayer = getXPlayer(playerId)
if not xPlayer then
-- DebugPrint and DebugPrint('Error: Player not found - ID: ' .. tostring(playerId))
return false
end
local acc = xPlayer.getAccount(moneyType)
local bal = acc and acc.money or 0
if bal < amount then
-- DebugPrint and DebugPrint(('Error: Insufficient funds (%s < %s) - ID: %s'):format(bal, amount, tostring(playerId)))
return false
end
xPlayer.removeAccountMoney(moneyType, amount, "codem-phone")
return true
end
Core.Functions.AddMoney = function(playerId, amount, moneyType)
if not playerId then
-- DebugPrint and DebugPrint('Error: playerId is required')
return false
end
amount = tonumber(amount)
if not amount or amount <= 0 then
-- DebugPrint and DebugPrint('Error: amount must be positive')
return false
end
moneyType = moneyType or 'bank'
local xPlayer = getXPlayer(playerId)
if not xPlayer then
-- DebugPrint and DebugPrint('Error: Player not found - ID: ' .. tostring(playerId))
return false
end
xPlayer.addAccountMoney(moneyType, amount, "codem-phone")
return true
end
-- Offline money update (users.accounts JSON)
Core.Functions.AddMoneyOffline = function(identifier, amount, moneyType)
if not identifier then
return { success = false, message = "Identifier is required." }
end
amount = tonumber(amount)
if not amount or amount <= 0 then
return { success = false, message = "Invalid amount." }
end
moneyType = moneyType or 'bank'
-- get accounts JSON
local accountsJson = MySQL.scalar.await("SELECT accounts FROM users WHERE identifier = ?", { identifier })
if not accountsJson then
return { success = false, message = "Player not found in database." }
end
local acc = {}
if type(accountsJson) == "string" and accountsJson ~= "" then
acc = json.decode(accountsJson) or {}
elseif type(accountsJson) == "table" then
acc = accountsJson
end
acc[moneyType] = (tonumber(acc[moneyType]) or 0) + amount
local rows = MySQL.update.await("UPDATE users SET accounts = ? WHERE identifier = ?",
{ json.encode(acc), identifier })
if rows and rows > 0 then
return { success = true }
else
return { success = false, message = "Failed to update money for offline player." }
end
end
Core.Functions.SetPlayerJob = function(playerId, jobname, grade)
local xPlayer = getXPlayer(playerId)
if not xPlayer then
-- DebugPrint and DebugPrint('Error: Player not found - ID: ' .. tostring(playerId))
return false
end
local exists = MySQL.scalar.await("SELECT 1 FROM jobs WHERE name = ? LIMIT 1", { jobname })
if not exists then
-- DebugPrint and DebugPrint(('Error: job "%s" not found in DB'):format(jobname))
return false
end
xPlayer.setJob(jobname, tonumber(grade) or 0)
return true
end
Core.Functions.CreateUseableItem = function(itemName, callback)
if not itemName or type(itemName) ~= 'string' then
-- DebugPrint and DebugPrint('Error: itemName must be a valid string')
return
end
if not callback or type(callback) ~= 'function' then
-- DebugPrint and DebugPrint('Error: callback must be a valid function')
return
end
if ESX.RegisterUsableItem then
ESX.RegisterUsableItem(itemName, function(source, item)
callback(source, item)
end)
else
-- DebugPrint and DebugPrint('Error: ESX.RegisterUsableItem not available in this build')
end
end