Skip to content

NLR-DevTeam/PayloadLib

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

12 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

PayloadLib

Easier approach for Paper servers to send & handle custom payloads, simple & powerful.

We support a wide range of types, please see Supported Data Types.

| Supported Versions | Basic Usage | Advanced Usage | Examples |

Warning

CraftBukkit and Spigot servers are not supported, and this plugin is likely to crash when running on them.

Importing

Note

PayloadLib hasn't been published to the maven central yet, but you can use the snapshot repository.

To access the snapshot version of PayloadLib, please add this in your build.gradle:

repositories {
  maven {
    name = 'Central Portal Snapshots'
    url = 'https://central.sonatype.com/repository/maven-snapshots/'
  }
}

dependencies {
    implementation 'top.nlrdev:payloadlib:0.0.2-SNAPSHOT'

    // Optional dependency, providing ByteBuf
    implementation 'io.netty:netty-buffer:4.2.2.Final'
}

Embedding this plugin within other plugin JARs is not recommended, as it can lead to various issues.

Supported Versions

Bukkit's API has an annoying limit, so we use NMS to send payloads directly.

Please notice that we do only support recent versions of Minecraft (excluding some versions that nobody cares).

NMS Target Compatible With
1.21.4 1.21.4, 1.21.5, 1.21.7
1.21.1 1.21, 1.21.1
1.20.6 1.20.5, 1.20.6
1.20.4 1.20.3, 1.20.4
1.20.1 1.20, 1.20.1

Note: Italic means the version is not fully tested, but may be usable.

Usage

First, add PayloadLib in the plugin.yml as an dependency:

# ...
depend: [PayloadLib]

Then, declare your payload like this:

import top.nlrdev.payloadlib.Payload;
import top.nlrdev.payloadlib.types.Identifier;

public record MyPayload(int id, String data) implements Payload {
    public static final Identifier ID = Identifier.of("namespace", "path");
    // Or: public static final Identifier ID = Identifier.parse("namespace:path");

    @Override
    public Identifier getId() {
        return ID;
    }
}

Next, register your packets as follows:

Note

Both C2S (Serverbound) and S2C (Clientbound) payloads must be registered for serialization.

import top.nlrdev.payloadlib.PayloadLib;

// In your JavaPlugin implementation
@Override
public void onEnable() {
    // ...
    PayloadLib.registerPayload(MyPayload.ID, MyPayload.class);
}

If this is a C2S (Serverbound) payload, you can register a handler like this:

PayloadLib.<MyPayload>registerGlobalReceiver(MyPayload.ID, (/* Bukkit Player */ sender, /* MyPayload */ payload) -> {
    sender.sendMessage("ID: %s, Data: %s".formatted(payload.id(), payload.data()));
});

Else, if this is a S2C (Clientbound) payload, you can send it in two methods:

Payload payload = new MyPayload(1234, "some-data");
payload.sendTo(player1, player2, player3, ...);

// Or
PayloadLib.sendPayload(payload, player1, player2, player3, ...);

Supported Data Types

Note

We only support types with a PacketCodec in vanilla Minecraft.

Please refer to SerializationImpl.java for more details.

Primitive

Primitive Packaged Array Unsigned Implementation
boolean Boolean Unsupported None
byte Byte byte[] None
short Short Unsupported top.nlrdev.payloadlib.types.UnsignedShort
char Character Unsupported None
int Integer Unsupported None
long Long Unsupported None
float Float Unsupported None
double Double Unsupported None

Non-Primitive

Type PayloadLib
String Unchanged
UUID Unchanged
org.joml.Vector3f Unchanged
org.joml.Quaternionf Unchanged
Minecraft (Official) Minecraft (Yarn) PayloadLib
net.minecraft.resources.ResourceLocation net.minecraft.util.Identifier top.nlrdev.payloadlib.types.Identifier
ByteBufCodecs#VAR_INT PacketCodecs#VAR_INT top.nlrdev.payloadlib.types.VarInt
ByteBufCodecs#VAR_LONG PacketCodecs#VAR_LONG top.nlrdev.payloadlib.types.VarLong
net.minecraft.world.phys.Vec3 net.minecraft.util.math.Vec3d org.joml.Vector3d
net.minecraft.core.BlockPos net.minecraft.util.math.BlockPos top.nlrdev.payloadlib.types.BlockPos
net.minecraft.core.GlobalPos net.minecraft.util.math.GlobalPos org.bukkit.Location

