Skip to content

Steam Provider and Authentication System

Provider is the central singleton managing Unturned's connection to Steam — it handles the Steamworks client and server initialization, authentication ticket exchange, server browser advertisement, workshop synchronization, player connection lifecycle, and network channel management. It is a Unity MonoBehaviour that lives across scene loads and coordinates the entire networking stack. It is a partial class split across multiple files to organize the large amount of static state.

This article covers the Provider singleton architecture, the Steam connection lifecycle (server init, client join, authentication, workshop sync, spawn), the server browser listing system, the player add/remove flow, the rate limiting system, and the third-party anti-cheat integration points.

Source code location: Unturned/Provider/Provider.cs, Unturned/Provider/NetTransportFactory.cs, SteamworksProvider/

Provider Singleton Architecture

Provider is a partial class MonoBehaviour with static fields for every networking and game state property. Key static state includes:

csharp
public partial class Provider : MonoBehaviour
{
    public static readonly string STEAM_IC = "Steam";
    public static readonly string STEAM_DC = "<color=#2784c6>Steam</color>";

    public static readonly AppId_t APP_ID = new AppId_t(304930);  // 407660 experimental
    public static readonly AppId_t PRO_ID = new AppId_t(306460);
    public static string APP_VERSION { get; protected set; }
    public static uint APP_VERSION_PACKED { get; protected set; }
    public static readonly string APP_NAME = "Unturned";
    public static readonly string APP_AUTHOR = "Nelson Sexton";

    public static readonly int CLIENT_TIMEOUT = 30;
    internal static readonly float PING_REQUEST_INTERVAL = 1f;
    // ...
}

Key state:

  • APP_ID304930 (production) or 407660 (experimental). PRO_ID is 306460.
  • APP_VERSION and APP_VERSION_PACKED — the current game version as a string and packed 32-bit integer for network replication.
  • CLIENT_TIMEOUT — 30 seconds.
  • _server (CSteamID) — the current server's Steam ID.
  • _client (CSteamID) — the local client's Steam ID.
  • _user (CSteamID) — the logged-in Steam user's ID.
  • _isServer, _isClient, _isConnected — connection state booleans.
  • _clientsList<SteamPlayer> of fully connected players.
  • pendingList<SteamPending> of players in the connection queue.
  • _transportConnectionToPlayerMap — maps ITransportConnection to SteamPlayer for fast lookup.
  • _transportConnectionToPendingPlayerMap — same for pending players.
  • modeConfigData — the current game mode's config data, including event timings for arena, airdrop, and compactor.

The Provider also manages:

  • Screenshot capture pipeline (Steam overlay integration, supersampling support).
  • Rich presence updates (Steam SetRichPresence, community service status updates).
  • Localization root path and language management.
  • Workshop file ID registration and server-workshop file synchronization.
  • Anti-cheat initialization (VAC and third-party via partial methods).

Localization System

csharp
private static string privateLanguage;
public static string language
{
    get => privateLanguage;
    private set
    {
        privateLanguage = value;
        languageIsEnglish = value == "English";
    }
}
public static string localizationRoot { get; private set; }
public static Local localization;
public static List<string> streamerNames { get; private set; }

localizationRoot points to the directory containing language-specific .dat files. streamerNames is loaded from a configuration file and used by the chat filter system.

Rich Presence

updateRichPresence() updates Steam rich presence based on the current game state:

csharp
private static void updateSteamRichPresence()
{
    if (OptionsSettings.ShouldHideRichPresence)
    {
        SteamFriends.ClearRichPresence();
        return;
    }
    if (Level.info != null)
    {
        if (Level.isEditor)
            SteamFriends.SetRichPresence("steam_display", "#Status_EditingLevel");
        else if (isConnected && !isServer && server.m_SteamID > 0)
            SteamFriends.SetRichPresence("steam_display", "#Status_PlayingMultiplayer");
        else
            SteamFriends.SetRichPresence("steam_display", "#Status_PlayingSingleplayer");
        SteamFriends.SetRichPresence("level_name", Level.info.getLocalizedName());
    }
    else
    {
        if (Lobbies.inLobby)
            SteamFriends.SetRichPresence("steam_display", "#Status_WaitingInLobby");
        else
            SteamFriends.SetRichPresence("steam_display", "#Status_AtMainMenu");
    }
}

