Developer

6 min readUpdated Today

Overview

Integration happens through exports and vehicle state bags. Fuel is server-authoritative: clients can read it anywhere, but writes go through the server exports. The open/ folder is not escrowed and is reserved for customer code; it ships with the HRS Base Building mapping in open/shared.lua.

Vehicle State Bags

Every vehicle carries its fuel state in replicated state bags. Read them from any resource, client or server:

lua
1local state = Entity(vehicle).state
2
3state.fuel              -- number, 0-100 percent
4state.fuelType          -- 'fuel' | 'diesel' | 'kerosene' | ...
5state.fuelTypeConverted -- true after a conversion kit or SetFuelType
6state.tankSize          -- tank capacity in liters (with DynamicTankSize)
7state.tankBonus         -- extra liters from installed tank upgrades
8state.engineOn          -- mirrored engine state, used by the idle drain

fuel is a percentage. Convert with tankSize or the client exports FuelPercentToLiters / FuelLitersToPercent. While driving, the driver client updates the level every tick and replicates it every Config.Consumption.replicationInterval ticks.

lua
1GlobalState['ml_fuel:mode']     -- 'advanced' | 'simple'
2GlobalState['ml_fuel:stations'] -- active scavenge stations: { { idx, coords, charges, maxCharges }, ... }

Server Exports

Vehicle Fuel

  • GetFuel(netId): fuel level 0-100, or nil for an invalid vehicle
  • SetFuel(netId, fuel): set the level (clamped 0-100). Returns boolean
  • GetFuelType(netId): current fuel type string
  • SetFuelType(netId, fuelType): set the type (must exist in Config.FuelTypes) and mark the vehicle as converted. Returns boolean
  • GetVehicleFuel(netId) / SetVehicleFuel(netId, fuel) / GetVehicleFuelType(netId) / SetVehicleFuelType(netId, fuelType): aliases of the four above
  • GetFuelTypes(): the Config.FuelTypes table (key -> { label, color, consumptionRate })

Conversions and Upgrades

  • GetVehicleConversion(plate): converted fuel type for a plate, or nil
  • SetVehicleConversion(plate, fuelType, license?): write a conversion to the database
  • DeleteVehicleConversion(plate): remove a conversion
  • GetVehicleTankBonus(plate): total bonus liters installed on a plate
  • GetVehicleUpgrades(plate): { tankBonus, upgrades } for a plate
  • CanInstallUpgrade(plate, upgradeItem): false when the upgrade hit its maxStacks

Scavenge Stations

  • GetActiveStations(): array of { idx, coords, charges, maxCharges }
  • IsStationActiveAtCoords(coords): true when the coordinate falls inside an active station zone
  • IsStationActive(stationIdx) / GetStationCharges(stationIdx): state of one station by index
  • RefreshActiveStations(): force a station rotation now
  • GetSiphonItemName(): the configured siphon tool item name

Database

  • WaitForDatabase() / IsDatabaseReady(): block until / check whether the table init finished

Client Exports

Reading Fuel

  • GetVehicleFuel(vehicle): fuel level 0-100 (entity handle, not netId)
  • GetVehicleFuelType(vehicle): current fuel type
  • GetFuel(vehicle): alias of GetVehicleFuel
  • IsFuelCompatible(vehicle, fuelType): true when the vehicle runs the given type
  • GetVehicleTankSize(vehicle): tank capacity in liters (includes upgrade bonus)
  • FuelPercentToLiters(vehicle, percent) / FuelLitersToPercent(vehicle, liters): unit conversion against the vehicle tank
  • GetVehicleHandlingData(vehicle): cached handling values used by the consumption model
No client-side writes

There are no SetFuel exports on the client. Modify fuel from the server exports only.

UI and Menus

  • OpenRefuelMenu(vehicle): open the Transfer UI against a vehicle with the best compatible container
  • OpenJerryCanMenu(slot, itemName): open the Transfer UI for a specific jerry can slot
  • OpenTransferUI(jerrycanData, vehicleData, mode): low-level open. mode = 'refuel' | 'siphon'
  • OpenTransferUIAdvanced(jerrycanData, vehicleData, options): open with mode switching (options.canRefuel, options.canSiphon)
  • OpenGeneratorTransfer(propId, genData): open the Transfer UI against an HRS generator. genData = { fuel, fuelTank, fuelItem }
  • CloseTransferUI() / IsTransferUIOpen()
  • ShowFuelUI() / HideFuelUI() / UpdateFuelDisplay(data): control the gauge HUD

