Party messages

client.party() — in the DSL just party — carries small binary messages between the scripts of everyone in your party, laid out by a shape you declare. An event reaches whoever is online at that moment; a state is the last value you published under a topic, and the server hands it to whoever joins later.

val Appearance = shape("appearance") {
    string(1, "hat")
    int(2, "tint", default = 0xFFFFFF)
}

val cosmetics = party.channel("crashsystem:cosmetics")
val worn = mutableMapOf<String, String>()

cosmetics.onState(Appearance) { message ->
    worn[message.sender().login()] = message.string("hat")
}

cosmetics.onStateCleared(Appearance) { message ->
    worn.remove(message.sender().login())
}

onEnable {
    cosmetics.publish(Appearance, "hat" to "halo")
}

The party

Method Type Description
party.connected() boolean the client has a live socket session (API 4)
party.inParty() boolean you are in a party right now (API 4)
party.self() PartyMember? you, null outside a party (API 4)
party.leader() PartyMember? current leader, null outside a party (API 4)
party.isLeader() boolean you lead this party (API 4)
party.code() String the join code, empty outside a party (API 5)
party.members() List<PartyMember> immutable snapshot, you included (API 4)
party.member(login) PartyMember? member by account login, null when absent (API 4)
party.channel(namespace) PartyChannel opens the channel, or returns the one already open (API 4) (throws ScriptException on a malformed namespace, on one another loaded script holds, or past 8 channels)
party.buffer() PartyPayloadWriter empty writer for packing a bytes field yourself (API 4)
party.reader(data) PartyPayloadReader reader over a bytes field you packed (API 4) (throws ScriptException when data is null)
party.maxEventBytes() int 2048 (API 4)
party.maxStateBytes() int 1024 (API 4)
party.maxStateTopics() int 8 (API 4)

A member

Method Type Description
login() String account login, stable across servers and worlds (API 4)
name() String Minecraft name, changes with the account's nickname (API 4)
leader() boolean leads the party (API 4)
self() boolean this member is you (API 4)
color() int server-assigned member colour, opaque ARGB, 0 when the sender is unknown (API 5)
position() Vec? last known position, null until one arrives (API 5)
positionAge() long ms since that position, 0 for yourself, -1 when there is none (API 5)

Positions arrive twice a second, only for the other members and only while they are online; position() interpolates between the last two and stops at the newest instead of running past it, and your own entry returns the live player position. The colour comes from a ten entry palette, distinct per member for as long as the party lives, so two members draw the same person the same way.

A shape

Declaration Takes Description
shape(topic) { … } declares the shape, the name is the topic (API 4)
bool(id, name) Boolean one varint byte (API 4)
int(id, name) Int zigzag varint, 1 to 5 bytes (API 4)
long(id, name) Long zigzag varint, 1 to 10 bytes (API 4)
float(id, name) Float 4 bytes (API 4)
double(id, name) Double 8 bytes (API 4)
string(id, name) String length plus utf-8, at most 512 bytes (API 4)
bytes(id, name) ByteArray length plus the raw array (API 4)
uuid(id, name) UUID 16 bytes (API 4)
vec(id, name) Vec three doubles, 24 bytes (API 4)
struct(id, name, shape) PartyStruct one nested shape (API 4)
list(id, name, type) List<Any> a list of one PartyFieldType (API 4)
list(id, name, shape) List<PartyStruct> a list of nested shapes (API 4)
ints(id, name) List<Int> a list of INT (API 4)
longs(id, name) List<Long> a list of LONG (API 4)
floats(id, name) List<Float> a list of FLOAT (API 4)
doubles(id, name) List<Double> a list of DOUBLE (API 4)
strings(id, name) List<String> a list of STRING (API 4)
vecs(id, name) List<Vec> a list of VEC (API 4)
uuids(id, name) List<UUID> a list of UUID (API 4)

