local sharedConfig = require 'config.shared' local clientConfig = require 'config.client' local interiorShell DecorationObjects = {} local properties = {} local insideProperty = false local isPropertyRental = false local interactions local isConcealing = false local concealWhitelist = {} local blips = {} local function createBlip(apartmentCoords, label) local blip = AddBlipForCoord(apartmentCoords.x, apartmentCoords.y, apartmentCoords.z) SetBlipSprite(blip, 40) SetBlipAsShortRange(blip, true) SetBlipScale(blip, 0.8) SetBlipColour(blip, 2) BeginTextCommandSetBlipName('STRING') AddTextComponentString(label) EndTextCommandSetBlipName(blip) return blip end local function prepareKeyMenu() local keyholders = lib.callback.await('qbx_properties:callback:requestKeyHolders') local options = { { title = locale('menu.add_keyholder'), icon = 'plus', arrow = true, onSelect = function() local insidePlayers = lib.callback.await('qbx_properties:callback:requestPotentialKeyholders') local options = {} for i = 1, #insidePlayers do options[#options + 1] = { title = insidePlayers[i].name, icon = 'user', arrow = true, onSelect = function() local alert = lib.alertDialog({ header = insidePlayers[i].name, content = locale('alert.give_keys'), centered = true, cancel = true }) if alert == 'confirm' then TriggerServerEvent('qbx_properties:server:addKeyholder', insidePlayers[i].citizenid) end end } end lib.registerContext({ id = 'qbx_properties_insideMenu', title = locale('menu.people_inside'), menu = 'qbx_properties_keyMenu', options = options }) lib.showContext('qbx_properties_insideMenu') end } } for i = 1, #keyholders do options[#options + 1] = { title = keyholders[i].name, icon = 'user', arrow = true, onSelect = function() local alert = lib.alertDialog({ header = keyholders[i].name, content = locale('alert.want_remove_keys'), centered = true, cancel = true }) if alert == 'confirm' then TriggerServerEvent('qbx_properties:server:removeKeyholder', keyholders[i].citizenid) end end } end lib.registerContext({ id = 'qbx_properties_keyMenu', title = locale('menu.keyholders'), menu = 'qbx_properties_manageMenu', options = options }) lib.showContext('qbx_properties_keyMenu') end local function prepareDoorbellMenu() local ringers = lib.callback.await('qbx_properties:callback:requestRingers') local options = {} for i = 1, #ringers do options[#options + 1] = { title = ringers[i].name, icon = 'user', arrow = true, onSelect = function() local alert = lib.alertDialog({ header = ringers[i].name, content = locale('alert.want_let_person_in'), centered = true, cancel = true }) if alert == 'confirm' then TriggerServerEvent('qbx_properties:server:letRingerIn', ringers[i].citizenid) end end } end lib.registerContext({ id = 'qbx_properties_doorbellMenu', title = locale('menu.doorbell_ringers'), menu = 'qbx_properties_manageMenu', options = options }) lib.showContext('qbx_properties_doorbellMenu') end local function prepareManageMenu() local hasAccess = lib.callback.await('qbx_properties:callback:checkAccess') if not hasAccess then exports.qbx_core:Notify(locale('notify.no_access'), 'error') return end local options = { { title = locale('menu.manage_keys'), icon = 'key', arrow = true, onSelect = function() prepareKeyMenu() end }, { title = locale('menu.doorbell'), icon = 'bell', arrow = true, onSelect = function() prepareDoorbellMenu() end }, { title = locale('menu.start_decorating'), icon = 'shrimp', onSelect = function() ToggleDecorating() end } } if isPropertyRental then options[#options+1] = { title = 'Stop Renting', icon = 'file-invoice-dollar', arrow = true, onSelect = function() local alert = lib.alertDialog({ header = 'Stop Renting', content = 'Are you sure that you want to stop renting this place?', centered = true, cancel = true }) if alert == 'confirm' then TriggerServerEvent('qbx_properties:server:stopRenting') end end } end lib.registerContext({ id = 'qbx_properties_manageMenu', title = locale('menu.manage_property'), options = options }) lib.showContext('qbx_properties_manageMenu') end local function checkInteractions() local interactOptions = { ['stash'] = function(coords) qbx.drawText3d({ coords = coords, text = locale('drawtext.stash') }) if IsControlJustPressed(0, 38) then TriggerServerEvent('qbx_properties:server:openStash') end end, ['exit'] = function(coords) qbx.drawText3d({ coords = coords, text = locale('drawtext.exit') }) if IsControlJustPressed(0, 38) then DoScreenFadeOut(1000) while not IsScreenFadedOut() do Wait(0) end TriggerServerEvent('qbx_properties:server:exitProperty') end if IsControlJustPressed(0, 47) then prepareManageMenu() end end, ['clothing'] = function(coords) qbx.drawText3d({ coords = coords, text = locale('drawtext.clothing') }) if IsControlJustPressed(0, 38) then exports['illenium-appearance']:startPlayerCustomization(function(appearance) if appearance then TriggerServerEvent("illenium-appearance:server:saveAppearance", appearance) end end, { components = true, componentConfig = { masks = true, upperBody = true, lowerBody = true, bags = true, shoes = true, scarfAndChains = true, bodyArmor = true, shirts = true, decals = true, jackets = true }, props = true, propConfig = { hats = true, glasses = true, ear = true, watches = true, bracelets = true }, enableExit = true, }) end if IsControlJustPressed(0, 47) then TriggerEvent('illenium-appearance:client:openOutfitMenu') end end, ['logout'] = function(coords) qbx.drawText3d({ coords = coords, text = locale('drawtext.logout') }) if IsControlJustPressed(0, 38) then DoScreenFadeOut(1000) while not IsScreenFadedOut() do Wait(0) end TriggerServerEvent('qbx_properties:server:logoutProperty') end end, } CreateThread(function() while insideProperty do local sleep = 800 local playerCoords = GetEntityCoords(cache.ped) for i = 1, #interactions do if #(playerCoords - interactions[i].coords) < 1.5 and not IsDecorating then sleep = 0 interactOptions[interactions[i].type](interactions[i].coords) end end Wait(sleep) end end) end local function hideExterior(name) local models = clientConfig.exteriorHashs[name] if not models then return end CreateThread(function() while insideProperty do for i = 1, #models, 1 do EnableExteriorCullModelThisFrame(models[i]) end Wait(0) end end) end RegisterNetEvent('qbx_properties:client:updateInteractions', function(interactionsData, interiorString, isRental) DoScreenFadeIn(1000) interactions = interactionsData insideProperty = true isPropertyRental = isRental checkInteractions() hideExterior(interiorString) end) RegisterNetEvent('qbx_properties:client:createInterior', function(interiorHash, interiorCoords) lib.requestModel(interiorHash, 2000) interiorShell = CreateObjectNoOffset(interiorHash, interiorCoords.x, interiorCoords.y, interiorCoords.z, false, false, false) FreezeEntityPosition(interiorShell, true) SetModelAsNoLongerNeeded(interiorHash) end) RegisterNetEvent('qbx_properties:client:loadDecorations', function(decorations) for i = 1, #decorations do local decoration = decorations[i] lib.requestModel(decoration.model, 5000) DecorationObjects[decoration.id] = CreateObjectNoOffset(decoration.model, decoration.coords.x, decoration.coords.y, decoration.coords.z, false, false, false) SetEntityCollision(DecorationObjects[decoration.id], true, true) FreezeEntityPosition(DecorationObjects[decoration.id], true) SetEntityRotation(DecorationObjects[decoration.id], decoration.rotation.x, decoration.rotation.y, decoration.rotation.z, 2, false) SetModelAsNoLongerNeeded(decoration.model) end end) RegisterNetEvent('qbx_properties:client:addDecoration', function(id, hash, coords, rotation) lib.requestModel(hash, 5000) DecorationObjects[id] = CreateObjectNoOffset(hash, coords.x, coords.y, coords.z, false, false, false) FreezeEntityPosition(DecorationObjects[id], true) SetEntityRotation(DecorationObjects[id], rotation.x, rotation.y, rotation.z, 2, false) SetModelAsNoLongerNeeded(hash) end) RegisterNetEvent('qbx_properties:client:removeDecoration', function(objectId) if DoesEntityExist(DecorationObjects[objectId]) then DeleteEntity(DecorationObjects[objectId]) end DecorationObjects[objectId] = nil end) RegisterNetEvent('qbx_properties:client:unloadProperty', function() DoScreenFadeIn(1000) insideProperty = false if DoesEntityExist(interiorShell) then DeleteEntity(interiorShell) end for _, v in pairs(DecorationObjects) do if DoesEntityExist(v) then DeleteEntity(v) end end interiorShell = nil DecorationObjects = {} end) local function singlePropertyMenu(property, noBackMenu) local options = {} if QBX.PlayerData.citizenid == property.owner or lib.table.contains(json.decode(property.keyholders), QBX.PlayerData.citizenid) then options[#options + 1] = { title = locale('menu.enter'), icon = 'cog', arrow = true, onSelect = function() DoScreenFadeOut(1000) while not IsScreenFadedOut() do Wait(0) end end, serverEvent = 'qbx_properties:server:enterProperty', args = { id = property.id } } elseif property.owner == nil then if property.rent_interval then options[#options + 1] = { title = 'Rent', icon = 'dollar-sign', arrow = true, onSelect = function() local alert = lib.alertDialog({ header = string.format('Renting - %s', property.property_name), content = string.format('Are you sure you want to rent %s for $%s which will be billed every %sh(s)?', property.property_name, property.price, property.rent_interval), centered = true, cancel = true }) if alert == 'confirm' then TriggerServerEvent('qbx_properties:server:rentProperty', property.id) end end, } else options[#options + 1] = { title = 'Buy', icon = 'dollar-sign', arrow = true, onSelect = function() local alert = lib.alertDialog({ header = string.format('Buying - %s', property.property_name), content = string.format('Are you sure you want to buy %s for $%s?', property.property_name, property.price), centered = true, cancel = true }) if alert == 'confirm' then TriggerServerEvent('qbx_properties:server:buyProperty', property.id) end end, } end else options[#options + 1] = { title = locale('menu.ring_doorbell'), icon = 'bell', arrow = true, serverEvent = 'qbx_properties:server:ringProperty', args = { id = property.id } } end local menu = 'qbx_properties_propertiesMenu' ---@diagnostic disable-next-line: cast-local-type if noBackMenu then menu = nil end lib.registerContext({ id = 'qbx_properties_propertyMenu', title = property.property_name, menu = menu, options = options }) lib.showContext('qbx_properties_propertyMenu') end local function propertyMenu(propertyList, owned) local options = { { title = locale('menu.retrieve_properties'), description = locale('menu.show_owned_properties'), icon = 'bars', onSelect = function() propertyMenu(propertyList, true) end } } for i = 1, #propertyList do if owned and propertyList[i].owner == QBX.PlayerData.citizenid or lib.table.contains(json.decode(propertyList[i].keyholders), QBX.PlayerData.citizenid) then options[#options + 1] = { title = propertyList[i].property_name, icon = 'home', arrow = true, onSelect = function() singlePropertyMenu(propertyList[i]) end } elseif not owned then options[#options + 1] = { title = propertyList[i].property_name, icon = 'home', arrow = true, onSelect = function() singlePropertyMenu(propertyList[i]) end } end end lib.registerContext({ id = 'qbx_properties_propertiesMenu', title = locale('menu.properties'), options = options }) lib.showContext('qbx_properties_propertiesMenu') end function PreparePropertyMenu(propertyCoords) local propertyList = lib.callback.await('qbx_properties:callback:requestProperties', false, propertyCoords) if #propertyList == 1 then singlePropertyMenu(propertyList[1], true) else propertyMenu(propertyList) end end CreateThread(function() for i = 1, #sharedConfig.apartmentOptions do local data = sharedConfig.apartmentOptions[i] if not blips[data.enter] then blips[data.enter] = createBlip(data.enter, data.label) end end properties = lib.callback.await('qbx_properties:callback:loadProperties') while true do local sleep = 800 local playerCoords = GetEntityCoords(cache.ped) for i = 1, #properties do if #(playerCoords - properties[i].xyz) < 1.6 then sleep = 0 qbx.drawText3d({ coords = properties[i].xyz, text = locale('drawtext.view_property') }) if IsControlJustPressed(0, 38) then PreparePropertyMenu(properties[i]) end end end Wait(sleep) end end) RegisterNetEvent('qbx_properties:client:concealPlayers', function(playerIds) local players = GetActivePlayers() for i = 1, #players do NetworkConcealPlayer(players[i], false, false) end concealWhitelist = playerIds if not isConcealing then isConcealing = true while isConcealing do players = GetActivePlayers() for i = 1, #players do if not lib.table.contains(concealWhitelist, GetPlayerServerId(players[i])) then NetworkConcealPlayer(players[i], true, false) end end Wait(3000) end end end) RegisterNetEvent('qbx_properties:client:revealPlayers', function() local players = GetActivePlayers() for i = 1, #players do NetworkConcealPlayer(players[i], false, false) end isConcealing = false end) RegisterNetEvent('qbx_properties:client:addProperty', function(propertyCoords) if lib.table.contains(properties, propertyCoords) then return end properties[#properties + 1] = propertyCoords end)