Documentation
¶
Overview ¶
Package protocol implements the Minecraft wire format: framing, the primitive encodings, chunk decoding, and the per-version tables that say which packet ID means what.
It knows nothing about playing the game. Everything here is about getting bytes on and off a socket correctly, which is why it is separable from the package that decides what to do with them.
Files ¶
conn.go framing, compression, the read/write halves of a connection varint.go VarInt and VarLong, the two encodings everything else rests on reader.go bounded field decoding; accumulates the first error writer.go field encoding chunk.go paletted chunk containers and block-state lookup version.go Version, VersionSpec, and the per-version lookups registry.go the name and protocol-number registries constants.go wire constants: packet states, dig statuses, slot indices names.go namespacing helpers for item and entity names uuid.go offline-mode UUID derivation
The generated tables are not here. They are ~9,700 lines across three files, which in one directory buries the ten hand-written ones above; they live in protocol/versions instead.
Versions ¶
Almost everything that changes between Minecraft releases is a number in a table rather than a branch in code. Packet IDs are dense indices that shift whenever Mojang inserts a packet; entity and item names are indexed by wire ID; block-state classification is a set of ranges. Those tables are generated from minecraft-data by internal/gen/genversion.mjs into protocol/versions, where they register themselves from init.
So this package defines the machinery and protocol/versions supplies the data. Importing protocol alone gives you an empty registry — something must import protocol/versions for ByName and ByProtocol to resolve anything. The understudy package does that for you; a program using protocol directly needs the blank import itself.
Keeping the data out means a test, or a tool that speaks one version, can build exactly the Version it needs through NewVersion without linking in three full tables.
The handful of genuine format differences that cannot be expressed as a table live in ChunkFormat, and each is documented with the symptom it causes when it is wrong. They share a shape: nothing errors at the mistake, and a short read surfaces several sections downstream.
Decoding ¶
Reader accumulates the first error and keeps going, so a packet decoder reads every field and checks once at the end rather than after each. Every length that arrives from the wire is bounded before it is used to size an allocation or as a divisor — see MaxStringLen, MaxSections and MaxBitsPerEntry. That is not defensive programming for its own sake: a desynced stream puts arbitrary bytes where a length prefix should be, and an unbounded one turns a single corrupt packet into an out-of-memory kill or a division by zero.
Concurrency ¶
A Conn may be written from any goroutine; reads are single-threaded by contract, since there is one read loop. A Version is immutable once registered and safe to share. Reader and Writer are not safe for concurrent use, and neither are ChunkColumn and ChunkSection — the caller holding those holds the lock.
Index ¶
- Constants
- Variables
- func AppendVarInt(dst []byte, v int32) []byte
- func AppendVarLong(dst []byte, v int64) []byte
- func BareName(name string) string
- func DecodeBlockPos(v int64) (x, y, z int32)
- func Names() []string
- func Namespaced(name string) string
- func ReadVarInt(r io.ByteReader) (int32, error)
- func ReadVarLong(r io.ByteReader) (int64, error)
- func Register(v *Version)
- func ValidFace(face int32) bool
- func VarIntLen(v int32) int
- type Box
- type ChunkColumn
- type ChunkFormat
- type ChunkSection
- type ComponentEncoding
- type Conn
- type Packet
- type PacketIDs
- type Reader
- func (r *Reader) Bool() bool
- func (r *Reader) Err() error
- func (r *Reader) F32() float32
- func (r *Reader) F64() float64
- func (r *Reader) Fail(err error)
- func (r *Reader) I8() int8
- func (r *Reader) I16() int16
- func (r *Reader) I32() int32
- func (r *Reader) I64() int64
- func (r *Reader) ReadByte() (byte, error)
- func (r *Reader) Remaining() []byte
- func (r *Reader) Skip(n int)
- func (r *Reader) String() string
- func (r *Reader) U8() uint8
- func (r *Reader) UUID() UUID
- func (r *Reader) VarInt() int32
- func (r *Reader) VarLong() int64
- type State
- type UUID
- type Version
- func (v *Version) BlocksMovement(state int32) bool
- func (v *Version) CollisionHeight(state int32) float64
- func (v *Version) CollisionShape(state int32) []Box
- func (v *Version) ComponentEncoding() (ComponentEncoding, bool)
- func (v *Version) ComponentKind(wire int32) (int32, bool)
- func (v *Version) EffectName(id int32) string
- func (v *Version) EntityTypeName(id int32) string
- func (v *Version) HasCollisionData(state int32) bool
- func (v *Version) HasComponentIDs() bool
- func (v *Version) IsAir(state int32) bool
- func (v *Version) IsFluid(state int32) bool
- func (v *Version) IsFullCube(state int32) bool
- func (v *Version) IsLava(state int32) bool
- func (v *Version) IsSolid(state int32) bool
- func (v *Version) IsTargetable(state int32) bool
- func (v *Version) IsWater(state int32) bool
- func (v *Version) ItemID(name string) (int32, bool)
- func (v *Version) ItemName(id int32) string
- func (v *Version) SlotDisplayKind(wire int32) (int32, bool)
- func (v *Version) StackSize(id int32) int32
- func (v *Version) StackSizeOf(name string) int32
- func (v *Version) SupportsAttackPacket() bool
- type VersionSpec
- type Writer
- func (w *Writer) BlockPos(x, y, z int32) *Writer
- func (w *Writer) Bool(v bool) *Writer
- func (w *Writer) Bytes() []byte
- func (w *Writer) F16(v float32) *Writer
- func (w *Writer) F32(v float32) *Writer
- func (w *Writer) F64(v float64) *Writer
- func (w *Writer) I8(v int8) *Writer
- func (w *Writer) I16(v int16) *Writer
- func (w *Writer) I32(v int32) *Writer
- func (w *Writer) I64(v int64) *Writer
- func (w *Writer) String(v string) *Writer
- func (w *Writer) U8(v uint8) *Writer
- func (w *Writer) U16(v uint16) *Writer
- func (w *Writer) UUID(v UUID) *Writer
- func (w *Writer) VarInt(v int32) *Writer
- func (w *Writer) VarLong(v int64) *Writer
Constants ¶
const ( OverworldMinY int32 = -64 NetherMinY int32 = 0 )
OverworldMinY and NetherMinY are the two dimension floors this client sees. The floor is not carried in the chunk packet, so it is inferred from the section count rather than by decoding the dimension registry.
const ( ClientCommandPerformRespawn int32 = 0 ClientCommandRequestStats int32 = 1 )
ClientCommand action IDs, the argument to the client_command packet.
const ( DigStart int32 = 0 DigCancel int32 = 1 DigFinish int32 = 2 DigDropStack int32 = 3 DigDropItem int32 = 4 DigReleaseUse int32 = 5 DigSwapOffhand int32 = 6 )
BlockDig statuses, the first field of the block_dig packet.
Breaking a block is a *pair*: DigStart then DigFinish. The server measures the interval against the expected break time for the block and the held tool, so sending only one of them, or sending them too close together, leaves the block standing with no error reported.
const ( FaceBottom int32 = 0 // -Y FaceTop int32 = 1 // +Y FaceNorth int32 = 2 // -Z FaceSouth int32 = 3 // +Z FaceWest int32 = 4 // -X FaceEast int32 = 5 // +X )
Block faces, as used by dig and place. The face decides which side a block is placed against, so it is not cosmetic.
const ( MainHand int32 = 0 OffHand int32 = 1 )
Hands.
const ( MovementOnGround uint8 = 1 << 0 MovementHasHorizontalCollision uint8 = 1 << 1 )
MovementFlags bits, the trailing field of the movement packets.
const ( InputForward uint8 = 1 << 0 InputBackward uint8 = 1 << 1 InputLeft uint8 = 1 << 2 InputRight uint8 = 1 << 3 InputJump uint8 = 1 << 4 InputSneak uint8 = 1 << 5 InputSprint uint8 = 1 << 6 )
PlayerInput bitflags. Sneaking moved here in 26.1 — entity_action no longer carries start/stop_sneaking, so a client that looks for it there finds nothing and silently never sneaks.
const Absent int32 = -1
Absent is the ID of a packet a version does not have.
const AirState int32 = 0
AirState is the block state ID for air, which is 0 in every version.
const CompressionDisabled = -1
CompressionDisabled is the threshold value meaning "no compression yet". The server switches compression on mid-login by sending set_compression, so this is the starting state of every connection.
const DefaultNamespace = "minecraft"
DefaultNamespace is the namespace every vanilla identifier lives in.
const DefaultStackSize int32 = 64
DefaultStackSize is what an unrecognised item is assumed to stack to.
const FaceCount int32 = 6
FaceCount is how many block faces there are; a face is always 0..5.
const MaxBitsPerEntry uint8 = 32
MaxBitsPerEntry caps the entry width of a paletted container.
Direct encoding needs ceil(log2(stateCount)) bits, which is 15 for every version this client speaks. The cap exists because bitsPerEntry arrives from the wire and is used as a divisor: a corrupt or hostile value above 64 makes 64/bits zero, and the next division panics the whole process. Rejecting it here keeps a malformed chunk a dropped packet rather than a crash.
const MaxPacketSize = 1 << 23 // 8 MiB
MaxPacketSize bounds a single frame. The server should never send anything close to this; the cap exists so a desynced length prefix fails fast instead of making the client allocate gigabytes.
const MaxSections = 64
MaxSections bounds how tall a decoded column may be. The overworld is 24 sections and no vanilla dimension exceeds that; the cap stops a malformed blob from being read as an unbounded run of sections.
const MaxStringLen = 32767 * 4
MaxStringLen bounds a decoded string.
The protocol's own limit is 32767 characters, which is at most four bytes each. Anything longer is a desynced stream rather than a real field, and without a cap the length prefix — which is whatever bytes happened to land at that offset — decides how much memory this client allocates.
const RelativeMoveUnit = 1.0 / 4096.0
RelativeMoveUnit converts the i16 deltas in the relative-move packets to blocks. Entity movement is sent in 1/4096ths of a block, so treating the raw value as blocks puts entities thousands of blocks away.
const SectionHeight = 16
SectionHeight is the edge length of a chunk section: 16×16×16 blocks.
const ShapeUnit = 32
ShapeUnit is how many parts of a block a Box coordinate counts in.
Vanilla geometry is built from sixteenths, with a handful of thirty-seconds (the amethyst buds), so 1/32 represents every shape exactly. Storing integer units rather than floats keeps the tables four times smaller and, more usefully, makes comparisons exact: a slab is 16 units tall, never 0.49999.
Variables ¶
var FullCube = Box{0, 0, 0, ShapeUnit, ShapeUnit, ShapeUnit}
FullCube is the shape of an ordinary block. Named so comparisons against it read as an assertion rather than a magic literal.
Functions ¶
func AppendVarInt ¶
AppendVarInt appends the VarInt encoding of v to dst.
func AppendVarLong ¶
AppendVarLong appends the VarLong encoding of v to dst.
func BareName ¶
BareName strips the namespace from an identifier: "minecraft:oak_log" becomes "oak_log". An unqualified name is returned unchanged.
func DecodeBlockPos ¶
DecodeBlockPos unpacks the 64-bit block position form written by Writer.BlockPos: x:26, z:26, y:12, each signed. The shifts sign-extend by shifting left then arithmetic-right, which is why they are written as a pair rather than a mask.
func Namespaced ¶
Namespaced qualifies a bare identifier with the vanilla namespace, leaving an already-qualified one alone: "zombie" becomes "minecraft:zombie", while "mypack:widget" is returned unchanged.
Every lookup keyed on a wire name goes through here. Callers hand this package names typed by a human ("dirt", "diamond_pickaxe"), while the wire only ever carries the qualified form — normalising in one place is what keeps a bare name from silently matching nothing.
func ReadVarLong ¶
func ReadVarLong(r io.ByteReader) (int64, error)
ReadVarLong reads a VarLong from r.
func Register ¶
func Register(v *Version)
Register adds a version to the registry. It panics on a duplicate name or protocol number, which can only be a build-time mistake in the generated tables — two versions claiming one protocol number would otherwise make auto-detection silently pick whichever registered last.
func ValidFace ¶
ValidFace reports whether a face value addresses a real block side.
Worth checking at any boundary that accepts one from outside: the wire encodes the face as a single signed byte, so an out-of-range value is truncated rather than rejected — face 260 becomes face 4, and the block is placed against a side the caller never named.
Types ¶
type Box ¶
type Box [6]int8
Box is one axis-aligned collision box in units of 1/ShapeUnit of a block, relative to the block's own corner.
Coordinates can fall outside 0..ShapeUnit: fences and walls stand 1.5 blocks tall so their collision reaches 48, and a few shapes start slightly negative. Anything treating a box as clamped to its own block will get those wrong.
type ChunkColumn ¶
type ChunkColumn struct {
X, Z int32
MinY int32
Sections []*ChunkSection
}
ChunkColumn is a 16×N×16 column of sections at a chunk coordinate.
func ParseChunkData ¶
func ParseChunkData(v *Version, x, z int32, data []byte) (*ChunkColumn, error)
ParseChunkData decodes the chunkData blob of a map_chunk packet.
Sections are read until the buffer is exhausted rather than from a declared count, because the count depends on the dimension's height and this client deliberately never decodes the dimension registry.
func (*ChunkColumn) BlockState ¶
func (c *ChunkColumn) BlockState(x, y, z int32) int32
BlockState returns the state at absolute world coordinates, or air if the coordinate falls outside the column.
func (*ChunkColumn) SetBlockState ¶
func (c *ChunkColumn) SetBlockState(x, y, z, state int32)
SetBlockState overwrites a single block, for the block-update packets.
A uniform (bitsPerEntry 0) section cannot represent two different states, so it is expanded to a direct-encoded section first. Sections are almost always uniform air, and a single placed block is exactly the case that breaks that assumption.
type ChunkFormat ¶
ChunkFormat captures the parts of the chunk encoding that changed between versions. These are the three that actually bite:
- HasSizePrefix: before 1.21.5 each paletted container carried a VarInt count of longs. From 1.21.5 it is computed instead, saving a byte.
- HasFluidCount: from 26.1 each section carries a second int16 after the solid block count.
- NBTHeightmaps: before 1.21.5 the heightmaps in a chunk packet are a single (nameless) NBT compound. From 1.21.5 they are a prefixed array of {VarInt type, prefixed array of long}. Nothing here reads heightmaps, but they sit between the coordinates and the chunk data, so walking them with the wrong shape puts the data blob at the wrong offset.
All three are invisible until they are wrong, and then they surface as a short read several sections downstream — nowhere near the actual mistake. The 1.21.5 chunk rework moved two of them at once, which is why HasSizePrefix and NBTHeightmaps share a threshold.
type ChunkSection ¶
type ChunkSection struct {
// contains filtered or unexported fields
}
ChunkSection is one 16³ cube of block states.
The states are kept in the wire's own paletted form rather than expanded to 4096 int32s per section. A loaded view distance is hundreds of sections, and expanding them all would cost tens of megabytes to answer questions about a handful of blocks.
A ChunkSection is not safe for concurrent use; callers hold the lock.
func (*ChunkSection) BlockState ¶
func (s *ChunkSection) BlockState(x, y, z int32) int32
BlockState returns the state at a local coordinate, each 0..15.
type ComponentEncoding ¶
type ComponentEncoding struct {
// NestedStacksCountFirst: an item stack held inside a component leads with
// its count rather than its id, and an empty container slot is a zero count
// rather than an absent optional. Affects container, charged_projectiles,
// bundle_contents and use_remainder.
NestedStacksCountFirst bool
// TagsAreBareStrings: a component whose value is a tag writes the tag name
// on its own, where 26.1 wraps it in a holder set — a count of zero
// followed by the name. Affects damage_resistant and
// provides_banner_patterns.
TagsAreBareStrings bool
// RegistryRefsHaveLeadingFlag: six components write a byte before their
// registry reference, which is 1 in every sample seen. Affects damage_type,
// instrument, jukebox_playable, provides_trim_material, chicken/variant and
// zombie_nautilus/variant — and no other reference, so break_sound, trim
// and banner_patterns are untouched. True on 1.21.11 and, notably, false on
// 1.21.4, so the older version is not simply "more of the same".
RegistryRefsHaveLeadingFlag bool
// EntityDataKeepsTypeInNBT: entity_data, bucket_entity_data,
// block_entity_data and the bees inside a hive carry their type inside the
// compound rather than hoisted in front of it. A pig spawn egg is 30 bytes
// of nbt on 1.21.4 and 11 with the type pulled out on 26.1.
EntityDataKeepsTypeInNBT bool
// LegacyBlockPredicates: can_place_on and can_break hold predicates that
// match on blocks, state and nbt only — no component matchers at all — and
// the list is followed by a show-in-tooltip bool, which later versions
// moved out into tooltip_display. A pickaxe restricted to dirt and stone is
// eight bytes here and nine on 26.1.
LegacyBlockPredicates bool
// EquippableHasNoShearing: equippable stops after three flags, without the
// equip-on-interact and shearable flags or the shearing sound that later
// versions add. Twelve bytes becomes nine.
EquippableHasNoShearing bool
// TrimHasTooltipFlag: an armour trim carries the show-in-tooltip bool that
// later versions moved out into tooltip_display.
TrimHasTooltipFlag bool
// ProfileHasNoVariantTag: a profile leads straight into its optional name
// rather than with the partial/resolved discriminator, so a head named
// Notch starts `01 05 Notch` rather than `00 01 05 Notch`.
ProfileHasNoVariantTag bool
}
ComponentEncoding describes the ways component payloads differ between versions, as measured rather than as guessed.
Every field is false for 26.1 and true for 1.21.11 and 1.21.4. They were found by putting the same items on servers of each version and comparing the bytes: of sixty-seven components sampled on both, fifty-one are byte for byte identical and the rest fall into these three groups.
type Conn ¶
type Conn struct {
// contains filtered or unexported fields
}
Conn is a framed Minecraft connection.
It deliberately knows nothing about packet semantics — only how to get bytes on and off the wire. Reads are single-threaded by contract (one read loop), but writes are mutex-guarded because keep-alive and command traffic are generated from different goroutines.
func NewConn ¶
NewConn wraps an already-established connection.
Exported so the framing layer can be driven over an in-memory pipe — a fake server in a test, or a proxy — without going through Dial and a real socket.
func (*Conn) CompressionThreshold ¶
CompressionThreshold returns the current threshold, or CompressionDisabled.
func (*Conn) ReadPacket ¶
ReadPacket reads one frame.
Uncompressed framing is [VarInt length][VarInt id][payload]. Once compression is on it becomes [VarInt packetLength][VarInt dataLength][body], where dataLength == 0 means the body is stored raw (it was under the threshold) and any other value is the *decompressed* size of a zlib body. Treating a 0 marker as a zlib stream is the classic bug here, so the two cases are handled explicitly.
func (*Conn) RemoteAddr ¶
RemoteAddr returns the server address.
func (*Conn) SetCompressionThreshold ¶
SetCompressionThreshold enables (>= 0) or disables (< 0) compression. It is safe to call while reads and writes are in flight.
func (*Conn) SetReadDeadline ¶
SetReadDeadline bounds how long the next read may block.
func (*Conn) WritePacket ¶
WritePacket writes an encoded payload (which already begins with the packet ID VarInt) as one frame. It is safe to call from multiple goroutines: the control API writes while the read loop runs.
type PacketIDs ¶
type PacketIDs struct {
SBHandshake int32
SBLoginStart int32
SBLoginAcknowledged int32
CBLoginDisconnect int32
CBLoginEncryptionBegin int32
CBLoginSuccess int32
CBLoginCompress int32
SBConfigSettings int32
SBConfigFinishConfiguration int32
SBConfigKeepAlive int32
SBConfigPong int32
SBConfigSelectKnownPacks int32
SBConfigAcceptCodeOfConduct int32
CBConfigDisconnect int32
CBConfigFinishConfiguration int32
CBConfigKeepAlive int32
CBConfigPing int32
CBConfigSelectKnownPacks int32
CBConfigCodeOfConduct int32
SBPlayTeleportConfirm int32
SBPlayAttack int32
SBPlayChatMessage int32
SBPlayClientCommand int32
SBPlayUseEntity int32
SBPlayUseItem int32
SBPlayKeepAlive int32
SBPlayPosition int32
SBPlayPositionLook int32
SBPlayLook int32
SBPlayBlockDig int32
SBPlayHeldItemSlot int32
SBPlayArmAnimation int32
SBPlayBlockPlace int32
SBPlayWindowClick int32
SBPlayCloseWindow int32
SBPlaySelectTrade int32
SBPlayCraftRecipeRequest int32
SBPlayContainerButton int32
SBPlayNameItem int32
SBPlaySetBeaconEffect int32
SBPlaySetCreativeSlot int32
SBPlayPlayerInput int32
SBPlayEntityAction int32
SBPlayChunkBatchReceived int32
SBPlayPlayerLoaded int32
CBPlaySpawnEntity int32
CBPlayBlockChange int32
CBPlayKickDisconnect int32
CBPlayUnloadChunk int32
CBPlayKeepAlive int32
CBPlayMapChunk int32
CBPlayLogin int32
CBPlayRelEntityMove int32
CBPlayEntityMoveLook int32
CBPlayEntityTeleport int32
CBPlayDeathCombatEvent int32
CBPlayPosition int32
CBPlayEntityDestroy int32
CBPlayMultiBlockChange int32
CBPlayRespawn int32
CBPlayUpdateHealth int32
CBPlayGameStateChange int32
CBPlayEntityEffect int32
CBPlayRemoveEntityEffect int32
CBPlayWindowItems int32
CBPlaySetSlot int32
CBPlayOpenWindow int32
CBPlayCloseWindow int32
CBPlayTradeList int32
CBPlayRecipeBookAdd int32
CBPlayHeldItemSlot int32
CBPlayCollect int32
CBPlayChunkBatchStart int32
CBPlayChunkBatchFinished int32
}
PacketIDs holds every packet ID this client uses, for one protocol version.
IDs are dense indices that shift whenever Mojang inserts a packet, so they cannot be constants. A field of -1 means the packet does not exist in that version; since real IDs are non-negative, an absent packet simply never matches a dispatch and never gets sent.
type Reader ¶
type Reader struct {
// contains filtered or unexported fields
}
Reader decodes a packet payload. Every read is bounds-checked and the first failure is sticky: once Err is set, later reads return zero values instead of panicking. That lets a decoder read a whole packet and check the error once at the end rather than after every field.
A Reader is not safe for concurrent use.
func NewReader ¶
NewReader returns a Reader over buf. The buffer is not copied, so it must not be modified while the Reader is in use.
func (*Reader) Fail ¶
Fail records an error from a caller that decoded something impossible.
The reader itself only catches short reads. A length that is well-formed but cannot describe the bytes remaining is a decode error the caller can see and the reader cannot, and it has to stop the read the same way.
func (*Reader) ReadByte ¶
ReadByte satisfies io.ByteReader so the VarInt helpers can read from here.
func (*Reader) Remaining ¶
Remaining returns the undecoded tail of the payload, or nil once a read has failed. It aliases the underlying buffer.
func (*Reader) Skip ¶
Skip advances the cursor past n bytes without decoding them.
For fields this client does not need but must still step over exactly — the pre-1.21.5 NBT heightmaps being the case it exists for. It shares take's bounds check, so a length that overruns the buffer sets the error rather than moving the cursor somewhere impossible.
func (*Reader) String ¶
String reads a length-prefixed UTF-8 string, rejecting implausible lengths rather than allocating whatever the prefix claims. See MaxStringLen.
type State ¶
type State int
State is a protocol state. The packet ID namespace is scoped to the state, so 0x00 means different things in each — decoding without tracking state is the classic way to misread a stream.
The four protocol states, in the order a connection passes through them.
type UUID ¶
type UUID [16]byte
UUID is a raw 128-bit Minecraft UUID in wire order.
func OfflineUUID ¶
OfflineUUID derives the UUID an offline-mode server assigns to a username.
Vanilla computes `UUID.nameUUIDFromBytes(("OfflinePlayer:"+name).getBytes(UTF_8))`, a plain RFC-4122 v3 (MD5) UUID. The bot has to derive the identical value, because on an offline-mode server the UUID *is* the player's identity: every statistic, advancement and permission the server records is keyed by it.
Get this wrong and the bot plays perfectly while anything checking up on it looks at a player who does not exist. Any other tool that addresses the same player must derive the UUID the same way.
type Version ¶
type Version struct {
Name string
Protocol int32
Chunk ChunkFormat
Packets PacketIDs
// contains filtered or unexported fields
}
Version is everything this client needs to know that varies between Minecraft versions.
A Version is immutable once registered and is shared by every Client speaking that version, so all its methods are safe for concurrent use.
func ByProtocol ¶
ByProtocol looks up a version by its wire protocol number. This is what a server-list ping reports, so it is the entry point for auto-detection.
func NewVersion ¶
func NewVersion(spec VersionSpec) *Version
NewVersion builds a Version from a spec. It does not register it; call Register for that.
func (*Version) BlocksMovement ¶
BlocksMovement reports whether a state has any collision at all.
Deliberately distinct from IsSolid, which answers from the coarse boundingBox ranges. Where they disagree the shape is right — IsSolid calls a fence solid and a slab solid without distinguishing them, and calls a pressure plate solid when it has no collision worth the name.
func (*Version) CollisionHeight ¶
CollisionHeight returns how far up a block a player would be lifted by standing on it, in blocks, measured from the block's own floor.
This is what a boolean cannot express and what movement actually needs: a slab is 0.5, a full block 1.0, a fence 1.5 — so a fence is not a step, it is a wall, and treating it as solid-and-therefore-steppable walks a bot into it and stalls with no error.
Boxes that do not start at the block floor are ignored: standing on the top half of a vertical slab is not something walking gets you to.
func (*Version) CollisionShape ¶
CollisionShape returns the boxes a block state collides with.
An empty result means the state has no collision at all — air, but also grass, torches and carpet-thin decoration. Callers must not read "no boxes" as "unknown": an unknown state also returns none, which is why HasCollisionData exists to tell the two apart.
func (*Version) ComponentEncoding ¶
func (v *Version) ComponentEncoding() (ComponentEncoding, bool)
ComponentEncoding reports how this version encodes component payloads, and whether that is known at all.
Separate from HasComponentIDs because the two are independent. 1.21.11's ids are known — generated from its own registries report — and its payloads still differ from 26.1's, so knowing which id is which buys nothing on its own.
func (*Version) ComponentKind ¶
ComponentKind translates a data component's wire id into the canonical id the decoder recognises, reporting whether this version has an id for it.
The translation matters because the ids are per-version registry indices. On 1.21.11 an attribute_modifiers component arrives as 13 and on 26.1 as 16, and nothing in the payload says which was meant — so reading one as the other does not fail, it consumes the wrong number of bytes and desynchronises everything after it.
func (*Version) EffectName ¶
EffectName returns a status effect's namespaced name.
func (*Version) EntityTypeName ¶
EntityTypeName resolves a wire entity type ID to its namespaced name.
func (*Version) HasCollisionData ¶
HasCollisionData reports whether the version's tables describe this state.
A state outside the table is not "empty", it is unmapped — a version mismatch, or a modded block. Movement code has to treat that as an obstacle rather than as clear air, because walking confidently into an unknown block is a stall with no error attached.
func (*Version) HasComponentIDs ¶
HasComponentIDs reports whether this version's component ids are known at all.
func (*Version) IsAir ¶
IsAir reports whether a block state is any kind of air.
Not just state 0: cave_air and void_air are distinct states with their own IDs, and underground they are the overwhelming majority. Treating only state 0 as air makes a crosshair ray stop dead on the first cave air block it meets.
func (*Version) IsFullCube ¶
IsFullCube reports whether a state fills its block exactly.
This is the case the old boolean got right, and it is worth keeping cheap: most movement questions have a fast answer when the block is a plain cube.
func (*Version) IsTargetable ¶
IsTargetable reports whether the crosshair would stop on a block.
This is deliberately NOT IsSolid. Vanilla raycasts the block's *outline* shape, not its collision shape, and the two differ for exactly the blocks a test suite cares about: cobweb, crops, torches and flowers are all walk-through, so a collision-based ray passes straight through them and reports an empty line of sight to something standing right in front of the crosshair. Fluids are excluded because a block can be targeted through water.
func (*Version) IsWater ¶
IsWater reports whether a block state is water or a bubble column.
Water is emphatically not just "non-solid air": it cancels fall damage completely and drowns anything that stays submerged. Treating it as empty is how a bot falls into a lake like a stone and dies at the bottom.
func (*Version) ItemID ¶
ItemID resolves a namespaced item name to its wire ID. A bare name is assumed to be in the minecraft namespace.
func (*Version) SlotDisplayKind ¶
SlotDisplayKind translates a recipe book slot display kind into the canonical one, reporting whether this version has an id for it.
func (*Version) StackSize ¶
StackSize returns how many of an item fit in one slot.
This is not cosmetic for goal feasibility: totems stack to 1, so "hold 5 totems" needs five whole slots, while "hold 2304 dirt" needs exactly 36 — every storage slot a player has.
func (*Version) StackSizeOf ¶
StackSizeOf returns the stack size for an item name, defaulting to 64 for anything unrecognised.
func (*Version) SupportsAttackPacket ¶
SupportsAttackPacket reports whether the version has a dedicated attack packet. Versions before 26.1 fold attacking into use_entity with a mode field, which this client does not implement — so attacking is unavailable there rather than silently doing nothing.
type VersionSpec ¶
type VersionSpec struct {
Name string
Protocol int32
Chunk ChunkFormat
Packets PacketIDs
// Components describes how this version encodes component payloads. Leave
// nil for a version whose encodings have not been checked; setting it
// wrongly desynchronises windows silently.
Components *ComponentEncoding
// ComponentIDs maps this version's data component wire ids to canonical
// ids. Generated by internal/gen/gencomponents.mjs from the server's own
// registries report; leave nil for a version whose ids are unknown.
//
// They are dense registry indices, like packet ids, and shift whenever
// Mojang inserts a component — between 1.21.4 and 26.1 only five of
// sixty-seven kept their number. Unlike packet ids they are in no published
// dataset, which is why they come from the server rather than from
// minecraft-data.
ComponentIDs map[int32]int32
// SlotDisplayIDs maps this version's slot display kinds to canonical ones.
// Generated alongside ComponentIDs.
SlotDisplayIDs map[int32]int32
// EntityNames and ItemNames are indexed by wire ID; an empty string means
// the ID is unused in this version.
EntityNames []string
// EffectNames is indexed by effect id.
EffectNames []string
ItemNames []string
// ItemStacks is indexed by wire ID. A non-positive entry means the default.
ItemStacks []int32
// Shapes holds the distinct collision shapes, indexed by ShapeRuns.
Shapes [][]Box
// ShapeRuns maps block-state ranges to a shape, as sorted {lo, hi, shape}.
ShapeRuns [][3]int32
// Block-state classification, as sorted inclusive [lo, hi] ranges.
Solid [][2]int32
Water [][2]int32
Lava [][2]int32
Air [][2]int32
}
VersionSpec describes a protocol table to build.
It exists because a Version's tables are unexported — which is right, they are an implementation detail of the lookups — but that also meant only the generated files, being in this package, could construct one. Anything wanting a small synthetic version (a test, a tool) had no way to make it.
type Writer ¶
type Writer struct {
// contains filtered or unexported fields
}
Writer encodes a packet payload. The methods chain, so a packet reads as a single expression in field order — which is how it is checked against the protocol description.
A Writer is not safe for concurrent use.
func NewWriter ¶
NewWriter returns a Writer with the given packet ID already encoded, which is where every outbound payload starts.
func (*Writer) BlockPos ¶
BlockPos writes a block coordinate in the packed form the protocol uses: a single 64-bit word of x:26, z:26, y:12, in that order. Note the ordering — z sits between x and y, which is the usual place to get this wrong, and a wrong packing addresses a real block somewhere else entirely rather than erroring.
func (*Writer) Bytes ¶
Bytes returns the encoded payload. It aliases the Writer's buffer, so it must not be retained across further writes.
func (*Writer) F16 ¶
F16 writes an IEEE-754 half-precision float.
Minecraft uses these where a full float would be wasteful and the precision does not matter — the "lpVec3" (low-precision vec3) carrying the point on an entity that an interaction hit, which only has to be good enough to tell one part of a boat from another.
Getting the width wrong here is not subtle: the server reports the packet as longer or shorter than it expected and drops the connection, which is at least loud.