Every declaration except bytes, uuid, struct and the list forms also takes default = …, used when the sender did not send that field. Ids 1 to 15 cost one byte on the wire. Only the id and the type go on the wire, so writing and reading in different orders is fine and a field the receiver does not declare is skipped by its wire type; reusing or reordering an id makes two versions of a script read each other wrong.

PartyShape

Method Type Description
PartyShape.builder(name) PartyShapeBuilder starts a shape without the DSL (API 4) (throws ScriptException when the name is not lowercase ASCII or is over 32 characters)
shape.name() String the topic this shape names (API 4)
shape.fields() List<PartyField> declared fields, in declaration order (API 4)
shape.field(name) PartyField? field by name, null when absent (API 4)
shape.field(id) PartyField? field by id, null when absent (API 4)
shape.capacity() int highest declared id plus one (API 4)
shape.fieldNames() String names joined by a comma (API 4)
shape.toString() String name {id:field type, …} (API 4)
PartyShape.MAX_FIELD_ID int 2047 (API 4)
PartyShape.MAX_FIELDS int 64 (API 4)
PartyShape.MAX_FIELD_NAME_LENGTH int 32 (API 4)
Shape("field" to value, …) PartyStruct builds a value for a struct or list field (API 4)

PartyShapeBuilder

Method Type Description
boolField(id, field) PartyShapeBuilder Java name behind bool(id, name) (API 4)
boolField(id, field, default) PartyShapeBuilder bool field with a default (API 4)
intField(id, field) PartyShapeBuilder Java name behind int(id, name) (API 4)
intField(id, field, default) PartyShapeBuilder int field with a default (API 4)
longField(id, field) PartyShapeBuilder Java name behind long(id, name) (API 4)
longField(id, field, default) PartyShapeBuilder long field with a default (API 4)
floatField(id, field) PartyShapeBuilder Java name behind float(id, name) (API 4)
floatField(id, field, default) PartyShapeBuilder float field with a default (API 4)
doubleField(id, field) PartyShapeBuilder Java name behind double(id, name) (API 4)
doubleField(id, field, default) PartyShapeBuilder double field with a default (API 4)
stringField(id, field) PartyShapeBuilder Java name behind string(id, name) (API 4)
stringField(id, field, default) PartyShapeBuilder string field with a default (API 4)
bytesField(id, field) PartyShapeBuilder Java name behind bytes(id, name), no default form (API 4)
uuidField(id, field) PartyShapeBuilder Java name behind uuid(id, name), no default form (API 4)
vecField(id, field) PartyShapeBuilder Java name behind vec(id, name) (API 4)
vecField(id, field, default) PartyShapeBuilder vec field with a default (API 4)
structField(id, field, shape) PartyShapeBuilder Java name behind struct(id, name, shape) (API 4) (throws ScriptException when shape is null)
listField(id, field, type) PartyShapeBuilder list of one PartyFieldType (API 4) (throws ScriptException when the type is LIST or STRUCT)
listField(id, field, shape) PartyShapeBuilder list of nested shapes (API 4) (throws ScriptException when shape is null)
build() PartyShape finishes the shape, what shape(topic) { … } calls last (API 4) (throws ScriptException when no field was declared)

Every method returns the builder, and every field declaration throws ScriptException when the id is outside 1..2047, the field name is not ASCII or is over 32 characters, the shape already holds 64 fields, or the name or the id is taken.

PartyField

Method Type Description
field.id() int wire id, 1..2047 (API 4)
field.name() String field name, never sent on the wire (API 4)
field.type() PartyFieldType declared type (API 4)
field.element() PartyFieldType? list element type, null unless the field is a list (API 4)
field.shape() PartyShape? nested shape, null unless struct or list of structs (API 4)
field.hasDefault() boolean a default was declared (API 4)
field.defaultValue() Object? the declared default, null when there is none (API 4)
field.typeLabel() String list of vec, the nested shape name, or the type (API 4)
field.toString() String name (id N, type) (API 4)

PartyFieldType

