2025-04-07 01:41:12 +00:00

247 lines
8.4 KiB
Lua

assert(lib.checkDependency('qbx_core', '1.19.0', true))
assert(lib.checkDependency('qbx_vehicles', '1.3.1', true))
lib.versionCheck('Qbox-project/qbx_garages')
---@class ErrorResult
---@field code string
---@field message string
---@class PlayerVehicle
---@field id number
---@field citizenid? string
---@field modelName string
---@field garage string
---@field state VehicleState
---@field depotPrice integer
---@field props table ox_lib properties table
Config = require 'config.server'
VEHICLES = exports.qbx_core:GetVehiclesByName()
Storage = require 'server.storage'
---@type table<string, GarageConfig>
Garages = Config.garages
lib.callback.register('qbx_garages:server:getGarages', function()
return Garages
end)
---Returns garages for use server side.
local function getGarages()
return Garages
end
exports('GetGarages', getGarages)
---@param name string
---@param config GarageConfig
local function registerGarage(name, config)
Garages[name] = config
TriggerClientEvent('qbx_garages:client:garageRegistered', -1, name, config)
TriggerEvent('qbx_garages:server:garageRegistered', name, config)
end
exports('RegisterGarage', registerGarage)
---Sets the vehicle's garage. It is the caller's responsibility to make sure the vehicle is not currently spawned in the world, or else this may have no effect.
---@param vehicleId integer
---@param garageName string
---@return boolean success, ErrorResult?
local function setVehicleGarage(vehicleId, garageName)
local garage = Garages[garageName]
if not garage then
return false, {
code = 'not_found',
message = string.format('garage name %s not found. Did you forget to register it?', garageName)
}
end
local state = garage.type == GarageType.DEPOT and VehicleState.IMPOUNDED or VehicleState.GARAGED
local numRowsAffected = Storage.setVehicleGarage(vehicleId, garageName, state)
if numRowsAffected == 0 then
return false, {
code = 'no_rows_changed',
message = string.format('no rows were changed for vehicleId=%s', vehicleId)
}
end
return true
end
exports('SetVehicleGarage', setVehicleGarage)
---Sets the vehicle's price for retrieval at a depot. Only affects vehicles that are OUT or IMPOUNDED.
---@param vehicleId integer
---@param depotPrice integer
---@return boolean success, ErrorResult?
local function setVehicleDepotPrice(vehicleId, depotPrice)
local numRowsAffected = Storage.setVehicleDepotPrice(vehicleId, depotPrice)
if numRowsAffected == 0 then
return false, {
code = 'no_rows_changed',
message = string.format('no rows were changed for vehicleId=%s', vehicleId)
}
end
return true
end
exports('SetVehicleDepotPrice', setVehicleDepotPrice)
function FindPlateOnServer(plate)
local vehicles = GetAllVehicles()
for i = 1, #vehicles do
if plate == GetVehicleNumberPlateText(vehicles[i]) then
return true
end
end
end
---@param garage string
---@return GarageType?
function GetGarageType(garage)
return Garages[garage]?.type
end
---@class PlayerVehiclesFilters
---@field citizenid? string
---@field states? VehicleState|VehicleState[]
---@field garage? string
---@param source number
---@param garageName string
---@return PlayerVehiclesFilters
function GetPlayerVehicleFilter(source, garageName)
local player = exports.qbx_core:GetPlayer(source)
local garage = Garages[garageName]
local filter = {}
filter.citizenid = not garage.shared and player.PlayerData.citizenid or nil
filter.states = garage.states or VehicleState.GARAGED
filter.garage = not garage.skipGarageCheck and garageName or nil
return filter
end
local function getCanAccessGarage(player, garage)
if garage.groups and not exports.qbx_core:HasPrimaryGroup(player.PlayerData.source, garage.groups) then
return false
end
if garage.canAccess ~= nil and not garage.canAccess(player.PlayerData.source) then
return false
end
return true
end
---@param playerVehicle PlayerVehicle
---@return VehicleType
local function getVehicleType(playerVehicle)
if VEHICLES[playerVehicle.modelName].category == 'helicopters' or VEHICLES[playerVehicle.modelName].category == 'planes' then
return VehicleType.AIR
elseif VEHICLES[playerVehicle.modelName].category == 'boats' then
return VehicleType.SEA
else
return VehicleType.CAR
end
end
---@param source number
---@param garageName string
---@return PlayerVehicle[]?
lib.callback.register('qbx_garages:server:getGarageVehicles', function(source, garageName)
local player = exports.qbx_core:GetPlayer(source)
local garage = Garages[garageName]
if not getCanAccessGarage(player, garage) then return end
local filter = GetPlayerVehicleFilter(source, garageName)
local playerVehicles = exports.qbx_vehicles:GetPlayerVehicles(filter)
local toSend = {}
if not playerVehicles[1] then return end
for _, vehicle in pairs(playerVehicles) do
if not FindPlateOnServer(vehicle.props.plate) then
local vehicleType = Garages[garageName].vehicleType
if vehicleType == getVehicleType(vehicle) then
toSend[#toSend + 1] = vehicle
end
end
end
return toSend
end)
---@param source number
---@param vehicleId string
---@param garageName string
---@return boolean
local function isParkable(source, vehicleId, garageName)
local garageType = GetGarageType(garageName)
--- DEPOTS are only for retrieving, not storing
if garageType == GarageType.DEPOT then return false end
if not vehicleId then return false end
local player = exports.qbx_core:GetPlayer(source)
local garage = Garages[garageName]
if not getCanAccessGarage(player, garage) then
return false
end
---@type PlayerVehicle
local playerVehicle = exports.qbx_vehicles:GetPlayerVehicle(vehicleId)
if getVehicleType(playerVehicle) ~= garage.vehicleType then
return false
end
if not garage.shared then
if playerVehicle.citizenid ~= player.PlayerData.citizenid then
return false
end
end
return true
end
lib.callback.register('qbx_garages:server:isParkable', function(source, garage, netId)
local vehicle = NetworkGetEntityFromNetworkId(netId)
local vehicleId = Entity(vehicle).state.vehicleid or exports.qbx_vehicles:GetVehicleIdByPlate(GetVehicleNumberPlateText(vehicle))
return isParkable(source, vehicleId, garage)
end)
---@param source number
---@param netId number
---@param props table ox_lib vehicle props https://github.com/overextended/ox_lib/blob/master/resource/vehicleProperties/client.lua#L3
---@param garage string
lib.callback.register('qbx_garages:server:parkVehicle', function(source, netId, props, garage)
assert(Garages[garage] ~= nil, string.format('Garage %s not found. Did you register this garage?', garage))
local vehicle = NetworkGetEntityFromNetworkId(netId)
local vehicleId = Entity(vehicle).state.vehicleid or exports.qbx_vehicles:GetVehicleIdByPlate(GetVehicleNumberPlateText(vehicle))
local owned = isParkable(source, vehicleId, garage) --Check ownership
if not owned then
exports.qbx_core:Notify(source, locale('error.not_owned'), 'error')
return
end
exports.qbx_vehicles:SaveVehicle(vehicle, {
garage = garage,
state = VehicleState.GARAGED,
props = props
})
exports.qbx_core:DeleteVehicle(vehicle)
end)
AddEventHandler('onResourceStart', function(resource)
if resource ~= cache.resource then return end
Wait(100)
if Config.autoRespawn then
Storage.moveOutVehiclesIntoGarages()
end
end)
---@param vehicleId string
---@return boolean? success true if successfully paid
lib.callback.register('qbx_garages:server:payDepotPrice', function(source, vehicleId)
local player = exports.qbx_core:GetPlayer(source)
local cashBalance = player.PlayerData.money.cash
local bankBalance = player.PlayerData.money.bank
local vehicle = exports.qbx_vehicles:GetPlayerVehicle(vehicleId)
local depotPrice = vehicle.depotPrice
if not depotPrice or depotPrice == 0 then return true end
if cashBalance >= depotPrice then
player.Functions.RemoveMoney('cash', depotPrice, 'paid-depot')
return true
elseif bankBalance >= depotPrice then
player.Functions.RemoveMoney('bank', depotPrice, 'paid-depot')
return true
end
end)