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
local function keyPressed()
CreateThread(function() -- Not sure if a thread is needed but why not eh?
CreateThread(function()
SendNUIMessage({
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()
ShutdownLoadingScreenNui()
LocalPlayer.state:set('isLoggedIn', true, false)
@@ -17,6 +14,7 @@ RegisterNetEvent('QBCore:Client:PvpHasToggled', function(pvp_state)
SetCanAttackFriendly(PlayerPedId(), pvp_state, false)
NetworkSetFriendlyFireOption(pvp_state)
end)
-- Teleport Commands
RegisterNetEvent('QBCore:Command:TeleportToPlayer', function(coords)
@@ -182,30 +180,23 @@ end)
-- Other stuff
RegisterNetEvent('QBCore:Player:SetPlayerData', function(val)
QBCore.PlayerData = val
end)
RegisterNetEvent('QBCore:Player:UpdatePlayerDataField', function(key, val)
if QBCore.PlayerData and key then
RegisterNetEvent('QBCore:Client:OnPlayerUpdated', function(key, val)
if key == 'all' then
QBCore.PlayerData = val
TriggerEvent('QBCore:Player:SetPlayerData', val)
TriggerEvent('QBCore:Client:OnJobUpdate', val.job)
TriggerEvent('QBCore:Client:OnGangUpdate', val.gang)
elseif QBCore.PlayerData and key then
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)
RegisterNetEvent('QBCore:Player:UpdatePlayerData', function()
TriggerServerEvent('QBCore:UpdatePlayer')
end)
RegisterNetEvent('QBCore:Notify', function(text, type, length, icon)
QBCore.Functions.Notify(text, type, length, icon)
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)
cb(QBCore.Config.Notify)
end)
+1
View File
@@ -1,3 +1,4 @@
QBCore.PlayerData = {}
QBCore.Functions = {}
-- 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
QBConfig.DefaultSpawn = vector4(-1035.71, -2731.87, 12.86, 0.0)
QBConfig.UpdateInterval = 5 -- how often to update player data in minutes
QBConfig.StatusInterval = 5000 -- how often to check hunger/thirst status in milliseconds
QBCore.Config.MaxPlayers = GetConvarInt('sv_maxclients', 48) -- Gets max players from config file, default 48
QBCore.Config.DefaultSpawn = vector4(-1035.71, -2731.87, 12.86, 0.0)
QBCore.Config.UpdateInterval = 5 -- how often to update player data in minutes
QBCore.Config.StatusInterval = 5000 -- how often to check hunger/thirst status in milliseconds
QBConfig.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!
QBConfig.Money.DontAllowMinus = { 'cash', 'crypto' } -- Money that is not allowed going in minus
QBConfig.Money.MinusLimit = -5000 -- The maximum amount you can be negative
QBConfig.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 = {}
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!
QBCore.Config.Money.DontAllowMinus = { 'cash', 'crypto' } -- Money that is not allowed going in minus
QBCore.Config.Money.MinusLimit = -5000 -- The maximum amount you can be negative
QBCore.Config.Money.PayCheckTimeOut = 10 -- The time in minutes that it will give the paycheck
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 = {}
QBConfig.Player.HungerRate = 4.2 -- Rate at which hunger goes down.
QBConfig.Player.ThirstRate = 3.8 -- Rate at which thirst goes down.
QBConfig.Player.Bloodtypes = {
QBCore.Config.Player = {}
QBCore.Config.Player.HungerRate = 4.2 -- Rate at which hunger goes down.
QBCore.Config.Player.ThirstRate = 3.8 -- Rate at which thirst goes down.
QBCore.Config.Player.Bloodtypes = {
'A+', 'A-', 'B+', 'B-', 'AB+', 'AB-', 'O+', 'O-',
}
QBConfig.Player.PlayerDefaults = {
QBCore.Config.Player.PlayerDefaults = {
citizenid = function() return QBCore.Player.CreateCitizenId() end,
cid = 1,
money = function()
local moneyDefaults = {}
for moneytype, startamount in pairs(QBConfig.Money.MoneyTypes) do
for moneytype, startamount in pairs(QBCore.Config.Money.MoneyTypes) do
moneyDefaults[moneytype] = startamount
end
return moneyDefaults
@@ -76,7 +77,7 @@ QBConfig.Player.PlayerDefaults = {
rep = {},
currentapartment = nil,
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,
walletid = function() return QBCore.Player.CreateWalletId() end,
criminalrecord = {
@@ -100,27 +101,27 @@ QBConfig.Player.PlayerDefaults = {
InstalledApps = {}
}
},
position = QBConfig.DefaultSpawn,
position = QBCore.Config.DefaultSpawn,
items = {},
}
QBConfig.Server = {} -- General server config
QBConfig.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
QBConfig.Server.Uptime = 0 -- Time the server has been up.
QBConfig.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
QBConfig.Server.PVP = true -- Enable or disable pvp on the server (Ability to shoot other players)
QBConfig.Server.Discord = '' -- Discord invite link
QBConfig.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 = {} -- General server config
QBCore.Config.Server.Closed = false -- Set server closed (no one can join except people with ace permission 'qbadmin.join')
QBCore.Config.Server.ClosedReason = 'Server Closed' -- Reason message to display when people can't join the server
QBCore.Config.Server.Uptime = 0 -- Time the server has been up.
QBCore.Config.Server.Whitelist = false -- Enable or disable whitelist on the server
QBCore.Config.Server.WhitelistPermission = 'admin' -- Permission that's able to enter the server when the whitelist is on
QBCore.Config.Server.PVP = true -- Enable or disable pvp on the server (Ability to shoot other players)
QBCore.Config.Server.Discord = '' -- Discord invite link
QBCore.Config.Server.CheckDuplicateLicense = true -- Check for duplicate rockstar license on join
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
QBConfig.Commands.OOCColor = { 255, 151, 133 } -- RGB color code for the OOC command
QBCore.Config.Commands = {} -- Command Configuration
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
position = 'right', -- top-left | top-right | bottom-left | bottom-right | top | bottom | left | right | center
progress = true -- Display Progress Bar
@@ -129,7 +130,7 @@ QBConfig.Notify.NotificationStyling = {
-- These are how you define different notification variants
-- The "color" key is background of the notification
-- The "icon" key is the css-icon code, this project uses `Material Icons` & `Font Awesome`
QBConfig.Notify.VariantDefinitions = {
QBCore.Config.Notify.VariantDefinitions = {
success = {
classes = 'success',
icon = 'check_circle'
+1 -2
View File
@@ -11,6 +11,7 @@ shared_scripts {
'locale/en.lua',
'locale/*.lua',
'shared/main.lua',
'shared/functions.lua',
'shared/items.lua',
'shared/jobs.lua',
'shared/vehicles.lua',
@@ -20,7 +21,6 @@ shared_scripts {
}
client_scripts {
'client/main.lua',
'client/functions.lua',
'client/loops.lua',
'client/events.lua',
@@ -29,7 +29,6 @@ client_scripts {
server_scripts {
'@oxmysql/lib/MySQL.lua',
'server/main.lua',
'server/functions.lua',
'server/player.lua',
'server/events.lua',
+3 -30
View File
@@ -1,35 +1,8 @@
:root {
/* Typography */
--font-primary: "Exo 2", sans-serif;
--font-weight-regular: 400;
--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 */
--primary-bg: #211f26;
--active-bg: #c10114;
--font-color: #e6e1e5;
--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;
}
+93 -117
View File
@@ -1,49 +1,8 @@
:root {
/* Typography */
--font-primary: "Exo 2", sans-serif;
--font-weight-regular: 400;
--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);
/* Border Radius */
--md-radius-small: 4px;
--md-radius-medium: 8px;
}
* {
@@ -55,94 +14,111 @@ html::-webkit-scrollbar {
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 {
background-color: var(--md-success-container);
color: var(--md-on-success-container);
padding: 12px 16px;
font-weight: var(--font-weight-medium);
--accent: #006e1c;
}
.primary {
background-color: var(--md-primary-container);
color: var(--md-on-primary-container);
padding: 12px 16px;
font-weight: var(--font-weight-medium);
--accent: #0061a4;
}
.warning {
background-color: var(--md-warning-container);
color: var(--md-on-warning-container);
padding: 12px 16px;
font-weight: var(--font-weight-medium);
--accent: #7d5800;
}
.error {
background-color: var(--md-error-container);
color: var(--md-on-error-container);
padding: 12px 16px;
font-weight: var(--font-weight-medium);
--accent: #ba1a1a;
}
.police {
background-color: var(--md-info-container);
color: var(--md-on-info-container);
padding: 12px 16px;
font-weight: var(--font-weight-medium);
--accent: #0062a1;
}
.ambulance {
--accent: #006874;
}
.success,
.primary,
.warning,
.error,
.police,
.ambulance {
background-color: var(--md-error-container);
color: var(--md-on-error-container);
background-color: #211f26;
color: #e6e1e5;
padding: 12px 16px;
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>
<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://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 href="https://fonts.googleapis.com/css?family=Roboto:100,300,400,500,700,900|Material+Icons" rel="stylesheet" type="text/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/style.css" />
<link rel="stylesheet" href="css/drawtext.css" />
<script type="module" src="js/app.js"></script>
<script src="js/drawtext.js" defer></script>
</head>
<body>
<div id="q-app" style="min-height: 100vh"></div>
<div id="drawtext-container">
<div id="text" class="text"></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 { onMounted, onUnmounted } = Vue;
const defaultConfig = {
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 resourceName = window.GetParentResourceName();
const rawResp = await fetch(`https://${resourceName}/${evName}`, {
const resp = await fetch(`https://${resourceName}/${evName}`, {
body: JSON.stringify(data),
headers: {
"Content-Type": "application/json; charset=UTF8",
},
headers: { "Content-Type": "application/json; charset=UTF8" },
method: "POST",
});
return await rawResp.json();
return resp.json();
};
window.fetchNui = fetchNui;
const determineStyleFromVariant = (variant) => {
return NOTIFY_CONFIG.VariantDefinitions[variant] ?? NOTIFY_CONFIG.VariantDefinitions["primary"];
};
const app = Vue.createApp({
setup() {
const $q = useQuasar();
const fetchNotifyConfig = async () => {
try {
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 }) => {
if (data?.action !== "notify") return;
const POSITION_MAP = {
"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 { classes, icon } = determineStyleFromVariant(type);
let container = null;
if (dataIcon) {
icon = dataIcon;
}
const getContainer = () => {
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) {
console.error("The notification config did not load properly, trying again for next time");
await fetchNotifyConfig();
if (NOTIFY_CONFIG) return showNotif({ data });
}
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
$q.notify({
message: text,
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 {};
},
});
const showNotif = async ({ data }) => {
if (data?.action !== "notify") return;
app.use(Quasar, { config: {} });
app.mount("#q-app");
if (!NOTIFY_CONFIG) {
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();
});
+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')
end
else
local location = QBShared.Locations[args[1]]
local location = QBCore.Shared.Locations[args[1]]
if location then
TriggerClientEvent('QBCore:Command:TeleportToCoords', source, location.x, location.y, location.z, location.w)
else
@@ -101,12 +101,12 @@ QBCore.Commands.Add('tp', Lang:t('command.tp.help'), { { name = Lang:t('command.
end
else
if args[1] and args[2] and args[3] then
local x = tonumber((args[1]:gsub(',', ''))) + .0
local y = tonumber((args[2]:gsub(',', ''))) + .0
local z = tonumber((args[3]:gsub(',', ''))) + .0
local heading = args[4] and tonumber((args[4]:gsub(',', ''))) + .0 or false
if x ~= 0 and y ~= 0 and z ~= 0 then
TriggerClientEvent('QBCore:Command:TeleportToCoords', source, x, y, z, heading)
local x = tonumber((args[1]:gsub(',', '')))
local y = tonumber((args[2]:gsub(',', '')))
local z = tonumber((args[3]:gsub(',', '')))
local heading = args[4] and tonumber((args[4]:gsub(',', ''))) or false
if x and y and z then
TriggerClientEvent('QBCore:Command:TeleportToCoords', source, x + .0, y + .0, z + .0, heading and heading + .0 or false)
else
TriggerClientEvent('QBCore:Notify', source, Lang:t('error.wrong_format'), 'error')
end
@@ -240,7 +240,9 @@ end, 'admin')
-- Job
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 }))
end, 'user')
@@ -256,7 +258,9 @@ end, 'admin')
-- Gang
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 }))
end, 'user')
+18 -12
View File
@@ -9,36 +9,42 @@ local function tPrint(tbl, indent)
print(formatting)
tPrint(v, indent + 1)
elseif tblType == 'boolean' then
print(('%s^1 %s ^0'):format(formatting, v))
print(('%s ^1%s^0'):format(formatting, v))
elseif tblType == 'function' then
print(('%s^9 %s ^0'):format(formatting, v))
print(('%s ^9%s^0'):format(formatting, v))
elseif tblType == 'number' then
print(('%s^5 %s ^0'):format(formatting, v))
print(('%s ^5%s^0'):format(formatting, v))
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
print(('%s^2 %s ^0'):format(formatting, v))
print(('%s ^2%s^0'):format(formatting, tostring(v)))
end
end
else
print(('%s ^0%s'):format(string.rep(' ', indent), tbl))
print(('%s ^0%s'):format(string.rep(' ', indent), tostring(tbl)))
end
end
RegisterServerEvent('QBCore:DebugSomething', function(tbl, indent, resource)
print(('\x1b[4m\x1b[36m[ %s : DEBUG]\x1b[0m'):format(resource))
RegisterNetEvent('QBCore:DebugSomething', function(tbl, indent, resource)
resource = resource or 'unknown'
print(('^4[ %s : DEBUG]^0'):format(resource))
tPrint(tbl, indent)
print('\x1b[4m\x1b[36m[ END DEBUG ]\x1b[0m')
print('^4[ END DEBUG ]^0')
end)
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
function QBCore.ShowError(resource, msg)
print('\x1b[31m[' .. resource .. ':ERROR]\x1b[0m ' .. msg)
print(('^1[%s:ERROR]^0 %s'):format(resource, msg))
end
function QBCore.ShowSuccess(resource, msg)
print('\x1b[32m[' .. resource .. ':LOG]\x1b[0m ' .. msg)
print(('^2[%s:LOG]^0 %s'):format(resource, msg))
end
+39 -34
View File
@@ -10,16 +10,18 @@ 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)
TriggerEvent('QBCore:Server:PlayerDropped', Player)
Player.Functions.Save()
QBCore.Player_Buckets[Player.PlayerData.license] = nil
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
AddEventHandler('onResourceStop', function(resName)
for i, v in pairs(QBCore.UsableItems) do
if v.resource == resName then
QBCore.UsableItems[i] = nil
end
@@ -36,7 +38,7 @@ if readyFunction ~= nil then
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})
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
@@ -124,7 +126,7 @@ end)
-- Client Callback
RegisterNetEvent('QBCore:Server:TriggerClientCallback', function(name, ...)
local ClientCallback = QBCore.ClientCallbacks[name..source]
local ClientCallback = QBCore.ClientCallbacks[name .. source]
if ClientCallback then
ClientCallback.promise:resolve(...)
@@ -132,7 +134,7 @@ RegisterNetEvent('QBCore:Server:TriggerClientCallback', function(name, ...)
ClientCallback.callback(...)
end
QBCore.ClientCallbacks[name..source] = nil
QBCore.ClientCallbacks[name .. source] = nil
end
end)
@@ -149,8 +151,12 @@ 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
@@ -161,8 +167,9 @@ RegisterNetEvent('QBCore:UpdatePlayer', function()
if newThirst <= 0 then
newThirst = 0
end
Player.Functions.SetMetaData('thirst', newThirst)
Player.Functions.SetMetaData('hunger', newHunger)
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)
@@ -183,6 +190,24 @@ RegisterNetEvent('QBCore:ToggleDuty', function()
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
@@ -224,26 +249,6 @@ RegisterServerEvent('baseevents:leftVehicle', function(veh, seat, modelName)
TriggerClientEvent('QBCore:Client:VehicleInfo', src, data)
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)
RegisterNetEvent('QBCore:CallCommand', function(command, args)
@@ -269,7 +274,7 @@ end)
-- 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(NetworkGetNetworkIdFromEntity(veh))
cb(DoesEntityExist(veh) and NetworkGetNetworkIdFromEntity(veh) or nil)
end)
-- Use this for long distance vehicle spawning
@@ -278,7 +283,7 @@ end)
-- 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(NetworkGetNetworkIdFromEntity(veh))
cb(DoesEntityExist(veh) and NetworkGetNetworkIdFromEntity(veh) or nil)
end)
--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
return false, 'invalid_method_name'
end
if QBCore.Functions[methodName] ~= nil then
return false, 'method_exists'
end
QBCore.Functions[methodName] = handler
TriggerEvent('QBCore:Server:UpdateObject')
return true, 'success'
end
@@ -19,11 +19,11 @@ local function SetField(fieldName, data)
if type(fieldName) ~= 'string' then
return false, 'invalid_field_name'
end
if QBCore[fieldName] ~= nil then
return false, 'field_exists'
end
QBCore[fieldName] = data
TriggerEvent('QBCore:Server:UpdateObject')
return true, 'success'
end
@@ -52,32 +52,16 @@ exports('AddJob', AddJob)
-- Multiple Add Jobs
local function AddJobs(jobs)
local shouldContinue = true
local message = 'success'
local errorItem = nil
for key, value in pairs(jobs) do
if type(key) ~= 'string' then
message = 'invalid_job_name'
shouldContinue = false
errorItem = jobs[key]
break
end
if QBCore.Shared.Jobs[key] then
message = 'job_exists'
shouldContinue = false
errorItem = jobs[key]
break
end
if type(key) ~= 'string' then return false, 'invalid_job_name', value end
if QBCore.Shared.Jobs[key] then return false, 'job_exists', value end
end
for key, value in pairs(jobs) do
QBCore.Shared.Jobs[key] = value
end
if not shouldContinue then return false, message, errorItem end
TriggerClientEvent('QBCore:Client:OnSharedUpdateMultiple', -1, 'Jobs', jobs)
TriggerEvent('QBCore:Server:UpdateObject')
return true, message, nil
return true, 'success', nil
end
QBCore.Functions.AddJobs = AddJobs
@@ -162,32 +146,16 @@ exports('UpdateItem', UpdateItem)
-- Multiple Add Items
local function AddItems(items)
local shouldContinue = true
local message = 'success'
local errorItem = nil
for key, value in pairs(items) do
if type(key) ~= 'string' then
message = 'invalid_item_name'
shouldContinue = false
errorItem = items[key]
break
end
if QBCore.Shared.Items[key] then
message = 'item_exists'
shouldContinue = false
errorItem = items[key]
break
end
if type(key) ~= 'string' then return false, 'invalid_item_name', value end
if QBCore.Shared.Items[key] then return false, 'item_exists', value end
end
for key, value in pairs(items) do
QBCore.Shared.Items[key] = value
end
if not shouldContinue then return false, message, errorItem end
TriggerClientEvent('QBCore:Client:OnSharedUpdateMultiple', -1, 'Items', items)
TriggerEvent('QBCore:Server:UpdateObject')
return true, message, nil
return true, 'success', nil
end
QBCore.Functions.AddItems = AddItems
@@ -235,32 +203,16 @@ exports('AddGang', AddGang)
-- Multiple Add Gangs
local function AddGangs(gangs)
local shouldContinue = true
local message = 'success'
local errorItem = nil
for key, value in pairs(gangs) do
if type(key) ~= 'string' then
message = 'invalid_gang_name'
shouldContinue = false
errorItem = gangs[key]
break
end
if QBCore.Shared.Gangs[key] then
message = 'gang_exists'
shouldContinue = false
errorItem = gangs[key]
break
end
if type(key) ~= 'string' then return false, 'invalid_gang_name', value end
if QBCore.Shared.Gangs[key] then return false, 'gang_exists', value end
end
for key, value in pairs(gangs) do
QBCore.Shared.Gangs[key] = value
end
if not shouldContinue then return false, message, errorItem end
TriggerClientEvent('QBCore:Client:OnSharedUpdateMultiple', -1, 'Gangs', gangs)
TriggerEvent('QBCore:Server:UpdateObject')
return true, message, nil
return true, 'success', nil
end
QBCore.Functions.AddGangs = AddGangs
+40 -50
View File
@@ -55,12 +55,7 @@ end
---@param citizenid string
---@return table?
function QBCore.Functions.GetPlayerByCitizenId(citizenid)
for _, Player in pairs(QBCore.Players) do
if Player.PlayerData.citizenid == citizenid then
return Player
end
end
return nil
return QBCore.PlayersByCitizenId[citizenid]
end
---Get offline player by citizen id
@@ -304,16 +299,12 @@ end
---@return table|boolean
function QBCore.Functions.GetPlayersInBucket(bucket)
local curr_bucket_pool = {}
if QBCore.Player_Buckets and next(QBCore.Player_Buckets) then
for _, v in pairs(QBCore.Player_Buckets) do
if v.bucket == bucket then
curr_bucket_pool[#curr_bucket_pool + 1] = v.id
end
for _, v in pairs(QBCore.Player_Buckets) do
if v.bucket == bucket then
curr_bucket_pool[#curr_bucket_pool + 1] = v.id
end
return curr_bucket_pool
else
return false
end
return curr_bucket_pool
end
---Will return an array of all the entities inside the current bucket
@@ -322,16 +313,12 @@ end
---@return table|boolean
function QBCore.Functions.GetEntitiesInBucket(bucket)
local curr_bucket_pool = {}
if QBCore.Entity_Buckets and next(QBCore.Entity_Buckets) then
for _, v in pairs(QBCore.Entity_Buckets) do
if v.bucket == bucket then
curr_bucket_pool[#curr_bucket_pool + 1] = v.id
end
for _, v in pairs(QBCore.Entity_Buckets) do
if v.bucket == bucket then
curr_bucket_pool[#curr_bucket_pool + 1] = v.id
end
return curr_bucket_pool
else
return false
end
return curr_bucket_pool
end
---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
local heading = coords.w and coords.w or 0.0
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
while GetVehiclePedIsIn(ped) ~= veh do
t = 0
while GetVehiclePedIsIn(ped) ~= veh and t < 100 do
Wait(0)
t += 1
TaskWarpPedIntoVehicle(ped, veh, -1)
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
end
@@ -373,7 +370,11 @@ function QBCore.Functions.CreateAutomobile(source, model, coords, warp)
local heading = coords.w and coords.w or 0.0
local CreateAutomobile = `CREATE_AUTOMOBILE`
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
return veh
end
@@ -395,7 +396,11 @@ function QBCore.Functions.CreateVehicle(source, model, vehtype, coords, warp)
if not coords then coords = GetEntityCoords(GetPlayerPed(source)) end
local heading = coords.w and coords.w or 0.0
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
return veh
end
@@ -408,9 +413,11 @@ function PaycheckInterval()
CreateThread(function()
for _, Player in pairs(QBCore.Players) do
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 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
local account = exports['qb-banking']:GetAccountBalance(Player.PlayerData.job.name)
if account ~= 0 then
@@ -463,7 +470,9 @@ function QBCore.Functions.TriggerClientCallback(name, source, ...)
if cb == nil then
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
@@ -538,7 +547,7 @@ function QBCore.Functions.Kick(source, reason, setKickReason, deferrals)
for _ = 0, 4 do
while true do
if source then
if GetPlayerPing(source) >= 0 then
if GetPlayerPing(source) < 0 then
break
end
CreateThread(function()
@@ -723,27 +732,8 @@ function QBCore.Functions.Notify(source, text, type, length)
TriggerClientEvent('QBCore:Notify', source, text, type, length)
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
if type(func) == 'function' then
exports(functionName, func)
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 {}
QBShared.Gangs = {
QBCore.Shared.Gangs = {
none = { label = 'No Gang', grades = { ['0'] = { name = 'Unaffiliated' } } },
lostmc = {
label = 'The Lost MC',
+1 -2
View File
@@ -1,5 +1,4 @@
QBShared = QBShared or {}
QBShared.Items = {
QBCore.Shared.Items = {
-- WEAPONS
-- Melee
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 {}
QBShared.ForceJobDefaultDutyAtLogin = true -- true: Force duty state to jobdefaultDuty | false: set duty state from database last saved
QBShared.Jobs = {
QBCore.Shared.ForceJobDefaultDutyAtLogin = true -- true: Force duty state to jobdefaultDuty | false: set duty state from database last saved
QBCore.Shared.Jobs = {
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 } } },
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
aircraft_carrier_int = vector4(3081.0042, -4693.6875, 15.2623, 76.8169),
apt_1_int = vector4(-1452.4602, -540.2056, 74.0443, 31.9129),
+16 -173
View File
@@ -1,185 +1,28 @@
QBShared = QBShared or {}
QBCore.Shared = {}
QBCore.ClientCallbacks = {}
QBCore.ServerCallbacks = {}
local StringCharset = {}
local NumberCharset = {}
QBShared.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 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
--- @param filters ('Config'|'Shared'|'ClientCallbacks'|'ServerCallbacks'|'PlayerData'|'Functions'|'Players'|'PlayersByCitizenId'|'Player_Buckets'|'Entity_Buckets'|'UsableItems'|'Commands')[]?
--- @return table
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)
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 item string
--- @return table
function GetShared(namespace, item)
return QBCore.Shared[namespace]?[item]
local ns = QBCore.Shared[namespace]
return ns and ns[item]
end
exports('GetShared', GetShared)
+4 -5
View File
@@ -1,5 +1,4 @@
QBShared = QBShared or {}
QBShared.Vehicles = QBShared.Vehicles or {}
QBCore.Shared.Vehicles = QBCore.Shared.Vehicles or {}
local Vehicles = {
--- Compacts (0)
@@ -765,10 +764,10 @@ local Vehicles = {
{ 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
local hash = joaat(Vehicles[i].model)
QBShared.Vehicles[Vehicles[i].model] = {
QBCore.Shared.Vehicles[Vehicles[i].model] = {
spawncode = Vehicles[i].model,
name = Vehicles[i].name,
brand = Vehicles[i].brand,
@@ -779,5 +778,5 @@ for i = 1, #Vehicles do
type = Vehicles[i].type,
shop = Vehicles[i].shop
}
QBShared.VehicleHashes[hash] = QBShared.Vehicles[Vehicles[i].model]
QBCore.Shared.VehicleHashes[hash] = QBCore.Shared.Vehicles[Vehicles[i].model]
end
+1 -2
View File
@@ -1,5 +1,4 @@
QBShared = QBShared or {}
QBShared.Weapons = {
QBCore.Shared.Weapons = {
-- // WEAPONS
-- Melee
[`weapon_unarmed`] = { name = 'weapon_unarmed', label = 'Fists', weapontype = 'Melee', ammotype = nil, damagereason = 'Melee killed / Whacked / Executed / Beat down / Murdered / Battered' },