1.21.60 support

This commit is contained in:
AkmalFairuz
2025-02-13 10:42:17 +07:00
parent 1eea0360a5
commit f424b90afd
28 changed files with 10000 additions and 96 deletions

Binary file not shown.

Binary file not shown.

File diff suppressed because it is too large Load Diff

View File

@@ -3,8 +3,8 @@ package legacyver
import (
"fmt"
"github.com/akmalfairuz/legacy-version/internal/item"
"github.com/akmalfairuz/legacy-version/legacyver/proto"
"github.com/akmalfairuz/legacy-version/mapping"
"github.com/akmalfairuz/legacy-version/packbuilder"
"github.com/df-mc/dragonfly/server/world"
"github.com/samber/lo"
"github.com/sandertv/gophertunnel/minecraft"
@@ -39,6 +39,10 @@ type ItemTranslator interface {
Register(item world.CustomItem, replacement string)
// CustomItems lists all custom items used as substitutes, with the runtime id as the key
CustomItems() map[int32]world.CustomItem
// DowngradeLegacyItemRegistry ...
DowngradeLegacyItemRegistry(entries []proto.LegacyItemRegistryEntry) []proto.LegacyItemRegistryEntry
// UpgradeLegacyItemRegistry ...
UpgradeLegacyItemRegistry(entries []proto.LegacyItemRegistryEntry) []proto.LegacyItemRegistryEntry
}
type DefaultItemTranslator struct {
@@ -480,51 +484,84 @@ func (t *DefaultItemTranslator) DowngradeItemPackets(pks []packet.Packet, _ *min
})
pk.EventData = (itemType.NetworkID << 16) | int32(itemType.MetadataValue)
}
case *packet.StartGame:
for i, entry := range pk.Items {
if !entry.ComponentBased {
itemType := t.DowngradeItemType(protocol.ItemType{
NetworkID: int32(entry.RuntimeID),
MetadataValue: 0,
})
if itemType.NetworkID == t.mapping.Air() {
removeIndex(pk.Items, i)
continue
}
entry.RuntimeID = int16(itemType.NetworkID)
var ok bool
if entry.Name, ok = t.mapping.ItemRuntimeIDToName(itemType.NetworkID); !ok {
panic(itemType)
}
} else {
t.latest.RegisterEntry(entry.Name)
entry.RuntimeID = int16(t.mapping.RegisterEntry(entry.Name))
}
pk.Items[i] = entry
}
for rid, i := range t.CustomItems() {
name, _ := i.EncodeItem()
pk.Items = append(pk.Items, protocol.ItemEntry{
Name: name,
RuntimeID: int16(rid),
ComponentBased: true,
})
}
case *packet.ItemComponent:
for _, i := range t.CustomItems() {
name, _ := i.EncodeItem()
pk.Items = append(pk.Items, protocol.ItemComponentEntry{
Name: name,
Data: packbuilder.Components(i),
})
}
//case *packet.ItemRegistry:
// for _, i := range t.CustomItems() {
// name, _ := i.EncodeItem()
// pk.Items = append(pk.Items, protocol.ItemEntry{
// Name: name,
// Data: packbuilder.Components(i),
// })
// }
}
result = append(result, pk)
}
return result
}
func (t *DefaultItemTranslator) DowngradeLegacyItemRegistry(entries []proto.LegacyItemRegistryEntry) []proto.LegacyItemRegistryEntry {
for i, entry := range entries {
if !entry.ComponentBased {
itemType := t.DowngradeItemType(protocol.ItemType{
NetworkID: int32(entry.RuntimeID),
MetadataValue: 0,
})
if itemType.NetworkID == t.mapping.Air() {
removeIndex(entries, i)
continue
}
entry.RuntimeID = int16(itemType.NetworkID)
var ok bool
if entry.Name, ok = t.mapping.ItemRuntimeIDToName(itemType.NetworkID); !ok {
panic(itemType)
}
} else {
t.latest.RegisterEntryRID(entry.Name, int32(entry.RuntimeID), 2)
entry.RuntimeID = int16(t.mapping.RegisterEntry(entry.Name))
}
entries[i] = entry
}
for rid, i := range t.CustomItems() {
name, _ := i.EncodeItem()
entries = append(entries, proto.LegacyItemRegistryEntry{
Name: name,
RuntimeID: int16(rid),
ComponentBased: true,
})
}
return entries
}
func (t *DefaultItemTranslator) UpgradeLegacyItemRegistry(entries []proto.LegacyItemRegistryEntry) []proto.LegacyItemRegistryEntry {
for i, entry := range entries {
if !entry.ComponentBased {
itemType := t.UpgradeItemType(protocol.ItemType{
NetworkID: int32(entry.RuntimeID),
MetadataValue: 0,
})
entry.RuntimeID = int16(itemType.NetworkID)
var ok bool
if entry.Name, ok = t.latest.ItemRuntimeIDToName(itemType.NetworkID); !ok {
panic(itemType)
}
} else {
t.latest.RegisterEntryRID(entry.Name, int32(entry.RuntimeID), 2)
entry.RuntimeID = int16(t.mapping.RegisterEntry(entry.Name))
}
entries[i] = entry
}
for rid, i := range t.CustomItems() {
name, _ := i.EncodeItem()
entries = append(entries, proto.LegacyItemRegistryEntry{
Name: name,
RuntimeID: int16(rid),
ComponentBased: true,
})
}
return entries
}
func (t *DefaultItemTranslator) UpgradeItemPackets(pks []packet.Packet, _ *minecraft.Conn) (result []packet.Packet) {
for _, pk := range pks {
switch pk := pk.(type) {
@@ -703,41 +740,14 @@ func (t *DefaultItemTranslator) UpgradeItemPackets(pks []packet.Packet, _ *minec
})
pk.EventData = (itemType.NetworkID << 16) | int32(itemType.MetadataValue)
}
case *packet.StartGame:
for i, entry := range pk.Items {
if !entry.ComponentBased {
itemType := t.UpgradeItemType(protocol.ItemType{
NetworkID: int32(entry.RuntimeID),
MetadataValue: 0,
})
entry.RuntimeID = int16(itemType.NetworkID)
var ok bool
if entry.Name, ok = t.latest.ItemRuntimeIDToName(itemType.NetworkID); !ok {
panic(itemType)
}
} else {
t.latest.RegisterEntry(entry.Name)
entry.RuntimeID = int16(t.mapping.RegisterEntry(entry.Name))
}
pk.Items[i] = entry
}
for rid, i := range t.CustomItems() {
name, _ := i.EncodeItem()
pk.Items = append(pk.Items, protocol.ItemEntry{
Name: name,
RuntimeID: int16(rid),
ComponentBased: true,
})
}
case *packet.ItemComponent:
for _, i := range t.CustomItems() {
name, _ := i.EncodeItem()
pk.Items = append(pk.Items, protocol.ItemComponentEntry{
Name: name,
Data: packbuilder.Components(i),
})
}
//case *packet.ItemRegistry:
// for _, i := range t.CustomItems() {
// name, _ := i.EncodeItem()
// pk.Items = append(pk.Items, protocol.ItemEntry{
// Name: name,
// Data: packbuilder.Components(i),
// })
// }
}
result = append(result, pk)
}

