Hello everyone,
I'm currently working on an engine tuning system for vehicles in MTA and I'm running into an issue with setVehicleHandling that I haven't been able to solve despite many attempts. My goal is for vehicles to get faster with each tuning level (1-3).
Problem Description:
The function setVehicleHandling(theVehicle, handlingTable) consistently returns false for me, and the vehicle retains its original handling. This means the KM/H values do not change, even though I'm trying to modify maxVelocity, engineAcceleration, and driveForce.
What I've Tried So Far:
I'm fetching the original handling using getOriginalHandling(modelID).
I'm creating a new handling table, carrying over most of the original values.
I'm modifying maxVelocity, engineAcceleration, and driveForce based on the tuning level using multipliers > 1.0 (e.g., Level 1 = 1.10x, Level 2 = 1.20x, Level 3 = 1.30x).
I'm converting values like driveType and engineType to their (presumably) expected numerical equivalents for the table overload of setVehicleHandling (e.g., "awd" to 2, "petrol" to 0).
headLightType and tailLightType are passed as numbers.
Vector values like centerOfMass and inertiaTensor are passed as tables {x=..., y=..., z=...}.
The final table is filtered to only include properties supported by setVehicleHandling.
Relevant Log Output of the finalHandling table (Infernus, Model 411, Engine Level 3 - Target: faster): (Assuming base values: baseMaxVelocity=240, baseEngineAcceleration=12, baseDriveForce=0.3 for Infernus)
[HH:MM:SS] INFO: [VehiclesServer DEBUG V1.32] Finales Handling für Modell 411, Level 3: {
ABS = false,
brakeBias = 0.50999999046326,
brakeDeceleration = 11,
centerOfMass = { x = 0, y = 0, z = -0.25 },
collisionDamageMultiplier = 0.72000002861023,
dragCoeff = 1.5,
driveForce = 0.345, -- (0.3 * 1.15)
driveType = 2,
engineAcceleration = 15.6, -- (12 * 1.30)
engineInertia = 10,
engineType = 0,
handlingFlags = 12599296,
headLightType = 0,
inertiaTensor = { x = 0.1, y = 0.1, z = 0.1 },
mass = 1400,
maxVelocity = 312, -- (240 * 1.30)
modelFlags = 1073750020,
numberOfGears = 5,
percentSubmerged = 70,
seatOffsetDistance = 0.37000000476837,
steeringLock = 30,
suspensionAntiDiveMultiplier = 0.40000000596046,
suspensionBiasBetweenFrontAndRear = 0.5,
suspensionDampingLevel = 0.18999999761581,
suspensionForceLevel = 1.2000000476837,
suspensionHighSpeedDamping = 0,
suspensionLowerLimit = -0.10000000149012,
suspensionUpperLimit = 0.25,
tailLightType = 0,
tractionBias = 0.5,
tractionLoss = 0.80000001192093,
tractionMultiplier = 0.69999998807907,
turnMass = 2725.3000488281
}
[HH:MM:SS] INFO: [applyUpgradesOnSpawn] setVehicleHandling FEHLGESCHLAGEN für Lvl 3, Fahrzeug ID 7 (Modell: 411).
[HH:MM:SS] INFO: [applyUpgradesOnSpawn] Aktuelles Handling NACH Fehlversuch: { ... } -- (Das Original-Handling)
My getModifiedHandlingForSpawn function (Version 1.32): (It's probably best to provide the full function here, including the helper functions, for complete context in the English forum as well.)
-- Helper functions (getNumericValue_vehicles, isVectorTable_vehicles, createCleanVector_vehicles, typeConverters_vehicles)
-- These perform standard type conversions and vector cleaning.
-- (You can briefly describe them or paste their code here if concise enough)
local function getNumericValue_vehicles(val)
if type(val) == "number" then return val end
if type(val) == "string" then
local num = tonumber(val)
if num ~= nil then return num end
end
return nil
end
local function isVectorTable_vehicles(tbl)
if type(tbl) ~= "table" then return false end
if getNumericValue_vehicles(tbl.x) ~= nil and getNumericValue_vehicles(tbl.y) ~= nil and getNumericValue_vehicles(tbl.z) ~= nil then
return true
end
if getNumericValue_vehicles(tbl[1]) ~= nil and getNumericValue_vehicles(tbl[2]) ~= nil and getNumericValue_vehicles(tbl[3]) ~= nil then
return true
end
return false
end
local function createCleanVector_vehicles(originalVector, defaultX, defaultY, defaultZ)
local defaultVec = { x = defaultX or 0, y = defaultY or 0, z = defaultZ or 0 }
if type(originalVector) ~= "table" then return defaultVec end
if isVectorTable_vehicles(originalVector) then
local x_val, y_val, z_val
if getNumericValue_vehicles(originalVector.x) ~= nil and getNumericValue_vehicles(originalVector.y) ~= nil and getNumericValue_vehicles(originalVector.z) ~= nil then
x_val = getNumericValue_vehicles(originalVector.x); y_val = getNumericValue_vehicles(originalVector.y); z_val = getNumericValue_vehicles(originalVector.z)
elseif getNumericValue_vehicles(originalVector[1]) ~= nil and getNumericValue_vehicles(originalVector[2]) ~= nil and getNumericValue_vehicles(originalVector[3]) ~= nil then
x_val = getNumericValue_vehicles(originalVector[1]); y_val = getNumericValue_vehicles(originalVector[2]); z_val = getNumericValue_vehicles(originalVector[3])
end
if x_val ~= nil and y_val ~= nil and z_val ~= nil then return { x = x_val, y = y_val, z = z_val } end
end
return defaultVec
end
local typeConverters_vehicles = {
engineType = function(val, default)
if type(val) == "string" then local s = string.lower(val); if s == "petrol" then return 0 elseif s == "diesel" then return 1 elseif s == "electric" then return 2 end end
local num_val = getNumericValue_vehicles(val); if num_val ~= nil and (num_val >= 0 and num_val <= 2) then return num_val end
return default
end,
driveType = function(val, default)
if type(val) == "string" then local s = string.lower(val); if s == "fwd" then return 0 elseif s == "rwd" then return 1 elseif s == "awd" or s == "4wd" then return 2 end end
local num_val = getNumericValue_vehicles(val); if num_val ~= nil and (num_val >= 0 and num_val <= 2) then return num_val end
return default
end,
headLightType = function(val, default)
if type(val) == "string" then local s = string.lower(val); if s == "small" then return 0 elseif s == "long" then return 1 elseif s == "big" then return 2 elseif s == "tall" then return 3 end end
local num_val = getNumericValue_vehicles(val); if num_val ~= nil and (num_val >= 0 and num_val <= 3) then return num_val end
return default or 0
end,
tailLightType = function(val, default)
if type(val) == "string" then local s = string.lower(val); if s == "small" then return 0 elseif s == "long" then return 1 elseif s == "big" then return 2 elseif s == "tall" then return 3 end end
local num_val = getNumericValue_vehicles(val); if num_val ~= nil and (num_val >= 0 and num_val <= 3) then return num_val end
return default or 0
end,
ABS = function(val, default)
if type(val) == "boolean" then return val end
if type(val) == "string" then local s = string.lower(val); if s == "true" then return true elseif s == "false" then return false end end
local num_val = getNumericValue_vehicles(val); if num_val ~= nil and (num_val == 0 or num_val == 1) then return num_val == 1 end
return default or false
end
}
function getModifiedHandlingForSpawn(theVehicle, engineLevel)
if not isElement(theVehicle) or getElementType(theVehicle) ~= "vehicle" then
outputDebugString("[VehiclesServer|getModifiedHandlingForSpawn] Error: 'theVehicle' is not a valid vehicle element.")
return false
end
if not engineLevel or type(engineLevel) ~= "number" or engineLevel < 0 or engineLevel > 3 then
outputDebugString("[VehiclesServer|getModifiedHandlingForSpawn] Error: Invalid 'engineLevel'.")
return false
end
local vehicleModelID = getElementModel(theVehicle)
local success, originalHandling_raw = pcall(getOriginalHandling, vehicleModelID)
if not success or type(originalHandling_raw) ~= "table" then
outputDebugString(string.format("[VehiclesServer|getModifiedHandlingForSpawn] pcall(getOriginalHandling) for model %d FAILED. Error/Type: %s", vehicleModelID, tostring(originalHandling_raw)))
return false
end
local oh = originalHandling_raw
local finalHandling = {}
local transData = oh.transmissionData or oh
finalHandling.mass = getNumericValue_vehicles(oh.mass) or 1700.0
finalHandling.turnMass = getNumericValue_vehicles(oh.turnMass) or 5000.0
finalHandling.dragCoeff = getNumericValue_vehicles(oh.dragCoeff) or 2.5
finalHandling.centerOfMass = createCleanVector_vehicles(oh.centerOfMass or oh.vecCentreOfMass, 0, 0, -0.1)
finalHandling.percentSubmerged = getNumericValue_vehicles(oh.percentSubmerged) or 85
finalHandling.tractionMultiplier = getNumericValue_vehicles(oh.tractionMultiplier) or 0.75
finalHandling.tractionLoss = getNumericValue_vehicles(oh.tractionLoss) or 0.85
finalHandling.tractionBias = getNumericValue_vehicles(oh.tractionBias or oh.driveBias) or 0.5
finalHandling.numberOfGears = getNumericValue_vehicles(transData.maxGear or transData.numberOfGears) or 4
finalHandling.maxVelocity = getNumericValue_vehicles(transData.maxVelocity) or 200.0
finalHandling.engineAcceleration = getNumericValue_vehicles(transData.engineAcceleration) or 10.0
finalHandling.engineInertia = getNumericValue_vehicles(oh.engineInertia or (transData and transData.engineInertia)) or 5.0
finalHandling.driveType = typeConverters_vehicles.driveType(oh.driveType or (transData and transData.driveType), 1)
finalHandling.engineType = typeConverters_vehicles.engineType(oh.engineType or (transData and transData.engineType), 0)
finalHandling.brakeDeceleration = getNumericValue_vehicles(oh.brakeDeceleration) or 10.0
finalHandling.brakeBias = getNumericValue_vehicles(oh.brakeBias) or 0.5
finalHandling.ABS = typeConverters_vehicles.ABS(oh.ABS, (oh.ABS ~= nil and typeConverters_vehicles.ABS(oh.ABS)) or false)
finalHandling.steeringLock = getNumericValue_vehicles(oh.steeringLock) or 35.0
finalHandling.suspensionForceLevel = getNumericValue_vehicles(oh.suspensionForceLevel) or 1.0
finalHandling.suspensionDampingLevel = getNumericValue_vehicles(oh.suspensionDampingLevel or oh.suspensionDamping) or 0.1
finalHandling.suspensionHighSpeedDamping = getNumericValue_vehicles(oh.suspensionHighSpeedDamping) or 0.0
finalHandling.suspensionUpperLimit = getNumericValue_vehicles(oh.suspensionUpperLimit) or 0.35
finalHandling.suspensionLowerLimit = getNumericValue_vehicles(oh.suspensionLowerLimit) or -0.15
finalHandling.suspensionBiasBetweenFrontAndRear = getNumericValue_vehicles(oh.suspensionBiasBetweenFrontAndRear or oh.suspensionFrontRearBias) or 0.5
finalHandling.suspensionAntiDiveMultiplier = getNumericValue_vehicles(oh.suspensionAntiDiveMultiplier) or 0.3
finalHandling.seatOffsetDistance = getNumericValue_vehicles(oh.seatOffsetDistance) or 0.0
finalHandling.collisionDamageMultiplier = getNumericValue_vehicles(oh.collisionDamageMultiplier) or 1.0
finalHandling.modelFlags = (type(oh.modelFlags) == "string" and string.sub(oh.modelFlags, 1, 2) == "0x" and tonumber(oh.modelFlags, 16)) or getNumericValue_vehicles(oh.modelFlags) or 0
finalHandling.handlingFlags = (type(oh.handlingFlags) == "string" and string.sub(oh.handlingFlags, 1, 2) == "0x" and tonumber(oh.handlingFlags, 16)) or getNumericValue_vehicles(oh.handlingFlags) or 0
finalHandling.headLightType = typeConverters_vehicles.headLightType(oh.headLight or oh.headLightType, (oh.headLight and typeConverters_vehicles.headLightType(oh.headLight)) or 0)
finalHandling.tailLightType = typeConverters_vehicles.tailLightType(oh.tailLight or oh.tailLightType, (oh.tailLight and typeConverters_vehicles.tailLightType(oh.tailLight)) or 0)
finalHandling.inertiaTensor = createCleanVector_vehicles(oh.inertiaTensor or oh.vecInertia, 0.1, 0.1, 0.1)
local baseDriveForce = getNumericValue_vehicles(oh.driveForce)
if not baseDriveForce or baseDriveForce <= 0.001 then
baseDriveForce = (vehicleModelID == 451 and 0.30) or (vehicleModelID == 411 and 0.35) or (vehicleModelID == 560 and 0.25) or 0.20
end
finalHandling.driveForce = baseDriveForce
-- Modifications for engineLevel (to make vehicle FASTER)
if engineLevel > 0 then
local velocityMultiplier, accelerationMultiplier, driveForceMultiplier = 1.0, 1.0, 1.0
if engineLevel == 1 then
velocityMultiplier = 1.10; accelerationMultiplier = 1.10; driveForceMultiplier = 1.05
elseif engineLevel == 2 then
velocityMultiplier = 1.20; accelerationMultiplier = 1.20; driveForceMultiplier = 1.10
elseif engineLevel == 3 then
velocityMultiplier = 1.30; accelerationMultiplier = 1.30; driveForceMultiplier = 1.15
end
finalHandling.maxVelocity = (getNumericValue_vehicles(transData.maxVelocity) or 200.0) * velocityMultiplier
finalHandling.engineAcceleration = (getNumericValue_vehicles(transData.engineAcceleration) or 10.0) * accelerationMultiplier
finalHandling.driveForce = baseDriveForce * driveForceMultiplier
end
outputDebugString("[VehiclesServer DEBUG V1.32] Final handling for model "..vehicleModelID..", Level "..engineLevel..": " .. inspect(finalHandling))
return finalHandling
end
Question for the Community:
Despite ensuring that driveType (e.g., 2 for AWD) and engineType (e.g., 0 for Petrol) are numerical, and headLightType/tailLightType are also numerical in the table passed to setVehicleHandling(theVehicle, handlingTable), the function still returns false. The other values in the table appear plausible and are based on getOriginalHandling. The multipliers for engineAcceleration, maxVelocity, and driveForce are now > 1.0, aiming to make the vehicle faster.
Does anyone have an idea why setVehicleHandling might still be failing with the finalHandling table shown in the logs above? Could there be an issue with min/max value violations for other properties, or a missing critical property that setVehicleHandling (table overload) requires? The test vehicle is an Infernus (model 411).
I've also tried sending a very minimal table with only the modified values (engineAcceleration, maxVelocity, driveForce) and some core values (mass, numberOfGears, driveType, engineType), which also failed.
Any help or insights would be greatly appreciated!