From 11df3cb1f8574a694f64988df8cd50e3e151b87d Mon Sep 17 00:00:00 2001 From: TwistedAsylumMC Date: Fri, 2 Aug 2024 16:06:49 +0100 Subject: [PATCH 1/4] minecraft/protocol: Initial support for 1.21.20 --- minecraft/protocol/ability.go | 1 + minecraft/protocol/camera.go | 20 ++++++++ minecraft/protocol/container.go | 14 +++++ minecraft/protocol/entity_link.go | 3 ++ minecraft/protocol/info.go | 4 +- minecraft/protocol/inventory.go | 21 ++++++++ minecraft/protocol/item_stack.go | 6 +-- .../protocol/packet/camera_instruction.go | 6 +++ minecraft/protocol/packet/change_dimension.go | 5 ++ .../compressed_biome_definition_list.go | 8 +-- .../packet/current_structure_feature.go | 22 ++++++++ minecraft/protocol/packet/disconnect.go | 3 ++ minecraft/protocol/packet/editor_network.go | 3 ++ minecraft/protocol/packet/id.go | 5 ++ .../protocol/packet/inventory_content.go | 4 ++ minecraft/protocol/packet/inventory_slot.go | 4 ++ .../protocol/packet/jigsaw_structure_data.go | 21 ++++++++ .../protocol/packet/mob_armour_equipment.go | 3 ++ minecraft/protocol/packet/pool.go | 8 ++- .../packet/server_bound_diagnostics.go | 51 +++++++++++++++++++ .../packet/server_bound_loading_screen.go | 31 +++++++++++ minecraft/protocol/packet/set_title.go | 3 ++ minecraft/protocol/packet/stop_sound.go | 3 ++ .../packet/structure_template_data_request.go | 1 - .../structure_template_data_response.go | 1 - minecraft/protocol/resource_pack.go | 6 +++ 26 files changed, 245 insertions(+), 12 deletions(-) create mode 100644 minecraft/protocol/packet/current_structure_feature.go create mode 100644 minecraft/protocol/packet/jigsaw_structure_data.go create mode 100644 minecraft/protocol/packet/server_bound_diagnostics.go create mode 100644 minecraft/protocol/packet/server_bound_loading_screen.go diff --git a/minecraft/protocol/ability.go b/minecraft/protocol/ability.go index 560c37f2..b8db8bac 100644 --- a/minecraft/protocol/ability.go +++ b/minecraft/protocol/ability.go @@ -29,6 +29,7 @@ const ( AbilityLayerTypeSpectator AbilityLayerTypeCommands AbilityLayerTypeEditor + AbilityLayerTypeLoadingScreen ) const ( diff --git a/minecraft/protocol/camera.go b/minecraft/protocol/camera.go index 75e7a7dd..e5e2cbd3 100644 --- a/minecraft/protocol/camera.go +++ b/minecraft/protocol/camera.go @@ -118,6 +118,20 @@ func (x *CameraInstructionFade) Marshal(r IO) { OptionalFunc(r, &x.Colour, r.RGB) } +// CameraInstructionTarget represents a camera instruction that targets a specific entity. +type CameraInstructionTarget struct { + // CenterOffset is the offset from the center of the entity that the camera should target. + CenterOffset Optional[mgl32.Vec3] + // EntityUniqueID is the unique ID of the entity that the camera should target. + EntityUniqueID int64 +} + +// Marshal encodes/decodes a CameraInstructionTarget. +func (x *CameraInstructionTarget) Marshal(r IO) { + OptionalFunc(r, &x.CenterOffset, r.Vec3) + r.Int64(&x.EntityUniqueID) +} + // CameraPreset represents a basic preset that can be extended upon by more complex instructions. type CameraPreset struct { // Name is the name of the preset. Each preset must have their own unique name. @@ -134,6 +148,10 @@ type CameraPreset struct { RotX Optional[float32] // RotY is the default yaw of the camera. RotY Optional[float32] + // ViewOffset ... + ViewOffset Optional[mgl32.Vec2] + // Radius ... + Radius Optional[float32] // AudioListener defines where the audio should be played from when using this preset. This is one of the constants // above. AudioListener Optional[byte] @@ -150,6 +168,8 @@ func (x *CameraPreset) Marshal(r IO) { OptionalFunc(r, &x.PosZ, r.Float32) OptionalFunc(r, &x.RotX, r.Float32) OptionalFunc(r, &x.RotY, r.Float32) + OptionalFunc(r, &x.ViewOffset, r.Vec2) + OptionalFunc(r, &x.Radius, r.Float32) OptionalFunc(r, &x.AudioListener, r.Uint8) OptionalFunc(r, &x.PlayerEffects, r.Bool) } diff --git a/minecraft/protocol/container.go b/minecraft/protocol/container.go index daac53ea..a2fd8e42 100644 --- a/minecraft/protocol/container.go +++ b/minecraft/protocol/container.go @@ -64,6 +64,7 @@ const ( ContainerCreatedOutput ContainerSmithingTableTemplate ContainerCrafterLevelEntity + ContainerDynamic ) const ( @@ -106,3 +107,16 @@ const ( ContainerTypeDecoratedPot ContainerTypeCrafter ) + +type FullContainerName struct { + // ContainerID is the ID of the container that the slot was in. + ContainerID byte + // DynamicContainerID is the ID of the container if it is dynamic. If the container is not dynamic, this field is + // set to 0. + DynamicContainerID uint32 +} + +func (x *FullContainerName) Marshal(r IO) { + r.Uint8(&x.ContainerID) + r.Uint32(&x.DynamicContainerID) +} diff --git a/minecraft/protocol/entity_link.go b/minecraft/protocol/entity_link.go index 879ed212..4155b808 100644 --- a/minecraft/protocol/entity_link.go +++ b/minecraft/protocol/entity_link.go @@ -27,6 +27,8 @@ type EntityLink struct { // RiderInitiated specifies if the link was created by the rider, for example the player starting to ride // a horse by itself. This is generally true in vanilla environment for players. RiderInitiated bool + // VehicleAngularVelocity is the angular velocity of the vehicle that the rider is riding. + VehicleAngularVelocity float32 } // Marshal encodes/decodes a single entity link. @@ -36,4 +38,5 @@ func (x *EntityLink) Marshal(r IO) { r.Uint8(&x.Type) r.Bool(&x.Immediate) r.Bool(&x.RiderInitiated) + r.Float32(&x.VehicleAngularVelocity) } diff --git a/minecraft/protocol/info.go b/minecraft/protocol/info.go index 932d16c6..652fcd2e 100644 --- a/minecraft/protocol/info.go +++ b/minecraft/protocol/info.go @@ -2,7 +2,7 @@ package protocol const ( // CurrentProtocol is the current protocol version for the version below. - CurrentProtocol = 686 + CurrentProtocol = 712 // CurrentVersion is the current version of Minecraft as supported by the `packet` package. - CurrentVersion = "1.21.2" + CurrentVersion = "1.21.20" ) diff --git a/minecraft/protocol/inventory.go b/minecraft/protocol/inventory.go index 3469f6d8..54099587 100644 --- a/minecraft/protocol/inventory.go +++ b/minecraft/protocol/inventory.go @@ -120,6 +120,17 @@ const ( UseItemActionBreakBlock ) +const ( + TriggerTypeUnknown = iota + TriggerTypePlayerInput + TriggerTypeSimulationTick +) + +const ( + ClientPredictionFailure = iota + ClientPredictionSuccess +) + // UseItemTransactionData represents an inventory transaction data object sent when the client uses an item on // a block. type UseItemTransactionData struct { @@ -142,6 +153,11 @@ type UseItemTransactionData struct { // ActionType is the type of the UseItem inventory transaction. It is one of the action types found above, // and specifies the way the player interacted with the block. ActionType uint32 + // TriggerType is the type of the trigger that caused the inventory transaction. It is one of the trigger + // types found in the constants above. If TriggerType is TriggerTypePlayerInput, the transaction is from + // the initial input of the player. If it is TriggerTypeSimulationTick, the transaction is from a simulation + // tick when the player is holding down the input. + TriggerType uint32 // BlockPosition is the position of the block that was interacted with. This is only really a correct // block position if ActionType is not UseItemActionClickAir. BlockPosition BlockPos @@ -163,6 +179,9 @@ type UseItemTransactionData struct { // BlockRuntimeID is the runtime ID of the block that was clicked. It may be used by the server to verify // that the player's world client-side is synchronised with the server's. BlockRuntimeID uint32 + // ClientPrediction is the client's prediction on the output of the transaction. It is one of the client + // prediction found in the constants above. + ClientPrediction byte } const ( @@ -219,6 +238,7 @@ type ReleaseItemTransactionData struct { // Marshal ... func (data *UseItemTransactionData) Marshal(r IO) { r.Varuint32(&data.ActionType) + r.Varuint32(&data.TriggerType) r.UBlockPos(&data.BlockPosition) r.Varint32(&data.BlockFace) r.Varint32(&data.HotBarSlot) @@ -226,6 +246,7 @@ func (data *UseItemTransactionData) Marshal(r IO) { r.Vec3(&data.Position) r.Vec3(&data.ClickedPosition) r.Varuint32(&data.BlockRuntimeID) + r.Uint8(&data.ClientPrediction) } // Marshal ... diff --git a/minecraft/protocol/item_stack.go b/minecraft/protocol/item_stack.go index 0b44276c..6a25e391 100644 --- a/minecraft/protocol/item_stack.go +++ b/minecraft/protocol/item_stack.go @@ -515,8 +515,8 @@ func (a *CraftResultsDeprecatedStackRequestAction) Marshal(r IO) { // StackRequestSlotInfo holds information on a specific slot client-side. type StackRequestSlotInfo struct { - // ContainerID is the ID of the container that the slot was in. - ContainerID byte + // Container is the FullContainerName that describes the container that the slot is in. + Container FullContainerName // Slot is the index of the slot within the container with the ContainerID above. Slot byte // StackNetworkID is the unique stack ID that the client assumes to be present in this slot. The server @@ -527,7 +527,7 @@ type StackRequestSlotInfo struct { // StackReqSlotInfo reads/writes a StackRequestSlotInfo x using IO r. func StackReqSlotInfo(r IO, x *StackRequestSlotInfo) { - r.Uint8(&x.ContainerID) + Single(r, &x.Container) r.Uint8(&x.Slot) r.Varint32(&x.StackNetworkID) } diff --git a/minecraft/protocol/packet/camera_instruction.go b/minecraft/protocol/packet/camera_instruction.go index edd231e4..a0f114dd 100644 --- a/minecraft/protocol/packet/camera_instruction.go +++ b/minecraft/protocol/packet/camera_instruction.go @@ -12,6 +12,10 @@ type CameraInstruction struct { Clear protocol.Optional[bool] // Fade is a camera instruction that fades the screen to a specified colour. Fade protocol.Optional[protocol.CameraInstructionFade] + // Target is a camera instruction that targets a specific entity. + Target protocol.Optional[protocol.CameraInstructionTarget] + // RemoveTarget can be set to true to remove the current target entity. + RemoveTarget protocol.Optional[bool] } // ID ... @@ -23,4 +27,6 @@ func (pk *CameraInstruction) Marshal(io protocol.IO) { protocol.OptionalMarshaler(io, &pk.Set) protocol.OptionalFunc(io, &pk.Clear, io.Bool) protocol.OptionalMarshaler(io, &pk.Fade) + protocol.OptionalMarshaler(io, &pk.Target) + protocol.OptionalFunc(io, &pk.RemoveTarget, io.Bool) } diff --git a/minecraft/protocol/packet/change_dimension.go b/minecraft/protocol/packet/change_dimension.go index 53a217f0..383a1e2c 100644 --- a/minecraft/protocol/packet/change_dimension.go +++ b/minecraft/protocol/packet/change_dimension.go @@ -28,6 +28,10 @@ type ChangeDimension struct { // PlayerActionDimensionChangeRequest if it dies in another dimension, indicating that it needs a // DimensionChange packet with Respawn set to true. Respawn bool + // LoadingScreenID is a unique ID for the loading screen that is displayed while the client is changing + // dimensions. The client will update the server on its state through the ServerBoundLoadingScreen packet. + // This field should be unique for every ChangeDimension packet sent. + LoadingScreenID protocol.Optional[uint32] } // ID ... @@ -39,4 +43,5 @@ func (pk *ChangeDimension) Marshal(io protocol.IO) { io.Varint32(&pk.Dimension) io.Vec3(&pk.Position) io.Bool(&pk.Respawn) + protocol.OptionalFunc(io, &pk.LoadingScreenID, io.Uint32) } diff --git a/minecraft/protocol/packet/compressed_biome_definition_list.go b/minecraft/protocol/packet/compressed_biome_definition_list.go index 0b1e1d75..6908bb56 100644 --- a/minecraft/protocol/packet/compressed_biome_definition_list.go +++ b/minecraft/protocol/packet/compressed_biome_definition_list.go @@ -7,9 +7,9 @@ import ( // CompressedBiomeDefinitionList is sent by the server to send a list of biomes to the client. The contents of this packet // are very large, even after being compressed. This packet is only required when using client-side chunk generation. type CompressedBiomeDefinitionList struct { - // Biomes is a map of biomes with their identifier as key, and the biome data as value. The biome data contains many - // different fields such as climate, surface materials and generation rules etc. - Biomes map[string]any + // SerialisedBiomeDefinitions is a network NBT serialised compound of all definitions of biomes that are + // available on the server. + SerialisedBiomeDefinitions []byte } // ID ... @@ -18,5 +18,5 @@ func (*CompressedBiomeDefinitionList) ID() uint32 { } func (pk *CompressedBiomeDefinitionList) Marshal(io protocol.IO) { - io.CompressedBiomeDefinitions(&pk.Biomes) + io.Bytes(&pk.SerialisedBiomeDefinitions) } diff --git a/minecraft/protocol/packet/current_structure_feature.go b/minecraft/protocol/packet/current_structure_feature.go new file mode 100644 index 00000000..5ce16418 --- /dev/null +++ b/minecraft/protocol/packet/current_structure_feature.go @@ -0,0 +1,22 @@ +package packet + +import ( + "github.com/sandertv/gophertunnel/minecraft/protocol" +) + +// CurrentStructureFeature is sent by the server to let the client know the name of the structure feature +// that the player is currently occupying. +type CurrentStructureFeature struct { + // CurrentFeature is the identifier of the structure feature that the player is currently occupying. + // If the player is not occupying any structure feature, this field is empty. + CurrentFeature string +} + +// ID ... +func (*CurrentStructureFeature) ID() uint32 { + return IDCurrentStructureFeature +} + +func (pk *CurrentStructureFeature) Marshal(io protocol.IO) { + io.String(&pk.CurrentFeature) +} diff --git a/minecraft/protocol/packet/disconnect.go b/minecraft/protocol/packet/disconnect.go index 16764eda..94e629e5 100644 --- a/minecraft/protocol/packet/disconnect.go +++ b/minecraft/protocol/packet/disconnect.go @@ -16,6 +16,8 @@ type Disconnect struct { // Message is an optional message to show when disconnected. This message is only written if the // HideDisconnectionScreen field is set to true. Message string + // FilteredMessage is always set to empty and the usage is currently unknown. + FilteredMessage string } // ID ... @@ -28,5 +30,6 @@ func (pk *Disconnect) Marshal(io protocol.IO) { io.Bool(&pk.HideDisconnectionScreen) if !pk.HideDisconnectionScreen { io.String(&pk.Message) + io.String(&pk.FilteredMessage) } } diff --git a/minecraft/protocol/packet/editor_network.go b/minecraft/protocol/packet/editor_network.go index 21a3eda6..5de43b6c 100644 --- a/minecraft/protocol/packet/editor_network.go +++ b/minecraft/protocol/packet/editor_network.go @@ -8,6 +8,8 @@ import ( // EditorNetwork is a packet sent from the server to the client and vise-versa to communicate editor-mode related // information. It carries a single compound tag containing the relevant information. type EditorNetwork struct { + // RouteToManager ... + RouteToManager bool // Payload is a network little endian compound tag holding data relevant to the editor. Payload map[string]any } @@ -18,5 +20,6 @@ func (*EditorNetwork) ID() uint32 { } func (pk *EditorNetwork) Marshal(io protocol.IO) { + io.Bool(&pk.RouteToManager) io.NBT(&pk.Payload, nbt.NetworkLittleEndian) } diff --git a/minecraft/protocol/packet/id.go b/minecraft/protocol/packet/id.go index 9e0011d1..ef63df5b 100644 --- a/minecraft/protocol/packet/id.go +++ b/minecraft/protocol/packet/id.go @@ -211,4 +211,9 @@ const ( IDSetHud IDAwardAchievement IDClientBoundCloseForm + _ + IDServerBoundLoadingScreen + IDJigsawStructureData + IDCurrentStructureFeature + IDServerBoundDiagnostics ) diff --git a/minecraft/protocol/packet/inventory_content.go b/minecraft/protocol/packet/inventory_content.go index 5bb93f62..49503663 100644 --- a/minecraft/protocol/packet/inventory_content.go +++ b/minecraft/protocol/packet/inventory_content.go @@ -14,6 +14,9 @@ type InventoryContent struct { // Content is the new content of the inventory. The length of this slice must be equal to the full size of // the inventory window updated. Content []protocol.ItemInstance + // DynamicWindowID is the ID of the window if it is dynamic. If the window is not dynamic, this field is + // set to 0. + DynamicWindowID uint32 } // ID ... @@ -24,4 +27,5 @@ func (*InventoryContent) ID() uint32 { func (pk *InventoryContent) Marshal(io protocol.IO) { io.Varuint32(&pk.WindowID) protocol.FuncSlice(io, &pk.Content, io.ItemInstance) + io.Varuint32(&pk.DynamicWindowID) } diff --git a/minecraft/protocol/packet/inventory_slot.go b/minecraft/protocol/packet/inventory_slot.go index 4782a7f2..8e368825 100644 --- a/minecraft/protocol/packet/inventory_slot.go +++ b/minecraft/protocol/packet/inventory_slot.go @@ -14,6 +14,9 @@ type InventorySlot struct { // Slot is the index of the slot that the packet modifies. The new item will be set to the slot at this // index. Slot uint32 + // DynamicWindowID is the ID of the window if it is dynamic. If the window is not dynamic, this field is + // set to 0. + DynamicWindowID uint32 // NewItem is the item to be put in the slot at Slot. It will overwrite any item that may currently // be present in that slot. NewItem protocol.ItemInstance @@ -27,5 +30,6 @@ func (*InventorySlot) ID() uint32 { func (pk *InventorySlot) Marshal(io protocol.IO) { io.Varuint32(&pk.WindowID) io.Varuint32(&pk.Slot) + io.Varuint32(&pk.DynamicWindowID) io.ItemInstance(&pk.NewItem) } diff --git a/minecraft/protocol/packet/jigsaw_structure_data.go b/minecraft/protocol/packet/jigsaw_structure_data.go new file mode 100644 index 00000000..cf0f419b --- /dev/null +++ b/minecraft/protocol/packet/jigsaw_structure_data.go @@ -0,0 +1,21 @@ +package packet + +import ( + "github.com/sandertv/gophertunnel/minecraft/protocol" +) + +// JigsawStructureData is sent by the server to let the client know all the rules for jigsaw structures. +type JigsawStructureData struct { + // StructureData is a network NBT serialised compound of all the jigsaw structure rules defined + // on the server. + StructureData []byte +} + +// ID ... +func (*JigsawStructureData) ID() uint32 { + return IDJigsawStructureData +} + +func (pk *JigsawStructureData) Marshal(io protocol.IO) { + io.Bytes(&pk.StructureData) +} diff --git a/minecraft/protocol/packet/mob_armour_equipment.go b/minecraft/protocol/packet/mob_armour_equipment.go index 81174ff9..572327d1 100644 --- a/minecraft/protocol/packet/mob_armour_equipment.go +++ b/minecraft/protocol/packet/mob_armour_equipment.go @@ -21,6 +21,8 @@ type MobArmourEquipment struct { Leggings protocol.ItemInstance // Boots is the item worn as boots by the entity. Items not wearable as boots will not be rendered. Boots protocol.ItemInstance + // Body is the item worn on the body of the entity. Items not wearable on the body will not be rendered. + Body protocol.ItemInstance } // ID ... @@ -34,4 +36,5 @@ func (pk *MobArmourEquipment) Marshal(io protocol.IO) { io.ItemInstance(&pk.Chestplate) io.ItemInstance(&pk.Leggings) io.ItemInstance(&pk.Boots) + io.ItemInstance(&pk.Body) } diff --git a/minecraft/protocol/packet/pool.go b/minecraft/protocol/packet/pool.go index 6f969067..6e0b01f9 100644 --- a/minecraft/protocol/packet/pool.go +++ b/minecraft/protocol/packet/pool.go @@ -252,6 +252,10 @@ func init() { IDSetHud: func() Packet { return &SetHud{} }, IDAwardAchievement: func() Packet { return &AwardAchievement{} }, IDClientBoundCloseForm: func() Packet { return &ClientBoundCloseForm{} }, + IDServerBoundLoadingScreen: func() Packet { return &ServerBoundLoadingScreen{} }, + IDJigsawStructureData: func() Packet { return &JigsawStructureData{} }, + IDCurrentStructureFeature: func() Packet { return &CurrentStructureFeature{} }, + IDServerBoundDiagnostics: func() Packet { return &ServerBoundDiagnostics{} }, } for id, pk := range serverOriginating { RegisterPacketFromServer(id, pk) @@ -286,7 +290,7 @@ func init() { IDMapInfoRequest: func() Packet { return &MapInfoRequest{} }, IDRequestChunkRadius: func() Packet { return &RequestChunkRadius{} }, IDBossEvent: func() Packet { return &BossEvent{} }, - IDShowCredits: func() Packet { return &ShowCredits{} }, + IDShowCredits: func() Packet { return &ShowCredits{} }, IDCommandRequest: func() Packet { return &CommandRequest{} }, IDCommandBlockUpdate: func() Packet { return &CommandBlockUpdate{} }, IDResourcePackChunkRequest: func() Packet { return &ResourcePackChunkRequest{} }, @@ -336,6 +340,8 @@ func init() { IDBlockActorData: func() Packet { return &BlockActorData{} }, IDPlayerToggleCrafterSlotRequest: func() Packet { return &PlayerToggleCrafterSlotRequest{} }, IDSetPlayerInventoryOptions: func() Packet { return &SetPlayerInventoryOptions{} }, + IDServerBoundLoadingScreen: func() Packet { return &ServerBoundLoadingScreen{} }, + IDServerBoundDiagnostics: func() Packet { return &ServerBoundDiagnostics{} }, } for id, pk := range clientOriginating { RegisterPacketFromClient(id, pk) diff --git a/minecraft/protocol/packet/server_bound_diagnostics.go b/minecraft/protocol/packet/server_bound_diagnostics.go new file mode 100644 index 00000000..17b4c4fd --- /dev/null +++ b/minecraft/protocol/packet/server_bound_diagnostics.go @@ -0,0 +1,51 @@ +package packet + +import ( + "github.com/sandertv/gophertunnel/minecraft/protocol" +) + +// ServerBoundDiagnostics is sent by the client to tell the server about the performance diagnostics +// of the client. It is +type ServerBoundDiagnostics struct { + // AverageFramesPerSecond is the average amount of frames per second that the client has been + // running at. + AverageFramesPerSecond float32 + // AverageServerSimTickTime is the average time that the server spends simulating a single tick + // in milliseconds. + AverageServerSimTickTime float32 + // AverageClientSimTickTime is the average time that the client spends simulating a single tick + // in milliseconds. + AverageClientSimTickTime float32 + // AverageBeginFrameTime is the average time that the client spends beginning a frame in + // milliseconds. + AverageBeginFrameTime float32 + // AverageInputTime is the average time that the client spends processing input in milliseconds. + AverageInputTime float32 + // AverageRenderTime is the average time that the client spends rendering in milliseconds. + AverageRenderTime float32 + // AverageEndFrameTime is the average time that the client spends ending a frame in milliseconds. + AverageEndFrameTime float32 + // AverageRemainderTimePercent is the average percentage of time that the client spends on + // tasks that are not accounted for. + AverageRemainderTimePercent float32 + // AverageUnaccountedTimePercent is the average percentage of time that the client spends on + // unaccounted tasks. + AverageUnaccountedTimePercent float32 +} + +// ID ... +func (*ServerBoundDiagnostics) ID() uint32 { + return IDServerBoundDiagnostics +} + +func (pk *ServerBoundDiagnostics) Marshal(io protocol.IO) { + io.Float32(&pk.AverageFramesPerSecond) + io.Float32(&pk.AverageServerSimTickTime) + io.Float32(&pk.AverageClientSimTickTime) + io.Float32(&pk.AverageBeginFrameTime) + io.Float32(&pk.AverageInputTime) + io.Float32(&pk.AverageRenderTime) + io.Float32(&pk.AverageEndFrameTime) + io.Float32(&pk.AverageRemainderTimePercent) + io.Float32(&pk.AverageUnaccountedTimePercent) +} diff --git a/minecraft/protocol/packet/server_bound_loading_screen.go b/minecraft/protocol/packet/server_bound_loading_screen.go new file mode 100644 index 00000000..66ffce22 --- /dev/null +++ b/minecraft/protocol/packet/server_bound_loading_screen.go @@ -0,0 +1,31 @@ +package packet + +import ( + "github.com/sandertv/gophertunnel/minecraft/protocol" +) + +const ( + LoadingScreenTypeUnknown = iota + LoadingScreenTypeStart + LoadingScreenTypeEnd +) + +// ServerBoundLoadingScreen is sent by the client to tell the server about the state of the loading +// screen that the client is currently displaying. +type ServerBoundLoadingScreen struct { + // Type is the type of the loading screen event. It is one of the constants that may be found above. + Type int32 + // LoadingScreenID is the ID of the screen that was previously sent by the server in the ChangeDimension + // packet. The server should validate that the screen ID is correct. + LoadingScreenID protocol.Optional[uint32] +} + +// ID ... +func (*ServerBoundLoadingScreen) ID() uint32 { + return IDServerBoundLoadingScreen +} + +func (pk *ServerBoundLoadingScreen) Marshal(io protocol.IO) { + io.Varint32(&pk.Type) + protocol.OptionalFunc(io, &pk.LoadingScreenID, io.Uint32) +} diff --git a/minecraft/protocol/packet/set_title.go b/minecraft/protocol/packet/set_title.go index c5d9c4fe..538842e2 100644 --- a/minecraft/protocol/packet/set_title.go +++ b/minecraft/protocol/packet/set_title.go @@ -39,6 +39,8 @@ type SetTitle struct { XUID string // PlatformOnlineID is either a uint64 or an empty string. PlatformOnlineID string + // FilteredMessage is always set to empty and the usage is currently unknown. + FilteredMessage string } // ID ... @@ -54,4 +56,5 @@ func (pk *SetTitle) Marshal(io protocol.IO) { io.Varint32(&pk.FadeOutDuration) io.String(&pk.XUID) io.String(&pk.PlatformOnlineID) + io.String(&pk.FilteredMessage) } diff --git a/minecraft/protocol/packet/stop_sound.go b/minecraft/protocol/packet/stop_sound.go index 5489cc0b..694ddcf7 100644 --- a/minecraft/protocol/packet/stop_sound.go +++ b/minecraft/protocol/packet/stop_sound.go @@ -13,6 +13,9 @@ type StopSound struct { // StopAll specifies if all sounds currently playing to the player should be stopped. If set to true, the // SoundName field may be left empty. StopAll bool + // StopMusic specifies if all music currently playing to the player should be stopped. If set to true, the + // SoundName field may be left empty. + StopMusic bool } // ID ... diff --git a/minecraft/protocol/packet/structure_template_data_request.go b/minecraft/protocol/packet/structure_template_data_request.go index d388fd08..42b28d9b 100644 --- a/minecraft/protocol/packet/structure_template_data_request.go +++ b/minecraft/protocol/packet/structure_template_data_request.go @@ -8,7 +8,6 @@ const ( StructureTemplateRequestExportFromSave = iota + 1 StructureTemplateRequestExportFromLoad StructureTemplateRequestQuerySavedStructure - StructureTemplateRequestImportFromSave ) // StructureTemplateDataRequest is sent by the client to request data of a structure. diff --git a/minecraft/protocol/packet/structure_template_data_response.go b/minecraft/protocol/packet/structure_template_data_response.go index 62acc152..d9c9c4a8 100644 --- a/minecraft/protocol/packet/structure_template_data_response.go +++ b/minecraft/protocol/packet/structure_template_data_response.go @@ -8,7 +8,6 @@ import ( const ( StructureTemplateResponseExport = iota + 1 StructureTemplateResponseQuery - StructureTemplateResponseImport ) // StructureTemplateDataResponse is sent by the server to send data of a structure to the client in response diff --git a/minecraft/protocol/resource_pack.go b/minecraft/protocol/resource_pack.go index db256525..753f3353 100644 --- a/minecraft/protocol/resource_pack.go +++ b/minecraft/protocol/resource_pack.go @@ -23,6 +23,8 @@ type BehaviourPackInfo struct { // HasScripts specifies if the behaviour packs has any scripts in it. A client will only download the // behaviour pack if it supports scripts, which, up to 1.11, only includes Windows 10. HasScripts bool + // AddonPack specifies if the texture pack is from an addon. + AddonPack bool } // Marshal encodes/decodes a BehaviourPackInfo. @@ -34,6 +36,7 @@ func (x *BehaviourPackInfo) Marshal(r IO) { r.String(&x.SubPackName) r.String(&x.ContentIdentity) r.Bool(&x.HasScripts) + r.Bool(&x.AddonPack) } // TexturePackInfo represents a texture pack's info sent over network. It holds information about the @@ -60,6 +63,8 @@ type TexturePackInfo struct { // HasScripts specifies if the texture packs has any scripts in it. A client will only download the // behaviour pack if it supports scripts, which, up to 1.11, only includes Windows 10. HasScripts bool + // AddonPack specifies if the texture pack is from an addon. + AddonPack bool // RTXEnabled specifies if the texture pack uses the raytracing technology introduced in 1.16.200. RTXEnabled bool } @@ -73,6 +78,7 @@ func (x *TexturePackInfo) Marshal(r IO) { r.String(&x.SubPackName) r.String(&x.ContentIdentity) r.Bool(&x.HasScripts) + r.Bool(&x.AddonPack) r.Bool(&x.RTXEnabled) } From cdf135d65037ef2645c052d8ff4b05af7f19d2b5 Mon Sep 17 00:00:00 2001 From: TwistedAsylumMC Date: Wed, 14 Aug 2024 09:58:58 +0100 Subject: [PATCH 2/4] minecraft/protocol: Initial support for 1.21.20 --- minecraft/protocol/camera.go | 10 ++++---- minecraft/protocol/container.go | 1 + minecraft/protocol/item_stack.go | 19 +++++++++++++++ minecraft/protocol/packet/change_dimension.go | 7 +++--- .../packet/correct_player_move_prediction.go | 3 +++ .../protocol/packet/player_armour_damage.go | 23 +++++++++++++++---- .../packet/server_bound_diagnostics.go | 2 +- .../packet/server_bound_loading_screen.go | 2 +- minecraft/protocol/packet/stop_sound.go | 6 ++--- 9 files changed, 57 insertions(+), 16 deletions(-) diff --git a/minecraft/protocol/camera.go b/minecraft/protocol/camera.go index e5e2cbd3..1ec6d91a 100644 --- a/minecraft/protocol/camera.go +++ b/minecraft/protocol/camera.go @@ -148,12 +148,14 @@ type CameraPreset struct { RotX Optional[float32] // RotY is the default yaw of the camera. RotY Optional[float32] - // ViewOffset ... + // ViewOffset is only used in a follow_orbit camera and controls an offset based on a pivot point to the + // player, causing it to be shifted in a certain direction. ViewOffset Optional[mgl32.Vec2] - // Radius ... + // Radius is only used in a follow_orbit camera and controls how far away from the player the camera should + // be rendered. Radius Optional[float32] - // AudioListener defines where the audio should be played from when using this preset. This is one of the constants - // above. + // AudioListener defines where the audio should be played from when using this preset. This is one of the + // constants above. AudioListener Optional[byte] // PlayerEffects is currently unknown. PlayerEffects Optional[bool] diff --git a/minecraft/protocol/container.go b/minecraft/protocol/container.go index a2fd8e42..d9a36991 100644 --- a/minecraft/protocol/container.go +++ b/minecraft/protocol/container.go @@ -108,6 +108,7 @@ const ( ContainerTypeCrafter ) +// FullContainerName contains information required to identify a container in a StackRequestSlotInfo. type FullContainerName struct { // ContainerID is the ID of the container that the slot was in. ContainerID byte diff --git a/minecraft/protocol/item_stack.go b/minecraft/protocol/item_stack.go index d9e8daac..c09f031c 100644 --- a/minecraft/protocol/item_stack.go +++ b/minecraft/protocol/item_stack.go @@ -468,11 +468,15 @@ type CraftRecipeStackRequestAction struct { // one of the recipes sent in the CraftingData packet, where each of the recipes have a RecipeNetworkID as // of 1.16. RecipeNetworkID uint32 + // NumberOfCrafts is how many times the recipe was crafted. This field appears to be boilerplate and + // has no effect. + NumberOfCrafts byte } // Marshal ... func (a *CraftRecipeStackRequestAction) Marshal(r IO) { r.Varuint32(&a.RecipeNetworkID) + r.Uint8(&a.NumberOfCrafts) } // AutoCraftRecipeStackRequestAction is sent by the client similarly to the CraftRecipeStackRequestAction. The @@ -482,6 +486,8 @@ type AutoCraftRecipeStackRequestAction struct { // one of the recipes sent in the CraftingData packet, where each of the recipes have a RecipeNetworkID as // of 1.16. RecipeNetworkID uint32 + // NumberOfCrafts is how many times the recipe was crafted. This field is just a duplicate of TimesCrafted. + NumberOfCrafts byte // TimesCrafted is how many times the recipe was crafted. TimesCrafted byte // Ingredients is a slice of ItemDescriptorCount that contains the ingredients that were used to craft the recipe. @@ -492,6 +498,7 @@ type AutoCraftRecipeStackRequestAction struct { // Marshal ... func (a *AutoCraftRecipeStackRequestAction) Marshal(r IO) { r.Varuint32(&a.RecipeNetworkID) + r.Uint8(&a.NumberOfCrafts) r.Uint8(&a.TimesCrafted) FuncSlice(r, &a.Ingredients, r.ItemDescriptorCount) } @@ -502,11 +509,15 @@ type CraftCreativeStackRequestAction struct { // CreativeItemNetworkID is the network ID of the creative item that is being created. This is one of the // creative item network IDs sent in the CreativeContent packet. CreativeItemNetworkID uint32 + // NumberOfCrafts is how many times the recipe was crafted. This field appears to be boilerplate and + // has no effect. + NumberOfCrafts byte } // Marshal ... func (a *CraftCreativeStackRequestAction) Marshal(r IO) { r.Varuint32(&a.CreativeItemNetworkID) + r.Uint8(&a.NumberOfCrafts) } // CraftRecipeOptionalStackRequestAction is sent when using an anvil. When this action is sent, the @@ -517,6 +528,9 @@ type CraftRecipeOptionalStackRequestAction struct { // one of the multi-recipes sent in the CraftingData packet, where each of the recipes have a RecipeNetworkID as // of 1.16. RecipeNetworkID uint32 + // NumberOfCrafts is how many times the recipe was crafted. This field appears to be boilerplate and + // has no effect. + NumberOfCrafts byte // FilterStringIndex is the index of a filter string sent in a ItemStackRequest. FilterStringIndex int32 } @@ -524,6 +538,7 @@ type CraftRecipeOptionalStackRequestAction struct { // Marshal ... func (c *CraftRecipeOptionalStackRequestAction) Marshal(r IO) { r.Varuint32(&c.RecipeNetworkID) + r.Uint8(&c.NumberOfCrafts) r.Int32(&c.FilterStringIndex) } @@ -534,6 +549,9 @@ type CraftGrindstoneRecipeStackRequestAction struct { // one of the recipes sent in the CraftingData packet, where each of the recipes have a RecipeNetworkID as // of 1.16. RecipeNetworkID uint32 + // NumberOfCrafts is how many times the recipe was crafted. This field appears to be boilerplate and + // has no effect. + NumberOfCrafts byte // Cost is the cost of the recipe that was crafted. Cost int32 } @@ -541,6 +559,7 @@ type CraftGrindstoneRecipeStackRequestAction struct { // Marshal ... func (c *CraftGrindstoneRecipeStackRequestAction) Marshal(r IO) { r.Varuint32(&c.RecipeNetworkID) + r.Uint8(&c.NumberOfCrafts) r.Varint32(&c.Cost) } diff --git a/minecraft/protocol/packet/change_dimension.go b/minecraft/protocol/packet/change_dimension.go index 383a1e2c..e7a6b5e2 100644 --- a/minecraft/protocol/packet/change_dimension.go +++ b/minecraft/protocol/packet/change_dimension.go @@ -28,9 +28,10 @@ type ChangeDimension struct { // PlayerActionDimensionChangeRequest if it dies in another dimension, indicating that it needs a // DimensionChange packet with Respawn set to true. Respawn bool - // LoadingScreenID is a unique ID for the loading screen that is displayed while the client is changing - // dimensions. The client will update the server on its state through the ServerBoundLoadingScreen packet. - // This field should be unique for every ChangeDimension packet sent. + // LoadingScreenID is a unique ID for the loading screen that the player is currently in. The client will + // update the server on its state through the ServerBoundLoadingScreen packet, and it can be used to not + // send specific packets to the client if it is changing dimensions. This field should be unique for every + //ChangeDimension packet sent. LoadingScreenID protocol.Optional[uint32] } diff --git a/minecraft/protocol/packet/correct_player_move_prediction.go b/minecraft/protocol/packet/correct_player_move_prediction.go index 8a15cc7e..6fa352f1 100644 --- a/minecraft/protocol/packet/correct_player_move_prediction.go +++ b/minecraft/protocol/packet/correct_player_move_prediction.go @@ -25,6 +25,8 @@ type CorrectPlayerMovePrediction struct { // Rotation is the rotation of the player at the tick written in the field below. It is only included if // PredictionType is PredictionTypeVehicle. Rotation mgl32.Vec2 + // VehicleAngularVelocity is the angular velocity of the vehicle that the rider is riding. + VehicleAngularVelocity protocol.Optional[float32] // OnGround specifies if the player was on the ground at the time of the tick below. OnGround bool // Tick is the tick of the movement which was corrected by this packet. @@ -42,6 +44,7 @@ func (pk *CorrectPlayerMovePrediction) Marshal(io protocol.IO) { io.Vec3(&pk.Delta) if pk.PredictionType == PredictionTypeVehicle { io.Vec2(&pk.Rotation) + protocol.OptionalFunc(io, &pk.VehicleAngularVelocity, io.Float32) } io.Bool(&pk.OnGround) io.Varuint64(&pk.Tick) diff --git a/minecraft/protocol/packet/player_armour_damage.go b/minecraft/protocol/packet/player_armour_damage.go index b173209b..a9492cf3 100644 --- a/minecraft/protocol/packet/player_armour_damage.go +++ b/minecraft/protocol/packet/player_armour_damage.go @@ -4,6 +4,14 @@ import ( "github.com/sandertv/gophertunnel/minecraft/protocol" ) +const ( + PlayerArmourDamageFlagHelmet = 1 << (iota + 1) + PlayerArmourDamageFlagChestplate + PlayerArmourDamageFlagLeggings + PlayerArmourDamageFlagBoots + PlayerArmourDamageFlagBody +) + // PlayerArmourDamage is sent by the server to damage the armour of a player. It is a very efficient packet, // but generally it's much easier to just send a slot update for the damaged armour. type PlayerArmourDamage struct { @@ -19,6 +27,8 @@ type PlayerArmourDamage struct { LeggingsDamage int32 // BootsDamage is the amount of damage that should be dealt to the boots. BootsDamage int32 + // BodyDamage is the amount of damage that should be dealt to the body. + BodyDamage int32 } // ID ... @@ -28,24 +38,29 @@ func (pk *PlayerArmourDamage) ID() uint32 { func (pk *PlayerArmourDamage) Marshal(io protocol.IO) { io.Uint8(&pk.Bitset) - if pk.Bitset&0b0001 != 0 { + if pk.Bitset&PlayerArmourDamageFlagHelmet != 0 { io.Varint32(&pk.HelmetDamage) } else { pk.HelmetDamage = 0 } - if pk.Bitset&0b0010 != 0 { + if pk.Bitset&PlayerArmourDamageFlagChestplate != 0 { io.Varint32(&pk.ChestplateDamage) } else { pk.ChestplateDamage = 0 } - if pk.Bitset&0b0100 != 0 { + if pk.Bitset&PlayerArmourDamageFlagLeggings != 0 { io.Varint32(&pk.LeggingsDamage) } else { pk.LeggingsDamage = 0 } - if pk.Bitset&0b1000 != 0 { + if pk.Bitset&PlayerArmourDamageFlagBoots != 0 { io.Varint32(&pk.BootsDamage) } else { pk.BootsDamage = 0 } + if pk.Bitset&PlayerArmourDamageFlagBody != 0 { + io.Varint32(&pk.BodyDamage) + } else { + pk.BodyDamage = 0 + } } diff --git a/minecraft/protocol/packet/server_bound_diagnostics.go b/minecraft/protocol/packet/server_bound_diagnostics.go index 17b4c4fd..5173e267 100644 --- a/minecraft/protocol/packet/server_bound_diagnostics.go +++ b/minecraft/protocol/packet/server_bound_diagnostics.go @@ -5,7 +5,7 @@ import ( ) // ServerBoundDiagnostics is sent by the client to tell the server about the performance diagnostics -// of the client. It is +// of the client. It is sent by the client roughly every 500ms or 10 in-game ticks. type ServerBoundDiagnostics struct { // AverageFramesPerSecond is the average amount of frames per second that the client has been // running at. diff --git a/minecraft/protocol/packet/server_bound_loading_screen.go b/minecraft/protocol/packet/server_bound_loading_screen.go index 66ffce22..76188b8f 100644 --- a/minecraft/protocol/packet/server_bound_loading_screen.go +++ b/minecraft/protocol/packet/server_bound_loading_screen.go @@ -16,7 +16,7 @@ type ServerBoundLoadingScreen struct { // Type is the type of the loading screen event. It is one of the constants that may be found above. Type int32 // LoadingScreenID is the ID of the screen that was previously sent by the server in the ChangeDimension - // packet. The server should validate that the screen ID is correct. + // packet. The server should validate that the ID matches the last one it sent. LoadingScreenID protocol.Optional[uint32] } diff --git a/minecraft/protocol/packet/stop_sound.go b/minecraft/protocol/packet/stop_sound.go index 694ddcf7..bd129467 100644 --- a/minecraft/protocol/packet/stop_sound.go +++ b/minecraft/protocol/packet/stop_sound.go @@ -13,9 +13,8 @@ type StopSound struct { // StopAll specifies if all sounds currently playing to the player should be stopped. If set to true, the // SoundName field may be left empty. StopAll bool - // StopMusic specifies if all music currently playing to the player should be stopped. If set to true, the - // SoundName field may be left empty. - StopMusic bool + // StopMusicLegacy is currently unknown. + StopMusicLegacy bool } // ID ... @@ -26,4 +25,5 @@ func (*StopSound) ID() uint32 { func (pk *StopSound) Marshal(io protocol.IO) { io.String(&pk.SoundName) io.Bool(&pk.StopAll) + io.Bool(&pk.StopMusicLegacy) } From 324cf6ed74a36ce1d7a59058141d65076d262273 Mon Sep 17 00:00:00 2001 From: TwistedAsylumMC Date: Wed, 14 Aug 2024 11:07:04 +0100 Subject: [PATCH 3/4] protocol/inventory.go: ClientPrediction is a Varuint32 --- minecraft/protocol/inventory.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/minecraft/protocol/inventory.go b/minecraft/protocol/inventory.go index 54099587..2fd31ad4 100644 --- a/minecraft/protocol/inventory.go +++ b/minecraft/protocol/inventory.go @@ -181,7 +181,7 @@ type UseItemTransactionData struct { BlockRuntimeID uint32 // ClientPrediction is the client's prediction on the output of the transaction. It is one of the client // prediction found in the constants above. - ClientPrediction byte + ClientPrediction uint32 } const ( @@ -246,7 +246,7 @@ func (data *UseItemTransactionData) Marshal(r IO) { r.Vec3(&data.Position) r.Vec3(&data.ClickedPosition) r.Varuint32(&data.BlockRuntimeID) - r.Uint8(&data.ClientPrediction) + r.Varuint32(&data.ClientPrediction) } // Marshal ... From 91f4c189ff1b972aa3ce5b9479a83ac2234d05e7 Mon Sep 17 00:00:00 2001 From: Doge Date: Wed, 14 Aug 2024 15:35:01 +0300 Subject: [PATCH 4/4] minecraft/protocol: handle new `UseItemTransactionData` fields in `PlayerInventoryAction` (#261) --- minecraft/protocol/reader.go | 2 ++ minecraft/protocol/writer.go | 2 ++ 2 files changed, 4 insertions(+) diff --git a/minecraft/protocol/reader.go b/minecraft/protocol/reader.go index 9be72597..86bbf8be 100644 --- a/minecraft/protocol/reader.go +++ b/minecraft/protocol/reader.go @@ -251,6 +251,7 @@ func (r *Reader) PlayerInventoryAction(x *UseItemTransactionData) { } Slice(r, &x.Actions) r.Varuint32(&x.ActionType) + r.Varuint32(&x.TriggerType) r.BlockPos(&x.BlockPosition) r.Varint32(&x.BlockFace) r.Varint32(&x.HotBarSlot) @@ -258,6 +259,7 @@ func (r *Reader) PlayerInventoryAction(x *UseItemTransactionData) { r.Vec3(&x.Position) r.Vec3(&x.ClickedPosition) r.Varuint32(&x.BlockRuntimeID) + r.Varuint32(&x.ClientPrediction) } // GameRule reads a GameRule x from the Reader. diff --git a/minecraft/protocol/writer.go b/minecraft/protocol/writer.go index cd8712e0..52dc59bc 100644 --- a/minecraft/protocol/writer.go +++ b/minecraft/protocol/writer.go @@ -165,6 +165,7 @@ func (w *Writer) PlayerInventoryAction(x *UseItemTransactionData) { } Slice(w, &x.Actions) w.Varuint32(&x.ActionType) + w.Varuint32(&x.TriggerType) w.BlockPos(&x.BlockPosition) w.Varint32(&x.BlockFace) w.Varint32(&x.HotBarSlot) @@ -172,6 +173,7 @@ func (w *Writer) PlayerInventoryAction(x *UseItemTransactionData) { w.Vec3(&x.Position) w.Vec3(&x.ClickedPosition) w.Varuint32(&x.BlockRuntimeID) + w.Varuint32(&x.ClientPrediction) } // GameRule writes a GameRule x to the Writer.