diff --git a/[core]/es_extended/fxmanifest.lua b/[core]/es_extended/fxmanifest.lua index 8ab7f86da..bfc108ec0 100644 --- a/[core]/es_extended/fxmanifest.lua +++ b/[core]/es_extended/fxmanifest.lua @@ -20,25 +20,18 @@ shared_scripts { server_scripts { '@oxmysql/lib/MySQL.lua', + + 'server/modules/utils/*.lua', 'shared/config/logs.lua', + 'server/modules/*.lua', - 'server/common.lua', - 'server/modules/callback.lua', 'server/classes/player.lua', 'server/classes/overrides/*.lua', - 'server/functions.lua', - 'server/modules/onesync.lua', - 'server/modules/paycheck.lua', - - 'server/main.lua', - 'server/modules/commands.lua', - 'server/bridge/**/*.lua', - 'server/modules/actions.lua', - 'server/modules/npwd.lua', - 'server/modules/createJob.lua' + 'server/bridge/**/*.lua', } + client_scripts { 'client/common.lua', 'client/functions.lua', diff --git a/[core]/es_extended/server/bridge/inventory/oxinventory.lua b/[core]/es_extended/server/bridge/inventory/oxinventory.lua index 62b4c2afd..aac30652a 100644 --- a/[core]/es_extended/server/bridge/inventory/oxinventory.lua +++ b/[core]/es_extended/server/bridge/inventory/oxinventory.lua @@ -1,4 +1,14 @@ -if Config.CustomInventory ~= "ox" then return end +if Config.CustomInventory ~= "ox" then + if GetResourceState("ox_inventory") ~= "missing" then + Config.CustomInventory = "ox" + Config.OxInventory = true + else + return + end +end + +SetConvarReplicated("inventory:framework", "esx") +SetConvarReplicated("inventory:weight", tostring(Config.MaxWeight * 1000)) MySQL.ready(function() TriggerEvent("__cfx_export_ox_inventory_Items", function(ref) diff --git a/[core]/es_extended/server/common.lua b/[core]/es_extended/server/common.lua deleted file mode 100644 index edbc39529..000000000 --- a/[core]/es_extended/server/common.lua +++ /dev/null @@ -1,67 +0,0 @@ -ESX.Players = {} -ESX.Jobs = {} -ESX.JobsPlayerCount = {} -ESX.Items = {} -Core = {} -Core.UsableItemsCallbacks = {} -Core.RegisteredCommands = {} -Core.Pickups = {} -Core.PickupId = 0 -Core.PlayerFunctionOverrides = {} -Core.DatabaseConnected = false -Core.playersByIdentifier = {} - -Core.vehicleTypesByModel = {} - -RegisterNetEvent("esx:onPlayerSpawn", function() - ESX.Players[source].spawned = true -end) - -if Config.CustomInventory then - SetConvarReplicated("inventory:framework", "esx") - SetConvarReplicated("inventory:weight", tostring(Config.MaxWeight * 1000)) -end - -local function StartDBSync() - CreateThread(function() - local interval = 10 * 60 * 1000 - while true do - Wait(interval) - Core.SavePlayers() - end - end) -end - -MySQL.ready(function() - Core.DatabaseConnected = true - if not Config.CustomInventory then - local items = MySQL.query.await("SELECT * FROM items") - for _, v in ipairs(items) do - ESX.Items[v.name] = { label = v.label, weight = v.weight, rare = v.rare, canRemove = v.can_remove } - end - end - - ESX.RefreshJobs() - - print(("[^2INFO^7] ESX ^5Legacy %s^0 initialized!"):format(GetResourceMetadata(GetCurrentResourceName(), "version", 0))) - - StartDBSync() - if Config.EnablePaycheck then - StartPayCheck() - end -end) - -RegisterNetEvent("esx:clientLog", function(msg) - if Config.EnableDebug then - print(("[^2TRACE^7] %s^7"):format(msg)) - end -end) - -RegisterNetEvent("esx:ReturnVehicleType", function(Type, Request) - if Core.ClientCallbacks[Request] then - Core.ClientCallbacks[Request](Type) - Core.ClientCallbacks[Request] = nil - end -end) - -GlobalState.playerCount = 0 diff --git a/[core]/es_extended/server/functions.lua b/[core]/es_extended/server/functions.lua deleted file mode 100644 index 606ab0e74..000000000 --- a/[core]/es_extended/server/functions.lua +++ /dev/null @@ -1,615 +0,0 @@ ----@param msg string ----@return nil -function ESX.Trace(msg) - if Config.EnableDebug then - print(("[^2TRACE^7] %s^7"):format(msg)) - end -end - ---- Triggers an event for one or more clients. ----@param eventName string The name of the event to trigger. ----@param playerIds table|number If a number, represents a single player ID. If a table, represents an array of player IDs. ----@param ... any Additional arguments to pass to the event handler. -function ESX.TriggerClientEvent(eventName, playerIds, ...) - if type(playerIds) == "number" then - TriggerClientEvent(eventName, playerIds, ...) - return - end - - local payload = msgpack.pack_args(...) - local payloadLength = #payload - - for i = 1, #playerIds do - TriggerClientEventInternal(eventName, playerIds[i], payload, payloadLength) - end -end - ----@param name string | table ----@param group string | table ----@param cb function ----@param allowConsole? boolean ----@param suggestion? table -function ESX.RegisterCommand(name, group, cb, allowConsole, suggestion) - if type(name) == "table" then - for _, v in ipairs(name) do - ESX.RegisterCommand(v, group, cb, allowConsole, suggestion) - end - return - end - - if Core.RegisteredCommands[name] then - print(('[^3WARNING^7] Command ^5"%s" ^7already registered, overriding command'):format(name)) - - if Core.RegisteredCommands[name].suggestion then - TriggerClientEvent("chat:removeSuggestion", -1, ("/%s"):format(name)) - end - end - - if suggestion then - if not suggestion.arguments then - suggestion.arguments = {} - end - if not suggestion.help then - suggestion.help = "" - end - - TriggerClientEvent("chat:addSuggestion", -1, ("/%s"):format(name), suggestion.help, suggestion.arguments) - end - - Core.RegisteredCommands[name] = { group = group, cb = cb, allowConsole = allowConsole, suggestion = suggestion } - - RegisterCommand(name, function(playerId, args) - local command = Core.RegisteredCommands[name] - - if not command.allowConsole and playerId == 0 then - print(("[^3WARNING^7] ^5%s"):format(TranslateCap("commanderror_console"))) - else - local xPlayer, error = ESX.Players[playerId], nil - - if command.suggestion then - if command.suggestion.validate then - if #args ~= #command.suggestion.arguments then - error = TranslateCap("commanderror_argumentmismatch", #args, #command.suggestion.arguments) - end - end - - if not error and command.suggestion.arguments then - local newArgs = {} - - for k, v in ipairs(command.suggestion.arguments) do - if v.type then - if v.type == "number" then - local newArg = tonumber(args[k]) - - if newArg then - newArgs[v.name] = newArg - else - error = TranslateCap("commanderror_argumentmismatch_number", k) - end - elseif v.type == "player" or v.type == "playerId" then - local targetPlayer = tonumber(args[k]) - - if args[k] == "me" then - targetPlayer = playerId - end - - if targetPlayer then - local xTargetPlayer = ESX.GetPlayerFromId(targetPlayer) - - if xTargetPlayer then - if v.type == "player" then - newArgs[v.name] = xTargetPlayer - else - newArgs[v.name] = targetPlayer - end - else - error = TranslateCap("commanderror_invalidplayerid") - end - else - error = TranslateCap("commanderror_argumentmismatch_number", k) - end - elseif v.type == "string" then - local newArg = tonumber(args[k]) - if not newArg then - newArgs[v.name] = args[k] - else - error = TranslateCap("commanderror_argumentmismatch_string", k) - end - elseif v.type == "item" then - if ESX.Items[args[k]] then - newArgs[v.name] = args[k] - else - error = TranslateCap("commanderror_invaliditem") - end - elseif v.type == "weapon" then - if ESX.GetWeapon(args[k]) then - newArgs[v.name] = string.upper(args[k]) - else - error = TranslateCap("commanderror_invalidweapon") - end - elseif v.type == "any" then - newArgs[v.name] = args[k] - elseif v.type == "merge" then - local length = 0 - for i = 1, k - 1 do - length = length + string.len(args[i]) + 1 - end - local merge = table.concat(args, " ") - - newArgs[v.name] = string.sub(merge, length) - elseif v.type == "coordinate" then - local coord = tonumber(args[k]:match("(-?%d+%.?%d*)")) - if not coord then - error = TranslateCap("commanderror_argumentmismatch_number", k) - else - newArgs[v.name] = coord - end - end - end - - --backwards compatibility - if v.validate ~= nil and not v.validate then - error = nil - end - - if error then - break - end - end - - args = newArgs - end - end - - if error then - if playerId == 0 then - print(("[^3WARNING^7] %s^7"):format(error)) - else - xPlayer.showNotification(error) - end - else - cb(xPlayer or false, args, function(msg) - if playerId == 0 then - print(("[^3WARNING^7] %s^7"):format(msg)) - else - xPlayer.showNotification(msg) - end - end) - end - end - end, true) - - if type(group) == "table" then - for _, v in ipairs(group) do - ExecuteCommand(("add_ace group.%s command.%s allow"):format(v, name)) - end - else - ExecuteCommand(("add_ace group.%s command.%s allow"):format(group, name)) - end -end - -local function updateHealthAndArmorInMetadata(xPlayer) - local ped = GetPlayerPed(xPlayer.source) - xPlayer.setMeta("health", GetEntityHealth(ped)) - xPlayer.setMeta("armor", GetPedArmour(ped)) - xPlayer.setMeta("lastPlaytime", xPlayer.getPlayTime()) -end - ----@param xPlayer table ----@param cb? function ----@return nil -function Core.SavePlayer(xPlayer, cb) - if not xPlayer.spawned then - return cb and cb() - end - - updateHealthAndArmorInMetadata(xPlayer) - local parameters = { - json.encode(xPlayer.getAccounts(true)), - xPlayer.job.name, - xPlayer.job.grade, - xPlayer.group, - json.encode(xPlayer.getCoords(false, true)), - json.encode(xPlayer.getInventory(true)), - json.encode(xPlayer.getLoadout(true)), - json.encode(xPlayer.getMeta()), - xPlayer.identifier, - } - - MySQL.prepare( - "UPDATE `users` SET `accounts` = ?, `job` = ?, `job_grade` = ?, `group` = ?, `position` = ?, `inventory` = ?, `loadout` = ?, `metadata` = ? WHERE `identifier` = ?", - parameters, - function(affectedRows) - if affectedRows == 1 then - print(('[^2INFO^7] Saved player ^5"%s^7"'):format(xPlayer.name)) - TriggerEvent("esx:playerSaved", xPlayer.playerId, xPlayer) - end - if cb then - cb() - end - end - ) -end - ----@param cb? function ----@return nil -function Core.SavePlayers(cb) - local xPlayers = ESX.Players - if not next(xPlayers) then - return - end - - local startTime = os.time() - local parameters = {} - - for _, xPlayer in pairs(ESX.Players) do - updateHealthAndArmorInMetadata(xPlayer) - parameters[#parameters + 1] = { - json.encode(xPlayer.getAccounts(true)), - xPlayer.job.name, - xPlayer.job.grade, - xPlayer.group, - json.encode(xPlayer.getCoords(false, true)), - json.encode(xPlayer.getInventory(true)), - json.encode(xPlayer.getLoadout(true)), - json.encode(xPlayer.getMeta()), - xPlayer.identifier, - } - end - - MySQL.prepare( - "UPDATE `users` SET `accounts` = ?, `job` = ?, `job_grade` = ?, `group` = ?, `position` = ?, `inventory` = ?, `loadout` = ?, `metadata` = ? WHERE `identifier` = ?", - parameters, - function(results) - if not results then - return - end - - if type(cb) == "function" then - return cb() - end - - print(("[^2INFO^7] Saved ^5%s^7 %s over ^5%s^7 ms"):format(#parameters, #parameters > 1 and "players" or "player", ESX.Math.Round((os.time() - startTime) / 1000000, 2))) - end - ) -end - -ESX.GetPlayers = GetPlayers - -local function checkTable(key, val, player, xPlayers) - for valIndex = 1, #val do - local value = val[valIndex] - if not xPlayers[value] then - xPlayers[value] = {} - end - - if (key == "job" and player.job.name == value) or player[key] == value then - xPlayers[value][#xPlayers[value] + 1] = player - end - end -end - ----@param key? string ----@param val? string|table ----@return table -function ESX.GetExtendedPlayers(key, val) - local xPlayers = {} - if type(val) == "table" then - for _, v in pairs(ESX.Players) do - checkTable(key, val, v, xPlayers) - end - else - for _, v in pairs(ESX.Players) do - if key then - if (key == "job" and v.job.name == val) or v[key] == val then - xPlayers[#xPlayers + 1] = v - end - else - xPlayers[#xPlayers + 1] = v - end - end - end - - return xPlayers -end - ----@param key? string ----@param val? string|table ----@return number | table -function ESX.GetNumPlayers(key, val) - if not key then - return #GetPlayers() - end - - if type(val) == "table" then - local numPlayers = {} - if key == "job" then - for _, v in ipairs(val) do - numPlayers[v] = (ESX.JobsPlayerCount[v] or 0) - end - return numPlayers - end - - local filteredPlayers = ESX.GetExtendedPlayers(key, val) - for i, v in pairs(filteredPlayers) do - numPlayers[i] = (#v or 0) - end - return numPlayers - end - - if key == "job" then - return (ESX.JobsPlayerCount[val] or 0) - end - - return #ESX.GetExtendedPlayers(key, val) -end - ----@param source number ----@return table -function ESX.GetPlayerFromId(source) - return ESX.Players[tonumber(source)] -end - ----@param identifier string ----@return table -function ESX.GetPlayerFromIdentifier(identifier) - return Core.playersByIdentifier[identifier] -end - ----@param playerId number | string ----@return string -function ESX.GetIdentifier(playerId) - local fxDk = GetConvarInt("sv_fxdkMode", 0) - if fxDk == 1 then - return "ESX-DEBUG-LICENCE" - end - - playerId = tostring(playerId) - - local identifier = GetPlayerIdentifierByType(playerId, "license") - return identifier and identifier:gsub("license:", "") -end - ----@param model string|number ----@param player number ----@param cb function ----@diagnostic disable-next-line: duplicate-set-field -function ESX.GetVehicleType(model, player, cb) - model = type(model) == "string" and joaat(model) or model - - if Core.vehicleTypesByModel[model] then - return cb(Core.vehicleTypesByModel[model]) - end - - ESX.TriggerClientCallback(player, "esx:GetVehicleType", function(vehicleType) - Core.vehicleTypesByModel[model] = vehicleType - cb(vehicleType) - end, model) -end - ----@param name string ----@param title string ----@param color string ----@param message string ----@return nil -function ESX.DiscordLog(name, title, color, message) - local webHook = Config.DiscordLogs.Webhooks[name] or Config.DiscordLogs.Webhooks.default - local embedData = { - { - ["title"] = title, - ["color"] = Config.DiscordLogs.Colors[color] or Config.DiscordLogs.Colors.default, - ["footer"] = { - ["text"] = "| ESX Logs | " .. os.date(), - ["icon_url"] = "https://cdn.discordapp.com/attachments/944789399852417096/1020099828266586193/blanc-800x800.png", - }, - ["description"] = message, - ["author"] = { - ["name"] = "ESX Framework", - ["icon_url"] = "https://cdn.discordapp.com/emojis/939245183621558362.webp?size=128&quality=lossless", - }, - }, - } - PerformHttpRequest( - webHook, - function () - return - end, - "POST", - json.encode({ - username = "Logs", - embeds = embedData, - }), - { - ["Content-Type"] = "application/json", - } - ) -end - ----@param name string ----@param title string ----@param color string ----@param fields table ----@return nil -function ESX.DiscordLogFields(name, title, color, fields) - local webHook = Config.DiscordLogs.Webhooks[name] or Config.DiscordLogs.Webhooks.default - local embedData = { - { - ["title"] = title, - ["color"] = Config.DiscordLogs.Colors[color] or Config.DiscordLogs.Colors.default, - ["footer"] = { - ["text"] = "| ESX Logs | " .. os.date(), - ["icon_url"] = "https://cdn.discordapp.com/attachments/944789399852417096/1020099828266586193/blanc-800x800.png", - }, - ["fields"] = fields, - ["description"] = "", - ["author"] = { - ["name"] = "ESX Framework", - ["icon_url"] = "https://cdn.discordapp.com/emojis/939245183621558362.webp?size=128&quality=lossless", - }, - }, - } - PerformHttpRequest( - webHook, - function () - return - end, - "POST", - json.encode({ - username = "Logs", - embeds = embedData, - }), - { - ["Content-Type"] = "application/json", - } - ) -end - ----@return nil -function ESX.RefreshJobs() - local Jobs = {} - local jobs = MySQL.query.await("SELECT * FROM jobs") - - for _, v in ipairs(jobs) do - Jobs[v.name] = v - Jobs[v.name].grades = {} - end - - local jobGrades = MySQL.query.await("SELECT * FROM job_grades") - - for _, v in ipairs(jobGrades) do - if Jobs[v.job_name] then - Jobs[v.job_name].grades[tostring(v.grade)] = v - else - print(('[^3WARNING^7] Ignoring job grades for ^5"%s"^0 due to missing job'):format(v.job_name)) - end - end - - for _, v in pairs(Jobs) do - if ESX.Table.SizeOf(v.grades) == 0 then - Jobs[v.name] = nil - print(('[^3WARNING^7] Ignoring job ^5"%s"^0 due to no job grades found'):format(v.name)) - end - end - - if not Jobs then - -- Fallback data, if no jobs exist - ESX.Jobs["unemployed"] = { label = "Unemployed", grades = { ["0"] = { grade = 0, label = "Unemployed", salary = 200, skin_male = {}, skin_female = {} } } } - else - ESX.Jobs = Jobs - end -end - ----@param item string ----@param cb function ----@return nil -function ESX.RegisterUsableItem(item, cb) - Core.UsableItemsCallbacks[item] = cb -end - ----@param source number ----@param item string ----@param ... any ----@return nil -function ESX.UseItem(source, item, ...) - if ESX.Items[item] then - local itemCallback = Core.UsableItemsCallbacks[item] - - if itemCallback then - local success, result = pcall(itemCallback, source, item, ...) - - if not success then - return result and print(result) or print(('[^3WARNING^7] An error occured when using item ^5"%s"^7! This was not caused by ESX.'):format(item)) - end - end - else - print(('[^3WARNING^7] Item ^5"%s"^7 was used but does not exist!'):format(item)) - end -end - ----@param index string ----@param overrides table ----@return nil -function ESX.RegisterPlayerFunctionOverrides(index, overrides) - Core.PlayerFunctionOverrides[index] = overrides -end - ----@param index string ----@return nil -function ESX.SetPlayerFunctionOverride(index) - if not index or not Core.PlayerFunctionOverrides[index] then - return print("[^3WARNING^7] No valid index provided.") - end - - Config.PlayerFunctionOverride = index -end - ----@param item string ----@return string? ----@diagnostic disable-next-line: duplicate-set-field -function ESX.GetItemLabel(item) - if ESX.Items[item] then - return ESX.Items[item].label - else - print(("[^3WARNING^7] Attemting to get invalid Item -> ^5%s^7"):format(item)) - end -end - ----@return table -function ESX.GetJobs() - return ESX.Jobs -end - ----@return table -function ESX.GetUsableItems() - local Usables = {} - for k in pairs(Core.UsableItemsCallbacks) do - Usables[k] = true - end - return Usables -end - -if not Config.CustomInventory then - ---@param itemType string - ---@param name string - ---@param count integer - ---@param label string - ---@param playerId number - ---@param components? string | table - ---@param tintIndex? integer - ---@param coords? table | vector3 - ---@return nil - function ESX.CreatePickup(itemType, name, count, label, playerId, components, tintIndex, coords) - local pickupId = (Core.PickupId == 65635 and 0 or Core.PickupId + 1) - local xPlayer = ESX.Players[playerId] - coords = ((type(coords) == "vector3" or type(coords) == "vector4") and coords.xyz or xPlayer.getCoords(true)) - - Core.Pickups[pickupId] = { type = itemType, name = name, count = count, label = label, coords = coords } - - if itemType == "item_weapon" then - Core.Pickups[pickupId].components = components - Core.Pickups[pickupId].tintIndex = tintIndex - end - - TriggerClientEvent("esx:createPickup", -1, pickupId, label, coords, itemType, name, components, tintIndex) - Core.PickupId = pickupId - end -end - ----@param job string ----@param grade string ----@return boolean -function ESX.DoesJobExist(job, grade) - return (ESX.Jobs[job] and ESX.Jobs[job].grades[tostring(grade)] ~= nil) or false -end - ----@param playerId string | number ----@return boolean -function Core.IsPlayerAdmin(playerId) - playerId = tostring(playerId) - if (IsPlayerAceAllowed(playerId, "command") or GetConvar("sv_lan", "") == "true") then - return true - end - - local xPlayer = ESX.Players[playerId] - return (xPlayer and Config.AdminGroups[xPlayer.group] and true) or false -end diff --git a/[core]/es_extended/server/main.lua b/[core]/es_extended/server/main.lua deleted file mode 100644 index 57108b323..000000000 --- a/[core]/es_extended/server/main.lua +++ /dev/null @@ -1,715 +0,0 @@ -SetMapName("San Andreas") -SetGameType("ESX Legacy") - -local oneSyncState = GetConvar("onesync", "off") -local newPlayer = "INSERT INTO `users` SET `accounts` = ?, `identifier` = ?, `group` = ?" -local loadPlayer = "SELECT `accounts`, `job`, `job_grade`, `group`, `position`, `inventory`, `skin`, `loadout`, `metadata`" - -if Config.Multichar then - newPlayer = newPlayer .. ", `firstname` = ?, `lastname` = ?, `dateofbirth` = ?, `sex` = ?, `height` = ?" -end - -if Config.StartingInventoryItems then - newPlayer = newPlayer .. ", `inventory` = ?" -end - -if Config.Multichar or Config.Identity then - loadPlayer = loadPlayer .. ", `firstname`, `lastname`, `dateofbirth`, `sex`, `height`" -end - -loadPlayer = loadPlayer .. " FROM `users` WHERE identifier = ?" - -local function createESXPlayer(identifier, playerId, data) - local accounts = {} - - for account, money in pairs(Config.StartingAccountMoney) do - accounts[account] = money - end - - local defaultGroup = "user" - if Core.IsPlayerAdmin(playerId) then - print(("[^2INFO^0] Player ^5%s^0 Has been granted admin permissions via ^5Ace Perms^7."):format(playerId)) - defaultGroup = "admin" - end - - local parameters = Config.Multichar and { json.encode(accounts), identifier, defaultGroup, data.firstname, data.lastname, data.dateofbirth, data.sex, data.height } or { json.encode(accounts), identifier, defaultGroup } - - if Config.StartingInventoryItems then - table.insert(parameters, json.encode(Config.StartingInventoryItems)) - end - - MySQL.prepare(newPlayer, parameters, function() - loadESXPlayer(identifier, playerId, true) - end) -end - - -local function onPlayerJoined(playerId) - local identifier = ESX.GetIdentifier(playerId) - if not identifier then - return DropPlayer(playerId, "there was an error loading your character!\nError code: identifier-missing-ingame\n\nThe cause of this error is not known, your identifier could not be found. Please come back later or report this problem to the server administration team.") - end - - if ESX.GetPlayerFromIdentifier(identifier) then - DropPlayer( - playerId, - ("there was an error loading your character!\nError code: identifier-active-ingame\n\nThis error is caused by a player on this server who has the same identifier as you have. Make sure you are not playing on the same Rockstar account.\n\nYour Rockstar identifier: %s"):format( - identifier - ) - ) - else - local result = MySQL.scalar.await("SELECT 1 FROM users WHERE identifier = ?", { identifier }) - if result then - loadESXPlayer(identifier, playerId, false) - else - createESXPlayer(identifier, playerId) - end - end -end - -if Config.Multichar then - AddEventHandler("esx:onPlayerJoined", function(src, char, data) - while not next(ESX.Jobs) do - Wait(50) - end - - if not ESX.Players[src] then - local identifier = char .. ":" .. ESX.GetIdentifier(src) - if data then - createESXPlayer(identifier, src, data) - else - loadESXPlayer(identifier, src, false) - end - end - end) -else - RegisterNetEvent("esx:onPlayerJoined", function() - local _source = source - while not next(ESX.Jobs) do - Wait(50) - end - - if not ESX.Players[_source] then - onPlayerJoined(_source) - end - end) -end - -if not Config.Multichar then - AddEventHandler("playerConnecting", function(_, _, deferrals) - local playerId = source - deferrals.defer() - Wait(0) -- Required - local identifier = ESX.GetIdentifier(playerId) - - - if not SetEntityOrphanMode then - return deferrals.done(("[ESX] ESX Requires a minimum Artifact version of 10188, Please update your server.")) - end - - if oneSyncState == "off" or oneSyncState == "legacy" then - return deferrals.done(("[ESX] ESX Requires Onesync Infinity to work. This server currently has Onesync set to: %s"):format(oneSyncState)) - end - - if not Core.DatabaseConnected then - return deferrals.done("[ESX] OxMySQL Was Unable To Connect to your database. Please make sure it is turned on and correctly configured in your server.cfg") - end - - if identifier then - if ESX.GetPlayerFromIdentifier(identifier) then - return deferrals.done( - ("[ESX] There was an error loading your character!\nError code: identifier-active\n\nThis error is caused by a player on this server who has the same identifier as you have. Make sure you are not playing on the same account.\n\nYour identifier: %s"):format(identifier) - ) - else - return deferrals.done() - end - else - return deferrals.done("[ESX] There was an error loading your character!\nError code: identifier-missing\n\nThe cause of this error is not known, your identifier could not be found. Please come back later or report this problem to the server administration team.") - end - end) -end - -function loadESXPlayer(identifier, playerId, isNew) - local userData = { - accounts = {}, - inventory = {}, - loadout = {}, - weight = 0, - name = GetPlayerName(playerId), - identifier = identifier, - firstName = "John", - lastName = "Doe", - dateofbirth = "01/01/2000", - height = 120, - dead = false, - } - - local result = MySQL.prepare.await(loadPlayer, { identifier }) - - -- Accounts - local accounts = result.accounts - accounts = (accounts and accounts ~= "") and json.decode(accounts) or {} - - for account, data in pairs(Config.Accounts) do - data.round = data.round or data.round == nil - - local index = #userData.accounts + 1 - userData.accounts[index] = { - name = account, - money = accounts[account] or Config.StartingAccountMoney[account] or 0, - label = data.label, - round = data.round, - index = index, - } - end - - -- Job - local job, grade = result.job, tostring(result.job_grade) - - if not ESX.DoesJobExist(job, grade) then - print(("[^3WARNING^7] Ignoring invalid job for ^5%s^7 [job: ^5%s^7, grade: ^5%s^7]"):format(identifier, job, grade)) - job, grade = "unemployed", "0" - end - - local jobObject, gradeObject = ESX.Jobs[job], ESX.Jobs[job].grades[grade] - - userData.job = { - id = jobObject.id, - name = jobObject.name, - label = jobObject.label, - - grade = tonumber(grade), - grade_name = gradeObject.name, - grade_label = gradeObject.label, - grade_salary = gradeObject.salary, - - skin_male = gradeObject.skin_male and json.decode(gradeObject.skin_male) or {}, - skin_female = gradeObject.skin_female and json.decode(gradeObject.skin_female) or {}, - } - - -- Inventory - if not Config.CustomInventory then - local inventory = (result.inventory and result.inventory ~= "") and json.decode(result.inventory) or {} - - for name, item in pairs(ESX.Items) do - local count = inventory[name] or 0 - userData.weight += (count * item.weight) - - userData.inventory[#userData.inventory + 1] = { - name = name, - count = count, - label = item.label, - weight = item.weight, - usable = Core.UsableItemsCallbacks[name] ~= nil, - rare = item.rare, - canRemove = item.canRemove, - } - end - table.sort(userData.inventory, function(a, b) - return a.label < b.label - end) - elseif result.inventory and result.inventory ~= "" then - userData.inventory = json.decode(result.inventory) - end - - -- Group - if result.group then - if result.group == "superadmin" then - userData.group = "admin" - print("[^3WARNING^7] ^5Superadmin^7 detected, setting group to ^5admin^7") - else - userData.group = result.group - end - else - userData.group = "user" - end - - -- Loadout - if not Config.CustomInventory then - if result.loadout and result.loadout ~= "" then - - local loadout = json.decode(result.loadout) - for name, weapon in pairs(loadout) do - local label = ESX.GetWeaponLabel(name) - - if label then - userData.loadout[#userData.loadout + 1] = { - name = name, - ammo = weapon.ammo, - label = label, - components = weapon.components or {}, - tintIndex = weapon.tintIndex or 0, - } - end - end - end - end - - -- Position - userData.coords = json.decode(result.position) or Config.DefaultSpawns[ESX.Math.Random(1,#Config.DefaultSpawns)] - - -- Skin - userData.skin = (result.skin and result.skin ~= "") and json.decode(result.skin) or { sex = userData.sex == "f" and 1 or 0 } - - -- Metadata - userData.metadata = (result.metadata and result.metadata ~= "") and json.decode(result.metadata) or {} - - -- xPlayer Creation - local xPlayer = CreateExtendedPlayer(playerId, identifier, userData.group, userData.accounts, userData.inventory, userData.weight, userData.job, userData.loadout, GetPlayerName(playerId), userData.coords, userData.metadata) - - GlobalState["playerCount"] = GlobalState["playerCount"] + 1 - ESX.Players[playerId] = xPlayer - Core.playersByIdentifier[identifier] = xPlayer - - -- Identity - if result.firstname and result.firstname ~= "" then - userData.firstName = result.firstname - userData.lastName = result.lastname - - local name = ("%s %s"):format(result.firstname, result.lastname) - userData.name = name - - xPlayer.set("firstName", result.firstname) - xPlayer.set("lastName", result.lastname) - xPlayer.setName(name) - - if result.dateofbirth then - userData.dateofbirth = result.dateofbirth - xPlayer.set("dateofbirth", result.dateofbirth) - end - if result.sex then - userData.sex = result.sex - xPlayer.set("sex", result.sex) - end - if result.height then - userData.height = result.height - xPlayer.set("height", result.height) - end - end - - TriggerEvent("esx:playerLoaded", playerId, xPlayer, isNew) - userData.money = xPlayer.getMoney() - userData.maxWeight = xPlayer.getMaxWeight() - xPlayer.triggerEvent("esx:playerLoaded", userData, isNew, userData.skin) - - if not Config.CustomInventory then - xPlayer.triggerEvent("esx:createMissingPickups", Core.Pickups) - elseif setPlayerInventory then - setPlayerInventory(playerId, xPlayer, userData.inventory, isNew) - end - - xPlayer.triggerEvent("esx:registerSuggestions", Core.RegisteredCommands) - print(('[^2INFO^0] Player ^5"%s"^0 has connected to the server. ID: ^5%s^7'):format(xPlayer.getName(), playerId)) -end - -AddEventHandler("chatMessage", function(playerId, _, message) - local xPlayer = ESX.GetPlayerFromId(playerId) - if xPlayer and message:sub(1, 1) == "/" and playerId > 0 then - CancelEvent() - local commandName = message:sub(1):gmatch("%w+")() - xPlayer.showNotification(TranslateCap("commanderror_invalidcommand", commandName)) - end -end) - -AddEventHandler("playerDropped", function(reason) - local playerId = source - local xPlayer = ESX.GetPlayerFromId(playerId) - - if xPlayer then - TriggerEvent("esx:playerDropped", playerId, reason) - local job = xPlayer.getJob().name - local currentJob = ESX.JobsPlayerCount[job] - ESX.JobsPlayerCount[job] = ((currentJob and currentJob > 0) and currentJob or 1) - 1 - - GlobalState[("%s:count"):format(job)] = ESX.JobsPlayerCount[job] - Core.playersByIdentifier[xPlayer.identifier] = nil - - Core.SavePlayer(xPlayer, function() - GlobalState["playerCount"] = GlobalState["playerCount"] - 1 - ESX.Players[playerId] = nil - end) - end -end) - -AddEventHandler("esx:playerLoaded", function(_, xPlayer) - local job = xPlayer.getJob().name - local jobKey = ("%s:count"):format(job) - - ESX.JobsPlayerCount[job] = (ESX.JobsPlayerCount[job] or 0) + 1 - GlobalState[jobKey] = ESX.JobsPlayerCount[job] -end) - -AddEventHandler("esx:setJob", function(_, job, lastJob) - local lastJobKey = ("%s:count"):format(lastJob.name) - local jobKey = ("%s:count"):format(job.name) - local currentLastJob = ESX.JobsPlayerCount[lastJob.name] - - ESX.JobsPlayerCount[lastJob.name] = ((currentLastJob and currentLastJob > 0) and currentLastJob or 1) - 1 - ESX.JobsPlayerCount[job.name] = (ESX.JobsPlayerCount[job.name] or 0) + 1 - - GlobalState[lastJobKey] = ESX.JobsPlayerCount[lastJob.name] - GlobalState[jobKey] = ESX.JobsPlayerCount[job.name] -end) - -AddEventHandler("esx:playerLogout", function(playerId, cb) - local xPlayer = ESX.GetPlayerFromId(playerId) - if xPlayer then - TriggerEvent("esx:playerDropped", playerId) - - Core.playersByIdentifier[xPlayer.identifier] = nil - Core.SavePlayer(xPlayer, function() - GlobalState["playerCount"] = GlobalState["playerCount"] - 1 - ESX.Players[playerId] = nil - if cb then - cb() - end - end) - end - TriggerClientEvent("esx:onPlayerLogout", playerId) -end) - -if not Config.CustomInventory then - RegisterNetEvent("esx:updateWeaponAmmo", function(weaponName, ammoCount) - local xPlayer = ESX.GetPlayerFromId(source) - - if xPlayer then - xPlayer.updateWeaponAmmo(weaponName, ammoCount) - end - end) - - RegisterNetEvent("esx:giveInventoryItem", function(target, itemType, itemName, itemCount) - local playerId = source - local sourceXPlayer = ESX.GetPlayerFromId(playerId) - local targetXPlayer = ESX.GetPlayerFromId(target) - local distance = #(GetEntityCoords(GetPlayerPed(playerId)) - GetEntityCoords(GetPlayerPed(target))) - if not sourceXPlayer or not targetXPlayer or distance > Config.DistanceGive then - print(("[^3WARNING^7] Player Detected Cheating: ^5%s^7"):format(GetPlayerName(playerId))) - return - end - - if itemType == "item_standard" then - local sourceItem = sourceXPlayer.getInventoryItem(itemName) - - if itemCount < 1 or sourceItem.count < itemCount then - return sourceXPlayer.showNotification(TranslateCap("imp_invalid_quantity")) - end - - if not targetXPlayer.canCarryItem(itemName, itemCount) then - return sourceXPlayer.showNotification(TranslateCap("ex_inv_lim", targetXPlayer.name)) - end - - sourceXPlayer.removeInventoryItem(itemName, itemCount) - targetXPlayer.addInventoryItem(itemName, itemCount) - - sourceXPlayer.showNotification(TranslateCap("gave_item", itemCount, sourceItem.label, targetXPlayer.name)) - targetXPlayer.showNotification(TranslateCap("received_item", itemCount, sourceItem.label, sourceXPlayer.name)) - elseif itemType == "item_account" then - if itemCount < 1 or sourceXPlayer.getAccount(itemName).money < itemCount then - return sourceXPlayer.showNotification(TranslateCap("imp_invalid_amount")) - end - - sourceXPlayer.removeAccountMoney(itemName, itemCount, "Gave to " .. targetXPlayer.name) - targetXPlayer.addAccountMoney(itemName, itemCount, "Received from " .. sourceXPlayer.name) - - sourceXPlayer.showNotification(TranslateCap("gave_account_money", ESX.Math.GroupDigits(itemCount), Config.Accounts[itemName].label, targetXPlayer.name)) - targetXPlayer.showNotification(TranslateCap("received_account_money", ESX.Math.GroupDigits(itemCount), Config.Accounts[itemName].label, sourceXPlayer.name)) - elseif itemType == "item_weapon" then - if not sourceXPlayer.hasWeapon(itemName) then - return - end - - local weaponLabel = ESX.GetWeaponLabel(itemName) - if targetXPlayer.hasWeapon(itemName) then - sourceXPlayer.showNotification(TranslateCap("gave_weapon_hasalready", targetXPlayer.name, weaponLabel)) - targetXPlayer.showNotification(TranslateCap("received_weapon_hasalready", sourceXPlayer.name, weaponLabel)) - return - end - - local _, weapon = sourceXPlayer.getWeapon(itemName) - local _, weaponObject = ESX.GetWeapon(itemName) - itemCount = weapon.ammo - local weaponComponents = ESX.Table.Clone(weapon.components) - local weaponTint = weapon.tintIndex - - if weaponTint then - targetXPlayer.setWeaponTint(itemName, weaponTint) - end - - if weaponComponents then - for _, v in pairs(weaponComponents) do - targetXPlayer.addWeaponComponent(itemName, v) - end - end - - sourceXPlayer.removeWeapon(itemName) - targetXPlayer.addWeapon(itemName, itemCount) - - if weaponObject.ammo and itemCount > 0 then - local ammoLabel = weaponObject.ammo.label - sourceXPlayer.showNotification(TranslateCap("gave_weapon_withammo", weaponLabel, itemCount, ammoLabel, targetXPlayer.name)) - targetXPlayer.showNotification(TranslateCap("received_weapon_withammo", weaponLabel, itemCount, ammoLabel, sourceXPlayer.name)) - else - sourceXPlayer.showNotification(TranslateCap("gave_weapon", weaponLabel, targetXPlayer.name)) - targetXPlayer.showNotification(TranslateCap("received_weapon", weaponLabel, sourceXPlayer.name)) - end - elseif itemType == "item_ammo" then - if not sourceXPlayer.hasWeapon(itemName) then - return - end - - local _, weapon = sourceXPlayer.getWeapon(itemName) - - if not targetXPlayer.hasWeapon(itemName) then - sourceXPlayer.showNotification(TranslateCap("gave_weapon_noweapon", targetXPlayer.name)) - targetXPlayer.showNotification(TranslateCap("received_weapon_noweapon", sourceXPlayer.name, weapon.label)) - return - end - - local _, weaponObject = ESX.GetWeapon(itemName) - - if not weaponObject.ammo then return end - - local ammoLabel = weaponObject.ammo.label - if weapon.ammo >= itemCount then - sourceXPlayer.removeWeaponAmmo(itemName, itemCount) - targetXPlayer.addWeaponAmmo(itemName, itemCount) - - sourceXPlayer.showNotification(TranslateCap("gave_weapon_ammo", itemCount, ammoLabel, weapon.label, targetXPlayer.name)) - targetXPlayer.showNotification(TranslateCap("received_weapon_ammo", itemCount, ammoLabel, weapon.label, sourceXPlayer.name)) - end - end - end) - - RegisterNetEvent("esx:removeInventoryItem", function(itemType, itemName, itemCount) - local playerId = source - local xPlayer = ESX.GetPlayerFromId(playerId) - - if itemType == "item_standard" then - if not itemCount or itemCount < 1 then - return xPlayer.showNotification(TranslateCap("imp_invalid_quantity")) - end - - local xItem = xPlayer.getInventoryItem(itemName) - - if itemCount > xItem.count or xItem.count < 1 then - return xPlayer.showNotification(TranslateCap("imp_invalid_quantity")) - end - - xPlayer.removeInventoryItem(itemName, itemCount) - local pickupLabel = ("%s [%s]"):format(xItem.label, itemCount) - ESX.CreatePickup("item_standard", itemName, itemCount, pickupLabel, playerId) - xPlayer.showNotification(TranslateCap("threw_standard", itemCount, xItem.label)) - elseif itemType == "item_account" then - if itemCount == nil or itemCount < 1 then - return xPlayer.showNotification(TranslateCap("imp_invalid_amount")) - end - - local account = xPlayer.getAccount(itemName) - - if itemCount > account.money or account.money < 1 then - return xPlayer.showNotification(TranslateCap("imp_invalid_amount")) - end - - xPlayer.removeAccountMoney(itemName, itemCount, "Threw away") - local pickupLabel = ("%s [%s]"):format(account.label, TranslateCap("locale_currency", ESX.Math.GroupDigits(itemCount))) - ESX.CreatePickup("item_account", itemName, itemCount, pickupLabel, playerId) - xPlayer.showNotification(TranslateCap("threw_account", ESX.Math.GroupDigits(itemCount), string.lower(account.label))) - elseif itemType == "item_weapon" then - itemName = string.upper(itemName) - - if not xPlayer.hasWeapon(itemName) then return end - - local _, weapon = xPlayer.getWeapon(itemName) - local _, weaponObject = ESX.GetWeapon(itemName) - -- luacheck: ignore weaponPickupLabel - local weaponPickupLabel = "" - local components = ESX.Table.Clone(weapon.components) - xPlayer.removeWeapon(itemName) - - if weaponObject.ammo and weapon.ammo > 0 then - local ammoLabel = weaponObject.ammo.label - weaponPickupLabel = ("%s [%s %s]"):format(weapon.label, weapon.ammo, ammoLabel) - xPlayer.showNotification(TranslateCap("threw_weapon_ammo", weapon.label, weapon.ammo, ammoLabel)) - else - weaponPickupLabel = ("%s"):format(weapon.label) - xPlayer.showNotification(TranslateCap("threw_weapon", weapon.label)) - end - - ESX.CreatePickup("item_weapon", itemName, weapon.ammo, weaponPickupLabel, playerId, components, weapon.tintIndex) - end - end) - - RegisterNetEvent("esx:useItem", function(itemName) - local source = source - local xPlayer = ESX.GetPlayerFromId(source) - local count = xPlayer.getInventoryItem(itemName).count - - if count < 1 then - return xPlayer.showNotification(TranslateCap("act_imp")) - end - - ESX.UseItem(source, itemName) - end) - - RegisterNetEvent("esx:onPickup", function(pickupId) - local pickup, xPlayer, success = Core.Pickups[pickupId], ESX.GetPlayerFromId(source) - - if not pickup then return end - - local playerPickupDistance = #(pickup.coords - xPlayer.getCoords(true)) - if playerPickupDistance > 5.0 then - print(("[^3WARNING^7] Player Detected Cheating (Out of range pickup): ^5%s^7"):format(xPlayer.getIdentifier())) - return - end - - if pickup.type == "item_standard" then - if not xPlayer.canCarryItem(pickup.name, pickup.count) then - return xPlayer.showNotification(TranslateCap("threw_cannot_pickup")) - end - - xPlayer.addInventoryItem(pickup.name, pickup.count) - success = true - elseif pickup.type == "item_account" then - success = true - xPlayer.addAccountMoney(pickup.name, pickup.count, "Picked up") - elseif pickup.type == "item_weapon" then - if xPlayer.hasWeapon(pickup.name) then - return xPlayer.showNotification(TranslateCap("threw_weapon_already")) - end - - success = true - xPlayer.addWeapon(pickup.name, pickup.count) - xPlayer.setWeaponTint(pickup.name, pickup.tintIndex) - - for _, v in ipairs(pickup.components) do - xPlayer.addWeaponComponent(pickup.name, v) - end - end - - if success then - Core.Pickups[pickupId] = nil - TriggerClientEvent("esx:removePickup", -1, pickupId) - end - end) -end - -ESX.RegisterServerCallback("esx:getPlayerData", function(source, cb) - local xPlayer = ESX.GetPlayerFromId(source) - - cb({ - identifier = xPlayer.identifier, - accounts = xPlayer.getAccounts(), - inventory = xPlayer.getInventory(), - job = xPlayer.getJob(), - loadout = xPlayer.getLoadout(), - money = xPlayer.getMoney(), - position = xPlayer.getCoords(true), - metadata = xPlayer.getMeta(), - }) -end) - -ESX.RegisterServerCallback("esx:isUserAdmin", function(source, cb) - cb(Core.IsPlayerAdmin(source)) -end) - -ESX.RegisterServerCallback("esx:getGameBuild", function(_, cb) - cb(tonumber(GetConvar("sv_enforceGameBuild", "1604"))) -end) - -ESX.RegisterServerCallback("esx:getOtherPlayerData", function(_, cb, target) - local xPlayer = ESX.GetPlayerFromId(target) - - cb({ - identifier = xPlayer.identifier, - accounts = xPlayer.getAccounts(), - inventory = xPlayer.getInventory(), - job = xPlayer.getJob(), - loadout = xPlayer.getLoadout(), - money = xPlayer.getMoney(), - position = xPlayer.getCoords(true), - metadata = xPlayer.getMeta(), - }) -end) - -ESX.RegisterServerCallback("esx:getPlayerNames", function(source, cb, players) - players[source] = nil - - for playerId, _ in pairs(players) do - local xPlayer = ESX.GetPlayerFromId(playerId) - - if xPlayer then - players[playerId] = xPlayer.getName() - else - players[playerId] = nil - end - end - - cb(players) -end) - -ESX.RegisterServerCallback("esx:spawnVehicle", function(source, cb, vehData) - local ped = GetPlayerPed(source) - ESX.OneSync.SpawnVehicle(vehData.model or `ADDER`, vehData.coords or GetEntityCoords(ped), vehData.coords.w or 0.0, vehData.props or {}, function(id) - if vehData.warp then - local vehicle = NetworkGetEntityFromNetworkId(id) - local timeout = 0 - while GetVehiclePedIsIn(ped, false) ~= vehicle and timeout <= 15 do - Wait(0) - TaskWarpPedIntoVehicle(ped, vehicle, -1) - timeout += 1 - end - end - cb(id) - end) -end) - -AddEventHandler("txAdmin:events:scheduledRestart", function(eventData) - if eventData.secondsRemaining == 60 then - CreateThread(function() - Wait(50000) - Core.SavePlayers() - end) - end -end) - -AddEventHandler("txAdmin:events:serverShuttingDown", function() - Core.SavePlayers() -end) - -local DoNotUse = { - ["essentialmode"] = true, - ["es_admin2"] = true, - ["basic-gamemode"] = true, - ["mapmanager"] = true, - ["fivem-map-skater"] = true, - ["fivem-map-hipster"] = true, - ["qb-core"] = true, - ["default_spawnpoint"] = true, -} - -AddEventHandler("onResourceStart", function(key) - if DoNotUse[string.lower(key)] then - while GetResourceState(key) ~= "started" do - Wait(0) - end - - StopResource(key) - error(("WE STOPPED A RESOURCE THAT WILL BREAK ^1ESX^1, PLEASE REMOVE ^5%s^1"):format(key)) - end - - if not SetEntityOrphanMode then - CreateThread(function() - while true do - error("ESX Requires a minimum Artifact version of 10188, Please update your server.") - Wait(60 * 1000) - end - end) - end -end) - -for key in pairs(DoNotUse) do - if GetResourceState(key) == "started" or GetResourceState(key) == "starting" then - StopResource(key) - error(("WE STOPPED A RESOURCE THAT WILL BREAK ^1ESX^1, PLEASE REMOVE ^5%s^1"):format(key)) - end -end diff --git a/[core]/es_extended/server/modules/actions.lua b/[core]/es_extended/server/modules/actions.lua deleted file mode 100644 index 52eeeb5b1..000000000 --- a/[core]/es_extended/server/modules/actions.lua +++ /dev/null @@ -1,28 +0,0 @@ -RegisterServerEvent("esx:playerPedChanged") -RegisterServerEvent("esx:playerJumping") -RegisterServerEvent("esx:enteringVehicle") -RegisterServerEvent("esx:enteringVehicleAborted") -RegisterServerEvent("esx:enteredVehicle") -RegisterServerEvent("esx:exitedVehicle") - -if Config.EnableDebug then - AddEventHandler("esx:playerPedChanged", function(netId) - print("esx:playerPedChanged", source, netId) - end) - - AddEventHandler("esx:enteringVehicle", function(plate, seat, netId) - print("esx:enteringVehicle", "source", source, "plate", plate, "seat", seat, "netId", netId) - end) - - AddEventHandler("esx:enteringVehicleAborted", function() - print("esx:enteringVehicleAborted", source) - end) - - AddEventHandler("esx:enteredVehicle", function(plate, seat, displayName, netId) - print("esx:enteredVehicle", "source", source, "plate", plate, "seat", seat, "displayName", displayName, "netId", netId) - end) - - AddEventHandler("esx:exitedVehicle", function(plate, seat, displayName, netId) - print("esx:exitedVehicle", "source", source, "plate", plate, "seat", seat, "displayName", displayName, "netId", netId) - end) -end diff --git a/[core]/es_extended/server/modules/commands.lua b/[core]/es_extended/server/modules/commands.lua index 4e9d68625..27b03aa71 100644 --- a/[core]/es_extended/server/modules/commands.lua +++ b/[core]/es_extended/server/modules/commands.lua @@ -1,3 +1,178 @@ +Core.RegisteredCommands = {} + +---@param name string | table +---@param group string | table +---@param cb function +---@param allowConsole? boolean +---@param suggestion? table +function ESX.RegisterCommand(name, group, cb, allowConsole, suggestion) + if type(name) == "table" then + for _, v in ipairs(name) do + ESX.RegisterCommand(v, group, cb, allowConsole, suggestion) + end + return + end + + if Core.RegisteredCommands[name] then + print(('[^3WARNING^7] Command ^5"%s" ^7already registered, overriding command'):format(name)) + + if Core.RegisteredCommands[name].suggestion then + TriggerClientEvent("chat:removeSuggestion", -1, ("/%s"):format(name)) + end + end + + if suggestion then + if not suggestion.arguments then + suggestion.arguments = {} + end + if not suggestion.help then + suggestion.help = "" + end + + TriggerClientEvent("chat:addSuggestion", -1, ("/%s"):format(name), suggestion.help, suggestion.arguments) + end + + Core.RegisteredCommands[name] = { group = group, cb = cb, allowConsole = allowConsole, suggestion = suggestion } + + RegisterCommand(name, function(playerId, args) + local command = Core.RegisteredCommands[name] + + if not command.allowConsole and playerId == 0 then + print(("[^3WARNING^7] ^5%s"):format(TranslateCap("commanderror_console"))) + else + local xPlayer, error = ESX.Players[playerId], nil + + if command.suggestion then + if command.suggestion.validate then + if #args ~= #command.suggestion.arguments then + error = TranslateCap("commanderror_argumentmismatch", #args, #command.suggestion.arguments) + end + end + + if not error and command.suggestion.arguments then + local newArgs = {} + + for k, v in ipairs(command.suggestion.arguments) do + if v.type then + if v.type == "number" then + local newArg = tonumber(args[k]) + + if newArg then + newArgs[v.name] = newArg + else + error = TranslateCap("commanderror_argumentmismatch_number", k) + end + elseif v.type == "player" or v.type == "playerId" then + local targetPlayer = tonumber(args[k]) + + if args[k] == "me" then + targetPlayer = playerId + end + + if targetPlayer then + local xTargetPlayer = ESX.GetPlayerFromId(targetPlayer) + + if xTargetPlayer then + if v.type == "player" then + newArgs[v.name] = xTargetPlayer + else + newArgs[v.name] = targetPlayer + end + else + error = TranslateCap("commanderror_invalidplayerid") + end + else + error = TranslateCap("commanderror_argumentmismatch_number", k) + end + elseif v.type == "string" then + local newArg = tonumber(args[k]) + if not newArg then + newArgs[v.name] = args[k] + else + error = TranslateCap("commanderror_argumentmismatch_string", k) + end + elseif v.type == "item" then + if ESX.Items[args[k]] then + newArgs[v.name] = args[k] + else + error = TranslateCap("commanderror_invaliditem") + end + elseif v.type == "weapon" then + if ESX.GetWeapon(args[k]) then + newArgs[v.name] = string.upper(args[k]) + else + error = TranslateCap("commanderror_invalidweapon") + end + elseif v.type == "any" then + newArgs[v.name] = args[k] + elseif v.type == "merge" then + local length = 0 + for i = 1, k - 1 do + length = length + string.len(args[i]) + 1 + end + local merge = table.concat(args, " ") + + newArgs[v.name] = string.sub(merge, length) + elseif v.type == "coordinate" then + local coord = tonumber(args[k]:match("(-?%d+%.?%d*)")) + if not coord then + error = TranslateCap("commanderror_argumentmismatch_number", k) + else + newArgs[v.name] = coord + end + end + end + + --backwards compatibility + if v.validate ~= nil and not v.validate then + error = nil + end + + if error then + break + end + end + + args = newArgs + end + end + + if error then + if playerId == 0 then + print(("[^3WARNING^7] %s^7"):format(error)) + else + xPlayer.showNotification(error) + end + else + cb(xPlayer or false, args, function(msg) + if playerId == 0 then + print(("[^3WARNING^7] %s^7"):format(msg)) + else + xPlayer.showNotification(msg) + end + end) + end + end + end, true) + + if type(group) == "table" then + for _, v in ipairs(group) do + ExecuteCommand(("add_ace group.%s command.%s allow"):format(v, name)) + end + else + ExecuteCommand(("add_ace group.%s command.%s allow"):format(group, name)) + end +end + +AddEventHandler("chatMessage", function(playerId, _, message) + local xPlayer = ESX.GetPlayerFromId(playerId) + if xPlayer and message:sub(1, 1) == "/" and playerId > 0 then + CancelEvent() + local commandName = message:sub(1):gmatch("%w+")() + xPlayer.showNotification(TranslateCap("commanderror_invalidcommand", commandName)) + end +end) + ESX.RegisterCommand( { "setcoords", "tp" }, "admin", diff --git a/[core]/es_extended/server/modules/connecting.lua b/[core]/es_extended/server/modules/connecting.lua new file mode 100644 index 000000000..426b4bf08 --- /dev/null +++ b/[core]/es_extended/server/modules/connecting.lua @@ -0,0 +1,87 @@ +function Core.PlayerJoined(playerId) + local identifier = ESX.GetIdentifier(playerId) + if not identifier then + return DropPlayer(playerId, "[ESX] there was an error loading your character!\nError code: identifier-missing-ingame\n\nThe cause of this error is not known, your identifier could not be found. Please come back later or report this problem to the server administration team.") + end + + if ESX.GetPlayerFromIdentifier(identifier) then + DropPlayer( + playerId, + ("[ESX] there was an error loading your character!\nError code: identifier-active-ingame\n\nThis error is caused by a player on this server who has the same identifier as you have. Make sure you are not playing on the same Rockstar account.\n\nYour Rockstar identifier: %s"):format( + identifier + ) + ) + else + local result = MySQL.scalar.await("SELECT 1 FROM users WHERE identifier = ?", { identifier }) + if result then + Core.LoadPlayer(identifier, playerId, false) + else + Core.CreatePlayer(identifier, playerId) + end + end +end + +if Config.Multichar then + AddEventHandler("esx:onPlayerJoined", function(src, char, data) + while not next(ESX.Jobs) do + Wait(50) + end + + if not ESX.Players[src] then + local identifier = char .. ":" .. ESX.GetIdentifier(src) + if data then + Core.CreatePlayer(identifier, src, data) + else + Core.LoadPlayer(identifier, src, false) + end + end + end) +else + RegisterNetEvent("esx:onPlayerJoined", function() + local _source = source + while not next(ESX.Jobs) do + Wait(50) + end + + if not ESX.Players[_source] then + Core.PlayerJoined(_source) + end + end) + + AddEventHandler("playerConnecting", function(_, _, deferrals) + local playerId = source + deferrals.defer() + Wait(0) -- Required + local identifier = ESX.GetIdentifier(playerId) + + + if not SetEntityOrphanMode then + return deferrals.done(("[ESX] ESX Requires a minimum Artifact version of 10188, Please update your server.")) + end + + local oneSyncState = GetConvar("onesync", "off") + if oneSyncState == "off" or oneSyncState == "legacy" then + return deferrals.done(("[ESX] ESX Requires Onesync Infinity to work. This server currently has Onesync set to: %s"):format(oneSyncState)) + end + + if not Core.DatabaseConnected then + return deferrals.done("[ESX] OxMySQL Was Unable To Connect to your database. Please make sure it is turned on and correctly configured in your server.cfg") + end + + if identifier then + if ESX.GetPlayerFromIdentifier(identifier) then + return deferrals.done( + ("[ESX] There was an error loading your character!\nError code: identifier-active\n\nThis error is caused by a player on this server who has the same identifier as you have. Make sure you are not playing on the same account.\n\nYour identifier: %s"):format(identifier) + ) + else + return deferrals.done() + end + else + return deferrals.done("[ESX] There was an error loading your character!\nError code: identifier-missing\n\nThe cause of this error is not known, your identifier could not be found. Please come back later or report this problem to the server administration team.") + end + end) +end + +RegisterNetEvent("esx:onPlayerSpawn", function() + ESX.Players[source].spawned = true +end) diff --git a/[core]/es_extended/server/modules/database.lua b/[core]/es_extended/server/modules/database.lua new file mode 100644 index 000000000..44170fd03 --- /dev/null +++ b/[core]/es_extended/server/modules/database.lua @@ -0,0 +1,37 @@ +Core.DatabaseConnected = false + +NewPlayer = "INSERT INTO `users` SET `accounts` = ?, `identifier` = ?, `group` = ?" +LoadPlayer = "SELECT `accounts`, `job`, `job_grade`, `group`, `position`, `inventory`, `skin`, `loadout`, `metadata`" + +if Config.Multichar then + NewPlayer = NewPlayer .. ", `firstname` = ?, `lastname` = ?, `dateofbirth` = ?, `sex` = ?, `height` = ?" +end + +if Config.StartingInventoryItems then + NewPlayer = NewPlayer .. ", `inventory` = ?" +end + +if Config.Multichar or Config.Identity then + LoadPlayer = LoadPlayer .. ", `firstname`, `lastname`, `dateofbirth`, `sex`, `height`" +end + +LoadPlayer = LoadPlayer .. " FROM `users` WHERE identifier = ?" + +MySQL.ready(function() + Core.DatabaseConnected = true + if not Config.CustomInventory then + local items = MySQL.query.await("SELECT * FROM items") + for _, v in ipairs(items) do + ESX.Items[v.name] = { label = v.label, weight = v.weight, rare = v.rare, canRemove = v.can_remove } + end + end + + ESX.RefreshJobs() + + print(("[^2INFO^7] ESX ^5Legacy %s^0 initialized!"):format(GetResourceMetadata(GetCurrentResourceName(), "version", 0))) + + Core.PlayerSaveLoop() + if Config.EnablePaycheck then + StartPayCheck() + end +end) diff --git a/[core]/es_extended/server/modules/debug.lua b/[core]/es_extended/server/modules/debug.lua new file mode 100644 index 000000000..f24b65dee --- /dev/null +++ b/[core]/es_extended/server/modules/debug.lua @@ -0,0 +1,30 @@ +if Config.EnableDebug then + RegisterNetEvent("esx:enteringVehicle", function(plate, seat, netId) + print("esx:enteringVehicle", "source", source, "plate", plate, "seat", seat, "netId", netId) + end) + + RegisterNetEvent("esx:enteringVehicleAborted", function() + print("esx:enteringVehicleAborted", source) + end) + + RegisterNetEvent("esx:enteredVehicle", function(plate, seat, displayName, netId) + print("esx:enteredVehicle", "source", source, "plate", plate, "seat", seat, "displayName", displayName, "netId", netId) + end) + + RegisterNetEvent("esx:exitedVehicle", function(plate, seat, displayName, netId) + print("esx:exitedVehicle", "source", source, "plate", plate, "seat", seat, "displayName", displayName, "netId", netId) + end) + + RegisterNetEvent("esx:clientLog", function(msg) + print(("[^2TRACE^7] %s^7"):format(msg)) + end) +end + +---@param msg string +---@return nil +function ESX.Trace(msg) + if Config.EnableDebug then + print(("[^2TRACE^7] %s^7"):format(msg)) + end +end + diff --git a/[core]/es_extended/server/modules/defaultCallbacks.lua b/[core]/es_extended/server/modules/defaultCallbacks.lua new file mode 100644 index 000000000..c8efbb9ea --- /dev/null +++ b/[core]/es_extended/server/modules/defaultCallbacks.lua @@ -0,0 +1,54 @@ +ESX.RegisterServerCallback("esx:getPlayerData", function(source, cb) + local xPlayer = ESX.GetPlayerFromId(source) + + cb({ + identifier = xPlayer.identifier, + accounts = xPlayer.getAccounts(), + inventory = xPlayer.getInventory(), + job = xPlayer.getJob(), + loadout = xPlayer.getLoadout(), + money = xPlayer.getMoney(), + position = xPlayer.getCoords(true), + metadata = xPlayer.getMeta(), + }) +end) + +ESX.RegisterServerCallback("esx:isUserAdmin", function(source, cb) + cb(Core.IsPlayerAdmin(source)) +end) + +ESX.RegisterServerCallback("esx:getGameBuild", function(_, cb) + cb(tonumber(GetConvar("sv_enforceGameBuild", "1604"))) +end) + +ESX.RegisterServerCallback("esx:getOtherPlayerData", function(_, cb, target) + local xPlayer = ESX.GetPlayerFromId(target) + + cb({ + identifier = xPlayer.identifier, + accounts = xPlayer.getAccounts(), + inventory = xPlayer.getInventory(), + job = xPlayer.getJob(), + loadout = xPlayer.getLoadout(), + money = xPlayer.getMoney(), + position = xPlayer.getCoords(true), + metadata = xPlayer.getMeta(), + }) +end) + +ESX.RegisterServerCallback("esx:getPlayerNames", function(source, cb, players) + players[source] = nil + + for playerId, _ in pairs(players) do + local xPlayer = ESX.GetPlayerFromId(playerId) + + if xPlayer then + players[playerId] = xPlayer.getName() + else + players[playerId] = nil + end + end + + cb(players) +end) + diff --git a/[core]/es_extended/server/modules/items.lua b/[core]/es_extended/server/modules/items.lua new file mode 100644 index 000000000..89288208a --- /dev/null +++ b/[core]/es_extended/server/modules/items.lua @@ -0,0 +1,233 @@ +ESX.Items = {} +Core.UsableItemsCallbacks = {} + +---@return table +function ESX.GetUsableItems() + local Usables = {} + for k in pairs(Core.UsableItemsCallbacks) do + Usables[k] = true + end + return Usables +end + +---@param item string +---@return string? +---@diagnostic disable-next-line: duplicate-set-field +function ESX.GetItemLabel(item) + if ESX.Items[item] then + return ESX.Items[item].label + else + print(("[^3WARNING^7] Attemting to get invalid Item -> ^5%s^7"):format(item)) + end +end + +---@param item string +---@param cb function +---@return nil +function ESX.RegisterUsableItem(item, cb) + Core.UsableItemsCallbacks[item] = cb +end + +---@param source number +---@param item string +---@param ... any +---@return nil +function ESX.UseItem(source, item, ...) + if ESX.Items[item] then + local itemCallback = Core.UsableItemsCallbacks[item] + + if itemCallback then + local success, result = pcall(itemCallback, source, item, ...) + + if not success then + return result and print(result) or print(('[^3WARNING^7] An error occured when using item ^5"%s"^7! This was not caused by ESX.'):format(item)) + end + end + else + print(('[^3WARNING^7] Item ^5"%s"^7 was used but does not exist!'):format(item)) + end +end + +if Config.EnableDefaultInventory then + RegisterNetEvent("esx:updateWeaponAmmo", function(weaponName, ammoCount) + local xPlayer = ESX.GetPlayerFromId(source) + + if xPlayer then + xPlayer.updateWeaponAmmo(weaponName, ammoCount) + end + end) + + RegisterNetEvent("esx:giveInventoryItem", function(target, itemType, itemName, itemCount) + local playerId = source + local sourceXPlayer = ESX.GetPlayerFromId(playerId) + local targetXPlayer = ESX.GetPlayerFromId(target) + local distance = #(GetEntityCoords(GetPlayerPed(playerId)) - GetEntityCoords(GetPlayerPed(target))) + if not sourceXPlayer or not targetXPlayer or distance > Config.DistanceGive then + print(("[^3WARNING^7] Player Detected Cheating: ^5%s^7"):format(GetPlayerName(playerId))) + return + end + + if itemType == "item_standard" then + local sourceItem = sourceXPlayer.getInventoryItem(itemName) + + if itemCount < 1 or sourceItem.count < itemCount then + return sourceXPlayer.showNotification(TranslateCap("imp_invalid_quantity")) + end + + if not targetXPlayer.canCarryItem(itemName, itemCount) then + return sourceXPlayer.showNotification(TranslateCap("ex_inv_lim", targetXPlayer.name)) + end + + sourceXPlayer.removeInventoryItem(itemName, itemCount) + targetXPlayer.addInventoryItem(itemName, itemCount) + + sourceXPlayer.showNotification(TranslateCap("gave_item", itemCount, sourceItem.label, targetXPlayer.name)) + targetXPlayer.showNotification(TranslateCap("received_item", itemCount, sourceItem.label, sourceXPlayer.name)) + elseif itemType == "item_account" then + if itemCount < 1 or sourceXPlayer.getAccount(itemName).money < itemCount then + return sourceXPlayer.showNotification(TranslateCap("imp_invalid_amount")) + end + + sourceXPlayer.removeAccountMoney(itemName, itemCount, "Gave to " .. targetXPlayer.name) + targetXPlayer.addAccountMoney(itemName, itemCount, "Received from " .. sourceXPlayer.name) + + sourceXPlayer.showNotification(TranslateCap("gave_account_money", ESX.Math.GroupDigits(itemCount), Config.Accounts[itemName].label, targetXPlayer.name)) + targetXPlayer.showNotification(TranslateCap("received_account_money", ESX.Math.GroupDigits(itemCount), Config.Accounts[itemName].label, sourceXPlayer.name)) + elseif itemType == "item_weapon" then + if not sourceXPlayer.hasWeapon(itemName) then + return + end + + local weaponLabel = ESX.GetWeaponLabel(itemName) + if targetXPlayer.hasWeapon(itemName) then + sourceXPlayer.showNotification(TranslateCap("gave_weapon_hasalready", targetXPlayer.name, weaponLabel)) + targetXPlayer.showNotification(TranslateCap("received_weapon_hasalready", sourceXPlayer.name, weaponLabel)) + return + end + + local _, weapon = sourceXPlayer.getWeapon(itemName) + local _, weaponObject = ESX.GetWeapon(itemName) + itemCount = weapon.ammo + local weaponComponents = ESX.Table.Clone(weapon.components) + local weaponTint = weapon.tintIndex + + if weaponTint then + targetXPlayer.setWeaponTint(itemName, weaponTint) + end + + if weaponComponents then + for _, v in pairs(weaponComponents) do + targetXPlayer.addWeaponComponent(itemName, v) + end + end + + sourceXPlayer.removeWeapon(itemName) + targetXPlayer.addWeapon(itemName, itemCount) + + if weaponObject.ammo and itemCount > 0 then + local ammoLabel = weaponObject.ammo.label + sourceXPlayer.showNotification(TranslateCap("gave_weapon_withammo", weaponLabel, itemCount, ammoLabel, targetXPlayer.name)) + targetXPlayer.showNotification(TranslateCap("received_weapon_withammo", weaponLabel, itemCount, ammoLabel, sourceXPlayer.name)) + else + sourceXPlayer.showNotification(TranslateCap("gave_weapon", weaponLabel, targetXPlayer.name)) + targetXPlayer.showNotification(TranslateCap("received_weapon", weaponLabel, sourceXPlayer.name)) + end + elseif itemType == "item_ammo" then + if not sourceXPlayer.hasWeapon(itemName) then + return + end + + local _, weapon = sourceXPlayer.getWeapon(itemName) + + if not targetXPlayer.hasWeapon(itemName) then + sourceXPlayer.showNotification(TranslateCap("gave_weapon_noweapon", targetXPlayer.name)) + targetXPlayer.showNotification(TranslateCap("received_weapon_noweapon", sourceXPlayer.name, weapon.label)) + return + end + + local _, weaponObject = ESX.GetWeapon(itemName) + + if not weaponObject.ammo then return end + + local ammoLabel = weaponObject.ammo.label + if weapon.ammo >= itemCount then + sourceXPlayer.removeWeaponAmmo(itemName, itemCount) + targetXPlayer.addWeaponAmmo(itemName, itemCount) + + sourceXPlayer.showNotification(TranslateCap("gave_weapon_ammo", itemCount, ammoLabel, weapon.label, targetXPlayer.name)) + targetXPlayer.showNotification(TranslateCap("received_weapon_ammo", itemCount, ammoLabel, weapon.label, sourceXPlayer.name)) + end + end + end) + + RegisterNetEvent("esx:removeInventoryItem", function(itemType, itemName, itemCount) + local playerId = source + local xPlayer = ESX.GetPlayerFromId(playerId) + + if itemType == "item_standard" then + if not itemCount or itemCount < 1 then + return xPlayer.showNotification(TranslateCap("imp_invalid_quantity")) + end + + local xItem = xPlayer.getInventoryItem(itemName) + + if itemCount > xItem.count or xItem.count < 1 then + return xPlayer.showNotification(TranslateCap("imp_invalid_quantity")) + end + + xPlayer.removeInventoryItem(itemName, itemCount) + local pickupLabel = ("%s [%s]"):format(xItem.label, itemCount) + ESX.CreatePickup("item_standard", itemName, itemCount, pickupLabel, playerId) + xPlayer.showNotification(TranslateCap("threw_standard", itemCount, xItem.label)) + elseif itemType == "item_account" then + if itemCount == nil or itemCount < 1 then + return xPlayer.showNotification(TranslateCap("imp_invalid_amount")) + end + + local account = xPlayer.getAccount(itemName) + + if itemCount > account.money or account.money < 1 then + return xPlayer.showNotification(TranslateCap("imp_invalid_amount")) + end + + xPlayer.removeAccountMoney(itemName, itemCount, "Threw away") + local pickupLabel = ("%s [%s]"):format(account.label, TranslateCap("locale_currency", ESX.Math.GroupDigits(itemCount))) + ESX.CreatePickup("item_account", itemName, itemCount, pickupLabel, playerId) + xPlayer.showNotification(TranslateCap("threw_account", ESX.Math.GroupDigits(itemCount), string.lower(account.label))) + elseif itemType == "item_weapon" then + itemName = string.upper(itemName) + + if not xPlayer.hasWeapon(itemName) then return end + + local _, weapon = xPlayer.getWeapon(itemName) + local _, weaponObject = ESX.GetWeapon(itemName) + -- luacheck: ignore weaponPickupLabel + local weaponPickupLabel = "" + local components = ESX.Table.Clone(weapon.components) + xPlayer.removeWeapon(itemName) + + if weaponObject.ammo and weapon.ammo > 0 then + local ammoLabel = weaponObject.ammo.label + weaponPickupLabel = ("%s [%s %s]"):format(weapon.label, weapon.ammo, ammoLabel) + xPlayer.showNotification(TranslateCap("threw_weapon_ammo", weapon.label, weapon.ammo, ammoLabel)) + else + weaponPickupLabel = ("%s"):format(weapon.label) + xPlayer.showNotification(TranslateCap("threw_weapon", weapon.label)) + end + + ESX.CreatePickup("item_weapon", itemName, weapon.ammo, weaponPickupLabel, playerId, components, weapon.tintIndex) + end + end) + + RegisterNetEvent("esx:useItem", function(itemName) + local source = source + local xPlayer = ESX.GetPlayerFromId(source) + local count = xPlayer.getInventoryItem(itemName).count + + if count < 1 then + return xPlayer.showNotification(TranslateCap("act_imp")) + end + + ESX.UseItem(source, itemName) + end) +end diff --git a/[core]/es_extended/server/modules/jobs.lua b/[core]/es_extended/server/modules/jobs.lua new file mode 100644 index 000000000..3467b40c4 --- /dev/null +++ b/[core]/es_extended/server/modules/jobs.lua @@ -0,0 +1,61 @@ +ESX.Jobs = {} +ESX.JobsPlayerCount = {} + +---@param job string +---@param grade string +---@return boolean +function ESX.DoesJobExist(job, grade) + return (ESX.Jobs[job] and ESX.Jobs[job].grades[tostring(grade)] ~= nil) or false +end + +---@return table +function ESX.GetJobs() + return ESX.Jobs +end + +---@return nil +function ESX.RefreshJobs() + local Jobs = {} + local jobs = MySQL.query.await("SELECT * FROM jobs") + + for _, v in ipairs(jobs) do + Jobs[v.name] = v + Jobs[v.name].grades = {} + end + + local jobGrades = MySQL.query.await("SELECT * FROM job_grades") + + for _, v in ipairs(jobGrades) do + if Jobs[v.job_name] then + Jobs[v.job_name].grades[tostring(v.grade)] = v + else + print(('[^3WARNING^7] Ignoring job grades for ^5"%s"^0 due to missing job'):format(v.job_name)) + end + end + + for _, v in pairs(Jobs) do + if ESX.Table.SizeOf(v.grades) == 0 then + Jobs[v.name] = nil + print(('[^3WARNING^7] Ignoring job ^5"%s"^0 due to no job grades found'):format(v.name)) + end + end + + if not Jobs then + -- Fallback data, if no jobs exist + ESX.Jobs["unemployed"] = { label = "Unemployed", grades = { ["0"] = { grade = 0, label = "Unemployed", salary = 200, skin_male = {}, skin_female = {} } } } + else + ESX.Jobs = Jobs + end +end + +AddEventHandler("esx:setJob", function(_, job, lastJob) + local lastJobKey = ("%s:count"):format(lastJob.name) + local jobKey = ("%s:count"):format(job.name) + local currentLastJob = ESX.JobsPlayerCount[lastJob.name] + + ESX.JobsPlayerCount[lastJob.name] = ((currentLastJob and currentLastJob > 0) and currentLastJob or 1) - 1 + ESX.JobsPlayerCount[job.name] = (ESX.JobsPlayerCount[job.name] or 0) + 1 + + GlobalState[lastJobKey] = ESX.JobsPlayerCount[lastJob.name] + GlobalState[jobKey] = ESX.JobsPlayerCount[job.name] +end) diff --git a/[core]/es_extended/server/modules/logs.lua b/[core]/es_extended/server/modules/logs.lua new file mode 100644 index 000000000..9bccf3dac --- /dev/null +++ b/[core]/es_extended/server/modules/logs.lua @@ -0,0 +1,77 @@ + +---@param name string +---@param title string +---@param color string +---@param message string +---@return nil +function ESX.DiscordLog(name, title, color, message) + local webHook = Config.DiscordLogs.Webhooks[name] or Config.DiscordLogs.Webhooks.default + local embedData = { + { + ["title"] = title, + ["color"] = Config.DiscordLogs.Colors[color] or Config.DiscordLogs.Colors.default, + ["footer"] = { + ["text"] = "| ESX Logs | " .. os.date(), + ["icon_url"] = "https://cdn.discordapp.com/attachments/944789399852417096/1020099828266586193/blanc-800x800.png", + }, + ["description"] = message, + ["author"] = { + ["name"] = "ESX Framework", + ["icon_url"] = "https://cdn.discordapp.com/emojis/939245183621558362.webp?size=128&quality=lossless", + }, + }, + } + PerformHttpRequest( + webHook, + function () + return + end, + "POST", + json.encode({ + username = "Logs", + embeds = embedData, + }), + { + ["Content-Type"] = "application/json", + } + ) +end + +---@param name string +---@param title string +---@param color string +---@param fields table +---@return nil +function ESX.DiscordLogFields(name, title, color, fields) + local webHook = Config.DiscordLogs.Webhooks[name] or Config.DiscordLogs.Webhooks.default + local embedData = { + { + ["title"] = title, + ["color"] = Config.DiscordLogs.Colors[color] or Config.DiscordLogs.Colors.default, + ["footer"] = { + ["text"] = "| ESX Logs | " .. os.date(), + ["icon_url"] = "https://cdn.discordapp.com/attachments/944789399852417096/1020099828266586193/blanc-800x800.png", + }, + ["fields"] = fields, + ["description"] = "", + ["author"] = { + ["name"] = "ESX Framework", + ["icon_url"] = "https://cdn.discordapp.com/emojis/939245183621558362.webp?size=128&quality=lossless", + }, + }, + } + PerformHttpRequest( + webHook, + function () + return + end, + "POST", + json.encode({ + username = "Logs", + embeds = embedData, + }), + { + ["Content-Type"] = "application/json", + } + ) +end diff --git a/[core]/es_extended/server/modules/pickup.lua b/[core]/es_extended/server/modules/pickup.lua new file mode 100644 index 000000000..9ad055d8d --- /dev/null +++ b/[core]/es_extended/server/modules/pickup.lua @@ -0,0 +1,70 @@ +Core.Pickups = {} +Core.PickupId = 0 + +if not Config.CustomInventory then + ---@param itemType string + ---@param name string + ---@param count integer + ---@param label string + ---@param playerId number + ---@param components? string | table + ---@param tintIndex? integer + ---@param coords? table | vector3 + ---@return nil + function ESX.CreatePickup(itemType, name, count, label, playerId, components, tintIndex, coords) + local pickupId = (Core.PickupId == 65635 and 0 or Core.PickupId + 1) + local xPlayer = ESX.Players[playerId] + coords = ((type(coords) == "vector3" or type(coords) == "vector4") and coords.xyz or xPlayer.getCoords(true)) + + Core.Pickups[pickupId] = { type = itemType, name = name, count = count, label = label, coords = coords } + + if itemType == "item_weapon" then + Core.Pickups[pickupId].components = components + Core.Pickups[pickupId].tintIndex = tintIndex + end + + TriggerClientEvent("esx:createPickup", -1, pickupId, label, coords, itemType, name, components, tintIndex) + Core.PickupId = pickupId + end + + RegisterNetEvent("esx:onPickup", function(pickupId) + local pickup, xPlayer, success = Core.Pickups[pickupId], ESX.GetPlayerFromId(source) + + if not pickup then return end + + local playerPickupDistance = #(pickup.coords - xPlayer.getCoords(true)) + if playerPickupDistance > 5.0 then + print(("[^3WARNING^7] Player Detected Cheating (Out of range pickup): ^5%s^7"):format(xPlayer.getIdentifier())) + return + end + + if pickup.type == "item_standard" then + if not xPlayer.canCarryItem(pickup.name, pickup.count) then + return xPlayer.showNotification(TranslateCap("threw_cannot_pickup")) + end + + xPlayer.addInventoryItem(pickup.name, pickup.count) + success = true + elseif pickup.type == "item_account" then + success = true + xPlayer.addAccountMoney(pickup.name, pickup.count, "Picked up") + elseif pickup.type == "item_weapon" then + if xPlayer.hasWeapon(pickup.name) then + return xPlayer.showNotification(TranslateCap("threw_weapon_already")) + end + + success = true + xPlayer.addWeapon(pickup.name, pickup.count) + xPlayer.setWeaponTint(pickup.name, pickup.tintIndex) + + for _, v in ipairs(pickup.components) do + xPlayer.addWeaponComponent(pickup.name, v) + end + end + + if success then + Core.Pickups[pickupId] = nil + TriggerClientEvent("esx:removePickup", -1, pickupId) + end + end) +end diff --git a/[core]/es_extended/server/modules/players.lua b/[core]/es_extended/server/modules/players.lua new file mode 100644 index 000000000..aab1e8c3c --- /dev/null +++ b/[core]/es_extended/server/modules/players.lua @@ -0,0 +1,469 @@ +ESX.Players = {} +Core.playersByIdentifier = {} +Core.PlayerFunctionOverrides = {} +GlobalState.playerCount = 0 + +local function updateHealthAndArmorInMetadata(xPlayer) + local ped = GetPlayerPed(xPlayer.source) + xPlayer.setMeta("health", GetEntityHealth(ped)) + xPlayer.setMeta("armor", GetPedArmour(ped)) + xPlayer.setMeta("lastPlaytime", xPlayer.getPlayTime()) +end + +---@param xPlayer table +---@param cb? function +---@return nil +function Core.SavePlayer(xPlayer, cb) + if not xPlayer.spawned then + return cb and cb() + end + + updateHealthAndArmorInMetadata(xPlayer) + local parameters = { + json.encode(xPlayer.getAccounts(true)), + xPlayer.job.name, + xPlayer.job.grade, + xPlayer.group, + json.encode(xPlayer.getCoords(false, true)), + json.encode(xPlayer.getInventory(true)), + json.encode(xPlayer.getLoadout(true)), + json.encode(xPlayer.getMeta()), + xPlayer.identifier, + } + + MySQL.prepare( + "UPDATE `users` SET `accounts` = ?, `job` = ?, `job_grade` = ?, `group` = ?, `position` = ?, `inventory` = ?, `loadout` = ?, `metadata` = ? WHERE `identifier` = ?", + parameters, + function(affectedRows) + if affectedRows == 1 then + print(('[^2INFO^7] Saved player ^5"%s^7"'):format(xPlayer.name)) + TriggerEvent("esx:playerSaved", xPlayer.playerId, xPlayer) + end + if cb then + cb() + end + end + ) +end + +---@param cb? function +---@return nil +function Core.SavePlayers(cb) + local xPlayers = ESX.Players + if not next(xPlayers) then + return + end + + local startTime = os.time() + local parameters = {} + + for _, xPlayer in pairs(ESX.Players) do + updateHealthAndArmorInMetadata(xPlayer) + parameters[#parameters + 1] = { + json.encode(xPlayer.getAccounts(true)), + xPlayer.job.name, + xPlayer.job.grade, + xPlayer.group, + json.encode(xPlayer.getCoords(false, true)), + json.encode(xPlayer.getInventory(true)), + json.encode(xPlayer.getLoadout(true)), + json.encode(xPlayer.getMeta()), + xPlayer.identifier, + } + end + + MySQL.prepare( + "UPDATE `users` SET `accounts` = ?, `job` = ?, `job_grade` = ?, `group` = ?, `position` = ?, `inventory` = ?, `loadout` = ?, `metadata` = ? WHERE `identifier` = ?", + parameters, + function(results) + if not results then + return + end + + if type(cb) == "function" then + return cb() + end + + print(("[^2INFO^7] Saved ^5%s^7 %s over ^5%s^7 ms"):format(#parameters, #parameters > 1 and "players" or "player", ESX.Math.Round((os.time() - startTime) / 1000000, 2))) + end + ) +end + +function Core.PlayerSaveLoop() + CreateThread(function() + local interval = Config.SaveInterval or 10 * 60 * 1000 + while true do + Wait(interval) + Core.SavePlayers() + end + end) +end + +local function checkTable(key, val, player, xPlayers) + for valIndex = 1, #val do + local value = val[valIndex] + if not xPlayers[value] then + xPlayers[value] = {} + end + + if (key == "job" and player.job.name == value) or player[key] == value then + xPlayers[value][#xPlayers[value] + 1] = player + end + end +end + +---@param key? string +---@param val? string|table +---@return table +function ESX.GetExtendedPlayers(key, val) + local xPlayers = {} + if type(val) == "table" then + for _, v in pairs(ESX.Players) do + checkTable(key, val, v, xPlayers) + end + else + for _, v in pairs(ESX.Players) do + if key then + if (key == "job" and v.job.name == val) or v[key] == val then + xPlayers[#xPlayers + 1] = v + end + else + xPlayers[#xPlayers + 1] = v + end + end + end + + return xPlayers +end + +---@param key? string +---@param val? string|table +---@return number | table +function ESX.GetNumPlayers(key, val) + if not key then + return #GetPlayers() + end + + if type(val) == "table" then + local numPlayers = {} + if key == "job" then + for _, v in ipairs(val) do + numPlayers[v] = (ESX.JobsPlayerCount[v] or 0) + end + return numPlayers + end + + local filteredPlayers = ESX.GetExtendedPlayers(key, val) + for i, v in pairs(filteredPlayers) do + numPlayers[i] = (#v or 0) + end + return numPlayers + end + + if key == "job" then + return (ESX.JobsPlayerCount[val] or 0) + end + + return #ESX.GetExtendedPlayers(key, val) +end + +---@param source number +---@return table +function ESX.GetPlayerFromId(source) + return ESX.Players[tonumber(source)] +end + +---@param identifier string +---@return table +function ESX.GetPlayerFromIdentifier(identifier) + return Core.playersByIdentifier[identifier] +end + + +---@param playerId number | string +---@return string +function ESX.GetIdentifier(playerId) + local fxDk = GetConvarInt("sv_fxdkMode", 0) + if fxDk == 1 then + return "ESX-DEBUG-LICENCE" + end + + playerId = tostring(playerId) + + local identifier = GetPlayerIdentifierByType(playerId, "license") + return identifier and identifier:gsub("license:", "") +end + + +---@param index string +---@param overrides table +---@return nil +function ESX.RegisterPlayerFunctionOverrides(index, overrides) + Core.PlayerFunctionOverrides[index] = overrides +end + +---@param index string +---@return nil +function ESX.SetPlayerFunctionOverride(index) + if not index or not Core.PlayerFunctionOverrides[index] then + return print("[^3WARNING^7] No valid index provided.") + end + + Config.PlayerFunctionOverride = index +end + +---@param playerId string | number +---@return boolean +function Core.IsPlayerAdmin(playerId) + playerId = tostring(playerId) + if (IsPlayerAceAllowed(playerId, "command") or GetConvar("sv_lan", "") == "true") then + return true + end + + local xPlayer = ESX.Players[playerId] + return (xPlayer and Config.AdminGroups[xPlayer.group] and true) or false +end + +function Core.LoadPlayer(identifier, playerId, isNew) + local userData = { + accounts = {}, + inventory = {}, + loadout = {}, + weight = 0, + name = GetPlayerName(playerId), + identifier = identifier, + firstName = "John", + lastName = "Doe", + dateofbirth = "01/01/2000", + height = 120, + dead = false, + } + + local result = MySQL.prepare.await(LoadPlayer, { identifier }) + + -- Accounts + local accounts = result.accounts + accounts = (accounts and accounts ~= "") and json.decode(accounts) or {} + + for account, data in pairs(Config.Accounts) do + data.round = data.round or data.round == nil + + local index = #userData.accounts + 1 + userData.accounts[index] = { + name = account, + money = accounts[account] or Config.StartingAccountMoney[account] or 0, + label = data.label, + round = data.round, + index = index, + } + end + + -- Job + local job, grade = result.job, tostring(result.job_grade) + + if not ESX.DoesJobExist(job, grade) then + print(("[^3WARNING^7] Ignoring invalid job for ^5%s^7 [job: ^5%s^7, grade: ^5%s^7]"):format(identifier, job, grade)) + job, grade = "unemployed", "0" + end + + local jobObject, gradeObject = ESX.Jobs[job], ESX.Jobs[job].grades[grade] + + userData.job = { + id = jobObject.id, + name = jobObject.name, + label = jobObject.label, + + grade = tonumber(grade), + grade_name = gradeObject.name, + grade_label = gradeObject.label, + grade_salary = gradeObject.salary, + + skin_male = gradeObject.skin_male and json.decode(gradeObject.skin_male) or {}, + skin_female = gradeObject.skin_female and json.decode(gradeObject.skin_female) or {}, + } + + -- Inventory + if not Config.CustomInventory then + local inventory = (result.inventory and result.inventory ~= "") and json.decode(result.inventory) or {} + + for name, item in pairs(ESX.Items) do + local count = inventory[name] or 0 + userData.weight += (count * item.weight) + + userData.inventory[#userData.inventory + 1] = { + name = name, + count = count, + label = item.label, + weight = item.weight, + usable = Core.UsableItemsCallbacks[name] ~= nil, + rare = item.rare, + canRemove = item.canRemove, + } + end + table.sort(userData.inventory, function(a, b) + return a.label < b.label + end) + elseif result.inventory and result.inventory ~= "" then + userData.inventory = json.decode(result.inventory) + end + + -- Group + if result.group then + if result.group == "superadmin" then + userData.group = "admin" + print("[^3WARNING^7] ^5Superadmin^7 detected, setting group to ^5admin^7") + else + userData.group = result.group + end + else + userData.group = "user" + end + + -- Loadout + if not Config.CustomInventory then + if result.loadout and result.loadout ~= "" then + + local loadout = json.decode(result.loadout) + for name, weapon in pairs(loadout) do + local label = ESX.GetWeaponLabel(name) + + if label then + userData.loadout[#userData.loadout + 1] = { + name = name, + ammo = weapon.ammo, + label = label, + components = weapon.components or {}, + tintIndex = weapon.tintIndex or 0, + } + end + end + end + end + + -- Position + userData.coords = json.decode(result.position) or Config.DefaultSpawns[ESX.Math.Random(1,#Config.DefaultSpawns)] + + -- Skin + userData.skin = (result.skin and result.skin ~= "") and json.decode(result.skin) or { sex = userData.sex == "f" and 1 or 0 } + + -- Metadata + userData.metadata = (result.metadata and result.metadata ~= "") and json.decode(result.metadata) or {} + + -- xPlayer Creation + local xPlayer = CreateExtendedPlayer(playerId, identifier, userData.group, userData.accounts, userData.inventory, userData.weight, userData.job, userData.loadout, GetPlayerName(playerId), userData.coords, userData.metadata) + + GlobalState["playerCount"] = GlobalState["playerCount"] + 1 + ESX.Players[playerId] = xPlayer + Core.playersByIdentifier[identifier] = xPlayer + + -- Identity + if result.firstname and result.firstname ~= "" then + userData.firstName = result.firstname + userData.lastName = result.lastname + + local name = ("%s %s"):format(result.firstname, result.lastname) + userData.name = name + + xPlayer.set("firstName", result.firstname) + xPlayer.set("lastName", result.lastname) + xPlayer.setName(name) + + if result.dateofbirth then + userData.dateofbirth = result.dateofbirth + xPlayer.set("dateofbirth", result.dateofbirth) + end + if result.sex then + userData.sex = result.sex + xPlayer.set("sex", result.sex) + end + if result.height then + userData.height = result.height + xPlayer.set("height", result.height) + end + end + + TriggerEvent("esx:playerLoaded", playerId, xPlayer, isNew) + userData.money = xPlayer.getMoney() + userData.maxWeight = xPlayer.getMaxWeight() + xPlayer.triggerEvent("esx:playerLoaded", userData, isNew, userData.skin) + + if not Config.CustomInventory then + xPlayer.triggerEvent("esx:createMissingPickups", Core.Pickups) + elseif setPlayerInventory then + setPlayerInventory(playerId, xPlayer, userData.inventory, isNew) + end + + xPlayer.triggerEvent("esx:registerSuggestions", Core.RegisteredCommands) + print(('[^2INFO^0] Player ^5"%s"^0 has connected to the server. ID: ^5%s^7'):format(xPlayer.getName(), playerId)) +end + +function Core.CreatePlayer(identifier, playerId, data) + local accounts = {} + + for account, money in pairs(Config.StartingAccountMoney) do + accounts[account] = money + end + + local defaultGroup = "user" + if Core.IsPlayerAdmin(playerId) then + print(("[^2INFO^0] Player ^5%s^0 Has been granted admin permissions via ^5Ace Perms^7."):format(playerId)) + defaultGroup = "admin" + end + + local parameters = Config.Multichar and { json.encode(accounts), identifier, defaultGroup, data.firstname, data.lastname, data.dateofbirth, data.sex, data.height } or { json.encode(accounts), identifier, defaultGroup } + + if Config.StartingInventoryItems then + parameters[#parameters + 1] = json.encode(Config.StartingInventoryItems) + end + + MySQL.prepare(NewPlayer, parameters, function() + Core.LoadPlayer(identifier, playerId, true) + end) +end + +ESX.GetPlayers = GetPlayers + +AddEventHandler("esx:playerLoaded", function(_, xPlayer) + local job = xPlayer.getJob().name + local jobKey = ("%s:count"):format(job) + + ESX.JobsPlayerCount[job] = (ESX.JobsPlayerCount[job] or 0) + 1 + GlobalState[jobKey] = ESX.JobsPlayerCount[job] +end) + +AddEventHandler("esx:playerLogout", function(playerId, cb) + local xPlayer = ESX.GetPlayerFromId(playerId) + if xPlayer then + TriggerEvent("esx:playerDropped", playerId) + + Core.playersByIdentifier[xPlayer.identifier] = nil + Core.SavePlayer(xPlayer, function() + GlobalState["playerCount"] = GlobalState["playerCount"] - 1 + ESX.Players[playerId] = nil + if cb then + cb() + end + end) + end + TriggerClientEvent("esx:onPlayerLogout", playerId) +end) + +AddEventHandler("playerDropped", function(reason) + local playerId = source + local xPlayer = ESX.GetPlayerFromId(playerId) + + if xPlayer then + TriggerEvent("esx:playerDropped", playerId, reason) + local job = xPlayer.getJob().name + local currentJob = ESX.JobsPlayerCount[job] + ESX.JobsPlayerCount[job] = ((currentJob and currentJob > 0) and currentJob or 1) - 1 + + GlobalState[("%s:count"):format(job)] = ESX.JobsPlayerCount[job] + Core.playersByIdentifier[xPlayer.identifier] = nil + + Core.SavePlayer(xPlayer, function() + GlobalState["playerCount"] = GlobalState["playerCount"] - 1 + ESX.Players[playerId] = nil + end) + end +end) diff --git a/[core]/es_extended/server/modules/start.lua b/[core]/es_extended/server/modules/start.lua new file mode 100644 index 000000000..a35cc5835 --- /dev/null +++ b/[core]/es_extended/server/modules/start.lua @@ -0,0 +1,53 @@ +SetMapName(Config.MapName or "San Andreas") +SetGameType(Config.GameType or "ESX-Framework") + +Core.BlacklistedScripts = { + ["essentialmode"] = true, + ["es_admin2"] = true, + ["basic-gamemode"] = true, + ["mapmanager"] = true, + ["fivem-map-skater"] = true, + ["fivem-map-hipster"] = true, + ["qb-core"] = true, + ["default_spawnpoint"] = true, +} + +AddEventHandler("onResourceStart", function(key) + if Core.BlacklistedScripts[string.lower(key)] then + while GetResourceState(key) ~= "started" do + Wait(0) + end + + StopResource(key) + error(("WE STOPPED A RESOURCE THAT WILL BREAK ^1ESX^1, PLEASE REMOVE ^5%s^1"):format(key)) + end + + if not SetEntityOrphanMode then + CreateThread(function() + while true do + error("ESX Requires a minimum Artifact version of 10188, Please update your server.") + Wait(60 * 1000) + end + end) + end +end) + +AddEventHandler("txAdmin:events:scheduledRestart", function(eventData) + if eventData.secondsRemaining == 60 then + CreateThread(function() + Wait(50000) + Core.SavePlayers() + end) + end +end) + +AddEventHandler("txAdmin:events:serverShuttingDown", function() + Core.SavePlayers() +end) + +for key in pairs(Core.BlacklistedScripts) do + if GetResourceState(key) == "started" or GetResourceState(key) == "starting" then + StopResource(key) + error(("WE STOPPED A RESOURCE THAT WILL BREAK ^1ESX^1, PLEASE REMOVE ^5%s^1"):format(key)) + end +end diff --git a/[core]/es_extended/server/modules/callback.lua b/[core]/es_extended/server/modules/utils/callback.lua similarity index 100% rename from [core]/es_extended/server/modules/callback.lua rename to [core]/es_extended/server/modules/utils/callback.lua diff --git a/[core]/es_extended/server/modules/utils/clientEvent.lua b/[core]/es_extended/server/modules/utils/clientEvent.lua new file mode 100644 index 000000000..355d0360c --- /dev/null +++ b/[core]/es_extended/server/modules/utils/clientEvent.lua @@ -0,0 +1,17 @@ +--- Triggers an event for one or more clients. +---@param eventName string The name of the event to trigger. +---@param playerIds table|number If a number, represents a single player ID. If a table, represents an array of player IDs. +---@param ... any Additional arguments to pass to the event handler. +function ESX.TriggerClientEvent(eventName, playerIds, ...) + if type(playerIds) == "number" then + TriggerClientEvent(eventName, playerIds, ...) + return + end + + local payload = msgpack.pack_args(...) + local payloadLength = #payload + + for i = 1, #playerIds do + TriggerClientEventInternal(eventName, playerIds[i], payload, payloadLength) + end +end diff --git a/[core]/es_extended/server/modules/createJob.lua b/[core]/es_extended/server/modules/utils/createJob.lua similarity index 100% rename from [core]/es_extended/server/modules/createJob.lua rename to [core]/es_extended/server/modules/utils/createJob.lua diff --git a/[core]/es_extended/server/modules/onesync.lua b/[core]/es_extended/server/modules/utils/onesync.lua similarity index 94% rename from [core]/es_extended/server/modules/onesync.lua rename to [core]/es_extended/server/modules/utils/onesync.lua index cb9bfdfa2..6a10e90c8 100644 --- a/[core]/es_extended/server/modules/onesync.lua +++ b/[core]/es_extended/server/modules/utils/onesync.lua @@ -1,4 +1,5 @@ ESX.OneSync = {} +Core.vehicleTypesByModel = {} ---@param source number|vector3 ---@param closest boolean @@ -78,6 +79,23 @@ function ESX.OneSync.GetClosestPlayer(source, maxDistance, ignore) return getNearbyPlayers(source, true, maxDistance, ignore) end +---@param model string|number +---@param player number +---@param cb function +---@diagnostic disable-next-line: duplicate-set-field +function ESX.GetVehicleType(model, player, cb) + model = type(model) == "string" and joaat(model) or model + + if Core.vehicleTypesByModel[model] then + return cb(Core.vehicleTypesByModel[model]) + end + + ESX.TriggerClientCallback(player, "esx:GetVehicleType", function(vehicleType) + Core.vehicleTypesByModel[model] = vehicleType + cb(vehicleType) + end, model) +end + ---@param model number|string ---@param coords vector3|table ---@param heading number diff --git a/[core]/es_extended/shared/config/adjustments.lua b/[core]/es_extended/shared/config/adjustments.lua index ae7b8a466..2a3a897df 100644 --- a/[core]/es_extended/shared/config/adjustments.lua +++ b/[core]/es_extended/shared/config/adjustments.lua @@ -58,7 +58,7 @@ Config.CustomAIPlates = "........" -- Custom plates for AI vehicles ]] Config.DiscordActivity = { - appId = 0, -- Discord Application ID, + appId = "", -- Discord Application ID, assetName = "LargeIcon", --image name for the "large" icon. assetText = "{server_name}", -- Text to display on the asset buttons = { diff --git a/[core]/es_extended/shared/config/main.lua b/[core]/es_extended/shared/config/main.lua index 2c3756053..905956b65 100644 --- a/[core]/es_extended/shared/config/main.lua +++ b/[core]/es_extended/shared/config/main.lua @@ -39,9 +39,13 @@ Config.EnablePaycheck = true -- enable paycheck Config.LogPaycheck = false -- Logs paychecks to a nominated Discord channel via webhook (default is false) Config.EnableSocietyPayouts = false -- pay from the society account that the player is employed at? Requirement: esx_society Config.MaxWeight = 24 -- the max inventory weight without a backpack +Config.SaveInterval = 10 * 60 * 1000 -- how often to save player data in milliseconds (default is 10 minutes) Config.PaycheckInterval = 7 * 60000 -- how often to receive paychecks in milliseconds Config.EnableDebug = false -- Use Debug options? +Config.MapName = "San Andreas" +Config.GameType = "ESX-Framework" + Config.DefaultJobDuty = true -- A players default duty status when changing jobs Config.Multichar = GetResourceState("esx_multicharacter") ~= "missing" diff --git a/[core]/es_extended/shared/main.lua b/[core]/es_extended/shared/main.lua index 0e78f0cb7..0adccc7f4 100644 --- a/[core]/es_extended/shared/main.lua +++ b/[core]/es_extended/shared/main.lua @@ -1,4 +1,5 @@ ESX = {} +Core = {} exports("getSharedObject", function() return ESX @@ -9,5 +10,5 @@ AddEventHandler("esx:getSharedObject", function() error(("Resource ^5%s^1 Used the ^5getSharedObject^1 Event, this event ^1no longer exists!^1 Visit https://documentation.esx-framework.org/tutorials/tutorials-esx/sharedevent for how to fix!"):format(Invoke)) end) --- backwards compatibility (DO NOT TOUCH !) -Config.OxInventory = Config.CustomInventory == "ox" \ No newline at end of file +-- backwards compatibility (DO NOT TOUCH !) +Config.OxInventory = Config.CustomInventory == "ox"