The rich presence tokens %level_name% are substituted by Steam from the level_name key. The community service also receives a formatted status string.

Screenshot Capture

Provider implements a coroutine-based screenshot pipeline:

csharp
private IEnumerator CaptureScreenshot()
{
    bool useSupersampling = OptionsSettings.enableScreenshotSupersampling;
    int maxSizeMultiplier = useSupersampling ? 4 : 16;
    int sizeMultiplier = Mathf.Clamp(OptionsSettings.screenshotSizeMultiplier, 1, maxSizeMultiplier);

    int finalWidth = Screen.width * sizeMultiplier;
    int finalHeight = Screen.height * sizeMultiplier;
    if (finalWidth > SystemInfo.maxTextureSize || finalHeight > SystemInfo.maxTextureSize)
    {
        UnturnedLog.warn("Screenshot exceeds max supported texture size");
        yield break;
    }

    if (useSupersampling)
    {
        // Supersample at 2x resolution, then bilinear downsample
        int superSize = sizeMultiplier * 2;
        Texture2D supersampledTexture = ScreenCapture.CaptureScreenshotAsTexture(superSize);
        RenderTexture downsampleRT = RenderTexture.GetTemporary(finalWidth, finalHeight,
            0, supersampledTexture.graphicsFormat);
        Graphics.Blit(supersampledTexture, downsampleRT, screenshotBlitMaterial);
        // Read back from render texture, encode to PNG, write to disk
    }
    else
    {
        ScreenCapture.CaptureScreenshot(filePath, sizeMultiplier);
    }

    // Tag screenshot with Steam location and player info
    ScreenshotHandle handle = SteamScreenshots.AddScreenshotToLibrary(filePath, null, finalWidth, finalHeight);
    if (Level.info != null) SteamScreenshots.SetLocation(handle, Level.info.getLocalizedName());
    foreach (SteamPlayer client in clients)
    {
        // Tag visible players within 64m range
        Vector3 worldPosition = client.player.transform.position + Vector3.up;
        if ((worldPosition - cameraPosition).sqrMagnitude > 64 * 64) continue;
        SteamScreenshots.TagUser(handle, client.playerID.steamID);
    }
}

The screenshot pipeline supports supersampling (captures at 2x target resolution, then bilinear downsamples for quality), and tags both the game location and visible players in the Steam screenshot library.

Server Initialization Flow

  1. Provider.Start() triggers server startup on dedicated servers via SteamGameServer initialization.
  2. SteamGameServer.Init() is called with the app ID (304930), listening port (27015), Game Server Steam API port (27016), and query port (27017).
  3. SteamGameServer.LogOn(gslt) authenticates the server with its Game Server Login Token.
  4. SteamGameServer.SetGameTags() and SteamGameServer.SetGameDescription() advertise server metadata including transport type, map name, game mode, PvP status, and mod counts.
  5. NetTransportFactory.CreateServerTransport() instantiates the transport implementation — default is ServerTransport_SteamNetworkingSockets, overridable via -NetTransport command-line argument.
  6. serverTransport.startListening() opens the transport listener on the configured port.
  7. The server enters the game loop, processing incoming connections through receiveTransportConnection().

Client Connection Flow

Connect Parameters

csharp
internal class ServerConnectParameters
{
    public string address;
    public ushort port;
    public string password;
    public CSteamID steamID;    // For Steam ID-based joins
    public ulong lobbyID;      // For lobby joins
}

The client can connect by IP:port, Steam ID (friend join), or lobby ID.

Connection Sequence

  1. connect(ServerConnectParameters parameters) initiates the connection. If connecting by IP, SteamMatchmakingServers.PingInternet() retrieves SteamServerAdvertisement (A2S info).
  2. The client sends an initial transport connection request to the server.
  3. On acceptance, receiveTransportConnection() on the server creates a SteamPending entry and adds it to the pending queue.
  4. The pending player receives ServerMessageHandler_ReadyToConnect — the server asks for their authentication data.
  5. The client responds with EServerMessage.Authenticate, sending their Steam auth ticket, character data, and equipped item IDs.
  6. The server validates the auth ticket via SteamGameServer.BeginAuthSession().
  7. The client waits for workshop file list via ServerMessageHandler_GetWorkshopFiles and ClientMessageHandler_DownloadWorkshopFiles.
  8. Once workshop files are confirmed (either present or downloaded), the client sends EServerMessage.ReadyToConnect.
  9. The server sends EClientMessage.Accepted with the player's assigned NetId block, spawn position, equipment state, and initial player data.
  10. addPlayer() constructs the player's SteamPlayer and Player objects, sets up the player's RPC channel, and broadcasts onServerConnected.

