Network GUI

Client ↔ server communication for EriAPI GUIs — send actions, receive data, and open screens from the server.

fr.eri.eriapi.network Forge 1.12.2 Java 8

Introduction

The Network module provides a simplified system for communicating between your EriAPI GUIs and the Minecraft server. Without it, GUIs are purely visual — they cannot read server data or trigger server-side actions.

Client vs Server in Minecraft

Minecraft runs two sides simultaneously: the client (what the player sees — the game window, GUIs, rendering) and the server (what manages the world, items, players, economy). They communicate via packets.

When a player clicks "Buy" in your GUI, the click happens on the client. But the item transfer must happen on the server. You need to send a packet to tell the server "this player clicked Buy". That is what the network system is for.

When do you need the network system?

  • When a button click should give or take items from a player
  • When you need to read server-side data (another player's stats, a database, etc.)
  • When you want to open a GUI remotely (the server tells a client to open a screen)
  • When multiple players need to see the same data update in real time
Don't need networking? Great!

If your GUI only displays information stored locally (a scoreboard, a settings screen), you don't need this module at all. The network system is only needed when actions must be validated or executed on the server.

Class / Interface Role
GuiNetworkHandler Central hub — initialization, sending/receiving packets, registries
IGuiDataReceiver Interface to implement in a GUI to receive data from the server
PacketGuiAction Packet client → server for actions (button click, etc.)
PacketGuiData Bidirectional packet for key/value data synchronization
PacketGuiOpen Packet server → client to open a GUI with initial data

Initialization

The network system must be initialized exactly once in FMLPreInitializationEvent. Then register your GUI factories and action handlers in FMLInitializationEvent.

Java — setup in the @Mod class
@Mod(modid = "mymod")
public class MyMod {

    @Mod.EventHandler
    public void preInit(FMLPreInitializationEvent event) {
        // Initialize the network system first
        GuiNetworkHandler.init("mymod");
    }

    @Mod.EventHandler
    public void init(FMLInitializationEvent event) {
        // Register a GUI factory (client-side)
        // When the server calls openGuiFor("shop_menu", ...), this factory creates the GUI
        GuiNetworkHandler.registerGui("shop_menu", data ->
            new GuiShop(data.getInteger("shop_type"))
        );

        // Register an action handler (server-side)
        // When a client calls sendAction("shop_menu", "buy_btn", "buy_item", "42"),
        // this handler is called on the server
        GuiNetworkHandler.registerActionHandler("shop_menu", (player, params) -> {
            String action = params.get("action");       // "buy_item"
            String data   = params.get("data");         // "42"
            String compId = params.get("component");    // "buy_btn"
            if ("buy_item".equals(action)) {
                ShopManager.buyItem((EntityPlayerMP) player, Integer.parseInt(data));
            }
        });
    }
}
preInit is mandatory

GuiNetworkHandler.init(modId) must be called in FMLPreInitializationEvent before any packet is sent. Calling sendAction() or openGuiFor() before initialization causes a NullPointerException.

GuiNetworkHandler

Central hub of the GUI network system. All methods are static. The network channel is created under the name {modId}_gui.

Package

fr.eri.eriapi.network.GuiNetworkHandler

Initialization

SignatureDescription
static void init(String modId) Initializes the network system and registers the 4 internal packets (action, server data, client data, open). Must be called exactly once in FMLPreInitializationEvent. The channel will be named {modId}_gui.
static boolean isInitialized() Returns true if the system has been initialized. Useful for defensive checks.

Registries

SignatureDescription
static void registerGui(String guiId,
Function<NBTTagCompound, GuiScreen> factory)
[Client-side] Registers a GUI factory identified by guiId. When the server calls openGuiFor(player, guiId, data), the factory is invoked with the NBT data and the returned GuiScreen is displayed. Register in FMLInitializationEvent.
static void registerActionHandler(String guiId,
BiConsumer<EntityPlayer, Map<String,String>> handler)
[Server-side] Registers a handler for actions sent from a GUI. The Map<String,String> params contains: "component" (source component ID), "action" (action type), "data" (additional payload).
static void registerDataHandler(String guiId,
BiConsumer<EntityPlayer, Map<String,String>> handler)
[Server-side] Registers a handler for data sent from a GUI. The Map<String,String> params contains: "component", "key", "value".

Sending — Client to Server

SignatureDescription
static void sendAction(String guiId, String componentId,
String action, Object data)
[Client only — @SideOnly(CLIENT)] Sends an action to the server. guiId identifies the GUI (must match a registered handler). componentId identifies the source component (e.g. "buy_btn"). action is the action type (e.g. "buy_item"). data is additional payload — converted to String via toString(), or an empty string if null.
static void sendData(String guiId, String componentId,
String key, Object value)
[Client only] Sends a key/value pair to the server. Useful for input fields (e.g. the value of a TextField when the user submits). value is converted to String via toString().

Sending — Server to Client

SignatureDescription
static void openGuiFor(EntityPlayerMP player,
String guiId, NBTTagCompound data)
[Server only] Sends a PacketGuiOpen to the player. On the client, the factory registered under guiId is called with data and the resulting GUI is displayed. data may be an empty new NBTTagCompound() if no initial data is needed.
static void sendDataToClient(EntityPlayerMP player,
String guiId, String componentId,
String key, Object value)
[Server only] Sends a key/value pair to the player's client. If the player's currently open GUI implements IGuiDataReceiver, its onDataUpdate(componentId, key, value) method is called. value is converted to String.
Thread-safe execution

Handlers registered via registerActionHandler and registerDataHandler are called on the server main thread (via addScheduledTask in the PacketHandler). You can modify player data directly without extra synchronized blocks.

Likewise, the client-side data dispatch is called on the Minecraft main thread (via addScheduledTask). You can modify GUI components directly.

IGuiDataReceiver

Interface to implement in an EriGuiScreen to receive data updates sent by the server via sendDataToClient().

When the server sends a PacketGuiData to the client, GuiNetworkHandler checks whether the currently open GUI implements IGuiDataReceiver and calls onDataUpdate().

Package

fr.eri.eriapi.network.IGuiDataReceiver

Method to implement

SignatureDescription
void onDataUpdate(String componentId, String key, String value) Called on the client when data is received from the server. componentId: identifier of the targeted component (as passed to sendDataToClient). key: data key. value: data value (always a String).
Java — IGuiDataReceiver in an EriGuiScreen
public class GuiShop extends EriGuiScreen implements IGuiDataReceiver {

    private Label balanceLabel;
    private Label statusLabel;

    @Override
    protected void buildGui() {
        balanceLabel = new Label(20, 20, 400, 30, "Loading...")
            .scale(1.4f)
            .color(0xFFFFD700);
        getRoot().add(balanceLabel);

        statusLabel = new Label(20, 60, 400, 24, "")
            .scale(1.0f)
            .color(0xFFFFFFFF);
        getRoot().add(statusLabel);
    }

    @Override
    public void onDataUpdate(String componentId, String key, String value) {
        // Filter by component ID (useful when multiple components receive data)
        if ("balance_display".equals(componentId)) {
            if ("gold".equals(key)) {
                balanceLabel.text("Gold: " + value);
            }
        } else if ("purchase_result".equals(componentId)) {
            if ("success".equals(key)) {
                boolean ok = Boolean.parseBoolean(value);
                statusLabel.text(ok ? "Purchase successful!" : "Insufficient funds.")
                           .color(ok ? 0xFF44FF88 : 0xFFFF4444);
            }
        }
    }
}

Packets

The system uses three internal packets. In general you never interact with the packet classes directly — always go through GuiNetworkHandler. This section documents their format for reference.

PacketGuiAction — Client → Server

Sent by GuiNetworkHandler.sendAction() when a client component triggers an action (button click, etc.).

FieldTypeDescription
guiIdStringSource GUI identifier (e.g. "shop_menu")
componentIdStringSource component identifier (e.g. "buy_btn")
actionStringAction type (e.g. "buy_item", "cancel")
dataStringAdditional payload (e.g. "42" for an item ID)

The server handler receives a Map<String, String> with keys "component", "action", "data".

PacketGuiData — Bidirectional

Used by sendData() (client → server) and sendDataToClient() (server → client) to synchronize a key/value pair.

FieldTypeDescription
guiIdStringIdentifier of the GUI concerned
componentIdStringIdentifier of the component concerned
keyStringData key
valueStringValue (always a String)

PacketGuiOpen — Server → Client

Sent by openGuiFor() to open a GUI on the client.

FieldTypeDescription
guiIdStringGUI identifier to open (must be registered via registerGui)
initialDataNBTTagCompoundInitial data passed to the GUI factory
Serialization

All packets use ByteBufUtils.writeUTF8String / readUTF8String for String fields, and ByteBufUtils.writeTag / readTag for NBT. The maximum String size is limited by the Minecraft packet MTU (approximately 32 767 characters).

Full Example — Client/Server Shop

A complete example showing both sides: the client GUI sends an action, the server processes it and replies with updated data.

1. Setup (in the @Mod class)

Java — MyMod.java
@Mod(modid = "mymod")
public class MyMod {

    @Mod.EventHandler
    public void preInit(FMLPreInitializationEvent event) {
        GuiNetworkHandler.init("mymod");
    }

    @Mod.EventHandler
    public void init(FMLInitializationEvent event) {
        // GUI factory: the server can open this GUI on the client
        GuiNetworkHandler.registerGui("shop", data -> {
            String shopName  = data.getString("name");
            int initialGold  = data.getInteger("gold");
            return new GuiShop(shopName, initialGold);
        });

        // Action handler: the server processes purchases
        GuiNetworkHandler.registerActionHandler("shop", (player, params) -> {
            EntityPlayerMP mp = (EntityPlayerMP) player;
            String action = params.get("action");
            String data   = params.get("data");

            if ("buy".equals(action)) {
                int itemId   = Integer.parseInt(data);
                boolean ok   = ShopManager.processPurchase(mp, itemId);
                int newGold  = ShopManager.getGold(mp);

                // Reply to the client
                GuiNetworkHandler.sendDataToClient(mp, "shop", "result", "success", String.valueOf(ok));
                GuiNetworkHandler.sendDataToClient(mp, "shop", "balance", "gold",   String.valueOf(newGold));
            }
        });
    }
}

2. Opening the GUI from the server

Java — ShopNPC.java (player interaction)
// When a player right-clicks the Shop NPC on the server:
NBTTagCompound data = new NBTTagCompound();
data.setString("name", "Pierre's Shop");
data.setInteger("gold", ShopManager.getGold(player));
GuiNetworkHandler.openGuiFor(player, "shop", data);

3. Client GUI

Java — GuiShop.java
public class GuiShop extends EriGuiScreen implements IGuiDataReceiver {

    private final String shopName;
    private int currentGold;

    private Label labelGold;
    private Label labelStatus;

    public GuiShop(String shopName, int initialGold) {
        this.shopName    = shopName;
        this.currentGold = initialGold;
    }

    @Override
    protected void buildGui() {
        ContainerComponent root = getRoot();

        // Background
        root.add(new Rectangle(0, 0, 1920, 1080).fillColor(0x88000000));

        // Center panel
        ContainerComponent panel = new ContainerComponent(660, 240, 600, 600);
        panel.add(new Rectangle(0, 0, 600, 600).fillColor(0xCC1A1A2E).cornerRadius(16));
        panel.add(new Label(0, 24, 600, 40, shopName).scale(2.0f).color(0xFFFFD700)
            .align(Label.Align.CENTER));

        // Gold balance
        labelGold = new Label(20, 80, 560, 30, "Gold: " + currentGold);
        labelGold.scale(1.3f).color(0xFFFFD700);
        panel.add(labelGold);

        // Status label
        labelStatus = new Label(20, 120, 560, 24, "");
        labelStatus.scale(1.0f).color(0xFFFFFFFF);
        panel.add(labelStatus);

        // Buy button
        panel.add(new Button(200, 180, 200, 44, "Buy Sword (100 gold)")
            .colorScheme(0xFF1A6A3A)
            .cornerRadius(8)
            .onClick(() -> {
                GuiNetworkHandler.sendAction("shop", "buy_btn", "buy", "1");
            })
        );

        root.add(panel);
    }

    @Override
    public void onDataUpdate(String componentId, String key, String value) {
        if ("result".equals(componentId) && "success".equals(key)) {
            boolean ok = Boolean.parseBoolean(value);
            labelStatus.text(ok ? "Purchase successful!" : "Not enough gold!")
                       .color(ok ? 0xFF44FF88 : 0xFFFF4444);
        } else if ("balance".equals(componentId) && "gold".equals(key)) {
            currentGold = Integer.parseInt(value);
            labelGold.text("Gold: " + currentGold);
        }
    }
}
Full flow
  1. Player interacts with the NPC → server calls openGuiFor()
  2. Client receives PacketGuiOpen → factory creates GuiShop
  3. Player clicks "Buy" → sendAction() sends PacketGuiAction
  4. Server receives the action → handler processes the purchase
  5. Server calls sendDataToClient() twice (result + balance)
  6. Client receives PacketGuiDataonDataUpdate() updates the GUI