docsresourcesQb Apartments

QB-Apartments

QB-Apartments provides a comprehensive apartment housing system for QBCore servers, offering players the ability to purchase, customize, and manage apartment properties with advanced features and integrations.

📋 Overview

QB-Apartments includes:

  • Multiple Apartment Types - Studios, 1-bedroom, 2-bedroom, and luxury apartments
  • Apartment Ownership - Buy, sell, and transfer property ownership
  • Interior Customization - Furniture placement and decoration options
  • Storage System - Personal storage with configurable limits
  • Wardrobe Integration - Save and change outfits in your apartment
  • Visitor System - Invite friends and manage access permissions
  • Utility Bills - Realistic electricity and water bills
  • Real Estate Market - Dynamic pricing and availability

⚙️ Installation & Setup

Prerequisites

  • QB-Core framework installed
  • QB-Inventory for storage functionality
  • QB-Banking for financial transactions
  • QB-Phone (optional, for real estate notifications)
  • QB-Interior (optional, for advanced interior customization)

Installation Steps

  1. Download Resource

    git clone https://github.com/qbcore-framework/qb-apartments.git
  2. Place in Resources

    resources/
    └── [qb]/
        └── qb-apartments/
  3. Database Setup

    -- Import the SQL file
    SOURCE qb-apartments.sql;
     
    -- Main apartments table
    CREATE TABLE IF NOT EXISTS `apartments` (
      `id` int(11) NOT NULL AUTO_INCREMENT,
      `name` varchar(255) DEFAULT NULL,
      `type` varchar(255) DEFAULT NULL,
      `label` varchar(255) DEFAULT NULL,
      `citizenid` varchar(255) DEFAULT NULL,
      `price` int(11) DEFAULT NULL,
      `tier` tinyint(2) DEFAULT 1,
      PRIMARY KEY (`id`),
      KEY `citizenid` (`citizenid`),
      KEY `name` (`name`)
    );
     
    -- Apartment objects (furniture)
    CREATE TABLE IF NOT EXISTS `apartment_objects` (
      `id` int(11) NOT NULL AUTO_INCREMENT,
      `apartment_id` int(11) NOT NULL,
      `object_id` varchar(255) NOT NULL,
      `coords` text NOT NULL,
      `rotation` text NOT NULL,
      `model` varchar(255) NOT NULL,
      PRIMARY KEY (`id`),
      KEY `apartment_id` (`apartment_id`)
    );
  4. Server Configuration

    # server.cfg
    ensure qb-apartments

🔧 Configuration

Main Configuration

-- config.lua
Config = Config or {}
 
-- Apartment settings
Config.Payment = 0 -- Apartment payment time (0 = disabled, time in days)
Config.RentPayment = true -- Enable rent payments
Config.PaymentAccount = 'bank' -- Payment account (bank/cash)
 
-- Apartment spawn settings
Config.SpawnInside = true -- Spawn inside apartment on login
Config.EnableGarages = true -- Enable apartment garages
 
-- Utility bills
Config.UtilityBills = {
    enabled = true,
    electricity = 150, -- Daily electricity cost
    water = 80, -- Daily water cost
    internet = 50 -- Daily internet cost
}
 
-- Apartment interaction
Config.InteractionDistance = 2.0
Config.DrawMarkers = true
Config.Use3DText = true

Apartment Types

-- config.lua - Apartment configurations
Config.Apartments = {
    ["apartment1"] = {
        label = "South Rockford Drive",
        coords = {
            enter = vector4(-1037.17, -2738.01, 13.76, 331.47),
            exit = vector4(-1037.17, -2738.01, 13.76, 331.47)
        },
        objects = {
            exit = vector3(-1037.17, -2738.01, 13.76),
            stash = vector3(-1035.17, -2738.01, 13.76),
            wardrobe = vector3(-1034.17, -2737.01, 13.76),
            logout = vector3(-1033.17, -2738.01, 13.76)
        },
        type = "shell", -- shell or mlo
        tier = 1,
        price = 50000,
        maxStorage = 4000000, -- Storage weight limit
        garage = {
            enabled = true,
            coords = vector4(-1039.17, -2740.01, 13.76, 331.47),
            spawnCoords = vector4(-1041.17, -2742.01, 13.76, 331.47),
            maxVehicles = 2
        }
    },
    
    ["apartment2"] = {
        label = "Morningwood Blvd",
        coords = {
            enter = vector4(-1289.78, -430.57, 35.15, 218.5),
            exit = vector4(-1289.78, -430.57, 35.15, 218.5)
        },
        objects = {
            exit = vector3(-1289.78, -430.57, 35.15),
            stash = vector3(-1287.78, -430.57, 35.15),
            wardrobe = vector3(-1286.78, -429.57, 35.15),
            logout = vector3(-1285.78, -430.57, 35.15)
        },
        type = "shell",
        tier = 2,
        price = 85000,
        maxStorage = 6000000,
        garage = {
            enabled = true,
            coords = vector4(-1291.78, -432.57, 35.15, 218.5),
            spawnCoords = vector4(-1293.78, -434.57, 35.15, 218.5),
            maxVehicles = 3
        }
    }
}

