├── fxmanifest.lua ├── bridge ├── server │ ├── qb.lua │ ├── nd.lua │ ├── esx.lua │ └── ox.lua └── client │ ├── esx.lua │ ├── qb.lua │ ├── nd.lua │ └── ox.lua ├── README.md ├── config.lua ├── sv_routes.lua ├── sv_cargo.lua └── cl_cargo.lua /fxmanifest.lua: -------------------------------------------------------------------------------- 1 | fx_version 'cerulean' 2 | game 'gta5' 3 | 4 | author 'Randolio' 5 | description 'Cargo Deliveries' 6 | 7 | shared_scripts { 8 | 'config.lua', 9 | '@ox_lib/init.lua' 10 | } 11 | 12 | client_scripts { 13 | 'bridge/client/**.lua', 14 | 'cl_cargo.lua', 15 | } 16 | 17 | server_scripts { 18 | 'bridge/server/**.lua', 19 | 'sv_routes.lua', 20 | 'sv_cargo.lua', 21 | } 22 | 23 | lua54 'yes' 24 | -------------------------------------------------------------------------------- /bridge/server/qb.lua: -------------------------------------------------------------------------------- 1 | if GetResourceState('qb-core') ~= 'started' then return end 2 | 3 | local QBCore = exports['qb-core']:GetCoreObject() 4 | 5 | function GetPlayer(id) 6 | return QBCore.Functions.GetPlayer(id) 7 | end 8 | 9 | function DoNotification(src, text, nType) 10 | TriggerClientEvent('QBCore:Notify', src, text, nType) 11 | end 12 | 13 | function AddMoney(Player, moneyType, amount) 14 | Player.Functions.AddMoney(moneyType, amount, "cargo-delivery") 15 | end 16 | -------------------------------------------------------------------------------- /bridge/server/nd.lua: -------------------------------------------------------------------------------- 1 | if not lib.checkDependency('ND_Core', '2.0.0') then return end 2 | 3 | local NDCore = exports["ND_Core"] 4 | 5 | function GetPlayer(id) 6 | return NDCore:getPlayer(id) 7 | end 8 | 9 | function DoNotification(src, text, nType) 10 | local player = NDCore:getPlayer(src) 11 | return player and player.notify({ type = nType, description = text }) 12 | end 13 | 14 | function AddMoney(player, moneyType, amount) 15 | player.addMoney(moneyType, amount, "cargo-delivery") 16 | end 17 | -------------------------------------------------------------------------------- /bridge/server/esx.lua: -------------------------------------------------------------------------------- 1 | if GetResourceState('es_extended') ~= 'started' then return end 2 | 3 | local ESX = exports['es_extended']:getSharedObject() 4 | 5 | function GetPlayer(id) 6 | return ESX.GetPlayerFromId(id) 7 | end 8 | 9 | function DoNotification(src, text, nType) 10 | TriggerClientEvent('esx:showNotification', src, text, nType) 11 | end 12 | 13 | function AddMoney(xPlayer, moneyType, amount) 14 | local account = moneyType == 'cash' and 'money' or moneyType 15 | xPlayer.addAccountMoney(account, amount, "cargo-delivery") 16 | end 17 | -------------------------------------------------------------------------------- /bridge/client/esx.lua: -------------------------------------------------------------------------------- 1 | if GetResourceState('es_extended') ~= 'started' then return end 2 | 3 | local ESX = exports['es_extended']:getSharedObject() 4 | 5 | RegisterNetEvent('esx:playerLoaded', function(xPlayer) 6 | ESX.PlayerLoaded = true 7 | OnPlayerLoaded() 8 | end) 9 | 10 | RegisterNetEvent('esx:onPlayerLogout', function() 11 | ESX.PlayerLoaded = false 12 | OnPlayerUnload() 13 | end) 14 | 15 | function handleVehicleKeys(veh) 16 | local plate = GetVehicleNumberPlateText(veh) 17 | end 18 | 19 | function hasPlyLoaded() 20 | return ESX.PlayerLoaded 21 | end 22 | 23 | function DoNotification(text, nType) 24 | ESX.ShowNotification(text, nType) 25 | end 26 | -------------------------------------------------------------------------------- /bridge/client/qb.lua: -------------------------------------------------------------------------------- 1 | if GetResourceState('qb-core') ~= 'started' then return end 2 | 3 | local QBCore = exports['qb-core']:GetCoreObject() 4 | 5 | RegisterNetEvent('QBCore:Client:OnPlayerLoaded', function() 6 | OnPlayerLoaded() 7 | end) 8 | 9 | RegisterNetEvent('QBCore:Client:OnPlayerUnload', function() 10 | OnPlayerUnload() 11 | end) 12 | 13 | function handleVehicleKeys(veh) 14 | local plate = GetVehicleNumberPlateText(veh) 15 | TriggerServerEvent('qb-vehiclekeys:server:AcquireVehicleKeys', plate) 16 | end 17 | 18 | function hasPlyLoaded() 19 | return LocalPlayer.state.isLoggedIn 20 | end 21 | 22 | function DoNotification(text, nType) 23 | QBCore.Functions.Notify(text, nType) 24 | end 25 | -------------------------------------------------------------------------------- /bridge/client/nd.lua: -------------------------------------------------------------------------------- 1 | if not lib.checkDependency('ND_Core', '2.0.0') then return end 2 | 3 | local NDCore = exports["ND_Core"] 4 | 5 | RegisterNetEvent('ND:characterUnloaded', function() 6 | LocalPlayer.state.isLoggedIn = false 7 | OnPlayerUnload() 8 | end) 9 | 10 | RegisterNetEvent('ND:characterLoaded', function(character) 11 | LocalPlayer.state.isLoggedIn = true 12 | OnPlayerLoaded() 13 | end) 14 | 15 | function handleVehicleKeys(veh) 16 | SetTimeout(1000, function() 17 | SetVehicleDoorsLocked(veh, 0) 18 | end) 19 | end 20 | 21 | function hasPlyLoaded() 22 | return LocalPlayer.state.isLoggedIn 23 | end 24 | 25 | function DoNotification(text, nType) 26 | NDCore:notify({ title = "Notification", description = text, type = nType, }) 27 | end 28 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Randolio: Cargo Deliveries 2 | 3 | **ESX/QB/ND support with bridge** 4 | 5 | Inspired from the GTA Online cargo deliveries which use the flatbed and the duneloader. Random vehicle type, route and payout all configurable from sv_routes.lua. 6 | GTA scaleforms and drop off draw marker for nostalgic reasons. Utilizing ox lib throughout. 7 | 8 | There is an event handler called 'randol_cargo:handleVehicleKeys' for each framework in the bridge. You'll have to fill that in to fit your keys system. 9 | 10 | # Showcase 11 | https://streamable.com/c4vov9 12 | 13 | ## Requirements 14 | 15 | * [ox_lib](https://github.com/overextended/ox_lib/releases/tag/v3.16.2) 16 | 17 | **You have permission to use this in your server and edit for your personal needs but are not allowed to redistribute.** 18 | -------------------------------------------------------------------------------- /bridge/server/ox.lua: -------------------------------------------------------------------------------- 1 | if GetResourceState('ox_core') ~= 'started' then return end 2 | 3 | local file = ('imports/%s.lua'):format(IsDuplicityVersion() and 'server' or 'client') 4 | local import = LoadResourceFile('ox_core', file) 5 | local chunk = assert(load(import, ('@@ox_core/%s'):format(file))) 6 | chunk() 7 | 8 | function GetPlayer(id) 9 | return Ox.GetPlayer(id) 10 | end 11 | 12 | function DoNotification(src, text, nType) 13 | TriggerClientEvent('ox_lib:notify', src, { type = nType, description = text }) 14 | end 15 | 16 | function AddMoney(Player, moneyType, amount) 17 | if moneyType == 'cash' then 18 | exports.ox_inventory:AddItem(Player.source, 'money', amount) -- support for ox_inventory because why do you need to use anything else at this point... 19 | else 20 | -- ox_core doesn't have a bank system, so we'll just leave this empty so people can fill it out on their own. 21 | end 22 | end 23 | -------------------------------------------------------------------------------- /bridge/client/ox.lua: -------------------------------------------------------------------------------- 1 | if GetResourceState('ox_core') ~= 'started' then return end 2 | 3 | local file = ('imports/%s.lua'):format(IsDuplicityVersion() and 'server' or 'client') 4 | local import = LoadResourceFile('ox_core', file) 5 | local chunk = assert(load(import, ('@@ox_core/%s'):format(file))) 6 | chunk() 7 | 8 | AddEventHandler('ox:playerLoaded', function() 9 | OnPlayerLoaded() 10 | end) 11 | 12 | AddEventHandler('ox:playerLogout', function() 13 | OnPlayerUnload() 14 | end) 15 | 16 | function handleVehicleKeys(veh) 17 | local plate = GetVehicleNumberPlateText(veh) 18 | -- ox_core doesn't have a vehicle key system, so we'll just leave this empty so people can fill it out on their own. 19 | end 20 | 21 | function hasPlyLoaded() 22 | return player and true or false 23 | end 24 | 25 | function DoNotification(text, nType) 26 | lib.notify({ title = "Notification", description = text, type = nType, }) 27 | end 28 | -------------------------------------------------------------------------------- /config.lua: -------------------------------------------------------------------------------- 1 | return { 2 | Debug = false, 3 | Fuel = { 4 | enable = false, -- I use ox_fuel so I set this to false and use statebag to set the fuel 5 | script = 'LegacyFuel', 6 | }, 7 | Ped = 'mp_m_weapexp_01', 8 | PedCoords = vec4(-413.96, 6171.53, 30.48, 320.39), 9 | VehicleSpawn = vec4(-411.37, 6175.33, 31.48, 228.09), 10 | DeliveryInfo = { 11 | title = 'Cargo Delivery', 12 | msg = 'Deliver the cargo to the set location.', 13 | sec = 3, 14 | audioName = 'Boss_Message_Orange', audioRef = 'GTAO_Boss_Goons_FM_Soundset' 15 | }, 16 | ReturnInfo = { 17 | title = 'Delivery Complete', 18 | msg = 'Return back to the warehouse to get paid.', 19 | sec = 7, 20 | audioName = 'Mission_Pass_Notify', audioRef = 'DLC_HEISTS_GENERAL_FRONTEND_SOUNDS' 21 | }, 22 | Target = true, -- true = use target | false = ox lib zones [E] to interact. 23 | } 24 | -------------------------------------------------------------------------------- /sv_routes.lua: -------------------------------------------------------------------------------- 1 | return { 2 | { 3 | vehicle = 'flatbed', 4 | prop = 'reh_prop_reh_tarpcrate_01a', 5 | routes = { 6 | vec3(-217.21, -1361.46, 31.26), 7 | vec3(66.42, 121.47, 79.11), 8 | vec3(-560.05, 302.05, 83.17), 9 | vec3(-1132.72, 2697.24, 18.8), 10 | vec3(2900.75, 4395.3, 50.28), 11 | vec3(-743.48, -1504.03, 5.0), 12 | vec3(744.02, -969.11, 24.58), 13 | }, 14 | attach = vec3(0.0, -2.11, 0.45), 15 | payout = { min = 2500, max = 3700, } 16 | }, 17 | { 18 | vehicle = 'dloader', 19 | prop = 'prop_mil_crate_02', 20 | routes = { 21 | vec3(1688.46, 3748.94, 34.19), 22 | vec3(2410.31, 3129.27, 48.2), 23 | vec3(-1819.57, 3134.15, 32.79), 24 | vec3(-3158.1, 1130.32, 20.85), 25 | vec3(-1319.9, -389.64, 36.45), 26 | vec3(-703.52, -870.01, 23.47), 27 | vec3(-7.87, -1112.16, 28.56), 28 | vec3(821.9, -2142.67, 28.85), 29 | }, 30 | attach = vec3(0.0, -1.51, 0.85), 31 | payout = { min = 2100, max = 3200, } 32 | }, 33 | } -------------------------------------------------------------------------------- /sv_cargo.lua: -------------------------------------------------------------------------------- 1 | local jobRoutes = lib.require('sv_routes') 2 | local Config = lib.require('config') 3 | local storedRoute = {} 4 | 5 | local function setCargoVehicle(source, truck, prop) 6 | local cargoVeh = CreateVehicle(joaat(truck), Config.VehicleSpawn.x, Config.VehicleSpawn.y, Config.VehicleSpawn.z, Config.VehicleSpawn.w, true, true) 7 | local ped = GetPlayerPed(source) 8 | 9 | while not DoesEntityExist(cargoVeh) do Wait(0) end 10 | 11 | while GetVehiclePedIsIn(ped, false) ~= cargoVeh do 12 | TaskWarpPedIntoVehicle(ped, cargoVeh, -1) 13 | Wait(0) 14 | end 15 | 16 | local crate = CreateObject(joaat(prop), Config.VehicleSpawn.x, Config.VehicleSpawn.y, Config.VehicleSpawn.z-5.0, true, true, false) 17 | while not DoesEntityExist(crate) do Wait(10) end 18 | 19 | return cargoVeh, crate 20 | end 21 | 22 | lib.callback.register('randol_cargo:server:beginRoute', function(source) 23 | local src = source 24 | local Player = GetPlayer(src) 25 | local roll = math.random(#jobRoutes) 26 | local MY_ROUTE = jobRoutes[roll] 27 | if storedRoute[src] then return false end 28 | 29 | local vehicle, crate = setCargoVehicle(src, MY_ROUTE.vehicle, MY_ROUTE.prop) 30 | 31 | storedRoute[src] = { 32 | vehicle = vehicle, 33 | prop = MY_ROUTE.prop, 34 | attach = MY_ROUTE.attach, 35 | payout = math.random(MY_ROUTE.payout.min, MY_ROUTE.payout.max), 36 | route = MY_ROUTE.routes[math.random(#MY_ROUTE.routes)], 37 | complete = false, 38 | crateHandle = crate, 39 | } 40 | 41 | TriggerClientEvent('randol_cargo:client:startRoute', src, storedRoute[src], NetworkGetNetworkIdFromEntity(vehicle), NetworkGetNetworkIdFromEntity(crate)) 42 | return true 43 | end) 44 | 45 | lib.callback.register('randol_cargo:server:updateRoute', function(source, CRATE_NET) 46 | if not CRATE_NET or not storedRoute[source] then return false end 47 | 48 | local src = source 49 | local Player = GetPlayer(src) 50 | local pos = GetEntityCoords(GetPlayerPed(src)) 51 | local entity = NetworkGetEntityFromNetworkId(CRATE_NET) 52 | 53 | local data = storedRoute[src] 54 | if not data.complete then 55 | if #(pos - data.route) < 15.0 then 56 | data.complete = true 57 | DeleteEntity(entity) 58 | return true 59 | end 60 | end 61 | return false 62 | end) 63 | 64 | lib.callback.register('randol_cargo:server:finishRoute', function(source) 65 | if not storedRoute[source] then return false end 66 | 67 | local src = source 68 | local Player = GetPlayer(src) 69 | local details = storedRoute[src] 70 | local truck = details.vehicle 71 | 72 | if details.complete then 73 | local payment = details.payout 74 | DoNotification(src, ('You were paid $%s'):format(payment), 'success') 75 | AddMoney(Player, 'cash', payment, 'cargo-job') 76 | else 77 | DoNotification(src, 'You ended your run without completing the route so you weren\'t paid.', 'error') 78 | local entity = storedRoute[src].crateHandle 79 | 80 | if DoesEntityExist(entity) then DeleteEntity(entity) end 81 | end 82 | 83 | if DoesEntityExist(truck) then DeleteEntity(truck) end 84 | 85 | storedRoute[src] = nil 86 | return true 87 | end) 88 | 89 | AddEventHandler('playerDropped', function() 90 | if storedRoute[source] then 91 | if DoesEntityExist(storedRoute[source].vehicle) then 92 | DeleteEntity(storedRoute[source].vehicle) 93 | end 94 | if DoesEntityExist(storedRoute[source].crateHandle) then 95 | DeleteEntity(storedRoute[source].crateHandle) 96 | end 97 | storedRoute[source] = nil 98 | end 99 | end) 100 | -------------------------------------------------------------------------------- /cl_cargo.lua: -------------------------------------------------------------------------------- 1 | local Config = lib.require('config') 2 | 3 | local isHired, droppingOff, showText = false, false, false 4 | local CRATE_OBJECT, cargoPed, DropOffZone, jobBlip, startPoint, pedInteract 5 | local routeData = {} 6 | 7 | local CARGO_BLIP = AddBlipForCoord(Config.PedCoords.x, Config.PedCoords.y, Config.PedCoords.z) 8 | SetBlipSprite(CARGO_BLIP, 615) 9 | SetBlipDisplay(CARGO_BLIP, 4) 10 | SetBlipScale(CARGO_BLIP, 0.8) 11 | SetBlipAsShortRange(CARGO_BLIP, true) 12 | SetBlipColour(CARGO_BLIP, 52) 13 | BeginTextCommandSetBlipName("STRING") 14 | AddTextComponentSubstringPlayerName("Cargo Delivery") 15 | EndTextCommandSetBlipName(CARGO_BLIP) 16 | 17 | local function targetLocalEntity(entity, options, distance) 18 | if GetResourceState('ox_target') == 'started' then 19 | for _, option in ipairs(options) do 20 | option.distance = distance 21 | option.onSelect = option.action 22 | option.action = nil 23 | end 24 | exports.ox_target:addLocalEntity(entity, options) 25 | else 26 | exports['qb-target']:AddTargetEntity(entity, { 27 | options = options, 28 | distance = distance 29 | }) 30 | end 31 | end 32 | 33 | local function showCargoScaleform(bool) 34 | local scaleform = lib.requestScaleformMovie('MIDSIZED_MESSAGE', 1000) 35 | local info = Config.DeliveryInfo 36 | BeginScaleformMovieMethod(scaleform, 'SHOW_COND_SHARD_MESSAGE') 37 | if bool then info = Config.ReturnInfo end 38 | 39 | PushScaleformMovieMethodParameterString(info.title) 40 | PushScaleformMovieMethodParameterString(info.msg) 41 | EndScaleformMovieMethod() 42 | PlaySoundFrontend(-1, info.audioName, info.audioRef, 0) 43 | local sec = info.sec 44 | while sec > 0 do 45 | Wait(1) 46 | sec -= 0.01 47 | DrawScaleformMovieFullscreen(scaleform, 255, 255, 255, 255) 48 | end 49 | SetScaleformMovieAsNoLongerNeeded(scaleform) 50 | end 51 | 52 | local function finishRoute() 53 | if not isHired then return end 54 | local ped = cache.ped 55 | local success = lib.callback.await('randol_cargo:server:finishRoute', false) 56 | if success then 57 | if DropOffZone then DropOffZone:remove() end 58 | if DoesBlipExist(jobBlip) then RemoveBlip(jobBlip) end 59 | isHired = false 60 | CRATE_OBJECT = nil 61 | table.wipe(routeData) 62 | end 63 | end 64 | 65 | local function interactContext() 66 | lib.registerContext({ 67 | id = 'interact_cargo', 68 | title = "Cargo Deliveries", 69 | options = { 70 | { 71 | title = "Start Cargo Delivery", 72 | icon = "fa-solid fa-truck-moving", 73 | disabled = isHired, 74 | onSelect = function() 75 | if IsAnyVehicleNearPoint(Config.VehicleSpawn.x, Config.VehicleSpawn.y, Config.VehicleSpawn.z, 5.0) then 76 | DoNotification('A vehicle is blocking the spawn.', 'error') 77 | return 78 | end 79 | local success = lib.callback.await('randol_cargo:server:beginRoute', false) 80 | end, 81 | }, 82 | { 83 | title = "Finish Delivery", 84 | icon = "fa-solid fa-clipboard-check", 85 | disabled = not isHired, 86 | onSelect = function() 87 | finishRoute() 88 | end, 89 | }, 90 | } 91 | }) 92 | lib.showContext('interact_cargo') 93 | end 94 | 95 | local function nearZone(point) 96 | DrawMarker(1, point.coords.x, point.coords.y, point.coords.z - 1, 0, 0, 0, 0, 0, 0, 6.0, 6.0, 1.5, 79, 194, 247, 165, 0, 0, 0,0) 97 | 98 | if point.isClosest and point.currentDistance <= 4 then 99 | if not showText then 100 | showText = true 101 | lib.showTextUI('**E** - Deliver Cargo', {position = "left-center"}) 102 | end 103 | if isHired and cache.vehicle and IsEntityAttachedToEntity(cache.vehicle, CRATE_OBJECT) then 104 | if IsControlJustPressed(0, 38) and not droppingOff then 105 | droppingOff = true 106 | lib.hideTextUI() 107 | if lib.progressCircle({ 108 | duration = 5000, 109 | position = 'bottom', 110 | label = "Dropping cargo..", 111 | useWhileDead = true, 112 | canCancel = false, 113 | disable = { move = true, car = true, mouse = false, combat = true, }, 114 | }) then 115 | DropOffZone:remove() 116 | DropOffZone = nil 117 | RemoveBlip(jobBlip) 118 | NetworkFadeOutEntity(CRATE_OBJECT, 0, 1) 119 | Wait(500) 120 | local success = lib.callback.await('randol_cargo:server:updateRoute', false, NetworkGetNetworkIdFromEntity(CRATE_OBJECT)) 121 | if success then 122 | droppingOff = false 123 | showCargoScaleform(true) 124 | end 125 | end 126 | end 127 | end 128 | elseif showText then 129 | showText = false 130 | lib.hideTextUI() 131 | end 132 | end 133 | 134 | local function setRoute(route) 135 | jobBlip = AddBlipForCoord(route.x, route.y, route.z) 136 | SetBlipSprite(jobBlip, 615) 137 | SetBlipDisplay(jobBlip, 4) 138 | SetBlipScale(jobBlip, 1.0) 139 | SetBlipFlashes(jobBlip, false) 140 | SetBlipAsShortRange(jobBlip, true) 141 | SetBlipColour(jobBlip, 3) 142 | SetBlipRoute(jobBlip, true) 143 | SetBlipRouteColour(jobBlip, 3) 144 | BeginTextCommandSetBlipName("STRING") 145 | AddTextComponentSubstringPlayerName("Delivery Point") 146 | EndTextCommandSetBlipName(jobBlip) 147 | 148 | DropOffZone = lib.points.new({ coords = vec3(route.x, route.y, route.z), distance = 30, nearby = nearZone, }) 149 | isHired = true 150 | showCargoScaleform() 151 | end 152 | 153 | local function yeetPed() 154 | if DoesEntityExist(cargoPed) then 155 | if GetResourceState('ox_target') == 'started' then 156 | exports.ox_target:removeLocalEntity(cargoPed, {'Start Cargo Delivery', 'Finish Delivery'}) 157 | else 158 | exports['qb-target']:RemoveTargetEntity(cargoPed, {'Start Cargo Delivery', 'Finish Delivery'}) 159 | end 160 | DeleteEntity(cargoPed) 161 | cargoPed = nil 162 | if pedInteract then 163 | pedInteract:remove() 164 | pedInteract = nil 165 | end 166 | end 167 | end 168 | 169 | local function spawnPed() 170 | if DoesEntityExist(cargoPed) then return end 171 | 172 | lib.requestModel(joaat(Config.Ped)) 173 | cargoPed = CreatePed(0, Config.Ped, Config.PedCoords, false, false) 174 | SetEntityAsMissionEntity(cargoPed, true, true) 175 | SetPedFleeAttributes(cargoPed, 0, 0) 176 | SetBlockingOfNonTemporaryEvents(cargoPed, true) 177 | SetEntityInvincible(cargoPed, true) 178 | FreezeEntityPosition(cargoPed, true) 179 | SetModelAsNoLongerNeeded(joaat(Config.Ped)) 180 | 181 | if Config.Target then 182 | targetLocalEntity(cargoPed, { 183 | { 184 | icon = "fa-solid fa-truck-moving", 185 | label = "Start Cargo Delivery", 186 | canInteract = function() return not isHired end, 187 | action = function() 188 | if IsAnyVehicleNearPoint(Config.VehicleSpawn.x, Config.VehicleSpawn.y, Config.VehicleSpawn.z, 5.0) then 189 | DoNotification('A vehicle is blocking the spawn.', 'error') 190 | return 191 | end 192 | local success = lib.callback.await('randol_cargo:server:beginRoute', false) 193 | end, 194 | }, 195 | { 196 | icon = "fa-solid fa-clipboard-check", 197 | label = "Finish Delivery", 198 | canInteract = function() return isHired end, 199 | action = function() 200 | finishRoute() 201 | end, 202 | }, 203 | }, 1.5) 204 | else 205 | pedInteract = lib.zones.box({ 206 | coords = vec3(Config.PedCoords.x, Config.PedCoords.y, Config.PedCoords.z+0.5), 207 | size = vector3(2, 2, 2), 208 | rotation = GetEntityHeading(cargoPed), 209 | debug = false, 210 | onEnter = function() 211 | lib.showTextUI('**E** - Interact', {position = "left-center"}) 212 | end, 213 | onExit = function() 214 | lib.hideTextUI() 215 | end, 216 | inside = function() 217 | if IsControlJustPressed(0, 38) then 218 | interactContext() 219 | end 220 | end, 221 | }) 222 | end 223 | end 224 | 225 | RegisterNetEvent('randol_cargo:client:startRoute', function(data, vehNet, crateNet) 226 | if GetInvokingResource() then return end 227 | routeData = data 228 | 229 | local veh = lib.waitFor(function() 230 | if NetworkDoesEntityExistWithNetworkId(vehNet) then 231 | return NetToVeh(vehNet) 232 | end 233 | end, 'Could not load entity in time.', 1000) 234 | 235 | local rnd = tostring(math.random(1000, 9999)) 236 | CRATE_OBJECT = NetworkGetEntityFromNetworkId(crateNet) 237 | 238 | SetVehicleNumberPlateText(veh, "CARG"..rnd) 239 | handleVehicleKeys(veh) 240 | SetVehicleEngineOn(veh, true, true) 241 | SetVehicleExtra(veh, 2, true) 242 | SetVehicleExtra(veh, 3, true) 243 | 244 | if Config.Fuel.enable then 245 | exports[Config.Fuel.script]:SetFuel(veh, 100.0) 246 | else 247 | Entity(veh).state.fuel = 100 248 | end 249 | 250 | local x, y, z = routeData.attach.x, routeData.attach.y, routeData.attach.z 251 | AttachEntityToEntity(CRATE_OBJECT, veh, GetEntityBoneIndexByName(veh, 'bodyshell'), x, y, z, 0, 0, 0, 1, 1, 0, 1, 0, 1) 252 | FreezeEntityPosition(CRATE_OBJECT, true) 253 | setRoute(routeData.route) 254 | end) 255 | 256 | local function createStartPoint() 257 | startPoint = lib.points.new({ 258 | coords = Config.PedCoords.xyz, 259 | distance = 30, 260 | onEnter = spawnPed, 261 | onExit = yeetPed, 262 | }) 263 | end 264 | 265 | function OnPlayerLoaded() 266 | createStartPoint() 267 | end 268 | 269 | function OnPlayerUnload() 270 | if DoesEntityExist(cargoPed) then 271 | if GetResourceState('ox_target') == 'started' then 272 | exports.ox_target:removeLocalEntity(cargoPed, {'Start Cargo Delivery', 'Finish Delivery'}) 273 | else 274 | exports['qb-target']:RemoveTargetEntity(cargoPed, {'Start Cargo Delivery', 'Finish Delivery'}) 275 | end 276 | DeleteEntity(cargoPed) 277 | end 278 | if DoesBlipExist(jobBlip) then RemoveBlip(jobBlip) end 279 | if startPoint then startPoint:remove() end 280 | if DropOffZone then DropOffZone:remove() end 281 | if isHired then isHired = false end 282 | table.wipe(routeData) 283 | end 284 | 285 | AddEventHandler('onResourceStart', function(resource) 286 | if GetCurrentResourceName() ~= resource or not hasPlyLoaded() then return end 287 | createStartPoint() 288 | end) 289 | 290 | AddEventHandler('onResourceStop', function(resourceName) 291 | if GetCurrentResourceName() == resourceName then 292 | if DropOffZone then DropOffZone:remove() end 293 | if DoesEntityExist(cargoPed) then 294 | if GetResourceState('ox_target') == 'started' then 295 | exports.ox_target:removeLocalEntity(cargoPed, {'Start Cargo Delivery', 'Finish Delivery'}) 296 | else 297 | exports['qb-target']:RemoveTargetEntity(cargoPed, {'Start Cargo Delivery', 'Finish Delivery'}) 298 | end 299 | DeleteEntity(cargoPed) 300 | end 301 | end 302 | end) 303 | --------------------------------------------------------------------------------