Workshop Synchronization

csharp
private static List<ulong> _serverWorkshopFileIDs = new List<ulong>();

public static void registerServerUsingWorkshopFileId(ulong id)
{
    if (_serverWorkshopFileIDs.Contains(id)) return;
    _serverWorkshopFileIDs.Add(id);
    ServerRequiredWorkshopFile requiredFile = new ServerRequiredWorkshopFile()
    {
        fileId = id,
        timestamp = DateTimeEx.FromUtcUnixTimeSeconds(timestamp)
    };
    serverRequiredWorkshopFiles.Add(requiredFile);
}

The server maintains _serverWorkshopFileIDs — a list of all Workshop file IDs used by mods on the server. When a client connects:

  1. The server compiles serverRequiredWorkshopFiles from registerServerUsingWorkshopFileId() calls.
  2. The server sends the list to the client via ServerMessageHandler_GetWorkshopFiles.
  3. The client compares against locally cached files and downloads any missing via ClientMessageHandler_DownloadWorkshopFiles.
  4. After download, receiveWorkshopResponse() validates the server's workshop response against the advertised data.

Workshop Response Validation

csharp
internal static void receiveWorkshopResponse(CachedWorkshopResponse response)
{
    // Cache server info
    serverName = response.serverName;
    map = response.levelName;
    isPvP = response.isPvP;
    mode = response.gameMode;
    cameraMode = response.cameraMode;
    maxPlayers = response.maxPlayers;
    isVacActive = response.isVACSecure;

    // Validate against advertisement
    if (CurrentServerAdvertisement != null)
    {
        if (!string.Equals(CurrentServerAdvertisement.map, response.levelName, ...))
        {
            _connectionFailureInfo = ESteamConnectionFailureInfo.SERVER_MAP_ADVERTISEMENT_MISMATCH;
            RequestDisconnect("server map advertisement mismatch");
            return;
        }
        if (CurrentServerAdvertisement.maxPlayers != response.maxPlayers)
        {
            _connectionFailureInfo = ESteamConnectionFailureInfo.SERVER_MAXPLAYERS_ADVERTISEMENT_MISMATCH;
            RequestDisconnect();
            return;
        }
        // ... camera mode, PvP, VAC checks
    }

    if (queryIDs.Count < 1)
    {
        launch(); // No workshop items needed
    }
    else
    {
        // Download and verify workshop items
        provider.workshopService.queryServerWorkshopItems(queryIDs, response.ip);
    }
}

The client validates that the server's workshop response matches the advertised data to prevent the server from advertising a different configuration than it actually runs. Workshop file IDs must match the advertised set (cannot add hidden items).

Authentication and Anti-Cheat

Steam Auth

SteamGameServer.BeginAuthSession() validates the client's auth ticket. The server also checks:

  • SteamGameServer.RequestUserGroupStatus() for group membership checks.
  • Ban list via SteamBlacklist checks against Steam ID, IP, and hardware IDs.

VAC (Valve Anti-Cheat)

VAC status is obtained from the server's GameServerItem.m_bSecure field (advertised) and the workshop response isVACSecure field. Note: the VAC advertisement comparison is currently commented out due to m_bSecure variability issues.

Third-Party Anti-Cheat

Defined as partial methods in Provider:

csharp
#if WITH_THIRDPARTYAC
static partial void AddClientToThirdpartyAntiCheat(ITransportConnection clientId, SteamPlayerID playerID, SteamPlayer newClient);
static partial void RemoveClientFromThirdpartyAntiCheat(SteamPlayer clientToRemove);
static partial void ShutdownThirdpartyAntiCheatServer();
static partial void ShutdownThirdpartyAntiCheatClient();
private static partial bool InitThirdpartyAntiCheatServer();
static partial void RunThirdpartyAntiCheatFrame();
private static partial bool CheckThirdpartyAntiCheatWantsRestart();
#endif