Scavenging and State

  • StartScavenging(pumpCoords, pumpEntity?): start the pump scavenge flow
  • TryScavengeNearbyWreck() / GetNearbyWreck() / SearchNearestWreck(): wreck scavenge helpers
  • IsPumpAtActiveStation(coords) / FindClosestPump(coords, radius)
  • IsInNoLootZone(): true when the player stands inside a no-loot zone
  • IsFueling() / SetFueling(value) / GetLastVehicle()
  • GetFuelCapPosition(vehicle) / IsNearFuelCap(vehicle, maxDistance?)
  • GetJerryCanItems() / HasRefuelItem(): configured jerry can names and whether the player carries any refuel container

Jerry Can Metadata Contract

Jerry cans are the exchange format between ML Fuel and other resources. A jerry can is a normal inventory item with four metadata keys:

lua
1metadata = {
2    fuelType      = 'diesel',   -- raw fuel type key, nil when empty
3    fuelTypeLabel = 'Diesel',   -- localized label for the tooltip
4    fuelAmount    = 18.5,       -- current liters, rounded to 2 decimals
5    maxCapacity   = 25,         -- capacity from Config.JerryCans[item].capacity
6}
  • The item is refillable. A consumer empties the metadata, it never removes the item
  • An empty can has fuelAmount = 0 and fuelType = nil, and can then be filled with any type in its acceptsFuel list
  • Fuel types never mix: a can holding diesel refuses gasoline until emptied
  • Always re-read the slot server-side before consuming, never trust amounts sent by a client

The read-and-consume pattern for accepting ML Fuel jerry cans in another resource:

your_script/server.lua
1local ACCEPTED = { fuel = true, diesel = true }
2
3function DrawFuelFromJerryCan(src, slot, litersWanted)
4    local item = Bridge.GetSlot(src, slot)
5    if not item then return 0 end
6
7    local meta = item.metadata or {}
8    local amount = meta.fuelAmount or 0
9    local fuelType = meta.fuelType
10
11    if amount <= 0 or not ACCEPTED[fuelType] then return 0 end
12
13    local drawn = math.min(litersWanted, amount)
14    local left = math.floor((amount - drawn) * 100 + 0.5) / 100
15
16    Bridge.SetItemMeta(src, slot, {
17        fuelAmount    = left,
18        fuelType      = left > 0 and fuelType or nil,
19        fuelTypeLabel = left > 0 and meta.fuelTypeLabel or nil,
20        maxCapacity   = meta.maxCapacity,
21    })
22
23    return drawn
24end

ml_recycler (machine fuel) and hrs_base_building generators consume jerry cans through this exact contract.

HRS Base Building

The generator integration is built in. When a player opens "Manage Fuel" on an HRS generator, ML Fuel opens its Transfer UI instead of the default one, finds a jerry can holding the matching fuel type, and pours with hold-to-pour. The fuelItem value set on the generator is mapped to an ML Fuel type through Config.HRS.fuelTypeMapping in open/shared.lua.

Two open-file edits are required on the HRS side: a pair of exports in server/main_unlocked.lua and a UI intercept in client/main_unlocked.lua. Copy them from open/HRS_INTEGRATION.md, which ships with the resource.

Commands

  • /mlfuel setfuel|settype|info|resetcooldowns: admin utilities on the current or last vehicle; resetcooldowns forces a station rotation
  • /showpumps: admin. Temporary blips on the active scavenge stations. Name and blip settings in Config.PumpScavenging.staffCommand
  • /mlwreckreset: admin or server console. Clears all wreck cooldowns
  • /mlfueldebug: unified debug menu. Only registered when Config.Debug = true
  • ml_fuel_interact: key mapping (default E) for pump/wreck interaction in textui mode
  • ml_fuel_stop_charge: key mapping (default BACKSPACE) to stop an EV charging session

Examples

your_script/client.lua
1if cache.vehicle then
2    local fuel = exports.ml_fuel:GetVehicleFuel(cache.vehicle)
3    local fuelType = exports.ml_fuel:GetVehicleFuelType(cache.vehicle)
4    local liters = exports.ml_fuel:FuelPercentToLiters(cache.vehicle, fuel)
5    print(('%.1f%% (%.1fL) of %s'):format(fuel, liters, fuelType))
6end
your_script/server.lua
1local netId = NetworkGetNetworkIdFromEntity(vehicle)
2exports.ml_fuel:SetFuel(netId, 100.0)
3exports.ml_fuel:SetFuelType(netId, 'diesel')
your_script/client.lua
1AddStateBagChangeHandler('fuel', nil, function(bagName, _, value)
2    local entity = GetEntityFromStateBagName(bagName)
3    if entity == 0 or entity ~= cache.vehicle then return end
4    if value and value < 10 then
5        -- low fuel warning
6    end
7end)