diff --git a/go.mod b/go.mod index a47678e6..958ed801 100644 --- a/go.mod +++ b/go.mod @@ -3,6 +3,7 @@ module go.minekube.com/gate go 1.21.0 require ( + github.com/Tnze/go-mc v1.20.2-0.20231123224931-bc3d77d78437 github.com/agext/levenshtein v1.2.3 github.com/dboslee/lru v0.0.1 github.com/edwingeng/deque/v2 v2.1.1 @@ -33,13 +34,13 @@ require ( golang.org/x/text v0.12.0 golang.org/x/time v0.3.0 google.golang.org/grpc v1.57.0 + gopkg.in/yaml.v2 v2.4.0 gopkg.in/yaml.v3 v3.0.1 nhooyr.io/websocket v1.8.7 ) require ( buf.build/gen/go/minekube/connect/protocolbuffers/go v1.31.0-20230517110945-04c17e7d2fd9.1 // indirect - github.com/Tnze/go-mc v1.20.2-0.20231123224931-bc3d77d78437 // indirect github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/df-mc/atomic v1.10.0 // indirect diff --git a/go.sum b/go.sum index 28bb5b15..38ebf480 100644 --- a/go.sum +++ b/go.sum @@ -47,8 +47,6 @@ dmitri.shuralyov.com/state v0.0.0-20180228185332-28bcc343414c/go.mod h1:0PRwlb0D git.apache.org/thrift.git v0.0.0-20180902110319-2566ecd5d999/go.mod h1:fPE2ZNJGynbRyZ4dJvy6G277gSllfV2HJqblrnkyeyg= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= -github.com/Tnze/go-mc v1.20.1 h1:1Mwxyg2nvIVgRtebf6CpjMjvXLtRIng95so5hTEQtXA= -github.com/Tnze/go-mc v1.20.1/go.mod h1:c1znJQglgqa1Jjs3Dr29woN/msguiJrlNtWXhKedh2U= github.com/Tnze/go-mc v1.20.2-0.20231123224931-bc3d77d78437 h1:cINogPegf6TCIEmAon1kyVuKJttjoM0H7kyIM5WXre4= github.com/Tnze/go-mc v1.20.2-0.20231123224931-bc3d77d78437/go.mod h1:geoRj2HsXSkB3FJBuhr7wCzXegRlzWsVXd7h7jiJ6aQ= github.com/agext/levenshtein v1.2.3 h1:YB2fHEn0UJagG8T1rrWknE3ZQzWM06O8AMAatNn7lmo= diff --git a/pkg/edition/java/netmc/connection.go b/pkg/edition/java/netmc/connection.go index 23e04e58..41449e12 100644 --- a/pkg/edition/java/netmc/connection.go +++ b/pkg/edition/java/netmc/connection.go @@ -329,7 +329,7 @@ func (c *minecraftConn) closeOnWriteErr(err error, logKeysAndValues ...any) { return } _ = c.Close() - if err == ErrClosedConn { + if errors.Is(err, ErrClosedConn) { return // Don't log this error } var opErr *net.OpError diff --git a/pkg/edition/java/proto/packet/chat/component_holder.go b/pkg/edition/java/proto/packet/chat/component_holder.go index 9daa2dd4..8966c8f9 100644 --- a/pkg/edition/java/proto/packet/chat/component_holder.go +++ b/pkg/edition/java/proto/packet/chat/component_holder.go @@ -8,10 +8,8 @@ import ( "go.minekube.com/gate/pkg/edition/java/proto/util" "go.minekube.com/gate/pkg/edition/java/proto/version" "go.minekube.com/gate/pkg/gate/proto" - "gopkg.in/yaml.v3" "io" "log/slog" - "regexp" ) func FromComponent(comp component.Component) *ComponentHolder { @@ -56,13 +54,8 @@ func ReadComponentHolderNP(rd io.Reader, protocol proto.Protocol) (ComponentHold func (c *ComponentHolder) read(rd io.Reader, protocol proto.Protocol) (err error) { c.Protocol = protocol if protocol.GreaterEqual(version.Minecraft_1_20_3) { - dec := nbt2.NewDecoder(rd) - dec.NetworkFormat(true) // skip tag name - _, err := dec.Decode(&c.BinaryTag) - if err != nil { - return fmt.Errorf("error while reading binaryTag: %w", err) - } - return nil + c.BinaryTag, err = util.ReadBinaryTag(rd, protocol) + return err } j, err := util.ReadString(rd) c.JSON = json.RawMessage(j) @@ -72,13 +65,11 @@ func (c *ComponentHolder) read(rd io.Reader, protocol proto.Protocol) (err error // Write writes the component holder to the writer. func (c *ComponentHolder) Write(wr io.Writer, protocol proto.Protocol) error { if protocol.GreaterEqual(version.Minecraft_1_20_3) { - enc := nbt2.NewEncoder(wr) - enc.NetworkFormat(true) // skip tag name - err := enc.Encode(c.BinaryTag, "") + bt, err := c.AsBinaryTag() if err != nil { - return fmt.Errorf("error while reading binaryTag: %w", err) + return err } - return nil + return util.WriteBinaryTag(wr, protocol, bt) } j, err := c.AsJson() if err != nil { @@ -110,7 +101,7 @@ func (c *ComponentHolder) AsComponent() (component.Component, error) { return c.Component, err case len(c.BinaryTag.Data) != 0: var err error - c.JSON, err = binaryTagToJSON(&c.BinaryTag) + c.JSON, err = BinaryTagToJSON(&c.BinaryTag) if err != nil { return nil, fmt.Errorf("error while marshalling binaryTag to JSON: %w", err) } @@ -128,7 +119,7 @@ func (c *ComponentHolder) AsJson() (json.RawMessage, error) { } if len(c.BinaryTag.Data) != 0 { var err error - c.JSON, err = binaryTagToJSON(&c.BinaryTag) + c.JSON, err = BinaryTagToJSON(&c.BinaryTag) return c.JSON, err } comp, err := c.AsComponent() @@ -151,42 +142,13 @@ func (c *ComponentHolder) AsJsonOrNil() json.RawMessage { return j } -// AsBinaryTag returns the component as a binary NBT tag. -//func (c *ComponentHolder) AsBinaryTag() (*nbt2.RawMessage, error) { -// if len(c.BinaryTag.Data) != 0 { -// return &c.BinaryTag, nil -// } -// j, err := c.AsJson() -// if err != nil { -// return nil, err -// } -// err = nbt.UnmarshalEncoding(j, &c.BinaryTag, nbt.BigEndian) // TODO -// return &c.BinaryTag, err -//} - -func binaryTagToJSON(tag *nbt2.RawMessage) (json.RawMessage, error) { - return snbtToJSON(tag.String()) -} - -var snbtRe = regexp.MustCompile(`(?m)([^"]):([^"])`) - -// snbtToJSON converts a stringified NBT to JSON. -// Example: {a:1,b:hello,c:"world",d:true} -> {"a":1,"b":"hello","c":"world","d":true} -func snbtToJSON(snbt string) (json.RawMessage, error) { - // Add spaces after colons that are not within quotes - snbt = snbtRe.ReplaceAllString(snbt, "$1: $2") - - // Parse non-standard json with yaml, which is a superset of json. - // We use YAML parser, since it's a superset of JSON and quotes are optional. - type M map[string]any - var m M - if err := yaml.Unmarshal([]byte(snbt), &m); err != nil { - return nil, err +func (c *ComponentHolder) AsBinaryTag() (util.BinaryTag, error) { + if len(c.BinaryTag.Data) != 0 { + return c.BinaryTag, nil } - // Marshal back to JSON - j, err := json.Marshal(m) + j, err := c.AsJson() if err != nil { - return nil, err + return c.BinaryTag, err } - return j, nil + return JsonToBinaryTag(j) } diff --git a/pkg/edition/java/proto/packet/chat/component_holder_test.go b/pkg/edition/java/proto/packet/chat/component_holder_test.go deleted file mode 100644 index 97a2386a..00000000 --- a/pkg/edition/java/proto/packet/chat/component_holder_test.go +++ /dev/null @@ -1,59 +0,0 @@ -package chat - -import ( - "encoding/json" - "github.com/stretchr/testify/assert" - "testing" -) - -func TestSnbtToJSON(t *testing.T) { - tests := []struct { - name string - snbt string - want json.RawMessage - wantErr bool - }{ - { - name: "Test 1 with spaces", - snbt: `{a: 1,b: hello,c: "world",d: true}`, - want: json.RawMessage(`{"a":1,"b":"hello","c":"world","d":true}`), - wantErr: false, - }, - { - name: "Test 2 without spaces", - snbt: `{a:1,b:hello,c:"world",d:true}`, - want: json.RawMessage(`{"a":1,"b":"hello","c":"world","d":true}`), - wantErr: false, - }, - { - name: "Test 3 with spaces and colons in values", - snbt: `{a: 1,b: hello:world,c: "world",d: true}`, - want: json.RawMessage(`{"a":1,"b":"hello:world","c":"world","d":true}`), - wantErr: false, - }, - { - name: "Test 4 inception", - snbt: `{a: 1,b: {c: 2,d: {e: 3}}}`, - want: json.RawMessage(`{"a":1,"b":{"c":2,"d":{"e":3}}}`), - wantErr: false, - }, - { - name: "Test 4 inception as string", - snbt: `{a: 1,b: "{c:2,d: {e: 3}}"}`, - want: json.RawMessage(`{"a":1,"b":"{c:2,d: {e: 3}}}"}`), - wantErr: false, - }, - // Add more test cases as needed - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - got, err := snbtToJSON(tt.snbt) - if (err != nil) != tt.wantErr { - t.Errorf("snbtToJSON() error = %v, wantErr %v", err, tt.wantErr) - return - } - assert.Equal(t, string(tt.want), string(got)) - }) - } -} diff --git a/pkg/edition/java/proto/packet/chat/snbt_json.go b/pkg/edition/java/proto/packet/chat/snbt_json.go new file mode 100644 index 00000000..ad6926ae --- /dev/null +++ b/pkg/edition/java/proto/packet/chat/snbt_json.go @@ -0,0 +1,176 @@ +package chat + +import ( + "bytes" + "encoding/json" + "errors" + "fmt" + "github.com/Tnze/go-mc/nbt" + "gopkg.in/yaml.v3" + "strings" +) + +// formatSNBT adds spaces after colons that are not within quotes. +// Example: {a:1,b:hello,c:"world",d:true} -> {a: 1, b: hello, c: "world", d: true} +// This is needed because the yaml parser requires spaces after colons +func formatSNBT(snbt string) string { // TODO test / rewrite properly + var result strings.Builder + inQuotes := false + + for i := 0; i < len(snbt); i++ { + switch snbt[i] { + case '"': + inQuotes = !inQuotes + case ':', ',': + if !inQuotes { + result.WriteByte(snbt[i]) + result.WriteByte(' ') + continue + } + } + result.WriteByte(snbt[i]) + } + + return result.String() +} + +var errSNBTInvalid = errors.New("invalid input for SNBT, must be a non-empty string starting with '{' and ending with '}'") + +// SnbtToJSON converts a stringified NBT to JSON. +// Example: {a:1,b:hello,c:"world",d:true} -> {"a":1,"b":"hello","c":"world","d":true} +func SnbtToJSON(snbt string) (json.RawMessage, error) { + // Trim whitespace, newlines, return characters, and tabs + snbt = strings.Trim(snbt, " \n\r\t") + + // Ensure that input is not empty or trivially malformed + if len(snbt) < 2 || !strings.HasPrefix(snbt, "{") || !strings.HasSuffix(snbt, "}") { + // get first and last few characters of input and put ... in between + var truncated string + if len(snbt) > 10 { + truncated = snbt[:5] + "..." + snbt[len(snbt)-5:] + } else { + truncated = snbt + } + return nil, fmt.Errorf("%w: but got %q", errSNBTInvalid, truncated) + } + + // Add spaces after colons that are not within quotes + snbt = formatSNBT(snbt) + + // Parse non-standard json with yaml, which is a superset of json. + // We use YAML parser, since it's a superset of JSON and quotes are optional. + type M map[string]any + var m M + if err := yaml.Unmarshal([]byte(snbt), &m); err != nil { + return nil, fmt.Errorf("error unmarshalling snbt to yaml: %w", err) + } + // Marshal back to JSON + j, err := json.Marshal(m) + if err != nil { + return nil, fmt.Errorf("error marshalling yaml to json: %w", err) + } + return j, nil +} + +// JsonToSNBT converts a JSON to stringified NBT. +// Example: {"a":1,"b":"hello","c":"world","d":true} -> {a:1,b:hello,c:"world",d:true} +func JsonToSNBT(j json.RawMessage) (string, error) { + var m map[string]any + if err := json.Unmarshal(j, &m); err != nil { + return "", fmt.Errorf("error unmarshalling json to map: %w", err) + } + var b strings.Builder + err := ConvertToSNBT(m, &b) + return b.String(), err +} + +func ConvertToSNBT(v any, b *strings.Builder) error { + switch v := v.(type) { + case map[string]any: + return mapToSNBT(v, b) + case []any: + return sliceToSNBT(v, b) + case string: + if len(v) == 0 { + // Empty strings are represented as two double quotes + b.WriteString(`""`) + } else { + // Quote strings that contain spaces or special characters + if strings.ContainsAny(v, " {}:[]/") { + b.WriteString(fmt.Sprintf(`"%s"`, v)) + } else { + b.WriteString(v) + } + } + default: + b.WriteString(fmt.Sprintf("%v", v)) + } + return nil +} + +func mapToSNBT(m map[string]any, b *strings.Builder) error { + b.WriteString("{") + sep := "" + for k, v := range m { + b.WriteString(sep) + b.WriteString(k) + b.WriteString(":") + err := ConvertToSNBT(v, b) + if err != nil { + return err + } + sep = "," + } + b.WriteString("}") + return nil +} + +func sliceToSNBT(s []any, b *strings.Builder) error { + b.WriteString("[") + for i, item := range s { + if i != 0 { + b.WriteString(",") + } + err := ConvertToSNBT(item, b) + if err != nil { + return err + } + } + b.WriteString("]") + return nil +} + +func BinaryTagToJSON(tag *nbt.RawMessage) (json.RawMessage, error) { + return SnbtToJSON(tag.String()) +} + +func SnbtToBinaryTag(snbt string) (nbt.RawMessage, error) { + // Convert SNBT to JSON + j, err := SnbtToJSON(snbt) + if err != nil { + return nbt.RawMessage{}, err + } + // Then convert JSON to binary tag + return JsonToBinaryTag(j) +} + +func JsonToBinaryTag(tag json.RawMessage) (nbt.RawMessage, error) { + // Convert JSON to snbt + snbt, err := JsonToSNBT(tag) + if err != nil { + return nbt.RawMessage{}, err + } + // Then convert snbt to bytes + buf := new(bytes.Buffer) + err = nbt.StringifiedMessage(snbt).MarshalNBT(buf) + if err != nil { + return nbt.RawMessage{}, fmt.Errorf("error marshalling snbt to binary: %w", err) + } + // Then convert bytes to binary tag + var m nbt.RawMessage + err = nbt.Unmarshal(buf.Bytes(), &m) + if err != nil { + return nbt.RawMessage{}, fmt.Errorf("error unmarshalling binary to binary tag: %w", err) + } + return m, nil +} diff --git a/pkg/edition/java/proto/packet/chat/snbt_json_test.go b/pkg/edition/java/proto/packet/chat/snbt_json_test.go new file mode 100644 index 00000000..e955626d --- /dev/null +++ b/pkg/edition/java/proto/packet/chat/snbt_json_test.go @@ -0,0 +1,60 @@ +//go:build go1.18 + +package chat + +import ( + "encoding/json" + "github.com/stretchr/testify/assert" + "testing" +) + +func TestSnbtToJSON(t *testing.T) { + tests := []struct { + name string + snbt string + want json.RawMessage + wantErr bool + }{ + { + name: "without spaces", + snbt: `{a:1,b:hello,c:"world",d:true}`, + want: json.RawMessage(`{"a":1,"b":"hello","c":"world","d":true}`), + wantErr: false, + }, + { + name: "inception as string", + snbt: `{a:1,b:"{c:2,d: {e: 3}}"}`, + want: json.RawMessage(`{"a":1,"b":"{c:2,d: {e: 3}}"}`), + wantErr: false, + }, + // Add more test cases as needed + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := SnbtToJSON(tt.snbt) + if (err != nil) != tt.wantErr { + t.Errorf("SnbtToJSON() error = %v, wantErr %v", err, tt.wantErr) + return + } + assert.Equal(t, string(tt.want), string(got)) + + // test jsonToSNBT + if tt.wantErr { + return + } + got2, err := JsonToSNBT(got) + if err != nil { + t.Errorf("jsonToSNBT() error = %v", err) + return + } + // back to json + got3, err := SnbtToJSON(got2) + if err != nil { + t.Errorf("SnbtToJSON() error = %v", err) + return + } + assert.Equal(t, string(tt.want), string(got3)) + }) + } +} diff --git a/pkg/edition/java/proto/packet/joingame.go b/pkg/edition/java/proto/packet/joingame.go index e7892f8b..989923ab 100644 --- a/pkg/edition/java/proto/packet/joingame.go +++ b/pkg/edition/java/proto/packet/joingame.go @@ -22,15 +22,15 @@ type JoinGame struct { ViewDistance int // 1.14+ ReducedDebugInfo bool ShowRespawnScreen bool - DoLimitedCrafting bool // 1.20.2+ - LevelNames []string // a set of strings, 1.16+ - Registry util.NBT // 1.16+ - DimensionInfo *DimensionInfo // 1.16+ - CurrentDimensionData util.NBT // 1.16.2+ - PreviousGamemode int16 // 1.16+ - SimulationDistance int // 1.18+ - LastDeathPosition *DeathPosition // 1.19+ - PortalCooldown int // 1.20+ + DoLimitedCrafting bool // 1.20.2+ + LevelNames []string // a set of strings, 1.16+ + Registry util.CompoundBinaryTag // 1.16+ + DimensionInfo *DimensionInfo // 1.16+ + CurrentDimensionData util.CompoundBinaryTag // 1.16.2+ + PreviousGamemode int16 // 1.16+ + SimulationDistance int // 1.18+ + LastDeathPosition *DeathPosition // 1.19+ + PortalCooldown int // 1.20+ } type DimensionInfo struct { @@ -99,9 +99,9 @@ func (j *JoinGame) encode116Up(c *proto.PacketContext, wr io.Writer) error { } w.Byte(byte(j.PreviousGamemode)) w.Strings(j.LevelNames) - w.NBT(j.Registry) + w.CompoundBinaryTag(j.Registry, c.Protocol) if c.Protocol.GreaterEqual(version.Minecraft_1_16_2) && c.Protocol.Lower(version.Minecraft_1_19) { - w.NBT(j.CurrentDimensionData) + w.CompoundBinaryTag(j.CurrentDimensionData, c.Protocol) w.String(j.DimensionInfo.RegistryIdentifier) } else { w.String(j.DimensionInfo.RegistryIdentifier) @@ -273,18 +273,17 @@ func (j *JoinGame) decode116Up(c *proto.PacketContext, rd io.Reader) (err error) j.PreviousGamemode = int16(util.PReadByteVal(rd)) r.Strings(&j.LevelNames) - nbtDecoder := util.NewNBTDecoder(rd) - j.Registry, err = util.DecodeNBT(nbtDecoder) + j.Registry, err = util.ReadCompoundTag(rd, c.Protocol) if err != nil { - return err + return fmt.Errorf("error reading registry: %w", err) } var dimensionIdentifier, levelName string if c.Protocol.GreaterEqual(version.Minecraft_1_16_2) && c.Protocol.Lower(version.Minecraft_1_19) { - j.CurrentDimensionData, err = util.DecodeNBT(nbtDecoder) + j.CurrentDimensionData, err = util.ReadCompoundTag(rd, c.Protocol) if err != nil { - return err + return fmt.Errorf("error reading current dimension data: %w", err) } r.String(&dimensionIdentifier) } else { @@ -346,7 +345,6 @@ func (j *JoinGame) decode1202Up(c *proto.PacketContext, rd io.Reader) error { r.Int64(&j.PartialHashedSeed) j.Gamemode = int16(util.PReadByteVal(rd)) - j.PreviousGamemode = int16(util.PReadByteVal(rd)) isDebug := r.Ok() @@ -360,9 +358,10 @@ func (j *JoinGame) decode1202Up(c *proto.PacketContext, rd io.Reader) error { // optional death location if r.Ok() { - key := util.PReadStringVal(rd) - value := util.PReadInt64Val(rd) - j.LastDeathPosition = &DeathPosition{Key: key, Value: value} + j.LastDeathPosition = &DeathPosition{ + Key: util.PReadStringVal(rd), + Value: util.PReadInt64Val(rd), + } } r.VarInt(&j.PortalCooldown) diff --git a/pkg/edition/java/proto/packet/packet_test.go b/pkg/edition/java/proto/packet/packet_test.go index e269befb..0439442c 100644 --- a/pkg/edition/java/proto/packet/packet_test.go +++ b/pkg/edition/java/proto/packet/packet_test.go @@ -12,6 +12,7 @@ import ( "fmt" "go.minekube.com/common/minecraft/key" "go.minekube.com/gate/pkg/edition/java/proto/packet/config" + "go.minekube.com/gate/pkg/edition/java/proto/util" "io" "reflect" "testing" @@ -42,6 +43,10 @@ var ( //go:embed testdata/PlayerChat-1.19.gob playerChatPacketGob []byte playerChatPacket = new(chat.KeyedPlayerChat) + + //go:embed testdata/1-dimension_codec.snbt + dimensionCodecSnbt string + dimensionBinaryTag util.CompoundBinaryTag ) func init() { @@ -49,6 +54,11 @@ func init() { if err != nil { panic(err) } + + dimensionBinaryTag, err = chat.SnbtToBinaryTag(dimensionCodecSnbt) + if err != nil { + panic(err) + } } // All packets to test. @@ -60,9 +70,9 @@ var packets = []proto.Packet{ &TabCompleteRequest{}, &TabCompleteResponse{ Offers: []TabCompleteOffer{ - {Text: mustFakeStr(), Tooltip: &component.Text{Content: "MyTooltip"}}, - {Text: mustFakeStr(), Tooltip: &component.Text{Content: "MyTooltip2"}}, - {Text: mustFakeStr(), Tooltip: &component.Text{Content: "MyTooltip3"}}, + {Text: mustFakeStr(), Tooltip: chat.FromComponent(&component.Text{Content: "MyTooltip"})}, + {Text: mustFakeStr(), Tooltip: chat.FromComponent(&component.Text{Content: "MyTooltip2"})}, + {Text: mustFakeStr(), Tooltip: chat.FromComponent(&component.Text{Content: "MyTooltip3"})}, }, }, &AvailableCommands{RootNode: func() *brigodier.RootCommandNode { @@ -81,7 +91,9 @@ var packets = []proto.Packet{ return n }()}, &ClientSettings{}, - &Disconnect{}, + &Disconnect{ + Reason: chat.FromComponent(&component.Text{Content: "Disconnect"}), + }, &Handshake{}, &KeepAlive{}, &ServerLogin{ @@ -101,15 +113,18 @@ var packets = []proto.Packet{ &StatusRequest{}, &StatusResponse{}, &StatusPing{}, - &HeaderAndFooter{}, + &HeaderAndFooter{ + Header: *chat.FromComponent(&component.Text{Content: "Header"}), + Footer: *chat.FromComponent(&component.Text{Content: "Footer"}), + }, &EncryptionRequest{ ServerID: "984hgf8097c4gh8734hr", PublicKey: []byte("9wh90fh23dh203d2b23b3"), VerifyToken: []byte("32f8d89dh3di"), }, - &title.Text{Component: `{"text":"sub title"}`}, - &title.Subtitle{Component: `{"text":"sub title"}`}, - &title.Actionbar{Component: `{"text":"action bar"}`}, + &title.Text{Component: *chat.FromComponent(&component.Text{Content: "title"})}, + &title.Subtitle{Component: *chat.FromComponent(&component.Text{Content: "sub title"})}, + &title.Actionbar{Component: *chat.FromComponent(&component.Text{Content: "action bar"})}, &title.Clear{Action: title.Reset}, &title.Times{ FadeIn: 1, @@ -164,8 +179,8 @@ var packets = []proto.Packet{ PreviousGamemode: 2, SimulationDistance: 3, LastDeathPosition: mustFake(&DeathPosition{}), - CurrentDimensionData: map[string]any{}, - Registry: map[string]any{}, + CurrentDimensionData: dimensionBinaryTag, + Registry: dimensionBinaryTag, // still use dimension codec for now }, &Respawn{ Dimension: 1, @@ -176,13 +191,13 @@ var packets = []proto.Packet{ DataToKeep: 0, DimensionInfo: mustFake(&DimensionInfo{}), PreviousGamemode: 0, - CurrentDimensionData: map[string]any{}, + CurrentDimensionData: dimensionBinaryTag, LastDeathPosition: mustFake(&DeathPosition{}), }, chat.NewKeyedPlayerCommand("command", []string{"a", "b", "c"}, time.Now()), playerChatPacket, &chat.SystemChat{ - Component: &component.Text{Content: "Preview", S: component.Style{Color: color.Red}}, + Component: chat.FromComponent(&component.Text{Content: "Preview", S: component.Style{Color: color.Red}}), Type: chat.SystemMessageType, }, &chat.LegacyChat{}, @@ -215,14 +230,14 @@ var packets = []proto.Packet{ }, &PlayerChatCompletion{}, &ServerData{ - Description: &component.Text{Content: "Description", S: component.Style{Color: color.Red}}, + Description: chat.FromComponent(&component.Text{Content: "Description", S: component.Style{Color: color.Red}}), Favicon: "Favicon", SecureChatEnforced: true, }, &bossbar.BossBar{ ID: uuid.New(), Action: bossbar.UpdateStyleAction, - Name: &component.Text{Content: "BossBar", S: component.Style{Color: color.Red}}, + Name: chat.FromComponent(&component.Text{Content: "BossBar", S: component.Style{Color: color.Red}}), Percent: 0.5, Color: bossbar.PurpleColor, Overlay: bossbar.Notched10Overlay, @@ -359,7 +374,7 @@ func TestLegacyTitle(t *testing.T) { []proto.Packet{ &title.Legacy{ Action: title.SetActionBar, - Component: `{"text":"legacy action bar"}`, + Component: chat.FromComponent(&component.Text{Content: "legacy action bar"}), }, }...) PacketCodings(t, @@ -368,7 +383,7 @@ func TestLegacyTitle(t *testing.T) { []proto.Packet{ &title.Legacy{ Action: title.SetSubtitle, - Component: `{"text":"legacy sub title"}`, + Component: chat.FromComponent(&component.Text{Content: "legacy sub title"}), }, &title.Legacy{ Action: title.SetTimes, @@ -378,7 +393,7 @@ func TestLegacyTitle(t *testing.T) { }, &title.Legacy{ Action: title.SetTitle, - Component: `{"text":"legacy title"}`, + Component: chat.FromComponent(&component.Text{Content: "legacy title"}), }, }...) } diff --git a/pkg/edition/java/proto/packet/respawn.go b/pkg/edition/java/proto/packet/respawn.go index e33adfbf..ff2516bd 100644 --- a/pkg/edition/java/proto/packet/respawn.go +++ b/pkg/edition/java/proto/packet/respawn.go @@ -1,6 +1,7 @@ package packet import ( + "fmt" "io" "go.minekube.com/gate/pkg/edition/java/proto/util" @@ -13,87 +14,49 @@ type Respawn struct { PartialHashedSeed int64 Difficulty int16 Gamemode int16 - LevelType string // empty by default - DataToKeep byte // 1.16+ - DimensionInfo *DimensionInfo // 1.16-1.16.1 - PreviousGamemode int16 // 1.16+ - CurrentDimensionData util.NBT // 1.16.2+ - LastDeathPosition *DeathPosition // 1.19+ - PortalCooldown int // 1.20+ + LevelType string // empty by default + DataToKeep byte // 1.16+ + DimensionInfo *DimensionInfo // 1.16-1.16.1 + PreviousGamemode int16 // 1.16+ + CurrentDimensionData util.CompoundBinaryTag // 1.16.2+ + LastDeathPosition *DeathPosition // 1.19+ + PortalCooldown int // 1.20+ } func (r *Respawn) Encode(c *proto.PacketContext, wr io.Writer) (err error) { + w := util.PanicWriter(wr) if c.Protocol.GreaterEqual(version.Minecraft_1_16) { if c.Protocol.GreaterEqual(version.Minecraft_1_16_2) && c.Protocol.Lower(version.Minecraft_1_19) { - err = r.CurrentDimensionData.Write(wr) - if err != nil { - return err - } - err = util.WriteString(wr, r.DimensionInfo.RegistryIdentifier) + err = util.WriteBinaryTag(wr, c.Protocol, r.CurrentDimensionData) if err != nil { return err } + w.String(r.DimensionInfo.RegistryIdentifier) } else { - err = util.WriteString(wr, r.DimensionInfo.RegistryIdentifier) - if err != nil { - return err - } - err = util.WriteString(wr, *r.DimensionInfo.LevelName) - if err != nil { - return err - } + w.String(r.DimensionInfo.RegistryIdentifier) + w.String(*r.DimensionInfo.LevelName) } } else { - err = util.WriteInt32(wr, int32(r.Dimension)) - if err != nil { - return err - } + w.Int(r.Dimension) } if c.Protocol.LowerEqual(version.Minecraft_1_13_2) { - err = util.WriteByte(wr, byte(r.Difficulty)) - if err != nil { - return err - } + w.Byte(byte(r.Difficulty)) } if c.Protocol.GreaterEqual(version.Minecraft_1_15) { - err = util.WriteInt64(wr, r.PartialHashedSeed) - if err != nil { - return err - } - } - err = util.WriteByte(wr, byte(r.Gamemode)) - if err != nil { - return err + w.Int64(r.PartialHashedSeed) } + w.Byte(byte(r.Gamemode)) if c.Protocol.GreaterEqual(version.Minecraft_1_16) { - err = util.WriteByte(wr, byte(r.PreviousGamemode)) - if err != nil { - return err - } - err = util.WriteBool(wr, r.DimensionInfo.DebugType) - if err != nil { - return err - } - err = util.WriteBool(wr, r.DimensionInfo.Flat) - if err != nil { - return err - } + w.Byte(byte(r.PreviousGamemode)) + w.Bool(r.DimensionInfo.DebugType) + w.Bool(r.DimensionInfo.Flat) if c.Protocol.Lower(version.Minecraft_1_19_3) { - err = util.WriteBool(wr, r.DataToKeep != 0) - if err != nil { - return err - } + w.Bool(r.DataToKeep != 0) } else if c.Protocol.Lower(version.Minecraft_1_20_2) { - err = util.WriteByte(wr, r.DataToKeep) - if err != nil { - return err - } + w.Byte(r.DataToKeep) } } else { - err = util.WriteString(wr, r.LevelType) - if err != nil { - return err - } + w.String(r.LevelType) } // optional death location @@ -101,80 +64,41 @@ func (r *Respawn) Encode(c *proto.PacketContext, wr io.Writer) (err error) { r.LastDeathPosition.encode(wr) } if c.Protocol.GreaterEqual(version.Minecraft_1_20) { - err = util.WriteVarInt(wr, r.PortalCooldown) - if err != nil { - return err - } + w.VarInt(r.PortalCooldown) } if c.Protocol.GreaterEqual(version.Minecraft_1_20_2) { - err = util.WriteByte(wr, r.DataToKeep) - if err != nil { - return err - } + w.Byte(r.DataToKeep) } return nil } - func (r *Respawn) Decode(c *proto.PacketContext, rd io.Reader) (err error) { + pr := util.PanicReader(rd) var dimensionIdentifier, levelName string if c.Protocol.GreaterEqual(version.Minecraft_1_16) { if c.Protocol.GreaterEqual(version.Minecraft_1_16_2) && c.Protocol.Lower(version.Minecraft_1_19) { - r.CurrentDimensionData, err = util.ReadNBT(rd) + r.CurrentDimensionData, err = util.ReadCompoundTag(rd, c.Protocol) if err != nil { - return err - } - dimensionIdentifier, err = util.ReadString(rd) - if err != nil { - return err + return fmt.Errorf("error reading current dimension data: %w", err) } + pr.String(&dimensionIdentifier) } else { - dimensionIdentifier, err = util.ReadString(rd) - if err != nil { - return err - } - levelName, err = util.ReadString(rd) - if err != nil { - return err - } + pr.String(&dimensionIdentifier) + pr.String(&levelName) } } else { - r.Dimension, err = util.ReadInt(rd) - if err != nil { - return err - } + pr.Int(&r.Dimension) } if c.Protocol.LowerEqual(version.Minecraft_1_13_2) { - difficulty, err := util.ReadByte(rd) - if err != nil { - return err - } - r.Difficulty = int16(difficulty) + r.Difficulty = int16(util.PReadByteVal(rd)) } if c.Protocol.GreaterEqual(version.Minecraft_1_15) { - r.PartialHashedSeed, err = util.ReadInt64(rd) - if err != nil { - return err - } - } - gamemode, err := util.ReadByte(rd) - if err != nil { - return err + pr.Int64(&r.PartialHashedSeed) } - r.Gamemode = int16(gamemode) + r.Gamemode = int16(util.PReadByteVal(rd)) if c.Protocol.GreaterEqual(version.Minecraft_1_16) { - previousGamemode, err := util.ReadByte(rd) - if err != nil { - return err - } - r.PreviousGamemode = int16(previousGamemode) - debug, err := util.ReadBool(rd) - if err != nil { - return err - } - flat, err := util.ReadBool(rd) - if err != nil { - return err - } + r.PreviousGamemode = int16(util.PReadByteVal(rd)) + debug := pr.Ok() + flat := pr.Ok() r.DimensionInfo = &DimensionInfo{ RegistryIdentifier: dimensionIdentifier, LevelName: &levelName, @@ -183,26 +107,16 @@ func (r *Respawn) Decode(c *proto.PacketContext, rd io.Reader) (err error) { } if c.Protocol.Lower(version.Minecraft_1_19_3) { - ok, err := util.ReadBool(rd) - if err != nil { - return err - } - if ok { + if pr.Ok() { r.DataToKeep = 1 } else { r.DataToKeep = 0 } } else if c.Protocol.Lower(version.Minecraft_1_20_2) { - r.DataToKeep, err = util.ReadByte(rd) - if err != nil { - return err - } + pr.Byte(&r.DataToKeep) } } else { - r.LevelType, err = util.ReadStringMax(rd, 16) - if err != nil { - return err - } + pr.String(&r.LevelType) } if c.Protocol.GreaterEqual(version.Minecraft_1_19) { r.LastDeathPosition, err = decodeDeathPosition(rd) @@ -211,16 +125,10 @@ func (r *Respawn) Decode(c *proto.PacketContext, rd io.Reader) (err error) { } } if c.Protocol.GreaterEqual(version.Minecraft_1_20) { - r.PortalCooldown, err = util.ReadVarInt(rd) - if err != nil { - return err - } + pr.VarInt(&r.PortalCooldown) } if c.Protocol.GreaterEqual(version.Minecraft_1_20_2) { - r.DataToKeep, err = util.ReadByte(rd) - if err != nil { - return err - } + pr.Byte(&r.DataToKeep) } return nil } diff --git a/pkg/edition/java/proto/packet/testdata/1-dimension_codec.snbt b/pkg/edition/java/proto/packet/testdata/1-dimension_codec.snbt new file mode 100644 index 00000000..6cb3f241 --- /dev/null +++ b/pkg/edition/java/proto/packet/testdata/1-dimension_codec.snbt @@ -0,0 +1,2085 @@ +{ + "minecraft:dimension_type": { + type: "minecraft:dimension_type", + value: [ + { + name: "minecraft:overworld", + id: 0, + element: { + piglin_safe: 0b, + natural: 1b, + ambient_light: 0.0f, + infiniburn: "minecraft:infiniburn_overworld", + respawn_anchor_works: 0b, + has_skylight: 1b, + bed_works: 1b, + effects: "minecraft:overworld", + has_raids: 1b, + logical_height: 256, + coordinate_scale: 1.0d, + ultrawarm: 0b, + has_ceiling: 0b + } + }, + { + name: "minecraft:overworld_caves", + id: 1, + element: { + piglin_safe: 0b, + natural: 1b, + ambient_light: 0.0f, + infiniburn: "minecraft:infiniburn_overworld", + respawn_anchor_works: 0b, + has_skylight: 1b, + bed_works: 1b, + effects: "minecraft:overworld", + has_raids: 1b, + logical_height: 256, + coordinate_scale: 1.0d, + ultrawarm: 0b, + has_ceiling: 1b + } + }, + { + name: "minecraft:the_nether", + id: 2, + element: { + piglin_safe: 1b, + natural: 0b, + ambient_light: 0.1f, + infiniburn: "minecraft:infiniburn_nether", + respawn_anchor_works: 1b, + has_skylight: 0b, + bed_works: 0b, + effects: "minecraft:the_nether", + fixed_time: 18000L, + has_raids: 0b, + logical_height: 128, + coordinate_scale: 8.0d, + ultrawarm: 1b, + has_ceiling: 1b + } + }, + { + name: "minecraft:the_end", + id: 3, + element: { + piglin_safe: 0b, + natural: 0b, + ambient_light: 0.0f, + infiniburn: "minecraft:infiniburn_end", + respawn_anchor_works: 0b, + has_skylight: 0b, + bed_works: 0b, + effects: "minecraft:the_end", + fixed_time: 6000L, + has_raids: 1b, + logical_height: 256, + coordinate_scale: 1.0d, + ultrawarm: 0b, + has_ceiling: 0b + } + } + ] + }, + "minecraft:worldgen/biome": { + type: "minecraft:worldgen/biome", + value: [ + { + name: "minecraft:ocean", + id: 0, + element: { + precipitation: "rain", + effects: { + sky_color: 8103167, + water_fog_color: 329011, + fog_color: 12638463, + water_color: 4159204, + mood_sound: { + tick_delay: 6000, + offset: 2.0d, + sound: "minecraft:ambient.cave", + block_search_extent: 8 + } + }, + depth: -1.0f, + temperature: 0.5f, + scale: 0.1f, + downfall: 0.5f, + category: "ocean" + } + }, + { + name: "minecraft:plains", + id: 1, + element: { + precipitation: "rain", + effects: { + sky_color: 7907327, + water_fog_color: 329011, + fog_color: 12638463, + water_color: 4159204, + mood_sound: { + tick_delay: 6000, + offset: 2.0d, + sound: "minecraft:ambient.cave", + block_search_extent: 8 + } + }, + depth: 0.125f, + temperature: 0.8f, + scale: 0.05f, + downfall: 0.4f, + category: "plains" + } + }, + { + name: "minecraft:desert", + id: 2, + element: { + precipitation: "none", + effects: { + sky_color: 7254527, + water_fog_color: 329011, + fog_color: 12638463, + water_color: 4159204, + mood_sound: { + tick_delay: 6000, + offset: 2.0d, + sound: "minecraft:ambient.cave", + block_search_extent: 8 + } + }, + depth: 0.125f, + temperature: 2.0f, + scale: 0.05f, + downfall: 0.0f, + category: "desert" + } + }, + { + name: "minecraft:mountains", + id: 3, + element: { + precipitation: "rain", + effects: { + sky_color: 8233727, + water_fog_color: 329011, + fog_color: 12638463, + water_color: 4159204, + mood_sound: { + tick_delay: 6000, + offset: 2.0d, + sound: "minecraft:ambient.cave", + block_search_extent: 8 + } + }, + depth: 1.0f, + temperature: 0.2f, + scale: 0.5f, + downfall: 0.3f, + category: "extreme_hills" + } + }, + { + name: "minecraft:forest", + id: 4, + element: { + precipitation: "rain", + effects: { + sky_color: 7972607, + water_fog_color: 329011, + fog_color: 12638463, + water_color: 4159204, + mood_sound: { + tick_delay: 6000, + offset: 2.0d, + sound: "minecraft:ambient.cave", + block_search_extent: 8 + } + }, + depth: 0.1f, + temperature: 0.7f, + scale: 0.2f, + downfall: 0.8f, + category: "forest" + } + }, + { + name: "minecraft:taiga", + id: 5, + element: { + precipitation: "rain", + effects: { + sky_color: 8233983, + water_fog_color: 329011, + fog_color: 12638463, + water_color: 4159204, + mood_sound: { + tick_delay: 6000, + offset: 2.0d, + sound: "minecraft:ambient.cave", + block_search_extent: 8 + } + }, + depth: 0.2f, + temperature: 0.25f, + scale: 0.2f, + downfall: 0.8f, + category: "taiga" + } + }, + { + name: "minecraft:swamp", + id: 6, + element: { + precipitation: "rain", + effects: { + grass_color_modifier: "swamp", + sky_color: 7907327, + foliage_color: 6975545, + water_fog_color: 2302743, + fog_color: 12638463, + water_color: 6388580, + mood_sound: { + tick_delay: 6000, + offset: 2.0d, + sound: "minecraft:ambient.cave", + block_search_extent: 8 + } + }, + depth: -0.2f, + temperature: 0.8f, + scale: 0.1f, + downfall: 0.9f, + category: "swamp" + } + }, + { + name: "minecraft:river", + id: 7, + element: { + precipitation: "rain", + effects: { + sky_color: 8103167, + water_fog_color: 329011, + fog_color: 12638463, + water_color: 4159204, + mood_sound: { + tick_delay: 6000, + offset: 2.0d, + sound: "minecraft:ambient.cave", + block_search_extent: 8 + } + }, + depth: -0.5f, + temperature: 0.5f, + scale: 0.0f, + downfall: 0.5f, + category: "river" + } + }, + { + name: "minecraft:nether_wastes", + id: 8, + element: { + precipitation: "none", + effects: { + music: { + replace_current_music: 0b, + max_delay: 24000, + sound: "minecraft:music.nether.nether_wastes", + min_delay: 12000 + }, + sky_color: 7254527, + ambient_sound: "minecraft:ambient.nether_wastes.loop", + additions_sound: { + sound: "minecraft:ambient.nether_wastes.additions", + tick_chance: 0.0111d + }, + water_fog_color: 329011, + fog_color: 3344392, + water_color: 4159204, + mood_sound: { + tick_delay: 6000, + offset: 2.0d, + sound: "minecraft:ambient.nether_wastes.mood", + block_search_extent: 8 + } + }, + depth: 0.1f, + temperature: 2.0f, + scale: 0.2f, + downfall: 0.0f, + category: "nether" + } + }, + { + name: "minecraft:the_end", + id: 9, + element: { + precipitation: "none", + effects: { + sky_color: 0, + water_fog_color: 329011, + fog_color: 10518688, + water_color: 4159204, + mood_sound: { + tick_delay: 6000, + offset: 2.0d, + sound: "minecraft:ambient.cave", + block_search_extent: 8 + } + }, + depth: 0.1f, + temperature: 0.5f, + scale: 0.2f, + downfall: 0.5f, + category: "the_end" + } + }, + { + name: "minecraft:frozen_ocean", + id: 10, + element: { + precipitation: "snow", + effects: { + sky_color: 8364543, + water_fog_color: 329011, + fog_color: 12638463, + water_color: 3750089, + mood_sound: { + tick_delay: 6000, + offset: 2.0d, + sound: "minecraft:ambient.cave", + block_search_extent: 8 + } + }, + depth: -1.0f, + temperature: 0.0f, + scale: 0.1f, + downfall: 0.5f, + category: "ocean", + temperature_modifier: "frozen" + } + }, + { + name: "minecraft:frozen_river", + id: 11, + element: { + precipitation: "snow", + effects: { + sky_color: 8364543, + water_fog_color: 329011, + fog_color: 12638463, + water_color: 3750089, + mood_sound: { + tick_delay: 6000, + offset: 2.0d, + sound: "minecraft:ambient.cave", + block_search_extent: 8 + } + }, + depth: -0.5f, + temperature: 0.0f, + scale: 0.0f, + downfall: 0.5f, + category: "river" + } + }, + { + name: "minecraft:snowy_tundra", + id: 12, + element: { + precipitation: "snow", + effects: { + sky_color: 8364543, + water_fog_color: 329011, + fog_color: 12638463, + water_color: 4159204, + mood_sound: { + tick_delay: 6000, + offset: 2.0d, + sound: "minecraft:ambient.cave", + block_search_extent: 8 + } + }, + depth: 0.125f, + temperature: 0.0f, + scale: 0.05f, + downfall: 0.5f, + category: "icy" + } + }, + { + name: "minecraft:snowy_mountains", + id: 13, + element: { + precipitation: "snow", + effects: { + sky_color: 8364543, + water_fog_color: 329011, + fog_color: 12638463, + water_color: 4159204, + mood_sound: { + tick_delay: 6000, + offset: 2.0d, + sound: "minecraft:ambient.cave", + block_search_extent: 8 + } + }, + depth: 0.45f, + temperature: 0.0f, + scale: 0.3f, + downfall: 0.5f, + category: "icy" + } + }, + { + name: "minecraft:mushroom_fields", + id: 14, + element: { + precipitation: "rain", + effects: { + sky_color: 7842047, + water_fog_color: 329011, + fog_color: 12638463, + water_color: 4159204, + mood_sound: { + tick_delay: 6000, + offset: 2.0d, + sound: "minecraft:ambient.cave", + block_search_extent: 8 + } + }, + depth: 0.2f, + temperature: 0.9f, + scale: 0.3f, + downfall: 1.0f, + category: "mushroom" + } + }, + { + name: "minecraft:mushroom_field_shore", + id: 15, + element: { + precipitation: "rain", + effects: { + sky_color: 7842047, + water_fog_color: 329011, + fog_color: 12638463, + water_color: 4159204, + mood_sound: { + tick_delay: 6000, + offset: 2.0d, + sound: "minecraft:ambient.cave", + block_search_extent: 8 + } + }, + depth: 0.0f, + temperature: 0.9f, + scale: 0.025f, + downfall: 1.0f, + category: "mushroom" + } + }, + { + name: "minecraft:beach", + id: 16, + element: { + precipitation: "rain", + effects: { + sky_color: 7907327, + water_fog_color: 329011, + fog_color: 12638463, + water_color: 4159204, + mood_sound: { + tick_delay: 6000, + offset: 2.0d, + sound: "minecraft:ambient.cave", + block_search_extent: 8 + } + }, + depth: 0.0f, + temperature: 0.8f, + scale: 0.025f, + downfall: 0.4f, + category: "beach" + } + }, + { + name: "minecraft:desert_hills", + id: 17, + element: { + precipitation: "none", + effects: { + sky_color: 7254527, + water_fog_color: 329011, + fog_color: 12638463, + water_color: 4159204, + mood_sound: { + tick_delay: 6000, + offset: 2.0d, + sound: "minecraft:ambient.cave", + block_search_extent: 8 + } + }, + depth: 0.45f, + temperature: 2.0f, + scale: 0.3f, + downfall: 0.0f, + category: "desert" + } + }, + { + name: "minecraft:wooded_hills", + id: 18, + element: { + precipitation: "rain", + effects: { + sky_color: 7972607, + water_fog_color: 329011, + fog_color: 12638463, + water_color: 4159204, + mood_sound: { + tick_delay: 6000, + offset: 2.0d, + sound: "minecraft:ambient.cave", + block_search_extent: 8 + } + }, + depth: 0.45f, + temperature: 0.7f, + scale: 0.3f, + downfall: 0.8f, + category: "forest" + } + }, + { + name: "minecraft:taiga_hills", + id: 19, + element: { + precipitation: "rain", + effects: { + sky_color: 8233983, + water_fog_color: 329011, + fog_color: 12638463, + water_color: 4159204, + mood_sound: { + tick_delay: 6000, + offset: 2.0d, + sound: "minecraft:ambient.cave", + block_search_extent: 8 + } + }, + depth: 0.45f, + temperature: 0.25f, + scale: 0.3f, + downfall: 0.8f, + category: "taiga" + } + }, + { + name: "minecraft:mountain_edge", + id: 20, + element: { + precipitation: "rain", + effects: { + sky_color: 8233727, + water_fog_color: 329011, + fog_color: 12638463, + water_color: 4159204, + mood_sound: { + tick_delay: 6000, + offset: 2.0d, + sound: "minecraft:ambient.cave", + block_search_extent: 8 + } + }, + depth: 0.8f, + temperature: 0.2f, + scale: 0.3f, + downfall: 0.3f, + category: "extreme_hills" + } + }, + { + name: "minecraft:jungle", + id: 21, + element: { + precipitation: "rain", + effects: { + sky_color: 7842047, + water_fog_color: 329011, + fog_color: 12638463, + water_color: 4159204, + mood_sound: { + tick_delay: 6000, + offset: 2.0d, + sound: "minecraft:ambient.cave", + block_search_extent: 8 + } + }, + depth: 0.1f, + temperature: 0.95f, + scale: 0.2f, + downfall: 0.9f, + category: "jungle" + } + }, + { + name: "minecraft:jungle_hills", + id: 22, + element: { + precipitation: "rain", + effects: { + sky_color: 7842047, + water_fog_color: 329011, + fog_color: 12638463, + water_color: 4159204, + mood_sound: { + tick_delay: 6000, + offset: 2.0d, + sound: "minecraft:ambient.cave", + block_search_extent: 8 + } + }, + depth: 0.45f, + temperature: 0.95f, + scale: 0.3f, + downfall: 0.9f, + category: "jungle" + } + }, + { + name: "minecraft:jungle_edge", + id: 23, + element: { + precipitation: "rain", + effects: { + sky_color: 7842047, + water_fog_color: 329011, + fog_color: 12638463, + water_color: 4159204, + mood_sound: { + tick_delay: 6000, + offset: 2.0d, + sound: "minecraft:ambient.cave", + block_search_extent: 8 + } + }, + depth: 0.1f, + temperature: 0.95f, + scale: 0.2f, + downfall: 0.8f, + category: "jungle" + } + }, + { + name: "minecraft:deep_ocean", + id: 24, + element: { + precipitation: "rain", + effects: { + sky_color: 8103167, + water_fog_color: 329011, + fog_color: 12638463, + water_color: 4159204, + mood_sound: { + tick_delay: 6000, + offset: 2.0d, + sound: "minecraft:ambient.cave", + block_search_extent: 8 + } + }, + depth: -1.8f, + temperature: 0.5f, + scale: 0.1f, + downfall: 0.5f, + category: "ocean" + } + }, + { + name: "minecraft:stone_shore", + id: 25, + element: { + precipitation: "rain", + effects: { + sky_color: 8233727, + water_fog_color: 329011, + fog_color: 12638463, + water_color: 4159204, + mood_sound: { + tick_delay: 6000, + offset: 2.0d, + sound: "minecraft:ambient.cave", + block_search_extent: 8 + } + }, + depth: 0.1f, + temperature: 0.2f, + scale: 0.8f, + downfall: 0.3f, + category: "none" + } + }, + { + name: "minecraft:snowy_beach", + id: 26, + element: { + precipitation: "snow", + effects: { + sky_color: 8364543, + water_fog_color: 329011, + fog_color: 12638463, + water_color: 4020182, + mood_sound: { + tick_delay: 6000, + offset: 2.0d, + sound: "minecraft:ambient.cave", + block_search_extent: 8 + } + }, + depth: 0.0f, + temperature: 0.05f, + scale: 0.025f, + downfall: 0.3f, + category: "beach" + } + }, + { + name: "minecraft:birch_forest", + id: 27, + element: { + precipitation: "rain", + effects: { + sky_color: 8037887, + water_fog_color: 329011, + fog_color: 12638463, + water_color: 4159204, + mood_sound: { + tick_delay: 6000, + offset: 2.0d, + sound: "minecraft:ambient.cave", + block_search_extent: 8 + } + }, + depth: 0.1f, + temperature: 0.6f, + scale: 0.2f, + downfall: 0.6f, + category: "forest" + } + }, + { + name: "minecraft:birch_forest_hills", + id: 28, + element: { + precipitation: "rain", + effects: { + sky_color: 8037887, + water_fog_color: 329011, + fog_color: 12638463, + water_color: 4159204, + mood_sound: { + tick_delay: 6000, + offset: 2.0d, + sound: "minecraft:ambient.cave", + block_search_extent: 8 + } + }, + depth: 0.45f, + temperature: 0.6f, + scale: 0.3f, + downfall: 0.6f, + category: "forest" + } + }, + { + name: "minecraft:dark_forest", + id: 29, + element: { + precipitation: "rain", + effects: { + grass_color_modifier: "dark_forest", + sky_color: 7972607, + water_fog_color: 329011, + fog_color: 12638463, + water_color: 4159204, + mood_sound: { + tick_delay: 6000, + offset: 2.0d, + sound: "minecraft:ambient.cave", + block_search_extent: 8 + } + }, + depth: 0.1f, + temperature: 0.7f, + scale: 0.2f, + downfall: 0.8f, + category: "forest" + } + }, + { + name: "minecraft:snowy_taiga", + id: 30, + element: { + precipitation: "snow", + effects: { + sky_color: 8625919, + water_fog_color: 329011, + fog_color: 12638463, + water_color: 4020182, + mood_sound: { + tick_delay: 6000, + offset: 2.0d, + sound: "minecraft:ambient.cave", + block_search_extent: 8 + } + }, + depth: 0.2f, + temperature: -0.5f, + scale: 0.2f, + downfall: 0.4f, + category: "taiga" + } + }, + { + name: "minecraft:snowy_taiga_hills", + id: 31, + element: { + precipitation: "snow", + effects: { + sky_color: 8625919, + water_fog_color: 329011, + fog_color: 12638463, + water_color: 4020182, + mood_sound: { + tick_delay: 6000, + offset: 2.0d, + sound: "minecraft:ambient.cave", + block_search_extent: 8 + } + }, + depth: 0.45f, + temperature: -0.5f, + scale: 0.3f, + downfall: 0.4f, + category: "taiga" + } + }, + { + name: "minecraft:giant_tree_taiga", + id: 32, + element: { + precipitation: "rain", + effects: { + sky_color: 8168447, + water_fog_color: 329011, + fog_color: 12638463, + water_color: 4159204, + mood_sound: { + tick_delay: 6000, + offset: 2.0d, + sound: "minecraft:ambient.cave", + block_search_extent: 8 + } + }, + depth: 0.2f, + temperature: 0.3f, + scale: 0.2f, + downfall: 0.8f, + category: "taiga" + } + }, + { + name: "minecraft:giant_tree_taiga_hills", + id: 33, + element: { + precipitation: "rain", + effects: { + sky_color: 8168447, + water_fog_color: 329011, + fog_color: 12638463, + water_color: 4159204, + mood_sound: { + tick_delay: 6000, + offset: 2.0d, + sound: "minecraft:ambient.cave", + block_search_extent: 8 + } + }, + depth: 0.45f, + temperature: 0.3f, + scale: 0.3f, + downfall: 0.8f, + category: "taiga" + } + }, + { + name: "minecraft:wooded_mountains", + id: 34, + element: { + precipitation: "rain", + effects: { + sky_color: 8233727, + water_fog_color: 329011, + fog_color: 12638463, + water_color: 4159204, + mood_sound: { + tick_delay: 6000, + offset: 2.0d, + sound: "minecraft:ambient.cave", + block_search_extent: 8 + } + }, + depth: 1.0f, + temperature: 0.2f, + scale: 0.5f, + downfall: 0.3f, + category: "extreme_hills" + } + }, + { + name: "minecraft:savanna", + id: 35, + element: { + precipitation: "none", + effects: { + sky_color: 7711487, + water_fog_color: 329011, + fog_color: 12638463, + water_color: 4159204, + mood_sound: { + tick_delay: 6000, + offset: 2.0d, + sound: "minecraft:ambient.cave", + block_search_extent: 8 + } + }, + depth: 0.125f, + temperature: 1.2f, + scale: 0.05f, + downfall: 0.0f, + category: "savanna" + } + }, + { + name: "minecraft:savanna_plateau", + id: 36, + element: { + precipitation: "none", + effects: { + sky_color: 7776511, + water_fog_color: 329011, + fog_color: 12638463, + water_color: 4159204, + mood_sound: { + tick_delay: 6000, + offset: 2.0d, + sound: "minecraft:ambient.cave", + block_search_extent: 8 + } + }, + depth: 1.5f, + temperature: 1.0f, + scale: 0.025f, + downfall: 0.0f, + category: "savanna" + } + }, + { + name: "minecraft:badlands", + id: 37, + element: { + precipitation: "none", + effects: { + sky_color: 7254527, + grass_color: 9470285, + foliage_color: 10387789, + water_fog_color: 329011, + fog_color: 12638463, + water_color: 4159204, + mood_sound: { + tick_delay: 6000, + offset: 2.0d, + sound: "minecraft:ambient.cave", + block_search_extent: 8 + } + }, + depth: 0.1f, + temperature: 2.0f, + scale: 0.2f, + downfall: 0.0f, + category: "mesa" + } + }, + { + name: "minecraft:wooded_badlands_plateau", + id: 38, + element: { + precipitation: "none", + effects: { + sky_color: 7254527, + grass_color: 9470285, + foliage_color: 10387789, + water_fog_color: 329011, + fog_color: 12638463, + water_color: 4159204, + mood_sound: { + tick_delay: 6000, + offset: 2.0d, + sound: "minecraft:ambient.cave", + block_search_extent: 8 + } + }, + depth: 1.5f, + temperature: 2.0f, + scale: 0.025f, + downfall: 0.0f, + category: "mesa" + } + }, + { + name: "minecraft:badlands_plateau", + id: 39, + element: { + precipitation: "none", + effects: { + sky_color: 7254527, + grass_color: 9470285, + foliage_color: 10387789, + water_fog_color: 329011, + fog_color: 12638463, + water_color: 4159204, + mood_sound: { + tick_delay: 6000, + offset: 2.0d, + sound: "minecraft:ambient.cave", + block_search_extent: 8 + } + }, + depth: 1.5f, + temperature: 2.0f, + scale: 0.025f, + downfall: 0.0f, + category: "mesa" + } + }, + { + name: "minecraft:small_end_islands", + id: 40, + element: { + precipitation: "none", + effects: { + sky_color: 0, + water_fog_color: 329011, + fog_color: 10518688, + water_color: 4159204, + mood_sound: { + tick_delay: 6000, + offset: 2.0d, + sound: "minecraft:ambient.cave", + block_search_extent: 8 + } + }, + depth: 0.1f, + temperature: 0.5f, + scale: 0.2f, + downfall: 0.5f, + category: "the_end" + } + }, + { + name: "minecraft:end_midlands", + id: 41, + element: { + precipitation: "none", + effects: { + sky_color: 0, + water_fog_color: 329011, + fog_color: 10518688, + water_color: 4159204, + mood_sound: { + tick_delay: 6000, + offset: 2.0d, + sound: "minecraft:ambient.cave", + block_search_extent: 8 + } + }, + depth: 0.1f, + temperature: 0.5f, + scale: 0.2f, + downfall: 0.5f, + category: "the_end" + } + }, + { + name: "minecraft:end_highlands", + id: 42, + element: { + precipitation: "none", + effects: { + sky_color: 0, + water_fog_color: 329011, + fog_color: 10518688, + water_color: 4159204, + mood_sound: { + tick_delay: 6000, + offset: 2.0d, + sound: "minecraft:ambient.cave", + block_search_extent: 8 + } + }, + depth: 0.1f, + temperature: 0.5f, + scale: 0.2f, + downfall: 0.5f, + category: "the_end" + } + }, + { + name: "minecraft:end_barrens", + id: 43, + element: { + precipitation: "none", + effects: { + sky_color: 0, + water_fog_color: 329011, + fog_color: 10518688, + water_color: 4159204, + mood_sound: { + tick_delay: 6000, + offset: 2.0d, + sound: "minecraft:ambient.cave", + block_search_extent: 8 + } + }, + depth: 0.1f, + temperature: 0.5f, + scale: 0.2f, + downfall: 0.5f, + category: "the_end" + } + }, + { + name: "minecraft:warm_ocean", + id: 44, + element: { + precipitation: "rain", + effects: { + sky_color: 8103167, + water_fog_color: 270131, + fog_color: 12638463, + water_color: 4445678, + mood_sound: { + tick_delay: 6000, + offset: 2.0d, + sound: "minecraft:ambient.cave", + block_search_extent: 8 + } + }, + depth: -1.0f, + temperature: 0.5f, + scale: 0.1f, + downfall: 0.5f, + category: "ocean" + } + }, + { + name: "minecraft:lukewarm_ocean", + id: 45, + element: { + precipitation: "rain", + effects: { + sky_color: 8103167, + water_fog_color: 267827, + fog_color: 12638463, + water_color: 4566514, + mood_sound: { + tick_delay: 6000, + offset: 2.0d, + sound: "minecraft:ambient.cave", + block_search_extent: 8 + } + }, + depth: -1.0f, + temperature: 0.5f, + scale: 0.1f, + downfall: 0.5f, + category: "ocean" + } + }, + { + name: "minecraft:cold_ocean", + id: 46, + element: { + precipitation: "rain", + effects: { + sky_color: 8103167, + water_fog_color: 329011, + fog_color: 12638463, + water_color: 4020182, + mood_sound: { + tick_delay: 6000, + offset: 2.0d, + sound: "minecraft:ambient.cave", + block_search_extent: 8 + } + }, + depth: -1.0f, + temperature: 0.5f, + scale: 0.1f, + downfall: 0.5f, + category: "ocean" + } + }, + { + name: "minecraft:deep_warm_ocean", + id: 47, + element: { + precipitation: "rain", + effects: { + sky_color: 8103167, + water_fog_color: 270131, + fog_color: 12638463, + water_color: 4445678, + mood_sound: { + tick_delay: 6000, + offset: 2.0d, + sound: "minecraft:ambient.cave", + block_search_extent: 8 + } + }, + depth: -1.8f, + temperature: 0.5f, + scale: 0.1f, + downfall: 0.5f, + category: "ocean" + } + }, + { + name: "minecraft:deep_lukewarm_ocean", + id: 48, + element: { + precipitation: "rain", + effects: { + sky_color: 8103167, + water_fog_color: 267827, + fog_color: 12638463, + water_color: 4566514, + mood_sound: { + tick_delay: 6000, + offset: 2.0d, + sound: "minecraft:ambient.cave", + block_search_extent: 8 + } + }, + depth: -1.8f, + temperature: 0.5f, + scale: 0.1f, + downfall: 0.5f, + category: "ocean" + } + }, + { + name: "minecraft:deep_cold_ocean", + id: 49, + element: { + precipitation: "rain", + effects: { + sky_color: 8103167, + water_fog_color: 329011, + fog_color: 12638463, + water_color: 4020182, + mood_sound: { + tick_delay: 6000, + offset: 2.0d, + sound: "minecraft:ambient.cave", + block_search_extent: 8 + } + }, + depth: -1.8f, + temperature: 0.5f, + scale: 0.1f, + downfall: 0.5f, + category: "ocean" + } + }, + { + name: "minecraft:deep_frozen_ocean", + id: 50, + element: { + precipitation: "rain", + effects: { + sky_color: 8103167, + water_fog_color: 329011, + fog_color: 12638463, + water_color: 3750089, + mood_sound: { + tick_delay: 6000, + offset: 2.0d, + sound: "minecraft:ambient.cave", + block_search_extent: 8 + } + }, + depth: -1.8f, + temperature: 0.5f, + scale: 0.1f, + downfall: 0.5f, + category: "ocean", + temperature_modifier: "frozen" + } + }, + { + name: "minecraft:the_void", + id: 127, + element: { + precipitation: "none", + effects: { + sky_color: 8103167, + water_fog_color: 329011, + fog_color: 12638463, + water_color: 4159204, + mood_sound: { + tick_delay: 6000, + offset: 2.0d, + sound: "minecraft:ambient.cave", + block_search_extent: 8 + } + }, + depth: 0.1f, + temperature: 0.5f, + scale: 0.2f, + downfall: 0.5f, + category: "none" + } + }, + { + name: "minecraft:sunflower_plains", + id: 129, + element: { + precipitation: "rain", + effects: { + sky_color: 7907327, + water_fog_color: 329011, + fog_color: 12638463, + water_color: 4159204, + mood_sound: { + tick_delay: 6000, + offset: 2.0d, + sound: "minecraft:ambient.cave", + block_search_extent: 8 + } + }, + depth: 0.125f, + temperature: 0.8f, + scale: 0.05f, + downfall: 0.4f, + category: "plains" + } + }, + { + name: "minecraft:desert_lakes", + id: 130, + element: { + precipitation: "none", + effects: { + sky_color: 7254527, + water_fog_color: 329011, + fog_color: 12638463, + water_color: 4159204, + mood_sound: { + tick_delay: 6000, + offset: 2.0d, + sound: "minecraft:ambient.cave", + block_search_extent: 8 + } + }, + depth: 0.225f, + temperature: 2.0f, + scale: 0.25f, + downfall: 0.0f, + category: "desert" + } + }, + { + name: "minecraft:gravelly_mountains", + id: 131, + element: { + precipitation: "rain", + effects: { + sky_color: 8233727, + water_fog_color: 329011, + fog_color: 12638463, + water_color: 4159204, + mood_sound: { + tick_delay: 6000, + offset: 2.0d, + sound: "minecraft:ambient.cave", + block_search_extent: 8 + } + }, + depth: 1.0f, + temperature: 0.2f, + scale: 0.5f, + downfall: 0.3f, + category: "extreme_hills" + } + }, + { + name: "minecraft:flower_forest", + id: 132, + element: { + precipitation: "rain", + effects: { + sky_color: 7972607, + water_fog_color: 329011, + fog_color: 12638463, + water_color: 4159204, + mood_sound: { + tick_delay: 6000, + offset: 2.0d, + sound: "minecraft:ambient.cave", + block_search_extent: 8 + } + }, + depth: 0.1f, + temperature: 0.7f, + scale: 0.4f, + downfall: 0.8f, + category: "forest" + } + }, + { + name: "minecraft:taiga_mountains", + id: 133, + element: { + precipitation: "rain", + effects: { + sky_color: 8233983, + water_fog_color: 329011, + fog_color: 12638463, + water_color: 4159204, + mood_sound: { + tick_delay: 6000, + offset: 2.0d, + sound: "minecraft:ambient.cave", + block_search_extent: 8 + } + }, + depth: 0.3f, + temperature: 0.25f, + scale: 0.4f, + downfall: 0.8f, + category: "taiga" + } + }, + { + name: "minecraft:swamp_hills", + id: 134, + element: { + precipitation: "rain", + effects: { + grass_color_modifier: "swamp", + sky_color: 7907327, + foliage_color: 6975545, + water_fog_color: 2302743, + fog_color: 12638463, + water_color: 6388580, + mood_sound: { + tick_delay: 6000, + offset: 2.0d, + sound: "minecraft:ambient.cave", + block_search_extent: 8 + } + }, + depth: -0.1f, + temperature: 0.8f, + scale: 0.3f, + downfall: 0.9f, + category: "swamp" + } + }, + { + name: "minecraft:ice_spikes", + id: 140, + element: { + precipitation: "snow", + effects: { + sky_color: 8364543, + water_fog_color: 329011, + fog_color: 12638463, + water_color: 4159204, + mood_sound: { + tick_delay: 6000, + offset: 2.0d, + sound: "minecraft:ambient.cave", + block_search_extent: 8 + } + }, + depth: 0.425f, + temperature: 0.0f, + scale: 0.45000002f, + downfall: 0.5f, + category: "icy" + } + }, + { + name: "minecraft:modified_jungle", + id: 149, + element: { + precipitation: "rain", + effects: { + sky_color: 7842047, + water_fog_color: 329011, + fog_color: 12638463, + water_color: 4159204, + mood_sound: { + tick_delay: 6000, + offset: 2.0d, + sound: "minecraft:ambient.cave", + block_search_extent: 8 + } + }, + depth: 0.2f, + temperature: 0.95f, + scale: 0.4f, + downfall: 0.9f, + category: "jungle" + } + }, + { + name: "minecraft:modified_jungle_edge", + id: 151, + element: { + precipitation: "rain", + effects: { + sky_color: 7842047, + water_fog_color: 329011, + fog_color: 12638463, + water_color: 4159204, + mood_sound: { + tick_delay: 6000, + offset: 2.0d, + sound: "minecraft:ambient.cave", + block_search_extent: 8 + } + }, + depth: 0.2f, + temperature: 0.95f, + scale: 0.4f, + downfall: 0.8f, + category: "jungle" + } + }, + { + name: "minecraft:tall_birch_forest", + id: 155, + element: { + precipitation: "rain", + effects: { + sky_color: 8037887, + water_fog_color: 329011, + fog_color: 12638463, + water_color: 4159204, + mood_sound: { + tick_delay: 6000, + offset: 2.0d, + sound: "minecraft:ambient.cave", + block_search_extent: 8 + } + }, + depth: 0.2f, + temperature: 0.6f, + scale: 0.4f, + downfall: 0.6f, + category: "forest" + } + }, + { + name: "minecraft:tall_birch_hills", + id: 156, + element: { + precipitation: "rain", + effects: { + sky_color: 8037887, + water_fog_color: 329011, + fog_color: 12638463, + water_color: 4159204, + mood_sound: { + tick_delay: 6000, + offset: 2.0d, + sound: "minecraft:ambient.cave", + block_search_extent: 8 + } + }, + depth: 0.55f, + temperature: 0.6f, + scale: 0.5f, + downfall: 0.6f, + category: "forest" + } + }, + { + name: "minecraft:dark_forest_hills", + id: 157, + element: { + precipitation: "rain", + effects: { + grass_color_modifier: "dark_forest", + sky_color: 7972607, + water_fog_color: 329011, + fog_color: 12638463, + water_color: 4159204, + mood_sound: { + tick_delay: 6000, + offset: 2.0d, + sound: "minecraft:ambient.cave", + block_search_extent: 8 + } + }, + depth: 0.2f, + temperature: 0.7f, + scale: 0.4f, + downfall: 0.8f, + category: "forest" + } + }, + { + name: "minecraft:snowy_taiga_mountains", + id: 158, + element: { + precipitation: "snow", + effects: { + sky_color: 8625919, + water_fog_color: 329011, + fog_color: 12638463, + water_color: 4020182, + mood_sound: { + tick_delay: 6000, + offset: 2.0d, + sound: "minecraft:ambient.cave", + block_search_extent: 8 + } + }, + depth: 0.3f, + temperature: -0.5f, + scale: 0.4f, + downfall: 0.4f, + category: "taiga" + } + }, + { + name: "minecraft:giant_spruce_taiga", + id: 160, + element: { + precipitation: "rain", + effects: { + sky_color: 8233983, + water_fog_color: 329011, + fog_color: 12638463, + water_color: 4159204, + mood_sound: { + tick_delay: 6000, + offset: 2.0d, + sound: "minecraft:ambient.cave", + block_search_extent: 8 + } + }, + depth: 0.2f, + temperature: 0.25f, + scale: 0.2f, + downfall: 0.8f, + category: "taiga" + } + }, + { + name: "minecraft:giant_spruce_taiga_hills", + id: 161, + element: { + precipitation: "rain", + effects: { + sky_color: 8233983, + water_fog_color: 329011, + fog_color: 12638463, + water_color: 4159204, + mood_sound: { + tick_delay: 6000, + offset: 2.0d, + sound: "minecraft:ambient.cave", + block_search_extent: 8 + } + }, + depth: 0.2f, + temperature: 0.25f, + scale: 0.2f, + downfall: 0.8f, + category: "taiga" + } + }, + { + name: "minecraft:modified_gravelly_mountains", + id: 162, + element: { + precipitation: "rain", + effects: { + sky_color: 8233727, + water_fog_color: 329011, + fog_color: 12638463, + water_color: 4159204, + mood_sound: { + tick_delay: 6000, + offset: 2.0d, + sound: "minecraft:ambient.cave", + block_search_extent: 8 + } + }, + depth: 1.0f, + temperature: 0.2f, + scale: 0.5f, + downfall: 0.3f, + category: "extreme_hills" + } + }, + { + name: "minecraft:shattered_savanna", + id: 163, + element: { + precipitation: "none", + effects: { + sky_color: 7776767, + water_fog_color: 329011, + fog_color: 12638463, + water_color: 4159204, + mood_sound: { + tick_delay: 6000, + offset: 2.0d, + sound: "minecraft:ambient.cave", + block_search_extent: 8 + } + }, + depth: 0.3625f, + temperature: 1.1f, + scale: 1.225f, + downfall: 0.0f, + category: "savanna" + } + }, + { + name: "minecraft:shattered_savanna_plateau", + id: 164, + element: { + precipitation: "none", + effects: { + sky_color: 7776511, + water_fog_color: 329011, + fog_color: 12638463, + water_color: 4159204, + mood_sound: { + tick_delay: 6000, + offset: 2.0d, + sound: "minecraft:ambient.cave", + block_search_extent: 8 + } + }, + depth: 1.05f, + temperature: 1.0f, + scale: 1.2125001f, + downfall: 0.0f, + category: "savanna" + } + }, + { + name: "minecraft:eroded_badlands", + id: 165, + element: { + precipitation: "none", + effects: { + sky_color: 7254527, + grass_color: 9470285, + foliage_color: 10387789, + water_fog_color: 329011, + fog_color: 12638463, + water_color: 4159204, + mood_sound: { + tick_delay: 6000, + offset: 2.0d, + sound: "minecraft:ambient.cave", + block_search_extent: 8 + } + }, + depth: 0.1f, + temperature: 2.0f, + scale: 0.2f, + downfall: 0.0f, + category: "mesa" + } + }, + { + name: "minecraft:modified_wooded_badlands_plateau", + id: 166, + element: { + precipitation: "none", + effects: { + sky_color: 7254527, + grass_color: 9470285, + foliage_color: 10387789, + water_fog_color: 329011, + fog_color: 12638463, + water_color: 4159204, + mood_sound: { + tick_delay: 6000, + offset: 2.0d, + sound: "minecraft:ambient.cave", + block_search_extent: 8 + } + }, + depth: 0.45f, + temperature: 2.0f, + scale: 0.3f, + downfall: 0.0f, + category: "mesa" + } + }, + { + name: "minecraft:modified_badlands_plateau", + id: 167, + element: { + precipitation: "none", + effects: { + sky_color: 7254527, + grass_color: 9470285, + foliage_color: 10387789, + water_fog_color: 329011, + fog_color: 12638463, + water_color: 4159204, + mood_sound: { + tick_delay: 6000, + offset: 2.0d, + sound: "minecraft:ambient.cave", + block_search_extent: 8 + } + }, + depth: 0.45f, + temperature: 2.0f, + scale: 0.3f, + downfall: 0.0f, + category: "mesa" + } + }, + { + name: "minecraft:bamboo_jungle", + id: 168, + element: { + precipitation: "rain", + effects: { + sky_color: 7842047, + water_fog_color: 329011, + fog_color: 12638463, + water_color: 4159204, + mood_sound: { + tick_delay: 6000, + offset: 2.0d, + sound: "minecraft:ambient.cave", + block_search_extent: 8 + } + }, + depth: 0.1f, + temperature: 0.95f, + scale: 0.2f, + downfall: 0.9f, + category: "jungle" + } + }, + { + name: "minecraft:bamboo_jungle_hills", + id: 169, + element: { + precipitation: "rain", + effects: { + sky_color: 7842047, + water_fog_color: 329011, + fog_color: 12638463, + water_color: 4159204, + mood_sound: { + tick_delay: 6000, + offset: 2.0d, + sound: "minecraft:ambient.cave", + block_search_extent: 8 + } + }, + depth: 0.45f, + temperature: 0.95f, + scale: 0.3f, + downfall: 0.9f, + category: "jungle" + } + }, + { + name: "minecraft:soul_sand_valley", + id: 170, + element: { + precipitation: "none", + effects: { + music: { + replace_current_music: 0b, + max_delay: 24000, + sound: "minecraft:music.nether.soul_sand_valley", + min_delay: 12000 + }, + sky_color: 7254527, + ambient_sound: "minecraft:ambient.soul_sand_valley.loop", + additions_sound: { + sound: "minecraft:ambient.soul_sand_valley.additions", + tick_chance: 0.0111d + }, + particle: { + probability: 0.00625f, + options: { + type: "minecraft:ash" + } + }, + water_fog_color: 329011, + fog_color: 1787717, + water_color: 4159204, + mood_sound: { + tick_delay: 6000, + offset: 2.0d, + sound: "minecraft:ambient.soul_sand_valley.mood", + block_search_extent: 8 + } + }, + depth: 0.1f, + temperature: 2.0f, + scale: 0.2f, + downfall: 0.0f, + category: "nether" + } + }, + { + name: "minecraft:crimson_forest", + id: 171, + element: { + precipitation: "none", + effects: { + music: { + replace_current_music: 0b, + max_delay: 24000, + sound: "minecraft:music.nether.crimson_forest", + min_delay: 12000 + }, + sky_color: 7254527, + ambient_sound: "minecraft:ambient.crimson_forest.loop", + additions_sound: { + sound: "minecraft:ambient.crimson_forest.additions", + tick_chance: 0.0111d + }, + particle: { + probability: 0.025f, + options: { + type: "minecraft:crimson_spore" + } + }, + water_fog_color: 329011, + fog_color: 3343107, + water_color: 4159204, + mood_sound: { + tick_delay: 6000, + offset: 2.0d, + sound: "minecraft:ambient.crimson_forest.mood", + block_search_extent: 8 + } + }, + depth: 0.1f, + temperature: 2.0f, + scale: 0.2f, + downfall: 0.0f, + category: "nether" + } + }, + { + name: "minecraft:warped_forest", + id: 172, + element: { + precipitation: "none", + effects: { + music: { + replace_current_music: 0b, + max_delay: 24000, + sound: "minecraft:music.nether.warped_forest", + min_delay: 12000 + }, + sky_color: 7254527, + ambient_sound: "minecraft:ambient.warped_forest.loop", + additions_sound: { + sound: "minecraft:ambient.warped_forest.additions", + tick_chance: 0.0111d + }, + particle: { + probability: 0.01428f, + options: { + type: "minecraft:warped_spore" + } + }, + water_fog_color: 329011, + fog_color: 1705242, + water_color: 4159204, + mood_sound: { + tick_delay: 6000, + offset: 2.0d, + sound: "minecraft:ambient.warped_forest.mood", + block_search_extent: 8 + } + }, + depth: 0.1f, + temperature: 2.0f, + scale: 0.2f, + downfall: 0.0f, + category: "nether" + } + }, + { + name: "minecraft:basalt_deltas", + id: 173, + element: { + precipitation: "none", + effects: { + music: { + replace_current_music: 0b, + max_delay: 24000, + sound: "minecraft:music.nether.basalt_deltas", + min_delay: 12000 + }, + sky_color: 7254527, + ambient_sound: "minecraft:ambient.basalt_deltas.loop", + additions_sound: { + sound: "minecraft:ambient.basalt_deltas.additions", + tick_chance: 0.0111d + }, + particle: { + probability: 0.118093334f, + options: { + type: "minecraft:white_ash" + } + }, + water_fog_color: 4341314, + fog_color: 6840176, + water_color: 4159204, + mood_sound: { + tick_delay: 6000, + offset: 2.0d, + sound: "minecraft:ambient.basalt_deltas.mood", + block_search_extent: 8 + } + }, + depth: 0.1f, + temperature: 2.0f, + scale: 0.2f, + downfall: 0.0f, + category: "nether" + } + } + ] + } +} diff --git a/pkg/edition/java/proto/packet/title/title.go b/pkg/edition/java/proto/packet/title/title.go index da9ed8cb..bb18bc21 100644 --- a/pkg/edition/java/proto/packet/title/title.go +++ b/pkg/edition/java/proto/packet/title/title.go @@ -199,6 +199,9 @@ func (l *Legacy) Decode(c *proto.PacketContext, rd io.Reader) error { return nil case SetTitle, SetSubtitle, SetActionBar: l.Component, err = chat.ReadComponentHolder(rd, c.Protocol) + if err != nil { + err = fmt.Errorf("error reading component: %w", err) + } case SetTimes: l.FadeIn, err = util.ReadInt(rd) if err != nil { diff --git a/pkg/edition/java/proto/util/nbt.go b/pkg/edition/java/proto/util/nbt.go index 4832ecc8..a32c40c1 100644 --- a/pkg/edition/java/proto/util/nbt.go +++ b/pkg/edition/java/proto/util/nbt.go @@ -1,165 +1,79 @@ package util import ( + "bytes" + "fmt" + "github.com/Tnze/go-mc/nbt" + "go.minekube.com/gate/pkg/edition/java/proto/version" + "go.minekube.com/gate/pkg/gate/proto" "io" - - "github.com/sandertv/gophertunnel/minecraft/nbt" ) -// NBT is a named binary tag aka compound binary tag. -type NBT map[string]any - -func (b NBT) Bool(name string) (bool, bool) { - val, ok := b.Uint8(name) - return val == 1, ok -} +type ( + // BinaryTag is a binary tag. + BinaryTag = nbt.RawMessage + // CompoundBinaryTag is a compound binary tag. + CompoundBinaryTag = BinaryTag +) -func (b NBT) Int8(name string) (ret int8, ok bool) { - var val any - if val, ok = b[name]; ok { - ret, ok = val.(int8) - } - return -} -func (b NBT) Uint8(name string) (ret uint8, ok bool) { - var val any - if val, ok = b[name]; ok { - ret, ok = val.(uint8) +// ReadBinaryTag reads a binary tag from the provided reader. +func ReadBinaryTag(r io.Reader, protocol proto.Protocol) (bt BinaryTag, err error) { + // Read the type + bt.Type, err = ReadByte(r) + if err != nil { + return bt, fmt.Errorf("error reading binary tag type: %w", err) } - return -} -func (b NBT) Int16(name string) (ret int16, ok bool) { - var val any - if val, ok = b[name]; ok { - ret, ok = val.(int16) + // Skip bytes if protocol version is less than 1.20.2. + if protocol.Lower(version.Minecraft_1_20_2) { + _, err = ReadUint16(r) + if err != nil { + return bt, fmt.Errorf("error skipping bytes: %w", err) + } } - return -} -func (b NBT) Int32(name string) (ret int32, ok bool) { - var val any - if val, ok = b[name]; ok { - ret, ok = val.(int32) - } - return -} -func (b NBT) Int(name string) (int, bool) { - i, ok := b.Int32(name) - return int(i), ok -} + // use io.MultiReader() to reassemble the reader to use for decoding + // the binary tag without the skipped bytes + mr := io.MultiReader(bytes.NewReader([]byte{bt.Type}), r) -func (b NBT) Int64(name string) (ret int64, ok bool) { - var val any - if val, ok = b[name]; ok { - ret, ok = val.(int64) - } - return -} + // Read the data + dec := nbt.NewDecoder(mr) + dec.NetworkFormat(true) // skip tag name -func (b NBT) Float32(name string) (ret float32, ok bool) { - var val any - if val, ok = b[name]; ok { - ret, ok = val.(float32) + if _, err = dec.Decode(&bt); err != nil { + return bt, fmt.Errorf("error decoding binary tag: %w", err) } - return -} -func (b NBT) Float64(name string) (ret float64, ok bool) { - var val any - if val, ok = b[name]; ok { - ret, ok = val.(float64) - } - return + return bt, nil } -func (b NBT) ByteArray(name string) (ret []byte, ok bool) { - var val any - if val, ok = b[name]; ok { - ret, ok = val.([]byte) +// ReadCompoundTag reads a compound binary tag from the provided reader. +func ReadCompoundTag(r io.Reader, protocol proto.Protocol) (CompoundBinaryTag, error) { + bt, err := ReadBinaryTag(r, protocol) + if err != nil { + return bt, err } - return -} - -func (b NBT) String(name string) (ret string, ok bool) { - var val any - if val, ok = b[name]; ok { - ret, ok = val.(string) + if bt.Type != nbt.TagCompound { + return bt, fmt.Errorf("expected root tag to be a compound tag, got %v", bt.Type) } - return + return bt, nil } -func (b NBT) NBT(name string) (ret NBT, ok bool) { - var val any - if val, ok = b[name]; ok { - ret, ok = val.(map[string]any) - if !ok { - ret, ok = val.(NBT) - } +// WriteBinaryTag writes a binary tag to the provided writer. +func WriteBinaryTag(w io.Writer, protocol proto.Protocol, bt BinaryTag) error { + // Write the type + if err := WriteByte(w, bt.Type); err != nil { + return fmt.Errorf("error writing binary tag type: %w", err) } - return -} - -func (b NBT) List(name string) (ret []NBT, ok bool) { - var val any - if val, ok = b[name]; ok { - var l []any - l, ok = val.([]any) - if !ok { - ret, ok = val.([]NBT) - return - } - var n NBT - for _, e := range l { - n, ok = e.(map[string]any) - if ok { - ret = append(ret, n) - } + if protocol.Lower(version.Minecraft_1_20_2) { + // Empty name + if err := WriteInt16(w, 0); err != nil { + return fmt.Errorf("error writing binary tag name: %w", err) } } - return -} - -func (b NBT) Int32Array(name string) (ret []int32, ok bool) { - var val any - if val, ok = b[name]; ok { - ret, ok = val.([]int32) - } - return -} - -func (b NBT) Int64Array(name string) (ret []int64, ok bool) { - var val any - if val, ok = b[name]; ok { - ret, ok = val.([]int64) + // Write the data + if _, err := w.Write(bt.Data); err != nil { + return fmt.Errorf("error writing binary tag data: %w", err) } - return -} - -func ReadNBT(rd io.Reader) (NBT, error) { - return DecodeNBT(NewNBTDecoder(rd)) -} - -func DecodeNBT( - decoder interface{ Decode(any) error }, -) (NBT, error) { - v := NBT{} - err := decoder.Decode(&v) - return v, err -} - -func NewNBTDecoder(r io.Reader) *nbt.Decoder { - return nbt.NewDecoderWithEncoding(r, nbt.BigEndian) -} - -func NewNBTEncoder(w io.Writer) *nbt.Encoder { - return nbt.NewEncoderWithEncoding(w, nbt.BigEndian) -} - -func WriteNBT(w io.Writer, nbt NBT) error { - return NewNBTEncoder(w).Encode(nbt) -} - -func (b NBT) Write(w io.Writer) error { - return WriteNBT(w, b) + return nil } diff --git a/pkg/edition/java/proto/util/preader.go b/pkg/edition/java/proto/util/preader.go index a6d58743..0cb63d7a 100644 --- a/pkg/edition/java/proto/util/preader.go +++ b/pkg/edition/java/proto/util/preader.go @@ -43,6 +43,10 @@ func (r *PReader) Strings(i *[]string) { PReadStrings(r.r, i) } +func (r *PReader) Byte(b *byte) { + PReadByte(r.r, b) +} + func PReadStrings(r io.Reader, i *[]string) { v, err := ReadStringArray(r) if err != nil { diff --git a/pkg/edition/java/proto/util/pwriter.go b/pkg/edition/java/proto/util/pwriter.go index e50a8416..0dd631f5 100644 --- a/pkg/edition/java/proto/util/pwriter.go +++ b/pkg/edition/java/proto/util/pwriter.go @@ -1,6 +1,9 @@ package util -import "io" +import ( + "go.minekube.com/gate/pkg/gate/proto" + "io" +) // Recover is a helper function to recover from a panic and set the error pointer to the recovered error. // If the panic is not an error, it will be re-panicked. @@ -73,13 +76,12 @@ func (w *PWriter) Byte(b byte) { func (w *PWriter) Strings(s []string) { PWriteStrings(w.w, s) } - -func (w *PWriter) NBT(nbt NBT) { - PWriteNBT(w.w, nbt) +func (w *PWriter) CompoundBinaryTag(cbt CompoundBinaryTag, protocol proto.Protocol) { + PWriteCompoundBinaryTag(w.w, protocol, cbt) } -func PWriteNBT(w io.Writer, nbt NBT) { - if err := WriteNBT(w, nbt); err != nil { +func PWriteCompoundBinaryTag(w io.Writer, protocol proto.Protocol, cbt CompoundBinaryTag) { + if err := WriteBinaryTag(w, protocol, cbt); err != nil { panic(err) } } diff --git a/pkg/edition/java/title/title.go b/pkg/edition/java/title/title.go index 9e77040e..530e10db 100644 --- a/pkg/edition/java/title/title.go +++ b/pkg/edition/java/title/title.go @@ -2,6 +2,7 @@ package title import ( + "go.minekube.com/gate/pkg/edition/java/proto/packet/chat" "time" "go.minekube.com/common/minecraft/component" @@ -116,7 +117,7 @@ func ShowTitle(viewer Viewer, opts *Options) error { } subtitlePkt, err := title.New(protocol, &title.Builder{ Action: title.SetSubtitle, - Component: subtitle, + Component: *chat.FromComponentProtocol(subtitle, protocol), }) if err != nil { return err @@ -133,7 +134,7 @@ func ShowTitle(viewer Viewer, opts *Options) error { } titlePkt, err := title.New(protocol, &title.Builder{ Action: title.SetTitle, - Component: ti, + Component: *chat.FromComponentProtocol(ti, protocol), }) if err != nil { return err