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.
This commit is contained in:
Kakarot
2026-05-19 18:13:35 -05:00
parent 891b039bf3
commit 74ac036d4a
28 changed files with 1242 additions and 1419 deletions
+11
View File
@@ -0,0 +1,11 @@
* text=auto eol=lf
# Lua and web files - always treat as text
*.lua text eol=lf
*.js text eol=lf
*.html text eol=lf
*.css text eol=lf
*.json text eol=lf
*.xml text eol=lf
*.yml text eol=lf
*.md text eol=lf
+1 -1
View File
@@ -29,7 +29,7 @@ local function changeText(text, position)
end end
local function keyPressed() local function keyPressed()
CreateThread(function() -- Not sure if a thread is needed but why not eh? CreateThread(function()
SendNUIMessage({ SendNUIMessage({
action = 'KEY_PRESSED', action = 'KEY_PRESSED',
}) })
+10 -19
View File
@@ -1,6 +1,3 @@
-- Player load and unload handling
-- New method for checking if logged in across all scripts (optional)
-- if LocalPlayer.state['isLoggedIn'] then
RegisterNetEvent('QBCore:Client:OnPlayerLoaded', function() RegisterNetEvent('QBCore:Client:OnPlayerLoaded', function()
ShutdownLoadingScreenNui() ShutdownLoadingScreenNui()
LocalPlayer.state:set('isLoggedIn', true, false) LocalPlayer.state:set('isLoggedIn', true, false)
@@ -17,6 +14,7 @@ RegisterNetEvent('QBCore:Client:PvpHasToggled', function(pvp_state)
SetCanAttackFriendly(PlayerPedId(), pvp_state, false) SetCanAttackFriendly(PlayerPedId(), pvp_state, false)
NetworkSetFriendlyFireOption(pvp_state) NetworkSetFriendlyFireOption(pvp_state)
end) end)
-- Teleport Commands -- Teleport Commands
RegisterNetEvent('QBCore:Command:TeleportToPlayer', function(coords) RegisterNetEvent('QBCore:Command:TeleportToPlayer', function(coords)
@@ -182,30 +180,23 @@ end)
-- Other stuff -- Other stuff
RegisterNetEvent('QBCore:Player:SetPlayerData', function(val) RegisterNetEvent('QBCore:Client:OnPlayerUpdated', function(key, val)
QBCore.PlayerData = val if key == 'all' then
end) QBCore.PlayerData = val
TriggerEvent('QBCore:Player:SetPlayerData', val)
RegisterNetEvent('QBCore:Player:UpdatePlayerDataField', function(key, val) TriggerEvent('QBCore:Client:OnJobUpdate', val.job)
if QBCore.PlayerData and key then TriggerEvent('QBCore:Client:OnGangUpdate', val.gang)
elseif QBCore.PlayerData and key then
QBCore.PlayerData[key] = val QBCore.PlayerData[key] = val
if key == 'job' then TriggerEvent('QBCore:Client:OnJobUpdate', val) end
if key == 'gang' then TriggerEvent('QBCore:Client:OnGangUpdate', val) end
end end
end) end)
RegisterNetEvent('QBCore:Player:UpdatePlayerData', function()
TriggerServerEvent('QBCore:UpdatePlayer')
end)
RegisterNetEvent('QBCore:Notify', function(text, type, length, icon) RegisterNetEvent('QBCore:Notify', function(text, type, length, icon)
QBCore.Functions.Notify(text, type, length, icon) QBCore.Functions.Notify(text, type, length, icon)
end) end)
-- This event is exploitable and should not be used. It has been deprecated, and will be removed soon.
RegisterNetEvent('QBCore:Client:UseItem', function(item)
QBCore.Debug(string.format('%s triggered QBCore:Client:UseItem by ID %s with the following data. This event is deprecated due to exploitation, and will be removed soon. Check qb-inventory for the right use on this event.', GetInvokingResource(), GetPlayerServerId(PlayerId())))
QBCore.Debug(item)
end)
RegisterNUICallback('getNotifyConfig', function(_, cb) RegisterNUICallback('getNotifyConfig', function(_, cb)
cb(QBCore.Config.Notify) cb(QBCore.Config.Notify)
end) end)
+1
View File
@@ -1,3 +1,4 @@
QBCore.PlayerData = {}
QBCore.Functions = {} QBCore.Functions = {}
-- Callbacks -- Callbacks
-50
View File
@@ -1,50 +0,0 @@
QBCore = {}
QBCore.PlayerData = {}
QBCore.Config = QBConfig
QBCore.Shared = QBShared
QBCore.ClientCallbacks = {}
QBCore.ServerCallbacks = {}
-- Get the full QBCore object (default behavior):
-- local QBCore = GetCoreObject()
-- Get only specific parts of QBCore:
-- local QBCore = GetCoreObject({'Players', 'Config'})
local function GetCoreObject(filters)
if not filters then return QBCore end
local results = {}
for i = 1, #filters do
local key = filters[i]
if QBCore[key] then
results[key] = QBCore[key]
end
end
return results
end
exports('GetCoreObject', GetCoreObject)
local function GetSharedItems()
return QBShared.Items
end
exports('GetSharedItems', GetSharedItems)
local function GetSharedVehicles()
return QBShared.Vehicles
end
exports('GetSharedVehicles', GetSharedVehicles)
local function GetSharedWeapons()
return QBShared.Weapons
end
exports('GetSharedWeapons', GetSharedWeapons)
local function GetSharedJobs()
return QBShared.Jobs
end
exports('GetSharedJobs', GetSharedJobs)
local function GetSharedGangs()
return QBShared.Gangs
end
exports('GetSharedGangs', GetSharedGangs)
+35 -34
View File
@@ -1,30 +1,31 @@
QBConfig = {} QBCore = {}
QBCore.Config = {}
QBConfig.MaxPlayers = GetConvarInt('sv_maxclients', 48) -- Gets max players from config file, default 48 QBCore.Config.MaxPlayers = GetConvarInt('sv_maxclients', 48) -- Gets max players from config file, default 48
QBConfig.DefaultSpawn = vector4(-1035.71, -2731.87, 12.86, 0.0) QBCore.Config.DefaultSpawn = vector4(-1035.71, -2731.87, 12.86, 0.0)
QBConfig.UpdateInterval = 5 -- how often to update player data in minutes QBCore.Config.UpdateInterval = 5 -- how often to update player data in minutes
QBConfig.StatusInterval = 5000 -- how often to check hunger/thirst status in milliseconds QBCore.Config.StatusInterval = 5000 -- how often to check hunger/thirst status in milliseconds
QBConfig.Money = {} QBCore.Config.Money = {}
QBConfig.Money.MoneyTypes = { cash = 500, bank = 5000, crypto = 0 } -- type = startamount - Add or remove money types for your server (for ex. blackmoney = 0), remember once added it will not be removed from the database! QBCore.Config.Money.MoneyTypes = { cash = 500, bank = 5000, crypto = 0 } -- type = startamount - Add or remove money types for your server (for ex. blackmoney = 0), remember once added it will not be removed from the database!
QBConfig.Money.DontAllowMinus = { 'cash', 'crypto' } -- Money that is not allowed going in minus QBCore.Config.Money.DontAllowMinus = { 'cash', 'crypto' } -- Money that is not allowed going in minus
QBConfig.Money.MinusLimit = -5000 -- The maximum amount you can be negative QBCore.Config.Money.MinusLimit = -5000 -- The maximum amount you can be negative
QBConfig.Money.PayCheckTimeOut = 10 -- The time in minutes that it will give the paycheck QBCore.Config.Money.PayCheckTimeOut = 10 -- The time in minutes that it will give the paycheck
QBConfig.Money.PayCheckSociety = false -- If true paycheck will come from the society account that the player is employed at, requires qb-management QBCore.Config.Money.PayCheckSociety = false -- If true paycheck will come from the society account that the player is employed at, requires qb-management
QBConfig.Player = {} QBCore.Config.Player = {}
QBConfig.Player.HungerRate = 4.2 -- Rate at which hunger goes down. QBCore.Config.Player.HungerRate = 4.2 -- Rate at which hunger goes down.
QBConfig.Player.ThirstRate = 3.8 -- Rate at which thirst goes down. QBCore.Config.Player.ThirstRate = 3.8 -- Rate at which thirst goes down.
QBConfig.Player.Bloodtypes = { QBCore.Config.Player.Bloodtypes = {
'A+', 'A-', 'B+', 'B-', 'AB+', 'AB-', 'O+', 'O-', 'A+', 'A-', 'B+', 'B-', 'AB+', 'AB-', 'O+', 'O-',
} }
QBConfig.Player.PlayerDefaults = { QBCore.Config.Player.PlayerDefaults = {
citizenid = function() return QBCore.Player.CreateCitizenId() end, citizenid = function() return QBCore.Player.CreateCitizenId() end,
cid = 1, cid = 1,
money = function() money = function()
local moneyDefaults = {} local moneyDefaults = {}
for moneytype, startamount in pairs(QBConfig.Money.MoneyTypes) do for moneytype, startamount in pairs(QBCore.Config.Money.MoneyTypes) do
moneyDefaults[moneytype] = startamount moneyDefaults[moneytype] = startamount
end end
return moneyDefaults return moneyDefaults
@@ -76,7 +77,7 @@ QBConfig.Player.PlayerDefaults = {
rep = {}, rep = {},
currentapartment = nil, currentapartment = nil,
callsign = 'NO CALLSIGN', callsign = 'NO CALLSIGN',
bloodtype = function() return QBConfig.Player.Bloodtypes[math.random(1, #QBConfig.Player.Bloodtypes)] end, bloodtype = function() return QBCore.Config.Player.Bloodtypes[math.random(1, #QBCore.Config.Player.Bloodtypes)] end,
fingerprint = function() return QBCore.Player.CreateFingerId() end, fingerprint = function() return QBCore.Player.CreateFingerId() end,
walletid = function() return QBCore.Player.CreateWalletId() end, walletid = function() return QBCore.Player.CreateWalletId() end,
criminalrecord = { criminalrecord = {
@@ -100,27 +101,27 @@ QBConfig.Player.PlayerDefaults = {
InstalledApps = {} InstalledApps = {}
} }
}, },
position = QBConfig.DefaultSpawn, position = QBCore.Config.DefaultSpawn,
items = {}, items = {},
} }
QBConfig.Server = {} -- General server config QBCore.Config.Server = {} -- General server config
QBConfig.Server.Closed = false -- Set server closed (no one can join except people with ace permission 'qbadmin.join') QBCore.Config.Server.Closed = false -- Set server closed (no one can join except people with ace permission 'qbadmin.join')
QBConfig.Server.ClosedReason = 'Server Closed' -- Reason message to display when people can't join the server QBCore.Config.Server.ClosedReason = 'Server Closed' -- Reason message to display when people can't join the server
QBConfig.Server.Uptime = 0 -- Time the server has been up. QBCore.Config.Server.Uptime = 0 -- Time the server has been up.
QBConfig.Server.Whitelist = false -- Enable or disable whitelist on the server QBCore.Config.Server.Whitelist = false -- Enable or disable whitelist on the server
QBConfig.Server.WhitelistPermission = 'admin' -- Permission that's able to enter the server when the whitelist is on QBCore.Config.Server.WhitelistPermission = 'admin' -- Permission that's able to enter the server when the whitelist is on
QBConfig.Server.PVP = true -- Enable or disable pvp on the server (Ability to shoot other players) QBCore.Config.Server.PVP = true -- Enable or disable pvp on the server (Ability to shoot other players)
QBConfig.Server.Discord = '' -- Discord invite link QBCore.Config.Server.Discord = '' -- Discord invite link
QBConfig.Server.CheckDuplicateLicense = true -- Check for duplicate rockstar license on join QBCore.Config.Server.CheckDuplicateLicense = true -- Check for duplicate rockstar license on join
QBConfig.Server.Permissions = { 'god', 'admin', 'mod' } -- Add as many groups as you want here after creating them in your server.cfg QBCore.Config.Server.Permissions = { 'god', 'admin', 'mod' } -- Add as many groups as you want here after creating them in your server.cfg
QBConfig.Commands = {} -- Command Configuration QBCore.Config.Commands = {} -- Command Configuration
QBConfig.Commands.OOCColor = { 255, 151, 133 } -- RGB color code for the OOC command QBCore.Config.Commands.OOCColor = { 255, 151, 133 } -- RGB color code for the OOC command
QBConfig.Notify = {} QBCore.Config.Notify = {}
QBConfig.Notify.NotificationStyling = { QBCore.Config.Notify.NotificationStyling = {
group = false, -- Allow notifications to stack with a badge instead of repeating group = false, -- Allow notifications to stack with a badge instead of repeating
position = 'right', -- top-left | top-right | bottom-left | bottom-right | top | bottom | left | right | center position = 'right', -- top-left | top-right | bottom-left | bottom-right | top | bottom | left | right | center
progress = true -- Display Progress Bar progress = true -- Display Progress Bar
@@ -129,7 +130,7 @@ QBConfig.Notify.NotificationStyling = {
-- These are how you define different notification variants -- These are how you define different notification variants
-- The "color" key is background of the notification -- The "color" key is background of the notification
-- The "icon" key is the css-icon code, this project uses `Material Icons` & `Font Awesome` -- The "icon" key is the css-icon code, this project uses `Material Icons` & `Font Awesome`
QBConfig.Notify.VariantDefinitions = { QBCore.Config.Notify.VariantDefinitions = {
success = { success = {
classes = 'success', classes = 'success',
icon = 'check_circle' icon = 'check_circle'
+1 -2
View File
@@ -11,6 +11,7 @@ shared_scripts {
'locale/en.lua', 'locale/en.lua',
'locale/*.lua', 'locale/*.lua',
'shared/main.lua', 'shared/main.lua',
'shared/functions.lua',
'shared/items.lua', 'shared/items.lua',
'shared/jobs.lua', 'shared/jobs.lua',
'shared/vehicles.lua', 'shared/vehicles.lua',
@@ -20,7 +21,6 @@ shared_scripts {
} }
client_scripts { client_scripts {
'client/main.lua',
'client/functions.lua', 'client/functions.lua',
'client/loops.lua', 'client/loops.lua',
'client/events.lua', 'client/events.lua',
@@ -29,7 +29,6 @@ client_scripts {
server_scripts { server_scripts {
'@oxmysql/lib/MySQL.lua', '@oxmysql/lib/MySQL.lua',
'server/main.lua',
'server/functions.lua', 'server/functions.lua',
'server/player.lua', 'server/player.lua',
'server/events.lua', 'server/events.lua',
+3 -30
View File
@@ -1,35 +1,8 @@
:root { :root {
/* Typography */ --primary-bg: #211f26;
--font-primary: "Exo 2", sans-serif; --active-bg: #c10114;
--font-weight-regular: 400; --font-color: #e6e1e5;
--font-weight-medium: 500;
--font-weight-bold: 700;
--font-weight-light: 300;
/* Colors */
--md-primary: #1c75d2;
--md-primary-container: #6facec;
--md-error: #c10114;
--md-error-container: #fe4255;
--md-success: #20bb44;
--md-success-container: #6ae587;
--md-warning: #ff9800;
--md-warning-container: #ffc107;
--md-info: #2197f2;
--md-info-container: #7ac1f7;
--md-surface: #fffbff;
--md-on-surface: #1c1b1f;
/* Custom Variables */
--primary-bg: rgba(23, 23, 23, 90%);
--active-bg: var(--md-error);
--font-color: white;
/* Elevation */
--md-elevation-1: 0px 1px 3px 1px rgba(0, 0, 0, 0.15); --md-elevation-1: 0px 1px 3px 1px rgba(0, 0, 0, 0.15);
/* Border Radius */
--md-radius-small: 5px;
--md-radius-medium: 10px;
--md-radius-extra-small: 0.15rem; --md-radius-extra-small: 0.15rem;
} }
+93 -117
View File
@@ -1,49 +1,8 @@
:root { :root {
/* Typography */
--font-primary: "Exo 2", sans-serif; --font-primary: "Exo 2", sans-serif;
--font-weight-regular: 400; --font-weight-regular: 400;
--font-weight-medium: 500; --font-weight-medium: 500;
--font-weight-bold: 700;
--font-weight-light: 300;
/* Colors */
--md-primary: #0061a4;
--md-on-primary: #ffffff;
--md-primary-container: #d1e4ff;
--md-on-primary-container: #001d36;
--md-error: #ba1a1a;
--md-on-error: #ffffff;
--md-error-container: #ffdad6;
--md-on-error-container: #410002;
--md-success: #006e1c;
--md-on-success: #ffffff;
--md-success-container: #9df996;
--md-on-success-container: #002106;
--md-warning: #7d5800;
--md-on-warning: #ffffff;
--md-warning-container: #ffdf93;
--md-on-warning-container: #271900;
--md-info: #0062a1;
--md-on-info: #ffffff;
--md-info-container: #d0e4ff;
--md-on-info-container: #001d35;
/* Surface colors */
--md-surface: #fcfcff;
--md-on-surface: #1a1c1e;
--md-surface-variant: #dfe2eb;
--md-on-surface-variant: #43474e;
/* Elevation */
--md-elevation-1: 0px 1px 3px 1px rgba(0, 0, 0, 0.15); --md-elevation-1: 0px 1px 3px 1px rgba(0, 0, 0, 0.15);
/* Border Radius */
--md-radius-small: 4px;
--md-radius-medium: 8px;
} }
* { * {
@@ -55,94 +14,111 @@ html::-webkit-scrollbar {
display: none; display: none;
} }
@media (width: 3840px) and (height: 2160px) {
.success {
background-color: rgba(23, 23, 23, 90%);
color: var(--md-on-surface);
box-shadow: var(--md-elevation-1);
border-left: 0.5rem solid var(--md-success);
font-size: 1.5vh;
}
.primary {
background-color: rgba(23, 23, 23, 90%);
color: var(--md-on-surface);
box-shadow: var(--md-elevation-1);
border-left: 5px solid var(--md-primary);
font-size: 1.5vh;
}
.error {
background-color: rgba(23, 23, 23, 90%);
color: var(--md-on-surface);
box-shadow: var(--md-elevation-1);
border-left: 5px solid var(--md-error);
font-size: 1.5vh;
}
.warning {
background-color: rgba(23, 23, 23, 90%);
color: var(--md-on-surface);
box-shadow: var(--md-elevation-1);
border-left: 5px solid var(--md-warning);
font-size: 1.5vh;
}
.police {
background-color: rgba(23, 23, 23, 90%);
color: var(--md-on-surface);
box-shadow: var(--md-elevation-1);
border-left: 5px solid var(--md-info);
font-size: 1.5vh;
}
.ambulance {
background-color: rgba(23, 23, 23, 90%);
color: var(--md-on-surface);
box-shadow: var(--md-elevation-1);
border-left: 5px solid var(--md-error);
font-size: 1.5vh;
}
}
.success { .success {
background-color: var(--md-success-container); --accent: #006e1c;
color: var(--md-on-success-container);
padding: 12px 16px;
font-weight: var(--font-weight-medium);
} }
.primary { .primary {
background-color: var(--md-primary-container); --accent: #0061a4;
color: var(--md-on-primary-container);
padding: 12px 16px;
font-weight: var(--font-weight-medium);
} }
.warning { .warning {
background-color: var(--md-warning-container); --accent: #7d5800;
color: var(--md-on-warning-container);
padding: 12px 16px;
font-weight: var(--font-weight-medium);
} }
.error { .error {
background-color: var(--md-error-container); --accent: #ba1a1a;
color: var(--md-on-error-container);
padding: 12px 16px;
font-weight: var(--font-weight-medium);
} }
.police { .police {
background-color: var(--md-info-container); --accent: #0062a1;
color: var(--md-on-info-container); }
padding: 12px 16px; .ambulance {
font-weight: var(--font-weight-medium); --accent: #006874;
} }
.success,
.primary,
.warning,
.error,
.police,
.ambulance { .ambulance {
background-color: var(--md-error-container); background-color: #211f26;
color: var(--md-on-error-container); color: #e6e1e5;
padding: 12px 16px; padding: 12px 16px;
font-weight: var(--font-weight-medium); font-weight: var(--font-weight-medium);
box-shadow: var(--md-elevation-1);
}
.notify-item {
display: flex;
align-items: flex-start;
gap: 10px;
border-radius: 0;
min-width: 280px;
max-width: 400px;
opacity: 0;
transform: translateX(16px);
transition:
opacity 0.2s ease,
transform 0.2s ease;
position: relative;
overflow: hidden;
}
.notify-item.notify-show {
opacity: 1;
transform: translateX(0);
}
.notify-item.notify-hide {
opacity: 0;
transform: translateX(16px);
transition:
opacity 0.3s ease,
transform 0.3s ease;
}
.notify-icon {
color: var(--accent);
flex-shrink: 0;
font-size: 20px;
margin-top: 1px;
}
.notify-content {
flex: 1;
min-width: 0;
}
.notify-message {
font-size: clamp(12px, 1.1vw, 16px);
line-height: 1.4;
}
.notify-multiline {
white-space: pre-wrap;
word-break: break-word;
}
.notify-caption {
font-size: clamp(10px, 0.9vw, 14px);
margin-top: 2px;
opacity: 0.75;
}
.notify-progress {
position: absolute;
bottom: 0;
left: 0;
height: 3px;
width: 100%;
background: var(--accent);
opacity: 0.35;
animation: notify-shrink linear forwards;
}
@keyframes notify-shrink {
from {
width: 100%;
}
to {
width: 0%;
}
} }
+8 -8
View File
@@ -1,18 +1,18 @@
<!doctype html>
<html> <html>
<head> <head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>qb-core</title>
<link href="https://fonts.googleapis.com/css2?family=Exo+2:wght@300;400;500;600;700&display=swap" rel="stylesheet" /> <link href="https://fonts.googleapis.com/css2?family=Exo+2:wght@300;400;500;600;700&display=swap" rel="stylesheet" />
<link href="https://cdn.jsdelivr.net/npm/quasar@2.12.0/dist/quasar.prod.css" rel="stylesheet" type="text/css" /> <link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Material+Icons" />
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css" /> <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css" />
<link href="https://fonts.googleapis.com/css?family=Roboto:100,300,400,500,700,900|Material+Icons" rel="stylesheet" type="text/css" /> <link rel="stylesheet" href="css/style.css" />
<link href="css/style.css" rel="stylesheet" />
<script src="https://cdn.jsdelivr.net/npm/vue@3/dist/vue.global.prod.js" defer></script>
<script src="https://cdn.jsdelivr.net/npm/quasar@2.12.0/dist/quasar.umd.prod.js" defer></script>
<script type="module" src="js/app.js"></script>
<script src="js/drawtext.js"></script>
<link rel="stylesheet" href="css/drawtext.css" /> <link rel="stylesheet" href="css/drawtext.css" />
<script type="module" src="js/app.js"></script>
<script src="js/drawtext.js" defer></script>
</head> </head>
<body> <body>
<div id="q-app" style="min-height: 100vh"></div>
<div id="drawtext-container"> <div id="drawtext-container">
<div id="text" class="text"></div> <div id="text" class="text"></div>
</div> </div>
+104 -49
View File
@@ -1,65 +1,120 @@
import { determineStyleFromVariant, fetchNotifyConfig, NOTIFY_CONFIG } from "./config.js"; let NOTIFY_CONFIG = null;
const { useQuasar } = Quasar; const defaultConfig = {
const { onMounted, onUnmounted } = Vue; NotificationStyling: {
group: false,
position: "right",
progress: true,
},
VariantDefinitions: {
success: { classes: "success", icon: "check_circle" },
primary: { classes: "primary", icon: "notifications" },
warning: { classes: "warning", icon: "warning" },
error: { classes: "error", icon: "error" },
police: { classes: "police", icon: "local_police" },
ambulance: { classes: "ambulance", icon: "fas fa-ambulance" },
},
};
const fetchNui = async (evName, data) => { const fetchNui = async (evName, data) => {
const resourceName = window.GetParentResourceName(); const resourceName = window.GetParentResourceName();
const resp = await fetch(`https://${resourceName}/${evName}`, {
const rawResp = await fetch(`https://${resourceName}/${evName}`, {
body: JSON.stringify(data), body: JSON.stringify(data),
headers: { headers: { "Content-Type": "application/json; charset=UTF8" },
"Content-Type": "application/json; charset=UTF8",
},
method: "POST", method: "POST",
}); });
return resp.json();
return await rawResp.json();
}; };
window.fetchNui = fetchNui; const determineStyleFromVariant = (variant) => {
return NOTIFY_CONFIG.VariantDefinitions[variant] ?? NOTIFY_CONFIG.VariantDefinitions["primary"];
};
const app = Vue.createApp({ const fetchNotifyConfig = async () => {
setup() { try {
const $q = useQuasar(); NOTIFY_CONFIG = await fetchNui("getNotifyConfig", {});
if (!NOTIFY_CONFIG) NOTIFY_CONFIG = defaultConfig;
} catch (error) {
console.error("Failed to fetch notification config, using default", error);
NOTIFY_CONFIG = defaultConfig;
}
};
const showNotif = async ({ data }) => { const POSITION_MAP = {
if (data?.action !== "notify") return; "top-left": { top: "16px", left: "16px" },
"top-right": { top: "16px", right: "16px" },
top: { top: "16px", left: "50%", transform: "translateX(-50%)" },
"bottom-left": { bottom: "16px", left: "16px" },
"bottom-right": { bottom: "16px", right: "16px" },
bottom: { bottom: "16px", left: "50%", transform: "translateX(-50%)" },
left: { top: "50%", left: "16px", transform: "translateY(-50%)" },
right: { top: "50%", right: "16px", transform: "translateY(-50%)" },
center: { top: "50%", left: "50%", transform: "translate(-50%, -50%)" },
};
const { text, length, type, caption, icon: dataIcon } = data; let container = null;
let { classes, icon } = determineStyleFromVariant(type);
if (dataIcon) { const getContainer = () => {
icon = dataIcon; if (container) return container;
} const pos = NOTIFY_CONFIG.NotificationStyling.position ?? "right";
const isBottom = pos.startsWith("bottom");
container = document.createElement("div");
container.id = "notify-container";
Object.assign(container.style, {
position: "fixed",
display: "flex",
flexDirection: isBottom ? "column-reverse" : "column",
gap: "8px",
zIndex: "9999",
maxWidth: "400px",
pointerEvents: "none",
...(POSITION_MAP[pos] ?? POSITION_MAP["right"]),
});
document.body.appendChild(container);
return container;
};
if (!NOTIFY_CONFIG) { const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
console.error("The notification config did not load properly, trying again for next time");
await fetchNotifyConfig();
if (NOTIFY_CONFIG) return showNotif({ data });
}
$q.notify({ const showNotif = async ({ data }) => {
message: text, if (data?.action !== "notify") return;
multiLine: text.length > 100,
group: NOTIFY_CONFIG.NotificationStyling.group ?? false,
progress: NOTIFY_CONFIG.NotificationStyling.progress ?? true,
position: NOTIFY_CONFIG.NotificationStyling.position ?? "right",
timeout: length,
caption,
classes,
icon,
});
};
onMounted(() => {
window.addEventListener("message", showNotif);
});
onUnmounted(() => {
window.removeEventListener("message", showNotif);
});
return {};
},
});
app.use(Quasar, { config: {} }); if (!NOTIFY_CONFIG) {
app.mount("#q-app"); await fetchNotifyConfig();
}
const { text: message, type, length: duration = 5000, caption } = data;
const style = determineStyleFromVariant(type ?? "primary");
const showProgress = NOTIFY_CONFIG.NotificationStyling.progress && duration > 0;
const isFa = style.icon.startsWith("fa");
const iconHtml = isFa ? `<i class="notify-icon ${style.icon}"></i>` : `<span class="notify-icon material-icons">${style.icon}</span>`;
const item = document.createElement("div");
item.className = `notify-item ${style.classes}`;
item.innerHTML = `
${iconHtml}
<div class="notify-content">
<div class="notify-message${!caption ? " notify-multiline" : ""}">${message}</div>
${caption ? `<div class="notify-caption">${caption}</div>` : ""}
</div>
${showProgress ? `<div class="notify-progress" style="animation-duration:${duration}ms"></div>` : ""}
`;
const c = getContainer();
c.appendChild(item);
await sleep(10);
item.classList.add("notify-show");
if (duration > 0) {
setTimeout(() => {
item.classList.remove("notify-show");
item.classList.add("notify-hide");
setTimeout(() => item.remove(), 350);
}, duration);
}
};
window.addEventListener("message", showNotif);
window.addEventListener("load", fetchNotifyConfig);
-53
View File
@@ -1,53 +0,0 @@
export let NOTIFY_CONFIG = null;
const defaultConfig = {
NotificationStyling: {
group: true,
position: "top-right",
progress: true,
},
VariantDefinitions: {
success: {
classes: "success",
icon: "done",
},
primary: {
classes: "primary",
icon: "info",
},
error: {
classes: "error",
icon: "dangerous",
},
police: {
classes: "police",
icon: "local_police",
},
ambulance: {
classes: "ambulance",
icon: "fas fa-ambulance",
},
},
};
export const determineStyleFromVariant = (variant) => {
const variantData = NOTIFY_CONFIG.VariantDefinitions[variant];
if (!variantData) throw new Error(`Style of type: ${variant}, does not exist in the config`);
return variantData;
};
export const fetchNotifyConfig = async () => {
try {
NOTIFY_CONFIG = await window.fetchNui("getNotifyConfig", {});
if (!NOTIFY_CONFIG) {
NOTIFY_CONFIG = defaultConfig;
}
} catch (error) {
console.error("Failed to fetch notification config, using default", error);
NOTIFY_CONFIG = defaultConfig;
}
};
window.addEventListener("load", async () => {
await fetchNotifyConfig();
});
+129 -129
View File
@@ -1,129 +1,129 @@
local Translations = { local Translations = {
error = { error = {
not_online = 'ผู้เล่นไม่ออนไลน์', not_online = 'ผู้เล่นไม่ออนไลน์',
wrong_format = 'รูปแบบไม่ถูกต้อง', wrong_format = 'รูปแบบไม่ถูกต้อง',
missing_args = 'ตัวแปรที่ต้องการไม่ถูกกำหนด (x, y, z)', missing_args = 'ตัวแปรที่ต้องการไม่ถูกกำหนด (x, y, z)',
missing_args2 = 'ต้องกำหนดตัวแปรทุกตัวให้ครบ!', missing_args2 = 'ต้องกำหนดตัวแปรทุกตัวให้ครบ!',
no_access = 'ไม่สามารถเข้าถึงคำสั่งนี้ได้', no_access = 'ไม่สามารถเข้าถึงคำสั่งนี้ได้',
company_too_poor = 'บริษัทของคุณกำลังจะล้มละลาย', company_too_poor = 'บริษัทของคุณกำลังจะล้มละลาย',
item_not_exist = 'ไม่มีไอเทมนี้', item_not_exist = 'ไม่มีไอเทมนี้',
too_heavy = 'กระเป๋าเต็มแล้ว', too_heavy = 'กระเป๋าเต็มแล้ว',
location_not_exist = 'ไม่มีตำแหน่งนี้', location_not_exist = 'ไม่มีตำแหน่งนี้',
duplicate_license = 'พบใบอนุญาต Rockstar ซ้ำ', duplicate_license = 'พบใบอนุญาต Rockstar ซ้ำ',
no_valid_license = 'ไม่พบใบอนุญาต Rockstar ที่ถูกต้อง', no_valid_license = 'ไม่พบใบอนุญาต Rockstar ที่ถูกต้อง',
not_whitelisted = 'คุณไม่ได้รับอนุญาตให้เข้าใช้เซิร์ฟเวอร์นี้', not_whitelisted = 'คุณไม่ได้รับอนุญาตให้เข้าใช้เซิร์ฟเวอร์นี้',
server_already_open = 'เซิร์ฟเวอร์ถูกเปิดแล้ว', server_already_open = 'เซิร์ฟเวอร์ถูกเปิดแล้ว',
server_already_closed = 'เซิร์ฟเวอร์ถูกปิดแล้ว', server_already_closed = 'เซิร์ฟเวอร์ถูกปิดแล้ว',
no_permission = 'คุณไม่ได้รับสิทธิ์ในการใช้คำสั่งนี้..', no_permission = 'คุณไม่ได้รับสิทธิ์ในการใช้คำสั่งนี้..',
no_waypoint = 'ไม่มีจุดเส้นทางที่ตั้ง', no_waypoint = 'ไม่มีจุดเส้นทางที่ตั้ง',
tp_error = 'ข้อผิดพลาดขณะวาร์ป', tp_error = 'ข้อผิดพลาดขณะวาร์ป',
connecting_database_error = 'เกิดข้อผิดพลาดในการเชื่อมต่อกับเซิร์ฟเวอร์ (SQL server เปิดอยู่ไหม?)', connecting_database_error = 'เกิดข้อผิดพลาดในการเชื่อมต่อกับเซิร์ฟเวอร์ (SQL server เปิดอยู่ไหม?)',
connecting_database_timeout = 'เชื่อมต่อกับฐานข้อมูลเกิด timeout (SQL server เปิดอยู่ไหม?' connecting_database_timeout = 'เชื่อมต่อกับฐานข้อมูลเกิด timeout (SQL server เปิดอยู่ไหม?'
}, },
success = { success = {
server_opened = 'เซิฟเวอร์เปิดแล้ว', server_opened = 'เซิฟเวอร์เปิดแล้ว',
server_closed = 'เซิฟเวอร์เปิดแล้ว', server_closed = 'เซิฟเวอร์เปิดแล้ว',
teleported_waypoint = 'เคลือนย้ายไปยังจุดหมาย.', teleported_waypoint = 'เคลือนย้ายไปยังจุดหมาย.',
}, },
info = { info = {
received_paycheck = 'คุณได้รับเงินเดือน $%{value}', received_paycheck = 'คุณได้รับเงินเดือน $%{value}',
job_info = 'อาชีพ: %{value} | ระดับ: %{value2} | กำลังปฏิบัติหน้าที่: %{value3}', job_info = 'อาชีพ: %{value} | ระดับ: %{value2} | กำลังปฏิบัติหน้าที่: %{value3}',
gang_info = 'แก๊ง: %{value} | ระดับ: %{value2}', gang_info = 'แก๊ง: %{value} | ระดับ: %{value2}',
on_duty = 'คุณเริ่มปฏิบัติหน้าที่แล้ว!', on_duty = 'คุณเริ่มปฏิบัติหน้าที่แล้ว!',
off_duty = 'คุณออกจากการปฏิบัติหน้าที่แล้ว!', off_duty = 'คุณออกจากการปฏิบัติหน้าที่แล้ว!',
checking_ban = 'สวัสดี %s. เรากำลังตรวจสอบสถานะการโดนแบนของคุณ.', checking_ban = 'สวัสดี %s. เรากำลังตรวจสอบสถานะการโดนแบนของคุณ.',
join_server = 'ยินดีต้อนรับ %s เข้าสู่ {Server Name}.', join_server = 'ยินดีต้อนรับ %s เข้าสู่ {Server Name}.',
checking_whitelisted = 'สัวสดี %s. เรากำลังเช็ค whitelisted ของคุณ.', checking_whitelisted = 'สัวสดี %s. เรากำลังเช็ค whitelisted ของคุณ.',
exploit_banned = 'คุณโดนแบนจากการโกง. เข้าร่วม Discord สำหรับข้อมูลเพิ่มเติม: %{discord}', exploit_banned = 'คุณโดนแบนจากการโกง. เข้าร่วม Discord สำหรับข้อมูลเพิ่มเติม: %{discord}',
exploit_dropped = 'คุณโดนเตะออกจากเซิฟเวอร์ เนื่องจากเหตุผลเกี่ยวกับการใช้โปรแกรมโกง', exploit_dropped = 'คุณโดนเตะออกจากเซิฟเวอร์ เนื่องจากเหตุผลเกี่ยวกับการใช้โปรแกรมโกง',
}, },
command = { command = {
tp = { tp = {
help = 'เคลือนย้ายไปยัง ผู้เล่น หรือ ตำแหน่งพิกัด (สำหรับ Admin)', help = 'เคลือนย้ายไปยัง ผู้เล่น หรือ ตำแหน่งพิกัด (สำหรับ Admin)',
params = { params = {
x = { name = 'id/x', help = 'ไอดีของผู้เล่น หรือ พิกัด x'}, x = { name = 'id/x', help = 'ไอดีของผู้เล่น หรือ พิกัด x'},
y = { name = 'y', help = 'พิกัด y'}, y = { name = 'y', help = 'พิกัด y'},
z = { name = 'z', help = 'พิกัด z'}, z = { name = 'z', help = 'พิกัด z'},
}, },
}, },
tpm = { help = 'เคลือนย้ายไปบังจุดมาร์ค (สำหรับ Admin)' }, tpm = { help = 'เคลือนย้ายไปบังจุดมาร์ค (สำหรับ Admin)' },
togglepvp = { help = 'เปิดใช้ pvp ในเซิฟเวอร์ (สำหรับ Admin)' }, togglepvp = { help = 'เปิดใช้ pvp ในเซิฟเวอร์ (สำหรับ Admin)' },
addpermission = { addpermission = {
help = 'ให้สิทธิ์กับผู้เล่น (สำหรับ God)', help = 'ให้สิทธิ์กับผู้เล่น (สำหรับ God)',
params = { params = {
id = { name = 'id', help = 'ไอดีของผู้เล่น' }, id = { name = 'id', help = 'ไอดีของผู้เล่น' },
permission = { name = 'permission', help = 'ระดับของสิทธิ์' }, permission = { name = 'permission', help = 'ระดับของสิทธิ์' },
}, },
}, },
removepermission = { removepermission = {
help = 'ลบสิทธิ์ของผู้เล่น (สำหรับ God)', help = 'ลบสิทธิ์ของผู้เล่น (สำหรับ God)',
params = { params = {
id = { name = 'id', help = 'ID of player' }, id = { name = 'id', help = 'ID of player' },
permission = { name = 'permission', help = 'ระดับของสิทธิ์' }, permission = { name = 'permission', help = 'ระดับของสิทธิ์' },
}, },
}, },
openserver = { help = 'เปิดเซิฟเวอร์สำหรับทุกคน (สำหรับ Admin)' }, openserver = { help = 'เปิดเซิฟเวอร์สำหรับทุกคน (สำหรับ Admin)' },
closeserver = { closeserver = {
help = 'ปิดเซิฟเวอร์สำหรับทุกคนที่ไม่มีสิทธิ์การเข้าถึง (สำหรับ Admin)', help = 'ปิดเซิฟเวอร์สำหรับทุกคนที่ไม่มีสิทธิ์การเข้าถึง (สำหรับ Admin)',
params = { params = {
reason = { name = 'reason', help = 'เหตุผลที่ปิด (ถ้ามี)' }, reason = { name = 'reason', help = 'เหตุผลที่ปิด (ถ้ามี)' },
}, },
}, },
car = { car = {
help = 'เรียกยานพาหนะ (สำหรับ Admin)', help = 'เรียกยานพาหนะ (สำหรับ Admin)',
params = { params = {
model = { name = 'model', help = 'ชื่อของยานพาหนะ' }, model = { name = 'model', help = 'ชื่อของยานพาหนะ' },
}, },
}, },
dv = { help = 'ลบยานพาหนะ (สำหรับ Admin)' }, dv = { help = 'ลบยานพาหนะ (สำหรับ Admin)' },
givemoney = { givemoney = {
help = 'ให้เงินกับผู้เล่น (สำหรับ Admin)', help = 'ให้เงินกับผู้เล่น (สำหรับ Admin)',
params = { params = {
id = { name = 'id', help = 'ไอดีของผู้เล่น' }, id = { name = 'id', help = 'ไอดีของผู้เล่น' },
moneytype = { name = 'moneytype', help = 'ประเภทของเงิน (เงินสด, เงินฝาก, คริปโต)' }, moneytype = { name = 'moneytype', help = 'ประเภทของเงิน (เงินสด, เงินฝาก, คริปโต)' },
amount = { name = 'amount', help = 'จำนวนเงิน' }, amount = { name = 'amount', help = 'จำนวนเงิน' },
}, },
}, },
setmoney = { setmoney = {
help = 'กำหนดจำนวนเงินของผู้เล่น (สำหรับ Admin)', help = 'กำหนดจำนวนเงินของผู้เล่น (สำหรับ Admin)',
params = { params = {
id = { name = 'id', help = 'ไอดีของผู้เล่น' }, id = { name = 'id', help = 'ไอดีของผู้เล่น' },
moneytype = { name = 'moneytype', help = 'ประเภทของเงิน (เงินสด, เงินฝาก, คริปโต)' }, moneytype = { name = 'moneytype', help = 'ประเภทของเงิน (เงินสด, เงินฝาก, คริปโต)' },
amount = { name = 'amount', help = 'จำนวนเงิน' }, amount = { name = 'amount', help = 'จำนวนเงิน' },
}, },
}, },
job = { help = 'ตรวจสอบอาชีพของคุณ' }, job = { help = 'ตรวจสอบอาชีพของคุณ' },
setjob = { setjob = {
help = 'กำหนดอาชีพของผู้เล่น (สำหรับ Admin)', help = 'กำหนดอาชีพของผู้เล่น (สำหรับ Admin)',
params = { params = {
id = { name = 'id', help = 'ไอดีของผู้เล่น' }, id = { name = 'id', help = 'ไอดีของผู้เล่น' },
job = { name = 'job', help = 'ชื่ออาชีพ่' }, job = { name = 'job', help = 'ชื่ออาชีพ่' },
grade = { name = 'grade', help = 'ระดับ' }, grade = { name = 'grade', help = 'ระดับ' },
}, },
}, },
gang = { help = 'ตรวจสอบแก๊งของคุณ' }, gang = { help = 'ตรวจสอบแก๊งของคุณ' },
setgang = { setgang = {
help = 'กำหนดแก๊งของผู้เล่น (สำหรับ Admin)', help = 'กำหนดแก๊งของผู้เล่น (สำหรับ Admin)',
params = { params = {
id = { name = 'id', help = 'ไอดีของผู้เล่น' }, id = { name = 'id', help = 'ไอดีของผู้เล่น' },
gang = { name = 'gang', help = 'ชื่อแก๊ง' }, gang = { name = 'gang', help = 'ชื่อแก๊ง' },
grade = { name = 'grade', help = 'ระดับ' }, grade = { name = 'grade', help = 'ระดับ' },
}, },
}, },
ooc = { help = 'ข้อความ OOC' }, ooc = { help = 'ข้อความ OOC' },
me = { me = {
help = 'แสดงข้อความใกล้เขียง', help = 'แสดงข้อความใกล้เขียง',
params = { params = {
message = { name = 'message', help = 'ข้อความที่จะส่ง' } message = { name = 'message', help = 'ข้อความที่จะส่ง' }
}, },
}, },
} }
} }
if GetConvar('qb_locale', 'en') == 'th' then if GetConvar('qb_locale', 'en') == 'th' then
Lang = Locale:new({ Lang = Locale:new({
phrases = Translations, phrases = Translations,
warnOnMissing = true, warnOnMissing = true,
fallbackLang = Lang, fallbackLang = Lang,
}) })
end end
+13 -9
View File
@@ -92,7 +92,7 @@ QBCore.Commands.Add('tp', Lang:t('command.tp.help'), { { name = Lang:t('command.
TriggerClientEvent('QBCore:Notify', source, Lang:t('error.not_online'), 'error') TriggerClientEvent('QBCore:Notify', source, Lang:t('error.not_online'), 'error')
end end
else else
local location = QBShared.Locations[args[1]] local location = QBCore.Shared.Locations[args[1]]
if location then if location then
TriggerClientEvent('QBCore:Command:TeleportToCoords', source, location.x, location.y, location.z, location.w) TriggerClientEvent('QBCore:Command:TeleportToCoords', source, location.x, location.y, location.z, location.w)
else else
@@ -101,12 +101,12 @@ QBCore.Commands.Add('tp', Lang:t('command.tp.help'), { { name = Lang:t('command.
end end
else else
if args[1] and args[2] and args[3] then if args[1] and args[2] and args[3] then
local x = tonumber((args[1]:gsub(',', ''))) + .0 local x = tonumber((args[1]:gsub(',', '')))
local y = tonumber((args[2]:gsub(',', ''))) + .0 local y = tonumber((args[2]:gsub(',', '')))
local z = tonumber((args[3]:gsub(',', ''))) + .0 local z = tonumber((args[3]:gsub(',', '')))
local heading = args[4] and tonumber((args[4]:gsub(',', ''))) + .0 or false local heading = args[4] and tonumber((args[4]:gsub(',', ''))) or false
if x ~= 0 and y ~= 0 and z ~= 0 then if x and y and z then
TriggerClientEvent('QBCore:Command:TeleportToCoords', source, x, y, z, heading) TriggerClientEvent('QBCore:Command:TeleportToCoords', source, x + .0, y + .0, z + .0, heading and heading + .0 or false)
else else
TriggerClientEvent('QBCore:Notify', source, Lang:t('error.wrong_format'), 'error') TriggerClientEvent('QBCore:Notify', source, Lang:t('error.wrong_format'), 'error')
end end
@@ -240,7 +240,9 @@ end, 'admin')
-- Job -- Job
QBCore.Commands.Add('job', Lang:t('command.job.help'), {}, false, function(source) QBCore.Commands.Add('job', Lang:t('command.job.help'), {}, false, function(source)
local PlayerJob = QBCore.Functions.GetPlayer(source).PlayerData.job local Player = QBCore.Functions.GetPlayer(source)
if not Player then return end
local PlayerJob = Player.PlayerData.job
TriggerClientEvent('QBCore:Notify', source, Lang:t('info.job_info', { value = PlayerJob.label, value2 = PlayerJob.grade.name, value3 = PlayerJob.onduty })) TriggerClientEvent('QBCore:Notify', source, Lang:t('info.job_info', { value = PlayerJob.label, value2 = PlayerJob.grade.name, value3 = PlayerJob.onduty }))
end, 'user') end, 'user')
@@ -256,7 +258,9 @@ end, 'admin')
-- Gang -- Gang
QBCore.Commands.Add('gang', Lang:t('command.gang.help'), {}, false, function(source) QBCore.Commands.Add('gang', Lang:t('command.gang.help'), {}, false, function(source)
local PlayerGang = QBCore.Functions.GetPlayer(source).PlayerData.gang local Player = QBCore.Functions.GetPlayer(source)
if not Player then return end
local PlayerGang = Player.PlayerData.gang
TriggerClientEvent('QBCore:Notify', source, Lang:t('info.gang_info', { value = PlayerGang.label, value2 = PlayerGang.grade.name })) TriggerClientEvent('QBCore:Notify', source, Lang:t('info.gang_info', { value = PlayerGang.label, value2 = PlayerGang.grade.name }))
end, 'user') end, 'user')
+18 -12
View File
@@ -9,36 +9,42 @@ local function tPrint(tbl, indent)
print(formatting) print(formatting)
tPrint(v, indent + 1) tPrint(v, indent + 1)
elseif tblType == 'boolean' then elseif tblType == 'boolean' then
print(('%s^1 %s ^0'):format(formatting, v)) print(('%s ^1%s^0'):format(formatting, v))
elseif tblType == 'function' then elseif tblType == 'function' then
print(('%s^9 %s ^0'):format(formatting, v)) print(('%s ^9%s^0'):format(formatting, v))
elseif tblType == 'number' then elseif tblType == 'number' then
print(('%s^5 %s ^0'):format(formatting, v)) print(('%s ^5%s^0'):format(formatting, v))
elseif tblType == 'string' then elseif tblType == 'string' then
print(("%s ^2'%s' ^0"):format(formatting, v)) print(('%s ^2\'%s\'^0'):format(formatting, v))
elseif tblType == 'nil' then
print(('%s ^8nil^0'):format(formatting))
else else
print(('%s^2 %s ^0'):format(formatting, v)) print(('%s ^2%s^0'):format(formatting, tostring(v)))
end end
end end
else else
print(('%s ^0%s'):format(string.rep(' ', indent), tbl)) print(('%s ^0%s'):format(string.rep(' ', indent), tostring(tbl)))
end end
end end
RegisterServerEvent('QBCore:DebugSomething', function(tbl, indent, resource) RegisterNetEvent('QBCore:DebugSomething', function(tbl, indent, resource)
print(('\x1b[4m\x1b[36m[ %s : DEBUG]\x1b[0m'):format(resource)) resource = resource or 'unknown'
print(('^4[ %s : DEBUG]^0'):format(resource))
tPrint(tbl, indent) tPrint(tbl, indent)
print('\x1b[4m\x1b[36m[ END DEBUG ]\x1b[0m') print('^4[ END DEBUG ]^0')
end) end)
function QBCore.Debug(tbl, indent) function QBCore.Debug(tbl, indent)
TriggerEvent('QBCore:DebugSomething', tbl, indent, GetInvokingResource() or 'qb-core') local resource = GetInvokingResource() or 'qb-core'
print(('^4[ %s : DEBUG]^0'):format(resource))
tPrint(tbl, indent)
print('^4[ END DEBUG ]^0')
end end
function QBCore.ShowError(resource, msg) function QBCore.ShowError(resource, msg)
print('\x1b[31m[' .. resource .. ':ERROR]\x1b[0m ' .. msg) print(('^1[%s:ERROR]^0 %s'):format(resource, msg))
end end
function QBCore.ShowSuccess(resource, msg) function QBCore.ShowSuccess(resource, msg)
print('\x1b[32m[' .. resource .. ':LOG]\x1b[0m ' .. msg) print(('^2[%s:LOG]^0 %s'):format(resource, msg))
end end
+39 -34
View File
@@ -10,16 +10,18 @@ end)
AddEventHandler('playerDropped', function(reason) AddEventHandler('playerDropped', function(reason)
local src = source local src = source
if not QBCore.Players[src] then return end if not QBCore.Players[src] then return end
local Player = QBCore.Players[src] local player = QBCore.Players[src]
TriggerEvent('qb-log:server:CreateLog', 'joinleave', 'Dropped', 'red', '**' .. GetPlayerName(src) .. '** (' .. Player.PlayerData.license .. ') left..' .. '\n **Reason:** ' .. reason) TriggerEvent('qb-log:server:CreateLog', 'joinleave', 'Dropped', 'red', '**' .. GetPlayerName(src) .. '** (' .. player.PlayerData.license .. ') left..' .. '\n **Reason:** ' .. reason)
TriggerEvent('QBCore:Server:PlayerDropped', Player) player.Functions.Save()
Player.Functions.Save() TriggerEvent('QBCore:Server:PlayerDropped', src)
QBCore.Player_Buckets[Player.PlayerData.license] = nil TriggerEvent('QBCore:Server:OnPlayerUnload', src)
QBCore.Player_Buckets[player.PlayerData.license] = nil
QBCore.PlayersByCitizenId[player.PlayerData.citizenid] = nil
QBCore.Players[src] = nil QBCore.Players[src] = nil
end) end)
AddEventHandler("onResourceStop", function(resName) AddEventHandler('onResourceStop', function(resName)
for i,v in pairs(QBCore.UsableItems) do for i, v in pairs(QBCore.UsableItems) do
if v.resource == resName then if v.resource == resName then
QBCore.UsableItems[i] = nil QBCore.UsableItems[i] = nil
end end
@@ -36,7 +38,7 @@ if readyFunction ~= nil then
local DatabaseInfo = QBCore.Functions.GetDatabaseInfo() local DatabaseInfo = QBCore.Functions.GetDatabaseInfo()
if not DatabaseInfo or not DatabaseInfo.exists then return end 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}) 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 if result and result[1] then
bansTableExists = true bansTableExists = true
end end
@@ -124,7 +126,7 @@ end)
-- Client Callback -- Client Callback
RegisterNetEvent('QBCore:Server:TriggerClientCallback', function(name, ...) RegisterNetEvent('QBCore:Server:TriggerClientCallback', function(name, ...)
local ClientCallback = QBCore.ClientCallbacks[name..source] local ClientCallback = QBCore.ClientCallbacks[name .. source]
if ClientCallback then if ClientCallback then
ClientCallback.promise:resolve(...) ClientCallback.promise:resolve(...)
@@ -132,7 +134,7 @@ RegisterNetEvent('QBCore:Server:TriggerClientCallback', function(name, ...)
ClientCallback.callback(...) ClientCallback.callback(...)
end end
QBCore.ClientCallbacks[name..source] = nil QBCore.ClientCallbacks[name .. source] = nil
end end
end) end)
@@ -149,8 +151,12 @@ end)
-- Player -- Player
local updateCooldowns = {}
RegisterNetEvent('QBCore:UpdatePlayer', function() RegisterNetEvent('QBCore:UpdatePlayer', function()
local src = source 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) local Player = QBCore.Functions.GetPlayer(src)
if not Player then return end if not Player then return end
local newHunger = Player.PlayerData.metadata['hunger'] - QBCore.Config.Player.HungerRate local newHunger = Player.PlayerData.metadata['hunger'] - QBCore.Config.Player.HungerRate
@@ -161,8 +167,9 @@ RegisterNetEvent('QBCore:UpdatePlayer', function()
if newThirst <= 0 then if newThirst <= 0 then
newThirst = 0 newThirst = 0
end end
Player.Functions.SetMetaData('thirst', newThirst) Player.PlayerData.metadata['hunger'] = newHunger
Player.Functions.SetMetaData('hunger', newHunger) Player.PlayerData.metadata['thirst'] = newThirst
Player.Functions.UpdateClient('metadata', Player.PlayerData.metadata)
TriggerClientEvent('hud:client:UpdateNeeds', src, newHunger, newThirst) TriggerClientEvent('hud:client:UpdateNeeds', src, newHunger, newThirst)
Player.Functions.Save() Player.Functions.Save()
end) end)
@@ -183,6 +190,24 @@ RegisterNetEvent('QBCore:ToggleDuty', function()
TriggerClientEvent('QBCore:Client:SetDuty', src, Player.PlayerData.job.onduty) TriggerClientEvent('QBCore:Client:SetDuty', src, Player.PlayerData.job.onduty)
end) 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 -- BaseEvents
-- Vehicles -- Vehicles
@@ -224,26 +249,6 @@ RegisterServerEvent('baseevents:leftVehicle', function(veh, seat, modelName)
TriggerClientEvent('QBCore:Client:VehicleInfo', src, data) TriggerClientEvent('QBCore:Client:VehicleInfo', src, data)
end) end)
-- Items
-- This event is exploitable and should not be used. It has been deprecated, and will be removed soon.
RegisterNetEvent('QBCore:Server:UseItem', function(item)
print(string.format('%s triggered QBCore:Server:UseItem by ID %s with the following data. This event is deprecated due to exploitation, and will be removed soon. Check qb-inventory for the right use on this event.', GetInvokingResource(), source))
QBCore.Debug(item)
end)
-- This event is exploitable and should not be used. It has been deprecated, and will be removed soon. function(itemName, amount, slot)
RegisterNetEvent('QBCore:Server:RemoveItem', function(itemName, amount)
local src = source
print(string.format('%s triggered QBCore:Server:RemoveItem by ID %s for %s %s. This event is deprecated due to exploitation, and will be removed soon. Adjust your events accordingly to do this server side with player functions.', GetInvokingResource(), src, amount, itemName))
end)
-- This event is exploitable and should not be used. It has been deprecated, and will be removed soon. function(itemName, amount, slot, info)
RegisterNetEvent('QBCore:Server:AddItem', function(itemName, amount)
local src = source
print(string.format('%s triggered QBCore:Server:AddItem by ID %s for %s %s. This event is deprecated due to exploitation, and will be removed soon. Adjust your events accordingly to do this server side with player functions.', GetInvokingResource(), src, amount, itemName))
end)
-- Non-Chat Command Calling (ex: qb-adminmenu) -- Non-Chat Command Calling (ex: qb-adminmenu)
RegisterNetEvent('QBCore:CallCommand', function(command, args) RegisterNetEvent('QBCore:CallCommand', function(command, args)
@@ -269,7 +274,7 @@ end)
-- convert it to a vehicle via the NetToVeh native -- convert it to a vehicle via the NetToVeh native
QBCore.Functions.CreateCallback('QBCore:Server:SpawnVehicle', function(source, cb, model, coords, warp) QBCore.Functions.CreateCallback('QBCore:Server:SpawnVehicle', function(source, cb, model, coords, warp)
local veh = QBCore.Functions.SpawnVehicle(source, model, coords, warp) local veh = QBCore.Functions.SpawnVehicle(source, model, coords, warp)
cb(NetworkGetNetworkIdFromEntity(veh)) cb(DoesEntityExist(veh) and NetworkGetNetworkIdFromEntity(veh) or nil)
end) end)
-- Use this for long distance vehicle spawning -- Use this for long distance vehicle spawning
@@ -278,7 +283,7 @@ end)
-- convert it to a vehicle via the NetToVeh native -- convert it to a vehicle via the NetToVeh native
QBCore.Functions.CreateCallback('QBCore:Server:CreateVehicle', function(source, cb, model, coords, warp) QBCore.Functions.CreateCallback('QBCore:Server:CreateVehicle', function(source, cb, model, coords, warp)
local veh = QBCore.Functions.CreateAutomobile(source, model, coords, warp) local veh = QBCore.Functions.CreateAutomobile(source, model, coords, warp)
cb(NetworkGetNetworkIdFromEntity(veh)) cb(DoesEntityExist(veh) and NetworkGetNetworkIdFromEntity(veh) or nil)
end) end)
--QBCore.Functions.CreateCallback('QBCore:HasItem', function(source, cb, items, amount) --QBCore.Functions.CreateCallback('QBCore:HasItem', function(source, cb, items, amount)
+21 -69
View File
@@ -3,11 +3,11 @@ local function SetMethod(methodName, handler)
if type(methodName) ~= 'string' then if type(methodName) ~= 'string' then
return false, 'invalid_method_name' return false, 'invalid_method_name'
end end
if QBCore.Functions[methodName] ~= nil then
return false, 'method_exists'
end
QBCore.Functions[methodName] = handler QBCore.Functions[methodName] = handler
TriggerEvent('QBCore:Server:UpdateObject') TriggerEvent('QBCore:Server:UpdateObject')
return true, 'success' return true, 'success'
end end
@@ -19,11 +19,11 @@ local function SetField(fieldName, data)
if type(fieldName) ~= 'string' then if type(fieldName) ~= 'string' then
return false, 'invalid_field_name' return false, 'invalid_field_name'
end end
if QBCore[fieldName] ~= nil then
return false, 'field_exists'
end
QBCore[fieldName] = data QBCore[fieldName] = data
TriggerEvent('QBCore:Server:UpdateObject') TriggerEvent('QBCore:Server:UpdateObject')
return true, 'success' return true, 'success'
end end
@@ -52,32 +52,16 @@ exports('AddJob', AddJob)
-- Multiple Add Jobs -- Multiple Add Jobs
local function AddJobs(jobs) local function AddJobs(jobs)
local shouldContinue = true
local message = 'success'
local errorItem = nil
for key, value in pairs(jobs) do for key, value in pairs(jobs) do
if type(key) ~= 'string' then if type(key) ~= 'string' then return false, 'invalid_job_name', value end
message = 'invalid_job_name' if QBCore.Shared.Jobs[key] then return false, 'job_exists', value end
shouldContinue = false end
errorItem = jobs[key] for key, value in pairs(jobs) do
break
end
if QBCore.Shared.Jobs[key] then
message = 'job_exists'
shouldContinue = false
errorItem = jobs[key]
break
end
QBCore.Shared.Jobs[key] = value QBCore.Shared.Jobs[key] = value
end end
if not shouldContinue then return false, message, errorItem end
TriggerClientEvent('QBCore:Client:OnSharedUpdateMultiple', -1, 'Jobs', jobs) TriggerClientEvent('QBCore:Client:OnSharedUpdateMultiple', -1, 'Jobs', jobs)
TriggerEvent('QBCore:Server:UpdateObject') TriggerEvent('QBCore:Server:UpdateObject')
return true, message, nil return true, 'success', nil
end end
QBCore.Functions.AddJobs = AddJobs QBCore.Functions.AddJobs = AddJobs
@@ -162,32 +146,16 @@ exports('UpdateItem', UpdateItem)
-- Multiple Add Items -- Multiple Add Items
local function AddItems(items) local function AddItems(items)
local shouldContinue = true
local message = 'success'
local errorItem = nil
for key, value in pairs(items) do for key, value in pairs(items) do
if type(key) ~= 'string' then if type(key) ~= 'string' then return false, 'invalid_item_name', value end
message = 'invalid_item_name' if QBCore.Shared.Items[key] then return false, 'item_exists', value end
shouldContinue = false end
errorItem = items[key] for key, value in pairs(items) do
break
end
if QBCore.Shared.Items[key] then
message = 'item_exists'
shouldContinue = false
errorItem = items[key]
break
end
QBCore.Shared.Items[key] = value QBCore.Shared.Items[key] = value
end end
if not shouldContinue then return false, message, errorItem end
TriggerClientEvent('QBCore:Client:OnSharedUpdateMultiple', -1, 'Items', items) TriggerClientEvent('QBCore:Client:OnSharedUpdateMultiple', -1, 'Items', items)
TriggerEvent('QBCore:Server:UpdateObject') TriggerEvent('QBCore:Server:UpdateObject')
return true, message, nil return true, 'success', nil
end end
QBCore.Functions.AddItems = AddItems QBCore.Functions.AddItems = AddItems
@@ -235,32 +203,16 @@ exports('AddGang', AddGang)
-- Multiple Add Gangs -- Multiple Add Gangs
local function AddGangs(gangs) local function AddGangs(gangs)
local shouldContinue = true
local message = 'success'
local errorItem = nil
for key, value in pairs(gangs) do for key, value in pairs(gangs) do
if type(key) ~= 'string' then if type(key) ~= 'string' then return false, 'invalid_gang_name', value end
message = 'invalid_gang_name' if QBCore.Shared.Gangs[key] then return false, 'gang_exists', value end
shouldContinue = false end
errorItem = gangs[key] for key, value in pairs(gangs) do
break
end
if QBCore.Shared.Gangs[key] then
message = 'gang_exists'
shouldContinue = false
errorItem = gangs[key]
break
end
QBCore.Shared.Gangs[key] = value QBCore.Shared.Gangs[key] = value
end end
if not shouldContinue then return false, message, errorItem end
TriggerClientEvent('QBCore:Client:OnSharedUpdateMultiple', -1, 'Gangs', gangs) TriggerClientEvent('QBCore:Client:OnSharedUpdateMultiple', -1, 'Gangs', gangs)
TriggerEvent('QBCore:Server:UpdateObject') TriggerEvent('QBCore:Server:UpdateObject')
return true, message, nil return true, 'success', nil
end end
QBCore.Functions.AddGangs = AddGangs QBCore.Functions.AddGangs = AddGangs
+40 -50
View File
@@ -55,12 +55,7 @@ end
---@param citizenid string ---@param citizenid string
---@return table? ---@return table?
function QBCore.Functions.GetPlayerByCitizenId(citizenid) function QBCore.Functions.GetPlayerByCitizenId(citizenid)
for _, Player in pairs(QBCore.Players) do return QBCore.PlayersByCitizenId[citizenid]
if Player.PlayerData.citizenid == citizenid then
return Player
end
end
return nil
end end
---Get offline player by citizen id ---Get offline player by citizen id
@@ -304,16 +299,12 @@ end
---@return table|boolean ---@return table|boolean
function QBCore.Functions.GetPlayersInBucket(bucket) function QBCore.Functions.GetPlayersInBucket(bucket)
local curr_bucket_pool = {} local curr_bucket_pool = {}
if QBCore.Player_Buckets and next(QBCore.Player_Buckets) then for _, v in pairs(QBCore.Player_Buckets) do
for _, v in pairs(QBCore.Player_Buckets) do if v.bucket == bucket then
if v.bucket == bucket then curr_bucket_pool[#curr_bucket_pool + 1] = v.id
curr_bucket_pool[#curr_bucket_pool + 1] = v.id
end
end end
return curr_bucket_pool
else
return false
end end
return curr_bucket_pool
end end
---Will return an array of all the entities inside the current bucket ---Will return an array of all the entities inside the current bucket
@@ -322,16 +313,12 @@ end
---@return table|boolean ---@return table|boolean
function QBCore.Functions.GetEntitiesInBucket(bucket) function QBCore.Functions.GetEntitiesInBucket(bucket)
local curr_bucket_pool = {} local curr_bucket_pool = {}
if QBCore.Entity_Buckets and next(QBCore.Entity_Buckets) then for _, v in pairs(QBCore.Entity_Buckets) do
for _, v in pairs(QBCore.Entity_Buckets) do if v.bucket == bucket then
if v.bucket == bucket then curr_bucket_pool[#curr_bucket_pool + 1] = v.id
curr_bucket_pool[#curr_bucket_pool + 1] = v.id
end
end end
return curr_bucket_pool
else
return false
end end
return curr_bucket_pool
end end
---Server side vehicle creation with optional callback ---Server side vehicle creation with optional callback
@@ -347,14 +334,24 @@ function QBCore.Functions.SpawnVehicle(source, model, coords, warp)
if not coords then coords = GetEntityCoords(ped) end if not coords then coords = GetEntityCoords(ped) end
local heading = coords.w and coords.w or 0.0 local heading = coords.w and coords.w or 0.0
local veh = CreateVehicle(model, coords.x, coords.y, coords.z, heading, true, true) local veh = CreateVehicle(model, coords.x, coords.y, coords.z, heading, true, true)
while not DoesEntityExist(veh) do Wait(0) end local t = 0
while not DoesEntityExist(veh) and t < 1000 do
Wait(0)
t += 1
end
if warp then if warp then
while GetVehiclePedIsIn(ped) ~= veh do t = 0
while GetVehiclePedIsIn(ped) ~= veh and t < 100 do
Wait(0) Wait(0)
t += 1
TaskWarpPedIntoVehicle(ped, veh, -1) TaskWarpPedIntoVehicle(ped, veh, -1)
end end
end end
while NetworkGetEntityOwner(veh) ~= source do Wait(0) end t = 0
while NetworkGetEntityOwner(veh) ~= source and t < 1000 do
Wait(0)
t += 1
end
return veh return veh
end end
@@ -373,7 +370,11 @@ function QBCore.Functions.CreateAutomobile(source, model, coords, warp)
local heading = coords.w and coords.w or 0.0 local heading = coords.w and coords.w or 0.0
local CreateAutomobile = `CREATE_AUTOMOBILE` local CreateAutomobile = `CREATE_AUTOMOBILE`
local veh = Citizen.InvokeNative(CreateAutomobile, model, coords, heading, true, true) local veh = Citizen.InvokeNative(CreateAutomobile, model, coords, heading, true, true)
while not DoesEntityExist(veh) do Wait(0) end local t = 0
while not DoesEntityExist(veh) and t < 1000 do
Wait(0)
t += 1
end
if warp then TaskWarpPedIntoVehicle(GetPlayerPed(source), veh, -1) end if warp then TaskWarpPedIntoVehicle(GetPlayerPed(source), veh, -1) end
return veh return veh
end end
@@ -395,7 +396,11 @@ function QBCore.Functions.CreateVehicle(source, model, vehtype, coords, warp)
if not coords then coords = GetEntityCoords(GetPlayerPed(source)) end if not coords then coords = GetEntityCoords(GetPlayerPed(source)) end
local heading = coords.w and coords.w or 0.0 local heading = coords.w and coords.w or 0.0
local veh = CreateVehicleServerSetter(model, vehtype, coords, heading) local veh = CreateVehicleServerSetter(model, vehtype, coords, heading)
while not DoesEntityExist(veh) do Wait(0) end local t = 0
while not DoesEntityExist(veh) and t < 1000 do
Wait(0)
t += 1
end
if warp then TaskWarpPedIntoVehicle(GetPlayerPed(source), veh, -1) end if warp then TaskWarpPedIntoVehicle(GetPlayerPed(source), veh, -1) end
return veh return veh
end end
@@ -408,9 +413,11 @@ function PaycheckInterval()
CreateThread(function() CreateThread(function()
for _, Player in pairs(QBCore.Players) do for _, Player in pairs(QBCore.Players) do
if Player then if Player then
local payment = QBShared.Jobs[Player.PlayerData.job.name]['grades'][tostring(Player.PlayerData.job.grade.level)].payment local jobData = QBCore.Shared.Jobs[Player.PlayerData.job.name]
local gradeData = jobData and jobData['grades'][tostring(Player.PlayerData.job.grade.level)]
local payment = gradeData and gradeData.payment
if not payment then payment = Player.PlayerData.job.payment end if not payment then payment = Player.PlayerData.job.payment end
if Player.PlayerData.job and payment > 0 and (QBShared.Jobs[Player.PlayerData.job.name].offDutyPay or Player.PlayerData.job.onduty) then if Player.PlayerData.job and payment > 0 and (QBCore.Shared.Jobs[Player.PlayerData.job.name].offDutyPay or Player.PlayerData.job.onduty) then
if QBCore.Config.Money.PayCheckSociety then if QBCore.Config.Money.PayCheckSociety then
local account = exports['qb-banking']:GetAccountBalance(Player.PlayerData.job.name) local account = exports['qb-banking']:GetAccountBalance(Player.PlayerData.job.name)
if account ~= 0 then if account ~= 0 then
@@ -463,7 +470,9 @@ function QBCore.Functions.TriggerClientCallback(name, source, ...)
if cb == nil then if cb == nil then
Citizen.Await(QBCore.ClientCallbacks[name .. source].promise) Citizen.Await(QBCore.ClientCallbacks[name .. source].promise)
return QBCore.ClientCallbacks[name .. source].promise.value local value = QBCore.ClientCallbacks[name .. source].promise.value
QBCore.ClientCallbacks[name .. source] = nil
return value
end end
end end
@@ -538,7 +547,7 @@ function QBCore.Functions.Kick(source, reason, setKickReason, deferrals)
for _ = 0, 4 do for _ = 0, 4 do
while true do while true do
if source then if source then
if GetPlayerPing(source) >= 0 then if GetPlayerPing(source) < 0 then
break break
end end
CreateThread(function() CreateThread(function()
@@ -723,27 +732,8 @@ function QBCore.Functions.Notify(source, text, type, length)
TriggerClientEvent('QBCore:Notify', source, text, type, length) TriggerClientEvent('QBCore:Notify', source, text, type, length)
end end
---???? ... ok
---@param source any
---@param data any
---@param pattern any
---@return boolean
function QBCore.Functions.PrepForSQL(source, data, pattern)
data = tostring(data)
local src = source
local player = QBCore.Functions.GetPlayer(src)
local result = string.match(data, pattern)
if not result or string.len(result) ~= string.len(data) then
TriggerEvent('qb-log:server:CreateLog', 'anticheat', 'SQL Exploit Attempted', 'red', string.format('%s attempted to exploit SQL!', player.PlayerData.license))
return false
end
return true
end
for functionName, func in pairs(QBCore.Functions) do for functionName, func in pairs(QBCore.Functions) do
if type(func) == 'function' then if type(func) == 'function' then
exports(functionName, func) exports(functionName, func)
end end
end end
-- Access a specific function directly:
-- exports['qb-core']:Notify(source, 'Hello Player!')
-49
View File
@@ -1,49 +0,0 @@
QBCore = {}
QBCore.Config = QBConfig
QBCore.Shared = QBShared
QBCore.ClientCallbacks = {}
QBCore.ServerCallbacks = {}
-- Get the full QBCore object (default behavior):
-- local QBCore = GetCoreObject()
-- Get only specific parts of QBCore:
-- local QBCore = GetCoreObject({'Players', 'Config'})
local function GetCoreObject(filters)
if not filters then return QBCore end
local results = {}
for i = 1, #filters do
local key = filters[i]
if QBCore[key] then
results[key] = QBCore[key]
end
end
return results
end
exports('GetCoreObject', GetCoreObject)
local function GetSharedItems()
return QBShared.Items
end
exports('GetSharedItems', GetSharedItems)
local function GetSharedVehicles()
return QBShared.Vehicles
end
exports('GetSharedVehicles', GetSharedVehicles)
local function GetSharedWeapons()
return QBShared.Weapons
end
exports('GetSharedWeapons', GetSharedWeapons)
local function GetSharedJobs()
return QBShared.Jobs
end
exports('GetSharedJobs', GetSharedJobs)
local function GetSharedGangs()
return QBShared.Gangs
end
exports('GetSharedGangs', GetSharedGangs)
+516 -515
View File
File diff suppressed because it is too large Load Diff
+172
View File
@@ -0,0 +1,172 @@
local StringCharset = {}
local NumberCharset = {}
QBCore.Shared.StarterItems = {
['phone'] = { amount = 1, item = 'phone' },
['id_card'] = { amount = 1, item = 'id_card' },
['driver_license'] = { amount = 1, item = 'driver_license' },
}
for i = 48, 57 do NumberCharset[#NumberCharset + 1] = string.char(i) end
for i = 65, 90 do StringCharset[#StringCharset + 1] = string.char(i) end
for i = 97, 122 do StringCharset[#StringCharset + 1] = string.char(i) end
function QBCore.Shared.RandomStr(length)
if length <= 0 then return '' end
return QBCore.Shared.RandomStr(length - 1) .. StringCharset[math.random(1, #StringCharset)]
end
function QBCore.Shared.RandomInt(length)
if length <= 0 then return '' end
return QBCore.Shared.RandomInt(length - 1) .. NumberCharset[math.random(1, #NumberCharset)]
end
function QBCore.Shared.SplitStr(str, delimiter)
local result = {}
local from = 1
local delim_from, delim_to = string.find(str, delimiter, from)
while delim_from do
result[#result + 1] = string.sub(str, from, delim_from - 1)
from = delim_to + 1
delim_from, delim_to = string.find(str, delimiter, from)
end
result[#result + 1] = string.sub(str, from)
return result
end
function QBCore.Shared.Trim(value)
if not value then return nil end
return (string.gsub(value, '^%s*(.-)%s*$', '%1'))
end
function QBCore.Shared.FirstToUpper(value)
if not value then return nil end
return (value:gsub('^%l', string.upper))
end
function QBCore.Shared.Round(value, numDecimalPlaces)
if not numDecimalPlaces then return math.floor(value + 0.5) end
local power = 10 ^ numDecimalPlaces
return math.floor((value * power) + 0.5) / (power)
end
function QBCore.Shared.ChangeVehicleExtra(vehicle, extra, enable)
if DoesExtraExist(vehicle, extra) then
if enable then
SetVehicleExtra(vehicle, extra, false)
if not IsVehicleExtraTurnedOn(vehicle, extra) then
QBCore.Shared.ChangeVehicleExtra(vehicle, extra, enable)
end
else
SetVehicleExtra(vehicle, extra, true)
if IsVehicleExtraTurnedOn(vehicle, extra) then
QBCore.Shared.ChangeVehicleExtra(vehicle, extra, enable)
end
end
end
end
function QBCore.Shared.IsFunction(value)
if type(value) == 'table' then
return value.__cfx_functionReference ~= nil and type(value.__cfx_functionReference) == 'string'
end
return type(value) == 'function'
end
function QBCore.Shared.SetDefaultVehicleExtras(vehicle, config)
for i = 1, 20 do
if DoesExtraExist(vehicle, i) then
SetVehicleExtra(vehicle, i, 1)
end
end
for id, enabled in pairs(config) do
if type(enabled) ~= 'boolean' then
enabled = true
end
QBCore.Shared.ChangeVehicleExtra(vehicle, tonumber(id), enabled)
end
end
QBCore.Shared.MaleNoGloves = {
[0] = true,
[1] = true,
[2] = true,
[3] = true,
[4] = true,
[5] = true,
[6] = true,
[7] = true,
[8] = true,
[9] = true,
[10] = true,
[11] = true,
[12] = true,
[13] = true,
[14] = true,
[15] = true,
[18] = true,
[26] = true,
[52] = true,
[53] = true,
[54] = true,
[55] = true,
[56] = true,
[57] = true,
[58] = true,
[59] = true,
[60] = true,
[61] = true,
[62] = true,
[112] = true,
[113] = true,
[114] = true,
[118] = true,
[125] = true,
[132] = true
}
QBCore.Shared.FemaleNoGloves = {
[0] = true,
[1] = true,
[2] = true,
[3] = true,
[4] = true,
[5] = true,
[6] = true,
[7] = true,
[8] = true,
[9] = true,
[10] = true,
[11] = true,
[12] = true,
[13] = true,
[14] = true,
[15] = true,
[19] = true,
[59] = true,
[60] = true,
[61] = true,
[62] = true,
[63] = true,
[64] = true,
[65] = true,
[66] = true,
[67] = true,
[68] = true,
[69] = true,
[70] = true,
[71] = true,
[129] = true,
[130] = true,
[131] = true,
[135] = true,
[142] = true,
[149] = true,
[153] = true,
[157] = true,
[161] = true,
[165] = true
}
+1 -2
View File
@@ -1,5 +1,4 @@
QBShared = QBShared or {} QBCore.Shared.Gangs = {
QBShared.Gangs = {
none = { label = 'No Gang', grades = { ['0'] = { name = 'Unaffiliated' } } }, none = { label = 'No Gang', grades = { ['0'] = { name = 'Unaffiliated' } } },
lostmc = { lostmc = {
label = 'The Lost MC', label = 'The Lost MC',
+1 -2
View File
@@ -1,5 +1,4 @@
QBShared = QBShared or {} QBCore.Shared.Items = {
QBShared.Items = {
-- WEAPONS -- WEAPONS
-- Melee -- Melee
weapon_unarmed = { name = 'weapon_unarmed', label = 'Fists', weight = 1000, type = 'weapon', ammotype = nil, image = 'placeholder.png', unique = true, useable = false, description = 'Fisticuffs' }, weapon_unarmed = { name = 'weapon_unarmed', label = 'Fists', weight = 1000, type = 'weapon', ammotype = nil, image = 'placeholder.png', unique = true, useable = false, description = 'Fisticuffs' },
+2 -3
View File
@@ -1,6 +1,5 @@
QBShared = QBShared or {} QBCore.Shared.ForceJobDefaultDutyAtLogin = true -- true: Force duty state to jobdefaultDuty | false: set duty state from database last saved
QBShared.ForceJobDefaultDutyAtLogin = true -- true: Force duty state to jobdefaultDuty | false: set duty state from database last saved QBCore.Shared.Jobs = {
QBShared.Jobs = {
unemployed = { label = 'Civilian', defaultDuty = true, offDutyPay = false, grades = { ['0'] = { name = 'Freelancer', payment = 10 } } }, unemployed = { label = 'Civilian', defaultDuty = true, offDutyPay = false, grades = { ['0'] = { name = 'Freelancer', payment = 10 } } },
bus = { label = 'Bus', defaultDuty = true, offDutyPay = false, grades = { ['0'] = { name = 'Driver', payment = 50 } } }, bus = { label = 'Bus', defaultDuty = true, offDutyPay = false, grades = { ['0'] = { name = 'Driver', payment = 50 } } },
judge = { label = 'Honorary', defaultDuty = true, offDutyPay = false, grades = { ['0'] = { name = 'Judge', payment = 100 } } }, judge = { label = 'Honorary', defaultDuty = true, offDutyPay = false, grades = { ['0'] = { name = 'Judge', payment = 100 } } },
+1 -1
View File
@@ -1,4 +1,4 @@
QBShared.Locations = { QBCore.Shared.Locations = {
-- Base game MLO interiors -- Base game MLO interiors
aircraft_carrier_int = vector4(3081.0042, -4693.6875, 15.2623, 76.8169), aircraft_carrier_int = vector4(3081.0042, -4693.6875, 15.2623, 76.8169),
apt_1_int = vector4(-1452.4602, -540.2056, 74.0443, 31.9129), apt_1_int = vector4(-1452.4602, -540.2056, 74.0443, 31.9129),
+17 -174
View File
@@ -1,185 +1,28 @@
QBShared = QBShared or {} QBCore.Shared = {}
QBCore.ClientCallbacks = {}
QBCore.ServerCallbacks = {}
local StringCharset = {} --- @param filters ('Config'|'Shared'|'ClientCallbacks'|'ServerCallbacks'|'PlayerData'|'Functions'|'Players'|'PlayersByCitizenId'|'Player_Buckets'|'Entity_Buckets'|'UsableItems'|'Commands')[]?
local NumberCharset = {} --- @return table
local function GetCoreObject(filters)
QBShared.StarterItems = { if not filters then return QBCore end
['phone'] = { amount = 1, item = 'phone' }, local results = {}
['id_card'] = { amount = 1, item = 'id_card' }, for i = 1, #filters do
['driver_license'] = { amount = 1, item = 'driver_license' }, local key = filters[i]
} if QBCore[key] then
results[key] = QBCore[key]
for i = 48, 57 do NumberCharset[#NumberCharset + 1] = string.char(i) end
for i = 65, 90 do StringCharset[#StringCharset + 1] = string.char(i) end
for i = 97, 122 do StringCharset[#StringCharset + 1] = string.char(i) end
function QBShared.RandomStr(length)
if length <= 0 then return '' end
return QBShared.RandomStr(length - 1) .. StringCharset[math.random(1, #StringCharset)]
end
function QBShared.RandomInt(length)
if length <= 0 then return '' end
return QBShared.RandomInt(length - 1) .. NumberCharset[math.random(1, #NumberCharset)]
end
function QBShared.SplitStr(str, delimiter)
local result = {}
local from = 1
local delim_from, delim_to = string.find(str, delimiter, from)
while delim_from do
result[#result + 1] = string.sub(str, from, delim_from - 1)
from = delim_to + 1
delim_from, delim_to = string.find(str, delimiter, from)
end
result[#result + 1] = string.sub(str, from)
return result
end
function QBShared.Trim(value)
if not value then return nil end
return (string.gsub(value, '^%s*(.-)%s*$', '%1'))
end
function QBShared.FirstToUpper(value)
if not value then return nil end
return (value:gsub("^%l", string.upper))
end
function QBShared.Round(value, numDecimalPlaces)
if not numDecimalPlaces then return math.floor(value + 0.5) end
local power = 10 ^ numDecimalPlaces
return math.floor((value * power) + 0.5) / (power)
end
function QBShared.ChangeVehicleExtra(vehicle, extra, enable)
if DoesExtraExist(vehicle, extra) then
if enable then
SetVehicleExtra(vehicle, extra, false)
if not IsVehicleExtraTurnedOn(vehicle, extra) then
QBShared.ChangeVehicleExtra(vehicle, extra, enable)
end
else
SetVehicleExtra(vehicle, extra, true)
if IsVehicleExtraTurnedOn(vehicle, extra) then
QBShared.ChangeVehicleExtra(vehicle, extra, enable)
end
end end
end end
return results
end end
exports('GetCoreObject', GetCoreObject)
function QBShared.IsFunction(value)
if type(value) == 'table' then
return value.__cfx_functionReference ~= nil and type(value.__cfx_functionReference) == "string"
end
return type(value) == 'function'
end
function QBShared.SetDefaultVehicleExtras(vehicle, config)
-- Clear Extras
for i = 1, 20 do
if DoesExtraExist(vehicle, i) then
SetVehicleExtra(vehicle, i, 1)
end
end
for id, enabled in pairs(config) do
if type(enabled) ~= 'boolean' then
enabled = true
end
QBShared.ChangeVehicleExtra(vehicle, tonumber(id), enabled)
end
end
QBShared.MaleNoGloves = {
[0] = true,
[1] = true,
[2] = true,
[3] = true,
[4] = true,
[5] = true,
[6] = true,
[7] = true,
[8] = true,
[9] = true,
[10] = true,
[11] = true,
[12] = true,
[13] = true,
[14] = true,
[15] = true,
[18] = true,
[26] = true,
[52] = true,
[53] = true,
[54] = true,
[55] = true,
[56] = true,
[57] = true,
[58] = true,
[59] = true,
[60] = true,
[61] = true,
[62] = true,
[112] = true,
[113] = true,
[114] = true,
[118] = true,
[125] = true,
[132] = true
}
QBShared.FemaleNoGloves = {
[0] = true,
[1] = true,
[2] = true,
[3] = true,
[4] = true,
[5] = true,
[6] = true,
[7] = true,
[8] = true,
[9] = true,
[10] = true,
[11] = true,
[12] = true,
[13] = true,
[14] = true,
[15] = true,
[19] = true,
[59] = true,
[60] = true,
[61] = true,
[62] = true,
[63] = true,
[64] = true,
[65] = true,
[66] = true,
[67] = true,
[68] = true,
[69] = true,
[70] = true,
[71] = true,
[129] = true,
[130] = true,
[131] = true,
[135] = true,
[142] = true,
[149] = true,
[153] = true,
[157] = true,
[161] = true,
[165] = true
}
--- Get a shared item from a shared field
--- @param namespace 'Vehicles' | 'VehicleHashes' | 'Items' | 'Gangs' | 'Jobs' | 'Locations' | 'Weapons' --- @param namespace 'Vehicles' | 'VehicleHashes' | 'Items' | 'Gangs' | 'Jobs' | 'Locations' | 'Weapons'
--- @param item string --- @param item string
--- @return table --- @return table
function GetShared(namespace, item) function GetShared(namespace, item)
return QBCore.Shared[namespace]?[item] local ns = QBCore.Shared[namespace]
return ns and ns[item]
end end
exports('GetShared', GetShared) exports('GetShared', GetShared)
+4 -5
View File
@@ -1,5 +1,4 @@
QBShared = QBShared or {} QBCore.Shared.Vehicles = QBCore.Shared.Vehicles or {}
QBShared.Vehicles = QBShared.Vehicles or {}
local Vehicles = { local Vehicles = {
--- Compacts (0) --- Compacts (0)
@@ -765,10 +764,10 @@ local Vehicles = {
{ model = 'formula', name = 'PR4', brand = 'Progen', price = 100000, category = 'openwheel', type = 'automobile', shop = 'none' }, { model = 'formula', name = 'PR4', brand = 'Progen', price = 100000, category = 'openwheel', type = 'automobile', shop = 'none' },
} }
QBShared.VehicleHashes = QBShared.VehicleHashes or {} QBCore.Shared.VehicleHashes = QBCore.Shared.VehicleHashes or {}
for i = 1, #Vehicles do for i = 1, #Vehicles do
local hash = joaat(Vehicles[i].model) local hash = joaat(Vehicles[i].model)
QBShared.Vehicles[Vehicles[i].model] = { QBCore.Shared.Vehicles[Vehicles[i].model] = {
spawncode = Vehicles[i].model, spawncode = Vehicles[i].model,
name = Vehicles[i].name, name = Vehicles[i].name,
brand = Vehicles[i].brand, brand = Vehicles[i].brand,
@@ -779,5 +778,5 @@ for i = 1, #Vehicles do
type = Vehicles[i].type, type = Vehicles[i].type,
shop = Vehicles[i].shop shop = Vehicles[i].shop
} }
QBShared.VehicleHashes[hash] = QBShared.Vehicles[Vehicles[i].model] QBCore.Shared.VehicleHashes[hash] = QBCore.Shared.Vehicles[Vehicles[i].model]
end end
+1 -2
View File
@@ -1,5 +1,4 @@
QBShared = QBShared or {} QBCore.Shared.Weapons = {
QBShared.Weapons = {
-- // WEAPONS -- // WEAPONS
-- Melee -- Melee
[`weapon_unarmed`] = { name = 'weapon_unarmed', label = 'Fists', weapontype = 'Melee', ammotype = nil, damagereason = 'Melee killed / Whacked / Executed / Beat down / Murdered / Battered' }, [`weapon_unarmed`] = { name = 'weapon_unarmed', label = 'Fists', weapontype = 'Melee', ammotype = nil, damagereason = 'Melee killed / Whacked / Executed / Beat down / Murdered / Battered' },