EndCore Framework

en-garages

Abandoned roadside vehicles, claiming and owning cars, persistent parking, garages, dealers, fuel and repair kits.

GTA's traffic and parked cars are switched off on an EndCore server, so every vehicle in the world comes from en-garages. It leaves abandoned vehicles at roadsides, out of players' sight. They are damaged, low on fuel and often locked. You break in or hotwire one (see en-vehiclekeys), drive it to a garage lot and pay cash to claim it. You then get a key item and the vehicle becomes yours for good.

Owned vehicles stay exactly where you parked them, even across restarts. They only exist in the world while a player is nearby. You can store and retrieve them at garages, recall one from wherever it is, or recover it after a wreck. Dealers sell a few working vehicles for cash. The resource also handles fuel (burned while driving, refilled from jerry cans, cans filled at the rare working pump) and repair kits.

At a glance

Depends on/onesync, oxmysql, en-core, en-ui, en-inventory, en-vehiclekeys
Start afterAll of the above
Database tablesencore_vehicles (created automatically)
Config filesconfig/shared.lua (Config, sent to clients), config/server.lua (ServerConfig, server only)
Key bindsen_garages_use (default E): store, claim, open a garage or browse a dealer, depending on where you are
In-game builderContent kinds garage and dealer in en-admin

How it works

Abandoned vehicles

On each spawn pass the server asks a nearby player's client for roadside points. It checks the distance, requires 6 m of clearance, and spawns the vehicle server-side with a random plate. It then rolls random fuel, engine and body health and dirt, plus a chance of flat tyres and broken windows. The vehicle is locked with probability lockedChance. An unclaimed vehicle despawns after despawnAfter seconds with nobody within despawnDistance.

Claiming

To claim a vehicle you must:

  • sit in the driver seat, inside the radius of a garage with claim = true
  • use a vehicle nobody owns yet
  • have hotwired it, or have key access to it
  • own fewer than maxPerCharacter vehicles
  • pay claimCost cash

The plate is kept if it's free, otherwise a new one is issued (ABC 123 format). You receive a vehicle_key item, and the vehicle's trunk and glovebox inventories (trunk:<plate> and glovebox:<plate>) become persistent. With groupClaims on, group leaders and ranks with the invite permission can register vehicles to their group instead.

Persistence

Owned vehicles that are out spawn when a player comes within 350 m. They despawn, with the position saved, after 45 s with nobody within 450 m and nobody inside. Positions, fuel and damage are batch-saved every 30 s. Appearance is saved when the driver gets out.

A vehicle whose engine health drops to -3900 or lower is marked wrecked.

Retrieving

Where the vehicle isCost
Stored at this garageFree
Stored at another garagerecallCost (free if that garage no longer exists)
Out in the worldrecallCost
WreckedrecoverCost, returned with engine 250, body 400 and at least 10 fuel

Fuel

The driving client burns fuel based on revs, vehicle class and Config.fuel.usage. To refuel, target any vehicle while carrying a jerrycan (8 s). A can's fill level is stored in its metadata.durability (0–100). Fuel pump props can fill cans for pricePerCan cash (6 s), but whether a pump works is rolled per pump on every restart (25% by default).

Repair

Target a vehicle's engine or bonnet with a repair_kit. The repair skill bonus cuts the duration by up to 50%, and the engine and body health restored are multiplied by 1 + repair bonus. Each repair awards mechanics XP. See en-skills.

Configuration

config/shared.lua

