Compreendendo a arquitetura do sistema de garagem
Um sistema de garagem é um dos recursos mais essenciais em qualquer servidor de RPG FiveM, servindo como a principal forma de os jogadores armazenarem, recuperarem e gerenciarem seus veículos. Em sua essência, um sistema de garagem consiste em três camadas interconectadas: uma camada de banco de dados que persiste a propriedade e o estado do veículo, uma camada lógica do lado do servidor que lida com operações de spawn e despawn com validação e uma camada de UI do lado do cliente que permite aos jogadores interagir com seus veículos armazenados. Antes de escrever qualquer código, você precisa decidir sobre as principais escolhas arquitetônicas, como se as garagens são baseadas em localização ou globais, se os jogadores podem acessar qualquer garagem ou apenas aquelas específicas, e como você deseja lidar com as propriedades do veículo, como modificações, nível de combustível e estado de danos. Os melhores sistemas de garagem armazenam o objeto completo de propriedades do veículo para que, quando um jogador recuperar seu carro, ele volte exatamente como o deixou, incluindo pinturas personalizadas, atualizações de desempenho e até mesmo o nível de sujeira na carroceria.
Esquema de banco de dados e persistência de veículos
O esquema do seu banco de dados constitui a base de todo o sistema de garagem. Você precisa de uma tabela que rastreie a propriedade do veículo, o estado atual e as propriedades armazenadas. A coluna de estado é crítica porque determina se um veículo está atualmente gerado no mundo, armazenado em uma garagem ou parado no estacionamento. Aqui está um esquema prático que cobre os campos essenciais:
CREATE TABLE IF NOT EXISTS player_vehicles (
id INT AUTO_INCREMENT PRIMARY KEY,
citizenid VARCHAR(50) NOT NULL,
vehicle VARCHAR(50) NOT NULL,
hash VARCHAR(50) NOT NULL,
mods LONGTEXT DEFAULT '{}',
plate VARCHAR(8) NOT NULL,
fakeplate VARCHAR(8) DEFAULT NULL,
garage VARCHAR(50) DEFAULT 'pillboxgarage',
fuel INT DEFAULT 100,
engine FLOAT DEFAULT 1000.0,
body FLOAT DEFAULT 1000.0,
state INT DEFAULT 1, -- 0 = out, 1 = garaged, 2 = impounded
depotprice INT DEFAULT 0,
drivingdistance INT DEFAULT 0,
INDEX idx_citizenid (citizenid),
INDEX idx_plate (plate),
INDEX idx_state (state)
);A coluna mods armazena um objeto codificado em JSON contendo todas as modificações do veículo retornadas por funções como QBCore.Functions.GetVehicleProperties(vehicle) ou equivalente em ESX. A indexação das colunas citizenid, plate e state garante que as pesquisas permaneçam rápidas mesmo quando sua base de jogadores cresce para milhares. Sempre use consultas parametrizadas ao interagir com esta tabela para evitar ataques de injeção de SQL.
Lógica de Spawn e Despawn do lado do servidor
O lado do servidor é onde acontece toda a validação crítica. Quando um jogador solicita a retirada de um veículo da garagem, o servidor deve verificar se o jogador realmente possui aquele veículo, se o veículo está atualmente no estado de garagem e se há um ponto de spawn válido disponível. Nunca deixe o cliente ditar a posição de spawn diretamente porque os trapaceiros podem gerar veículos em qualquer lugar do mapa. Em vez disso, defina pontos de spawn no servidor e selecione o mais próximo disponível. Aqui está um exemplo de um gerenciador de entrega seguro do lado do servidor:
RegisterNetEvent('garage:server:takeVehicle', function(vehicleId, garageId)
local src = source
local Player = QBCore.Functions.GetPlayer(src)
if not Player then return end
local citizenid = Player.PlayerData.citizenid
local result = MySQL.query.await(
'SELECT * FROM player_vehicles WHERE id = ? AND citizenid = ? AND state = 1',
{vehicleId, citizenid}
)
if not result or not result[1] then
TriggerClientEvent('QBCore:Notify', src, 'Vehicle not found', 'error')
return
end
local vehData = result[1]
local spawnPoint = GetAvailableSpawnPoint(garageId)
if not spawnPoint then
TriggerClientEvent('QBCore:Notify', src, 'No parking spots available', 'error')
return
end
MySQL.update('UPDATE player_vehicles SET state = 0 WHERE id = ?', {vehicleId})
TriggerClientEvent('garage:client:spawnVehicle', src, vehData, spawnPoint)
end)Para o processo de desaparecimento, o servidor precisa capturar as propriedades atuais do veículo antes de removê-lo do mundo. Isso garante que as modificações feitas desde o último armazenamento sejam salvas. Sempre atualize o combustível, a saúde do motor e a saúde do corpo junto com os mods JSON para que tudo persista corretamente. Implemente uma verificação de distância no lado do servidor para garantir que o jogador esteja realmente perto de uma garagem antes de permitir operações de armazenamento.
UI do Garage do lado do cliente com NUI
A UI da garagem é onde os jogadores interagem com o sistema, e uma interface bem projetada faz a diferença entre uma experiência frustrante e perfeita. Use NUI com HTML, CSS e JavaScript para construir um painel responsivo que exiba todos os veículos armazenados na garagem atual. Cada entrada de veículo deve mostrar rapidamente o nome do veículo, placa, nível de combustível e condição geral. Inclui um sistema de visualização que gera o modelo do veículo temporariamente para que os jogadores possam ver o que estão selecionando, especialmente útil quando um jogador possui vários veículos do mesmo tipo. Aqui está a lógica do lado do cliente para abrir o menu da garagem e coletar dados do veículo:
RegisterNetEvent('garage:client:openMenu', function(garageId)
QBCore.Functions.TriggerCallback('garage:server:getVehicles', function(vehicles)
if not vehicles or #vehicles == 0 then
QBCore.Functions.Notify('No vehicles stored here', 'info')
return
end
SetNuiFocus(true, true)
SendNUIMessage({
action = 'openGarage',
vehicles = vehicles,
garageName = Config.Garages[garageId].label
})
end, garageId)
end)
RegisterNUICallback('takeVehicle', function(data, cb)
SetNuiFocus(false, false)
TriggerServerEvent('garage:server:takeVehicle', data.vehicleId, currentGarage)
cb('ok')
end)No lado JavaScript, renderize cada veículo como um cartão com botões de ação para retirar o veículo ou transferi-lo para outra garagem. Considere adicionar opções de classificação e filtragem para que jogadores com coleções grandes possam encontrar rapidamente o veículo de que precisam. Uma barra de pesquisa que filtra por número de placa ou nome do veículo é uma pequena adição que melhora drasticamente a usabilidade em servidores onde os jogadores acumulam muitos veículos ao longo do tempo.
Armazenamento e Restauração de Propriedade de Veículos
Salvar e restaurar adequadamente as propriedades do veículo é uma das partes mais complicadas do desenvolvimento de um sistema de garagem. O objeto de propriedades contém dezenas de campos, incluindo cores, pinturas, luzes de néon, tonalidades das janelas, cor da fumaça dos pneus, extras e todas as modificações de desempenho. Ao armazenar um veículo, capture as propriedades imediatamente antes de excluir a entidade para garantir que você obtenha o estado mais atual. Ao gerar um veículo de volta, você precisa esperar que a entidade carregue totalmente antes de aplicar as propriedades, caso contrário, modificações como rodas personalizadas ou atualizações de motor falharão silenciosamente. Use um pequeno atraso ou um loop de verificação de existência de entidade adequado:
function SpawnAndApplyMods(vehData, spawnPoint)
local model = GetHashKey(vehData.vehicle)
RequestModel(model)
while not HasModelLoaded(model) do
Wait(10)
end
local veh = CreateVehicle(model, spawnPoint.x, spawnPoint.y, spawnPoint.z,
spawnPoint.w, true, false)
while not DoesEntityExist(veh) do
Wait(10)
end
local props = json.decode(vehData.mods)
if props then
QBCore.Functions.SetVehicleProperties(veh, props)
end
SetVehicleFuelLevel(veh, vehData.fuel + 0.0)
SetVehicleEngineHealth(veh, vehData.engine + 0.0)
SetVehicleBodyHealth(veh, vehData.body + 0.0)
SetEntityAsMissionEntity(veh, true, true)
SetModelAsNoLongerNeeded(model)
TaskWarpPedIntoVehicle(PlayerPedId(), veh, -1)
endPreste atenção especial aos veículos adicionais porque às vezes eles têm extras personalizados ou índices de pintura que se comportam de maneira diferente dos veículos GTA padrão. Teste completamente o ciclo de salvamento e restauração de sua propriedade com uma variedade de tipos de veículos para detectar casos extremos antecipadamente.
Integração do sistema de apreensão
Um sistema de apreensão funciona de mãos dadas com sua garagem e adiciona uma camada de realismo que os servidores de roleplay exigem. Os veículos acabam na apreensão por vários motivos: apreensão policial durante uma prisão, limpeza automática de veículos abandonados após a reinicialização do servidor ou ação administrativa por violação de regras. Quando um veículo é apreendido, atualize seu estado para 2 no banco de dados e, opcionalmente, defina um preço de depósito que o jogador deverá pagar para recuperá-lo. O lote apreendido deve funcionar de forma semelhante a uma garagem, mas com a exigência adicional de pagamento antes da liberação. Crie um local de apreensão separado no mapa com seus próprios pontos de spawn e interface NUI que exibe a taxa de apreensão com destaque.
RegisterNetEvent('police:server:impoundVehicle', function(plate, price)
local src = source
local Player = QBCore.Functions.GetPlayer(src)
if not Player then return end
-- Verify the player has police job authorization
if Player.PlayerData.job.name ~= 'police' then return end
local result = MySQL.update.await(
'UPDATE player_vehicles SET state = 2, depotprice = ? WHERE plate = ?',
{price or 500, plate}
)
if result > 0 then
TriggerClientEvent('QBCore:Notify', src, 'Vehicle impounded', 'success')
end
end)Considere implementar um sistema de preços escalonados em que a taxa de apreensão aumente cada vez que o mesmo veículo é apreendido, desencorajando os jogadores de tratarem o apreensão como estacionamento gratuito. Você também pode adicionar uma mecânica baseada no tempo, onde os veículos deixados em apreensão por mais de um número configurável de dias no mundo real são automaticamente liberados de volta para a garagem sem nenhum custo, evitando cenários de perda permanente que frustram os jogadores.
Garage Blips e integração de alvo
Tornar as garagens detectáveis e fáceis de interagir requer o posicionamento adequado dos blip e zonas de interação. Adicione ícones de mapa para cada local de garagem para que os jogadores possam encontrá-los no minimapa e use marcadores baseados em proximidade ou integração de sistema de alvo para o gatilho de interação. Sistemas de alvo como ox_target ou qb-target proporcionam uma experiência mais limpa porque só mostram opções de interação quando o jogador mira em um ponto específico, reduzindo a confusão na tela. Defina os locais de sua garagem em um arquivo de configuração compartilhado que tanto o cliente quanto o servidor podem referenciar, mantendo coordenadas, pontos de spawn e configurações sincronizados:
Config.Garages = {
['pillboxgarage'] = {
label = 'Pillbox Garage',
coords = vector3(215.83, -810.18, 30.73),
spawnPoints = {
vector4(218.32, -803.28, 30.73, 248.5),
vector4(222.41, -799.84, 30.73, 248.5),
vector4(226.52, -796.41, 30.73, 248.5),
},
blip = { sprite = 357, color = 3, scale = 0.7 },
vehicleType = 'car', -- car, boat, aircraft
},
}Apoie vários tipos de veículos criando garagens separadas para barcos e aeronaves com locais de desova apropriados perto da água ou em aeroportos. O filtro vehicleType garante que os jogadores vejam apenas veículos terrestres em uma garagem na rua e apenas barcos em uma marina, evitando confusão e problemas de spawn. Quando um jogador se aproxima de uma garagem, verifique se há algum veículo armazenado lá antes de mostrar o prompt de interação para evitar aberturas de menu desnecessárias para jogadores que não possuem veículos naquele local.
Dicas de otimização de desempenho
Os sistemas de garagem podem se tornar um gargalo de desempenho se não forem implementados com cuidado, especialmente em servidores com centenas de jogadores simultâneos, cada um possuindo vários veículos. Armazene listas de veículos em cache no lado do servidor em vez de consultar o banco de dados toda vez que um jogador abre um menu de garagem e invalide o cache somente quando o estado de um veículo muda. No lado do cliente, evite manter os quadros NUI abertos quando não forem necessários, pois mesmo os quadros NUI ocultos consomem recursos se estiverem executando temporizadores ou animações JavaScript. Ao gerar veículos, certifique-se de limpar as entidades adequadamente, configurando-as como não mais necessárias após o jogador armazená-las, e implemente uma rotina de limpeza alternativa que seja executada periodicamente para capturar quaisquer entidades de veículos órfãs que não tenham desaparecido adequadamente devido a falhas ou desconexões. Use funções nativas como GetGamePool('CVehicle') com moderação e armazene os resultados em cache quando precisar verificar veículos de jogadores existentes no mundo. Por fim, considere implementar um limite máximo de veículos por garagem para manter as consultas ao banco de dados limitadas e evitar que qualquer jogador armazene centenas de veículos, o que poderia retardar as operações de recuperação.

