Skip to content

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
  → ItemFuelAsset

Class 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

FieldType.dat KeyDefaultDescription
_fuelushortFuelTotal fuel capacity (max 65,535 units)
shouldDeleteAfterFillingTargetboolDelete_After_Filling_TargetfalseWhether the can is consumed after transferring fuel
shouldAlwaysSpawnFullboolAlways_Spawn_FullfalseWhether world-spawned cans start full

Audio

FieldTypeSourceDescription
_useAudioClipBundle "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;
}
ConditionInitial State
EItemOrigin.ADMINFull (all _fuel bytes)
shouldAlwaysSpawnFullFull
Otherwise0 (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)

  1. Player faces a vehicle or generator with a non-full fuel tank.
  2. A raycast detects the InteractableVehicle or InteractableGenerator.
  3. Fuel is transferred per-tick: transferRate = canFuel / totalTicks.
  4. Can state bytes decrease; target tank state increases.
  5. Transfer completes when can is empty or target is full.
  6. 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)

  1. Player faces a vehicle or generator with fuel in its tank and holds an empty fuel can.
  2. Fuel transfers from the source to the can.
  3. The can's state bytes increase.
  4. Transfer completes when can is full or source is empty.
  5. shouldDeleteAfterFillingTarget is 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

PropertyGeneratorVehicle
Target componentInteractableGeneratorInteractableVehicle
Capacity sourceItemGeneratorAsset.capacityVehicleAsset._fuel
Transfer directionFill onlyFill and drain
Power interactionDirect to wired itemsN/A (fuel used for movement)
State storage3 bytes (powered + fuel)Vehicle save data

Common Issues

  1. State desync — The 2-byte fuel state is replicated from server to client. If the fuel amount is set beyond _fuel capacity (via admin commands or mods), the description displays over 100% and extra fuel may not transfer correctly.
  2. Empty admin cansEItemOrigin.ADMIN spawns a full can, but if the Fuel key is 0 in the .dat, the can spawns with 0/0 fuel. The percentage display shows NaN or 0%.
  3. Fuel state overflow — The state uses ushort (max 65,535). A Fuel value of 0 combined with a reverse transfer could overflow the state bytes, though in practice UseableFuel clamps transfers to capacity.
  4. Delete after filling vs reverseshouldDeleteAfterFillingTarget only triggers on forward transfer (can → target). Reverse transfer never deletes the can regardless of this flag.
  5. 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.
  6. Infinite fuel exploit — A fuel can with Fuel=0 that 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

  1. 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.

  2. Can deletion during PvPshouldDeleteAfterFillingTarget destroys 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, Fuel is 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

Document history