Developer
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:
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 drainfuel 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.
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 vehicleSetFuel(netId, fuel): set the level (clamped 0-100). Returns booleanGetFuelType(netId): current fuel type stringSetFuelType(netId, fuelType): set the type (must exist inConfig.FuelTypes) and mark the vehicle as converted. Returns booleanGetVehicleFuel(netId)/SetVehicleFuel(netId, fuel)/GetVehicleFuelType(netId)/SetVehicleFuelType(netId, fuelType): aliases of the four aboveGetFuelTypes(): theConfig.FuelTypestable (key -> { label, color, consumptionRate })
Conversions and Upgrades
GetVehicleConversion(plate): converted fuel type for a plate, or nilSetVehicleConversion(plate, fuelType, license?): write a conversion to the databaseDeleteVehicleConversion(plate): remove a conversionGetVehicleTankBonus(plate): total bonus liters installed on a plateGetVehicleUpgrades(plate): { tankBonus, upgrades } for a plateCanInstallUpgrade(plate, upgradeItem): false when the upgrade hit itsmaxStacks
Scavenge Stations
GetActiveStations(): array of { idx, coords, charges, maxCharges }IsStationActiveAtCoords(coords): true when the coordinate falls inside an active station zoneIsStationActive(stationIdx)/GetStationCharges(stationIdx): state of one station by indexRefreshActiveStations(): force a station rotation nowGetSiphonItemName(): 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 typeGetFuel(vehicle): alias ofGetVehicleFuelIsFuelCompatible(vehicle, fuelType): true when the vehicle runs the given typeGetVehicleTankSize(vehicle): tank capacity in liters (includes upgrade bonus)FuelPercentToLiters(vehicle, percent)/FuelLitersToPercent(vehicle, liters): unit conversion against the vehicle tankGetVehicleHandlingData(vehicle): cached handling values used by the consumption model
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 containerOpenJerryCanMenu(slot, itemName): open the Transfer UI for a specific jerry can slotOpenTransferUI(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 flowTryScavengeNearbyWreck()/GetNearbyWreck()/SearchNearestWreck(): wreck scavenge helpersIsPumpAtActiveStation(coords)/FindClosestPump(coords, radius)IsInNoLootZone(): true when the player stands inside a no-loot zoneIsFueling()/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:
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 = 0andfuelType = nil, and can then be filled with any type in itsacceptsFuellist - 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:
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
24endml_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;resetcooldownsforces a station rotation/showpumps: admin. Temporary blips on the active scavenge stations. Name and blip settings inConfig.PumpScavenging.staffCommand/mlwreckreset: admin or server console. Clears all wreck cooldowns/mlfueldebug: unified debug menu. Only registered whenConfig.Debug = trueml_fuel_interact: key mapping (default E) for pump/wreck interaction in textui modeml_fuel_stop_charge: key mapping (default BACKSPACE) to stop an EV charging session
Examples
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))
6end1local netId = NetworkGetNetworkIdFromEntity(vehicle)
2exports.ml_fuel:SetFuel(netId, 100.0)
3exports.ml_fuel:SetFuelType(netId, 'diesel')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)