------------------------------------------ -- iEnsomatic RealisticVehicleFailure -- ------------------------------------------ -- -- Created by Jens Sandalgaard -- -- This work is licensed under a Creative Commons Attribution-ShareAlike 4.0 International License. -- -- https://github.com/iEns/RealisticVehicleFailure -- local kmMultiplier = 5.0 ---@type table local materials = {} Citizen.CreateThread(function() xpcall(function() kmMultiplier = tonumber(GetConvar("kmh_multiplier", "5.0")) or 5.0 end, Traceback or debug.traceback) while ESX.IsPlayerLoaded() ~= true do Citizen.Wait(500) end ESX.PlayerData = ESX.GetPlayerData() end) DecorRegister('_ODO_METER', 1) local pedInSameVehicleLast = false local currentVehicle local numWheels = 0 local lastVehicle local vehicleClass local vehicleModel local fCollisionDamageMult = 0.0 local fDeformationDamageMult = 0.0 local fEngineDamageMult = 0.0 local fBrakeForce = 1.0 local isBrakingForward = false local isBrakingReverse = false local healthEngineLast = 1000.0 local healthEngineCurrent = 1000.0 local healthEngineNew = 1000.0 local healthEngineDelta = 0.0 local healthEngineDeltaScaled = 0.0 local healthBodyLast = 1000.0 local healthBodyCurrent = 1000.0 local healthBodyNew = 1000.0 local healthBodyDelta = 0.0 local healthBodyDeltaScaled = 0.0 local healthPetrolTankLast = 1000.0 local healthPetrolTankCurrent = 1000.0 local healthPetrolTankNew = 1000.0 local healthPetrolTankDelta = 0.0 local healthPetrolTankDeltaScaled = 0.0 local tireBurstLuckyNumber local lastMaterial = nil local gripModifier = 1.0 local debugMode = false local defaultExponent = 0.5 math.randomseed(GetCloudTimeAsInt()); math.random() local tireBurstMaxNumber = cfg.randomTireBurstInterval * 60; -- the tire burst lottery runs roughly 1200 times per minute if cfg.randomTireBurstInterval ~= 0 then tireBurstLuckyNumber = math.random(cfg.randomTireBurstInterval * 60) end -- If we hit this number again randomly, a tire will burst. local originalMin, originalMax, originalMass, originalBrakeForce, currentMin, currentMax, currentMass, currentBrakeForce AddEventHandler('baseevents:enteredVehicle', function(vehicle, seat, displayName, netId, class) if seat == -1 then EnteredVehicle(vehicle) end end) AddEventHandler('baseevents:changedSeat', function(_vehicle, seat, displayName, netId, model, oldSeat) if seat == -1 then EnteredVehicle(_vehicle) end if oldSeat == -1 then currentVehicle = nil Citizen.Wait(0) LeftVehicle(_vehicle) end end) AddEventHandler("baseevents:leftVehicle", function(_vehicle, seat, displayName, netId) if seat == -1 then currentVehicle = nil Citizen.Wait(0) LeftVehicle(_vehicle) end end) function VerifyHandlingFloat(number) return number and number > 0.1 end local cachedHandling = {} --- Caches the best value and returns the best value, so the highest of the two --- @param model Hash The vehicle model to check --- @param fieldName string The handling fieldName to check --- @param handlingFloat number The handling float from GetVehicleHandlingFloat function CacheAndGetLowestValue(model, fieldName, handlingFloat) if not cachedHandling[model] then cachedHandling[model] = {} end if not handlingFloat then handlingFloat = 1.0 end if not cachedHandling[model][fieldName] or handlingFloat < cachedHandling[model][fieldName] then cachedHandling[model][fieldName] = handlingFloat end return cachedHandling[model][fieldName] end --- Caches the best value and returns the best value, so the highest of the two --- @param model Hash The vehicle model to check --- @param fieldName string The handling fieldName to check --- @param handlingFloat number The handling float from GetVehicleHandlingFloat function CacheAndGetHighestValue(model, fieldName, handlingFloat) if not cachedHandling[model] then cachedHandling[model] = {} end if not handlingFloat then handlingFloat = 1.0 end if not cachedHandling[model][fieldName] or handlingFloat > cachedHandling[model][fieldName] then cachedHandling[model][fieldName] = handlingFloat end return cachedHandling[model][fieldName] end function GetLowestHandlingFloat(vehicle, fieldName) local model = GetEntityModel(vehicle) local handlingFloat = GetVehicleHandlingFloat(vehicle, "CHandlingData", fieldName) return CacheAndGetLowestValue(model, fieldName, handlingFloat) end function GetHighestHandlingFloat(vehicle, fieldName) local model = GetEntityModel(vehicle) local handlingFloat = GetVehicleHandlingFloat(vehicle, "CHandlingData", fieldName) return CacheAndGetHighestValue(model, fieldName, handlingFloat) end function EnteredVehicle(_vehicle) if not DoesEntityExist(_vehicle) then return end if _vehicle ~= GetVehiclePedIsUsing(PlayerPedId()) then --print("^1ERROR: ^7Vehicle and GetVehiclePedIsUsing not same!") _vehicle = GetVehiclePedIsUsing(PlayerPedId()) end originalMax = originalMax or GetHighestHandlingFloat(_vehicle, "fTractionCurveMax") originalMin = originalMin or GetHighestHandlingFloat(_vehicle, "fTractionCurveMin") originalMass = originalMass or GetHighestHandlingFloat(_vehicle, "fMass") originalBrakeForce = originalBrakeForce or GetHighestHandlingFloat(_vehicle, "fBrakeForce") or 1.0 currentMin, currentMax = originalMin, originalMax vehicleClass = GetVehicleClass(_vehicle) currentVehicle = _vehicle numWheels = GetVehicleNumberOfWheels(_vehicle) if numWheels == _vehicle or numWheels > 40 then numWheels = 0 end end local isStuck = false function LeftVehicle(_vehicle) currentVehicle = nil numWheels = 0 if not DoesEntityExist(_vehicle) then fBrakeForce = originalBrakeForce or 1.0 originalMin, currentMin = nil, nil originalMax, currentMax = nil, nil originalMass, currentMass = nil, nil originalBrakeForce, currentBrakeForce = nil, nil gripModifier = 1.0 vehicleClass = nil return end ModifyGrip(_vehicle, 1.0) ModifyTorque(_vehicle, 1.0) if originalMin and originalMin > 0.0 then SetVehicleHandlingFloat(_vehicle, "CHandlingData", "fTractionCurveMin", originalMin) end if originalMax and originalMax > 0.0 then SetVehicleHandlingFloat(_vehicle, "CHandlingData", "fTractionCurveMax", originalMax) end if originalMass and originalMass > 0.0 then SetVehicleHandlingFloat(_vehicle, "CHandlingData", "fMass", originalMass) end if originalBrakeForce and originalBrakeForce > 0.0 then SetVehicleHandlingFloat(_vehicle, "CHandlingData", "fBrakeForce", originalBrakeForce) end SetVehicleEngineTorqueMultiplier(_vehicle, 1.0) if isStuck then SetVehicleBurnout(_vehicle, false) isStuck = false end fBrakeForce = originalBrakeForce or 1.0 originalMin, currentMin = nil, nil originalMax, currentMax = nil, nil originalMass, currentMass = nil, nil originalBrakeForce, currentBrakeForce = nil, nil gripModifier = 1.0 vehicleClass = nil _vehicle = 0 end local currentModifier, lastModifiedVehicle function ModifyGrip(vehicle, modifier) if not DoesEntityExist(currentVehicle) then return end if currentModifier == modifier and vehicle == lastModifiedVehicle then return end lastModifiedVehicle = vehicle currentModifier = modifier if originalMin then currentMin = originalMin * modifier SetVehicleHandlingFloat(vehicle, "CHandlingData", "fTractionCurveMin", currentMin) end if originalMax then currentMax = originalMax * modifier SetVehicleHandlingFloat(vehicle, "CHandlingData", "fTractionCurveMax", currentMax) end if originalMass and originalMass < 1000 then currentMass = originalMass / modifier SetVehicleHandlingFloat(vehicle, "CHandlingData", "fMass", currentMass) end -- if originalBrakeForce then -- currentBrakeForce = originalBrakeForce * modifier -- fBrakeForce = currentBrakeForce -- SetVehicleHandlingFloat(vehicle, "CHandlingData", "fBrakeForce", currentBrakeForce) -- end if modifier == 1 then currentMin = nil currentMax = nil currentBrakeForce = nil currentMass = nil fBrakeForce = originalBrakeForce end end function ModifyTorque(vehicle, torqueFactor) SetVehicleEngineTorqueMultiplier(vehicle, torqueFactor) if originalBrakeForce then currentBrakeForce = originalBrakeForce * torqueFactor fBrakeForce = currentBrakeForce SetVehicleHandlingFloat(currentVehicle, "CHandlingData", "fBrakeForce", currentBrakeForce) end end -- AddEventHandler("baseevents:changedSeat", function(currentVehicle, currentSeat, displayName, netId, model, oldSeat) -- end) local function notification(msg) SetNotificationTextEntry("STRING") AddTextComponentString(msg) DrawNotification(false, false) end local function isPedDrivingAVehicle() local ped = PlayerPedId() local vehicle = GetVehiclePedIsIn(ped, false) if IsPedInAnyVehicle(ped, false) then -- Check if ped is in driver seat if GetPedInVehicleSeat(vehicle, -1) == ped then return true end end return false end local function fscale(inputValue, originalMin, originalMax, newBegin, newEnd, curve) local OriginalRange = 0.0 local NewRange = 0.0 local zeroRefCurVal = 0.0 local normalizedCurVal = 0.0 local rangedValue = 0.0 local invFlag = 0 if (curve > 10.0) then curve = 10.0 end if (curve < -10.0) then curve = -10.0 end curve = (curve * -.1) curve = 10.0 ^ curve if (inputValue < originalMin) then inputValue = originalMin end if inputValue > originalMax then inputValue = originalMax end OriginalRange = originalMax - originalMin if (newEnd > newBegin) then NewRange = newEnd - newBegin else NewRange = newBegin - newEnd invFlag = 1 end zeroRefCurVal = inputValue - originalMin normalizedCurVal = zeroRefCurVal / OriginalRange if (originalMin > originalMax ) then return 0 end if (invFlag == 0) then rangedValue = ((normalizedCurVal ^ curve) * NewRange) + newBegin else rangedValue = newBegin - ((normalizedCurVal ^ curve) * NewRange) end return rangedValue end local lastPlate local lastCoords local odometer local ownedVehicle local lastSync = 0 local wheelConversion = { [0] = 0, [1] = 1, [2] = 4, [3] = 5, [4] = 2, [5] = 3 } local currentLimit = nil function ResetLimit(currentVehicle) currentLimit = nil return exports["esx_cruisecontrol"]:resetLimit(currentVehicle) end function SetLimit(currentVehicle, speed) currentLimit = speed return exports["esx_cruisecontrol"]:setLimit(currentVehicle, speed) end function GetMaterial(source, args, raw) local vehicle = GetVehiclePedIsIn(PlayerPedId()) local materialIndex = GetVehicleWheelSurfaceMaterial(vehicle, 0) local material = materials[materialIndex] if not material then --print("Geen materiaal gevonden onder linker voorwiel!") return end local message = ("materialHash: %s; material: %s; materialIndex: %s;"):format(material.hash, material.name, materialIndex) --print(message) --print("rainLevel: ", GetRainLevel()) return message end function BurstTyre(source, args, raw) if args[1] == 'misstake' then local vehicle = GetVehiclePedIsIn(PlayerPedId()) local numWheels = GetVehicleNumberOfWheels(vehicle) -- We won the lottery, lets burst a tire. if GetVehicleTyresCanBurst(vehicle) == false then return end local affectedTire if numWheels == 2 then affectedTire = (math.random(2)-1)*4 -- wheel 0 or 4 elseif numWheels == 4 then affectedTire = (math.random(4)-1) if affectedTire > 1 then affectedTire = affectedTire + 2 end -- 0, 1, 4, 5 elseif numWheels == 6 then affectedTire = (math.random(6)-1) else affectedTire = 0 end SetVehicleTyreBurst(vehicle, affectedTire, false, 1000.0) end end exports('odometer', function() return odometer end) function UpdateOdometer(vehicle, odometer) DecorSetFloat(vehicle, "_ODO_METER", odometer) TriggerEvent("vehiclefailure:odometer", vehicle, odometer) end local oldZ = 0 local zeroCoords = vector3(0,0,0) local function tireBurstLottery() Citizen.SetTimeout(1000, tireBurstLottery) local _vehicle = currentVehicle if _vehicle == nil then if not isPedDrivingAVehicle() then return else _vehicle = GetVehiclePedIsIn(PlayerPedId()) end end if not DoesEntityExist(_vehicle) then return end local coords = GetEntityCoords(_vehicle) local speed = GetEntitySpeed(_vehicle) local plate = ESX.Math.Trim(GetVehicleNumberPlateText(_vehicle)) if oldZ < -50.0 and coords.z > -1.0 then Citizen.CreateThread(function() for i=1, 2 do local roll = GetEntityRoll(_vehicle) if (roll > 50.0 or roll < -50.0) then SetEntityRotation(_vehicle, 0.0, 0.0, GetEntityHeading(_vehicle)) SetVehicleOnGroundProperly(_vehicle) end Citizen.Wait(500) end end) end oldZ = coords.z if coords.z < -75.0 and NetworkGetEntityIsNetworked(_vehicle) then local retval, roadCoords, roadCoords2 = GetClosestRoad(coords.x, coords.y, coords.z) if retval then local distance1 = #(coords - roadCoords) local distance2 = #(coords - roadCoords2) if distance1 < distance2 and distance1 < 300.0 then SetPedCoordsKeepVehicle(PlayerPedId(), roadCoords.x, roadCoords.y, roadCoords.z + 1.0) SetVehicleOnGroundProperly(_vehicle) elseif distance2 < distance1 and distance2 < 300.0 then SetPedCoordsKeepVehicle(PlayerPedId(), roadCoords2.x, roadCoords2.y, roadCoords2.z + 1.0) SetVehicleOnGroundProperly(_vehicle) end Citizen.Wait(100) Citizen.Wait(100) end end if odometer ~= nil and lastPlate == plate and lastCoords ~= nil and coords ~= nil and #(zeroCoords - coords) > 20.0 and #(zeroCoords - coords) > 20.0 then local distance = #(coords - lastCoords) if distance - speed > 10.0 then distance = speed end odometer = odometer + (distance / 1000) UpdateOdometer(_vehicle, odometer) end if GetGameTimer() - lastSync > 10000 and lastPlate == plate then lastSync = GetGameTimer() if not ownedVehicle then SetResourceKvpFloat(plate, odometer) end end if lastPlate ~= plate then odometer = nil ownedVehicle = false TriggerServerEvent("vehiclefailure:getodometer", plate) end lastPlate = plate lastCoords = coords if numWheels > 0 then local burst = false local wheelSpeed = speed for i=0, numWheels do local index = i if numWheels == 4 then index = (i + 4) % 6 end if numWheels == 2 then index = (i + 5) % 5 end wheelSpeed = math.max(math.abs(GetVehicleWheelSpeed(_vehicle, wheelConversion[index])), speed) if wheelSpeed > 10 and IsVehicleTyreBurst(_vehicle, index, false) then burst = true wheelSpeed = wheelSpeed - 7.5 healthEngineLast = healthEngineLast - (wheelSpeed ^ 1.2) end end if burst and math.abs(healthEngineLast - healthEngineCurrent) > 0.01 then SetVehicleEngineHealth(_vehicle, healthEngineLast + 0.0) end if speed > 10 and cfg.randomTireBurstInterval ~= 0 then local tireBurstNumber = math.random(tireBurstMaxNumber) if speed > 25 and tireBurstNumber ~= tireBurstLuckyNumber then tireBurstNumber = math.random(tireBurstMaxNumber) end if tireBurstNumber == tireBurstLuckyNumber then -- We won the lottery, lets burst a tire. if GetVehicleTyresCanBurst(_vehicle) == false then return end local affectedTire if numWheels == 2 then affectedTire = (math.random(2)-1)*4 -- wheel 0 or 4 elseif numWheels == 4 then affectedTire = (math.random(4)-1) if affectedTire > 1 then affectedTire = affectedTire + 2 end -- 0, 1, 4, 5 elseif numWheels == 6 then affectedTire = (math.random(6)-1) else affectedTire = 0 end SetVehicleTyreBurst(_vehicle, affectedTire, false, 1000.0) tireBurstLuckyNumber = math.random(tireBurstMaxNumber) -- Select a new number to hit, just in case some numbers occur more often than others end end end end RegisterNetEvent("vehiclefailure:getodometer") AddEventHandler("vehiclefailure:getodometer", function(distance) ownedVehicle = distance ~= nil local vehicle = GetVehiclePedIsIn(PlayerPedId()) local plate = ESX.Math.Trim(GetVehicleNumberPlateText(vehicle)) odometer = distance or (plate and GetResourceKvpFloat(plate) or 0.0) or 0.0 DecorSetFloat(vehicle, "_ODO_METER", odometer) end) local wheels = { "wheel_lr", "wheel_rf", "wheel_rr" } local offroadSnowModifier = 0.2 local classModifiers = { [9] = offroadSnowModifier, -- Offroad [10] = offroadSnowModifier, -- Industrial [11] = offroadSnowModifier, -- Utility [18] = offroadSnowModifier, [20] = offroadSnowModifier, -- Commercial (vrachtwagens) } local function getSnowModifier() local modifier = 0.60 local wheelType = GetVehicleWheelType(currentVehicle) if classModifiers[vehicleClass] then modifier = modifier + classModifiers[vehicleClass] end if wheelType == 4 then -- Adjust modifier for vehicles with offroad tyres modifier = modifier + 0.15 end return math.min(modifier, 1.0) end local debugInfo = {} local running = false function DrawThread() if running then return end running = true Citizen.CreateThread(function() while debugMode do for k,v in pairs(debugInfo) do SetTextFont(0) SetTextProportional(1) SetTextScale(0.0, 0.30) SetTextDropshadow(0, 0, 0, 0, 255) SetTextEdge(1, 0, 0, 0, 255) SetTextDropShadow() SetTextOutline() SetTextEntry("STRING") AddTextComponentString(v) EndTextCommandDisplayText(0.3, 0.7+(k/30)) end Citizen.Wait(0) end running = false end) end local function drawInfo(text, i) debugInfo[i] = text end local bikeModels = { [`bf400`] = true, [`policeb`] = true, [`kmarb`] = true, } local function isMotorCycle() if vehicleClass == 8 or bikeModels[vehicleModel] then return true else return false end end --- Gets the modifiers of the surface the tyres of the current vehicle are on --- @return number torqueModifier --- @return number modifier --- @return number maxSpeed --- @return number step --- @return table material --- @return integer materialIndex local function getMaterialModifiersFromTyres() local materialIndex local material local torqueModifier, modifier, maxSpeed, step local gripModifiersApplied, torqueModifiersApplied = 0, 0 local tractionLossExponent = nil local numFound = 0 local stuck = 0 local stepsApplied = 0 if numWheels > 0 then for i=0, numWheels - 1 do local _materialIndex = GetVehicleWheelSurfaceMaterial(currentVehicle, i) local _material = materials[_materialIndex] if _material and _material.name ~= "unk" then materialIndex = _materialIndex material = _material if _material.modifier then gripModifiersApplied = gripModifiersApplied + 1 modifier = (modifier or 0) + _material.modifier end if _material.torqueModifier then torqueModifiersApplied = torqueModifiersApplied + 1 torqueModifier = (torqueModifier or 0) + _material.torqueModifier end if _material.maxSpeed then maxSpeed = math.min(maxSpeed or 10000, _material.maxSpeed) end if _material.step then stepsApplied = stepsApplied + 1 step = (step or 0) + _material.step end if debugMode then drawInfo(("%s: %s %.2f %.2f"):format(i, _material.name, _material.modifier or -1.0, GetVehicleWheelTractionVectorLength(currentVehicle, i) or -1.0), i) DrawThread() end if _material.stuck then stuck = stuck + 1 end numFound = numFound + 1 tractionLossExponent = (tractionLossExponent or 0) + (_material.tractionLossExponent or defaultExponent) end end if stuck == numWheels then stuck = true else stuck = false end if step and stepsApplied then step = step / stepsApplied end if tractionLossExponent then tractionLossExponent = tractionLossExponent / numFound else tractionLossExponent = defaultExponent end if gripModifiersApplied > 0 then modifier = modifier / gripModifiersApplied end if torqueModifiersApplied > 0 then torqueModifier = torqueModifier / torqueModifiersApplied end end return torqueModifier, modifier, maxSpeed, step, material, materialIndex, tractionLossExponent or 1.0, stuck end local function getDeceleration(vehicle, step) if not step then return 0 end local frameTime = GetFrameTime() return step * frameTime end if cfg.torqueMultiplierEnabled or cfg.preventVehicleFlip or cfg.limpMode then Citizen.CreateThread(function() while true do Citizen.Wait(1) if cfg.torqueMultiplierEnabled or cfg.sundayDriver or cfg.limpMode then if pedInSameVehicleLast then local factor = 1.0 if cfg.torqueMultiplierEnabled and healthEngineNew < 900 then factor = (healthEngineNew+200.0) / 1100 end if cfg.sundayDriver and GetVehicleClass(vehicle) ~= 14 then -- Not for boats local accelerator = GetControlValue(2,71) local brake = GetControlValue(2,72) local speed = GetEntitySpeedVector(vehicle, true)['y'] -- Change Braking force local brk = fBrakeForce if speed >= 1.0 then -- Going forward if accelerator > 127 then -- Forward and accelerating local acc = fscale(accelerator, 127.0, 254.0, 0.1, 1.0, 10.0-(cfg.sundayDriverAcceleratorCurve*2.0)) factor = factor * acc end if brake > 127 then -- Forward and braking isBrakingForward = true brk = fscale(brake, 127.0, 254.0, 0.01, fBrakeForce, 10.0-(cfg.sundayDriverBrakeCurve*2.0)) end elseif speed <= -1.0 then -- Going reverse if brake > 127 then -- Reversing and accelerating (using the brake) local rev = fscale(brake, 127.0, 254.0, 0.1, 1.0, 10.0-(cfg.sundayDriverAcceleratorCurve*2.0)) factor = factor * rev end if accelerator > 127 then -- Reversing and braking (Using the accelerator) isBrakingReverse = true brk = fscale(accelerator, 127.0, 254.0, 0.01, fBrakeForce, 10.0-(cfg.sundayDriverBrakeCurve*2.0)) end else -- Stopped or almost stopped or sliding sideways local entitySpeed = GetEntitySpeed(vehicle) if entitySpeed < 1 then -- Not sliding sideways if isBrakingForward == true then --Stopped or going slightly forward while braking DisableControlAction(2,72,true) -- Disable Brake until user lets go of brake SetVehicleForwardSpeed(vehicle,speed*0.98) SetVehicleBrakeLights(vehicle,true) end if isBrakingReverse == true then --Stopped or going slightly in reverse while braking DisableControlAction(2,71,true) -- Disable reverse Brake until user lets go of reverse brake (Accelerator) SetVehicleForwardSpeed(vehicle,speed*0.98) SetVehicleBrakeLights(vehicle,true) end if isBrakingForward == true and GetDisabledControlNormal(2,72) == 0 then -- We let go of the brake isBrakingForward=false end if isBrakingReverse == true and GetDisabledControlNormal(2,71) == 0 then -- We let go of the reverse brake (Accelerator) isBrakingReverse = false end end end if brk > fBrakeForce - 0.02 then brk = fBrakeForce end -- Make sure we can brake max. SetVehicleHandlingFloat(vehicle, 'CHandlingData', 'fBrakeForce', brk) -- Set new Brake Force multiplier end if cfg.limpMode == true and healthEngineNew < cfg.engineSafeGuard + 5 then factor = cfg.limpModeMultiplier end SetVehicleEngineTorqueMultiplier(vehicle, factor) end end if cfg.preventVehicleFlip then local roll = GetEntityRoll(vehicle) if (roll > 75.0 or roll < -75.0) and GetEntitySpeed(vehicle) < 2 then DisableControlAction(2,59,true) -- Disable left/right DisableControlAction(2,60,true) -- Disable up/down end end end end) end Citizen.CreateThread(function() while true do Citizen.Wait(50) local ped = PlayerPedId() if isPedDrivingAVehicle() then vehicle = GetVehiclePedIsIn(ped, false) vehicleClass = GetVehicleClass(vehicle) healthEngineCurrent = GetVehicleEngineHealth(vehicle) if healthEngineCurrent == 1000 then healthEngineLast = 1000.0 end healthEngineNew = healthEngineCurrent healthEngineDelta = healthEngineLast - healthEngineCurrent healthEngineDeltaScaled = healthEngineDelta * cfg.damageFactorEngine * cfg.classDamageMultiplier[vehicleClass] healthBodyCurrent = GetVehicleBodyHealth(vehicle) if healthBodyCurrent == 1000 then healthBodyLast = 1000.0 end healthBodyNew = healthBodyCurrent healthBodyDelta = healthBodyLast - healthBodyCurrent healthBodyDeltaScaled = healthBodyDelta * cfg.damageFactorBody * cfg.classDamageMultiplier[vehicleClass] healthPetrolTankCurrent = GetVehiclePetrolTankHealth(vehicle) if cfg.compatibilityMode and healthPetrolTankCurrent < 1 then -- SetVehiclePetrolTankHealth(vehicle, healthPetrolTankLast) -- healthPetrolTankCurrent = healthPetrolTankLast healthPetrolTankLast = healthPetrolTankCurrent end if healthPetrolTankCurrent == 1000 then healthPetrolTankLast = 1000.0 end healthPetrolTankNew = healthPetrolTankCurrent healthPetrolTankDelta = healthPetrolTankLast-healthPetrolTankCurrent healthPetrolTankDeltaScaled = healthPetrolTankDelta * cfg.damageFactorPetrolTank * cfg.classDamageMultiplier[vehicleClass] if healthEngineCurrent > cfg.engineSafeGuard+1 then SetVehicleUndriveable(vehicle,false) end if healthEngineCurrent <= cfg.engineSafeGuard+1 and cfg.limpMode == false then SetVehicleUndriveable(vehicle,true) end -- If ped spawned a new vehicle while in a vehicle or teleported from one vehicle to another, handle as if we just entered the car if vehicle ~= lastVehicle then pedInSameVehicleLast = false end if pedInSameVehicleLast == true then -- Damage happened while in the car = can be multiplied -- Only do calculations if any damage is present on the car. Prevents weird behavior when fixing using trainer or other script if healthEngineCurrent ~= 1000.0 or healthBodyCurrent ~= 1000.0 or healthPetrolTankCurrent ~= 1000.0 then -- Combine the delta values (Get the largest of the three) local healthEngineCombinedDelta = math.max(healthEngineDeltaScaled, healthBodyDeltaScaled, healthPetrolTankDeltaScaled) -- If huge damage, scale back a bit if healthEngineCombinedDelta > (healthEngineCurrent - cfg.engineSafeGuard) then healthEngineCombinedDelta = healthEngineCombinedDelta * 0.7 end -- If complete damage, but not catastrophic (ie. explosion territory) pull back a bit, to give a couple of seconds og engine runtime before dying if healthEngineCombinedDelta > healthEngineCurrent then healthEngineCombinedDelta = healthEngineCurrent - (cfg.cascadingFailureThreshold / 5) end ------- Calculate new value healthEngineNew = healthEngineLast - healthEngineCombinedDelta ------- Sanity Check on new values and further manipulations -- If somewhat damaged, slowly degrade until slightly before cascading failure sets in, then stop if healthEngineNew > (cfg.cascadingFailureThreshold + 5) and healthEngineNew < cfg.degradingFailureThreshold then healthEngineNew = healthEngineNew-(0.038 * cfg.degradingHealthSpeedFactor) end -- If Damage is near catastrophic, cascade the failure if healthEngineNew < cfg.cascadingFailureThreshold then healthEngineNew = healthEngineNew-(0.1 * cfg.cascadingFailureSpeedFactor) end -- Prevent Engine going to or below zero. Ensures you can reenter a damaged car. if healthEngineNew < cfg.engineSafeGuard then healthEngineNew = cfg.engineSafeGuard end -- Prevent Explosions if cfg.compatibilityMode == false and healthPetrolTankCurrent < 750 then healthPetrolTankNew = 750.0 end -- Prevent negative body damage. if healthBodyNew < 0 then healthBodyNew = 0.0 end end else -- Just got in the vehicle. Damage can not be multiplied this round -- Set vehicle handling data fDeformationDamageMult = GetVehicleHandlingFloat(vehicle, 'CHandlingData', 'fDeformationDamageMult') fBrakeForce = GetVehicleHandlingFloat(vehicle, 'CHandlingData', 'fBrakeForce') local newFDeformationDamageMult = fDeformationDamageMult ^ cfg.deformationExponent -- Pull the handling file value closer to 1 if cfg.deformationMultiplier ~= -1 then SetVehicleHandlingFloat(vehicle, 'CHandlingData', 'fDeformationDamageMult', newFDeformationDamageMult * cfg.deformationMultiplier) end -- Multiply by our factor if cfg.weaponsDamageMultiplier ~= -1 then SetVehicleHandlingFloat(vehicle, 'CHandlingData', 'fWeaponDamageMult', cfg.weaponsDamageMultiplier/cfg.damageFactorBody) end -- Set weaponsDamageMultiplier and compensate for damageFactorBody --Get the CollisionDamageMultiplier fCollisionDamageMult = GetVehicleHandlingFloat(vehicle, 'CHandlingData', 'fCollisionDamageMult') --Modify it by pulling all number a towards 1.0 local newFCollisionDamageMultiplier = fCollisionDamageMult ^ cfg.collisionDamageExponent -- Pull the handling file value closer to 1 SetVehicleHandlingFloat(vehicle, 'CHandlingData', 'fCollisionDamageMult', newFCollisionDamageMultiplier) --Get the EngineDamageMultiplier fEngineDamageMult = GetVehicleHandlingFloat(vehicle, 'CHandlingData', 'fEngineDamageMult') --Modify it by pulling all number a towards 1.0 local newFEngineDamageMult = fEngineDamageMult ^ cfg.engineDamageExponent -- Pull the handling file value closer to 1 SetVehicleHandlingFloat(vehicle, 'CHandlingData', 'fEngineDamageMult', newFEngineDamageMult) -- If body damage catastrophic, reset somewhat so we can get new damage to multiply if healthBodyCurrent < cfg.cascadingFailureThreshold then healthBodyNew = cfg.cascadingFailureThreshold end pedInSameVehicleLast = true end -- set the actual new values if healthEngineNew ~= healthEngineCurrent then SetVehicleEngineHealth(vehicle, healthEngineNew) end if healthBodyNew ~= healthBodyCurrent then SetVehicleBodyHealth(vehicle, healthBodyNew) end if healthPetrolTankNew ~= healthPetrolTankCurrent then SetVehiclePetrolTankHealth(vehicle, healthPetrolTankNew) end -- Store current values, so we can calculate delta next time around healthEngineLast = healthEngineNew healthBodyLast = healthBodyNew healthPetrolTankLast = healthPetrolTankNew lastVehicle=vehicle if cfg.randomTireBurstInterval ~= 0 and GetEntitySpeed(vehicle) > 10 then tireBurstLottery() end else if pedInSameVehicleLast == true then -- We just got out of the vehicle lastVehicle = GetVehiclePedIsIn(ped, true) if cfg.deformationMultiplier ~= -1 then SetVehicleHandlingFloat(lastVehicle, 'CHandlingData', 'fDeformationDamageMult', fDeformationDamageMult) end -- Restore deformation multiplier SetVehicleHandlingFloat(lastVehicle, 'CHandlingData', 'fBrakeForce', fBrakeForce) -- Restore Brake Force multiplier if cfg.weaponsDamageMultiplier ~= -1 then SetVehicleHandlingFloat(lastVehicle, 'CHandlingData', 'fWeaponDamageMult', cfg.weaponsDamageMultiplier) end -- Since we are out of the vehicle, we should no longer compensate for bodyDamageFactor SetVehicleHandlingFloat(lastVehicle, 'CHandlingData', 'fCollisionDamageMult', fCollisionDamageMult) -- Restore the original CollisionDamageMultiplier SetVehicleHandlingFloat(lastVehicle, 'CHandlingData', 'fEngineDamageMult', fEngineDamageMult) -- Restore the original EngineDamageMultiplier end pedInSameVehicleLast = false end end end) AddEventHandler('onClientResourceStop', function(resourceName) if resourceName == GetCurrentResourceName() then LeftVehicle(currentVehicle) end end) AddEventHandler('onResourceStop', function(resourceName) if resourceName == GetCurrentResourceName() then LeftVehicle(currentVehicle) end end) if DoesEntityExist(GetVehiclePedIsIn(PlayerPedId())) then EnteredVehicle(GetVehiclePedIsIn(PlayerPedId())) end function GetPlateSecure(vehicle) local plate = ESX.Math.Trim(GetVehicleNumberPlateText(vehicle)) if not plate then local timeout = 0 while not plate and timeout < 1000 do plate = ESX.Math.Trim(GetVehicleNumberPlateText(vehicle)) timeout = timeout + 100 Citizen.Wait(100) end end return plate end exports('setEngineHealth', function(vehicle, health) if not health then return end if not vehicle then error("No entity given on setEngineHealth!") end if not DoesEntityExist(vehicle) then error("Entity given on setEngineHealth doesn't exist!") end if vehicle == currentVehicle then healthEngineCurrent = health healthEngineLast = health healthEngineNew = health healthEngineDelta = 0 end SetVehicleEngineHealth(vehicle, health + 0.0) end) exports('setBodyHealth', function(vehicle, health) if not health then return end if not vehicle then error("No entity given on setBodyHealth!") end if not DoesEntityExist(vehicle) then error("Entity given on setBodyHealth doesn't exist!") end health = health + 0.0 if vehicle == currentVehicle then healthBodyCurrent = health healthBodyLast = health healthBodyNew = health healthBodyDelta = 0 end SetVehicleBodyHealth(vehicle, health + 0.0) end) Citizen.SetTimeout(1000, tireBurstLottery) function DeleteVeh(vehicle, driver) NetworkRequestControlOfEntity(vehicle) NetworkRequestControlOfEntity(driver) local start = GetGameTimer() while (not NetworkHasControlOfEntity(vehicle) or NetworkHasControlOfEntity(driver)) and GetGameTimer() - start < 5000 do Citizen.Wait(100) NetworkRequestControlOfEntity(vehicle) NetworkRequestControlOfEntity(driver) end SetEntityAsMissionEntity(vehicle, false, true) --Car Removal DeleteEntity(vehicle) SetEntityAsMissionEntity(driver, false, true) --Driver Removal DeleteEntity(driver) RemoveBlip(mechBlip) --Blip Removal if DoesEntityExist(vehicle) then NetworkRequestControlOfEntity(vehicle) SetEntityAsNoLongerNeeded(vehicle) SetEntityCoords(vehicle, 10000, 10000, -100, nil, nil, nil, true) end if DoesEntityExist(driver) then NetworkRequestControlOfEntity(driver) SetEntityAsNoLongerNeeded(driver) SetEntityCoords(driver, 10000, 10000, -100, nil, nil, nil, true) end end function GoToTarget(x, y, z, vehicle, driver, vehhash, target) TaskVehicleDriveToCoord(driver, vehicle, x, y, z, 17.0, 0, vehhash, drivingStyle, 1, true) ShowAdvancedNotification(companyIcon, companyName, "ANWB'er onderweg", "Er is een ANWB'er naar uw locatie onderweg. Bedankt voor het gebruik maken van onze diensten.") enroute = true while enroute do Citizen.Wait(500) distanceToTarget = #(GetEntityCoords(target).xy - GetEntityCoords(vehicle).xy) if simplerRepair then if distanceToTarget < 20 then TaskVehicleTempAction(driver, vehicle, 27, 6000) Citizen.Wait(3000) RepairVehicle(target, vehicle, driver) end else if distanceToTarget < 20 then TaskVehicleTempAction(driver, vehicle, 27, 6000) SetVehicleUndriveable(vehicle, true) GoToTargetWalking(target, vehicle, driver) end end end end function GoToTargetWalking(target, vehicle, driver) local startTime = GetGameTimer() + 20000 local networkControl = GetGameTimer() NetworkRequestControlOfEntity(driver) while not NetworkHasControlOfEntity(driver) do NetworkRequestControlOfEntity(driver) Citizen.Wait(100) end while enroute do Citizen.Wait(500) engine = GetWorldPositionOfEntityBone(target, GetEntityBoneIndexByName(target, "engine")) TaskGoToCoordAnyMeans(driver, engine, 2.0, 0, 0, 786603, 0xbf800000) distanceToTarget = #(engine - GetEntityCoords(driver)) norunrange = false if distanceToTarget <= 10 and not norunrange then -- stops ai from sprinting when close TaskGoToCoordAnyMeans(driver, engine, 1.0, 0, 0, 786603, 0xbf800000) norunrange = true startTime = GetGameTimer() end if distanceToTarget <= 2 or GetGameTimer() - startTime > 7500 then SetVehicleUndriveable(target, true) TaskTurnPedToFaceCoord(driver, GetEntityCoords(target), -1) Citizen.Wait(1000) TaskStartScenarioInPlace(driver, "PROP_HUMAN_BUM_BIN", 0, 1) SetVehicleDoorOpen(target, 4, false, false) Citizen.Wait(10000) ClearPedTasks(driver) RepairVehicle(target, vehicle, driver) end end end function RepairVehicle(target, vehicle, driver) enroute = false norunrange = false FreezeEntityPosition(driver, false) SetVehicleDoorShut(target, 4, false, false) Citizen.Wait(500) local health = math.max(GetVehicleBodyHealth(target), GetVehicleEngineHealth(target)) local health = ((GetVehicleEngineHealth(target) * 1.0) + (GetVehicleBodyHealth(target) * 1.0)) / 2 local price = math.max(math.floor((900-health) * 3), 100) ShowAdvancedNotification(mechPedPick.icon, mechPedPick.name, "Vehicle Repaired" , mechPedPick.lines[math.random(#mechPedPick.lines)] .. "\nDat wordt dan €" .. price .. ",-") TriggerServerEvent('iens:pay', price) if repairComsticDamage then TriggerServerEvent('eden_garage:repairVehicle', ESX.Math.Trim(GetVehicleNumberPlateText(target))) SetVehicleEngineHealth(target, 1000.0) SetVehicleBodyHealth(target, 1000.0) SetVehicleDeformationFixed(target) SetVehicleFixed(target) SetVehicleUndriveable(target, false) SetVehicleEngineOn(target, true, true) if NetworkGetEntityIsNetworked(target) then TriggerServerEvent('esx_mechanicjob:fixVehicle', NetworkGetNetworkIdFromEntity(target)) end TriggerEvent('CruiseControl:SetLimiter', target) else SetVehicleEngineHealth(target, 1000.0) TriggerEvent('CruiseControl:SetLimiter', target) end if flipVehicle then local roll = GetEntityRoll(target) if (roll > 40.0 or roll < -40.0) and GetEntitySpeed(target) < 2 then DisableControlAction(2,59,true) -- Disable left/right DisableControlAction(2,60,true) -- Disable up/down SetVehicleOnGroundProperly(target) end end TriggerServerEvent('eden_garage:repairVehicle', ESX.Math.Trim(GetVehicleNumberPlateText(target))) TriggerServerEvent('vehiclefailure:repairDone') SetVehicleUndriveable(target, false) Citizen.Wait(5000) TriggerEvent('CruiseControl:SetLimiter', target) LeaveTarget(vehicle, driver) end function LeaveTarget(vehicle, driver) TaskVehicleDriveWander(driver, vehicle, 17.0, drivingStyle) SetEntityAsNoLongerNeeded(vehicle) SetEntityAsNoLongerNeeded(driver) SetEntityInvincible(mechPed, false) RemoveBlip(mechBlip) TriggerServerEvent("vehiclefailure:removeped", NetworkGetNetworkIdFromEntity(vehicle), NetworkGetNetworkIdFromEntity(driver)) local _vehicle = mechVeh local _ped = mechPed SetTimeout(30000, function() ESX.Game.TryDeleteAny(_vehicle) ESX.Game.TryDeleteAny(_ped, DeletePed) mechVeh = nil mechPed = nil end) targetVeh = nil end function GetTargetVehicle(player, dir) if IsPedSittingInAnyVehicle(player) then dmgVeh = GetVehiclePedIsIn(player, false) else local returnVeh, distance = ESX.Game.GetClosestVehicle(GetEntityCoords(player)) if distance < 5.0 then dmgVeh = returnVeh else dmgVeh = 0 end end if DoesEntityExist(dmgVeh) then return dmgVeh else ShowNotification("Failed to find a vehicle.") end end function GetMechPed() mechPedPick = mechPeds[math.random(#mechPeds)] end function GetVehicleInDirection(coordFrom, coordTo) local rayHandle = CastRayPointToPoint( coordFrom.x, coordFrom.y, coordFrom.z, coordTo.x, coordTo.y, coordTo.z, 10, PlayerPedId(), 0) local _, _, _, _, vehicle = GetRaycastResult(rayHandle) return vehicle end RegisterNetEvent("vehiclefailure:removeped") AddEventHandler("vehiclefailure:removeped", function(netId) if not netId then return end local entity = NetworkGetEntityFromNetworkId(netId) ESX.Game.TryDeleteAny(entity, DeletePed) end) RegisterNetEvent("vehiclefailure:removevehicle") AddEventHandler("vehiclefailure:removevehicle", function(netId) if not netId then return end local entity = NetworkGetEntityFromNetworkId(netId) ESX.Game.TryDeleteAny(entity) end) function playRadioAnim(player) Citizen.CreateThread(function() RequestAnimDict("cellphone@") TaskPlayAnim(player, "cellphone@", "cellphone_call_in", 1.5, 2.0, -1, 50, 2.0, 0, 0, 0 ) RemoveAnimDict("cellphone@") Citizen.Wait(6000) ClearPedTasks(player) end) end function loadAnimDict(dict) while (not HasAnimDictLoaded(dict)) do RequestAnimDict(dict) Citizen.Wait(0) end end function ShowAdvancedNotification(icon, sender, title, text) SetNotificationTextEntry("STRING") AddTextComponentString(text) SetNotificationMessage(icon, icon, true, 4, sender, title, text) DrawNotification(false, true) end function ShowNotification(text) SetNotificationTextEntry("STRING") AddTextComponentString(text) DrawNotification(false, false) end if false then ---@class GripData ---@field torqueModifier number Higher values means more torque/power on this material, 1.0 is default ---@field modifier number Lower values means more grip, 0.0 is default ---@field hash string The hash of this material ---@field name string The name of this material ---@field index integer The material index of this material (get auto generated so no use in adding this manually) ---@field stuck boolean Whether this material should make a vehicle get stuck if the grip and speed are too low ---@field maxSpeed number The maximum speed (in km/h) on this material, this gets modified by the modifier ---@field tractionLossExponent number The traction loss is raised to the power of this, defaults to defaultExponent local gripData end materials = { [ 0] = { name = "default", hash = "1" }, [ 1] = { name = "concrete", hash = "1187676648" }, [ 2] = { name = "concrete_pothole", modifier = 0.10, hash = "359120722" }, [ 3] = { name = "concrete_dusty", hash = "-1084640111" }, [ 4] = { name = "tarmac", hash = "282940568" }, [ 5] = { name = "tarmac_painted", hash = "-1301352528", modifier = 0.10 }, [ 6] = { name = "tarmac_pothole", modifier = 0.10, hash = "1886546517" }, [ 7] = { name = "rumble_strip", modifier = 0.08, hash = "-250168275" }, [ 8] = { name = "breeze_block", hash = "-954112554" }, [ 9] = { name = "rock", hash = "-840216541" }, [10] = { name = "rock_mossy", modifier = 0.20, hash = "-124769592" }, [11] = { name = "stone", hash = "765206029" }, [12] = { name = "cobblestone", hash = "576169331" }, [13] = { name = "brick", hash = "1639053622" }, [14] = { name = "marble", modifier = 0.10, hash = "1945073303" }, [15] = { name = "paving_slab", hash = "1907048430" }, [16] = { name = "sandstone_solid", hash = "592446772" }, [17] = { name = "sandstone_brittle", hash = "1913209870" }, [18] = { name = "sand_loose", torqueModifier = 0.25, modifier = 0.65, hash = "-1595148316", maxSpeed = 60.0, stuck = true }, [19] = { name = "sand_compact", torqueModifier = 0.35, modifier = 0.55, hash = "510490462", step = 2.0, maxSpeed = 85.0, stuck = true }, [20] = { name = "sand_wet", torqueModifier = 0.45, modifier = 0.70, hash = "909950165", step = 2.0, maxSpeed = 80.0, stuck = true }, [21] = { name = "sand_track", torqueModifier = 0.40, modifier = 0.5, hash = "-1907520769", maxSpeed = 80.0 }, [22] = { name = "sand_underwater", torqueModifier = 0.40, modifier = 0.5, hash = "-1136057692", maxSpeed = 80.0, stuck = true }, [23] = { name = "sand_dry_deep", torqueModifier = 0.40, modifier = 0.60, hash = "509508168", maxSpeed = 80.0 }, [24] = { name = "sand_wet_deep", torqueModifier = 0.40, modifier = 0.60, hash = "1288448767", maxSpeed = 60.0, stuck = true }, [25] = { name = "ice", modifier = 0.55, hash = "-786060715" }, [26] = { name = "ice_tarmac", modifier = 0.35, hash = "-1931024423" }, [27] = { name = "snow_loose", modifier = 0.5, hash = "-1937569590" }, [28] = { name = "snow_compact", modifier = 0.60, hash = "-878560889" }, [29] = { name = "snow_deep", modifier = 0.5, hash = "1619704960" }, [30] = { name = "snow_tarmac", modifier = 0.10, hash = "1550304810" }, [31] = { name = "gravel_small", maxSpeed = 70.0, modifier = 0.5, hash = "951832588", step = 7.0, torqueModifier = 0.40, tractionLossExponent = 0.5, stuck = true }, [32] = { name = "gravel_large", maxSpeed = 70.0, modifier = 0.40, hash = "2128369009", step = 8.0, torqueModifier = 0.40, tractionLossExponent = 0.5, stuck = true }, [33] = { name = "gravel_deep", maxSpeed = 60.0, modifier = 0.5, hash = "-356706482", step = 7.0, torqueModifier = 0.40, tractionLossExponent = 0.5, stuck = true }, [34] = { name = "gravel_train_track", maxSpeed = 70.0, modifier = 0.5, hash = "1925605558", step = 6.0, torqueModifier = 0.40, tractionLossExponent = 0.5, stuck = true }, [35] = { name = "dirt_track", maxSpeed = 85.0, modifier = 0.40, hash = "-1885547121", torqueModifier = 0.55 }, [36] = { name = "mud_hard", maxSpeed = 80.0, torqueModifier = 0.3, modifier = 0.6, hash = "-1942898710", step = 3.0 }, [37] = { name = "mud_pothole", maxSpeed = 80.0, torqueModifier = 0.3, modifier = 0.10, hash = "312396330", step = 3.0 }, [38] = { name = "mud_soft", maxSpeed = 60.0, torqueModifier = 0.3, modifier = 0.6, hash = "1635937914", step = 3.0, stuck = true }, [39] = { name = "mud_underwater", maxSpeed = 60.0, torqueModifier = 0.3, modifier = 0.6, hash = "-273490167", step = 3.0, stuck = true }, [40] = { name = "mud_deep", torqueModifier = 0.2, modifier = 0.6, maxSpeed = 40.0, hash = "1109728704", step = 3.0, stuck = true }, [41] = { name = "marsh", torqueModifier = 0.4, modifier = 0.5, hash = "223086562", step = 3.0, stuck = true }, [42] = { name = "marsh_deep", torqueModifier = 0.2, modifier = 0.5, maxSpeed = 40.0, hash = "1584636462", step = 3.0, stuck = true }, [43] = { name = "soil", modifier = 0.5, hash = "-700658213" }, [44] = { name = "clay_hard", torqueModifier = 0.4, modifier = 0.5, hash = "1144315879", step = 0.5 }, [45] = { name = "clay_soft", torqueModifier = 0.4, modifier = 0.5, hash = "560985072", step = 0.5 }, [46] = { name = "grass_long", maxSpeed = 60.0, modifier = 0.60, hash = "-461750719", torqueModifier = 0.4, step = 1.0 }, [47] = { name = "grass", maxSpeed = 90.0, modifier = 0.50, hash = "1333033863", torqueModifier = 0.50, step = 0.75 }, [48] = { name = "grass_short", maxSpeed = 90.0, modifier = 0.60, hash = "-1286696947", torqueModifier = 0.50, step = 0.75 }, [49] = { name = "hay", hash = "-1833527165" }, [50] = { name = "bushes", hash = "581794674" }, [51] = { name = "twigs", hash = "-913351839" }, [52] = { name = "leaves", hash = "-2041329971" }, [53] = { name = "woodchips", hash = "-309121453" }, [54] = { name = "tree_bark", hash = "-1915425863" }, [55] = { name = "metal_solid_small", hash = "-1447280105" }, [56] = { name = "metal_solid_medium", hash = "-365631240" }, [57] = { name = "metal_solid_large", hash = "752131025" }, [58] = { name = "metal_hollow_small", hash = "15972667" }, [59] = { name = "metal_hollow_medium", hash = "1849540536" }, [60] = { name = "metal_hollow_large", hash = "-583213831" }, [61] = { name = "metal_chainlink_small", hash = "762193613" }, [62] = { name = "metal_chainlink_large", hash = "125958708" }, [63] = { name = "metal_corrugated_iron", hash = "834144982" }, [64] = { name = "metal_grille", modifier = 0.10, hash = "-426118011" }, [65] = { name = "metal_railing", hash = "2100727187" }, [66] = { name = "metal_duct", hash = "1761524221" }, [67] = { name = "metal_garage_door", hash = "-231260695" }, [68] = { name = "metal_manhole", hash = "-754997699", fallOffBike = true }, [69] = { name = "wood_solid_small", modifier = 0.20, hash = "-399872228" }, [70] = { name = "wood_solid_medium", modifier = 0.20, hash = "555004797" }, } for k,v in pairs(materials) do v.index = k end