Version 1.34.1
Highlights
- Added a configurable global vehicle purchase-price catalogue so vehicle-value-based tuning prices can use your dealership's existing table and spawn-name fields.
- Fixed an issue where Space could not restore the tuning cursor after entering free camera.
- Fixed an issue where the first tuning save failed for owned Qbox vehicles with missing or empty saved properties.
- Fixed an issue where Mechanic failed to initialize when shared dependencies took more than 60 seconds to load.
Notable Changes
- Added a Free camera in tuning menu feature switch in
/jobconfig, allowing you to disable free-camera entry separately from automatic part focusing. - Fixed an issue where vehicle and Dyno updates produced network-object warnings outside a player's streaming range.
- Fixed an issue where lift attachment updates tried to use a vehicle after it left streaming range during loading.
- Improved Mechanic startup so a failed migration is reported without aborting the entire initializer.
Other Changes
- Updated the release package to keep its own dependency bootstrap editable for support.
Changed Files
sky_mechanicjob/README.mdsky_mechanicjob/config/config.luasky_mechanicjob/config/locales/*sky_mechanicjob/config/sv_functions.luasky_mechanicjob/fxmanifest.luasky_mechanicjob/source/*— delete and reinsert
Update InstructionsA guided update for server owners1.34.01.34.1
Guided update
Update sky_mechanicjob safely
Complete the five phases in order. Your progress is kept while this page remains open.
Phase 1 of 5
Prepare
These instructions cover sky_mechanicjob 1.34.0 → the refreshed 1.34.1 package. If you already installed the earlier 1.34.1 startup hotfix, download the latest 1.34.1 package again for these additions. The manifest version stays 1.34.1, so the version alone cannot distinguish the earlier package. Check that the new download includes Config.VehiclePrices and Config.ToggleFeatures.tuningFreeCamera in sky_mechanicjob/config/config.lua, then check the settings after installation.
Back up resources, the database, custom configuration, translations and saved configurator data. Validate the update on staging and stop the server for replacement. Install the latest refreshed Base 1.45.1, then Jobs Base 1.48.1, then Mechanic 1.34.1 and the matching updates for your installed consumers. Base supplies the Qbox first-save storage contract and Jobs Base supplies startup readiness plus the matching configurator fields and labels. An older package with the same .1 version may lack the new additions. Complete earlier release migrations first if upgrading from before 1.34.0.
Phase 2 of 5
Replace files
Use the complete refreshed 1.34.1 customer package. Delete the entire sky_mechanicjob/source/ folder and insert the new folder, then replace sky_mechanicjob/fxmanifest.lua from that same package. This replaces the runtime code and generated interface together; do not merge old generated interface files.
Merge the new sky_mechanicjob/config/config.lua entries and the updated purchase-price hook in sky_mechanicjob/config/sv_functions.lua as described below, preserving intentional custom values and hooks. Replace sky_mechanicjob/README.md and refresh the complete sky_mechanicjob/config/locales/ tree, merging custom translations. Keep your selected Base language.
The supplied dependency bootstrap remains editable for support and must match the new source and manifest. Keep the new source/dependencies.lua; do not restore an older custom bootstrap. There are no outside-source removals or renames in this Mechanic update.
Phase 3 of 5
Merge config
Keep your existing jobs, grades, workshop locations and saved overrides. Merge the changed values into sky_mechanicjob/config/config.lua; do not replace your custom configuration with all shipped defaults. Config.UseJobConfigurator remains true by default. When it is true, managed features and settings use the packaged defaults plus saved database overrides, so edit them in /jobconfig. Plaintext edits to those managed values do not change the effective saved configuration. When it is false, the Lua values below are authoritative. Startup settings such as Config.UseJobConfigurator and Config.AutoExecuteQuery remain plaintext settings. No configuration reset or re-import is required.
Vehicle purchase prices — old: no Config.VehiclePrices block; the supplied hook used ESX vehicles.model / vehicles.price or the QBCore/Qbox catalogue. New: add this complete block, with the default auto preserving those sources:
Config.VehiclePrices = {
source = "auto",
tableName = "vehicles",
modelColumn = "model",
priceColumn = "price",
hashColumn = "" -- Optional numeric model-hash column; leave empty to hash the spawn names.
}
In /jobconfig, open Mechanic Jobs → General → Database and set Vehicle price source, Vehicle price table, Spawn name column, Price column, and Model hash column (optional). This is one global catalogue for the resource's vehicle-value-based tuning calculations, not a separate mapping for each workshop. The saved setting keys and new defaults are vehiclePriceSource = "auto", vehiclePriceTable = "vehicles", vehiclePriceModelColumn = "model", vehiclePricePriceColumn = "price", and vehiclePriceHashColumn = "". Missing new keys inherit those defaults; saved non-default mappings remain authoritative. Save the intended values there and reopen the section after a restart to check them.
Choose auto / Automatic (framework) for the existing behaviour: ESX reads the configured SQL mapping, while QBCore and Qbox use their framework vehicle catalogue. Choose database / Database to read a custom SQL catalogue on any of those frameworks. Use the exact names of an existing table and columns. The model column must contain spawn names, the price column numeric purchase prices, and SQL table/column names must contain 1–64 letters, digits or underscores. Do not enter schema prefixes, spaces or SQL expressions. Leave the optional hash column empty to match model hashes from spawn names; an existing numeric hash column may contain signed or unsigned values, including numeric strings.
For an existing JG Dealerships catalogue that uses these exact column names, the complete example is:
Config.VehiclePrices = {
source = "database",
tableName = "dealership_vehicles",
modelColumn = "spawn_code",
priceColumn = "price",
hashColumn = "hashkey"
}
With /jobconfig enabled, enter the same five values in General → Database instead of relying on the Lua example. JG's model column is a display name; use spawn_code for the spawn name. The mapping only reads prices and does not create or change dealership tables. Missing or nonpositive prices use your existing Config.TuningCostProfile.fallbackVehicleValue or the applicable workshop/instant-tuning price profile's fallback. The shipped global fallback remains 50000; preserve intentional pricing overrides.
Merge sky_mechanicjob/config/sv_functions.lua from the same package. The supplied Config.Functions.GetVehiclePurchasePrice(model, modelHash, source) now reads Config.VehiclePrices; restoring an older supplied hook would bypass the mapping. If you deliberately use a custom resource API or complex query, preserve that custom hook and check its returned prices; the new SQL settings do not override custom hook code. Keep the supplied startup readiness line at the top of this file. For the standard mapping, this is the complete new hook:
Config.Functions.GetVehiclePurchasePrice = function(model, modelHash, source)
local modelName = tostring(model or "")
modelName = modelName:match("^%s*(.-)%s*$")
local normalizedHash = tonumber(modelHash)
if normalizedHash then
normalizedHash = normalizedHash % 4294967296
end
local function getSharedVehiclePrice(vehicles)
if modelName ~= "" then
local directVehicle = vehicles[modelName] or vehicles[modelName:lower()] or vehicles[modelName:upper()]
local directPrice = tonumber(directVehicle and directVehicle.price) or 0
if directPrice > 0 then
return math.floor(directPrice)
end
end
if not normalizedHash then
return nil
end
for catalogModel, vehicle in pairs(vehicles) do
local price = tonumber(vehicle.price) or 0
if price > 0 then
local vehicleModel = tostring(vehicle.model or vehicle.hash or catalogModel or "")
if vehicleModel ~= "" and (joaat(vehicleModel) % 4294967296 == normalizedHash
or (tonumber(vehicleModel) and tonumber(vehicleModel) % 4294967296 == normalizedHash)) then
return math.floor(price)
end
end
end
return nil
end
local price_source = Config.VehiclePrices or ConfigDefaults.VehiclePrices
if price_source.source ~= "auto" and price_source.source ~= "database" then
error("[sky_mechanicjob] Config.VehiclePrices.source must be auto or database")
end
if price_source.source == "database" or Sky.Config.framework == "esx" then
local identifiers = {}
for _, key in ipairs({ "tableName", "modelColumn", "priceColumn", "hashColumn" }) do
local identifier = tostring(price_source[key] or ""):match("^%s*(.-)%s*$")
if key ~= "hashColumn" or identifier ~= "" then
if #identifier == 0 or #identifier > 64 or not identifier:match("^[%w_]+$") then
error(("[sky_mechanicjob] Invalid Config.VehiclePrices.%s: use a SQL identifier of 1-64 letters, digits or underscores"):format(key))
end
identifiers[key] = "`" .. identifier .. "`"
end
end
if modelName ~= "" then
local rows = Sky.Query(("SELECT %s AS price FROM %s WHERE %s = ? LIMIT 1")
:format(identifiers.priceColumn, identifiers.tableName, identifiers.modelColumn), { modelName })
local price = tonumber(rows[1] and rows[1].price) or 0
if price > 0 then
return math.floor(price)
end
end
if not normalizedHash then
return nil
end
if identifiers.hashColumn then
local signed_hash = normalizedHash >= 2147483648 and normalizedHash - 4294967296 or normalizedHash
local rows = Sky.Query(("SELECT %s AS price FROM %s WHERE %s IN (?, ?) LIMIT 1")
:format(identifiers.priceColumn, identifiers.tableName, identifiers.hashColumn),
{ ("%.0f"):format(signed_hash), ("%.0f"):format(normalizedHash) })
local price = tonumber(rows[1] and rows[1].price) or 0
return price > 0 and math.floor(price) or nil
end
local rows = Sky.Query(("SELECT %s AS model, %s AS price FROM %s")
:format(identifiers.modelColumn, identifiers.priceColumn, identifiers.tableName), {})
for _, row in ipairs(rows) do
local rowModel = tostring(row.model or "")
local price = tonumber(row.price) or 0
if rowModel ~= "" and price > 0 and joaat(rowModel) % 4294967296 == normalizedHash then
return math.floor(price)
end
end
return nil
end
if Sky.Config.framework == "qbox" then
return getSharedVehiclePrice(exports.qbx_core:GetVehiclesByName())
end
local qb = exports["qb-core"]:GetCoreObject()
return getSharedVehiclePrice(qb.Shared.Vehicles)
end
Free camera — old: Config.ToggleFeatures.tuningFreeCamera was absent and free-camera entry was not separately configurable. New: add tuningFreeCamera = true alongside the existing tuningCamera = true. tuningCamera still controls automatic focusing of the selected tuning part; its shipped default is unchanged. tuningFreeCamera controls hiding the cursor for manual camera movement, independently of the automatic camera. This complete shipped block is a reference: add the new entry and preserve your existing choices for all other entries:
Config.ToggleFeatures = {
kuv = false,
vehicleRegistration = false,
stance = true,
tuningCamera = true, -- Automatically focus the selected tuning part. Also managed in /jobconfig.
tuningFreeCamera = true, -- Allow Space to hide the tuning cursor for free camera movement. Also managed in /jobconfig.
instantTuning = false,
partsDelivery = true,
carryItems = false,
nitro = true,
antiLag = true,
twoStep = true,
electronicTuning = true,
wheelDamage = true,
customHandling = true,
handlingEditor = true, -- Admin editor and its automatic plate/model overrides.
mileageHud = true,
-- Requires jg-vehiclemileage v2.1.1+ to be started first. Existing Sky mileage is not migrated to JG.
jgMileage = false,
workshopLift = true,
mobileDyno = true,
brakeOverheating = true,
selfServiceOrderNotifications = true,
vehicleMaintenanceNotifications = true,
subscriptions = true
}
In /jobconfig, open Mechanic Jobs → Features → Free camera in tuning menu. Its saved feature key is tuningFreeCamera, with default true; a saved false remains disabled after restart. Keep or adjust Automatic tuning camera separately (tuningCamera, unchanged default true). With free camera enabled, Space releases the tuning cursor and pressing it again restores the cursor; Escape still closes the menu. With it disabled, Space does not enter free camera. Disabling it through /jobconfig during an unfocused tuning session restores the cursor. The existing focus-key default remains Config.Keybinds.tuningFocusKey = "SPACE"; retain player key bindings and existing hotkey overrides.
Update the matching Jobs Base 1.48.1 package and its complete sky_jobs_base/config/locales/jobconfigurator/sky_mechanicjob/ tree for the new price fields, General → Hotkeys/Database labels and free-camera feature label. Refresh Mechanic's own complete locale tree and merge custom translations. Keep the selected language in sky_base/config/config.lua. The configurator labels are owned by Jobs Base.
The existing Parts Delivery Shop → Delivery handling selector is restored by Jobs Base 1.48.1. Its setting remains partsDeliveryMode; without /jobconfig, retain Config.PartsDelivery.deliveryMode in sky_mechanicjob/config/config.lua ("standard" or "player_inventory"). The shipped default remains "standard". Keep saved overrides.
Optional diagnostics remain disabled by default. If support requests a recording, put setr sky_profiler 1 before the Sky ensure lines in server.cfg, restart the server, run profiler record 1000, wait for profiler status to show completion, then run profiler saveJSON sky-startup-hotfix.json. Disable with setr sky_profiler 0 and another full server restart. This is a plaintext server convar, not a /jobconfig setting; no recording or upload starts automatically.
Phase 4 of 5
SQL & items
SQL: No new table, column or SQL import is introduced by this Mechanic update. Keep your existing Config.AutoExecuteQuery policy. Repair the original database error if an earlier migration failed; continuing startup does not create missing schema. Do not replay a full import, clear tuning tables, force success markers or reset saved jobs/configurator data.
The vehicle-price mapping reads an existing catalogue; it does not create a dealership table, alter its schema or change prices. Check the table and columns before choosing Database. JG Dealerships is optional and is only needed if you choose its existing catalogue or custom hook.
Qbox: Update the refreshed Base 1.45.1 together with Mechanic 1.34.1, keeping your existing Qbox qbx_core and qbx_vehicles setup. An already-owned vehicle with missing or empty properties can receive its first complete tuning snapshot through qbx_vehicles; later saves continue preserving stored fuel, damage and other fields. This does not create an ownership record, repair malformed nonempty property data, or require a framework patch or manual SQL repair. Do not empty an existing vehicle's saved properties to trigger initialization. If you customized sky_base/config/vehicle_storage.lua, merge its Qbox initialization-marker update while preserving your garage schema mapping.
Items: No new item definitions, inventory registrations or item migration are introduced. Assets: No new item images, models, audio or stream assets are introduced. Keep the existing inventory and asset setup. The new frontend files are supplied inside the replaced source/ folder.
Progress providers: Keep your selected provider configuration. If you use ZSX_UIV2, install the matching Base 1.45.1 provider files and follow Base's provider setup; ensure the provider starts before Base. ZSX supports player cancellation; no programmatic stop export is assumed.
Phase 5 of 5
Restart & test
Perform a full server restart after replacement so changed manifests and file lists reload. Start the framework, database connector and selected providers first, then Base 1.45.1, Jobs Base 1.48.1, Mechanic 1.34.1 and other installed consumers. Reconnect clients. The new changes keep the existing startup hotfix: Mechanic waits for Jobs Base initialization instead of aborting after 60 seconds. SQL migration warnings remain actionable and failure to read saved settings remains a visible initialization error.
Open the Mechanic tablet at your existing workshop. With /jobconfig, confirm the General → Database values, the Free camera in tuning menu feature, and Parts Delivery Shop → Delivery handling. Save a deliberate non-default price mapping or free-camera choice, reopen the section and restart once to verify that choice persists; do not reset unrelated overrides.
For vehicle-value-based pricing, use a vehicle with a known positive purchase price in your selected catalogue and check its tuning quote. With an optional hash column, test a vehicle whose catalogue stores its hash in the format you use. Also check a missing-price vehicle against the intended fallback. Fixed-price or otherwise overridden tuning options may not change when the purchase catalogue changes.
In the tuning menu, test Space to hide and restore the cursor and Escape to close, with Automatic tuning camera both enabled and disabled. Then disable Free camera in tuning menu and confirm that entry is blocked; if you disable it during an unfocused session, confirm that the cursor returns. Test your existing rebound focus key too.
On Qbox, use an already-owned staging vehicle with naturally missing or empty saved properties. Complete one permitted tuning install, store and retrieve it through your configured garage, and check the installed tuning, plate/model, fuel and damage. Repeat a save with an existing complete record and check that stored fuel, damage and other vehicle data remain intact. Source/contract tests do not establish the customer's paid install or garage round trip.
With two clients or a staging streaming-range test, check a spawned tuned vehicle, Dyno updates, remote AntiLag/TwoStep effects, catalytic smoke, wheel repair and engine-sound previews both nearby and outside streaming range. Check that no new network-object warnings occur and that nearby effects and restoration still work. Test lift attachment while in range, moving out of range during loading, and detachment; this change does not create a missing garage/lift vehicle association.
Complete and cancel one non-minigame install/repair using your selected progress provider; include ZSX_UIV2 if configured. Do not assume a provider has a programmatic stop export when it does not document one. If a feature remains unavailable, collect the first initialization/SQL error, preceding lines and actual package versions. Customer/client/provider acceptance remains a check to perform on your server.