1723 Commits

Author SHA1 Message Date
_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
_Not_ 85936f0c4d Merge pull request #1803 from rwixy/medal-lib
feat: integrate Medal.tv globally
2026-08-04 18:21:33 -05:00
Ihsan 9128ad5857 feat(commands): centralize admin permissions via Config.AdminGroups 2026-08-01 20:18:23 +05:30
Selt f5ed52d03b fix(esx_menu_dialog): drop the timeout id bookkeeping
With the callback checking whether a menu is still open, cancelling the
previous timers by id is no longer needed.

The table it kept them in was never emptied, so every open iterated over
every id ever created and called ClearTimeout on all of them. That is
quadratic, and clearTimeout only marks an id in xLib's CancelledTimeouts,
which is cleared when the timer fires. Ids that had already fired stayed
in there for good, in a table shared by every resource.
2026-07-30 02:38:25 +02:00
Selt 760f31eb6c fix(esx_multicharacter): correct the chin_3 typo in the default skins
Both default skins spell the chin width key chin_13. skinchanger only
knows chin_1 through chin_4, so the value never reaches the ped and the
key is stored as junk in users.skin.

It also breaks the ped. SetFace reads every feature through
Normalise(weight, 10), which divides, so a missing chin_3 raises
"attempt to perform arithmetic on a nil value" at feature index 17.
Everything after that in ApplySkin is skipped: the remaining features,
the eye colour, the head overlays, the components and the props.
2026-07-30 01:50:20 +02:00
Selt a890abd110 fix(esx_lib): make table.dump work and drop the shadowed contains
table.dump called its own table argument as a function, so it threw
"attempt to call a table value" for every table that had at least one
entry. An empty table returned "{ } " and a non-table returned tostring,
which is why it looks fine until you actually dump something.

table.contains was defined twice in the same file. Lua keeps the second
one, so the annotated version further up was unreachable. Removed the
unreachable one; behaviour is unchanged.
2026-07-30 01:45:05 +02:00
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 bac379118d fix(esx_lib): match uint against integer and drop the unread interaction list
math.type only returns 'integer', 'float' or nil, so the 'uint' branch
comparing against 'int' never matched and threw whenever throw_error was set.
pressedInteractions was written on every interaction and read nowhere, so it
grew for the whole session and kept removed interactions reachable.
2026-07-28 00:53:00 +02:00
Selt 1b5e29c62b fix(cron): build the scheduled time from the day the job belongs to
OnTime always built scheduledTimestamp from the date of the current tick, so
a tick that skipped the 23:59 minute compared it against 23:59 of the next
day. The run was dropped without a trace. Fall back to the previous day when
the scheduled time lands in the future.
2026-07-28 00:44:30 +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 eb58d4674b fix(esx_lib/game): keep the secondary colour when the primary is custom
setVehicleProperties passes props.color1 straight into SetVehicleColours when the
secondary colour comes from the palette. If the primary is a custom RGB colour,
the getter stored it as a table, so a table reaches a native that wants a paint
index and the call does nothing. The secondary colour is silently lost.

Any car sprayed with a custom primary and a palette secondary comes back from
storage with the wrong secondary colour.

Fall back to the vehicle's current primary when props.color1 is not a paint index.
The custom primary was already applied by the block above, so nothing is lost.

Measured in game on artifact 25770, reading properties off a car with a custom red
primary and palette secondary 12, then applying them to a second car:
  before: color2 came back 0
  after:  color2 came back 12
The custom primary survives either way, mods and wheels are untouched.
2026-07-27 23:04:19 +02:00
Selt 443953b24b fix(esx_lib/points): start the points loop only once
startLoop is called from the esx:playerLoaded handler in es_extended, which fires
again on every character switch, and the loop it creates runs `while true` with no
exit. Each relog therefore leaves another points thread running: they all scan the
same table, fire the same enter and leave callbacks, and none of them ever stop.

A guard makes a second call a no-op. The loop is meant to live for the resource
lifetime, so starting it twice is never right.

Measured in game on artifact 25770, counting scans over four seconds:
  before: 1.0 -> 2.0 -> 3.0 loops across two relogs
  after:  1.0 -> 1.0 -> 1.0
Points keep working, the surviving loop is unaffected.

Note this touches the same file as #1831, which guards the callbacks inside the
loop. The two changes are in different places and independent of each other.
2026-07-27 22:50:24 +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 07b9253401 fix(esx_progressbar): honour the requested duration and ignore cancelled runs
The processing thread counted the remaining time down in 1000 ms steps and read
the global CurrentProgress on every wake, which caused two problems.

Any duration that is not a whole number of seconds finished late, since the
thread only checked after another full second. The NUI bar runs on wall clock
time, so the bar completed and the player then stood frozen waiting for onFinish.

