├── lockerTimes.json ├── fxmanifest.lua ├── .gitignore ├── LICENSE ├── README.md ├── config.lua ├── client └── client.lua └── server └── server.lua /lockerTimes.json: -------------------------------------------------------------------------------- 1 | {} 2 | -------------------------------------------------------------------------------- /fxmanifest.lua: -------------------------------------------------------------------------------- 1 | fx_version 'cerulean' 2 | game 'gta5' 3 | 4 | author 'Atiya' 5 | description 'Locker Confiscation System' 6 | version '2.0.5' 7 | 8 | shared_scripts { 9 | '@ox_lib/init.lua', 10 | 'config.lua' 11 | } 12 | client_script 'client/*.lua' 13 | server_script 'server/*.lua' 14 | 15 | dependencies { 16 | 'yarn', 17 | } 18 | 19 | lua54 'yes' 20 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled Lua sources 2 | luac.out 3 | 4 | # luarocks build files 5 | *.src.rock 6 | *.zip 7 | *.tar.gz 8 | 9 | # Object files 10 | *.o 11 | *.os 12 | *.ko 13 | *.obj 14 | *.elf 15 | 16 | # Precompiled Headers 17 | *.gch 18 | *.pch 19 | 20 | # Libraries 21 | *.lib 22 | *.a 23 | *.la 24 | *.lo 25 | *.def 26 | *.exp 27 | 28 | # Shared objects (inc. Windows DLLs) 29 | *.dll 30 | *.so 31 | *.so.* 32 | *.dylib 33 | 34 | # Executables 35 | *.exe 36 | *.out 37 | *.app 38 | *.i*86 39 | *.x86_64 40 | *.hex 41 | 42 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | This is free and unencumbered software released into the public domain. 2 | 3 | Anyone is free to copy, modify, publish, use, compile, sell, or 4 | distribute this software, either in source code form or as a compiled 5 | binary, for any purpose, commercial or non-commercial, and by any 6 | means. 7 | 8 | In jurisdictions that recognize copyright laws, the author or authors 9 | of this software dedicate any and all copyright interest in the 10 | software to the public domain. We make this dedication for the benefit 11 | of the public at large and to the detriment of our heirs and 12 | successors. We intend this dedication to be an overt act of 13 | relinquishment in perpetuity of all present and future rights to this 14 | software under copyright law. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 20 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 21 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | 24 | For more information, please refer to 25 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | # Confiscation Locker for FiveM [QBCore] 3 | 4 | * Made by Atiya 5 | 6 | * Discord: atiya. 7 | 8 | * Github: [MAtiyaaa](https://github.com/MAtiyaaa) 9 | 10 | 11 | # Description 12 | 13 | > Simple drag-and-drop 14 | 15 | > Police don't have to get spammed with 911 calls anymore. The police can simply open the player's locker and place their items in there. 16 | 17 | > Auto-confiscation for jail or hospital check-ins, certain items specified in config will be removed from their inventory and placed in their locker. [Optional] 18 | 19 | > A police officer can use the command or input menu to store the items of a player in a locker and can use another command to lock and unlock the locker for a certain period of time. 20 | 21 | > The player can go receive their items at the location you choose in the config, after the specified period in the lock command (if used) 22 | 23 | > QB-Inventory compatibility added by iOx 24 | 25 | > Formerly known as `qb-policelockers` 26 | 27 | 28 | 29 | # Dependencies 30 | 31 | * [QBCore](https://github.com/qbcore-framework) 32 | 33 | * [ox_lib by oxerextended](https://github.com/overextended/ox_lib) 34 | 35 | * [oxmysql by overextended](https://github.com/overextended/oxmysql) (Only If you're using qb-inventory AND plan on using the auto-confiscation feature) 36 | 37 | * [QB-Inventory](https://github.com/qbcore-framework/qb-inventory) 38 | 39 | * **OR** 40 | 41 | * [ox_inventory by overextended](https://github.com/overextended/ox_inventory) 42 | 43 | * BELOW IS OPTIONAL, SET CONFIG TO **3D TEXT** IF YOU ARE NOT USING EITHER ONE 44 | 45 | * [QB-Target](https://github.com/qbcore-framework/qb-target) 46 | 47 | * **OR** 48 | 49 | * [OX_Target by overextended](https://github.com/overextended/ox_target) 50 | 51 | 52 | 53 | # Installation 54 | 55 | * Literally a drag and drop but this is for those who aren't as familiar. 56 | 57 | * Add `qb-confiscation` to your resources folder 58 | 59 | * Ensure `qb-confiscation`, or the folder it's in 60 | 61 | * Enjoy! 62 | 63 | 64 | 65 | # Optional Installation 66 | 67 | * This is for if you want auto-confiscation and for items to go into the locker after going to jail or the hospital. 68 | 69 | 70 | 71 | * For [QB-Ambulancejob](https://github.com/qbcore-framework/qb-ambulancejob) 72 | 73 | * Open `main.lua` in the server folder of `qb-ambulancejob` 74 | 75 | * Find `RegisterNetEvent('hospital:server:SendToBed', function(bedId, isRevive)` 76 | 77 | * Add `TriggerEvent('qb-confiscation:confiscateItems', src)` at the end 78 | 79 | 80 | 81 | * For [QB-Policejob](https://github.com/qbcore-framework/qb-policejob) 82 | 83 | * Open `main.lua` in the server folder of `qb-policejob` 84 | 85 | * Find `RegisterNetEvent('police:server:JailPlayer', function(playerId, time)` 86 | 87 | * Add `TriggerEvent('qb-confiscation:confiscateItems', playerId)` directly before `TriggerClientEvent('police:client:SendToJail', OtherPlayer.PlayerData.source, time)` 88 | -------------------------------------------------------------------------------- /config.lua: -------------------------------------------------------------------------------- 1 | Config = {} 2 | 3 | Config.Commands = { 4 | openLocker = { 5 | enabled = true, -- Option to enable/disable the command 6 | name = 'openlocker', -- Name of the command 7 | description = 'Open a player\'s locker', -- Command description 8 | usage = 'ID/CID', -- Command usage 9 | locationOnly = true, -- Disable if you want to be able to use this command anywhere (not recommended to disable, exploitable) 10 | locations = { 11 | { coords = vector3(442.39, -981.911, 29.69), radius = 50.0 }, -- Only Works if locationOnly is set to true 12 | -- { coords = vector3(200.0, 300.0, 40.0), radius = 15.0 }, 13 | }, 14 | }, 15 | lockLocker = { 16 | enabled = true, 17 | name = 'locklocker', 18 | description = 'Lock player\'s locker', 19 | }, 20 | unlockLocker = { 21 | enabled = true, 22 | name = 'unlocklocker', 23 | description = 'Unlock player\'s locker', 24 | usage = 'ID/CID' 25 | } 26 | } 27 | 28 | Config.Inventory = 'QB' -- 'QB' for qb-inventory, 'OX' for ox_inventory 29 | Config.Target = 'OX' -- 'QB' for qb-target, 'OX' for ox_target, '3D' for 3D Text 30 | 31 | Config.InputMenu = { 32 | Enabled = true, -- Want a menu to insert a player id? You could use this, or the openLocker command, or both! 33 | method = 'QB', -- QB for qb-input, 'OX' for ox_lib 34 | locations = { 35 | { coords = vector3(444.51, -984.911, 30.00), radius = 10.0 }, -- GABZ MRPD 36 | -- { coords = vector3(200.0, 300.0, 40.0), radius = 15.0 }, 37 | }, 38 | } 39 | 40 | Config.Peds = { 41 | { 42 | model = 's_m_y_cop_01', -- Get ped names from https://wiki.rage.mp/index.php?title=Peds 43 | location = vector4(442.39, -981.911, 29.69, 85.0), -- GABZ MRPD 44 | animDict = 'missheistdockssetup1clipboard@base', -- Get animations from https://alexguirre.github.io/animations-list/ 45 | animName = 'base', -- Get animations from https://alexguirre.github.io/animations-list/ 46 | prop = 'prop_notepad_01', -- Find Prop here https://gist.github.com/leonardosnt/53faac01a38fc94505e9 47 | propBone = 18905, -- Find Bones here https://wiki.rage.mp/index.php?title=Bones 48 | propPlacement = vector3(0.1, 0.02, 0.05), 49 | propRotation = vector3(10.0, 0.0, 0.0) 50 | }, 51 | { 52 | model = 'csb_cop', 53 | location = vector4(1844.57, 2581.75, 45.01, 76.00), -- GABZ BOILINGBROKE PEN. 54 | animDict = 'missheistdockssetup1clipboard@base', 55 | animName = 'base', 56 | prop = 'prop_notepad_01', 57 | propBone = 18905, 58 | propPlacement = vector3(0.1, 0.02, 0.05), 59 | propRotation = vector3(10.0, 0.0, 0.0) 60 | }, 61 | } 62 | 63 | Config.HospitalConfiscate = true -- Useless config, if you don't want it to confiscate in hospital, don't add the ambulancejob snippet. 64 | Config.JailConfiscate = true -- Useless config, if you don't want it to confiscate in jail, don't add the policejob snippet. 65 | 66 | Config.Confiscation = { 67 | Mode = 'blacklist', -- 'blacklist' to take ONLY the items below, 'whitelist' to take everything BUT the items below 68 | Items = { 69 | "weapon_knife", 70 | "weapon_pistol", 71 | } 72 | } 73 | Config.AccessControl = { 74 | jobName = { 75 | ['leo'] = true, 76 | --['ems'] = true, 77 | }, 78 | checkType = 'type', -- 'job' for jobName, 'type' for jobType (if you're using jobtype = 'leo' due to multiple departments, set to 'type', if only using the default police job, set to 'job') 79 | } 80 | 81 | Config.Unlocking = { 82 | allowedGrade = 2, -- What grades can unlock the lockers 83 | adminCanUnlock = true, -- True = admin can unlock lockers 84 | } 85 | 86 | 87 | Config.Locker = { 88 | slots = 100, -- Slots in the locker 89 | weight = 250000 -- Maximum weight (10000 = 10kg) 90 | } 91 | 92 | return Config 93 | -------------------------------------------------------------------------------- /client/client.lua: -------------------------------------------------------------------------------- 1 | QBCore = exports['qb-core']:GetCoreObject() 2 | 3 | Citizen.CreateThread(function() 4 | for _, pedConfig in pairs(Config.Peds) do 5 | local pedModel = GetHashKey(pedConfig.model) 6 | RequestModel(pedModel) 7 | while not HasModelLoaded(pedModel) do 8 | Wait(1) 9 | end 10 | 11 | local ped = CreatePed(4, pedModel, pedConfig.location.x, pedConfig.location.y, pedConfig.location.z, pedConfig.location.w, false, true) 12 | SetEntityInvincible(ped, true) 13 | SetBlockingOfNonTemporaryEvents(ped, true) 14 | FreezeEntityPosition(ped, true) 15 | 16 | if pedConfig.animDict and pedConfig.animName then 17 | RequestAnimDict(pedConfig.animDict) 18 | while not HasAnimDictLoaded(pedConfig.animDict) do 19 | Wait(1) 20 | end 21 | TaskPlayAnim(ped, pedConfig.animDict, pedConfig.animName, 8.0, -8.0, -1, 1, 0, false, false, false) 22 | end 23 | 24 | if pedConfig.prop then 25 | local propHash = GetHashKey(pedConfig.prop) 26 | RequestModel(propHash) 27 | while not HasModelLoaded(propHash) do 28 | Wait(1) 29 | end 30 | 31 | local prop = CreateObject(propHash, 0, 0, 0, true, true, true) 32 | AttachEntityToEntity(prop, ped, GetPedBoneIndex(ped, pedConfig.propBone), pedConfig.propPlacement.x, pedConfig.propPlacement.y, pedConfig.propPlacement.z, pedConfig.propRotation.x, pedConfig.propRotation.y, pedConfig.propRotation.z, true, true, false, true, 1, true) 33 | end 34 | 35 | local interactionOptions = { 36 | { 37 | event = 'qb-confiscation:client:RequestOpenLocker', 38 | icon = 'fas fa-lock', 39 | label = 'Open Locker', 40 | }, 41 | } 42 | 43 | if Config.Target == 'QB' then 44 | exports['qb-target']:AddTargetEntity(ped, { 45 | options = interactionOptions, 46 | distance = 2.0 47 | }) 48 | elseif Config.Target == 'OX' then 49 | exports.ox_target:addLocalEntity(ped, interactionOptions) 50 | elseif Config.Target == '3D' then 51 | Citizen.CreateThread(function() 52 | while true do 53 | Citizen.Wait(0) 54 | local playerCoords = GetEntityCoords(PlayerPedId()) 55 | for _, v in pairs(Config.Peds) do 56 | local distance = #(playerCoords - vector3(v.location.x, v.location.y, v.location.z)) 57 | 58 | if distance < 2.5 then 59 | DrawText3Ds(v.location.x, v.location.y, v.location.z + 1.0, "Press ~r~[E]~s~ to Open ~y~Locker~s~") 60 | if IsControlJustReleased(0, 38) then 61 | TriggerEvent('qb-confiscation:client:RequestOpenLocker') 62 | end 63 | end 64 | end 65 | end 66 | end) 67 | end 68 | end 69 | 70 | for _, locationConfig in pairs(Config.InputMenu.locations) do 71 | if Config.InputMenu.Enabled then 72 | --print("[Debug] Adding box zone at:", locationConfig.coords) 73 | 74 | local jobs = Config.AccessControl.jobName 75 | local interactionOptions = {} 76 | 77 | for _, job in pairs(jobs) do 78 | table.insert(interactionOptions, { 79 | event = 'qb-confiscation:client:locker-input', 80 | icon = 'fas fa-key', 81 | label = 'Open a Player\'s Locker', 82 | job = job, 83 | }) 84 | end 85 | 86 | --print("[Debug] Interaction options:", interactionOptions) 87 | 88 | if Config.Target == 'QB' then 89 | exports['qb-target']:AddBoxZone("locker-input", locationConfig.coords, 2, 2, { 90 | name = "locker-input", 91 | heading = 0, 92 | debugPoly = false, 93 | minZ = locationConfig.coords.z - 1, 94 | maxZ = locationConfig.coords.z + 1, 95 | }, { 96 | options = interactionOptions, 97 | distance = 2.0, 98 | }) 99 | --print("[Debug] Added QB-target box zone") 100 | 101 | elseif Config.Target == 'OX' then 102 | exports.ox_target:addBoxZone({ 103 | coords = locationConfig.coords, 104 | size = vec3(2, 2, 1), 105 | rotation = 0, 106 | debug = false, 107 | options = interactionOptions, 108 | distance = 2.0, 109 | }) 110 | --print("[Debug] Added OX-target box zone") 111 | elseif Config.Target == '3D' then 112 | Citizen.CreateThread(function() 113 | while true do 114 | Citizen.Wait(0) 115 | local playerCoords = GetEntityCoords(PlayerPedId()) 116 | for _, location in pairs(Config.InputMenu.locations) do 117 | local distance = #(playerCoords - location.coords) 118 | 119 | if distance < 2.5 then 120 | DrawText3Ds(location.coords.x, location.coords.y, location.coords.z + 1.0, "Press ~r~[E]~s~ to open a player's locker") 121 | if IsControlJustReleased(0, 38) then 122 | TriggerEvent('qb-confiscation:client:locker-input') 123 | end 124 | end 125 | end 126 | end 127 | end) 128 | --print("[Debug] Added 3D text interaction for locker input") 129 | end 130 | else 131 | --print("[Debug] Input menu disabled") 132 | return 133 | end 134 | end 135 | end) 136 | 137 | RegisterNetEvent('qb-confiscation:client:locker-input', function() 138 | local inputMethod = Config.InputMenu.method 139 | local inputResult = nil 140 | 141 | if inputMethod == 'QB' then 142 | inputResult = exports['qb-input']:ShowInput({ 143 | header = 'Locker Management', 144 | submitText = 'Submit', 145 | inputs = { 146 | { 147 | type = 'select', 148 | isRequired = true, 149 | name = 'action', 150 | text = 'Select Action', 151 | options = { 152 | { value = 'open', text = 'Open Locker' }, 153 | { value = 'lock', text = 'Lock Locker' }, 154 | { value = 'unlock', text = 'Unlock Locker' }, 155 | }, 156 | }, 157 | }, 158 | }) 159 | 160 | if not inputResult then 161 | TriggerEvent('QBCore:Notify', 'No valid action selected.', 'error') 162 | return 163 | end 164 | 165 | local action = inputResult["action"] 166 | 167 | if action == 'lock' then 168 | local lockResult = exports['qb-input']:ShowInput({ 169 | header = 'Lock Locker', 170 | submitText = 'Lock', 171 | inputs = { 172 | { 173 | type = 'text', 174 | isRequired = true, 175 | name = 'targetId', 176 | text = 'Enter Player ID or Citizen ID', 177 | }, 178 | { 179 | type = 'number', 180 | isRequired = true, 181 | name = 'lockDuration', 182 | text = 'Lock Duration (in minutes)', 183 | }, 184 | }, 185 | }) 186 | 187 | if not lockResult then 188 | TriggerEvent('QBCore:Notify', 'No valid input for locking.', 'error') 189 | return 190 | end 191 | 192 | local targetId = lockResult["targetId"] 193 | local lockDuration = tonumber(lockResult["lockDuration"]) 194 | 195 | if not targetId or targetId == "" then 196 | TriggerEvent('QBCore:Notify', 'No valid ID entered.', 'error') 197 | return 198 | end 199 | 200 | TriggerServerEvent('qb-confiscation:server:lockLocker', targetId, lockDuration) 201 | 202 | elseif action == 'unlock' then 203 | local unlockResult = exports['qb-input']:ShowInput({ 204 | header = 'Unlock Locker', 205 | submitText = 'Unlock', 206 | inputs = { 207 | { 208 | type = 'text', 209 | isRequired = true, 210 | name = 'targetId', 211 | text = 'Enter Player ID or Citizen ID', 212 | }, 213 | }, 214 | }) 215 | 216 | if not unlockResult then 217 | TriggerEvent('QBCore:Notify', 'No valid input for unlocking.', 'error') 218 | return 219 | end 220 | 221 | local targetId = unlockResult["targetId"] 222 | 223 | if not targetId or targetId == "" then 224 | TriggerEvent('QBCore:Notify', 'No valid ID entered.', 'error') 225 | return 226 | end 227 | 228 | TriggerServerEvent('qb-confiscation:server:unlockLocker', targetId) 229 | 230 | elseif action == 'open' then 231 | local openResult = exports['qb-input']:ShowInput({ 232 | header = 'Open Locker', 233 | submitText = 'Open', 234 | inputs = { 235 | { 236 | type = 'text', 237 | isRequired = true, 238 | name = 'targetId', 239 | text = 'Enter Player ID or Citizen ID', 240 | }, 241 | }, 242 | }) 243 | 244 | if not openResult then 245 | TriggerEvent('QBCore:Notify', 'No valid input for opening.', 'error') 246 | return 247 | end 248 | 249 | local targetId = openResult["targetId"] 250 | 251 | if not targetId or targetId == "" then 252 | TriggerEvent('QBCore:Notify', 'No valid ID entered.', 'error') 253 | return 254 | end 255 | 256 | TriggerServerEvent('qb-confiscation:server:OpenLocker', targetId) 257 | end 258 | 259 | elseif inputMethod == 'OX' and GetResourceState('ox_lib') == 'started' then 260 | local baseInput = lib.inputDialog('Locker Management', { 261 | { 262 | type = 'select', 263 | label = 'Select Action', 264 | options = { 265 | { value = 'Open', text = 'Open Locker' }, 266 | { value = 'Lock', text = 'Lock Locker' }, 267 | { value = 'Unlock', text = 'Unlock Locker' }, 268 | }, 269 | }, 270 | }) 271 | 272 | if not baseInput then 273 | TriggerEvent('QBCore:Notify', 'No valid action selected.', 'error') 274 | return 275 | end 276 | 277 | local action = baseInput[1] 278 | 279 | if action == 'Lock' then 280 | local lockInput = lib.inputDialog('Lock Locker', { 281 | { 282 | type = 'input', 283 | label = 'Enter Player ID or Citizen ID', 284 | required = true, 285 | }, 286 | { 287 | type = 'number', 288 | label = 'Lock Duration (in minutes)', 289 | required = true, 290 | }, 291 | }) 292 | 293 | if not lockInput then 294 | TriggerEvent('QBCore:Notify', 'No valid input for locking.', 'error') 295 | return 296 | end 297 | 298 | local targetId = lockInput[1] 299 | local lockDuration = tonumber(lockInput[2]) 300 | 301 | if not targetId or targetId == "" then 302 | TriggerEvent('QBCore:Notify', 'No valid ID entered.', 'error') 303 | return 304 | end 305 | 306 | TriggerServerEvent('qb-confiscation:server:lockLocker', targetId, lockDuration) 307 | 308 | elseif action == 'Unlock' then 309 | local unlockInput = lib.inputDialog('Unlock Locker', { 310 | { 311 | type = 'input', 312 | label = 'Enter Player ID or Citizen ID', 313 | required = true, 314 | }, 315 | }) 316 | 317 | if not unlockInput then 318 | TriggerEvent('QBCore:Notify', 'No valid input for unlocking.', 'error') 319 | return 320 | end 321 | 322 | local targetId = unlockInput[1] 323 | 324 | if not targetId or targetId == "" then 325 | TriggerEvent('QBCore:Notify', 'No valid ID entered.', 'error') 326 | return 327 | end 328 | 329 | TriggerServerEvent('qb-confiscation:server:unlockLocker', targetId) 330 | 331 | elseif action == 'Open' then 332 | local openInput = lib.inputDialog('Open Locker', { 333 | { 334 | type = 'input', 335 | label = 'Enter Player ID or Citizen ID', 336 | required, 337 | }, 338 | }) 339 | 340 | if not openInput then 341 | TriggerEvent('QBCore:Notify', 'No valid input for opening.', 'error') 342 | return 343 | end 344 | 345 | local targetId = openInput[1] 346 | 347 | if not targetId or targetId == "" then 348 | TriggerEvent('QBCore:Notify', 'No valid ID entered.', 'error') 349 | return 350 | end 351 | 352 | TriggerServerEvent('qb-confiscation:server:OpenLocker', targetId) 353 | end 354 | end 355 | end) 356 | 357 | RegisterNetEvent('qb-confiscation:client:RequestOpenLocker', function() 358 | local Player = QBCore.Functions.GetPlayerData() 359 | TriggerServerEvent('qb-confiscation:server:CheckLockerStatus', Player.citizenid) 360 | end) 361 | 362 | RegisterNetEvent('qb-confiscation:client:LockerStatus', function(isLocked, remainingTime) 363 | if isLocked then 364 | QBCore.Functions.Notify('Locker is locked for ' .. remainingTime .. ' more minutes.', 'error') 365 | else 366 | TriggerServerEvent('qb-confiscation:server:OpenLocker', QBCore.Functions.GetPlayerData().citizenid) 367 | end 368 | end) 369 | 370 | RegisterNetEvent('qb-confiscation:client:open-locker-custom', function(lockerId) 371 | TriggerServerEvent('inventory:server:OpenInventory', 'stash', lockerId) 372 | TriggerEvent('inventory:client:SetCurrentStash', lockerId) 373 | end) 374 | 375 | function DrawText3Ds(x, y, z, text) 376 | local onScreen, _x, _y = World3dToScreen2d(x, y, z) 377 | SetTextScale(0.32, 0.32) 378 | SetTextFont(4) 379 | SetTextProportional(1) 380 | SetTextColour(255, 255, 255, 255) 381 | SetTextEntry("STRING") 382 | SetTextCentre(1) 383 | AddTextComponentString(text) 384 | DrawText(_x, _y) 385 | local factor = (string.len(text)) / 500 386 | DrawRect(_x, _y + 0.0125, 0.015 + factor, 0.03, 0, 0, 0, 80) 387 | end 388 | -------------------------------------------------------------------------------- /server/server.lua: -------------------------------------------------------------------------------- 1 | QBCore = exports['qb-core']:GetCoreObject() 2 | 3 | local lockers = {} 4 | local lockerTimers = {} 5 | MySQL = exports.oxmysql 6 | 7 | local function saveLockerTimes() 8 | local timeData = {} 9 | for cid, timeLocked in pairs(lockerTimers) do 10 | timeData[cid] = timeLocked 11 | end 12 | 13 | local file = io.open('lockerTimes.json', 'w') 14 | if file then 15 | file:write(json.encode(timeData)) 16 | file:close() 17 | end 18 | end 19 | 20 | 21 | local function loadLockerTimes() 22 | local file = io.open('lockerTimes.json', 'r') 23 | if file then 24 | local data = file:read('*a') 25 | file:close() 26 | lockerTimers = json.decode(data) or {} 27 | end 28 | end 29 | 30 | AddEventHandler('onResourceStart', function(resourceName) 31 | if GetCurrentResourceName() == resourceName then 32 | loadLockerTimes() 33 | end 34 | end) 35 | 36 | local function getLocker(citizenId) 37 | return lockers[citizenId] 38 | end 39 | 40 | local function hasAccess(player) 41 | local jobName = player.PlayerData.job.name 42 | local jobType = player.PlayerData.job.type 43 | local accessGranted = false 44 | 45 | if Config.AccessControl.checkType == 'job' then 46 | for _, allowedJob in ipairs(Config.AccessControl.jobName) do 47 | if jobName == allowedJob then 48 | accessGranted = true 49 | break 50 | end 51 | end 52 | 53 | elseif Config.AccessControl.checkType == 'type' then 54 | accessGranted = Config.AccessControl.jobName[jobType] or false 55 | end 56 | 57 | --print('[Debug] Player Job Name:', jobName) 58 | --print('[Debug] Player Job Type:', jobType) 59 | if accessGranted then 60 | --print('[Debug] Access granted by', Config.AccessControl.checkType .. ':', (Config.AccessControl.checkType == 'job') and jobName or jobType) 61 | else 62 | --print('[Debug] Access denied by', Config.AccessControl.checkType .. ':', (Config.AccessControl.checkType == 'job') and jobName or jobType) 63 | end 64 | 65 | return accessGranted 66 | end 67 | 68 | RegisterNetEvent('qb-confiscation:server:checkAccess', function() 69 | local src = source 70 | local Player = QBCore.Functions.GetPlayer(src) 71 | if Player then 72 | local serverHasAccess = hasAccess(Player, Player.PlayerData.citizenid) 73 | TriggerClientEvent('qb-confiscation:client:checkAccessResult', src, serverHasAccess) 74 | end 75 | end) 76 | 77 | 78 | local function createLocker(citizenId) 79 | local locker = getLocker(citizenId) 80 | if not locker then 81 | local lockerId = 'locker-' .. citizenId 82 | local lockerLabel = 'Locker - ' .. citizenId 83 | local slots = Config.Locker.slots 84 | local maxWeight = Config.Locker.weight 85 | locker = { id = lockerId, label = lockerLabel, slots = slots, weight = maxWeight, owner = citizenId } 86 | lockers[citizenId] = locker 87 | if Config.Inventory == 'OX' then 88 | exports.ox_inventory:RegisterStash(lockerId, lockerLabel, slots, maxWeight, citizenId) 89 | else 90 | return locker 91 | end 92 | end 93 | return locker 94 | end 95 | 96 | QBCore.Commands.Add(Config.Commands.openLocker.name, Config.Commands.openLocker.description, {}, true, function(source, args) 97 | local src = source 98 | local Player = QBCore.Functions.GetPlayer(src) 99 | local targetId = args[1] 100 | 101 | if not Player then 102 | TriggerClientEvent('QBCore:Notify', src, 'Player not found!', 'error') 103 | return 104 | end 105 | 106 | local isAdmin = QBCore.Functions.HasPermission(src, 'admin') 107 | local hasAccessResult = hasAccess(Player) 108 | 109 | if not hasAccessResult and not isAdmin then 110 | TriggerClientEvent('QBCore:Notify', src, 'You are not authorized!', 'error') 111 | --print("[Debug] Access denied for player:", Player.PlayerData.charinfo.firstname, Player.PlayerData.charinfo.lastname) 112 | return 113 | end 114 | if Config.Commands.openLocker.locationOnly then 115 | local playerCoords = GetEntityCoords(GetPlayerPed(src)) 116 | local isInAllowedLocation = false 117 | 118 | for _, location in ipairs(Config.Commands.openLocker.locations) do 119 | if #(playerCoords - location.coords) <= location.radius then 120 | isInAllowedLocation = true 121 | break 122 | end 123 | end 124 | 125 | if not isInAllowedLocation then 126 | TriggerClientEvent('QBCore:Notify', src, 'This command can only be used in specific locations.', 'error') 127 | --print("[Debug] Location check failed. Current coordinates:", playerCoords) 128 | return 129 | end 130 | end 131 | local targetPlayer = QBCore.Functions.GetPlayerByCitizenId(targetId) or QBCore.Functions.GetPlayer(tonumber(targetId)) 132 | if not targetPlayer or not targetPlayer.PlayerData or not targetPlayer.PlayerData.citizenid then 133 | TriggerClientEvent('QBCore:Notify', src, 'Player not found or missing citizen ID.', 'error') 134 | --print("[Debug] Target player not found or missing citizen ID.") 135 | return 136 | end 137 | 138 | local citizenId = targetPlayer.PlayerData.citizenid 139 | local lockerId = 'Locker ' .. citizenId 140 | local locker = createLocker(citizenId) 141 | 142 | if not lockerTimers[citizenId] or lockerTimers[citizenId] <= os.time() then 143 | if Config.Inventory == 'QB' then 144 | TriggerClientEvent('qb-confiscation:client:open-locker-custom', src, lockerId) 145 | elseif Config.Inventory == 'OX' then 146 | exports.ox_inventory:forceOpenInventory(src, 'stash', locker.id) 147 | end 148 | else 149 | TriggerClientEvent('QBCore:Notify', src, 'This locker is currently locked.', 'error') 150 | --print("[Debug] Locker is currently locked.") 151 | end 152 | end, false) 153 | 154 | QBCore.Commands.Add(Config.Commands.lockLocker.name, Config.Commands.lockLocker.description, { 155 | {name = 'ID/CitizenID', help = 'Enter the player\'s server ID or CitizenID'}, 156 | {name = 'Minutes', help = 'How many minutes should the locker stay locked'} 157 | }, true, function(source, args) 158 | if Config.Commands.lockLocker.enabled then 159 | local src = source 160 | local Player = QBCore.Functions.GetPlayer(src) 161 | local targetId = args[1] 162 | local lockDuration = tonumber(args[2]) 163 | 164 | if not Player then 165 | TriggerClientEvent('QBCore:Notify', src, 'Player not found!', 'error') 166 | --print('[Debug] Player not found for source:', src) 167 | return 168 | end 169 | 170 | local isAdmin = QBCore.Functions.HasPermission(src, 'admin') 171 | local isAllowedToLock = hasAccess(Player) or (Config.Unlocking.adminCanUnlock and isAdmin) 172 | 173 | --print('[Debug] Player Info - Name:', Player.PlayerData.name, 'Admin:', 'redacted', 'Access:', isAllowedToLock) 174 | 175 | if not isAllowedToLock then 176 | TriggerClientEvent('QBCore:Notify', src, 'You are not authorized to lock this locker!', 'error') 177 | --print('[Debug] Access denied to player:', Player.PlayerData.name) 178 | return 179 | end 180 | 181 | local targetPlayer = QBCore.Functions.GetPlayerByCitizenId(targetId) or QBCore.Functions.GetPlayer(tonumber(targetId)) 182 | if not targetPlayer or not targetPlayer.PlayerData.citizenid then 183 | TriggerClientEvent('QBCore:Notify', src, 'Invalid player data or missing citizen ID.', 'error') 184 | --print('[Debug] Invalid target player data:', targetId) 185 | return 186 | end 187 | 188 | local citizenId = targetPlayer.PlayerData.citizenid 189 | 190 | if lockDuration and lockDuration > 0 then 191 | lockerTimers[citizenId] = os.time() + (lockDuration * 60) 192 | saveLockerTimes() 193 | TriggerClientEvent('QBCore:Notify', src, 'Locker locked for ' .. lockDuration .. ' minutes', 'success') 194 | --print('[Debug] Locker locked for', lockDuration, 'minutes by', Player.PlayerData.name, 'for target:', citizenId) 195 | else 196 | TriggerClientEvent('QBCore:Notify', src, 'Invalid lock duration specified.', 'error') 197 | --print('[Debug] Invalid lock duration:', lockDuration) 198 | end 199 | else 200 | --print('[Debug] Lock locker command is disabled in the config') 201 | return 202 | end 203 | end, false) 204 | 205 | QBCore.Commands.Add(Config.Commands.unlockLocker.name, Config.Commands.unlockLocker.description, {{name = 'id', help = Config.Commands.unlockLocker.usage}}, true, function(source, args) 206 | if Config.Commands.unlockLocker.enabled then 207 | local src = source 208 | local Player = QBCore.Functions.GetPlayer(src) 209 | local targetId = args[1] 210 | local lockDuration = tonumber(args[2]) 211 | local isAdmin = QBCore.Functions.HasPermission(source, 'admin') 212 | local isAllowedToUnlock = Player.PlayerData.job.grade.level >= Config.Unlocking.allowedGrade or (Config.Unlocking.adminCanUnlock and isAdmin) 213 | 214 | if Player and hasAccess(Player, targetId) and isAllowedToUnlock then 215 | local targetPlayer = QBCore.Functions.GetPlayerByCitizenId(targetId) or QBCore.Functions.GetPlayer(tonumber(targetId)) 216 | if targetPlayer then 217 | local citizenId = targetPlayer.PlayerData.citizenid 218 | lockerTimers[citizenId] = os.time() + (0 * 60) 219 | saveLockerTimes() 220 | TriggerClientEvent('QBCore:Notify', src, 'Locker has been unlocked.', 'success') 221 | else 222 | TriggerClientEvent('QBCore:Notify', src, 'This locker is not currently locked.', 'error') 223 | end 224 | else 225 | TriggerClientEvent('QBCore:Notify', src, 'You are not authorized!', 'error') 226 | end 227 | else 228 | return 229 | end 230 | end, false) 231 | 232 | RegisterNetEvent('qb-confiscation:server:lockLocker', function(targetId, lockDuration) 233 | local src = source 234 | local citizenId 235 | 236 | if tonumber(targetId) then 237 | local targetPlayer = QBCore.Functions.GetPlayer(tonumber(targetId)) 238 | if targetPlayer then 239 | citizenId = targetPlayer.PlayerData.citizenid 240 | end 241 | else 242 | citizenId = targetId 243 | end 244 | 245 | if not citizenId then 246 | TriggerClientEvent('QBCore:Notify', src, 'Invalid ID or Citizen ID.', 'error') 247 | return 248 | end 249 | 250 | lockerTimers[citizenId] = os.time() + (lockDuration * 60) 251 | saveLockerTimes() 252 | 253 | TriggerClientEvent('QBCore:Notify', src, 'Locker locked for ' .. lockDuration .. ' minutes.', 'success') 254 | end) 255 | 256 | RegisterNetEvent('qb-confiscation:server:unlockLocker', function(targetId) 257 | local src = source 258 | local citizenId 259 | 260 | if tonumber(targetId) then 261 | local targetPlayer = QBCore.Functions.GetPlayer(tonumber(targetId)) 262 | if targetPlayer then 263 | citizenId = targetPlayer.PlayerData.citizenid 264 | end 265 | else 266 | citizenId = targetId 267 | end 268 | 269 | if not citizenId then 270 | TriggerClientEvent('QBCore:Notify', src, 'Invalid ID or Citizen ID.', 'error') 271 | return 272 | end 273 | 274 | lockerTimers[citizenId] = 0 275 | saveLockerTimes() 276 | 277 | TriggerClientEvent('QBCore:Notify', src, 'Locker unlocked.', 'success') 278 | end) 279 | 280 | RegisterNetEvent('qb-confiscation:server:CheckLockerStatus', function(citizenId) 281 | local src = source 282 | local locker = createLocker(citizenId) 283 | local lockerId = 'Locker ' .. citizenId 284 | if locker then 285 | if lockerTimers[citizenId] and lockerTimers[citizenId] > os.time() then 286 | local remainingTime = math.ceil((lockerTimers[citizenId] - os.time()) / 60) 287 | TriggerClientEvent('qb-confiscation:client:LockerStatus', src, true, remainingTime) 288 | else 289 | if Config.Inventory == 'QB' then 290 | TriggerClientEvent('qb-confiscation:client:open-locker-custom', src, lockerId) 291 | elseif Config.Inventory == 'OX' then 292 | exports.ox_inventory:forceOpenInventory(src, 'stash', locker.id) 293 | end 294 | end 295 | else 296 | TriggerClientEvent('QBCore:Notify', src, 'Your locker is empty', 'error') 297 | end 298 | end) 299 | 300 | RegisterNetEvent('qb-confiscation:confiscateItems', function(playerId) 301 | local Player = QBCore.Functions.GetPlayer(playerId) 302 | if not Player then 303 | -- print("Player not found with ID: " .. playerId) 304 | return 305 | end 306 | if Config.Inventory == 'QB' then 307 | local itemsProcessedQB = false 308 | 309 | local shouldProcessItem = function(itemName) 310 | local item = string.lower(itemName) 311 | local isInList = table.contains(Config.Confiscation.Items, item) 312 | if Config.Confiscation.Mode == 'blacklist' then 313 | return isInList 314 | else 315 | return not isInList 316 | end 317 | end 318 | 319 | for _, itemData in pairs(Player.PlayerData.items) do 320 | if itemData and itemData.amount > 0 and shouldProcessItem(itemData.name) then 321 | Player.Functions.RemoveItem(itemData.name, itemData.amount, itemData.slot) 322 | AddToLockerForQB(Player.PlayerData.citizenid, itemData.name, itemData.amount, itemData.info) 323 | itemsProcessedQB = true 324 | end 325 | end 326 | 327 | if itemsProcessedQB then 328 | TriggerClientEvent('QBCore:Notify', playerId, 'Some of your items have been placed in your locker', 'error') 329 | end 330 | 331 | elseif Config.Inventory == 'OX' then 332 | if Player then 333 | local lockerId = 'locker-' .. Player.PlayerData.citizenid 334 | local lockerInventory = exports.ox_inventory:GetInventory(lockerId) 335 | local itemsProcessed = false 336 | 337 | for _, itemSlot in pairs(Player.PlayerData.items) do 338 | if itemSlot.name and itemSlot.count and itemSlot.count > 0 then 339 | local item = string.lower(itemSlot.name) 340 | local amount = itemSlot.count 341 | local isInList = table.contains(Config.Confiscation.Items, item) 342 | 343 | local shouldProcess = (Config.Confiscation.Mode == 'blacklist' and isInList) or 344 | (Config.Confiscation.Mode == 'whitelist' and not isInList) 345 | 346 | if shouldProcess then 347 | local canCarry = lockerInventory and exports.ox_inventory:CanCarryItem(lockerInventory, itemSlot.name, amount) 348 | if canCarry then 349 | itemsProcessed = true 350 | Player.Functions.RemoveItem(itemSlot.name, amount) 351 | AddToLocker(Player.PlayerData.citizenid, itemSlot.name, amount) 352 | else 353 | -- print("Not enough space in the locker to add " .. itemSlot.name) 354 | TriggerClientEvent('QBCore:Notify', playerId, "Not enough space in your locker for " .. itemSlot.name, 'error') 355 | end 356 | end 357 | else 358 | -- print("Invalid item data or count for item: " .. tostring(itemSlot.name)) 359 | end 360 | end 361 | 362 | if itemsProcessed then 363 | TriggerClientEvent('QBCore:Notify', playerId, 'Some of your items have been placed in your locker', 'error') 364 | end 365 | end 366 | end 367 | end) 368 | 369 | function table.contains(table, element) 370 | for _, value in pairs(table) do 371 | if string.lower(value) == element then 372 | return true 373 | end 374 | end 375 | return false 376 | end 377 | 378 | RegisterNetEvent('qb-confiscation:server:OpenLocker', function(inputId) 379 | local src = source 380 | local citizenId 381 | 382 | if tonumber(inputId) then 383 | local targetPlayer = QBCore.Functions.GetPlayer(tonumber(inputId)) 384 | if targetPlayer then 385 | citizenId = targetPlayer.PlayerData.citizenid 386 | end 387 | else 388 | citizenId = inputId 389 | end 390 | 391 | if not citizenId then 392 | TriggerClientEvent('QBCore:Notify', src, 'Invalid ID or Citizen ID.', 'error') 393 | return 394 | end 395 | 396 | if lockerTimers[citizenId] and lockerTimers[citizenId] > os.time() then 397 | local remainingTime = math.ceil((lockerTimers[citizenId] - os.time()) / 60) 398 | TriggerClientEvent('qb-confiscation:client:LockerStatus', src, true, remainingTime) 399 | return 400 | end 401 | 402 | local locker = createLocker(citizenId) 403 | local lockerId = 'Locker ' .. citizenId 404 | 405 | if locker then 406 | if Config.Inventory == 'QB' then 407 | TriggerClientEvent('qb-confiscation:client:open-locker-custom', src, lockerId) 408 | elseif Config.Inventory == 'OX' then 409 | exports.ox_inventory:forceOpenInventory(src, 'stash', locker.id) 410 | end 411 | else 412 | TriggerClientEvent('QBCore:Notify', src, 'Your locker is empty', 'error') 413 | end 414 | end) 415 | 416 | function AddToLocker(citizenId, item, amount, metadata) 417 | local lockerId = 'locker-' .. citizenId 418 | local lockerLabel = 'Locker - ' .. citizenId 419 | 420 | local lockerInventory = exports.ox_inventory:GetInventory(lockerId, citizenId) 421 | if not lockerInventory then 422 | local slots = Config.Locker.slots 423 | local maxWeight = Config.Locker.weight 424 | exports.ox_inventory:RegisterStash(lockerId, lockerLabel, slots, maxWeight, citizenId) 425 | lockerInventory = { id = lockerId, owner = citizenId } 426 | end 427 | 428 | if not exports.ox_inventory:CanCarryItem(lockerInventory, item, amount) then 429 | -- print('Locker cannot carry more items') 430 | return 431 | end 432 | local success, response = exports.ox_inventory:AddItem(lockerId, item, amount) 433 | if not success then 434 | -- print('Failed to add item to locker:', response) 435 | end 436 | end 437 | 438 | function asyncUpdateStashItems(lockerId, items, callback) 439 | local updateQuery = 'UPDATE stashitems SET items = ? WHERE stash = ?' 440 | exports.oxmysql:execute_async(updateQuery, {json.encode(items), lockerId}, function(affectedRows) 441 | if callback then 442 | callback(affectedRows) 443 | end 444 | end) 445 | end 446 | 447 | function AddToLockerForQB(citizenId, item, amount, info) 448 | local lockerId = 'Locker ' .. citizenId 449 | local itemsJson = exports.oxmysql:scalar_async('SELECT items FROM stashitems WHERE stash = ?', { lockerId }) 450 | local items = itemsJson and json.decode(itemsJson) or {} 451 | 452 | local itemData = QBCore.Shared.Items[item:lower()] 453 | if not itemData then 454 | -- print("Item data not found for item:", item) 455 | return 456 | end 457 | local metaInfo = info or {} 458 | 459 | if itemData.type == 'weapon' then 460 | metaInfo.serie = metaInfo.serie or tostring(QBCore.Shared.RandomInt(2) .. QBCore.Shared.RandomStr(3) .. QBCore.Shared.RandomInt(1) .. QBCore.Shared.RandomStr(2) .. QBCore.Shared.RandomInt(3) .. QBCore.Shared.RandomStr(4)) 461 | metaInfo.quality = metaInfo.quality or 100 462 | 463 | elseif itemData.name == 'harness' then 464 | metaInfo.uses = metaInfo.uses or 20 465 | 466 | elseif itemData.name == 'markedbills' then 467 | metaInfo.worth = metaInfo.worth or math.random(5000,10000) 468 | 469 | elseif itemData.name == 'id_card' then 470 | metaInfo.citizenid = metaInfo.citizenid or Player.PlayerData.citizenid 471 | metaInfo.firstname = metaInfo.firstname or Player.PlayerData.charinfo.firstname 472 | metaInfo.lastname = metaInfo.lastname or Player.PlayerData.charinfo.lastname 473 | metaInfo.birthdate = metaInfo.birthdate or Player.PlayerData.charinfo.birthdate 474 | metaInfo.gender = metaInfo.gender or Player.PlayerData.charinfo.gender 475 | metaInfo.nationality = metaInfo.nationality or Player.PlayerData.charinfo.nationality 476 | 477 | elseif itemData.name == 'driver_license' then 478 | metaInfo.firstname = metaInfo.firstname or Player.PlayerData.charinfo.firstname 479 | metaInfo.lastname = metaInfo.lastname or Player.PlayerData.charinfo.lastname 480 | metaInfo.birthdate = metaInfo.birthdate or Player.PlayerData.charinfo.birthdate 481 | metaInfo.type = metaInfo.type or 'Class C Driver License' 482 | end 483 | 484 | local isUnique = itemData.unique 485 | 486 | if isUnique then 487 | for i = 1, amount do 488 | table.insert(items, { 489 | name = item, 490 | amount = 1, 491 | info = metaInfo, 492 | label = itemData.label, 493 | weight = itemData.weight, 494 | unique = true, 495 | slot = #items + 1 496 | }) 497 | end 498 | else 499 | local found = false 500 | for i, lockerItem in ipairs(items) do 501 | if lockerItem.name == item and not lockerItem.unique then 502 | lockerItem.amount = lockerItem.amount + amount 503 | found = true 504 | break 505 | end 506 | end 507 | if not found then 508 | table.insert(items, { 509 | name = item, 510 | amount = amount, 511 | info = metaInfo, 512 | label = itemData.label, 513 | weight = itemData.weight, 514 | unique = false, 515 | slot = #items + 1 516 | }) 517 | end 518 | end 519 | 520 | asyncUpdateStashItems(lockerId, items, function(response) 521 | if response and response > 0 then 522 | -- print('Items updated successfully in locker:', lockerId) 523 | else 524 | -- print('Failed to update items for locker:', lockerId) 525 | end 526 | end) 527 | end 528 | --------------------------------------------------------------------------------