Value Type Description
BOOL PartyFieldType varint, 0 or 1 (API 4)
INT PartyFieldType zigzag varint (API 4)
LONG PartyFieldType zigzag varint (API 4)
FLOAT PartyFieldType fixed 4 bytes (API 4)
DOUBLE PartyFieldType fixed 8 bytes (API 4)
STRING PartyFieldType length plus utf-8, at most 512 bytes (API 4)
BYTES PartyFieldType length plus the raw bytes (API 4)
UUID PartyFieldType length plus 16 bytes (API 4)
VEC PartyFieldType length plus three doubles (API 4)
STRUCT PartyFieldType length plus a nested shape (API 4)
LIST PartyFieldType length plus a count and the elements (API 4)
type.wire() PartyWire the wire type this one maps to (API 4)
type.label() String lowercase constant name (API 4)

PartyWire

Value Type Description
VARINT PartyWire code 0 (API 4)
FIXED64 PartyWire code 1 (API 4)
LENGTH PartyWire code 2, length prefixed (API 4)
FIXED32 PartyWire code 5 (API 4)
wire.code() int the 3-bit code packed into a field tag (API 4)

PartyStruct

Method Type Description
PartyStruct(shape) PartyStruct empty value of that shape (API 4) (throws ScriptException when shape is null)
struct.set(name, value) PartyStruct sets one field (API 4) (throws ScriptException on an unknown field or a null value)
struct.shape() PartyShape the shape this value belongs to (API 4)
struct.values() Map<String, Object> the fields set so far, in insertion order (API 4)

A channel

Method Type Description
channel.namespace() String namespace this channel was opened on (API 4)
channel.send(shape, "field" to value, …) PartySendResult sends an event to the whole party (API 4)
channel.send(shape, …, target = target) PartySendResult same, to one target (API 4)
channel.send(shape, target) { set(name, value) } PartySendResult block form of the same event (API 4)
channel.publish(shape, "field" to value, …) PartySendResult publishes state under the shape's topic (API 4)
channel.publish(shape) { set(name, value) } PartySendResult block form of the same publish (API 4)
channel.clearState(shape) PartySendResult drops that state on every member and on the server (API 4)
channel.clearStates() void clears every state this channel published (API 4)
channel.publishedStates() List<String> topics this channel currently holds state on (API 4)
channel.onEvent(shape, handler) Subscription handler for events on that shape's topic (API 4)
channel.onEvent(shape, rule, handler) Subscription same, filtered by SenderRule (API 4)
channel.onState(shape, handler) Subscription handler for state, also replays every sender's last known value (API 4)
channel.onState(shape, rule, handler) Subscription same, filtered by SenderRule (API 4)
channel.onStateCleared(shape, handler) Subscription handler for a cleared state (API 4)
channel.onStateCleared(shape, rule, handler) Subscription same, filtered by SenderRule (API 4)

A namespace is author:feature: lowercase ASCII [a-z0-9][a-z0-9._-]* on both sides of the colon, at most 48 characters, no empty half. One loaded script holds a namespace at a time. Handlers run on the client thread, one message at a time, inside the same error handling and CPU budget as any other script handler, and only while the script is loaded and enabled.

PartyChannel

Method Type Description
channel.sendEvent(shape, body) PartySendResult Java name behind send(shape, …) (API 4)
channel.sendEvent(shape, target, body) PartySendResult Java name behind send(shape, …, target = …) (API 4)
channel.publishState(shape, body) PartySendResult Java name behind publish(shape, …) (API 4)
channel.clearState(topic) PartySendResult topic form, for a shape you do not declare (API 4) (throws ScriptException on a malformed topic)
channel.onStateCleared(topic, handler) Subscription topic form of the cleared-state handler (API 4)
channel.onStateCleared(topic, rule, handler) Subscription same, filtered by SenderRule (API 4)

PartyShapedWriter