The third-party anti-cheat system uses a separate message channel (EClientMessage.ThirdpartyAntiCheat, EServerMessage.ThirdPartyAntiCheat) for encrypted or opaque data exchange. Player IDs are allocated sequentially via AllocThirdpartyAntiCheatPlayerId().

Server Browser Listing

The server advertises itself in the Steam game server browser via:

  • SteamGameServer.SetGameTags() — serializes key-value pairs: transport tag ("sns", "sys", "def"), map name, game mode, PvP status, VAC status, anti-cheat status, Workshop status.
  • SteamGameServer.SetGameDescription() — human-readable description.
  • SteamGameServer.SetMaxPlayerCount() — from server config.

On the client side, SteamServerAdvertisement is populated from gameserveritem_t during a ping response:

csharp
public static SteamServerAdvertisement CurrentServerAdvertisement => _currentServerAdvertisement;

It stores:

  • Server name, map, game description.
  • Ping, player count, max players.
  • Steam ID, IP, port (GetQueryPort(), GetConnectionAddress()).
  • VAC secure flag, third-party anti-cheat flag.
  • Bot count, server version, OS.
  • Is Workshop-enabled flag.
  • steamID for friend-join routing.

Player Connection Lifecycle

Adding a Player

csharp
internal static SteamPlayer addPlayer(ITransportConnection transportConnection, NetId netId,
    SteamPlayerID playerID, Vector3 point, byte angle, bool isPro, bool isAdmin, int channel,
    byte face, byte hair, byte beard, Color skin, Color color, Color markerColor, Color beardColor,
    bool hand, int shirtItem, int pantsItem, int hatItem, int backpackItem, int vestItem,
    int maskItem, int glassesItem, int[] skinItems, string[] skinTags, string[] skinDynamicProps,
    EPlayerSkillset skillset, string language, CSteamID lobbyID, EClientPlatform clientPlatform)
{
    // Destroy placeholder audio listener for local player
    if (playerID.steamID == client && Level.placeholderAudioListener != null)
    {
        Destroy(Level.placeholderAudioListener);
        Level.placeholderAudioListener = null;
    }
    // Instantiate player game object from game mode
    Transform model = gameMode.getPlayerGameObject(playerID).transform;
    model.position = point;
    // Create SteamPlayer wrapper
    SteamPlayer newClient = new SteamPlayer(transportConnection, netId, playerID, model,
        isPro, isAdmin, channel, face, hair, beard, skin, color, markerColor, beardColor, hand,
        shirtItem, pantsItem, hatItem, backpackItem, vestItem, maskItem, glassesItem, skinItems,
        skinTags, skinDynamicProps, skillset, language, lobbyID, clientPlatform);
    clients.Add(newClient);
    _transportConnectionToPlayerMap.Add(transportConnection, newClient);
    updateRichPresence();
    broadcastEnemyConnected(newClient);
    return newClient;
}

addPlayer() is called with 34 parameters covering identity, cosmetics, economy items, skills, and network state. The player's NetId block (17 IDs) is pre-allocated via ClaimNetIdBlockForNewPlayer().

Removing a Player

csharp
internal static void RemoveClient(SteamPlayer clientToRemove)
{
    // Clean up third-party anti-cheat
#if WITH_THIRDPARTYAC
    RemoveClientFromThirdpartyAntiCheat(clientToRemove);
#endif
    // Close transport connection
    if (Dedicator.IsDedicatedServer)
        clientToRemove.transportConnection.CloseConnection();
    // Notify other clients
    broadcastEnemyDisconnected(clientToRemove);
    // Release NetId block
    clientToRemove.player.ReleaseNetIdBlock();
    // Destroy player game object
    if (clientToRemove.model != null)
    {
        EffectManager.ClearAttachments(clientToRemove.model);
        clientToRemove.player.isExpectingDestroy = true;
        Destroy(clientToRemove.model.gameObject);
    }
    NetIdRegistry.Release(clientToRemove.GetNetId());
    // Remove from lookup maps
    _transportConnectionToPlayerMap.Remove(clientToRemove.transportConnection);
    CSteamID steamId = clientToRemove.playerID.steamID;
    clients.Remove(clientToRemove);
    // Clean up culled player references
    foreach (SteamPlayer otherClient in clients)
        otherClient.culledPlayers.Remove(steamId);
    // Let next queued player in
    verifyNextPlayerInQueue();
    updateRichPresence();
}