Cancelling did not stop the thread either. CancelProgressbar clears
CurrentProgress, but the sleeping thread only notices up to a second later, and if
a new bar was started in that window it saw a non-nil CurrentProgress and kept
decrementing the new one alongside its own thread. The new bar then finished at
roughly half its length, firing onFinish early.

The run now carries an id, so a thread stops as soon as its run is no longer the
current one, and the end is an absolute timestamp rather than a countdown.

Measured in game on artifact 25770, requested versus actual onFinish:
  before: 500 -> 1011, 4200 -> 5042, cancel then 3000 -> 1427
  after:  500 ->  519, 4200 -> 4222, cancel then 3000 -> 3058
2026-07-27 21:51:15 +02:00
Selt e801856d1a fix(esx_menu_dialog, esx_menu_list): do not grab NUI focus after the menu closed
Opening a menu schedules SetNuiFocus(true, true) 200 ms later so the NUI has time
to render. Closing sets the focus off but does not cancel that timer, so a menu
closed within those 200 ms leaves the timer to fire afterwards and turn the focus
back on with nothing open: the player gets a cursor and loses movement and input
until another menu is opened and closed. esx_menu_list never stored the timer id
at all, so it could not be cancelled in any case.

Checking inside the callback whether a menu is still open fixes both, and also
covers a quick open-close-open where cancelling by id would drop a focus that is
still wanted. esx_menu_default is unaffected, it has no timer.

Tested on artifact 25770 with a dialog closed 50 ms after opening: before,
IsNuiFocused() was still true 600 ms later; after, it stays false.
2026-07-27 21:18:26 +02:00
Selt f98a6e2245 fix(cron): keep the scheduler alive when a job errors
Registered jobs are third party callbacks and were invoked bare. An error inside
one propagates out of OnTime into Tick, so Tick never reaches its
SetTimeout(60000, Tick) at the end and nothing ever reschedules it. From that
moment cron is dead for every resource on the server - paychecks, cleanups,
anything registered through cron:runAt - until a restart. The operator sees one
stack trace and then silence.

Wrapping the callback keeps the loop and the reschedule intact, and names the job
that failed.

Simulated the scheduler chain in standalone Lua with three jobs where the first
raises: before, the tick died before SetTimeout and the two later jobs never ran
again; after, the failure is reported and both later jobs fire.
2026-07-27 21:17:12 +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 6cbc408102 fix(esx_lib): correct isArray, toPascal and replace helpers
Three small logic bugs in the shared helpers, each verified with a standalone Lua
run.

isArray returned true for sparse tables like {[1]=1,[3]=3}: the in-loop
`count > maxIndex` check can never fire (with positive integer keys count is always
<= maxIndex), so it was dead code. Dropped it and return `count == maxIndex`, which
is false for gaps and still true for a dense sequence or an empty table.

toPascal lowercased the letters after an underscore: `[%w_]*` swallowed the
underscore and the next word into the part that gets lowercased, so "hello_world"
became "Helloworld". Excluding the underscore from that class leaves each word to be
capitalised, then the trailing gsub removes the underscores -> "HelloWorld".

replace escaped the pattern but not the replacement, so a "%" in the replacement
raised "invalid use of '%' in replacement string" and "%1" style sequences expanded
captures. Escape "%" in a string replacement; a function or table replacement is
left untouched.
2026-07-27 20:22:11 +02:00
Selt 0fb74a74ef fix(esx_inventory): let the quantity dialog accept a corrected amount
The quantity dialog resolved its promise with nil on an invalid amount, so the
give/remove had already aborted by the time the player typed a valid value. The
dialog stayed open and a corrected amount did nothing.

Dropping the early resolve leaves the promise pending, so the same open dialog
accepts the retry and drops/gives the item. Cancel still resolves nil.

Tested on artifact 25770: entered an over-count amount, got "invalid amount",
corrected to a valid amount. Before, nothing happened; after, the item was
dropped.
2026-07-27 20:12:50 +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 e9d5cc9f8d perf(esx_multicharacter): look up characters by exact identifier instead of LIKE
The character selection query used `WHERE identifier LIKE 'char%:<license>'`. Every
multicharacter identifier starts with `char`, so the primary key range covers the whole
table and it degrades to a full scan on every join.

Build the exact identifiers (`char1:<license>` .. `charN:<license>` for the player's
slots) and query with `IN`, which hits the primary key directly.

Measured on a 50k row users table (MariaDB 12.3.2): rows examined 45648 -> 3, query
time 52.7 ms -> 3.2 ms. In-game the selection screen shows the same characters in the
same slots.
2026-07-27 18:34:10 +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
Selt 9a248406b0 fix(esx_lib/points): keep the points loop alive when a callback errors
Every point from every resource is served by one shared thread, and the enter, leave
and inside callbacks are all invoked bare. A Lua error in any one of them terminates
that coroutine, and nothing restarts it - startLoop only runs once, from the spawn
handler in es_extended.

