ItemFuelAsset — Fuel Items
Managing fuel logistics for vehicles, generators, and oil pumps in Unturned requires understanding how fuel cans store a ushort fuel amount in 2-byte state, transfer fuel bidirectionally, and interact with the UseableFuel runtime class for refueling operations. ItemFuelAsset defines fuel cans — portable containers that hold liquid fuel for vehicles and generators. It extends ItemAsset directly and carries a 2-byte state encoding the current fuel amount.
Source code location: Unturned/Bundles/ItemFuelAsset.cs
Inheritance Chain
ItemAsset
→ ItemFuelAssetClass Definition
csharp
public class ItemFuelAsset : ItemAsset
{
protected AudioClip _use;
public AudioClip use => _use;
protected ushort _fuel;
public ushort fuel => _fuel;
public bool shouldDeleteAfterFillingTarget { get; protected set; }
private bool shouldAlwaysSpawnFull;
private byte[] fuelState;
}Core Fields
| Field | Type | .dat Key | Default | Description |
|---|---|---|---|---|
_fuel | ushort | Fuel | — | Total fuel capacity (max 65,535 units) |
shouldDeleteAfterFillingTarget | bool | Delete_After_Filling_Target | false | Whether the can is consumed after transferring fuel |
shouldAlwaysSpawnFull | bool | Always_Spawn_Full | false | Whether world-spawned cans start full |
Audio
| Field | Type | Source | Description |
|---|---|---|---|
_use | AudioClip | Bundle "Use" | Sound played during fuel transfer |
State Management
The fuel can uses a 2-byte state encoding the current fuel amount as a ushort:
csharp
private byte[] fuelState; // Cached full-state bytes
public override byte[] getState(EItemOrigin origin)
{
byte[] state = new byte[2];
if (origin == EItemOrigin.ADMIN || shouldAlwaysSpawnFull)
{
state[0] = fuelState[0];
state[1] = fuelState[1];
}
return state;
}| Condition | Initial State |
|---|---|
EItemOrigin.ADMIN | Full (all _fuel bytes) |
shouldAlwaysSpawnFull | Full |
| Otherwise | 0 (empty) |
The fuelState byte array is cached during PopulateAsset:
csharp
fuelState = System.BitConverter.GetBytes(fuel);The state is updated during fuel transfer by UseableFuel. The 2-byte state allows up to 65,535 fuel units, matching ushort.MaxValue.
PopulateAsset
csharp
public override void PopulateAsset(in PopulateAssetParameters p)
{
base.PopulateAsset(in p);
_use = p.bundle.load<AudioClip>("Use");
_fuel = p.data.ParseUInt16("Fuel");
fuelState = BitConverter.GetBytes(fuel);
shouldDeleteAfterFillingTarget = p.data.ParseBool("Delete_After_Filling_Target");
shouldAlwaysSpawnFull = p.data.ParseBool("Always_Spawn_Full");
}BuildDescription
The inventory description shows current fuel as fraction and percentage:
csharp
public override void BuildDescription(ItemDescriptionBuilder builder, Item itemInstance)
{
base.BuildDescription(builder, itemInstance);
if (itemInstance != null)
{
ushort stateFuel = BitConverter.ToUInt16(itemInstance.state, 0);
float percentage = (float)stateFuel / (float)fuel;
builder.Append(localization.format("ItemDescription_FuelAmountWithCapacity",
stateFuel, fuel, percentage.ToString("P")), DescSort_Important);
}
}The display format shows "20 / 40 (50.00%)" — current fuel, max capacity, and percentage. The percentage uses the invariant culture's "P" format (percent).
UseableFuel — Fuel Transfer Mechanics
The UseableFuel class implements two transfer modes:
Fill Target from Can (Forward Transfer)
- Player faces a vehicle or generator with a non-full fuel tank.
- A raycast detects the
InteractableVehicleorInteractableGenerator. - Fuel is transferred per-tick:
transferRate = canFuel / totalTicks. - Can state bytes decrease; target tank state increases.
- Transfer completes when can is empty or target is full.
- If
shouldDeleteAfterFillingTarget, the empty can is removed from inventory.
csharp
// Per-tick fuel transfer (32 ticks/sec)
float transferFraction = 1.0f / totalTicks;
float fuelToTransfer = Mathf.Min(canFuel * transferFraction, vehicleNeeds);
canFuel -= fuelToTransfer;
vehicleFuel += fuelToTransfer;Fill Can from Target (Reverse Transfer)
- Player faces a vehicle or generator with fuel in its tank and holds an empty fuel can.
- Fuel transfers from the source to the can.
- The can's state bytes increase.
- Transfer completes when can is full or source is empty.
shouldDeleteAfterFillingTargetis ignored for reverse transfers.
Generator Interaction
The same UseableFuel class handles generator fuel:
- Generator fuel capacity is
ItemGeneratorAsset.capacity(ushort). - The fuel can's state is updated identically to vehicle transfers.
- Forward transfer only (can → generator). No reverse transfer from generators.
- Power output begins when fuel level exceeds zero.
Vehicle Interaction
For vehicles:
- Target capacity is
VehicleAsset._fuel(ushort). - Both forward and reverse transfers are supported.
- Vehicle fuel state is part of the vehicle's save data, not the item state.
Transfer Interruption
If the player moves away mid-transfer (exceeds interaction range):
- The partial transfer is preserved.
- Remaining fuel stays in the can.
- The target receives whatever was transferred up to the interruption point.
Comparison: Generator vs Vehicle Fuel Transfer
| Property | Generator | Vehicle |
|---|---|---|
| Target component | InteractableGenerator | InteractableVehicle |
| Capacity source | ItemGeneratorAsset.capacity | VehicleAsset._fuel |
| Transfer direction | Fill only | Fill and drain |
| Power interaction | Direct to wired items | N/A (fuel used for movement) |
| State storage | 3 bytes (powered + fuel) | Vehicle save data |
Common Issues
- State desync — The 2-byte fuel state is replicated from server to client. If the fuel amount is set beyond
_fuelcapacity (via admin commands or mods), the description displays over 100% and extra fuel may not transfer correctly. - Empty admin cans —
EItemOrigin.ADMINspawns a full can, but if theFuelkey is 0 in the.dat, the can spawns with 0/0 fuel. The percentage display shows NaN or 0%. - Fuel state overflow — The state uses
ushort(max 65,535). AFuelvalue of 0 combined with a reverse transfer could overflow the state bytes, though in practiceUseableFuelclamps transfers to capacity. - Delete after filling vs reverse —
shouldDeleteAfterFillingTargetonly triggers on forward transfer (can → target). Reverse transfer never deletes the can regardless of this flag. - Missing Use audio — If the bundle lacks a "Use" AudioClip,
p.bundle.load<AudioClip>("Use")returns null and no sound plays during transfer. There is no fallback audio key in the.dat. - Infinite fuel exploit — A fuel can with
Fuel=0that is full (from admin spawn) provides 0 capacity but the state shows full. This is technically 0/0 = 100% but can't transfer any fuel.
Worked Code Example: Fuel Transfer Tracker
csharp
using SDG.Unturned;
public static class FuelTransferTracker
{
/// <summary>
/// Calculates how many fuel cans are needed to fill a vehicle tank,
/// accounting for partial cans and capacity limits.
/// </summary>
public static int CansRequiredForVehicle(VehicleAsset vehicleAsset, ushort canFuelCapacity)
{
if (canFuelCapacity == 0) return int.MaxValue;
return Mathf.CeilToInt((float)vehicleAsset.fuel / canFuelCapacity);
}
/// <summary>
/// Estimates the total fuel needed to run a generator for N hours,
/// translating burn rate into can equivalents.
/// </summary>
public static int CansRequiredForGeneratorRuntime(ItemGeneratorAsset genAsset, ushort canFuelCapacity, float hours)
{
float totalFuelNeeded = genAsset.burn * (3600f * hours);
if (canFuelCapacity == 0) return int.MaxValue;
return Mathf.CeilToInt(totalFuelNeeded / canFuelCapacity);
}
}Mermaid Diagram: Fuel Transfer Flow
Failure Modes and Common Mistakes
State size mismatch on legacy saves — Older saves may have 1-byte fuel states. The SDK reads ushort (2 bytes). A 1-byte legacy state causes a partial read and an incorrect fuel value for the second byte.
Can deletion during PvP —
shouldDeleteAfterFillingTargetdestroys the can when the target reaches full. During PvP, a player refueling a vehicle with a 5000-capacity can may lose the entire can on the last few units of transfer.
How This Differs from SDG Docs
- SDG docs describe fuel cans as "infinite refill." Some community guides claim you can refuel forever. In the SDK,
Fuelis a finite ushort — once transferred, the can is empty or deleted. - SDG docs reference fuel quality or contamination. Early documentation referenced clean vs. contaminated fuel states. In the SDK, contamination is a separate mechanic in
ItemFilterAsset, not part of the fuel can. Clean/contaminated is a binary flag managed by the filter item, not the can itself.
Performance Considerations
Fuel transfer is a single ushort arithmetic operation per interaction. No per-frame cost. State sync via RPC is 2 bytes per transfer. Memory per fuel can: 2 bytes of state + Item instance (~20 bytes).
Deeper FAQ
Q: Can I modify fuel transfer rate?
Not through vanilla. Transfer is instantaneous (full amount in one interaction). A Harmony patch on UseableFuel is needed for incremental transfer.
Q: What happens if I use a fuel can on a destroyed vehicle?
The UseableFuel validates the vehicle exists. A destroyed vehicle's transform is null — the interaction silently fails. The can is not consumed.
Q: Can fuel cans be placed as barricades?
No. ItemFuelAsset extends ItemAsset, not ItemBarricadeAsset. Fuel cans are inventory-only items. For placed fuel storage, use ItemTankAsset or ItemGeneratorAsset.
Cross-References
- ItemGeneratorAsset — Generator Asset Definition — Generator fuel consumption that drives fuel can demand.
- ItemOilPumpAsset — Oil Pump Items — Oil pumps refill fuel cans via extraction.
- ItemFilterAsset — Fuel Filter Items — Fuel contamination cleaning using filters.
- ItemTankAsset — Fuel Tank Storage — Static fuel storage barricades.
