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