diff --git a/.gitignore b/.gitignore index 608f7ca0..1c50a9a4 100755 --- a/.gitignore +++ b/.gitignore @@ -13,7 +13,5 @@ players/* .DS_Store .fleet/ .vscode.code-workspace -example/spectrum/spectrum.exe -example/spectrum/logs/* example/default/logs/* -example/default/proxy.exe \ No newline at end of file +example/default/proxy.exe diff --git a/README.md b/README.md index 93347ae5..75a0d740 100755 --- a/README.md +++ b/README.md @@ -8,8 +8,6 @@ Oomph implements a server authoritative system for movement and combat, allowing ## Dependencies - [Dragonfly](https://github.com/oomph-ac/dragonfly) -- [Spectrum](https://github.com/oomph-ac/spectrum) -- (For your PM server) [Spectrum-PM](https://github.com/oomph-ac/spectrum-pm) - [OConfig](https://github.com/oomph-ac/oconfig) The multi-version will not be going public at the moment, you may remove the dependency from `go.mod`. @@ -65,4 +63,3 @@ Logic for combat, setting the client & server tick, and lag compensating entitie * [JustTalDevelops](https://github.com/JustTalDevelops) - Created the base of Oomph, making it able to intercept packets, and avoiding pesky import cycle. * [هاشم](https://github.com/hashimthearab) - Created the base of Oomph * [cjmustard](https://www.github.com/cjmustard) - Moral support - diff --git a/example/default/default.go b/example/default/default.go index b0c36da2..17bfc706 100644 --- a/example/default/default.go +++ b/example/default/default.go @@ -127,7 +127,6 @@ func handleConn(conn *minecraft.Conn, listener *minecraft.Listener) { completion := make(chan struct{}, 1) go func() { defer listener.Disconnect(conn, "connection lost") - defer serverConn.Close() defer func() { completion <- struct{}{} }() @@ -149,7 +148,15 @@ func handleConn(conn *minecraft.Conn, listener *minecraft.Listener) { continue } - if err := serverConn.WritePacket(pk); err != nil { + currentServerConn := p.ServerConn() + if currentServerConn == nil { + return + } + + if err := currentServerConn.WritePacket(pk); err != nil { + if latestServerConn := p.ServerConn(); latestServerConn != nil && latestServerConn != currentServerConn { + continue + } var disc minecraft.DisconnectError if ok := errors.As(err, &disc); ok { fmt.Println(err, "Client -> Server") @@ -160,15 +167,26 @@ func handleConn(conn *minecraft.Conn, listener *minecraft.Listener) { } }() go func() { - defer serverConn.Close() defer listener.Disconnect(conn, "connection lost") defer func() { completion <- struct{}{} }() for { - pk, err := serverConn.ReadPacket() + currentServerConn := p.ServerConn() + if currentServerConn == nil { + return + } + currentMinecraftServerConn, ok := currentServerConn.(*minecraft.Conn) + if !ok { + return + } + + pk, err := currentMinecraftServerConn.ReadPacket() if err != nil { + if latestServerConn := p.ServerConn(); latestServerConn != nil && latestServerConn != currentServerConn { + continue + } var disc minecraft.DisconnectError if ok := errors.As(err, &disc); ok { fmt.Println(err, "Server -> Client") diff --git a/example/spectrum/spectrum.go b/example/spectrum/spectrum.go deleted file mode 100644 index 2db6f4cd..00000000 --- a/example/spectrum/spectrum.go +++ /dev/null @@ -1,188 +0,0 @@ -package main - -import ( - "context" - "errors" - "fmt" - "log/slog" - "os" - "os/signal" - "runtime/debug" - "time" - - "github.com/akmalfairuz/legacy-version/legacyver" - "github.com/cooldogedev/spectrum" - "github.com/cooldogedev/spectrum/server" - "github.com/cooldogedev/spectrum/session" - "github.com/cooldogedev/spectrum/util" - "github.com/go-echarts/statsview" - "github.com/go-echarts/statsview/viewer" - "github.com/oomph-ac/oconfig" - "github.com/oomph-ac/oomph" - "github.com/oomph-ac/oomph/player" - "github.com/oomph-ac/oomph/world" - "github.com/sandertv/gophertunnel/minecraft" - "github.com/sandertv/gophertunnel/minecraft/protocol/packet" - - _ "net/http/pprof" - - _ "github.com/oomph-ac/oomph/utils/collisions" - - "github.com/oomph-ac/oomph/utils" -) - -var evHandler = player.NewExampleEventHandler() - -func main() { - logger := slog.Default() - if len(os.Args) < 3 { - logger.Info("Usage: ./oomph-bin ") - return - } - - if os.Getenv("PPROF_ENABLED") != "" { - // set configurations before calling `statsview.New()` method - viewer.SetConfiguration(viewer.WithTheme(viewer.ThemeWesteros), viewer.WithAddr("192.168.1.172:8080")) - - mgr := statsview.New() - go mgr.Start() - //go http.ListenAndServe("localhost:8080", nil) - } - - debug.SetGCPercent(-1) - debug.SetMemoryLimit(4 * 1024 * 1024 * 1024) // 4GB - fmt.Println("process ID:", os.Getpid()) - - opts := util.DefaultOpts() - opts.ClientDecode = player.ClientDecode - opts.AutoLogin = false - opts.Addr = ":" + os.Args[1] - opts.SyncProtocol = false - opts.LatencyInterval = 1000 - - /* if len(os.Args) >= 4 { - opts.Token = os.Args[3] - } */ - statusProvider, err := minecraft.NewForeignStatusProvider(os.Args[2]) - if err != nil { - panic(err) - } - - oconfig.Global = oconfig.DefaultConfig - //oconfig.Global.Network.Transport = oconfig.NetworkTransportSpectral - - oconfig.Global.Movement.AcceptClientPosition = false - oconfig.Global.Movement.PositionAcceptanceThreshold = 0.003 - oconfig.Global.Movement.AcceptClientVelocity = false - oconfig.Global.Movement.VelocityAcceptanceThreshold = 0.077 - - oconfig.Global.Movement.PersuasionThreshold = 0.001 - oconfig.Global.Movement.CorrectionThreshold = 0.003 - - oconfig.Global.Combat.MaximumAttackAngle = 90 - oconfig.Global.Combat.EnableClientEntityTracking = true - - oconfig.Global.Network.GlobalMovementCutoffThreshold = -1 - oconfig.Global.Network.MaxEntityRewind = 6 - oconfig.Global.Network.MaxGhostBlockChain = 7 - oconfig.Global.Network.MaxKnockbackDelay = -1 - oconfig.Global.Network.MaxBlockUpdateDelay = -1 - - /* packs, err := utils.ResourcePacks("/home/ethaniccc/temp/proxy-packs", "content_keys.json") - if err != nil { - panic(err) - } */ - - /* var netTransport transport.Transport - switch tr := oconfig.Network().Transport; tr { - case oconfig.NetworkTransportTCP: - netTransport = otransport.NewTCP() - default: - if tr != oconfig.NetworkTransportSpectral { - logger.Warn("unknown/unsupported transport, defaulting to spectral", "transportMode", tr) - } - netTransport = transport.NewSpectral(logger) - } */ - - // Register custom blocks here - world.FinalizeBlockRegistry() - - proxy := spectrum.NewSpectrum( - server.NewStaticDiscovery(os.Args[2], os.Args[2]), - logger, - opts, - nil, - ) - protos := legacyver.All(false) - if err := proxy.Listen(minecraft.ListenConfig{ - StatusProvider: statusProvider, - FlushRate: -1, // FlushRate is set to -1 to allow Oomph to manually flush the connection. - AcceptedProtocols: protos, - //ResourcePacks: packs, - TexturePacksRequired: false, - AllowInvalidPackets: false, - AllowUnknownPackets: false, - }); err != nil { - panic(err) - } - - go func() { - var interrupt = make(chan os.Signal, 1) - signal.Notify(interrupt, os.Interrupt) - <-interrupt - for _, s := range proxy.Registry().GetSessions() { - s.Server().WritePacket(&packet.Disconnect{}) - s.Disconnect("Proxy restarting...") - } - time.Sleep(time.Second) - os.Exit(0) - }() - - utils.InitializeBlockNameMapping() - - for { - initalSession, err := proxy.Accept() - if err != nil { - continue - } - - go func(s *session.Session) { - // Disable auto-login so that Oomph's processor can modify the StartGame data to allow server-authoritative movement. - f, err := os.OpenFile(fmt.Sprintf("./logs/%s.log", s.Client().IdentityData().DisplayName), os.O_RDWR|os.O_CREATE|os.O_APPEND, 0744) - if err != nil { - s.Disconnect("failed to create log file") - return - } - playerLogHandler := slog.NewTextHandler(f, &slog.HandlerOptions{ - Level: slog.LevelDebug, - }) - playerLog := slog.New(playerLogHandler) - proc := oomph.NewProcessor(s, proxy.Registry(), proxy.Listener(), playerLog) - proc.Player().SetCloser(func() { - f.Close() - }) - proc.Player().SetRecoverFunc(func(p *player.Player, err any) { - fmt.Println("ERROR:", err) - debug.PrintStack() - fmt.Println("Please remember this is an example, and you should set this recovery function to something that can log errors, like Sentry.") - os.Exit(1) - }) - proc.Player().AddPerm(player.PermissionDebug) - proc.Player().AddPerm(player.PermissionAlerts) - proc.Player().AddPerm(player.PermissionLogs) - proc.Player().HandleEvents(evHandler) - s.SetProcessor(proc) - - if err := s.Login(); err != nil { - s.Disconnect(err.Error()) - f.Close() - if !errors.Is(err, context.Canceled) { - logger.Error("failed to login session", "err", err) - } - return - } - - proc.Player().SetServerConn(s.Server()) - }(initalSession) - } -} diff --git a/go.mod b/go.mod index b231858c..2fcd0480 100755 --- a/go.mod +++ b/go.mod @@ -3,17 +3,15 @@ module github.com/oomph-ac/oomph go 1.25.1 require ( - github.com/akmalfairuz/legacy-version v1.5.4 github.com/chewxy/math32 v1.10.1 - github.com/cooldogedev/spectrum v0.0.40-0.20250527034552-55ddfe1bba67 github.com/df-mc/dragonfly v0.10.9 github.com/ethaniccc/float32-cube v0.0.0-20250511224129-7af1f8c4ee12 github.com/getsentry/sentry-go v0.40.0 github.com/go-echarts/statsview v0.4.2 github.com/go-gl/mathgl v1.2.0 github.com/oomph-ac/oconfig v0.0.0-20250315200330-e36f34d634e5 - github.com/sandertv/go-raknet v1.15.0 - github.com/sandertv/gophertunnel v1.52.3-0.20260103191303-85f94a5d3b01 + github.com/sandertv/go-raknet v1.15.1-0.20260112202637-beca0b10c217 + github.com/sandertv/gophertunnel v1.54.0 github.com/zeebo/xxh3 v1.0.2 golang.org/x/exp v0.0.0-20251209150349-8475f28825e9 ) @@ -21,33 +19,28 @@ require ( require ( github.com/brentp/intintmap v0.0.0-20190211203843-30dc0ade9af9 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect - github.com/cooldogedev/spectral v0.0.5 // indirect + github.com/coreos/go-oidc/v3 v3.17.0 // indirect + github.com/df-mc/go-playfab v1.0.0 // indirect + github.com/df-mc/go-xsapi v1.0.1 // indirect github.com/df-mc/goleveldb v1.1.9 // indirect github.com/df-mc/jsonc v1.0.5 // indirect github.com/df-mc/worldupgrader v1.0.20 // indirect - github.com/francoispqt/gojay v1.2.13 // indirect github.com/go-echarts/go-echarts/v2 v2.5.1 // indirect github.com/go-jose/go-jose/v4 v4.1.3 // indirect github.com/golang/snappy v1.0.0 // indirect + github.com/google/go-cmp v0.7.0 // indirect github.com/google/uuid v1.6.0 // indirect - github.com/hashicorp/go-version v1.8.0 // indirect github.com/hjson/hjson-go/v4 v4.4.0 // indirect - github.com/klauspost/compress v1.18.2 // indirect + github.com/klauspost/compress v1.18.4 // indirect github.com/klauspost/cpuid/v2 v2.0.9 // indirect github.com/onsi/gomega v1.36.3 // indirect - github.com/quic-go/quic-go v0.55.0 // indirect github.com/rs/cors v1.11.0 // indirect - github.com/samber/lo v1.49.1 // indirect - github.com/scylladb/go-set v1.0.2 // indirect github.com/segmentio/fasthash v1.0.3 // indirect - golang.org/x/crypto v0.46.0 // indirect - golang.org/x/mod v0.31.0 // indirect + github.com/stretchr/testify v1.10.0 // indirect golang.org/x/net v0.48.0 // indirect golang.org/x/oauth2 v0.34.0 // indirect - golang.org/x/sync v0.19.0 // indirect golang.org/x/sys v0.39.0 // indirect golang.org/x/text v0.32.0 // indirect - golang.org/x/tools v0.40.0 // indirect ) //replace github.com/sandertv/go-raknet => github.com/tedacmc/tedac-raknet v0.0.4 @@ -60,8 +53,4 @@ require ( replace github.com/df-mc/dragonfly => ../dragonfly -replace github.com/cooldogedev/spectrum => ../spectrum - replace github.com/oomph-ac/oconfig => ../oconfig - -replace github.com/akmalfairuz/legacy-version => ../legacy-version diff --git a/go.sum b/go.sum index dda52bc7..ee18ce60 100755 --- a/go.sum +++ b/go.sum @@ -1,277 +1,112 @@ -cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -cloud.google.com/go v0.31.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -cloud.google.com/go v0.37.0/go.mod h1:TS1dMSSfndXH133OKGwekG838Om/cQT0BUHV3HcBgoo= -dmitri.shuralyov.com/app/changes v0.0.0-20180602232624-0a106ad413e3/go.mod h1:Yl+fi1br7+Rr3LqpNJf1/uxUdtRUV+Tnj0o93V2B9MU= -dmitri.shuralyov.com/html/belt v0.0.0-20180602232347-f7d459c86be0/go.mod h1:JLBrvjyP0v+ecvNYvCpyZgu5/xkfAUhi6wJj28eUfSU= -dmitri.shuralyov.com/service/change v0.0.0-20181023043359-a85b471d5412/go.mod h1:a1inKt/atXimZ4Mv927x+r7UpyzRUf4emIoiiSC2TN4= -dmitri.shuralyov.com/state v0.0.0-20180228185332-28bcc343414c/go.mod h1:0PRwlb0D6DFvNNtx+9ybjezNCa8XF0xaYcETyp6rHWU= -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/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c= -github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= -github.com/bradfitz/go-smtpd v0.0.0-20170404230938-deb6d6237625/go.mod h1:HYsPBTaaSFSlLx/70C2HPIMNZpVV8+vt/A+FMnYP11g= github.com/brentp/intintmap v0.0.0-20190211203843-30dc0ade9af9 h1:/G0ghZwrhou0Wq21qc1vXXMm/t/aKWkALWwITptKbE0= github.com/brentp/intintmap v0.0.0-20190211203843-30dc0ade9af9/go.mod h1:TOk10ahXejq9wkEaym3KPRNeuR/h5Jx+s8QRWIa2oTM= -github.com/buger/jsonparser v0.0.0-20181115193947-bf1c66bbce23/go.mod h1:bbYlZJ7hK1yFx9hf58LP0zeX7UjIGs20ufpu3evjr+s= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/chewxy/math32 v1.10.1 h1:LFpeY0SLJXeaiej/eIp2L40VYfscTvKh/FSEZ68uMkU= github.com/chewxy/math32 v1.10.1/go.mod h1:dOB2rcuFrCn6UHrze36WSLVPKtzPMRAQvBvUwkSsLqs= -github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= -github.com/cooldogedev/spectral v0.0.5 h1:VTWbJkqwDqg/eeDwIXwC6+jpXEGlOzu6QzP1hSEUhIM= -github.com/cooldogedev/spectral v0.0.5/go.mod h1:Oq9dVLgqaRiS/hZvKvLk/XWXFOZmmTKM29u6o4GBe84= -github.com/coreos/go-systemd v0.0.0-20181012123002-c6f51f82210d/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= +github.com/coreos/go-oidc/v3 v3.17.0 h1:hWBGaQfbi0iVviX4ibC7bk8OKT5qNr4klBaCHVNvehc= +github.com/coreos/go-oidc/v3 v3.17.0/go.mod h1:wqPbKFrVnE90vty060SB40FCJ8fTHTxSwyXJqZH+sI8= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/df-mc/go-playfab v1.0.0 h1:6gVukk3aQbJ934GJFdcZJHVIw9lhauK+KHOevbwJA10= +github.com/df-mc/go-playfab v1.0.0/go.mod h1:nGOlE+JFGOH5Z0iidEgJapHhndFi/oNk17RN9pKCF+k= +github.com/df-mc/go-xsapi v1.0.1 h1:H1SbxYr4rXOqZSB8MwiODbDUsHRihxbHf+YOljUWgXw= +github.com/df-mc/go-xsapi v1.0.1/go.mod h1:uKC/a/2/JOamgRDezvgVe7OmXdqERUfmCcIWAOp9hPA= github.com/df-mc/goleveldb v1.1.9 h1:ihdosZyy5jkQKrxucTQmN90jq/2lUwQnJZjIYIC/9YU= github.com/df-mc/goleveldb v1.1.9/go.mod h1:+NHCup03Sci5q84APIA21z3iPZCuk6m6ABtg4nANCSk= github.com/df-mc/jsonc v1.0.5 h1:O7oh07kbS5AYY+l2Fji6l4h0iHcdjKbxCtK5VlZlLMU= github.com/df-mc/jsonc v1.0.5/go.mod h1:+Q++JuCE9IKiP8v7sWImdf/RjQX0nfXyfX6PdfTTmc4= github.com/df-mc/worldupgrader v1.0.20 h1:wfJyG3bFeaM/HXy7TCiO4HKVw3Mf3N4gPFmgxMHsKnc= github.com/df-mc/worldupgrader v1.0.20/go.mod h1:tsSOLTRm9mpG7VHvYpAjjZrkRHWmSbKZAm9bOLNnlDk= -github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= github.com/ethaniccc/float32-cube v0.0.0-20250511224129-7af1f8c4ee12 h1:o8NDdPPBeF7y//XYIRvzrXPB08/Lblt/ceu1+3vS1hM= github.com/ethaniccc/float32-cube v0.0.0-20250511224129-7af1f8c4ee12/go.mod h1:xBh0GYHZ5yHg3YvvUGriGLSAlm7YW2S9SRULstdLZLk= -github.com/fatih/set v0.2.1 h1:nn2CaJyknWE/6txyUDGwysr3G5QC6xWB/PtVjPBbeaA= -github.com/fatih/set v0.2.1/go.mod h1:+RKtMCH+favT2+3YecHGxcc0b4KyVWA1QWWJUs4E0CI= -github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc= -github.com/francoispqt/gojay v1.2.13 h1:d2m3sFjloqoIUQU3TsHBgj6qg/BVGlTBeHDUmyJnXKk= -github.com/francoispqt/gojay v1.2.13/go.mod h1:ehT5mTG4ua4581f1++1WLG0vPdaA9HaiDsoyrBGkyDY= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/getsentry/sentry-go v0.40.0 h1:VTJMN9zbTvqDqPwheRVLcp0qcUcM+8eFivvGocAaSbo= github.com/getsentry/sentry-go v0.40.0/go.mod h1:eRXCoh3uvmjQLY6qu63BjUZnaBu5L5WhMV1RwYO8W5s= -github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= -github.com/gliderlabs/ssh v0.1.1/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0= github.com/go-echarts/go-echarts/v2 v2.4.1/go.mod h1:56YlvzhW/a+du15f3S2qUGNDfKnFOeJSThBIrVFHDtI= github.com/go-echarts/go-echarts/v2 v2.5.1 h1:kFVNaS3IsszKOQmUyCi95D2IhipE5twfvaBhFLOfPrs= github.com/go-echarts/go-echarts/v2 v2.5.1/go.mod h1:56YlvzhW/a+du15f3S2qUGNDfKnFOeJSThBIrVFHDtI= github.com/go-echarts/statsview v0.4.2 h1:DEfN6nAR4Y/VfP/AEOD47t1mPZEhg3bL7pc9CL2n3Sc= github.com/go-echarts/statsview v0.4.2/go.mod h1:o2Jhr2AppGIkjWSjVnuYoSEELRBYU7NN17A19s6buZM= -github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q= github.com/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxIA= github.com/go-errors/errors v1.4.2/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og= github.com/go-gl/mathgl v1.2.0 h1:v2eOj/y1B2afDxF6URV1qCYmo1KW08lAMtTbOn3KXCY= github.com/go-gl/mathgl v1.2.0/go.mod h1:pf9+b5J3LFP7iZ4XXaVzZrCle0Q/vNpB/vDe5+3ulRE= github.com/go-jose/go-jose/v4 v4.1.3 h1:CVLmWDhDVRa6Mi/IgCgaopNosCaHz7zrMeF9MlZRkrs= github.com/go-jose/go-jose/v4 v4.1.3/go.mod h1:x4oUasVrzR7071A4TnHLGSPpNOm2a21K9Kf04k1rs08= -github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= -github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= -github.com/golang/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:tluoj9z5200jBnyusfRPU2LqT6J+DAorxEvtC7LHB+E= -github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= -github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/golang/snappy v1.0.0 h1:Oy607GVXHs7RtbggtPBnr2RmDArIsAefDwvrdWvRhGs= github.com/golang/snappy v1.0.0/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= -github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= -github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= -github.com/google/go-github v17.0.0+incompatible/go.mod h1:zLgOLi98H3fifZn+44m+umXrS52loVEgC2AApnigrVQ= -github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= -github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= -github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/googleapis/gax-go v2.0.0+incompatible/go.mod h1:SFVmujtThgffbyetf+mdk2eWhX2bMyUtNHzFKcPA9HY= -github.com/googleapis/gax-go/v2 v2.0.3/go.mod h1:LLvjysVCY1JZeum8Z6l8qUty8fiNwE08qbEPm1M08qg= -github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= -github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= -github.com/grpc-ecosystem/grpc-gateway v1.5.0/go.mod h1:RSKVYQBd5MCa4OVpNdGskqpgL2+G+NZTnrVHpWWfpdw= -github.com/hashicorp/go-version v1.8.0 h1:KAkNb1HAiZd1ukkxDFGmokVZe1Xy9HG6NUp+bPle2i4= -github.com/hashicorp/go-version v1.8.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= github.com/hjson/hjson-go/v4 v4.4.0 h1:D/NPvqOCH6/eisTb5/ztuIS8GUvmpHaLOcNk1Bjr298= github.com/hjson/hjson-go/v4 v4.4.0/go.mod h1:KaYt3bTw3zhBjYqnXkYywcYctk0A2nxeEFTse3rH13E= github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= -github.com/jellevandenhooff/dkim v0.0.0-20150330215556-f50fe3d243e1/go.mod h1:E0B/fFc00Y+Rasa88328GlI/XbtyysCtTHZS8h7IrBU= -github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= -github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= -github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= -github.com/klauspost/compress v1.18.2 h1:iiPHWW0YrcFgpBYhsA6D1+fqHssJscY/Tm/y2Uqnapk= -github.com/klauspost/compress v1.18.2/go.mod h1:R0h/fSBs8DE4ENlcrlib3PsXS61voFxhIs2DeRhCvJ4= +github.com/klauspost/compress v1.18.4 h1:RPhnKRAQ4Fh8zU2FY/6ZFDwTVTxgJ/EMydqSTzE9a2c= +github.com/klauspost/compress v1.18.4/go.mod h1:R0h/fSBs8DE4ENlcrlib3PsXS61voFxhIs2DeRhCvJ4= github.com/klauspost/cpuid/v2 v2.0.9 h1:lgaqFMSdTdQYdZ04uHyN2d/eKdOMyi2YLSvlQIBFYa4= github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= -github.com/kr/pty v1.1.3/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= -github.com/lunixbochs/vtclean v1.0.0/go.mod h1:pHhQNgMf3btfWnGBVipUOjRYhoOsdGqdm/+2c2E2WMI= -github.com/mailru/easyjson v0.0.0-20190312143242-1de009706dbe/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= -github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= -github.com/microcosm-cc/bluemonday v1.0.1/go.mod h1:hsXNsILzKxV+sX77C5b8FSuKF00vh2OMYv+xgHpAMF4= -github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= -github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= -github.com/neelance/astrewrite v0.0.0-20160511093645-99348263ae86/go.mod h1:kHJEU3ofeGjhHklVoIGuVj85JJwZ6kWPaJwCIxgnFmo= -github.com/neelance/sourcemap v0.0.0-20151028013722-8c68805598ab/go.mod h1:Qr6/a/Q4r9LP1IltGz7tA7iOK1WonHEYhu1HRBA7ZiM= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.7.0 h1:WSHQ+IS43OoUrWtD1/bbclrwK8TTH5hzp+umCiuxHgs= github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/onsi/gomega v1.36.3 h1:hID7cr8t3Wp26+cYnfcjR6HpJ00fdogN6dqZ1t6IylU= github.com/onsi/gomega v1.36.3/go.mod h1:8D9+Txp43QWKhM24yyOBEdpkzN8FvJyAwecBgsU4KU0= -github.com/openzipkin/zipkin-go v0.1.1/go.mod h1:NtoC/o8u3JlF1lSlyPNswIbeQH9bJTmOf0Erfk+hxe8= github.com/pingcap/errors v0.11.4 h1:lFuQV/oaUMGcD2tqt+01ROSmJs75VG1ToEOkZIZ4nE4= github.com/pingcap/errors v0.11.4/go.mod h1:Oi8TUi2kEtXXLMJk9l1cGmz20kV3TaQ0usTwv5KuLY8= -github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/prometheus/client_golang v0.8.0/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= -github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= -github.com/prometheus/common v0.0.0-20180801064454-c7de2306084e/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= -github.com/prometheus/procfs v0.0.0-20180725123919-05ee40e3a273/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= -github.com/quic-go/quic-go v0.55.0 h1:zccPQIqYCXDt5NmcEabyYvOnomjs8Tlwl7tISjJh9Mk= -github.com/quic-go/quic-go v0.55.0/go.mod h1:DR51ilwU1uE164KuWXhinFcKWGlEjzys2l8zUl5Ss1U= github.com/rs/cors v1.11.0 h1:0B9GE/r9Bc2UxRMMtymBkHTenPkHDv0CW4Y98GBY+po= github.com/rs/cors v1.11.0/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU= -github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= -github.com/samber/lo v1.49.1 h1:4BIFyVfuQSEpluc7Fua+j1NolZHiEHEpaSEKdsH0tew= -github.com/samber/lo v1.49.1/go.mod h1:dO6KHFzUKXgP8LDhU0oI8d2hekjXnGOu0DB8Jecxd6o= -github.com/sandertv/go-raknet v1.15.0 h1:OCJFvVsn5RWXOXF+gZD5uTAmy9RbOofUzOhvSyuR1tE= -github.com/sandertv/go-raknet v1.15.0/go.mod h1:/yysjwfCXm2+2OY8mBazLzcxJ3irnylKCyG3FLgUPVU= -github.com/sandertv/gophertunnel v1.52.3-0.20260103191303-85f94a5d3b01 h1:Ajx1kZLeCoYHfBSS6LBgDNSqt3VuE7dgZOaWnN2ovts= -github.com/sandertv/gophertunnel v1.52.3-0.20260103191303-85f94a5d3b01/go.mod h1:IhLg93aMPY/rKgB7lKxTzOZ6i11OgkL0WinVs+gMvlI= -github.com/scylladb/go-set v1.0.2 h1:SkvlMCKhP0wyyct6j+0IHJkBkSZL+TDzZ4E7f7BCcRE= -github.com/scylladb/go-set v1.0.2/go.mod h1:DkpGd78rljTxKAnTDPFqXSGxvETQnJyuSOQwsHycqfs= +github.com/sandertv/go-raknet v1.15.1-0.20260112202637-beca0b10c217 h1:UZQq2253Q+7co/C9Et62RYPBggzz+L+2yqGlvQhSNM8= +github.com/sandertv/go-raknet v1.15.1-0.20260112202637-beca0b10c217/go.mod h1:/yysjwfCXm2+2OY8mBazLzcxJ3irnylKCyG3FLgUPVU= +github.com/sandertv/gophertunnel v1.54.0 h1:iAHY+0k84GtoRVe3IEDNLScM0WRS0Rxh9YODAGjzjOM= +github.com/sandertv/gophertunnel v1.54.0/go.mod h1:F8+ZPbzxJ0LqunXEaDjqeyUgHVB0rI5ZU+PHnptXGfI= github.com/segmentio/fasthash v1.0.3 h1:EI9+KE1EwvMLBWwjpRDc+fEM+prwxDYbslddQGtrmhM= github.com/segmentio/fasthash v1.0.3/go.mod h1:waKX8l2N8yckOgmSsXJi7x1ZfdKZ4x7KRMzBtS3oedY= -github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= -github.com/shurcooL/component v0.0.0-20170202220835-f88ec8f54cc4/go.mod h1:XhFIlyj5a1fBNx5aJTbKoIq0mNaPvOagO+HjB3EtxrY= -github.com/shurcooL/events v0.0.0-20181021180414-410e4ca65f48/go.mod h1:5u70Mqkb5O5cxEA8nxTsgrgLehJeAw6Oc4Ab1c/P1HM= -github.com/shurcooL/github_flavored_markdown v0.0.0-20181002035957-2122de532470/go.mod h1:2dOwnU2uBioM+SGy2aZoq1f/Sd1l9OkAeAUvjSyvgU0= -github.com/shurcooL/go v0.0.0-20180423040247-9e1955d9fb6e/go.mod h1:TDJrrUr11Vxrven61rcy3hJMUqaf/CLWYhHNPmT14Lk= -github.com/shurcooL/go-goon v0.0.0-20170922171312-37c2f522c041/go.mod h1:N5mDOmsrJOB+vfqUK+7DmDyjhSLIIBnXo9lvZJj3MWQ= -github.com/shurcooL/gofontwoff v0.0.0-20180329035133-29b52fc0a18d/go.mod h1:05UtEgK5zq39gLST6uB0cf3NEHjETfB4Fgr3Gx5R9Vw= -github.com/shurcooL/gopherjslib v0.0.0-20160914041154-feb6d3990c2c/go.mod h1:8d3azKNyqcHP1GaQE/c6dDgjkgSx2BZ4IoEi4F1reUI= -github.com/shurcooL/highlight_diff v0.0.0-20170515013008-09bb4053de1b/go.mod h1:ZpfEhSmds4ytuByIcDnOLkTHGUI6KNqRNPDLHDk+mUU= -github.com/shurcooL/highlight_go v0.0.0-20181028180052-98c3abbbae20/go.mod h1:UDKB5a1T23gOMUJrI+uSuH0VRDStOiUVSjBTRDVBVag= -github.com/shurcooL/home v0.0.0-20181020052607-80b7ffcb30f9/go.mod h1:+rgNQw2P9ARFAs37qieuu7ohDNQ3gds9msbT2yn85sg= -github.com/shurcooL/htmlg v0.0.0-20170918183704-d01228ac9e50/go.mod h1:zPn1wHpTIePGnXSHpsVPWEktKXHr6+SS6x/IKRb7cpw= -github.com/shurcooL/httperror v0.0.0-20170206035902-86b7830d14cc/go.mod h1:aYMfkZ6DWSJPJ6c4Wwz3QtW22G7mf/PEgaB9k/ik5+Y= -github.com/shurcooL/httpfs v0.0.0-20171119174359-809beceb2371/go.mod h1:ZY1cvUeJuFPAdZ/B6v7RHavJWZn2YPVFQ1OSXhCGOkg= -github.com/shurcooL/httpgzip v0.0.0-20180522190206-b1c53ac65af9/go.mod h1:919LwcH0M7/W4fcZ0/jy0qGght1GIhqyS/EgWGH2j5Q= -github.com/shurcooL/issues v0.0.0-20181008053335-6292fdc1e191/go.mod h1:e2qWDig5bLteJ4fwvDAc2NHzqFEthkqn7aOZAOpj+PQ= -github.com/shurcooL/issuesapp v0.0.0-20180602232740-048589ce2241/go.mod h1:NPpHK2TI7iSaM0buivtFUc9offApnI0Alt/K8hcHy0I= -github.com/shurcooL/notifications v0.0.0-20181007000457-627ab5aea122/go.mod h1:b5uSkrEVM1jQUspwbixRBhaIjIzL2xazXp6kntxYle0= -github.com/shurcooL/octicon v0.0.0-20181028054416-fa4f57f9efb2/go.mod h1:eWdoE5JD4R5UVWDucdOPg1g2fqQRq78IQa9zlOV1vpQ= -github.com/shurcooL/reactions v0.0.0-20181006231557-f2e0b4ca5b82/go.mod h1:TCR1lToEk4d2s07G3XGfz2QrgHXg4RJBvjrOozvoWfk= -github.com/shurcooL/sanitized_anchor_name v0.0.0-20170918181015-86672fcb3f95/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= -github.com/shurcooL/users v0.0.0-20180125191416-49c67e49c537/go.mod h1:QJTqeLYEDaXHZDBsXlPCDqdhQuJkuw4NOtaxYe3xii4= -github.com/shurcooL/webdavfs v0.0.0-20170829043945-18c3829fa133/go.mod h1:hKmq5kWdCj2z2KEozexVbfEZIWiTjhE0+UjmZgPqehw= -github.com/sourcegraph/annotate v0.0.0-20160123013949-f4cad6c6324d/go.mod h1:UdhH50NIW0fCiwBSr0co2m7BnFLdv4fQTgdqdJTHFeE= -github.com/sourcegraph/syntaxhighlight v0.0.0-20170531221838-bd320f5d308e/go.mod h1:HuIsMU8RRBOtsCgI77wP899iHVBQpCmg4ErYMZB+2IA= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.6.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= -github.com/tarm/serial v0.0.0-20180830185346-98f6abe2eb07/go.mod h1:kDXzergiv9cbyO7IOYJZWg1U88JhDg3PB6klq9Hg2pA= -github.com/viant/assertly v0.4.8/go.mod h1:aGifi++jvCrUaklKEKT0BU95igDNaqkvz+49uaYMPRU= -github.com/viant/toolbox v0.24.0/go.mod h1:OxMCG57V0PXuIP2HNQrtJf2CjqdmbrOx5EkMILuUhzM= github.com/zeebo/assert v1.3.0 h1:g7C04CbJuIDKNPFHmsk4hwZDO5O+kntRxzaUoNXj+IQ= github.com/zeebo/assert v1.3.0/go.mod h1:Pq9JiuJQpG8JLJdtkwrJESF0Foym2/D9XMU5ciN/wJ0= github.com/zeebo/xxh3 v1.0.2 h1:xZmwmqxHZA8AI603jOQ0tMqmBr9lPeFwGg6d+xy9DC0= github.com/zeebo/xxh3 v1.0.2/go.mod h1:5NWz9Sef7zIDm2JHfFlcQvNekmcEl9ekUZQQKCYaDcA= -go.opencensus.io v0.18.0/go.mod h1:vKdFvxhtzZ9onBp9VKHK8z/sRpBMnKAsufL7wlDrCOA= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= -go.uber.org/mock v0.5.2 h1:LbtPTcP8A5k9WPXj54PPPbjcI4Y6lhyOZXn+VS7wNko= -go.uber.org/mock v0.5.2/go.mod h1:wLlUxC2vVTPTaE3UD51E0BGOAElKrILxhVSDYQLld5o= -go4.org v0.0.0-20180809161055-417644f6feb5/go.mod h1:MkTOUMDaeVYJUOUsaDXIhWPZYa1yOyC1qaOBpL57BhE= -golang.org/x/build v0.0.0-20190111050920-041ab4dc3f9d/go.mod h1:OWs+y06UdEOHN4y+MfF/py+xQ/tYqIWW03b70/CG9Rw= -golang.org/x/crypto v0.0.0-20181030102418-4d3f4d9ffa16/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= -golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20190313024323-a1f597ede03a/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.46.0 h1:cKRW/pmt1pKAfetfu+RCEvjvZkA9RimPbh7bhFjGVBU= -golang.org/x/crypto v0.46.0/go.mod h1:Evb/oLKmMraqjZ2iQTwDwvCtJkczlDuTmdJXoZVzqU0= -golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20251209150349-8475f28825e9 h1:MDfG8Cvcqlt9XXrmEiD4epKn7VJHZO84hejP9Jmp0MM= golang.org/x/exp v0.0.0-20251209150349-8475f28825e9/go.mod h1:EPRbTFwzwjXj9NpYyyrvenVh9Y+GFeEvMNh7Xuz7xgU= -golang.org/x/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= -golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= -golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= -golang.org/x/mod v0.31.0 h1:HaW9xtz0+kOcWKwli0ZXy79Ix+UW/vOfmWI5QVd2tgI= -golang.org/x/mod v0.31.0/go.mod h1:43JraMp9cGx1Rx3AqioxrbrhNsLl2l/iNAvuBkrezpg= -golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20181029044818-c44066c5c816/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20181106065722-10aee1819953/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190313220215-9f648a60d977/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.48.0 h1:zyQRTTrjc33Lhh0fBgT/H3oZq9WuvRR5gPC70xpDiQU= golang.org/x/net v0.48.0/go.mod h1:+ndRgGjkh8FGtu1w1FGbEC31if4VrNVMuKTgcAAnQRY= -golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= -golang.org/x/oauth2 v0.0.0-20181017192945-9dcd33a902f4/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= -golang.org/x/oauth2 v0.0.0-20181203162652-d668ce993890/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= -golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.34.0 h1:hqK/t4AKgbqWkdkcAeI8XLmbK+4m4G5YeQRrmiotGlw= golang.org/x/oauth2 v0.34.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA= -golang.org/x/perf v0.0.0-20180704124530-6e6d33e29852/go.mod h1:JLpeXjPJfIyPr5TlbXLkXWLhP8nz10XfvxElABhCtcw= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4= -golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= -golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20181029174526-d69651ed3497/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190316082340-a2f829d7f35f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk= golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.32.0 h1:ZD01bjUt1FQ9WJ0ClOL5vxgxOI/sVCNgX1YtKwcY0mU= golang.org/x/text v0.32.0/go.mod h1:o/rUWzghvpD5TXrTIBuJU77MTaN0ljMWE47kxGJQ7jY= -golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20181030000716-a0a13e073c7b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= -golang.org/x/tools v0.40.0 h1:yLkxfA+Qnul4cs9QA3KnlFu0lVmd8JJfoq+E41uSutA= -golang.org/x/tools v0.40.0/go.mod h1:Ik/tzLRlbscWpqqMRjyWYDisX8bG13FrdXp3o4Sr9lc= -google.golang.org/api v0.0.0-20180910000450-7ca32eb868bf/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0= -google.golang.org/api v0.0.0-20181030000543-1d582fd0359e/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0= -google.golang.org/api v0.1.0/go.mod h1:UGEZY7KEX120AnNLIHFMKIo4obdJhkp2tPbaPlQx13Y= -google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= -google.golang.org/appengine v1.2.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= -google.golang.org/appengine v1.3.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= -google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= -google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= -google.golang.org/genproto v0.0.0-20180831171423-11092d34479b/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= -google.golang.org/genproto v0.0.0-20181029155118-b69ba1387ce2/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= -google.golang.org/genproto v0.0.0-20181202183823-bd91e49a0898/go.mod h1:7Ep/1NZk928CDR8SjdVbjWNpdIf6nzjE3BTgJDr2Atg= -google.golang.org/genproto v0.0.0-20190306203927-b5d61aea6440/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/grpc v1.14.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= -google.golang.org/grpc v1.16.0/go.mod h1:0JHn/cJsOMiMfNA9+DeHDlAU7KAAB5GDlYFpa9MZMio= -google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs= -google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= -gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -grpc.go4.org v0.0.0-20170609214715-11d0a25b4919/go.mod h1:77eQGdRu53HpSqPFJFmuJdjuHRquDANNeA4x7B8WQ9o= -honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -sourcegraph.com/sourcegraph/go-diff v0.5.0/go.mod h1:kuch7UrkMzY0X+p9CRK03kfuPQ2zzQcaEFbx8wA8rck= -sourcegraph.com/sqs/pbtypes v0.0.0-20180604144634-d3ebe8f20ae4/go.mod h1:ketZ/q3QxT9HOBeFhu6RdvsftgpsbFHBF5Cas6cDKZ0= diff --git a/player/component/world.go b/player/component/world.go index e655f575..f061a198 100644 --- a/player/component/world.go +++ b/player/component/world.go @@ -442,6 +442,19 @@ func (c *WorldUpdaterComponent) Flush() { c.batchedBlockUpdates = acknowledgement.NewUpdateBlockBatchACK(c.mPlayer) } +func (c *WorldUpdaterComponent) ResetForTransfer() { + c.clientPlacedBlocks = make(map[df_cube.Pos]*chainedBlockPlacement) + c.pendingBlockUpdates = make(map[df_cube.Pos]uint32) + c.batchedBlockUpdates = acknowledgement.NewUpdateBlockBatchACK(c.mPlayer) + + c.breakingBlockPos = nil + c.prevPlaceRequest = nil + c.initalInteractionAccepted = true + + c.serverChunkRadius = 0 + c.chunkRadius = 1_000_000_000 +} + func (c *WorldUpdaterComponent) Tick() { for pos, pl := range c.clientPlacedBlocks { pl.remainingTicks-- diff --git a/player/network.go b/player/network.go index c7db769e..d3039656 100755 --- a/player/network.go +++ b/player/network.go @@ -27,7 +27,10 @@ func (p *Player) SetConn(conn *minecraft.Conn) { p.conn = conn p.RuntimeId = conn.GameData().EntityRuntimeID + p.ClientRuntimeId = conn.GameData().EntityRuntimeID p.UniqueId = conn.GameData().EntityUniqueID + p.ClientUniqueId = conn.GameData().EntityUniqueID + p.IDModified = false p.ClientDat = conn.ClientData() p.IdentityDat = conn.IdentityData() @@ -42,18 +45,23 @@ func (p *Player) SetServerConn(conn ServerConn) { return } - if p.serverConn == nil { - p.GameDat = conn.GameData() - for _, item := range p.GameDat.Items { - if i, ok := world.ItemByName(item.Name, 0); ok { - p.items[item.RuntimeID] = i - } + modified := p.serverConn != nil + + p.serverConn = conn + p.GameDat = conn.GameData() + for _, item := range p.GameDat.Items { + if i, ok := world.ItemByName(item.Name, 0); ok { + p.items[item.RuntimeID] = i } } - p.serverConn = conn p.RuntimeId = conn.GameData().EntityRuntimeID p.UniqueId = conn.GameData().EntityUniqueID + if !modified { + p.ClientRuntimeId = conn.GameData().EntityRuntimeID + p.ClientUniqueId = conn.GameData().EntityUniqueID + } + p.IDModified = modified p.GameMode = conn.GameData().PlayerGameMode if p.GameMode == 5 { p.GameMode = conn.GameData().WorldGameMode diff --git a/player/packet.go b/player/packet.go index e54d6696..89db48a0 100644 --- a/player/packet.go +++ b/player/packet.go @@ -39,6 +39,8 @@ var ClientDecode = []uint32{ var ServerDecode = []uint32{ packet.IDAddActor, packet.IDAddPlayer, + packet.IDBossEvent, + packet.IDTransfer, packet.IDChunkRadiusUpdated, packet.IDInventorySlot, packet.IDInventoryContent, @@ -60,6 +62,9 @@ var ServerDecode = []uint32{ packet.IDContainerClose, packet.IDCraftingData, packet.IDCreativeContent, + packet.IDPlayerList, + packet.IDSetDisplayObjective, + packet.IDRemoveObjective, packet.IDAvailableCommands, } @@ -75,6 +80,10 @@ func (p *Player) HandleClientPacket(ctx *context.HandlePacketContext) { }() pk := *(ctx.Packet()) + if p.translateClientPacketForTransfer(pk) { + ctx.SetModified() + } + switch pk := pk.(type) { case *packet.PacketViolationWarning: p.Log().Warn( @@ -339,10 +348,23 @@ func (p *Player) HandleServerPacket(ctx *context.HandlePacketContext) { }() pk := *(ctx.Packet()) + if transfer, ok := pk.(*packet.Transfer); ok { + if err := p.TryTransfer(transfer.Address, transfer.Port); err != nil { + p.Log().Warn("failed to handle upstream transfer", "address", transfer.Address, "port", transfer.Port, "error", err) + return + } + ctx.Cancel() + return + } + switch pk := pk.(type) { case *packet.AvailableCommands: p.initOomphCommand(pk) case *packet.AddActor: + if p.IDModified && pk.EntityRuntimeID == p.RuntimeId { + ctx.Cancel() + return + } width, height, scale := calculateBBSize(pk.EntityMetadata, 0.6, 1.8, 1.0) p.entTracker.AddEntity(pk.EntityRuntimeID, entity.New( pk.EntityRuntimeID, @@ -369,6 +391,10 @@ func (p *Player) HandleServerPacket(ctx *context.HandlePacketContext) { &p.log, )) case *packet.AddPlayer: + if p.IDModified && pk.EntityRuntimeID == p.RuntimeId { + ctx.Cancel() + return + } width, height, scale := calculateBBSize(pk.EntityMetadata, 0.6, 1.8, 1.0) p.entTracker.AddEntity(pk.EntityRuntimeID, entity.New( pk.EntityRuntimeID, @@ -455,6 +481,24 @@ func (p *Player) HandleServerPacket(ctx *context.HandlePacketContext) { case *packet.RemoveActor: p.entTracker.RemoveEntity(uint64(pk.EntityUniqueID)) p.clientEntTracker.RemoveEntity(uint64(pk.EntityUniqueID)) + case *packet.BossEvent: + if pk.EventType == packet.BossEventHide { + delete(p.transferBossBars, pk.BossEntityUniqueID) + } else { + p.transferBossBars[pk.BossEntityUniqueID] = struct{}{} + } + case *packet.PlayerList: + for _, entry := range pk.Entries { + if pk.ActionType == packet.PlayerListActionAdd { + p.transferPlayerList[entry.UUID] = struct{}{} + continue + } + delete(p.transferPlayerList, entry.UUID) + } + case *packet.SetDisplayObjective: + p.transferObjectives[pk.ObjectiveName] = struct{}{} + case *packet.RemoveObjective: + delete(p.transferObjectives, pk.ObjectiveName) case *packet.SetActorData: pk.Tick = 0 ctx.SetModified() @@ -521,4 +565,12 @@ func (p *Player) HandleServerPacket(ctx *context.HandlePacketContext) { p.CreativeItems[item.CreativeItemNetworkID] = item } } + + cancel, modified := p.translateServerPacketForTransfer(pk) + if cancel { + ctx.Cancel() + } + if modified { + ctx.SetModified() + } } diff --git a/player/player.go b/player/player.go index 9ab448e7..a5255252 100755 --- a/player/player.go +++ b/player/player.go @@ -10,6 +10,7 @@ import ( "github.com/df-mc/dragonfly/server/event" df_world "github.com/df-mc/dragonfly/server/world" + "github.com/google/uuid" "github.com/oomph-ac/oconfig" "github.com/oomph-ac/oomph/entity" "github.com/oomph-ac/oomph/game" @@ -71,10 +72,14 @@ type Player struct { GameDat minecraft.GameData Version int32 - // With fast transfers, the client will still retain it's original runtime and unique IDs, so - // we must translate them to new ones, while still retaining the old ones for the client to use. + // RuntimeId/UniqueId are the server-side entity IDs for the current upstream connection. RuntimeId uint64 UniqueId int64 + // ClientRuntimeId/ClientUniqueId are the IDs initially assigned to the local client connection. + // When IDModified is true, packet IDs are translated between client IDs and current server IDs. + ClientRuntimeId uint64 + ClientUniqueId int64 + IDModified bool // ClientTick is the tick of the client, synchronized with the server's on an interval. // InputCount is the amount of PlayerAuthInput packets the client has sent. @@ -105,6 +110,12 @@ type Player struct { LastEquipmentData *packet.MobEquipment // LastActorMetadata is the last actor metadata packet sent by the client LastSetActorData *packet.SetActorData + // transferBossBars tracks active bossbars so they can be explicitly hidden during fast transfers. + transferBossBars map[int64]struct{} + // transferPlayerList tracks player-list entries shown to the client so they can be removed during fast transfers. + transferPlayerList map[uuid.UUID]struct{} + // transferObjectives tracks active scoreboard objective names so they can be removed during fast transfers. + transferObjectives map[string]struct{} // Recipies is a map of recipe network IDs to recipes. Recipies map[uint32]protocol.Recipe @@ -229,8 +240,11 @@ func New(log *slog.Logger, mState MonitoringState, listener *minecraft.Listener) CloseChan: make(chan bool), RunChan: make(chan func(), 32), - Recipies: make(map[uint32]protocol.Recipe), - CreativeItems: make(map[uint32]protocol.CreativeItem), + Recipies: make(map[uint32]protocol.Recipe), + CreativeItems: make(map[uint32]protocol.CreativeItem), + transferBossBars: make(map[int64]struct{}), + transferPlayerList: make(map[uuid.UUID]struct{}), + transferObjectives: make(map[string]struct{}), deferredPackets: make([]packet.Packet, 0, 256), diff --git a/player/transfer.go b/player/transfer.go new file mode 100644 index 00000000..0f636c72 --- /dev/null +++ b/player/transfer.go @@ -0,0 +1,448 @@ +package player + +import ( + "bytes" + "fmt" + "math" + "time" + + df_cube "github.com/df-mc/dragonfly/server/block/cube" + df_world "github.com/df-mc/dragonfly/server/world" + "github.com/df-mc/dragonfly/server/world/chunk" + "github.com/go-gl/mathgl/mgl32" + oworld "github.com/oomph-ac/oomph/world" + "github.com/sandertv/gophertunnel/minecraft" + "github.com/sandertv/gophertunnel/minecraft/protocol" + "github.com/sandertv/gophertunnel/minecraft/protocol/packet" +) + +const transferDialTimeout = 30 * time.Second + +// TryTransfer attempts to transfer the player to a new upstream server without disconnecting them from this proxy. +func (p *Player) TryTransfer(address string, port uint16) error { + if p.MState.IsReplay { + return fmt.Errorf("cannot transfer while replaying") + } + if p.conn == nil { + return fmt.Errorf("client connection is nil") + } + + targetAddress := fmt.Sprintf("%s:%d", address, port) + clientData := p.ClientDat + clientData.ThirdPartyName = p.IdentityDat.DisplayName + + dialer := minecraft.Dialer{ + ClientData: clientData, + IdentityData: p.IdentityDat, + + DisconnectOnUnknownPackets: false, + DisconnectOnInvalidPackets: true, + FlushRate: -1, + } + serverConn, err := dialer.DialTimeout("raknet", targetAddress, transferDialTimeout) + if err != nil { + return err + } + if err := serverConn.DoSpawn(); err != nil { + _ = serverConn.Close() + return err + } + + oldServerConn := p.serverConn + oldDimension := p.GameDat.Dimension + + p.SetServerConn(serverConn) + p.ACKs().Invalidate() + + p.clearTransferWorld(oldDimension) + p.clearTransferEntities() + p.clearTransferBossBars() + p.clearTransferPlayerList() + p.clearTransferObjectives() + p.clearTransferEffects() + p.clearTransferWeather() + + if w := p.World(); w != nil { + w.SetSTWTicks(300) + } + if wu := p.WorldUpdater(); wu != nil { + wu.ResetForTransfer() + } + if c := p.Combat(); c != nil { + c.Reset() + } + if c := p.ClientCombat(); c != nil { + c.Reset() + } + + gameData := p.GameDat + _ = p.SendPacketToClient(&packet.SetPlayerGameType{ + GameType: p.GameMode, + }) + _ = p.SendPacketToClient(&packet.GameRulesChanged{ + GameRules: gameData.GameRules, + }) + _ = p.SendPacketToClient(&packet.SetDifficulty{ + Difficulty: uint32(gameData.Difficulty), + }) + metadata := protocol.NewEntityMetadata() + metadata.SetFlag(protocol.EntityDataKeyFlags, protocol.EntityDataFlagBreathing) + metadata.SetFlag(protocol.EntityDataKeyFlags, protocol.EntityDataFlagHasGravity) + _ = p.SendPacketToClient(&packet.SetActorData{ + EntityRuntimeID: p.ClientRuntimeId, + EntityMetadata: metadata, + }) + + p.Movement().SetPos(gameData.PlayerPosition.Sub(mgl32.Vec3{0, 1.62, 0})) + p.Movement().SetVel(mgl32.Vec3{}) + p.Movement().SetOnGround(true) + p.Movement().SetImmobile(false) + p.Movement().SetNoClip(false) + + _ = p.SendPacketToClient(&packet.MovePlayer{ + EntityRuntimeID: p.ClientRuntimeId, + Position: gameData.PlayerPosition, + Mode: packet.MoveModeReset, + OnGround: true, + }) + + chunkRadius := int32(p.ChunkRadius()) + if wu := p.WorldUpdater(); wu != nil { + if wuRadius := wu.ChunkRadius(); wuRadius > 0 { + chunkRadius = wuRadius + } + } + if chunkRadius <= 0 { + chunkRadius = 4 + } + maxChunkRadius := uint8(chunkRadius) + if chunkRadius > 255 { + maxChunkRadius = 255 + } + + _ = p.SendPacketToClient(&packet.NetworkChunkPublisherUpdate{ + Position: protocol.BlockPos{ + int32(math.Floor(float64(gameData.PlayerPosition.X()))) >> 4, + 0, + int32(math.Floor(float64(gameData.PlayerPosition.Z()))) >> 4, + }, + Radius: uint32(chunkRadius * 16), + }) + _ = serverConn.WritePacket(&packet.RequestChunkRadius{ + ChunkRadius: chunkRadius, + MaxChunkRadius: maxChunkRadius, + }) + + _ = p.Flush() + _ = serverConn.Flush() + + if oldServerConn != nil && oldServerConn != serverConn { + _ = oldServerConn.Close() + } + return nil +} + +func (p *Player) clearTransferWorld(oldDimension int32) { + positions := p.World().ChunkPositions() + if len(positions) == 0 { + return + } + + dimension, ok := df_world.DimensionByID(int(oldDimension)) + if !ok { + dimension = df_world.Overworld + } + subChunkCount, emptyPayload := makeEmptyChunkPayload(dimension.Range()) + for _, chunkPos := range positions { + _ = p.SendPacketToClient(&packet.LevelChunk{ + Position: chunkPos, + Dimension: oldDimension, + SubChunkCount: subChunkCount, + RawPayload: emptyPayload, + }) + } + p.World().PurgeChunks() +} + +func (p *Player) clearTransferEntities() { + removed := make(map[uint64]struct{}, len(p.EntityTracker().All())+len(p.ClientEntityTracker().All())) + remove := func(rid uint64) { + if rid == p.ClientRuntimeId { + return + } + if _, ok := removed[rid]; ok { + return + } + removed[rid] = struct{}{} + _ = p.SendPacketToClient(&packet.RemoveActor{ + EntityUniqueID: int64(rid), + }) + } + + for rid := range p.EntityTracker().All() { + remove(rid) + p.EntityTracker().RemoveEntity(rid) + } + for rid := range p.ClientEntityTracker().All() { + remove(rid) + p.ClientEntityTracker().RemoveEntity(rid) + } +} + +func (p *Player) clearTransferBossBars() { + if len(p.transferBossBars) == 0 { + return + } + for bossID := range p.transferBossBars { + _ = p.SendPacketToClient(&packet.BossEvent{ + BossEntityUniqueID: bossID, + EventType: packet.BossEventHide, + }) + delete(p.transferBossBars, bossID) + } +} + +func (p *Player) clearTransferPlayerList() { + if len(p.transferPlayerList) == 0 { + return + } + + entries := make([]protocol.PlayerListEntry, 0, len(p.transferPlayerList)) + for uuid := range p.transferPlayerList { + entries = append(entries, protocol.PlayerListEntry{UUID: uuid}) + delete(p.transferPlayerList, uuid) + } + _ = p.SendPacketToClient(&packet.PlayerList{ + ActionType: packet.PlayerListActionRemove, + Entries: entries, + }) +} + +func (p *Player) clearTransferObjectives() { + if len(p.transferObjectives) == 0 { + return + } + for objective := range p.transferObjectives { + _ = p.SendPacketToClient(&packet.RemoveObjective{ + ObjectiveName: objective, + }) + delete(p.transferObjectives, objective) + } +} + +func (p *Player) clearTransferEffects() { + for id := range p.Effects().All() { + _ = p.SendPacketToClient(&packet.MobEffect{ + EntityRuntimeID: p.ClientRuntimeId, + Operation: packet.MobEffectRemove, + EffectType: id, + }) + p.Effects().Remove(id) + } +} + +func (p *Player) clearTransferWeather() { + _ = p.SendPacketToClient(&packet.LevelEvent{ + EventType: packet.LevelEventStopThunderstorm, + EventData: 0, + }) + _ = p.SendPacketToClient(&packet.LevelEvent{ + EventType: packet.LevelEventStopRaining, + EventData: 10000, + }) +} + +func makeEmptyChunkPayload(dimRange df_cube.Range) (subChunkCount uint32, payload []byte) { + c := chunk.New(oworld.AirRuntimeID, dimRange) + data := chunk.Encode(c, chunk.NetworkEncoding) + + buf := bytes.NewBuffer(nil) + for _, sub := range data.SubChunks { + buf.Write(sub) + } + buf.Write(data.Biomes) + buf.WriteByte(0) + return uint32(len(data.SubChunks)), buf.Bytes() +} + +func (p *Player) translateClientPacketForTransfer(pk packet.Packet) (modified bool) { + if !p.IDModified { + return false + } + + switch pk := pk.(type) { + case *packet.MobEquipment: + pk.EntityRuntimeID = p.RuntimeId + return true + case *packet.InventoryTransaction: + if dat, ok := pk.TransactionData.(*protocol.UseItemOnEntityTransactionData); ok { + switch dat.TargetEntityRuntimeID { + case math.MaxInt64: + dat.TargetEntityRuntimeID = p.ClientRuntimeId + return true + case p.ClientRuntimeId: + dat.TargetEntityRuntimeID = p.RuntimeId + return true + } + } + case *packet.Respawn: + pk.EntityRuntimeID = p.RuntimeId + return true + case *packet.Animate: + pk.EntityRuntimeID = p.RuntimeId + return true + case *packet.MovePlayer: + pk.EntityRuntimeID = p.RuntimeId + return true + case *packet.Interact: + switch pk.TargetEntityRuntimeID { + case math.MaxInt64: + pk.TargetEntityRuntimeID = p.ClientRuntimeId + return true + case p.ClientRuntimeId: + pk.TargetEntityRuntimeID = p.RuntimeId + return true + } + case *packet.PlayerAction: + pk.EntityRuntimeID = p.RuntimeId + return true + case *packet.ContainerOpen: + if pk.ContainerEntityUniqueID == math.MaxInt64 { + pk.ContainerEntityUniqueID = p.ClientUniqueId + return true + } + } + return false +} + +func (p *Player) translateServerPacketForTransfer(pk packet.Packet) (cancel bool, modified bool) { + if !p.IDModified { + return false, false + } + + switch pk := pk.(type) { + case *packet.Animate: + if pk.EntityRuntimeID == p.RuntimeId { + pk.EntityRuntimeID = p.ClientRuntimeId + return false, true + } + case *packet.ActorEvent: + if pk.EntityRuntimeID != p.RuntimeId && pk.EntityRuntimeID == p.ClientRuntimeId { + pk.EntityRuntimeID = math.MaxInt64 + return false, true + } + if pk.EntityRuntimeID == p.RuntimeId { + pk.EntityRuntimeID = p.ClientRuntimeId + return false, true + } + case *packet.AddPlayer: + if pk.EntityRuntimeID == p.RuntimeId { + return true, false + } + if pk.EntityRuntimeID == p.ClientRuntimeId { + pk.EntityRuntimeID = math.MaxInt64 + return false, true + } + case *packet.AddActor: + if pk.EntityRuntimeID == p.RuntimeId { + return true, false + } + if pk.EntityRuntimeID == p.ClientRuntimeId { + pk.EntityRuntimeID = math.MaxInt64 + return false, true + } + case *packet.MoveActorAbsolute: + if pk.EntityRuntimeID != p.RuntimeId && pk.EntityRuntimeID == p.ClientRuntimeId { + pk.EntityRuntimeID = math.MaxInt64 + return false, true + } + if pk.EntityRuntimeID == p.RuntimeId { + pk.EntityRuntimeID = p.ClientRuntimeId + return false, true + } + case *packet.MovePlayer: + if pk.EntityRuntimeID != p.RuntimeId && pk.EntityRuntimeID == p.ClientRuntimeId { + pk.EntityRuntimeID = math.MaxInt64 + return false, true + } + if pk.EntityRuntimeID == p.RuntimeId { + pk.EntityRuntimeID = p.ClientRuntimeId + return false, true + } + case *packet.SetActorData: + if pk.EntityRuntimeID != p.RuntimeId && pk.EntityRuntimeID == p.ClientRuntimeId { + pk.EntityRuntimeID = math.MaxInt64 + return false, true + } + if pk.EntityRuntimeID == p.RuntimeId { + pk.EntityRuntimeID = p.ClientRuntimeId + return false, true + } + case *packet.UpdateAttributes: + if pk.EntityRuntimeID != p.RuntimeId && pk.EntityRuntimeID == p.ClientRuntimeId { + pk.EntityRuntimeID = math.MaxInt64 + return false, true + } + if pk.EntityRuntimeID == p.RuntimeId { + pk.EntityRuntimeID = p.ClientRuntimeId + return false, true + } + case *packet.SetActorMotion: + if pk.EntityRuntimeID != p.RuntimeId && pk.EntityRuntimeID == p.ClientRuntimeId { + pk.EntityRuntimeID = math.MaxInt64 + return false, true + } + if pk.EntityRuntimeID == p.RuntimeId { + pk.EntityRuntimeID = p.ClientRuntimeId + return false, true + } + case *packet.MobEffect: + if pk.EntityRuntimeID != p.RuntimeId && pk.EntityRuntimeID == p.ClientRuntimeId { + pk.EntityRuntimeID = math.MaxInt64 + return false, true + } + if pk.EntityRuntimeID == p.RuntimeId { + pk.EntityRuntimeID = p.ClientRuntimeId + return false, true + } + case *packet.Respawn: + if pk.EntityRuntimeID == p.RuntimeId { + pk.EntityRuntimeID = p.ClientRuntimeId + return false, true + } + case *packet.AddItemActor: + if pk.EntityRuntimeID == p.ClientRuntimeId { + pk.EntityRuntimeID = math.MaxInt64 + return false, true + } + case *packet.MobEquipment: + if pk.EntityRuntimeID != p.RuntimeId && pk.EntityRuntimeID == p.ClientRuntimeId { + pk.EntityRuntimeID = math.MaxInt64 + return false, true + } + if pk.EntityRuntimeID == p.RuntimeId { + pk.EntityRuntimeID = p.ClientRuntimeId + return false, true + } + case *packet.MobArmourEquipment: + if pk.EntityRuntimeID != p.RuntimeId && pk.EntityRuntimeID == p.ClientRuntimeId { + pk.EntityRuntimeID = math.MaxInt64 + return false, true + } + if pk.EntityRuntimeID == p.RuntimeId { + pk.EntityRuntimeID = p.ClientRuntimeId + return false, true + } + case *packet.RemoveActor: + if pk.EntityUniqueID == p.ClientUniqueId { + pk.EntityUniqueID = math.MaxInt64 + return false, true + } + case *packet.ContainerOpen: + if pk.ContainerEntityUniqueID == p.ClientUniqueId { + pk.ContainerEntityUniqueID = math.MaxInt64 + return false, true + } + } + return false, false +} diff --git a/player/world.go b/player/world.go index 9e2e16ed..b65c31af 100644 --- a/player/world.go +++ b/player/world.go @@ -58,6 +58,8 @@ type WorldUpdaterComponent interface { Tick() // Flush flushes the world updater component. Flush() + // ResetForTransfer resets temporary/pending state that may become stale after a fast transfer. + ResetForTransfer() } func (p *Player) SetWorldUpdater(c WorldUpdaterComponent) { diff --git a/spectrum.go b/spectrum.go deleted file mode 100644 index dc0f9b5f..00000000 --- a/spectrum.go +++ /dev/null @@ -1,159 +0,0 @@ -package oomph - -import ( - "log/slog" - "os" - "sync/atomic" - "time" - - "github.com/cooldogedev/spectrum/session" - "github.com/oomph-ac/oomph/player" - "github.com/oomph-ac/oomph/player/component" - "github.com/oomph-ac/oomph/player/context" - "github.com/oomph-ac/oomph/player/detection" - "github.com/sandertv/gophertunnel/minecraft" - "github.com/sandertv/gophertunnel/minecraft/protocol/login" - "github.com/sandertv/gophertunnel/minecraft/protocol/packet" -) - -var _ session.Processor = &Processor{} - -type Processor struct { - session.NopProcessor - - identity login.IdentityData - registry *session.Registry - pl atomic.Pointer[player.Player] - transferring atomic.Bool - - dbgTransfer bool -} - -func NewProcessor( - s *session.Session, - registry *session.Registry, - listener *minecraft.Listener, - log *slog.Logger, -) *Processor { - pl := player.New(log, player.MonitoringState{ - IsReplay: false, - IsRecording: false, - CurrentTime: time.Now(), - }, listener) - pl.SetConn(s.Client()) - - component.Register(pl) - detection.Register(pl) - - go pl.StartTicking() - p := &Processor{identity: s.Client().IdentityData(), registry: registry} - p.pl.Store(pl) - return p -} - -func (p *Processor) ProcessStartGame(ctx *session.Context, gd *minecraft.GameData) { - //gd.PlayerMovementSettings.MovementType = protocol.PlayerMovementModeServerWithRewind - gd.PlayerMovementSettings.RewindHistorySize = 100 -} - -func (p *Processor) ProcessServer(ctx *session.Context, pk *packet.Packet) { - pl := p.pl.Load() - if pl == nil || p.transferring.Load() { - return - } - - pkCtx := context.NewHandlePacketContext(pk) - pl.HandleServerPacket(pkCtx) - - if pkCtx.Cancelled() { - ctx.Cancel() - return - } -} - -func (p *Processor) ProcessClient(ctx *session.Context, pk *packet.Packet) { - if os.Getenv("DBG") != "" { - if txt, ok := (*pk).(*packet.Text); ok && txt.Message == "transferme" { - if !p.dbgTransfer { - p.registry.GetSession(p.identity.XUID).Transfer("127.0.0.1:20002") - p.dbgTransfer = true - } else { - p.registry.GetSession(p.identity.XUID).Transfer("127.0.0.1:20000") - p.dbgTransfer = false - } - } - } - - pl := p.pl.Load() - if pl == nil || pl.Conn() == nil || p.transferring.Load() { - return - } - - pkCtx := context.NewHandlePacketContext(pk) - pl.HandleClientPacket(pkCtx) - - if pkCtx.Cancelled() { - ctx.Cancel() - return - } -} - -func (p *Processor) ProcessFlush(ctx *session.Context) { - pl := p.pl.Load() - if pl == nil || p.transferring.Load() { - return - } - - pl.PauseProcessing() - defer pl.ResumeProcessing() - - // We want Oomph to flush the connection whilst processing is stopped to prevent it from handling other packets before the connection - // is actually flushed. - ctx.Cancel() - - if acks := pl.ACKs(); acks != nil { - acks.Flush() - if err := pl.Conn().Flush(); err != nil { - pl.Log().Error("error flushing client connection", "error", err) - } - } -} - -func (p *Processor) ProcessPreTransfer(*session.Context, *string, *string) { - p.transferring.Store(true) - if pl := p.pl.Load(); pl != nil { - pl.PauseProcessing() - } -} - -func (p *Processor) ProcessPostTransfer(_ *session.Context, _ *string, _ *string) { - p.transferring.Store(false) - if s, pl := p.registry.GetSession(p.identity.XUID), p.pl.Load(); s != nil && pl != nil { - pl.SetServerConn(s.Server()) - // Remove save-the-world state from the player's world, we can start removing chunks once this ACK is processed. - if w := pl.World(); w != nil { - w.SetSTWTicks(300) // 15 seconds - } - pl.Effects().RemoveAll() - pl.ACKs().Invalidate() // Invalidate all pending UpdateBlock acknowledgments. - pl.ResumeProcessing() - } -} - -func (p *Processor) ProcessTransferFailure(_ *session.Context, origin *string, target *string) { - p.transferring.Store(false) - if s, pl := p.registry.GetSession(p.identity.XUID), p.pl.Load(); s != nil && pl != nil { - pl.ResumeProcessing() - } -} - -func (p *Processor) ProcessDisconnection(_ *session.Context, _ *string) { - if pl := p.pl.Load(); pl != nil { - _ = pl.Close() - p.pl.Store(nil) - } -} - -func (p *Processor) Player() *player.Player { - return p.pl.Load() -} diff --git a/world/world.go b/world/world.go index dd1f7721..41fc9ac4 100644 --- a/world/world.go +++ b/world/world.go @@ -161,6 +161,15 @@ func (w *World) PurgeChunks() { } } +// ChunkPositions returns a snapshot of all chunk positions currently tracked in this world. +func (w *World) ChunkPositions() []protocol.ChunkPos { + positions := make([]protocol.ChunkPos, 0, len(w.chunks)) + for chunkPos := range w.chunks { + positions = append(positions, chunkPos) + } + return positions +} + func (w *World) removeChunk(info ChunkInfo, chunkPos protocol.ChunkPos) { if info.Cached { unsubC(info.Hash)