1243 Commits

Author SHA1 Message Date
_Not_ 2f7bada4ab Merge pull request #1864 from esx-framework/v1.14.1
Bump legacy version to 1.14.1
2026-08-16 07:58:08 -05:00
_Not_ b7efa23ebb Bump version from 1.14.0 to 1.14.1 2026-08-15 14:37:43 -05:00
_Not_ a1e9d144d2 change: rename folder from 1.14.2 to 1.14.1 2026-08-15 14:01:11 -05:00
_Not_ caf4ed72e0 Merge branch 'v1.14.1' into ox-inv 2026-08-15 11:00:37 -05:00
Ihsan 5bcdf4a6fb refactor(ox_inventory): update account list parsing and enhance export validation 2026-08-15 21:01:30 +05:30
Ihsan e1164b487b refactor(ox_inventory): enhance account list parsing and improve proxy method validation 2026-08-15 12:47:06 +05:30
Ihsan e08eb5079d refactor(ox_inventory): validate module methods and fix hasItem return contract 2026-08-15 12:47:05 +05:30
Ihsan d0bcb3674b refactor(oxinventory): improve error handling and streamline inventory functions 2026-08-15 12:47:05 +05:30
_Not_ 85fc241659 Merge pull request #1819 from seltonmt012/fix/additems-db-insert
fix(es_extended/server/functions): persist items added via AddItems
2026-08-14 20:20:26 -05:00
_Not_ 9c0652b292 Merge pull request #1820 from seltonmt012/fix/command-merge-argument
fix(es_extended/server/functions): drop leading space from merge command arguments
2026-08-14 20:20:10 -05:00
_Not_ 1be57a0648 Merge pull request #1821 from seltonmt012/fix/default-skin-sex
fix(es_extended/server/main): use the identity sex for the default skin
2026-08-14 20:19:33 -05:00
_Not_ ef14a011a1 Merge pull request #1822 from seltonmt012/fix/loadout-unknown-weapon
fix(es_extended/server/main): skip unknown weapons when loading a loadout
2026-08-14 20:18:12 -05:00
_Not_ a87d260c6f Merge pull request #1823 from seltonmt012/fix/callback-timeout
fix(es_extended/modules/callback): compare the promise state against its numeric value
2026-08-14 20:17:02 -05:00
_Not_ f41fe2b961 Merge pull request #1824 from seltonmt012/fix/give-weapon-order
fix(es_extended/server/main): add the weapon before restoring its tint and components
2026-08-14 20:15:31 -05:00
_Not_ 53ee758fd8 Merge pull request #1827 from seltonmt012/fix/locale-string-invalid-utf8
fix(es_extended/shared): return false instead of raising on invalid utf8
2026-08-14 19:47:23 -05:00
_Not_ 3263c19cb3 Merge pull request #1832 from seltonmt012/perf/pickup-closest-player
perf(es_extended/client): only look up the closest player when the pickup prompt is used
2026-08-14 19:44:56 -05:00
_Not_ f6485c19cc Merge pull request #1834 from seltonmt012/fix/saveplayers-spawned-guard
fix(es_extended/server): skip unspawned players in the bulk save
2026-08-14 19:43:34 -05:00
_Not_ d7f6a6f5e7 Merge pull request #1835 from seltonmt012/perf/multichar-delete-indexes
perf(esx_multicharacter): index the columns used by character deletion
2026-08-14 19:43:12 -05:00
_Not_ 0a73be82c4 Merge pull request #1838 from seltonmt012/fix/translatecap-return
fix(es_extended/locale): return a single value from TranslateCap
2026-08-14 19:41:52 -05:00
_Not_ cad375c63d Merge pull request #1844 from seltonmt012/fix/adjustments-relog-leak
fix(es_extended/client): stop stacking adjustment threads on every relog
2026-08-14 19:39:13 -05:00
_Not_ 76b10dd5f6 Merge pull request #1847 from seltonmt012/fix/setplate-stale-handle
fix(es_extended/server/classes/vehicle): keep xVehicle usable after setPlate
2026-08-14 19:36:17 -05:00
Selt 06ac429353 fix(es_extended/server/modules/createJob): store the encoded skin in ESX.Jobs
CreateJob wrote the caller's skin table straight into ESX.Jobs while the row
got the encoded string. setJob then ran json.decode over a table and died with
"bad argument #1 to 'json.decode' (string expected, got table)". A restart
appeared to repair the job, because the grade came back from the database as a
string. Cache the same value that goes into the row.
2026-08-08 20:25:15 +02:00
Selt a5623a2039 fix(es_extended/server/modules/createJob): cache only the grades that were inserted
A grade that already exists in the database is skipped for the insert, but it
was still written to ESX.Jobs. A second CreateJob call therefore replaced the
stored name, label and salary in memory while the row itself kept the old
values, and the two only agreed again after a restart. CreateJob now caches
just the grades it inserted.