Shell Interiors

-- Shell interior configurations
Config.Shells = {
    ["shell_apartment1"] = {
        label = "Basic Studio",
        hash = `shell_apartment1`,
        price = 0, -- Additional cost for this shell
        objects = {
            stash = vector3(4.42, 1.06, 1.2),
            wardrobe = vector3(8.81, 4.74, 1.2),
            logout = vector3(-0.3, -2.74, 1.2)
        }
    },
    ["shell_apartment2"] = {
        label = "Modern Studio",
        hash = `shell_apartment2`,
        price = 10000,
        objects = {
            stash = vector3(-1.24, 4.84, 1.2),
            wardrobe = vector3(7.84, 3.18, 1.2),
            logout = vector3(-0.92, -5.32, 1.2)
        }
    },
    ["shell_loft"] = {
        label = "Urban Loft",
        hash = `shell_loft`,
        price = 25000,
        objects = {
            stash = vector3(-8.25, 2.72, 1.2),
            wardrobe = vector3(3.46, 7.65, 1.2),
            logout = vector3(-1.28, -6.59, 1.2)
        }
    }
}

🏠 Apartment Management

Purchasing Process

  1. View Available Apartments

    • Players visit apartment buildings
    • Interact with entrance markers
    • Browse available units and pricing
  2. Purchase Decision

    • Select apartment type and shell
    • Choose payment method (cash/bank)
    • Confirm purchase and receive keys
  3. Move In

    • Automatic apartment assignment
    • Access to storage and wardrobe
    • Set as spawn location

Ownership Features

-- Example apartment ownership data
local apartmentData = {
    citizenid = "ABC12345",
    apartment_id = "apartment1_unit_5",
    apartment_type = "apartment1",
    shell = "shell_apartment1",
    purchase_date = "2025-01-15",
    rent_due = "2025-02-15",
    utilities = {
        electricity = true,
        water = true,
        internet = true
    },
    access_list = {
        "DEF67890", -- Friend's citizenid
        "GHI11111"  -- Partner's citizenid
    }
}

🔌 API Reference

Server Events

Apartment Management

-- Create new apartment
RegisterNetEvent('qb-apartments:server:CreateApartment', function(apartmentType, citizenid)
    local Player = QBCore.Functions.GetPlayer(source)
    if not Player then return end
    
    local apartment = CreatePlayerApartment(apartmentType, citizenid or Player.PlayerData.citizenid)
    if apartment then
        TriggerClientEvent('QBCore:Notify', source, 'Apartment created successfully', 'success')
    end
end)
 
-- Transfer apartment ownership
RegisterNetEvent('qb-apartments:server:TransferApartment', function(apartmentId, newOwner)
    local src = source
    local Player = QBCore.Functions.GetPlayer(src)
    
    if Player and CanTransferApartment(apartmentId, Player.PlayerData.citizenid) then
        TransferApartmentOwnership(apartmentId, newOwner)
        TriggerClientEvent('QBCore:Notify', src, 'Apartment transferred successfully', 'success')
    end
end)

Storage System

-- Open apartment storage
RegisterNetEvent('qb-apartments:server:openStorage', function()
    local src = source
    local Player = QBCore.Functions.GetPlayer(src)
    local apartment = GetPlayerApartment(Player.PlayerData.citizenid)
    
    if apartment then
        exports['qb-inventory']:OpenInventory(src, 'stash', 'apartment_' .. apartment.id, {
            maxweight = apartment.maxStorage,
            slots = 50,
        })
    end
end)

Utility System

-- Pay utility bills
RegisterNetEvent('qb-apartments:server:payBills', function(apartmentId)
    local src = source
    local Player = QBCore.Functions.GetPlayer(src)
    local bills = GetApartmentBills(apartmentId)
    
    if Player.Functions.RemoveMoney('bank', bills.total) then
        ClearApartmentBills(apartmentId)
        TriggerClientEvent('QBCore:Notify', src, 'Bills paid successfully', 'success')
    end
end)

