diff --git a/[SQL]/legacy.sql b/[SQL]/legacy.sql index 05d5d10b..64dcc549 100644 --- a/[SQL]/legacy.sql +++ b/[SQL]/legacy.sql @@ -16,7 +16,8 @@ INSERT INTO `addon_account` (`name`, `label`, `shared`) VALUES ('society_cardealer', 'Cardealer', 1), ('society_mechanic', 'Mechanic', 1), ('society_police', 'Police', 1), -('society_taxi', 'Taxi', 1); +('society_taxi', 'Taxi', 1), +('bank_savings','Savings account',0); -- -------------------------------------------------------- @@ -89,7 +90,7 @@ CREATE TABLE `billing` ( `identifier` varchar(60) NOT NULL, `sender` varchar(60) NOT NULL, `target_type` varchar(50) NOT NULL, - `target` varchar(40) NOT NULL, + `target` varchar(60) NOT NULL, `label` varchar(255) NOT NULL, `amount` int(11) NOT NULL ) ENGINE=InnoDB; @@ -364,7 +365,7 @@ CREATE TABLE `rented_vehicles` ( `player_name` varchar(255) NOT NULL, `base_price` int(11) NOT NULL, `rent_price` int(11) NOT NULL, - `owner` varchar(22) NOT NULL + `owner` varchar(60) NOT NULL ) ENGINE=InnoDB; -- @@ -749,7 +750,8 @@ ALTER TABLE `addon_account` ALTER TABLE `addon_account_data` ADD PRIMARY KEY (`id`), ADD UNIQUE KEY `index_addon_account_data_account_name_owner` (`account_name`,`owner`), - ADD KEY `index_addon_account_data_account_name` (`account_name`); + ADD KEY `index_addon_account_data_account_name` (`account_name`), + ADD KEY `esx_addon_account_data_owner` (`owner`); -- -- Indexes for table `addon_inventory` @@ -764,13 +766,15 @@ ALTER TABLE `addon_inventory_items` ADD PRIMARY KEY (`id`), ADD KEY `index_addon_inventory_items_inventory_name_name` (`inventory_name`,`name`), ADD KEY `index_addon_inventory_items_inventory_name_name_owner` (`inventory_name`,`name`,`owner`), - ADD KEY `index_addon_inventory_inventory_name` (`inventory_name`); + ADD KEY `index_addon_inventory_inventory_name` (`inventory_name`), + ADD KEY `esx_addon_inventory_items_owner` (`owner`); -- -- Indexes for table `billing` -- ALTER TABLE `billing` - ADD PRIMARY KEY (`id`); + ADD PRIMARY KEY (`id`), + ADD KEY `esx_billing_identifier` (`identifier`); -- -- Indexes for table `cardealer_vehicles` @@ -790,7 +794,8 @@ ALTER TABLE `datastore` ALTER TABLE `datastore_data` ADD PRIMARY KEY (`id`), ADD UNIQUE KEY `index_datastore_data_name_owner` (`name`,`owner`), - ADD KEY `index_datastore_data_name` (`name`); + ADD KEY `index_datastore_data_name` (`name`), + ADD KEY `esx_datastore_data_owner` (`owner`); -- -- Indexes for table `items` @@ -821,7 +826,8 @@ ALTER TABLE `licenses` -- Indexes for table `owned_vehicles` -- ALTER TABLE `owned_vehicles` - ADD PRIMARY KEY (`plate`); + ADD PRIMARY KEY (`plate`), + ADD KEY `esx_owned_vehicles_owner` (`owner`); -- -- @@ -840,7 +846,8 @@ ALTER TABLE `rented_vehicles` -- Indexes for table `society_moneywash` -- ALTER TABLE `society_moneywash` - ADD PRIMARY KEY (`id`); + ADD PRIMARY KEY (`id`), + ADD KEY `esx_society_moneywash_identifier` (`identifier`); -- -- Indexes for table `users` @@ -856,7 +863,8 @@ ALTER TABLE `users` -- Indexes for table `user_licenses` -- ALTER TABLE `user_licenses` - ADD PRIMARY KEY (`id`); + ADD PRIMARY KEY (`id`), + ADD KEY `esx_user_licenses_owner` (`owner`); -- -- Indexes for table `vehicle_categories` @@ -991,27 +999,6 @@ INSERT INTO `fine_types` (label, amount, category) VALUES ('Fraud', 2000, 2); --- --- ESX Bankerjob --- - -INSERT INTO `addon_account` (name, label, shared) VALUES - ('society_banker','Bank',1), - ('bank_savings','Savings account',0) -; - -INSERT INTO `jobs` (name, label) VALUES - ('banker','Banker') -; - -INSERT INTO `job_grades` (job_name, grade, name, label, salary, skin_male, skin_female) VALUES - ('banker',0,'advisor','Consultant',10,'{}','{}'), - ('banker',1,'banker','Banker',20,'{}','{}'), - ('banker',2,'business_banker',"Investment banker",30,'{}','{}'), - ('banker',3,'trader','Broker',40,'{}','{}'), - ('banker',4,'boss','Boss',0,'{}','{}') -; - -- -- ESX Banking -- diff --git a/[core]/cron/server/main.lua b/[core]/cron/server/main.lua index 51da085d..1cce057f 100644 --- a/[core]/cron/server/main.lua +++ b/[core]/cron/server/main.lua @@ -26,19 +26,30 @@ end ---@param timestamp number function OnTime(timestamp) - for i = 1, #cronJobs, 1 do - local scheduledTimestamp = os.time({ - hour = cronJobs[i].h, - min = cronJobs[i].m, + local function scheduledAt(job, dayOffset) + return os.time({ + hour = job.h, + min = job.m, sec = 0, -- Assuming tasks run at the start of the minute - day = os.date("%d", timestamp), + day = os.date("%d", timestamp) + dayOffset, month = os.date("%m", timestamp), year = os.date("%Y", timestamp), }) + end - if timestamp >= scheduledTimestamp and (not lastTimestamp or lastTimestamp < scheduledTimestamp) then + for i = 1, #cronJobs, 1 do + local scheduledTimestamp = scheduledAt(cronJobs[i], 0) + + if scheduledTimestamp > timestamp then + scheduledTimestamp = scheduledAt(cronJobs[i], -1) + end + + if not lastTimestamp or lastTimestamp < scheduledTimestamp then local d = os.date('*t', scheduledTimestamp).wday - cronJobs[i].cb(d, cronJobs[i].h, cronJobs[i].m) + + if not pcall(cronJobs[i].cb, d, cronJobs[i].h, cronJobs[i].m) then + print(("[^1ERROR^7] cron job at ^5%02d:%02d^7 errored, skipping it"):format(cronJobs[i].h, cronJobs[i].m)) + end end end end diff --git a/[core]/es_extended/client/modules/adjustments.lua b/[core]/es_extended/client/modules/adjustments.lua index 1c83b148..90d9deb6 100644 --- a/[core]/es_extended/client/modules/adjustments.lua +++ b/[core]/es_extended/client/modules/adjustments.lua @@ -25,7 +25,9 @@ function Adjustments:DisableNPCDrops() end function Adjustments:SeatShuffle() - if Config.DisableVehicleSeatShuff then + if Config.DisableVehicleSeatShuff and not self.seatShuffleRegistered then + self.seatShuffleRegistered = true + AddEventHandler("esx:enteredVehicle", function(vehicle, _, seat) if seat > -1 then SetPedIntoVehicle(ESX.PlayerData.ped, vehicle, seat) @@ -43,7 +45,7 @@ end function Adjustments:AmmoAndVehicleRewards() CreateThread(function() - while true do + while ESX.PlayerLoaded do if Config.DisableDisplayAmmo then DisplayAmmoThisFrame(false) end @@ -175,11 +177,11 @@ function Adjustments:ReplacePlaceholders(text) local success, result = pcall(cb) if not success then - error(("Failed to execute placeholder: ^5%s^7\n%s"):format(placeholder, result)) + print(("[^1ERROR^7] Failed to execute placeholder: ^5%s^7\n%s"):format(placeholder, result)) result = "Unknown" end - text = text:gsub(("{%s}"):format(placeholder), tostring(result)) + text = text:gsub(("{%s}"):format(placeholder), (tostring(result):gsub("%%", "%%%%"))) end return text end @@ -187,7 +189,7 @@ end function Adjustments:DiscordPresence() if Config.DiscordActivity.appId ~= 0 then CreateThread(function() - while true do + while ESX.PlayerLoaded do SetDiscordAppId(Config.DiscordActivity.appId) SetRichPresence(self:ReplacePlaceholders(Config.DiscordActivity.presence)) SetDiscordRichPresenceAsset(Config.DiscordActivity.assetName) @@ -213,7 +215,9 @@ function Adjustments:WantedLevel() end function Adjustments:DisableRadio() - if Config.RemoveHudComponents[16] then + if Config.RemoveHudComponents[16] and not self.disableRadioRegistered then + self.disableRadioRegistered = true + AddEventHandler("esx:enteredVehicle", function(vehicle, plate, seat, displayName, netId) SetVehRadioStation(vehicle,"OFF") SetUserRadioControlEnabled(false) @@ -223,7 +227,7 @@ end function Adjustments:Multipliers() CreateThread(function() - while true do + while ESX.PlayerLoaded do SetPedDensityMultiplierThisFrame(Config.Multipliers.pedDensity) SetScenarioPedDensityMultiplierThisFrame(Config.Multipliers.scenarioPedDensityInterior, Config.Multipliers.scenarioPedDensityExterior) SetAmbientVehicleRangeMultiplierThisFrame(Config.Multipliers.ambientVehicleRange) diff --git a/[core]/es_extended/client/modules/callback.lua b/[core]/es_extended/client/modules/callback.lua index 30211b9a..b0dca393 100644 --- a/[core]/es_extended/client/modules/callback.lua +++ b/[core]/es_extended/client/modules/callback.lua @@ -102,7 +102,7 @@ function ESX.AwaitServerCallback(eventName, ...) -- if the server callback takes longer than 15 seconds to respond, reject the promise SetTimeout(15000, function() - if p.state == "pending" then + if p.state == 0 then p:reject("Server Callback Timed Out") end end) diff --git a/[core]/es_extended/client/modules/events.lua b/[core]/es_extended/client/modules/events.lua index 5cffaeb6..566457ed 100644 --- a/[core]/es_extended/client/modules/events.lua +++ b/[core]/es_extended/client/modules/events.lua @@ -372,7 +372,6 @@ if not Config.CustomInventory then while true do local Sleep = 1500 local playerCoords = GetEntityCoords(ESX.PlayerData.ped) - local _, closestDistance = ESX.Game.GetClosestPlayer(playerCoords) for pickupId, pickup in pairs(pickups) do local distance = #(playerCoords - pickup.coords) @@ -383,6 +382,8 @@ if not Config.CustomInventory then if distance < 1 then if IsControlJustReleased(0, 38) then + local _, closestDistance = ESX.Game.GetClosestPlayer(playerCoords) + if IsPedOnFoot(ESX.PlayerData.ped) and (closestDistance == -1 or closestDistance > 3) and not pickup.inRange then pickup.inRange = true diff --git a/[core]/es_extended/fxmanifest.lua b/[core]/es_extended/fxmanifest.lua index f7f9789e..5ad53897 100644 --- a/[core]/es_extended/fxmanifest.lua +++ b/[core]/es_extended/fxmanifest.lua @@ -3,7 +3,7 @@ fx_version 'cerulean' game 'gta5' description 'The Core resource that provides the functionalities for all other resources.' lua54 'yes' -version '1.14.0' +version '1.14.1' shared_scripts { '@esx_lib/imports.lua', diff --git a/[core]/es_extended/locale.lua b/[core]/es_extended/locale.lua index b19cd79f..54346f17 100644 --- a/[core]/es_extended/locale.lua +++ b/[core]/es_extended/locale.lua @@ -82,7 +82,7 @@ function Translate(str, ...) -- Translate string end function TranslateCap(str, ...) -- Translate string first char uppercase - return _(str, ...):gsub("^%l", string.upper) + return (_(str, ...):gsub("^%l", string.upper)) end _ = Translate diff --git a/[core]/es_extended/server/classes/overrides/oxinventory.lua b/[core]/es_extended/server/classes/overrides/oxinventory.lua index c3be26a8..fcd3e3f3 100644 --- a/[core]/es_extended/server/classes/overrides/oxinventory.lua +++ b/[core]/es_extended/server/classes/overrides/oxinventory.lua @@ -1,11 +1,175 @@ -local Inventory +local OxInventory -if Config.CustomInventory ~= "ox" then return end +if Config.CustomInventory ~= "ox" then + return +end +local requiredMethods = { + "GetItem", + "AddItem", + "RemoveItem", + "SetItem", + "CanCarryItem", + "CanSwapItem", + "SetMaxWeight", +} + +---Parses the inventory:accounts convar to build a lookup table. +---Uses the same default and JSON format as ox_inventory. +---@return table +local function getAccountList() + local convar = GetConvar("inventory:accounts", '["money"]') + local ok, list = pcall(json.decode, convar) + + if not ok or type(list) ~= "table" then + error( + ("[es_extended] Invalid inventory:accounts convar: %s") + :format(tostring(convar)), + 2 + ) + end + + local accounts = {} + + for i = 1, #list do + local account = list[i] + + if type(account) == "string" and account ~= "" then + accounts[account] = true + end + end + + return accounts +end + +---Safely resolves an ox_inventory export without executing it. +---@param resourceExports table +---@param method string +---@return function +local function resolveExport(resourceExports, method) + local ok, exportFn = pcall(function() + return resourceExports[method] + end) + + if not ok then + error( + ("[es_extended] Missing required ox_inventory export '%s': %s") + :format(method, tostring(exportFn)), + 2 + ) + end + + if type(exportFn) ~= "function" then + error( + ("[es_extended] Invalid ox_inventory export '%s' (expected function, got %s)") + :format(method, type(exportFn)), + 2 + ) + end + + return exportFn +end + +---Creates a proxy table that routes method calls to public ox_inventory exports. +---@return table +local function createOxInventoryProxy() + local proxy = {} + local resourceExports = exports.ox_inventory + + print("^3[es_extended] ox_inventory: using direct export proxy (Inventory() interface unavailable or incomplete)^7") + + for i = 1, #requiredMethods do + local method = requiredMethods[i] + local exportFn = resolveExport(resourceExports, method) + + proxy[method] = function(...) + return exportFn(resourceExports, ...) + end + end + + proxy.accounts = getAccountList() + + return proxy +end + +---Checks if an Inventory() module provides everything ESX requires. +---@param module table +---@return boolean +local function isValidModule(module) + if type(module) ~= "table" then + return false + end + + for i = 1, #requiredMethods do + if type(module[requiredMethods[i]]) ~= "function" then + return false + end + end + + if type(module.accounts) ~= "table" then + return false + end + + return true +end + +---Returns the ox_inventory interface. +---Uses Inventory() when available, otherwise falls back to public exports. +---@return table +local function getOxInventory() + if OxInventory then + return OxInventory + end + + local state = GetResourceState("ox_inventory") + + if state ~= "started" then + error( + ("[es_extended] ox_inventory not started; current status: %s") + :format(tostring(state)), + 2 + ) + end + + local success, module = pcall(function() + return exports.ox_inventory:Inventory() + end) + + if success and isValidModule(module) then + OxInventory = module + else + OxInventory = createOxInventoryProxy() + end + + return OxInventory +end + +-- Standard method used when ox_inventory finishes loading. AddEventHandler("ox_inventory:loadInventory", function(module) - Inventory = module + if isValidModule(module) then + OxInventory = module + else + OxInventory = createOxInventoryProxy() + end end) +-- Standard method used when ox_inventory is stopped. +AddEventHandler("onResourceStop", function(resourceName) + if resourceName == "ox_inventory" then + OxInventory = nil + end +end) + +local function emptyMethod() + return function() end +end + +local function falseMethod() + return function() + return false + end +end + Core.PlayerFunctionOverrides.OxInventory = { getInventory = function(self) return function(minimal) @@ -13,26 +177,26 @@ Core.PlayerFunctionOverrides.OxInventory = { return self.inventory end - local minimalInventory = {} + local result = {} - for k, v in pairs(self.inventory) do - if v.count and v.count > 0 then - local metadata = v.metadata + for slot, item in pairs(self.inventory) do + if item.count and item.count > 0 then + local metadata = item.metadata - if v.metadata and next(v.metadata) == nil then + if type(metadata) == "table" and next(metadata) == nil then metadata = nil end - minimalInventory[#minimalInventory + 1] = { - name = v.name, - count = v.count, - slot = k, + result[#result + 1] = { + name = item.name, + count = item.count, + slot = slot, metadata = metadata, } end end - return minimalInventory + return result end end, @@ -45,18 +209,33 @@ Core.PlayerFunctionOverrides.OxInventory = { setAccountMoney = function(self) return function(accountName, money, reason) reason = reason or "unknown" - if money < 0 then return end + + if money < 0 then + return + end + local account = self.getAccount(accountName) - if not account then return end + if not account then + return + end money = account.round and ESX.Math.Round(money) or money self.accounts[account.index].money = money self.triggerEvent("esx:setAccountMoney", account) - TriggerEvent("esx:setAccountMoney", self.source, accountName, money, reason) - if Inventory.accounts[accountName] then - Inventory.SetItem(self.source, accountName, money) + TriggerEvent( + "esx:setAccountMoney", + self.source, + accountName, + money, + reason + ) + + local inventory = getOxInventory() + + if inventory.accounts[accountName] then + inventory.SetItem(self.source, accountName, money) end end end, @@ -64,17 +243,34 @@ Core.PlayerFunctionOverrides.OxInventory = { addAccountMoney = function(self) return function(accountName, money, reason) reason = reason or "unknown" - if money < 1 then return end + + if money < 1 then + return + end local account = self.getAccount(accountName) - if not account then return end + + if not account then + return + end money = account.round and ESX.Math.Round(money) or money - self.accounts[account.index].money = self.accounts[account.index].money + money + self.accounts[account.index].money = + self.accounts[account.index].money + money + self.triggerEvent("esx:setAccountMoney", account) - TriggerEvent("esx:addAccountMoney", self.source, accountName, money, reason) - if Inventory.accounts[accountName] then - Inventory.AddItem(self.source, accountName, money) + TriggerEvent( + "esx:addAccountMoney", + self.source, + accountName, + money, + reason + ) + + local inventory = getOxInventory() + + if inventory.accounts[accountName] then + inventory.AddItem(self.source, accountName, money) end end end, @@ -82,54 +278,103 @@ Core.PlayerFunctionOverrides.OxInventory = { removeAccountMoney = function(self) return function(accountName, money, reason) reason = reason or "unknown" - if money < 1 then return end + + if money < 1 then + return + end local account = self.getAccount(accountName) - if not account then return end + + if not account then + return + end money = account.round and ESX.Math.Round(money) or money - self.accounts[account.index].money = self.accounts[account.index].money - money + self.accounts[account.index].money = + self.accounts[account.index].money - money + self.triggerEvent("esx:setAccountMoney", account) - TriggerEvent("esx:removeAccountMoney", self.source, accountName, money, reason) - if Inventory.accounts[accountName] then - Inventory.RemoveItem(self.source, accountName, money) + TriggerEvent( + "esx:removeAccountMoney", + self.source, + accountName, + money, + reason + ) + + local inventory = getOxInventory() + + if inventory.accounts[accountName] then + inventory.RemoveItem(self.source, accountName, money) end end end, getInventoryItem = function(self) return function(name, metadata) - return Inventory.GetItem(self.source, name, metadata) + return getOxInventory().GetItem( + self.source, + name, + metadata + ) end end, addInventoryItem = function(self) return function(name, count, metadata, slot) - return Inventory.AddItem(self.source, name, count or 1, metadata, slot) + return getOxInventory().AddItem( + self.source, + name, + count or 1, + metadata, + slot + ) end end, removeInventoryItem = function(self) return function(name, count, metadata, slot) - return Inventory.RemoveItem(self.source, name, count or 1, metadata, slot) + return getOxInventory().RemoveItem( + self.source, + name, + count or 1, + metadata, + slot + ) end end, setInventoryItem = function(self) return function(name, count, metadata) - return Inventory.SetItem(self.source, name, count, metadata) + return getOxInventory().SetItem( + self.source, + name, + count, + metadata + ) end end, canCarryItem = function(self) return function(name, count, metadata) - return Inventory.CanCarryItem(self.source, name, count, metadata) + return getOxInventory().CanCarryItem( + self.source, + name, + count, + metadata + ) end end, canSwapItem = function(self) return function(firstItem, firstItemCount, testItem, testItemCount) - return Inventory.CanSwapItem(self.source, firstItem, firstItemCount, testItem, testItemCount) + return getOxInventory().CanSwapItem( + self.source, + firstItem, + firstItemCount, + testItem, + testItemCount + ) end end, @@ -137,83 +382,71 @@ Core.PlayerFunctionOverrides.OxInventory = { return function(newWeight) self.maxWeight = newWeight self.triggerEvent("esx:setMaxWeight", self.maxWeight) - return Inventory.SetMaxWeight(self.source, newWeight) + + return getOxInventory().SetMaxWeight( + self.source, + newWeight + ) end end, - addWeapon = function() - return function() end - end, + addWeapon = emptyMethod, + addWeaponComponent = emptyMethod, + addWeaponAmmo = emptyMethod, + updateWeaponAmmo = emptyMethod, + setWeaponTint = emptyMethod, + getWeaponTint = emptyMethod, + removeWeapon = emptyMethod, + removeWeaponComponent = emptyMethod, + removeWeaponAmmo = emptyMethod, - addWeaponComponent = function() - return function() end - end, - - addWeaponAmmo = function() - return function() end - end, - - updateWeaponAmmo = function() - return function() end - end, - - setWeaponTint = function() - return function() end - end, - - getWeaponTint = function() - return function() end - end, - - removeWeapon = function() - return function() end - end, - - removeWeaponComponent = function() - return function() end - end, - - removeWeaponAmmo = function() - return function() end - end, - - hasWeaponComponent = function() - return function() - return false - end - end, - - hasWeapon = function() - return function() - return false - end - end, + hasWeaponComponent = falseMethod, + hasWeapon = falseMethod, hasItem = function(self) return function(name, metadata) - return Inventory.GetItem(self.source, name, metadata) + local item = getOxInventory().GetItem( + self.source, + name, + metadata + ) + + if not item or not item.count or item.count < 1 then + return false + end + + return item, item.count end end, - getWeapon = function() - return function() end - end, + getWeapon = emptyMethod, syncInventory = function(self) return function(weight, maxWeight, items, money) - self.weight, self.maxWeight = weight, maxWeight + self.weight = weight + self.maxWeight = maxWeight self.inventory = items - if not money then return end + if not money then + return + end + for accountName, amount in pairs(money) do local account = self.getAccount(accountName) if account and ESX.Math.Round(account.money) ~= amount then account.money = amount + self.triggerEvent("esx:setAccountMoney", account) - TriggerEvent("esx:setAccountMoney", self.source, accountName, amount, "Sync account with item") + TriggerEvent( + "esx:setAccountMoney", + self.source, + accountName, + amount, + "Sync account with item" + ) end end end end, -} +} \ No newline at end of file diff --git a/[core]/es_extended/server/classes/player.lua b/[core]/es_extended/server/classes/player.lua index 367c9838..231d86a9 100644 --- a/[core]/es_extended/server/classes/player.lua +++ b/[core]/es_extended/server/classes/player.lua @@ -505,6 +505,10 @@ function CreateExtendedPlayer(playerId, identifier, ssn, group, accounts, invent if item and count >= 0 then count = ESX.Math.Round(count) + if count == item.count then + return + end + if count > item.count then self.addInventoryItem(item.name, count - item.count) else diff --git a/[core]/es_extended/server/classes/vehicle.lua b/[core]/es_extended/server/classes/vehicle.lua index b85c8f65..8c7d1e22 100644 --- a/[core]/es_extended/server/classes/vehicle.lua +++ b/[core]/es_extended/server/classes/vehicle.lua @@ -154,6 +154,7 @@ Core.vehicleClass = { vehicleData.plate = newPlate Core.vehicles[newPlate] = table.clone(vehicleData) Core.vehicles[oldPlate] = nil + self.plate = newPlate TriggerEvent("esx:changedExtendedVehiclePlate", vehicleData.plate, oldPlate) Wait(0) @@ -167,7 +168,7 @@ Core.vehicleClass = { assert(type(newProps) == "table", "Expected 'props' to be a table") local vehicleData = Core.vehicles[self.plate] - local affectedRows = MySQL.update.await("UPDATE `owned_vehicles` SET `vehicle` = ? WHERE `plate` = ? AND `owner` = ?", json.encode(newProps), vehicleData.plate, vehicleData.owner) + local affectedRows = MySQL.update.await("UPDATE `owned_vehicles` SET `vehicle` = ? WHERE `plate` = ? AND `owner` = ?", { json.encode(newProps), vehicleData.plate, vehicleData.owner }) if affectedRows <= 0 then self:delete() return false diff --git a/[core]/es_extended/server/functions.lua b/[core]/es_extended/server/functions.lua index 7ab5f9b4..d2ff2404 100644 --- a/[core]/es_extended/server/functions.lua +++ b/[core]/es_extended/server/functions.lua @@ -136,7 +136,7 @@ function ESX.RegisterCommand(name, group, cb, allowConsole, suggestion) end local merge = table.concat(args, " ") - newArgs[v.name] = string.sub(merge, length) + newArgs[v.name] = string.sub(merge, length + 1) elseif v.type == "coordinate" then local coord = tonumber(args[k]:match("(-?%d+%.?%d*)")) if not coord then @@ -251,18 +251,24 @@ function Core.SavePlayers(cb) 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, - } + if xPlayer.spawned then + 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 + end + + if not parameters[1] then + return cb and cb() end MySQL.prepare( @@ -799,9 +805,15 @@ if not Config.CustomInventory then end if #toInsert > 0 then + local parameters = {} + for i = 1, #toInsert do + local row = toInsert[i] + parameters[i] = { row.name, row.label, row.weight, row.rare, row.canRemove } + end + MySQL.prepare.await( "INSERT IGNORE INTO `items` (`name`, `label`, `weight`, `rare`, `can_remove`) VALUES (?, ?, ?, ?, ?)", - toInsert) + parameters) for i = 1, #toInsert do local row = toInsert[i] diff --git a/[core]/es_extended/server/main.lua b/[core]/es_extended/server/main.lua index 96c5cc64..28e88f42 100644 --- a/[core]/es_extended/server/main.lua +++ b/[core]/es_extended/server/main.lua @@ -2,6 +2,7 @@ SetMapName("San Andreas") SetGameType("ESX Legacy") local oneSyncState = GetConvar("onesync", "off") +local isEnhanced = xLib.isEnhanced() local newPlayer = "INSERT INTO `users` SET `accounts` = ?, `identifier` = ?, `ssn` = ?, `group` = ?" local loadPlayer = "SELECT `accounts`, `ssn`, `job`, `job_grade`, `group`, `position`, `inventory`, `skin`, `loadout`, `metadata`" @@ -151,7 +152,7 @@ if not Config.Multichar 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 + if not isEnhanced and (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 @@ -288,9 +289,9 @@ function loadESXPlayer(identifier, playerId, isNew) local loadout = json.decode(result.loadout) for name, weapon in pairs(loadout) do - local label = ESX.GetWeaponLabel(name) + local found, label = pcall(ESX.GetWeaponLabel, name) - if label then + if found and label then userData.loadout[#userData.loadout + 1] = { name = name, ammo = weapon.ammo, @@ -307,7 +308,7 @@ function loadESXPlayer(identifier, playerId, isNew) 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 } + userData.skin = (result.skin and result.skin ~= "") and json.decode(result.skin) or { sex = result.sex == "f" and 1 or 0 } -- Metadata userData.metadata = (result.metadata and result.metadata ~= "") and json.decode(result.metadata) or {} @@ -474,6 +475,9 @@ if not Config.CustomInventory then local weaponComponents = ESX.Table.Clone(weapon.components) local weaponTint = weapon.tintIndex + sourceXPlayer.removeWeapon(itemName) + targetXPlayer.addWeapon(itemName, itemCount) + if weaponTint then targetXPlayer.setWeaponTint(itemName, weaponTint) end @@ -484,9 +488,6 @@ if not Config.CustomInventory then 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)) diff --git a/[core]/es_extended/server/migration/v1.14.1/billingTargetLength/main.lua b/[core]/es_extended/server/migration/v1.14.1/billingTargetLength/main.lua new file mode 100644 index 00000000..f7699a60 --- /dev/null +++ b/[core]/es_extended/server/migration/v1.14.1/billingTargetLength/main.lua @@ -0,0 +1,41 @@ +local esxVersion = "v1.14.2" + +Core.Migrations = Core.Migrations or {} +Core.Migrations[esxVersion] = Core.Migrations[esxVersion] or {} + +if GetResourceKvpInt(("esx_migration:%s"):format(esxVersion)) == 1 then + return +end + +---@return boolean restartRequired +Core.Migrations[esxVersion].billingTargetLength = function() + print("^4[esx_migration:v1.14.2:billingTargetLength]^7 Checking the length of billing.target.") + + local length = MySQL.scalar.await([[ + SELECT CHARACTER_MAXIMUM_LENGTH + FROM INFORMATION_SCHEMA.COLUMNS + WHERE TABLE_SCHEMA = DATABASE() + AND TABLE_NAME = 'billing' + AND COLUMN_NAME = 'target' + ]]) + + if not length then + print("^4[esx_migration:v1.14.2:billingTargetLength]^7 Column not found, migration not needed.") + return false + end + + if length >= 60 then + print(("^4[esx_migration:v1.14.2:billingTargetLength]^7 Column is already varchar(%s), migration not needed."):format(length)) + return false + end + + print(("^4[esx_migration:v1.14.2:billingTargetLength]^7 Column is varchar(%s), widening it to 60."):format(length)) + + MySQL.update.await([[ + ALTER TABLE `billing` + MODIFY `target` VARCHAR(60) NOT NULL + ]]) + + print("^4[esx_migration:v1.14.2:billingTargetLength]^7 Migration complete.") + return true +end diff --git a/[core]/es_extended/server/migration/v1.14.1/multicharDeleteIndexes/main.lua b/[core]/es_extended/server/migration/v1.14.1/multicharDeleteIndexes/main.lua new file mode 100644 index 00000000..5915bd77 --- /dev/null +++ b/[core]/es_extended/server/migration/v1.14.1/multicharDeleteIndexes/main.lua @@ -0,0 +1,53 @@ +local esxVersion = "v1.14.2" + +Core.Migrations = Core.Migrations or {} +Core.Migrations[esxVersion] = Core.Migrations[esxVersion] or {} + +if GetResourceKvpInt(("esx_migration:%s"):format(esxVersion)) == 1 then + return +end + +local targets = { + { table = "billing", column = "identifier" }, + { table = "owned_vehicles", column = "owner" }, + { table = "user_licenses", column = "owner" }, + { table = "society_moneywash", column = "identifier" }, + { table = "addon_account_data", column = "owner" }, + { table = "addon_inventory_items", column = "owner" }, + { table = "datastore_data", column = "owner" }, +} + +---@return boolean restartRequired +Core.Migrations[esxVersion].multicharDeleteIndexes = function() + print("^4[esx_migration:v1.14.2:multicharDeleteIndexes]^7 Indexing character deletion columns.") + + for i = 1, #targets do + local target = targets[i] + + local tableExists = MySQL.scalar.await([[ + SELECT COUNT(*) + FROM INFORMATION_SCHEMA.TABLES + WHERE TABLE_SCHEMA = DATABASE() + AND TABLE_NAME = ? + ]], { target.table }) + + if tableExists ~= 0 then + local leadingIndex = MySQL.scalar.await([[ + SELECT COUNT(*) + FROM INFORMATION_SCHEMA.STATISTICS + WHERE TABLE_SCHEMA = DATABASE() + AND TABLE_NAME = ? + AND COLUMN_NAME = ? + AND SEQ_IN_INDEX = 1 + ]], { target.table, target.column }) + + if leadingIndex == 0 then + MySQL.update.await(("CREATE INDEX `esx_%s_%s` ON `%s` (`%s`)"):format(target.table, target.column, target.table, target.column)) + print(("^4[esx_migration:v1.14.2:multicharDeleteIndexes]^7 Indexed ^5%s.%s^7."):format(target.table, target.column)) + end + end + end + + print("^4[esx_migration:v1.14.2:multicharDeleteIndexes]^7 Migration complete.") + return false +end diff --git a/[core]/es_extended/server/migration/v1.14.1/rentedVehiclesOwnerLength/main.lua b/[core]/es_extended/server/migration/v1.14.1/rentedVehiclesOwnerLength/main.lua new file mode 100644 index 00000000..74518e11 --- /dev/null +++ b/[core]/es_extended/server/migration/v1.14.1/rentedVehiclesOwnerLength/main.lua @@ -0,0 +1,41 @@ +local esxVersion = "v1.14.2" + +Core.Migrations = Core.Migrations or {} +Core.Migrations[esxVersion] = Core.Migrations[esxVersion] or {} + +if GetResourceKvpInt(("esx_migration:%s"):format(esxVersion)) == 1 then + return +end + +---@return boolean restartRequired +Core.Migrations[esxVersion].rentedVehiclesOwnerLength = function() + print("^4[esx_migration:v1.14.2:rentedVehiclesOwnerLength]^7 Checking the length of rented_vehicles.owner.") + + local length = MySQL.scalar.await([[ + SELECT CHARACTER_MAXIMUM_LENGTH + FROM INFORMATION_SCHEMA.COLUMNS + WHERE TABLE_SCHEMA = DATABASE() + AND TABLE_NAME = 'rented_vehicles' + AND COLUMN_NAME = 'owner' + ]]) + + if not length then + print("^4[esx_migration:v1.14.2:rentedVehiclesOwnerLength]^7 Column not found, migration not needed.") + return false + end + + if length >= 60 then + print(("^4[esx_migration:v1.14.2:rentedVehiclesOwnerLength]^7 Column is already varchar(%s), migration not needed."):format(length)) + return false + end + + print(("^4[esx_migration:v1.14.2:rentedVehiclesOwnerLength]^7 Column is varchar(%s), widening it to 60."):format(length)) + + MySQL.update.await([[ + ALTER TABLE `rented_vehicles` + MODIFY `owner` VARCHAR(60) NOT NULL + ]]) + + print("^4[esx_migration:v1.14.2:rentedVehiclesOwnerLength]^7 Migration complete.") + return true +end diff --git a/[core]/es_extended/server/modules/callback.lua b/[core]/es_extended/server/modules/callback.lua index c0fa8f73..b29347ea 100644 --- a/[core]/es_extended/server/modules/callback.lua +++ b/[core]/es_extended/server/modules/callback.lua @@ -107,7 +107,7 @@ function ESX.AwaitClientCallback(player, eventName, ...) if not p then return end SetTimeout(15000, function() - if p.state == "pending" then + if p.state == 0 then p:reject("Server Callback Timed Out") end end) diff --git a/[core]/es_extended/server/modules/commands.lua b/[core]/es_extended/server/modules/commands.lua index 2320e82f..b4b4141a 100644 --- a/[core]/es_extended/server/modules/commands.lua +++ b/[core]/es_extended/server/modules/commands.lua @@ -1,6 +1,12 @@ +local CommandPermissions = Config.CommandPermissions or {} + +local function FilterGroups(groups) + return (groups and #groups > 0) and groups or { "user" } +end + ESX.RegisterCommand( { "setcoords", "tp" }, - "admin", + FilterGroups(CommandPermissions.setcoords), function(xPlayer, args) xPlayer.setCoords({ x = args.x, y = args.y, z = args.z }) if Config.AdminLogging then @@ -27,7 +33,7 @@ ESX.RegisterCommand( ESX.RegisterCommand( "setjob", - "admin", + FilterGroups(CommandPermissions.setjob), function(xPlayer, args, showError) if not ESX.DoesJobExist(args.job, args.grade) then return showError(TranslateCap("command_setjob_invalid")) @@ -68,7 +74,7 @@ local upgrades = Config.SpawnVehMaxUpgrades and { ESX.RegisterCommand( "car", - "admin", + FilterGroups(CommandPermissions.car), function(xPlayer, args, showError) if not xPlayer then return showError("[^1ERROR^7] The xPlayer value is nil") @@ -132,7 +138,7 @@ ESX.RegisterCommand( ESX.RegisterCommand( { "cardel", "dv" }, - "admin", + FilterGroups(CommandPermissions.cardel), function(xPlayer, args) local ped = GetPlayerPed(xPlayer.source) local pedVehicle = GetVehiclePedIsIn(ped, false) @@ -168,7 +174,7 @@ ESX.RegisterCommand( ESX.RegisterCommand( { "fix", "repair" }, - "admin", + FilterGroups(CommandPermissions.fix), function(xPlayer, args, showError) local xTarget = args.playerId local ped = GetPlayerPed(xTarget.source) @@ -178,9 +184,11 @@ ESX.RegisterCommand( return end xTarget.triggerEvent("esx:repairPedVehicle") - xPlayer.showNotification(TranslateCap("command_repair_success"), true, false, 140) - if xPlayer.source ~= xTarget.source then - xTarget.showNotification(TranslateCap("command_repair_success_target"), true, false, 140) + if xPlayer then + xPlayer.showNotification(TranslateCap("command_repair_success")) + end + if not xPlayer or xPlayer.source ~= xTarget.source then + xTarget.showNotification(TranslateCap("command_repair_success_target")) end if Config.AdminLogging then ESX.DiscordLogFields("UserActions", "Fix Vehicle /fix Triggered!", "pink", { @@ -202,7 +210,7 @@ ESX.RegisterCommand( ESX.RegisterCommand( "setaccountmoney", - "admin", + FilterGroups(CommandPermissions.setaccountmoney), function(xPlayer, args, showError) if not args.playerId.getAccount(args.account) then return showError(TranslateCap("command_giveaccountmoney_invalid")) @@ -232,7 +240,7 @@ ESX.RegisterCommand( ESX.RegisterCommand( "giveaccountmoney", - "admin", + FilterGroups(CommandPermissions.giveaccountmoney), function(xPlayer, args, showError) if not args.playerId.getAccount(args.account) then return showError(TranslateCap("command_giveaccountmoney_invalid")) @@ -262,7 +270,7 @@ ESX.RegisterCommand( ESX.RegisterCommand( "removeaccountmoney", - "admin", + FilterGroups(CommandPermissions.removeaccountmoney), function(xPlayer, args, showError) if not args.playerId.getAccount(args.account) then return showError(TranslateCap("command_removeaccountmoney_invalid")) @@ -293,7 +301,7 @@ ESX.RegisterCommand( if not Config.CustomInventory then ESX.RegisterCommand( "giveitem", - "admin", + FilterGroups(CommandPermissions.giveitem), function(xPlayer, args) args.playerId.addInventoryItem(args.item, args.count) if Config.AdminLogging then @@ -323,7 +331,7 @@ if not Config.CustomInventory then ESX.RegisterCommand( "giveweapon", - "admin", + FilterGroups(CommandPermissions.giveweapon), function(xPlayer, args, showError) if args.playerId.hasWeapon(args.weapon) then return showError(TranslateCap("command_giveweapon_hasalready")) @@ -353,7 +361,7 @@ if not Config.CustomInventory then ESX.RegisterCommand( "giveammo", - "admin", + FilterGroups(CommandPermissions.giveammo), function(xPlayer, args, showError) if not args.playerId.hasWeapon(args.weapon) then return showError(TranslateCap("command_giveammo_noweapon_found")) @@ -383,7 +391,7 @@ if not Config.CustomInventory then ESX.RegisterCommand( "giveweaponcomponent", - "admin", + FilterGroups(CommandPermissions.giveweaponcomponent), function(xPlayer, args, showError) if args.playerId.hasWeapon(args.weaponName) then local component = ESX.GetWeaponComponent(args.weaponName, args.componentName) @@ -423,11 +431,11 @@ if not Config.CustomInventory then ) end -ESX.RegisterCommand({ "clear", "cls" }, "user", function(xPlayer) +ESX.RegisterCommand({ "clear", "cls" }, FilterGroups(CommandPermissions.clear), function(xPlayer) xPlayer.triggerEvent("chat:clear") end, false, { help = TranslateCap("command_clear") }) -ESX.RegisterCommand({ "clearall", "clsall" }, "admin", function(xPlayer) +ESX.RegisterCommand({ "clearall", "clsall" }, FilterGroups(CommandPermissions.clearall), function(xPlayer) TriggerClientEvent("chat:clear", -1) if Config.AdminLogging then ESX.DiscordLogFields("UserActions", "Clear Chat /clearall Triggered!", "pink", { @@ -437,20 +445,24 @@ ESX.RegisterCommand({ "clearall", "clsall" }, "admin", function(xPlayer) end end, true, { help = TranslateCap("command_clearall") }) -ESX.RegisterCommand("refreshjobs", "admin", function() +ESX.RegisterCommand("refreshjobs", FilterGroups(CommandPermissions.refreshjobs), function() ESX.RefreshJobs() end, true, { help = TranslateCap("command_clearall") }) if not Config.CustomInventory then - ESX.RegisterCommand("refreshitems", "admin", function(xPlayer) + ESX.RegisterCommand("refreshitems", FilterGroups(CommandPermissions.refreshitems), function(xPlayer) local itemCount = ESX.RefreshItems() - xPlayer.showNotification(Translate("command_refreshitems_success", itemCount), true, false, 140) + if xPlayer then + xPlayer.showNotification(Translate("command_refreshitems_success", itemCount)) + else + print(("[^2INFO^7] %s^7"):format(Translate("command_refreshitems_success", itemCount))) + end end, true, { help = TranslateCap("command_refreshitems") }) ESX.RegisterCommand( "clearinventory", - "admin", + FilterGroups(CommandPermissions.clearinventory), function(xPlayer, args) for _, v in ipairs(args.playerId.inventory) do if v.count > 0 then @@ -478,7 +490,7 @@ if not Config.CustomInventory then ESX.RegisterCommand( "clearloadout", - "admin", + FilterGroups(CommandPermissions.clearloadout), function(xPlayer, args) for i = #args.playerId.loadout, 1, -1 do args.playerId.removeWeapon(args.playerId.loadout[i].name) @@ -505,7 +517,7 @@ end ESX.RegisterCommand( "setgroup", - "admin", + FilterGroups(CommandPermissions.setgroup), function(xPlayer, args) if not args.playerId then args.playerId = xPlayer.source @@ -537,7 +549,7 @@ ESX.RegisterCommand( ESX.RegisterCommand( "save", - "admin", + FilterGroups(CommandPermissions.save), function(_, args) Core.SavePlayer(args.playerId) print(("[^2Info^0] Saved Player - ^5%s^0"):format(args.playerId.source)) @@ -552,26 +564,26 @@ ESX.RegisterCommand( } ) -ESX.RegisterCommand("saveall", "admin", function() +ESX.RegisterCommand("saveall", FilterGroups(CommandPermissions.saveall), function() Core.SavePlayers() end, true, { help = TranslateCap("command_saveall") }) -ESX.RegisterCommand("group", { "user", "admin" }, function(xPlayer, _, _) +ESX.RegisterCommand("group", FilterGroups(CommandPermissions.group), function(xPlayer, _, _) print(("%s, you are currently: ^5%s^0"):format(xPlayer.getName(), xPlayer.getGroup())) -end, true) +end, false) -ESX.RegisterCommand("job", { "user", "admin" }, function(xPlayer, _, _) +ESX.RegisterCommand("job", FilterGroups(CommandPermissions.job), function(xPlayer, _, _) local job = xPlayer.getJob() print(("%s, your job is: ^5%s^0 - ^5%s^0 - ^5%s^0"):format(xPlayer.getName(), job.name, job.grade_label, job.onDuty and "On Duty" or "Off Duty")) end, false) -ESX.RegisterCommand("info", { "user", "admin" }, function(xPlayer) +ESX.RegisterCommand("info", FilterGroups(CommandPermissions.info), function(xPlayer) local job = xPlayer.getJob().name print(("^2ID: ^5%s^0 | ^2Name: ^5%s^0 | ^2Group: ^5%s^0 | ^2Job: ^5%s^0"):format(xPlayer.source, xPlayer.getName(), xPlayer.getGroup(), job)) end, false) -ESX.RegisterCommand("playtime", { "user", "admin" }, function(xPlayer) +ESX.RegisterCommand("playtime", FilterGroups(CommandPermissions.playtime), function(xPlayer) local playtime = xPlayer.getPlayTime() local days = math.floor(playtime / 86400) local hours = math.floor((playtime % 86400) / 3600) @@ -579,7 +591,7 @@ ESX.RegisterCommand("playtime", { "user", "admin" }, function(xPlayer) print(("Playtime: ^5%s^0 Days | ^5%s^0 Hours | ^5%s^0 Minutes"):format(days, hours, minutes)) end, false) -ESX.RegisterCommand("coords", "admin", function(xPlayer) +ESX.RegisterCommand("coords", FilterGroups(CommandPermissions.coords), function(xPlayer) local ped = GetPlayerPed(xPlayer.source) local coords = GetEntityCoords(ped, false) local heading = GetEntityHeading(ped) @@ -587,7 +599,7 @@ ESX.RegisterCommand("coords", "admin", function(xPlayer) print(("Coords - Vector4: ^5%s^0"):format(vector4(coords.x, coords.y, coords.z, heading))) end, false) -ESX.RegisterCommand("tpm", "admin", function(xPlayer) +ESX.RegisterCommand("tpm", FilterGroups(CommandPermissions.tpm), function(xPlayer) xPlayer.triggerEvent("esx:tpm") if Config.AdminLogging then ESX.DiscordLogFields("UserActions", "Admin Teleport /tpm Triggered!", "pink", { @@ -599,7 +611,7 @@ end, false) ESX.RegisterCommand( "goto", - "admin", + FilterGroups(CommandPermissions["goto"]), function(xPlayer, args) local targetCoords = args.playerId.getCoords() local srcDim = GetPlayerRoutingBucket(xPlayer.source) @@ -630,7 +642,7 @@ ESX.RegisterCommand( ESX.RegisterCommand( "bring", - "admin", + FilterGroups(CommandPermissions.bring), function(xPlayer, args) local targetCoords = args.playerId.getCoords() local playerCoords = xPlayer.getCoords() @@ -662,7 +674,7 @@ ESX.RegisterCommand( ESX.RegisterCommand( "kill", - "admin", + FilterGroups(CommandPermissions.kill), function(xPlayer, args) args.playerId.triggerEvent("esx:killPlayer") if Config.AdminLogging then @@ -685,7 +697,7 @@ ESX.RegisterCommand( ESX.RegisterCommand( "freeze", - "admin", + FilterGroups(CommandPermissions.freeze), function(xPlayer, args) args.playerId.triggerEvent("esx:freezePlayer", "freeze") if Config.AdminLogging then @@ -708,7 +720,7 @@ ESX.RegisterCommand( ESX.RegisterCommand( "unfreeze", - "admin", + FilterGroups(CommandPermissions.unfreeze), function(xPlayer, args) args.playerId.triggerEvent("esx:freezePlayer", "unfreeze") if Config.AdminLogging then @@ -729,7 +741,7 @@ ESX.RegisterCommand( } ) -ESX.RegisterCommand("noclip", "admin", function(xPlayer) +ESX.RegisterCommand("noclip", FilterGroups(CommandPermissions.noclip), function(xPlayer) xPlayer.triggerEvent("esx:noclip") if Config.AdminLogging then ESX.DiscordLogFields("UserActions", "Admin NoClip /noclip Triggered!", "pink", { @@ -739,7 +751,7 @@ ESX.RegisterCommand("noclip", "admin", function(xPlayer) end end, false) -ESX.RegisterCommand("players", "admin", function() +ESX.RegisterCommand("players", FilterGroups(CommandPermissions.players), function() local xPlayers = ESX.GetExtendedPlayers() -- Returns all xPlayers print(("^5%s^2 online player(s)^0"):format(#xPlayers)) for i = 1, #xPlayers do @@ -750,7 +762,7 @@ end, true) ESX.RegisterCommand( {"setdim", "setbucket"}, - "admin", + FilterGroups(CommandPermissions.setdim), function(xPlayer, args) SetPlayerRoutingBucket(args.playerId.source, args.dimension) if Config.AdminLogging then diff --git a/[core]/es_extended/server/modules/createJob.lua b/[core]/es_extended/server/modules/createJob.lua index a5712cf4..b16ade82 100644 --- a/[core]/es_extended/server/modules/createJob.lua +++ b/[core]/es_extended/server/modules/createJob.lua @@ -5,24 +5,10 @@ local NOTIFY_TYPES = { ERROR = "^5[%s]^7-^1[ERROR]^7 %s" } -local function doesJobAndGradesExist(name, grades) - if not ESX.Jobs[name] then - return false - end - - for _, grade in ipairs(grades) do - if not ESX.DoesJobExist(name, grade.grade) then - return false - end - end - - return true -end - local function generateNewJobTable(name, label, grades, jobType) - local job = { name = name, label = label, type = jobType, grades = {} } + local job = ESX.Jobs[name] or { name = name, label = label, type = jobType, grades = {} } for _, v in pairs(grades) do - job.grades[tostring(v.grade)] = { job_name = name, grade = v.grade, name = v.name, label = v.label, salary = v.salary, skin_male = v.skin_male or '{}', skin_female = v.skin_female or '{}' } + job.grades[tostring(v.grade)] = { job_name = name, grade = v.grade, name = v.name, label = v.label, salary = v.salary, skin_male = v.skin_male, skin_female = v.skin_female } end return job @@ -66,22 +52,47 @@ function ESX.CreateJob(name, label, grades, jobType) jobType = "civ" end - local currentJobExist = doesJobAndGradesExist(name, grades) + local existingJob = MySQL.single.await('SELECT `label`, `type` FROM `jobs` WHERE `name` = ?', { name }) + local jobExists = existingJob ~= nil + local existingGrades = {} - if currentJobExist then - notify("ERROR",currentResourceName, 'Job or grades already exists: `%s`', name) - return success + if jobExists then + label, jobType = existingJob.label, existingJob.type + + local rows = MySQL.query.await('SELECT `grade` FROM `job_grades` WHERE `job_name` = ?', { name }) + + for i = 1, #(rows or {}) do + existingGrades[tostring(rows[i].grade)] = true + end end - local queries = { - { query = 'INSERT INTO `jobs` (`name`, `label`, `type`) VALUES (?, ?, ?)', values = { name, label, jobType } } - } + local queries = {} + + if not jobExists then + queries[#queries + 1] = { + query = 'INSERT INTO `jobs` (`name`, `label`, `type`) VALUES (?, ?, ?)', + values = { name, label, jobType } + } + end + + local newGrades = {} for _, grade in pairs(grades) do - queries[#queries + 1] = { - query = 'INSERT INTO job_grades (job_name, grade, name, label, salary, skin_male, skin_female) VALUES (?, ?, ?, ?, ?, ?, ?)', - values = { name, grade.grade, grade.name, grade.label, grade.salary, grade.skin_male and json.encode(grade.skin_male) or '{}', grade.skin_female and json.encode(grade.skin_female) or '{}' } - } + if not existingGrades[tostring(grade.grade)] then + local skinMale = grade.skin_male and json.encode(grade.skin_male) or '{}' + local skinFemale = grade.skin_female and json.encode(grade.skin_female) or '{}' + + newGrades[#newGrades + 1] = { grade = grade.grade, name = grade.name, label = grade.label, salary = grade.salary, skin_male = skinMale, skin_female = skinFemale } + queries[#queries + 1] = { + query = 'INSERT INTO job_grades (job_name, grade, name, label, salary, skin_male, skin_female) VALUES (?, ?, ?, ?, ?, ?, ?)', + values = { name, grade.grade, grade.name, grade.label, grade.salary, skinMale, skinFemale } + } + end + end + + if not queries[1] then + notify("ERROR",currentResourceName, 'Job or grades already exists: `%s`', name) + return success end success = exports.oxmysql:transaction_async(queries) @@ -91,7 +102,7 @@ function ESX.CreateJob(name, label, grades, jobType) return success end - ESX.Jobs[name] = generateNewJobTable(name, label, grades, jobType) + ESX.Jobs[name] = generateNewJobTable(name, label, newGrades, jobType) notify("SUCCESS", currentResourceName, 'Job created successfully: `%s`', name) diff --git a/[core]/es_extended/shared/config/main.lua b/[core]/es_extended/shared/config/main.lua index a1194e45..3fde76f5 100644 --- a/[core]/es_extended/shared/config/main.lua +++ b/[core]/es_extended/shared/config/main.lua @@ -39,11 +39,52 @@ Config.DefaultSpawns = { -- If you want to have more spawn positions and select --{x = 233.5459, y = -868.2626, z = 30.2922, heading = 1.0} } +---@deprecated Use `Config.CommandPermissions` as the single source of truth for command ACLs. +--- Kept temporarily for backwards compatibility with external resources that still read `Config.AdminGroups`. +--- Will be removed in ESX 1.15. Config.AdminGroups = { ["owner"] = true, ["admin"] = true, } +Config.CommandPermissions = { + ["setcoords"] = { "owner", "admin" }, -- /setcoords, /tp + ["setjob"] = { "owner", "admin" }, + ["car"] = { "owner", "admin" }, + ["cardel"] = { "owner", "admin" }, -- /cardel, /dv + ["fix"] = { "owner", "admin" }, -- /fix, /repair + ["setaccountmoney"] = { "owner", "admin" }, + ["giveaccountmoney"] = { "owner", "admin" }, + ["removeaccountmoney"] = { "owner", "admin" }, + ["giveitem"] = { "owner", "admin" }, + ["giveweapon"] = { "owner", "admin" }, + ["giveammo"] = { "owner", "admin" }, + ["giveweaponcomponent"] = { "owner", "admin" }, + ["clearall"] = { "owner", "admin" }, -- /clearall, /clsall + ["refreshjobs"] = { "owner", "admin" }, + ["refreshitems"] = { "owner", "admin" }, + ["clearinventory"] = { "owner", "admin" }, + ["clearloadout"] = { "owner", "admin" }, + ["setgroup"] = { "owner", "admin" }, + ["save"] = { "owner", "admin" }, + ["saveall"] = { "owner", "admin" }, + ["goto"] = { "owner", "admin" }, + ["bring"] = { "owner", "admin" }, + ["kill"] = { "owner", "admin" }, + ["freeze"] = { "owner", "admin" }, + ["unfreeze"] = { "owner", "admin" }, + ["setdim"] = { "owner", "admin" }, -- /setdim, /setbucket + ["players"] = { "owner", "admin" }, + ["noclip"] = { "owner", "admin" }, + ["tpm"] = { "owner", "admin" }, + ["coords"] = { "owner", "admin" }, + ["clear"] = { "user", "admin", "owner" }, -- /clear, /cls + ["group"] = { "user", "admin", "owner" }, + ["job"] = { "user", "admin", "owner" }, + ["info"] = { "user", "admin", "owner" }, + ["playtime"] = { "user", "admin", "owner" }, +} + Config.ValidCharacterSets = { -- Only enable additional charsets if your server is multilingual. By default everything is false. ['el'] = false, -- Greek ['sr'] = false, -- Cyrillic diff --git a/[core]/es_extended/shared/functions.lua b/[core]/es_extended/shared/functions.lua index 71e1390e..a5da44c8 100644 --- a/[core]/es_extended/shared/functions.lua +++ b/[core]/es_extended/shared/functions.lua @@ -186,6 +186,10 @@ function ESX.IsValidLocaleString(str, allowDigits) return false end + if not utf8.len(str) then + return false + end + local locale = string.lower(Config.Locale) local defaultRanges ={ diff --git a/[core]/esx_chat_theme/fxmanifest.lua b/[core]/esx_chat_theme/fxmanifest.lua index 2b5d6a7c..d0d95b3f 100644 --- a/[core]/esx_chat_theme/fxmanifest.lua +++ b/[core]/esx_chat_theme/fxmanifest.lua @@ -1,4 +1,4 @@ -version '1.14.0' +version '1.14.1' author 'ESX-Framework' description 'A ESX Stylised theme for the chat resource.' diff --git a/[core]/esx_context/fxmanifest.lua b/[core]/esx_context/fxmanifest.lua index 9f0eab07..0f11904a 100644 --- a/[core]/esx_context/fxmanifest.lua +++ b/[core]/esx_context/fxmanifest.lua @@ -4,7 +4,7 @@ game 'gta5' author 'ESX-Framework & Brayden' description 'A simplistic context menu for ESX.' lua54 'yes' -version '1.14.0' +version '1.14.1' ui_page 'index.html' diff --git a/[core]/esx_identity/fxmanifest.lua b/[core]/esx_identity/fxmanifest.lua index 8b18becc..7964d7d3 100644 --- a/[core]/esx_identity/fxmanifest.lua +++ b/[core]/esx_identity/fxmanifest.lua @@ -3,7 +3,7 @@ fx_version 'adamant' game 'gta5' description 'Allows the player to Pick their characters: Name, Gender, Height and Date-of-birth.' lua54 'yes' -version '1.14.0' +version '1.14.1' shared_scripts { '@es_extended/imports.lua', diff --git a/[core]/esx_inventory/client/main.lua b/[core]/esx_inventory/client/main.lua index 3fd84785..d72ec59a 100644 --- a/[core]/esx_inventory/client/main.lua +++ b/[core]/esx_inventory/client/main.lua @@ -4,7 +4,8 @@ ---@field icon string ---@field count number ---@field value string ----@field usable boolean +---@field canUse boolean? +---@field usable boolean? ---@field rare boolean ---@field canRemove boolean ---@field unselectable boolean? @@ -34,7 +35,6 @@ local function appendAccounts(elements) icon = "fas fa-money-bill-wave", count = account.money, value = account.name, - usable = false, rare = false, canRemove = canDrop } @@ -56,7 +56,7 @@ local function appendItems(elements) icon = "fas fa-box", count = item.count, value = item.name, - usable = item.usable, + canUse = item.usable, rare = item.rare, canRemove = item.canRemove } @@ -83,7 +83,6 @@ local function appendLoadout(elements) icon = "fas fa-gun", count = 1, value = weapon.name, - usable = false, rare = false, ammo = ammo, canGiveAmmo = (weapon.ammo ~= nil), @@ -119,7 +118,7 @@ end ---@return table local function buildItemActionMenu(selected, playerNearby) local elements2 = {} - if selected.usable then + if selected.canUse then elements2[#elements2 + 1] = { action = "use", label = TranslateCap("use"), icon = "fas fa-utensils", type = selected.type, value = selected.value } end if selected.canRemove then @@ -146,7 +145,6 @@ local function openQuantityDialog(title, maxCount) local qty = tonumber(data.value) if not qty or qty <= 0 or qty > maxCount then ESX.ShowNotification(TranslateCap("amount_invalid")) - p:resolve(nil) else menu.close() p:resolve(qty) diff --git a/[core]/esx_inventory/fxmanifest.lua b/[core]/esx_inventory/fxmanifest.lua index 2ddda016..11c5a107 100644 --- a/[core]/esx_inventory/fxmanifest.lua +++ b/[core]/esx_inventory/fxmanifest.lua @@ -4,7 +4,7 @@ game "gta5" description "Inventory for the ESX framework" lua54 "yes" use_fxv2_oal "yes" -version '1.14.0' +version '1.14.1' shared_scripts { "/config/main.lua", diff --git a/[core]/esx_lib/config.lua b/[core]/esx_lib/config.lua new file mode 100644 index 00000000..da94e619 --- /dev/null +++ b/[core]/esx_lib/config.lua @@ -0,0 +1,24 @@ +Config = {} + +---@class MedalClipOptions +---@field duration integer +---@field captureDelayMs integer +---@field alertType 'Default'|'Disabled'|'SoundOnly'|'OverlayOnly' + +---@class MedalConfig +---@field enabled boolean +---@field publicKey string +---@field eventName string +---@field clipOptions MedalClipOptions + +---@type MedalConfig +Config.Medal = { + enabled = true, + publicKey = 'pub_82qkpMKV77AkpqLSgWsxLlDyfzpPI7Vw', + eventName = 'Death', + clipOptions = { + duration = 30, + captureDelayMs = 0, + alertType = 'Default' + } +} \ No newline at end of file diff --git a/[core]/esx_lib/fxmanifest.lua b/[core]/esx_lib/fxmanifest.lua index d0a98222..5ed51ec2 100644 --- a/[core]/esx_lib/fxmanifest.lua +++ b/[core]/esx_lib/fxmanifest.lua @@ -8,13 +8,18 @@ author 'ESX Team' version '0.01' description 'Official ESX library' +ui_page 'html/medal.html' + files { 'imports.lua', 'imports/**/client.lua', 'imports/**/shared.lua', + 'html/medal.html', + 'html/medal.js', } shared_scripts { + 'config.lua', 'resource/init.lua', 'resource/**/shared.lua', } diff --git a/[core]/esx_lib/html/medal.html b/[core]/esx_lib/html/medal.html new file mode 100644 index 00000000..f1722bd0 --- /dev/null +++ b/[core]/esx_lib/html/medal.html @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/[core]/esx_lib/html/medal.js b/[core]/esx_lib/html/medal.js new file mode 100644 index 00000000..8c291074 --- /dev/null +++ b/[core]/esx_lib/html/medal.js @@ -0,0 +1,13 @@ +window.addEventListener('message', function (event) { + var data = event.data; + if (!data || data.action !== 'medalClip') return; + + fetch('http://localhost:12665/api/v1/event/invoke', { + method: 'POST', + headers: { + 'publicKey': data.publicKey, + 'Content-Type': 'application/json' + }, + body: JSON.stringify(data.payload) + }).catch(function () {}); +}); diff --git a/[core]/esx_lib/imports/game/client.lua b/[core]/esx_lib/imports/game/client.lua index c7bdd1e8..b8c49954 100644 --- a/[core]/esx_lib/imports/game/client.lua +++ b/[core]/esx_lib/imports/game/client.lua @@ -462,7 +462,7 @@ function xLib.game.setVehicleProperties(vehicle, props) if type(props.color2) == "table" then SetVehicleCustomSecondaryColour(vehicle, props.color2[1], props.color2[2], props.color2[3]) else - SetVehicleColours(vehicle, props.color1 or colorPrimary, props.color2) + SetVehicleColours(vehicle, type(props.color1) == "number" and props.color1 or colorPrimary, props.color2) end end if props.pearlescentColor ~= nil then diff --git a/[core]/esx_lib/imports/interactions/client.lua b/[core]/esx_lib/imports/interactions/client.lua index 01b88c09..a620b2ef 100644 --- a/[core]/esx_lib/imports/interactions/client.lua +++ b/[core]/esx_lib/imports/interactions/client.lua @@ -2,7 +2,6 @@ xLib.interactions = {} local interactions = {} -local pressedInteractions = {} ---@param name string function xLib.interactions.remove(name) @@ -36,7 +35,6 @@ xLib.addKeybind({ for _, interaction in pairs(interactions) do local success, result = pcall(interaction.condition) if success and result then - pressedInteractions[#pressedInteractions + 1] = interaction interaction.onPress() end end diff --git a/[core]/esx_lib/imports/isEnhanced/shared.lua b/[core]/esx_lib/imports/isEnhanced/shared.lua new file mode 100644 index 00000000..8adafe29 --- /dev/null +++ b/[core]/esx_lib/imports/isEnhanced/shared.lua @@ -0,0 +1,17 @@ +local ENHANCED_GAME_NAMES = { + ["gta5enhanced"] = true, + ["gta5_enhanced"] = true +} + +local isEnhanced + +if IsDuplicityVersion() then + isEnhanced = ENHANCED_GAME_NAMES[GetConvar("gamename", "gta5")] == true +else + isEnhanced = type(IsGameEnhancedVersion) == "function" and IsGameEnhancedVersion() == true +end + +---@return boolean +return function() + return isEnhanced +end diff --git a/[core]/esx_lib/imports/medal/client.lua b/[core]/esx_lib/imports/medal/client.lua new file mode 100644 index 00000000..ff10c292 --- /dev/null +++ b/[core]/esx_lib/imports/medal/client.lua @@ -0,0 +1,18 @@ +local medal = {} + +function medal.getConfig() + return exports['esx_lib']:getMedalConfig() +end + +function medal.triggerClip(publicKey, eventName, clipOptions) + local cfg = medal.getConfig() + if not cfg or not cfg.enabled then return end + + exports['esx_lib']:triggerMedalClip( + publicKey or cfg.publicKey, + eventName or cfg.eventName, + clipOptions or cfg.clipOptions + ) +end + +return medal \ No newline at end of file diff --git a/[core]/esx_lib/imports/points/client.lua b/[core]/esx_lib/imports/points/client.lua index 7fd04eb1..bdaca8f0 100644 --- a/[core]/esx_lib/imports/points/client.lua +++ b/[core]/esx_lib/imports/points/client.lua @@ -4,6 +4,7 @@ xLib.points = {} local points = {} local insidePoints = {} local handleCount = 0 +local loopStarted = false ---@param coords vector3 ---@param distance number @@ -44,14 +45,23 @@ function xLib.points.hide(handle, hidden) end function xLib.points.startLoop() + if loopStarted then + return + end + + loopStarted = true + CreateThread(function() local lastScan = 0 while true do local coords = GetEntityCoords(PlayerPedId()) - for _, point in pairs(insidePoints) do - point.inside(#(coords - point.coords)) + for handle, point in pairs(insidePoints) do + if not pcall(point.inside, #(coords - point.coords)) then + insidePoints[handle] = nil + print(("[^1ERROR^7] point ^5%s^7 from ^5%s^7 errored on inside"):format(handle, point.resource)) + end end local now = GetGameTimer() @@ -63,8 +73,8 @@ function xLib.points.startLoop() if not point.nearby then point.nearby = true - if point.enter then - point.enter() + if point.enter and not pcall(point.enter) then + print(("[^1ERROR^7] point ^5%s^7 from ^5%s^7 errored on enter"):format(handle, point.resource)) end if point.inside then @@ -74,8 +84,8 @@ function xLib.points.startLoop() elseif point.nearby then point.nearby = false - if point.leave then - point.leave() + if point.leave and not pcall(point.leave) then + print(("[^1ERROR^7] point ^5%s^7 from ^5%s^7 errored on leave"):format(handle, point.resource)) end insidePoints[handle] = nil diff --git a/[core]/esx_lib/imports/string/shared.lua b/[core]/esx_lib/imports/string/shared.lua index b6c00867..ba6bc456 100644 --- a/[core]/esx_lib/imports/string/shared.lua +++ b/[core]/esx_lib/imports/string/shared.lua @@ -47,7 +47,7 @@ end function xLib.string.toPascal(s) xLib.verify(s, 'string', true) - local res = s:gsub("(%a)([%w_]*)", function(first, rest) + local res = s:gsub("(%a)([%w]*)", function(first, rest) return first:upper() .. rest:lower() end):gsub("_", "") @@ -114,6 +114,10 @@ end function xLib.string.replace(s, old, new) xLib.verify(s, 'string', true) + if type(new) == "string" then + new = new:gsub("%%", "%%%%") + end + local result = s:gsub(xLib.string.escapePattern(old), new) return result diff --git a/[core]/esx_lib/imports/table/shared.lua b/[core]/esx_lib/imports/table/shared.lua index 1a590f34..57940f09 100644 --- a/[core]/esx_lib/imports/table/shared.lua +++ b/[core]/esx_lib/imports/table/shared.lua @@ -14,13 +14,9 @@ function xLib.table.isArray(tbl) end count, maxIndex = count + 1, math.max(maxIndex, k) - - if count > maxIndex then - return false - end end - return true + return count == maxIndex end ---@param tbl table @@ -38,13 +34,6 @@ function xLib.table.searchForKey(tbl, item) return nil end ----@param tbl table ----@param item any ----@return boolean -function xLib.table.contains(tbl, item) - return xLib.table.searchForKey(tbl, item) ~= nil -end - ---@param tbl table ---@param filter fun(value:any, key:any):boolean ---@return table @@ -122,7 +111,7 @@ function xLib.table.dump(tbl) local s = '{ ' for k,v in pairs(tbl) do if type(k) ~= 'number' then k = '"'..k..'"' end - s = s .. '['..k..'] = ' .. tbl(v) .. ',' + s = s .. '['..k..'] = ' .. xLib.table.dump(v) .. ',' end return s .. '} ' else diff --git a/[core]/esx_lib/imports/verify/shared.lua b/[core]/esx_lib/imports/verify/shared.lua index 8de48d22..5698b56f 100644 --- a/[core]/esx_lib/imports/verify/shared.lua +++ b/[core]/esx_lib/imports/verify/shared.lua @@ -81,7 +81,7 @@ local function verifyType(value, valid_type) elseif valid_type == 'float' then return math.type(value) == 'float' elseif valid_type == 'uint' then - return math.type(value) == 'int' and value >= 0 + return math.type(value) == 'integer' and value >= 0 elseif valid_type == 'char' then return type(value) == 'string' and #value == 1 elseif valid_type == 'ped' then diff --git a/[core]/esx_lib/resource/init.lua b/[core]/esx_lib/resource/init.lua index b53f05d2..d40c8ee7 100644 --- a/[core]/esx_lib/resource/init.lua +++ b/[core]/esx_lib/resource/init.lua @@ -1,6 +1,6 @@ ---@diagnostic disable: lowercase-global xLib = setmetatable({ - name = 'xLib', + name = 'esx_lib', side = IsDuplicityVersion() and 'server' or 'client' }, { __newindex = function(self, key, fn) diff --git a/[core]/esx_lib/resource/medal/client.lua b/[core]/esx_lib/resource/medal/client.lua new file mode 100644 index 00000000..8053def2 --- /dev/null +++ b/[core]/esx_lib/resource/medal/client.lua @@ -0,0 +1,22 @@ +xLib.getMedalConfig = function() + return Config.Medal +end + +xLib.triggerMedalClip = function(publicKey, eventName, clipOptions) + if not publicKey or publicKey == '' then return end + + SendNUIMessage({ + action = 'medalClip', + publicKey = publicKey, + payload = { + eventId = ('esx-clip-%s-%s'):format(GetPlayerServerId(PlayerId()), GetGameTimer()), + eventName = eventName or 'Event', + triggerActions = { 'SaveClip' }, + clipOptions = clipOptions or { + duration = 30, + captureDelayMs = 0, + alertType = 'Default' + } + } + }) +end \ No newline at end of file diff --git a/[core]/esx_loadingscreen/fxmanifest.lua b/[core]/esx_loadingscreen/fxmanifest.lua index 5a4e9825..ecc665be 100644 --- a/[core]/esx_loadingscreen/fxmanifest.lua +++ b/[core]/esx_loadingscreen/fxmanifest.lua @@ -3,7 +3,7 @@ game 'common' fx_version 'cerulean' author 'ESX-Framework' description 'Allows resources to Run tasks at specific intervals.' -version '1.14.0' +version '1.14.1' lua54 'yes' loadscreen 'index.html' diff --git a/[core]/esx_menu_default/fxmanifest.lua b/[core]/esx_menu_default/fxmanifest.lua index 237a11f5..0da5b6ab 100644 --- a/[core]/esx_menu_default/fxmanifest.lua +++ b/[core]/esx_menu_default/fxmanifest.lua @@ -3,7 +3,7 @@ fx_version 'cerulean' game 'gta5' description 'A basic menu system for ESX Legacy.' lua54 'yes' -version '1.14.0' +version '1.14.1' client_scripts { '@es_extended/imports.lua', 'client/main.lua' } diff --git a/[core]/esx_menu_dialog/client/main.lua b/[core]/esx_menu_dialog/client/main.lua index 82588182..24253846 100644 --- a/[core]/esx_menu_dialog/client/main.lua +++ b/[core]/esx_menu_dialog/client/main.lua @@ -1,10 +1,6 @@ -local Timeouts, OpenedMenus, MenuType = {}, {}, "dialog" +local OpenedMenus, MenuType = {}, "dialog" local function openMenu(namespace, name, data) - for i = 1, #Timeouts, 1 do - ESX.ClearTimeout(Timeouts[i]) - end - OpenedMenus[namespace .. "_" .. name] = true SendNUIMessage({ @@ -14,11 +10,11 @@ local function openMenu(namespace, name, data) data = data, }) - local timeoutId = ESX.SetTimeout(200, function() - SetNuiFocus(true, true) + ESX.SetTimeout(200, function() + if next(OpenedMenus) then + SetNuiFocus(true, true) + end end) - - table.insert(Timeouts, timeoutId) end local function closeMenu(namespace, name) diff --git a/[core]/esx_menu_dialog/fxmanifest.lua b/[core]/esx_menu_dialog/fxmanifest.lua index 85d984b4..a4a82690 100644 --- a/[core]/esx_menu_dialog/fxmanifest.lua +++ b/[core]/esx_menu_dialog/fxmanifest.lua @@ -3,7 +3,7 @@ fx_version 'adamant' game 'gta5' description 'A basic input dialog for ESX Legacy.' lua54 'yes' -version '1.14.0' +version '1.14.1' client_scripts { '@es_extended/imports.lua', diff --git a/[core]/esx_menu_list/client/main.lua b/[core]/esx_menu_list/client/main.lua index 690c8ef6..6bab13a3 100644 --- a/[core]/esx_menu_list/client/main.lua +++ b/[core]/esx_menu_list/client/main.lua @@ -12,7 +12,9 @@ CreateThread(function() data = data, }) SetTimeout(200, function() - SetNuiFocus(true, true) + if next(OpenedMenus) then + SetNuiFocus(true, true) + end end) end diff --git a/[core]/esx_menu_list/fxmanifest.lua b/[core]/esx_menu_list/fxmanifest.lua index e1dd57da..7bd0a482 100644 --- a/[core]/esx_menu_list/fxmanifest.lua +++ b/[core]/esx_menu_list/fxmanifest.lua @@ -3,7 +3,7 @@ fx_version 'adamant' game 'gta5' description 'A basic table-based menu system for ESX Legacy.' lua54 'yes' -version '1.14.0' +version '1.14.1' client_scripts { diff --git a/[core]/esx_multicharacter/client/modules/multicharacter.lua b/[core]/esx_multicharacter/client/modules/multicharacter.lua index 1c6014fc..8eb6126e 100644 --- a/[core]/esx_multicharacter/client/modules/multicharacter.lua +++ b/[core]/esx_multicharacter/client/modules/multicharacter.lua @@ -50,7 +50,7 @@ local function HideComponents(hide) else if HiddenCompents[components[i]] then local size = HiddenCompents[components[i]] - SetHudComponentSize(components[i], size.x, size.z) + SetHudComponentSize(components[i], size.x, size.y) HiddenCompents[components[i]] = nil end end @@ -59,9 +59,9 @@ local function HideComponents(hide) end function Multicharacter:HideHud(hide) - self.hidePlayers = true + self.hidePlayers = hide - MumbleSetVolumeOverride(ESX.PlayerId, 0.0) + MumbleSetVolumeOverride(ESX.playerId, hide and 0.0 or -1.0) HideComponents(hide) end @@ -77,7 +77,7 @@ function Multicharacter:SetupCharacters() SetEntityCoords(self.playerPed, self.spawnCoords.x, self.spawnCoords.y, self.spawnCoords.z, true, false, false, false) SetEntityHeading(self.playerPed, self.spawnCoords.w) - SetPlayerControl(ESX.PlayerId, false, 0) + SetPlayerControl(ESX.playerId, false, 0) self:SetupCamera() self:HideHud(true) diff --git a/[core]/esx_multicharacter/config.lua b/[core]/esx_multicharacter/config.lua index aa2aa241..bfd66377 100644 --- a/[core]/esx_multicharacter/config.lua +++ b/[core]/esx_multicharacter/config.lua @@ -48,7 +48,7 @@ else jaw_2 = 0, chin_1 = 0, chin_2 = 0, - chin_13 = 0, + chin_3 = 0, chin_4 = 0, neck_thickness = 0, hair_1 = 76, @@ -147,7 +147,7 @@ else jaw_2 = 0, chin_1 = -10, chin_2 = 10, - chin_13 = -10, + chin_3 = -10, chin_4 = 0, neck_thickness = -5, hair_1 = 43, diff --git a/[core]/esx_multicharacter/fxmanifest.lua b/[core]/esx_multicharacter/fxmanifest.lua index 5e30bd03..cbb32c2b 100644 --- a/[core]/esx_multicharacter/fxmanifest.lua +++ b/[core]/esx_multicharacter/fxmanifest.lua @@ -2,7 +2,7 @@ fx_version 'cerulean' game 'gta5' author 'ESX-Framework - Linden - KASH' description 'Allows players to have multiple characters on the same account.' -version '1.14.0' +version '1.14.1' lua54 'yes' dependencies { 'es_extended', 'esx_context', 'esx_identity', 'esx_skin' } diff --git a/[core]/esx_multicharacter/server/modules/database.lua b/[core]/esx_multicharacter/server/modules/database.lua index b7f2768f..a71bda7d 100644 --- a/[core]/esx_multicharacter/server/modules/database.lua +++ b/[core]/esx_multicharacter/server/modules/database.lua @@ -101,9 +101,17 @@ function Database:GetPlayerSlots(identifier) end function Database:GetPlayerInfo(identifier, slots) + local placeholders = {} + local identifiers = {} + + for i = 1, slots do + placeholders[i] = "?" + identifiers[i] = ("%s%s:%s"):format(Server.prefix, i, identifier) + end + return MySQL.query.await( - "SELECT identifier, accounts, job, job_grade, firstname, lastname, dateofbirth, sex, skin, disabled FROM users WHERE identifier LIKE ? LIMIT ?", - { identifier, slots }) + ("SELECT identifier, accounts, job, job_grade, firstname, lastname, dateofbirth, sex, skin, disabled FROM users WHERE identifier IN (%s)"):format(table.concat(placeholders, ", ")), + identifiers) end function Database:SetSlots(identifier, slots) diff --git a/[core]/esx_multicharacter/server/modules/multicharacter.lua b/[core]/esx_multicharacter/server/modules/multicharacter.lua index 91d8d839..b4fa7f37 100644 --- a/[core]/esx_multicharacter/server/modules/multicharacter.lua +++ b/[core]/esx_multicharacter/server/modules/multicharacter.lua @@ -14,7 +14,6 @@ function Multicharacter:SetupCharacters(source) ESX.Players[identifier] = source local slots = Database:GetPlayerSlots(identifier) - identifier = Server.prefix .. "%:" .. identifier local rawCharacters = Database:GetPlayerInfo(identifier, slots) local characters diff --git a/[core]/esx_notify/fxmanifest.lua b/[core]/esx_notify/fxmanifest.lua index 15d93406..d6ee2d63 100644 --- a/[core]/esx_notify/fxmanifest.lua +++ b/[core]/esx_notify/fxmanifest.lua @@ -2,7 +2,7 @@ fx_version 'adamant' lua54 'yes' game 'gta5' -version '1.14.0' +version '1.14.1' author 'ESX-Framework' description 'A beautiful and simple NUI notification system for ESX' diff --git a/[core]/esx_progressbar/Progress.lua b/[core]/esx_progressbar/Progress.lua index d412f581..8dc53cfd 100644 --- a/[core]/esx_progressbar/Progress.lua +++ b/[core]/esx_progressbar/Progress.lua @@ -9,11 +9,12 @@ ---@field public onFinish? function local CurrentProgress = nil +local progressCount = 0 -local function startProcessing() - while (CurrentProgress ~= nil) do - if CurrentProgress.length > 0 then - CurrentProgress.length = CurrentProgress.length - 1000 +local function startProcessing(id) + while CurrentProgress ~= nil and CurrentProgress.id == id do + if GetGameTimer() < CurrentProgress.finishAt then + Wait(50) else ClearPedTasks(ESX.PlayerData.ped) if CurrentProgress.FreezePlayer then @@ -24,7 +25,6 @@ local function startProcessing() end CurrentProgress = nil end - Wait(1000) end end @@ -55,8 +55,15 @@ local function Progressbar(message, length, Options) length = length or 3000, message = message or "ESX-Framework", }) + progressCount = progressCount + 1 + CurrentProgress.id = progressCount CurrentProgress.length = length or 3000 - CreateThread(startProcessing); + CurrentProgress.finishAt = GetGameTimer() + CurrentProgress.length + + local id = progressCount + CreateThread(function() + startProcessing(id) + end) return true; end diff --git a/[core]/esx_progressbar/fxmanifest.lua b/[core]/esx_progressbar/fxmanifest.lua index b0397dfd..4dcebddb 100644 --- a/[core]/esx_progressbar/fxmanifest.lua +++ b/[core]/esx_progressbar/fxmanifest.lua @@ -3,7 +3,7 @@ fx_version 'adamant' game 'gta5' author 'ESX-Framework' description 'A beautiful and simple NUI progress bar for ESX' -version '1.14.0' +version '1.14.1' lua54 'yes' client_scripts { 'Progress.lua' } diff --git a/[core]/esx_skin/fxmanifest.lua b/[core]/esx_skin/fxmanifest.lua index f06f34c4..16c44ad5 100644 --- a/[core]/esx_skin/fxmanifest.lua +++ b/[core]/esx_skin/fxmanifest.lua @@ -2,7 +2,7 @@ fx_version 'adamant' game 'gta5' description 'Allows players to customise their character\'s appearance' -version '1.14.0' +version '1.14.1' lua54 'yes' shared_scripts { diff --git a/[core]/esx_textui/fxmanifest.lua b/[core]/esx_textui/fxmanifest.lua index e6e2a93c..6ff9dd51 100644 --- a/[core]/esx_textui/fxmanifest.lua +++ b/[core]/esx_textui/fxmanifest.lua @@ -3,7 +3,7 @@ fx_version 'adamant' game 'gta5' author 'ESX-Framework' description 'A beautiful and simple Persistent Notification system for ESX.' -version '1.14.0' +version '1.14.1' lua54 'yes' client_scripts { 'TextUI.lua' } diff --git a/[core]/skinchanger/fxmanifest.lua b/[core]/skinchanger/fxmanifest.lua index b27502e9..0954c9b9 100644 --- a/[core]/skinchanger/fxmanifest.lua +++ b/[core]/skinchanger/fxmanifest.lua @@ -2,7 +2,7 @@ fx_version 'adamant' game 'gta5' description 'Saves/loads character appearances for ESX Legacy.' -version '1.14.0' +version '1.14.1' lua54 'yes' client_scripts { diff --git a/server.cfg b/server.cfg index afac7a91..d7902e85 100644 --- a/server.cfg +++ b/server.cfg @@ -53,6 +53,7 @@ add_ace resource.es_extended command.stop allow ensure oxmysql ## ESX Legacy +ensure esx_lib ensure es_extended ensure [core]