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.
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.
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.
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, ...);
Note
We only support types with a PacketCodec
in vanilla Minecraft.
Please refer to SerializationImpl.java for more details.
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 |
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.
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));
});
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);
}
}
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);
}
);
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
.
You can refer to these live examples to get inspiration:
We welcome your contributions!
Please feel free to open an Issue or a Pull Request.
This mod is licensed under MIT License.