Files
Kakarot 74ac036d4a Refactor core structure and notification UI
Major refactor to centralize QBCore and replace the web UI stack:

- Move QBConfig into a unified QBCore.Config and initialize QBCore.PlayerData; add shared/functions.lua.
- Remove legacy client/main.lua and server/main.lua exports and update fxmanifest to include shared/functions and drop those main scripts.
- Update client event handling: adapt player data events to QBCore:Client:OnPlayerUpdated and trigger job/gang update events; remove deprecated exploitable handlers.
- Web UI rewrite: remove Vue/Quasar dependency and config module; implement a lightweight vanilla JS notification system (html/js/app.js) with updated html and css assets, plus drawtext tweaks.
- CSS overhaul for notification styling and animations; HTML head/meta/font cleanup.
- Server fixes: use QBCore.Shared.Locations in teleport command and strengthen numeric parsing/validation for coords.
- Add .gitattributes for consistent text/eol handling and small code cleanups (thread/comment removal).

These changes consolidate config/shared logic under QBCore, simplify the frontend dependencies, and fix a few parsing/event issues.
2026-05-19 18:13:35 -05:00

292 lines
10 KiB
Lua

-- Event Handler
AddEventHandler('chatMessage', function(_, _, message)
if string.sub(message, 1, 1) == '/' then
CancelEvent()
return
end
end)
AddEventHandler('playerDropped', function(reason)
local src = source
if not QBCore.Players[src] then return end
local player = QBCore.Players[src]
TriggerEvent('qb-log:server:CreateLog', 'joinleave', 'Dropped', 'red', '**' .. GetPlayerName(src) .. '** (' .. player.PlayerData.license .. ') left..' .. '\n **Reason:** ' .. reason)
player.Functions.Save()
TriggerEvent('QBCore:Server:PlayerDropped', src)
TriggerEvent('QBCore:Server:OnPlayerUnload', src)
QBCore.Player_Buckets[player.PlayerData.license] = nil
QBCore.PlayersByCitizenId[player.PlayerData.citizenid] = nil
QBCore.Players[src] = nil
end)
AddEventHandler('onResourceStop', function(resName)
for i, v in pairs(QBCore.UsableItems) do
if v.resource == resName then
QBCore.UsableItems[i] = nil
end
end
end)
-- Player Connecting
local readyFunction = MySQL.ready
local databaseConnected, bansTableExists = readyFunction == nil, readyFunction == nil
if readyFunction ~= nil then
MySQL.ready(function()
databaseConnected = true
local DatabaseInfo = QBCore.Functions.GetDatabaseInfo()
if not DatabaseInfo or not DatabaseInfo.exists then return end
local result = MySQL.query.await('SELECT TABLE_NAME FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA = ? AND TABLE_NAME = "bans";', { DatabaseInfo.database })
if result and result[1] then
bansTableExists = true
end
end)
end
local function onPlayerConnecting(name, _, deferrals)
local src = source
deferrals.defer()
if QBCore.Config.Server.Closed and not IsPlayerAceAllowed(src, 'qbadmin.join') then
return deferrals.done(QBCore.Config.Server.ClosedReason)
end
if not databaseConnected then
return deferrals.done(Lang:t('error.connecting_database_error'))
end
if QBCore.Config.Server.Whitelist then
Wait(0)
deferrals.update(string.format(Lang:t('info.checking_whitelisted'), name))
if not QBCore.Functions.IsWhitelisted(src) then
return deferrals.done(Lang:t('error.not_whitelisted'))
end
end
Wait(0)
deferrals.update(string.format('Hello %s. Your license is being checked', name))
local license = QBCore.Functions.GetIdentifier(src, 'license')
if not license then
return deferrals.done(Lang:t('error.no_valid_license'))
elseif QBCore.Config.Server.CheckDuplicateLicense and QBCore.Functions.IsLicenseInUse(license) then
return deferrals.done(Lang:t('error.duplicate_license'))
end
Wait(0)
deferrals.update(string.format(Lang:t('info.checking_ban'), name))
if not bansTableExists then
return deferrals.done(Lang:t('error.ban_table_not_found'))
end
local success, isBanned, reason = pcall(QBCore.Functions.IsPlayerBanned, src)
if not success then return deferrals.done(Lang:t('error.connecting_database_error')) end
if isBanned then return deferrals.done(reason) end
Wait(0)
deferrals.update(string.format(Lang:t('info.join_server'), name))
deferrals.done()
TriggerClientEvent('QBCore:Client:SharedUpdate', src, QBCore.Shared)
end
AddEventHandler('playerConnecting', onPlayerConnecting)
-- Open & Close Server (prevents players from joining)
RegisterNetEvent('QBCore:Server:CloseServer', function(reason)
local src = source
if QBCore.Functions.HasPermission(src, 'admin') then
reason = reason or 'No reason specified'
QBCore.Config.Server.Closed = true
QBCore.Config.Server.ClosedReason = reason
for k in pairs(QBCore.Players) do
if not QBCore.Functions.HasPermission(k, QBCore.Config.Server.WhitelistPermission) then
QBCore.Functions.Kick(k, reason, nil, nil)
end
end
else
QBCore.Functions.Kick(src, Lang:t('error.no_permission'), nil, nil)
end
end)
RegisterNetEvent('QBCore:Server:OpenServer', function()
local src = source
if QBCore.Functions.HasPermission(src, 'admin') then
QBCore.Config.Server.Closed = false
else
QBCore.Functions.Kick(src, Lang:t('error.no_permission'), nil, nil)
end
end)
-- Callback Events --
-- Client Callback
RegisterNetEvent('QBCore:Server:TriggerClientCallback', function(name, ...)
local ClientCallback = QBCore.ClientCallbacks[name .. source]
if ClientCallback then
ClientCallback.promise:resolve(...)
if ClientCallback.callback then
ClientCallback.callback(...)
end
QBCore.ClientCallbacks[name .. source] = nil
end
end)
-- Server Callback
RegisterNetEvent('QBCore:Server:TriggerCallback', function(name, ...)
if not QBCore.ServerCallbacks[name] then return end
local src = source
QBCore.ServerCallbacks[name](src, function(...)
TriggerClientEvent('QBCore:Client:TriggerCallback', src, name, ...)
end, ...)
end)
-- Player
local updateCooldowns = {}
RegisterNetEvent('QBCore:UpdatePlayer', function()
local src = source
local now = GetGameTimer()
if updateCooldowns[src] and (now - updateCooldowns[src]) < 10000 then return end
updateCooldowns[src] = now
local Player = QBCore.Functions.GetPlayer(src)
if not Player then return end
local newHunger = Player.PlayerData.metadata['hunger'] - QBCore.Config.Player.HungerRate
local newThirst = Player.PlayerData.metadata['thirst'] - QBCore.Config.Player.ThirstRate
if newHunger <= 0 then
newHunger = 0
end
if newThirst <= 0 then
newThirst = 0
end
Player.PlayerData.metadata['hunger'] = newHunger
Player.PlayerData.metadata['thirst'] = newThirst
Player.Functions.UpdateClient('metadata', Player.PlayerData.metadata)
TriggerClientEvent('hud:client:UpdateNeeds', src, newHunger, newThirst)
Player.Functions.Save()
end)
RegisterNetEvent('QBCore:ToggleDuty', function()
local src = source
local Player = QBCore.Functions.GetPlayer(src)
if not Player then return end
if Player.PlayerData.job.onduty then
Player.Functions.SetJobDuty(false)
TriggerClientEvent('QBCore:Notify', src, Lang:t('info.off_duty'))
else
Player.Functions.SetJobDuty(true)
TriggerClientEvent('QBCore:Notify', src, Lang:t('info.on_duty'))
end
TriggerEvent('QBCore:Server:SetDuty', src, Player.PlayerData.job.onduty)
TriggerClientEvent('QBCore:Client:SetDuty', src, Player.PlayerData.job.onduty)
end)
RegisterNetEvent('QBCore:Server:OnPlayerLoaded', function()
local src = source
if not QBCore.Players[src] then return end
TriggerClientEvent('QBCore:Client:OnPlayerLoaded', src)
end)
-- Central server-side data change handler — re-fires legacy events for backward compat
AddEventHandler('QBCore:Server:OnPlayerUpdated', function(src, key, val)
if key == 'job' then
TriggerEvent('QBCore:Server:OnJobUpdate', src, val)
elseif key == 'gang' then
TriggerEvent('QBCore:Server:OnGangUpdate', src, val)
elseif key == 'all' then
TriggerEvent('QBCore:Server:OnJobUpdate', src, val.job)
TriggerEvent('QBCore:Server:OnGangUpdate', src, val.gang)
end
end)
-- BaseEvents
-- Vehicles
RegisterServerEvent('baseevents:enteringVehicle', function(veh, seat, modelName)
local src = source
local data = {
vehicle = veh,
seat = seat,
name = modelName,
event = 'Entering'
}
TriggerClientEvent('QBCore:Client:VehicleInfo', src, data)
end)
RegisterServerEvent('baseevents:enteredVehicle', function(veh, seat, modelName)
local src = source
local data = {
vehicle = veh,
seat = seat,
name = modelName,
event = 'Entered'
}
TriggerClientEvent('QBCore:Client:VehicleInfo', src, data)
end)
RegisterServerEvent('baseevents:enteringAborted', function()
local src = source
TriggerClientEvent('QBCore:Client:AbortVehicleEntering', src)
end)
RegisterServerEvent('baseevents:leftVehicle', function(veh, seat, modelName)
local src = source
local data = {
vehicle = veh,
seat = seat,
name = modelName,
event = 'Left'
}
TriggerClientEvent('QBCore:Client:VehicleInfo', src, data)
end)
-- Non-Chat Command Calling (ex: qb-adminmenu)
RegisterNetEvent('QBCore:CallCommand', function(command, args)
local src = source
if not QBCore.Commands.List[command] then return end
local Player = QBCore.Functions.GetPlayer(src)
if not Player then return end
local hasPerm = QBCore.Functions.HasPermission(src, 'command.' .. QBCore.Commands.List[command].name)
if hasPerm then
if QBCore.Commands.List[command].argsrequired and #QBCore.Commands.List[command].arguments ~= 0 and not args[#QBCore.Commands.List[command].arguments] then
TriggerClientEvent('QBCore:Notify', src, Lang:t('error.missing_args2'), 'error')
else
QBCore.Commands.List[command].callback(src, args)
end
else
TriggerClientEvent('QBCore:Notify', src, Lang:t('error.no_access'), 'error')
end
end)
-- Use this for player vehicle spawning
-- Vehicle server-side spawning callback (netId)
-- use the netid on the client with the NetworkGetEntityFromNetworkId native
-- convert it to a vehicle via the NetToVeh native
QBCore.Functions.CreateCallback('QBCore:Server:SpawnVehicle', function(source, cb, model, coords, warp)
local veh = QBCore.Functions.SpawnVehicle(source, model, coords, warp)
cb(DoesEntityExist(veh) and NetworkGetNetworkIdFromEntity(veh) or nil)
end)
-- Use this for long distance vehicle spawning
-- vehicle server-side spawning callback (netId)
-- use the netid on the client with the NetworkGetEntityFromNetworkId native
-- convert it to a vehicle via the NetToVeh native
QBCore.Functions.CreateCallback('QBCore:Server:CreateVehicle', function(source, cb, model, coords, warp)
local veh = QBCore.Functions.CreateAutomobile(source, model, coords, warp)
cb(DoesEntityExist(veh) and NetworkGetNetworkIdFromEntity(veh) or nil)
end)
--QBCore.Functions.CreateCallback('QBCore:HasItem', function(source, cb, items, amount)
-- https://github.com/qbcore-framework/qb-inventory/blob/e4ef156d93dd1727234d388c3f25110c350b3bcf/server/main.lua#L2066
--end)