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_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
--
+1 -1
View File
@@ -4,6 +4,6 @@ game 'gta5'
author 'ESX-Framework'
description 'Allows resources to Run tasks at specific intervals.'
lua54 'yes'
version '1.13.5'
version '1.14.0'
server_script 'server/main.lua'
+18 -7
View File
@@ -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
@@ -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)
@@ -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)
+2 -1
View File
@@ -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
+1 -1
View File
@@ -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.13.5'
version '1.14.0'
shared_scripts {
'@esx_lib/imports.lua',
+1 -1
View File
@@ -33,7 +33,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
@@ -15,57 +15,84 @@ local requiredMethods = {
}
---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>
local function getAccountList()
local accounts = {}
local convar = GetConvar("inventory:accounts", '["money"]')
local ok, list = pcall(json.decode, convar)
if ok and type(list) == "table" then
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
accounts[list[i]] = true
end
else
-- Fallback for non-JSON formats (e.g. "money" or "money black_money")
for account in convar:gmatch("[%w_]+") do
if account ~= "[]" then
local account = list[i]
if type(account) == "string" and account ~= "" then
accounts[account] = true
end
end
end
return accounts
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
local function createOxInventoryProxy()
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
local method = requiredMethods[i]
local ok, err = pcall(function()
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
local exportFn = resolveExport(resourceExports, method)
proxy[method] = function(...)
return exports.ox_inventory[method](exports.ox_inventory, ...)
return exportFn(resourceExports, ...)
end
end
proxy.accounts = accounts
proxy.accounts = getAccountList()
return proxy
end
---Checks if a module table contains all required methods.
---Checks if an Inventory() module provides everything ESX requires.
---@param module table
---@return boolean
local function isValidModule(module)
@@ -79,12 +106,15 @@ local function isValidModule(module)
end
end
if type(module.accounts) ~= "table" then
return false
end
return true
end
---Returns the ox_inventory interface.
---Tries the legacy Inventory() export first for backward compatibility,
---then falls back to direct export calls.
---Uses Inventory() when available, otherwise falls back to public exports.
---@return table
local function getOxInventory()
if OxInventory then
@@ -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
@@ -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
+14 -2
View File
@@ -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,6 +251,7 @@ function Core.SavePlayers(cb)
local parameters = {}
for _, xPlayer in pairs(ESX.Players) do
if xPlayer.spawned then
updateHealthAndArmorInMetadata(xPlayer)
parameters[#parameters + 1] = {
json.encode(xPlayer.getAccounts(true)),
@@ -264,6 +265,11 @@ function Core.SavePlayers(cb)
xPlayer.identifier,
}
end
end
if not parameters[1] then
return cb and cb()
end
MySQL.prepare(
"UPDATE `users` SET `accounts` = ?, `job` = ?, `job_grade` = ?, `group` = ?, `position` = ?, `inventory` = ?, `loadout` = ?, `metadata` = ? WHERE `identifier` = ?",
@@ -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]
+8 -7
View File
@@ -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))
@@ -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
SetTimeout(15000, function()
if p.state == "pending" then
if p.state == 0 then
p:reject("Server Callback Timed Out")
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(
{ "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
+35 -24
View File
@@ -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,23 +52,48 @@ 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
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, grade.skin_male and json.encode(grade.skin_male) or '{}', grade.skin_female and json.encode(grade.skin_female) or '{}' }
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)
+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}
}
---@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
+4
View File
@@ -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 ={
+1 -1
View File
@@ -1,4 +1,4 @@
version '1.13.5'
version '1.14.0'
author 'ESX-Framework'
description 'A ESX Stylised theme for the chat resource.'
+1 -1
View File
@@ -4,7 +4,7 @@ game 'gta5'
author 'ESX-Framework & Brayden'
description 'A simplistic context menu for ESX.'
lua54 'yes'
version '1.13.5'
version '1.14.0'
ui_page 'index.html'
+1 -1
View File
@@ -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.13.5'
version '1.14.0'
shared_scripts {
'@es_extended/imports.lua',
+4 -6
View File
@@ -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)
+1 -1
View File
@@ -4,7 +4,7 @@ game "gta5"
description "Inventory for the ESX framework"
lua54 "yes"
use_fxv2_oal "yes"
version '1.13.5'
version '1.14.0'
shared_scripts {
"/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'
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',
}
+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
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
@@ -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
@@ -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 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
+5 -1
View File
@@ -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
+2 -13
View File
@@ -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
+1 -1
View File
@@ -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
+1 -1
View File
@@ -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)
+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'
author 'ESX-Framework'
description 'Allows resources to Run tasks at specific intervals.'
version '1.13.5'
version '1.14.0'
lua54 'yes'
loadscreen 'index.html'
+1 -1
View File
@@ -3,7 +3,7 @@ fx_version 'cerulean'
game 'gta5'
description 'A basic menu system for ESX Legacy.'
lua54 'yes'
version '1.13.5'
version '1.14.0'
client_scripts { '@es_extended/imports.lua', 'client/main.lua' }
+4 -8
View File
@@ -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()
ESX.SetTimeout(200, function()
if next(OpenedMenus) then
SetNuiFocus(true, true)
end
end)
table.insert(Timeouts, timeoutId)
end
local function closeMenu(namespace, name)
+1 -1
View File
@@ -3,7 +3,7 @@ fx_version 'adamant'
game 'gta5'
description 'A basic input dialog for ESX Legacy.'
lua54 'yes'
version '1.13.5'
version '1.14.0'
client_scripts {
'@es_extended/imports.lua',
+2
View File
@@ -12,7 +12,9 @@ CreateThread(function()
data = data,
})
SetTimeout(200, function()
if next(OpenedMenus) then
SetNuiFocus(true, true)
end
end)
end
+1 -1
View File
@@ -3,7 +3,7 @@ fx_version 'adamant'
game 'gta5'
description 'A basic table-based menu system for ESX Legacy.'
lua54 'yes'
version '1.13.5'
version '1.14.0'
client_scripts {
@@ -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)
+2 -2
View File
@@ -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,
+1 -1
View File
@@ -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.13.5'
version '1.14.0'
lua54 'yes'
dependencies { 'es_extended', 'esx_context', 'esx_identity', 'esx_skin' }
@@ -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)
@@ -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
+1 -1
View File
@@ -2,7 +2,7 @@ fx_version 'adamant'
lua54 'yes'
game 'gta5'
version '1.13.5'
version '1.14.0'
author 'ESX-Framework'
description 'A beautiful and simple NUI notification system for ESX'
+13 -6
View File
@@ -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
+1 -1
View File
@@ -3,7 +3,7 @@ fx_version 'adamant'
game 'gta5'
author 'ESX-Framework'
description 'A beautiful and simple NUI progress bar for ESX'
version '1.13.5'
version '1.14.0'
lua54 'yes'
client_scripts { 'Progress.lua' }
+1 -1
View File
@@ -2,7 +2,7 @@ fx_version 'adamant'
game 'gta5'
description 'Allows players to customise their character\'s appearance'
version '1.13.5'
version '1.14.0'
lua54 'yes'
shared_scripts {
+1 -1
View File
@@ -3,7 +3,7 @@ fx_version 'adamant'
game 'gta5'
author 'ESX-Framework'
description 'A beautiful and simple Persistent Notification system for ESX.'
version '1.13.5'
version '1.14.0'
lua54 'yes'
client_scripts { 'TextUI.lua' }
+1 -1
View File
@@ -2,7 +2,7 @@ fx_version 'adamant'
game 'gta5'
description 'Saves/loads character appearances for ESX Legacy.'
version '1.13.5'
version '1.14.0'
lua54 'yes'
client_scripts {
+1
View File
@@ -53,6 +53,7 @@ add_ace resource.es_extended command.stop allow
ensure oxmysql
## ESX Legacy
ensure esx_lib
ensure es_extended
ensure [core]