Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/twhl-community/halflife-unified-sdk/llms.txt

Use this file to discover all available pages before exploring further.

The Half-Life Unified SDK includes a network data system that allows the server to transfer large amounts of immutable, generated data to connecting clients. Rather than hard-coding data on both sides, game systems can register themselves as block handlers and serialize their state into a shared JSON file. Clients download and deserialize that file when they connect, keeping both sides in sync without requiring separate distribution of static data files. The network data system uses the logger named net_data.

How It Works

When a map loads, each registered game system serializes its data into a file called networkdata/data.json. This file is then precached so that clients automatically download it when they connect to the server. Once downloaded, the client loads the file and uses it to initialize the corresponding client-side systems. Before downloading a new copy, the client deletes its local version of the file to ensure it always receives the authoritative file from the server rather than using a stale cached copy. Project information is embedded in the file and checked on the client side to verify that the client and server are running compatible builds. If they are not, the client automatically disconnects to prevent further problems.
Alpha builds disable the compatibility check to simplify development workflows.
The constant NetworkDataProtocolVersion should be incremented whenever the structure of the data in the file changes. This allows the system to detect compatibility mismatches early.

File Size

The engine can transfer files up to the size specified by the sv_filetransfermaxsize cvar, which defaults to 10 MiB (10,485,760 bytes). For an unmodified Unified SDK installation, the average data.json file is approximately 100 KiB, with most of that space taken up by the sentences list.

Required CVars

To ensure correct operation the following cvars are forcibly enabled by the system:
  • sv_allowdownload
  • sv_send_resources
  • sv_allow_dlfile
  • cl_allowdownload

Known Limitations

You cannot connect a client to a dedicated server that is running from the same game installation as the client. This is a known limitation of the system. In practice this should never be an issue because the dedicated server tool distributed alongside the client no longer works, and dedicated servers should always be installed separately via SteamCMD.

Error Handling

  • If an error occurs while generating the data on the server, the network data system shuts down and the error is printed to the console.
  • If the client encounters an error while deserializing the downloaded file, the player is returned to the main menu and the error is printed to the console.

Using FastDL Servers

When using a FastDL (HTTP) file server, there is one additional consideration. Because the Unified SDK generates networkdata/data.json fresh on every map load, the FastDL server cannot host the real file — it changes every time. To prevent an error message from appearing in the client console, place a dummy file on your FastDL server at the same path (networkdata/data.json) containing only an empty JSON object:
{}
The client will download this dummy file from the FastDL server, delete it (as it always does before fetching the real file), and then download the real generated file directly from the game server. Without the dummy file the game still works correctly, but players will see an error message in their console.

Implementing a Block Handler

Game systems that need to transfer data across the network implement the INetworkDataBlockHandler interface and register themselves with the network data manager.

Registering a Handler

class AmmoTypeSystem final : public IGameSystem, public INetworkDataBlockHandler
{
public:
    bool Initialize() override;
    void HandleNetworkDataBlock(NetworkDataBlock& block) override;

    // Remainder of class...
};
The handler is registered during system initialization:
bool AmmoTypeSystem::Initialize()
{
    g_NetworkData.RegisterHandler("AmmoTypes", this);
    return true;
}
The string passed to RegisterHandler becomes the key for this system’s data block inside data.json.

Serializing and Deserializing

The same HandleNetworkDataBlock function is called on both the server (to serialize) and the client (to deserialize), distinguished by block.Mode:
void AmmoTypeSystem::HandleNetworkDataBlock(NetworkDataBlock& block)
{
    if (block.Mode == NetworkDataMode::Serialize)
    {
        block.Data = json::array();

        for (const auto& type : m_AmmoTypes)
        {
            json data = json::object();

            data.emplace("Name", type.Name.c_str());
            data.emplace("MaximumCapacity", type.MaximumCapacity);

            if (!type.WeaponName.empty())
            {
                data.emplace("WeaponName", type.WeaponName.c_str());
            }

            block.Data.push_back(std::move(data));
        }
    }
    else
    {
        Clear();

        for (const auto& data : block.Data)
        {
            const auto name = data.value("Name", "");
            const int maximumCapacity = data.value("MaximumCapacity", -2);
            const auto weaponName = data.value("WeaponName", "");

            if (Register(name, maximumCapacity, weaponName) == -1)
            {
                block.ErrorMessage = "Invalid ammo type received from server";
                return;
            }
        }
    }
}
Setting block.ErrorMessage to a non-empty string aborts the serialization or deserialization process and triggers the system’s error handling behaviour.
Any JSON data type is supported in block.Data, but keeping the structure as simple as possible reduces the size of the generated file and keeps transfer times short.

Build docs developers (and LLMs) love