Skip to content

Creating Packets

In contrast to the LabyMod 3 Server API, the LabyMod 4 Server API doesn't communicate with JsonElements. Instead, it writes data directory into a ByteBuffer. This is a more efficient way of communication and allows for more flexibility in the data that can be sent.

The downside of this is that it is even more important to read and write packets correctly. Therefore, it is recommended to share the classes on the server and client-side. The core artifact for example, is also shared between the implementation in LabyMod 4 and the server.

For an easier process of creating packets, we provide the classes PayloadReader and PayloadWriter. Both contain a variety of methods to read and write data from and to the ByteBuffer for any types thinkable. From collections and arrays to nullable objects.

If you are still unsure if you are doing everything correctly, please don't hesitate to check out the Packets of the LabyMod Protocol on GitHub or ask on our Discord Server for Developers.

Creating a Packet

For our example packet, we'll be reading and writing a nullable component, an integer and a collection of objects with a string and a bunch of booleans.

First, we'll create that implements the Packet interface. And create all the fields we want to read and write. Also, we create a record for the object that our List will contain.
Don't mind the field names, they are just placeholders for this example.

import net.labymod.serverapi.api.model.component.ServerAPIComponent;
import net.labymod.serverapi.api.packet.Packet;
import net.labymod.serverapi.api.payload.io.PayloadReader;
import net.labymod.serverapi.api.payload.io.PayloadWriter;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import java.util.List;

public class ExamplePacket implements Packet {

  private ServerAPIComponent component;
  private int color;
  private List<ExamplePacketObject> objects;

  public ExamplePacket(
      @Nullable ServerAPIComponent component,
      @NotNull List<ExamplePacketObject> objects,
      int color
  ) {
    // Throw an exception if the list is null
    Objects.requireNonNull(objects, "Objects list cannot be null");

    // Assign the values to the fields
    this.component = component;
    this.objects = objects;
    this.color = color;
  }

  public @Nullable ServerAPIComponent getComponent() {
    return this.component;
  }

  public int getColor() {
    return this.color;
  }

  /**
   * This can be annotated with {@link NotNull}, as the list is never null. 
   * We know that because our constructor is throwing an exception if no list
   * is provided. And if for some reason the list is null upon reading, it 
   * will never be handed over to the PacketHandler.
   */
  public @NotNull List<ExamplePacketObject> getObjects() {
    return this.objects;
  }

  /**
   * It's always a good idea to override the {@link Object#toString()} method to
   * provide a human-readable representation of the object.
   */
  @Override
  public String toString() {
    return "ExamplePacket{" +
        "component=" + component +
        ", color=" + color +
        '}';
  }

  /**
   * The record is a simple data class that is used to store the data of
   * the objects list.
   */
  public record ExamplePacketObject(
      String name,
      boolean editable,
      boolean createdByUser,
      boolean global
  ) {

    /**
     * It's always a good idea to override the {@link Object#toString()} method 
     * to provide a human-readable representation of the object.
     */
    @Override
    public String toString() {
      return "ExamplePacketObject{" +
          "name='" + name + '\'' +
          ", editable=" + editable +
          ", createdByUser=" + createdByUser +
          ", global=" + global +
          '}';
    }
  }
}

Write the Packet

To write the packet, we need to override the write method of the Packet interface. Be sure not to call super.write, as this will throw an exception upon writing the packet.

Important

Always keep in mind that the order of writing and reading the values of the Packet must be the same, also the types must match. You can't write a variable integer and read a normal integer. If you are unsure what to do (or whether what you've done is correct), don't hesitate to ask on our Discord Server for Developers.

@Override
public void write(@NotNull PayloadWriter writer) {
  // Write the nullable component
  writer.writeOptional(
      this.component, // The component
      writer::writeComponent // the consumer to write the component
  );

  // Write the color. We're using writeVarInt here, as variable integers are 
  // more efficient on the network stack for smaller values. Alternatively 
  // you can use PayloadWriter#writeInt
  writer.writeVarInt(this.color);

  // Write the list of objects
  writer.writeCollection(
      this.objects, // The list to write
      object -> { // the consumer is called for every object in the list
        // write the name
        writer.writeString(object.name());

        // write the boolean values
        writer.writeBoolean(object.editable());
        writer.writeBoolean(object.createdByUser());
        writer.writeBoolean(object.global());
      }
  );
}

Read the Packet

To read the packet, we need to override the read method of the Packet interface. Be sure not to call super.read, as this will throw an exception upon reading the packet.

Important

Always keep in mind that the order of writing and reading the values of the Packet must be the same, also the types must match. You can't write a variable integer and read a normal integer. If you are unsure what to do (or if what you've done is correct), don't hesitate to ask on our Discord Server for Developers.

@Override
public void read(@NotNull PayloadReader reader) {
  // Read the optional component
  this.component = reader.readOptional(reader::readComponent);

  // Read the color as variable integer. 
  this.color = reader.readVarInt();

  // Read the list of objects
  this.objects = reader.readList(() -> { // the supplier is called for every object in the list
    return new ExamplePacketObject(
        reader.readString(), // read the name
        reader.readBoolean(), // read the editable boolean
        reader.readBoolean(), // read the createdByUser boolean
        reader.readBoolean() // read the global boolean
    );
  });
}

Final Words

That's it. You've successfully created a packet that can be sent the to or from the server. Now all you have to do is to register the Packet in your Protocol.