├── .gitattributes ├── README.md ├── __INSTALL__ ├── image │ └── recycle.png └── items.txt ├── configs ├── config_main.lua ├── sv_webhook.lua └── utils.lua ├── fxmanifest.lua └── src ├── client └── c_main.lua └── server ├── s_framework.lua └── s_main.lua /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | ![App Screenshot](https://i.imgur.com/wa6J4hE.png) 5 | 6 | 7 | # Recycle Job 8 | 9 | 10 | 11 | We’re excited to release our Recycle Job script, developed in response to popular demand. This fully configurable script allows you to customize items, locations, progress bars, and notifications. It utilizes a few props and requires only ox_lib along with either qb-target or ox_target. Compatible with ESX, QBCore, and Qbox frameworks, this script offers flexibility and ease of use. As always, we provide the same high level of support for this free script as we do for our paid ones via our Discord. 12 | 13 | 14 | ## Compatibility & Dependencies 15 | 16 | **Frameworks:** ESX, Qbox, QBCore 17 | 18 | **Resources:** ox_lib, ox_target or qb-target 19 | 20 | 21 | ## Installation 22 | 23 | - Drag and drop the resource into your server's resource folder 24 | - Add the item 'recycle' to your server's database or inventory system 25 | - Configure the items table in the config file to your servers needs 26 | - Set up your Discord webhook for logging. 27 | 28 | 29 | 30 | ## Feedback & Support 31 | 32 | If you have any feedback or issues, please reach out to us at https://discord.gg/CBSSMpmqrK 33 | 34 | -------------------------------------------------------------------------------- /__INSTALL__/image/recycle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VoidHubScripts/vhs-recycle/449e3ff68d72e6df13786ed2eb12a80c133ba601/__INSTALL__/image/recycle.png -------------------------------------------------------------------------------- /__INSTALL__/items.txt: -------------------------------------------------------------------------------- 1 | ## ESX STANDALONE 2 | 3 | INSERT INTO `items` (`name`, `label`, `weight`, `rare`, `can_remove`) VALUES 4 | ('recycle', 'Recycled Goods', 2, 0, 1); 5 | 6 | ## OX_INVENTORY 7 | 8 | ['recycle'] = { 9 | label = 'Recycled Goods', 10 | weight = 250, 11 | stack = true, 12 | }, 13 | 14 | ## QBCore 15 | 16 | ["recycle"] = {["name"] = "recycle", ["label"] = "Recycled Goods", ["weight"] = 250, ["type"] = "item", ["image"] = "recycle.png", ["unique"] = false, ["useable"] = false, ["shouldClose"] = true, ["combinable"] = nil, ["description"] = ""}, 17 | 18 | -------------------------------------------------------------------------------- /configs/config_main.lua: -------------------------------------------------------------------------------- 1 | Config = Config or {} 2 | 3 | Framework = 'esx' -- esx, qbcore 4 | Notifications = 'ox_lib' -- qbcore, esx, ox_lib 5 | Progress = 'ox_lib_circle' -- ox_lib_circle, ox_lib_bar, qbcore 6 | InventoryImagePath = "nui://ox_inventory/web/images/" 7 | 8 | giveItemAmount = math.random(2,5) 9 | items = {steel = 5, rubber = 3, metalscrap = 6, plastic = 3, copper = 10, fabric = 4} 10 | 11 | depot = { 12 | npc = {zone = vector4(1090.5276, -3103.3540, -39.9999, 357.3629), ped = 'a_m_m_farmer_01', scenario = 'WORLD_HUMAN_CLIPBOARD_FACILITY'}, 13 | sell = {zone = vector4(37.3948, -1288.4828, 28.2922, 291.4622), ped = 'a_m_m_soucent_02', scenario = 'WORLD_HUMAN_STAND_MOBILE'}, 14 | bin = {prop = 'prop_recyclebin_05_a', placement = vector4(1096.2809, -3102.6228, -39.9999, 178.2123)}, 15 | 16 | blip = { sprite = 728, color = 2, size = 1.0 }, 17 | outside = { zone = vector4(36.3291, -1283.9606, 29.2956, 89.5190), 18 | minZ = 2.5, 19 | maxZ = 4.5 20 | }, 21 | inside = { 22 | zone = vector4(1088.0067, -3099.5161, -39.0000, 277.3064) 23 | }, 24 | exit = { 25 | zone = vector3(1087.4320, -3099.3901, -38.9999), 26 | h = 89.7790, 27 | minZ = 1.5, 28 | maxZ = 3.5 29 | }, 30 | duty = { zone = vector3(1087.9154, -3101.2112, -39.1759), 31 | h = 119.7421, 32 | minZ = 1.5, 33 | maxZ = 2.0 34 | }, 35 | } 36 | 37 | 38 | props = { 39 | prop1 = { prop = 'sm_prop_smug_crate_l_fake', loc = vector4(1088.7007, -3096.2437, -40.0000, 1.8227) }, 40 | prop2 = { prop = 'ba_prop_battle_crate_art_02_bc', loc = vector4(1091.2141, -3096.6941, -39.9999, 357.8739) }, 41 | prop3 = { prop = 'ex_prop_crate_narc_bc', loc = vector4(1095.0951, -3096.8997, -39.9999, 357.5524) }, 42 | prop4 = { prop = 'ex_prop_crate_wlife_bc', loc = vector4(1097.7412, -3097.0847, -39.9999, 0.8478) }, 43 | prop5 = { prop = 'ex_prop_crate_clothing_sc', loc = vector4(1101.3965, -3096.7812, -39.9999, 356.3791) }, 44 | prop6 = { prop = 'ex_prop_crate_art_02_sc', loc = vector4(1103.9249, -3096.8760, -39.9999, 356.1726) }, 45 | } 46 | 47 | 48 | -------------------------------------------------------------------------------- /configs/sv_webhook.lua: -------------------------------------------------------------------------------- 1 | WebhookConfig = { 2 | URL = 'YOUR_WEBHOOK_HERE' 3 | 4 | } -------------------------------------------------------------------------------- /configs/utils.lua: -------------------------------------------------------------------------------- 1 | 2 | if Framework == 'esx' then ESX = exports["es_extended"]:getSharedObject() else QBCore = exports['qb-core']:GetCoreObject() end 3 | 4 | 5 | Notify = function(type, title, text, targetClient) 6 | local types = { info = "inform", error = "error", success = "success" } 7 | if not types[type] then return end 8 | 9 | local message = title 10 | if text and text ~= "" then 11 | message = message .. ": " .. text 12 | end 13 | 14 | if Notifications == "ox_lib" then 15 | if IsDuplicityVersion() then 16 | TriggerClientEvent('ox_lib:notify', targetClient, { type = types[type], title = title, position = 'center-right', description = text, }) 17 | else 18 | lib.notify({ title = title, description = text, position = 'center-right', type = types[type] }) 19 | end 20 | elseif Notifications == "esx" then 21 | if IsDuplicityVersion() then 22 | TriggerClientEvent('esx:showNotification', targetClient, message) 23 | else 24 | ESX.ShowNotification(message) 25 | end 26 | elseif Notifications == "qbcore" then 27 | local types = {info = "primary", error = "error", success = "success"} 28 | if types[type] then 29 | if IsDuplicityVersion() then 30 | TriggerClientEvent('QBCore:Notify', targetClient, message, types[type]) 31 | else 32 | QBCore.Functions.Notify(message, types[type]) 33 | end 34 | end 35 | elseif Notifications == "custom" then 36 | -- Custom notification handling 37 | else 38 | -- Default notification handling 39 | end 40 | end 41 | ProgressBar = function(duration, label) 42 | if Progress == "ox_lib_circle" then 43 | lib.progressCircle({ 44 | duration = duration, 45 | label = label, 46 | position = 'bottom', 47 | useWhileDead = false, 48 | canCancel = false, 49 | allowSwimming = true, 50 | disable = { move = true, car = false, combat = true, }, 51 | }) 52 | elseif Progress == "ox_lib_bar" then 53 | lib.progressBar({ 54 | duration = duration, 55 | label = label, 56 | useWhileDead = false, 57 | canCancel = false, 58 | disable = { move = true, car = true, combat = true, }, 59 | }) 60 | elseif Progress == "qbcore" then 61 | QBCore.Functions.Progressbar(label, label, duration, false, false, { disableMovement = true, disableCarMovement = false, disableMouse = false, disableCombat = true, }, {}, {}, {}, function() end) 62 | Wait(duration) 63 | elseif Progress == "custom" then 64 | exports['progressBars']:startUI(duration, label) 65 | end 66 | end 67 | -------------------------------------------------------------------------------- /fxmanifest.lua: -------------------------------------------------------------------------------- 1 | fx_version 'cerulean' 2 | game 'gta5' 3 | lua54 'yes' 4 | 5 | description "vhs-recycle - [ESX, Qbox, QBCore] Free Support: discord.gg/CBSSMpmqrK" 6 | author "VoidHubScripts" 7 | version '1.2' 8 | 9 | 10 | client_scripts { 11 | 'configs/config_main.lua', 12 | 'src/client/c_main.lua' 13 | } 14 | 15 | server_scripts { 16 | '@oxmysql/lib/MySQL.lua', 17 | 'src/server/s_framework.lua', 18 | 'configs/sv_webhook.lua', 19 | 'src/server/s_main.lua', 20 | } 21 | 22 | shared_scripts { 23 | '@ox_lib/init.lua', 24 | 'configs/config_main.lua', 25 | 'configs/utils.lua', 26 | } 27 | -------------------------------------------------------------------------------- /src/client/c_main.lua: -------------------------------------------------------------------------------- 1 | if Framework == 'esx' then ESX = exports["es_extended"]:getSharedObject() else QBCore = exports['qb-core']:GetCoreObject() end 2 | 3 | local spawnedProps = {} 4 | local spawnedNPCs = {} 5 | local selectedProp = nil 6 | local carriedProp = nil 7 | 8 | function spawnProps() 9 | for _, propData in pairs(props) do 10 | local prop = propData.prop 11 | local loc = propData.loc 12 | local propObject = CreateObject(GetHashKey(prop), loc.x, loc.y, loc.z, false, true, false) 13 | SetEntityHeading(propObject, loc.w) 14 | FreezeEntityPosition(propObject, true) 15 | propData.object = propObject 16 | table.insert(spawnedProps, propObject) 17 | end 18 | end 19 | 20 | function createBlip(depot) 21 | local blip = AddBlipForCoord(depot.outside.zone.x, depot.outside.zone.y, depot.outside.zone.z) 22 | SetBlipSprite(blip, depot.blip.sprite) 23 | SetBlipDisplay(blip, 4) 24 | SetBlipScale(blip, depot.blip.size) 25 | SetBlipColour(blip, depot.blip.color) 26 | SetBlipAsShortRange(blip, true) 27 | BeginTextCommandSetBlipName("STRING") 28 | AddTextComponentString("Recycling Depot") 29 | EndTextCommandSetBlipName(blip) 30 | end 31 | 32 | function spawnBin() 33 | local binModel = GetHashKey(depot.bin.prop) 34 | local loc = depot.bin.placement 35 | lib.requestModel(binModel, 10000) 36 | local binObject = CreateObject(binModel, loc.x, loc.y, loc.z, false, true, false) 37 | SetEntityHeading(binObject, loc.w) 38 | FreezeEntityPosition(binObject, true) 39 | depot.bin.object = binObject 40 | table.insert(spawnedProps, binObject) 41 | targetModel(binModel, 'reecyclebin', 'vhs-recycle:process', 'fas fa-trash-alt', 'Process Items', item, nil, interact, job, gang, 1.5) 42 | end 43 | 44 | Citizen.CreateThread(function() 45 | spawnNPC() 46 | spawnProps() 47 | createBlip(depot) 48 | spawnBin() 49 | end) 50 | 51 | function deleteProps() 52 | for _, propObject in ipairs(spawnedProps) do 53 | if DoesEntityExist(propObject) then 54 | DeleteObject(propObject) 55 | end 56 | end 57 | end 58 | 59 | function spawnNPC() 60 | local npcConfigs = { depot.npc, depot.sell } 61 | for _, npcData in pairs(npcConfigs) do 62 | local pedModel = GetHashKey(npcData.ped) 63 | lib.requestModel(pedModel, 10000) 64 | local npc = CreatePed(4, pedModel, npcData.zone.x, npcData.zone.y, npcData.zone.z, npcData.zone.w, false, true) 65 | TaskStartScenarioInPlace(npc, npcData.scenario, 0, true) 66 | SetBlockingOfNonTemporaryEvents(npc, true) 67 | SetEntityInvincible(npc, true) 68 | FreezeEntityPosition(npc, true) 69 | table.insert(spawnedNPCs, npc) 70 | end 71 | end 72 | 73 | RegisterNetEvent('vhs-recycle:enter') 74 | AddEventHandler('vhs-recycle:enter', function() 75 | local ped = PlayerPedId() 76 | RequestCollisionAtCoord(depot.inside.zone[1], depot.inside.zone[2], depot.inside.zone[3]) 77 | SetEntityCoords(ped, depot.inside.zone[1], depot.inside.zone[2], depot.inside.zone[3] + 1.0, false, false, false, true) 78 | while not HasCollisionLoadedAroundEntity(ped) do 79 | Citizen.Wait(0) 80 | end 81 | SetEntityCoords(ped, depot.inside.zone[1], depot.inside.zone[2], depot.inside.zone[3], false, false, false, true) 82 | SetEntityHeading(ped, depot.inside.zone[4]) 83 | end) 84 | 85 | RegisterNetEvent('vhs-recycle:exit') 86 | AddEventHandler('vhs-recycle:exit', function() 87 | local ped = PlayerPedId() 88 | RequestCollisionAtCoord(depot.outside.zone[1], depot.outside.zone[2], depot.outside.zone[3]) 89 | SetEntityCoords(ped, depot.outside.zone[1], depot.outside.zone[2], depot.outside.zone[3] + 1.0, false, false, false, true) 90 | while not HasCollisionLoadedAroundEntity(ped) do 91 | Citizen.Wait(0) 92 | end 93 | SetEntityCoords(ped, depot.outside.zone[1], depot.outside.zone[2], depot.outside.zone[3], false, false, false, true) 94 | SetEntityHeading(ped, depot.outside.zone[4]) 95 | end) 96 | 97 | 98 | RegisterNetEvent('vhs-recycle:interact') 99 | AddEventHandler('vhs-recycle:interact', function() 100 | local playerPed = PlayerPedId() 101 | local propPlacement = { vector3(0.025000, 0.080000, 0.255000), vector3(-145.000000, 290.000000, 0.000000) } 102 | lib.requestAnimDict('anim@heists@box_carry@', 500) 103 | lib.requestModel('hei_prop_heist_box', 10000) 104 | TaskPlayAnim(playerPed, 'anim@heists@box_carry@', 'idle', 8.0, -8.0, -1, 50, 0, false, false, false) 105 | carriedProp = CreateObject(GetHashKey('hei_prop_heist_box'), 0, 0, 0, true, true, true) 106 | AttachEntityToEntity(carriedProp, playerPed, GetPedBoneIndex(playerPed, 60309), propPlacement[1].x, propPlacement[1].y, propPlacement[1].z, propPlacement[2].x, propPlacement[2].y, propPlacement[2].z, true, true, false, true, 1, true) 107 | end) 108 | 109 | RegisterNetEvent('vhs-recycle:interactNPC') 110 | AddEventHandler('vhs-recycle:interactNPC', function() 111 | lib.registerContext({ 112 | id = 'npc_menu', 113 | title = '♻️ **Recycling Items**', 114 | options = { { title = 'Process Items (5x)', description = 'Process recycled 5x products', icon = 'recycle', event = 'vhs-recycle:iteemz', args = { amountToAdd = 5 } }, { title = 'Process Items (10x)', description = 'Process recycled 10x products', icon = 'recycle', event = 'vhs-recycle:iteemz', args = { amountToAdd = 10 } } } 115 | }) 116 | lib.showContext('npc_menu') 117 | end) 118 | 119 | RegisterNetEvent('vhs-recycle:interactsell') 120 | AddEventHandler('vhs-recycle:interactsell', function() 121 | local menuOptions = lib.callback.await('vhs-recycle:sellmenu', false) 122 | if menuOptions then 123 | lib.registerContext({ id = 'sell_menu', title = '♻️ **Recycling Sell Items**', options = menuOptions }) 124 | lib.showContext('sell_menu') 125 | end 126 | end) 127 | 128 | RegisterNetEvent('vhs-recycle:iteemz') 129 | AddEventHandler('vhs-recycle:iteemz', function(data) 130 | local playerPed = PlayerPedId() 131 | local amountToAdd = data.amountToAdd 132 | lib.requestAnimDict('misscarsteal4@actor', 500) 133 | TaskPlayAnim(playerPed, 'misscarsteal4@actor', 'actor_berating_loop', 8.0, -8.0, -1, 50, 0, false, false, false) 134 | ProgressBar(5000, 'Processing Items') 135 | local giveitems = lib.callback.await('vhs-recycle:giveitems', false, amountToAdd) 136 | ClearPedTasksImmediately(playerPed) 137 | if giveitems then 138 | Notify("success", "Recycling Bin", amountToAdd .. " items processed successfully!") 139 | else 140 | Notify("error", "Recycling Bin", "Failed to process items!") 141 | end 142 | end) 143 | 144 | RegisterNetEvent('vhs-recycle:process') 145 | AddEventHandler('vhs-recycle:process', function() 146 | local playerPed = PlayerPedId() 147 | if carriedProp and DoesEntityExist(carriedProp) then 148 | ClearPedTasksImmediately(playerPed) 149 | DeleteEntity(carriedProp) 150 | carriedProp = nil 151 | lib.requestAnimDict('mini@repair', 500) 152 | TaskPlayAnim(playerPed, 'mini@repair', 'fixing_a_ped', 8.0, -8.0, -1, 50, 0, false, false, false) 153 | ProgressBar(5000, 'Recycling Items') 154 | ClearPedTasksImmediately(playerPed) 155 | local added = lib.callback.await('vhs-recycle:processing', false) 156 | if added then 157 | Notify("success", "Recycling Bin", "Items processed successfully!", playerPed) 158 | else 159 | Notify("error", "Recycling Bin", "Failed to process items!", playerPed) 160 | end 161 | else 162 | Notify("info", "Recycling Bin", "You have nothing to process!", playerPed) 163 | end 164 | end) 165 | 166 | RegisterNetEvent('vhs-recycle:sellamount') 167 | AddEventHandler('vhs-recycle:sellamount', function(data) 168 | local input = lib.inputDialog('Enter quantity to sell', {'Quantity'}) 169 | if not input or not tonumber(input[1]) then 170 | Notify("info", "Invalid Amount", 'Please enter a valid amount') 171 | return 172 | end 173 | local quantity = math.floor(tonumber(input[1])) 174 | if quantity < 1 then 175 | Notify("info", "Invalid Amount", 'Please enter a valid amount') 176 | return 177 | end 178 | lib.callback.await('vhs-recycle:sellItem', 5000, { item = data.item, amount = quantity, price = data.price }) 179 | end) 180 | 181 | 182 | function targetModel(model, name, event, icon, label, item, action, interact, job, gang, distance) 183 | local targetOptions = { name = name, icon = icon, label = label, distance = distance, items = item, groups = job, 184 | canInteract = function(entity, dist, coords, name, bone) 185 | return type(interact) == "function" and interact(entity, dist, coords, name, bone) or true 186 | end, 187 | } 188 | if event then 189 | targetOptions.event = event 190 | end 191 | if action then 192 | targetOptions.onSelect = function(data) 193 | if type(action) == "function" then 194 | action(data) 195 | end 196 | end 197 | end 198 | if GetResourceState('ox_target') == 'started' then 199 | exports.ox_target:addModel(model, { targetOptions }) 200 | elseif GetResourceState('qb-target') == 'started' then 201 | local qbOptions = { 202 | options = {{ event = event, icon = icon, label = label, item = item, job = job, gang = gang, 203 | action = action and function(entity) 204 | if type(action) == "function" then 205 | action(entity) 206 | end 207 | end or nil, 208 | canInteract = function(entity, dist, data) 209 | return type(interact) == "function" and interact(entity, dist, data) or true 210 | end 211 | }}, 212 | distance = distance 213 | } 214 | if not action then 215 | qbOptions.options[1].action = nil 216 | end 217 | if not event then 218 | qbOptions.options[1].event = nil 219 | end 220 | exports['qb-target']:AddTargetModel(model, qbOptions) 221 | else 222 | print('Neither ox_target nor qb-target is started.') 223 | end 224 | end 225 | 226 | function targetBox(name, coords, size, options, job, gang, distance, rotation, debug) 227 | if GetResourceState('ox_target') == 'started' then 228 | local oxSize = vector3(size[1], size[2], 2.0 + 9) 229 | local oxOptions = { { name = name, coords = coords, size = oxSize, rotation = rotation or 0, debug = debug or false, distance = distance or 2.0, options = {} } } 230 | for _, opt in ipairs(options) do 231 | table.insert(oxOptions[1].options, { event = opt.event, icon = opt.icon, label = opt.label, items = opt.item, groups = job, 232 | onSelect = function(data) 233 | if opt.action and type(opt.action) == "function" then 234 | opt.action(data) 235 | end 236 | end, 237 | canInteract = function(entity, dist, zoneCoords, zoneName, bone) 238 | if type(opt.canInteract) == "function" then 239 | return opt.canInteract(entity, dist, zoneCoords, zoneName, bone) 240 | end 241 | return true 242 | end 243 | }) 244 | end 245 | exports.ox_target:addBoxZone(oxOptions[1]) 246 | elseif GetResourceState('qb-target') == 'started' then 247 | local qbOptions = {} 248 | for _, opt in ipairs(options) do 249 | table.insert(qbOptions, { event = opt.event, icon = opt.icon, label = opt.label, item = opt.item, job = job or 'all', gang = gang, 250 | action = function(entity) 251 | if opt.action and type(opt.action) == "function" then 252 | opt.action(entity) 253 | end 254 | end, 255 | canInteract = function(entity, dist, data) 256 | if type(opt.canInteract) == "function" then 257 | return opt.canInteract(entity, dist, data) 258 | end 259 | return true 260 | end 261 | }) 262 | end 263 | exports['qb-target']:AddBoxZone(name, coords, size[1], size[2], { name = name, heading = rotation or 0, debugPoly = debug or false, minZ = coords.z - 1.0, maxZ = coords.z + 1.0, }, { options = qbOptions, distance = distance or 2.0 }) 264 | else 265 | print('Neither ox_target nor qb-target is started.') 266 | end 267 | end 268 | 269 | targetModel(depot.npc.ped, 'npc', 'vhs-recycle:interactNPC', 'fas fa-hand', 'Open Recycle Menu', item, nil, interact, job, gang, 1.5) 270 | targetModel(depot.sell.ped, 'sell', 'vhs-recycle:interactsell', 'fas fa-hand', 'Sell Items', nil, nil, interact, job, gang, 1.5) 271 | 272 | targetBox('outside', depot.outside.zone, {3.5, 5.5}, { { icon = 'fas fa-door-open', label = 'Enter Depot', action = function(entity) TriggerEvent('vhs-recycle:enter') end }, }, job, gang, 1.5, rotation, false) 273 | targetBox('exit', depot.exit.zone, {1.5, 1.3}, { { icon = 'fas fa-door-open', label = 'Exit Warehouse', action = function(entity) TriggerEvent('vhs-recycle:exit') end }, }, job, gang, 1.5, rotation, false) 274 | targetBox('duty', depot.duty.zone, {1, 1}, { { icon = 'fas fa-door-open', label = 'Start Working', action = function(entity) TriggerEvent('vhs-recycle:start') end }, }, job, gang, 1.5, rotation, false) 275 | 276 | 277 | RegisterNetEvent('vhs-recycle:start') 278 | AddEventHandler('vhs-recycle:start', function() 279 | for _, propData in pairs(props) do 280 | if DoesEntityExist(propData.object) then 281 | local model = GetHashKey(propData.prop) 282 | targetModel(model, 'interact', 'vhs-recycle:interact', 'fas fa-hand', 'Take Items', nil, nil, interact, job, gang, 1.5) 283 | end 284 | end 285 | end) 286 | 287 | -------------------------------------------------------------------------------- /src/server/s_framework.lua: -------------------------------------------------------------------------------- 1 | function GetPlayerData(source) 2 | if Framework == 'esx' then 3 | ESX = exports["es_extended"]:getSharedObject() 4 | return 5 | ESX.GetPlayerFromId(source) 6 | elseif Framework == 'qbcore' then 7 | QBCore = exports['qb-core']:GetCoreObject() 8 | return 9 | QBCore.Functions.GetPlayer(source) 10 | else 11 | print('Unsupported Framework') 12 | end 13 | end 14 | 15 | function GetItemLabel(item) 16 | if Framework == 'esx' then 17 | return ESX.GetItemLabel(item) 18 | elseif Framework == 'qbcore' then 19 | if QBCore and QBCore.Shared and QBCore.Shared.Items[item] then 20 | return QBCore.Shared.Items[item].label 21 | else 22 | return item 23 | end 24 | else 25 | print("Unsupported framework.") 26 | return item 27 | end 28 | end 29 | 30 | function AddMoney(source, amount) 31 | if Framework == 'esx' then 32 | local xPlayer = ESX.GetPlayerFromId(source) 33 | if xPlayer then 34 | xPlayer.addMoney(amount) 35 | else 36 | end 37 | elseif Framework == 'qbcore' then 38 | local xPlayer = QBCore.Functions.GetPlayer(source) 39 | if xPlayer then 40 | xPlayer.Functions.AddMoney('cash', amount) 41 | else 42 | end 43 | else 44 | print("Unsupported framework.") 45 | end 46 | end 47 | 48 | function AddItem(source, item, amount) 49 | if Framework == 'esx' then 50 | local xPlayer = ESX.GetPlayerFromId(source) 51 | if xPlayer and xPlayer.canCarryItem(item, amount) then 52 | xPlayer.addInventoryItem(item, amount) 53 | else 54 | Notify("error", "Error", 'You cannot carry anymore!', source) 55 | print('ESX: Inventory full or cannot carry item') 56 | end 57 | elseif Framework == 'qbcore' then 58 | local xPlayer = QBCore.Functions.GetPlayer(source) 59 | if xPlayer then 60 | local added = xPlayer.Functions.AddItem(item, amount) 61 | if added then 62 | TriggerClientEvent('inventory:client:ItemBox', source, QBCore.Shared.Items[item], 'add', amount) 63 | else 64 | print('QBCore: Inventory full or cannot carry item') 65 | end 66 | else 67 | print('QBCore: Player not found') 68 | end 69 | else 70 | print('') 71 | end 72 | end 73 | 74 | function GetInventoryItem(source, item) 75 | if Framework == 'esx' then 76 | local xPlayer = GetPlayerData(source) 77 | if xPlayer then 78 | local esxItem = xPlayer.getInventoryItem(item) 79 | if esxItem and esxItem.count > 0 then 80 | return { count = esxItem.count, label = esxItem.label } 81 | end 82 | end 83 | elseif Framework == 'qbcore' then 84 | local xPlayer = QBCore.Functions.GetPlayer(source) 85 | if xPlayer then 86 | local qItem = xPlayer.Functions.GetItemByName(item) 87 | if qItem then 88 | return { count = qItem.amount, label = qItem.label } 89 | end 90 | end 91 | else 92 | print("Unsupported framework.") 93 | end 94 | return { count = 0, label = item } 95 | 96 | end 97 | 98 | function RemoveItem(source, item, amount) 99 | if Framework == 'esx' then 100 | local xPlayer = ESX.GetPlayerFromId(source) 101 | if xPlayer then 102 | if xPlayer.getInventoryItem(item).count >= amount then 103 | xPlayer.removeInventoryItem(item, amount) 104 | return true 105 | else 106 | print("Player does not have enough of the item.") 107 | return false 108 | end 109 | else 110 | print("Player not found.") 111 | return false 112 | end 113 | elseif Framework == 'qbcore' then 114 | local xPlayer = QBCore.Functions.GetPlayer(source) 115 | if xPlayer then 116 | xPlayer.Functions.RemoveItem(item, amount) 117 | TriggerClientEvent('inventory:client:ItemBox', source, QBCore.Shared.Items[item], 'remove', amount) 118 | return true 119 | else 120 | print("Player not found.") 121 | return false 122 | end 123 | else 124 | print("Set your framework!") 125 | return false 126 | end 127 | end 128 | 129 | 130 | -------------------------------------------------------------------------------- /src/server/s_main.lua: -------------------------------------------------------------------------------- 1 | if Framework == 'esx' then ESX = exports["es_extended"]:getSharedObject() else QBCore = exports['qb-core']:GetCoreObject() end 2 | 3 | function logDiscord(title, message, color) 4 | local data = { username = "vh-fishing", avatar_url = "https://i.imgur.com/E2Z3mDO.png", embeds = { { ["color"] = color, ["title"] = title, ["description"] = message, ["footer"] = { ["text"] = "Installation Support - [ESX, QBCore Qbox] - https://discord.gg/CBSSMpmqrK" },} }} PerformHttpRequest(WebhookConfig.URL, function(err, text, headers) end, 'POST', json.encode(data), {['Content-Type'] = 'application/json'}) 5 | end 6 | 7 | lib.callback.register('vhs-recycle:processing', function(source) 8 | local src = source 9 | AddItem(src, 'recycle', 1) 10 | return true 11 | end) 12 | 13 | lib.callback.register('vhs-recycle:sellmenu', function(source) 14 | local src = source 15 | local menuOptions = {} 16 | for itemName, itemPrice in pairs(items) do 17 | table.insert(menuOptions, { title = GetItemLabel(itemName) .. " ($" .. itemPrice .. ")", icon = InventoryImagePath .. itemName .. ".png", event = 'vhs-recycle:sellamount', args = { item = itemName, price = itemPrice } }) 18 | end 19 | return menuOptions 20 | end) 21 | 22 | 23 | lib.callback.register('vhs-recycle:sellItem', function(source, data) 24 | local xPlayer = GetPlayerData(source) 25 | if not xPlayer then 26 | Notify("info", "Player Not Found", source) 27 | return false 28 | end 29 | local item = GetInventoryItem(source, data.item) 30 | if item and item.count >= data.amount then 31 | local moneys = data.amount * data.price 32 | RemoveItem(source, data.item, data.amount) 33 | AddMoney(source, moneys) 34 | local discordMessage = string.format("**%s sold %d x %s for $%d**", GetPlayerName(source), data.amount, GetItemLabel(data.item), moneys) 35 | logDiscord("\u{1F4B8} Recycle Sold - " .. GetItemLabel(data.item), discordMessage, 65280) 36 | Notify("info", "Recycling Depot", "You sold " .. data.amount .. " " .. GetItemLabel(data.item) .. " for $" .. moneys, source) 37 | return true 38 | else 39 | Notify("error", "Recycling Depot", "Not enough items to sell.", source) 40 | return false 41 | end 42 | end) 43 | 44 | lib.callback.register('vhs-recycle:giveitems', function(source, amount) 45 | local src = source 46 | local recycleItem = GetInventoryItem(src, 'recycle') 47 | if recycleItem.count >= amount then 48 | RemoveItem(src, 'recycle', amount) 49 | local itemsAdd = {} 50 | for i = 1, amount do 51 | for j = 1, giveItemAmount do 52 | local randomItem = getRandomItem() 53 | if itemsAdd[randomItem] then 54 | itemsAdd[randomItem] = itemsAdd[randomItem] + 1 55 | else 56 | itemsAdd[randomItem] = 1 57 | end 58 | end 59 | end 60 | for item, count in pairs(itemsAdd) do 61 | AddItem(src, item, count) 62 | end 63 | return true 64 | else 65 | return false, "Not enough recycle items" 66 | end 67 | end) 68 | 69 | function getRandomItem() 70 | local keys = {} 71 | for key in pairs(items) do 72 | table.insert(keys, key) 73 | end 74 | return keys[math.random(#keys)] 75 | end 76 | 77 | --------------------------------------------------------------------------------