https://github.com/nlr-devteam/payloadlib
Easier approach for Bukkit to send & handle custom payloads, simple & powerful.
https://github.com/nlr-devteam/payloadlib
Last synced: about 1 year ago
JSON representation
Easier approach for Bukkit to send & handle custom payloads, simple & powerful.
- Host: GitHub
- URL: https://github.com/nlr-devteam/payloadlib
- Owner: NLR-DevTeam
- License: mit
- Created: 2025-07-11T02:58:58.000Z (about 1 year ago)
- Default Branch: main
- Last Pushed: 2025-07-11T09:26:36.000Z (about 1 year ago)
- Last Synced: 2025-07-11T12:33:51.971Z (about 1 year ago)
- Language: Java
- Size: 56.6 KB
- Stars: 1
- Watchers: 0
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- License: LICENSE
Awesome Lists containing this project
README
# 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-data-types).
| [Supported Versions](#supported-versions) | [Basic Usage](#usage) | [Advanced Usage](#advanced-usage) | [Examples](#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`:
```groovy
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:
```yml
# ...
depend: [PayloadLib]
```
Then, declare your payload like this:
```java
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.
```java
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:
```java
PayloadLib.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:
```java
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](/core/src/main/java/top/nlrdev/payloadlib/serialization/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` (or `ResourceKey` 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:
```java
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](#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.
```java
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:
```java
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:
```shell
./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:
- [XIAYM-gh/SittingPlus-uwu-Bukkit](https://github.com/XIAYM-gh/SittingPlus-uwu-Bukkit)
## Contributing
We welcome your contributions!
Please feel free to open an Issue or a Pull Request.
## License
This mod is licensed under [MIT License](/LICENSE).