Method Type Description
writer.shape() PartyShape the shape being written (API 4)
writer.set(name, value) PartyShapedWriter writes one field (API 4) (throws ScriptException on an unknown field, a null value, a wrong type, a field set twice, or a payload that no longer fits)
writer.size() int bytes written, including the 1-byte format marker (API 4)
writer.remaining() int bytes left before the limit (API 4)
writer.limit() int 2048 for an event, 1024 for state (API 4)

The writer is the receiver of the { set(name, value) } block. The order of set calls does not matter, and a field left unset never goes on the wire, so the receiver reads its declared default.

Choosing who receives

Method Type Description
PartyTarget.all() PartyTarget every other online member; you never receive your own broadcast (API 4)
PartyTarget.leader() PartyTarget the current leader only, nobody when you are the leader (API 4)
PartyTarget.member(login) PartyTarget one member by account login (API 4)
PartyTarget.member(member) PartyTarget one member by PartyMember, a null member sends INVALID_TARGET (API 4)
target.kind() PartyTargetKind ALL, LEADER or MEMBER (API 4)
target.login() String? login for MEMBER, null otherwise (API 4)
target.toString() String all, leader or member(login) (API 4)

State is always party-wide: publish and clearState take no target.

PartyTargetKind

Value Type Description
ALL PartyTargetKind every other member (API 4)
LEADER PartyTargetKind the leader only (API 4)
MEMBER PartyTargetKind one named member (API 4)

Filtering who sent it

Value Type Description
SenderRule.ANY_MEMBER SenderRule default, any member of your party (API 4)
SenderRule.LEADER_ONLY SenderRule only what the server marked as sent by the leader (API 4)

The result of a send

Value Type Description
SENT PartySendResult handed to the socket (API 4)
COALESCED PartySendResult replaced a state publish that had not gone out yet (API 4)
NOT_CONNECTED PartySendResult no socket session; a state publish is remembered and republished on reconnect (API 4)
NOT_IN_PARTY PartySendResult you are in no party; a state publish is remembered and republished on join (API 4)
INVALID_TARGET PartySendResult the target login is not a current party member (API 4)
TOO_LARGE PartySendResult payload over the limit for its kind (API 4)
STATE_LIMIT PartySendResult you already hold 8 state topics (API 4)
RATE_LIMITED PartySendResult the client rate limiter dropped it (API 4)
result.ok() boolean true for SENT and COALESCED (API 4)

The message

Method Type Description
sender() PartyMember filled in by the server, never by the payload (API 4)
namespace() String namespace it arrived on (API 4)
topic() String topic it arrived on (API 4)
kind() PartyMessageKind EVENT, STATE_SET or STATE_CLEAR (API 4)

sender().self() is always false on a received message. An onEvent or onState handler receives a PartyShapedMessage, which is this table plus the decoded fields below; an onStateCleared handler receives this table only.

PartyMessageKind

Value Type Description
EVENT PartyMessageKind one-shot, never stored (API 4)
STATE_SET PartyMessageKind the last value for a topic (API 4)
STATE_CLEAR PartyMessageKind a stored state was dropped (API 4)

Reading fields

Method Type Description
shape() PartyShape the shape it was decoded with (API 4)
has(name) boolean the sender actually sent this field (API 4)
bool(name) boolean (API 4)
int(name) int (API 4)
long(name) long (API 4)
float(name) float (API 4)
double(name) double (API 4)
string(name) String (API 4)
bytes(name) ByteArray fresh array (API 4)
uuid(name) UUID (API 4)
vec(name) Vec (API 4)
struct(name) PartyFields one nested shape, read with these same methods (API 4)
structs(name) List<PartyFields> a list of nested shapes (API 4)
ints(name) List<Int> a list of int (API 4)
floats(name) List<Float> a list of float (API 4)
doubles(name) List<Double> a list of double (API 4)
strings(name) List<String> a list of string (API 4)
vecs(name) List<Vec> a list of vec (API 4)
dump() String every field as one line, defaults marked (API 4)

A field the sender left out returns its declared default; without a default it throws ScriptException naming the field. Asking for the wrong type throws too, and so does a name the shape does not declare.