Tip

Mojang's RegistryKey<T> (or ResourceKey<T> in official mappings) sends only its value in the registry to the server, which means:

  • It is impossible to read the registry root
  • You should use Identifier for serialization

Note

When the world provided in GlobalPos can't be found, the deserializer will simply return null.

Besides, GlobalPos can only save block positions, so there's a precision loss, and the deserialized yaw, pitch will always be zero.

Advanced Usage

Handling ByteBuf Directly

There's a high-level API called registerRawReceiver, here's its usage:

import top.nlrdev.payloadlib.encoding.StringEncoding;

PayloadLib.registerRawReceiver(MyPayload.ID, (/* Bukkit Player */ sender, /* ByteBuf */ buf) -> {
    int id = buf.readInt();
    String data = StringEncoding.decode(buf, /* Max Length */ 65535);

    sender.sendMessage("ID: %s, Data: %s".formatted(id, data));
});

Declaring Custom (De)Serializer

Warning

This is a dangerous operation, if your requirement is quite simple, please refer to Registering Custom Data Type.

You need to use annotations to implement custom serializer / deserialzer, here's an example.

The type is a Bukkit Player, and we will use its UUID for serialization.

import io.netty.Buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
import top.nlrdev.payloadlib.Payload;
import top.nlrdev.payloadlib.types.Identifier;
import top.nlrdev.payloadlib.serialization.PayloadSerializer;
import top.nlrdev.payloadlib.serialization.PayloadDeserializer;
import top.nlrdev.payloadlib.serialization.SerializationImpl;

import java.util.UUID;

public record MyPayloadTwo(Player player) implements Payload {
    public static final Identifier ID = Identifier.of("namespace", "path");

    @Override
    public Identifier getId() {
        return ID;
    }

    @PayloadSerializer
    public static ByteBuf serialize(MyPayloadTwo instance) {
        ByteBuf buf = Unpooled.buffer();
        SerializationImpl.getInternalSerializer(UUID.class).accept(buf, instance.player.getUniqueId());
        return buf;
    }

    @PayloadDeserializer
    public static MyPayloadTwo deserialize(ByteBuf buf) {
        UUID uuid = SerializationImpl.getInternalDeserializer(UUID.class).apply(buf);
        Player player = Bukkit.getPlayer(uuid);
        assert player != null;

        return new MyPayloadTwo(player);
    }
}

Registering Custom Data Type

Overwriting the (de)serializer is dangerous, and registering custom data type is more convenient. Here's an instance:

import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
import top.nlrdev.payloadlib.serialization.SerializationImpl;

import java.util.UUID;

SerializationImpl.registerType(
    Player.class,
    /* Serializer */ (/* ByteBuf */ buf, player) -> SerializationImpl.getInternalSerializer(UUID.class).accept(buf, player.getUniqueId()),
    /* Deserializer */ buf -> {
        UUID uuid = SerializationImpl.getInternalDeserializer(UUID.class).apply(buf);
        return Bukkit.getPlayer(uuid);
    }
);

Building

Thanks to paperweight-userdev, building this plugin will cost a lot of RAM. You need about 8 GiB of free RAM to complete the whole compiling process.

To build, run:

./gradlew build --no-daemon

And you'll see the plugin JAR inside the folder build/libs.

Examples

You can refer to these live examples to get inspiration:

Contributing

We welcome your contributions!

Please feel free to open an Issue or a Pull Request.

License

This mod is licensed under MIT License.

About

Easier approach for Paper servers to send & handle custom payloads, simple & powerful.

Topics

Resources

License

Stars

Watchers

Forks

Languages