Client Events

Apartment Access

-- Enter apartment
RegisterNetEvent('qb-apartments:client:enterApartment', function(apartmentData)
    local playerPed = PlayerPedId()
    
    -- Fade out screen
    DoScreenFadeOut(500)
    Wait(500)
    
    -- Teleport to apartment interior
    SetEntityCoords(playerPed, apartmentData.interior.coords.x, 
                   apartmentData.interior.coords.y, apartmentData.interior.coords.z)
    SetEntityHeading(playerPed, apartmentData.interior.coords.w)
    
    -- Load shell if needed
    if apartmentData.shell then
        LoadApartmentShell(apartmentData.shell, apartmentData.interior.coords)
    end
    
    DoScreenFadeIn(500)
    TriggerEvent('qb-apartments:client:setupApartmentInteractions')
end)
 
-- Setup apartment interactions
RegisterNetEvent('qb-apartments:client:setupApartmentInteractions', function()
    -- Storage interaction
    exports['qb-target']:AddBoxZone("apartment_storage", vector3(-1.24, 4.84, 1.2), 1.0, 1.0, {
        name = "apartment_storage",
        heading = 0,
        debugPoly = false,
        minZ = 0.2,
        maxZ = 2.2,
    }, {
        options = {
            {
                type = "client",
                event = "qb-apartments:client:openStorage",
                icon = "fas fa-box",
                label = "Open Storage",
            }
        },
        distance = 2.0
    })
end)

Exports

Apartment Information

-- Get player's apartment
local apartment = exports['qb-apartments']:GetOwnedApartment(citizenid)
if apartment then
    print('Player owns apartment: ' .. apartment.label)
end
 
-- Check if player has access to apartment
local hasAccess = exports['qb-apartments']:HasApartmentAccess(apartmentId, citizenid)

Real Estate Functions

-- Get available apartments for sale
local availableApartments = exports['qb-apartments']:GetAvailableApartments()
 
-- Calculate apartment value
local value = exports['qb-apartments']:CalculateApartmentValue(apartmentType, upgrades)
 
-- Get apartment market data
local marketData = exports['qb-apartments']:GetMarketData()

🎮 Player Experience

Daily Life Features

  1. Storage Management

    • Personal inventory extension
    • Item organization and sorting
    • Shared storage for roommates
  2. Wardrobe System

    • Save custom outfits
    • Quick outfit changes
    • Seasonal clothing storage
  3. Utility Management

    • Monitor electricity usage
    • Pay bills to avoid disconnection
    • Energy-saving features
  4. Security Features

    • Key management system
    • Access permission controls
    • Security camera integration

Social Features

-- Invite system example
RegisterNetEvent('qb-apartments:client:invitePlayer', function(targetId)
    local apartment = GetPlayerApartment()
    if apartment and CanInviteToApartment(apartment.id) then
        TriggerServerEvent('qb-apartments:server:sendInvite', targetId, apartment.id)
    end
end)
 
-- Roommate system
RegisterNetEvent('qb-apartments:server:addRoommate', function(apartmentId, citizenid)
    local src = source
    local Player = QBCore.Functions.GetPlayer(src)
    
    if IsApartmentOwner(apartmentId, Player.PlayerData.citizenid) then
        AddApartmentRoommate(apartmentId, citizenid)
        TriggerClientEvent('QBCore:Notify', src, 'Roommate added successfully', 'success')
    end
end)

🛠️ Furniture System

Furniture Placement

-- Place furniture in apartment
local function PlaceFurniture(model, coords, rotation)
    local object = CreateObject(model, coords.x, coords.y, coords.z, false, false, false)
    SetEntityRotation(object, rotation.x, rotation.y, rotation.z, 2, true)
    FreezeEntityPosition(object, true)
    
    -- Save to database
    TriggerServerEvent('qb-apartments:server:saveFurniture', {
        apartmentId = GetCurrentApartment(),
        model = model,
        coords = coords,
        rotation = rotation
    })
    
    return object
end
 
-- Furniture categories
Config.Furniture = {
    ["seating"] = {
        ["sofa_01"] = {
            model = `prop_couch_01`,
            label = "Modern Sofa",
            price = 1500,
            category = "seating"
        },
        ["chair_office"] = {
            model = `prop_chair_office_01`,
            label = "Office Chair",
            price = 400,
            category = "seating"
        }
    },
    ["tables"] = {
        ["coffee_table"] = {
            model = `prop_coffee_table_01`,
            label = "Coffee Table",
            price = 800,
            category = "tables"
        }
    },
    ["decorations"] = {
        ["plant_01"] = {
            model = `prop_plant_01a`,
            label = "House Plant",
            price = 150,
            category = "decorations"
        }
    }
}