Label and type of an existing job come from the database for the same reason.
The grade lookup compares strings, so passing grade "1" for an existing grade 1
no longer inserts a second row.
2026-08-08 20:25:15 +02:00
Selt 4b07899061 fix(es_extended/server/modules/createJob): only insert what is missing
The jobs row was inserted unconditionally, so adding a grade to an existing
job hit a duplicate primary key and rolled the whole transaction back. The
guard above it only caught the case where there was nothing to add, and it
read ESX.Jobs, which does not list jobs that have no grades yet.
2026-08-08 20:25:14 +02:00
_Not_ 9983c7c2a0 Merge pull request #1852 from seltonmt012/fix/console-commands-nil-xplayer
fix(es_extended/server/modules/commands): stop console commands from throwing on a missing xPlayer
2026-08-07 22:12:09 -05:00
_Not_ c4df5faa46 Merge pull request #1853 from seltonmt012/fix/discord-presence-placeholders
fix(es_extended/client/modules/adjustments): escape placeholder values before substitution
2026-08-07 22:07:09 -05:00
_Not_ 110b30a2ca Merge pull request #1855 from seltonmt012/fix/setinventoryitem-noop
fix(es_extended/server/classes/player): stop setInventoryItem throwing when nothing changes
2026-08-07 21:54:30 -05:00
_Not_ b5cbfb9841 Merge pull request #1856 from seltonmt012/fix/billing-target-length
fix(es_extended): widen the identifier columns that are too short
2026-08-07 21:52:38 -05:00
_Not_ 08d83d238d Merge pull request #1859 from rwixy/centralize-admin
feat(commands): centralize admin permissions via Config.AdminGroups
2026-08-07 15:29:24 -05:00
Ihsan 4a22b68511 refactor(commands): simplify group filtering and update deprecation notice for AdminGroups 2026-08-08 00:16:03 +05:30
_Not_ 519d9fe6b2 Merge pull request #1862 from rwixy/ox-inv
refactor(oxinventory): improve inventory module initialization and cleanup
2026-08-06 13:27:35 -05:00
Ihsan 1a430fe604 refactor(ox_inventory): validate module methods and fix hasItem return contract 2026-08-06 23:43:48 +05:30
Ihsan 87003d41aa refactor(oxinventory): improve error handling and streamline inventory functions 2026-08-06 21:17:52 +05:30
Ihsan e85b05558c feat: per-command group permissions for admin commands 2026-08-05 13:54:44 +05:30
_Not_ cedac84504 Merge pull request #1811 from ASTROWwwW/feat/edition-detection
feat(esx_lib): add isEnhanced game edition detection
2026-08-04 18:26:44 -05:00
Ihsan 9128ad5857 feat(commands): centralize admin permissions via Config.AdminGroups 2026-08-01 20:18:23 +05:30
Selt ad195a6516 fix(es_extended): widen rented_vehicles.owner to hold an identifier
The column is varchar(22), which is a leftover from Steam identifiers.
esx_vehicleshop writes a player identifier into it, and on a
multicharacter server those are 46 characters long, so renting a vehicle
out fails.

The stock vehicle is deleted from cardealer_vehicles before the insert
runs, so the dealer loses the car and the customer never gets a rental
contract. Neither side is told anything, because the handler dies on the
insert.

