Skip to content

Extended Functionality Of Default Packets

Vrekt edited this page May 11, 2023 · 1 revision

Lunar comes with many default packets such as CPacketJoinWorld. This packet is sent by a client when requesting to join a new world.

It only has a few basic fields such as world name and player username. You may want to extend this for whatever reason to include, for example, a boolean field describing something about the player (or anything else you desire)

(TLDR AT BOTTOM)

Our Custom Packet Client

First, lets start with our own custom packet that extends CPacketJoinWorld.

public class TestCustomJoinWorldPacket extends CPacketJoinWorld {

    private boolean testField;

    public static void handle(ClientPacketHandler handler, ByteBuf buf) {
        Gdx.app.log("Testing", "Hello From handle custom");
        handler.handleJoinWorld(new TestCustomJoinWorldPacket(buf));
    }

    public TestCustomJoinWorldPacket(String worldName, String username) {
        super(worldName, username);
    }

    public TestCustomJoinWorldPacket(ByteBuf buffer) {
        super(buffer);
    }

    public void setTestField(boolean testField) {
        this.testField = testField;
    }

    public boolean isTestField() {
        return testField;
    }

    @Override
    public void encode() {
        writeId();
        writeString(worldName);
        writeString(username);
        buffer.writeBoolean(testField);
    }

    @Override
    public void decode() {
        worldName = readString();
        username = readString();
        testField = buffer.readBoolean();
    }
}

This is basically just CPacketJoinWorld but we overrided the handle, encode and decode methods to provide for our extra boolean field.

Our Custom Packet Server

This is just the same thing as our client packet but instead its meant to be sent from the server to the client

public class TestCustomJoinWorldPacketServer extends SPacketJoinWorld {

    protected boolean testField;

    public static void handle(ServerPacketHandler handler, ByteBuf buf) {
        handler.handleJoinWorld(new TestCustomJoinWorldPacketServer(buf));
    }

    public TestCustomJoinWorldPacketServer(String worldName, int entityId) {
        super(worldName, entityId);
    }

    public TestCustomJoinWorldPacketServer(ByteBuf buffer) {
        super(buffer);
    }

    public void setTestField(boolean testField) {
        this.testField = testField;
    }

    public boolean isTestField() {
        return testField;
    }

    @Override
    public void encode() {
        super.encode();
        buffer.writeBoolean(testField);
    }

    @Override
    public void decode() {
        worldName = readString();
        entityId = buffer.readInt();
        testField = buffer.readBoolean();
    }

}

Server Side Registering

Now, in our game server we need to create a custom connection handler. This could extend ServerPlayerConnection or ServerAbstractConnection.

// our custom connection class to handle extra logic for whatever packet we desire.
public class Connection extends ServerPlayerConnection {

    public Connection(Channel channel, LunarServer server) {
        super(channel, server);
    }

    @Override
    public void handleJoinWorld(CPacketJoinWorld packet) {
        // here we cast and call our other method instead.
        // because we changed the protocol routing we know this packet is of that type (see below)
        handleJoinWorld((TestCustomJoinWorldPacket) packet);
    }

    public void handleJoinWorld(TestCustomJoinWorldPacket packet) {
        System.err.println("Test field is " + packet.isTestField());
        // whatever else
    }

}

A custom connection provider is essential for all custom game/server logic. The default one is just a template and only handles basic things.

Now that we have our own connection provider we must register it with the NettyServer.

final LunarProtocol protocol = new LunarProtocol(true);
final GameServer gameServer = new GameServer(protocol, "1.0");
server.setConnectionProvider(channel -> new Connection(channel, gameServer));

Protocol Changes For Server

Next, we can change how the protocol routes the packets. Since we have a new custom packet we can change the default packet routing. In the below example we change the default packet handler for CPacketJoinWorld. Inside, we include our own custom logic for handling our special packet that extends CPacketJoinWorld

protocol.changeDefaultClientPacketHandlerFor(CPacketJoinWorld.PID, (buf, handler) -> {
            final TestCustomJoinWorldPacket packet = new TestCustomJoinWorldPacket(buf);
            handler.handleJoinWorld(packet);
        });

Protocol Changes For Client

Client side is the same thing, we have a custom connection provider/handler. We just need to register it. In the example below instead we change the default handler for a server packet since the server will be sending us the server packet.

protocol.changeDefaultServerPacketHandlerFor(SPacketJoinWorld.PID, (buf, handler) -> {
            handler.handleJoinWorld(new TestCustomJoinWorldPacketServer(buf));
});

Then, we can just handle the packet normally, in my game example I do this.

// we cast because we know its gonna be that type because we changed the protocol routing
connection.registerHandlerSync(ConnectionOption.HANDLE_JOIN_WORLD, packet -> myWorld.handleWorldJoin((TestCustomJoinWorldPacketServer) packet));

TLDR

1. Create a custom packet extending a default packet (for example `CPacketJoinWorld`)
  1.1: Do this for both client and server packets. (for example `SPacketJoinWorld`)
2. Override encode and decode methods so you can add in your custom fields.
3. Register your client packet in your game server with `protocol.changeDefaultClientPacketHandlerFor(CPacketJoinWorld.PID, (buf, handler) -> {}`
4. Register your server packet in your client game with `protocol.changeDefaultServerPacketHandlerFor(SPacketJoinWorld.PID, (buf, handler) -> {}`
5. Include your custom logic for handling your custom packet within those methods.