View File

@@ -53,7 +53,7 @@ type AddPlayer struct {
// attributes of the entity.
EntityProperties protocol.EntityProperties
// AbilityData represents various data about the abilities of a player, such as ability layers or permissions.
AbilityData protocol.AbilityData
AbilityData proto.AbilityData
// EntityLinks is a list of entity links that are currently active on the player. These links alter the
// way the player shows up when first spawned in terms of it shown as riding an entity. Setting these
// links is important for new viewers to see the player is riding another entity.

View File

@@ -0,0 +1,115 @@
package legacypacket
import (
"github.com/akmalfairuz/legacy-version/legacyver/proto"
"github.com/sandertv/gophertunnel/minecraft/protocol"
"github.com/sandertv/gophertunnel/minecraft/protocol/packet"
)
const (
BossEventShow = iota
BossEventRegisterPlayer
BossEventHide
BossEventUnregisterPlayer
BossEventHealthPercentage
BossEventTitle
BossEventAppearanceProperties
BossEventTexture
BossEventRequest
)
const (
BossEventColourGrey = iota
BossEventColourBlue
BossEventColourRed
BossEventColourGreen
BossEventColourYellow
BossEventColourPurple
BossEventColourWhite
)
// BossEvent is sent by the server to make a specific 'boss event' occur in the
// world. It includes features such as showing a boss bar to the player and
// turning the sky dark.
type BossEvent struct {
// BossEntityUniqueID is the unique ID of the boss entity that the boss
// event sent involves. By default, the health percentage and title of the
// boss bar depend on the health and name tag of this entity. If
// BossEntityUniqueID is the same as the client's entity unique ID, its
// HealthPercentage and BossBarTitle can be freely altered.
BossEntityUniqueID int64
// EventType is the type of the event. The fields written depend on the
// event type set, and some event types are sent by the client, whereas
// others are sent by the server. The event type is one of the constants
// above.
EventType uint32
// PlayerUniqueID is the unique ID of the player that is registered to or
// unregistered from the boss fight. It is set if EventType is either
// BossEventRegisterPlayer or BossEventUnregisterPlayer.
PlayerUniqueID int64
// BossBarTitle is the title shown above the boss bar. It may be set to set
// a different title if the BossEntityUniqueID matches the client's entity
// unique ID.
BossBarTitle string
// FilteredBossBarTitle is a filtered version of BossBarTitle with all the
// profanity removed. The client will use this over BossBarTitle if this
// field is not empty and they have the "Filter Profanity" setting enabled.
FilteredBossBarTitle string
// HealthPercentage is the percentage of health that is shown in the boss
// bar (0.0-1.0). The HealthPercentage may be set to a specific value if the
// BossEntityUniqueID matches the client's entity unique ID.
HealthPercentage float32
// ScreenDarkening currently seems not to do anything.
ScreenDarkening uint16
// Colour is the colour of the boss bar that is shown when a player is
// subscribed. It is only set if the EventType is BossEventShow,
// BossEventAppearanceProperties or BossEventTexture. This is functional as
// of 1.18 and can be any of the BossEventColour constants listed above.
Colour uint32
// Overlay is the overlay of the boss bar that is shown on top of the boss
// bar when a player is subscribed. It currently does not function. It is
// only set if the EventType is BossEventShow, BossEventAppearanceProperties
// or BossEventTexture.
Overlay uint32
}
// ID ...
func (*BossEvent) ID() uint32 {
return packet.IDBossEvent
}
func (pk *BossEvent) Marshal(io protocol.IO) {
io.Varint64(&pk.BossEntityUniqueID)
io.Varuint32(&pk.EventType)
switch pk.EventType {
case BossEventShow:
io.String(&pk.BossBarTitle)
if proto.IsProtoGTE(io, proto.ID776) {
io.String(&pk.FilteredBossBarTitle)
}
io.Float32(&pk.HealthPercentage)
io.Uint16(&pk.ScreenDarkening)
io.Varuint32(&pk.Colour)
io.Varuint32(&pk.Overlay)
case BossEventRegisterPlayer, BossEventUnregisterPlayer, BossEventRequest:
io.Varint64(&pk.PlayerUniqueID)
case BossEventHide:
// No extra payload for this boss event type.
case BossEventHealthPercentage:
io.Float32(&pk.HealthPercentage)
case BossEventTitle:
io.String(&pk.BossBarTitle)
if proto.IsProtoGTE(io, proto.ID776) {
io.String(&pk.FilteredBossBarTitle)
}
case BossEventAppearanceProperties:
io.Uint16(&pk.ScreenDarkening)
io.Varuint32(&pk.Colour)
io.Varuint32(&pk.Overlay)
case BossEventTexture:
io.Varuint32(&pk.Colour)
io.Varuint32(&pk.Overlay)
default:
io.UnknownEnumOption(pk.EventType, "boss event type")
}
}

View File

@@ -0,0 +1,36 @@
package legacypacket
import (
"github.com/akmalfairuz/legacy-version/legacyver/proto"
"github.com/sandertv/gophertunnel/minecraft/protocol"
"github.com/sandertv/gophertunnel/minecraft/protocol/packet"
)
const (
CameraAunAssistPresetOperationSet = iota
CameraAunAssistPresetOperationAddToExisting
)
// CameraAimAssistPresets is sent by the server to the client to provide a list of categories and presets
// that can be used when sending a CameraAimAssist packet or a CameraInstruction including aim assist.
type CameraAimAssistPresets struct {
// CategoryGroups is a list of groups of categories which can be referenced by one of the Presets.
CategoryGroups []protocol.CameraAimAssistCategoryGroup
// Presets is a list of presets which define a base for how aim assist should behave
Presets []protocol.CameraAimAssistPreset
// Operation is the operation to perform with the presets. It is one of the constants above.
Operation byte
}
// ID ...
func (*CameraAimAssistPresets) ID() uint32 {
return packet.IDCameraAimAssistPresets
}
func (pk *CameraAimAssistPresets) Marshal(io protocol.IO) {
protocol.Slice(io, &pk.CategoryGroups)
protocol.Slice(io, &pk.Presets)
if proto.IsProtoGTE(io, proto.ID776) {
io.Uint8(&pk.Operation)
}
}

View File

@@ -0,0 +1,24 @@
package legacypacket
import (
"github.com/akmalfairuz/legacy-version/legacyver/proto"
"github.com/sandertv/gophertunnel/minecraft/protocol"
"github.com/sandertv/gophertunnel/minecraft/protocol/packet"
)
// ClientCheatAbility functions the same as UpdateAbilities. It is unclear why these two were separated.
//
// Deprecated: ClientCheatAbility is deprecated as of 1.20.10.
type ClientCheatAbility struct {
// AbilityData represents various data about the abilities of a player, such as ability layers or permissions.
AbilityData proto.AbilityData
}
// ID ...
func (*ClientCheatAbility) ID() uint32 {
return packet.IDClientCheatAbility
}
func (pk *ClientCheatAbility) Marshal(io protocol.IO) {
protocol.Single(io, &pk.AbilityData)
}

View File

@@ -0,0 +1,88 @@
package legacypacket
import (
"github.com/akmalfairuz/legacy-version/legacyver/proto"
"github.com/sandertv/gophertunnel/minecraft/protocol"
"github.com/sandertv/gophertunnel/minecraft/protocol/packet"
)
const (
CommandBlockImpulse = iota
CommandBlockRepeating
CommandBlockChain
)
// CommandBlockUpdate is sent by the client to update a command block at a specific position. The command
// block may be either a physical block or an entity.
type CommandBlockUpdate struct {
// Block specifies if the command block updated was an actual physical block. If false, the command block
// is in a minecart and has an entity runtime ID instead.
Block bool
// Position is the position of the command block updated. It is only set if Block is set to true. Nothing
// happens if no command block is set at this position.
Position protocol.BlockPos
// Mode is the mode of the command block. It is either CommandBlockImpulse, CommandBlockChain or
// CommandBlockRepeat. It is only set if Block is set to true.
Mode uint32
// NeedsRedstone specifies if the command block needs to be powered by redstone to be activated. If false,
// the command block is always active. The field is only set if Block is set to true.
NeedsRedstone bool
// Conditional specifies the behaviour of the command block if the command block before it (the opposite
// side of the direction the arrow if facing) fails to execute. If set to false, it will activate at all
// times, whereas if set to true, it will activate only if the previous command block executed
// successfully. The field is only set if Block is set to true.
Conditional bool
// MinecartEntityRuntimeID is the runtime ID of the minecart entity carrying the command block that is
// updated. It is set only if Block is set to false.
MinecartEntityRuntimeID uint64
// Command is the command currently entered in the command block. This is the command that is executed
// when the command block is activated.
Command string
// LastOutput is the output of the last command executed by the command block. It may be left empty to
// show simply no output at all, in combination with setting ShouldTrackOutput to false.
LastOutput string
// Name is the name of the command block updated. If not empty, it will show this name hovering above the
// command block when hovering over the block with the cursor.
Name string
// FilteredName is a filtered version of Name with all the profanity removed. The client will use this
// over Name if this field is not empty and they have the "Filter Profanity" setting enabled.
FilteredName string
// ShouldTrackOutput specifies if the command block tracks output. If set to false, the output box won't
// be shown within the command block.
ShouldTrackOutput bool
// TickDelay is the delay in ticks between executions of a command block, if it is a repeating command
// block.
TickDelay int32
// ExecuteOnFirstTick specifies if the command block should execute on the first tick, AKA as soon as the
// command block is enabled.
ExecuteOnFirstTick bool
}
// ID ...
func (*CommandBlockUpdate) ID() uint32 {
return packet.IDCommandBlockUpdate
}
func (pk *CommandBlockUpdate) Marshal(io protocol.IO) {
io.Bool(&pk.Block)
if pk.Block {
io.UBlockPos(&pk.Position)
io.Varuint32(&pk.Mode)
io.Bool(&pk.NeedsRedstone)
io.Bool(&pk.Conditional)
} else {
io.Varuint64(&pk.MinecartEntityRuntimeID)
}
io.String(&pk.Command)
io.String(&pk.LastOutput)
io.String(&pk.Name)
if proto.IsProtoGTE(io, proto.ID776) {
io.String(&pk.FilteredName)
}
io.Bool(&pk.ShouldTrackOutput)
io.Int32(&pk.TickDelay)
io.Bool(&pk.ExecuteOnFirstTick)
}

View File

@@ -0,0 +1,53 @@
package legacypacket
import (
"github.com/akmalfairuz/legacy-version/legacyver/proto"
"github.com/sandertv/gophertunnel/minecraft/protocol"
"github.com/sandertv/gophertunnel/minecraft/protocol/packet"
)
// CreativeContent is a packet sent by the server to set the creative inventory's content for a player.
// Introduced in 1.16, this packet replaces the previous method - sending an InventoryContent packet with
// creative inventory window ID.
// As of v1.21.60, this packet is no longer required to be sent as part of the login sequence however the
// client will crash if they try to open their creative inventory before receiving this packet. Every item
// must be part of a group, any items that are not part of a group will need to reference an "anonymous group"
// which has an empty name OR no icon. The order of Groups and Items is how the client will render items
// in the creative inventory compared to the previous, hard coded order.
//
// Below is an example of defining 2 ungrouped items, 2 grouped items and then another 2 ungrouped items, all
// in the nature category.
//
// CreativeContent{
// Groups: []protocol.CreativeGroup{
// {Category: 1}, // No name or icon, this is the "anonymous group"
// {Category: 1, Name: "itemGroup.name.planks", Icon: protocol.ItemStack{...}}, // A "planks" group
// {Category: 1}, // Another "anonymous group"
// },
// Items: []protocol.CreativeItem{
// {CreativeItemNetworkID: 0, Item: protocol.ItemStack{...}, GroupIndex: 0}, // Ungrouped before "planks"
// {CreativeItemNetworkID: 1, Item: protocol.ItemStack{...}, GroupIndex: 0}, // Ungrouped before "planks"
// {CreativeItemNetworkID: 2, Item: protocol.ItemStack{...}, GroupIndex: 1}, // Grouped under the "planks" group
// {CreativeItemNetworkID: 3, Item: protocol.ItemStack{...}, GroupIndex: 1}, // Grouped under the "planks" group
// {CreativeItemNetworkID: 4, Item: protocol.ItemStack{...}, GroupIndex: 2}, // Ungrouped after "planks"
// {CreativeItemNetworkID: 5, Item: protocol.ItemStack{...}, GroupIndex: 2}, // Ungrouped after "planks"
// }
// }
type CreativeContent struct {
// Groups is a list of the groups that should be added to the creative inventory.
Groups []protocol.CreativeGroup
// Items is a list of the items that should be added to the creative inventory.
Items []proto.CreativeItem
}
// ID ...
func (*CreativeContent) ID() uint32 {
return packet.IDCreativeContent
}
func (pk *CreativeContent) Marshal(io protocol.IO) {
if proto.IsProtoGTE(io, proto.ID776) {
protocol.Slice(io, &pk.Groups)
}
protocol.Slice(io, &pk.Items)
}

View File

@@ -0,0 +1,25 @@
package legacypacket
import (
"github.com/akmalfairuz/legacy-version/legacyver/proto"
"github.com/sandertv/gophertunnel/minecraft/protocol"
"github.com/sandertv/gophertunnel/minecraft/protocol/packet"
)
// ItemRegistry is sent by the server to send the client a list of available items and attach client-side
// components to a custom item. This packet was formerly known as the ItemComponent packet before 1.21.60,
// which did not include item definitions but only the components.
type ItemRegistry struct {
// Items is a list of all items with their legacy IDs which are available in the game. Failing to send any
// of the items that are in the game will crash mobile clients. Any custom components are also attached to
// the items in this list.
Items []proto.ItemEntry
}
// ID ...
func (*ItemRegistry) ID() uint32 {
return packet.IDItemRegistry
}
func (pk *ItemRegistry) Marshal(io protocol.IO) {
protocol.Slice(io, &pk.Items)
}

View File

@@ -200,7 +200,7 @@ type StartGame struct {
Blocks []protocol.BlockEntry
// Items is a list of all items with their legacy IDs which are available in the game. Failing to send any
// of the items that are in the game will crash mobile clients.
Items []protocol.ItemEntry
Items []proto.LegacyItemRegistryEntry
// MultiPlayerCorrelationID is a unique ID specifying the multi-player session of the player. A random
// UUID should be filled out for this field.
MultiPlayerCorrelationID string
@@ -316,7 +316,11 @@ func (pk *StartGame) Marshal(io protocol.IO) {
io.Int64(&pk.Time)
io.Varint32(&pk.EnchantmentSeed)
protocol.Slice(io, &pk.Blocks)
protocol.Slice(io, &pk.Items)
if proto.IsProtoLT(io, proto.ID776) {
protocol.Slice(io, &pk.Items)
} else {
proto.EmptySlice(io, &pk.Items)
}
io.String(&pk.MultiPlayerCorrelationID)
io.Bool(&pk.ServerAuthoritativeInventory)
io.String(&pk.GameVersion)

View File

@@ -0,0 +1,82 @@
package legacypacket
import (
"github.com/akmalfairuz/legacy-version/legacyver/proto"
"github.com/sandertv/gophertunnel/minecraft/protocol"
"github.com/sandertv/gophertunnel/minecraft/protocol/packet"
)
const (
StructureBlockData = iota
StructureBlockSave
StructureBlockLoad
StructureBlockCorner
StructureBlockInvalid
StructureBlockExport
)
const (
StructureRedstoneSaveModeMemory = iota
StructureRedstoneSaveModeDisk
)
// StructureBlockUpdate is sent by the client when it updates a structure block using the in-game UI. The
// data it contains depends on the type of structure block that it is. In Minecraft Bedrock Edition v1.11,
// there is only the Export structure block type, but in v1.13 the ones present in Java Edition will,
// according to the wiki, be added too.
type StructureBlockUpdate struct {
// Position is the position of the structure block that is updated.
Position protocol.BlockPos
// StructureName is the name of the structure that was set in the structure block's UI. This is the name
// used to export the structure to a file.
StructureName string
// FilteredStructureName is a filtered version of StructureName with all the profanity removed. The client
// will use this over StructureName if this field is not empty and they have the "Filter Profanity"
// setting enabled.
FilteredStructureName string
// DataField is the name of a function to run, usually used during natural generation. A description can
// be found here: https://minecraft.wiki/w/Structure_Block#Data.
DataField string
// IncludePlayers specifies if the 'Include Players' toggle has been enabled, meaning players are also
// exported by the structure block.
IncludePlayers bool
// ShowBoundingBox specifies if the structure block should have its bounds outlined. A thin line will
// encapsulate the bounds of the structure if set to true.
ShowBoundingBox bool
// StructureBlockType is the type of the structure block updated. A list of structure block types that
// will be used can be found in the constants above.
StructureBlockType int32
// Settings is a struct of settings that should be used for exporting the structure. These settings are
// identical to the last sent in the StructureBlockUpdate packet by the client.
Settings protocol.StructureSettings
// RedstoneSaveMode is the mode that should be used to save the structure when used with redstone. In
// Java Edition, this is always stored in memory, but in Bedrock Edition it can be stored either to disk
// or memory. See the constants above for the options.
RedstoneSaveMode int32
// ShouldTrigger specifies if the structure block should be triggered immediately after this packet
// reaches the server.
ShouldTrigger bool
// Waterlogged specifies if non-air blocks replace water or combine with water.
Waterlogged bool
}
// ID ...
func (*StructureBlockUpdate) ID() uint32 {
return packet.IDStructureBlockUpdate
}
func (pk *StructureBlockUpdate) Marshal(io protocol.IO) {
io.UBlockPos(&pk.Position)
io.String(&pk.StructureName)
if proto.IsProtoGTE(io, proto.ID776) {
io.String(&pk.FilteredStructureName)
}
io.String(&pk.DataField)
io.Bool(&pk.IncludePlayers)
io.Bool(&pk.ShowBoundingBox)
io.Varint32(&pk.StructureBlockType)
protocol.Single(io, &pk.Settings)
io.Varint32(&pk.RedstoneSaveMode)
io.Bool(&pk.ShouldTrigger)
io.Bool(&pk.Waterlogged)
}

View File

@@ -0,0 +1,23 @@
package legacypacket
import (
"github.com/akmalfairuz/legacy-version/legacyver/proto"
"github.com/sandertv/gophertunnel/minecraft/protocol"
"github.com/sandertv/gophertunnel/minecraft/protocol/packet"
)
// UpdateAbilities is a packet sent from the server to the client to update the abilities of the player. It, along with
// the UpdateAdventureSettings packet, are replacements of the AdventureSettings packet since v1.19.10.
type UpdateAbilities struct {
// AbilityData represents various data about the abilities of a player, such as ability layers or permissions.
AbilityData proto.AbilityData
}
// ID ...
func (*UpdateAbilities) ID() uint32 {
return packet.IDUpdateAbilities
}
func (pk *UpdateAbilities) Marshal(io protocol.IO) {
protocol.Single(io, &pk.AbilityData)
}

108
legacyver/proto/ability.go Normal file
View File

@@ -0,0 +1,108 @@
package proto
import (
"github.com/sandertv/gophertunnel/minecraft/protocol"
)
// AbilityData represents various data about the abilities of a player, such as ability layers or permissions.
type AbilityData struct {
// EntityUniqueID is a unique identifier of the player. It appears it is not required to fill this field
// out with a correct value. Simply writing 0 seems to work.
EntityUniqueID int64
// PlayerPermissions is the permission level of the player as it shows up in the player list built up using
// the PlayerList packet.
PlayerPermissions byte
// CommandPermissions is a set of permissions that specify what commands a player is allowed to execute.
CommandPermissions byte
// Layers contains all ability layers and their potential values. This should at least have one entry, being the
// base layer.
Layers []AbilityLayer
}
// Marshal encodes/decodes an AbilityData.
func (x *AbilityData) Marshal(r protocol.IO) {
r.Int64(&x.EntityUniqueID)
r.Uint8(&x.PlayerPermissions)
r.Uint8(&x.CommandPermissions)
protocol.SliceUint8Length(r, &x.Layers)
}
// ToLatest ...
func (x *AbilityData) ToLatest() protocol.AbilityData {
layers := make([]protocol.AbilityLayer, len(x.Layers))
for i, layer := range x.Layers {
layers[i] = layer.ToLatest()
}
return protocol.AbilityData{
EntityUniqueID: x.EntityUniqueID,
PlayerPermissions: x.PlayerPermissions,
CommandPermissions: x.CommandPermissions,
Layers: layers,
}
}
// FromLatest ...
func (x *AbilityData) FromLatest(y protocol.AbilityData) AbilityData {
layers := make([]AbilityLayer, len(y.Layers))
for i, layer := range y.Layers {
layers[i] = (&AbilityLayer{}).FromLatest(layer)
}
x.EntityUniqueID = y.EntityUniqueID
x.PlayerPermissions = y.PlayerPermissions
x.CommandPermissions = y.CommandPermissions
x.Layers = layers
return *x
}
// AbilityLayer represents the abilities of a specific layer, such as the base layer or the spectator layer.
type AbilityLayer struct {
// Type represents the type of the layer. This is one of the AbilityLayerType constants defined above.
Type uint16
// Abilities is a set of abilities that are enabled for the layer. This is one of the Ability constants defined
// above.
Abilities uint32
// Values is a set of values that are associated with the enabled abilities, representing the values of the
// abilities.
Values uint32
// FlySpeed is the default horizontal fly speed of the layer.
FlySpeed float32
// VerticalFlySpeed is the default vertical fly speed of the layer.
VerticalFlySpeed float32
// WalkSpeed is the default walk speed of the layer.
WalkSpeed float32
}
// Marshal encodes/decodes an AbilityLayer.
func (x *AbilityLayer) Marshal(r protocol.IO) {
r.Uint16(&x.Type)
r.Uint32(&x.Abilities)
r.Uint32(&x.Values)
r.Float32(&x.FlySpeed)
if IsProtoGTE(r, ID776) {
r.Float32(&x.VerticalFlySpeed)
}
r.Float32(&x.WalkSpeed)
}
// ToLatest ...
func (x *AbilityLayer) ToLatest() protocol.AbilityLayer {
return protocol.AbilityLayer{
Type: x.Type,
Abilities: x.Abilities,
Values: x.Values,
FlySpeed: x.FlySpeed,
VerticalFlySpeed: x.VerticalFlySpeed,
WalkSpeed: x.WalkSpeed,
}
}
// FromLatest ...
func (x *AbilityLayer) FromLatest(y protocol.AbilityLayer) AbilityLayer {
x.Type = y.Type
x.Abilities = y.Abilities
x.Values = y.Values
x.FlySpeed = y.FlySpeed
x.VerticalFlySpeed = y.VerticalFlySpeed
x.WalkSpeed = y.WalkSpeed
return *x
}

View File

@@ -41,6 +41,10 @@ type CameraPreset struct {
// Radius is only used in a follow_orbit camera and controls how far away from the player the camera should
// be rendered.
Radius protocol.Optional[float32]
// MinYawLimit is the minimum yaw limit of the camera.
MinYawLimit protocol.Optional[float32]
// MaxYawLimit is the maximum yaw limit of the camera.
MaxYawLimit protocol.Optional[float32]
// AudioListener defines where the audio should be played from when using this preset. This is one of the
// constants above.
AudioListener protocol.Optional[byte]
@@ -95,6 +99,8 @@ func (x *CameraPreset) ToLatest() protocol.CameraPreset {
ViewOffset: x.ViewOffset,
EntityOffset: x.EntityOffset,
Radius: x.Radius,
MinYawLimit: x.MinYawLimit,
MaxYawLimit: x.MaxYawLimit,
AudioListener: x.AudioListener,
PlayerEffects: x.PlayerEffects,
AlignTargetAndCameraForward: x.AlignTargetAndCameraForward,
@@ -123,6 +129,10 @@ func (x *CameraPreset) Marshal(r protocol.IO) {
if IsProtoGTE(r, ID766) {
protocol.OptionalFunc(r, &x.TrackingRadius, r.Float32)
}
if IsProtoGTE(r, ID776) {
protocol.OptionalFunc(r, &x.MinYawLimit, r.Float32)
protocol.OptionalFunc(r, &x.MaxYawLimit, r.Float32)
}
protocol.OptionalFunc(r, &x.ViewOffset, r.Vec2)
if IsProtoGTE(r, ID729) {
protocol.OptionalFunc(r, &x.EntityOffset, r.Vec3)

View File

@@ -0,0 +1,41 @@
package proto
import "github.com/sandertv/gophertunnel/minecraft/protocol"
// CreativeItem represents a creative item present in the creative inventory.
type CreativeItem struct {
// CreativeItemNetworkID is a unique ID for the creative item. It has to be unique for each creative item
// sent to the client. An incrementing ID per creative item does the job.
CreativeItemNetworkID uint32
// Item is the item that should be added to the creative inventory.
Item protocol.ItemStack
// GroupIndex is the index of the group that the item should be placed in. It is the index of the group in
// the CreativeContent packet previously sent to the client.
GroupIndex uint32
}
// ToLatest ...
func (x *CreativeItem) ToLatest() protocol.CreativeItem {
return protocol.CreativeItem{
CreativeItemNetworkID: x.CreativeItemNetworkID,
Item: x.Item,
GroupIndex: x.GroupIndex,
}
}
// FromLatest ...
func (x *CreativeItem) FromLatest(y protocol.CreativeItem) CreativeItem {
x.CreativeItemNetworkID = y.CreativeItemNetworkID
x.Item = y.Item
x.GroupIndex = y.GroupIndex
return *x
}
// Marshal encodes/decodes a CreativeItem.
func (x *CreativeItem) Marshal(r protocol.IO) {
r.Varuint32(&x.CreativeItemNetworkID)
r.Item(&x.Item)
if IsProtoGTE(r, ID776) {
r.Varuint32(&x.GroupIndex)
}
}

View File

@@ -3,6 +3,7 @@ package proto
import "github.com/sandertv/gophertunnel/minecraft/protocol"
const (
ID776 = 776 // v1.21.60
ID766 = 766 // v1.21.50
ID748 = 748 // v1.21.40
ID729 = 729 // v1.21.30

92
legacyver/proto/item.go Normal file
View File

@@ -0,0 +1,92 @@
package proto
import (
"github.com/sandertv/gophertunnel/minecraft/nbt"
"github.com/sandertv/gophertunnel/minecraft/protocol"
)
// LegacyItemRegistryEntry is an item sent in the StartGame item table. It holds a name and a legacy ID, which is used to
// point back to that name.
type LegacyItemRegistryEntry struct {
// Name if the name of the item, which is a name like 'minecraft:stick'.
Name string
// RuntimeID is the ID that is used to identify the item over network. After sending all items in the
// StartGame packet, items will then be identified using these numerical IDs.
RuntimeID int16
// ComponentBased specifies if the item was created using components, meaning the item is a custom item.
ComponentBased bool
}
// Marshal encodes/decodes an LegacyItemRegistryEntry.
func (x *LegacyItemRegistryEntry) Marshal(r protocol.IO) {
r.String(&x.Name)
r.Int16(&x.RuntimeID)
r.Bool(&x.ComponentBased)
}
// FromLatest ...
func (x *LegacyItemRegistryEntry) FromLatest(y protocol.ItemEntry) LegacyItemRegistryEntry {
x.Name = y.Name
x.RuntimeID = y.RuntimeID
x.ComponentBased = y.ComponentBased
return *x
}
// ToLatest ...
func (x *LegacyItemRegistryEntry) ToLatest() protocol.ItemEntry {
return protocol.ItemEntry{
Name: x.Name,
RuntimeID: x.RuntimeID,
ComponentBased: x.ComponentBased,
Version: 2,
}
}
// ItemEntry is an item sent in the StartGame item table. It holds a name and a legacy ID, which is used to
// point back to that name.
type ItemEntry struct {
// Name if the name of the item, which is a name like 'minecraft:stick'.
Name string
// RuntimeID is the ID that is used to identify the item over network. After sending all items in the
// StartGame packet, items will then be identified using these numerical IDs.
RuntimeID int16
// ComponentBased specifies if the item was created using components, meaning the item is a custom item.
ComponentBased bool
// Version is the version of the item entry which is used by the client to determine how to handle the
// item entry. It is one of the constants above.
Version int32
// Data is a map containing the components and properties of the item, if the item is component based.
Data map[string]any
}
// ToLatest ...
func (x *ItemEntry) ToLatest() protocol.ItemEntry {
return protocol.ItemEntry{
Name: x.Name,
RuntimeID: x.RuntimeID,
ComponentBased: x.ComponentBased,
Version: x.Version,
Data: x.Data,
}
}
// FromLatest ...
func (x *ItemEntry) FromLatest(y protocol.ItemEntry) ItemEntry {
x.Name = y.Name
x.RuntimeID = y.RuntimeID
x.ComponentBased = y.ComponentBased
x.Version = y.Version
x.Data = y.Data
return *x
}
// Marshal encodes/decodes an ItemEntry.
func (x *ItemEntry) Marshal(r protocol.IO) {
r.String(&x.Name)
if IsProtoGTE(r, ID776) {
r.Int16(&x.RuntimeID)
r.Bool(&x.ComponentBased)
r.Varint32(&x.Version)
}
r.NBT(&x.Data, nbt.NetworkLittleEndian)
}

View File

@@ -20,6 +20,10 @@ func init() {
for pkId, cur := range packetPoolClient {
packetPoolClient[pkId] = convertPacketFunc(pkId, cur)
}
for pkId, cur := range packetPoolServer {
packetPoolServer[pkId] = convertPacketFunc(pkId, cur)
}
}
func convertPacketFunc(pid uint32, cur func() packet.Packet) func() packet.Packet {
@@ -86,6 +90,22 @@ func convertPacketFunc(pid uint32, cur func() packet.Packet) func() packet.Packe
return func() packet.Packet { return &legacypacket.StartGame{} }
case packet.IDCodeBuilderSource:
return func() packet.Packet { return &legacypacket.CodeBuilderSource{} }
case packet.IDItemRegistry:
return func() packet.Packet { return &legacypacket.ItemRegistry{} }
case packet.IDStructureBlockUpdate:
return func() packet.Packet { return &legacypacket.StructureBlockUpdate{} }
case packet.IDBossEvent:
return func() packet.Packet { return &legacypacket.BossEvent{} }
case packet.IDCameraAimAssistPresets:
return func() packet.Packet { return &legacypacket.CameraAimAssistPresets{} }
case packet.IDCommandBlockUpdate:
return func() packet.Packet { return &legacypacket.CommandBlockUpdate{} }
case packet.IDCreativeContent:
return func() packet.Packet { return &legacypacket.CreativeContent{} }
case packet.IDUpdateAbilities:
return func() packet.Packet { return &legacypacket.UpdateAbilities{} }
case packet.IDClientCheatAbility:
return func() packet.Packet { return &legacypacket.ClientCheatAbility{} }
default:
return cur
}
@@ -309,7 +329,7 @@ func (p *Protocol) downgradePackets(pks []packet.Packet, conn *minecraft.Conn) [
GameType: pk.GameType,
EntityMetadata: pk.EntityMetadata,
EntityProperties: pk.EntityProperties,
AbilityData: pk.AbilityData,
AbilityData: (&proto.AbilityData{}).FromLatest(pk.AbilityData),
EntityLinks: links,
DeviceID: pk.DeviceID,
BuildPlatform: pk.BuildPlatform,
@@ -441,7 +461,15 @@ func (p *Protocol) downgradePackets(pks []packet.Packet, conn *minecraft.Conn) [
pk.GameVersion = p.ver
pk.BaseGameVersion = p.ver
items := make([]proto.LegacyItemRegistryEntry, len(conn.GameData().Items))
for i, it := range conn.GameData().Items {
items[i] = (&proto.LegacyItemRegistryEntry{}).FromLatest(it)
}
items = p.itemTranslator.DowngradeLegacyItemRegistry(items)
pks[pkIndex] = &legacypacket.StartGame{
Items: items,
EntityUniqueID: pk.EntityUniqueID,
EntityRuntimeID: pk.EntityRuntimeID,
PlayerGameMode: pk.PlayerGameMode,
@@ -505,7 +533,6 @@ func (p *Protocol) downgradePackets(pks []packet.Packet, conn *minecraft.Conn) [
Time: pk.Time,
EnchantmentSeed: pk.EnchantmentSeed,
Blocks: pk.Blocks,
Items: pk.Items,
MultiPlayerCorrelationID: pk.MultiPlayerCorrelationID,
ServerAuthoritativeInventory: pk.ServerAuthoritativeInventory,
GameVersion: pk.GameVersion,
@@ -527,6 +554,75 @@ func (p *Protocol) downgradePackets(pks []packet.Packet, conn *minecraft.Conn) [
Category: pk.Category,
CodeStatus: pk.CodeStatus,
}
case *packet.ItemRegistry:
items := make([]proto.ItemEntry, len(pk.Items))
for i, it := range pk.Items {
items[i] = (&proto.ItemEntry{}).FromLatest(it)
}
pks[pkIndex] = &legacypacket.ItemRegistry{
Items: items,
}
case *packet.StructureBlockUpdate:
pks[pkIndex] = &legacypacket.StructureBlockUpdate{
Position: pk.Position,
StructureName: pk.StructureName,
FilteredStructureName: pk.FilteredStructureName,
DataField: pk.DataField,
IncludePlayers: pk.IncludePlayers,
ShowBoundingBox: pk.ShowBoundingBox,
StructureBlockType: pk.StructureBlockType,
Settings: pk.Settings,
RedstoneSaveMode: pk.RedstoneSaveMode,
ShouldTrigger: pk.ShouldTrigger,
Waterlogged: pk.Waterlogged,
}
case *packet.BossEvent:
pks[pkIndex] = &legacypacket.BossEvent{
BossEntityUniqueID: pk.BossEntityUniqueID,
EventType: pk.EventType,
PlayerUniqueID: pk.PlayerUniqueID,
BossBarTitle: pk.BossBarTitle,
FilteredBossBarTitle: pk.FilteredBossBarTitle,
HealthPercentage: pk.HealthPercentage,
ScreenDarkening: pk.ScreenDarkening,
Colour: pk.Colour,
Overlay: pk.Overlay,
}
case *packet.CameraAimAssistPresets:
pks[pkIndex] = &legacypacket.CameraAimAssistPresets{
CategoryGroups: pk.CategoryGroups,
Presets: pk.Presets,
Operation: pk.Operation,
}
case *packet.CommandBlockUpdate:
pks[pkIndex] = &legacypacket.CommandBlockUpdate{
Block: pk.Block,
Position: pk.Position,
Mode: pk.Mode,
NeedsRedstone: pk.NeedsRedstone,
Conditional: pk.Conditional,
MinecartEntityRuntimeID: pk.MinecartEntityRuntimeID,
Command: pk.Command,
LastOutput: pk.LastOutput,
Name: pk.Name,
FilteredName: pk.FilteredName,
ShouldTrackOutput: pk.ShouldTrackOutput,
TickDelay: pk.TickDelay,
ExecuteOnFirstTick: pk.ExecuteOnFirstTick,
}
case *packet.CreativeContent:
items := make([]proto.CreativeItem, len(pk.Items))
for i, it := range pk.Items {
items[i] = (&proto.CreativeItem{}).FromLatest(it)
}
pks[pkIndex] = &legacypacket.CreativeContent{
Groups: pk.Groups,
Items: items,
}
case *packet.UpdateAbilities:
pks[pkIndex] = &legacypacket.UpdateAbilities{
AbilityData: (&proto.AbilityData{}).FromLatest(pk.AbilityData),
}
}
}
@@ -705,7 +801,7 @@ func (p *Protocol) upgradePackets(pks []packet.Packet, conn *minecraft.Conn) []p
GameType: pk.GameType,
EntityMetadata: pk.EntityMetadata,
EntityProperties: pk.EntityProperties,
AbilityData: pk.AbilityData,
AbilityData: pk.AbilityData.ToLatest(),
EntityLinks: links,
DeviceID: pk.DeviceID,
BuildPlatform: pk.BuildPlatform,
@@ -897,7 +993,6 @@ func (p *Protocol) upgradePackets(pks []packet.Packet, conn *minecraft.Conn) []p
Time: pk.Time,
EnchantmentSeed: pk.EnchantmentSeed,
Blocks: pk.Blocks,
Items: pk.Items,
MultiPlayerCorrelationID: pk.MultiPlayerCorrelationID,
ServerAuthoritativeInventory: pk.ServerAuthoritativeInventory,
GameVersion: pk.GameVersion,
@@ -919,6 +1014,65 @@ func (p *Protocol) upgradePackets(pks []packet.Packet, conn *minecraft.Conn) []p
Category: pk.Category,
CodeStatus: pk.CodeStatus,
}
case *legacypacket.BossEvent:
pks[pkIndex] = &packet.BossEvent{
BossEntityUniqueID: pk.BossEntityUniqueID,
EventType: pk.EventType,
PlayerUniqueID: pk.PlayerUniqueID,
BossBarTitle: pk.BossBarTitle,
FilteredBossBarTitle: pk.FilteredBossBarTitle,
HealthPercentage: pk.HealthPercentage,
ScreenDarkening: pk.ScreenDarkening,
Colour: pk.Colour,
Overlay: pk.Overlay,
}
case *legacypacket.CommandBlockUpdate:
pks[pkIndex] = &packet.CommandBlockUpdate{
Block: pk.Block,
Position: pk.Position,
Mode: pk.Mode,
NeedsRedstone: pk.NeedsRedstone,
Conditional: pk.Conditional,
MinecartEntityRuntimeID: pk.MinecartEntityRuntimeID,
Command: pk.Command,
LastOutput: pk.LastOutput,
Name: pk.Name,
FilteredName: pk.FilteredName,
ShouldTrackOutput: pk.ShouldTrackOutput,
TickDelay: pk.TickDelay,
ExecuteOnFirstTick: pk.ExecuteOnFirstTick,
}
case *legacypacket.CreativeContent:
items := make([]protocol.CreativeItem, len(pk.Items))
for i, it := range pk.Items {
items[i] = it.ToLatest()
}
pks[pkIndex] = &packet.CreativeContent{
Groups: pk.Groups,
Items: items,
}
case *legacypacket.ItemRegistry:
items := make([]protocol.ItemEntry, len(pk.Items))
for i, it := range pk.Items {
items[i] = it.ToLatest()
}
pks[pkIndex] = &packet.ItemRegistry{Items: items}
case *legacypacket.StructureBlockUpdate:
pks[pkIndex] = &packet.StructureBlockUpdate{
Position: pk.Position,
StructureName: pk.StructureName,
FilteredStructureName: pk.FilteredStructureName,
DataField: pk.DataField,
IncludePlayers: pk.IncludePlayers,
ShowBoundingBox: pk.ShowBoundingBox,
StructureBlockType: pk.StructureBlockType,
Settings: pk.Settings,
RedstoneSaveMode: pk.RedstoneSaveMode,
ShouldTrigger: pk.ShouldTrigger,
Waterlogged: pk.Waterlogged,
}
case *legacypacket.UpdateAbilities:
pks[pkIndex] = &packet.UpdateAbilities{AbilityData: pk.AbilityData.ToLatest()}
}
}
return pks

View File

@@ -2,12 +2,14 @@ package legacyver
import (
_ "embed"
"github.com/akmalfairuz/legacy-version/internal/chunk"
"github.com/akmalfairuz/legacy-version/legacyver/proto"
"github.com/akmalfairuz/legacy-version/mapping"
)
const (
// ItemVersion766 ...
ItemVersion766 = 231
ItemVersion766 = 221
// BlockVersion766 ...
BlockVersion766 int32 = (1 << 24) | (21 << 16) | (50 << 8)
)
@@ -20,6 +22,15 @@ var (
//go:embed data/block_states_766.nbt
blockStateData766 []byte
itemMappingLatest = mapping.NewItemMapping(itemRuntimeIDData766, requiredItemList766, ItemVersion766, false)
blockMappingLatest = mapping.NewBlockMapping(blockStateData766)
itemMapping766 = mapping.NewItemMapping(itemRuntimeIDData766, requiredItemList766, ItemVersion766, false)
blockMapping766 = mapping.NewBlockMapping(blockStateData766)
)
func New766() *Protocol {
return &Protocol{
ver: "1.21.50",
id: proto.ID766,
blockTranslator: NewBlockTranslator(blockMapping766, latestBlockMapping, chunk.NewNetworkPersistentEncoding(blockMapping766, BlockVersion766), chunk.NewBlockPaletteEncoding(blockMapping766, BlockVersion766), false),
itemTranslator: NewItemTranslator(itemMapping766, itemMappingLatest, blockMapping766, blockMappingLatest),
}
}

25
legacyver/v776.go Normal file
View File

@@ -0,0 +1,25 @@
package legacyver
import (
_ "embed"
"github.com/akmalfairuz/legacy-version/mapping"
)
const (
// ItemVersion776 ...
ItemVersion776 = 241
// BlockVersion776 ...
BlockVersion776 int32 = (1 << 24) | (21 << 16) | (60 << 8)
)
var (
//go:embed data/item_runtime_ids_776.nbt
itemRuntimeIDData776 []byte
//go:embed data/required_item_list_776.json
requiredItemList776 []byte
//go:embed data/block_states_776.nbt
blockStateData776 []byte
itemMappingLatest = mapping.NewItemMapping(itemRuntimeIDData776, requiredItemList776, ItemVersion776, false)
blockMappingLatest = mapping.NewBlockMapping(blockStateData776)
)