Furniture Store Integration

-- Furniture shopping system
RegisterNetEvent('qb-apartments:client:openFurnitureShop', function()
    local elements = {}
    
    for category, items in pairs(Config.Furniture) do
        for itemId, item in pairs(items) do
            table.insert(elements, {
                label = item.label .. ' - $' .. item.price,
                value = itemId,
                item = item
            })
        end
    end
    
    -- Open furniture catalog
    SetNuiFocus(true, true)
    SendNUIMessage({
        action = "openFurnitureShop",
        furniture = elements
    })
end)

🏗️ Integration Examples

With QB-Banking

-- Apartment mortgage system
RegisterNetEvent('qb-apartments:server:processMortgage', function(apartmentData)
    local src = source
    local Player = QBCore.Functions.GetPlayer(src)
    
    local mortgageAmount = apartmentData.price * 0.8 -- 80% financing
    local downPayment = apartmentData.price * 0.2   -- 20% down
    
    if Player.Functions.RemoveMoney('bank', downPayment) then
        -- Create mortgage account
        exports['qb-banking']:CreateLoan(Player.PlayerData.citizenid, {
            amount = mortgageAmount,
            rate = 3.5, -- 3.5% interest
            term = 360, -- 30 years
            type = 'mortgage',
            collateral = apartmentData.id
        })
        
        -- Process apartment purchase
        CreatePlayerApartment(apartmentData.type, Player.PlayerData.citizenid)
    end
end)

With QB-Phone

-- Real estate app integration
RegisterNetEvent('qb-apartments:client:openRealEstateApp', function()
    local apartments = GetAvailableApartments()
    
    TriggerEvent('qb-phone:client:app:open', 'realestate', {
        apartments = apartments,
        owned = GetPlayerApartment(),
        market = GetMarketData()
    })
end)
 
-- Property notifications
RegisterNetEvent('qb-apartments:server:sendPropertyAlert', function(citizenid, message)
    TriggerClientEvent('qb-phone:client:addNotification', GetPlayerByCitizenId(citizenid), {
        title = "Property Alert",
        text = message,
        icon = "fas fa-home"
    })
end)

With QB-Inventory

-- Apartment storage integration
exports['qb-inventory']:CreateStorage('apartment_' .. apartmentId, {
    maxweight = apartment.maxStorage,
    slots = 50,
    restricted = {apartment.citizenid} -- Only owner can access
})
 
-- Moving boxes for apartment transfers
local function CreateMovingBox(citizenid, items)
    local boxId = 'moving_box_' .. citizenid .. '_' .. os.time()
    
    exports['qb-inventory']:CreateStorage(boxId, {
        maxweight = 100000,
        slots = 40,
        items = items,
        temporary = true -- Auto-delete after 7 days
    })
    
    return boxId
end

💰 Economic System

Dynamic Pricing

-- Real estate market calculations
local function CalculateApartmentValue(apartmentType, factors)
    local basePrice = Config.Apartments[apartmentType].price
    local multiplier = 1.0
    
    -- Location desirability
    multiplier = multiplier + (factors.location_score * 0.1)
    
    -- Market demand
    local occupancyRate = GetAreaOccupancyRate(apartmentType)
    if occupancyRate > 0.8 then
        multiplier = multiplier + 0.15 -- High demand
    elseif occupancyRate < 0.3 then
        multiplier = multiplier - 0.15 -- Low demand
    end
    
    -- Neighborhood safety
    multiplier = multiplier + (factors.safety_rating * 0.05)
    
    -- Amenities bonus
    if factors.has_garage then multiplier = multiplier + 0.1 end
    if factors.has_gym then multiplier = multiplier + 0.08 end
    if factors.has_pool then multiplier = multiplier + 0.12 end
    
    return math.floor(basePrice * multiplier)
end

Rent and Utilities

-- Automated billing system
CreateThread(function()
    while true do
        Wait(86400000) -- Run once per day
        
        for apartmentId, apartment in pairs(GetAllApartments()) do
            if apartment.citizenid then
                local bills = CalculateUtilityBills(apartmentId)
                local rent = CalculateRent(apartment.type)
                
                -- Add bills to player's account
                AddPlayerBills(apartment.citizenid, {
                    rent = rent,
                    electricity = bills.electricity,
                    water = bills.water,
                    internet = bills.internet
                })
                
                -- Send notification
                TriggerEvent('qb-apartments:server:sendPropertyAlert', 
                    apartment.citizenid, 
                    'Your monthly bills are ready for payment: $' .. (rent + bills.total)
                )
            end
        end
    end
end)

