From 5c5c391aa62a284a6fcae5bce99699434254b473 Mon Sep 17 00:00:00 2001 From: adman Date: Thu, 16 Jul 2026 01:13:31 +0200 Subject: [PATCH] yes --- cmd/strom/main.go | 4 +- cmd/strom/messenger/main.go | 57 +++++++ cmd/strom/packet_inspector/main.go | 4 +- cmd/strom/status/main.go | 4 +- mc/client/client.go | 38 +++-- mc/client/dns.go | 66 ++------ mc/client/dns_test.go | 2 +- mc/client/login.go | 54 +++++-- mc/client/status.go | 6 +- mc/data/multi_noise.go | 247 +++++++++++++++++++++++++++-- mc/data/multi_noise_test.go | 42 +++++ mc/event/event_bus.go | 3 +- mc/proto/connection.go | 2 +- mc/proto/smart_convert.go | 32 ++-- 14 files changed, 441 insertions(+), 120 deletions(-) create mode 100644 cmd/strom/messenger/main.go diff --git a/cmd/strom/main.go b/cmd/strom/main.go index fdac7bf..9cd77f1 100644 --- a/cmd/strom/main.go +++ b/cmd/strom/main.go @@ -13,6 +13,7 @@ import ( "github.com/admin-else/strom/cmd/strom/data" "github.com/admin-else/strom/cmd/strom/extract_mca" "github.com/admin-else/strom/cmd/strom/mca_to_tmcpr" + "github.com/admin-else/strom/cmd/strom/messenger" "github.com/admin-else/strom/cmd/strom/offline_uuid" "github.com/admin-else/strom/cmd/strom/packet_info" "github.com/admin-else/strom/cmd/strom/packet_inspector" @@ -63,6 +64,7 @@ var subcommands = map[string]func(args []string) error{ "serve-world": serve_world.Run, "mca-to-tmcpr": mca_to_tmcpr.Run, "serve-tmcpr": serve_tmcpr.Run, + "messenger": messenger.Run, } func Help(args []string) (err error) { @@ -75,7 +77,7 @@ func Help(args []string) (err error) { func mainE() (err error) { subcommands["help"] = Help args := os.Args - //args = []string{"TEST", "packet-spy", "-addr", "127.0.0.1:25571", "-true-forward"} + args = []string{"TEST", "messenger", "-addr", "localhost"} if len(args) < 2 { err = ExpectedASubcommandErr diff --git a/cmd/strom/messenger/main.go b/cmd/strom/messenger/main.go new file mode 100644 index 0000000..ab108b4 --- /dev/null +++ b/cmd/strom/messenger/main.go @@ -0,0 +1,57 @@ +package messenger + +import ( + "flag" + + "github.com/admin-else/strom/mc/api" + "github.com/admin-else/strom/mc/client" + "github.com/admin-else/strom/mc/event" + "github.com/admin-else/strom/mc/proto" + "github.com/admin-else/strom/mc/proto_generated/v1_21_11" +) + +var ( + cmd = flag.NewFlagSet("status", flag.ContinueOnError) + connectToFlag = cmd.String("addr", "", "server address to connect to") + nameFlag = cmd.String("name", "strom_messenger", "the name to use") +) + +type Messenger struct { + *proto.Conn +} + +func (m *Messenger) OnChat(e *v1_21_11.PlayToClientPacketPlayerChat) (err error) { + m.Log.Info("chat", "author", e.SenderUuid, "m", e.PlainMessage) + return +} + +func (m *Messenger) OnChatUnsigned(e *v1_21_11.PlayToClientPacketProfilelessChat) (err error) { + m.Log.Info("chatP", "author", e.Name, "m", e.Message) + return +} + +func (m *Messenger) OnChatSystem(e *v1_21_11.PlayToClientPacketSystemChat) (err error) { + m.Log.Info("chatS", "m", e.Content) + return +} + +func (m *Messenger) OnStdin(e event.Stdin) (err error) { + return m.Send(&v1_21_11.PlayToServerPacketChatMessage{Message: e.Val}) +} + +func Run(args []string) (err error) { + err = cmd.Parse(args) + + c, err := client.Login(*connectToFlag, api.NewOfflineAccount(*nameFlag)) + if err != nil { + return + } + defer c.Close() + + m := Messenger{Conn: c} + event.StartListingStdin(m.Loop) + m.Register(m.OnStdin) + m.RegisterUntilLatest(m.OnChat, m.OnChatUnsigned, m.OnChatSystem) + + return m.StartConn() +} diff --git a/cmd/strom/packet_inspector/main.go b/cmd/strom/packet_inspector/main.go index 391d5f9..c8e558f 100644 --- a/cmd/strom/packet_inspector/main.go +++ b/cmd/strom/packet_inspector/main.go @@ -178,11 +178,11 @@ func Run(args []string) (err error) { } } - *TargetAddr, err = client.DoDnsSimple(*TargetAddr) + *TargetAddr, _, _, err = client.DoDNSChecked(nil, *TargetAddr, nil) if err != nil { return } - c, err := client.Connect(*TargetAddr) + c, err := client.Connect(nil, *TargetAddr, "") if err != nil { return } diff --git a/cmd/strom/status/main.go b/cmd/strom/status/main.go index fafd1dc..f861f1a 100644 --- a/cmd/strom/status/main.go +++ b/cmd/strom/status/main.go @@ -46,7 +46,7 @@ func (s *StatusClient) OnPong(p *v1_21_8.StatusToClientPacketPing) (err error) { // So ignore the resource leak warning. And maybe attach a warn ignore comment. // It does not resolve SRV records. func StatusRaw(addr string) (s *StatusClient, err error) { - c, err := client.ConnectVersionLess(addr) + c, err := client.ConnectVersionLess(nil, addr) if err != nil { return } @@ -82,7 +82,7 @@ func Run(args []string) (err error) { return } if *srvFlag { - *addrFlag, err = client.DoDnsSimple(*addrFlag) + *addrFlag, _, _, err = client.DoDNSChecked(nil, *addrFlag, nil) if err != nil { return } diff --git a/mc/client/client.go b/mc/client/client.go index a2f4e3a..a141ff1 100644 --- a/mc/client/client.go +++ b/mc/client/client.go @@ -4,33 +4,47 @@ import ( "context" "net" + "github.com/admin-else/strom/mc/data" "github.com/admin-else/strom/mc/proto" "github.com/admin-else/strom/mc/proto_base" + "github.com/admin-else/strom/mc/server" ) // Connect connects to the given address note that it does not resolve the SRV records it's just a raw net.Dial // You must close the connection when you're done with it -func Connect(addr string) (ret *proto.Conn, err error) { - s, err := Status(addr) +// version must be a vaild minecraft version eg 1.21.11 if left empty it will be detected with a status-request. +func Connect(ctx context.Context, addr string, version string) (ret *proto.Conn, err error) { + var protocolVersion int32 + if version == "" { + var s server.StatusResponse + s, err = Status(ctx, addr) + if err != nil { + return + } + protocolVersion = s.Version.Protocol + } else { + var v data.VersionInfo + v, err = data.LookUpVersionByName(version) + if err != nil { + return + } + protocolVersion = v.Version + } + ret, err = ConnectVersionLess(ctx, addr) if err != nil { return } - ret, err = ConnectVersionLess(addr) - if err != nil { - return - } - err = ret.SetProtocolVersion(s.Version.Protocol) + err = ret.SetProtocolVersion(protocolVersion) return } -func ConnectVersionLess(addr string) (ret *proto.Conn, err error) { - return ConnectVersionLessCtx(context.Background(), addr) -} - // ConnectVersionLess connects to the given address without setting the version you should set it // when using this function. // You must close the connection when you're done with it -func ConnectVersionLessCtx(ctx context.Context, addr string) (ret *proto.Conn, err error) { +func ConnectVersionLess(ctx context.Context, addr string) (ret *proto.Conn, err error) { + if ctx == nil { + ctx = context.Background() + } ret = proto.NewConnCtx(ctx) ret.Actor = proto_base.Client ret.Conn, err = (&net.Dialer{}).DialContext(ctx, "tcp", addr) diff --git a/mc/client/dns.go b/mc/client/dns.go index 214e6d3..5540cf0 100644 --- a/mc/client/dns.go +++ b/mc/client/dns.go @@ -11,21 +11,21 @@ import ( type DNSCheckFunc func(addr string) error -// DoDns resolves a DNS name to an IP address -// It should resolve a minecraft compatible address into something net.Dial -func DoDns(in string) (out string, host string, portN uint16, err error) { - return DoDNSChecked(in, nil) -} - // DoDNSChecked resolves a DNS name to an IP address // It should resolve a minecraft compatible address into something net.Dial // It also optionally accepts a function to "check" the address -func DoDNSChecked(in string, f DNSCheckFunc) (connectTo string, host string, portN uint16, err error) { +func DoDNSChecked(ctx context.Context, in string, f DNSCheckFunc) (connectTo string, host string, portN uint16, err error) { + if f == nil { + f = func(addr string) error { return nil } + } + if ctx == nil { + ctx = context.Background() + } + // https://mcsrc.dev/1/26.1-pre-1/net/minecraft/client/multiplayer/resolver/ServerAddress#L34 host, port, err := net.SplitHostPort(in) if err != nil { - var addrErr *net.AddrError - if errors.As(err, &addrErr) && addrErr.Err == "missing port in address" { + if addrErr, ok := errors.AsType[*net.AddrError](err); ok && addrErr.Err == "missing port in address" { err = nil host = in port = "25565" @@ -37,9 +37,6 @@ func DoDNSChecked(in string, f DNSCheckFunc) (connectTo string, host string, por if err != nil { return } - if f == nil { - f = func(addr string) error { return nil } - } err = f(host) if err != nil { @@ -58,7 +55,7 @@ func DoDNSChecked(in string, f DNSCheckFunc) (connectTo string, host string, por if portN != 25565 { return } - ctx, cancel := context.WithTimeout(context.Background(), time.Second) // otherwise this takes 10+ seconds + ctx, cancel := context.WithTimeout(ctx, time.Second) // otherwise this takes 10+ seconds defer cancel() r := &net.Resolver{} @@ -81,46 +78,3 @@ func DoDNSChecked(in string, f DNSCheckFunc) (connectTo string, host string, por connectTo = outIP.String() + ":" + port return } - -// DoDnsSimple resolves a DNS name to an IP address -// It should resolve a minecraft compatible address into something net.Dial -// It does not check the address -func DoDnsSimple(in string) (out string, err error) { - host, port, err := net.SplitHostPort(in) - if err != nil { - var addrErr *net.AddrError - if errors.As(err, &addrErr) && addrErr.Err == "missing port in address" { - err = nil - host = in - port = "25565" - } else { - return - } - } - porti, err := strconv.Atoi(port) - if err != nil { - return - } - portN := uint16(porti) - _, err = net.ResolveIPAddr("ip", host) - if err != nil { - return - } - out = host + ":" + strconv.Itoa(int(portN)) - if portN != 25565 { - return - } - ctx, cancel := context.WithTimeout(context.Background(), time.Second) // otherwise this takes 10+ seconds - defer cancel() - - r := &net.Resolver{} - - _, srvs, _ := r.LookupSRV(ctx, "minecraft", "tcp", host) - if len(srvs) == 0 { - return - } - host = strings.TrimSuffix(srvs[0].Target, ".") - portN = srvs[0].Port - out = host + ":" + strconv.Itoa(int(portN)) - return -} diff --git a/mc/client/dns_test.go b/mc/client/dns_test.go index bb5e87e..ce20cff 100644 --- a/mc/client/dns_test.go +++ b/mc/client/dns_test.go @@ -15,7 +15,7 @@ func TestDoDNS(t *testing.T) { } for _, c := range cases { t.Logf("Testing %s", c.Input) - _, host, portN, err := DoDns(c.Input) + _, host, portN, err := DoDNSChecked(nil, c.Input, nil) if err != nil { t.Error(err) continue diff --git a/mc/client/login.go b/mc/client/login.go index d64d674..5cf2c2d 100644 --- a/mc/client/login.go +++ b/mc/client/login.go @@ -1,6 +1,7 @@ package client import ( + "context" "crypto/rand" "crypto/rsa" "crypto/x509" @@ -155,24 +156,55 @@ func LoginRaw(c *proto.Conn, account *api.Account) (err error) { return } -func LoginNoSrv(connectTo string, account *api.Account) (c *proto.Conn, err error) { - c, err = Connect(connectTo) - if err != nil { - return +func WithoutDns() func(ls *loginSettings) { + return func(ls *loginSettings) { + ls.NoDns = true } - err = LoginRaw(c, account) - return } -func Login(connectTo string, account *api.Account) (c *proto.Conn, err error) { - connectTo, err = DoDnsSimple(connectTo) - if err != nil { - return +func WithContext(ctx context.Context) func(ls *loginSettings) { + return func(ls *loginSettings) { + ls.Ctx = ctx } - c, err = Connect(connectTo) +} + +func WithVersion(version string) func(ls *loginSettings) { + return func(ls *loginSettings) { + ls.Version = version + } +} + +type loginSettings struct { + NoDns bool + Ctx context.Context + Version string + IgnoreConfig bool +} + +func Login(connectTo string, account *api.Account, settings ...func(ls *loginSettings)) (c *proto.Conn, err error) { + ls := &loginSettings{} + + for _, setting := range settings { + setting(ls) + } + + if !ls.NoDns { + connectTo, _, _, err = DoDNSChecked(ls.Ctx, connectTo, nil) + if err != nil { + return + } + } + + c, err = Connect(ls.Ctx, connectTo, ls.Version) if err != nil { return } err = LoginRaw(c, account) + if err != nil { + return + } + if !ls.IgnoreConfig { + err = IgnoreConfig(c) + } return } diff --git a/mc/client/status.go b/mc/client/status.go index 686cb31..d4dbe6d 100644 --- a/mc/client/status.go +++ b/mc/client/status.go @@ -40,7 +40,7 @@ func (s *StatusClient) OnPong(p *v1_21_8.StatusToClientPacketPing) (err error) { // So ignore the resource leak warning. And maybe attach a warn ignore comment. // It does not resolve SRV records. func StatusRaw(ctx context.Context, addr string) (s *StatusClient, err error) { - c, err := ConnectVersionLess(addr) + c, err := ConnectVersionLess(ctx, addr) if err != nil { return } @@ -81,8 +81,8 @@ func StatusNoDns(addr string) (status server.StatusResponse, err error) { } // Status resolves the status of the minecraft server at the given address. -func Status(addr string) (status server.StatusResponse, err error) { - addr, err = DoDnsSimple(addr) +func Status(ctx context.Context, addr string) (status server.StatusResponse, err error) { + addr, _, _, err = DoDNSChecked(ctx, addr, nil) if err != nil { return } diff --git a/mc/data/multi_noise.go b/mc/data/multi_noise.go index f5406e2..be500d0 100644 --- a/mc/data/multi_noise.go +++ b/mc/data/multi_noise.go @@ -3,6 +3,7 @@ package data import ( "fmt" "math" + "sync" ) // MultiNoisePreset names the two built-in multi-noise parameter lists. @@ -32,6 +33,10 @@ type ParameterPoint struct { // MultiNoiseData holds the parameter lists for each preset. type MultiNoiseData struct { Presets map[string][]ParameterPoint + + bvls map[string]*bvhNode + bvhOnce sync.Once + bvhErr error } // raw JSON shapes. @@ -95,25 +100,20 @@ func (m *MultiNoiseData) Lookup(preset string, t, h, c, e, d, w int64) (string, return "", fmt.Errorf("multi-noise preset %s has no entries", preset) } - bestBiome := "" - bestDist := int64(math.MaxInt64) - - for _, p := range points { - dist := distanceToRange(t, p.Temperature) + - distanceToRange(h, p.Humidity) + - distanceToRange(c, p.Continentalness) + - distanceToRange(e, p.Erosion) + - distanceToRange(d, p.Depth) + - distanceToRange(w, p.Weirdness) + - p.Offset - - if dist < bestDist { - bestDist = dist - bestBiome = p.Biome - } + m.bvhOnce.Do(func() { m.bvhErr = m.buildBVHs() }) + if m.bvhErr != nil { + return "", m.bvhErr } - return bestBiome, nil + root := m.bvls[preset] + if root == nil { + return "", fmt.Errorf("no BVH for preset: %s", preset) + } + + q := point6{t, h, c, e, d, w} + best := result{dist: int64(math.MaxInt64)} + root.lookup(q, points, &best) + return best.biome, nil } func distanceToRange(value int64, r ParamRange) int64 { @@ -127,3 +127,216 @@ func distanceToRange(value int64, r ParamRange) int64 { } return 0 } + +// point6 is a quantized climate sample in six dimensions. +type point6 struct{ t, h, c, e, d, w int64 } + +func (q point6) dist(p ParameterPoint) int64 { + return distanceToRange(q.t, p.Temperature) + + distanceToRange(q.h, p.Humidity) + + distanceToRange(q.c, p.Continentalness) + + distanceToRange(q.e, p.Erosion) + + distanceToRange(q.d, p.Depth) + + distanceToRange(q.w, p.Weirdness) + + p.Offset +} + +type result struct { + dist int64 + biome string +} + +// bvhNode is a bounding-volume-hierarchy node for fast nearest-box search. +type bvhNode struct { + bounds ParameterPoint + left *bvhNode + right *bvhNode + leaf []int // indices into original slice; non-nil only for leaves +} + +func (m *MultiNoiseData) buildBVHs() error { + m.bvls = make(map[string]*bvhNode, len(m.Presets)) + for preset, points := range m.Presets { + indices := make([]int, len(points)) + for i := range indices { + indices[i] = i + } + m.bvls[preset] = buildBVH(points, indices, 0) + } + return nil +} + +func buildBVH(points []ParameterPoint, indices []int, depth int) *bvhNode { + node := &bvhNode{} + node.bounds = boundsOf(points, indices) + + // Small leaves avoid BVH overhead for tiny lists. + if len(indices) <= 16 { + node.leaf = make([]int, len(indices)) + copy(node.leaf, indices) + return node + } + + // Pick axis by largest variance. + axis := depth % 6 + if axis < 0 { + axis = 0 + } + + // Partition by median of the chosen axis' center. + mid := len(indices) / 2 + quickSelectByAxis(points, indices, axis, mid) + + leftIdx := indices[:mid] + rightIdx := indices[mid:] + + // Degenerate split: fall back to leaf to avoid infinite recursion. + if len(leftIdx) == 0 || len(rightIdx) == 0 { + node.leaf = make([]int, len(indices)) + copy(node.leaf, indices) + return node + } + + node.left = buildBVH(points, leftIdx, depth+1) + node.right = buildBVH(points, rightIdx, depth+1) + return node +} + +func boundsOf(points []ParameterPoint, indices []int) ParameterPoint { + if len(indices) == 0 { + return ParameterPoint{} + } + b := points[indices[0]] + for _, i := range indices[1:] { + p := points[i] + if p.Temperature.Min < b.Temperature.Min { + b.Temperature.Min = p.Temperature.Min + } + if p.Temperature.Max > b.Temperature.Max { + b.Temperature.Max = p.Temperature.Max + } + if p.Humidity.Min < b.Humidity.Min { + b.Humidity.Min = p.Humidity.Min + } + if p.Humidity.Max > b.Humidity.Max { + b.Humidity.Max = p.Humidity.Max + } + if p.Continentalness.Min < b.Continentalness.Min { + b.Continentalness.Min = p.Continentalness.Min + } + if p.Continentalness.Max > b.Continentalness.Max { + b.Continentalness.Max = p.Continentalness.Max + } + if p.Erosion.Min < b.Erosion.Min { + b.Erosion.Min = p.Erosion.Min + } + if p.Erosion.Max > b.Erosion.Max { + b.Erosion.Max = p.Erosion.Max + } + if p.Depth.Min < b.Depth.Min { + b.Depth.Min = p.Depth.Min + } + if p.Depth.Max > b.Depth.Max { + b.Depth.Max = p.Depth.Max + } + if p.Weirdness.Min < b.Weirdness.Min { + b.Weirdness.Min = p.Weirdness.Min + } + if p.Weirdness.Max > b.Weirdness.Max { + b.Weirdness.Max = p.Weirdness.Max + } + if p.Offset < b.Offset { + b.Offset = p.Offset + } + } + return b +} + +func (n *bvhNode) lowerBound(q point6) int64 { + return distanceToRange(q.t, n.bounds.Temperature) + + distanceToRange(q.h, n.bounds.Humidity) + + distanceToRange(q.c, n.bounds.Continentalness) + + distanceToRange(q.e, n.bounds.Erosion) + + distanceToRange(q.d, n.bounds.Depth) + + distanceToRange(q.w, n.bounds.Weirdness) + + n.bounds.Offset +} + +func (n *bvhNode) lookup(q point6, points []ParameterPoint, best *result) { + if n.lowerBound(q) >= best.dist { + return + } + + if n.leaf != nil { + for _, idx := range n.leaf { + p := points[idx] + d := q.dist(p) + if d < best.dist { + best.dist = d + best.biome = p.Biome + } + } + return + } + + // Visit the child with the smaller lower bound first for better pruning. + lbL := n.left.lowerBound(q) + lbR := n.right.lowerBound(q) + if lbL < lbR { + n.left.lookup(q, points, best) + n.right.lookup(q, points, best) + } else { + n.right.lookup(q, points, best) + n.left.lookup(q, points, best) + } +} + +func quickSelectByAxis(points []ParameterPoint, indices []int, axis, k int) { + if len(indices) <= 1 { + return + } + for { + pivot := points[indices[len(indices)/2]] + pivotVal := axisCenter(pivot, axis) + left, right := 0, len(indices)-1 + for left <= right { + for axisCenter(points[indices[left]], axis) < pivotVal { + left++ + } + for axisCenter(points[indices[right]], axis) > pivotVal { + right-- + } + if left <= right { + indices[left], indices[right] = indices[right], indices[left] + left++ + right-- + } + } + if k <= right { + indices = indices[:right+1] + } else if k >= left { + indices = indices[left:] + k -= left + } else { + return + } + } +} + +func axisCenter(p ParameterPoint, axis int) int64 { + switch axis { + case 0: + return (p.Temperature.Min + p.Temperature.Max) / 2 + case 1: + return (p.Humidity.Min + p.Humidity.Max) / 2 + case 2: + return (p.Continentalness.Min + p.Continentalness.Max) / 2 + case 3: + return (p.Erosion.Min + p.Erosion.Max) / 2 + case 4: + return (p.Depth.Min + p.Depth.Max) / 2 + case 5: + return (p.Weirdness.Min + p.Weirdness.Max) / 2 + } + return 0 +} diff --git a/mc/data/multi_noise_test.go b/mc/data/multi_noise_test.go index 5264e28..8083cdb 100644 --- a/mc/data/multi_noise_test.go +++ b/mc/data/multi_noise_test.go @@ -1,6 +1,7 @@ package data import ( + "math" "testing" ) @@ -32,3 +33,44 @@ func TestMultiNoiseLookup_NetherOrigin(t *testing.T) { t.Errorf("nether origin = %s, want minecraft:nether_wastes", biome) } } + +func TestMultiNoiseLookup_BVHMatchesBruteForce(t *testing.T) { + data, err := LoadMultiNoise("1.21.11") + if err != nil { + t.Fatalf("LoadMultiNoise: %v", err) + } + + brute := func(preset string, q point6) (string, int64) { + best := result{dist: int64(math.MaxInt64)} + for _, p := range data.Presets[preset] { + d := q.dist(p) + if d < best.dist { + best.dist = d + best.biome = p.Biome + } + } + return best.biome, best.dist + } + + presets := []string{MultiNoiseOverworld, MultiNoiseNether} + for _, preset := range presets { + for i := 0; i < 1000; i++ { + q := point6{ + t: int64(i%20000 - 10000), + h: int64((i*7)%20000 - 10000), + c: int64((i*13)%20000 - 10000), + e: int64((i*17)%20000 - 10000), + d: int64((i*3)%20000 - 10000), + w: int64((i*11)%20000 - 10000), + } + wantBiome, wantDist := brute(preset, q) + gotBiome, err := data.Lookup(preset, q.t, q.h, q.c, q.e, q.d, q.w) + if err != nil { + t.Fatalf("Lookup(%s): %v", preset, err) + } + if gotBiome != wantBiome { + t.Errorf("preset=%s query=%+v got=%s want=%s dist=%d", preset, q, gotBiome, wantBiome, wantDist) + } + } + } +} diff --git a/mc/event/event_bus.go b/mc/event/event_bus.go index 7960e13..6c3ef33 100644 --- a/mc/event/event_bus.go +++ b/mc/event/event_bus.go @@ -4,7 +4,6 @@ package event import ( "context" "errors" - "fmt" "log/slog" "reflect" ) @@ -153,7 +152,6 @@ func (l *Loop) FireFound(event any) (found bool, err error) { errV := handler.Call([]reflect.Value{reflect.ValueOf(event)})[0] if !errV.IsNil() { err = errV.Interface().(error) - err = errors.Join(fmt.Errorf("event handler failed at: %T", event), err) return } } @@ -239,6 +237,7 @@ func NewLoop() (l *Loop) { return NewLoopCtx(context.Background()) } +// NewLoopCtx creates a new Loop instance with a cancellable context, using the provided context or context.Background if nil. func NewLoopCtx(ctx context.Context) (l *Loop) { if ctx == nil { ctx = context.Background() diff --git a/mc/proto/connection.go b/mc/proto/connection.go index aaee5a0..0b33486 100644 --- a/mc/proto/connection.go +++ b/mc/proto/connection.go @@ -164,7 +164,7 @@ func (c *Conn) ActivateReceiveRoutine() (chSending <-chan any) { } func (c *Conn) translatePacketVersion(packet proto_base.EncodeDecodeAble, packetInfo proto_base.PacketInfo) (convertedPacket proto_base.EncodeDecodeAble, packetInfoReal proto_base.PacketInfo, err error) { - packetInfoReal, found := LookupPacketInfoByNameProtocolVersionAndState(packetInfo.Name, c.ProtocolVersion, c.State()) + packetInfoReal, found := LookupPacketInfoByNameProtocolVersionStateAndDirection(packetInfo.Name, c.ProtocolVersion, c.State(), packetInfo.Direction) if !found { err = PacketDoesntExistInFutureVersionErr return diff --git a/mc/proto/smart_convert.go b/mc/proto/smart_convert.go index 989b451..5dfc673 100644 --- a/mc/proto/smart_convert.go +++ b/mc/proto/smart_convert.go @@ -63,32 +63,40 @@ func SmartConvert(from reflect.Value, to reflect.Type) (ret reflect.Value) { // RegisterUntilLatest registers a handler for all versions between the passed in packet version and the latest supported version. // Warning: If a bad handler is passed in, the program will panic. -func (c *Conn) RegisterUntilLatest(h any) { - eventType, hv := event.ValidateHandler(h) - c.RegisterUntilLow(eventType, hv, LatestVersion, false) +func (c *Conn) RegisterUntilLatest(hs ...any) { + for _, h := range hs { + eventType, hv := event.ValidateHandler(h) + c.RegisterUntilLow(eventType, hv, LatestVersion, false) + } } // RegisterUntil registers a handler for all versions between the passed in packet version and the specified version. // Warning: If a bad handler is passed in, the program will panic. -func (c *Conn) RegisterUntil(h any, until string) { - eventType, hv := event.ValidateHandler(h) - c.RegisterUntilLow(eventType, hv, until, false) +func (c *Conn) RegisterUntil(until string, hs ...any) { + for _, h := range hs { + eventType, hv := event.ValidateHandler(h) + c.RegisterUntilLow(eventType, hv, until, false) + } } // RegisterCriticalUntilLatest registers a handler for all versions between the passed in packet version and the latest supported version. // If this handler returns an error, the connection will be closed. // Warning: If a bad handler is passed in, the program will panic. -func (c *Conn) RegisterCriticalUntilLatest(h any) { - eventType, hv := event.ValidateHandler(h) - c.RegisterUntilLow(eventType, hv, LatestVersion, true) +func (c *Conn) RegisterCriticalUntilLatest(hs ...any) { + for _, h := range hs { + eventType, hv := event.ValidateHandler(h) + c.RegisterUntilLow(eventType, hv, LatestVersion, true) + } } // RegisterCriticalUntil registers a handler for all versions between the passed in packet version and the specified version. // If this handler returns an error, the connection will be closed. // Warning: If a bad handler is passed in, the program will panic. -func (c *Conn) RegisterCriticalUntil(h any, until string) { - eventType, hv := event.ValidateHandler(h) - c.RegisterUntilLow(eventType, hv, until, true) +func (c *Conn) RegisterCriticalUntil(until string, hs ...any) { + for _, h := range hs { + eventType, hv := event.ValidateHandler(h) + c.RegisterUntilLow(eventType, hv, until, true) + } } func (c *Conn) RegisterUntilLow(eventType reflect.Type, hv reflect.Value, until string, critical bool) {