So a single faulty third party resource takes down point detection for the whole
client. Shops, garages and job markers stop reacting, with no hint beyond one stack
trace, until the player reconnects.

inside is the worst of the three because it runs every frame while the player stands
in the point, so it is also dropped from insidePoints once it errors. Otherwise the
guard would turn a dead thread into a console flood at framerate. enter and leave only
fire on a transition, so they stay registered.

Measured in game on artifact 25770, with a healthy point, a point whose enter raises
and a point whose inside raises, all created while standing on them:

  before: the healthy point fired enter, then the broken one raised at points/client.lua
          and after that nothing happened at all - no leave when walking away, and
          points created afterwards never fired enter either, the thread was gone
  after:  both failures are logged against the owning point and resource, all three
          points fired leave when walking away, and points created afterwards worked
          normally. The inside failure printed once rather than every frame.

The runtime already prints the error and its stack, so the added line only names the
point and the resource it came from.
2026-07-27 17:19:40 +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 48154495f8 fix(esx_multicharacter/client): act on the local player in HideHud
ESX.PlayerId does not exist - the shared object only defines ESX.playerId,
and these were the only two places in the repo using the capitalised name.
The nil is passed to the natives as player index 0, so neither call touched
the local player. SetPlayerControl at line 266 already used the correct
spelling, which is why controls were re-enabled after spawning but never
disabled during character selection.

Measured on artifact 25770, local player index 128:
  SetPlayerControl(ESX.PlayerId, false, 0) -> IsPlayerControlOn stays 1
  SetPlayerControl(ESX.playerId, false, 0) -> IsPlayerControlOn becomes false

HideHud also ignored its argument: hidePlayers was hardcoded to true and the
volume override was set to 0.0 for both hiding and unhiding. Correcting only
the name would have made the mute apply for the first time and never lift it,
so the reset value and the argument have to be handled together.
2026-07-27 16:17:29 +02:00
Selt 2a6ec4c7d4 fix(esx_multicharacter/client): restore the stored height of hidden hud components
HideComponents stores the value GetHudComponentSize returns and reads back
size.x and size.z when restoring, but the meaningful fields are x and y - the
hide branch a few lines above tests exactly those. size.z is 0.0, so the
components are restored at height zero and stay invisible for the rest of the
session.

Measured on artifact 25770 after character selection, components 11 and 12:
  before: x=0.218 y=0.000  (width restored, height lost)
  after:  x=0.218 y=0.389
2026-07-27 16:17:29 +02:00
Selt a507ec6a31 fix(esx_inventory/client): stop overloading the menu usable flag
esx_menu_default treats usable == false as "this row cannot be submitted".
esx_inventory was filling the same field with its own meaning - whether an
item has a use callback - and set it to false for accounts, for weapons and
for every item without a registered callback.

The result was that pressing enter on cash, bank, dirty money, any weapon or
any non consumable item did nothing at all. buildItemActionMenu was never
reached, even though those entries carry canRemove and the submenu would have
offered give and throw.

The action menu needs the value for its own "use" entry, so it moves to a
separate canUse field. The weight row keeps usable = false, since not being
submittable is correct there.

Verified on artifact 25770. Before: enter on cash and on bread did nothing.
After: cash offers throw and return, bank offers only return because it is
not droppable, bread offers throw and return without a use entry, and the
weight row still cannot be selected.
2026-07-27 16:17:28 +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
Selt 7538debb6f fix(es_extended/server/main): add the weapon before restoring its tint and components
esx:giveInventoryItem applies the tint and the components to the receiving
player before calling addWeapon. Both setWeaponTint and addWeaponComponent
start with getWeapon and only proceed when the player already owns the weapon,
and the handler returns early a few lines above if the target does own it - so
at that point the target provably does not, and both calls do nothing.

The result is that handing a weapon to another player silently strips its
attachments and its paint. The pickup path in the same file already adds the
weapon first and then restores tint and components.

The values are snapshotted from the source weapon before any of this, and
removeWeapon already ran before addWeapon, so only the position of the two
restore blocks changes.

Verified on artifact 25770 by running both orders against a loadout:
  tint and component first -> both return false, weapon ends up tint 0 with no
                              components
  addWeapon first          -> both return true, weapon ends up tint 1 with the
                              component attached
2026-07-27 16:17:27 +02:00
Selt d80274187c fix(es_extended/server/functions): drop leading space from merge command arguments
The merge argument type computes the length of the preceding arguments plus
their separators, then passes that value straight to string.sub. Lua strings
are 1-indexed, so the merged text actually starts one character later and the
separating space ends up at the front of the value.

