diff --git a/bridge/qb/client/main.lua b/bridge/qb/client/main.lua index 856cee989..0fcc923ff 100644 --- a/bridge/qb/client/main.lua +++ b/bridge/qb/client/main.lua @@ -1,6 +1,6 @@ local qbCoreCompat = {} qbCoreCompat.PlayerData = QBX.PlayerData -qbCoreCompat.Config = Config +qbCoreCompat.Config = lib.table.merge(require 'config.client', require 'config.shared') qbCoreCompat.Shared = require 'bridge.qb.shared.main' qbCoreCompat.Functions = require 'bridge.qb.client.functions' diff --git a/bridge/qb/server/main.lua b/bridge/qb/server/main.lua index fbf987d8b..6a8d6a5a6 100644 --- a/bridge/qb/server/main.lua +++ b/bridge/qb/server/main.lua @@ -1,6 +1,6 @@ local qbCoreCompat = {} -qbCoreCompat.Config = Config +qbCoreCompat.Config = lib.table.merge(require 'config.server', require 'config.shared') qbCoreCompat.Shared = require 'bridge.qb.shared.main' qbCoreCompat.Players = QBX.Players qbCoreCompat.Player = require 'bridge.qb.server.player' diff --git a/client/character.lua b/client/character.lua index 2c0bb3a2b..71b528d5c 100644 --- a/client/character.lua +++ b/client/character.lua @@ -1,7 +1,10 @@ -if Config.Characters.UseExternalCharacters then return end +local config = require 'config.client' +local defaultSpawn = require 'config.shared'.DefaultSpawn + +if config.Characters.UseExternalCharacters then return end local previewCam = nil -local randomLocation = Config.Characters.Locations[math.random(1, #Config.Characters.Locations)] +local randomLocation = config.Characters.Locations[math.random(1, #config.Characters.Locations)] local randomPedModels = { `a_m_o_soucent_02`, @@ -50,19 +53,19 @@ end local function previewPed(citizenId) if not citizenId then local model = randomPedModels[math.random(1, #randomPedModels)] - lib.requestModel(model, Config.LoadingModelsTimeout) + lib.requestModel(model, config.LoadingModelsTimeout) SetPlayerModel(cache.playerId, model) return end local clothing, model = lib.callback.await('qbx_core:server:getPreviewPedData', false, citizenId) if model and clothing then - lib.requestModel(model, Config.LoadingModelsTimeout) + lib.requestModel(model, config.LoadingModelsTimeout) SetPlayerModel(cache.playerId, model) pcall(function() exports['illenium-appearance']:setPedAppearance(PlayerPedId(), json.decode(clothing)) end) else model = randomPedModels[math.random(1, #randomPedModels)] - lib.requestModel(model, Config.LoadingModelsTimeout) + lib.requestModel(model, config.LoadingModelsTimeout) SetPlayerModel(cache.playerId, model) end end @@ -133,14 +136,14 @@ end ---@return boolean local function checkStrings(dialog, input) local str = dialog[input] - if Config.Characters.ProfanityWords[str:lower()] then return false end + if config.Characters.ProfanityWords[str:lower()] then return false end local split = {string.strsplit(' ', str)} if #split > 5 then return false end for i = 1, #split do local word = split[i] - if Config.Characters.ProfanityWords[word:lower()] then return false end + if config.Characters.ProfanityWords[word:lower()] then return false end end return true @@ -235,7 +238,7 @@ local function createCharacter(cid) spawnDefault() TriggerEvent('qb-clothes:client:CreateFirstCharacter') else - if Config.Characters.StartingApartment then + if config.Characters.StartingApartment then TriggerEvent('apartments:client:setupSpawnUI', newData) else TriggerEvent('qbx_core:client:spawnNoApartments') @@ -247,7 +250,7 @@ local function createCharacter(cid) end local function chooseCharacter() - randomLocation = Config.Characters.Locations[math.random(1, #Config.Characters.Locations)] + randomLocation = config.Characters.Locations[math.random(1, #config.Characters.Locations)] DoScreenFadeOut(500) @@ -325,7 +328,7 @@ local function chooseCharacter() destroyPreviewCam() end }, - Config.Characters.EnableDeleteButton and { + config.Characters.EnableDeleteButton and { title = Lang:t('info.delete_character'), description = Lang:t('info.delete_character_description', { playerName = name }), icon = 'trash', @@ -364,8 +367,8 @@ end RegisterNetEvent('qbx_core:client:spawnNoApartments', function() -- This event is only for no starting apartments DoScreenFadeOut(500) Wait(2000) - SetEntityCoords(cache.ped, Config.DefaultSpawn.x, Config.DefaultSpawn.y, Config.DefaultSpawn.z, false, false, false, false) - SetEntityHeading(cache.ped, Config.DefaultSpawn.w) + SetEntityCoords(cache.ped, defaultSpawn.x, defaultSpawn.y, defaultSpawn.z, false, false, false, false) + SetEntityHeading(cache.ped, defaultSpawn.w) Wait(500) destroyPreviewCam() SetEntityVisible(cache.ped, true, false) @@ -391,7 +394,7 @@ CreateThread(function() if NetworkIsSessionStarted() then pcall(function() exports.spawnmanager:setAutoSpawn(false) end) Wait(250) - lib.requestModel(model, Config.LoadingModelsTimeout) + lib.requestModel(model, config.LoadingModelsTimeout) SetPlayerModel(cache.playerId, model) chooseCharacter() break diff --git a/client/discord.lua b/client/discord.lua index 095fa10f0..c473584c2 100644 --- a/client/discord.lua +++ b/client/discord.lua @@ -1,13 +1,17 @@ +local maxPlayers = GlobalState.MaxPlayers +local discord = require 'config.client'.Discord + AddStateBagChangeHandler('PlayerCount', nil, function(bagName, _, value) - if bagName ~= 'global' or not value then return end - local players = 'Players %s/' .. Config.MaxPlayers - SetRichPresence((players):format(value)) + if bagName == 'global' and value then + local players = 'Players %s/' .. maxPlayers + SetRichPresence((players):format(value)) + end end) -SetDiscordAppId(Config.Discord.AppId) -SetDiscordRichPresenceAsset(Config.Discord.LargeIcon.icon) -SetDiscordRichPresenceAssetText(Config.Discord.LargeIcon.text) -SetDiscordRichPresenceAssetSmall(Config.Discord.SmallIcon.icon) -SetDiscordRichPresenceAssetSmallText(Config.Discord.SmallIcon.text) -SetDiscordRichPresenceAction(0, Config.Discord.FirstButton.text, Config.Discord.FirstButton.link) -SetDiscordRichPresenceAction(1, Config.Discord.SecondButton.text, Config.Discord.SecondButton.link) +SetDiscordAppId(discord.AppId) +SetDiscordRichPresenceAsset(discord.LargeIcon.icon) +SetDiscordRichPresenceAssetText(discord.LargeIcon.text) +SetDiscordRichPresenceAssetSmall(discord.SmallIcon.icon) +SetDiscordRichPresenceAssetSmallText(discord.SmallIcon.text) +SetDiscordRichPresenceAction(0, discord.FirstButton.text, discord.FirstButton.link) +SetDiscordRichPresenceAction(1, discord.SecondButton.text, discord.SecondButton.link) diff --git a/client/events.lua b/client/events.lua index cbb88fb86..98a350e31 100644 --- a/client/events.lua +++ b/client/events.lua @@ -3,9 +3,11 @@ RegisterNetEvent('QBCore:Client:OnPlayerLoaded', function() ShutdownLoadingScreenNui() LocalPlayer.state:set('isLoggedIn', true, false) QBX.IsLoggedIn = true - if not Config.Server.PVP then return end - SetCanAttackFriendly(cache.ped, true, false) - NetworkSetFriendlyFireOption(true) + + if GlobalState.PVPEnabled then + SetCanAttackFriendly(cache.ped, true, false) + NetworkSetFriendlyFireOption(true) + end end) ---@param val PlayerData @@ -19,10 +21,12 @@ RegisterNetEvent('QBCore:Client:OnPlayerUnload', function() QBX.IsLoggedIn = false end) ----@param pvp_state boolean -RegisterNetEvent('QBCore:Client:PvpHasToggled', function(pvp_state) - SetCanAttackFriendly(cache.ped, pvp_state, false) - NetworkSetFriendlyFireOption(pvp_state) +---@param value boolean +AddStateBagChangeHandler('PVPEnabled', nil, function(bagName, _, value) + if bagName == 'global' then + SetCanAttackFriendly(cache.ped, value, false) + NetworkSetFriendlyFireOption(value) + end end) -- Teleport Commands @@ -141,20 +145,10 @@ RegisterNetEvent('qbx_core:client:vehicleSpawned', function(netId, props) end end) -RegisterNetEvent('QBCore:Command:DeleteVehicle', function() - if cache.vehicle then - SetEntityAsMissionEntity(cache.vehicle, true, true) - DeleteVehicle(cache.vehicle) - else - local pcoords = GetEntityCoords(cache.ped) - local vehicles = GetGamePool('CVehicle') - for _, v in pairs(vehicles) do - if #(pcoords - GetEntityCoords(v)) <= 5.0 then - SetEntityAsMissionEntity(v, true, true) - DeleteVehicle(v) - end - end - end +lib.callback.register('qbx_core:client:getNearestVehicle', function() + local vehicle = lib.getClosestVehicle(GetEntityCoords(cache.ped), 5) + + return vehicle and VehToNet(vehicle) end) -- Other stuff diff --git a/client/functions.lua b/client/functions.lua index f1155cb4a..28bd70c95 100644 --- a/client/functions.lua +++ b/client/functions.lua @@ -1,3 +1,5 @@ +local NotifyPosition = require 'config.shared'.NotifyPosition + ---Text box popup for player which dissappears after a set time. ---@param text table|string text of the notification ---@param notifyType? NotificationType informs default styling. Defaults to 'inform' @@ -18,7 +20,7 @@ function Notify(text, notifyType, duration, subTitle, notifyPosition, notifyStyl else description = text end - local position = notifyPosition or Config.NotifyPosition + local position = notifyPosition or NotifyPosition lib.notify({ id = title, diff --git a/client/loops.lua b/client/loops.lua index eafa73188..9c163d552 100644 --- a/client/loops.lua +++ b/client/loops.lua @@ -1,5 +1,7 @@ +local StatusInterval = require 'config.client'.StatusInterval + CreateThread(function() - local timeout = 60000 * Config.StatusInterval + local timeout = 60000 * StatusInterval while true do Wait(timeout) diff --git a/client/main.lua b/client/main.lua index c829527f9..4b20319e6 100644 --- a/client/main.lua +++ b/client/main.lua @@ -60,6 +60,6 @@ lib.callback.register('qbx_core:client:setHealth', function(health) SetEntityHealth(cache.ped, health) end) -local mapText = Config.Server.PauseMapText +local mapText = require 'config.client'.PauseMapText if mapText == '' or type(mapText) ~= 'string' then mapText = 'FiveM' end AddTextEntry('FE_THDR_GTAO', mapText) \ No newline at end of file diff --git a/config.lua b/config.lua deleted file mode 100644 index 089d974a9..000000000 --- a/config.lua +++ /dev/null @@ -1,175 +0,0 @@ -Config = {} - -Config.MaxPlayers = GetConvarInt('sv_maxclients', 48) -- Gets max players from config file, default 48. Only works for server -Config.DefaultSpawn = vec4(-540.58, -212.02, 37.65, 208.88) -Config.UpdateInterval = 5 -- how often to update player data in minutes -Config.StatusInterval = 5 -- how often to check hunger/thirst status in minutes - -Config.Characters = {} -Config.LoadingModelsTimeout = 10000 -- Waiting time for ox_lib to load the models before throws an error, for low specs pc -Config.Characters.UseExternalCharacters = false -- Whether you have an external character management resource. (If true, disables the character management inside the core) -Config.Characters.EnableDeleteButton = true -- Whether players should be able to delete characters themselves. -Config.Characters.StartingApartment = true -- If set to false, skips apartment choice in the beginning (requires qbx_spawn if true) -Config.Characters.DefaultNumberOfCharacters = 3 -- Define maximum amount of default characters (maximum 3 characters defined by default) -Config.Characters.PlayersNumberOfCharacters = { -- Define maximum amount of player characters by rockstar license (you can find this license in your server's database in the player table) - ['license2:xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'] = 5, -} -Config.Characters.ProfanityWords = { - ['bad word'] = true -} -Config.Characters.Locations = { -- Spawn locations for multichar, these are chosen randomly - { - pedCoords = vec4(969.25, 72.61, 116.18, 276.55), - camCoords = vec4(972.2, 72.9, 116.68, 97.27), - }, - { - pedCoords = vec4(1104.49, 195.9, -49.44, 44.22), - camCoords = vec4(1102.29, 198.14, -48.86, 225.07), - }, - { - pedCoords = vec4(-2163.87, 1134.51, -24.37, 310.05), - camCoords = vec4(-2161.7, 1136.4, -23.77, 131.52), - }, - { - pedCoords = vec4(-996.71, -68.07, -99.0, 57.61), - camCoords = vec4(-999.90, -66.30, -98.45, 241.68), - }, - { - pedCoords = vec4(-1023.45, -418.42, 67.66, 205.69), - camCoords = vec4(-1021.8, -421.7, 68.14, 27.11), - }, - { - pedCoords = vec4(2265.27, 2925.02, -84.8, 267.77), - camCoords = vec4(2268.24, 2925.02, -84.36, 90.88), - } -} - -Config.Money = {} - ----@alias MoneyType 'cash' | 'bank' | 'crypto' ----@alias Money {cash: number, bank: number, crypto: number} ----@type Money -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! - -Config.Money.DontAllowMinus = { 'cash', 'crypto' } -- Money that is not allowed going in minus -Config.Money.PaycheckTimeout = 10 -- The time in minutes that it will give the paycheck -Config.Money.PaycheckSociety = false -- If true paycheck will come from the society account that the player is employed at, requires qb-management - -Config.Player = {} -Config.Player.HungerRate = 4.2 -- Rate at which hunger goes down. -Config.Player.ThirstRate = 3.8 -- Rate at which thirst goes down. - ----@enum BloodType -Config.Player.Bloodtypes = { - "A+", "A-", "B+", "B-", "AB+", "AB-", "O+", "O-", -} - ----@alias UniqueIdType 'citizenid' | 'AccountNumber' | 'PhoneNumber' | 'FingerId' | 'WalletId' | 'SerialNumber' ----@type table -Config.Player.IdentifierTypes = { - citizenid = { - valueFunction = function() - return tostring(RandomLetter(3) .. RandomNumber(5)):upper() - end, - }, - AccountNumber = { - valueFunction = function() - return 'US0' .. math.random(1, 9) .. 'QBX' .. math.random(1111, 9999) .. math.random(1111, 9999) .. math.random(11, 99) - end, - }, - PhoneNumber = { - valueFunction = function() - return math.random(100,999) .. math.random(1000000,9999999) - end, - }, - FingerId = { - valueFunction = function() - return tostring(RandomLetter(2) .. RandomNumber(3) .. RandomLetter(1) .. RandomNumber(2) .. RandomLetter(3) .. RandomNumber(4)) - end, - }, - WalletId = { - valueFunction = function() - return 'QB-' .. math.random(11111111, 99999999) - end, - }, - SerialNumber = { - valueFunction = function() - return math.random(11111111, 99999999) - end, - }, -} - -Config.Server = {} -- General server config -Config.Server.Closed = false -- Set server closed (no one can join except people with ace permission 'qbadmin.join') -Config.Server.ClosedReason = "Server Closed" -- Reason message to display when people can't join the server -Config.Server.Uptime = 0 -- Time the server has been up. -Config.Server.Whitelist = false -- Enable or disable whitelist on the server -Config.Server.WhitelistPermission = 'admin' -- Permission that's able to enter the server when the whitelist is on -Config.Server.PVP = true -- Enable or disable pvp on the server (Ability to shoot other players) -Config.Server.Discord = "" -- Discord invite link -Config.Server.CheckDuplicateLicense = true -- Check for duplicate rockstar license on join -Config.Server.Permissions = { 'god', 'admin', 'mod' } -- Add as many groups as you want here after creating them in your server.cfg -Config.Server.PauseMapText = 'Powered by Qbox' -- Text shown above the map when ESC is pressed. If left empty 'FiveM' will appear - -Config.NotifyPosition = 'top-right' -- 'top' | 'top-right' | 'top-left' | 'bottom' | 'bottom-right' | 'bottom-left' - -Config.Discord = {} -- Discord Rich Presence config -Config.Discord.AppId = '' -- This is the Application ID (Replace this with you own) -Config.Discord.LargeIcon = { -- To set this up, visit https://forum.cfx.re/t/how-to-updated-discord-rich-presence-custom-image/157686 - icon = 'logo_name', -- Here you will have to put the image name for the 'large' icon. - text = 'This is a large icon with text', -- Here you can add hover text for the 'large' icon. -} -Config.Discord.SmallIcon = { - icon = 'logo_name', -- Here you will have to put the image name for the 'small' icon. - text = 'This is a small icon with text', -- Here you can add hover text for the 'small' icon. -} -Config.Discord.FirstButton = { - text = 'First Button!', - link = 'fivem://connect/localhost:30120', -} -Config.Discord.SecondButton = { - text = 'Second Button!', - link = 'fivem://connect/localhost:30120', -} - ----@alias TableName string ----@alias ColumnName string - ----@type table -Config.CharacterDataTables = { - players = 'citizenid', - apartments = 'citizenid', - bank_accounts_new = 'id', - crypto_transactions = 'citizenid', - phone_invoices = 'citizenid', - phone_messages = 'citizenid', - playerskins = 'citizenid', - player_contacts = 'citizenid', - player_houses = 'citizenid', - player_mails = 'citizenid', - player_outfits = 'citizenid', - player_vehicles = 'citizenid', -} -- Rows to be deleted when the character is deleted - ----@type { name: string, amount: integer, metadata: fun(source: number): table } -Config.StarterItems = { -- Character starting items - { name = 'phone', amount = 1 }, - { name = 'id_card', amount = 1, metadata = function(source) - if GetResourceState("qbx_idcard") ~= 'started' then - error("qbx_idcard resource not found. Required to give an id_card as a starting item") - end - return exports.qbx_idcard:GetMetaLicense(source, {'id_card'}) - end - }, - { name = 'driver_license', amount = 1, metadata = function(source) - if GetResourceState("qbx_idcard") ~= 'started' then - error("qbx_idcard resource not found. Required to give an id_card as a starting item") - end - return exports.qbx_idcard:GetMetaLicense(source, {'driver_license'}) - end - }, -} - -Config.GiveVehicleKeys = function(src, plate) - exports.qbx_vehiclekeys:GiveKeys(src, plate) -end diff --git a/config/client.lua b/config/client.lua new file mode 100644 index 000000000..1cda554d5 --- /dev/null +++ b/config/client.lua @@ -0,0 +1,68 @@ +return { + StatusInterval = 5, -- how often to check hunger/thirst status in minutes + LoadingModelsTimeout = 10000, -- Waiting time for ox_lib to load the models before throws an error, for low specs pc + + PauseMapText = 'Powered by Qbox', -- Text shown above the map when ESC is pressed. If left empty 'FiveM' will appear + + Characters = { + UseExternalCharacters = false, -- Whether you have an external character management resource. (If true, disables the character management inside the core) + EnableDeleteButton = true, -- Whether players should be able to delete characters themselves. + StartingApartment = true, -- If set to false, skips apartment choice in the beginning (requires qbx_spawn if true) + + ProfanityWords = { + ['bad word'] = true + }, + + Locations = { -- Spawn locations for multichar, these are chosen randomly + { + pedCoords = vec4(969.25, 72.61, 116.18, 276.55), + camCoords = vec4(972.2, 72.9, 116.68, 97.27), + }, + { + pedCoords = vec4(1104.49, 195.9, -49.44, 44.22), + camCoords = vec4(1102.29, 198.14, -48.86, 225.07), + }, + { + pedCoords = vec4(-2163.87, 1134.51, -24.37, 310.05), + camCoords = vec4(-2161.7, 1136.4, -23.77, 131.52), + }, + { + pedCoords = vec4(-996.71, -68.07, -99.0, 57.61), + camCoords = vec4(-999.90, -66.30, -98.45, 241.68), + }, + { + pedCoords = vec4(-1023.45, -418.42, 67.66, 205.69), + camCoords = vec4(-1021.8, -421.7, 68.14, 27.11), + }, + { + pedCoords = vec4(2265.27, 2925.02, -84.8, 267.77), + camCoords = vec4(2268.24, 2925.02, -84.36, 90.88), + } + }, + + }, + + Discord = { + AppId = '', -- This is the Application ID (Replace this with you own) + + LargeIcon = { -- To set this up, visit https://forum.cfx.re/t/how-to-updated-discord-rich-presence-custom-image/157686 + icon = 'logo_name', -- Here you will have to put the image name for the 'large' icon. + text = 'This is a large icon with text', -- Here you can add hover text for the 'large' icon. + }, + + SmallIcon = { + icon = 'logo_name', -- Here you will have to put the image name for the 'small' icon. + text = 'This is a small icon with text', -- Here you can add hover text for the 'small' icon. + }, + + FirstButton = { + text = 'First Button!', + link = 'fivem://connect/localhost:30120', + }, + + SecondButton = { + text = 'Second Button!', + link = 'fivem://connect/localhost:30120', + } + } +} \ No newline at end of file diff --git a/config/server.lua b/config/server.lua new file mode 100644 index 000000000..3a230be3a --- /dev/null +++ b/config/server.lua @@ -0,0 +1,122 @@ +return { + UpdateInterval = 5, -- how often to update player data in minutes + + + ---@alias MoneyType 'cash' | 'bank' | 'crypto' + ---@alias Money {cash: number, bank: number, crypto: number} + ---@type Money + 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! + DontAllowMinus = { 'cash', 'crypto' }, -- Money that is not allowed going in minus + PaycheckTimeout = 10, -- The time in minutes that it will give the paycheck + PaycheckSociety = false -- If true paycheck will come from the society account that the player is employed at, requires qb-management + }, + + Player = { + HungerRate = 4.2, -- Rate at which hunger goes down. + ThirstRate = 3.8, -- Rate at which thirst goes down. + + ---@enum BloodType + Bloodtypes = { + "A+", "A-", "B+", "B-", "AB+", "AB-", "O+", "O-", + }, + + ---@alias UniqueIdType 'citizenid' | 'AccountNumber' | 'PhoneNumber' | 'FingerId' | 'WalletId' | 'SerialNumber' + ---@type table + IdentifierTypes = { + citizenid = { + valueFunction = function() + return tostring(RandomLetter(3) .. RandomNumber(5)):upper() + end, + }, + AccountNumber = { + valueFunction = function() + return 'US0' .. math.random(1, 9) .. 'QBX' .. math.random(1111, 9999) .. math.random(1111, 9999) .. math.random(11, 99) + end, + }, + PhoneNumber = { + valueFunction = function() + return math.random(100,999) .. math.random(1000000,9999999) + end, + }, + FingerId = { + valueFunction = function() + return tostring(RandomLetter(2) .. RandomNumber(3) .. RandomLetter(1) .. RandomNumber(2) .. RandomLetter(3) .. RandomNumber(4)) + end, + }, + WalletId = { + valueFunction = function() + return 'QB-' .. math.random(11111111, 99999999) + end, + }, + SerialNumber = { + valueFunction = function() + return math.random(11111111, 99999999) + end, + }, + } + }, + + + ---@alias TableName string + ---@alias ColumnName string + ---@type table + CharacterDataTables = { + players = 'citizenid', + apartments = 'citizenid', + bank_accounts_new = 'id', + crypto_transactions = 'citizenid', + phone_invoices = 'citizenid', + phone_messages = 'citizenid', + playerskins = 'citizenid', + player_contacts = 'citizenid', + player_houses = 'citizenid', + player_mails = 'citizenid', + player_outfits = 'citizenid', + player_vehicles = 'citizenid', + }, -- Rows to be deleted when the character is deleted + + + Server = { + PVP = true, -- Enable or disable pvp on the server (Ability to shoot other players) + Closed = false, -- Set server closed (no one can join except people with ace permission 'qbadmin.join') + ClosedReason = 'Server Closed', -- Reason message to display when people can't join the server + Uptime = 0, -- Time the server has been up. + Whitelist = false, -- Enable or disable whitelist on the server + WhitelistPermission = 'admin', -- Permission that's able to enter the server when the whitelist is on + Discord = '', -- Discord invite link + CheckDuplicateLicense = true, -- Check for duplicate rockstar license on join + Permissions = { 'god', 'admin', 'mod' }, -- Add as many groups as you want here after creating them in your server.cfg + }, + + Characters = { + PlayersNumberOfCharacters = { -- Define maximum amount of player characters by rockstar license (you can find this license in your server's database in the player table) + ['license2:xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'] = 5, + }, + + DefaultNumberOfCharacters = 3, -- Define maximum amount of default characters (maximum 3 characters defined by default) + }, + + ---@type { name: string, amount: integer, metadata: fun(source: number): table } + StarterItems = { -- Character starting items + { name = 'phone', amount = 1 }, + { name = 'id_card', amount = 1, metadata = function(source) + if GetResourceState('qbx_idcard') ~= 'started' then + error('qbx_idcard resource not found. Required to give an id_card as a starting item') + end + return exports.qbx_idcard:GetMetaLicense(source, {'id_card'}) + end + }, + { name = 'driver_license', amount = 1, metadata = function(source) + if GetResourceState('qbx_idcard') ~= 'started' then + error('qbx_idcard resource not found. Required to give an id_card as a starting item') + end + return exports.qbx_idcard:GetMetaLicense(source, {'driver_license'}) + end + }, + }, + + GiveVehicleKeys = function(src, plate) + exports.qbx_vehiclekeys:GiveKeys(src, plate) + end +} \ No newline at end of file diff --git a/config/shared.lua b/config/shared.lua new file mode 100644 index 000000000..d96cb56c1 --- /dev/null +++ b/config/shared.lua @@ -0,0 +1,4 @@ +return { + DefaultSpawn = vec4(-540.58, -212.02, 37.65, 208.88), + NotifyPosition = 'top-right' -- 'top' | 'top-right' | 'top-left' | 'bottom' | 'bottom-right' | 'bottom-left' +} \ No newline at end of file diff --git a/fxmanifest.lua b/fxmanifest.lua index 806c650a9..6e7ce05e0 100644 --- a/fxmanifest.lua +++ b/fxmanifest.lua @@ -10,7 +10,6 @@ shared_scripts { 'shared/locale.lua', 'locale/en.lua', 'locale/*.lua', - 'config.lua', 'import.lua', } @@ -60,9 +59,15 @@ files { 'bridge/qb/server/functions.lua', 'bridge/qb/server/main.lua', 'bridge/qb/server/player.lua', + 'config/client.lua', + 'config/shared.lua' +} + +dependencies { + 'ox_lib', + 'oxmysql', } -dependency 'oxmysql' provide 'qb-core' lua54 'yes' use_experimental_fxv2_oal 'yes' diff --git a/modules/utils.lua b/modules/utils.lua index fda7477fa..44f4f0265 100644 --- a/modules/utils.lua +++ b/modules/utils.lua @@ -216,24 +216,26 @@ if isServer then end if warp then SetPedIntoVehicle(ped, veh, -1) end - + local owner = lib.waitFor(function() local owner = NetworkGetEntityOwner(veh) if owner ~= -1 then return owner end end, 5000) - + local netId = NetworkGetNetworkIdFromEntity(veh) TriggerClientEvent('qbx_core:client:vehicleSpawned', owner, netId, props) return netId end + + local discordLink = GetConvar('qbx:discordlink', 'discord.gg/qbox') --Kick Player ---@param source Source ---@param reason string ---@param setKickReason? fun(reason: string) ---@param deferrals? Deferrals function KickWithReason(source, reason, setKickReason, deferrals) -- luacheck: ignore - reason = '\n' .. reason .. '\n🔸 Check our Discord for further information: ' .. Config.Server.Discord + reason = '\n' .. reason .. '\n🔸 Check our Discord for further information: ' .. discordLink if setKickReason then setKickReason(reason) end diff --git a/server/character.lua b/server/character.lua index 7f806c5df..d0de8963e 100644 --- a/server/character.lua +++ b/server/character.lua @@ -1,7 +1,9 @@ +local config = require 'config.server' + ---@param license2 string ---@param license? string local function getAllowedAmountOfCharacters(license2, license) - return Config.Characters.PlayersNumberOfCharacters[license2] or license and Config.Characters.PlayersNumberOfCharacters[license] or Config.Characters.DefaultNumberOfCharacters + return config.Characters.PlayersNumberOfCharacters[license2] or license and config.Characters.PlayersNumberOfCharacters[license] or config.Characters.DefaultNumberOfCharacters end ---@param source Source @@ -10,8 +12,8 @@ local function giveStarterItems(source) Wait(100) end - for i = 1, #Config.StarterItems do - local item = Config.StarterItems[i] + for i = 1, #config.StarterItems do + local item = config.StarterItems[i] if item.metadata and type(item.metadata) == 'function' then exports.ox_inventory:AddItem(source, item.name, item.amount, item.metadata(source)) else diff --git a/server/commands.lua b/server/commands.lua index 7ed4f6db1..93a32dce6 100644 --- a/server/commands.lua +++ b/server/commands.lua @@ -1,3 +1,7 @@ +local config = require 'config.server' + +GlobalState.PVPEnabled = config.Server.PVP + -- Teleport lib.addCommand('tp', { help = Lang:t("command.tp.help"), @@ -43,8 +47,8 @@ lib.addCommand('togglepvp', { help = Lang:t("command.togglepvp.help"), restricted = "group.admin" }, function() - Config.Server.PVP = not Config.Server.PVP - TriggerClientEvent('QBCore:Client:PvpHasToggled', -1, Config.Server.PVP) + config.Server.PVP = not config.Server.PVP + GlobalState.PVPEnabled = config.Server.PVP end) -- Permissions @@ -89,12 +93,12 @@ lib.addCommand('openserver', { help = Lang:t("command.openserver.help"), restricted = "group.admin" }, function(source) - if not Config.Server.Closed then + if not config.Server.Closed then Notify(source, Lang:t('error.server_already_open'), 'error') return end if HasPermission(source, 'admin') then - Config.Server.Closed = false + config.Server.Closed = false Notify(source, Lang:t('success.server_opened'), 'success') else KickWithReason(source, Lang:t("error.no_permission"), nil, nil) @@ -108,16 +112,16 @@ lib.addCommand('closeserver', { }, restricted = "group.admin" }, function(source, args) - if Config.Server.Closed then + if config.Server.Closed then Notify(source, Lang:t('error.server_already_closed'), 'error') return end if HasPermission(source, 'admin') then local reason = args[Lang:t("command.closeserver.params.reason.name")] or 'No reason specified' - Config.Server.Closed = true - Config.Server.ClosedReason = reason + config.Server.Closed = true + config.Server.ClosedReason = reason for k in pairs(QBX.Players) do - if not HasPermission(k, Config.Server.WhitelistPermission) then + if not HasPermission(k, config.Server.WhitelistPermission) then KickWithReason(k, reason, nil, nil) end end @@ -139,7 +143,7 @@ lib.addCommand('car', { if not args then return end local netId = SpawnVehicle(source, args[Lang:t("command.car.params.model.name")], nil, true) local plate = GetPlate(NetworkGetEntityFromNetworkId(netId)) - Config.GiveVehicleKeys(source, plate) + config.GiveVehicleKeys(source, plate) end) lib.addCommand('dv', { @@ -149,6 +153,26 @@ lib.addCommand('dv', { TriggerClientEvent('QBCore:Command:DeleteVehicle', source) end) +lib.addCommand('dv', { + help = Lang:t("command.dv.help"), + restricted = 'group.admin' +}, function(source) + local ped = GetPlayerPed(source) + local pedCar = GetVehiclePedIsIn(ped, false) + + if not pedCar then + local vehicle = lib.callback.await('qbx_core:client:getNearestVehicle', source) + + if vehicle then + pedCar = NetworkGetEntityFromNetworkId(vehicle) + end + end + + if pedCar and DoesEntityExist(pedCar) then + DeleteEntity(pedCar) + end +end) + -- Money lib.addCommand('givemoney', { diff --git a/server/events.lua b/server/events.lua index a05879382..adb6a99b6 100644 --- a/server/events.lua +++ b/server/events.lua @@ -1,3 +1,5 @@ +local serverConfig = require 'config.server'.Server + -- Event Handler local usedLicenses = {} @@ -11,7 +13,7 @@ AddEventHandler('chatMessage', function(_, _, message) end) AddEventHandler('playerJoining', function() - if not Config.Server.CheckDuplicateLicense then return end + if not serverConfig.CheckDuplicateLicense then return end local src = source --[[@as string]] local license = GetPlayerIdentifierByType(src, 'license2') or GetPlayerIdentifierByType(src, 'license') if not license then return end @@ -56,9 +58,9 @@ local function onPlayerConnecting(name, _, deferrals) -- Mandatory wait Wait(0) - if Config.Server.Closed then + if serverConfig.Closed then if not IsPlayerAceAllowed(src, 'qbadmin.join') then - deferrals.done(Config.Server.ClosedReason) + deferrals.done(serverConfig.ClosedReason) end end @@ -71,7 +73,7 @@ local function onPlayerConnecting(name, _, deferrals) if not license then deferrals.done(Lang:t('error.no_valid_license')) - elseif Config.Server.CheckDuplicateLicense and IsLicenseInUse(license) then + elseif serverConfig.CheckDuplicateLicense and IsLicenseInUse(license) then deferrals.done(Lang:t('error.duplicate_license')) end @@ -88,7 +90,7 @@ local function onPlayerConnecting(name, _, deferrals) end end) - if Config.Server.Whitelist and success then + if serverConfig.Whitelist and success then deferrals.update(string.format(Lang:t('info.checking_whitelisted'), name)) success, err = pcall(function() if not IsWhitelisted(src --[[@as Source]]) then @@ -146,10 +148,10 @@ RegisterNetEvent('QBCore:Server:CloseServer', function(reason) local src = source --[[@as Source]] if HasPermission(src, 'admin') then reason = reason or 'No reason specified' - Config.Server.Closed = true - Config.Server.ClosedReason = reason + serverConfig.Closed = true + serverConfig.ClosedReason = reason for k in pairs(QBX.Players) do - if not HasPermission(k, Config.Server.WhitelistPermission) then + if not HasPermission(k, serverConfig.WhitelistPermission) then KickWithReason(k, reason, nil, nil) end end @@ -161,7 +163,7 @@ end) RegisterNetEvent('QBCore:Server:OpenServer', function() local src = source --[[@as Source]] if HasPermission(src, 'admin') then - Config.Server.Closed = false + serverConfig.Closed = false else KickWithReason(src, Lang:t("error.no_permission"), nil, nil) end diff --git a/server/functions.lua b/server/functions.lua index d33232734..7e719c4fb 100644 --- a/server/functions.lua +++ b/server/functions.lua @@ -1,3 +1,6 @@ +local serverConfig = require 'config.server'.Server +local positionConfig = require 'config.shared'.NotifyPosition + -- Getters -- Get your player first and then trigger a function on them -- ex: local player = GetPlayer(source) @@ -206,8 +209,8 @@ exports('CanUseItem', CanUseItem) ---@param source Source ---@return boolean function IsWhitelisted(source) - if not Config.Server.Whitelist then return true end - if HasPermission(source, Config.Server.WhitelistPermission) then return true end + if not serverConfig.Whitelist then return true end + if HasPermission(source, serverConfig.WhitelistPermission) then return true end return false end @@ -241,7 +244,7 @@ function RemovePermission(source, permission) end else local hasUpdated = false - for _, v in pairs(Config.Server.Permissions) do + for _, v in pairs(serverConfig.Permissions) do if IsPlayerAceAllowed(source --[[@as string]], v) then lib.removePrincipal('player.' .. source, 'group.' .. v) lib.removeAce('player.' .. source, 'group.' .. v) @@ -279,7 +282,7 @@ exports('HasPermission', HasPermission) ---@return table function GetPermission(source) local perms = {} - for _, v in pairs (Config.Server.Permissions) do + for _, v in pairs (serverConfig.Permissions) do if IsPlayerAceAllowed(source --[[@as string]], v) then perms[v] = true end @@ -350,7 +353,7 @@ function Notify(source, text, notifyType, duration, subTitle, notifyPosition, no else description = text end - local position = notifyPosition or Config.NotifyPosition + local position = notifyPosition or positionConfig TriggerClientEvent('ox_lib:notify', source, { id = title, @@ -395,7 +398,7 @@ local function ExploitBan(playerId, origin) bannedBy = 'Anti Cheat' }) end) - DropPlayer(playerId --[[@as string]], Lang:t('info.exploit_banned', {discord = Config.Server.Discord})) + DropPlayer(playerId --[[@as string]], Lang:t('info.exploit_banned', {discord = serverConfig.Discord})) TriggerEvent("qb-log:server:CreateLog", "anticheat", "Anti-Cheat", "red", name .. " has been banned for exploiting " .. origin, true) end diff --git a/server/loops.lua b/server/loops.lua index a26499d1d..c28a45ac0 100644 --- a/server/loops.lua +++ b/server/loops.lua @@ -1,8 +1,10 @@ -lib.cron.new(('*/%s * * * *'):format(Config.UpdateInterval), function() +local config = require 'config.server' + +lib.cron.new(('*/%s * * * *'):format(config.UpdateInterval), function() for src, player in pairs(QBX.Players) do if player then - local newHunger = player.PlayerData.metadata.hunger - Config.Player.HungerRate - local newThirst = player.PlayerData.metadata.thirst - Config.Player.ThirstRate + local newHunger = player.PlayerData.metadata.hunger - config.Player.HungerRate + local newThirst = player.PlayerData.metadata.thirst - config.Player.ThirstRate if newHunger <= 0 then newHunger = 0 end @@ -27,7 +29,7 @@ local function pay(player) local payment = QBX.Shared.Jobs[job.name].grades[job.grade.level].payment or job.payment if payment <= 0 then return end if not QBX.Shared.Jobs[job.name].offDutyPay and not job.onduty then return end - if not Config.Money.PaycheckSociety then + if not config.Money.PaycheckSociety then sendPaycheck(player, payment) return end @@ -44,7 +46,7 @@ local function pay(player) sendPaycheck(player, payment) end -lib.cron.new(('*/%s * * * *'):format(Config.Money.PaycheckTimeout), function() +lib.cron.new(('*/%s * * * *'):format(config.Money.PaycheckTimeout), function() for _, player in pairs(QBX.Players) do pay(player) end diff --git a/server/main.lua b/server/main.lua index aede2a501..c2b6597e8 100644 --- a/server/main.lua +++ b/server/main.lua @@ -11,6 +11,7 @@ QBX.Shared = require 'shared.main' ---@type table QBX.Players = {} GlobalState.PlayerCount = 0 +GlobalState.MaxPlayers = GetConvarInt('sv_maxclients', 48) QBX.Player_Buckets = {} QBX.Entity_Buckets = {} diff --git a/server/player.lua b/server/player.lua index 4026cf28e..56f61436c 100644 --- a/server/player.lua +++ b/server/player.lua @@ -1,3 +1,6 @@ +local config = require 'config.server' +local defaultSpawn = require 'config.shared'.DefaultSpawn + ---@class PlayerData : PlayerEntity ---@field source? Source present if player is online ---@field optin? boolean present if player is online @@ -66,7 +69,7 @@ function CheckPlayerData(source, playerData) playerData.cid = playerData.charinfo?.cid or playerData.cid or 1 playerData.money = playerData.money or {} playerData.optin = playerData.optin or true - for moneytype, startamount in pairs(Config.Money.MoneyTypes) do + for moneytype, startamount in pairs(config.Money.MoneyTypes) do playerData.money[moneytype] = playerData.money[moneytype] or startamount end @@ -98,7 +101,7 @@ function CheckPlayerData(source, playerData) playerData.metadata.phone = playerData.metadata.phone or {} playerData.metadata.fitbit = playerData.metadata.fitbit or {} playerData.metadata.commandbinds = playerData.metadata.commandbinds or {} - playerData.metadata.bloodtype = playerData.metadata.bloodtype or Config.Player.Bloodtypes[math.random(1, #Config.Player.Bloodtypes)] + playerData.metadata.bloodtype = playerData.metadata.bloodtype or config.Player.Bloodtypes[math.random(1, #config.Player.Bloodtypes)] playerData.metadata.dealerrep = playerData.metadata.dealerrep or 0 playerData.metadata.craftingrep = playerData.metadata.craftingrep or 0 playerData.metadata.attachmentcraftingrep = playerData.metadata.attachmentcraftingrep or 0 @@ -155,7 +158,7 @@ function CheckPlayerData(source, playerData) playerData.gang.grade.name = playerData.gang.grade.name or 'none' playerData.gang.grade.level = playerData.gang.grade.level or 0 -- Other - playerData.position = playerData.position or Config.DefaultSpawn + playerData.position = playerData.position or defaultSpawn playerData.items = GetResourceState('qb-inventory') ~= 'missing' and exports['qb-inventory']:LoadInventory(playerData.source, playerData.citizenid) or {} return CreatePlayer(playerData --[[@as PlayerData]], Offline) end @@ -364,7 +367,7 @@ function CreatePlayer(playerData, Offline) amount = tonumber(amount) --[[@as number]] if amount < 0 then return false end if not self.PlayerData.money[moneytype] then return false end - for _, mtype in pairs(Config.Money.DontAllowMinus) do + for _, mtype in pairs(config.Money.DontAllowMinus) do if mtype == moneytype then if (self.PlayerData.money[moneytype] - amount) < 0 then return false @@ -549,7 +552,7 @@ exports('DeleteCharacter', ForceDeleteCharacter) ---@return string | number UniqueVal unique value generated function GenerateUniqueIdentifier(type) local isUnique, uniqueId - local table = Config.Player.IdentifierTypes[type] + local table = config.Player.IdentifierTypes[type] repeat uniqueId = table.valueFunction() isUnique = FetchIsUnique(type, uniqueId) diff --git a/server/storage.lua b/server/storage.lua index d9ffb7a0e..656eedecf 100644 --- a/server/storage.lua +++ b/server/storage.lua @@ -1,3 +1,6 @@ +local DefaultSpawn = require 'config.shared'.DefaultSpawn +local CharacterDataTables = require 'config.server'.CharacterDataTables + ---@class InsertBanRequest ---@field name string ---@field license? string @@ -177,8 +180,8 @@ end local function convertPosition(position) local pos = json.decode(position) - local actualPos = (not pos.x or not pos.y or not pos.z) and Config.DefaultSpawn or pos - return vec4(actualPos.x, actualPos.y, actualPos.z, actualPos.w or Config.DefaultSpawn.w) + local actualPos = (not pos.x or not pos.y or not pos.z) and DefaultSpawn or pos + return vec4(actualPos.x, actualPos.y, actualPos.z, actualPos.w or DefaultSpawn.w) end ---@param license2 string @@ -229,7 +232,7 @@ function DeletePlayerEntity(citizenId) local query = "DELETE FROM %s WHERE %s = ?" local queries = {} - for tableName, columnName in pairs(Config.CharacterDataTables) do + for tableName, columnName in pairs(CharacterDataTables) do queries[#queries + 1] = { query = query:format(tableName, columnName), values = {