291 lines
9.8 KiB
Lua
291 lines
9.8 KiB
Lua
assert(lib.checkDependency('qbx_core', '1.2.0', true))
|
|
lib.versionCheck('Qbox-project/qbx_vehicles')
|
|
|
|
---@class ErrorResult
|
|
---@field code string
|
|
---@field message string
|
|
|
|
---@enum State
|
|
local State = {
|
|
OUT = 0,
|
|
GARAGED = 1,
|
|
IMPOUNDED = 2
|
|
}
|
|
|
|
local triggerEventHooks = require '@qbx_core.modules.hooks'
|
|
|
|
---Returns true if the given plate exists
|
|
---@param plate string
|
|
---@return boolean
|
|
local function doesEntityPlateExist(plate)
|
|
local result = MySQL.scalar.await('SELECT 1 FROM player_vehicles WHERE plate = ? LIMIT 1', {plate})
|
|
return result ~= nil
|
|
end
|
|
|
|
exports('DoesPlayerVehiclePlateExist', doesEntityPlateExist)
|
|
|
|
---@class PlayerVehicle
|
|
---@field id number
|
|
---@field citizenid? string
|
|
---@field modelName string
|
|
---@field garage string
|
|
---@field state State
|
|
---@field depotPrice integer
|
|
---@field props table ox_lib properties table
|
|
|
|
---@class PlayerVehiclesFilters
|
|
---@field citizenid? string
|
|
---@field states? State|State[]
|
|
---@field garage? string
|
|
|
|
---@class PlayerVehiclesInternalFilters: PlayerVehiclesFilters
|
|
---@field vehicleId? number
|
|
|
|
---@param filters? PlayerVehiclesInternalFilters
|
|
---@return string whereClause, any[] placeholders
|
|
local function buildWhereClause(filters)
|
|
if not filters then
|
|
return '', {}
|
|
end
|
|
local placeholders = {}
|
|
local whereClauseCrumbs = {}
|
|
if filters.vehicleId then
|
|
whereClauseCrumbs[#whereClauseCrumbs+1] = 'id = ?'
|
|
placeholders[#placeholders+1] = filters.vehicleId
|
|
end
|
|
if filters.citizenid then
|
|
whereClauseCrumbs[#whereClauseCrumbs+1] = 'citizenid = ?'
|
|
placeholders[#placeholders+1] = filters.citizenid
|
|
end
|
|
if filters.garage then
|
|
whereClauseCrumbs[#whereClauseCrumbs+1] = 'garage = ?'
|
|
placeholders[#placeholders+1] = filters.garage
|
|
end
|
|
if filters.states then
|
|
if type(filters.states) ~= 'table' then
|
|
---@diagnostic disable-next-line: assign-type-mismatch
|
|
filters.states = {filters.states}
|
|
end
|
|
if #filters.states > 0 then
|
|
local statePlaceholders = {}
|
|
for i = 1, #filters.states do
|
|
placeholders[#placeholders+1] = filters.states[i]
|
|
statePlaceholders[i] = 'state = ?'
|
|
end
|
|
whereClauseCrumbs[#whereClauseCrumbs+1] = string.format('(%s)', table.concat(statePlaceholders, ' OR '))
|
|
end
|
|
end
|
|
|
|
return string.format(' WHERE %s', table.concat(whereClauseCrumbs, ' AND ')), placeholders
|
|
end
|
|
|
|
---@param filters? PlayerVehiclesInternalFilters
|
|
---@return PlayerVehicle[]
|
|
local function getPlayerVehiclesInternal(filters)
|
|
local query = 'SELECT id, citizenid, vehicle, mods, garage, state, depotprice FROM player_vehicles'
|
|
local whereClause, placeholders = buildWhereClause(filters)
|
|
lib.print.debug(query .. whereClause)
|
|
local results = MySQL.query.await(query .. whereClause, placeholders)
|
|
local ownedVehicles = {}
|
|
for _, data in pairs(results) do
|
|
ownedVehicles[#ownedVehicles+1] = {
|
|
id = data.id,
|
|
citizenid = data.citizenid,
|
|
modelName = data.vehicle,
|
|
garage = data.garage,
|
|
state = data.state,
|
|
depotPrice = data.depotprice,
|
|
props = json.decode(data.mods)
|
|
}
|
|
end
|
|
return ownedVehicles
|
|
end
|
|
|
|
---@param filters? PlayerVehiclesFilters
|
|
---@return PlayerVehicle[]
|
|
local function getPlayerVehicles(filters)
|
|
---@diagnostic disable-next-line: param-type-mismatch
|
|
return getPlayerVehiclesInternal(filters)
|
|
end
|
|
|
|
exports('GetPlayerVehicles', getPlayerVehicles)
|
|
|
|
---@param vehicleId number
|
|
---@param filters? PlayerVehiclesFilters
|
|
---@return PlayerVehicle?
|
|
local function getPlayerVehicle(vehicleId, filters)
|
|
assert(vehicleId ~= nil, "required field vehicleId was nil")
|
|
if not filters then filters = {} end
|
|
---@diagnostic disable-next-line: inject-field
|
|
filters.vehicleId = vehicleId
|
|
---@diagnostic disable-next-line: param-type-mismatch
|
|
return getPlayerVehiclesInternal(filters)[1]
|
|
end
|
|
|
|
exports('GetPlayerVehicle', getPlayerVehicle)
|
|
|
|
---@class CreatePlayerVehicleRequest
|
|
---@field model string model name
|
|
---@field citizenid? string owner of the vehicle
|
|
---@field garage? string
|
|
---@field props? table ox_lib properties to set. See https://github.com/overextended/ox_lib/blob/master/resource/vehicleProperties/client.lua#L3
|
|
|
|
---@param request CreatePlayerVehicleRequest
|
|
---@return integer? vehicleId, ErrorResult? errorResult
|
|
local function createPlayerVehicle(request)
|
|
assert(request.model ~= nil, 'missing required field: model')
|
|
|
|
local props = request.props or {}
|
|
if not props.plate then
|
|
repeat
|
|
props.plate = qbx.generateRandomPlate()
|
|
until doesEntityPlateExist(props.plate) == false
|
|
end
|
|
props.engineHealth = props.engineHealth or 1000
|
|
props.bodyHealth = props.bodyHealth or 1000
|
|
props.fuelLevel = props.fuelLevel or 100
|
|
props.model = joaat(request.model)
|
|
|
|
if not triggerEventHooks('createPlayerVehicle', { citizenid = request.citizenid, garage = request.garage, props = props }) then
|
|
return nil, {
|
|
code = 'hook_cancelled',
|
|
message = 'a createPlayerVehicle event hook cancelled this operation'
|
|
}
|
|
end
|
|
|
|
return MySQL.insert.await('INSERT INTO player_vehicles (license, citizenid, vehicle, hash, mods, plate, state, garage) VALUES ((SELECT license FROM players WHERE citizenid = @citizenid), @citizenid, @vehicle, @hash, @mods, @plate, @state, @garage)', {
|
|
citizenid = request.citizenid,
|
|
vehicle = request.model,
|
|
hash = props.model,
|
|
mods = json.encode(props),
|
|
plate = props.plate,
|
|
state = request.garage and State.GARAGED or State.OUT,
|
|
garage = request.garage
|
|
})
|
|
end
|
|
|
|
exports('CreatePlayerVehicle', createPlayerVehicle)
|
|
|
|
---@param vehicleId integer
|
|
---@param citizenid? string
|
|
---@return boolean success, ErrorResult? errorResult
|
|
local function setPlayerVehicleOwner(vehicleId, citizenid)
|
|
assert(vehicleId ~= nil, "required field vehicleId was nil")
|
|
if not triggerEventHooks('changeVehicleOwner', { vehicleId = vehicleId, newCitizenId = citizenid }) then
|
|
return false, {
|
|
code = 'hook_cancelled',
|
|
message = 'a changeVehicleOwner event hook cancelled this operation'
|
|
}
|
|
end
|
|
MySQL.update.await('UPDATE player_vehicles SET citizenid = @citizenid, license = (SELECT license FROM players WHERE citizenid = @citizenid) WHERE id = @id', {
|
|
citizenid = citizenid,
|
|
id = vehicleId
|
|
})
|
|
return true
|
|
end
|
|
|
|
exports('SetPlayerVehicleOwner', setPlayerVehicleOwner)
|
|
|
|
---@param idType 'citizenid'|'license'|'plate'|'vehicleId'
|
|
---@param idValue string | number
|
|
---@return boolean success, ErrorResult? errorResult
|
|
local function deletePlayerVehicles(idType, idValue)
|
|
assert(idType == 'citizenid' or idType == 'license' or idType == 'plate' or idType == 'vehicleId', json.encode(idType) .. ' is not a valid idType')
|
|
|
|
local column = idType == 'vehicleId' and 'id' or idType
|
|
MySQL.query.await('DELETE FROM player_vehicles WHERE ' .. column .. ' = ?', {
|
|
idValue
|
|
})
|
|
return true
|
|
end
|
|
|
|
exports('DeletePlayerVehicles', deletePlayerVehicles)
|
|
|
|
---Find the vehicleId with the given plate if it exists.
|
|
---@param plate string
|
|
---@return integer? vehicleId
|
|
local function getVehicleIdByPlate(plate)
|
|
return MySQL.scalar.await('SELECT id FROM player_vehicles WHERE plate = ?', {
|
|
qbx.string.trim(plate)
|
|
})
|
|
end
|
|
|
|
exports('GetVehicleIdByPlate', getVehicleIdByPlate)
|
|
|
|
---@class SaveVehicleOptions
|
|
---@field garage? string
|
|
---@field state? State
|
|
---@field depotPrice? integer
|
|
---@field props? table ox_lib properties table
|
|
|
|
---@param vehicleId integer
|
|
---@param options SaveVehicleOptions
|
|
---@return string query, table placeholders
|
|
local function buildSaveVehicleQuery(vehicleId, options)
|
|
local crumbs = {}
|
|
local placeholders = {}
|
|
|
|
if options.state then
|
|
crumbs[#crumbs+1] = 'state = ?'
|
|
placeholders[#placeholders+1] = options.state
|
|
end
|
|
|
|
if options.depotPrice then
|
|
crumbs[#crumbs+1] = 'depotprice = ?'
|
|
placeholders[#placeholders+1] = options.depotPrice
|
|
end
|
|
|
|
if options.garage then
|
|
crumbs[#crumbs+1] = 'garage = ?'
|
|
placeholders[#placeholders+1] = options.garage
|
|
end
|
|
|
|
if options.props then
|
|
crumbs[#crumbs+1] = 'mods = ?'
|
|
placeholders[#placeholders+1] = json.encode(options.props)
|
|
|
|
if options.props.plate then
|
|
crumbs[#crumbs+1] = 'plate = ?'
|
|
placeholders[#placeholders+1] = options.props.plate
|
|
end
|
|
|
|
if options.props.fuelLevel then
|
|
crumbs[#crumbs+1] = 'fuel = ?'
|
|
placeholders[#placeholders+1] = options.props.fuelLevel
|
|
end
|
|
|
|
if options.props.engineHealth then
|
|
crumbs[#crumbs+1] = 'engine = ?'
|
|
placeholders[#placeholders+1] = options.props.engineHealth
|
|
end
|
|
|
|
if options.props.bodyHealth then
|
|
crumbs[#crumbs+1] = 'body = ?'
|
|
placeholders[#placeholders+1] = options.props.bodyHealth
|
|
end
|
|
end
|
|
|
|
placeholders[#placeholders+1] = vehicleId
|
|
|
|
return string.format('UPDATE player_vehicles SET %s WHERE id = ?', table.concat(crumbs, ',')), placeholders
|
|
end
|
|
|
|
---@param vehicle number entity
|
|
---@param options SaveVehicleOptions
|
|
---@return boolean success, ErrorResult? errorResult
|
|
local function saveVehicle(vehicle, options)
|
|
local vehicleId = Entity(vehicle).state.vehicleid or getVehicleIdByPlate(GetVehicleNumberPlateText(vehicle))
|
|
if not vehicleId then
|
|
return false, {
|
|
code = 'not_owned',
|
|
message = 'vehicle does not have a vehicleId and plate is not in the player_vehicles table'
|
|
}
|
|
end
|
|
|
|
local query, placeholders = buildSaveVehicleQuery(vehicleId, options)
|
|
MySQL.update.await(query, placeholders)
|
|
TriggerEvent('qbx_vehicles:server:vehicleSaved', vehicleId)
|
|
return true
|
|
end
|
|
|
|
exports('SaveVehicle', saveVehicle) |