add player package with key client-side player functionality, update protocol codecs, and refactor metadata definitions and slot usage
This commit is contained in:
4
go.mod
4
go.mod
@ -7,6 +7,10 @@ toolchain go1.24.4
|
|||||||
require (
|
require (
|
||||||
github.com/Tnze/go-mc v1.20.3-0.20241224032005-539b4a3a7f03
|
github.com/Tnze/go-mc v1.20.3-0.20241224032005-539b4a3a7f03
|
||||||
github.com/google/uuid v1.6.0
|
github.com/google/uuid v1.6.0
|
||||||
|
golang.org/x/exp v0.0.0-20250606033433-dcc06ee1d476
|
||||||
|
golang.org/x/sync v0.16.0
|
||||||
)
|
)
|
||||||
|
|
||||||
|
require github.com/go-gl/mathgl v1.2.0
|
||||||
|
|
||||||
replace github.com/Tnze/go-mc v1.20.3-0.20241224032005-539b4a3a7f03 => git.konjactw.dev/patyhank/go-mc v1.20.3-0.20250619063151-133e3fab4ac2
|
replace github.com/Tnze/go-mc v1.20.3-0.20241224032005-539b4a3a7f03 => git.konjactw.dev/patyhank/go-mc v1.20.3-0.20250619063151-133e3fab4ac2
|
||||||
|
18
go.sum
18
go.sum
@ -1,16 +1,10 @@
|
|||||||
git.konjactw.dev/patyhank/go-mc v1.20.3-0.20250619063151-133e3fab4ac2 h1:KiA1OsQQGjrKxev45URJPwvyuVwen9Bb4TzjEg/ojz8=
|
git.konjactw.dev/patyhank/go-mc v1.20.3-0.20250619063151-133e3fab4ac2 h1:KiA1OsQQGjrKxev45URJPwvyuVwen9Bb4TzjEg/ojz8=
|
||||||
git.konjactw.dev/patyhank/go-mc v1.20.3-0.20250619063151-133e3fab4ac2/go.mod h1:e3pBU8tqRfYDHrhtZRtyfGdYijA86b1fF3XgnEDSgHk=
|
git.konjactw.dev/patyhank/go-mc v1.20.3-0.20250619063151-133e3fab4ac2/go.mod h1:e3pBU8tqRfYDHrhtZRtyfGdYijA86b1fF3XgnEDSgHk=
|
||||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
github.com/go-gl/mathgl v1.2.0 h1:v2eOj/y1B2afDxF6URV1qCYmo1KW08lAMtTbOn3KXCY=
|
||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/go-gl/mathgl v1.2.0/go.mod h1:pf9+b5J3LFP7iZ4XXaVzZrCle0Q/vNpB/vDe5+3ulRE=
|
||||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
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/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
github.com/mlange-42/ark v0.4.3 h1:k5MydLdgONyOaHT0mfTJNW68kkHz24hkMnmzog6SOjw=
|
golang.org/x/exp v0.0.0-20250606033433-dcc06ee1d476 h1:bsqhLWFR6G6xiQcb+JoGqdKdRU6WzPWmK8E0jxTjzo4=
|
||||||
github.com/mlange-42/ark v0.4.3/go.mod h1:47KXHr5HLftLn4iyL8w04iv7KJUNUoDymEIotD41f3o=
|
golang.org/x/exp v0.0.0-20250606033433-dcc06ee1d476/go.mod h1:3//PLf8L/X+8b4vuAfHzxeRUl04Adcb341+IGKfnqS8=
|
||||||
github.com/mlange-42/ark-tools v0.1.4 h1:GD+CYe+jx7b/HQCgdHNym04IwxsAfTdPgKskoM3Mwpw=
|
golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw=
|
||||||
github.com/mlange-42/ark-tools v0.1.4/go.mod h1:+uS2tMZrMXRtPfRRN0S6F/ou7XbivqcFV/Lc214XRCQ=
|
golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
|
||||||
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/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
|
|
||||||
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
|
||||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
|
||||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
|
||||||
|
381
pkg/auth/auth.go
Normal file
381
pkg/auth/auth.go
Normal file
@ -0,0 +1,381 @@
|
|||||||
|
package auth
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"context"
|
||||||
|
"crypto/aes"
|
||||||
|
"crypto/cipher"
|
||||||
|
"crypto/md5"
|
||||||
|
"crypto/rand"
|
||||||
|
"crypto/rsa"
|
||||||
|
"crypto/sha1"
|
||||||
|
"crypto/x509"
|
||||||
|
"encoding/hex"
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"git.konjactw.dev/patyhank/minego/pkg/protocol/packet/login/client"
|
||||||
|
"git.konjactw.dev/patyhank/minego/pkg/protocol/packet/login/server"
|
||||||
|
"github.com/Tnze/go-mc/chat"
|
||||||
|
"github.com/Tnze/go-mc/data/packetid"
|
||||||
|
"github.com/Tnze/go-mc/net"
|
||||||
|
"github.com/Tnze/go-mc/net/CFB8"
|
||||||
|
pk "github.com/Tnze/go-mc/net/packet"
|
||||||
|
"github.com/google/uuid"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
ErrLogin = errors.New("login")
|
||||||
|
ErrKick = errors.New("login.kicked")
|
||||||
|
ErrEncrypt = errors.New("login.encrypt")
|
||||||
|
)
|
||||||
|
|
||||||
|
type Profile struct {
|
||||||
|
Name string
|
||||||
|
UUID uuid.UUID
|
||||||
|
}
|
||||||
|
|
||||||
|
type Provider interface {
|
||||||
|
Authenticate(ctx context.Context, conn *net.Conn, content client.LoginHello) error
|
||||||
|
FetchProfile(ctx context.Context) *Profile
|
||||||
|
}
|
||||||
|
|
||||||
|
type Auth struct {
|
||||||
|
*net.Conn
|
||||||
|
Provider
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *Auth) HandleLogin(ctx context.Context) error {
|
||||||
|
profile := a.FetchProfile(ctx)
|
||||||
|
|
||||||
|
err := a.WritePacket(pk.Marshal(packetid.ServerboundLoginHello, server.LoginHello{
|
||||||
|
Name: profile.Name,
|
||||||
|
UUID: profile.UUID,
|
||||||
|
}))
|
||||||
|
if err != nil {
|
||||||
|
return errors.Join(ErrLogin, fmt.Errorf("write login hello fail: %w", err))
|
||||||
|
}
|
||||||
|
|
||||||
|
var p pk.Packet
|
||||||
|
for {
|
||||||
|
err = a.ReadPacket(&p)
|
||||||
|
if err != nil {
|
||||||
|
return errors.Join(ErrLogin, fmt.Errorf("read packet fail: %w", err))
|
||||||
|
}
|
||||||
|
|
||||||
|
switch packetid.ClientboundPacketID(p.ID) {
|
||||||
|
case packetid.ClientboundLoginLoginDisconnect:
|
||||||
|
var reason chat.JsonMessage
|
||||||
|
err = p.Scan(&reason)
|
||||||
|
|
||||||
|
return errors.Join(ErrKick, fmt.Errorf("kicked by server: %s", chat.Message(reason).ClearString()))
|
||||||
|
case packetid.ClientboundLoginHello:
|
||||||
|
var hello client.LoginHello
|
||||||
|
err = p.Scan(&hello)
|
||||||
|
if err != nil {
|
||||||
|
return errors.Join(ErrLogin, fmt.Errorf("read login hello fail: %w", err))
|
||||||
|
}
|
||||||
|
|
||||||
|
err = a.Authenticate(ctx, a.Conn, hello)
|
||||||
|
if err != nil {
|
||||||
|
return errors.Join(ErrLogin, fmt.Errorf("authenticate fail: %w", err))
|
||||||
|
}
|
||||||
|
case packetid.ClientboundLoginLoginFinished:
|
||||||
|
err = a.WritePacket(pk.Marshal(packetid.ServerboundLoginLoginAcknowledged))
|
||||||
|
if err != nil {
|
||||||
|
return errors.Join(ErrLogin, fmt.Errorf("write login ack fail: %w", err))
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
case packetid.ClientboundLoginLoginCompression:
|
||||||
|
var threshold int32
|
||||||
|
|
||||||
|
err = p.Scan((*pk.VarInt)(&threshold))
|
||||||
|
if err != nil {
|
||||||
|
return errors.Join(ErrLogin, fmt.Errorf("read login compression fail: %w", err))
|
||||||
|
}
|
||||||
|
a.Conn.SetThreshold(int(threshold))
|
||||||
|
case packetid.ClientboundLoginCustomQuery:
|
||||||
|
var query client.LoginCustomQuery
|
||||||
|
|
||||||
|
err = p.Scan(&query)
|
||||||
|
if err != nil {
|
||||||
|
return errors.Join(ErrLogin, fmt.Errorf("read login custom query fail: %w", err))
|
||||||
|
}
|
||||||
|
|
||||||
|
err = a.WritePacket(pk.Marshal(
|
||||||
|
packetid.ServerboundLoginCustomQueryAnswer,
|
||||||
|
&server.LoginCustomQueryAnswer{MessageID: query.MessageID},
|
||||||
|
))
|
||||||
|
if err != nil {
|
||||||
|
return errors.Join(ErrLogin, fmt.Errorf("read login custom query fail: %w", err))
|
||||||
|
}
|
||||||
|
case packetid.ClientboundLoginCookieRequest:
|
||||||
|
var cookie client.LoginCookieRequest
|
||||||
|
err = p.Scan(&cookie)
|
||||||
|
if err != nil {
|
||||||
|
return errors.Join(ErrLogin, fmt.Errorf("read login cookie request fail: %w", err))
|
||||||
|
}
|
||||||
|
err = a.WritePacket(pk.Marshal(
|
||||||
|
packetid.ServerboundLoginCookieResponse,
|
||||||
|
&server.LoginCookieResponse{Key: cookie.Key},
|
||||||
|
))
|
||||||
|
if err != nil {
|
||||||
|
return errors.Join(ErrLogin, fmt.Errorf("read login cookie request fail: %w", err))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type OnlineAuthServer struct {
|
||||||
|
SessionServer string
|
||||||
|
AuthServer string
|
||||||
|
}
|
||||||
|
|
||||||
|
type OnlineAuth struct {
|
||||||
|
AccessToken string
|
||||||
|
Profile Profile
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *OnlineAuth) Authenticate(ctx context.Context, conn *net.Conn, content client.LoginHello) error {
|
||||||
|
key, encodeStream, decodeStream := newSymmetricEncryption()
|
||||||
|
|
||||||
|
err := o.LoginAuth(ctx, content, key)
|
||||||
|
if err != nil {
|
||||||
|
return errors.Join(ErrEncrypt, fmt.Errorf("login auth fail: %w", err))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Response with Encryption Key
|
||||||
|
var pkt pk.Packet
|
||||||
|
pkt, err = genEncryptionKeyResponse(key, content.PublicKey, content.VerifyToken)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("gen encryption key response fail: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = conn.WritePacket(pkt)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set Connection Encryption
|
||||||
|
conn.SetCipher(encodeStream, decodeStream)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func genEncryptionKeyResponse(shareSecret, publicKey, verifyToken []byte) (erp pk.Packet, err error) {
|
||||||
|
iPK, err := x509.ParsePKIXPublicKey(publicKey)
|
||||||
|
if err != nil {
|
||||||
|
err = fmt.Errorf("decode public key fail: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
rsaKey := iPK.(*rsa.PublicKey)
|
||||||
|
cryptPK, err := rsa.EncryptPKCS1v15(rand.Reader, rsaKey, shareSecret)
|
||||||
|
if err != nil {
|
||||||
|
err = fmt.Errorf("encryption share secret fail: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
verifyT, err := rsa.EncryptPKCS1v15(rand.Reader, rsaKey, verifyToken)
|
||||||
|
if err != nil {
|
||||||
|
err = fmt.Errorf("encryption verfy tokenfail: %v", err)
|
||||||
|
return erp, err
|
||||||
|
}
|
||||||
|
return pk.Marshal(
|
||||||
|
packetid.ServerboundLoginKey,
|
||||||
|
pk.ByteArray(cryptPK),
|
||||||
|
pk.ByteArray(verifyT),
|
||||||
|
), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *OnlineAuth) LoginAuth(ctx context.Context, content client.LoginHello, key []byte) error {
|
||||||
|
digest := authDigest(content.ServerID, key, content.PublicKey)
|
||||||
|
|
||||||
|
request, err := json.Marshal(struct {
|
||||||
|
AccessToken string `json:"accessToken"`
|
||||||
|
SelectedProfile string `json:"selectedProfile"`
|
||||||
|
ServerID string `json:"serverId"`
|
||||||
|
}{
|
||||||
|
AccessToken: o.AccessToken,
|
||||||
|
SelectedProfile: hex.EncodeToString(o.Profile.UUID[:]),
|
||||||
|
ServerID: digest,
|
||||||
|
})
|
||||||
|
|
||||||
|
PostRequest, err := http.NewRequestWithContext(ctx, http.MethodPost, "https://sessionserver.mojang.com/session/minecraft/join",
|
||||||
|
bytes.NewReader(request))
|
||||||
|
if err != nil {
|
||||||
|
return errors.Join(ErrEncrypt, fmt.Errorf("make request fail: %w", err))
|
||||||
|
}
|
||||||
|
PostRequest.Header.Set("User-agent", "go-mc")
|
||||||
|
PostRequest.Header.Set("Connection", "keep-alive")
|
||||||
|
PostRequest.Header.Set("Content-Type", "application/json")
|
||||||
|
resp, err := http.DefaultClient.Do(PostRequest)
|
||||||
|
if err != nil {
|
||||||
|
return errors.Join(ErrEncrypt, fmt.Errorf("session mojang fail: %w", err))
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
body, _ := io.ReadAll(resp.Body)
|
||||||
|
if resp.StatusCode != http.StatusNoContent {
|
||||||
|
return errors.Join(ErrEncrypt, fmt.Errorf("session join fail: %s", string(body)))
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func newSymmetricEncryption() (key []byte, encoStream, decoStream cipher.Stream) {
|
||||||
|
key = make([]byte, 16)
|
||||||
|
if _, err := rand.Read(key); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
b, err := aes.NewCipher(key)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
decoStream = CFB8.NewCFB8Decrypt(b, key)
|
||||||
|
encoStream = CFB8.NewCFB8Encrypt(b, key)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *OnlineAuth) FetchProfile(ctx context.Context) *Profile {
|
||||||
|
return &o.Profile
|
||||||
|
}
|
||||||
|
|
||||||
|
func authDigest(serverID string, sharedSecret, publicKey []byte) string {
|
||||||
|
h := sha1.New()
|
||||||
|
h.Write([]byte(serverID))
|
||||||
|
h.Write(sharedSecret)
|
||||||
|
h.Write(publicKey)
|
||||||
|
hash := h.Sum(nil)
|
||||||
|
|
||||||
|
// Check for negative hashes
|
||||||
|
negative := (hash[0] & 0x80) == 0x80
|
||||||
|
if negative {
|
||||||
|
hash = twosComplement(hash)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Trim away zeroes
|
||||||
|
res := strings.TrimLeft(hex.EncodeToString(hash), "0")
|
||||||
|
if negative {
|
||||||
|
res = "-" + res
|
||||||
|
}
|
||||||
|
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
|
// little endian
|
||||||
|
func twosComplement(p []byte) []byte {
|
||||||
|
carry := true
|
||||||
|
for i := len(p) - 1; i >= 0; i-- {
|
||||||
|
p[i] = ^p[i]
|
||||||
|
if carry {
|
||||||
|
carry = p[i] == 0xff
|
||||||
|
p[i]++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return p
|
||||||
|
}
|
||||||
|
|
||||||
|
type OfflineAuth struct {
|
||||||
|
Username string
|
||||||
|
}
|
||||||
|
|
||||||
|
// NameToUUID return the UUID from player name in offline mode
|
||||||
|
func NameToUUID(name string) uuid.UUID {
|
||||||
|
version := 3
|
||||||
|
h := md5.New()
|
||||||
|
h.Write([]byte("OfflinePlayer:"))
|
||||||
|
h.Write([]byte(name))
|
||||||
|
var id uuid.UUID
|
||||||
|
h.Sum(id[:0])
|
||||||
|
id[6] = (id[6] & 0x0f) | uint8((version&0xf)<<4)
|
||||||
|
id[8] = (id[8] & 0x3f) | 0x80 // RFC 4122 variant
|
||||||
|
return id
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *OfflineAuth) FetchProfile(ctx context.Context) *Profile {
|
||||||
|
return &Profile{
|
||||||
|
Name: o.Username,
|
||||||
|
UUID: NameToUUID(o.Username),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *OfflineAuth) Authenticate(ctx context.Context, conn *net.Conn, content client.LoginHello) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type KonjacAuth struct {
|
||||||
|
*OnlineAuth
|
||||||
|
UserCode string
|
||||||
|
Profile Profile
|
||||||
|
}
|
||||||
|
|
||||||
|
func (k *KonjacAuth) LoginAuth(ctx context.Context, content client.LoginHello, key []byte) error {
|
||||||
|
digest := authDigest(content.ServerID, key, content.PublicKey)
|
||||||
|
|
||||||
|
request, err := json.Marshal(struct {
|
||||||
|
AccessToken string `json:"accessToken"`
|
||||||
|
SelectedProfile string `json:"selectedProfile"`
|
||||||
|
ServerID string `json:"serverId"`
|
||||||
|
}{
|
||||||
|
AccessToken: k.UserCode,
|
||||||
|
SelectedProfile: "",
|
||||||
|
ServerID: digest,
|
||||||
|
})
|
||||||
|
|
||||||
|
PostRequest, err := http.NewRequestWithContext(ctx, http.MethodPost, "http://127.0.0.1:37565/ss/session/minecraft/join",
|
||||||
|
bytes.NewReader(request))
|
||||||
|
if err != nil {
|
||||||
|
return errors.Join(ErrEncrypt, fmt.Errorf("make request fail: %w", err))
|
||||||
|
}
|
||||||
|
PostRequest.Header.Set("Content-Type", "application/json")
|
||||||
|
resp, err := http.DefaultClient.Do(PostRequest)
|
||||||
|
if err != nil {
|
||||||
|
return errors.Join(ErrEncrypt, fmt.Errorf("session mojang fail: %w", err))
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
body, _ := io.ReadAll(resp.Body)
|
||||||
|
if resp.StatusCode != http.StatusNoContent {
|
||||||
|
return errors.Join(ErrEncrypt, fmt.Errorf("session join fail: %s", string(body)))
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (k *KonjacAuth) FetchProfile(ctx context.Context) *Profile {
|
||||||
|
data, err := json.Marshal(map[string]any{
|
||||||
|
"agent": map[string]any{
|
||||||
|
"name": "minego",
|
||||||
|
"version": 0,
|
||||||
|
},
|
||||||
|
"username": k.UserCode,
|
||||||
|
"password": "",
|
||||||
|
"clientToken": "",
|
||||||
|
"requestUser": "",
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
ctx, cancel := context.WithTimeout(ctx, 10*time.Second)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
req, err := http.NewRequestWithContext(ctx, http.MethodPost, "http://127.0.0.1:37565/as/authenticate", bytes.NewReader(data))
|
||||||
|
if err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
req.Header.Set("Content-Type", "application/json")
|
||||||
|
resp, err := http.DefaultClient.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
_, _ = io.ReadAll(resp.Body)
|
||||||
|
if resp.StatusCode != http.StatusNoContent {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
29
pkg/bot/client.go
Normal file
29
pkg/bot/client.go
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
package bot
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"git.konjactw.dev/patyhank/minego/pkg/auth"
|
||||||
|
"git.konjactw.dev/patyhank/minego/pkg/protocol/packet/game/server"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Client interface {
|
||||||
|
Connect(ctx context.Context, addr string, options *ConnectOptions) error
|
||||||
|
Close(ctx context.Context) error
|
||||||
|
IsConnected() bool
|
||||||
|
WritePacket(ctx context.Context, packet server.ServerboundPacket) error
|
||||||
|
|
||||||
|
PacketHandler() PacketHandler
|
||||||
|
EventHandler() EventHandler
|
||||||
|
World() World
|
||||||
|
Inventory() InventoryHandler
|
||||||
|
Player() Player
|
||||||
|
}
|
||||||
|
|
||||||
|
type ClientOptions struct {
|
||||||
|
AuthProvider auth.Provider
|
||||||
|
}
|
||||||
|
|
||||||
|
type ConnectOptions struct {
|
||||||
|
FakeHost string
|
||||||
|
}
|
20
pkg/bot/event.go
Normal file
20
pkg/bot/event.go
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
package bot
|
||||||
|
|
||||||
|
type EventHandler interface {
|
||||||
|
PublishEvent(event string, data any) error
|
||||||
|
SubscribeEvent(event string, handler func(data any) error)
|
||||||
|
}
|
||||||
|
|
||||||
|
type Event interface {
|
||||||
|
EventID() string
|
||||||
|
}
|
||||||
|
|
||||||
|
func PublishEvent(client Client, event Event) error {
|
||||||
|
return client.EventHandler().PublishEvent(event.EventID(), event)
|
||||||
|
}
|
||||||
|
|
||||||
|
func SubscribeEvent(client Client, event string, handler func(event Event) error) {
|
||||||
|
client.EventHandler().SubscribeEvent(event, func(data any) error {
|
||||||
|
return handler(data.(Event))
|
||||||
|
})
|
||||||
|
}
|
24
pkg/bot/handler.go
Normal file
24
pkg/bot/handler.go
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
package bot
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"git.konjactw.dev/patyhank/minego/pkg/protocol/packet/game/client"
|
||||||
|
"github.com/Tnze/go-mc/data/packetid"
|
||||||
|
)
|
||||||
|
|
||||||
|
type PacketHandler interface {
|
||||||
|
AddPacketHandler(id packetid.ClientboundPacketID, handler func(ctx context.Context, p client.ClientboundPacket))
|
||||||
|
AddGenericPacketHandler(handler func(ctx context.Context, p client.ClientboundPacket))
|
||||||
|
HandlePacket(ctx context.Context, p client.ClientboundPacket)
|
||||||
|
}
|
||||||
|
|
||||||
|
type HandlerFunc[T client.ClientboundPacket] func(ctx context.Context, p T)
|
||||||
|
|
||||||
|
func AddHandler[T client.ClientboundPacket](c Client, f HandlerFunc[T]) {
|
||||||
|
var t T
|
||||||
|
handler := c.PacketHandler()
|
||||||
|
handler.AddPacketHandler(t.PacketID(), func(ctx context.Context, p client.ClientboundPacket) {
|
||||||
|
f(ctx, p.(T))
|
||||||
|
})
|
||||||
|
}
|
23
pkg/bot/inventory.go
Normal file
23
pkg/bot/inventory.go
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
package bot
|
||||||
|
|
||||||
|
import (
|
||||||
|
"git.konjactw.dev/patyhank/minego/pkg/protocol/slot"
|
||||||
|
"github.com/Tnze/go-mc/level/item"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Container interface {
|
||||||
|
GetSlot(index int) slot.Slot
|
||||||
|
Slots() []slot.Slot
|
||||||
|
SlotCount() int
|
||||||
|
FindEmpty() int16
|
||||||
|
FindItem(itemID item.ID) int16
|
||||||
|
Click(slot int16, mode int32, button int32) error
|
||||||
|
}
|
||||||
|
|
||||||
|
type InventoryHandler interface {
|
||||||
|
Inventory() Container
|
||||||
|
Container() Container
|
||||||
|
CurrentContainerID() int32
|
||||||
|
Click(container int32, slot int16, mode int32, button int32) error
|
||||||
|
Close()
|
||||||
|
}
|
25
pkg/bot/player.go
Normal file
25
pkg/bot/player.go
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
package bot
|
||||||
|
|
||||||
|
import (
|
||||||
|
"git.konjactw.dev/patyhank/minego/pkg/protocol"
|
||||||
|
"github.com/go-gl/mathgl/mgl64"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Player interface {
|
||||||
|
StateID() int32
|
||||||
|
UpdateStateID(id int32)
|
||||||
|
Entity() Entity
|
||||||
|
|
||||||
|
FlyTo(pos mgl64.Vec3) error
|
||||||
|
WalkTo(pos mgl64.Vec3) error
|
||||||
|
LookAt(vec3 mgl64.Vec3) error
|
||||||
|
|
||||||
|
BreakBlock(pos protocol.Position) error
|
||||||
|
PlaceBlock(pos protocol.Position) error
|
||||||
|
PlaceBlockWithArgs(pos protocol.Position, face int32, cursor mgl64.Vec3) error
|
||||||
|
OpenContainer(pos protocol.Position) (Container, error)
|
||||||
|
|
||||||
|
UseItem(hand int8) error
|
||||||
|
|
||||||
|
OpenMenu(command string) (Container, error)
|
||||||
|
}
|
35
pkg/bot/world.go
Normal file
35
pkg/bot/world.go
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
package bot
|
||||||
|
|
||||||
|
import (
|
||||||
|
"git.konjactw.dev/patyhank/minego/pkg/protocol"
|
||||||
|
"git.konjactw.dev/patyhank/minego/pkg/protocol/metadata"
|
||||||
|
"git.konjactw.dev/patyhank/minego/pkg/protocol/slot"
|
||||||
|
"github.com/Tnze/go-mc/data/entity"
|
||||||
|
"github.com/Tnze/go-mc/level/block"
|
||||||
|
"github.com/go-gl/mathgl/mgl64"
|
||||||
|
"github.com/google/uuid"
|
||||||
|
)
|
||||||
|
|
||||||
|
type World interface {
|
||||||
|
GetBlock(pos protocol.Position) (block.Block, error)
|
||||||
|
SetBlock(pos protocol.Position, b block.Block) error
|
||||||
|
|
||||||
|
GetNearbyBlocks(pos protocol.Position, radius int32) ([]block.Block, error)
|
||||||
|
FindNearbyBlock(pos protocol.Position, radius int32, blk block.Block) (protocol.Position, error)
|
||||||
|
|
||||||
|
Entities() []Entity
|
||||||
|
GetEntity(id int32) Entity
|
||||||
|
GetNearbyEntities(radius int32) []Entity
|
||||||
|
GetEntitiesByType(entityType entity.ID) []Entity
|
||||||
|
}
|
||||||
|
|
||||||
|
type Entity interface {
|
||||||
|
ID() int32
|
||||||
|
UUID() uuid.UUID
|
||||||
|
Type() entity.ID
|
||||||
|
Position() mgl64.Vec3
|
||||||
|
Rotation() mgl64.Vec2
|
||||||
|
|
||||||
|
Metadata() map[uint8]metadata.Metadata
|
||||||
|
Equipment() map[int8]slot.Slot
|
||||||
|
}
|
180
pkg/client/client.go
Normal file
180
pkg/client/client.go
Normal file
@ -0,0 +1,180 @@
|
|||||||
|
package client
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"net"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"git.konjactw.dev/patyhank/minego/pkg/auth"
|
||||||
|
"git.konjactw.dev/patyhank/minego/pkg/bot"
|
||||||
|
"git.konjactw.dev/patyhank/minego/pkg/game/inventory"
|
||||||
|
"git.konjactw.dev/patyhank/minego/pkg/game/world"
|
||||||
|
"git.konjactw.dev/patyhank/minego/pkg/protocol/packet/game/client"
|
||||||
|
"git.konjactw.dev/patyhank/minego/pkg/protocol/packet/game/server"
|
||||||
|
"github.com/Tnze/go-mc/data/packetid"
|
||||||
|
mcnet "github.com/Tnze/go-mc/net"
|
||||||
|
pk "github.com/Tnze/go-mc/net/packet"
|
||||||
|
"golang.org/x/sync/errgroup"
|
||||||
|
)
|
||||||
|
|
||||||
|
type botClient struct {
|
||||||
|
conn *mcnet.Conn
|
||||||
|
packetHandler bot.PacketHandler
|
||||||
|
eventHandler bot.EventHandler
|
||||||
|
world bot.World
|
||||||
|
inventory *inventory.Manager
|
||||||
|
connected bool
|
||||||
|
authProvider auth.Provider
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *botClient) Close(ctx context.Context) error {
|
||||||
|
if err := b.conn.Close(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *botClient) IsConnected() bool {
|
||||||
|
return b.connected
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *botClient) WritePacket(ctx context.Context, packet server.ServerboundPacket) error {
|
||||||
|
err := b.conn.WritePacket(pk.Marshal(packet.PacketID(), packet))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *botClient) PacketHandler() bot.PacketHandler {
|
||||||
|
return b.packetHandler
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *botClient) EventHandler() bot.EventHandler {
|
||||||
|
return b.eventHandler
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *botClient) World() bot.World {
|
||||||
|
return b.world
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *botClient) Inventory() bot.InventoryHandler {
|
||||||
|
return b.inventory
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *botClient) Connect(ctx context.Context, addr string, options *bot.ConnectOptions) error {
|
||||||
|
// 套用 go-mc 的連接邏輯
|
||||||
|
host, portStr, err := net.SplitHostPort(addr)
|
||||||
|
var port uint64
|
||||||
|
if err != nil {
|
||||||
|
var addrErr *net.AddrError
|
||||||
|
const missingPort = "missing port in address"
|
||||||
|
if errors.As(err, &addrErr) && addrErr.Err == missingPort {
|
||||||
|
host = addr
|
||||||
|
port = 25565
|
||||||
|
} else {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
port, err = strconv.ParseUint(portStr, 0, 16)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 建立連接
|
||||||
|
dialer := &mcnet.DefaultDialer
|
||||||
|
conn, err := dialer.DialMCContext(ctx, addr)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// 執行握手
|
||||||
|
if options != nil && options.FakeHost != "" {
|
||||||
|
host = options.FakeHost
|
||||||
|
}
|
||||||
|
|
||||||
|
err = b.handshake(conn, host, port)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = b.login()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = b.configuration()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
b.conn = conn
|
||||||
|
b.connected = true
|
||||||
|
|
||||||
|
// 啟動封包處理 goroutine
|
||||||
|
go b.handlePackets(ctx)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *botClient) handshake(conn *mcnet.Conn, host string, port uint64) error {
|
||||||
|
return conn.WritePacket(pk.Marshal(
|
||||||
|
0,
|
||||||
|
pk.VarInt(772),
|
||||||
|
pk.String(host),
|
||||||
|
pk.UnsignedShort(port),
|
||||||
|
pk.VarInt(2), // to game state
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *botClient) handlePackets(ctx context.Context) {
|
||||||
|
group, ctx := errgroup.WithContext(ctx)
|
||||||
|
group.SetLimit(15)
|
||||||
|
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
return
|
||||||
|
default:
|
||||||
|
var p pk.Packet
|
||||||
|
if err := b.conn.ReadPacket(&p); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
creator, ok := client.ClientboundPackets[packetid.ClientboundPacketID(p.ID)]
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
pkt := creator()
|
||||||
|
_, err := pkt.ReadFrom(bytes.NewReader(p.Data))
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
group.Go(func() error {
|
||||||
|
b.packetHandler.HandlePacket(ctx, pkt)
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewClient(options *bot.ClientOptions) bot.Client {
|
||||||
|
c := &botClient{
|
||||||
|
packetHandler: newPacketHandler(),
|
||||||
|
authProvider: options.AuthProvider,
|
||||||
|
}
|
||||||
|
|
||||||
|
if options.AuthProvider == nil {
|
||||||
|
c.authProvider = &auth.OfflineAuth{Username: "Steve"}
|
||||||
|
}
|
||||||
|
|
||||||
|
c.world = world.NewWorld(c)
|
||||||
|
c.eventHandler = NewEventHandler()
|
||||||
|
c.inventory = inventory.NewManager(c)
|
||||||
|
|
||||||
|
return c
|
||||||
|
}
|
74
pkg/client/connect.go
Normal file
74
pkg/client/connect.go
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
package client
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"git.konjactw.dev/patyhank/minego/pkg/auth"
|
||||||
|
"github.com/Tnze/go-mc/chat"
|
||||||
|
"github.com/Tnze/go-mc/data/packetid"
|
||||||
|
pk "github.com/Tnze/go-mc/net/packet"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (b *botClient) login() error {
|
||||||
|
a := &auth.Auth{
|
||||||
|
Conn: b.conn,
|
||||||
|
Provider: b.authProvider,
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx, cancelFunc := context.WithTimeout(context.Background(), 30*time.Second)
|
||||||
|
defer cancelFunc()
|
||||||
|
|
||||||
|
return a.HandleLogin(ctx)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *botClient) configuration() (err error) {
|
||||||
|
var p pk.Packet
|
||||||
|
for {
|
||||||
|
err = b.conn.ReadPacket(&p)
|
||||||
|
|
||||||
|
switch packetid.ClientboundPacketID(p.ID) {
|
||||||
|
case packetid.ClientboundConfigDisconnect:
|
||||||
|
var reason chat.Message
|
||||||
|
err = p.Scan(&reason)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return errors.New("kicked: " + reason.String())
|
||||||
|
case packetid.ClientboundConfigFinishConfiguration:
|
||||||
|
err = b.conn.WritePacket(pk.Marshal(
|
||||||
|
packetid.ServerboundConfigFinishConfiguration,
|
||||||
|
))
|
||||||
|
return err
|
||||||
|
case packetid.ClientboundConfigKeepAlive:
|
||||||
|
var keepAliveID pk.Long
|
||||||
|
err = p.Scan(&keepAliveID)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = b.conn.WritePacket(pk.Marshal(packetid.ServerboundConfigKeepAlive, keepAliveID))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
case packetid.ClientboundConfigPing:
|
||||||
|
var pingID pk.Int
|
||||||
|
err = p.Scan(&pingID)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = b.conn.WritePacket(pk.Marshal(packetid.ServerboundConfigPong, pingID))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
case packetid.ClientboundConfigSelectKnownPacks:
|
||||||
|
err = b.conn.WritePacket(pk.Marshal(packetid.ServerboundConfigSelectKnownPacks, pk.VarInt(0)))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
1
pkg/client/connection.go
Normal file
1
pkg/client/connection.go
Normal file
@ -0,0 +1 @@
|
|||||||
|
package client
|
70
pkg/client/event.go
Normal file
70
pkg/client/event.go
Normal file
@ -0,0 +1,70 @@
|
|||||||
|
package client
|
||||||
|
|
||||||
|
import (
|
||||||
|
"sync"
|
||||||
|
)
|
||||||
|
|
||||||
|
// EventHandler 是一個泛型事件總線
|
||||||
|
type EventHandler struct {
|
||||||
|
mu sync.RWMutex
|
||||||
|
handlers map[string][]func(event any) error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *EventHandler) PublishEvent(event string, data any) error {
|
||||||
|
e.mu.RLock()
|
||||||
|
defer e.mu.RUnlock()
|
||||||
|
if hs, ok := e.handlers[event]; ok {
|
||||||
|
for _, h := range hs {
|
||||||
|
if err := h(data); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *EventHandler) SubscribeEvent(event string, handler func(data any) error) {
|
||||||
|
e.mu.Lock()
|
||||||
|
defer e.mu.Unlock()
|
||||||
|
e.handlers[event] = append(e.handlers[event], handler)
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewEventHandler() *EventHandler {
|
||||||
|
return &EventHandler{
|
||||||
|
handlers: make(map[string][]func(any) error),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//func SubscribeEvent[T bot.Event[T]](eb *EventHandler, t T, f func(event T) error) {
|
||||||
|
// eb.mu.Lock()
|
||||||
|
// defer eb.mu.Unlock()
|
||||||
|
//
|
||||||
|
// eb.handlers[t.ID()] = append(eb.handlers[t.ID()], func(d any) error {
|
||||||
|
// t2 := d.(T)
|
||||||
|
// return f(t2)
|
||||||
|
// })
|
||||||
|
//}
|
||||||
|
//
|
||||||
|
//func PublishEvent[T bot.Event[T]](eb *EventHandler, t T) error {
|
||||||
|
// eb.mu.RLock()
|
||||||
|
// defer eb.mu.RUnlock()
|
||||||
|
// if hs, ok := eb.handlers[t.ID()]; ok {
|
||||||
|
// for _, h := range hs {
|
||||||
|
// if err := h(t); err != nil {
|
||||||
|
// return err
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// return nil
|
||||||
|
//}
|
||||||
|
//
|
||||||
|
//func GoPublishEvent[T bot.Event[T]](eb *EventHandler, t T) {
|
||||||
|
// eb.mu.RLock()
|
||||||
|
// defer eb.mu.RUnlock()
|
||||||
|
// if hs, ok := eb.handlers[t.ID()]; ok {
|
||||||
|
// for _, h := range hs {
|
||||||
|
// go h(t)
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
//}
|
42
pkg/client/handler.go
Normal file
42
pkg/client/handler.go
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
package client
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"git.konjactw.dev/patyhank/minego/pkg/bot"
|
||||||
|
"git.konjactw.dev/patyhank/minego/pkg/protocol/packet/game/client"
|
||||||
|
"github.com/Tnze/go-mc/data/packetid"
|
||||||
|
)
|
||||||
|
|
||||||
|
func newPacketHandler() bot.PacketHandler {
|
||||||
|
return &packetHandler{
|
||||||
|
handlerMap: make(map[packetid.ClientboundPacketID][]func(ctx context.Context, p client.ClientboundPacket)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type packetHandler struct {
|
||||||
|
handlerMap map[packetid.ClientboundPacketID][]func(ctx context.Context, p client.ClientboundPacket)
|
||||||
|
genericMap []func(ctx context.Context, p client.ClientboundPacket)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ph *packetHandler) AddPacketHandler(id packetid.ClientboundPacketID, handler func(ctx context.Context, p client.ClientboundPacket)) {
|
||||||
|
f := ph.handlerMap[id]
|
||||||
|
f = append(f, handler)
|
||||||
|
ph.handlerMap[id] = f
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ph *packetHandler) AddGenericPacketHandler(handler func(ctx context.Context, p client.ClientboundPacket)) {
|
||||||
|
ph.genericMap = append(ph.genericMap, handler)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ph *packetHandler) HandlePacket(ctx context.Context, p client.ClientboundPacket) {
|
||||||
|
f := ph.handlerMap[p.PacketID()]
|
||||||
|
if f != nil {
|
||||||
|
for _, handler := range f {
|
||||||
|
handler(ctx, p)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, handler := range ph.genericMap {
|
||||||
|
handler(ctx, p)
|
||||||
|
}
|
||||||
|
}
|
13
pkg/game/inventory/event.go
Normal file
13
pkg/game/inventory/event.go
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
package inventory
|
||||||
|
|
||||||
|
import "github.com/Tnze/go-mc/chat"
|
||||||
|
|
||||||
|
type ContainerOpenEvent struct {
|
||||||
|
WindowID int32
|
||||||
|
Type int32
|
||||||
|
Title chat.Message
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c ContainerOpenEvent) EventID() string {
|
||||||
|
return "inventory:container_open"
|
||||||
|
}
|
96
pkg/game/inventory/inventory.go
Normal file
96
pkg/game/inventory/inventory.go
Normal file
@ -0,0 +1,96 @@
|
|||||||
|
package inventory
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"git.konjactw.dev/patyhank/minego/pkg/bot"
|
||||||
|
"git.konjactw.dev/patyhank/minego/pkg/protocol/packet/game/server"
|
||||||
|
"git.konjactw.dev/patyhank/minego/pkg/protocol/slot"
|
||||||
|
"github.com/Tnze/go-mc/level/item"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Container 代表一個容器
|
||||||
|
type Container struct {
|
||||||
|
containerID int32
|
||||||
|
slots []slot.Slot
|
||||||
|
c bot.Client
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewContainer(c bot.Client, cID int32) *Container {
|
||||||
|
return &Container{
|
||||||
|
c: c,
|
||||||
|
containerID: cID,
|
||||||
|
slots: make([]slot.Slot, 0),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewContainerWithSize(c bot.Client, cID, size int32) *Container {
|
||||||
|
return &Container{
|
||||||
|
c: c,
|
||||||
|
containerID: cID,
|
||||||
|
slots: make([]slot.Slot, size),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Container) GetSlot(index int) slot.Slot {
|
||||||
|
if index < 0 || index >= len(c.slots) {
|
||||||
|
return slot.Slot{}
|
||||||
|
}
|
||||||
|
return c.slots[index]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Container) Slots() []slot.Slot {
|
||||||
|
return c.slots
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Container) SlotCount() int {
|
||||||
|
return len(c.slots)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Container) FindEmpty() int16 {
|
||||||
|
for i, s := range c.slots {
|
||||||
|
if s.Count <= 0 {
|
||||||
|
return int16(i)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Container) FindItem(itemID item.ID) int16 {
|
||||||
|
for i, s := range c.slots {
|
||||||
|
if s.ItemID == itemID && s.Count > 0 {
|
||||||
|
return int16(i)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Container) SetSlot(index int, s slot.Slot) {
|
||||||
|
// 自動擴容
|
||||||
|
for len(c.slots) <= index {
|
||||||
|
c.slots = append(c.slots, slot.Slot{})
|
||||||
|
}
|
||||||
|
if index >= 0 && index < len(c.slots) {
|
||||||
|
c.slots[index] = s
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Container) SetSlots(slots []slot.Slot) {
|
||||||
|
c.slots = make([]slot.Slot, len(slots))
|
||||||
|
copy(c.slots, slots)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Container) Clear() {
|
||||||
|
c.slots = make([]slot.Slot, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Container) Click(idx int16, mode int32, button int32) error {
|
||||||
|
clickPacket := &server.ContainerClick{
|
||||||
|
WindowID: c.containerID,
|
||||||
|
StateID: c.c.Player().StateID(),
|
||||||
|
Slot: idx,
|
||||||
|
Button: int8(button),
|
||||||
|
Mode: mode,
|
||||||
|
}
|
||||||
|
return c.c.WritePacket(context.Background(), clickPacket)
|
||||||
|
}
|
98
pkg/game/inventory/manager.go
Normal file
98
pkg/game/inventory/manager.go
Normal file
@ -0,0 +1,98 @@
|
|||||||
|
package inventory
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"git.konjactw.dev/patyhank/minego/pkg/bot"
|
||||||
|
"git.konjactw.dev/patyhank/minego/pkg/protocol/packet/game/client"
|
||||||
|
"git.konjactw.dev/patyhank/minego/pkg/protocol/packet/game/server"
|
||||||
|
"git.konjactw.dev/patyhank/minego/pkg/protocol/slot"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Manager 管理inventory和container
|
||||||
|
type Manager struct {
|
||||||
|
c bot.Client
|
||||||
|
inventory *Container
|
||||||
|
container *Container
|
||||||
|
cursor *slot.Slot
|
||||||
|
currentContainerID int32
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewManager(c bot.Client) *Manager {
|
||||||
|
m := &Manager{
|
||||||
|
c: c,
|
||||||
|
inventory: NewContainerWithSize(c, 0, 45),
|
||||||
|
currentContainerID: 0,
|
||||||
|
}
|
||||||
|
|
||||||
|
bot.AddHandler(c, func(ctx context.Context, p *client.SetContainerContent) {
|
||||||
|
if p.WindowID == 0 {
|
||||||
|
m.inventory.SetSlots(p.Slots)
|
||||||
|
} else if m.container != nil {
|
||||||
|
m.container.SetSlots(p.Slots)
|
||||||
|
}
|
||||||
|
m.c.Player().UpdateStateID(p.StateID)
|
||||||
|
})
|
||||||
|
bot.AddHandler(c, func(ctx context.Context, p *client.ContainerSetSlot) {
|
||||||
|
if p.ContainerID == 0 {
|
||||||
|
m.inventory.SetSlot(int(p.Slot), p.ItemStack)
|
||||||
|
} else if m.container != nil {
|
||||||
|
m.container.SetSlot(int(p.Slot), p.ItemStack)
|
||||||
|
}
|
||||||
|
m.c.Player().UpdateStateID(p.StateID)
|
||||||
|
})
|
||||||
|
bot.AddHandler(c, func(ctx context.Context, p *client.CloseContainer) {
|
||||||
|
if p.WindowID == m.currentContainerID {
|
||||||
|
m.currentContainerID = -1
|
||||||
|
if m.container != nil {
|
||||||
|
m.container = nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
bot.AddHandler(c, func(ctx context.Context, p *client.OpenScreen) {
|
||||||
|
m.currentContainerID = p.WindowID
|
||||||
|
m.container = NewContainer(c, p.WindowID)
|
||||||
|
go bot.PublishEvent(m.c, ContainerOpenEvent{
|
||||||
|
WindowID: p.WindowID,
|
||||||
|
Type: p.WindowType,
|
||||||
|
Title: p.WindowTitle,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
return m
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Manager) Inventory() bot.Container {
|
||||||
|
return m.inventory
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Manager) Container() bot.Container {
|
||||||
|
return m.container
|
||||||
|
}
|
||||||
|
func (m *Manager) Cursor() *slot.Slot {
|
||||||
|
return m.cursor
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Manager) CurrentContainerID() int32 {
|
||||||
|
return m.currentContainerID
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Manager) Close() {
|
||||||
|
if m.currentContainerID != -1 {
|
||||||
|
_ = m.c.WritePacket(context.Background(), &server.ContainerClose{WindowID: m.currentContainerID})
|
||||||
|
} else {
|
||||||
|
_ = m.c.WritePacket(context.Background(), &server.ContainerClose{WindowID: 0})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Click 點擊容器slot
|
||||||
|
func (m *Manager) Click(id int32, slotIndex int16, mode int32, button int32) error {
|
||||||
|
clickPacket := &server.ContainerClick{
|
||||||
|
WindowID: id,
|
||||||
|
StateID: m.c.Player().StateID(),
|
||||||
|
Slot: slotIndex,
|
||||||
|
Button: int8(button),
|
||||||
|
Mode: mode,
|
||||||
|
}
|
||||||
|
return m.c.WritePacket(context.Background(), clickPacket)
|
||||||
|
}
|
11
pkg/game/player/event.go
Normal file
11
pkg/game/player/event.go
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
package player
|
||||||
|
|
||||||
|
import "github.com/Tnze/go-mc/chat"
|
||||||
|
|
||||||
|
type MessageEvent struct {
|
||||||
|
Message chat.Message
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m MessageEvent) EventID() string {
|
||||||
|
return "player:message"
|
||||||
|
}
|
188
pkg/game/player/pathfinding.go
Normal file
188
pkg/game/player/pathfinding.go
Normal file
@ -0,0 +1,188 @@
|
|||||||
|
package player
|
||||||
|
|
||||||
|
import (
|
||||||
|
"container/heap"
|
||||||
|
"math"
|
||||||
|
|
||||||
|
"git.konjactw.dev/patyhank/minego/pkg/bot"
|
||||||
|
"git.konjactw.dev/patyhank/minego/pkg/protocol"
|
||||||
|
"github.com/Tnze/go-mc/level/block"
|
||||||
|
"github.com/go-gl/mathgl/mgl64"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Node 表示 A* 演算法中的節點
|
||||||
|
type Node struct {
|
||||||
|
Position protocol.Position
|
||||||
|
G float64 // 從起點到當前節點的實際距離
|
||||||
|
H float64 // 從當前節點到終點的啟發式距離
|
||||||
|
F float64 // G + H
|
||||||
|
Parent *Node
|
||||||
|
Index int // heap 索引
|
||||||
|
}
|
||||||
|
|
||||||
|
// NodeHeap 實現 heap.Interface 用於優先佇列
|
||||||
|
type NodeHeap []*Node
|
||||||
|
|
||||||
|
func (h NodeHeap) Len() int { return len(h) }
|
||||||
|
func (h NodeHeap) Less(i, j int) bool { return h[i].F < h[j].F }
|
||||||
|
func (h NodeHeap) Swap(i, j int) {
|
||||||
|
h[i], h[j] = h[j], h[i]
|
||||||
|
h[i].Index = i
|
||||||
|
h[j].Index = j
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *NodeHeap) Push(x interface{}) {
|
||||||
|
n := len(*h)
|
||||||
|
node := x.(*Node)
|
||||||
|
node.Index = n
|
||||||
|
*h = append(*h, node)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *NodeHeap) Pop() interface{} {
|
||||||
|
old := *h
|
||||||
|
n := len(old)
|
||||||
|
node := old[n-1]
|
||||||
|
node.Index = -1
|
||||||
|
*h = old[0 : n-1]
|
||||||
|
return node
|
||||||
|
}
|
||||||
|
|
||||||
|
// AStar 使用 A* 演算法尋找路徑
|
||||||
|
func AStar(world bot.World, start, goal mgl64.Vec3) ([]mgl64.Vec3, error) {
|
||||||
|
startPos := protocol.Position{int32(start.X()), int32(start.Y()), int32(start.Z())}
|
||||||
|
goalPos := protocol.Position{int32(goal.X()), int32(goal.Y()), int32(goal.Z())}
|
||||||
|
|
||||||
|
openSet := &NodeHeap{}
|
||||||
|
heap.Init(openSet)
|
||||||
|
|
||||||
|
closedSet := make(map[protocol.Position]bool)
|
||||||
|
allNodes := make(map[protocol.Position]*Node)
|
||||||
|
|
||||||
|
startNode := &Node{
|
||||||
|
Position: startPos,
|
||||||
|
G: 0,
|
||||||
|
H: heuristic(startPos, goalPos),
|
||||||
|
}
|
||||||
|
startNode.F = startNode.G + startNode.H
|
||||||
|
|
||||||
|
heap.Push(openSet, startNode)
|
||||||
|
allNodes[startPos] = startNode
|
||||||
|
|
||||||
|
for openSet.Len() > 0 {
|
||||||
|
current := heap.Pop(openSet).(*Node)
|
||||||
|
|
||||||
|
if current.Position == goalPos {
|
||||||
|
return reconstructPath(current), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
closedSet[current.Position] = true
|
||||||
|
|
||||||
|
// 檢查相鄰節點
|
||||||
|
for _, neighbor := range getNeighbors(current.Position) {
|
||||||
|
if closedSet[neighbor] {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// 檢查是否可通行
|
||||||
|
if !isWalkable(world, neighbor) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
tentativeG := current.G + distance(current.Position, neighbor)
|
||||||
|
|
||||||
|
neighborNode, exists := allNodes[neighbor]
|
||||||
|
if !exists {
|
||||||
|
neighborNode = &Node{
|
||||||
|
Position: neighbor,
|
||||||
|
G: math.Inf(1),
|
||||||
|
H: heuristic(neighbor, goalPos),
|
||||||
|
}
|
||||||
|
allNodes[neighbor] = neighborNode
|
||||||
|
}
|
||||||
|
|
||||||
|
if tentativeG < neighborNode.G {
|
||||||
|
neighborNode.Parent = current
|
||||||
|
neighborNode.G = tentativeG
|
||||||
|
neighborNode.F = neighborNode.G + neighborNode.H
|
||||||
|
|
||||||
|
if neighborNode.Index == -1 {
|
||||||
|
heap.Push(openSet, neighborNode)
|
||||||
|
} else {
|
||||||
|
heap.Fix(openSet, neighborNode.Index)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, nil // 找不到路徑
|
||||||
|
}
|
||||||
|
|
||||||
|
// heuristic 計算啟發式距離(曼哈頓距離)
|
||||||
|
func heuristic(a, b protocol.Position) float64 {
|
||||||
|
return math.Abs(float64(a[0]-b[0])) + math.Abs(float64(a[1]-b[1])) + math.Abs(float64(a[2]-b[2]))
|
||||||
|
}
|
||||||
|
|
||||||
|
// distance 計算兩點間的實際距離
|
||||||
|
func distance(a, b protocol.Position) float64 {
|
||||||
|
dx := float64(a[0] - b[0])
|
||||||
|
dy := float64(a[1] - b[1])
|
||||||
|
dz := float64(a[2] - b[2])
|
||||||
|
return math.Sqrt(dx*dx + dy*dy + dz*dz)
|
||||||
|
}
|
||||||
|
|
||||||
|
// getNeighbors 獲取相鄰節點
|
||||||
|
func getNeighbors(pos protocol.Position) []protocol.Position {
|
||||||
|
neighbors := []protocol.Position{
|
||||||
|
{pos[0] + 1, pos[1], pos[2]}, // 東
|
||||||
|
{pos[0] - 1, pos[1], pos[2]}, // 西
|
||||||
|
{pos[0], pos[1], pos[2] + 1}, // 南
|
||||||
|
{pos[0], pos[1], pos[2] - 1}, // 北
|
||||||
|
{pos[0], pos[1] + 1, pos[2]}, // 上
|
||||||
|
{pos[0], pos[1] - 1, pos[2]}, // 下
|
||||||
|
}
|
||||||
|
return neighbors
|
||||||
|
}
|
||||||
|
|
||||||
|
// isWalkable 檢查位置是否可通行
|
||||||
|
func isWalkable(world bot.World, pos protocol.Position) bool {
|
||||||
|
// 檢查腳部位置
|
||||||
|
footBlock, err := world.GetBlock(pos)
|
||||||
|
if err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// 檢查頭部位置
|
||||||
|
headPos := protocol.Position{pos[0], pos[1] + 1, pos[2]}
|
||||||
|
headBlock, err := world.GetBlock(headPos)
|
||||||
|
if err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// 檢查地面位置
|
||||||
|
groundPos := protocol.Position{pos[0], pos[1] - 1, pos[2]}
|
||||||
|
groundBlock, err := world.GetBlock(groundPos)
|
||||||
|
if err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// 腳部和頭部必須是空氣,地面必須是固體方塊
|
||||||
|
return footBlock == block.Air{} && headBlock == block.Air{} && groundBlock != block.Air{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// reconstructPath 重建路徑
|
||||||
|
func reconstructPath(node *Node) []mgl64.Vec3 {
|
||||||
|
var path []mgl64.Vec3
|
||||||
|
current := node
|
||||||
|
|
||||||
|
for current != nil {
|
||||||
|
pos := mgl64.Vec3{
|
||||||
|
float64(current.Position[0]),
|
||||||
|
float64(current.Position[1]),
|
||||||
|
float64(current.Position[2]),
|
||||||
|
}
|
||||||
|
path = append([]mgl64.Vec3{pos}, path...)
|
||||||
|
current = current.Parent
|
||||||
|
}
|
||||||
|
|
||||||
|
return path
|
||||||
|
}
|
323
pkg/game/player/player.go
Normal file
323
pkg/game/player/player.go
Normal file
@ -0,0 +1,323 @@
|
|||||||
|
package player
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"math"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"git.konjactw.dev/patyhank/minego/pkg/bot"
|
||||||
|
"git.konjactw.dev/patyhank/minego/pkg/game/world"
|
||||||
|
"git.konjactw.dev/patyhank/minego/pkg/protocol"
|
||||||
|
"git.konjactw.dev/patyhank/minego/pkg/protocol/packet/game/client"
|
||||||
|
"git.konjactw.dev/patyhank/minego/pkg/protocol/packet/game/server"
|
||||||
|
pk "github.com/Tnze/go-mc/net/packet"
|
||||||
|
"github.com/go-gl/mathgl/mgl64"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Player struct {
|
||||||
|
c bot.Client
|
||||||
|
|
||||||
|
entity *world.Entity
|
||||||
|
stateID int32
|
||||||
|
|
||||||
|
lastReceivedPacketTime time.Time
|
||||||
|
}
|
||||||
|
|
||||||
|
// New 創建新的 Player 實例
|
||||||
|
func New(c bot.Client) *Player {
|
||||||
|
pl := &Player{
|
||||||
|
c: c,
|
||||||
|
entity: &world.Entity{},
|
||||||
|
}
|
||||||
|
|
||||||
|
c.PacketHandler().AddGenericPacketHandler(func(ctx context.Context, pk client.ClientboundPacket) {
|
||||||
|
pl.lastReceivedPacketTime = time.Now()
|
||||||
|
})
|
||||||
|
|
||||||
|
bot.AddHandler(c, func(ctx context.Context, p *client.SystemChatMessage) {
|
||||||
|
if !p.Overlay {
|
||||||
|
bot.PublishEvent(c, MessageEvent{Message: p.Content})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
bot.AddHandler(c, func(ctx context.Context, p *client.PlayerPosition) {
|
||||||
|
pl.entity.SetPosition(mgl64.Vec3{p.X, p.Y, p.Z})
|
||||||
|
pl.entity.SetRotation(mgl64.Vec2{float64(p.XRot), float64(p.YRot)})
|
||||||
|
|
||||||
|
c.WritePacket(context.Background(), &server.AcceptTeleportation{TeleportID: p.ID})
|
||||||
|
})
|
||||||
|
bot.AddHandler(c, func(ctx context.Context, p *client.PlayerRotation) {
|
||||||
|
pl.entity.SetRotation(mgl64.Vec2{float64(p.Yaw), float64(p.Pitch)})
|
||||||
|
})
|
||||||
|
|
||||||
|
return pl
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Player) CheckServer() {
|
||||||
|
for time.Since(p.lastReceivedPacketTime) > 50*time.Millisecond && p.c.IsConnected() {
|
||||||
|
time.Sleep(50 * time.Millisecond)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// StateID 返回當前狀態 ID
|
||||||
|
func (p *Player) StateID() int32 {
|
||||||
|
return p.stateID
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateStateID 更新狀態 ID
|
||||||
|
func (p *Player) UpdateStateID(id int32) {
|
||||||
|
p.stateID = id
|
||||||
|
}
|
||||||
|
|
||||||
|
// Entity 返回玩家實體
|
||||||
|
func (p *Player) Entity() bot.Entity {
|
||||||
|
return p.entity
|
||||||
|
}
|
||||||
|
|
||||||
|
// FlyTo 直線飛行到指定位置,每5格飛行一段
|
||||||
|
func (p *Player) FlyTo(pos mgl64.Vec3) error {
|
||||||
|
if p.c == nil {
|
||||||
|
return fmt.Errorf("client is not initialized")
|
||||||
|
}
|
||||||
|
|
||||||
|
if p.entity == nil {
|
||||||
|
return fmt.Errorf("player entity is not initialized")
|
||||||
|
}
|
||||||
|
|
||||||
|
currentPos := p.entity.Position()
|
||||||
|
direction := pos.Sub(currentPos)
|
||||||
|
distance := direction.Len()
|
||||||
|
|
||||||
|
if distance == 0 {
|
||||||
|
return nil // 已經在目標位置
|
||||||
|
}
|
||||||
|
|
||||||
|
segmentLength := 8.0
|
||||||
|
|
||||||
|
for {
|
||||||
|
currentPos = p.entity.Position()
|
||||||
|
|
||||||
|
direction = pos.Sub(currentPos)
|
||||||
|
distance = direction.Len()
|
||||||
|
|
||||||
|
if distance == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// 正規化方向向量
|
||||||
|
direction = direction.Normalize()
|
||||||
|
|
||||||
|
moveDistance := math.Min(segmentLength, distance)
|
||||||
|
|
||||||
|
target := currentPos.Add(direction.Mul(moveDistance))
|
||||||
|
|
||||||
|
if err := p.c.WritePacket(context.Background(), &server.MovePlayerPos{
|
||||||
|
X: target.X(),
|
||||||
|
FeetY: target.Y(),
|
||||||
|
Z: target.Z(),
|
||||||
|
Flags: 0x00,
|
||||||
|
}); err != nil {
|
||||||
|
return fmt.Errorf("failed to move player: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
time.Sleep(50 * time.Millisecond)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// WalkTo 使用 A* 演算法步行到指定位置
|
||||||
|
func (p *Player) WalkTo(pos mgl64.Vec3) error {
|
||||||
|
if p.c == nil {
|
||||||
|
return fmt.Errorf("client is not initialized")
|
||||||
|
}
|
||||||
|
|
||||||
|
if p.entity == nil {
|
||||||
|
return fmt.Errorf("player entity is not initialized")
|
||||||
|
}
|
||||||
|
|
||||||
|
currentPos := p.entity.Position()
|
||||||
|
|
||||||
|
// 使用 A* 演算法尋找路徑
|
||||||
|
path, err := AStar(p.c.World(), currentPos, pos)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to find path: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(path) == 0 {
|
||||||
|
return fmt.Errorf("no path found to target position")
|
||||||
|
}
|
||||||
|
|
||||||
|
// 沿著路徑移動
|
||||||
|
for _, waypoint := range path {
|
||||||
|
if err := p.c.WritePacket(context.Background(), &server.MovePlayerPos{
|
||||||
|
X: waypoint.X(),
|
||||||
|
FeetY: waypoint.Y(),
|
||||||
|
Z: waypoint.Z(),
|
||||||
|
Flags: 0x0,
|
||||||
|
}); err != nil {
|
||||||
|
return fmt.Errorf("failed to move to waypoint: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 短暫延遲以模擬真實移動
|
||||||
|
time.Sleep(100 * time.Millisecond)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// LookAt 看向指定位置
|
||||||
|
func (p *Player) LookAt(target mgl64.Vec3) error {
|
||||||
|
if p.c == nil {
|
||||||
|
return fmt.Errorf("client is not initialized")
|
||||||
|
}
|
||||||
|
|
||||||
|
if p.entity == nil {
|
||||||
|
return fmt.Errorf("player entity is not initialized")
|
||||||
|
}
|
||||||
|
|
||||||
|
// 計算視角
|
||||||
|
playerPos := p.entity.Position()
|
||||||
|
direction := target.Sub(playerPos).Normalize()
|
||||||
|
|
||||||
|
// 計算 yaw 和 pitch
|
||||||
|
yaw := float32(math.Atan2(-direction.X(), direction.Z()) * 180 / math.Pi)
|
||||||
|
pitch := float32(math.Asin(-direction.Y()) * 180 / math.Pi)
|
||||||
|
|
||||||
|
return p.c.WritePacket(context.Background(), &server.MovePlayerRot{
|
||||||
|
Yaw: yaw,
|
||||||
|
Pitch: pitch,
|
||||||
|
Flags: 0x00,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// BreakBlock 破壞指定位置的方塊
|
||||||
|
func (p *Player) BreakBlock(pos protocol.Position) error {
|
||||||
|
if p.c == nil {
|
||||||
|
return fmt.Errorf("client is not initialized")
|
||||||
|
}
|
||||||
|
|
||||||
|
// 發送開始挖掘封包
|
||||||
|
startPacket := &server.PlayerAction{
|
||||||
|
Status: 0,
|
||||||
|
Sequence: p.stateID,
|
||||||
|
Location: pk.Position{X: int(pos[0]), Y: int(pos[1]), Z: int(pos[2])},
|
||||||
|
Face: 1,
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := p.c.WritePacket(context.Background(), startPacket); err != nil {
|
||||||
|
return fmt.Errorf("failed to send start destroy packet: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 發送完成挖掘封包
|
||||||
|
finishPacket := &server.PlayerAction{
|
||||||
|
Status: 2,
|
||||||
|
Sequence: p.stateID,
|
||||||
|
Location: pk.Position{X: int(pos[0]), Y: int(pos[1]), Z: int(pos[2])},
|
||||||
|
Face: 1,
|
||||||
|
}
|
||||||
|
|
||||||
|
return p.c.WritePacket(context.Background(), finishPacket)
|
||||||
|
}
|
||||||
|
|
||||||
|
// PlaceBlock 在指定位置放置方塊
|
||||||
|
func (p *Player) PlaceBlock(pos protocol.Position) error {
|
||||||
|
if p.c == nil {
|
||||||
|
return fmt.Errorf("client is not initialized")
|
||||||
|
}
|
||||||
|
|
||||||
|
packet := &server.UseItemOn{
|
||||||
|
Hand: 0,
|
||||||
|
Location: pk.Position{X: int(pos[0]), Y: int(pos[1]), Z: int(pos[2])},
|
||||||
|
Face: 1,
|
||||||
|
CursorX: 0.5,
|
||||||
|
CursorY: 0.5,
|
||||||
|
CursorZ: 0.5,
|
||||||
|
InsideBlock: false,
|
||||||
|
Sequence: p.stateID,
|
||||||
|
}
|
||||||
|
|
||||||
|
return p.c.WritePacket(context.Background(), packet)
|
||||||
|
}
|
||||||
|
|
||||||
|
// PlaceBlock 在指定位置放置方塊
|
||||||
|
func (p *Player) PlaceBlockWithArgs(pos protocol.Position, face int32, cursor mgl64.Vec3) error {
|
||||||
|
if p.c == nil {
|
||||||
|
return fmt.Errorf("client is not initialized")
|
||||||
|
}
|
||||||
|
|
||||||
|
packet := &server.UseItemOn{
|
||||||
|
Hand: 0,
|
||||||
|
Location: pk.Position{X: int(pos[0]), Y: int(pos[1]), Z: int(pos[2])},
|
||||||
|
Face: face,
|
||||||
|
CursorX: float32(cursor[0]),
|
||||||
|
CursorY: float32(cursor[1]),
|
||||||
|
CursorZ: float32(cursor[2]),
|
||||||
|
InsideBlock: false,
|
||||||
|
Sequence: p.stateID,
|
||||||
|
}
|
||||||
|
|
||||||
|
return p.c.WritePacket(context.Background(), packet)
|
||||||
|
}
|
||||||
|
|
||||||
|
// OpenContainer 打開指定位置的容器
|
||||||
|
func (p *Player) OpenContainer(pos protocol.Position) (bot.Container, error) {
|
||||||
|
if p.c == nil {
|
||||||
|
return nil, fmt.Errorf("client is not initialized")
|
||||||
|
}
|
||||||
|
|
||||||
|
// 發送使用物品封包來打開容器
|
||||||
|
packet := &server.UseItemOn{
|
||||||
|
Hand: 1,
|
||||||
|
Location: pk.Position{X: int(pos[0]), Y: int(pos[1]), Z: int(pos[2])},
|
||||||
|
Face: 1,
|
||||||
|
CursorX: 0.5,
|
||||||
|
CursorY: 0.5,
|
||||||
|
CursorZ: 0.5,
|
||||||
|
InsideBlock: false,
|
||||||
|
WorldBorderHit: false,
|
||||||
|
Sequence: p.stateID,
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := p.c.WritePacket(context.Background(), packet); err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to open container: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx, cancelFunc := context.WithTimeout(context.Background(), time.Second*10)
|
||||||
|
defer cancelFunc()
|
||||||
|
|
||||||
|
for p.c.Inventory().Container() == nil && ctx.Err() == nil {
|
||||||
|
time.Sleep(time.Millisecond * 50)
|
||||||
|
}
|
||||||
|
|
||||||
|
return p.c.Inventory().Container(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// UseItem 使用指定手中的物品
|
||||||
|
func (p *Player) UseItem(hand int8) error {
|
||||||
|
if p.c == nil {
|
||||||
|
return fmt.Errorf("client is not initialized")
|
||||||
|
}
|
||||||
|
|
||||||
|
return p.c.WritePacket(context.Background(), &server.UseItem{
|
||||||
|
Hand: int32(hand),
|
||||||
|
Sequence: p.stateID,
|
||||||
|
Yaw: 0,
|
||||||
|
Pitch: 0,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// OpenMenu 打開指定命令的選單
|
||||||
|
func (p *Player) OpenMenu(command string) (bot.Container, error) {
|
||||||
|
if p.c == nil {
|
||||||
|
return nil, fmt.Errorf("client is not initialized")
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := p.c.WritePacket(context.Background(), &server.ChatCommand{
|
||||||
|
Command: command,
|
||||||
|
}); err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to open menu with command '%s': %w", command, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 返回客戶端的容器處理器
|
||||||
|
return p.c.Inventory().Container(), nil
|
||||||
|
}
|
59
pkg/game/world/entity.go
Normal file
59
pkg/game/world/entity.go
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
package world
|
||||||
|
|
||||||
|
import (
|
||||||
|
"git.konjactw.dev/patyhank/minego/pkg/protocol/metadata"
|
||||||
|
"git.konjactw.dev/patyhank/minego/pkg/protocol/slot"
|
||||||
|
"github.com/Tnze/go-mc/data/entity"
|
||||||
|
"github.com/go-gl/mathgl/mgl64"
|
||||||
|
"github.com/google/uuid"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Entity struct {
|
||||||
|
id int32
|
||||||
|
entityUUID uuid.UUID
|
||||||
|
entityType entity.ID
|
||||||
|
pos mgl64.Vec3
|
||||||
|
rot mgl64.Vec2
|
||||||
|
metadata map[uint8]metadata.Metadata
|
||||||
|
equipment map[int8]slot.Slot
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *Entity) ID() int32 {
|
||||||
|
return e.id
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *Entity) UUID() uuid.UUID {
|
||||||
|
return e.entityUUID
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *Entity) Type() entity.ID {
|
||||||
|
return e.entityType
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *Entity) Position() mgl64.Vec3 {
|
||||||
|
return e.pos
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *Entity) Rotation() mgl64.Vec2 {
|
||||||
|
return e.rot
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *Entity) Metadata() map[uint8]metadata.Metadata {
|
||||||
|
return e.metadata
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *Entity) Equipment() map[int8]slot.Slot {
|
||||||
|
return e.equipment
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *Entity) SetPosition(pos mgl64.Vec3) {
|
||||||
|
e.pos = pos
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *Entity) SetRotation(rot mgl64.Vec2) {
|
||||||
|
e.rot = rot
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *Entity) SetID(id int32) {
|
||||||
|
e.id = id
|
||||||
|
}
|
19
pkg/game/world/event.go
Normal file
19
pkg/game/world/event.go
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
package world
|
||||||
|
|
||||||
|
import "git.konjactw.dev/patyhank/minego/pkg/bot"
|
||||||
|
|
||||||
|
type EntityRemoveEvent struct {
|
||||||
|
Entity bot.Entity
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e EntityRemoveEvent) EventID() string {
|
||||||
|
return "world:entity_remove"
|
||||||
|
}
|
||||||
|
|
||||||
|
type EntityAddEvent struct {
|
||||||
|
EntityID int32
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e EntityAddEvent) EventID() string {
|
||||||
|
return "world:entity_add"
|
||||||
|
}
|
304
pkg/game/world/world.go
Normal file
304
pkg/game/world/world.go
Normal file
@ -0,0 +1,304 @@
|
|||||||
|
package world
|
||||||
|
|
||||||
|
import (
|
||||||
|
"container/list"
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"git.konjactw.dev/patyhank/minego/pkg/bot"
|
||||||
|
"git.konjactw.dev/patyhank/minego/pkg/protocol"
|
||||||
|
"git.konjactw.dev/patyhank/minego/pkg/protocol/metadata"
|
||||||
|
cp "git.konjactw.dev/patyhank/minego/pkg/protocol/packet/game/client"
|
||||||
|
"git.konjactw.dev/patyhank/minego/pkg/protocol/slot"
|
||||||
|
"github.com/Tnze/go-mc/data/entity"
|
||||||
|
"github.com/Tnze/go-mc/level"
|
||||||
|
"github.com/Tnze/go-mc/level/block"
|
||||||
|
pk "github.com/Tnze/go-mc/net/packet"
|
||||||
|
"github.com/go-gl/mathgl/mgl64"
|
||||||
|
"golang.org/x/exp/constraints"
|
||||||
|
)
|
||||||
|
|
||||||
|
type World struct {
|
||||||
|
c bot.Client
|
||||||
|
|
||||||
|
Columns map[level.ChunkPos]*level.Chunk
|
||||||
|
|
||||||
|
entities map[int32]*Entity
|
||||||
|
|
||||||
|
entityLock sync.Mutex
|
||||||
|
chunkLock sync.Mutex
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewWorld(c bot.Client) *World {
|
||||||
|
w := &World{
|
||||||
|
c: c,
|
||||||
|
Columns: make(map[level.ChunkPos]*level.Chunk),
|
||||||
|
}
|
||||||
|
|
||||||
|
bot.AddHandler(c, func(ctx context.Context, p *cp.LevelChunkWithLight) {
|
||||||
|
w.chunkLock.Lock()
|
||||||
|
defer w.chunkLock.Unlock()
|
||||||
|
|
||||||
|
w.Columns[p.Pos] = p.Data
|
||||||
|
})
|
||||||
|
bot.AddHandler(c, func(ctx context.Context, p *cp.ForgetLevelChunk) {
|
||||||
|
w.chunkLock.Lock()
|
||||||
|
defer w.chunkLock.Unlock()
|
||||||
|
|
||||||
|
delete(w.Columns, p.Pos)
|
||||||
|
})
|
||||||
|
bot.AddHandler(c, func(ctx context.Context, p *cp.Respawn) {
|
||||||
|
w.chunkLock.Lock()
|
||||||
|
defer w.chunkLock.Unlock()
|
||||||
|
|
||||||
|
w.Columns = make(map[level.ChunkPos]*level.Chunk)
|
||||||
|
})
|
||||||
|
|
||||||
|
bot.AddHandler(c, func(ctx context.Context, p *cp.AddEntity) {
|
||||||
|
w.entities[p.ID] = &Entity{
|
||||||
|
id: p.ID,
|
||||||
|
entityUUID: p.UUID,
|
||||||
|
entityType: entity.ID(p.Type),
|
||||||
|
pos: mgl64.Vec3{p.X, p.Y, p.Z},
|
||||||
|
rot: mgl64.Vec2{pk.Angle(p.XRot).ToDeg(), pk.Angle(p.YRot).ToDeg()},
|
||||||
|
metadata: nil,
|
||||||
|
equipment: nil,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
bot.AddHandler(c, func(ctx context.Context, p *cp.RemoveEntities) {
|
||||||
|
w.entityLock.Lock()
|
||||||
|
defer w.entityLock.Unlock()
|
||||||
|
for _, d := range p.EntityIDs {
|
||||||
|
e, ok := w.entities[d]
|
||||||
|
if ok {
|
||||||
|
bot.PublishEvent(c, EntityRemoveEvent{Entity: e})
|
||||||
|
delete(w.entities, d)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
bot.AddHandler(c, func(ctx context.Context, p *cp.SetEntityMetadata) {
|
||||||
|
w.entityLock.Lock()
|
||||||
|
defer w.entityLock.Unlock()
|
||||||
|
e, ok := w.entities[p.EntityID]
|
||||||
|
if ok {
|
||||||
|
if e.metadata == nil {
|
||||||
|
e.metadata = make(map[uint8]metadata.Metadata)
|
||||||
|
}
|
||||||
|
for u, entityMetadata := range p.Metadata.Data {
|
||||||
|
e.metadata[u] = entityMetadata
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
bot.AddHandler(c, func(ctx context.Context, p *cp.SetEquipment) {
|
||||||
|
w.entityLock.Lock()
|
||||||
|
defer w.entityLock.Unlock()
|
||||||
|
e, ok := w.entities[p.EntityID]
|
||||||
|
if ok {
|
||||||
|
if e.equipment == nil {
|
||||||
|
e.equipment = make(map[int8]slot.Slot)
|
||||||
|
}
|
||||||
|
for _, equipment := range p.Equipment {
|
||||||
|
e.equipment[equipment.Slot] = equipment.Item
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
bot.AddHandler(c, func(ctx context.Context, p *cp.UpdateEntityPosition) {
|
||||||
|
w.entityLock.Lock()
|
||||||
|
defer w.entityLock.Unlock()
|
||||||
|
if e, ok := w.entities[p.EntityID]; ok {
|
||||||
|
e.pos = e.pos.Add(mgl64.Vec3{float64(p.DeltaX) / 4096.0, float64(p.DeltaY) / 4096.0, float64(p.DeltaZ) / 4096.0})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
bot.AddHandler(c, func(ctx context.Context, p *cp.UpdateEntityRotation) {
|
||||||
|
w.entityLock.Lock()
|
||||||
|
defer w.entityLock.Unlock()
|
||||||
|
if e, ok := w.entities[p.EntityID]; ok {
|
||||||
|
e.rot = mgl64.Vec2{float64(p.Yaw), float64(p.Pitch)}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
bot.AddHandler(c, func(ctx context.Context, p *cp.UpdateEntityPositionAndRotation) {
|
||||||
|
w.entityLock.Lock()
|
||||||
|
defer w.entityLock.Unlock()
|
||||||
|
if e, ok := w.entities[p.EntityID]; ok {
|
||||||
|
e.pos = e.pos.Add(mgl64.Vec3{float64(p.DeltaX) / 4096.0, float64(p.DeltaY) / 4096.0, float64(p.DeltaZ) / 4096.0})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
return w
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *World) GetBlock(pos protocol.Position) (block.Block, error) {
|
||||||
|
w.chunkLock.Lock()
|
||||||
|
defer w.chunkLock.Unlock()
|
||||||
|
chunkX := pos[0] >> 4
|
||||||
|
chunkZ := pos[2] >> 4
|
||||||
|
pos2d := level.ChunkPos{chunkX, chunkZ}
|
||||||
|
|
||||||
|
chunk, ok := w.Columns[pos2d]
|
||||||
|
if !ok {
|
||||||
|
return nil, errors.New("chunk not loaded")
|
||||||
|
}
|
||||||
|
|
||||||
|
blockX := pos[0] & 15
|
||||||
|
blockZ := pos[2] & 15
|
||||||
|
blockIdx := (pos[1] << 8) | (blockZ << 4) | blockX
|
||||||
|
sectionY := pos[1] >> 4
|
||||||
|
if sectionY < 0 || int(sectionY) >= len(chunk.Sections) {
|
||||||
|
return nil, errors.New("invalid section Y coordinate")
|
||||||
|
}
|
||||||
|
blockStateId := chunk.Sections[sectionY].GetBlock(int(blockIdx))
|
||||||
|
return block.StateList[blockStateId], nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *World) SetBlock(pos protocol.Position, blk block.Block) error {
|
||||||
|
w.chunkLock.Lock()
|
||||||
|
defer w.chunkLock.Unlock()
|
||||||
|
|
||||||
|
chunkX := pos[0] >> 4
|
||||||
|
chunkZ := pos[2] >> 4
|
||||||
|
pos2d := level.ChunkPos{chunkX, chunkZ}
|
||||||
|
|
||||||
|
chunk, ok := w.Columns[pos2d]
|
||||||
|
if !ok {
|
||||||
|
return errors.New("chunk not loaded")
|
||||||
|
}
|
||||||
|
|
||||||
|
blockX := pos[0] & 15
|
||||||
|
blockZ := pos[2] & 15
|
||||||
|
sectionY := pos[1] >> 4
|
||||||
|
blockY := pos[1] & 15
|
||||||
|
|
||||||
|
if sectionY < 0 || int(sectionY) >= len(chunk.Sections) {
|
||||||
|
return errors.New("invalid section Y coordinate")
|
||||||
|
}
|
||||||
|
|
||||||
|
section := chunk.Sections[sectionY]
|
||||||
|
|
||||||
|
blockIdx := (blockY << 8) | (blockZ << 4) | blockX
|
||||||
|
section.SetBlock(int(blockIdx), block.ToStateID[blk])
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *World) GetNearbyBlocks(pos protocol.Position, radius int32) ([]block.Block, error) {
|
||||||
|
w.chunkLock.Lock()
|
||||||
|
defer w.chunkLock.Unlock()
|
||||||
|
|
||||||
|
var blocks []block.Block
|
||||||
|
|
||||||
|
for dx := -radius; dx <= radius; dx++ {
|
||||||
|
for dy := -radius; dy <= radius; dy++ {
|
||||||
|
for dz := -radius; dz <= radius; dz++ {
|
||||||
|
blk, err := w.GetBlock(protocol.Position{pos[0] + dx, pos[1] + dy, pos[2] + dz})
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
blocks = append(blocks, blk)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return blocks, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *World) FindNearbyBlock(pos protocol.Position, radius int32, blk block.Block) (protocol.Position, error) {
|
||||||
|
w.chunkLock.Lock()
|
||||||
|
defer w.chunkLock.Unlock()
|
||||||
|
visited := make(map[protocol.Position]bool)
|
||||||
|
queue := list.New()
|
||||||
|
start := pos
|
||||||
|
queue.PushBack(start)
|
||||||
|
visited[start] = true
|
||||||
|
|
||||||
|
// Direction vectors for 6-way adjacent blocks
|
||||||
|
dirs := []protocol.Position{
|
||||||
|
{1, 0, 0}, {-1, 0, 0},
|
||||||
|
{0, 1, 0}, {0, -1, 0},
|
||||||
|
{0, 0, 1}, {0, 0, -1},
|
||||||
|
}
|
||||||
|
for queue.Len() > 0 {
|
||||||
|
current := queue.Remove(queue.Front()).(protocol.Position)
|
||||||
|
|
||||||
|
// Skip if beyond the radius
|
||||||
|
if abs(current[0]-pos[0]) > radius || abs(current[1]-pos[1]) > radius || abs(current[2]-pos[2]) > radius {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if current block matches target
|
||||||
|
if currentBlock, err := w.GetBlock(current); err == nil {
|
||||||
|
if currentBlock == blk {
|
||||||
|
return current, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check all 6 adjacent blocks
|
||||||
|
for _, dir := range dirs {
|
||||||
|
next := protocol.Position{
|
||||||
|
current[0] + dir[0],
|
||||||
|
current[1] + dir[1],
|
||||||
|
current[2] + dir[2],
|
||||||
|
}
|
||||||
|
|
||||||
|
if !visited[next] {
|
||||||
|
visited[next] = true
|
||||||
|
queue.PushBack(next)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return protocol.Position{}, errors.New("block not found")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *World) Entities() []bot.Entity {
|
||||||
|
w.entityLock.Lock()
|
||||||
|
defer w.entityLock.Unlock()
|
||||||
|
var entities []bot.Entity
|
||||||
|
for _, e := range w.entities {
|
||||||
|
entities = append(entities, e)
|
||||||
|
}
|
||||||
|
return entities
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *World) GetEntity(id int32) bot.Entity {
|
||||||
|
w.entityLock.Lock()
|
||||||
|
defer w.entityLock.Unlock()
|
||||||
|
return w.entities[id]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *World) GetNearbyEntities(radius int32) []bot.Entity {
|
||||||
|
w.entityLock.Lock()
|
||||||
|
defer w.entityLock.Unlock()
|
||||||
|
|
||||||
|
selfPos := w.c.Player().Entity().Position()
|
||||||
|
var entities []bot.Entity
|
||||||
|
|
||||||
|
for _, e := range w.entities {
|
||||||
|
sqr := e.pos.Sub(selfPos).LenSqr()
|
||||||
|
if sqr <= float64(radius*radius) {
|
||||||
|
entities = append(entities, e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return entities
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *World) GetEntitiesByType(entityType entity.ID) []bot.Entity {
|
||||||
|
w.entityLock.Lock()
|
||||||
|
defer w.entityLock.Unlock()
|
||||||
|
|
||||||
|
var entities []bot.Entity
|
||||||
|
for _, e := range w.entities {
|
||||||
|
if e.entityType == entityType {
|
||||||
|
entities = append(entities, e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return entities
|
||||||
|
}
|
||||||
|
|
||||||
|
func abs[T constraints.Signed | constraints.Float](x T) T {
|
||||||
|
if x < 0 {
|
||||||
|
return -x
|
||||||
|
}
|
||||||
|
return x
|
||||||
|
}
|
@ -3698,102 +3698,6 @@ func (a *BoolVarIntArray) ReadFrom(r io.Reader) (n int64, err error) {
|
|||||||
return n, err
|
return n, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Int32VarIntArray a utility type for encoding/decoding packet.Int -> int32[packet.VarInt] slice.
|
|
||||||
type Int32VarIntArray []int32
|
|
||||||
|
|
||||||
func (a Int32VarIntArray) WriteTo(w io.Writer) (n int64, err error) {
|
|
||||||
size := len(a)
|
|
||||||
nn, err := packet.VarInt(size).WriteTo(w)
|
|
||||||
if err != nil {
|
|
||||||
return n, err
|
|
||||||
}
|
|
||||||
n += nn
|
|
||||||
for i := 0; i < size; i++ {
|
|
||||||
nn, err := packet.Int(a[i]).WriteTo(w)
|
|
||||||
n += nn
|
|
||||||
if err != nil {
|
|
||||||
return n, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return n, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *Int32VarIntArray) ReadFrom(r io.Reader) (n int64, err error) {
|
|
||||||
var size packet.VarInt
|
|
||||||
nn, err := size.ReadFrom(r)
|
|
||||||
n += nn
|
|
||||||
if err != nil {
|
|
||||||
return n, err
|
|
||||||
}
|
|
||||||
if size < 0 {
|
|
||||||
return n, errors.New("array length less than zero")
|
|
||||||
}
|
|
||||||
|
|
||||||
if cap(*a) >= int(size) {
|
|
||||||
*a = (*a)[:int(size)]
|
|
||||||
} else {
|
|
||||||
*a = make(Int32VarIntArray, int(size))
|
|
||||||
}
|
|
||||||
|
|
||||||
for i := 0; i < int(size); i++ {
|
|
||||||
nn, err = (*packet.Int)(&(*a)[i]).ReadFrom(r)
|
|
||||||
n += nn
|
|
||||||
if err != nil {
|
|
||||||
return n, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return n, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Int32PrefixedArrayVarIntArray a utility type for encoding/decoding packet.Int -> int32[packet.VarInt] slice.
|
|
||||||
type Int32PrefixedArrayVarIntArray []int32
|
|
||||||
|
|
||||||
func (a Int32PrefixedArrayVarIntArray) WriteTo(w io.Writer) (n int64, err error) {
|
|
||||||
size := len(a)
|
|
||||||
nn, err := packet.VarInt(size).WriteTo(w)
|
|
||||||
if err != nil {
|
|
||||||
return n, err
|
|
||||||
}
|
|
||||||
n += nn
|
|
||||||
for i := 0; i < size; i++ {
|
|
||||||
nn, err := packet.Int(a[i]).WriteTo(w)
|
|
||||||
n += nn
|
|
||||||
if err != nil {
|
|
||||||
return n, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return n, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *Int32PrefixedArrayVarIntArray) ReadFrom(r io.Reader) (n int64, err error) {
|
|
||||||
var size packet.VarInt
|
|
||||||
nn, err := size.ReadFrom(r)
|
|
||||||
n += nn
|
|
||||||
if err != nil {
|
|
||||||
return n, err
|
|
||||||
}
|
|
||||||
if size < 0 {
|
|
||||||
return n, errors.New("array length less than zero")
|
|
||||||
}
|
|
||||||
|
|
||||||
if cap(*a) >= int(size) {
|
|
||||||
*a = (*a)[:int(size)]
|
|
||||||
} else {
|
|
||||||
*a = make(Int32PrefixedArrayVarIntArray, int(size))
|
|
||||||
}
|
|
||||||
|
|
||||||
for i := 0; i < int(size); i++ {
|
|
||||||
nn, err = (*packet.Int)(&(*a)[i]).ReadFrom(r)
|
|
||||||
n += nn
|
|
||||||
if err != nil {
|
|
||||||
return n, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return n, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// StringVarIntArray a utility type for encoding/decoding packet.String -> string[packet.VarInt] slice.
|
// StringVarIntArray a utility type for encoding/decoding packet.String -> string[packet.VarInt] slice.
|
||||||
type StringVarIntArray []string
|
type StringVarIntArray []string
|
||||||
|
|
||||||
@ -3889,3 +3793,99 @@ func (a *Int32VarIntVarIntArray) ReadFrom(r io.Reader) (n int64, err error) {
|
|||||||
|
|
||||||
return n, err
|
return n, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Int32VarIntArray a utility type for encoding/decoding packet.Int -> int32[packet.VarInt] slice.
|
||||||
|
type Int32VarIntArray []int32
|
||||||
|
|
||||||
|
func (a Int32VarIntArray) WriteTo(w io.Writer) (n int64, err error) {
|
||||||
|
size := len(a)
|
||||||
|
nn, err := packet.VarInt(size).WriteTo(w)
|
||||||
|
if err != nil {
|
||||||
|
return n, err
|
||||||
|
}
|
||||||
|
n += nn
|
||||||
|
for i := 0; i < size; i++ {
|
||||||
|
nn, err := packet.Int(a[i]).WriteTo(w)
|
||||||
|
n += nn
|
||||||
|
if err != nil {
|
||||||
|
return n, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return n, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *Int32VarIntArray) ReadFrom(r io.Reader) (n int64, err error) {
|
||||||
|
var size packet.VarInt
|
||||||
|
nn, err := size.ReadFrom(r)
|
||||||
|
n += nn
|
||||||
|
if err != nil {
|
||||||
|
return n, err
|
||||||
|
}
|
||||||
|
if size < 0 {
|
||||||
|
return n, errors.New("array length less than zero")
|
||||||
|
}
|
||||||
|
|
||||||
|
if cap(*a) >= int(size) {
|
||||||
|
*a = (*a)[:int(size)]
|
||||||
|
} else {
|
||||||
|
*a = make(Int32VarIntArray, int(size))
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := 0; i < int(size); i++ {
|
||||||
|
nn, err = (*packet.Int)(&(*a)[i]).ReadFrom(r)
|
||||||
|
n += nn
|
||||||
|
if err != nil {
|
||||||
|
return n, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return n, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Int32PrefixedArrayVarIntArray a utility type for encoding/decoding packet.Int -> int32[packet.VarInt] slice.
|
||||||
|
type Int32PrefixedArrayVarIntArray []int32
|
||||||
|
|
||||||
|
func (a Int32PrefixedArrayVarIntArray) WriteTo(w io.Writer) (n int64, err error) {
|
||||||
|
size := len(a)
|
||||||
|
nn, err := packet.VarInt(size).WriteTo(w)
|
||||||
|
if err != nil {
|
||||||
|
return n, err
|
||||||
|
}
|
||||||
|
n += nn
|
||||||
|
for i := 0; i < size; i++ {
|
||||||
|
nn, err := packet.Int(a[i]).WriteTo(w)
|
||||||
|
n += nn
|
||||||
|
if err != nil {
|
||||||
|
return n, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return n, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *Int32PrefixedArrayVarIntArray) ReadFrom(r io.Reader) (n int64, err error) {
|
||||||
|
var size packet.VarInt
|
||||||
|
nn, err := size.ReadFrom(r)
|
||||||
|
n += nn
|
||||||
|
if err != nil {
|
||||||
|
return n, err
|
||||||
|
}
|
||||||
|
if size < 0 {
|
||||||
|
return n, errors.New("array length less than zero")
|
||||||
|
}
|
||||||
|
|
||||||
|
if cap(*a) >= int(size) {
|
||||||
|
*a = (*a)[:int(size)]
|
||||||
|
} else {
|
||||||
|
*a = make(Int32PrefixedArrayVarIntArray, int(size))
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := 0; i < int(size); i++ {
|
||||||
|
nn, err = (*packet.Int)(&(*a)[i]).ReadFrom(r)
|
||||||
|
n += nn
|
||||||
|
if err != nil {
|
||||||
|
return n, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return n, err
|
||||||
|
}
|
||||||
|
@ -46,13 +46,13 @@ const (
|
|||||||
MetadataQuaternion
|
MetadataQuaternion
|
||||||
)
|
)
|
||||||
|
|
||||||
type entityMetadata interface {
|
type Metadata interface {
|
||||||
EntityMetadataType() MetadataType
|
EntityMetadataType() MetadataType
|
||||||
pk.Field
|
pk.Field
|
||||||
}
|
}
|
||||||
|
|
||||||
type EntityMetadata struct {
|
type EntityMetadata struct {
|
||||||
Data map[uint8]entityMetadata
|
Data map[uint8]Metadata
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m EntityMetadata) WriteTo(w io.Writer) (int64, error) {
|
func (m EntityMetadata) WriteTo(w io.Writer) (int64, error) {
|
||||||
@ -114,44 +114,44 @@ func (m *EntityMetadata) ReadFrom(r io.Reader) (int64, error) {
|
|||||||
return n, nil
|
return n, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
type metadataCreator func() entityMetadata
|
type metadataCreator func() Metadata
|
||||||
|
|
||||||
var metadataType = map[MetadataType]metadataCreator{}
|
var metadataType = map[MetadataType]metadataCreator{}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
metadataType[MetadataByte] = func() entityMetadata { return &Byte{} }
|
metadataType[MetadataByte] = func() Metadata { return &Byte{} }
|
||||||
metadataType[MetadataVarInt] = func() entityMetadata { return &VarInt{} }
|
metadataType[MetadataVarInt] = func() Metadata { return &VarInt{} }
|
||||||
metadataType[MetadataVarLong] = func() entityMetadata { return &VarLong{} }
|
metadataType[MetadataVarLong] = func() Metadata { return &VarLong{} }
|
||||||
metadataType[MetadataFloat] = func() entityMetadata { return &Float{} }
|
metadataType[MetadataFloat] = func() Metadata { return &Float{} }
|
||||||
metadataType[MetadataString] = func() entityMetadata { return &String{} }
|
metadataType[MetadataString] = func() Metadata { return &String{} }
|
||||||
metadataType[MetadataChat] = func() entityMetadata { return &Chat{} }
|
metadataType[MetadataChat] = func() Metadata { return &Chat{} }
|
||||||
metadataType[MetadataOptChat] = func() entityMetadata { return &OptChat{} }
|
metadataType[MetadataOptChat] = func() Metadata { return &OptChat{} }
|
||||||
metadataType[MetadataSlot] = func() entityMetadata { return &Slot{} }
|
metadataType[MetadataSlot] = func() Metadata { return &Slot{} }
|
||||||
metadataType[MetadataBoolean] = func() entityMetadata { return &Boolean{} }
|
metadataType[MetadataBoolean] = func() Metadata { return &Boolean{} }
|
||||||
metadataType[MetadataRotation] = func() entityMetadata { return &Rotation{} }
|
metadataType[MetadataRotation] = func() Metadata { return &Rotation{} }
|
||||||
metadataType[MetadataPosition] = func() entityMetadata { return &Position{} }
|
metadataType[MetadataPosition] = func() Metadata { return &Position{} }
|
||||||
metadataType[MetadataOptPosition] = func() entityMetadata { return &OptPosition{} }
|
metadataType[MetadataOptPosition] = func() Metadata { return &OptPosition{} }
|
||||||
metadataType[MetadataDirection] = func() entityMetadata { return &Direction{} }
|
metadataType[MetadataDirection] = func() Metadata { return &Direction{} }
|
||||||
metadataType[MetadataOptLivingEntity] = func() entityMetadata { return &OptLivingEntity{} }
|
metadataType[MetadataOptLivingEntity] = func() Metadata { return &OptLivingEntity{} }
|
||||||
metadataType[MetadataBlockState] = func() entityMetadata { return &BlockState{} }
|
metadataType[MetadataBlockState] = func() Metadata { return &BlockState{} }
|
||||||
metadataType[MetadataOptBlockState] = func() entityMetadata { return &OptBlockState{} }
|
metadataType[MetadataOptBlockState] = func() Metadata { return &OptBlockState{} }
|
||||||
metadataType[MetadataNBT] = func() entityMetadata { return &NBT{} }
|
metadataType[MetadataNBT] = func() Metadata { return &NBT{} }
|
||||||
metadataType[MetadataParticle] = func() entityMetadata { return &Particle{} }
|
metadataType[MetadataParticle] = func() Metadata { return &Particle{} }
|
||||||
metadataType[MetadataParticles] = func() entityMetadata { return &Particles{} }
|
metadataType[MetadataParticles] = func() Metadata { return &Particles{} }
|
||||||
metadataType[MetadataVillagerData] = func() entityMetadata { return &VillagerData{} }
|
metadataType[MetadataVillagerData] = func() Metadata { return &VillagerData{} }
|
||||||
metadataType[MetadataOptVarInt] = func() entityMetadata { return &OptVarInt{} }
|
metadataType[MetadataOptVarInt] = func() Metadata { return &OptVarInt{} }
|
||||||
metadataType[MetadataPose] = func() entityMetadata { return &Pose{} }
|
metadataType[MetadataPose] = func() Metadata { return &Pose{} }
|
||||||
metadataType[MetadataCatVariant] = func() entityMetadata { return &CatVariant{} }
|
metadataType[MetadataCatVariant] = func() Metadata { return &CatVariant{} }
|
||||||
metadataType[MetadataCowVariant] = func() entityMetadata { return &CowVariant{} }
|
metadataType[MetadataCowVariant] = func() Metadata { return &CowVariant{} }
|
||||||
metadataType[MetadataWolfVariant] = func() entityMetadata { return &WolfVariant{} }
|
metadataType[MetadataWolfVariant] = func() Metadata { return &WolfVariant{} }
|
||||||
metadataType[MetadataWolfSoundVariant] = func() entityMetadata { return &WolfSoundVariant{} }
|
metadataType[MetadataWolfSoundVariant] = func() Metadata { return &WolfSoundVariant{} }
|
||||||
metadataType[MetadataFrogVariant] = func() entityMetadata { return &FrogVariant{} }
|
metadataType[MetadataFrogVariant] = func() Metadata { return &FrogVariant{} }
|
||||||
metadataType[MetadataPigVariant] = func() entityMetadata { return &PigVariant{} }
|
metadataType[MetadataPigVariant] = func() Metadata { return &PigVariant{} }
|
||||||
metadataType[MetadataChickenVariant] = func() entityMetadata { return &ChickenVariant{} }
|
metadataType[MetadataChickenVariant] = func() Metadata { return &ChickenVariant{} }
|
||||||
metadataType[MetadataOptGlobalPosition] = func() entityMetadata { return &OptGlobalPosition{} }
|
metadataType[MetadataOptGlobalPosition] = func() Metadata { return &OptGlobalPosition{} }
|
||||||
metadataType[MetadataPaintingVariant] = func() entityMetadata { return &PaintingVariant{} }
|
metadataType[MetadataPaintingVariant] = func() Metadata { return &PaintingVariant{} }
|
||||||
metadataType[MetadataSnifferVariant] = func() entityMetadata { return &SnifferVariant{} }
|
metadataType[MetadataSnifferVariant] = func() Metadata { return &SnifferVariant{} }
|
||||||
metadataType[MetadataArmadilloState] = func() entityMetadata { return &ArmadilloState{} }
|
metadataType[MetadataArmadilloState] = func() Metadata { return &ArmadilloState{} }
|
||||||
metadataType[MetadataVector3] = func() entityMetadata { return &Vector3{} }
|
metadataType[MetadataVector3] = func() Metadata { return &Vector3{} }
|
||||||
metadataType[MetadataQuaternion] = func() entityMetadata { return &Quaternion{} }
|
metadataType[MetadataQuaternion] = func() Metadata { return &Quaternion{} }
|
||||||
}
|
}
|
||||||
|
@ -343,54 +343,6 @@ func (c ConfigUpdateEnabledFeatures) WriteTo(w io.Writer) (n int64, err error) {
|
|||||||
return n, err
|
return n, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// StringIdentifierVarIntArray a utility type for encoding/decoding packet.Identifier -> string[packet.VarInt] slice.
|
|
||||||
type StringIdentifierVarIntArray []string
|
|
||||||
|
|
||||||
func (a StringIdentifierVarIntArray) WriteTo(w io.Writer) (n int64, err error) {
|
|
||||||
size := len(a)
|
|
||||||
nn, err := packet.VarInt(size).WriteTo(w)
|
|
||||||
if err != nil {
|
|
||||||
return n, err
|
|
||||||
}
|
|
||||||
n += nn
|
|
||||||
for i := 0; i < size; i++ {
|
|
||||||
nn, err := packet.Identifier(a[i]).WriteTo(w)
|
|
||||||
n += nn
|
|
||||||
if err != nil {
|
|
||||||
return n, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return n, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *StringIdentifierVarIntArray) ReadFrom(r io.Reader) (n int64, err error) {
|
|
||||||
var size packet.VarInt
|
|
||||||
nn, err := size.ReadFrom(r)
|
|
||||||
n += nn
|
|
||||||
if err != nil {
|
|
||||||
return n, err
|
|
||||||
}
|
|
||||||
if size < 0 {
|
|
||||||
return n, errors.New("array length less than zero")
|
|
||||||
}
|
|
||||||
|
|
||||||
if cap(*a) >= int(size) {
|
|
||||||
*a = (*a)[:int(size)]
|
|
||||||
} else {
|
|
||||||
*a = make(StringIdentifierVarIntArray, int(size))
|
|
||||||
}
|
|
||||||
|
|
||||||
for i := 0; i < int(size); i++ {
|
|
||||||
nn, err = (*packet.Identifier)(&(*a)[i]).ReadFrom(r)
|
|
||||||
n += nn
|
|
||||||
if err != nil {
|
|
||||||
return n, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return n, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Int8VarIntArray a utility type for encoding/decoding packet.Byte -> int8[packet.VarInt] slice.
|
// Int8VarIntArray a utility type for encoding/decoding packet.Byte -> int8[packet.VarInt] slice.
|
||||||
type Int8VarIntArray []int8
|
type Int8VarIntArray []int8
|
||||||
|
|
||||||
@ -438,3 +390,51 @@ func (a *Int8VarIntArray) ReadFrom(r io.Reader) (n int64, err error) {
|
|||||||
|
|
||||||
return n, err
|
return n, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// StringIdentifierVarIntArray a utility type for encoding/decoding packet.Identifier -> string[packet.VarInt] slice.
|
||||||
|
type StringIdentifierVarIntArray []string
|
||||||
|
|
||||||
|
func (a StringIdentifierVarIntArray) WriteTo(w io.Writer) (n int64, err error) {
|
||||||
|
size := len(a)
|
||||||
|
nn, err := packet.VarInt(size).WriteTo(w)
|
||||||
|
if err != nil {
|
||||||
|
return n, err
|
||||||
|
}
|
||||||
|
n += nn
|
||||||
|
for i := 0; i < size; i++ {
|
||||||
|
nn, err := packet.Identifier(a[i]).WriteTo(w)
|
||||||
|
n += nn
|
||||||
|
if err != nil {
|
||||||
|
return n, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return n, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *StringIdentifierVarIntArray) ReadFrom(r io.Reader) (n int64, err error) {
|
||||||
|
var size packet.VarInt
|
||||||
|
nn, err := size.ReadFrom(r)
|
||||||
|
n += nn
|
||||||
|
if err != nil {
|
||||||
|
return n, err
|
||||||
|
}
|
||||||
|
if size < 0 {
|
||||||
|
return n, errors.New("array length less than zero")
|
||||||
|
}
|
||||||
|
|
||||||
|
if cap(*a) >= int(size) {
|
||||||
|
*a = (*a)[:int(size)]
|
||||||
|
} else {
|
||||||
|
*a = make(StringIdentifierVarIntArray, int(size))
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := 0; i < int(size); i++ {
|
||||||
|
nn, err = (*packet.Identifier)(&(*a)[i]).ReadFrom(r)
|
||||||
|
n += nn
|
||||||
|
if err != nil {
|
||||||
|
return n, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return n, err
|
||||||
|
}
|
||||||
|
@ -1557,35 +1557,7 @@ func (c Explode) WriteTo(w io.Writer) (n int64, err error) {
|
|||||||
}
|
}
|
||||||
return n, err
|
return n, err
|
||||||
}
|
}
|
||||||
func (c *ChunkPos) ReadFrom(r io.Reader) (n int64, err error) {
|
|
||||||
var temp int64
|
|
||||||
temp, err = (*packet.Int)(&c.X).ReadFrom(r)
|
|
||||||
n += temp
|
|
||||||
if err != nil {
|
|
||||||
return n, err
|
|
||||||
}
|
|
||||||
temp, err = (*packet.Int)(&c.Z).ReadFrom(r)
|
|
||||||
n += temp
|
|
||||||
if err != nil {
|
|
||||||
return n, err
|
|
||||||
}
|
|
||||||
return n, err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c ChunkPos) WriteTo(w io.Writer) (n int64, err error) {
|
|
||||||
var temp int64
|
|
||||||
temp, err = (*packet.Int)(&c.X).WriteTo(w)
|
|
||||||
n += temp
|
|
||||||
if err != nil {
|
|
||||||
return n, err
|
|
||||||
}
|
|
||||||
temp, err = (*packet.Int)(&c.Z).WriteTo(w)
|
|
||||||
n += temp
|
|
||||||
if err != nil {
|
|
||||||
return n, err
|
|
||||||
}
|
|
||||||
return n, err
|
|
||||||
}
|
|
||||||
func (c *ForgetLevelChunk) ReadFrom(r io.Reader) (n int64, err error) {
|
func (c *ForgetLevelChunk) ReadFrom(r io.Reader) (n int64, err error) {
|
||||||
var temp int64
|
var temp int64
|
||||||
temp, err = (&c.Pos).ReadFrom(r)
|
temp, err = (&c.Pos).ReadFrom(r)
|
||||||
@ -1810,45 +1782,6 @@ func (c KeepAlive) WriteTo(w io.Writer) (n int64, err error) {
|
|||||||
}
|
}
|
||||||
return n, err
|
return n, err
|
||||||
}
|
}
|
||||||
func (c *LevelChunkWithLight) ReadFrom(r io.Reader) (n int64, err error) {
|
|
||||||
var temp int64
|
|
||||||
temp, err = (*packet.Int)(&c.X).ReadFrom(r)
|
|
||||||
n += temp
|
|
||||||
if err != nil {
|
|
||||||
return n, err
|
|
||||||
}
|
|
||||||
temp, err = (*packet.Int)(&c.Z).ReadFrom(r)
|
|
||||||
n += temp
|
|
||||||
if err != nil {
|
|
||||||
return n, err
|
|
||||||
}
|
|
||||||
temp, err = (&c.Data).ReadFrom(r)
|
|
||||||
n += temp
|
|
||||||
if err != nil {
|
|
||||||
return n, err
|
|
||||||
}
|
|
||||||
return n, err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c LevelChunkWithLight) WriteTo(w io.Writer) (n int64, err error) {
|
|
||||||
var temp int64
|
|
||||||
temp, err = (*packet.Int)(&c.X).WriteTo(w)
|
|
||||||
n += temp
|
|
||||||
if err != nil {
|
|
||||||
return n, err
|
|
||||||
}
|
|
||||||
temp, err = (*packet.Int)(&c.Z).WriteTo(w)
|
|
||||||
n += temp
|
|
||||||
if err != nil {
|
|
||||||
return n, err
|
|
||||||
}
|
|
||||||
temp, err = (&c.Data).WriteTo(w)
|
|
||||||
n += temp
|
|
||||||
if err != nil {
|
|
||||||
return n, err
|
|
||||||
}
|
|
||||||
return n, err
|
|
||||||
}
|
|
||||||
func (c *LevelEvent) ReadFrom(r io.Reader) (n int64, err error) {
|
func (c *LevelEvent) ReadFrom(r io.Reader) (n int64, err error) {
|
||||||
var temp int64
|
var temp int64
|
||||||
temp, err = (*packet.Int)(&c.Type).ReadFrom(r)
|
temp, err = (*packet.Int)(&c.Type).ReadFrom(r)
|
||||||
@ -7157,102 +7090,6 @@ func (c Waypoint) WriteTo(w io.Writer) (n int64, err error) {
|
|||||||
return n, err
|
return n, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// StringIdentifierVarIntArray a utility type for encoding/decoding packet.Identifier -> string[packet.VarInt] slice.
|
|
||||||
type StringIdentifierVarIntArray []string
|
|
||||||
|
|
||||||
func (a StringIdentifierVarIntArray) WriteTo(w io.Writer) (n int64, err error) {
|
|
||||||
size := len(a)
|
|
||||||
nn, err := packet.VarInt(size).WriteTo(w)
|
|
||||||
if err != nil {
|
|
||||||
return n, err
|
|
||||||
}
|
|
||||||
n += nn
|
|
||||||
for i := 0; i < size; i++ {
|
|
||||||
nn, err := packet.Identifier(a[i]).WriteTo(w)
|
|
||||||
n += nn
|
|
||||||
if err != nil {
|
|
||||||
return n, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return n, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *StringIdentifierVarIntArray) ReadFrom(r io.Reader) (n int64, err error) {
|
|
||||||
var size packet.VarInt
|
|
||||||
nn, err := size.ReadFrom(r)
|
|
||||||
n += nn
|
|
||||||
if err != nil {
|
|
||||||
return n, err
|
|
||||||
}
|
|
||||||
if size < 0 {
|
|
||||||
return n, errors.New("array length less than zero")
|
|
||||||
}
|
|
||||||
|
|
||||||
if cap(*a) >= int(size) {
|
|
||||||
*a = (*a)[:int(size)]
|
|
||||||
} else {
|
|
||||||
*a = make(StringIdentifierVarIntArray, int(size))
|
|
||||||
}
|
|
||||||
|
|
||||||
for i := 0; i < int(size); i++ {
|
|
||||||
nn, err = (*packet.Identifier)(&(*a)[i]).ReadFrom(r)
|
|
||||||
n += nn
|
|
||||||
if err != nil {
|
|
||||||
return n, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return n, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Int32VarIntVarIntArray a utility type for encoding/decoding packet.VarInt -> int32[packet.VarInt] slice.
|
|
||||||
type Int32VarIntVarIntArray []int32
|
|
||||||
|
|
||||||
func (a Int32VarIntVarIntArray) WriteTo(w io.Writer) (n int64, err error) {
|
|
||||||
size := len(a)
|
|
||||||
nn, err := packet.VarInt(size).WriteTo(w)
|
|
||||||
if err != nil {
|
|
||||||
return n, err
|
|
||||||
}
|
|
||||||
n += nn
|
|
||||||
for i := 0; i < size; i++ {
|
|
||||||
nn, err := packet.VarInt(a[i]).WriteTo(w)
|
|
||||||
n += nn
|
|
||||||
if err != nil {
|
|
||||||
return n, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return n, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *Int32VarIntVarIntArray) ReadFrom(r io.Reader) (n int64, err error) {
|
|
||||||
var size packet.VarInt
|
|
||||||
nn, err := size.ReadFrom(r)
|
|
||||||
n += nn
|
|
||||||
if err != nil {
|
|
||||||
return n, err
|
|
||||||
}
|
|
||||||
if size < 0 {
|
|
||||||
return n, errors.New("array length less than zero")
|
|
||||||
}
|
|
||||||
|
|
||||||
if cap(*a) >= int(size) {
|
|
||||||
*a = (*a)[:int(size)]
|
|
||||||
} else {
|
|
||||||
*a = make(Int32VarIntVarIntArray, int(size))
|
|
||||||
}
|
|
||||||
|
|
||||||
for i := 0; i < int(size); i++ {
|
|
||||||
nn, err = (*packet.VarInt)(&(*a)[i]).ReadFrom(r)
|
|
||||||
n += nn
|
|
||||||
if err != nil {
|
|
||||||
return n, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return n, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// StringVarIntArray a utility type for encoding/decoding packet.String -> string[packet.VarInt] slice.
|
// StringVarIntArray a utility type for encoding/decoding packet.String -> string[packet.VarInt] slice.
|
||||||
type StringVarIntArray []string
|
type StringVarIntArray []string
|
||||||
|
|
||||||
@ -7349,6 +7186,54 @@ func (a *UuidUUIDUUIDVarIntArray) ReadFrom(r io.Reader) (n int64, err error) {
|
|||||||
return n, err
|
return n, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Int32VarIntVarIntArray a utility type for encoding/decoding packet.VarInt -> int32[packet.VarInt] slice.
|
||||||
|
type Int32VarIntVarIntArray []int32
|
||||||
|
|
||||||
|
func (a Int32VarIntVarIntArray) WriteTo(w io.Writer) (n int64, err error) {
|
||||||
|
size := len(a)
|
||||||
|
nn, err := packet.VarInt(size).WriteTo(w)
|
||||||
|
if err != nil {
|
||||||
|
return n, err
|
||||||
|
}
|
||||||
|
n += nn
|
||||||
|
for i := 0; i < size; i++ {
|
||||||
|
nn, err := packet.VarInt(a[i]).WriteTo(w)
|
||||||
|
n += nn
|
||||||
|
if err != nil {
|
||||||
|
return n, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return n, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *Int32VarIntVarIntArray) ReadFrom(r io.Reader) (n int64, err error) {
|
||||||
|
var size packet.VarInt
|
||||||
|
nn, err := size.ReadFrom(r)
|
||||||
|
n += nn
|
||||||
|
if err != nil {
|
||||||
|
return n, err
|
||||||
|
}
|
||||||
|
if size < 0 {
|
||||||
|
return n, errors.New("array length less than zero")
|
||||||
|
}
|
||||||
|
|
||||||
|
if cap(*a) >= int(size) {
|
||||||
|
*a = (*a)[:int(size)]
|
||||||
|
} else {
|
||||||
|
*a = make(Int32VarIntVarIntArray, int(size))
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := 0; i < int(size); i++ {
|
||||||
|
nn, err = (*packet.VarInt)(&(*a)[i]).ReadFrom(r)
|
||||||
|
n += nn
|
||||||
|
if err != nil {
|
||||||
|
return n, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return n, err
|
||||||
|
}
|
||||||
|
|
||||||
// Int64VarLongVarIntArray a utility type for encoding/decoding packet.VarLong -> int64[packet.VarInt] slice.
|
// Int64VarLongVarIntArray a utility type for encoding/decoding packet.VarLong -> int64[packet.VarInt] slice.
|
||||||
type Int64VarLongVarIntArray []int64
|
type Int64VarLongVarIntArray []int64
|
||||||
|
|
||||||
@ -7397,102 +7282,6 @@ func (a *Int64VarLongVarIntArray) ReadFrom(r io.Reader) (n int64, err error) {
|
|||||||
return n, err
|
return n, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Int8ByteVarIntArray a utility type for encoding/decoding packet.Byte -> int8[packet.VarInt] slice.
|
|
||||||
type Int8ByteVarIntArray []int8
|
|
||||||
|
|
||||||
func (a Int8ByteVarIntArray) WriteTo(w io.Writer) (n int64, err error) {
|
|
||||||
size := len(a)
|
|
||||||
nn, err := packet.VarInt(size).WriteTo(w)
|
|
||||||
if err != nil {
|
|
||||||
return n, err
|
|
||||||
}
|
|
||||||
n += nn
|
|
||||||
for i := 0; i < size; i++ {
|
|
||||||
nn, err := packet.Byte(a[i]).WriteTo(w)
|
|
||||||
n += nn
|
|
||||||
if err != nil {
|
|
||||||
return n, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return n, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *Int8ByteVarIntArray) ReadFrom(r io.Reader) (n int64, err error) {
|
|
||||||
var size packet.VarInt
|
|
||||||
nn, err := size.ReadFrom(r)
|
|
||||||
n += nn
|
|
||||||
if err != nil {
|
|
||||||
return n, err
|
|
||||||
}
|
|
||||||
if size < 0 {
|
|
||||||
return n, errors.New("array length less than zero")
|
|
||||||
}
|
|
||||||
|
|
||||||
if cap(*a) >= int(size) {
|
|
||||||
*a = (*a)[:int(size)]
|
|
||||||
} else {
|
|
||||||
*a = make(Int8ByteVarIntArray, int(size))
|
|
||||||
}
|
|
||||||
|
|
||||||
for i := 0; i < int(size); i++ {
|
|
||||||
nn, err = (*packet.Byte)(&(*a)[i]).ReadFrom(r)
|
|
||||||
n += nn
|
|
||||||
if err != nil {
|
|
||||||
return n, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return n, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// StringStringVarIntArray a utility type for encoding/decoding packet.String -> string[packet.VarInt] slice.
|
|
||||||
type StringStringVarIntArray []string
|
|
||||||
|
|
||||||
func (a StringStringVarIntArray) WriteTo(w io.Writer) (n int64, err error) {
|
|
||||||
size := len(a)
|
|
||||||
nn, err := packet.VarInt(size).WriteTo(w)
|
|
||||||
if err != nil {
|
|
||||||
return n, err
|
|
||||||
}
|
|
||||||
n += nn
|
|
||||||
for i := 0; i < size; i++ {
|
|
||||||
nn, err := packet.String(a[i]).WriteTo(w)
|
|
||||||
n += nn
|
|
||||||
if err != nil {
|
|
||||||
return n, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return n, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *StringStringVarIntArray) ReadFrom(r io.Reader) (n int64, err error) {
|
|
||||||
var size packet.VarInt
|
|
||||||
nn, err := size.ReadFrom(r)
|
|
||||||
n += nn
|
|
||||||
if err != nil {
|
|
||||||
return n, err
|
|
||||||
}
|
|
||||||
if size < 0 {
|
|
||||||
return n, errors.New("array length less than zero")
|
|
||||||
}
|
|
||||||
|
|
||||||
if cap(*a) >= int(size) {
|
|
||||||
*a = (*a)[:int(size)]
|
|
||||||
} else {
|
|
||||||
*a = make(StringStringVarIntArray, int(size))
|
|
||||||
}
|
|
||||||
|
|
||||||
for i := 0; i < int(size); i++ {
|
|
||||||
nn, err = (*packet.String)(&(*a)[i]).ReadFrom(r)
|
|
||||||
n += nn
|
|
||||||
if err != nil {
|
|
||||||
return n, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return n, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Int8VarIntArray a utility type for encoding/decoding packet.Byte -> int8[packet.VarInt] slice.
|
// Int8VarIntArray a utility type for encoding/decoding packet.Byte -> int8[packet.VarInt] slice.
|
||||||
type Int8VarIntArray []int8
|
type Int8VarIntArray []int8
|
||||||
|
|
||||||
@ -7588,3 +7377,147 @@ func (a *Int64VarIntArray) ReadFrom(r io.Reader) (n int64, err error) {
|
|||||||
|
|
||||||
return n, err
|
return n, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// StringIdentifierVarIntArray a utility type for encoding/decoding packet.Identifier -> string[packet.VarInt] slice.
|
||||||
|
type StringIdentifierVarIntArray []string
|
||||||
|
|
||||||
|
func (a StringIdentifierVarIntArray) WriteTo(w io.Writer) (n int64, err error) {
|
||||||
|
size := len(a)
|
||||||
|
nn, err := packet.VarInt(size).WriteTo(w)
|
||||||
|
if err != nil {
|
||||||
|
return n, err
|
||||||
|
}
|
||||||
|
n += nn
|
||||||
|
for i := 0; i < size; i++ {
|
||||||
|
nn, err := packet.Identifier(a[i]).WriteTo(w)
|
||||||
|
n += nn
|
||||||
|
if err != nil {
|
||||||
|
return n, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return n, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *StringIdentifierVarIntArray) ReadFrom(r io.Reader) (n int64, err error) {
|
||||||
|
var size packet.VarInt
|
||||||
|
nn, err := size.ReadFrom(r)
|
||||||
|
n += nn
|
||||||
|
if err != nil {
|
||||||
|
return n, err
|
||||||
|
}
|
||||||
|
if size < 0 {
|
||||||
|
return n, errors.New("array length less than zero")
|
||||||
|
}
|
||||||
|
|
||||||
|
if cap(*a) >= int(size) {
|
||||||
|
*a = (*a)[:int(size)]
|
||||||
|
} else {
|
||||||
|
*a = make(StringIdentifierVarIntArray, int(size))
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := 0; i < int(size); i++ {
|
||||||
|
nn, err = (*packet.Identifier)(&(*a)[i]).ReadFrom(r)
|
||||||
|
n += nn
|
||||||
|
if err != nil {
|
||||||
|
return n, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return n, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Int8ByteVarIntArray a utility type for encoding/decoding packet.Byte -> int8[packet.VarInt] slice.
|
||||||
|
type Int8ByteVarIntArray []int8
|
||||||
|
|
||||||
|
func (a Int8ByteVarIntArray) WriteTo(w io.Writer) (n int64, err error) {
|
||||||
|
size := len(a)
|
||||||
|
nn, err := packet.VarInt(size).WriteTo(w)
|
||||||
|
if err != nil {
|
||||||
|
return n, err
|
||||||
|
}
|
||||||
|
n += nn
|
||||||
|
for i := 0; i < size; i++ {
|
||||||
|
nn, err := packet.Byte(a[i]).WriteTo(w)
|
||||||
|
n += nn
|
||||||
|
if err != nil {
|
||||||
|
return n, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return n, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *Int8ByteVarIntArray) ReadFrom(r io.Reader) (n int64, err error) {
|
||||||
|
var size packet.VarInt
|
||||||
|
nn, err := size.ReadFrom(r)
|
||||||
|
n += nn
|
||||||
|
if err != nil {
|
||||||
|
return n, err
|
||||||
|
}
|
||||||
|
if size < 0 {
|
||||||
|
return n, errors.New("array length less than zero")
|
||||||
|
}
|
||||||
|
|
||||||
|
if cap(*a) >= int(size) {
|
||||||
|
*a = (*a)[:int(size)]
|
||||||
|
} else {
|
||||||
|
*a = make(Int8ByteVarIntArray, int(size))
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := 0; i < int(size); i++ {
|
||||||
|
nn, err = (*packet.Byte)(&(*a)[i]).ReadFrom(r)
|
||||||
|
n += nn
|
||||||
|
if err != nil {
|
||||||
|
return n, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return n, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// StringStringVarIntArray a utility type for encoding/decoding packet.String -> string[packet.VarInt] slice.
|
||||||
|
type StringStringVarIntArray []string
|
||||||
|
|
||||||
|
func (a StringStringVarIntArray) WriteTo(w io.Writer) (n int64, err error) {
|
||||||
|
size := len(a)
|
||||||
|
nn, err := packet.VarInt(size).WriteTo(w)
|
||||||
|
if err != nil {
|
||||||
|
return n, err
|
||||||
|
}
|
||||||
|
n += nn
|
||||||
|
for i := 0; i < size; i++ {
|
||||||
|
nn, err := packet.String(a[i]).WriteTo(w)
|
||||||
|
n += nn
|
||||||
|
if err != nil {
|
||||||
|
return n, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return n, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *StringStringVarIntArray) ReadFrom(r io.Reader) (n int64, err error) {
|
||||||
|
var size packet.VarInt
|
||||||
|
nn, err := size.ReadFrom(r)
|
||||||
|
n += nn
|
||||||
|
if err != nil {
|
||||||
|
return n, err
|
||||||
|
}
|
||||||
|
if size < 0 {
|
||||||
|
return n, errors.New("array length less than zero")
|
||||||
|
}
|
||||||
|
|
||||||
|
if cap(*a) >= int(size) {
|
||||||
|
*a = (*a)[:int(size)]
|
||||||
|
} else {
|
||||||
|
*a = make(StringStringVarIntArray, int(size))
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := 0; i < int(size); i++ {
|
||||||
|
nn, err = (*packet.String)(&(*a)[i]).ReadFrom(r)
|
||||||
|
n += nn
|
||||||
|
if err != nil {
|
||||||
|
return n, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return n, err
|
||||||
|
}
|
||||||
|
@ -2,18 +2,14 @@ package client
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/Tnze/go-mc/data/packetid"
|
"github.com/Tnze/go-mc/data/packetid"
|
||||||
|
"github.com/Tnze/go-mc/level"
|
||||||
)
|
)
|
||||||
|
|
||||||
var _ ClientboundPacket = (*ForgetLevelChunk)(nil)
|
var _ ClientboundPacket = (*ForgetLevelChunk)(nil)
|
||||||
|
|
||||||
//codec:gen
|
|
||||||
type ChunkPos struct {
|
|
||||||
X, Z int32
|
|
||||||
}
|
|
||||||
|
|
||||||
//codec:gen
|
//codec:gen
|
||||||
type ForgetLevelChunk struct {
|
type ForgetLevelChunk struct {
|
||||||
Pos ChunkPos
|
Pos level.ChunkPos
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ForgetLevelChunk) ClientboundPacketID() packetid.ClientboundPacketID {
|
func (ForgetLevelChunk) ClientboundPacketID() packetid.ClientboundPacketID {
|
||||||
|
@ -1,12 +1,45 @@
|
|||||||
package client
|
package client
|
||||||
|
|
||||||
import "github.com/Tnze/go-mc/level"
|
import (
|
||||||
|
"io"
|
||||||
|
|
||||||
|
"github.com/Tnze/go-mc/level"
|
||||||
|
)
|
||||||
|
|
||||||
var _ ClientboundPacket = (*LevelChunkWithLight)(nil)
|
var _ ClientboundPacket = (*LevelChunkWithLight)(nil)
|
||||||
|
|
||||||
//codec:gen
|
|
||||||
type LevelChunkWithLight struct {
|
type LevelChunkWithLight struct {
|
||||||
X int32
|
Pos level.ChunkPos
|
||||||
Z int32
|
Data *level.Chunk
|
||||||
Data level.Chunk
|
}
|
||||||
|
|
||||||
|
func (c *LevelChunkWithLight) ReadFrom(r io.Reader) (n int64, err error) {
|
||||||
|
temp, err := c.Pos.ReadFrom(r)
|
||||||
|
if err != nil {
|
||||||
|
return temp, err
|
||||||
|
}
|
||||||
|
c.Data = level.EmptyChunk(36)
|
||||||
|
|
||||||
|
temp, err = (c.Data).ReadFrom(r)
|
||||||
|
n += temp
|
||||||
|
if err != nil {
|
||||||
|
return n, err
|
||||||
|
}
|
||||||
|
return n, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c LevelChunkWithLight) WriteTo(w io.Writer) (n int64, err error) {
|
||||||
|
var temp int64
|
||||||
|
temp, err = c.Pos.WriteTo(w)
|
||||||
|
n += temp
|
||||||
|
if err != nil {
|
||||||
|
return n, err
|
||||||
|
}
|
||||||
|
|
||||||
|
temp, err = (*level.Chunk)(c.Data).WriteTo(w)
|
||||||
|
n += temp
|
||||||
|
if err != nil {
|
||||||
|
return n, err
|
||||||
|
}
|
||||||
|
return n, err
|
||||||
}
|
}
|
||||||
|
51
pkg/protocol/position.go
Normal file
51
pkg/protocol/position.go
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
package protocol
|
||||||
|
|
||||||
|
import "math"
|
||||||
|
|
||||||
|
type Position [3]int32
|
||||||
|
|
||||||
|
func (p Position) DistanceTo(other Position) float64 {
|
||||||
|
dx := float64(p[0] - other[0])
|
||||||
|
dy := float64(p[1] - other[1])
|
||||||
|
dz := float64(p[2] - other[2])
|
||||||
|
return math.Sqrt(dx*dx + dy*dy + dz*dz)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p Position) DistanceToSquared(other Position) float64 {
|
||||||
|
dx := float64(p[0] - other[0])
|
||||||
|
dy := float64(p[1] - other[1])
|
||||||
|
dz := float64(p[2] - other[2])
|
||||||
|
return dx*dx + dy*dy + dz*dz
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p Position) Add(other Position) Position {
|
||||||
|
return Position{p[0] + other[0], p[1] + other[1], p[2] + other[2]}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p Position) Sub(other Position) Position {
|
||||||
|
return Position{p[0] - other[0], p[1] - other[1], p[2] - other[2]}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p Position) Mul(scalar float64) Position {
|
||||||
|
return Position{int32(float64(p[0]) * scalar), int32(float64(p[1]) * scalar), int32(float64(p[2]) * scalar)}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p Position) Div(scalar float64) Position {
|
||||||
|
return Position{int32(float64(p[0]) / scalar), int32(float64(p[1]) / scalar), int32(float64(p[2]) / scalar)}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p Position) IsZero() bool {
|
||||||
|
return p[0] == 0 && p[1] == 0 && p[2] == 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p Position) Clone() Position {
|
||||||
|
return Position{p[0], p[1], p[2]}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p Position) String() string {
|
||||||
|
return "(" + string(p[0]) + ", " + string(p[1]) + ", " + string(p[2]) + ")"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p Position) Equals(other Position) bool {
|
||||||
|
return p[0] == other[0] && p[1] == other[1] && p[2] == other[2]
|
||||||
|
}
|
@ -3,12 +3,13 @@ package slot
|
|||||||
import (
|
import (
|
||||||
"io"
|
"io"
|
||||||
|
|
||||||
|
"github.com/Tnze/go-mc/level/item"
|
||||||
pk "github.com/Tnze/go-mc/net/packet"
|
pk "github.com/Tnze/go-mc/net/packet"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Slot struct {
|
type Slot struct {
|
||||||
Count int32
|
Count int32
|
||||||
ItemID int32
|
ItemID item.ID
|
||||||
AddComponent []Component
|
AddComponent []Component
|
||||||
RemoveComponent []ComponentID
|
RemoveComponent []ComponentID
|
||||||
}
|
}
|
||||||
@ -64,12 +65,16 @@ func (s *Slot) ReadFrom(r io.Reader) (n int64, err error) {
|
|||||||
return temp, err
|
return temp, err
|
||||||
}
|
}
|
||||||
n += temp
|
n += temp
|
||||||
temp, err = (*pk.VarInt)(&s.ItemID).ReadFrom(r)
|
|
||||||
|
var itemID int32
|
||||||
|
temp, err = (*pk.VarInt)(&itemID).ReadFrom(r)
|
||||||
n += temp
|
n += temp
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return temp, err
|
return temp, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
s.ItemID = item.ID(itemID)
|
||||||
|
|
||||||
addLens := int32(0)
|
addLens := int32(0)
|
||||||
temp, err = (*pk.VarInt)(&addLens).ReadFrom(r)
|
temp, err = (*pk.VarInt)(&addLens).ReadFrom(r)
|
||||||
n += temp
|
n += temp
|
||||||
|
118
readme.md
118
readme.md
@ -1,3 +1,117 @@
|
|||||||
## 說明
|
# minego
|
||||||
|
|
||||||
Minecraft 1.21.6 go-mc server and client
|
go-mc with command-line-only bot client.
|
||||||
|
|
||||||
|
# 建議目錄結構
|
||||||
|
|
||||||
|
```
|
||||||
|
minego/
|
||||||
|
├─ cmd/
|
||||||
|
│ ├─ minectl/ # 範例 CLI:連線、發包、抓封包
|
||||||
|
│ │ └─ main.go
|
||||||
|
│ └─ proxy/ # 範例:簡易協議代理/抓包器
|
||||||
|
│ └─ main.go
|
||||||
|
│
|
||||||
|
├─ pkg/ # 對外公開 API(庫)
|
||||||
|
│ ├─ client/ # 高階 Client SDK(使用者只需要這個)
|
||||||
|
│ │ ├─ client.go # Client 對外介面、New(...)、Connect(...)
|
||||||
|
│ │ ├─ options.go # 可選項:代理、壓縮、密碼學、登入方式
|
||||||
|
│ │ ├─ session.go # 與伺服器的一次連線(狀態機)
|
||||||
|
│ │ ├─ pipeline.go # 封包處理管線(decode -> route -> handler)
|
||||||
|
│ │ ├─ dispatcher.go # 事件/封包分發與訂閱
|
||||||
|
│ │ ├─ keepalive.go
|
||||||
|
│ │ ├─ reconnect.go
|
||||||
|
│ │ └─ errors.go
|
||||||
|
│ │
|
||||||
|
│ ├─ transport/ # 可替換傳輸層(TCP wrapper)
|
||||||
|
│ │ ├─ tcp/
|
||||||
|
│ │ │ └─ conn.go
|
||||||
|
│ │ └─ transport.go # 抽象介面:Dial(ctx, addr) (Conn, error)
|
||||||
|
│ │
|
||||||
|
│ ├─ auth/ # 登入/加密/密鑰交換(mojang、離線、自訂yggdrasil)
|
||||||
|
│ │ ├─ offline.go
|
||||||
|
│ │ ├─ mojang.go
|
||||||
|
│ │ └─ encrypt.go
|
||||||
|
│ │
|
||||||
|
│ ├─ handler/ # 封包與事件處理(基於協議的 client 方向)
|
||||||
|
│ │ ├─ login.go
|
||||||
|
│ │ ├─ play_entities.go
|
||||||
|
│ │ ├─ play_world.go
|
||||||
|
│ │ ├─ chat.go
|
||||||
|
│ │ └─ registry.go # 封包 -> handler 的綁定註冊
|
||||||
|
│ │
|
||||||
|
│ ├─ game/ # 遊戲狀態(抽象,不與 GUI 綁死)
|
||||||
|
│ │ ├─ world/
|
||||||
|
│ │ │ ├─ chunk.go
|
||||||
|
│ │ │ ├─ palette.go
|
||||||
|
│ │ │ └─ biome.go
|
||||||
|
│ │ ├─ entity/
|
||||||
|
│ │ │ └─ entity.go
|
||||||
|
│ │ └─ inventory/
|
||||||
|
│ │ └─ slots.go
|
||||||
|
│ │
|
||||||
|
│ ├─ data/ # 協議資料&對照(版本表、映射、assets)
|
||||||
|
│ │ ├─ versions/
|
||||||
|
│ │ │ └─ 1_21.json
|
||||||
|
│ │ └─ registries/
|
||||||
|
│ │ └─ packets.json
|
||||||
|
│ │
|
||||||
|
│ ├─ protocol/ # 你現有的 codec/packet/metadata 可移到這
|
||||||
|
│ │ ├─ codec/…
|
||||||
|
│ │ ├─ packet/…
|
||||||
|
│ │ └─ nbt/…
|
||||||
|
│ │
|
||||||
|
│ └─ util/ # 小工具:varint、zlib、pool、log
|
||||||
|
│ └─ …
|
||||||
|
│
|
||||||
|
└─ go.mod
|
||||||
|
```
|
||||||
|
|
||||||
|
> 原則:
|
||||||
|
>
|
||||||
|
> * `pkg/` 對外公開、穩定 API;`internal/` 只給本專案使用。
|
||||||
|
> * `protocol` 保持「與傳輸無關」;`transport` 抽象連線;`client` 串起狀態機與 handler。
|
||||||
|
> * `handler` 專心處理「已解碼封包」到「遊戲狀態/事件」的映射。
|
||||||
|
> * `game` 做資料模型(世界、實體、物品),不要直接依賴 UI。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# 模組邏輯切分(設計重點)
|
||||||
|
|
||||||
|
1. **狀態機(State Machine)**
|
||||||
|
|
||||||
|
* `Handshake -> Status/Login -> Play -> (Disconnected)`
|
||||||
|
* 在 `session.go` 以 goroutine + channel 管理讀寫,使用 `context.Context` 控制生命週期。
|
||||||
|
2. **封包管線(Pipeline)**
|
||||||
|
|
||||||
|
* `reader` 取得原始 bytes → `protocol/codec` 解碼 → `dispatcher` 依封包 ID 分派到 handler。
|
||||||
|
* 可在管線節點插中介:壓縮、加密、記錄、度量。
|
||||||
|
3. **事件導向 API**
|
||||||
|
|
||||||
|
* 對外提供:
|
||||||
|
|
||||||
|
```go
|
||||||
|
type Client interface {
|
||||||
|
On(event Event, fn any) Unsub
|
||||||
|
Send(ctx context.Context, p protocol.Packet) error
|
||||||
|
State() client.State
|
||||||
|
}
|
||||||
|
```
|
||||||
|
* `On(PacketPlayChat, func(*ChatMessage){…})` 這種型別安全的註冊可以用泛型或介面實作。
|
||||||
|
4. **錯誤與可觀測性**
|
||||||
|
|
||||||
|
* 統一 `errors.go`;在 pipeline/handler 位置加可選的 `WithLogger / WithMetrics`。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# 命名與風格小訣竅
|
||||||
|
|
||||||
|
* 套件名短小、名詞為主:`client`, `auth`, `transport`, `handler`, `game`, `protocol`.
|
||||||
|
* 檔名以職責分組,不以每個封包獨立檔案(容易爆量難找)。
|
||||||
|
* 對外只匯出 `pkg/client` 的型別;其餘盡量小寫封裝。
|
||||||
|
* 盡量用 `context.Context`、`io.Reader/Writer` 介面做邊界。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
> MCProtocol(新wiki.vg) https://minecraft.wiki/w/Java_Edition_protocol
|
||||||
|
> 使用套件: github.com/Tnze/go-mc
|
Reference in New Issue
Block a user