Appearance
Messages
The Message API is an abstraction over the Fabric Networking API. It uses Kotlinx.Serialization to send messages over the network, without needing to create a NbtElement
or PacketByteBuf
.
TIP
This page assumes you have basic knowledge of Kotlinx Serialization!
Message Basics
The Messages API allows us to send an object over the network, from the Client to the Server and vice-versa. To make a serializable object, it must have the @Serializable
annotation and inherit from ClientMsg
or ServerMsg
.
kotlin
import io.ejekta.kambrik.api.message.ClientMsg
@Serializable
data class TestMsg(val num: Int) : ClientMsg() {
override fun onClientReceived(ctx: MsgContext) {
println("Got num!: $num")
}
}
Registration and usage of these messages is simple.
kotlin
// Registration (one time, in onInitialize):
Kambrik.Messages.registerClientMessage(
TestMsg.serializer(),
Identifier("my_mod", "test_msg")
)
// Usage:
TestMsg(100).sendToClient(some_player)
// or
TestMsg(100).sendToClients(some_players)
WARNING
NOTE: Every message class must have one and only one associated identifier.
Serialization
When creating a ClientMsg
/ServerMsg
, all class properties should be serializable. If they are not, they must be marked as @Transient
.
Serializing Vanilla Classes
We can also serialize a limited selection of classes from Vanilla.
To do so, we mark the property/type with @Contextual
:
kotlin
@Serializable
class TellServerHello(val pos: @Contextual BlockPos) : ServerMsg() {
override fun onServerReceived(ctx: MsgContext) {
println("Hello from $pos!")
}
}
A list of all contextual classes can be found here.
Reference Serializers
We can also reference some values that would otherwise be unserializable, and pass the reference to the other side. For example, Item
classes are not serializable, but we can pass a reference to an Item registry object in a message.
Sending reference serializers works exactly like before, with a @Contextual
annotation. In this example we send the server a Bucket:
kotlin
@Serializable
class BanItem(val item: @Contextual Item) : ServerMsg() {
override fun onServerReceived(ctx: MsgContext) {
println("We got passed a: ${item.name.asString()}!")
}
}
//...
BanItem(Items.BUCKET).sendToServer()
Obviously, this will cause incorrect behaviour if the bucket does not exist on the server side. Because of this, you should stick to using reference serializers only when you know the data exists on both sides.
A list of all reference serializers can be found here.
Usage in Commands
ClientMsg
classes also have a sendToClients
method. We can send this message to whichever players we'd like! The Fabric Networking API has a helper class for this called PlayerLookup
. The following example shows a message being sent to all players that are 'tracking' (within view distance of) the world origin.
kotlin
@Serializable
class PurgeArea : ClientMsg() {
override fun onClientReceived(ctx: MsgContext) {
ctx.client.player?.kill()
}
}
// later on, in a Command DSL:
"purge" runs {
PurgeArea().sendToClients(
PlayerLookup.tracking(it.source.world, BlockPos.ORIGIN)
)
1
}
Examples
This example shows how the Messages API could save us a lot of code:
kotlin
// Message Definition
@Serializable
class PlaceItemAtPosition(
val item: @Contextual Item, val amount: Int, val pos: @Contextual BlockPos
) : ServerMsg() {
override fun onServerReceived(ctx: MsgContext) {
// do placement here
}
}
// Register Message
Kambrik.Message.registerServerMessage(
PlaceItemAtPosition.serializer(),
Identifier("kambrik", "place_item")
)
// Send Message
PlaceItemAtPosition(Items.BUCKET, 3, BlockPos.ORIGIN).sendToServer()
kotlin
// Register packet
ClientPlayNetworking.registerGlobalReceiver(Identifier("kambrik", "place_item")) {
client, handler, buf, responseSender ->
val itemStr = buf.readString()
val item = Registry.ITEM.get(Identifier(itemStr))
val amount = buf.readInt()
val pos = buf.readBlockPos()
client.execute {
// do placement here
}
}
// Create packet
fun createPlacementPacket(item: Item, amount: Int, pos: BlockPos): PacketByteBuf {
return PacketByteBufs.create().apply {
writeString(Registry.ITEM.getId(item).toString())
writeInt(amount)
writeBlockPos(pos)
}
}
// Send packet
ClientPlayNetworking.send(
Identifier("kambrik", "place_item"),
createPlacementPacket(Items.BUCKET, 3, BlockPos.ORIGIN)
)