Skip to content

Commit

Permalink
feat: add qbx_garages support (#43)
Browse files Browse the repository at this point in the history
* feat: added qbx_garages support

* refactor: removed duplicate code

* refactor: addGaragePoint checks

* refactor: cleaned code

* refactor: cleaned code

* Apply suggestions from code review

---------

Co-authored-by: Frowmza <[email protected]>
  • Loading branch information
CodexisPhantom and Frowmza authored Feb 9, 2025
1 parent dc79071 commit a71ebdc
Show file tree
Hide file tree
Showing 5 changed files with 143 additions and 13 deletions.
81 changes: 78 additions & 3 deletions client/realtor.lua
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,24 @@ for k in pairs(sharedConfig.interiors) do
values[#values + 1] = k
end

local car = 0
local shell = 0
local playerCoords = vec3(0, 0, 0)
local garageCoords = nil
local garageHeading = 0.0
local isPreviewing = false

local function showText()
lib.showTextUI('BACKSPACE - Exit \n ARROW LEFT - Turn left \n ARROW RIGHT - Turn right \n ENTER - Confirm Placement')
end

local function spawnCar()
local model = lib.requestModel('sultanrs', 2500)
car = CreateVehicle(model, 0.0, 0.0, 0.0, 0.0, false, false)
SetEntityCompletelyDisableCollision(car, false, false)
SetModelAsNoLongerNeeded(model)
end

local function previewProperty(propertyIndex)
if DoesEntityExist(shell) then DeleteEntity(shell) end
if type(propertyIndex) == 'number' then
Expand Down Expand Up @@ -35,6 +49,63 @@ local function stopPreview()
end
end

local function addGaragePoint()
local isAddingGarage = true
garageCoords = nil

showText()
spawnCar()

while isAddingGarage do
Wait(0)

local hit, _, endCoords = lib.raycast.fromCamera(511, 4, 25.0)

if not hit then
SetEntityCoords(car, 0.0, 0.0, 0.0, false, false, false, false)
else
SetEntityCoords(car, endCoords.x, endCoords.y, endCoords.z + 1.0, false, false, false, false)
SetVehicleOnGroundProperly(car)
end

if IsControlJustPressed(0, 202) then
if DoesEntityExist(car) then
DeleteEntity(car)
end
isAddingGarage = false
end

if IsControlPressed(0, 190) then
garageHeading = (garageHeading + 2.5) % 360.0
SetEntityHeading(car, garageHeading)
Wait(100)
end

if IsControlPressed(0, 189) then
garageHeading = (garageHeading - 2.5) % 360.0
SetEntityHeading(car, garageHeading)
Wait(100)
end

if IsControlJustPressed(0, 18) then
if hit then
garageCoords = endCoords
garageHeading = GetEntityHeading(car)
if DoesEntityExist(car) then
DeleteEntity(car)
end
isAddingGarage = false
else
lib.notify({type = 'error', title = 'Error', description = 'Invalid garage location.'})
end
end

SetEntityHeading(car, garageHeading) -- Prevent the car from rotating
end

lib.hideTextUI()
end

lib.registerMenu({
id = 'qbx_properties_realtor_menu',
title = locale('menu.interior_preview'),
Expand All @@ -54,10 +125,14 @@ lib.registerMenu({
{type = 'input', label = locale('alert.property_name'), description = locale('alert.property_name_description'), required = true, min = 4, max = 32, icon = 'home'},
{type = 'number', label = locale('alert.price'), description = locale('alert.price_description'), icon = 'dollar-sign', required = true, min = 1},
{type = 'number', label = locale('alert.rent_interval'), description = locale('alert.rent_interval_description'), icon = 'clock', min = 1, max = 24, step = 1},
{type = 'checkbox', label = locale('alert.add_garage')},
})
if input then
TriggerServerEvent('qbx_properties:server:createProperty', args[scrollIndex], input, playerCoords)
if not input then return end

if input[4] then
addGaragePoint()
end
TriggerServerEvent('qbx_properties:server:createProperty', args[scrollIndex], input, playerCoords, vec4(garageCoords.x, garageCoords.y, garageCoords.z + 1.0, garageHeading))
end)

RegisterNetEvent('qbx_properties:client:createProperty', function()
Expand All @@ -74,4 +149,4 @@ RegisterNetEvent('qbx_properties:client:createProperty', function()
end
Wait(3000)
end
end)
end)
3 changes: 2 additions & 1 deletion locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@
"price": "Price",
"price_description": "The purchasing price or the price deducted for rent",
"rent_interval": "Rent interval",
"rent_interval_description": "The interval in which the rent will be deducted in hours (makes the property rentable instead of purchasable)"
"rent_interval_description": "The interval in which the rent will be deducted in hours (makes the property rentable instead of purchasable)",
"add_garage": "Add Garage"
},
"menu": {
"add_keyholder": "Add Keyholder",
Expand Down
1 change: 1 addition & 0 deletions property_garages.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
ALTER TABLE `properties` ADD COLUMN IF NOT EXISTS `garage` JSON DEFAULT NULL AFTER `stash_options`;
53 changes: 50 additions & 3 deletions server/property.lua
Original file line number Diff line number Diff line change
Expand Up @@ -338,11 +338,45 @@ AddEventHandler('playerDropped', function ()
MySQL.update('UPDATE players SET position = ? WHERE citizenid = ?', { json.encode(vec4(coords.x, coords.y, coords.z, 0.0)), citizenid[playerSource] })
end)

local function canAccess(source, owner, keyholders)
local player = exports.qbx_core:GetPlayer(source)
if player.PlayerData.citizenid == owner then return true end
for i = 1, #keyholders do
if player.PlayerData.citizenid == keyholders[i] then return true end
end
return false
end

local function registerGarage(name, owner, keyholders, garage)
local garageName = 'property_' .. string.gsub(string.lower(name), ' ', '_')
exports.qbx_garages:RegisterGarage(garageName, {
label = name,
vehicleType = 'car',
accessPoints = {
{
coords = vec4(garage.x, garage.y, garage.z, garage.w),
}
},
canAccess = function(source)
return canAccess(source, owner, keyholders)
end
})
end

local function registerGarages()
local properties = MySQL.query.await('SELECT property_name, owner, keyholders, garage FROM properties WHERE owner IS NOT NULL AND garage IS NOT NULL')
if not properties then return end
for i = 1, #properties do
local property = properties[i]
registerGarage(property.property_name, property.owner, json.decode(property.keyholders), json.decode(property.garage))
end
end

local function startRentThread(propertyId)
CreateThread(function()
while true do
local property = MySQL.single.await('SELECT owner, price, rent_interval, property_name FROM properties WHERE id = ?', {propertyId})
if not property or not property.owner then break end
if not property or not property.owner then break end

local player = exports.qbx_core:GetPlayerByCitizenId(property.owner) or exports.qbx_core:GetOfflinePlayer(property.owner)
if not player then print(string.format('%s does not exist anymore, consider checking property id %s', property.owner, propertyId)) break end
Expand All @@ -369,7 +403,7 @@ RegisterNetEvent('qbx_properties:server:rentProperty', function(propertyId)
local playerSource = source --[[@as number]]
local player = exports.qbx_core:GetPlayer(playerSource)
local playerCoords = GetEntityCoords(GetPlayerPed(playerSource))
local property = MySQL.single.await('SELECT owner, price, property_name, coords, rent_interval FROM properties WHERE id = ?', {propertyId})
local property = MySQL.single.await('SELECT owner, price, property_name, coords, rent_interval, keyholders, garage FROM properties WHERE id = ?', {propertyId})
local propertyCoords = json.decode(property.coords)
if #(playerCoords - vec3(propertyCoords.x, propertyCoords.y, propertyCoords.z)) > 8.0 then return end
if property.owner then return end
Expand All @@ -380,6 +414,10 @@ RegisterNetEvent('qbx_properties:server:rentProperty', function(propertyId)
return
end

if property.garage then
registerGarage(property.property_name, player.PlayerData.citizenid, json.decode(property.keyholders), json.decode(property.garage))
end

exports.qbx_core:Notify(playerSource, string.format('Successfully started renting %s', property.property_name), 'success')
MySQL.update('UPDATE properties SET owner = ? WHERE id = ?', {player.PlayerData.citizenid, propertyId})
startRentThread()
Expand All @@ -389,7 +427,7 @@ RegisterNetEvent('qbx_properties:server:buyProperty', function(propertyId)
local playerSource = source --[[@as number]]
local player = exports.qbx_core:GetPlayer(playerSource)
local playerCoords = GetEntityCoords(GetPlayerPed(playerSource))
local property = MySQL.single.await('SELECT owner, price, property_name, coords FROM properties WHERE id = ?', {propertyId})
local property = MySQL.single.await('SELECT owner, price, property_name, coords, keyholders, garage FROM properties WHERE id = ?', {propertyId})
local propertyCoords = json.decode(property.coords)

if #(playerCoords - vec3(propertyCoords.x, propertyCoords.y, propertyCoords.z)) > 8.0 or property.owner then return end
Expand All @@ -399,20 +437,29 @@ RegisterNetEvent('qbx_properties:server:buyProperty', function(propertyId)
return
end

if property.garage then
registerGarage(property.property_name, player.PlayerData.citizenid, json.decode(property.keyholders), json.decode(property.garage))
end

MySQL.update('UPDATE properties SET owner = ? WHERE id = ?', {player.PlayerData.citizenid, propertyId})
exports.qbx_core:Notify(playerSource, string.format('Successfully purchased %s for $%s', property.property_name, property.price))
end)

Citizen.CreateThreadNow(function()
local sql1 = LoadResourceFile(cache.resource, 'property.sql')
local sql2 = LoadResourceFile(cache.resource, 'decorations.sql')
local sql3 = LoadResourceFile(cache.resource, 'property_garages.sql')

MySQL.query.await(sql1)
MySQL.query.await(sql2)
MySQL.query.await(sql3)

local properties = MySQL.query.await('SELECT id FROM properties WHERE owner IS NOT NULL AND rent_interval IS NOT NULL')
for i = 1, #properties do
startRentThread(properties[i].id)
end

registerGarages()
end)

RegisterNetEvent('qbx_properties:server:stopRenting', function()
Expand Down
18 changes: 12 additions & 6 deletions server/realtor.lua
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,14 @@ lib.addCommand('createproperty', {
TriggerClientEvent('qbx_properties:client:createProperty', source)
end)

RegisterNetEvent('qbx_properties:server:createProperty', function(interiorIndex, data, propertyCoords)
RegisterNetEvent('qbx_properties:server:createProperty', function(interiorIndex, data, propertyCoords, garageCoords)
local playerSource = source --[[@as number]]
local player = exports.qbx_core:GetPlayer(playerSource)
local playerCoords = GetEntityCoords(GetPlayerPed(playerSource))

if player.PlayerData.job.name ~= 'realestate' then return end
if #(playerCoords - propertyCoords) > 5.0 then return end
if not garageCoords and #(playerCoords - propertyCoords) > 5.0 then return end
if garageCoords and #(playerCoords - garageCoords.xyz) > 5.0 then return end

local interactData = {
{
Expand All @@ -33,23 +34,28 @@ RegisterNetEvent('qbx_properties:server:createProperty', function(interiorIndex,
coords = sharedConfig.interiors[interiorIndex].exit
}
}

local stashData = {
{
coords = sharedConfig.interiors[interiorIndex].stash,
slots = config.apartmentStash.slots,
maxWeight = config.apartmentStash.maxWeight,
}
}

local result = MySQL.single.await('SELECT id FROM properties ORDER BY id DESC', {})
local propertNumber = result?.id or 0
MySQL.insert('INSERT INTO `properties` (`coords`, `property_name`, `price`, `interior`, `interact_options`, `stash_options`, `rent_interval`) VALUES (?, ?, ?, ?, ?, ?, ?)', {
local propertyNumber = result?.id or 0
local propertyName = string.format('%s %s', data[1], propertyNumber)

MySQL.insert('INSERT INTO `properties` (`coords`, `property_name`, `price`, `interior`, `interact_options`, `stash_options`, `rent_interval`, `garage`) VALUES (?, ?, ?, ?, ?, ?, ?, ?)', {
json.encode(propertyCoords),
string.format('%s %s', data[1], propertNumber),
propertyName,
data[2],
interiorIndex,
json.encode(interactData),
json.encode(stashData),
data[3]
data[3],
garageCoords and json.encode(garageCoords) or nil,
})
TriggerClientEvent('qbx_properties:client:addProperty', -1, propertyCoords)
end)

0 comments on commit a71ebdc

Please sign in to comment.