KeyDefaultWhat it does
Config.garages4 garages: motel, sapcounsel, sandy, paletoEach has id, label, coords (vec3), radius, claim (can unowned vehicles be registered here) and spawns (vec4 list, the first clear one is used)
Config.dealerslarrys (Larry's Salvage)Each has id, label, coords, radius, spawn (vec4 delivery spot) and stock of { model, label, price, type }
Config.ownership.maxPerCharacter5Most vehicles one character can own
Config.ownership.claimCost250Cash to register a scavenged vehicle (0 = free)
Config.ownership.groupClaimstrueLet group leaders and ranks with invite permission register vehicles to their group
Config.ownership.recoverCost750Fee to recover a wreck
Config.ownership.recallCost250Fee to recall a vehicle that is out, or move it between garages
Config.fuel.enabledtrueTurns fuel burn on or off
Config.fuel.usage1.6Percent of the tank burned per minute at full revs
Config.fuel.classMultipliertable by class 0–21Per-class burn rate; cycles (13) use 0.0, motorcycles (8) 0.6, helicopters and planes (15, 16) 2.0
Config.fuel.jerrycanAmount35Fuel a full can adds
Config.fuel.jerrycanItem'jerrycan'Jerry can item name
Config.fuel.pumpsenabled = true, workingChance = 0.25, pricePerCan = 120, models = {...}Pump props that can fill cans
Config.repairitem = 'repair_kit', duration = 12000, engine = 450, body = 300, xp = { mechanics = 20 }Repair kit behaviour
Config.keys.use'E'Interact key

config/server.lua

KeyDefaultWhat it does
ServerConfig.persistenceinterval = 5000, streamDistance = 350.0, despawnDistance = 450.0, despawnAfter = 45, saveInterval = 30000How owned vehicles stream in and out, and how often they save
ServerConfig.world.enabledtrueSpawn abandoned vehicles
ServerConfig.world.maxTotal45Most abandoned vehicles in the world
ServerConfig.world.perPlayer4Most abandoned vehicles within 300 m of a player
ServerConfig.world.spawnDistance{ min = 110, max = 240 }How far from players they appear
ServerConfig.world.despawnDistance / despawnAfter400 / 150Remove after this many seconds with nobody within this range
ServerConfig.world.interval15000Milliseconds between spawn passes
ServerConfig.world.lockedChance0.6Chance a spawned vehicle is locked
ServerConfig.world.fuel, engine, body, dirt{2,35}, {150,850}, {350,950}, {8,15}Random ranges for condition
ServerConfig.world.flatTyreChance / brokenWindowChance0.15 / 0.2Chance of flat tyres and broken windows
ServerConfig.world.poolslistModels that can spawn, as { model, type, weight }

Valid vehicle types are automobile, bike, boat, heli and plane.

Adding garages, dealers and scavenged models

lua
-- config/shared.lua
Config.garages[#Config.garages + 1] = {
    id = 'grapeseed', label = 'Grapeseed Barn',
    coords = vec3(2447.1, 4971.2, 46.8), radius = 20.0, claim = true,
    spawns = { vec4(2440.3, 4975.0, 46.2, 45.0), vec4(2436.8, 4970.9, 46.2, 45.0) },
}

-- inside Config.dealers[1].stock
{ model = 'sandking', label = 'Vapid Sandking', price = 6800, type = 'automobile' },

-- config/server.lua, inside ServerConfig.world.pools
{ model = 'dune', type = 'automobile', weight = 2 },
Tip

You can also place garages and dealers in game with en-admin. A placed entry with the same id as a config entry replaces it, and deleting the placement brings the config version back.

Exports

All exports are server-side. There are no client exports.

ExportArgumentsReturns
GetVehicleByPlateplate{ id, plate, citizenid, group_id, model, label, state, garage, fuel, engine, body } or nil. The plate is normalised through en-vehiclekeys
IsOwnedVehicleentityboolean
GetOwnedVehiclessourceVehicles the player manages as owner or group member: { id, plate, label, state, where, fuel, engine, body, group, cost, action }[]
GiveVehiclesource, model, label?, garageId?, vehicleType?New record id or nil. Stored in garageId, else the first config garage that still exists, else any garage. Also gives a key
GetFuelentityNumber 0–100 or nil
SetFuelentity, fuelSets the fuel state bag, clamped to 0–100
BuilderListkindFor 'garage' or 'dealer': { id, label, subtitle, coords, placed, inConfig }[], sorted by label
BuilderGetkind, idEditable data for the entry
BuilderValidatekind, id, dataok, err
BuilderSchemakindForm fields for en-admin
BuilderTemplatekindDefaults for a new garage or dealer

The Builder* exports are used by en-admin's in-game builder; see Content registry.

Commands

All commands are restricted to group.admin.

CommandWhat it does
/givevehicle <target> <model> [label...]Creates an owned vehicle stored in a garage
/deletevehicle <plate...>Permanently deletes an owned vehicle
/clearworldvehiclesRemoves every unclaimed world vehicle

Events

Server-local events, fired with TriggerEvent:

EventPayload
en-garages:server:claimedsource, vehicleId, isGroup
en-garages:server:storedsource, vehicleId, garageId
en-garages:server:retrievedsource, vehicleId, garageId, action (retrieve, transfer, recall or recover)
en-garages:server:boughtsource, vehicleId, dealerId
en-garages:server:wreckedvehicleId

The client event en-garages:client:repaired (netId, engine, body) is sent to the entity owner after a repair.

State bags

Entity state bags on vehicles:

KeyMeaning
ownedtrue on owned vehicles
vehicleIdDatabase id of an owned vehicle
groupIdOwning group id, for group vehicles
modelModel name of an owned vehicle
worldtrue on an unclaimed abandoned vehicle; cleared when claimed
fuel0–100. The driving client publishes it when it changes by 0.5 or more, or every 15 s
propsAppearance and damage to apply; the owning client applies it, then clears the bag
hotwiredSet by en-vehiclekeys; claiming needs this or key access

Database

encore_vehicles: id (primary key), plate (unique, VARCHAR 12), citizenid, group_id, model, type, label, props (JSON), fuel, engine, body, state (stored, out or wrecked), garage, position (JSON {x,y,z,heading}), created_at, updated_at.

Examples

Give a vehicle as a quest or event reward:

lua
-- server
local id = exports['en-garages']:GiveVehicle(source, 'bodhi2', 'Canis Bodhi', 'sandy', 'automobile')

if not id then
    print(('Could not give a vehicle to %s'):format(source))
end

Top up the fuel of whatever a player is driving:

lua
-- server
local vehicle = GetVehiclePedIsIn(GetPlayerPed(source), false)

if vehicle ~= 0 and exports['en-garages']:IsOwnedVehicle(vehicle) then
    local fuel = exports['en-garages']:GetFuel(vehicle) or 0
    exports['en-garages']:SetFuel(vehicle, fuel + 25)
end

Log every claim:

lua
AddEventHandler('en-garages:server:claimed', function(source, vehicleId, isGroup)
    print(('%s claimed vehicle %d%s'):format(GetPlayerName(source), vehicleId, isGroup and ' for their group' or ''))
end)