Zum Hauptinhalt springen

v4.0.0

A rewrite of the framework bridge, plus the bug hunt that came with reading every module again.

This release is not backwards compatible

MSK.GetPlayer() returns a different shape, ox_core is gone, and the event payloads changed. Read the migration notes before you update, and replace the whole resource, not single files.

Added

Qbox is a framework of its own

Until 4.0.0 a Qbox server ended up in the QBCore branch, because qbx_core/fxmanifest.lua declares provide 'qb-core' and the detection asked GetResourceState('qb-core'). That worked by accident and cost the one thing Qbox has and QBCore does not: multijob. PlayerData.jobs is a table<string, integer>, and the qb compatibility layer flattens it to a single job.

Detection now asks for each framework under its own resource name and checks qbx_core first. See Frameworks.

A provide hit is a fallback, never a detection

FiveM changed how provided names answer resource-state lookups twice during 2026, and for two weeks GetResourceState returned missing for a provided name without a single line of script code having changed.

One player shape on every framework

MSK.GetPlayer(id) returns the same fields whether ESX, QBCore or Qbox is running: source, identifier, license, name, firstName, lastName, dob, sex, phone, group, job, jobs, gang, gangs, money, metadata, position.

job and gang always look the same too: name, label, grade, gradeName, gradeLabel, salary, isBoss, onDuty.

jobs and gangs are filled on every framework. On Qbox that is the real multijob map, on ESX and QBCore it holds the single job the player has, so consumer code can read player.jobs without asking which framework it is on.

MSK.GetPlayer takes whatever you have

A server id, an identifier or citizenid, or a table ({source = }, {identifier = }, {citizenid = }, {phone = }, {userId = }). Passing a plain number used to raise attempt to index a number value.

The player object has methods again, and this time they arrive

SetJob, SetDuty, AddJob, RemoveJob, HasJob, AddMoney, RemoveMoney, GetMoney, SetMoney, GetMeta, SetMeta, AddItem, RemoveItem, HasItem, CanCarryItem, Notify, Kick, Save, Refresh and more. See the migration notes for why they did not before.

Job and gang definitions

MSK.GetJobs() and MSK.GetGangs(), client and server. Every framework keeps its list somewhere else: ESX behind ESX.GetJobs(), QBCore in QBCore.Shared.Jobs, Qbox behind its own export. A script that only wanted to fill a dropdown had to know all three and usually covered two.

The grades come back normalised into a list sorted by grade number, no matter how the framework stored them. MSK.GetGangs() is empty on ESX, which has no gangs.

MSK.VehicleStore

One shape over the framework's owned-vehicle table: GetSchema, GetByPlate, CountByPlate, Insert, Update, Delete, ClearJob and a paginated, filtered Browse. See Vehicle Store.

The tables differ by more than their names. ESX keeps every property as JSON in owned_vehicles.vehicle, QBCore and Qbox keep the spawn name in player_vehicles.vehicle and the properties in mods, so a name-to-name mapping is not enough. The two columns QBCore and Qbox do not have, job and type, are added on start with ALTER TABLE ... ADD COLUMN IF NOT EXISTS, which leaves existing rows untouched.

warnung

Vehicle properties are handed back in the format the running framework wrote them in. They are deliberately not unified, because every other garage on the server reads the same column.

More

  • MSK.IsPlayerDead() as a function of its own, including the visn_are and osp_ambulance special cases. It used to sit on the client player object where consumers could not reach it.
  • MSK.GetPlayerJobs, MSK.GetPlayerGang and, on the client, MSK.GetPlayerData, MSK.IsPlayerLoaded, MSK.GetPlayerJob, MSK.GetPlayerGang, MSK.GetPlayerJobs.
  • MSK.Offline.GetPlayerTable() returns the character table of the running framework together with its key column: users and identifier on ESX, players and citizenid on QBCore and Qbox. Scripts that hang their own column on that table were guessing users, which is right on one framework out of three.
  • player.ClearInventory(), with a clear entry in every inventory adapter.
  • Company accounts follow the banking resource, not the framework. MSK.Society detects Renewed-Banking, qb-banking, qb-management or esx_addonaccount. Before this it branched on the framework, so a Qbox server got a hard 0 back no matter what was installed. MSK.Society.GetProvider() reports which one was found.
  • MSK.Offline and MSK.GetModelFromPlate know the Qbox tables.

Changed

Framework and inventory are separate axes

No framework bridge carries item code any more, and no inventory adapter carries framework code. All item handling lives in inventories/server/*.lua, including the new default.lua for the inventory built into the running framework. FunctionOverride, which glued item functions onto the player object after the fact, is gone.

CanCarryItem may answer nil

nil means the running inventory cannot check that, which is not the same as false. The old code answered true without checking on QBCore and Qbox, and an item handed out on that promise ends up on the floor.

The client has no framework adapter any more

The server normalises player data and sends it with every msk_core:* event, and bridge/client.lua receives it. One shape, one place. The client also cannot write player data back any more, which was an exploit path.

More

  • MSK.HasItem(playerId, item, count, metadata) takes an optional minimum count. Passing metadata as the third argument still works.
  • The bridge no longer writes to the framework's own objects. In 3.x MSK.GetPlayer() replaced xPlayer.job with the bridge's own shape, for every resource on the server, and set label to the technical job name.

Fixed

The load and logout events never fired

bridge/*/server.lua and client.lua registered handlers for msk_core:playerLoaded, :playerLogout, :setJob and :setPlayerData, but nothing in msk_core ever triggered them and no framework event was ever bound. MSK.LoadedPlayers stayed empty on every framework, client player data was only ever filled on a resource restart, and consumers waiting for a load event waited forever.

A single unban disabled the ban system

