Kartik Scripts

YSeries Phone Setup

kartik-properties includes built-in support for YSeries Phone's Housing Application. This allows players to manage owned and leased properties, grant or revoke keys, navigate to their properties, and remotely toggle door locks directly from their phone.

Official Integration Notice: Official out-of-the-box support for kartik-properties is coming soon directly in an upcoming update of YSeries Phone. In the meantime, follow the straightforward setup guide below.


Features Supported

  • 🏠 Property Directory: Lists all citizen-owned properties, rented/leased units, and properties where the player holds keys.
  • 🔑 Key Management: Grant keys to nearby or online players directly through the phone.
  • 🚫 Revoke Keys: Remove keyholders in real-time with instant synchronization.
  • 🔒 Remote Door Lock: Lock and unlock property doors remotely from the app.
  • 📍 GPS Navigation: Set a waypoint directly to the property's physical entrance.

⚙️ Installation & Setup

Step 1: Configure YSeries Auto-Detection

Open yseries/shared/resourceDetection.lua and add 'kartik-properties' to the end of the housings auto-detection array:

local housings = {
    'qb-houses',
    'ps-housing',
    'flight-housing',
    'bcs_housing',
    'nolag_properties',
    'RxHousing',
    'kartik-properties',
}

Ensure Config.Housing in yseries/config/config.housing.lua is set to "auto" (to auto-detect) or set it manually to "kartik-properties":

Config.Housing = "kartik-properties"

Step 2: Create Client Bridge File

Create the file: yseries/client/apps/framework/home/kartik-properties.lua and paste the following code:

CreateThread(function()
    if Config.Housing ~= 'kartik-properties' then return end

    debugPrint("Using kartik-properties")

    function MapHomes(homes)
        return homes
    end

    function MapKeyHolders(keys)
        return keys
    end
end)

Step 3: Create Server Bridge File

Create the file: yseries/server/apps/framework/home/kartik-properties.lua and paste the following code:

CreateThread(function()
    if Config.Housing ~= 'kartik-properties' then return end

    debugPrint("Using kartik-properties")

    lib.callback.register('yseries:server:home:get', function(source)
        local playerId = Framework.GetPlayerFromId(source)?.identifier

        if (not playerId) then
            debugPrint('Home: Could not get player id')
            return {}
        end

        local housingData = exports['kartik-properties']:GetPlayerHousingData(playerId)
        if not housingData then return {} end

        local propertiesResult = {}
        for _, v in ipairs(housingData) do
            local keyholderIds = {}
            if v.keyholders then
                for _, kh in ipairs(v.keyholders) do
                    if kh.identifier ~= v.owner then
                        keyholderIds[#keyholderIds + 1] = kh.identifier
                    end
                end
            end

            propertiesResult[#propertiesResult + 1] = {
                id = v.houseId,
                citizenid = v.owner,
                coords = v.coords,
                house = v.houseId,
                identifier = playerId,
                keyholders = keyholderIds,
                label = v.label,
                name = v.label,
                owned = true,
                price = v.price or 0,
                tier = 1,
            }
        end

        return propertiesResult
    end)

    lib.callback.register('yseries:server:home:get-key-holders-details', function(_, keyHolders)
        local charinfoData = {}
        if not keyHolders or type(keyHolders) ~= 'table' then
            return charinfoData
        end

        for _, citizenid in ipairs(keyHolders) do
            local name = citizenid
            if Config.Framework == 'esx' then
                local result = MySQL.single.await('SELECT firstname, lastname FROM users WHERE identifier = ?', { citizenid })
                if result and result.firstname then
                    name = result.firstname .. ' ' .. result.lastname
                end
            elseif Config.Framework == 'qb' or Config.Framework == 'qbox' then
                local charinfoStr = MySQL.scalar.await('SELECT charinfo FROM players WHERE citizenid = ?', { citizenid })
                local charinfo = charinfoStr and json.decode(charinfoStr)
                if charinfo and charinfo.firstname then
                    name = charinfo.firstname .. ' ' .. charinfo.lastname
                end
            end

            charinfoData[#charinfoData + 1] = {
                citizenid = citizenid,
                name = name
            }
        end

        return charinfoData
    end)

    lib.callback.register('yseries:server:home:remove-key-holder', function(source, data)
        local propertyId = tonumber(data.house)
        local removedKeyHolder = data.removedKeyHolder
        local revokedByCid = Framework.GetPlayerFromId(source)?.identifier
        if not revokedByCid then return false end

        local propData = exports['kartik-properties']:GetPropertyHousingData(propertyId)
        if not propData then return false end

        -- Safeguard: Owner cannot remove themselves, and only the owner or the keyholder themselves can revoke a key
        if removedKeyHolder == propData.owner then
            return false
        end

        if revokedByCid ~= propData.owner and revokedByCid ~= removedKeyHolder then
            return false
        end

        local res = exports['kartik-properties']:RevokeHousingKey(propertyId, removedKeyHolder, revokedByCid)
        if res and res.success then
            return true
        else
            return false
        end
    end)

    lib.callback.register('yseries:server:home:add-key-holder', function(source, data)
        local targetSource = tonumber(data.targetSource)
        local targetPlayer = Framework.GetPlayerFromId(targetSource)
        local targetCid = targetPlayer?.identifier

        local grantedByCid = Framework.GetPlayerFromId(source)?.identifier
        if not targetCid or not grantedByCid then
            debugPrint('Home: Could not get target or source player id')
            return { success = false }
        end

        local propertyId = tonumber(data.houseId)
        local propData = exports['kartik-properties']:GetPropertyHousingData(propertyId)
        if not propData then return { success = false } end

        -- Safeguard: target must not be the owner, and only the owner can add keyholders
        if targetCid == propData.owner or grantedByCid ~= propData.owner then
            return { success = false }
        end

        -- Check if the player already exists in the array
        for _, id in ipairs(data.currentKeyholders) do
            if id == targetCid then
                return { success = false }
            end
        end

        local res = exports['kartik-properties']:GrantHousingKey(propertyId, targetCid, grantedByCid)
        if res and res.success then
            local keyholders = data.currentKeyholders
            keyholders[#keyholders + 1] = targetCid
            return {
                success = true,
                newKeyholders = keyholders
            }
        else
            return { success = false }
        end
    end)
end)

Step 4: Ensure Startup Order in server.cfg

Make sure kartik-properties starts before yseries:

ensure kartik-properties
ensure yseries

On this page