esx_vehicleshop's own esx_vehicleshop.sql already declares varchar(60).
2026-07-30 01:22:37 +02:00
Selt 6c50d22615 fix(es_extended): widen billing.target to hold an identifier
The column is varchar(40) but esx_billing writes an identifier into it, and
those are 46 characters once multicharacter is on, so the insert fails with
1406 and the handler dies before notifying either player. The other two
identifier columns in the same table are already varchar(60).
2026-07-28 03:06:20 +02:00
Selt e6b6d1f40a fix(es_extended/server/classes/player): make setInventoryItem a no-op on no change
Setting an item to the count it already has sent a zero difference to
removeInventoryItem, which treats zero as an error and calls error(). That
unwinds out of the caller, so anything after the call was skipped.
2026-07-28 01:54:04 +02:00
Selt e60674cb56 fix(es_extended/client/modules/adjustments): escape placeholder values
Placeholder results go straight into gsub as the replacement string, so a %
in a player name either throws and kills the presence thread or silently
mangles the output. The "Unknown" fallback above it was unreachable because
the error() on the previous line always propagates.
2026-07-28 01:46:58 +02:00
Selt 65d6d32c6e fix(es_extended/server/modules/commands): survive console use and fix notifications
RegisterCommand hands the callback xPlayer or false, so group, refreshitems
and fix threw from the server console instead of doing their work. The same
three lines still used the old showNotification argument list, which put a
number into title and made esx_notify throw, so players got no confirmation
either.
2026-07-28 01:16:36 +02:00
Selt 0bf541905c fix(es_extended/server/classes/vehicle): update self.plate in setPlate
setPlate re-keys Core.vehicles but never updates self.plate, so the object
it was called on keeps pointing at the removed key. isValid() fails from
then on and every later call on that handle is a no-op, including delete(),
which leaves the vehicle spawned and owned_vehicles.stored at false.
2026-07-28 00:13:58 +02:00
Selt d41b0f1feb fix(es_extended/client): stop stacking adjustment threads on every relog
Adjustments:Load() runs from the esx:playerLoaded handler, so it runs again on
every character switch. AmmoAndVehicleRewards, Multipliers and DiscordPresence
each start a `while true` thread that never exits, and SeatShuffle and
DisableRadio register another esx:enteredVehicle handler. Nothing tears any of it
down, so a player who switches characters a few times ends up with several
per-frame threads all writing the same values, and client performance degrades
until they reconnect.

The loops now run `while ESX.PlayerLoaded`, the same way StartServerSyncLoops and
Actions:SlowLoop already do, so they end on logout and a fresh one starts on the
next load. The two event handlers are registered once. Actions:Init already
documents this exact concern in a comment; Adjustments never got the same
treatment.

Measured in game on artifact 25770 by counting thread ticks per frame:
  before: 2.01 -> 3.01 -> 4.02 across two relogs
  after:  1.00 -> 1.00 -> 1.00
2026-07-27 22:36:56 +02:00
Selt 19272bc3e8 fix(es_extended/locale): return a single value from TranslateCap
TranslateCap returned the result of gsub directly, and gsub returns the string
plus the number of substitutions. Whenever TranslateCap is the last argument of
a call, that count is passed along as an extra argument.

The common case is showNotification(msg, notifyType, ...), which is called with
just the message in about 49 places in the core. Those all pass notifyType = 1
instead of nil, so the notification is rendered with a numeric type rather than
the intended default. 71 call sites in total have TranslateCap in trailing
position.

Wrapping the call in parentheses truncates it to one value. The string itself is
unchanged, and nothing reads the second value.
2026-07-27 21:14:26 +02:00
Selt d3aaab0692 perf(esx_multicharacter): index the columns used by character deletion
Deleting a character runs one DELETE per table that has an identifier/owner
column, all in a single transaction. Only users.identifier is indexed; the other
core tables (billing, owned_vehicles, user_licenses, society_moneywash,
addon_account_data, addon_inventory_items, datastore_data) have no usable index on
the delete column, so each DELETE is a full table scan and they all hold locks at
once.

Measured on 200k rows per table (MariaDB 12.3.2): the transaction went from about
1573 ms to about 22 ms once the seven columns are indexed, EXPLAIN going from
type=ALL scanning ~200k rows to a single-row index lookup per table. The addon and
datastore tables were the worst because their composite indexes do not lead with
the delete column, so they cannot serve WHERE owner = ?.

