v1.14.9
Summary
Skipped v1.14.8. This release improves inventory snapshots and report/chat tooling, and expands bridge/config options for reports, logs.
What's New
Features
- Entity opacity control for props / entity inspector
- Markdown support in chat and report messages
- Vehicle database plate search (exact match) and search-type tabs
- Improved
jaksam_inventorystash/trunk/glovebox item listing - Report feature toggles:
video_record,voice_channel,transcripts(plus new transcript permissions) logs.discord_onlyoption to skip panel log storage and use Discord only- Snapshot lookup columns/indexes for better death-inventory performance
- Clothing menu reliability and custom clothing builder improvements
Bug Fixes
- Vehicles plate search no longer leaves the loading spinner stuck
- Ace permissions correctly show for the owner group; NUI callback error fixed
- Custom clothing builder variants fixed
- Staff member create flow fixed
- Prop delete reliability fixes
- Blips fixes
- Snapshot query/cleanup performance improvements
File Changes
⚙️ Configuration & Bridge Files
These files are not encrypted. Review the changes below — you may need to manually apply updates to your server's config files.
bridge/server/inventory.lua
return items
end
elseif isResourceRunning("jaksam_inventory") then
+ local invId = id
if typeofStash == "trunk" or typeofStash == "glovebox" then
- return exports['jaksam_inventory']:getInventoryIdFromPlate(id, typeofStash)
- else
- return exports['jaksam_inventory']:getInventory(id)
+ invId = exports['jaksam_inventory']:getInventoryIdFromPlate(id, typeofStash)
+ if not invId then return {} end
+ end
+
+ local jaksamInventory = exports['jaksam_inventory']:getInventory(invId)
+ if not jaksamInventory or type(jaksamInventory.items) ~= "table" then return {} end
+
+ local items = {}
+ for slotKey, item in pairs(jaksamInventory.items) do
+ if type(item) == "table" and item.name then
+ items[#items + 1] = {
+ name = item.name,
+ amount = item.amount,
+ slot = tonumber(item.slot) or tonumber(tostring(slotKey):match("%d+")),
+ metadata = item.metadata,
+ }
+ end
end
+
+ return items
elseif isResourceRunning("core_inventory") then
return exports.core_inventory:getInventory("stash-" .. id)
endbridge/server/job.lua
end
function JobManager.getGangs()
+ if GetResourceState("op-crime") == "started" then
+ local gangs = exports['op-crime']:getOrganisationsList()
+ return lib.array.map(gangs, function(g)
+ return {
+ name = g.identifier,
+ label = g.label,
+ grades = {}
+ }
+ end)
+ end
+
if Framework.name == "esx" then
return {}
elseif Framework.name == "qb" or Framework.name == "qbx" thenbridge/server/player.lua
---@param playerObj table
---@return {name:string, label:string, grade: {name:string, level:number, isboss:boolean}} | nil
function player.getGang(playerObj)
+ if GetResourceState("op_crime") == "started" then
+ local gangName = Player(playerObj.PlayerData.source).state.gangName
+ local gangId = Player(playerObj.PlayerData.source).state.gangId
+ local rank = Player(playerObj.PlayerData.source).state.gangRankName
+ return {
+ name = gangId,
+ label = gangName,
+ grade = {
+ name = rank,
+ level = rank,
+ isboss = false,
+ },
+ }
+ end
+
+
if Framework.name == 'esx' then
return nil -- ESX uses jobs for gangs
elseif Framework.name == 'qb' or Framework.name == 'qbx' then
function player.openClothingMenu(source)
if GetResourceState("17mov_CharacterSystem") == "started" then
- if Framework.name == "esx" then
- TriggerClientEvent("esx_skin:openSaveableMenu", source)
- else
- TriggerClientEvent("qb-clothing:client:openMenuCommand", source)
- end
+ TriggerClientEvent("esx_skin:openSaveableMenu", source)
+ TriggerClientEvent("qb-clothing:client:openMenuCommand", source)
elseif GetResourceState('illenium-appearance') == "started" then
TriggerClientEvent("illenium-appearance:client:openClothingShop", source, true)
elseif GetResourceState('vms_clothestore') == "started" then
TriggerClientEvent("qb-clothing:client:openMenu", source)
elseif GetResourceState("0r-clothingv2") == "started" then
TriggerClientEvent('0r-clothing:openCharacterCreationMenu', source, true, true)
+ elseif GetResourceState("um-clothing") == "started" then
+ if Framework.name == "esx" then
+ TriggerClientEvent("esx_skin:openSaveableMenu", source)
+ else
+ TriggerClientEvent("qb-clothing:client:openMenu", source)
+ end
end
endconfig/config.json
"__comment__": "Use luxu's built-in weather system"
},
"reports": {
- "enabled": true
+ "enabled": true,
+ "video_record": true,
+ "voice_channel": true,
+ "transcripts": true,
+ "_comment_": "Set video_record, voice_channel, or transcripts to false to disable those report features."
},
"external_web_panel": {
"enabled": true,
},
"logs": {
- "_comment_": "Adjust as needed",
+ "_comment_": "Set discord_only to true to disable the panel logs page and database storage. Logs will only be sent to configured Discord channels.",
"enabled": true,
+ "discord_only": false,
"max_age_days": 7,
"tag_discord_user": true,config/database/queries.d.ts
/** Returns all vehicles of a given model hash with their owner's charId. */
getVehicleOwners(modelHash: number): Promise<{ plate: string; char_id: string }[]>;
+ /**
+ * Returns the vehicle matching an exact plate, with its owner's charId and
+ * model (hash for ESX/QB/QBX, model name for vRP) for display purposes.
+ * Uses an exact match (rather than a `LIKE` scan) so the lookup stays index-friendly.
+ */
+ getVehicleOwnersByPlate(plate: string): Promise<{ plate: string; char_id: string; model: string | number }[]>;
+
/**
* Returns the vehicle mod/properties data for a given plate.
* ESX reads `vehicle` from `owned_vehicles`; QB/QBX reads `mods` from `player_vehicles`.config/database/queries.js
);
},
+ /**
+ * Returns the vehicle matching an exact plate, with its owner's charId and
+ * model (hash for ESX/QB/QBX, model name for vRP) for display purposes.
+ * Uses an exact match (rather than a `LIKE` scan) so the lookup stays index-friendly.
+ * @param {string} plate
+ * @returns {Promise<{ plate: string, char_id: string, model: string | number }[]>}
+ */
+ async getVehicleOwnersByPlate(plate) {
+ if (!plate) return [];
+
+ if (Framework.name === 'esx') {
+ return await oxmysql.query(
+ `SELECT plate, owner as char_id, CAST(JSON_UNQUOTE(JSON_EXTRACT(vehicle, "$.model")) AS SIGNED) as model
+ FROM owned_vehicles WHERE plate = ? LIMIT 1`,
+ [plate]
+ );
+ }
+ if (Framework.name === 'vrp') {
+ const rows = await oxmysql.query('SELECT id FROM vrp_characters');
+ for (const row of rows) {
+ const vehicles = await Luxu.garage.getOwnedVehicles(String(row.id));
+ for (const vehicle of vehicles) {
+ if (String(vehicle.plate).toLowerCase() === plate.toLowerCase()) {
+ return [{ plate: vehicle.plate, char_id: String(row.id), model: Number(vehicle.model) }];
+ }
+ }
+ }
+ return [];
+ }
+
+ return await oxmysql.query(
+ 'SELECT plate, citizenid as char_id, hash as model FROM player_vehicles WHERE plate = ? LIMIT 1',
+ [plate]
+ );
+ },
+
/**
* Returns the vehicle mod/properties data for a given plate.
* ESX reads `vehicle` from `owned_vehicles`; QB/QBX reads `mods` from `player_vehicles`.config/permissions.json
"props.delete": "perm_props_delete",
"props.view": "perm_props_view",
"reports.close": "perm_reports_close",
+ "reports.transcript_view": "perm_reports_transcript_view",
+ "reports.transcript_delete": "perm_reports_transcript_delete",
"resources.manage": "perm_resources_manage",
"resources.read": "perm_resources_read",
"self.blackout": "perm_self_blackout",
"self.delete_vehicle": "perm_self_delete_vehicle",
"self.entity_controls": "perm_self_entity_controls",
"self.entity_inspector": "perm_self_entity_inspector",
+ "self.delete_entities": "perm_self_delete_entities",
"self.freeze": "perm_self_freeze",
"self.give_vehicle": "perm_self_give_vehicle",
"self.godmode": "perm_self_godmode",fxmanifest.lua
author 'luxu-gg'
description 'Admin menu for FiveM'
-version '1.14.7'
+version '1.14.9'
escrow_ignore { 'config/**/*', 'bridge/**/*' }sql/install.sql
`claimed_by_name` varchar(50) DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `FK_luxu_admin_chats_luxu_admin_group_members` (`claimed_by`),
+ KEY `idx_report_closed_created` (`is_report`, `is_closed`, `created_at`),
CONSTRAINT `FK_luxu_admin_chats_luxu_admin_group_members` FOREIGN KEY (`claimed_by`) REFERENCES `luxu_admin_group_members` (`id`) ON DELETE SET NULL ON UPDATE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`type` varchar(64) NOT NULL,
`data` longtext NOT NULL,
+ `unique_id` varchar(128) DEFAULT NULL,
+ `char_id` varchar(128) DEFAULT NULL,
+ `license` varchar(128) DEFAULT NULL,
+ `item_count` int(10) unsigned NOT NULL DEFAULT 0,
`created_at` datetime NOT NULL DEFAULT current_timestamp(),
PRIMARY KEY (`id`),
- KEY `idx_type_created_at` (`type`, `created_at`)
+ KEY `idx_type_created_at` (`type`, `created_at`),
+ KEY `idx_snapshot_lookup` (`type`, `unique_id`, `char_id`, `license`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
CREATE TABLE IF NOT EXISTS `luxu_admin_player_notes` (Full list of files changed
| Change | File |
|---|---|
| Modified | bridge/server/inventory.lua |
| Modified | bridge/server/job.lua |
| Modified | bridge/server/player.lua |
| Modified | client/modules/actions.lua |
| Modified | client/modules/chat.lua |
| Modified | client/modules/clothingmenu.lua |
| Modified | client/modules/entity_inspector.lua |
| Modified | client/modules/new_clothing_menu.lua |
| Modified | client/modules/props.lua |
| Modified | config/config.json |
| Modified | config/database/queries.d.ts |
| Modified | config/database/queries.js |
| Modified | config/permissions.json |
| Modified | fxmanifest.lua |
| Modified | server/modules/props.lua |
| Modified | server/modules/snapshots.lua |
| Modified | sql/install.sql |
| Modified | server/index.js |
| Modified | web/ |