local refuelingThread = nil local isRefuelling = false local inCooldown = false local vehicleAttachedToNozzle = nil local remainingFuelToRefuel = 0 local currentFuelTypePurchased = nil local distanceToCap, distanceToPump = math.maxinteger, math.maxinteger local litersDeductedEachTick = 0.5 ----------------------------------------------------------------------------------------------------------------------------------------- -- Refuelling ----------------------------------------------------------------------------------------------------------------------------------------- RegisterNetEvent('lc_fuel:getPumpNozzle') AddEventHandler('lc_fuel:getPumpNozzle', function(fuelAmountPurchased, fuelTypePurchased) closeUI() if DoesEntityExist(fuelNozzle) then return end if not currentPump then return end local ped = PlayerPedId() local pumpCoords = GetEntityCoords(currentPump) -- Animate the ped to grab the nozzle Utils.Animations.loadAnimDict("anim@am_hold_up@male") TaskPlayAnim(ped, "anim@am_hold_up@male", "shoplift_high", 2.0, 8.0, -1, 50, 0, false, false, false) Wait(300) StopAnimTask(ped, "anim@am_hold_up@male", "shoplift_high", 1.0) -- Spawn the nozzle fuelNozzle = createFuelNozzleObject(fuelTypePurchased) -- Attach the nozzle attachNozzleToPed() if Config.EnablePumpRope then fuelRope = CreateRopeToPump(pumpCoords) end -- Get the max distance the player can go with the nozzle local ropeLength = getNearestPumpRopeLength(fuelTypePurchased, pumpCoords) -- Thread to handle fuel nozzle CreateThread(function() while DoesEntityExist(fuelNozzle) do local waitTime = 500 local nozzleCoords = GetEntityCoords(fuelNozzle) -- Conside the nozzle position, not the ped distanceToPump = #(pumpCoords - nozzleCoords) -- If player reach the distance limit delete the nozzle if distanceToPump > ropeLength then exports['lc_utils']:notify("error", Utils.translate("too_far_away")) deleteRopeAndNozzleProp() end -- If player is near the distance limit, show a notification to him if distanceToPump > (ropeLength * 0.7) then Utils.Markers.showHelpNotification(Utils.translate("too_far_away"), true) end -- Check if ped entered a vehicle if IsPedSittingInAnyVehicle(ped) then -- Gives him 2 seconds to leave before clearing the nozzle SetTimeout(2000,function() if IsPedSittingInAnyVehicle(ped) and DoesEntityExist(fuelNozzle) then exports['lc_utils']:notify("error", Utils.translate("too_far_away")) deleteRopeAndNozzleProp() end end) end if Utils.Config.custom_scripts_compatibility.target == "disabled" and distanceToPump < 1.5 then waitTime = 2 Utils.Markers.showHelpNotification(cachedTranslations.return_nozzle, true) if IsControlJustPressed(0,38) then -- See which one the player is nearer. The fuel cap or fuel pump if distanceToPump < distanceToCap then -- Avoid player press E to return nozzle and press E to refuel in same tick, so it gives preference to refuel Wait(100) returnNozzle() end end end Wait(waitTime) end -- Not near the pump anymore distanceToPump = math.maxinteger end) -- Thread to refuel the vehicle CreateThread(function() -- Set the fuel purchased in a global variable remainingFuelToRefuel = fuelAmountPurchased -- Set the fuel type in a global variable currentFuelTypePurchased = fuelTypePurchased -- Trigger the function to allow refuel on markers if Utils.Config.custom_scripts_compatibility.target == "disabled" then refuelLoop(false) end -- Not near the fuel cap anymore distanceToCap = math.maxinteger end) end) function returnNozzle() local ped = PlayerPedId() if not isRefuelling then Utils.Animations.loadAnimDict("anim@am_hold_up@male") TaskPlayAnim(ped, "anim@am_hold_up@male", "shoplift_high", 2.0, 8.0, -1, 50, 0, false, false, false) Wait(300) StopAnimTask(ped, "anim@am_hold_up@male", "shoplift_high", 1.0) deleteRopeAndNozzleProp() if Config.ReturnNozzleRefund then local isElectric = Utils.Table.contains({"electricnormal", "electricfast"}, currentFuelTypePurchased) TriggerServerEvent('lc_fuel:returnNozzle', remainingFuelToRefuel, isElectric) end end end function executeRefuelAction(isFromJerryCan, closestVehicle, closestCapPos, closestVehicleHash, customVehicleParameters) if Config.Debug then print("executeRefuelAction:p", isFromJerryCan, closestVehicle, closestCapPos, closestVehicleHash, customVehicleParameters) end local ped = PlayerPedId() local refuelTick = Config.RefuelTick local isElectric = false local fuelTypePurchased = currentFuelTypePurchased -- Change the fuel tick if its electric charging if fuelTypePurchased == "electricfast" then isElectric = true refuelTick = Config.Electric.chargeTypes.fast.time * 1000 / 2 -- Divide by 2 because each tick adds 0.5kWh. end if fuelTypePurchased == "electricnormal" then isElectric = true refuelTick = Config.Electric.chargeTypes.normal.time * 1000 / 2 end local animationDuration = 1000 -- 1 sec if isFromJerryCan then animationDuration = -1 -- (infinite) Do not allow the player walk during refuel from jerry can end -- Do not allow user mix electric and petrol fuel/vehicles if (isElectric and Config.Electric.vehiclesListHash[closestVehicleHash]) or (not isElectric and not Config.Electric.vehiclesListHash[closestVehicleHash]) then if not isRefuelling and not vehicleAttachedToNozzle then if remainingFuelToRefuel > 0 then -- Reset the vehicle fuel to 0 when refueling with a different fuel type if not isFromJerryCan and not isElectric then local fuelType = getVehicleFuelTypeFromServer(closestVehicle) if fuelTypePurchased ~= fuelType then changeVehicleFuelType(closestVehicle, fuelTypePurchased) end end isRefuelling = true -- Animate the ped TaskTurnPedToFaceCoord(ped, closestCapPos.x, closestCapPos.y, closestCapPos.z, animationDuration) Utils.Animations.loadAnimDict("weapons@misc@jerrycan@") TaskPlayAnim(ped, "weapons@misc@jerrycan@", "fire", 2.0, 8.0, animationDuration, 50, 0, false, false, false) -- Plug the nozzle in the car attachNozzleToVehicle(closestVehicle, customVehicleParameters) -- Refuel the vehicle refuelingThread = CreateThread(function() local vehicleToRefuel = closestVehicle local startingFuel = GetFuel(vehicleToRefuel) -- Get vehicle fuel level local vehicleTankSize = getVehicleTankSize(vehicleToRefuel) local currentFuel = startingFuel -- Loop keep happening while the player has not canceled, while the fuelNozzle exists and while the ped still has jerry can in hands while isRefuelling and (DoesEntityExist(fuelNozzle) or (isFromJerryCan and GetSelectedPedWeapon(ped) == JERRY_CAN_HASH)) do currentFuel = GetFuel(vehicleToRefuel) local percentageOfFuelToAdd = calculateFuelToAddPercentage(vehicleTankSize) -- Add 0.5L each tick, but the % is proportional to the vehicle tank if currentFuel + percentageOfFuelToAdd > 100 then -- Increase the vehicle fuel level percentageOfFuelToAdd = 100 - currentFuel end if remainingFuelToRefuel < litersDeductedEachTick then -- Break when the user has used all the fuel he paid for break end if percentageOfFuelToAdd <= 0.01 then -- Break when the vehicle tank is full exports['lc_utils']:notify("info", Utils.translate("vehicle_tank_full")) break end -- Decrease the purchased fuel amount and increase the vehicle fuel level remainingFuelToRefuel = remainingFuelToRefuel - litersDeductedEachTick currentFuel = currentFuel + percentageOfFuelToAdd SetFuel(vehicleToRefuel, currentFuel) SendNUIMessage({ showRefuelDisplay = true, remainingFuelAmount = remainingFuelToRefuel, currentVehicleTankSize = vehicleTankSize, currentDisplayFuelAmount = getVehicleDisplayFuelAmount(currentFuel, vehicleTankSize), isElectric = isElectric, fuelTypePurchased = fuelTypePurchased }) if Config.Debug then print("executeRefuelAction:remainingFuelToRefuel", remainingFuelToRefuel) end Wait(refuelTick) end if isFromJerryCan then -- Update the jerry can ammo SetPedAmmo(ped, JERRY_CAN_HASH, remainingFuelToRefuel) updateWeaponAmmo(remainingFuelToRefuel) vehicleAttachedToNozzle = nil end if isElectric then exports['lc_utils']:notify("success", Utils.translate("vehicle_recharged"):format(Utils.Math.round(getVehicleDisplayFuelAmount(currentFuel, vehicleTankSize) - getVehicleDisplayFuelAmount(startingFuel, vehicleTankSize), 1))) else exports['lc_utils']:notify("success", Utils.translate("vehicle_refueled"):format(Utils.Math.round(getVehicleDisplayFuelAmount(currentFuel, vehicleTankSize) - getVehicleDisplayFuelAmount(startingFuel, vehicleTankSize), 1))) end -- Stop refuelling stopRefuelAnimation() SendNUIMessage({ hideRefuelDisplay = true }) isRefuelling = false end) else exports['lc_utils']:notify("error", Utils.translate("not_enough_refuel")) end else -- Terminate refuelling stopRefuelAction() -- Cooldown to prevent the user to spam E and glitch things inCooldown = true SetTimeout(refuelTick + 1,function() inCooldown = false end) end else exports['lc_utils']:notify("error", Utils.translate("incompatible_fuel")) end end function calculateFuelToAddPercentage(totalVolumeLiters) local percentage = (litersDeductedEachTick / totalVolumeLiters) * 100 return percentage end ----------------------------------------------------------------------------------------------------------------------------------------- -- Markers ----------------------------------------------------------------------------------------------------------------------------------------- function refuelLoop(isFromJerryCan) -- Load variables to open te UI loadNuiVariables() local ped = PlayerPedId() local closestCapPos local closestVehicle local customVehicleParameters local closestVehicleHash if isFromJerryCan then remainingFuelToRefuel = getJerryCanAmmo() end isRefuelling = false while DoesEntityExist(fuelNozzle) or (isFromJerryCan and GetSelectedPedWeapon(ped) == JERRY_CAN_HASH) do local waitTime = 200 if closestCapPos then distanceToCap = #(GetEntityCoords(ped) - vector3(closestCapPos.x,closestCapPos.y,closestCapPos.z)) if distanceToCap < customVehicleParameters.distance + 0.0 and (not vehicleAttachedToNozzle or (vehicleAttachedToNozzle and DoesEntityExist(vehicleAttachedToNozzle) and vehicleAttachedToNozzle == closestVehicle)) then waitTime = 1 Utils.Markers.drawText3D(closestCapPos.x,closestCapPos.y,closestCapPos.z, cachedTranslations.interact_with_vehicle) if IsControlJustPressed(0, 38) and not inCooldown then -- See which one the player is nearer. The fuel cap or fuel pump if distanceToPump >= distanceToCap then executeRefuelAction(isFromJerryCan, closestVehicle, closestCapPos, closestVehicleHash, customVehicleParameters) end end else -- Player is not near the cap, set it to null to find it again later closestCapPos = nil end else -- Find the nearest vehicle and cap pos closestVehicle, closestCapPos, closestVehicleHash, customVehicleParameters = getClosestVehicleVariables() end Wait(waitTime) end terminateRefuelThread() end ----------------------------------------------------------------------------------------------------------------------------------------- -- Target ----------------------------------------------------------------------------------------------------------------------------------------- function createTargetForVehicleIteraction() local attachParams = { labelText = Utils.translate("target.start_refuel"), icon = "fas fa-gas-pump", iconColor = "#2986cc", zone_id = "start_refuel", distance = 2.0 } Utils.Target.createTargetForBone(vehicleCapBoneList(),attachParams,executeRefuelActionFromTarget,nil,canAttachNozzleTargetCallback) local detachParams = { labelText = Utils.translate("target.stop_refuel"), icon = "fas fa-gas-pump", iconColor = "#2986cc", zone_id = "stop_refuel", distance = 2.0 } Utils.Target.createTargetForBone(vehicleCapBoneList(),detachParams,stopRefuelAction,nil,canDetachNozzleTargetCallback) end function executeRefuelActionFromTarget() -- Load variables to open te UI loadNuiVariables() local ped = PlayerPedId() -- Calculate if player is holding a jerry can local isFromJerryCan = false if not IsPedInAnyVehicle(ped, false) and GetSelectedPedWeapon(ped) == JERRY_CAN_HASH then isFromJerryCan = true remainingFuelToRefuel = getJerryCanAmmo() if Config.Debug then print("executeRefuelActionFromTarget:remainingFuelToRefuel",remainingFuelToRefuel) end end local closestVehicle, closestCapPos, closestVehicleHash, customVehicleParameters = getClosestVehicleVariables() executeRefuelAction(isFromJerryCan, closestVehicle, closestCapPos, closestVehicleHash, customVehicleParameters) end function canAttachNozzleTargetCallback(entity, distance) local ped = PlayerPedId() if (DoesEntityExist(fuelNozzle) or GetSelectedPedWeapon(ped) == JERRY_CAN_HASH) and not isRefuelling and not vehicleAttachedToNozzle then return true end return false end function canDetachNozzleTargetCallback(entity, distance) local ped = PlayerPedId() if (DoesEntityExist(fuelNozzle) or GetSelectedPedWeapon(ped) == JERRY_CAN_HASH) and vehicleAttachedToNozzle then return true end return false end function canOpenPumpUiTargetCallback() return not DoesEntityExist(fuelNozzle) end function canReturnNozzleTargetCallback() return DoesEntityExist(fuelNozzle) end ----------------------------------------------------------------------------------------------------------------------------------------- -- Utils ----------------------------------------------------------------------------------------------------------------------------------------- function getClosestVehicleVariables() -- Get the closest vehicle and its cap pos local closestVehicle = GetClosestVehicle() local closestCapPos = GetVehicleCapPos(closestVehicle) local closestVehicleHash = GetEntityModel(closestVehicle) local customVehicleParameters = (Config.CustomVehicleParametersHash[closestVehicleHash] or Config.CustomVehicleParametersHash.default or { distance = 1.2, nozzleOffset = { forward = 0.0, right = -0.15, up = 0.5 }, nozzleRotation = { x = 0, y = 0, z = 0} }) if not closestCapPos then print("Cap not found for vehicle") end local finalWorldPos = getWorldPosFromOffset(closestVehicle, customVehicleParameters.nozzleOffset) return closestVehicle, finalWorldPos, closestVehicleHash, customVehicleParameters end function getWorldPosFromOffset(vehicle, offset) local closestCapPos = GetVehicleCapPos(vehicle) local forwardVector, rightVector, upVector, _ = GetEntityMatrix(vehicle) -- Adjust the offsets local forwardOffset = forwardVector * offset.forward local rightoffset = rightVector * offset.right local upOffset = upVector * offset.up -- Final world position of the nozzle point return vector3( closestCapPos.x + forwardOffset.x + rightoffset.x + upOffset.x, closestCapPos.y + forwardOffset.y + rightoffset.y + upOffset.y, closestCapPos.z + forwardOffset.z + rightoffset.z + upOffset.z ) end function terminateRefuelThread() -- Stop the refueling process if refuelingThread and IsThreadActive(refuelingThread) then TerminateThread(refuelingThread) refuelingThread = nil end end function stopRefuelAnimation() local ped = PlayerPedId() ClearPedTasks(ped) RemoveAnimDict("weapons@misc@jerrycan@") end function stopRefuelAction() -- Stop refuelling stopRefuelAnimation() SendNUIMessage({ hideRefuelDisplay = true }) attachNozzleToPed() isRefuelling = false end function attachNozzleToVehicle(closestVehicle, customVehicleParameters) DetachEntity(fuelNozzle, true, true) -- Find the appropriate bone for the fuel cap local tankBones = vehicleCapBoneList() local boneIndex = -1 for _, boneName in ipairs(tankBones) do boneIndex = GetEntityBoneIndexByName(closestVehicle, boneName) if boneIndex ~= -1 then break end end if boneIndex ~= -1 then local vehicleRotation = GetEntityRotation(closestVehicle) local forwardVector, rightVector, upVector, _ = GetEntityMatrix(closestVehicle) -- Adjust the offsets local forwardOffset = forwardVector * customVehicleParameters.nozzleOffset.forward local rightoffset = rightVector * customVehicleParameters.nozzleOffset.right local upOffset = upVector * customVehicleParameters.nozzleOffset.up local finalOffset = forwardOffset + rightoffset + upOffset -- Adjust the rotation local nozzleRotation = customVehicleParameters.nozzleRotation or { x = 0, y = 0, z = 0 } local finalRotationX = vehicleRotation.x + nozzleRotation.x local finalRotationY = vehicleRotation.y + nozzleRotation.y local finalRotationZ = vehicleRotation.z + nozzleRotation.z -- Attach the nozzle to the vehicle's fuel cap bone with the calculated rotation AttachEntityToEntity(fuelNozzle, closestVehicle, boneIndex, finalOffset.x, finalOffset.y, finalOffset.z, finalRotationX - 45, finalRotationY, finalRotationZ - 90, false, false, false, false, 2, false) else print("No valid fuel cap bone found on the vehicle.") end -- Set the global variable to indicate the vehicle attached to nozzle vehicleAttachedToNozzle = closestVehicle end function attachNozzleToPed() DetachEntity(fuelNozzle, true, true) local ped = PlayerPedId() local pedBone = GetPedBoneIndex(ped, 18905) AttachEntityToEntity(fuelNozzle, ped, pedBone, 0.13, 0.04, 0.01, -42.0, -115.0, -63.42, false, true, false, true, 0, true) vehicleAttachedToNozzle = nil end function getNearestPumpRopeLength(fuelTypePurchased, pumpCoords) local distanceToFindPump = 10 local ropeLength = Config.DefaultRopeLength if fuelTypePurchased == "electricfast" or fuelTypePurchased == "electricnormal" then for _, pumpConfig in pairs(Config.Electric.chargersLocation) do local distance = #(vector3(pumpConfig.location.x, pumpConfig.location.y, pumpConfig.location.z) - pumpCoords) if distance < distanceToFindPump then ropeLength = pumpConfig.ropeLength break end end else for _, pumpConfig in pairs(Config.CustomGasPumpLocations) do local distance = #(vector3(pumpConfig.location.x, pumpConfig.location.y, pumpConfig.location.z) - pumpCoords) if distance < distanceToFindPump then ropeLength = pumpConfig.ropeLength break end end end return ropeLength end function createFuelNozzleObject(fuelTypePurchased) local nozzle_prop_label = Config.NozzleProps.gas -- Change the nozzle prop to electric if fuelTypePurchased == "electricfast" or fuelTypePurchased == "electricnormal" then nozzle_prop_label = Config.NozzleProps.electric end RequestModel(nozzle_prop_label) while not HasModelLoaded(nozzle_prop_label) do Wait(50) end return CreateObject(joaat(nozzle_prop_label), 1.0, 1.0, 1.0, true, true, false) end