PartyFields

Method Type Description
booleanValue(name) boolean Java name behind bool(name) (API 4)
intValue(name) int Java name behind int(name) (API 4)
longValue(name) long Java name behind long(name) (API 4)
floatValue(name) float Java name behind float(name) (API 4)
doubleValue(name) double Java name behind double(name) (API 4)
values(name) List<Object> boxed list field, behind ints floats doubles strings vecs structs (API 4)

Packing bytes yourself

Method Type Description
writeBoolean(value) PartyPayloadWriter 1 byte (API 4)
writeByte(value) PartyPayloadWriter 1 byte, low 8 bits of the int (API 4)
writeShort(value) PartyPayloadWriter 2 bytes, big endian (API 4)
writeInt(value) PartyPayloadWriter 4 bytes, big endian (API 4)
writeLong(value) PartyPayloadWriter 8 bytes, big endian (API 4)
writeFloat(value) PartyPayloadWriter 4 bytes (API 4)
writeDouble(value) PartyPayloadWriter 8 bytes (API 4)
writeString(value) PartyPayloadWriter 2-byte length plus utf-8, at most 512 bytes (API 4) (throws ScriptException when null or longer)
writeBytes(value) PartyPayloadWriter 2-byte length plus a copy of the array (API 4) (throws ScriptException when null)
writeUuid(value) PartyPayloadWriter 16 bytes (API 4) (throws ScriptException when null)
toByteArray() ByteArray what you wrote, ready for a bytes field (API 4)
size() int bytes written (API 4)
remaining() int bytes left before the limit (API 4)
limit() int 2048 from party.buffer() (API 4)

A bytes field carries whatever you put in it: party.buffer() hands out the writer, party.reader(data) reads it back, and both are positional, so read order has to match write order. Any write past the limit throws ScriptException.

PartyPayloadReader

Method Type Description
readBoolean() boolean 1 byte, non-zero is true (API 4)
readByte() byte one signed byte (API 4)
readShort() short 2 bytes, big endian (API 4)
readInt() int 4 bytes, big endian (API 4)
readLong() long 8 bytes, big endian (API 4)
readFloat() float 4 bytes (API 4)
readDouble() double 8 bytes (API 4)
readString() String 2-byte length plus utf-8 (API 4)
readBytes() ByteArray 2-byte length plus the bytes (API 4)
readUuid() UUID 16 bytes (API 4)
size() int payload length (API 4)
remaining() int bytes not read yet (API 4)
hasMore() boolean whether any bytes are left (API 4)

Reading past the end throws ScriptException; the payload is a plain array, so a truncated or reordered read gives garbage rather than an error.

Limits

Limit Value Description
Event payload 2048 bytes per message
State payload 1024 bytes per topic
String inside a payload 512 bytes, and it counts toward the payload budget
Fields in a shape 64
Field id 1..2047 1 to 15 take one byte on the wire
Field name 32 ASCII characters
State topics 8 per account, across all your scripts
Namespace 48 ASCII characters
Topic 32 ASCII characters, the shape name is the topic
Channels 8 per script
State lifetime 5 min since the last publish, unless republished

Sends beyond the client rate limiter come back RATE_LIMITED; the server keeps its own, stricter budgets and drops what exceeds them without an error. State publishes coalesce per topic, so publishing on every tick costs one packet every few ticks and only the newest value goes out.

What the server decides

Fact Description
Payload opaque bytes; the server never reads a field or knows your shape
Sender derived from the authenticated session, so a payload cannot claim to be someone else
Party derived from the sender's current party; a message never leaves it
Leader derived from the party, so SenderRule.LEADER_ONLY cannot be spoofed
Recipients derived from the party; the client never sends a recipient list
State snapshot sent to a member on join, with events never replayed
State cleanup on leave, kick, disband, disconnect and after the lifetime above

A namespace is a routing name, not an identity: it proves nothing about which script sent a message, only which account did.

Last updated