├── config.lua ├── fxmanifest.lua ├── bridge ├── client │ ├── esx.lua │ ├── nd.lua │ └── qb.lua └── server │ ├── nd.lua │ ├── esx.lua │ └── qb.lua ├── README.md ├── sv_config.lua ├── sv_trucking.lua └── cl_trucking.lua /config.lua: -------------------------------------------------------------------------------- 1 | return { 2 | Fuel = {enable = true, script = 'LegacyFuel'}, 3 | BossModel = `a_m_m_farmer_01`, 4 | BossCoords = vec4(1197.0, -3109.78, 5.03, 0.71), 5 | VehicleSpawn = vec4(1190.82, -3099.81, 6.09, 89.03), 6 | } -------------------------------------------------------------------------------- /fxmanifest.lua: -------------------------------------------------------------------------------- 1 | fx_version 'cerulean' 2 | game 'gta5' 3 | 4 | author 'Randolio' 5 | description 'Trucking Job' 6 | 7 | shared_scripts { 8 | 'config.lua', 9 | '@ox_lib/init.lua' 10 | } 11 | 12 | client_scripts { 13 | 'bridge/client/**.lua', 14 | 'cl_trucking.lua' 15 | } 16 | 17 | server_scripts { 18 | 'bridge/server/**.lua', 19 | 'sv_config.lua', 20 | 'sv_trucking.lua', 21 | } 22 | 23 | lua54 'yes' 24 | -------------------------------------------------------------------------------- /bridge/client/esx.lua: -------------------------------------------------------------------------------- 1 | if GetResourceState('es_extended') ~= 'started' then return end 2 | local ox_inv = GetResourceState('ox_inventory') == 'started' 3 | 4 | local ESX = exports['es_extended']:getSharedObject() 5 | 6 | RegisterNetEvent('esx:playerLoaded', function(xPlayer) 7 | ESX.PlayerLoaded = true 8 | OnPlayerLoaded() 9 | end) 10 | 11 | RegisterNetEvent('esx:onPlayerLogout', function() 12 | ESX.PlayerLoaded = false 13 | OnPlayerUnload() 14 | end) 15 | 16 | function hasPlyLoaded() 17 | return ESX.PlayerLoaded 18 | end 19 | 20 | function hasItem(item) 21 | if not ox_inv then return 0 end 22 | local count = exports.ox_inventory:Search('count', item) 23 | return count and count > 0 24 | end 25 | 26 | function DoNotification(text, nType) 27 | ESX.ShowNotification(text, nType) 28 | end 29 | 30 | function handleVehicleKeys(veh) 31 | -- ? 32 | end -------------------------------------------------------------------------------- /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 hasPlyLoaded() 16 | return LocalPlayer.state.isLoggedIn 17 | end 18 | 19 | function hasItem(item) 20 | local count = exports.ox_inventory:Search('count', item) 21 | return count and count > 0 22 | end 23 | 24 | function DoNotification(text, nType) 25 | NDCore:notify({ title = "Notification", description = text, type = nType, }) 26 | end 27 | 28 | function handleVehicleKeys(veh) 29 | SetTimeout(1000, function() 30 | SetVehicleDoorsLocked(veh, 0) 31 | end) 32 | end 33 | -------------------------------------------------------------------------------- /bridge/client/qb.lua: -------------------------------------------------------------------------------- 1 | if GetResourceState('qb-core') ~= 'started' then return end 2 | 3 | local QBCore = exports['qb-core']:GetCoreObject() 4 | local ox_inv = GetResourceState('ox_inventory') == 'started' 5 | 6 | RegisterNetEvent('QBCore:Client:OnPlayerLoaded', function() 7 | OnPlayerLoaded() 8 | end) 9 | 10 | RegisterNetEvent('QBCore:Client:OnPlayerUnload', function() 11 | OnPlayerUnload() 12 | end) 13 | 14 | function hasPlyLoaded() 15 | return LocalPlayer.state.isLoggedIn 16 | end 17 | 18 | function hasItem(item) 19 | if ox_inv then 20 | local count = exports.ox_inventory:Search('count', item) 21 | return count and count > 0 22 | else 23 | return QBCore.Functions.HasItem(item) 24 | end 25 | end 26 | 27 | function DoNotification(text, nType) 28 | QBCore.Functions.Notify(text, nType) 29 | end 30 | 31 | function handleVehicleKeys(veh) 32 | local plate = GetVehicleNumberPlateText(veh) 33 | TriggerServerEvent('qb-vehiclekeys:server:AcquireVehicleKeys', plate) 34 | end -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Randolio: Trucking 2 | 3 | **ESX/QB support with bridge** 4 | 5 | A modern trucking job with a built in queue system to generate new routes to all those in the queue every x amount of minutes. 6 | A player can have a max of 5 routes at a time, completion of a route will remove it from your routes and you will be paid. 7 | 8 | Manually clocking out will wipe all your current routes, remove you from the queue and delete your work vehicle and any trailers. 9 | You have the ability to abort your current route, if you happen to run into any trouble with the trailer. 10 | 11 | Routes are stored server side, so you can disconnect from the server, come back and your previous routes will still be there and will auto clock you back in on player load. 12 | 13 | **Note**: I'm aware that you may encounter an issue where multiple players may have the same pickup point and their trailers may spawn on top of each other. It is what it is. 14 | 15 | 16 | # Showcase 17 | https://streamable.com/a03nxr 18 | 19 | ## Requirements 20 | 21 | * [ox_lib](https://github.com/overextended/ox_lib/releases/) 22 | 23 | 24 | **You have permission to use this in your server and edit for your personal needs but are not allowed to redistribute.** -------------------------------------------------------------------------------- /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 GetPlyIdentifier(player) 15 | return player?.id 16 | end 17 | 18 | function GetSourceFromIdentifier(cid) 19 | local players = NDCore:getPlayers() 20 | for _, info in pairs(players) do 21 | if info.id == cid then 22 | return info.source 23 | end 24 | end 25 | return false 26 | end 27 | 28 | function GetCharacterName(Player) 29 | return player?.fullname 30 | end 31 | 32 | function AddItem(Player, item, amount) 33 | exports.ox_inventory:AddItem(Player.source, item, amount) 34 | end 35 | 36 | function RemoveItem(Player, item, amount) 37 | exports.ox_inventory:RemoveItem(Player.source, item, amount) 38 | end 39 | 40 | function AddMoney(Player, moneyType, amount) 41 | Player.addMoney(moneyType, amount) 42 | end 43 | 44 | function itemCount(Player, item) 45 | local count = exports.ox_inventory:GetItemCount(Player.source, item) 46 | return count 47 | end 48 | 49 | function itemLabel(item) 50 | return exports.ox_inventory:Items(item).label 51 | end 52 | 53 | AddEventHandler("ND:characterLoaded", function(character) 54 | if not character then return end 55 | OnPlayerLoaded(character.source) 56 | end) 57 | 58 | AddEventHandler("ND:characterUnloaded", function(src, character) 59 | OnPlayerUnload(src) 60 | end) 61 | -------------------------------------------------------------------------------- /bridge/server/esx.lua: -------------------------------------------------------------------------------- 1 | if GetResourceState('es_extended') ~= 'started' then return end 2 | local ox_inv = GetResourceState('ox_inventory') == 'started' 3 | 4 | local ESX = exports['es_extended']:getSharedObject() 5 | 6 | function GetPlayer(id) 7 | return ESX.GetPlayerFromId(id) 8 | end 9 | 10 | function DoNotification(src, text, nType) 11 | TriggerClientEvent('esx:showNotification', src, text, nType) 12 | end 13 | 14 | function GetPlyIdentifier(xPlayer) 15 | return xPlayer.identifier 16 | end 17 | 18 | function GetSourceFromIdentifier(cid) 19 | local xPlayer = ESX.GetPlayerFromIdentifier(cid) 20 | return xPlayer and xPlayer.source or false 21 | end 22 | 23 | function GetCharacterName(xPlayer) 24 | return xPlayer.getName() 25 | end 26 | 27 | function AddItem(xPlayer, item, amount) 28 | if ox_inv then 29 | exports.ox_inventory:AddItem(xPlayer.source, item, amount) 30 | end 31 | end 32 | 33 | function RemoveItem(xPlayer, item, amount) 34 | if ox_inv then 35 | exports.ox_inventory:RemoveItem(xPlayer.source, item, amount) 36 | end 37 | end 38 | 39 | function AddMoney(xPlayer, moneyType, amount) 40 | local account = moneyType == 'cash' and 'money' or moneyType 41 | xPlayer.addAccountMoney(account, amount) 42 | end 43 | 44 | function itemCount(xPlayer, item) 45 | if not ox_inv then return 0 end 46 | 47 | local count = exports.ox_inventory:GetItemCount(xPlayer.source, item) 48 | return count 49 | end 50 | 51 | function itemLabel(item) 52 | return exports.ox_inventory:Items(item).label 53 | end 54 | 55 | AddEventHandler('esx:playerLogout', function(playerId) 56 | OnPlayerUnload(playerId) 57 | end) 58 | 59 | AddEventHandler('esx:playerLoaded', function(source) 60 | OnPlayerLoaded(source) 61 | end) 62 | -------------------------------------------------------------------------------- /sv_config.lua: -------------------------------------------------------------------------------- 1 | return { 2 | QueueTimer = 10, -- every 10 minutes, it will generate a route for all the players in queue who have less than 5 routes stored. 3 | Payment = { 4 | min = 1500, 5 | max = 2400, 6 | }, 7 | Deliveries = { 8 | vec3(1004.78, -1381.54, 31.37), 9 | vec3(137.82, 312.4, 112.14), 10 | vec3(581.68, 130.11, 98.04), 11 | vec3(-1057.19, -2019.36, 13.16), 12 | vec3(690.81, 603.72, 128.91), 13 | vec3(1525.84, 785.08, 77.43), 14 | vec3(-2965.65, 370.62, 14.74), 15 | vec3(1206.53, 1859.04, 78.92), 16 | vec3(736.88, 2534.39, 73.17), 17 | vec3(585.82, 2790.33, 42.17), 18 | vec3(-1142.25, 2680.74, 18.1), 19 | vec3(-1782.78, -379.43, 44.98), 20 | vec3(-675.84, -2390.05, 13.85), 21 | vec3(-3156.71, 1150.7, 21.07), 22 | vec3(-878.02, -2732.84, 13.83), 23 | vec3(-1026.11, -2374.17, 13.94), 24 | vec3(143.59, -3000.76, 7.03), 25 | vec3(-317.46, -2745.42, 6.01), 26 | vec3(-1267.14, -820.18, 17.1), 27 | vec3(-744.11, -1504.82, 4.98), 28 | vec3(-1151.9, -205.66, 37.96), 29 | vec3(-827.32, -1263.61, 5.0), 30 | }, 31 | Pickups = { 32 | vec4(361.62, -2541.45, 5.74, 88.47), 33 | vec4(1185.48, -3028.04, 5.9, 1.8), 34 | vec4(808.34, -841.19, 26.36, 90.16), 35 | vec4(699.85, -1112.91, 22.52, 343.58), 36 | vec4(627.38, -2890.94, 6.04, 89.44), 37 | vec4(728.93, -1370.37, 26.39, 99.42), 38 | vec4(903.88, -1735.43, 30.51, 266.82), 39 | vec4(1126.46, -2914.82, 5.9, 184.59), 40 | vec4(1273.67, -3187.22, 5.9, 92.33), 41 | vec4(799.08, -2934.35, 5.89, 268.65), 42 | vec4(842.91, -3033.88, 5.74, 86.13), 43 | vec4(1273.62, -3202.21, 5.9, 87.92), 44 | vec4(838.7, -1984.29, 29.3, 354.95), 45 | vec4(1212.03, -2958.28, 5.87, 90.7), 46 | vec4(1180.0, -2913.84, 5.9, 178.49), 47 | vec4(448.96, -1968.45, 22.94, 221.24), 48 | vec4(1270.54, -3294.52, 5.9, 89.52), 49 | vec4(1137.69, -3099.89, 5.86, 273.53), 50 | vec4(238.61, -2240.85, 5.86, 275.12), 51 | vec4(1274.17, -3230.06, 5.9, 90.63), 52 | vec4(-181.65, -2246.78, 7.81, 357.64), 53 | vec4(-54.54, -1836.6, 26.56, 320.29), 54 | }, 55 | Trailers = {'trailers', 'trailers2', 'trailers3'}, 56 | Trucks = {'hauler', 'packer', 'phantom', 'phantom3'} 57 | } 58 | -------------------------------------------------------------------------------- /bridge/server/qb.lua: -------------------------------------------------------------------------------- 1 | if GetResourceState('qb-core') ~= 'started' then return end 2 | 3 | local QBCore = exports['qb-core']:GetCoreObject() 4 | local ox_inv = GetResourceState('ox_inventory') == 'started' 5 | 6 | function GetPlayer(id) 7 | return QBCore.Functions.GetPlayer(id) 8 | end 9 | 10 | function DoNotification(src, text, nType) 11 | TriggerClientEvent('QBCore:Notify', src, text, nType) 12 | end 13 | 14 | function GetPlyIdentifier(Player) 15 | return Player.PlayerData.citizenid 16 | end 17 | 18 | function GetSourceFromIdentifier(cid) 19 | local Player = QBCore.Functions.GetPlayerByCitizenId(cid) 20 | return Player and Player.PlayerData.source or false 21 | end 22 | 23 | function GetCharacterName(Player) 24 | return Player.PlayerData.charinfo.firstname.. ' ' ..Player.PlayerData.charinfo.lastname 25 | end 26 | 27 | function AddItem(Player, item, amount) 28 | if ox_inv then 29 | exports.ox_inventory:AddItem(Player.PlayerData.source, item, amount) 30 | else 31 | Player.Functions.AddItem(item, amount, false) 32 | TriggerClientEvent("inventory:client:ItemBox", Player.PlayerData.source, QBCore.Shared.Items[item], "add", amount) 33 | end 34 | end 35 | 36 | function RemoveItem(Player, item, amount) 37 | if ox_inv then 38 | exports.ox_inventory:RemoveItem(Player.PlayerData.source, item, amount) 39 | else 40 | Player.Functions.RemoveItem(item, amount) 41 | TriggerClientEvent("inventory:client:ItemBox", Player.PlayerData.source, QBCore.Shared.Items[item], "remove", amount) 42 | end 43 | end 44 | 45 | function AddMoney(Player, moneyType, amount) 46 | Player.Functions.AddMoney(moneyType, amount) 47 | end 48 | 49 | function itemCount(Player, item) 50 | local count = 0 51 | if ox_inv then 52 | count = exports.ox_inventory:GetItemCount(Player.PlayerData.source, item) 53 | else 54 | for _, data in pairs(Player.PlayerData.items) do -- Apparently qb only counts the amount from the first slot so I gotta do this. 55 | if data.name == item then 56 | count += data.amount 57 | end 58 | end 59 | end 60 | return count 61 | end 62 | 63 | function itemLabel(item) 64 | local label = ox_inv and exports.ox_inventory:Items(item).label or QBCore.Shared.Items[item].label 65 | return label 66 | end 67 | 68 | RegisterNetEvent('QBCore:Server:OnPlayerUnload', function(source) 69 | OnPlayerUnload(source) 70 | end) 71 | 72 | RegisterNetEvent('QBCore:Server:OnPlayerLoaded', function() 73 | OnPlayerLoaded(source) 74 | end) -------------------------------------------------------------------------------- /sv_trucking.lua: -------------------------------------------------------------------------------- 1 | local Server = lib.require('sv_config') 2 | local Config = lib.require('config') 3 | local storedRoutes = {} 4 | local queue = {} 5 | local spawnedTrailers = {} 6 | local handlingPayments = {} 7 | 8 | local function removeFromQueue(cid) 9 | for i, cids in ipairs(queue) do 10 | if cids == cid then 11 | table.remove(queue, i) 12 | break 13 | end 14 | end 15 | end 16 | 17 | local function createTruckingVehicle(source, model, warp, coords) 18 | if not coords then coords = Config.VehicleSpawn end 19 | 20 | -- CreateVehicleServerSetter can be funky and I cba, especially for a temp vehicle. Cry about it. I just need the entity handle. 21 | local vehicle = CreateVehicle(joaat(model), coords.x, coords.y, coords.z, coords.w, true, true) 22 | local ped = GetPlayerPed(source) 23 | 24 | while not DoesEntityExist(vehicle) do Wait(0) end 25 | 26 | if warp then 27 | while GetVehiclePedIsIn(ped, false) ~= vehicle do 28 | TaskWarpPedIntoVehicle(ped, vehicle, -1) 29 | Wait(100) 30 | end 31 | end 32 | 33 | return vehicle 34 | end 35 | 36 | local function resetEverything() 37 | local players = GetPlayers() 38 | if #players > 0 then 39 | for i = 1, #players do 40 | local src = tonumber(players[i]) 41 | local player = GetPlayer(src) 42 | 43 | if player then 44 | if Player(src).state.truckDuty then 45 | Player(src).state:set('truckDuty', false, true) 46 | end 47 | local cid = GetPlyIdentifier(player) 48 | if storedRoutes[cid] and storedRoutes[cid].vehicle and DoesEntityExist(storedRoutes[cid].vehicle) then 49 | DeleteEntity(storedRoutes[cid].vehicle) 50 | end 51 | end 52 | 53 | if spawnedTrailers[src] and DoesEntityExist(spawnedTrailers[src]) then 54 | DeleteEntity(spawnedTrailers[src]) 55 | end 56 | end 57 | end 58 | end 59 | 60 | local function generateRoute(cid) 61 | local data = {} 62 | data.pickup = Server.Pickups[math.random(#Server.Pickups)] 63 | data.payment = math.random(Server.Payment.min, Server.Payment.max) 64 | repeat 65 | data.deliver = Server.Deliveries[math.random(#Server.Deliveries)] 66 | 67 | local found = false 68 | for _, route in ipairs(storedRoutes[cid].routes) do 69 | if route.deliver == data.deliver then 70 | found = true 71 | break 72 | end 73 | end 74 | 75 | if not found then break end 76 | until false 77 | 78 | return data 79 | end 80 | 81 | lib.callback.register('randol_trucking:server:clockIn', function(source) 82 | local src = source 83 | local player = GetPlayer(src) 84 | local cid = GetPlyIdentifier(player) 85 | 86 | if storedRoutes[cid] then 87 | DoNotification(src, 'You have already clocked in. Check your routes.') 88 | return false 89 | end 90 | 91 | queue[#queue+1] = cid 92 | storedRoutes[cid] = { routes = {}, vehicle = 0, } 93 | Player(src).state:set('truckDuty', true, true) 94 | 95 | DoNotification(src, 'You have clocked in for trucking work. Look out for job notifications or check your current routes.', 'success', 7000) 96 | return true 97 | end) 98 | 99 | lib.callback.register('randol_trucking:server:clockOut', function(source) 100 | local src = source 101 | local player = GetPlayer(src) 102 | local cid = GetPlyIdentifier(player) 103 | 104 | if not storedRoutes[cid] or not Player(src).state.truckDuty then 105 | DoNotification(src, 'You are not clocked in to the trucking job.', 'error') 106 | return false 107 | end 108 | 109 | local workTruck = storedRoutes[cid].vehicle 110 | local workTrailer = spawnedTrailers[src] 111 | 112 | if workTruck and DoesEntityExist(workTruck) then DeleteEntity(workTruck) end 113 | if workTrailer and DoesEntityExist(workTrailer) then DeleteEntity(workTrailer) end 114 | 115 | removeFromQueue(cid) 116 | storedRoutes[cid] = nil 117 | Player(src).state:set('truckDuty', false, true) 118 | TriggerClientEvent('randol_trucking:client:clearRoutes', src) 119 | DoNotification(src, 'You have clocked out and cleared all your routes.', 'success') 120 | return true 121 | end) 122 | 123 | lib.callback.register('randol_trucking:server:spawnTruck', function(source) 124 | local src = source 125 | local player = GetPlayer(src) 126 | local cid = GetPlyIdentifier(player) 127 | 128 | if not storedRoutes[cid] or not Player(src).state.truckDuty then 129 | DoNotification(src, 'You are not clocked in to the trucking job.', 'error') 130 | return false 131 | end 132 | 133 | local workTruck = storedRoutes[cid].vehicle 134 | 135 | if DoesEntityExist(workTruck) then 136 | local coords = GetEntityCoords(workTruck) 137 | return false, coords, NetworkGetNetworkIdFromEntity(workTruck) 138 | end 139 | 140 | local model = Server.Trucks[math.random(#Server.Trucks)] 141 | local vehicle = createTruckingVehicle(src, model, true) 142 | 143 | storedRoutes[cid].vehicle = vehicle 144 | DoNotification(src, 'You have pulled out your work truck. Check out your current routes or wait for one to come through.', 'success') 145 | TriggerClientEvent('randol_trucking:server:spawnTruck', src, NetworkGetNetworkIdFromEntity(vehicle)) 146 | return true 147 | end) 148 | 149 | lib.callback.register('randol_trucking:server:spawnTrailer', function(source) 150 | local src = source 151 | local player = GetPlayer(src) 152 | local cid = GetPlyIdentifier(player) 153 | 154 | if not storedRoutes[cid] or not Player(src).state.truckDuty then return false end 155 | 156 | local model = Server.Trailers[math.random(#Server.Trailers)] 157 | local coords = storedRoutes[cid].currentRoute.pickup 158 | local trailer = createTruckingVehicle(src, model, false, coords) 159 | 160 | spawnedTrailers[src] = trailer 161 | return true, NetworkGetNetworkIdFromEntity(trailer) 162 | end) 163 | 164 | lib.callback.register('randol_trucking:server:chooseRoute', function(source, index) 165 | local src = source 166 | local player = GetPlayer(src) 167 | local cid = GetPlyIdentifier(player) 168 | 169 | if not storedRoutes[cid] or not Player(src).state.truckDuty then return false end 170 | 171 | if spawnedTrailers[src] or storedRoutes[cid].currentRoute then 172 | DoNotification(src, 'You already have an active route to complete.', 'success') 173 | return false 174 | end 175 | 176 | storedRoutes[cid].currentRoute = storedRoutes[cid].routes[index] 177 | storedRoutes[cid].currentRoute.index = index 178 | 179 | return storedRoutes[cid].currentRoute 180 | end) 181 | 182 | lib.callback.register('randol_trucking:server:getRoutes', function(source) 183 | local src = source 184 | local player = GetPlayer(src) 185 | local cid = GetPlyIdentifier(player) 186 | 187 | if not storedRoutes[cid] or not Player(src).state.truckDuty then return false end 188 | 189 | return storedRoutes[cid].routes 190 | end) 191 | 192 | lib.callback.register('randol_trucking:server:updateRoute', function(source, netid, route) 193 | if handlingPayments[source] then return false end 194 | handlingPayments[source] = true 195 | local src = source 196 | local player = GetPlayer(src) 197 | local cid = GetPlyIdentifier(player) 198 | local pos = GetEntityCoords(GetPlayerPed(src)) 199 | local entity = NetworkGetEntityFromNetworkId(netid) 200 | local coords = GetEntityCoords(entity) 201 | local data = storedRoutes[cid] 202 | 203 | if not data or not DoesEntityExist(entity) or #(coords - data.currentRoute.deliver.xyz) > 15.0 or #(pos - data.currentRoute.deliver.xyz) > 15.0 then 204 | handlingPayments[src] = nil 205 | return false 206 | end 207 | 208 | if spawnedTrailers[src] == entity and route.index == data.currentRoute.index then 209 | local payout = data.currentRoute.payment 210 | DeleteEntity(entity) 211 | spawnedTrailers[src] = nil 212 | data.currentRoute = nil 213 | table.remove(data.routes, route.index) 214 | AddMoney(player, 'cash', payout) 215 | DoNotification(src, ('You finished the route and received $%s'):format(payout), 'success', 7000) 216 | SetTimeout(2000, function() 217 | handlingPayments[src] = nil 218 | end) 219 | end 220 | end) 221 | 222 | lib.callback.register('randol_trucking:server:abortRoute', function(source, index) 223 | local src = source 224 | local player = GetPlayer(src) 225 | local cid = GetPlyIdentifier(player) 226 | 227 | if not storedRoutes[cid] or not Player(src).state.truckDuty then return false end 228 | 229 | local data = storedRoutes[cid] 230 | 231 | if data.currentRoute and data.currentRoute.index == index then 232 | if spawnedTrailers[src] and DoesEntityExist(spawnedTrailers[src]) then 233 | DeleteEntity(spawnedTrailers[src]) 234 | spawnedTrailers[src] = nil 235 | end 236 | data.currentRoute = nil 237 | table.remove(data.routes, index) 238 | TriggerClientEvent('randol_trucking:client:clearRoutes', src) 239 | return true 240 | end 241 | 242 | return false 243 | end) 244 | 245 | AddEventHandler('onResourceStop', function(resource) 246 | if resource ~= GetCurrentResourceName() then return end 247 | resetEverything() 248 | end) 249 | 250 | AddEventHandler('playerDropped', function() 251 | local src = source 252 | if Player(src).state.truckDuty then 253 | Player(src).state:set('truckDuty', false, true) 254 | if spawnedTrailers[src] and DoesEntityExist(spawnedTrailers[src]) then 255 | DeleteEntity(spawnedTrailers[src]) 256 | end 257 | end 258 | end) 259 | 260 | function OnPlayerLoaded(source) 261 | local src = source 262 | local player = GetPlayer(src) 263 | local cid = GetPlyIdentifier(player) 264 | 265 | if storedRoutes[cid] then 266 | Player(src).state:set('truckDuty', true, true) 267 | end 268 | end 269 | 270 | function OnPlayerUnload(source) 271 | local src = source 272 | if Player(src).state.truckDuty then 273 | Player(src).state:set('truckDuty', false, true) 274 | if spawnedTrailers[src] and DoesEntityExist(spawnedTrailers[src]) then 275 | DeleteEntity(spawnedTrailers[src]) 276 | end 277 | end 278 | end 279 | 280 | local function initQueue() 281 | if #queue == 0 then return end 282 | 283 | for i = 1, #queue do 284 | local cid = queue[i] 285 | local src = GetSourceFromIdentifier(cid) 286 | local player = GetPlayer(src) 287 | if player and Player(src).state.truckDuty then 288 | if #storedRoutes[cid].routes < 5 then 289 | storedRoutes[cid].routes[#storedRoutes[cid].routes + 1] = generateRoute(cid) 290 | DoNotification(src, 'A new route has been added to your current routes.') 291 | end 292 | end 293 | end 294 | end 295 | 296 | SetInterval(initQueue, Server.QueueTimer * 60000) -------------------------------------------------------------------------------- /cl_trucking.lua: -------------------------------------------------------------------------------- 1 | local Config = lib.require('config') 2 | local DropOffZone, activeTrailer, pickupZone, PICKUP_BLIP, DELIVERY_BLIP 3 | local activeRoute = {} 4 | local droppingOff = false 5 | local delay = false 6 | 7 | local TruckerWork = AddBlipForCoord(Config.BossCoords.x, Config.BossCoords.y, Config.BossCoords.z) 8 | SetBlipSprite(TruckerWork, 479) 9 | SetBlipDisplay(TruckerWork, 4) 10 | SetBlipScale(TruckerWork, 0.8) 11 | SetBlipAsShortRange(TruckerWork, true) 12 | SetBlipColour(TruckerWork, 56) 13 | BeginTextCommandSetBlipName('STRING') 14 | AddTextComponentSubstringPlayerName('Trucking Work') 15 | EndTextCommandSetBlipName(TruckerWork) 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 cleanupShit() 34 | if DropOffZone then DropOffZone:remove() DropOffZone = nil end 35 | if pickupZone then pickupZone:remove() pickupZone = nil end 36 | if DoesBlipExist(PICKUP_BLIP) then RemoveBlip(PICKUP_BLIP) end 37 | if DoesBlipExist(DELIVERY_BLIP) then RemoveBlip(DELIVERY_BLIP) end 38 | 39 | activeTrailer, PICKUP_BLIP, DELIVERY_BLIP = nil 40 | table.wipe(activeRoute) 41 | delay = false 42 | droppingOff = false 43 | end 44 | 45 | local function getStreetandZone(coords) 46 | local currentStreetHash = GetStreetNameAtCoord(coords.x, coords.y, coords.z) 47 | local currentStreetName = GetStreetNameFromHashKey(currentStreetHash) 48 | return currentStreetName 49 | end 50 | 51 | local function createRouteBlip(coords, label) 52 | local blip = AddBlipForCoord(coords.x, coords.y, coords.z) 53 | SetBlipSprite(blip, 479) 54 | SetBlipDisplay(blip, 4) 55 | SetBlipScale(blip, 1.0) 56 | SetBlipAsShortRange(blip, true) 57 | SetBlipColour(blip, 3) 58 | SetBlipRoute(blip, true) 59 | SetBlipRouteColour(blip, 3) 60 | BeginTextCommandSetBlipName('STRING') 61 | AddTextComponentSubstringPlayerName(label) 62 | EndTextCommandSetBlipName(blip) 63 | return blip 64 | end 65 | 66 | local function viewRoutes() 67 | local context = {} 68 | 69 | local routes = lib.callback.await('randol_trucking:server:getRoutes', false) 70 | if not next(routes) then 71 | return DoNotification('You do not have any routes currently.', 'error') 72 | end 73 | 74 | for index, data in pairs(routes) do 75 | local isDisabled = activeRoute.index == index 76 | local info = ('Route: %s \nPayment: $%s'):format(getStreetandZone(data.deliver.xyz), data.payment) 77 | context[#context + 1] = { 78 | title = ('%s'):format(getStreetandZone(data.pickup.xyz)), 79 | description = info, 80 | icon = 'fa-solid fa-location-dot', 81 | disabled = isDisabled, 82 | onSelect = function() 83 | local choice = lib.callback.await('randol_trucking:server:chooseRoute', false, index) 84 | if choice and type(choice) == 'table' then 85 | activeRoute = choice 86 | activeRoute.index = index 87 | SetRoute() 88 | end 89 | end, 90 | } 91 | end 92 | 93 | lib.registerContext({ id = 'view_work_routes', title = 'Work Routes', options = context }) 94 | lib.showContext('view_work_routes') 95 | end 96 | 97 | local function nearZone(point) 98 | 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) 99 | 100 | if point.isClosest and point.currentDistance <= 4 then 101 | if not showText then 102 | showText = true 103 | lib.showTextUI('**E** - Deliver Trailer', {position = 'left-center'}) 104 | end 105 | if next(activeRoute) and cache.vehicle and IsEntityAttachedToEntity(cache.vehicle, activeTrailer) then 106 | if IsControlJustPressed(0, 38) and not droppingOff then 107 | droppingOff = true 108 | FreezeEntityPosition(cache.vehicle, true) 109 | lib.hideTextUI() 110 | if lib.progressCircle({ 111 | duration = 5000, 112 | position = 'bottom', 113 | label = 'Dropping trailer..', 114 | useWhileDead = false, 115 | canCancel = false, 116 | disable = { move = true, car = true, mouse = false, combat = true, }, 117 | }) then 118 | DetachEntity(activeTrailer, true, true) 119 | NetworkFadeOutEntity(activeTrailer, 0, 1) 120 | Wait(500) 121 | lib.callback.await('randol_trucking:server:updateRoute', false, NetworkGetNetworkIdFromEntity(activeTrailer), activeRoute) 122 | FreezeEntityPosition(cache.vehicle, false) 123 | cleanupShit() 124 | end 125 | end 126 | end 127 | elseif showText then 128 | showText = false 129 | lib.hideTextUI() 130 | end 131 | end 132 | 133 | local function createDropoff() 134 | RemoveBlip(PICKUP_BLIP) 135 | pickupZone:remove() 136 | DropOffZone = lib.points.new({ coords = vec3(activeRoute.deliver.x, activeRoute.deliver.y, activeRoute.deliver.z), distance = 20, nearby = nearZone }) 137 | DELIVERY_BLIP = createRouteBlip(activeRoute.deliver.xyz, 'Delivery Point') 138 | SetNewWaypoint(activeRoute.deliver.x, activeRoute.deliver.y) 139 | DoNotification('Your delivery route has been marked.', 'success') 140 | Wait(1000) 141 | delay = false 142 | end 143 | 144 | function SetRoute() 145 | PICKUP_BLIP = createRouteBlip(activeRoute.pickup.xyz, 'Pickup Point') 146 | DoNotification('Head to the pick up point and collect your trailer.') 147 | pickupZone = lib.points.new({ 148 | coords = vec3(activeRoute.pickup.x, activeRoute.pickup.y, activeRoute.pickup.z), 149 | distance = 70, 150 | onEnter = function() 151 | if not activeTrailer then 152 | local success, netid = lib.callback.await('randol_trucking:server:spawnTrailer', false) 153 | if success and netid then 154 | activeTrailer = lib.waitFor(function() 155 | if NetworkDoesEntityExistWithNetworkId(netid) then 156 | return NetToVeh(netid) 157 | end 158 | end, 'Could not load entity in time.', 3000) 159 | end 160 | end 161 | end, 162 | nearby = function() 163 | DrawMarker(1, activeRoute.pickup.x, activeRoute.pickup.y, activeRoute.pickup.z - 1, 0, 0, 0, 0, 0, 0, 6.0, 6.0, 1.5, 79, 194, 247, 165, 0, 0, 0,0) 164 | 165 | if cache.vehicle and IsEntityAttachedToEntity(cache.vehicle, activeTrailer) and not delay then 166 | delay = true 167 | createDropoff() 168 | end 169 | end, 170 | }) 171 | end 172 | 173 | local function removePedSpawned() 174 | if GetResourceState('ox_target') == 'started' then 175 | exports.ox_target:removeLocalEntity(truckerPed, {'Clock In', 'Clock Out', 'View Routes', 'Pull Out Vehicle', 'Abort Route'}) 176 | else 177 | exports['qb-target']:RemoveTargetEntity(truckerPed, {'Clock In', 'Clock Out', 'View Routes', 'Pull Out Vehicle', 'Abort Route'}) 178 | end 179 | DeleteEntity(truckerPed) 180 | truckerPed = nil 181 | end 182 | 183 | local function spawnPed() 184 | if DoesEntityExist(truckerPed) then return end 185 | 186 | lib.requestModel(Config.BossModel) 187 | truckerPed = CreatePed(0, Config.BossModel, Config.BossCoords, false, false) 188 | SetEntityAsMissionEntity(truckerPed, true, true) 189 | SetPedFleeAttributes(truckerPed, 0, 0) 190 | SetBlockingOfNonTemporaryEvents(truckerPed, true) 191 | SetEntityInvincible(truckerPed, true) 192 | FreezeEntityPosition(truckerPed, true) 193 | SetModelAsNoLongerNeeded(Config.BossModel) 194 | targetLocalEntity(truckerPed, { 195 | { 196 | num = 1, 197 | icon = 'fa-solid fa-clipboard-check', 198 | label = 'Clock In', 199 | canInteract = function() 200 | return not LocalPlayer.state.truckDuty 201 | end, 202 | action = function() 203 | lib.callback.await('randol_trucking:server:clockIn', false) 204 | end, 205 | }, 206 | { 207 | num = 2, 208 | icon = 'fa-solid fa-clipboard-check', 209 | label = 'Clock Out', 210 | canInteract = function() return LocalPlayer.state.truckDuty end, 211 | action = function() 212 | lib.callback.await('randol_trucking:server:clockOut', false) 213 | end, 214 | }, 215 | { 216 | num = 3, 217 | icon = 'fa-solid fa-clipboard-check', 218 | label = 'View Routes', 219 | canInteract = function() return LocalPlayer.state.truckDuty end, 220 | action = function() 221 | viewRoutes() 222 | end, 223 | }, 224 | { 225 | num = 4, 226 | icon = 'fa-solid fa-truck', 227 | label = 'Pull Out Vehicle', 228 | canInteract = function() return LocalPlayer.state.truckDuty end, 229 | action = function() 230 | if IsAnyVehicleNearPoint(Config.VehicleSpawn.x, Config.VehicleSpawn.y, Config.VehicleSpawn.z, 15.0) then 231 | return DoNotification('A vehicle is blocking the spawn.', 'error') 232 | end 233 | 234 | local success, coords = lib.callback.await('randol_trucking:server:spawnTruck', false) 235 | if not success and coords then 236 | SetNewWaypoint(coords.x, coords.y) 237 | DoNotification('Your work truck is already out. It has been located on your GPS.') 238 | end 239 | end, 240 | }, 241 | { 242 | num = 5, 243 | icon = 'fa-solid fa-xmark', 244 | label = 'Abort Route', 245 | canInteract = function() return LocalPlayer.state.truckDuty and next(activeRoute) end, 246 | action = function() 247 | local success = lib.callback.await('randol_trucking:server:abortRoute', false, activeRoute.index) 248 | if success then 249 | DoNotification('You aborted your current route.', 'error') 250 | end 251 | end, 252 | }, 253 | }, 1.5) 254 | end 255 | 256 | RegisterNetEvent('randol_trucking:client:clearRoutes', function() 257 | if GetInvokingResource() then return end 258 | cleanupShit() 259 | end) 260 | 261 | RegisterNetEvent('randol_trucking:server:spawnTruck', function(netid) 262 | if GetInvokingResource() or not netid then return end 263 | local MY_VEH = lib.waitFor(function() 264 | if NetworkDoesEntityExistWithNetworkId(netid) then 265 | return NetToVeh(netid) 266 | end 267 | end, 'Could not load entity in time.', 3000) 268 | 269 | handleVehicleKeys(MY_VEH) 270 | if Config.Fuel.enable then 271 | exports[Config.Fuel.script]:SetFuel(MY_VEH, 100.0) 272 | else 273 | Entity(MY_VEH).state.fuel = 100 274 | end 275 | end) 276 | 277 | local function createTruckingStart() 278 | truckingPedZone = lib.points.new({ 279 | coords = Config.BossCoords.xyz, 280 | distance = 60, 281 | onEnter = spawnPed, 282 | onExit = removePedSpawned, 283 | }) 284 | end 285 | 286 | AddEventHandler('onResourceStop', function(resourceName) 287 | if GetCurrentResourceName() == resourceName and hasPlyLoaded() then 288 | OnPlayerUnload() 289 | end 290 | end) 291 | 292 | AddEventHandler('onResourceStart', function(resource) 293 | if GetCurrentResourceName() == resource and hasPlyLoaded() then 294 | createTruckingStart() 295 | end 296 | end) 297 | 298 | function OnPlayerLoaded() 299 | createTruckingStart() 300 | end 301 | 302 | function OnPlayerUnload() 303 | if truckingPedZone then truckingPedZone:remove() truckingPedZone = nil end 304 | removePedSpawned() 305 | cleanupShit() 306 | end 307 | --------------------------------------------------------------------------------