v1.14.4
Summary
This release introduces playtime-based jail sentences that only count down while a player is online and actively jailed, alongside the existing date-based expiry mode. Noclip and clothing reload behavior are routed through the action system for more reliable handling, NUI callbacks are fixed, and the admin UI receives several quality-of-life updates.
What's New
Features
- Jail playtime mode — new
time_modeoption inconfig.json(dateorplaytime). Playtime sentences trackduration_msandremaining_msin the database and only decrement while the player is online, loaded, and jailed. - Sentence command improvements —
/sentencesupports both time modes, auto-close timing (auto_close_seconds), and updated SQL active-jail detection. - Clothing reload —
reloadskinis only run for supported appearance resources; fallbackelsebranch ensures other setups still reload.
Bug Fixes
- NUI callbacks — response handling and callback registration fixes.
- UI fixes — assorted admin panel UI corrections (jail modal, moderation, monitoring, players, statistics, and related components).
Other
- fxmanifest — production
ui_pagerestored (web/index.html); local dev URL commented out. - Player jail management — optimizations to jail state handling on connect and during sessions.
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.
fxmanifest.lua
author 'luxu-gg'
description 'Admin menu for FiveM'
-version '1.14.3'
+version '1.14.4'
dependencies {
'oxmysql',
files { 'config/config.json', 'config/themes.css', 'config/permissions.json', 'config/sounds/*', 'config/help.lua', 'config/constants/*.json', 'config/logging/cl_logs.lua', 'config/teleports.lua', 'config/commands/cl_commands.lua', 'config/custom_functions/client.lua', 'locales/*.json', "web/index.html", 'web/**/*', "bridge/framework.lua", "bridge/shared/**/*.lua", "bridge/client/**/*.lua" }
---ui_page 'web/index.html'
-ui_page 'http://localhost:7017/'
+ui_page 'web/index.html'
+--ui_page 'http://localhost:7017/'
escrow_ignore { 'config/**/*', 'bridge/**/*' }bridge/client/clothing.lua
local clothing = {}
function clothing.loadPlayerSkin()
- ExecuteCommand("reloadskin")
if GetResourceState("qb-clothing") == "started" then
TriggerServerEvent("qb-clothes:loadPlayerSkin")
elseif GetResourceState("illenium-appearance") == "started" then
local result = lib.callback.await("illenium-appearance:server:getAppearance")
exports["illenium-appearance"]:setPlayerAppearance(result)
elseif GetResourceState("rcore_clothing") == "started" then
ExecuteCommand("reloadskin")
elseif GetResourceState("skinchanger") == "started" and GetResourceState("esx_skin") == "started" then
Framework.object.TriggerServerCallback('esx_skin:getPlayerSkin', function(skin)
TriggerEvent('skinchanger:loadSkin', skin)
end)
elseif GetResourceState("qs-appearance") == "started" or GetResourceState("ak47_clothing") == "started" or GetResourceState("ak47_qb_clothing") == "started" then
ExecuteCommand("reloadskin")
+ else
+ ExecuteCommand("reloadskin")
end
endconfig/config.json
},
"jail": {
"enabled": true,
+ "time_mode": "playtime",
"jail_cells": [
{ "name": "Jail Cell 1", "coords": { "x": 459.57, "y": -1001.68, "z": 24.9, "w": 266.29 }, "safeRadius": 5.0 },
{ "name": "Jail Cell 2", "coords": { "x": 458.89, "y": -998.03, "z": 24.9, "w": 269.29 }, "safeRadius": 5.0 },
{ "name": "Jail Cell 3", "coords": { "x": 460.5, "y": -994.34, "z": 24.9, "w": 269.29 }, "safeRadius": 5.0 }
],
- "_comment_": "Unique Names | The safe radius is the radius around the jail cell where the player walk freely. If the player is not within the safe radius, they will be teleported to the cell."
+ "_comment_": "time_mode can be 'date' or 'playtime'. Date sentences expire at the exact timestamp. Playtime sentences only count down while the player is online, loaded, and actively jailed. Jail cell names must be unique. The safe radius is the radius around the jail cell where the player walk freely. If the player is not within the safe radius, they will be teleported to the cell."
},
- "weather":{
+ "weather": {
"enabled": false,
"__comment__": "Use luxu's built-in weather system"
},
"sentence": {
"enabled": true,
"name": "sentence",
- "description": "Check your remaining jail time"
+ "description": "Check your remaining jail time",
+ "auto_close_seconds": 5,
+ "__comment__": "Set auto_close_seconds to 0 to make it persistent"
},
"jail": {
"enabled": true,config/commands/cl_commands.lua
if not HasPermission("self.noclip", false, true) then return end
local freeFall = IsDisabledControlPressed(0, 36)
- ToggleNoclip(nil, freeFall)
+ Luxu.callbackAsync("actions:noclip", false, function() end, {
+ target = Luxu.cache.serverId,
+ freeFall = freeFall
+ })
end or function() end
})config/commands/sv_commands.lua
restricted = false,
}, function(src, args)
if not HasPermission(src, "self.noclip", true, true) then return end
- Luxu.triggerClientEvent('actions:noclip', src);
+ exports[GetCurrentResourceName()]:action("noclip", src, {
+ target = src
+ })
end)
end
end)
end
+local JAIL_ACTIVE_CONDITION =
+ "((COALESCE(time_mode, 'date') = 'date' AND expires_at > UTC_TIMESTAMP()) OR (time_mode = 'playtime' AND remaining_ms > 0))"
if commands.sentence.enabled then
Luxu.addCommand(commands.sentence.name, {
help = commands.sentence.description,
-- Get jail information from database
local jailInfo = MySQL.prepare.await(
- "SELECT *, TIMESTAMPDIFF(SECOND, '1970-01-01 00:00:00', expires_at) as expires_at, TIMESTAMPDIFF(SECOND, '1970-01-01 00:00:00', created_at) as created_at FROM luxu_admin_jail WHERE char_id = ? AND expires_at > UTC_TIMESTAMP()",
+ "SELECT *, TIMESTAMPDIFF(SECOND, '1970-01-01 00:00:00', expires_at) as expires_at, TIMESTAMPDIFF(SECOND, '1970-01-01 00:00:00', created_at) as created_at FROM luxu_admin_jail WHERE char_id = ? AND " ..
+ JAIL_ACTIVE_CONDITION .. " ORDER BY id DESC LIMIT 1",
{ charId })
return
end
- jailInfo.expires_at = jailInfo.expires_at * 1000
- jailInfo.created_at = jailInfo.created_at * 1000
+ jailInfo.time_mode = jailInfo.time_mode == "playtime" and "playtime" or "date"
+ jailInfo.expires_at = (tonumber(jailInfo.expires_at) or 0) * 1000
+ jailInfo.created_at = (tonumber(jailInfo.created_at) or 0) * 1000
+ jailInfo.duration_ms = tonumber(jailInfo.duration_ms) or math.max(0, jailInfo.expires_at - jailInfo.created_at)
+ jailInfo.remaining_ms = math.max(0, tonumber(jailInfo.remaining_ms) or 0)
-- Calculate remaining time
local currentTime = os.time() * 1000 -- Convert to milliseconds
- local remainingMs = jailInfo.expires_at - currentTime
+ local remainingMs = jailInfo.time_mode == "playtime" and jailInfo.remaining_ms or jailInfo.expires_at - currentTime
local isExpired = remainingMs <= 0
-- Send sentence information to client for displaysql/install.sql
`cell_name` varchar(255) DEFAULT NULL,
`last_coords` longtext NOT NULL,
`bucket` int(10) unsigned NOT NULL DEFAULT 0,
+ `time_mode` enum('date','playtime') NOT NULL DEFAULT 'date',
+ `duration_ms` bigint(20) unsigned NOT NULL DEFAULT 0,
+ `remaining_ms` bigint(20) unsigned NOT NULL DEFAULT 0,
`expires_at` datetime NOT NULL,
`created_at` datetime NOT NULL DEFAULT current_timestamp(),
PRIMARY KEY (`id`),Full list of files changed
| Change | File |
|---|---|
| Modified | bridge/client/clothing.lua |
| Modified | client/modules/actions.lua |
| Modified | client/modules/nui.lua |
| Modified | client/modules/zones.lua |
| Modified | client/utils/cl_functions.lua |
| Modified | client/utils/noclip.lua |
| Modified | config/commands/cl_commands.lua |
| Modified | config/commands/sv_commands.lua |
| Modified | config/config.json |
| Modified | fxmanifest.lua |
| Modified | server/modules/jail.lua |
| Modified | sql/install.sql |
| Modified | server/index.js |
| Modified | web/ |