The removal flow ensures proper cleanup of NetIds, visual attachments, queued players, and per-player culling state.

Connection Queue

csharp
internal static void verifyNextPlayerInQueue()
{
    if (pending.Count < 1) return;
    if (clients.Count >= maxPlayers) return;
    SteamPending pendingPlayer = pending[0];
    if (pendingPlayer.hasSentVerifyPacket) return;
    pendingPlayer.sendVerifyPacket();
}

When the server is full, new connections enter a pending queue. hasRoomForNewConnection returns true if clients.Count < maxPlayers or pending.Count < queueSize. The dequeue logic checks pending queue non-empty, free slot, and hasn't already sent a verify packet.

IP Rate Limiting

csharp
public static bool IsBlockedByMaxClientsWithSameIpAddressRule(ITransportConnection transportConnection,
    bool includeQueuedPlayers)
{
    if (configData.Server.Use_FakeIP) return false;
    if (!transportConnection.TryGetIPv4Address(out uint address)) return false;

    int max = configData.Server.Max_Clients_With_Same_IP_Address;
    int otherClientCount = 0;
    // Count pending and connected players with same IP
    // ...
    if (otherClientCount + 1 > max)
    {
        if (configData.Server.Max_Clients_With_Same_IP_Address_Log_Warnings)
            CommandWindow.LogWarning("Connection {transportConnection} hit IP limit");
        return true;
    }
    return false;
}

Prevents more than Max_Clients_With_Same_IP_Address clients from the same IPv4 address. Use_FakeIP bypasses this.

The badMessageRateLimiter (TransportConnectionRateLimiter) tracks per-connection "bad" packet counts — packets that may be legitimate but excessive. If a connection exceeds the threshold, it's likely an attacker trying to waste server CPU time.

Spawn Position Loading

csharp
private static void loadPlayerSpawn(SteamPlayerID playerID, out Vector3 point, out byte angle,
    out EPlayerStance initialStance)
{
    point = Vector3.zero;
    angle = 0;
    initialStance = EPlayerStance.STAND;
    bool needsSpawn = false;
    if (PlayerSavedata.fileExists(playerID, "/Player/Player.dat") && Level.info.type == ELevelType.SURVIVAL)
    {
        Block block = PlayerSavedata.readBlock(playerID, "/Player/Player.dat", 1);
        point = block.readSingleVector3() + new Vector3(0, 0.01f, 0);
        angle = block.readByte();
        if (!point.IsFinite()) { needsSpawn = true; }
        else if (point.y > Level.HEIGHT) { point.y = Level.HEIGHT - 10.0f; }
        else if (!PlayerStance.getStanceForPosition(point, ref initialStance)) { needsSpawn = true; }
    }
    else
    {
        needsSpawn = true;
    }
    // Fire plugin hook
    onLoginSpawning?.Invoke(playerID, ref point, ref yaw, ref initialStance, ref needsSpawn);
    if (needsSpawn)
    {
        PlayerSpawnpoint spawn = LevelPlayers.getSpawn(false);
        point = spawn.point + new Vector3(0, 0.5f, 0);
        angle = (byte)(spawn.angle / 2);
    }
}

Survival mode loads saved spawn positions from Player.dat. Non-survival modes and first-spawn always use a fresh spawnpoint. The onLoginSpawning delegate allows plugins to override spawn position.

Channel System

csharp
private static int nextPlayerChannelId = 2;
private static int allocPlayerChannelId()
{
    const int maxID = byte.MaxValue;
    for (int attempt = 0; attempt < maxID; ++attempt)
    {
        int pendingId = nextPlayerChannelId;
        ++nextPlayerChannelId;
        if (nextPlayerChannelId > maxID) nextPlayerChannelId = 2;
        SteamChannel existingComponent = findChannelComponent(pendingId);
        if (existingComponent == null) return pendingId;
    }
    CommandWindow.LogError("Ran out of player RPC channel IDs");
    shutdown(1, "Ran out of player RPC channel IDs");
    return 2;
}

Provider maintains a list of SteamChannel receivers. Channel 1 is reserved for manager components (LevelManager, EffectManager). Channels 2+ are allocated per-player. Channel IDs are 8-bit (max 255). The allocator wraps around at max and skips existing channels.

  • openChannel(receiver) — adds a receiver and logs it.
  • closeChannel(receiver) — removes the receiver.
  • findChannelComponent(id) — finds a channel by ID with null-safety cleanup.