UnbanPlayer set an array slot to nil instead of removing the entry, and MSK.IsPlayerBanned walks 1..#bannedPlayers. The next player to connect hit that hole, the playerConnecting handler died before reaching its CancelEvent(), and every banned player could join again until the next restart. A ban row with an unreadable time value did the same.

Deleting a cron job disabled cron

Same nil-in-an-array cause, same effect on the two tick loops. On top of that, msk_core:createCron was a net event, so any client could schedule jobs on the server, and a job created from a plain timestamp ended the scheduler on its first run.

Any client could read any player's data

The msk_core:getPlayerData callback returned the complete player table for whatever id it was asked about: cash, bank, every metadata field, the licence. It now answers with identity and job for another player, and with everything only for the caller themselves. It was also registered twice, and the later registration silently replaced the other.

The client player mirror could die and freeze

getPlayerDeath() went through MSK.Call, which raised on timeout rather than returning nil, so one slow answer from an ambulance script ended the thread and ped, vehicle, seat and weapon stayed at their last value for the rest of the session. MSK.Call now returns nil instead of raising, which is what its own comment always claimed it did.

More

  • MSK.Player.serverId could stay wrong for a whole session. GetPlayerServerId answers -1 until the session is up, and the value was read once at resource start and never corrected.
  • A closed input or numpad left its caller waiting forever. Both build a promise for the blocking call and both cleared only the callback on close, so escape or a resource stop left the promise unresolved.
  • The menu stopped accepting keys. Menu.Show calls Menu.Hide, Hide calls onClose, and if that callback yields, the input thread ends itself and clears the flag that would have let a new one start.
  • Callback timeouts. All three call sites rejected a promise they never awaited, which FiveM reports as an unhandled rejection, and returned a silent nil. The request-id collision check compared a number against entries stored under a string key, so it could never match.
  • sex was inverted on QBCore and Qbox: gender == 1 was read as male, while the character creator writes 0 for male.
  • The client bridge for QBCore read a field that does not exist. It asked for self.PlayerData.citizenid where self is already the PlayerData.
  • MSK.Timeout tracked cancelled ids instead of pending ones, so a Clear after the callback had run left an entry nothing removed again.
  • Points.Remove(id) called its handler without self and could not work at all. Progress stopped animations with anim.clip, a field that is never set. Coords.Copy indexed MSK.Player[id] unchecked. Context and Menu built inline ids from GetGameTimer() and never cleaned them up. AdvancedNotification turned an explicit flash = false back on. Check.Dependency compared against nil when the two versions had a different number of parts, and the "should not be renamed" warning was printed even when the name was right. MSK.AddWebhook fired HTTP requests at an empty URL and said nothing.

Removed

ox_core

The branch carried no guarantee, was never finished (its GetPlayerData was an empty function) and is gone. Config.Framework = 'OXCore' now stops with an explicit message instead of failing later.

Migration

MSK.GetPlayer() returns the unified shape

Field names changed:

3.x4.0.0
grade_namegradeName
grade_labelgradeLabel
grade_salarysalary
dateofbirthdob

sex is 'male' or 'female' on every framework. Money is player.money.cash and player.money.bank, and GetMoney('cash') works everywhere.

The methods work in your resource now

They are built by modules/Player, which import.lua compiles into the consumer. Before, msk_core built the object and handed it over an export, where functions do not survive: every method arrived as nil and only the data half was usable. Reaching them through MSK.GetPlayer(...) is unchanged.

local player = MSK.GetPlayer(source) -- full object, methods included
local data = exports.msk_core:GetPlayerData(source) -- data only

MSK.Bridge is a real table in a consumer

It used to fall through to the export proxy and become a function, so MSK.Bridge.Framework.Type raised attempt to index a function value. Scripts carrying if MSK.Bridge and MSK.Bridge.Framework and ... now do what they always meant to.

MSK.Bridge.Framework.Core stays inside msk_core: the raw framework object is not handed across the export boundary.

Event payloads carry the unified shape

msk_core:playerLoaded hands over a player table, msk_core:setJob a job table. Two events were added: msk_core:setGang and msk_core:setDuty. The full list is on the Frameworks page.

Config.Framework = 'OXCore'

Has to be changed to 'AUTO' or a supported framework.

Changed files

Replace the whole resource.

fxmanifest.lua
config.lua
aliases.lua
init/shared.lua
init/server.lua
bridge/shared.lua
bridge/server.lua (new)
bridge/client.lua (new)
bridge/esx/server.lua
bridge/qbcore/server.lua
bridge/qbox/server.lua (new)
bridge/esx/client.lua (removed)
bridge/qbcore/client.lua (removed)
bridge/oxcore/ (removed)
inventories/server/default.lua (new)
inventories/server/ox_inventory.lua
inventories/server/jaksam_inventory.lua
inventories/server/core_inventory.lua
inventories/server/custom.lua
inventories/server/hasitem_server.lua
inventories/server/registeritems.lua
inventories/client/hasitem_client.lua
modules/Bridge/shared.lua (new)
modules/VehicleStore/server.lua (new)
modules/Player/server.lua
modules/Player/client.lua
modules/Ban/server.lua
modules/Cron/server.lua
modules/Check/server.lua
modules/Callback/shared.lua
modules/Callback/client.lua
modules/Callback/server.lua
modules/Society/server.lua
modules/Offline/server.lua
modules/Command/server.lua
modules/Notify/client.lua
modules/Vehicle/server.lua
modules/Timeout/shared.lua
modules/World/server.lua
modules/Coords/server.lua
modules/Input/client.lua
modules/Numpad/client.lua
modules/Progress/client.lua
modules/Points/client.lua
modules/Context/
modules/Menu/