Adds the indexes to legacy.sql for fresh installs and a migration for existing
databases. The migration checks INFORMATION_SCHEMA for an index that leads with the
column (SEQ_IN_INDEX = 1) and only creates one where it is missing, so it is a no-op
on databases that already have it and re-running is guarded by the migration key.
2026-07-27 19:21:31 +02:00
Selt 12f92e33a4 fix(es_extended/server): skip unspawned players in the bulk save
Core.SavePlayer already refuses to save a player whose spawned flag is not set,
because an unspawned player has no server-side ped and getCoords/health return
0,0,0 / 0. Core.SavePlayers, the bulk autosave that runs every few minutes and on
restart/shutdown, had no such guard and saved every player in ESX.Players.

A player still in the load or character-selection window when the autosave fires
gets their row overwritten with position 0,0,0 and health 0. On the next load the
default-spawn fallback does not kick in, since a decoded {"x":0.0,...} is truthy,
so they spawn at the world origin, and with SaveDeathStatus on, dead.

Guard the loop with the same spawned check, and skip the query entirely when no
spawned player remains.

Tested on artifact 25770: forced a connected player to spawned=false and ran
saveall. Before, the player's row was overwritten (the save ran); after, the row
was left untouched and no save was issued.
2026-07-27 19:14:25 +02:00
Selt 4fc2d72941 perf(es_extended/client): only look up the closest player when the pickup prompt is used
The pickup loop calls ESX.Game.GetClosestPlayer at the top of every iteration, but
closestDistance is only read inside the nested check that runs when the player presses
the pickup control right next to a drop. As soon as any pickup is within five metres
the loop sets Sleep to 0, so the lookup runs every frame while standing near a drop.

GetClosestPlayer builds two tables and calls GetPlayers plus GetPlayerPed,
DoesEntityExist and GetEntityCoords per player, so the cost grows with the number of
players in scope - worst exactly when the server is busy.

Measured in game on artifact 25770 with a single player standing on a dropped pickup,
reading es_extended in the resource monitor over about 20 seconds each:

  before: 0.16 - 0.18 ms, sitting around 0.17
  after:  0.13 - 0.15 ms, sitting around 0.14

The two ranges do not overlap. Idle, with no pickup within five metres, is 0.04 ms in
both cases, as expected, since the loop sleeps 1500 ms then.

One player is the smallest saving this can ever show, because GetClosestPlayer scales
with the number of players in scope. The larger saving on a populated server follows
from that per player work and was not measured, testing it would need several clients.

Behaviour is unchanged, the value is computed in the same frame it is used.
2026-07-27 17:33:11 +02:00
zykem 5354e278de Merge pull request #1786 from rock1565/main
fix SQL parameter passing in setProps to save complex modifications
2026-07-27 16:31:59 +02:00
Selt d264553490 fix(es_extended/shared): return false instead of raising on invalid utf8
ESX.IsValidLocaleString only checks that the argument is a string, but a Lua
string is a byte array and need not be valid utf8. utf8.codes raises on a
malformed sequence rather than stopping, so the function throws where its
annotation promises a boolean.

esx_identity feeds raw client input into it through checkNameFormat, so a
modified client sending a lone 0xFF byte as a first name aborts the
registerIdentity callback before cb() runs. The client is left waiting on a
promise that never resolves and cannot get past the identity screen, and it
can be repeated at will.

utf8.len returns nil instead of raising, so it is enough to reject the string
up front.

Verified on artifact 25770: "Jo\255hn" used to raise "invalid UTF-8 code" and
now returns false. "John" still returns true, "Jo!hn" still returns false, and
"Jo3hn" with allowDigits still returns true.
2026-07-27 16:17:28 +02:00
Selt eaf0db6c5e fix(es_extended/modules/callback): compare the promise state against its numeric value
promise.state is a number, not a string - the runtime defines PENDING as 0 and
Citizen.Await tests against 0 as well. Comparing it to "pending" is therefore
always false and the documented 15 second timeout never rejects anything. When
the other side never answers, Citizen.Await blocks the calling coroutine for
good.

Measured on artifact 25770 with the real 15 second delay:
  never answers, current guard -> state 0, not rejected, await never returns
  never answers, fixed guard   -> state 0, rejected, await returns the timeout
  answers after 1s, fixed guard-> state 3, untouched, await returns normally

Nothing inside esx_core calls AwaitServerCallback or AwaitClientCallback, so
no core behaviour changes.
2026-07-27 16:17:27 +02:00