Tested with a command whose merge argument sits at position 1, 2 and 3:
positions 2 and 3 returned " def ghi" and " ccc ddd", now they return
"def ghi" and "ccc ddd". Position 1 is unaffected because string.sub clamps
index 0 to 1.
2026-07-27 16:17:26 +02:00
Selt 22ddbadd6e fix(es_extended/server/main): use the identity sex for the default skin
The fallback skin is built from userData.sex, but that field does not exist
yet at this point - it is only assigned further down when the identity data
is read from the result row. The comparison therefore always ran against nil
and every character without a saved skin fell back to the male model.

result.sex holds the value already and is used a few lines later, so read it
directly.

Tested locally on artifact 25770 with a female character whose skin column is
empty: the client used to receive skin.sex 0 and spawned as the male model,
it now receives 1 and spawns female. A male character with an empty skin
column still receives 0.
2026-07-27 16:17:26 +02:00
Selt 49c31bcb61 fix(es_extended/server/main): skip unknown weapons when loading a loadout
ESX.GetWeaponLabel asserts on a name that is not in Config.Weapons, so it
never returns a falsy value and the `if label then` check below it can never
skip anything. The assert propagates out of loadESXPlayer, which means the
player object is never created and esx:playerLoaded never fires - the player
is stuck on the loading screen and hits the same error on every reconnect.

This happens whenever a saved loadout holds a weapon the config no longer
knows, for example after changing sv_enforceGameBuild or after removing a
weapon from shared/weapons.lua.

Wrapping the lookup keeps GetWeaponLabel itself untouched and lets the
existing check do what it was written for.
2026-07-27 16:17:26 +02:00
Selt c30225f223 fix(es_extended/server/functions): persist items added via AddItems
MySQL.prepare.await batch mode expects an array of positional parameter
arrays, but toInsert holds keyed tables, so the placeholders received no
values and the INSERT silently did nothing. Items still showed up in-game
because ESX.Items was populated from the keyed tables, but nothing was
written to the items table. Build a positional parameters array for the
prepared insert.

Closes #1768
2026-07-27 16:17:25 +02:00
ASTROWwwW 477b71384f fix(esx_lib): load imports from the correct resource name
xLib.name was "xLib", which is not a resource, so the import metatable fed it to LoadResourceFile and resolved nothing. It failed silently on Legacy and hard-errored on Enhanced (Resource 'xLib' not found). Point it at the real resource name.
2026-07-27 00:25:14 +02:00
ASTROWwwW 94002da05b feat(esx_lib): add isEnhanced game edition detection
xLib.isEnhanced() returns whether the game runs GTA V Enhanced (server: gamename convar, client: IsGameEnhancedVersion native). es_extended reads it once and requires OneSync Infinity on Legacy only, since Enhanced runs OneSync natively.
2026-07-23 20:51:21 +02:00
Arctos. 220a1bf028 Merge pull request #1808 from ASTROWwwW/feat/locale-key-fallback
feat(es_extended): fall back to English for missing locale keys
2026-07-21 18:49:22 +02:00
ASTROWwwW 25d534f46e refactor(es_extended): backfill locale from English once at load
Instead of checking the English fallback on every Translate call, fill the missing keys into the target locale the first time it loads, log the gaps, and drop the English table. The active locale ends up self-contained: no per-call fallback and only one table stays in memory.
2026-07-21 17:15:56 +02:00
ASTROWwwW 567ee506a1 feat(es_extended): fall back to English for missing locale keys
When a key is absent from the active locale, Translate now borrows the English string instead of returning a placeholder. Locale loading is factored out so the target and English tables are lazily cached. Toggle with the esx:localeFallback convar (on by default) or per-resource Config.LocaleFallback.
2026-07-21 16:50:25 +02:00
ASTROWwwW 511f5fc74e chore: bump manifest version to 1.14.0 2026-07-18 16:58:18 +02:00
Ihsan e7788b2bb4 feat: integrate Medal.tv globally 2026-07-18 14:42:02 +05:30
ASTROWwwW a33913aa41 fix(esx_lib): stop the table module from overwriting the stdlib 2026-07-16 14:51:53 +02:00
_Not_ 915332b092 Fix typo in ESX.Table.SizeOf assignment in compat.lua 2026-07-16 00:58:00 -05:00
_Not_ 9a4c492b51 Add initialization for ESX.Table in compat.lua 2026-07-16 00:55:38 -05:00
_Not_ 48e19810ce Merge remote-tracking branch 'Astro/feat/xlib-waitfor' into 1.14.0 2026-07-15 23:51:13 -05:00