The channel system routes ClientInstanceMethod and ServerInstanceMethod invocations to the correct component instance.

Provider Network Stats

csharp
private static uint _bytesSent;
public static uint bytesSent => _bytesSent;
private static uint _bytesReceived;
public static uint bytesReceived => _bytesReceived;
private static uint _packetsSent;
public static uint packetsSent => _packetsSent;
private static uint _packetsReceived;
public static uint packetsReceived => _packetsReceived;

Provider tracks bandwidth and packet counters since last reset. resetChannels() clears all connection state, NetId registry, invocation deferral registry, asset integrity cache, and physics material net table. It is called when disconnecting or resetting the networking layer.

Transport Connection Utilities

Provider includes helper methods for gathering connection lists:

csharp
public static PooledTransportConnectionList GatherClientConnections()
public static PooledTransportConnectionList GatherRemoteClientConnections()
public static PooledTransportConnectionList GatherClientConnectionsWithinSphere(Vector3 position, float radius)
public static PooledTransportConnectionList GatherClientConnectionsMatchingPredicate(Predicate<SteamPlayer> predicate)
public static PooledTransportConnectionList GatherRemoteClientConnectionsWithinSphere(Vector3 position, float radius)

All use TransportConnectionListPool to minimize allocation. "Remote" variants exclude the local loopback host player. "WithinSphere" variants check sqrMagnitude against the player's transform position.

Player lookup methods:

  • findPlayer(ITransportConnection) — resolve connection to SteamPlayer.
  • findPendingPlayer(ITransportConnection) — resolve to SteamPending.
  • findTransportConnection(CSteamID) — resolve Steam ID to connection (searches both pending and connected players).
  • findTransportConnectionSteamId(ITransportConnection) — reverse resolve.

Connection Failure Handling

csharp
private static bool canCurrentlyHandleClientTransportFailure;
private static bool hasPendingClientTransportFailure;

Some loading operations (workshop download, level loading) can't handle transport failures mid-process. canCurrentlyHandleClientTransportFailure gates whether transport errors trigger immediate disconnect or queue the failure for after the loading completes.

Server List Integration

The Provider integrates with Steam's server browser system for server discovery:

csharp
// Server advertisement via game tags
SteamGameServer.SetGameTags($"Transport:{transportTag}"
    + $" Map:{mapName}"
    + $" Mode:{gameMode}"
    + $" PvP:{(isPvP ? 1 : 0)}"
    + $" VAC:{(isVacSecure ? 1 : 0)}"
    + $" AC:{(antiCheatEnabled ? 1 : 0)}"
    + $" Workshop:{(hasWorkshopItems ? 1 : 0)}"
    + $" Mods:{modCount}");

The client parses these tags to display server details in the server browser UI. ServerBookmarksManager allows players to bookmark favorite servers for quick reconnect, storing the server's Steam ID, last-known name, and connection history.

Lobby Integration

While the Provider manages direct server connections, Lobbies manages Steam lobby-based connections:

  • Lobbies.inLobby — whether the local client is currently in a Steam lobby.
  • Lobbies.currentLobby — the current CSteamID of the lobby.
  • Lobbies use Steam's matchmaking system for player discovery and invitation.

When joining a lobby, the lobby's metadata includes the game server's Steam ID or connection string, which is passed to Provider.connect() via ServerConnectParameters.lobbyID.

Config Data Flow

The Provider holds the authoritative config that all gameplay systems reference:

csharp
public static ModeConfigData modeConfigData;

This is populated from:

  1. Server-side: LiveConfig reads the Config.json file on startup.
  2. Client-side: ClientMessageHandler_ReplicateConfig receives the config from the server.
  3. Per-map overrides: LevelInfoConfigData.GetPerDifficultyConfigOverrides() applies overrides.

The config data includes:

  • Events section: airdrop frequency/speed, arena timings/speeds, compactor settings.
  • Barricades section: decay rates, damage multipliers.
  • Inventory section: stacking limits, drop settings.
  • Player section: health/food/water/virus settings, skills, experience.
  • Vehicles section: spawn rates, fuel consumption, damage.