1.20.80 support (#1)

This commit is contained in:
Akmal Fairuz
2025-01-01 21:42:55 +07:00
committed by GitHub
parent 03c065881e
commit 445a4a82f8
20 changed files with 7807 additions and 14 deletions

Binary file not shown.

Binary file not shown.

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,32 @@
package legacypacket
import (
"github.com/akmalfairuz/legacy-version/legacyver/proto"
"github.com/sandertv/gophertunnel/minecraft/protocol"
"github.com/sandertv/gophertunnel/minecraft/protocol/packet"
)
// CodeBuilderSource is an Education Edition packet sent by the client to the server to run an operation with a
// code builder.
type CodeBuilderSource struct {
// Operation is used to distinguish the operation performed. It is always one of the constants listed above.
Operation byte
// Category is used to distinguish the category of the operation performed. It is always one of the constants
// listed above.
Category byte
// CodeStatus is the status of the code builder. It is always one of the constants listed above.
CodeStatus byte
}
// ID ...
func (pk *CodeBuilderSource) ID() uint32 {
return packet.IDCodeBuilderSource
}
func (pk *CodeBuilderSource) Marshal(io protocol.IO) {
io.Uint8(&pk.Operation)
io.Uint8(&pk.Category)
if proto.IsProtoGTE(io, proto.ID685) {
io.Uint8(&pk.CodeStatus)
}
}

View File

@@ -0,0 +1,35 @@
package legacypacket
import (
"github.com/akmalfairuz/legacy-version/legacyver/proto"
"github.com/sandertv/gophertunnel/minecraft/protocol"
"github.com/sandertv/gophertunnel/minecraft/protocol/packet"
)
// ContainerClose is sent by the server to close a container the player currently has opened, which was opened
// using the ContainerOpen packet, or by the client to tell the server it closed a particular container, such
// as the crafting grid.
type ContainerClose struct {
// WindowID is the ID representing the window of the container that should be closed. It must be equal to
// the one sent in the ContainerOpen packet to close the designated window.
WindowID byte
// ContainerType is the type of container that the server is trying to close. This is used to validate on
// the client side whether or not the server's close request is valid.
ContainerType byte
// ServerSide determines whether or not the container was force-closed by the server. If this value is
// not set correctly, the client may ignore the packet and respond with a PacketViolationWarning.
ServerSide bool
}
// ID ...
func (*ContainerClose) ID() uint32 {
return packet.IDContainerClose
}
func (pk *ContainerClose) Marshal(io protocol.IO) {
io.Uint8(&pk.WindowID)
if proto.IsProtoGTE(io, proto.ID685) {
io.Uint8(&pk.ContainerType)
}
io.Bool(&pk.ServerSide)
}

View File

@@ -0,0 +1,42 @@
package legacypacket
import (
"github.com/akmalfairuz/legacy-version/legacyver/proto"
"github.com/sandertv/gophertunnel/minecraft/protocol"
"github.com/sandertv/gophertunnel/minecraft/protocol/packet"
)
// CraftingData is sent by the server to let the client know all crafting data that the server maintains. This
// includes shapeless crafting, crafting table recipes, furnace recipes etc. Each crafting station's recipes
// are included in it.
type CraftingData struct {
// Recipes is a list of all recipes available on the server. It includes among others shapeless, shaped
// and furnace recipes. The client will only be able to craft these recipes.
Recipes []proto.Recipe
// PotionRecipes is a list of all potion mixing recipes which may be used in the brewing stand.
PotionRecipes []protocol.PotionRecipe
// PotionContainerChangeRecipes is a list of all recipes to convert a potion from one type to another,
// such as from a drinkable potion to a splash potion, or from a splash potion to a lingering potion.
PotionContainerChangeRecipes []protocol.PotionContainerChangeRecipe
// MaterialReducers is a list of all material reducers which is used in education edition chemistry.
MaterialReducers []protocol.MaterialReducer
// ClearRecipes indicates if all recipes currently active on the client should be cleaned. Doing this
// means that the client will have no recipes active by itself: Any CraftingData packets previously sent
// will also be discarded, and only the recipes in this CraftingData packet will be used.
ClearRecipes bool
}
// ID ...
func (*CraftingData) ID() uint32 {
return packet.IDCraftingData
}
func (pk *CraftingData) Marshal(io protocol.IO) {
protocol.FuncSlice(io, &pk.Recipes, func(p *proto.Recipe) {
proto.IORecipe(io, p)
})
protocol.Slice(io, &pk.PotionRecipes)
protocol.Slice(io, &pk.PotionContainerChangeRecipes)
protocol.FuncSlice(io, &pk.MaterialReducers, io.MaterialReducer)
io.Bool(&pk.ClearRecipes)
}

View File

@@ -0,0 +1,329 @@
package legacypacket
import (
"github.com/akmalfairuz/legacy-version/legacyver/proto"
"github.com/go-gl/mathgl/mgl32"
"github.com/google/uuid"
"github.com/sandertv/gophertunnel/minecraft/nbt"
"github.com/sandertv/gophertunnel/minecraft/protocol"
"github.com/sandertv/gophertunnel/minecraft/protocol/packet"
)
// StartGame is sent by the server to send information about the world the player will be spawned in. It
// contains information about the position the player spawns in, and information about the world in general
// such as its game rules.
type StartGame struct {
// EntityUniqueID is the unique ID of the player. The unique ID is a value that remains consistent across
// different sessions of the same world, but most servers simply fill the runtime ID of the entity out for
// this field.
EntityUniqueID int64
// EntityRuntimeID is the runtime ID of the player. The runtime ID is unique for each world session, and
// entities are generally identified in packets using this runtime ID.
EntityRuntimeID uint64
// PlayerGameMode is the game mode the player currently has. It is a value from 0-4, with 0 being
// survival mode, 1 being creative mode, 2 being adventure mode, 3 being survival spectator and 4 being
// creative spectator.
// This field may be set to 5 to make the client fall back to the game mode set in the WorldGameMode
// field.
PlayerGameMode int32
// PlayerPosition is the spawn position of the player in the world. In servers this is often the same as
// the world's spawn position found below.
PlayerPosition mgl32.Vec3
// Pitch is the vertical rotation of the player. Facing straight forward yields a pitch of 0. Pitch is
// measured in degrees.
Pitch float32
// Yaw is the horizontal rotation of the player. Yaw is also measured in degrees.
Yaw float32
// WorldSeed is the seed used to generate the world. Unlike in PC edition, the seed is a 32bit integer
// here.
WorldSeed int64
// SpawnBiomeType specifies if the biome that the player spawns in is user defined (through behaviour
// packs) or builtin. See the constants above.
SpawnBiomeType int16
// UserDefinedBiomeName is a readable name of the biome that the player spawned in, such as 'plains'. This
// might be a custom biome name if any custom biomes are present through behaviour packs.
UserDefinedBiomeName string
// Dimension is the ID of the dimension that the player spawns in. It is a value from 0-2, with 0 being
// the overworld, 1 being the nether and 2 being the end.
Dimension int32
// Generator is the generator used for the world. It is a value from 0-4, with 0 being old limited worlds,
// 1 being infinite worlds, 2 being flat worlds, 3 being nether worlds and 4 being end worlds. A value of
// 0 will actually make the client stop rendering chunks you send beyond the world limit.
Generator int32
// WorldGameMode is the game mode that a player gets when it first spawns in the world. It is shown in the
// settings and is used if the PlayerGameMode is set to 5.
WorldGameMode int32
// Hardcore is if the world is in hardcore mode. In hardcore mode, the player cannot respawn after dying.
Hardcore bool
// Difficulty is the difficulty of the world. It is a value from 0-3, with 0 being peaceful, 1 being easy,
// 2 being normal and 3 being hard.
Difficulty int32
// WorldSpawn is the block on which the world spawn of the world. This coordinate has no effect on the
// place that the client spawns, but it does have an effect on the direction that a compass points.
WorldSpawn protocol.BlockPos
// AchievementsDisabled defines if achievements are disabled in the world. The client crashes if this
// value is set to true while the player's or the world's game mode is creative, and it's recommended to
// simply always set this to false as a server.
AchievementsDisabled bool
// EditorWorldType is a value to dictate the type of editor mode, a special mode recently introduced adding
// "powerful tools for editing worlds, intended for experienced creators." It is one of the constants above.
EditorWorldType int32
// CreatedInEditor is a value to dictate if the world was created as a project in the editor mode. The functionality
// of this field is currently unknown.
CreatedInEditor bool
// ExportedFromEditor is a value to dictate if the world was exported from editor mode. The functionality of this
// field is currently unknown.
ExportedFromEditor bool
// DayCycleLockTime is the time at which the day cycle was locked if the day cycle is disabled using the
// respective game rule. The client will maintain this time as long as the day cycle is disabled.
DayCycleLockTime int32
// EducationEditionOffer is some Minecraft: Education Edition field that specifies what 'region' the world
// was from, with 0 being None, 1 being RestOfWorld, and 2 being China.
// The actual use of this field is unknown.
EducationEditionOffer int32
// EducationFeaturesEnabled specifies if the world has education edition features enabled, such as the
// blocks or entities specific to education edition.
EducationFeaturesEnabled bool
// EducationProductID is a UUID used to identify the education edition server instance. It is generally
// unique for education edition servers.
EducationProductID string
// RainLevel is the level specifying the intensity of the rain falling. When set to 0, no rain falls at
// all.
RainLevel float32
// LightningLevel is the level specifying the intensity of the thunder. This may actually be set
// independently from the RainLevel, meaning dark clouds can be produced without rain.
LightningLevel float32
// ConfirmedPlatformLockedContent ...
ConfirmedPlatformLockedContent bool
// MultiPlayerGame specifies if the world is a multi-player game. This should always be set to true for
// servers.
MultiPlayerGame bool
// LANBroadcastEnabled specifies if LAN broadcast was intended to be enabled for the world.
LANBroadcastEnabled bool
// XBLBroadcastMode is the mode used to broadcast the joined game across XBOX Live.
XBLBroadcastMode int32
// PlatformBroadcastMode is the mode used to broadcast the joined game across the platform.
PlatformBroadcastMode int32
// CommandsEnabled specifies if commands are enabled for the player. It is recommended to always set this
// to true on the server, as setting it to false means the player cannot, under any circumstance, use a
// command.
CommandsEnabled bool
// TexturePackRequired specifies if the texture pack the world might hold is required, meaning the client
// was forced to download it before joining.
TexturePackRequired bool
// GameRules defines game rules currently active with their respective values. The value of these game
// rules may be either 'bool', 'int32' or 'float32'. Some game rules are server side only, and don't
// necessarily need to be sent to the client.
GameRules []protocol.GameRule
// Experiments holds a list of experiments that are either enabled or disabled in the world that the
// player spawns in.
Experiments []protocol.ExperimentData
// ExperimentsPreviouslyToggled specifies if any experiments were previously toggled in this world. It is
// probably used for some kind of metrics.
ExperimentsPreviouslyToggled bool
// BonusChestEnabled specifies if the world had the bonus map setting enabled when generating it. It does
// not have any effect client-side.
BonusChestEnabled bool
// StartWithMapEnabled specifies if the world has the start with map setting enabled, meaning each joining
// player obtains a map. This should always be set to false, because the client obtains a map all on its
// own accord if this is set to true.
StartWithMapEnabled bool
// PlayerPermissions is the permission level of the player. It is a value from 0-3, with 0 being visitor,
// 1 being member, 2 being operator and 3 being custom.
PlayerPermissions int32
// ServerChunkTickRadius is the radius around the player in which chunks are ticked. Most servers set this
// value to a fixed number, as it does not necessarily affect anything client-side.
ServerChunkTickRadius int32
// HasLockedBehaviourPack specifies if the behaviour pack of the world is locked, meaning it cannot be
// disabled from the world. This is typically set for worlds on the marketplace that have a dedicated
// behaviour pack.
HasLockedBehaviourPack bool
// HasLockedTexturePack specifies if the texture pack of the world is locked, meaning it cannot be
// disabled from the world. This is typically set for worlds on the marketplace that have a dedicated
// texture pack.
HasLockedTexturePack bool
// FromLockedWorldTemplate specifies if the world from the server was from a locked world template. For
// servers this should always be set to false.
FromLockedWorldTemplate bool
// MSAGamerTagsOnly ..
MSAGamerTagsOnly bool
// FromWorldTemplate specifies if the world from the server was from a world template. For servers this
// should always be set to false.
FromWorldTemplate bool
// WorldTemplateSettingsLocked specifies if the world was a template that locks all settings that change
// properties above in the settings GUI. It is recommended to set this to true for servers that do not
// allow things such as setting game rules through the GUI.
WorldTemplateSettingsLocked bool
// OnlySpawnV1Villagers is a hack that Mojang put in place to preserve backwards compatibility with old
// villagers. The bool is never actually read though, so it has no functionality.
OnlySpawnV1Villagers bool
// PersonaDisabled is true if persona skins are disabled for the current game session.
PersonaDisabled bool
// CustomSkinsDisabled is true if custom skins are disabled for the current game session.
CustomSkinsDisabled bool
// EmoteChatMuted specifies if players will be sent a chat message when using certain emotes.
EmoteChatMuted bool
// BaseGameVersion is the version of the game from which Vanilla features will be used. The exact function
// of this field isn't clear.
BaseGameVersion string
// LimitedWorldWidth and LimitedWorldDepth are the dimensions of the world if the world is a limited
// world. For unlimited worlds, these may simply be left as 0.
LimitedWorldWidth, LimitedWorldDepth int32
// NewNether specifies if the server runs with the new nether introduced in the 1.16 update.
NewNether bool
// EducationSharedResourceURI is an education edition feature that transmits education resource settings to clients.
EducationSharedResourceURI protocol.EducationSharedResourceURI
// ForceExperimentalGameplay specifies if experimental gameplay should be force enabled. For servers this
// should always be set to false.
ForceExperimentalGameplay protocol.Optional[bool]
// LevelID is a base64 encoded world ID that is used to identify the world.
LevelID string
// WorldName is the name of the world that the player is joining. Note that this field shows up above the
// player list for the rest of the game session, and cannot be changed. Setting the server name to this
// field is recommended.
WorldName string
// TemplateContentIdentity is a UUID specific to the premium world template that might have been used to
// generate the world. Servers should always fill out an empty string for this.
TemplateContentIdentity string
// Trial specifies if the world was a trial world, meaning features are limited and there is a time limit
// on the world.
Trial bool
// PlayerMovementSettings ...
PlayerMovementSettings protocol.PlayerMovementSettings
// Time is the total time that has elapsed since the start of the world.
Time int64
// EnchantmentSeed is the seed used to seed the random used to produce enchantments in the enchantment
// table. Note that the exact correct random implementation must be used to produce the correct results
// both client- and server-side.
EnchantmentSeed int32
// Blocks is a list of all custom blocks registered on the server.
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
// 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
// ServerAuthoritativeInventory specifies if the server authoritative inventory system is enabled. This
// is a new system introduced in 1.16. Backwards compatibility with the inventory transactions has to
// some extent been preserved, but will eventually be removed.
ServerAuthoritativeInventory bool
// GameVersion is the version of the game the server is running. The exact function of this field isn't clear.
GameVersion string
// PropertyData contains properties that should be applied on the player. These properties are the same as the
// ones that are sent in the SyncActorProperty packet.
PropertyData map[string]any
// ServerBlockStateChecksum is a checksum to ensure block states between the server and client match.
// This can simply be left empty, and the client will avoid trying to verify it.
ServerBlockStateChecksum uint64
// ClientSideGeneration is true if the client should use the features registered in the FeatureRegistry packet to
// generate terrain client-side to save on bandwidth.
ClientSideGeneration bool
// WorldTemplateID is a UUID that identifies the template that was used to generate the world. Servers that do not
// use a world based off of a template can set this to an empty UUID.
WorldTemplateID uuid.UUID
// ChatRestrictionLevel specifies the level of restriction on in-game chat. It is one of the constants above.
ChatRestrictionLevel uint8
// DisablePlayerInteractions is true if the client should ignore other players when interacting with the world.
DisablePlayerInteractions bool
// ServerID is always empty in vanilla and its usage is currently unknown.
ServerID string
// WorldID is always empty in vanilla and its usage is currently unknown.
WorldID string
// ScenarioID is always empty in vanilla and its usage is currently unknown.
ScenarioID string
// UseBlockNetworkIDHashes is true if the client should use the hash of a block's name as its network ID rather than
// its index in the expected block palette. This is useful for servers that wish to support multiple protocol versions
// and custom blocks, but it will result in extra bytes being written for every block in a sub chunk palette.
UseBlockNetworkIDHashes bool
// ServerAuthoritativeSound is currently unknown as to what it does.
ServerAuthoritativeSound bool
}
// ID ...
func (*StartGame) ID() uint32 {
return packet.IDStartGame
}
func (pk *StartGame) Marshal(io protocol.IO) {
io.Varint64(&pk.EntityUniqueID)
io.Varuint64(&pk.EntityRuntimeID)
io.Varint32(&pk.PlayerGameMode)
io.Vec3(&pk.PlayerPosition)
io.Float32(&pk.Pitch)
io.Float32(&pk.Yaw)
io.Int64(&pk.WorldSeed)
io.Int16(&pk.SpawnBiomeType)
io.String(&pk.UserDefinedBiomeName)
io.Varint32(&pk.Dimension)
io.Varint32(&pk.Generator)
io.Varint32(&pk.WorldGameMode)
io.Bool(&pk.Hardcore)
io.Varint32(&pk.Difficulty)
io.UBlockPos(&pk.WorldSpawn)
io.Bool(&pk.AchievementsDisabled)
io.Varint32(&pk.EditorWorldType)
io.Bool(&pk.CreatedInEditor)
io.Bool(&pk.ExportedFromEditor)
io.Varint32(&pk.DayCycleLockTime)
io.Varint32(&pk.EducationEditionOffer)
io.Bool(&pk.EducationFeaturesEnabled)
io.String(&pk.EducationProductID)
io.Float32(&pk.RainLevel)
io.Float32(&pk.LightningLevel)
io.Bool(&pk.ConfirmedPlatformLockedContent)
io.Bool(&pk.MultiPlayerGame)
io.Bool(&pk.LANBroadcastEnabled)
io.Varint32(&pk.XBLBroadcastMode)
io.Varint32(&pk.PlatformBroadcastMode)
io.Bool(&pk.CommandsEnabled)
io.Bool(&pk.TexturePackRequired)
protocol.FuncSlice(io, &pk.GameRules, io.GameRule)
protocol.SliceUint32Length(io, &pk.Experiments)
io.Bool(&pk.ExperimentsPreviouslyToggled)
io.Bool(&pk.BonusChestEnabled)
io.Bool(&pk.StartWithMapEnabled)
io.Varint32(&pk.PlayerPermissions)
io.Int32(&pk.ServerChunkTickRadius)
io.Bool(&pk.HasLockedBehaviourPack)
io.Bool(&pk.HasLockedTexturePack)
io.Bool(&pk.FromLockedWorldTemplate)
io.Bool(&pk.MSAGamerTagsOnly)
io.Bool(&pk.FromWorldTemplate)
io.Bool(&pk.WorldTemplateSettingsLocked)
io.Bool(&pk.OnlySpawnV1Villagers)
io.Bool(&pk.PersonaDisabled)
io.Bool(&pk.CustomSkinsDisabled)
io.Bool(&pk.EmoteChatMuted)
io.String(&pk.BaseGameVersion)
io.Int32(&pk.LimitedWorldWidth)
io.Int32(&pk.LimitedWorldDepth)
io.Bool(&pk.NewNether)
protocol.Single(io, &pk.EducationSharedResourceURI)
protocol.OptionalFunc(io, &pk.ForceExperimentalGameplay, io.Bool)
io.Uint8(&pk.ChatRestrictionLevel)
io.Bool(&pk.DisablePlayerInteractions)
if proto.IsProtoGTE(io, proto.ID685) {
io.String(&pk.ServerID)
io.String(&pk.WorldID)
io.String(&pk.ScenarioID)
}
io.String(&pk.LevelID)
io.String(&pk.WorldName)
io.String(&pk.TemplateContentIdentity)
io.Bool(&pk.Trial)
protocol.PlayerMoveSettings(io, &pk.PlayerMovementSettings)
io.Int64(&pk.Time)
io.Varint32(&pk.EnchantmentSeed)
protocol.Slice(io, &pk.Blocks)
protocol.Slice(io, &pk.Items)
io.String(&pk.MultiPlayerCorrelationID)
io.Bool(&pk.ServerAuthoritativeInventory)
io.String(&pk.GameVersion)
io.NBT(&pk.PropertyData, nbt.NetworkLittleEndian)
io.Uint64(&pk.ServerBlockStateChecksum)
io.UUID(&pk.WorldTemplateID)
io.Bool(&pk.ClientSideGeneration)
io.Bool(&pk.UseBlockNetworkIDHashes)
io.Bool(&pk.ServerAuthoritativeSound)
}

View File

@@ -0,0 +1,79 @@
package legacypacket
import (
"github.com/akmalfairuz/legacy-version/legacyver/proto"
"github.com/sandertv/gophertunnel/minecraft/protocol"
"github.com/sandertv/gophertunnel/minecraft/protocol/packet"
)
const (
TextTypeRaw = iota
TextTypeChat
TextTypeTranslation
TextTypePopup
TextTypeJukeboxPopup
TextTypeTip
TextTypeSystem
TextTypeWhisper
TextTypeAnnouncement
TextTypeObjectWhisper
TextTypeObject
TextTypeObjectAnnouncement
)
// Text is sent by the client to the server to send chat messages, and by the server to the client to forward
// or send messages, which may be chat, popups, tips etc.
type Text struct {
// TextType is the type of the text sent. When a client sends this to the server, it should always be
// TextTypeChat. If the server sends it, it may be one of the other text types above.
TextType byte
// NeedsTranslation specifies if any of the messages need to be translated. It seems that where % is found
// in translatable text types, these are translated regardless of this bool. Translatable text types
// include TextTypeTranslation, TextTypeTip, TextTypePopup and TextTypeJukeboxPopup.
NeedsTranslation bool
// SourceName is the name of the source of the messages. This source is displayed in text types such as
// the TextTypeChat and TextTypeWhisper, where typically the username is shown.
SourceName string
// Message is the message of the packet. This field is set for each TextType and is the main component of
// the packet.
Message string
// Parameters is a list of parameters that should be filled into the message. These parameters are only
// written if the type of the packet is TextTypeTranslation, TextTypeTip, TextTypePopup or TextTypeJukeboxPopup.
Parameters []string
// XUID is the XBOX Live user ID of the player that sent the message. It is only set for packets of
// TextTypeChat. When sent to a player, the player will only be shown the chat message if a player with
// this XUID is present in the player list and not muted, or if the XUID is empty.
XUID string
// PlatformChatID is an identifier only set for particular platforms when chatting (presumably only for
// Nintendo Switch). It is otherwise an empty string, and is used to decide which players are able to
// chat with each other.
PlatformChatID string
// FilteredMessage is a filtered version of Message with all the profanity removed. The client will use
// this over Message if this field is not empty and they have the "Filter Profanity" setting enabled.
FilteredMessage string
}
// ID ...
func (*Text) ID() uint32 {
return packet.IDText
}
func (pk *Text) Marshal(io protocol.IO) {
io.Uint8(&pk.TextType)
io.Bool(&pk.NeedsTranslation)
switch pk.TextType {
case TextTypeChat, TextTypeWhisper, TextTypeAnnouncement:
io.String(&pk.SourceName)
io.String(&pk.Message)
case TextTypeRaw, TextTypeTip, TextTypeSystem, TextTypeObject, TextTypeObjectWhisper, TextTypeObjectAnnouncement:
io.String(&pk.Message)
case TextTypeTranslation, TextTypePopup, TextTypeJukeboxPopup:
io.String(&pk.Message)
protocol.FuncSlice(io, &pk.Parameters, io.String)
}
io.String(&pk.XUID)
io.String(&pk.PlatformChatID)
if proto.IsProtoGTE(io, proto.ID685) {
io.String(&pk.FilteredMessage)
}
}

View File

@@ -9,6 +9,7 @@ const (
ID712 = 712 // v1.21.20
ID686 = 686 // v1.21.2
ID685 = 685 // v1.21.0
ID671 = 671 // v1.20.80
)
func IsProtoGTE(io protocol.IO, proto int32) bool {

View File

@@ -112,3 +112,22 @@ func IOStackRequestAction(io protocol.IO, x *protocol.StackRequestAction) {
}
(*x).Marshal(io)
}
func IORecipe(io protocol.IO, recipe *Recipe) {
if IsReader(io) {
var recipeType int32
io.Varint32(&recipeType)
if !lookupRecipe(recipeType, recipe) {
io.UnknownEnumOption(recipeType, "crafting data recipe type")
return
}
(*recipe).Unmarshal(io.(*Reader))
} else {
var recipeType int32
if !lookupRecipeType(*recipe, &recipeType) {
io.UnknownEnumOption(fmt.Sprintf("%T", *recipe), "crafting recipe type")
}
io.Varint32(&recipeType)
(*recipe).Marshal(io.(*Writer))
}
}

699
legacyver/proto/recipe.go Normal file
View File

@@ -0,0 +1,699 @@
package proto
import (
"github.com/google/uuid"
"github.com/sandertv/gophertunnel/minecraft/protocol"
)
const (
RecipeUnlockContextNone = iota
RecipeUnlockContextAlwaysUnlocked
RecipeUnlockContextPlayerInWater
RecipeUnlockContextPlayerHasManyItems
)
// RecipeUnlockRequirement represents a requirement that must be met in order to unlock a recipe. This is used
// for both shaped and shapeless recipes.
type RecipeUnlockRequirement struct {
// Context is the context in which the recipe is unlocked. This is one of the constants above.
Context byte
// Ingredients are the ingredients required to unlock the recipe and only used if Context is set to none.
Ingredients []protocol.ItemDescriptorCount
}
func (x *RecipeUnlockRequirement) FromLatest(requirement protocol.RecipeUnlockRequirement) RecipeUnlockRequirement {
x.Context = requirement.Context
x.Ingredients = requirement.Ingredients
return *x
}
func (x *RecipeUnlockRequirement) ToLatest() protocol.RecipeUnlockRequirement {
return protocol.RecipeUnlockRequirement{
Context: x.Context,
Ingredients: x.Ingredients,
}
}
// Marshal ...
func (x *RecipeUnlockRequirement) Marshal(r protocol.IO) {
r.Uint8(&x.Context)
if x.Context == RecipeUnlockContextNone {
protocol.FuncSlice(r, &x.Ingredients, r.ItemDescriptorCount)
}
}
const (
RecipeShapeless int32 = iota
RecipeShaped
RecipeFurnace
RecipeFurnaceData
RecipeMulti
RecipeShulkerBox
RecipeShapelessChemistry
RecipeShapedChemistry
RecipeSmithingTransform
RecipeSmithingTrim
)
// Recipe represents a recipe that may be sent in a CraftingData packet to let the client know what recipes
// are available server-side.
type Recipe interface {
// Marshal encodes the recipe data to its binary representation into buf.
Marshal(w *Writer)
// Unmarshal decodes a serialised recipe from Reader r into the recipe instance.
Unmarshal(r *Reader)
}
func takePtr[T any](x T) *T {
return &x
}
func RecipeToLatest(x Recipe) protocol.Recipe {
switch x := x.(type) {
case *ShapelessRecipe:
return takePtr(x.ToLatest())
case *ShulkerBoxRecipe:
return takePtr(x.ToLatest())
case *ShapelessChemistryRecipe:
return takePtr(x.ToLatest())
case *ShapedRecipe:
return takePtr(x.ToLatest())
case *ShapedChemistryRecipe:
return takePtr(x.ToLatest())
case *FurnaceRecipe:
return takePtr(x.ToLatest())
case *FurnaceDataRecipe:
return takePtr(x.ToLatest())
case *MultiRecipe:
return takePtr(x.ToLatest())
case *SmithingTransformRecipe:
return takePtr(x.ToLatest())
case *SmithingTrimRecipe:
return takePtr(x.ToLatest())
}
panic("RecipeToLatest: unknown recipe type")
}
func RecipeFromLatest(x protocol.Recipe) Recipe {
switch x := x.(type) {
case *protocol.ShapelessRecipe:
return (&ShapelessRecipe{}).FromLatest(*x)
case *protocol.ShulkerBoxRecipe:
return (&ShulkerBoxRecipe{}).FromLatest(*x)
case *protocol.ShapelessChemistryRecipe:
return (&ShapelessChemistryRecipe{}).FromLatest(*x)
case *protocol.ShapedRecipe:
return (&ShapedRecipe{}).FromLatest(*x)
case *protocol.ShapedChemistryRecipe:
return (&ShapedChemistryRecipe{}).FromLatest(*x)
case *protocol.FurnaceRecipe:
return (&FurnaceRecipe{}).FromLatest(*x)
case *protocol.FurnaceDataRecipe:
return (&FurnaceDataRecipe{}).FromLatest(*x)
case *protocol.MultiRecipe:
return (&MultiRecipe{}).FromLatest(*x)
case *protocol.SmithingTransformRecipe:
return (&SmithingTransformRecipe{}).FromLatest(*x)
case *protocol.SmithingTrimRecipe:
return (&SmithingTrimRecipe{}).FromLatest(*x)
}
panic("RecipeFromLatest: unknown recipe type")
}
// lookupRecipe looks up the Recipe for a recipe type. False is returned if not
// found.
func lookupRecipe(recipeType int32, x *Recipe) bool {
switch recipeType {
case RecipeShapeless:
*x = &ShapelessRecipe{}
case RecipeShaped:
*x = &ShapedRecipe{}
case RecipeFurnace:
*x = &FurnaceRecipe{}
case RecipeFurnaceData:
*x = &FurnaceDataRecipe{}
case RecipeMulti:
*x = &MultiRecipe{}
case RecipeShulkerBox:
*x = &ShulkerBoxRecipe{}
case RecipeShapelessChemistry:
*x = &ShapelessChemistryRecipe{}
case RecipeShapedChemistry:
*x = &ShapedChemistryRecipe{}
case RecipeSmithingTransform:
*x = &SmithingTransformRecipe{}
case RecipeSmithingTrim:
*x = &SmithingTrimRecipe{}
default:
return false
}
return true
}
// lookupRecipeType looks up the recipe type for a Recipe. False is returned if
// none was found.
func lookupRecipeType(x Recipe, recipeType *int32) bool {
switch x.(type) {
case *ShapelessRecipe:
*recipeType = RecipeShapeless
case *ShapedRecipe:
*recipeType = RecipeShaped
case *FurnaceRecipe:
*recipeType = RecipeFurnace
case *FurnaceDataRecipe:
*recipeType = RecipeFurnaceData
case *MultiRecipe:
*recipeType = RecipeMulti
case *ShulkerBoxRecipe:
*recipeType = RecipeShulkerBox
case *ShapelessChemistryRecipe:
*recipeType = RecipeShapelessChemistry
case *ShapedChemistryRecipe:
*recipeType = RecipeShapedChemistry
case *SmithingTransformRecipe:
*recipeType = RecipeSmithingTransform
case *SmithingTrimRecipe:
*recipeType = RecipeSmithingTrim
default:
return false
}
return true
}
// ShapelessRecipe is a recipe that has no particular shape. Its functionality is shared with the
// RecipeShulkerBox and RecipeShapelessChemistry types.
type ShapelessRecipe struct {
// RecipeID is a unique ID of the recipe. This ID must be unique amongst all other types of recipes too,
// but its functionality is not exactly known.
RecipeID string
// Input is a list of items that serve as the input of the shapeless recipe. These items are the items
// required to craft the output.
Input []protocol.ItemDescriptorCount
// Output is a list of items that are created as a result of crafting the recipe.
Output []protocol.ItemStack
// UUID is a UUID identifying the recipe. Since the CraftingEvent packet no longer exists, this can always be empty.
UUID uuid.UUID
// Block is the block name that is required to craft the output of the recipe. The block is not prefixed
// with 'minecraft:', so it will look like 'crafting_table' as an example.
// The available blocks are:
// - crafting_table
// - cartography_table
// - stonecutter
// - furnace
// - blast_furnace
// - smoker
// - campfire
Block string
// Priority ...
Priority int32
// UnlockRequirement is a requirement that must be met in order to unlock the recipe.
UnlockRequirement RecipeUnlockRequirement
// RecipeNetworkID is a unique ID used to identify the recipe over network. Each recipe must have a unique
// network ID. Recommended is to just increment a variable for each unique recipe registered.
// This field must never be 0.
RecipeNetworkID uint32
}
// ShulkerBoxRecipe is a shapeless recipe made specifically for shulker box crafting, so that they don't lose
// their user data when dyeing a shulker box.
type ShulkerBoxRecipe struct {
ShapelessRecipe
}
// ShapelessChemistryRecipe is a recipe specifically made for chemistry related features, which exist only in
// the Education Edition. They function the same as shapeless recipes do.
type ShapelessChemistryRecipe struct {
ShapelessRecipe
}
// ShapedRecipe is a recipe that has a specific shape that must be used to craft the output of the recipe.
// Trying to craft the item in any other shape will not work. The ShapedRecipe is of the same structure as the
// ShapedChemistryRecipe.
type ShapedRecipe struct {
// RecipeID is a unique ID of the recipe. This ID must be unique amongst all other types of recipes too,
// but its functionality is not exactly known.
RecipeID string
// Width is the width of the recipe's shape.
Width int32
// Height is the height of the recipe's shape.
Height int32
// Input is a list of items that serve as the input of the shapeless recipe. These items are the items
// required to craft the output. The amount of input items must be exactly equal to Width * Height.
Input []protocol.ItemDescriptorCount
// Output is a list of items that are created as a result of crafting the recipe.
Output []protocol.ItemStack
// UUID is a UUID identifying the recipe. Since the CraftingEvent packet no longer exists, this can always be empty.
UUID uuid.UUID
// Block is the block name that is required to craft the output of the recipe. The block is not prefixed
// with 'minecraft:', so it will look like 'crafting_table' as an example.
Block string
// Priority ...
Priority int32
// AssumeSymmetry specifies if the recipe is symmetrical. If this is set to true, the recipe will be
// mirrored along the diagonal axis. This means that the recipe will be the same if rotated 180 degrees.
AssumeSymmetry bool
// UnlockRequirement is a requirement that must be met in order to unlock the recipe.
UnlockRequirement RecipeUnlockRequirement
// RecipeNetworkID is a unique ID used to identify the recipe over network. Each recipe must have a unique
// network ID. Recommended is to just increment a variable for each unique recipe registered.
// This field must never be 0.
RecipeNetworkID uint32
}
// ShapedChemistryRecipe is a recipe specifically made for chemistry related features, which exist only in the
// Education Edition. It functions the same as a normal ShapedRecipe.
type ShapedChemistryRecipe struct {
ShapedRecipe
}
// FurnaceRecipe is a recipe that is specifically used for all kinds of furnaces. These recipes don't just
// apply to furnaces, but also blast furnaces and smokers.
type FurnaceRecipe struct {
// InputType is the item type of the input item. The metadata value of the item is not used in the
// FurnaceRecipe. Use FurnaceDataRecipe to allow an item with only one metadata value.
InputType protocol.ItemType
// Output is the item that is created as a result of smelting/cooking an item in the furnace.
Output protocol.ItemStack
// Block is the block name that is required to create the output of the recipe. The block is not prefixed
// with 'minecraft:', so it will look like 'furnace' as an example.
Block string
}
// FurnaceDataRecipe is a recipe specifically used for furnace-type crafting stations. It is equal to
// FurnaceRecipe, except it has an input item with a specific metadata value, instead of any metadata value.
type FurnaceDataRecipe struct {
FurnaceRecipe
}
// MultiRecipe serves as an 'enable' switch for multi-shape recipes.
type MultiRecipe struct {
// UUID is a UUID identifying the recipe. Since the CraftingEvent packet no longer exists, this can always be empty.
UUID uuid.UUID
// RecipeNetworkID is a unique ID used to identify the recipe over network. Each recipe must have a unique
// network ID. Recommended is to just increment a variable for each unique recipe registered.
// This field must never be 0.
RecipeNetworkID uint32
}
// SmithingTransformRecipe is a recipe specifically used for smithing tables. It has three input items and adds them
// together, resulting in a new item.
type SmithingTransformRecipe struct {
// RecipeNetworkID is a unique ID used to identify the recipe over network. Each recipe must have a unique
// network ID. Recommended is to just increment a variable for each unique recipe registered.
// This field must never be 0.
RecipeNetworkID uint32
// RecipeID is a unique ID of the recipe. This ID must be unique amongst all other types of recipes too,
// but its functionality is not exactly known.
RecipeID string
// Template is the item that is used to shape the Base item based on the Addition being applied.
Template protocol.ItemDescriptorCount
// Base is the item that the Addition is being applied to in the smithing table.
Base protocol.ItemDescriptorCount
// Addition is the item that is being added to the Base item to result in a modified item.
Addition protocol.ItemDescriptorCount
// Result is the resulting item from the two items being added together.
Result protocol.ItemStack
// Block is the block name that is required to create the output of the recipe. The block is not prefixed with
// 'minecraft:', so it will look like 'smithing_table' as an example.
Block string
}
// SmithingTrimRecipe is a recipe specifically used for applying armour trims to an armour piece inside a smithing table.
type SmithingTrimRecipe struct {
// RecipeNetworkID is a unique ID used to identify the recipe over network. Each recipe must have a unique
// network ID. Recommended is to just increment a variable for each unique recipe registered.
// This field must never be 0.
RecipeNetworkID uint32
// RecipeID is a unique ID of the recipe. This ID must be unique amongst all other types of recipes too,
// but its functionality is not exactly known.
RecipeID string
// Template is the item that is used to shape the Base item based on the Addition being applied.
Template protocol.ItemDescriptorCount
// Base is the item that the Addition is being applied to in the smithing table.
Base protocol.ItemDescriptorCount
// Addition is the item that is being added to the Base item to result in a modified item.
Addition protocol.ItemDescriptorCount
// Block is the block name that is required to create the output of the recipe. The block is not prefixed with
// 'minecraft:', so it will look like 'smithing_table' as an example.
Block string
}
// FromLatest ...
func (recipe *ShapelessRecipe) FromLatest(v protocol.ShapelessRecipe) *ShapelessRecipe {
recipe.RecipeID = v.RecipeID
recipe.Input = v.Input
recipe.Output = v.Output
recipe.UUID = v.UUID
recipe.Block = v.Block
recipe.Priority = v.Priority
recipe.UnlockRequirement = (&RecipeUnlockRequirement{}).FromLatest(v.UnlockRequirement)
recipe.RecipeNetworkID = v.RecipeNetworkID
return recipe
}
// ToLatest ...
func (recipe *ShapelessRecipe) ToLatest() protocol.ShapelessRecipe {
return protocol.ShapelessRecipe{
RecipeID: recipe.RecipeID,
Input: recipe.Input,
Output: recipe.Output,
UUID: recipe.UUID,
Block: recipe.Block,
Priority: recipe.Priority,
UnlockRequirement: recipe.UnlockRequirement.ToLatest(),
RecipeNetworkID: recipe.RecipeNetworkID,
}
}
// Marshal ...
func (recipe *ShapelessRecipe) Marshal(w *Writer) {
marshalShapeless(w, recipe)
}
// Unmarshal ...
func (recipe *ShapelessRecipe) Unmarshal(r *Reader) {
marshalShapeless(r, recipe)
}
// FromLatest ...
func (recipe *ShulkerBoxRecipe) FromLatest(v protocol.ShulkerBoxRecipe) *ShulkerBoxRecipe {
recipe.ShapelessRecipe = *(&ShapelessRecipe{}).FromLatest(v.ShapelessRecipe)
return recipe
}
// ToLatest ...
func (recipe *ShulkerBoxRecipe) ToLatest() protocol.ShulkerBoxRecipe {
return protocol.ShulkerBoxRecipe{
ShapelessRecipe: recipe.ShapelessRecipe.ToLatest(),
}
}
// Marshal ...
func (recipe *ShulkerBoxRecipe) Marshal(w *Writer) {
marshalShapeless(w, &recipe.ShapelessRecipe)
}
// Unmarshal ...
func (recipe *ShulkerBoxRecipe) Unmarshal(r *Reader) {
marshalShapeless(r, &recipe.ShapelessRecipe)
}
// FromLatest ...
func (recipe *ShapelessChemistryRecipe) FromLatest(v protocol.ShapelessChemistryRecipe) *ShapelessChemistryRecipe {
recipe.ShapelessRecipe = *(&ShapelessRecipe{}).FromLatest(v.ShapelessRecipe)
return recipe
}
// ToLatest ...
func (recipe *ShapelessChemistryRecipe) ToLatest() protocol.ShapelessChemistryRecipe {
return protocol.ShapelessChemistryRecipe{
ShapelessRecipe: recipe.ShapelessRecipe.ToLatest(),
}
}
// Marshal ...
func (recipe *ShapelessChemistryRecipe) Marshal(w *Writer) {
marshalShapeless(w, &recipe.ShapelessRecipe)
}
// Unmarshal ...
func (recipe *ShapelessChemistryRecipe) Unmarshal(r *Reader) {
marshalShapeless(r, &recipe.ShapelessRecipe)
}
// FromLatest ...
func (recipe *ShapedRecipe) FromLatest(v protocol.ShapedRecipe) *ShapedRecipe {
recipe.RecipeID = v.RecipeID
recipe.Width = v.Width
recipe.Height = v.Height
recipe.Input = v.Input
recipe.Output = v.Output
recipe.UUID = v.UUID
recipe.Block = v.Block
recipe.AssumeSymmetry = v.AssumeSymmetry
recipe.UnlockRequirement = (&RecipeUnlockRequirement{}).FromLatest(v.UnlockRequirement)
recipe.Priority = v.Priority
recipe.RecipeNetworkID = v.RecipeNetworkID
return recipe
}
// ToLatest ...
func (recipe *ShapedRecipe) ToLatest() protocol.ShapedRecipe {
return protocol.ShapedRecipe{
RecipeID: recipe.RecipeID,
Width: recipe.Width,
Height: recipe.Height,
Input: recipe.Input,
Output: recipe.Output,
UUID: recipe.UUID,
Block: recipe.Block,
AssumeSymmetry: recipe.AssumeSymmetry,
UnlockRequirement: recipe.UnlockRequirement.ToLatest(),
Priority: recipe.Priority,
RecipeNetworkID: recipe.RecipeNetworkID,
}
}
// Marshal ...
func (recipe *ShapedRecipe) Marshal(w *Writer) {
marshalShaped(w, recipe)
}
// Unmarshal ...
func (recipe *ShapedRecipe) Unmarshal(r *Reader) {
marshalShaped(r, recipe)
}
// FromLatest ...
func (recipe *ShapedChemistryRecipe) FromLatest(v protocol.ShapedChemistryRecipe) *ShapedChemistryRecipe {
recipe.ShapedRecipe = *(&ShapedRecipe{}).FromLatest(v.ShapedRecipe)
return recipe
}
// ToLatest ...
func (recipe *ShapedChemistryRecipe) ToLatest() protocol.ShapedChemistryRecipe {
return protocol.ShapedChemistryRecipe{
ShapedRecipe: recipe.ShapedRecipe.ToLatest(),
}
}
// Marshal ...
func (recipe *ShapedChemistryRecipe) Marshal(w *Writer) {
marshalShaped(w, &recipe.ShapedRecipe)
}
// Unmarshal ...
func (recipe *ShapedChemistryRecipe) Unmarshal(r *Reader) {
marshalShaped(r, &recipe.ShapedRecipe)
}
// FromLatest ...
func (recipe *FurnaceRecipe) FromLatest(v protocol.FurnaceRecipe) *FurnaceRecipe {
recipe.InputType = v.InputType
recipe.Output = v.Output
recipe.Block = v.Block
return recipe
}
// ToLatest ...
func (recipe *FurnaceRecipe) ToLatest() protocol.FurnaceRecipe {
return protocol.FurnaceRecipe{
InputType: recipe.InputType,
Output: recipe.Output,
Block: recipe.Block,
}
}
// Marshal ...
func (recipe *FurnaceRecipe) Marshal(w *Writer) {
w.Varint32(&recipe.InputType.NetworkID)
w.Item(&recipe.Output)
w.String(&recipe.Block)
}
// Unmarshal ...
func (recipe *FurnaceRecipe) Unmarshal(r *Reader) {
r.Varint32(&recipe.InputType.NetworkID)
r.Item(&recipe.Output)
r.String(&recipe.Block)
}
// FromLatest ...
func (recipe *FurnaceDataRecipe) FromLatest(v protocol.FurnaceDataRecipe) *FurnaceDataRecipe {
recipe.FurnaceRecipe = *(&FurnaceRecipe{}).FromLatest(v.FurnaceRecipe)
return recipe
}
// ToLatest ...
func (recipe *FurnaceDataRecipe) ToLatest() protocol.FurnaceDataRecipe {
return protocol.FurnaceDataRecipe{
FurnaceRecipe: recipe.FurnaceRecipe.ToLatest(),
}
}
// Marshal ...
func (recipe *FurnaceDataRecipe) Marshal(w *Writer) {
w.Varint32(&recipe.InputType.NetworkID)
aux := int32(recipe.InputType.MetadataValue)
w.Varint32(&aux)
w.Item(&recipe.Output)
w.String(&recipe.Block)
}
// Unmarshal ...
func (recipe *FurnaceDataRecipe) Unmarshal(r *Reader) {
var dataValue int32
r.Varint32(&recipe.InputType.NetworkID)
r.Varint32(&dataValue)
recipe.InputType.MetadataValue = uint32(dataValue)
r.Item(&recipe.Output)
r.String(&recipe.Block)
}
// FromLatest ...
func (recipe *MultiRecipe) FromLatest(v protocol.MultiRecipe) *MultiRecipe {
recipe.UUID = v.UUID
recipe.RecipeNetworkID = v.RecipeNetworkID
return recipe
}
// ToLatest ...
func (recipe *MultiRecipe) ToLatest() protocol.MultiRecipe {
return protocol.MultiRecipe{
UUID: recipe.UUID,
RecipeNetworkID: recipe.RecipeNetworkID,
}
}
// Marshal ...
func (recipe *MultiRecipe) Marshal(w *Writer) {
w.UUID(&recipe.UUID)
w.Varuint32(&recipe.RecipeNetworkID)
}
// Unmarshal ...
func (recipe *MultiRecipe) Unmarshal(r *Reader) {
r.UUID(&recipe.UUID)
r.Varuint32(&recipe.RecipeNetworkID)
}
// FromLatest ...
func (recipe *SmithingTransformRecipe) FromLatest(v protocol.SmithingTransformRecipe) *SmithingTransformRecipe {
recipe.RecipeNetworkID = v.RecipeNetworkID
recipe.RecipeID = v.RecipeID
recipe.Template = v.Template
recipe.Base = v.Base
recipe.Addition = v.Addition
recipe.Result = v.Result
recipe.Block = v.Block
return recipe
}
// ToLatest ...
func (recipe *SmithingTransformRecipe) ToLatest() protocol.SmithingTransformRecipe {
return protocol.SmithingTransformRecipe{
RecipeNetworkID: recipe.RecipeNetworkID,
RecipeID: recipe.RecipeID,
Template: recipe.Template,
Base: recipe.Base,
Addition: recipe.Addition,
Result: recipe.Result,
Block: recipe.Block,
}
}
// Marshal ...
func (recipe *SmithingTransformRecipe) Marshal(w *Writer) {
w.String(&recipe.RecipeID)
w.ItemDescriptorCount(&recipe.Template)
w.ItemDescriptorCount(&recipe.Base)
w.ItemDescriptorCount(&recipe.Addition)
w.Item(&recipe.Result)
w.String(&recipe.Block)
w.Varuint32(&recipe.RecipeNetworkID)
}
// Unmarshal ...
func (recipe *SmithingTransformRecipe) Unmarshal(r *Reader) {
r.String(&recipe.RecipeID)
r.ItemDescriptorCount(&recipe.Template)
r.ItemDescriptorCount(&recipe.Base)
r.ItemDescriptorCount(&recipe.Addition)
r.Item(&recipe.Result)
r.String(&recipe.Block)
r.Varuint32(&recipe.RecipeNetworkID)
}
// FromLatest ...
func (recipe *SmithingTrimRecipe) FromLatest(v protocol.SmithingTrimRecipe) *SmithingTrimRecipe {
recipe.RecipeNetworkID = v.RecipeNetworkID
recipe.RecipeID = v.RecipeID
recipe.Template = v.Template
recipe.Base = v.Base
recipe.Addition = v.Addition
recipe.Block = v.Block
return recipe
}
// ToLatest ...
func (recipe *SmithingTrimRecipe) ToLatest() protocol.SmithingTrimRecipe {
return protocol.SmithingTrimRecipe{
RecipeNetworkID: recipe.RecipeNetworkID,
RecipeID: recipe.RecipeID,
Template: recipe.Template,
Base: recipe.Base,
Addition: recipe.Addition,
Block: recipe.Block,
}
}
// Marshal ...
func (recipe *SmithingTrimRecipe) Marshal(w *Writer) {
w.String(&recipe.RecipeID)
w.ItemDescriptorCount(&recipe.Template)
w.ItemDescriptorCount(&recipe.Base)
w.ItemDescriptorCount(&recipe.Addition)
w.String(&recipe.Block)
w.Varuint32(&recipe.RecipeNetworkID)
}
// Unmarshal ...
func (recipe *SmithingTrimRecipe) Unmarshal(r *Reader) {
r.String(&recipe.RecipeID)
r.ItemDescriptorCount(&recipe.Template)
r.ItemDescriptorCount(&recipe.Base)
r.ItemDescriptorCount(&recipe.Addition)
r.String(&recipe.Block)
r.Varuint32(&recipe.RecipeNetworkID)
}
// marshalShaped ...
func marshalShaped(r protocol.IO, recipe *ShapedRecipe) {
r.String(&recipe.RecipeID)
r.Varint32(&recipe.Width)
r.Varint32(&recipe.Height)
protocol.FuncSliceOfLen(r, uint32(recipe.Width*recipe.Height), &recipe.Input, r.ItemDescriptorCount)
protocol.FuncSlice(r, &recipe.Output, r.Item)
r.UUID(&recipe.UUID)
r.String(&recipe.Block)
r.Varint32(&recipe.Priority)
r.Bool(&recipe.AssumeSymmetry)
if IsProtoGTE(r, ID685) {
protocol.Single(r, &recipe.UnlockRequirement)
}
r.Varuint32(&recipe.RecipeNetworkID)
}
// marshalShapeless ...
func marshalShapeless(r protocol.IO, recipe *ShapelessRecipe) {
r.String(&recipe.RecipeID)
protocol.FuncSlice(r, &recipe.Input, r.ItemDescriptorCount)
protocol.FuncSlice(r, &recipe.Output, r.Item)
r.UUID(&recipe.UUID)
r.String(&recipe.Block)
r.Varint32(&recipe.Priority)
if IsProtoGTE(r, ID685) {
protocol.Single(r, &recipe.UnlockRequirement)
}
r.Varuint32(&recipe.RecipeNetworkID)
}

View File

@@ -76,6 +76,16 @@ func convertPacketFunc(pid uint32, cur func() packet.Packet) func() packet.Packe
return func() packet.Packet { return &legacypacket.InventoryTransaction{} }
case packet.IDItemStackRequest:
return func() packet.Packet { return &legacypacket.ItemStackRequest{} }
case packet.IDCraftingData:
return func() packet.Packet { return &legacypacket.CraftingData{} }
case packet.IDContainerClose:
return func() packet.Packet { return &legacypacket.ContainerClose{} }
case packet.IDText:
return func() packet.Packet { return &legacypacket.Text{} }
case packet.IDStartGame:
return func() packet.Packet { return &legacypacket.StartGame{} }
case packet.IDCodeBuilderSource:
return func() packet.Packet { return &legacypacket.CodeBuilderSource{} }
default:
return cur
}
@@ -127,6 +137,8 @@ func (p *Protocol) ConvertFromLatest(pk packet.Packet, conn *minecraft.Conn) []p
func (p *Protocol) downgradePackets(pks []packet.Packet, conn *minecraft.Conn) []packet.Packet {
for pkIndex, pk := range pks {
switch pk := pk.(type) {
case *packet.ClientCacheStatus:
pk.Enabled = false // TODO: enable when chunk translation is not broken
case *packet.CameraPresets:
presets := make([]proto.CameraPreset, len(pk.Presets))
for i, p := range pk.Presets {
@@ -135,9 +147,6 @@ func (p *Protocol) downgradePackets(pks []packet.Packet, conn *minecraft.Conn) [
pks[pkIndex] = &legacypacket.CameraPresets{
Presets: presets,
}
case *packet.StartGame:
pk.GameVersion = p.ver
pk.BaseGameVersion = p.ver
case *packet.PlayerAuthInput:
inputData := pk.InputData
if p.ID() < proto.ID766 {
@@ -398,6 +407,126 @@ func (p *Protocol) downgradePackets(pks []packet.Packet, conn *minecraft.Conn) [
requests[i] = (&proto.ItemStackRequest{}).FromLatest(r)
}
pks[pkIndex] = &legacypacket.ItemStackRequest{Requests: requests}
case *packet.Text:
pks[pkIndex] = &legacypacket.Text{
TextType: pk.TextType,
NeedsTranslation: pk.NeedsTranslation,
SourceName: pk.SourceName,
Message: pk.Message,
Parameters: pk.Parameters,
XUID: pk.XUID,
PlatformChatID: pk.PlatformChatID,
FilteredMessage: pk.FilteredMessage,
}
case *packet.ContainerClose:
pks[pkIndex] = &legacypacket.ContainerClose{
WindowID: pk.WindowID,
ContainerType: pk.ContainerType,
ServerSide: pk.ServerSide,
}
case *packet.CraftingData:
recipes := make([]proto.Recipe, len(pk.Recipes))
for i, r := range pk.Recipes {
recipes[i] = proto.RecipeFromLatest(r)
}
pks[pkIndex] = &legacypacket.CraftingData{
Recipes: recipes,
PotionRecipes: pk.PotionRecipes,
PotionContainerChangeRecipes: pk.PotionContainerChangeRecipes,
MaterialReducers: pk.MaterialReducers,
ClearRecipes: pk.ClearRecipes,
}
case *packet.StartGame:
// Adjust game version
pk.GameVersion = p.ver
pk.BaseGameVersion = p.ver
pks[pkIndex] = &legacypacket.StartGame{
EntityUniqueID: pk.EntityUniqueID,
EntityRuntimeID: pk.EntityRuntimeID,
PlayerGameMode: pk.PlayerGameMode,
PlayerPosition: pk.PlayerPosition,
Pitch: pk.Pitch,
Yaw: pk.Yaw,
WorldSeed: pk.WorldSeed,
SpawnBiomeType: pk.SpawnBiomeType,
UserDefinedBiomeName: pk.UserDefinedBiomeName,
Dimension: pk.Dimension,
Generator: pk.Generator,
WorldGameMode: pk.WorldGameMode,
Hardcore: pk.Hardcore,
Difficulty: pk.Difficulty,
WorldSpawn: pk.WorldSpawn,
AchievementsDisabled: pk.AchievementsDisabled,
EditorWorldType: pk.EditorWorldType,
CreatedInEditor: pk.CreatedInEditor,
ExportedFromEditor: pk.ExportedFromEditor,
DayCycleLockTime: pk.DayCycleLockTime,
EducationEditionOffer: pk.EducationEditionOffer,
EducationFeaturesEnabled: pk.EducationFeaturesEnabled,
EducationProductID: pk.EducationProductID,
RainLevel: pk.RainLevel,
LightningLevel: pk.LightningLevel,
ConfirmedPlatformLockedContent: pk.ConfirmedPlatformLockedContent,
MultiPlayerGame: pk.MultiPlayerGame,
LANBroadcastEnabled: pk.LANBroadcastEnabled,
XBLBroadcastMode: pk.XBLBroadcastMode,
PlatformBroadcastMode: pk.PlatformBroadcastMode,
CommandsEnabled: pk.CommandsEnabled,
TexturePackRequired: pk.TexturePackRequired,
GameRules: pk.GameRules,
Experiments: pk.Experiments,
ExperimentsPreviouslyToggled: pk.ExperimentsPreviouslyToggled,
BonusChestEnabled: pk.BonusChestEnabled,
StartWithMapEnabled: pk.StartWithMapEnabled,
PlayerPermissions: pk.PlayerPermissions,
ServerChunkTickRadius: pk.ServerChunkTickRadius,
HasLockedBehaviourPack: pk.HasLockedBehaviourPack,
HasLockedTexturePack: pk.HasLockedTexturePack,
FromLockedWorldTemplate: pk.FromLockedWorldTemplate,
MSAGamerTagsOnly: pk.MSAGamerTagsOnly,
FromWorldTemplate: pk.FromWorldTemplate,
WorldTemplateSettingsLocked: pk.WorldTemplateSettingsLocked,
OnlySpawnV1Villagers: pk.OnlySpawnV1Villagers,
PersonaDisabled: pk.PersonaDisabled,
CustomSkinsDisabled: pk.CustomSkinsDisabled,
EmoteChatMuted: pk.EmoteChatMuted,
BaseGameVersion: pk.BaseGameVersion,
LimitedWorldWidth: pk.LimitedWorldWidth,
LimitedWorldDepth: pk.LimitedWorldDepth,
NewNether: pk.NewNether,
EducationSharedResourceURI: pk.EducationSharedResourceURI,
ForceExperimentalGameplay: pk.ForceExperimentalGameplay,
LevelID: pk.LevelID,
WorldName: pk.WorldName,
TemplateContentIdentity: pk.TemplateContentIdentity,
Trial: pk.Trial,
PlayerMovementSettings: pk.PlayerMovementSettings,
Time: pk.Time,
EnchantmentSeed: pk.EnchantmentSeed,
Blocks: pk.Blocks,
Items: pk.Items,
MultiPlayerCorrelationID: pk.MultiPlayerCorrelationID,
ServerAuthoritativeInventory: pk.ServerAuthoritativeInventory,
GameVersion: pk.GameVersion,
PropertyData: pk.PropertyData,
ServerBlockStateChecksum: pk.ServerBlockStateChecksum,
ClientSideGeneration: pk.ClientSideGeneration,
WorldTemplateID: pk.WorldTemplateID,
ChatRestrictionLevel: pk.ChatRestrictionLevel,
DisablePlayerInteractions: pk.DisablePlayerInteractions,
ServerID: pk.ServerID,
WorldID: pk.WorldID,
ScenarioID: pk.ScenarioID,
UseBlockNetworkIDHashes: pk.UseBlockNetworkIDHashes,
ServerAuthoritativeSound: pk.ServerAuthoritativeSound,
}
case *packet.CodeBuilderSource:
pks[pkIndex] = &legacypacket.CodeBuilderSource{
Operation: pk.Operation,
Category: pk.Category,
CodeStatus: pk.CodeStatus,
}
}
}
@@ -674,6 +803,122 @@ func (p *Protocol) upgradePackets(pks []packet.Packet, conn *minecraft.Conn) []p
requests[i] = r.ToLatest()
}
pks[pkIndex] = &packet.ItemStackRequest{Requests: requests}
case *legacypacket.CraftingData:
recipes := make([]protocol.Recipe, len(pk.Recipes))
for i, r := range pk.Recipes {
recipes[i] = proto.RecipeToLatest(r)
}
pks[pkIndex] = &packet.CraftingData{
Recipes: recipes,
PotionRecipes: pk.PotionRecipes,
PotionContainerChangeRecipes: pk.PotionContainerChangeRecipes,
MaterialReducers: pk.MaterialReducers,
ClearRecipes: pk.ClearRecipes,
}
case *legacypacket.ContainerClose:
pks[pkIndex] = &packet.ContainerClose{
WindowID: pk.WindowID,
ContainerType: pk.ContainerType,
ServerSide: pk.ServerSide,
}
case *legacypacket.Text:
pks[pkIndex] = &packet.Text{
TextType: pk.TextType,
NeedsTranslation: pk.NeedsTranslation,
SourceName: pk.SourceName,
Message: pk.Message,
Parameters: pk.Parameters,
XUID: pk.XUID,
PlatformChatID: pk.PlatformChatID,
FilteredMessage: pk.FilteredMessage,
}
case *legacypacket.StartGame:
pks[pkIndex] = &packet.StartGame{
EntityUniqueID: pk.EntityUniqueID,
EntityRuntimeID: pk.EntityRuntimeID,
PlayerGameMode: pk.PlayerGameMode,
PlayerPosition: pk.PlayerPosition,
Pitch: pk.Pitch,
Yaw: pk.Yaw,
WorldSeed: pk.WorldSeed,
SpawnBiomeType: pk.SpawnBiomeType,
UserDefinedBiomeName: pk.UserDefinedBiomeName,
Dimension: pk.Dimension,
Generator: pk.Generator,
WorldGameMode: pk.WorldGameMode,
Hardcore: pk.Hardcore,
Difficulty: pk.Difficulty,
WorldSpawn: pk.WorldSpawn,
AchievementsDisabled: pk.AchievementsDisabled,
EditorWorldType: pk.EditorWorldType,
CreatedInEditor: pk.CreatedInEditor,
ExportedFromEditor: pk.ExportedFromEditor,
DayCycleLockTime: pk.DayCycleLockTime,
EducationEditionOffer: pk.EducationEditionOffer,
EducationFeaturesEnabled: pk.EducationFeaturesEnabled,
EducationProductID: pk.EducationProductID,
RainLevel: pk.RainLevel,
LightningLevel: pk.LightningLevel,
ConfirmedPlatformLockedContent: pk.ConfirmedPlatformLockedContent,
MultiPlayerGame: pk.MultiPlayerGame,
LANBroadcastEnabled: pk.LANBroadcastEnabled,
XBLBroadcastMode: pk.XBLBroadcastMode,
PlatformBroadcastMode: pk.PlatformBroadcastMode,
CommandsEnabled: pk.CommandsEnabled,
TexturePackRequired: pk.TexturePackRequired,
GameRules: pk.GameRules,
Experiments: pk.Experiments,
ExperimentsPreviouslyToggled: pk.ExperimentsPreviouslyToggled,
BonusChestEnabled: pk.BonusChestEnabled,
StartWithMapEnabled: pk.StartWithMapEnabled,
PlayerPermissions: pk.PlayerPermissions,
ServerChunkTickRadius: pk.ServerChunkTickRadius,
HasLockedBehaviourPack: pk.HasLockedBehaviourPack,
HasLockedTexturePack: pk.HasLockedTexturePack,
FromLockedWorldTemplate: pk.FromLockedWorldTemplate,
MSAGamerTagsOnly: pk.MSAGamerTagsOnly,
FromWorldTemplate: pk.FromWorldTemplate,
WorldTemplateSettingsLocked: pk.WorldTemplateSettingsLocked,
OnlySpawnV1Villagers: pk.OnlySpawnV1Villagers,
PersonaDisabled: pk.PersonaDisabled,
CustomSkinsDisabled: pk.CustomSkinsDisabled,
EmoteChatMuted: pk.EmoteChatMuted,
BaseGameVersion: pk.BaseGameVersion,
LimitedWorldWidth: pk.LimitedWorldWidth,
LimitedWorldDepth: pk.LimitedWorldDepth,
NewNether: pk.NewNether,
EducationSharedResourceURI: pk.EducationSharedResourceURI,
ForceExperimentalGameplay: pk.ForceExperimentalGameplay,
LevelID: pk.LevelID,
WorldName: pk.WorldName,
TemplateContentIdentity: pk.TemplateContentIdentity,
Trial: pk.Trial,
PlayerMovementSettings: pk.PlayerMovementSettings,
Time: pk.Time,
EnchantmentSeed: pk.EnchantmentSeed,
Blocks: pk.Blocks,
Items: pk.Items,
MultiPlayerCorrelationID: pk.MultiPlayerCorrelationID,
ServerAuthoritativeInventory: pk.ServerAuthoritativeInventory,
GameVersion: pk.GameVersion,
PropertyData: pk.PropertyData,
ServerBlockStateChecksum: pk.ServerBlockStateChecksum,
ClientSideGeneration: pk.ClientSideGeneration,
WorldTemplateID: pk.WorldTemplateID,
ChatRestrictionLevel: pk.ChatRestrictionLevel,
DisablePlayerInteractions: pk.DisablePlayerInteractions,
ServerID: pk.ServerID,
WorldID: pk.WorldID,
ScenarioID: pk.ScenarioID,
UseBlockNetworkIDHashes: pk.UseBlockNetworkIDHashes,
ServerAuthoritativeSound: pk.ServerAuthoritativeSound,
}
case *legacypacket.CodeBuilderSource:
pks[pkIndex] = &packet.CodeBuilderSource{
Operation: pk.Operation,
Category: pk.Category,
CodeStatus: pk.CodeStatus,
}
}
}
return pks

37
legacyver/v671.go Normal file
View File

@@ -0,0 +1,37 @@
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 (
// ItemVersion671 ...
ItemVersion671 = 181
// BlockVersion671 ...
BlockVersion671 int32 = (1 << 24) | (20 << 16) | (80 << 8)
)
var (
//go:embed data/item_runtime_ids_671.nbt
itemRuntimeIDData671 []byte
//go:embed data/required_item_list_671.json
requiredItemList671 []byte
//go:embed data/block_states_671.nbt
blockStateData671 []byte
)
// New671 ...
func New671() *Protocol {
itemMapping := mapping.NewItemMapping(itemRuntimeIDData671, requiredItemList671, ItemVersion671, false)
blockMapping := mapping.NewBlockMapping(blockStateData671)
return &Protocol{
ver: "1.20.80",
id: proto.ID671,
blockTranslator: NewBlockTranslator(blockMapping, latestBlockMapping, chunk.NewNetworkPersistentEncoding(blockMapping, BlockVersion671), chunk.NewBlockPaletteEncoding(blockMapping, BlockVersion671), false),
itemTranslator: NewItemTranslator(itemMapping, itemMappingLatest, blockMapping, blockMappingLatest),
}
}

View File

@@ -15,7 +15,7 @@ const (
)
// New685 uses same data as 686
func New685(direct bool) *Protocol {
func New685() *Protocol {
itemMapping := mapping.NewItemMapping(itemRuntimeIDData686, requiredItemList686, ItemVersion685, false)
blockMapping := mapping.NewBlockMapping(blockStateData686)

View File

@@ -23,7 +23,7 @@ var (
blockStateData686 []byte
)
func New686(direct bool) *Protocol {
func New686() *Protocol {
itemMapping := mapping.NewItemMapping(itemRuntimeIDData686, requiredItemList686, ItemVersion686, false)
blockMapping := mapping.NewBlockMapping(blockStateData686)

View File

@@ -23,7 +23,7 @@ var (
blockStateData712 []byte
)
func New712(direct bool) *Protocol {
func New712() *Protocol {
itemMapping := mapping.NewItemMapping(itemRuntimeIDData712, requiredItemList712, ItemVersion712, false)
blockMapping := mapping.NewBlockMapping(blockStateData712)

View File

@@ -23,7 +23,7 @@ var (
blockStateData729 []byte
)
func New729(direct bool) *Protocol {
func New729() *Protocol {
itemMapping := mapping.NewItemMapping(itemRuntimeIDData729, requiredItemList729, ItemVersion729, false)
blockMapping := mapping.NewBlockMapping(blockStateData729)

View File

@@ -23,7 +23,7 @@ var (
blockStateData748 []byte
)
func New748(direct bool) *Protocol {
func New748() *Protocol {
itemMapping := mapping.NewItemMapping(itemRuntimeIDData748, requiredItemList748, ItemVersion748, false)
blockMapping := mapping.NewBlockMapping(blockStateData748)