🛡️ Security Features

Access Control

-- Advanced security system
local function CheckApartmentAccess(apartmentId, citizenid)
    local apartment = GetApartmentData(apartmentId)
    
    -- Owner access
    if apartment.citizenid == citizenid then
        return true, 'owner'
    end
    
    -- Roommate access
    if IsRoommate(apartmentId, citizenid) then
        return true, 'roommate'
    end
    
    -- Temporary access (key sharing)
    if HasTemporaryAccess(apartmentId, citizenid) then
        return true, 'temporary'
    end
    
    -- Emergency access (police/medical)
    if HasEmergencyAccess(citizenid) then
        return true, 'emergency'
    end
    
    return false, 'denied'
end
 
-- Security logging
local function LogApartmentAccess(apartmentId, citizenid, accessType, action)
    MySQL.insert('INSERT INTO apartment_logs (apartment_id, citizenid, access_type, action, timestamp) VALUES (?, ?, ?, ?, ?)', {
        apartmentId, citizenid, accessType, action, os.time()
    })
end

Anti-Griefing Protection

-- Prevent apartment exploitation
local apartmentCooldowns = {}
 
RegisterNetEvent('qb-apartments:server:enterApartment', function(apartmentId)
    local src = source
    local citizenid = QBCore.Functions.GetIdentifier(src, 'citizenid')
    
    -- Check access permissions
    local hasAccess, accessType = CheckApartmentAccess(apartmentId, citizenid)
    if not hasAccess then
        TriggerClientEvent('QBCore:Notify', src, 'You do not have access to this apartment', 'error')
        return
    end
    
    -- Check cooldown to prevent spam
    local cooldownKey = citizenid .. '_' .. apartmentId
    if apartmentCooldowns[cooldownKey] and apartmentCooldowns[cooldownKey] > GetGameTimer() then
        TriggerClientEvent('QBCore:Notify', src, 'Please wait before entering again', 'error')
        return
    end
    
    apartmentCooldowns[cooldownKey] = GetGameTimer() + 5000 -- 5 second cooldown
    
    -- Log access
    LogApartmentAccess(apartmentId, citizenid, accessType, 'enter')
    
    -- Process apartment entry
    TriggerClientEvent('qb-apartments:client:enterApartment', src, GetApartmentData(apartmentId))
end)

❓ Troubleshooting

Common Issues

Issue: Players can’t enter apartments

-- Debug apartment access
RegisterCommand('debugapartment', function(source, args)
    local apartmentId = args[1]
    local apartment = GetApartmentData(apartmentId)
    
    if apartment then
        print('Apartment Data:')
        print(json.encode(apartment, {indent = true}))
    else
        print('Apartment not found: ' .. apartmentId)
    end
end, true)

Issue: Storage not working

-- Verify storage setup
local function VerifyApartmentStorage(apartmentId)
    local storageId = 'apartment_' .. apartmentId
    local storage = exports['qb-inventory']:GetStorage(storageId)
    
    if not storage then
        print('Creating missing storage for apartment: ' .. apartmentId)
        exports['qb-inventory']:CreateStorage(storageId, {
            maxweight = 4000000,
            slots = 50
        })
    end
end

Issue: Bills not calculating correctly

-- Debug utility calculations
RegisterCommand('checkbills', function(source, args)
    local citizenid = args[1] or QBCore.Functions.GetIdentifier(source, 'citizenid')
    local apartment = GetPlayerApartment(citizenid)
    
    if apartment then
        local bills = CalculateUtilityBills(apartment.id)
        print('Bills for ' .. citizenid .. ':')
        print('Electricity: $' .. bills.electricity)
        print('Water: $' .. bills.water)
        print('Internet: $' .. bills.internet)
        print('Total: $' .. bills.total)
    end
end, true)

Performance Optimization

-- Optimize apartment loading
local loadedApartments = {}
 
local function LoadApartmentShell(shellType, coords)
    local shellHash = GetHashKey(shellType)
    
    if not loadedApartments[shellHash] then
        RequestModel(shellHash)
        while not HasModelLoaded(shellHash) do
            Wait(10)
        end
        
        loadedApartments[shellHash] = CreateObject(shellHash, coords.x, coords.y, coords.z, false, false, false)
        FreezeEntityPosition(loadedApartments[shellHash], true)
    end
    
    return loadedApartments[shellHash]
end

📚 Additional Resources