yes
This commit is contained in:
+3
-1
@@ -13,6 +13,7 @@ import (
|
|||||||
"github.com/admin-else/strom/cmd/strom/data"
|
"github.com/admin-else/strom/cmd/strom/data"
|
||||||
"github.com/admin-else/strom/cmd/strom/extract_mca"
|
"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/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/offline_uuid"
|
||||||
"github.com/admin-else/strom/cmd/strom/packet_info"
|
"github.com/admin-else/strom/cmd/strom/packet_info"
|
||||||
"github.com/admin-else/strom/cmd/strom/packet_inspector"
|
"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,
|
"serve-world": serve_world.Run,
|
||||||
"mca-to-tmcpr": mca_to_tmcpr.Run,
|
"mca-to-tmcpr": mca_to_tmcpr.Run,
|
||||||
"serve-tmcpr": serve_tmcpr.Run,
|
"serve-tmcpr": serve_tmcpr.Run,
|
||||||
|
"messenger": messenger.Run,
|
||||||
}
|
}
|
||||||
|
|
||||||
func Help(args []string) (err error) {
|
func Help(args []string) (err error) {
|
||||||
@@ -75,7 +77,7 @@ func Help(args []string) (err error) {
|
|||||||
func mainE() (err error) {
|
func mainE() (err error) {
|
||||||
subcommands["help"] = Help
|
subcommands["help"] = Help
|
||||||
args := os.Args
|
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 {
|
if len(args) < 2 {
|
||||||
err = ExpectedASubcommandErr
|
err = ExpectedASubcommandErr
|
||||||
|
|||||||
@@ -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()
|
||||||
|
}
|
||||||
@@ -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 {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
c, err := client.Connect(*TargetAddr)
|
c, err := client.Connect(nil, *TargetAddr, "")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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.
|
// So ignore the resource leak warning. And maybe attach a warn ignore comment.
|
||||||
// It does not resolve SRV records.
|
// It does not resolve SRV records.
|
||||||
func StatusRaw(addr string) (s *StatusClient, err error) {
|
func StatusRaw(addr string) (s *StatusClient, err error) {
|
||||||
c, err := client.ConnectVersionLess(addr)
|
c, err := client.ConnectVersionLess(nil, addr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -82,7 +82,7 @@ func Run(args []string) (err error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
if *srvFlag {
|
if *srvFlag {
|
||||||
*addrFlag, err = client.DoDnsSimple(*addrFlag)
|
*addrFlag, _, _, err = client.DoDNSChecked(nil, *addrFlag, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
+26
-12
@@ -4,33 +4,47 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"net"
|
"net"
|
||||||
|
|
||||||
|
"github.com/admin-else/strom/mc/data"
|
||||||
"github.com/admin-else/strom/mc/proto"
|
"github.com/admin-else/strom/mc/proto"
|
||||||
"github.com/admin-else/strom/mc/proto_base"
|
"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
|
// 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
|
// You must close the connection when you're done with it
|
||||||
func Connect(addr string) (ret *proto.Conn, err error) {
|
// version must be a vaild minecraft version eg 1.21.11 if left empty it will be detected with a status-request.
|
||||||
s, err := Status(addr)
|
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 {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
ret, err = ConnectVersionLess(addr)
|
err = ret.SetProtocolVersion(protocolVersion)
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
err = ret.SetProtocolVersion(s.Version.Protocol)
|
|
||||||
return
|
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
|
// ConnectVersionLess connects to the given address without setting the version you should set it
|
||||||
// when using this function.
|
// when using this function.
|
||||||
// You must close the connection when you're done with it
|
// 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 = proto.NewConnCtx(ctx)
|
||||||
ret.Actor = proto_base.Client
|
ret.Actor = proto_base.Client
|
||||||
ret.Conn, err = (&net.Dialer{}).DialContext(ctx, "tcp", addr)
|
ret.Conn, err = (&net.Dialer{}).DialContext(ctx, "tcp", addr)
|
||||||
|
|||||||
+10
-56
@@ -11,21 +11,21 @@ import (
|
|||||||
|
|
||||||
type DNSCheckFunc func(addr string) error
|
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
|
// DoDNSChecked resolves a DNS name to an IP address
|
||||||
// It should resolve a minecraft compatible address into something net.Dial
|
// It should resolve a minecraft compatible address into something net.Dial
|
||||||
// It also optionally accepts a function to "check" the address
|
// 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
|
// https://mcsrc.dev/1/26.1-pre-1/net/minecraft/client/multiplayer/resolver/ServerAddress#L34
|
||||||
host, port, err := net.SplitHostPort(in)
|
host, port, err := net.SplitHostPort(in)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
var addrErr *net.AddrError
|
if addrErr, ok := errors.AsType[*net.AddrError](err); ok && addrErr.Err == "missing port in address" {
|
||||||
if errors.As(err, &addrErr) && addrErr.Err == "missing port in address" {
|
|
||||||
err = nil
|
err = nil
|
||||||
host = in
|
host = in
|
||||||
port = "25565"
|
port = "25565"
|
||||||
@@ -37,9 +37,6 @@ func DoDNSChecked(in string, f DNSCheckFunc) (connectTo string, host string, por
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if f == nil {
|
|
||||||
f = func(addr string) error { return nil }
|
|
||||||
}
|
|
||||||
|
|
||||||
err = f(host)
|
err = f(host)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -58,7 +55,7 @@ func DoDNSChecked(in string, f DNSCheckFunc) (connectTo string, host string, por
|
|||||||
if portN != 25565 {
|
if portN != 25565 {
|
||||||
return
|
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()
|
defer cancel()
|
||||||
|
|
||||||
r := &net.Resolver{}
|
r := &net.Resolver{}
|
||||||
@@ -81,46 +78,3 @@ func DoDNSChecked(in string, f DNSCheckFunc) (connectTo string, host string, por
|
|||||||
connectTo = outIP.String() + ":" + port
|
connectTo = outIP.String() + ":" + port
|
||||||
return
|
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
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ func TestDoDNS(t *testing.T) {
|
|||||||
}
|
}
|
||||||
for _, c := range cases {
|
for _, c := range cases {
|
||||||
t.Logf("Testing %s", c.Input)
|
t.Logf("Testing %s", c.Input)
|
||||||
_, host, portN, err := DoDns(c.Input)
|
_, host, portN, err := DoDNSChecked(nil, c.Input, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Error(err)
|
t.Error(err)
|
||||||
continue
|
continue
|
||||||
|
|||||||
+43
-11
@@ -1,6 +1,7 @@
|
|||||||
package client
|
package client
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"crypto/rand"
|
"crypto/rand"
|
||||||
"crypto/rsa"
|
"crypto/rsa"
|
||||||
"crypto/x509"
|
"crypto/x509"
|
||||||
@@ -155,24 +156,55 @@ func LoginRaw(c *proto.Conn, account *api.Account) (err error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func LoginNoSrv(connectTo string, account *api.Account) (c *proto.Conn, err error) {
|
func WithoutDns() func(ls *loginSettings) {
|
||||||
c, err = Connect(connectTo)
|
return func(ls *loginSettings) {
|
||||||
if err != nil {
|
ls.NoDns = true
|
||||||
return
|
|
||||||
}
|
}
|
||||||
err = LoginRaw(c, account)
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func Login(connectTo string, account *api.Account) (c *proto.Conn, err error) {
|
func WithContext(ctx context.Context) func(ls *loginSettings) {
|
||||||
connectTo, err = DoDnsSimple(connectTo)
|
return func(ls *loginSettings) {
|
||||||
if err != nil {
|
ls.Ctx = ctx
|
||||||
return
|
|
||||||
}
|
}
|
||||||
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 {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
err = LoginRaw(c, account)
|
err = LoginRaw(c, account)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if !ls.IgnoreConfig {
|
||||||
|
err = IgnoreConfig(c)
|
||||||
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
+3
-3
@@ -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.
|
// So ignore the resource leak warning. And maybe attach a warn ignore comment.
|
||||||
// It does not resolve SRV records.
|
// It does not resolve SRV records.
|
||||||
func StatusRaw(ctx context.Context, addr string) (s *StatusClient, err error) {
|
func StatusRaw(ctx context.Context, addr string) (s *StatusClient, err error) {
|
||||||
c, err := ConnectVersionLess(addr)
|
c, err := ConnectVersionLess(ctx, addr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
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.
|
// Status resolves the status of the minecraft server at the given address.
|
||||||
func Status(addr string) (status server.StatusResponse, err error) {
|
func Status(ctx context.Context, addr string) (status server.StatusResponse, err error) {
|
||||||
addr, err = DoDnsSimple(addr)
|
addr, _, _, err = DoDNSChecked(ctx, addr, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
+230
-17
@@ -3,6 +3,7 @@ package data
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"math"
|
"math"
|
||||||
|
"sync"
|
||||||
)
|
)
|
||||||
|
|
||||||
// MultiNoisePreset names the two built-in multi-noise parameter lists.
|
// 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.
|
// MultiNoiseData holds the parameter lists for each preset.
|
||||||
type MultiNoiseData struct {
|
type MultiNoiseData struct {
|
||||||
Presets map[string][]ParameterPoint
|
Presets map[string][]ParameterPoint
|
||||||
|
|
||||||
|
bvls map[string]*bvhNode
|
||||||
|
bvhOnce sync.Once
|
||||||
|
bvhErr error
|
||||||
}
|
}
|
||||||
|
|
||||||
// raw JSON shapes.
|
// 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)
|
return "", fmt.Errorf("multi-noise preset %s has no entries", preset)
|
||||||
}
|
}
|
||||||
|
|
||||||
bestBiome := ""
|
m.bvhOnce.Do(func() { m.bvhErr = m.buildBVHs() })
|
||||||
bestDist := int64(math.MaxInt64)
|
if m.bvhErr != nil {
|
||||||
|
return "", m.bvhErr
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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 {
|
func distanceToRange(value int64, r ParamRange) int64 {
|
||||||
@@ -127,3 +127,216 @@ func distanceToRange(value int64, r ParamRange) int64 {
|
|||||||
}
|
}
|
||||||
return 0
|
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
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package data
|
package data
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"math"
|
||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -32,3 +33,44 @@ func TestMultiNoiseLookup_NetherOrigin(t *testing.T) {
|
|||||||
t.Errorf("nether origin = %s, want minecraft:nether_wastes", biome)
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ package event
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
|
||||||
"log/slog"
|
"log/slog"
|
||||||
"reflect"
|
"reflect"
|
||||||
)
|
)
|
||||||
@@ -153,7 +152,6 @@ func (l *Loop) FireFound(event any) (found bool, err error) {
|
|||||||
errV := handler.Call([]reflect.Value{reflect.ValueOf(event)})[0]
|
errV := handler.Call([]reflect.Value{reflect.ValueOf(event)})[0]
|
||||||
if !errV.IsNil() {
|
if !errV.IsNil() {
|
||||||
err = errV.Interface().(error)
|
err = errV.Interface().(error)
|
||||||
err = errors.Join(fmt.Errorf("event handler failed at: %T", event), err)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -239,6 +237,7 @@ func NewLoop() (l *Loop) {
|
|||||||
return NewLoopCtx(context.Background())
|
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) {
|
func NewLoopCtx(ctx context.Context) (l *Loop) {
|
||||||
if ctx == nil {
|
if ctx == nil {
|
||||||
ctx = context.Background()
|
ctx = context.Background()
|
||||||
|
|||||||
@@ -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) {
|
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 {
|
if !found {
|
||||||
err = PacketDoesntExistInFutureVersionErr
|
err = PacketDoesntExistInFutureVersionErr
|
||||||
return
|
return
|
||||||
|
|||||||
+20
-12
@@ -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.
|
// 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.
|
// Warning: If a bad handler is passed in, the program will panic.
|
||||||
func (c *Conn) RegisterUntilLatest(h any) {
|
func (c *Conn) RegisterUntilLatest(hs ...any) {
|
||||||
eventType, hv := event.ValidateHandler(h)
|
for _, h := range hs {
|
||||||
c.RegisterUntilLow(eventType, hv, LatestVersion, false)
|
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.
|
// 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.
|
// Warning: If a bad handler is passed in, the program will panic.
|
||||||
func (c *Conn) RegisterUntil(h any, until string) {
|
func (c *Conn) RegisterUntil(until string, hs ...any) {
|
||||||
eventType, hv := event.ValidateHandler(h)
|
for _, h := range hs {
|
||||||
c.RegisterUntilLow(eventType, hv, until, false)
|
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.
|
// 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.
|
// If this handler returns an error, the connection will be closed.
|
||||||
// Warning: If a bad handler is passed in, the program will panic.
|
// Warning: If a bad handler is passed in, the program will panic.
|
||||||
func (c *Conn) RegisterCriticalUntilLatest(h any) {
|
func (c *Conn) RegisterCriticalUntilLatest(hs ...any) {
|
||||||
eventType, hv := event.ValidateHandler(h)
|
for _, h := range hs {
|
||||||
c.RegisterUntilLow(eventType, hv, LatestVersion, true)
|
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.
|
// 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.
|
// If this handler returns an error, the connection will be closed.
|
||||||
// Warning: If a bad handler is passed in, the program will panic.
|
// Warning: If a bad handler is passed in, the program will panic.
|
||||||
func (c *Conn) RegisterCriticalUntil(h any, until string) {
|
func (c *Conn) RegisterCriticalUntil(until string, hs ...any) {
|
||||||
eventType, hv := event.ValidateHandler(h)
|
for _, h := range hs {
|
||||||
c.RegisterUntilLow(eventType, hv, until, true)
|
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) {
|
func (c *Conn) RegisterUntilLow(eventType reflect.Type, hv reflect.Value, until string, critical bool) {
|
||||||
|
|||||||
Reference in New Issue
Block a user