Merge branch 'v1.14.1' into ox-inv

This commit is contained in:
_Not_
2026-08-15 11:00:37 -05:00
committed by GitHub
58 changed files with 613 additions and 238 deletions
+18 -31
View File
@@ -16,7 +16,8 @@ INSERT INTO `addon_account` (`name`, `label`, `shared`) VALUES
('society_cardealer', 'Cardealer', 1), ('society_cardealer', 'Cardealer', 1),
('society_mechanic', 'Mechanic', 1), ('society_mechanic', 'Mechanic', 1),
('society_police', 'Police', 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, `identifier` varchar(60) NOT NULL,
`sender` varchar(60) NOT NULL, `sender` varchar(60) NOT NULL,
`target_type` varchar(50) NOT NULL, `target_type` varchar(50) NOT NULL,
`target` varchar(40) NOT NULL, `target` varchar(60) NOT NULL,
`label` varchar(255) NOT NULL, `label` varchar(255) NOT NULL,
`amount` int(11) NOT NULL `amount` int(11) NOT NULL
) ENGINE=InnoDB; ) ENGINE=InnoDB;
@@ -364,7 +365,7 @@ CREATE TABLE `rented_vehicles` (
`player_name` varchar(255) NOT NULL, `player_name` varchar(255) NOT NULL,
`base_price` int(11) NOT NULL, `base_price` int(11) NOT NULL,
`rent_price` int(11) NOT NULL, `rent_price` int(11) NOT NULL,
`owner` varchar(22) NOT NULL `owner` varchar(60) NOT NULL
) ENGINE=InnoDB; ) ENGINE=InnoDB;
-- --
@@ -749,7 +750,8 @@ ALTER TABLE `addon_account`
ALTER TABLE `addon_account_data` ALTER TABLE `addon_account_data`
ADD PRIMARY KEY (`id`), ADD PRIMARY KEY (`id`),
ADD UNIQUE KEY `index_addon_account_data_account_name_owner` (`account_name`,`owner`), 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` -- Indexes for table `addon_inventory`
@@ -764,13 +766,15 @@ ALTER TABLE `addon_inventory_items`
ADD PRIMARY KEY (`id`), 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` (`inventory_name`,`name`),
ADD KEY `index_addon_inventory_items_inventory_name_name_owner` (`inventory_name`,`name`,`owner`), 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` -- Indexes for table `billing`
-- --
ALTER TABLE `billing` ALTER TABLE `billing`
ADD PRIMARY KEY (`id`); ADD PRIMARY KEY (`id`),
ADD KEY `esx_billing_identifier` (`identifier`);
-- --
-- Indexes for table `cardealer_vehicles` -- Indexes for table `cardealer_vehicles`
@@ -790,7 +794,8 @@ ALTER TABLE `datastore`
ALTER TABLE `datastore_data` ALTER TABLE `datastore_data`
ADD PRIMARY KEY (`id`), ADD PRIMARY KEY (`id`),
ADD UNIQUE KEY `index_datastore_data_name_owner` (`name`,`owner`), 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` -- Indexes for table `items`
@@ -821,7 +826,8 @@ ALTER TABLE `licenses`
-- Indexes for table `owned_vehicles` -- Indexes for table `owned_vehicles`
-- --
ALTER 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` -- Indexes for table `society_moneywash`
-- --
ALTER 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` -- Indexes for table `users`
@@ -856,7 +863,8 @@ ALTER TABLE `users`
-- Indexes for table `user_licenses` -- Indexes for table `user_licenses`
-- --
ALTER 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` -- Indexes for table `vehicle_categories`
@@ -991,27 +999,6 @@ INSERT INTO `fine_types` (label, amount, category) VALUES
('Fraud', 2000, 2); ('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 -- ESX Banking
-- --
+1 -1
View File
@@ -4,6 +4,6 @@ game 'gta5'
author 'ESX-Framework' author 'ESX-Framework'
description 'Allows resources to Run tasks at specific intervals.' description 'Allows resources to Run tasks at specific intervals.'
lua54 'yes' lua54 'yes'
version '1.13.5' version '1.14.0'
server_script 'server/main.lua' server_script 'server/main.lua'
+18 -7
View File
@@ -26,19 +26,30 @@ end
---@param timestamp number ---@param timestamp number
function OnTime(timestamp) function OnTime(timestamp)
for i = 1, #cronJobs, 1 do local function scheduledAt(job, dayOffset)
local scheduledTimestamp = os.time({ return os.time({
hour = cronJobs[i].h, hour = job.h,
min = cronJobs[i].m, min = job.m,
sec = 0, -- Assuming tasks run at the start of the minute 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), month = os.date("%m", timestamp),
year = os.date("%Y", 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 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 end
end end
@@ -25,7 +25,9 @@ function Adjustments:DisableNPCDrops()
end end
function Adjustments:SeatShuffle() function Adjustments:SeatShuffle()
if Config.DisableVehicleSeatShuff then if Config.DisableVehicleSeatShuff and not self.seatShuffleRegistered then
self.seatShuffleRegistered = true
AddEventHandler("esx:enteredVehicle", function(vehicle, _, seat) AddEventHandler("esx:enteredVehicle", function(vehicle, _, seat)
if seat > -1 then if seat > -1 then
SetPedIntoVehicle(ESX.PlayerData.ped, vehicle, seat) SetPedIntoVehicle(ESX.PlayerData.ped, vehicle, seat)
@@ -43,7 +45,7 @@ end
function Adjustments:AmmoAndVehicleRewards() function Adjustments:AmmoAndVehicleRewards()
CreateThread(function() CreateThread(function()
while true do while ESX.PlayerLoaded do
if Config.DisableDisplayAmmo then if Config.DisableDisplayAmmo then
DisplayAmmoThisFrame(false) DisplayAmmoThisFrame(false)
end end
@@ -175,11 +177,11 @@ function Adjustments:ReplacePlaceholders(text)
local success, result = pcall(cb) local success, result = pcall(cb)
if not success then 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" result = "Unknown"
end end
text = text:gsub(("{%s}"):format(placeholder), tostring(result)) text = text:gsub(("{%s}"):format(placeholder), (tostring(result):gsub("%%", "%%%%")))
end end
return text return text
end end
@@ -187,7 +189,7 @@ end
function Adjustments:DiscordPresence() function Adjustments:DiscordPresence()
if Config.DiscordActivity.appId ~= 0 then if Config.DiscordActivity.appId ~= 0 then
CreateThread(function() CreateThread(function()
while true do while ESX.PlayerLoaded do
SetDiscordAppId(Config.DiscordActivity.appId) SetDiscordAppId(Config.DiscordActivity.appId)
SetRichPresence(self:ReplacePlaceholders(Config.DiscordActivity.presence)) SetRichPresence(self:ReplacePlaceholders(Config.DiscordActivity.presence))
SetDiscordRichPresenceAsset(Config.DiscordActivity.assetName) SetDiscordRichPresenceAsset(Config.DiscordActivity.assetName)
@@ -213,7 +215,9 @@ function Adjustments:WantedLevel()
end end
function Adjustments:DisableRadio() 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) AddEventHandler("esx:enteredVehicle", function(vehicle, plate, seat, displayName, netId)
SetVehRadioStation(vehicle,"OFF") SetVehRadioStation(vehicle,"OFF")
SetUserRadioControlEnabled(false) SetUserRadioControlEnabled(false)
@@ -223,7 +227,7 @@ end
function Adjustments:Multipliers() function Adjustments:Multipliers()
CreateThread(function() CreateThread(function()
while true do while ESX.PlayerLoaded do
SetPedDensityMultiplierThisFrame(Config.Multipliers.pedDensity) SetPedDensityMultiplierThisFrame(Config.Multipliers.pedDensity)
SetScenarioPedDensityMultiplierThisFrame(Config.Multipliers.scenarioPedDensityInterior, Config.Multipliers.scenarioPedDensityExterior) SetScenarioPedDensityMultiplierThisFrame(Config.Multipliers.scenarioPedDensityInterior, Config.Multipliers.scenarioPedDensityExterior)
SetAmbientVehicleRangeMultiplierThisFrame(Config.Multipliers.ambientVehicleRange) SetAmbientVehicleRangeMultiplierThisFrame(Config.Multipliers.ambientVehicleRange)
@@ -102,7 +102,7 @@ function ESX.AwaitServerCallback(eventName, ...)
-- if the server callback takes longer than 15 seconds to respond, reject the promise -- if the server callback takes longer than 15 seconds to respond, reject the promise
SetTimeout(15000, function() SetTimeout(15000, function()
if p.state == "pending" then if p.state == 0 then
p:reject("Server Callback Timed Out") p:reject("Server Callback Timed Out")
end end
end) end)
+2 -1
View File
@@ -372,7 +372,6 @@ if not Config.CustomInventory then
while true do while true do
local Sleep = 1500 local Sleep = 1500
local playerCoords = GetEntityCoords(ESX.PlayerData.ped) local playerCoords = GetEntityCoords(ESX.PlayerData.ped)
local _, closestDistance = ESX.Game.GetClosestPlayer(playerCoords)
for pickupId, pickup in pairs(pickups) do for pickupId, pickup in pairs(pickups) do
local distance = #(playerCoords - pickup.coords) local distance = #(playerCoords - pickup.coords)
@@ -383,6 +382,8 @@ if not Config.CustomInventory then
if distance < 1 then if distance < 1 then
if IsControlJustReleased(0, 38) 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 if IsPedOnFoot(ESX.PlayerData.ped) and (closestDistance == -1 or closestDistance > 3) and not pickup.inRange then
pickup.inRange = true pickup.inRange = true
+1 -1
View File
@@ -3,7 +3,7 @@ fx_version 'cerulean'
game 'gta5' game 'gta5'
description 'The Core resource that provides the functionalities for all other resources.' description 'The Core resource that provides the functionalities for all other resources.'
lua54 'yes' lua54 'yes'
version '1.13.5' version '1.14.0'
shared_scripts { shared_scripts {
'@esx_lib/imports.lua', '@esx_lib/imports.lua',
+1 -1
View File
@@ -33,7 +33,7 @@ function Translate(str, ...) -- Translate string
end end
function TranslateCap(str, ...) -- Translate string first char uppercase function TranslateCap(str, ...) -- Translate string first char uppercase
return _(str, ...):gsub("^%l", string.upper) return (_(str, ...):gsub("^%l", string.upper))
end end
_ = Translate _ = Translate
@@ -15,57 +15,84 @@ local requiredMethods = {
} }
---Parses the inventory:accounts convar to build a lookup table. ---Parses the inventory:accounts convar to build a lookup table.
---Uses the same default as ox_inventory: '["money"]' ---Uses the same default and JSON format as ox_inventory.
---@return table<string, boolean> ---@return table<string, boolean>
local function getAccountList() local function getAccountList()
local accounts = {}
local convar = GetConvar("inventory:accounts", '["money"]') local convar = GetConvar("inventory:accounts", '["money"]')
local ok, list = pcall(json.decode, convar) local ok, list = pcall(json.decode, convar)
if ok and type(list) == "table" then if not ok or type(list) ~= "table" then
for i = 1, #list do error(
accounts[list[i]] = true ("[es_extended] Invalid inventory:accounts convar: %s")
end :format(tostring(convar)),
else 2
-- Fallback for non-JSON formats (e.g. "money" or "money black_money") )
for account in convar:gmatch("[%w_]+") do end
if account ~= "[]" then
accounts[account] = true local accounts = {}
end
for i = 1, #list do
local account = list[i]
if type(account) == "string" and account ~= "" then
accounts[account] = true
end end
end end
return accounts return accounts
end end
---Creates a proxy table that routes method calls to ox_inventory exports. ---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 ---@return table
local function createOxInventoryProxy() local function createOxInventoryProxy()
local proxy = {} local proxy = {}
local accounts = getAccountList() local resourceExports = exports.ox_inventory
print("^3[es_extended] ox_inventory: using direct export proxy (Inventory() module unavailable)^7") print("^3[es_extended] ox_inventory: using direct export proxy (Inventory() interface unavailable or incomplete)^7")
for i = 1, #requiredMethods do for i = 1, #requiredMethods do
local method = requiredMethods[i] local method = requiredMethods[i]
local ok, err = pcall(function() local exportFn = resolveExport(resourceExports, method)
exports.ox_inventory[method](exports.ox_inventory, 0, "test", 0)
end)
if not ok and tostring(err):find("No such export") then
print(("^1[es_extended] CRITICAL: exports.ox_inventory.%s is missing^7"):format(method))
end
proxy[method] = function(...) proxy[method] = function(...)
return exports.ox_inventory[method](exports.ox_inventory, ...) return exportFn(resourceExports, ...)
end end
end end
proxy.accounts = accounts proxy.accounts = getAccountList()
return proxy return proxy
end end
---Checks if a module table contains all required methods. ---Checks if an Inventory() module provides everything ESX requires.
---@param module table ---@param module table
---@return boolean ---@return boolean
local function isValidModule(module) local function isValidModule(module)
@@ -79,12 +106,15 @@ local function isValidModule(module)
end end
end end
if type(module.accounts) ~= "table" then
return false
end
return true return true
end end
---Returns the ox_inventory interface. ---Returns the ox_inventory interface.
---Tries the legacy Inventory() export first for backward compatibility, ---Uses Inventory() when available, otherwise falls back to public exports.
---then falls back to direct export calls.
---@return table ---@return table
local function getOxInventory() local function getOxInventory()
if OxInventory then if OxInventory then
@@ -505,6 +505,10 @@ function CreateExtendedPlayer(playerId, identifier, ssn, group, accounts, invent
if item and count >= 0 then if item and count >= 0 then
count = ESX.Math.Round(count) count = ESX.Math.Round(count)
if count == item.count then
return
end
if count > item.count then if count > item.count then
self.addInventoryItem(item.name, count - item.count) self.addInventoryItem(item.name, count - item.count)
else else
@@ -154,6 +154,7 @@ Core.vehicleClass = {
vehicleData.plate = newPlate vehicleData.plate = newPlate
Core.vehicles[newPlate] = table.clone(vehicleData) Core.vehicles[newPlate] = table.clone(vehicleData)
Core.vehicles[oldPlate] = nil Core.vehicles[oldPlate] = nil
self.plate = newPlate
TriggerEvent("esx:changedExtendedVehiclePlate", vehicleData.plate, oldPlate) TriggerEvent("esx:changedExtendedVehiclePlate", vehicleData.plate, oldPlate)
Wait(0) Wait(0)
@@ -167,7 +168,7 @@ Core.vehicleClass = {
assert(type(newProps) == "table", "Expected 'props' to be a table") assert(type(newProps) == "table", "Expected 'props' to be a table")
local vehicleData = Core.vehicles[self.plate] 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 if affectedRows <= 0 then
self:delete() self:delete()
return false return false
+26 -14
View File
@@ -136,7 +136,7 @@ function ESX.RegisterCommand(name, group, cb, allowConsole, suggestion)
end end
local merge = table.concat(args, " ") 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 elseif v.type == "coordinate" then
local coord = tonumber(args[k]:match("(-?%d+%.?%d*)")) local coord = tonumber(args[k]:match("(-?%d+%.?%d*)"))
if not coord then if not coord then
@@ -251,18 +251,24 @@ function Core.SavePlayers(cb)
local parameters = {} local parameters = {}
for _, xPlayer in pairs(ESX.Players) do for _, xPlayer in pairs(ESX.Players) do
updateHealthAndArmorInMetadata(xPlayer) if xPlayer.spawned then
parameters[#parameters + 1] = { updateHealthAndArmorInMetadata(xPlayer)
json.encode(xPlayer.getAccounts(true)), parameters[#parameters + 1] = {
xPlayer.job.name, json.encode(xPlayer.getAccounts(true)),
xPlayer.job.grade, xPlayer.job.name,
xPlayer.group, xPlayer.job.grade,
json.encode(xPlayer.getCoords(false, true)), xPlayer.group,
json.encode(xPlayer.getInventory(true)), json.encode(xPlayer.getCoords(false, true)),
json.encode(xPlayer.getLoadout(true)), json.encode(xPlayer.getInventory(true)),
json.encode(xPlayer.getMeta()), json.encode(xPlayer.getLoadout(true)),
xPlayer.identifier, json.encode(xPlayer.getMeta()),
} xPlayer.identifier,
}
end
end
if not parameters[1] then
return cb and cb()
end end
MySQL.prepare( MySQL.prepare(
@@ -799,9 +805,15 @@ if not Config.CustomInventory then
end end
if #toInsert > 0 then 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( MySQL.prepare.await(
"INSERT IGNORE INTO `items` (`name`, `label`, `weight`, `rare`, `can_remove`) VALUES (?, ?, ?, ?, ?)", "INSERT IGNORE INTO `items` (`name`, `label`, `weight`, `rare`, `can_remove`) VALUES (?, ?, ?, ?, ?)",
toInsert) parameters)
for i = 1, #toInsert do for i = 1, #toInsert do
local row = toInsert[i] local row = toInsert[i]
+8 -7
View File
@@ -2,6 +2,7 @@ SetMapName("San Andreas")
SetGameType("ESX Legacy") SetGameType("ESX Legacy")
local oneSyncState = GetConvar("onesync", "off") local oneSyncState = GetConvar("onesync", "off")
local isEnhanced = xLib.isEnhanced()
local newPlayer = "INSERT INTO `users` SET `accounts` = ?, `identifier` = ?, `ssn` = ?, `group` = ?" local newPlayer = "INSERT INTO `users` SET `accounts` = ?, `identifier` = ?, `ssn` = ?, `group` = ?"
local loadPlayer = "SELECT `accounts`, `ssn`, `job`, `job_grade`, `group`, `position`, `inventory`, `skin`, `loadout`, `metadata`" 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.")) return deferrals.done(("[ESX] ESX Requires a minimum Artifact version of 10188, Please update your server."))
end 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)) return deferrals.done(("[ESX] ESX Requires Onesync Infinity to work. This server currently has Onesync set to: %s"):format(oneSyncState))
end end
@@ -288,9 +289,9 @@ function loadESXPlayer(identifier, playerId, isNew)
local loadout = json.decode(result.loadout) local loadout = json.decode(result.loadout)
for name, weapon in pairs(loadout) do 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] = { userData.loadout[#userData.loadout + 1] = {
name = name, name = name,
ammo = weapon.ammo, 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)] userData.coords = json.decode(result.position) or Config.DefaultSpawns[ESX.Math.Random(1,#Config.DefaultSpawns)]
-- Skin -- 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 -- Metadata
userData.metadata = (result.metadata and result.metadata ~= "") and json.decode(result.metadata) or {} 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 weaponComponents = ESX.Table.Clone(weapon.components)
local weaponTint = weapon.tintIndex local weaponTint = weapon.tintIndex
sourceXPlayer.removeWeapon(itemName)
targetXPlayer.addWeapon(itemName, itemCount)
if weaponTint then if weaponTint then
targetXPlayer.setWeaponTint(itemName, weaponTint) targetXPlayer.setWeaponTint(itemName, weaponTint)
end end
@@ -484,9 +488,6 @@ if not Config.CustomInventory then
end end
end end
sourceXPlayer.removeWeapon(itemName)
targetXPlayer.addWeapon(itemName, itemCount)
if weaponObject.ammo and itemCount > 0 then if weaponObject.ammo and itemCount > 0 then
local ammoLabel = weaponObject.ammo.label local ammoLabel = weaponObject.ammo.label
sourceXPlayer.showNotification(TranslateCap("gave_weapon_withammo", weaponLabel, itemCount, ammoLabel, targetXPlayer.name)) sourceXPlayer.showNotification(TranslateCap("gave_weapon_withammo", weaponLabel, itemCount, ammoLabel, targetXPlayer.name))
@@ -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
@@ -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
@@ -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
@@ -107,7 +107,7 @@ function ESX.AwaitClientCallback(player, eventName, ...)
if not p then return end if not p then return end
SetTimeout(15000, function() SetTimeout(15000, function()
if p.state == "pending" then if p.state == 0 then
p:reject("Server Callback Timed Out") p:reject("Server Callback Timed Out")
end end
end) end)
+52 -40
View File
@@ -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( ESX.RegisterCommand(
{ "setcoords", "tp" }, { "setcoords", "tp" },
"admin", FilterGroups(CommandPermissions.setcoords),
function(xPlayer, args) function(xPlayer, args)
xPlayer.setCoords({ x = args.x, y = args.y, z = args.z }) xPlayer.setCoords({ x = args.x, y = args.y, z = args.z })
if Config.AdminLogging then if Config.AdminLogging then
@@ -27,7 +33,7 @@ ESX.RegisterCommand(
ESX.RegisterCommand( ESX.RegisterCommand(
"setjob", "setjob",
"admin", FilterGroups(CommandPermissions.setjob),
function(xPlayer, args, showError) function(xPlayer, args, showError)
if not ESX.DoesJobExist(args.job, args.grade) then if not ESX.DoesJobExist(args.job, args.grade) then
return showError(TranslateCap("command_setjob_invalid")) return showError(TranslateCap("command_setjob_invalid"))
@@ -68,7 +74,7 @@ local upgrades = Config.SpawnVehMaxUpgrades and {
ESX.RegisterCommand( ESX.RegisterCommand(
"car", "car",
"admin", FilterGroups(CommandPermissions.car),
function(xPlayer, args, showError) function(xPlayer, args, showError)
if not xPlayer then if not xPlayer then
return showError("[^1ERROR^7] The xPlayer value is nil") return showError("[^1ERROR^7] The xPlayer value is nil")
@@ -132,7 +138,7 @@ ESX.RegisterCommand(
ESX.RegisterCommand( ESX.RegisterCommand(
{ "cardel", "dv" }, { "cardel", "dv" },
"admin", FilterGroups(CommandPermissions.cardel),
function(xPlayer, args) function(xPlayer, args)
local ped = GetPlayerPed(xPlayer.source) local ped = GetPlayerPed(xPlayer.source)
local pedVehicle = GetVehiclePedIsIn(ped, false) local pedVehicle = GetVehiclePedIsIn(ped, false)
@@ -168,7 +174,7 @@ ESX.RegisterCommand(
ESX.RegisterCommand( ESX.RegisterCommand(
{ "fix", "repair" }, { "fix", "repair" },
"admin", FilterGroups(CommandPermissions.fix),
function(xPlayer, args, showError) function(xPlayer, args, showError)
local xTarget = args.playerId local xTarget = args.playerId
local ped = GetPlayerPed(xTarget.source) local ped = GetPlayerPed(xTarget.source)
@@ -178,9 +184,11 @@ ESX.RegisterCommand(
return return
end end
xTarget.triggerEvent("esx:repairPedVehicle") xTarget.triggerEvent("esx:repairPedVehicle")
xPlayer.showNotification(TranslateCap("command_repair_success"), true, false, 140) if xPlayer then
if xPlayer.source ~= xTarget.source then xPlayer.showNotification(TranslateCap("command_repair_success"))
xTarget.showNotification(TranslateCap("command_repair_success_target"), true, false, 140) end
if not xPlayer or xPlayer.source ~= xTarget.source then
xTarget.showNotification(TranslateCap("command_repair_success_target"))
end end
if Config.AdminLogging then if Config.AdminLogging then
ESX.DiscordLogFields("UserActions", "Fix Vehicle /fix Triggered!", "pink", { ESX.DiscordLogFields("UserActions", "Fix Vehicle /fix Triggered!", "pink", {
@@ -202,7 +210,7 @@ ESX.RegisterCommand(
ESX.RegisterCommand( ESX.RegisterCommand(
"setaccountmoney", "setaccountmoney",
"admin", FilterGroups(CommandPermissions.setaccountmoney),
function(xPlayer, args, showError) function(xPlayer, args, showError)
if not args.playerId.getAccount(args.account) then if not args.playerId.getAccount(args.account) then
return showError(TranslateCap("command_giveaccountmoney_invalid")) return showError(TranslateCap("command_giveaccountmoney_invalid"))
@@ -232,7 +240,7 @@ ESX.RegisterCommand(
ESX.RegisterCommand( ESX.RegisterCommand(
"giveaccountmoney", "giveaccountmoney",
"admin", FilterGroups(CommandPermissions.giveaccountmoney),
function(xPlayer, args, showError) function(xPlayer, args, showError)
if not args.playerId.getAccount(args.account) then if not args.playerId.getAccount(args.account) then
return showError(TranslateCap("command_giveaccountmoney_invalid")) return showError(TranslateCap("command_giveaccountmoney_invalid"))
@@ -262,7 +270,7 @@ ESX.RegisterCommand(
ESX.RegisterCommand( ESX.RegisterCommand(
"removeaccountmoney", "removeaccountmoney",
"admin", FilterGroups(CommandPermissions.removeaccountmoney),
function(xPlayer, args, showError) function(xPlayer, args, showError)
if not args.playerId.getAccount(args.account) then if not args.playerId.getAccount(args.account) then
return showError(TranslateCap("command_removeaccountmoney_invalid")) return showError(TranslateCap("command_removeaccountmoney_invalid"))
@@ -293,7 +301,7 @@ ESX.RegisterCommand(
if not Config.CustomInventory then if not Config.CustomInventory then
ESX.RegisterCommand( ESX.RegisterCommand(
"giveitem", "giveitem",
"admin", FilterGroups(CommandPermissions.giveitem),
function(xPlayer, args) function(xPlayer, args)
args.playerId.addInventoryItem(args.item, args.count) args.playerId.addInventoryItem(args.item, args.count)
if Config.AdminLogging then if Config.AdminLogging then
@@ -323,7 +331,7 @@ if not Config.CustomInventory then
ESX.RegisterCommand( ESX.RegisterCommand(
"giveweapon", "giveweapon",
"admin", FilterGroups(CommandPermissions.giveweapon),
function(xPlayer, args, showError) function(xPlayer, args, showError)
if args.playerId.hasWeapon(args.weapon) then if args.playerId.hasWeapon(args.weapon) then
return showError(TranslateCap("command_giveweapon_hasalready")) return showError(TranslateCap("command_giveweapon_hasalready"))
@@ -353,7 +361,7 @@ if not Config.CustomInventory then
ESX.RegisterCommand( ESX.RegisterCommand(
"giveammo", "giveammo",
"admin", FilterGroups(CommandPermissions.giveammo),
function(xPlayer, args, showError) function(xPlayer, args, showError)
if not args.playerId.hasWeapon(args.weapon) then if not args.playerId.hasWeapon(args.weapon) then
return showError(TranslateCap("command_giveammo_noweapon_found")) return showError(TranslateCap("command_giveammo_noweapon_found"))
@@ -383,7 +391,7 @@ if not Config.CustomInventory then
ESX.RegisterCommand( ESX.RegisterCommand(
"giveweaponcomponent", "giveweaponcomponent",
"admin", FilterGroups(CommandPermissions.giveweaponcomponent),
function(xPlayer, args, showError) function(xPlayer, args, showError)
if args.playerId.hasWeapon(args.weaponName) then if args.playerId.hasWeapon(args.weaponName) then
local component = ESX.GetWeaponComponent(args.weaponName, args.componentName) local component = ESX.GetWeaponComponent(args.weaponName, args.componentName)
@@ -423,11 +431,11 @@ if not Config.CustomInventory then
) )
end end
ESX.RegisterCommand({ "clear", "cls" }, "user", function(xPlayer) ESX.RegisterCommand({ "clear", "cls" }, FilterGroups(CommandPermissions.clear), function(xPlayer)
xPlayer.triggerEvent("chat:clear") xPlayer.triggerEvent("chat:clear")
end, false, { help = TranslateCap("command_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) TriggerClientEvent("chat:clear", -1)
if Config.AdminLogging then if Config.AdminLogging then
ESX.DiscordLogFields("UserActions", "Clear Chat /clearall Triggered!", "pink", { ESX.DiscordLogFields("UserActions", "Clear Chat /clearall Triggered!", "pink", {
@@ -437,20 +445,24 @@ ESX.RegisterCommand({ "clearall", "clsall" }, "admin", function(xPlayer)
end end
end, true, { help = TranslateCap("command_clearall") }) end, true, { help = TranslateCap("command_clearall") })
ESX.RegisterCommand("refreshjobs", "admin", function() ESX.RegisterCommand("refreshjobs", FilterGroups(CommandPermissions.refreshjobs), function()
ESX.RefreshJobs() ESX.RefreshJobs()
end, true, { help = TranslateCap("command_clearall") }) end, true, { help = TranslateCap("command_clearall") })
if not Config.CustomInventory then if not Config.CustomInventory then
ESX.RegisterCommand("refreshitems", "admin", function(xPlayer) ESX.RegisterCommand("refreshitems", FilterGroups(CommandPermissions.refreshitems), function(xPlayer)
local itemCount = ESX.RefreshItems() 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") }) end, true, { help = TranslateCap("command_refreshitems") })
ESX.RegisterCommand( ESX.RegisterCommand(
"clearinventory", "clearinventory",
"admin", FilterGroups(CommandPermissions.clearinventory),
function(xPlayer, args) function(xPlayer, args)
for _, v in ipairs(args.playerId.inventory) do for _, v in ipairs(args.playerId.inventory) do
if v.count > 0 then if v.count > 0 then
@@ -478,7 +490,7 @@ if not Config.CustomInventory then
ESX.RegisterCommand( ESX.RegisterCommand(
"clearloadout", "clearloadout",
"admin", FilterGroups(CommandPermissions.clearloadout),
function(xPlayer, args) function(xPlayer, args)
for i = #args.playerId.loadout, 1, -1 do for i = #args.playerId.loadout, 1, -1 do
args.playerId.removeWeapon(args.playerId.loadout[i].name) args.playerId.removeWeapon(args.playerId.loadout[i].name)
@@ -505,7 +517,7 @@ end
ESX.RegisterCommand( ESX.RegisterCommand(
"setgroup", "setgroup",
"admin", FilterGroups(CommandPermissions.setgroup),
function(xPlayer, args) function(xPlayer, args)
if not args.playerId then if not args.playerId then
args.playerId = xPlayer.source args.playerId = xPlayer.source
@@ -537,7 +549,7 @@ ESX.RegisterCommand(
ESX.RegisterCommand( ESX.RegisterCommand(
"save", "save",
"admin", FilterGroups(CommandPermissions.save),
function(_, args) function(_, args)
Core.SavePlayer(args.playerId) Core.SavePlayer(args.playerId)
print(("[^2Info^0] Saved Player - ^5%s^0"):format(args.playerId.source)) 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() Core.SavePlayers()
end, true, { help = TranslateCap("command_saveall") }) 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())) 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() 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")) 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) end, false)
ESX.RegisterCommand("info", { "user", "admin" }, function(xPlayer) ESX.RegisterCommand("info", FilterGroups(CommandPermissions.info), function(xPlayer)
local job = xPlayer.getJob().name 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)) 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) end, false)
ESX.RegisterCommand("playtime", { "user", "admin" }, function(xPlayer) ESX.RegisterCommand("playtime", FilterGroups(CommandPermissions.playtime), function(xPlayer)
local playtime = xPlayer.getPlayTime() local playtime = xPlayer.getPlayTime()
local days = math.floor(playtime / 86400) local days = math.floor(playtime / 86400)
local hours = math.floor((playtime % 86400) / 3600) 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)) print(("Playtime: ^5%s^0 Days | ^5%s^0 Hours | ^5%s^0 Minutes"):format(days, hours, minutes))
end, false) end, false)
ESX.RegisterCommand("coords", "admin", function(xPlayer) ESX.RegisterCommand("coords", FilterGroups(CommandPermissions.coords), function(xPlayer)
local ped = GetPlayerPed(xPlayer.source) local ped = GetPlayerPed(xPlayer.source)
local coords = GetEntityCoords(ped, false) local coords = GetEntityCoords(ped, false)
local heading = GetEntityHeading(ped) 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))) print(("Coords - Vector4: ^5%s^0"):format(vector4(coords.x, coords.y, coords.z, heading)))
end, false) end, false)
ESX.RegisterCommand("tpm", "admin", function(xPlayer) ESX.RegisterCommand("tpm", FilterGroups(CommandPermissions.tpm), function(xPlayer)
xPlayer.triggerEvent("esx:tpm") xPlayer.triggerEvent("esx:tpm")
if Config.AdminLogging then if Config.AdminLogging then
ESX.DiscordLogFields("UserActions", "Admin Teleport /tpm Triggered!", "pink", { ESX.DiscordLogFields("UserActions", "Admin Teleport /tpm Triggered!", "pink", {
@@ -599,7 +611,7 @@ end, false)
ESX.RegisterCommand( ESX.RegisterCommand(
"goto", "goto",
"admin", FilterGroups(CommandPermissions["goto"]),
function(xPlayer, args) function(xPlayer, args)
local targetCoords = args.playerId.getCoords() local targetCoords = args.playerId.getCoords()
local srcDim = GetPlayerRoutingBucket(xPlayer.source) local srcDim = GetPlayerRoutingBucket(xPlayer.source)
@@ -630,7 +642,7 @@ ESX.RegisterCommand(
ESX.RegisterCommand( ESX.RegisterCommand(
"bring", "bring",
"admin", FilterGroups(CommandPermissions.bring),
function(xPlayer, args) function(xPlayer, args)
local targetCoords = args.playerId.getCoords() local targetCoords = args.playerId.getCoords()
local playerCoords = xPlayer.getCoords() local playerCoords = xPlayer.getCoords()
@@ -662,7 +674,7 @@ ESX.RegisterCommand(
ESX.RegisterCommand( ESX.RegisterCommand(
"kill", "kill",
"admin", FilterGroups(CommandPermissions.kill),
function(xPlayer, args) function(xPlayer, args)
args.playerId.triggerEvent("esx:killPlayer") args.playerId.triggerEvent("esx:killPlayer")
if Config.AdminLogging then if Config.AdminLogging then
@@ -685,7 +697,7 @@ ESX.RegisterCommand(
ESX.RegisterCommand( ESX.RegisterCommand(
"freeze", "freeze",
"admin", FilterGroups(CommandPermissions.freeze),
function(xPlayer, args) function(xPlayer, args)
args.playerId.triggerEvent("esx:freezePlayer", "freeze") args.playerId.triggerEvent("esx:freezePlayer", "freeze")
if Config.AdminLogging then if Config.AdminLogging then
@@ -708,7 +720,7 @@ ESX.RegisterCommand(
ESX.RegisterCommand( ESX.RegisterCommand(
"unfreeze", "unfreeze",
"admin", FilterGroups(CommandPermissions.unfreeze),
function(xPlayer, args) function(xPlayer, args)
args.playerId.triggerEvent("esx:freezePlayer", "unfreeze") args.playerId.triggerEvent("esx:freezePlayer", "unfreeze")
if Config.AdminLogging then 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") xPlayer.triggerEvent("esx:noclip")
if Config.AdminLogging then if Config.AdminLogging then
ESX.DiscordLogFields("UserActions", "Admin NoClip /noclip Triggered!", "pink", { ESX.DiscordLogFields("UserActions", "Admin NoClip /noclip Triggered!", "pink", {
@@ -739,7 +751,7 @@ ESX.RegisterCommand("noclip", "admin", function(xPlayer)
end end
end, false) end, false)
ESX.RegisterCommand("players", "admin", function() ESX.RegisterCommand("players", FilterGroups(CommandPermissions.players), function()
local xPlayers = ESX.GetExtendedPlayers() -- Returns all xPlayers local xPlayers = ESX.GetExtendedPlayers() -- Returns all xPlayers
print(("^5%s^2 online player(s)^0"):format(#xPlayers)) print(("^5%s^2 online player(s)^0"):format(#xPlayers))
for i = 1, #xPlayers do for i = 1, #xPlayers do
@@ -750,7 +762,7 @@ end, true)
ESX.RegisterCommand( ESX.RegisterCommand(
{"setdim", "setbucket"}, {"setdim", "setbucket"},
"admin", FilterGroups(CommandPermissions.setdim),
function(xPlayer, args) function(xPlayer, args)
SetPlayerRoutingBucket(args.playerId.source, args.dimension) SetPlayerRoutingBucket(args.playerId.source, args.dimension)
if Config.AdminLogging then if Config.AdminLogging then
+39 -28
View File
@@ -5,24 +5,10 @@ local NOTIFY_TYPES = {
ERROR = "^5[%s]^7-^1[ERROR]^7 %s" 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 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 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 end
return job return job
@@ -66,22 +52,47 @@ function ESX.CreateJob(name, label, grades, jobType)
jobType = "civ" jobType = "civ"
end 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 if jobExists then
notify("ERROR",currentResourceName, 'Job or grades already exists: `%s`', name) label, jobType = existingJob.label, existingJob.type
return success
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 end
local queries = { local queries = {}
{ query = 'INSERT INTO `jobs` (`name`, `label`, `type`) VALUES (?, ?, ?)', values = { name, label, jobType } }
} 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 for _, grade in pairs(grades) do
queries[#queries + 1] = { if not existingGrades[tostring(grade.grade)] then
query = 'INSERT INTO job_grades (job_name, grade, name, label, salary, skin_male, skin_female) VALUES (?, ?, ?, ?, ?, ?, ?)', local skinMale = grade.skin_male and json.encode(grade.skin_male) or '{}'
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 '{}' } 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 end
success = exports.oxmysql:transaction_async(queries) success = exports.oxmysql:transaction_async(queries)
@@ -91,7 +102,7 @@ function ESX.CreateJob(name, label, grades, jobType)
return success return success
end 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) notify("SUCCESS", currentResourceName, 'Job created successfully: `%s`', name)
+41
View File
@@ -34,11 +34,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} --{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 = { Config.AdminGroups = {
["owner"] = true, ["owner"] = true,
["admin"] = 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. Config.ValidCharacterSets = { -- Only enable additional charsets if your server is multilingual. By default everything is false.
['el'] = false, -- Greek ['el'] = false, -- Greek
['sr'] = false, -- Cyrillic ['sr'] = false, -- Cyrillic
+4
View File
@@ -186,6 +186,10 @@ function ESX.IsValidLocaleString(str, allowDigits)
return false return false
end end
if not utf8.len(str) then
return false
end
local locale = string.lower(Config.Locale) local locale = string.lower(Config.Locale)
local defaultRanges ={ local defaultRanges ={
+1 -1
View File
@@ -1,4 +1,4 @@
version '1.13.5' version '1.14.0'
author 'ESX-Framework' author 'ESX-Framework'
description 'A ESX Stylised theme for the chat resource.' description 'A ESX Stylised theme for the chat resource.'
+1 -1
View File
@@ -4,7 +4,7 @@ game 'gta5'
author 'ESX-Framework & Brayden' author 'ESX-Framework & Brayden'
description 'A simplistic context menu for ESX.' description 'A simplistic context menu for ESX.'
lua54 'yes' lua54 'yes'
version '1.13.5' version '1.14.0'
ui_page 'index.html' ui_page 'index.html'
+1 -1
View File
@@ -3,7 +3,7 @@ fx_version 'adamant'
game 'gta5' game 'gta5'
description 'Allows the player to Pick their characters: Name, Gender, Height and Date-of-birth.' description 'Allows the player to Pick their characters: Name, Gender, Height and Date-of-birth.'
lua54 'yes' lua54 'yes'
version '1.13.5' version '1.14.0'
shared_scripts { shared_scripts {
'@es_extended/imports.lua', '@es_extended/imports.lua',
+4 -6
View File
@@ -4,7 +4,8 @@
---@field icon string ---@field icon string
---@field count number ---@field count number
---@field value string ---@field value string
---@field usable boolean ---@field canUse boolean?
---@field usable boolean?
---@field rare boolean ---@field rare boolean
---@field canRemove boolean ---@field canRemove boolean
---@field unselectable boolean? ---@field unselectable boolean?
@@ -34,7 +35,6 @@ local function appendAccounts(elements)
icon = "fas fa-money-bill-wave", icon = "fas fa-money-bill-wave",
count = account.money, count = account.money,
value = account.name, value = account.name,
usable = false,
rare = false, rare = false,
canRemove = canDrop canRemove = canDrop
} }
@@ -56,7 +56,7 @@ local function appendItems(elements)
icon = "fas fa-box", icon = "fas fa-box",
count = item.count, count = item.count,
value = item.name, value = item.name,
usable = item.usable, canUse = item.usable,
rare = item.rare, rare = item.rare,
canRemove = item.canRemove canRemove = item.canRemove
} }
@@ -83,7 +83,6 @@ local function appendLoadout(elements)
icon = "fas fa-gun", icon = "fas fa-gun",
count = 1, count = 1,
value = weapon.name, value = weapon.name,
usable = false,
rare = false, rare = false,
ammo = ammo, ammo = ammo,
canGiveAmmo = (weapon.ammo ~= nil), canGiveAmmo = (weapon.ammo ~= nil),
@@ -119,7 +118,7 @@ end
---@return table ---@return table
local function buildItemActionMenu(selected, playerNearby) local function buildItemActionMenu(selected, playerNearby)
local elements2 = {} 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 } elements2[#elements2 + 1] = { action = "use", label = TranslateCap("use"), icon = "fas fa-utensils", type = selected.type, value = selected.value }
end end
if selected.canRemove then if selected.canRemove then
@@ -146,7 +145,6 @@ local function openQuantityDialog(title, maxCount)
local qty = tonumber(data.value) local qty = tonumber(data.value)
if not qty or qty <= 0 or qty > maxCount then if not qty or qty <= 0 or qty > maxCount then
ESX.ShowNotification(TranslateCap("amount_invalid")) ESX.ShowNotification(TranslateCap("amount_invalid"))
p:resolve(nil)
else else
menu.close() menu.close()
p:resolve(qty) p:resolve(qty)
+1 -1
View File
@@ -4,7 +4,7 @@ game "gta5"
description "Inventory for the ESX framework" description "Inventory for the ESX framework"
lua54 "yes" lua54 "yes"
use_fxv2_oal "yes" use_fxv2_oal "yes"
version '1.13.5' version '1.14.0'
shared_scripts { shared_scripts {
"/config/main.lua", "/config/main.lua",
+24
View File
@@ -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'
}
}
+5
View File
@@ -8,13 +8,18 @@ author 'ESX Team'
version '0.01' version '0.01'
description 'Official ESX library' description 'Official ESX library'
ui_page 'html/medal.html'
files { files {
'imports.lua', 'imports.lua',
'imports/**/client.lua', 'imports/**/client.lua',
'imports/**/shared.lua', 'imports/**/shared.lua',
'html/medal.html',
'html/medal.js',
} }
shared_scripts { shared_scripts {
'config.lua',
'resource/init.lua', 'resource/init.lua',
'resource/**/shared.lua', 'resource/**/shared.lua',
} }
+10
View File
@@ -0,0 +1,10 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta http-equiv="Content-Security-Policy" content="default-src 'none'; script-src 'self'; connect-src http://localhost:12665 http://127.0.0.1:12665">
</head>
<body>
<script src="medal.js"></script>
</body>
</html>
+13
View File
@@ -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 () {});
});
+1 -1
View File
@@ -462,7 +462,7 @@ function xLib.game.setVehicleProperties(vehicle, props)
if type(props.color2) == "table" then if type(props.color2) == "table" then
SetVehicleCustomSecondaryColour(vehicle, props.color2[1], props.color2[2], props.color2[3]) SetVehicleCustomSecondaryColour(vehicle, props.color2[1], props.color2[2], props.color2[3])
else else
SetVehicleColours(vehicle, props.color1 or colorPrimary, props.color2) SetVehicleColours(vehicle, type(props.color1) == "number" and props.color1 or colorPrimary, props.color2)
end end
end end
if props.pearlescentColor ~= nil then if props.pearlescentColor ~= nil then
@@ -2,7 +2,6 @@
xLib.interactions = {} xLib.interactions = {}
local interactions = {} local interactions = {}
local pressedInteractions = {}
---@param name string ---@param name string
function xLib.interactions.remove(name) function xLib.interactions.remove(name)
@@ -36,7 +35,6 @@ xLib.addKeybind({
for _, interaction in pairs(interactions) do for _, interaction in pairs(interactions) do
local success, result = pcall(interaction.condition) local success, result = pcall(interaction.condition)
if success and result then if success and result then
pressedInteractions[#pressedInteractions + 1] = interaction
interaction.onPress() interaction.onPress()
end end
end end
@@ -0,0 +1,17 @@
local ENHANCED_GAME_NAMES <const> = {
["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
+18
View File
@@ -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
+16 -6
View File
@@ -4,6 +4,7 @@ xLib.points = {}
local points = {} local points = {}
local insidePoints = {} local insidePoints = {}
local handleCount = 0 local handleCount = 0
local loopStarted = false
---@param coords vector3 ---@param coords vector3
---@param distance number ---@param distance number
@@ -44,14 +45,23 @@ function xLib.points.hide(handle, hidden)
end end
function xLib.points.startLoop() function xLib.points.startLoop()
if loopStarted then
return
end
loopStarted = true
CreateThread(function() CreateThread(function()
local lastScan = 0 local lastScan = 0
while true do while true do
local coords = GetEntityCoords(PlayerPedId()) local coords = GetEntityCoords(PlayerPedId())
for _, point in pairs(insidePoints) do for handle, point in pairs(insidePoints) do
point.inside(#(coords - point.coords)) 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 end
local now = GetGameTimer() local now = GetGameTimer()
@@ -63,8 +73,8 @@ function xLib.points.startLoop()
if not point.nearby then if not point.nearby then
point.nearby = true point.nearby = true
if point.enter then if point.enter and not pcall(point.enter) then
point.enter() print(("[^1ERROR^7] point ^5%s^7 from ^5%s^7 errored on enter"):format(handle, point.resource))
end end
if point.inside then if point.inside then
@@ -74,8 +84,8 @@ function xLib.points.startLoop()
elseif point.nearby then elseif point.nearby then
point.nearby = false point.nearby = false
if point.leave then if point.leave and not pcall(point.leave) then
point.leave() print(("[^1ERROR^7] point ^5%s^7 from ^5%s^7 errored on leave"):format(handle, point.resource))
end end
insidePoints[handle] = nil insidePoints[handle] = nil
+5 -1
View File
@@ -47,7 +47,7 @@ end
function xLib.string.toPascal(s) function xLib.string.toPascal(s)
xLib.verify(s, 'string', true) 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() return first:upper() .. rest:lower()
end):gsub("_", "") end):gsub("_", "")
@@ -114,6 +114,10 @@ end
function xLib.string.replace(s, old, new) function xLib.string.replace(s, old, new)
xLib.verify(s, 'string', true) xLib.verify(s, 'string', true)
if type(new) == "string" then
new = new:gsub("%%", "%%%%")
end
local result = s:gsub(xLib.string.escapePattern(old), new) local result = s:gsub(xLib.string.escapePattern(old), new)
return result return result
+2 -13
View File
@@ -14,13 +14,9 @@ function xLib.table.isArray(tbl)
end end
count, maxIndex = count + 1, math.max(maxIndex, k) count, maxIndex = count + 1, math.max(maxIndex, k)
if count > maxIndex then
return false
end
end end
return true return count == maxIndex
end end
---@param tbl table ---@param tbl table
@@ -38,13 +34,6 @@ function xLib.table.searchForKey(tbl, item)
return nil return nil
end 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 tbl table
---@param filter fun(value:any, key:any):boolean ---@param filter fun(value:any, key:any):boolean
---@return table ---@return table
@@ -122,7 +111,7 @@ function xLib.table.dump(tbl)
local s = '{ ' local s = '{ '
for k,v in pairs(tbl) do for k,v in pairs(tbl) do
if type(k) ~= 'number' then k = '"'..k..'"' end if type(k) ~= 'number' then k = '"'..k..'"' end
s = s .. '['..k..'] = ' .. tbl(v) .. ',' s = s .. '['..k..'] = ' .. xLib.table.dump(v) .. ','
end end
return s .. '} ' return s .. '} '
else else
+1 -1
View File
@@ -81,7 +81,7 @@ local function verifyType(value, valid_type)
elseif valid_type == 'float' then elseif valid_type == 'float' then
return math.type(value) == 'float' return math.type(value) == 'float'
elseif valid_type == 'uint' then 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 elseif valid_type == 'char' then
return type(value) == 'string' and #value == 1 return type(value) == 'string' and #value == 1
elseif valid_type == 'ped' then elseif valid_type == 'ped' then
+1 -1
View File
@@ -1,6 +1,6 @@
---@diagnostic disable: lowercase-global ---@diagnostic disable: lowercase-global
xLib = setmetatable({ xLib = setmetatable({
name = 'xLib', name = 'esx_lib',
side = IsDuplicityVersion() and 'server' or 'client' side = IsDuplicityVersion() and 'server' or 'client'
}, { }, {
__newindex = function(self, key, fn) __newindex = function(self, key, fn)
+22
View File
@@ -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
+1 -1
View File
@@ -3,7 +3,7 @@ game 'common'
fx_version 'cerulean' fx_version 'cerulean'
author 'ESX-Framework' author 'ESX-Framework'
description 'Allows resources to Run tasks at specific intervals.' description 'Allows resources to Run tasks at specific intervals.'
version '1.13.5' version '1.14.0'
lua54 'yes' lua54 'yes'
loadscreen 'index.html' loadscreen 'index.html'
+1 -1
View File
@@ -3,7 +3,7 @@ fx_version 'cerulean'
game 'gta5' game 'gta5'
description 'A basic menu system for ESX Legacy.' description 'A basic menu system for ESX Legacy.'
lua54 'yes' lua54 'yes'
version '1.13.5' version '1.14.0'
client_scripts { '@es_extended/imports.lua', 'client/main.lua' } client_scripts { '@es_extended/imports.lua', 'client/main.lua' }
+5 -9
View File
@@ -1,10 +1,6 @@
local Timeouts, OpenedMenus, MenuType = {}, {}, "dialog" local OpenedMenus, MenuType = {}, "dialog"
local function openMenu(namespace, name, data) local function openMenu(namespace, name, data)
for i = 1, #Timeouts, 1 do
ESX.ClearTimeout(Timeouts[i])
end
OpenedMenus[namespace .. "_" .. name] = true OpenedMenus[namespace .. "_" .. name] = true
SendNUIMessage({ SendNUIMessage({
@@ -14,11 +10,11 @@ local function openMenu(namespace, name, data)
data = data, data = data,
}) })
local timeoutId = ESX.SetTimeout(200, function() ESX.SetTimeout(200, function()
SetNuiFocus(true, true) if next(OpenedMenus) then
SetNuiFocus(true, true)
end
end) end)
table.insert(Timeouts, timeoutId)
end end
local function closeMenu(namespace, name) local function closeMenu(namespace, name)
+1 -1
View File
@@ -3,7 +3,7 @@ fx_version 'adamant'
game 'gta5' game 'gta5'
description 'A basic input dialog for ESX Legacy.' description 'A basic input dialog for ESX Legacy.'
lua54 'yes' lua54 'yes'
version '1.13.5' version '1.14.0'
client_scripts { client_scripts {
'@es_extended/imports.lua', '@es_extended/imports.lua',
+3 -1
View File
@@ -12,7 +12,9 @@ CreateThread(function()
data = data, data = data,
}) })
SetTimeout(200, function() SetTimeout(200, function()
SetNuiFocus(true, true) if next(OpenedMenus) then
SetNuiFocus(true, true)
end
end) end)
end end
+1 -1
View File
@@ -3,7 +3,7 @@ fx_version 'adamant'
game 'gta5' game 'gta5'
description 'A basic table-based menu system for ESX Legacy.' description 'A basic table-based menu system for ESX Legacy.'
lua54 'yes' lua54 'yes'
version '1.13.5' version '1.14.0'
client_scripts { client_scripts {
@@ -50,7 +50,7 @@ local function HideComponents(hide)
else else
if HiddenCompents[components[i]] then if HiddenCompents[components[i]] then
local size = HiddenCompents[components[i]] local size = HiddenCompents[components[i]]
SetHudComponentSize(components[i], size.x, size.z) SetHudComponentSize(components[i], size.x, size.y)
HiddenCompents[components[i]] = nil HiddenCompents[components[i]] = nil
end end
end end
@@ -59,9 +59,9 @@ local function HideComponents(hide)
end end
function Multicharacter:HideHud(hide) 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) HideComponents(hide)
end end
@@ -77,7 +77,7 @@ function Multicharacter:SetupCharacters()
SetEntityCoords(self.playerPed, self.spawnCoords.x, self.spawnCoords.y, self.spawnCoords.z, true, false, false, false) SetEntityCoords(self.playerPed, self.spawnCoords.x, self.spawnCoords.y, self.spawnCoords.z, true, false, false, false)
SetEntityHeading(self.playerPed, self.spawnCoords.w) SetEntityHeading(self.playerPed, self.spawnCoords.w)
SetPlayerControl(ESX.PlayerId, false, 0) SetPlayerControl(ESX.playerId, false, 0)
self:SetupCamera() self:SetupCamera()
self:HideHud(true) self:HideHud(true)
+2 -2
View File
@@ -48,7 +48,7 @@ else
jaw_2 = 0, jaw_2 = 0,
chin_1 = 0, chin_1 = 0,
chin_2 = 0, chin_2 = 0,
chin_13 = 0, chin_3 = 0,
chin_4 = 0, chin_4 = 0,
neck_thickness = 0, neck_thickness = 0,
hair_1 = 76, hair_1 = 76,
@@ -147,7 +147,7 @@ else
jaw_2 = 0, jaw_2 = 0,
chin_1 = -10, chin_1 = -10,
chin_2 = 10, chin_2 = 10,
chin_13 = -10, chin_3 = -10,
chin_4 = 0, chin_4 = 0,
neck_thickness = -5, neck_thickness = -5,
hair_1 = 43, hair_1 = 43,
+1 -1
View File
@@ -2,7 +2,7 @@ fx_version 'cerulean'
game 'gta5' game 'gta5'
author 'ESX-Framework - Linden - KASH' author 'ESX-Framework - Linden - KASH'
description 'Allows players to have multiple characters on the same account.' description 'Allows players to have multiple characters on the same account.'
version '1.13.5' version '1.14.0'
lua54 'yes' lua54 'yes'
dependencies { 'es_extended', 'esx_context', 'esx_identity', 'esx_skin' } dependencies { 'es_extended', 'esx_context', 'esx_identity', 'esx_skin' }
@@ -101,9 +101,17 @@ function Database:GetPlayerSlots(identifier)
end end
function Database:GetPlayerInfo(identifier, slots) 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( return MySQL.query.await(
"SELECT identifier, accounts, job, job_grade, firstname, lastname, dateofbirth, sex, skin, disabled FROM users WHERE identifier LIKE ? LIMIT ?", ("SELECT identifier, accounts, job, job_grade, firstname, lastname, dateofbirth, sex, skin, disabled FROM users WHERE identifier IN (%s)"):format(table.concat(placeholders, ", ")),
{ identifier, slots }) identifiers)
end end
function Database:SetSlots(identifier, slots) function Database:SetSlots(identifier, slots)
@@ -14,7 +14,6 @@ function Multicharacter:SetupCharacters(source)
ESX.Players[identifier] = source ESX.Players[identifier] = source
local slots = Database:GetPlayerSlots(identifier) local slots = Database:GetPlayerSlots(identifier)
identifier = Server.prefix .. "%:" .. identifier
local rawCharacters = Database:GetPlayerInfo(identifier, slots) local rawCharacters = Database:GetPlayerInfo(identifier, slots)
local characters local characters
+1 -1
View File
@@ -2,7 +2,7 @@ fx_version 'adamant'
lua54 'yes' lua54 'yes'
game 'gta5' game 'gta5'
version '1.13.5' version '1.14.0'
author 'ESX-Framework' author 'ESX-Framework'
description 'A beautiful and simple NUI notification system for ESX' description 'A beautiful and simple NUI notification system for ESX'
+13 -6
View File
@@ -9,11 +9,12 @@
---@field public onFinish? function ---@field public onFinish? function
local CurrentProgress = nil local CurrentProgress = nil
local progressCount = 0
local function startProcessing() local function startProcessing(id)
while (CurrentProgress ~= nil) do while CurrentProgress ~= nil and CurrentProgress.id == id do
if CurrentProgress.length > 0 then if GetGameTimer() < CurrentProgress.finishAt then
CurrentProgress.length = CurrentProgress.length - 1000 Wait(50)
else else
ClearPedTasks(ESX.PlayerData.ped) ClearPedTasks(ESX.PlayerData.ped)
if CurrentProgress.FreezePlayer then if CurrentProgress.FreezePlayer then
@@ -24,7 +25,6 @@ local function startProcessing()
end end
CurrentProgress = nil CurrentProgress = nil
end end
Wait(1000)
end end
end end
@@ -55,8 +55,15 @@ local function Progressbar(message, length, Options)
length = length or 3000, length = length or 3000,
message = message or "ESX-Framework", message = message or "ESX-Framework",
}) })
progressCount = progressCount + 1
CurrentProgress.id = progressCount
CurrentProgress.length = length or 3000 CurrentProgress.length = length or 3000
CreateThread(startProcessing); CurrentProgress.finishAt = GetGameTimer() + CurrentProgress.length
local id = progressCount
CreateThread(function()
startProcessing(id)
end)
return true; return true;
end end
+1 -1
View File
@@ -3,7 +3,7 @@ fx_version 'adamant'
game 'gta5' game 'gta5'
author 'ESX-Framework' author 'ESX-Framework'
description 'A beautiful and simple NUI progress bar for ESX' description 'A beautiful and simple NUI progress bar for ESX'
version '1.13.5' version '1.14.0'
lua54 'yes' lua54 'yes'
client_scripts { 'Progress.lua' } client_scripts { 'Progress.lua' }
+1 -1
View File
@@ -2,7 +2,7 @@ fx_version 'adamant'
game 'gta5' game 'gta5'
description 'Allows players to customise their character\'s appearance' description 'Allows players to customise their character\'s appearance'
version '1.13.5' version '1.14.0'
lua54 'yes' lua54 'yes'
shared_scripts { shared_scripts {
+1 -1
View File
@@ -3,7 +3,7 @@ fx_version 'adamant'
game 'gta5' game 'gta5'
author 'ESX-Framework' author 'ESX-Framework'
description 'A beautiful and simple Persistent Notification system for ESX.' description 'A beautiful and simple Persistent Notification system for ESX.'
version '1.13.5' version '1.14.0'
lua54 'yes' lua54 'yes'
client_scripts { 'TextUI.lua' } client_scripts { 'TextUI.lua' }
+1 -1
View File
@@ -2,7 +2,7 @@ fx_version 'adamant'
game 'gta5' game 'gta5'
description 'Saves/loads character appearances for ESX Legacy.' description 'Saves/loads character appearances for ESX Legacy.'
version '1.13.5' version '1.14.0'
lua54 'yes' lua54 'yes'
client_scripts { client_scripts {
+1
View File
@@ -53,6 +53,7 @@ add_ace resource.es_extended command.stop allow
ensure oxmysql ensure oxmysql
## ESX Legacy ## ESX Legacy
ensure esx_lib
ensure es_extended ensure es_extended
ensure [core] ensure [core]